1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
|
.. index:: technical details
===============
Technical Guide
===============
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 whether
:data:`value` is an instance of :class:`UFloat`, through
``isinstance(value, uncertainties.UFloat)``.
.. index:: pickling
.. index:: saving to file; number with uncertainty
.. index:: reading from file; number with uncertainty
.. _pickling:
Pickling
--------
The quantities with uncertainties created by the :mod:`uncertainties`
package can be `pickled <http://docs.python.org/library/pickle.html>`_
(they can be stored in a file, for instance).
If multiple variables are pickled together (including when pickling
:doc:`NumPy arrays <numpy_guide>`), 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+/-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.
.. index:: linear propagation of uncertainties
.. _linear_method:
Linear propagation of uncertainties
-----------------------------------
Constraints on the uncertainties
================================
This package calculates the standard deviation of mathematical
expressions through the linear approximation of `error propagation
theory`_.
The standard deviations and nominal values calculated by this package
are thus meaningful approximations as long as the final calculated
functions have **precise linear expansions in the region where the
probability distribution of their variables is the
largest**. Mathematically, this means that the linear terms of the
final calculated 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 example, calculating ``x*10`` with :data:`x` = 5±3 gives a
*perfect result* since the calculated function is linear. So does
``umath.atan(umath.tan(x))`` for :data:`x` = 0±1, since only the
*final* function counts (not an intermediate function like
:func:`tan`).
Another example is ``sin(0+/-0.01)``, for which :mod:`uncertainties`
yields a meaningful standard deviation since the sine is quite linear
over 0±0.01. However, ``cos(0+/-0.01)``, yields an approximate
standard deviation of 0 because it is parabolic around 0 instead of
linear; this might not be precise enough for all applications.
**More precise uncertainty estimates** can be obtained, if necessary,
with the soerp_ and mcerp_ packages. The soerp_ package performs
*second-order* error propagation: this is still quite fast, but the
standard deviation of higher-order functions like f(x) = x\ :sup:`3`
for x = 0±0.1 is calculated as being exactly zero (as with
:mod:`uncertainties`). The mcerp_ package performs Monte-Carlo
calculations, and can in principle yield very precise results, but
calculations are much slower than with approximation schemes.
.. index:: NaN
Not-a-number uncertainties
==========================
If linear `error propagation theory`_ cannot be applied, the functions
defined by :mod:`uncertainties` internally use a `not-a-number value
<http://en.wikipedia.org/wiki/Not_a_number>`_ (``nan``) for the
derivative.
As a consequence, it is possible for uncertainties to be ``nan``:
>>> umath.sqrt(ufloat(0, 1))
0.0+/-nan
This indicates that **the derivative required by linear error
propagation theory is not defined** (a Monte-Carlo calculation of the
resulting random variable is more adapted to this specific case).
However, the :mod:`uncertainties` package **correctly handles
perfectly precise numbers**, in this case:
>>> umath.sqrt(ufloat(0, 0))
0.0+/-0
gives the correct result, despite the fact that the derivative of the
square root is not defined in zero.
Mathematical definition of numbers with uncertainties
-----------------------------------------------------
.. index:: number with uncertainty; definition
.. index:: probability distribution
Mathematically, **numbers with uncertainties** are, in this package,
**probability distributions**. They are *not restricted* to normal
(Gaussian) distributions and can be **any distribution**. These
probability distributions are reduced to two numbers: a nominal value
and an uncertainty.
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`
and :attr:`std_dev` attributes).
.. index:: uncertainty; definition
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.
.. index:: nominal value; definition
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).
.. index:: comparison operators; technical details
.. _comparison_operators:
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.
.. index:: boolean value
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. Note that this is different
from the result of ``z = 3.14; t = 3.14; print z != t``, because
:data:`x` and :data:`y` are *random variables*, not pure numbers.
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
of approximate linearity made in this package on 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`: ``y-x`` correctly satisfies the assumption of linearity,
since it is a constant "random" function (with value 0.0002, even
though :data:`y` and :data:`x` are random). Thus, it is indeed true
that :data:`y` > :data:`x`.
.. _differentiation method:
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
essentially consists in keeping track of the value of derivatives, and
in automatically applying the `chain rule
<http://en.wikipedia.org/wiki/Chain_rule>`_. Automatic differentiation
is often faster than symbolic differentiation and more precise than
numerical differentiation (when used with analytical formulas, like in
:mod:`uncertainties`).
The derivatives of any expression can be obtained with
:mod:`uncertainties` in a simple way, as demonstrated in the :ref:`User
Guide <derivatives>`.
.. _variable_tracking:
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`.
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.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.
.. index:: number with uncertainty; classes, Variable class
.. index:: AffineScalarFunc class
.. _classes:
Python classes for variables and functions with uncertainty
-----------------------------------------------------------
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 on:
>>> type(umath.sin(x))
<class 'uncertainties.AffineScalarFunc'>
.. _automatic differentiation: http://en.wikipedia.org/wiki/Automatic_differentiation
.. _pydoc: http://docs.python.org/library/pydoc.html
.. _error propagation theory: http://en.wikipedia.org/wiki/Error_propagation
.. _soerp: https://pypi.python.org/pypi/soerp
.. _mcerp: https://pypi.python.org/pypi/mcerp
|