Skip to content

Commit d9736a4

Browse files
committed
handle phase wrapping in margin
1 parent 16325db commit d9736a4

File tree

3 files changed

+31
-6
lines changed

3 files changed

+31
-6
lines changed

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,12 +496,18 @@ function sisomargin(sys::LTISystem, w::AbstractVector{<:Real}; full=false, allMa
496496
wgm, = _allPhaseCrossings(w, phase)
497497
gm = similar(wgm)
498498
for i = eachindex(wgm)
499-
gm[i] = 1 ./ abs(freqresp(sys,[wgm[i]])[1][1])
499+
gm[i] = 1 ./ abs(freqresp(sys,wgm[i])[1])
500500
end
501501
wpm, fi = _allGainCrossings(w, mag)
502502
pm = similar(wpm)
503503
for i = eachindex(wpm)
504-
pm[i] = mod(rad2deg(angle(freqresp(sys,[wpm[i]])[1][1])),360)-180
504+
# We have to access the actual phase value from the `phase` array to get unwrapped phase. This value is not fully accurate since it is computed at a grid point, so we compute the more accurate phase at the interpolated frequency. This accurate value is not unwrapped, so we add an integer multiple of 360 to get the closest unwrapped phase.
505+
φ_nom = rad2deg(angle(freqresp(sys,wpm[i])[1]))
506+
φ_rounded = phase[clamp(round(Int, fi[i]), 1, length(phase))] # fi is interpolated, so we round to the closest integer
507+
φ_int = φ_nom - 360 * round( (φ_nom - φ_rounded) / 360 )
508+
509+
# Now compute phase margin relative to -180:
510+
pm[i] = 180 + φ_int
505511
end
506512
if !allMargins #Only output the smallest margins
507513
gm, idx = findmin([gm;Inf])

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -771,10 +771,10 @@ marginplot
771771
mag = 1 ./ gm
772772
oneLine = 1
773773
end
774-
titles[j,i,1,1] *= "["*join([Printf.@sprintf("%2.2f",v) for v in gm],", ")*"] "
775-
titles[j,i,1,2] *= "["*join([Printf.@sprintf("%2.2f",v) for v in wgm],", ")*"] "
776-
titles[j,i,2,1] *= "["*join([Printf.@sprintf("%2.2f",v) for v in pm],", ")*"] "
777-
titles[j,i,2,2] *= "["*join([Printf.@sprintf("%2.2f",v) for v in wpm],", ")*"] "
774+
titles[j,i,1,1] *= "["*join([Printf.@sprintf("%3.2g",v) for v in gm],", ")*"] "
775+
titles[j,i,1,2] *= "["*join([Printf.@sprintf("%3.2g",v) for v in wgm],", ")*"] "
776+
titles[j,i,2,1] *= "["*join([Printf.@sprintf("%3.2g",v) for v in pm],", ")*"] "
777+
titles[j,i,2,2] *= "["*join([Printf.@sprintf("%3.2g",v) for v in wpm],", ")*"] "
778778

779779
subplot := min(s2i((plotphase ? (2i-1) : i),j), prod(plotattributes[:layout]))
780780
if si == length(systems)

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,25 @@ P = tf(1,[5, 10.25, 6.25, 1])
250250
w_180, gm, w_c, pm = margin(50P)
251251
@test pm[] -35.1 rtol=1e-2
252252

253+
## Tricky case from https://www.reddit.com/r/ControlTheory/comments/1inhxsz/understanding_stability_in_highorder/
254+
s = tf("s")
255+
kpu = -10.593216768722073; kiu = -0.00063; t = 1000; tau = 180; a = 1/8.3738067325406132e-5;
256+
kpd = 15.92190277847431; kid = 0.000790960718241793;
257+
kpo = -10.39321676872207317; kio = -0.00063;
258+
kpb = kpd; kib = kid;
259+
260+
C1 = (kpu + kiu/s)*(1/(t*s + 1))
261+
C2 = (kpu + kiu/s)*(1/(t*s + 1))
262+
C3 = (kpo + kio/s)*(1/(t*s + 1))
263+
Cb = (kpb + kib/s)*(1/(t*s + 1))
264+
OL = (ss(Cb)*ss(C1)*ss(C2)*ss(C3)*exp(-3*tau*s))/((C1 - a*s)*(C2 - a*s)*(C3 - a*s));
265+
266+
wgm, gm, ωϕₘ, ϕₘ = margin(OL; full=true, allMargins=true)
267+
@test ϕₘ[][] -320 rtol=1e-2
268+
for wgm in wgm[]
269+
@test mod(rad2deg(angle(freqresp(OL, wgm)[])), 360)-180 0 atol=1e-1
270+
end
271+
253272
# RGA
254273
a = 10
255274
P = ss([0 a; -a 0], I(2), [1 a; -a 1], 0)

0 commit comments

Comments
 (0)