Skip to content

Commit

Permalink
Remove pinv and implement matfree.eig (#198)
Browse files Browse the repository at this point in the history
  • Loading branch information
pnkraemer authored May 28, 2024
1 parent c9ac24d commit 2a045ff
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 198 deletions.
2 changes: 1 addition & 1 deletion matfree/bounds.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Bounds on functions of matrices."""
"""Matrix-free bounds on functions of matrices."""

from matfree.backend import linalg, np

Expand Down
57 changes: 0 additions & 57 deletions matfree/decomp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,45 +12,6 @@
from matfree.backend import containers, control_flow, func, linalg, np, tree_util
from matfree.backend.typing import Array, Callable, Tuple

# todo: rename svd_approx to svd_partial() because the algorithm is called
# "Partial SVD", not "Approximate SVD".
# todo: move svd_approx to a dedicated eigenvalue-module?


def svd_approx(
v0: Array, depth: int, Av: Callable, vA: Callable, matrix_shape: Tuple[int, ...]
):
"""Approximate singular value decomposition.
Uses GKL with full reorthogonalisation to bi-diagonalise the target matrix
and computes the full SVD of the (small) bidiagonal matrix.
Parameters
----------
v0:
Initial vector for Golub-Kahan-Lanczos bidiagonalisation.
depth:
Depth of the Krylov space constructed by Golub-Kahan-Lanczos bidiagonalisation.
Choosing `depth = min(nrows, ncols) - 1` would yield behaviour similar to
e.g. `np.linalg.svd`.
Av:
Matrix-vector product function.
vA:
Vector-matrix product function.
matrix_shape:
Shape of the matrix involved in matrix-vector and vector-matrix products.
"""
# Factorise the matrix
algorithm = bidiag(Av, vA, depth, matrix_shape=matrix_shape)
u, (d, e), vt, _ = algorithm(v0)

# Compute SVD of factorisation
B = _bidiagonal_dense(d, e)
U, S, Vt = linalg.svd(B, full_matrices=False)

# Combine orthogonal transformations
return u @ U, S, Vt @ vt


class _LanczosAlg(containers.NamedTuple):
"""Lanczos decomposition algorithm."""
Expand Down Expand Up @@ -670,21 +631,3 @@ def body_fun(_, s):

result = control_flow.fori_loop(lower, upper, body_fun=body_fun, init_val=init_val)
return extract(result)


def _bidiagonal_dense(d, e):
diag = linalg.diagonal_matrix(d)
offdiag = linalg.diagonal_matrix(e, 1)
return diag + offdiag


def _eigh_tridiag_sym(diag, off_diag):
# todo: once jax supports eigh_tridiagonal(eigvals_only=False),
# use it here. Until then: an eigen-decomposition of size (order + 1)
# does not hurt too much...
diag = linalg.diagonal_matrix(diag)
offdiag1 = linalg.diagonal_matrix(off_diag, -1)
offdiag2 = linalg.diagonal_matrix(off_diag, 1)
dense_matrix = diag + offdiag1 + offdiag2
eigvals, eigvecs = linalg.eigh(dense_matrix)
return eigvals, eigvecs
46 changes: 46 additions & 0 deletions matfree/eig.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Matrix-free eigenvalue and singular-value analysis."""

from matfree import decomp
from matfree.backend import linalg
from matfree.backend.typing import Array, Callable, Tuple


def svd_partial(
v0: Array, depth: int, Av: Callable, vA: Callable, matrix_shape: Tuple[int, ...]
):
"""Partial singular value decomposition.
Combines bidiagonalisation with full reorthogonalisation
and computes the full SVD of the (small) bidiagonal matrix.
Parameters
----------
v0:
Initial vector for Golub-Kahan-Lanczos bidiagonalisation.
depth:
Depth of the Krylov space constructed by Golub-Kahan-Lanczos bidiagonalisation.
Choosing `depth = min(nrows, ncols) - 1` would yield behaviour similar to
e.g. `np.linalg.svd`.
Av:
Matrix-vector product function.
vA:
Vector-matrix product function.
matrix_shape:
Shape of the matrix involved in matrix-vector and vector-matrix products.
"""
# Factorise the matrix
algorithm = decomp.bidiag(Av, vA, depth, matrix_shape=matrix_shape)
u, (d, e), vt, _ = algorithm(v0)

# Compute SVD of factorisation
B = _bidiagonal_dense(d, e)
U, S, Vt = linalg.svd(B, full_matrices=False)

# Combine orthogonal transformations
return u @ U, S, Vt @ vt


def _bidiagonal_dense(d, e):
diag = linalg.diagonal_matrix(d)
offdiag = linalg.diagonal_matrix(e, 1)
return diag + offdiag
3 changes: 1 addition & 2 deletions matfree/funm.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
"""Functions of matrices implemented as matrix-function-vector products.
"""Matrix-free implementations of functions of matrices.
Examples
--------
Expand Down
79 changes: 0 additions & 79 deletions matfree/pinv.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Tests for SVD functionality."""

from matfree import decomp, test_util
from matfree import eig, test_util
from matfree.backend import linalg, np, testing


Expand Down Expand Up @@ -34,7 +34,7 @@ def vA(v):

v0 = np.ones((ncols,))
v0 /= linalg.vector_norm(v0)
U, S, Vt = decomp.svd_approx(v0, depth, Av, vA, matrix_shape=np.shape(A))
U, S, Vt = eig.svd_partial(v0, depth, Av, vA, matrix_shape=np.shape(A))
U_, S_, Vt_ = linalg.svd(A, full_matrices=False)

tols_decomp = {"atol": 1e-5, "rtol": 1e-5}
Expand Down
57 changes: 0 additions & 57 deletions tests/test_pinv/test_pseudo_inverse_correct.py

This file was deleted.

0 comments on commit 2a045ff

Please sign in to comment.