Source code for pybamm.models.submodels.particle.polynomial_profile

#
# Class for many particles with polynomial concentration profile
#
import pybamm

from .base_particle import BaseParticle


[docs] class PolynomialProfile(BaseParticle): """ Class for molar conservation in particles employing Fick's law, assuming a polynomial concentration profile in r, and allowing variation in the electrode domain. Model equations from :footcite:t:`Subramanian2005`. Parameters ---------- param : parameter class The parameters to use for this submodel domain : str The domain of the model either 'Negative' or 'Positive' options: dict A dictionary of options to be passed to the model. See :class:`pybamm.BaseBatteryModel` phase : str, optional Phase of the particle (default is "primary") """ def __init__(self, param, domain, options, phase="primary"): super().__init__(param, domain, options, phase) self.name = getattr(self.options, self.domain)["particle"] if self.name == "Fickian diffusion": raise ValueError( "Particle type must be 'uniform profile', " "'quadratic profile' or 'quartic profile'" ) pybamm.citations.register("Subramanian2005")
[docs] def get_fundamental_variables(self): domain, Domain = self.domain_Domain variables = {} # For all orders we solve an equation for the average concentration if self.size_distribution is False: c_s_rav = pybamm.Variable( f"R-averaged {domain} particle concentration [mol.m-3]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, bounds=(0, self.phase_param.c_max), scale=self.phase_param.c_max, ) r = pybamm.SpatialVariable( f"r_{domain[0]}", domain=[f"{domain} particle"], auxiliary_domains={ "secondary": f"{domain} electrode", "tertiary": "current collector", }, coord_sys="spherical polar", ) R = self.phase_param.R else: c_s_rav_distribution = pybamm.Variable( f"R-averaged {domain} particle concentration distribution [mol.m-3]", domain=f"{domain} particle size", auxiliary_domains={ "secondary": f"{domain} electrode", "tertiary": "current collector", }, bounds=(0, self.phase_param.c_max), scale=self.phase_param.c_max, ) r = pybamm.SpatialVariable( f"r_{domain[0]}", domain=[f"{domain} particle"], auxiliary_domains={ "secondary": f"{domain} particle size", "tertiary": f"{domain} electrode", "quaternary": "current collector", }, coord_sys="spherical polar", ) R = pybamm.SpatialVariable( f"R_{domain[0]}", domain=[f"{domain} particle size"], auxiliary_domains={ "secondary": f"{domain} electrode", "tertiary": "current collector", }, coord_sys="cartesian", ) variables = self._get_distribution_variables(R) # Standard concentration distribution variables (size-dependent) variables.update( self._get_standard_concentration_distribution_variables( c_s_rav_distribution ) ) # Standard size-averaged variables. Average concentrations using # the volume-weighted distribution since they are volume-based # quantities. Necessary for output variables "Total lithium in # negative electrode [mol]", etc, to be calculated correctly f_v_dist = variables[ f"{Domain} volume-weighted particle-size distribution [m-1]" ] c_s_rav = pybamm.Integral(f_v_dist * c_s_rav_distribution, R) c_s_surf = c_s_rav c_s = pybamm.PrimaryBroadcast(c_s_rav, [f"{domain} particle"]) variables.update( self._get_standard_concentration_variables( c_s, c_s_rav=c_s_rav, c_s_surf=c_s_surf ) ) return variables if self.name == "uniform profile": # The concentration is uniform so the surface value is equal to # the average c_s_surf = c_s_rav elif self.name in ["quadratic profile", "quartic profile"]: # We solve an equation for the surface concentration, so it is # a variable in the model c_s_surf = pybamm.Variable( f"{Domain} particle surface concentration [mol.m-3]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, bounds=(0, self.phase_param.c_max), scale=self.phase_param.c_max, ) if self.name == "quartic profile": # For the fourth order polynomial approximation we also solve an # equation for the average concentration gradient. Note: in the original # paper this quantity is referred to as the flux, but here we make the # distinction between the flux defined as N = -D*dc/dr and the # concentration gradient q = dc/dr q_s_rav = pybamm.Variable( f"R-averaged {domain} particle concentration gradient [mol.m-4]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, scale=self.phase_param.c_max / self.phase_param.R_typ, ) variables.update( { f"R-averaged {domain} particle " "concentration gradient [mol.m-4]": q_s_rav } ) # Set concentration depending on polynomial order if self.name == "uniform profile": # The concentration is uniform A = c_s_rav B = pybamm.FullBroadcast(0, f"{domain} electrode", "current collector") C = pybamm.FullBroadcast(0, f"{domain} electrode", "current collector") elif self.name == "quadratic profile": # The concentration is given by c = A + B*r**2 # eqs 11-12 in Subramanian2005 A = 5 / 2 * c_s_rav - 3 / 2 * c_s_surf B = 5 / 2 * (c_s_surf - c_s_rav) C = pybamm.FullBroadcast(0, f"{domain} electrode", "current collector") elif self.name == "quartic profile": # The concentration is given by c = A + B*r**2 + C*r**4 # eqs 24-26 in Subramanian2005 A = 39 / 4 * c_s_surf - 3 * q_s_rav * R - 35 / 4 * c_s_rav B = -35 * c_s_surf + 10 * q_s_rav + 35 * c_s_rav C = 105 / 4 * c_s_surf - 7 * q_s_rav * R - 105 / 4 * c_s_rav A = pybamm.PrimaryBroadcast(A, [f"{domain} particle"]) B = pybamm.PrimaryBroadcast(B, [f"{domain} particle"]) C = pybamm.PrimaryBroadcast(C, [f"{domain} particle"]) c_s = A + B * r**2 / R**2 + C * r**4 / R**4 variables.update( self._get_standard_concentration_variables( c_s, c_s_rav=c_s_rav, c_s_surf=c_s_surf ) ) return variables
[docs] def get_coupled_variables(self, variables): domain, Domain = self.domain_Domain if self.size_distribution is False: c_s = variables[f"{Domain} particle concentration [mol.m-3]"] c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] c_s_surf = variables[f"{Domain} particle surface concentration [mol.m-3]"] T = pybamm.PrimaryBroadcast( variables[f"{Domain} electrode temperature [K]"], [f"{domain} particle"] ) current = variables["Total current density [A.m-2]"] D_eff = self._get_effective_diffusivity(c_s, T, current) r = pybamm.SpatialVariable( f"r_{domain[0]}", domain=[f"{domain} particle"], auxiliary_domains={ "secondary": f"{domain} electrode", "tertiary": "current collector", }, coord_sys="spherical polar", ) R = variables[f"{Domain} particle radius [m]"] variables.update(self._get_standard_diffusivity_variables(D_eff)) else: # only uniform concentration implemented, no need to calculate D_eff pass # Set flux depending on polynomial order if self.name == "uniform profile": # The flux is zero since there is no concentration gradient N_s = pybamm.FullBroadcastToEdges( 0, [f"{domain} particle"], auxiliary_domains={ "secondary": f"{domain} electrode", "tertiary": "current collector", }, ) elif self.name == "quadratic profile": # The flux may be computed directly from the polynomial for c N_s = -D_eff * 5 * (c_s_surf - c_s_rav) * r / R**2 elif self.name == "quartic profile": q_s_rav = variables[ f"R-averaged {domain} particle concentration gradient [mol.m-4]" ] # The flux may be computed directly from the polynomial for c N_s = -D_eff * ( (-70 * c_s_surf + 20 * q_s_rav * R + 70 * c_s_rav) * r / R**2 + (105 * c_s_surf - 28 * q_s_rav * R - 105 * c_s_rav) * r**3 / R**4 ) variables.update(self._get_standard_flux_variables(N_s)) return variables
[docs] def set_rhs(self, variables): domain, Domain = self.domain_Domain if self.size_distribution is False: c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] j = variables[f"{Domain} electrode interfacial current density [A.m-2]"] R = variables[f"{Domain} particle radius [m]"] else: c_s_rav = variables[ f"R-averaged {domain} particle concentration distribution [mol.m-3]" ] j = variables[ f"{Domain} electrode interfacial current density distribution [A.m-2]" ] R = variables[f"{Domain} particle sizes [m]"] self.rhs = {c_s_rav: -3 * j / self.param.F / R} if self.name == "quartic profile": # We solve an extra ODE for the average particle flux q_s_rav = variables[ f"R-averaged {domain} particle concentration gradient [mol.m-4]" ] c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] D_eff = variables[f"{Domain} particle effective diffusivity [m2.s-1]"] # eq 30 of Subramanian2005 self.rhs.update( { q_s_rav: -30 * pybamm.r_average(D_eff) * q_s_rav / R**2 - 45 / 2 * j / self.param.F / R**2 } )
[docs] def set_algebraic(self, variables): if self.name == "uniform profile": # No algebraic equations since we only solve for the average concentration return domain, Domain = self.domain_Domain c_s_surf = variables[f"{Domain} particle surface concentration [mol.m-3]"] c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] D_eff = variables[f"{Domain} particle effective diffusivity [m2.s-1]"] j = variables[f"{Domain} electrode interfacial current density [A.m-2]"] R = variables[f"{Domain} particle radius [m]"] c_max = self.phase_param.c_max T_ref = self.param.T_ref D_c_max_scale = self.phase_param.D(c_max, T_ref) * c_max if self.name == "quadratic profile": # We solve an algebraic equation for the surface concentration self.algebraic = { c_s_surf: ( pybamm.surf(D_eff) * (c_s_surf - c_s_rav) + j * R / self.param.F / 5 ) / D_c_max_scale } elif self.name == "quartic profile": # We solve a different algebraic equation for the surface concentration # that accounts for the average concentration gradient inside the particle q_s_rav = variables[ f"R-averaged {domain} particle concentration gradient [mol.m-4]" ] # eq 31 of Subramanian2005 D_c_max_over_R_scale = D_c_max_scale / self.phase_param.R_typ self.algebraic = { c_s_surf: ( pybamm.surf(D_eff) * (35 / R * (c_s_surf - c_s_rav) - 8 * q_s_rav) + j / self.param.F ) / D_c_max_over_R_scale }
[docs] def set_initial_conditions(self, variables): domain, Domain = self.domain_Domain c_init = pybamm.r_average(self.phase_param.c_init) if self.size_distribution is False: c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] else: c_s_rav = variables[ f"R-averaged {domain} particle concentration distribution [mol.m-3]" ] c_init = pybamm.PrimaryBroadcast(c_init, [f"{domain} particle size"]) self.initial_conditions = {c_s_rav: c_init} if self.name in ["quadratic profile", "quartic profile"]: # We also need to provide an initial condition (initial guess for the # algebraic solver) for the surface concentration c_s_surf = variables[f"{Domain} particle surface concentration [mol.m-3]"] self.initial_conditions.update({c_s_surf: c_init}) if self.name == "quartic profile": # We also need to provide an initial condition for the average # concentration gradient q_s_rav = variables[ f"R-averaged {domain} particle concentration gradient [mol.m-4]" ] self.initial_conditions.update({q_s_rav: 0})