1
2
3
4 '''
5 Calculations with full error propagation for quantities with uncertainties.
6 Derivatives can also be calculated.
7
8 Web user guide: http://packages.python.org/uncertainties/.
9
10 Example of possible calculation: (0.2 +/- 0.01)**2 = 0.04 +/- 0.004.
11
12 Correlations between expressions are correctly taken into account (for
13 instance, with x = 0.2+/-0.01, 2*x-x-x is exactly zero, as is y-x-x
14 with y = 2*x).
15
16 Examples:
17
18 import uncertainties
19 from uncertainties import ufloat
20 from uncertainties.umath import * # sin(), etc.
21
22 # Mathematical operations:
23 x = ufloat((0.20, 0.01)) # x = 0.20+/-0.01
24 x = ufloat("0.20+/-0.01") # Other representation
25 x = ufloat("0.20(1)") # Other representation
26 x = ufloat("0.20") # Implicit uncertainty of +/-1 on the last digit
27 print x**2 # Square: prints "0.04+/-0.004"
28 print sin(x**2) # Prints "0.0399...+/-0.00399..."
29
30 print x.std_score(0.17) # Prints "-3.0": deviation of -3 sigmas
31
32 # Access to the nominal value, and to the uncertainty:
33 square = x**2 # Square
34 print square # Prints "0.04+/-0.004"
35 print square.nominal_value # Prints "0.04"
36 print square.std_dev() # Prints "0.004..."
37
38 print square.derivatives[x] # Partial derivative: 0.4 (= 2*0.20)
39
40 # Correlations:
41 u = ufloat((1, 0.05), "u variable") # Tag
42 v = ufloat((10, 0.1), "v variable")
43 sum_value = u+v
44
45 u.set_std_dev(0.1) # Standard deviations can be updated on the fly
46 print sum_value - u - v # Prints "0.0" (exact result)
47
48 # List of all sources of error:
49 print sum_value # Prints "11+/-0.1414..."
50 for (var, error) in sum_value.error_components().iteritems():
51 print "%s: %f" % (var.tag, error) # Individual error components
52
53 # Covariance matrices:
54 cov_matrix = uncertainties.covariance_matrix([u, v, sum_value])
55 print cov_matrix # 3x3 matrix
56
57 # Correlated variables can be constructed from a covariance matrix, if
58 # NumPy is available:
59 (u2, v2, sum2) = uncertainties.correlated_values([1, 10, 11],
60 cov_matrix)
61 print u2 # Value and uncertainty of u: correctly recovered (1+/-0.1)
62 print uncertainties.covariance_matrix([u2, v2, sum2]) # == cov_matrix
63
64 - The main function provided by this module is ufloat, which creates
65 numbers with uncertainties (Variable objects). Variable objects can
66 be used as if they were regular Python numbers. The main attributes
67 and methods of Variable objects are defined in the documentation of
68 the Variable class.
69
70 - Valid operations on numbers with uncertainties include basic
71 mathematical functions (addition, etc.).
72
73 Most operations from the standard math module (sin, etc.) can be applied
74 on numbers with uncertainties by using their generalization from the
75 uncertainties.umath module:
76
77 from uncertainties.umath import sin
78 print sin(ufloat("1+/-0.01")) # 0.841...+/-0.005...
79 print sin(1) # umath.sin() also works on floats, exactly like math.sin()
80
81 Logical operations (>, ==, etc.) are also supported.
82
83 Basic operations on NumPy arrays or matrices of numbers with
84 uncertainties can be performed:
85
86 2*numpy.array([ufloat((1, 0.01)), ufloat((2, 0.1))])
87
88 More complex operations on NumPy arrays can be performed through the
89 dedicated uncertainties.unumpy sub-module (see its documentation).
90
91 Calculations that are performed through non-Python code (Fortran, C,
92 etc.) can handle numbers with uncertainties instead of floats through
93 the provided wrap() wrapper:
94
95 import uncertainties
96
97 # wrapped_f is a version of f that can take arguments with
98 # uncertainties, even if f only takes floats:
99 wrapped_f = uncertainties.wrap(f)
100
101 If some derivatives of the wrapped function f are known (analytically,
102 or numerically), they can be given to wrap()--see the documentation
103 for wrap().
104
105 - Utility functions are also provided: the covariance matrix between
106 random variables can be calculated with covariance_matrix(), or used
107 as input for the definition of correlated quantities (correlated_values()
108 function--defined only if the NumPy module is available).
109
110 - Mathematical expressions involving numbers with uncertainties
111 generally return AffineScalarFunc objects, which also print as a value
112 with uncertainty. Their most useful attributes and methods are
113 described in the documentation for AffineScalarFunc. Note that
114 Variable objects are also AffineScalarFunc objects. UFloat is an
115 alias for AffineScalarFunc, provided as a convenience: testing whether
116 a value carries an uncertainty handled by this module should be done
117 with insinstance(my_value, UFloat).
118
119 - Mathematically, numbers with uncertainties are, in this package,
120 probability distributions. These probabilities are reduced to two
121 numbers: a nominal value and an uncertainty. Thus, both variables
122 (Variable objects) and the result of mathematical operations
123 (AffineScalarFunc objects) contain these two values (respectively in
124 their nominal_value attribute and through their std_dev() method).
125
126 The uncertainty of a number with uncertainty is simply defined in
127 this package as the standard deviation of the underlying probability
128 distribution.
129
130 The numbers with uncertainties manipulated by this package are assumed
131 to have a probability distribution mostly contained around their
132 nominal value, in an interval of about the size of their standard
133 deviation. This should cover most practical cases. A good choice of
134 nominal value for a number with uncertainty is thus the median of its
135 probability distribution, the location of highest probability, or the
136 average value.
137
138 - When manipulating ensembles of numbers, some of which contain
139 uncertainties, it can be useful to access the nominal value and
140 uncertainty of all numbers in a uniform manner:
141
142 x = ufloat("3+/-0.1")
143 print nominal_value(x) # Prints 3
144 print std_dev(x) # Prints 0.1
145 print nominal_value(3) # Prints 3: nominal_value works on floats
146 print std_dev(3) # Prints 0: std_dev works on floats
147
148 - Probability distributions (random variables and calculation results)
149 are printed as:
150
151 nominal value +/- standard deviation
152
153 but this does not imply any property on the nominal value (beyond the
154 fact that the nominal value is normally inside the region of high
155 probability density), or that the probability distribution of the
156 result is symmetrical (this is rarely strictly the case).
157
158 - Linear approximations of functions (around the nominal values) are
159 used for the calculation of the standard deviation of mathematical
160 expressions with this package.
161
162 The calculated standard deviations and nominal values are thus
163 meaningful approximations as long as the functions involved have
164 precise linear expansions in the region where the probability
165 distribution of their variables is the largest. It is therefore
166 important that uncertainties be small. Mathematically, this means
167 that the linear term of functions around the nominal values of their
168 variables should be much larger than the remaining higher-order terms
169 over the region of significant probability.
170
171 For instance, sin(0+/-0.01) yields a meaningful standard deviation
172 since it is quite linear over 0+/-0.01. However, cos(0+/-0.01) yields
173 an approximate standard deviation of 0 (because the cosine is not well
174 approximated by a line around 0), which might not be precise enough
175 for all applications.
176
177 - Comparison operations (>, ==, etc.) on numbers with uncertainties
178 have a pragmatic semantics, in this package: numbers with
179 uncertainties can be used wherever Python numbers are used, most of
180 the time with a result identical to the one that would be obtained
181 with their nominal value only. However, since the objects defined in
182 this module represent probability distributions and not pure numbers,
183 comparison operator are interpreted in a specific way.
184
185 The result of a comparison operation ("==", ">", etc.) is defined so as
186 to be essentially consistent with the requirement that uncertainties
187 be small: the value of a comparison operation is True only if the
188 operation yields True for all infinitesimal variations of its random
189 variables, except, possibly, for an infinitely small number of cases.
190
191 Example:
192
193 "x = 3.14; y = 3.14" is such that x == y
194
195 but
196
197 x = ufloat((3.14, 0.01))
198 y = ufloat((3.14, 0.01))
199
200 is not such that x == y, since x and y are independent random
201 variables that almost never give the same value. However, x == x
202 still holds.
203
204 The boolean value (bool(x), "if x...") of a number with uncertainty x
205 is the result of x != 0.
206
207 - The uncertainties package is for Python 2.5 and above.
208
209 - This package contains tests. They can be run either manually or
210 automatically with the nose unit testing framework (nosetests).
211
212 (c) 2009-2013 by Eric O. LEBIGOT (EOL) <eric.lebigot@normalesup.org>.
213 Please send feature requests, bug reports, or feedback to this address.
214
215 Please support future development by donating $5 or more through PayPal!
216
217 This software is released under a dual license. (1) The BSD license.
218 (2) Any other license, as long as it is obtained from the original
219 author.'''
220
221
222
223
224
225
226
227
228
229 from __future__ import division
230
231 import re
232 import math
233 from math import sqrt, log
234 import copy
235 import warnings
236
237
238 __version_info__ = (1, 9)
239 __version__ = '.'.join(map(str, __version_info__))
240
241 __author__ = 'Eric O. LEBIGOT (EOL) <eric.lebigot@normalesup.org>'
242
243
244
245 __all__ = [
246
247
248
249 'ufloat',
250
251
252 'nominal_value',
253 'std_dev',
254
255
256 'covariance_matrix',
257
258
259
260
261
262 'UFloat',
263
264
265
266 'wrap',
267
268
269
270
271
272 'partial_derivative'
273 ]
274
275
276
277 -def set_doc(doc_string):
278 """
279 Decorator function that sets the docstring to the given text.
280
281 It is useful for functions whose docstring is calculated
282 (including string substitutions).
283 """
284 def set_doc_string(func):
285 func.__doc__ = doc_string
286 return func
287 return set_doc_string
288
289
290
291
292 CONSTANT_TYPES = (float, int, complex)
298 '''
299 Warns the user with the given message, by issuing a
300 DeprecationWarning.
301 '''
302 warnings.warn(message, DeprecationWarning, stacklevel=2)
303
304
305
306
307
308
309
310 try:
311 import numpy
312 except ImportError:
313 pass
314 else:
315
316
317 CONSTANT_TYPES += (numpy.number,)
385
386 __all__.append('correlated_values')
413
414 __all__.append('correlated_values_norm')
415
416
417
418
419
420
421 -class NotUpcast(Exception):
422 'Raised when an object cannot be converted to a number with uncertainty'
423
425 """
426 Transforms x into a constant affine scalar function
427 (AffineScalarFunc), unless it is already an AffineScalarFunc (in
428 which case x is returned unchanged).
429
430 Raises an exception unless 'x' belongs to some specific classes of
431 objects that are known not to depend on AffineScalarFunc objects
432 (which then cannot be considered as constants).
433 """
434
435 if isinstance(x, AffineScalarFunc):
436 return x
437
438
439 if isinstance(x, CONSTANT_TYPES):
440
441 return AffineScalarFunc(x, {})
442
443
444 raise NotUpcast("%s cannot be converted to a number with"
445 " uncertainty" % type(x))
446
448 """
449 Returns a function that numerically calculates the partial
450 derivative of function f with respect to its argument number
451 param_num.
452
453 The step parameter represents the shift of the parameter used in
454 the numerical approximation.
455 """
456
457 def partial_derivative_of_f(*args, **kws):
458 """
459 Partial derivative, calculated with the (-epsilon, +epsilon)
460 method, which is more precise than the (0, +epsilon) method.
461 """
462
463 param_kw = None
464 if '__param__kw__' in kws:
465 param_kw = kws.pop('__param__kw__')
466 shifted_args = list(args)
467 shifted_kws = {}
468 for k, v in kws.items():
469 shifted_kws[k] = v
470 step = 1.e-8
471 if param_kw in shifted_kws:
472 step = step*abs(shifted_kws[param_kw])
473 elif param_num < len(shifted_args):
474
475
476 step = step*abs(shifted_args[param_num])
477
478 if param_kw in shifted_kws:
479 shifted_kws[param_kw] += step
480 elif param_num < len(shifted_args):
481 shifted_args[param_num] += step
482
483 shifted_f_plus = f(*shifted_args, **shifted_kws)
484
485 if param_kw in shifted_kws:
486 shifted_kws[param_kw] -= 2*step
487 elif param_num < len(shifted_args):
488 shifted_args[param_num] -= 2*step
489 shifted_f_minus = f(*shifted_args, **shifted_kws)
490
491 return (shifted_f_plus - shifted_f_minus)/2/step
492
493 return partial_derivative_of_f
494
496 """
497 Convenient access to the partial derivatives of a function,
498 calculated numerically.
499 """
500
501
502
504 """
505 'function' is the function whose derivatives can be computed.
506 """
507 self._function = function
508
510 """
511 Returns the n-th numerical derivative of the function.
512 """
513 return partial_derivative(self._function, n)
514
515 -def wrap(f, derivatives_iter=None):
516 """
517 Wraps a function f into a function that also accepts numbers with
518 uncertainties (UFloat objects) and returns a number with
519 uncertainties. Doing so may be necessary when function f cannot
520 be expressed analytically (with uncertainties-compatible operators
521 and functions like +, *, umath.sin(), etc.).
522
523 f must return a scalar (not a list, etc.).
524
525 In the wrapped function, the standard Python scalar arguments of f
526 (float, int, etc.) can be replaced by numbers with
527 uncertainties. The result will contain the appropriate
528 uncertainty.
529
530 If no argument to the wrapped function has an uncertainty, f
531 simply returns its usual, scalar result.
532
533 If supplied, derivatives_iter can be an iterable that generally
534 contains functions; each successive function is the partial
535 derivative of f with respect to the corresponding variable (one
536 function for each argument of f, which takes as many arguments as
537 f). If instead of a function, an element of derivatives_iter
538 contains None, then it is automatically replaced by the relevant
539 numerical derivative; this can be used for non-scalar arguments of
540 f (like string arguments).
541
542 If derivatives_iter is None, or if derivatives_iter contains a
543 fixed (and finite) number of elements, then any missing derivative
544 is calculated numerically.
545
546 An infinite number of derivatives can be specified by having
547 derivatives_iter be an infinite iterator; this can for instance
548 be used for specifying the derivatives of functions with a
549 undefined number of argument (like sum(), whose partial
550 derivatives all return 1).
551
552 Example (for illustration purposes only, as
553 uncertainties.umath.sin() runs faster than the examples that
554 follow): wrap(math.sin) is a sine function that can be applied to
555 numbers with uncertainties. Its derivative will be calculated
556 numerically. wrap(math.sin, [None]) would have produced the same
557 result. wrap(math.sin, [math.cos]) is the same function, but with
558 an analytically defined derivative.
559 """
560
561 if derivatives_iter is None:
562 derivatives_iter = NumericalDerivatives(f)
563 else:
564
565
566
567
568 try:
569 len(derivatives_iter)
570 except TypeError:
571 pass
572 else:
573 derivatives_iter = [
574 partial_derivative(f, k) if derivative is None
575 else derivative
576 for (k, derivative) in enumerate(derivatives_iter)]
577
578
579
580 @set_doc("""\
581 Version of %s(...) that returns an affine approximation
582 (AffineScalarFunc object), if its result depends on variables
583 (Variable objects). Otherwise, returns a simple constant (when
584 applied to constant arguments).
585
586 Warning: arguments of the function that are not AffineScalarFunc
587 objects must not depend on uncertainties.Variable objects in any
588 way. Otherwise, the dependence of the result in
589 uncertainties.Variable objects will be incorrect.
590
591 Original documentation:
592 %s""" % (f.__name__, f.__doc__))
593 def f_with_affine_output(*args, **kwargs):
594
595
596 try:
597 old_funcs = map(to_affine_scalar, args)
598 aff_funcs = [to_affine_scalar(a) for a in args]
599 aff_kws = kwargs
600 aff_varkws = []
601 for key, val in kwargs.items():
602 if isinstance(val, Variable):
603 aff_kws[key] = to_affine_scalar(val)
604 aff_varkws.append(key)
605
606 except NotUpcast:
607
608
609
610
611
612
613
614
615 if any(isinstance(arg, AffineScalarFunc) for arg in args):
616
617
618
619
620 return NotImplemented
621 else:
622
623
624
625
626
627 return f(*args, **kwargs)
628
629
630
631 args_values = [e.nominal_value for e in aff_funcs]
632 kw_values = {}
633 for key, val in aff_kws.items():
634 kw_values[key] = val
635 if key in aff_varkws:
636 kw_values[key] = val.nominal_value
637 f_nominal_value = f(*args_values, **kw_values)
638
639
640
641
642 variables = set()
643 for expr in aff_funcs:
644 variables |= set(expr.derivatives)
645 for vname in aff_varkws:
646 variables |= set(aff_kws[vname].derivatives)
647
648
649
650
651
652
653
654
655
656
657
658
659 if not variables or isinstance(f_nominal_value, bool):
660 return f_nominal_value
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692 derivatives_wrt_args = []
693 for (arg, derivative) in zip(aff_funcs, derivatives_iter):
694 derivatives_wrt_args.append(derivative(*args_values, **aff_kws)
695 if arg.derivatives
696 else 0)
697
698
699 kws_values = []
700 for vname in aff_varkws:
701 kws_values.append( aff_kws[vname].nominal_value)
702 for (vname, derivative) in zip(aff_varkws, derivatives_iter):
703 derivatives_wrt_args.append(derivative(__param__kw__=vname,
704 **kw_values)
705 if aff_kws[vname].derivatives
706 else 0)
707
708
709
710
711
712
713 derivatives_wrt_vars = dict((var, 0.) for var in variables)
714
715
716
717
718 for (func, f_derivative) in zip(aff_funcs, derivatives_wrt_args):
719 for (var, func_derivative) in func.derivatives.items():
720 derivatives_wrt_vars[var] += f_derivative * func_derivative
721
722 for (vname, f_derivative) in zip(aff_varkws, derivatives_wrt_args):
723 func = aff_kws[vname]
724 for (var, func_derivative) in func.derivatives.items():
725 derivatives_wrt_vars[var] += f_derivative * func_derivative
726
727
728 return AffineScalarFunc(f_nominal_value, derivatives_wrt_vars)
729
730
731
732 f_with_affine_output.__name__ = f.__name__
733
734 return f_with_affine_output
735
737 """
738 Takes an operator op(x, y) and wraps it.
739
740 The constructed operator returns func(x, to_affine_scalar(y)) if y
741 can be upcast with to_affine_scalar(); otherwise, it returns
742 NotImplemented.
743
744 Thus, func() is only called on two AffineScalarFunc objects, if
745 its first argument is an AffineScalarFunc.
746 """
747
748 def op_on_upcast_args(x, y):
749 """
750 Returns %s(self, to_affine_scalar(y)) if y can be upcast
751 through to_affine_scalar. Otherwise returns NotImplemented.
752 """ % func.__name__
753
754 try:
755 y_with_uncert = to_affine_scalar(y)
756 except NotUpcast:
757
758
759
760
761 return NotImplemented
762 else:
763 return func(x, y_with_uncert)
764
765 return op_on_upcast_args
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783 -def _eq_on_aff_funcs(self, y_with_uncert):
784 """
785 __eq__ operator, assuming that both self and y_with_uncert are
786 AffineScalarFunc objects.
787 """
788 difference = self - y_with_uncert
789
790
791 return not(difference._nominal_value or difference.std_dev())
792
794 """
795 __ne__ operator, assuming that both self and y_with_uncert are
796 AffineScalarFunc objects.
797 """
798
799 return not _eq_on_aff_funcs(self, y_with_uncert)
800
802 """
803 __gt__ operator, assuming that both self and y_with_uncert are
804 AffineScalarFunc objects.
805 """
806 return self._nominal_value > y_with_uncert._nominal_value
807
809 """
810 __ge__ operator, assuming that both self and y_with_uncert are
811 AffineScalarFunc objects.
812 """
813
814 return (_gt_on_aff_funcs(self, y_with_uncert)
815 or _eq_on_aff_funcs(self, y_with_uncert))
816
818 """
819 __lt__ operator, assuming that both self and y_with_uncert are
820 AffineScalarFunc objects.
821 """
822 return self._nominal_value < y_with_uncert._nominal_value
823
825 """
826 __le__ operator, assuming that both self and y_with_uncert are
827 AffineScalarFunc objects.
828 """
829
830 return (_lt_on_aff_funcs(self, y_with_uncert)
831 or _eq_on_aff_funcs(self, y_with_uncert))
832
836 """
837 Affine functions that support basic mathematical operations
838 (addition, etc.). Such functions can for instance be used for
839 representing the local (linear) behavior of any function.
840
841 This class is mostly meant to be used internally.
842
843 This class can also be used to represent constants.
844
845 The variables of affine scalar functions are Variable objects.
846
847 AffineScalarFunc objects include facilities for calculating the
848 'error' on the function, from the uncertainties on its variables.
849
850 Main attributes and methods:
851
852 - nominal_value, std_dev(): value at the origin / nominal value,
853 and standard deviation.
854
855 - error_components(): error_components()[x] is the error due to
856 Variable x.
857
858 - derivatives: derivatives[x] is the (value of the) derivative
859 with respect to Variable x. This attribute is a dictionary
860 whose keys are the Variable objects on which the function
861 depends.
862
863 All the Variable objects on which the function depends are in
864 'derivatives'.
865
866 - std_score(x): position of number x with respect to the
867 nominal value, in units of the standard deviation.
868 """
869
870
871 __slots__ = ('_nominal_value', 'derivatives')
872
873
874
875
876
877
878
879
880 - def __init__(self, nominal_value, derivatives):
881 """
882 nominal_value -- value of the function at the origin.
883 nominal_value must not depend in any way of the Variable
884 objects in 'derivatives' (the value at the origin of the
885 function being defined is a constant).
886
887 derivatives -- maps each Variable object on which the function
888 being defined depends to the value of the derivative with
889 respect to that variable, taken at the nominal value of all
890 variables.
891
892 Warning: the above constraint is not checked, and the user is
893 responsible for complying with it.
894 """
895
896
897
898
899
900
901
902
903
904 self._nominal_value = float(nominal_value)
905 self.derivatives = derivatives
906
907
908
909 @property
911 "Nominal value of the random number."
912 return self._nominal_value
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
934 """
935 Equivalent to self != 0.
936 """
937
938
939
940
941
942
943 return self != 0.
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974 __eq__ = _force_aff_func_args(_eq_on_aff_funcs)
975
976 __ne__ = _force_aff_func_args(_ne_on_aff_funcs)
977 __gt__ = _force_aff_func_args(_gt_on_aff_funcs)
978
979
980
981
982 __ge__ = _force_aff_func_args(_ge_on_aff_funcs)
983
984 __lt__ = _force_aff_func_args(_lt_on_aff_funcs)
985 __le__ = _force_aff_func_args(_le_on_aff_funcs)
986
987
988
989
990
992 """
993 Individual components of the standard deviation of the affine
994 function (in absolute value), returned as a dictionary with
995 Variable objects as keys.
996
997 This method assumes that the derivatives contained in the
998 object take scalar values (and are not a tuple, like what
999 math.frexp() returns, for instance).
1000 """
1001
1002
1003 error_components = {}
1004 for (variable, derivative) in self.derivatives.items():
1005
1006 error_components[variable] = abs(derivative*variable._std_dev)
1007
1008 return error_components
1009
1011 """
1012 Standard deviation of the affine function.
1013
1014 This method assumes that the function returns scalar results.
1015
1016 This returned standard deviation depends on the current
1017 standard deviations [std_dev()] of the variables (Variable
1018 objects) involved.
1019 """
1020
1021
1022
1023
1024
1025
1026 return sqrt(sum(
1027 delta**2 for delta in self.error_components().itervalues()))
1028
1030 """
1031 Uses the to_string() conversion function on both the nominal
1032 value and the standard deviation, and returns a string that
1033 describes them.
1034
1035 to_string() is typically repr() or str().
1036 """
1037
1038 (nominal_value, std_dev) = (self._nominal_value, self.std_dev())
1039
1040
1041
1042
1043
1044
1045
1046
1047 return ("%s+/-%s" % (to_string(nominal_value), to_string(std_dev))
1048 if std_dev
1049 else to_string(nominal_value))
1050
1053
1056
1058 """
1059 Returns 'value' - nominal value, in units of the standard
1060 deviation.
1061
1062 Raises a ValueError exception if the standard deviation is zero.
1063 """
1064 try:
1065
1066
1067 return (value - self._nominal_value) / self.std_dev()
1068 except ZeroDivisionError:
1069 raise ValueError("The standard deviation is zero:"
1070 " undefined result.")
1071
1073 """
1074 Hook for the standard copy module.
1075
1076 The returned AffineScalarFunc is a completely fresh copy,
1077 which is fully independent of any variable defined so far.
1078 New variables are specially created for the returned
1079 AffineScalarFunc object.
1080 """
1081 return AffineScalarFunc(
1082 self._nominal_value,
1083 dict((copy.deepcopy(var), deriv)
1084 for (var, deriv) in self.derivatives.items()))
1085
1087 """
1088 Hook for the pickle module.
1089 """
1090 obj_slot_values = dict((k, getattr(self, k)) for k in
1091
1092
1093 AffineScalarFunc.__slots__)
1094 return obj_slot_values
1095
1097 """
1098 Hook for the pickle module.
1099 """
1100 for (name, value) in data_dict.items():
1101 setattr(self, name, value)
1102
1103
1104 UFloat = AffineScalarFunc
1107
1108 """
1109 Returns operators with a reflection, along with their derivatives
1110 (for float operands).
1111 """
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130 derivatives_list = {
1131 'add': ("1.", "1."),
1132
1133
1134
1135
1136 'div': ("1/y", "-x/y**2"),
1137 'floordiv': ("0.", "0."),
1138
1139
1140 'mod': ("1.", "partial_derivative(float.__mod__, 1)(x, y)"),
1141 'mul': ("y", "x"),
1142 'pow': ("y*x**(y-1)", "log(x)*x**y"),
1143 'sub': ("1.", "-1."),
1144 'truediv': ("1/y", "-x/y**2")
1145 }
1146
1147
1148 ops_with_reflection = {}
1149 for (op, derivatives) in derivatives_list.items():
1150 ops_with_reflection[op] = [
1151 eval("lambda x, y: %s" % expr) for expr in derivatives ]
1152
1153 ops_with_reflection["r"+op] = [
1154 eval("lambda y, x: %s" % expr) for expr in reversed(derivatives)]
1155
1156 return ops_with_reflection
1157
1158
1159 _ops_with_reflection = get_ops_with_reflection()
1160
1161
1162 _modified_operators = []
1163 _modified_ops_with_reflection = []
1166 """
1167 Adds many operators (__add__, etc.) to the AffineScalarFunc class.
1168 """
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182 simple_numerical_operators_derivatives = {
1183 'abs': lambda x: 1. if x>=0 else -1.,
1184 'neg': lambda x: -1.,
1185 'pos': lambda x: 1.,
1186 'trunc': lambda x: 0.
1187 }
1188
1189 for (op, derivative) in (
1190 simple_numerical_operators_derivatives.items()):
1191
1192 attribute_name = "__%s__" % op
1193
1194
1195
1196 try:
1197 setattr(AffineScalarFunc, attribute_name,
1198 wrap(getattr(float, attribute_name),
1199 [derivative]))
1200 except AttributeError:
1201 pass
1202 else:
1203 _modified_operators.append(op)
1204
1205
1206
1207
1208 for (op, derivatives) in _ops_with_reflection.items():
1209 attribute_name = '__%s__' % op
1210
1211
1212
1213 try:
1214 setattr(AffineScalarFunc, attribute_name,
1215 wrap(getattr(float, attribute_name), derivatives))
1216 except AttributeError:
1217 pass
1218 else:
1219 _modified_ops_with_reflection.append(op)
1220
1221
1222
1223
1224 for coercion_type in ('complex', 'int', 'long', 'float'):
1225 def raise_error(self):
1226 raise TypeError("can't convert an affine function (%s)"
1227 ' to %s; use x.nominal_value'
1228
1229 % (self.__class__, coercion_type))
1230
1231 setattr(AffineScalarFunc, '__%s__' % coercion_type, raise_error)
1232
1233 add_operators_to_AffineScalarFunc()
1236 """
1237 Representation of a float-like scalar random variable, along with
1238 its uncertainty.
1239
1240 Objects are meant to represent variables that are independent from
1241 each other (correlations are handled through the AffineScalarFunc
1242 class).
1243 """
1244
1245
1246 __slots__ = ('_std_dev', 'tag')
1247
1248 - def __init__(self, value, std_dev, tag=None):
1249 """
1250 The nominal value and the standard deviation of the variable
1251 are set. These values must be scalars.
1252
1253 'tag' is a tag that the user can associate to the variable. This
1254 is useful for tracing variables.
1255
1256 The meaning of the nominal value is described in the main
1257 module documentation.
1258 """
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269 value = float(value)
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282 super(Variable, self).__init__(value, {self: 1.})
1283
1284
1285
1286
1287
1288
1289
1290 assert std_dev >= 0, "the error must be a positive number"
1291
1292
1293 self._std_dev = std_dev
1294
1295 self.tag = tag
1296
1297
1298
1299
1300
1302 """
1303 Updates the standard deviation of the variable to a new value.
1304 """
1305
1306
1307
1308
1309
1310 self._std_dev = value
1311
1312
1314 """
1315 Uses the to_string() conversion function on both the nominal
1316 value and standard deviation and returns a string that
1317 describes the number.
1318
1319 to_string() is typically repr() or str().
1320 """
1321 num_repr = super(Variable, self)._general_representation(to_string)
1322
1323
1324
1325
1326
1327
1328 return (num_repr if ((self.tag is None) or (to_string != repr))
1329 else "< %s = %s >" % (self.tag, num_repr))
1330
1332
1333
1334
1335
1336 return id(self)
1337
1339 """
1340 Hook for the standard copy module.
1341 """
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352 return Variable(self.nominal_value, self.std_dev(), self.tag)
1353
1355 """
1356 Hook for the standard copy module.
1357
1358 A new variable is created.
1359 """
1360
1361
1362
1363
1364
1365
1366
1367 return self.__copy__()
1368
1370 """
1371 Hook for the standard pickle module.
1372 """
1373 obj_slot_values = dict((k, getattr(self, k)) for k in self.__slots__)
1374 obj_slot_values.update(AffineScalarFunc.__getstate__(self))
1375
1376 return obj_slot_values
1377
1379 """
1380 Hook for the standard pickle module.
1381 """
1382 for (name, value) in data_dict.items():
1383 setattr(self, name, value)
1384
1390 """
1391 Returns the nominal value of x if it is a quantity with
1392 uncertainty (i.e., an AffineScalarFunc object); otherwise, returns
1393 x unchanged.
1394
1395 This utility function is useful for transforming a series of
1396 numbers, when only some of them generally carry an uncertainty.
1397 """
1398
1399 return x.nominal_value if isinstance(x, AffineScalarFunc) else x
1400
1402 """
1403 Returns the standard deviation of x if it is a quantity with
1404 uncertainty (i.e., an AffineScalarFunc object); otherwise, returns
1405 the float 0.
1406
1407 This utility function is useful for transforming a series of
1408 numbers, when only some of them generally carry an uncertainty.
1409 """
1410
1411 return x.std_dev() if isinstance(x, AffineScalarFunc) else 0.
1412
1414 """
1415 Returns a matrix that contains the covariances between the given
1416 sequence of numbers with uncertainties (AffineScalarFunc objects).
1417 The resulting matrix implicitly depends on their ordering in
1418 'nums_with_uncert'.
1419
1420 The covariances are floats (never int objects).
1421
1422 The returned covariance matrix is the exact linear approximation
1423 result, if the nominal values of the numbers with uncertainties
1424 and of their variables are their mean. Otherwise, the returned
1425 covariance matrix should be close to its linear approximation
1426 value.
1427
1428 The returned matrix is a list of lists.
1429 """
1430
1431
1432 covariance_matrix = []
1433 for (i1, expr1) in enumerate(nums_with_uncert):
1434 derivatives1 = expr1.derivatives
1435 vars1 = set(derivatives1)
1436 coefs_expr1 = []
1437 for (i2, expr2) in enumerate(nums_with_uncert[:i1+1]):
1438 derivatives2 = expr2.derivatives
1439 coef = 0.
1440 for var in vars1.intersection(derivatives2):
1441
1442
1443 coef += (derivatives1[var]*derivatives2[var]*var._std_dev**2)
1444 coefs_expr1.append(coef)
1445 covariance_matrix.append(coefs_expr1)
1446
1447
1448 for (i, covariance_coefs) in enumerate(covariance_matrix):
1449 covariance_coefs.extend(covariance_matrix[j][i]
1450 for j in range(i+1, len(covariance_matrix)))
1451
1452 return covariance_matrix
1453
1454 try:
1455 import numpy
1456 except ImportError:
1457 pass
1458 else:
1460 '''
1461 Returns the correlation matrix of the given sequence of
1462 numbers with uncertainties, as a NumPy array of floats.
1463 '''
1464
1465 cov_mat = numpy.array(covariance_matrix(nums_with_uncert))
1466
1467 std_devs = numpy.sqrt(cov_mat.diagonal())
1468
1469 return cov_mat/std_devs/std_devs[numpy.newaxis].T
1470
1471 __all__.append('correlation_matrix')
1472
1473
1474
1475
1476 POSITIVE_DECIMAL_UNSIGNED = r'(\d+)(\.\d*)?'
1477
1478
1479
1480 NUMBER_WITH_UNCERT_RE_STR = '''
1481 ([+-])? # Sign
1482 %s # Main number
1483 (?:\(%s\))? # Optional uncertainty
1484 ([eE][+-]?\d+)? # Optional exponent
1485 ''' % (POSITIVE_DECIMAL_UNSIGNED, POSITIVE_DECIMAL_UNSIGNED)
1486
1487 NUMBER_WITH_UNCERT_RE = re.compile(
1488 "^%s$" % NUMBER_WITH_UNCERT_RE_STR, re.VERBOSE)
1491 """
1492 Returns (value, error) from a string representing a number with
1493 uncertainty like 12.34(5), 12.34(142), 12.5(3.4) or 12.3(4.2)e3.
1494 If no parenthesis is given, an uncertainty of one on the last
1495 digit is assumed.
1496
1497 Raises ValueError if the string cannot be parsed.
1498 """
1499
1500 match = NUMBER_WITH_UNCERT_RE.search(representation)
1501
1502 if match:
1503
1504
1505
1506 (sign, main_int, main_dec, uncert_int, uncert_dec,
1507 exponent) = match.groups()
1508 else:
1509 raise ValueError("Unparsable number representation: '%s'."
1510 " Was expecting a string of the form 1.23(4)"
1511 " or 1.234" % representation)
1512
1513
1514 value = float(''.join((sign or '',
1515 main_int,
1516 main_dec or '.0',
1517 exponent or '')))
1518
1519 if uncert_int is None:
1520
1521
1522 uncert_int = '1'
1523
1524
1525 if uncert_dec is not None:
1526 uncert = float("%s%s" % (uncert_int, uncert_dec or ''))
1527 else:
1528
1529
1530
1531
1532 num_digits_after_period = (0 if main_dec is None
1533 else len(main_dec)-1)
1534 uncert = int(uncert_int)/10**num_digits_after_period
1535
1536
1537 uncert *= float("1%s" % (exponent or ''))
1538
1539 return (value, uncert)
1540
1546 """
1547 Given a string that represents a number with uncertainty, returns the
1548 nominal value and the uncertainty.
1549
1550 The string can be of the form:
1551 - 124.5+/-0.15
1552 - 124.50(15)
1553 - 124.50(123)
1554 - 124.5
1555
1556 When no numerical error is given, an uncertainty of 1 on the last
1557 digit is implied.
1558
1559 Raises ValueError if the string cannot be parsed.
1560 """
1561
1562 try:
1563
1564 (value, uncert) = representation.split('+/-')
1565 except ValueError:
1566
1567 parsed_value = parse_error_in_parentheses(representation)
1568 else:
1569 try:
1570 parsed_value = (float(value), float(uncert))
1571 except ValueError:
1572 raise ValueError('Cannot parse %s: was expecting a number'
1573 ' like 1.23+/-0.1' % representation)
1574
1575 return parsed_value
1576
1577 -def ufloat(representation, tag=None):
1578 """
1579 Returns a random variable (Variable object).
1580
1581 Converts the representation of a number into a number with
1582 uncertainty (a random variable, defined by a nominal value and
1583 a standard deviation).
1584
1585 The representation can be a (value, standard deviation) sequence,
1586 or a string.
1587
1588 Strings of the form '12.345+/-0.015', '12.345(15)', or '12.3' are
1589 recognized (see full list below). In the last case, an
1590 uncertainty of +/-1 is assigned to the last digit.
1591
1592 'tag' is an optional string tag for the variable. Variables
1593 don't have to have distinct tags. Tags are useful for tracing
1594 what values (and errors) enter in a given result (through the
1595 error_components() method).
1596
1597 Examples of valid string representations:
1598
1599 -1.23(3.4)
1600 -1.34(5)
1601 1(6)
1602 3(4.2)
1603 -9(2)
1604 1234567(1.2)
1605 12.345(15)
1606 -12.3456(78)e-6
1607 12.3(0.4)e-5
1608 0.29
1609 31.
1610 -31.
1611 31
1612 -3.1e10
1613 169.0(7)
1614 169.1(15)
1615 """
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631 if isinstance(representation, basestring):
1632 representation = str_to_number_with_uncert(representation)
1633
1634
1635
1636
1637
1638 if tag is not None:
1639
1640 assert isinstance(tag, (str, unicode)), "The tag can only be a string."
1641
1642
1643
1644 return Variable(*representation, **{'tag': tag})
1645