Repository navigation
Compute the default axis limits of nyquistplot per subplot from the curves and the critical point - #1084
Merged
Conversation
…urves and the critical point The Plots recipe clipped the curves to a fixed window of ±20, failed with an ArgumentError for curves outside this window, took the limits of channel (1,1) of the first system for all subplots and systems, and ignored the keyword argument critical_point. The Makie recipe set no limits. The new function nyquist_limits computes the limits of each channel from the union over all systems of the critical point and a neighborhood of it, the origin, the requested circles of radius at most 2, and the Nyquist curve excluding the frequencies at which the factor contributed by poles on the imaginary axis exceeds a bound (nyquist_limit_mask). Both recipes use it. Part of #1081 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HigNnun4aJKMoWmY7wyULn
|
This is an automated message.
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1084 +/- ##
==========================================
+ Coverage 91.98% 92.09% +0.10%
==========================================
Files 42 42
Lines 5976 6056 +80
==========================================
+ Hits 5497 5577 +80
Misses 479 479 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…and without user-provided limits The default limits require the poles and zeros of the systems, which are not available for, e.g., uncertain numbers (MonteCarloMeasurements.Particles) or BigFloat without GenericSchur. For such number types, the limits are left to the plotting backend. The Plots recipe does not compute the default limits if both xlims and ylims are provided. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HigNnun4aJKMoWmY7wyULn
|
This is an automated message.
|
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HigNnun4aJKMoWmY7wyULn
baggepinnen
referenced
this pull request
Oct 13, 2026
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HigNnun4aJKMoWmY7wyULn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.












Part of #1081
Problem
The default axis limits of the Plots recipe
nyquistplotwere computed asoutside of the
@seriesblock, with the following consequences:nyquistplot(1000/(s+1)^3)shows Re ∈ (-19.8, 1.05), Im ∈ (-1.05, 15.9), although the curve spans Re ∈ [-250, 1000], Im ∈ [-730, 20].nyquistplot(ss(100) + tf(1,[1,1]))givesArgumentError: reducing over an empty collection is not allowed.-->does not overwrite, the limits of channel (1,1) of the first system are used for all subplots and all systems:nyquistplot([tf(1,[1,1]) tf(10,[1,1]); tf(0.1,[1,1]) tf(10,[1,2,1])])gives (-1.05, 1.05) × (-1.05, 1.05) in every subplot, andnyquistplot([tf(1,[1,1])^2, tf(10,[1,1])^2])gives (-1.05, 1.05) × (-1.05, 1.05).critical_pointis given. The Makie recipe sets no limits; for1/(s(s+1))the axis extends to Im = -100, and the region around -1 cannot be read.Change
A new function
ControlSystemsBase.nyquist_limits(systems, w; critical_point, circles, max_factor = 5, pad = 0.1, responses)returns a matrix with the limits(xlims, ylims)of each channel. The limits are the smallest box containing, for all systems,cand the square of half side|c|/2centered atc,nyquist_limit_mask(sys, w; max_factor).The box is enlarged by the fraction
padof its extent along each axis.nyquist_limit_maskkeeps all frequencies if the system has no poles on the imaginary axis (the unit circle in discrete time). Otherwise, it excludes the frequencies at which the magnitude of the factor contributed by these poles exceedsmax_factor:npoles at±iωp: factor(ωp²/|ωp² - ω²|)^n, equal to one at ω = 0,npoles in the origin: factor(ωl/ω)^n, whereωlis the smallest magnitude of the poles and zeros not located in the origin. If there is no such pole or zero (e.g.1/s^2), the curve has no intrinsic scale and is not used.A pole is classified as located on the imaginary axis if
count_eigval_multiplicitycounts it among the poles at its projection onto the imaginary axis (the unit circle), i.e., with the tolerance(100ϵ)^(1/n)·max|p|used forcount_integrators. Discrete-time poles and zeros are mapped bys = log(z)/Ts. The result is invariant undertime_scale(tested). For aDelayLtiSystem, the poles and zeros of the delay-free system are used; for other non-rational systems, the entire curve is used.The magnitude-based alternative (excluding points whose magnitude exceeds a multiple of the median magnitude of the curve) was not chosen, since the median depends on the frequency grid: for a user-provided grid extending far beyond the bandwidth, the median is small and parts of a finite curve would be excluded.
The Plots recipe sets
xlims -->andylims -->in the last series of each subplot (wheretitleandyguideare already set, since Plots applies the subplot attributes of the last series of each subplot), so that each subplot obtains its own limits and limits provided by the user take precedence. The Makie recipe callslimits!on each Cartesian axis unless the keyword argumentlimitsis provided; polar axes are unchanged (rlimits).Systems with other number types: the default limits require the poles and zeros, so they are computed only if
float(numeric_type(sys))is a LAPACK type (BlasFloat) for all systems. For other number types (e.g.MonteCarloMeasurements.Particles, dual numbers,BigFloatwithout GenericSchur), the limits are left to the plotting backend, as for any plot without explicit limits. The Plots recipe also skips the computation if bothxlimsandylimsare provided, and the Makie recipe iflimitsis provided. This keepsnyquistplotworking for systems with uncertain coefficients, which a downstream test of RobustAndOptimalControl uses (nyquistplot(delay(Particles(...))*tf(1, [0.01, 1])*pid(2, 1), w; xlims, ylims)errored incount_eigval_multiplicitywith the first commit of this PR).Effect on users
The default limits of all Nyquist plots change:
1/(s(s+1))the window is Re ∈ (-1.65, 0.15), Im ∈ (-5.3, 1.0) instead of (-1.05, 1.05) × (-19.9, 1.05).Remaining differences between the backends (not addressed here): the Makie recipe has no
disk_margin_circleskeyword argument and nohzhover information.Visual comparison
Rendered with Plots/GR and CairoMakie (before/after, not committed):
1000/(s+1)^3, Plots: before, only the part with Re ∈ (-20, 1) of the high-frequency end of the curve is visible; after, the whole spiral from 1000 down to the origin is visible.1/(s(s+1)), Makie: before, a thin axis with Im down to -100 and Re ticks overlapping; after, Im ∈ (-5.3, 1.0), the asymptote Re = -1 and the critical point are readable.1/((s²+1)(s+1)), Plots: before, Re, Im ∈ (-15, 15); after, approximately (-2.6, 3) × (-2.8, 2.8) with the critical point in the center.Tests
New testset
nyquistplot limitsintest_plots.jl(reproducers above, limits contain the curve of the high-gain system, per-channel limits of the MIMO example and the union over two systems, invariance undertime_scale, exact masks for integrators, double integrators, undamped modes and a discrete-time integrator, finite limits containing -1 for1/(s(s+1)),1/s^2and a delayed integrator,critical_point, circles, per-subplot limits in the Plots recipe, user-providedxlims, aBigFloatsystem with and without user-provided limits), and Makie tests intest_makie_plots.jl(axis limits equalnyquist_limitsfor each axis, user-providedlimitsrespected, no default limits for aBigFloatsystem). The RobustAndOptimalControl reproducer withParticleswas verified manually, with and without user-provided limits.Run locally (Julia 1.12.7):
test_plots,test_makie_plots,test_freqresp,test_delayed_systems: 363 passed, 0 failed.Host:
demeter6, Claude Code sessionsession_01HigNnun4aJKMoWmY7wyULn, working directory~/.julia/dev/ControlSystems/.claude/worktrees/agent-a8a6bc7868b047e82🤖 Generated with Claude Code
https://claude.ai/code/session_01HigNnun4aJKMoWmY7wyULn