Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full support for uncertainties #24

Closed
lebigot opened this issue May 14, 2013 · 6 comments
Closed

Full support for uncertainties #24

lebigot opened this issue May 14, 2013 · 6 comments

Comments

@lebigot
Copy link
Contributor

lebigot commented May 14, 2013

It would be nice to have full support for uncertainties (including correlations, all mathematical functions, etc.).

Maybe you could make use of the (pure Python) module I wrote for this (uncertainties).

@hgrecco
Copy link
Owner

hgrecco commented May 14, 2013

I know the uncertainties package, it is great. I would really like to integrate it into Pint. I see several ways:

  1. Using the object returned by ufloat as the magnitude of Quantity.
  2. Keeping a dedicated Measurement class, and taking the mathematical methods from uncertainties.
  3. Making Measurement class inherit from UFloat or AffineScalarFunc.

All options will need some work to patch uncertainties.math, but in option number three this will be restricted to making the units part behave properly. Additionally I would like to apply in Measurement the same ideas behind NumPy support for Quantity. This will allow me to do numpy.cos(m) where m is a measurement value.

I think that some of this ideas will be easier to implement if the wrap function uses duck-typing instead of isinstance. But I have only looked superficially at the uncertainties code.

What do you think?

@lebigot
Copy link
Contributor Author

lebigot commented May 20, 2013

Thank you for your interest in this!

I like option 3 the best too: a number with uncertainty is a number with uncertainty even if it has units. Inheriting from UFloat is good.

I would be happy to adjust the internals of uncertainties so that it is as easy as possible for you to integrate it into Pint.

With option 3, the only isinstance() that I can find that could need to be generalized is isinstance(f_nominal_value, FLOAT_LIKE_TYPES). I could do a try: float(f_nominal_value) or add the type you want to FLOAT_LIKE_TYPES, which contains types compatible with a (float) nominal value.

That said, wouldn't be possible to do the pure-value (no unit) calculations by using instead the mathematical functions from uncertainties.AffineScalarFunc and uncertainties.umath instead of going through wrap()?

@hgrecco
Copy link
Owner

hgrecco commented May 20, 2013

I will need to look more into uncertainties.AffineScalarFuncand uncertainties.math to make an informed opinion. We also need to make sure that units are not erased when using uncertainties. I think this can be separated in two problems: dealing with operations in which there is a magic method defined (e.g. __add__) and dealing with those in which there are not (e.g. math.cos).

But let me tell you about the current internals and maybe we can figure out the best way.

The main class is pint.Quantity that stores a numerical value (any type: float, decimal, fraction, ndarray) and the units (in a dict-like class called UnitsContainer). pint.Measurement stores two instances of pint.Quantity in instance variables called value and error (I chose these names because they are shorter. I will be fine to alias them to yours). This is of course redundant, as the units are stored twice. I was planning to store only a Quantity (value) and a float (error) in the next version and change the error getter to build a quantity when necessary.

The nice thing about having a Quantity (instead of float) as the value is the way that operations on measurements are implemented. If op is +, -, *, /, M1 op M2 is implemented by dispatching op to the Quantitities stored in the value of each measurement. The resulting value will have the right units.

I was under the impression that wrap() was used to wrap all the math functions as well. I thought at a good route was to patch math to operate in Quantity. As your derivatives are build on these basic operations so it should work. Additionally, instead of returning an AffineScalarFunc you return something of the same class as the input value (I do not know how this will affect the AffineScalarFunc vs. Variable distinction. What do you think?

@lebigot
Copy link
Contributor Author

lebigot commented May 22, 2013

You are right, uncertainties does use wrap() to create uncertainty-aware mathematical functions from the math functions. You do not want to redo the wrapping, though: you would in effect mostly duplicate what is done in uncertainties. Better to use my sweat and not sweat yourself. :)

I wanted to propose a scheme that differs from the one you propose, so that you can see if it fits in your framework:

Not duplicating the wrapping of math functions implies that your pint.Measurements would dispatch unit-less operations to uncertainties.UFloats. What about turning your scheme inside out like so: instead of having a value with uncertainty pint.Measurement store a value without uncertainty through a pint.Quantity, it looks more natural to me to allow your pint.Measurement to instead use a numerical value with uncertainty (that is, an uncertainties.UFloat instead of a float, decimal, etc.). This has the advantage of not attaching the unit only to the value (and not to the error), which is more natural. In this scheme, there is no need for a separate pint.Measurement (but if you want one, you can subclass ping.Quantity so as to allow the value to be an uncertainties.Ufloat).

In fact, I guess that pint.Quantity already leverages operations (+, -, etc.) on the numerical value: you would get the same effect for free with UFLoats.

As for the math functions, maybe you can write functions that (1) check the units and (2) perform a unit-less calculation on the value(s), possibly using the corresponding uncertainties.umath function (I would guess that you do something similar for numpy.cos, etc.)?

@hgrecco
Copy link
Owner

hgrecco commented May 23, 2013

I have started a temporary branch named _uncertainties to prototype some ideas. I have transformed Measurement in a Quantity derived class and used ufloat to create its magnitude. It was quite easy, but the constructor of Measurement changed a bit. I am still not sure about the API and the hierarchy but it works!

Now is time to think how the math support is implemented.

  1. functions from the math module.
    It would be nice to transform your umath module in a function plus a function call
import math

def wrap_module(module, wrapped, wrapfun):
    # Here goes most of the code inside umath
    # but replacing 
    # math -> wrapped
    # wraps -> wrapfun

wrap_module(sys.modules[__name__], math, wraps)

In this way, pint.math could just call wrap_module with a different wrapping function. Any improvement that you make to the module, Pint will get it without any effort.

  1. numpy functions and ufuncs
    There are two ways here.

In the short term, create a pint.npmath module using the same trick as I am suggesting for pint.math. This will be called internally by numpy aware pint methods.

In the long run, I think it will be great if uncertainties uses the hooks provided by numpy in the same way like pint does it now. This is done by overriding __array_wraps__ and other __array_*. I think this way is much cleaner than creating an ndarray of ufloats.

@lebigot
Copy link
Contributor Author

lebigot commented Jun 13, 2013

Your comments look interesting; I will have a closer look.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants