Modifying the value of a float-like object

E

Eric.Le.Bigot

Steven, I'd appreciate if you could refrain from criticizing so
bluntly so many points. I'd be great if you trusted me more for
knowing what I'm talking about; I've been a programmer for 25 years,
now, and I pretty well know what my own code looks like! I appreciate
your input, but please soften your style!

For instance, for your information, to respond only to your first
point (below), I'd like to be more precise: I don't want a function
that calculates a result (such as f() in Peter's code above) to
explicitly contain code for handling uncertainties. This is what I
had in mind when I said, perhaps inadequately, that "the place in the
code where (foo, baz) is calculated has no idea (...) of where they
were defined, etc.". Thus, either f() manipulates numbers without
knowing that it is actually performing error calculations when doing
x*y, or some code calls f() and does the job of calculating the
uncertainty. The first method does not work, for many reasons: (1)
correlated errors are really hard to take into account, (2) the 2
modules you list are interesting, but none of them gives the standard
deviation of an arbitrary mathematical expression.

Steven, there is obviously a strong misunderstanding problem here.
For instance, I do not "conflate four different issues as if they were
one". There are, however, various issues raised in this stimulating
thread, and many paths are worth exploring.

It looks to me like Dan has paved the way to a good solution to the
initial problem: calculate the uncertainty on any calculation that was
written for code that works with floats, without modifying this code;
the method consists essentially in performing Peter's calc()
calculation, but changing the values of the quantities used through
references (as Ben described). But, again, I don't yet see how to
unambiguously keep track of the uncertainty associated to a numerical
value. I'll try this, to see if the devil does lie in the details,
here. :)

The other, approximate solution, would be to coerce with float() any
parameter that can carry an uncertainty, in calculations (numbers with
uncertainty would have a __float__() method that returns a result that
can be changed after instantiation); but this would modify (and make
less legible) existing code.
 
D

Dave Angel

Steven said:
Oh nonsense. Many programming languages have mutable floats.
That's irrelevant. Python doesn't. So introducing one will quite
likely alter the OP's code's behavior. It doesn't matter if it's
possible, it matters whether the existing code's behavior might change,
and of course if a future maintainer might have trouble making sense of it.

BTW, just what languages have mutable floats? I don't think I've come
across any since Fortran (on a CDC 6400), and I don't think that was
deliberate. In that implementation it was possible to change the value
of a literal 2.0 to something else. I remember writing a trivial
program that printed the value of 2.0 + 2.0 as 5.0. It was about 1971,
I believe. I know I haven't used Fortran since 1973.

If you're going to use the fact that many languages pass arguments by
reference, then you should realize that a reference to a float is a
different kind of variable than a float. And although that language
might use the terminology of mutable, it wouldn't mean the same thing
that mutable does in Python.
 
E

Eric.Le.Bigot

To Dave A. and Piet: I appreciate your taking the time to make
suggestions. I understand that there is a hitch in the approach that
you describe, which I would like to insist on: how do you handle
functions that use math.sin(), for instance? numpy does this kind of
magic, but I'm not sure it's wise to spend time reproducing it. I'd
to not have to modify any calculation code, so that it is legible and
general (in particular, so that it can work with Python floats). And
again, interval arithmetic fails to produce real standard deviations,
because of correlations between uncertainties.

Hence my idea to have mutable floats, that would be changed in some
_external_ error calculation routine (i.e. a routine which is
completely independent from the calculation f()), in a way equivalent
to Peter's derive() function--except that the values used in f() are
only accessible through a list of objects, as in Ben's post. I'll
implement this with 1x1 numpy.array objects, even though this will
mean that numbers will not have an unambiguous uncertainty attribute,
as I mentioned in a previous post. I'll post here the result of my
investigations.
 
A

Arnaud Delobelle

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 += 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
Isn't this what you need?
 
E

Eric.Le.Bigot

It looks like Dan found what is in effect a mutable float
(numpy.array).

Now, with respect to the initial problem of having mutable floats that
also contain an uncertainty attribute, I'd like to note that
numpy.ndarray can be subclassed: it now looks possible to create a
mutable float class that also contains an uncertainty attribute!

So, I'll see how/whether this can be implemented without pain... I'll
leave a message here when I've got news!

Thanks again everybody for helping me out!

Is there a way to easily build an object that behaves exactly like a
float, but whose value can be changed?  The goal is to maintain a list
[x, y,…] of these float-like objects, and to modify their value on the
fly (with something like x.value = 3.14) so that any expression like "x
+y" uses the new value.

