Source code for pybamm.models.submodels.interface.base_interface

#
# Base interface class
#

import pybamm


[docs] class BaseInterface(pybamm.BaseSubModel): """ Base class for interfacial currents Parameters ---------- param : parameter class The parameters to use for this submodel domain : str The domain to implement the model, either: 'Negative' or 'Positive'. reaction : str The name of the reaction being implemented options: dict, optional 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, reaction, options=None, phase="primary"): super().__init__(param, domain, options=options, phase=phase) if reaction in ["lithium-ion main", "lithium metal plating"]: self.reaction_name = "" elif reaction == "lead-acid main": self.reaction_name = "" # empty reaction name for the main reaction elif reaction == "lead-acid oxygen": self.reaction_name = "oxygen " elif reaction in ["SEI", "SEI on cracks", "lithium plating"]: self.reaction_name = reaction + " " if reaction in [ "lithium-ion main", "lithium metal plating", "lithium plating", "SEI", "SEI on cracks", ]: # phase_name can be "" or "primary " or "secondary " self.reaction_name = self.phase_name + self.reaction_name self.reaction = reaction domain_options = getattr(self.options, domain) self.size_distribution = domain_options["particle size"] == "distribution" def _get_exchange_current_density(self, variables): """ A private function to obtain the exchange current density Parameters ---------- variables: dict The variables in the full model. Returns ------- j0 : :class: `pybamm.Symbol` The exchange current density. """ phase_param = self.phase_param domain, Domain = self.domain_Domain phase_name = self.phase_name domain_options = getattr(self.options, domain) c_e = variables[f"{Domain} electrolyte concentration [mol.m-3]"] T = variables[f"{Domain} electrode temperature [K]"] if self.reaction == "lithium-ion main": # For "particle-size distribution" submodels, take distribution version # of c_s_surf that depends on particle size. domain_options = getattr(self.options, domain) if domain_options["particle size"] == "distribution": c_s_surf = variables[ f"{Domain} {phase_name}particle surface " "concentration distribution [mol.m-3]" ] # If all variables were broadcast (in "x"), take only the orphans, # then re-broadcast c_e if ( isinstance(c_s_surf, pybamm.Broadcast) and isinstance(c_e, pybamm.Broadcast) and isinstance(T, pybamm.Broadcast) ): c_s_surf = c_s_surf.orphans[0] c_e = c_e.orphans[0] T = T.orphans[0] # as c_e must now be a scalar, re-broadcast to # "current collector" c_e = pybamm.PrimaryBroadcast(c_e, ["current collector"]) # broadcast c_e, T onto "particle size" c_e = pybamm.PrimaryBroadcast( c_e, [f"{domain} {phase_name}particle size"] ) T = pybamm.PrimaryBroadcast(T, [f"{domain} {phase_name}particle size"]) else: c_s_surf = variables[ f"{Domain} {phase_name}particle surface concentration [mol.m-3]" ] # If all variables were broadcast, take only the orphans if ( isinstance(c_s_surf, pybamm.Broadcast) and isinstance(c_e, pybamm.Broadcast) and isinstance(T, pybamm.Broadcast) ): c_s_surf = c_s_surf.orphans[0] c_e = c_e.orphans[0] T = T.orphans[0] # Get main reaction exchange-current density (may have empirical hysteresis) j0_option = getattr(domain_options, self.phase)["exchange-current density"] if j0_option == "single": j0 = phase_param.j0(c_e, c_s_surf, T) elif j0_option == "current sigmoid": current = variables["Total current density [A.m-2]"] k = 100 if Domain == "Positive": lithiation_current = current elif Domain == "Negative": lithiation_current = -current m_lith = pybamm.sigmoid( 0, lithiation_current, k ) # lithiation_current > 0 m_delith = 1 - m_lith # lithiation_current < 0 j0_lith = phase_param.j0(c_e, c_s_surf, T, "lithiation") j0_delith = phase_param.j0(c_e, c_s_surf, T, "delithiation") j0 = m_lith * j0_lith + m_delith * j0_delith elif self.reaction == "lithium metal plating": # compute T on the surface of the anode (interface with separator) T = pybamm.boundary_value(T, "right") c_Li_metal = 1 / self.param.V_bar_Li j0 = self.param.j0_Li_metal(c_e, c_Li_metal, T) elif self.reaction == "lead-acid main": # If variable was broadcast, take only the orphan if isinstance(c_e, pybamm.Broadcast) and isinstance(T, pybamm.Broadcast): c_e = c_e.orphans[0] T = T.orphans[0] j0 = phase_param.j0(c_e, T) elif self.reaction == "lead-acid oxygen": # If variable was broadcast, take only the orphan if isinstance(c_e, pybamm.Broadcast) and isinstance(T, pybamm.Broadcast): c_e = c_e.orphans[0] T = T.orphans[0] if self.domain == "negative": j0 = pybamm.Scalar(0) elif self.domain == "positive": j0 = self.param.p.prim.j0_Ox(c_e, T) return j0 def _get_number_of_electrons_in_reaction(self): """Returns the number of electrons in the reaction.""" if self.reaction in [ "lithium-ion main", "lithium metal plating", ]: return self.phase_param.ne elif self.reaction == "lead-acid main": return self.phase_param.ne elif self.reaction == "lead-acid oxygen": return self.param.ne_Ox def _get_average_total_interfacial_current_density(self, variables): """ Method to obtain the average total interfacial current density. Note: for lithium-ion models this is only exact if all the particles have the same radius. For the current set of models implemeted in pybamm, having the radius as a function of through-cell distance only makes sense for the DFN model. In the DFN, the correct average interfacial current density is computed in 'base_kinetics.py' by averaging the actual interfacial current density. The approximation here is only used to get the approximate constant additional resistance term for the "average" SEI film resistance model (if using), where only negligible errors will be introduced. For "leading-order" and "composite" submodels (as used in the SPM and SPMe) there is only a single particle radius, so this method returns correct result. """ domain = self.domain i_boundary_cc = variables["Current collector current density [A.m-2]"] if self.options.electrode_types[domain] == "planar": # In a half-cell the total interfacial current density is the current # collector current density, not divided by electrode thickness i_boundary_cc = variables["Current collector current density [A.m-2]"] j_total_average = i_boundary_cc a_j_total_average = i_boundary_cc else: a_av = variables[ f"X-averaged {domain} electrode {self.phase_name}" "surface area to volume ratio [m-1]" ] sgn = 1 if self.domain == "negative" else -1 a_j_total_average = sgn * i_boundary_cc / (self.domain_param.L) j_total_average = a_j_total_average / a_av return j_total_average, a_j_total_average def _get_standard_interfacial_current_variables(self, j): domain, Domain = self.domain_Domain reaction_name = self.reaction_name j.print_name = f"j_{domain[0]}" if self.reaction == "lithium metal plating": # Half-cell domain, j should not be broadcast variables = {"Lithium metal plating current density [A.m-2]": j} return variables # Size average. For j variables that depend on particle size, see # "_get_standard_size_distribution_interfacial_current_variables" if j.domain in [ ["negative particle size"], ["positive particle size"], ["negative primary particle size"], ["positive primary particle size"], ["negative secondary particle size"], ["positive secondary particle size"], ]: j = pybamm.size_average(j) # Average, and broadcast if necessary j_av = pybamm.x_average(j) if j.domain == []: j = pybamm.FullBroadcast(j, f"{domain} electrode", "current collector") elif ( j.domain == ["current collector"] and getattr(self, "reaction_loc", None) != "interface" ): j = pybamm.PrimaryBroadcast(j, f"{domain} electrode") variables = { f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]": j, f"X-averaged {domain} electrode {reaction_name}" "interfacial current density [A.m-2]": j_av, } if self.phase_name == reaction_name: variables.update( { f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]": j, f"X-averaged {domain} electrode {reaction_name}interfacial current density [A.m-2]": j_av, } ) return variables def _get_standard_total_interfacial_current_variables(self, j_tot_av, a_j_tot_av): domain = self.domain if self.options.electrode_types[domain] == "planar": variables = { "Lithium metal total interfacial current density [A.m-2]": j_tot_av, } else: variables = { f"X-averaged {domain} electrode total interfacial " "current density [A.m-2]": j_tot_av, f"X-averaged {domain} electrode total volumetric interfacial " "current density [A.m-3]": a_j_tot_av, } return variables def _get_standard_exchange_current_variables(self, j0): domain, Domain = self.domain_Domain reaction_name = self.reaction_name if self.reaction == "lithium metal plating": # half-cell domain variables = { "Lithium metal interface exchange current density [A.m-2]": j0, } return variables # Size average. For j0 variables that depend on particle size, see # "_get_standard_size_distribution_exchange_current_variables" if j0.domain in [["negative particle size"], ["positive particle size"]]: j0 = pybamm.size_average(j0) # Average, and broadcast if necessary j0_av = pybamm.x_average(j0) # X-average, and broadcast if necessary if j0.domain == []: j0 = pybamm.FullBroadcast(j0, f"{domain} electrode", "current collector") elif j0.domain == ["current collector"]: j0 = pybamm.PrimaryBroadcast(j0, f"{domain} electrode") variables = { f"{Domain} electrode {reaction_name}exchange current density [A.m-2]": j0, f"X-averaged {domain} electrode {reaction_name}" "exchange current density [A.m-2]": j0_av, } return variables def _get_standard_volumetric_current_density_variables(self, variables): domain, Domain = self.domain_Domain if self.options.electrode_types[domain] == "planar": return variables reaction_name = self.reaction_name phase_name = self.phase_name if isinstance(self, pybamm.kinetics.NoReaction): a = 0 else: a = variables[ f"{Domain} electrode {phase_name}surface area to volume ratio [m-1]" ] j = variables[ f"{Domain} electrode {reaction_name}interfacial current density [A.m-2]" ] a_j = a * j a_j_av = pybamm.x_average(a_j) if reaction_name == "SEI on cracks ": roughness = variables[f"{Domain} {phase_name}electrode roughness ratio"] - 1 roughness_av = ( variables[f"X-averaged {domain} {phase_name}electrode roughness ratio"] - 1 ) else: roughness = 1 roughness_av = 1 variables.update( { f"{Domain} electrode {reaction_name}volumetric " "interfacial current density [A.m-3]": a_j * roughness, f"X-averaged {domain} electrode {reaction_name}volumetric interfacial " "current density [A.m-3]": a_j_av * roughness_av, } ) return variables def _get_standard_overpotential_variables(self, eta_r): domain, Domain = self.domain_Domain reaction_name = self.reaction_name if self.reaction == "lithium metal plating": # half-cell domain variables = {"Lithium metal interface reaction overpotential [V]": eta_r} return variables # Size average. For eta_r variables that depend on particle size, see # "_get_standard_size_distribution_overpotential_variables" if eta_r.domain in [ ["negative particle size"], ["positive particle size"], ["negative primary particle size"], ["positive primary particle size"], ["negative secondary particle size"], ["positive secondary particle size"], ]: eta_r = pybamm.size_average(eta_r) # X-average, and broadcast if necessary eta_r_av = pybamm.x_average(eta_r) if eta_r.domain == ["current collector"]: eta_r = pybamm.PrimaryBroadcast(eta_r, f"{domain} electrode") variables = { f"{Domain} electrode {reaction_name}reaction overpotential [V]": eta_r, f"X-averaged {domain} electrode {reaction_name}reaction " "overpotential [V]": eta_r_av, } return variables def _get_standard_sei_film_overpotential_variables(self, eta_sei): domain, Domain = self.domain_Domain phase_name = self.phase_name Phase_name = phase_name.capitalize() if self.options.electrode_types[domain] == "planar": # half-cell domain variables = { f"{Domain} electrode {Phase_name}SEI film overpotential [V]": eta_sei, } return variables # Average, and broadcast if necessary eta_sei_av = pybamm.x_average(eta_sei) if eta_sei.domain == []: eta_sei = pybamm.FullBroadcast( eta_sei, f"{domain} electrode", "current collector" ) elif eta_sei.domain == ["current collector"]: eta_sei = pybamm.PrimaryBroadcast(eta_sei, f"{domain} electrode") variables = { f"{Domain} electrode {phase_name}SEI film overpotential [V]": eta_sei, f"X-averaged {domain} electrode {phase_name}SEI" " film overpotential [V]": eta_sei_av, } return variables def _get_standard_average_surface_potential_difference_variables( self, delta_phi_av ): domain = self.domain if self.options.electrode_types[domain] == "planar": variables = { "Lithium metal interface surface potential difference [V]" "": delta_phi_av, } else: variables = { f"X-averaged {domain} electrode " "surface potential difference [V]": delta_phi_av, } return variables def _get_standard_size_distribution_interfacial_current_variables(self, j): """ Interfacial current density variables that depend on particle size R, relevant if "particle size" option is "distribution". """ domain, Domain = self.domain_Domain reaction_name = self.reaction_name # X-average and broadcast if necessary if j.domains["secondary"] == [f"{domain} electrode"]: # x-average j_xav = pybamm.x_average(j) elif getattr(self, "reaction_loc", None) != "interface": j_xav = j j = pybamm.SecondaryBroadcast(j_xav, [f"{domain} electrode"]) else: j_xav = j variables = { f"{Domain} electrode {reaction_name}" "interfacial current density distribution [A.m-2]": j, f"X-averaged {domain} electrode {reaction_name}" "interfacial current density distribution [A.m-2]": j_xav, } return variables def _get_standard_size_distribution_exchange_current_variables(self, j0): """ Exchange current variables that depend on particle size. """ domain, Domain = self.domain_Domain reaction_name = self.reaction_name # X-average or broadcast to electrode if necessary if j0.domains["secondary"] != [f"{domain} electrode"]: j0_av = j0 j0 = pybamm.SecondaryBroadcast(j0, f"{domain} electrode") else: j0_av = pybamm.x_average(j0) variables = { f"{Domain} electrode {reaction_name}" "exchange current density distribution [A.m-2]": j0, f"X-averaged {domain} electrode {reaction_name}" "exchange current density distribution [A.m-2]": j0_av, } return variables def _get_standard_size_distribution_overpotential_variables(self, eta_r): """ Overpotential variables that depend on particle size. """ domain, Domain = self.domain_Domain reaction_name = self.reaction_name # X-average or broadcast to electrode if necessary if eta_r.domains["secondary"] != [f"{domain} electrode"]: eta_r_av = eta_r eta_r = pybamm.SecondaryBroadcast(eta_r, f"{domain} electrode") else: eta_r_av = pybamm.x_average(eta_r) domain_reaction = f"{Domain} electrode {reaction_name}reaction overpotential" variables = { f"{domain_reaction} [V]": eta_r, f"X-averaged {domain_reaction.lower()} distribution [V]": eta_r_av, } return variables