Skip to content

Commit 8a84ec4

Browse files
committed
Merge branch 'master' of github.com:JuliaControl/RobustAndOptimalControl.jl
2 parents 2cb4498 + c1de12d commit 8a84ec4

15 files changed

Lines changed: 489 additions & 76 deletions

examples/hinf_example_tank.jl

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,10 @@ using ControlSystems, RobustAndOptimalControl
22
using Plots
33
using LinearAlgebra
44
"""
5-
This is a simple SISO example with integrator dynamics corresponding to the
6-
quad tank process in the lab.
7-
8-
The example can be set to visualize and save plots using the variables
9-
makeplots - true/false (true if plots are to be generated, false for testing)
10-
SavePlots - true/false (true if plots are to be saved, false for testing)
5+
This is a simple SISO example with integrator dynamics corresponding to a
6+
quad tank process.
117
"""
12-
makeplots = true
8+
139

1410
# Define the proces parameters
1511
k1, k2, kc, g = 3.33, 3.35, 0.5, 981
@@ -57,16 +53,10 @@ C, γ = hinfsynthesize(P)
5753
Pcl, S, CS, T = hinfsignals(P, G, C)
5854

5955
## Plot the specifications
60-
# TODO figure out why I get segmentation errors when using ss instead of tf for
61-
# the weighting functions, makes no sense at all
62-
if makeplots
63-
specificationplot([S, CS, T], [WS[1,1], 0.01, WT[1,1]], γ)
56+
specificationplot([S, CS, T], [WS[1,1], 0.01, WT[1,1]], γ)
6457

6558
## Plot the closed loop gain from w to z
66-
# TODO figure out why the legends don't seem to work in this case
67-
68-
specificationplot(Pcl, γ; s_labels=["\$\\sigma(P_{cl}(j\\omega))\$"], w_labels=["\$\\gamma\$"])
59+
specificationplot(Pcl, γ; s_labels=["\$\\sigma(P_{cl}(j\\omega))\$"], w_labels=["\$\\gamma\$"])
6960

70-
times = [i for i in range(0, stop=300, length=10000)]
71-
plot(step(T, times))
72-
end
61+
times = [i for i in range(0, stop=300, length=10000)]
62+
plot(step(T, times))

src/ExtendedStateSpace.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,8 +179,13 @@ function Base.getproperty(sys::ExtendedStateSpace, s::Symbol)
179179
end
180180
end
181181

182+
Base.propertynames(sys::ExtendedStateSpace) = (:A, :B, :C, :D, :B1, :B2, :C1, :C2, :D11, :D12, :D21, :D22, :Ts, :timeevol, :nx, :ny, :nu, :nw, :nz, :zinds, :yinds, :winds, :uinds)
183+
182184
ControlSystems.StateSpace(s::ExtendedStateSpace) = ss(ssdata(s)..., s.timeevol)
183185

186+
"""
187+
A, B1, B2, C1, C2, D11, D12, D21, D22 = ssdata_e(sys)
188+
"""
184189
ssdata_e(sys::ExtendedStateSpace) = sys.A,
185190
sys.B1,
186191
sys.B2,

src/RobustAndOptimalControl.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ include("descriptor.jl")
3030
export ExtendedStateSpace, system_mapping, performance_mapping, noise_mapping, ssdata_e, partition, ss
3131
include("ExtendedStateSpace.jl")
3232

33+
export modal_form, schur_form, hess_form
34+
include("canonical.jl")
35+
3336
export δ, δr, δc, δss, nominal, UncertainSS, uss, blocksort
3437
include("uncertainty_interface.jl")
3538

