@@ -602,7 +602,9 @@ def dlqr(*args, **kwargs):
602
602
# Function to create an I/O sytems representing a state feedback controller
603
603
def create_statefbk_iosystem (
604
604
sys , gain , integral_action = None , estimator = None , type = None ,
605
- xd_labels = 'xd[{i}]' , ud_labels = 'ud[{i}]' , gainsched_indices = None ):
605
+ xd_labels = 'xd[{i}]' , ud_labels = 'ud[{i}]' , gainsched_indices = None ,
606
+ gainsched_method = 'linear' , name = None , inputs = None , outputs = None ,
607
+ states = None ):
606
608
"""Create an I/O system using a (full) state feedback controller
607
609
608
610
This function creates an input/output system that implements a
@@ -640,29 +642,25 @@ def create_statefbk_iosystem(
640
642
set to a matrix or a function, then additional columns
641
643
represent the gains of the integral states of the controller.
642
644
643
- If a tuple is given, then it specifies a gain schedule. The
644
- tuple should be of the form
645
-
646
- (gains, points[, method])
647
-
648
- where gains is a list of gains :math:`K_j` and points is a list of
649
- values :math:`\\ mu_j` at which the gains are computed. If `method`
650
- is specified, it is passed to :func:`scipy.interpolate.griddata` to
651
- specify the method of interpolation. Possible values include
652
- `linear`, `nearest`, and `cubic`.
645
+ If a tuple is given, then it specifies a gain schedule. The tuple
646
+ should be of the form ``(gains, points)`` where gains is a list of
647
+ gains :math:`K_j` and points is a list of values :math:`\\ mu_j` at
648
+ which the gains are computed. The `gainsched_indices` parameter
649
+ should be used to specify the scheduling variables.
653
650
654
651
xd_labels, ud_labels : str or list of str, optional
655
652
Set the name of the signals to use for the desired state and inputs.
656
653
If a single string is specified, it should be a format string using
657
654
the variable ``i`` as an index. Otherwise, a list of strings
658
655
matching the size of xd and ud, respectively, should be used.
659
656
Default is ``'xd[{i}]'`` for xd_labels and ``'ud[{i}]'`` for
660
- ud_labels.
657
+ ud_labels. These settings can also be overriden using the `inputs`
658
+ keyword.
661
659
662
660
integral_action : None, ndarray, or func, optional
663
661
If this keyword is specified, the controller can include integral
664
- action in addition to state feedback. If ``integral_action`` is an
665
- ndarray , it will be multiplied by the current and desired state to
662
+ action in addition to state feedback. If ``integral_action`` is a
663
+ matrix , it will be multiplied by the current and desired state to
666
664
generate the error for the internal integrator states of the control
667
665
law. If ``integral_action`` is a function ``h``, that function will
668
666
be called with the signature h(t, x, u, params) to obtain the
@@ -674,45 +672,59 @@ def create_statefbk_iosystem(
674
672
If an estimator is provided, use the states of the estimator as
675
673
the system inputs for the controller.
676
674
677
- gainsched_indices : list of integers , optional
675
+ gainsched_indices : list of int or str , optional
678
676
If a gain scheduled controller is specified, specify the indices of
679
- the controller input to use for scheduling the gain. The input to
677
+ the controller input to use for scheduling the gain. The input to
680
678
the controller is the desired state xd, the desired input ud, and
681
679
either the system state x or the system output y (if an estimator is
682
- given).
680
+ given). The indices can either be specified as integer offsets into
681
+ the input vector or as strings matching the signal names of the
682
+ input vector.
683
+
684
+ gainsched_method : str, optional
685
+ The method to use for gain scheduling. Possible values include
686
+ `linear` (default), `nearest`, and `cubic`. More information is
687
+ available in :func:`scipy.interpolate.griddata`. For points outside
688
+ of the convex hull of the scheduling points, the gain at the nearest
689
+ point is used.
683
690
684
691
type : 'linear' or 'nonlinear', optional
685
692
Set the type of controller to create. The default for a linear gain
686
693
is a linear controller implementing the LQR regulator. If the type
687
694
is 'nonlinear', a :class:NonlinearIOSystem is created instead, with
688
695
the gain ``K`` as a parameter (allowing modifications of the gain at
689
- runtime). If the gain parameter is a tuple, the a nonlinear,
696
+ runtime). If the gain parameter is a tuple, then a nonlinear,
690
697
gain-scheduled controller is created.
691
698
692
699
Returns
693
700
-------
694
701
ctrl : InputOutputSystem
695
702
Input/output system representing the controller. This system takes
696
703
as inputs the desired state xd, the desired input ud, and either the
697
- system state x or the system output y (if an estimator is given).
698
- It outputs the controller action u according to the formula u = ud -
699
- K(x - xd). If the keyword `integral_action` is specified, then an
700
- additional set of integrators is included in the control system
701
- (with the gain matrix K having the integral gains appended after the
702
- state gains). If a gain scheduled controller is specified, the gain
703
- (proportional and integral) are evaluated using the input mu.
704
+ system state x or the estimated state xhat. It outputs the
705
+ controller action u according to the formula u = ud - K(x - xd). If
706
+ the keyword `integral_action` is specified, then an additional set
707
+ of integrators is included in the control system (with the gain
708
+ matrix K having the integral gains appended after the state gains).
709
+ If a gain scheduled controller is specified, the gain (proportional
710
+ and integral) are evaluated using the scheduling variables specified
711
+ by ``gainsched_indices``.
704
712
705
713
clsys : InputOutputSystem
706
714
Input/output system representing the closed loop system. This
707
- systems takes as inputs the desired trajectory (xd, ud) along with
708
- any unassigned gain scheduling values mu and outputs the system
709
- state x and the applied input u (vertically stacked).
710
-
711
- Notes
712
- -----
713
- 1. If the gain scheduling variable labes are set to the names of system
714
- states, inputs, or outputs or desired states or inputs, then the
715
- scheduling variables are internally connected to those variables.
715
+ systems takes as inputs the desired trajectory (xd, ud) and outputs
716
+ the system state x and the applied input u (vertically stacked).
717
+
718
+ Other Parameters
719
+ ----------------
720
+ inputs, outputs : str, or list of str, optional
721
+ List of strings that name the individual signals of the transformed
722
+ system. If not given, the inputs and outputs are the same as the
723
+ original system.
724
+
725
+ name : string, optional
726
+ System name. If unspecified, a generic name <sys[id]> is generated
727
+ with a unique integer id.
716
728
717
729
"""
718
730
# Make sure that we were passed an I/O system as an input
@@ -759,8 +771,9 @@ def create_statefbk_iosystem(
759
771
760
772
elif isinstance (gain , tuple ):
761
773
# Check for gain scheduled controller
774
+ if len (gain ) != 2 :
775
+ raise ControlArgument ("gain must be a 2-tuple for gain scheduling" )
762
776
gains , points = gain [0 :2 ]
763
- method = 'nearest' if len (gain ) < 3 else gain [2 ]
764
777
765
778
# Stack gains and points if past as a list
766
779
gains = np .stack (gains )
@@ -788,8 +801,13 @@ def create_statefbk_iosystem(
788
801
# Generate the list of labels using the argument as a format string
789
802
ud_labels = [ud_labels .format (i = i ) for i in range (sys .ninputs )]
790
803
791
- # Create the string of labels for the control system
792
- input_labels = xd_labels + ud_labels + estimator .output_labels
804
+ # Create the signal and system names
805
+ if inputs is None :
806
+ inputs = xd_labels + ud_labels + estimator .output_labels
807
+ if outputs is None :
808
+ outputs = list (sys .input_index .keys ())
809
+ if states is None :
810
+ states = nintegrators
793
811
794
812
# Process gainscheduling variables, if present
795
813
if gainsched :
@@ -806,21 +824,22 @@ def create_statefbk_iosystem(
806
824
# Process scheduling variables
807
825
for i , idx in enumerate (gainsched_indices ):
808
826
if isinstance (idx , str ):
809
- gainsched_indices [i ] = input_labels .index (gainsched_indices [i ])
827
+ gainsched_indices [i ] = inputs .index (gainsched_indices [i ])
810
828
811
829
# Create interpolating function
812
- if method == 'nearest' :
830
+ if gainsched_method == 'nearest' :
813
831
_interp = sp .interpolate .NearestNDInterpolator (points , gains )
814
- _nearest = _interp
815
- elif method == 'linear' :
832
+ def _nearest (mu ):
833
+ raise SystemError (f"could not find nearest gain at mu = { mu } " )
834
+ elif gainsched_method == 'linear' :
816
835
_interp = sp .interpolate .LinearNDInterpolator (points , gains )
817
836
_nearest = sp .interpolate .NearestNDInterpolator (points , gains )
818
- elif method == 'cubic' :
837
+ elif gainsched_method == 'cubic' :
819
838
_interp = sp .interpolate .CloughTocher2DInterpolator (points , gains )
820
839
_nearest = sp .interpolate .NearestNDInterpolator (points , gains )
821
840
else :
822
841
raise ControlArgument (
823
- f"unknown gain scheduling method '{ method } '" )
842
+ f"unknown gain scheduling method '{ gainsched_method } '" )
824
843
825
844
def _compute_gain (mu ):
826
845
K = _interp (mu )
@@ -860,9 +879,8 @@ def _control_output(t, states, inputs, params):
860
879
861
880
params = {} if gainsched else {'K' : K }
862
881
ctrl = NonlinearIOSystem (
863
- _control_update , _control_output , name = 'control' ,
864
- inputs = input_labels , outputs = list (sys .input_index .keys ()),
865
- params = params , states = nintegrators )
882
+ _control_update , _control_output , name = name , inputs = inputs ,
883
+ outputs = outputs , states = states , params = params )
866
884
867
885
elif type == 'linear' or type is None :
868
886
# Create the matrices implementing the controller
@@ -879,9 +897,8 @@ def _control_output(t, states, inputs, params):
879
897
])
880
898
881
899
ctrl = ss (
882
- A_lqr , B_lqr , C_lqr , D_lqr , dt = sys .dt , name = 'control' ,
883
- inputs = xd_labels + ud_labels + estimator .output_labels ,
884
- outputs = list (sys .input_index .keys ()), states = nintegrators )
900
+ A_lqr , B_lqr , C_lqr , D_lqr , dt = sys .dt , name = name ,
901
+ inputs = inputs , outputs = outputs , states = states )
885
902
886
903
else :
887
904
raise ControlArgument (f"unknown type '{ type } '" )
@@ -890,7 +907,7 @@ def _control_output(t, states, inputs, params):
890
907
closed = interconnect (
891
908
[sys , ctrl ] if estimator == sys else [sys , ctrl , estimator ],
892
909
name = sys .name + "_" + ctrl .name ,
893
- inplist = xd_labels + ud_labels , inputs = xd_labels + ud_labels ,
910
+ inplist = inputs [: - sys . nstates ] , inputs = inputs [: - sys . nstates ] ,
894
911
outlist = sys .output_labels + sys .input_labels ,
895
912
outputs = sys .output_labels + sys .input_labels
896
913
)
0 commit comments