Skip to content

Commit fdfef02

Browse files
committed
Merge branch 'inexcessfix' of github.com:JuliaControl/ControlSystems.jl into inexcessfix
2 parents 638739b + 3d57108 commit fdfef02

File tree

6 files changed

+42
-12
lines changed

6 files changed

+42
-12
lines changed

lib/ControlSystemsBase/src/analysis.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -494,12 +494,18 @@ function sisomargin(sys::LTISystem, w::AbstractVector{<:Real}; full=false, allMa
494494
wgm, = _allPhaseCrossings(w, phase)
495495
gm = similar(wgm)
496496
for i = eachindex(wgm)
497-
gm[i] = 1 ./ abs(freqresp(sys,[wgm[i]])[1][1])
497+
gm[i] = 1 ./ abs(freqresp(sys,wgm[i])[1])
498498
end
499499
wpm, fi = _allGainCrossings(w, mag)
500500
pm = similar(wpm)
501501
for i = eachindex(wpm)
502-
pm[i] = mod(rad2deg(angle(freqresp(sys,[wpm[i]])[1][1])),360)-180
502+
# 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.
503+
φ_nom = rad2deg(angle(freqresp(sys,wpm[i])[1]))
504+
φ_rounded = phase[clamp(round(Int, fi[i]), 1, length(phase))] # fi is interpolated, so we round to the closest integer
505+
φ_int = φ_nom - 360 * round( (φ_nom - φ_rounded) / 360 )
506+
507+
# Now compute phase margin relative to -180:
508+
pm[i] = 180 + φ_int
503509
end
504510
if !allMargins #Only output the smallest margins
505511
gm, idx = findmin([gm;Inf])

lib/ControlSystemsBase/src/connections.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,10 @@ function /(sys1::Union{StateSpace,AbstractStateSpace}, sys2::LTISystem)
215215
sys1new, sys2new = promote(sys1, 1/sys2)
216216
return sys1new*sys2new
217217
end
218+
function /(sys1::Union{StateSpace,AbstractStateSpace}, sys2::TransferFunction) # This method is handling ambiguity between method above and one with explicit TF as second argument, hit by ss(1)/tf(1)
219+
sys1new, sys2new = promote(sys1, 1/sys2)
220+
return sys1new*sys2new
221+
end
218222

219223
@static if VERSION >= v"1.8.0-beta1"
220224
blockdiag(anything...) = cat(anything..., dims=Val((1,2)))

lib/ControlSystemsBase/src/pid_design.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,8 @@ r ┌─────┐ ┌─────┐ │ │ │
150150
```
151151
152152
The `form` can be chosen as one of the following (determines how the arguments `param_p, param_i, param_d` are interpreted)
153-
* `:standard` - ``K_p*(br-y + (r-y)/(T_i s) + T_d s (cr-y)/(T_f s + 1))``
154-
* `:parallel` - ``K_p*(br-y) + K_i (r-y)/s + K_d s (cr-y)/(Tf s + 1)``
153+
* `:standard` - ``K_p(br-y + (r-y)/(T_i s) + T_d s (cr-y)/(T_f s + 1))``
154+
* `:parallel` - ``K_p(br-y) + K_i (r-y)/s + K_d s (cr-y)/(Tf s + 1)``
155155
156156
- `b` is a set-point weighting for the proportional term
157157
- `c` is a set-point weighting for the derivative term, this defaults to 0.

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,9 @@ end
293293
else
294294
sbal = s
295295
end
296+
if plotphase && adjust_phase_start && isrational(sbal)
297+
intexcess = integrator_excess(sbal)
298+
end
296299
mag, phase = bode(sbal, w; unwrap=false)
297300
if _PlotScale == "dB" # Set by setPlotScale(str) globally
298301
mag = 20*log10.(mag)
@@ -330,7 +333,6 @@ end
330333
plotphase || continue
331334

332335
if adjust_phase_start == true && isrational(sbal)
333-
intexcess = integrator_excess(sbal)
334336
if intexcess != 0
335337
# Snap phase so that it starts at -90*intexcess
336338
nineties = round(Int, phasedata[1] / 90)
@@ -429,7 +431,7 @@ nyquistplot
429431
if lab !== nothing
430432
label --> lab
431433
end
432-
hover --> [hz ? Printf.@sprintf("f = %.3f", w/2π) : Printf.@sprintf("ω = %.3f", w) for w in w]
434+
hover --> [hz ? Printf.@sprintf("f = %.3g", w/2π) : Printf.@sprintf("ω = %.3g", w) for w in w]
433435
(redata, imdata)
434436
end
435437

@@ -725,11 +727,12 @@ Plot all the amplitude and phase margins of the system(s) `sys`.
725727
726728
- A frequency vector `w` can be optionally provided.
727729
- `balance`: Call [`balance_statespace`](@ref) on the system before plotting.
730+
- `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.
728731
729732
`kwargs` is sent as argument to RecipesBase.plot.
730733
"""
731734
marginplot
732-
@recipe function marginplot(p::Marginplot; plotphase=true, hz=false, balance=true)
735+
@recipe function marginplot(p::Marginplot; plotphase=true, hz=false, balance=true, adjust_phase_start=true)
733736
systems, w = _processfreqplot(Val{:bode}(), p.args...)
734737
ny, nu = size(systems[1])
735738
s2i(i,j) = LinearIndices((nu,(plotphase ? 2 : 1)*ny))[j,i]
@@ -746,6 +749,11 @@ marginplot
746749
s = balance_statespace(s)[1]
747750
end
748751
bmag, bphase = bode(s, w)
752+
753+
if plotphase && adjust_phase_start && isrational(s)
754+
intexcess = integrator_excess(s)
755+
end
756+
749757
for j=1:nu
750758
for i=1:ny
751759
wgm, gm, wpm, pm, fullPhase = sisomargin(s[i,j],w, full=true, allMargins=true)
@@ -771,10 +779,10 @@ marginplot
771779
mag = 1 ./ gm
772780
oneLine = 1
773781
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],", ")*"] "
782+
titles[j,i,1,1] *= "["*join([Printf.@sprintf("%3.2g",v) for v in gm],", ")*"] "
783+
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],", ")*"] "
785+
titles[j,i,2,2] *= "["*join([Printf.@sprintf("%3.2g",v) for v in wpm],", ")*"] "
778786

779787
subplot := min(s2i((plotphase ? (2i-1) : i),j), prod(plotattributes[:layout]))
780788
if si == length(systems)
@@ -801,6 +809,15 @@ marginplot
801809
[wgm wgm]', [ones(length(mag)) mag]'
802810
end
803811
plotphase || continue
812+
813+
phasedata = bphase[i, j, :]
814+
if plotphase && adjust_phase_start && isrational(s)
815+
if intexcess != 0
816+
# Snap phase so that it starts at -90*intexcess
817+
nineties = round(Int, phasedata[1] / 90)
818+
phasedata .+= ((90*(-intexcess-nineties)) ÷ 360) * 360
819+
end
820+
end
804821

805822
# Phase margins
806823
subplot := s2i(2i,j)
@@ -810,7 +827,7 @@ marginplot
810827
@series begin
811828
primary := true
812829
seriestype := :bodephase
813-
w, bphase[i, j, :]
830+
w, phasedata
814831
end
815832
@series begin
816833
color --> :gray

lib/ControlSystemsBase/test/test_analysis.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ nintbig = ControlSystemsBase.count_integrators(big(1.0)*pade(OL, 2))
278278
@test ControlSystemsBase.count_integrators(pade(OL, 4)) == nintbig
279279
@test ControlSystemsBase.count_integrators(pade(OL, 10)) == nintbig
280280

281+
281282
# RGA
282283
a = 10
283284
P = ss([0 a; -a 0], I(2), [1 a; -a 1], 0)

lib/ControlSystemsBase/test/test_connections.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,4 +438,6 @@ Pr = input_resolvent(P)
438438
@test Pr.C == I
439439
@test iszero(Pr.D)
440440

441+
@test ss(1) / tf(1) == ss(1) # Test no method ambiguity
442+
441443
end

0 commit comments

Comments
 (0)