src/canonical.jl

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
blockdiagonalize(A::AbstractMatrix) = cdf2rdf(eigen(A, sortby=eigsortby))
2+
3+
eigsortby::Real) = λ
4+
eigsortby::Complex) = (abs(imag(λ)),real(λ))
5+
6+
function complex_indices(A::Matrix) # assumes A on block diagonal form
7+
findall(diag(A, -1) .!= 0)
8+
end
9+
10+
function real_indices(A::Matrix) # assumes A on block diagonal form
11+
size(A,1) == 1 && return [1]
12+
setdiff(findall(diag(A, -1) .== 0), complex_indices(A).+1)
13+
end
14+
15+
function complex_indices(D::AbstractVector)
16+
complex_eigs = imag.(D) .!= 0
17+
findall(complex_eigs)
18+
end
19+
20+
function cdf2rdf(E::Eigen)
21+
# Implementation inspired by scipy https://docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.cdf2rdf.html
22+
# with the licence https://github.com/scipy/scipy/blob/v1.6.3/LICENSE.txt
23+
D,V = E
24+
n = length(D)
25+
26+
# get indices for each first pair of complex eigenvalues
27+
complex_inds = complex_indices(D)
28+
29+
# eigvals are sorted so conjugate pairs are next to each other
30+
j = complex_inds[1:2:end]
31+
k = complex_inds[2:2:end]
32+
33+
# put real parts on diagonal
34+
Db = zeros(n, n)
35+
Db[diagind(Db)] .= real(D)
36+
37+
# compute eigenvectors for real block diagonal eigenvalues
38+
U = zeros(eltype(D), n, n)
39+
U[diagind(U)] .= 1.0
40+
41+
# transform complex eigvals to real blockdiag form
42+
for (k,j) in zip(k,j)
43+
Db[j, k] = imag(D[j]) # put imaginary parts in blocks
44+
Db[k, j] = imag(D[k])
45+
46+
U[j, j] = 0.5im
47+
U[j, k] = 0.5
48+
U[k, j] = -0.5im
49+
U[k, k] = 0.5
50+
end
51+
Vb = real(V*U)
52+
53+
return Db, Vb
54+
end
55+
56+
"""
57+
sysm, T = modal_form(sys; C1 = false)
58+
59+
Bring `sys` to modal form.
60+
61+
The modal form is characterized by being tridiagonal with the real values of eigenvalues of `A` on the main diagonal and the complex parts on the first sub and super diagonals. `T` is the similarity transform applied to the system such that
62+
```julia
63+
sysm ≈ similarity_transform(sys, T)
64+
```
65+
66+
If `C1`, then an additional convention for SISO systems is used, that the `C`-matrix coefficient of real eigenvalues is 1. If `C1 = false`, the `B` and `C` coefficients are chosen in a balanced fashion.
67+
68+
See also [`hess_form`](@ref) and [`schur_form`](@ref)
69+
"""
70+
function modal_form(sys; C1 = false)
71+
Ab,T = blockdiagonalize(sys.A)
72+
# Calling similarity_transform looks like a detour, but this implementation allows modal_form to work with any AbstractStateSpace which implements a custom method for similarity transform
73+
sysm = similarity_transform(sys, T)
74+
sysm.A .= Ab # sysm.A should already be Ab after similarity_transform, but Ab has less numerical noise
75+
if ControlSystems.issiso(sysm)
76+
# This enforces a convention: the C matrix entry for the first component in each mode is positive. This allows SISO systems on modal form to be interpolated in a meaningful way by interpolating their coefficients.
77+
# Ref: "New Metrics Between Rational Spectra and their Connection to Optimal Transport" , Bagge Carlson, Chitre
78+
ci = complex_indices(sysm.A)
79+
flips = ones(sysm.nx)
80+
for i in ci
81+
if sysm.C[1, i] < 0
82+
flips[i] = -1
83+
flips[i .+ 1] = -1
84+
end
85+
end
86+
ri = real_indices(sysm.A)
87+
for i in ri
88+
c = sysm.C[1, i]
89+
if C1
90+
if c != 0
91+
flips[i] /= c
92+
end
93+
else
94+
b = sysm.B[i, 1]
95+
flips[i] *= sqrt(abs(b))/(sqrt(abs(c)) + eps(b))
96+
end
97+
end
98+
T2 = diagm(flips)
99+
sysm = similarity_transform(sysm, T2)
100+
T = T*T2
101+
sysm.A .= Ab # Ab unchanged by diagonal T
102+
end
103+
sysm, T
104+
end
105+
106+
"""
107+
sysm, T = schur_form(sys)
108+
109+
Bring `sys` to Schur form.
110+
111+
The Schur form is characterized by `A` being Schur with the real values of eigenvalues of `A` on the main diagonal. `T` is the similarity transform applied to the system such that
112+
```julia
113+
sysm ≈ similarity_transform(sys, T)
114+
```
115+
116+
See also [`modal_form`](@ref) and [`hess_form`](@ref)
117+
"""
118+
function schur_form(sys)
119+
SF = schur(sys.A)
120+
A = SF.T
121+
B = SF.Z'*sys.B
122+
C = sys.C*SF.Z
123+
ss(A,B,C,sys.D, sys.timeevol), SF.Z, SF
124+
end
125+
126+
"""
127+
sysm, T = hess_form(sys)
128+
129+
Bring `sys` to Hessenberg form form.
130+
131+
The Hessenberg form is characterized by `A` having upper Hessenberg structure. `T` is the similarity transform applied to the system such that
132+
```julia
133+
sysm ≈ similarity_transform(sys, T)
134+
```
135+
136+
See also [`modal_form`](@ref) and [`schur_form`](@ref)
137+
"""
138+
function hess_form(sys)
139+
F = hessenberg(sys.A)
140+
Q = Matrix(F.Q)
141+
A = F.H
142+
B = Q'sys.B
143+
C = sys.C*Q
144+
D = sys.D
145+
ss(A,B,C,D, sys.timeevol), Q, F
146+
end

