4444
4545from . import statesp
4646from .mateqn import care
47- from .statesp import _ssmatrix
48- from .exception import ControlSlycot , ControlArgument , ControlDimension
47+ from .statesp import _ssmatrix , _convert_to_statespace
48+ from .lti import LTI
49+ from .exception import ControlSlycot , ControlArgument , ControlDimension , \
50+ ControlNotImplemented
4951
5052# Make sure we have access to the right slycot routines
5153try :
@@ -257,8 +259,8 @@ def place_varga(A, B, p, dtime=False, alpha=None):
257259
258260
259261# 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])
262+ def lqe (* args , ** keywords ):
263+ """lqe(A, G, C, Q, R , [, N])
262264
263265 Linear quadratic estimator design (Kalman filter) for continuous-time
264266 systems. Given the system
@@ -270,25 +272,38 @@ def lqe(A, G, C, QN, RN, NN=None):
270272
271273 with unbiased process noise w and measurement noise v with covariances
272274
273- .. math:: E{ww'} = QN , E{vv'} = RN , E{wv'} = NN
275+ .. math:: E{ww'} = Q , E{vv'} = R , E{wv'} = N
274276
275277 The lqe() function computes the observer gain matrix L such that the
276278 stationary (non-time-varying) Kalman filter
277279
278280 .. math:: x_e = A x_e + B u + L(y - C x_e - D u)
279281
280282 produces a state estimate x_e that minimizes the expected squared error
281- using the sensor measurements y. The noise cross-correlation `NN ` is
283+ using the sensor measurements y. The noise cross-correlation `N ` is
282284 set to zero when omitted.
283285
286+ The function can be called with either 3, 4, 5, or 6 arguments:
287+
288+ * ``L, P, E = lqe(sys, Q, R)``
289+ * ``L, P, E = lqe(sys, Q, R, N)``
290+ * ``L, P, E = lqe(A, G, C, Q, R)``
291+ * ``L, P, E = lqe(A, B, C, Q, R, N)``
292+
293+ where `sys` is an `LTI` object, and `A`, `G`, `C`, `Q`, `R`, and `N` are
294+ 2D arrays or matrices of appropriate dimension.
295+
284296 Parameters
285297 ----------
286- A, G : 2D array_like
287- Dynamics and noise input matrices
288- QN, RN : 2D array_like
298+ A, G, C : 2D array_like
299+ Dynamics, process noise (disturbance), and output matrices
300+ sys : LTI (StateSpace or TransferFunction)
301+ Linear I/O system, with the process noise input taken as the system
302+ input.
303+ Q, R : 2D array_like
289304 Process and sensor noise covariance matrices
290- NN : 2D array, optional
291- Cross covariance matrix
305+ N : 2D array, optional
306+ Cross covariance matrix. Not currently implemented.
292307
293308 Returns
294309 -------
@@ -326,11 +341,60 @@ def lqe(A, G, C, QN, RN, NN=None):
326341 # NN = np.zeros(QN.size(0),RN.size(1))
327342 # NG = G @ NN
328343
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 )
344+ #
345+ # Process the arguments and figure out what inputs we received
346+ #
347+
348+ # Get the system description
349+ if (len (args ) < 3 ):
350+ raise ControlArgument ("not enough input arguments" )
351+
352+ try :
353+ sys = args [0 ] # Treat the first argument as a system
354+ if isinstance (sys , LTI ):
355+ # Convert LTI system to state space
356+ sys = _convert_to_statespace (sys )
357+
358+ # Extract A, G (assume disturbances come through input), and C
359+ A = np .array (sys .A , ndmin = 2 , dtype = float )
360+ G = np .array (sys .B , ndmin = 2 , dtype = float )
361+ C = np .array (sys .C , ndmin = 2 , dtype = float )
362+ index = 1
363+
364+ except AttributeError :
365+ # Arguments should be A and B matrices
366+ A = np .array (args [0 ], ndmin = 2 , dtype = float )
367+ G = np .array (args [1 ], ndmin = 2 , dtype = float )
368+ C = np .array (args [2 ], ndmin = 2 , dtype = float )
369+ index = 3
370+
371+ # Get the weighting matrices (converting to matrices, if needed)
372+ Q = np .array (args [index ], ndmin = 2 , dtype = float )
373+ R = np .array (args [index + 1 ], ndmin = 2 , dtype = float )
374+
375+ # Get the cross-covariance matrix, if given
376+ if (len (args ) > index + 2 ):
377+ N = np .array (args [index + 2 ], ndmin = 2 , dtype = float )
378+ raise ControlNotImplemented ("cross-covariance not implemented" )
379+
380+ else :
381+ N = np .zeros ((Q .shape [0 ], R .shape [1 ]))
382+
383+ # Check dimensions for consistency
384+ nstates = A .shape [0 ]
385+ ninputs = G .shape [1 ]
386+ noutputs = C .shape [0 ]
387+ if (A .shape [0 ] != nstates or A .shape [1 ] != nstates or
388+ G .shape [0 ] != nstates or C .shape [1 ] != nstates ):
389+ raise ControlDimension ("inconsistent system dimensions" )
390+
391+ elif (Q .shape [0 ] != ninputs or Q .shape [1 ] != ninputs or
392+ R .shape [0 ] != noutputs or R .shape [1 ] != noutputs or
393+ N .shape [0 ] != ninputs or N .shape [1 ] != noutputs ):
394+ raise ControlDimension ("incorrect covariance matrix dimensions" )
395+
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
@@ -394,17 +458,17 @@ def lqr(*args, **keywords):
394458
395459 The function can be called with either 3, 4, or 5 arguments:
396460
397- * ``lqr(sys, Q, R)``
398- * ``lqr(sys, Q, R, N)``
399- * ``lqr(A, B, Q, R)``
400- * ``lqr(A, B, Q, R, N)``
461+ * ``K, S, E = lqr(sys, Q, R)``
462+ * ``K, S, E = lqr(sys, Q, R, N)``
463+ * ``K, S, E = lqr(A, B, Q, R)``
464+ * ``K, S, E = 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