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
@@ -6,7 +6,6 @@ from collections import OrderedDict
6
6
  from typing import Callable, Dict, List, Union
7
7
 
8
8
  import casadi as ca
9
-
10
9
  import numpy as np
11
10
 
12
11
  from .optimization_problem import OptimizationProblem
@@ -177,11 +176,14 @@ class Goal(metaclass=ABCMeta):
177
176
  * The goal size determines how many goals there are.
178
177
  * The goal function has shape ``(goal size, 1)``.
179
178
  * The function is either minimized or has, possibly various, targets.
180
- * Function nominal can either be an array with as many entries as the goal size or have a single value.
181
- * Function ranges can either be an array with as many entries as the goal size or have a single value.
182
- * In a goal, the target can either be an array with as many entries as the goal size or have a single value.
183
- * In a path goal, the target can also be a Timeseries whose values are either a 1-dimensional vector or have
184
- as many columns as the goal size.
179
+ * Function nominal can either be an array with as many entries as the goal size or have a
180
+ single value.
181
+ * Function ranges can either be an array with as many entries as the goal size or have a
182
+ single value.
183
+ * In a goal, the target can either be an array with as many entries as the goal size or
184
+ have a single value.
185
+ * In a path goal, the target can also be a Timeseries whose values are either a
186
+ 1-dimensional vector or have as many columns as the goal size.
185
187
 
186
188
  **Examples**
187
189
 
@@ -289,12 +291,16 @@ class Goal(metaclass=ABCMeta):
289
291
  """
290
292
  ``True`` if the user goal has min/max bounds.
291
293
  """
292
- return (self.has_target_min or self.has_target_max)
294
+ return self.has_target_min or self.has_target_max
293
295
 
294
296
  @property
295
297
  def is_empty(self) -> bool:
296
- target_min_set = isinstance(self.target_min, Timeseries) or np.any(np.isfinite(self.target_min))
297
- target_max_set = isinstance(self.target_max, Timeseries) or np.any(np.isfinite(self.target_max))
298
+ target_min_set = isinstance(self.target_min, Timeseries) or np.any(
299
+ np.isfinite(self.target_min)
300
+ )
301
+ target_max_set = isinstance(self.target_max, Timeseries) or np.any(
302
+ np.isfinite(self.target_max)
303
+ )
298
304
 
299
305
  if not target_min_set and not target_max_set:
300
306
  # A minimization goal
@@ -313,25 +319,28 @@ class Goal(metaclass=ABCMeta):
313
319
 
314
320
  return min_empty and max_empty
315
321
 
316
- def get_function_key(self, optimization_problem: OptimizationProblem, ensemble_member: int) -> str:
322
+ def get_function_key(
323
+ self, optimization_problem: OptimizationProblem, ensemble_member: int
324
+ ) -> str:
317
325
  """
318
326
  Returns a key string uniquely identifying the goal function. This
319
327
  is used to eliminate linearly dependent constraints from the optimization problem.
320
328
  """
321
- if hasattr(self, 'function_key'):
329
+ if hasattr(self, "function_key"):
322
330
  return self.function_key
323
331
 
324
332
  # This must be deterministic. See RTCTOOLS-485.
325
- if not hasattr(Goal, '_function_key_counter'):
333
+ if not hasattr(Goal, "_function_key_counter"):
326
334
  Goal._function_key_counter = 0
327
- self.function_key = '{}_{}'.format(self.__class__.__name__, Goal._function_key_counter)
335
+ self.function_key = "{}_{}".format(self.__class__.__name__, Goal._function_key_counter)
328
336
  Goal._function_key_counter += 1
329
337
 
330
338
  return self.function_key
331
339
 
332
340
  def __repr__(self) -> str:
333
- return '{}(priority={}, target_min={}, target_max={}, function_range={})'.format(
334
- self.__class__, self.priority, self.target_min, self.target_max, self.function_range)
341
+ return "{}(priority={}, target_min={}, target_max={}, function_range={})".format(
342
+ self.__class__, self.priority, self.target_min, self.target_max, self.function_range
343
+ )
335
344
 
