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,292 @@
1
+ import logging
2
+ from datetime import timedelta
3
+
4
+ import rtctools.data.pi as pi
5
+ import rtctools.data.rtc as rtc
6
+ from rtctools.optimization.io_mixin import IOMixin
7
+
8
+ logger = logging.getLogger("rtctools")
9
+
10
+
11
+ class PIMixin(IOMixin):
12
+ """
13
+ Adds `Delft-FEWS Published Interface
14
+ <https://publicwiki.deltares.nl/display/FEWSDOC/The+Delft-Fews+Published+Interface>`_
15
+ I/O to your optimization problem.
16
+
17
+ During preprocessing, files named ``rtcDataConfig.xml``, ``timeseries_import.xml``,
18
+ ``rtcParameterConfig.xml``, and ``rtcParameterConfig_Numerical.xml`` are read from the
19
+ ``input`` subfolder. ``rtcDataConfig.xml`` maps tuples of FEWS identifiers, including
20
+ location and parameter ID, to RTC-Tools time series identifiers.
21
+
22
+ During postprocessing, a file named ``timeseries_export.xml`` is written to the ``output``
23
+ subfolder.
24
+
25
+ :cvar pi_binary_timeseries:
26
+ Whether to use PI binary timeseries format. Default is ``False``.
27
+ :cvar pi_parameter_config_basenames:
28
+ List of parameter config file basenames to read. Default is [``rtcParameterConfig``].
29
+ :cvar pi_parameter_config_numerical_basename:
30
+ Numerical config file basename to read. Default is ``rtcParameterConfig_Numerical``.
31
+ :cvar pi_check_for_duplicate_parameters:
32
+ Check if duplicate parameters are read. Default is ``True``.
33
+ :cvar pi_validate_timeseries:
34
+ Check consistency of timeseries. Default is ``True``.
35
+ """
36
+
37
+ #: Whether to use PI binary timeseries format
38
+ pi_binary_timeseries = False
39
+
40
+ #: Location of rtcParameterConfig files
41
+ pi_parameter_config_basenames = ["rtcParameterConfig"]
42
+ pi_parameter_config_numerical_basename = "rtcParameterConfig_Numerical"
43
+
44
+ #: Check consistency of timeseries
45
+ pi_validate_timeseries = True
46
+
47
+ #: Check for duplicate parameters
48
+ pi_check_for_duplicate_parameters = True
49
+
50
+ def __init__(self, **kwargs):
51
+ # Call parent class first for default behaviour.
52
+ super().__init__(**kwargs)
53
+
54
+ # Load rtcDataConfig.xml. We assume this file does not change over the
55
+ # life time of this object.
56
+ self.__data_config = rtc.DataConfig(self._input_folder)
57
+
58
+ def read(self):
59
+ # Call parent class first for default behaviour.
60
+ super().read()
61
+
62
+ # rtcParameterConfig
63
+ self.__parameter_config = []
64
+ try:
65
+ for pi_parameter_config_basename in self.pi_parameter_config_basenames:
66
+ self.__parameter_config.append(
67
+ pi.ParameterConfig(self._input_folder, pi_parameter_config_basename)
68
+ )
69
+ except IOError:
70
+ raise Exception(
71
+ "PIMixin: {}.xml not found in {}.".format(
72
+ pi_parameter_config_basename, self._input_folder
73
+ )
74
+ )
75
+
76
+ try:
77
+ self.__parameter_config_numerical = pi.ParameterConfig(
78
+ self._input_folder, self.pi_parameter_config_numerical_basename
79
+ )
80
+ except IOError:
81
+ self.__parameter_config_numerical = None
82
+
83
+ try:
84
+ self.__timeseries_import = pi.Timeseries(
85
+ self.__data_config,
86
+ self._input_folder,
87
+ self.timeseries_import_basename,
88
+ binary=self.pi_binary_timeseries,
89
+ pi_validate_times=self.pi_validate_timeseries,
90
+ )
91
+ except IOError:
92
+ raise Exception(
93
+ "PIMixin: {}.xml not found in {}.".format(
94
+ self.timeseries_import_basename, self._input_folder
95
+ )
96
+ )
97
+
98
+ self.__timeseries_export = pi.Timeseries(
99
+ self.__data_config,
100
+ self._output_folder,
101
+ self.timeseries_export_basename,
102
+ binary=self.pi_binary_timeseries,
103
+ pi_validate_times=False,
104
+ make_new_file=True,
105
+ )
106
+
107
+ # Convert timeseries timestamps to seconds since t0 for internal use
108
+ timeseries_import_times = self.__timeseries_import.times
109
+
110
+ # Timestamp check
111
+ if self.pi_validate_timeseries:
112
+ for i in range(len(timeseries_import_times) - 1):
113
+ if timeseries_import_times[i] >= timeseries_import_times[i + 1]:
114
+ raise Exception("PIMixin: Time stamps must be strictly increasing.")
115
+
116
+ if self.__timeseries_import.dt:
117
+ # Check if the timeseries are truly equidistant
118
+ if self.pi_validate_timeseries:
119
+ dt = timeseries_import_times[1] - timeseries_import_times[0]
120
+ for i in range(len(timeseries_import_times) - 1):
121
+ if timeseries_import_times[i + 1] - timeseries_import_times[i] != dt:
122
+ raise Exception(
123
+ "PIMixin: Expecting equidistant timeseries, the time step "
124
+ "towards {} is not the same as the time step(s) before. Set "
125
+ "unit to nonequidistant if this is intended.".format(
126
+ timeseries_import_times[i + 1]
127
+ )
128
+ )
129
+
130
+ # Offer input timeseries to IOMixin
131
+ self.io.reference_datetime = self.__timeseries_import.forecast_datetime
132
+
133
+ for ensemble_member in range(self.__timeseries_import.ensemble_size):
134
+ for variable, values in self.__timeseries_import.items(ensemble_member):
135
+ self.io.set_timeseries(variable, timeseries_import_times, values, ensemble_member)
136
+
137
+ # store the parameters in the internal data store. Note that we
138
+ # are effectively broadcasting parameters, as ParameterConfig does
139
+ # not support parameters varying per ensemble member
140
+ for parameter_config in self.__parameter_config:
141
+ for location_id, model_id, parameter_id, value in parameter_config:
142
+ try:
143
+ parameter = self.__data_config.parameter(
144
+ parameter_id, location_id, model_id
145
+ )
146
+ except KeyError:
147
+ parameter = parameter_id
148
+
149
+ self.io.set_parameter(
150
+ parameter,
151
+ value,
152
+ ensemble_member,
153
+ check_duplicates=self.pi_check_for_duplicate_parameters,
154
+ )
155
+
156
+ def solver_options(self):
157
+ # Call parent
158
+ options = super().solver_options()
159
+
160
+ # Only do this if we have a rtcParameterConfig_Numerical
161
+ if self.__parameter_config_numerical is None:
162
+ return options
163
+
164
+ # Load solver options from parameter config
165
+ for _location_id, _model, option, value in self.__parameter_config_numerical:
166
+ options[option] = value
167
+
168
+ # Done
169
+ return options
170
+
171
+ def write(self):
172
+ # Call parent class first for default behaviour.
173
+ super().write()
174
+
175
+ # Get time stamps
176
+ times = self.times()
177
+ if len(set(times[1:] - times[:-1])) == 1:
178
+ dt = timedelta(seconds=times[1] - times[0])
179
+ else:
180
+ dt = None
181
+
182
+ # Start of write output
183
+ # Write the time range for the export file.
184
+ self.__timeseries_export.times = [
185
+ self.__timeseries_import.times[self.__timeseries_import.forecast_index]
186
+ + timedelta(seconds=s)
187
+ for s in times
188
+ ]
189
+
190
+ # Write other time settings
191
+ self.__timeseries_export.forecast_datetime = self.__timeseries_import.forecast_datetime
192
+ self.__timeseries_export.dt = dt
193
+ self.__timeseries_export.timezone = self.__timeseries_import.timezone
194
+
195
+ # Write the ensemble properties for the export file.
196
+ if self.ensemble_size > 1:
197
+ self.__timeseries_export.contains_ensemble = True
198
+ self.__timeseries_export.ensemble_size = self.ensemble_size
199
+ self.__timeseries_export.contains_ensemble = self.ensemble_size > 1
200
+
201
+ # Start looping over the ensembles for extraction of the output values.
202
+ for ensemble_member in range(self.ensemble_size):
203
+ results = self.extract_results(ensemble_member)
204
+
205
+ # For all variables that are output variables the values are
206
+ # extracted from the results.
207
+ for variable in [sym.name() for sym in self.output_variables]:
208
+ for alias in self.alias_relation.aliases(variable):
209
+ try:
210
+ values = results[alias]
211
+ if len(values) != len(times):
212
+ values = self.interpolate(
213
+ times, self.times(alias), values, self.interpolation_method(alias)
214
+ )
215
+ except KeyError:
216
+ try:
217
+ ts = self.get_timeseries(alias, ensemble_member)
218
+ if len(ts.times) != len(times):
219
+ values = self.interpolate(times, ts.times, ts.values)
220
+ else:
221
+ values = ts.values
222
+ except KeyError:
223
+ logger.error(
224
+ "PIMixin: Output requested for non-existent alias {}. "
225
+ "Will not be in output file.".format(alias)
226
+ )
227
+ continue
228
+
229
+ # Check if ID mapping is present
230
+ try:
231
+ self.__data_config.pi_variable_ids(alias)
232
+ except KeyError:
233
+ logger.debug(
234
+ "PIMixin: variable {} has no mapping defined in rtcDataConfig "
235
+ "so cannot be added to the output file.".format(alias)
236
+ )
237
+ continue
238
+
239
+ # Add series to output file.
240
+ # NOTE: We use the unit of the zeroth ensemble member, as
241
+ # we might be outputting more ensembles than we read in.
242
+ self.__timeseries_export.set(
243
+ alias,
244
+ values,
245
+ unit=self.__timeseries_import.get_unit(alias, ensemble_member=0),
246
+ ensemble_member=ensemble_member,
247
+ )
248
+
249
+ # Write output file to disk
250
+ self.__timeseries_export.write()
251
+
252
+ def set_timeseries(self, variable: str, *args, unit: str = None, **kwargs):
253
+ if unit is not None:
254
+ self.__timeseries_import.set_unit(variable, unit, 0)
255
+
256
+ super().set_timeseries(variable, *args, **kwargs)
257
+
258
+ @property
259
+ def timeseries_import(self):
260
+ """
261
+ :class:`pi.Timeseries` object containing the input data.
262
+ """
263
+ return self.__timeseries_import
264
+
265
+ @property
266
+ def timeseries_import_times(self):
267
+ """
268
+ List of time stamps for which input data is specified.
269
+
270
+ The time stamps are in seconds since t0, and may be negative.
271
+ """
272
+ return self.io.times_sec
273
+
274
+ @property
275
+ def timeseries_export(self):
276
+ """
277
+ :class:`pi.Timeseries` object for holding the output data.
278
+ """
279
+ return self.__timeseries_export
280
+
281
+ def set_unit(self, variable: str, unit: str):
282
+ """
283
+ Set the unit of a time series.
284
+
285
+ :param variable: Time series ID.
286
+ :param unit: Unit.
287
+ """
288
+ assert hasattr(self, "_PIMixin__timeseries_import"), (
289
+ "set_unit can only be called after read() in pre() has finished."
290
+ )
291
+ self.__timeseries_import.set_unit(variable, unit, 0)
292
+ self.__timeseries_export.set_unit(variable, unit, 0)
@@ -0,0 +1,19 @@
1
+ from rtctools.optimization.optimization_problem import OptimizationProblem
2
+
3
+
4
+ class PlanningMixin(OptimizationProblem):
5
+ """
6
+ Uses default discretization logic for planning variables, but uses
7
+ dedicated per-ensemble-member decision variables for other, non-planning control
8
+ variables.
9
+ """
10
+
11
+ # Planning variables
12
+ planning_variables = []
13
+
14
+ def discretize_control(self, variable, ensemble_member, times, offset):
15
+ if variable not in self.planning_variables:
16
+ # Non-planning variables are never shared between ensemble members
17
+ return slice(offset, offset + len(times))
18
+ else:
19
+ return super().discretize_control(variable, ensemble_member, times, offset)