Skip to content

Commit 5de6a91

Browse files
authored
Merge pull request #42 from JuliaControl/factors
output additional information from factorization routines
2 parents aa28cef + e769d6b commit 5de6a91

7 files changed

Lines changed: 61 additions & 33 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "RobustAndOptimalControl"
22
uuid = "21fd56a4-db03-40ee-82ee-a87907bee541"
33
authors = ["Fredrik Bagge Carlson", "Marcus Greiff"]
4-
version = "0.3.4"
4+
version = "0.4.0"
55

66
[deps]
77
ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"

src/RobustAndOptimalControl.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ using ChainRulesCore
2424
export show_construction, vec2sys
2525
include("utils.jl")
2626

27-
export dss, hinfnorm2, h2norm, hankelnorm, nugap, νgap, baltrunc2, stab_unstab, baltrunc_unstab, baltrunc_coprime
27+
export dss, hinfnorm2, linfnorm2, h2norm, hankelnorm, nugap, νgap, baltrunc2, stab_unstab, baltrunc_unstab, baltrunc_coprime
2828
include("descriptor.jl")
2929

3030
export ExtendedStateSpace, system_mapping, performance_mapping, noise_mapping, ssdata_e, partition, ss

src/canonical.jl

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
blockdiagonalize(A::AbstractMatrix) = cdf2rdf(eigen(A, sortby=eigsortby))
1+
function blockdiagonalize(A::AbstractMatrix)
2+
E = eigen(A, sortby=eigsortby)
3+
Db,Vb = cdf2rdf(E)
4+
Db,Vb,E
5+
end
26

37
eigsortby::Real) = λ
48
eigsortby::Complex) = (abs(imag(λ)),real(λ))
@@ -54,7 +58,7 @@ function cdf2rdf(E::Eigen)
5458
end
5559

5660
"""
57-
sysm, T = modal_form(sys; C1 = false)
61+
sysm, T, E = modal_form(sys; C1 = false)
5862
5963
Bring `sys` to modal form.
6064
@@ -65,10 +69,12 @@ sysm ≈ similarity_transform(sys, T)
6569
6670
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.
6771
72+
`E` is an eigen factorization of `A`.
73+
6874
See also [`hess_form`](@ref) and [`schur_form`](@ref)
6975
"""
7076
function modal_form(sys; C1 = false)
71-
Ab,T = blockdiagonalize(sys.A)
77+
Ab,T,E = blockdiagonalize(sys.A)
7278
# 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
7379
sysm = similarity_transform(sys, T)
7480
sysm.A .= Ab # sysm.A should already be Ab after similarity_transform, but Ab has less numerical noise
@@ -100,18 +106,19 @@ function modal_form(sys; C1 = false)
100106
T = T*T2
101107
sysm.A .= Ab # Ab unchanged by diagonal T
102108
end
103-
sysm, T
109+
sysm, T, E
104110
end
105111

106112
"""
107-
sysm, T = schur_form(sys)
113+
sysm, T, SF = schur_form(sys)
108114
109115
Bring `sys` to Schur form.
110116
111117
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
112118
```julia
113119
sysm ≈ similarity_transform(sys, T)
114120
```
121+
`SF` is the Schur-factorization of `A`.
115122
116123
See also [`modal_form`](@ref) and [`hess_form`](@ref)
117124
"""
@@ -124,14 +131,15 @@ function schur_form(sys)
124131
end
125132

126133
"""
127-
sysm, T = hess_form(sys)
134+
sysm, T, HF = hess_form(sys)
128135
129136
Bring `sys` to Hessenberg form form.
130137
131138
The Hessenberg form is characterized by `A` having upper Hessenberg structure. `T` is the similarity transform applied to the system such that
132139
```julia
133140
sysm ≈ similarity_transform(sys, T)
134141
```
142+
`HF` is the Hessenberg-factorization of `A`.
135143
136144
See also [`modal_form`](@ref) and [`schur_form`](@ref)
137145
"""