336
345
 
337
346
  class StateGoal(Goal):
@@ -362,9 +371,9 @@ class StateGoal(Goal):
362
371
 
363
372
  my_state_goal = MyStateGoal(optimization_problem)
364
373
 
365
- Note that ``StateGoal`` is a helper class. State goals can also be defined using ``Goal`` as direct base class,
366
- by implementing the ``function`` method and providing the ``function_range`` and ``function_nominal``
367
- class variables manually.
374
+ Note that ``StateGoal`` is a helper class. State goals can also be defined using ``Goal`` as
375
+ direct base class, by implementing the ``function`` method and providing the
376
+ ``function_range`` and ``function_nominal`` class variables manually.
368
377
 
369
378
  """
370
379
 
@@ -380,45 +389,52 @@ class StateGoal(Goal):
380
389
 
381
390
  # Check whether a state has been specified
382
391
  if self.state is None:
383
- raise Exception('Please specify a state.')
392
+ raise Exception("Please specify a state.")
384
393
 
385
394
  # Extract state range from model
386
395
  if self.has_target_bounds:
387
396
  try:
388
397
  self.function_range = optimization_problem.bounds()[self.state]
389
398
  except KeyError:
390
- raise Exception('State {} has no bounds or does not exist in the model.'.format(self.state))
399
+ raise Exception(
400
+ "State {} has no bounds or does not exist in the model.".format(self.state)
401
+ )
391
402
 
392
403
  if self.function_range[0] is None:
393
- raise Exception('Please provide a lower bound for state {}.'.format(self.state))
404
+ raise Exception("Please provide a lower bound for state {}.".format(self.state))
394
405
  if self.function_range[1] is None:
395
- raise Exception('Please provide an upper bound for state {}.'.format(self.state))
406
+ raise Exception("Please provide an upper bound for state {}.".format(self.state))
396
407
 
397
408
  # Extract state nominal from model
398
409
  self.function_nominal = optimization_problem.variable_nominal(self.state)
399
410
 
400
411
  # Set function key
401
412
  canonical, sign = optimization_problem.alias_relation.canonical_signed(self.state)
402
- self.function_key = canonical if sign > 0.0 else '-' + canonical
413
+ self.function_key = canonical if sign > 0.0 else "-" + canonical
403
414
 
404
415
  def function(self, optimization_problem, ensemble_member):
405
416
  return optimization_problem.state(self.state)
406
417
 
407
418
  def __repr__(self):
408
- return '{}(priority={}, state={}, target_min={}, target_max={}, function_range={})'.format(
409
- self.__class__, self.priority, self.state, self.target_min, self.target_max, self.function_range)
419
+ return "{}(priority={}, state={}, target_min={}, target_max={}, function_range={})".format(
420
+ self.__class__,
421
+ self.priority,
422
+ self.state,
423
+ self.target_min,
424
+ self.target_max,
425
+ self.function_range,
426
+ )
410
427
 
411
428
 
412
429
  class _GoalConstraint:
413
-
414
430
  def __init__(
415
- self,
416
- goal: Goal,
417
- function: Callable[[OptimizationProblem], ca.MX],
418
- m: Union[float, np.ndarray, Timeseries],
419
- M: Union[float, np.ndarray, Timeseries],
420
- optimized: bool):
421
-
431
+ self,
432
+ goal: Goal,
433
+ function: Callable[[OptimizationProblem], ca.MX],
434
+ m: Union[float, np.ndarray, Timeseries],
435
+ M: Union[float, np.ndarray, Timeseries],
436
+ optimized: bool,
437
+ ):
422
438
  assert isinstance(m, (float, np.ndarray, Timeseries))
423
439
  assert isinstance(M, (float, np.ndarray, Timeseries))
424
440
  assert type(m) == type(M)
@@ -434,7 +450,7 @@ class _GoalConstraint:
434
450
  self.max = M
435
451
  self.optimized = optimized
436
452
 
437
- def update_bounds(self, other, enforce='self'):
453
+ def update_bounds(self, other, enforce="self"):
438
454
  # NOTE: a.update_bounds(b) is _not_ the same as b.update_bounds(a).
439
455
  # See how the 'enforce' parameter is used.
440
456
 
@@ -456,7 +472,7 @@ class _GoalConstraint:
456
472
 
457
473
  # Ensure new constraint bounds do not loosen or shift
458
474
  # previous bounds due to numerical errors.
459
- if enforce == 'self':
475
+ if enforce == "self":
460
476
  min_ = np.minimum(max_, other_min)
461
477
  max_ = np.maximum(min_, other_max)
462
478
  else:
@@ -476,16 +492,19 @@ class _GoalConstraint:
476
492
 
477
493
 
478
494
  class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
479
-
480
495
  def _gp_n_objectives(self, subproblem_objectives, subproblem_path_objectives, ensemble_member):
481
- return ca.vertcat(*[o(self, ensemble_member) for o in subproblem_objectives]).size1() \
496
+ return (
497
+ ca.vertcat(*[o(self, ensemble_member) for o in subproblem_objectives]).size1()
482
498
  + ca.vertcat(*[o(self, ensemble_member) for o in subproblem_path_objectives]).size1()
499
+ )
483
500
 
484
501
  def _gp_objective(self, subproblem_objectives, n_objectives, ensemble_member):
485
502
  if len(subproblem_objectives) > 0:
486
- acc_objective = ca.sum1(ca.vertcat(*[o(self, ensemble_member) for o in subproblem_objectives]))
503
+ acc_objective = ca.sum1(
504
+ ca.vertcat(*[o(self, ensemble_member) for o in subproblem_objectives])
505
+ )
487
506
 
488
- if self.goal_programming_options()['scale_by_problem_size']:
507
+ if self.goal_programming_options()["scale_by_problem_size"]:
489
508
  acc_objective = acc_objective / n_objectives
490
509
 
491
510
  return acc_objective
@@ -494,9 +513,11 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
494
513
 
495
514
  def _gp_path_objective(self, subproblem_path_objectives, n_objectives, ensemble_member):
496
515
  if len(subproblem_path_objectives) > 0:
497
- acc_objective = ca.sum1(ca.vertcat(*[o(self, ensemble_member) for o in subproblem_path_objectives]))
516
+ acc_objective = ca.sum1(
517
+ ca.vertcat(*[o(self, ensemble_member) for o in subproblem_path_objectives])
518
+ )
498
519
 
499
- if self.goal_programming_options()['scale_by_problem_size']:
520
+ if self.goal_programming_options()["scale_by_problem_size"]:
500
521
  # Objective is already divided by number of active time steps
501
522
  # at this point when `scale_by_problem_size` is set.
502
523
  acc_objective = acc_objective / n_objectives
@@ -540,8 +561,7 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
540
561
 
541
562
  m, M = None, None
542
563
  if isinstance(g.target_min, Timeseries):
543
- m = self.interpolate(
544
- times, g.target_min.times, g.target_min.values, -np.inf, -np.inf)
564
+ m = self.interpolate(times, g.target_min.times, g.target_min.values, -np.inf, -np.inf)
545
565
  if m.ndim > 1:
546
566
  m = m.transpose()
547
567
  elif isinstance(g.target_min, np.ndarray) and target_shape:
@@ -551,8 +571,7 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
551
571
  else:
552
572
  m = np.array([g.target_min]).transpose()
553
573
  if isinstance(g.target_max, Timeseries):
554
- M = self.interpolate(
555
- times, g.target_max.times, g.target_max.values, np.inf, np.inf)
574
+ M = self.interpolate(times, g.target_max.times, g.target_max.values, np.inf, np.inf)
556
575
  if M.ndim > 1:
557
576
  M = M.transpose()
558
577
  elif isinstance(g.target_max, np.ndarray) and target_shape:
@@ -570,7 +589,7 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
570
589
  if g.size > 1:
571
590
  assert m.shape == (g.size, 1 if target_shape is None else target_shape)
572
591
  else:
573
- assert m.shape == (1 if target_shape is None else target_shape, )
592
+ assert m.shape == (1 if target_shape is None else target_shape,)
574
593
  assert m.shape == M.shape
575
594
 
576
595
  return m, M
@@ -623,7 +642,9 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
623
642
  raise Exception("Goal weight should be positive for goal {}".format(goal))
624
643
  else:
625
644
  if goal.function_range != (np.nan, np.nan):
626
- raise Exception("Specifying function range not allowed for goal {}".format(goal))
645
+ raise Exception(
646
+ "Specifying function range not allowed for goal {}".format(goal)
647
+ )
627
648
 
628
649
  if not is_path_goal:
629
650
  if isinstance(goal.target_min, Timeseries):
@@ -636,15 +657,25 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
636
657
  except ValueError:
637
658
  raise Exception("Priority of not int or castable to int for goal {}".format(goal))
638
659
 
639
- if options['keep_soft_constraints']:
660
+ if options["keep_soft_constraints"]:
640
661
  if goal.relaxation != 0.0:
641
- raise Exception("Relaxation not allowed with `keep_soft_constraints` for goal {}".format(goal))
662
+ raise Exception(
663
+ "Relaxation not allowed with `keep_soft_constraints` for goal {}".format(
664
+ goal
665
+ )
666
+ )
642
667
  if goal.violation_timeseries_id is not None:
643
668
  raise Exception(
644
- "Violation timeseries id not allowed with `keep_soft_constraints` for goal {}".format(goal))
669
+ "Violation timeseries id not allowed with "
670
+ "`keep_soft_constraints` for goal {}".format(goal)
671
+ )
645
672
  else:
646
673
  if goal.size > 1:
647
- raise Exception("Option `keep_soft_constraints` needs to be set for vector goal {}".format(goal))
674
+ raise Exception(
675
+ "Option `keep_soft_constraints` needs to be set for vector goal {}".format(
676
+ goal
677
+ )
678
+ )
648
679
 
649
680
  if goal.critical and goal.size > 1:
650
681
  raise Exception("Vector goal cannot be critical for goal {}".format(goal))
@@ -657,7 +688,7 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
657
688
  # Check consistency and monotonicity of goals. Scalar target min/max
658
689
  # of normal goals are also converted to arrays to unify checks with
659
690
  # path goals.
660
- if options['check_monotonicity']:
691
+ if options["check_monotonicity"]:
661
692
  for e in range(self.ensemble_size):
662
693
  # Store the previous goal of a certain function key we
663
694
  # encountered, such that we can compare to it.
@@ -672,21 +703,25 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
672
703
  goal_m, goal_M = self._gp_min_max_arrays(goal, target_shape)
673
704
  other_m, other_M = self._gp_min_max_arrays(prev, target_shape)
674
705
 
675
- indices = np.where(np.logical_not(np.logical_or(
676
- np.isnan(goal_m), np.isnan(other_m))))
706
+ indices = np.where(
707
+ np.logical_not(np.logical_or(np.isnan(goal_m), np.isnan(other_m)))
708
+ )
677
709
  if goal.has_target_min:
678
710
  if np.any(goal_m[indices] < other_m[indices]):
679
711
  raise Exception(
680
- 'Target minimum of goal {} must be greater or equal than '
681
- 'target minimum of goal {}.'.format(goal, prev))
712
+ "Target minimum of goal {} must be greater or equal than "
713
+ "target minimum of goal {}.".format(goal, prev)
714
+ )
682
715
 
683
- indices = np.where(np.logical_not(np.logical_or(
684
- np.isnan(goal_M), np.isnan(other_M))))
716
+ indices = np.where(
717
+ np.logical_not(np.logical_or(np.isnan(goal_M), np.isnan(other_M)))
718
+ )
685
719
  if goal.has_target_max:
686
720
  if np.any(goal_M[indices] > other_M[indices]):
687
721
  raise Exception(
688
- 'Target maximum of goal {} must be less or equal than '
689
- 'target maximum of goal {}'.format(goal, prev))
722
+ "Target maximum of goal {} must be less or equal than "
723
+ "target maximum of goal {}".format(goal, prev)
724
+ )
690
725
 
691
726
  for goal in goals:
692
727
  goal_m, goal_M = self._gp_min_max_arrays(goal, target_shape)
@@ -694,35 +729,42 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
694
729
  goal_ub = np.broadcast_to(goal.function_range[1], goal_M.shape[::-1]).transpose()
695
730
 
696
731
  if goal.has_target_min and goal.has_target_max:
697
- indices = np.where(np.logical_not(np.logical_or(
698
- np.isnan(goal_m), np.isnan(goal_M))))
732
+ indices = np.where(
733
+ np.logical_not(np.logical_or(np.isnan(goal_m), np.isnan(goal_M)))
734
+ )
699
735
 
700
736
  if np.any(goal_m[indices] > goal_M[indices]):
701
- raise Exception("Target minimum exceeds target maximum for goal {}".format(goal))
737
+ raise Exception(
738
+ "Target minimum exceeds target maximum for goal {}".format(goal)
739
+ )
702
740
 
703
741
  if goal.has_target_min and not goal.critical:
704
742
  indices = np.where(np.isfinite(goal_m))
705
743
  if np.any(goal_m[indices] <= goal_lb[indices]):
706
744
  raise Exception(
707
- 'Target minimum should be greater than the lower bound of the function range for goal {}'
708
- .format(goal))
745
+ "Target minimum should be greater than the lower bound "
746
+ "of the function range for goal {}".format(goal)
747
+ )
709
748
  if np.any(goal_m[indices] > goal_ub[indices]):
710
749
  raise Exception(
711
- 'Target minimum should not be greater than the upper bound of the function range for goal {}'
712
- .format(goal))
750
+ "Target minimum should not be greater than the upper bound "
751
+ "of the function range for goal {}".format(goal)
752
+ )
713
753
  if goal.has_target_max and not goal.critical:
714
754
  indices = np.where(np.isfinite(goal_M))
715
755
  if np.any(goal_M[indices] >= goal_ub[indices]):
716
756
  raise Exception(
717
- 'Target maximum should be smaller than the upper bound of the function range for goal {}'
718
- .format(goal))
757
+ "Target maximum should be smaller than the upper bound "
758
+ "of the function range for goal {}".format(goal)
759
+ )
719
760
  if np.any(goal_M[indices] < goal_lb[indices]):
720
761
  raise Exception(
721
- 'Target maximum should not be smaller than the lower bound of the function range for goal {}'
722
- .format(goal))
762
+ "Target maximum should not be smaller than the lower bound "
763
+ "of the function range for goal {}".format(goal)
764
+ )
723
765
 
724
766
  if goal.relaxation < 0.0:
725
- raise Exception('Relaxation of goal {} should be a nonnegative value'.format(goal))
767
+ raise Exception("Relaxation of goal {} should be a nonnegative value".format(goal))
726
768
 
727
769
  def _gp_goal_constraints(self, goals, sym_index, options, is_path_goal):
728
770
  """
