#
# Standard parameters for lithium-ion battery models
#
import pybamm
from .base_parameters import BaseParameters, NullParameters
[docs]
class LithiumIonParameters(BaseParameters):
"""
Standard parameters for lithium-ion battery models
Parameters
----------
options : dict, optional
A dictionary of options to be passed to the parameters, see
:class:`pybamm.BatteryModelOptions`.
"""
def __init__(self, options=None):
self.options = options
# Get geometric, electrical and thermal parameters
self.geo = pybamm.GeometricParameters(options)
self.elec = pybamm.electrical_parameters
self.therm = pybamm.thermal_parameters
# Initialize domain parameters
self.n = DomainLithiumIonParameters("negative", self)
self.s = DomainLithiumIonParameters("separator", self)
self.p = DomainLithiumIonParameters("positive", self)
self.domain_params = {
"negative": self.n,
"separator": self.s,
"positive": self.p,
}
# Set parameters
self._set_parameters()
def _set_parameters(self):
"""Defines the dimensional parameters"""
# Physical constants
self.R = pybamm.constants.R
self.F = pybamm.constants.F
self.k_b = pybamm.constants.k_b
self.q_e = pybamm.constants.q_e
# Thermal parameters
self.T_ref = self.therm.T_ref
self.T_init = self.therm.T_init
self.T_amb = self.therm.T_amb
self.T_amb_av = self.therm.T_amb_av
self.h_edge = self.therm.h_edge
self.h_total = self.therm.h_total
self.rho_c_p_eff = self.therm.rho_c_p_eff
self.lambda_eff = self.therm.lambda_eff
self.cell_heat_capacity = self.therm.cell_heat_capacity
# pouch bcs
self.h_edge_x_min = self.therm.h_edge_x_min
self.h_edge_x_max = self.therm.h_edge_x_max
self.h_edge_y_max = self.therm.h_edge_y_max
self.h_edge_y_min = self.therm.h_edge_y_min
self.h_edge_z_min = self.therm.h_edge_z_min
self.h_edge_z_max = self.therm.h_edge_z_max
# cylinder specific bcs
self.h_edge_radial_min = self.therm.h_edge_radial_min
self.h_edge_radial_max = self.therm.h_edge_radial_max
# Macroscale geometry
self.L_x = self.geo.L_x
self.L = self.geo.L
self.L_y = self.geo.L_y
self.L_z = self.geo.L_z
self.r_inner = self.geo.r_inner
self.r_outer = self.geo.r_outer
self.A_cc = self.geo.A_cc
self.A_cooling = self.geo.A_cooling
self.V_cell = self.geo.V_cell
# Electrical
self.current_with_time = self.elec.current_with_time
self.current_density_with_time = self.elec.current_density_with_time
self.Q = self.elec.Q
self.R_contact = self.elec.R_contact
self.n_electrodes_parallel = self.geo.n_electrodes_parallel
self.n_cells = self.elec.n_cells
self.voltage_low_cut = self.elec.voltage_low_cut
self.voltage_high_cut = self.elec.voltage_high_cut
self.ocp_soc_0 = self.elec.ocp_soc_0
self.ocp_soc_100 = self.elec.ocp_soc_100
# Domain parameters
for domain in self.domain_params.values():
domain._set_parameters()
# Electrolyte properties
self.epsilon_init = pybamm.concatenation(
*[
self.domain_params[domain.split()[0]].epsilon_init
for domain in self.options.whole_cell_domains
]
)
# Required by lithium plating and lithium metal plating reactions
self.V_bar_Li = pybamm.Parameter(
"Lithium metal partial molar volume [m3.mol-1]"
)
# Initial conditions
# Note: the initial concentration in the electrodes can be set as a function
# of through-cell position, so is defined later as a function
self.c_e_init = pybamm.Parameter(
"Initial concentration in electrolyte [mol.m-3]"
)
self.c_e_init_av = pybamm.xyz_average(self.c_e_init)
self.c_e_init_av.print_name = "c_e_init"
self.alpha_T_cell = pybamm.Parameter(
"Cell thermal expansion coefficient [m.K-1]"
)
# Total lithium
# Electrolyte
c_e_av_init = pybamm.xyz_average(self.epsilon_init) * self.c_e_init
self.n_Li_e_init = c_e_av_init * self.L_x * self.A_cc
self.n_Li_particles_init = self.n.n_Li_init + self.p.n_Li_init
self.n_Li_init = self.n_Li_particles_init + self.n_Li_e_init
self.Q_Li_particles_init = self.n_Li_particles_init * self.F / 3600
self.Q_Li_init = self.n_Li_init * self.F / 3600
# Reference OCP based on initial concentration
self.ocv_init = self.p.prim.U_init - self.n.prim.U_init
# Some scales
self.thermal_voltage = self.R * self.T_ref / self.F
self.I_typ = self.Q / self.A_cc
self.a_j_scale = self.I_typ / self.L_x
def chi(self, c_e, T):
"""
Thermodynamic factor:
(1-2*t_plus) is for Nernst-Planck,
2*(1-t_plus) for Stefan-Maxwell,
see Bizeray et al (2016) "Resolving a discrepancy ...".
"""
return (2 * (1 - self.t_plus(c_e, T))) * (self.thermodynamic_factor(c_e, T))
def chiRT_over_Fc(self, c_e, T):
"""
chi * (1 + Theta * T) / c,
as it appears in the electrolyte potential equation
"""
tol = pybamm.settings.tolerances["chi__c_e"]
c_e = pybamm.maximum(c_e, tol)
return (self.R * T / self.F) * self.chi(c_e, T) / c_e
def t_plus(self, c_e, T):
"""Cation transference number"""
inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T}
return pybamm.FunctionParameter("Cation transference number", inputs)
def thermodynamic_factor(self, c_e, T):
"""Thermodynamic factor"""
inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T}
return pybamm.FunctionParameter("Thermodynamic factor", inputs)
def D_e(self, c_e, T):
"""Dimensional diffusivity in electrolyte"""
tol = pybamm.settings.tolerances["D_e__c_e"]
c_e = pybamm.maximum(c_e, tol)
inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T}
return pybamm.FunctionParameter("Electrolyte diffusivity [m2.s-1]", inputs)
def kappa_e(self, c_e, T):
"""Dimensional electrolyte conductivity"""
tol = pybamm.settings.tolerances["kappa_e__c_e"]
c_e = pybamm.maximum(c_e, tol)
inputs = {"Electrolyte concentration [mol.m-3]": c_e, "Temperature [K]": T}
return pybamm.FunctionParameter("Electrolyte conductivity [S.m-1]", inputs)
def j0_Li_metal(self, c_e, c_Li, T):
"""Dimensional exchange-current density for lithium metal electrode [A.m-2]"""
inputs = {
"Electrolyte concentration [mol.m-3]": c_e,
"Lithium metal concentration [mol.m-3]": c_Li,
"Temperature [K]": T,
}
return pybamm.FunctionParameter(
"Exchange-current density for lithium metal electrode [A.m-2]", inputs
)
class DomainLithiumIonParameters(BaseParameters):
def __init__(self, domain, main_param):
self.domain = domain
self.main_param = main_param
self.geo = getattr(main_param.geo, domain[0])
self.therm = getattr(main_param.therm, domain[0])
if domain != "separator":
self.prim = ParticleLithiumIonParameters("primary", self)
phases_option = int(getattr(main_param.options, domain)["particle phases"])
if phases_option >= 2:
self.sec = ParticleLithiumIonParameters("secondary", self)
else:
self.sec = NullParameters()
else:
self.prim = NullParameters()
self.sec = NullParameters()
self.phase_params = {"primary": self.prim, "secondary": self.sec}
def _set_parameters(self):
main = self.main_param
domain, Domain = self.domain_Domain
# Parameters that appear in the separator
self.b_e = self.geo.b_e
self.tau_e = self.geo.tau_e
self.L = self.geo.L
# Thermal
self.rho_c_p = self.therm.rho_c_p
self.lambda_ = self.therm.lambda_
self.h_cc = self.therm.h_cc
self.h_tab = self.therm.h_tab
if domain == "separator":
x = pybamm.standard_spatial_vars.x_s
y = pybamm.PrimaryBroadcast(pybamm.standard_spatial_vars.y, "separator")
z = pybamm.PrimaryBroadcast(pybamm.standard_spatial_vars.z, "separator")
self.epsilon_init = pybamm.FunctionParameter(
"Separator porosity",
{
"Through-cell distance (x) [m]": x,
"Horizontal distance (y) [m]": y,
"Vertical distance (z) [m]": z,
},
)
self.epsilon_inactive = 1 - self.epsilon_init
return
self.rho_c_p_cc = self.therm.rho_c_p_cc
self.lambda_cc = self.therm.lambda_cc
x = pybamm.SpatialVariable(
f"x_{domain[0]}",
domain=[f"{domain} electrode"],
auxiliary_domains={"secondary": "current collector"},
coord_sys="cartesian",
)
y = pybamm.PrimaryBroadcast(
pybamm.standard_spatial_vars.y, f"{domain} electrode"
)
z = pybamm.PrimaryBroadcast(
pybamm.standard_spatial_vars.z, f"{domain} electrode"
)
# Macroscale geometry
self.L_cc = self.geo.L_cc
for phase in self.phase_params.values():
phase._set_parameters()
# Tab geometry (for pouch cells)
self.L_tab = self.geo.L_tab
self.centre_y_tab = self.geo.centre_y_tab
self.centre_z_tab = self.geo.centre_z_tab
self.A_tab = self.geo.A_tab
# Particle properties
self.sigma_cc = pybamm.Parameter(
f"{Domain} current collector conductivity [S.m-1]"
)
if main.options.electrode_types[domain] == "porous":
self.epsilon_init = pybamm.FunctionParameter(
f"{Domain} electrode porosity",
{
"Through-cell distance (x) [m]": x,
"Horizontal distance (y) [m]": y,
"Vertical distance (z) [m]": z,
},
)
epsilon_s_tot = sum(phase.epsilon_s for phase in self.phase_params.values())
self.epsilon_inactive = 1 - self.epsilon_init - epsilon_s_tot
self.Q_init = sum(phase.Q_init for phase in self.phase_params.values())
# Use primary phase to set the reference potential
self.n_Li_init = sum(phase.n_Li_init for phase in self.phase_params.values())
self.Q_Li_init = sum(phase.Q_Li_init for phase in self.phase_params.values())
# Tortuosity parameters
self.b_s = self.geo.b_s
self.tau_s = self.geo.tau_s
# Utilisation parameters
self.u_init = pybamm.Parameter(
f"Initial {domain} electrode interface utilisation"
)
self.beta_utilisation = pybamm.Parameter(
f"{Domain} electrode current-driven interface utilisation factor [m3.mol-1]"
)
def C_dl(self, T):
"""Dimensional double-layer capacity [F.m-2]"""
inputs = {"Temperature [K]": T}
Domain = self.domain.capitalize()
return pybamm.FunctionParameter(
f"{Domain} electrode double-layer capacity [F.m-2]", inputs
)
def sigma(self, T):
"""Dimensional electrical conductivity in electrode"""
inputs = {"Temperature [K]": T}
Domain = self.domain.capitalize()
return pybamm.FunctionParameter(
f"{Domain} electrode conductivity [S.m-1]", inputs
)
def LAM_rate_current(self, i, T):
"""
Dimensional rate of loss of active material as a function of applied current
density
"""
Domain = self.domain.capitalize()
inputs = {"Total current density [A.m-2]": i, "Temperature [K]": T}
return pybamm.FunctionParameter(
f"{Domain} electrode current-driven LAM rate", inputs
)
class ParticleLithiumIonParameters(BaseParameters):
def __init__(self, phase, domain_param):
self.domain_param = domain_param
self.domain = domain_param.domain
self.main_param = domain_param.main_param
self.phase = phase
self.set_phase_name()
if self.phase == "primary":
self.geo = domain_param.geo.prim
elif self.phase == "secondary":
self.geo = domain_param.geo.sec
self.options = getattr(self.main_param.options, self.domain)
def _set_parameters(self):
main = self.main_param
domain, Domain = self.domain_Domain
phase_name = self.phase_name
pref = self.phase_prefactor
# Electrochemical reactions
self.ne = pybamm.Scalar(1)
# Intercalation kinetics
self.mhc_lambda = pybamm.Parameter(
f"{pref}{Domain} electrode reorganization energy [eV]"
)
self.alpha_bv = pybamm.Parameter(
f"{pref}{Domain} electrode Butler-Volmer transfer coefficient"
)
# SEI parameters
self.V_bar_sei = pybamm.Parameter(f"{pref}SEI partial molar volume [m3.mol-1]")
self.j0_sei = pybamm.Parameter(
f"{pref}SEI reaction exchange current density [A.m-2]"
)
self.R_sei = pybamm.Parameter(f"{pref}SEI resistivity [Ohm.m]")
self.D_sol = pybamm.Parameter(f"{pref}SEI solvent diffusivity [m2.s-1]")
self.c_sol = pybamm.Parameter(f"{pref}Bulk solvent concentration [mol.m-3]")
self.U_sei = pybamm.Parameter(f"{pref}SEI open-circuit potential [V]")
self.kappa_inner = pybamm.Parameter(f"{pref}SEI electron conductivity [S.m-1]")
self.D_li = pybamm.Parameter(
f"{pref}SEI lithium interstitial diffusivity [m2.s-1]"
)
self.c_li_0 = pybamm.Parameter(
f"{pref}Lithium interstitial reference concentration [mol.m-3]"
)
self.kappa_Li_ion = pybamm.Parameter(
f"{pref}SEI lithium ion conductivity [S.m-1]"
)
self.L_sei_0 = pybamm.Parameter(f"{pref}Initial SEI thickness [m]")
self.L_sei_cr0 = pybamm.Parameter(f"{pref}Initial SEI on cracks thickness [m]")
self.L_tunneling = pybamm.Parameter(
f"{pref}Tunneling distance for electrons [m]"
)
self.beta_tunnelling = pybamm.Parameter(f"{pref}Tunneling barrier factor [m-1]")
self.E_sei = pybamm.Parameter(f"{pref}SEI growth activation energy [J.mol-1]")
self.alpha_SEI = pybamm.Parameter(f"{pref}SEI growth transfer coefficient")
self.z_sei = pybamm.Parameter(f"{pref}Ratio of lithium moles to SEI moles")
# EC reaction
self.c_ec_0 = pybamm.Parameter(
f"{pref}EC initial concentration in electrolyte [mol.m-3]"
)
self.D_ec = pybamm.Parameter(f"{pref}EC diffusivity [m2.s-1]")
self.k_sei = pybamm.Parameter(f"{pref}SEI kinetic rate constant [m.s-1]")
# Lithium plating parameters
self.c_Li_typ = pybamm.Parameter(
f"{pref}Typical plated lithium concentration [mol.m-3]"
)
self.c_plated_Li_0 = pybamm.Parameter(
f"{pref}Initial plated lithium concentration [mol.m-3]"
)
self.alpha_plating = pybamm.Parameter(
f"{pref}Lithium plating transfer coefficient"
)
self.alpha_stripping = 1 - self.alpha_plating
self.c_e_init = pybamm.Parameter(
"Initial concentration in electrolyte [mol.m-3]"
)
if main.options.electrode_types[domain] == "planar":
self.n_Li_init = pybamm.Scalar(0)
self.Q_Li_init = pybamm.Scalar(0)
self.U_init = pybamm.Scalar(0)
return
# Spatial variables for parameters that depend on position within the cell
# and/or particle
x = pybamm.SpatialVariable(
f"x_{domain[0]}",
domain=[f"{domain} electrode"],
auxiliary_domains={"secondary": "current collector"},
coord_sys="cartesian",
)
y = pybamm.PrimaryBroadcast(
pybamm.standard_spatial_vars.y, f"{domain} electrode"
)
z = pybamm.PrimaryBroadcast(
pybamm.standard_spatial_vars.z, f"{domain} electrode"
)
r = pybamm.SpatialVariable(
f"r_{domain[0]}",
domain=[f"{domain} {self.phase_name}particle"],
auxiliary_domains={
"secondary": f"{domain} electrode",
"tertiary": "current collector",
},
coord_sys="spherical polar",
)
# Microscale geometry
# Note: the surface area to volume ratio is defined later with the function
# parameters. The particle size as a function of through-cell position is
# already defined in geometric_parameters.py
self.R = self.geo.R
self.R_typ = self.geo.R_typ
# Particle-size distribution parameters
self.R_min = self.geo.R_min
self.R_max = self.geo.R_max
self.f_a_dist = self.geo.f_a_dist
# Particle properties
self.epsilon_s = pybamm.FunctionParameter(
f"{pref}{Domain} electrode active material volume fraction",
{
"Through-cell distance (x) [m]": x,
"Horizontal distance (y) [m]": y,
"Vertical distance (z) [m]": z,
},
)
self.epsilon_s_av = pybamm.xyz_average(self.epsilon_s)
self.c_max = pybamm.Parameter(
f"{pref}Maximum concentration in {domain} electrode [mol.m-3]"
)
if self.options["open-circuit potential"] == "MSMR":
self.U_init = pybamm.Parameter(
f"{pref}Initial voltage in {domain} electrode [V]",
)
self.c_init = self.x(self.U_init, main.T_init) * self.c_max
else:
self.c_init = pybamm.FunctionParameter(
f"{pref}Initial concentration in {domain} electrode [mol.m-3]",
{
"Radial distance (r) [m]": r,
"Through-cell distance (x) [m]": pybamm.PrimaryBroadcast(
x, f"{domain} {phase_name}particle"
),
},
)
self.c_init_av = pybamm.xyz_average(pybamm.r_average(self.c_init))
self.sto_init_av = self.c_init_av / self.c_max
eps_c_init_av = pybamm.xyz_average(
self.epsilon_s * pybamm.r_average(self.c_init)
)
# if self.options['open-circuit potential'] == 'Plett':
self.hysteresis_switch = pybamm.Parameter(
f"{pref}{Domain} particle hysteresis switching factor"
)
self.h_init = pybamm.FunctionParameter(
f"{pref}Initial hysteresis state in {domain} electrode",
{"Through-cell distance (x) [m]": x},
)
if self.options["open-circuit potential"] != "MSMR":
self.U_init = self.U(self.sto_init_av, main.T_init)
# Electrode loading and capacity
self.elec_loading = (
self.epsilon_s_av * self.domain_param.L * self.c_max * main.F / 3600
)
self.n_Li_init = eps_c_init_av * self.domain_param.L * main.A_cc
self.Q_Li_init = self.n_Li_init * main.F / 3600
self.Q_init = self.elec_loading * main.A_cc
if self.options["particle shape"] == "spherical":
self.a_typ = 3 * pybamm.xyz_average(self.epsilon_s) / self.R_typ
# Mechanical property
self.nu = pybamm.Parameter(f"{pref}{Domain} electrode Poisson's ratio")
# Loss of active material parameters
self.m_LAM = pybamm.Parameter(
f"{pref}{Domain} electrode LAM constant exponential term"
)
self.stress_critical = pybamm.Parameter(
f"{pref}{Domain} electrode critical stress [Pa]"
)
self.beta_LAM_sei = pybamm.Parameter(
f"{pref}{Domain} electrode reaction-driven LAM factor [m3.mol-1]"
)
# Mechanical parameters
self.c_0 = pybamm.Parameter(
f"{pref}{Domain} electrode reference concentration for free of deformation "
"[mol.m-3]"
)
self.l_cr_0 = pybamm.Parameter(
f"{pref}{Domain} electrode initial crack length [m]"
)
self.w_cr = pybamm.Parameter(
f"{pref}{Domain} electrode initial crack width [m]"
)
self.rho_cr = pybamm.Parameter(
f"{pref}{Domain} electrode number of cracks per unit area [m-2]"
)
self.b_cr = pybamm.Parameter(f"{pref}{Domain} electrode Paris' law constant b")
self.m_cr = pybamm.Parameter(f"{pref}{Domain} electrode Paris' law constant m")
def hysteresis_decay(self, sto, T, lithiation=None):
"""
Rate at which the open-circuit potential approaches the lithiation
or delithiation branch when it exhibits hysteresis.
"""
Domain = self.domain.capitalize()
if lithiation is None:
lithiation = ""
else:
lithiation = lithiation + " "
inputs = {
f"{self.phase_prefactor}{Domain} particle stoichiometry": sto,
f"{Domain} electrode temperature [K]": T,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} particle {lithiation}hysteresis decay rate",
inputs,
)
def k_cr(self, T):
"""
Cracking rate for the electrode;
"""
phase_prefactor = self.phase_prefactor
Domain = self.domain.capitalize()
return pybamm.FunctionParameter(
f"{phase_prefactor}{Domain} electrode cracking rate", {"Temperature [K]": T}
)
def D(self, c_s, T, lithiation=None):
"""
Dimensional diffusivity in particle. In the parameter sets this is defined as
a function of stoichiometry (dimensionless), but in the models we use it as a
function of concentration (mol/m3). We convert from concentration to
stoichiometry by dividing by the maximum concentration.
"""
Domain = self.domain.capitalize()
sto = c_s / self.c_max
tol = pybamm.settings.tolerances["D__c_s"]
sto = pybamm.maximum(pybamm.minimum(sto, 1 - tol), tol)
if lithiation is None:
lithiation = ""
else:
lithiation = lithiation + " "
inputs = {
f"{self.phase_prefactor}{Domain} particle stoichiometry": sto,
"Temperature [K]": T,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} particle {lithiation}diffusivity [m2.s-1]",
inputs,
)
def j0(self, c_e, c_s_surf, T, lithiation=None):
"""Dimensional exchange-current density [A.m-2]"""
tol = pybamm.settings.tolerances["j0__c_e"]
c_e = pybamm.maximum(c_e, tol)
tol = pybamm.settings.tolerances["j0__c_s"]
c_s_surf = pybamm.maximum(
pybamm.minimum(c_s_surf, (1 - tol) * self.c_max), tol * self.c_max
)
domain, Domain = self.domain_Domain
if lithiation is None:
lithiation = ""
else:
lithiation = lithiation + " "
inputs = {
"Electrolyte concentration [mol.m-3]": c_e,
f"{Domain} particle surface concentration [mol.m-3]": c_s_surf,
f"{self.phase_prefactor}Maximum {domain} particle "
"surface concentration [mol.m-3]": self.c_max,
"Temperature [K]": T,
}
# Regularise all powers of c_e, c_s_surf, and c_max - c_s_surf to
# make their derivative well behaved when the concentrations
# approach 0/c_max.
regulariser = pybamm.RegulariseSqrtAndPower(
scales={
c_e: self.c_e_init,
c_s_surf: self.c_max,
self.c_max - c_s_surf: self.c_max,
},
inputs=inputs,
)
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode {lithiation}"
"exchange-current density [A.m-2]",
inputs,
post_processor=regulariser,
)
def j0_stripping(self, c_e, c_Li, T):
"""Dimensional exchange-current density for stripping [A.m-2]"""
tol = pybamm.settings.tolerances["j0__c_e"]
c_e = pybamm.maximum(c_e, tol)
tol = pybamm.settings.tolerances["j0__c_Li"]
c_Li = pybamm.maximum(c_Li, tol)
Domain = self.domain.capitalize()
inputs = {
f"{Domain} electrolyte concentration [mol.m-3]": c_e,
f"{Domain} plated lithium concentration [mol.m-3]": c_Li,
f"{Domain} temperature [K]": T,
}
regulariser = pybamm.RegulariseSqrtAndPower(
scales={
c_e: self.c_e_init,
c_Li: self.c_Li_typ,
},
inputs=inputs,
)
return pybamm.FunctionParameter(
f"{self.phase_prefactor}Exchange-current density for stripping [A.m-2]",
inputs,
post_processor=regulariser,
)
def j0_plating(self, c_e, c_Li, T):
"""Dimensional exchange-current density for plating [A.m-2]"""
tol = pybamm.settings.tolerances["j0__c_e"]
c_e = pybamm.maximum(c_e, tol)
tol = pybamm.settings.tolerances["j0__c_Li"]
c_Li = pybamm.maximum(c_Li, tol)
Domain = self.domain.capitalize()
inputs = {
f"{Domain} electrolyte concentration [mol.m-3]": c_e,
f"{Domain} plated lithium concentration [mol.m-3]": c_Li,
f"{Domain} temperature [K]": T,
}
regulariser = pybamm.RegulariseSqrtAndPower(
scales={
c_e: self.c_e_init,
c_Li: self.c_Li_typ,
},
inputs=inputs,
)
return pybamm.FunctionParameter(
f"{self.phase_prefactor}Exchange-current density for plating [A.m-2]",
inputs,
post_processor=regulariser,
)
def dead_lithium_decay_rate(self, L_sei):
"""Dimensional dead lithium decay rate [s-1]"""
Domain = self.domain.capitalize()
inputs = {f"{Domain} {self.phase_name}SEI thickness [m]": L_sei}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}Dead lithium decay rate [s-1]", inputs
)
def U(self, sto, T, lithiation=None):
"""
Dimensional open-circuit potential [V], calculated as
U(x,T) = U_ref(x) + dUdT(x) * (T - T_ref). See the documentation for
dUdT for more details.
"""
# bound stoichiometry between tol and 1-tol. Adding 1/sto + 1/(sto-1) later
# will ensure that ocp goes to +- infinity if sto goes into that region
# anyway
Domain = self.domain.capitalize()
tol = pybamm.settings.tolerances["U__c_s"]
sto_clipped = pybamm.maximum(pybamm.minimum(sto, 1 - tol), tol)
if lithiation is None:
lithiation = ""
else:
lithiation = lithiation + " "
inputs = {f"{self.phase_prefactor}{Domain} particle stoichiometry": sto_clipped}
u_ref = pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode {lithiation}OCP [V]", inputs
)
dudt_func = self.dUdT(sto_clipped)
u_ref = u_ref + (T - self.main_param.T_ref) * dudt_func
# Add a term to the OCPs to ensure they approach a large positive value
# as sto -> 0 and a large negative value as sto -> 1. This will not affect
# the OCP for most values of sto.
# see #1435 and #5371 for discussion on OCP asymptotes.
out = u_ref + U_asymptotes(sto)
if self.domain == "negative":
out.print_name = r"U_\mathrm{n}(c^\mathrm{surf}_\mathrm{s,n}, T)"
elif self.domain == "positive":
out.print_name = r"U_\mathrm{p}(c^\mathrm{surf}_\mathrm{s,p}, T)"
return out
def dUdT(self, sto):
"""
Dimensional entropic change of the open-circuit potential [V.K-1].
Note: in the "classical" formulation, the open-circuit potential is defined
as U(x,T) = U_ref(x) + dUdT(x) * (T - T_ref). The user provides U_ref and
dUdT, and the model uses these to calculate U. dUdT is also used to calculate
the reversible heat generation term in the thermal model. However, in the
"MSMR" formulation, stoichiometry is explicitly defined as a function of U and
T, and dUdT is only used to calculate the reversible heat generation term.
"""
Domain = self.domain.capitalize()
inputs = {
f"{Domain} particle stoichiometry": sto,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode OCP entropic change [V.K-1]",
inputs,
)
def X_j(self, T, index):
"Available host sites indexed by reaction j"
inputs = {"Temperature [K]": T}
domain = self.domain
Electrode = domain.capitalize()
Xj = pybamm.FunctionParameter(
f"{Electrode} electrode host site occupancy fraction ({index})", inputs
)
return Xj
def U0_j(self, T, index):
"Equilibrium potential indexed by reaction j"
inputs = {"Temperature [K]": T}
domain = self.domain
Electrode = domain.capitalize()
U0j = pybamm.FunctionParameter(
f"{Electrode} electrode host site standard potential ({index}) [V]", inputs
)
return U0j
def w_j(self, T, index):
"Order parameter indexed by reaction j"
inputs = {"Temperature [K]": T}
domain = self.domain
Electrode = domain.capitalize()
wj = pybamm.FunctionParameter(
f"{Electrode} electrode host site ideality factor ({index})", inputs
)
return wj
def alpha_bv_j(self, T, index):
"Dimensional Butler-Volmer exchange-current density indexed by reaction j"
inputs = {"Temperature [K]": T}
domain = self.domain
Electrode = domain.capitalize()
alpha_bv_j = pybamm.FunctionParameter(
f"{Electrode} electrode host site charge transfer coefficient ({index})",
inputs,
)
return alpha_bv_j
def x_j(self, U, T, index):
"Fractional occupancy of site j as a function of potential"
f = self.main_param.F / (self.main_param.R * T)
U0j = self.U0_j(T, index)
wj = self.w_j(T, index)
Xj = self.X_j(T, index)
# Equation 5, Baker et al 2018
xj = Xj / (1 + pybamm.exp(f * (U - U0j) / wj))
return xj
def dxdU_j(self, U, T, index):
"Derivative of fractional occupancy of site j as a function of potential [V-1]"
f = self.main_param.F / (self.main_param.R * T)
U0j = self.U0_j(T, index)
wj = self.w_j(T, index)
Xj = self.X_j(T, index)
e = pybamm.exp(f * (U - U0j) / wj)
# Equation 25, Baker et al 2018
dxjdU = -(f / wj) * (Xj * e) / (1 + e) ** 2
return dxjdU
def j0_j(self, c_e, U, T, index):
"Exchange-current density index by reaction j [A.m-2]"
domain = self.domain
Electrode = domain.capitalize()
tol = pybamm.settings.tolerances["j0__c_e"]
c_e = pybamm.maximum(c_e, tol)
c_e_ref = self.main_param.c_e_init
xj = self.x_j(U, T, index)
# xj = pybamm.maximum(pybamm.minimum(xj, (1 - tol)), tol)
f = self.main_param.F / (self.main_param.R * T)
wj = self.w_j(T, index)
self.X_j(T, index)
aj = self.alpha_bv_j(T, index)
j0_ref_j = pybamm.FunctionParameter(
f"{Electrode} electrode host site reference exchange-current density ({index}) [A.m-2]",
{"Temperature [K]": T},
)
# Equation 16, Baker et al 2018. The original formulation would be implemented
# as:
# j0_j = (
# j0_ref_j
# * xj ** (wj * aj)
# * (Xj - xj) ** (wj * (1 - aj))
# * (c_e / c_e_ref) ** (1 - aj)
# )
# However, we reformulate in terms of potential to avoid singularity as x_j
# approaches X_j
j0_j = (
j0_ref_j
* xj**wj
* pybamm.exp(f * (1 - aj) * (U - self.U0_j(T, index)))
* pybamm.reg_power(c_e / c_e_ref, 1 - aj)
)
return j0_j
def x(self, U, T):
"Stoichiometry as a function of potential (for use with MSMR models)"
N = int(self.options["number of MSMR reactions"])
# Equation 6, Baker et al 2018
x = 0
for i in range(N):
x += self.x_j(U, T, i)
return x
def dxdU(self, U, T):
"""
Differential stoichiometry as a function of potential (for use with MSMR models)
"""
N = int(self.options["number of MSMR reactions"])
# Equation 25, Baker et al 2018
dxdU = 0
for i in range(N):
dxdU += self.dxdU_j(U, T, i)
return dxdU
def t_change(self, sto):
"""
Volume change for the electrode; sto should be R-averaged
"""
Domain = self.domain.capitalize()
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode volume change",
{
f"{Domain} particle stoichiometry": sto,
},
)
def Omega(self, sto, T):
"""Dimensional partial molar volume of Li in solid solution [m3.mol-1]"""
_domain, Domain = self.domain_Domain
inputs = {
f"{self.phase_prefactor} particle stoichiometry": sto,
"Temperature [K]": T,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode partial molar volume [m3.mol-1]",
inputs,
)
def E(self, sto, T):
"""Dimensional Young's modulus"""
_domain, Domain = self.domain_Domain
inputs = {
f"{self.phase_prefactor} particle stoichiometry": sto,
"Temperature [K]": T,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode Young's modulus [Pa]", inputs
)
def beta_LAM(self, T, direction=None):
"""Dimensional LAM constant proportional term [s-1]"""
_domain, Domain = self.domain_Domain
if direction is None:
direction = ""
else:
direction = f" ({direction})"
inputs = {
"Temperature [K]": T,
}
return pybamm.FunctionParameter(
f"{self.phase_prefactor}{Domain} electrode LAM constant proportional term{direction} [s-1]",
inputs,
)
def U_asymptote_approaching_zero(sto):
"""
OCP asymptote approaching zero as sto -> 0. This function's hyperparameters
are tuned to provide a barrier of 1 mV at sto = 0.001 and 1000 mV at sto = 0.
Parameters
----------
sto : float
The stoichiometry of the electrode.
Returns
-------
float
The OCP asymptote.
"""
a = 205.0568621937484
b = 6910.192179565431
c = -7.7e-4
# Clamp sto to avoid exp overflow (float64 max is ~exp(709))
max_safe_exp = 700
sto_limit = c - max_safe_exp / b
# For sto >= sto_limit: use log(1 + exp(...))
# For sto < sto_limit: continue linearly with slope -b -- this is an exact floating
# point match for large sto values.
out = pybamm.log(1 + pybamm.exp(-b * (pybamm.maximum(sto_limit, sto) - c))) + -b * (
pybamm.minimum(0, sto - sto_limit)
)
return a * out
def U_asymptotes(sto):
"""
Applies a smooth function to the OCP to ensure it approaches a large positive value
as sto -> 0 and a large negative value as sto -> 1. This will not affect the OCP
for most values of sto.
Note: for numerical stability, the asymptote approaches a large value instead of
infinity at sto = 0 or 1
Parameters
----------
sto : float
The stoichiometry of the electrode.
Returns
-------
float
The OCP asymptote.
"""
left = U_asymptote_approaching_zero(sto)
right = -U_asymptote_approaching_zero(1 - sto)
return left + right