@@ -578,7 +578,7 @@ def acker(A, B, poles):
578578 return _ssmatrix (K )
579579
580580
581- def lqr (* args , ** keywords ):
581+ def lqr (* args , ** kwargs ):
582582 """lqr(A, B, Q, R[, N])
583583
584584 Linear quadratic regulator design
@@ -646,18 +646,15 @@ def lqr(*args, **keywords):
646646 # Process the arguments and figure out what inputs we received
647647 #
648648
649- # Get the method to use (if specified as a keyword)
650- method = keywords .get ('method' , None )
649+ # If we were passed a discrete time system as the first arg, use dlqr()
650+ if isinstance (args [0 ], LTI ) and isdtime (args [0 ], strict = True ):
651+ # Call dlqr
652+ return dlqr (* args , ** kwargs )
651653
652654 # Get the system description
653655 if (len (args ) < 3 ):
654656 raise ControlArgument ("not enough input arguments" )
655657
656- # If we were passed a discrete time system as the first arg, use dlqr()
657- if isinstance (args [0 ], LTI ) and isdtime (args [0 ], strict = True ):
658- # Call dlqr
659- return dlqr (* args , ** keywords )
660-
661658 # If we were passed a state space system, use that to get system matrices
662659 if isinstance (args [0 ], StateSpace ):
663660 A = np .array (args [0 ].A , ndmin = 2 , dtype = float )
@@ -682,12 +679,47 @@ def lqr(*args, **keywords):
682679 else :
683680 N = None
684681
682+ #
683+ # Process keywords
684+ #
685+
686+ # Get the method to use (if specified as a keyword)
687+ method = kwargs .pop ('method' , None )
688+
689+ # See if we should augment the controller with integral feedback
690+ integral_action = kwargs .pop ('integral_action' , None )
691+ if integral_action is not None :
692+ # Figure out the size of the system
693+ nstates = A .shape [0 ]
694+ ninputs = B .shape [1 ]
695+
696+ # Make sure that the integral action argument is the right type
697+ if not isinstance (integral_action , np .ndarray ):
698+ raise ControlArgument ("Integral action must pass an array" )
699+ elif integral_action .shape [1 ] != nstates :
700+ raise ControlArgument (
701+ "Integral gain output size must match system input size" )
702+
703+ # Process the states to be integrated
704+ nintegrators = integral_action .shape [0 ]
705+ C = integral_action
706+
707+ # Augment the system with integrators
708+ A = np .block ([
709+ [A , np .zeros ((nstates , nintegrators ))],
710+ [C , np .zeros ((nintegrators , nintegrators ))]
711+ ])
712+ B = np .vstack ([B , np .zeros ((nintegrators , ninputs ))])
713+
714+ if kwargs :
715+ raise TypeError ("unrecognized keywords: " , str (kwargs ))
716+
685717 # Compute the result (dimension and symmetry checking done in care())
686718 X , L , G = care (A , B , Q , R , N , None , method = method , S_s = "N" )
687719 return G , X , L
688720
689721
690- def dlqr (* args , ** keywords ):
722+ def dlqr (* args , ** kwargs ):
691723 """dlqr(A, B, Q, R[, N])
692724
693725 Discrete-time linear quadratic regulator design
@@ -747,9 +779,6 @@ def dlqr(*args, **keywords):
747779 # Process the arguments and figure out what inputs we received
748780 #
749781
750- # Get the method to use (if specified as a keyword)
751- method = keywords .get ('method' , None )
752-
753782 # Get the system description
754783 if (len (args ) < 3 ):
755784 raise ControlArgument ("not enough input arguments" )
@@ -782,6 +811,39 @@ def dlqr(*args, **keywords):
782811 else :
783812 N = np .zeros ((Q .shape [0 ], R .shape [1 ]))
784813
814+ #
815+ # Process keywords
816+ #
817+
818+ # Get the method to use (if specified as a keyword)
819+ method = kwargs .pop ('method' , None )
820+
821+ # See if we should augment the controller with integral feedback
822+ integral_action = kwargs .pop ('integral_action' , None )
823+ if integral_action is not None :
824+ # Figure out the size of the system
825+ nstates = A .shape [0 ]
826+ ninputs = B .shape [1 ]
827+
828+ if not isinstance (integral_action , np .ndarray ):
829+ raise ControlArgument ("Integral action must pass an array" )
830+ elif integral_action .shape [1 ] != nstates :
831+ raise ControlArgument (
832+ "Integral gain output size must match system input size" )
833+ else :
834+ nintegrators = integral_action .shape [0 ]
835+ C = integral_action
836+
837+ # Augment the system with integrators
838+ A = np .block ([
839+ [A , np .zeros ((nstates , nintegrators ))],
840+ [C , np .eye (nintegrators )]
841+ ])
842+ B = np .vstack ([B , np .zeros ((nintegrators , ninputs ))])
843+
844+ if kwargs :
845+ raise TypeError ("unrecognized keywords: " , str (kwargs ))
846+
785847 # Compute the result (dimension and symmetry checking done in dare())
786848 S , E , K = dare (A , B , Q , R , N , method = method , S_s = "N" )
787849 return _ssmatrix (K ), _ssmatrix (S ), E
@@ -948,7 +1010,12 @@ def _control_output(t, e, z, params):
9481010
9491011 elif type == 'linear' or type is None :
9501012 # Create the matrices implementing the controller
951- A_lqr = np .zeros ((C .shape [0 ], C .shape [0 ]))
1013+ if isctime (sys ):
1014+ # Continuous time: integrator
1015+ A_lqr = np .zeros ((C .shape [0 ], C .shape [0 ]))
1016+ else :
1017+ # Discrete time: summer
1018+ A_lqr = np .eye (C .shape [0 ])
9521019 B_lqr = np .hstack ([- C , np .zeros ((C .shape [0 ], sys .ninputs )), C ])
9531020 C_lqr = - K [:, sys .nstates :]
9541021 D_lqr = np .hstack ([
0 commit comments