Skip to content

Add scipy fallbacks for generalized Lyapunov and discrete Sylvester equations#1234

Open
kwlee2025cpp wants to merge 1 commit into
python-control:mainfrom
kangwonlee:scipy-matrix-equation-fallbacks
Open

Add scipy fallbacks for generalized Lyapunov and discrete Sylvester equations#1234
kwlee2025cpp wants to merge 1 commit into
python-control:mainfrom
kangwonlee:scipy-matrix-equation-fallbacks

Conversation

@kwlee2025cpp

Copy link
Copy Markdown

Summary

lyap and dlyap currently raise ControlArgument("method='scipy' not valid for ...") for three problems, so solving them requires the optional
slycot package:

  • the generalized continuous Lyapunov equation A X Eᵀ + E X Aᵀ + Q = 0,
  • the generalized discrete Lyapunov equation A X Aᵀ − E X Eᵀ + Q = 0,
  • the discrete-time Sylvester equation A X Qᵀ − X + C = 0.

This PR implements pure scipy/numpy fallbacks for all three, so they work
when slycot is not installed. When slycot is installed it remains the
default (method='slycot'), and the new tests cross-validate the scipy
results against it.

What it adds

Generalized Lyapunov (continuous and discrete). The equation is reduced
to a standard Lyapunov equation by a congruence transform with inv(E)
(Ã = E⁻¹A, Q̃ = E⁻¹Q E⁻ᵀ, leaving X unchanged), then solved with
scipy.linalg.solve_{continuous,discrete}_lyapunov. This requires E to be
nonsingular. SLICOT SG03AD (method='slycot'), based on the generalized
Schur method of Penzl, additionally handles singular E; that case raises a
clear ControlArgument pointing the user to method='slycot'.

Discrete-time Sylvester. Solved by the Bartels–Stewart method: complex
Schur factorizations of A and Qᵀ, then a column-by-column back
substitution. This matches the O(n³ + m³) complexity of the
Hessenberg–Schur algorithm in SLICOT SB04QD (method='slycot').
Singularity (a pair of eigenvalues with λᵢ·λⱼ ≈ 1) is detected and raises
a clear ControlArgument.

Ill-conditioned E. Because the generalized-Lyapunov fallback inverts
E, accuracy degrades when E is poorly conditioned. A UserWarning is
now issued when cond(E) > 1/√eps, recommending method='slycot'. (This
also surfaces a case in the continuous path that was previously silently
inaccurate.)

Fixes included

  • Import-alias bug in the optional-slycot guards: the except ImportError branches bound the sentinels under the wrong names
    (sb0qmd for sb04qd, sb04ad for sg03ad), so on a slycot-less
    install the intended None fallbacks were never set under the names the
    code references.
  • Stale dlyap docstring note (implied a Kronecker-product method);
    corrected to Bartels–Stewart, with Golub–Nash–Van Loan and
    Bartels–Stewart references added to the Notes/References.

Testing

  • scipy↔slycot cross-validation added to test_lyap_g, test_dlyap_g, and
    test_dlyap_sylvester (scipy result matches slycot to ~1e-9…1e-16).
  • New test_lyap_g_ill_conditioned_E_scipy (parametrized over lyap/dlyap)
    asserts the ill-conditioned-E UserWarning.
  • New test_dlyap_sylvester_singular_scipy asserts the singular-pencil
    ControlArgument.
  • Green both with and without slycot: 19 passed, 12 skipped (no slycot) /
    31 passed (slycot).

Only control/mateqn.py and control/tests/mateqn_test.py are touched.

Scope

This is an intentionally small, self-contained slice. It is the first of a
short series factoring a larger "scipy fallbacks for slycot-gated routines"
effort into independently reviewable PRs; the matrix-equation solvers go
first because they are foundational and easy to validate against SLICOT.
gram is the planned next slice.

Alternatives considered

The same inv(E) reduction is used by pyMOR's dense scipy Lyapunov
backend. Factoring the pencil (A, E) with scipy.linalg.qz instead of
inverting E (as harold does) is more robust for ill-conditioned E
and is a natural follow-up. Truly singular E (descriptor systems with
infinite generalized eigenvalues) is out of scope here and continues to
require method='slycot'; a pure-scipy path for that case would need
spectral-projector deflation of the infinite spectrum.

…quations

lyap and dlyap previously raised ControlArgument for method='scipy'
(explicit, or auto-selected when slycot is absent) on three cases that
SLICOT handles. Add pure scipy/numpy fallbacks for all three:

- Generalized Lyapunov, continuous and discrete (E != I): congruence
  transform by inv(E) to a standard Lyapunov equation, then scipy's
  solve_continuous/discrete_lyapunov. Requires E nonsingular; SLICOT
  sg03ad (Penzl's generalized Schur method) also handles singular E and
  remains the method='slycot' path. A nonsingular-E failure raises a
  clear ControlArgument. Because the transform inverts E, an
  ill-conditioned E yields reduced accuracy; this now emits a UserWarning
  recommending method='slycot' (the continuous path was previously
  silent, while the discrete path warned only incidentally).

- Discrete Sylvester (A X Q^T - X + C = 0): Bartels-Stewart method via
  complex Schur factors of A and Q^T with column-by-column triangular
  solves, O(n^3 + m^3), matching the Hessenberg-Schur cost of SLICOT
  sb04qd. Includes the solvability check that no eigenvalue pair of
  A and Q^T has product (almost) equal to 1.

Also fix two incorrect slycot import-fallback aliases (sb0qmd -> sb04qd,
sb04ad -> sg03ad) so the names resolve to None, not NameError, when
slycot is absent; and correct the dlyap Notes, which described the
scipy Sylvester path as a Kronecker O((nm)^3) solve (it is now
Bartels-Stewart, O(n^3 + m^3)).

Tests parametrize method=[None, 'scipy', 'slycot'] and cross-check that
the scipy and slycot solutions agree, and cover the nonsingular-E
requirement, the ill-conditioned-E warning, and the singular
discrete-Sylvester case.

Co-Authored-By: Claude Fable 5 <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
@kwlee2025cpp kwlee2025cpp force-pushed the scipy-matrix-equation-fallbacks branch from 2c68e3e to c1ab92c Compare June 15, 2026 15:38
@slivingston

Copy link
Copy Markdown
Member

@kwlee2025cpp Thanks for proposing these changes. Can you answer a few questions to help with review and consideration for merging?

  1. Is there a prior issue or discussion that provides context and motivation for this addition?
  2. If the feature is already provided when slycot is installed, is it worth the mantenance effort of an alternative implementation that does not require it? For example, how does the performance compare between this PR (the "fallbacks") and those requiring slycot?
  3. Are you using this in your own work and now want to share the result as open source?

I am a little concerned that this PR is just a Claude-generated thing that no one is asking for. I am also concerned we are about to get many more Claude-generated PRs as your description suggestions ("...It is the first of a
short series factoring a larger 'scipy fallbacks for slycot-gated routines' effort into independently reviewable PRs..."). I am happy to be mistaken about that, so please let me know.

@bnavigator

Copy link
Copy Markdown
Contributor

Approving the workflows because the code on first glance looks good and obviously not malicious. No assessment about scientific merit, I fully support @slivingston's inquiry.

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 94.744% (+0.01%) from 94.73% — kangwonlee:scipy-matrix-equation-fallbacks into python-control:main

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.

4 participants