src/descriptor.jl

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ function hinfnorm2(sys::LTISystem; kwargs...)
3333
DescriptorSystems.ghinfnorm(dss(ss(sys)); kwargs...)
3434
end
3535

36+
function linfnorm2(sys::LTISystem; kwargs...)
37+
DescriptorSystems.glinfnorm(dss(ss(sys)); kwargs...)
38+
end
39+
3640
"""
3741
n = h2norm(sys::LTISystem; kwargs...)
3842
@@ -74,7 +78,7 @@ const νgap = nugap
7478

7579

7680
"""
77-
baltrunc2(sys::LTISystem; residual=false, n=missing, kwargs...)
81+
sysr, hs = baltrunc2(sys::LTISystem; residual=false, n=missing, kwargs...)
7882
7983
Compute the a balanced truncation of order `n` and the hankel singular values
8084
@@ -87,7 +91,7 @@ function baltrunc2(sys::LTISystem; residual=false, n=missing, kwargs...)
8791
end
8892

8993
"""
90-
baltrunc_coprime(sys; residual = false, n = missing, factorization::F = DescriptorSystems.glcf, kwargs...)
94+
sysr, hs, info = baltrunc_coprime(sys; residual = false, n = missing, factorization::F = DescriptorSystems.glcf, kwargs...)
9195
9296
Compute a balanced truncation of the left coprime factorization of `sys`.
9397
See [`baltrunc2`](@ref) for additional keyword-argument help.
@@ -96,15 +100,19 @@ See [`baltrunc2`](@ref) for additional keyword-argument help.
96100
- `factorization`: The function to perform the coprime factorization. A normalized factorization may be used by passing `RobustAndOptimalControl.DescriptorSystems.gnlcf`.
97101
- `kwargs`: Are passed to `DescriptorSystems.gbalmr`
98102
"""
99-
function baltrunc_coprime(sys; residual=false, n=missing, factorization::F = DescriptorSystems.glcf, kwargs...) where F
100-
N,M = factorization(dss(sys))
101-
nu = size(N.B, 2)
102-
A,E,B,C,D = DescriptorSystems.dssdata(N)
103-
NM = DescriptorSystems.dss(A,E,[B M.B],C,[D M.D])
103+
function baltrunc_coprime(sys, info=nothing; residual=false, n=missing, factorization::F = DescriptorSystems.glcf, kwargs...) where F
104+
if info !== nothing && hasproperty(info, :NM)
105+
@unpack N, M, NM = info
106+
else
107+
N,M = factorization(dss(sys))
108+
A,E,B,C,D = DescriptorSystems.dssdata(N)
109+
NM = DescriptorSystems.dss(A,E,[B M.B],C,[D M.D])
110+
end
104111
sysr, hs = DescriptorSystems.gbalmr(NM; matchdc=residual, ord=n, kwargs...)
105-
112+
106113
A,E,B,C,D = DescriptorSystems.dssdata(DescriptorSystems.dss2ss(sysr)[1])
107-
114+
115+
nu = size(N.B, 2)
108116
BN = B[:, 1:nu]
109117
DN = D[:, 1:nu]
110118
BM = B[:, nu+1:end]
@@ -115,7 +123,7 @@ function baltrunc_coprime(sys; residual=false, n=missing, factorization::F = Des
115123
Br = BN - BM * (DMi * DN)
116124
Dr = (DMi * DN)
117125

118-
ss(Ar,Br,Cr,Dr,sys.timeevol), hs
126+
ss(Ar,Br,Cr,Dr,sys.timeevol), hs, (; NM, N, M)
119127
end
120128

121129

@@ -126,14 +134,18 @@ Balanced truncation for unstable models. An additive decomposition of sys into `
126134
127135
See `baltrunc2` for other keyword arguments.
128136
"""
129-
function baltrunc_unstab(sys::LTISystem; residual=false, n=missing, kwargs...)
130-
stab, unstab = DescriptorSystems.gsdec(dss(sys); job="stable", kwargs...)
137+
function baltrunc_unstab(sys::LTISystem, info=nothing; residual=false, n=missing, kwargs...)
138+
if info !== nothing && hasproperty(info, :stab)
139+
@unpack stab, unstab = info
140+
else
141+
stab, unstab = DescriptorSystems.gsdec(dss(sys); job="stable", kwargs...)
142+
end
131143
nx_unstab = size(unstab.A, 1)
132144
if n isa Integer && n < nx_unstab
133145
error("The model contains $(nx_unstab) poles outside the stability region, the reduced-order model must be of at least this order.")
134146
end
135147
sysr, hs = DescriptorSystems.gbalmr(stab; matchdc=residual, ord=n-nx_unstab, kwargs...)
136-
ss(sysr + unstab), hs
148+
ss(sysr + unstab), hs, (; stab, unstab)
137149
end
138150

139151
"""

src/glover_mcfarlane.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ end
284284
285285
Joint design of feedback and feedforward compensators
286286
```math
287-
K = \\left[K_1 & K_2\\right]
287+
K = \\begin{bmatrix} K_1 & K_2 \\end{bmatrix}
288288
```
289289
```
290290
┌──────┐ ┌──────┐ ┌──────┐ ┌─────┐

src/reduction.jl

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ using ControlSystems: ssdata
44

55

66
"""
7-
frequency_weighted_reduction(G, Wo, Wi; residual=true)
7+
sysr, hs = frequency_weighted_reduction(G, Wo, Wi; residual=true)
88
99
Find Gr such that ||Wₒ(G-Gr)Wᵢ||∞ is minimized.
1010
For a realtive reduction, set Wo = inv(G) and Wi = I.
@@ -113,7 +113,7 @@ function frequency_weighted_reduction(G, Wo, Wi, r=nothing; residual=true, atol=
113113
# determine a standard reduced system
114114
SV = svd!(Er)
115115
di2 = Diagonal(1 ./sqrt.(SV.S))
116-
return ss(di2*SV.U'*Ar*SV.Vt'*di2, di2*(SV.U'*Br), (Cr*SV.Vt')*di2, Dr)
116+
return ss(di2*SV.U'*Ar*SV.Vt'*di2, di2*(SV.U'*Br), (Cr*SV.Vt')*di2, Dr), Σ
117117
else
118118
L = (Y'X)\Y'
119119
T = X
@@ -122,7 +122,7 @@ function frequency_weighted_reduction(G, Wo, Wi, r=nothing; residual=true, atol=
122122
C = C*T
123123
D = D
124124
end
125-
ss(A,B,C,D, G.timeevol)
125+
ss(A,B,C,D, G.timeevol), Σ
126126
end
127127

128128

@@ -178,6 +178,14 @@ end
178178

179179
@deprecate minreal2 minreal
180180

181+
"""
182+
error_bound(hs)
183+
184+
Given a vector of Hankel singular vlues, return the theoretical error bound as a function of model order after balanced-truncation model reduction.
185+
(twice sum of all the removed singular values).
186+
"""
187+
error_bound(hs) = [2reverse(cumsum(reverse(hs)))[1:end-1]; 0]
188+
181189
# slide 189 https://cscproxy.mpi-magdeburg.mpg.de/mpcsc/benner/talks/lecture-MOR.pdf
182190
# This implementation works, but results in a complex-valued system.
183191
# function model_reduction_irka(sys::AbstractStateSpace{Continuous}, r; tol = 1e-6)

test/test_reduction.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ sys = let
99
_D = [0.0]
1010
ss(_A, _B, _C, _D)
1111
end
12-
sysr = frequency_weighted_reduction(sys, 1, 1, 10, residual=false)
12+
sysr, _ = frequency_weighted_reduction(sys, 1, 1, 10, residual=false)
1313
sysr2 = baltrunc(sys, n=10)[1]
1414
@test sysr.nx == 10
1515
@test hinorm(sys-sysr) < 1e-5
@@ -25,7 +25,7 @@ sys = let
2525
ss(_A, _B, _C, _D)
2626
end
2727
sysi = fudge_inv(sys)
28-
sysr = frequency_weighted_reduction(sys, sysi, 1, 5, residual=false)
28+
sysr, _ = frequency_weighted_reduction(sys, sysi, 1, 5, residual=false)
2929
sysr2 = baltrunc(sys, n=5)[1]
3030
@test sysr.nx == 5
3131
@test norm(sys-sysr) < 0.1
@@ -35,7 +35,7 @@ sysr2 = baltrunc(sys, n=5)[1]
3535

3636

3737
Wi = tf(1, [1, 1])
38-
sysr = frequency_weighted_reduction(sys, 1, Wi, 5, residual=false)
38+
sysr, _ = frequency_weighted_reduction(sys, 1, Wi, 5, residual=false)
3939
sysr2 = baltrunc(sys, n=5)[1]
4040
@test sysr.nx == 5
4141
@test norm(sys-sysr) < 0.1
@@ -47,7 +47,7 @@ sysr2 = baltrunc(sys, n=5)[1]
4747

4848
Wo = sysi
4949
Wi = tf(1, [1, 1])
50-
sysr = frequency_weighted_reduction(sys, Wo, Wi, 5, residual=false)
50+
sysr, _ = frequency_weighted_reduction(sys, Wo, Wi, 5, residual=false)
5151
sysr2 = baltrunc(sys, n=5)[1]
5252
@test sysr.nx == 5
5353
@test_broken norm(sys-sysr) < 0.1
@@ -58,7 +58,7 @@ sysr2 = baltrunc(sys, n=5)[1]
5858

5959
# residual
6060
sysi = fudge_inv(sys)
61-
sysr = frequency_weighted_reduction(sys, sysi, 1, 3, residual=true)
61+
sysr, _ = frequency_weighted_reduction(sys, sysi, 1, 3, residual=true)
6262
sysr2 = baltrunc(sys, n=3, residual=true)[1]
6363
@test sysr.nx == 3
6464
@test norm(sys-sysr, Inf) < 3
@@ -72,7 +72,7 @@ sysr2 = baltrunc(sys, n=3, residual=true)[1]
7272
G = tf([8, 6, 2], [1, 4, 5, 2])
7373
Wi = tf(1, [1, 3])
7474
Wo = tf(1, [1, 4])
75-
sysr = frequency_weighted_reduction(ss(G), Wo, Wi, 1, residual=true)
75+
sysr, _ = frequency_weighted_reduction(ss(G), Wo, Wi, 1, residual=true)
7676
@test isstable(sysr)
7777

7878
@test tf(sysr) tf([2.398, 1.739], [1, 1.739]) rtol=1e-1
@@ -100,7 +100,7 @@ Wo = Wi = ss(Aw,Dw,Cw,Dw)
100100

101101

102102
errors = map(1:3) do r
103-
Gr = frequency_weighted_reduction(G, Wo, Wi, r, residual=false)
103+
Gr, _ = frequency_weighted_reduction(G, Wo, Wi, r, residual=false)
104104
hinfnorm2(Wo*(G-Gr)*Wi)[1]
105105
end
106106

@@ -110,7 +110,7 @@ end
110110

111111

112112
errors = map(1:3) do r
113-
Gr = frequency_weighted_reduction(G, Wo, Wi, r, residual=true)
113+
Gr, _ = frequency_weighted_reduction(G, Wo, Wi, r, residual=true)
114114
hinfnorm2(Wo*(G-Gr)*Wi)[1]
115115
end
116116

0 commit comments

Comments
 (0)