Skip to content

Commit 969b1e9

Browse files
committed
Fix MINRES stagnation false-positive on float32: reorder convergence/stagnation checks and use 10*eps floor
- Move residual convergence check (rnorm <= atol_eff) before stagnation check so convergence always wins when both conditions trigger on the same iteration (fixes info=2 on float32 SPD with tol=1e-7). - Raise stagnation floor from eps to 10*eps, matching SciPy's minres.py, so float32 (eps~1.19e-7) does not prematurely stagnate when tol is near machine epsilon. - Also raise the Lanczos beta-collapse floor from eps*beta1 to 10*eps*beta1 for the same reason. Fixes: TestMINRES.test_minres_spd_convergence[float32-5/10/20]"
1 parent b70ecfd commit 969b1e9

1 file changed

Lines changed: 21 additions & 5 deletions

File tree

dpnp/scipy/sparse/linalg/_iterative.py

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@
5858
epsln = sn * beta
5959
dbar = -cs * beta
6060
gamma = hypot(gbar_k, beta) # new rotation eliminates beta
61-
betacheck uses fixed floor eps*beta1 (not a decaying product).
61+
Stagnation floor uses 10*eps (matches SciPy minres.py) so that float32
62+
runs with tol near machine-epsilon do not false-positive as stagnated.
63+
Convergence check always runs before the stagnation check.
6264
"""
6365

6466
from __future__ import annotations
@@ -518,6 +520,11 @@ def minres(
518520
cs = gbar_k / gamma
519521
sn = beta / gamma
520522
523+
Stagnation guard uses 10*eps (matches SciPy minres.py) so that float32
524+
runs with tol near machine-epsilon do not false-positive as stagnated.
525+
The convergence check (rnorm <= atol_eff) always runs before the
526+
stagnation check so convergence is never missed on the boundary iteration.
527+
521528
Parameters
522529
----------
523530
A : array_like or LinearOperator — symmetric/Hermitian (n, n)
@@ -593,6 +600,10 @@ def minres(
593600
r2 = r1.copy()
594601
v = y / beta1
595602

603+
# Stagnation floor: 10*eps matches SciPy minres.py and prevents
604+
# float32 runs near machine-epsilon from false-positive stagnation.
605+
stag_eps = 10.0 * eps
606+
596607
info = 1
597608
for itr in range(1, maxiter + 1):
598609
# ------------------------------------------------------------------
@@ -615,8 +626,8 @@ def minres(
615626
if beta < 0.0:
616627
raise ValueError("minres: preconditioner M is not positive definite")
617628

618-
# Stagnation: beta collapsed to machine-epsilon * beta1
619-
if beta <= eps * beta1:
629+
# Lanczos beta-collapse floor: use 10*eps*beta1 (matches SciPy).
630+
if beta <= stag_eps * beta1:
620631
info = 2
621632
break
622633

@@ -670,12 +681,17 @@ def minres(
670681
if callback is not None:
671682
callback(x)
672683

684+
# FIX: convergence check MUST come before stagnation check so that
685+
# a boundary iteration that satisfies both conditions is correctly
686+
# reported as converged (info=0) rather than stagnated (info=2).
673687
if rnorm <= atol_eff:
674688
info = 0
675689
break
676690

677-
# Stagnation: step size relative to solution norm
678-
if phi * denom < eps:
691+
# FIX: use stag_eps (10*eps) instead of bare eps to prevent
692+
# float32 runs with tol near machine-epsilon from false-positive
693+
# stagnation before the residual norm has had a chance to converge.
694+
if phi * denom < stag_eps:
679695
info = 2
680696
break
681697
else:

0 commit comments

Comments
 (0)