diff --git a/CHANGELOG.md b/CHANGELOG.md index ce83454dc1..b6c67c1174 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Added `NewmanTobias` li-ion battery model ([#1423](https://github.com/pybamm-team/PyBaMM/pull/1423)) - Added `plot_voltage_components` to easily plot the component overpotentials that make up the voltage ([#1419](https://github.com/pybamm-team/PyBaMM/pull/1419)) - Made `QuickPlot` more customizable and added an example ([#1419](https://github.com/pybamm-team/PyBaMM/pull/1419)) - `Solution` objects can now be created by stepping *different* models ([#1408](https://github.com/pybamm-team/PyBaMM/pull/1408)) diff --git a/docs/source/models/lithium_ion/index.rst b/docs/source/models/lithium_ion/index.rst index d64edcb205..c3cb147cd1 100644 --- a/docs/source/models/lithium_ion/index.rst +++ b/docs/source/models/lithium_ion/index.rst @@ -7,3 +7,4 @@ Lithium-ion Models spm spme dfn + newman_tobias diff --git a/docs/source/models/lithium_ion/newman_tobias.rst b/docs/source/models/lithium_ion/newman_tobias.rst new file mode 100644 index 0000000000..e304c69535 --- /dev/null +++ b/docs/source/models/lithium_ion/newman_tobias.rst @@ -0,0 +1,5 @@ +Newman-Tobias +============= + +.. autoclass:: pybamm.lithium_ion.NewmanTobias + :members: diff --git a/examples/scripts/compare_lithium_ion.py b/examples/scripts/compare_lithium_ion.py index d018988106..6108036b9b 100644 --- a/examples/scripts/compare_lithium_ion.py +++ b/examples/scripts/compare_lithium_ion.py @@ -6,7 +6,12 @@ pybamm.set_logging_level("INFO") # load models -models = [pybamm.lithium_ion.SPM(), pybamm.lithium_ion.SPMe(), pybamm.lithium_ion.DFN()] +models = [ + pybamm.lithium_ion.SPM(), + pybamm.lithium_ion.SPMe(), + pybamm.lithium_ion.DFN(), + pybamm.lithium_ion.NewmanTobias(), +] # create and run simulations sims = [] diff --git a/pybamm/CITATIONS.txt b/pybamm/CITATIONS.txt index d92c303d17..fe71c76820 100644 --- a/pybamm/CITATIONS.txt +++ b/pybamm/CITATIONS.txt @@ -358,4 +358,25 @@ doi={10.1149/2.0661810jes} year={2004}, publisher={IOP Publishing}, doi={10.1149/1.1634273}, -} \ No newline at end of file +} + +@article{Newman1962, + title={Theoretical analysis of current distribution in porous electrodes}, + author={Newman, John S and Tobias, Charles W}, + journal={Journal of The Electrochemical Society}, + volume={109}, + number={12}, + pages={1183}, + year={1962}, + publisher={IOP Publishing} +} + +@article{Chu2020, + title={Parameterization of prismatic lithium--iron--phosphate cells through a streamlined thermal/electrochemical model}, + author={Chu, Howie N and Kim, Sun Ung and Rahimian, Saeed Khaleghi and Siegel, Jason B and Monroe, Charles W}, + journal={Journal of Power Sources}, + volume={453}, + pages={227787}, + year={2020}, + publisher={Elsevier} +} diff --git a/pybamm/models/full_battery_models/base_battery_model.py b/pybamm/models/full_battery_models/base_battery_model.py index 8821f9a947..bb206670c4 100644 --- a/pybamm/models/full_battery_models/base_battery_model.py +++ b/pybamm/models/full_battery_models/base_battery_model.py @@ -33,6 +33,9 @@ class Options(pybamm.FuzzyDict): * "dimensionality" : int Sets the dimension of the current collector problem. Can be 0 (default), 1 or 2. + * "electrolyte conductivity" : str + Can be "default" (default), "full", "leading order", "composite" or + "integrated". * "external submodels" : list A list of the submodels that you would like to supply an external variable for instead of solving in PyBaMM. The entries of the lists @@ -131,9 +134,6 @@ class Options(pybamm.FuzzyDict): solve an algebraic equation for it. Default is "false", unless "SEI film resistance" is distributed in which case it is automatically set to "true". - * "electrolyte conductivity" : str - Can be "default" (default), "full", "leading order", "composite" or - "integrated" **Extends:** :class:`dict` """ @@ -459,7 +459,6 @@ def set_standard_output_variables(self): # Spatial var = pybamm.standard_spatial_vars L_x = self.param.L_x - L_y = self.param.L_y L_z = self.param.L_z self.variables.update( { @@ -476,8 +475,9 @@ def set_standard_output_variables(self): if self.options["dimensionality"] == 1: self.variables.update({"z": var.z, "z [m]": var.z * L_z}) elif self.options["dimensionality"] == 2: + # Note: both y and z are scaled with L_z self.variables.update( - {"y": var.y, "y [m]": var.y * L_y, "z": var.z, "z [m]": var.z * L_z} + {"y": var.y, "y [m]": var.y * L_z, "z": var.z, "z [m]": var.z * L_z} ) # Initialize "total reaction" variables @@ -511,7 +511,7 @@ def build_fundamental_and_external(self): ) self.variables.update(submodel.get_fundamental_variables()) - # set the submodels that are external + # Set the submodels that are external for sub in self.options["external submodels"]: self.submodels[sub].external = True diff --git a/pybamm/models/full_battery_models/lithium_ion/__init__.py b/pybamm/models/full_battery_models/lithium_ion/__init__.py index 52e9e3dc76..31f9b3032c 100644 --- a/pybamm/models/full_battery_models/lithium_ion/__init__.py +++ b/pybamm/models/full_battery_models/lithium_ion/__init__.py @@ -5,6 +5,7 @@ from .spm import SPM from .spme import SPMe from .dfn import DFN +from .newman_tobias import NewmanTobias from .basic_dfn import BasicDFN from .basic_spm import BasicSPM from .basic_dfn_half_cell import BasicDFNHalfCell diff --git a/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py b/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py new file mode 100644 index 0000000000..35b6c9bab2 --- /dev/null +++ b/pybamm/models/full_battery_models/lithium_ion/newman_tobias.py @@ -0,0 +1,111 @@ +# +# Newman Tobias Model +# +import pybamm +from .dfn import DFN + + +class NewmanTobias(DFN): + """ + Newman-Tobias model of a lithium-ion battery based on the formulation in [1]_. + This model assumes a uniform concentration profile in the electrolyte. + Unlike the model posed in [1]_, this model accounts for nonlinear Butler-Volmer + kinetics. It also tracks the average concentration in the solid phase in each + electrode, which is equivalent to including an equation for the local state of + charge as in [2]_. The user can pass the "particle" option to include mass + transport in the particles. + + Parameters + ---------- + options : dict, optional + A dictionary of options to be passed to the model. + name : str, optional + The name of the model. + build : bool, optional + Whether to build the model on instantiation. Default is True. Setting this + option to False allows users to change any number of the submodels before + building the complete model (submodels cannot be changed after the model is + built). + + References + ---------- + .. [1] JS Newman and CW Tobias. "Theoretical Analysis of Current Distribution + in Porous Electrodes". Journal of The Electrochemical Society, + 109(12):A1183-A1191, 1962 + .. [2] HN Chu, SU Kim, SK Rahimian, JB Siegel and CW Monroe. "Parameterization + of prismatic lithium–iron–phosphate cells through a streamlined + thermal/electrochemical model". Journal of Power Sources, 453, p.227787, + 2020 + + + **Extends:** :class:`pybamm.lithium_ion.DFN` + """ + + def __init__(self, options=None, name="Newman-Tobias model", build=True): + + # Set default option "uniform profile" for particle submodel. Other + # default options are those given in `pybamm.Options` defined in + # `base_battery_model.py`. + options = options or {} + if "particle" not in options: + options["particle"] = "uniform profile" + + super().__init__(options, name, build) + + pybamm.citations.register("Newman1962") + pybamm.citations.register("Chu2020") + + def set_particle_submodel(self): + + if self.options["particle"] == "Fickian diffusion": + self.submodels["negative particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Negative" + ) + self.submodels["positive particle"] = pybamm.particle.FickianSingleParticle( + self.param, "Positive" + ) + elif self.options["particle"] in [ + "uniform profile", + "quadratic profile", + "quartic profile", + ]: + self.submodels[ + "negative particle" + ] = pybamm.particle.PolynomialSingleParticle( + self.param, "Negative", self.options["particle"] + ) + self.submodels[ + "positive particle" + ] = pybamm.particle.PolynomialSingleParticle( + self.param, "Positive", self.options["particle"] + ) + + def set_electrolyte_submodel(self): + + surf_form = pybamm.electrolyte_conductivity.surface_potential_form + + self.submodels[ + "electrolyte diffusion" + ] = pybamm.electrolyte_diffusion.ConstantConcentration(self.param) + + if self.options["electrolyte conductivity"] not in ["default", "full"]: + raise pybamm.OptionError( + "electrolyte conductivity '{}' not suitable for Newman-Tobias".format( + self.options["electrolyte conductivity"] + ) + ) + + if self.options["surface form"] == "false": + self.submodels[ + "electrolyte conductivity" + ] = pybamm.electrolyte_conductivity.Full(self.param) + elif self.options["surface form"] == "differential": + for domain in ["Negative", "Separator", "Positive"]: + self.submodels[ + domain.lower() + " electrolyte conductivity" + ] = surf_form.FullDifferential(self.param, domain) + elif self.options["surface form"] == "algebraic": + for domain in ["Negative", "Separator", "Positive"]: + self.submodels[ + domain.lower() + " electrolyte conductivity" + ] = surf_form.FullAlgebraic(self.param, domain) diff --git a/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py b/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py index 570bce61b3..7001675d2f 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py +++ b/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py @@ -45,3 +45,19 @@ def get_coupled_variables(self, variables): variables.update(self._get_total_concentration_electrolyte(c_e, eps)) return variables + + def set_boundary_conditions(self, variables): + """ + We provide boundary conditions even though the concentration is constant + so that the gradient of the concentration has the correct shape after + discretisation. + """ + + c_e = variables["Electrolyte concentration"] + + self.boundary_conditions = { + c_e: { + "left": (pybamm.Scalar(0), "Neumann"), + "right": (pybamm.Scalar(0), "Neumann"), + } + } diff --git a/pybamm/simulation.py b/pybamm/simulation.py index b74b070e46..e5c4f0e3a3 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -832,6 +832,8 @@ def plot(self, output_variables=None, quick_plot_vars=None, **kwargs): self._solution, output_variables=output_variables, **kwargs ) + return self.quick_plot + @property def model(self): return self._model diff --git a/tests/integration/test_models/standard_output_tests.py b/tests/integration/test_models/standard_output_tests.py index 783edb6866..4e6acab491 100644 --- a/tests/integration/test_models/standard_output_tests.py +++ b/tests/integration/test_models/standard_output_tests.py @@ -624,7 +624,7 @@ def test_interfacial_current_average(self): axis=0, ), self.i_cell / self.l_n, - decimal=3, + decimal=4, ) np.testing.assert_array_almost_equal( np.mean( @@ -633,7 +633,7 @@ def test_interfacial_current_average(self): axis=0, ), -self.i_cell / self.l_p, - decimal=3, + decimal=4, ) def test_conservation(self): diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py index 56a0f0d502..66dd960a2b 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_dfn.py @@ -172,6 +172,10 @@ def positive_radius(x): param["Negative particle radius [m]"] = negative_radius param["Positive particle radius [m]"] = positive_radius + # Only get 3dp of accuracy in some tests at 1C with particle distribution + # TODO: investigate if there is a bug or some way to improve the + # implementation + param["Current function [A]"] = 0.5 * param["Nominal cell capacity [A.h]"] modeltest = tests.StandardModelTest(model, parameter_values=param) modeltest.test_all() diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py new file mode 100644 index 0000000000..ea24ab2312 --- /dev/null +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py @@ -0,0 +1,241 @@ +# +# Tests for the lithium-ion Newman-Tobias model +# +import pybamm +import tests + +import numpy as np +import unittest + + +class TestNewmanTobias(unittest.TestCase): + def test_basic_processing(self): + options = {"thermal": "isothermal"} + model = pybamm.lithium_ion.NewmanTobias(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_basic_processing_1plus1D(self): + options = {"current collector": "potential pair", "dimensionality": 1} + model = pybamm.lithium_ion.NewmanTobias(options) + var = pybamm.standard_spatial_vars + var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.y: 5, + var.z: 5, + } + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all(skip_output_tests=True) + + def test_basic_processing_2plus1D(self): + options = {"current collector": "potential pair", "dimensionality": 2} + model = pybamm.lithium_ion.NewmanTobias(options) + var = pybamm.standard_spatial_vars + var_pts = { + var.x_n: 5, + var.x_s: 5, + var.x_p: 5, + var.r_n: 5, + var.r_p: 5, + var.y: 5, + var.z: 5, + } + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all(skip_output_tests=True) + + def test_optimisations(self): + options = {"thermal": "isothermal"} + model = pybamm.lithium_ion.NewmanTobias(options) + optimtest = tests.OptimisationsTest(model) + + original = optimtest.evaluate_model() + using_known_evals = optimtest.evaluate_model(use_known_evals=True) + to_python = optimtest.evaluate_model(to_python=True) + np.testing.assert_array_almost_equal(original, using_known_evals) + np.testing.assert_array_almost_equal(original, to_python) + + def test_set_up(self): + model = pybamm.lithium_ion.NewmanTobias() + optimtest = tests.OptimisationsTest(model) + optimtest.set_up_model(to_python=True) + optimtest.set_up_model(to_python=False) + + def test_particle_fickian(self): + options = {"particle": "Fickian diffusion"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_particle_quadratic(self): + options = {"particle": "quadratic profile"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_particle_quartic(self): + options = {"particle": "quartic profile"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_full_thermal(self): + options = {"thermal": "x-full"} + model = pybamm.lithium_ion.NewmanTobias(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_lumped_thermal(self): + options = {"thermal": "lumped"} + model = pybamm.lithium_ion.NewmanTobias(options) + var = pybamm.standard_spatial_vars + var_pts = {var.x_n: 10, var.x_s: 10, var.x_p: 10, var.r_n: 5, var.r_p: 5} + modeltest = tests.StandardModelTest(model, var_pts=var_pts) + modeltest.test_all() + + def test_loss_active_material(self): + options = {"particle cracking": "none", "loss of active material": "none"} + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_loss_active_material_negative(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "negative", + } + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_loss_active_material_positive(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "positive", + } + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_loss_active_material_both(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "both", + } + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_surface_form_differential(self): + options = {"surface form": "differential"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_surface_form_algebraic(self): + options = {"surface form": "algebraic"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + +class TestNewmanTobiasWithSEI(unittest.TestCase): + def test_well_posed_constant(self): + options = {"SEI": "constant"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_reaction_limited(self): + options = {"SEI": "reaction limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_reaction_limited_average_film_resistance(self): + options = {"SEI": "reaction limited", "SEI film resistance": "average"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_solvent_diffusion_limited(self): + options = {"SEI": "solvent-diffusion limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_electron_migration_limited(self): + options = {"SEI": "electron-migration limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_interstitial_diffusion_limited(self): + options = {"SEI": "interstitial-diffusion limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + def test_well_posed_ec_reaction_limited(self): + options = {"SEI": "ec reaction limited", "SEI porosity change": "true"} + model = pybamm.lithium_ion.NewmanTobias(options) + modeltest = tests.StandardModelTest(model) + modeltest.test_all() + + +class TestNewmanTobiasWithCrack(unittest.TestCase): + def test_well_posed_no_cracking(self): + options = {"particle cracking": "no cracking"} + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_well_posed_negative_cracking(self): + options = {"particle cracking": "negative"} + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_well_posed_positive_cracking(self): + options = {"particle cracking": "positive"} + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + def test_well_posed_both_cracking(self): + options = {"particle cracking": "both"} + model = pybamm.lithium_ion.NewmanTobias(options) + chemistry = pybamm.parameter_sets.Ai2020 + parameter_values = pybamm.ParameterValues(chemistry=chemistry) + modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) + modeltest.test_all() + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + unittest.main() diff --git a/tests/unit/test_citations.py b/tests/unit/test_citations.py index f870e3cab9..4d5a318562 100644 --- a/tests/unit/test_citations.py +++ b/tests/unit/test_citations.py @@ -140,6 +140,17 @@ def test_brosaplanella_2020(self): pybamm.electrolyte_conductivity.Integrated(None) self.assertIn("BrosaPlanella2020", citations._papers_to_cite) + def test_newman_tobias(self): + # Test that calling relevant bits of code adds the right paper to citations + citations = pybamm.citations + + citations._reset() + self.assertNotIn("Newman1962", citations._papers_to_cite) + self.assertNotIn("Chu2020", citations._papers_to_cite) + pybamm.lithium_ion.NewmanTobias() + self.assertIn("Newman1962", citations._papers_to_cite) + self.assertIn("Chu2020", citations._papers_to_cite) + def test_scikit_fem(self): citations = pybamm.citations diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py new file mode 100644 index 0000000000..4372269641 --- /dev/null +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_newman_tobias.py @@ -0,0 +1,246 @@ +# +# Tests for the lithium-ion Newman-Tobias model +# +import pybamm +import unittest + + +class TestNewmanTobias(unittest.TestCase): + def test_well_posed(self): + options = {"thermal": "isothermal"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_2plus1D(self): + options = {"current collector": "potential pair", "dimensionality": 1} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + options = {"current collector": "potential pair", "dimensionality": 2} + pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + options = {"bc_options": {"dimensionality": 5}} + with self.assertRaises(pybamm.OptionError): + model = pybamm.lithium_ion.NewmanTobias(options) + + def test_lumped_thermal_model_1D(self): + options = {"thermal": "x-lumped"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_x_full_thermal_model(self): + options = {"thermal": "x-full"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_x_full_Nplus1D_not_implemented(self): + # 1plus1D + options = { + "current collector": "potential pair", + "dimensionality": 1, + "thermal": "x-full", + } + with self.assertRaises(NotImplementedError): + pybamm.lithium_ion.NewmanTobias(options) + # 2plus1D + options = { + "current collector": "potential pair", + "dimensionality": 2, + "thermal": "x-full", + } + with self.assertRaises(NotImplementedError): + pybamm.lithium_ion.NewmanTobias(options) + + def test_lumped_thermal_1plus1D(self): + options = { + "current collector": "potential pair", + "dimensionality": 1, + "thermal": "lumped", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_lumped_thermal_2plus1D(self): + options = { + "current collector": "potential pair", + "dimensionality": 2, + "thermal": "lumped", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_thermal_1plus1D(self): + options = { + "current collector": "potential pair", + "dimensionality": 1, + "thermal": "x-lumped", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_thermal_2plus1D(self): + options = { + "current collector": "potential pair", + "dimensionality": 2, + "thermal": "x-lumped", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_particle_fickian(self): + options = {"particle": "Fickian diffusion"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_particle_quadratic(self): + options = {"particle": "quadratic profile"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_particle_quartic(self): + options = {"particle": "quartic profile"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_particle_shape_user(self): + options = {"particle shape": "user"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_loss_active_material(self): + options = { + "loss of active material": "none", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_loss_active_material_negative(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "negative", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_loss_active_material_positive(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "positive", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_loss_active_material_both(self): + options = { + "particle cracking": "no cracking", + "loss of active material": "both", + } + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_surface_form_differential(self): + options = {"surface form": "differential"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_surface_form_algebraic(self): + options = {"surface form": "algebraic"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_electrolyte_options(self): + options = {"electrolyte conductivity": "integrated"} + with self.assertRaisesRegex(pybamm.OptionError, "electrolyte conductivity"): + pybamm.lithium_ion.NewmanTobias(options) + + +class TestNewmanTobiasWithSEI(unittest.TestCase): + def test_well_posed_constant(self): + options = {"SEI": "constant"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_reaction_limited(self): + options = {"SEI": "reaction limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_reaction_limited_average_film_resistance(self): + options = {"SEI": "reaction limited", "SEI film resistance": "average"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_solvent_diffusion_limited(self): + options = {"SEI": "solvent-diffusion limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_electron_migration_limited(self): + options = {"SEI": "electron-migration limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_interstitial_diffusion_limited(self): + options = {"SEI": "interstitial-diffusion limited"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_ec_reaction_limited(self): + options = {"SEI": "ec reaction limited", "SEI porosity change": "true"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + +class TestNewmanTobiasWithCrack(unittest.TestCase): + def test_well_posed_none_crack(self): + options = {"particle cracking": "none"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_no_cracking(self): + options = {"particle cracking": "no cracking"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_negative_cracking(self): + options = {"particle cracking": "negative"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_positive_cracking(self): + options = {"particle cracking": "positive"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_both_cracking(self): + options = {"particle cracking": "both"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + +class TestNewmanTobiasWithPlating(unittest.TestCase): + def test_well_posed_none_plating(self): + options = {"lithium plating": "none"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_reversible_plating(self): + options = {"lithium plating": "reversible"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + def test_well_posed_irreversible_plating(self): + options = {"lithium plating": "irreversible"} + model = pybamm.lithium_ion.NewmanTobias(options) + model.check_well_posedness() + + +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()