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
@@ -4,13 +4,18 @@ from collections import OrderedDict
4
4
  from typing import Dict, Union
5
5
 
6
6
  import casadi as ca
7
-
8
7
  import numpy as np
9
8
 
10
9
  from rtctools._internal.alias_tools import AliasDict
11
10
 
12
- from .goal_programming_mixin_base import Goal, StateGoal, _GoalProgrammingMixinBase # noqa: F401
13
- from .goal_programming_mixin_base import _EmptyEnsembleList, _EmptyEnsembleOrderedDict, _GoalConstraint
11
+ from .goal_programming_mixin_base import ( # noqa: F401
12
+ Goal,
13
+ StateGoal,
14
+ _EmptyEnsembleList,
15
+ _EmptyEnsembleOrderedDict,
16
+ _GoalConstraint,
17
+ _GoalProgrammingMixinBase,
18
+ )
14
19
  from .timeseries import Timeseries
15
20
 
16
21
  logger = logging.getLogger("rtctools")
@@ -62,8 +67,12 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
62
67
 
63
68
  def bounds(self):
64
69
  bounds = super().bounds()
65
- for epsilon in (self.__subproblem_epsilons + self.__subproblem_path_epsilons +
66
- self.__problem_epsilons + self.__problem_path_epsilons):
70
+ for epsilon in (
71
+ self.__subproblem_epsilons
72
+ + self.__subproblem_path_epsilons
73
+ + self.__problem_epsilons
74
+ + self.__problem_path_epsilons
75
+ ):
67
76
  bounds[epsilon.name()] = (0.0, 1.0)
68
77
  return bounds
69
78
 
@@ -82,7 +91,7 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
82
91
 
83
92
  # Append min/max timeseries to the constant inputs. Note that min/max
84
93
  # timeseries are shared between all ensemble members.
85
- for (variable, value) in self.__subproblem_path_timeseries + self.__problem_path_timeseries:
94
+ for variable, value in self.__subproblem_path_timeseries + self.__problem_path_timeseries:
86
95
  if isinstance(value, np.ndarray):
87
96
  value = Timeseries(self.times(), np.broadcast_to(value, (n_times, len(value))))
88
97
  elif not isinstance(value, Timeseries):
@@ -104,7 +113,7 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
104
113
 
105
114
  # Append min/max values to the parameters. Note that min/max values
106
115
  # are shared between all ensemble members.
107
- for (variable, value) in self.__subproblem_parameters + self.__problem_parameters:
116
+ for variable, value in self.__subproblem_parameters + self.__problem_parameters:
108
117
  parameters[variable] = value
109
118
  return parameters
110
119
 
@@ -116,13 +125,15 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
116
125
  seed = AliasDict(self.alias_relation)
117
126
  for key, result in self.__results[ensemble_member].items():
118
127
  times = self.times(key)
119
- if ((result.ndim == 1 and len(result) == len(times))
120
- or (result.ndim == 2 and result.shape[0] == len(times))):
128
+ if (result.ndim == 1 and len(result) == len(times)) or (
129
+ result.ndim == 2 and result.shape[0] == len(times)
130
+ ):
121
131
  # Only include seed timeseries which are consistent
122
132
  # with the specified time stamps.
123
133
  seed[key] = Timeseries(times, result)
124
- elif ((result.ndim == 1 and len(result) == 1)
125
- or (result.ndim == 2 and result.shape[0] == 1)):
134
+ elif (result.ndim == 1 and len(result) == 1) or (
135
+ result.ndim == 2 and result.shape[0] == 1
136
+ ):
126
137
  seed[key] = result
127
138
 
128
139
  # Seed epsilons of current priority
@@ -144,14 +155,18 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
144
155
  return seed
145
156
 
146
157
  def objective(self, ensemble_member):
147
- n_objectives = self._gp_n_objectives(self.__subproblem_objectives, self.__subproblem_path_objectives,
148
- ensemble_member)
158
+ n_objectives = self._gp_n_objectives(
159
+ self.__subproblem_objectives, self.__subproblem_path_objectives, ensemble_member
160
+ )
149
161
  return self._gp_objective(self.__subproblem_objectives, n_objectives, ensemble_member)
150
162
 
