Source code for pybamm.models.submodels.interface.sei.sei_growth

#
# Class for SEI growth
#
import pybamm

from .base_sei import BaseModel


[docs] class SEIGrowth(BaseModel): """ Class for SEI growth. Most of the models are from Section 5.6.4 of :footcite:t:`Marquis2020` and references therein. The ec reaction limited model is from :footcite:t:`Yang2017`. Parameters ---------- param : parameter class The parameters to use for this submodel reaction_loc : str Where the reaction happens: "x-average" (SPM, SPMe, etc), "full electrode" (full DFN), or "interface" (half-cell model) options : dict A dictionary of options to be passed to the model. phase : str, optional Phase of the particle (default is "primary") cracks : bool, optional Whether this is a submodel for standard SEI or SEI on cracks """ def __init__(self, param, domain, options, phase="primary", cracks=False): super().__init__(param, domain, options=options, phase=phase, cracks=cracks) if self.options.electrode_types[domain] == "planar": self.reaction_loc = "interface" elif self.options["x-average side reactions"] == "true": self.reaction_loc = "x-average" else: self.reaction_loc = "full electrode" SEI_option = getattr(self.options, domain)["SEI"] if SEI_option == "ec reaction limited": pybamm.citations.register("Yang2017") elif SEI_option == "VonKolzenberg2020": pybamm.citations.register("VonKolzenberg2020") elif SEI_option == "tunnelling limited": pybamm.citations.register("Tang2012") else: pybamm.citations.register("Marquis2020")
[docs] def get_fundamental_variables(self): domain, Domain = self.domain_Domain L_sei_0 = self.phase_param.L_sei_0 V_bar_sei = self.phase_param.V_bar_sei if self.reaction_loc == "x-average": # c_sei_xav and c_sei are bulk quantities [mol.m-3] scale = L_sei_0 * self.phase_param.a_typ / V_bar_sei c_sei_xav = pybamm.Variable( f"X-averaged {domain} {self.reaction_name}concentration [mol.m-3]", domain="current collector", scale=scale, ) c_sei_xav.print_name = "c_sei_xav" c_sei = pybamm.PrimaryBroadcast(c_sei_xav, f"{domain} electrode") elif self.reaction_loc == "full electrode": # c_sei is a bulk quantity [mol.m-3] scale = L_sei_0 * self.phase_param.a_typ / V_bar_sei c_sei = pybamm.Variable( f"{Domain} {self.reaction_name}concentration [mol.m-3]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, scale=scale, ) elif self.reaction_loc == "interface": # c_sei is an interfacial quantity [mol.m-2] scale = L_sei_0 / V_bar_sei c_sei = pybamm.Variable( f"{Domain} {self.reaction_name}concentration [mol.m-2]", domain="current collector", scale=scale, ) c_sei.print_name = "c_sei" variables = self._get_standard_concentration_variables(c_sei) return variables
[docs] def get_coupled_variables(self, variables): phase_param = self.phase_param domain, Domain = self.domain_Domain SEI_option = getattr(getattr(self.options, domain), self.phase)["SEI"] T = variables[f"{Domain} electrode temperature [K]"] # delta_phi = phi_s - phi_e if self.reaction_loc == "interface": delta_phi = variables[ "Lithium metal interface surface potential difference [V]" ] T = pybamm.boundary_value(T, "right") else: delta_phi = variables[ f"{Domain} electrode surface potential difference [V]" ] # Look for current that contributes to the -IR drop # If we can't find the interfacial current density from the main reaction, j, # it's ok to fall back on the total interfacial current density, j_tot # This should only happen when the interface submodel is "InverseButlerVolmer" # or "InverseLinear" in which case j = j_tot (uniform) anyway if f"{Domain} electrode interfacial current density [A.m-2]" in variables: j = variables[f"{Domain} electrode interfacial current density [A.m-2]"] elif self.reaction_loc == "interface": j = variables["Lithium metal total interfacial current density [A.m-2]"] else: j = variables[ f"X-averaged {domain} electrode total " "interfacial current density [A.m-2]" ] L_sei = variables[f"{Domain} {self.reaction_name}thickness [m]"] R_sei = phase_param.R_sei eta_SEI = delta_phi - phase_param.U_sei - j * L_sei * R_sei # Thermal prefactor for reaction, interstitial and EC models F_RT = self.param.F / (self.param.R * T) # Define alpha_SEI depending on whether it is symmetric or asymmetric. This # applies to "reaction limited" and "EC reaction limited" if SEI_option.endswith("(asymmetric)"): alpha_SEI = phase_param.alpha_SEI else: alpha_SEI = 0.5 if SEI_option.startswith("reaction limited"): # Scott Marquis thesis (eq. 5.92) j_sei = -phase_param.j0_sei * pybamm.exp(-alpha_SEI * F_RT * eta_SEI) elif SEI_option == "electron-migration limited": # Scott Marquis thesis (eq. 5.94) eta_inner = delta_phi - phase_param.U_sei j_sei = (eta_inner < 0) * phase_param.kappa_inner * eta_inner / L_sei elif SEI_option == "tunnelling limited": # # This comes from eq.25 in Tang, M., Lu, S. and Newman, J., 2012. # Experimental and theoretical investigation of solid-electrolyte-interphase # formation mechanisms on glassy carbon. # Journal of The Electrochemical Society, 159(11), p.A1775. j_sei = ( -phase_param.j0_sei * pybamm.exp(-alpha_SEI * F_RT * delta_phi) * pybamm.exp(-phase_param.beta_tunnelling * L_sei) ) elif SEI_option == "VonKolzenberg2020": # Equation 19 in # von Kolzenberg L, Latz A, Horstmann B. # Solid electrolyte interphase during battery cycling: # Theory of growth regimes. ChemSusChem. 2020 Aug 7;13(15):3901-10. eta_bar = F_RT * (delta_phi) L_diff = ( phase_param.D_li * phase_param.c_li_0 * self.param.F / phase_param.j0_sei ) * pybamm.exp(-(1 - alpha_SEI) * eta_bar) L_tun = phase_param.L_tunneling L_app = (L_sei - L_tun) * ((L_sei - L_tun) > 0) L_mig = ( 2 / F_RT * phase_param.kappa_Li_ion / pybamm.maximum(pybamm.AbsoluteValue(j), 1e-38) ) sign_j = 2 * (j > 0) - 1 LL_k = (1 - L_app / L_mig * sign_j) / ( 1 - L_app / L_mig * sign_j + L_app / L_diff ) j_sei = -phase_param.j0_sei * LL_k * pybamm.exp(-alpha_SEI * eta_bar) elif SEI_option == "interstitial-diffusion limited": # Scott Marquis thesis (eq. 5.96) j_sei = -( phase_param.D_li * phase_param.c_li_0 * self.param.F / L_sei ) * pybamm.exp(-F_RT * delta_phi) elif SEI_option == "solvent-diffusion limited": # Scott Marquis thesis (eq. 5.91) j_sei = -phase_param.D_sol * phase_param.c_sol * self.param.F / L_sei elif SEI_option.startswith("ec reaction limited"): # we have a linear system for j and c # c = c_0 + j * L / F / D [1] (eq 11 in the Yang2017 paper) # j = - F * c * k_exp() [2] (eq 10 in the Yang2017 paper, factor # of a is outside the defn of j here) # [1] into [2] gives (F cancels in the second terms) # j = - F * c_0 * k_exp() - j * L * k_exp() / D # rearrange # j = -F * c_0* k_exp() / (1 + L * k_exp() / D) # c_ec = c_0 - L * k_exp() / D / (1 + L * k_exp() / D) # = c_0 / (1 + L * k_exp() / D) k_exp = phase_param.k_sei * pybamm.exp(-alpha_SEI * F_RT * eta_SEI) L_over_D = L_sei / phase_param.D_ec c_0 = phase_param.c_ec_0 j_sei = -self.param.F * c_0 * k_exp / (1 + L_over_D * k_exp) c_ec = c_0 / (1 + L_over_D * k_exp) # Get variables related to the concentration c_ec_av = pybamm.x_average(c_ec) if self.reaction == "SEI on cracks": name = f"{Domain} EC concentration on cracks [mol.m-3]" else: name = f"{Domain} EC surface concentration [mol.m-3]" variables.update({name: c_ec, f"X-averaged {name}": c_ec_av}) # All SEI growth mechanisms assumed to have Arrhenius dependence arrhenius = pybamm.exp( phase_param.E_sei / self.param.R * (1 / self.param.T_ref - 1 / T) ) j_sei = arrhenius * j_sei variables.update(self._get_standard_reaction_variables(j_sei)) # Add other standard coupled variables variables.update(super().get_coupled_variables(variables)) return variables
[docs] def set_rhs(self, variables): domain, Domain = self.domain_Domain if self.reaction_loc == "interface": # c_sei is an interfacial quantity [mol.m-2] c_sei = variables[f"{Domain} {self.reaction_name}concentration [mol.m-2]"] j_sei = variables[ f"{Domain} electrode {self.reaction_name}" "interfacial current density [A.m-2]" ] a = 1 elif self.reaction_loc == "x-average": # c_sei is a bulk quanitity [mol.m-3] c_sei = variables[ f"X-averaged {domain} {self.reaction_name}concentration [mol.m-3]" ] j_sei = variables[ f"X-averaged {domain} electrode {self.reaction_name}" "interfacial current density [A.m-2]" ] a = variables[ f"X-averaged {domain} electrode {self.phase_name}" "surface area to volume ratio [m-1]" ] else: c_sei = variables[f"{Domain} {self.reaction_name}concentration [mol.m-3]"] j_sei = variables[ f"{Domain} electrode {self.reaction_name}" "interfacial current density [A.m-2]" ] a = variables[ f"{Domain} electrode {self.phase_name}" "surface area to volume ratio [m-1]" ] # For SEI on cracks, need to use crack area instead of surface area # To do this, use the roughness parameter, which works as follows: # a + a_cr = roughness * a # Rearrange to get a_cr = (roughness - 1) * a # This is done here by replacing a with a_cr using a *= roughness - 1 if self.reaction == "SEI on cracks": if self.reaction_loc == "x-average": roughness = variables[f"X-averaged {domain} electrode roughness ratio"] else: roughness = variables[f"{Domain} electrode roughness ratio"] a *= roughness - 1 # Replace surface area with crack area # a * j_sei / F is the rate of consumption of Li moles by SEI reaction # 1/z_sei converts from Li moles to SEI moles (z_sei=Li mol per sei mol) # a * j_sei / (F * z_sei) = rate of consumption of SEI moles by SEI reaction dcdt_sei = a * j_sei / (self.param.F * self.phase_param.z_sei) # Therefore, -a * j_sei / (F * z_sei) = rate of creation of SEI moles self.rhs = {c_sei: -dcdt_sei}
[docs] def set_initial_conditions(self, variables): domain, Domain = self.domain_Domain L_sei_0 = self.phase_param.L_sei_0 V_bar_sei = self.phase_param.V_bar_sei if self.reaction_loc == "interface": # c_sei is an interfacial quantity [mol.m-2] c_sei = variables[f"{Domain} {self.reaction_name}concentration [mol.m-2]"] c_sei_0 = L_sei_0 / V_bar_sei elif self.reaction_loc == "x-average": # c_sei is a bulk quantity [mol.m-3] c_sei = variables[ f"X-averaged {domain} {self.reaction_name}concentration [mol.m-3]" ] c_sei_0 = L_sei_0 * self.phase_param.a_typ / V_bar_sei c_sei_cr0 = self.phase_param.L_sei_cr0 * self.phase_param.a_typ / V_bar_sei else: # c_sei is a bulk quantity [mol.m-3] c_sei = variables[f"{Domain} {self.reaction_name}concentration [mol.m-3]"] c_sei_0 = L_sei_0 * self.phase_param.a_typ / V_bar_sei c_sei_cr0 = self.phase_param.L_sei_cr0 * self.phase_param.a_typ / V_bar_sei if self.reaction == "SEI on cracks": self.initial_conditions = {c_sei: c_sei_cr0} else: self.initial_conditions = {c_sei: c_sei_0}