1- # obc .py - optimization based control module
1+ # optimal .py - optimization based control module
22#
33# RMM, 11 Feb 2021
44#
55
6- """The :mod:`~control.obc ` module provides support for optimization-based
6+ """The :mod:`~control.optimal ` module provides support for optimization-based
77controllers for nonlinear systems with state and input constraints.
88
99"""
@@ -249,17 +249,26 @@ def _cost_function(self, inputs):
249249
250250 # Trajectory cost
251251 # TODO: vectorize
252- cost = 0
253- for i , t in enumerate (self .time_vector ):
254- if ct .isctime (self .system ):
252+ if ct .isctime (self .system ):
253+ # Evaluate the costs
254+ costs = [self .integral_cost (states [:, i ], inputs [:, i ]) for
255+ i in range (self .time_vector .size )]
256+
257+ # Compute the time intervals
258+ dt = np .diff (self .time_vector )
259+
260+ # Integrate the cost
261+ cost = 0
262+ for i in range (self .time_vector .size - 1 ):
255263 # Approximate the integral using trapezoidal rule
256- if i > 0 :
257- cost += 0.5 * (
258- self .integral_cost (states [:, i - 1 ], inputs [:, i - 1 ]) +
259- self .integral_cost (states [:, i ], inputs [:, i ])) * (
260- self .time_vector [i ] - self .time_vector [i - 1 ])
261- else :
262- cost += self .integral_cost (states [:,i ], inputs [:,i ])
264+ cost += 0.5 * (costs [i ] + costs [i + 1 ]) * dt [i ]
265+
266+ else :
267+ # Sum the integral cost over the time (second) indices
268+ # cost += self.integral_cost(states[:,i], inputs[:,i])
269+ cost = sum (map (
270+ self .integral_cost , np .transpose (states ),
271+ np .transpose (inputs )))
263272
264273 # Terminal cost
265274 if self .terminal_cost is not None :
@@ -526,8 +535,8 @@ def _update(t, x, u, params={}):
526535 inputs = x .reshape ((self .system .ninputs , self .time_vector .size ))
527536 self .initial_guess = np .hstack (
528537 [inputs [:,1 :], inputs [:,- 1 :]]).reshape (- 1 )
529- result = self .compute_trajectory (u )
530- return result .inputs .reshape (- 1 )
538+ res = self .compute_trajectory (u , print_summary = False )
539+ return res .inputs .reshape (- 1 )
531540
532541 def _output (t , x , u , params = {}):
533542 inputs = x .reshape ((self .system .ninputs , self .time_vector .size ))
@@ -541,15 +550,15 @@ def _output(t, x, u, params={}):
541550
542551 # Compute the optimal trajectory from the current state
543552 def compute_trajectory (
544- self , x , squeeze = None , transpose = None , return_x = None ,
545- print_summary = True ):
553+ self , x , squeeze = None , transpose = None , return_states = None ,
554+ print_summary = True , ** kwargs ):
546555 """Compute the optimal input at state x
547556
548557 Parameters
549558 ----------
550559 x : array-like or number, optional
551560 Initial state for the system.
552- return_x : bool, optional
561+ return_states : bool, optional
553562 If True, return the values of the state at each time (default =
554563 False).
555564 squeeze : bool, optional
@@ -564,17 +573,25 @@ def compute_trajectory(
564573
565574 Returns
566575 -------
567- time : array
576+ res : OptimalControlResult
577+ Bundle object with the results of the optimal control problem.
578+ res.success: bool
579+ Boolean flag indicating whether the optimization was successful.
580+ res.time : array
568581 Time values of the input.
569- inputs : array
582+ res. inputs : array
570583 Optimal inputs for the system. If the system is SISO and squeeze
571584 is not True, the array is 1D (indexed by time). If the system is
572585 not SISO or squeeze is False, the array is 2D (indexed by the
573586 output number and time).
574- states : array
575- Time evolution of the state vector (if return_x =True).
587+ res. states : array
588+ Time evolution of the state vector (if return_states =True).
576589
577590 """
591+ # Allow 'return_x` as a synonym for 'return_states'
592+ return_states = ct .config ._get_param (
593+ 'optimal' , 'return_x' , kwargs , return_states , pop = True )
594+
578595 # Store the initial state (for use in _constraint_function)
579596 self .x = x
580597
@@ -585,7 +602,7 @@ def compute_trajectory(
585602
586603 # Process and return the results
587604 return OptimalControlResult (
588- self , res , transpose = transpose , return_x = return_x ,
605+ self , res , transpose = transpose , return_states = return_states ,
589606 squeeze = squeeze , print_summary = print_summary )
590607
591608 # Compute the current input to apply from the current state (MPC style)
@@ -615,8 +632,8 @@ def compute_mpc(self, x, squeeze=None):
615632 if the optimization failed.
616633
617634 """
618- results = self .compute_trajectory (x , squeeze = squeeze )
619- return inputs [:, 0 ] if results .success else None
635+ res = self .compute_trajectory (x , squeeze = squeeze )
636+ return inputs [:, 0 ] if res .success else None
620637
621638
622639# Optimal control result
@@ -640,8 +657,10 @@ class OptimalControlResult(sp.optimize.OptimizeResult):
640657
641658 """
642659 def __init__ (
643- self , ocp , res , return_x = False , print_summary = False ,
660+ self , ocp , res , return_states = False , print_summary = False ,
644661 transpose = None , squeeze = None ):
662+ """Create a OptimalControlResult object"""
663+
645664 # Copy all of the fields we were sent by sp.optimize.minimize()
646665 for key , val in res .items ():
647666 setattr (self , key , val )
@@ -663,7 +682,7 @@ def __init__(
663682 if print_summary :
664683 ocp ._print_statistics ()
665684
666- if return_x and res .success :
685+ if return_states and res .success :
667686 # Simulate the system if we need the state back
668687 _ , _ , states = ct .input_output_response (
669688 ocp .system , ocp .time_vector , inputs , ocp .x , return_x = True )
@@ -673,18 +692,18 @@ def __init__(
673692
674693 retval = _process_time_response (
675694 ocp .system , ocp .time_vector , inputs , states ,
676- transpose = transpose , return_x = return_x , squeeze = squeeze )
695+ transpose = transpose , return_x = return_states , squeeze = squeeze )
677696
678697 self .time = retval [0 ]
679698 self .inputs = retval [1 ]
680699 self .states = None if states is None else retval [2 ]
681700
682701
683702# Compute the input for a nonlinear, (constrained) optimal control problem
684- def compute_optimal_input (
703+ def solve_ocp (
685704 sys , horizon , X0 , cost , constraints = [], terminal_cost = None ,
686705 terminal_constraints = [], initial_guess = None , squeeze = None ,
687- transpose = None , return_x = None , log = False , ** kwargs ):
706+ transpose = None , return_states = None , log = False , ** kwargs ):
688707
689708 """Compute the solution to an optimal control problem
690709
@@ -738,7 +757,7 @@ def compute_optimal_input(
738757 log : bool, optional
739758 If `True`, turn on logging messages (using Python logging module).
740759
741- return_x : bool, optional
760+ return_states : bool, optional
742761 If True, return the values of the state at each time (default = False).
743762
744763 squeeze : bool, optional
@@ -756,15 +775,23 @@ def compute_optimal_input(
756775
757776 Returns
758777 -------
759- time : array
760- Time values of the input or `None` if the optimimation fails.
761- inputs : array
762- Optimal inputs for the system. If the system is SISO and squeeze is not
763- True, the array is 1D (indexed by time). If the system is not SISO or
764- squeeze is False, the array is 2D (indexed by the output number and
765- time).
766- states : array
767- Time evolution of the state vector (if return_x=True).
778+ res : OptimalControlResult
779+ Bundle object with the results of the optimal control problem.
780+
781+ res.success: bool
782+ Boolean flag indicating whether the optimization was successful.
783+
784+ res.time : array
785+ Time values of the input.
786+
787+ res.inputs : array
788+ Optimal inputs for the system. If the system is SISO and squeeze is
789+ not True, the array is 1D (indexed by time). If the system is not
790+ SISO or squeeze is False, the array is 2D (indexed by the output
791+ number and time).
792+
793+ res.states : array
794+ Time evolution of the state vector (if return_states=True).
768795
769796 """
770797 # Set up the optimal control problem
@@ -775,7 +802,7 @@ def compute_optimal_input(
775802
776803 # Solve for the optimal input from the current state
777804 return ocp .compute_trajectory (
778- X0 , squeeze = squeeze , transpose = None , return_x = None )
805+ X0 , squeeze = squeeze , transpose = None , return_states = None )
779806
780807
781808# Create a model predictive controller for an optimal control problem
@@ -803,7 +830,7 @@ def create_mpc_iosystem(
803830
804831 constraints : list of tuples, optional
805832 List of constraints that should hold at each point in the time vector.
806- See :func:`~control.obc.compute_optimal_input ` for more details.
833+ See :func:`~control.optimal.solve_ocp ` for more details.
807834
808835 terminal_cost : callable, optional
809836 Function that returns the terminal cost given the current state
0 commit comments