src/glover_mcfarlane.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ end
154154
155155
For discrete systems, the `info` tuple contains also feedback gains `F, L` and observer gain `Hkf` such that the controller on observer form is given by
156156
```math
157-
x^+ = Ax + Bu + H_{kf}*(Cx - y)\\\\
158-
u = Fx + L*(Cx - y)
157+
x^+ = Ax + Bu + H_{kf} (Cx - y)\\\\
158+
u = Fx + L (Cx - y)
159159
```
160160
Note, this controller is *not* strictly proper, i.e., it has a non-zero D matrix.
161161
The controller can be transformed to observer form for the scaled plant (`info.Gs`)

src/h2_design.jl

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,18 @@ If `γ = nothing`, use the formulas for H₂ in Ch 14.5. If γ is a large value,
1010
function h2synthesize(P::ExtendedStateSpace, γ = nothing)
1111

1212
if γ === nothing
13+
iszero(P.D11) && iszero(P.D22) || error("D11 and D22 must be zero for the standard H2 formulation to be used, try calling with γ=1000 to use the H∞ formulation.")
1314
X2, Y2, F2, L2 = _solvematrixequations2(P)
1415
Â2 = P.A + P.B2*F2 + L2*P.C2
15-
K = ss(Â2, -L2, F2, 0)
16+
# Af2 = P.A + P.B2*F2
17+
# C1f2 = P.C1 + P.D12*F2
18+
# Gc = ss(Af2, I(size(Af2, 1)), C1f2, 0)
19+
K = ss(Â2, -L2, F2, 0, P.timeevol)
1620
return K, lft(ss(P), K)
1721
end
1822

23+
iscontinuous(P) || throw(ArgumentError("h2syn with specified γ is only supported for continuous systems."))
24+
1925
P̄, Ltrans12, Rtrans12, Ltrans21, Rtrans21 = _transformp2pbar(P)
2026
X2, Y2, F2, L2 = _solvematrixequations(P̄, γ)
2127

@@ -52,26 +58,35 @@ function _solvematrixequations2(P::ExtendedStateSpace)
5258
B2 = P.B2
5359
C1 = P.C1
5460
C2 = P.C2
55-
D11 = P.D11
5661
D12 = P.D12
5762
D21 = P.D21
58-
D22 = P.D22
5963

60-
P1 = size(C1, 1)
61-
P2 = size(C2, 1)
62-
M1 = size(B1, 2)
63-
M2 = size(B2, 2)
64+
# P1 = size(C1, 1)
65+
# P2 = size(C2, 1)
66+
# M1 = size(B1, 2)
67+
# M2 = size(B2, 2)
68+
69+
# HX = [A zeros(size(A)); -C1'*C1 -A'] - [B2; -C1'*D12] * [D12'*C1 B2']
70+
# HY = [A' zeros(size(A)); -B1*B1' -A] - [C2'; -B1*D21'] * [D21*B1' C2]
71+
72+
# # Solve matrix equations
73+
# X2 = _solvehamiltonianare(HX)
74+
# Y2 = _solvehamiltonianare(HY)
75+
76+
# # These formulas appear under ch14.5, but different formulas where D12'C1=0 and B1 * D21'=0 appear
77+
# # in ch 14.9.1, the difference is explained in ch13. See also LQGProblem(P::ExtendedStateSpace)
78+
# F2 = (B2'X2 + D12'C1)
79+
# L2 = (B1 * D21' + Y2 * C2')
6480

