Skip to content

Commit 75801d6

Browse files
authored
add option to draw an M circle in the Nyquist plot (#856)
* add option to draw an M circle in the Nyquist plot * Rename M_circles to disk_margin_circles, document margins - Rename keyword to avoid clashing with the classical Hall M-circle terminology already used by Mt_circles. - Document the symmetric gain margin [(M-1)/M, M/(M-1)] and the phase margin atan((2M-1)/(2M(M-1))) guarantees. - Default to Float64[] for type stability. - Validate M > 1 (avoids the M = 1 singularity and M < 1 sign flip).
1 parent 2e102ad commit 75801d6

2 files changed

Lines changed: 21 additions & 5 deletions

File tree

lib/ControlSystemsBase/src/plotting.jl

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -408,23 +408,24 @@ end
408408

409409
@userplot Nyquistplot
410410
"""
411-
fig = nyquistplot(sys; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
412-
nyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
411+
fig = nyquistplot(sys; Ms_circles=Float64[], Mt_circles=Float64[], disk_margin_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
412+
nyquistplot(LTISystem[sys1, sys2...]; Ms_circles=Float64[], Mt_circles=Float64[], disk_margin_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, kwargs...)
413413
414414
Create a Nyquist plot of the `LTISystem`(s). A frequency vector `w` can be
415415
optionally provided.
416416
417417
- `unit_circle`: if the unit circle should be displayed. The Nyquist curve crosses the unit circle at the gain crossover frequency.
418418
- `Ms_circles`: draw circles corresponding to given levels of sensitivity (circles around -1 with radii `1/Ms`). `Ms_circles` can be supplied as a number or a vector of numbers. A design staying outside such a circle has a phase margin of at least `2asin(1/(2Ms))` rad and a gain margin of at least `Ms/(Ms-1)`. See also [`margin_bounds`](@ref), [`Ms_from_phase_margin`](@ref) and [`Ms_from_gain_margin`](@ref).
419419
- `Mt_circles`: draw circles corresponding to given levels of complementary sensitivity. `Mt_circles` can be supplied as a number or a vector of numbers.
420+
- `disk_margin_circles`: draw disk-margin circles for the given values of `M` (`M > 1`, supplied as a number or vector of numbers). Each circle is the disk on the negative real axis passing through `-(M-1)/M` and `-M/(M-1)`. A design whose Nyquist curve stays outside this disk has a symmetric (balanced) gain margin of at least `[(M-1)/M, M/(M-1)]` — i.e. the loop gain may be multiplied by any real factor in that interval without losing stability — together with a phase margin of at least `atan((2M-1)/(2M(M-1)))` rad. Larger `M` corresponds to a smaller, more permissive disk and weaker margin guarantees.
420421
- `critical_point`: point on real axis to mark as critical for encirclements
421422
- If `hz=true`, the hover information will be displayed in Hertz, the input frequency vector is still treated as rad/s.
422423
- `balance`: Call [`balance_statespace`](@ref) on the system before plotting.
423424
424425
`kwargs` is sent as argument to plot.
425426
"""
426427
nyquistplot
427-
@recipe function nyquistplot(p::Nyquistplot; Ms_circles=Float64[], Mt_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, balance=true, adaptive=true)
428+
@recipe function nyquistplot(p::Nyquistplot; Ms_circles=Float64[], Mt_circles=Float64[], disk_margin_circles=Float64[], unit_circle=false, hz=false, critical_point=-1, balance=true, adaptive=true)
428429
systems, w = _processfreqplot(Val{:nyquist}(), p.args...; adaptive)
429430
ny, nu = size(systems[1])
430431
nw = length(w)
@@ -490,7 +491,22 @@ nyquistplot
490491
rt = Mt/(Mt^2-1) # Mt radius
491492
ct.+rt.*C, rt.*S
492493
end
493-
end
494+
end
495+
for M in disk_margin_circles
496+
M > 1 || throw(ArgumentError("disk_margin_circles requires M > 1, got M = $M"))
497+
@series begin
498+
subplot --> s2i(i,j)
499+
primary := false
500+
linestyle := :dash
501+
linecolor := :gray
502+
seriestype := :path
503+
markershape := :none
504+
label := "Disk margin M = $(round(M, digits=2))"
505+
ct = -(2M^2 - 2M + 1)/(2M*(M-1)) # disk-margin center
506+
rt = (2M - 1)/(2M*(M-1)) # disk-margin radius
507+
ct.+rt.*C, rt.*S
508+
end
509+
end
494510
if unit_circle
495511
@series begin
496512
subplot --> s2i(i,j)

lib/ControlSystemsBase/test/test_plots.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ function getexamples()
1818
setPlotScale("dB")
1919
bodeplot(sys,ws)
2020
end
21-
nyquistgen() = nyquistplot(sysss,ws, Ms_circles=1.2, Mt_circles=1.2)
21+
nyquistgen() = nyquistplot(sysss,ws, Ms_circles=1.2, Mt_circles=1.2, disk_margin_circles=1.2)
2222
sigmagen() = sigmaplot(sysss,ws)
2323
#Only siso for now
2424
nicholsgen() = nicholsplot(tf1,ws)

0 commit comments

Comments
 (0)