Hi Eric,

Numpy's array object can do something like what you want:

In [27]: x=array(0.0)

In [28]: print x, sin(x)
0.0 0.0

In [29]: x.itemset(pi/2)

In [30]: print x, sin(x)
1.57079632679 1.0

Not sure if this a recommended way of using array or not, but it seems
to work. The problem is that any calculation you do with such an object
will result in a float, and not another numpy array (although inplace
operations work):

In [40]: print (x*2).__class__
<type 'numpy.float64'>

In [41]: x *= 2

In [42]: print x.__class__
<type 'numpy.ndarray'>

So it's not a perfect solution, but it might be OK for what you need.

Dan
 
P

Piet van Oostrum

ELB> To Dave A. and Piet: I appreciate your taking the time to make
ELB> suggestions. I understand that there is a hitch in the approach that
ELB> you describe, which I would like to insist on: how do you handle
ELB> functions that use math.sin(), for instance? numpy does this kind of
ELB> magic, but I'm not sure it's wise to spend time reproducing it. I'd
ELB> to not have to modify any calculation code, so that it is legible and
ELB> general (in particular, so that it can work with Python floats). And
ELB> again, interval arithmetic fails to produce real standard deviations,
ELB> because of correlations between uncertainties.

Mine does allow you to use math.sin(x) as you can see. Dave's also I
think. His solution is similar to mine but he puts the list stuff in it
whereas I came up with only the float.

In my solution I think you can mostly use normal formula's with the
Float objects, although there may be some functions that give problems
for example if they have explicit tests like type(x) == float. Why don't
you give it a try?
 
E

Eric.Le.Bigot

Arnaud, your code is very interesting!

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

Here is why: the code that your proposed (or any code that does
straightforward error propagation, for that matter) does not generally
calculate uncertainties correctly:
0.0 +- 0.14142135623823598

The correct result is obviously 0.0 +- 0.0. This is the effect of
what I referred to in previous posts as "correlated errors".

Anyway, I learned some interesting stuff about what you can do in
Python, thanks to your code! :)
 
E

Eric.Le.Bigot

Thanks, Piet! Before reading your post, I did not know that defining
__float__() was enough for math.sin() to be able to calculate
something!

To summarize my current understanding for the original problem:

- Mutable floats seem to be the only way of performing (correct)
uncertainty calculations without modifying the code of the modules in
which the calculations are done (in particular when we call object
methods that perform calculations whose parameters are _not passed as
arguments by the caller_ [Peter's post shows a simpler, less general
case]).

- A mutable float can be obtained through a numpy.ndarray object (see
Dan's post), which can be subclassed so as to add an "uncertainty"
attribute. Pros: the way the mutable float behaves in calculation is
taken care of by numpy. Cons: this requires numpy.

- A mutable float can also be created directly (see Piet's post), and
the regular float behavior can be quite well (or fully?) approximated
by defining a __float__() member, as well as implementing the basic
methods of Python's floats (addition, etc.). Pros: this is clean
Python. Cons: as Piet noted, one might have to explicitly use float
(x) in some expressions.

I'd like to add (in particular for those who think that mutable floats
are dangerous beasts to play with) that the example "x = y" given in
previous posts (where "y" is a number with uncertainty) actually gives
the intended behavior when calculating "x-y" with its uncertainty,
which would not be the case if "y" was copied by value into "x". When
it comes to calculating uncertainties, this example shows that
mutability is actually a good thing.

Well, that's two implementations to be tried! I'll get back to this
thread with the results! Thanks for the lively thread!
 
S

Steven D'Aprano

That's irrelevant. Python doesn't. So introducing one will quite
likely alter the OP's code's behavior. It doesn't matter if it's
possible, it matters whether the existing code's behavior might change,
and of course if a future maintainer might have trouble making sense of
it.

What are you talking about? Python introduces new types quite frequently.
We now have sets, frozensets, rationals, decimals and namedtuples, and
we'll soon be getting ordereddicts and probably others as well. People
create new types in their code *all the time*, using the class statement.
If the OP wants to create a MutableFloat type, that's his prerogative.

BTW, just what languages have mutable floats?

C and Pascal. Probably Java. As far as I know, any language with
variables. Here's a sample piece of Pascal code which demonstrates that
assignment to a float mutates the value in a fixed memory location:


program main(input, output);
var
x: real; {called 'float' in other languages}
ptr: ^real;

begin
x := 23.0;
ptr := @x; {point ptr to the location of x}
writeln('Before: x = ', x, '; address = ', integer(ptr));
x := 42.0;
ptr := @x;
writeln('After: x = ', x, '; address = ', integer(ptr));
end.


Executing it, I get:

Before: x = 2.300000000000000e+01; address = 134692368
After: x = 4.200000000000000e+01; address = 134692368

The equivalent in Python is this:
After: x = 42.0 ; address = 147599980

As you can see, x becomes bound to a completely different float object.

I don't think I've come
across any since Fortran (on a CDC 6400), and I don't think that was
deliberate. In that implementation it was possible to change the value
of a literal 2.0 to something else.

Who is talking about changing the values of float literals? That would be
very silly.

If you're going to use the fact that many languages pass arguments by
reference,

Nobody is talking about parameter passing conventions.
 
P

Piet van Oostrum

I think the term 'mutable float' is causing a lot of confusion. My
solution I wouldn't call a mutable float, but a float container that
tries to act like a float in a context where this is required.

Another solution would have been to use automatically dereferencing
pointers but that is something Python does not support. That also would
be very close to C++'s ref (&) parameters.
 
A

Arnaud Delobelle

Arnaud, your code is very interesting!



Here is why: the code that your proposed (or any code that does
straightforward error propagation, for that matter) does not generally
calculate uncertainties correctly:

0.0 +- 0.14142135623823598

The correct result is obviously 0.0 +- 0.0. This is the effect of
what I referred to in previous posts as "correlated errors".

Anyway, I learned some interesting stuff about what you can do in
Python, thanks to your code! :)