151
163
  def path_objective(self, ensemble_member):
152
- n_objectives = self._gp_n_objectives(self.__subproblem_objectives, self.__subproblem_path_objectives,
153
- ensemble_member)
154
- return self._gp_path_objective(self.__subproblem_path_objectives, n_objectives, ensemble_member)
164
+ n_objectives = self._gp_n_objectives(
165
+ self.__subproblem_objectives, self.__subproblem_path_objectives, ensemble_member
166
+ )
167
+ return self._gp_path_objective(
168
+ self.__subproblem_path_objectives, n_objectives, ensemble_member
169
+ )
155
170
 
156
171
  def constraints(self, ensemble_member):
157
172
  constraints = super().constraints(ensemble_member)
@@ -159,7 +174,8 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
159
174
  additional_constraints = itertools.chain(
160
175
  self.__constraint_store[ensemble_member].values(),
161
176
  self.__problem_constraints[ensemble_member],
162
- self.__subproblem_soft_constraints[ensemble_member])
177
+ self.__subproblem_soft_constraints[ensemble_member],
178
+ )
163
179
 
164
180
  for constraint in additional_constraints:
165
181
  constraints.append((constraint.function(self), constraint.min, constraint.max))
@@ -172,7 +188,8 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
172
188
  additional_path_constraints = itertools.chain(
173
189
  self.__path_constraint_store[ensemble_member].values(),
174
190
  self.__problem_path_constraints[ensemble_member],
175
- self.__subproblem_path_soft_constraints[ensemble_member])
191
+ self.__subproblem_path_soft_constraints[ensemble_member],
192
+ )
176
193
 
177
194
  for constraint in additional_path_constraints:
178
195
  path_constraints.append((constraint.function(self), constraint.min, constraint.max))
@@ -183,26 +200,25 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
183
200
  # Call parent
184
201
  options = super().solver_options()
185
202
 
186
- solver = options['solver']
187
- assert solver in ['bonmin', 'ipopt']
203
+ solver = options["solver"]
204
+ assert solver in ["bonmin", "ipopt"]
188
205
 
189
206
  # Make sure constant states, such as min/max timeseries for violation variables,
190
207
  # are turned into parameters for the final optimization problem.
191
208
  ipopt_options = options[solver]
192
- ipopt_options['fixed_variable_treatment'] = 'make_parameter'
209
+ ipopt_options["fixed_variable_treatment"] = "make_parameter"
193
210
 
194
211
  # Define temporary variable to avoid infinite loop between
195
212
  # solver_options and goal_programming_options.
196
213
  self._loop_breaker_solver_options = True
197
214
 
198
- if not hasattr(self, '_loop_breaker_goal_programming_options'):
199
- if not self.goal_programming_options()['mu_reinit']:
200
- ipopt_options['mu_strategy'] = 'monotone'
215
+ if not hasattr(self, "_loop_breaker_goal_programming_options"):
216
+ if not self.goal_programming_options()["mu_reinit"]:
217
+ ipopt_options["mu_strategy"] = "monotone"
201
218
  if not self._gp_first_run:
202
- ipopt_options['mu_init'] = self.solver_stats['iterations'][
203
- 'mu'][-1]
219
+ ipopt_options["mu_init"] = self.solver_stats["iterations"]["mu"][-1]
204
220
 
205
- delattr(self, '_loop_breaker_solver_options')
221
+ delattr(self, "_loop_breaker_solver_options")
206
222
 
207
223
  return options
208
224
 
@@ -243,9 +259,9 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
243
259
  1. Minimization goals do not get ``constraint_relaxation`` applied when
244
260
  ``fix_minimized_values`` is True.
245
261
 
246
- 2. Because of the constraints it generates, when ``keep_soft_constraints`` is True, the option
247
- ``fix_minimized_values`` needs to be set to False for the ``constraint_relaxation`` to
248
- be applied at all.
262
+ 2. Because of the constraints it generates, when ``keep_soft_constraints`` is True, the
263
+ option ``fix_minimized_values`` needs to be set to False for the
264
+ ``constraint_relaxation`` to be applied at all.
249
265
 
250
266
  A goal is considered to be violated if the violation, scaled between 0 and 1, is greater
