Skip to content

Commit c9e2a45

Browse files
Merge pull request #2518 from pybamm-team/issue-2358-add-temperature-to-experiment
Issue 2358 add temperature to experiment
2 parents 728eda4 + e8d78a8 commit c9e2a45

File tree

9 files changed

+436
-265
lines changed

9 files changed

+436
-265
lines changed

CHANGELOG.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@
22

33
## Features
44

5+
- Added temperature control to experiment class. ([#2518])(https://github.com/pybamm-team/PyBaMM/pull/2518)
56
- The "particle size" option can now be a tuple to allow different behaviour in each electrode([#2672](https://github.com/pybamm-team/PyBaMM/pull/2672)).
67

8+
## Bug fixes
9+
10+
- Fixed the length scaling for the first dimension of r-R plots ([#2663](https://github.com/pybamm-team/PyBaMM/pull/2663)).
11+
712
# [v23.1](https://github.com/pybamm-team/PyBaMM/tree/v23.1) - 2023-01-31
813

914
## Features
@@ -17,7 +22,6 @@
1722

1823
- Fixed a bug where the solid phase conductivity was double-corrected for tortuosity when loading parameters from a BPX file ([#2638](https://github.com/pybamm-team/PyBaMM/pull/2638)).
1924
- Changed termination from "success" to "final time" for algebraic solvers to match ODE/DAE solvers ([#2613](https://github.com/pybamm-team/PyBaMM/pull/2613)).
20-
- Fixed the length scaling for the first dimension of r-R plots ([#2663](https://github.com/pybamm-team/PyBaMM/pull/2663)).
2125

2226
# [v22.12](https://github.com/pybamm-team/PyBaMM/tree/v22.12) - 2022-12-31
2327

examples/scripts/run_ecm.py

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,22 @@
22

33
pybamm.set_logging_level("INFO")
44

5-
# options = {"number of rc elements": 2}
6-
options = {}
7-
model = pybamm.equivalent_circuit.Thevenin(options=options)
8-
9-
parameter_values = model.default_parameter_values
10-
# parameter_values.update(
11-
# {
12-
# "R2 [Ohm]": 0.3e-3,
13-
# "C2 [F]": 1000 / 0.3e-3,
14-
# "Element-2 initial overpotential [V]": 0,
15-
# },
16-
# check_already_exists=False,
17-
# )
5+
model = pybamm.equivalent_circuit.Thevenin()
186

197
experiment = pybamm.Experiment(
208
[
219
(
22-
"Discharge at C/10 for 10 hours or until 3.3 V",
23-
"Rest for 1 hour",
24-
"Charge at 100 A until 4.1 V (1 second period)",
25-
"Hold at 4.1 V until 5 A (1 seconds period)",
26-
"Rest for 1 hour",
10+
"Discharge at C/10 for 10 hours or until 3.3 V at 15oC",
11+
"Rest for 30 minutes at 15oC",
12+
"Rest for 2 hours at 35oC",
13+
"Charge at 100 A until 4.1 V at 35oC (1 second period)",
14+
"Hold at 4.1 V until 5 A at 35oC (1 seconds period)",
15+
"Rest for 30 minutes at 35oC",
16+
"Rest for 1 hour at 25oC",
2717
),
2818
]
2919
)
3020

31-
sim = pybamm.Simulation(model, experiment=experiment, parameter_values=parameter_values)
21+
sim = pybamm.Simulation(model, experiment=experiment)
3222
sim.solve()
33-
sim.plot(
34-
output_variables=[
35-
"SoC",
36-
"Open circuit voltage [V]",
37-
"Current [A]",
38-
"Cell temperature [degC]",
39-
"Entropic change [V/K]",
40-
"R0 [Ohm]",
41-
"R1 [Ohm]",
42-
"C1 [F]",
43-
]
44-
)
23+
sim.plot()

pybamm/experiments/experiment.py

Lines changed: 99 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,28 @@
33
#
44

55
import numpy as np
6+
import re
7+
import pybamm
68

79
examples = """
810
9-
Discharge at 1C for 0.5 hours,
10-
Discharge at C/20 for 0.5 hours,
11-
Charge at 0.5 C for 45 minutes,
12-
Discharge at 1 A for 90 seconds,
13-
Charge at 200mA for 45 minutes (1 minute period),
14-
Discharge at 1 W for 0.5 hours,
15-
Charge at 200 mW for 45 minutes,
16-
Rest for 10 minutes (5 minute period),
17-
Hold at 1 V for 20 seconds,
18-
Charge at 1 C until 4.1V,
19-
Hold at 4.1 V until 50 mA,
20-
Hold at 3V until C/50,
21-
Run US06 (A),
22-
Run US06 (A) for 20 seconds,
23-
Run US06 (V) for 45 minutes,
24-
Run US06 (W) for 2 hours,
11+
"Discharge at 1C for 0.5 hours at 27oC",
12+
"Discharge at C/20 for 0.5 hours at 29oC",
13+
"Charge at 0.5 C for 45 minutes at -5oC",
14+
"Discharge at 1 A for 0.5 hours at -5.1oC",
15+
"Charge at 200 mA for 45 minutes at 10.2oC (1 minute period)",
16+
"Discharge at 1W for 0.5 hours at -10.4oC",
17+
"Charge at 200mW for 45 minutes",
18+
"Rest for 10 minutes (5 minute period)",
19+
"Hold at 1V for 20 seconds",
20+
"Charge at 1 C until 4.1V",
21+
"Hold at 4.1 V until 50mA",
22+
"Hold at 3V until C/50",
23+
"Discharge at C/3 for 2 hours or until 2.5 V at 26oC",
24+
"Run US06 (A) at -5oC",
25+
"Run US06 (V) for 5 minutes",
26+
"Run US06 (W) for 0.5 hours",
27+
2528
"""
2629

2730

@@ -31,12 +34,18 @@ class Experiment:
3134
list of operating conditions should be passed in. Each operating condition should
3235
be of the form "Do this for this long" or "Do this until this happens". For example,
3336
"Charge at 1 C for 1 hour", or "Charge at 1 C until 4.2 V", or "Charge at 1 C for 1
34-
hour or until 4.2 V". The instructions can be of the form "(Dis)charge at x A/C/W",
35-
"Rest", or "Hold at x V". The running time should be a time in seconds, minutes or
37+
hour or until 4.2 V at 25oC". The instructions can be of the form
38+
"(Dis)charge at x A/C/W", "Rest", or "Hold at x V until y A at z oC". The running
39+
time should be a time in seconds, minutes or
3640
hours, e.g. "10 seconds", "3 minutes" or "1 hour". The stopping conditions should be
3741
a circuit state, e.g. "1 A", "C/50" or "3 V". The parameter drive_cycles is
3842
mandatory to run drive cycle. For example, "Run x", then x must be the key
39-
of drive_cycles dictionary.
43+
of drive_cycles dictionary. The temperature should be provided after the stopping
44+
condition but before the period, e.g. "1 A at 25 oC (1 second period)". It is
45+
not essential to provide a temperature and a global temperature can be set either
46+
from within the paramter values of passing a temperature to this experiment class.
47+
If the temperature is not specified in a line, then the global temperature is used,
48+
even if another temperature has been set in an earlier line.
4049
4150
Parameters
4251
----------
@@ -45,6 +54,10 @@ class Experiment:
4554
period : string, optional
4655
Period (1/frequency) at which to record outputs. Default is 1 minute. Can be
4756
overwritten by individual operating conditions.
57+
temperature: float, optional
58+
The ambient air temperature in degrees Celsius at which to run the experiment.
59+
Default is None whereby the ambient temperature is taken from the parameter set.
60+
This value is overwritten if the temperature is specified in a step.
4861
termination : list, optional
4962
List of conditions under which to terminate the experiment. Default is None.
5063
drive_cycles : dict
@@ -60,6 +73,7 @@ def __init__(
6073
self,
6174
operating_conditions,
6275
period="1 minute",
76+
temperature=None,
6377
termination=None,
6478
drive_cycles={},
6579
cccv_handling="two-step",
@@ -71,12 +85,15 @@ def __init__(
7185
self.args = (
7286
operating_conditions,
7387
period,
88+
temperature,
7489
termination,
7590
drive_cycles,
7691
cccv_handling,
7792
)
7893

7994
self.period = self.convert_time_to_seconds(period.split())
95+
self.temperature = temperature
96+
8097
operating_conditions_cycles = []
8198
for cycle in operating_conditions:
8299
# Check types and convert strings to 1-tuples
@@ -163,16 +180,26 @@ def read_string(self, cond, drive_cycles):
163180
cond_CC, cond_CV = cond.split(" then ")
164181
op_CC = self.read_string(cond_CC, drive_cycles)
165182
op_CV = self.read_string(cond_CV, drive_cycles)
183+
184+
if op_CC["temperature"] != op_CV["temperature"]:
185+
raise ValueError(
186+
"The temperature for the CC and CV steps must be the same."
187+
f"Got {op_CC['temperature']} and {op_CV['temperature']}"
188+
f"from {op_CC['string']} and {op_CV['string']}"
189+
)
190+
166191
tag_CC = op_CC["tags"] or []
167192
tag_CV = op_CV["tags"] or []
168193
tags = list(np.unique(tag_CC + tag_CV))
169194
if len(tags) == 0:
170195
tags = None
196+
171197
outputs = {
172198
"type": "CCCV",
173199
"Voltage input [V]": op_CV["Voltage input [V]"],
174200
"time": op_CV["time"],
175201
"period": op_CV["period"],
202+
"temperature": op_CC["temperature"],
176203
"dc_data": None,
177204
"string": cond,
178205
"events": op_CV["events"],
@@ -198,6 +225,11 @@ def read_string(self, cond, drive_cycles):
198225
period = self.convert_time_to_seconds(time.split())
199226
else:
200227
period = self.period
228+
229+
# Temperature part of the condition is removed here
230+
unprocessed_cond = cond
231+
temperature, cond = self._read_and_drop_temperature(cond)
232+
201233
# Read instructions
202234
if "Run" in cond:
203235
cond_list = cond.split()
@@ -234,13 +266,17 @@ def read_string(self, cond, drive_cycles):
234266
cond_list = cond.split()
235267
idx_for = cond_list.index("for")
236268
idx_until = cond_list.index("or")
269+
237270
electric = self.convert_electric(cond_list[:idx_for])
271+
238272
time = self.convert_time_to_seconds(cond_list[idx_for + 1 : idx_until])
239273
events = self.convert_electric(cond_list[idx_until + 2 :])
274+
240275
elif "for" in cond:
241276
# e.g. for 3 hours
242277
cond_list = cond.split()
243278
idx = cond_list.index("for")
279+
244280
electric = self.convert_electric(cond_list[:idx])
245281
time = self.convert_time_to_seconds(cond_list[idx + 1 :])
246282
events = None
@@ -264,8 +300,9 @@ def read_string(self, cond, drive_cycles):
264300
**electric,
265301
"time": time,
266302
"period": period,
303+
"temperature": temperature,
267304
"dc_data": dc_data,
268-
"string": cond,
305+
"string": unprocessed_cond,
269306
"events": events,
270307
"tags": tags,
271308
}
@@ -329,6 +366,9 @@ def convert_electric(self, electric):
329366
raise ValueError(
330367
"Instruction must be 'discharge', 'charge', 'rest', 'hold' or "
331368
f"'Run'. For example: {examples}"
369+
""
370+
"The following instruction does not comply: "
371+
f"{instruction}"
332372
)
333373
elif len(electric) == 2:
334374
# e.g. 3 A, 4.1 V
@@ -377,6 +417,44 @@ def convert_electric(self, electric):
377417
)
378418
)
379419

420+
def _detect_mistyped_temperatures(self, cond):
421+
if "oC" in cond:
422+
raise ValueError(f"Temperature not written correctly on step: '{cond}'")
423+
424+
def _read_and_drop_temperature(self, cond):
425+
matches = re.findall(r"at\s-*\d+\.*\d*\s*oC", cond)
426+
427+
if len(matches) == 0:
428+
self._detect_mistyped_temperatures(cond)
429+
430+
if self.temperature is None:
431+
pybamm.logger.warning(
432+
"Temperature not found on step: "
433+
f"'{cond}', using temperature "
434+
"from parameter values."
435+
)
436+
437+
else:
438+
pybamm.logger.warning(
439+
f"Temperature not found on step: '{cond}', "
440+
f"using global temperature "
441+
f"({self.temperature}oC) instead"
442+
)
443+
444+
temperature = self.temperature
445+
reduced_cond = cond
446+
447+
elif len(matches) == 1:
448+
match = matches[0]
449+
numerical_part = re.findall(r"-*\d+\.*\d*", match)[0]
450+
temperature = float(numerical_part)
451+
reduced_cond = cond.replace(match, "")
452+
453+
else:
454+
raise ValueError(f"More than one temperature found on step: '{cond}'")
455+
456+
return temperature, reduced_cond
457+
380458
def convert_time_to_seconds(self, time_and_units):
381459
"""Convert a time in seconds, minutes or hours to a time in seconds"""
382460
time, units = time_and_units
@@ -442,7 +520,7 @@ def is_cccv(self, step, next_step):
442520
# e.g. step="Charge at 2.0 A until 4.2V"
443521
# next_step="Hold at 4.2V until C/50"
444522
if (
445-
step.startswith("Charge")
523+
(step.startswith("Charge") or step.startswith("Discharge"))
446524
and "until" in step
447525
and "V" in step
448526
and "Hold at " in next_step

pybamm/input/parameters/ecm/example_set.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,10 @@ def get_parameter_values():
7979
values = {
8080
"chemistry": "ecm",
8181
"Initial SoC": 0.5,
82-
"Initial cell temperature [degC]": 25,
83-
"Initial jig temperature [degC]": 25,
82+
"Initial temperature [K]": 25 + 273.15,
8483
"Cell capacity [A.h]": cell_capacity,
8584
"Nominal cell capacity [A.h]": cell_capacity,
86-
"Ambient temperature [degC]": 25,
85+
"Ambient temperature [K]": 25 + 273.15,
8786
"Current function [A]": 100,
8887
"Upper voltage cut-off [V]": 4.2,
8988
"Lower voltage cut-off [V]": 3.2,

pybamm/parameters/ecm_parameters.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,11 +42,14 @@ def _set_compatibility_parameters(self):
4242

4343
def _set_initial_condition_parameters(self):
4444
self.initial_soc = pybamm.Parameter("Initial SoC")
45-
self.initial_T_cell = pybamm.Parameter("Initial cell temperature [degC]")
46-
self.initial_T_jig = pybamm.Parameter("Initial jig temperature [degC]")
45+
self.initial_T_cell = pybamm.Parameter("Initial temperature [K]") - 273.15
46+
self.initial_T_jig = pybamm.Parameter("Initial temperature [K]") - 273.15
4747

4848
def T_amb(self, t):
49-
return pybamm.FunctionParameter("Ambient temperature [degC]", {"Time [s]": t})
49+
ambient_temperature_K = pybamm.FunctionParameter(
50+
"Ambient temperature [K]", {"Time [s]": t}
51+
)
52+
return ambient_temperature_K - 273.15
5053

5154
def ocv(self, soc):
5255
return pybamm.FunctionParameter("Open circuit voltage [V]", {"SoC": soc})

0 commit comments

Comments
 (0)