|
"""Tools for arithmetic error propagation.""" |
|
|
|
from itertools import repeat, combinations |
|
|
|
from sympy.core.add import Add |
|
from sympy.core.mul import Mul |
|
from sympy.core.power import Pow |
|
from sympy.core.singleton import S |
|
from sympy.core.symbol import Symbol |
|
from sympy.functions.elementary.exponential import exp |
|
from sympy.simplify.simplify import simplify |
|
from sympy.stats.symbolic_probability import RandomSymbol, Variance, Covariance |
|
from sympy.stats.rv import is_random |
|
|
|
_arg0_or_var = lambda var: var.args[0] if len(var.args) > 0 else var |
|
|
|
|
|
def variance_prop(expr, consts=(), include_covar=False): |
|
r"""Symbolically propagates variance (`\sigma^2`) for expressions. |
|
This is computed as as seen in [1]_. |
|
|
|
Parameters |
|
========== |
|
|
|
expr : Expr |
|
A SymPy expression to compute the variance for. |
|
consts : sequence of Symbols, optional |
|
Represents symbols that are known constants in the expr, |
|
and thus have zero variance. All symbols not in consts are |
|
assumed to be variant. |
|
include_covar : bool, optional |
|
Flag for whether or not to include covariances, default=False. |
|
|
|
Returns |
|
======= |
|
|
|
var_expr : Expr |
|
An expression for the total variance of the expr. |
|
The variance for the original symbols (e.g. x) are represented |
|
via instance of the Variance symbol (e.g. Variance(x)). |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy import symbols, exp |
|
>>> from sympy.stats.error_prop import variance_prop |
|
>>> x, y = symbols('x y') |
|
|
|
>>> variance_prop(x + y) |
|
Variance(x) + Variance(y) |
|
|
|
>>> variance_prop(x * y) |
|
x**2*Variance(y) + y**2*Variance(x) |
|
|
|
>>> variance_prop(exp(2*x)) |
|
4*exp(4*x)*Variance(x) |
|
|
|
References |
|
========== |
|
|
|
.. [1] https://en.wikipedia.org/wiki/Propagation_of_uncertainty |
|
|
|
""" |
|
args = expr.args |
|
if len(args) == 0: |
|
if expr in consts: |
|
return S.Zero |
|
elif is_random(expr): |
|
return Variance(expr).doit() |
|
elif isinstance(expr, Symbol): |
|
return Variance(RandomSymbol(expr)).doit() |
|
else: |
|
return S.Zero |
|
nargs = len(args) |
|
var_args = list(map(variance_prop, args, repeat(consts, nargs), |
|
repeat(include_covar, nargs))) |
|
if isinstance(expr, Add): |
|
var_expr = Add(*var_args) |
|
if include_covar: |
|
terms = [2 * Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand() \ |
|
for x, y in combinations(var_args, 2)] |
|
var_expr += Add(*terms) |
|
elif isinstance(expr, Mul): |
|
terms = [v/a**2 for a, v in zip(args, var_args)] |
|
var_expr = simplify(expr**2 * Add(*terms)) |
|
if include_covar: |
|
terms = [2*Covariance(_arg0_or_var(x), _arg0_or_var(y)).expand()/(a*b) \ |
|
for (a, b), (x, y) in zip(combinations(args, 2), |
|
combinations(var_args, 2))] |
|
var_expr += Add(*terms) |
|
elif isinstance(expr, Pow): |
|
b = args[1] |
|
v = var_args[0] * (expr * b / args[0])**2 |
|
var_expr = simplify(v) |
|
elif isinstance(expr, exp): |
|
var_expr = simplify(var_args[0] * expr**2) |
|
else: |
|
|
|
var_expr = Variance(expr) |
|
return var_expr |
|
|