#
# Basic Single Particle Model (SPM)
#
import pybamm
from .base_lithium_ion_model import BaseModel
[docs]
class BasicSPM(BaseModel):
"""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.
Parameters
----------
name : str, optional
The name of the model.
"""
def __init__(self, name="Single Particle Model"):
super().__init__({}, name)
pybamm.citations.register("Marquis2019")
# `param` is a class containing all the relevant parameters and functions for
# this model. These are purely symbolic at this stage, and will be set by the
# `ParameterValues` class when the model is processed.
######################
# Variables
######################
# Variables that depend on time only are created without a domain
Q = pybamm.Variable("Discharge capacity [A.h]")
# Variables that vary spatially are created with a domain
c_s_n = pybamm.Variable(
"X-averaged negative particle concentration [mol.m-3]",
domain="negative particle",
)
c_s_p = pybamm.Variable(
"X-averaged positive particle concentration [mol.m-3]",
domain="positive particle",
)
# Constant temperature
T = self.param.T_init
######################
# Other set-up
######################
# Current density
i_cell = self.param.current_density_with_time
a_n = 3 * self.param.n.prim.epsilon_s_av / self.param.n.prim.R_typ
a_p = 3 * self.param.p.prim.epsilon_s_av / self.param.p.prim.R_typ
j_n = i_cell / (self.param.n.L * a_n)
j_p = -i_cell / (self.param.p.L * a_p)
######################
# State of Charge
######################
I = self.param.current_with_time
# The `rhs` dictionary contains differential equations, with the key being the
# variable in the d/dt
self.rhs[Q] = I / 3600
# Initial conditions must be provided for the ODEs
self.initial_conditions[Q] = pybamm.Scalar(0)
######################
# Particles
######################
# The div and grad operators will be converted to the appropriate matrix
# multiplication at the discretisation stage
N_s_n = -self.param.n.prim.D(c_s_n, T) * pybamm.grad(c_s_n)
N_s_p = -self.param.p.prim.D(c_s_p, T) * pybamm.grad(c_s_p)
self.rhs[c_s_n] = -pybamm.div(N_s_n)
self.rhs[c_s_p] = -pybamm.div(N_s_p)
# Surf takes the surface value of a variable, i.e. its boundary value on the
# right side. This is also accessible via `boundary_value(x, "right")`, with
# "left" providing the boundary value of the left side
c_s_surf_n = pybamm.surf(c_s_n)
c_s_surf_p = pybamm.surf(c_s_p)
# Boundary conditions must be provided for equations with spatial derivatives
self.boundary_conditions[c_s_n] = {
"left": (pybamm.Scalar(0), "Neumann"),
"right": (
-j_n / (self.param.F * pybamm.surf(self.param.n.prim.D(c_s_n, T))),
"Neumann",
),
}
self.boundary_conditions[c_s_p] = {
"left": (pybamm.Scalar(0), "Neumann"),
"right": (
-j_p / (self.param.F * pybamm.surf(self.param.p.prim.D(c_s_p, T))),
"Neumann",
),
}
# c_n_init and c_p_init are functions of r and x, but for the SPM we
# take the x-averaged value since there is no x-dependence in the particles
self.initial_conditions[c_s_n] = pybamm.x_average(self.param.n.prim.c_init)
self.initial_conditions[c_s_p] = pybamm.x_average(self.param.p.prim.c_init)
# Events specify points at which a solution should terminate
sto_surf_n = c_s_surf_n / self.param.n.prim.c_max
sto_surf_p = c_s_surf_p / self.param.p.prim.c_max
self.events += [
pybamm.Event(
"Minimum negative particle surface stoichiometry",
pybamm.min(sto_surf_n) - 0.01,
),
pybamm.Event(
"Maximum negative particle surface stoichiometry",
(1 - 0.01) - pybamm.max(sto_surf_n),
),
pybamm.Event(
"Minimum positive particle surface stoichiometry",
pybamm.min(sto_surf_p) - 0.01,
),
pybamm.Event(
"Maximum positive particle surface stoichiometry",
(1 - 0.01) - pybamm.max(sto_surf_p),
),
]
# Note that the SPM does not have any algebraic equations, so the `algebraic`
# dictionary remains empty
######################
# (Some) variables
######################
# Interfacial reactions
RT_F = self.param.R * T / self.param.F
j0_n = self.param.n.prim.j0(self.param.c_e_init_av, c_s_surf_n, T)
j0_p = self.param.p.prim.j0(self.param.c_e_init_av, c_s_surf_p, T)
eta_n = (2 / self.param.n.prim.ne) * RT_F * pybamm.arcsinh(j_n / (2 * j0_n))
eta_p = (2 / self.param.p.prim.ne) * RT_F * pybamm.arcsinh(j_p / (2 * j0_p))
phi_s_n = 0
phi_e = -eta_n - self.param.n.prim.U(sto_surf_n, T)
phi_s_p = eta_p + phi_e + self.param.p.prim.U(sto_surf_p, T)
V = phi_s_p
num_cells = pybamm.Parameter(
"Number of cells connected in series to make a battery"
)
whole_cell = ["negative electrode", "separator", "positive electrode"]
# The `variables` dictionary contains all variables that might be useful for
# visualising the solution of the model
# Primary broadcasts are used to broadcast scalar quantities across a domain
# into a vector of the right shape, for multiplying with other vectors
self.variables = {
"Time [s]": pybamm.t,
"Discharge capacity [A.h]": Q,
"X-averaged negative particle concentration [mol.m-3]": c_s_n,
"Negative particle surface "
"concentration [mol.m-3]": pybamm.PrimaryBroadcast(
c_s_surf_n, "negative electrode"
),
"Electrolyte concentration [mol.m-3]": pybamm.PrimaryBroadcast(
self.param.c_e_init_av, whole_cell
),
"X-averaged positive particle concentration [mol.m-3]": c_s_p,
"Positive particle surface "
"concentration [mol.m-3]": pybamm.PrimaryBroadcast(
c_s_surf_p, "positive electrode"
),
"Current [A]": I,
"Current variable [A]": I, # for compatibility with pybamm.Experiment
"Negative electrode potential [V]": pybamm.PrimaryBroadcast(
phi_s_n, "negative electrode"
),
"Electrolyte potential [V]": pybamm.PrimaryBroadcast(phi_e, whole_cell),
"Positive electrode potential [V]": pybamm.PrimaryBroadcast(
phi_s_p, "positive electrode"
),
"Voltage [V]": V,
"Battery voltage [V]": V * num_cells,
}
# Events specify points at which a solution should terminate
self.events += [
pybamm.Event("Minimum voltage [V]", V - self.param.voltage_low_cut),
pybamm.Event("Maximum voltage [V]", self.param.voltage_high_cut - V),
]