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 :
5959from slycot import sb03md57
60+
6061# wrap without the deprecation warning
6162def sb03md (n ,C ,A ,U ,dico ,job = 'X' ,fact = 'N' ,trana = 'N' ,ldwork = None ):
6263ret = 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 ,ref_labels = None ,
587+ feedfwd_pattern = 'trajgen' ,gainsched_indices = None ,
586588gainsched_method = 'linear' ,control_indices = None ,state_indices = None ,
587589name = None ,inputs = None ,outputs = None ,states = None ,** kwargs ):
588590r"""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 controller 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
@@ -631,14 +645,15 @@ def create_statefbk_iosystem(
631645 If an I/O system is given, the error e = x - xd is passed to the
632646 system and the output is used as the feedback compensation term.
633647
634- xd_labels, ud_labels : str or list of str, optional
635- Set the name of the signals to use for the desired state and
636- inputs. If a single string is specified, it should be a format
637- string using the variable `i` as an index. Otherwise, a list of
638- strings matching the size of `x_d` and `u_d`, respectively, should
639- be used. Default is "xd[{i}]" for xd_labels and "ud[{i}]" for
640- ud_labels. These settings can also be overridden using the
641- `inputs` keyword.
648+ feedfwd_gain : array_like, optional
649+ Specify the feedforward gain, `k_f`. Used only for the reference
650+ gain design pattern. If not given and if `sys` is a `StateSpace`
651+ (linear) system, will be computed as -1/(C (A-BK)^{-1}) B.
652+
653+ feedfwd_pattern : str, optional
654+ If set to 'refgain', the reference gain design pattern is used to
655+ create the controller instead of the trajectory generation
656+ ('trajgen') pattern.
642657
643658 integral_action : ndarray, optional
644659 If this keyword is specified, the controller can include integral
@@ -680,17 +695,19 @@ def create_statefbk_iosystem(
680695 Returns
681696 -------
682697 ctrl : NonlinearIOSystem
683- Input/output system representing the controller. This system
684- takes as inputs the desired state `x_d`, the desired input
685- `u_d`, and either the system state `x` or the estimated state
686- `xhat`. It outputs the controller action `u` according to the
687- formula `u = u_d - K(x - x_d)`. If the keyword
688- `integral_action` is specified, then an additional set of
689- integrators is included in the control system (with the gain
690- matrix `K` having the integral gains appended after the state
691- gains). If a gain scheduled controller is specified, the gain
692- (proportional and integral) are evaluated using the scheduling
693- variables specified by `gainsched_indices`.
698+ Input/output system representing the controller. For the 'trajgen'
699+ design pattern (default), this system takes as inputs the desired
700+ state `x_d`, the desired input `u_d`, and either the system state
701+ `x` or the estimated state `xhat`. It outputs the controller
702+ action `u` according to the formula `u = u_d - K(x - x_d)`. For
703+ the 'refgain' design pattern, the system takes as inputs the
704+ reference input `r` and the system or estimated state. If the
705+ keyword `integral_action` is specified, then an additional set of
706+ integrators is included in the control system (with the gain matrix
707+ `K` having the integral gains appended after the state gains). If
708+ a gain scheduled controller is specified, the gain (proportional
709+ and integral) are evaluated using the scheduling variables
710+ specified by `gainsched_indices`.
694711
695712 clsys : NonlinearIOSystem
696713 Input/output system representing the closed loop system. This
@@ -716,6 +733,15 @@ def create_statefbk_iosystem(
716733 specified as either integer offsets or as estimator/system output
717734 signal names. If not specified, defaults to the system states.
718735
736+ xd_labels, ud_labels, ref_labels : str or list of str, optional
737+ Set the name of the signals to use for the desired state and inputs
738+ or the reference inputs (for the 'refgain' design pattern). If a
739+ single string is specified, it should be a format string using the
740+ variable `i` as an index. Otherwise, a list of strings matching
741+ the size of `x_d` and `u_d`, respectively, should be used. Default
742+ is "xd[{i}]" for xd_labels and "ud[{i}]" for ud_labels. These
743+ settings can also be overridden using the `inputs` keyword.
744+
719745 inputs, outputs, states : str, or list of str, optional
720746 List of strings that name the individual signals of the transformed
721747 system. If not given, the inputs, outputs, and states are the same
@@ -753,6 +779,11 @@ def create_statefbk_iosystem(
753779if kwargs :
754780raise TypeError ("unrecognized keywords: " ,str (kwargs ))
755781
782+ # Check for consistency of positional parameters
783+ if feedfwd_gain is not None and feedfwd_pattern != 'refgain' :
784+ raise ControlArgument (
785+ "feedfwd_gain specified but feedfwd_pattern != 'refgain'" )
786+
756787# Figure out what inputs to the system to use
757788control_indices = _process_indices (
758789control_indices ,'control' ,sys .input_labels ,sys .ninputs )
@@ -812,14 +843,18 @@ def create_statefbk_iosystem(
812843# Check for gain scheduled controller
813844if len (gain )!= 2 :
814845raise ControlArgument ("gain must be a 2-tuple for gain scheduling" )
846+ elif feedfwd_pattern != 'trajgen' :
847+ raise NotImplementedError (
848+ "Gain scheduling is not implemented for pattern "
849+ f"'{ feedfwd_pattern } '" )
815850gains ,points = gain [0 :2 ]
816851
817852# Stack gains and points if past as a list
818853gains = np .stack (gains )
819854points = np .stack (points )
820855gainsched = True
821856
822- elif isinstance (gain ,NonlinearIOSystem ):
857+ elif isinstance (gain ,NonlinearIOSystem )and feedfwd_pattern != 'refgain' :
823858if controller_type not in ['iosystem' ,None ]:
824859raise ControlArgument (
825860f"incompatible controller type '{ controller_type } '" )
@@ -841,20 +876,29 @@ def create_statefbk_iosystem(
841876raise ControlArgument (f"unknown controller_type '{ controller_type } '" )
842877
843878# 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
879+ if feedfwd_pattern == 'trajgen' :
880+ xd_labels = _process_labels (xd_labels ,'xd' , [
881+ 'xd[{i}]' .format (i = i )for i in range (sys_nstates )])
882+ ud_labels = _process_labels (ud_labels ,'ud' , [
883+ 'ud[{i}]' .format (i = i )for i in range (sys_ninputs )])
884+
885+ # Create the signal and system names
886+ if inputs is None :
887+ inputs = xd_labels + ud_labels + estimator .output_labels
888+ elif feedfwd_pattern == 'refgain' :
889+ ref_labels = _process_labels (ref_labels ,'r' , [
890+ f'r[{ i } ]' for i in range (sys_ninputs )])
891+ if inputs is None :
892+ inputs = ref_labels + estimator .output_labels
893+ else :
894+ raise NotImplementedError (f"unknown pattern '{ feedfwd_pattern } '" )
895+
852896if outputs is None :
853897outputs = [sys .input_labels [i ]for i in control_indices ]
854898if states is None :
855899states = nintegrators
856900
857- # Processgainscheduling variables, if present
901+ # Processgain scheduling variables, if present
858902if gainsched :
859903# Create a copy of the scheduling variable indices (default = xd)
860904gainsched_indices = _process_indices (
@@ -897,7 +941,7 @@ def _compute_gain(mu):
897941return K
898942
899943# Define the controller system
900- if controller_type == 'nonlinear' :
944+ if controller_type == 'nonlinear' and feedfwd_pattern == 'trajgen' :
901945# Create an I/O system for the state feedback gains
902946def _control_update (t ,states ,inputs ,params ):
903947# Split input into desired state, nominal input, and current state
@@ -931,7 +975,7 @@ def _control_output(t, states, inputs, params):
931975_control_update ,_control_output ,name = name ,inputs = inputs ,
932976outputs = outputs ,states = states ,params = params )
933977
934- elif controller_type == 'iosystem' :
978+ elif controller_type == 'iosystem' and feedfwd_pattern == 'trajgen' :
935979# Use the passed system to compute feedback compensation
936980def _control_update (t ,states ,inputs ,params ):
937981# Split input into desired state, nominal input, and current state
@@ -955,7 +999,7 @@ def _control_output(t, states, inputs, params):
955999_control_update ,_control_output ,name = name ,inputs = inputs ,
9561000outputs = outputs ,states = fbkctrl .state_labels ,dt = fbkctrl .dt )
9571001
958- elif controller_type == 'linear' or controller_type is None :
1002+ elif controller_type in 'linear' and feedfwd_pattern == 'trajgen' :
9591003# Create the matrices implementing the controller
9601004if isctime (sys ):
9611005# Continuous time: integrator
@@ -973,6 +1017,37 @@ def _control_output(t, states, inputs, params):
9731017A_lqr ,B_lqr ,C_lqr ,D_lqr ,dt = sys .dt ,name = name ,
9741018inputs = inputs ,outputs = outputs ,states = states )
9751019
1020+ elif feedfwd_pattern == 'refgain' :
1021+ if controller_type not in ['linear' ,'iosystem' ]:
1022+ raise ControlArgument (
1023+ "refgain design pattern only supports linear controllers" )
1024+
1025+ if feedfwd_gain is None :
1026+ raise ControlArgument (
1027+ "'feedfwd_gain' required for reference gain pattern" )
1028+
1029+ # Check to make sure the reference gain is valid
1030+ Kf = np .atleast_2d (feedfwd_gain )
1031+ if Kf .ndim != 2 or Kf .shape [0 ]!= sys .ninputs or \
1032+ Kf .shape [1 ]!= sys .ninputs :
1033+ raise ControlArgument ("feedfwd_gain is not the right shape" )
1034+
1035+ # Create the matrices implementing the controller
1036+ # [r, x]->[u]: u = k_f r - K_p x - K_i \int(C x - r)
1037+ if isctime (sys ):
1038+ # Continuous time: integrator
1039+ A_lqr = np .zeros ((C .shape [0 ],C .shape [0 ]))
1040+ else :
1041+ # Discrete time: summer
1042+ A_lqr = np .eye (C .shape [0 ])
1043+ B_lqr = np .hstack ([- np .eye (C .shape [0 ],sys_ninputs ),C ])
1044+ C_lqr = - K [:,sys_nstates :]# integral gain (opt)
1045+ D_lqr = np .hstack ([Kf ,- K ])
1046+
1047+ ctrl = ss (
1048+ A_lqr ,B_lqr ,C_lqr ,D_lqr ,dt = sys .dt ,name = name ,
1049+ inputs = inputs ,outputs = outputs ,states = states )
1050+
9761051else :
9771052raise ControlArgument (f"unknown controller_type '{ controller_type } '" )
9781053
@@ -1020,7 +1095,7 @@ def ctrb(A, B, t=None):
10201095bmat = _ssmatrix (B )
10211096n = np .shape (amat )[0 ]
10221097m = np .shape (bmat )[1 ]
1023-
1098+
10241099if t is None or t > n :
10251100t = n
10261101
@@ -1042,7 +1117,7 @@ def obsv(A, C, t=None):
10421117 Dynamics and output matrix of the system
10431118 t : None or integer
10441119 maximum time horizon of the controllability matrix, max = A.shape[0]
1045-
1120+
10461121 Returns
10471122 -------
10481123 O : 2D array (or matrix)
@@ -1062,14 +1137,14 @@ def obsv(A, C, t=None):
10621137cmat = _ssmatrix (C )
10631138n = np .shape (amat )[0 ]
10641139p = np .shape (cmat )[0 ]
1065-
1140+
10661141if t is None or t > n :
10671142t = n
10681143
10691144# Construct the observability matrix
10701145obsv = np .zeros ((t * p ,n ))
10711146obsv [:p , :]= cmat
1072-
1147+
10731148for k in range (1 ,t ):
10741149obsv [k * p :(k + 1 )* p , :]= np .dot (obsv [(k - 1 )* p :k * p , :],amat )
10751150