I still don't think mutable floats are necessary. Here is an approach
below - I'll let the code speak because I have to do some shopping!

It still relies on Peter Otten's method for error calculation - which I
trust is good as my numerical analysis is to say the least very rusty!

---------- uncert2.py ----------
def uexpr(x):
return x if isinstance(x, UBase) else UVal(x)

def uified(f):
def decorated(*args):
args = map(uexpr, args)
basis = set()
for arg in args:
basis |= arg.basis
uf = lambda values: f(*(x.f(values) for x in args))
ret = UExpr(basis, uf)
return ret if ret.err else ret.value
return decorated

class UBase(object):
def __repr__(self):
return "%r +- %r" % (self.value, self.err)
def __hash__(self):
return id(self)
__neg__ = uified(float.__neg__)
for op in 'add', 'sub', 'mul', 'div', 'pow':
for r in '', 'r':
exec """__%s__ = uified(float.__%s__)""" % (r+op, r+op)
del op, r

class UVal(UBase):
def __init__(self, value, err=0):
if isinstance(value, UVal):
value, err = value.value, value.err
self.value = value
self.err = err
self.basis = set([self])
def f(self, values):
return values[self]


class UExpr(UBase):
def __init__(self, basis, f):
self.basis = basis
self.f = f
self.calc()
def derive(self, i, eps=1e-5):
values = dict((x, x.value) for x in self.basis)
values += eps
x2 = self.f(values)
return (x2-self.value)/eps
def calc(self):
sigma = 0
values = dict((x, x.value) for x in self.basis)
self.value = self.f(values)
for i in self.basis:
x = self.derive(i)*i.err
sigma += x*x
self.err = sigma**0.5


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

Example:

marigold:junk arno$ python -i uncert2.py
 
S

Suraj Barkale

Hello,

Is there a way to easily build an object that behaves exactly like a
float, but whose value can be changed? The goal is to maintain a list
[x, y,…] of these float-like objects, and to modify their value on the
fly (with something like x.value = 3.14) so that any expression like "x
+y" uses the new value.

