Skip to content

Commit 593d2aa

Browse files
Update C-Rate current for changing nominal capacity (#5286)
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 73ab559 commit 593d2aa

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

src/pybamm/simulation.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def __init__(
128128
self._model_with_set_params = None
129129
self._built_model = None
130130
self._built_initial_soc = None
131+
self._built_nominal_capacity = None
131132
self.steps_to_built_models = None
132133
self.steps_to_built_solvers = None
133134
self._mesh = None
@@ -163,6 +164,42 @@ def set_up_and_parameterise_experiment(self, solve_kwargs=None):
163164
warnings.warn(msg, DeprecationWarning, stacklevel=2)
164165
self._set_up_and_parameterise_experiment(solve_kwargs=solve_kwargs)
165166

167+
def _update_experiment_models_for_capacity(self, solve_kwargs=None):
168+
"""
169+
Check if the nominal capacity has changed and update the experiment models
170+
if needed. This re-processes the models without rebuilding the mesh and
171+
discretisation.
172+
"""
173+
current_capacity = self._parameter_values.get(
174+
"Nominal cell capacity [A.h]", None
175+
)
176+
177+
if self._built_nominal_capacity == current_capacity:
178+
return
179+
180+
# Capacity has changed, need to re-process the models
181+
pybamm.logger.info(
182+
f"Nominal capacity changed from {self._built_nominal_capacity} to "
183+
f"{current_capacity}. Re-processing experiment models."
184+
)
185+
186+
# Re-parameterise the experiment with the new capacity
187+
self._set_up_and_parameterise_experiment(solve_kwargs)
188+
189+
# Re-discretise the models
190+
self.steps_to_built_models = {}
191+
self.steps_to_built_solvers = {}
192+
for (
193+
step,
194+
model_with_set_params,
195+
) in self.experiment_unique_steps_to_model.items():
196+
built_model = self._disc.process_model(model_with_set_params, inplace=True)
197+
solver = self._solver.copy()
198+
self.steps_to_built_solvers[step] = solver
199+
self.steps_to_built_models[step] = built_model
200+
201+
self._built_nominal_capacity = current_capacity
202+
166203
def _set_up_and_parameterise_experiment(self, solve_kwargs=None):
167204
"""
168205
Create and parameterise the models for each step in the experiment.
@@ -266,6 +303,7 @@ def set_initial_state(self, initial_soc, direction=None, inputs=None):
266303
# reset
267304
self._model_with_set_params = None
268305
self._built_model = None
306+
self._built_nominal_capacity = None
269307
self.steps_to_built_models = None
270308
self.steps_to_built_solvers = None
271309

@@ -338,6 +376,8 @@ def build_for_experiment(
338376
self.set_initial_state(initial_soc, direction=direction, inputs=inputs)
339377

340378
if self.steps_to_built_models:
379+
# Check if we need to update the models due to capacity change
380+
self._update_experiment_models_for_capacity(solve_kwargs)
341381
return
342382
else:
343383
self._set_up_and_parameterise_experiment(solve_kwargs)
@@ -366,6 +406,10 @@ def build_for_experiment(
366406
self.steps_to_built_solvers[step] = solver
367407
self.steps_to_built_models[step] = built_model
368408

409+
self._built_nominal_capacity = self._parameter_values.get(
410+
"Nominal cell capacity [A.h]", None
411+
)
412+
369413
def solve(
370414
self,
371415
t_eval=None,

tests/unit/test_experiments/test_simulation_with_experiment.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
import os
23
from datetime import datetime
34

@@ -1022,3 +1023,125 @@ def neg_stoich_cutoff(variables):
10221023

10231024
neg_stoich = sol["Negative electrode stoichiometry"].data
10241025
assert neg_stoich[-1] == pytest.approx(0.5, abs=0.0001)
1026+
1027+
def test_simulation_changing_capacity_crate_steps(self):
1028+
"""Test that C-rate steps are correctly updated when capacity changes"""
1029+
model = pybamm.lithium_ion.SPM()
1030+
experiment = pybamm.Experiment(
1031+
[
1032+
(
1033+
"Discharge at C/5 for 20 minutes",
1034+
"Discharge at C/2 for 20 minutes",
1035+
"Discharge at 1C for 20 minutes",
1036+
)
1037+
]
1038+
)
1039+
param = pybamm.ParameterValues("Chen2020")
1040+
sim = pybamm.Simulation(model, experiment=experiment, parameter_values=param)
1041+
1042+
# First solve
1043+
sol1 = sim.solve(calc_esoh=False)
1044+
original_capacity = param["Nominal cell capacity [A.h]"]
1045+
1046+
# Check that C-rates correspond to expected currents
1047+
I_C5_1 = np.abs(sol1.cycles[0].steps[0]["Current [A]"].data).mean()
1048+
I_C2_1 = np.abs(sol1.cycles[0].steps[1]["Current [A]"].data).mean()
1049+
I_1C_1 = np.abs(sol1.cycles[0].steps[2]["Current [A]"].data).mean()
1050+
1051+
np.testing.assert_allclose(I_C5_1, original_capacity / 5, rtol=1e-2)
1052+
np.testing.assert_allclose(I_C2_1, original_capacity / 2, rtol=1e-2)
1053+
np.testing.assert_allclose(I_1C_1, original_capacity, rtol=1e-2)
1054+
1055+
# Update capacity
1056+
new_capacity = 0.9 * original_capacity
1057+
sim._parameter_values.update({"Nominal cell capacity [A.h]": new_capacity})
1058+
1059+
# Second solve with updated capacity
1060+
sol2 = sim.solve(calc_esoh=False)
1061+
1062+
# Check that C-rates now correspond to updated currents
1063+
I_C5_2 = np.abs(sol2.cycles[0].steps[0]["Current [A]"].data).mean()
1064+
I_C2_2 = np.abs(sol2.cycles[0].steps[1]["Current [A]"].data).mean()
1065+
I_1C_2 = np.abs(sol2.cycles[0].steps[2]["Current [A]"].data).mean()
1066+
1067+
np.testing.assert_allclose(I_C5_2, new_capacity / 5, rtol=1e-2)
1068+
np.testing.assert_allclose(I_C2_2, new_capacity / 2, rtol=1e-2)
1069+
np.testing.assert_allclose(I_1C_2, new_capacity, rtol=1e-2)
1070+
1071+
# Verify all currents scaled proportionally
1072+
np.testing.assert_allclose(I_C5_2 / I_C5_1, 0.9, rtol=1e-2)
1073+
np.testing.assert_allclose(I_C2_2 / I_C2_1, 0.9, rtol=1e-2)
1074+
np.testing.assert_allclose(I_1C_2 / I_1C_1, 0.9, rtol=1e-2)
1075+
1076+
def test_simulation_multiple_cycles_with_capacity_change(self):
1077+
"""Test capacity changes across multiple experiment cycles"""
1078+
model = pybamm.lithium_ion.SPM()
1079+
experiment = pybamm.Experiment(
1080+
[("Discharge at 1C for 5 minutes", "Charge at 1C for 5 minutes")] * 2
1081+
)
1082+
param = pybamm.ParameterValues("Chen2020")
1083+
sim = pybamm.Simulation(model, experiment=experiment, parameter_values=param)
1084+
1085+
# First solve
1086+
sol1 = sim.solve(calc_esoh=False)
1087+
original_capacity = param["Nominal cell capacity [A.h]"]
1088+
1089+
# Get discharge currents for both cycles
1090+
I_discharge_cycle1 = np.abs(sol1.cycles[0].steps[0]["Current [A]"].data).mean()
1091+
I_discharge_cycle2 = np.abs(sol1.cycles[1].steps[0]["Current [A]"].data).mean()
1092+
1093+
# Both cycles should use the same capacity initially
1094+
np.testing.assert_allclose(I_discharge_cycle1, original_capacity, rtol=1e-2)
1095+
np.testing.assert_allclose(I_discharge_cycle2, original_capacity, rtol=1e-2)
1096+
1097+
# Update capacity between cycles
1098+
new_capacity = 0.85 * original_capacity
1099+
sim._parameter_values.update({"Nominal cell capacity [A.h]": new_capacity})
1100+
1101+
# Solve again
1102+
sol2 = sim.solve(calc_esoh=False)
1103+
1104+
# All cycles in the new solution should use updated capacity
1105+
I_discharge_cycle1_new = np.abs(
1106+
sol2.cycles[0].steps[0]["Current [A]"].data
1107+
).mean()
1108+
I_discharge_cycle2_new = np.abs(
1109+
sol2.cycles[1].steps[0]["Current [A]"].data
1110+
).mean()
1111+
1112+
np.testing.assert_allclose(I_discharge_cycle1_new, new_capacity, rtol=1e-2)
1113+
np.testing.assert_allclose(I_discharge_cycle2_new, new_capacity, rtol=1e-2)
1114+
1115+
def test_simulation_logging_with_capacity_change(self, caplog):
1116+
"""Test that capacity changes are logged appropriately"""
1117+
model = pybamm.lithium_ion.SPM()
1118+
experiment = pybamm.Experiment([("Discharge at 1C for 10 minutes",)])
1119+
param = pybamm.ParameterValues("Chen2020")
1120+
sim = pybamm.Simulation(model, experiment=experiment, parameter_values=param)
1121+
1122+
# First solve
1123+
sim.solve(calc_esoh=False)
1124+
original_capacity = param["Nominal cell capacity [A.h]"]
1125+
1126+
# Update capacity
1127+
new_capacity = 0.75 * original_capacity
1128+
sim._parameter_values.update({"Nominal cell capacity [A.h]": new_capacity})
1129+
1130+
# Set logging level to capture INFO messages
1131+
original_log_level = pybamm.logger.level
1132+
pybamm.set_logging_level("INFO")
1133+
1134+
try:
1135+
# Second solve should log capacity change
1136+
with caplog.at_level(logging.INFO, logger="pybamm.logger"):
1137+
sim.solve(calc_esoh=False)
1138+
1139+
# Check that a log message about capacity change was recorded
1140+
log_messages = [record.message for record in caplog.records]
1141+
capacity_change_logged = any(
1142+
"Nominal capacity changed" in msg for msg in log_messages
1143+
)
1144+
assert capacity_change_logged
1145+
finally:
1146+
# Restore original logging level
1147+
pybamm.logger.setLevel(original_log_level)

0 commit comments

Comments
 (0)