44
44
45
45
from .import statesp
46
46
from .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
49
51
50
52
# Make sure we have access to the right slycot routines
51
53
try :
@@ -257,8 +259,8 @@ def place_varga(A, B, p, dtime=False, alpha=None):
257
259
258
260
259
261
# 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])
262
264
263
265
Linear quadratic estimator design (Kalman filter) for continuous-time
264
266
systems. Given the system
@@ -270,25 +272,38 @@ def lqe(A, G, C, QN, RN, NN=None):
270
272
271
273
with unbiased process noise w and measurement noise v with covariances
272
274
273
- .. math:: E{ww'} =QN , E{vv'} =RN , E{wv'} =NN
275
+ .. math:: E{ww'} =Q , E{vv'} =R , E{wv'} =N
274
276
275
277
The lqe() function computes the observer gain matrix L such that the
276
278
stationary (non-time-varying) Kalman filter
277
279
278
280
.. math:: x_e = A x_e + B u + L(y - C x_e - D u)
279
281
280
282
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
282
284
set to zero when omitted.
283
285
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
+
284
296
Parameters
285
297
----------
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
289
304
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.
292
307
293
308
Returns
294
309
-------
@@ -326,11 +341,60 @@ def lqe(A, G, C, QN, RN, NN=None):
326
341
# NN = np.zeros(QN.size(0),RN.size(1))
327
342
# NG = G @ NN
328
343
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 )
334
398
return _ssmatrix (LT .T ),_ssmatrix (P ),E
335
399
336
400
@@ -394,17 +458,17 @@ def lqr(*args, **keywords):
394
458
395
459
The function can be called with either 3, 4, or 5 arguments:
396
460
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)``
401
465
402
466
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.
404
468
405
469
Parameters
406
470
----------
407
- A, B : 2Darray
471
+ A, B : 2Darray_like
408
472
Dynamics and input matrices
409
473
sys : LTI (StateSpace or TransferFunction)
410
474
Linear I/O system