Quickstart¶
Build an operator¶
Wrap an array, or describe structure directly:
import jax
import jax.numpy as jnp
import linox
dense = jax.random.normal(jax.random.PRNGKey(0), (4, 4))
spd = dense @ dense.T + 4 * jnp.eye(4)
a = linox.Matrix(spd) # a general matrix
d = linox.Diagonal(jnp.array([1.0, 2.0, 3.0, 4.0])) # only the diagonal is stored
i = linox.Identity(4) # nothing is stored
assert a.shape == d.shape == i.shape == (4, 4)
Compose¶
Arithmetic is lazy. Nothing is evaluated until you apply the result to something.
import jax.numpy as jnp
import linox
d = linox.Diagonal(jnp.arange(1.0, 5.0))
op = 2.0 * d + linox.Identity(4)
x = op @ jnp.ones(4)
assert jnp.allclose(x, 2.0 * jnp.arange(1.0, 5.0) + 1.0)
Apply, solve, decompose¶
import jax
import jax.numpy as jnp
import linox
dense = jax.random.normal(jax.random.PRNGKey(0), (4, 4))
op = linox.Matrix(dense @ dense.T + 4 * jnp.eye(4))
b = jnp.ones(4)
x = op @ b # matvec
y = linox.solve(op, b) # solve op @ y == b
w, q = linox.eigh(op) # eigendecomposition
assert jnp.linalg.norm(linox.todense(op) @ y - b) < 1e-10
assert w.shape == (4,)
Failure is reported, not hidden¶
A singular system raises rather than returning a plausible-looking wrong answer:
import jax
import jax.numpy as jnp
import linox
from linox import LinearSolveError
u = jax.random.normal(jax.random.PRNGKey(0), (6, 3))
singular = linox.Matrix(u @ u.T) # rank 3, not invertible
try:
linox.solve(singular, jnp.ones(6))
raise AssertionError("expected a failure")
except LinearSolveError:
pass
# Opt out, or inspect the outcome yourself:
x = linox.solve(singular, jnp.ones(6), throw=False)
x, info = linox.solve(singular, jnp.ones(6), throw=False, return_info=True)
assert info.result != linox.RESULTS.successful
It is all JAX¶
Operators are pytrees, so they pass through transformations unchanged:
import jax
import jax.numpy as jnp
import linox
dense = jax.random.normal(jax.random.PRNGKey(0), (4, 4))
spd = dense @ dense.T + 4 * jnp.eye(4)
b = jnp.ones(4)
jitted = jax.jit(lambda m, v: linox.solve(linox.Matrix(m), v))
grad = jax.grad(lambda v: linox.solve(linox.Matrix(spd), v).sum())
batched = jax.vmap(lambda v: linox.solve(linox.Matrix(spd), v))
assert jitted(spd, b).shape == (4,)
assert grad(b).shape == (4,)
assert batched(jnp.ones((5, 4))).shape == (5, 4)
Next¶
Linear operators explains the model; Choosing a method covers exact versus approximate.