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
@@ -0,0 +1,300 @@
1
+ import logging
2
+ import os
3
+ from datetime import timedelta
4
+
5
+ import numpy as np
6
+
7
+ import rtctools.data.csv as csv
8
+ from rtctools._internal.alias_tools import AliasDict
9
+ from rtctools._internal.caching import cached
10
+ from rtctools.optimization.io_mixin import IOMixin
11
+ from rtctools.optimization.timeseries import Timeseries
12
+
13
+ logger = logging.getLogger("rtctools")
14
+
15
+
16
+ class CSVMixin(IOMixin):
17
+ """
18
+ Adds reading and writing of CSV timeseries and parameters to your optimization problem.
19
+
20
+ During preprocessing, files named ``timeseries_import.csv``, ``initial_state.csv``,
21
+ and ``parameters.csv`` are read from the ``input`` subfolder.
22
+
23
+ During postprocessing, a file named ``timeseries_export.csv`` is written to the ``output``
24
+ subfolder.
25
+
26
+ In ensemble mode, a file named ``ensemble.csv`` is read from the ``input`` folder. This file
27
+ contains two columns. The first column gives the name of the ensemble member, and the second
28
+ column its probability. Furthermore, the other XML files appear one level deeper inside the
29
+ filesystem hierarchy, inside subfolders with the names of the ensemble members.
30
+
31
+ :cvar csv_initial_state_basename:
32
+ Initial state file basename. Default is ``initial_state``.
33
+ :cvar csv_parameters_basename:
34
+ Parameters file basename. Default is ``parameters``.
35
+ :cvar csv_ensemble_basename:
36
+ Ensemble file basename. Default is ``ensemble``.
37
+ :cvar csv_delimiter:
38
+ Column delimiter used in CSV files. Default is ``,``.
39
+ :cvar csv_equidistant:
40
+ Whether or not the timeseries data is equidistant. Default is ``True``.
41
+ :cvar csv_ensemble_mode:
42
+ Whether or not to use ensembles. Default is ``False``.
43
+ :cvar csv_validate_timeseries:
44
+ Check consistency of timeseries. Default is ``True``.
45
+ """
46
+
47
+ #: Initial state file basename
48
+ csv_initial_state_basename = "initial_state"
49
+
50
+ #: Parameters file basename
51
+ csv_parameters_basename = "parameters"
52
+
53
+ #: Ensemble file basename
54
+ csv_ensemble_basename = "ensemble"
55
+
56
+ #: Column delimiter used in CSV files
57
+ csv_delimiter = ","
58
+
59
+ #: Whether or not the timeseries data is equidistant
60
+ csv_equidistant = True
61
+
62
+ #: Whether or not to use ensembles
63
+ csv_ensemble_mode = False
64
+
65
+ #: Check consistency of timeseries
66
+ csv_validate_timeseries = True
67
+
68
+ def __init__(self, **kwargs):
69
+ # Call parent class first for default behaviour.
70
+ super().__init__(**kwargs)
71
+
72
+ def read(self):
73
+ # Call parent class first for default behaviour.
74
+ super().read()
75
+
76
+ # Helper function to check if initial state array actually defines
77
+ # only the initial state
78
+ def check_initial_state_array(initial_state):
79
+ """
80
+ Check length of initial state array, throw exception when larger than 1.
81
+ """
82
+ if initial_state.shape:
83
+ raise Exception(
84
+ "CSVMixin: Initial state file {} contains more than one row of data. "
85
+ "Please remove the data row(s) that do not describe the initial state.".format(
86
+ os.path.join(self._input_folder, self.csv_initial_state_basename + ".csv")
87
+ )
88
+ )
89
+
90
+ # Read CSV files
91
+ self.__initial_state = []
92
+ if self.csv_ensemble_mode:
93
+ self.__ensemble = np.genfromtxt(
94
+ os.path.join(self._input_folder, self.csv_ensemble_basename + ".csv"),
95
+ delimiter=",",
96
+ deletechars="",
97
+ dtype=None,
98
+ names=True,
99
+ encoding=None,
100
+ )
101
+ if len(self.__ensemble.shape) == 0:
102
+ # If there is only one ensemble member, the array is 0-dimensional.
103
+ self.__ensemble = np.expand_dims(self.__ensemble, 0)
104
+
105
+ logger.debug("CSVMixin: Read ensemble description")
106
+
107
+ for ensemble_member_index, ensemble_member_name in enumerate(self.__ensemble["name"]):
108
+ _timeseries = csv.load(
109
+ os.path.join(
110
+ self._input_folder,
111
+ ensemble_member_name,
112
+ self.timeseries_import_basename + ".csv",
113
+ ),
114
+ delimiter=self.csv_delimiter,
115
+ with_time=True,
116
+ )
117
+ self.__timeseries_times = _timeseries[_timeseries.dtype.names[0]]
118
+
119
+ self.io.reference_datetime = self.__timeseries_times[0]
120
+
121
+ for key in _timeseries.dtype.names[1:]:
122
+ self.io.set_timeseries(
123
+ key,
124
+ self.__timeseries_times,
125
+ np.asarray(_timeseries[key], dtype=np.float64),
126
+ ensemble_member_index,
127
+ )
128
+ logger.debug("CSVMixin: Read timeseries")
129
+
130
+ for ensemble_member_index, ensemble_member_name in enumerate(self.__ensemble["name"]):
131
+ try:
132
+ _parameters = csv.load(
133
+ os.path.join(
134
+ self._input_folder,
135
+ ensemble_member_name,
136
+ self.csv_parameters_basename + ".csv",
137
+ ),
138
+ delimiter=self.csv_delimiter,
139
+ )
140
+ for key in _parameters.dtype.names:
141
+ self.io.set_parameter(key, float(_parameters[key]), ensemble_member_index)
142
+ except IOError:
143
+ pass
144
+ logger.debug("CSVMixin: Read parameters.")
145
+
146
+ for ensemble_member_name in self.__ensemble["name"]:
147
+ try:
148
+ _initial_state = csv.load(
149
+ os.path.join(
150
+ self._input_folder,
151
+ ensemble_member_name,
152
+ self.csv_initial_state_basename + ".csv",
153
+ ),
154
+ delimiter=self.csv_delimiter,
155
+ )
156
+ check_initial_state_array(_initial_state)
157
+ _initial_state = {
158
+ key: float(_initial_state[key]) for key in _initial_state.dtype.names
159
+ }
160
+ except IOError:
161
+ _initial_state = {}
162
+ self.__initial_state.append(AliasDict(self.alias_relation, _initial_state))
163
+ logger.debug("CSVMixin: Read initial state.")
164
+ else:
165
+ _timeseries = csv.load(
166
+ os.path.join(self._input_folder, self.timeseries_import_basename + ".csv"),
167
+ delimiter=self.csv_delimiter,
168
+ with_time=True,
169
+ )
170
+ self.__timeseries_times = _timeseries[_timeseries.dtype.names[0]]
171
+
172
+ self.io.reference_datetime = self.__timeseries_times[0]
173
+
174
+ for key in _timeseries.dtype.names[1:]:
175
+ self.io.set_timeseries(
176
+ key, self.__timeseries_times, np.asarray(_timeseries[key], dtype=np.float64)
177
+ )
178
+ logger.debug("CSVMixin: Read timeseries.")
179
+
180
+ try:
181
+ _parameters = csv.load(
182
+ os.path.join(self._input_folder, self.csv_parameters_basename + ".csv"),
183
+ delimiter=self.csv_delimiter,
184
+ )
185
+ logger.debug("CSVMixin: Read parameters.")
186
+ for key in _parameters.dtype.names:
187
+ self.io.set_parameter(key, float(_parameters[key]))
188
+ except IOError:
189
+ pass
190
+
191
+ try:
192
+ _initial_state = csv.load(
193
+ os.path.join(self._input_folder, self.csv_initial_state_basename + ".csv"),
194
+ delimiter=self.csv_delimiter,
195
+ )
196
+ logger.debug("CSVMixin: Read initial state.")
197
+ check_initial_state_array(_initial_state)
198
+ _initial_state = {
199
+ key: float(_initial_state[key]) for key in _initial_state.dtype.names
200
+ }
201
+ except IOError:
202
+ _initial_state = {}
203
+ self.__initial_state.append(AliasDict(self.alias_relation, _initial_state))
204
+
205
+ # Timestamp check
206
+ if self.csv_validate_timeseries:
207
+ times = self.__timeseries_times
208
+ for i in range(len(times) - 1):
209
+ if times[i] >= times[i + 1]:
210
+ raise Exception("CSVMixin: Time stamps must be strictly increasing.")
211
+
212
+ if self.csv_equidistant:
213
+ # Check if the timeseries are truly equidistant
214
+ if self.csv_validate_timeseries:
215
+ times = self.__timeseries_times
216
+ dt = times[1] - times[0]
217
+ for i in range(len(times) - 1):
218
+ if times[i + 1] - times[i] != dt:
219
+ raise Exception(
220
+ "CSVMixin: Expecting equidistant timeseries, the time step towards "
221
+ "{} is not the same as the time step(s) before. "
222
+ "Set csv_equidistant = False if this is intended.".format(times[i + 1])
223
+ )
224
+
225
+ def ensemble_member_probability(self, ensemble_member):
226
+ if self.csv_ensemble_mode:
227
+ return self.__ensemble["probability"][ensemble_member]
228
+ else:
229
+ return 1.0
230
+
231
+ @cached
232
+ def history(self, ensemble_member):
233
+ # Call parent class first for default values.
234
+ history = super().history(ensemble_member)
235
+
236
+ initial_time = np.array([self.initial_time])
237
+
238
+ # Load parameters from parameter config
239
+ for variable in self.dae_variables["free_variables"]:
240
+ variable = variable.name()
241
+ try:
242
+ history[variable] = Timeseries(
243
+ initial_time, self.__initial_state[ensemble_member][variable]
244
+ )
245
+ except (KeyError, ValueError):
246
+ pass
247
+ else:
248
+ if logger.getEffectiveLevel() == logging.DEBUG:
249
+ logger.debug("CSVMixin: Read initial state {}".format(variable))
250
+ return history
251
+
252
+ def write(self):
253
+ # Call parent class first for default behaviour.
254
+ super().write()
255
+
256
+ # Write output
257
+ times = self.times()
258
+
259
+ def write_output(ensemble_member, folder):
260
+ results = self.extract_results(ensemble_member)
261
+ names = ["time"] + sorted({sym.name() for sym in self.output_variables})
262
+ formats = ["O"] + (len(names) - 1) * ["f8"]
263
+ dtype = {"names": names, "formats": formats}
264
+ data = np.zeros(len(times), dtype=dtype)
265
+ data["time"] = [self.io.reference_datetime + timedelta(seconds=s) for s in times]
266
+ for output_variable in self.output_variables:
267
+ output_variable = output_variable.name()
268
+ try:
269
+ values = results[output_variable]
270
+ if len(values) != len(times):
271
+ values = self.interpolate(
272
+ times,
273
+ self.times(output_variable),
274
+ values,
275
+ self.interpolation_method(output_variable),
276
+ )
277
+ except KeyError:
278
+ try:
279
+ ts = self.get_timeseries(output_variable, ensemble_member)
280
+ if len(ts.times) != len(times):
281
+ values = self.interpolate(times, ts.times, ts.values)
282
+ else:
283
+ values = ts.values
284
+ except KeyError:
285
+ logger.error(
286
+ "Output requested for non-existent variable {}".format(output_variable)
287
+ )
288
+ continue
289
+ data[output_variable] = values
290
+
291
+ fname = os.path.join(folder, self.timeseries_export_basename + ".csv")
292
+ csv.save(fname, data, delimiter=self.csv_delimiter, with_time=True)
293
+
294
+ if self.csv_ensemble_mode:
295
+ for ensemble_member, ensemble_member_name in enumerate(self.__ensemble["name"]):
296
+ write_output(
297
+ ensemble_member, os.path.join(self._output_folder, ensemble_member_name)
298
+ )
299
+ else:
300
+ write_output(0, self._output_folder)