Source code for opinf.post._errors

# post/_errors.py
"""Tools for accuracy and error evaluation."""

__all__ = [
    "projection_error",
    "frobenius_error",
    "lp_error",
    "Lp_error",
]

import numpy as np
import scipy.linalg as la
import scipy.integrate as spintegrate


def _absolute_and_relative_error(Qtrue, Qapprox, norm):
    """Compute the absolute and relative errors between Qtrue and Qapprox,
    where Qapprox approximates Qtrue:

        absolute_error = ||Qtrue - Qapprox||,
        relative_error = ||Qtrue - Qapprox|| / ||Qtrue||
                       = absolute_error / ||Qtrue||,

    with ||Q|| defined by norm(Q).
    """
    norm_of_data = norm(Qtrue)
    absolute_error = norm(Qtrue - Qapprox)
    return absolute_error, absolute_error / norm_of_data


[docs] def projection_error(states, basis): """Calculate the absolute and relative projection errors induced by projecting states to a low dimensional basis, i.e., absolute_error = ||Q - Vr Vr^T Q||_F, relative_error = ||Q - Vr Vr^T Q||_F / ||Q||_F where Q = states and Vr = basis. Note that Vr Vr^T is the orthogonal projector onto subspace of R^n defined by the basis. Parameters ---------- states : (n, k) or (k,) ndarray Matrix of k snapshots where each column is a single snapshot, or a single 1D snapshot. If 2D, use the Frobenius norm; if 1D, the l2 norm. Vr : (n, r) ndarray Low-dimensional basis of rank r. Each column is one basis vector. Returns ------- absolute_error : float Absolute projection error ||Q - Vr Vr^T Q||_F. relative_error : float Relative projection error ||Q - Vr Vr^T Q||_F / ||Q||_F. """ Qapprox = basis @ (basis.T @ states) return _absolute_and_relative_error(states, Qapprox, la.norm)
[docs] def frobenius_error(Qtrue, Qapprox): """Compute the absolute and relative Frobenius-norm errors between the snapshot sets Qtrue and Qapprox, where Qapprox approximates Qtrue: absolute_error = ||Qtrue - Qapprox||_F, relative_error = ||Qtrue - Qapprox||_F / ||Qtrue||_F. Parameters ---------- Qtrue : (n, k) "True" data. Each column is one snapshot, i.e., Qtrue[:, j] is the data at some time t[j]. Qapprox : (n, k) An approximation to Qtrue, i.e., Qapprox[:, j] approximates Qtrue[:, j] and corresponds to some time t[j]. Returns ------- abs_err : float Absolute error ||Qtrue - Qapprox||_F. rel_err : float Relative error ||Qtrue - Qapprox||_F / ||Qtrue||_F. """ # Check dimensions. if Qtrue.shape != Qapprox.shape: raise ValueError("Qtrue and Qapprox not aligned") if Qtrue.ndim != 2: raise ValueError("Qtrue and Qapprox must be two-dimensional") # Compute the errors. return _absolute_and_relative_error( Qtrue, Qapprox, lambda Z: la.norm(Z, ord="fro") )
[docs] def lp_error(Qtrue, Qapprox, p=2, normalize=False): """Compute the absolute and relative lp-norm errors between the snapshot sets Qtrue and Qapprox, where Qapprox approximates to Qtrue: absolute_error_j = ||Qtrue_j - Qapprox_j||_p, relative_error_j = ||Qtrue_j - Qapprox_j||_p / ||Qtrue_j||_p. Parameters ---------- Qtrue : (n, k) or (n,) ndarray "True" data. Each column is one snapshot, i.e., Qtrue[:, j] is the data at some time t[j]. If one-dimensional, all of Qtrue is a single snapshot. Qapprox : (n, k) or (n,) ndarray An approximation to Qtrue, i.e., Qapprox[:, j] approximates Qtrue[:, j] and corresponds to some time t[j]. If one-dimensional, all of Qapprox is a single snapshot approximation. p : float Order of the lp norm (default p=2 is the Euclidean norm). Used as the `ord` argument for scipy.linalg.norm(); see options at docs.scipy.org/doc/scipy/reference/generated/scipy.linalg.norm.html. normalize : bool If true, compute the normalized absolute error instead of the relative error, defined by normalized_absolute_error_j = ||Qtrue_j - Qapprox_j||_2 / max_k{||Qtrue_k||_2}. Returns ------- abs_err : (k,) ndarray or float Absolute error of each pair of snapshots Qtrue[:, j] and Qapprox[:, j]. If Qtrue and Qapprox are one-dimensional, Qtrue and Qapprox are treated as single snapshots, so the error is a float. rel_err : (k,) ndarray or float Relative or normed absolute error of each pair of snapshots Qtrue[:, j] and Qapprox[:, j]. If Qtrue and Qapprox are one-dimensional, Qtrue and Qapprox are treated as single snapshots, so the error is a float. """ # Check p. if not np.isscalar(p) or p <= 0: raise ValueError("norm order p must be positive (np.inf ok)") # Check dimensions. if Qtrue.shape != Qapprox.shape: raise ValueError("Qtrue and Qapprox not aligned") if Qtrue.ndim not in (1, 2): raise ValueError("Qtrue and Qapprox must be one- or two-dimensional") # Compute the error. norm_of_data = la.norm(Qtrue, ord=p, axis=0) if normalize: norm_of_data = norm_of_data.max() absolute_error = la.norm(Qtrue - Qapprox, ord=p, axis=0) return absolute_error, absolute_error / norm_of_data
[docs] def Lp_error(Qtrue, Qapprox, t=None, p=2): """Compute the absolute and relative Lp-norm error (with respect to time) between the snapshot sets Qtrue and Qapprox, where Qapprox approximates Qtrue: absolute_error = ||Qtrue - Qapprox||_{L^p}, relative_error = ||Qtrue - Qapprox||_{L^p} / ||Qtrue||_{L^p}, where ||Z||_{L^p} = (int_{t} ||z(t)||_{p} dt)^{1/p}, p < infinity, ||Z||_{L^p} = sup_{t}||z(t)||_{p}, p = infinity. The trapezoidal rule is used to approximate the integrals (for finite p). This error measure is only consistent for data sets where each snapshot represents function values, i.e., Qtrue[:, j] = [q(t1), q(t2), ..., q(tk)]^T. Parameters ---------- Qtrue : (n, k) or (k,) ndarray "True" data corresponding to time t. Each column is one snapshot, i.e., Qtrue[:, j] is the data at time t[j]. If one-dimensional, each entry is one snapshot. Qapprox : (n, k) or (k,) ndarray An approximation to Qtrue, i.e., Qapprox[:, j] approximates Qtrue[:, j] and corresponds to time t[j]. If one-dimensional, each entry is one snapshot. t : (k,) ndarray Time domain of the data Qtrue and the Qapprox. Required unless p == np.inf. p : float > 0 Order of the Lp norm. May be infinite (np.inf). Returns ------- abs_err : float Absolute error ||Qtrue - Qapprox||_{L^p}. rel_err : float Relative error ||Qtrue - Qapprox||_{L^p} / ||Qtrue||_{L^p}. """ # Check p. if not np.isscalar(p) or p <= 0: raise ValueError("norm order p must be positive (np.inf ok)") # Check dimensions. if Qtrue.shape != Qapprox.shape: raise ValueError("Qtrue and Qapprox not aligned") if Qtrue.ndim == 1: Qtrue = np.atleast_2d(Qtrue) Qapprox = np.atleast_2d(Qapprox) elif Qtrue.ndim > 2: raise ValueError("Qtrue and Qapprox must be one- or two-dimensional") # Pick the norm based on p. if 0 < p < np.inf: if t is None: raise ValueError("time t required for p < infinty") if t.ndim != 1: raise ValueError("time t must be one-dimensional") if Qtrue.shape[-1] != t.shape[0]: raise ValueError("Qtrue not aligned with time t") def pnorm(Z): Zpnorm = np.sum(np.abs(Z) ** p, axis=0) return spintegrate.trapezoid(Zpnorm, t) ** (1 / p) else: # p == np.inf def pnorm(Z): return np.max(np.abs(Z), axis=0).max() # Compute the error. return _absolute_and_relative_error(Qtrue, Qapprox, pnorm)