rtc-tools 2.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. rtc_tools-2.7.3.dist-info/METADATA +53 -0
  2. rtc_tools-2.7.3.dist-info/RECORD +50 -0
  3. rtc_tools-2.7.3.dist-info/WHEEL +5 -0
  4. rtc_tools-2.7.3.dist-info/entry_points.txt +3 -0
  5. rtc_tools-2.7.3.dist-info/licenses/COPYING.LESSER +165 -0
  6. rtc_tools-2.7.3.dist-info/top_level.txt +1 -0
  7. rtctools/__init__.py +5 -0
  8. rtctools/_internal/__init__.py +0 -0
  9. rtctools/_internal/alias_tools.py +188 -0
  10. rtctools/_internal/caching.py +25 -0
  11. rtctools/_internal/casadi_helpers.py +99 -0
  12. rtctools/_internal/debug_check_helpers.py +41 -0
  13. rtctools/_version.py +21 -0
  14. rtctools/data/__init__.py +4 -0
  15. rtctools/data/csv.py +150 -0
  16. rtctools/data/interpolation/__init__.py +3 -0
  17. rtctools/data/interpolation/bspline.py +31 -0
  18. rtctools/data/interpolation/bspline1d.py +169 -0
  19. rtctools/data/interpolation/bspline2d.py +54 -0
  20. rtctools/data/netcdf.py +467 -0
  21. rtctools/data/pi.py +1236 -0
  22. rtctools/data/rtc.py +228 -0
  23. rtctools/data/storage.py +343 -0
  24. rtctools/optimization/__init__.py +0 -0
  25. rtctools/optimization/collocated_integrated_optimization_problem.py +3208 -0
  26. rtctools/optimization/control_tree_mixin.py +221 -0
  27. rtctools/optimization/csv_lookup_table_mixin.py +462 -0
  28. rtctools/optimization/csv_mixin.py +300 -0
  29. rtctools/optimization/goal_programming_mixin.py +769 -0
  30. rtctools/optimization/goal_programming_mixin_base.py +1094 -0
  31. rtctools/optimization/homotopy_mixin.py +165 -0
  32. rtctools/optimization/initial_state_estimation_mixin.py +89 -0
  33. rtctools/optimization/io_mixin.py +320 -0
  34. rtctools/optimization/linearization_mixin.py +33 -0
  35. rtctools/optimization/linearized_order_goal_programming_mixin.py +235 -0
  36. rtctools/optimization/min_abs_goal_programming_mixin.py +385 -0
  37. rtctools/optimization/modelica_mixin.py +482 -0
  38. rtctools/optimization/netcdf_mixin.py +177 -0
  39. rtctools/optimization/optimization_problem.py +1302 -0
  40. rtctools/optimization/pi_mixin.py +292 -0
  41. rtctools/optimization/planning_mixin.py +19 -0
  42. rtctools/optimization/single_pass_goal_programming_mixin.py +676 -0
  43. rtctools/optimization/timeseries.py +56 -0
  44. rtctools/rtctoolsapp.py +131 -0
  45. rtctools/simulation/__init__.py +0 -0
  46. rtctools/simulation/csv_mixin.py +171 -0
  47. rtctools/simulation/io_mixin.py +195 -0
  48. rtctools/simulation/pi_mixin.py +255 -0
  49. rtctools/simulation/simulation_problem.py +1293 -0
  50. rtctools/util.py +241 -0
