diff --git a/CHANGELOG.md b/CHANGELOG.md index 443c50ee3f..43f3bc243b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- Added variables which track the total amount of lithium in the system ([#1136](https://github.com/pybamm-team/PyBaMM/pull/1136)) - Added `Upwind` and `Downwind` operators for convection ([#1134](https://github.com/pybamm-team/PyBaMM/pull/1134)) - Added Getting Started notebook on solver options and changing the mesh. Also added a notebook detailing the different thermal options, and a notebook explaining the steps that occur behind the scenes in the `Simulation` class ([#1131](https://github.com/pybamm-team/PyBaMM/pull/1131)) - Added particle submodel that use a polynomial approximation to the concentration within the electrode particles ([#1130](https://github.com/pybamm-team/PyBaMM/pull/1130)) @@ -18,6 +19,7 @@ ## Bug fixes +- Fixed bug where some parameters were not being set by the `EcRectionLimited` SEI model ([#1136](https://github.com/pybamm-team/PyBaMM/pull/1136)) - Fixed bug on electrolyte potential for `BasicDFNHalfCell` ([#1133](https://github.com/pybamm-team/PyBaMM/pull/1133)) - Fixed `r_average` to work with `SecondaryBroadcast` ([#1118](https://github.com/pybamm-team/PyBaMM/pull/1118)) - Fixed finite volume discretisation of spherical integrals ([#1118](https://github.com/pybamm-team/PyBaMM/pull/1118)) diff --git a/examples/notebooks/Getting Started/SPMe.pkl b/examples/notebooks/Getting Started/SPMe.pkl deleted file mode 100644 index 0eedbf32cc..0000000000 Binary files a/examples/notebooks/Getting Started/SPMe.pkl and /dev/null differ diff --git a/examples/notebooks/Getting Started/SPMe_sol.pkl b/examples/notebooks/Getting Started/SPMe_sol.pkl deleted file mode 100644 index 400516a563..0000000000 Binary files a/examples/notebooks/Getting Started/SPMe_sol.pkl and /dev/null differ diff --git a/examples/notebooks/Getting Started/Tutorial 6 - Managing simulation outputs.ipynb b/examples/notebooks/Getting Started/Tutorial 6 - Managing simulation outputs.ipynb index 1fa8465c3e..565726a863 100644 --- a/examples/notebooks/Getting Started/Tutorial 6 - Managing simulation outputs.ipynb +++ b/examples/notebooks/Getting Started/Tutorial 6 - Managing simulation outputs.ipynb @@ -31,7 +31,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 1, @@ -265,7 +265,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "dc5c33b364df4561acc98455a6ec6d9c", + "model_id": "7e116fdff90e40b28b3f13b79f3e7347", "version_major": 2, "version_minor": 0 }, @@ -313,7 +313,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "c3c3296fe37e4bffba0dada58dc06afa", + "model_id": "cb9640519af3475b9b921b8523c01020", "version_major": 2, "version_minor": 0 }, @@ -327,7 +327,7 @@ { "data": { "text/plain": [ - "" + "" ] }, "execution_count": 11, @@ -380,6 +380,27 @@ "In this notebook we have shown how to extract and store the outputs of PyBaMM's simulations. Next, in [Tutorial 7](./Tutorial%207%20-%20Model%20options.ipynb) we will show how to change the model options." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Before finishing we will remove the data files we saved so that we leave the directory as we found it" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "os.remove(\"SPMe.pkl\")\n", + "os.remove(\"SPMe_sol.pkl\")\n", + "os.remove(\"sol_data.pkl\")\n", + "os.remove(\"sol_data.csv\")\n", + "os.remove(\"sol_data.mat\")" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/examples/notebooks/Getting Started/sol_data.pkl b/examples/notebooks/Getting Started/sol_data.pkl deleted file mode 100644 index d99d51ee52..0000000000 Binary files a/examples/notebooks/Getting Started/sol_data.pkl and /dev/null differ diff --git a/examples/notebooks/using-submodels.ipynb b/examples/notebooks/using-submodels.ipynb index 0f8c6aa97f..c63e32f87e 100644 --- a/examples/notebooks/using-submodels.ipynb +++ b/examples/notebooks/using-submodels.ipynb @@ -68,28 +68,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "external circuit \n", - "porosity \n", - "electrolyte tortuosity \n", - "electrode tortuosity \n", - "through-cell convection \n", - "transverse convection \n", - "negative interface \n", - "positive interface \n", - "negative interface current \n", - "positive interface current \n", - "negative oxygen interface \n", - "positive oxygen interface \n", - "negative particle \n", - "positive particle \n", - "negative electrode \n", - "leading-order electrolyte conductivity \n", - "electrolyte diffusion \n", - "positive electrode \n", - "thermal \n", - "current collector \n", - "negative sei \n", - "positive sei \n" + "external circuit \n", + "porosity \n", + "electrolyte tortuosity \n", + "electrode tortuosity \n", + "through-cell convection \n", + "transverse convection \n", + "negative interface \n", + "positive interface \n", + "negative interface current \n", + "positive interface current \n", + "negative oxygen interface \n", + "positive oxygen interface \n", + "negative particle \n", + "positive particle \n", + "negative electrode \n", + "leading-order electrolyte conductivity \n", + "electrolyte diffusion \n", + "positive electrode \n", + "thermal \n", + "current collector \n", + "negative sei \n", + "positive sei \n" ] } ], @@ -153,28 +153,28 @@ "name": "stdout", "output_type": "stream", "text": [ - "external circuit \n", - "porosity \n", - "electrolyte tortuosity \n", - "electrode tortuosity \n", - "through-cell convection \n", - "transverse convection \n", - "negative interface \n", - "positive interface \n", - "negative interface current \n", - "positive interface current \n", - "negative oxygen interface \n", - "positive oxygen interface \n", - "negative particle \n", - "positive particle \n", - "negative electrode \n", - "leading-order electrolyte conductivity \n", - "electrolyte diffusion \n", - "positive electrode \n", - "thermal \n", - "current collector \n", - "negative sei \n", - "positive sei \n" + "external circuit \n", + "porosity \n", + "electrolyte tortuosity \n", + "electrode tortuosity \n", + "through-cell convection \n", + "transverse convection \n", + "negative interface \n", + "positive interface \n", + "negative interface current \n", + "positive interface current \n", + "negative oxygen interface \n", + "positive oxygen interface \n", + "negative particle \n", + "positive particle \n", + "negative electrode \n", + "leading-order electrolyte conductivity \n", + "electrolyte diffusion \n", + "positive electrode \n", + "thermal \n", + "current collector \n", + "negative sei \n", + "positive sei \n" ] } ], @@ -241,9 +241,9 @@ { "data": { "text/plain": [ - "{Variable(0x3a309675652f70ba, Discharge capacity [A.h], children=[], domain=[], auxiliary_domains={}): Division(-0x48e9be91028d92ad, /, children=['Current function [A] * 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m] / function (absolute)', '3600.0'], domain=[], auxiliary_domains={}),\n", - " Variable(0x3374064f53cb2a3e, R-X-averaged negative particle concentration, children=[], domain=['current collector'], auxiliary_domains={}): Division(0x51b35ab4d15c2595, /, children=[\"-3.0 * integral dx_n ['negative electrode'](broadcast(broadcast(Current function [A] / Typical current [A] * function (sign)) / Negative electrode thickness [m] / Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) - broadcast(0.0) + broadcast(0.0)) / Negative electrode thickness [m] / Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]\", '3.0 * Negative electrode active material volume fraction / Negative particle radius [m] * Negative particle radius [m]'], domain=['current collector'], auxiliary_domains={}),\n", - " Variable(0x3960e4961b0efac3, X-averaged positive particle concentration, children=[], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"}): Multiplication(-0x33a2cbb3618019b9, *, children=['-1.0 / Positive particle radius [m] ** 2.0 / Positive electrode diffusivity [m2.s-1] / 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m] / function (absolute)', 'div(-Positive electrode diffusivity [m2.s-1] / Positive electrode diffusivity [m2.s-1] * grad(X-averaged positive particle concentration))'], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"})}" + "{Variable(-0x3a598270a8eb6e0d, Discharge capacity [A.h], children=[], domain=[], auxiliary_domains={}): Division(-0x67bf165961891da0, /, children=['Current function [A] * 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m] / function (absolute)', '3600.0'], domain=[], auxiliary_domains={}),\n", + " Variable(-0x26eb1beb51f287ce, R-X-averaged negative particle concentration, children=[], domain=['current collector'], auxiliary_domains={}): Division(-0x13cd5de5ba47373f, /, children=[\"-3.0 * integral dx_n ['negative electrode'](broadcast(broadcast(Current function [A] / Typical current [A] * function (sign)) / Negative electrode thickness [m] / Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]) - broadcast(0.0) + broadcast(0.0)) / Negative electrode thickness [m] / Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m]\", '3.0 * Negative electrode active material volume fraction / Negative particle radius [m] * Negative particle radius [m]'], domain=['current collector'], auxiliary_domains={}),\n", + " Variable(0x174cc116f4516de8, X-averaged positive particle concentration, children=[], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"}): Multiplication(-0x617f4041866fdbc6, *, children=['-1.0 / Positive particle radius [m] ** 2.0 / Positive electrode diffusivity [m2.s-1] / 96485.33212 * Maximum concentration in negative electrode [mol.m-3] * Negative electrode thickness [m] + Separator thickness [m] + Positive electrode thickness [m] / function (absolute)', 'div(-Positive electrode diffusivity [m2.s-1] / Positive electrode diffusivity [m2.s-1] * grad(X-averaged positive particle concentration))'], domain=['positive particle'], auxiliary_domains={'secondary': \"['current collector']\"})}" ] }, "execution_count": 9, @@ -270,7 +270,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "f4ddc46fcbac454499728be7b33bc4f3", + "model_id": "df45b8c8b48b415d83703b37df5d1099", "version_major": 2, "version_minor": 0 }, @@ -329,7 +329,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We want to build a 1D model, so select the `Uniform` current collector model (if the current collectors are behaving uniformly, then a 1D model is appropriate). We also want the model to be isothermal, so select the thermal model accordingly. " + "We want to build a 1D model, so select the `Uniform` current collector model (if the current collectors are behaving uniformly, then a 1D model is appropriate). We also want the model to be isothermal, so select the thermal model accordingly. Further, we assume that the porosity is constant in time." ] }, { @@ -339,7 +339,8 @@ "outputs": [], "source": [ "model.submodels[\"current collector\"] = pybamm.current_collector.Uniform(model.param)\n", - "model.submodels[\"thermal\"] = pybamm.thermal.isothermal.Isothermal(model.param)" + "model.submodels[\"thermal\"] = pybamm.thermal.isothermal.Isothermal(model.param)\n", + "model.submodels[\"porosity\"] = pybamm.porosity.Constant(model.param)" ] }, { @@ -484,7 +485,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d92d5bc712d24b239326254b78d22bd4", + "model_id": "b72a4d3d547e416cac2871daf105072a", "version_major": 2, "version_minor": 0 }, diff --git a/examples/scripts/SPMe_SOC.py b/examples/scripts/SPMe_SOC.py index f0f971a5b8..d58d782550 100644 --- a/examples/scripts/SPMe_SOC.py +++ b/examples/scripts/SPMe_SOC.py @@ -74,8 +74,8 @@ # solve model t_eval = np.linspace(0, 3600, 100) sol = model.default_solver.solve(model, t_eval) - xpext = sol["Positive electrode average extent of lithiation"] - xnext = sol["Negative electrode average extent of lithiation"] + xpext = sol["X-averaged positive electrode extent of lithiation"] + xnext = sol["X-averaged negative electrode extent of lithiation"] xpsurf = sol["X-averaged positive particle surface concentration"] xnsurf = sol["X-averaged negative particle surface concentration"] time = sol["Time [h]"] diff --git a/examples/scripts/custom_model.py b/examples/scripts/custom_model.py index 20eabc0166..70348ffb70 100644 --- a/examples/scripts/custom_model.py +++ b/examples/scripts/custom_model.py @@ -16,6 +16,7 @@ ) model.submodels["current collector"] = pybamm.current_collector.Uniform(model.param) model.submodels["thermal"] = pybamm.thermal.isothermal.Isothermal(model.param) +model.submodels["porosity"] = pybamm.porosity.Constant(model.param) model.submodels["negative electrode"] = pybamm.electrode.ohm.LeadingOrder( model.param, "Negative" ) diff --git a/pybamm/models/submodels/electrolyte_diffusion/base_electrolyte_diffusion.py b/pybamm/models/submodels/electrolyte_diffusion/base_electrolyte_diffusion.py index 7748cde679..a2c9ce99d4 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/base_electrolyte_diffusion.py +++ b/pybamm/models/submodels/electrolyte_diffusion/base_electrolyte_diffusion.py @@ -105,6 +105,35 @@ def _get_standard_flux_variables(self, N_e): return variables + def _get_total_concentration_electrolyte(self, c_e, epsilon): + """ + A private function to obtain the total ion concentration in the electrolyte. + + Parameters + ---------- + c_e : :class:`pybamm.Symbol` + The electrolyte concentration + epsilon : :class:`pybamm.Symbol` + The porosity + + Returns + ------- + variables : dict + The "Total concentration in electrolyte [mol]" variable. + """ + + c_e_typ = self.param.c_e_typ + L_x = self.param.L_x + A = self.param.A_cc + + c_e_total = pybamm.x_average(epsilon * c_e) + + variables = { + "Total concentration in electrolyte [mol]": c_e_typ * L_x * A * c_e_total + } + + return variables + def set_events(self, variables): c_e = variables["Electrolyte concentration"] self.events.append( diff --git a/pybamm/models/submodels/electrolyte_diffusion/composite_diffusion.py b/pybamm/models/submodels/electrolyte_diffusion/composite_diffusion.py index eac8e91f27..1a8050d83e 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/composite_diffusion.py +++ b/pybamm/models/submodels/electrolyte_diffusion/composite_diffusion.py @@ -36,6 +36,7 @@ def get_fundamental_variables(self): def get_coupled_variables(self, variables): tor_0 = variables["Leading-order electrolyte tortuosity"] + eps = variables["Leading-order porosity"] c_e_0_av = variables["Leading-order x-averaged electrolyte concentration"] c_e = variables["Electrolyte concentration"] i_e = variables["Electrolyte current density"] @@ -51,6 +52,7 @@ def get_coupled_variables(self, variables): N_e = N_e_diffusion + N_e_migration + N_e_convection variables.update(self._get_standard_flux_variables(N_e)) + variables.update(self._get_total_concentration_electrolyte(c_e, eps)) return variables diff --git a/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py b/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py index 8b90396f7e..570bce61b3 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py +++ b/pybamm/models/submodels/electrolyte_diffusion/constant_concentration.py @@ -38,3 +38,10 @@ def get_fundamental_variables(self): return variables + def get_coupled_variables(self, variables): + c_e = variables["Electrolyte concentration"] + eps = variables["Porosity"] + + variables.update(self._get_total_concentration_electrolyte(c_e, eps)) + + return variables diff --git a/pybamm/models/submodels/electrolyte_diffusion/first_order_diffusion.py b/pybamm/models/submodels/electrolyte_diffusion/first_order_diffusion.py index 06d5ec45af..c2501a16e3 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/first_order_diffusion.py +++ b/pybamm/models/submodels/electrolyte_diffusion/first_order_diffusion.py @@ -138,4 +138,13 @@ def get_coupled_variables(self, variables): ) variables.update(self._get_standard_flux_variables(N_e)) + c_e = pybamm.Concatenation(c_e_n, c_e_s, c_e_p) + eps = pybamm.Concatenation( + pybamm.PrimaryBroadcast(eps_n_0, "negative electrode"), + pybamm.PrimaryBroadcast(eps_s_0, "separator"), + pybamm.PrimaryBroadcast(eps_p_0, "positive electrode"), + ) + + variables.update(self._get_total_concentration_electrolyte(c_e, eps)) + return variables diff --git a/pybamm/models/submodels/electrolyte_diffusion/full_diffusion.py b/pybamm/models/submodels/electrolyte_diffusion/full_diffusion.py index 4bec2350f7..8b61806e02 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/full_diffusion.py +++ b/pybamm/models/submodels/electrolyte_diffusion/full_diffusion.py @@ -34,6 +34,7 @@ def get_fundamental_variables(self): def get_coupled_variables(self, variables): tor = variables["Electrolyte tortuosity"] + eps = variables["Porosity"] c_e = variables["Electrolyte concentration"] i_e = variables["Electrolyte current density"] v_box = variables["Volume-averaged velocity"] @@ -48,6 +49,7 @@ def get_coupled_variables(self, variables): N_e = N_e_diffusion + N_e_migration + N_e_convection variables.update(self._get_standard_flux_variables(N_e)) + variables.update(self._get_total_concentration_electrolyte(c_e, eps)) return variables diff --git a/pybamm/models/submodels/electrolyte_diffusion/leading_order_diffusion.py b/pybamm/models/submodels/electrolyte_diffusion/leading_order_diffusion.py index 7e96682e0e..b14a19eda9 100644 --- a/pybamm/models/submodels/electrolyte_diffusion/leading_order_diffusion.py +++ b/pybamm/models/submodels/electrolyte_diffusion/leading_order_diffusion.py @@ -42,6 +42,16 @@ def get_coupled_variables(self, variables): variables.update(self._get_standard_flux_variables(N_e)) + c_e_av = pybamm.standard_variables.c_e_av + c_e = pybamm.Concatenation( + pybamm.PrimaryBroadcast(c_e_av, ["negative electrode"]), + pybamm.PrimaryBroadcast(c_e_av, ["separator"]), + pybamm.PrimaryBroadcast(c_e_av, ["positive electrode"]), + ) + eps = variables["Porosity"] + + variables.update(self._get_total_concentration_electrolyte(c_e, eps)) + return variables def set_rhs(self, variables): diff --git a/pybamm/models/submodels/interface/sei/base_sei.py b/pybamm/models/submodels/interface/sei/base_sei.py index 68bbddbcd5..c82cf537d3 100644 --- a/pybamm/models/submodels/interface/sei/base_sei.py +++ b/pybamm/models/submodels/interface/sei/base_sei.py @@ -44,37 +44,18 @@ def _get_standard_thickness_variables(self, L_inner, L_outer): The variables which can be derived from the SEI thicknesses. """ param = self.param + domain = self.domain.lower() + " electrode" - # Set scales to one for the "no SEI" model so that they are not required - # by parameter values in general + # Set length scale to one for the "no SEI" model so that it is not + # required by parameter values in general if isinstance(self, pybamm.sei.NoSEI): L_scale = 1 - n_scale = 1 - n_outer_scale = 1 - v_bar = 1 else: L_scale = param.L_sei_0_dim - n_scale = param.L_sei_0_dim * param.a_n_dim / param.V_bar_inner_dimensional - n_outer_scale = ( - param.L_sei_0_dim * param.a_n_dim / param.V_bar_outer_dimensional - ) - v_bar = param.v_bar L_inner_av = pybamm.x_average(L_inner) L_outer_av = pybamm.x_average(L_outer) - n_inner = L_inner # inner SEI concentration - n_outer = L_outer # outer SEI concentration - n_inner_av = pybamm.x_average(L_inner) - n_outer_av = pybamm.x_average(L_outer) - - n_SEI = n_inner + n_outer / v_bar # SEI concentration - n_SEI_av = pybamm.x_average(n_SEI) - - Q_sei = n_SEI_av * self.param.L_n * self.param.L_y * self.param.L_z - - domain = self.domain.lower() + " electrode" - variables = { "Inner " + domain + " sei thickness": L_inner, "Inner " + domain + " sei thickness [m]": L_inner * L_scale, @@ -84,21 +65,10 @@ def _get_standard_thickness_variables(self, L_inner, L_outer): "Outer " + domain + " sei thickness [m]": L_outer * L_scale, "X-averaged outer " + domain + " sei thickness": L_outer_av, "X-averaged outer " + domain + " sei thickness [m]": L_outer_av * L_scale, - "Inner " + domain + " sei concentration [mol.m-3]": n_inner * n_scale, - "X-averaged inner " - + domain - + " sei concentration [mol.m-3]": n_inner_av * n_scale, - "Outer " + domain + " sei concentration [mol.m-3]": n_outer * n_outer_scale, - "X-averaged outer " - + domain - + " sei concentration [mol.m-3]": n_outer_av * n_outer_scale, - self.domain + " sei concentration [mol.m-3]": n_SEI * n_scale, - "X-averaged " + domain + " sei concentration [mol.m-3]": n_SEI_av * n_scale, - "Loss of lithium to " + domain + " sei [mol]": Q_sei * n_scale, } + # Get variables related to the total thickness L_sei = L_inner + L_outer - variables.update(self._get_standard_total_thickness_variables(L_sei)) return variables @@ -125,6 +95,98 @@ def _get_standard_total_thickness_variables(self, L_sei): } return variables + def _get_standard_concentraion_variables(self, variables): + "Update variables related to the SEI concentration" + param = self.param + domain = self.domain.lower() + " electrode" + + # Set scales to one for the "no SEI" model so that they are not required + # by parameter values in general + if isinstance(self, pybamm.sei.NoSEI): + n_scale = 1 + n_outer_scale = 1 + v_bar = 1 + # Set scales for the "EC Reaction Limited" model + elif isinstance(self, pybamm.sei.EcReactionLimited): + n_scale = 1 + n_outer_scale = self.param.c_ec_0_dim + v_bar = 1 + else: + n_scale = param.L_sei_0_dim * param.a_n_dim / param.V_bar_inner_dimensional + n_outer_scale = ( + param.L_sei_0_dim * param.a_n_dim / param.V_bar_outer_dimensional + ) + v_bar = param.v_bar + + L_inner = variables["Inner " + domain + " sei thickness"] + L_outer = variables["Outer " + domain + " sei thickness"] + + # Set SEI concentration variables. Note these are defined differently for + # the "EC Reaction Limited" model + if isinstance(self, pybamm.sei.EcReactionLimited): + j_outer = variables["Outer " + domain + " sei interfacial current density"] + # concentration of EC on graphite surface, base case = 1 + if self.domain == "Negative": + C_ec = self.param.C_ec_n + + c_ec = pybamm.Scalar(1) + j_outer * L_outer * C_ec + c_ec_av = pybamm.x_average(c_ec) + + n_inner = pybamm.FullBroadcast( + 0, self.domain.lower() + " electrode", "current collector" + ) # inner SEI concentration + n_outer = j_outer * L_outer * C_ec # outer SEI concentration + else: + n_inner = L_inner # inner SEI concentration + n_outer = L_outer # outer SEI concentration + + n_inner_av = pybamm.x_average(L_inner) + n_outer_av = pybamm.x_average(L_outer) + + n_SEI = n_inner + n_outer / v_bar # SEI concentration + n_SEI_av = pybamm.x_average(n_SEI) + + Q_sei = n_SEI_av * self.param.L_n * self.param.L_y * self.param.L_z + + variables.update( + { + "Inner " + domain + " sei concentration [mol.m-3]": n_inner * n_scale, + "X-averaged inner " + + domain + + " sei concentration [mol.m-3]": n_inner_av * n_scale, + "Outer " + + domain + + " sei concentration [mol.m-3]": n_outer * n_outer_scale, + "X-averaged outer " + + domain + + " sei concentration [mol.m-3]": n_outer_av * n_outer_scale, + self.domain + " sei concentration [mol.m-3]": n_SEI * n_scale, + "X-averaged " + + domain + + " sei concentration [mol.m-3]": n_SEI_av * n_scale, + "Loss of lithium to " + domain + " sei [mol]": Q_sei * n_scale, + } + ) + + # Also set variables for EC surface concentration + if isinstance(self, pybamm.sei.EcReactionLimited): + variables.update( + { + self.domain + " electrode EC surface concentration": c_ec, + self.domain + + " electrode EC surface concentration [mol.m-3]": c_ec + * n_outer_scale, + "X-averaged " + + self.domain.lower() + + " electrode EC surface concentration": c_ec_av, + "X-averaged " + + self.domain.lower() + + " electrode EC surface concentration": c_ec_av * n_outer_scale, + } + ) + + return variables + def _get_standard_reaction_variables(self, j_inner, j_outer): """ A private function to obtain the standard variables which diff --git a/pybamm/models/submodels/interface/sei/constant_sei.py b/pybamm/models/submodels/interface/sei/constant_sei.py index d31f9f6dd6..89cb986b2b 100644 --- a/pybamm/models/submodels/interface/sei/constant_sei.py +++ b/pybamm/models/submodels/interface/sei/constant_sei.py @@ -6,7 +6,8 @@ class ConstantSEI(BaseModel): - """Base class for SEI with constant thickness. + """ + Class for SEI with constant thickness. Note that there is no SEI current, so we don't need to update the "sum of interfacial current densities" variables from @@ -31,6 +32,9 @@ def get_fundamental_variables(self): L_outer = self.param.L_outer_0 variables = self._get_standard_thickness_variables(L_inner, L_outer) + # Concentrations (derived from thicknesses) + variables.update(self._get_standard_concentraion_variables(variables)) + # Reactions zero = pybamm.FullBroadcast( pybamm.Scalar(0), self.domain.lower() + " electrode", "current collector" diff --git a/pybamm/models/submodels/interface/sei/ec_reaction_limited.py b/pybamm/models/submodels/interface/sei/ec_reaction_limited.py index a806181bd2..72495bb27b 100644 --- a/pybamm/models/submodels/interface/sei/ec_reaction_limited.py +++ b/pybamm/models/submodels/interface/sei/ec_reaction_limited.py @@ -6,7 +6,9 @@ class EcReactionLimited(BaseModel): - """Base class for reaction limited SEI growth. + """ + Class for reaction limited SEI growth. This model assumes the "inner" + SEI layer is of zero thickness and only models the "outer" SEI layer. Parameters ---------- @@ -23,46 +25,30 @@ def __init__(self, param, domain): def get_fundamental_variables(self): - L_sei = pybamm.Variable( - "Total " + self.domain.lower() + " electrode sei thickness", - domain=self.domain.lower() + " electrode", - auxiliary_domains={"secondary": "current collector"}, + L_inner = pybamm.FullBroadcast( + 0, self.domain.lower() + " electrode", "current collector" ) - j_sei = pybamm.Variable( - self.domain + " electrode sei interfacial current density", + L_outer = pybamm.standard_variables.L_outer + + j_inner = pybamm.FullBroadcast( + 0, self.domain.lower() + " electrode", "current collector" + ) + j_outer = pybamm.Variable( + "Outer " + self.domain + " electrode sei interfacial current density", domain=self.domain.lower() + " electrode", auxiliary_domains={"secondary": "current collector"}, ) - variables = self._get_standard_total_thickness_variables(L_sei) - variables.update(self._get_standard_total_reaction_variables(j_sei)) + variables = self._get_standard_thickness_variables(L_inner, L_outer) + variables.update(self._get_standard_reaction_variables(j_inner, j_outer)) return variables def get_coupled_variables(self, variables): - j_sei = variables[self.domain + " electrode sei interfacial current density"] - L_sei = variables["Total " + self.domain.lower() + " electrode sei thickness"] - c_scale = self.param.c_ec_0_dim - # concentration of EC on graphite surface, base case = 1 - if self.domain == "Negative": - C_ec = self.param.C_ec_n - - c_ec = pybamm.Scalar(1) + j_sei * L_sei * C_ec - c_ec_av = pybamm.x_average(c_ec) - variables.update( - { - self.domain + " electrode EC surface concentration": c_ec, - self.domain - + " electrode EC surface concentration [mol.m-3]": c_ec * c_scale, - "X-averaged " - + self.domain.lower() - + " electrode EC surface concentration": c_ec_av, - "X-averaged " - + self.domain.lower() - + " electrode EC surface concentration": c_ec_av * c_scale, - } - ) + # Get variables related to the concentration + variables.update(self._get_standard_concentraion_variables(variables)) + # Update whole cell variables, which also updates the "sum of" variables if ( "Negative electrode sei interfacial current density" in variables @@ -77,8 +63,8 @@ def get_coupled_variables(self, variables): def set_rhs(self, variables): domain = self.domain.lower() + " electrode" - L_sei = variables["Total " + domain + " sei thickness"] - j_sei = variables[self.domain + " electrode sei interfacial current density"] + L_sei = variables["Outer " + domain + " sei thickness"] + j_sei = variables["Outer " + domain + " sei interfacial current density"] if self.domain == "Negative": Gamma_SEI = self.param.Gamma_SEI_n @@ -88,8 +74,12 @@ def set_rhs(self, variables): def set_algebraic(self, variables): phi_s_n = variables[self.domain + " electrode potential"] phi_e_n = variables[self.domain + " electrolyte potential"] - j_sei = variables[self.domain + " electrode sei interfacial current density"] - L_sei = variables["Total " + self.domain.lower() + " electrode sei thickness"] + j_sei = variables[ + "Outer " + + self.domain.lower() + + " electrode sei interfacial current density" + ] + L_sei = variables["Outer " + self.domain.lower() + " electrode sei thickness"] c_ec = variables[self.domain + " electrode EC surface concentration"] # Look for current that contributes to the -IR drop @@ -115,7 +105,6 @@ def set_algebraic(self, variables): R_sei = self.param.R_sei_n # need to revise for thermal case - self.algebraic = { j_sei: j_sei + C_sei_ec @@ -124,8 +113,12 @@ def set_algebraic(self, variables): } def set_initial_conditions(self, variables): - L_sei = variables["Total " + self.domain.lower() + " electrode sei thickness"] - j_sei = variables[self.domain + " electrode sei interfacial current density"] + L_sei = variables["Outer " + self.domain.lower() + " electrode sei thickness"] + j_sei = variables[ + "Outer " + + self.domain.lower() + + " electrode sei interfacial current density" + ] L_sei_0 = pybamm.Scalar(1) j_sei_0 = pybamm.Scalar(0) diff --git a/pybamm/models/submodels/interface/sei/electron_migration_limited.py b/pybamm/models/submodels/interface/sei/electron_migration_limited.py index c64ef6d389..a4988ccdcd 100644 --- a/pybamm/models/submodels/interface/sei/electron_migration_limited.py +++ b/pybamm/models/submodels/interface/sei/electron_migration_limited.py @@ -6,7 +6,8 @@ class ElectronMigrationLimited(BaseModel): - """Base class for electron-migration limited SEI growth. + """ + Class for electron-migration limited SEI growth. Parameters ---------- @@ -26,6 +27,7 @@ def get_fundamental_variables(self): L_outer = pybamm.standard_variables.L_outer variables = self._get_standard_thickness_variables(L_inner, L_outer) + variables.update(self._get_standard_concentraion_variables(variables)) return variables diff --git a/pybamm/models/submodels/interface/sei/interstitial_diffusion_limited.py b/pybamm/models/submodels/interface/sei/interstitial_diffusion_limited.py index 4f7089b154..3865a7a1ca 100644 --- a/pybamm/models/submodels/interface/sei/interstitial_diffusion_limited.py +++ b/pybamm/models/submodels/interface/sei/interstitial_diffusion_limited.py @@ -6,7 +6,8 @@ class InterstitialDiffusionLimited(BaseModel): - """Base class for interstitial-diffusion limited SEI growth. + """ + Class for interstitial-diffusion limited SEI growth. Parameters ---------- @@ -26,6 +27,7 @@ def get_fundamental_variables(self): L_outer = pybamm.standard_variables.L_outer variables = self._get_standard_thickness_variables(L_inner, L_outer) + variables.update(self._get_standard_concentraion_variables(variables)) return variables diff --git a/pybamm/models/submodels/interface/sei/no_sei.py b/pybamm/models/submodels/interface/sei/no_sei.py index b23b951210..2a477e5ded 100644 --- a/pybamm/models/submodels/interface/sei/no_sei.py +++ b/pybamm/models/submodels/interface/sei/no_sei.py @@ -6,7 +6,8 @@ class NoSEI(BaseModel): - """Base class for no SEI. + """ + Class for no SEI. Parameters ---------- @@ -26,6 +27,7 @@ def get_fundamental_variables(self): pybamm.Scalar(0), self.domain.lower() + " electrode", "current collector" ) variables = self._get_standard_thickness_variables(zero, zero) + variables.update(self._get_standard_concentraion_variables(variables)) variables.update(self._get_standard_reaction_variables(zero, zero)) return variables diff --git a/pybamm/models/submodels/interface/sei/reaction_limited.py b/pybamm/models/submodels/interface/sei/reaction_limited.py index 84102d0b62..0f6fd0ed37 100644 --- a/pybamm/models/submodels/interface/sei/reaction_limited.py +++ b/pybamm/models/submodels/interface/sei/reaction_limited.py @@ -6,7 +6,8 @@ class ReactionLimited(BaseModel): - """Base class for reaction limited SEI growth. + """ + Class for reaction limited SEI growth. Parameters ---------- @@ -26,6 +27,7 @@ def get_fundamental_variables(self): L_outer = pybamm.standard_variables.L_outer variables = self._get_standard_thickness_variables(L_inner, L_outer) + variables.update(self._get_standard_concentraion_variables(variables)) return variables diff --git a/pybamm/models/submodels/interface/sei/solvent_diffusion_limited.py b/pybamm/models/submodels/interface/sei/solvent_diffusion_limited.py index 74bdaff598..521a581af6 100644 --- a/pybamm/models/submodels/interface/sei/solvent_diffusion_limited.py +++ b/pybamm/models/submodels/interface/sei/solvent_diffusion_limited.py @@ -6,7 +6,8 @@ class SolventDiffusionLimited(BaseModel): - """Base class for solvent-diffusion limited SEI growth. + """ + Class for solvent-diffusion limited SEI growth. Parameters ---------- @@ -26,6 +27,7 @@ def get_fundamental_variables(self): L_outer = pybamm.standard_variables.L_outer variables = self._get_standard_thickness_variables(L_inner, L_outer) + variables.update(self._get_standard_concentraion_variables(variables)) return variables diff --git a/pybamm/models/submodels/particle/base_particle.py b/pybamm/models/submodels/particle/base_particle.py index b24485dd6e..0e9b13d25a 100644 --- a/pybamm/models/submodels/particle/base_particle.py +++ b/pybamm/models/submodels/particle/base_particle.py @@ -34,8 +34,6 @@ def _get_standard_concentration_variables( concentration are computed automatically from the particle concentration. """ - param = self.param - # Get surface concentration if not provided as fundamental variable to # solve for c_s_surf = c_s_surf or pybamm.surf(c_s) @@ -43,17 +41,20 @@ def _get_standard_concentration_variables( if self.domain == "Negative": c_scale = self.param.c_n_max - active_volume = param.epsilon_s_n + eps_s = self.param.epsilon_s_n + L = self.param.L_n elif self.domain == "Positive": c_scale = self.param.c_p_max - active_volume = param.epsilon_s_p + eps_s = self.param.epsilon_s_p + L = self.param.L_p + A = self.param.A_cc # Get average concentration(s) if not provided as fundamental variable to # solve for c_s_xav = c_s_xav or pybamm.x_average(c_s) c_s_rav = c_s_rav or pybamm.r_average(c_s) c_s_av = c_s_av or pybamm.r_average(c_s_xav) - c_s_av_vol = active_volume * c_s_av + c_s_vol_av = pybamm.x_average(eps_s * c_s_rav) variables = { self.domain + " particle concentration": c_s, @@ -79,12 +80,18 @@ def _get_standard_concentration_variables( "X-averaged " + self.domain.lower() + " particle surface concentration [mol.m-3]": c_scale * c_s_surf_av, - self.domain + " electrode active volume fraction": active_volume, - self.domain + " electrode volume-averaged concentration": c_s_av_vol, + self.domain + " electrode active volume fraction": eps_s, + self.domain + " electrode volume-averaged concentration": c_s_vol_av, self.domain + " electrode " - + "volume-averaged concentration [mol.m-3]": c_s_av_vol * c_scale, - self.domain + " electrode average extent of lithiation": c_s_av, + + "volume-averaged concentration [mol.m-3]": c_s_vol_av * c_scale, + self.domain + " electrode extent of lithiation": c_s_rav, + "X-averaged " + + self.domain.lower() + + " electrode extent of lithiation": c_s_av, + "Total lithium in " + + self.domain.lower() + + " electrode [mol]": c_s_vol_av * c_scale * L * A, } return variables diff --git a/tests/integration/test_models/standard_output_tests.py b/tests/integration/test_models/standard_output_tests.py index fb0f748017..d186fc4596 100644 --- a/tests/integration/test_models/standard_output_tests.py +++ b/tests/integration/test_models/standard_output_tests.py @@ -259,9 +259,19 @@ def __init__(self, model, param, disc, solution, operating_condition): self.c_s_n_surf = solution["Negative particle surface concentration"] self.c_s_p_surf = solution["Positive particle surface concentration"] + self.c_s_n_tot = solution["Total lithium in negative electrode [mol]"] + self.c_s_p_tot = solution["Total lithium in positive electrode [mol]"] + self.N_s_n = solution["Negative particle flux"] self.N_s_p = solution["Positive particle flux"] + self.n_SEI_n_av = solution[ + "X-averaged negative electrode sei concentration [mol.m-3]" + ] + self.n_SEI_p_av = solution[ + "X-averaged positive electrode sei concentration [mol.m-3]" + ] + def test_concentration_increase_decrease(self): """Test all concentrations in negative particles decrease and all concentrations in positive particles increase over a discharge.""" @@ -303,8 +313,22 @@ def test_concentration_limits(self): np.testing.assert_array_less(self.c_s_p(t, x_p, r_p), 1) def test_conservation(self): - "Test amount of lithium stored across all particles is constant." - # TODO: add an output for total lithium in particles + """Test amount of lithium stored across all particles and in SEI layers is + constant.""" + L_n = self.param["Negative electrode thickness [m]"] + L_p = self.param["Positive electrode thickness [m]"] + L_y = self.param["Electrode width [m]"] + L_z = self.param["Electrode height [m]"] + A = L_y * L_z + + self.c_s_tot = ( + self.c_s_n_tot(self.solution.t) + + self.c_s_p_tot(self.solution.t) + + self.n_SEI_n_av(self.solution.t) * L_n * A + + self.n_SEI_p_av(self.solution.t) * L_p * A + ) + diff = (self.c_s_tot[1:] - self.c_s_tot[:-1]) / self.c_s_tot[:-1] + np.testing.assert_array_almost_equal(diff, 0) def test_concentration_profile(self): """Test that the concentration in the centre of the negative particles is @@ -369,11 +393,11 @@ def __init__(self, model, param, disc, solution, operating_condition): self.c_e_s = solution["Separator electrolyte concentration"] self.c_e_p = solution["Positive electrolyte concentration"] - # TODO: output average electrolyte concentration - # self.c_e_av = solution["X-averaged electrolyte concentration"] - # self.c_e_n_av = solution["X-averaged negative electrolyte concentration"] - # self.c_e_s_av = solution["X-averaged separator electrolyte concentration"] - # self.c_e_p_av = solution["X-averaged positive electrolyte concentration"] + self.c_e_av = solution["X-averaged electrolyte concentration"] + self.c_e_n_av = solution["X-averaged negative electrolyte concentration"] + self.c_e_s_av = solution["X-averaged separator electrolyte concentration"] + self.c_e_p_av = solution["X-averaged positive electrolyte concentration"] + self.c_e_tot = solution["Total concentration in electrolyte [mol]"] self.N_e_hat = solution["Electrolyte flux"] # self.N_e_hat = solution["Reduced cation flux"] @@ -386,8 +410,10 @@ def test_conservation(self): "Test conservation of species in the electrolyte." # sufficient to check average concentration is constant - # diff = self.c_e_av.entries[:, 1:] - self.c_e_av.entries[:, :-1] - # np.testing.assert_array_almost_equal(diff, 0) + diff = ( + self.c_e_tot(self.solution.t[1:]) - self.c_e_tot(self.solution.t[:-1]) + ) / self.c_e_tot(self.solution.t[:-1]) + np.testing.assert_array_almost_equal(diff, 0) def test_concentration_profile(self): """Test continuity of the concentration profile. Test average concentration is diff --git a/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_constant_concentration.py b/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_constant_concentration.py index 56765201c7..1d2c7898e6 100644 --- a/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_constant_concentration.py +++ b/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_constant_concentration.py @@ -10,10 +10,10 @@ class TestConstantConcentration(unittest.TestCase): def test_public_functions(self): param = pybamm.LithiumIonParameters() - submodel = pybamm.electrolyte_diffusion.ConstantConcentration( - param - ) - std_tests = tests.StandardSubModelTests(submodel) + a = pybamm.Scalar(0) + variables = {"Porosity": a, "Electrolyte concentration": a} + submodel = pybamm.electrolyte_diffusion.ConstantConcentration(param) + std_tests = tests.StandardSubModelTests(submodel, variables) std_tests.test_all() diff --git a/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_leading_order_diffusion.py b/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_leading_order_diffusion.py index fc442147c8..69933a1b93 100644 --- a/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_leading_order_diffusion.py +++ b/tests/unit/test_models/test_submodels/test_electrolyte_diffusion/test_leading_order_diffusion.py @@ -12,6 +12,7 @@ def test_public_functions(self): param = pybamm.LeadAcidParameters() a = pybamm.Scalar(0) variables = { + "Porosity": a, "X-averaged negative electrode porosity": a, "X-averaged separator porosity": a, "X-averaged positive electrode porosity": a,