Skip to content

Fix #1579: QR::solve handles overdetermined (least-squares) systems#1608

Closed
StefanoD wants to merge 1 commit into
dimforge:mainfrom
StefanoD:fix-1579-qr-least-squares
Closed

Fix #1579: QR::solve handles overdetermined (least-squares) systems#1608
StefanoD wants to merge 1 commit into
dimforge:mainfrom
StefanoD:fix-1579-qr-least-squares

Conversation

@StefanoD

Copy link
Copy Markdown

Summary

Closes #1579.

QR::solve / solve_mut previously asserted is_square() and panicked on any
non-square matrix. They now solve overdetermined (m ≥ n) systems in the
least-squares sense (minimizing ‖Ax − b‖₂), consistent with SVD::solve,
nalgebra-lapack's QR::solve, and Julia/Octave's A \ b.

The decomposition already supported non-square matrices; only the solve was
restricted. The algorithm is the standard thin-QR least squares: apply Qᵀ to b
in place (Householder), keep the first n rows, back-substitute R₁ x = (Qᵀb)[..n].

Changes

  • Moved solve / solve_mut / solve_upper_triangular_mut into the general
    impl<T, R: DimMin<C>, C: Dim> block; try_inverse / is_invertible stay square-only.
  • solve_mut: is_square() assertion → nrows ≥ ncols (underdetermined still panics).
    The solution lands in the first ncols rows of b; the trailing rows hold Qᵀb's
    tail, whose per-column norm is the least-squares residual.
  • solve: now returns an ncols-row matrix (OMatrix<T, C, C2>); source-compatible
    for square systems.
  • Documented the full-column-rank assumption (use ColPivQR / SVD for rank-deficient cases).

Tests

Added proptests (f64 + complex, dynamic + static 5×3) verifying the least-squares
normal equations Aᴴ A x = Aᴴ b:

cargo test --features "arbitrary,debug,compare,rand,macros,proptest-support" qr

Notes

  • ColPivQR::solve has the same square-only limitation; left for a focused follow-up
    (needs care with the column-pivot permutation).
  • Per discussion in the issue, the least-squares residual is recoverable from
    solve_mut's output, so no separate (solution, residual) method was added.

Note: I did this with the help of Claude. If this is against your rules or the PR is garbage, maybe it can still serve as template for the issue.

Previously `QR::solve`/`solve_mut` lived in a square-only impl block and
asserted `is_square()`, panicking with "QR solve: unable to solve a
non-square system." on any overdetermined system.

They now solve `m >= n` systems in the least-squares sense (minimizing
‖Ax - b‖₂), matching nalgebra-lapack's `QR::solve`, the crate's own SVD
solver, and Julia/Octave's `A \ b`. The math is the standard thin-QR
least squares: form Qᵀb, keep its first n rows, back-substitute R x = (Qᵀb)[..n].

- Move solve/solve_mut/solve_upper_triangular_mut into the general
  `impl<T, R: DimMin<C>, C: Dim>` block; keep try_inverse/is_invertible
  square-only.
- solve_mut: replace the is_square() assertion with `nrows >= ncols`
  (underdetermined systems still panic). The solution is written into the
  first `ncols` rows of `b` (LAPACK dgels in-place convention).
- solve_upper_triangular_mut: back-substitute over the first min(m, n)
  rows only.
- solve: return an `ncols`-row matrix (`OMatrix<T, C, C2>`).

Document that solve_mut leaves Qᵀb's tail in the trailing rows of `b`
(its per-column norm is the least-squares residual), and that both
methods assume full column rank — ColPivQR or SVD are needed for the
rank-deficient / ill-conditioned case.

Add proptests (f64 + complex) verifying the least-squares normal
equations Aᴴ A x = Aᴴ b for dynamic and static (5x3) overdetermined
systems.
@StefanoD StefanoD closed this by deleting the head repository Jun 27, 2026
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.

qr.solve() cannot handle overdetermined systems

1 participant