Skip to content

Commit 619914d

Browse files
authored
use new generic zeros from MatrixPencils (#1004)
* use new generic zeros from MatrixPencils * update tests
1 parent 92faf85 commit 619914d

File tree

3 files changed

+112
-113
lines changed

3 files changed

+112
-113
lines changed

lib/ControlSystemsBase/Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ ImplicitDifferentiation = "0.7.2"
3838
LinearAlgebra = "<0.0.1, 1"
3939
MacroTools = "0.5"
4040
MatrixEquations = "1, 2.1"
41-
MatrixPencils = "1.6"
41+
MatrixPencils = "1.8.3"
4242
Polynomials = "3.0, 4.0"
4343
PrecompileTools = "1"
4444
Printf = "<0.0.1, 1"

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 105 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ function tzeros(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::Abst
276276
tzeros(A2, B2, C2, D2)
277277
end
278278

279-
function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}, D::AbstractMatrix{T}; extra::Val{E} = Val{false}(), balance=true, kwargs...) where {T <: BlasFloat, E}
279+
function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}, D::AbstractMatrix{T}; extra::Val{E} = Val{false}(), balance=true, kwargs...) where {T, E}
280280
if balance
281281
A, B, C = balance_statespace(A, B, C)
282282
end
@@ -289,110 +289,110 @@ function tzeros(A::AbstractMatrix{T}, B::AbstractMatrix{T}, C::AbstractMatrix{T}
289289
end
290290
end
291291

292-
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}
293-
isempty(A) && return complex(T)[]
294-
295-
# Balance the system
296-
A, B, C = balance_statespace(A, B, C; verbose=false)
297-
298-
# Compute a good tolerance
299-
meps = 10*eps(real(T))*norm([A B; C D])
300-
301-
# Step 1:
302-
A_r, B_r, C_r, D_r = reduce_sys(A, B, C, D, meps)
303-
304-
# Step 2: (conjugate transpose should be avoided since single complex zeros get conjugated)
305-
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)
306-
307-
# Step 3:
308-
# Compress cols of [C D] to [0 Df]
309-
mat = [C_rc D_rc]
310-
Wr = qr(mat').Q * I
311-
W = reverse(Wr, dims=2)
312-
mat = mat*W
313-
if fastrank(mat', meps) > 0
314-
nf = size(A_rc, 1)
315-
m = size(D_rc, 2)
316-
Af = ([A_rc B_rc] * W)[1:nf, 1:nf]
317-
Bf = ([Matrix{T}(I, nf, nf) zeros(nf, m)] * W)[1:nf, 1:nf]
318-
Z = schur(complex.(Af), complex.(Bf)) # Schur instead of eigvals to handle BigFloat
319-
zs = Z.values
320-
ControlSystemsBase._fix_conjugate_pairs!(zs) # Generalized eigvals does not return exact conj. pairs
321-
else
322-
zs = complex(T)[]
323-
end
324-
return zs
325-
end
326-
327-
"""
328-
reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
329-
Implements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed
330-
A, B, C, D matrices. These are empty if there are no zeros.
331-
"""
332-
function reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
333-
T = promote_type(eltype(A), eltype(B), eltype(C), eltype(D))
334-
Cbar, Dbar = C, D
335-
if isempty(A)
336-
return A, B, C, D
337-
end
338-
while true
339-
# Compress rows of D
340-
U = qr(D).Q
341-
D = U'D
342-
C = U'C
343-
sigma = fastrank(D, meps)
344-
Cbar = C[1:sigma, :]
345-
Dbar = D[1:sigma, :]
346-
Ctilde = C[(1 + sigma):end, :]
347-
if sigma == size(D, 1)
348-
break
349-
end
350-
351-
# Compress columns of Ctilde
352-
V = reverse(qr(Ctilde').Q * I, dims=2)
353-
Sj = Ctilde*V
354-
rho = fastrank(Sj', meps)
355-
nu = size(Sj, 2) - rho
356-
357-
if rho == 0
358-
break
359-
elseif nu == 0
360-
# System has no zeros, return empty matrices
361-
A = B = Cbar = Dbar = Matrix{T}(undef, 0,0)
362-
break
363-
end
364-
# Update System
365-
n, m = size(B)
366-
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)
367-
if sigma > 0
368-
M = [A B; Cbar Dbar]
369-
Vs = [copy(V') zeros(T, n, sigma) ; zeros(T, sigma, n) Matrix{T}(I, sigma, sigma)]
370-
else
371-
M = [A B]
372-
Vs = copy(V')
373-
end
374-
sigma, rho, nu
375-
M = Vs * M * Vm
376-
A = M[1:nu, 1:nu]
377-
B = M[1:nu, (nu + rho + 1):end]
378-
C = M[(nu + 1):end, 1:nu]
379-
D = M[(nu + 1):end, (nu + rho + 1):end]
380-
end
381-
return A, B, Cbar, Dbar
382-
end
383-
384-
# Determine the number of non-zero rows, with meps as a tolerance. For an
385-
# upper-triangular matrix, this is a good proxy for determining the row-rank.
386-
function fastrank(A::AbstractMatrix, meps::Real)
387-
n, m = size(A)
388-
if n*m == 0 return 0 end
389-
norms = Vector{real(eltype(A))}(undef, n)
390-
for i = 1:n
391-
norms[i] = norm(A[i, :])
392-
end
393-
mrank = sum(norms .> meps)
394-
return mrank
395-
end
292+
# 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}
293+
# isempty(A) && return complex(T)[]
294+
295+
# # Balance the system
296+
# A, B, C = balance_statespace(A, B, C; verbose=false)
297+
298+
# # Compute a good tolerance
299+
# meps = 10*eps(real(T))*norm([A B; C D])
300+
301+
# # Step 1:
302+
# A_r, B_r, C_r, D_r = reduce_sys(A, B, C, D, meps)
303+
304+
# # Step 2: (conjugate transpose should be avoided since single complex zeros get conjugated)
305+
# 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)
306+
307+
# # Step 3:
308+
# # Compress cols of [C D] to [0 Df]
309+
# mat = [C_rc D_rc]
310+
# Wr = qr(mat').Q * I
311+
# W = reverse(Wr, dims=2)
312+
# mat = mat*W
313+
# if fastrank(mat', meps) > 0
314+
# nf = size(A_rc, 1)
315+
# m = size(D_rc, 2)
316+
# Af = ([A_rc B_rc] * W)[1:nf, 1:nf]
317+
# Bf = ([Matrix{T}(I, nf, nf) zeros(nf, m)] * W)[1:nf, 1:nf]
318+
# Z = schur(complex.(Af), complex.(Bf)) # Schur instead of eigvals to handle BigFloat
319+
# zs = Z.values
320+
# ControlSystemsBase._fix_conjugate_pairs!(zs) # Generalized eigvals does not return exact conj. pairs
321+
# else
322+
# zs = complex(T)[]
323+
# end
324+
# return zs
325+
# end
326+
327+
# """
328+
# reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
329+
# Implements REDUCE in the Emami-Naeini & Van Dooren paper. Returns transformed
330+
# A, B, C, D matrices. These are empty if there are no zeros.
331+
# """
332+
# function reduce_sys(A::AbstractMatrix, B::AbstractMatrix, C::AbstractMatrix, D::AbstractMatrix, meps::AbstractFloat)
333+
# T = promote_type(eltype(A), eltype(B), eltype(C), eltype(D))
334+
# Cbar, Dbar = C, D
335+
# if isempty(A)
336+
# return A, B, C, D
337+
# end
338+
# while true
339+
# # Compress rows of D
340+
# U = qr(D).Q
341+
# D = U'D
342+
# C = U'C
343+
# sigma = fastrank(D, meps)
344+
# Cbar = C[1:sigma, :]
345+
# Dbar = D[1:sigma, :]
346+
# Ctilde = C[(1 + sigma):end, :]
347+
# if sigma == size(D, 1)
348+
# break
349+
# end
350+
351+
# # Compress columns of Ctilde
352+
# V = reverse(qr(Ctilde').Q * I, dims=2)
353+
# Sj = Ctilde*V
354+
# rho = fastrank(Sj', meps)
355+
# nu = size(Sj, 2) - rho
356+
357+
# if rho == 0
358+
# break
359+
# elseif nu == 0
360+
# # System has no zeros, return empty matrices
361+
# A = B = Cbar = Dbar = Matrix{T}(undef, 0,0)
362+
# break
363+
# end
364+
# # Update System
365+
# n, m = size(B)
366+
# 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)
367+
# if sigma > 0
368+
# M = [A B; Cbar Dbar]
369+
# Vs = [copy(V') zeros(T, n, sigma) ; zeros(T, sigma, n) Matrix{T}(I, sigma, sigma)]
370+
# else
371+
# M = [A B]
372+
# Vs = copy(V')
373+
# end
374+
# sigma, rho, nu
375+
# M = Vs * M * Vm
376+
# A = M[1:nu, 1:nu]
377+
# B = M[1:nu, (nu + rho + 1):end]
378+
# C = M[(nu + 1):end, 1:nu]
379+
# D = M[(nu + 1):end, (nu + rho + 1):end]
380+
# end
381+
# return A, B, Cbar, Dbar
382+
# end
383+
384+
# # Determine the number of non-zero rows, with meps as a tolerance. For an
385+
# # upper-triangular matrix, this is a good proxy for determining the row-rank.
386+
# function fastrank(A::AbstractMatrix, meps::Real)
387+
# n, m = size(A)
388+
# if n*m == 0 return 0 end
389+
# norms = Vector{real(eltype(A))}(undef, n)
390+
# for i = 1:n
391+
# norms[i] = norm(A[i, :])
392+
# end
393+
# mrank = sum(norms .> meps)
394+
# return mrank
395+
# end
396396

397397
"""
398398
relative_gain_array(G, w::AbstractVector)

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
@test_throws MethodError poles(big(1.0)*ssrand(1,1,1)) # This errors before loading GenericSchur
22
using GenericSchur # Required to compute eigvals (in tzeros and poles) of a matrix with exotic element types
3-
@testset "test_analysis" begin
3+
44
es(x) = sort(x, by=LinearAlgebra.eigsortby)
55
## tzeros ##
66
# Examples from the Emami-Naeini & Van Dooren Paper
@@ -23,7 +23,7 @@ ex_3 = ss(A, B, C, D)
2323
@test es(tzeros(ex_3)) es([0.3411639019140099 + 1.161541399997252im,
2424
0.3411639019140099 - 1.161541399997252im,
2525
0.9999999999999999 + 0.0im,
26-
-0.6823278038280199 + 0.0im])
26+
-0.6823278038280199 + 0.0im]) atol=1e-6
2727
# Example 4
2828
A = [-0.129 0.0 0.396e-1 0.25e-1 0.191e-1;
2929
0.329e-2 0.0 -0.779e-4 0.122e-3 -0.621;
@@ -58,7 +58,7 @@ C = [0 -1 0]
5858
D = [0]
5959
ex_6 = ss(A, B, C, D)
6060
@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."
61-
@test_broken tzeros(big(1.0)ex_6) == [2]
61+
@test tzeros(big(1.0)ex_6) == [2]
6262
@test ControlSystemsBase.count_integrators(ex_6) == 2
6363

6464
@test ss(A, [0 0 1]', C, D) == ex_6
@@ -79,8 +79,8 @@ C = [0 0 0 1 0 0]
7979
D = [0]
8080
ex_8 = ss(A, B, C, D)
8181
# TODO : there may be a way to improve the precision of this example.
82-
@test tzeros(ex_8) [-1.0, -1.0] atol=1e-7
83-
@test tzeros(big(1)ex_8) [-1.0, -1.0] atol=1e-7
82+
@test tzeros(ex_8) [-1.0, -1.0] atol=1e-6
83+
@test tzeros(big(1)ex_8) [-1.0, -1.0] atol=1e-12
8484
@test ControlSystemsBase.count_integrators(ex_8) == 0
8585

8686
# Example 9
@@ -103,7 +103,7 @@ D = [0 0;
103103
0 0;
104104
0 0]
105105
ex_11 = ss(A, B, C, D)
106-
@test tzeros(ex_11) [4.0, -3.0]
106+
@test tzeros(ex_11) [4.0, -3.0] atol=1e-5
107107
@test tzeros(big(1)ex_11) [4.0, -3.0]
108108

109109
# Test for multiple zeros, siso tf
@@ -405,7 +405,6 @@ end
405405
@test length(tzeros(G)) == 3
406406
@test es(tzeros(G)) es(tzeros(big(1)G))
407407

408-
end
409408

410409

411410
## large TF poles and zeros

0 commit comments

Comments
 (0)