251
267
  than the specified tolerance. Violated goals are fixed. Use of this option is normally not
@@ -259,67 +275,74 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
259
275
  If ``fix_minimized_values`` is set to ``True``, goal functions will be set to equal their
260
276
  optimized values in optimization problems generated during subsequent priorities. Otherwise,
261
277
  only an upper bound will be set. Use of this option is normally not required.
262
- Note that a non-zero goal relaxation overrules this option; a non-zero relaxation will always
263
- result in only an upper bound being set.
264
- Also note that the use of this option may add non-convex constraints to the optimization problem.
265
- The default value for this parameter is ``True`` for the default solvers IPOPT/BONMIN. If any
266
- other solver is used, the default value is ``False``.
267
-
268
- If ``check_monotonicity`` is set to ``True``, then it will be checked whether goals with the same
269
- function key form a monotonically decreasing sequence with regards to the target interval.
270
-
271
- The option ``equality_threshold`` controls when a two-sided inequality constraint is folded into
272
- an equality constraint.
273
-
274
- The option ``interior_distance`` controls the distance from the scaled target bounds, starting
275
- from which the function value is considered to lie in the interior of the target space.
276
-
277
- If ``scale_by_problem_size`` is set to ``True``, the objective (i.e. the sum of the violation variables)
278
- will be divided by the number of goals, and the path objective will be divided by the number
279
- of path goals and the number of active time steps (per goal). This will make sure the objectives are always in
280
- the range [0, 1], at the cost of solving each goal/time step less accurately.
281
-
282
- The option ``keep_soft_constraints`` controls how the epsilon variables introduced in the target
283
- goals are dealt with in subsequent priorities.
284
- If ``keep_soft_constraints`` is set to False, each epsilon is replaced by its computed value and
285
- those are used to derive a new set of constraints.
286
- If ``keep_soft_constraints`` is set to True, the epsilons are kept as variables and the constraints
287
- are not modified. To ensure the goal programming philosophy, i.e., Pareto optimality, a single
288
- constraint is added to enforce that the objective function must always be at most the objective
289
- value. This method allows for a larger solution space, at the cost of having a (possibly) more complex
290
- optimization problem. Indeed, more variables are kept around throughout the optimization and any
291
- objective function is turned into a constraint for the subsequent priorities (while in the False
292
- option this was the case only for the function of minimization goals).
278
+ Note that a non-zero goal relaxation overrules this option; a non-zero relaxation will
279
+ always result in only an upper bound being set.
280
+ Also note that the use of this option may add non-convex constraints to the optimization
281
+ problem.
282
+ The default value for this parameter is ``True`` for the default solvers IPOPT/BONMIN. If
283
+ any other solver is used, the default value is ``False``.
284
+
285
+ If ``check_monotonicity`` is set to ``True``, then it will be checked whether goals with
286
+ the same function key form a monotonically decreasing sequence with regards to the target
287
+ interval.
288
+
289
+ The option ``equality_threshold`` controls when a two-sided inequality constraint is folded
290
+ into an equality constraint.
291
+
292
+ The option ``interior_distance`` controls the distance from the scaled target bounds,
293
+ starting from which the function value is considered to lie in the interior of the target
294
+ space.
295
+
296
+ If ``scale_by_problem_size`` is set to ``True``, the objective (i.e. the sum of the
297
+ violation variables) will be divided by the number of goals, and the path objective will
298
+ be divided by the number of path goals and the number of active time steps (per goal).
299
+ This will make sure the objectives are always in the range [0, 1], at the cost of solving
300
+ each goal/time step less accurately.
301
+
302
+ The option ``keep_soft_constraints`` controls how the epsilon variables introduced in the
303
+ target goals are dealt with in subsequent priorities.
304
+ If ``keep_soft_constraints`` is set to False, each epsilon is replaced by its computed
305
+ value and those are used to derive a new set of constraints.
306
+ If ``keep_soft_constraints`` is set to True, the epsilons are kept as variables and the
307
+ constraints are not modified. To ensure the goal programming philosophy, i.e., Pareto
308
+ optimality, a single constraint is added to enforce that the objective function must
309
+ always be at most the objective value. This method allows for a larger solution space, at
310
+ the cost of having a (possibly) more complex optimization problem. Indeed, more variables
311
+ are kept around throughout the optimization and any objective function is turned into a
312
+ constraint for the subsequent priorities (while in the False option this was the case only
313
+ for the function of minimization goals).
293
314
 
