lobpcg::TruncatedSvd maps non-converged LOBPCG results to Ok, hiding convergence failure
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 48/100
Rechercherichtung
Start in src/lobpcg/svd.rs at TruncatedSvd::decompose and inspect LobpcgResult handling around lines 162-172, then review the result type and the maxiter setup around line 105. Check src/generate.rs and issue #336 for the separate RNG concern. Done means non-convergence is no longer silently reported as success, with the chosen result API covered by appropriate tests.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Summary
lobpcg::TruncatedSvd::decompose maps a non-converged LOBPCG result onto Ok(..), so callers cannot distinguish a converged decomposition from a failed one and may silently receive wrong singular values.
Version inspected: ndarray-linalg 0.17.0 (source from crates.io).
Where
match res {
LobpcgResult::Ok(vals, vecs, _) | LobpcgResult::Err(vals, vecs, _, _) => {
Ok(TruncatedSvdResult {
problem: self.problem,
eigvals: vals,
eigvecs: vecs,
ngm: n > m,
})
}
LobpcgResult::NoResult(err) => Err(err),
}
LobpcgResult::Err(..) carries the error as its fourth field and it is dropped on the floor. Returning the partial result is reasonable; giving the caller no way to learn that it is partial is the problem.
Contributing factor: maxiter defaults to problem.len_of(Axis(0)) * 2 (svd.rs:105), which is a small iteration budget for small inputs — exactly where I measured the worst errors in the sibling implementation.
Evidence
I want to be straightforward about what I did and did not run.
Verified by source inspection in ndarray-linalg 0.17.0: the LobpcgResult::Err(..) => Ok(..) mapping above, and the maxiter default.
Not executed against ndarray-linalg: I do not have a LAPACK backend configured locally, so I could not run a reproduction through this crate.
Measured in linfa-linalg 0.2.1, whose lobpcg::TruncatedSvd is the same algorithm with the same structure (Err((_, Some(Lobpcg{..}))) => Ok(..), same 2 * n_samples default, same epsilon * correction * λ_max cutoff). Taking X = U diag(4, 3, 2, 1) Vᵀ with U (8×4) and V (4×4) built from Sylvester–Hadamard columns, so the singular values are exactly [4, 3, 2, 1] by construction:
k=1 returned Ok sigma=[4.0] max rel err=2.220e-16
k=2 returned Ok sigma=[4.0, 3.0] max rel err=4.441e-16
k=3 returned Ok sigma=[3.94368, 2.67522, 1.477172] max rel err=2.614e-1 <== WRONG
k=4 returned Ok sigma=[4.0, 3.0, 2.0, 1.0] max rel err=2.517e-15
A 26% error reported as success, with neighbouring block sizes exact to machine precision. I would expect the same behaviour here given the shared structure, but I have not confirmed the exact numbers under LAPACK — if that distinction matters to you, treat the code-level finding as the claim and the numbers as indicative.
Filed in parallel as rust-ml/linfa-linalg#20.
Secondary: the result is not reproducible
decompose initialises its starting block with generate::random(..) (svd.rs:132), and generate::random uses thread_rng() (src/generate.rs:36). There is no new_with_rng equivalent, so successive runs of the same program on the same input can land on different subspaces and different component signs. Combined with the silent non-convergence above, that makes the failure intermittent as well as quiet. This overlaps with #336.
Suggested remedies
- Surface convergence to the caller — e.g. a
converged: boolor the residual norms onTruncatedSvdResult— rather than discarding the error. - Or return
Errby default, with the partial result available through an explicit opt-in. - Consider a seedable entry point (
new_with_rng) so results are reproducible;linfa-linalghas one.
Glad to put up a PR if you have a preference on direction.
- Vorherrschende Sprache
- Rust
- Sterne
- 452
- Forks
- 95
- PR-Merge-Kennzahlen
- Keine gemergten PRs in 30 T.
Entwicklungsumgebung
Dieses Projekt bietet weder Dev-Container noch Dockerfile noch Beitragsleitfaden – die Einrichtung liegt bei Ihnen. Beginnen Sie mit der README; die allgemeinen Schritte stehen in unserem Leitfaden für den ersten Beitrag.
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus rust-ndarray/ndarray-linalg
-
Thin SVDOffen
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 38/100
rust-ndarray/ndarray-linalg#414 ·
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 30/100
rust-ndarray/ndarray-linalg#413 · 1 Kommentar ·
-
Schwierigkeit 5/5 Über eine Woche Anfängerfreundlichkeit 25/100
rust-ndarray/ndarray-linalg#404 ·
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 35/100
rust-ndarray/ndarray-linalg#402 · 1 Kommentar ·
-
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 35/100
rust-ndarray/ndarray-linalg#401 · 2 Reaktionen ·
Alle Issues in rust-ndarray/ndarray-linalg
Ähnliche Issues
-
bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
wardian-app/Wardian#1603 ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 70/100
Maintainer antworten meist innerhalb von 2 Tagen
-
bug
Schwierigkeit 1/5 1-3 Stunden Anfängerfreundlichkeit 72/100
peteonrails/voxtype#844 ·
Maintainer antworten meist innerhalb von 1 Tag
-
feature
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 85/100
uwuclxdy/clauth#107 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 4 Tagen