@@ -175,6 +175,20 @@ def gershgorin_bounds(H):
175175 return lb , ub
176176
177177
178+ @jit
179+ def _chol_failed (L ):
180+ """Whether a Cholesky factor is unusable because the matrix was not positive.
181+
182+ ``potrf`` only errors out, and so returns nan, when a pivot comes out
183+ non-positive. A pivot at the noise level instead completes but gives a factor that
184+ is useless to solve with, and which of the two a marginal matrix gets depends on
185+ the LAPACK build, so treat both as a failure.
186+ """
187+ d = jnp .diag (L )
188+ tol = L .shape [- 1 ] * jnp .finfo (L .dtype ).eps
189+ return jnp .any (jnp .isnan (L )) | (jnp .min (d ) ** 2 <= tol * jnp .max (d ) ** 2 )
190+
191+
178192@jit
179193def _cholmod (A , maxiter = 4 ):
180194 """Modified Cholesky factorization of indefinite matrix.
@@ -233,13 +247,13 @@ def _cholmod(A, maxiter=4):
233247 for i in range (maxiter ):
234248 L = jnp .linalg .cholesky (A + alphas [kbest ] * eye )
235249 # check if it succeeded
236- isnan = jnp . any ( jnp . isnan ( L ) )
250+ failed = _chol_failed ( L )
237251 # adjust bounds for correction
238- klow = isnan * kbest + (1 - isnan ) * klow
239- khigh = isnan * khigh + (1 - isnan ) * kbest
252+ klow = failed * kbest + (1 - failed ) * klow
253+ khigh = failed * khigh + (1 - failed ) * kbest
240254 kbest = (klow + khigh ) // 2
241255 # if it succeeded, mark it as the best so far
242- Lbest = cond (isnan , lambda _ : Lbest , lambda _ : L , None )
256+ Lbest = cond (failed , lambda _ : Lbest , lambda _ : L , None )
243257 return Lbest
244258
245259
@@ -263,7 +277,7 @@ def chol(A):
263277
264278 """
265279 L = jnp .linalg .cholesky (A )
266- L = cond (jnp . any ( jnp . isnan ( L ) ), lambda A : _cholmod (A ), lambda A : L , A )
280+ L = cond (_chol_failed ( L ), lambda A : _cholmod (A ), lambda A : L , A )
267281 return L
268282
269283
0 commit comments