Skip to content

Commit c7ea22f

Browse files
valentinsulzerkratman
authored andcommitted
Issue 4224 duration bug (#4239)
* make longer default duration and calculate it for C-rate * add tests * typo * #4224 add warning for time termination and add abs * fix tests * #4224 keep non-C-rate default at 24h for performance reasons * trying to fix experiment * fix example * #4224 eric comments * fix bug
1 parent 1e582fd commit c7ea22f

File tree

8 files changed

+193
-59
lines changed

8 files changed

+193
-59
lines changed

docs/source/examples/notebooks/getting_started/tutorial-5-run-experiments.ipynb

Lines changed: 89 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,18 +25,8 @@
2525
"name": "stdout",
2626
"output_type": "stream",
2727
"text": [
28-
"\n",
29-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m23.3.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.0\u001b[0m\n",
30-
"\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n",
3128
"Note: you may need to restart the kernel to use updated packages.\n"
3229
]
33-
},
34-
{
35-
"name": "stderr",
36-
"output_type": "stream",
37-
"text": [
38-
"An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.\n"
39-
]
4030
}
4131
],
4232
"source": [
@@ -163,18 +153,19 @@
163153
"name": "stderr",
164154
"output_type": "stream",
165155
"text": [
166-
"At t = 522.66 and h = 1.1556e-13, the corrector convergence failed repeatedly or with |h| = hmin.\n"
156+
"At t = 339.952 and h = 1.4337e-18, the corrector convergence failed repeatedly or with |h| = hmin.\n",
157+
"At t = 522.687 and h = 4.04917e-14, the corrector convergence failed repeatedly or with |h| = hmin.\n"
167158
]
168159
},
169160
{
170161
"data": {
171162
"application/vnd.jupyter.widget-view+json": {
172-
"model_id": "5ab1f22de6af4878b6ca43d27ffc01c5",
163+
"model_id": "93feca98298f4111909ae487e2a1e273",
173164
"version_major": 2,
174165
"version_minor": 0
175166
},
176167
"text/plain": [
177-
"interactive(children=(FloatSlider(value=0.0, description='t', max=40.132949019384355, step=0.40132949019384356"
168+
"interactive(children=(FloatSlider(value=0.0, description='t', max=40.13268704803602, step=0.4013268704803602),"
178169
]
179170
},
180171
"metadata": {},
@@ -183,7 +174,7 @@
183174
{
184175
"data": {
185176
"text/plain": [
186-
"<pybamm.plotting.quick_plot.QuickPlot at 0x7f1a11f76690>"
177+
"<pybamm.plotting.quick_plot.QuickPlot at 0x16520e690>"
187178
]
188179
},
189180
"execution_count": 5,
@@ -211,12 +202,12 @@
211202
{
212203
"data": {
213204
"application/vnd.jupyter.widget-view+json": {
214-
"model_id": "7cdac234d74241a2814918053454f6a6",
205+
"model_id": "4d6e43032f4e4aa6be5843c4916b4b50",
215206
"version_major": 2,
216207
"version_minor": 0
217208
},
218209
"text/plain": [
219-
"interactive(children=(FloatSlider(value=0.0, description='t', max=13.076977041121545, step=0.13076977041121546"
210+
"interactive(children=(FloatSlider(value=0.0, description='t', max=13.076887099589111, step=0.1307688709958911)"
220211
]
221212
},
222213
"metadata": {},
@@ -225,7 +216,7 @@
225216
{
226217
"data": {
227218
"text/plain": [
228-
"<pybamm.plotting.quick_plot.QuickPlot at 0x7f1a105ecb50>"
219+
"<pybamm.plotting.quick_plot.QuickPlot at 0x1696cf290>"
229220
]
230221
},
231222
"execution_count": 6,
@@ -255,7 +246,7 @@
255246
{
256247
"data": {
257248
"text/plain": [
258-
"_Step(C-rate, 1.0, duration=1 hour, period=1 minute, temperature=25oC, tags=['tag1'], description=Discharge at 1C for 1 hour)"
249+
"Step(1.0, duration=1 hour, period=1 minute, temperature=25oC, tags=['tag1'], description=Discharge at 1C for 1 hour, direction=Discharge)"
259250
]
260251
},
261252
"execution_count": 7,
@@ -293,7 +284,7 @@
293284
{
294285
"data": {
295286
"text/plain": [
296-
"_Step(current, 1, duration=1 hour, termination=2.5 V)"
287+
"Step(1, duration=1 hour, termination=2.5 V, direction=Discharge)"
297288
]
298289
},
299290
"execution_count": 8,
@@ -321,7 +312,7 @@
321312
{
322313
"data": {
323314
"text/plain": [
324-
"_Step(current, 1.0, duration=1 hour, termination=2.5V, description=Discharge at 1A for 1 hour or until 2.5V)"
315+
"Step(1.0, duration=1 hour, termination=2.5V, description=Discharge at 1A for 1 hour or until 2.5V, direction=Discharge)"
325316
]
326317
},
327318
"execution_count": 9,
@@ -348,10 +339,78 @@
348339
"execution_count": 10,
349340
"metadata": {},
350341
"outputs": [
342+
{
343+
"name": "stderr",
344+
"output_type": "stream",
345+
"text": [
346+
"2024-07-10 14:41:02.625 - [WARNING] callbacks.on_experiment_infeasible_time(240): \n",
347+
"\n",
348+
"\tExperiment is infeasible: default duration (1.0 seconds) was reached during 'Step([[ 0.00000000e+00 0.00000000e+00]\n",
349+
" [ 1.69491525e-02 5.31467428e-02]\n",
350+
" [ 3.38983051e-02 1.05691312e-01]\n",
351+
" [ 5.08474576e-02 1.57038356e-01]\n",
352+
" [ 6.77966102e-02 2.06606093e-01]\n",
353+
" [ 8.47457627e-02 2.53832900e-01]\n",
354+
" [ 1.01694915e-01 2.98183679e-01]\n",
355+
" [ 1.18644068e-01 3.39155918e-01]\n",
356+
" [ 1.35593220e-01 3.76285385e-01]\n",
357+
" [ 1.52542373e-01 4.09151388e-01]\n",
358+
" [ 1.69491525e-01 4.37381542e-01]\n",
359+
" [ 1.86440678e-01 4.60655989e-01]\n",
360+
" [ 2.03389831e-01 4.78711019e-01]\n",
361+
" [ 2.20338983e-01 4.91342062e-01]\n",
362+
" [ 2.37288136e-01 4.98406004e-01]\n",
363+
" [ 2.54237288e-01 4.99822806e-01]\n",
364+
" [ 2.71186441e-01 4.95576416e-01]\n",
365+
" [ 2.88135593e-01 4.85714947e-01]\n",
366+
" [ 3.05084746e-01 4.70350133e-01]\n",
367+
" [ 3.22033898e-01 4.49656065e-01]\n",
368+
" [ 3.38983051e-01 4.23867214e-01]\n",
369+
" [ 3.55932203e-01 3.93275778e-01]\n",
370+
" [ 3.72881356e-01 3.58228370e-01]\n",
371+
" [ 3.89830508e-01 3.19122092e-01]\n",
372+
" [ 4.06779661e-01 2.76400033e-01]\n",
373+
" [ 4.23728814e-01 2.30546251e-01]\n",
374+
" [ 4.40677966e-01 1.82080288e-01]\n",
375+
" [ 4.57627119e-01 1.31551282e-01]\n",
376+
" [ 4.74576271e-01 7.95317480e-02]\n",
377+
" [ 4.91525424e-01 2.66110874e-02]\n",
378+
" [ 5.08474576e-01 -2.66110874e-02]\n",
379+
" [ 5.25423729e-01 -7.95317480e-02]\n",
380+
" [ 5.42372881e-01 -1.31551282e-01]\n",
381+
" [ 5.59322034e-01 -1.82080288e-01]\n",
382+
" [ 5.76271186e-01 -2.30546251e-01]\n",
383+
" [ 5.93220339e-01 -2.76400033e-01]\n",
384+
" [ 6.10169492e-01 -3.19122092e-01]\n",
385+
" [ 6.27118644e-01 -3.58228370e-01]\n",
386+
" [ 6.44067797e-01 -3.93275778e-01]\n",
387+
" [ 6.61016949e-01 -4.23867214e-01]\n",
388+
" [ 6.77966102e-01 -4.49656065e-01]\n",
389+
" [ 6.94915254e-01 -4.70350133e-01]\n",
390+
" [ 7.11864407e-01 -4.85714947e-01]\n",
391+
" [ 7.28813559e-01 -4.95576416e-01]\n",
392+
" [ 7.45762712e-01 -4.99822806e-01]\n",
393+
" [ 7.62711864e-01 -4.98406004e-01]\n",
394+
" [ 7.79661017e-01 -4.91342062e-01]\n",
395+
" [ 7.96610169e-01 -4.78711019e-01]\n",
396+
" [ 8.13559322e-01 -4.60655989e-01]\n",
397+
" [ 8.30508475e-01 -4.37381542e-01]\n",
398+
" [ 8.47457627e-01 -4.09151388e-01]\n",
399+
" [ 8.64406780e-01 -3.76285385e-01]\n",
400+
" [ 8.81355932e-01 -3.39155918e-01]\n",
401+
" [ 8.98305085e-01 -2.98183679e-01]\n",
402+
" [ 9.15254237e-01 -2.53832900e-01]\n",
403+
" [ 9.32203390e-01 -2.06606093e-01]\n",
404+
" [ 9.49152542e-01 -1.57038356e-01]\n",
405+
" [ 9.66101695e-01 -1.05691312e-01]\n",
406+
" [ 9.83050847e-01 -5.31467428e-02]\n",
407+
" [ 1.00000000e+00 -1.22464680e-16]], duration=1.0, period=0.016949152542372836, direction=Rest)'. The returned solution only contains up to step 1 of cycle 1. Please specify a duration in the step instructions.\n"
408+
]
409+
},
351410
{
352411
"data": {
353412
"application/vnd.jupyter.widget-view+json": {
354-
"model_id": "730d5e19b17e447ebde5679de68c46ef",
413+
"model_id": "6364b4579fc447e2a607f2f8414172ba",
355414
"version_major": 2,
356415
"version_minor": 0
357416
},
@@ -365,7 +424,7 @@
365424
{
366425
"data": {
367426
"text/plain": [
368-
"<pybamm.plotting.quick_plot.QuickPlot at 0x7f1a1a4c2ed0>"
427+
"<pybamm.plotting.quick_plot.QuickPlot at 0x16a1d8d90>"
369428
]
370429
},
371430
"execution_count": 10,
@@ -419,13 +478,14 @@
419478
"output_type": "stream",
420479
"text": [
421480
"[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n",
422-
"[2] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n",
423-
"[3] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n",
424-
"[4] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n",
425-
"[5] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.\n",
426-
"[6] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n",
427-
"[7] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.\n",
428-
"[8] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.\n",
481+
"[2] Von DAG Bruggeman. Berechnung verschiedener physikalischer konstanten von heterogenen substanzen. i. dielektrizitätskonstanten und leitfähigkeiten der mischkörper aus isotropen substanzen. Annalen der physik, 416(7):636–664, 1935.\n",
482+
"[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n",
483+
"[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n",
484+
"[5] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n",
485+
"[6] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.\n",
486+
"[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n",
487+
"[8] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.\n",
488+
"[9] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.\n",
429489
"\n"
430490
]
431491
}

pybamm/callbacks.py

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -36,37 +36,37 @@ def on_experiment_start(self, logs):
3636
"""
3737
Called at the start of an experiment simulation.
3838
"""
39-
pass
39+
pass # pragma: no cover
4040

4141
def on_cycle_start(self, logs):
4242
"""
4343
Called at the start of each cycle in an experiment simulation.
4444
"""
45-
pass
45+
pass # pragma: no cover
4646

4747
def on_step_start(self, logs):
4848
"""
4949
Called at the start of each step in an experiment simulation.
5050
"""
51-
pass
51+
pass # pragma: no cover
5252

5353
def on_step_end(self, logs):
5454
"""
5555
Called at the end of each step in an experiment simulation.
5656
"""
57-
pass
57+
pass # pragma: no cover
5858

5959
def on_cycle_end(self, logs):
6060
"""
6161
Called at the end of each cycle in an experiment simulation.
6262
"""
63-
pass
63+
pass # pragma: no cover
6464

6565
def on_experiment_end(self, logs):
6666
"""
6767
Called at the end of an experiment simulation.
6868
"""
69-
pass
69+
pass # pragma: no cover
7070

7171
def on_experiment_error(self, logs):
7272
"""
@@ -75,13 +75,19 @@ def on_experiment_error(self, logs):
7575
For example, this could be used to send an error alert with a bug report when
7676
running batch simulations in the cloud.
7777
"""
78-
pass
78+
pass # pragma: no cover
7979

80-
def on_experiment_infeasible(self, logs):
80+
def on_experiment_infeasible_time(self, logs):
8181
"""
82-
Called when an experiment simulation is infeasible.
82+
Called when an experiment simulation is infeasible due to reaching maximum time.
8383
"""
84-
pass
84+
pass # pragma: no cover
85+
86+
def on_experiment_infeasible_event(self, logs):
87+
"""
88+
Called when an experiment simulation is infeasible due to an event.
89+
"""
90+
pass # pragma: no cover
8591

8692

8793
########################################################################################
@@ -226,7 +232,19 @@ def on_experiment_error(self, logs):
226232
error = logs["error"]
227233
pybamm.logger.error(f"Simulation error: {error}")
228234

229-
def on_experiment_infeasible(self, logs):
235+
def on_experiment_infeasible_time(self, logs):
236+
duration = logs["step duration"]
237+
cycle_num = logs["cycle number"][0]
238+
step_num = logs["step number"][0]
239+
operating_conditions = logs["step operating conditions"]
240+
self.logger.warning(
241+
f"\n\n\tExperiment is infeasible: default duration ({duration} seconds) "
242+
f"was reached during '{operating_conditions}'. The returned solution only "
243+
f"contains up to step {step_num} of cycle {cycle_num}. "
244+
"Please specify a duration in the step instructions."
245+
)
246+
247+
def on_experiment_infeasible_event(self, logs):
230248
termination = logs["termination"]
231249
cycle_num = logs["cycle number"][0]
232250
step_num = logs["step number"][0]

pybamm/experiment/step/base_step.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ def __init__(
7171
description=None,
7272
direction=None,
7373
):
74+
self.input_duration = duration
75+
self.input_value = value
7476
# Check if drive cycle
7577
self.is_drive_cycle = isinstance(value, np.ndarray)
7678
if self.is_drive_cycle:
@@ -84,8 +86,11 @@ def __init__(
8486
if t[0] != 0:
8587
raise ValueError("Drive cycle must start at t=0")
8688

89+
# Record whether the step uses the default duration
90+
# This will be used by the experiment to check whether the step is feasible
91+
self.uses_default_duration = duration is None
8792
# Set duration
88-
if duration is None:
93+
if self.uses_default_duration:
8994
duration = self.default_duration(value)
9095
self.duration = _convert_time_to_seconds(duration)
9196

@@ -179,8 +184,8 @@ def copy(self):
179184
A copy of the step.
180185
"""
181186
return self.__class__(
182-
self.value,
183-
duration=self.duration,
187+
self.input_value,
188+
duration=self.input_duration,
184189
termination=self.termination,
185190
period=self.period,
186191
temperature=self.temperature,
@@ -243,7 +248,7 @@ def default_duration(self, value):
243248
t = value[:, 0]
244249
return t[-1]
245250
else:
246-
return 24 * 3600 # 24 hours in seconds
251+
return 24 * 3600 # one day in seconds
247252

248253
def process_model(self, model, parameter_values):
249254
new_model = model.new_copy()
@@ -343,10 +348,16 @@ def set_up(self, new_model, new_parameter_values):
343348

344349
def _convert_time_to_seconds(time_and_units):
345350
"""Convert a time in seconds, minutes or hours to a time in seconds"""
346-
# If the time is a number, assume it is in seconds
347-
if isinstance(time_and_units, numbers.Number) or time_and_units is None:
351+
if time_and_units is None:
348352
return time_and_units
349353

354+
# If the time is a number, assume it is in seconds
355+
if isinstance(time_and_units, numbers.Number):
356+
if time_and_units <= 0:
357+
raise ValueError("time must be positive")
358+
else:
359+
return time_and_units
360+
350361
# Split number and units
351362
units = time_and_units.lstrip("0123456789.- ")
352363
time = time_and_units[: -len(units)]

pybamm/experiment/step/steps.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,11 @@ def __init__(self, value, **kwargs):
157157
def current_value(self, variables):
158158
return self.value * pybamm.Parameter("Nominal cell capacity [A.h]")
159159

160+
def default_duration(self, value):
161+
# "value" is C-rate, so duration is "1 / value" hours in seconds
162+
# with a 2x safety factor
163+
return 1 / abs(value) * 3600 * 2
164+
160165

161166
def c_rate(value, **kwargs):
162167
"""

0 commit comments

Comments
 (0)