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
|
@@ -2,14 +2,19 @@ import itertools
|
|
|
2
2
|
import logging
|
|
3
3
|
import warnings
|
|
4
4
|
from abc import ABCMeta, abstractmethod
|
|
5
|
+
from typing import Dict, Union
|
|
5
6
|
|
|
6
7
|
import casadi as ca
|
|
7
|
-
|
|
8
8
|
import numpy as np
|
|
9
9
|
|
|
10
10
|
from rtctools._internal.alias_tools import AliasDict
|
|
11
|
-
from rtctools._internal.casadi_helpers import
|
|
12
|
-
interpolate,
|
|
11
|
+
from rtctools._internal.casadi_helpers import (
|
|
12
|
+
interpolate,
|
|
13
|
+
is_affine,
|
|
14
|
+
nullvertcat,
|
|
15
|
+
reduce_matvec,
|
|
16
|
+
substitute_in_external,
|
|
17
|
+
)
|
|
13
18
|
from rtctools._internal.debug_check_helpers import DebugLevel, debug_check
|
|
14
19
|
|
|
15
20
|
from .optimization_problem import OptimizationProblem
|
|
@@ -25,6 +30,11 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
25
30
|
Collocation means that the discretized model equations are included as constraints
|
|
26
31
|
between state variables in the optimization problem.
|
|
27
32
|
|
|
33
|
+
Integration means that the model equations are solved from one time step to the next
|
|
34
|
+
in a sequential fashion, using a rootfinding algorithm at each and every step. The
|
|
35
|
+
results of the integration procedure feature as inputs to the objective functions
|
|
36
|
+
as well as to any constraints that do not originate from the DAE model.
|
|
37
|
+
|
|
28
38
|
.. note::
|
|
29
39
|
|
|
30
40
|
To ensure that your optimization problem only has globally optimal solutions,
|
|
@@ -44,24 +54,33 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
44
54
|
|
|
45
55
|
def __init__(self, **kwargs):
|
|
46
56
|
# Variables that will be optimized
|
|
47
|
-
self.dae_variables[
|
|
48
|
-
|
|
57
|
+
self.dae_variables["free_variables"] = (
|
|
58
|
+
self.dae_variables["states"]
|
|
59
|
+
+ self.dae_variables["algebraics"]
|
|
60
|
+
+ self.dae_variables["control_inputs"]
|
|
61
|
+
)
|
|
49
62
|
|
|
50
63
|
# Cache names of states
|
|
51
|
-
self.__differentiated_states = [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
self.
|
|
55
|
-
|
|
64
|
+
self.__differentiated_states = [
|
|
65
|
+
variable.name() for variable in self.dae_variables["states"]
|
|
66
|
+
]
|
|
67
|
+
self.__differentiated_states_map = {
|
|
68
|
+
v: i for i, v in enumerate(self.__differentiated_states)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
self.__algebraic_states = [variable.name() for variable in self.dae_variables["algebraics"]]
|
|
56
72
|
self.__algebraic_states_map = {v: i for i, v in enumerate(self.__algebraic_states)}
|
|
57
73
|
|
|
58
|
-
self.__controls = [variable.name()
|
|
59
|
-
for variable in self.dae_variables['control_inputs']]
|
|
74
|
+
self.__controls = [variable.name() for variable in self.dae_variables["control_inputs"]]
|
|
60
75
|
self.__controls_map = {v: i for i, v in enumerate(self.__controls)}
|
|
61
76
|
|
|
62
|
-
self.__derivative_names = [
|
|
77
|
+
self.__derivative_names = [
|
|
78
|
+
variable.name() for variable in self.dae_variables["derivatives"]
|
|
79
|
+
]
|
|
63
80
|
|
|
64
|
-
self.__initial_derivative_names = [
|
|
81
|
+
self.__initial_derivative_names = [
|
|
82
|
+
"initial_" + variable for variable in self.__derivative_names
|
|
83
|
+
]
|
|
65
84
|
|
|
66
85
|
self.__initial_derivative_nominals = {}
|
|
67
86
|
|
|
@@ -73,12 +92,13 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
73
92
|
# Create dictionary of variables so that we have O(1) state lookup available
|
|
74
93
|
self.__variables = AliasDict(self.alias_relation)
|
|
75
94
|
for var in itertools.chain(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
95
|
+
self.dae_variables["states"],
|
|
96
|
+
self.dae_variables["algebraics"],
|
|
97
|
+
self.dae_variables["control_inputs"],
|
|
98
|
+
self.dae_variables["constant_inputs"],
|
|
99
|
+
self.dae_variables["parameters"],
|
|
100
|
+
self.dae_variables["time"],
|
|
101
|
+
):
|
|
82
102
|
self.__variables[var.name()] = var
|
|
83
103
|
|
|
84
104
|
# Call super
|
|
@@ -106,16 +126,11 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
106
126
|
return self.INTERPOLATION_LINEAR
|
|
107
127
|
|
|
108
128
|
@property
|
|
109
|
-
def
|
|
129
|
+
def integrate_states(self):
|
|
110
130
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.. warning:: This is an experimental feature.
|
|
114
|
-
|
|
115
|
-
.. deprecated:: 2.4
|
|
116
|
-
Support for integrated states will be removed in a future release.
|
|
131
|
+
TRUE if all states are to be integrated rather than collocated.
|
|
117
132
|
"""
|
|
118
|
-
return
|
|
133
|
+
return False
|
|
119
134
|
|
|
120
135
|
@property
|
|
121
136
|
def theta(self):
|
|
@@ -130,10 +145,11 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
130
145
|
|
|
131
146
|
.. math::
|
|
132
147
|
|
|
133
|
-
x_{i+1} = x_i + \Delta t \left[\theta f(x_{i+1}, u_{i+1})
|
|
148
|
+
x_{i+1} = x_i + \Delta t \left[\theta f(x_{i+1}, u_{i+1})
|
|
149
|
+
+ (1 - \theta) f(x_i, u_i)\right]
|
|
134
150
|
|
|
135
|
-
The default is :math:`\theta = 1`, resulting in the implicit or backward Euler method.
|
|
136
|
-
case, the control input at the initial time step is not used.
|
|
151
|
+
The default is :math:`\theta = 1`, resulting in the implicit or backward Euler method. Note
|
|
152
|
+
that in this case, the control input at the initial time step is not used.
|
|
137
153
|
|
|
138
154
|
Set :math:`\theta = 0` to use the explicit or forward Euler method. Note that in this
|
|
139
155
|
case, the control input at the final time step is not used.
|
|
@@ -152,10 +168,43 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
152
168
|
# N.B. Setting theta to any value strictly between 0 and 1 will cause
|
|
153
169
|
# algebraic equations to be solved in an average sense. This may
|
|
154
170
|
# induce unexpected oscillations.
|
|
155
|
-
# TODO Fix these issue by performing index reduction and splitting DAE into ODE and
|
|
156
|
-
# Theta then only applies to the ODE part.
|
|
171
|
+
# TODO Fix these issue by performing index reduction and splitting DAE into ODE and
|
|
172
|
+
# algebraic parts. Theta then only applies to the ODE part.
|
|
157
173
|
return 1.0
|
|
158
174
|
|
|
175
|
+
def map_options(self) -> Dict[str, Union[str, int]]:
|
|
176
|
+
"""
|
|
177
|
+
Returns a dictionary of CasADi ``map()`` options.
|
|
178
|
+
|
|
179
|
+
+---------------+-----------+---------------+
|
|
180
|
+
| Option | Type | Default value |
|
|
181
|
+
+===============+===========+===============+
|
|
182
|
+
| ``mode`` | ``str` | ``openmp`` |
|
|
183
|
+
+---------------+-----------+---------------+
|
|
184
|
+
| ``n_threads`` | ``int`` | ``None`` |
|
|
185
|
+
+---------------+-----------+---------------+
|
|
186
|
+
|
|
187
|
+
The ``mode`` option controls the mode of the ``map()`` call. Valid values include
|
|
188
|
+
``openmp``, ``thread``, and ``unroll``. See the CasADi and documentation for detailed
|
|
189
|
+
documentation on these modes.
|
|
190
|
+
|
|
191
|
+
The ``n_threads`` option controls the number of threads used when in ``thread`` mode.
|
|
192
|
+
|
|
193
|
+
.. note::
|
|
194
|
+
|
|
195
|
+
Not every CasADi build has support for OpenMP enabled. For such builds, the `thread`
|
|
196
|
+
mode offers an alternative parallelization mode.
|
|
197
|
+
|
|
198
|
+
.. note::
|
|
199
|
+
|
|
200
|
+
The use of ``expand=True`` in ``solver_options()`` may negate the parallelization
|
|
201
|
+
benefits obtained using ``map()``.
|
|
202
|
+
|
|
203
|
+
:returns: A dictionary of options for the `map()` call used to evaluate constraints on
|
|
204
|
+
every time stamp.
|
|
205
|
+
"""
|
|
206
|
+
return {"mode": "openmp"}
|
|
207
|
+
|
|
159
208
|
def transcribe(self):
|
|
160
209
|
# DAE residual
|
|
161
210
|
dae_residual = self.dae_residual
|
|
@@ -164,8 +213,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
164
213
|
initial_residual = self.initial_residual
|
|
165
214
|
|
|
166
215
|
logger.info(
|
|
167
|
-
|
|
168
|
-
|
|
216
|
+
f"Transcribing problem with a DAE of {dae_residual.size1()} equations, "
|
|
217
|
+
f"{len(self.times())} collocation points, "
|
|
218
|
+
f"and {len(self.dae_variables['free_variables'])} free variables"
|
|
219
|
+
)
|
|
169
220
|
|
|
170
221
|
# Reset dictionary of variables
|
|
171
222
|
for var in itertools.chain(self.path_variables, self.extra_variables):
|
|
@@ -173,13 +224,16 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
173
224
|
|
|
174
225
|
# Split the constant inputs into those used in the DAE, and additional
|
|
175
226
|
# ones used for just the objective and/or constraints
|
|
176
|
-
dae_constant_inputs_names = [x.name() for x in self.dae_variables[
|
|
227
|
+
dae_constant_inputs_names = [x.name() for x in self.dae_variables["constant_inputs"]]
|
|
177
228
|
extra_constant_inputs_name_and_size = []
|
|
178
229
|
for ensemble_member in range(self.ensemble_size):
|
|
179
230
|
extra_constant_inputs_name_and_size.extend(
|
|
180
|
-
[
|
|
181
|
-
|
|
182
|
-
|
|
231
|
+
[
|
|
232
|
+
(x, v.values.shape[1] if v.values.ndim > 1 else 1)
|
|
233
|
+
for x, v in self.constant_inputs(ensemble_member).items()
|
|
234
|
+
if x not in dae_constant_inputs_names
|
|
235
|
+
]
|
|
236
|
+
)
|
|
183
237
|
|
|
184
238
|
self.__extra_constant_inputs = []
|
|
185
239
|
for var_name, size in extra_constant_inputs_name_and_size:
|
|
@@ -188,17 +242,19 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
188
242
|
self.__extra_constant_inputs.append(var)
|
|
189
243
|
|
|
190
244
|
# Cache extra and path variable names, and variable sizes
|
|
191
|
-
self.__path_variable_names = [variable.name()
|
|
192
|
-
|
|
193
|
-
self.__extra_variable_names = [variable.name()
|
|
194
|
-
for variable in self.extra_variables]
|
|
245
|
+
self.__path_variable_names = [variable.name() for variable in self.path_variables]
|
|
246
|
+
self.__extra_variable_names = [variable.name() for variable in self.extra_variables]
|
|
195
247
|
|
|
196
248
|
# Cache the variable sizes, as a repeated call to .name() and .size1()
|
|
197
249
|
# is expensive due to SWIG call overhead.
|
|
198
250
|
self.__variable_sizes = {}
|
|
199
251
|
|
|
200
|
-
for variable in itertools.chain(
|
|
201
|
-
|
|
252
|
+
for variable in itertools.chain(
|
|
253
|
+
self.differentiated_states,
|
|
254
|
+
self.algebraic_states,
|
|
255
|
+
self.controls,
|
|
256
|
+
self.__initial_derivative_names,
|
|
257
|
+
):
|
|
202
258
|
self.__variable_sizes[variable] = 1
|
|
203
259
|
|
|
204
260
|
for mx_symbol, variable in zip(self.path_variables, self.__path_variable_names):
|
|
@@ -211,7 +267,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
211
267
|
# history has (roughly) identical time steps for the entire ensemble.
|
|
212
268
|
self.__initial_derivative_nominals = {}
|
|
213
269
|
history_0 = self.history(0)
|
|
214
|
-
for variable, initial_der_name in zip(
|
|
270
|
+
for variable, initial_der_name in zip(
|
|
271
|
+
self.__differentiated_states, self.__initial_derivative_names
|
|
272
|
+
):
|
|
215
273
|
times = self.times(variable)
|
|
216
274
|
default_time_step_size = 0
|
|
217
275
|
if len(times) > 1:
|
|
@@ -228,24 +286,43 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
228
286
|
|
|
229
287
|
if dt > 0:
|
|
230
288
|
self.__initial_derivative_nominals[initial_der_name] = (
|
|
231
|
-
self.variable_nominal(variable) / dt
|
|
289
|
+
self.variable_nominal(variable) / dt
|
|
290
|
+
)
|
|
232
291
|
else:
|
|
233
|
-
self.__initial_derivative_nominals[initial_der_name] = (
|
|
234
|
-
|
|
292
|
+
self.__initial_derivative_nominals[initial_der_name] = self.variable_nominal(
|
|
293
|
+
variable
|
|
294
|
+
)
|
|
235
295
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
296
|
+
# Check that the removed (because broken) integrated_states option is not used
|
|
297
|
+
try:
|
|
298
|
+
_ = self.integrated_states
|
|
299
|
+
except AttributeError:
|
|
300
|
+
# We expect there to be an error as users should use self.integrate_states
|
|
301
|
+
pass
|
|
302
|
+
else:
|
|
303
|
+
raise Exception(
|
|
304
|
+
"The integrated_states property is no longer supported. "
|
|
305
|
+
"Use integrate_states instead."
|
|
306
|
+
)
|
|
239
307
|
|
|
240
308
|
# Variables that are integrated states are not yet allowed to have size > 1
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
309
|
+
if self.integrate_states:
|
|
310
|
+
self.__integrated_states = [*self.differentiated_states, *self.algebraic_states]
|
|
311
|
+
|
|
312
|
+
for variable in self.__integrated_states:
|
|
313
|
+
if self.__variable_sizes.get(variable, 1) > 1:
|
|
314
|
+
raise NotImplementedError(
|
|
315
|
+
"Vector symbol not supported for integrated state '{}'".format(variable)
|
|
316
|
+
)
|
|
317
|
+
else:
|
|
318
|
+
self.__integrated_states = []
|
|
244
319
|
|
|
245
320
|
# The same holds for controls
|
|
246
321
|
for variable in self.controls:
|
|
247
322
|
if self.__variable_sizes.get(variable, 1) > 1:
|
|
248
|
-
raise NotImplementedError(
|
|
323
|
+
raise NotImplementedError(
|
|
324
|
+
"Vector symbol not supported for control state '{}'".format(variable)
|
|
325
|
+
)
|
|
249
326
|
|
|
250
327
|
# Collocation times
|
|
251
328
|
collocation_times = self.times()
|
|
@@ -256,7 +333,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
256
333
|
dynamic_parameter_names = set()
|
|
257
334
|
|
|
258
335
|
# Parameter symbols
|
|
259
|
-
symbolic_parameters = ca.vertcat(*self.dae_variables[
|
|
336
|
+
symbolic_parameters = ca.vertcat(*self.dae_variables["parameters"])
|
|
260
337
|
|
|
261
338
|
def _interpolate_constant_inputs(variables, raw_constant_inputs):
|
|
262
339
|
constant_inputs_interpolated = {}
|
|
@@ -265,13 +342,18 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
265
342
|
try:
|
|
266
343
|
constant_input = raw_constant_inputs[variable]
|
|
267
344
|
except KeyError:
|
|
268
|
-
raise Exception(
|
|
269
|
-
"No values found for constant input {}".format(variable))
|
|
345
|
+
raise Exception("No values found for constant input {}".format(variable))
|
|
270
346
|
else:
|
|
271
347
|
values = constant_input.values
|
|
272
348
|
interpolation_method = self.interpolation_method(variable)
|
|
273
349
|
constant_inputs_interpolated[variable] = self.interpolate(
|
|
274
|
-
collocation_times,
|
|
350
|
+
collocation_times,
|
|
351
|
+
constant_input.times,
|
|
352
|
+
values,
|
|
353
|
+
0.0,
|
|
354
|
+
0.0,
|
|
355
|
+
interpolation_method,
|
|
356
|
+
)
|
|
275
357
|
|
|
276
358
|
return constant_inputs_interpolated
|
|
277
359
|
|
|
@@ -283,26 +365,28 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
283
365
|
|
|
284
366
|
# Store parameters
|
|
285
367
|
parameters = self.parameters(ensemble_member)
|
|
286
|
-
parameter_values = [None] * len(self.dae_variables[
|
|
287
|
-
for i, symbol in enumerate(self.dae_variables[
|
|
368
|
+
parameter_values = [None] * len(self.dae_variables["parameters"])
|
|
369
|
+
for i, symbol in enumerate(self.dae_variables["parameters"]):
|
|
288
370
|
variable = symbol.name()
|
|
289
371
|
try:
|
|
290
372
|
parameter_values[i] = parameters[variable]
|
|
291
373
|
except KeyError:
|
|
292
|
-
raise Exception(
|
|
293
|
-
"No value specified for parameter {}".format(variable))
|
|
374
|
+
raise Exception("No value specified for parameter {}".format(variable))
|
|
294
375
|
|
|
295
376
|
if len(dynamic_parameters) > 0:
|
|
296
377
|
jac_1 = ca.jacobian(symbolic_parameters, ca.vertcat(*dynamic_parameters))
|
|
297
378
|
jac_2 = ca.jacobian(ca.vertcat(*parameter_values), ca.vertcat(*dynamic_parameters))
|
|
298
|
-
for i, symbol in enumerate(self.dae_variables[
|
|
379
|
+
for i, symbol in enumerate(self.dae_variables["parameters"]):
|
|
299
380
|
if jac_1[i, :].nnz() > 0 or jac_2[i, :].nnz() > 0:
|
|
300
381
|
dynamic_parameter_names.add(symbol.name())
|
|
301
382
|
|
|
302
|
-
if np.any(
|
|
383
|
+
if np.any(
|
|
384
|
+
[isinstance(value, ca.MX) and not value.is_constant() for value in parameter_values]
|
|
385
|
+
):
|
|
303
386
|
parameter_values = nullvertcat(*parameter_values)
|
|
304
387
|
[parameter_values] = substitute_in_external(
|
|
305
|
-
[parameter_values], self.dae_variables[
|
|
388
|
+
[parameter_values], self.dae_variables["parameters"], parameter_values
|
|
389
|
+
)
|
|
306
390
|
else:
|
|
307
391
|
parameter_values = nullvertcat(*parameter_values)
|
|
308
392
|
|
|
@@ -315,9 +399,11 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
315
399
|
raw_constant_inputs = self.constant_inputs(ensemble_member)
|
|
316
400
|
|
|
317
401
|
ensemble_data["constant_inputs"] = _interpolate_constant_inputs(
|
|
318
|
-
self.dae_variables[
|
|
402
|
+
self.dae_variables["constant_inputs"], raw_constant_inputs
|
|
403
|
+
)
|
|
319
404
|
ensemble_data["extra_constant_inputs"] = _interpolate_constant_inputs(
|
|
320
|
-
self.__extra_constant_inputs, raw_constant_inputs
|
|
405
|
+
self.__extra_constant_inputs, raw_constant_inputs
|
|
406
|
+
)
|
|
321
407
|
|
|
322
408
|
# Handle all extra constant input data uniformly as 2D arrays
|
|
323
409
|
for k, v in ensemble_data["extra_constant_inputs"].items():
|
|
@@ -327,12 +413,24 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
327
413
|
bounds = self.bounds()
|
|
328
414
|
|
|
329
415
|
# Initialize control discretization
|
|
330
|
-
|
|
331
|
-
|
|
416
|
+
(
|
|
417
|
+
control_size,
|
|
418
|
+
discrete_control,
|
|
419
|
+
lbx_control,
|
|
420
|
+
ubx_control,
|
|
421
|
+
x0_control,
|
|
422
|
+
indices_control,
|
|
423
|
+
) = self.discretize_controls(bounds)
|
|
332
424
|
|
|
333
425
|
# Initialize state discretization
|
|
334
|
-
|
|
335
|
-
|
|
426
|
+
(
|
|
427
|
+
state_size,
|
|
428
|
+
discrete_state,
|
|
429
|
+
lbx_state,
|
|
430
|
+
ubx_state,
|
|
431
|
+
x0_state,
|
|
432
|
+
indices_state,
|
|
433
|
+
) = self.discretize_states(bounds)
|
|
336
434
|
|
|
337
435
|
# Merge state vector offset dictionary
|
|
338
436
|
self.__indices = indices_control
|
|
@@ -345,7 +443,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
345
443
|
self.__indices[ensemble_member][key] = value
|
|
346
444
|
|
|
347
445
|
# Initialize vector of optimization symbols
|
|
348
|
-
X = ca.MX.sym(
|
|
446
|
+
X = ca.MX.sym("X", control_size + state_size)
|
|
349
447
|
self.__solver_input = X
|
|
350
448
|
|
|
351
449
|
# Later on, we will be slicing MX/SX objects a few times for vectorized operations (to
|
|
@@ -372,14 +470,14 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
372
470
|
|
|
373
471
|
x0 = np.zeros(X.size1())
|
|
374
472
|
|
|
375
|
-
discrete[:len(discrete_control)] = discrete_control
|
|
376
|
-
discrete[len(discrete_control):] = discrete_state
|
|
377
|
-
lbx[:len(lbx_control)] = lbx_control
|
|
378
|
-
lbx[len(lbx_control):] = lbx_state
|
|
379
|
-
ubx[:len(ubx_control)] = ubx_control
|
|
380
|
-
ubx[len(lbx_control):] = ubx_state
|
|
381
|
-
x0[:len(x0_control)] = x0_control
|
|
382
|
-
x0[len(x0_control):] = x0_state
|
|
473
|
+
discrete[: len(discrete_control)] = discrete_control
|
|
474
|
+
discrete[len(discrete_control) :] = discrete_state
|
|
475
|
+
lbx[: len(lbx_control)] = lbx_control
|
|
476
|
+
lbx[len(lbx_control) :] = lbx_state
|
|
477
|
+
ubx[: len(ubx_control)] = ubx_control
|
|
478
|
+
ubx[len(lbx_control) :] = ubx_state
|
|
479
|
+
x0[: len(x0_control)] = x0_control
|
|
480
|
+
x0[len(x0_control) :] = x0_state
|
|
383
481
|
|
|
384
482
|
# Provide a state for self.state_at() and self.der() to work with.
|
|
385
483
|
self.__control_size = control_size
|
|
@@ -387,42 +485,46 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
387
485
|
self.__symbol_cache = {}
|
|
388
486
|
|
|
389
487
|
# Free variables for the collocated optimization problem
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
for variable in self.dae_variables['control_inputs']:
|
|
398
|
-
# TODO treat these separately.
|
|
399
|
-
collocated_variables.append(variable)
|
|
488
|
+
if self.integrate_states:
|
|
489
|
+
integrated_variables = self.dae_variables["states"] + self.dae_variables["algebraics"]
|
|
490
|
+
collocated_variables = []
|
|
491
|
+
else:
|
|
492
|
+
integrated_variables = []
|
|
493
|
+
collocated_variables = self.dae_variables["states"] + self.dae_variables["algebraics"]
|
|
494
|
+
collocated_variables += self.dae_variables["control_inputs"]
|
|
400
495
|
|
|
401
496
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
402
|
-
logger.debug("Integrating variables {}".format(
|
|
403
|
-
|
|
404
|
-
logger.debug("Collocating variables {}".format(
|
|
405
|
-
repr(collocated_variables)))
|
|
497
|
+
logger.debug("Integrating variables {}".format(repr(integrated_variables)))
|
|
498
|
+
logger.debug("Collocating variables {}".format(repr(collocated_variables)))
|
|
406
499
|
|
|
407
500
|
integrated_variable_names = [v.name() for v in integrated_variables]
|
|
408
|
-
integrated_variable_nominals = np.array(
|
|
501
|
+
integrated_variable_nominals = np.array(
|
|
502
|
+
[self.variable_nominal(v) for v in integrated_variable_names]
|
|
503
|
+
)
|
|
409
504
|
|
|
410
505
|
collocated_variable_names = [v.name() for v in collocated_variables]
|
|
411
|
-
collocated_variable_nominals = np.array(
|
|
506
|
+
collocated_variable_nominals = np.array(
|
|
507
|
+
[self.variable_nominal(v) for v in collocated_variable_names]
|
|
508
|
+
)
|
|
412
509
|
|
|
413
510
|
# Split derivatives into "integrated" and "collocated" lists.
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
collocated_derivatives.append(
|
|
422
|
-
self.dae_variables['derivatives'][k])
|
|
511
|
+
|
|
512
|
+
if self.integrate_states:
|
|
513
|
+
integrated_derivatives = self.dae_variables["derivatives"][:]
|
|
514
|
+
collocated_derivatives = []
|
|
515
|
+
else:
|
|
516
|
+
integrated_derivatives = []
|
|
517
|
+
collocated_derivatives = self.dae_variables["derivatives"][:]
|
|
423
518
|
self.__algebraic_and_control_derivatives = []
|
|
424
|
-
for var in
|
|
425
|
-
sym = ca.MX.sym(
|
|
519
|
+
for var in self.dae_variables["algebraics"]:
|
|
520
|
+
sym = ca.MX.sym("der({})".format(var.name()))
|
|
521
|
+
self.__algebraic_and_control_derivatives.append(sym)
|
|
522
|
+
if self.integrate_states:
|
|
523
|
+
integrated_derivatives.append(sym)
|
|
524
|
+
else:
|
|
525
|
+
collocated_derivatives.append(sym)
|
|
526
|
+
for var in self.dae_variables["control_inputs"]:
|
|
527
|
+
sym = ca.MX.sym("der({})".format(var.name()))
|
|
426
528
|
self.__algebraic_and_control_derivatives.append(sym)
|
|
427
529
|
collocated_derivatives.append(sym)
|
|
428
530
|
|
|
@@ -432,14 +534,20 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
432
534
|
# Path constraints
|
|
433
535
|
path_constraints = self.path_constraints(0)
|
|
434
536
|
path_constraint_expressions = ca.vertcat(
|
|
435
|
-
*[f_constraint for (f_constraint, lb, ub) in path_constraints]
|
|
537
|
+
*[f_constraint for (f_constraint, lb, ub) in path_constraints]
|
|
538
|
+
)
|
|
436
539
|
|
|
437
540
|
# Delayed feedback
|
|
438
|
-
delayed_feedback_expressions, delayed_feedback_states, delayed_feedback_durations =
|
|
541
|
+
delayed_feedback_expressions, delayed_feedback_states, delayed_feedback_durations = (
|
|
542
|
+
[],
|
|
543
|
+
[],
|
|
544
|
+
[],
|
|
545
|
+
)
|
|
439
546
|
delayed_feedback = self.delayed_feedback()
|
|
440
547
|
if delayed_feedback:
|
|
441
|
-
delayed_feedback_expressions, delayed_feedback_states, delayed_feedback_durations =
|
|
442
|
-
|
|
548
|
+
delayed_feedback_expressions, delayed_feedback_states, delayed_feedback_durations = zip(
|
|
549
|
+
*delayed_feedback
|
|
550
|
+
)
|
|
443
551
|
# Make sure the original data cannot be used anymore, because it will
|
|
444
552
|
# become incorrect/stale with the inlining of constant parameters.
|
|
445
553
|
del delayed_feedback
|
|
@@ -450,12 +558,18 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
450
558
|
# Establish integrator theta
|
|
451
559
|
theta = self.theta
|
|
452
560
|
if theta < 1:
|
|
453
|
-
warnings.warn(
|
|
454
|
-
|
|
561
|
+
warnings.warn(
|
|
562
|
+
(
|
|
563
|
+
"Explicit collocation/integration is deprecated "
|
|
564
|
+
"and will be removed in a future version."
|
|
565
|
+
),
|
|
566
|
+
FutureWarning,
|
|
567
|
+
stacklevel=1,
|
|
568
|
+
)
|
|
455
569
|
|
|
456
570
|
# Set CasADi function options
|
|
457
571
|
options = self.solver_options()
|
|
458
|
-
function_options = {
|
|
572
|
+
function_options = {"max_num_dir": options["optimized_num_dir"]}
|
|
459
573
|
|
|
460
574
|
# Update the store of all ensemble-member-specific data for all ensemble members
|
|
461
575
|
# with initial states, derivatives, and path variables.
|
|
@@ -485,34 +599,39 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
485
599
|
|
|
486
600
|
initial_der_name = self.__initial_derivative_names[i]
|
|
487
601
|
init_der_variable_nominals.append(self.variable_nominal(initial_der_name))
|
|
488
|
-
init_der_variable_indices.append(
|
|
602
|
+
init_der_variable_indices.append(
|
|
603
|
+
self.__indices[ensemble_member][initial_der_name]
|
|
604
|
+
)
|
|
489
605
|
init_der_variable.append(j)
|
|
490
606
|
|
|
491
607
|
except KeyError:
|
|
492
|
-
# We do interpolation here instead of relying on der_at. This faster
|
|
608
|
+
# We do interpolation here instead of relying on der_at. This is faster because:
|
|
493
609
|
# 1. We can reuse the history variable.
|
|
494
610
|
# 2. We know that "variable" is a canonical state
|
|
495
|
-
# 3. We know that we are only dealing with history (numeric values, not
|
|
611
|
+
# 3. We know that we are only dealing with history (numeric values, not
|
|
612
|
+
# symbolics)
|
|
496
613
|
try:
|
|
497
614
|
h = history[variable]
|
|
498
615
|
if h.times[0] == t0 or len(h.values) == 1:
|
|
499
616
|
init_der = 0.0
|
|
500
617
|
else:
|
|
501
618
|
assert h.times[-1] == t0
|
|
502
|
-
init_der = (h.values[-1] - h.values[-2])/(h.times[-1] - h.times[-2])
|
|
619
|
+
init_der = (h.values[-1] - h.values[-2]) / (h.times[-1] - h.times[-2])
|
|
503
620
|
except KeyError:
|
|
504
621
|
init_der = 0.0
|
|
505
622
|
|
|
506
623
|
init_der_constant_values.append(init_der)
|
|
507
624
|
init_der_constant.append(j)
|
|
508
625
|
|
|
509
|
-
initial_derivatives[init_der_variable] = (
|
|
510
|
-
|
|
626
|
+
initial_derivatives[init_der_variable] = X[init_der_variable_indices] * np.array(
|
|
627
|
+
init_der_variable_nominals
|
|
628
|
+
)
|
|
511
629
|
if len(init_der_constant_values) > 0:
|
|
512
630
|
initial_derivatives[init_der_constant] = init_der_constant_values
|
|
513
631
|
|
|
514
632
|
ensemble_data["initial_state"] = X[initial_state_indices] * np.concatenate(
|
|
515
|
-
(integrated_variable_nominals, collocated_variable_nominals)
|
|
633
|
+
(integrated_variable_nominals, collocated_variable_nominals)
|
|
634
|
+
)
|
|
516
635
|
ensemble_data["initial_derivatives"] = initial_derivatives
|
|
517
636
|
|
|
518
637
|
# Store initial path variables
|
|
@@ -524,13 +643,19 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
524
643
|
offset = 0
|
|
525
644
|
for variable in self.__path_variable_names:
|
|
526
645
|
step = len(self.times(variable))
|
|
527
|
-
initial_path_variable_inds.extend(
|
|
646
|
+
initial_path_variable_inds.extend(
|
|
647
|
+
self.__indices_as_lists[ensemble_member][variable][0::step]
|
|
648
|
+
)
|
|
528
649
|
|
|
529
650
|
variable_size = self.__variable_sizes[variable]
|
|
530
|
-
path_variables_nominals[offset:offset + variable_size] = self.variable_nominal(
|
|
651
|
+
path_variables_nominals[offset : offset + variable_size] = self.variable_nominal(
|
|
652
|
+
variable
|
|
653
|
+
)
|
|
531
654
|
offset += variable_size
|
|
532
655
|
|
|
533
|
-
ensemble_data["initial_path_variables"] =
|
|
656
|
+
ensemble_data["initial_path_variables"] = (
|
|
657
|
+
X[initial_path_variable_inds] * path_variables_nominals
|
|
658
|
+
)
|
|
534
659
|
|
|
535
660
|
# Replace parameters which are constant across the entire ensemble
|
|
536
661
|
constant_parameters = []
|
|
@@ -539,10 +664,14 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
539
664
|
ensemble_parameters = []
|
|
540
665
|
ensemble_parameter_values = [[] for i in range(self.ensemble_size)]
|
|
541
666
|
|
|
542
|
-
for i, parameter in enumerate(self.dae_variables[
|
|
543
|
-
values = [
|
|
544
|
-
|
|
545
|
-
|
|
667
|
+
for i, parameter in enumerate(self.dae_variables["parameters"]):
|
|
668
|
+
values = [
|
|
669
|
+
ensemble_store[ensemble_member]["parameters"][i]
|
|
670
|
+
for ensemble_member in range(self.ensemble_size)
|
|
671
|
+
]
|
|
672
|
+
if (
|
|
673
|
+
len(values) == 1 or (np.all(values) == values[0])
|
|
674
|
+
) and parameter.name() not in dynamic_parameter_names:
|
|
546
675
|
constant_parameters.append(parameter)
|
|
547
676
|
constant_parameter_values.append(values[0])
|
|
548
677
|
else:
|
|
@@ -555,99 +684,104 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
555
684
|
# Inline constant parameter values
|
|
556
685
|
if constant_parameters:
|
|
557
686
|
delayed_feedback_expressions = ca.substitute(
|
|
558
|
-
delayed_feedback_expressions,
|
|
559
|
-
|
|
560
|
-
constant_parameter_values)
|
|
687
|
+
delayed_feedback_expressions, constant_parameters, constant_parameter_values
|
|
688
|
+
)
|
|
561
689
|
|
|
562
690
|
delayed_feedback_durations = ca.substitute(
|
|
563
|
-
delayed_feedback_durations,
|
|
564
|
-
|
|
565
|
-
constant_parameter_values)
|
|
691
|
+
delayed_feedback_durations, constant_parameters, constant_parameter_values
|
|
692
|
+
)
|
|
566
693
|
|
|
567
|
-
path_objective, path_constraint_expressions =
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
694
|
+
path_objective, path_constraint_expressions = ca.substitute(
|
|
695
|
+
[path_objective, path_constraint_expressions],
|
|
696
|
+
constant_parameters,
|
|
697
|
+
constant_parameter_values,
|
|
698
|
+
)
|
|
572
699
|
|
|
573
700
|
# Collect extra variable symbols
|
|
574
701
|
symbolic_extra_variables = ca.vertcat(*self.extra_variables)
|
|
575
702
|
|
|
576
703
|
# Aggregate ensemble data
|
|
577
704
|
ensemble_aggregate = {}
|
|
578
|
-
ensemble_aggregate["parameters"] = ca.horzcat(
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
705
|
+
ensemble_aggregate["parameters"] = ca.horzcat(
|
|
706
|
+
*[nullvertcat(*p) for p in ensemble_parameter_values]
|
|
707
|
+
)
|
|
708
|
+
ensemble_aggregate["initial_constant_inputs"] = ca.horzcat(
|
|
709
|
+
*[
|
|
710
|
+
nullvertcat(
|
|
711
|
+
*[
|
|
712
|
+
float(d["constant_inputs"][variable.name()][0])
|
|
713
|
+
for variable in self.dae_variables["constant_inputs"]
|
|
714
|
+
]
|
|
715
|
+
)
|
|
716
|
+
for d in ensemble_store
|
|
717
|
+
]
|
|
718
|
+
)
|
|
719
|
+
ensemble_aggregate["initial_extra_constant_inputs"] = ca.horzcat(
|
|
720
|
+
*[
|
|
721
|
+
nullvertcat(
|
|
722
|
+
*[
|
|
723
|
+
d["extra_constant_inputs"][variable.name()][0, :]
|
|
724
|
+
for variable in self.__extra_constant_inputs
|
|
725
|
+
]
|
|
726
|
+
)
|
|
727
|
+
for d in ensemble_store
|
|
728
|
+
]
|
|
729
|
+
)
|
|
589
730
|
ensemble_aggregate["initial_state"] = ca.horzcat(
|
|
590
|
-
*[d["initial_state"] for d in ensemble_store]
|
|
731
|
+
*[d["initial_state"] for d in ensemble_store]
|
|
732
|
+
)
|
|
591
733
|
ensemble_aggregate["initial_state"] = reduce_matvec(
|
|
592
|
-
ensemble_aggregate["initial_state"], self.solver_input
|
|
734
|
+
ensemble_aggregate["initial_state"], self.solver_input
|
|
735
|
+
)
|
|
593
736
|
ensemble_aggregate["initial_derivatives"] = ca.horzcat(
|
|
594
|
-
*[d["initial_derivatives"] for d in ensemble_store]
|
|
737
|
+
*[d["initial_derivatives"] for d in ensemble_store]
|
|
738
|
+
)
|
|
595
739
|
ensemble_aggregate["initial_derivatives"] = reduce_matvec(
|
|
596
|
-
ensemble_aggregate["initial_derivatives"], self.solver_input
|
|
740
|
+
ensemble_aggregate["initial_derivatives"], self.solver_input
|
|
741
|
+
)
|
|
597
742
|
ensemble_aggregate["initial_path_variables"] = ca.horzcat(
|
|
598
|
-
*[d["initial_path_variables"] for d in ensemble_store]
|
|
743
|
+
*[d["initial_path_variables"] for d in ensemble_store]
|
|
744
|
+
)
|
|
599
745
|
ensemble_aggregate["initial_path_variables"] = reduce_matvec(
|
|
600
|
-
ensemble_aggregate["initial_path_variables"], self.solver_input
|
|
746
|
+
ensemble_aggregate["initial_path_variables"], self.solver_input
|
|
747
|
+
)
|
|
601
748
|
|
|
602
|
-
if (self.__dae_residual_function_collocated is None) and (
|
|
749
|
+
if (self.__dae_residual_function_collocated is None) and (
|
|
750
|
+
self.__integrator_step_function is None
|
|
751
|
+
):
|
|
603
752
|
# Insert lookup tables. No support yet for different lookup tables per ensemble member.
|
|
604
753
|
lookup_tables = self.lookup_tables(0)
|
|
605
754
|
|
|
606
|
-
for sym in self.dae_variables[
|
|
755
|
+
for sym in self.dae_variables["lookup_tables"]:
|
|
607
756
|
sym_name = sym.name()
|
|
608
757
|
|
|
609
758
|
try:
|
|
610
759
|
lookup_table = lookup_tables[sym_name]
|
|
611
760
|
except KeyError:
|
|
612
|
-
raise Exception(
|
|
613
|
-
"Unable to find lookup table function for {}".format(sym_name))
|
|
761
|
+
raise Exception("Unable to find lookup table function for {}".format(sym_name))
|
|
614
762
|
else:
|
|
615
|
-
input_syms = [
|
|
616
|
-
|
|
763
|
+
input_syms = [
|
|
764
|
+
self.variable(input_sym.name()) for input_sym in lookup_table.inputs
|
|
765
|
+
]
|
|
617
766
|
|
|
618
767
|
value = lookup_table.function(*input_syms)
|
|
619
|
-
[dae_residual] = ca.substitute(
|
|
620
|
-
[dae_residual], [sym], [value])
|
|
768
|
+
[dae_residual] = ca.substitute([dae_residual], [sym], [value])
|
|
621
769
|
|
|
622
|
-
if len(self.dae_variables[
|
|
623
|
-
logger.warning(
|
|
624
|
-
"Using lookup tables of ensemble member #0 for all members.")
|
|
770
|
+
if len(self.dae_variables["lookup_tables"]) > 0 and self.ensemble_size > 1:
|
|
771
|
+
logger.warning("Using lookup tables of ensemble member #0 for all members.")
|
|
625
772
|
|
|
626
773
|
# Insert constant parameter values
|
|
627
|
-
dae_residual, initial_residual =
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
for output in dae_outputs:
|
|
639
|
-
contains = False
|
|
640
|
-
for derivative in integrated_derivatives:
|
|
641
|
-
if ca.depends_on(output, derivative):
|
|
642
|
-
contains = True
|
|
643
|
-
break
|
|
644
|
-
|
|
645
|
-
if contains:
|
|
646
|
-
dae_residual_integrated.append(output)
|
|
647
|
-
else:
|
|
648
|
-
dae_residual_collocated.append(output)
|
|
649
|
-
dae_residual_integrated = ca.vertcat(*dae_residual_integrated)
|
|
650
|
-
dae_residual_collocated = ca.vertcat(*dae_residual_collocated)
|
|
774
|
+
dae_residual, initial_residual = ca.substitute(
|
|
775
|
+
[dae_residual, initial_residual], constant_parameters, constant_parameter_values
|
|
776
|
+
)
|
|
777
|
+
|
|
778
|
+
# Allocate DAE to an integrated or to a collocated part
|
|
779
|
+
if self.integrate_states:
|
|
780
|
+
dae_residual_integrated = dae_residual
|
|
781
|
+
dae_residual_collocated = ca.MX()
|
|
782
|
+
else:
|
|
783
|
+
dae_residual_integrated = ca.MX()
|
|
784
|
+
dae_residual_collocated = dae_residual
|
|
651
785
|
|
|
652
786
|
# Check linearity of collocated part
|
|
653
787
|
if self.check_collocation_linearity and dae_residual_collocated.size1() > 0:
|
|
@@ -660,52 +794,75 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
660
794
|
# Note that this not an exhaustive check, as other values for the
|
|
661
795
|
# parameters/constant inputs may result in a non-affine DAE (or vice-versa).
|
|
662
796
|
np.random.seed(42)
|
|
663
|
-
fixed_vars = ca.vertcat(
|
|
664
|
-
|
|
665
|
-
|
|
797
|
+
fixed_vars = ca.vertcat(
|
|
798
|
+
*self.dae_variables["time"],
|
|
799
|
+
*self.dae_variables["constant_inputs"],
|
|
800
|
+
ca.MX(symbolic_parameters),
|
|
801
|
+
)
|
|
666
802
|
fixed_var_values = np.random.rand(fixed_vars.size1())
|
|
667
803
|
|
|
668
|
-
if not is_affine(
|
|
669
|
-
|
|
670
|
-
|
|
804
|
+
if not is_affine(
|
|
805
|
+
ca.substitute(dae_residual_collocated, fixed_vars, fixed_var_values),
|
|
806
|
+
ca.vertcat(
|
|
807
|
+
*collocated_variables
|
|
808
|
+
+ integrated_variables
|
|
809
|
+
+ collocated_derivatives
|
|
810
|
+
+ integrated_derivatives
|
|
811
|
+
),
|
|
812
|
+
):
|
|
671
813
|
self.linear_collocation = False
|
|
672
814
|
|
|
673
815
|
logger.warning(
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
816
|
+
"The DAE residual contains equations that are not affine. "
|
|
817
|
+
"There is therefore no guarantee that the optimization problem is convex. "
|
|
818
|
+
"This will, in general, result in the existence of multiple local optima "
|
|
819
|
+
"and trouble finding a feasible initial solution."
|
|
820
|
+
)
|
|
678
821
|
|
|
679
822
|
# Transcribe DAE using theta method collocation
|
|
680
|
-
if
|
|
681
|
-
I = ca.MX.sym(
|
|
682
|
-
I0 = ca.MX.sym(
|
|
683
|
-
C0 = [ca.MX.sym(
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
823
|
+
if self.integrate_states:
|
|
824
|
+
I = ca.MX.sym("I", len(integrated_variables)) # noqa: E741
|
|
825
|
+
I0 = ca.MX.sym("I0", len(integrated_variables))
|
|
826
|
+
C0 = [ca.MX.sym("C0[{}]".format(i)) for i in range(len(collocated_variables))]
|
|
827
|
+
CI0 = [
|
|
828
|
+
ca.MX.sym("CI0[{}]".format(i))
|
|
829
|
+
for i in range(len(self.dae_variables["constant_inputs"]))
|
|
830
|
+
]
|
|
831
|
+
dt_sym = ca.MX.sym("dt")
|
|
688
832
|
|
|
689
833
|
integrated_finite_differences = (I - I0) / dt_sym
|
|
690
834
|
|
|
691
835
|
[dae_residual_integrated_0] = ca.substitute(
|
|
692
836
|
[dae_residual_integrated],
|
|
693
|
-
(
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
837
|
+
(
|
|
838
|
+
integrated_variables
|
|
839
|
+
+ collocated_variables
|
|
840
|
+
+ integrated_derivatives
|
|
841
|
+
+ self.dae_variables["constant_inputs"]
|
|
842
|
+
+ self.dae_variables["time"]
|
|
843
|
+
),
|
|
844
|
+
(
|
|
845
|
+
[I0[i] for i in range(len(integrated_variables))]
|
|
846
|
+
+ [C0[i] for i in range(len(collocated_variables))]
|
|
847
|
+
+ [
|
|
848
|
+
integrated_finite_differences[i]
|
|
849
|
+
for i in range(len(integrated_derivatives))
|
|
850
|
+
]
|
|
851
|
+
+ [CI0[i] for i in range(len(self.dae_variables["constant_inputs"]))]
|
|
852
|
+
+ [self.dae_variables["time"][0] - dt_sym]
|
|
853
|
+
),
|
|
854
|
+
)
|
|
703
855
|
[dae_residual_integrated_1] = ca.substitute(
|
|
704
856
|
[dae_residual_integrated],
|
|
705
|
-
(integrated_variables +
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
857
|
+
(integrated_variables + integrated_derivatives),
|
|
858
|
+
(
|
|
859
|
+
[I[i] for i in range(len(integrated_variables))]
|
|
860
|
+
+ [
|
|
861
|
+
integrated_finite_differences[i]
|
|
862
|
+
for i in range(len(integrated_derivatives))
|
|
863
|
+
]
|
|
864
|
+
),
|
|
865
|
+
)
|
|
709
866
|
|
|
710
867
|
if theta == 0:
|
|
711
868
|
dae_residual_integrated = dae_residual_integrated_0
|
|
@@ -713,25 +870,34 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
713
870
|
dae_residual_integrated = dae_residual_integrated_1
|
|
714
871
|
else:
|
|
715
872
|
dae_residual_integrated = (
|
|
716
|
-
1 - theta
|
|
873
|
+
1 - theta
|
|
874
|
+
) * dae_residual_integrated_0 + theta * dae_residual_integrated_1
|
|
717
875
|
|
|
718
876
|
dae_residual_function_integrated = ca.Function(
|
|
719
|
-
|
|
720
|
-
[
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
877
|
+
"dae_residual_function_integrated",
|
|
878
|
+
[
|
|
879
|
+
I,
|
|
880
|
+
I0,
|
|
881
|
+
symbolic_parameters,
|
|
882
|
+
ca.vertcat(
|
|
883
|
+
*(
|
|
884
|
+
[C0[i] for i in range(len(collocated_variables))]
|
|
885
|
+
+ [
|
|
886
|
+
CI0[i]
|
|
887
|
+
for i in range(len(self.dae_variables["constant_inputs"]))
|
|
888
|
+
]
|
|
889
|
+
+ [dt_sym]
|
|
890
|
+
+ collocated_variables
|
|
891
|
+
+ collocated_derivatives
|
|
892
|
+
+ self.dae_variables["constant_inputs"]
|
|
893
|
+
+ self.dae_variables["time"]
|
|
894
|
+
)
|
|
895
|
+
),
|
|
896
|
+
],
|
|
731
897
|
[dae_residual_integrated],
|
|
732
|
-
function_options
|
|
898
|
+
function_options,
|
|
899
|
+
)
|
|
733
900
|
|
|
734
|
-
# if not self.dae_is_external_function:
|
|
735
901
|
try:
|
|
736
902
|
dae_residual_function_integrated = dae_residual_function_integrated.expand()
|
|
737
903
|
except RuntimeError as e:
|
|
@@ -743,24 +909,34 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
743
909
|
|
|
744
910
|
options = self.integrator_options()
|
|
745
911
|
self.__integrator_step_function = ca.rootfinder(
|
|
746
|
-
|
|
912
|
+
"integrator_step_function",
|
|
913
|
+
"fast_newton",
|
|
914
|
+
dae_residual_function_integrated,
|
|
915
|
+
options,
|
|
916
|
+
)
|
|
747
917
|
|
|
748
918
|
# Initialize a Function for the DAE residual (collocated part)
|
|
749
|
-
|
|
919
|
+
elif len(collocated_variables) > 0:
|
|
750
920
|
self.__dae_residual_function_collocated = ca.Function(
|
|
751
|
-
|
|
752
|
-
[
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
921
|
+
"dae_residual_function_collocated",
|
|
922
|
+
[
|
|
923
|
+
symbolic_parameters,
|
|
924
|
+
ca.vertcat(
|
|
925
|
+
*(
|
|
926
|
+
collocated_variables
|
|
927
|
+
+ collocated_derivatives
|
|
928
|
+
+ self.dae_variables["constant_inputs"]
|
|
929
|
+
+ self.dae_variables["time"]
|
|
930
|
+
)
|
|
931
|
+
),
|
|
932
|
+
],
|
|
760
933
|
[dae_residual_collocated],
|
|
761
|
-
function_options
|
|
934
|
+
function_options,
|
|
935
|
+
)
|
|
762
936
|
try:
|
|
763
|
-
self.__dae_residual_function_collocated =
|
|
937
|
+
self.__dae_residual_function_collocated = (
|
|
938
|
+
self.__dae_residual_function_collocated.expand()
|
|
939
|
+
)
|
|
764
940
|
except RuntimeError as e:
|
|
765
941
|
# We only expect to fail if the DAE was an external function
|
|
766
942
|
if "'eval_sx' not defined for External" in str(e):
|
|
@@ -768,12 +944,12 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
768
944
|
else:
|
|
769
945
|
raise
|
|
770
946
|
|
|
771
|
-
if
|
|
947
|
+
if self.integrate_states:
|
|
772
948
|
integrator_step_function = self.__integrator_step_function
|
|
773
|
-
|
|
949
|
+
dae_residual_collocated_size = 0
|
|
950
|
+
elif len(collocated_variables) > 0:
|
|
774
951
|
dae_residual_function_collocated = self.__dae_residual_function_collocated
|
|
775
|
-
dae_residual_collocated_size = dae_residual_function_collocated.mx_out(
|
|
776
|
-
0).size1()
|
|
952
|
+
dae_residual_collocated_size = dae_residual_function_collocated.mx_out(0).size1()
|
|
777
953
|
else:
|
|
778
954
|
dae_residual_collocated_size = 0
|
|
779
955
|
|
|
@@ -782,146 +958,167 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
782
958
|
self.__func_orig_inputs = [
|
|
783
959
|
symbolic_parameters,
|
|
784
960
|
ca.vertcat(
|
|
785
|
-
*integrated_variables,
|
|
786
|
-
*
|
|
787
|
-
*
|
|
788
|
-
*
|
|
789
|
-
|
|
961
|
+
*integrated_variables,
|
|
962
|
+
*collocated_variables,
|
|
963
|
+
*integrated_derivatives,
|
|
964
|
+
*collocated_derivatives,
|
|
965
|
+
*self.dae_variables["constant_inputs"],
|
|
966
|
+
*self.dae_variables["time"],
|
|
967
|
+
*self.path_variables,
|
|
968
|
+
*self.__extra_constant_inputs,
|
|
969
|
+
),
|
|
970
|
+
symbolic_extra_variables,
|
|
971
|
+
]
|
|
790
972
|
|
|
791
973
|
# Initialize a Function for the path objective
|
|
792
|
-
# Note that we assume that the path objective expression is the same for all ensemble
|
|
974
|
+
# Note that we assume that the path objective expression is the same for all ensemble
|
|
975
|
+
# members
|
|
793
976
|
path_objective_function = ca.Function(
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
[path_objective],
|
|
797
|
-
function_options)
|
|
977
|
+
"path_objective", self.__func_orig_inputs, [path_objective], function_options
|
|
978
|
+
)
|
|
798
979
|
path_objective_function = path_objective_function.expand()
|
|
799
980
|
|
|
800
981
|
# Initialize a Function for the path constraints
|
|
801
|
-
# Note that we assume that the path constraint expression is the same for all ensemble
|
|
982
|
+
# Note that we assume that the path constraint expression is the same for all ensemble
|
|
983
|
+
# members
|
|
802
984
|
path_constraints_function = ca.Function(
|
|
803
|
-
|
|
985
|
+
"path_constraints",
|
|
804
986
|
self.__func_orig_inputs,
|
|
805
987
|
[path_constraint_expressions],
|
|
806
|
-
function_options
|
|
988
|
+
function_options,
|
|
989
|
+
)
|
|
807
990
|
path_constraints_function = path_constraints_function.expand()
|
|
808
991
|
|
|
809
992
|
# Initialize a Function for the delayed feedback
|
|
810
993
|
delayed_feedback_function = ca.Function(
|
|
811
|
-
|
|
994
|
+
"delayed_feedback",
|
|
812
995
|
self.__func_orig_inputs,
|
|
813
996
|
delayed_feedback_expressions,
|
|
814
|
-
function_options
|
|
997
|
+
function_options,
|
|
998
|
+
)
|
|
815
999
|
delayed_feedback_function = delayed_feedback_function.expand()
|
|
816
1000
|
|
|
817
1001
|
# Set up accumulation over time (integration, and generation of
|
|
818
1002
|
# collocation constraints)
|
|
819
|
-
if
|
|
820
|
-
accumulated_X = ca.MX.sym(
|
|
1003
|
+
if self.integrate_states:
|
|
1004
|
+
accumulated_X = ca.MX.sym("accumulated_X", len(integrated_variables))
|
|
821
1005
|
else:
|
|
822
|
-
accumulated_X = ca.MX.sym(
|
|
1006
|
+
accumulated_X = ca.MX.sym("accumulated_X", 0)
|
|
823
1007
|
|
|
824
1008
|
path_variables_size = sum(x.size1() for x in self.path_variables)
|
|
825
1009
|
extra_constant_inputs_size = sum(x.size1() for x in self.__extra_constant_inputs)
|
|
826
1010
|
|
|
827
1011
|
accumulated_U = ca.MX.sym(
|
|
828
|
-
|
|
829
|
-
(
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
1012
|
+
"accumulated_U",
|
|
1013
|
+
(
|
|
1014
|
+
2 * (len(collocated_variables) + len(self.dae_variables["constant_inputs"]) + 1)
|
|
1015
|
+
+ path_variables_size
|
|
1016
|
+
+ extra_constant_inputs_size
|
|
1017
|
+
),
|
|
1018
|
+
)
|
|
1019
|
+
|
|
1020
|
+
integrated_states_0 = accumulated_X[0 : len(integrated_variables)]
|
|
1021
|
+
integrated_states_1 = ca.MX.sym("integrated_states_1", len(integrated_variables))
|
|
1022
|
+
collocated_states_0 = accumulated_U[0 : len(collocated_variables)]
|
|
838
1023
|
collocated_states_1 = accumulated_U[
|
|
839
|
-
len(collocated_variables):2 * len(collocated_variables)
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1024
|
+
len(collocated_variables) : 2 * len(collocated_variables)
|
|
1025
|
+
]
|
|
1026
|
+
constant_inputs_0 = accumulated_U[
|
|
1027
|
+
2 * len(collocated_variables) : 2 * len(collocated_variables)
|
|
1028
|
+
+ len(self.dae_variables["constant_inputs"])
|
|
1029
|
+
]
|
|
1030
|
+
constant_inputs_1 = accumulated_U[
|
|
1031
|
+
2 * len(collocated_variables)
|
|
1032
|
+
+ len(self.dae_variables["constant_inputs"]) : 2 * len(collocated_variables)
|
|
1033
|
+
+ 2 * len(self.dae_variables["constant_inputs"])
|
|
1034
|
+
]
|
|
1035
|
+
|
|
1036
|
+
offset = 2 * (len(collocated_variables) + len(self.dae_variables["constant_inputs"]))
|
|
846
1037
|
collocation_time_0 = accumulated_U[offset + 0]
|
|
847
1038
|
collocation_time_1 = accumulated_U[offset + 1]
|
|
848
|
-
path_variables_1 = accumulated_U[offset + 2:offset + 2 + len(self.path_variables)]
|
|
849
|
-
extra_constant_inputs_1 = accumulated_U[offset + 2 + len(self.path_variables):]
|
|
1039
|
+
path_variables_1 = accumulated_U[offset + 2 : offset + 2 + len(self.path_variables)]
|
|
1040
|
+
extra_constant_inputs_1 = accumulated_U[offset + 2 + len(self.path_variables) :]
|
|
850
1041
|
|
|
851
1042
|
# Approximate derivatives using backwards finite differences
|
|
852
1043
|
dt = collocation_time_1 - collocation_time_0
|
|
853
|
-
|
|
854
|
-
|
|
1044
|
+
integrated_finite_differences = ca.MX() # Overwritten later if integrate_states is True
|
|
1045
|
+
collocated_finite_differences = (collocated_states_1 - collocated_states_0) / dt
|
|
855
1046
|
|
|
856
1047
|
# We use ca.vertcat to compose the list into an MX. This is, in
|
|
857
1048
|
# CasADi 2.4, faster.
|
|
858
1049
|
accumulated_Y = []
|
|
859
1050
|
|
|
860
1051
|
# Integrate integrated states
|
|
861
|
-
if
|
|
1052
|
+
if self.integrate_states:
|
|
862
1053
|
# Perform step by computing implicit function
|
|
863
1054
|
# CasADi shares subexpressions that are bundled into the same Function.
|
|
864
1055
|
# The first argument is the guess for the new value of
|
|
865
1056
|
# integrated_states.
|
|
866
1057
|
[integrated_states_1] = integrator_step_function.call(
|
|
867
|
-
[
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1058
|
+
[
|
|
1059
|
+
integrated_states_0,
|
|
1060
|
+
integrated_states_0,
|
|
1061
|
+
symbolic_parameters,
|
|
1062
|
+
ca.vertcat(
|
|
1063
|
+
collocated_states_0,
|
|
1064
|
+
constant_inputs_0,
|
|
1065
|
+
dt,
|
|
1066
|
+
collocated_states_1,
|
|
1067
|
+
collocated_finite_differences,
|
|
1068
|
+
constant_inputs_1,
|
|
1069
|
+
collocation_time_1 - t0,
|
|
1070
|
+
),
|
|
1071
|
+
],
|
|
1072
|
+
False,
|
|
1073
|
+
True,
|
|
1074
|
+
)
|
|
879
1075
|
accumulated_Y.append(integrated_states_1)
|
|
880
1076
|
|
|
881
|
-
# Recompute finite differences with computed new state
|
|
882
|
-
# We don't use substititute() for this, as it becomes expensive
|
|
883
|
-
#
|
|
884
|
-
|
|
885
|
-
integrated_finite_differences = (
|
|
886
|
-
integrated_states_1 - integrated_states_0) / dt
|
|
887
|
-
else:
|
|
888
|
-
integrated_finite_differences = ca.MX()
|
|
1077
|
+
# Recompute finite differences with computed new state.
|
|
1078
|
+
# We don't use substititute() for this, as it becomes expensive over long
|
|
1079
|
+
# integration horizons.
|
|
1080
|
+
integrated_finite_differences = (integrated_states_1 - integrated_states_0) / dt
|
|
889
1081
|
|
|
890
1082
|
# Call DAE residual at collocation point
|
|
891
1083
|
# Time stamp following paragraph 3.6.7 of the Modelica
|
|
892
1084
|
# specifications, version 3.3.
|
|
893
|
-
|
|
1085
|
+
elif len(collocated_variables) > 0:
|
|
894
1086
|
if theta < 1:
|
|
895
1087
|
# Obtain state vector
|
|
896
1088
|
[dae_residual_0] = dae_residual_function_collocated.call(
|
|
897
|
-
[
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1089
|
+
[
|
|
1090
|
+
symbolic_parameters,
|
|
1091
|
+
ca.vertcat(
|
|
1092
|
+
collocated_states_0,
|
|
1093
|
+
collocated_finite_differences,
|
|
1094
|
+
constant_inputs_0,
|
|
1095
|
+
collocation_time_0 - t0,
|
|
1096
|
+
),
|
|
1097
|
+
],
|
|
1098
|
+
False,
|
|
1099
|
+
True,
|
|
1100
|
+
)
|
|
906
1101
|
if theta > 0:
|
|
907
1102
|
# Obtain state vector
|
|
908
1103
|
[dae_residual_1] = dae_residual_function_collocated.call(
|
|
909
|
-
[
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
1104
|
+
[
|
|
1105
|
+
symbolic_parameters,
|
|
1106
|
+
ca.vertcat(
|
|
1107
|
+
collocated_states_1,
|
|
1108
|
+
collocated_finite_differences,
|
|
1109
|
+
constant_inputs_1,
|
|
1110
|
+
collocation_time_1 - t0,
|
|
1111
|
+
),
|
|
1112
|
+
],
|
|
1113
|
+
False,
|
|
1114
|
+
True,
|
|
1115
|
+
)
|
|
918
1116
|
if theta == 0:
|
|
919
1117
|
accumulated_Y.append(dae_residual_0)
|
|
920
1118
|
elif theta == 1:
|
|
921
1119
|
accumulated_Y.append(dae_residual_1)
|
|
922
1120
|
else:
|
|
923
|
-
accumulated_Y.append(
|
|
924
|
-
(1 - theta) * dae_residual_0 + theta * dae_residual_1)
|
|
1121
|
+
accumulated_Y.append((1 - theta) * dae_residual_0 + theta * dae_residual_1)
|
|
925
1122
|
|
|
926
1123
|
self.__func_inputs_implicit = [
|
|
927
1124
|
symbolic_parameters,
|
|
@@ -933,42 +1130,55 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
933
1130
|
constant_inputs_1,
|
|
934
1131
|
collocation_time_1 - t0,
|
|
935
1132
|
path_variables_1,
|
|
936
|
-
extra_constant_inputs_1
|
|
937
|
-
|
|
1133
|
+
extra_constant_inputs_1,
|
|
1134
|
+
),
|
|
1135
|
+
symbolic_extra_variables,
|
|
1136
|
+
]
|
|
938
1137
|
|
|
939
|
-
accumulated_Y.extend(path_objective_function.call(
|
|
940
|
-
self.__func_inputs_implicit, False, True))
|
|
1138
|
+
accumulated_Y.extend(path_objective_function.call(self.__func_inputs_implicit, False, True))
|
|
941
1139
|
|
|
942
|
-
accumulated_Y.extend(
|
|
943
|
-
self.__func_inputs_implicit, False, True)
|
|
1140
|
+
accumulated_Y.extend(
|
|
1141
|
+
path_constraints_function.call(self.__func_inputs_implicit, False, True)
|
|
1142
|
+
)
|
|
944
1143
|
|
|
945
|
-
accumulated_Y.extend(
|
|
946
|
-
self.__func_inputs_implicit, False, True)
|
|
1144
|
+
accumulated_Y.extend(
|
|
1145
|
+
delayed_feedback_function.call(self.__func_inputs_implicit, False, True)
|
|
1146
|
+
)
|
|
947
1147
|
|
|
948
1148
|
# Save the accumulated inputs such that can be used later in map_path_expression()
|
|
949
1149
|
self.__func_accumulated_inputs = (
|
|
950
|
-
accumulated_X,
|
|
951
|
-
|
|
1150
|
+
accumulated_X,
|
|
1151
|
+
accumulated_U,
|
|
1152
|
+
ca.veccat(symbolic_parameters, symbolic_extra_variables),
|
|
1153
|
+
)
|
|
952
1154
|
|
|
953
1155
|
# Use map/mapaccum to capture integration and collocation constraint generation over the
|
|
954
1156
|
# entire time horizon with one symbolic operation. This saves a lot of memory.
|
|
955
1157
|
if n_collocation_times > 1:
|
|
956
|
-
if
|
|
1158
|
+
if self.integrate_states:
|
|
957
1159
|
accumulated = ca.Function(
|
|
958
|
-
|
|
1160
|
+
"accumulated",
|
|
959
1161
|
self.__func_accumulated_inputs,
|
|
960
1162
|
[accumulated_Y[0], ca.vertcat(*accumulated_Y[1:])],
|
|
961
|
-
function_options
|
|
962
|
-
|
|
1163
|
+
function_options,
|
|
1164
|
+
)
|
|
1165
|
+
accumulation = accumulated.mapaccum("accumulation", n_collocation_times - 1)
|
|
963
1166
|
else:
|
|
964
1167
|
# Fully collocated problem. Use map(), so that we can use
|
|
965
1168
|
# parallelization along the time axis.
|
|
966
1169
|
accumulated = ca.Function(
|
|
967
|
-
|
|
1170
|
+
"accumulated",
|
|
968
1171
|
self.__func_accumulated_inputs,
|
|
969
1172
|
[ca.vertcat(*accumulated_Y)],
|
|
970
|
-
function_options
|
|
971
|
-
|
|
1173
|
+
function_options,
|
|
1174
|
+
)
|
|
1175
|
+
options = self.map_options()
|
|
1176
|
+
if options["mode"] == "thread":
|
|
1177
|
+
accumulation = accumulated.map(
|
|
1178
|
+
n_collocation_times - 1, options["mode"], options["n_threads"]
|
|
1179
|
+
)
|
|
1180
|
+
else:
|
|
1181
|
+
accumulation = accumulated.map(n_collocation_times - 1, options["mode"])
|
|
972
1182
|
else:
|
|
973
1183
|
accumulation = None
|
|
974
1184
|
|
|
@@ -981,29 +1191,43 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
981
1191
|
# Add constraints for initial conditions
|
|
982
1192
|
if self.__initial_residual_with_params_fun_map is None:
|
|
983
1193
|
initial_residual_with_params_fun = ca.Function(
|
|
984
|
-
|
|
985
|
-
[
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1194
|
+
"initial_residual_total",
|
|
1195
|
+
[
|
|
1196
|
+
symbolic_parameters,
|
|
1197
|
+
ca.vertcat(
|
|
1198
|
+
*(
|
|
1199
|
+
self.dae_variables["states"]
|
|
1200
|
+
+ self.dae_variables["algebraics"]
|
|
1201
|
+
+ self.dae_variables["control_inputs"]
|
|
1202
|
+
+ integrated_derivatives
|
|
1203
|
+
+ collocated_derivatives
|
|
1204
|
+
+ self.dae_variables["constant_inputs"]
|
|
1205
|
+
+ self.dae_variables["time"]
|
|
1206
|
+
)
|
|
1207
|
+
),
|
|
1208
|
+
],
|
|
994
1209
|
[ca.veccat(dae_residual, initial_residual)],
|
|
995
|
-
function_options
|
|
1210
|
+
function_options,
|
|
1211
|
+
)
|
|
996
1212
|
self.__initial_residual_with_params_fun_map = initial_residual_with_params_fun.map(
|
|
997
|
-
self.ensemble_size
|
|
1213
|
+
self.ensemble_size
|
|
1214
|
+
)
|
|
998
1215
|
initial_residual_with_params_fun_map = self.__initial_residual_with_params_fun_map
|
|
999
1216
|
[res] = initial_residual_with_params_fun_map.call(
|
|
1000
|
-
[
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1217
|
+
[
|
|
1218
|
+
ensemble_aggregate["parameters"],
|
|
1219
|
+
ca.vertcat(
|
|
1220
|
+
*[
|
|
1221
|
+
ensemble_aggregate["initial_state"],
|
|
1222
|
+
ensemble_aggregate["initial_derivatives"],
|
|
1223
|
+
ensemble_aggregate["initial_constant_inputs"],
|
|
1224
|
+
ca.repmat([0.0], 1, self.ensemble_size),
|
|
1225
|
+
]
|
|
1226
|
+
),
|
|
1227
|
+
],
|
|
1228
|
+
False,
|
|
1229
|
+
True,
|
|
1230
|
+
)
|
|
1007
1231
|
|
|
1008
1232
|
res = ca.vec(res)
|
|
1009
1233
|
g.append(res)
|
|
@@ -1013,33 +1237,47 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1013
1237
|
|
|
1014
1238
|
# The initial values and the interpolated mapped arguments are saved
|
|
1015
1239
|
# such that can be reused in map_path_expression().
|
|
1016
|
-
self.__func_mapped_inputs = []
|
|
1017
1240
|
self.__func_initial_inputs = []
|
|
1241
|
+
self.__func_map_args = []
|
|
1242
|
+
|
|
1243
|
+
# Integrators are saved for result extraction later on
|
|
1244
|
+
self.__integrators = []
|
|
1018
1245
|
|
|
1019
1246
|
# Process the objectives and constraints for each ensemble member separately.
|
|
1020
|
-
# Note that we don't use map here for the moment, so as to allow each ensemble member to
|
|
1021
|
-
# constraints and objectives.
|
|
1022
|
-
# at the moment.
|
|
1023
|
-
# path constraints as well, once CasADi has some kind
|
|
1247
|
+
# Note that we don't use map here for the moment, so as to allow each ensemble member to
|
|
1248
|
+
# define its own constraints and objectives. Path constraints are applied for all ensemble
|
|
1249
|
+
# members simultaneously at the moment. We can get rid of map again, and allow every
|
|
1250
|
+
# ensemble member to specify its own path constraints as well, once CasADi has some kind
|
|
1251
|
+
# of loop detection.
|
|
1024
1252
|
for ensemble_member in range(self.ensemble_size):
|
|
1025
1253
|
logger.info(
|
|
1026
|
-
"Transcribing ensemble member {}/{}".format(ensemble_member + 1, self.ensemble_size)
|
|
1254
|
+
"Transcribing ensemble member {}/{}".format(ensemble_member + 1, self.ensemble_size)
|
|
1255
|
+
)
|
|
1027
1256
|
|
|
1028
1257
|
initial_state = ensemble_aggregate["initial_state"][:, ensemble_member]
|
|
1029
1258
|
initial_derivatives = ensemble_aggregate["initial_derivatives"][:, ensemble_member]
|
|
1030
|
-
initial_path_variables = ensemble_aggregate["initial_path_variables"][
|
|
1031
|
-
|
|
1032
|
-
|
|
1259
|
+
initial_path_variables = ensemble_aggregate["initial_path_variables"][
|
|
1260
|
+
:, ensemble_member
|
|
1261
|
+
]
|
|
1262
|
+
initial_constant_inputs = ensemble_aggregate["initial_constant_inputs"][
|
|
1263
|
+
:, ensemble_member
|
|
1264
|
+
]
|
|
1265
|
+
initial_extra_constant_inputs = ensemble_aggregate["initial_extra_constant_inputs"][
|
|
1266
|
+
:, ensemble_member
|
|
1267
|
+
]
|
|
1033
1268
|
parameters = ensemble_aggregate["parameters"][:, ensemble_member]
|
|
1034
|
-
extra_variables = ca.vertcat(
|
|
1035
|
-
self.extra_variable(var.name(), ensemble_member) for var in self.extra_variables]
|
|
1269
|
+
extra_variables = ca.vertcat(
|
|
1270
|
+
*[self.extra_variable(var.name(), ensemble_member) for var in self.extra_variables]
|
|
1271
|
+
)
|
|
1036
1272
|
|
|
1037
1273
|
constant_inputs = ensemble_store[ensemble_member]["constant_inputs"]
|
|
1038
1274
|
extra_constant_inputs = ensemble_store[ensemble_member]["extra_constant_inputs"]
|
|
1039
1275
|
|
|
1040
1276
|
# Initial conditions specified in history timeseries
|
|
1041
1277
|
history = self.history(ensemble_member)
|
|
1042
|
-
for variable in itertools.chain(
|
|
1278
|
+
for variable in itertools.chain(
|
|
1279
|
+
self.differentiated_states, self.algebraic_states, self.controls
|
|
1280
|
+
):
|
|
1043
1281
|
try:
|
|
1044
1282
|
history_timeseries = history[variable]
|
|
1045
1283
|
except KeyError:
|
|
@@ -1047,14 +1285,24 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1047
1285
|
else:
|
|
1048
1286
|
interpolation_method = self.interpolation_method(variable)
|
|
1049
1287
|
val = self.interpolate(
|
|
1050
|
-
t0,
|
|
1288
|
+
t0,
|
|
1289
|
+
history_timeseries.times,
|
|
1290
|
+
history_timeseries.values,
|
|
1291
|
+
np.nan,
|
|
1292
|
+
np.nan,
|
|
1293
|
+
interpolation_method,
|
|
1294
|
+
)
|
|
1051
1295
|
val /= self.variable_nominal(variable)
|
|
1052
1296
|
|
|
1053
1297
|
if not np.isnan(val):
|
|
1054
1298
|
idx = self.__indices_as_lists[ensemble_member][variable][0]
|
|
1055
1299
|
|
|
1056
1300
|
if val < lbx[idx] or val > ubx[idx]:
|
|
1057
|
-
logger.warning(
|
|
1301
|
+
logger.warning(
|
|
1302
|
+
"Initial value {} for variable '{}' outside bounds.".format(
|
|
1303
|
+
val, variable
|
|
1304
|
+
)
|
|
1305
|
+
)
|
|
1058
1306
|
|
|
1059
1307
|
lbx[idx] = ubx[idx] = val
|
|
1060
1308
|
|
|
@@ -1066,7 +1314,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1066
1314
|
except KeyError:
|
|
1067
1315
|
pass
|
|
1068
1316
|
else:
|
|
1069
|
-
if len(history_timeseries.times) <= 1 or np.isnan(
|
|
1317
|
+
if len(history_timeseries.times) <= 1 or np.isnan(
|
|
1318
|
+
history_timeseries.values[-2]
|
|
1319
|
+
):
|
|
1070
1320
|
continue
|
|
1071
1321
|
|
|
1072
1322
|
assert history_timeseries.times[-1] == t0
|
|
@@ -1075,7 +1325,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1075
1325
|
t0_val = self.state_vector(variable, ensemble_member=ensemble_member)[0]
|
|
1076
1326
|
t0_val *= self.variable_nominal(variable)
|
|
1077
1327
|
|
|
1078
|
-
val = (t0_val - history_timeseries.values[-2]) / (
|
|
1328
|
+
val = (t0_val - history_timeseries.values[-2]) / (
|
|
1329
|
+
t0 - history_timeseries.times[-2]
|
|
1330
|
+
)
|
|
1079
1331
|
sym = initial_derivatives[i]
|
|
1080
1332
|
initial_derivative_constraints.append(sym - val)
|
|
1081
1333
|
else:
|
|
@@ -1087,11 +1339,13 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1087
1339
|
history_timeseries.values,
|
|
1088
1340
|
np.nan,
|
|
1089
1341
|
np.nan,
|
|
1090
|
-
interpolation_method
|
|
1342
|
+
interpolation_method,
|
|
1091
1343
|
)
|
|
1092
1344
|
initial_der_name = self.__initial_derivative_names[i]
|
|
1093
1345
|
|
|
1094
|
-
val = (t0_val - history_timeseries.values[-2]) / (
|
|
1346
|
+
val = (t0_val - history_timeseries.values[-2]) / (
|
|
1347
|
+
t0 - history_timeseries.times[-2]
|
|
1348
|
+
)
|
|
1095
1349
|
val /= self.variable_nominal(initial_der_name)
|
|
1096
1350
|
|
|
1097
1351
|
idx = self.__indices[ensemble_member][initial_der_name]
|
|
@@ -1104,24 +1358,24 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1104
1358
|
|
|
1105
1359
|
# Initial conditions for integrator
|
|
1106
1360
|
accumulation_X0 = []
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
variable, ensemble_member=ensemble_member)[0]
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
# if len(self.integrated_states) > 0:
|
|
1115
|
-
# accumulation_X0.extend(
|
|
1116
|
-
# [0.0] * (dae_residual_collocated_size + 1))
|
|
1361
|
+
if self.integrate_states:
|
|
1362
|
+
for variable in integrated_variable_names:
|
|
1363
|
+
value = self.state_vector(variable, ensemble_member=ensemble_member)[0]
|
|
1364
|
+
nominal = self.variable_nominal(variable)
|
|
1365
|
+
if nominal != 1:
|
|
1366
|
+
value *= nominal
|
|
1367
|
+
accumulation_X0.append(value)
|
|
1117
1368
|
accumulation_X0 = ca.vertcat(*accumulation_X0)
|
|
1118
1369
|
|
|
1119
1370
|
# Input for map
|
|
1120
1371
|
logger.info("Interpolating states")
|
|
1121
1372
|
|
|
1122
1373
|
accumulation_U = [None] * (
|
|
1123
|
-
1
|
|
1124
|
-
+ len(self.
|
|
1374
|
+
1
|
|
1375
|
+
+ 2 * len(self.dae_variables["constant_inputs"])
|
|
1376
|
+
+ 3
|
|
1377
|
+
+ len(self.__extra_constant_inputs)
|
|
1378
|
+
)
|
|
1125
1379
|
|
|
1126
1380
|
# Most variables have collocation times equal to the global
|
|
1127
1381
|
# collocation times. Use a vectorized approach to process them.
|
|
@@ -1141,10 +1395,16 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1141
1395
|
interpolated_states_explicit.extend(var_inds[:-1])
|
|
1142
1396
|
interpolated_states_implicit.extend(var_inds[1:])
|
|
1143
1397
|
|
|
1144
|
-
repeated_nominals = np.tile(
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
interpolated_states =
|
|
1398
|
+
repeated_nominals = np.tile(
|
|
1399
|
+
np.repeat(collocated_variable_nominals, n_collocation_times - 1), 2
|
|
1400
|
+
)
|
|
1401
|
+
interpolated_states = (
|
|
1402
|
+
ca.vertcat(X[interpolated_states_explicit], X[interpolated_states_implicit])
|
|
1403
|
+
* repeated_nominals
|
|
1404
|
+
)
|
|
1405
|
+
interpolated_states = interpolated_states.reshape(
|
|
1406
|
+
(n_collocation_times - 1, len(collocated_variables) * 2)
|
|
1407
|
+
)
|
|
1148
1408
|
|
|
1149
1409
|
# Handle variables that have different collocation times.
|
|
1150
1410
|
for j, variable in enumerate(collocated_variable_names):
|
|
@@ -1156,7 +1416,8 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1156
1416
|
interpolation_method = self.interpolation_method(variable)
|
|
1157
1417
|
values = self.state_vector(variable, ensemble_member=ensemble_member)
|
|
1158
1418
|
interpolated = interpolate(
|
|
1159
|
-
times, values, collocation_times, False, interpolation_method
|
|
1419
|
+
times, values, collocation_times, False, interpolation_method
|
|
1420
|
+
)
|
|
1160
1421
|
|
|
1161
1422
|
nominal = self.variable_nominal(variable)
|
|
1162
1423
|
if nominal != 1:
|
|
@@ -1165,97 +1426,132 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1165
1426
|
interpolated_states[:, j] = interpolated[:-1]
|
|
1166
1427
|
interpolated_states[:, len(collocated_variables) + j] = interpolated[1:]
|
|
1167
1428
|
|
|
1168
|
-
# We do not cache the Jacobians, as the structure may change from ensemble member to
|
|
1169
|
-
# and from goal programming/homotopy run to run.
|
|
1170
|
-
# We could, of course, pick the states apart into controls and states,
|
|
1171
|
-
#
|
|
1172
|
-
#
|
|
1173
|
-
|
|
1429
|
+
# We do not cache the Jacobians, as the structure may change from ensemble member to
|
|
1430
|
+
# member, and from goal programming/homotopy run to run.
|
|
1431
|
+
# We could, of course, pick the states apart into controls and states, and generate
|
|
1432
|
+
# Jacobians for each set separately and for each ensemble member separately, but in
|
|
1433
|
+
# this case the increased complexity may well offset the performance gained by
|
|
1434
|
+
# caching.
|
|
1435
|
+
interpolated_states = reduce_matvec(interpolated_states, self.solver_input)
|
|
1174
1436
|
|
|
1175
|
-
|
|
1437
|
+
accumulation_U[0] = interpolated_states
|
|
1438
|
+
|
|
1439
|
+
for j, variable in enumerate(self.dae_variables["constant_inputs"]):
|
|
1176
1440
|
variable = variable.name()
|
|
1177
1441
|
constant_input = constant_inputs[variable]
|
|
1178
|
-
accumulation_U[
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
accumulation_U[1 + 2 * len(self.dae_variables[
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1442
|
+
accumulation_U[1 + j] = ca.MX(constant_input[0 : n_collocation_times - 1])
|
|
1443
|
+
accumulation_U[1 + len(self.dae_variables["constant_inputs"]) + j] = ca.MX(
|
|
1444
|
+
constant_input[1:n_collocation_times]
|
|
1445
|
+
)
|
|
1446
|
+
|
|
1447
|
+
accumulation_U[1 + 2 * len(self.dae_variables["constant_inputs"])] = ca.MX(
|
|
1448
|
+
collocation_times[0 : n_collocation_times - 1]
|
|
1449
|
+
)
|
|
1450
|
+
accumulation_U[1 + 2 * len(self.dae_variables["constant_inputs"]) + 1] = ca.MX(
|
|
1451
|
+
collocation_times[1:n_collocation_times]
|
|
1452
|
+
)
|
|
1187
1453
|
|
|
1188
1454
|
path_variables = [None] * len(self.path_variables)
|
|
1189
1455
|
for j, variable in enumerate(self.__path_variable_names):
|
|
1190
1456
|
variable_size = self.__variable_sizes[variable]
|
|
1191
|
-
values = self.state_vector(
|
|
1192
|
-
variable, ensemble_member=ensemble_member)
|
|
1457
|
+
values = self.state_vector(variable, ensemble_member=ensemble_member)
|
|
1193
1458
|
|
|
1194
1459
|
nominal = self.variable_nominal(variable)
|
|
1195
1460
|
if isinstance(nominal, np.ndarray):
|
|
1196
|
-
nominal =
|
|
1461
|
+
nominal = (
|
|
1462
|
+
np.broadcast_to(nominal, (n_collocation_times, variable_size))
|
|
1463
|
+
.transpose()
|
|
1464
|
+
.ravel()
|
|
1465
|
+
)
|
|
1197
1466
|
values *= nominal
|
|
1198
1467
|
elif nominal != 1:
|
|
1199
1468
|
values *= nominal
|
|
1200
1469
|
|
|
1201
1470
|
path_variables[j] = values.reshape((n_collocation_times, variable_size))[1:, :]
|
|
1202
1471
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1472
|
+
path_variables = reduce_matvec(ca.horzcat(*path_variables), self.solver_input)
|
|
1473
|
+
|
|
1474
|
+
accumulation_U[1 + 2 * len(self.dae_variables["constant_inputs"]) + 2] = path_variables
|
|
1206
1475
|
|
|
1207
1476
|
for j, variable in enumerate(self.__extra_constant_inputs):
|
|
1208
1477
|
variable = variable.name()
|
|
1209
1478
|
constant_input = extra_constant_inputs[variable]
|
|
1210
|
-
accumulation_U[1 + 2 * len(self.dae_variables[
|
|
1211
|
-
|
|
1479
|
+
accumulation_U[1 + 2 * len(self.dae_variables["constant_inputs"]) + 3 + j] = ca.MX(
|
|
1480
|
+
constant_input[1:n_collocation_times, :]
|
|
1481
|
+
)
|
|
1212
1482
|
|
|
1213
1483
|
# Construct matrix using O(states) CasADi operations
|
|
1214
1484
|
# This is faster than using blockcat, presumably because of the
|
|
1215
1485
|
# row-wise scaling operations.
|
|
1216
1486
|
logger.info("Aggregating and de-scaling variables")
|
|
1217
1487
|
|
|
1488
|
+
accumulation_U = [var for var in accumulation_U if var.numel() > 0]
|
|
1218
1489
|
accumulation_U = ca.transpose(ca.horzcat(*accumulation_U))
|
|
1219
1490
|
|
|
1220
1491
|
# Map to all time steps
|
|
1221
1492
|
logger.info("Mapping")
|
|
1222
1493
|
|
|
1223
1494
|
# Save these inputs such that can be used later in map_path_expression()
|
|
1224
|
-
self.
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1495
|
+
self.__func_initial_inputs.append(
|
|
1496
|
+
[
|
|
1497
|
+
parameters,
|
|
1498
|
+
ca.vertcat(
|
|
1499
|
+
initial_state,
|
|
1500
|
+
initial_derivatives,
|
|
1501
|
+
initial_constant_inputs,
|
|
1502
|
+
0.0,
|
|
1503
|
+
initial_path_variables,
|
|
1504
|
+
initial_extra_constant_inputs,
|
|
1505
|
+
),
|
|
1506
|
+
extra_variables,
|
|
1507
|
+
]
|
|
1508
|
+
)
|
|
1232
1509
|
|
|
1233
1510
|
if accumulation is not None:
|
|
1234
1511
|
integrators_and_collocation_and_path_constraints = accumulation(
|
|
1235
|
-
|
|
1512
|
+
accumulation_X0,
|
|
1513
|
+
accumulation_U,
|
|
1514
|
+
ca.repmat(ca.vertcat(parameters, extra_variables), 1, n_collocation_times - 1),
|
|
1515
|
+
)
|
|
1236
1516
|
else:
|
|
1237
1517
|
integrators_and_collocation_and_path_constraints = None
|
|
1238
1518
|
|
|
1239
|
-
if accumulation is not None and
|
|
1519
|
+
if accumulation is not None and self.integrate_states:
|
|
1240
1520
|
integrators = integrators_and_collocation_and_path_constraints[0]
|
|
1241
|
-
integrators_and_collocation_and_path_constraints =
|
|
1521
|
+
integrators_and_collocation_and_path_constraints = (
|
|
1522
|
+
integrators_and_collocation_and_path_constraints[1]
|
|
1523
|
+
)
|
|
1242
1524
|
if (
|
|
1243
|
-
accumulation is not None
|
|
1244
|
-
integrators_and_collocation_and_path_constraints.numel() > 0
|
|
1525
|
+
accumulation is not None
|
|
1526
|
+
and integrators_and_collocation_and_path_constraints.numel() > 0
|
|
1245
1527
|
):
|
|
1246
|
-
collocation_constraints = ca.vec(
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1528
|
+
collocation_constraints = ca.vec(
|
|
1529
|
+
integrators_and_collocation_and_path_constraints[
|
|
1530
|
+
:dae_residual_collocated_size, 0 : n_collocation_times - 1
|
|
1531
|
+
]
|
|
1532
|
+
)
|
|
1533
|
+
discretized_path_objective = ca.vec(
|
|
1534
|
+
integrators_and_collocation_and_path_constraints[
|
|
1535
|
+
dae_residual_collocated_size : dae_residual_collocated_size
|
|
1536
|
+
+ path_objective.size1(),
|
|
1537
|
+
0 : n_collocation_times - 1,
|
|
1538
|
+
]
|
|
1539
|
+
)
|
|
1540
|
+
discretized_path_constraints = ca.vec(
|
|
1541
|
+
integrators_and_collocation_and_path_constraints[
|
|
1542
|
+
dae_residual_collocated_size
|
|
1543
|
+
+ path_objective.size1() : dae_residual_collocated_size
|
|
1544
|
+
+ path_objective.size1()
|
|
1545
|
+
+ path_constraint_expressions.size1(),
|
|
1546
|
+
0 : n_collocation_times - 1,
|
|
1547
|
+
]
|
|
1548
|
+
)
|
|
1256
1549
|
discretized_delayed_feedback = integrators_and_collocation_and_path_constraints[
|
|
1257
|
-
dae_residual_collocated_size
|
|
1258
|
-
|
|
1550
|
+
dae_residual_collocated_size
|
|
1551
|
+
+ path_objective.size1()
|
|
1552
|
+
+ path_constraint_expressions.size1() :,
|
|
1553
|
+
0 : n_collocation_times - 1,
|
|
1554
|
+
]
|
|
1259
1555
|
else:
|
|
1260
1556
|
collocation_constraints = ca.MX()
|
|
1261
1557
|
discretized_path_objective = ca.MX()
|
|
@@ -1265,30 +1561,101 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1265
1561
|
logger.info("Composing NLP segment")
|
|
1266
1562
|
|
|
1267
1563
|
# Store integrators for result extraction
|
|
1268
|
-
if
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1564
|
+
if self.integrate_states:
|
|
1565
|
+
# Store integrators for result extraction
|
|
1566
|
+
self.__integrators.append(
|
|
1567
|
+
{
|
|
1568
|
+
variable: integrators[i, :]
|
|
1569
|
+
for i, variable in enumerate(integrated_variable_names)
|
|
1570
|
+
}
|
|
1571
|
+
)
|
|
1572
|
+
else:
|
|
1573
|
+
# Add collocation constraints
|
|
1278
1574
|
g.append(collocation_constraints)
|
|
1279
1575
|
zeros = np.zeros(collocation_constraints.size1())
|
|
1280
1576
|
lbg.extend(zeros)
|
|
1281
1577
|
ubg.extend(zeros)
|
|
1282
1578
|
|
|
1579
|
+
# Prepare arguments for map_path_expression() calls to ca.map()
|
|
1580
|
+
if len(integrated_variables) + len(collocated_variables) > 0:
|
|
1581
|
+
if self.integrate_states:
|
|
1582
|
+
# Inputs
|
|
1583
|
+
states_and_algebraics_and_controls = ca.vertcat(
|
|
1584
|
+
*[
|
|
1585
|
+
self.variable_nominal(variable)
|
|
1586
|
+
* self.__integrators[ensemble_member][variable]
|
|
1587
|
+
for variable in integrated_variable_names
|
|
1588
|
+
],
|
|
1589
|
+
interpolated_states[
|
|
1590
|
+
:,
|
|
1591
|
+
len(collocated_variables) :,
|
|
1592
|
+
].T,
|
|
1593
|
+
)
|
|
1594
|
+
states_and_algebraics_and_controls_derivatives = (
|
|
1595
|
+
(
|
|
1596
|
+
states_and_algebraics_and_controls
|
|
1597
|
+
- ca.horzcat(
|
|
1598
|
+
ensemble_store[ensemble_member]["initial_state"],
|
|
1599
|
+
states_and_algebraics_and_controls[:, :-1],
|
|
1600
|
+
)
|
|
1601
|
+
).T
|
|
1602
|
+
/ (collocation_times[1:] - collocation_times[:-1])
|
|
1603
|
+
).T
|
|
1604
|
+
else:
|
|
1605
|
+
states_and_algebraics_and_controls = interpolated_states[
|
|
1606
|
+
:, len(collocated_variables) :
|
|
1607
|
+
].T
|
|
1608
|
+
states_and_algebraics_and_controls_derivatives = (
|
|
1609
|
+
(
|
|
1610
|
+
interpolated_states[:, len(collocated_variables) :]
|
|
1611
|
+
- interpolated_states[:, : len(collocated_variables)]
|
|
1612
|
+
)
|
|
1613
|
+
/ (collocation_times[1:] - collocation_times[:-1])
|
|
1614
|
+
).T
|
|
1615
|
+
else:
|
|
1616
|
+
states_and_algebraics_and_controls = ca.MX()
|
|
1617
|
+
states_and_algebraics_and_controls_derivatives = ca.MX()
|
|
1618
|
+
|
|
1619
|
+
self.__func_map_args.append(
|
|
1620
|
+
[
|
|
1621
|
+
ca.repmat(
|
|
1622
|
+
ca.vertcat(*ensemble_parameter_values[ensemble_member]),
|
|
1623
|
+
1,
|
|
1624
|
+
n_collocation_times - 1,
|
|
1625
|
+
),
|
|
1626
|
+
ca.vertcat(
|
|
1627
|
+
states_and_algebraics_and_controls,
|
|
1628
|
+
states_and_algebraics_and_controls_derivatives,
|
|
1629
|
+
*[
|
|
1630
|
+
ca.horzcat(*constant_inputs[variable][1:])
|
|
1631
|
+
for variable in dae_constant_inputs_names
|
|
1632
|
+
],
|
|
1633
|
+
ca.horzcat(*collocation_times[1:]),
|
|
1634
|
+
path_variables.T if path_variables.numel() > 0 else ca.MX(),
|
|
1635
|
+
*[
|
|
1636
|
+
ca.horzcat(*extra_constant_inputs[variable][1:])
|
|
1637
|
+
for (variable, _) in extra_constant_inputs_name_and_size
|
|
1638
|
+
],
|
|
1639
|
+
),
|
|
1640
|
+
ca.repmat(extra_variables, 1, n_collocation_times - 1),
|
|
1641
|
+
]
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1283
1644
|
# Delayed feedback
|
|
1284
1645
|
# Make an array of all unique times in history series
|
|
1285
1646
|
history_times = np.unique(
|
|
1286
|
-
np.hstack(
|
|
1287
|
-
|
|
1647
|
+
np.hstack(
|
|
1648
|
+
(np.array([]), *[history_series.times for history_series in history.values()])
|
|
1649
|
+
)
|
|
1650
|
+
)
|
|
1651
|
+
# By convention, the last timestep in history series is the initial time. We drop this
|
|
1652
|
+
# index
|
|
1288
1653
|
history_times = history_times[:-1]
|
|
1289
1654
|
|
|
1290
1655
|
# Find the historical values of states, extrapolating backward if necessary
|
|
1291
|
-
history_values = np.empty(
|
|
1656
|
+
history_values = np.empty(
|
|
1657
|
+
(history_times.shape[0], len(integrated_variables) + len(collocated_variables))
|
|
1658
|
+
)
|
|
1292
1659
|
if history_times.shape[0] > 0:
|
|
1293
1660
|
for j, var in enumerate(integrated_variables + collocated_variables):
|
|
1294
1661
|
var_name = var.name()
|
|
@@ -1304,19 +1671,23 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1304
1671
|
history_series.values,
|
|
1305
1672
|
np.nan,
|
|
1306
1673
|
np.nan,
|
|
1307
|
-
interpolation_method
|
|
1674
|
+
interpolation_method,
|
|
1675
|
+
)
|
|
1308
1676
|
|
|
1309
1677
|
# Calculate the historical derivatives of historical values
|
|
1310
1678
|
history_derivatives = ca.repmat(np.nan, 1, history_values.shape[1])
|
|
1311
1679
|
if history_times.shape[0] > 1:
|
|
1312
1680
|
history_derivatives = ca.vertcat(
|
|
1313
1681
|
history_derivatives,
|
|
1314
|
-
np.diff(history_values, axis=0) / np.diff(history_times)[:, None]
|
|
1682
|
+
np.diff(history_values, axis=0) / np.diff(history_times)[:, None],
|
|
1683
|
+
)
|
|
1315
1684
|
|
|
1316
1685
|
# Find the historical values of constant inputs, extrapolating backward if necessary
|
|
1317
|
-
constant_input_values = np.empty(
|
|
1686
|
+
constant_input_values = np.empty(
|
|
1687
|
+
(history_times.shape[0], len(self.dae_variables["constant_inputs"]))
|
|
1688
|
+
)
|
|
1318
1689
|
if history_times.shape[0] > 0:
|
|
1319
|
-
for j, var in enumerate(self.dae_variables[
|
|
1690
|
+
for j, var in enumerate(self.dae_variables["constant_inputs"]):
|
|
1320
1691
|
var_name = var.name()
|
|
1321
1692
|
try:
|
|
1322
1693
|
constant_input_series = raw_constant_inputs[var_name]
|
|
@@ -1330,41 +1701,62 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1330
1701
|
constant_input_series.values,
|
|
1331
1702
|
np.nan,
|
|
1332
1703
|
np.nan,
|
|
1333
|
-
interpolation_method
|
|
1704
|
+
interpolation_method,
|
|
1705
|
+
)
|
|
1334
1706
|
|
|
1335
1707
|
if len(delayed_feedback_expressions) > 0:
|
|
1336
|
-
delayed_feedback_history = np.zeros(
|
|
1708
|
+
delayed_feedback_history = np.zeros(
|
|
1709
|
+
(history_times.shape[0], len(delayed_feedback_expressions))
|
|
1710
|
+
)
|
|
1337
1711
|
for i, time in enumerate(history_times):
|
|
1338
1712
|
history_delayed_feedback_res = delayed_feedback_function.call(
|
|
1339
|
-
[
|
|
1340
|
-
|
|
1341
|
-
ca.
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1713
|
+
[
|
|
1714
|
+
parameters,
|
|
1715
|
+
ca.veccat(
|
|
1716
|
+
ca.transpose(history_values[i, :]),
|
|
1717
|
+
ca.transpose(history_derivatives[i, :]),
|
|
1718
|
+
ca.transpose(constant_input_values[i, :]),
|
|
1719
|
+
time,
|
|
1720
|
+
ca.repmat(np.nan, len(self.path_variables)),
|
|
1721
|
+
ca.repmat(np.nan, len(self.__extra_constant_inputs)),
|
|
1722
|
+
),
|
|
1723
|
+
ca.repmat(np.nan, len(self.extra_variables)),
|
|
1724
|
+
]
|
|
1725
|
+
)
|
|
1347
1726
|
delayed_feedback_history[i, :] = [
|
|
1348
|
-
float(val) for val in history_delayed_feedback_res
|
|
1727
|
+
float(val) for val in history_delayed_feedback_res
|
|
1728
|
+
]
|
|
1349
1729
|
|
|
1350
1730
|
initial_delayed_feedback = delayed_feedback_function.call(
|
|
1351
|
-
self.__func_initial_inputs[ensemble_member], False, True
|
|
1731
|
+
self.__func_initial_inputs[ensemble_member], False, True
|
|
1732
|
+
)
|
|
1352
1733
|
|
|
1353
1734
|
path_variables_nominal = np.ones(path_variables_size)
|
|
1354
1735
|
offset = 0
|
|
1355
1736
|
for variable in self.__path_variable_names:
|
|
1356
1737
|
variable_size = self.__variable_sizes[variable]
|
|
1357
|
-
path_variables_nominal[offset:offset + variable_size] = self.variable_nominal(
|
|
1738
|
+
path_variables_nominal[offset : offset + variable_size] = self.variable_nominal(
|
|
1739
|
+
variable
|
|
1740
|
+
)
|
|
1358
1741
|
offset += variable_size
|
|
1359
1742
|
|
|
1360
1743
|
nominal_delayed_feedback = delayed_feedback_function.call(
|
|
1361
|
-
[
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1744
|
+
[
|
|
1745
|
+
parameters,
|
|
1746
|
+
ca.vertcat(
|
|
1747
|
+
[
|
|
1748
|
+
self.variable_nominal(var.name())
|
|
1749
|
+
for var in integrated_variables + collocated_variables
|
|
1750
|
+
],
|
|
1751
|
+
np.zeros((initial_derivatives.size1(), 1)),
|
|
1752
|
+
initial_constant_inputs,
|
|
1753
|
+
0.0,
|
|
1754
|
+
path_variables_nominal,
|
|
1755
|
+
initial_extra_constant_inputs,
|
|
1756
|
+
),
|
|
1757
|
+
extra_variables,
|
|
1758
|
+
]
|
|
1759
|
+
)
|
|
1368
1760
|
|
|
1369
1761
|
if delayed_feedback_expressions:
|
|
1370
1762
|
# Resolve delay values
|
|
@@ -1377,19 +1769,21 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1377
1769
|
substituted_delay_durations = ca.substitute(
|
|
1378
1770
|
delayed_feedback_durations,
|
|
1379
1771
|
[ca.vertcat(symbolic_parameters)],
|
|
1380
|
-
[ca.vertcat(parameters)]
|
|
1772
|
+
[ca.vertcat(parameters)],
|
|
1773
|
+
)
|
|
1381
1774
|
|
|
1382
1775
|
# Use mapped function to evaluate delay in terms of constant inputs
|
|
1383
1776
|
mapped_delay_function = ca.Function(
|
|
1384
|
-
|
|
1385
|
-
self.dae_variables[
|
|
1386
|
-
substituted_delay_durations
|
|
1777
|
+
"delay_values",
|
|
1778
|
+
self.dae_variables["time"] + self.dae_variables["constant_inputs"],
|
|
1779
|
+
substituted_delay_durations,
|
|
1387
1780
|
).map(len(collocation_times))
|
|
1388
1781
|
|
|
1389
1782
|
# Call mapped delay function with inputs as arrays
|
|
1390
1783
|
evaluated_delay_durations = mapped_delay_function.call(
|
|
1391
|
-
[collocation_times]
|
|
1392
|
-
[constant_inputs[v.name()] for v in self.dae_variables[
|
|
1784
|
+
[collocation_times]
|
|
1785
|
+
+ [constant_inputs[v.name()] for v in self.dae_variables["constant_inputs"]]
|
|
1786
|
+
)
|
|
1393
1787
|
|
|
1394
1788
|
for i in range(len(delayed_feedback_expressions)):
|
|
1395
1789
|
in_variable_name = delayed_feedback_states[i]
|
|
@@ -1397,27 +1791,28 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1397
1791
|
delay = evaluated_delay_durations[i]
|
|
1398
1792
|
|
|
1399
1793
|
# Resolve aliases
|
|
1400
|
-
in_canonical, in_sign = self.alias_relation.canonical_signed(
|
|
1401
|
-
in_variable_name)
|
|
1794
|
+
in_canonical, in_sign = self.alias_relation.canonical_signed(in_variable_name)
|
|
1402
1795
|
in_times = self.times(in_canonical)
|
|
1403
1796
|
in_nominal = self.variable_nominal(in_canonical)
|
|
1404
|
-
in_values = in_nominal *
|
|
1405
|
-
|
|
1406
|
-
|
|
1797
|
+
in_values = in_nominal * self.state_vector(
|
|
1798
|
+
in_canonical, ensemble_member=ensemble_member
|
|
1799
|
+
)
|
|
1407
1800
|
if in_sign < 0:
|
|
1408
1801
|
in_values *= in_sign
|
|
1409
1802
|
|
|
1410
1803
|
# Cast delay from DM to np.array
|
|
1411
1804
|
delay = delay.toarray().flatten()
|
|
1412
1805
|
|
|
1413
|
-
assert np.all(
|
|
1414
|
-
|
|
1806
|
+
assert np.all(
|
|
1807
|
+
np.isfinite(delay)
|
|
1808
|
+
), "Delay duration must be resolvable to real values at transcribe()"
|
|
1415
1809
|
|
|
1416
1810
|
out_times = np.concatenate([history_times, collocation_times])
|
|
1417
1811
|
out_values = ca.veccat(
|
|
1418
1812
|
delayed_feedback_history[:, i],
|
|
1419
1813
|
initial_delayed_feedback[i],
|
|
1420
|
-
ca.transpose(discretized_delayed_feedback[i, :])
|
|
1814
|
+
ca.transpose(discretized_delayed_feedback[i, :]),
|
|
1815
|
+
)
|
|
1421
1816
|
|
|
1422
1817
|
# Check whether enough history has been specified, and that no
|
|
1423
1818
|
# needed history values are missing
|
|
@@ -1427,25 +1822,32 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1427
1822
|
# We need an earlier value to interpolate with
|
|
1428
1823
|
hist_start_ind -= 1
|
|
1429
1824
|
|
|
1430
|
-
if hist_start_ind < 0 or np.any(
|
|
1825
|
+
if hist_start_ind < 0 or np.any(
|
|
1826
|
+
np.isnan(delayed_feedback_history[hist_start_ind:, i])
|
|
1827
|
+
):
|
|
1431
1828
|
logger.warning(
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
out_times = out_times[len(history_times):]
|
|
1436
|
-
out_values = out_values[len(history_times):]
|
|
1829
|
+
"Incomplete history for delayed expression {}. "
|
|
1830
|
+
"Extrapolating t0 value backwards in time.".format(expression)
|
|
1831
|
+
)
|
|
1832
|
+
out_times = out_times[len(history_times) :]
|
|
1833
|
+
out_values = out_values[len(history_times) :]
|
|
1437
1834
|
|
|
1438
1835
|
# Set up delay constraints
|
|
1439
1836
|
if len(collocation_times) != len(in_times):
|
|
1440
|
-
interpolation_method = self.interpolation_method(
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1837
|
+
interpolation_method = self.interpolation_method(in_canonical)
|
|
1838
|
+
x_in = interpolate(
|
|
1839
|
+
in_times, in_values, collocation_times, False, interpolation_method
|
|
1840
|
+
)
|
|
1444
1841
|
else:
|
|
1445
1842
|
x_in = in_values
|
|
1446
1843
|
interpolation_method = self.interpolation_method(in_canonical)
|
|
1447
1844
|
x_out_delayed = interpolate(
|
|
1448
|
-
out_times,
|
|
1845
|
+
out_times,
|
|
1846
|
+
out_values,
|
|
1847
|
+
collocation_times - delay,
|
|
1848
|
+
False,
|
|
1849
|
+
interpolation_method,
|
|
1850
|
+
)
|
|
1449
1851
|
|
|
1450
1852
|
nominal = nominal_delayed_feedback[i]
|
|
1451
1853
|
|
|
@@ -1460,25 +1862,24 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1460
1862
|
f_member = 0
|
|
1461
1863
|
if path_objective.size1() > 0:
|
|
1462
1864
|
initial_path_objective = path_objective_function.call(
|
|
1463
|
-
self.__func_initial_inputs[ensemble_member], False, True
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
f.append(self.ensemble_member_probability(
|
|
1467
|
-
ensemble_member) * f_member)
|
|
1865
|
+
self.__func_initial_inputs[ensemble_member], False, True
|
|
1866
|
+
)
|
|
1867
|
+
f_member += initial_path_objective[0] + ca.sum1(discretized_path_objective)
|
|
1868
|
+
f.append(self.ensemble_member_probability(ensemble_member) * f_member)
|
|
1468
1869
|
|
|
1469
1870
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
1470
|
-
logger.debug(
|
|
1471
|
-
"Adding objective {}".format(f_member))
|
|
1871
|
+
logger.debug("Adding objective {}".format(f_member))
|
|
1472
1872
|
|
|
1473
1873
|
# Constraints
|
|
1474
1874
|
constraints = self.constraints(ensemble_member)
|
|
1475
1875
|
if constraints is None:
|
|
1476
|
-
raise Exception(
|
|
1876
|
+
raise Exception(
|
|
1877
|
+
"The `constraints` method returned None, but should always return a list."
|
|
1878
|
+
)
|
|
1477
1879
|
|
|
1478
1880
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
1479
1881
|
for constraint in constraints:
|
|
1480
|
-
logger.debug(
|
|
1481
|
-
"Adding constraint {}, {}, {}".format(*constraint))
|
|
1882
|
+
logger.debug("Adding constraint {}, {}, {}".format(*constraint))
|
|
1482
1883
|
|
|
1483
1884
|
if constraints:
|
|
1484
1885
|
g_constraint, lbg_constraint, ubg_constraint = list(zip(*constraints))
|
|
@@ -1487,20 +1888,30 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1487
1888
|
ubg_constraint = list(ubg_constraint)
|
|
1488
1889
|
|
|
1489
1890
|
# Broadcast lbg/ubg if it's a vector constraint
|
|
1490
|
-
for i, (g_i, lbg_i, ubg_i) in enumerate(
|
|
1891
|
+
for i, (g_i, lbg_i, ubg_i) in enumerate(
|
|
1892
|
+
zip(g_constraint, lbg_constraint, ubg_constraint)
|
|
1893
|
+
):
|
|
1491
1894
|
s = g_i.size1()
|
|
1492
1895
|
if s > 1:
|
|
1493
1896
|
if not isinstance(lbg_i, np.ndarray) or lbg_i.shape[0] == 1:
|
|
1494
1897
|
lbg_constraint[i] = np.full(s, lbg_i)
|
|
1495
1898
|
elif lbg_i.shape[0] != g_i.shape[0]:
|
|
1496
|
-
raise Exception(
|
|
1497
|
-
|
|
1899
|
+
raise Exception(
|
|
1900
|
+
"Shape mismatch between constraint "
|
|
1901
|
+
"#{} ({},) and its lower bound ({},)".format(
|
|
1902
|
+
i, g_i.shape[0], lbg_i.shape[0]
|
|
1903
|
+
)
|
|
1904
|
+
)
|
|
1498
1905
|
|
|
1499
1906
|
if not isinstance(ubg_i, np.ndarray) or ubg_i.shape[0] == 1:
|
|
1500
1907
|
ubg_constraint[i] = np.full(s, ubg_i)
|
|
1501
1908
|
elif ubg_i.shape[0] != g_i.shape[0]:
|
|
1502
|
-
raise Exception(
|
|
1503
|
-
|
|
1909
|
+
raise Exception(
|
|
1910
|
+
"Shape mismatch between constraint "
|
|
1911
|
+
"#{} ({},) and its upper bound ({},)".format(
|
|
1912
|
+
i, g_i.shape[0], ubg_i.shape[0]
|
|
1913
|
+
)
|
|
1914
|
+
)
|
|
1504
1915
|
|
|
1505
1916
|
g.extend(g_constraint)
|
|
1506
1917
|
lbg.extend(lbg_constraint)
|
|
@@ -1516,45 +1927,51 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1516
1927
|
# We need to evaluate the path constraints at t0, as the initial time is not
|
|
1517
1928
|
# included in the accumulation.
|
|
1518
1929
|
[initial_path_constraints] = path_constraints_function.call(
|
|
1519
|
-
self.__func_initial_inputs[ensemble_member], False, True
|
|
1930
|
+
self.__func_initial_inputs[ensemble_member], False, True
|
|
1931
|
+
)
|
|
1520
1932
|
g.append(initial_path_constraints)
|
|
1521
1933
|
g.append(discretized_path_constraints)
|
|
1522
1934
|
|
|
1523
1935
|
lbg_path_constraints = np.empty(
|
|
1524
|
-
(path_constraint_expressions.size1(), n_collocation_times)
|
|
1936
|
+
(path_constraint_expressions.size1(), n_collocation_times)
|
|
1937
|
+
)
|
|
1525
1938
|
ubg_path_constraints = np.empty(
|
|
1526
|
-
(path_constraint_expressions.size1(), n_collocation_times)
|
|
1939
|
+
(path_constraint_expressions.size1(), n_collocation_times)
|
|
1940
|
+
)
|
|
1527
1941
|
|
|
1528
1942
|
j = 0
|
|
1529
1943
|
for path_constraint in path_constraints:
|
|
1530
1944
|
if logger.getEffectiveLevel() == logging.DEBUG:
|
|
1531
|
-
logger.debug(
|
|
1532
|
-
"Adding path constraint {}, {}, {}".format(*path_constraint))
|
|
1945
|
+
logger.debug("Adding path constraint {}, {}, {}".format(*path_constraint))
|
|
1533
1946
|
|
|
1534
1947
|
s = path_constraint[0].size1()
|
|
1535
1948
|
|
|
1536
1949
|
lb = path_constraint[1]
|
|
1537
1950
|
if isinstance(lb, ca.MX) and not lb.is_constant():
|
|
1538
1951
|
[lb] = ca.substitute(
|
|
1539
|
-
[lb], symbolic_parameters, self.__parameter_values_ensemble_member_0
|
|
1952
|
+
[lb], symbolic_parameters, self.__parameter_values_ensemble_member_0
|
|
1953
|
+
)
|
|
1540
1954
|
elif isinstance(lb, Timeseries):
|
|
1541
1955
|
lb = self.interpolate(
|
|
1542
|
-
collocation_times, lb.times, lb.values, -np.inf, -np.inf
|
|
1956
|
+
collocation_times, lb.times, lb.values, -np.inf, -np.inf
|
|
1957
|
+
).transpose()
|
|
1543
1958
|
elif isinstance(lb, np.ndarray):
|
|
1544
1959
|
lb = np.broadcast_to(lb, (n_collocation_times, s)).transpose()
|
|
1545
1960
|
|
|
1546
1961
|
ub = path_constraint[2]
|
|
1547
1962
|
if isinstance(ub, ca.MX) and not ub.is_constant():
|
|
1548
1963
|
[ub] = ca.substitute(
|
|
1549
|
-
[ub], symbolic_parameters, self.__parameter_values_ensemble_member_0
|
|
1964
|
+
[ub], symbolic_parameters, self.__parameter_values_ensemble_member_0
|
|
1965
|
+
)
|
|
1550
1966
|
elif isinstance(ub, Timeseries):
|
|
1551
1967
|
ub = self.interpolate(
|
|
1552
|
-
collocation_times, ub.times, ub.values, np.inf, np.inf
|
|
1968
|
+
collocation_times, ub.times, ub.values, np.inf, np.inf
|
|
1969
|
+
).transpose()
|
|
1553
1970
|
elif isinstance(ub, np.ndarray):
|
|
1554
1971
|
ub = np.broadcast_to(ub, (n_collocation_times, s)).transpose()
|
|
1555
1972
|
|
|
1556
|
-
lbg_path_constraints[j:j+s, :] = lb
|
|
1557
|
-
ubg_path_constraints[j:j+s, :] = ub
|
|
1973
|
+
lbg_path_constraints[j : j + s, :] = lb
|
|
1974
|
+
ubg_path_constraints[j : j + s, :] = ub
|
|
1558
1975
|
|
|
1559
1976
|
j += s
|
|
1560
1977
|
|
|
@@ -1564,7 +1981,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1564
1981
|
# NLP function
|
|
1565
1982
|
logger.info("Creating NLP dictionary")
|
|
1566
1983
|
|
|
1567
|
-
nlp = {
|
|
1984
|
+
nlp = {"x": X, "f": ca.sum1(ca.vertcat(*f)), "g": ca.vertcat(*g)}
|
|
1568
1985
|
|
|
1569
1986
|
# Done
|
|
1570
1987
|
logger.info("Done transcribing problem")
|
|
@@ -1604,21 +2021,21 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1604
2021
|
return self.__solver_input
|
|
1605
2022
|
|
|
1606
2023
|
def solver_options(self):
|
|
1607
|
-
options = super(CollocatedIntegratedOptimizationProblem,
|
|
1608
|
-
self).solver_options()
|
|
2024
|
+
options = super(CollocatedIntegratedOptimizationProblem, self).solver_options()
|
|
1609
2025
|
|
|
1610
|
-
solver = options[
|
|
1611
|
-
assert solver in [
|
|
2026
|
+
solver = options["solver"]
|
|
2027
|
+
assert solver in ["bonmin", "ipopt"]
|
|
1612
2028
|
|
|
1613
2029
|
# Set the option in both cases, to avoid one inadvertently remaining in the cache.
|
|
1614
|
-
options[solver][
|
|
2030
|
+
options[solver]["jac_c_constant"] = "yes" if self.linear_collocation else "no"
|
|
1615
2031
|
return options
|
|
1616
2032
|
|
|
1617
2033
|
def integrator_options(self):
|
|
1618
2034
|
"""
|
|
1619
2035
|
Configures the implicit function used for time step integration.
|
|
1620
2036
|
|
|
1621
|
-
:returns: A dictionary of CasADi :class:`rootfinder` options. See the CasADi documentation
|
|
2037
|
+
:returns: A dictionary of CasADi :class:`rootfinder` options. See the CasADi documentation
|
|
2038
|
+
for details.
|
|
1622
2039
|
"""
|
|
1623
2040
|
return {}
|
|
1624
2041
|
|
|
@@ -1633,7 +2050,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1633
2050
|
ubx = np.full(count, np.inf, dtype=np.float64)
|
|
1634
2051
|
|
|
1635
2052
|
# Variables that are not collocated, and only have a single entry in the state vector
|
|
1636
|
-
scalar_variables_set = set(self.__extra_variable_names) | set(self.
|
|
2053
|
+
scalar_variables_set = set(self.__extra_variable_names) | set(self.__integrated_states)
|
|
1637
2054
|
|
|
1638
2055
|
variable_sizes = self.__variable_sizes
|
|
1639
2056
|
|
|
@@ -1657,7 +2074,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1657
2074
|
nominal = self.variable_nominal(variable)
|
|
1658
2075
|
interpolation_method = self.interpolation_method(variable)
|
|
1659
2076
|
if isinstance(nominal, np.ndarray):
|
|
1660
|
-
nominal =
|
|
2077
|
+
nominal = (
|
|
2078
|
+
np.broadcast_to(nominal, (n_times, variable_size)).transpose().ravel()
|
|
2079
|
+
)
|
|
1661
2080
|
|
|
1662
2081
|
if bound[0] is not None:
|
|
1663
2082
|
if isinstance(bound[0], Timeseries):
|
|
@@ -1667,9 +2086,14 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1667
2086
|
bound[0].values,
|
|
1668
2087
|
-np.inf,
|
|
1669
2088
|
-np.inf,
|
|
1670
|
-
interpolation_method
|
|
2089
|
+
interpolation_method,
|
|
2090
|
+
).ravel()
|
|
1671
2091
|
elif isinstance(bound[0], np.ndarray):
|
|
1672
|
-
lower_bound =
|
|
2092
|
+
lower_bound = (
|
|
2093
|
+
np.broadcast_to(bound[0], (n_times, variable_size))
|
|
2094
|
+
.transpose()
|
|
2095
|
+
.ravel()
|
|
2096
|
+
)
|
|
1673
2097
|
else:
|
|
1674
2098
|
lower_bound = bound[0]
|
|
1675
2099
|
lbx[inds] = lower_bound / nominal
|
|
@@ -1682,18 +2106,23 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1682
2106
|
bound[1].values,
|
|
1683
2107
|
+np.inf,
|
|
1684
2108
|
+np.inf,
|
|
1685
|
-
interpolation_method
|
|
2109
|
+
interpolation_method,
|
|
2110
|
+
).ravel()
|
|
1686
2111
|
elif isinstance(bound[1], np.ndarray):
|
|
1687
|
-
upper_bound =
|
|
2112
|
+
upper_bound = (
|
|
2113
|
+
np.broadcast_to(bound[1], (n_times, variable_size))
|
|
2114
|
+
.transpose()
|
|
2115
|
+
.ravel()
|
|
2116
|
+
)
|
|
1688
2117
|
else:
|
|
1689
2118
|
upper_bound = bound[1]
|
|
1690
2119
|
ubx[inds] = upper_bound / nominal
|
|
1691
2120
|
|
|
1692
2121
|
# Warn for NaNs
|
|
1693
2122
|
if np.any(np.isnan(lbx[inds])):
|
|
1694
|
-
logger.error(
|
|
2123
|
+
logger.error("Lower bound on variable {} contains NaN".format(variable))
|
|
1695
2124
|
if np.any(np.isnan(ubx[inds])):
|
|
1696
|
-
logger.error(
|
|
2125
|
+
logger.error("Upper bound on variable {} contains NaN".format(variable))
|
|
1697
2126
|
|
|
1698
2127
|
return lbx, ubx
|
|
1699
2128
|
|
|
@@ -1701,7 +2130,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1701
2130
|
x0 = np.zeros(count, dtype=np.float64)
|
|
1702
2131
|
|
|
1703
2132
|
# Variables that are not collocated, and only have a single entry in the state vector
|
|
1704
|
-
scalar_variables_set = set(self.__extra_variable_names) | set(self.
|
|
2133
|
+
scalar_variables_set = set(self.__extra_variable_names) | set(self.__integrated_states)
|
|
1705
2134
|
|
|
1706
2135
|
variable_sizes = self.__variable_sizes
|
|
1707
2136
|
|
|
@@ -1722,16 +2151,18 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1722
2151
|
nominal = self.variable_nominal(variable)
|
|
1723
2152
|
interpolation_method = self.interpolation_method(variable)
|
|
1724
2153
|
if isinstance(nominal, np.ndarray):
|
|
1725
|
-
nominal =
|
|
2154
|
+
nominal = (
|
|
2155
|
+
np.broadcast_to(nominal, (n_times, variable_size)).transpose().ravel()
|
|
2156
|
+
)
|
|
1726
2157
|
|
|
1727
2158
|
if isinstance(seed_k, Timeseries):
|
|
1728
|
-
x0[inds] =
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
2159
|
+
x0[inds] = (
|
|
2160
|
+
self.interpolate(
|
|
2161
|
+
times, seed_k.times, seed_k.values, 0, 0, interpolation_method
|
|
2162
|
+
)
|
|
2163
|
+
.transpose()
|
|
2164
|
+
.ravel()
|
|
2165
|
+
)
|
|
1735
2166
|
else:
|
|
1736
2167
|
x0[inds] = seed_k
|
|
1737
2168
|
|
|
@@ -1749,27 +2180,34 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1749
2180
|
|
|
1750
2181
|
return discrete
|
|
1751
2182
|
|
|
1752
|
-
def
|
|
2183
|
+
def discretize_control(self, variable, ensemble_member, times, offset):
|
|
1753
2184
|
# Default implementation: One single set of control inputs for all
|
|
1754
2185
|
# ensembles
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
2186
|
+
try:
|
|
2187
|
+
return self.__discretize_control_cache[variable]
|
|
2188
|
+
except KeyError:
|
|
2189
|
+
control_indices = slice(offset, offset + len(times))
|
|
2190
|
+
self.__discretize_control_cache[variable] = control_indices
|
|
2191
|
+
return control_indices
|
|
1759
2192
|
|
|
1760
|
-
|
|
2193
|
+
def discretize_controls(self, bounds):
|
|
2194
|
+
self.__discretize_control_cache = {}
|
|
1761
2195
|
|
|
1762
2196
|
indices = [{} for ensemble_member in range(self.ensemble_size)]
|
|
1763
2197
|
|
|
1764
|
-
|
|
2198
|
+
count = 0
|
|
1765
2199
|
for variable in self.controls:
|
|
1766
2200
|
times = self.times(variable)
|
|
1767
|
-
n_times = len(times)
|
|
1768
2201
|
|
|
1769
2202
|
for ensemble_member in range(self.ensemble_size):
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
2203
|
+
control_indices = self.discretize_control(variable, ensemble_member, times, count)
|
|
2204
|
+
indices[ensemble_member][variable] = control_indices
|
|
2205
|
+
control_indices_stop = (
|
|
2206
|
+
control_indices.stop
|
|
2207
|
+
if isinstance(control_indices, slice)
|
|
2208
|
+
else (int(np.max(control_indices)) + 1)
|
|
2209
|
+
) # indices need not be ordered
|
|
2210
|
+
count = max(count, control_indices_stop)
|
|
1773
2211
|
|
|
1774
2212
|
discrete = self._collint_get_discrete(count, indices)
|
|
1775
2213
|
lbx, ubx = self._collint_get_lbx_ubx(count, indices)
|
|
@@ -1808,23 +2246,36 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1808
2246
|
|
|
1809
2247
|
def discretize_states(self, bounds):
|
|
1810
2248
|
# Default implementation: States for all ensemble members
|
|
1811
|
-
ensemble_member_size = 0
|
|
1812
|
-
|
|
1813
2249
|
variable_sizes = self.__variable_sizes
|
|
1814
2250
|
|
|
1815
2251
|
# Space for collocated states
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2252
|
+
ensemble_member_size = 0
|
|
2253
|
+
if self.integrate_states:
|
|
2254
|
+
n_model_states = len(self.differentiated_states) + len(self.algebraic_states)
|
|
2255
|
+
if len(self.__integrated_states) != n_model_states:
|
|
2256
|
+
error_msg = (
|
|
2257
|
+
"CollocatedIntegratedOptimizationProblem: "
|
|
2258
|
+
"integrated_states should specify all model states, or none at all"
|
|
2259
|
+
)
|
|
2260
|
+
logger.error(error_msg)
|
|
2261
|
+
raise Exception(error_msg)
|
|
2262
|
+
|
|
2263
|
+
# Count initial states only
|
|
2264
|
+
ensemble_member_size += n_model_states
|
|
2265
|
+
else:
|
|
2266
|
+
# Count discretised states over optimization horizon
|
|
2267
|
+
for variable in itertools.chain(self.differentiated_states, self.algebraic_states):
|
|
1820
2268
|
ensemble_member_size += variable_sizes[variable] * len(self.times(variable))
|
|
2269
|
+
# Count any additional path variables (which cannot be integrated)
|
|
2270
|
+
for variable in self.__path_variable_names:
|
|
2271
|
+
ensemble_member_size += variable_sizes[variable] * len(self.times(variable))
|
|
1821
2272
|
|
|
1822
2273
|
# Space for extra variables
|
|
1823
2274
|
for variable in self.__extra_variable_names:
|
|
1824
2275
|
ensemble_member_size += variable_sizes[variable]
|
|
1825
2276
|
|
|
1826
2277
|
# Space for initial states and derivatives
|
|
1827
|
-
ensemble_member_size += len(self.dae_variables[
|
|
2278
|
+
ensemble_member_size += len(self.dae_variables["derivatives"])
|
|
1828
2279
|
|
|
1829
2280
|
# Total space requirement
|
|
1830
2281
|
count = self.ensemble_size * ensemble_member_size
|
|
@@ -1834,12 +2285,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1834
2285
|
|
|
1835
2286
|
for ensemble_member in range(self.ensemble_size):
|
|
1836
2287
|
offset = ensemble_member * ensemble_member_size
|
|
1837
|
-
for variable in itertools.chain(
|
|
1838
|
-
self.differentiated_states, self.algebraic_states, self.__path_variable_names):
|
|
1839
|
-
|
|
2288
|
+
for variable in itertools.chain(self.differentiated_states, self.algebraic_states):
|
|
1840
2289
|
variable_size = variable_sizes[variable]
|
|
1841
2290
|
|
|
1842
|
-
if
|
|
2291
|
+
if self.integrate_states:
|
|
1843
2292
|
assert variable_size == 1
|
|
1844
2293
|
indices[ensemble_member][variable] = offset
|
|
1845
2294
|
|
|
@@ -1848,10 +2297,22 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1848
2297
|
times = self.times(variable)
|
|
1849
2298
|
n_times = len(times)
|
|
1850
2299
|
|
|
1851
|
-
indices[ensemble_member][variable] = slice(
|
|
2300
|
+
indices[ensemble_member][variable] = slice(
|
|
2301
|
+
offset, offset + n_times * variable_size
|
|
2302
|
+
)
|
|
1852
2303
|
|
|
1853
2304
|
offset += n_times * variable_size
|
|
1854
2305
|
|
|
2306
|
+
for variable in self.__path_variable_names:
|
|
2307
|
+
variable_size = variable_sizes[variable]
|
|
2308
|
+
|
|
2309
|
+
times = self.times(variable)
|
|
2310
|
+
n_times = len(times)
|
|
2311
|
+
|
|
2312
|
+
indices[ensemble_member][variable] = slice(offset, offset + n_times * variable_size)
|
|
2313
|
+
|
|
2314
|
+
offset += n_times * variable_size
|
|
2315
|
+
|
|
1855
2316
|
for extra_variable in self.__extra_variable_names:
|
|
1856
2317
|
variable_size = variable_sizes[extra_variable]
|
|
1857
2318
|
|
|
@@ -1875,27 +2336,40 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1875
2336
|
# Solver output
|
|
1876
2337
|
X = self.solver_output.copy()
|
|
1877
2338
|
|
|
2339
|
+
indices = self.__indices[ensemble_member]
|
|
2340
|
+
|
|
1878
2341
|
# Extract control inputs
|
|
1879
2342
|
results = {}
|
|
1880
2343
|
|
|
1881
2344
|
# Perform integration, in order to extract integrated variables
|
|
1882
2345
|
# We bundle all integrations into a single Function, so that subexpressions
|
|
1883
2346
|
# are evaluated only once.
|
|
1884
|
-
if
|
|
1885
|
-
# Use
|
|
2347
|
+
if self.integrate_states:
|
|
2348
|
+
# Use integrators to facilitate common subexpression
|
|
1886
2349
|
# elimination.
|
|
1887
|
-
f = ca.Function(
|
|
1888
|
-
|
|
2350
|
+
f = ca.Function(
|
|
2351
|
+
"f",
|
|
2352
|
+
[self.solver_input],
|
|
2353
|
+
[
|
|
2354
|
+
ca.vertcat(
|
|
2355
|
+
*[
|
|
2356
|
+
self.__integrators[ensemble_member][variable]
|
|
2357
|
+
for variable in self.__integrated_states
|
|
2358
|
+
]
|
|
2359
|
+
)
|
|
2360
|
+
],
|
|
2361
|
+
)
|
|
1889
2362
|
integrators_output = f(X)
|
|
1890
2363
|
j = 0
|
|
1891
|
-
for variable in self.
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
2364
|
+
for variable in self.__integrated_states:
|
|
2365
|
+
inds = indices[variable]
|
|
2366
|
+
initial_value = X[inds]
|
|
2367
|
+
n = self.__integrators[ensemble_member][variable].size1()
|
|
2368
|
+
results[variable] = self.variable_nominal(variable) * np.concatenate(
|
|
2369
|
+
[[initial_value], np.array(integrators_output[j : j + n, :]).ravel()]
|
|
2370
|
+
)
|
|
1895
2371
|
j += n
|
|
1896
2372
|
|
|
1897
|
-
indices = self.__indices[ensemble_member]
|
|
1898
|
-
|
|
1899
2373
|
# Extract initial derivatives
|
|
1900
2374
|
for initial_der_name in self.__initial_derivative_names:
|
|
1901
2375
|
inds = indices[initial_der_name]
|
|
@@ -1909,8 +2383,12 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1909
2383
|
# Extract all other variables
|
|
1910
2384
|
variable_sizes = self.__variable_sizes
|
|
1911
2385
|
|
|
1912
|
-
for variable in itertools.chain(
|
|
1913
|
-
|
|
2386
|
+
for variable in itertools.chain(
|
|
2387
|
+
self.differentiated_states,
|
|
2388
|
+
self.algebraic_states,
|
|
2389
|
+
self.__path_variable_names,
|
|
2390
|
+
self.__extra_variable_names,
|
|
2391
|
+
):
|
|
1914
2392
|
if variable in results:
|
|
1915
2393
|
continue
|
|
1916
2394
|
|
|
@@ -1918,8 +2396,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1918
2396
|
variable_size = variable_sizes[variable]
|
|
1919
2397
|
|
|
1920
2398
|
if variable_size > 1:
|
|
1921
|
-
results[variable] = X[inds]
|
|
1922
|
-
.reshape((variable_size, -1)).transpose()
|
|
2399
|
+
results[variable] = X[inds].reshape((variable_size, -1)).transpose()
|
|
1923
2400
|
else:
|
|
1924
2401
|
results[variable] = X[inds]
|
|
1925
2402
|
|
|
@@ -1927,15 +2404,16 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1927
2404
|
|
|
1928
2405
|
# Extract constant input aliases
|
|
1929
2406
|
constant_inputs = self.constant_inputs(ensemble_member)
|
|
1930
|
-
for variable in self.dae_variables[
|
|
2407
|
+
for variable in self.dae_variables["constant_inputs"]:
|
|
1931
2408
|
variable = variable.name()
|
|
1932
2409
|
try:
|
|
1933
2410
|
constant_input = constant_inputs[variable]
|
|
1934
2411
|
except KeyError:
|
|
1935
2412
|
pass
|
|
1936
2413
|
else:
|
|
1937
|
-
results[variable] = np.interp(
|
|
1938
|
-
variable), constant_input.times, constant_input.values
|
|
2414
|
+
results[variable] = np.interp(
|
|
2415
|
+
self.times(variable), constant_input.times, constant_input.values
|
|
2416
|
+
)
|
|
1939
2417
|
|
|
1940
2418
|
return results
|
|
1941
2419
|
|
|
@@ -1951,9 +2429,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1951
2429
|
raise NotImplementedError("state_at() not supported for vector states")
|
|
1952
2430
|
|
|
1953
2431
|
name = "{}[{},{}]{}".format(
|
|
1954
|
-
variable, ensemble_member, t - self.initial_time,
|
|
2432
|
+
variable, ensemble_member, t - self.initial_time, "S" if scaled else ""
|
|
2433
|
+
)
|
|
1955
2434
|
if extrapolate:
|
|
1956
|
-
name +=
|
|
2435
|
+
name += "E"
|
|
1957
2436
|
try:
|
|
1958
2437
|
return self.__symbol_cache[name]
|
|
1959
2438
|
except KeyError:
|
|
@@ -1973,14 +2452,15 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
1973
2452
|
else:
|
|
1974
2453
|
times = self.times(canonical)
|
|
1975
2454
|
|
|
1976
|
-
if
|
|
2455
|
+
if self.integrate_states:
|
|
1977
2456
|
nominal = 1
|
|
1978
2457
|
if t == self.initial_time:
|
|
1979
2458
|
sym = sign * X[inds]
|
|
1980
2459
|
found = True
|
|
1981
2460
|
else:
|
|
1982
|
-
variable_values = ca.horzcat(
|
|
1983
|
-
canonical]
|
|
2461
|
+
variable_values = ca.horzcat(
|
|
2462
|
+
sign * X[inds], self.__integrators[ensemble_member][canonical]
|
|
2463
|
+
).T
|
|
1984
2464
|
else:
|
|
1985
2465
|
nominal = self.variable_nominal(canonical)
|
|
1986
2466
|
variable_values = X[inds]
|
|
@@ -2007,17 +2487,21 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2007
2487
|
history_timeseries.values,
|
|
2008
2488
|
f_left,
|
|
2009
2489
|
f_right,
|
|
2010
|
-
interpolation_method
|
|
2490
|
+
interpolation_method,
|
|
2491
|
+
)
|
|
2011
2492
|
if scaled and nominal != 1:
|
|
2012
2493
|
sym /= nominal
|
|
2013
2494
|
else:
|
|
2014
2495
|
if not extrapolate and (t < times[0] or t > times[-1]):
|
|
2015
|
-
raise Exception(
|
|
2016
|
-
|
|
2496
|
+
raise Exception(
|
|
2497
|
+
"Cannot interpolate for {}: "
|
|
2498
|
+
"Point {} outside of range [{}, {}]".format(
|
|
2499
|
+
canonical, t, times[0], times[-1]
|
|
2500
|
+
)
|
|
2501
|
+
)
|
|
2017
2502
|
|
|
2018
2503
|
interpolation_method = self.interpolation_method(canonical)
|
|
2019
|
-
sym = interpolate(
|
|
2020
|
-
times, variable_values, [t], False, interpolation_method)
|
|
2504
|
+
sym = interpolate(times, variable_values, [t], False, interpolation_method)
|
|
2021
2505
|
if not scaled and nominal != 1:
|
|
2022
2506
|
sym *= nominal
|
|
2023
2507
|
if sign < 0:
|
|
@@ -2039,7 +2523,13 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2039
2523
|
f_right = constant_input.values[-1]
|
|
2040
2524
|
interpolation_method = self.interpolation_method(variable)
|
|
2041
2525
|
sym = self.interpolate(
|
|
2042
|
-
t,
|
|
2526
|
+
t,
|
|
2527
|
+
constant_input.times,
|
|
2528
|
+
constant_input.values,
|
|
2529
|
+
f_left,
|
|
2530
|
+
f_right,
|
|
2531
|
+
interpolation_method,
|
|
2532
|
+
)
|
|
2043
2533
|
if not found:
|
|
2044
2534
|
parameters = self.parameters(ensemble_member)
|
|
2045
2535
|
try:
|
|
@@ -2081,7 +2571,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2081
2571
|
# Find canonical variable
|
|
2082
2572
|
canonical, sign = self.alias_relation.canonical_signed(variable)
|
|
2083
2573
|
nominal = self.variable_nominal(canonical)
|
|
2084
|
-
state =
|
|
2574
|
+
state = self.state_vector(canonical, ensemble_member)
|
|
2575
|
+
if self.integrate_states and canonical in self.__integrators[ensemble_member]:
|
|
2576
|
+
state = ca.vertcat(state, ca.transpose(self.__integrators[ensemble_member][canonical]))
|
|
2577
|
+
state *= nominal
|
|
2085
2578
|
if sign < 0:
|
|
2086
2579
|
state *= -1
|
|
2087
2580
|
|
|
@@ -2092,7 +2585,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2092
2585
|
history_timeseries = history[canonical]
|
|
2093
2586
|
except KeyError:
|
|
2094
2587
|
raise Exception(
|
|
2095
|
-
"No history found for variable {}, but a historical value was requested".format(
|
|
2588
|
+
"No history found for variable {}, but a historical value was requested".format(
|
|
2589
|
+
variable
|
|
2590
|
+
)
|
|
2591
|
+
)
|
|
2096
2592
|
else:
|
|
2097
2593
|
history_times = history_timeseries.times[:-1]
|
|
2098
2594
|
history = history_timeseries.values[:-1]
|
|
@@ -2103,9 +2599,8 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2103
2599
|
history = np.empty(0)
|
|
2104
2600
|
|
|
2105
2601
|
# Collect time stamps and states, "knots".
|
|
2106
|
-
indices, = np.where(np.logical_and(times >= t0, times <= tf))
|
|
2107
|
-
history_indices, = np.where(np.logical_and(
|
|
2108
|
-
history_times >= t0, history_times <= tf))
|
|
2602
|
+
(indices,) = np.where(np.logical_and(times >= t0, times <= tf))
|
|
2603
|
+
(history_indices,) = np.where(np.logical_and(history_times >= t0, history_times <= tf))
|
|
2109
2604
|
if (t0 not in times[indices]) and (t0 not in history_times[history_indices]):
|
|
2110
2605
|
x0 = self.state_at(variable, t0, ensemble_member)
|
|
2111
2606
|
else:
|
|
@@ -2115,8 +2610,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2115
2610
|
else:
|
|
2116
2611
|
tf = xf = ca.MX()
|
|
2117
2612
|
t = ca.vertcat(t0, history_times[history_indices], times[indices], tf)
|
|
2118
|
-
x = ca.vertcat(x0, history[history_indices],
|
|
2119
|
-
state[indices[0]:indices[-1] + 1], xf)
|
|
2613
|
+
x = ca.vertcat(x0, history[history_indices], state[indices[0] : indices[-1] + 1], xf)
|
|
2120
2614
|
|
|
2121
2615
|
return x, t
|
|
2122
2616
|
|
|
@@ -2130,8 +2624,8 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2130
2624
|
|
|
2131
2625
|
if x.size1() > 1:
|
|
2132
2626
|
# Integrate knots using trapezoid rule
|
|
2133
|
-
x_avg = 0.5 * (x[:x.size1() - 1] + x[1:])
|
|
2134
|
-
dt = t[1:] - t[:x.size1() - 1]
|
|
2627
|
+
x_avg = 0.5 * (x[: x.size1() - 1] + x[1:])
|
|
2628
|
+
dt = t[1:] - t[: x.size1() - 1]
|
|
2135
2629
|
return ca.sum1(x_avg * dt)
|
|
2136
2630
|
else:
|
|
2137
2631
|
return ca.MX(0)
|
|
@@ -2141,7 +2635,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2141
2635
|
canonical, sign = self.alias_relation.canonical_signed(variable)
|
|
2142
2636
|
try:
|
|
2143
2637
|
i = self.__differentiated_states_map[canonical]
|
|
2144
|
-
return sign * self.dae_variables[
|
|
2638
|
+
return sign * self.dae_variables["derivatives"][i]
|
|
2145
2639
|
except KeyError:
|
|
2146
2640
|
try:
|
|
2147
2641
|
i = self.__algebraic_states_map[canonical]
|
|
@@ -2193,8 +2687,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2193
2687
|
# Use finite differences when between collocation points, and
|
|
2194
2688
|
# backward finite differences when on one.
|
|
2195
2689
|
if t > history_and_times[i] and t <= history_and_times[i + 1]:
|
|
2196
|
-
dx =
|
|
2197
|
-
|
|
2690
|
+
dx = self.state_at(
|
|
2691
|
+
variable, history_and_times[i + 1], ensemble_member=ensemble_member
|
|
2692
|
+
) - self.state_at(variable, history_and_times[i], ensemble_member=ensemble_member)
|
|
2198
2693
|
dt = history_and_times[i + 1] - history_and_times[i]
|
|
2199
2694
|
return dx / dt
|
|
2200
2695
|
|
|
@@ -2202,19 +2697,14 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2202
2697
|
raise IndexError
|
|
2203
2698
|
|
|
2204
2699
|
def map_path_expression(self, expr, ensemble_member):
|
|
2205
|
-
|
|
2206
|
-
f = ca.Function('f', self.__func_orig_inputs, [expr]).expand()
|
|
2700
|
+
f = ca.Function("f", self.__func_orig_inputs, [expr]).expand()
|
|
2207
2701
|
initial_values = f(*self.__func_initial_inputs[ensemble_member])
|
|
2208
2702
|
|
|
2209
|
-
# Replace the original MX symbols with those that were mapped
|
|
2210
|
-
[expr_impl] = f.call(self.__func_inputs_implicit)
|
|
2211
|
-
f_impl = ca.Function('f_implicit', list(self.__func_accumulated_inputs), [expr_impl]).expand()
|
|
2212
|
-
|
|
2213
2703
|
# Map
|
|
2214
2704
|
number_of_timeslots = len(self.times())
|
|
2215
2705
|
if number_of_timeslots > 1:
|
|
2216
|
-
fmap =
|
|
2217
|
-
values = fmap(*self.
|
|
2706
|
+
fmap = f.map(number_of_timeslots - 1)
|
|
2707
|
+
values = fmap(*self.__func_map_args[ensemble_member])
|
|
2218
2708
|
|
|
2219
2709
|
all_values = ca.horzcat(initial_values, values)
|
|
2220
2710
|
else:
|
|
@@ -2228,12 +2718,12 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2228
2718
|
return super().solver_success(*args, **kwargs)
|
|
2229
2719
|
|
|
2230
2720
|
def _debug_get_named_nlp(self, nlp):
|
|
2231
|
-
x = nlp[
|
|
2232
|
-
f = nlp[
|
|
2233
|
-
g = nlp[
|
|
2721
|
+
x = nlp["x"]
|
|
2722
|
+
f = nlp["f"]
|
|
2723
|
+
g = nlp["g"]
|
|
2234
2724
|
|
|
2235
|
-
expand_f_g = ca.Function(
|
|
2236
|
-
x_sx = ca.SX.sym(
|
|
2725
|
+
expand_f_g = ca.Function("f_g", [x], [f, g]).expand()
|
|
2726
|
+
x_sx = ca.SX.sym("X", *x.shape)
|
|
2237
2727
|
f_sx, g_sx = expand_f_g(x_sx)
|
|
2238
2728
|
|
|
2239
2729
|
x, f, g = x_sx, f_sx, g_sx
|
|
@@ -2266,34 +2756,45 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2266
2756
|
if len(ensemble_members) == 1:
|
|
2267
2757
|
ensemble_members = ensemble_members[0]
|
|
2268
2758
|
else:
|
|
2269
|
-
ensemble_members = "[{}]".format(
|
|
2759
|
+
ensemble_members = "[{}]".format(
|
|
2760
|
+
",".join((str(x) for x in sorted(ensemble_members)))
|
|
2761
|
+
)
|
|
2270
2762
|
|
|
2271
|
-
var_names.append(
|
|
2763
|
+
var_names.append("{}__e{}__t{}".format(var_name, ensemble_members, t_i))
|
|
2272
2764
|
|
|
2273
2765
|
# Create named versions of the constraints
|
|
2274
2766
|
named_x = ca.vertcat(*(ca.SX.sym(v) for v in var_names))
|
|
2275
|
-
named_g = ca.vertsplit(ca.Function(
|
|
2276
|
-
named_f = ca.vertsplit(ca.Function(
|
|
2767
|
+
named_g = ca.vertsplit(ca.Function("tmp", [x], [g])(named_x))
|
|
2768
|
+
named_f = ca.vertsplit(ca.Function("tmp", [x], [f])(named_x))[0]
|
|
2277
2769
|
|
|
2278
2770
|
return var_names, named_x, named_f, named_g
|
|
2279
2771
|
|
|
2280
2772
|
@debug_check(DebugLevel.VERYHIGH)
|
|
2281
|
-
def __debug_check_transcribe_linear_coefficients(
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2773
|
+
def __debug_check_transcribe_linear_coefficients(
|
|
2774
|
+
self,
|
|
2775
|
+
discrete,
|
|
2776
|
+
lbx,
|
|
2777
|
+
ubx,
|
|
2778
|
+
lbg,
|
|
2779
|
+
ubg,
|
|
2780
|
+
x0,
|
|
2781
|
+
nlp,
|
|
2782
|
+
tol_rhs=1e6,
|
|
2783
|
+
tol_zero=1e-12,
|
|
2784
|
+
tol_up=1e2,
|
|
2785
|
+
tol_down=1e-2,
|
|
2786
|
+
tol_range=1e3,
|
|
2787
|
+
evaluate_at_x0=False,
|
|
2788
|
+
):
|
|
2288
2789
|
nlp = nlp.copy()
|
|
2289
2790
|
|
|
2290
|
-
expand_f_g = ca.Function(
|
|
2291
|
-
X_sx = ca.SX.sym(
|
|
2791
|
+
expand_f_g = ca.Function("f_g", [nlp["x"]], [nlp["f"], nlp["g"]]).expand()
|
|
2792
|
+
X_sx = ca.SX.sym("X", *nlp["x"].shape)
|
|
2292
2793
|
f_sx, g_sx = expand_f_g(X_sx)
|
|
2293
2794
|
|
|
2294
|
-
nlp[
|
|
2295
|
-
nlp[
|
|
2296
|
-
nlp[
|
|
2795
|
+
nlp["x"] = X_sx
|
|
2796
|
+
nlp["f"] = f_sx
|
|
2797
|
+
nlp["g"] = g_sx
|
|
2297
2798
|
|
|
2298
2799
|
lbg = np.array(ca.veccat(*lbg)).ravel()
|
|
2299
2800
|
ubg = np.array(ca.veccat(*ubg)).ravel()
|
|
@@ -2325,7 +2826,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2325
2826
|
if np.any(np.isfinite(lbg_abs_no_zero)):
|
|
2326
2827
|
logger.info("Smallest (absolute) lbg coefficient {}".format(lbg_abs_no_zero[ind]))
|
|
2327
2828
|
logger.info("E.g., {}".format(constr_to_str(ind)))
|
|
2328
|
-
lbg_inds =
|
|
2829
|
+
lbg_inds = lbg_abs_no_zero < tol_zero
|
|
2329
2830
|
if np.any(lbg_inds):
|
|
2330
2831
|
logger.info("Too small of a (absolute) lbg found: {}".format(min(lbg[lbg_inds])))
|
|
2331
2832
|
|
|
@@ -2335,7 +2836,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2335
2836
|
if np.any(np.isfinite(ubg_abs_no_zero)):
|
|
2336
2837
|
logger.info("Smallest (absolute) ubg coefficient {}".format(ubg_abs_no_zero[ind]))
|
|
2337
2838
|
logger.info("E.g., {}".format(constr_to_str(ind)))
|
|
2338
|
-
ubg_inds =
|
|
2839
|
+
ubg_inds = ubg_abs_no_zero < tol_zero
|
|
2339
2840
|
if np.any(ubg_inds):
|
|
2340
2841
|
logger.info("Too small of a (absolute) ubg found: {}".format(min(ubg[ubg_inds])))
|
|
2341
2842
|
|
|
@@ -2348,7 +2849,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2348
2849
|
logger.info("Largest (absolute) lbg coefficient {}".format(lbg_abs_no_inf[ind]))
|
|
2349
2850
|
logger.info("E.g., {}".format(constr_to_str(ind)))
|
|
2350
2851
|
|
|
2351
|
-
lbg_inds =
|
|
2852
|
+
lbg_inds = lbg_abs_no_inf > tol_rhs
|
|
2352
2853
|
if np.any(lbg_inds):
|
|
2353
2854
|
raise Exception("Too large of a (absolute) lbg found: {}".format(max(lbg[lbg_inds])))
|
|
2354
2855
|
|
|
@@ -2359,7 +2860,7 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2359
2860
|
logger.info("Largest (absolute) ubg coefficient {}".format(ubg_abs_no_inf[ind]))
|
|
2360
2861
|
logger.info("E.g., {}".format(constr_to_str(ind)))
|
|
2361
2862
|
|
|
2362
|
-
ubg_inds =
|
|
2863
|
+
ubg_inds = ubg_abs_no_inf > tol_rhs
|
|
2363
2864
|
if np.any(ubg_inds):
|
|
2364
2865
|
raise Exception("Too large of a (absolute) ubg found: {}".format(max(ubg[ubg_inds])))
|
|
2365
2866
|
|
|
@@ -2367,13 +2868,16 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2367
2868
|
eval_point_str = "x0" if evaluate_at_x0 else "1.0"
|
|
2368
2869
|
|
|
2369
2870
|
# Check coefficient matrix
|
|
2370
|
-
logger.info(
|
|
2871
|
+
logger.info(
|
|
2872
|
+
"Sanity check on objective and constraints Jacobian matrix"
|
|
2873
|
+
"/constant coefficients values"
|
|
2874
|
+
)
|
|
2371
2875
|
|
|
2372
|
-
in_var = nlp[
|
|
2876
|
+
in_var = nlp["x"]
|
|
2373
2877
|
out = []
|
|
2374
|
-
for o in [nlp[
|
|
2375
|
-
Af = ca.Function(
|
|
2376
|
-
bf = ca.Function(
|
|
2878
|
+
for o in [nlp["f"], nlp["g"]]:
|
|
2879
|
+
Af = ca.Function("Af", [in_var], [ca.jacobian(o, in_var)])
|
|
2880
|
+
bf = ca.Function("bf", [in_var], [o])
|
|
2377
2881
|
|
|
2378
2882
|
A = Af(eval_point)
|
|
2379
2883
|
A = ca.sparsify(A)
|
|
@@ -2385,28 +2889,39 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2385
2889
|
|
|
2386
2890
|
# Objective
|
|
2387
2891
|
A_obj, b_obj = out[0]
|
|
2388
|
-
logger.info(
|
|
2389
|
-
|
|
2892
|
+
logger.info(
|
|
2893
|
+
"Statistics of objective: max & min of abs(jac(f, {}))) f({}), constants".format(
|
|
2894
|
+
eval_point_str, eval_point_str
|
|
2895
|
+
)
|
|
2896
|
+
)
|
|
2390
2897
|
max_obj_A = max(np.abs(A_obj.data), default=None)
|
|
2391
2898
|
min_obj_A = min(np.abs(A_obj.data[A_obj.data != 0.0]), default=None)
|
|
2392
|
-
obj_x0 = np.array(ca.Function(
|
|
2899
|
+
obj_x0 = np.array(ca.Function("tmp", [in_var], [nlp["f"]])(eval_point)).ravel()[0]
|
|
2393
2900
|
obj_b = b_obj.data[0] if len(b_obj.data) > 0 else 0.0
|
|
2394
2901
|
|
|
2395
2902
|
logger.info("{} & {}, {}, {}".format(max_obj_A, min_obj_A, obj_x0, obj_b))
|
|
2396
2903
|
|
|
2397
2904
|
if abs(obj_b) > tol_up:
|
|
2398
|
-
logger.info(
|
|
2905
|
+
logger.info(
|
|
2906
|
+
"Constant '{}' in objective exceeds upper tolerance of '{}'".format(obj_b, tol_up)
|
|
2907
|
+
)
|
|
2399
2908
|
if abs(obj_b) > tol_up:
|
|
2400
|
-
logger.info(
|
|
2909
|
+
logger.info(
|
|
2910
|
+
"Objective value at x0 '{}' exceeds upper tolerance of '{}'".format(obj_x0, tol_up)
|
|
2911
|
+
)
|
|
2401
2912
|
|
|
2402
2913
|
# Constraints
|
|
2403
2914
|
A_constr, b_constr = out[1]
|
|
2404
|
-
logger.info(
|
|
2915
|
+
logger.info(
|
|
2916
|
+
"Statistics of constraints: max & min of abs(jac(g, x0))), max & min of abs(g(x0))"
|
|
2917
|
+
)
|
|
2405
2918
|
max_constr_A = max(np.abs(A_constr.data), default=None)
|
|
2406
2919
|
min_constr_A = min(np.abs(A_constr.data[A_constr.data != 0.0]), default=None)
|
|
2407
2920
|
max_constr_b = max(np.abs(b_constr.data), default=None)
|
|
2408
2921
|
min_constr_b = min(np.abs(b_constr.data[b_constr.data != 0.0]), default=None)
|
|
2409
|
-
logger.info(
|
|
2922
|
+
logger.info(
|
|
2923
|
+
"{} & {}, {} & {}".format(max_constr_A, min_constr_A, max_constr_b, min_constr_b)
|
|
2924
|
+
)
|
|
2410
2925
|
|
|
2411
2926
|
maxs = [x for x in [max_constr_A, max_constr_b, max_obj_A, obj_b] if x is not None]
|
|
2412
2927
|
mins = [x for x in [min_constr_A, min_constr_b, min_obj_A, obj_b] if x is not None]
|
|
@@ -2436,17 +2951,28 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2436
2951
|
exceedences.append((i, max_r, min_r, c_str))
|
|
2437
2952
|
|
|
2438
2953
|
if exceedences:
|
|
2439
|
-
logger.info(
|
|
2440
|
-
|
|
2441
|
-
|
|
2954
|
+
logger.info(
|
|
2955
|
+
"Exceedence in jacobian of constraints evaluated at x0"
|
|
2956
|
+
" (max > {:g}, min < {:g}, or max / min > {:g}):".format(
|
|
2957
|
+
tol_up, tol_down, tol_range
|
|
2958
|
+
)
|
|
2959
|
+
)
|
|
2442
2960
|
|
|
2443
2961
|
exceedences = sorted(exceedences, key=lambda x: x[1] / x[2], reverse=True)
|
|
2444
2962
|
|
|
2445
2963
|
for i, (r, max_r, min_r, c) in enumerate(exceedences):
|
|
2446
|
-
logger.info(
|
|
2964
|
+
logger.info(
|
|
2965
|
+
"row {} (max: {}, min: {}, range: {}): {}".format(
|
|
2966
|
+
r, max_r, min_r, max_r / min_r, c
|
|
2967
|
+
)
|
|
2968
|
+
)
|
|
2447
2969
|
|
|
2448
2970
|
if i >= 9:
|
|
2449
|
-
logger.info(
|
|
2971
|
+
logger.info(
|
|
2972
|
+
"Too many warnings of same type ({} others remain).".format(
|
|
2973
|
+
len(exceedences) - 10
|
|
2974
|
+
)
|
|
2975
|
+
)
|
|
2450
2976
|
break
|
|
2451
2977
|
|
|
2452
2978
|
# Columns
|
|
@@ -2456,7 +2982,9 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2456
2982
|
|
|
2457
2983
|
max_range_found = 1.0
|
|
2458
2984
|
|
|
2459
|
-
logger.info(
|
|
2985
|
+
logger.info(
|
|
2986
|
+
"Checking for range exceedence for each variable (i.e., check Jacobian matrix columns)"
|
|
2987
|
+
)
|
|
2460
2988
|
exceedences = []
|
|
2461
2989
|
|
|
2462
2990
|
for c in range(A_constr_csc.shape[1]):
|
|
@@ -2501,12 +3029,20 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2501
3029
|
logger.info("Exceedence in range per column (max / min > {:g}):".format(tol_range))
|
|
2502
3030
|
|
|
2503
3031
|
for i, (c, exc, min_, max_, c_min_str, c_max_str) in enumerate(exceedences):
|
|
2504
|
-
logger.info(
|
|
3032
|
+
logger.info(
|
|
3033
|
+
"col {} ({}): range {}, min {}, max {}".format(
|
|
3034
|
+
c, var_names[c], exc, min_, max_
|
|
3035
|
+
)
|
|
3036
|
+
)
|
|
2505
3037
|
logger.info(c_min_str)
|
|
2506
3038
|
logger.info(c_max_str)
|
|
2507
3039
|
|
|
2508
3040
|
if i >= 9:
|
|
2509
|
-
logger.info(
|
|
3041
|
+
logger.info(
|
|
3042
|
+
"Too many warnings of same type ({} others remain).".format(
|
|
3043
|
+
len(exceedences) - 10
|
|
3044
|
+
)
|
|
3045
|
+
)
|
|
2510
3046
|
break
|
|
2511
3047
|
|
|
2512
3048
|
logger.info("Checking for range exceedence for variables in the objective function")
|
|
@@ -2533,19 +3069,27 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2533
3069
|
|
|
2534
3070
|
logger.info("Max range found: {}".format(max_range_found))
|
|
2535
3071
|
if exceedences:
|
|
2536
|
-
logger.info(
|
|
2537
|
-
|
|
3072
|
+
logger.info(
|
|
3073
|
+
"Exceedence in range of objective variable (range > {:g}):".format(tol_range)
|
|
3074
|
+
)
|
|
2538
3075
|
|
|
2539
3076
|
for i, (c, max_range, obj_coeff, min_r, max_r) in enumerate(exceedences):
|
|
2540
|
-
logger.info(
|
|
2541
|
-
|
|
3077
|
+
logger.info(
|
|
3078
|
+
"col {} ({}): range: {}, obj: {}, min constr: {}, max constr {}".format(
|
|
3079
|
+
c, var_names[c], max_range, obj_coeff, min_r, max_r
|
|
3080
|
+
)
|
|
3081
|
+
)
|
|
2542
3082
|
|
|
2543
3083
|
if i >= 9:
|
|
2544
|
-
logger.info(
|
|
3084
|
+
logger.info(
|
|
3085
|
+
"Too many warnings of same type ({} others remain).".format(
|
|
3086
|
+
len(exceedences) - 10
|
|
3087
|
+
)
|
|
3088
|
+
)
|
|
2545
3089
|
break
|
|
2546
3090
|
|
|
2547
3091
|
@debug_check(DebugLevel.VERYHIGH)
|
|
2548
|
-
def __debug_check_state_output_scaling(self, tol_up=
|
|
3092
|
+
def __debug_check_state_output_scaling(self, tol_up=1e4, tol_down=1e-2, ignore_all_zero=True):
|
|
2549
3093
|
"""
|
|
2550
3094
|
Check the scaling using the resulting/optimized solver output.
|
|
2551
3095
|
|
|
@@ -2582,8 +3126,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2582
3126
|
exceedences = sorted(exceedences, key=lambda x: x[1], reverse=True)
|
|
2583
3127
|
|
|
2584
3128
|
if exceedences:
|
|
2585
|
-
logger.info(
|
|
2586
|
-
|
|
3129
|
+
logger.info(
|
|
3130
|
+
"Variables with at least one (absolute) state vector entry/entries "
|
|
3131
|
+
"larger than {}".format(tol_up)
|
|
3132
|
+
)
|
|
2587
3133
|
|
|
2588
3134
|
for k, v in exceedences:
|
|
2589
3135
|
logger.info("{}: abs max = {}".format(k, v))
|
|
@@ -2599,8 +3145,10 @@ class CollocatedIntegratedOptimizationProblem(OptimizationProblem, metaclass=ABC
|
|
|
2599
3145
|
|
|
2600
3146
|
if next((v for k, v in exceedences if not ignore_all_zero or v > 0.0), None):
|
|
2601
3147
|
ignore_all_zero_string = " (but not all zero)" if ignore_all_zero else ""
|
|
2602
|
-
logger.info(
|
|
2603
|
-
|
|
3148
|
+
logger.info(
|
|
3149
|
+
"Variables with all (absolute) state vector entry/entries "
|
|
3150
|
+
"smaller than {}{}".format(tol_down, ignore_all_zero_string)
|
|
3151
|
+
)
|
|
2604
3152
|
|
|
2605
3153
|
for k, v in exceedences:
|
|
2606
3154
|
if ignore_all_zero and v == 0.0:
|