Skip to content

Commit

Permalink
Merge pull request #1309 from lonnbornj/issue-1195-track-cycles-in-so…
Browse files Browse the repository at this point in the history
…lution

Issue 1195 track cycles in solution
  • Loading branch information
valentinsulzer committed Dec 31, 2020
2 parents 93bb12b + 692b19d commit c573c78
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Features

- Added option to express experiments (and extract solutions) in terms of cycles of operating condition ([#1309](https://github.com/pybamm-team/PyBaMM/pull/1309))
- Reformatted the `BasicDFNHalfCell` to be consistent with the other models ([#1282](https://github.com/pybamm-team/PyBaMM/pull/1282))
- Added option to make the total interfacial current density a state ([#1280](https://github.com/pybamm-team/PyBaMM/pull/1280))
- Added functionality to initialize a model using the solution from another model ([#1278](https://github.com/pybamm-team/PyBaMM/pull/1278))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,11 @@
"source": [
"experiment = pybamm.Experiment(\n",
" [\n",
" \"Discharge at C/10 for 10 hours or until 3.3 V\",\n",
" (\"Discharge at C/10 for 10 hours or until 3.3 V\",\n",
" \"Rest for 1 hour\",\n",
" \"Charge at 1 A until 4.1 V\",\n",
" \"Hold at 4.1 V until 50 mA\",\n",
" \"Rest for 1 hour\",\n",
" \"Rest for 1 hour\"),\n",
" ] * 3\n",
")"
]
Expand All @@ -60,7 +60,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"In this case, the experiment consists of a cycle of constant current C/10 discahrge, a one hour rest, a constant current (1 A) constant voltage (4.1 V) and another one hour rest, all of it repeated three times (notice the `* 3`).\n",
"A cycle is defined by a tuple of operating instructions. In this case, the experiment consists of a cycle of constant current C/10 discharge, a one hour rest, a constant current (1 A) constant voltage (4.1 V) and another one hour rest, all of it repeated three times (notice the * 3).\n",
"\n",
"Then we can choose our model"
]
Expand Down Expand Up @@ -146,33 +146,30 @@
"\n",
"Optionally, each instruction can contain at the end the expression \"(x minute period)\" in which the period at which to record the simulation outputs during that instruction. To change the period for the whole experiment we can pass it as a keyword argument in the experiment.\n",
"\n",
"Additionally, we can use the operators `+` and `*` to combine and repeat lists:"
"Additionally, we can use the operators `+` and `*` on lists in order to combine and repeat cycles:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 4,
"metadata": {},
"outputs": [
{
"output_type": "execute_result",
"data": {
"text/plain": [
"['Discharge at 1C for 0.5 hours',\n",
" 'Discharge at C/20 for 0.5 hours',\n",
" 'Discharge at 1C for 0.5 hours',\n",
" 'Discharge at C/20 for 0.5 hours',\n",
" 'Discharge at 1C for 0.5 hours',\n",
" 'Discharge at C/20 for 0.5 hours',\n",
" 'Charge at 0.5 C for 45 minutes']"
"[('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),\n",
" ('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),\n",
" ('Discharge at 1C for 0.5 hours', 'Discharge at C/20 for 0.5 hours'),\n",
" ('Charge at 0.5 C for 45 minutes',)]"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
"execution_count": 4
}
],
"source": [
"[\"Discharge at 1C for 0.5 hours\", \"Discharge at C/20 for 0.5 hours\"] * 3 + [\"Charge at 0.5 C for 45 minutes\"]"
"[(\"Discharge at 1C for 0.5 hours\", \"Discharge at C/20 for 0.5 hours\")] * 3 + [(\"Charge at 0.5 C for 45 minutes\",)]"
]
},
{
Expand Down Expand Up @@ -206,9 +203,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.8"
"version": "3.8.2-final"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
}
14 changes: 8 additions & 6 deletions examples/scripts/experimental_protocols/cccv.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
pybamm.set_logging_level("INFO")
experiment = pybamm.Experiment(
[
"Discharge at C/10 for 10 hours or until 3.3 V",
"Rest for 1 hour",
"Charge at 1 A until 4.1 V",
"Hold at 4.1 V until 50 mA",
"Rest for 1 hour",
(
"Discharge at C/10 for 10 hours or until 3.3 V",
"Rest for 1 hour",
"Charge at 1 A until 4.1 V",
"Hold at 4.1 V until 50 mA",
"Rest for 1 hour",
),
]
* 3
)
Expand All @@ -23,7 +25,7 @@
fig, ax = plt.subplots()
for i in range(3):
# Extract sub solutions
sol = sim.solution.sub_solutions[i * 5]
sol = sim.solution.cycles[i][0]
# Extract variables
t = sol["Time [h]"].entries
V = sol["Terminal voltage [V]"].entries
Expand Down
4 changes: 3 additions & 1 deletion examples/scripts/experimental_protocols/gitt.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import pybamm

pybamm.set_logging_level("INFO")
experiment = pybamm.Experiment(["Discharge at C/20 for 1 hour", "Rest for 1 hour"] * 20)
experiment = pybamm.Experiment(
[("Discharge at C/20 for 1 hour", "Rest for 1 hour")] * 20
)
model = pybamm.lithium_ion.DFN()
sim = pybamm.Simulation(model, experiment=experiment, solver=pybamm.CasadiSolver())
sim.solve()
Expand Down
43 changes: 32 additions & 11 deletions pybamm/experiments/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,35 @@ class Experiment:

def __init__(self, operating_conditions, parameters=None, period="1 minute"):
self.period = self.convert_time_to_seconds(period.split())
operating_conditions_cycles = []
for cycle in operating_conditions:
# Check types and convert strings to 1-tuples
if (isinstance(cycle, tuple) or isinstance(cycle, str)) and all(
[isinstance(cond, str) for cond in cycle]
):
operating_conditions_cycles.append(
cycle if isinstance(cycle, tuple) else (cycle,)
)
else:
try:
# Condition is not a string
badly_typed_conditions = [
cond for cond in cycle if not isinstance(cond, str)
]
except TypeError:
# Cycle is not a tuple or string
badly_typed_conditions = []
badly_typed_conditions = badly_typed_conditions or [cycle]
raise TypeError(
"""Operating conditions should be strings or tuples of strings, not {}. For example: {}
""".format(
type(badly_typed_conditions[0]), examples
)
)
self.cycle_lengths = [len(cycle) for cycle in operating_conditions_cycles]
operating_conditions = [
cond for cycle in operating_conditions_cycles for cond in cycle
]
self.operating_conditions_strings = operating_conditions
self.operating_conditions, self.events = self.read_operating_conditions(
operating_conditions
Expand Down Expand Up @@ -78,17 +107,9 @@ def read_operating_conditions(self, operating_conditions):
converted_operating_conditions = []
events = []
for cond in operating_conditions:
if isinstance(cond, str):
next_op, next_event = self.read_string(cond)
converted_operating_conditions.append(next_op)
events.append(next_event)
else:
raise TypeError(
"""Operating conditions should be strings, not {}. For example: {}
""".format(
type(cond), examples
)
)
next_op, next_event = self.read_string(cond)
converted_operating_conditions.append(next_op)
events.append(next_event)

return converted_operating_conditions, events

Expand Down
13 changes: 13 additions & 0 deletions pybamm/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,19 @@ def solve(
"or reducing the period.\n\n"
)
break
if hasattr(self.solution, "_sub_solutions"):
# Construct solution.cycles (a list of tuples) from sub_solutions
self.solution.cycles = []
for cycle_num, cycle_length in enumerate(self.experiment.cycle_lengths):
cycle_start_idx = sum(self.experiment.cycle_lengths[0:cycle_num])
self.solution.cycles.append(
tuple(
[
self.solution.sub_solutions[cycle_start_idx + idx]
for idx in range(cycle_length)
]
)
)
pybamm.logger.info(
"Finish experiment simulation, took {}".format(timer.time())
)
Expand Down
25 changes: 24 additions & 1 deletion tests/unit/test_experiments/test_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ def test_read_strings_repeat(self):
)
self.assertEqual(experiment.period, 60)

def test_cycle_unpacking(self):
experiment = pybamm.Experiment(
[
("Discharge at C/20 for 0.5 hours", "Charge at C/5 for 45 minutes"),
("Discharge at C/20 for 0.5 hours"),
"Charge at C/5 for 45 minutes",
]
)
self.assertEqual(
experiment.operating_conditions,
[
(0.05, "C", 1800.0, 60.0),
(-0.2, "C", 2700.0, 60.0),
(0.05, "C", 1800.0, 60.0),
(-0.2, "C", 2700.0, 60.0),
],
)
self.assertEqual(experiment.cycle_lengths, [2, 1, 1])

def test_str_repr(self):
conds = ["Discharge at 1 C for 20 seconds", "Charge at 0.5 W for 10 minutes"]
experiment = pybamm.Experiment(conds)
Expand All @@ -94,9 +113,13 @@ def test_str_repr(self):

def test_bad_strings(self):
with self.assertRaisesRegex(
TypeError, "Operating conditions should be strings"
TypeError, "Operating conditions should be strings or tuples of strings"
):
pybamm.Experiment([1, 2, 3])
with self.assertRaisesRegex(
TypeError, "Operating conditions should be strings or tuples of strings"
):
pybamm.Experiment([(1, 2, 3)])
with self.assertRaisesRegex(ValueError, "Operating conditions must contain"):
pybamm.Experiment(["Discharge at 1 A at 2 hours"])
with self.assertRaisesRegex(ValueError, "instruction must be"):
Expand Down
17 changes: 17 additions & 0 deletions tests/unit/test_solvers/test_solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ def test_append(self):
sol1.sub_solutions[1].inputs["a"], 2 * np.ones_like(t2)[np.newaxis, :]
)

def test_cycles(self):
model = pybamm.lithium_ion.DFN()
experiment = pybamm.Experiment(
[
("Discharge at C/20 for 0.5 hours", "Charge at C/20 for 15 minutes"),
("Discharge at C/20 for 0.5 hours", "Charge at C/20 for 15 minutes"),
]
)
sim = pybamm.Simulation(model, experiment=experiment)
sim.solve()
num_cycles = len(experiment.cycle_lengths)
for idx, sub_solution in enumerate(sim.solution.sub_solutions):
cycle_sub_solution = sim.solution.cycles[idx // num_cycles][
idx % num_cycles
]
self.assertEqual(cycle_sub_solution, sub_solution)

def test_total_time(self):
sol = pybamm.Solution([], None)
sol.set_up_time = 0.5
Expand Down

0 comments on commit c573c78

Please sign in to comment.