diff --git a/CHANGELOG.md b/CHANGELOG.md index 118a18197a..a125d704f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - Added `plot_thermal_components` to plot the contributions to the total heat generation in a battery ([#4021](https://github.com/pybamm-team/PyBaMM/pull/4021)) - Added functions for normal probability density function (`pybamm.normal_pdf`) and cumulative distribution function (`pybamm.normal_cdf`) ([#3999](https://github.com/pybamm-team/PyBaMM/pull/3999)) +- "Basic" models are now compatible with experiments ([#3995](https://github.com/pybamm-team/PyBaMM/pull/3995)) - Updates multiprocess `Pool` in `BaseSolver.solve()` to be constructed with context `fork`. Adds small example for multiprocess inputs. ([#3974](https://github.com/pybamm-team/PyBaMM/pull/3974)) - Lithium plating now works on composite electrodes ([#3919](https://github.com/pybamm-team/PyBaMM/pull/3919)) - Added lithium plating parameters to `Ecker2015` and `Ecker2015_graphite_halfcell` parameter sets ([#3919](https://github.com/pybamm-team/PyBaMM/pull/3919)) diff --git a/docs/source/api/models/submodels/external_circuit/discharge_throughput.rst b/docs/source/api/models/submodels/external_circuit/discharge_throughput.rst new file mode 100644 index 0000000000..b8c64dad65 --- /dev/null +++ b/docs/source/api/models/submodels/external_circuit/discharge_throughput.rst @@ -0,0 +1,7 @@ +Discharge and throughput variables +================================== + +Calculates the discharge and throughput variables (capacity and power) for the battery. + +.. autoclass:: pybamm.external_circuit.DischargeThroughput + :members: diff --git a/docs/source/api/models/submodels/external_circuit/index.rst b/docs/source/api/models/submodels/external_circuit/index.rst index 21790e4666..b07ae817a6 100644 --- a/docs/source/api/models/submodels/external_circuit/index.rst +++ b/docs/source/api/models/submodels/external_circuit/index.rst @@ -11,5 +11,6 @@ variable to be constant. .. toctree:: :maxdepth: 1 + discharge_throughput explicit_control_external_circuit function_control_external_circuit diff --git a/pybamm/models/base_model.py b/pybamm/models/base_model.py index 5a90381777..85789bab39 100644 --- a/pybamm/models/base_model.py +++ b/pybamm/models/base_model.py @@ -105,6 +105,7 @@ def __init__(self, name="Unnamed model"): self._boundary_conditions = {} self._variables_by_submodel = {} self._variables = pybamm.FuzzyDict({}) + self._summary_variables = [] self._events = [] self._concatenated_rhs = None self._concatenated_algebraic = None diff --git a/pybamm/models/full_battery_models/base_battery_model.py b/pybamm/models/full_battery_models/base_battery_model.py index 335ac26650..e12f211abb 100644 --- a/pybamm/models/full_battery_models/base_battery_model.py +++ b/pybamm/models/full_battery_models/base_battery_model.py @@ -1160,6 +1160,9 @@ def set_external_circuit_submodel(self): self.param, self.options["operating mode"], self.options ) self.submodels["external circuit"] = model + self.submodels["discharge and throughput variables"] = ( + pybamm.external_circuit.DischargeThroughput(self.param, self.options) + ) def set_transport_efficiency_submodels(self): self.submodels["electrolyte transport efficiency"] = ( diff --git a/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py b/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py index b7a8cfec71..879b8effe1 100644 --- a/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py +++ b/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py @@ -181,7 +181,7 @@ def set_degradation_variables(self): } ) - def set_summary_variables(self): + def set_default_summary_variables(self): """ Sets the default summary variables. """ diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py index 5b38926699..08809b645f 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py @@ -34,6 +34,7 @@ def __init__(self, name="Doyle-Fuller-Newman model"): ###################### # Variables that depend on time only are created without a domain Q = pybamm.Variable("Discharge capacity [A.h]") + # Variables that vary spatially are created with a domain c_e_n = pybamm.Variable( "Negative electrolyte concentration [mol.m-3]", @@ -240,18 +241,32 @@ def __init__(self, name="Doyle-Fuller-Newman model"): # (Some) variables ###################### voltage = pybamm.boundary_value(phi_s_p, "right") + num_cells = pybamm.Parameter( + "Number of cells connected in series to make a battery" + ) # The `variables` dictionary contains all variables that might be useful for # visualising the solution of the model self.variables = { + "Negative particle concentration [mol.m-3]": c_s_n, "Negative particle surface concentration [mol.m-3]": c_s_surf_n, "Electrolyte concentration [mol.m-3]": c_e, + "Negative electrolyte concentration [mol.m-3]": c_e_n, + "Separator electrolyte concentration [mol.m-3]": c_e_s, + "Positive electrolyte concentration [mol.m-3]": c_e_p, + "Positive particle concentration [mol.m-3]": c_s_p, "Positive particle surface concentration [mol.m-3]": c_s_surf_p, "Current [A]": I, + "Current variable [A]": I, # for compatibility with pybamm.Experiment "Negative electrode potential [V]": phi_s_n, "Electrolyte potential [V]": phi_e, + "Negative electrolyte potential [V]": phi_e_n, + "Separator electrolyte potential [V]": phi_e_s, + "Positive electrolyte potential [V]": phi_e_p, "Positive electrode potential [V]": phi_s_p, "Voltage [V]": voltage, + "Battery voltage [V]": voltage * num_cells, "Time [s]": pybamm.t, + "Discharge capacity [A.h]": Q, } # Events specify points at which a solution should terminate self.events += [ diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py index 76889d28ea..95f65f4d50 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_composite.py @@ -341,18 +341,40 @@ def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): ocp_av_p = pybamm.x_average(ocp_p) a_j_n_p1_av = pybamm.x_average(a_j_n_p1) a_j_n_p2_av = pybamm.x_average(a_j_n_p2) + num_cells = pybamm.Parameter( + "Number of cells connected in series to make a battery" + ) # The `variables` dictionary contains all variables that might be useful for # visualising the solution of the model self.variables = { "Negative primary particle concentration [mol.m-3]": c_s_n_p1, "Negative secondary particle concentration [mol.m-3]": c_s_n_p2, + "R-averaged negative primary particle concentration " + "[mol.m-3]": c_s_rav_n_p1, + "R-averaged negative secondary particle concentration " + "[mol.m-3]": c_s_rav_n_p2, + "Average negative primary particle concentration " + "[mol.m-3]": c_s_xrav_n_p1, + "Average negative secondary particle concentration " + "[mol.m-3]": c_s_xrav_n_p2, + "Positive particle concentration [mol.m-3]": c_s_p, + "Average positive particle concentration [mol.m-3]": c_s_xrav_p, + "Electrolyte concentration [mol.m-3]": c_e, + "Negative electrolyte concentration [mol.m-3]": c_e_n, + "Separator electrolyte concentration [mol.m-3]": c_e_s, + "Positive electrolyte concentration [mol.m-3]": c_e_p, "Negative electrode potential [V]": phi_s_n, - "Electrolyte potential [V]": phi_e, "Positive electrode potential [V]": phi_s_p, + "Electrolyte potential [V]": phi_e, + "Negative electrolyte potential [V]": phi_e_n, + "Separator electrolyte potential [V]": phi_e_s, + "Positive electrolyte potential [V]": phi_e_p, "Current [A]": I, + "Current variable [A]": I, # for compatibility with pybamm.Experiment "Discharge capacity [A.h]": Q, "Time [s]": pybamm.t, "Voltage [V]": voltage, + "Battery voltage [V]": voltage * num_cells, "Negative electrode primary open-circuit potential [V]": ocp_n_p1, "Negative electrode secondary open-circuit potential [V]": ocp_n_p2, "X-averaged negative electrode primary open-circuit potential " @@ -361,15 +383,6 @@ def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): "[V]": ocp_av_n_p2, "Positive electrode open-circuit potential [V]": ocp_p, "X-averaged positive electrode open-circuit potential [V]": ocp_av_p, - "R-averaged negative primary particle concentration " - "[mol.m-3]": c_s_rav_n_p1, - "R-averaged negative secondary particle concentration " - "[mol.m-3]": c_s_rav_n_p2, - "Average negative primary particle concentration " - "[mol.m-3]": c_s_xrav_n_p1, - "Average negative secondary particle concentration " - "[mol.m-3]": c_s_xrav_n_p2, - "Average positive particle concentration [mol.m-3]": c_s_xrav_p, "Negative electrode primary interfacial current density [A.m-2]": j_n_p1, "Negative electrode secondary interfacial current density [A.m-2]": j_n_p2, "X-averaged negative electrode primary interfacial current density " @@ -385,7 +398,12 @@ def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): "X-averaged negative electrode secondary volumetric " "interfacial current density [A.m-3]": a_j_n_p2_av, } + # Events specify points at which a solution should terminate self.events += [ pybamm.Event("Minimum voltage [V]", voltage - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - voltage), ] + + @property + def default_parameter_values(self): + return pybamm.ParameterValues("Chen2020_composite") diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_half_cell.py index dd91d82cf2..bc1eba3a83 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn_half_cell.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_half_cell.py @@ -30,6 +30,7 @@ class BasicDFNHalfCell(BaseModel): """ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"): + options = {"working electrode": "positive"} super().__init__(options, name) pybamm.citations.register("Marquis2019") # `param` is a class containing all the relevant parameters and functions for @@ -244,6 +245,10 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"): vdrop_cell = pybamm.boundary_value(phi_s_w, "right") - ref_potential vdrop_Li = -eta_Li - delta_phis_Li voltage = vdrop_cell + vdrop_Li + num_cells = pybamm.Parameter( + "Number of cells connected in series to make a battery" + ) + c_e_total = pybamm.x_average(eps * c_e) c_s_surf_w_av = pybamm.x_average(c_s_surf_w) @@ -280,22 +285,29 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"): # visualising the solution of the model self.variables = { "Time [s]": pybamm.t, + "Discharge capacity [A.h]": Q, "Positive particle surface concentration [mol.m-3]": c_s_surf_w, "X-averaged positive particle surface concentration " "[mol.m-3]": c_s_surf_w_av, "Positive particle concentration [mol.m-3]": c_s_w, "Total lithium in positive electrode [mol]": c_s_vol_av * L_w * param.A_cc, "Electrolyte concentration [mol.m-3]": c_e, + "Separator electrolyte concentration [mol.m-3]": c_e_s, + "Positive electrolyte concentration [mol.m-3]": c_e_w, "Total lithium in electrolyte [mol]": c_e_total * param.L_x * param.A_cc, "Current [A]": I, + "Current variable [A]": I, # for compatibility with pybamm.Experiment "Current density [A.m-2]": i_cell, "Positive electrode potential [V]": phi_s_w, "Positive electrode open-circuit potential [V]": U_w(sto_surf_w, T), "Electrolyte potential [V]": phi_e, + "Separator electrolyte potential [V]": phi_e_s, + "Positive electrolyte potential [V]": phi_e_w, "Voltage drop in the cell [V]": vdrop_cell, "Negative electrode exchange current density [A.m-2]": j_Li, "Negative electrode reaction overpotential [V]": eta_Li, "Negative electrode potential drop [V]": delta_phis_Li, "Voltage [V]": voltage, + "Battery voltage [V]": voltage * num_cells, "Instantaneous power [W.m-2]": i_cell * voltage, } diff --git a/pybamm/models/full_battery_models/lithium_ion/basic_spm.py b/pybamm/models/full_battery_models/lithium_ion/basic_spm.py index 802f8037e3..6bd93f3b27 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_spm.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_spm.py @@ -139,6 +139,9 @@ def __init__(self, name="Single Particle Model"): phi_e = -eta_n - param.n.prim.U(sto_surf_n, T) phi_s_p = eta_p + phi_e + param.p.prim.U(sto_surf_p, T) V = phi_s_p + num_cells = pybamm.Parameter( + "Number of cells connected in series to make a battery" + ) whole_cell = ["negative electrode", "separator", "positive electrode"] # The `variables` dictionary contains all variables that might be useful for @@ -146,7 +149,9 @@ def __init__(self, name="Single Particle Model"): # Primary broadcasts are used to broadcast scalar quantities across a domain # into a vector of the right shape, for multiplying with other vectors self.variables = { + "Time [s]": pybamm.t, "Discharge capacity [A.h]": Q, + "X-averaged negative particle concentration [mol.m-3]": c_s_n, "Negative particle surface " "concentration [mol.m-3]": pybamm.PrimaryBroadcast( c_s_surf_n, "negative electrode" @@ -154,11 +159,13 @@ def __init__(self, name="Single Particle Model"): "Electrolyte concentration [mol.m-3]": pybamm.PrimaryBroadcast( param.c_e_init_av, whole_cell ), + "X-averaged positive particle concentration [mol.m-3]": c_s_p, "Positive particle surface " "concentration [mol.m-3]": pybamm.PrimaryBroadcast( c_s_surf_p, "positive electrode" ), "Current [A]": I, + "Current variable [A]": I, # for compatibility with pybamm.Experiment "Negative electrode potential [V]": pybamm.PrimaryBroadcast( phi_s_n, "negative electrode" ), @@ -167,7 +174,9 @@ def __init__(self, name="Single Particle Model"): phi_s_p, "positive electrode" ), "Voltage [V]": V, + "Battery voltage [V]": V * num_cells, } + # Events specify points at which a solution should terminate self.events += [ pybamm.Event("Minimum voltage [V]", V - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - V), diff --git a/pybamm/models/full_battery_models/lithium_ion/dfn.py b/pybamm/models/full_battery_models/lithium_ion/dfn.py index 345cd7d962..c2ff8f4fc4 100644 --- a/pybamm/models/full_battery_models/lithium_ion/dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/dfn.py @@ -121,3 +121,6 @@ def set_electrolyte_potential_submodel(self): self.submodels[f"{domain} surface potential difference"] = surf_model( self.param, domain, self.options ) + + def set_summary_variables(self): + self.set_default_summary_variables() diff --git a/pybamm/models/full_battery_models/lithium_ion/spm.py b/pybamm/models/full_battery_models/lithium_ion/spm.py index e0a7dcd8f2..e655050b28 100644 --- a/pybamm/models/full_battery_models/lithium_ion/spm.py +++ b/pybamm/models/full_battery_models/lithium_ion/spm.py @@ -160,3 +160,6 @@ def set_electrolyte_potential_submodel(self): self.submodels[f"{domain} surface potential difference"] = surf_model( self.param, domain, options=self.options ) + + def set_summary_variables(self): + self.set_default_summary_variables() diff --git a/pybamm/models/submodels/external_circuit/__init__.py b/pybamm/models/submodels/external_circuit/__init__.py index bd71790295..3b2bb30b16 100644 --- a/pybamm/models/submodels/external_circuit/__init__.py +++ b/pybamm/models/submodels/external_circuit/__init__.py @@ -1,4 +1,5 @@ from .base_external_circuit import BaseModel +from .discharge_throughput import DischargeThroughput from .explicit_control_external_circuit import ( ExplicitCurrentControl, ExplicitPowerControl, diff --git a/pybamm/models/submodels/external_circuit/base_external_circuit.py b/pybamm/models/submodels/external_circuit/base_external_circuit.py index 713616c063..75eae091d5 100644 --- a/pybamm/models/submodels/external_circuit/base_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/base_external_circuit.py @@ -9,58 +9,3 @@ class BaseModel(pybamm.BaseSubModel): def __init__(self, param, options): super().__init__(param, options=options) - - def get_fundamental_variables(self): - Q_Ah = pybamm.Variable("Discharge capacity [A.h]") - Q_Ah.print_name = "Q_Ah" - # Throughput capacity (cumulative) - Qt_Ah = pybamm.Variable("Throughput capacity [A.h]") - Qt_Ah.print_name = "Qt_Ah" - - variables = { - "Discharge capacity [A.h]": Q_Ah, - "Throughput capacity [A.h]": Qt_Ah, - } - if self.options["calculate discharge energy"] == "true": - Q_Wh = pybamm.Variable("Discharge energy [W.h]") - # Throughput energy (cumulative) - Qt_Wh = pybamm.Variable("Throughput energy [W.h]") - variables.update( - { - "Discharge energy [W.h]": Q_Wh, - "Throughput energy [W.h]": Qt_Wh, - } - ) - else: - variables.update( - { - "Discharge energy [W.h]": pybamm.Scalar(0), - "Throughput energy [W.h]": pybamm.Scalar(0), - } - ) - return variables - - def set_initial_conditions(self, variables): - Q_Ah = variables["Discharge capacity [A.h]"] - Qt_Ah = variables["Throughput capacity [A.h]"] - self.initial_conditions[Q_Ah] = pybamm.Scalar(0) - self.initial_conditions[Qt_Ah] = pybamm.Scalar(0) - if self.options["calculate discharge energy"] == "true": - Q_Wh = variables["Discharge energy [W.h]"] - Qt_Wh = variables["Throughput energy [W.h]"] - self.initial_conditions[Q_Wh] = pybamm.Scalar(0) - self.initial_conditions[Qt_Wh] = pybamm.Scalar(0) - - def set_rhs(self, variables): - # ODEs for discharge capacity and throughput capacity - Q_Ah = variables["Discharge capacity [A.h]"] - Qt_Ah = variables["Throughput capacity [A.h]"] - I = variables["Current [A]"] - self.rhs[Q_Ah] = I / 3600 # Returns to zero after a complete cycle - self.rhs[Qt_Ah] = abs(I) / 3600 # Increases with each cycle - if self.options["calculate discharge energy"] == "true": - Q_Wh = variables["Discharge energy [W.h]"] - Qt_Wh = variables["Throughput energy [W.h]"] - V = variables["Voltage [V]"] - self.rhs[Q_Wh] = I * V / 3600 # Returns to zero after a complete cycle - self.rhs[Qt_Wh] = abs(I * V) / 3600 # Increases with each cycle diff --git a/pybamm/models/submodels/external_circuit/discharge_throughput.py b/pybamm/models/submodels/external_circuit/discharge_throughput.py new file mode 100644 index 0000000000..e9aecd6aed --- /dev/null +++ b/pybamm/models/submodels/external_circuit/discharge_throughput.py @@ -0,0 +1,64 @@ +# +# Variables related to discharge and throughput capacity and energy +# +import pybamm +from .base_external_circuit import BaseModel + + +class DischargeThroughput(BaseModel): + """Model calculate discharge and throughput capacity and energy.""" + + def get_fundamental_variables(self): + Q_Ah = pybamm.Variable("Discharge capacity [A.h]") + Q_Ah.print_name = "Q_Ah" + # Throughput capacity (cumulative) + Qt_Ah = pybamm.Variable("Throughput capacity [A.h]") + Qt_Ah.print_name = "Qt_Ah" + + variables = { + "Discharge capacity [A.h]": Q_Ah, + "Throughput capacity [A.h]": Qt_Ah, + } + if self.options["calculate discharge energy"] == "true": + Q_Wh = pybamm.Variable("Discharge energy [W.h]") + # Throughput energy (cumulative) + Qt_Wh = pybamm.Variable("Throughput energy [W.h]") + variables.update( + { + "Discharge energy [W.h]": Q_Wh, + "Throughput energy [W.h]": Qt_Wh, + } + ) + else: + variables.update( + { + "Discharge energy [W.h]": pybamm.Scalar(0), + "Throughput energy [W.h]": pybamm.Scalar(0), + } + ) + return variables + + def set_initial_conditions(self, variables): + Q_Ah = variables["Discharge capacity [A.h]"] + Qt_Ah = variables["Throughput capacity [A.h]"] + self.initial_conditions[Q_Ah] = pybamm.Scalar(0) + self.initial_conditions[Qt_Ah] = pybamm.Scalar(0) + if self.options["calculate discharge energy"] == "true": + Q_Wh = variables["Discharge energy [W.h]"] + Qt_Wh = variables["Throughput energy [W.h]"] + self.initial_conditions[Q_Wh] = pybamm.Scalar(0) + self.initial_conditions[Qt_Wh] = pybamm.Scalar(0) + + def set_rhs(self, variables): + # ODEs for discharge capacity and throughput capacity + Q_Ah = variables["Discharge capacity [A.h]"] + Qt_Ah = variables["Throughput capacity [A.h]"] + I = variables["Current [A]"] + self.rhs[Q_Ah] = I / 3600 # Returns to zero after a complete cycle + self.rhs[Qt_Ah] = abs(I) / 3600 # Increases with each cycle + if self.options["calculate discharge energy"] == "true": + Q_Wh = variables["Discharge energy [W.h]"] + Qt_Wh = variables["Throughput energy [W.h]"] + V = variables["Voltage [V]"] + self.rhs[Q_Wh] = I * V / 3600 # Returns to zero after a complete cycle + self.rhs[Qt_Wh] = abs(I * V) / 3600 # Increases with each cycle diff --git a/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py b/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py index 5c72da2db1..760e9e2b20 100644 --- a/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py @@ -20,9 +20,6 @@ def get_fundamental_variables(self): "C-rate": I / self.param.Q, } - # Add discharge capacity variable - variables.update(super().get_fundamental_variables()) - return variables diff --git a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py index c49c1f8c7e..60d6fb0e40 100644 --- a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py @@ -50,19 +50,14 @@ def get_fundamental_variables(self): "C-rate": I / param.Q, } - # Add discharge capacity variable - variables.update(super().get_fundamental_variables()) - return variables def set_initial_conditions(self, variables): - super().set_initial_conditions(variables) # Initial condition as a guess for consistent initial conditions i_cell = variables["Current variable [A]"] self.initial_conditions[i_cell] = self.param.Q def set_rhs(self, variables): - super().set_rhs(variables) # External circuit submodels are always equations on the current # The external circuit function should provide an update law for the current # based on current/voltage/power/etc. diff --git a/pybamm/simulation.py b/pybamm/simulation.py index a81f7b62b2..fde7383fb6 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -85,12 +85,6 @@ def __init__( self._parameter_values = parameter_values or model.default_parameter_values self._unprocessed_parameter_values = self._parameter_values - if isinstance(model, pybamm.lithium_ion.BasicDFNHalfCell): - if experiment is not None: - raise NotImplementedError( - "BasicDFNHalfCell is not compatible with experiment simulations." - ) - if experiment is None: # Check to see if the current is provided as data (i.e. drive cycle) current = self._parameter_values.get("Current function [A]") @@ -834,6 +828,13 @@ def solve( logs["stopping conditions"]["capacity"] = capacity_stop logs["elapsed time"] = timer.time() + + # Add minimum voltage to summary variable logs if there is a voltage stop + # See PR #3995 + if voltage_stop is not None: + min_voltage = np.min(cycle_solution["Battery voltage [V]"].data) + logs["summary variables"]["Minimum voltage [V]"] = min_voltage + callbacks.on_cycle_end(logs) # Break if stopping conditions are met @@ -844,7 +845,6 @@ def solve( break if voltage_stop is not None: - min_voltage = cycle_sum_vars["Minimum voltage [V]"] if min_voltage <= voltage_stop[0]: break diff --git a/pybamm/solvers/solution.py b/pybamm/solvers/solution.py index 03b113b91e..39281ef4b0 100644 --- a/pybamm/solvers/solution.py +++ b/pybamm/solvers/solution.py @@ -935,33 +935,11 @@ def _get_cycle_summary_variables(cycle_solution, esoh_solver, user_inputs=None): model = cycle_solution.all_models[0] cycle_summary_variables = pybamm.FuzzyDict({}) - # Measured capacity variables - if "Discharge capacity [A.h]" in model.variables: - Q = cycle_solution["Discharge capacity [A.h]"].data - min_Q, max_Q = np.min(Q), np.max(Q) - - cycle_summary_variables.update( - { - "Minimum measured discharge capacity [A.h]": min_Q, - "Maximum measured discharge capacity [A.h]": max_Q, - "Measured capacity [A.h]": max_Q - min_Q, - } - ) - - # Voltage variables - if "Battery voltage [V]" in model.variables: - V = cycle_solution["Battery voltage [V]"].data - min_V, max_V = np.min(V), np.max(V) - - cycle_summary_variables.update( - {"Minimum voltage [V]": min_V, "Maximum voltage [V]": max_V} - ) - - # Degradation variables - degradation_variables = model.summary_variables + # Summary variables + summary_variables = model.summary_variables first_state = cycle_solution.first_state last_state = cycle_solution.last_state - for var in degradation_variables: + for var in summary_variables: data_first = first_state[var].data data_last = last_state[var].data cycle_summary_variables[var] = data_last[0] @@ -975,6 +953,8 @@ def _get_cycle_summary_variables(cycle_solution, esoh_solver, user_inputs=None): esoh_solver is not None and isinstance(model, pybamm.lithium_ion.BaseModel) and model.options.electrode_types["negative"] == "porous" + and "Negative electrode capacity [A.h]" in model.variables + and "Positive electrode capacity [A.h]" in model.variables ): Q_n = last_state["Negative electrode capacity [A.h]"].data[0] Q_p = last_state["Positive electrode capacity [A.h]"].data[0] diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_half_cell_models.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_half_cell_models.py deleted file mode 100644 index 0d69fbae28..0000000000 --- a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_half_cell_models.py +++ /dev/null @@ -1,78 +0,0 @@ -# -# Test basic half-cell model with different parameter values -# -from tests import TestCase -import pybamm - -import numpy as np -import unittest - - -class TestBasicHalfCellModels(TestCase): - def test_runs_Xu2019(self): - options = {"working electrode": "positive"} - model = pybamm.lithium_ion.BasicDFNHalfCell(options=options) - - # create geometry - geometry = model.default_geometry - - # load parameter values - param = pybamm.ParameterValues("Xu2019") - - param["Current function [A]"] = 2.5e-3 - - # process model and geometry - param.process_model(model) - param.process_geometry(geometry) - - # set mesh - var_pts = model.default_var_pts - mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) - - # discretise model - disc = pybamm.Discretisation(mesh, model.default_spatial_methods) - disc.process_model(model) - - # solve model - t_eval = np.linspace(0, 7200, 1000) - solver = pybamm.CasadiSolver(mode="safe", atol=1e-6, rtol=1e-3) - solver.solve(model, t_eval) - - def test_runs_OKane2022_negative(self): - # load model - options = {"working electrode": "positive"} - model = pybamm.lithium_ion.BasicDFNHalfCell(options=options) - - # create geometry - geometry = model.default_geometry - - # load parameter values - param = pybamm.ParameterValues("OKane2022_graphite_SiOx_halfcell") - - param["Current function [A]"] = -2.5 # C/2 charge - - # process model and geometry - param.process_model(model) - param.process_geometry(geometry) - - # set mesh - var_pts = model.default_var_pts - mesh = pybamm.Mesh(geometry, model.default_submesh_types, var_pts) - - # discretise model - disc = pybamm.Discretisation(mesh, model.default_spatial_methods) - disc.process_model(model) - - # solve model - t_eval = np.linspace(0, 7200, 1000) - solver = pybamm.CasadiSolver(mode="safe", atol=1e-6, rtol=1e-3) - solver.solve(model, t_eval) - - -if __name__ == "__main__": - print("Add -v for more debug output") - import sys - - if "-v" in sys.argv: - debug = True - unittest.main() diff --git a/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py new file mode 100644 index 0000000000..932405dc67 --- /dev/null +++ b/tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py @@ -0,0 +1,51 @@ +# +# Test basic model classes +# +from tests import TestCase +import pybamm + +import unittest + + +class BaseBasicModelTest: + def test_with_experiment(self): + model = self.model + experiment = pybamm.Experiment( + [ + "Discharge at C/3 until 3.5V", + "Hold at 3.5V for 1 hour", + "Rest for 10 min", + ] + ) + sim = pybamm.Simulation(model, experiment=experiment) + sim.solve(calc_esoh=False) + + +class TestBasicSPM(BaseBasicModelTest, TestCase): + def setUp(self): + self.model = pybamm.lithium_ion.BasicSPM() + + +class TestBasicDFN(BaseBasicModelTest, TestCase): + def setUp(self): + self.model = pybamm.lithium_ion.BasicDFN() + + +class TestBasicDFNComposite(BaseBasicModelTest, TestCase): + def setUp(self): + self.model = pybamm.lithium_ion.BasicDFNComposite() + + +class TestBasicDFNHalfCell(BaseBasicModelTest, TestCase): + def setUp(self): + options = {"working electrode": "positive"} + self.model = pybamm.lithium_ion.BasicDFNHalfCell(options) + + +if __name__ == "__main__": + print("Add -v for more debug output") + import sys + + if "-v" in sys.argv: + debug = True + unittest.main() diff --git a/tests/unit/test_experiments/test_experiment.py b/tests/unit/test_experiments/test_experiment.py index 93713a76d2..6c342bd269 100644 --- a/tests/unit/test_experiments/test_experiment.py +++ b/tests/unit/test_experiments/test_experiment.py @@ -124,18 +124,6 @@ def test_termination(self): ) self.assertEqual(experiment.termination, {"capacity": (4.1, "Ah")}) - experiment = pybamm.Experiment( - ["Discharge at 1 C for 20 seconds"], termination=["3V"] - ) - self.assertEqual(experiment.termination, {"voltage": (3, "V")}) - - experiment = pybamm.Experiment( - ["Discharge at 1 C for 20 seconds"], termination=["3V", "4.1Ah capacity"] - ) - self.assertEqual( - experiment.termination, {"voltage": (3, "V"), "capacity": (4.1, "Ah")} - ) - with self.assertRaisesRegex(ValueError, "Only capacity"): experiment = pybamm.Experiment( ["Discharge at 1 C for 20 seconds"], termination="bla bla capacity bla" diff --git a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py index f2f5a5ef40..2f00bb260c 100644 --- a/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py +++ b/tests/unit/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py @@ -11,9 +11,6 @@ def test_dfn_well_posed(self): model = pybamm.lithium_ion.BasicDFN() model.check_well_posedness() - copy = model.new_copy() - copy.check_well_posedness() - def test_spm_well_posed(self): model = pybamm.lithium_ion.BasicSPM() model.check_well_posedness() @@ -23,26 +20,6 @@ def test_dfn_half_cell_well_posed(self): model = pybamm.lithium_ion.BasicDFNHalfCell(options=options) model.check_well_posedness() - def test_dfn_half_cell_simulation_with_experiment_error(self): - options = {"working electrode": "positive"} - model = pybamm.lithium_ion.BasicDFNHalfCell(options=options) - experiment = pybamm.Experiment( - [("Discharge at C/10 for 10 hours or until 3.5 V")] - ) - with self.assertRaisesRegex( - NotImplementedError, - "BasicDFNHalfCell is not compatible with experiment simulations.", - ): - pybamm.Simulation(model, experiment=experiment) - - def test_basic_dfn_half_cell_simulation(self): - model = pybamm.lithium_ion.BasicDFNHalfCell( - options={"working electrode": "positive"} - ) - sim = pybamm.Simulation(model=model) - sim.solve([0, 100]) - self.assertTrue(isinstance(sim.solution, pybamm.solvers.solution.Solution)) - def test_dfn_composite_well_posed(self): model = pybamm.lithium_ion.BasicDFNComposite() model.check_well_posedness()