Have you looked at sympy (http://code.google.com/p/sympy/)? It implements
symbolic mathematics so you can define the equations & put in the values to get
the result. You should be able to derive from the Symbol class to implement the
accuracy behavior.

Regards,
Suraj
 
D

Dan Goodman

I initially tried to create a Float_ref class that inherits from
numpy.array, so that objects of the new class behave like numpy.array
in calculations, but also contain an "uncertainty" atribute, but this
is apparently not allowed ("cannot create 'builtin_function_or_method'
instances").

So I thought I'd directly add an attribute to a numpy.array: "x =
numpy.array(3.14); x.uncertainty = 0.01", but this is not allowed
either.

Thus, it is not obvious for me to extend the 1x1 numpy.array object so
that it also contains an "uncertainty" attribute.

Subclassing numpy arrays is possible but it can be a bit fiddly. You
can't add attributes to a numpy array unless it's a subclass.

The first place to look is:

http://www.scipy.org/Subclasses

Actually, I haven't read this (new) page in the numpy docs, but it looks
better:

http://docs.scipy.org/doc/numpy/user/basics.subclassing.html

It also seems to include a simple example of how to write a subclass
that just adds a single attribute.

Dan
 
D

Dave Angel

Steven said:
What are you talking about? Python introduces new types quite frequently.
We now have sets, frozensets, rationals, decimals and namedtuples, and
we'll soon be getting ordereddicts and probably others as well. People
create new types in their code *all the time*, using the class statement.
If the OP wants to create a MutableFloat type, that's his prerogative.
The OP has existing code, and apparently a good deal of it, which he
wants to run unchanged. That's the whole purpose behind the
discussion. All I've been asserting is that changing the variables
that used to be floats to some new mutable-type would be risky to his
code. Since he's willing to assume the risk, then he can use the code
which I proposed. I'd much rather point out the risks, than have
somebody paste something in and assume it'll work like before.


We'd better skip the discussion of other languages, since you're mixing
terminology something awful.
 
A

Aahz

Steven, I'd appreciate if you could refrain from criticizing so
bluntly so many points. I'd be great if you trusted me more for
knowing what I'm talking about; I've been a programmer for 25 years,
now, and I pretty well know what my own code looks like! I appreciate
your input, but please soften your style!

Fair enough -- but could you please fix your quoting style? Notice how
everyone else is putting quotes above commentary.

A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet?
 
E

Eric.Le.Bigot

Th^H^H

Fair enough -- but could you please fix your quoting style?  Notice how
everyone else is putting quotes above commentary.

A: Because it messes up the order in which people normally read text.
Q: Why is top-posting such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet?

Thanks for this good piece of advice! :D
 
E

Eric.Le.Bigot

I still don't think mutable floats are necessary.  Here is an approach
below - I'll let the code speak because I have to do some shopping!

Hats off to you, Arnaud! I'm very impressed by the ideas found in
your code. :)

Your UExpr object is almost a mutable float, though ("x=UVal(...);
y=x; x.value =...; print y+0" gives a new value). What was not needed
was some kind of "external" access to numbers with error, that would
have allowed an external routine to modify them so as to perform
calculations of Python expressions with different parameters. Your
idea of building an expression (UExpr) that keeps track of the
variables involved in it (basis) was great! I was somehow stuck with
the idea that "float with uncertainty" objects should return a float,
when involved in mathematical calculations.

I adjusted your code in a few ways, and put the result at
http://code.activestate.com/recipes/576721/ (with due credit):

1) There was a strange behavior, which is fixed (by performing only
dynamic calculations of UExpr, with an optimization through a new
"constant" class):
200.0 +- 2.0000000006348273 should equal 6.2800000000000002 +-
2.0000000000131024

2) More operations are supported: calculations with integers (UVal
(1)), comparisons, unary operators + and - (+Uval(1.)),...

3) The code is documented, and identifiers are longer and more
explicit.

Voilà!

Thank you all for this lively and productive thread!
 
S

smichr

I adjusted your code in a few ways, and put the result athttp://code.activestate.com/recipes/576721/(with due credit):

1) There was a strange behavior, which is fixed (by performing only
dynamic calculations of UExpr, with an optimization through a new
"constant" class):

Although normal arithmetic operations are correlated, the correlation
is lost (AFAICT) as soon as you use a function:
.... return a+cos(a)
....0.85887999194013276

It's effectively creating a new variable, tmp=cos(a) so y looks like a
+tmp and the uncertainty in this is (a.error**2 + tmp.error**2)**.5:1.009908340729422

That result we got when requesting the error in y
We can also see that the a inside the cos() is invisible by taking the
derivative wrt a
0.99999999996214217

Also, this approach is limited in that only variables can be arguments
to functions, not node expressions. If x and d are variables, x/d
becomes an expression and you cannot compute sin(x/d). Or am I missing
something?

/c
 
S

smichr

Also, this approach is limited in that only variables can be arguments
to functions, not node expressions. If x and d are variables, x/d
becomes an expression and you cannot compute sin(x/d). Or am I missing
something?

Note to self...it's best to restart with a fresh session if you've
been making modifications. everything appears to work fine without any
of the above observed problems:
<class '__main__.Semi_formal_expr_node'> object with result
0.731688868874 +- 0.213012112511

Sorry for the false alarm,
/c
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,291
Messages
2,571,493
Members
48,160
Latest member
KieranKisc

Latest Threads

Top