@@ -772,10 +814,14 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
772
814
 
773
815
  if isinstance(goal.target_min, Timeseries):
774
816
  target_min = Timeseries(goal.target_min.times, goal.target_min.values)
775
- inds = np.logical_or(np.isnan(target_min.values), np.isneginf(target_min.values))
817
+ inds = np.logical_or(
818
+ np.isnan(target_min.values), np.isneginf(target_min.values)
819
+ )
776
820
  target_min.values[inds] = -sys.float_info.max
777
821
  n_times = len(goal.target_min.times)
778
- target_min_slice_inds = ~np.all(np.broadcast_to(inds.transpose(), (goal.size, n_times)), axis=1)
822
+ target_min_slice_inds = ~np.all(
823
+ np.broadcast_to(inds.transpose(), (goal.size, n_times)), axis=1
824
+ )
779
825
  elif isinstance(goal.target_min, np.ndarray):
780
826
  target_min = goal.target_min.copy()
781
827
  inds = np.logical_or(np.isnan(target_min), np.isneginf(target_min))
@@ -795,10 +841,14 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
795
841
 
796
842
  if isinstance(goal.target_max, Timeseries):
797
843
  target_max = Timeseries(goal.target_max.times, goal.target_max.values)
798
- inds = np.logical_or(np.isnan(target_max.values), np.isposinf(target_max.values))
844
+ inds = np.logical_or(
845
+ np.isnan(target_max.values), np.isposinf(target_max.values)
846
+ )
799
847
  target_max.values[inds] = sys.float_info.max
