diff --git a/CHANGELOG.md b/CHANGELOG.md index 0446b5125a..701603584c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,15 @@ ## Optimizations - Removed the `start_step_offset` setting and disabled minimum `dt` warnings for drive cycles with the (`IDAKLUSolver`). ([#4416](https://github.com/pybamm-team/PyBaMM/pull/4416)) +## Features + +- Added phase-dependent particle options to LAM #4369 + ## Breaking changes - The parameters "... electrode OCP entropic change [V.K-1]" and "... electrode volume change" are now expected to be functions of stoichiometry only instead of functions of both stoichiometry and maximum concentration ([#4427](https://github.com/pybamm-team/PyBaMM/pull/4427)) - Renamed `set_events` function to `add_events_from` to better reflect its purpose. ([#4421](https://github.com/pybamm-team/PyBaMM/pull/4421)) + # [v24.9.0](https://github.com/pybamm-team/PyBaMM/tree/v24.9.0) - 2024-09-03 ## Features diff --git a/docs/source/examples/notebooks/models/loss_of_active_materials.ipynb b/docs/source/examples/notebooks/models/loss_of_active_materials.ipynb index ad428e6791..1ce1cca826 100644 --- a/docs/source/examples/notebooks/models/loss_of_active_materials.ipynb +++ b/docs/source/examples/notebooks/models/loss_of_active_materials.ipynb @@ -30,7 +30,7 @@ "output_type": "stream", "text": [ "At t = 57.3387, , mxstep steps taken before reaching tout.\n", - "At t = 57.3387 and h = 7.05477e-15, the corrector convergence failed repeatedly or with |h| = hmin.\n", + "At t = 57.3387, , mxstep steps taken before reaching tout.\n", "At t = 57.3387, , mxstep steps taken before reaching tout.\n", "At t = 57.3387, , mxstep steps taken before reaching tout.\n" ] @@ -83,12 +83,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6b19474c3912495eb75217e009760637", + "model_id": "ccfc7ae873d1492197fa7b554339a3d7", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=2.329196798170269, step=0.02329196798170269)…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=2.3291967981693755, step=0.02329196798169375…" ] }, "metadata": {}, @@ -97,7 +97,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -137,11 +137,11 @@ "output_type": "stream", "text": [ "At t = 57.3387, , mxstep steps taken before reaching tout.\n", - "At t = 57.3387 and h = 7.05477e-15, the corrector convergence failed repeatedly or with |h| = hmin.\n", + "At t = 57.3387, , mxstep steps taken before reaching tout.\n", "At t = 57.3387, , mxstep steps taken before reaching tout.\n", "At t = 57.3387, , mxstep steps taken before reaching tout.\n", "At t = 57.3307, , mxstep steps taken before reaching tout.\n", - "At t = 57.3307, , mxstep steps taken before reaching tout.\n", + "At t = 57.3307 and h = 3.45325e-14, the corrector convergence failed repeatedly or with |h| = hmin.\n", "At t = 57.3307, , mxstep steps taken before reaching tout.\n", "At t = 57.3307, , mxstep steps taken before reaching tout.\n", "At t = 57.2504, , mxstep steps taken before reaching tout.\n", @@ -153,12 +153,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "789a681c8c574bb8b3d3016a844dd9a2", + "model_id": "b34472112ae344da92ccc8af5178c64b", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=2.329196798170269, step=0.02329196798170269)…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=2.3291967981693755, step=0.02329196798169375…" ] }, "metadata": {}, @@ -167,7 +167,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 3, @@ -225,12 +225,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "ad36439975754b29bbbef1bd94379408", + "model_id": "60db1d0de494460493cc8edd5b61d4e7", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=1.8531298311682403, step=0.01853129831168240…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=1.85353350947348, step=0.0185353350947348), …" ] }, "metadata": {}, @@ -239,7 +239,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -248,6 +248,16 @@ } ], "source": [ + "import pybamm\n", + "\n", + "experiment = pybamm.Experiment(\n", + " [\n", + " \"Discharge at 1C until 3 V\",\n", + " \"Rest for 600 seconds\",\n", + " \"Charge at 1C until 4.2 V\",\n", + " \"Hold at 4.199 V for 600 seconds\",\n", + " ]\n", + ")\n", "model = pybamm.lithium_ion.DFN(\n", " options={\n", " \"SEI\": \"solvent-diffusion limited\",\n", @@ -255,7 +265,7 @@ " }\n", ")\n", "param = pybamm.ParameterValues(\"Chen2020\")\n", - "param.update({\"Negative electrode reaction-driven LAM factor [m3.mol-1]\": 1e-3})\n", + "param.update({\"Negative electrode reaction-driven LAM factor [m3.mol-1]\": 1e-4})\n", "sim = pybamm.Simulation(\n", " model,\n", " experiment=experiment,\n", @@ -300,12 +310,12 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "91ea043e10d342049929095e48e98c5e", + "model_id": "1dfb1de5ccde449c9eefcda1b1f44468", "version_major": 2, "version_minor": 0 }, "text/plain": [ - "interactive(children=(FloatSlider(value=0.0, description='t', max=1.8506629989989005, step=0.01850662998998900…" + "interactive(children=(FloatSlider(value=0.0, description='t', max=1.8506629988943608, step=0.01850662998894360…" ] }, "metadata": {}, @@ -314,7 +324,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -358,6 +368,297 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## LAM with composite electrode\n", + "The LAM submodel is also compatible with multiple phases within an electrode for both stress- and reaction-driven loss of active material. Currently, there is no single parameter set that combines both LAM degradation and composite materials. The following examples use the Chen2020 composite parameter set with LAM parameters taken from the Ai2020 parameter set. " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "# Volume change functions from Ai2020 parameters\n", + "\n", + "\n", + "def graphite_volume_change_Ai2020(sto):\n", + " p1 = 145.907\n", + " p2 = -681.229\n", + " p3 = 1334.442\n", + " p4 = -1415.710\n", + " p5 = 873.906\n", + " p6 = -312.528\n", + " p7 = 60.641\n", + " p8 = -5.706\n", + " p9 = 0.386\n", + " p10 = -4.966e-05\n", + " t_change = (\n", + " p1 * sto**9\n", + " + p2 * sto**8\n", + " + p3 * sto**7\n", + " + p4 * sto**6\n", + " + p5 * sto**5\n", + " + p6 * sto**4\n", + " + p7 * sto**3\n", + " + p8 * sto**2\n", + " + p9 * sto\n", + " + p10\n", + " )\n", + " return t_change\n", + "\n", + "\n", + "def lico2_volume_change_Ai2020(sto):\n", + " omega = pybamm.Parameter(\"Positive electrode partial molar volume [m3.mol-1]\")\n", + " c_s_max = pybamm.Parameter(\"Maximum concentration in positive electrode [mol.m-3]\")\n", + " t_change = omega * c_s_max * sto\n", + " return t_change" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Stress-driven composite anode\n", + "The secondary phase LAM parameters have been adjusted from the Ai2020 by about 10% to show less degradation in that phase. The model is set up in the same way the single-phase simulation is but with additional parameters." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "options = {\n", + " \"particle phases\": (\"2\", \"1\"),\n", + " \"open-circuit potential\": ((\"single\", \"current sigmoid\"), \"single\"),\n", + " \"loss of active material\": \"stress-driven\",\n", + "}\n", + "\n", + "model = pybamm.lithium_ion.SPM(options)\n", + "parameter_values = pybamm.ParameterValues(\"Chen2020_composite\")\n", + "second = 0.1\n", + "parameter_values.update(\n", + " {\n", + " \"Primary: Negative electrode LAM constant proportional term [s-1]\": 1e-4 / 3600,\n", + " \"Secondary: Negative electrode LAM constant proportional term [s-1]\": 1e-4\n", + " / 3600\n", + " * second,\n", + " \"Positive electrode LAM constant proportional term [s-1]\": 1e-4 / 3600,\n", + " \"Primary: Negative electrode partial molar volume [m3.mol-1]\": 3.1e-06,\n", + " \"Primary: Negative electrode Young's modulus [Pa]\": 15000000000.0,\n", + " \"Primary: Negative electrode Poisson's ratio\": 0.3,\n", + " \"Primary: Negative electrode critical stress [Pa]\": 60000000.0,\n", + " \"Secondary: Negative electrode critical stress [Pa]\": 60000000.0,\n", + " \"Primary: Negative electrode LAM constant exponential term\": 2.0,\n", + " \"Secondary: Negative electrode LAM constant exponential term\": 2.0,\n", + " \"Secondary: Negative electrode partial molar volume [m3.mol-1]\": 3.1e-06\n", + " * second,\n", + " \"Secondary: Negative electrode Young's modulus [Pa]\": 15000000000.0 * second,\n", + " \"Secondary: Negative electrode Poisson's ratio\": 0.3 * second,\n", + " \"Negative electrode reference concentration for free of deformation [mol.m-3]\": 0.0,\n", + " \"Primary: Negative electrode volume change\": graphite_volume_change_Ai2020,\n", + " \"Secondary: Negative electrode volume change\": graphite_volume_change_Ai2020,\n", + " \"Positive electrode partial molar volume [m3.mol-1]\": -7.28e-07,\n", + " \"Positive electrode Young's modulus [Pa]\": 375000000000.0,\n", + " \"Positive electrode Poisson's ratio\": 0.2,\n", + " \"Positive electrode critical stress [Pa]\": 375000000.0,\n", + " \"Positive electrode LAM constant exponential term\": 2.0,\n", + " \"Positive electrode reference concentration for free of deformation [mol.m-3]\": 0.0,\n", + " \"Positive electrode volume change\": lico2_volume_change_Ai2020,\n", + " },\n", + " check_already_exists=False,\n", + ")\n", + "\n", + "# sim = pybamm.Simulation(model, parameter_values=parameter_values)\n", + "# sim.solve([0, 4500])\n", + "experiment = pybamm.Experiment(\n", + " [\n", + " \"Discharge at 1C until 3 V\",\n", + " \"Rest for 600 seconds\",\n", + " \"Charge at 1C until 4.2 V\",\n", + " \"Hold at 4.199 V for 600 seconds\",\n", + " ]\n", + ")\n", + "sim = pybamm.Simulation(\n", + " model,\n", + " experiment=experiment,\n", + " parameter_values=parameter_values,\n", + " discretisation_kwargs={\"remove_independent_variables_from_rhs\": True},\n", + ")\n", + "solution = sim.solve(calc_esoh=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The two phase LAM model can be compared between the cathode and two anode phases." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "074bcadceb3e4fbd8cc786e798bb6508", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=2.1702864080208446, step=0.02170286408020844…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "pybamm.dynamic_plot(\n", + " sim,\n", + " [\n", + " \"Voltage [V]\",\n", + " \"Current [A]\",\n", + " [\n", + " \"Average negative primary particle concentration\",\n", + " \"Average negative secondary particle concentration\",\n", + " \"Average positive particle concentration\",\n", + " ],\n", + " \"X-averaged negative electrode primary active material volume fraction\",\n", + " \"X-averaged positive electrode active material volume fraction\",\n", + " \"X-averaged negative electrode secondary active material volume fraction\",\n", + " \"Sum of x-averaged positive electrode volumetric interfacial current densities [A.m-3]\",\n", + " \"Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]\",\n", + " \"X-averaged positive particle surface tangential stress [Pa]\",\n", + " \"X-averaged negative primary particle surface tangential stress [Pa]\",\n", + " \"X-averaged negative secondary particle surface tangential stress [Pa]\",\n", + " ],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reaction-driven composite anode\n", + "The same process is repeated for the reaction-driven LAM degradation." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "98a2b1762a3c43bcaa9ceff5a146d704", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "interactive(children=(FloatSlider(value=0.0, description='t', max=2.081773444877257, step=0.02081773444877257)…" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "options = {\n", + " \"particle phases\": (\"2\", \"1\"),\n", + " \"open-circuit potential\": ((\"single\", \"current sigmoid\"), \"single\"),\n", + " \"SEI\": \"solvent-diffusion limited\",\n", + " \"loss of active material\": \"reaction-driven\",\n", + "}\n", + "\n", + "model = pybamm.lithium_ion.SPM(options)\n", + "parameter_values = pybamm.ParameterValues(\"Chen2020_composite\")\n", + "second = 0.9\n", + "\n", + "parameter_values.update(\n", + " {\n", + " \"Primary: Negative electrode partial molar volume [m3.mol-1]\": 3.1e-06,\n", + " \"Primary: Negative electrode Young's modulus [Pa]\": 15000000000.0,\n", + " \"Primary: Negative electrode Poisson's ratio\": 0.3,\n", + " \"Negative electrode critical stress [Pa]\": 60000000.0,\n", + " \"Negative electrode LAM constant exponential term\": 2.0,\n", + " \"Secondary: Negative electrode partial molar volume [m3.mol-1]\": 3.1e-06\n", + " * second,\n", + " \"Secondary: Negative electrode Young's modulus [Pa]\": 15000000000.0 * second,\n", + " \"Secondary: Negative electrode Poisson's ratio\": 0.3 * second,\n", + " \"Negative electrode reference concentration for free of deformation [mol.m-3]\": 0.0,\n", + " \"Primary: Negative electrode volume change\": graphite_volume_change_Ai2020,\n", + " \"Secondary: Negative electrode volume change\": graphite_volume_change_Ai2020,\n", + " \"Positive electrode partial molar volume [m3.mol-1]\": -7.28e-07,\n", + " \"Positive electrode Young's modulus [Pa]\": 375000000000.0,\n", + " \"Positive electrode Poisson's ratio\": 0.2,\n", + " \"Positive electrode critical stress [Pa]\": 375000000.0,\n", + " \"Positive electrode LAM constant exponential term\": 2.0,\n", + " \"Positive electrode reference concentration for free of deformation [mol.m-3]\": 0.0,\n", + " \"Positive electrode volume change\": lico2_volume_change_Ai2020,\n", + " \"Primary: Negative electrode reaction-driven LAM factor [m3.mol-1]\": 1e-9,\n", + " \"Secondary: Negative electrode reaction-driven LAM factor [m3.mol-1]\": 10,\n", + " },\n", + " check_already_exists=False,\n", + ")\n", + "\n", + "# Changing secondary SEI solvent diffusivity to show different degradation between phases\n", + "parameter_values.update(\n", + " {\n", + " \"Secondary: Outer SEI solvent diffusivity [m2.s-1]\": 2.5000000000000002e-24,\n", + " }\n", + ")\n", + "\n", + "# sim = pybamm.Simulation(model, parameter_values=parameter_values)\n", + "# sim.solve([0, 4100])\n", + "sim = pybamm.Simulation(\n", + " model,\n", + " experiment=experiment,\n", + " parameter_values=parameter_values,\n", + " solver=pybamm.CasadiSolver(\"fast with events\"),\n", + ")\n", + "solution = sim.solve(calc_esoh=False)\n", + "\n", + "sim.plot(\n", + " [\n", + " \"Voltage [V]\",\n", + " \"Current [A]\",\n", + " \"Sum of x-averaged negative electrode volumetric interfacial current densities [A.m-3]\",\n", + " \"X-averaged negative electrode primary active material volume fraction\",\n", + " \"X-averaged negative electrode secondary active material volume fraction\",\n", + " \"Negative total primary SEI thickness [m]\",\n", + " \"Negative total secondary SEI thickness [m]\",\n", + " ]\n", + ")" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -369,22 +670,26 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "[1] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.\n", - "[2] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n", - "[3] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.\n", - "[4] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.\n", - "[5] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n", - "[6] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n", - "[7] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.\n", - "[8] Jorn M. Reniers, Grietus Mulder, and David A. Howey. Review and performance comparison of mechanical-chemical degradation models for lithium-ion batteries. Journal of The Electrochemical Society, 166(14):A3189, 2019. doi:10.1149/2.0281914jes.\n", - "[9] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n", + "[1] Weilong Ai, Niall Kirkaldy, Yang Jiang, Gregory Offer, Huizhi Wang, and Billy Wu. A composite electrode model for lithium-ion batteries with silicon/graphite negative electrodes. Journal of Power Sources, 527:231142, 2022. URL: https://www.sciencedirect.com/science/article/pii/S0378775322001604, doi:https://doi.org/10.1016/j.jpowsour.2022.231142.\n", + "[2] Weilong Ai, Ludwig Kraft, Johannes Sturm, Andreas Jossen, and Billy Wu. Electrochemical thermal-mechanical modelling of stress inhomogeneity in lithium-ion pouch cells. Journal of The Electrochemical Society, 167(1):013512, 2019. doi:10.1149/2.0122001JES.\n", + "[3] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n", + "[4] Ferran Brosa Planella and W. Dhammika Widanage. Systematic derivation of a Single Particle Model with Electrolyte and Side Reactions (SPMe+SR) for degradation of lithium-ion batteries. Submitted for publication, ():, 2022. doi:.\n", + "[5] Von DAG Bruggeman. Berechnung verschiedener physikalischer konstanten von heterogenen substanzen. i. dielektrizitätskonstanten und leitfähigkeiten der mischkörper aus isotropen substanzen. Annalen der physik, 416(7):636–664, 1935.\n", + "[6] Chang-Hui Chen, Ferran Brosa Planella, Kieran O'Regan, Dominika Gastol, W. Dhammika Widanage, and Emma Kendrick. Development of Experimental Techniques for Parameterization of Multi-scale Lithium-ion Battery Models. Journal of The Electrochemical Society, 167(8):080534, 2020. doi:10.1149/1945-7111/ab9050.\n", + "[7] Rutooj Deshpande, Mark Verbrugge, Yang-Tse Cheng, John Wang, and Ping Liu. Battery cycle life prediction with coupled chemical degradation and fatigue mechanics. Journal of the Electrochemical Society, 159(10):A1730, 2012. doi:10.1149/2.049210jes.\n", + "[8] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n", + "[9] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n", + "[10] Scott G. Marquis. Long-term degradation of lithium-ion batteries. PhD thesis, University of Oxford, 2020.\n", + "[11] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n", + "[12] Jorn M. Reniers, Grietus Mulder, and David A. Howey. Review and performance comparison of mechanical-chemical degradation models for lithium-ion batteries. Journal of The Electrochemical Society, 166(14):A3189, 2019. doi:10.1149/2.0281914jes.\n", + "[13] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n", "\n" ] } @@ -417,7 +722,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.9" }, "toc": { "base_numbering": 1, diff --git a/src/pybamm/models/full_battery_models/base_battery_model.py b/src/pybamm/models/full_battery_models/base_battery_model.py index e0ade7b429..f4943a1b2b 100644 --- a/src/pybamm/models/full_battery_models/base_battery_model.py +++ b/src/pybamm/models/full_battery_models/base_battery_model.py @@ -618,14 +618,11 @@ def __init__(self, extra_options): options["surface form"] != "false" and options["particle size"] == "single" and options["particle"] == "Fickian diffusion" - and options["particle mechanics"] == "none" - and options["loss of active material"] == "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'" + "'Fickian diffusion'." ) if options["surface temperature"] == "lumped": diff --git a/src/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py b/src/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py index dfe2512f6e..b1367f8300 100644 --- a/src/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py +++ b/src/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py @@ -365,29 +365,31 @@ def set_crack_submodel(self): for domain in self.options.whole_cell_domains: if domain != "separator": domain = domain.split()[0].lower() - crack = getattr(self.options, domain)["particle mechanics"] - if crack == "none": - self.submodels[f"{domain} particle mechanics"] = ( - pybamm.particle_mechanics.NoMechanics( - self.param, domain, options=self.options, phase="primary" + phases = self.options.phases[domain] + for phase in phases: + crack = getattr(self.options, domain)["particle mechanics"] + if crack == "none": + self.submodels[f"{domain} {phase}particle mechanics"] = ( + pybamm.particle_mechanics.NoMechanics( + self.param, domain, options=self.options, phase=phase + ) ) - ) - elif crack == "swelling only": - self.submodels[f"{domain} particle mechanics"] = ( - pybamm.particle_mechanics.SwellingOnly( - self.param, domain, options=self.options, phase="primary" + elif crack == "swelling only": + self.submodels[f"{domain} {phase}particle mechanics"] = ( + pybamm.particle_mechanics.SwellingOnly( + self.param, domain, options=self.options, phase=phase + ) ) - ) - elif crack == "swelling and cracking": - self.submodels[f"{domain} particle mechanics"] = ( - pybamm.particle_mechanics.CrackPropagation( - self.param, - domain, - self.x_average, - options=self.options, - phase="primary", + elif crack == "swelling and cracking": + self.submodels[f"{domain} {phase}particle mechanics"] = ( + pybamm.particle_mechanics.CrackPropagation( + self.param, + domain, + self.x_average, + options=self.options, + phase=phase, + ) ) - ) def set_active_material_submodel(self): for domain in ["negative", "positive"]: @@ -401,7 +403,7 @@ def set_active_material_submodel(self): ) else: submod = pybamm.active_material.LossActiveMaterial( - self.param, domain, self.options, self.x_average + self.param, domain, self.options, self.x_average, phase ) self.submodels[f"{domain} {phase} active material"] = submod diff --git a/src/pybamm/models/submodels/active_material/constant_active_material.py b/src/pybamm/models/submodels/active_material/constant_active_material.py index 3237775f1c..e978168e9a 100644 --- a/src/pybamm/models/submodels/active_material/constant_active_material.py +++ b/src/pybamm/models/submodels/active_material/constant_active_material.py @@ -23,6 +23,7 @@ class Constant(BaseModel): def get_fundamental_variables(self): domain = self.domain + phase = self.phase_name eps_solid = self.phase_param.epsilon_s deps_solid_dt = pybamm.FullBroadcast( 0, f"{domain} electrode", "current collector" @@ -35,7 +36,7 @@ def get_fundamental_variables(self): variables.update( { - "Loss of lithium due to loss of active material " + f"Loss of lithium due to loss of {phase}active material " f"in {domain} electrode [mol]": pybamm.Scalar(0) } ) diff --git a/src/pybamm/models/submodels/active_material/loss_active_material.py b/src/pybamm/models/submodels/active_material/loss_active_material.py index 6f027d89e6..ffba064d03 100644 --- a/src/pybamm/models/submodels/active_material/loss_active_material.py +++ b/src/pybamm/models/submodels/active_material/loss_active_material.py @@ -23,34 +23,36 @@ class LossActiveMaterial(BaseModel): """ - def __init__(self, param, domain, options, x_average): - super().__init__(param, domain, options=options) + def __init__(self, param, domain, options, x_average, phase): + super().__init__(param, domain, options=options, phase=phase) pybamm.citations.register("Reniers2019") self.x_average = x_average def get_fundamental_variables(self): domain, Domain = self.domain_Domain + phase = self.phase_name if self.x_average is True: eps_solid_xav = pybamm.Variable( - f"X-averaged {domain} electrode active material volume fraction", + f"X-averaged {domain} electrode {phase}active material volume fraction", domain="current collector", ) eps_solid = pybamm.PrimaryBroadcast(eps_solid_xav, f"{domain} electrode") else: eps_solid = pybamm.Variable( - f"{Domain} electrode active material volume fraction", + f"{Domain} electrode {phase}active material volume fraction", domain=f"{domain} electrode", auxiliary_domains={"secondary": "current collector"}, ) variables = self._get_standard_active_material_variables(eps_solid) lli_due_to_lam = pybamm.Variable( - "Loss of lithium due to loss of active material " + f"Loss of lithium due to loss of {phase}active material " f"in {domain} electrode [mol]" ) + variables.update( { - "Loss of lithium due to loss of active material " + f"Loss of lithium due to loss of {phase}active material " f"in {domain} electrode [mol]": lli_due_to_lam } ) @@ -58,6 +60,7 @@ def get_fundamental_variables(self): def get_coupled_variables(self, variables): domain, Domain = self.domain_Domain + phase_name = self.phase_name deps_solid_dt = 0 lam_option = getattr(getattr(self.options, domain), self.phase)[ @@ -68,22 +71,22 @@ def get_coupled_variables(self, variables): # This is loss of active material model by mechanical effects if self.x_average is True: stress_t_surf = variables[ - f"X-averaged {domain} particle surface tangential stress [Pa]" + f"X-averaged {domain} {phase_name}particle surface tangential stress [Pa]" ] stress_r_surf = variables[ - f"X-averaged {domain} particle surface radial stress [Pa]" + f"X-averaged {domain} {phase_name}particle surface radial stress [Pa]" ] else: stress_t_surf = variables[ - f"{Domain} particle surface tangential stress [Pa]" + f"{Domain} {phase_name}particle surface tangential stress [Pa]" ] stress_r_surf = variables[ - f"{Domain} particle surface radial stress [Pa]" + f"{Domain} {phase_name}particle surface radial stress [Pa]" ] - beta_LAM = self.domain_param.beta_LAM - stress_critical = self.domain_param.stress_critical - m_LAM = self.domain_param.m_LAM + beta_LAM = self.phase_param.beta_LAM + stress_critical = self.phase_param.stress_critical + m_LAM = self.phase_param.m_LAM stress_h_surf = (stress_r_surf + 2 * stress_t_surf) / 3 # compressive stress make no contribution @@ -97,15 +100,15 @@ def get_coupled_variables(self, variables): deps_solid_dt += j_stress_LAM if "reaction" in lam_option: - beta_LAM_sei = self.domain_param.beta_LAM_sei + beta_LAM_sei = self.phase_param.beta_LAM_sei if self.x_average is True: a_j_sei = variables[ - f"X-averaged {domain} electrode SEI " + f"X-averaged {domain} electrode {phase_name}SEI " "volumetric interfacial current density [A.m-3]" ] else: a_j_sei = variables[ - f"{Domain} electrode SEI volumetric " + f"{Domain} electrode {phase_name}SEI volumetric " "interfacial current density [A.m-3]" ] @@ -131,19 +134,22 @@ def get_coupled_variables(self, variables): def set_rhs(self, variables): domain, Domain = self.domain_Domain + phase_name = self.phase_name if self.x_average is True: eps_solid = variables[ - f"X-averaged {domain} electrode active material volume fraction" + f"X-averaged {domain} electrode {phase_name}active material volume fraction" ] deps_solid_dt = variables[ - f"X-averaged {domain} electrode active material " + f"X-averaged {domain} electrode {phase_name}active material " "volume fraction change [s-1]" ] else: - eps_solid = variables[f"{Domain} electrode active material volume fraction"] + eps_solid = variables[ + f"{Domain} electrode {phase_name}active material volume fraction" + ] deps_solid_dt = variables[ - f"{Domain} electrode active material volume fraction change [s-1]" + f"{Domain} electrode {phase_name}active material volume fraction change [s-1]" ] # Loss of lithium due to loss of active material @@ -151,11 +157,13 @@ def set_rhs(self, variables): # simulations using adaptive inter-cycle extrapolation algorithm." # Journal of The Electrochemical Society 168.12 (2021): 120531. lli_due_to_lam = variables[ - "Loss of lithium due to loss of active material " + f"Loss of lithium due to loss of {phase_name}active material " f"in {domain} electrode [mol]" ] # Multiply by mol.m-3 * m3 to get mol - c_s_av = variables[f"Average {domain} particle concentration [mol.m-3]"] + c_s_av = variables[ + f"Average {domain} {phase_name}particle concentration [mol.m-3]" + ] V = self.domain_param.L * self.param.A_cc self.rhs = { @@ -166,20 +174,23 @@ def set_rhs(self, variables): def set_initial_conditions(self, variables): domain, Domain = self.domain_Domain + phase_name = self.phase_name - eps_solid_init = self.domain_param.prim.epsilon_s + eps_solid_init = self.phase_param.epsilon_s if self.x_average is True: eps_solid_xav = variables[ - f"X-averaged {domain} electrode active material volume fraction" + f"X-averaged {domain} electrode {phase_name}active material volume fraction" ] self.initial_conditions = {eps_solid_xav: pybamm.x_average(eps_solid_init)} else: - eps_solid = variables[f"{Domain} electrode active material volume fraction"] + eps_solid = variables[ + f"{Domain} electrode {phase_name}active material volume fraction" + ] self.initial_conditions = {eps_solid: eps_solid_init} lli_due_to_lam = variables[ - "Loss of lithium due to loss of active material " + f"Loss of lithium due to loss of {phase_name}active material " f"in {domain} electrode [mol]" ] self.initial_conditions[lli_due_to_lam] = pybamm.Scalar(0) diff --git a/src/pybamm/models/submodels/active_material/total_active_material.py b/src/pybamm/models/submodels/active_material/total_active_material.py index 5e1d7e2f92..f86486ff53 100644 --- a/src/pybamm/models/submodels/active_material/total_active_material.py +++ b/src/pybamm/models/submodels/active_material/total_active_material.py @@ -34,6 +34,7 @@ def get_coupled_variables(self, variables): f"{Domain} electrode {{}}active material volume fraction change [s-1]", f"X-averaged {domain} electrode {{}}active material " "volume fraction change [s-1]", + f"Loss of lithium due to loss of {{}}active material in {domain} electrode [mol]", ]: sumvar = sum( variables[variable_template.format(phase + " ")] for phase in phases diff --git a/src/pybamm/models/submodels/particle/base_particle.py b/src/pybamm/models/submodels/particle/base_particle.py index dab48b5f79..fe37d2ff2e 100644 --- a/src/pybamm/models/submodels/particle/base_particle.py +++ b/src/pybamm/models/submodels/particle/base_particle.py @@ -57,9 +57,9 @@ def _get_effective_diffusivity(self, c, T, current): if stress_option == "true": # Ai2019 eq [12] sto = c / phase_param.c_max - Omega = pybamm.r_average(domain_param.Omega(sto, T)) - E = pybamm.r_average(domain_param.E(sto, T)) - nu = domain_param.nu + Omega = pybamm.r_average(phase_param.Omega(sto, T)) + E = pybamm.r_average(phase_param.E(sto, T)) + nu = phase_param.nu theta_M = Omega / (param.R * T) * (2 * Omega * E) / (9 * (1 - nu)) stress_factor = 1 + theta_M * (c - domain_param.c_0) else: diff --git a/src/pybamm/models/submodels/particle_mechanics/base_mechanics.py b/src/pybamm/models/submodels/particle_mechanics/base_mechanics.py index 4e25becbab..1301722da0 100644 --- a/src/pybamm/models/submodels/particle_mechanics/base_mechanics.py +++ b/src/pybamm/models/submodels/particle_mechanics/base_mechanics.py @@ -38,30 +38,37 @@ def _get_standard_variables(self, l_cr): def _get_mechanical_results(self, variables): domain_param = self.domain_param domain, Domain = self.domain_Domain + phase_name = self.phase_name + phase_param = self.phase_param - c_s_rav = variables[f"R-averaged {domain} particle concentration [mol.m-3]"] - sto_rav = variables[f"R-averaged {domain} particle concentration"] - c_s_surf = variables[f"{Domain} particle surface concentration [mol.m-3]"] + c_s_rav = variables[ + f"R-averaged {domain} {phase_name}particle concentration [mol.m-3]" + ] + sto_rav = variables[f"R-averaged {domain} {phase_name}particle concentration"] + c_s_surf = variables[ + f"{Domain} {phase_name}particle surface concentration [mol.m-3]" + ] T_xav = variables["X-averaged cell temperature [K]"] - phase_name = self.phase_name T = pybamm.PrimaryBroadcast( variables[f"{Domain} electrode temperature [K]"], [f"{domain} {phase_name}particle"], ) - eps_s = variables[f"{Domain} electrode active material volume fraction"] + eps_s = variables[ + f"{Domain} electrode {phase_name}active material volume fraction" + ] # use a tangential approximation for omega - sto = variables[f"{Domain} particle concentration"] - Omega = pybamm.r_average(domain_param.Omega(sto, T)) - R0 = domain_param.prim.R + sto = variables[f"{Domain} {phase_name}particle concentration"] + Omega = pybamm.r_average(phase_param.Omega(sto, T)) + R0 = phase_param.R c_0 = domain_param.c_0 - E0 = pybamm.r_average(domain_param.E(sto, T)) - nu = domain_param.nu + E0 = pybamm.r_average(phase_param.E(sto, T)) + nu = phase_param.nu L0 = domain_param.L - sto_init = pybamm.r_average(domain_param.prim.c_init / domain_param.prim.c_max) + sto_init = pybamm.r_average(phase_param.c_init / phase_param.c_max) v_change = pybamm.x_average( - eps_s * domain_param.prim.t_change(sto_rav) - ) - pybamm.x_average(eps_s * domain_param.prim.t_change(sto_init)) + eps_s * phase_param.t_change(sto_rav) + ) - pybamm.x_average(eps_s * phase_param.t_change(sto_init)) electrode_thickness_change = self.param.n_electrodes_parallel * v_change * L0 # Ai2019 eq [10] @@ -81,18 +88,27 @@ def _get_mechanical_results(self, variables): variables.update( { - f"{Domain} particle surface radial stress [Pa]": stress_r_surf, - f"{Domain} particle surface tangential stress [Pa]": stress_t_surf, - f"{Domain} particle surface displacement [m]": disp_surf, - f"X-averaged {domain} particle surface " + f"{Domain} {phase_name}particle surface radial stress [Pa]": stress_r_surf, + f"{Domain} {phase_name}particle surface tangential stress [Pa]": stress_t_surf, + f"{Domain} {phase_name}particle surface displacement [m]": disp_surf, + f"X-averaged {domain} {phase_name}particle surface " "radial stress [Pa]": stress_r_surf_av, - f"X-averaged {domain} particle surface " + f"X-averaged {domain} {phase_name}particle surface " "tangential stress [Pa]": stress_t_surf_av, - f"X-averaged {domain} particle surface displacement [m]": disp_surf_av, - f"{Domain} electrode thickness change [m]": electrode_thickness_change, + f"X-averaged {domain} {phase_name}particle surface displacement [m]": disp_surf_av, + f"{Domain} electrode {phase_name}thickness change [m]": electrode_thickness_change, } ) + if ( + f"{Domain} primary thickness change [m]" in variables + and f"{Domain} secondary thickness change [m]" in variables + ): + variables[f"{Domain} thickness change [m]"] = ( + variables[f"{Domain} primary thickness change [m]"] + + variables[f"{Domain} secondary thickness change [m]"] + ) + if ( "Negative electrode thickness change [m]" in variables and "Positive electrode thickness change [m]" in variables diff --git a/src/pybamm/parameters/lithium_ion_parameters.py b/src/pybamm/parameters/lithium_ion_parameters.py index e372cab4a4..3902242d78 100644 --- a/src/pybamm/parameters/lithium_ion_parameters.py +++ b/src/pybamm/parameters/lithium_ion_parameters.py @@ -269,7 +269,6 @@ def _set_parameters(self): self.tau_s = self.geo.tau_s # Mechanical parameters - self.nu = pybamm.Parameter(f"{Domain} electrode Poisson's ratio") self.c_0 = pybamm.Parameter( f"{Domain} electrode reference concentration for free of deformation " "[mol.m-3]" @@ -283,20 +282,6 @@ def _set_parameters(self): self.b_cr = pybamm.Parameter(f"{Domain} electrode Paris' law constant b") self.m_cr = pybamm.Parameter(f"{Domain} electrode Paris' law constant m") - # Loss of active material parameters - self.m_LAM = pybamm.Parameter( - f"{Domain} electrode LAM constant exponential term" - ) - self.beta_LAM = pybamm.Parameter( - f"{Domain} electrode LAM constant proportional term [s-1]" - ) - self.stress_critical = pybamm.Parameter( - f"{Domain} electrode critical stress [Pa]" - ) - self.beta_LAM_sei = pybamm.Parameter( - f"{Domain} electrode reaction-driven LAM factor [m3.mol-1]" - ) - # Utilisation parameters self.u_init = pybamm.Parameter( f"Initial {domain} electrode interface utilisation" @@ -313,22 +298,6 @@ def C_dl(self, T): f"{Domain} electrode double-layer capacity [F.m-2]", inputs ) - def Omega(self, sto, T): - """Dimensional partial molar volume of Li in solid solution [m3.mol-1]""" - Domain = self.domain.capitalize() - inputs = {f"{Domain} particle stoichiometry": sto, "Temperature [K]": T} - return pybamm.FunctionParameter( - f"{Domain} electrode partial molar volume [m3.mol-1]", inputs - ) - - def E(self, sto, T): - """Dimensional Young's modulus""" - Domain = self.domain.capitalize() - inputs = {f"{Domain} particle stoichiometry": sto, "Temperature [K]": T} - return pybamm.FunctionParameter( - f"{Domain} electrode Young's modulus [Pa]", inputs - ) - def sigma(self, T): """Dimensional electrical conductivity in electrode""" inputs = {"Temperature [K]": T} @@ -538,6 +507,23 @@ def _set_parameters(self): if self.options["particle shape"] == "spherical": self.a_typ = 3 * pybamm.xyz_average(self.epsilon_s) / self.R_typ + # Mechanical property + self.nu = pybamm.Parameter(f"{pref}{Domain} electrode Poisson's ratio") + + # Loss of active material parameters + self.m_LAM = pybamm.Parameter( + f"{pref}{Domain} electrode LAM constant exponential term" + ) + self.beta_LAM = pybamm.Parameter( + f"{pref}{Domain} electrode LAM constant proportional term [s-1]" + ) + self.stress_critical = pybamm.Parameter( + f"{pref}{Domain} electrode critical stress [Pa]" + ) + self.beta_LAM_sei = pybamm.Parameter( + f"{pref}{Domain} electrode reaction-driven LAM factor [m3.mol-1]" + ) + def D(self, c_s, T, lithiation=None): """ Dimensional diffusivity in particle. In the parameter sets this is defined as @@ -794,8 +780,31 @@ def t_change(self, sto): """ Domain = self.domain.capitalize() return pybamm.FunctionParameter( - f"{Domain} electrode volume change", + f"{self.phase_prefactor}{Domain} electrode volume change", { f"{Domain} particle stoichiometry": sto, }, ) + + def Omega(self, sto, T): + """Dimensional partial molar volume of Li in solid solution [m3.mol-1]""" + domain, Domain = self.domain_Domain + inputs = { + f"{self.phase_prefactor} particle stoichiometry": sto, + "Temperature [K]": T, + } + return pybamm.FunctionParameter( + f"{self.phase_prefactor}{Domain} electrode partial molar volume [m3.mol-1]", + inputs, + ) + + def E(self, sto, T): + """Dimensional Young's modulus""" + domain, Domain = self.domain_Domain + inputs = { + f"{self.phase_prefactor} particle stoichiometry": sto, + "Temperature [K]": T, + } + return pybamm.FunctionParameter( + f"{self.phase_prefactor}{Domain} electrode Young's modulus [Pa]", inputs + ) diff --git a/tests/integration/test_models/standard_output_tests.py b/tests/integration/test_models/standard_output_tests.py index 83b88c0ff0..ca5e27607d 100644 --- a/tests/integration/test_models/standard_output_tests.py +++ b/tests/integration/test_models/standard_output_tests.py @@ -449,7 +449,7 @@ def test_conservation(self): # this seems to be linked to using constant concentration but not sure why decimal = 12 elif self.model.options["particle phases"] != "1": - decimal = 13 + decimal = 9 elif "current-driven" in self.model.options["loss of active material"]: # current driven LAM model doesn't perfectly conserve lithium, not sure why decimal = 9 diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py index 60e8dfb819..7c176249fd 100644 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py @@ -375,3 +375,161 @@ def temp_drive_cycle(y, z, t): model = self.model() modeltest = tests.StandardModelTest(model, parameter_values=parameter_values) modeltest.test_all(skip_output_tests=True) + + def test_composite_stress_driven_LAM(self): + options = { + "particle phases": ("2", "1"), + "open-circuit potential": (("single", "current sigmoid"), "single"), + "loss of active material": "stress-driven", + } + + # taken from Ai2020 + def graphite_volume_change_Ai2020(sto): + p1 = 145.907 + p2 = -681.229 + p3 = 1334.442 + p4 = -1415.710 + p5 = 873.906 + p6 = -312.528 + p7 = 60.641 + p8 = -5.706 + p9 = 0.386 + p10 = -4.966e-05 + t_change = ( + p1 * sto**9 + + p2 * sto**8 + + p3 * sto**7 + + p4 * sto**6 + + p5 * sto**5 + + p6 * sto**4 + + p7 * sto**3 + + p8 * sto**2 + + p9 * sto + + p10 + ) + return t_change + + # taken from Ai2020 + def lico2_volume_change_Ai2020(sto): + omega = pybamm.Parameter( + "Positive electrode partial molar volume [m3.mol-1]" + ) + c_s_max = pybamm.Parameter( + "Maximum concentration in positive electrode [mol.m-3]" + ) + t_change = omega * c_s_max * sto + return t_change + + # use Chen2020 composite and add Ai2020 stress-driven parameters + parameter_values = pybamm.ParameterValues("Chen2020_composite") + parameter_values.update( + { + "Primary: Negative electrode LAM constant proportional term [s-1]": 1e-4 + / 3600, + "Secondary: Negative electrode LAM constant proportional term [s-1]": 1e-4 + / 3600, + "Positive electrode LAM constant proportional term [s-1]": 1e-4 / 3600, + "Primary: Negative electrode partial molar volume [m3.mol-1]": 3.1e-06, + "Primary: Negative electrode Young's modulus [Pa]": 15000000000.0, + "Primary: Negative electrode Poisson's ratio": 0.3, + "Primary: Negative electrode critical stress [Pa]": 60000000.0, + "Secondary: Negative electrode critical stress [Pa]": 60000000.0, + "Primary: Negative electrode LAM constant exponential term": 2.0, + "Secondary: Negative electrode LAM constant exponential term": 2.0, + "Secondary: Negative electrode partial molar volume [m3.mol-1]": 3.1e-06, + "Secondary: Negative electrode Young's modulus [Pa]": 15000000000.0, + "Secondary: Negative electrode Poisson's ratio": 0.3, + "Negative electrode reference concentration for free of deformation [mol.m-3]": 0.0, + "Primary: Negative electrode volume change": graphite_volume_change_Ai2020, + "Secondary: Negative electrode volume change": graphite_volume_change_Ai2020, + "Positive electrode partial molar volume [m3.mol-1]": -7.28e-07, + "Positive electrode Young's modulus [Pa]": 375000000000.0, + "Positive electrode Poisson's ratio": 0.2, + "Positive electrode critical stress [Pa]": 375000000.0, + "Positive electrode LAM constant exponential term": 2.0, + "Positive electrode reference concentration for free of deformation [mol.m-3]": 0.0, + "Positive electrode volume change": lico2_volume_change_Ai2020, + }, + check_already_exists=False, + ) + + self.run_basic_processing_test(options, parameter_values=parameter_values) + + def test_composite_reaction_driven_LAM(self): + options = { + "particle phases": ("2", "1"), + "open-circuit potential": (("single", "current sigmoid"), "single"), + "loss of active material": "reaction-driven", + } + + # taken from Ai2020 + def graphite_volume_change_Ai2020(sto): + p1 = 145.907 + p2 = -681.229 + p3 = 1334.442 + p4 = -1415.710 + p5 = 873.906 + p6 = -312.528 + p7 = 60.641 + p8 = -5.706 + p9 = 0.386 + p10 = -4.966e-05 + t_change = ( + p1 * sto**9 + + p2 * sto**8 + + p3 * sto**7 + + p4 * sto**6 + + p5 * sto**5 + + p6 * sto**4 + + p7 * sto**3 + + p8 * sto**2 + + p9 * sto + + p10 + ) + return t_change + + # taken from Ai2020 + def lico2_volume_change_Ai2020(sto): + omega = pybamm.Parameter( + "Positive electrode partial molar volume [m3.mol-1]" + ) + c_s_max = pybamm.Parameter( + "Maximum concentration in positive electrode [mol.m-3]" + ) + t_change = omega * c_s_max * sto + return t_change + + # use Chen2020 composite and add Ai2020 stress-driven parameters + parameter_values = pybamm.ParameterValues("Chen2020_composite") + parameter_values.update( + { + "Primary: Negative electrode LAM constant proportional term [s-1]": 1e-4 + / 3600, + "Secondary: Negative electrode LAM constant proportional term [s-1]": 1e-4 + / 3600, + "Positive electrode LAM constant proportional term [s-1]": 1e-4 / 3600, + "Primary: Negative electrode partial molar volume [m3.mol-1]": 3.1e-06, + "Primary: Negative electrode Young's modulus [Pa]": 15000000000.0, + "Primary: Negative electrode Poisson's ratio": 0.3, + "Primary: Negative electrode critical stress [Pa]": 60000000.0, + "Secondary: Negative electrode critical stress [Pa]": 60000000.0, + "Primary: Negative electrode LAM constant exponential term": 2.0, + "Secondary: Negative electrode LAM constant exponential term": 2.0, + "Secondary: Negative electrode partial molar volume [m3.mol-1]": 3.1e-06, + "Secondary: Negative electrode Young's modulus [Pa]": 15000000000.0, + "Secondary: Negative electrode Poisson's ratio": 0.3, + "Negative electrode reference concentration for free of deformation [mol.m-3]": 0.0, + "Primary: Negative electrode volume change": graphite_volume_change_Ai2020, + "Secondary: Negative electrode volume change": graphite_volume_change_Ai2020, + "Positive electrode partial molar volume [m3.mol-1]": -7.28e-07, + "Positive electrode Young's modulus [Pa]": 375000000000.0, + "Positive electrode Poisson's ratio": 0.2, + "Positive electrode critical stress [Pa]": 375000000.0, + "Positive electrode LAM constant exponential term": 2.0, + "Positive electrode reference concentration for free of deformation [mol.m-3]": 0.0, + "Positive electrode volume change": lico2_volume_change_Ai2020, + }, + check_already_exists=False, + ) + + self.run_basic_processing_test(options, parameter_values=parameter_values) 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 index 7d457fd7dc..121bc3a018 100644 --- 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 @@ -26,3 +26,9 @@ def test_composite_graphite_silicon(self): def test_composite_graphite_silicon_sei(self): pass # skip this test + + def test_composite_reaction_driven_LAM(self): + pass # skip this test + + def test_composite_stress_driven_LAM(self): + pass # skip this test diff --git a/tests/unit/test_citations.py b/tests/unit/test_citations.py index 7133cf234a..c87912490f 100644 --- a/tests/unit/test_citations.py +++ b/tests/unit/test_citations.py @@ -296,7 +296,7 @@ def test_reniers_2019(self): citations._reset() assert "Reniers2019" not in citations._papers_to_cite - pybamm.active_material.LossActiveMaterial(None, "negative", None, True) + pybamm.active_material.LossActiveMaterial(None, "negative", None, True, None) assert "Reniers2019" in citations._papers_to_cite assert "Reniers2019" in citations._citation_tags.keys() diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py index 9c093c0c65..7b690257dc 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/base_lithium_ion_tests.py @@ -588,3 +588,20 @@ def test_well_posed_composite_different_degradation(self): "lithium plating": (("none", "irreversible"), "none"), } self.check_well_posedness(options) + + def test_well_posed_composite_LAM(self): + # phases with LAM degradation + options = { + "particle phases": ("2", "1"), + "open-circuit potential": (("single", "current sigmoid"), "single"), + "SEI": "solvent-diffusion limited", + "loss of active material": "reaction-driven", + } + self.check_well_posedness(options) + + options = { + "particle phases": ("2", "1"), + "open-circuit potential": (("single", "current sigmoid"), "single"), + "loss of active material": "stress-driven", + } + self.check_well_posedness(options) 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 index c979474e13..ea641ee7cc 100644 --- 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 @@ -39,3 +39,7 @@ def test_well_posed_composite_diffusion_hysteresis(self): @pytest.mark.skip(reason="Test currently not implemented") def test_well_posed_composite_different_degradation(self): pass # skip this test + + @pytest.mark.skip(reason="Test currently not implemented") + def test_well_posed_composite_LAM(self): + pass # skip this test