hgrecco / pint

Operate and manipulate physical quantities in Python
http://pint.readthedocs.org/
Other
2.38k stars 465 forks source link

Make commutative operations commutative in both magnitude and units #1019

Open jthielen opened 4 years ago

jthielen commented 4 years ago

As brought up in https://github.com/pydata/xarray/pull/3706#discussion_r376233066, having + (and other should-be-commutative operations like np.where) give differing results based on order was an unexpected result for a duck array. In other words, something like the following would ideally be an invariant:

(a + b).units == (b + a).units

A suggestion by @shoyer was:

[...] I think it would be worth considering adding some sort of unit casting hierarchy into pint's unit registries. It almost doesn't matter what exactly the rules are as long as it exists.

So, instead of ensuring consistent units by converting to the first unit, there will need to be some hierarchical way of giving a preference to units of a given dimensionality.

While there might be some fully general way of doing so, it would have to be very complex. So, I would think the best way forward is to decide on some heuristics on which unit to choose, and then fall back to base units if those heuristics do not resolve a consistent unit.

I like working in base units when possible, so having the set of heuristics be minimal (perhaps just choosing the closer-to-unity prefix, and always converting to base units when in different systems) would be good with me, but I don't know what others' thoughts on this are.

jules-ch commented 4 years ago

True we should have commutative operations on any operations IMO it would make the use of the library more consistent. I don't think it will change the way we use pint anyway.

Only requirement for this is simplicity since it won't change how operation is conducted.

My thoughts at the moment :

But maybe we should be opinionated about it and do operations on simple base units. We need some more thought s on the matter.

dalito commented 4 years ago

I am not sure if falling back to base unit is a good solution. For example:

1 foot + 12 inch = ?

What is the expected result: (a) 24 inch, (b) 2 foot, (c) 0.6096meter? For the typical user (c) is least expected, probably (b) is the nices. On the other hand, for 1 foot - 10 inch the nicest result would be "2 inch".

jules-ch commented 4 years ago

What is the expected result: (a) 24 inch, (b) 2 foot, (c) 0.6096meter? For the typical user (c) is least expected, probably (b) is the nices. On the other hand, for 1 foot - 10 inch the nicest result would be "2 inch".

That's why we should not let the user guess which unit he is going to have after such operations. I think the current public API gives enough flexibility with things like systems, convert functions to / to_compact to let the user be explicit if he wants a particular unit output.

dalito commented 4 years ago

Your rules are simple enough to remember and I would be OK with them. Maybe good documentation can prevent false expectations. And you are right that pint offers enough options to get the desired output.

hgrecco commented 4 years ago

I agree. Can we have a PR?

jules-ch commented 4 years ago

I can give it a shot.

jules-ch commented 2 years ago

@hgrecco About operation with same unit, I think i'm gonna change my mind. Taking the most precise unit is simpler & maybe the best option.

Numpy does that with timedelta dtype.

Like so :

>>> a = np.timedelta64(1, 'D') 
>>> b = np.timedelta64(4, 'ms')
>>> a+b
numpy.timedelta64(86400004,'ms')
>>> b+a
numpy.timedelta64(86400004,'ms')

When you have multiple units things get tricky. I need to go deeper in the options that we have.