Skip to content

Fall back to ExpMethodGeneric for immutable matrices in exponential!#237

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:staticarray-exponential-fallback
Jul 3, 2026
Merged

Fall back to ExpMethodGeneric for immutable matrices in exponential!#237
ChrisRackauckas merged 1 commit into
SciML:masterfrom
ChrisRackauckas-Claude:staticarray-exponential-fallback

Conversation

@ChrisRackauckas-Claude

Copy link
Copy Markdown
Contributor

Note

This PR should be ignored until reviewed by @ChrisRackauckas.

What

exponential!(A) with no method argument now falls back to ExpMethodGeneric() when A is immutable (per ArrayInterface.ismutable, the same predicate ExpMethodGeneric uses internally), instead of always selecting the in-place ExpMethodHigham2005.

Why

exponential!(A) on a StaticArrays SMatrix currently throws:

setindex!(::SMatrix{4, 4, Float64, 16}, value, ::Int) is not defined.

because the default ExpMethodHigham2005 path mutates. Meanwhile ExpMethodGeneric already contains a fully non-mutating code path for immutable inputs (the ismutable(x) guard in src/exp_generic.jl), so the capability exists — it just isn't reachable from the no-method entry point.

This came out of https://discourse.julialang.org/t/how-to-use-forwarddiff-jl-with-inv-and-matrix-exponential/137880, where the recommendation to use the (now-removed name) exp_generic on static arrays failed with the error above.

Two additional data points verified locally while preparing this:

  • ForwardDiff.jacobian(exponential!, ::SMatrix) works through the new fallback and matches finite differences (~1e-7) for 3×3/4×4, Float64 and Float32.
  • For SMatrix{3,3,Float32}, StaticArrays' own exp gives NaN Jacobians in ~1/3 of random samples (its Base-style Padé coefficients, up to ~3.2e16, overflow Float32 through the 3×3 cofactor solve — as diagnosed by mikmoore on Discourse). ExpMethodGeneric gave 0 NaNs in 1000 samples because its Padé coefficients are normalized (≤ 1). So the fallback is also the numerically safer path for Float32 static arrays, not just the one that doesn't error.

Changes

  • src/exp.jl: default method selection routes immutable matrices to ExpMethodGeneric(); docstring note added.
  • test/basictests.jl: new testset covering exponential!(::SMatrix) values vs exp(Matrix(A)) and ForwardDiff.jacobian vs the Matrix reference, for 3×3/4×4 × Float64/Float32, including NaN checks.

Behavior note: scalars also now work through the no-method entry point (ismutable(::Number) == falseExpMethodGeneric), where previously exponential!(1.0) threw a MethodError. Purely additive.

Testing

GROUP=Core Pkg.test() run locally on Julia 1.11.9: Core/basictests.jl | 349 passed, 1 broken (pre-existing kiops @test_broken), 0 failures. Runic applied to the changed files.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WQWjLDJ228E8w9631ZeTdM

exponential!(A) with no method previously always selected the in-place
ExpMethodHigham2005, which throws a setindex! error on immutable inputs
such as StaticArrays' SMatrix. ExpMethodGeneric already has a fully
non-mutating code path for immutable matrices, so route them there.

This also makes ForwardDiff.jacobian(exponential!, ::SMatrix) work,
including for Float32 where StaticArrays' own exp overflows its Pade
coefficients through the 3x3 cofactor solve and returns NaN; the
normalized Pade coefficients of ExpMethodGeneric avoid that overflow.

See https://discourse.julialang.org/t/how-to-use-forwarddiff-jl-with-inv-and-matrix-exponential/137880

Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
@ChrisRackauckas ChrisRackauckas marked this pull request as ready for review July 3, 2026 13:05
@ChrisRackauckas ChrisRackauckas merged commit 09fdc25 into SciML:master Jul 3, 2026
34 of 35 checks passed
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