Skip to content

Add slow_fast decomposition and modal_reduction - #1087

Merged
baggepinnen merged 3 commits into
masterfrom
slow-fast
Oct 12, 2026
Merged

baggepinnen merged 3 commits into
masterfrom
slow-fast

Conversation

@baggepinnen

@baggepinnen baggepinnen commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

This PR adds a decomposition of a system into slow and fast parts, and a model-reduction method based on it.

slow_fast(sys, ωc)

Decomposes sys = slow + fast, where slow contains the poles with natural frequency less than ωc and fast contains the remaining poles and the direct feedthrough term D. The natural frequency of a pole p is |p| in continuous time and |log(p)|/Ts in discrete time, consistent with damp. The implementation uses an ordered Schur decomposition followed by a Sylvester equation, as in MatrixPencils.ssblkdiag. ssblkdiag cannot be reused because it only supports a half-plane or a disc centred at the origin as the selection region.

stab_unstab(sys; smarg) splits continuous-time systems by the real part of the poles, so a lightly damped high-frequency mode is assigned to the slow part. For discrete-time systems, the disc centred at the origin separates the poles by decay rate rather than by frequency. slow_fast assigns such modes by natural frequency in both cases.

modal_reduction(sys, ωc; residual = true, coordinates = :modal, states = nothing)

Removes the modes with natural frequency greater than or equal to ωc by residualization (default, preserves the static gain) or truncation. The poles of the reduced model are the slow poles of sys. The returned matrix T maps the original state to the reduced state.

  • coordinates = :modal: the reduced state is in the slow modal coordinates. With residual = true, the reduced model equals slow + ss(dcgain(fast)).
  • coordinates = :original: the reduced state is a subset x[states] of the original state variables. The slow invariant subspace is represented as x2 = X*x1, where X solves the nonsymmetric algebraic Riccati equation A22*X - X*A11 - X*A12*X + A21 = 0, and the deviation x2 - X*x1 is residualized or truncated. This is the singular perturbation approximation with the exact slow manifold. X is computed from the ordered Schur basis of A. This is the same computation MatrixEquations.nare performs for this equation (added in MatrixEquations.jl v2.7.0), so the MatrixEquations compat bound does not change. If states is not provided, the retained state variables are selected by a QR decomposition with column pivoting of the slow invariant subspace basis. A user-provided states for which the subspace cannot be represented throws an error.

The two choices of coordinates yield reduced models with the same poles and static gain but in general different transfer functions. The method does not account for controllability and observability. The docstring therefore refers to baltrunc for reduction based on input-output importance.

Tests

The tests in test_matrix_comps.jl cover continuous-time, discrete-time, complex-coefficient and BigFloat systems, both coordinate choices, the cases where all poles are slow or all are fast, and invalid arguments. BigFloat systems require GenericSchur for the Schur decomposition, as elsewhere in ControlSystemsBase. The conditioning check for coordinates = :original uses the 1-norm condition number, since the 2-norm condition number requires an SVD that is unavailable for BigFloat without GenericLinearAlgebra. The full ControlSystemsBase test suite passes locally (including Aqua). ExplicitImports.check_no_stale_explicit_imports reports no new stale imports, and ordschur! was added to the explicit LinearAlgebra imports.


Host: demeter2, session ID: 527acd1d-a7ce-41f9-9f9e-d6aff56e7521, working directory: ~/.julia/dev/ControlSystems/.claude/worktrees/expressive-yawning-squid

🤖 Generated with Claude Code

`slow_fast(sys, ωc)` decomposes a system additively into a part with the
poles of natural frequency less than ωc and a part with the remaining poles
and the direct feedthrough term. The natural frequency is |p| in continuous
time and |log(p)|/Ts in discrete time, consistent with `damp`.

`modal_reduction(sys, ωc)` removes the fast modes by residualization
(default) or truncation. The reduced state is either the slow modal
coordinates or a subset of the original state variables, in which case the
slow invariant subspace is represented as the solution of a nonsymmetric
algebraic Riccati equation computed from the ordered Schur basis.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@JuliaControlBot

Copy link
Copy Markdown

This is an automated message.
Plots were compared to references. 5/11 images have changed, see differences below.
After pulling this PR, please update the reference images by creating a PR to ControlExamplePlots.jl here.

Difference Reference Image New Image
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.013 Reference New

@codecov

codecov Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 92.17%. Comparing base (f86f5f9) to head (b82bd4b).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1087      +/-   ##
==========================================
+ Coverage   92.09%   92.17%   +0.08%     
==========================================
  Files          42       42              
  Lines        6056     6119      +63     
==========================================
+ Hits         5577     5640      +63     
  Misses        479      479              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

The conditioning check used the 2-norm condition number, which requires an
SVD that LinearAlgebra does not provide for BigFloat. The 1-norm condition
number is computed from an LU factorization and is available for all number
types. Add tests for BigFloat systems, which require GenericSchur for the
Schur decomposition.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@JuliaControlBot

Copy link
Copy Markdown

This is an automated message.
Plots were compared to references. 5/11 images have changed, see differences below.
After pulling this PR, please update the reference images by creating a PR to ControlExamplePlots.jl here.

Difference Reference Image New Image
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.0 Reference New
✔️ 0.013 Reference New

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@baggepinnen
baggepinnen merged commit ef5f104 into master Oct 12, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants