diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c59ab6073..49fdfb9c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +# [Unreleased](https://github.com/pybamm-team/PyBaMM) + +## Features +- Added composite surface form electrolyte models: `CompositeDifferential` and `CompositeAlgebraic` ([#1207](https://github.com/pybamm-team/PyBaMM/issues/1207)) + +## Optimizations + +## Bug fixes + +## Breaking changes + # [v0.3.0](https://github.com/pybamm-team/PyBaMM) - 2020-11-22 This release introduces a new aging model for particle swelling and cracking, a new reduced-order model (TSPMe), and a parameter set for A123 LFP cells. Additionally, there have been several backend optimizations to speed up model creation and solving, and other minor features and bug fixes. @@ -36,7 +47,7 @@ This release introduces a new aging model for particle swelling and cracking, a ## Breaking changes - Operations such as `1*x` and `0+x` now directly return `x`. This can be bypassed by explicitly creating the binary operators, e.g. `pybamm.Multiplication(1, x)` ([#1252](https://github.com/pybamm-team/PyBaMM/pull/1252)) -- The parameters "Positive/Negative particle distribution in x" and "Positive/Negative surface area per unit volume distribution in x" have been deprecated. Instead, users can provide "Positive/Negative particle radius [m]" and "Positive/Negative surface area per unit volume [m-1]" directly as functions of through-cell position (x [m]) ([#1237](https://github.com/pybamm-team/PyBaMM/pull/1237)) +- The parameters "Positive/Negative particle distribution in x" and "Positive/Negative surface area per unit volume distribution in x" have been deprecated. Instead, users can provide "Positive/Negative particle radius [m]" and "Positive/Negative surface area per unit volume [m-1]" directly as functions of through-cell position (x [m]) ([#1237](https://github.com/pybamm-team/PyBaMM/pull/1237)) # [v0.2.4](https://github.com/pybamm-team/PyBaMM/tree/v0.2.4) - 2020-09-07 diff --git a/examples/scripts/calendar_ageing.py b/examples/scripts/calendar_ageing.py index 3d37f296d5..1eb35d429a 100644 --- a/examples/scripts/calendar_ageing.py +++ b/examples/scripts/calendar_ageing.py @@ -5,9 +5,13 @@ models = [ pb.lithium_ion.SPM({"sei": "reaction limited"}), + pb.lithium_ion.SPMe({"sei": "reaction limited"}), pb.lithium_ion.SPM( {"sei": "reaction limited", "surface form": "algebraic"}, name="Algebraic SPM" ), + pb.lithium_ion.SPMe( + {"sei": "reaction limited", "surface form": "algebraic"}, name="Algebraic SPMe" + ), pb.lithium_ion.DFN({"sei": "reaction limited"}), ] diff --git a/pybamm/models/full_battery_models/lithium_ion/spme.py b/pybamm/models/full_battery_models/lithium_ion/spme.py index acfd940f12..cf59e1fdc8 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spme.py +++ b/pybamm/models/full_battery_models/lithium_ion/spme.py @@ -81,22 +81,31 @@ def set_tortuosity_submodels(self): def set_interfacial_submodel(self): - self.submodels["negative interface"] = pybamm.interface.InverseButlerVolmer( - self.param, "Negative", "lithium-ion main", self.options - ) - self.submodels["positive interface"] = pybamm.interface.InverseButlerVolmer( - self.param, "Positive", "lithium-ion main", self.options - ) - self.submodels[ - "negative interface current" - ] = pybamm.interface.CurrentForInverseButlerVolmer( - self.param, "Negative", "lithium-ion main" - ) - self.submodels[ - "positive interface current" - ] = pybamm.interface.CurrentForInverseButlerVolmer( - self.param, "Positive", "lithium-ion main" - ) + if self.options["surface form"] is False: + self.submodels["negative interface"] = pybamm.interface.InverseButlerVolmer( + self.param, "Negative", "lithium-ion main", self.options + ) + self.submodels["positive interface"] = pybamm.interface.InverseButlerVolmer( + self.param, "Positive", "lithium-ion main", self.options + ) + self.submodels[ + "negative interface current" + ] = pybamm.interface.CurrentForInverseButlerVolmer( + self.param, "Negative", "lithium-ion main" + ) + self.submodels[ + "positive interface current" + ] = pybamm.interface.CurrentForInverseButlerVolmer( + self.param, "Positive", "lithium-ion main" + ) + else: + self.submodels["negative interface"] = pybamm.interface.ButlerVolmer( + self.param, "Negative", "lithium-ion main", self.options + ) + + self.submodels["positive interface"] = pybamm.interface.ButlerVolmer( + self.param, "Positive", "lithium-ion main", self.options + ) def set_particle_submodel(self): @@ -137,6 +146,8 @@ def set_positive_electrode_submodel(self): def set_electrolyte_submodel(self): + surf_form = pybamm.electrolyte_conductivity.surface_potential_form + if self.options["electrolyte conductivity"] not in [ "default", "composite", @@ -158,17 +169,15 @@ def set_electrolyte_submodel(self): "electrolyte conductivity" ] = pybamm.electrolyte_conductivity.Integrated(self.param) elif self.options["surface form"] == "differential": - raise NotImplementedError( - "surface form '{}' has not been implemented for SPMe yet".format( - self.options["surface form"] - ) - ) + for domain in ["Negative", "Separator", "Positive"]: + self.submodels[ + domain.lower() + " electrolyte conductivity" + ] = surf_form.CompositeDifferential(self.param, domain) elif self.options["surface form"] == "algebraic": - raise NotImplementedError( - "surface form '{}' has not been implemented for SPMe yet".format( - self.options["surface form"] - ) - ) + for domain in ["Negative", "Separator", "Positive"]: + self.submodels[ + domain.lower() + " electrolyte conductivity" + ] = surf_form.CompositeAlgebraic(self.param, domain) self.submodels["electrolyte diffusion"] = pybamm.electrolyte_diffusion.Full( self.param diff --git a/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/__init__.py b/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/__init__.py index 985cb52531..ecec3243ec 100644 --- a/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/__init__.py +++ b/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/__init__.py @@ -1,6 +1,12 @@ # Full order models from .full_surface_form_conductivity import FullAlgebraic, FullDifferential +# Composite models +from .composite_surface_form_conductivity import ( + CompositeDifferential, + CompositeAlgebraic, +) + # Leading-order models from .leading_surface_form_conductivity import ( LeadingOrderDifferential, diff --git a/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/composite_surface_form_conductivity.py b/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/composite_surface_form_conductivity.py new file mode 100644 index 0000000000..0cd28f6757 --- /dev/null +++ b/pybamm/models/submodels/electrolyte_conductivity/surface_potential_form/composite_surface_form_conductivity.py @@ -0,0 +1,158 @@ +# +# Class for composite surface form electrolyte conductivity employing stefan-maxwell +# +import pybamm + +from ..composite_conductivity import Composite + + +class BaseModel(Composite): + """ + Base class for composite conservation of charge in the electrolyte employing + the Stefan-Maxwell constitutive equations employing the surface potential difference + formulation. + + Parameters + ---------- + param : parameter class + The parameters to use for this submodel + domain : str + The domain in which the model holds + reactions : dict + Dictionary of reaction terms + + **Extends:** :class:`pybamm.electrolyte_conductivity.Composite` + """ + + def __init__(self, param, domain): + super().__init__(param, domain) + + def get_fundamental_variables(self): + + if self.domain == "Negative": + delta_phi = pybamm.standard_variables.delta_phi_n_av + elif self.domain == "Separator": + return {} + elif self.domain == "Positive": + delta_phi = pybamm.standard_variables.delta_phi_p_av + + variables = self._get_standard_surface_potential_difference_variables(delta_phi) + return variables + + def set_initial_conditions(self, variables): + + if self.domain == "Separator": + return + + delta_phi = variables[ + "X-averaged " + + self.domain.lower() + + " electrode surface potential difference" + ] + if self.domain == "Negative": + delta_phi_init = self.param.U_n(self.param.c_n_init(0), self.param.T_init) + elif self.domain == "Positive": + delta_phi_init = self.param.U_p(self.param.c_p_init(1), self.param.T_init) + + self.initial_conditions = {delta_phi: delta_phi_init} + + def set_boundary_conditions(self, variables): + if self.domain == "Negative": + phi_e = variables["Electrolyte potential"] + self.boundary_conditions = { + phi_e: { + "left": (pybamm.Scalar(0), "Neumann"), + "right": (pybamm.Scalar(0), "Neumann"), + } + } + + +class CompositeDifferential(BaseModel): + """ + Composite model for conservation of charge in the electrolyte employing the + Stefan-Maxwell constitutive equations employing the surface potential difference + formulation and where capacitance is present. + + Parameters + ---------- + param : parameter class + The parameters to use for this submodel + + + **Extends:** :class:`BaseModel` + """ + + def __init__(self, param, domain): + super().__init__(param, domain) + + def set_rhs(self, variables): + if self.domain == "Separator": + return + + param = self.param + + sum_j = variables[ + "Sum of x-averaged " + + self.domain.lower() + + " electrode interfacial current densities" + ] + + sum_j_av = variables[ + "X-averaged " + + self.domain.lower() + + " electrode total interfacial current density" + ] + delta_phi = variables[ + "X-averaged " + + self.domain.lower() + + " electrode surface potential difference" + ] + + if self.domain == "Negative": + C_dl = param.C_dl_n + elif self.domain == "Positive": + C_dl = param.C_dl_p + + self.rhs[delta_phi] = 1 / C_dl * (sum_j_av - sum_j) + + +class CompositeAlgebraic(BaseModel): + """ + Composite model for conservation of charge in the electrolyte employing the + Stefan-Maxwell constitutive equations employing the surface potential difference + formulation. + + Parameters + ---------- + param : parameter class + The parameters to use for this submodel + + + **Extends:** :class:`BaseModel` + """ + + def __init__(self, param, domain): + super().__init__(param, domain) + + def set_algebraic(self, variables): + if self.domain == "Separator": + return + + sum_j = variables[ + "Sum of x-averaged " + + self.domain.lower() + + " electrode interfacial current densities" + ] + + sum_j_av = variables[ + "X-averaged " + + self.domain.lower() + + " electrode total interfacial current density" + ] + delta_phi = variables[ + "X-averaged " + + self.domain.lower() + + " electrode surface potential difference" + ] + + self.algebraic[delta_phi] = sum_j_av - sum_j diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spme.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spme.py index bf16934b4c..748d3e644d 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spme.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_spme.py @@ -110,13 +110,13 @@ def test_particle_shape_user(self): def test_surface_form_differential(self): options = {"surface form": "differential"} - with self.assertRaises(NotImplementedError): - pybamm.lithium_ion.SPMe(options) + model = pybamm.lithium_ion.SPMe(options) + model.check_well_posedness() def test_surface_form_algebraic(self): options = {"surface form": "algebraic"} - with self.assertRaises(NotImplementedError): - pybamm.lithium_ion.SPMe(options) + model = pybamm.lithium_ion.SPMe(options) + model.check_well_posedness() def test_integrated_conductivity(self): options = {"electrolyte conductivity": "integrated"} diff --git a/tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_surface_form/test_composite_surface_form_conductivity.py b/tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_surface_form/test_composite_surface_form_conductivity.py new file mode 100644 index 0000000000..a3cf14029b --- /dev/null +++ b/tests/unit/test_models/test_submodels/test_electrolyte_conductivity/test_surface_form/test_composite_surface_form_conductivity.py @@ -0,0 +1,76 @@ +# +# Test leading surface form stefan maxwell electrolyte conductivity submodel +# + +import pybamm +import tests +import unittest + + +class TestCompositeModel(unittest.TestCase): + def test_public_functions(self): + param = pybamm.LithiumIonParameters() + a = pybamm.PrimaryBroadcast(0, "current collector") + a_n = pybamm.FullBroadcast( + pybamm.Scalar(0), ["negative electrode"], "current collector" + ) + a_s = pybamm.FullBroadcast(pybamm.Scalar(0), ["separator"], "current collector") + a_p = pybamm.FullBroadcast( + pybamm.Scalar(0), ["positive electrode"], "current collector" + ) + c_e_n = pybamm.standard_variables.c_e_n + c_e_s = pybamm.standard_variables.c_e_s + c_e_p = pybamm.standard_variables.c_e_p + variables = { + "Leading-order current collector current density": a, + "Negative electrolyte concentration": c_e_n, + "Separator electrolyte concentration": c_e_s, + "Positive electrolyte concentration": c_e_p, + "X-averaged electrolyte concentration": a, + "X-averaged negative electrode potential": a, + "X-averaged negative electrode surface potential difference": a, + "Leading-order x-averaged negative electrode porosity": a, + "Leading-order x-averaged separator porosity": a, + "Leading-order x-averaged positive electrode porosity": a, + "Leading-order x-averaged negative electrolyte tortuosity": a, + "Leading-order x-averaged separator tortuosity": a, + "Leading-order x-averaged positive electrolyte tortuosity": a, + "X-averaged cell temperature": a, + "Current collector current density": a, + "Negative electrode porosity": a_n, + "Sum of x-averaged negative electrode interfacial current densities": a, + "X-averaged negative electrode total interfacial current density": a, + "Current collector current density": a, + "Negative electrolyte potential": a_n, + "Negative electrolyte current density": a_n, + "Separator electrolyte potential": a_s, + "Separator electrolyte current density": a_s, + "Positive electrode porosity": a_p, + "Sum of x-averaged positive electrode interfacial current densities": a, + "X-averaged positive electrode total interfacial current density": a, + } + + spf = pybamm.electrolyte_conductivity.surface_potential_form + submodel = spf.CompositeAlgebraic(param, "Negative") + std_tests = tests.StandardSubModelTests(submodel, variables) + std_tests.test_all() + submodel = spf.CompositeDifferential(param, "Negative") + std_tests = tests.StandardSubModelTests(submodel, variables) + std_tests.test_all() + + submodel = spf.CompositeAlgebraic(param, "Positive") + std_tests = tests.StandardSubModelTests(submodel, variables) + std_tests.test_all() + submodel = spf.CompositeDifferential(param, "Positive") + std_tests = tests.StandardSubModelTests(submodel, variables) + std_tests.test_all() + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + pybamm.settings.debug_mode = True + unittest.main()