From 4644235ac81dba01f400f5ae6b91966057763897 Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Fri, 26 Nov 2021 14:43:40 -0500 Subject: [PATCH 1/2] #1826 add voltage termination --- pybamm/experiments/experiment.py | 7 ++++-- pybamm/simulation.py | 18 ++++++++++++++ .../unit/test_experiments/test_experiment.py | 14 +++++++++++ .../test_simulation_with_experiment.py | 24 ++++++++++++++++++- 4 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pybamm/experiments/experiment.py b/pybamm/experiments/experiment.py index ee06ed5218..fec96abf8f 100644 --- a/pybamm/experiments/experiment.py +++ b/pybamm/experiments/experiment.py @@ -459,10 +459,13 @@ def read_termination(self, termination): "Capacity termination must be given in the form " "'80%', '4Ah', or '4A.h'" ) + elif term.endswith("V"): + end_discharge_V = term.split("V")[0] + termination_dict["voltage"] = (float(end_discharge_V), "V") else: raise ValueError( - "Only capacity can be provided as a termination reason, " - "e.g. '80% capacity' or '4 Ah capacity'" + "Only capacity or voltage can be provided as a termination reason, " + "e.g. '80% capacity', '4 Ah capacity', or '2.5 V'" ) return termination_dict diff --git a/pybamm/simulation.py b/pybamm/simulation.py index baef45c1f4..a9a8a374cc 100644 --- a/pybamm/simulation.py +++ b/pybamm/simulation.py @@ -842,6 +842,8 @@ def solve( else: esoh_sim = None + voltage_stop = self.experiment.termination.get("voltage") + idx = 0 num_cycles = len(self.experiment.cycle_lengths) feasible = True # simulation will stop if experiment is infeasible @@ -983,6 +985,22 @@ def solve( ) break + # Check voltage stop + if voltage_stop is not None: + min_voltage = np.min(cycle_solution["Battery voltage [V]"].data) + if min_voltage > voltage_stop[0]: + pybamm.logger.notice( + f"Minimum voltage is now {min_voltage:.3f} V " + f"(will stop at {voltage_stop[0]:.3f} V)" + ) + else: + pybamm.logger.notice( + "Stopping experiment since minimum voltage " + f"({min_voltage:.3f} V) " + f"is below stopping voltage ({voltage_stop[0]:.3f} V)." + ) + break + if self.solution is not None and len(all_cycle_solutions) > 0: self.solution.cycles = all_cycle_solutions self.solution.set_summary_variables(all_summary_variables) diff --git a/tests/unit/test_experiments/test_experiment.py b/tests/unit/test_experiments/test_experiment.py index b91645460e..6e3eb5a3fa 100644 --- a/tests/unit/test_experiments/test_experiment.py +++ b/tests/unit/test_experiments/test_experiment.py @@ -339,6 +339,7 @@ def test_termination(self): ["Discharge at 1 C for 20 seconds"], termination="80.7% capacity" ) self.assertEqual(experiment.termination, {"capacity": (80.7, "%")}) + experiment = pybamm.Experiment( ["Discharge at 1 C for 20 seconds"], termination="80.7 % capacity" ) @@ -348,11 +349,24 @@ def test_termination(self): ["Discharge at 1 C for 20 seconds"], termination="4.1Ah capacity" ) self.assertEqual(experiment.termination, {"capacity": (4.1, "Ah")}) + experiment = pybamm.Experiment( ["Discharge at 1 C for 20 seconds"], termination="4.1 A.h capacity" ) 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 bd3de80c88..5bb94236f0 100644 --- a/tests/unit/test_experiments/test_simulation_with_experiment.py +++ b/tests/unit/test_experiments/test_simulation_with_experiment.py @@ -204,7 +204,7 @@ def test_run_experiment_breaks_early(self): pybamm.set_logging_level("WARNING") self.assertEqual(sim._solution, None) - def test_run_experiment_termination(self): + def test_run_experiment_termination_capacity(self): # with percent experiment = pybamm.Experiment( [ @@ -247,6 +247,28 @@ def test_run_experiment_termination(self): # all but the last value should be above the termination condition np.testing.assert_array_less(5.04, C[:-1]) + 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) + sol = sim.solve() + # Only two cycles should be completed, only 2nd cycle should go below 4V + np.testing.assert_array_less( + 4, np.min(sol.cycles[0]["Terminal voltage [V]"].data) + ) + np.testing.assert_array_less( + np.min(sol.cycles[1]["Terminal voltage [V]"].data), 4 + ) + self.assertEqual(len(sol.cycles), 2) + def test_save_at_cycles(self): experiment = pybamm.Experiment( [ From b86afd910160a2423ec6b18d0c8f39e7004fe32a Mon Sep 17 00:00:00 2001 From: Valentin Sulzer Date: Thu, 2 Dec 2021 13:13:52 -0500 Subject: [PATCH 2/2] #1826 changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 112bf4345b..0c3761eb9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) - 2021-12-01 ## Bug fixes + +- Experiments can be set to terminate when a voltage is reached (across all steps) ([#1832](https://github.com/pybamm-team/PyBaMM/pull/1832)) - Solid tortuosity is now correctly calculated with Bruggeman coefficient of the respective electrode ([#1773](https://github.com/pybamm-team/PyBaMM/pull/1773)) # [v21.11](https://github.com/pybamm-team/PyBaMM/tree/v21.11) - 2021-11-30