|
43 | 43 | import numpy as np |
44 | 44 | import scipy as sp |
45 | 45 | from . import statesp |
| 46 | +from .mateqn import care |
46 | 47 | from .statesp import _ssmatrix |
47 | 48 | from .exception import ControlSlycot, ControlArgument, ControlDimension |
48 | 49 |
|
49 | | -__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr', 'acker'] |
| 50 | +__all__ = ['ctrb', 'obsv', 'gram', 'place', 'place_varga', 'lqr', 'lqe', 'acker'] |
50 | 51 |
|
51 | 52 |
|
52 | 53 | # Pole placement |
@@ -219,6 +220,76 @@ def place_varga(A, B, p, dtime=False, alpha=None): |
219 | 220 | # Return the gain matrix, with MATLAB gain convention |
220 | 221 | return _ssmatrix(-F) |
221 | 222 |
|
| 223 | +# contributed by Sawyer B. Fuller <minster@uw.edu> |
| 224 | +def lqe(A, G, C, QN, RN, NN=None): |
| 225 | + """lqe(A, G, C, QN, RN, [, N]) |
| 226 | +
|
| 227 | + Linear quadratic estimator design (Kalman filter) for continuous-time |
| 228 | + systems. Given the system |
| 229 | +
|
| 230 | + Given the system |
| 231 | + .. math:: |
| 232 | + x = Ax + Bu + Gw |
| 233 | + y = Cx + Du + v |
| 234 | + |
| 235 | + with unbiased process noise w and measurement noise v with covariances |
| 236 | +
|
| 237 | + .. math:: E{ww'} = QN, E{vv'} = RN, E{wv'} = NN |
| 238 | +
|
| 239 | + The lqe() function computes the observer gain matrix L such that the |
| 240 | + stationary (non-time-varying) Kalman filter |
| 241 | +
|
| 242 | + .. math:: x_e = A x_e + B u + L(y - C x_e - D u) |
| 243 | +
|
| 244 | + produces a state estimate that x_e that minimizes the expected squared error |
| 245 | + using the sensor measurements y. The noise cross-correlation `NN` is set to |
| 246 | + zero when omitted. |
| 247 | +
|
| 248 | + Parameters |
| 249 | + ---------- |
| 250 | + A, G: 2-d array |
| 251 | + Dynamics and noise input matrices |
| 252 | + QN, RN: 2-d array |
| 253 | + Process and sensor noise covariance matrices |
| 254 | + NN: 2-d array, optional |
| 255 | + Cross covariance matrix |
| 256 | +
|
| 257 | + Returns |
| 258 | + ------- |
| 259 | + L: 2D array |
| 260 | + Kalman estimator gain |
| 261 | + P: 2D array |
| 262 | + Solution to Riccati equation |
| 263 | + .. math:: |
| 264 | + A P + P A^T - (P C^T + G N) R^-1 (C P + N^T G^T) + G Q G^T = 0 |
| 265 | + E: 1D array |
| 266 | + Eigenvalues of estimator poles eig(A - L C) |
| 267 | + |
| 268 | +
|
| 269 | + Examples |
| 270 | + -------- |
| 271 | + >>> K, P, E = lqe(A, G, C, QN, RN) |
| 272 | + >>> K, P, E = lqe(A, G, C, QN, RN, NN) |
| 273 | +
|
| 274 | + See Also |
| 275 | + -------- |
| 276 | + lqr |
| 277 | + """ |
| 278 | + |
| 279 | + # TODO: incorporate cross-covariance NN, something like this, |
| 280 | + # which doesn't work for some reason |
| 281 | + #if NN is None: |
| 282 | + # NN = np.zeros(QN.size(0),RN.size(1)) |
| 283 | + #NG = G @ NN |
| 284 | + |
| 285 | + #LT, P, E = lqr(A.T, C.T, G @ QN @ G.T, RN) |
| 286 | + #P, E, LT = care(A.T, C.T, G @ QN @ G.T, RN) |
| 287 | + A, G, C = np.array(A, ndmin=2), np.array(G, ndmin=2), np.array(C, ndmin=2) |
| 288 | + QN, RN = np.array(QN, ndmin=2), np.array(RN, ndmin=2) |
| 289 | + P, E, LT = care(A.T, C.T, np.dot(np.dot(G, QN), G.T), RN) |
| 290 | + return _ssmatrix(LT.T), _ssmatrix(P), _ssmatrix(E) |
| 291 | + |
| 292 | + |
222 | 293 | # Contributed by Roberto Bucher <roberto.bucher@supsi.ch> |
223 | 294 | def acker(A, B, poles): |
224 | 295 | """Pole placement using Ackermann method |
@@ -307,6 +378,10 @@ def lqr(*args, **keywords): |
307 | 378 | >>> K, S, E = lqr(sys, Q, R, [N]) |
308 | 379 | >>> K, S, E = lqr(A, B, Q, R, [N]) |
309 | 380 |
|
| 381 | + See Also |
| 382 | + -------- |
| 383 | + lqe |
| 384 | + |
310 | 385 | """ |
311 | 386 |
|
312 | 387 | # Make sure that SLICOT is installed |
|
0 commit comments