Skip to content

Commit 67e9968

Browse files
docs: add notebooks on pipeline and solver performance (#4787)
* docs: add performance notebooks 01-simulation-pipeline and 02-input-parameters * docs: add performance notebook 05-solver-tolerances * docs: start performance notebook 06-output-variables * docs: fill out 06-output-variables * add 03-pybamm-solvers notebook * add 04-interpolation-points * update 04-interpolation-points * review 04-interpolation-points * misc fixes to notebooks * feat: add multithreading notebook * misc fixes * fix links * fix input parameter increased solver time * add performance notebooks to index * fix failing notebook * mention pybamm.FunctionParameter in pipeline * fix input param code, remove increase in solve time section * explain pybamm solver comparison plot a bit more * explain pybamm solver comparison plot a bit more * Update docs/source/examples/notebooks/performance/04-interpolation-points.ipynb Co-authored-by: Ferran Brosa Planella <[email protected]> * Update docs/source/examples/notebooks/performance/04-interpolation-points.ipynb Co-authored-by: Ferran Brosa Planella <[email protected]> * Update docs/source/examples/notebooks/performance/03-pybamm-solvers.ipynb Co-authored-by: Ferran Brosa Planella <[email protected]> --------- Co-authored-by: Ferran Brosa Planella <[email protected]>
1 parent 10a404e commit 67e9968

File tree

9 files changed

+1845
-0
lines changed

9 files changed

+1845
-0
lines changed

docs/source/examples/index.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,18 @@ The notebooks are organised into subfolders, and can be viewed in the galleries
3333
notebooks/creating_models/6-a-simple-SEI-model.ipynb
3434
notebooks/creating_models/7-creating-a-submodel.ipynb
3535

36+
.. nbgallery::
37+
:caption: performance
38+
:glob:
39+
40+
notebooks/performance/01-simulation-pipeline.ipynb
41+
notebooks/performance/02-input-parameters.ipynb
42+
notebooks/performance/03-pybamm-solvers.ipynb
43+
notebooks/performance/04-interpolation-points.ipynb
44+
notebooks/performance/05-solver-tolerances.ipynb
45+
notebooks/performance/06-output-variables.ipynb
46+
notebooks/performance/07-multithreading.ipynb
47+
3648
.. nbgallery::
3749
:caption: Expression Tree
3850
:glob:

docs/source/examples/notebooks/performance/01-simulation-pipeline.ipynb

Lines changed: 385 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"## Using Input Parameters to efficiently re-run simulations with different parameters\n",
8+
"\n",
9+
"The previous notebook described the PyBaMM pipeline and how you can use the `pybamm.Simulation` class to efficiently run simulations. Recall the pipeline was:\n",
10+
"\n",
11+
"1. Parameter replacement\n",
12+
"2. Discretisation\n",
13+
"3. Solver setup\n",
14+
"4. Solver solve\n",
15+
"5. Post-processing\n",
16+
"\n",
17+
"An obvious question is how can we efficiently re-run simulations with different parameters (step 1 in the pipeline)? Ideally we'd like to do this without having to re-build the model and discretisation each time, which as we've seen is a costly process."
18+
]
19+
},
20+
{
21+
"cell_type": "code",
22+
"execution_count": 1,
23+
"metadata": {},
24+
"outputs": [
25+
{
26+
"name": "stdout",
27+
"output_type": "stream",
28+
"text": [
29+
"Note: you may need to restart the kernel to use updated packages.\n"
30+
]
31+
}
32+
],
33+
"source": [
34+
"%pip install \"pybamm[plot,cite]\" -q # install PyBaMM if it is not installed\n",
35+
"import pybamm\n",
36+
"import numpy as np"
37+
]
38+
},
39+
{
40+
"cell_type": "markdown",
41+
"metadata": {},
42+
"source": [
43+
"\n",
44+
"## Using Input Parameters\n",
45+
"\n",
46+
"The answer is to use input parameters and the [`pybamm.InputParameter`](https://docs.pybamm.org/en/stable/source/api/expression_tree/input_parameter.html) class. Input parameters are a special type of parameter that is *not* replaced by a value during step 1 of the pipeline, and can be used to replace many of the parameters in the model (as we'll see later this does not apply to some geometric parameters). \n",
47+
"\n",
48+
"If you use the `pybamm.ParameterVariables` class to set the parameters of your model, you can set any parameter in the model to be an input parameter by updating its parameter value to be the string `[input]`. For example, to set the current in the SPM model to be an input parameter, you can do the following:"
49+
]
50+
},
51+
{
52+
"cell_type": "code",
53+
"execution_count": 2,
54+
"metadata": {},
55+
"outputs": [],
56+
"source": [
57+
"model = pybamm.lithium_ion.SPM()\n",
58+
"parameter_values = model.default_parameter_values\n",
59+
"parameter_values[\"Current function [A]\"] = \"[input]\""
60+
]
61+
},
62+
{
63+
"cell_type": "markdown",
64+
"metadata": {},
65+
"source": [
66+
"\n",
67+
"If you are building up a model from scratch, you can also use `pybamm.InputParameter` directly to create an input parameter. For example, the code below creates an exponential decay model with a decay constant as an input parameter:"
68+
]
69+
},
70+
{
71+
"cell_type": "code",
72+
"execution_count": 3,
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"model = pybamm.BaseModel()\n",
77+
"k = pybamm.InputParameter(\"k\")\n",
78+
"x = pybamm.Variable(\"x\")\n",
79+
"model.rhs = {x: -k * x}"
80+
]
81+
},
82+
{
83+
"cell_type": "markdown",
84+
"metadata": {},
85+
"source": [
86+
"## Example\n",
87+
"\n",
88+
"Let's see how we can use input parameters to efficiently re-run simulations with different parameters. We'll start with a script that loops over the current to solve the model, and then show how we can use input parameters to do this more efficiently."
89+
]
90+
},
91+
{
92+
"cell_type": "code",
93+
"execution_count": 4,
94+
"metadata": {},
95+
"outputs": [
96+
{
97+
"name": "stdout",
98+
"output_type": "stream",
99+
"text": [
100+
"Average time taken: 0.193 seconds\n"
101+
]
102+
}
103+
],
104+
"source": [
105+
"import time\n",
106+
"\n",
107+
"average_time = 0\n",
108+
"n = 9\n",
109+
"model = pybamm.lithium_ion.SPM()\n",
110+
"solver = pybamm.IDAKLUSolver()\n",
111+
"params = model.default_parameter_values\n",
112+
"for current in np.linspace(-1.1, 1.0, n):\n",
113+
" time_start = time.perf_counter()\n",
114+
" params[\"Current function [A]\"] = current\n",
115+
" sim = pybamm.Simulation(model, solver=solver, parameter_values=params)\n",
116+
" sol = sim.solve([0, 3600])\n",
117+
" t_evals = np.linspace(0, 3600, 100)\n",
118+
" voltage = sol[\"Terminal voltage [V]\"](t_evals)\n",
119+
" time_end = time.perf_counter()\n",
120+
" average_time += time_end - time_start\n",
121+
"print(f\"Average time taken: {average_time / n:.3f} seconds\")"
122+
]
123+
},
124+
{
125+
"cell_type": "markdown",
126+
"metadata": {},
127+
"source": [
128+
"The most important thing to note about this script is that we are creating the simulation object on every iteration of this loop. This means we are running though all the steps of the pipeline, rebuilding the model and performing the discretisations, at each iteration of the loop. We do this even though most of the structure of the model, and in particular the numerical discretisations of the spatial gradients, is unchanged. As we've seen previously, the discretisation step is often the most expensive part of the pipeline, so we'd like to avoid repeating it if possible.\n",
129+
"\n",
130+
"Now let's see how we will use input parameters to do this more efficiently. To do this we will move the simulation object creation outside of the loop, and use an input parameter for the current instead."
131+
]
132+
},
133+
{
134+
"cell_type": "code",
135+
"execution_count": 5,
136+
"metadata": {},
137+
"outputs": [
138+
{
139+
"name": "stdout",
140+
"output_type": "stream",
141+
"text": [
142+
"Average time taken: 0.033 seconds\n"
143+
]
144+
}
145+
],
146+
"source": [
147+
"average_time = 0\n",
148+
"n = 10\n",
149+
"model = pybamm.lithium_ion.SPM()\n",
150+
"solver = pybamm.IDAKLUSolver()\n",
151+
"params = model.default_parameter_values\n",
152+
"params[\"Current function [A]\"] = \"[input]\"\n",
153+
"sim = pybamm.Simulation(model, solver=solver, parameter_values=params)\n",
154+
"for current in np.linspace(0.1, 1.0, n):\n",
155+
" time_start = time.perf_counter()\n",
156+
" sol = sim.solve([0, 3600], inputs={\"Current function [A]\": current})\n",
157+
" t_evals = np.linspace(0, 3600, 100)\n",
158+
" voltage = sol[\"Terminal voltage [V]\"](t_evals)\n",
159+
" time_end = time.perf_counter()\n",
160+
" average_time += time_end - time_start\n",
161+
"print(f\"Average time taken: {average_time / n:.3f} seconds\")"
162+
]
163+
},
164+
{
165+
"cell_type": "markdown",
166+
"metadata": {},
167+
"source": [
168+
"You can see that we've improved the performance of our loop significantly by moving the creation of the simulation object outside of the loop, thus avoiding the need to rebuild the model from scratch at each iteration. \n"
169+
]
170+
},
171+
{
172+
"cell_type": "markdown",
173+
"metadata": {},
174+
"source": [
175+
"## Limitations"
176+
]
177+
},
178+
{
179+
"cell_type": "markdown",
180+
"metadata": {},
181+
"source": [
182+
"\n",
183+
"\n",
184+
"### Geometric parameters cannot be input parameters\n",
185+
"\n",
186+
"Input parameters cannot be used for parameters that are used during the descretisation step. These are generally parameters affecting the geometry, such as the electrode or separator thicknesses. If you try to use an input parameter for a parameter that affects the discretisation, you will get an error, most likely during the discretisation step. For example, these are the parameters that fail for the SPM model, along with the error messages:"
187+
]
188+
},
189+
{
190+
"cell_type": "code",
191+
"execution_count": 6,
192+
"metadata": {},
193+
"outputs": [
194+
{
195+
"name": "stdout",
196+
"output_type": "stream",
197+
"text": [
198+
"Failed for parameter Separator thickness [m]. Error was Cannot interpret 'Addition(-0x1e5235efb4a04db1, +, children=['0.0001', 'Separator thickness [m]'], domains={})' as a data type\n",
199+
"Traceback (most recent call last):\n",
200+
" File \"/tmp/ipykernel_1968898/3514874421.py\", line 12, in <module>\n",
201+
" sim.solve([0, 3600], inputs={param: original_param})\n",
202+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 472, in solve\n",
203+
" self.build(initial_soc=initial_soc, inputs=inputs)\n",
204+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 328, in build\n",
205+
" self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts)\n",
206+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 117, in __init__\n",
207+
" self[domain] = submesh_types[domain](geometry[domain], submesh_pts[domain])\n",
208+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 301, in __call__\n",
209+
" return self.submesh_type(lims, npts, **self.submesh_params)\n",
210+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/one_dimensional_submeshes.py\", line 130, in __init__\n",
211+
" edges = np.linspace(spatial_lims[\"min\"], spatial_lims[\"max\"], npts + 1)\n",
212+
" File \"/home/mrobins/git/PyBaMM/env/lib/python3.10/site-packages/numpy/core/function_base.py\", line 132, in linspace\n",
213+
" dt = result_type(start, stop, float(num))\n",
214+
"TypeError: Cannot interpret 'Addition(-0x1e5235efb4a04db1, +, children=['0.0001', 'Separator thickness [m]'], domains={})' as a data type\n",
215+
"\n",
216+
"Failed for parameter Negative electrode thickness [m]. Error was Cannot interpret 'InputParameter(0x6c630c7d1f75ef82, Negative electrode thickness [m], children=[], domains={})' as a data type\n",
217+
"Traceback (most recent call last):\n",
218+
" File \"/tmp/ipykernel_1968898/3514874421.py\", line 12, in <module>\n",
219+
" sim.solve([0, 3600], inputs={param: original_param})\n",
220+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 472, in solve\n",
221+
" self.build(initial_soc=initial_soc, inputs=inputs)\n",
222+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 328, in build\n",
223+
" self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts)\n",
224+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 117, in __init__\n",
225+
" self[domain] = submesh_types[domain](geometry[domain], submesh_pts[domain])\n",
226+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 301, in __call__\n",
227+
" return self.submesh_type(lims, npts, **self.submesh_params)\n",
228+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/one_dimensional_submeshes.py\", line 130, in __init__\n",
229+
" edges = np.linspace(spatial_lims[\"min\"], spatial_lims[\"max\"], npts + 1)\n",
230+
" File \"/home/mrobins/git/PyBaMM/env/lib/python3.10/site-packages/numpy/core/function_base.py\", line 132, in linspace\n",
231+
" dt = result_type(start, stop, float(num))\n",
232+
"TypeError: Cannot interpret 'InputParameter(0x6c630c7d1f75ef82, Negative electrode thickness [m], children=[], domains={})' as a data type\n",
233+
"\n",
234+
"Failed for parameter Positive electrode thickness [m]. Error was Cannot interpret 'Addition(-0x30e83b9fe1971dbd, +, children=['0.000125', 'Positive electrode thickness [m]'], domains={})' as a data type\n",
235+
"Traceback (most recent call last):\n",
236+
" File \"/tmp/ipykernel_1968898/3514874421.py\", line 12, in <module>\n",
237+
" sim.solve([0, 3600], inputs={param: original_param})\n",
238+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 472, in solve\n",
239+
" self.build(initial_soc=initial_soc, inputs=inputs)\n",
240+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/simulation.py\", line 328, in build\n",
241+
" self._mesh = pybamm.Mesh(self._geometry, self._submesh_types, self._var_pts)\n",
242+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 117, in __init__\n",
243+
" self[domain] = submesh_types[domain](geometry[domain], submesh_pts[domain])\n",
244+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/meshes.py\", line 301, in __call__\n",
245+
" return self.submesh_type(lims, npts, **self.submesh_params)\n",
246+
" File \"/home/mrobins/git/PyBaMM/src/pybamm/meshes/one_dimensional_submeshes.py\", line 130, in __init__\n",
247+
" edges = np.linspace(spatial_lims[\"min\"], spatial_lims[\"max\"], npts + 1)\n",
248+
" File \"/home/mrobins/git/PyBaMM/env/lib/python3.10/site-packages/numpy/core/function_base.py\", line 132, in linspace\n",
249+
" dt = result_type(start, stop, float(num))\n",
250+
"TypeError: Cannot interpret 'Addition(-0x30e83b9fe1971dbd, +, children=['0.000125', 'Positive electrode thickness [m]'], domains={})' as a data type\n",
251+
"\n"
252+
]
253+
}
254+
],
255+
"source": [
256+
"import traceback\n",
257+
"\n",
258+
"model = pybamm.lithium_ion.SPM()\n",
259+
"model_params = model.get_parameter_info()\n",
260+
"for param in model_params:\n",
261+
" if model_params[param][1] == \"Parameter\":\n",
262+
" params = model.default_parameter_values\n",
263+
" original_param = params[param]\n",
264+
" params[param] = \"[input]\"\n",
265+
" sim = pybamm.Simulation(model, parameter_values=params)\n",
266+
" try:\n",
267+
" sim.solve([0, 3600], inputs={param: original_param})\n",
268+
" except Exception as e:\n",
269+
" print(f\"Failed for parameter {param}. Error was {e}\")\n",
270+
" tb = traceback.format_exc()\n",
271+
" print(tb)"
272+
]
273+
}
274+
],
275+
"metadata": {
276+
"kernelspec": {
277+
"display_name": "env",
278+
"language": "python",
279+
"name": "python3"
280+
},
281+
"language_info": {
282+
"codemirror_mode": {
283+
"name": "ipython",
284+
"version": 3
285+
},
286+
"file_extension": ".py",
287+
"mimetype": "text/x-python",
288+
"name": "python",
289+
"nbconvert_exporter": "python",
290+
"pygments_lexer": "ipython3",
291+
"version": "3.10.12"
292+
}
293+
},
294+
"nbformat": 4,
295+
"nbformat_minor": 2
296+
}

0 commit comments

Comments
 (0)