You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fix singular handling in the StaticArrays square fast path with SVD rescue for the default (#1085)
* Fix silent Success with non-finite u for singular static square matrices
The StaticArrays fast path in `solve` returned `retcode = Success` with
`u = [Inf NaN; ...]` (N <= 3, direct inverse formulas) or threw a
`SingularException` (N >= 4, checked `lu`) when the square matrix was
singular (#1084).
Now the static square default detects LU failure via
`lu(A, check = false)` + `issuccess` and rescues with an SVD
least-squares solve, returning the finite min-norm pseudo-solution with
`Success` — mirroring the dense default's LU -> pivoted-QR
`safetyfallback`. SVD is used instead of QR because
`qr(::SMatrix) \ b` least-squares is not defined in StaticArrays (the
same reason `defaultalg(::SMatrix)` uses `SVDFactorization()` for
non-square).
Explicit `LUFactorization()` on singular static input now returns
`retcode = Failure` with the zero-initialized `u` instead of throwing,
matching the dense `LUFactorization` behavior.
Nonsingular solves stay bit-identical to `\` and the fast path remains
allocation-free (AllocCheck passes); non-square static problems are
unchanged.
Fixes#1084
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
* Let the small-N static solve inline (review)
Drops the @noinline bit-identity barrier: StaticArrays small-size
formulas have inlining-context-dependent FMA contraction, so
nonsingular N <= 3 results may differ from a bare A \ b in the last
bit; the test asserts a 4-ulp match instead. Measured 2x2 default
solve: 13.2 ns inlined vs 11.8 ns with the barrier — the singularity
check dominates, not the call, so the barrier bought nothing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
* Static GESVFactorization dispatch: one-shot solve, Failure on singular (review)
GESVFactorization previously fell through to the generic init path,
whose dense-only strided requirement rejects SMatrix. The static
dispatch mirrors the dense gesv driver semantics: a one-shot
factorize-and-solve whose singular failures are reported through the
return code — no SVD rescue (that is the default's behavior, which
gesv semantics do not include). N <= 3 uses the direct small-size
formulas with the (measured-free) finiteness/issuccess check; larger N
uses lu(check = false), never touching the throwing triangular solve
on failure. Non-square input gets an informative ArgumentError.
Measured 2x2 API: gesv 16.4 ns, default 12.8 ns, LUFactorization
21.7 ns.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
* docs: StaticArrays tutorial + fix pre-existing docs build failure
Add a StaticArrays tutorial page documenting the non-caching static
fast path (0-allocation direct solve, immutable so no init!/solve!),
the algorithm tier (DirectLdiv! / GESVFactorization / LUFactorization /
default) with their singular-input behavior and measured overhead, and
non-square SVD routing. Note the static GESV dispatch in the
GESVFactorization docstring.
Also raise the HTML size_threshold to 500 KiB: solvers.md (a long
auto-listed solver catalog) exceeds the default 200 KiB limit, which
has been failing the Documentation CI on main independently of this PR.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
---------
Co-authored-by: ChrisRackauckas-Claude <accounts@chrisrackauckas.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
`SVector`) take a dedicated non-caching fast path: the solve happens directly
6
+
on the immutable arrays with **zero heap allocations**, and the returned
7
+
solution's `u` is a static array of the matching shape.
8
+
9
+
```@example static
10
+
using LinearSolve, StaticArrays
11
+
12
+
A = SA[2.0 1.0; 1.0 3.0]
13
+
b = SA[1.0, 2.0]
14
+
prob = LinearProblem(A, b)
15
+
16
+
sol = solve(prob)
17
+
sol.u
18
+
```
19
+
20
+
Because static problems are immutable, there is no `init!`/`solve!` caching
21
+
interface for them — each `solve` is a self-contained direct solve. (There is
22
+
also nothing to cache: no factorization object is retained.)
23
+
24
+
## Algorithm choices and singular-input behavior
25
+
26
+
Static square problems support a tier of algorithms trading safety against
27
+
overhead. All of them are allocation-free; timings below are for a 2×2
28
+
`Float64` system on a prebuilt `LinearProblem` (bare `A \ b` is 4.4 ns on the
29
+
same machine) and scale with the usual method costs at larger sizes.
30
+
31
+
| algorithm |~2×2 cost | on singular `A`|
32
+
|---|---|---|
33
+
|`DirectLdiv!()`| 5.6 ns | unchecked: whatever `\` gives — non-finite values for `N ≤ 3`, a thrown `SingularException` for larger `N`|
34
+
|`GESVFactorization()`| 8–9 ns |`ReturnCode.Failure` with zeroed `u`; no rescue (one-shot factorize-and-solve, the static analog of LAPACK's `gesv` driver) |
35
+
|`LUFactorization()`| 22 ns |`ReturnCode.Failure` with zeroed `u`, matching the dense `LUFactorization` behavior |
36
+
| default (`solve(prob)`) | 9.2 ns | rescued: an SVD least-squares min-norm pseudo-solution with `ReturnCode.Success`, mirroring the dense default's singular-LU → pivoted-QR safety fallback |
37
+
38
+
Guidance:
39
+
40
+
- Use the **default** unless you have a reason not to: it is nearly as fast
41
+
as the unchecked path (the finiteness check fuses with the solve) and a
42
+
singular matrix degrades gracefully instead of silently producing `Inf`/
43
+
`NaN`.
44
+
- Use **`DirectLdiv!`** when the matrix is known nonsingular and every
45
+
nanosecond counts: it is a bare `A \ b` behind one algorithm-type branch.
46
+
- Use **`GESVFactorization`** when you want failure *reported* (a checkable
47
+
return code) but not *repaired* — e.g. inside an iteration that has its own
48
+
recovery logic. It uses the direct small-size kernels, so it is faster than
49
+
`LUFactorization` at small sizes.
50
+
-`LUFactorization` on static arrays exists for algorithm-genericity (code
51
+
that passes an algorithm through to both dense and static problems); the
52
+
static `lu` costs about 3× the direct inverse formulas at `N ≤ 3`.
53
+
54
+
`QRFactorization`, `CholeskyFactorization`, `NormalCholeskyFactorization`, and
55
+
`SVDFactorization` also have direct static dispatches with their usual
56
+
applicability requirements.
57
+
58
+
Nonsingular results from the checked algorithms match `A \ b` up to the last
59
+
bit or ulp-level differences from inlining-dependent FMA contraction in
60
+
StaticArrays' small-size kernels.
61
+
62
+
## Non-square static problems
63
+
64
+
Non-square static problems route to an SVD least-squares solve by default
65
+
(static QR least-squares division is not available in StaticArrays), returning
66
+
the minimum-norm solution:
67
+
68
+
```@example static
69
+
Ans = SA[1.0 2.0; 3.0 4.0; 5.0 6.0]
70
+
bns = SA[1.0, 2.0, 3.0]
71
+
solve(LinearProblem(Ans, bns)).u
72
+
```
73
+
74
+
`GESVFactorization` is square-only and throws an `ArgumentError` for non-square
75
+
static input, matching the LAPACK driver it mirrors.
0 commit comments