Formula reference: sensitivity

Generated from the docstrings of vaft.formula.sensitivity: 6 public functions. Each entry is what vaft.formula.describe("sensitivity.<name>") prints. All formula categories

Overview

The mathematics shared by every VAFT domain that linearizes something: a Jacobian by finite differences, covariance pushed through a Jacobian, the same push by sampling, the singular-value spectrum of a Jacobian, and a test of whether the linearization still describes a finite step.

  • A forward model f maps a 1-D parameter vector x [n] to a 1-D output vector y [m]; a scalar output is treated as m = 1. The Jacobian is J[i, j] = dy_i/dx_j with shape (m, n).
  • Nothing here knows what the parameters mean. Which perturbation is physical, which step is admissible and what the derivative is for belong to the domain that owns f (#1642 s12); interpreting the result belongs to vaft.validation.sensitivity.
  • Every function is cheap except monte_carlo_propagation, which calls f once per sample and is therefore never invoked by anything else in VAFT: it exists to be called explicitly (#1639 s8).
  • propagate_formula_uncertainty connects these kernels to one public vaft.formula function by its parameter names (#1874). It is opt-in: no formula calls it, and a formula’s own signature, values and cost do not change. A formula may carry a domain-owned analytic Jacobian (vaft.formula._propagation); otherwise the Jacobian is a scale-aware finite difference.

Functions

finite_difference_jacobianJacobian of a vector function by forward or central differences.
linear_covariance_propagationOutput covariance of a linearized model, correlations included.
linearity_ratioHow much of a finite response the linearization misses.
monte_carlo_propagationOutput mean and covariance by sampling Gaussian inputs through the full model.
propagate_formula_uncertaintyPropagate input uncertainty through one vaft.formula function, correlations included.
singular_value_spectrumSingular values, numerical rank and null space of a (column-scaled) Jacobian.

finite_difference_jacobian

source
finite_difference_jacobian(f, x, step=None, scheme='central')
Show source vaft/formula/sensitivity.py, lines 51–125
def finite_difference_jacobian(
    f: Callable[[np.ndarray], np.ndarray],
    x: np.ndarray,
    step: float | np.ndarray | None = None,
    scheme: str = "central",
) -> np.ndarray:
    r"""Jacobian of a vector function by forward or central differences.

    $$J_{ij} \approx \frac{f_i(x + h_j e_j) - f_i(x - h_j e_j)}{2 h_j}
      \quad\text{(central)}, \qquad
      J_{ij} \approx \frac{f_i(x + h_j e_j) - f_i(x)}{h_j} \quad\text{(forward)}$$

    Parameters
    ----------
    f : callable
        Forward model taking a parameter vector and returning an output vector [any].
    x : array-like
        Point of linearization [any].
    step : float or array-like, optional
        Absolute step per parameter; default
        $h_j = \epsilon^{1/3}\max(1, |x_j|)$ (central) or
        $\epsilon^{1/2}\max(1, |x_j|)$ (forward) [any].
    scheme : str, optional
        ``"central"`` (second order) or ``"forward"`` (first order); either
        costs n + 1 or 2n + 1 evaluations, the extra one fixing the output
        size [-].

    Returns
    -------
    numpy.ndarray
        ``(m, n)`` Jacobian [any].

    Raises
    ------
    ValueError
        On an unknown scheme or a non-positive step.

    Numerical notes
    ---------------
    The default steps balance truncation against round-off for a model
    evaluated to machine precision.  They are absolute and floored at one
    unit, so a parameter far below unit magnitude (``1e-8``) is stepped far
    beyond its own size -- possibly across a domain edge such as a sign or a
    logarithm; give such a parameter its own ``step``.  A model with its own solver tolerance
    (an iterative equilibrium, an external code) needs a step well above that
    tolerance, chosen by its owner: a finite difference across a convergence
    threshold measures the solver, not the physics (#1642 s18).

    References
    ----------
    .. [1] J. Nocedal and S. J. Wright, *Numerical Optimization*, 2nd ed.,
           Springer (2006), Sec. 8.1.
    """
    if scheme not in ("central", "forward"):
        raise ValueError(f"scheme must be 'central' or 'forward', got {scheme!r}")
    x0 = _vector(x)
    eps = np.finfo(float).eps
    if step is None:
        h = (eps ** (1 / 3) if scheme == "central" else eps ** 0.5) * np.maximum(1.0, np.abs(x0))
    else:
        h = np.broadcast_to(np.asarray(step, dtype=float), x0.shape).copy()
    if np.any(~np.isfinite(h)) or np.any(h <= 0):
        raise ValueError("finite-difference steps must be finite and positive")
    f0 = _vector(f(x0.copy()))
    jac = np.empty((f0.size, x0.size))
    for j in range(x0.size):
        up = x0.copy()
        up[j] += h[j]
        if scheme == "central":
            down = x0.copy()
            down[j] -= h[j]
            jac[:, j] = (_vector(f(up)) - _vector(f(down))) / (2.0 * h[j])
        else:
            jac[:, j] = (_vector(f(up)) - f0) / h[j]
    return jac

Jacobian of a vector function by forward or central differences.

\[J_{ij} \approx \frac{f_i(x + h_j e_j) - f_i(x - h_j e_j)}{2 h_j} \quad\text{(central)}, \qquad J_{ij} \approx \frac{f_i(x + h_j e_j) - f_i(x)}{h_j} \quad\text{(forward)}\]
NameTypeUnitDescription
fcallableany

Forward model taking a parameter vector and returning an output vector.

xarray-likeany

Point of linearization.

stepfloat or array-like, optionalany
Absolute step per parameter; default $h_j = \epsilon^{1/3}\max(1, x_j )$ (central) or $\epsilon^{1/2}\max(1, x_j )$ (forward).
schemestr, optional-

"central" (second order) or "forward" (first order); either costs n + 1 or 2n + 1 evaluations, the extra one fixing the output size.

Returns
—numpy.ndarrayany

(m, n) Jacobian.

Raises

  • ValueError On an unknown scheme or a non-positive step.
Numerical notes · References

Numerical notes

The default steps balance truncation against round-off for a model evaluated to machine precision. They are absolute and floored at one unit, so a parameter far below unit magnitude (1e-8) is stepped far beyond its own size – possibly across a domain edge such as a sign or a logarithm; give such a parameter its own step. A model with its own solver tolerance (an iterative equilibrium, an external code) needs a step well above that tolerance, chosen by its owner: a finite difference across a convergence threshold measures the solver, not the physics (#1642 s18).

References

  1. J. Nocedal and S. J. Wright, Numerical Optimization, 2nd ed., Springer (2006), Sec. 8.1.

linear_covariance_propagation

source
linear_covariance_propagation(jacobian, covariance)
Show source vaft/formula/sensitivity.py, lines 141–178
def linear_covariance_propagation(jacobian: np.ndarray, covariance: np.ndarray) -> np.ndarray:
    r"""Output covariance of a linearized model, correlations included.

    $$\Sigma_y = J\,\Sigma_x\,J^{\mathsf T}$$

    Parameters
    ----------
    jacobian : array-like
        ``(m, n)`` sensitivity matrix [any].
    covariance : array-like
        ``(n, n)`` input covariance; a 1-D array is read as independent
        variances [any].

    Returns
    -------
    numpy.ndarray
        ``(m, m)`` output covariance, symmetrized [any].

    Raises
    ------
    ValueError
        When the shapes do not agree or the input covariance is not symmetric
        to round-off (relative ``1e-12`` of its largest entry).

    Assumptions
    -----------
    The model is linear over the spread of the inputs; :func:`linearity_ratio`
    and :func:`monte_carlo_propagation` test that.

    References
    ----------
    .. [1] JCGM 100:2008, *Evaluation of measurement data -- Guide to the
           expression of uncertainty in measurement*, Sec. 5.2, Eq. (13).
    """
    jac = np.atleast_2d(np.asarray(jacobian, dtype=float))
    cov = _covariance(covariance, jac.shape[1])
    out = jac @ cov @ jac.T
    return 0.5 * (out + out.T)

Output covariance of a linearized model, correlations included.

\[\Sigma_y = J\,\Sigma_x\,J^{\mathsf T}\]
NameTypeUnitDescription
jacobianarray-likeany

(m, n) sensitivity matrix.

covariancearray-likeany

(n, n) input covariance; a 1-D array is read as independent variances.

Returns
—numpy.ndarrayany

(m, m) output covariance, symmetrized.

Raises

  • ValueError When the shapes do not agree or the input covariance is not symmetric to round-off (relative 1e-12 of its largest entry).
Assumptions · References

Assumptions

The model is linear over the spread of the inputs; linearity_ratio and monte_carlo_propagation test that.

References

  1. JCGM 100:2008, Evaluation of measurement data – Guide to the expression of uncertainty in measurement, Sec. 5.2, Eq. (13).

linearity_ratio

source
linearity_ratio(f, x, step, jacobian, output_scale=None)
Show source vaft/formula/sensitivity.py, lines 330–378
def linearity_ratio(
    f: Callable[[np.ndarray], np.ndarray],
    x: np.ndarray,
    step: np.ndarray,
    jacobian: np.ndarray,
    output_scale: np.ndarray | None = None,
) -> float:
    r"""How much of a finite response the linearization misses.

    $$r = \frac{\lVert W(f(x + \delta) - f(x) - J\delta)\rVert}{\lVert W(f(x + \delta) - f(x))\rVert},
      \qquad W = \operatorname{diag}(1/s_i)$$

    Parameters
    ----------
    f : callable
        Forward model [any].
    x : array-like
        Point of linearization [any].
    step : array-like
        Finite perturbation $\delta$, typically one standard deviation along
        a direction of interest [any].
    jacobian : array-like
        $J$ at ``x`` [any].
    output_scale : array-like, optional
        Typical magnitude $s_i$ per output, so outputs in different units
        weigh alike; default unweighted [any].

    Returns
    -------
    float
        $r$; 0 for an exactly linear response, of order 1 when the local
        Jacobian no longer describes the step; ``nan`` when the response is
        zero [-].

    Assumptions
    -----------
    One direction is tested per call; a model can be linear along one and not
    another.
    """
    x0 = _vector(x)
    delta = _vector(step)
    f0 = _vector(f(x0.copy()))
    response = _vector(f(x0 + delta)) - f0
    predicted = np.atleast_2d(np.asarray(jacobian, dtype=float)) @ delta
    weight = np.ones_like(response) if output_scale is None else 1.0 / _vector(output_scale)
    norm = float(np.linalg.norm(weight * response))
    if norm == 0.0 or not np.isfinite(norm):
        return float("nan")
    return float(np.linalg.norm(weight * (response - predicted)) / norm)

How much of a finite response the linearization misses.

\[r = \frac{\lVert W(f(x + \delta) - f(x) - J\delta)\rVert}{\lVert W(f(x + \delta) - f(x))\rVert}, \qquad W = \operatorname{diag}(1/s_i)\]
NameTypeUnitDescription
fcallableany

Forward model.

xarray-likeany

Point of linearization.

steparray-likeany

Finite perturbation $\delta$, typically one standard deviation along a direction of interest.

jacobianarray-likeany

$J$ at x.

output_scalearray-like, optionalany

Typical magnitude $s_i$ per output, so outputs in different units weigh alike; default unweighted.

Returns
—float-

$r$; 0 for an exactly linear response, of order 1 when the local Jacobian no longer describes the step; nan when the response is zero.

Assumptions

Assumptions

One direction is tested per call; a model can be linear along one and not another.

monte_carlo_propagation

source
monte_carlo_propagation(f, mean, covariance, samples=2000, seed=0)

Output mean and covariance by sampling Gaussian inputs through the full model.

\[x^{(k)} \sim \mathcal N(\mu, \Sigma_x), \qquad \hat\Sigma_y = \frac{1}{K-1}\sum_k (y^{(k)} - \bar y)(y^{(k)} - \bar y)^{\mathsf T}\]
NameTypeUnitDescription
fcallableany

Forward model, called once per sample.

meanarray-likeany

Input mean.

covariancearray-likeany

Input covariance, positive semi-definite; 1-D means independent variances.

samplesint, optional-

Number of draws $K$.

seedint, optional-

Seed of the NumPy generator, so a result is reproducible.

Returns
—dictany

mean and covariance of the finite outputs, samples used, rejected (draws whose output was not finite or whose evaluation raised).

Raises

  • ValueError When samples is below 2, or the covariance does not match the mean, is not symmetric, or is not positive semi-definite.
Assumptions · Limitations · References

Assumptions

Gaussian inputs. Expensive: samples model evaluations, so it is never called implicitly (#1639 s8); the cost is the caller’s decision.

Limitations

The statistics are over the accepted draws only, so a model that fails on part of the input distribution yields a covariance conditioned on success; rejected says how much was conditioned away. An output with infinite variance (a pole inside the input distribution) has a sample covariance that does not converge with samples.

References

  1. JCGM 101:2008, Propagation of distributions using a Monte Carlo method, Sec. 7.

propagate_formula_uncertainty

source
propagate_formula_uncertainty(formula, inputs, *, input_names=None, covariance=None, std=None, method='linear', derivative='auto', fd_step=None, domain=None, samples=2000, seed=0)

Propagate input uncertainty through one vaft.formula function, correlations included.

\[\Sigma_y \approx J\,\Sigma_x\,J^{\mathsf T}, \qquad J_{ij} = \partial f_i/\partial x_j\]
NameTypeUnitDescription
formulacallableany

A public formula, called with keyword arguments.

inputsmappingany

Every argument the call needs, by parameter name; uncertain inputs at their mean values.

input_namessequence of str, optional-

The uncertain inputs, in the order of the covariance axes; every other entry of inputs is fixed configuration. Default: the numeric scalar entries of inputs in the formula’s parameter order.

covariancearray-like, optionalany

(n, n) covariance of the inputs in input_names order, or a 1-D array of variances. Exactly one of covariance and std.

stdarray-like, optionalany

1-D standard deviations of independent inputs, input_names order.

methodstr, optional-

"linear" (first order, $J\Sigma J^{\mathsf T}$) or "monte_carlo" (Gaussian sampling through the formula).

derivativestr, optional-

"auto" (analytic when the formula carries one, else finite difference), "analytic" (fail if it carries none) or "finite_difference".

fd_stepfloat, array-like or mapping, optionalany
Absolute finite-difference step per input; default $\epsilon^{1/3} x_j $, scaled to each input’s own magnitude.
domaincallable, optional-

Predicate on the call’s arguments, True where the formula is physically defined; default the domain the formula declares (vaft.formula._propagation), else none.

samplesint, optional-

Monte Carlo draws.

seedint, optional-

Monte Carlo seed.

Returns
—FormulaPropagationany

Value, covariance, standard deviation and their provenance.

Raises

  • ValueError No uncertainty given (unknown is not zero), both covariance and std, an uncertain input missing, non-scalar, unknown to the formula or non-finite, an uncertainty that is NaN, infinite, negative, not symmetric or not positive semi-definite, input means outside the formula’s domain (declared, or a raised or non-finite evaluation), a non-finite Jacobian, a Monte Carlo run that accepts fewer than two draws, or an unknown or inconsistent method/derivative.
Assumptions · Limitations · References

Assumptions

"linear" is a local first-order result at the input means; it is not a verified global uncertainty and carries no model-form or fitted-coefficient uncertainty. "monte_carlo" assumes Gaussian inputs (no other input distribution is supported), and its statistics cover the accepted draws only: a draw outside the domain is rejected and counted in rejected.

Limitations

Uncertain inputs must be scalars; profile- and field-valued covariance is out of scope (#1874). A finite difference is not a solver-native linearization.

References

  1. JCGM 100:2008, Guide to the expression of uncertainty in measurement, Sec. 5.2.
  2. JCGM 101:2008, Propagation of distributions using a Monte Carlo method, Sec. 7.

singular_value_spectrum

source
singular_value_spectrum(jacobian, column_scale=None, rtol=None)
Show source vaft/formula/sensitivity.py, lines 267–327
def singular_value_spectrum(
    jacobian: np.ndarray,
    column_scale: np.ndarray | None = None,
    rtol: float | None = None,
) -> dict:
    r"""Singular values, numerical rank and null space of a (column-scaled) Jacobian.

    $$J D = U \Sigma V^{\mathsf T}, \qquad
      \operatorname{rank} = \#\{\sigma_k > \tau\,\sigma_1\}, \qquad
      \kappa = \sigma_1/\sigma_{\min}$$

    Parameters
    ----------
    jacobian : array-like
        ``(m, n)`` sensitivity matrix, ideally already weighted by the inverse
        measurement uncertainty so its rows are commensurate [any].
    column_scale : array-like, optional
        Diagonal of $D$, e.g. a typical magnitude per parameter, making the
        columns commensurate; default no scaling [any].
    rtol : float, optional
        Relative rank tolerance $\tau$; default $\max(m, n)\,\epsilon$ [-].

    Returns
    -------
    dict
        ``singular_values`` (descending), ``rank``, ``nullity`` (n - rank),
        ``condition_number`` (inf when rank deficient), ``null_space``
        (``(n, nullity)`` basis in the *unscaled* parameters) [any].

    Raises
    ------
    ValueError
        When the matrix is not finite or ``column_scale`` has a zero.

    Numerical notes
    ---------------
    The SVD is taken of $JD$ itself, never of $J^{\mathsf T}J$, which would
    square the condition number -- the practice of
    :func:`vaft.code.efit.analyze_efit_identifiability`.

    References
    ----------
    .. [1] G. H. Golub and C. F. Van Loan, *Matrix Computations*, 4th ed.,
           Johns Hopkins (2013), Secs. 2.4 and 5.4.
    """
    jac = np.atleast_2d(np.asarray(jacobian, dtype=float))
    if not np.all(np.isfinite(jac)):
        raise ValueError("Jacobian must be finite")
    n = jac.shape[1]
    scale = np.ones(n) if column_scale is None else _vector(column_scale)
    if scale.shape != (n,) or np.any(scale == 0) or not np.all(np.isfinite(scale)):
        raise ValueError("column_scale must be finite, non-zero and one per column")
    _, sigma, vt = np.linalg.svd(jac * scale, full_matrices=True)
    tol = (max(jac.shape) * np.finfo(float).eps) if rtol is None else float(rtol)
    rank = int(np.sum(sigma > tol * sigma[0])) if sigma.size and sigma[0] > 0 else 0
    null = (vt[rank:].T * scale[:, None]) if rank < n else np.zeros((n, 0))
    if null.shape[1]:
        null = null / np.linalg.norm(null, axis=0)
    condition = float(sigma[0] / sigma[n - 1]) if rank == n else float("inf")
    return {"singular_values": sigma, "rank": rank, "nullity": n - rank,
            "condition_number": condition, "null_space": null}

Singular values, numerical rank and null space of a (column-scaled) Jacobian.

\[J D = U \Sigma V^{\mathsf T}, \qquad \operatorname{rank} = \#\{\sigma_k > \tau\,\sigma_1\}, \qquad \kappa = \sigma_1/\sigma_{\min}\]
NameTypeUnitDescription
jacobianarray-likeany

(m, n) sensitivity matrix, ideally already weighted by the inverse measurement uncertainty so its rows are commensurate.

column_scalearray-like, optionalany

Diagonal of $D$, e.g. a typical magnitude per parameter, making the columns commensurate; default no scaling.

rtolfloat, optional-

Relative rank tolerance $\tau$; default $\max(m, n)\,\epsilon$.

Returns
—dictany

singular_values (descending), rank, nullity (n - rank), condition_number (inf when rank deficient), null_space ((n, nullity) basis in the unscaled parameters).

Raises

  • ValueError When the matrix is not finite or column_scale has a zero.
Numerical notes · References

Numerical notes

The SVD is taken of $JD$ itself, never of $J^{\mathsf T}J$, which would square the condition number – the practice of vaft.code.efit.analyze_efit_identifiability.

References

  1. G. H. Golub and C. F. Van Loan, Matrix Computations, 4th ed., Johns Hopkins (2013), Secs. 2.4 and 5.4.

results matching ""

    No results matching ""