800
848
  n_times = len(goal.target_max.times)
801
- target_max_slice_inds = ~np.all(np.broadcast_to(inds.transpose(), (goal.size, n_times)), axis=1)
849
+ target_max_slice_inds = ~np.all(
850
+ np.broadcast_to(inds.transpose(), (goal.size, n_times)), axis=1
851
+ )
802
852
  elif isinstance(goal.target_max, np.ndarray):
803
853
  target_max = goal.target_max.copy()
804
854
  inds = np.logical_or(np.isnan(target_max), np.isposinf(target_max))
@@ -813,11 +863,13 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
813
863
 
814
864
  # Make objective for soft constraints and minimization goals
815
865
  if not goal.critical:
816
- if hasattr(goal, '_objective_func'):
866
+ if hasattr(goal, "_objective_func"):
817
867
  _objective_func = goal._objective_func
818
868
  elif goal.has_target_bounds:
819
- if is_path_goal and options['scale_by_problem_size']:
820
- goal_m, goal_M = self._gp_min_max_arrays(goal, target_shape=len(self.times()))
869
+ if is_path_goal and options["scale_by_problem_size"]:
870
+ goal_m, goal_M = self._gp_min_max_arrays(
871
+ goal, target_shape=len(self.times())
872
+ )
821
873
  goal_active = np.isfinite(goal_m) | np.isfinite(goal_M)
