41
41
42
42
# External packages and modules
43
43
import numpy as np
44
+ import scipy as sp
44
45
45
46
from . import statesp
46
47
from .mateqn import care , dare , _check_shape
@@ -71,7 +72,7 @@ def sb03md(n, C, A, U, dico, job='X',fact='N',trana='N',ldwork=None):
71
72
sb03od = None
72
73
73
74
74
- __all__ = ['ctrb' , 'obsv' , 'gram' , 'place' , 'place_varga' , 'lqr' ,
75
+ __all__ = ['ctrb' , 'obsv' , 'gram' , 'place' , 'place_varga' , 'lqr' ,
75
76
'dlqr' , 'acker' , 'create_statefbk_iosystem' ]
76
77
77
78
@@ -600,8 +601,8 @@ def dlqr(*args, **kwargs):
600
601
601
602
# Function to create an I/O sytems representing a state feedback controller
602
603
def create_statefbk_iosystem (
603
- sys , K , integral_action = None , xd_labels = 'xd[{i}]' , ud_labels = 'ud[{i}]' ,
604
- estimator = None , type = 'linear' ):
604
+ sys , gain , integral_action = None , estimator = None , type = None ,
605
+ xd_labels = 'xd[{i}]' , ud_labels = 'ud[{i}]' , gainsched_indices = None ):
605
606
"""Create an I/O system using a (full) state feedback controller
606
607
607
608
This function creates an input/output system that implements a
@@ -617,26 +618,46 @@ def create_statefbk_iosystem(
617
618
feedback gain (eg, from LQR). The function returns the controller
618
619
``ctrl`` and the closed loop systems ``clsys``, both as I/O systems.
619
620
621
+ A gain scheduled controller can also be created, by passing a list of
622
+ gains and a corresponding list of values of a set of scheduling
623
+ variables. In this case, the controller has the form
624
+
625
+ u = ud - K_p(mu) (x - xd) - K_i(mu) integral(C x - C x_d)
626
+
627
+ where mu represents the scheduling variable.
628
+
620
629
Parameters
621
630
----------
622
631
sys : InputOutputSystem
623
632
The I/O system that represents the process dynamics. If no estimator
624
633
is given, the output of this system should represent the full state.
625
634
626
- K : ndarray
627
- The state feedback gain. This matrix defines the gains to be
628
- applied to the system. If ``integral_action`` is None, then the
629
- dimensions of this array should be (sys.ninputs, sys.nstates). If
630
- `integral action` is set to a matrix or a function, then additional
631
- columns represent the gains of the integral states of the
632
- controller.
635
+ gain : ndarray or tuple
636
+ If a array is give, it represents the state feedback gain (K).
637
+ This matrix defines the gains to be applied to the system. If
638
+ ``integral_action`` is None, then the dimensions of this array
639
+ should be (sys.ninputs, sys.nstates). If `integral action` is
640
+ set to a matrix or a function, then additional columns
641
+ represent the gains of the integral states of the controller.
642
+
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`.
633
653
634
654
xd_labels, ud_labels : str or list of str, optional
635
655
Set the name of the signals to use for the desired state and inputs.
636
656
If a single string is specified, it should be a format string using
637
- the variable ``i`` as an index. Otherwise, a list of strings matching
638
- the size of xd and ud, respectively, should be used. Default is
639
- ``'xd[{i}]'`` for xd_labels and ``'xd[{i}]'`` for ud_labels.
657
+ the variable ``i`` as an index. Otherwise, a list of strings
658
+ matching the size of xd and ud, respectively, should be used.
659
+ Default is ``'xd[{i}]'`` for xd_labels and ``'ud[{i}]'`` for
660
+ ud_labels.
640
661
641
662
integral_action : None, ndarray, or func, optional
642
663
If this keyword is specified, the controller can include integral
@@ -650,30 +671,48 @@ def create_statefbk_iosystem(
650
671
``K`` matrix.
651
672
652
673
estimator : InputOutputSystem, optional
653
- If an estimator is provided, using the states of the estimator as
674
+ If an estimator is provided, use the states of the estimator as
654
675
the system inputs for the controller.
655
676
656
- type : 'nonlinear' or 'linear', optional
657
- Set the type of controller to create. The default is a linear
658
- controller implementing the LQR regulator. If the type is 'nonlinear',
659
- a :class:NonlinearIOSystem is created instead, with the gain ``K`` as
660
- a parameter (allowing modifications of the gain at runtime).
677
+ gainsched_indices : list of integers, optional
678
+ If a gain scheduled controller is specified, specify the indices of
679
+ the controller input to use for scheduling the gain. The input to
680
+ the controller is the desired state xd, the desired input ud, and
681
+ either the system state x or the system output y (if an estimator is
682
+ given).
683
+
684
+ type : 'linear' or 'nonlinear', optional
685
+ Set the type of controller to create. The default for a linear gain
686
+ is a linear controller implementing the LQR regulator. If the type
687
+ is 'nonlinear', a :class:NonlinearIOSystem is created instead, with
688
+ the gain ``K`` as a parameter (allowing modifications of the gain at
689
+ runtime). If the gain parameter is a tuple, the a nonlinear,
690
+ gain-scheduled controller is created.
661
691
662
692
Returns
663
693
-------
664
694
ctrl : InputOutputSystem
665
695
Input/output system representing the controller. This system takes
666
- as inputs the desired state xd, the desired input ud, and the system
667
- state x. It outputs the controller action u according to the
668
- formula u = ud - K(x - xd). If the keyword `integral_action` is
669
- specified, then an additional set of integrators is included in the
670
- control system (with the gain matrix K having the integral gains
671
- appended after the state gains).
696
+ 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.
672
704
673
705
clsys : InputOutputSystem
674
706
Input/output system representing the closed loop system. This
675
- systems takes as inputs the desired trajectory (xd, ud) and outputs
676
- the system state x and the applied input u (vertically stacked).
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.
677
716
678
717
"""
679
718
# Make sure that we were passed an I/O system as an input
@@ -709,52 +748,104 @@ def create_statefbk_iosystem(
709
748
C = np .zeros ((0 , sys .nstates ))
710
749
711
750
# Check to make sure that state feedback has the right shape
712
- if not isinstance (K , np .ndarray ) or \
713
- K .shape != (sys .ninputs , estimator .noutputs + nintegrators ):
751
+ if isinstance (gain , np .ndarray ):
752
+ K = gain
753
+ if K .shape != (sys .ninputs , estimator .noutputs + nintegrators ):
754
+ raise ControlArgument (
755
+ f'Control gain must be an array of size { sys .ninputs } '
756
+ f'x { sys .nstates } ' +
757
+ (f'+{ nintegrators } ' if nintegrators > 0 else '' ))
758
+ gainsched = False
759
+
760
+ elif isinstance (gain , tuple ):
761
+ # Check for gain scheduled controller
762
+ gains , points = gain [0 :2 ]
763
+ method = 'nearest' if len (gain ) < 3 else gain [2 ]
764
+
765
+ # Stack gains and points if past as a list
766
+ gains = np .stack (gains )
767
+ points = np .stack (points )
768
+ gainsched = True
769
+
770
+ else :
771
+ raise ControlArgument ("gain must be an array or a tuple" )
772
+
773
+ # Decide on the type of system to create
774
+ if gainsched and type == 'linear' :
714
775
raise ControlArgument (
715
- f'Control gain must be an array of size { sys .ninputs } '
716
- f'x { sys .nstates } ' +
717
- (f'+{ nintegrators } ' if nintegrators > 0 else '' ))
776
+ "type 'linear' not allowed for gain scheduled controller" )
777
+ elif type is None :
778
+ type = 'nonlinear' if gainsched else 'linear'
779
+ elif type not in {'linear' , 'nonlinear' }:
780
+ raise ControlArgument (f"unknown type '{ type } '" )
718
781
719
782
# Figure out the labels to use
720
783
if isinstance (xd_labels , str ):
721
- # Gnerate the list of labels using the argument as a format string
784
+ # Generate the list of labels using the argument as a format string
722
785
xd_labels = [xd_labels .format (i = i ) for i in range (sys .nstates )]
723
786
724
787
if isinstance (ud_labels , str ):
725
- # Gnerate the list of labels using the argument as a format string
788
+ # Generate the list of labels using the argument as a format string
726
789
ud_labels = [ud_labels .format (i = i ) for i in range (sys .ninputs )]
727
790
791
+ # Process gainscheduling variables, if present
792
+ if gainsched :
793
+ # Create a copy of the scheduling variable indices (default = empty)
794
+ gainsched_indices = [] if gainsched_indices is None \
795
+ else list (gainsched_indices )
796
+
797
+ # Make sure the scheduling variable indices are the right length
798
+ if len (gainsched_indices ) != points .shape [1 ]:
799
+ raise ControlArgument (
800
+ "Length of gainsched_indices must match dimension of"
801
+ " scheduling variables" )
802
+
803
+ # TODO: Process scheduling variables
804
+
728
805
# Define the controller system
729
806
if type == 'nonlinear' :
730
807
# Create an I/O system for the state feedback gains
731
- def _control_update (t , x , inputs , params ):
808
+ def _control_update (t , states , inputs , params ):
732
809
# Split input into desired state, nominal input, and current state
733
810
xd_vec = inputs [0 :sys .nstates ]
734
811
x_vec = inputs [- estimator .nstates :]
735
812
736
813
# Compute the integral error in the xy coordinates
737
- return C @ x_vec - C @ xd_vec
814
+ return C @ (x_vec - xd_vec )
815
+
816
+ def _compute_gain (mu , gains_ , points_ ):
817
+ K = np .array ([
818
+ [sp .interpolate .griddata (
819
+ points_ , gains_ [:, i , j ], mu , method = method ).item ()
820
+ for j in range (gains_ .shape [2 ])]
821
+ for i in range (gains_ .shape [1 ])
822
+ ])
823
+ return K
738
824
739
- def _control_output (t , e , z , params ):
740
- K = params .get ('K' )
825
+ def _control_output (t , states , inputs , params ):
826
+ if gainsched :
827
+ mu = inputs [gainsched_indices ]
828
+ K_ = _compute_gain (mu , gains , points )
829
+ else :
830
+ K_ = params .get ('K' )
741
831
742
832
# Split input into desired state, nominal input, and current state
743
- xd_vec = z [0 :sys .nstates ]
744
- ud_vec = z [sys .nstates :sys .nstates + sys .ninputs ]
745
- x_vec = z [- sys .nstates :]
833
+ xd_vec = inputs [0 :sys .nstates ]
834
+ ud_vec = inputs [sys .nstates :sys .nstates + sys .ninputs ]
835
+ x_vec = inputs [- sys .nstates :]
746
836
747
837
# Compute the control law
748
- u = ud_vec - K [:, 0 :sys .nstates ] @ (x_vec - xd_vec )
838
+ u = ud_vec - K_ [:, 0 :sys .nstates ] @ (x_vec - xd_vec )
749
839
if nintegrators > 0 :
750
- u -= K [:, sys .nstates :] @ e
840
+ u -= K_ [:, sys .nstates :] @ states
751
841
752
842
return u
753
843
844
+ params = {} if gainsched else {'K' : K }
754
845
ctrl = NonlinearIOSystem (
755
846
_control_update , _control_output , name = 'control' ,
756
847
inputs = xd_labels + ud_labels + estimator .output_labels ,
757
- outputs = list (sys .input_index .keys ()), params = { 'K' : K } ,
848
+ outputs = list (sys .input_index .keys ()), params = params ,
758
849
states = nintegrators )
759
850
760
851
elif type == 'linear' or type is None :
0 commit comments