4343import numpy as np
4444
4545from . import statesp
46- from .mateqn import care
46+ from .mateqn import care , dare
4747from .statesp import _ssmatrix , _convert_to_statespace
48- from .lti import LTI
48+ from .lti import LTI , isdtime
4949from .exception import ControlSlycot , ControlArgument , ControlDimension , \
5050 ControlNotImplemented
5151
@@ -69,7 +69,7 @@ def sb03md(n, C, A, U, dico, job='X',fact='N',trana='N',ldwork=None):
6969
7070
7171__all__ = ['ctrb' , 'obsv' , 'gram' , 'place' , 'place_varga' , 'lqr' , 'lqe' ,
72- 'acker' ]
72+ 'dlqr' , 'dlqe' , ' acker' ]
7373
7474
7575# Pole placement
@@ -335,7 +335,7 @@ def lqe(*args, **keywords):
335335
336336 See Also
337337 --------
338- lqr
338+ lqr, dlqe, dlqr
339339
340340 """
341341
@@ -403,6 +403,82 @@ def lqe(*args, **keywords):
403403 P , E , LT = care (A .T , C .T , G @ Q @ G .T , R , method = method )
404404 return _ssmatrix (LT .T ), _ssmatrix (P ), E
405405
406+ # contributed by Sawyer B. Fuller <minster@uw.edu>
407+ def dlqe (A , G , C , QN , RN , NN = None ):
408+ """dlqe(A, G, C, QN, RN, [, N])
409+
410+ Linear quadratic estimator design (Kalman filter) for discrete-time
411+ systems. Given the system
412+
413+ .. math::
414+
415+ x[n+1] &= Ax[n] + Bu[n] + Gw[n] \\ \\
416+ y[n] &= Cx[n] + Du[n] + v[n]
417+
418+ with unbiased process noise w and measurement noise v with covariances
419+
420+ .. math:: E{ww'} = QN, E{vv'} = RN, E{wv'} = NN
421+
422+ The dlqe() function computes the observer gain matrix L such that the
423+ stationary (non-time-varying) Kalman filter
424+
425+ .. math:: x_e[n+1] = A x_e[n] + B u[n] + L(y[n] - C x_e[n] - D u[n])
426+
427+ produces a state estimate x_e[n] that minimizes the expected squared error
428+ using the sensor measurements y. The noise cross-correlation `NN` is
429+ set to zero when omitted.
430+
431+ Parameters
432+ ----------
433+ A, G : 2D array_like
434+ Dynamics and noise input matrices
435+ QN, RN : 2D array_like
436+ Process and sensor noise covariance matrices
437+ NN : 2D array, optional
438+ Cross covariance matrix
439+
440+ Returns
441+ -------
442+ L : 2D array (or matrix)
443+ Kalman estimator gain
444+ P : 2D array (or matrix)
445+ Solution to Riccati equation
446+
447+ .. math::
448+
449+ A P + P A^T - (P C^T + G N) R^{-1} (C P + N^T G^T) + G Q G^T = 0
450+
451+ E : 1D array
452+ Eigenvalues of estimator poles eig(A - L C)
453+
454+ Notes
455+ -----
456+ The return type for 2D arrays depends on the default class set for
457+ state space operations. See :func:`~control.use_numpy_matrix`.
458+
459+ Examples
460+ --------
461+ >>> L, P, E = dlqe(A, G, C, QN, RN)
462+ >>> L, P, E = dlqe(A, G, C, QN, RN, NN)
463+
464+ See Also
465+ --------
466+ dlqr, lqe, lqr
467+
468+ """
469+
470+ # TODO: incorporate cross-covariance NN, something like this,
471+ # which doesn't work for some reason
472+ # if NN is None:
473+ # NN = np.zeros(QN.size(0),RN.size(1))
474+ # NG = G @ NN
475+
476+ # LT, P, E = lqr(A.T, C.T, G @ QN @ G.T, RN)
477+ # P, E, LT = care(A.T, C.T, G @ QN @ G.T, RN)
478+ A , G , C = np .array (A , ndmin = 2 ), np .array (G , ndmin = 2 ), np .array (C , ndmin = 2 )
479+ QN , RN = np .array (QN , ndmin = 2 ), np .array (RN , ndmin = 2 )
480+ P , E , LT = dare (A .T , C .T , np .dot (np .dot (G , QN ), G .T ), RN )
481+ return _ssmatrix (LT .T ), _ssmatrix (P ), E
406482
407483# Contributed by Roberto Bucher <roberto.bucher@supsi.ch>
408484def acker (A , B , poles ):
@@ -458,7 +534,7 @@ def lqr(*args, **keywords):
458534 Linear quadratic regulator design
459535
460536 The lqr() function computes the optimal state feedback controller
461- that minimizes the quadratic cost
537+ u = -K x that minimizes the quadratic cost
462538
463539 .. math:: J = \\ int_0^\\ infty (x' Q x + u' R u + 2 x' N u) dt
464540
@@ -476,8 +552,8 @@ def lqr(*args, **keywords):
476552 ----------
477553 A, B : 2D array_like
478554 Dynamics and input matrices
479- sys : LTI ( StateSpace or TransferFunction)
480- Linear I/O system
555+ sys : LTI StateSpace system
556+ Linear system
481557 Q, R : 2D array
482558 State and input weight matrices
483559 N : 2D array, optional
@@ -546,6 +622,111 @@ def lqr(*args, **keywords):
546622 X , L , G = care (A , B , Q , R , N , None , method = method )
547623 return G , X , L
548624
625+ def dlqr (* args , ** keywords ):
626+ """dlqr(A, B, Q, R[, N])
627+
628+ Discrete-time linear quadratic regulator design
629+
630+ The dlqr() function computes the optimal state feedback controller
631+ u[n] = - K x[n] that minimizes the quadratic cost
632+
633+ .. math:: J = \\ Sum_0^\\ infty (x[n]' Q x[n] + u[n]' R u[n] + 2 x[n]' N u[n])
634+
635+ The function can be called with either 3, 4, or 5 arguments:
636+
637+ * ``dlqr(dsys, Q, R)``
638+ * ``dlqr(dsys, Q, R, N)``
639+ * ``dlqr(A, B, Q, R)``
640+ * ``dlqr(A, B, Q, R, N)``
641+
642+ where `dsys` is a discrete-time :class:`StateSpace` system, and `A`, `B`,
643+ `Q`, `R`, and `N` are 2d arrays of appropriate dimension (`dsys.dt` must
644+ not be 0.)
645+
646+ Parameters
647+ ----------
648+ A, B : 2D array
649+ Dynamics and input matrices
650+ dsys : LTI :class:`StateSpace`
651+ Discrete-time linear system
652+ Q, R : 2D array
653+ State and input weight matrices
654+ N : 2D array, optional
655+ Cross weight matrix
656+
657+ Returns
658+ -------
659+ K : 2D array (or matrix)
660+ State feedback gains
661+ S : 2D array (or matrix)
662+ Solution to Riccati equation
663+ E : 1D array
664+ Eigenvalues of the closed loop system
665+
666+ See Also
667+ --------
668+ lqr, lqe, dlqe
669+
670+ Notes
671+ -----
672+ The return type for 2D arrays depends on the default class set for
673+ state space operations. See :func:`~control.use_numpy_matrix`.
674+
675+ Examples
676+ --------
677+ >>> K, S, E = dlqr(dsys, Q, R, [N])
678+ >>> K, S, E = dlqr(A, B, Q, R, [N])
679+ """
680+
681+ #
682+ # Process the arguments and figure out what inputs we received
683+ #
684+
685+ # Get the system description
686+ if (len (args ) < 3 ):
687+ raise ControlArgument ("not enough input arguments" )
688+
689+ try :
690+ # If this works, we were (probably) passed a system as the
691+ # first argument; extract A and B
692+ A = np .array (args [0 ].A , ndmin = 2 , dtype = float )
693+ B = np .array (args [0 ].B , ndmin = 2 , dtype = float )
694+ index = 1
695+ except AttributeError :
696+ # Arguments should be A and B matrices
697+ A = np .array (args [0 ], ndmin = 2 , dtype = float )
698+ B = np .array (args [1 ], ndmin = 2 , dtype = float )
699+ index = 2
700+
701+ # confirm that if we received a system that it was discrete-time
702+ if index == 1 :
703+ if not isdtime (args [0 ]):
704+ raise ValueError ("dsys must be discrete (dt !=0)" )
705+
706+ # Get the weighting matrices (converting to matrices, if needed)
707+ Q = np .array (args [index ], ndmin = 2 , dtype = float )
708+ R = np .array (args [index + 1 ], ndmin = 2 , dtype = float )
709+ if (len (args ) > index + 2 ):
710+ N = np .array (args [index + 2 ], ndmin = 2 , dtype = float )
711+ else :
712+ N = np .zeros ((Q .shape [0 ], R .shape [1 ]))
713+
714+ # Check dimensions for consistency
715+ nstates = B .shape [0 ]
716+ ninputs = B .shape [1 ]
717+ if (A .shape [0 ] != nstates or A .shape [1 ] != nstates ):
718+ raise ControlDimension ("inconsistent system dimensions" )
719+
720+ elif (Q .shape [0 ] != nstates or Q .shape [1 ] != nstates or
721+ R .shape [0 ] != ninputs or R .shape [1 ] != ninputs or
722+ N .shape [0 ] != nstates or N .shape [1 ] != ninputs ):
723+ raise ControlDimension ("incorrect weighting matrix dimensions" )
724+
725+ # compute the result
726+ S , E , K = dare (A , B , Q , R , N )
727+ return _ssmatrix (K ), _ssmatrix (S ), E
728+
729+
549730def ctrb (A , B ):
550731 """Controllabilty matrix
551732
0 commit comments