822
874
  n_active = np.sum(goal_active.astype(int), axis=-1)
823
875
  # Avoid possible division by zero if goal is inactive
@@ -825,23 +877,34 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
825
877
  else:
826
878
  n_active = 1
827
879
 
828
- def _objective_func(problem, ensemble_member,
829
- goal=goal, epsilon=epsilon, is_path_goal=is_path_goal,
830
- n_active=n_active):
880
+ def _objective_func(
881
+ problem,
882
+ ensemble_member,
883
+ goal=goal,
884
+ epsilon=epsilon,
885
+ is_path_goal=is_path_goal,
886
+ n_active=n_active,
887
+ ):
831
888
  if is_path_goal:
832
889
  epsilon = problem.variable(epsilon.name())
833
890
  else:
834
891
  epsilon = problem.extra_variable(epsilon.name(), ensemble_member)
835
892
 
836
893
  return goal.weight * ca.constpow(epsilon, goal.order) / n_active
894
+
837
895
  else:
838
- if is_path_goal and options['scale_by_problem_size']:
896
+ if is_path_goal and options["scale_by_problem_size"]:
839
897
  n_active = len(self.times())
840
898
  else:
841
899
  n_active = 1
842
900
 
843
- def _objective_func(problem, ensemble_member, goal=goal, is_path_goal=is_path_goal,
844
- n_active=n_active):
901
+ def _objective_func(
902
+ problem,
903
+ ensemble_member,
904
+ goal=goal,
905
+ is_path_goal=is_path_goal,
906
+ n_active=n_active,
907
+ ):
845
908
  f = goal.function(problem, ensemble_member) / goal.function_nominal
