diff --git a/CHANGELOG.md b/CHANGELOG.md index de93a95d00..a38d5b8c76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # [Unreleased](https://github.com/pybamm-team/PyBaMM/) +## Features + +- Steps in `Experiment` can now be tagged and cycle numbers be searched based on those tags ([#2593](https://github.com/pybamm-team/PyBaMM/pull/2593)). + # [v22.12](https://github.com/pybamm-team/PyBaMM/tree/v22.12) - 2022-12-31 ## Features diff --git a/pybamm/experiments/experiment.py b/pybamm/experiments/experiment.py index 2dc432b71e..c907e767d6 100644 --- a/pybamm/experiments/experiment.py +++ b/pybamm/experiments/experiment.py @@ -145,7 +145,7 @@ def __repr__(self): def read_string(self, cond, drive_cycles): """ - Convert a string to a tuple of the right format + Convert a string to a dictionary of the right format Parameters ---------- @@ -163,6 +163,11 @@ def read_string(self, cond, drive_cycles): cond_CC, cond_CV = cond.split(" then ") op_CC = self.read_string(cond_CC, drive_cycles) op_CV = self.read_string(cond_CV, drive_cycles) + tag_CC = op_CC["tags"] or [] + tag_CV = op_CV["tags"] or [] + tags = list(np.unique(tag_CC + tag_CV)) + if len(tags) == 0: + tags = None outputs = { "type": "CCCV", "Voltage input [V]": op_CV["Voltage input [V]"], @@ -171,6 +176,7 @@ def read_string(self, cond, drive_cycles): "dc_data": None, "string": cond, "events": op_CV["events"], + "tags": tags, } if "Current input [A]" in op_CC: outputs["Current input [A]"] = op_CC["Current input [A]"] @@ -178,6 +184,13 @@ def read_string(self, cond, drive_cycles): outputs["C-rate input [-]"] = op_CC["C-rate input [-]"] return outputs + # Read tags + if " [" in cond: + cond, tag_str = cond.split(" [") + tags = tag_str[0:-1].split(",") + else: + tags = None + # Read period if " period)" in cond: cond, time_period = cond.split(" (") @@ -254,6 +267,7 @@ def read_string(self, cond, drive_cycles): "dc_data": dc_data, "string": cond, "events": events, + "tags": tags, } def unit_to_type(self, electric): @@ -440,3 +454,16 @@ def is_cccv(self, step, next_step): if op["events"] == {k: v for k, v in next_op.items() if k in op["events"]}: return True return False + + def search_tag(self, tag): + cycles = [] + for i, cycle in enumerate(self.operating_conditions_cycles): + for cond in cycle: + if " [" in cond: + cond, tag_str = cond.split(" [") + tags = tag_str[0:-1].split(",") + if tag in tags: + cycles.append(i) + break + + return cycles diff --git a/tests/unit/test_experiments/test_experiment.py b/tests/unit/test_experiments/test_experiment.py index f4ed7356d8..af0d5ea23c 100644 --- a/tests/unit/test_experiments/test_experiment.py +++ b/tests/unit/test_experiments/test_experiment.py @@ -21,14 +21,14 @@ def test_read_strings(self): experiment = pybamm.Experiment( [ - "Discharge at 1C for 0.5 hours", - "Discharge at C/20 for 0.5 hours", + "Discharge at 1C for 0.5 hours [tag1]", + "Discharge at C/20 for 0.5 hours [tag2,tag3]", "Charge at 0.5 C for 45 minutes", "Discharge at 1 A for 0.5 hours", "Charge at 200 mA for 45 minutes (1 minute period)", "Discharge at 1W for 0.5 hours", "Charge at 200mW for 45 minutes", - "Rest for 10 minutes (5 minute period)", + "Rest for 10 minutes (5 minute period) [tag1,tag3]", "Hold at 1V for 20 seconds", "Charge at 1 C until 4.1V", "Hold at 4.1 V until 50mA", @@ -53,6 +53,7 @@ def test_read_strings(self): "dc_data": None, "string": "Discharge at 1C for 0.5 hours", "events": None, + "tags": ["tag1"], }, { "C-rate input [-]": 0.05, @@ -62,6 +63,7 @@ def test_read_strings(self): "dc_data": None, "string": "Discharge at C/20 for 0.5 hours", "events": None, + "tags": ["tag2", "tag3"], }, { "C-rate input [-]": -0.5, @@ -71,6 +73,7 @@ def test_read_strings(self): "dc_data": None, "string": "Charge at 0.5 C for 45 minutes", "events": None, + "tags": None, }, { "Current input [A]": 1, @@ -80,6 +83,7 @@ def test_read_strings(self): "dc_data": None, "string": "Discharge at 1 A for 0.5 hours", "events": None, + "tags": None, }, { "Current input [A]": -0.2, @@ -89,6 +93,7 @@ def test_read_strings(self): "dc_data": None, "string": "Charge at 200 mA for 45 minutes", "events": None, + "tags": None, }, { "Power input [W]": 1, @@ -98,6 +103,7 @@ def test_read_strings(self): "dc_data": None, "string": "Discharge at 1W for 0.5 hours", "events": None, + "tags": None, }, { "Power input [W]": -0.2, @@ -107,6 +113,7 @@ def test_read_strings(self): "dc_data": None, "string": "Charge at 200mW for 45 minutes", "events": None, + "tags": None, }, { "Current input [A]": 0, @@ -116,6 +123,7 @@ def test_read_strings(self): "dc_data": None, "string": "Rest for 10 minutes", "events": None, + "tags": ["tag1", "tag3"], }, { "Voltage input [V]": 1, @@ -125,6 +133,7 @@ def test_read_strings(self): "dc_data": None, "string": "Hold at 1V for 20 seconds", "events": None, + "tags": None, }, { "C-rate input [-]": -1, @@ -134,6 +143,7 @@ def test_read_strings(self): "dc_data": None, "string": "Charge at 1 C until 4.1V", "events": {"Voltage input [V]": 4.1, "type": "voltage"}, + "tags": None, }, { "Voltage input [V]": 4.1, @@ -143,6 +153,7 @@ def test_read_strings(self): "dc_data": None, "string": "Hold at 4.1 V until 50mA", "events": {"Current input [A]": 0.05, "type": "current"}, + "tags": None, }, { "Voltage input [V]": 3, @@ -152,6 +163,7 @@ def test_read_strings(self): "dc_data": None, "string": "Hold at 3V until C/50", "events": {"C-rate input [-]": 0.02, "type": "C-rate"}, + "tags": None, }, { "C-rate input [-]": 1 / 3, @@ -161,6 +173,7 @@ def test_read_strings(self): "dc_data": None, "string": "Discharge at C/3 for 2 hours or until 2.5 V", "events": {"Voltage input [V]": 2.5, "type": "voltage"}, + "tags": None, }, ], ) @@ -180,18 +193,21 @@ def test_read_strings(self): self.assertEqual(experiment.operating_conditions[-3]["type"], "current") self.assertEqual(experiment.operating_conditions[-3]["time"], time_0) self.assertEqual(experiment.operating_conditions[-3]["period"], period_0) + self.assertEqual(experiment.operating_conditions[-3]["tags"], None) np.testing.assert_array_equal( experiment.operating_conditions[-2]["dc_data"], drive_cycle_1 ) self.assertEqual(experiment.operating_conditions[-2]["type"], "voltage") self.assertEqual(experiment.operating_conditions[-2]["time"], time_1) self.assertEqual(experiment.operating_conditions[-2]["period"], period_1) + self.assertEqual(experiment.operating_conditions[-2]["tags"], None) np.testing.assert_array_equal( experiment.operating_conditions[-1]["dc_data"], drive_cycle_2 ) self.assertEqual(experiment.operating_conditions[-1]["type"], "power") self.assertEqual(experiment.operating_conditions[-1]["time"], time_2) self.assertEqual(experiment.operating_conditions[-1]["period"], period_2) + self.assertEqual(experiment.operating_conditions[-1]["tags"], None) self.assertEqual(experiment.period, 20) def test_read_strings_cccv_combined(self): @@ -217,6 +233,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Discharge at C/20 for 0.5 hours", "events": None, + "tags": None, }, { "type": "CCCV", @@ -227,6 +244,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Charge at 0.5 C until 1V then hold at 1V until C/50", "events": {"C-rate input [-]": 0.02, "type": "C-rate"}, + "tags": None, }, { "C-rate input [-]": 0.05, @@ -236,6 +254,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Discharge at C/20 for 0.5 hours", "events": None, + "tags": None, }, ], ) @@ -259,6 +278,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Charge at 0.5 C until 2V", "events": {"Voltage input [V]": 2, "type": "voltage"}, + "tags": None, }, { "Voltage input [V]": 1, @@ -268,6 +288,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Hold at 1V until C/50", "events": {"C-rate input [-]": 0.02, "type": "C-rate"}, + "tags": None, }, ], ) @@ -289,6 +310,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Charge at 0.5 C for 2 minutes", "events": None, + "tags": None, }, { "Voltage input [V]": 1, @@ -298,6 +320,7 @@ def test_read_strings_cccv_combined(self): "dc_data": None, "string": "Hold at 1V until C/50", "events": {"C-rate input [-]": 0.02, "type": "C-rate"}, + "tags": None, }, ], ) @@ -321,6 +344,7 @@ def test_cycle_unpacking(self): "dc_data": None, "string": "Discharge at C/20 for 0.5 hours", "events": None, + "tags": None, }, { "C-rate input [-]": -0.2, @@ -330,6 +354,7 @@ def test_cycle_unpacking(self): "dc_data": None, "string": "Charge at C/5 for 45 minutes", "events": None, + "tags": None, }, { "C-rate input [-]": 0.05, @@ -339,6 +364,7 @@ def test_cycle_unpacking(self): "dc_data": None, "string": "Discharge at C/20 for 0.5 hours", "events": None, + "tags": None, }, { "C-rate input [-]": -0.2, @@ -348,6 +374,7 @@ def test_cycle_unpacking(self): "dc_data": None, "string": "Charge at C/5 for 45 minutes", "events": None, + "tags": None, }, ], ) @@ -447,6 +474,31 @@ def test_termination(self): ["Discharge at 1 C for 20 seconds"], termination="1 capacity" ) + def test_search_tag(self): + experiment = pybamm.Experiment( + [ + ("Discharge at 1C for 0.5 hours [tag1]",), + "Discharge at C/20 for 0.5 hours [tag2,tag3]", + ( + "Charge at 0.5 C for 45 minutes [tag2]", + "Discharge at 1 A for 0.5 hours [tag3]", + ), + "Charge at 200 mA for 45 minutes (1 minute period) [tag5]", + ( + "Discharge at 1W for 0.5 hours [tag4]", + "Charge at 200mW for 45 minutes [tag4]", + ), + "Rest for 10 minutes (5 minute period) [tag1,tag3,tag4]", + ] + ) + + self.assertEqual(experiment.search_tag("tag1"), [0, 5]) + self.assertEqual(experiment.search_tag("tag2"), [1, 2]) + self.assertEqual(experiment.search_tag("tag3"), [1, 2, 5]) + self.assertEqual(experiment.search_tag("tag4"), [4, 5]) + self.assertEqual(experiment.search_tag("tag5"), [3]) + self.assertEqual(experiment.search_tag("no_tag"), []) + if __name__ == "__main__": print("Add -v for more debug output")