Source code for pct_tools.lipschitz
"""Step-size estimation for the reconstruction: the Lipschitz constant of the gradient.
Steepest descent on ``1/2 ||A x - b||^2`` (see :mod:`pct_tools.recon`) is run with step size
``1 / L``, where ``L`` is the Lipschitz constant of the gradient ``A.T @ (A x - b)``. For a
linear least-squares problem ``L`` is the largest eigenvalue of ``A.T @ A``, i.e. the squared
spectral norm of ``A``. It is estimated with the power method, which only needs repeated
products ``A.T @ A @ x``; this module provides that product over a system matrix stored as
row blocks on disk. The power iteration itself is a few lines of numpy on the caller's side,
with ``filenames`` the compressed matrix blocks written by
:func:`pct_tools.matrix.compute_and_store_sparse_matrix` and ``n_voxels`` their number of
columns::
v = np.random.default_rng(0).random(n_voxels).astype(np.float32)
v /= np.linalg.norm(v)
for _ in range(n_iterations):
v_prev = v
v = compute_ATAx(v, filenames, n_threads=8)
v /= np.linalg.norm(v)
if np.linalg.norm(v - v_prev) < 1e-6:
break
L = np.linalg.norm(compute_ATAx(v, filenames, n_threads=8))
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import numpy as np
import pct_tools._ext
from ._arrays import require_dtype
if TYPE_CHECKING:
from collections.abc import Sequence
from numpy.typing import NDArray
[docs]
def compute_ATAx(
x: NDArray[np.float32],
filenames: Sequence[str],
*,
n_threads: int = 1,
) -> NDArray[np.float32]:
"""Compute ``A.T @ A @ x`` for a system matrix stored as row blocks on disk.
With ``A`` split into blocks ``A_i`` stacked vertically, the result is the sum of
``A_i.T @ (A_i @ x)`` over all blocks, which are read and processed in parallel. This is
the operation needed by the power method to estimate the largest eigenvalue of
``A.T @ A``, i.e. the Lipschitz constant of the gradient of ``1/2 ||A x - b||^2``.
Args:
x: A one-dimensional float32 array whose length equals the number of columns of
``A``.
filenames: Names of the files holding the compressed matrix blocks.
n_threads: The number of OpenMP threads to use; each thread handles whole blocks.
Returns:
A one-dimensional array of 32-bit floats with the same length as ``x``.
Raises:
TypeError: If ``x`` is not a float32 array.
"""
require_dtype(x, np.float32, "x")
return pct_tools._ext.compute_ATAx(x, filenames, n_threads)