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.
- rtc_tools-2.7.3.dist-info/METADATA +53 -0
- rtc_tools-2.7.3.dist-info/RECORD +50 -0
- rtc_tools-2.7.3.dist-info/WHEEL +5 -0
- rtc_tools-2.7.3.dist-info/entry_points.txt +3 -0
- rtc_tools-2.7.3.dist-info/licenses/COPYING.LESSER +165 -0
- rtc_tools-2.7.3.dist-info/top_level.txt +1 -0
- rtctools/__init__.py +5 -0
- rtctools/_internal/__init__.py +0 -0
- rtctools/_internal/alias_tools.py +188 -0
- rtctools/_internal/caching.py +25 -0
- rtctools/_internal/casadi_helpers.py +99 -0
- rtctools/_internal/debug_check_helpers.py +41 -0
- rtctools/_version.py +21 -0
- rtctools/data/__init__.py +4 -0
- rtctools/data/csv.py +150 -0
- rtctools/data/interpolation/__init__.py +3 -0
- rtctools/data/interpolation/bspline.py +31 -0
- rtctools/data/interpolation/bspline1d.py +169 -0
- rtctools/data/interpolation/bspline2d.py +54 -0
- rtctools/data/netcdf.py +467 -0
- rtctools/data/pi.py +1236 -0
- rtctools/data/rtc.py +228 -0
- rtctools/data/storage.py +343 -0
- rtctools/optimization/__init__.py +0 -0
- rtctools/optimization/collocated_integrated_optimization_problem.py +3208 -0
- rtctools/optimization/control_tree_mixin.py +221 -0
- rtctools/optimization/csv_lookup_table_mixin.py +462 -0
- rtctools/optimization/csv_mixin.py +300 -0
- rtctools/optimization/goal_programming_mixin.py +769 -0
- rtctools/optimization/goal_programming_mixin_base.py +1094 -0
- rtctools/optimization/homotopy_mixin.py +165 -0
- rtctools/optimization/initial_state_estimation_mixin.py +89 -0
- rtctools/optimization/io_mixin.py +320 -0
- rtctools/optimization/linearization_mixin.py +33 -0
- rtctools/optimization/linearized_order_goal_programming_mixin.py +235 -0
- rtctools/optimization/min_abs_goal_programming_mixin.py +385 -0
- rtctools/optimization/modelica_mixin.py +482 -0
- rtctools/optimization/netcdf_mixin.py +177 -0
- rtctools/optimization/optimization_problem.py +1302 -0
- rtctools/optimization/pi_mixin.py +292 -0
- rtctools/optimization/planning_mixin.py +19 -0
- rtctools/optimization/single_pass_goal_programming_mixin.py +676 -0
- rtctools/optimization/timeseries.py +56 -0
- rtctools/rtctoolsapp.py +131 -0
- rtctools/simulation/__init__.py +0 -0
- rtctools/simulation/csv_mixin.py +171 -0
- rtctools/simulation/io_mixin.py +195 -0
- rtctools/simulation/pi_mixin.py +255 -0
- rtctools/simulation/simulation_problem.py +1293 -0
- 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]
|
rtctools/data/storage.py
ADDED
|
@@ -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
|