Skip to content

Commit d0f3ab0

Browse files
authored
Merge pull request #105 from JuliaControl/better_lqg
Better lqg
2 parents e95213e + bf00432 commit d0f3ab0

File tree

8 files changed

+255
-33
lines changed

8 files changed

+255
-33
lines changed

src/ExtendedStateSpace.jl

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -654,7 +654,15 @@ Partition `P` into an [`ExtendedStateSpace`](@ref).
654654
"""
655655
function partition(P::AbstractStateSpace; u=nothing, y=nothing,
656656
w = nothing,
657-
z = nothing
657+
z = nothing,
658+
B1 = nothing,
659+
B2 = nothing,
660+
C1 = nothing,
661+
C2 = nothing,
662+
D11 = nothing,
663+
D12 = nothing,
664+
D21 = nothing,
665+
D22 = nothing,
658666
)
659667
if w === nothing
660668
w = setdiff(1:P.nu, u)
@@ -672,8 +680,16 @@ function partition(P::AbstractStateSpace; u=nothing, y=nothing,
672680
y = vcat(y)
673681
z = vcat(z)
674682
w = vcat(w)
675-
ss(P.A, P.B[:, w], P.B[:, u], P.C[z, :], P.C[y, :],
676-
P.D[z, w], P.D[z, u], P.D[y, w], P.D[y, u], P.timeevol)
683+
ss(P.A,
684+
B1 === nothing ? P.B[:, w] : B1,
685+
B2 === nothing ? P.B[:, u] : B2,
686+
C1 === nothing ? P.C[z, :] : C1,
687+
C2 === nothing ? P.C[y, :] : C2 ,
688+
D11 === nothing ? P.D[z, w] : D11,
689+
D12 === nothing ? P.D[z, u] : D12,
690+
D21 === nothing ? P.D[y, w] : D21,
691+
D22 === nothing ? P.D[y, u] : D22,
692+
P.timeevol)
677693
end
678694

679695
"""

src/RobustAndOptimalControl.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@ using DescriptorSystems: dss
2828

2929
using ChainRulesCore
3030

31-
export show_construction, vec2sys
32-
include("utils.jl")
33-
3431
export dss, hinfnorm2, linfnorm2, h2norm, hankelnorm, nugap, νgap, baltrunc2, stab_unstab, baltrunc_unstab, baltrunc_coprime
3532
include("descriptor.jl")
3633

@@ -83,4 +80,7 @@ include("mimo_diskmargin.jl")
8380
export nu_reduction, nu_reduction_recursive
8481
include("mcm_nugap.jl")
8582

83+
export show_construction, vec2sys
84+
include("utils.jl")
85+
8686
end

