diff --git a/CHANGELOG.md b/CHANGELOG.md index a8d7d155de..1e439dcf90 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ - Added `plot_thermal_components` to plot the contributions to the total heat generation in a battery ([#4021](https://github.com/pybamm-team/PyBaMM/pull/4021)) - Added functions for normal probability density function (`pybamm.normal_pdf`) and cumulative distribution function (`pybamm.normal_cdf`) ([#3999](https://github.com/pybamm-team/PyBaMM/pull/3999)) - Updates multiprocess `Pool` in `BaseSolver.solve()` to be constructed with context `fork`. Adds small example for multiprocess inputs. ([#3974](https://github.com/pybamm-team/PyBaMM/pull/3974)) +- Lithium plating now works on composite electrodes ([#3919](https://github.com/pybamm-team/PyBaMM/pull/3919)) +- Added lithium plating parameters to `Ecker2015` and `Ecker2015_graphite_halfcell` parameter sets ([#3919](https://github.com/pybamm-team/PyBaMM/pull/3919)) - Added custom experiment steps ([#3835](https://github.com/pybamm-team/PyBaMM/pull/3835)) - Added support for macOS arm64 (M-series) platforms. ([#3789](https://github.com/pybamm-team/PyBaMM/pull/3789)) - Added the ability to specify a custom solver tolerance in `get_initial_stoichiometries` and related functions ([#3714](https://github.com/pybamm-team/PyBaMM/pull/3714)) @@ -19,6 +21,7 @@ - Fixed a bug where independent variables were removed from models even if they appeared in events ([#4019](https://github.com/pybamm-team/PyBaMM/pull/4019)) - Fix bug with upwind and downwind schemes producing the wrong discretised system ([#3979](https://github.com/pybamm-team/PyBaMM/pull/3979)) - Allow evaluation of an `Interpolant` object with a number ([#3932](https://github.com/pybamm-team/PyBaMM/pull/3932)) +- Added scale to dead lithium variable ([#3919](https://github.com/pybamm-team/PyBaMM/pull/3919)) - `plot_voltage_components` now works even if the time does not start at 0 ([#3915](https://github.com/pybamm-team/PyBaMM/pull/3915)) - Fixed bug where separator porosity was used in calculation instead of transport efficiency ([#3905](https://github.com/pybamm-team/PyBaMM/pull/3905)) - Initial voltage can now match upper or lower cut-offs exactly ([#3842](https://github.com/pybamm-team/PyBaMM/pull/3842)) diff --git a/pybamm/input/parameters/lithium_ion/Ecker2015.py b/pybamm/input/parameters/lithium_ion/Ecker2015.py index fa9fd2dcad..32cc631293 100644 --- a/pybamm/input/parameters/lithium_ion/Ecker2015.py +++ b/pybamm/input/parameters/lithium_ion/Ecker2015.py @@ -293,6 +293,97 @@ def nco_electrolyte_exchange_current_density_Ecker2015(c_e, c_s_surf, c_s_max, T return m_ref * arrhenius * c_e**0.5 * c_s_surf**0.5 * (c_s_max - c_s_surf) ** 0.5 +def plating_exchange_current_density_OKane2020(c_e, c_Li, T): + """ + Exchange-current density for Li plating reaction [A.m-2]. + References + ---------- + .. [1] O’Kane, Simon EJ, Ian D. Campbell, Mohamed WJ Marzook, Gregory J. Offer, and + Monica Marinescu. "Physical origin of the differential voltage minimum associated + with lithium plating in Li-ion batteries." Journal of The Electrochemical Society + 167, no. 9 (2020): 090540. + Parameters + ---------- + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_Li : :class:`pybamm.Symbol` + Plated lithium concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + Returns + ------- + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + + k_plating = pybamm.Parameter("Lithium plating kinetic rate constant [m.s-1]") + + return pybamm.constants.F * k_plating * c_e + + +def stripping_exchange_current_density_OKane2020(c_e, c_Li, T): + """ + Exchange-current density for Li stripping reaction [A.m-2]. + + References + ---------- + + .. [1] O’Kane, Simon EJ, Ian D. Campbell, Mohamed WJ Marzook, Gregory J. Offer, and + Monica Marinescu. "Physical origin of the differential voltage minimum associated + with lithium plating in Li-ion batteries." Journal of The Electrochemical Society + 167, no. 9 (2020): 090540. + + Parameters + ---------- + + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_Li : :class:`pybamm.Symbol` + Plated lithium concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + + Returns + ------- + + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + + k_plating = pybamm.Parameter("Lithium plating kinetic rate constant [m.s-1]") + + return pybamm.constants.F * k_plating * c_Li + + +def SEI_limited_dead_lithium_OKane2022(L_sei): + """ + Decay rate for dead lithium formation [s-1]. + References + ---------- + .. [1] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diega Alonso-Alvarez, + Robert Timms, Valentin Sulzer, Jaqueline Sophie Edge, Billy Wu, Gregory J. Offer + and Monica Marinescu. "Lithium-ion battery degradation: how to model it." + Physical Chemistry: Chemical Physics 24, no. 13 (2022): 7909-7922. + Parameters + ---------- + L_sei : :class:`pybamm.Symbol` + Total SEI thickness [m] + Returns + ------- + :class:`pybamm.Symbol` + Dead lithium decay rate [s-1] + """ + + gamma_0 = pybamm.Parameter("Dead lithium decay constant [s-1]") + L_inner_0 = pybamm.Parameter("Initial inner SEI thickness [m]") + L_outer_0 = pybamm.Parameter("Initial outer SEI thickness [m]") + L_sei_0 = L_inner_0 + L_outer_0 + + gamma = gamma_0 * L_sei_0 / L_sei + + return gamma + + def electrolyte_diffusivity_Ecker2015(c_e, T): """ Diffusivity of LiPF6 in EC:DMC as a function of ion concentration [1, 2, 3]. @@ -409,6 +500,18 @@ def get_parameter_values(): return { "chemistry": "lithium_ion", + # lithium plating + "Lithium metal partial molar volume [m3.mol-1]": 1.3e-05, + "Lithium plating kinetic rate constant [m.s-1]": 1e-10, + "Exchange-current density for plating [A.m-2]" + "": plating_exchange_current_density_OKane2020, + "Exchange-current density for stripping [A.m-2]" + "": stripping_exchange_current_density_OKane2020, + "Initial plated lithium concentration [mol.m-3]": 0.0, + "Typical plated lithium concentration [mol.m-3]": 1000.0, + "Lithium plating transfer coefficient": 0.5, + "Dead lithium decay constant [s-1]": 1e-06, + "Dead lithium decay rate [s-1]": SEI_limited_dead_lithium_OKane2022, # sei "Ratio of lithium moles to SEI moles": 2.0, "Inner SEI reaction proportion": 0.5, @@ -530,5 +633,6 @@ def get_parameter_values(): "Zhao2018", "Hales2019", "Richardson2020", + "OKane2020", ], } diff --git a/pybamm/input/parameters/lithium_ion/Ecker2015_graphite_halfcell.py b/pybamm/input/parameters/lithium_ion/Ecker2015_graphite_halfcell.py index d878ff7400..365bb6386c 100644 --- a/pybamm/input/parameters/lithium_ion/Ecker2015_graphite_halfcell.py +++ b/pybamm/input/parameters/lithium_ion/Ecker2015_graphite_halfcell.py @@ -180,6 +180,97 @@ def graphite_electrolyte_exchange_current_density_Ecker2015(c_e, c_s_surf, c_s_m return m_ref * arrhenius * c_e**0.5 * c_s_surf**0.5 * (c_s_max - c_s_surf) ** 0.5 +def plating_exchange_current_density_OKane2020(c_e, c_Li, T): + """ + Exchange-current density for Li plating reaction [A.m-2]. + References + ---------- + .. [1] O’Kane, Simon EJ, Ian D. Campbell, Mohamed WJ Marzook, Gregory J. Offer, and + Monica Marinescu. "Physical origin of the differential voltage minimum associated + with lithium plating in Li-ion batteries." Journal of The Electrochemical Society + 167, no. 9 (2020): 090540. + Parameters + ---------- + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_Li : :class:`pybamm.Symbol` + Plated lithium concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + Returns + ------- + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + + k_plating = pybamm.Parameter("Lithium plating kinetic rate constant [m.s-1]") + + return pybamm.constants.F * k_plating * c_e + + +def stripping_exchange_current_density_OKane2020(c_e, c_Li, T): + """ + Exchange-current density for Li stripping reaction [A.m-2]. + + References + ---------- + + .. [1] O’Kane, Simon EJ, Ian D. Campbell, Mohamed WJ Marzook, Gregory J. Offer, and + Monica Marinescu. "Physical origin of the differential voltage minimum associated + with lithium plating in Li-ion batteries." Journal of The Electrochemical Society + 167, no. 9 (2020): 090540. + + Parameters + ---------- + + c_e : :class:`pybamm.Symbol` + Electrolyte concentration [mol.m-3] + c_Li : :class:`pybamm.Symbol` + Plated lithium concentration [mol.m-3] + T : :class:`pybamm.Symbol` + Temperature [K] + + Returns + ------- + + :class:`pybamm.Symbol` + Exchange-current density [A.m-2] + """ + + k_plating = pybamm.Parameter("Lithium plating kinetic rate constant [m.s-1]") + + return pybamm.constants.F * k_plating * c_Li + + +def SEI_limited_dead_lithium_OKane2022(L_sei): + """ + Decay rate for dead lithium formation [s-1]. + References + ---------- + .. [1] Simon E. J. O'Kane, Weilong Ai, Ganesh Madabattula, Diega Alonso-Alvarez, + Robert Timms, Valentin Sulzer, Jaqueline Sophie Edge, Billy Wu, Gregory J. Offer + and Monica Marinescu. "Lithium-ion battery degradation: how to model it." + Physical Chemistry: Chemical Physics 24, no. 13 (2022): 7909-7922. + Parameters + ---------- + L_sei : :class:`pybamm.Symbol` + Total SEI thickness [m] + Returns + ------- + :class:`pybamm.Symbol` + Dead lithium decay rate [s-1] + """ + + gamma_0 = pybamm.Parameter("Dead lithium decay constant [s-1]") + L_inner_0 = pybamm.Parameter("Initial inner SEI thickness [m]") + L_outer_0 = pybamm.Parameter("Initial outer SEI thickness [m]") + L_sei_0 = L_inner_0 + L_outer_0 + + gamma = gamma_0 * L_sei_0 / L_sei + + return gamma + + def electrolyte_diffusivity_Ecker2015(c_e, T): """ Diffusivity of LiPF6 in EC:DMC as a function of ion concentration [1, 2, 3]. @@ -332,6 +423,17 @@ def get_parameter_values(): return { "chemistry": "lithium_ion", + # lithium plating + "Lithium plating kinetic rate constant [m.s-1]": 1e-10, + "Exchange-current density for plating [A.m-2]" + "": plating_exchange_current_density_OKane2020, + "Exchange-current density for stripping [A.m-2]" + "": stripping_exchange_current_density_OKane2020, + "Initial plated lithium concentration [mol.m-3]": 0.0, + "Typical plated lithium concentration [mol.m-3]": 1000.0, + "Lithium plating transfer coefficient": 0.5, + "Dead lithium decay constant [s-1]": 1e-06, + "Dead lithium decay rate [s-1]": SEI_limited_dead_lithium_OKane2022, # sei "Ratio of lithium moles to SEI moles": 2.0, "Inner SEI reaction proportion": 0.5, @@ -435,5 +537,6 @@ def get_parameter_values(): "Hales2019", "Xu2019", "Richardson2020", + "OKane2020", ], } diff --git a/pybamm/models/full_battery_models/base_battery_model.py b/pybamm/models/full_battery_models/base_battery_model.py index 7a29ad64c7..335ac26650 100644 --- a/pybamm/models/full_battery_models/base_battery_model.py +++ b/pybamm/models/full_battery_models/base_battery_model.py @@ -609,14 +609,12 @@ def __init__(self, extra_options): and options["particle"] == "Fickian diffusion" and options["particle mechanics"] == "none" and options["loss of active material"] == "none" - and options["lithium plating"] == "none" ): raise pybamm.OptionError( "If there are multiple particle phases: 'surface form' cannot be " "'false', 'particle size' must be 'single', 'particle' must be " "'Fickian diffusion'. Also the following must " - "be 'none': 'particle mechanics', " - "'loss of active material', 'lithium plating'" + "be 'none': 'particle mechanics', 'loss of active material'" ) # Check options are valid diff --git a/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py b/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py index d6f80b3cc5..b7a8cfec71 100644 --- a/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py +++ b/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py @@ -331,17 +331,22 @@ def set_lithium_plating_submodel(self): if domain != "separator": domain = domain.split()[0].lower() lithium_plating_opt = getattr(self.options, domain)["lithium plating"] - if lithium_plating_opt == "none": - self.submodels[f"{domain} lithium plating"] = ( - pybamm.lithium_plating.NoPlating( - self.param, domain, self.options + phases = self.options.phases[domain] + for phase in phases: + if lithium_plating_opt == "none": + submodel = pybamm.lithium_plating.NoPlating( + self.param, domain, self.options, phase ) - ) - else: - x_average = self.options["x-average side reactions"] == "true" - self.submodels[f"{domain} lithium plating"] = ( - pybamm.lithium_plating.Plating( - self.param, domain, x_average, self.options + else: + x_average = self.options["x-average side reactions"] == "true" + submodel = pybamm.lithium_plating.Plating( + self.param, domain, x_average, self.options, phase + ) + self.submodels[f"{domain} {phase} lithium plating"] = submodel + if len(phases) > 1: + self.submodels[f"{domain} total lithium plating"] = ( + pybamm.lithium_plating.TotalLithiumPlating( + self.param, domain, self.options ) ) diff --git a/pybamm/models/submodels/interface/base_interface.py b/pybamm/models/submodels/interface/base_interface.py index b7e160ee2f..ab9b80eae0 100644 --- a/pybamm/models/submodels/interface/base_interface.py +++ b/pybamm/models/submodels/interface/base_interface.py @@ -38,6 +38,7 @@ def __init__(self, param, domain, reaction, options, phase="primary"): if reaction in [ "lithium-ion main", "lithium metal plating", + "lithium plating", "SEI", "SEI on cracks", ]: diff --git a/pybamm/models/submodels/interface/lithium_plating/__init__.py b/pybamm/models/submodels/interface/lithium_plating/__init__.py index cea178158d..1bb8262941 100644 --- a/pybamm/models/submodels/interface/lithium_plating/__init__.py +++ b/pybamm/models/submodels/interface/lithium_plating/__init__.py @@ -1,3 +1,4 @@ from .base_plating import BasePlating from .no_plating import NoPlating from .plating import Plating +from .total_lithium_plating import TotalLithiumPlating diff --git a/pybamm/models/submodels/interface/lithium_plating/base_plating.py b/pybamm/models/submodels/interface/lithium_plating/base_plating.py index ebfbe46831..7d9a4bb2e2 100644 --- a/pybamm/models/submodels/interface/lithium_plating/base_plating.py +++ b/pybamm/models/submodels/interface/lithium_plating/base_plating.py @@ -17,44 +17,30 @@ class BasePlating(BaseInterface): A dictionary of options to be passed to the model. """ - def __init__(self, param, domain, options=None): + def __init__(self, param, domain, options=None, phase="primary"): reaction = "lithium plating" - super().__init__(param, domain, reaction, options=options) + super().__init__(param, domain, reaction, options=options, phase=phase) def get_coupled_variables(self, variables): # Update some common variables domain, Domain = self.domain_Domain - if self.options.electrode_types[domain] == "porous": - j_plating = variables[ - f"{Domain} lithium plating interfacial current density [A.m-2]" - ] - j_plating_av = variables[ - f"X-averaged {domain} lithium plating " - "interfacial current density [A.m-2]" - ] - particle_phases_option = getattr(self.options, domain)["particle phases"] - if particle_phases_option == "1": - a = variables[f"{Domain} electrode surface area to volume ratio [m-1]"] - else: - a = variables[ - f"{Domain} electrode primary surface area to volume ratio [m-1]" - ] - a_j_plating = a * j_plating - a_j_plating_av = pybamm.x_average(a_j_plating) - - variables.update( - { - f"{Domain} electrode lithium plating interfacial current " - "density [A.m-2]": j_plating, - f"X-averaged {domain} electrode lithium plating " - "interfacial current density [A.m-2]": j_plating_av, - f"{Domain} lithium plating volumetric " - "interfacial current density [A.m-3]": a_j_plating, - f"X-averaged {domain} lithium plating volumetric " - "interfacial current density [A.m-3]": a_j_plating_av, - } - ) + j_plating_av = variables[ + f"X-averaged {domain} electrode {self.phase_name}lithium plating " + "interfacial current density [A.m-2]" + ] + j_plating = variables[ + f"{Domain} electrode {self.phase_name}lithium plating " + "interfacial current density [A.m-2]" + ] + variables.update( + { + f"X-averaged {domain} electrode {self.phase_name}lithium plating " + "interfacial current density [A.m-2]": j_plating_av, + f"{Domain} electrode {self.phase_name}lithium plating " + "interfacial current density [A.m-2]": j_plating, + } + ) variables.update( self._get_standard_volumetric_current_density_variables(variables) @@ -75,7 +61,8 @@ def _get_standard_concentration_variables(self, c_plated_Li, c_dead_Li): variables : dict The variables which can be derived from the plated lithium thickness. """ - param = self.param + phase_name = self.phase_name + phase_param = self.phase_param domain, Domain = self.domain_Domain # Set scales to one for the "no plating" model so that they are not required @@ -84,40 +71,43 @@ def _get_standard_concentration_variables(self, c_plated_Li, c_dead_Li): c_to_L = 1 L_k = 1 elif domain == "negative": - c_to_L = param.V_bar_Li / param.n.prim.a_typ - L_k = param.n.L + c_to_L = self.param.V_bar_Li / phase_param.a_typ + L_k = self.param.n.L elif domain == "positive": - c_to_L = param.V_bar_Li / param.p.prim.a_typ - L_k = param.p.L + c_to_L = self.param.V_bar_Li / phase_param.a_typ + L_k = self.param.p.L c_plated_Li_av = pybamm.x_average(c_plated_Li) L_plated_Li = c_plated_Li * c_to_L # plated Li thickness L_plated_Li_av = pybamm.x_average(L_plated_Li) - Q_plated_Li = c_plated_Li_av * L_k * param.L_y * param.L_z + Q_plated_Li = c_plated_Li_av * L_k * self.param.L_y * self.param.L_z c_dead_Li_av = pybamm.x_average(c_dead_Li) # dead Li "thickness", required by porosity submodel L_dead_Li = c_dead_Li * c_to_L L_dead_Li_av = pybamm.x_average(L_dead_Li) - Q_dead_Li = c_dead_Li_av * L_k * param.L_y * param.L_z + Q_dead_Li = c_dead_Li_av * L_k * self.param.L_y * self.param.L_z variables = { - f"{Domain} lithium plating concentration [mol.m-3]": c_plated_Li, - f"X-averaged {domain} lithium plating " - "concentration [mol.m-3]": c_plated_Li_av, - f"{Domain} dead lithium concentration [mol.m-3]": c_dead_Li, - f"X-averaged {domain} dead lithium concentration [mol.m-3]": c_dead_Li_av, - f"{Domain} lithium plating thickness [m]": L_plated_Li, - f"X-averaged {domain} lithium plating thickness [m]": L_plated_Li_av, - f"{Domain} dead lithium thickness [m]": L_dead_Li, - f"X-averaged {domain} dead lithium thickness [m]": L_dead_Li_av, - f"Loss of lithium to {domain} lithium plating " "[mol]": ( + f"{Domain} {phase_name}lithium plating concentration " + "[mol.m-3]": c_plated_Li, + f"X-averaged {domain} {phase_name}lithium plating concentration " + "[mol.m-3]": c_plated_Li_av, + f"{Domain} {phase_name}dead lithium concentration [mol.m-3]": c_dead_Li, + f"X-averaged {domain} {phase_name}dead lithium concentration " + "[mol.m-3]": c_dead_Li_av, + f"{Domain} {phase_name}lithium plating thickness [m]": L_plated_Li, + f"X-averaged {domain} {phase_name} lithium plating thickness " + "[m]": L_plated_Li_av, + f"{Domain} {phase_name}dead lithium thickness [m]": L_dead_Li, + f"X-averaged {domain} {phase_name}dead lithium thickness [m]": L_dead_Li_av, + f"Loss of lithium to {domain} {phase_name}lithium plating " "[mol]": ( Q_plated_Li + Q_dead_Li ), - f"Loss of capacity to {domain} lithium plating " "[A.h]": ( + f"Loss of capacity to {domain} {phase_name}lithium plating " "[A.h]": ( Q_plated_Li + Q_dead_Li ) - * param.F + * self.param.F / 3600, } @@ -140,9 +130,9 @@ def _get_standard_reaction_variables(self, j_stripping): j_stripping_av = pybamm.x_average(j_stripping) variables = { - f"{Domain} lithium plating interfacial current density " - "[A.m-2]": j_stripping, - f"X-averaged {domain} lithium plating " + f"{Domain} electrode {self.phase_name}lithium plating " + "interfacial current density [A.m-2]": j_stripping, + f"X-averaged {domain} electrode {self.phase_name}lithium plating " "interfacial current density [A.m-2]": j_stripping_av, } diff --git a/pybamm/models/submodels/interface/lithium_plating/no_plating.py b/pybamm/models/submodels/interface/lithium_plating/no_plating.py index 94697fdd89..ddc13cfb97 100644 --- a/pybamm/models/submodels/interface/lithium_plating/no_plating.py +++ b/pybamm/models/submodels/interface/lithium_plating/no_plating.py @@ -16,8 +16,8 @@ class NoPlating(BasePlating): A dictionary of options to be passed to the model. """ - def __init__(self, param, domain, options=None): - super().__init__(param, domain, options=options) + def __init__(self, param, domain, options=None, phase="primary"): + super().__init__(param, domain, options=options, phase=phase) def get_fundamental_variables(self): zero = pybamm.FullBroadcast( diff --git a/pybamm/models/submodels/interface/lithium_plating/plating.py b/pybamm/models/submodels/interface/lithium_plating/plating.py index 9f4de08d2f..cd130ab483 100644 --- a/pybamm/models/submodels/interface/lithium_plating/plating.py +++ b/pybamm/models/submodels/interface/lithium_plating/plating.py @@ -19,37 +19,42 @@ class Plating(BasePlating): A dictionary of options to be passed to the model. """ - def __init__(self, param, domain, x_average, options): - super().__init__(param, domain, options=options) + def __init__(self, param, domain, x_average, options, phase="primary"): + super().__init__(param, domain, options=options, phase=phase) self.x_average = x_average pybamm.citations.register("OKane2020") pybamm.citations.register("OKane2022") def get_fundamental_variables(self): domain, Domain = self.domain_Domain + scale = self.phase_param.c_Li_typ if self.x_average is True: c_plated_Li_av = pybamm.Variable( - f"X-averaged {domain} lithium plating concentration [mol.m-3]", + f"X-averaged {domain} {self.phase_name}lithium plating concentration " + "[mol.m-3]", domain="current collector", - scale=self.param.c_Li_typ, + scale=scale, ) c_plated_Li = pybamm.PrimaryBroadcast(c_plated_Li_av, f"{domain} electrode") c_dead_Li_av = pybamm.Variable( - f"X-averaged {domain} dead lithium concentration [mol.m-3]", + f"X-averaged {domain} {self.phase_name}dead lithium concentration " + "[mol.m-3]", domain="current collector", + scale=scale, ) c_dead_Li = pybamm.PrimaryBroadcast(c_dead_Li_av, f"{domain} electrode") else: c_plated_Li = pybamm.Variable( - f"{Domain} lithium plating concentration [mol.m-3]", + f"{Domain} {self.phase_name}lithium plating concentration [mol.m-3]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, - scale=self.param.c_Li_typ, + scale=scale, ) c_dead_Li = pybamm.Variable( - f"{Domain} dead lithium concentration [mol.m-3]", + f"{Domain} {self.phase_name}dead lithium concentration [mol.m-3]", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, + scale=scale, ) variables = self._get_standard_concentration_variables(c_plated_Li, c_dead_Li) @@ -57,22 +62,26 @@ def get_fundamental_variables(self): return variables def get_coupled_variables(self, variables): - param = self.param + phase_param = self.phase_param domain, Domain = self.domain_Domain delta_phi = variables[f"{Domain} electrode surface potential difference [V]"] c_e_n = variables[f"{Domain} electrolyte concentration [mol.m-3]"] T = variables[f"{Domain} electrode temperature [K]"] - eta_sei = variables[f"{Domain} electrode SEI film overpotential [V]"] - c_plated_Li = variables[f"{Domain} lithium plating concentration [mol.m-3]"] - j0_stripping = param.j0_stripping(c_e_n, c_plated_Li, T) - j0_plating = param.j0_plating(c_e_n, c_plated_Li, T) + eta_sei = variables[ + f"{Domain} electrode {self.phase_name}SEI film overpotential [V]" + ] + c_plated_Li = variables[ + f"{Domain} {self.phase_name}lithium plating concentration [mol.m-3]" + ] + j0_stripping = phase_param.j0_stripping(c_e_n, c_plated_Li, T) + j0_plating = phase_param.j0_plating(c_e_n, c_plated_Li, T) eta_stripping = delta_phi + eta_sei eta_plating = -eta_stripping - F_RT = param.F / (param.R * T) + F_RT = self.param.F / (self.param.R * T) # NEW: transfer coefficients can be set by the user - alpha_stripping = self.param.alpha_stripping - alpha_plating = self.param.alpha_plating + alpha_stripping = phase_param.alpha_stripping + alpha_plating = phase_param.alpha_plating lithium_plating_option = getattr(self.options, domain)["lithium plating"] if lithium_plating_option in ["reversible", "partially reversible"]: @@ -87,33 +96,41 @@ def get_coupled_variables(self, variables): variables.update(self._get_standard_overpotential_variables(eta_stripping)) variables.update(self._get_standard_reaction_variables(j_stripping)) - # Update whole cell variables, which also updates the "sum of" variables + # Add other standard coupled variables variables.update(super().get_coupled_variables(variables)) return variables def set_rhs(self, variables): domain, Domain = self.domain_Domain + phase_name = self.phase_name if self.x_average is True: c_plated_Li = variables[ - f"X-averaged {domain} lithium plating concentration [mol.m-3]" + f"X-averaged {domain} {phase_name}lithium plating concentration " + "[mol.m-3]" ] c_dead_Li = variables[ - f"X-averaged {domain} dead lithium concentration [mol.m-3]" + f"X-averaged {domain} {phase_name}dead lithium concentration [mol.m-3]" ] a_j_stripping = variables[ - f"X-averaged {domain} lithium plating volumetric " + f"X-averaged {domain} electrode {phase_name}lithium plating volumetric " "interfacial current density [A.m-3]" ] - L_sei = variables[f"X-averaged {domain} total SEI thickness [m]"] + L_sei = variables[ + f"X-averaged {domain} total {phase_name}SEI thickness [m]" + ] else: - c_plated_Li = variables[f"{Domain} lithium plating concentration [mol.m-3]"] - c_dead_Li = variables[f"{Domain} dead lithium concentration [mol.m-3]"] + c_plated_Li = variables[ + f"{Domain} {phase_name}lithium plating concentration [mol.m-3]" + ] + c_dead_Li = variables[ + f"{Domain} {phase_name}dead lithium concentration [mol.m-3]" + ] a_j_stripping = variables[ - f"{Domain} lithium plating volumetric " + f"{Domain} electrode {phase_name}lithium plating volumetric " "interfacial current density [A.m-3]" ] - L_sei = variables[f"{Domain} total SEI thickness [m]"] + L_sei = variables[f"{Domain} total {phase_name}SEI thickness [m]"] lithium_plating_option = getattr(self.options, domain)["lithium plating"] if lithium_plating_option == "reversible": @@ -127,7 +144,7 @@ def set_rhs(self, variables): elif lithium_plating_option == "partially reversible": # In the partially reversible plating model, the coupling term turns # reversible lithium into dead lithium over time. - dead_lithium_decay_rate = self.param.dead_lithium_decay_rate(L_sei) + dead_lithium_decay_rate = self.phase_param.dead_lithium_decay_rate(L_sei) coupling_term = dead_lithium_decay_rate * c_plated_Li dc_plated_Li = -a_j_stripping / self.param.F - coupling_term dc_dead_Li = coupling_term @@ -139,17 +156,23 @@ def set_rhs(self, variables): def set_initial_conditions(self, variables): domain, Domain = self.domain_Domain + phase_name = self.phase_name if self.x_average is True: c_plated_Li = variables[ - f"X-averaged {domain} lithium plating concentration [mol.m-3]" + f"X-averaged {domain} {phase_name}lithium plating concentration " + "[mol.m-3]" ] c_dead_Li = variables[ - f"X-averaged {domain} dead lithium concentration [mol.m-3]" + f"X-averaged {domain} {phase_name}dead lithium concentration [mol.m-3]" ] else: - c_plated_Li = variables[f"{Domain} lithium plating concentration [mol.m-3]"] - c_dead_Li = variables[f"{Domain} dead lithium concentration [mol.m-3]"] - c_plated_Li_0 = self.param.c_plated_Li_0 - zero = pybamm.Scalar(0) + c_plated_Li = variables[ + f"{Domain} {phase_name}lithium plating concentration [mol.m-3]" + ] + c_dead_Li = variables[ + f"{Domain} {phase_name}dead lithium concentration [mol.m-3]" + ] + c_plated_Li_0 = self.phase_param.c_plated_Li_0 + zero = 0 * c_plated_Li_0 self.initial_conditions = {c_plated_Li: c_plated_Li_0, c_dead_Li: zero} diff --git a/pybamm/models/submodels/interface/lithium_plating/total_lithium_plating.py b/pybamm/models/submodels/interface/lithium_plating/total_lithium_plating.py new file mode 100644 index 0000000000..105a9a938b --- /dev/null +++ b/pybamm/models/submodels/interface/lithium_plating/total_lithium_plating.py @@ -0,0 +1,43 @@ +# +# Class summing up contributions to the lithium plating reaction +# for cases with primary, secondary, ... reactions e.g. silicon-graphite +# +import pybamm + + +class TotalLithiumPlating(pybamm.BaseSubModel): + """ + Class summing up contributions to the lithium plating reaction + for cases with primary, secondary, ... reactions e.g. silicon-graphite + + Parameters + ---------- + param : + model parameters + options: dict + A dictionary of options to be passed to the model. + See :class:`pybamm.BaseBatteryModel` + """ + + def __init__(self, param, domain, options): + super().__init__(param, domain, options=options) + + def get_coupled_variables(self, variables): + domain, Domain = self.domain_Domain + phases = self.options.phases[domain] + # For each of the variables, the variable name without the phase name + # is constructed by summing all of the variable names with the phases + for variable_template in [ + f"{Domain} electrode {{}}lithium plating volumetric " + "interfacial current density [A.m-3]", + f"X-averaged {domain} electrode {{}}lithium plating volumetric " + "interfacial current density [A.m-3]", + f"Loss of lithium to {domain} {{}}lithium plating [mol]", + f"Loss of capacity to {domain} {{}}lithium plating [A.h]", + ]: + sumvar = sum( + variables[variable_template.format(phase + " ")] for phase in phases + ) + variables[variable_template.format("")] = sumvar + + return variables diff --git a/pybamm/parameters/lithium_ion_parameters.py b/pybamm/parameters/lithium_ion_parameters.py index e4e18579ba..7ef032f7f1 100644 --- a/pybamm/parameters/lithium_ion_parameters.py +++ b/pybamm/parameters/lithium_ion_parameters.py @@ -91,18 +91,10 @@ def _set_parameters(self): ] ) - # Lithium plating parameters + # Required by lithium plating and lithium metal plating reactions self.V_bar_Li = pybamm.Parameter( "Lithium metal partial molar volume [m3.mol-1]" ) - self.c_Li_typ = pybamm.Parameter( - "Typical plated lithium concentration [mol.m-3]" - ) - self.c_plated_Li_0 = pybamm.Parameter( - "Initial plated lithium concentration [mol.m-3]" - ) - self.alpha_plating = pybamm.Parameter("Lithium plating transfer coefficient") - self.alpha_stripping = 1 - self.alpha_plating # Initial conditions # Note: the initial concentration in the electrodes can be set as a function @@ -188,33 +180,6 @@ def j0_Li_metal(self, c_e, c_Li, T): "Exchange-current density for lithium metal electrode [A.m-2]", inputs ) - def j0_stripping(self, c_e, c_Li, T): - """Dimensional exchange-current density for stripping [A.m-2]""" - inputs = { - "Electrolyte concentration [mol.m-3]": c_e, - "Plated lithium concentration [mol.m-3]": c_Li, - "Temperature [K]": T, - } - return pybamm.FunctionParameter( - "Exchange-current density for stripping [A.m-2]", inputs - ) - - def j0_plating(self, c_e, c_Li, T): - """Dimensional exchange-current density for plating [A.m-2]""" - inputs = { - "Electrolyte concentration [mol.m-3]": c_e, - "Plated lithium concentration [mol.m-3]": c_Li, - "Temperature [K]": T, - } - return pybamm.FunctionParameter( - "Exchange-current density for plating [A.m-2]", inputs - ) - - def dead_lithium_decay_rate(self, L_sei): - """Dimensional dead lithium decay rate [s-1]""" - inputs = {"Total SEI thickness [m]": L_sei} - return pybamm.FunctionParameter("Dead lithium decay rate [s-1]", inputs) - class DomainLithiumIonParameters(BaseParameters): def __init__(self, domain, main_param): @@ -471,6 +436,18 @@ def _set_parameters(self): self.k_sei = pybamm.Parameter(f"{pref}SEI kinetic rate constant [m.s-1]") self.U_sei = pybamm.Parameter(f"{pref}SEI open-circuit potential [V]") + # 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 + if main.options.electrode_types[domain] == "planar": self.n_Li_init = pybamm.Scalar(0) self.Q_Li_init = pybamm.Scalar(0) @@ -601,6 +578,40 @@ def j0(self, c_e, c_s_surf, T, lithiation=None): inputs, ) + def j0_stripping(self, c_e, c_Li, T): + """Dimensional exchange-current density for stripping [A.m-2]""" + 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, + } + return pybamm.FunctionParameter( + f"{self.phase_prefactor}Exchange-current density for stripping [A.m-2]", + inputs, + ) + + def j0_plating(self, c_e, c_Li, T): + """Dimensional exchange-current density for plating [A.m-2]""" + 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, + } + return pybamm.FunctionParameter( + f"{self.phase_prefactor}Exchange-current density for plating [A.m-2]", + inputs, + ) + + def dead_lithium_decay_rate(self, L_sei): + """Dimensional dead lithium decay rate [s-1]""" + Domain = self.domain.capitalize() + inputs = {f"{Domain} total {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]""" # bound stoichiometry between tol and 1-tol. Adding 1/sto + 1/(sto-1) later diff --git a/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015.py b/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015.py index f239764743..2dc73d4484 100644 --- a/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015.py +++ b/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015.py @@ -13,6 +13,13 @@ def test_functions(self): T = pybamm.Scalar(298.15) fun_test = { + # Lithium plating + "Exchange-current density for plating [A.m-2]": ([1e3, 1e4, T], 9.6485e-3), + "Exchange-current density for stripping [A.m-2]": ( + [1e3, 1e4, T], + 9.6485e-2, + ), + "Dead lithium decay rate [s-1]": ([1e-8], 5e-7), # Negative electrode "Negative particle diffusivity [m2.s-1]": ([sto, T], 1.219e-14), "Negative electrode exchange-current density [A.m-2]": ( diff --git a/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015_graphite_halfcell.py b/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015_graphite_halfcell.py index 862ac8295b..f435ef6d36 100644 --- a/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015_graphite_halfcell.py +++ b/tests/unit/test_parameters/test_parameter_sets/test_Ecker2015_graphite_halfcell.py @@ -13,6 +13,13 @@ def test_functions(self): T = pybamm.Scalar(298.15) fun_test = { + # Lithium plating + "Exchange-current density for plating [A.m-2]": ([1e3, 1e4, T], 9.6485e-3), + "Exchange-current density for stripping [A.m-2]": ( + [1e3, 1e4, T], + 9.6485e-2, + ), + "Dead lithium decay rate [s-1]": ([1e-8], 5e-7), # Positive electrode "Positive particle diffusivity [m2.s-1]": ([sto, T], 1.219e-14), "Positive electrode exchange-current density [A.m-2]": (