eric.le.bi...@spectro.jussieu.fr writes:

> As for your idea of "straight-forward interval arithmetic", it's a
> good one, but I'd have to redefine lots of functions from the math
> module, to use them explicitly in my code, etc.: this is heavy; I was
> looking for a light-weight alternative, where all calculations are
> expressed in Python as if all numbers were regular floats, and where
> you don't have to redefine any mathematical operation.  In other
> words, I'm looking for a robust way of implementing the derive()
> function of Peter without knowing anything about which function uses
> which "numbers with an uncertainty": the equivalent of Peter's derive
> () would simply successively change all "numbers with uncertainty"
> defined so far and see how the result of a given calculation varies--
> the variables that are not used in the calculated expression don't
> change the result, for instance.  I understand that this is
> computationally inefficient (too many variables might be changed), but
> I don't need speed, only robustness in the propagation of errors, and
> a simple and very legible calculation code, and ideally with only a
> few lines of Python.

I still don't understand why you need mutable floats.

Here is a suggestion, based on Petter Otten's Value class and his derive
and calc functions.  I've modified Value slightly so that it implements
unary - and binary +, *, -, /, **. The 'for name in dir(math)' loop at
the end wraps each function in the math module in valueified (defined
below) so that they accept floats or Values.

---------------- uncert.py ---------------

def valueified(f):
    """
    Change a function that accepts floats to a function that accepts
    any of float or Value.
    """
    def decorated(*args):
        ret = calc(f, map(Value, args))
        return ret if ret.err else ret.value
    return decorated

class Value(object): 
    def __init__(self, value, err=0):
        if isinstance(value, Value):
            value, err = value.value, value.err
        self.value = float(value) 
        self.err = float(err) 
    def __repr__(self): 
        return "%r +- %r" % (self.value, self.err) 
    __neg__ = valueified(float.__neg__)
    for op in 'add', 'sub', 'mul', 'div', 'pow':
        for r in '', 'r':
            exec """__%s__ = valueified(float.__%s__)""" % (r+op, r+op)
    del op, r

def derive(f, values, i, eps=1e-5):
    x1 = f(*values) 
    values = list(values) 
    values[i] += eps 
    x2 = f(*values) 
    return (x2-x1)/eps 

def calc(f, args): 
    values = [v.value for v in args] 
    errs = [v.err for v in args] 
    sigma = 0 
    for i, (v, e) in enumerate(zip(values, errs)): 
        x = derive(f, values, i)*e 
        sigma += x*x 
    return Value(f(*values), sigma**0.5)

builtinf = type(sum)
import math
for name in dir(math):
    obj = getattr(math, name)
    if isinstance(obj, builtinf):
        setattr(math, name,  valueified(obj))

---------- end of uncert.py ---------------

Example:

marigold:junk arno$ python -i uncert.py 
>>> a = Value(2, 0.1)
>>> b = Value(7, 2)
>>> a+b
9.0 +- 2.0024984393742682
>>> a*b
14.0 +- 4.0607881007017825
>>> from math import *
>>> sin(a)
0.90929742682568171 +- 0.041615138303141563
>>> sin(2)
0.90929742682568171
>>> 2*a
4.0 +- 0.20000000000131024
>>> 2**b
128.0 +- 177.44629319622615
>>> a/b
0.2857142857142857 +- 0.082873111674175479
>>> sqrt(a**2+b**2)
7.2801098892805181 +- 1.9232454006952127
>>> hypot(a, b)
7.2801098892805181 +- 1.9232442191491188
>>> #etc

Isn't this what you need?

-- 
Arnaud
--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to