Skip to content

Commit f641985

Browse files
committed
#1839 rename timestamp to start_time and allow only datetime (Valentin's comments)
1 parent 7bbba9a commit f641985

File tree

8 files changed

+93
-113
lines changed

8 files changed

+93
-113
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
- Enable multithreading in IDAKLU solver ([#2947](https://github.com/pybamm-team/PyBaMM/pull/2947))
1212
- If a solution contains cycles and steps, the cycle number and step number are now saved when `solution.save_data()` is called ([#2931](https://github.com/pybamm-team/PyBaMM/pull/2931))
13-
- Experiments can now be given a timestamp to trigger when each step should start ([#2616](https://github.com/pybamm-team/PyBaMM/pull/2616))
13+
- Experiments can now be given a `start_time` to define when each step should be triggered ([#2616](https://github.com/pybamm-team/PyBaMM/pull/2616))
1414

1515
## Optimizations
1616

examples/notebooks/timestamped_experiments.ipynb renamed to examples/notebooks/experiments-start-time.ipynb

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"id": "regional-bedroom",
77
"metadata": {},
88
"source": [
9-
"# Timestamped experiments"
9+
"# Experiments with `start_time`"
1010
]
1111
},
1212
{
@@ -112,7 +112,7 @@
112112
"id": "0fdfced4",
113113
"metadata": {},
114114
"source": [
115-
"However, if we want to represent a realistic user case we might certain experiments to be run at a certain time instead, even if that means cutting short the previous step. In this case we can pass a timestamp as a keyword argument in the `pybamm.step.string` method. The timestamp can be passed as a `datetime.datetime` object or as a string in one of the supported formats for the date. Date can be passed either as `Day 1` or `2023-01-01`, and time can be passed either as `08:00:00` or `08:00`."
115+
"However, if we want to represent a realistic user case we might certain experiments to be run at a certain time instead, even if that means cutting short the previous step. In this case we can pass a starting time as a keyword argument in the `pybamm.step.string` method. The `start_time` should be passed as a `datetime.datetime` object."
116116
]
117117
},
118118
{
@@ -151,9 +151,9 @@
151151
"\n",
152152
"experiment = pybamm.Experiment(\n",
153153
" [\n",
154-
" s(\"Discharge at 1C for 1 hour\", timestamp=\"Day 1 08:00:00\"),\n",
155-
" s(\"Charge at C/3 for 10 minutes\", timestamp=\"Day 1 08:30:00\"),\n",
156-
" s(\"Discharge at C/2 for 30 minutes\", timestamp=\"Day 1 09:00:00\"),\n",
154+
" s(\"Discharge at 1C for 1 hour\", start_time=\"Day 1 08:00:00\"),\n",
155+
" s(\"Charge at C/3 for 10 minutes\", start_time=\"Day 1 08:30:00\"),\n",
156+
" s(\"Discharge at C/2 for 30 minutes\", start_time=\"Day 1 09:00:00\"),\n",
157157
" s(\"Rest for 1 hour\"),\n",
158158
" ]\n",
159159
")\n",
@@ -168,7 +168,7 @@
168168
"id": "edfa4c9f",
169169
"metadata": {},
170170
"source": [
171-
"In the example above, we note that the first step (1C discharge) is cut short as the second step (C/3 charge) timestamp occurs before the end of the first step. On the other hand, an additional resting period is added after the second step as the third step (C/2 discharge) timestamp is 20 minutes later than the end of the second step. The final step does not have a timestamp so it is triggered immediately after the previous step. Note that if timestamps are used in an experiment, the first step should always have a timestamp, otherwise the solver will throw an error."
171+
"In the example above, we note that the first step (1C discharge) is cut short as the second step (C/3 charge) start time occurs before the end of the first step. On the other hand, an additional resting period is added after the second step as the third step (C/2 discharge) start time is 20 minutes later than the end of the second step. The final step does not have a start time so it is triggered immediately after the previous step. Note that if the argument `start_time` is used in an experiment, the first step should always have a `start_time`, otherwise the solver will throw an error."
172172
]
173173
},
174174
{
@@ -221,7 +221,7 @@
221221
"name": "python",
222222
"nbconvert_exporter": "python",
223223
"pygments_lexer": "ipython3",
224-
"version": "3.9.16"
224+
"version": "3.9.17"
225225
},
226226
"toc": {
227227
"base_numbering": 1,

pybamm/experiment/experiment.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def __init__(
8383
self.operating_conditions_cycles = operating_conditions_cycles
8484
self.cycle_lengths = [len(cycle) for cycle in operating_conditions_cycles]
8585

86-
operating_conditions_steps_unprocessed = self._set_next_timestamp(
86+
operating_conditions_steps_unprocessed = self._set_next_start_time(
8787
[cond for cycle in operating_conditions_cycles for cond in cycle]
8888
)
8989

@@ -104,15 +104,15 @@ def __init__(
104104
processed_steps[step] for step in operating_conditions_steps_unprocessed
105105
]
106106

107-
self.initial_timestamp = self.operating_conditions_steps[0].timestamp
107+
self.initial_start_time = self.operating_conditions_steps[0].start_time
108108

109109
if (
110-
self.operating_conditions_steps[0].end_timestamp is not None
111-
and self.initial_timestamp is None
110+
self.operating_conditions_steps[0].end_time is not None
111+
and self.initial_start_time is None
112112
):
113113
raise ValueError(
114-
"When using timestamped experiments, the first step must have a "
115-
"timestamp to define the initial time."
114+
"When using experiments with `start_time`, the first step must have a "
115+
"`start_time`."
116116
)
117117

118118
self.termination_string = termination
@@ -195,12 +195,12 @@ def search_tag(self, tag):
195195

196196
return cycles
197197

198-
def _set_next_timestamp(self, operating_conditions):
198+
def _set_next_start_time(self, operating_conditions):
199199
if all(isinstance(i, str) for i in operating_conditions):
200200
return operating_conditions
201201

202-
end_timestamp = None
203-
next_timestamp = None
202+
end_time = None
203+
next_start_time = None
204204

205205
for op in reversed(operating_conditions):
206206
if isinstance(op, str):
@@ -210,11 +210,11 @@ def _set_next_timestamp(self, operating_conditions):
210210
"Operating conditions should be strings or _Step objects"
211211
)
212212

213-
op.next_timestamp = next_timestamp
214-
op.end_timestamp = end_timestamp
213+
op.next_start_time = next_start_time
214+
op.end_time = end_time
215215

216-
next_timestamp = op.timestamp
217-
if next_timestamp:
218-
end_timestamp = next_timestamp
216+
next_start_time = op.start_time
217+
if next_start_time:
218+
end_time = next_start_time
219219

220220
return operating_conditions

pybamm/simulation.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -257,8 +257,8 @@ def set_up_and_parameterise_model_for_experiment(self):
257257
)
258258
self.experiment_unique_steps_to_model[repr(op)] = parameterised_model
259259

260-
# Set up rest model if experiment has timestamps
261-
if self.experiment.initial_timestamp:
260+
# Set up rest model if experiment has start times
261+
if self.experiment.initial_start_time:
262262
new_model = self.model.new_copy()
263263
# Update parameter values
264264
new_parameter_values = self.parameter_values.copy()
@@ -693,14 +693,14 @@ def solve(
693693

694694
start_time = current_solution.t[-1]
695695

696-
# If step has an end timestamp, dt must take that into account
697-
if op_conds.end_timestamp:
696+
# If step has an end time, dt must take that into account
697+
if op_conds.end_time:
698698
dt = min(
699699
op_conds.duration,
700700
(
701-
op_conds.end_timestamp
701+
op_conds.end_time
702702
- (
703-
self.experiment.initial_timestamp
703+
self.experiment.initial_start_time
704704
+ timedelta(seconds=float(start_time))
705705
)
706706
).total_seconds(),
@@ -751,11 +751,11 @@ def solve(
751751
step_termination = step_solution.termination
752752

753753
# Add a padding rest step if necessary
754-
if op_conds.next_timestamp is not None:
754+
if op_conds.next_start_time is not None:
755755
rest_time = (
756-
op_conds.next_timestamp
756+
op_conds.next_start_time
757757
- (
758-
self.experiment.initial_timestamp
758+
self.experiment.initial_start_time
759759
+ timedelta(seconds=float(step_solution.t[-1]))
760760
)
761761
).total_seconds()

pybamm/step/_steps_util.py

Lines changed: 12 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def __init__(
6666
period=None,
6767
temperature=None,
6868
tags=None,
69-
timestamp=None,
69+
start_time=None,
7070
description=None,
7171
):
7272
self.type = typ
@@ -83,8 +83,8 @@ def __init__(
8383
self.args += f", temperature={temperature}"
8484
if tags:
8585
self.args += f", tags={tags}"
86-
if timestamp:
87-
self.args += f", timestamp={timestamp}"
86+
if start_time:
87+
self.args += f", start_time={start_time}"
8888
if description:
8989
self.args += f", description={description}"
9090

@@ -144,9 +144,12 @@ def __init__(
144144
tags = [tags]
145145
self.tags = tags
146146

147-
self.timestamp = _process_timestamp(timestamp)
148-
self.next_timestamp = None
149-
self.end_timestamp = None
147+
if start_time is None or isinstance(start_time, datetime):
148+
self.start_time = start_time
149+
else:
150+
raise TypeError("`start_time` should be a datetime.datetime object")
151+
self.next_start_time = None
152+
self.end_time = None
150153

151154
def __str__(self):
152155
if self.description is not None:
@@ -174,16 +177,16 @@ def to_dict(self):
174177
"period": self.period,
175178
"temperature": self.temperature,
176179
"tags": self.tags,
177-
"timestamp": self.timestamp,
180+
"start_time": self.start_time,
178181
"description": self.description,
179182
}
180183

181184
def __eq__(self, other):
182185
return (
183186
isinstance(other, _Step)
184187
and self.__repr__() == other.__repr__()
185-
and self.next_timestamp == other.next_timestamp
186-
and self.end_timestamp == other.end_timestamp
188+
and self.next_start_time == other.next_start_time
189+
and self.end_time == other.end_time
187190
)
188191

189192
def __hash__(self):
@@ -273,27 +276,3 @@ def _convert_electric(value_string):
273276
f"units must be 'A', 'V', 'W', 'Ohm', or 'C'. For example: {_examples}"
274277
)
275278
return typ, value
276-
277-
278-
datetime_formats = [
279-
"Day %j %H:%M:%S",
280-
"Day %j %H:%M",
281-
"%Y-%m-%d %H:%M:%S",
282-
"%Y-%m-%d %H:%M",
283-
]
284-
285-
286-
def _process_timestamp(timestamp):
287-
if timestamp is None or isinstance(timestamp, datetime):
288-
return timestamp
289-
else:
290-
for format in datetime_formats:
291-
try:
292-
return datetime.strptime(timestamp, format)
293-
except ValueError:
294-
pass
295-
296-
raise ValueError(
297-
f"The timestamp [{timestamp}] does not match any "
298-
"of the supported formats."
299-
)

tests/unit/test_experiments/test_experiment.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_cycle_unpacking(self):
2828
"description": "Discharge at C/20 for 0.5 hours",
2929
"termination": [],
3030
"tags": [],
31-
"timestamp": None,
31+
"start_time": None,
3232
},
3333
{
3434
"value": -0.2,
@@ -39,7 +39,7 @@ def test_cycle_unpacking(self):
3939
"description": "Charge at C/5 for 45 minutes",
4040
"termination": [],
4141
"tags": [],
42-
"timestamp": None,
42+
"start_time": None,
4343
},
4444
{
4545
"value": 0.05,
@@ -50,7 +50,7 @@ def test_cycle_unpacking(self):
5050
"description": "Discharge at C/20 for 0.5 hours",
5151
"termination": [],
5252
"tags": [],
53-
"timestamp": None,
53+
"start_time": None,
5454
},
5555
{
5656
"value": -0.2,
@@ -61,7 +61,7 @@ def test_cycle_unpacking(self):
6161
"description": "Charge at C/5 for 45 minutes",
6262
"termination": [],
6363
"tags": [],
64-
"timestamp": None,
64+
"start_time": None,
6565
},
6666
],
6767
)
@@ -172,29 +172,32 @@ def test_search_tag(self):
172172
self.assertEqual(experiment.search_tag("tag5"), [3])
173173
self.assertEqual(experiment.search_tag("no_tag"), [])
174174

175-
def test_no_initial_timestamp(self):
175+
def test_no_initial_start_time(self):
176176
s = pybamm.step.string
177-
with self.assertRaisesRegex(ValueError, "first step must have a timestamp"):
177+
with self.assertRaisesRegex(ValueError, "first step must have a `start_time`"):
178178
pybamm.Experiment(
179-
[s("Rest for 1 hour"), s("Rest for 1 hour", timestamp="Day 1 08:01:05")]
179+
[
180+
s("Rest for 1 hour"),
181+
s("Rest for 1 hour", start_time=datetime(2023, 1, 1, 8, 0)),
182+
]
180183
)
181184

182-
def test_set_next_timestamp(self):
183-
# Defined dummy experiment to access _set_next_timestamp
185+
def test_set_next_start_time(self):
186+
# Defined dummy experiment to access _set_next_start_time
184187
experiment = pybamm.Experiment(["Rest for 1 hour"])
185188
raw_op = [
186189
pybamm.step._Step(
187-
"current", 1, duration=3600, timestamp=datetime(2023, 1, 1, 8, 0)
190+
"current", 1, duration=3600, start_time=datetime(2023, 1, 1, 8, 0)
188191
),
189192
pybamm.step._Step(
190-
"current", 1, duration=3600, timestamp=datetime(2023, 1, 1, 12, 0)
193+
"current", 1, duration=3600, start_time=datetime(2023, 1, 1, 12, 0)
191194
),
192-
pybamm.step._Step("current", 1, duration=3600, timestamp=None),
195+
pybamm.step._Step("current", 1, duration=3600, start_time=None),
193196
pybamm.step._Step(
194-
"current", 1, duration=3600, timestamp=datetime(2023, 1, 1, 15, 0)
197+
"current", 1, duration=3600, start_time=datetime(2023, 1, 1, 15, 0)
195198
),
196199
]
197-
processed_op = experiment._set_next_timestamp(raw_op)
200+
processed_op = experiment._set_next_start_time(raw_op)
198201

199202
expected_next = [
200203
datetime(2023, 1, 1, 12, 0),
@@ -212,8 +215,8 @@ def test_set_next_timestamp(self):
212215

213216
for next, end, op in zip(expected_next, expected_end, processed_op):
214217
# useful form for debugging
215-
self.assertEqual(op.next_timestamp, next)
216-
self.assertEqual(op.end_timestamp, end)
218+
self.assertEqual(op.next_start_time, next)
219+
self.assertEqual(op.end_time, end)
217220

218221

219222
if __name__ == "__main__":

0 commit comments

Comments
 (0)