rtc-tools 2.7.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. rtc_tools-2.7.3.dist-info/METADATA +53 -0
  2. rtc_tools-2.7.3.dist-info/RECORD +50 -0
  3. rtc_tools-2.7.3.dist-info/WHEEL +5 -0
  4. rtc_tools-2.7.3.dist-info/entry_points.txt +3 -0
  5. rtc_tools-2.7.3.dist-info/licenses/COPYING.LESSER +165 -0
  6. rtc_tools-2.7.3.dist-info/top_level.txt +1 -0
  7. rtctools/__init__.py +5 -0
  8. rtctools/_internal/__init__.py +0 -0
  9. rtctools/_internal/alias_tools.py +188 -0
  10. rtctools/_internal/caching.py +25 -0
  11. rtctools/_internal/casadi_helpers.py +99 -0
  12. rtctools/_internal/debug_check_helpers.py +41 -0
  13. rtctools/_version.py +21 -0
  14. rtctools/data/__init__.py +4 -0
  15. rtctools/data/csv.py +150 -0
  16. rtctools/data/interpolation/__init__.py +3 -0
  17. rtctools/data/interpolation/bspline.py +31 -0
  18. rtctools/data/interpolation/bspline1d.py +169 -0
  19. rtctools/data/interpolation/bspline2d.py +54 -0
  20. rtctools/data/netcdf.py +467 -0
  21. rtctools/data/pi.py +1236 -0
  22. rtctools/data/rtc.py +228 -0
  23. rtctools/data/storage.py +343 -0
  24. rtctools/optimization/__init__.py +0 -0
  25. rtctools/optimization/collocated_integrated_optimization_problem.py +3208 -0
  26. rtctools/optimization/control_tree_mixin.py +221 -0
  27. rtctools/optimization/csv_lookup_table_mixin.py +462 -0
  28. rtctools/optimization/csv_mixin.py +300 -0
  29. rtctools/optimization/goal_programming_mixin.py +769 -0
  30. rtctools/optimization/goal_programming_mixin_base.py +1094 -0
  31. rtctools/optimization/homotopy_mixin.py +165 -0
  32. rtctools/optimization/initial_state_estimation_mixin.py +89 -0
  33. rtctools/optimization/io_mixin.py +320 -0
  34. rtctools/optimization/linearization_mixin.py +33 -0
  35. rtctools/optimization/linearized_order_goal_programming_mixin.py +235 -0
  36. rtctools/optimization/min_abs_goal_programming_mixin.py +385 -0
  37. rtctools/optimization/modelica_mixin.py +482 -0
  38. rtctools/optimization/netcdf_mixin.py +177 -0
  39. rtctools/optimization/optimization_problem.py +1302 -0
  40. rtctools/optimization/pi_mixin.py +292 -0
  41. rtctools/optimization/planning_mixin.py +19 -0
  42. rtctools/optimization/single_pass_goal_programming_mixin.py +676 -0
  43. rtctools/optimization/timeseries.py +56 -0
  44. rtctools/rtctoolsapp.py +131 -0
  45. rtctools/simulation/__init__.py +0 -0
  46. rtctools/simulation/csv_mixin.py +171 -0
  47. rtctools/simulation/io_mixin.py +195 -0
  48. rtctools/simulation/pi_mixin.py +255 -0
  49. rtctools/simulation/simulation_problem.py +1293 -0
  50. rtctools/util.py +241 -0