65-
HX = [A zeros(size(A)); -C1'*C1 -A'] - [B2; -C1'*D12] * [D12'*C1 B2']
66-
HY = [A' zeros(size(A)); -B1*B1' -A] - [C2'; -B1*D21'] * [D21*B1' C2]
6781

68-
# Solve matrix equations
69-
X2 = _solvehamiltonianare(HX)
70-
Y2 = _solvehamiltonianare(HY)
82+
# This is more robust than the approach above
83+
# https://ocw.snu.ac.kr/sites/default/files/NOTE/3938.pdf eq 73-74
7184

72-
F2 = -(B2'X2 + D12'C1)
85+
fun = iscontinuous(P) ? MatrixEquations.arec : MatrixEquations.ared
7386

74-
L2 = -(B1 * D21' + Y2 * C2')
87+
X2, _, F2 = fun(A, B2, D12'D12, C1'C1, C1'D12)
88+
Y2, _, L2 = fun(A', C2', D21*D21', B1*B1', B1*D21')
89+
L2 = L2'
7590

76-
return X2, Y2, F2, L2
91+
return X2, Y2, -F2, -L2
7792
end

src/hinfinity_design.jl

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,16 @@ function _synthesizecontroller(
264264
D1122 = D11[(P1-M2+1):P1, (M1-P2+1):M1]
265265

266266
# Equation 19
267-
D11hat = ((-D1121 * D1111') / (γ² * I - D1111 * D1111')) * D1112 - D1122
267+
J1 = (γ² * I - D1111 * D1111')
268+
D11hat = ((-D1121 * D1111') / J1) * D1112 - D1122
268269

269270
# Equation 20
270271
D12hatD12hat = I - (D1121 / (γ² * I - D1111' * D1111)) * D1121'
271272
_assertrealandpsd(D12hatD12hat; msg = " in equation (20)")
272273
D12hat = cholesky(Hermitian(D12hatD12hat)).L
273274

274275
# Equation 21
275-
D21hatD21hat = I - (D1112' / (γ² * I - D1111 * D1111')) * D1112
276+
D21hatD21hat = I - (D1112' / J1) * D1112
276277
_assertrealandpsd(D21hatD21hat; msg = " in equation (21)")
277278
D21hat = cholesky(Hermitian(D21hatD21hat)).U
278279

@@ -316,6 +317,29 @@ function _synthesizecontroller(
316317
return ss(Ac, Bc[:, 1:P2], Cc[1:M2, :], D11c)
317318
end
318319

320+
"""
321+
rqr(D, γ=1)
322+
323+
"Regularized" qr factorization. This struct represents \$(D'D + γI)\$ without forming \$D'D\$
324+
Note: this does not support negative γ or \$(γI - D'D)\$
325+
Supported operations: `\\,/,*`, i.e., it behaves also like a matrix (unlike the standard `QR` factorization object).
326+
"""
327+
struct rqr{T, PT, DGT}
328+
P::PT
329+
DG::DGT
330+
function rqr(D, γ=1)
331+
P = (D'D) + γ*I
332+
DG = qr([D; (γ)I])
333+
new{eltype(P), typeof(P), typeof(DG)}(P, DG)
334+
end
335+
end
336+
337+
Base.:\(d::rqr, b) = (d.DG.R\(adjoint(d.DG.R)\b))
338+
Base.:/(b, d::rqr) = ((b/d.DG.R)/adjoint(d.DG.R))
339+
Base.:*(d::rqr, b) = (d.P*b)
340+
Base.:*(b, d::rqr) = (b*d.P)
341+
342+
319343
"""
320344
_assertrealandpsd(A::AbstractMatrix, msg::AbstractString)
321345
@@ -457,11 +481,12 @@ function _γiterations(
457481
)
458482

459483
T = typeof(P.A)
484+
ET = eltype(T)
460485
XinfFeasible, YinfFeasible, FinfFeasible, HinfFeasible, gammFeasible =
461486
T(undef,0,0), T(undef,0,0), T(undef,0,0), T(undef,0,0), nothing
462487

463-
gl, gu = interval
464-
gl = max(1e-3, gl)
488+
gl, gu = ET.(interval)
489+
gl = max(ET(1e-3), gl)
465490
iters = ceil(Int, log2((gu-gl+1e-16)/gtol))
466491

467492
for iteration = 1:iters
@@ -607,6 +632,10 @@ weighting functions are empty arrays, numbers (static gains), and `LTISystem`s.
607632
Note, `system_mapping(P)` is equal to `-G`.
608633
"""
609634
function hinfpartition(G, WS, WU, WT)
635+
WS isa LTISystem && common_timeevol(G,WS)
636+
WU isa LTISystem && common_timeevol(G,WU)
637+
WT isa LTISystem && common_timeevol(G,WT)
638+
te = G.timeevol
610639
# # Convert the systems into state-space form
611640
Ag, Bg, Cg, Dg = _input2ss(G)
612641
Asw, Bsw, Csw, Dsw = _input2ss(WS)
@@ -739,7 +768,7 @@ function hinfpartition(G, WS, WU, WT)
739768
Dyw = Matrix{Float64}(I, mCg, nDuw)
740769
Dyu = -Dg
741770

742-
P = ss(A, Bw, Bu, Cz, Cy, Dzw, Dzu, Dyw, Dyu)
771+
P = ss(A, Bw, Bu, Cz, Cy, Dzw, Dzu, Dyw, Dyu, te)
743772

744773
end
745774

0 commit comments

Comments
 (0)