Skip to content

Commit c2ca937

Browse files
Merge pull request #2616 from pybamm-team/issue-1839-time-based-experiments
Timestamped experiments
2 parents e8300b6 + f641985 commit c2ca937

File tree

9 files changed

+532
-19
lines changed

9 files changed

+532
-19
lines changed

CHANGELOG.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
- Enable multithreading in IDAKLU solver ([#2947](https://github.com/pybamm-team/PyBaMM/pull/2947))
1212
- If a solution contains cycles and steps, the cycle number and step number are now saved when `solution.save_data()` is called ([#2931](https://github.com/pybamm-team/PyBaMM/pull/2931))
13+
- Experiments can now be given a `start_time` to define when each step should be triggered ([#2616](https://github.com/pybamm-team/PyBaMM/pull/2616))
1314

1415
## Optimizations
1516

@@ -38,20 +39,21 @@
3839
## Features
3940

4041
- Added verbose logging to `pybamm.print_citations()` and citation tags for the `pybamm.Citations` class so that users can now see where the citations were registered when running simulations ([#2862](https://github.com/pybamm-team/PyBaMM/pull/2862))
42+
- Updated to casadi 3.6, which required some changes to the casadi integrator ([#2859](https://github.com/pybamm-team/PyBaMM/pull/2859))
4143
- PyBaMM is now natively supported on Apple silicon chips (`M1/M2`) ([#2435](https://github.com/pybamm-team/PyBaMM/pull/2435))
4244
- PyBaMM is now supported on Python `3.10` and `3.11` ([#2435](https://github.com/pybamm-team/PyBaMM/pull/2435))
43-
- Updated to casadi 3.6, which required some changes to the casadi integrator. ([#2859](https://github.com/pybamm-team/PyBaMM/pull/2859))
45+
4446

4547
## Optimizations
4648

4749
- Fixed deprecated `interp2d` method by switching to `xarray.DataArray` as the backend for `ProcessedVariable` ([#2907](https://github.com/pybamm-team/PyBaMM/pull/2907))
4850

4951
## Bug fixes
5052

53+
- Initial conditions for sensitivity equations calculated correctly ([#2920](https://github.com/pybamm-team/PyBaMM/pull/2920))
5154
- Parameter sets can now contain the key "chemistry", and will ignore its value (this previously would give errors in some cases) ([#2901](https://github.com/pybamm-team/PyBaMM/pull/2901))
52-
- Fixed a bug in the discretisation of initial conditions of a scaled variable ([#2856](https://github.com/pybamm-team/PyBaMM/pull/2856))
5355
- Fixed keyerror on "all" when getting sensitivities from IDAKLU solver([#2883](https://github.com/pybamm-team/PyBaMM/pull/2883))
54-
- Initial conditions for sensitivity equations calculated correctly ([#2920](https://github.com/pybamm-team/PyBaMM/pull/2920))
56+
- Fixed a bug in the discretisation of initial conditions of a scaled variable ([#2856](https://github.com/pybamm-team/PyBaMM/pull/2856))
5557

5658
## Breaking changes
5759

@@ -73,7 +75,7 @@
7375

7476
- Fix non-deteministic outcome of some tests in the test suite ([#2844](https://github.com/pybamm-team/PyBaMM/pull/2844))
7577
- Fixed excessive RAM consumption when running multiple simulations ([#2823](https://github.com/pybamm-team/PyBaMM/pull/2823))
76-
- Fixed use of last_state as starting_solution in Simulation.solve() ([#2822](https://github.com/pybamm-team/PyBaMM/pull/2822))
78+
- Fixed use of `last_state` as `starting_solution` in `Simulation.solve()` ([#2822](https://github.com/pybamm-team/PyBaMM/pull/2822))
7779
- Fixed a bug where variable bounds could not contain `InputParameters` ([#2795](https://github.com/pybamm-team/PyBaMM/pull/2795))
7880
- Improved `model.latexify()` to have a cleaner and more readable output ([#2764](https://github.com/pybamm-team/PyBaMM/pull/2764))
7981
- Fixed electrolyte conservation in the case of concentration-dependent transference number ([#2758](https://github.com/pybamm-team/PyBaMM/pull/2758))
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
{
2+
"cells": [
3+
{
4+
"attachments": {},
5+
"cell_type": "markdown",
6+
"id": "regional-bedroom",
7+
"metadata": {},
8+
"source": [
9+
"# Experiments with `start_time`"
10+
]
11+
},
12+
{
13+
"attachments": {},
14+
"cell_type": "markdown",
15+
"id": "quantitative-radar",
16+
"metadata": {},
17+
"source": [
18+
"This notebook introduces functionality for simulating user case in which the experiment steps are triggered at a certain point in time."
19+
]
20+
},
21+
{
22+
"cell_type": "code",
23+
"execution_count": 1,
24+
"id": "novel-spectacular",
25+
"metadata": {},
26+
"outputs": [
27+
{
28+
"name": "stdout",
29+
"output_type": "stream",
30+
"text": [
31+
"\n",
32+
"\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.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m23.1.2\u001b[0m\n",
33+
"\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",
34+
"Note: you may need to restart the kernel to use updated packages.\n"
35+
]
36+
}
37+
],
38+
"source": [
39+
"%pip install pybamm -q # install PyBaMM if it is not installed\n",
40+
"import pybamm"
41+
]
42+
},
43+
{
44+
"attachments": {},
45+
"cell_type": "markdown",
46+
"id": "11c87da1",
47+
"metadata": {},
48+
"source": [
49+
"Let's start defining a model to illustrate this functionality, in this case we choose the SPM"
50+
]
51+
},
52+
{
53+
"cell_type": "code",
54+
"execution_count": 2,
55+
"id": "96b62a7f",
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"model = pybamm.lithium_ion.SPM()"
60+
]
61+
},
62+
{
63+
"attachments": {},
64+
"cell_type": "markdown",
65+
"id": "f388d538",
66+
"metadata": {},
67+
"source": [
68+
"Usually we define an experiment such that each step is triggered when the previous step is completed. For example, in this case we do a 1C discharge for 20 minutes and then a C/3 charge for 10 minutes. The charge step starts after 20 minutes, i.e. once the discharge step is finished."
69+
]
70+
},
71+
{
72+
"cell_type": "code",
73+
"execution_count": 3,
74+
"id": "eba027c8",
75+
"metadata": {},
76+
"outputs": [
77+
{
78+
"data": {
79+
"application/vnd.jupyter.widget-view+json": {
80+
"model_id": "5516f708a0264190a51d1811064afd82",
81+
"version_major": 2,
82+
"version_minor": 0
83+
},
84+
"text/plain": [
85+
"interactive(children=(FloatSlider(value=0.0, description='t', max=1800.0, step=18.0), Output()), _dom_classes=…"
86+
]
87+
},
88+
"metadata": {},
89+
"output_type": "display_data"
90+
},
91+
{
92+
"data": {
93+
"text/plain": [
94+
"<pybamm.plotting.quick_plot.QuickPlot at 0x7f3194e00eb0>"
95+
]
96+
},
97+
"execution_count": 3,
98+
"metadata": {},
99+
"output_type": "execute_result"
100+
}
101+
],
102+
"source": [
103+
"experiment = pybamm.Experiment([\"Discharge at 1C for 20 minutes\", \"Charge at C/3 for 10 minutes\"])\n",
104+
"sim = pybamm.Simulation(model, experiment=experiment)\n",
105+
"sim.solve()\n",
106+
"sim.plot()"
107+
]
108+
},
109+
{
110+
"attachments": {},
111+
"cell_type": "markdown",
112+
"id": "0fdfced4",
113+
"metadata": {},
114+
"source": [
115+
"However, if we want to represent a realistic user case we might certain experiments to be run at a certain time instead, even if that means cutting short the previous step. In this case we can pass a starting time as a keyword argument in the `pybamm.step.string` method. The `start_time` should be passed as a `datetime.datetime` object."
116+
]
117+
},
118+
{
119+
"cell_type": "code",
120+
"execution_count": 4,
121+
"id": "171550ac",
122+
"metadata": {},
123+
"outputs": [
124+
{
125+
"data": {
126+
"application/vnd.jupyter.widget-view+json": {
127+
"model_id": "4832134ba802437e957ccdd5072d6d93",
128+
"version_major": 2,
129+
"version_minor": 0
130+
},
131+
"text/plain": [
132+
"interactive(children=(FloatSlider(value=0.0, description='t', max=2.5, step=0.025), Output()), _dom_classes=('…"
133+
]
134+
},
135+
"metadata": {},
136+
"output_type": "display_data"
137+
},
138+
{
139+
"data": {
140+
"text/plain": [
141+
"<pybamm.plotting.quick_plot.QuickPlot at 0x7f31943d3760>"
142+
]
143+
},
144+
"execution_count": 4,
145+
"metadata": {},
146+
"output_type": "execute_result"
147+
}
148+
],
149+
"source": [
150+
"s = pybamm.step.string\n",
151+
"\n",
152+
"experiment = pybamm.Experiment(\n",
153+
" [\n",
154+
" s(\"Discharge at 1C for 1 hour\", start_time=\"Day 1 08:00:00\"),\n",
155+
" s(\"Charge at C/3 for 10 minutes\", start_time=\"Day 1 08:30:00\"),\n",
156+
" s(\"Discharge at C/2 for 30 minutes\", start_time=\"Day 1 09:00:00\"),\n",
157+
" s(\"Rest for 1 hour\"),\n",
158+
" ]\n",
159+
")\n",
160+
"sim = pybamm.Simulation(model, experiment=experiment)\n",
161+
"sim.solve()\n",
162+
"sim.plot()"
163+
]
164+
},
165+
{
166+
"attachments": {},
167+
"cell_type": "markdown",
168+
"id": "edfa4c9f",
169+
"metadata": {},
170+
"source": [
171+
"In the example above, we note that the first step (1C discharge) is cut short as the second step (C/3 charge) start time occurs before the end of the first step. On the other hand, an additional resting period is added after the second step as the third step (C/2 discharge) start time is 20 minutes later than the end of the second step. The final step does not have a start time so it is triggered immediately after the previous step. Note that if the argument `start_time` is used in an experiment, the first step should always have a `start_time`, otherwise the solver will throw an error."
172+
]
173+
},
174+
{
175+
"cell_type": "code",
176+
"execution_count": 5,
177+
"id": "driven-sensitivity",
178+
"metadata": {},
179+
"outputs": [
180+
{
181+
"name": "stdout",
182+
"output_type": "stream",
183+
"text": [
184+
"[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",
185+
"[2] 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",
186+
"[3] 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",
187+
"[4] 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",
188+
"[5] 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",
189+
"[6] 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",
190+
"[7] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.\n",
191+
"\n"
192+
]
193+
}
194+
],
195+
"source": [
196+
"pybamm.print_citations()"
197+
]
198+
},
199+
{
200+
"cell_type": "code",
201+
"execution_count": null,
202+
"id": "593ae90b",
203+
"metadata": {},
204+
"outputs": [],
205+
"source": []
206+
}
207+
],
208+
"metadata": {
209+
"kernelspec": {
210+
"display_name": "Python 3 (ipykernel)",
211+
"language": "python",
212+
"name": "python3"
213+
},
214+
"language_info": {
215+
"codemirror_mode": {
216+
"name": "ipython",
217+
"version": 3
218+
},
219+
"file_extension": ".py",
220+
"mimetype": "text/x-python",
221+
"name": "python",
222+
"nbconvert_exporter": "python",
223+
"pygments_lexer": "ipython3",
224+
"version": "3.9.17"
225+
},
226+
"toc": {
227+
"base_numbering": 1,
228+
"nav_menu": {},
229+
"number_sections": true,
230+
"sideBar": true,
231+
"skip_h1_title": false,
232+
"title_cell": "Table of Contents",
233+
"title_sidebar": "Contents",
234+
"toc_cell": false,
235+
"toc_position": {},
236+
"toc_section_display": true,
237+
"toc_window_display": true
238+
},
239+
"vscode": {
240+
"interpreter": {
241+
"hash": "612adcc456652826e82b485a1edaef831aa6d5abc680d008e93d513dd8724f14"
242+
}
243+
}
244+
},
245+
"nbformat": 4,
246+
"nbformat_minor": 5
247+
}

pybamm/experiment/experiment.py

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ def __init__(
6868
termination,
6969
)
7070

71+
self.datetime_formats = [
72+
"Day %j %H:%M:%S",
73+
"%Y-%m-%d %H:%M:%S",
74+
]
75+
7176
operating_conditions_cycles = []
7277
for cycle in operating_conditions:
7378
# Check types and convert to list
@@ -78,9 +83,9 @@ def __init__(
7883
self.operating_conditions_cycles = operating_conditions_cycles
7984
self.cycle_lengths = [len(cycle) for cycle in operating_conditions_cycles]
8085

81-
operating_conditions_steps_unprocessed = [
82-
cond for cycle in operating_conditions_cycles for cond in cycle
83-
]
86+
operating_conditions_steps_unprocessed = self._set_next_start_time(
87+
[cond for cycle in operating_conditions_cycles for cond in cycle]
88+
)
8489

8590
# Convert strings to pybamm.step._Step objects
8691
# We only do this once per unique step, do avoid unnecessary conversions
@@ -89,11 +94,7 @@ def __init__(
8994
for step in unique_steps_unprocessed:
9095
if isinstance(step, str):
9196
processed_steps[step] = pybamm.step.string(step)
92-
elif not isinstance(step, pybamm.step._Step):
93-
raise TypeError(
94-
"Operating conditions should be strings or _Step objects"
95-
)
96-
else:
97+
elif isinstance(step, pybamm.step._Step):
9798
processed_steps[step] = step
9899

99100
# Save the processed unique steps and the processed operating conditions
@@ -103,6 +104,17 @@ def __init__(
103104
processed_steps[step] for step in operating_conditions_steps_unprocessed
104105
]
105106

107+
self.initial_start_time = self.operating_conditions_steps[0].start_time
108+
109+
if (
110+
self.operating_conditions_steps[0].end_time is not None
111+
and self.initial_start_time is None
112+
):
113+
raise ValueError(
114+
"When using experiments with `start_time`, the first step must have a "
115+
"`start_time`."
116+
)
117+
106118
self.termination_string = termination
107119
self.termination = self.read_termination(termination)
108120

@@ -182,3 +194,27 @@ def search_tag(self, tag):
182194
break
183195

184196
return cycles
197+
198+
def _set_next_start_time(self, operating_conditions):
199+
if all(isinstance(i, str) for i in operating_conditions):
200+
return operating_conditions
201+
202+
end_time = None
203+
next_start_time = None
204+
205+
for op in reversed(operating_conditions):
206+
if isinstance(op, str):
207+
op = pybamm.step.string(op)
208+
elif not isinstance(op, pybamm.step._Step):
209+
raise TypeError(
210+
"Operating conditions should be strings or _Step objects"
211+
)
212+
213+
op.next_start_time = next_start_time
214+
op.end_time = end_time
215+
216+
next_start_time = op.start_time
217+
if next_start_time:
218+
end_time = next_start_time
219+
220+
return operating_conditions

0 commit comments

Comments
 (0)