Skip to content

Commit c15039a

Browse files
committed
add integrator option to extended_controller
1 parent aec29fe commit c15039a

File tree

3 files changed

+89
-15
lines changed

3 files changed

+89
-15
lines changed

docs/src/lqg_disturbance.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,31 @@ plot(f1, f2, titlefontsize=10)
9999
```
100100

101101
We see that we now have a slightly larger disturbance response than before, but in exchange, we lowered the peak sensitivity and complimentary sensitivity from (1.5, 1.31) to (1.25, 1.11), a more robust design. We also reduced the amplification of measurement noise ($CS = C/(1+PC)$). To be really happy with the design, we should probably add high-frequency roll-off as well.
102+
103+
## Extracting the controller
104+
Above, we have simulated the closed-loop response by creating closed-loop functions directly using [`G_PS`](@ref) and [`comp_sensitivity`](@ref). To extract the controller used underneath the hood in these examples, we may call [`observer_controller`](@ref) or [`extended_controller`](@ref), depending on whether we want a feedback controller only or a 2 DOF controller that takes separate reference inputs.
105+
106+
107+
## Explicit error integration
108+
Integral action can also be added to an LQG controller by calling [`extended_controller`](@ref) with the option `output_ref = true` and by providing an integrator gain matrix `Li`. We demonstrate this below
109+
110+
```@example LQG_DIST
111+
nx = G.nx
112+
nu = G.nu
113+
ny = G.ny
114+
x0 = zeros(G.nx) # Initial condition
115+
116+
Q1 = 100diagm(ones(G.nx)) # state cost matrix
117+
Q2 = 0.01diagm(ones(nu)) # control cost matrix
118+
119+
R1 = 0.001I(nx) # State noise covariance
120+
R2 = I(ny) # measurement noise covariance
121+
prob = LQGProblem(G, Q1, Q2, R1, R2)
122+
123+
Li = [3;;] # Integral gain
124+
C = extended_controller(prob; output_ref = true, Li)
125+
Gcl = feedback(G, -ss(C), Z1 = [1], Z2=[1], U2=(1:ny) .+ ny, Y1 = :, W2=[], W1=[1], pos_feedback=true)
126+
127+
res = lsim(Gcl, disturbance, 100)
128+
plot(res, ylabel=["y" "u"]); ylims!((-0.05, 0.3), sp = 1)
129+
```

src/lqg.jl

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ function extended_controller(K::AbstractStateSpace)
271271
end
272272

273273
"""
274-
extended_controller(l::LQGProblem, L = lqr(l), K = kalman(l); z = nothing)
274+
extended_controller(l::LQGProblem, L = lqr(l), K = kalman(l); z = nothing, output_ref = false, Li = nothing)
275275
276276
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`.
277277
@@ -290,25 +290,57 @@ system_mapping(Ce) == -C
290290
```
291291
292292
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.
293+
If the option `output_fer = true` is set, the function returns a controller that expects output references `yᵣ` instead of state references `xᵣ`. In this case, `ny` integrators are added that integrate the error \$e = yᵣ - y\$. The integrator gain matrix `Li` of size `(nu, ny)` must be provided in this case. This option is sometimes referred to as "Tracking LQG" or `lqgtrack`.
293294
"""
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)
297-
Ac = A - B*L - K*C + K*D*L # 8.26b
295+
function extended_controller(l::LQGProblem, L::AbstractMatrix = lqr(l), K::AbstractMatrix = kalman(l); z::Union{Nothing, AbstractVector} = nothing, output_ref = false, Li = nothing)
296+
P = system_mapping(l, identity)
298297
(; nx, nu, ny) = P
299-
B1 = zeros(nx, nx) # dynamics not affected by r
300-
# l.D21 does not appear here, see comment in kalman
301-
B2 = K # input y
302-
D21 = L # L*xᵣ # should be D21?
303-
C2 = -L # - L*x̂
304-
C1 = zeros(0, nx)
298+
A,B,C,D = ssdata(P)
299+
if output_ref
300+
Li === nothing && throw(ArgumentError("The integrator gain matrix Li must be provided when output_ref is true"))
301+
size(Li) == (nu, ny) || throw(ArgumentError("The integrator gain matrix Li must have size (nu, ny)"))
302+
# A,B,C,D = l.sys.A, l.sys.B2, l.sys.C1, l.sys.D12
303+
304+
if iscontinuous(P)
305+
Ac = [
306+
(A - B*L - K*C + K*D*L) (K*D*Li-B*Li)
307+
zeros(ny, nx+ny)
308+
]
309+
else
310+
Ac = [
311+
(A - B*L - K*C + K*D*L) (K*D*Li-B*Li)
312+
zeros(ny, nx) P.Ts*I(ny)
313+
]
314+
end
315+
B1 = [
316+
zeros(nx, ny)
317+
I(ny)
318+
]
319+
B2 = [
320+
K
321+
-I(ny)
322+
]
323+
C1 = zeros(0, nx+ny)
324+
C2 = [-L -Li]
325+
D21 = 0
326+
327+
else
328+
# State references
329+
Ac = A - B*L - K*C + K*D*L # 8.26b
330+
B1 = zeros(nx, nx) # dynamics not affected by r
331+
# l.D21 does not appear here, see comment in kalman
332+
B2 = K # input y
333+
D21 = L # L*xᵣ
334+
C1 = zeros(0, nx)
335+
C2 = -L # - L*x̂
336+
end
305337
Ce0 = ss(Ac, B1, B2, C1, C2; D21, Ts = l.timeevol)
306338
if z === nothing
307339
return Ce0
308340
end
309-
r = 1:nx
341+
r = 1:(output_ref ? ny : nx)
310342
Ce = ss(Ce0)
311-
cl = feedback(P, Ce, Z1 = z, Z2=[], U2=(1:ny) .+ nx, Y1 = :, W2=r, W1=[])
343+
cl = feedback(P, Ce, Z1 = z, Z2=[], U2=(1:ny) .+ (output_ref ? ny : nx), Y1 = :, W2=r, W1=[], pos_feedback=true)
312344
Ce0, cl
313345
end
314346

test/test_lqg.jl

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ Cfb = observer_controller(lqg)
502502
# Method 3, using the observer controller and the ff_controller
503503
cl = feedback(system_mapping(lqg), observer_controller(lqg))*RobustAndOptimalControl.ff_controller(lqg, comp_dc = true)
504504
@test dcgain(cl)[1,2] 1 rtol=1e-8
505+
@test isstable(cl)
505506

506507
# Method 4: Build compensation into R and compute the closed-loop DC gain, should be 1
507508
R = named_ss(ss(dc_gain_compensation*I(4)), "R") # Reference filter
@@ -515,23 +516,36 @@ connections = [
515516
]
516517
cl = RobustAndOptimalControl.connect([lsys, Cry], connections; w1 = R.u, z1 = [:x, :phi])
517518
@test inv(dcgain(cl)[1,2]) 1 rtol=1e-8
519+
@test isstable(cl)
518520

519521

520522
# Method 5: close the loop manually with reference as input and position as output
521523

522524
R = named_ss(ss(I(4)), "R") # Reference filter, used for signal names only
523525
Ce = named_ss(ss(extended_controller(lqg)); x = :xC, y = :u, u = [:Ry^4; :y^lqg.ny])
524-
cl = feedback(lsys, Ce, z1 = [:x], z2=[], u2=:y^2, y1 = [:x, :phi], w2=[:Ry2], w1=[])
526+
cl = feedback(lsys, Ce, z1 = [:x], z2=[], u2=:y^2, y1 = [:x, :phi], w2=[:Ry2], w1=[], pos_feedback=true)
525527
@test inv(dcgain(cl)[]) dc_gain_compensation rtol=1e-8
528+
@test isstable(cl)
526529

527-
cl = feedback(lsys, Ce, z1 = [:x, :phi], z2=[], u2=:y^2, y1 = [:x, :phi], w2=[:Ry2], w1=[])
530+
cl = feedback(lsys, Ce, z1 = [:x, :phi], z2=[], u2=:y^2, y1 = [:x, :phi], w2=[:Ry2], w1=[], pos_feedback=true)
528531
@test pinv(dcgain(cl)) [dc_gain_compensation 0] atol=1e-8
532+
@test isstable(cl)
529533

530534
# Method 6: use the z argument to extended_controller to compute the closed-loop TF
531535

532536
Ce, cl = extended_controller(lqg, z=[1, 2])
533537
@test pinv(dcgain(cl)[1,2]) dc_gain_compensation atol=1e-8
538+
@test isstable(cl)
534539

535540
Ce, cl = extended_controller(lqg, z=[1])
536541
@test pinv(dcgain(cl)[1,2]) dc_gain_compensation atol=1e-8
542+
@test isstable(cl)
543+
544+
## Output references
545+
Li = [1 0]
546+
Cry2, cl = extended_controller(lqg; output_ref = true, Li, z=[1])
547+
cl = minreal(cl)
548+
549+
@test dcgain(cl) [1 0] atol=1e-12
550+
@test isstable(cl)
537551

0 commit comments

Comments
 (0)