2424# pylint: disable=abstract-method
2525
2626_CACHED_MATVECS = {}
27+ _CACHED_FUNCTIONS = {}
2728
2829
2930class JaxBackend (abstract_backend .AbstractBackend ):
@@ -243,15 +244,15 @@ def eigs(self,
243244 which : Text = 'LR' ,
244245 maxiter : int = 20 ) -> Tuple [Tensor , List ]:
245246 """
246- Implicitly restarted Arnoldi method for finding the lowest
247- eigenvector-eigenvalue pairs of a linear operator `A`.
247+ Implicitly restarted Arnoldi method for finding the lowest
248+ eigenvector-eigenvalue pairs of a linear operator `A`.
248249 `A` is a function implementing the matrix-vector
249- product.
250+ product.
250251
251252 WARNING: This routine uses jax.jit to reduce runtimes. jitting is triggered
252- at the first invocation of `eigs`, and on any subsequent calls
253- if the python `id` of `A` changes, even if the formal definition of `A`
254- stays the same.
253+ at the first invocation of `eigs`, and on any subsequent calls
254+ if the python `id` of `A` changes, even if the formal definition of `A`
255+ stays the same.
255256 Example: the following will jit once at the beginning, and then never again:
256257
257258 ```python
@@ -265,7 +266,7 @@ def A(H,x):
265266 res = eigs(A, [H],x) #jitting is triggerd only at `n=0`
266267 ```
267268
268- The following code triggers jitting at every iteration, which
269+ The following code triggers jitting at every iteration, which
269270 results in considerably reduced performance
270271
271272 ```python
@@ -278,7 +279,7 @@ def A(H,x):
278279 x = jax.np.array(np.random.rand(10,10))
279280 res = eigs(A, [H],x) #jitting is triggerd at every step `n`
280281 ```
281-
282+
282283 Args:
283284 A: A (sparse) implementation of a linear operator.
284285 Call signature of `A` is `res = A(vector, *args)`, where `vector`
@@ -293,13 +294,13 @@ def A(H,x):
293294 num_krylov_vecs: The number of iterations (number of krylov vectors).
294295 numeig: The number of eigenvector-eigenvalue pairs to be computed.
295296 tol: The desired precision of the eigenvalues. For the jax backend
296- this has currently no effect, and precision of eigenvalues is not
297+ this has currently no effect, and precision of eigenvalues is not
297298 guaranteed. This feature may be added at a later point. To increase
298299 precision the caller can either increase `maxiter` or `num_krylov_vecs`.
299- which: Flag for targetting different types of eigenvalues. Currently
300- supported are `which = 'LR'` (larges real part) and `which = 'LM'`
300+ which: Flag for targetting different types of eigenvalues. Currently
301+ supported are `which = 'LR'` (larges real part) and `which = 'LM'`
301302 (larges magnitude).
302- maxiter: Maximum number of restarts. For `maxiter=0` the routine becomes
303+ maxiter: Maximum number of restarts. For `maxiter=0` the routine becomes
303304 equivalent to a simple Arnoldi method.
304305 Returns:
305306 (eigvals, eigvecs)
@@ -326,11 +327,12 @@ def A(H,x):
326327 type (initial_state )))
327328 if A not in _CACHED_MATVECS :
328329 _CACHED_MATVECS [A ] = libjax .tree_util .Partial (libjax .jit (A ))
329- if not hasattr (self , '_iram' ):
330- # pylint: disable=attribute-defined-outside-init
331- self ._iram = jitted_functions ._implicitly_restarted_arnoldi (libjax )
332- return self ._iram (_CACHED_MATVECS [A ], args , initial_state , num_krylov_vecs ,
333- numeig , which , tol , maxiter )
330+ if "imp_arnoldi" not in _CACHED_FUNCTIONS :
331+ imp_arnoldi = jitted_functions ._implicitly_restarted_arnoldi (libjax )
332+ _CACHED_FUNCTIONS ["imp_arnoldi" ] = imp_arnoldi
333+ return _CACHED_FUNCTIONS ["imp_arnoldi" ](_CACHED_MATVECS [A ], args ,
334+ initial_state , num_krylov_vecs ,
335+ numeig , which , tol , maxiter )
334336
335337 def eigsh_lanczos (
336338 self ,
@@ -347,12 +349,12 @@ def eigsh_lanczos(
347349 reorthogonalize : Optional [bool ] = False ) -> Tuple [Tensor , List ]:
348350 """
349351 Lanczos method for finding the lowest eigenvector-eigenvalue pairs
350- of a hermitian linear operator `A`. `A` is a function implementing
351- the matrix-vector product.
352+ of a hermitian linear operator `A`. `A` is a function implementing
353+ the matrix-vector product.
352354 WARNING: This routine uses jax.jit to reduce runtimes. jitting is triggered
353- at the first invocation of `eigsh_lanczos`, and on any subsequent calls
354- if the python `id` of `A` changes, even if the formal definition of `A`
355- stays the same.
355+ at the first invocation of `eigsh_lanczos`, and on any subsequent calls
356+ if the python `id` of `A` changes, even if the formal definition of `A`
357+ stays the same.
356358 Example: the following will jit once at the beginning, and then never again:
357359
358360 ```python
@@ -366,7 +368,7 @@ def A(H,x):
366368 res = eigsh_lanczos(A, [H],x) #jitting is triggerd only at `n=0`
367369 ```
368370
369- The following code triggers jitting at every iteration, which
371+ The following code triggers jitting at every iteration, which
370372 results in considerably reduced performance
371373
372374 ```python
@@ -379,7 +381,7 @@ def A(H,x):
379381 x = jax.np.array(np.random.rand(10,10))
380382 res = eigsh_lanczos(A, [H],x) #jitting is triggerd at every step `n`
381383 ```
382-
384+
383385 Args:
384386 A: A (sparse) implementation of a linear operator.
385387 Call signature of `A` is `res = A(vector, *args)`, where `vector`
@@ -395,7 +397,7 @@ def A(H,x):
395397 numeig: The number of eigenvector-eigenvalue pairs to be computed.
396398 If `numeig > 1`, `reorthogonalize` has to be `True`.
397399 tol: The desired precision of the eigenvalues. For the jax backend
398- this has currently no effect, and precision of eigenvalues is not
400+ this has currently no effect, and precision of eigenvalues is not
399401 guaranteed. This feature may be added at a later point.
400402 To increase precision the caller can increase `num_krylov_vecs`.
401403 delta: Stopping criterion for Lanczos iteration.
@@ -404,7 +406,7 @@ def A(H,x):
404406 is stopped. It means that an (approximate) invariant subspace has
405407 been found.
406408 ndiag: The tridiagonal Operator is diagonalized every `ndiag` iterations
407- to check convergence. This has currently no effect for the jax backend,
409+ to check convergence. This has currently no effect for the jax backend,
408410 but may be added at a later point.
409411 reorthogonalize: If `True`, Krylov vectors are kept orthogonal by
410412 explicit orthogonalization (more costly than `reorthogonalize=False`)
@@ -433,12 +435,162 @@ def A(H,x):
433435 type (initial_state )))
434436 if A not in _CACHED_MATVECS :
435437 _CACHED_MATVECS [A ] = libjax .tree_util .Partial (A )
436- if not hasattr (self , '_jaxlan' ):
437- # pylint: disable=attribute-defined-outside-init
438- self ._jaxlan = jitted_functions ._generate_jitted_eigsh_lanczos (libjax )
438+ if "eigsh_lanczos" not in _CACHED_FUNCTIONS :
439+ eigsh_lanczos = jitted_functions ._generate_jitted_eigsh_lanczos (libjax )
440+ _CACHED_FUNCTIONS ["eigsh_lanczos" ] = eigsh_lanczos
441+ eigsh_lanczos = _CACHED_FUNCTIONS ["eigsh_lanczos" ]
442+ return eigsh_lanczos (_CACHED_MATVECS [A ], args , initial_state ,
443+ num_krylov_vecs , numeig , delta , reorthogonalize )
444+
445+ def gmres (self ,
446+ A_mv : Callable ,
447+ b : Tensor ,
448+ A_args : Optional [List ] = None ,
449+ A_kwargs : Optional [dict ] = None ,
450+ x0 : Optional [Tensor ] = None ,
451+ tol : float = 1E-05 ,
452+ atol : Optional [float ] = None ,
453+ num_krylov_vectors : Optional [int ] = None ,
454+ maxiter : Optional [int ] = 1 ,
455+ M : Optional [Callable ] = None
456+ ) -> Tuple [Tensor , int ]:
457+ """ GMRES solves the linear system A @ x = b for x given a vector `b` and
458+ a general (not necessarily symmetric/Hermitian) linear operator `A`.
459+
460+ As a Krylov method, GMRES does not require a concrete matrix representation
461+ of the n by n `A`, but only a function
462+ `vector1 = A_mv(vector0, *A_args, **A_kwargs)`
463+ prescribing a one-to-one linear map from vector0 to vector1 (that is,
464+ A must be square, and thus vector0 and vector1 the same size). If `A` is a
465+ dense matrix, or if it is a symmetric/Hermitian operator, a different
466+ linear solver will usually be preferable.
467+
468+ GMRES works by first constructing the Krylov basis
469+ K = (x0, A_mv@x0, A_mv@A_mv@x0, ..., (A_mv^num_krylov_vectors)@x_0) and then
470+ solving a certain dense linear system K @ q0 = q1 from whose solution x can
471+ be approximated. For `num_krylov_vectors = n` the solution is provably exact
472+ in infinite precision, but the expense is cubic in `num_krylov_vectors` so
473+ one is typically interested in the `num_krylov_vectors << n` case.
474+ The solution can in this case be repeatedly
475+ improved, to a point, by restarting the Arnoldi iterations each time
476+ `num_krylov_vectors` is reached. Unfortunately the optimal parameter choices
477+ balancing expense and accuracy are difficult to predict in advance, so
478+ applying this function requires a degree of experimentation.
479+
480+ In a tensor network code one is typically interested in A_mv implementing
481+ some tensor contraction. This implementation thus allows `b` and `x0` to be
482+ of whatever arbitrary, though identical, shape `b = A_mv(x0, ...)` expects.
483+ Reshaping to and from a matrix problem is handled internally.
484+
485+ The Jax backend version of GMRES uses a homemade implementation that, for
486+ now, is suboptimal for num_krylov_vecs ~ b.size.
487+
488+ For the same reason as described in eigsh_lancsoz, the function A_mv
489+ should be Jittable (or already Jitted) and, if at all possible, defined
490+ only once at the global scope. A new compilation will be triggered each
491+ time an A_mv with a new function signature is passed in, even if the
492+ 'new' function is identical to the old one (function identity is
493+ undecidable).
494+
495+
496+ Args:
497+ A_mv : A function `v0 = A_mv(v, *A_args, **A_kwargs)` where `v0` and
498+ `v` have the same shape.
499+ b : The `b` in `A @ x = b`; it should be of the shape `A_mv`
500+ operates on.
501+ A_args : Positional arguments to `A_mv`, supplied to this interface
502+ as a list.
503+ Default: None.
504+ A_kwargs : In the other backends, keyword arguments to `A_mv`, supplied
505+ as a dictionary. However, the Jax backend does not support
506+ A_mv accepting
507+ keyword arguments since this causes problems with Jit.
508+ Therefore, an error is thrown if A_kwargs is specified.
509+ Default: None.
510+ x0 : An optional guess solution. Zeros are used by default.
511+ If `x0` is supplied, its shape and dtype must match those of
512+ `b`, or an
513+ error will be thrown.
514+ Default: zeros.
515+ tol, atol: Solution tolerance to achieve,
516+ norm(residual) <= max(tol*norm(b), atol).
517+ Default: tol=1E-05
518+ atol=tol
519+ num_krylov_vectors
520+ : Size of the Krylov space to build at each restart.
521+ Expense is cubic in this parameter. If supplied, it must be
522+ an integer in 0 < num_krylov_vectors <= b.size.
523+ Default: b.size.
524+ maxiter : The Krylov space will be repeatedly rebuilt up to this many
525+ times. Large values of this argument
526+ should be used only with caution, since especially for nearly
527+ symmetric matrices and small `num_krylov_vectors` convergence
528+ might well freeze at a value significantly larger than `tol`.
529+ Default: 1
530+ M : Inverse of the preconditioner of A; see the docstring for
531+ `scipy.sparse.linalg.gmres`. This is unsupported in the Jax
532+ backend, and NotImplementedError will be raised if it is
533+ supplied.
534+ Default: None.
535+
536+
537+ Raises:
538+ ValueError: -if `x0` is supplied but its shape differs from that of `b`.
539+ -if num_krylov_vectors is 0 or exceeds b.size.
540+ -if tol or atol was negative.
541+ NotImplementedError: - If M is supplied.
542+ - If A_kwargs is supplied.
543+
544+ Returns:
545+ x : The converged solution. It has the same shape as `b`.
546+ info : 0 if convergence was achieved, the number of restarts otherwise.
547+ """
439548
440- return self ._jaxlan (_CACHED_MATVECS [A ], args , initial_state ,
441- num_krylov_vecs , numeig , delta , reorthogonalize )
549+ if x0 is not None and x0 .shape != b .shape :
550+ errstring = (f"If x0 is supplied, its shape, { x0 .shape } , must match b's"
551+ f", { b .shape } ." )
552+ raise ValueError (errstring )
553+ if x0 is not None and x0 .dtype != b .dtype :
554+ errstring = (f"If x0 is supplied, its dtype, { x0 .dtype } , must match b's"
555+ f", { b .dtype } ." )
556+ raise ValueError (errstring )
557+ if num_krylov_vectors is None :
558+ num_krylov_vectors = b .size
559+ if num_krylov_vectors <= 0 or num_krylov_vectors > b .size :
560+ errstring = (f"num_krylov_vectors must be in "
561+ f"0 < { num_krylov_vectors } <= { b .size } ." )
562+ raise ValueError (errstring )
563+ if tol < 0 :
564+ raise ValueError (f"tol = { tol } must be positive." )
565+ if atol is None :
566+ atol = tol
567+ if atol < 0 :
568+ raise ValueError (f"atol = { atol } must be positive." )
569+
570+ if M is not None :
571+ raise NotImplementedError ("M is not supported by the Jax backend." )
572+ if A_kwargs is not None :
573+ raise NotImplementedError ("A_kwargs is not supported by the Jax backend." )
574+
575+ if A_args is None :
576+ A_args = []
577+
578+ if x0 is None :
579+ x0 = self .zeros (b .shape , b .dtype )
580+
581+ if A_mv not in _CACHED_MATVECS :
582+ _CACHED_MATVECS [A_mv ] = libjax .tree_util .Partial (A_mv )
583+ if "gmres_f" not in _CACHED_FUNCTIONS :
584+ _CACHED_FUNCTIONS ["gmres_f" ] = jitted_functions .gmres_wrapper (libjax )
585+ gmres_f = _CACHED_FUNCTIONS ["gmres_f" ]
586+ x , _ , n_iter , converged = gmres_f (_CACHED_MATVECS [A_mv ], A_args , b ,
587+ x0 , tol , atol , num_krylov_vectors ,
588+ maxiter )
589+ if converged :
590+ info = 0
591+ else :
592+ info = n_iter
593+ return x , info
442594
443595 def conj (self , tensor : Tensor ) -> Tensor :
444596 return jnp .conj (tensor )
0 commit comments