3939#
4040# $Id$
4141
42- # External packages and modules
42+ import warnings
43+
4344import numpy as np
4445import scipy as sp
45- import warnings
4646
4747from . import statesp
48- from .mateqn import care , dare , _check_shape
49- from .statesp import StateSpace , _ssmatrix , _convert_to_statespace , ss
48+ from .config import _process_legacy_keyword
49+ from .exception import ControlArgument , ControlDimension , \
50+ ControlNotImplemented , ControlSlycot
51+ from .iosys import _process_indices , _process_labels , isctime , isdtime
5052from .lti import LTI
51- from .iosys import isdtime , isctime , _process_indices , _process_labels
53+ from .mateqn import _check_shape , care , dare
5254from .nlsys import NonlinearIOSystem , interconnect
53- from .exception import ControlSlycot , ControlArgument , ControlDimension , \
54- ControlNotImplemented
55- from .config import _process_legacy_keyword
55+ from .statesp import StateSpace , _convert_to_statespace , _ssmatrix , ss
5656
5757# Make sure we have access to the right slycot routines
5858try :
5959 from slycot import sb03md57
60+
6061 # wrap without the deprecation warning
6162 def sb03md (n , C , A , U , dico , job = 'X' ,fact = 'N' ,trana = 'N' ,ldwork = None ):
6263 ret = sb03md57 (A , U , C , dico , job , fact , trana , ldwork )
@@ -581,8 +582,9 @@ def dlqr(*args, **kwargs):
581582
582583# Function to create an I/O sytems representing a state feedback controller
583584def create_statefbk_iosystem (
584- sys , gain , integral_action = None , estimator = None , controller_type = None ,
585- xd_labels = None , ud_labels = None , gainsched_indices = None ,
585+ sys , gain , feedfwd_gain = None , integral_action = None , estimator = None ,
586+ controller_type = None , xd_labels = None , ud_labels = None ,
587+ feedfwd_pattern = 'trajgen' , gainsched_indices = None ,
586588 gainsched_method = 'linear' , control_indices = None , state_indices = None ,
587589 name = None , inputs = None , outputs = None , states = None , ** kwargs ):
588590 r"""Create an I/O system using a (full) state feedback controller.
@@ -592,7 +594,7 @@ def create_statefbk_iosystem(
592594
593595 .. math:: u = u_d - K_p (x - x_d) - K_i \int(C x - C x_d)
594596
595- It can be called in the form::
597+ by calling
596598
597599 ctrl, clsys = ct.create_statefbk_iosystem(sys, K)
598600
@@ -608,6 +610,18 @@ def create_statefbk_iosystem(
608610
609611 where :math:`\mu` represents the scheduling variable.
610612
613+ Alternatively, a control of the form
614+
615+ .. math:: u = k_f r - K_p x - K_i \int(C x - r)
616+
617+ can be created by calling
618+
619+ ctrl, clsys = ct.create_statefbk_iosystem(
620+ sys, K, kf, feedfwd_pattern='refgain')
621+
622+ In either form, an estimator can also be used to compute the estimated
623+ state from the input and output measurements.
624+
611625 Parameters
612626 ----------
613627 sys : NonlinearIOSystem
@@ -640,6 +654,15 @@ def create_statefbk_iosystem(
640654 ud_labels. These settings can also be overridden using the
641655 `inputs` keyword.
642656
657+ feedfwd_pattern : str, optional
658+ If set to 'refgain', the reference gain design pattern is used to
659+ create the controller instead of the trajectory generation pattern.
660+
661+ feedfwd_gain : array_like, optional
662+ Specify the feedforward gain, `k_f`. Used only for the reference
663+ gain design pattern. If not given and if `sys` is a `StateSpace`
664+ (linear) system, will be computed as -1/(C (A-BK)^{-1}) B.
665+
643666 integral_action : ndarray, optional
644667 If this keyword is specified, the controller can include integral
645668 action in addition to state feedback. The value of the
@@ -841,20 +864,26 @@ def create_statefbk_iosystem(
841864 raise ControlArgument (f"unknown controller_type '{ controller_type } '" )
842865
843866 # Figure out the labels to use
844- xd_labels = _process_labels (
845- xd_labels , 'xd' , ['xd[{i}]' .format (i = i ) for i in range (sys_nstates )])
846- ud_labels = _process_labels (
847- ud_labels , 'ud' , ['ud[{i}]' .format (i = i ) for i in range (sys_ninputs )])
848-
849- # Create the signal and system names
850- if inputs is None :
851- inputs = xd_labels + ud_labels + estimator .output_labels
867+ if feedfwd_pattern == 'trajgen' :
868+ xd_labels = _process_labels (xd_labels , 'xd' , [
869+ 'xd[{i}]' .format (i = i ) for i in range (sys_nstates )])
870+ ud_labels = _process_labels (ud_labels , 'ud' , [
871+ 'ud[{i}]' .format (i = i ) for i in range (sys_ninputs )])
872+
873+ # Create the signal and system names
874+ if inputs is None :
875+ inputs = xd_labels + ud_labels + estimator .output_labels
876+ elif feedfwd_pattern == 'refgain' :
877+ raise NotImplementedError ("reference gain pattern not yet implemented" )
878+ else :
879+ raise NotImplementedError (f"unknown pattern '{ feedfwd_pattern } '" )
880+
852881 if outputs is None :
853882 outputs = [sys .input_labels [i ] for i in control_indices ]
854883 if states is None :
855884 states = nintegrators
856885
857- # Process gainscheduling variables, if present
886+ # Process gain scheduling variables, if present
858887 if gainsched :
859888 # Create a copy of the scheduling variable indices (default = xd)
860889 gainsched_indices = _process_indices (
@@ -897,7 +926,7 @@ def _compute_gain(mu):
897926 return K
898927
899928 # Define the controller system
900- if controller_type == 'nonlinear' :
929+ if controller_type == 'nonlinear' and feedfwd_pattern == 'trajgen' :
901930 # Create an I/O system for the state feedback gains
902931 def _control_update (t , states , inputs , params ):
903932 # Split input into desired state, nominal input, and current state
@@ -931,7 +960,7 @@ def _control_output(t, states, inputs, params):
931960 _control_update , _control_output , name = name , inputs = inputs ,
932961 outputs = outputs , states = states , params = params )
933962
934- elif controller_type == 'iosystem' :
963+ elif controller_type == 'iosystem' and feedfwd_pattern == 'trajgen' :
935964 # Use the passed system to compute feedback compensation
936965 def _control_update (t , states , inputs , params ):
937966 # Split input into desired state, nominal input, and current state
@@ -955,7 +984,7 @@ def _control_output(t, states, inputs, params):
955984 _control_update , _control_output , name = name , inputs = inputs ,
956985 outputs = outputs , states = fbkctrl .state_labels , dt = fbkctrl .dt )
957986
958- elif controller_type == 'linear' or controller_type is None :
987+ elif controller_type in 'linear' and feedfwd_pattern == 'trajgen' :
959988 # Create the matrices implementing the controller
960989 if isctime (sys ):
961990 # Continuous time: integrator
@@ -973,6 +1002,12 @@ def _control_output(t, states, inputs, params):
9731002 A_lqr , B_lqr , C_lqr , D_lqr , dt = sys .dt , name = name ,
9741003 inputs = inputs , outputs = outputs , states = states )
9751004
1005+ elif feedfwd_pattern == 'refgain' :
1006+ if controller_type not in ['linear' , 'iosystem' ]:
1007+ raise ControlArgument (
1008+ "refgain design pattern only supports linear controllers" )
1009+ raise NotImplementedError ("reference gain pattern not yet implemented" )
1010+
9761011 else :
9771012 raise ControlArgument (f"unknown controller_type '{ controller_type } '" )
9781013
@@ -1020,7 +1055,7 @@ def ctrb(A, B, t=None):
10201055 bmat = _ssmatrix (B )
10211056 n = np .shape (amat )[0 ]
10221057 m = np .shape (bmat )[1 ]
1023-
1058+
10241059 if t is None or t > n :
10251060 t = n
10261061
@@ -1042,7 +1077,7 @@ def obsv(A, C, t=None):
10421077 Dynamics and output matrix of the system
10431078 t : None or integer
10441079 maximum time horizon of the controllability matrix, max = A.shape[0]
1045-
1080+
10461081 Returns
10471082 -------
10481083 O : 2D array (or matrix)
@@ -1062,14 +1097,14 @@ def obsv(A, C, t=None):
10621097 cmat = _ssmatrix (C )
10631098 n = np .shape (amat )[0 ]
10641099 p = np .shape (cmat )[0 ]
1065-
1100+
10661101 if t is None or t > n :
10671102 t = n
10681103
10691104 # Construct the observability matrix
10701105 obsv = np .zeros ((t * p , n ))
10711106 obsv [:p , :] = cmat
1072-
1107+
10731108 for k in range (1 , t ):
10741109 obsv [k * p :(k + 1 ) * p , :] = np .dot (obsv [(k - 1 ) * p :k * p , :], amat )
10751110
0 commit comments