@@ -0,0 +1,235 @@
1
+ import casadi as ca
2
+ import numpy as np
3
+
4
+ from rtctools.optimization.goal_programming_mixin_base import (
5
+ Goal,
6
+ StateGoal,
7
+ _GoalConstraint,
8
+ _GoalProgrammingMixinBase,
9
+ )
10
+
11
+
12
+ class LinearizedOrderGoal(Goal):
13
+ #: Override linearization of goal order. Related global goal programming
14
+ #: option is ``linearize_goal_order``
15
+ #: (see :py:meth:`LinearizedOrderGoalProgrammingMixin.goal_programming_options`).
16
+ #: The default value of None defers to the global option, but the user can
17
+ #: explicitly override it per goal by setting this value to True or False.
18
+ linearize_order = None
19
+
20
+ #: Coefficients to linearize a goal's order
21
+ _linear_coefficients = {}
22
+
23
+ @classmethod
24
+ def _get_linear_coefficients(cls, order, eps=0.1, kind="balanced"):
25
+ assert order > 1, "Order should be strictly larger than one"
26
+
27
+ try:
28
+ return cls._linear_coefficients[eps][order]
29
+ except KeyError:
30
+ pass
31
+
32
+ x = ca.SX.sym("x")
33
+ a = ca.SX.sym("a")
34
+ b = ca.SX.sym("b")
35
+
36
+ # Strike a balance between "absolute error < eps" and "relative error < eps" by
37
+ # multiplying eps with x**(order-1)
38
+ if kind == "balanced":
39
+ f = x**order - eps * x ** (order - 1) - (a * x + b)
40
+ elif kind == "abs":
41
+ f = x**order - eps - (a * x + b)
42
+ else:
43
+ raise Exception("Unknown error approximation strategy '{}'".format(kind))
44
+
45
+ res_vals = ca.Function("res_vals", [x, ca.vertcat(a, b)], [f])
46
+
47
+ do_step = ca.rootfinder("next_state", "fast_newton", res_vals)
48
+
49
+ x = 0.0
50
+ a = 0.0
51
+ b = 0.0
52
+
53
+ xs = [0.0]
54
+ while x < 1.0:
55
+ # Initial guess larger than 1.0 to always have the next point be
56
+ # on the right (i.e. not left) side.
57
+ x = float(do_step(2.0, [a, b]))
58
+ a = order * x ** (order - 1)
59
+ b = x**order - a * x
60
+ xs.append(x)
61
+
62
+ # Turn underestimate into an overestimate, such that we get rid of
63
+ # horizontal line at origin.
64
+ xs[-1] = 1.0
65
+ xs = np.array(xs)
66
+ ys = xs**order
67
+
68
+ a = (ys[1:] - ys[:-1]) / (xs[1:] - xs[:-1])
69
+ b = ys[1:] - a * xs[1:]
70
+ lines = list(zip(a, b))
71
+
72
+ cls._linear_coefficients.setdefault(eps, {})[order] = lines
73
+
74
+ return lines
75
+
76
+
77
+ class LinearizedOrderStateGoal(LinearizedOrderGoal, StateGoal):
78
+ """
79
+ Convenience class definition for linearized order state goals. Note that
80
+ it is possible to just inherit from :py:class:`.LinearizedOrderGoal` to get the needed
81
+ functionality for control of the linearization at goal level.
82
+ """
83
+
84
+ pass
85
+
86
+
87
+ class LinearizedOrderGoalProgrammingMixin(_GoalProgrammingMixinBase):
88
+ """
89
+ Adds support for linearization of the goal objective functions, i.e. the
90
+ violation variables to a certain power. This can be used to keep a problem
91
+ fully linear and/or make sure that no quadratic constraints appear when using
92
+ the goal programming option ``keep_soft_constraints``.
93
+ """
94
+
95
+ def goal_programming_options(self):
96
+ """
97
+ If ``linearize_goal_order`` is set to ``True``, the goal's order will be
98
+ approximated linearly for any goals where order > 1. Note that this option
99
+ does not work with minimization goals of higher order. Instead, it is
100
+ suggested to transform these minimization goals into goals with a target (and
101
+ function range) when using this option. Note that this option can be overriden
102
+ on the level of a goal by using a :py:class:`LinearizedOrderGoal` (see
103
+ :py:attr:`LinearizedOrderGoal.linearize_order`).
104
+ """
105
+ options = super().goal_programming_options()
106
+ options["linearize_goal_order"] = True
107
+ return options
108
+
109
+ def _gp_validate_goals(self, goals, is_path_goal):
110
+ options = self.goal_programming_options()
111
+
112
+ for goal in goals:
113
+ goal_linearize = None
114
+ if isinstance(goal, LinearizedOrderGoal):
115
+ goal_linearize = goal.linearize_order
116
+
117
+ if goal_linearize or (options["linearize_goal_order"] and goal_linearize is not False):
118
+ if not goal.has_target_bounds and goal.order > 1:
119
+ raise Exception(
120
+ "Higher order minimization goals not allowed with "
121
+ "`linearize_goal_order` for goal {}".format(goal)
122
+ )
123
+
124
+ super()._gp_validate_goals(goals, is_path_goal)
125
+
126
+ def _gp_goal_constraints(self, goals, sym_index, options, is_path_goal):
127
+ options = self.goal_programming_options()
128
+
129
+ def _linearize_goal(goal):
130
+ goal_linearize = None
131
+ if isinstance(goal, LinearizedOrderGoal):
132
+ goal_linearize = goal.linearize_order
133
+
134
+ if goal_linearize or (options["linearize_goal_order"] and goal_linearize is not False):
135
+ if goal.order > 1 and not goal.critical:
136
+ return True
137
+ else:
138
+ return False
139
+ else:
140
+ return False
141
+
142
+ lo_soft_constraints = [[] for ensemble_member in range(self.ensemble_size)]
143
+ lo_epsilons = []
144
+
145
+ # For the linearized goals, we use all of the normal processing,
146
+ # except for the objective. We can override the objective function by
147
+ # setting a _objective_func function on the Goal object.
148
+ for j, goal in enumerate(goals):
149
+ if not _linearize_goal(goal):
150
+ continue
151
+
152
+ assert goal.has_target_bounds, "Cannot linearize minimization goals"
153
+
154
+ # Make a linear epsilon, and constraints relating the linear
155
+ # variable to the original objective function
156
+ path_prefix = "path_" if is_path_goal else ""
157
+ linear_variable = ca.MX.sym(
158
+ path_prefix + "lineps_{}_{}".format(sym_index, j), goal.size
159
+ )
160
+
161
+ lo_epsilons.append(linear_variable)
162
+
163
+ if isinstance(goal, LinearizedOrderGoal):
164
+ coeffs = goal._get_linear_coefficients(goal.order)
165
+ else:
166
+ coeffs = LinearizedOrderGoal._get_linear_coefficients(goal.order)
167
+
168
+ epsilon_name = path_prefix + "eps_{}_{}".format(sym_index, j)
169
+
170
+ for a, b in coeffs:
171
+ # We add to soft constraints, as these constraints are no longer valid when
172
+ # having `keep_soft_constraints` = False. This is because the `epsilon` and
173
+ # the `linear_variable` no longer exist in the next priority.
174
+ for ensemble_member in range(self.ensemble_size):
175
+
176
+ def _f(
177
+ problem,
178
+ goal=goal,
179
+ epsilon_name=epsilon_name,
180
+ linear_variable=linear_variable,
181
+ a=a,
182
+ b=b,
183
+ ensemble_member=ensemble_member,
184
+ is_path_constraint=is_path_goal,
185
+ ):
186
+ if is_path_constraint:
187
+ eps = problem.variable(epsilon_name)
188
+ lin = problem.variable(linear_variable.name())
189
+ else:
190
+ eps = problem.extra_variable(epsilon_name, ensemble_member)
191
+ lin = problem.extra_variable(linear_variable.name(), ensemble_member)
192
+
193
+ return lin - a * eps - b
194
+
195
+ lo_soft_constraints[ensemble_member].append(
196
+ _GoalConstraint(goal, _f, 0.0, np.inf, False)
197
+ )
198
+
199
+ if is_path_goal and options["scale_by_problem_size"]:
200
+ goal_m, goal_M = self._gp_min_max_arrays(goal, target_shape=len(self.times()))
201
+ goal_active = np.isfinite(goal_m) | np.isfinite(goal_M)
202
+ n_active = np.sum(goal_active.astype(int), axis=0)
203
+ else:
204
+ n_active = 1
205
+
206
+ def _objective_func(
207
+ problem,
208
+ ensemble_member,
209
+ goal=goal,
210
+ linear_variable=linear_variable,
211
+ is_path_goal=is_path_goal,
212
+ n_active=n_active,
213
+ ):
214
+ if is_path_goal:
215
+ lin = problem.variable(linear_variable.name())
216
+ else:
217
+ lin = problem.extra_variable(linear_variable.name(), ensemble_member)
218
+
219
+ return goal.weight * lin / n_active
220
+
221
+ goal._objective_func = _objective_func
222
+
223
+ (
224
+ epsilons,
225
+ objectives,
226
+ soft_constraints,
227
+ hard_constraints,
228
+ extra_constants,
229
+ ) = super()._gp_goal_constraints(goals, sym_index, options, is_path_goal)
230
+
231
+ epsilons = epsilons + lo_epsilons
232
+ for ensemble_member in range(self.ensemble_size):
233
+ soft_constraints[ensemble_member].extend(lo_soft_constraints[ensemble_member])
234
+
235
+ return epsilons, objectives, soft_constraints, hard_constraints, extra_constants
@@ -0,0 +1,385 @@
1
+ import functools
2
+ import itertools
3
+ from typing import List
4
+
5
+ import casadi as ca
6
+ import numpy as np
7
+
8
+ from .goal_programming_mixin import GoalProgrammingMixin
9
+ from .goal_programming_mixin_base import (
10
+ Goal,
11
+ StateGoal,
12
+ _EmptyEnsembleList,
13
+ _EmptyEnsembleOrderedDict,
14
+ _GoalConstraint,
15
+ _GoalProgrammingMixinBase,
16
+ )
17
+ from .single_pass_goal_programming_mixin import SinglePassGoalProgrammingMixin
18
+ from .timeseries import Timeseries
19
+
20
+
21
+ class MinAbsGoal(Goal):
22
+ """
23
+ Absolute minimization goal class which can be used to minimize the
24
+ absolute value of the goal's (linear) goal function. Contrary to its super
25
+ class, the default order is 1 as absolute minimization is typically
26
+ desired for fully linear problems.
27
+ """
28
+
29
+ order = 1
30
+
31
+
32
+ class MinAbsStateGoal(StateGoal, MinAbsGoal):
33
+ pass
34
+
35
+
36
+ class _ConvertedMinAbsGoal(Goal):
37
+ order = 1
38
+
39
+ def __init__(self, abs_variable, is_path_goal, orig_goal):
40
+ self.abs_variable = abs_variable
41
+ self.is_path_goal = is_path_goal
42
+ self.orig_goal = orig_goal
43
+
44
+ # Copy relevant properties
45
+ self.size = orig_goal.size
46
+ self.weight = orig_goal.weight
47
+ self.relaxation = orig_goal.relaxation / orig_goal.function_nominal
48
+ self.priority = orig_goal.priority
49
+
50
+ def function(self, optimization_problem, ensemble_member):
51
+ if self.is_path_goal:
52
+ return optimization_problem.variable(self.abs_variable.name())
53
+ else:
54
+ return optimization_problem.extra_variable(self.abs_variable.name(), ensemble_member)
55
+
56
+
57
+ class MinAbsGoalProgrammingMixin(_GoalProgrammingMixinBase):
58
+ """
59
+ Similar behavior to :py:class:`.GoalProgrammingMixin`, but any
60
+ :py:class:`MinAbsGoal` passed to :py:meth:`.min_abs_goals` or
61
+ :py:meth:`.min_abs_path_goals` will be automatically converted to:
62
+
63
+ 1. An auxiliary minimization variable
64
+ 2. Two additional linear constraints relating the auxiliary variable to the goal function
65
+ 3. A new goal (of a different type) minimizing the auxiliary variable
66
+ """
67
+
68
+ def __init__(self, *args, **kwargs):
69
+ super().__init__(*args, **kwargs)
70
+
71
+ # List for any absolute minimization goals
72
+ self.__problem_constraints = _EmptyEnsembleList()
73
+ self.__problem_vars = []
74
+ self.__problem_path_constraints = _EmptyEnsembleList()
75
+ self.__problem_path_vars = []
76
+ self.__seeds = _EmptyEnsembleOrderedDict()
77
+ self.__path_seeds = _EmptyEnsembleOrderedDict()
78
+
79
+ self.__first_run = True
80
+
81
+ @property
82
+ def extra_variables(self):
83
+ return super().extra_variables + self.__problem_vars
84
+
85
+ @property
86
+ def path_variables(self):
87
+ return super().path_variables + self.__problem_path_vars
88
+
89
+ def bounds(self):
90
+ bounds = super().bounds()
91
+ for abs_var in self.__problem_vars + self.__problem_path_vars:
92
+ bounds[abs_var.name()] = (0.0, np.inf)
93
+ return bounds
94
+
95
+ def seed(self, ensemble_member):
96
+ seed = super().seed(ensemble_member)
97
+
98
+ # Seed minimization variables of current priority (not those of
99
+ # previous priorities, as those are handled by GoalProgrammingMixin).
100
+ for abs_var, val in self.__seeds[ensemble_member].items():
101
+ seed[abs_var] = val
102
+
103
+ times = self.times()
104
+ for abs_var, val in self.__path_seeds[ensemble_member].items():
105
+ seed[abs_var] = Timeseries(times, val)
106
+
107
+ return seed
108
+
109
+ def constraints(self, ensemble_member):
110
+ constraints = super().constraints(ensemble_member)
111
+
112
+ for constraint in self.__problem_constraints[ensemble_member]:
113
+ constraints.append((constraint.function(self), constraint.min, constraint.max))
114
+
115
+ return constraints
116
+
117
+ def path_constraints(self, ensemble_member):
118
+ path_constraints = super().path_constraints(ensemble_member)
119
+
120
+ for constraint in self.__problem_path_constraints[ensemble_member]:
121
+ path_constraints.append((constraint.function(self), constraint.min, constraint.max))
122
+
123
+ return path_constraints
124
+
125
+ def __validate_goals(self, goals, is_path_goal):
126
+ goals = sorted(goals, key=lambda x: x.priority)
127
+
128
+ for goal in goals:
129
+ if not isinstance(goal, MinAbsGoal):
130
+ raise Exception(
131
+ "Absolute goal not an instance of MinAbsGoal for goal {}".format(goal)
132
+ )
133
+
134
+ if goal.function_range != (np.nan, np.nan):
135
+ raise Exception(
136
+ "Absolute goal function is only allowed for minimization for goal {}".format(
137
+ goal
138
+ )
139
+ )
140
+
141
+ if goal.order != 1:
142
+ raise Exception(
143
+ "Absolute goal function is only allowed for order = 1 for goal {}".format(goal)
144
+ )
145
+
146
+ if goal.weight <= 0:
147
+ raise Exception(
148
+ "Absolute goal function is only allowed for weight > 0 for goal {}".format(goal)
149
+ )
150
+
151
+ @staticmethod
152
+ def __convert_goals(goals, sym_index, ensemble_size, is_path_goal):
153
+ # Replace absolute minimization goals with a new goal, and some
154
+ # additional hard constraints.
155
+ constraints = [[] for ensemble_member in range(ensemble_size)]
156
+ variables = []
157
+
158
+ # It is easier to modify goals in place, but we do not want to modify
159
+ # the original input list of goals. Make a copy to work with and
160
+ # return when we are done.
161
+ goals = goals.copy()
162
+
163
+ for j, goal in enumerate(goals):
164
+ assert isinstance(goal, MinAbsGoal)
165
+
166
+ abs_variable_name = "abs_{}_{}".format(sym_index, j)
167
+ if is_path_goal:
168
+ abs_variable_name = "path_" + abs_variable_name
169
+
170
+ abs_variable = ca.MX.sym(abs_variable_name, goal.size)
171
+ variables.append(abs_variable)
172
+
173
+ # Set constraints on how the additional variable relates to the
174
+ # original goal function, such that it corresponds to its absolute
175
+ # value when minimizing.
176
+ for ensemble_member in range(ensemble_size):
177
+
178
+ def _constraint_func(
179
+ problem,
180
+ sign,
181
+ abs_variable=abs_variable,
182
+ ensemble_member=ensemble_member,
183
+ goal=goal,
184
+ is_path_goal=is_path_goal,
185
+ ):
186
+ if is_path_goal:
187
+ abs_variable = problem.variable(abs_variable.name())
188
+ else:
189
+ abs_variable = problem.extra_variable(abs_variable.name(), ensemble_member)
190
+
191
+ return (
192
+ abs_variable
193
+ + sign * goal.function(problem, ensemble_member) / goal.function_nominal
194
+ )
195
+
196
+ _pos = functools.partial(_constraint_func, sign=1)
197
+ _neg = functools.partial(_constraint_func, sign=-1)
198
+
199
+ constraints[ensemble_member].append(_GoalConstraint(None, _pos, 0.0, np.inf, False))
200
+ constraints[ensemble_member].append(_GoalConstraint(None, _neg, 0.0, np.inf, False))
201
+
202
+ # Overwrite the original goal, such that it is just a minimization
203
+ # of the additional variable.
204
+ goals[j] = _ConvertedMinAbsGoal(abs_variable, is_path_goal, goal)
205
+
206
+ return goals, constraints, variables
207
+
208
+ def __calculate_seed(self, goals, is_path_goal):
209
+ assert self.__first_run is False
210
+
211
+ seed = [{} for ensemble_member in range(self.ensemble_size)]
212
+
213
+ for goal in goals:
214
+ assert isinstance(goal, _ConvertedMinAbsGoal)
215
+
216
+ for ensemble_member in range(self.ensemble_size):
217
+ if is_path_goal:
218
+ expr = self.map_path_expression(
219
+ goal.orig_goal.function(self, ensemble_member), ensemble_member
220
+ )
221
+ else:
222
+ expr = goal.orig_goal.function(self, ensemble_member)
223
+
224
+ function = ca.Function("f", [self.solver_input], [expr])
225
+ value = np.array(function(self.solver_output))
226
+
227
+ assert value.ndim == 2
228
+
229
+ if goal.size == 1:
230
+ if is_path_goal:
231
+ value = value.ravel()
232
+ else:
233
+ value = value.item()
234
+
235
+ seed[ensemble_member][goal.abs_variable.name()] = np.abs(value)
236
+
237
+ return seed
238
+
239
+ def optimize(self, preprocessing=True, **kwargs):
240
+ # Do pre-processing
241
+ if preprocessing:
242
+ self.pre()
243
+
244
+ goals = self.min_abs_goals()
245
+ path_goals = self.min_abs_path_goals()
246
+
247
+ # Validate goal definitions
248
+ self.__validate_goals(goals, is_path_goal=False)
249
+ self.__validate_goals(path_goals, is_path_goal=True)
250
+
251
+ # List for absolute minimization goals. These will be incrementally
252
+ # filled only just before we need them to.
253
+ self.__problem_constraints = [[] for ensemble_member in range(self.ensemble_size)]
254
+ self.__problem_vars = []
255
+ self.__problem_path_constraints = [[] for ensemble_member in range(self.ensemble_size)]
256
+ self.__problem_path_vars = []
257
+
258
+ # Similar to the above, but these keep track of all auxiliary
259
+ # variables and constraints of priorities we have had (and therefore
260
+ # activated), and those yet to come (and have not yet activated).
261
+ self.__subproblem_constraints = {}
262
+ self.__subproblem_vars = {}
263
+ self.__subproblem_abs_goals = {}
264
+ self.__subproblem_path_constraints = {}
265
+ self.__subproblem_path_vars = {}
266
+ self.__subproblem_path_abs_goals = {}
267
+
268
+ # We want to have consistent naming with GPMixin for our auxiliary
269
+ # variables. We therefore need to loop over all priorities, regardless
270
+ # of whether there are any MinAbsGoals in it or not.
271
+ priorities = {
272
+ int(goal.priority)
273
+ for goal in itertools.chain(goals, path_goals, self.goals(), self.path_goals())
274
+ if not goal.is_empty
275
+ }
276
+
277
+ subproblems = []
278
+ for priority in sorted(priorities):
279
+ subproblems.append(
280
+ (
281
+ priority,
282
+ [
283
+ goal
284
+ for goal in goals
285
+ if int(goal.priority) == priority and not goal.is_empty
286
+ ],
287
+ [
288
+ goal
289
+ for goal in path_goals
290
+ if int(goal.priority) == priority and not goal.is_empty
291
+ ],
292
+ )
293
+ )
294
+
295
+ # Rewrite absolute minimization goals.
296
+ self.__converted_goals = []
297
+ self.__converted_path_goals = []
298
+
299
+ for i, (priority, goals, path_goals) in enumerate(subproblems):
300
+ (
301
+ goals,
302
+ self.__subproblem_constraints[priority],
303
+ self.__subproblem_vars[priority],
304
+ ) = self.__convert_goals(goals, i, self.ensemble_size, False)
305
+
306
+ self.__converted_goals.extend(goals)
307
+ self.__subproblem_abs_goals[priority] = goals
308
+
309
+ (
310
+ path_goals,
311
+ self.__subproblem_path_constraints[priority],
312
+ self.__subproblem_path_vars[priority],
313
+ ) = self.__convert_goals(path_goals, i, self.ensemble_size, True)
314
+
315
+ self.__converted_path_goals.extend(path_goals)
316
+ self.__subproblem_path_abs_goals[priority] = path_goals
317
+
318
+ return super().optimize(**kwargs, preprocessing=False)
319
+
320
+ def priority_started(self, priority):
321
+ super().priority_started(priority)
322
+
323
+ # Enable constraints and auxiliary variables that we need starting
324
+ # from this priority when using GoalProgrammingMixin. When using
325
+ # SinglePassGoalProgrammingMixin, we need to add all constraints from
326
+ # the start.
327
+ if isinstance(self, GoalProgrammingMixin):
328
+ priorities = [priority]
329
+ elif isinstance(self, SinglePassGoalProgrammingMixin):
330
+ if self.__first_run:
331
+ priorities = self.__subproblem_constraints.keys()
332
+ else:
333
+ priorities = []
334
+
335
+ for p in priorities:
336
+ for a, b in zip(self.__problem_constraints, self.__subproblem_constraints[p]):
337
+ a.extend(b)
338
+
339
+ self.__problem_vars.extend(self.__subproblem_vars[p])
340
+
341
+ for a, b in zip(self.__problem_path_constraints, self.__subproblem_path_constraints[p]):
342
+ a.extend(b)
343
+
344
+ self.__problem_path_vars.extend(self.__subproblem_path_vars[p])
345
+
346
+ # Calculate the seed needed for goals/variables introduced in this
347
+ # priority. We can only calculate a seed if this is not the first
348
+ # priority.
349
+ if not self.__first_run and isinstance(self, GoalProgrammingMixin):
350
+ self.__seeds = self.__calculate_seed(self.__subproblem_abs_goals[priority], False)
351
+ self.__path_seeds = self.__calculate_seed(
352
+ self.__subproblem_path_abs_goals[priority], True
353
+ )
354
+
355
+ self.__first_run = False
356
+
357
+ def min_abs_goals(self) -> List[MinAbsGoal]:
358
+ """
359
+ User problem returns list of :py:class:`MinAbsGoal` objects.
360
+
361
+ :returns: A list of goals.
362
+ """
363
+ return []
364
+
365
+ def goals(self):
366
+ goals = super().goals()
367
+ try:
368
+ return goals + self.__converted_goals
369
+ except AttributeError:
370
+ return goals
371
+
372
+ def min_abs_path_goals(self) -> List[MinAbsGoal]:
373
+ """
374
+ User problem returns list of :py:class:`MinAbsGoal` objects.
375
+
376
+ :returns: A list of goals.
377
+ """
378
+ return []
379
+
380
+ def path_goals(self):
381
+ goals = super().path_goals()
382
+ try:
383
+ return goals + self.__converted_path_goals
384
+ except AttributeError:
385
+ return goals