846
909
  return goal.weight * ca.constpow(f, goal.order) / n_active
847
910
 
@@ -852,15 +915,23 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
852
915
  if goal.critical:
853
916
  for ensemble_member in range(self.ensemble_size):
854
917
  constraint = self._gp_goal_hard_constraint(
855
- goal, epsilon, None, ensemble_member, options, is_path_goal)
918
+ goal, epsilon, None, ensemble_member, options, is_path_goal
919
+ )
856
920
  hard_constraints[ensemble_member].append(constraint)
857
921
  else:
858
922
  for ensemble_member in range(self.ensemble_size):
859
923
  # We use a violation variable formulation, with the violation
860
924
  # variables epsilon bounded between 0 and 1.
861
- def _soft_constraint_func(problem, target, bound, inds,
862
- goal=goal, epsilon=epsilon, ensemble_member=ensemble_member,
863
- is_path_constraint=is_path_goal):
925
+ def _soft_constraint_func(
926
+ problem,
927
+ target,
928
+ bound,
929
+ inds,
930
+ goal=goal,
931
+ epsilon=epsilon,
932
+ ensemble_member=ensemble_member,
933
+ is_path_constraint=is_path_goal,
934
+ ):
864
935
  if is_path_constraint:
865
936
  target = problem.variable(target)
