Skip to content

Define lq for sparse matrices as the adjoint of the sparse QR of the adjoint - #833

Merged
ViralBShah merged 1 commit into
mainfrom
vs/sparse-lq
Sep 17, 2026
Merged

ViralBShah merged 1 commit into
mainfrom
vs/sparse-lq

Conversation

@ViralBShah

Copy link
Copy Markdown
Member

Fixes #114.

julia> A = sparse([1.0 0 1 0; 0 1 0 1]);

julia> lq(A)
ERROR: MethodError: no method matching lq!(::SparseMatrixCSC{Float64, Int64})

lq(A) now returns qr(A')', the AdjointQRSparse from #804, computed by SPQR on the sparse transpose, with tol and ordering passed through. It gets L, Q, prow and pcol properties so that A[F.prow, F.pcol] == F.L * F.Q, with F.L a lower triangular SparseMatrixCSC and F.Q the adjoint of the sparse Q; F \ b is the minimum-norm solution for a wide A and throws DimensionMismatch for a tall one, as dense lq does; F' is qr(A'), lq(A') reuses qr(A) without a copy, and rank and show work. qr also accepts adjoint and transpose wrappers of a SparseMatrixCSC directly, and the \ for a tall A' uses that instead of spelling the copy. The identity F.L * F.Q exposed a bug in the padded products with a sparse Q: they took the thin width from the number of stored Householder vectors, which SPQR keeps below the column count when the matrix is sparse enough, so qr(A).Q * qr(A).R threw DimensionMismatch for such an A. The width now comes from the column count that QRSparseQ records.

Tests in test/spqr.jl cover the factorization identity, properties, solves, tolerance, the tall error, the adjoint and transpose inputs and the thin product regression, for real and complex eltypes and both index types; spqr, linalg, Aqua, whitespace and the doctests pass on 1.14-DEV. docs/src/solvers.md lists lq. #831 will use lq for the wide \.

🤖 Generated with Claude Code

…adjoint

`lq(A)` returns `qr(A')'`, an `AdjointQRSparse`, with `L`, `Q`, `prow` and
`pcol` properties so that `A[F.prow, F.pcol] == F.L * F.Q`, and `F \ b` gives
the minimum-norm solution through the adjoint solve. `qr` also accepts
adjoint and transpose wrappers directly. The padded Q products keyed the thin
width on the number of stored reflectors, which SPQR keeps below the column
count for sparse enough matrices; it now comes from the column count.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@codecov

codecov Bot commented Sep 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.77419% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 92.60%. Comparing base (88c6404) to head (8fb5d41).

Files with missing lines Patch % Lines
src/solvers/spqr.jl 96.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #833      +/-   ##
==========================================
+ Coverage   92.56%   92.60%   +0.04%     
==========================================
  Files          12       12              
  Lines        8699     8723      +24     
==========================================
+ Hits         8052     8078      +26     
+ Misses        647      645       -2     

☔ 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.

@ViralBShah
ViralBShah added this pull request to stack #834 September 17, 2026 21:37
@ViralBShah
ViralBShah merged commit e7949fd into main Sep 17, 2026
10 checks passed
@ViralBShah
ViralBShah deleted the vs/sparse-lq branch September 17, 2026 21:42
ViralBShah added a commit that referenced this pull request Sep 17, 2026
#831)

Fixes #301. Stacked on #833, which adds the sparse `lq`.

`A \ b` for a wide sparse `A` returned a basic solution, while dense `\`
returns the minimum-norm one:

```julia
julia> A = sparse([1.0 0 1 0; 0 1 0 1]); b = [1.0, 2.0];

julia> A \ b
4-element Vector{Float64}:
 1.0
 2.0
 0.0
 0.0

julia> Matrix(A) \ b
4-element Vector{Float64}:
 0.5
 1.0
 0.5
 1.0
```

The wide branch of `\` called `qr(A) \ B`, which SPQR solves by
back-substituting with the leading `rank` columns of `R`. It now calls
`lq(A) \ B`, the minimum-norm solve through the adjoint of the QR
factorization of `A'` from #804, as the `Adjoint`/`Transpose` methods
already do for a tall parent. `factorize(A)` returns `lq(A)` for a wide
`A`, so `factorize(A) \ b == A \ b`. `qr(A) \ b` still returns the basic
solution and its docstring says so.

Tests added next to the existing underdetermined check in the SPQR `\`
testset; `spqr`, `linalg`, `linalg_solvers`, whitespace and the doctests
pass on 1.14-DEV.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
ViralBShah added a commit that referenced this pull request Sep 19, 2026
…841)

The solvers page listed the factorizations and their docstrings but had
no narrative. This adds a section covering what `A \ b` and `factorize`
dispatch to (substitution, `cholesky` with its fallback, `lu`, `qr`, and
`lq` for the minimum-norm solution of wide systems), reusing and
refactorizing a factorization (`F \ B`, `ldiv!`,
`lu!`/`cholesky!`/`ldlt!`), extracting factors with the permuted
identities they satisfy, and the symmetric-input and `check =
false`/`issuccess` rules.

The material comes from reading `\` and `factorize` in `src/linalg.jl`,
the Hermitian `\` in `src/solvers/cholmod.jl`, the
`lu`/`cholesky`/`qr`/`lq` docstrings, and #830, #831, #833, #835. One
thing the text makes explicit: for a Hermitian matrix that is not
positive definite, `\` falls back to `lu` while `factorize` falls back
to `ldlt`. Every example is a doctest printing only `true`/`false`; the
Documenter build and doctests pass on nightly with no new warnings. Docs
only; tuning keywords and threading are left to a separate PR.

The insertion sits directly above the `@docs` line that #838 edits, so
whichever merges second will need a trivial rebase.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Viral B. Shah <ViralBShah@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
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.

LQ factorization is not implemented for sparse matrices

1 participant