Skip to content

Commit 1e55a03

Browse files
committed
update lqe() argument processing to match lqr()
1 parent 56cecc0 commit 1e55a03

1 file changed

Lines changed: 81 additions & 17 deletions

File tree

control/statefbk.py

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444

4545
from . import statesp
4646
from .mateqn import care
47-
from .statesp import _ssmatrix
47+
from .statesp import _ssmatrix, _convert_to_statespace
48+
from .lti import LTI
4849
from .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

Comments
 (0)