@@ -603,8 +603,8 @@ def dlqr(*args, **kwargs):
603603def create_statefbk_iosystem (
604604 sys , gain , integral_action = None , estimator = None , controller_type = None ,
605605 xd_labels = 'xd[{i}]' , ud_labels = 'ud[{i}]' , gainsched_indices = None ,
606- gainsched_method = 'linear' , name = None , inputs = None , outputs = None ,
607- states = None , ** kwargs ):
606+ gainsched_method = 'linear' , control_indices = None , state_indices = None ,
607+ name = None , inputs = None , outputs = None , states = None , ** kwargs ):
608608 """Create an I/O system using a (full) state feedback controller
609609
610610 This function creates an input/output system that implements a
@@ -714,6 +714,20 @@ def create_statefbk_iosystem(
714714
715715 Other Parameters
716716 ----------------
717+ control_indices : list of int or str, optional
718+ Specify the indices of the system inputs that should be determined
719+ by the state feedback controller. If not specified, defaults to
720+ the first `m` system inputs, where `m` is determined by the shape
721+ of the gain matrix, with the remaining inputs remaining as inputs
722+ to the overall closed loop system.
723+
724+ state_indices : list of int or str, optional
725+ Specify the indices of the system (or estimator) outputs that
726+ should be used by the state feedback controller. If not specified,
727+ defaults to the first `n` system/estimator outputs, where `n` is
728+ determined by the shape of the gain matrix, with the remaining
729+ outputs remaining as outputs to the overall closed loop system.
730+
717731 inputs, outputs : str, or list of str, optional
718732 List of strings that name the individual signals of the transformed
719733 system. If not given, the inputs and outputs are the same as the
@@ -740,47 +754,63 @@ def create_statefbk_iosystem(
740754 if kwargs :
741755 raise TypeError ("unrecognized keywords: " , str (kwargs ))
742756
743- # See whether we were give an estimator
744- if estimator is not None :
745- # Check to make sure the estimator is the right size
746- if estimator .noutputs != sys .nstates :
747- raise ControlArgument ("Estimator output size must match state" )
748- elif sys .noutputs != sys .nstates :
757+ # Figure out what inputs to the system to use
758+ control_indices = range (sys .ninputs ) if control_indices is None \
759+ else list (control_indices )
760+ for i , idx in enumerate (control_indices ):
761+ if isinstance (idx , str ):
762+ control_indices [i ] = sys .input_labels .index (control_indices [i ])
763+ sys_ninputs = len (control_indices )
764+
765+ # Decide what system is going to pass the states to the controller
766+ if estimator is None :
767+ estimator = sys
768+
769+ # Figure out what outputs (states) from the system/estimator to use
770+ state_indices = range (sys .nstates ) if state_indices is None \
771+ else list (state_indices )
772+ for i , idx in enumerate (state_indices ):
773+ if isinstance (idx , str ):
774+ state_indices [i ] = estimator .state_labels .index (state_indices [i ])
775+ sys_nstates = len (state_indices )
776+
777+ # Make sure the system/estimator states are proper dimension
778+ if estimator .noutputs < sys_nstates :
749779 # If no estimator, make sure that the system has all states as outputs
750- # TODO: check to make sure output map is the identity
751- raise ControlArgument ("System output must be the full state" )
752- else :
780+ raise ControlArgument (
781+ ("system" if estimator == sys else "estimator" ) +
782+ " output must include the full state" )
783+ elif estimator == sys :
753784 # Issue a warning if we can't verify state output
754785 if (isinstance (sys , NonlinearIOSystem ) and sys .outfcn is not None ) or \
755786 (isinstance (sys , StateSpace ) and
756- not (np .all (sys .C == np .eye (sys .nstates )) and np .all (sys .D == 0 ))):
787+ not (np .all (sys .C [np .ix_ (state_indices , state_indices )] ==
788+ np .eye (sys_nstates )) and
789+ np .all (sys .D [state_indices , :] == 0 ))):
757790 warnings .warn ("cannot verify system output is system state" )
758791
759- # Use the system directly instead of an estimator
760- estimator = sys
761-
762792 # See whether we should implement integral action
763793 nintegrators = 0
764794 if integral_action is not None :
765795 if not isinstance (integral_action , np .ndarray ):
766796 raise ControlArgument ("Integral action must pass an array" )
767- elif integral_action .shape [1 ] != sys . nstates :
797+ elif integral_action .shape [1 ] != sys_nstates :
768798 raise ControlArgument (
769799 "Integral gain size must match system state size" )
770800 else :
771801 nintegrators = integral_action .shape [0 ]
772802 C = integral_action
773803 else :
774804 # Create a C matrix with no outputs, just in case update gets called
775- C = np .zeros ((0 , sys . nstates ))
805+ C = np .zeros ((0 , sys_nstates ))
776806
777807 # Check to make sure that state feedback has the right shape
778808 if isinstance (gain , np .ndarray ):
779809 K = gain
780- if K .shape != (sys . ninputs , estimator .noutputs + nintegrators ):
810+ if K .shape != (sys_ninputs , estimator .noutputs + nintegrators ):
781811 raise ControlArgument (
782- f'Control gain must be an array of size { sys . ninputs } '
783- f'x { sys . nstates } ' +
812+ f'control gain must be an array of size { sys_ninputs } '
813+ f' x { sys_nstates } ' +
784814 (f'+{ nintegrators } ' if nintegrators > 0 else '' ))
785815 gainsched = False
786816
@@ -811,24 +841,24 @@ def create_statefbk_iosystem(
811841 # Figure out the labels to use
812842 if isinstance (xd_labels , str ):
813843 # Generate the list of labels using the argument as a format string
814- xd_labels = [xd_labels .format (i = i ) for i in range (sys . nstates )]
844+ xd_labels = [xd_labels .format (i = i ) for i in range (sys_nstates )]
815845
816846 if isinstance (ud_labels , str ):
817847 # Generate the list of labels using the argument as a format string
818- ud_labels = [ud_labels .format (i = i ) for i in range (sys . ninputs )]
848+ ud_labels = [ud_labels .format (i = i ) for i in range (sys_ninputs )]
819849
820850 # Create the signal and system names
821851 if inputs is None :
822852 inputs = xd_labels + ud_labels + estimator .output_labels
823853 if outputs is None :
824- outputs = list ( sys .input_index . keys ())
854+ outputs = [ sys .input_labels [ i ] for i in control_indices ]
825855 if states is None :
826856 states = nintegrators
827857
828858 # Process gainscheduling variables, if present
829859 if gainsched :
830860 # Create a copy of the scheduling variable indices (default = xd)
831- gainsched_indices = range (sys . nstates ) if gainsched_indices is None \
861+ gainsched_indices = range (sys_nstates ) if gainsched_indices is None \
832862 else list (gainsched_indices )
833863
834864 # If points is a 1D list, convert to 2D
@@ -877,8 +907,8 @@ def _compute_gain(mu):
877907 # Create an I/O system for the state feedback gains
878908 def _control_update (t , states , inputs , params ):
879909 # Split input into desired state, nominal input, and current state
880- xd_vec = inputs [0 :sys . nstates ]
881- x_vec = inputs [- estimator . nstates :]
910+ xd_vec = inputs [0 :sys_nstates ]
911+ x_vec = inputs [- sys_nstates :]
882912
883913 # Compute the integral error in the xy coordinates
884914 return C @ (x_vec - xd_vec )
@@ -891,14 +921,14 @@ def _control_output(t, states, inputs, params):
891921 K_ = params .get ('K' )
892922
893923 # Split input into desired state, nominal input, and current state
894- xd_vec = inputs [0 :sys . nstates ]
895- ud_vec = inputs [sys . nstates : sys . nstates + sys . ninputs ]
896- x_vec = inputs [- sys . nstates :]
924+ xd_vec = inputs [0 :sys_nstates ]
925+ ud_vec = inputs [sys_nstates : sys_nstates + sys_ninputs ]
926+ x_vec = inputs [- sys_nstates :]
897927
898928 # Compute the control law
899- u = ud_vec - K_ [:, 0 :sys . nstates ] @ (x_vec - xd_vec )
929+ u = ud_vec - K_ [:, 0 :sys_nstates ] @ (x_vec - xd_vec )
900930 if nintegrators > 0 :
901- u -= K_ [:, sys . nstates :] @ states
931+ u -= K_ [:, sys_nstates :] @ states
902932
903933 return u
904934
@@ -915,10 +945,10 @@ def _control_output(t, states, inputs, params):
915945 else :
916946 # Discrete time: summer
917947 A_lqr = np .eye (C .shape [0 ])
918- B_lqr = np .hstack ([- C , np .zeros ((C .shape [0 ], sys . ninputs )), C ])
919- C_lqr = - K [:, sys . nstates :]
948+ B_lqr = np .hstack ([- C , np .zeros ((C .shape [0 ], sys_ninputs )), C ])
949+ C_lqr = - K [:, sys_nstates :]
920950 D_lqr = np .hstack ([
921- K [:, 0 :sys . nstates ], np .eye (sys . ninputs ), - K [:, 0 :sys . nstates ]
951+ K [:, 0 :sys_nstates ], np .eye (sys_ninputs ), - K [:, 0 :sys_nstates ]
922952 ])
923953
924954 ctrl = ss (
@@ -929,12 +959,16 @@ def _control_output(t, states, inputs, params):
929959 raise ControlArgument (f"unknown controller_type '{ controller_type } '" )
930960
931961 # Define the closed loop system
962+ inplist = inputs [:- sys .nstates ]
963+ input_labels = inputs [:- sys .nstates ]
964+ outlist = sys .output_labels + [sys .input_labels [i ] for i in control_indices ]
965+ output_labels = sys .output_labels + \
966+ [sys .input_labels [i ] for i in control_indices ]
932967 closed = interconnect (
933968 [sys , ctrl ] if estimator == sys else [sys , ctrl , estimator ],
934- name = sys .name + "_" + ctrl .name ,
935- inplist = inputs [:- sys .nstates ], inputs = inputs [:- sys .nstates ],
936- outlist = sys .output_labels + sys .input_labels ,
937- outputs = sys .output_labels + sys .input_labels
969+ name = sys .name + "_" + ctrl .name , add_unused = True ,
970+ inplist = inplist , inputs = input_labels ,
971+ outlist = outlist , outputs = output_labels
938972 )
939973 return ctrl , closed
940974
0 commit comments