Fix elementwise variance accumulation in lsqr (closes #639)#751
Fix elementwise variance accumulation in lsqr (closes #639)#751gaoflow wants to merge 3 commits into
Conversation
lsqr's optional `var` output estimates the diagonal of (A^H A)^-1, one variance per unknown. The update accumulated `ncp.dot(self.dk, self.dk)`, which is the scalar sum of squares of `dk`, so the same value was added to every entry of `var` and all returned variances came out identical (and wrong). Accumulate the elementwise square `ncp.square(self.dk)` instead, matching Paige & Saunders (1982, p.53) and scipy's lsqr. The solution path is unchanged; only the `var` output is corrected. Add test_lsqr_calc_var asserting the variances are not all identical and match both scipy's lsqr and the dense diag(inv(A^H A)) on full-column-rank systems.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 0 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
|
@IruNikZe I was wondering if you got anywhere with this in scipy (and pylops), and if you will be willing to review this PR? |
|
@MothNik thanks a lot for the feedback and willingness to review this PR. What you say makes somehow sense to me, as usually also in VI type of solvers the mean always converges faster than the std, so not surprised this may be also the case for a solver like LSQR |
- Condense the inline comment in LSQR to a single line. - Drop the dense diag(inv(A^H A)) comparison from the test and keep scipy's LSQR as the reference; LSQR is not guaranteed to match the dense variance. - Use niter = 2 * nx (scipy's default) and note that niter is only a ceiling. - Move the issue reference out of the docstring into the targeted comment. - Add a CHANGELOG.md entry.
|
Thanks @MothNik, all addressed in the latest commit:
|
The test compares against scipy's lsqr as reference; scipy cannot implicitly convert cupy arrays (TypeError), so guard it with the same TEST_CUPY_PYLOPS skipif used by test_lsqr_pylops_scipy.
|
Pushed 515e51a to fix the CuPy CI failure: |
Problem
lsqr's optionalvaroutput estimates the diagonal ofbut
ncp.dot(self.dk, self.dk)is the scalar sum of squares ofdk, so the same number is added to every entry ofvar. The result is that all returned variances are identical (and wrong), as reported in #639.Reproduction (from the issue)
Before this PR
var_pylopsis a constant vector ([1.0598, 1.0598, ...]), whilevar_scipyandvar_denseagree.Fix
Accumulate the elementwise square instead:
This matches the recurrence in Paige & Saunders (1982, p.53) and scipy's
lsqr.ncp.squareis available across the NumPy / CuPy backends used elsewhere in this method, and the solution path is untouched — only thevaroutput changes. Credit to @IruNikZe for the diagnosis in #639.Tests
Added
test_lsqr_calc_var(overdetermined real, full column rank) asserting the variances are not all identical (guards against the regression) and match both scipy'slsqrand the densediag(inv(A^H A)).Verified locally:
pytest pytests/test_solver.py(98 passed),ruff checkclean, flake8 (max-line 88) clean on changed files.