Skip to content

Commit 836c11c

Browse files
committed
add tools for counting multiple eigvals and adjust bode phase start
1 parent fd44401 commit 836c11c

File tree

5 files changed

+88
-3
lines changed

5 files changed

+88
-3
lines changed

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,47 @@ function poles(sys::TransferFunction{<:TimeEvolution,SisoZpk{T,TR}}) where {T, T
3737
return lcmpoles
3838
end
3939

40+
41+
function count_eigval_multiplicity(p, location)
42+
T = float(real(eltype(p)))
43+
n = length(p)
44+
n == 0 && return 0
45+
maximum(1:n) do i
46+
# if we count i poles within the circle assuming i integrators, we return i
47+
if count(p->abs(p-location) < (i+1)*(eps(T)^(1/i)), p) >= i
48+
i
49+
else
50+
0
51+
end
52+
end
53+
end
54+
55+
"""
56+
count_integrators(P)
57+
58+
Count the number of poles in the origin by finding the maximum value of `n` for which the number of poles within a circle of radius `(n+1)*eps(numeric_type(sys))^(1/n)` arount the origin (1 in discrete time) equals `n`.
59+
"""
60+
function count_integrators(P::LTISystem)
61+
p = poles(P)
62+
location = iscontinuous(P) ? 0 : 1
63+
count_eigval_multiplicity(p, location)
64+
end
65+
66+
"""
67+
integrator_excess(P)
68+
69+
Count the number of integrators in the system by finding the difference between the number of poles in the origin and the number of zeros in the origin. If the number of zeros in the origin is greater than the number of poles in the origin, the count is negative.
70+
71+
For discrete-tiem systems, the origin ``s = 0`` is replaced by the point ``z = 1``.
72+
"""
73+
function integrator_excess(P::LTISystem)
74+
p = poles(P)
75+
z = tzeros(P)
76+
location = iscontinuous(P) ? 0 : 1
77+
count_eigval_multiplicity(p, location) - count_eigval_multiplicity(z, location)
78+
end
79+
80+
4081
# TODO: Improve implementation, should be more efficient ways.
4182
# Calculates the same minors several times in some cases.
4283
"""

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,7 @@ optionally provided. To change the Magnitude scale see [`setPlotScale`](@ref). T
253253
254254
- If `hz=true`, the plot x-axis will be displayed in Hertz, the input frequency vector is still treated as rad/s.
255255
- `balance`: Call [`balance_statespace`](@ref) on the system before plotting.
256+
- `adjust_phase_start`: If true, the phase will be adjusted so that it starts at -90*intexcess degrees, where `intexcess` is the integrator excess of the system.
256257
257258
`kwargs` is sent as argument to RecipesBase.plot.
258259
"""
@@ -274,7 +275,7 @@ function _get_plotlabel(s, i, j)
274275
end
275276
end
276277

277-
@recipe function bodeplot(p::Bodeplot; plotphase=true, ylimsphase=(), unwrap=true, hz=false, balance=true)
278+
@recipe function bodeplot(p::Bodeplot; plotphase=true, ylimsphase=(), unwrap=true, hz=false, balance=true, adjust_phase_start=true)
278279
systems, w = _processfreqplot(Val{:bode}(), p.args...)
279280
ws = (hz ? 1/(2π) : 1) .* w
280281
ny, nu = size(systems[1])
@@ -328,6 +329,15 @@ end
328329
end
329330
plotphase || continue
330331

332+
if adjust_phase_start == true
333+
intexcess = integrator_excess(sbal)
334+
if intexcess != 0
335+
# Snap phase so that it starts at -90*intexcess
336+
nineties = round(Int, phasedata[1] / 90)
337+
phasedata .+= ((90*(-intexcess-nineties)) ÷ 360) * 360
338+
end
339+
end
340+
331341
@series begin
332342
xscale --> :log10
333343
# ylims := ylimsphase

lib/ControlSystemsBase/src/simplification.jl

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,11 @@ function struct_ctrb_states(A::AbstractVecOrMat, B::AbstractVecOrMat)
4747
# UInt16 can only store up to 65535, so if A is completely dense and of size larger than 65535, the computations below might overflow. This is exceedingly unlikely though.
4848
bitA = UInt16.(.!iszero.(A)) # Convert to Int because multiplying with a bit matrix is slow
4949
x = vec(any(B .!= 0, dims=2)) # index vector indicating states that have been affected by input
50-
xi = bitA * x
50+
if A isa SMatrix
51+
xi = Vector(bitA * x) # To aboid setindex! error below
52+
else
53+
xi = bitA * x
54+
end
5155
xi2 = similar(xi)
5256
@. xi = (xi != false) | !iszero(x)
5357
for i = 2:size(A, 1) # apply A nx times, similar to controllability matrix

lib/ControlSystemsBase/src/types/conversion.jl

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,13 @@ function siso_ss_to_zpk(sys, i, j)
337337
nx = size(A, 1)
338338
nz = length(z)
339339
k = nz == nx ? D[1] : (C*(A^(nx - nz - 1))*B)[1]
340-
return z, eigvals(A), k
340+
if A isa SMatrix
341+
# Only hermitian matrices are diagonalizable by *StaticArrays*. Non-Hermitian matrices should be converted to `Array` first.
342+
p = eigvals(Matrix(A))
343+
else
344+
p = eigvals(A)
345+
end
346+
return z, p, k
341347
end
342348

343349
"""

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ D = [1 0;
1616
1 0]
1717

1818
ex_3 = ss(A, B, C, D)
19+
@test ControlSystemsBase.count_integrators(ex_3) == 6
1920
@test tzeros(ex_3) [0.3411639019140099 + 1.161541399997252im,
2021
0.3411639019140099 - 1.161541399997252im,
2122
0.9999999999999999 + 0.0im,
@@ -36,12 +37,14 @@ C = [1 0 0 0 0;
3637
D = zeros(2, 2)
3738
ex_4 = ss(A, B, C, D)
3839
@test tzeros(ex_4) [-0.06467751189940692,-0.3680512036036696]
40+
@test ControlSystemsBase.count_integrators(ex_4) == 1
3941

4042
# Example 5
4143
s = tf("s")
4244
ex_5 = 1/s^15
4345
@test tzeros(ex_5) == Float64[]
4446
@test tzeros(ss(ex_5)) == Float64[]
47+
@test ControlSystemsBase.count_integrators(ex_5) == 15
4548

4649
# Example 6
4750
A = [2 -1 0;
@@ -52,6 +55,7 @@ C = [0 -1 0]
5255
D = [0]
5356
ex_6 = ss(A, B, C, D)
5457
@test tzeros(ex_6) == Float64[]
58+
@test ControlSystemsBase.count_integrators(ex_6) == 2
5559

5660
@test ss(A, [0 0 1]', C, D) == ex_6
5761

@@ -72,6 +76,7 @@ D = [0]
7276
ex_8 = ss(A, B, C, D)
7377
# TODO : there may be a way to improve the precision of this example.
7478
@test tzeros(ex_8) [-1.0, -1.0] atol=1e-7
79+
@test ControlSystemsBase.count_integrators(ex_8) == 0
7580

7681
# Example 9
7782
ex_9 = (s - 20)/s^15
@@ -121,6 +126,7 @@ approxin2(el,col) = any(el.≈col)
121126

122127
ex_12 = ss(-3, 2, 1, 2)
123128
@test poles(ex_12) [-3]
129+
@test ControlSystemsBase.count_integrators(ex_12) == 0
124130

125131
ex_13 = ss([-1 1; 0 -1], [0; 1], [1 0], 0)
126132
@test poles(ex_13) [-1, -1]
@@ -307,4 +313,22 @@ gof = gangoffour(P,C)
307313
F = ssrand(2,2,2,proper=true)
308314
@test_nowarn gangofseven(P, C, F)
309315

316+
317+
## Approximate double integrator
318+
P = let
319+
tempA = [
320+
0.0 0.0 1.0 0.0 0.0 0.0
321+
0.0 0.0 0.0 0.0 1.0 0.0
322+
-400.0 400.0 -0.4 -0.0 0.4 -0.0
323+
0.0 0.0 0.0 0.0 0.0 1.0
324+
10.0 -11.0 0.01 1.0 -0.011 0.001
325+
0.0 10.0 0.0 -10.0 0.01 -0.01
326+
]
327+
tempB = [0.0 0.0; 0.0 0.0; 100.80166347371734 0.0; 0.0 0.0; 0.0 -0.001; 0.0 0.01]
328+
tempC = [0.0 0.0 0.0 1.0 0.0 0.0]
329+
tempD = [0.0 0.0]
330+
ss(tempA, tempB, tempC, tempD)
331+
end
332+
@test ControlSystemsBase.count_integrators(P) == 2
333+
310334
end

0 commit comments

Comments
 (0)