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.

Files changed (47) hide show
  1. {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/METADATA +7 -7
  2. rtc_tools-2.6.0.dist-info/RECORD +50 -0
  3. {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/WHEEL +1 -1
  4. rtctools/__init__.py +2 -1
  5. rtctools/_internal/alias_tools.py +12 -10
  6. rtctools/_internal/caching.py +5 -3
  7. rtctools/_internal/casadi_helpers.py +11 -32
  8. rtctools/_internal/debug_check_helpers.py +1 -1
  9. rtctools/_version.py +3 -3
  10. rtctools/data/__init__.py +2 -2
  11. rtctools/data/csv.py +54 -33
  12. rtctools/data/interpolation/bspline.py +3 -3
  13. rtctools/data/interpolation/bspline1d.py +42 -29
  14. rtctools/data/interpolation/bspline2d.py +10 -4
  15. rtctools/data/netcdf.py +137 -93
  16. rtctools/data/pi.py +304 -210
  17. rtctools/data/rtc.py +64 -53
  18. rtctools/data/storage.py +91 -51
  19. rtctools/optimization/collocated_integrated_optimization_problem.py +1244 -696
  20. rtctools/optimization/control_tree_mixin.py +68 -66
  21. rtctools/optimization/csv_lookup_table_mixin.py +107 -74
  22. rtctools/optimization/csv_mixin.py +83 -52
  23. rtctools/optimization/goal_programming_mixin.py +237 -146
  24. rtctools/optimization/goal_programming_mixin_base.py +204 -111
  25. rtctools/optimization/homotopy_mixin.py +36 -27
  26. rtctools/optimization/initial_state_estimation_mixin.py +8 -8
  27. rtctools/optimization/io_mixin.py +48 -43
  28. rtctools/optimization/linearization_mixin.py +3 -1
  29. rtctools/optimization/linearized_order_goal_programming_mixin.py +57 -28
  30. rtctools/optimization/min_abs_goal_programming_mixin.py +72 -29
  31. rtctools/optimization/modelica_mixin.py +135 -81
  32. rtctools/optimization/netcdf_mixin.py +32 -18
  33. rtctools/optimization/optimization_problem.py +181 -127
  34. rtctools/optimization/pi_mixin.py +68 -36
  35. rtctools/optimization/planning_mixin.py +19 -0
  36. rtctools/optimization/single_pass_goal_programming_mixin.py +159 -112
  37. rtctools/optimization/timeseries.py +4 -6
  38. rtctools/rtctoolsapp.py +18 -18
  39. rtctools/simulation/csv_mixin.py +37 -30
  40. rtctools/simulation/io_mixin.py +9 -5
  41. rtctools/simulation/pi_mixin.py +62 -32
  42. rtctools/simulation/simulation_problem.py +471 -180
  43. rtctools/util.py +84 -56
  44. rtc_tools-2.5.2rc4.dist-info/RECORD +0 -49
  45. {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/COPYING.LESSER +0 -0
  46. {rtc_tools-2.5.2rc4.dist-info → rtc_tools-2.6.0.dist-info}/entry_points.txt +0 -0
  47. {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, is_affine, nullvertcat, reduce_matvec, substitute_in_external
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['free_variables'] = self.dae_variables[
48
- 'states'] + self.dae_variables['algebraics'] + self.dae_variables['control_inputs']
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 = [variable.name() for variable in self.dae_variables['states']]
52
- self.__differentiated_states_map = {v: i for i, v in enumerate(self.__differentiated_states)}
53
-
54
- self.__algebraic_states = [variable.name()
55
- for variable in self.dae_variables['algebraics']]
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 = [variable.name() for variable in self.dae_variables['derivatives']]
77
+ self.__derivative_names = [
78
+ variable.name() for variable in self.dae_variables["derivatives"]
79
+ ]
63
80
 
64
- self.__initial_derivative_names = ["initial_" + variable for variable in self.__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
- self.dae_variables['states'],
77
- self.dae_variables['algebraics'],
78
- self.dae_variables['control_inputs'],
79
- self.dae_variables['constant_inputs'],
80
- self.dae_variables['parameters'],
81
- self.dae_variables['time']):
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 integrated_states(self):
129
+ def integrate_states(self):
110
130
  """
111
- A list of states that are integrated rather than collocated.
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}) + (1 - \theta) f(x_i, u_i)\right]
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. Note that in this
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 algebraic parts.
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
- 'Transcribing problem with a DAE of {} equations, {} collocation points, and {} free variables'.format(
168
- dae_residual.size1(), len(self.times()), len(self.dae_variables['free_variables'])))
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['constant_inputs']]
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
- [(x, v.values.shape[1] if v.values.ndim > 1 else 1)
181
- for x, v in self.constant_inputs(ensemble_member).items()
182
- if x not in dae_constant_inputs_names])
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
- for variable in self.path_variables]
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(self.differentiated_states, self.algebraic_states,
201
- self.controls, self.__initial_derivative_names):
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(self.__differentiated_states, self.__initial_derivative_names):
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
- self.variable_nominal(variable))
292
+ self.__initial_derivative_nominals[initial_der_name] = self.variable_nominal(
293
+ variable
294
+ )
235
295
 
236
- if self.integrated_states:
237
- warnings.warn("Integrated states are deprecated and support will be removed in a future version.",
238
- FutureWarning, stacklevel=1)
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
- for variable in self.integrated_states:
242
- if self.__variable_sizes.get(variable, 1) > 1:
243
- raise NotImplementedError("Vector symbol not supported for integrated state '{}'".format(variable))
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("Vector symbol not supported for control state '{}'".format(variable))
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['parameters'])
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, constant_input.times, values, 0.0, 0.0, interpolation_method)
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['parameters'])
287
- for i, symbol in enumerate(self.dae_variables['parameters']):
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['parameters']):
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([isinstance(value, ca.MX) and not value.is_constant() for value in parameter_values]):
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['parameters'], parameter_values)
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['constant_inputs'], raw_constant_inputs)
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
- control_size, discrete_control, lbx_control, ubx_control, x0_control, indices_control = \
331
- self.discretize_controls(bounds)
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
- state_size, discrete_state, lbx_state, ubx_state, x0_state, indices_state = \
335
- self.discretize_states(bounds)
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('X', control_size + state_size)
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
- integrated_variables = []
391
- collocated_variables = []
392
- for variable in itertools.chain(self.dae_variables['states'], self.dae_variables['algebraics']):
393
- if variable.name() in self.integrated_states:
394
- integrated_variables.append(variable)
395
- else:
396
- collocated_variables.append(variable)
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
- repr(integrated_variables)))
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([self.variable_nominal(v) for v in integrated_variable_names])
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([self.variable_nominal(v) for v in collocated_variable_names])
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
- integrated_derivatives = []
415
- collocated_derivatives = []
416
- for k, var in enumerate(self.dae_variables['states']):
417
- if var.name() in self.integrated_states:
418
- integrated_derivatives.append(
419
- self.dae_variables['derivatives'][k])
420
- else:
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 itertools.chain(self.dae_variables['algebraics'], self.dae_variables['control_inputs']):
425
- sym = ca.MX.sym('der({})'.format(var.name()))
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
- zip(*delayed_feedback)
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("Explicit collocation is deprecated and will be removed in a future version.",
454
- FutureWarning, stacklevel=1)
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 = {'max_num_dir': options['optimized_num_dir']}
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(self.__indices[ensemble_member][initial_der_name])
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 is because:
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 symbolics)
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
- X[init_der_variable_indices] * np.array(init_der_variable_nominals))
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(self.__indices_as_lists[ensemble_member][variable][0::step])
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(variable)
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"] = X[initial_path_variable_inds] * path_variables_nominals
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['parameters']):
543
- values = [ensemble_store[ensemble_member]["parameters"][i] for ensemble_member in range(self.ensemble_size)]
544
- if ((len(values) == 1 or (np.all(values) == values[0]))
545
- and parameter.name() not in dynamic_parameter_names):
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
- constant_parameters,
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
- constant_parameters,
565
- constant_parameter_values)
691
+ delayed_feedback_durations, constant_parameters, constant_parameter_values
692
+ )
566
693
 
567
- path_objective, path_constraint_expressions = \
568
- ca.substitute(
569
- [path_objective, path_constraint_expressions],
570
- constant_parameters,
571
- constant_parameter_values)
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(*[nullvertcat(*p) for p in ensemble_parameter_values])
579
- ensemble_aggregate["initial_constant_inputs"] = ca.horzcat(*[
580
- nullvertcat(*[
581
- float(d["constant_inputs"][variable.name()][0])
582
- for variable in self.dae_variables['constant_inputs']])
583
- for d in ensemble_store])
584
- ensemble_aggregate["initial_extra_constant_inputs"] = ca.horzcat(*[
585
- nullvertcat(*[
586
- d["extra_constant_inputs"][variable.name()][0, :]
587
- for variable in self.__extra_constant_inputs])
588
- for d in ensemble_store])
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 (self.__integrator_step_function is None):
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['lookup_tables']:
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 = [self.variable(input_sym.name())
616
- for input_sym in lookup_table.inputs]
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['lookup_tables']) > 0 and self.ensemble_size > 1:
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
- ca.substitute(
629
- [dae_residual, initial_residual],
630
- constant_parameters,
631
- constant_parameter_values)
632
-
633
- # Split DAE into integrated and into a collocated part
634
- dae_residual_integrated = []
635
- dae_residual_collocated = []
636
-
637
- dae_outputs = ca.vertsplit(dae_residual)
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(*self.dae_variables['time'],
664
- *self.dae_variables['constant_inputs'],
665
- ca.MX(symbolic_parameters))
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(ca.substitute(dae_residual_collocated, fixed_vars, fixed_var_values),
669
- ca.vertcat(* collocated_variables + integrated_variables +
670
- collocated_derivatives + integrated_derivatives)):
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
- 'The DAE residual contains equations that are not affine. '
675
- 'There is therefore no guarantee that the optimization problem is convex. '
676
- 'This will, in general, result in the existence of multiple local optima '
677
- 'and trouble finding a feasible initial solution.')
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 len(integrated_variables) > 0:
681
- I = ca.MX.sym('I', len(integrated_variables)) # noqa: E741
682
- I0 = ca.MX.sym('I0', len(integrated_variables))
683
- C0 = [ca.MX.sym('C0[{}]'.format(i))
684
- for i in range(len(collocated_variables))]
685
- CI0 = [ca.MX.sym('CI0[{}]'.format(i))
686
- for i in range(len(self.dae_variables['constant_inputs']))]
687
- dt_sym = ca.MX.sym('dt')
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
- (integrated_variables +
694
- collocated_variables +
695
- integrated_derivatives +
696
- self.dae_variables['constant_inputs'] +
697
- self.dae_variables['time']),
698
- ([I0[i] for i in range(len(integrated_variables))] +
699
- [C0[i] for i in range(len(collocated_variables))] +
700
- [integrated_finite_differences[i] for i in range(len(integrated_derivatives))] +
701
- [CI0[i] for i in range(len(self.dae_variables['constant_inputs']))] +
702
- [self.dae_variables['time'][0] - dt_sym]))
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
- integrated_derivatives),
707
- ([I[i] for i in range(len(integrated_variables))] +
708
- [integrated_finite_differences[i] for i in range(len(integrated_derivatives))]))
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) * dae_residual_integrated_0 + theta * dae_residual_integrated_1
873
+ 1 - theta
874
+ ) * dae_residual_integrated_0 + theta * dae_residual_integrated_1
717
875
 
718
876
  dae_residual_function_integrated = ca.Function(
719
- 'dae_residual_function_integrated',
720
- [I,
721
- I0,
722
- symbolic_parameters,
723
- ca.vertcat(*(
724
- [C0[i] for i in range(len(collocated_variables))] +
725
- [CI0[i] for i in range(len(self.dae_variables['constant_inputs']))] +
726
- [dt_sym] +
727
- collocated_variables +
728
- collocated_derivatives +
729
- self.dae_variables['constant_inputs'] +
730
- self.dae_variables['time']))],
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
- 'integrator_step_function', 'newton', dae_residual_function_integrated, options)
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
- if len(collocated_variables) > 0:
919
+ elif len(collocated_variables) > 0:
750
920
  self.__dae_residual_function_collocated = ca.Function(
751
- 'dae_residual_function_collocated',
752
- [symbolic_parameters,
753
- ca.vertcat(*(
754
- integrated_variables +
755
- collocated_variables +
756
- integrated_derivatives +
757
- collocated_derivatives +
758
- self.dae_variables['constant_inputs'] +
759
- self.dae_variables['time']))],
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 = self.__dae_residual_function_collocated.expand()
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 len(integrated_variables) > 0:
947
+ if self.integrate_states:
772
948
  integrator_step_function = self.__integrator_step_function
773
- if len(collocated_variables) > 0:
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, *collocated_variables, *integrated_derivatives,
786
- *collocated_derivatives, *self.dae_variables['constant_inputs'],
787
- *self.dae_variables['time'], *self.path_variables,
788
- *self.__extra_constant_inputs),
789
- symbolic_extra_variables]
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 members
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
- 'path_objective',
795
- self.__func_orig_inputs,
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 members
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
- 'path_constraints',
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
- 'delayed_feedback',
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 len(integrated_variables) > 0:
820
- accumulated_X = ca.MX.sym('accumulated_X', len(integrated_variables))
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('accumulated_X', 0)
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
- 'accumulated_U',
829
- (2 * (len(collocated_variables) +
830
- len(self.dae_variables['constant_inputs']) + 1) +
831
- path_variables_size +
832
- extra_constant_inputs_size))
833
-
834
- integrated_states_0 = accumulated_X[0:len(integrated_variables)]
835
- integrated_states_1 = ca.MX.sym(
836
- 'integrated_states_1', len(integrated_variables))
837
- collocated_states_0 = accumulated_U[0:len(collocated_variables)]
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
- constant_inputs_0 = accumulated_U[2 * len(collocated_variables):2 * len(
841
- collocated_variables) + len(self.dae_variables['constant_inputs'])]
842
- constant_inputs_1 = accumulated_U[2 * len(collocated_variables) + len(self.dae_variables[
843
- 'constant_inputs']):2 * len(collocated_variables) + 2 * len(self.dae_variables['constant_inputs'])]
844
-
845
- offset = 2 * (len(collocated_variables) + len(self.dae_variables['constant_inputs']))
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
- collocated_finite_differences = (
854
- collocated_states_1 - collocated_states_0) / dt
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 len(integrated_variables) > 0:
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
- [integrated_states_0,
868
- integrated_states_0,
869
- symbolic_parameters,
870
- ca.vertcat(
871
- collocated_states_0,
872
- constant_inputs_0,
873
- dt,
874
- collocated_states_1,
875
- collocated_finite_differences,
876
- constant_inputs_1,
877
- collocation_time_1 - t0)],
878
- False, True)
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, for use in the collocation part below
882
- # We don't use substititute() for this, as it becomes expensive
883
- # over long integration horizons.
884
- if len(collocated_variables) > 0:
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
- if len(collocated_variables) > 0:
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
- [symbolic_parameters,
898
- ca.vertcat(
899
- integrated_states_0,
900
- collocated_states_0,
901
- integrated_finite_differences,
902
- collocated_finite_differences,
903
- constant_inputs_0,
904
- collocation_time_0 - t0)],
905
- False, True)
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
- [symbolic_parameters,
910
- ca.vertcat(
911
- integrated_states_1,
912
- collocated_states_1,
913
- integrated_finite_differences,
914
- collocated_finite_differences,
915
- constant_inputs_1,
916
- collocation_time_1 - t0)],
917
- False, True)
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
- symbolic_extra_variables]
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(path_constraints_function.call(
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(delayed_feedback_function.call(
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, accumulated_U,
951
- ca.veccat(symbolic_parameters, symbolic_extra_variables))
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 len(integrated_variables) > 0:
1158
+ if self.integrate_states:
957
1159
  accumulated = ca.Function(
958
- 'accumulated',
1160
+ "accumulated",
959
1161
  self.__func_accumulated_inputs,
960
1162
  [accumulated_Y[0], ca.vertcat(*accumulated_Y[1:])],
961
- function_options)
962
- accumulation = accumulated.mapaccum('accumulation', n_collocation_times - 1)
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
- 'accumulated',
1170
+ "accumulated",
968
1171
  self.__func_accumulated_inputs,
969
1172
  [ca.vertcat(*accumulated_Y)],
970
- function_options)
971
- accumulation = accumulated.map(n_collocation_times - 1, 'openmp')
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
- 'initial_residual_total',
985
- [symbolic_parameters,
986
- ca.vertcat(*(
987
- self.dae_variables['states'] +
988
- self.dae_variables['algebraics'] +
989
- self.dae_variables['control_inputs'] +
990
- integrated_derivatives +
991
- collocated_derivatives +
992
- self.dae_variables['constant_inputs'] +
993
- self.dae_variables['time']))],
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
- [ensemble_aggregate["parameters"],
1001
- ca.vertcat(*[
1002
- ensemble_aggregate["initial_state"],
1003
- ensemble_aggregate["initial_derivatives"],
1004
- ensemble_aggregate["initial_constant_inputs"],
1005
- ca.repmat([0.0], 1, self.ensemble_size)])],
1006
- False, True)
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 define its own
1021
- # constraints and objectives. Path constraints are applied for all ensemble members simultaneously
1022
- # at the moment. We can get rid of map again, and allow every ensemble member to specify its own
1023
- # path constraints as well, once CasADi has some kind of loop detection.
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"][:, ensemble_member]
1031
- initial_constant_inputs = ensemble_aggregate["initial_constant_inputs"][:, ensemble_member]
1032
- initial_extra_constant_inputs = ensemble_aggregate["initial_extra_constant_inputs"][:, ensemble_member]
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(self.differentiated_states, self.algebraic_states, self.controls):
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, history_timeseries.times, history_timeseries.values, np.nan, np.nan, interpolation_method)
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("Initial value {} for variable '{}' outside bounds.".format(val, variable))
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(history_timeseries.values[-2]):
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]) / (t0 - history_timeseries.times[-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]) / (t0 - history_timeseries.times[-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
- for variable in self.integrated_states:
1108
- value = self.state_vector(
1109
- variable, ensemble_member=ensemble_member)[0]
1110
- nominal = self.variable_nominal(variable)
1111
- if nominal != 1:
1112
- value *= nominal
1113
- accumulation_X0.append(value)
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 + 2 * len(self.dae_variables['constant_inputs']) + 3
1124
- + len(self.__extra_constant_inputs))
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(np.repeat(collocated_variable_nominals, n_collocation_times - 1), 2)
1145
- interpolated_states = ca.vertcat(X[interpolated_states_explicit],
1146
- X[interpolated_states_implicit]) * repeated_nominals
1147
- interpolated_states = interpolated_states.reshape((n_collocation_times - 1, len(collocated_variables)*2))
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 member,
1169
- # and from goal programming/homotopy run to run.
1170
- # We could, of course, pick the states apart into controls and states,
1171
- # and generate Jacobians for each set separately and for each ensemble member separately, but
1172
- # in this case the increased complexity may well offset the performance gained by caching.
1173
- accumulation_U[0] = reduce_matvec(interpolated_states, self.solver_input)
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
- for j, variable in enumerate(self.dae_variables['constant_inputs']):
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
- 1 + j] = ca.MX(constant_input[0:n_collocation_times - 1])
1180
- accumulation_U[1 + len(self.dae_variables[
1181
- 'constant_inputs']) + j] = ca.MX(constant_input[1:n_collocation_times])
1182
-
1183
- accumulation_U[1 + 2 * len(self.dae_variables[
1184
- 'constant_inputs'])] = ca.MX(collocation_times[0:n_collocation_times - 1])
1185
- accumulation_U[1 + 2 * len(self.dae_variables[
1186
- 'constant_inputs']) + 1] = ca.MX(collocation_times[1:n_collocation_times])
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 = np.broadcast_to(nominal, (n_collocation_times, variable_size)).transpose().ravel()
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
- accumulation_U[1 + 2 * len(
1204
- self.dae_variables['constant_inputs']) + 2] = reduce_matvec(
1205
- ca.horzcat(*path_variables), self.solver_input)
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['constant_inputs']) + 3 + j] = \
1211
- ca.MX(constant_input[1:n_collocation_times, :])
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.__func_mapped_inputs.append(
1225
- (accumulation_X0, accumulation_U,
1226
- ca.repmat(ca.vertcat(parameters, extra_variables), 1, n_collocation_times - 1)))
1227
-
1228
- self.__func_initial_inputs.append([parameters, ca.vertcat(
1229
- initial_state, initial_derivatives, initial_constant_inputs, 0.0,
1230
- initial_path_variables, initial_extra_constant_inputs),
1231
- extra_variables])
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
- *self.__func_mapped_inputs[ensemble_member])
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 len(integrated_variables) > 0:
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 = integrators_and_collocation_and_path_constraints[1]
1521
+ integrators_and_collocation_and_path_constraints = (
1522
+ integrators_and_collocation_and_path_constraints[1]
1523
+ )
1242
1524
  if (
1243
- accumulation is not None and
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(integrators_and_collocation_and_path_constraints[
1247
- :dae_residual_collocated_size,
1248
- 0:n_collocation_times - 1])
1249
- discretized_path_objective = ca.vec(integrators_and_collocation_and_path_constraints[
1250
- dae_residual_collocated_size:dae_residual_collocated_size + path_objective.size1(),
1251
- 0:n_collocation_times - 1])
1252
- discretized_path_constraints = ca.vec(integrators_and_collocation_and_path_constraints[
1253
- dae_residual_collocated_size + path_objective.size1():dae_residual_collocated_size +
1254
- path_objective.size1() + path_constraint_expressions.size1(),
1255
- 0:n_collocation_times - 1])
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 + path_objective.size1() + path_constraint_expressions.size1():,
1258
- 0:n_collocation_times - 1]
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 len(integrated_variables) > 0:
1269
- self.integrators = {}
1270
- for i, variable in enumerate(integrated_variables):
1271
- self.integrators[variable.name()] = integrators[i, :]
1272
- self.integrators_mx = []
1273
- for j in range(integrators.size2()):
1274
- self.integrators_mx.append(integrators[:, j])
1275
-
1276
- # Add collocation constraints
1277
- if collocation_constraints.size1() > 0:
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((np.array([]), *[history_series.times for history_series in history.values()])))
1287
- # By convention, the last timestep in history series is the initial time. We drop this index
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((history_times.shape[0], len(integrated_variables) + len(collocated_variables)))
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((history_times.shape[0], len(self.dae_variables['constant_inputs'])))
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['constant_inputs']):
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((history_times.shape[0], len(delayed_feedback_expressions)))
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
- [parameters, ca.veccat(
1340
- ca.transpose(history_values[i, :]),
1341
- ca.transpose(history_derivatives[i, :]),
1342
- ca.transpose(constant_input_values[i, :]),
1343
- time,
1344
- ca.repmat(np.nan, len(self.path_variables)),
1345
- ca.repmat(np.nan, len(self.__extra_constant_inputs))),
1346
- ca.repmat(np.nan, len(self.extra_variables))])
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(variable)
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
- [parameters, ca.vertcat(
1362
- [self.variable_nominal(var.name()) for var in integrated_variables + collocated_variables],
1363
- np.zeros((initial_derivatives.size1(), 1)),
1364
- initial_constant_inputs,
1365
- 0.0,
1366
- path_variables_nominal,
1367
- initial_extra_constant_inputs), extra_variables])
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
- 'delay_values',
1385
- self.dae_variables['time'] + self.dae_variables['constant_inputs'],
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['constant_inputs']])
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
- self.state_vector(
1406
- in_canonical, ensemble_member=ensemble_member)
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(np.isfinite(delay)), (
1414
- 'Delay duration must be resolvable to real values at transcribe()')
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(np.isnan(delayed_feedback_history[hist_start_ind:, i])):
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
- 'Incomplete history for delayed expression {}. '
1433
- 'Extrapolating t0 value backwards in time.'.format(
1434
- expression))
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
- in_canonical)
1442
- x_in = interpolate(in_times, in_values,
1443
- collocation_times, False, interpolation_method)
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, out_values, collocation_times - delay, False, interpolation_method)
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
- f_member += initial_path_objective[0] + \
1465
- ca.sum1(discretized_path_objective)
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("The `constraints` method returned None, but should always return a list.")
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(zip(g_constraint, lbg_constraint, ubg_constraint)):
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("Shape mismatch between constraint #{} ({},) and its lower bound ({},)"
1497
- .format(i, g_i.shape[0], lbg_i.shape[0]))
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("Shape mismatch between constraint #{} ({},) and its upper bound ({},)"
1503
- .format(i, g_i.shape[0], ubg_i.shape[0]))
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).transpose()
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).transpose()
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 = {'x': X, 'f': ca.sum1(ca.vertcat(*f)), 'g': ca.vertcat(*g)}
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['solver']
1611
- assert solver in ['bonmin', 'ipopt']
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]['jac_c_constant'] = 'yes' if self.linear_collocation else 'no'
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 for details.
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.integrated_states)
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 = np.broadcast_to(nominal, (n_times, variable_size)).transpose().ravel()
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).ravel()
2089
+ interpolation_method,
2090
+ ).ravel()
1671
2091
  elif isinstance(bound[0], np.ndarray):
1672
- lower_bound = np.broadcast_to(bound[0], (n_times, variable_size)).transpose().ravel()
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).ravel()
2109
+ interpolation_method,
2110
+ ).ravel()
1686
2111
  elif isinstance(bound[1], np.ndarray):
1687
- upper_bound = np.broadcast_to(bound[1], (n_times, variable_size)).transpose().ravel()
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('Lower bound on variable {} contains NaN'.format(variable))
2123
+ logger.error("Lower bound on variable {} contains NaN".format(variable))
1695
2124
  if np.any(np.isnan(ubx[inds])):
1696
- logger.error('Upper bound on variable {} contains NaN'.format(variable))
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.integrated_states)
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 = np.broadcast_to(nominal, (n_times, variable_size)).transpose().ravel()
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] = self.interpolate(
1729
- times,
1730
- seed_k.times,
1731
- seed_k.values,
1732
- 0,
1733
- 0,
1734
- interpolation_method).transpose().ravel()
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 discretize_controls(self, bounds):
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
- count = 0
1756
- for variable in self.controls:
1757
- times = self.times(variable)
1758
- n_times = len(times)
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
- count += n_times
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
- offset = 0
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
- indices[ensemble_member][variable] = slice(offset, offset + n_times)
1771
-
1772
- offset += n_times
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
- for variable in itertools.chain(self.differentiated_states, self.algebraic_states, self.__path_variable_names):
1817
- if variable in self.integrated_states:
1818
- ensemble_member_size += 1 # Initial state
1819
- else:
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['derivatives'])
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 variable in self.integrated_states:
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(offset, offset + n_times * variable_size)
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 len(self.integrated_states) > 0:
1885
- # Use integrators_mx to facilitate common subexpression
2347
+ if self.integrate_states:
2348
+ # Use integrators to facilitate common subexpression
1886
2349
  # elimination.
1887
- f = ca.Function('f', [self.solver_input], [
1888
- ca.vertcat(*self.integrators_mx)])
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.integrated_states:
1892
- n = self.integrators[variable].size1()
1893
- results[variable] = self.variable_nominal(
1894
- variable) * np.array(integrators_output[j:j + n, 0]).ravel()
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(self.differentiated_states, self.algebraic_states,
1913
- self.__path_variable_names, self.__extra_variable_names):
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['constant_inputs']:
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(self.times(
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, 'S' if scaled else '')
2432
+ variable, ensemble_member, t - self.initial_time, "S" if scaled else ""
2433
+ )
1955
2434
  if extrapolate:
1956
- name += 'E'
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 canonical in self.integrated_states:
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(sign * X[inds], self.integrators[
1983
- canonical]).T
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("Cannot interpolate for {}: Point {} outside of range [{}, {}]".format(
2016
- canonical, t, times[0], times[-1]))
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, constant_input.times, constant_input.values, f_left, f_right, interpolation_method)
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 = nominal * self.state_vector(canonical, ensemble_member)
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(variable))
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['derivatives'][i]
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 = (self.state_at(variable, history_and_times[i + 1], ensemble_member=ensemble_member) -
2197
- self.state_at(variable, history_and_times[i], ensemble_member=ensemble_member))
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 = f_impl.map(number_of_timeslots - 1)
2217
- values = fmap(*self.__func_mapped_inputs[ensemble_member])
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['x']
2232
- f = nlp['f']
2233
- g = nlp['g']
2721
+ x = nlp["x"]
2722
+ f = nlp["f"]
2723
+ g = nlp["g"]
2234
2724
 
2235
- expand_f_g = ca.Function('f_g', [x], [f, g]).expand()
2236
- x_sx = ca.SX.sym('X', *x.shape)
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(",".join((str(x) for x in sorted(ensemble_members))))
2759
+ ensemble_members = "[{}]".format(
2760
+ ",".join((str(x) for x in sorted(ensemble_members)))
2761
+ )
2270
2762
 
2271
- var_names.append('{}__e{}__t{}'.format(var_name, ensemble_members, t_i))
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('tmp', [x], [g])(named_x))
2276
- named_f = ca.vertsplit(ca.Function('tmp', [x], [f])(named_x))[0]
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(self, discrete, lbx, ubx, lbg, ubg, x0, nlp,
2282
- tol_rhs=1E6,
2283
- tol_zero=1E-12,
2284
- tol_up=1E2,
2285
- tol_down=1E-2,
2286
- tol_range=1E3,
2287
- evaluate_at_x0=False):
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('f_g', [nlp['x']], [nlp['f'], nlp['g']]).expand()
2291
- X_sx = ca.SX.sym('X', *nlp['x'].shape)
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['x'] = X_sx
2295
- nlp['f'] = f_sx
2296
- nlp['g'] = g_sx
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 = (lbg_abs_no_zero < tol_zero)
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 = (ubg_abs_no_zero < tol_zero)
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 = (lbg_abs_no_inf > tol_rhs)
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 = (ubg_abs_no_inf > tol_rhs)
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("Sanity check on objective and constraints Jacobian matrix /constant coefficients values")
2871
+ logger.info(
2872
+ "Sanity check on objective and constraints Jacobian matrix"
2873
+ "/constant coefficients values"
2874
+ )
2371
2875
 
2372
- in_var = nlp['x']
2876
+ in_var = nlp["x"]
2373
2877
  out = []
2374
- for o in [nlp['f'], nlp['g']]:
2375
- Af = ca.Function('Af', [in_var], [ca.jacobian(o, in_var)])
2376
- bf = ca.Function('bf', [in_var], [o])
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("Statistics of objective: max & min of abs(jac(f, {}))) f({}), constants"
2389
- .format(eval_point_str, eval_point_str))
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('tmp', [in_var], [nlp['f']])(eval_point)).ravel()[0]
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("Constant '{}' in objective exceeds upper tolerance of '{}'".format(obj_b, tol_up))
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("Objective value at x0 '{}' exceeds upper tolerance of '{}'".format(obj_x0, tol_up))
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("Statistics of constraints: max & min of abs(jac(g, x0))), max & min of abs(g(x0))")
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("{} & {}, {} & {}".format(max_constr_A, min_constr_A, max_constr_b, min_constr_b))
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("Exceedence in jacobian of constraints evaluated at x0"
2440
- " (max > {:g}, min < {:g}, or max / min > {:g}):"
2441
- .format(tol_up, tol_down, tol_range))
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("row {} (max: {}, min: {}, range: {}): {}".format(r, max_r, min_r, max_r / min_r, c))
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("Too many warnings of same type ({} others remain).".format(len(exceedences) - 10))
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("Checking for range exceedence for each variable (i.e., check Jacobian matrix columns)")
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("col {} ({}): range {}, min {}, max {}".format(c, var_names[c], exc, min_, max_))
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("Too many warnings of same type ({} others remain).".format(len(exceedences) - 10))
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("Exceedence in range of objective variable (range > {:g}):"
2537
- .format(tol_range))
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("col {} ({}): range: {}, obj: {}, min constr: {}, max constr {}"
2541
- .format(c, var_names[c], max_range, obj_coeff, min_r, max_r))
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("Too many warnings of same type ({} others remain).".format(len(exceedences) - 10))
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=1E4, tol_down=1E-2, ignore_all_zero=True):
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("Variables with at least one (absolute) state vector entry/entries larger than {}"
2586
- .format(tol_up))
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("Variables with all (absolute) state vector entry/entries smaller than {}{}"
2603
- .format(tol_down, ignore_all_zero_string))
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: