From c387732117af0678c65655a559723959795ae47b Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Thu, 11 Apr 2024 12:01:41 +0100 Subject: [PATCH 1/9] make basic models compatible with experiments --- .../lithium_ion/basic_dfn.py | 19 ++++++++++ .../lithium_ion/basic_dfn_composite.py | 38 ++++++++++++++----- .../lithium_ion/basic_dfn_half_cell.py | 14 +++++++ .../lithium_ion/basic_spm.py | 13 +++++++ pybamm/simulation.py | 6 --- pybamm/solvers/solution.py | 2 + 6 files changed, 76 insertions(+), 16 deletions(-) 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..7e01bef904 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,21 +241,39 @@ 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 += [ pybamm.Event("Minimum voltage [V]", voltage - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - voltage), ] + # Summary variables is a list of variables that can be accessed and plotted + # per-cycle when running experiments. Typically used to track degradation + # variables (e.g LAM, LLI, capacity fade, etc.) + self.summary_variables = ["Time [s]", "Voltage [V]"] 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..1ee30fa01e 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), ] + # Summary variables is a list of variables that can be accessed and plotted + # per-cycle when running experiments. Typically used to track degradation + # variables (e.g LAM, LLI, capacity fade, etc.) + self.summary_variables = ["Time [s]", "Voltage [V]"] 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..e776929a4f 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 @@ -244,6 +244,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) @@ -286,16 +290,26 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"): "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, } + # Summary variables is a list of variables that can be accessed and plotted + # per-cycle when running experiments. Typically used to track degradation + # variables (e.g LAM, LLI, capacity fade, etc.) + self.summary_variables = ["Time [s]", "Voltage [V]"] 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..92c35e7578 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,8 +174,14 @@ 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), ] + # Summary variables is a list of variables that can be accessed and plotted + # per-cycle when running experiments. Typically used to track degradation + # variables (e.g LAM, LLI, capacity fade, etc.) + self.summary_variables = ["Time [s]", "Voltage [V]"] diff --git a/pybamm/simulation.py b/pybamm/simulation.py index 060d1cdd8c..ef4a2a9166 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -81,12 +81,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]") diff --git a/pybamm/solvers/solution.py b/pybamm/solvers/solution.py index 440d7381dc..ee12a8fe7a 100644 --- a/pybamm/solvers/solution.py +++ b/pybamm/solvers/solution.py @@ -974,6 +974,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] From 6f93f00efe26faedbe95acb8a2a1eb38c4198df1 Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Thu, 11 Apr 2024 14:34:45 +0100 Subject: [PATCH 2/9] update tests --- CHANGELOG.md | 1 + .../lithium_ion/base_lithium_ion_model.py | 2 +- .../lithium_ion/basic_dfn_composite.py | 4 + .../test_basic_half_cell_models.py | 78 ------------------- .../test_lithium_ion/test_basic_models.py | 51 ++++++++++++ .../test_lithium_ion/test_basic_models.py | 23 ------ 6 files changed, 57 insertions(+), 102 deletions(-) delete mode 100644 tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_half_cell_models.py create mode 100644 tests/integration/test_models/test_full_battery_models/test_lithium_ion/test_basic_models.py diff --git a/CHANGELOG.md b/CHANGELOG.md index b474549bc2..7bb004d65d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Features +- "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)) - Added custom experiment steps ([#3835](https://github.com/pybamm-team/PyBaMM/pull/3835)) - Added support for macOS arm64 (M-series) platforms. ([#3789](https://github.com/pybamm-team/PyBaMM/pull/3789)) 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 d6f80b3cc5..27d3681b28 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 @@ -158,7 +158,7 @@ def set_degradation_variables(self): ) # Lithium lost to side reactions - # Different way of measuring LLI but should give same value + # Different way of LLI but should give same value n_Li_lost_neg_sei = self.variables["Loss of lithium to negative SEI [mol]"] n_Li_lost_pos_sei = self.variables["Loss of lithium to positive SEI [mol]"] n_Li_lost_reactions = n_Li_lost_neg_sei + n_Li_lost_pos_sei 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 1ee30fa01e..e309b41af6 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 @@ -407,3 +407,7 @@ def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): # per-cycle when running experiments. Typically used to track degradation # variables (e.g LAM, LLI, capacity fade, etc.) self.summary_variables = ["Time [s]", "Voltage [V]"] + + @property + def default_parameter_values(self): + return pybamm.ParameterValues("Chen2020_composite") 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..ed8e302dd1 --- /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.SPM() + + +class TestBasicDFN(BaseBasicModelTest, TestCase): + def setUp(self): + self.model = pybamm.lithium_ion.DFN() + + +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_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() From 6a3d93ca620b47ed27aa385ae48f282ca30bda3b Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Thu, 11 Apr 2024 14:35:41 +0100 Subject: [PATCH 3/9] LLI comment --- .../full_battery_models/lithium_ion/base_lithium_ion_model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 27d3681b28..d6f80b3cc5 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 @@ -158,7 +158,7 @@ def set_degradation_variables(self): ) # Lithium lost to side reactions - # Different way of LLI but should give same value + # Different way of measuring LLI but should give same value n_Li_lost_neg_sei = self.variables["Loss of lithium to negative SEI [mol]"] n_Li_lost_pos_sei = self.variables["Loss of lithium to positive SEI [mol]"] n_Li_lost_reactions = n_Li_lost_neg_sei + n_Li_lost_pos_sei From 64ade637547b5d9c23e496577f0c7f37762d2075 Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Wed, 17 Apr 2024 12:02:30 +0100 Subject: [PATCH 4/9] remove default summary variables --- .../scripts/experimental_protocols/cccv.py | 56 +------------------ pybamm/callbacks.py | 14 ----- pybamm/experiment/experiment.py | 10 ++-- pybamm/models/base_model.py | 1 + .../lithium_ion/base_lithium_ion_model.py | 2 +- .../lithium_ion/basic_dfn.py | 4 -- .../lithium_ion/basic_dfn_composite.py | 4 -- .../lithium_ion/basic_dfn_half_cell.py | 4 -- .../lithium_ion/basic_spm.py | 4 -- .../full_battery_models/lithium_ion/dfn.py | 3 + .../full_battery_models/lithium_ion/spm.py | 3 + pybamm/simulation.py | 10 +--- pybamm/solvers/solution.py | 28 +--------- .../unit/test_experiments/test_experiment.py | 12 ---- .../test_simulation_with_experiment.py | 19 ------- 15 files changed, 19 insertions(+), 155 deletions(-) diff --git a/examples/scripts/experimental_protocols/cccv.py b/examples/scripts/experimental_protocols/cccv.py index c020588d07..2a9b475796 100644 --- a/examples/scripts/experimental_protocols/cccv.py +++ b/examples/scripts/experimental_protocols/cccv.py @@ -2,7 +2,6 @@ # Constant-current constant-voltage charge # import pybamm -import matplotlib.pyplot as plt pybamm.set_logging_level("NOTICE") experiment = pybamm.Experiment( @@ -17,66 +16,13 @@ ] * 3 ) -model = pybamm.lithium_ion.DFN({"SEI": "ec reaction limited"}) +model = pybamm.lithium_ion.BasicDFN() parameter_values = pybamm.ParameterValues("Chen2020") sim = pybamm.Simulation( model, experiment=experiment, parameter_values=parameter_values, - solver=pybamm.CasadiSolver("fast with events"), ) sim.solve() - -# Plot voltages from the discharge segments only -fig, ax = plt.subplots() -for i in range(3): - # Extract sub solutions - sol = sim.solution.cycles[i] - # Extract variables - t = sol["Time [h]"].entries - V = sol["Voltage [V]"].entries - # Plot - ax.plot(t - t[0], V, label=f"Discharge {i + 1}") - ax.set_xlabel("Time [h]") - ax.set_ylabel("Voltage [V]") - ax.set_xlim([0, t[-1] - t[0]]) -ax.legend(loc="lower left") - -# Save time, voltage, current, discharge capacity, temperature, and electrolyte -# concentration to csv and matlab formats -sim.solution.save_data( - "output.mat", - [ - "Time [h]", - "Current [A]", - "Voltage [V]", - "Discharge capacity [A.h]", - "X-averaged cell temperature [K]", - "Electrolyte concentration [mol.m-3]", - ], - to_format="matlab", - short_names={ - "Time [h]": "t", - "Current [A]": "I", - "Voltage [V]": "V", - "Discharge capacity [A.h]": "Q", - "X-averaged cell temperature [K]": "T", - "Electrolyte concentration [mol.m-3]": "c_e", - }, -) -# We can only save 0D variables to csv -sim.solution.save_data( - "output.csv", - [ - "Time [h]", - "Current [A]", - "Voltage [V]", - "Discharge capacity [A.h]", - "X-averaged cell temperature [K]", - ], - to_format="csv", -) - -# Show all plots sim.plot() diff --git a/pybamm/callbacks.py b/pybamm/callbacks.py index 32607bb716..8397e56958 100644 --- a/pybamm/callbacks.py +++ b/pybamm/callbacks.py @@ -201,20 +201,6 @@ def on_cycle_end(self, logs): f"is below stopping capacity ({cap_stop:.3f} Ah)." ) - voltage_stop = logs["stopping conditions"]["voltage"] - if voltage_stop is not None: - min_voltage = logs["summary variables"]["Minimum voltage [V]"] - if min_voltage > voltage_stop[0]: - self.logger.notice( - f"Minimum voltage is now {min_voltage:.3f} V " - f"(will stop at {voltage_stop[0]:.3f} V)" - ) - else: - self.logger.notice( - f"Stopping experiment since minimum voltage ({min_voltage:.3f} V) " - f"is below stopping voltage ({voltage_stop[0]:.3f} V)." - ) - def on_experiment_end(self, logs): elapsed_time = logs["elapsed time"] self.logger.notice(f"Finish experiment simulation, took {elapsed_time}") diff --git a/pybamm/experiment/experiment.py b/pybamm/experiment/experiment.py index 24b0b6e9b0..04e231b519 100644 --- a/pybamm/experiment/experiment.py +++ b/pybamm/experiment/experiment.py @@ -168,12 +168,14 @@ def read_termination(termination): "'80%', '4Ah', or '4A.h'" ) elif term.endswith("V"): - end_discharge_V = term.split("V")[0] - termination_dict["voltage"] = (float(end_discharge_V), "V") + raise ValueError( + "Voltage termination at the experiment-level is no longer supported. " + "Please set voltage termination conditions at the step level." + ) else: raise ValueError( - "Only capacity or voltage can be provided as a termination reason, " - "e.g. '80% capacity', '4 Ah capacity', or '2.5 V'" + "Only capacity can be provided as an experiment-level termination reason, " + "e.g. '80% capacity' or '4 Ah capacity'" ) return termination_dict 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/lithium_ion/base_lithium_ion_model.py b/pybamm/models/full_battery_models/lithium_ion/base_lithium_ion_model.py index d6f80b3cc5..61fd6cab37 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 7e01bef904..08809b645f 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_dfn.py @@ -273,7 +273,3 @@ def __init__(self, name="Doyle-Fuller-Newman model"): pybamm.Event("Minimum voltage [V]", voltage - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - voltage), ] - # Summary variables is a list of variables that can be accessed and plotted - # per-cycle when running experiments. Typically used to track degradation - # variables (e.g LAM, LLI, capacity fade, etc.) - self.summary_variables = ["Time [s]", "Voltage [V]"] 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 e309b41af6..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 @@ -403,10 +403,6 @@ def __init__(self, name="Composite graphite/silicon Doyle-Fuller-Newman model"): pybamm.Event("Minimum voltage [V]", voltage - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - voltage), ] - # Summary variables is a list of variables that can be accessed and plotted - # per-cycle when running experiments. Typically used to track degradation - # variables (e.g LAM, LLI, capacity fade, etc.) - self.summary_variables = ["Time [s]", "Voltage [V]"] @property def default_parameter_values(self): 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 e776929a4f..ccaf377d2b 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 @@ -309,7 +309,3 @@ def __init__(self, options=None, name="Doyle-Fuller-Newman half cell model"): "Battery voltage [V]": voltage * num_cells, "Instantaneous power [W.m-2]": i_cell * voltage, } - # Summary variables is a list of variables that can be accessed and plotted - # per-cycle when running experiments. Typically used to track degradation - # variables (e.g LAM, LLI, capacity fade, etc.) - self.summary_variables = ["Time [s]", "Voltage [V]"] 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 92c35e7578..6bd93f3b27 100644 --- a/pybamm/models/full_battery_models/lithium_ion/basic_spm.py +++ b/pybamm/models/full_battery_models/lithium_ion/basic_spm.py @@ -181,7 +181,3 @@ def __init__(self, name="Single Particle Model"): pybamm.Event("Minimum voltage [V]", V - param.voltage_low_cut), pybamm.Event("Maximum voltage [V]", param.voltage_high_cut - V), ] - # Summary variables is a list of variables that can be accessed and plotted - # per-cycle when running experiments. Typically used to track degradation - # variables (e.g LAM, LLI, capacity fade, etc.) - self.summary_variables = ["Time [s]", "Voltage [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/simulation.py b/pybamm/simulation.py index ef4a2a9166..3770b1a2d4 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -552,9 +552,6 @@ def solve( all_first_states = starting_solution_first_states current_solution = starting_solution or pybamm.EmptySolution() - voltage_stop = self.experiment.termination.get("voltage") - logs["stopping conditions"] = {"voltage": voltage_stop} - idx = 0 num_cycles = len(self.experiment.cycle_lengths) feasible = True # simulation will stop if experiment is infeasible @@ -823,7 +820,7 @@ def solve( capacity_stop = value / 100 * capacity_start else: capacity_stop = None - logs["stopping conditions"]["capacity"] = capacity_stop + logs["stopping conditions"] = {"capacity": capacity_stop} logs["elapsed time"] = timer.time() callbacks.on_cycle_end(logs) @@ -835,11 +832,6 @@ def solve( if not np.isnan(capacity_now) and capacity_now <= capacity_stop: break - if voltage_stop is not None: - min_voltage = cycle_sum_vars["Minimum voltage [V]"] - if min_voltage <= voltage_stop[0]: - break - # Break if the experiment is infeasible (or errored) if feasible is False: break diff --git a/pybamm/solvers/solution.py b/pybamm/solvers/solution.py index ee12a8fe7a..fa6cc9d3ab 100644 --- a/pybamm/solvers/solution.py +++ b/pybamm/solvers/solution.py @@ -934,33 +934,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] 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_experiments/test_simulation_with_experiment.py b/tests/unit/test_experiments/test_simulation_with_experiment.py index 16829bfaec..e2879bf294 100644 --- a/tests/unit/test_experiments/test_simulation_with_experiment.py +++ b/tests/unit/test_experiments/test_simulation_with_experiment.py @@ -330,25 +330,6 @@ def test_run_experiment_with_pbar(self): sim = pybamm.Simulation(model, experiment=experiment) sim.solve(showprogress=True) - def test_run_experiment_termination_voltage(self): - # with percent - experiment = pybamm.Experiment( - [ - ("Discharge at 0.5C for 10 minutes", "Rest for 10 minutes"), - ] - * 5, - termination="4V", - ) - model = pybamm.lithium_ion.SPM() - param = pybamm.ParameterValues("Chen2020") - sim = pybamm.Simulation(model, experiment=experiment, parameter_values=param) - # Test with calc_esoh=False here - sol = sim.solve(calc_esoh=False) - # Only two cycles should be completed, only 2nd cycle should go below 4V - np.testing.assert_array_less(4, np.min(sol.cycles[0]["Voltage [V]"].data)) - np.testing.assert_array_less(np.min(sol.cycles[1]["Voltage [V]"].data), 4) - self.assertEqual(len(sol.cycles), 2) - def test_save_at_cycles(self): experiment = pybamm.Experiment( [ From 75f2a6dce60dede77a212c27059fe60ae030c4fa Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Wed, 1 May 2024 12:23:57 +0100 Subject: [PATCH 5/9] dont add eqns for discharge and throughput capacity in experiment step set up --- pybamm/experiment/step/steps.py | 14 +++-- .../external_circuit/base_external_circuit.py | 25 ++++---- .../explicit_control_external_circuit.py | 3 +- .../function_control_external_circuit.py | 58 +++++++++++++++---- 4 files changed, 73 insertions(+), 27 deletions(-) diff --git a/pybamm/experiment/step/steps.py b/pybamm/experiment/step/steps.py index 66b3ff5645..03596971e2 100644 --- a/pybamm/experiment/step/steps.py +++ b/pybamm/experiment/step/steps.py @@ -179,7 +179,7 @@ def get_parameter_values(self, variables): def get_submodel(self, model): return pybamm.external_circuit.VoltageFunctionControl( - model.param, model.options + model.param, model.options, add_discharge_capacity=False ) @@ -211,7 +211,9 @@ def get_parameter_values(self, variables): return {"Power function [W]": self.value} def get_submodel(self, model): - return pybamm.external_circuit.PowerFunctionControl(model.param, model.options) + return pybamm.external_circuit.PowerFunctionControl( + model.param, model.options, add_discharge_capacity=False + ) def power(value, **kwargs): @@ -243,7 +245,7 @@ def get_parameter_values(self, variables): def get_submodel(self, model): return pybamm.external_circuit.ResistanceFunctionControl( - model.param, model.options + model.param, model.options, add_discharge_capacity=False ) @@ -410,7 +412,11 @@ def __init__(self, current_rhs_function, control="algebraic", **kwargs): def get_submodel(self, model): return pybamm.external_circuit.FunctionControl( - model.param, self.current_rhs_function, model.options, control=self.control + model.param, + self.current_rhs_function, + model.options, + control=self.control, + add_discharge_capacity=False, ) def copy(self): diff --git a/pybamm/models/submodels/external_circuit/base_external_circuit.py b/pybamm/models/submodels/external_circuit/base_external_circuit.py index 713616c063..40a9f72db0 100644 --- a/pybamm/models/submodels/external_circuit/base_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/base_external_circuit.py @@ -7,8 +7,9 @@ class BaseModel(pybamm.BaseSubModel): """Model to represent the behaviour of the external circuit.""" - def __init__(self, param, options): + def __init__(self, param, options, add_discharge_capacity=True): super().__init__(param, options=options) + self.add_discharge_capacity = add_discharge_capacity def get_fundamental_variables(self): Q_Ah = pybamm.Variable("Discharge capacity [A.h]") @@ -41,10 +42,11 @@ def get_fundamental_variables(self): 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.add_discharge_capacity: + 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]"] @@ -52,12 +54,13 @@ def set_initial_conditions(self, variables): 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.add_discharge_capacity: + # 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]"] 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..e560c50607 100644 --- a/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/explicit_control_external_circuit.py @@ -21,7 +21,8 @@ def get_fundamental_variables(self): } # Add discharge capacity variable - variables.update(super().get_fundamental_variables()) + if self.add_discharge_capacity: + 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..fa55f6c665 100644 --- a/pybamm/models/submodels/external_circuit/function_control_external_circuit.py +++ b/pybamm/models/submodels/external_circuit/function_control_external_circuit.py @@ -23,8 +23,15 @@ class FunctionControl(BaseModel): or 'differential'. """ - def __init__(self, param, external_circuit_function, options, control="algebraic"): - super().__init__(param, options) + def __init__( + self, + param, + external_circuit_function, + options, + control="algebraic", + add_discharge_capacity=True, + ): + super().__init__(param, options, add_discharge_capacity=add_discharge_capacity) self.external_circuit_function = external_circuit_function self.control = control @@ -51,7 +58,8 @@ def get_fundamental_variables(self): } # Add discharge capacity variable - variables.update(super().get_fundamental_variables()) + if self.add_discharge_capacity: + variables.update(super().get_fundamental_variables()) return variables @@ -84,8 +92,14 @@ class VoltageFunctionControl(FunctionControl): External circuit with voltage control, implemented as an extra algebraic equation. """ - def __init__(self, param, options): - super().__init__(param, self.constant_voltage, options, control="algebraic") + def __init__(self, param, options, add_discharge_capacity=True): + super().__init__( + param, + self.constant_voltage, + options, + control="algebraic", + add_discharge_capacity=add_discharge_capacity, + ) def constant_voltage(self, variables): V = variables["Voltage [V]"] @@ -97,8 +111,16 @@ def constant_voltage(self, variables): class PowerFunctionControl(FunctionControl): """External circuit with power control.""" - def __init__(self, param, options, control="algebraic"): - super().__init__(param, self.constant_power, options, control=control) + def __init__( + self, param, options, control="algebraic", add_discharge_capacity=True + ): + super().__init__( + param, + self.constant_power, + options, + control=control, + add_discharge_capacity=add_discharge_capacity, + ) def constant_power(self, variables): I = variables["Current [A]"] @@ -118,8 +140,16 @@ def constant_power(self, variables): class ResistanceFunctionControl(FunctionControl): """External circuit with resistance control.""" - def __init__(self, param, options, control="algebraic"): - super().__init__(param, self.constant_resistance, options, control=control) + def __init__( + self, param, options, control="algebraic", add_discharge_capacity=True + ): + super().__init__( + param, + self.constant_resistance, + options, + control=control, + add_discharge_capacity=add_discharge_capacity, + ) def constant_resistance(self, variables): I = variables["Current [A]"] @@ -145,8 +175,14 @@ class CCCVFunctionControl(FunctionControl): """ - def __init__(self, param, options): - super().__init__(param, self.cccv, options, control="differential with max") + def __init__(self, param, options, add_discharge_capacity=True): + super().__init__( + param, + self.cccv, + options, + control="differential with max", + add_discharge_capacity=add_discharge_capacity, + ) pybamm.citations.register("Mohtat2021") def cccv(self, variables): From 0aeec78beaf965f4b9fe4e3dfd459e4a322612f4 Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Wed, 1 May 2024 12:24:58 +0100 Subject: [PATCH 6/9] revert example --- .../scripts/experimental_protocols/cccv.py | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/examples/scripts/experimental_protocols/cccv.py b/examples/scripts/experimental_protocols/cccv.py index 2a9b475796..c020588d07 100644 --- a/examples/scripts/experimental_protocols/cccv.py +++ b/examples/scripts/experimental_protocols/cccv.py @@ -2,6 +2,7 @@ # Constant-current constant-voltage charge # import pybamm +import matplotlib.pyplot as plt pybamm.set_logging_level("NOTICE") experiment = pybamm.Experiment( @@ -16,13 +17,66 @@ ] * 3 ) -model = pybamm.lithium_ion.BasicDFN() +model = pybamm.lithium_ion.DFN({"SEI": "ec reaction limited"}) parameter_values = pybamm.ParameterValues("Chen2020") sim = pybamm.Simulation( model, experiment=experiment, parameter_values=parameter_values, + solver=pybamm.CasadiSolver("fast with events"), ) sim.solve() + +# Plot voltages from the discharge segments only +fig, ax = plt.subplots() +for i in range(3): + # Extract sub solutions + sol = sim.solution.cycles[i] + # Extract variables + t = sol["Time [h]"].entries + V = sol["Voltage [V]"].entries + # Plot + ax.plot(t - t[0], V, label=f"Discharge {i + 1}") + ax.set_xlabel("Time [h]") + ax.set_ylabel("Voltage [V]") + ax.set_xlim([0, t[-1] - t[0]]) +ax.legend(loc="lower left") + +# Save time, voltage, current, discharge capacity, temperature, and electrolyte +# concentration to csv and matlab formats +sim.solution.save_data( + "output.mat", + [ + "Time [h]", + "Current [A]", + "Voltage [V]", + "Discharge capacity [A.h]", + "X-averaged cell temperature [K]", + "Electrolyte concentration [mol.m-3]", + ], + to_format="matlab", + short_names={ + "Time [h]": "t", + "Current [A]": "I", + "Voltage [V]": "V", + "Discharge capacity [A.h]": "Q", + "X-averaged cell temperature [K]": "T", + "Electrolyte concentration [mol.m-3]": "c_e", + }, +) +# We can only save 0D variables to csv +sim.solution.save_data( + "output.csv", + [ + "Time [h]", + "Current [A]", + "Voltage [V]", + "Discharge capacity [A.h]", + "X-averaged cell temperature [K]", + ], + to_format="csv", +) + +# Show all plots sim.plot() From 44c51d3b7c1dd0022874476c66213519c167260a Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Wed, 1 May 2024 12:29:01 +0100 Subject: [PATCH 7/9] remove test files --- test.ipynb | 105 ----------------------------------------------------- test.py | 72 ------------------------------------ test2.py | 93 ----------------------------------------------- 3 files changed, 270 deletions(-) delete mode 100644 test.ipynb delete mode 100644 test.py delete mode 100644 test2.py diff --git a/test.ipynb b/test.ipynb deleted file mode 100644 index 9790e40d43..0000000000 --- a/test.ipynb +++ /dev/null @@ -1,105 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 22, - "metadata": {}, - "outputs": [], - "source": [ - "import pybamm" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "metadata": {}, - "outputs": [], - "source": [ - "a = pybamm.Parameter(\"a\")" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [], - "source": [ - "x = 3 * a + pybamm.sin(a)" - ] - }, - { - "cell_type": "code", - "execution_count": 26, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Addition(-0x37ab4fe9b491b710, +, children=['3.0 * a', 'sin(a)'], domains={})" - ] - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x" - ] - }, - { - "cell_type": "code", - "execution_count": 28, - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "Scalar(-0x6aa0c42e52ae1e3c, 3.0, children=[], domains={})" - ] - }, - "execution_count": 28, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "x.children[0].children[0]" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "venv", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.6" - }, - "orig_nbformat": 4, - "vscode": { - "interpreter": { - "hash": "9ff3d0c7e37de5f5aa47f4f719e4c84fc6cba7b39c571a05173422444e82fa58" - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/test.py b/test.py deleted file mode 100644 index e993e1a08a..0000000000 --- a/test.py +++ /dev/null @@ -1,72 +0,0 @@ -import pybamm -import numpy as np - - -def graphite_LGM50_ocp_Chen2020(sto): - u_eq = ( - 1.9793 * np.exp(-39.3631 * sto) - + 0.2482 - - 0.0909 * np.tanh(29.8538 * (sto - 0.1234)) - - 0.04478 * np.tanh(14.9159 * (sto - 0.2769)) - - 0.0205 * np.tanh(30.4444 * (sto - 0.6103)) - ) - - return u_eq - - -def nmc_LGM50_ocp_Chen2020(sto): - u_eq = ( - -0.8090 * sto - + 4.4875 - - 0.0428 * np.tanh(18.5138 * (sto - 0.5542)) - - 17.7326 * np.tanh(15.7890 * (sto - 0.3117)) - + 17.5842 * np.tanh(15.9308 * (sto - 0.3120)) - ) - - return u_eq - - -# state variables -x_n = pybamm.Variable("Negative electrode sto") -x_p = pybamm.Variable("Positive electrode sto") - -# parameters -x_n_0 = pybamm.Parameter("Negative electrode initial sto") -x_p_0 = pybamm.Parameter("Positive electrode initial sto") -i = pybamm.FunctionParameter("Current [A]", {"Time [s]": pybamm.t}) -u_n = pybamm.FunctionParameter("Negative electrode potential [V]", {"x_n": x_n}) -u_p = pybamm.FunctionParameter("Positive electrode potential [V]", {"x_p": x_p}) -q_n = pybamm.Parameter("Negative electrode capacity [A.h]") -q_p = pybamm.Parameter("Positive electrode capacity [A.h]") -r = pybamm.Parameter("Resistance [Ohm]") - -# pybamm model -model = pybamm.BaseModel("Simple reservoir model") -model.rhs[x_n] = -i / q_n -model.rhs[x_p] = i / q_p -model.initial_conditions[x_n] = x_n_0 -model.initial_conditions[x_p] = x_p_0 -model.variables["Negative electrode sto"] = x_n -model.variables["Positive electrode sto"] = x_p - -# events - - -# parameter values -parameter_values = pybamm.ParameterValues( - { - "Negative electrode initial sto": 0.9, - "Positive electrode initial sto": 0.1, - "Negative electrode capacity [A.h]": 1, - "Positive electrode capacity [A.h]": 1, - "Resistance [Ohm]": 1, - "Current [A]": lambda t: 1 + 0.5 * pybamm.sin(100 * t), - "Negative electrode potential [V]": graphite_LGM50_ocp_Chen2020, - "Positive electrode potential [V]": nmc_LGM50_ocp_Chen2020, - } -) - -# solver -sim = pybamm.Simulation(model, parameter_values=parameter_values) -sol = sim.solve([0, 3600]) -sol.plot(["Negative electrode sto", "Positive electrode sto"]) diff --git a/test2.py b/test2.py deleted file mode 100644 index a6676f81a1..0000000000 --- a/test2.py +++ /dev/null @@ -1,93 +0,0 @@ -import pybamm -import numpy as np - - -def graphite_LGM50_ocp_Chen2020(sto): - u_eq = ( - 1.9793 * np.exp(-39.3631 * sto) - + 0.2482 - - 0.0909 * np.tanh(29.8538 * (sto - 0.1234)) - - 0.04478 * np.tanh(14.9159 * (sto - 0.2769)) - - 0.0205 * np.tanh(30.4444 * (sto - 0.6103)) - ) - - return u_eq - - -def nmc_LGM50_ocp_Chen2020(sto): - u_eq = ( - -0.8090 * sto - + 4.4875 - - 0.0428 * np.tanh(18.5138 * (sto - 0.5542)) - - 17.7326 * np.tanh(15.7890 * (sto - 0.3117)) - + 17.5842 * np.tanh(15.9308 * (sto - 0.3120)) - ) - - return u_eq - - -x_n = pybamm.Variable("Negative electrode stochiometry") -x_p = pybamm.Variable("Positive electrode stochiometry") - -i = pybamm.FunctionParameter("Current function [A]", {"Time [s]": pybamm.t}) -x_n_0 = pybamm.Parameter("Initial negative electrode stochiometry") -x_p_0 = pybamm.Parameter("Initial positive electrode stochiometry") -U_p = pybamm.FunctionParameter("Positive electrode OCV", {"x_p": x_p}) -U_n = pybamm.FunctionParameter("Negative electrode OCV", {"x_n": x_n}) -Q_n = pybamm.Parameter("Negative electrode capacity [A.h]") -Q_p = pybamm.Parameter("Positive electrode capacity [A.h]") -R = pybamm.Parameter("Electrode resistance [Ohm]") - -model = pybamm.BaseModel("reservoir model") -model.rhs[x_n] = -i / Q_n -model.initial_conditions[x_n] = x_n_0 -model.rhs[x_p] = i / Q_p -model.initial_conditions[x_p] = x_p_0 - -model.variables["Voltage [V]"] = U_p - U_n - i * R -model.variables["Negative electrode stochiometry"] = x_n -model.variables["Positive electrode stochiometry"] = x_p - -model.rhs[x_n].visualise("x_n_rhs.png") - -# events -# stop_at_t_equal_3 = pybamm.Event("Stop at t = 3", pybamm.t -3) #expression has to equal zero - -model.events = [ - pybamm.Event("Minimum negative stochiometry", x_n - 0), - pybamm.Event("Maximum negative stochiometry", 1 - x_n), - pybamm.Event("Minimum positive stochiometry", x_p - 0), - pybamm.Event("Maximum positive stochiometry", 1 - x_p), -] - - -param = pybamm.ParameterValues( - { - "Current function [A]": lambda t: 0.1 + 0.05 * pybamm.sin(100 * t), - "Initial negative electrode stochiometry": 0.9, - "Initial positive electrode stochiometry": 0.1, - "Negative electrode capacity [A.h]": 1, - "Positive electrode capacity [A.h]": 1, - "Electrode resistance [Ohm]": 0.1, - "Positive electrode OCV": nmc_LGM50_ocp_Chen2020, - "Negative electrode OCV": graphite_LGM50_ocp_Chen2020, - } -) - -# debugging -# print(model.rhs[x_n]) -# print(model.rhs[x_n].children[0]) - -# time -# model.rhs[x_n].children[0].children[0].children[0] - -# solve it -sim = pybamm.Simulation(model, parameter_values=param) -sol = sim.solve([0, 10]) -sol.plot( - [ - "Voltage [V]", - "Negative electrode stochiometry", - "Positive electrode stochiometry", - ] -) From c438575b61a3b8aa756cdaea47402f3fd01c9963 Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Wed, 1 May 2024 14:05:12 +0100 Subject: [PATCH 8/9] add discharge capacity to BasicDFNHalfCell --- .../full_battery_models/lithium_ion/basic_dfn_half_cell.py | 1 + .../test_lithium_ion/test_basic_models.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) 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 ccaf377d2b..aa549bdf6c 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 @@ -284,6 +284,7 @@ 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, 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 index ed8e302dd1..932405dc67 100644 --- 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 @@ -23,12 +23,12 @@ def test_with_experiment(self): class TestBasicSPM(BaseBasicModelTest, TestCase): def setUp(self): - self.model = pybamm.lithium_ion.SPM() + self.model = pybamm.lithium_ion.BasicSPM() class TestBasicDFN(BaseBasicModelTest, TestCase): def setUp(self): - self.model = pybamm.lithium_ion.DFN() + self.model = pybamm.lithium_ion.BasicDFN() class TestBasicDFNComposite(BaseBasicModelTest, TestCase): From df61b36ce676928c0c68a71ab256decf6743a1ae Mon Sep 17 00:00:00 2001 From: Robert Timms Date: Mon, 6 May 2024 14:19:36 +0100 Subject: [PATCH 9/9] discharge variable submodel --- .../external_circuit/discharge_throughput.rst | 7 ++ .../submodels/external_circuit/index.rst | 1 + .../full_battery_models/base_battery_model.py | 3 + .../lithium_ion/basic_dfn_half_cell.py | 1 + .../submodels/external_circuit/__init__.py | 1 + .../external_circuit/base_external_circuit.py | 55 ---------------- .../external_circuit/discharge_throughput.py | 64 +++++++++++++++++++ .../explicit_control_external_circuit.py | 3 - .../function_control_external_circuit.py | 5 -- pybamm/simulation.py | 8 ++- 10 files changed, 84 insertions(+), 64 deletions(-) create mode 100644 docs/source/api/models/submodels/external_circuit/discharge_throughput.rst create mode 100644 pybamm/models/submodels/external_circuit/discharge_throughput.py 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/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/basic_dfn_half_cell.py b/pybamm/models/full_battery_models/lithium_ion/basic_dfn_half_cell.py index aa549bdf6c..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 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 984595bd4d..fde7383fb6 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -828,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 @@ -838,7 +845,6 @@ def solve( break if voltage_stop is not None: - min_voltage = np.min(cycle_solution["Battery voltage [V]"].data) if min_voltage <= voltage_stop[0]: break