Skip to content

Commit 4675763

Browse files
authored
add operator to experiment terminations (#4770)
add operator to experiment terminations
1 parent 9338b4e commit 4675763

File tree

6 files changed

+102
-17
lines changed

6 files changed

+102
-17
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# [Unreleased](https://github.com/pybamm-team/PyBaMM/)
22

3+
## Features
4+
- Added Operators to current and voltage termination events. ([#4770](https://github.com/pybamm-team/PyBaMM/pull/4770))
5+
36
# [v25.1.0](https://github.com/pybamm-team/PyBaMM/tree/v25.1.0) - 2025-01-14
47

58
## Features
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from .steps import *
22
from .base_step import BaseStep, BaseStepExplicit, BaseStepImplicit
3-
from .step_termination import *
3+
from .step_termination import BaseTermination, CurrentTermination, VoltageTermination, CustomTermination, CrateTermination, _read_termination
44

55
__all__ = ['base_step', 'step_termination', 'steps']

src/pybamm/experiment/step/base_step.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,14 @@ def __init__(
6767
tags=None,
6868
start_time=None,
6969
description=None,
70-
direction=None,
70+
direction: str | None = None,
7171
):
72+
potential_directions = ["charge", "discharge", "rest", None]
73+
if direction not in potential_directions:
74+
raise ValueError(
75+
f"Invalid direction: {direction}. Must be one of {potential_directions}"
76+
)
77+
self.input_duration = duration
7278
self.input_duration = duration
7379
self.input_value = value
7480
# Check if drive cycle
@@ -386,11 +392,11 @@ def value_based_charge_or_discharge(self):
386392
init_curr = self.value
387393
sign = np.sign(init_curr)
388394
if sign == 0:
389-
return "Rest"
395+
return "rest"
390396
elif sign > 0:
391-
return "Discharge"
397+
return "discharge"
392398
else:
393-
return "Charge"
399+
return "charge"
394400

395401
def record_tags(
396402
self,

src/pybamm/experiment/step/step_termination.py

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ class BaseTermination:
1414
The value at which the event is triggered
1515
"""
1616

17-
def __init__(self, value):
17+
def __init__(self, value, operator=None):
1818
self.value = value
19+
if operator not in ["<", ">", None]:
20+
raise ValueError(f"Invalid operator: {operator}")
21+
self.operator = operator
1922

2023
def get_event(self, variables, step):
2124
"""
@@ -67,9 +70,19 @@ def get_event(self, variables, step):
6770
"""
6871
See :meth:`BaseTermination.get_event`
6972
"""
73+
operator = self.operator
74+
if operator == ">":
75+
expr = self.value - variables["Current [A]"]
76+
event_string = f"Current [A] > {self.value} [A] [experiment]"
77+
elif operator == "<":
78+
expr = variables["Current [A]"] - self.value
79+
event_string = f"Current [A] < {self.value} [A] [experiment]"
80+
else:
81+
expr = abs(variables["Current [A]"]) - self.value
82+
event_string = f"abs(Current [A]) < {self.value} [A] [experiment]"
7083
event = pybamm.Event(
71-
"Current cut-off [A] [experiment]",
72-
abs(variables["Current [A]"]) - self.value,
84+
event_string,
85+
expr,
7386
)
7487
return event
7588

@@ -89,24 +102,48 @@ def get_event(self, variables, step):
89102
# figure out whether the voltage event is greater than the starting
90103
# voltage (charge) or less (discharge) and set the sign of the
91104
# event accordingly
92-
direction = step.direction.capitalize()
93-
if direction == "Charge":
105+
operator = self.operator
106+
if operator is None:
107+
direction = step.direction
108+
if direction == "charge":
109+
operator = ">"
110+
elif direction == "discharge":
111+
operator = "<"
112+
else:
113+
# No event for rest steps
114+
return None
115+
116+
if operator == ">":
94117
sign = -1
95-
elif direction == "Discharge":
118+
else:
119+
# operator can only be "<" or ">"
96120
sign = 1
97-
elif direction == "Rest":
98-
# No event for rest steps
99-
return None
100121

101122
# Event should be positive at initial conditions for both
102123
# charge and discharge
103124
event = pybamm.Event(
104-
f"{direction} voltage cut-off [V] [experiment]",
125+
f"Voltage {operator} {self.value} [V] [experiment]",
105126
sign * (variables["Battery voltage [V]"] - self.value),
106127
)
107128
return event
108129

109130

131+
class Voltage:
132+
def __gt__(self, value):
133+
return VoltageTermination(value, operator=">")
134+
135+
def __lt__(self, value):
136+
return VoltageTermination(value, operator="<")
137+
138+
139+
class Current:
140+
def __gt__(self, value):
141+
return CurrentTermination(value, operator=">")
142+
143+
def __lt__(self, value):
144+
return CurrentTermination(value, operator="<")
145+
146+
110147
class CustomTermination(BaseTermination):
111148
"""
112149
Define a custom termination event using a function. This can be used to create an

tests/unit/test_experiments/test_experiment_steps.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,3 +307,42 @@ def custom_step_voltage(variables):
307307

308308
with pytest.raises(ValueError, match="control must be"):
309309
pybamm.step.CustomStepImplicit(custom_step_voltage, control="bla")
310+
311+
def test_bad_direction(self):
312+
with pytest.raises(ValueError, match="Invalid direction"):
313+
pybamm.step.Voltage(4.1, direction="foo")
314+
315+
def test_steps_with_operators(self):
316+
# voltage
317+
step = pybamm.step.voltage(1, duration=3600)
318+
termination_lt_4_1 = pybamm.step.VoltageTermination(4.1, operator="<")
319+
termination_gt_4_1 = pybamm.step.VoltageTermination(4.1, operator=">")
320+
variables = {"Battery voltage [V]": 4.2}
321+
event_lt_4_1 = termination_lt_4_1.get_event(variables, step)
322+
np.testing.assert_allclose(event_lt_4_1.expression, 4.2 - 4.1)
323+
event_gt_4_1 = termination_gt_4_1.get_event(variables, step)
324+
np.testing.assert_allclose(event_gt_4_1.expression, 4.1 - 4.2)
325+
326+
# current
327+
step = pybamm.step.current(1, duration=3600)
328+
termination_lt_0_05 = pybamm.step.CurrentTermination(0.05, operator="<")
329+
termination_gt_0_05 = pybamm.step.CurrentTermination(0.05, operator=">")
330+
variables = {"Current [A]": 0.06}
331+
event_lt_0_05 = termination_lt_0_05.get_event(variables, step)
332+
np.testing.assert_allclose(event_lt_0_05.expression, 0.06 - 0.05)
333+
event_gt_0_05 = termination_gt_0_05.get_event(variables, step)
334+
np.testing.assert_allclose(event_gt_0_05.expression, 0.05 - 0.06)
335+
336+
# error
337+
with pytest.raises(ValueError, match="Invalid operator"):
338+
pybamm.step.CurrentTermination(0.05, operator="=")
339+
340+
# operator overloading
341+
termination_lt_0_05_oo = pybamm.step.step_termination.Current() < 0.05
342+
termination_gt_0_05_oo = pybamm.step.step_termination.Current() > 0.05
343+
assert termination_lt_0_05_oo == termination_gt_0_05
344+
assert termination_gt_0_05_oo == termination_gt_0_05
345+
termination_lt_4_1_oo = pybamm.step.step_termination.Voltage() < 4.1
346+
termination_gt_4_1_oo = pybamm.step.step_termination.Voltage() > 4.1
347+
assert termination_lt_4_1_oo == termination_gt_4_1
348+
assert termination_gt_4_1_oo == termination_gt_4_1

tests/unit/test_experiments/test_simulation_with_experiment.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ def test_set_up(self):
3333
steps[1].basic_repr()
3434
] # CC charge
3535
model_V = sim.experiment_unique_steps_to_model[steps[2].basic_repr()] # CV hold
36-
assert "Current cut-off [A] [experiment]" in [
36+
assert "abs(Current [A]) < 0.05 [A] [experiment]" in [
3737
event.name for event in model_V.events
3838
]
39-
assert "Charge voltage cut-off [V] [experiment]" in [
39+
assert "Voltage > 4.1 [V] [experiment]" in [
4040
event.name for event in model_I.events
4141
]
4242

0 commit comments

Comments
 (0)