# Source code for tensorplay.linalg._norms Source: https://www.tensorplay.cn/docs/_modules/tensorplay/linalg/_norms.html ``` """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") ```