lebigot/uncertainties

Subversion checkout URL

You can clone with
or
.
Fetching contributors…

Cannot retrieve contributors at this time

331 lines (241 sloc) 11.309 kB

Technical Guide

Mathematical definition of numbers with uncertainties

Mathematically, numbers with uncertainties are, in this package, probability distributions. They are not restricted to normal (Gaussian) distributions and can be any kind of distribution. These probability distributions are reduced to two numbers: a nominal value and a standard deviation.

Thus, both variables (:class:`Variable` objects) and the result of mathematical operations (:class:`AffineScalarFunc` objects) contain these two values (respectively in their :attr:`nominal_value` attribute and through their :meth:`std_dev` method).

The uncertainty of a number with uncertainty is simply defined in this package as the standard deviation of the underlying probability distribution.

The numbers with uncertainties manipulated by this package are assumed to have a probability distribution mostly contained around their nominal value, in an interval of about the size of their standard deviation. This should cover most practical cases.

A good choice of nominal value for a number with uncertainty is thus the median of its probability distribution, the location of highest probability, or the average value.

Probability distributions (random variables and calculation results) are printed as:

```nominal value +/- standard deviation
```

but this does not imply any property on the nominal value (beyond the fact that the nominal value is normally inside the region of high probability density), or that the probability distribution of the result is symmetrical (this is rarely strictly the case).

Tracking of random variables

This package keeps track of all the random variables a quantity depends on, which allows one to perform transparent calculations that yield correct uncertainties. For example:

```>>> x = ufloat((2, 0.1))
>>> a = 42
>>> poly = x**2 + a
>>> poly
46.0+/-0.4
>>> poly - x*x
42.0
```

Even though `x*x` has a non-zero uncertainty, the result has a zero uncertainty, because it is equal to :data:`a`.

However, only the dependence of quantities on random variables created by this module is tracked. Thus, if the variable :data:`a` above is modified, the value of :data:`poly` is not modified, as is usual in Python:

```>>> a = 123
>>> print poly
46.0+/-0.4  # Still equal to x**2 + 42, not x**2 + 123
```

Random variables can, on the other hand, have their uncertainty updated on the fly, because quantities with uncertainties (like :data:`poly`) keep track of them:

```>>> x.set_std_dev(0)
>>> print poly
46.0  # Zero uncertainty, now
```

As usual, Python keeps track of objects as long as they are used. Thus, redefining the value of :data:`x` does not change the fact that :data:`poly` depends on the quantity with uncertainty previously stored in :data:`x`:

```>>> x = 10000
>>> print poly
46.0  # Unchanged
```

These mechanisms make quantities with uncertainties behave mostly like regular numbers, while providing a fully transparent way of handling correlations between quantities.

Pickling

The quantities with uncertainties created by the :mod:`uncertainties` package can be pickled (they can be stored in a file, for instance).

If multiple variables are pickled together, their correlations are preserved:

```>>> import pickle
>>> x = ufloat((2, 0.1))
>>> y = 2*x
>>> p = pickle.dumps([x, y])  # Pickling to a string
>>> (x2, y2) = pickle.loads(p)  # Unpickling into new variables
>>> y2 - 2*x2
0.0
```

The final result is exactly zero because the unpickled variables :data:`x2` and :data:`y2` are completely correlated.

However, unpickling necessarily creates new variables that bear no relationship with the original variables (in fact, the pickled representation can be stored in a file and read from another program after the program that did the pickling is finished). Thus

```>>> x - x2
0.0+/-0.14142135623730953
```

which shows that the original variable :data:`x` and the new variable :data:`x2` are completely uncorrelated.

Uncertainties must be small

This package calculates the standard deviation of mathematical expressions through the linear approximation of error propagation theory. This is why this package also calculates partial :ref:`derivatives <derivatives>`.

The standard deviations and nominal values calculated by this package are thus meaningful approximations as long as the functions involved have precise linear expansions in the region where the probability distribution of their variables is the largest. It is therefore important that uncertainties be "small". Mathematically, this means that the linear terms of functions around the nominal values of their variables should be much larger than the remaining higher-order terms over the region of significant probability.

For instance, `sin(0+/-0.01)` yields a meaningful standard deviation since it is quite linear over 0±0.01. However, `cos(0+/-0.01)`, yields an approximate standard deviation of 0 (because around 0, the cosine is parabolic, not linear), which might not be precise enough for all applications.

Comparison operators