rtctools/data/rtc.py ADDED
@@ -0,0 +1,228 @@
1
+ import logging
2
+ import os
3
+ from collections import namedtuple
4
+
5
+ import defusedxml.ElementTree as DefusedElementTree
6
+
7
+ ts_ids = namedtuple("ids", "location_id parameter_id qualifier_id")
8
+ p_ids = namedtuple("ids", "model_id location_id parameter_id")
9
+
10
+ ns = {"fews": "http://www.wldelft.nl/fews", "pi": "http://www.wldelft.nl/fews/PI"}
11
+
12
+ logger = logging.getLogger("rtctools")
13
+
14
+
15
+ class DataConfig:
16
+ """
17
+ rtcDataConfig wrapper.
18
+
19
+ Used to map PI timeseries to RTC-Tools variable names.
20
+ """
21
+
22
+ def __init__(self, folder):
23
+ """
24
+ Parse rtcDataConfig file
25
+
26
+ :param folder: Folder in which rtcDataConfig.xml is located.
27
+ """
28
+ self.__variable_map = {}
29
+ self.__location_parameter_ids = {}
30
+ self.__parameter_map = {}
31
+ self.__model_parameter_ids = {}
32
+
33
+ path = os.path.join(folder, "rtcDataConfig.xml")
34
+ try:
35
+ tree = DefusedElementTree.parse(path)
36
+ root = tree.getroot()
37
+
38
+ timeseriess1 = root.findall("./*/fews:timeSeries", ns)
39
+ timeseriess2 = root.findall("./fews:timeSeries", ns)
40
+ timeseriess1.extend(timeseriess2)
41
+
42
+ for timeseries in timeseriess1:
43
+ pi_timeseries = timeseries.find("fews:PITimeSeries", ns)
44
+ if pi_timeseries is not None:
45
+ internal_id = timeseries.get("id")
46
+ external_id = self.__pi_timeseries_id(pi_timeseries, "fews")
47
+
48
+ if internal_id in self.__location_parameter_ids:
49
+ message = (
50
+ "Found more than one external timeseries "
51
+ "mapped to internal id {} in {}."
52
+ ).format(internal_id, path)
53
+ logger.error(message)
54
+ raise Exception(message)
55
+ elif external_id in self.__variable_map:
56
+ message = (
57
+ "Found more than one internal timeseries "
58
+ "mapped to external id {} in {}."
59
+ ).format(external_id, path)
60
+ logger.error(message)
61
+ raise Exception(message)
62
+ else:
63
+ self.__location_parameter_ids[internal_id] = (
64
+ self.__pi_location_parameter_id(pi_timeseries, "fews")
65
+ )
66
+ self.__variable_map[external_id] = internal_id
67
+
68
+ for k in ["import", "export"]:
69
+ res = root.find("./fews:%s/fews:PITimeSeriesFile/fews:timeSeriesFile" % k, ns)
70
+ if res is not None:
71
+ setattr(self, "basename_%s" % k, os.path.splitext(res.text)[0])
72
+
73
+ parameters = root.findall("./fews:parameter", ns)
74
+ if parameters is not None:
75
+ for parameter in parameters:
76
+ pi_parameter = parameter.find("fews:PIParameter", ns)
77
+ if pi_parameter is not None:
78
+ internal_id = parameter.get("id")
79
+ external_id = self.__pi_parameter_id(pi_parameter, "fews")
80
+
81
+ if internal_id in self.__model_parameter_ids:
82
+ message = (
83
+ "Found more than one external parameter mapped "
84
+ "to internal id {} in {}."
85
+ ).format(internal_id, path)
86
+ logger.error(message)
87
+ raise Exception(message)
88
+ if external_id in self.__parameter_map:
89
+ message = (
90
+ "Found more than one interal parameter mapped to external "
91
+ "modelId {}, locationId {}, parameterId {} in {}."
92
+ ).format(
93
+ external_id.model_id,
94
+ external_id.location_id,
95
+ external_id.parameter_id,
96
+ path,
97
+ )
98
+ logger.error(message)
99
+ raise Exception(message)
100
+ else:
101
+ self.__model_parameter_ids[internal_id] = self.__pi_model_parameter_id(
102
+ pi_parameter, "fews"
103
+ )
104
+ self.__parameter_map[external_id] = internal_id
105
+
106
+ except IOError:
107
+ logger.error('No rtcDataConfig.xml file was found in "{}".'.format(folder))
108
+ raise
109
+
110
+ def __pi_timeseries_id(self, el, namespace):
111
+ location_id = el.find(namespace + ":locationId", ns).text
112
+ parameter_id = el.find(namespace + ":parameterId", ns).text
113
+
114
+ timeseries_id = location_id + ":" + parameter_id
115
+
116
+ qualifiers = el.findall(namespace + ":qualifierId", ns)
117
+ qualifier_ids = []
118
+ for qualifier in qualifiers:
119
+ qualifier_ids.append(qualifier.text)
120
+
121
+ if len(qualifier_ids) > 0:
122
+ qualifier_ids.sort()
123
+
124
+ return timeseries_id + ":" + ":".join(qualifier_ids)
125
+ else:
126
+ return timeseries_id
127
+
128
+ def __pi_location_parameter_id(self, el, namespace):
129
+ qualifier_ids = []
130
+ qualifiers = el.findall(namespace + ":qualifierId", ns)
131
+ for qualifier in qualifiers:
132
+ qualifier_ids.append(qualifier.text)
133
+
134
+ location_parameter_ids = ts_ids(
135
+ location_id=el.find(namespace + ":locationId", ns).text,
136
+ parameter_id=el.find(namespace + ":parameterId", ns).text,
137
+ qualifier_id=qualifier_ids,
138
+ )
139
+ return location_parameter_ids
140
+
141
+ def __pi_parameter_id(self, el, namespace):
142
+ model_id = el.find(namespace + ":modelId", ns).text
143
+ location_id = el.find(namespace + ":locationId", ns).text
144
+ parameter_id = el.find(namespace + ":parameterId", ns).text
145
+
146
+ return self.__long_parameter_id(parameter_id, location_id, model_id)
147
+
148
+ def __pi_model_parameter_id(self, el, namespace):
149
+ model_id = el.find(namespace + ":modelId", ns).text
150
+ location_id = el.find(namespace + ":locationId", ns).text
151
+ parameter_id = el.find(namespace + ":parameterId", ns).text
152
+
153
+ model_parameter_ids = p_ids(
154
+ model_id=(model_id if model_id is not None else ""),
155
+ location_id=(location_id if location_id is not None else ""),
156
+ parameter_id=(parameter_id if parameter_id is not None else ""),
157
+ )
158
+
159
+ return model_parameter_ids
160
+
161
+ def __long_parameter_id(self, parameter_id, location_id=None, model_id=None):
162
+ """
163
+ Convert a model, location and parameter combination to a single parameter id
164
+ of the form model:location:parameter.
165
+ """
166
+ if location_id is not None:
167
+ parameter_id = location_id + ":" + parameter_id
168
+ if model_id is not None:
169
+ parameter_id = model_id + ":" + parameter_id
170
+ return parameter_id
171
+
172
+ def variable(self, pi_header):
173
+ """
174
+ Map a PI timeseries header to an RTC-Tools timeseries ID.
175
+
176
+ :param pi_header: XML ElementTree node containing a PI timeseries header.
177
+
178
+ :returns: A timeseries ID.
179
+ :rtype: string
180
+ """
181
+ series_id = self.__pi_timeseries_id(pi_header, "pi")
182
+ try:
183
+ return self.__variable_map[series_id]
184
+ except KeyError:
185
+ return series_id
186
+
187
+ def pi_variable_ids(self, variable):
188
+ """
189
+ Map an RTC-Tools timeseries ID to a named tuple of location, parameter
190
+ and qualifier ID's.
191
+
192
+ :param variable: A timeseries ID.
193
+
194
+ :returns: A named tuple with fields location_id, parameter_id and qualifier_id.
195
+ :rtype: namedtuple
196
+ :raises KeyError: If the timeseries ID has no mapping in rtcDataConfig.
197
+ """
198
+ return self.__location_parameter_ids[variable]
199
+
200
+ def parameter(self, parameter_id, location_id=None, model_id=None):
201
+ """
202
+ Map a combination of parameter ID, location ID, model ID to an
203
+ RTC-Tools parameter ID.
204
+
205
+ :param parameter_id: String with parameter ID
206
+ :param location_id: String with location ID
207
+ :param model_id: String with model ID
208
+
209
+ :returns: A parameter ID.
210
+ :rtype: string
211
+ :raises KeyError: If the combination has no mapping in rtcDataConfig.
212
+ """
213
+ parameter_id_long = self.__long_parameter_id(parameter_id, location_id, model_id)
214
+
215
+ return self.__parameter_map[parameter_id_long]
216
+
217
+ def pi_parameter_ids(self, parameter):
218
+ """
219
+ Map an RTC-Tools model parameter ID to a named tuple of model, location
220
+ and parameter ID's.
221
+
222
+ :param parameter: A model parameter ID.
223
+
224
+ :returns: A named tuple with fields model_id, location_id and parameter_id.
225
+ :rtype: namedtuple
226
+ :raises KeyError: If the parameter ID has no mapping in rtcDataConfig.
227
+ """
228
+ return self.__model_parameter_ids[parameter]
@@ -0,0 +1,343 @@
1
+ import logging
2
+ from abc import ABCMeta, abstractmethod
3
+ from datetime import datetime, timedelta
4
+ from typing import Iterable, List, Tuple, Union
5
+
6
+ import numpy as np
7
+
8
+ from rtctools._internal.alias_tools import AliasDict, AliasRelation
9
+
10
+ logger = logging.getLogger("rtctools")
11
+
12
+
13
+ class DataStoreAccessor(metaclass=ABCMeta):
14
+ """
15
+ Base class for all problems.
16
+ Adds an internal data store where timeseries and parameters can be stored.
17
+ Access to the internal data store is always done through the io accessor.
18
+
19
+ :cvar timeseries_import_basename:
20
+ Import file basename. Default is ``timeseries_import``.
21
+ :cvar timeseries_export_basename:
22
+ Export file basename. Default is ``timeseries_export``.
23
+ """
24
+
25
+ #: Import file basename
26
+ timeseries_import_basename = "timeseries_import"
27
+ #: Export file basename
28
+ timeseries_export_basename = "timeseries_export"
29
+
30
+ def __init__(self, **kwargs):
31
+ # Save arguments
32
+ self._input_folder = kwargs["input_folder"] if "input_folder" in kwargs else "input"
33
+ self._output_folder = kwargs["output_folder"] if "output_folder" in kwargs else "output"
34
+
35
+ if logger.getEffectiveLevel() == logging.DEBUG:
36
+ logger.debug("Expecting input files to be located in '" + self._input_folder + "'.")
37
+ logger.debug("Writing output files to '" + self._output_folder + "'.")
38
+
39
+ self.io = DataStore(self)
40
+
41
+ @property
42
+ @abstractmethod
43
+ def alias_relation(self) -> AliasRelation:
44
+ raise NotImplementedError
45
+
46
+
47
+ class DataStore:
48
+ """
49
+ DataStore class used by the DataStoreAccessor.
50
+ Contains all methods needed to access the internal data store.
51
+ """
52
+
53
+ def __init__(self, accessor):
54
+ self.__accessor = accessor
55
+
56
+ # Should all be set by subclass via setters
57
+ self.__reference_datetime = None
58
+ self.__timeseries_datetimes = None
59
+ self.__timeseries_times_sec = None
60
+ self.__timeseries_values = [AliasDict(self.__accessor.alias_relation)]
61
+ self.__parameters = [AliasDict(self.__accessor.alias_relation)]
62
+
63
+ self.__reference_datetime_fixed = False
64
+
65
+ self.__ensemble_size = 1
66
+
67
+ @property
68
+ def reference_datetime(self):
69
+ return self.__reference_datetime
70
+
71
+ @reference_datetime.setter
72
+ def reference_datetime(self, value):
73
+ if self.__reference_datetime_fixed and value != self.__reference_datetime:
74
+ raise RuntimeError(
75
+ "Cannot change reference datetime after times in seconds has been requested."
76
+ )
77
+ self.__reference_datetime = value
78
+
79
+ @property
80
+ def ensemble_size(self):
81
+ return self.__ensemble_size
82
+
83
+ def __update_ensemble_size(self, ensemble_size):
84
+ while ensemble_size > len(self.__timeseries_values):
85
+ self.__timeseries_values.append(AliasDict(self.__accessor.alias_relation))
86
+
87
+ while ensemble_size > len(self.__parameters):
88
+ self.__parameters.append(AliasDict(self.__accessor.alias_relation))
89
+
90
+ assert len(self.__parameters) == len(self.__timeseries_values)
91
+ assert len(self.__parameters) == ensemble_size
92
+
93
+ self.__ensemble_size = ensemble_size
94
+
95
+ @property
96
+ def datetimes(self) -> List[datetime]:
97
+ """
98
+ Returns the timeseries times in seconds.
99
+
100
+ :returns: timeseries datetimes, or None if there has been no call
101
+ to :py:meth:`set_timeseries`.
102
+ """
103
+ return self.__timeseries_datetimes.copy()
104
+
105
+ @property
106
+ def times_sec(self) -> np.ndarray:
107
+ """
108
+ Returns the timeseries times in seconds.
109
+
110
+ Note that once this method is called, it is no longer allowed to
111
+ change :py:attr:`reference_datetime`.
112
+
113
+ :returns: timeseries times in seconds.
114
+ """
115
+ self._datetimes_to_seconds()
116
+
117
+ return self.__timeseries_times_sec
118
+
119
+ def _datetimes_to_seconds(self):
120
+ if self.__reference_datetime_fixed:
121
+ pass
122
+ else:
123
+ # Currently we only allow a reference datetime that exists in the
124
+ # timeseries datetimes. That way we can guarantee that we have
125
+ # "0.0" as one of our times in seconds. This restriction may be
126
+ # loosened in the future.
127
+ if self.reference_datetime not in self.__timeseries_datetimes:
128
+ raise Exception(
129
+ "Reference datetime {} should be equal to "
130
+ "one of the timeseries datetimes {}".format(
131
+ self.reference_datetime, self.__timeseries_datetimes
132
+ )
133
+ )
134
+ self.__timeseries_times_sec = self.datetime_to_sec(
135
+ self.__timeseries_datetimes, self.reference_datetime
136
+ )
137
+ self.__timeseries_times_sec.flags.writeable = False
138
+ self.__reference_datetime_fixed = True
139
+
140
+ def set_timeseries(
141
+ self,
142
+ variable: str,
143
+ datetimes: Iterable[datetime],
144
+ values: np.ndarray,
145
+ ensemble_member: int = 0,
146
+ check_duplicates: bool = False,
147
+ ) -> None:
148
+ """
149
+ Stores input time series values in the internal data store.
150
+
151
+ :param variable: Variable name.
152
+ :param datetimes: Times as datetime objects.
153
+ :param values: The values to be stored.
154
+ :param ensemble_member: The ensemble member index.
155
+ :param check_duplicates: If True, a warning will be given when overwriting values.
156
+ If False, existing values are silently overwritten with new values.
157
+ """
158
+ datetimes = list(datetimes)
159
+
160
+ if not isinstance(datetimes[0], datetime):
161
+ raise TypeError("DateStore.set_timeseries() only support datetimes")
162
+
163
+ if self.__timeseries_datetimes is not None and datetimes != self.__timeseries_datetimes:
164
+ raise RuntimeError(
165
+ "Attempting to overwrite the input time series datetimes with different values. "
166
+ "Please ensure all input time series have the same datetimes."
167
+ )
168
+ self.__timeseries_datetimes = datetimes
169
+
170
+ if len(self.__timeseries_datetimes) != len(values):
171
+ raise ValueError(
172
+ "Length of values ({}) must be the same as length of datetimes ({})".format(
173
+ len(values), len(self.__timeseries_datetimes)
174
+ )
175
+ )
176
+
177
+ if ensemble_member >= self.__ensemble_size:
178
+ self.__update_ensemble_size(ensemble_member + 1)
179
+
180
+ if check_duplicates and variable in self.__timeseries_values[ensemble_member].keys():
181
+ logger.warning(
182
+ "Time series values for ensemble member {} and variable {} set twice. "
183
+ "Overwriting old values.".format(ensemble_member, variable)
184
+ )
185
+
186
+ self.__timeseries_values[ensemble_member][variable] = values
187
+
188
+ def get_timeseries(
189
+ self, variable: str, ensemble_member: int = 0
190
+ ) -> Tuple[List[datetime], np.ndarray]:
191
+ """
192
+ Looks up the time series in the internal data store.
193
+
194
+ :return a tuple (datetimes, values)
195
+ """
196
+ if ensemble_member >= self.__ensemble_size:
197
+ raise KeyError("ensemble_member {} does not exist".format(ensemble_member))
198
+ return self.__timeseries_datetimes, self.__timeseries_values[ensemble_member][variable]
199
+
200
+ def get_timeseries_names(self, ensemble_member: int = 0) -> Iterable[str]:
201
+ return self.__timeseries_values[ensemble_member].keys()
202
+
203
+ def set_timeseries_sec(
204
+ self,
205
+ variable: str,
206
+ times_in_sec: np.ndarray,
207
+ values: np.ndarray,
208
+ ensemble_member: int = 0,
209
+ check_duplicates: bool = False,
210
+ ) -> None:
211
+ """
212
+ Stores input time series values in the internal data store.
213
+
214
+ Note that once this method is called, it is no longer allowed to
215
+ change :py:attr:`reference_datetime`.
216
+
217
+ :param variable: Variable name.
218
+ :param times_in_sec: The times in seconds.
219
+ :param values: The values to be stored.
220
+ :param ensemble_member: The ensemble member index.
221
+ :param check_duplicates: If True, a warning will be given when overwriting values.
222
+ If False, existing values are silently overwritten with new values.
223
+ """
224
+ self._datetimes_to_seconds()
225
+
226
+ if self.reference_datetime is None:
227
+ raise RuntimeError("Cannot use times in seconds before reference datetime is set.")
228
+
229
+ if self.__timeseries_times_sec is not None and not np.array_equal(
230
+ times_in_sec, self.__timeseries_times_sec
231
+ ):
232
+ raise RuntimeError(
233
+ "Attempting to overwrite the input time series times with different values. "
234
+ "Please ensure all input time series have the same times."
235
+ )
236
+
237
+ if len(self.__timeseries_datetimes) != len(values):
238
+ raise ValueError(
239
+ "Length of values ({}) must be the same as length of times ({})".format(
240
+ len(values), len(self.__timeseries_datetimes)
241
+ )
242
+ )
243
+
244
+ if ensemble_member >= self.__ensemble_size:
245
+ self.__update_ensemble_size(ensemble_member + 1)
246
+
247
+ if check_duplicates and variable in self.__timeseries_values[ensemble_member].keys():
248
+ logger.warning(
249
+ "Time series values for ensemble member {} and variable {} set twice. "
250
+ "Overwriting old values.".format(ensemble_member, variable)
251
+ )
252
+
253
+ self.__timeseries_values[ensemble_member][variable] = values
254
+
255
+ def get_timeseries_sec(
256
+ self, variable: str, ensemble_member: int = 0
257
+ ) -> Tuple[np.ndarray, np.ndarray]:
258
+ """
259
+ Looks up the time series in the internal data store.
260
+
261
+ Note that once this method is called, it is no longer allowed to
262
+ change :py:attr:`reference_datetime`.
263
+
264
+ :return a tuple (times, values)
265
+ """
266
+ self._datetimes_to_seconds()
267
+
268
+ if ensemble_member >= self.__ensemble_size:
269
+ raise KeyError("ensemble_member {} does not exist".format(ensemble_member))
270
+ return self.__timeseries_times_sec, self.__timeseries_values[ensemble_member][variable]
271
+
272
+ def set_parameter(
273
+ self,
274
+ parameter_name: str,
275
+ value: float,
276
+ ensemble_member: int = 0,
277
+ check_duplicates: bool = False,
278
+ ) -> None:
279
+ """
280
+ Stores the parameter value in the internal data store.
281
+
282
+ :param parameter_name: Parameter name.
283
+ :param value: The values to be stored.
284
+ :param ensemble_member: The ensemble member index.
285
+ :param check_duplicates: If True, a warning will be given when overwriting values.
286
+ If False, existing values are silently overwritten with new values.
287
+ """
288
+ if ensemble_member >= self.__ensemble_size:
289
+ self.__update_ensemble_size(ensemble_member + 1)
290
+
291
+ if check_duplicates and parameter_name in self.__parameters[ensemble_member].keys():
292
+ logger.warning(
293
+ "Attempting to set parameter value for ensemble member {} and name {} twice. "
294
+ "Using new value of {}.".format(ensemble_member, parameter_name, value)
295
+ )
296
+
297
+ self.__parameters[ensemble_member][parameter_name] = value
298
+
299
+ def get_parameter(self, parameter_name: str, ensemble_member: int = 0) -> float:
300
+ """
301
+ Looks up the parameter value in the internal data store.
302
+ """
303
+ if ensemble_member >= self.__ensemble_size:
304
+ raise KeyError("ensemble_member {} does not exist".format(ensemble_member))
305
+ return self.__parameters[ensemble_member][parameter_name]
306
+
307
+ def parameters(self, ensemble_member: int = 0) -> AliasDict:
308
+ """
309
+ Returns an AliasDict of parameters to its values for the specified ensemble member.
310
+ """
311
+ if ensemble_member >= self.__ensemble_size:
312
+ raise KeyError("ensemble_member {} does not exist".format(ensemble_member))
313
+ return self.__parameters[ensemble_member]
314
+
315
+ @staticmethod
316
+ def datetime_to_sec(
317
+ d: Union[Iterable[datetime], datetime], t0: datetime
318
+ ) -> Union[np.ndarray, float]:
319
+ """
320
+ Returns the date/timestamps in seconds since t0.
321
+
322
+ :param d: Iterable of datetimes or a single datetime object.
323
+ :param t0: Reference datetime.
324
+ """
325
+ if hasattr(d, "__iter__"):
326
+ return np.array([(t - t0).total_seconds() for t in d])
327
+ else:
328
+ return (d - t0).total_seconds()
329
+
330
+ @staticmethod
331
+ def sec_to_datetime(
332
+ s: Union[Iterable[float], float], t0: datetime
333
+ ) -> Union[List[datetime], datetime]:
334
+ """
335
+ Return the date/timestamps in seconds since t0 as datetime objects.
336
+
337
+ :param s: Iterable of ints or a single int (number of seconds before or after t0).
338
+ :param t0: Reference datetime.
339
+ """
340
+ if hasattr(s, "__iter__"):
341
+ return [t0 + timedelta(seconds=t) for t in s]
342
+ else:
343
+ return t0 + timedelta(seconds=s)
File without changes