PyBaMM-GEM 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pybamm_gem/__init__.py ADDED
@@ -0,0 +1,77 @@
1
+ """
2
+ Copyright (c) 2026 Mohammed Asheruddin N. All rights reserved.
3
+
4
+ PyBaMM-GEM: A PyBaMM-compatible extension package for advanced battery models,
5
+ research models, and simulation tools.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from importlib.metadata import version
10
+
11
+ import pybamm
12
+ from pybamm_gem.entry_point import Model, models, parameter_sets
13
+
14
+ __version__ = version("PyBaMM-GEM")
15
+
16
+
17
+ def ParameterValues(name: str) -> pybamm.ParameterValues:
18
+ """
19
+ Return a PyBaMM ParameterValues object from a GEM parameter set.
20
+
21
+ Examples
22
+ --------
23
+ >>> import pybamm_gem
24
+ >>> params = pybamm_gem.ParameterValues("Imperial2026")
25
+ """
26
+ values = dict(parameter_sets[name])
27
+ values.pop("chemistry", None)
28
+ values.pop("notcite", None)
29
+ return pybamm.ParameterValues(values)
30
+
31
+
32
+ def about() -> None:
33
+ """
34
+ Display information about the installed PyBaMM-GEM package.
35
+ """
36
+ print(
37
+ f"""
38
+ ════════════════════════════════════════════════════════════
39
+
40
+ PyBaMM-GEM {__version__}
41
+
42
+ Extending PyBaMM {pybamm.__version__} with advanced battery
43
+ models, submodels, and parameter sets.
44
+
45
+ https://github.com/mohammedasher/PyBaMM-GEM
46
+
47
+ ════════════════════════════════════════════════════════════
48
+ """.strip()
49
+ )
50
+
51
+ def version() -> dict[str, str]:
52
+ """
53
+ Return version information for PyBaMM-GEM and the compatible PyBaMM installation.
54
+
55
+ Returns
56
+ -------
57
+ dict
58
+ Dictionary containing GEM and PyBaMM version information.
59
+ """
60
+ return {
61
+ "gem": __version__,
62
+ "pybamm": pybamm.__version__,
63
+ }
64
+
65
+ __all__ = [
66
+ "__version__",
67
+ "pybamm",
68
+ "parameter_sets",
69
+ "ParameterValues",
70
+ "Model",
71
+ "models",
72
+ "about",
73
+ "version",
74
+ ]
75
+
76
+ # Display package banner on import
77
+ about()
pybamm_gem/_version.py ADDED
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,154 @@
1
+ """
2
+ This code is adopted from the PyBaMM project under the BSD-3-Clause
3
+
4
+ Copyright (c) 2018-2024, the PyBaMM team.
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ * Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ """
32
+
33
+
34
+ import importlib.metadata
35
+ import sys
36
+ import textwrap
37
+ from collections.abc import Mapping
38
+ from typing import Callable
39
+
40
+ class EntryPoint(Mapping):
41
+ """
42
+ Dict-like interface for accessing parameter sets and models through entry points in copier template.
43
+ Access via :py:data:`pybamm_gem.parameter_sets` for parameter_sets
44
+ Access via :py:data:`pybamm_gem.Model` for Models
45
+
46
+ Examples
47
+ --------
48
+ Listing available parameter sets:
49
+ >>> import pybamm_gem
50
+ >>> list(pybamm_gem.parameter_sets)
51
+ ['Chen2020', ...]
52
+ >>> list(pybamm_gem.models)
53
+ ['SPM', ...]
54
+
55
+ Get the docstring for a parameter set/model:
56
+
57
+
58
+ >>> print(pybamm_gem.parameter_sets.get_docstring("Ai2020"))
59
+ <BLANKLINE>
60
+ Parameters for the Enertech cell (Ai2020), from the papers :footcite:t:`Ai2019`,
61
+ :footcite:t:`rieger2016new` and references therein.
62
+ ...
63
+
64
+ >>> print(pybamm_gem.models.get_docstring("SPM"))
65
+ <BLANKLINE>
66
+ Single Particle Model (SPM) model of a lithium-ion battery, from :footcite:t:`Marquis2019`. This class differs from the :class:`pybamm.lithium_ion.SPM` model class in that it shows the whole model in a single class. This comes at the cost of flexibility in combining different physical effects, and in general the main SPM class should be used instead.
67
+ ...
68
+ See also: :ref:`adding-parameter-sets`
69
+ """
70
+
71
+ _instances = 0
72
+ def __init__(self, group):
73
+ """Dict of entry points for parameter sets or models, lazily load entry points as"""
74
+ if not hasattr(self, 'initialized'): # Ensure __init__ is called once per instance
75
+ self.initialized = True
76
+ EntryPoint._instances += 1
77
+ self._all_entries = dict()
78
+ self.group = group
79
+ for entry_point in self.get_entries(self.group):
80
+ self._all_entries[entry_point.name] = entry_point
81
+
82
+ @staticmethod
83
+ def get_entries(group_name):
84
+ """Wrapper for the importlib version logic"""
85
+ if sys.version_info < (3, 10): # pragma: no cover
86
+ return importlib.metadata.entry_points()[group_name]
87
+ else:
88
+ return importlib.metadata.entry_points(group=group_name)
89
+
90
+ def __new__(cls, group):
91
+ """Ensure only two instances of entry points exist, one for parameter sets and the other for models"""
92
+ if EntryPoint._instances < 2:
93
+ cls.instance = super().__new__(cls)
94
+ return cls.instance
95
+
96
+ def __getitem__(self, key) -> dict:
97
+ return self._load_entry_point(key)()
98
+
99
+ def _load_entry_point(self, key) -> Callable:
100
+ """Check that ``key`` is a registered ``parameter_sets`` or ``models` ,
101
+ and return the entry point for the parameter set/model, loading it needed."""
102
+ if key not in self._all_entries:
103
+ raise KeyError(f"Unknown parameter set or model: {key}")
104
+ ps = self._all_entries[key]
105
+ try:
106
+ ps = self._all_entries[key] = ps.load()
107
+ except AttributeError:
108
+ pass
109
+ return ps
110
+
111
+ def __iter__(self):
112
+ return self._all_entries.__iter__()
113
+
114
+ def __len__(self) -> int:
115
+ return len(self._all_entries)
116
+
117
+ def get_docstring(self, key):
118
+ """Return the docstring for the ``key`` parameter set or model"""
119
+ return textwrap.dedent(self._load_entry_point(key).__doc__)
120
+
121
+ def __getattribute__(self, name):
122
+ try:
123
+ return super().__getattribute__(name)
124
+ except AttributeError as error:
125
+ raise error
126
+
127
+ #: Singleton Instance of :class:ParameterSets """
128
+ parameter_sets = EntryPoint(group="parameter_sets")
129
+
130
+ #: Singleton Instance of :class:ModelEntryPoints"""
131
+ models = EntryPoint(group="models")
132
+
133
+ def Model(model:str):
134
+ """
135
+ Returns the loaded model object
136
+
137
+ Parameters
138
+ ----------
139
+ model : str
140
+ The model name or author name of the model mentioned at the model entry point.
141
+ Returns
142
+ -------
143
+ pybamm.model
144
+ Model object of the initialised model.
145
+ Examples
146
+ --------
147
+ Listing available models:
148
+ >>> import pybamm_gem
149
+ >>> list(pybamm_gem.models)
150
+ ['SPM', ...]
151
+ >>> pybamm_gem.Model('Author/Year')
152
+ <pybamm_gem.models.input.SPM.SPM object>
153
+ """
154
+ return models[model]
@@ -0,0 +1,200 @@
1
+ """
2
+ This code is adopted from the PyBaMM project under the BSD-3-Clause
3
+
4
+ Copyright (c) 2018-2024, the PyBaMM team.
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ * Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ """
32
+
33
+
34
+ #
35
+ # Basic Reservoir Model
36
+ #
37
+ import pybamm
38
+
39
+ class BasicReservoir(pybamm.lithium_ion.BaseModel):
40
+ """Reservoir model of a lithium-ion battery, from
41
+ :footcite:t:`Marquis2019`.
42
+
43
+ Parameters
44
+ ----------
45
+ name : str, optional
46
+ The name of the model.
47
+ """
48
+
49
+ def __init__(self, name="Reservoir Model"):
50
+ super().__init__({}, name)
51
+ pybamm.citations.register("Marquis2019")
52
+ # `param` is a class containing all the relevant parameters and functions for
53
+ # this model. These are purely symbolic at this stage, and will be set by the
54
+ # `ParameterValues` class when the model is processed.
55
+ param = self.param
56
+
57
+ ######################
58
+ # Variables
59
+ ######################
60
+ # Variables that depend on time only are created without a domain
61
+ Q = pybamm.Variable("Discharge capacity [A.h]")
62
+ # Variables that vary spatially are created with a domain
63
+ sto_n = pybamm.Variable(
64
+ "Average negative particle stoichiometry",
65
+ domain="current collector",
66
+ bounds=(0, 1),
67
+ )
68
+ sto_p = pybamm.Variable(
69
+ "Average positive particle stoichiometry",
70
+ domain="current collector",
71
+ bounds=(0, 1),
72
+ )
73
+
74
+ # Constant temperature
75
+ T = param.T_init
76
+
77
+ ######################
78
+ # Other set-up
79
+ ######################
80
+
81
+ # Current density
82
+ i_cell = param.current_density_with_time
83
+ a_n = 3 * param.n.prim.epsilon_s_av / param.n.prim.R_typ
84
+ a_p = 3 * param.p.prim.epsilon_s_av / param.p.prim.R_typ
85
+ j_n = i_cell / (param.n.L * a_n)
86
+ j_p = -i_cell / (param.p.L * a_p)
87
+
88
+ ######################
89
+ # State of Charge
90
+ ######################
91
+ I = param.current_with_time
92
+ # The `rhs` dictionary contains differential equations, with the key being the
93
+ # variable in the d/dt
94
+ self.rhs[Q] = I / 3600
95
+ # Initial conditions must be provided for the ODEs
96
+ self.initial_conditions[Q] = pybamm.Scalar(0)
97
+
98
+ ######################
99
+ # Particles
100
+ ######################
101
+
102
+ self.rhs[sto_n] = -i_cell / (
103
+ param.n.L * param.n.prim.epsilon_s_av * param.n.prim.c_max * param.F
104
+ )
105
+ self.rhs[sto_p] = i_cell / (
106
+ param.p.L * param.p.prim.epsilon_s_av * param.p.prim.c_max * param.F
107
+ )
108
+ # c_n_init and c_p_init are functions of r and x, but for the reservoir model
109
+ # we take the x-averaged and r-averaged value since there are no x-dependence
110
+ # nor r-dependencein the particles
111
+ self.initial_conditions[sto_n] = (
112
+ pybamm.x_average(pybamm.r_average(param.n.prim.c_init)) / param.n.prim.c_max
113
+ )
114
+ self.initial_conditions[sto_p] = (
115
+ pybamm.x_average(pybamm.r_average(param.p.prim.c_init)) / param.p.prim.c_max
116
+ )
117
+
118
+ self.events += [
119
+ pybamm.Event(
120
+ "Minimum negative particle surface stoichiometry",
121
+ sto_n - 0.01,
122
+ ),
123
+ pybamm.Event(
124
+ "Maximum negative particle surface stoichiometry",
125
+ (1 - 0.01) - sto_n,
126
+ ),
127
+ pybamm.Event(
128
+ "Minimum positive particle surface stoichiometry",
129
+ sto_p - 0.01,
130
+ ),
131
+ pybamm.Event(
132
+ "Maximum positive particle surface stoichiometry",
133
+ (1 - 0.01) - sto_p,
134
+ ),
135
+ ]
136
+
137
+ # Note that the reservoir model does not have any algebraic equations, so the
138
+ # `algebraic` dictionary remains empty
139
+
140
+ ######################
141
+ # (Some) variables
142
+ ######################
143
+ # Interfacial reactions
144
+ RT_F = param.R * T / param.F
145
+ j0_n = param.n.prim.j0(param.c_e_init_av, sto_n * param.n.prim.c_max, T)
146
+ j0_p = param.p.prim.j0(param.c_e_init_av, sto_p * param.p.prim.c_max, T)
147
+ eta_n = (2 / param.n.prim.ne) * RT_F * pybamm.arcsinh(j_n / (2 * j0_n))
148
+ eta_p = (2 / param.p.prim.ne) * RT_F * pybamm.arcsinh(j_p / (2 * j0_p))
149
+ phi_s_n = 0
150
+ phi_e = -eta_n - param.n.prim.U(sto_n, T)
151
+ phi_s_p = eta_p + phi_e + param.p.prim.U(sto_p, T)
152
+ V = phi_s_p
153
+ num_cells = pybamm.Parameter(
154
+ "Number of cells connected in series to make a battery"
155
+ )
156
+ c_s_n = sto_n * param.n.prim.c_max
157
+ c_s_p = sto_p * param.p.prim.c_max
158
+
159
+ whole_cell = ["negative electrode", "separator", "positive electrode"]
160
+ # The `variables` dictionary contains all variables that might be useful for
161
+ # visualising the solution of the model
162
+ # Primary broadcasts are used to broadcast scalar quantities across a domain
163
+ # into a vector of the right shape, for multiplying with other vectors
164
+ self.variables = {
165
+ "Time [s]": pybamm.t,
166
+ "Discharge capacity [A.h]": Q,
167
+ "X-averaged negative particle concentration [mol.m-3]": pybamm.PrimaryBroadcast(
168
+ c_s_n, "negative particle"
169
+ ),
170
+ "Negative particle surface "
171
+ "concentration [mol.m-3]": pybamm.PrimaryBroadcast(
172
+ c_s_n, "negative electrode"
173
+ ),
174
+ "Electrolyte concentration [mol.m-3]": pybamm.PrimaryBroadcast(
175
+ param.c_e_init_av, whole_cell
176
+ ),
177
+ "X-averaged positive particle concentration [mol.m-3]": pybamm.PrimaryBroadcast(
178
+ c_s_p, "positive particle"
179
+ ),
180
+ "Positive particle surface "
181
+ "concentration [mol.m-3]": pybamm.PrimaryBroadcast(
182
+ c_s_p, "positive electrode"
183
+ ),
184
+ "Current [A]": I,
185
+ "Current variable [A]": I, # for compatibility with pybamm.Experiment
186
+ "Negative electrode potential [V]": pybamm.PrimaryBroadcast(
187
+ phi_s_n, "negative electrode"
188
+ ),
189
+ "Electrolyte potential [V]": pybamm.PrimaryBroadcast(phi_e, whole_cell),
190
+ "Positive electrode potential [V]": pybamm.PrimaryBroadcast(
191
+ phi_s_p, "positive electrode"
192
+ ),
193
+ "Voltage [V]": V,
194
+ "Battery voltage [V]": V * num_cells,
195
+ }
196
+ # Events specify points at which a solution should terminate
197
+ self.events += [
198
+ pybamm.Event("Minimum voltage [V]", V - param.voltage_low_cut),
199
+ pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - V),
200
+ ]
@@ -0,0 +1,215 @@
1
+ """
2
+ This code is adopted from the PyBaMM project under the BSD-3-Clause
3
+
4
+ Copyright (c) 2018-2024, the PyBaMM team.
5
+ All rights reserved.
6
+
7
+ Redistribution and use in source and binary forms, with or without
8
+ modification, are permitted provided that the following conditions are met:
9
+
10
+ * Redistributions of source code must retain the above copyright notice, this
11
+ list of conditions and the following disclaimer.
12
+
13
+ * Redistributions in binary form must reproduce the above copyright notice,
14
+ this list of conditions and the following disclaimer in the documentation
15
+ and/or other materials provided with the distribution.
16
+
17
+ * Neither the name of the copyright holder nor the names of its
18
+ contributors may be used to endorse or promote products derived from
19
+ this software without specific prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31
+ """
32
+
33
+
34
+ #
35
+ # Basic Single Particle Model (SPM)
36
+ #
37
+ import pybamm
38
+
39
+
40
+ class SPM(pybamm.lithium_ion.BaseModel):
41
+ """Single Particle Model (SPM) model of a lithium-ion battery, from
42
+ :footcite:t:`Marquis2019`.
43
+
44
+ This class differs from the :class:`pybamm.lithium_ion.SPM` model class in that it
45
+ shows the whole model in a single class. This comes at the cost of flexibility in
46
+ combining different physical effects, and in general the main SPM class should be
47
+ used instead.
48
+
49
+ Parameters
50
+ ----------
51
+ name : str, optional
52
+ The name of the model.
53
+ """
54
+
55
+ def __init__(self, name="Single Particle Model"):
56
+ super().__init__({}, name)
57
+ pybamm.citations.register("Marquis2019")
58
+ # `param` is a class containing all the relevant parameters and functions for
59
+ # this model. These are purely symbolic at this stage, and will be set by the
60
+ # `ParameterValues` class when the model is processed.
61
+ param = self.param
62
+
63
+ ######################
64
+ # Variables
65
+ ######################
66
+ # Variables that depend on time only are created without a domain
67
+ Q = pybamm.Variable("Discharge capacity [A.h]")
68
+ # Variables that vary spatially are created with a domain
69
+ c_s_n = pybamm.Variable(
70
+ "X-averaged negative particle concentration [mol.m-3]",
71
+ domain="negative particle",
72
+ )
73
+ c_s_p = pybamm.Variable(
74
+ "X-averaged positive particle concentration [mol.m-3]",
75
+ domain="positive particle",
76
+ )
77
+
78
+ # Constant temperature
79
+ T = param.T_init
80
+
81
+ ######################
82
+ # Other set-up
83
+ ######################
84
+
85
+ # Current density
86
+ i_cell = param.current_density_with_time
87
+ a_n = 3 * param.n.prim.epsilon_s_av / param.n.prim.R_typ
88
+ a_p = 3 * param.p.prim.epsilon_s_av / param.p.prim.R_typ
89
+ j_n = i_cell / (param.n.L * a_n)
90
+ j_p = -i_cell / (param.p.L * a_p)
91
+
92
+ ######################
93
+ # State of Charge
94
+ ######################
95
+ I = param.current_with_time
96
+ # The `rhs` dictionary contains differential equations, with the key being the
97
+ # variable in the d/dt
98
+ self.rhs[Q] = I / 3600
99
+ # Initial conditions must be provided for the ODEs
100
+ self.initial_conditions[Q] = pybamm.Scalar(0)
101
+
102
+ ######################
103
+ # Particles
104
+ ######################
105
+
106
+ # The div and grad operators will be converted to the appropriate matrix
107
+ # multiplication at the discretisation stage
108
+ N_s_n = -param.n.prim.D(c_s_n, T) * pybamm.grad(c_s_n)
109
+ N_s_p = -param.p.prim.D(c_s_p, T) * pybamm.grad(c_s_p)
110
+ self.rhs[c_s_n] = -pybamm.div(N_s_n)
111
+ self.rhs[c_s_p] = -pybamm.div(N_s_p)
112
+ # Surf takes the surface value of a variable, i.e. its boundary value on the
113
+ # right side. This is also accessible via `boundary_value(x, "right")`, with
114
+ # "left" providing the boundary value of the left side
115
+ c_s_surf_n = pybamm.surf(c_s_n)
116
+ c_s_surf_p = pybamm.surf(c_s_p)
117
+ # Boundary conditions must be provided for equations with spatial derivatives
118
+ self.boundary_conditions[c_s_n] = {
119
+ "left": (pybamm.Scalar(0), "Neumann"),
120
+ "right": (
121
+ -j_n / (param.F * pybamm.surf(param.n.prim.D(c_s_n, T))),
122
+ "Neumann",
123
+ ),
124
+ }
125
+ self.boundary_conditions[c_s_p] = {
126
+ "left": (pybamm.Scalar(0), "Neumann"),
127
+ "right": (
128
+ -j_p / (param.F * pybamm.surf(param.p.prim.D(c_s_p, T))),
129
+ "Neumann",
130
+ ),
131
+ }
132
+ # c_n_init and c_p_init are functions of r and x, but for the SPM we
133
+ # take the x-averaged value since there is no x-dependence in the particles
134
+ self.initial_conditions[c_s_n] = pybamm.x_average(param.n.prim.c_init)
135
+ self.initial_conditions[c_s_p] = pybamm.x_average(param.p.prim.c_init)
136
+ # Events specify points at which a solution should terminate
137
+ sto_surf_n = c_s_surf_n / param.n.prim.c_max
138
+ sto_surf_p = c_s_surf_p / param.p.prim.c_max
139
+ self.events += [
140
+ pybamm.Event(
141
+ "Minimum negative particle surface stoichiometry",
142
+ pybamm.min(sto_surf_n) - 0.01,
143
+ ),
144
+ pybamm.Event(
145
+ "Maximum negative particle surface stoichiometry",
146
+ (1 - 0.01) - pybamm.max(sto_surf_n),
147
+ ),
148
+ pybamm.Event(
149
+ "Minimum positive particle surface stoichiometry",
150
+ pybamm.min(sto_surf_p) - 0.01,
151
+ ),
152
+ pybamm.Event(
153
+ "Maximum positive particle surface stoichiometry",
154
+ (1 - 0.01) - pybamm.max(sto_surf_p),
155
+ ),
156
+ ]
157
+
158
+ # Note that the SPM does not have any algebraic equations, so the `algebraic`
159
+ # dictionary remains empty
160
+
161
+ ######################
162
+ # (Some) variables
163
+ ######################
164
+ # Interfacial reactions
165
+ RT_F = param.R * T / param.F
166
+ j0_n = param.n.prim.j0(param.c_e_init_av, c_s_surf_n, T)
167
+ j0_p = param.p.prim.j0(param.c_e_init_av, c_s_surf_p, T)
168
+ eta_n = (2 / param.n.prim.ne) * RT_F * pybamm.arcsinh(j_n / (2 * j0_n))
169
+ eta_p = (2 / param.p.prim.ne) * RT_F * pybamm.arcsinh(j_p / (2 * j0_p))
170
+ phi_s_n = 0
171
+ phi_e = -eta_n - param.n.prim.U(sto_surf_n, T)
172
+ phi_s_p = eta_p + phi_e + param.p.prim.U(sto_surf_p, T)
173
+ V = phi_s_p
174
+ num_cells = pybamm.Parameter(
175
+ "Number of cells connected in series to make a battery"
176
+ )
177
+
178
+ whole_cell = ["negative electrode", "separator", "positive electrode"]
179
+ # The `variables` dictionary contains all variables that might be useful for
180
+ # visualising the solution of the model
181
+ # Primary broadcasts are used to broadcast scalar quantities across a domain
182
+ # into a vector of the right shape, for multiplying with other vectors
183
+ self.variables = {
184
+ "Time [s]": pybamm.t,
185
+ "Discharge capacity [A.h]": Q,
186
+ "X-averaged negative particle concentration [mol.m-3]": c_s_n,
187
+ "Negative particle surface "
188
+ "concentration [mol.m-3]": pybamm.PrimaryBroadcast(
189
+ c_s_surf_n, "negative electrode"
190
+ ),
191
+ "Electrolyte concentration [mol.m-3]": pybamm.PrimaryBroadcast(
192
+ param.c_e_init_av, whole_cell
193
+ ),
194
+ "X-averaged positive particle concentration [mol.m-3]": c_s_p,
195
+ "Positive particle surface "
196
+ "concentration [mol.m-3]": pybamm.PrimaryBroadcast(
197
+ c_s_surf_p, "positive electrode"
198
+ ),
199
+ "Current [A]": I,
200
+ "Current variable [A]": I, # for compatibility with pybamm.Experiment
201
+ "Negative electrode potential [V]": pybamm.PrimaryBroadcast(
202
+ phi_s_n, "negative electrode"
203
+ ),
204
+ "Electrolyte potential [V]": pybamm.PrimaryBroadcast(phi_e, whole_cell),
205
+ "Positive electrode potential [V]": pybamm.PrimaryBroadcast(
206
+ phi_s_p, "positive electrode"
207
+ ),
208
+ "Voltage [V]": V,
209
+ "Battery voltage [V]": V * num_cells,
210
+ }
211
+ # Events specify points at which a solution should terminate
212
+ self.events += [
213
+ pybamm.Event("Minimum voltage [V]", V - param.voltage_low_cut),
214
+ pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - V),
215
+ ]