src/lqg.jl

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ Several functions are defined for instances of `LQGProblem`
6868
- [`observer_controller`](@ref)
6969
7070
A video tutorial on how to use the LQG interface is available [here](https://youtu.be/NuAxN1mGCPs)
71+
72+
## Introduction of references
73+
The most principled way of introducing references is to add references as measured inputs to the extended statespace model, and to let the performance output `z` be the differences between the references and the outputs for which references are provided.
74+
75+
A less cumbersome way is not not consider references when constructing the `LQGProblem`, and instead pass the `z` keyword arugment to [`extended_controller`](@ref) in order to obtain a closed-loop system from state references to controlled outputs, and use some form of inverse of the DC gain of this system (or one of its subsystems) to pre-compensate the reference input.
7176
"""
7277
struct LQGProblem
7378
sys::ExtendedStateSpace
@@ -266,15 +271,15 @@ function extended_controller(K::AbstractStateSpace)
266271
end
267272

268273
"""
269-
extended_controller(l::LQGProblem, L = lqr(l), K = kalman(l))
274+
extended_controller(l::LQGProblem, L = lqr(l), K = kalman(l); z = nothing)
270275
271-
Returns an expression for the controller that is obtained when state-feedback `u = -L(xᵣ-x̂)` is combined with a Kalman filter with gain `K` that produces state estimates x̂. The controller is an instance of `ExtendedStateSpace` where `C2 = -L, D21 = L` and `B2 = K`.
276+
Returns a statespace system representing the controller that is obtained when state-feedback `u = L(xᵣ-x̂)` is combined with a Kalman filter with gain `K` that produces state estimates x̂. The controller is an instance of `ExtendedStateSpace` where `C2 = -L, D21 = L` and `B2 = K`.
272277
273-
The returned system has *inputs* `[xᵣ; y]` and outputs the control signal `u`. If a reference model `R` is used to generate state references `xᵣ`, the controller from `e = ry - y -> u` is given by
278+
The returned system has *inputs* `[xᵣ; y]` and outputs the control signal `u`. If a reference model `R` is used to generate state references `xᵣ`, the controller from `(ry, y) -> u` where `ry - y = e` is given by
274279
```julia
275280
Ce = extended_controller(l)
276-
Ce = named_ss(Ce; x = :xC, y = :u, u = [R.y; :y^l.ny]) # Name the inputs of Ce the same as the outputs of `R`.
277-
connect([R, Ce]; u1 = R.y, y1 = R.y, w1 = [:ry^l.ny, :y^l.ny], z1=[:u])
281+
Ce = named_ss(ss(Ce); x = :xC, y = :u, u = [R.y; :y^l.ny]) # Name the inputs of Ce the same as the outputs of `R`.
282+
connect([R, Ce]; u1 = R.y, y1 = R.y, w1 = [R.u; :y^l.ny], z1=[:u])
278283
```
279284
280285
Since the negative part of the feedback is built into the returned system, we have
@@ -283,20 +288,31 @@ C = observer_controller(l)
283288
Ce = extended_controller(l)
284289
system_mapping(Ce) == -C
285290
```
291+
292+
Please note, without the reference pre-filter, the DC gain from references to controlled outputs may not be identity. If a vector of output indices is provided through the keyword argument `z`, the closed-loop system from state reference `xᵣ` to outputs `z` is returned as a second return argument. The inverse of the DC-gain of this closed-loop system may be useful to compensate for the DC-gain of the controller.
286293
"""
287-
function extended_controller(l::LQGProblem, L = lqr(l), K = kalman(l))
288-
A,B,C,D = ssdata(system_mapping(l))
294+
function extended_controller(l::LQGProblem, L::AbstractMatrix = lqr(l), K::AbstractMatrix = kalman(l); z::Union{Nothing, AbstractVector} = nothing)
295+
P = system_mapping(l)
296+
A,B,C,D = ssdata(P)
289297
Ac = A - B*L - K*C + K*D*L # 8.26b
290-
nx = l.nx
298+
(; nx, nu, ny) = P
291299
B1 = zeros(nx, nx) # dynamics not affected by r
292300
# l.D21 does not appear here, see comment in kalman
293301
B2 = K # input y
294302
D21 = L # L*xᵣ # should be D21?
295303
C2 = -L # - L*x̂
296304
C1 = zeros(0, nx)
297-
ss(Ac, B1, B2, C1, C2; D21, Ts = l.timeevol)
305+
Ce0 = ss(Ac, B1, B2, C1, C2; D21, Ts = l.timeevol)
306+
if z === nothing
307+
return Ce0
308+
end
309+
r = 1:nx
310+
Ce = ss(Ce0)
311+
cl = feedback(P, Ce, Z1 = z, Z2=[], U2=(1:ny) .+ nx, Y1 = :, W2=r, W1=[])
312+
Ce0, cl
298313
end
299314

315+
300316
"""
301317
observer_controller(l::LQGProblem, L = lqr(l), K = kalman(l))
302318
@@ -339,23 +355,39 @@ end
339355
Return the feedforward controller ``C_{ff}`` that maps references to plant inputs:
340356
``u = C_{fb}y + C_{ff}r``
341357
358+
The following should hold
359+
```
360+
Cff = RobustAndOptimalControl.ff_controller(l)
361+
Cfb = observer_controller(l)
362+
Gcl = feedback(system_mapping(l), Cfb) * Cff # Note the comma in feedback, P/(I + PC) * Cff
363+
dcgain(Gcl) ≈ I # Or some I-like non-square matrix
364+
```
365+
366+
Note, if [`extended_controller`](@ref) is used, the DC-gain compensation above cannot be used. The [`extended_controller`](@ref) assumes that the references enter like `u = L(xᵣ - x̂)`.
367+
342368
See also [`observer_controller`](@ref).
343369
"""
344-
function ff_controller(l::LQGProblem, L = lqr(l), K = kalman(l))
370+
function ff_controller(l::LQGProblem, L = lqr(l), K = kalman(l); comp_dc = true)
345371
Ae,Be,Ce,De = ssdata(system_mapping(l, identity))
346372
Ac = Ae - Be*L - K*Ce + K*De*L # 8.26c
347-
Bc = Be*static_gain_compensation(l, L)
348373
Cc = L
349374
Dc = 0
350-
return 1 - ss(Ac, Bc, Cc, Dc, l.timeevol)
375+
if comp_dc
376+
Lr = static_gain_compensation(l, L)
377+
Bc = Be*Lr
378+
return Lr - ss(Ac, Bc, Cc, Dc, l.timeevol)
379+
else
380+
Bc = Be
381+
return I(size(Cc, 1)) - ss(Ac, Bc, Cc, Dc, l.timeevol)
382+
end
351383
end
352384

353385
"""
354386
closedloop(l::LQGProblem, L = lqr(l), K = kalman(l))
355387
356388
Closed-loop system as defined in Glad and Ljung eq. 8.28. Note, this definition of closed loop is not the same as lft(P, K), which has B1 instead of B2 as input matrix. Use `lft(l)` to get the system from disturbances to controlled variables `w -> z`.
357389
358-
The return value will be the closed loop from reference only, other disturbance signals (B1) are ignored. See [`feedback`](@ref) for a more advanced option.
390+
The return value will be the closed loop from filtred reference only, other disturbance signals (B1) are ignored. See [`feedback`](@ref) for a more advanced option. This function assumes that the control signal is computed as `u = r̃ - Lx̂` (not `u = L(xᵣ - x̂)`), i.e., the feedforward signal `r̃` is added directly to the plant input. `r̃` must thus be produced by an inverse-like model that takes state references and output the feedforward signal.
359391
360392
Use `static_gain_compensation` to adjust the gain from references acting on the input B2, `dcgain(closedloop(l))*static_gain_compensation(l) ≈ I`
361393
"""

src/named_systems2.jl

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,7 @@ function ControlSystemsBase.feedback(s1::NamedStateSpace{T}, s2::NamedStateSpace
401401
@assert sys.nu == length(W1) + length(W2)
402402
@assert sys.ny == length(Z1) + length(Z2)
403403
@assert sys.nx == length(x1)
404-
nsys = NamedStateSpace{T,typeof(sys)}(sys, x1, s1.u[[W1; W2]], s1.y[[Z1; Z2]], "")
404+
nsys = NamedStateSpace{T,typeof(sys)}(sys, x1, [s1.u[W1]; s2.u[W2]], [s1.y[Z1]; s2.y[Z2]], "")
405405
sminreal(nsys)
406406
end
407407

@@ -697,30 +697,51 @@ end
697697

698698
function partition(P::NamedStateSpace; u=nothing, y=nothing,
699699
w = nothing,
700-
z = nothing
700+
z = nothing,
701+
B1 = nothing,
702+
B2 = nothing,
703+
C1 = nothing,
704+
C2 = nothing,
705+
D11 = nothing,
706+
D12 = nothing,
707+
D21 = nothing,
708+
D22 = nothing,
701709
)
702710
if w === nothing
703-
w = names2indices(setdiff(P.u, u), P.u)
704-
u = names2indices(u, P.u)
711+
inds = names2indices(u, P.u)
712+
w = setdiff(1:P.nu, inds)
713+
u = inds
705714
end
706715
if z === nothing
707-
z = names2indices(setdiff(P.y, y), P.y)
708-
y = names2indices(y, P.y)
716+
inds = names2indices(y, P.y)
717+
z = setdiff(1:P.ny, inds)
718+
y = inds
709719
end
710720
if u === nothing
711-
u = names2indices(setdiff(P.u, w), P.u)
712-
w = names2indices(w, P.u)
721+
inds = names2indices(w, P.u)
722+
u = setdiff(1:P.nu, inds)
723+
w = inds
713724
end
714725
if y === nothing
715-
y = names2indices(setdiff(P.y, z), P.y)
716-
z = names2indices(z, P.y)
726+
inds = names2indices(z, P.y)
727+
y = setdiff(1:P.ny, inds)
728+
z = inds
717729
end
718730
u = vcat(u)
719731
y = vcat(y)
720732
z = vcat(z)
721733
w = vcat(w)
722-
ss(P.A, P.B[:, w], P.B[:, u], P.C[z, :], P.C[y, :],
723-
P.D[z, w], P.D[z, u], P.D[y, w], P.D[y, u], P.timeevol)
734+
ss(P.A,
735+
B1 === nothing ? P.B[:, w] : B1,
736+
B2 === nothing ? P.B[:, u] : B2,
737+
C1 === nothing ? P.C[z, :] : C1,
738+
C2 === nothing ? P.C[y, :] : C2 ,
739+
D11 === nothing ? P.D[z, w] : D11,
740+
D12 === nothing ? P.D[z, u] : D12,
741+
D21 === nothing ? P.D[y, w] : D21,
742+
D22 === nothing ? P.D[y, u] : D22,
743+
P.timeevol
744+
)
724745
end
725746

726747
function CS.c2d(s::NamedStateSpace{Continuous}, Ts::Real, method::Symbol = :zoh, args...;

src/utils.jl

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ function show_construction(io::IO, sys::LTISystem; name = "temp", letb = true)
4848
nothing
4949
end
5050

51+
function show_construction(io::IO, sys::NamedStateSpace; name = "temp", letb = true)
52+
# sys = StateSpace(sys)
53+
letb && println(io, "$name = let")
54+
prestr = letb ? " " : ""
55+
println(io, prestr*"$(name)A = ", sys.A)
56+
println(io, prestr*"$(name)B = ", sys.B)
57+
println(io, prestr*"$(name)C = ", sys.C)
58+
println(io, prestr*"$(name)D = ", sys.D)
59+
letb || print(io, "$name = ")
60+
if isdiscrete(sys)
61+
println(io, prestr*"named_ss(ss($(name)A, $(name)B, $(name)C, $(name)D), $(sys.Ts), x=$(sys.x), u=$(sys.u), y=$(sys.y))")
62+
else
63+
println(io, prestr*"named_ss(ss($(name)A, $(name)B, $(name)C, $(name)D), x=$(sys.x), u=$(sys.u), y=$(sys.y))")
64+
end
65+
letb && println(io, "end")
66+
nothing
67+
end
68+
5169
function Base.vec(sys::LTISystem)
5270
[vec(sys.A); vec(sys.B); vec(sys.C); vec(sys.D)]
5371
end

test/test_extendedstatespace.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using RobustAndOptimalControl
22
using ControlSystemsBase
33
using ControlSystemsBase: balance_statespace
4+
using Test
45

56
G = ssrand(3,4,5)
67

0 commit comments

Comments
 (0)