latest (dev)
Copy
Latest development documentation · Updated 2026-10-08
Source code for tensorplay.linalg._norms
"""Vector and matrix norms, and the quantities derived from them."""
import tensorplay
from ._common import as_index, check_floating, eps_of
from ._decompositions import eigvalsh, svdvals
from ._solve import inv
__all__ = ["cond", "matrix_norm", "matrix_rank", "norm", "vector_norm"]
def _normalize_dims(dim, ndim, expected=None):
if ndim < 0:
raise ValueError(f"a reduction dimension requires a non-empty input, got {ndim}-D")
try:
dims = [as_index(dim, "dim")]
except TypeError:
try:
dims = [as_index(d, "dim") for d in dim]
except TypeError as exc:
raise TypeError("dim must be an integer or a sequence of integers") from exc
if expected is not None and len(dims) != expected:
raise ValueError(f"expected exactly {expected} dimensions, got {len(dims)}")
if not dims:
raise ValueError("at least one reduction dimension must be specified")
result = []
for value in dims:
value = value + ndim if value < 0 else value
if value < 0 or value >= ndim:
raise ValueError(f"dimension {value} out of range for {ndim}-D input")
if value in result:
raise ValueError("reduction dimensions must be unique")
result.append(value)
return result
def _restore_matrix_axes(value, remaining, matrix_dims, ndim):
current_positions = {dim: index for index, dim in enumerate(remaining)}
offset = len(remaining)
current_positions[matrix_dims[0]] = offset
current_positions[matrix_dims[1]] = offset + 1
order = [current_positions[dim] for dim in range(ndim)]
return value.permute(order)
[docs]
def vector_norm(x, ord=2, dim=None, keepdim=False):
"""vector_norm(x, ord=2, dim=None, keepdim=False) -> Tensor
``dim=None`` norms the whole tensor: the input is flattened first, and
``keepdim`` then restores the reduced axes as ones.
"""
inf = float("inf")
reduce_all = dim is None
ndim = x.dim()
work = x.reshape([-1]) if reduce_all else x
axes = [0] if reduce_all else _normalize_dims(dim, ndim)
# Reducing from the last axis toward the first keeps the remaining axis
# numbers stable when keepdim is false.
axes = sorted(axes, reverse=True)
inner_keepdim = keepdim and not reduce_all
magnitude = work.abs()
if ord == 0:
result = (magnitude != 0).to(magnitude.dtype)
for axis in axes:
result = result.sum(dim=axis, keepdim=inner_keepdim)
elif ord == inf:
result = magnitude
for axis in axes:
result = result.max(dim=axis, keepdim=inner_keepdim).values
elif ord == -inf:
result = magnitude
for axis in axes:
result = result.min(dim=axis, keepdim=inner_keepdim).values
else:
if isinstance(ord, str) or ord == 0:
raise ValueError(f"linalg.vector_norm: invalid ord {ord!r}")
result = magnitude.pow(ord)
for axis in axes:
result = result.sum(dim=axis, keepdim=inner_keepdim)
result = result.pow(1.0 / ord)
if reduce_all and keepdim:
result = result.reshape([1] * ndim)
return result
[docs]
def matrix_norm(A, ord="fro", dim=(-2, -1), keepdim=False):
"""matrix_norm(A, ord='fro', dim=(-2, -1), keepdim=False) -> Tensor"""
check_floating(A, "matrix_norm")
inf = float("inf")
ndim = A.dim()
matrix_dims = _normalize_dims(dim, ndim, expected=2)
remaining = [axis for axis in range(ndim) if axis not in matrix_dims]
moved = A.permute(remaining + matrix_dims).contiguous()
batch_shape = list(moved.shape[:-2])
def finish(value):
if keepdim:
value = value.reshape(batch_shape + [1, 1])
return _restore_matrix_axes(value, remaining, matrix_dims, ndim)
return value
magnitude = moved.abs()
if ord in ("fro", "frob"):
return finish(magnitude.pow(2).sum(dim=[-2, -1]).sqrt())
if ord == "nuc":
return finish(svdvals(moved).sum(dim=-1))
if ord == inf:
return finish(magnitude.sum(dim=-1).max(dim=-1).values)
if ord == -inf:
return finish(magnitude.sum(dim=-1).min(dim=-1).values)
if ord == 1:
return finish(magnitude.sum(dim=-2).max(dim=-1).values)
if ord == -1:
return finish(magnitude.sum(dim=-2).min(dim=-1).values)
if ord == 2 or ord == -2:
singular_values = svdvals(moved)
value = singular_values.max(dim=-1).values if ord == 2 \
else singular_values.min(dim=-1).values
return finish(value)
raise RuntimeError(f"linalg.matrix_norm: invalid ord {ord!r}")
[docs]
def norm(input, ord=None, dim=None, keepdim=False):
"""norm(input, ord=None, dim=None, keepdim=False) -> Tensor"""
if dim is None:
if isinstance(ord, str):
if ord not in ("fro", "frob"):
raise ValueError(f"linalg.norm: invalid ord {ord!r}")
ord = 2
return vector_norm(input, ord=2 if ord is None else ord,
dim=None, keepdim=keepdim)
dims = _normalize_dims(dim, input.dim())
if len(dims) == 1:
if isinstance(ord, str):
raise ValueError(
"linalg.norm: a string ord requires two reduction dimensions")
return vector_norm(input, ord=2 if ord is None else ord,
dim=dims, keepdim=keepdim)
if len(dims) == 2:
return matrix_norm(input, ord="fro" if ord is None else ord,
dim=dims, keepdim=keepdim)
raise ValueError("linalg.norm supports one or two reduction dimensions")
[docs]
def matrix_rank(A, *, atol=None, rtol=None, hermitian=False):
"""Computes the numerical rank of each matrix in ``A``.
A singular value counts towards the rank when it exceeds the sum of an
absolute tolerance and a relative tolerance scaled by the largest
singular value of its matrix.
Args:
A (Tensor): tensor of shape ``(..., m, n)`` holding the matrices.
atol (float, Tensor, optional): absolute threshold applied to the
singular values. Defaults to 0.
rtol (float, Tensor, optional): relative threshold applied to the
largest singular value. Defaults to ``max(m, n)`` times the
machine epsilon of ``A``'s dtype.
hermitian (bool): when True, ``A`` is treated as Hermitian and its
rank is derived from eigenvalues instead of singular values.
Returns:
Tensor: integer tensor with the rank of each matrix, with the batch
dimensions of ``A``.
"""
check_floating(A, "matrix_rank")
if A.dim() < 2:
raise ValueError("linalg.matrix_rank: input must contain matrices")
if hermitian and A.shape[-2] != A.shape[-1]:
raise ValueError("linalg.matrix_rank: hermitian input must be square")
if A.shape[-2] == 0 or A.shape[-1] == 0:
return tensorplay.zeros(
list(A.shape[:-2]), dtype=tensorplay.int64, device=A.device)
S = eigvalsh(A) if hermitian else svdvals(A)
magnitudes = S.abs() if hermitian else S
max_S = magnitudes.max(dim=-1, keepdim=True).values
eps = eps_of(A.dtype)
if atol is None:
atol_val = 0.0
elif isinstance(atol, tensorplay.Tensor):
if atol.numel() != 1:
raise ValueError("linalg.matrix_rank: atol must be a scalar")
if atol.device != A.device:
raise RuntimeError("linalg.matrix_rank: atol must be on the input device")
atol_val = atol.to(max_S.dtype)
else:
atol_val = float(atol)
if rtol is None:
rtol_val = eps * max(A.shape[-2], A.shape[-1])
elif isinstance(rtol, tensorplay.Tensor):
if rtol.numel() != 1:
raise ValueError("linalg.matrix_rank: rtol must be a scalar")
if rtol.device != A.device:
raise RuntimeError("linalg.matrix_rank: rtol must be on the input device")
rtol_val = rtol.to(max_S.dtype)
else:
rtol_val = float(rtol)
if isinstance(atol_val, float) and atol_val < 0:
raise ValueError("linalg.matrix_rank: atol must be non-negative")
if isinstance(rtol_val, float) and rtol_val < 0:
raise ValueError("linalg.matrix_rank: rtol must be non-negative")
if isinstance(atol_val, tensorplay.Tensor) and bool((atol_val < 0).item()):
raise ValueError("linalg.matrix_rank: atol must be non-negative")
if isinstance(rtol_val, tensorplay.Tensor) and bool((rtol_val < 0).item()):
raise ValueError("linalg.matrix_rank: rtol must be non-negative")
tol = atol_val + rtol_val * max_S
return (magnitudes > tol).to(tensorplay.int64).sum(dim=-1)
[docs]
def cond(A, p=None):
"""cond(A, p=None) -> Tensor"""
if A.dim() < 2:
raise ValueError("linalg.cond: input must contain matrices")
if p is None:
S = svdvals(A)
return S.max(dim=-1).values / S.min(dim=-1).values
if p in (2, -2):
S = svdvals(A)
return S.max(dim=-1).values / S.min(dim=-1).values
if p in ("fro", "nuc", float("inf"), -float("inf"), 1, -1):
return matrix_norm(A, ord=p) * matrix_norm(inv(A), ord=p)
raise RuntimeError(f"linalg.cond: p={p!r} is not supported")Help improve this page
Found an error, an unclear step, or a missing example?
Was this page helpful?