294
315
  :returns: A dictionary of goal programming options.
295
316
  """
296
317
 
297
318
  options = {}
298
319
 
299
- options['mu_reinit'] = True
300
- options['violation_relaxation'] = 0.0 # Disable by default
301
- options['constraint_relaxation'] = 0.0 # Disable by default
302
- options['violation_tolerance'] = np.inf # Disable by default
303
- options['fix_minimized_values'] = False
304
- options['check_monotonicity'] = True
305
- options['equality_threshold'] = 1e-8
306
- options['interior_distance'] = 1e-6
307
- options['scale_by_problem_size'] = False
308
- options['keep_soft_constraints'] = False
320
+ options["mu_reinit"] = True
321
+ options["violation_relaxation"] = 0.0 # Disable by default
322
+ options["constraint_relaxation"] = 0.0 # Disable by default
323
+ options["violation_tolerance"] = np.inf # Disable by default
324
+ options["fix_minimized_values"] = False
325
+ options["check_monotonicity"] = True
326
+ options["equality_threshold"] = 1e-8
327
+ options["interior_distance"] = 1e-6
328
+ options["scale_by_problem_size"] = False
329
+ options["keep_soft_constraints"] = False
309
330
 
310
331
  # Define temporary variable to avoid infinite loop between
311
332
  # solver_options and goal_programming_options.
312
333
  self._loop_breaker_goal_programming_options = True
313
334
 
314
- if not hasattr(self, '_loop_breaker_solver_options'):
315
- if self.solver_options()['solver'] in {'ipopt', 'bonmin'}:
316
- options['fix_minimized_values'] = True
335
+ if not hasattr(self, "_loop_breaker_solver_options"):
336
+ if self.solver_options()["solver"] in {"ipopt", "bonmin"}:
337
+ options["fix_minimized_values"] = True
317
338
 
318
- delattr(self, '_loop_breaker_goal_programming_options')
339
+ delattr(self, "_loop_breaker_goal_programming_options")
319
340
 
320
341
  return options
321
342
 
322
- def __goal_hard_constraint(self, goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal):
343
+ def __goal_hard_constraint(
344
+ self, goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal
345
+ ):
323
346
  if not is_path_goal:
324
347
  epsilon = epsilon[:1]
325
348
 
@@ -328,21 +351,29 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
328
351
  if goal.has_target_bounds:
329
352
  # We use a violation variable formulation, with the violation
330
353
  # variables epsilon bounded between 0 and 1.
331
- m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(epsilon, np.inf, dtype=np.float64)
354
+ m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(
355
+ epsilon, np.inf, dtype=np.float64
356
+ )
332
357
 
333
358
  # A function range does not have to be specified for critical
334
359
  # goals. Avoid multiplying with NaN in that case.
335
360
  if goal.has_target_min:
336
- m = (epsilon * ((goal.function_range[0] - goal_m) if not goal.critical else 0.0)
337
- + goal_m - goal.relaxation) / goal.function_nominal
361
+ m = (
362
+ epsilon * ((goal.function_range[0] - goal_m) if not goal.critical else 0.0)
363
+ + goal_m
364
+ - goal.relaxation
365
+ ) / goal.function_nominal
338
366
  if goal.has_target_max:
339
- M = (epsilon * ((goal.function_range[1] - goal_M) if not goal.critical else 0.0)
340
- + goal_M + goal.relaxation) / goal.function_nominal
367
+ M = (
368
+ epsilon * ((goal.function_range[1] - goal_M) if not goal.critical else 0.0)
369
+ + goal_M
370
+ + goal.relaxation
371
+ ) / goal.function_nominal
341
372
 
342
373
  if goal.has_target_min and goal.has_target_max:
343
374
  # Avoid comparing with NaN
344
375
  inds = ~(np.isnan(m) | np.isnan(M))
345
- inds[inds] &= np.abs(m[inds] - M[inds]) < options['equality_threshold']
376
+ inds[inds] &= np.abs(m[inds] - M[inds]) < options["equality_threshold"]
346
377
  if np.any(inds):
347
378
  avg = 0.5 * (m + M)
348
379
  m[inds] = M[inds] = avg[inds]
@@ -350,31 +381,35 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
350
381
  m[~np.isfinite(goal_m)] = -np.inf
351
382
  M[~np.isfinite(goal_M)] = np.inf
352
383
 
353
- inds = epsilon > options['violation_tolerance']
384
+ inds = epsilon > options["violation_tolerance"]
354
385
  if np.any(inds):
355
386
  if is_path_goal:
356
- expr = self.map_path_expression(goal.function(self, ensemble_member), ensemble_member)
387
+ expr = self.map_path_expression(
388
+ goal.function(self, ensemble_member), ensemble_member
389
+ )
357
390
  else:
358
391
  expr = goal.function(self, ensemble_member)
359
392
 
360
- function = ca.Function('f', [self.solver_input], [expr])
393
+ function = ca.Function("f", [self.solver_input], [expr])
361
394
  value = np.array(function(self.solver_output))
362
395
 
363
396
  m[inds] = (value - goal.relaxation) / goal.function_nominal
364
397
  M[inds] = (value + goal.relaxation) / goal.function_nominal
365
398
 
366
- m -= options['constraint_relaxation']
367
- M += options['constraint_relaxation']
399
+ m -= options["constraint_relaxation"]
400
+ M += options["constraint_relaxation"]
368
401
  else:
369
402
  # Epsilon encodes the position within the function range.
370
- if options['fix_minimized_values'] and goal.relaxation == 0.0:
403
+ if options["fix_minimized_values"] and goal.relaxation == 0.0:
371
404
  m = epsilon / goal.function_nominal
372
405
  M = epsilon / goal.function_nominal
373
406
  self.check_collocation_linearity = False
374
407
  self.linear_collocation = False
375
408
  else:
376
409
  m = -np.inf * np.ones(epsilon.shape)
377
- M = (epsilon + goal.relaxation) / goal.function_nominal + options['constraint_relaxation']
410
+ M = (epsilon + goal.relaxation) / goal.function_nominal + options[
411
+ "constraint_relaxation"
412
+ ]
378
413
 
379
414
  if is_path_goal:
380
415
  m = Timeseries(self.times(), m)
@@ -386,13 +421,17 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
386
421
  constraint = _GoalConstraint(
387
422
  goal,
388
423
  lambda problem, ensemble_member=ensemble_member, goal=goal: (
389
- goal.function(problem, ensemble_member) / goal.function_nominal),
390
- m, M, True)
424
+ goal.function(problem, ensemble_member) / goal.function_nominal
425
+ ),
426
+ m,
427
+ M,
428
+ True,
429
+ )
391
430
 
392
431
  # Epsilon is fixed. Override previous {min,max} constraints for this
393
432
  # state.
394
433
  if existing_constraint:
395
- constraint.update_bounds(existing_constraint, enforce='other')
434
+ constraint.update_bounds(existing_constraint, enforce="other")
396
435
 
397
436
  return constraint
398
437
 
@@ -419,9 +458,9 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
419
458
 
420
459
  for j, goal in enumerate(goals):
421
460
  if (
422
- not goal.has_target_bounds or
423
- goal.violation_timeseries_id is not None or
424
- goal.function_value_timeseries_id is not None
461
+ not goal.has_target_bounds
462
+ or goal.violation_timeseries_id is not None
463
+ or goal.function_value_timeseries_id is not None
425
464
  ):
426
465
  goal_functions[j] = goal.function(self, ensemble_member)
427
466
 
@@ -432,11 +471,10 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
432
471
  else:
433
472
  expr = ca.transpose(ca.vertcat(*goal_functions.values()))
434
473
 
435
- f = ca.Function('f', [self.solver_input], [expr])
474
+ f = ca.Function("f", [self.solver_input], [expr])
436
475
  raw_function_values = np.array(f(self.solver_output))
437
476
  goal_function_values[ensemble_member] = {
438
- k: raw_function_values[:, j].ravel()
439
- for j, k in enumerate(goal_functions.keys())
477
+ k: raw_function_values[:, j].ravel() for j, k in enumerate(goal_functions.keys())
440
478
  }
441
479
 
442
480
  # Re-add constraints, this time with epsilon values fixed
@@ -450,15 +488,14 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
450
488
  self.set_timeseries(
451
489
  goal.function_value_timeseries_id,
452
490
  Timeseries(times, function_value),
453
- ensemble_member
491
+ ensemble_member,
454
492
  )
455
493
 
456
494
  if goal.critical:
457
495
  continue
458
496
 
459
497
  if goal.has_target_bounds:
460
- epsilon = self.__results[ensemble_member][
461
- eps_format.format(sym_index, j)]
498
+ epsilon = self.__results[ensemble_member][eps_format.format(sym_index, j)]
462
499
 
463
500
  # Store results
464
501
  if goal.violation_timeseries_id is not None:
@@ -466,34 +503,48 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
466
503
  epsilon_active = np.copy(epsilon)
467
504
  m = goal.target_min
468
505
  if isinstance(m, Timeseries):
469
- m = self.interpolate(times, goal.target_min.times, goal.target_min.values)
506
+ m = self.interpolate(
507
+ times, goal.target_min.times, goal.target_min.values
508
+ )
470
509
  M = goal.target_max
471
510
  if isinstance(M, Timeseries):
472
- M = self.interpolate(times, goal.target_max.times, goal.target_max.values)
511
+ M = self.interpolate(
512
+ times, goal.target_max.times, goal.target_max.values
513
+ )
473
514
  w = np.ones_like(function_value)
474
515
  if goal.has_target_min:
475
516
  # Avoid comparing with NaN while making sure that
476
517
  # w[i] is True when m[i] is not finite.
477
518
  m = np.array(m)
478
519
  m[~np.isfinite(m)] = -np.inf
479
- w = np.logical_and(w, (function_value / goal.function_nominal >
480
- m / goal.function_nominal + options['interior_distance']))
520
+ w = np.logical_and(
521
+ w,
522
+ (
523
+ function_value / goal.function_nominal
524
+ > m / goal.function_nominal + options["interior_distance"]
525
+ ),
526
+ )
481
527
  if goal.has_target_max:
482
528
  # Avoid comparing with NaN while making sure that
483
529
  # w[i] is True when M[i] is not finite.
484
530
  M = np.array(M)
485
531
  M[~np.isfinite(M)] = np.inf
486
- w = np.logical_and(w, (function_value / goal.function_nominal <
487
- M / goal.function_nominal + options['interior_distance']))
532
+ w = np.logical_and(
533
+ w,
534
+ (
535
+ function_value / goal.function_nominal
536
+ < M / goal.function_nominal + options["interior_distance"]
537
+ ),
538
+ )
488
539
  epsilon_active[w] = np.nan
489
540
  self.set_timeseries(
490
541
  goal.violation_timeseries_id,
491
542
  Timeseries(times, epsilon_active),
492
- ensemble_member
543
+ ensemble_member,
493
544
  )
494
545
 
495
546
  # Add a relaxation to appease the barrier method.
496
- epsilon += options['violation_relaxation']
547
+ epsilon += options["violation_relaxation"]
497
548
  else:
498
549
  epsilon = function_value
499
550
 
@@ -501,7 +552,8 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
501
552
  existing_constraint = constraint_store[ensemble_member].get(fk, None)
502
553
 
503
554
  constraint_store[ensemble_member][fk] = self.__goal_hard_constraint(
504
- goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal)
555
+ goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal
556
+ )
505
557
 
506
558
  def __add_subproblem_objective_constraint(self):
507
559
  # We want to keep the additional variables/parameters we set around
@@ -512,44 +564,54 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
512
564
 
513
565
  for ensemble_member in range(self.ensemble_size):
514
566
  self.__problem_constraints[ensemble_member].extend(
515
- self.__subproblem_soft_constraints[ensemble_member])
567
+ self.__subproblem_soft_constraints[ensemble_member]
568
+ )
516
569
  self.__problem_path_constraints[ensemble_member].extend(
517
- self.__subproblem_path_soft_constraints[ensemble_member])
570
+ self.__subproblem_path_soft_constraints[ensemble_member]
571
+ )
518
572
 
519
- # Extract information about the objective value, this is used for the Pareto optimality constraint.
520
- # We only retain information about the objective functions defined through the goal framework as user
521
- # define objective functions may relay on local variables.
573
+ # Extract information about the objective value, this is used for the Pareto optimality
574
+ # constraint. We only retain information about the objective functions defined through the
575
+ # goal framework as user define objective functions may relay on local variables.
522
576
  subproblem_objectives = self.__subproblem_objectives.copy()
523
577
  subproblem_path_objectives = self.__subproblem_path_objectives.copy()
524
578
 
525
- def _constraint_func(problem,
526
- subproblem_objectives=subproblem_objectives,
527
- subproblem_path_objectives=subproblem_path_objectives):
579
+ def _constraint_func(
580
+ problem,
581
+ subproblem_objectives=subproblem_objectives,
582
+ subproblem_path_objectives=subproblem_path_objectives,
583
+ ):
528
584
  val = 0.0
529
585
  for ensemble_member in range(problem.ensemble_size):
530
586
  # NOTE: Users might be overriding objective() and/or path_objective(). Use the
531
587
  # private methods that work only on the goals.
532
588
  n_objectives = problem._gp_n_objectives(
533
- subproblem_objectives, subproblem_path_objectives, ensemble_member)
589
+ subproblem_objectives, subproblem_path_objectives, ensemble_member
590
+ )
534
591
  expr = problem._gp_objective(subproblem_objectives, n_objectives, ensemble_member)
535
- expr += ca.sum1(problem.map_path_expression(
536
- problem._gp_path_objective(subproblem_path_objectives, n_objectives, ensemble_member),
537
- ensemble_member))
592
+ expr += ca.sum1(
593
+ problem.map_path_expression(
594
+ problem._gp_path_objective(
595
+ subproblem_path_objectives, n_objectives, ensemble_member
596
+ ),
597
+ ensemble_member,
598
+ )
599
+ )
538
600
  val += problem.ensemble_member_probability(ensemble_member) * expr
539
601
 
540
602
  return val
541
603
 
542
- f = ca.Function('tmp', [self.solver_input], [_constraint_func(self)])
604
+ f = ca.Function("tmp", [self.solver_input], [_constraint_func(self)])
543
605
  obj_val = float(f(self.solver_output))
544
606
 
545
607
  options = self.goal_programming_options()
546
608
 
547
- if options['fix_minimized_values']:
609
+ if options["fix_minimized_values"]:
548
610
  constraint = _GoalConstraint(None, _constraint_func, obj_val, obj_val, True)
549
611
  self.check_collocation_linearity = False
550
612
  self.linear_collocation = False
551
613
  else:
552
- obj_val += options['constraint_relaxation']
614
+ obj_val += options["constraint_relaxation"]
553
615
  constraint = _GoalConstraint(None, _constraint_func, -np.inf, obj_val, True)
554
616
 
555
617
  # The goal works over all ensemble members, so we add it to the last
@@ -570,20 +632,36 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
570
632
  options = self.goal_programming_options()
571
633
 
572
634
  # Validate (in)compatible options
573
- if options['keep_soft_constraints'] and options['violation_relaxation']:
574
- raise Exception("The option 'violation_relaxation' cannot be used when 'keep_soft_constraints' is set.")
635
+ if options["keep_soft_constraints"] and options["violation_relaxation"]:
636
+ raise Exception(
637
+ "The option 'violation_relaxation' cannot be used "
638
+ "when 'keep_soft_constraints' is set."
639
+ )
575
640
 
576
641
  # Validate goal definitions
577
642
  self._gp_validate_goals(goals, is_path_goal=False)
578
643
  self._gp_validate_goals(path_goals, is_path_goal=True)
579
644
 
580
- priorities = {int(goal.priority) for goal in itertools.chain(goals, path_goals) if not goal.is_empty}
645
+ priorities = {
646
+ int(goal.priority) for goal in itertools.chain(goals, path_goals) if not goal.is_empty
647
+ }
581
648
 
582
649
  for priority in sorted(priorities):
583
- subproblems.append((
584
- priority,
585
- [goal for goal in goals if int(goal.priority) == priority and not goal.is_empty],
586
- [goal for goal in path_goals if int(goal.priority) == priority and not goal.is_empty]))
650
+ subproblems.append(
651
+ (
652
+ priority,
653
+ [
654
+ goal
655
+ for goal in goals
656
+ if int(goal.priority) == priority and not goal.is_empty
657
+ ],
658
+ [
659
+ goal
660
+ for goal in path_goals
661
+ if int(goal.priority) == priority and not goal.is_empty
662
+ ],
663
+ )
664
+ )
587
665
 
588
666
  # Solve the subproblems one by one
589
667
  logger.info("Starting goal programming")
@@ -591,7 +669,9 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
591
669
  success = False
592
670
 
593
671
  self.__constraint_store = [OrderedDict() for ensemble_member in range(self.ensemble_size)]
594
- self.__path_constraint_store = [OrderedDict() for ensemble_member in range(self.ensemble_size)]
672
+ self.__path_constraint_store = [
673
+ OrderedDict() for ensemble_member in range(self.ensemble_size)
674
+ ]
595
675
 
596
676
  # Lists for when `keep_soft_constraints` is True
597
677
  self.__problem_constraints = [[] for ensemble_member in range(self.ensemble_size)]
@@ -611,15 +691,21 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
611
691
  # Call the pre priority hook
612
692
  self.priority_started(priority)
613
693
 
614
- (self.__subproblem_epsilons, self.__subproblem_objectives,
615
- self.__subproblem_soft_constraints, hard_constraints,
616
- self.__subproblem_parameters) = \
617
- self._gp_goal_constraints(goals, i, options, is_path_goal=False)
618
-
619
- (self.__subproblem_path_epsilons, self.__subproblem_path_objectives,
620
- self.__subproblem_path_soft_constraints, path_hard_constraints,
621
- self.__subproblem_path_timeseries) = \
622
- self._gp_goal_constraints(path_goals, i, options, is_path_goal=True)
694
+ (
695
+ self.__subproblem_epsilons,
696
+ self.__subproblem_objectives,
697
+ self.__subproblem_soft_constraints,
698
+ hard_constraints,
699
+ self.__subproblem_parameters,
700
+ ) = self._gp_goal_constraints(goals, i, options, is_path_goal=False)
701
+
702
+ (
703
+ self.__subproblem_path_epsilons,
704
+ self.__subproblem_path_objectives,
705
+ self.__subproblem_path_soft_constraints,
706
+ path_hard_constraints,
707
+ self.__subproblem_path_timeseries,
708
+ ) = self._gp_goal_constraints(path_goals, i, options, is_path_goal=True)
623
709
 
624
710
  # Put hard constraints in the constraint stores
625
711
  self._gp_update_constraint_store(self.__constraint_store, hard_constraints)
@@ -627,7 +713,10 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
627
713
 
628
714
  # Solve subproblem
629
715
  success = super().optimize(
630
- preprocessing=False, postprocessing=False, log_solver_failure_as_error=log_solver_failure_as_error)
716
+ preprocessing=False,
717
+ postprocessing=False,
718
+ log_solver_failure_as_error=log_solver_failure_as_error,
719
+ )
631
720
  if not success:
632
721
  break
633
722
 
@@ -636,15 +725,17 @@ class GoalProgrammingMixin(_GoalProgrammingMixinBase):
636
725
  # Store results. Do this here, to make sure we have results even
637
726
  # if a subsequent priority fails.
638
727
  self.__results_are_current = False
639
- self.__results = [self.extract_results(
640
- ensemble_member) for ensemble_member in range(self.ensemble_size)]
728
+ self.__results = [
729
+ self.extract_results(ensemble_member)
730
+ for ensemble_member in range(self.ensemble_size)
731
+ ]
641
732
  self.__results_are_current = True
642
733
 
643
734
  # Call the post priority hook, so that intermediate results can be
644
735
  # logged/inspected.
645
736
  self.priority_completed(priority)
646
737
 
647
- if options['keep_soft_constraints']:
738
+ if options["keep_soft_constraints"]:
648
739
  self.__add_subproblem_objective_constraint()
649
740
  else:
650
741
  self.__soft_to_hard_constraints(goals, i, is_path_goal=False)