furax.linalg
Modules:
-
cholesky–Block-banded Cholesky factorization for symmetric positive-(semi)definite matrices.
-
low_rank–Low-rank approximation for PyTree-aware linear operators.
Classes:
-
CGResult–Result of the Conjugate Gradient solver.
-
LanczosResult–Result of Lanczos eigenvalue computation.
-
BandedCholeskyOperator–Inverse of a symmetric positive-(semi)definite matrix, by Cholesky solve.
-
LowRankOperator–Linear operator from low-rank terms: A = U @ diag(S) @ U^T.
-
LowRankTerms–Low-rank approximation of a Hermitian operator: A ≈ U @ diag(S) @ U^T.
Functions:
-
cg–Conjugate Gradient solver for symmetric positive definite systems Ax = b.
-
eigvalsh–Eigenvalues of batched symmetric matrices, sorted ascending.
-
lanczos_eigh–Lanczos algorithm for computing k eigenvalues via an m-dimensional Krylov subspace.
-
lanczos_tr–Thick-restart Lanczos for computing k eigenpairs of a Hermitian operator.
-
banded_cholesky–Block Cholesky factor of a symmetric positive-(semi)definite block-banded matrix.
-
banded_cholesky_solve–Solve
L Lᵀ x = bgiven the lower band factorlbfrombanded_cholesky. -
low_rank_mv–Apply low-rank approximation as a matrix-vector product.
furax.linalg.CGResult
Bases: NamedTuple
Result of the Conjugate Gradient solver.
Attributes:
-
solution(PyTree[Num[Array, ...]]) –The approximate solution x to Ax = b.
-
residuals(Float[Array, ' max_steps']) –Norm of the residual at each iteration, shape (max_steps,).
-
num_steps(Array) –Number of CG steps taken.
Source code in src/furax/linalg/_cg.py
solution
instance-attribute
residuals
instance-attribute
num_steps
instance-attribute
furax.linalg.LanczosResult
Bases: NamedTuple
Result of Lanczos eigenvalue computation.
Attributes:
-
eigenvalues(Float[Array, ' k']) –The k computed eigenvalues, sorted ascending.
-
eigenvectors(PyTree[Num[Array, ' k ...']]) –A block PyTree containing k eigenvectors.
-
residual_norms(Float[Array, ' k']) –The norm of the residual for each eigenpair.
Source code in src/furax/linalg/_lanczos.py
eigenvalues
instance-attribute
eigenvectors
instance-attribute
residual_norms
instance-attribute
furax.linalg.BandedCholeskyOperator
dataclass
Bases: AbstractLinearOperator
Inverse of a symmetric positive-(semi)definite matrix, by Cholesky solve.
This operator can be used with an ordinary dense matrix (from_dense), or from
a banded matrix (zero away from the diagonal, from_bands) and so is stored
compactly instead of as the full matrix.
Calling A(b) solves A x = b.
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import BandedCholeskyOperator
>>> A = jnp.array([[4., 2.], [2., 3.]])
>>> op = BandedCholeskyOperator.from_dense(A)
>>> op(jnp.array([1., 0.])) # solves A x = [1, 0]
Array([ 0.375, -0.25 ], dtype=float32)
Methods:
-
from_bands–Factor a block-banded matrix (see
banded_cholesky) and wrap it as an operator. -
from_dense–Factor a fully dense matrix (the degenerate
n_blocks=1, w=0band case). -
mv–
Attributes:
Source code in src/furax/linalg/cholesky.py
lb
instance-attribute
The lower-triangular Cholesky factor in compact band-layout.
from_bands(bands, in_structure=None, regularization=0.0)
classmethod
Factor a block-banded matrix (see banded_cholesky) and wrap it as an operator.
Parameters:
-
bands(Float[Array, '*batch n w1 k k']) –Upper-band representation of the block-banded matrix, shape
(*batch, n_blocks, w+1, k, k). -
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –Structure of the values the operator is called with. Defaults to a plain array of shape
(*batch, n_blocks, k). Pass a PyTree explicitly to solve for several arrays at once instead. -
regularization(float, default:0.0) –Relative ridge added to each diagonal block before factoring.
Source code in src/furax/linalg/cholesky.py
from_dense(matrix, in_structure=None, regularization=0.0)
classmethod
Factor a fully dense matrix (the degenerate n_blocks=1, w=0 band case).
Parameters:
-
matrix(Float[Array, '*batch k k']) –The dense matrix, shape
(*batch, k, k). -
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –Structure of the values the operator is called with. Defaults to a plain array of shape
(*batch, k). Pass a PyTree explicitly to solve for several arrays at once instead. -
regularization(float, default:0.0) –Relative ridge added to the diagonal before factoring.
Source code in src/furax/linalg/cholesky.py
mv(x)
Source code in src/furax/linalg/cholesky.py
furax.linalg.LowRankOperator
Bases: AbstractLinearOperator
Linear operator from low-rank terms: A = U @ diag(S) @ U^T.
This wraps a LowRankTerms as an AbstractLinearOperator, enabling composition with other operators.
Parameters:
-
terms(LowRankTerms) –Low-rank terms containing eigenvalues and eigenvectors.
-
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –The expected structure of the operator input. If None, inferred from the eigenvectors structure.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> terms = low_rank(A, rank=2, key=jax.random.PRNGKey(0))
>>> B = LowRankOperator(terms)
>>> x = jnp.ones(5)
>>> y = B(x) # Applies low-rank approximation
Methods:
Attributes:
-
terms(LowRankTerms) –
Source code in src/furax/linalg/low_rank.py
terms
instance-attribute
__init__(terms, *, in_structure=None)
Source code in src/furax/linalg/low_rank.py
furax.linalg.LowRankTerms
Bases: NamedTuple
Low-rank approximation of a Hermitian operator: A ≈ U @ diag(S) @ U^T.
Attributes:
-
eigenvalues(Float[Array, ' k']) –The k eigenvalues (S), shape (k,).
-
eigenvectors(PyTree[Num[Array, ' k ...']]) –The k eigenvectors (U) as a block PyTree with leading dimension k.
Source code in src/furax/linalg/low_rank.py
eigenvalues
instance-attribute
eigenvectors
instance-attribute
furax.linalg.cg(A, b, x0=None, *, preconditioner=None, max_steps=500, atol=0.0, rtol=1e-05, stabilise_every=10, negative_curvature='ignore', loop_kind='lax', iteration_callback=None)
Conjugate Gradient solver for symmetric positive definite systems Ax = b.
The residual norm is recorded at every iteration (see CGResult.residuals)
so convergence can be monitored. Inputs may be sharded along their contracting
dimensions. The solve is forward-mode differentiable by default; reverse-mode
(grad/vjp) requires loop_kind='bounded' or 'checkpointed'.
Convergence is declared when ||r|| <= atol + rtol * ||b||. With atol
and rtol both 0 the criterion is never met and the solver runs for exactly
max_steps steps; otherwise it stops early once the residual is small enough,
and max_steps is only a ceiling. A is assumed positive definite; negative
curvature is handled per negative_curvature.
Parameters:
-
A(AbstractLinearOperator) –A symmetric positive definite linear operator.
-
b(PyTree[Num[Array, ...]]) –Right-hand side of the system.
-
x0(PyTree[Num[Array, ...]] | None, default:None) –Initial guess. Defaults to zeros.
-
preconditioner(AbstractLinearOperator | None, default:None) –Optional preconditioner M such that MA is better conditioned. M must be symmetric positive definite.
-
max_steps(int, default:500) –Maximum number of iteration steps.
-
atol(float, default:0.0) –Absolute tolerance on the residual norm.
-
rtol(float, default:1e-05) –Relative tolerance on the residual norm (scaled by
||b||). -
stabilise_every(int, default:10) –If set, replace the recursively-updated residual with the true residual
b - A xevery N iterations (after steps N, 2N, 3N, ...). This counters floating-point drift at the cost of one extra matvec per stabilisation. -
negative_curvature(Literal['ignore', 'error', 'truncate'], default:'ignore') –How to handle negative curvature
p^T A p < 0, which a positive definiteAnever produces. One of:'ignore'(default): assumeAis positive definite and do not check. Fastest; the other modes add a per-iteration check.'error': raise as soon as negative curvature is encountered (a debugging aid for verifyingA; configurable via Equinox'sEQX_ON_ERROR, seeequinox.error_if).'truncate': stop and return the last iterate before the bad direction (truncated CG, as used by Newton-CG on indefinite Hessians).
-
loop_kind(Literal['lax', 'checkpointed', 'bounded'], default:'lax') –Lowering for the iteration loop (
equinox.internal.while_loop).'lax'(default) is fastest and forward-mode differentiable only.'bounded'adds reverse-mode AD but its cost scales withmax_steps(the checkpoint structure runs to the ceiling regardless of early exit), so keepmax_stepstight.'checkpointed'is reverse-mode only. -
iteration_callback(Callable[[Array, Array], None] | None, default:None) –Optional host callback called after each step with
(step, r_norm)as 0-d JAX arrays. Runs viajax.debug.callbackso it is JIT-compatible and ordered.
Returns:
-
CGResult–CGResult with the solution, per-iteration residual norms, and iteration count.
Examples:
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> from furax.linalg import cg
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> b = jnp.ones(5)
>>> result = cg(A, b, max_steps=20)
>>> expected = jnp.array([1.0, 0.5, 1 / 3, 0.25, 0.2])
>>> bool(jnp.allclose(result.solution, expected, atol=1e-4))
True
Source code in src/furax/linalg/_cg.py
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
furax.linalg.eigvalsh(A, batch_size=10000)
Eigenvalues of batched symmetric matrices, sorted ascending.
Uses an analytic closed-form for n<=3, and jax.numpy.linalg.eigvalsh for larger matrices.
Parameters:
-
A(Float[Array, '... n n']) –Batched symmetric matrix array with shape (..., n, n).
-
batch_size(int, default:10000) –Number of matrices to process per XLA kernel launch for n>3. When batch_size >= total number of matrices the full batch is processed in one launch (maximum parallelism). Reduce to limit peak memory usage.
Source code in src/furax/linalg/_eigvalsh.py
furax.linalg.lanczos_eigh(A, v0, *, k=20, m=None)
Lanczos algorithm for computing k eigenvalues via an m-dimensional Krylov subspace.
Builds an m-dimensional Krylov subspace (m >= k) and computes all m Ritz pairs. When m == k the method returns all m Ritz pairs sorted ascending. When m > k, only the k Ritz pairs with the smallest residual norms are returned. Selecting by residual norm (rather than by eigenvalue magnitude) picks the pairs that have converged most reliably within the subspace, which need not be the extremal ones.
Note
There is no which parameter (unlike lanczos_tr). Targeted selection
of smallest/largest pairs would require either restarts or post-filtering of
unconverged Ritz values, neither of which is meaningful for a single-shot
m-step factorization. Use lanczos_tr if you need extremal eigenpairs.
Note
Early breakdown (beta_j == 0, i.e. invariant subspace reached) is not
detected. The corresponding Lanczos vector becomes zero and the remaining
iterations produce zero contributions; affected Ritz pairs will have zero
residual norms but their eigenvectors should not be trusted.
The cheap Lanczos residual bound is used
||A y_i - θ_i y_i|| ≈ |β_m| |s_i[m-1]|
where s_i is the i-th eigenvector of the m×m tridiagonal T_m.
Parameters:
-
A(AbstractLinearOperator) –A Hermitian linear operator.
-
v0(PyTree[Num[Array, ...]]) –Initial vector for the Krylov subspace.
-
k(int, default:20) –Number of eigenpairs to return.
-
m(int | None, default:None) –Size of the Krylov subspace. Must be >= k. Defaults to min(2k, n). Larger m builds a richer subspace and can yield more accurate Ritz pairs, at the cost of m matrix-vector products and O(m) vector storage.
Returns:
-
LanczosResult–LanczosResult containing the k best eigenvalues, eigenvectors, and their
-
LanczosResult–residual norms, sorted by eigenvalue ascending.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure, normal_like
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> v0 = normal_like(as_structure(d), jax.random.PRNGKey(0))
>>> result = lanczos_eigh(A, v0, k=5)
>>> result.eigenvalues
Array([1., 2., 3., 4., 5.], dtype=float32)
Source code in src/furax/linalg/_lanczos.py
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 | |
furax.linalg.lanczos_tr(A, v0, *, k=20, m=None, which='LM', max_restarts=300, tol=1e-10)
Thick-restart Lanczos for computing k eigenpairs of a Hermitian operator.
Each restart cycle maintains an m-step Lanczos factorization:
where H is a bordered tridiagonal inner matrix.
A pair is converged when the cheap Lanczos residual bound (ARPACK criterion)
where S is the eigenvector matrix of the m×m inner matrix H, so
S[m-1, i] is the last component of the i-th eigenvector of H, and
max|theta| is used as an estimate of ||A||.
Uses full reorthogonalization throughout. No locking: all k pairs are recomputed at every restart regardless of convergence status.
Note
residual_norms is the cheap bound, not the true residual. Exactly
0 means "converged below the detectable coupling", not zero error.
Note
Lanczos converges fastest to extremal eigenvalues; interior ones
(near the middle of the spectrum) converge slowly. This makes which
targets that select interior pairs hard: 'SM' for an indefinite
operator picks eigenvalues closest to zero, which are interior, and
restarts alone will not converge them unless m is a large fraction
of n (no shift-invert is implemented). 'LM', 'LA', 'SA'
and the two ends of 'BE' are extremal and converge with small m.
Parameters:
-
A(AbstractLinearOperator) –A Hermitian linear operator.
-
v0(PyTree[Num[Array, ...]]) –Initial vector for the Krylov subspace.
-
k(int, default:20) –Number of eigenpairs to compute.
-
m(int | None, default:None) –Size of the Krylov subspace. Must be > k. Defaults to min(2*k, n).
-
which(LanczosWhich, default:'LM') –Which k eigenpairs to target. One of: - 'LM' (default): k largest magnitude (|λ|). - 'SM': k smallest magnitude (|λ|). - 'LA': k largest algebraic. - 'SA': k smallest algebraic. - 'BE': half (k//2) from each end of the spectrum; for odd k the extra pair comes from the high (largest algebraic) end.
-
max_restarts(int, default:300) –Maximum number of restart cycles.
-
tol(float, default:1e-10) –Convergence tolerance; see criterion above.
Returns:
-
LanczosResult–LanczosResult containing eigenvalues, eigenvectors, and residual norms,
-
LanczosResult–sorted by eigenvalue ascending.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure, normal_like
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> v0 = normal_like(as_structure(d), jax.random.PRNGKey(0))
>>> result = lanczos_tr(A, v0, k=2, which='SA')
>>> result.eigenvalues # Should be approximately [1, 2]
Array([1., 2.], dtype=float32)
Source code in src/furax/linalg/_lanczos.py
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 | |
furax.linalg.banded_cholesky(bands, regularization=0.0)
Block Cholesky factor of a symmetric positive-(semi)definite block-banded matrix.
"Block-banded" means A is made of k×k blocks, and only the blocks within w
of the diagonal can be non-zero. E.g., for n_blocks = 4 and bandwidth w = 1:
A = [ A00 A01 0 0 ]
[ A01ᵀ A11 A12 0 ]
[ 0 A12ᵀ A22 A23 ]
[ 0 0 A23ᵀ A33 ]
Only the diagonal and upper blocks are stored (symmetry gives the rest) packed as
bands[..., j, d, :, :] = A[j, j+d] for d = 0..w (d=0 the diagonal blocks,
d=w the furthest off-diagonal ones still inside the band)::
bands[:, 0] = [A00, A11, A22, A33] (d=0: the diagonal blocks)
bands[:, 1] = [A01, A12, A23, · ] (d=1: one block off the diagonal; the trailing
entry is unused padding, since block 3 has no
block 4 to pair with)
Returns the lower factor in the same band layout, lb[..., i, d, :, :] = L[i, i-d] with
L Lᵀ = A. Batched over arbitrary leading dims. Band w = 0 is the block-diagonal case
(every off-diagonal block is zero); n_blocks = 1 is the fully dense case.
regularization adds a relative ridge to each diagonal block before factoring, scaled by
that block's own mean diagonal. This is purely intended as a numerical safeguard.
Parameters:
-
bands(Float[Array, '*batch n w1 k k']) –Upper-band representation of the block-banded matrix, shape
(*batch, n_blocks, w+1, k, k). -
regularization(float, default:0.0) –Relative ridge added to each diagonal block before factoring.
Returns:
-
Float[Array, '*batch n w1 k k']–The lower band factor, same shape as
bands.
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import banded_cholesky
>>> # tridiagonal SPD A = [[4,1,0,0],[1,4,1,0],[0,1,4,1],[0,0,1,4]], scalar (k=1) blocks
>>> diag = jnp.array([4., 4., 4., 4.]).reshape(4, 1, 1, 1)
>>> off = jnp.array([1., 1., 1., 0.]).reshape(4, 1, 1, 1) # trailing entry is padding
>>> bands = jnp.concatenate([diag, off], axis=1) # (n_blocks=4, w1=2, k=1, k=1)
>>> lb = banded_cholesky(bands)
>>> lb[:, :, 0, 0] # columns are (L[i,i], L[i,i-1])
Array([[2. , 0. ],
[1.9364916 , 0.5 ],
[1.9321835 , 0.5163978 ],
[1.9318755 , 0.51754916]], dtype=float32)
Source code in src/furax/linalg/cholesky.py
furax.linalg.banded_cholesky_solve(lb, b)
Solve L Lᵀ x = b given the lower band factor lb from banded_cholesky.
Batched over arbitrary leading dims (matching lb's, excluding its trailing
(n, w1, k, k) core).
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import banded_cholesky, banded_cholesky_solve
>>> a = jnp.array([[4., 2.], [2., 3.]])
>>> lb = banded_cholesky(a[None, None, None])
>>> x = banded_cholesky_solve(lb, jnp.array([[[1., 0.]]]))
>>> a @ x[0, 0]
Array([1., 0.], dtype=float32)
Source code in src/furax/linalg/cholesky.py
furax.linalg.low_rank_mv(terms, x)
Apply low-rank approximation as a matrix-vector product.
Computes y = U @ diag(S) @ U^T @ x efficiently without forming the full matrix.
Parameters:
-
terms(LowRankTerms) –Low-rank terms containing eigenvalues and eigenvectors.
-
x(PyTree[Num[Array, ...]]) –Input PyTree with structure matching the eigenvectors (without leading k dimension).
Returns:
-
PyTree[Num[Array, ...]]–Output PyTree y = U @ diag(S) @ U^T @ x.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> terms = low_rank(A, rank=5, key=jax.random.PRNGKey(0)) # Full rank
>>> x = jnp.array([1., 0., 0., 0., 0.])
>>> y = low_rank_mv(terms, x)
>>> # Should be close to A @ x = [1, 0, 0, 0, 0]
Source code in src/furax/linalg/low_rank.py
furax.linalg.cholesky
Block-banded Cholesky factorization for symmetric positive-(semi)definite matrices.
Classes:
-
BandedCholeskyOperator–Inverse of a symmetric positive-(semi)definite matrix, by Cholesky solve.
Functions:
-
banded_cholesky–Block Cholesky factor of a symmetric positive-(semi)definite block-banded matrix.
-
banded_cholesky_solve–Solve
L Lᵀ x = bgiven the lower band factorlbfrombanded_cholesky.
BandedCholeskyOperator
dataclass
Bases: AbstractLinearOperator
Inverse of a symmetric positive-(semi)definite matrix, by Cholesky solve.
This operator can be used with an ordinary dense matrix (from_dense), or from
a banded matrix (zero away from the diagonal, from_bands) and so is stored
compactly instead of as the full matrix.
Calling A(b) solves A x = b.
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import BandedCholeskyOperator
>>> A = jnp.array([[4., 2.], [2., 3.]])
>>> op = BandedCholeskyOperator.from_dense(A)
>>> op(jnp.array([1., 0.])) # solves A x = [1, 0]
Array([ 0.375, -0.25 ], dtype=float32)
Methods:
-
from_bands–Factor a block-banded matrix (see
banded_cholesky) and wrap it as an operator. -
from_dense–Factor a fully dense matrix (the degenerate
n_blocks=1, w=0band case). -
mv–
Attributes:
Source code in src/furax/linalg/cholesky.py
lb
instance-attribute
The lower-triangular Cholesky factor in compact band-layout.
from_bands(bands, in_structure=None, regularization=0.0)
classmethod
Factor a block-banded matrix (see banded_cholesky) and wrap it as an operator.
Parameters:
-
bands(Float[Array, '*batch n w1 k k']) –Upper-band representation of the block-banded matrix, shape
(*batch, n_blocks, w+1, k, k). -
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –Structure of the values the operator is called with. Defaults to a plain array of shape
(*batch, n_blocks, k). Pass a PyTree explicitly to solve for several arrays at once instead. -
regularization(float, default:0.0) –Relative ridge added to each diagonal block before factoring.
Source code in src/furax/linalg/cholesky.py
from_dense(matrix, in_structure=None, regularization=0.0)
classmethod
Factor a fully dense matrix (the degenerate n_blocks=1, w=0 band case).
Parameters:
-
matrix(Float[Array, '*batch k k']) –The dense matrix, shape
(*batch, k, k). -
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –Structure of the values the operator is called with. Defaults to a plain array of shape
(*batch, k). Pass a PyTree explicitly to solve for several arrays at once instead. -
regularization(float, default:0.0) –Relative ridge added to the diagonal before factoring.
Source code in src/furax/linalg/cholesky.py
mv(x)
Source code in src/furax/linalg/cholesky.py
banded_cholesky(bands, regularization=0.0)
Block Cholesky factor of a symmetric positive-(semi)definite block-banded matrix.
"Block-banded" means A is made of k×k blocks, and only the blocks within w
of the diagonal can be non-zero. E.g., for n_blocks = 4 and bandwidth w = 1:
A = [ A00 A01 0 0 ]
[ A01ᵀ A11 A12 0 ]
[ 0 A12ᵀ A22 A23 ]
[ 0 0 A23ᵀ A33 ]
Only the diagonal and upper blocks are stored (symmetry gives the rest) packed as
bands[..., j, d, :, :] = A[j, j+d] for d = 0..w (d=0 the diagonal blocks,
d=w the furthest off-diagonal ones still inside the band)::
bands[:, 0] = [A00, A11, A22, A33] (d=0: the diagonal blocks)
bands[:, 1] = [A01, A12, A23, · ] (d=1: one block off the diagonal; the trailing
entry is unused padding, since block 3 has no
block 4 to pair with)
Returns the lower factor in the same band layout, lb[..., i, d, :, :] = L[i, i-d] with
L Lᵀ = A. Batched over arbitrary leading dims. Band w = 0 is the block-diagonal case
(every off-diagonal block is zero); n_blocks = 1 is the fully dense case.
regularization adds a relative ridge to each diagonal block before factoring, scaled by
that block's own mean diagonal. This is purely intended as a numerical safeguard.
Parameters:
-
bands(Float[Array, '*batch n w1 k k']) –Upper-band representation of the block-banded matrix, shape
(*batch, n_blocks, w+1, k, k). -
regularization(float, default:0.0) –Relative ridge added to each diagonal block before factoring.
Returns:
-
Float[Array, '*batch n w1 k k']–The lower band factor, same shape as
bands.
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import banded_cholesky
>>> # tridiagonal SPD A = [[4,1,0,0],[1,4,1,0],[0,1,4,1],[0,0,1,4]], scalar (k=1) blocks
>>> diag = jnp.array([4., 4., 4., 4.]).reshape(4, 1, 1, 1)
>>> off = jnp.array([1., 1., 1., 0.]).reshape(4, 1, 1, 1) # trailing entry is padding
>>> bands = jnp.concatenate([diag, off], axis=1) # (n_blocks=4, w1=2, k=1, k=1)
>>> lb = banded_cholesky(bands)
>>> lb[:, :, 0, 0] # columns are (L[i,i], L[i,i-1])
Array([[2. , 0. ],
[1.9364916 , 0.5 ],
[1.9321835 , 0.5163978 ],
[1.9318755 , 0.51754916]], dtype=float32)
Source code in src/furax/linalg/cholesky.py
banded_cholesky_solve(lb, b)
Solve L Lᵀ x = b given the lower band factor lb from banded_cholesky.
Batched over arbitrary leading dims (matching lb's, excluding its trailing
(n, w1, k, k) core).
Examples:
>>> import jax.numpy as jnp
>>> from furax.linalg import banded_cholesky, banded_cholesky_solve
>>> a = jnp.array([[4., 2.], [2., 3.]])
>>> lb = banded_cholesky(a[None, None, None])
>>> x = banded_cholesky_solve(lb, jnp.array([[[1., 0.]]]))
>>> a @ x[0, 0]
Array([1., 0.], dtype=float32)
Source code in src/furax/linalg/cholesky.py
furax.linalg.low_rank
Low-rank approximation for PyTree-aware linear operators.
Classes:
-
LowRankTerms–Low-rank approximation of a Hermitian operator: A ≈ U @ diag(S) @ U^T.
-
LowRankOperator–Linear operator from low-rank terms: A = U @ diag(S) @ U^T.
Functions:
-
low_rank–Compute a low-rank approximation of a Hermitian operator.
-
low_rank_mv–Apply low-rank approximation as a matrix-vector product.
Attributes:
LowRankMethod = Literal['lanczos', 'lanczos_tr']
module-attribute
LowRankTerms
Bases: NamedTuple
Low-rank approximation of a Hermitian operator: A ≈ U @ diag(S) @ U^T.
Attributes:
-
eigenvalues(Float[Array, ' k']) –The k eigenvalues (S), shape (k,).
-
eigenvectors(PyTree[Num[Array, ' k ...']]) –The k eigenvectors (U) as a block PyTree with leading dimension k.
Source code in src/furax/linalg/low_rank.py
eigenvalues
instance-attribute
eigenvectors
instance-attribute
LowRankOperator
Bases: AbstractLinearOperator
Linear operator from low-rank terms: A = U @ diag(S) @ U^T.
This wraps a LowRankTerms as an AbstractLinearOperator, enabling composition with other operators.
Parameters:
-
terms(LowRankTerms) –Low-rank terms containing eigenvalues and eigenvectors.
-
in_structure(PyTree[ShapeDtypeStruct] | None, default:None) –The expected structure of the operator input. If None, inferred from the eigenvectors structure.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> terms = low_rank(A, rank=2, key=jax.random.PRNGKey(0))
>>> B = LowRankOperator(terms)
>>> x = jnp.ones(5)
>>> y = B(x) # Applies low-rank approximation
Methods:
Attributes:
-
terms(LowRankTerms) –
Source code in src/furax/linalg/low_rank.py
terms
instance-attribute
__init__(terms, *, in_structure=None)
Source code in src/furax/linalg/low_rank.py
low_rank(A, rank, key, *, method='lanczos_tr', **kwargs)
Compute a low-rank approximation of a Hermitian operator.
The approximation is of the form A ≈ U @ diag(S) @ U^T, where U contains the k eigenvectors and S contains the corresponding eigenvalues.
Two solvers are available via method:
- 'lanczos': single-shot m-step Lanczos. Builds one Krylov subspace and returns the k
best-converged Ritz pairs by residual norm.
Extra kwargs:
m. - 'lanczos_tr' (default): thick-restart Lanczos. Restarts until convergence and targets
specific eigenpairs via
which('LM', 'SM', 'LA', 'SA', 'BE'; default 'LM'). Extra kwargs:m,which,max_restarts,tol.
Parameters:
-
A(AbstractLinearOperator) –A Hermitian linear operator.
-
rank(int) –Number of eigenvalues/eigenvectors to compute.
-
key(PRNGKeyArray) –Random key for initialization of the eigenvalue solver.
-
method(LowRankMethod, default:'lanczos_tr') –Eigenvalue solver to use (see above). Defaults to 'lanczos_tr'.
-
**kwargs(Any, default:{}) –Additional keyword arguments passed to the selected solver.
Returns:
-
LowRankTerms–LowRankTerms containing eigenvalues and eigenvectors.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> terms = low_rank(A, rank=2, key=jax.random.PRNGKey(0), method='lanczos_tr')
>>> terms.eigenvalues # Should be approximately [4, 5] (which='LM' by default)
Array([4., 5.], dtype=float32)
Source code in src/furax/linalg/low_rank.py
low_rank_mv(terms, x)
Apply low-rank approximation as a matrix-vector product.
Computes y = U @ diag(S) @ U^T @ x efficiently without forming the full matrix.
Parameters:
-
terms(LowRankTerms) –Low-rank terms containing eigenvalues and eigenvectors.
-
x(PyTree[Num[Array, ...]]) –Input PyTree with structure matching the eigenvectors (without leading k dimension).
Returns:
-
PyTree[Num[Array, ...]]–Output PyTree y = U @ diag(S) @ U^T @ x.
Examples:
>>> import jax
>>> import jax.numpy as jnp
>>> from furax import DiagonalOperator
>>> from furax.tree import as_structure
>>> d = jnp.array([1., 2., 3., 4., 5.])
>>> A = DiagonalOperator(d, in_structure=as_structure(d))
>>> terms = low_rank(A, rank=5, key=jax.random.PRNGKey(0)) # Full rank
>>> x = jnp.array([1., 0., 0., 0., 0.])
>>> y = low_rank_mv(terms, x)
>>> # Should be close to A @ x = [1, 0, 0, 0, 0]