866
937
  eps = problem.variable(epsilon.name())
@@ -873,16 +944,19 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
873
944
  f = goal.function(problem, ensemble_member)
874
945
  nominal = goal.function_nominal
875
946
 
876
- return ca.if_else(ca.fabs(target) < sys.float_info.max,
877
- (f - eps * (bound - target) - target) / nominal,
878
- 0.0)[inds]
947
+ return ca.if_else(
948
+ ca.fabs(target) < sys.float_info.max,
949
+ (f - eps * (bound - target) - target) / nominal,
950
+ 0.0,
951
+ )[inds]
879
952
 
880
953
  if goal.has_target_min and np.any(target_min_slice_inds):
881
954
  _f = functools.partial(
882
955
  _soft_constraint_func,
883
956
  target=min_variable,
884
957
  bound=goal.function_range[0],
885
- inds=target_min_slice_inds)
958
+ inds=target_min_slice_inds,
959
+ )
886
960
  constraint = _GoalConstraint(goal, _f, 0.0, np.inf, False)
887
961
  soft_constraints[ensemble_member].append(constraint)
888
962
  if goal.has_target_max and np.any(target_max_slice_inds):
@@ -890,13 +964,16 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
890
964
  _soft_constraint_func,
891
965
  target=max_variable,
892
966
  bound=goal.function_range[1],
893
- inds=target_max_slice_inds)
967
+ inds=target_max_slice_inds,
968
+ )
894
969
  constraint = _GoalConstraint(goal, _f, -np.inf, 0.0, False)
895
970
  soft_constraints[ensemble_member].append(constraint)
896
971
 
897
972
  return epsilons, objectives, soft_constraints, hard_constraints, extra_constants
898
973
 
899
- def _gp_goal_hard_constraint(self, goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal):
974
+ def _gp_goal_hard_constraint(
975
+ self, goal, epsilon, existing_constraint, ensemble_member, options, is_path_goal
976
+ ):
900
977
  if not is_path_goal:
901
978
  epsilon = epsilon[:1]
902
979
 
@@ -905,21 +982,29 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
905
982
  if goal.has_target_bounds:
906
983
  # We use a violation variable formulation, with the violation
907
984
  # variables epsilon bounded between 0 and 1.
908
- m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(epsilon, np.inf, dtype=np.float64)
985
+ m, M = np.full_like(epsilon, -np.inf, dtype=np.float64), np.full_like(
986
+ epsilon, np.inf, dtype=np.float64
987
+ )
909
988
 
910
989
  # A function range does not have to be specified for critical
911
990
  # goals. Avoid multiplying with NaN in that case.
912
991
  if goal.has_target_min:
913
- m = (epsilon * ((goal.function_range[0] - goal_m) if not goal.critical else 0.0)
914
- + goal_m - goal.relaxation) / goal.function_nominal
992
+ m = (
993
+ epsilon * ((goal.function_range[0] - goal_m) if not goal.critical else 0.0)
994
+ + goal_m
995
+ - goal.relaxation
996
+ ) / goal.function_nominal
915
997
  if goal.has_target_max:
