Skip to content

Commit 6249cba

Browse files
authored
LQG overhaul (#332)
* use getproperty instead of getfield for LQG * more updates for LQG * import getindex * merge updates from working * update formatting in docstring * handle messed-up merge * fix backslash
1 parent 90f500d commit 6249cba

File tree

3 files changed

+135
-107
lines changed

3 files changed

+135
-107
lines changed

src/ControlSystems.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ import Polynomials: Polynomial, coeffs
9999
using OrdinaryDiffEq, DelayDiffEq
100100
export Plots
101101
import Base: +, -, *, /, (==), (!=), isapprox, convert, promote_op
102-
import Base: getproperty
102+
import Base: getproperty, getindex
103103
import Base: exp # for exp(-s)
104104
import LinearAlgebra: BlasFloat
105105
export lyap # Make sure LinearAlgebra.lyap is available

src/types/lqg.jl

Lines changed: 126 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,52 @@
22
import Base.getindex
33

44
"""
5-
G = LQG(A,B,C,D, Q1, Q2, R1, R2; qQ=0, qR=0, integrator=false)
6-
G = LQG(sys, args...; kwargs...)
5+
G = LQG(sys::AbstractStateSpace, Q1, Q2, R1, R2; qQ=0, qR=0, integrator=false, M = sys.C)
76
87
Return an LQG object that describes the closed control loop around the process `sys=ss(A,B,C,D)`
98
where the controller is of LQG-type. The controller is specified by weight matrices `Q1,Q2`
109
that penalizes state deviations and control signal variance respectively, and covariance
1110
matrices `R1,R2` which specify state drift and measurement covariance respectively.
1211
This constructor calls [`lqr`](@ref) and [`kalman`](@ref) and forms the closed-loop system.
1312
14-
If `integrator=true`, the resulting controller will have intregral action.
13+
If `integrator=true`, the resulting controller will have integral action.
1514
This is achieved by adding a model of a constant disturbance on the inputs to the system
16-
described by `A,B,C,D`.
15+
described by `sys`.
1716
1817
`qQ` and `qR` can be set to incorporate loop transfer recovery, i.e.,
1918
```julia
2019
L = lqr(A, B, Q1+qQ*C'C, Q2)
2120
K = kalman(A, C, R1+qR*B*B', R2)
2221
```
2322
24-
# Fields
23+
`M` is a matrix that defines the controlled variables `z`, if none is provided, the default is to consider all measured outputs `y` of the system as controlled. The definitions of `z` and `y` are given below
24+
```
25+
y = C*x
26+
z = M*x
27+
```
28+
29+
# Fields and properties
2530
When the LQG-object is populated by the lqg-function, the following fields have been made available
2631
- `L` is the feedback matrix, such that `A-BL` is stable. Note that the length of the state vector (and the width of L) is increased by the number of inputs if the option `integrator=true`.
2732
- `K` is the kalman gain such that `A-KC` is stable
2833
- `sysc` is a dynamical system describing the controller `u=L*inv(A-BL-KC+KDL)Ky`
2934
30-
# Functions
31-
Several other properties of the object are accessible with the indexing function `getindex()`
32-
and are called with the syntax `G[:function]`. The available functions are
35+
Several other properties of the object are accessible as properties. The available properties are
3336
(some have many alternative names, separated with / )
3437
35-
-`G[:cl] / G[:closedloop]` is the closed-loop system, including observer, from reference to output, precompensated to have static gain 1 (`u = −Lx + lᵣr`).
36-
-`G[:S] / G[:Sin]` Input sensitivity function
37-
-`G[:T] / G[:Tin]` Input complementary sensitivity function
38-
-`G[:Sout]` Output sensitivity function
39-
-`G[:Tout]` Output complementary sensitivity function
40-
-`G[:CS]` The transfer function from measurement noise to control signal
41-
-`G[:DS]` The transfer function from input load disturbance to output
42-
-`G[:lt] / G[:looptransfer] / G[:loopgain] = PC`
43-
-`G[:rd] / G[:returndifference] = I + PC`
44-
-`G[:sr] / G[:stabilityrobustness] = I + inv(PC)`
45-
-`G[:sysc] / G[:controller]` Returns the controller as a StateSpace-system
46-
47-
It is also possible to access all fileds using the `G[:symbol]` syntax, the fields are `P
48-
,Q1,Q2,R1,R2,qQ,qR,sysc,L,K,integrator`
38+
- `G.cl / G.closedloop` is the closed-loop system, including observer, from reference to output, precompensated to have static gain 1 (`u = −Lx + lᵣr`).
39+
- `G.S / G.Sin` Input sensitivity function
40+
- `G.T / G.Tin` Input complementary sensitivity function
41+
- `G.Sout` Output sensitivity function
42+
- `G.Tout` Output complementary sensitivity function
43+
- `G.CS` The transfer function from measurement noise to control signal
44+
- `G.DS` The transfer function from input load disturbance to output
45+
- `G.lt / G.looptransfer / G.loopgain = PC`
46+
- `G.rd / G.returndifference = I + PC`
47+
- `G.sr / G.stabilityrobustness = I + inv(PC)`
48+
- `G.sysc / G.controller` Returns the controller as a StateSpace-system
49+
50+
It is also possible to access all fileds using the `G.symbol` syntax, the fields are `P,Q1,Q2,R1,R2,qQ,qR,sysc,L,K,integrator`
4951
5052
# Example
5153
@@ -64,9 +66,9 @@ R2 = 1eye(2)
6466
6567
G = LQG(sys, Q1, Q2, R1, R2, qQ=qQ, qR=qR, integrator=true)
6668
67-
Gcl = G[:cl]
68-
T = G[:T]
69-
S = G[:S]
69+
Gcl = G.cl
70+
T = G.T
71+
S = G.S
7072
sigmaplot([S,T],exp10.(range(-3, stop=3, length=1000)))
7173
stepplot(Gcl)
7274
```
@@ -83,148 +85,174 @@ struct LQG
8385
sysc::LTISystem
8486
L::AbstractMatrix
8587
K::AbstractMatrix
88+
M::AbstractMatrix
8689
integrator::Bool
8790
end
8891

8992
# Provide some constructors
90-
function LQG(A,B,C,D,Q1::AbstractMatrix,Q2::AbstractMatrix,R1::AbstractMatrix,R2::AbstractMatrix; qQ=0, qR=0, integrator=false)
91-
integrator ? _LQGi(A,B,C,D,Q1,Q2,R1,R2, qQ, qR) : _LQG(A,B,C,D,Q1,Q2,R1,R2, qQ, qR)
93+
function LQG(
94+
sys::LTISystem,
95+
Q1::AbstractMatrix,
96+
Q2::AbstractMatrix,
97+
R1::AbstractMatrix,
98+
R2::AbstractMatrix;
99+
qQ = 0,
100+
qR = 0,
101+
integrator = false,
102+
kwargs...,
103+
)
104+
integrator ? _LQGi(sys, Q1, Q2, R1, R2, qQ, qR; kwargs...) :
105+
_LQG(sys, Q1, Q2, R1, R2, qQ, qR; kwargs...)
92106
end # (1) Dispatches to final
93107

94-
function LQG(A,B,C,D,Q1::AbstractVector,Q2::AbstractVector,R1::AbstractVector,R2::AbstractVector; qQ=0, qR=0, integrator=false)
108+
function LQG(
109+
sys::LTISystem,
110+
Q1::AbstractVector,
111+
Q2::AbstractVector,
112+
R1::AbstractVector,
113+
R2::AbstractVector;
114+
qQ = 0,
115+
qR = 0,
116+
integrator = false,
117+
kwargs...,
118+
)
95119
Q1 = diagm(0 => Q1)
96120
Q2 = diagm(0 => Q2)
97121
R1 = diagm(0 => R1)
98122
R2 = diagm(0 => R2)
99-
integrator ? _LQGi(A,B,C,D,Q1,Q2,R1,R2, qQ, qR) : _LQG(A,B,C,D,Q1,Q2,R1,R2, qQ, qR)
123+
integrator ? _LQGi(sys, Q1, Q2, R1, R2, qQ, qR; kwargs...) :
124+
_LQG(sys, Q1, Q2, R1, R2, qQ, qR; kwargs...)
100125
end # (2) Dispatches to final
101126

102-
# (3) For conveniece of sending a sys, dispatches to (1/2)
103-
LQG(sys::LTISystem, args...; kwargs...) = LQG(sys.A,sys.B,sys.C,sys.D,args...; kwargs...)
104-
105-
106127

107128
# This function does the actual initialization in the standard case withput integrator
108-
function _LQG(A,B,C,D, Q1, Q2, R1, R2, qQ, qR)
109-
n = size(A,1)
110-
m = size(B,2)
111-
p = size(C,1)
112-
L = lqr(A, B, Q1+qQ*C'C, Q2)
113-
K = kalman(A, C, R1+qR*B*B', R2)
129+
function _LQG(sys::LTISystem, Q1, Q2, R1, R2, qQ, qR; M = sys.C)
130+
A, B, C, D = ssdata(sys)
131+
n = size(A, 1)
132+
m = size(B, 2)
133+
p = size(C, 1)
134+
L = lqr(A, B, Q1 + qQ * C'C, Q2)
135+
K = kalman(A, C, R1 + qR * B * B', R2)
114136

115137
# Controller system
116-
Ac=A-B*L-K*C+K*D*L
117-
Bc=K
118-
Cc=L
119-
Dc=zero(D')
120-
sysc = ss(Ac,Bc,Cc,Dc)
138+
Ac = A - B*L - K*C + K*D*L
139+
Bc = K
140+
Cc = L
141+
Dc = zero(D')
142+
sysc = ss(Ac, Bc, Cc, Dc)
121143

122-
return LQG(ss(A,B,C,D),Q1,Q2,R1,R2, qQ, qR, sysc, L, K, false)
144+
return LQG(ss(A, B, C, D), Q1, Q2, R1, R2, qQ, qR, sysc, L, K, M, false)
123145
end
124146

125147

126148
# This function does the actual initialization in the integrator case
127-
function _LQGi(A,B,C,D, Q1, Q2, R1, R2, qQ, qR)
128-
n = size(A,1)
129-
m = size(B,2)
130-
p = size(C,1)
149+
function _LQGi(sys::LTISystem, Q1, Q2, R1, R2, qQ, qR; M = sys.C)
150+
A, B, C, D = ssdata(sys)
151+
n = size(A, 1)
152+
m = size(B, 2)
153+
p = size(C, 1)
131154

132155
# Augment with disturbance model
133-
Ae = [A B; zeros(m,n+m)]
134-
Be = [B;zeros(m,m)]
135-
Ce = [C zeros(p,m)]
156+
Ae = [A B; zeros(m, n + m)]
157+
Be = [B; zeros(m, m)]
158+
Ce = [C zeros(p, m)]
136159
De = D
137160

138-
L = lqr(A, B, Q1+qQ*C'C, Q2)
161+
L = lqr(A, B, Q1 + qQ * C'C, Q2)
139162
Le = [L I]
140-
K = kalman(Ae, Ce, R1+qR*Be*Be', R2)
163+
K = kalman(Ae, Ce, R1 + qR * Be * Be', R2)
141164

142165
# Controller system
143-
Ac=Ae-Be*Le-K*Ce+K*De*Le
144-
Bc=K
145-
Cc=Le
146-
Dc=zero(D')
147-
sysc = ss(Ac,Bc,Cc,Dc)
166+
Ac = Ae - Be*Le - K*Ce + K*De*Le
167+
Bc = K
168+
Cc = Le
169+
Dc = zero(D')
170+
sysc = ss(Ac, Bc, Cc, Dc)
148171

149-
LQG(ss(A,B,C,D),Q1,Q2,R1,R2, qQ, qR, sysc, Le, K, true)
172+
LQG(ss(A, B, C, D), Q1, Q2, R1, R2, qQ, qR, sysc, Le, K, M, true)
150173
end
151174

175+
@deprecate getindex(G::LQG, s::Symbol) getfield(G, s)
152176

153-
function Base.getindex(G::LQG, s)
177+
function Base.getproperty(G::LQG, s::Symbol)
178+
if s (:L, :K, :Q1, :Q2, :R1, :R2, :qQ, :qR, :integrator, :P, :M)
179+
return getfield(G, s)
180+
end
154181
s === :A && return G.P.A
155182
s === :B && return G.P.B
156183
s === :C && return G.P.C
157184
s === :D && return G.P.D
158-
s === :L && return G.L
159-
s === :K && return G.K
160-
s === :Q1 && return G.Q1
161-
s === :Q2 && return G.Q2
162-
s === :R1 && return G.R1
163-
s === :R2 && return G.R2
164-
s === :qQ && return G.qQ
165-
s === :qR && return G.qR
166-
s [:sys, :P] && return G.P
167-
s [:sysc, :controller] && return G.sysc
168-
s === :integrator && return G.integrator
185+
s (:sys, :P) && return getfield(G, :P)
186+
s (:sysc, :controller) && return getfield(G, :sysc)
169187

170188
A = G.P.A
171189
B = G.P.B
172190
C = G.P.C
173191
D = G.P.D
192+
M = G.M
174193

175194
L = G.L
176195
K = G.K
177196
P = G.P
178197
sysc = G.sysc
179198

180-
n = size(A,1)
181-
m = size(B,2)
182-
p = size(C,1)
199+
n = size(A, 1)
200+
m = size(B, 2)
201+
p = size(C, 1)
202+
pm = size(M, 1)
183203

184204
# Extract interesting values
185205
if G.integrator # Augment with disturbance model
186-
A = [A B; zeros(m,n+m)]
187-
B = [B;zeros(m,m)]
188-
C = [C zeros(p,m)]
206+
A = [A B; zeros(m, n + m)]
207+
B = [B; zeros(m, m)]
208+
C = [C zeros(p, m)]
209+
M = [M zeros(pm, m)]
189210
D = D
190211
end
191212

192-
PC = P*sysc # Loop gain
213+
PC = P * sysc # Loop gain
193214

194-
if s [:cl, :closedloop, :ry] # Closed-loop system
215+
if s (:cl, :closedloop, :ry) # Closed-loop system
216+
# Compensate for static gain, pp. 264 G.L.
217+
dcg = P.C * ((P.B * L[:, 1:n] - P.A) \ P.B)
195218
Acl = [A-B*L B*L; zero(A) A-K*C]
196-
Bcl = [B; zero(B)]
197-
Ccl = [C zero(C)]
198-
Bcl = Bcl/(P.C*inv(P.B*L[:,1:n]-P.A)*P.B) # B*lᵣ # Always normalized with nominal plant static gain
199-
return syscl = ss(Acl,Bcl,Ccl,0)
200-
elseif s [:Sin, :S] # Sensitivity function
201-
return feedback(ss(Matrix{numeric_type(PC)}(I, m, m)),PC)
202-
elseif s [:Tin, :T] # Complementary sensitivity function
219+
Bcl = [B / dcg; zero(B)]
220+
Ccl = [M zero(M)]
221+
# rank(dcg) == size(A,1) && (Bcl = Bcl / dcg) # B*lᵣ # Always normalized with nominal plant static gain
222+
syscl = ss(Acl, Bcl, Ccl, 0)
223+
# return ss(A-B*L, B/dcg, M, 0)
224+
return syscl
225+
elseif s (:Sin, :S) # Sensitivity function
226+
return feedback(ss(Matrix{numeric_type(PC)}(I, m, m)), PC)
227+
elseif s (:Tin, :T) # Complementary sensitivity function
203228
return feedback(PC)
229+
# return ss(Acl, I(size(Acl,1)), Ccl, 0)[1,2]
204230
elseif s === :Sout # Sensitivity function, output
205-
return feedback(ss(Matrix{numeric_type(sys_c)}(I, m, m)),sysc*P)
231+
return feedback(ss(Matrix{numeric_type(sysc)}(I, m, m)), sysc * P)
206232
elseif s === :Tout # Complementary sensitivity function, output
207-
return feedback(sysc*P)
233+
return feedback(sysc * P)
208234
elseif s === :PS # Load disturbance to output
209-
return P*G[:S]
235+
return P * G.S
210236
elseif s === :CS # Noise to control signal
211-
return sysc*G[:S]
212-
elseif s [:lt, :looptransfer, :loopgain]
237+
return sysc * G.S
238+
elseif s (:lt, :looptransfer, :loopgain)
213239
return PC
214-
elseif s [:rd, :returndifference]
215-
return ss(Matrix{numeric_type(PC)}(I, p, p)) + PC
216-
elseif s [:sr, :stabilityrobustness]
217-
return ss(Matrix{numeric_type(PC)}(I, p, p)) + inv(PC)
240+
elseif s (:rd, :returndifference)
241+
return ss(Matrix{numeric_type(PC)}(I, p, p)) + PC
242+
elseif s (:sr, :stabilityrobustness)
243+
return ss(Matrix{numeric_type(PC)}(I, p, p)) + inv(PC)
218244
end
219245
error("The symbol $s does not have a function associated with it.")
220246
end
221247

222-
Base.:(==)(G1::LQG, G2::LQG) = G1.K == G2.K && G1.L == G2.L && G1.P == G2.P && G1.sysc == G2.sysc
248+
Base.:(==)(G1::LQG, G2::LQG) =
249+
G1.K == G2.K && G1.L == G2.L && G1.P == G2.P && G1.sysc == G2.sysc
223250

224251

225252
plot(G::LQG) = gangoffourplot(G)
253+
226254
function gangoffour(G::LQG)
227-
return G[:S], G[:PS], G[:CS], G[:T]
255+
G.S, G.PS, G.CS, G.T
228256
end
229257

230258
function gangoffourplot(G::LQG; kwargs...)

test/test_lqg.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,16 +30,16 @@ Gi = LQG(sys, Q1, Q2, R1, R2, qQ=qQ, qR=qR, integrator=true)
3030
gangoffourplot(Gi) # Test that it at least does not error
3131
@test approxsetequal(eigvals(Gi.sysc.A), [0.0, 0.0, -47.4832, -44.3442, -3.40255, -1.15355 ], rtol = 1e-3)
3232

33-
@test approxsetequal(eigvals(G[:cl].A), [-1.0, -14.1774, -2.21811, -14.3206, -1.60615, -22.526, -1.0, -14.1774], rtol=1e-3)
34-
@test approxsetequal(eigvals(G[:T].A), [-22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
35-
@test approxsetequal(eigvals(G[:S].A), [-22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
36-
@test approxsetequal(eigvals(G[:CS].A), [-31.6209+0.0im, -1.40629+0.0im, -15.9993+0.911174im, -15.9993-0.911174im, -22.526+0.0im, -2.21811+0.0im, -1.60615+0.0im, -14.3206+0.0im, -14.1774+0.0im, -1.0+0.0im, -1.0+0.0im, -14.1774+0.0im], rtol=1e-3)
37-
@test approxsetequal(eigvals(G[:PS].A), [-1.0, -1.0, -3.0, -1.0, -22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
33+
@test approxsetequal(eigvals(G.cl.A), [-1.0, -14.1774, -2.21811, -14.3206, -1.60615, -22.526, -1.0, -14.1774], rtol=1e-3)
34+
@test approxsetequal(eigvals(G.T.A), [-22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
35+
@test approxsetequal(eigvals(G.S.A), [-22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
36+
@test approxsetequal(eigvals(G.CS.A), [-31.6209+0.0im, -1.40629+0.0im, -15.9993+0.911174im, -15.9993-0.911174im, -22.526+0.0im, -2.21811+0.0im, -1.60615+0.0im, -14.3206+0.0im, -14.1774+0.0im, -1.0+0.0im, -1.0+0.0im, -14.1774+0.0im], rtol=1e-3)
37+
@test approxsetequal(eigvals(G.PS.A), [-1.0, -1.0, -3.0, -1.0, -22.526, -2.21811, -1.60615, -14.3206, -14.1774, -1.0, -1.0, -14.1774], rtol=1e-3)
3838

3939

40-
@test approxsetequal(eigvals(Gi[:cl].A), [-1.0, -44.7425, -44.8455, -2.23294, -4.28574, -2.06662, -0.109432, -1.31779, -0.78293, -1.0, 0.0, 0.0], rtol=1e-3)
41-
@test approxsetequal(eigvals(Gi[:T].A), [-44.7425, -44.8455, -4.28574, -0.109432, -2.23294, -2.06662, -1.31779, -0.78293, -1.0, -1.0], rtol=1e-3)
42-
@test approxsetequal(eigvals(Gi[:S].A), [-44.7425, -44.8455, -4.28574, -0.109432, -2.23294, -2.06662, -1.31779, -0.78293, -1.0, -1.0], rtol=1e-3)
40+
@test approxsetequal(eigvals(Gi.cl.A), [-1.0, -44.7425, -44.8455, -2.23294, -4.28574, -2.06662, -0.109432, -1.31779, -0.78293, -1.0, 0.0, 0.0], rtol=1e-3)
41+
@test approxsetequal(eigvals(Gi.T.A), [-44.7425, -44.8455, -4.28574, -0.109432, -2.23294, -2.06662, -1.31779, -0.78293, -1.0, -1.0], rtol=1e-3)
42+
@test approxsetequal(eigvals(Gi.S.A), [-44.7425, -44.8455, -4.28574, -0.109432, -2.23294, -2.06662, -1.31779, -0.78293, -1.0, -1.0], rtol=1e-3)
4343

4444

4545

0 commit comments

Comments
 (0)