Skip to content

Commit 6e64c52

Browse files
authored
Merge pull request #990 from JuliaControl/tf_pz
Use algorithms for poles and zeros of rational matrices from MatrixPencils.jl
2 parents b7a903b + b69edeb commit 6e64c52

File tree

4 files changed

+36
-9
lines changed

4 files changed

+36
-9
lines changed

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ poles(sys::AbstractStateSpace) = eigvalsnosort(sys.A)
1111

1212
# Seems to have a lot of rounding problems if we run the full thing with sisorational,
1313
# converting to zpk before works better in the cases I have tested.
14-
poles(sys::TransferFunction) = poles(zpk(sys))
14+
function poles(sys::TransferFunction{<:TimeEvolution,SisoRational{T}}; kwargs...) where {T}
15+
n,d = numpoly(sys), denpoly(sys)
16+
sort!(filter!(isfinite, MatrixPencils.rmpoles(n, d; kwargs...)[1]), by=abs)
17+
end
1518

1619
function poles(sys::TransferFunction{<:TimeEvolution,SisoZpk{T,TR}}) where {T, TR}
1720
# With right TR, this code works for any SisoTf
@@ -248,11 +251,20 @@ If `sys` is a state-space system the function has additional keyword arguments,
248251
249252
To compute zeros of a system with non-BLAS floats, such as `BigFloat`, install and load the package `GenericSchur.jl` before calling `tzeros`.
250253
"""
251-
function tzeros(sys::TransferFunction)
254+
function tzeros(sys::TransferFunction{<:TimeEvolution,SisoRational{T}}; kwargs...) where {T}
255+
if issiso(sys)
256+
return tzeros(sys.matrix[1,1])
257+
else
258+
n,d = numpoly(sys), denpoly(sys)
259+
filter!(isfinite, MatrixPencils.rmzeros(n, d; kwargs...)[1]) # This uses rm2ls -> rm2lspm internally
260+
end
261+
end
262+
263+
function tzeros(sys::TransferFunction{<:TimeEvolution,SisoZpk{T,TR}}) where {T, TR}
252264
if issiso(sys)
253265
return tzeros(sys.matrix[1,1])
254266
else
255-
return tzeros(ss(sys, minimal=true))
267+
return tzeros(ss(sys)) # Convert to ss since rmzeros does this anyways, so no reason to pass through tf{SisoRational}
256268
end
257269
end
258270

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ sys = s*(s + 1)*(s^2 + 1)*(s - 3)/((s + 1)*(s + 4)*(s - 4))
112112
@test tzeros(sys) [3.0, -1.0, im, -im, 0.0]
113113

114114
## POLE ##
115-
@test poles(sys) [4.0, -4.0, -1.0]
116-
@test poles([sys sys]) [4.0, -4.0, -1.0] # Issue #81
115+
@test es(poles(sys)) es([4.0, -4.0, -1.0])
116+
@test es(poles(sys, atol=1e-6)) es([4.0, -4.0]) # This cancels the -1 pole/zero
117+
@test es(poles([sys sys], atol=1e-6)) es([4.0, -4.0]) # Issue #81
118+
@test poles(zpk([sys sys])) [4.0, -4.0, -1.0] # This does not cancel the -1 pole/zero
117119
@test poles(ex_11) eigvals(ex_11.A)
118120
@test poles([2/(s+1) 3/(s+2); 1/(s+1) 1/(s+1)]) [-1, -1, -2]
119121

@@ -198,7 +200,7 @@ sys = s*(s + 1)*(s^2 + 1)*(s - 3)/((s + 1)*(s + 4)*(s - 4))
198200

199201
# Example 5.5 from https://www.control.lth.se/fileadmin/control/Education/EngineeringProgram/FRTN10/2019/e05_both.pdf
200202
G = [1/(s+2) -1/(s+2); 1/(s+2) (s+1)/(s+2)]
201-
@test_broken length(poles(G)) == 1 # The tf poles don't understand the cancellations
203+
@test length(poles(G)) == 1 # The tf poles do understand the cancellations
202204
@test length(poles(ss(G, minimal=true))) == 1 # The ss version with minimal realization does
203205
@test length(tzeros(G)) == 0 # tzeros converts to minimal ss relalization
204206
@test minreal(ss(G)).A [-2]
@@ -398,4 +400,16 @@ end
398400
@test length(tzeros(G)) == 3
399401
@test es(tzeros(G)) es(tzeros(big(1)G))
400402

401-
end
403+
end
404+
405+
406+
## large TF poles and zeros
407+
G = ssrand(2,3,4)
408+
Gtf = tf(G)
409+
410+
pss = poles(G)
411+
zss = tzeros(G)
412+
ptf = poles(Gtf)
413+
ztf = tzeros(Gtf)
414+
pzpk = poles(zpk(G))
415+
zzpk = tzeros(zpk(G))

lib/ControlSystemsBase/test/test_discrete.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ p = poles(Gcl)
140140
# Test that all desired poles are in the closed-loop system
141141
@test norm(minimum(abs.((poles(tf(Bm,Am)) .- sort(p, by=imag)')), dims=2)) < 1e-6
142142
# Test that the observer poles are in the closed-loop system
143-
@test norm(minimum(abs.((poles(tf(1,Ao)) .- sort(p, by=imag)')), dims=2)) < 1e-6
143+
# @test norm(minimum(abs.((poles(tf(1,Ao)) .- sort(p, by=imag)')), dims=2)) < 1e-6
144+
@test sort(poles(tf(1,Ao)), by=LinearAlgebra.eigsortby)[2:end] sort(p, by=LinearAlgebra.eigsortby) # One pole is (correctly) cancelled in Gcl, causing the test above to fail
144145

145146

146147
pd = c2d_poly2poly(A, 0.1)

lib/ControlSystemsBase/test/test_pid_design.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Tf = 0.01
5252

5353
# Different damping
5454
Ctf = pid(1,1,1, Tf=0.1, d = 1)
55-
@test all(p->imag(p) == 0, poles(Ctf))
55+
@test all(p->isapprox(imag(p), 0, atol=1e-6), poles(Ctf))
5656
Css = pid(1,1,1, Tf=0.1, d = 1, state_space=true)
5757
@test all(p->imag(p) == 0, poles(Css))
5858
@test tf(Css) Ctf

0 commit comments

Comments
 (0)