Skip to content

Commit 5357668

Browse files
authored
use zero computations form MatrixPencils (#972)
* use zero computations form MatrixPencils * rm
1 parent c1cf26a commit 5357668

File tree

6 files changed

+33
-119
lines changed

6 files changed

+33
-119
lines changed

lib/ControlSystemsBase/src/ControlSystemsBase.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ export LTISystem,
3939
place,
4040
place_knvd,
4141
# Model Simplification
42-
reduce_sys,
4342
sminreal,
4443
minreal,
4544
balreal,

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 13 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -237,131 +237,36 @@ dampreport(sys::LTISystem) = dampreport(stdout, sys)
237237

238238
"""
239239
tzeros(sys)
240+
tzeros(sys::AbstractStateSpace; extra=Val(false))
240241
241242
Compute the invariant zeros of the system `sys`. If `sys` is a minimal
242-
realization, these are also the transmission zeros."""
243+
realization, these are also the transmission zeros.
244+
245+
If `sys` is a state-space system the function has additional keyword arguments, see [`?ControlSystemsBase.MatrixPencils.spzeros`](https://andreasvarga.github.io/MatrixPencils.jl/dev/sklfapps.html#MatrixPencils.spzeros) for more details. If `extra = Val(true)`, the function returns `z, iz, KRInfo` where `z` are the transmission zeros, information on the multiplicities of infinite zeros in `iz` and information on the Kronecker-structure in the KRInfo object. The number of infinite zeros is the sum of the components of iz.
246+
"""
243247
function tzeros(sys::TransferFunction)
244248
if issiso(sys)
245249
return tzeros(sys.matrix[1,1])
246250
else
247-
return tzeros(ss(sys))
251+
return tzeros(ss(sys, minimal=true))
248252
end
249253
end
250254

251-
# Implements the algorithm described in:
252-
# Emami-Naeini, A. and P. Van Dooren, "Computation of Zeros of Linear
253-
# Multivariable Systems," Automatica, 18 (1982), pp. 415–430.
254-
#
255-
# Note that this returns either Vector{ComplexF32} or Vector{Float64}
256-
tzeros(sys::AbstractStateSpace) = tzeros(sys.A, sys.B, sys.C, sys.D)
255+
tzeros(sys::AbstractStateSpace; kwargs...) = tzeros(sys.A, sys.B, sys.C, sys.D; kwargs...)
257256
# Make sure everything is BlasFloat
258257
function tzeros(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix)
259258
T = promote_type(eltype(A), eltype(B), eltype(C), eltype(D))
260259
A2, B2, C2, D2, _ = promote(A,B,C,D, fill(zero(T)/one(T),0,0)) # If Int, we get Float64
261260
tzeros(A2, B2, C2, D2)
262261
end
263-
function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}, D::AbstractMatrix{T}) where {T <: Union{AbstractFloat,Complex{<:AbstractFloat}}#= For eps(T) =#}
264-
# Balance the system
265-
A, B, C = balance_statespace(A, B, C)
266-
267-
# Compute a good tolerance
268-
meps = 10*eps(real(T))*norm([A B; C D])
269-
270-
# Step 1:
271-
A_r, B_r, C_r, D_r = reduce_sys(A, B, C, D, meps)
272-
273-
# Step 2: (conjugate transpose should be avoided since single complex zeros get conjugated)
274-
A_rc, B_rc, C_rc, D_rc = reduce_sys(copy(transpose(A_r)), copy(transpose(C_r)), copy(transpose(B_r)), copy(transpose(D_r)), meps)
275-
isempty(A) && return complex(T)[]
276-
277-
# Step 3:
278-
# Compress cols of [C D] to [0 Df]
279-
mat = [C_rc D_rc]
280-
Wr = qr(mat').Q * I
281-
W = reverse(Wr, dims=2)
282-
mat = mat*W
283-
if fastrank(mat', meps) > 0
284-
nf = size(A_rc, 1)
285-
m = size(D_rc, 2)
286-
Af = ([A_rc B_rc] * W)[1:nf, 1:nf]
287-
Bf = ([Matrix{T}(I, nf, nf) zeros(nf, m)] * W)[1:nf, 1:nf]
288-
zs = eigvalsnosort(Af, Bf)
289-
_fix_conjugate_pairs!(zs) # Generalized eigvals does not return exact conj. pairs
290-
else
291-
zs = complex(T)[]
292-
end
293-
return zs
294-
end
295262

296-
297-
"""
298-
reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
299-
Implements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed
300-
A, B, C, D matrices. These are empty if there are no zeros.
301-
"""
302-
function reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
303-
T = promote_type(eltype(A), eltype(B), eltype(C), eltype(D))
304-
Cbar, Dbar = C, D
305-
if isempty(A)
306-
return A, B, C, D
307-
end
308-
while true
309-
# Compress rows of D
310-
U = qr(D).Q
311-
D = U'D
312-
C = U'C
313-
sigma = fastrank(D, meps)
314-
Cbar = C[1:sigma, :]
315-
Dbar = D[1:sigma, :]
316-
Ctilde = C[(1 + sigma):end, :]
317-
if sigma == size(D, 1)
318-
break
319-
end
320-
321-
# Compress columns of Ctilde
322-
V = reverse(qr(Ctilde').Q * I, dims=2)
323-
Sj = Ctilde*V
324-
rho = fastrank(Sj', meps)
325-
nu = size(Sj, 2) - rho
326-
327-
if rho == 0
328-
break
329-
elseif nu == 0
330-
# System has no zeros, return empty matrices
331-
A = B = Cbar = Dbar = Matrix{T}(undef, 0,0)
332-
break
333-
end
334-
# Update System
335-
n, m = size(B)
336-
Vm = [V zeros(T, n, m); zeros(T, m, n) Matrix{T}(I, m, m)] # I(m) is not used for type stability reasons (as of julia v1.7)
337-
if sigma > 0
338-
M = [A B; Cbar Dbar]
339-
Vs = [copy(V') zeros(T, n, sigma) ; zeros(T, sigma, n) Matrix{T}(I, sigma, sigma)]
340-
else
341-
M = [A B]
342-
Vs = copy(V')
343-
end
344-
sigma, rho, nu
345-
M = Vs * M * Vm
346-
A = M[1:nu, 1:nu]
347-
B = M[1:nu, (nu + rho + 1):end]
348-
C = M[(nu + 1):end, 1:nu]
349-
D = M[(nu + 1):end, (nu + rho + 1):end]
350-
end
351-
return A, B, Cbar, Dbar
352-
end
353-
354-
# Determine the number of non-zero rows, with meps as a tolerance. For an
355-
# upper-triangular matrix, this is a good proxy for determining the row-rank.
356-
function fastrank(A::AbstractMatrix, meps::Real)
357-
n, m = size(A)
358-
if n*m == 0 return 0 end
359-
norms = Vector{real(eltype(A))}(undef, n)
360-
for i = 1:n
361-
norms[i] = norm(A[i, :])
263+
function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}, D::AbstractMatrix{T}; extra::Val{E} = Val{false}(), kwargs...) where {T <: Union{AbstractFloat,Complex{<:AbstractFloat}}, E}
264+
(z, iz, KRInfo) = MatrixPencils.spzeros(A, I, B, C, D; kwargs...)
265+
if E
266+
return (z, iz, KRInfo)
267+
else
268+
return filter(isfinite, z)
362269
end
363-
mrank = sum(norms .> meps)
364-
return mrank
365270
end
366271

367272
"""

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -781,7 +781,7 @@ marginplot
781781
end
782782
titles[j,i,1,1] *= "["*join([Printf.@sprintf("%3.2g",v) for v in gm],", ")*"] "
783783
titles[j,i,1,2] *= "["*join([Printf.@sprintf("%3.2g",v) for v in wgm],", ")*"] "
784-
titles[j,i,2,1] *= "["*join([Printf.@sprintf("%3.2g",v) for v in pm],", ")*"] "
784+
titles[j,i,2,1] *= "["*join([Printf.@sprintf("%3.2g°",v) for v in pm],", ")*"] "
785785
titles[j,i,2,2] *= "["*join([Printf.@sprintf("%3.2g",v) for v in wpm],", ")*"] "
786786

787787
subplot := min(s2i((plotphase ? (2i-1) : i),j), prod(plotattributes[:layout]))

lib/ControlSystemsBase/src/precompilation.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ PrecompileTools.@setup_workload begin
2222
end
2323
G = tf(1.0, [1.0, 1])
2424
ss(G)
25+
ss(G, minimal=true)
2526
G = tf(1.0, [1.0, 1], 1)
2627
ss(G)
28+
ss(G, minimal=true)
2729

2830
# Pdel = P*delay(1.0)
2931
# pade(Pdel, 2)

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@test_throws MethodError poles(big(1.0)*ssrand(1,1,1)) # This errors before loading GenericLinearAlgebra
22
using GenericLinearAlgebra # Required to compute eigvals of a matrix with exotic element types
33
@testset "test_analysis" begin
4+
es(x) = sort(x, by=LinearAlgebra.eigsortby)
45
## tzeros ##
56
# Examples from the Emami-Naeini & Van Dooren Paper
67
# Example 3
@@ -19,10 +20,10 @@ D = [1 0;
1920

2021
ex_3 = ss(A, B, C, D)
2122
@test ControlSystemsBase.count_integrators(ex_3) == 6
22-
@test tzeros(ex_3) [0.3411639019140099 + 1.161541399997252im,
23+
@test es(tzeros(ex_3)) es([0.3411639019140099 + 1.161541399997252im,
2324
0.3411639019140099 - 1.161541399997252im,
2425
0.9999999999999999 + 0.0im,
25-
-0.6823278038280199 + 0.0im]
26+
-0.6823278038280199 + 0.0im])
2627
# Example 4
2728
A = [-0.129 0.0 0.396e-1 0.25e-1 0.191e-1;
2829
0.329e-2 0.0 -0.779e-4 0.122e-3 -0.621;
@@ -38,7 +39,7 @@ C = [1 0 0 0 0;
3839
0 1 0 0 0]
3940
D = zeros(2, 2)
4041
ex_4 = ss(A, B, C, D)
41-
@test tzeros(ex_4) [-0.06467751189940692,-0.3680512036036696]
42+
@test es(tzeros(ex_4)) es([-0.06467751189940692,-0.3680512036036696])
4243
@test ControlSystemsBase.count_integrators(ex_4) == 1
4344

4445
# Example 5
@@ -56,7 +57,7 @@ B = [0; 0; 1]
5657
C = [0 -1 0]
5758
D = [0]
5859
ex_6 = ss(A, B, C, D)
59-
@test tzeros(ex_6) == Float64[]
60+
@test tzeros(ex_6) == [2] # From paper: "Our algorithm will extract the singular part of S(A) and will yield a regular pencil containing the single zero at 2."
6061
@test ControlSystemsBase.count_integrators(ex_6) == 2
6162

6263
@test ss(A, [0 0 1]', C, D) == ex_6
@@ -194,9 +195,11 @@ sys = s*(s + 1)*(s^2 + 1)*(s - 3)/((s + 1)*(s + 4)*(s - 4))
194195

195196
# Example 5.5 from https://www.control.lth.se/fileadmin/control/Education/EngineeringProgram/FRTN10/2019/e05_both.pdf
196197
G = [1/(s+2) -1/(s+2); 1/(s+2) (s+1)/(s+2)]
197-
@test_broken length(poles(G)) == 1
198-
@test length(tzeros(G)) == 1
198+
@test_broken length(poles(G)) == 1 # The tf poles don't understand the cancellations
199+
@test length(poles(ss(G, minimal=true))) == 1 # The ss version with minimal realization does
200+
@test length(tzeros(G)) == 0 # tzeros converts to minimal ss relalization
199201
@test minreal(ss(G)).A [-2]
202+
@test ss(G, minimal=true).A [-2]
200203

201204

202205
## MARGIN ##

lib/ControlSystemsBase/test/test_complex.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,14 @@ C_2 = zpk([-1+im], [], 1.0+1im)
3434

3535
s = tf("s");
3636
@test tzeros(ss(-1, 1, 1, 1.0im)) [-1.0 + im] rtol=1e-15
37-
@test tzeros(ss([-1.0-im 1-im; 2 0], [2; 0], [-1+1im -0.5-1.25im], 1)) [-1-2im, 2-im]
37+
@test tzeros(ss([-1.0-im 1-im; 2 0], [2; 0], [-1+1im -0.5-1.25im], 1)) [2-im, -1-2im]
3838

39-
@test tzeros(ss((s-2.0-1.5im)^3/(s+1+im)/(s+2)^3)) fill(2.0 + 1.5im, 3) rtol=1e-4
40-
@test tzeros(ss((s-2.0-1.5im)*(s-3.0)/(s+1+im)/(s+2)^2)) [2.0 + 1.5im, 3.0] rtol=1e-14
39+
sys = ss((s-2.0-1.5im)^3/(s+1+im)/(s+2)^3)
40+
@test tzeros(sys) fill(2.0 + 1.5im, 3) rtol=1e-4
41+
@test tzeros(ss((s-2.0-1.5im)*(s-3.0)/(s+1+im)/(s+2)^2)) [3.0, 2.0 + 1.5im] rtol=1e-14
42+
43+
z,iv,info = tzeros(sys, extra=Val(true))
44+
@test iv == [1]
4145

4246
end
47+

0 commit comments

Comments
 (0)