fix(linalg,mps): scale-safe truncation-error accumulation - #488
Merged
Conversation
Truncation-error paths accumulated sums of squares naively, so the reported error saturated to inf once a singular value neared sqrt(real::MAX) and underflowed to zero for tiny tails. Route the sites through the shared scale_safe_norm / combine_norms kernel: - trunc_svd (dense) and cross_sector_truncate (block-sparse): the final Frobenius-norm error via scale_safe_norm, and the target_trunc_err rank-selection scan via a combine_norms fold that widens each value to f64 before squaring (an f32 singular value near sqrt(f32::MAX) previously overflowed the scan's si*si and wrongly kept the value). - truncate sweep drivers: fold per-step errors with combine_norms; the four step functions now return the unsquared error. Public error semantics are unchanged (Frobenius norm of the discarded singular values), now finite and nonzero across the real type's range. Closes #480
There was a problem hiding this comment.
Pull request overview
Routes truncation-error accumulation in both the linalg SVD truncation paths and the MPS sweep truncation paths through the shared scale-safe norm kernel (scale_safe_norm / combine_norms) to prevent overflow-to-inf and underflow-to-0 in extreme singular-value regimes, while preserving the public meaning of truncation error as the Frobenius norm of discarded singular values.
Changes:
- Replace naive sum-of-squares truncation-error accumulation with
scale_safe_norm(tail norms) andcombine_norms(incremental fold) in dense + block-sparse truncation code paths. - Change MPS private per-step truncation helpers to return unsquared per-step error and fold sweep totals via
combine_norms. - Add regression tests covering f64 overflow scale, f32 underflow scale, and the f32 target-error scan overflow case; adjust existing test documentation accordingly.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| crates/ariadnetor-linalg/src/decomposition/mod.rs | Uses combine_norms for target-error scan and scale_safe_norm for final truncation error in dense truncated SVD. |
| crates/ariadnetor-linalg/src/block_sparse_decomp.rs | Mirrors the dense fix for cross-sector block-sparse truncation (scan + final error). |
| crates/ariadnetor-mps/src/truncate.rs | Switches sweep error aggregation to combine_norms and returns unsquared per-step errors from private truncation steps. |
| crates/ariadnetor-linalg/tests/decomposition.rs | Adds dense trunc-SVD regression tests for overflow/underflow and scan correctness. |
| crates/ariadnetor-linalg/src/block_sparse_decomp/tests/mutant.rs | Adds block-sparse trunc-SVD overflow-scale regression test. |
| crates/ariadnetor-mps/tests/mps/truncate.rs | Updates test commentary to match scale-safe sweep aggregation behavior. |
| crates/ariadnetor-mps/tests/mps/truncate_mutant.rs | Adds dense sweep overflow-scale regression test. |
| crates/ariadnetor-mps/tests/mps/truncate_block_sparse.rs | Updates documentation/comments to reflect combine_norms folding and adds note about infeasible bsp sweep overflow fixture. |
| .cargo/mutants.toml | Removes fragile line-number-based mutant excludes now obsoleted by the accumulation changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The truncation-error paths accumulated sums of squares naively, so the reported error saturated to
infonce a singular value's square exceeded the real type's range and underflowed to zero for the smallest discarded tails — the failure the sharedscale_safe_norm/combine_normskernel exists to avoid. This routes the truncation-error sites through that kernel.Closes #480
Changes
crates/ariadnetor-linalg/src/decomposition/mod.rs—trunc_svd_with_policy_dense: the final Frobenius-norm error viascale_safe_norm, and thetarget_trunc_errrank-selection scan folds the discarded norm withcombine_norms, widening each value to f64 before it is squared.crates/ariadnetor-linalg/src/block_sparse_decomp.rs—cross_sector_truncate: the same two changes for the block-sparse twin.crates/ariadnetor-mps/src/truncate.rs— the four private step functions return the unsquared per-step error; thetruncate_dense/truncate_bspsweep drivers fold those errors withcombine_norms.Impact
The step functions are private and every call site is updated in the same diff. Public error semantics are unchanged —
TruncResult.errorandtrunc_errremain the Frobenius norm of the discarded singular values — now finite and nonzero across the real type's range. Thecombine_norms-foldedsqrt(sum of err^2)equals the previous accumulation mathematically.Test plan
New tests pin the two failure modes and the scan fix:
trunc_svd: f64 overflow stays finite (1e200), f32 underflow stays nonzero (1e-23), and the f32 rank-selection scan discards a value whosesi * sioverflows in the real type.cross_sector_truncate: f64 overflow stays finite.Existing boundary tests (
..._exact_boundary,..._strict_boundary) and the reconstruction-identity pins are unchanged and still pass.cargo test(linalg + mps + core): 934 passed, 0 failed; clippy and fmt clean.Notes
A direct block-sparse sweep overflow test is not included: the faer SVD backend fails to converge on the general block matrices a real chain presents at these magnitudes, so the per-step error the sweep would accumulate is never produced. That path's overflow safety is covered by composition — the block-sparse SVD-site overflow test supplies a scale-safe per-step error, and the dense sweep test exercises the identical
combine_normsfold.