Source code for cideMOD.simulation_interface.inputs
#
# Copyright (c) 2023 CIDETEC Energy Storage.
#
# This file is part of cideMOD.
#
# cideMOD is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from typing import List
from cideMOD.helpers.logging import VerbosityLevel, _print
from cideMOD.helpers.miscellaneous import format_time
from cideMOD.numerics.triggers import SolverCrashed, Trigger, TriggerDetected, TriggerSurpassed
from cideMOD.main import Problem
[docs]
def execute_step(step, problem: Problem):
status = step.execute(problem)
if isinstance(status, (TriggerDetected, TriggerSurpassed)):
if status.action().upper() == 'NEXT':
return 0
elif status.action().upper() == 'CV':
capacity = problem.cell_parser.ref_capacity
trig = Trigger(value=capacity / 20, variable='i', atol=capacity / 200)
inpt = VoltageInput(name='CV', v_app=status.trigger.trigger_value,
min_step=step.min_step, t_max=step.t_max,
store_delay=step.store_delay)
inpt.add_trigger(trig)
status = execute_step(inpt, problem)
return status
elif status.action().upper() in ('END', 'END CYCLE'):
return status
else:
available_actions = ['Next', 'CV', 'End', 'End Cycle']
raise ValueError(f"Unrecognized trigger action '{status.action()}'. "
+ "Available options: '" + "' '".join(available_actions) + "'")
return status
[docs]
class Input:
def __init__(self):
self.i_app = None
self.v_app = None
self.triggers: List[Trigger] = []
self.t_max = None
self.max_step = None
self.min_step = None
self.initial_step = None
self.store_delay = None
self.adaptive = None
self.time_adaptive_tol = None
[docs]
def execute(self, problem: Problem):
if problem.verbose >= VerbosityLevel.BASIC_PROGRESS_INFO:
_print(str(self), comm=problem._comm)
max_t = problem.time + self.t_max
status = problem.solve(
i_app=self.i_app, v_app=self.v_app, t_f=max_t, initial_step=self.initial_step,
max_step=self.max_step, min_step=self.min_step, triggers=self.triggers,
store_delay=self.store_delay, adaptive=self.adaptive,
time_adaptive_tol=self.time_adaptive_tol)
# status can be 0, TriggerDetected or SolverCrashed
# - 0 means final time reached
# - TriggerDetected has the trigger attribute
# - SolverCrashed means it didn't run well
return status
[docs]
def add_trigger(self, new_trigger: Trigger):
# TODO: Add a check to ensure there are no overlapping triggers
self.triggers.append(new_trigger)
[docs]
class Cycle:
def __init__(self, name: str, count: int):
self.name = name
self.count = count
self.steps = []
self.triggers = []
[docs]
def add_trigger(self, trigger: Trigger):
self.triggers.append(trigger)
for step in self.steps:
step.add_trigger(trigger)
[docs]
def execute(self, problem: Problem):
for i in range(self.count):
if problem.verbose >= VerbosityLevel.BASIC_PROGRESS_INFO:
_print(f"-- Cycle '{self.name}', iteration number {i} --", comm=problem._comm)
for step in self.steps:
status = execute_step(step, problem)
if isinstance(status, TriggerDetected):
if status.action() == 'End Cycle':
return 0
elif isinstance(status, SolverCrashed):
return status
def __str__(self) -> str:
string = f"""Cycle '{self.name}' repeats {self.count} times:\n"""
for i, step in enumerate(self.steps):
string += f"\t {i} - {str(step)}\n"
return string[:-1]
[docs]
class CurrentInput(Input):
def __init__(self, name, i_app, t_max, store_delay=10, initial_step=None,
max_step=3600, min_step=5, adaptive=True, time_adaptive_tol=1e-2):
super().__init__()
self.i_app = i_app
self._i_app = i_app
self.t_max = t_max
self.name = f'CC_{name}'
self.store_delay = store_delay
self.initial_step = initial_step
self.max_step = max_step
self.min_step = min_step
self.adaptive = adaptive
self.time_adaptive_tol = time_adaptive_tol
[docs]
def execute(self, problem: Problem):
if self._i_app is None:
self.i_app = problem._WH.get_global_variable_value('current')
return super().execute(problem)
def __repr__(self):
return (f"CurrentInput(name={self.name}, i_app={self.i_app}, "
+ f"t_max={self.t_max}, triggers={self.triggers})")
def __str__(self):
return (f"{self.name}: Apply {self.i_app} A during "
+ f"{format_time(self.t_max, 0)} until {self.triggers}")
[docs]
class VoltageInput(Input):
def __init__(self, name, v_app, t_max, store_delay=10, initial_step=None,
max_step=3600, min_step=5, adaptive=True, time_adaptive_tol=1e-2):
super().__init__()
self.v_app = v_app
self._v_app = v_app
self.t_max = t_max
self.name = f'CV_{name}'
self.store_delay = store_delay
self.initial_step = initial_step
self.max_step = max_step
self.min_step = min_step
self.adaptive = adaptive
self.time_adaptive_tol = time_adaptive_tol
[docs]
def execute(self, problem: Problem):
if self._v_app is None:
self.v_app = problem._WH.get_global_variable_value('voltage')
return super().execute(problem)
def __repr__(self):
return (f"VoltageInput(name={self.name}, v_app={self.v_app}, "
+ f"t_max={self.t_max}, triggers={self.triggers})")
def __str__(self):
return (f"{self.name}: Apply {self.v_app} V during "
+ f"{format_time(self.t_max, 0)} until {self.triggers}")
[docs]
class Rest(Input):
def __init__(self, name, t_max, store_delay=100, initial_step=None,
max_step=3600, min_step=5, adaptive=True, time_adaptive_tol=1e-2):
super().__init__()
self.i_app = 0
self.t_max = t_max
self.name = str(name)
self.store_delay = store_delay
self.initial_step = initial_step
self.max_step = max_step
self.min_step = min_step
self.adaptive = adaptive
self.time_adaptive_tol = time_adaptive_tol
def __repr__(self):
return f"Rest(name={self.name}, t_max={self.t_max})"
def __str__(self):
return f"{self.name}: Rest during {format_time(self.t_max, 0)}"