916
- M = (epsilon * ((goal.function_range[1] - goal_M) if not goal.critical else 0.0)
917
- + goal_M + goal.relaxation) / goal.function_nominal
998
+ M = (
999
+ epsilon * ((goal.function_range[1] - goal_M) if not goal.critical else 0.0)
1000
+ + goal_M
1001
+ + goal.relaxation
1002
+ ) / goal.function_nominal
918
1003
 
919
1004
  if goal.has_target_min and goal.has_target_max:
920
1005
  # Avoid comparing with NaN
921
1006
  inds = ~(np.isnan(m) | np.isnan(M))
922
- inds[inds] &= np.abs(m[inds] - M[inds]) < options['equality_threshold']
1007
+ inds[inds] &= np.abs(m[inds] - M[inds]) < options["equality_threshold"]
923
1008
  if np.any(inds):
924
1009
  avg = 0.5 * (m + M)
925
1010
  m[inds] = M[inds] = avg[inds]
@@ -927,31 +1012,35 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
927
1012
  m[~np.isfinite(goal_m)] = -np.inf
928
1013
  M[~np.isfinite(goal_M)] = np.inf
929
1014
 
930
- inds = epsilon > options['violation_tolerance']
1015
+ inds = epsilon > options["violation_tolerance"]
931
1016
  if np.any(inds):
932
1017
  if is_path_goal:
933
- expr = self.map_path_expression(goal.function(self, ensemble_member), ensemble_member)
1018
+ expr = self.map_path_expression(
1019
+ goal.function(self, ensemble_member), ensemble_member
1020
+ )
934
1021
  else:
935
1022
  expr = goal.function(self, ensemble_member)
936
1023
 
937
- function = ca.Function('f', [self.solver_input], [expr])
1024
+ function = ca.Function("f", [self.solver_input], [expr])
938
1025
  value = np.array(function(self.solver_output))
939
1026
 
940
1027
  m[inds] = (value - goal.relaxation) / goal.function_nominal
941
1028
  M[inds] = (value + goal.relaxation) / goal.function_nominal
942
1029
 
943
- m -= options['constraint_relaxation']
944
- M += options['constraint_relaxation']
1030
+ m -= options["constraint_relaxation"]
1031
+ M += options["constraint_relaxation"]
945
1032
  else:
946
1033
  # Epsilon encodes the position within the function range.
947
- if options['fix_minimized_values'] and goal.relaxation == 0.0:
1034
+ if options["fix_minimized_values"] and goal.relaxation == 0.0:
948
1035
  m = epsilon / goal.function_nominal
949
1036
  M = epsilon / goal.function_nominal
950
1037
  self.check_collocation_linearity = False
951
1038
  self.linear_collocation = False
952
1039
  else:
953
1040
  m = -np.inf * np.ones(epsilon.shape)
954
- M = (epsilon + goal.relaxation) / goal.function_nominal + options['constraint_relaxation']
1041
+ M = (epsilon + goal.relaxation) / goal.function_nominal + options[
1042
+ "constraint_relaxation"
1043
+ ]
955
1044
 
956
1045
  if is_path_goal:
957
1046
  m = Timeseries(self.times(), m)
@@ -963,13 +1052,17 @@ class _GoalProgrammingMixinBase(OptimizationProblem, metaclass=ABCMeta):
963
1052
  constraint = _GoalConstraint(
964
1053
  goal,
965
1054
  lambda problem, ensemble_member=ensemble_member, goal=goal: (
966
- goal.function(problem, ensemble_member) / goal.function_nominal),
967
- m, M, True)
1055
+ goal.function(problem, ensemble_member) / goal.function_nominal
1056
+ ),
1057
+ m,
1058
+ M,
1059
+ True,
1060
+ )
968
1061
 
969
1062
  # Epsilon is fixed. Override previous {min,max} constraints for this
970
1063
  # state.
971
1064
  if existing_constraint:
972
- constraint.update_bounds(existing_constraint, enforce='other')
1065
+ constraint.update_bounds(existing_constraint, enforce="other")
973
1066
 
974
1067
  return constraint
975
1068