From a99f3e5b4ce34ed1b5406b78bacb77e38cf27230 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Tue, 5 Dec 2023 19:39:24 -0500 Subject: [PATCH] #3530 add classes to handle termination --- pybamm/__init__.py | 1 + pybamm/experiment/experiment.py | 2 +- pybamm/{ => experiment}/step/__init__.py | 1 + pybamm/{ => experiment}/step/_steps_util.py | 12 ++-- pybamm/experiment/step/step_termination.py | 78 +++++++++++++++++++++ pybamm/{ => experiment}/step/steps.py | 0 pybamm/simulation.py | 53 +------------- 7 files changed, 90 insertions(+), 57 deletions(-) rename pybamm/{ => experiment}/step/__init__.py (58%) rename pybamm/{ => experiment}/step/_steps_util.py (96%) create mode 100644 pybamm/experiment/step/step_termination.py rename pybamm/{ => experiment}/step/steps.py (100%) diff --git a/pybamm/__init__.py b/pybamm/__init__.py index f9b809d121..f5c8b6fd70 100644 --- a/pybamm/__init__.py +++ b/pybamm/__init__.py @@ -228,6 +228,7 @@ # from .experiment.experiment import Experiment from . import experiment +from .experiment import step # diff --git a/pybamm/experiment/experiment.py b/pybamm/experiment/experiment.py index 9b02e3a20f..898d9b0f79 100644 --- a/pybamm/experiment/experiment.py +++ b/pybamm/experiment/experiment.py @@ -3,7 +3,7 @@ # import pybamm -from pybamm.step._steps_util import ( +from .step._steps_util import ( _convert_time_to_seconds, _convert_temperature_to_kelvin, ) diff --git a/pybamm/step/__init__.py b/pybamm/experiment/step/__init__.py similarity index 58% rename from pybamm/step/__init__.py rename to pybamm/experiment/step/__init__.py index eea47a54a7..e3b9ff8bd0 100644 --- a/pybamm/step/__init__.py +++ b/pybamm/experiment/step/__init__.py @@ -1,2 +1,3 @@ from .steps import * from .steps import _Step +from .step_termination import * diff --git a/pybamm/step/_steps_util.py b/pybamm/experiment/step/_steps_util.py similarity index 96% rename from pybamm/step/_steps_util.py rename to pybamm/experiment/step/_steps_util.py index e524bc6064..1bf98e5083 100644 --- a/pybamm/step/_steps_util.py +++ b/pybamm/experiment/step/_steps_util.py @@ -4,6 +4,7 @@ import pybamm import numpy as np from datetime import datetime +from .step_termination import read_termination _examples = """ @@ -136,8 +137,10 @@ def __init__( termination = [termination] self.termination = [] for term in termination: - typ, value = _convert_electric(term) - self.termination.append({"type": typ, "value": value}) + if isinstance(term, str): + term = _convert_electric(term) + term = read_termination(term) + self.termination.append(term) self.temperature = _convert_temperature_to_kelvin(temperature) @@ -193,10 +196,7 @@ def to_dict(self): } def __eq__(self, other): - return ( - isinstance(other, _Step) - and self.hash_args == other.hash_args - ) + return isinstance(other, _Step) and self.hash_args == other.hash_args def __hash__(self): return hash(self.basic_repr()) diff --git a/pybamm/experiment/step/step_termination.py b/pybamm/experiment/step/step_termination.py new file mode 100644 index 0000000000..0d5ce9c55f --- /dev/null +++ b/pybamm/experiment/step/step_termination.py @@ -0,0 +1,78 @@ +import pybamm +import numpy as np + + +class BaseTermination: + def __init__(self, value): + self.value = value + + def get_event(self, variables, step_value): + raise NotImplementedError + + +class CrateTermination(BaseTermination): + def get_event(self, variables, step_value): + event = pybamm.Event( + "C-rate cut-off [A] [experiment]", + abs(variables["C-rate"]) - self.value, + ) + return event + + +class CurrentTermination(BaseTermination): + def get_event(self, variables, step_value): + event = pybamm.Event( + "Current cut-off [A] [experiment]", + abs(variables["Current [A]"]) - self.value, + ) + return event + + +class VoltageTermination(BaseTermination): + def get_event(self, variables, step_value): + # The voltage event should be positive at the start of charge/ + # discharge. We use the sign of the current or power input to + # figure out whether the voltage event is greater than the starting + # voltage (charge) or less (discharge) and set the sign of the + # event accordingly + if isinstance(step_value, pybamm.Symbol): + inpt = {"start time": 0} + init_curr = step_value.evaluate(t=0, inputs=inpt).flatten()[0] + else: + init_curr = step_value + sign = np.sign(init_curr) + if sign > 0: + name = "Discharge" + else: + name = "Charge" + if sign != 0: + # Event should be positive at initial conditions for both + # charge and discharge + event = pybamm.Event( + f"{name} voltage cut-off [V] [experiment]", + sign * (variables["Battery voltage [V]"] - self.value), + ) + return event + + +class CustomTermination(BaseTermination): + def __init__(self, name, event_function): + self.name = name + self.event_function = event_function + + def get_event(self, variables, step_value): + return pybamm.Event(self.name, self.event_function(variables)) + + +def read_termination(termination): + if isinstance(termination, tuple): + typ, value = termination + else: + return termination + + termination_class = { + "current": CurrentTermination, + "voltage": VoltageTermination, + "C-rate": CrateTermination, + }[typ] + return termination_class(value) diff --git a/pybamm/step/steps.py b/pybamm/experiment/step/steps.py similarity index 100% rename from pybamm/step/steps.py rename to pybamm/experiment/step/steps.py diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 46ef3f330d..5c2cf0bff1 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -192,16 +192,6 @@ def set_up_and_parameterise_experiment(self): op_conds.type = "current" op_conds.value = op_conds.value * capacity - # Update terminations - termination = op_conds.termination - for term in termination: - term_type = term["type"] - if term_type == "C-rate": - # Change type to current - term["type"] = "current" - # Scale C-rate with capacity to obtain current - term["value"] = term["value"] * capacity - # Add time to the experiment times dt = op_conds.duration if dt is None: @@ -294,46 +284,9 @@ def set_up_and_parameterise_model_for_experiment(self): def update_new_model_events(self, new_model, op): for term in op.termination: - if term["type"] == "current": - new_model.events.append( - pybamm.Event( - "Current cut-off [A] [experiment]", - abs(new_model.variables["Current [A]"]) - term["value"], - ) - ) - - # add voltage events to the model - if term["type"] == "voltage": - # The voltage event should be positive at the start of charge/ - # discharge. We use the sign of the current or power input to - # figure out whether the voltage event is greater than the starting - # voltage (charge) or less (discharge) and set the sign of the - # event accordingly - if isinstance(op.value, pybamm.Interpolant) or isinstance( - op.value, pybamm.Multiplication - ): - inpt = {"start time": 0} - init_curr = op.value.evaluate(t=0, inputs=inpt).flatten()[0] - sign = np.sign(init_curr) - else: - sign = np.sign(op.value) - if sign > 0: - name = "Discharge" - else: - name = "Charge" - if sign != 0: - # Event should be positive at initial conditions for both - # charge and discharge - new_model.events.append( - pybamm.Event( - f"{name} voltage cut-off [V] [experiment]", - sign - * ( - new_model.variables["Battery voltage [V]"] - - term["value"] - ), - ) - ) + event = term.get_event(new_model.variables, op.value) + if event is not None: + new_model.events.append(event) # Keep the min and max voltages as safeguards but add some tolerances # so that they are not triggered before the voltage limits in the