Skip to content

Commit da4efa9

Browse files
authored
optionally return additional arguments from lqr and kalman (#994)
* optionally return additional arguments from `lqr, kalman` such as the solution to the Riccati equation and the closed-loop poles * fix comment * fix
1 parent 64a7974 commit da4efa9

File tree

5 files changed

+45
-22
lines changed

5 files changed

+45
-22
lines changed

docs/src/index.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,5 +100,4 @@ The following is a list of packages from the wider Julia ecosystem that may be o
100100
## Courses using ControlSystems.jl
101101
- [Carnegie Mellon University: Optimal-Control](https://github.com/Optimal-Control-16-745)
102102
- [Kasetsart University: Control Engineering with Julia](https://dewdotninja.github.io/julia/control/julia_control.html)
103-
- [Czech Technical University: Optimal and Robust Control](https://hurak.github.io/orr/). [github repo](https://github.com/hurak/orr/tree/main)
104-
-
103+
- [Czech Technical University: Optimal and Robust Control](https://hurak.github.io/orr/). See also [github repo](https://github.com/hurak/orr/tree/main).

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,15 @@ function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}
282282
end
283283

284284
(z, iz, KRInfo) = MatrixPencils.spzeros(A, I, B, C, D; kwargs...)
285+
if z isa Vector{<:Real}
286+
zc = complex(z)
287+
else
288+
zc = z
289+
end
285290
if E
286-
return (z, iz, KRInfo)
291+
return (zc, iz, KRInfo)
287292
else
288-
return filter(isfinite, z)
293+
return filter(isfinite, zc)
289294
end
290295
end
291296

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,6 @@ _span(vec) = -(reverse(extrema(vec))...)
371371
else
372372
ws, phasedata
373373
end
374-
375374
end
376375
end
377376
end

lib/ControlSystemsBase/src/synthesis.jl

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
2-
lqr(sys, Q, R)
3-
lqr(Continuous, A, B, Q, R, args...; kwargs...)
4-
lqr(Discrete, A, B, Q, R, args...; kwargs...)
2+
lqr(sys, Q, R; extra = Val(false))
3+
lqr(Continuous, A, B, Q, R, args...; extra = Val(false), kwargs...)
4+
lqr(Discrete, A, B, Q, R, args...; extra = Val(false), kwargs...)
55
66
Calculate the optimal gain matrix `K` for the state-feedback law `u = -K*x` that
77
minimizes the cost function:
@@ -18,6 +18,8 @@ To obtain also the solution to the Riccati equation and the eigenvalues of the c
1818
1919
To obtain a discrete-time approximation to a continuous-time LQR problem, the function [`c2d`](@ref) can be used to obtain corresponding discrete-time cost matrices.
2020
21+
If `extra = Val(true)`, the function returns `K, P, p`, where `P` is the solution to the Riccati equation (Lyapunov function `x'P*x`), and `p` are the eigenvalues of `A-BK`.
22+
2123
# Examples
2224
Continuous time
2325
```julia
@@ -64,14 +66,14 @@ This function requires
6466
- `R` must be positive definite
6567
- The pair `(Q,A)` must not have any unobservable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not penalized by `Q`. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
6668
"""
67-
function lqr(::ContinuousType, A, B, Q, R, args...; kwargs...)
68-
S, _, K = arec(A, B, R, Q, args...; kwargs...)
69-
return K
69+
function lqr(::ContinuousType, A, B, Q, R, args...; extra::Val{E} = Val(false), kwargs...) where E
70+
S, p, K, args... = arec(A, B, R, Q, args...; kwargs...)
71+
E ? (K, S, p, args...) : K
7072
end
7173

72-
function lqr(::DiscreteType, A, B, Q, R, args...; kwargs...)
73-
S, _, K = ared(A, B, R, Q, args...; kwargs...)
74-
return K
74+
function lqr(::DiscreteType, A, B, Q, R, args...; extra::Val{E} = Val(false), kwargs...) where E
75+
S, p, K, args... = ared(A, B, R, Q, args...; kwargs...)
76+
E ? (K, S, p, args...) : K
7577
end
7678

7779
@deprecate lqr(A::AbstractMatrix, args...; kwargs...) lqr(Continuous, A, args...; kwargs...)
@@ -80,9 +82,9 @@ end
8082

8183

8284
"""
83-
kalman(Continuous, A, C, R1, R2)
84-
kalman(Discrete, A, C, R1, R2; direct = false)
85-
kalman(sys, R1, R2; direct = false)
85+
kalman(Continuous, A, C, R1, R2; extra=Val(false))
86+
kalman(Discrete, A, C, R1, R2; extra=Val(false), direct = false)
87+
kalman(sys, R1, R2; extra=Val(false), direct = false)
8688
8789
Calculate the optimal asymptotic Kalman gain for the linear-Gaussian model
8890
```math
@@ -93,26 +95,42 @@ y &= Cx + v
9395
```
9496
where `w` is the dynamics noise with covariance `R1` and `v` is the measurement noise with covariance `R2`.
9597
96-
If `direct = true`, the observer gain is computed for the pair `(A, CA)` instead of `(A,C)`. This option is intended to be used together with the option `direct = true` to [`observer_controller`](@ref). Ref: "Computer-Controlled Systems" pp 140. `direct = false` is sometimes referred to as a "delayed" estimator, while `direct = true` is a "current" estimator.
98+
In discrete time, the returned Kalman gain `K` is designed to be used with (`direct = false`)
99+
```math
100+
x(t+1|t) = (A-KC)x(t|t-1) + Bu(t) + Ky(t)
101+
```
102+
and (`direct = true`)
103+
```math
104+
x(t+1|t+1) = (A-KC)x(t|t) + Bu(t) + Ky(t+1)
105+
```
106+
107+
If `direct = true`, the observer gain is computed as ``K_d = R_∞C^T (R_2 + CR_∞ C^T)^{-1}`` instead of ``K = A K_d``. This option is intended to be used together with the option `direct = true` to [`observer_controller`](@ref). Ref: "Computer-Controlled Systems" pp 140. `direct = false` is sometimes referred to as a "delayed" estimator, while `direct = true` is a "current" estimator.
97108
98109
To obtain a discrete-time approximation to a continuous-time LQG problem, the function [`c2d`](@ref) can be used to obtain corresponding discrete-time covariance matrices.
99110
100111
To obtain an LTISystem that represents the Kalman filter, pass the obtained Kalman feedback gain into [`observer_filter`](@ref). To obtain an LQG controller, pass the obtained Kalman feedback gain as well as a state-feedback gain computed using [`lqr`](@ref) into [`observer_controller`](@ref).
101112
102-
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?MatrixEquations.arec/ared` for more help.
113+
If `extra = Val(true)`, the function returns `K, R∞, p`, where `R∞` is the solution to the Riccati equation (the stationary prediction-error covariance matrix, the covariance of ``x̃(t|t-1)``), and `p` are the eigenvalues of `A-KC`. In this case, the _filtering error_ covariance is given by ``(I-KC) R∞``, i.e., the covariance of ``x̃(t|t)``.
114+
115+
The `args...; kwargs...` are sent to the Riccati solver, allowing specification of cross-covariance etc. See `?ControlSystemsBase.MatrixEquations.arec/ared` for more help.
103116
104117
# FAQ
105118
This function requires
106119
- `R1` must be positive semi-definite
107120
- `R2` must be positive definite
108121
- The pair `(A,R1)` must not have any uncontrollable modes on the imaginary axis (cont) / unit circle (disc), e.g., there must not be any integrating modes that are not affected through `R1`. if this condition does not hold, you may get the error "The Hamiltonian matrix is not dichotomic".
122+
123+
# Extended help
124+
`MatrixEquations.ared` solves the Riccati equation corresponding to the direct form, but returns the ``K`` matrix for the indirect form. The solution to the Riccati equation is the stationary prediction-error covariance matrix ``R_∞``, and the filtering error covariance is given by ``(I-A^{-1}KC) R_∞``. If using the direct form, the filter-error covariance is given by ``(I-K_d C) R_∞``
109125
"""
110-
function kalman(te, A, C, R1,R2, args...; direct = false, kwargs...)
126+
function kalman(te, A, C, R1,R2, args...; direct = false, extra::Val{E} = Val(false), kwargs...) where E
127+
Kt, R∞, p, args... = lqr(te, A',C',R1,R2, args...; extra=Val(true), kwargs...)
111128
if direct
112129
te isa ContinuousType && error("direct = true only applies to discrete-time systems")
113-
C = C*A
130+
K = (R∞*C')/(R2 + C*R∞*C') # ared returns K for the indirect form, which is A*Kdirect
131+
return E ? (K, R∞, p, args...) : K
114132
end
115-
Matrix(lqr(te, A',C',R1,R2, args...; kwargs...)')
133+
E ? (Matrix(Kt'), R∞, p, args...) : Matrix(Kt')
116134
end
117135

118136
function lqr(sys::AbstractStateSpace, Q, R, args...; kwargs...)

lib/ControlSystemsBase/test/test_synthesis.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ end
210210
R = I
211211
L = lqr(Discrete, A,B,Q,R)
212212
@test L [0.5890881713787511 0.7118839434795103]
213+
L, S, p = lqr(Discrete, A,B,Q,R, extra=Val(true))
214+
@test p eigvals(A-B*L)
213215
sys = ss(A,B,C,0,Ts)
214216
L = lqr(sys, Q, R)
215217
@test L [0.5890881713787511 0.7118839434795103]

0 commit comments

Comments
 (0)