4444
4545from . import statesp
4646from .mateqn import care
47- from .statesp import _ssmatrix
47+ from .statesp import _ssmatrix , _convert_to_statespace
48+ from .lti import LTI
4849from .exception import ControlSlycot , ControlArgument , ControlDimension
4950
5051# Make sure we have access to the right slycot routines
@@ -257,8 +258,8 @@ def place_varga(A, B, p, dtime=False, alpha=None):
257258
258259
259260# contributed by Sawyer B. Fuller <minster@uw.edu>
260- def lqe (A , G , C , QN , RN , NN = None ):
261- """lqe(A, G, C, QN, RN , [, N])
261+ def lqe (* args , ** keywords ):
262+ """lqe(A, G, C, Q, R , [, N])
262263
263264 Linear quadratic estimator design (Kalman filter) for continuous-time
264265 systems. Given the system
@@ -270,25 +271,38 @@ def lqe(A, G, C, QN, RN, NN=None):
270271
271272 with unbiased process noise w and measurement noise v with covariances
272273
273- .. math:: E{ww'} = QN , E{vv'} = RN , E{wv'} = NN
274+ .. math:: E{ww'} = Q , E{vv'} = R , E{wv'} = N
274275
275276 The lqe() function computes the observer gain matrix L such that the
276277 stationary (non-time-varying) Kalman filter
277278
278279 .. math:: x_e = A x_e + B u + L(y - C x_e - D u)
279280
280281 produces a state estimate x_e that minimizes the expected squared error
281- using the sensor measurements y. The noise cross-correlation `NN ` is
282+ using the sensor measurements y. The noise cross-correlation `N ` is
282283 set to zero when omitted.
283284
285+ The function can be called with either 3, 4, 5, or 6 arguments:
286+
287+ * ``lqe(sys, Q, R)``
288+ * ``lqe(sys, Q, R, N)``
289+ * ``lqe(A, G, C, Q, R)``
290+ * ``lqe(A, B, C, Q, R, N)``
291+
292+ where `sys` is an `LTI` object, and `A`, `G`, `C`, `Q`, `R`, and `N` are
293+ 2D arrays or matrices of appropriate dimension.
294+
284295 Parameters
285296 ----------
286- A, G : 2D array_like
287- Dynamics and noise input matrices
288- QN, RN : 2D array_like
297+ A, G, C : 2D array_like
298+ Dynamics, process noise (disturbance), and output matrices
299+ sys : LTI (StateSpace or TransferFunction)
300+ Linear I/O system, with the process noise input taken as the system
301+ input.
302+ Q, R : 2D array_like
289303 Process and sensor noise covariance matrices
290- NN : 2D array, optional
291- Cross covariance matrix
304+ N : 2D array, optional
305+ Cross covariance matrix. Not currently implemented.
292306
293307 Returns
294308 -------
@@ -326,11 +340,61 @@ def lqe(A, G, C, QN, RN, NN=None):
326340 # NN = np.zeros(QN.size(0),RN.size(1))
327341 # NG = G @ NN
328342
329- # LT, P, E = lqr(A.T, C.T, G @ QN @ G.T, RN)
330- # P, E, LT = care(A.T, C.T, G @ QN @ G.T, RN)
331- A , G , C = np .array (A , ndmin = 2 ), np .array (G , ndmin = 2 ), np .array (C , ndmin = 2 )
332- QN , RN = np .array (QN , ndmin = 2 ), np .array (RN , ndmin = 2 )
333- P , E , LT = care (A .T , C .T , np .dot (np .dot (G , QN ), G .T ), RN )
343+ #
344+ # Process the arguments and figure out what inputs we received
345+ #
346+
347+ # Get the system description
348+ if (len (args ) < 3 ):
349+ raise ControlArgument ("not enough input arguments" )
350+
351+ try :
352+ sys = args [0 ] # Treat the first argument as a system
353+ if isinstance (sys , LTI ):
354+ # Convert LTI system to state space
355+ sys = _convert_to_statespace (sys )
356+
357+ # Extract A, G (assume disturbances come through input), and C
358+ A = np .array (sys .A , ndmin = 2 , dtype = float )
359+ G = np .array (sys .B , ndmin = 2 , dtype = float )
360+ C = np .array (sys .C , ndmin = 2 , dtype = float )
361+ index = 1
362+
363+ except AttributeError :
364+ # Arguments should be A and B matrices
365+ A = np .array (args [0 ], ndmin = 2 , dtype = float )
366+ G = np .array (args [1 ], ndmin = 2 , dtype = float )
367+ C = np .array (args [2 ], ndmin = 2 , dtype = float )
368+ index = 3
369+
370+ # Get the weighting matrices (converting to matrices, if needed)
371+ Q = np .array (args [index ], ndmin = 2 , dtype = float )
372+ R = np .array (args [index + 1 ], ndmin = 2 , dtype = float )
373+
374+ # Get the cross-covariance matrix, if given
375+ if (len (args ) > index + 2 ):
376+ N = np .array (args [index + 2 ], ndmin = 2 , dtype = float )
377+ raise ControlNotImplemented ("cross-covariance not implemented" )
378+
379+ else :
380+ N = np .zeros ((Q .shape [0 ], R .shape [1 ]))
381+
382+ # Check dimensions for consistency
383+ nstates = A .shape [0 ]
384+ ninputs = G .shape [1 ]
385+ noutputs = C .shape [0 ]
386+ if (A .shape [0 ] != nstates or A .shape [1 ] != nstates or
387+ G .shape [0 ] != nstates or C .shape [1 ] != nstates ):
388+ raise ControlDimension ("inconsistent system dimensions" )
389+
390+ elif (Q .shape [0 ] != ninputs or Q .shape [1 ] != ninputs or
391+ R .shape [0 ] != noutputs or R .shape [1 ] != noutputs or
392+ N .shape [0 ] != ninputs or N .shape [1 ] != noutputs ):
393+ raise ControlDimension ("incorrect weighting matrix dimensions" )
394+
395+ # LT, P, E = lqr(A.T, C.T, G @ Q @ G.T, R)
396+ # P, E, LT = care(A.T, C.T, G @ Q @ G.T, R)
397+ P , E , LT = care (A .T , C .T , np .dot (np .dot (G , Q ), G .T ), R )
334398 return _ssmatrix (LT .T ), _ssmatrix (P ), E
335399
336400
@@ -400,11 +464,11 @@ def lqr(*args, **keywords):
400464 * ``lqr(A, B, Q, R, N)``
401465
402466 where `sys` is an `LTI` object, and `A`, `B`, `Q`, `R`, and `N` are
403- 2d arrays or matrices of appropriate dimension.
467+ 2D arrays or matrices of appropriate dimension.
404468
405469 Parameters
406470 ----------
407- A, B : 2D array
471+ A, B : 2D array_like
408472 Dynamics and input matrices
409473 sys : LTI (StateSpace or TransferFunction)
410474 Linear I/O system
0 commit comments