Skip to content

operator: final residual on NoConvergence, BiCGSTAB breakdown stops at the last finite iterate (#133, #134) - #137

Merged
shinaoka merged 1 commit into
mainfrom
fix/133-134-krylov-residual
Oct 29, 2026
Merged

shinaoka merged 1 commit into
mainfrom
fix/133-134-krylov-residual

Conversation

@shinaoka

Copy link
Copy Markdown
Member

Fixes two Krylov-solver defects that were reported upstream on Codeberg and that reproduce on this fork's main.

Closes #133. Closes #134.

What changes

  • Final residual (matrix_free: NoConvergence reports the initial residual, not the final one (upstream #328) #133, upstream sarah-quinones/faer#328). When bicgstab or conjugate_gradient stops at max_iters, NoConvergence now reports the residual of the x it returns, not |b|.
    • bicgstab kept each new norm in a local variable.
    • conjugate_gradient shadowed abs_residual inside the loop.
  • BiCGSTAB breakdown (bicgstab: a breakdown (r̃ᵀv = 0) is not detected, and x becomes NaN (upstream #327) #134, upstream #327).
    • The rank test on r̃ᵀ v now takes its tolerance from the bound on one entry, eps * k * max_j |r̃_j| * max_j |v_j|, instead of from its own largest pivot. A zero first pivot is therefore caught, and a wide block of right-hand sides is not scaled by its width. The comparison is strict, so v = 0 also counts.
    • At a breakdown (rank 0), or when the half step s or the new residual is not finite, the iteration stops. It keeps the last iterate whose residual is finite, and returns NoConvergence with that true residual.
  • Zero initial guess. InitialGuessStatus::Zero is now written out as zeros on entry in both solvers. Every exit, including max_iters = 0, therefore returns zeros or an iterate, never the caller's buffer.
  • Out of scope. A breakdown-specific error variant is bicgstab: a breakdown-specific error variant (upstream #334, feature) #135 (upstream #334, a feature with an API change). It is not part of this PR.

Known limit, recorded in PATCHSET: a finite residual does not bound x itself, so an operator scaled near the floating-point range limit can still overflow x.

Provenance

I wrote the fix from the upstream issue reports. No code comes from the open upstream PRs #330 and #331, and nothing was posted to Codeberg.

Verification

  • Nine focused tests:
    • codeberg_328 for both solvers
    • codeberg_327
    • a breakdown on the first step, with a NaN-filled zero guess
    • A r̃ = 0
    • a non-finite second half step
    • a zero guess with max_iters = 0, in both solvers
    • an f32 identity with 64 right-hand sides
  • Before the fix, codeberg_327, both codeberg_328 tests and the first-step breakdown fail. After it, all 23 matrix_free tests pass.
  • cargo +1.89.0 check passes in faer/. Clippy reports nothing in the changed files.
  • Nightly rustfmt leaves no diff in my edits. The only remaining diff is the existing codeberg_326 line on main.
  • An independent review (gpt-6.1-sol) found three issues, all fixed here: block-width scaling of the tolerance, zero-guess exits before the first iteration, and an overclaimed finite-x guarantee.

🤖 Generated with Claude Code

…t the last finite iterate (#133, #134; upstream #328, #327)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shinaoka
shinaoka merged commit 5d7a94b into main Oct 29, 2026
7 checks passed
@shinaoka
shinaoka deleted the fix/133-134-krylov-residual branch October 29, 2026 10:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant