Balance the realization in the frequency-response and norm functions - #1071
Merged
Merged
Conversation
The functions that depend on a system only through its input-output map now call `balance_statespace` internally instead of leaving the balancing to the caller. Balancing improves the numerical conditioning for poorly scaled realizations and does not change the result, and a `balance = false` keyword argument is available everywhere to turn it off. The balancing is performed in `freqresp`/`freqresp!` so that every function built on top of it benefits. The plot recipes for Plots.jl and Makie.jl no longer balance the system themselves, they forward their `balance` keyword argument to the inner function instead. As a side effect, the Nyquist, sigma and RGA recipes now label the series with the signal names of the original system rather than those of the balanced copy. The following entry points gained a `balance` keyword argument: `freqresp`, `freqresp!`, `freqresp_nohess`, `freqresp_nohess!`, `bode`, `bodemag!`, `nyquist`, `sigma`, `margin`, `sisomargin`, `delaymargin`, `dcgain`, `relative_gain_array`, `hinfnorm`, `linfnorm`, `norm` and `nicholsplot`. Two related defects are fixed: - `balance_transform` promoted the element type with `promote_type(eltype(A), eltype(B), eltype(C), Float32)`, which demotes an integer or rational realization to `Float32`. Balancing such a system before computing its H-infinity norm lost about eight digits of accuracy. - `balance_statespace` returned a `StateSpace` for every `AbstractStateSpace`, which turned a `HeteroStateSpace` holding static matrices into one holding dense matrices. A `HeteroStateSpace` method that restores the matrix container type is added. `evalfr` is left unchanged and its docstring states that it does not balance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
This is an automated message.
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1071 +/- ##
==========================================
- Coverage 91.66% 91.64% -0.02%
==========================================
Files 42 42
Lines 5793 5795 +2
==========================================
+ Hits 5310 5311 +1
- Misses 483 484 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
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.








Summary
balance_statespacewas called by the frequency-response plot recipes, but not by the functions they call, so a user callingbode,nyquist,marginetc. directly got the unbalanced result. This moves the balancing into the functions themselves, where it belongs, and gives every affected entry point abalance = falsekeyword argument to turn it off.The balancing is performed in
freqresp/freqresp!, so everything built on top of them benefits. The plot recipes for both Plots.jl and Makie.jl no longer balance the system themselves, they forward their existingbalancekeyword argument to the inner function.Entry points that gained a
balancekeyword argumentfreqresp,freqresp!,freqresp_nohess,freqresp_nohess!,bode,bodemag!,nyquist,sigma,margin,sisomargin,delaymargin,dcgain,relative_gain_array,hinfnorm,linfnorm,normandnicholsplot. All default tobalance = true.evalfris left unchanged, since it is the single-point primitive that the genericfreqresp!loop calls once per element and frequency. Its docstring now states that it does not balance.Related defects fixed
balance_transformcomputed its working element type aspromote_type(eltype(A), eltype(B), eltype(C), Float32), which demotes an integer or rational realization toFloat32. Balancingss([0 1; -1 -1], [0; 1], [1 0], 0)before computing its H-infinity norm therefore lost about eight digits of accuracy. The promotion now goes throughfloatfirst.balance_statespacereturned aStateSpacefor everyAbstractStateSpace, which turned aHeteroStateSpaceholdingSMatrixorSizedArraymatrices into one holding dense matrices, and additionally did not balance it at all (the in-place branch fails for immutable matrices and the failure was swallowed). AHeteroStateSpacemethod that computes the transformation with dense matrices and restores the matrix container type is added.Notes
balance = truedefault allocates a balanced copy of the system. This is negligible next to the frequency sweep itself, but it does matter for the in-placefreqresp!andbodemag!, which are otherwise free of allocations that scale with the state dimension. Both documentbalance = falsefor that case.swith the balanced copy, the Nyquist, sigma and RGA recipes now label the series with the signal names of the original system.docs/src/man/numerical.mdno longer states that the user is responsible for balancing before callingbodeandnyquist.Testing
lib/ControlSystemsBase/test/runtests.jlpasses in full (21322 passed, 6 broken, 0 failed). New tests intest_freqresp.jlcover the keyword on every entry point and the integer-realization accuracy, and new tests intest_staticsystems.jlcover the container-preservingHeteroStateSpacebalancing. The tests intest/of the outer package cover time responses only and were not run.🤖 Generated with Claude Code