rtc-tools 2.5.2rc4__py3-none-any.whl → 2.6.0__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.
Potentially problematic release.
This version of rtc-tools might be problematic. Click here for more details.
- {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/METADATA +7 -7
- rtc_tools-2.6.0.dist-info/RECORD +50 -0
- {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/WHEEL +1 -1
- rtctools/__init__.py +2 -1
- rtctools/_internal/alias_tools.py +12 -10
- rtctools/_internal/caching.py +5 -3
- rtctools/_internal/casadi_helpers.py +11 -32
- rtctools/_internal/debug_check_helpers.py +1 -1
- rtctools/_version.py +3 -3
- rtctools/data/__init__.py +2 -2
- rtctools/data/csv.py +54 -33
- rtctools/data/interpolation/bspline.py +3 -3
- rtctools/data/interpolation/bspline1d.py +42 -29
- rtctools/data/interpolation/bspline2d.py +10 -4
- rtctools/data/netcdf.py +137 -93
- rtctools/data/pi.py +304 -210
- rtctools/data/rtc.py +64 -53
- rtctools/data/storage.py +91 -51
- rtctools/optimization/collocated_integrated_optimization_problem.py +1244 -696
- rtctools/optimization/control_tree_mixin.py +68 -66
- rtctools/optimization/csv_lookup_table_mixin.py +107 -74
- rtctools/optimization/csv_mixin.py +83 -52
- rtctools/optimization/goal_programming_mixin.py +237 -146
- rtctools/optimization/goal_programming_mixin_base.py +204 -111
- rtctools/optimization/homotopy_mixin.py +36 -27
- rtctools/optimization/initial_state_estimation_mixin.py +8 -8
- rtctools/optimization/io_mixin.py +48 -43
- rtctools/optimization/linearization_mixin.py +3 -1
- rtctools/optimization/linearized_order_goal_programming_mixin.py +57 -28
- rtctools/optimization/min_abs_goal_programming_mixin.py +72 -29
- rtctools/optimization/modelica_mixin.py +135 -81
- rtctools/optimization/netcdf_mixin.py +32 -18
- rtctools/optimization/optimization_problem.py +181 -127
- rtctools/optimization/pi_mixin.py +68 -36
- rtctools/optimization/planning_mixin.py +19 -0
- rtctools/optimization/single_pass_goal_programming_mixin.py +159 -112
- rtctools/optimization/timeseries.py +4 -6
- rtctools/rtctoolsapp.py +18 -18
- rtctools/simulation/csv_mixin.py +37 -30
- rtctools/simulation/io_mixin.py +9 -5
- rtctools/simulation/pi_mixin.py +62 -32
- rtctools/simulation/simulation_problem.py +471 -180
- rtctools/util.py +84 -56
- rtc_tools-2.5.2rc4.dist-info/RECORD +0 -49
- {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/COPYING.LESSER +0 -0
- {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/entry_points.txt +0 -0
- {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/top_level.txt +0 -0
|
@@ -3,12 +3,17 @@ import itertools
|
|
|
3
3
|
from typing import List
|
|
4
4
|
|
|
5
5
|
import casadi as ca
|
|
6
|
-
|
|
7
6
|
import numpy as np
|
|
8
7
|
|
|
9
8
|
from .goal_programming_mixin import GoalProgrammingMixin
|
|
10
|
-
from .goal_programming_mixin_base import
|
|
11
|
-
|
|
9
|
+
from .goal_programming_mixin_base import (
|
|
10
|
+
Goal,
|
|
11
|
+
StateGoal,
|
|
12
|
+
_EmptyEnsembleList,
|
|
13
|
+
_EmptyEnsembleOrderedDict,
|
|
14
|
+
_GoalConstraint,
|
|
15
|
+
_GoalProgrammingMixinBase,
|
|
16
|
+
)
|
|
12
17
|
from .single_pass_goal_programming_mixin import SinglePassGoalProgrammingMixin
|
|
13
18
|
from .timeseries import Timeseries
|
|
14
19
|
|
|
@@ -20,6 +25,7 @@ class MinAbsGoal(Goal):
|
|
|
20
25
|
class, the default order is 1 as absolute minimization is typically
|
|
21
26
|
desired for fully linear problems.
|
|
22
27
|
"""
|
|
28
|
+
|
|
23
29
|
order = 1
|
|
24
30
|
|
|
25
31
|
|
|
@@ -28,7 +34,6 @@ class MinAbsStateGoal(StateGoal, MinAbsGoal):
|
|
|
28
34
|
|
|
29
35
|
|
|
30
36
|
class _ConvertedMinAbsGoal(Goal):
|
|
31
|
-
|
|
32
37
|
order = 1
|
|
33
38
|
|
|
34
39
|
def __init__(self, abs_variable, is_path_goal, orig_goal):
|
|
@@ -83,7 +88,7 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
83
88
|
|
|
84
89
|
def bounds(self):
|
|
85
90
|
bounds = super().bounds()
|
|
86
|
-
for abs_var in
|
|
91
|
+
for abs_var in self.__problem_vars + self.__problem_path_vars:
|
|
87
92
|
bounds[abs_var.name()] = (0.0, np.inf)
|
|
88
93
|
return bounds
|
|
89
94
|
|
|
@@ -122,20 +127,29 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
122
127
|
|
|
123
128
|
for goal in goals:
|
|
124
129
|
if not isinstance(goal, MinAbsGoal):
|
|
125
|
-
raise Exception(
|
|
130
|
+
raise Exception(
|
|
131
|
+
"Absolute goal not an instance of MinAbsGoal for goal {}".format(goal)
|
|
132
|
+
)
|
|
126
133
|
|
|
127
134
|
if goal.function_range != (np.nan, np.nan):
|
|
128
|
-
raise Exception(
|
|
135
|
+
raise Exception(
|
|
136
|
+
"Absolute goal function is only allowed for minimization for goal {}".format(
|
|
137
|
+
goal
|
|
138
|
+
)
|
|
139
|
+
)
|
|
129
140
|
|
|
130
141
|
if goal.order != 1:
|
|
131
|
-
raise Exception(
|
|
142
|
+
raise Exception(
|
|
143
|
+
"Absolute goal function is only allowed for order = 1 for goal {}".format(goal)
|
|
144
|
+
)
|
|
132
145
|
|
|
133
146
|
if goal.weight <= 0:
|
|
134
|
-
raise Exception(
|
|
147
|
+
raise Exception(
|
|
148
|
+
"Absolute goal function is only allowed for weight > 0 for goal {}".format(goal)
|
|
149
|
+
)
|
|
135
150
|
|
|
136
151
|
@staticmethod
|
|
137
152
|
def __convert_goals(goals, sym_index, ensemble_size, is_path_goal):
|
|
138
|
-
|
|
139
153
|
# Replace absolute minimization goals with a new goal, and some
|
|
140
154
|
# additional hard constraints.
|
|
141
155
|
constraints = [[] for ensemble_member in range(ensemble_size)]
|
|
@@ -160,15 +174,24 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
160
174
|
# original goal function, such that it corresponds to its absolute
|
|
161
175
|
# value when minimizing.
|
|
162
176
|
for ensemble_member in range(ensemble_size):
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
177
|
+
|
|
178
|
+
def _constraint_func(
|
|
179
|
+
problem,
|
|
180
|
+
sign,
|
|
181
|
+
abs_variable=abs_variable,
|
|
182
|
+
ensemble_member=ensemble_member,
|
|
183
|
+
goal=goal,
|
|
184
|
+
is_path_goal=is_path_goal,
|
|
185
|
+
):
|
|
166
186
|
if is_path_goal:
|
|
167
187
|
abs_variable = problem.variable(abs_variable.name())
|
|
168
188
|
else:
|
|
169
189
|
abs_variable = problem.extra_variable(abs_variable.name(), ensemble_member)
|
|
170
190
|
|
|
171
|
-
return
|
|
191
|
+
return (
|
|
192
|
+
abs_variable
|
|
193
|
+
+ sign * goal.function(problem, ensemble_member) / goal.function_nominal
|
|
194
|
+
)
|
|
172
195
|
|
|
173
196
|
_pos = functools.partial(_constraint_func, sign=1)
|
|
174
197
|
_neg = functools.partial(_constraint_func, sign=-1)
|
|
@@ -188,16 +211,17 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
188
211
|
seed = [{} for ensemble_member in range(self.ensemble_size)]
|
|
189
212
|
|
|
190
213
|
for goal in goals:
|
|
191
|
-
|
|
192
214
|
assert isinstance(goal, _ConvertedMinAbsGoal)
|
|
193
215
|
|
|
194
216
|
for ensemble_member in range(self.ensemble_size):
|
|
195
217
|
if is_path_goal:
|
|
196
|
-
expr = self.map_path_expression(
|
|
218
|
+
expr = self.map_path_expression(
|
|
219
|
+
goal.orig_goal.function(self, ensemble_member), ensemble_member
|
|
220
|
+
)
|
|
197
221
|
else:
|
|
198
222
|
expr = goal.orig_goal.function(self, ensemble_member)
|
|
199
223
|
|
|
200
|
-
function = ca.Function(
|
|
224
|
+
function = ca.Function("f", [self.solver_input], [expr])
|
|
201
225
|
value = np.array(function(self.solver_output))
|
|
202
226
|
|
|
203
227
|
assert value.ndim == 2
|
|
@@ -244,32 +268,49 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
244
268
|
# We want to have consistent naming with GPMixin for our auxiliary
|
|
245
269
|
# variables. We therefore need to loop over all priorities, regardless
|
|
246
270
|
# of whether there are any MinAbsGoals in it or not.
|
|
247
|
-
priorities = {
|
|
248
|
-
|
|
271
|
+
priorities = {
|
|
272
|
+
int(goal.priority)
|
|
273
|
+
for goal in itertools.chain(goals, path_goals, self.goals(), self.path_goals())
|
|
274
|
+
if not goal.is_empty
|
|
275
|
+
}
|
|
249
276
|
|
|
250
277
|
subproblems = []
|
|
251
278
|
for priority in sorted(priorities):
|
|
252
|
-
subproblems.append(
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
279
|
+
subproblems.append(
|
|
280
|
+
(
|
|
281
|
+
priority,
|
|
282
|
+
[
|
|
283
|
+
goal
|
|
284
|
+
for goal in goals
|
|
285
|
+
if int(goal.priority) == priority and not goal.is_empty
|
|
286
|
+
],
|
|
287
|
+
[
|
|
288
|
+
goal
|
|
289
|
+
for goal in path_goals
|
|
290
|
+
if int(goal.priority) == priority and not goal.is_empty
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
)
|
|
256
294
|
|
|
257
295
|
# Rewrite absolute minimization goals.
|
|
258
296
|
self.__converted_goals = []
|
|
259
297
|
self.__converted_path_goals = []
|
|
260
298
|
|
|
261
299
|
for i, (priority, goals, path_goals) in enumerate(subproblems):
|
|
262
|
-
|
|
263
|
-
|
|
300
|
+
(
|
|
301
|
+
goals,
|
|
264
302
|
self.__subproblem_constraints[priority],
|
|
265
|
-
self.__subproblem_vars[priority]
|
|
303
|
+
self.__subproblem_vars[priority],
|
|
304
|
+
) = self.__convert_goals(goals, i, self.ensemble_size, False)
|
|
266
305
|
|
|
267
306
|
self.__converted_goals.extend(goals)
|
|
268
307
|
self.__subproblem_abs_goals[priority] = goals
|
|
269
308
|
|
|
270
|
-
(
|
|
309
|
+
(
|
|
310
|
+
path_goals,
|
|
271
311
|
self.__subproblem_path_constraints[priority],
|
|
272
|
-
self.__subproblem_path_vars[priority]
|
|
312
|
+
self.__subproblem_path_vars[priority],
|
|
313
|
+
) = self.__convert_goals(path_goals, i, self.ensemble_size, True)
|
|
273
314
|
|
|
274
315
|
self.__converted_path_goals.extend(path_goals)
|
|
275
316
|
self.__subproblem_path_abs_goals[priority] = path_goals
|
|
@@ -307,7 +348,9 @@ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
|
|
|
307
348
|
# priority.
|
|
308
349
|
if not self.__first_run and isinstance(self, GoalProgrammingMixin):
|
|
309
350
|
self.__seeds = self.__calculate_seed(self.__subproblem_abs_goals[priority], False)
|
|
310
|
-
self.__path_seeds = self.__calculate_seed(
|
|
351
|
+
self.__path_seeds = self.__calculate_seed(
|
|
352
|
+
self.__subproblem_path_abs_goals[priority], True
|
|
353
|
+
)
|
|
311
354
|
|
|
312
355
|
self.__first_run = False
|
|
313
356
|
|
|
@@ -3,11 +3,8 @@ import logging
|
|
|
3
3
|
from typing import Dict, Union
|
|
4
4
|
|
|
5
5
|
import casadi as ca
|
|
6
|
-
|
|
7
6
|
import numpy as np
|
|
8
|
-
|
|
9
7
|
import pkg_resources
|
|
10
|
-
|
|
11
8
|
import pymoca
|
|
12
9
|
import pymoca.backends.casadi.api
|
|
13
10
|
|
|
@@ -28,7 +25,8 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
28
25
|
During preprocessing, the Modelica files located inside the ``model`` subfolder are loaded.
|
|
29
26
|
|
|
30
27
|
:cvar modelica_library_folders:
|
|
31
|
-
Folders in which any referenced Modelica libraries are to be found.
|
|
28
|
+
Folders in which any referenced Modelica libraries are to be found.
|
|
29
|
+
Default is an empty list.
|
|
32
30
|
"""
|
|
33
31
|
|
|
34
32
|
# Folders in which the referenced Modelica libraries are found
|
|
@@ -36,35 +34,38 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
36
34
|
|
|
37
35
|
def __init__(self, **kwargs):
|
|
38
36
|
# Check arguments
|
|
39
|
-
assert
|
|
37
|
+
assert "model_folder" in kwargs
|
|
40
38
|
|
|
41
39
|
# Log pymoca version
|
|
42
40
|
logger.debug("Using pymoca {}.".format(pymoca.__version__))
|
|
43
41
|
|
|
44
42
|
# Transfer model from the Modelica .mo file to CasADi using pymoca
|
|
45
|
-
if
|
|
46
|
-
model_name = kwargs[
|
|
43
|
+
if "model_name" in kwargs:
|
|
44
|
+
model_name = kwargs["model_name"]
|
|
47
45
|
else:
|
|
48
|
-
if hasattr(self,
|
|
46
|
+
if hasattr(self, "model_name"):
|
|
49
47
|
model_name = self.model_name
|
|
50
48
|
else:
|
|
51
49
|
model_name = self.__class__.__name__
|
|
52
50
|
|
|
53
51
|
self.__pymoca_model = pymoca.backends.casadi.api.transfer_model(
|
|
54
|
-
kwargs[
|
|
52
|
+
kwargs["model_folder"], model_name, self.compiler_options()
|
|
53
|
+
)
|
|
55
54
|
|
|
56
55
|
# Extract the CasADi MX variables used in the model
|
|
57
56
|
self.__mx = {}
|
|
58
|
-
self.__mx[
|
|
59
|
-
self.__mx[
|
|
60
|
-
self.__mx[
|
|
61
|
-
self.__mx[
|
|
62
|
-
self.__mx[
|
|
63
|
-
self.__mx[
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.__mx[
|
|
57
|
+
self.__mx["time"] = [self.__pymoca_model.time]
|
|
58
|
+
self.__mx["states"] = [v.symbol for v in self.__pymoca_model.states]
|
|
59
|
+
self.__mx["derivatives"] = [v.symbol for v in self.__pymoca_model.der_states]
|
|
60
|
+
self.__mx["algebraics"] = [v.symbol for v in self.__pymoca_model.alg_states]
|
|
61
|
+
self.__mx["parameters"] = [v.symbol for v in self.__pymoca_model.parameters]
|
|
62
|
+
self.__mx["string_parameters"] = [
|
|
63
|
+
v.name
|
|
64
|
+
for v in (*self.__pymoca_model.string_parameters, *self.__pymoca_model.string_constants)
|
|
65
|
+
]
|
|
66
|
+
self.__mx["control_inputs"] = []
|
|
67
|
+
self.__mx["constant_inputs"] = []
|
|
68
|
+
self.__mx["lookup_tables"] = []
|
|
68
69
|
|
|
69
70
|
# Merge with user-specified delayed feedback
|
|
70
71
|
for v in self.__pymoca_model.inputs:
|
|
@@ -72,29 +73,31 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
72
73
|
# Delayed feedback variables are local to each ensemble, and
|
|
73
74
|
# therefore belong to the collection of algebraic variables,
|
|
74
75
|
# rather than to the control inputs.
|
|
75
|
-
self.__mx[
|
|
76
|
+
self.__mx["algebraics"].append(v.symbol)
|
|
76
77
|
else:
|
|
77
|
-
if v.symbol.name() in kwargs.get(
|
|
78
|
-
self.__mx[
|
|
78
|
+
if v.symbol.name() in kwargs.get("lookup_tables", []):
|
|
79
|
+
self.__mx["lookup_tables"].append(v.symbol)
|
|
79
80
|
elif v.fixed:
|
|
80
|
-
self.__mx[
|
|
81
|
+
self.__mx["constant_inputs"].append(v.symbol)
|
|
81
82
|
else:
|
|
82
|
-
self.__mx[
|
|
83
|
+
self.__mx["control_inputs"].append(v.symbol)
|
|
83
84
|
|
|
84
85
|
# Initialize nominals and types
|
|
85
86
|
# These are not in @cached dictionary properties for backwards compatibility.
|
|
86
87
|
self.__python_types = AliasDict(self.alias_relation)
|
|
87
88
|
for v in itertools.chain(
|
|
88
|
-
|
|
89
|
+
self.__pymoca_model.states, self.__pymoca_model.alg_states, self.__pymoca_model.inputs
|
|
90
|
+
):
|
|
89
91
|
self.__python_types[v.symbol.name()] = v.python_type
|
|
90
92
|
|
|
91
93
|
# Initialize dae, initial residuals, as well as delay arguments
|
|
92
94
|
# These are not in @cached dictionary properties so that we need to create the list
|
|
93
95
|
# of function arguments only once.
|
|
94
|
-
variable_lists = [
|
|
96
|
+
variable_lists = ["states", "der_states", "alg_states", "inputs", "constants", "parameters"]
|
|
95
97
|
function_arguments = [self.__pymoca_model.time] + [
|
|
96
98
|
ca.veccat(*[v.symbol for v in getattr(self.__pymoca_model, variable_list)])
|
|
97
|
-
for variable_list in variable_lists
|
|
99
|
+
for variable_list in variable_lists
|
|
100
|
+
]
|
|
98
101
|
|
|
99
102
|
self.__dae_residual = self.__pymoca_model.dae_residual_function(*function_arguments)
|
|
100
103
|
if self.__dae_residual is None:
|
|
@@ -106,18 +109,36 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
106
109
|
|
|
107
110
|
# Log variables in debug mode
|
|
108
111
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
109
|
-
logger.debug(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
logger.debug(
|
|
120
|
-
|
|
112
|
+
logger.debug(
|
|
113
|
+
"ModelicaMixin: Found states {}".format(
|
|
114
|
+
", ".join([var.name() for var in self.__mx["states"]])
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
logger.debug(
|
|
118
|
+
"ModelicaMixin: Found derivatives {}".format(
|
|
119
|
+
", ".join([var.name() for var in self.__mx["derivatives"]])
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
logger.debug(
|
|
123
|
+
"ModelicaMixin: Found algebraics {}".format(
|
|
124
|
+
", ".join([var.name() for var in self.__mx["algebraics"]])
|
|
125
|
+
)
|
|
126
|
+
)
|
|
127
|
+
logger.debug(
|
|
128
|
+
"ModelicaMixin: Found control inputs {}".format(
|
|
129
|
+
", ".join([var.name() for var in self.__mx["control_inputs"]])
|
|
130
|
+
)
|
|
131
|
+
)
|
|
132
|
+
logger.debug(
|
|
133
|
+
"ModelicaMixin: Found constant inputs {}".format(
|
|
134
|
+
", ".join([var.name() for var in self.__mx["constant_inputs"]])
|
|
135
|
+
)
|
|
136
|
+
)
|
|
137
|
+
logger.debug(
|
|
138
|
+
"ModelicaMixin: Found parameters {}".format(
|
|
139
|
+
", ".join([var.name() for var in self.__mx["parameters"]])
|
|
140
|
+
)
|
|
141
|
+
)
|
|
121
142
|
|
|
122
143
|
# Call parent class first for default behaviour.
|
|
123
144
|
super().__init__(**kwargs)
|
|
@@ -125,55 +146,56 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
125
146
|
@cached
|
|
126
147
|
def compiler_options(self) -> Dict[str, Union[str, bool]]:
|
|
127
148
|
"""
|
|
128
|
-
Subclasses can configure the `pymoca <http://github.com/pymoca/pymoca>`_ compiler options
|
|
149
|
+
Subclasses can configure the `pymoca <http://github.com/pymoca/pymoca>`_ compiler options
|
|
150
|
+
here.
|
|
129
151
|
|
|
130
|
-
:returns:
|
|
152
|
+
:returns:
|
|
153
|
+
A dictionary of pymoca compiler options. See the pymoca documentation for details.
|
|
131
154
|
"""
|
|
132
155
|
|
|
133
156
|
# Default options
|
|
134
157
|
compiler_options = {}
|
|
135
158
|
|
|
136
159
|
# Expand vector states to multiple scalar component states.
|
|
137
|
-
compiler_options[
|
|
160
|
+
compiler_options["expand_vectors"] = True
|
|
138
161
|
|
|
139
162
|
# Where imported model libraries are located.
|
|
140
163
|
library_folders = self.modelica_library_folders.copy()
|
|
141
164
|
|
|
142
|
-
for ep in pkg_resources.iter_entry_points(group=
|
|
165
|
+
for ep in pkg_resources.iter_entry_points(group="rtctools.libraries.modelica"):
|
|
143
166
|
if ep.name == "library_folder":
|
|
144
|
-
library_folders.append(
|
|
145
|
-
pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
|
|
167
|
+
library_folders.append(pkg_resources.resource_filename(ep.module_name, ep.attrs[0]))
|
|
146
168
|
|
|
147
|
-
compiler_options[
|
|
169
|
+
compiler_options["library_folders"] = library_folders
|
|
148
170
|
|
|
149
171
|
# Eliminate equations of the type 'var = const'.
|
|
150
|
-
compiler_options[
|
|
172
|
+
compiler_options["eliminate_constant_assignments"] = True
|
|
151
173
|
|
|
152
174
|
# Eliminate constant symbols from model, replacing them with the values
|
|
153
175
|
# specified in the model.
|
|
154
|
-
compiler_options[
|
|
176
|
+
compiler_options["replace_constant_values"] = True
|
|
155
177
|
|
|
156
178
|
# Replace any constant expressions into the model.
|
|
157
|
-
compiler_options[
|
|
179
|
+
compiler_options["replace_constant_expressions"] = True
|
|
158
180
|
|
|
159
181
|
# Replace any parameter expressions into the model.
|
|
160
|
-
compiler_options[
|
|
182
|
+
compiler_options["replace_parameter_expressions"] = True
|
|
161
183
|
|
|
162
184
|
# Eliminate variables starting with underscores.
|
|
163
|
-
compiler_options[
|
|
185
|
+
compiler_options["eliminable_variable_expression"] = r"(.*[.]|^)_\w+(\[[\d,]+\])?\Z"
|
|
164
186
|
|
|
165
187
|
# Pymoca currently requires `expand_mx` to be set for
|
|
166
188
|
# `eliminable_variable_expression` to work.
|
|
167
|
-
compiler_options[
|
|
189
|
+
compiler_options["expand_mx"] = True
|
|
168
190
|
|
|
169
191
|
# Automatically detect and eliminate alias variables.
|
|
170
|
-
compiler_options[
|
|
192
|
+
compiler_options["detect_aliases"] = True
|
|
171
193
|
|
|
172
194
|
# Disallow aliasing to derivative states
|
|
173
|
-
compiler_options[
|
|
195
|
+
compiler_options["allow_derivative_aliases"] = False
|
|
174
196
|
|
|
175
197
|
# Cache the model on disk
|
|
176
|
-
compiler_options[
|
|
198
|
+
compiler_options["cache"] = True
|
|
177
199
|
|
|
178
200
|
# Done
|
|
179
201
|
return compiler_options
|
|
@@ -182,9 +204,10 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
182
204
|
delayed_feedback = super().delayed_feedback()
|
|
183
205
|
|
|
184
206
|
# Create delayed feedback
|
|
185
|
-
for delay_state, delay_argument in zip(
|
|
186
|
-
|
|
187
|
-
|
|
207
|
+
for delay_state, delay_argument in zip(
|
|
208
|
+
self.__pymoca_model.delay_states, self.__pymoca_model.delay_arguments
|
|
209
|
+
):
|
|
210
|
+
delayed_feedback.append((delay_argument.expr, delay_state, delay_argument.duration))
|
|
188
211
|
return delayed_feedback
|
|
189
212
|
|
|
190
213
|
@property
|
|
@@ -198,9 +221,8 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
198
221
|
@property
|
|
199
222
|
@cached
|
|
200
223
|
def output_variables(self):
|
|
201
|
-
output_variables = [ca.MX.sym(variable)
|
|
202
|
-
|
|
203
|
-
output_variables.extend(self.__mx['control_inputs'])
|
|
224
|
+
output_variables = [ca.MX.sym(variable) for variable in self.__pymoca_model.outputs]
|
|
225
|
+
output_variables.extend(self.__mx["control_inputs"])
|
|
204
226
|
return output_variables
|
|
205
227
|
|
|
206
228
|
@cached
|
|
@@ -234,7 +256,9 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
234
256
|
|
|
235
257
|
# Parameter values
|
|
236
258
|
parameters = self.parameters(ensemble_member)
|
|
237
|
-
parameter_values = [
|
|
259
|
+
parameter_values = [
|
|
260
|
+
parameters.get(param.name(), param) for param in self.__mx["parameters"]
|
|
261
|
+
]
|
|
238
262
|
|
|
239
263
|
# Initial conditions obtained from start attributes.
|
|
240
264
|
for v in self.__pymoca_model.states:
|
|
@@ -245,16 +269,24 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
245
269
|
if isinstance(start, ca.MX):
|
|
246
270
|
# If start contains symbolics, try substituting parameter values
|
|
247
271
|
if isinstance(start, ca.MX) and not start.is_constant():
|
|
248
|
-
[start] = substitute_in_external(
|
|
272
|
+
[start] = substitute_in_external(
|
|
273
|
+
[start], self.__mx["parameters"], parameter_values
|
|
274
|
+
)
|
|
249
275
|
if not start.is_constant() or np.isnan(float(start)):
|
|
250
|
-
raise Exception(
|
|
276
|
+
raise Exception(
|
|
277
|
+
"ModelicaMixin: Could not resolve initial value for {}".format(
|
|
278
|
+
sym_name
|
|
279
|
+
)
|
|
280
|
+
)
|
|
251
281
|
|
|
252
282
|
start = v.python_type(start)
|
|
253
283
|
|
|
254
284
|
history[sym_name] = Timeseries(initial_time, start)
|
|
255
285
|
|
|
256
286
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
257
|
-
logger.debug(
|
|
287
|
+
logger.debug(
|
|
288
|
+
"ModelicaMixin: Initial state variable {} = {}".format(sym_name, start)
|
|
289
|
+
)
|
|
258
290
|
|
|
259
291
|
return history
|
|
260
292
|
|
|
@@ -269,11 +301,14 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
269
301
|
|
|
270
302
|
# Parameter values
|
|
271
303
|
parameters = self.parameters(0)
|
|
272
|
-
parameter_values = [
|
|
304
|
+
parameter_values = [
|
|
305
|
+
parameters.get(param.name(), param) for param in self.__mx["parameters"]
|
|
306
|
+
]
|
|
273
307
|
|
|
274
308
|
# Load additional bounds from model
|
|
275
309
|
for v in itertools.chain(
|
|
276
|
-
|
|
310
|
+
self.__pymoca_model.states, self.__pymoca_model.alg_states, self.__pymoca_model.inputs
|
|
311
|
+
):
|
|
277
312
|
sym_name = v.symbol.name()
|
|
278
313
|
|
|
279
314
|
try:
|
|
@@ -286,16 +321,20 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
286
321
|
|
|
287
322
|
m_ = v.min
|
|
288
323
|
if isinstance(m_, ca.MX) and not m_.is_constant():
|
|
289
|
-
[m_] = substitute_in_external([m_], self.__mx[
|
|
324
|
+
[m_] = substitute_in_external([m_], self.__mx["parameters"], parameter_values)
|
|
290
325
|
if not m_.is_constant() or np.isnan(float(m_)):
|
|
291
|
-
raise Exception(
|
|
326
|
+
raise Exception(
|
|
327
|
+
"Could not resolve lower bound for variable {}".format(sym_name)
|
|
328
|
+
)
|
|
292
329
|
m_ = float(m_)
|
|
293
330
|
|
|
294
331
|
M_ = v.max
|
|
295
332
|
if isinstance(M_, ca.MX) and not M_.is_constant():
|
|
296
|
-
[M_] = substitute_in_external([M_], self.__mx[
|
|
333
|
+
[M_] = substitute_in_external([M_], self.__mx["parameters"], parameter_values)
|
|
297
334
|
if not M_.is_constant() or np.isnan(float(M_)):
|
|
298
|
-
raise Exception(
|
|
335
|
+
raise Exception(
|
|
336
|
+
"Could not resolve upper bound for variable {}".format(sym_name)
|
|
337
|
+
)
|
|
299
338
|
M_ = float(M_)
|
|
300
339
|
|
|
301
340
|
# We take the intersection of all provided bounds
|
|
@@ -313,7 +352,9 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
313
352
|
|
|
314
353
|
# Parameter values
|
|
315
354
|
parameters = self.parameters(ensemble_member)
|
|
316
|
-
parameter_values = [
|
|
355
|
+
parameter_values = [
|
|
356
|
+
parameters.get(param.name(), param) for param in self.__mx["parameters"]
|
|
357
|
+
]
|
|
317
358
|
|
|
318
359
|
# Load seeds
|
|
319
360
|
for var in itertools.chain(self.__pymoca_model.states, self.__pymoca_model.alg_states):
|
|
@@ -328,9 +369,13 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
328
369
|
|
|
329
370
|
# If start contains symbolics, try substituting parameter values
|
|
330
371
|
if isinstance(start, ca.MX) and not start.is_constant():
|
|
331
|
-
[start] = substitute_in_external(
|
|
372
|
+
[start] = substitute_in_external(
|
|
373
|
+
[start], self.__mx["parameters"], parameter_values
|
|
374
|
+
)
|
|
332
375
|
if not start.is_constant() or np.isnan(float(start)):
|
|
333
|
-
logger.error(
|
|
376
|
+
logger.error(
|
|
377
|
+
"ModelicaMixin: Could not resolve seed value for {}".format(sym_name)
|
|
378
|
+
)
|
|
334
379
|
continue
|
|
335
380
|
|
|
336
381
|
times = self.times(sym_name)
|
|
@@ -353,25 +398,31 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
353
398
|
@property
|
|
354
399
|
@cached
|
|
355
400
|
def __nominals(self):
|
|
356
|
-
|
|
357
401
|
# Make the dict
|
|
358
402
|
nominal_dict = AliasDict(self.alias_relation)
|
|
359
403
|
|
|
360
404
|
# Grab parameters and their values
|
|
361
405
|
parameters = self.parameters(0)
|
|
362
|
-
parameter_values = [
|
|
406
|
+
parameter_values = [
|
|
407
|
+
parameters.get(param.name(), param) for param in self.__mx["parameters"]
|
|
408
|
+
]
|
|
363
409
|
|
|
364
410
|
# Iterate over nominalizable states
|
|
365
411
|
for v in itertools.chain(
|
|
366
|
-
|
|
412
|
+
self.__pymoca_model.states, self.__pymoca_model.alg_states, self.__pymoca_model.inputs
|
|
413
|
+
):
|
|
367
414
|
sym_name = v.symbol.name()
|
|
368
415
|
nominal = v.nominal
|
|
369
416
|
|
|
370
417
|
# If nominal contains parameter symbols, substitute them
|
|
371
418
|
if isinstance(nominal, ca.MX) and not nominal.is_constant():
|
|
372
|
-
[nominal] = substitute_in_external(
|
|
419
|
+
[nominal] = substitute_in_external(
|
|
420
|
+
[nominal], self.__mx["parameters"], parameter_values
|
|
421
|
+
)
|
|
373
422
|
if not nominal.is_constant() or np.isnan(float(nominal)):
|
|
374
|
-
logger.error(
|
|
423
|
+
logger.error(
|
|
424
|
+
"ModelicaMixin: Could not resolve nominal value for {}".format(sym_name)
|
|
425
|
+
)
|
|
375
426
|
continue
|
|
376
427
|
|
|
377
428
|
nominal = float(nominal)
|
|
@@ -387,8 +438,11 @@ class ModelicaMixin(OptimizationProblem):
|
|
|
387
438
|
nominal_dict[sym_name] = nominal
|
|
388
439
|
|
|
389
440
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
390
|
-
logger.debug(
|
|
391
|
-
|
|
441
|
+
logger.debug(
|
|
442
|
+
"ModelicaMixin: Set nominal value for variable {} to {}".format(
|
|
443
|
+
sym_name, nominal
|
|
444
|
+
)
|
|
445
|
+
)
|
|
392
446
|
else:
|
|
393
447
|
logger.warning("ModelicaMixin: Could not set nominal value for {}".format(sym_name))
|
|
394
448
|
|