Comparison operations (>, ==, etc.) on numbers with uncertainties have a pragmatic semantics, in this package: numbers with uncertainties can be used wherever Python numbers are used, most of the time with a result identical to the one that would be obtained with their nominal value only. This allows code that runs with pure numbers to also work with numbers with uncertainties.

The boolean value (`bool(x)`, `if x …`) of a number with uncertainty :data:`x` is defined as the result of `x != 0`, as usual.

However, since the objects defined in this module represent probability distributions and not pure numbers, comparison operators are interpreted in a specific way.

The result of a comparison operation is defined so as to be essentially consistent with the requirement that uncertainties be small: the value of a comparison operation is True only if the operation yields True for all infinitesimal variations of its random variables around their nominal values, except, possibly, for an infinitely small number of cases.

Example:

```>>> x = ufloat((3.14, 0.01))
>>> x == x
True
```

because a sample from the probability distribution of :data:`x` is always equal to itself. However:

```>>> y = ufloat((3.14, 0.01))
>>> x != y
True
```

since :data:`x` and :data:`y` are independent random variables that almost always give a different value.

Similarly,

```>>> x = ufloat((3.14, 0.01))
>>> y = ufloat((3.00, 0.01))
>>> x > y
True
```

because :data:`x` is supposed to have a probability distribution largely contained in the 3.14±~0.01 interval, while :data:`y` is supposed to be well in the 3.00±~0.01 one: random samples of :data:`x` and :data:`y` will most of the time be such that the sample from :data:`x` is larger than the sample from :data:`y`. Therefore, it is natural to consider that for all practical purposes, `x > y`.

Since comparison operations are subject to the same constraints as other operations, as required by the :ref:`linear approximation <linear_method>` method, their result should be essentially constant over the regions of highest probability of their variables (this is the equivalent of the linearity of a real function, for boolean values). Thus, it is not meaningful to compare the following two independent variables, whose probability distributions overlap:

```>>> x = ufloat((3, 0.01))
>>> y = ufloat((3.0001, 0.01))
```

In fact the function (x, y) → (x > y) is not even continuous over the region where x and y are concentrated, which violates the assumption made in this package about operations involving numbers with uncertainties. Comparing such numbers therefore returns a boolean result whose meaning is undefined.

However, values with largely overlapping probability distributions can sometimes be compared unambiguously:

```>>> x = ufloat((3, 1))
>>> x
3.0+/-1.0
>>> y = x + 0.0002
>>> y
3.0002+/-1.0
>>> y > x
True
```

In fact, correlations guarantee that :data:`y` is always larger than :data:`x` (by 0.0002).

Classes

Testing whether an object is a number with uncertainty

The recommended way of testing whether :data:`value` carries an uncertainty handled by this module is by checking the value of `isinstance(value, UFloat)`.

Variables and functions

Numbers with uncertainties are represented through two different classes:

1. a class for independent random variables (:class:`Variable`, which inherits from :class:`UFloat`),
2. a class for functions that depend on independent variables (:class:`AffineScalarFunc`, aliased as :class:`UFloat`).

Documentation for these classes is available in their Python docstring, which can for instance displayed through pydoc.

The factory function :func:`ufloat` creates variables and thus returns a :class:`Variable` object:

```>>> x = ufloat((1, 0.1))
>>> type(x)
<class 'uncertainties.Variable'>
```

:class:`Variable` objects can be used as if they were regular Python numbers (the summation, etc. of these objects is defined).

Mathematical expressions involving numbers with uncertainties generally return :class:`AffineScalarFunc` objects, because they represent mathematical functions and not simple variables; these objects store all the variables they depend from:

```>>> type(umath.sin(x))
<class 'uncertainties.AffineScalarFunc'>
```

Differentiation method

The :mod:`uncertainties` package automatically calculates the derivatives required by linear error propagation theory.

Almost all the derivatives of the fundamental functions provided by :mod:`uncertainties` are obtained through a analytical formulas (the few mathematical functions that are instead differentiated through numerical approximation are listed in `umath.num_deriv_funcs`).

The derivatives of mathematical expressions are evaluated through a fast and precise method: :mod:`uncertainties` transparently implements automatic differentiation with reverse accumulation. This method is faster than symbolic differentiation and more precise than numerical differentiation.

The derivatives of any expression can be obtained in a simple way, as demonstrated in the :ref:`User Guide <derivatives>`.

Something went wrong with that request. Please try again.