PainterQubits / Unitful.jl

Physical quantities with arbitrary units
Other
613 stars 112 forks source link

Equivalencies as in astropy.units #383

Closed rmsrosa closed 3 years ago

rmsrosa commented 4 years ago

Hi, Developers,

I am new to Julia and I just heard about Unitful.jl.

I wonder whether Unitful.jl has methods to handle equivalencies as in astropy.units (see https://docs.astropy.org/en/stable/units/equivalencies.html).

I search the issues for "equivalent", "equivalencies", "equivalence", and quickly browsed the documentation and the code for that and didn't find anything, but I apologize if this is implemented in some way and I just missed it.

Thanks in advance!

sostock commented 4 years ago

Something like that is not implemented right now, but I think it would be nice to have.

rmsrosa commented 3 years ago

Hi, thanks for replying!

We have implemented this equivalency in UnitfulBrew.jl, which is going to be, of course, an extension with quantities related to beer brewing.

I will be happy to make a PR with that, including the equivalence between wavenumber and frequency, which is used in astropy.unit (and because both units are already in Unitful.jl).

UnitfulBrew.jl is our first repo in JuliaBrewing.

sostock commented 3 years ago

I think this would be nice as a separate package, e.g. UnitfulEquivalencies. I actually started to design such a package, maybe we can combine our ideas. I will try to upload my implementation in the next few days, hopefully we can discuss it then. Here are some ideas/comments:

rmsrosa commented 3 years ago

It is not clear to me whether it is best to use dimensions or units. I needed to use units since both `°P` and `sg` above are non-dimensional. 

- The second argument of `@equivalence` is unnecessary since it can actually be inferred from the function, but I left it like that for a start, but it makes sense to drop it and have the macro figure it out.

- I haven't thought about derived units :-|

PS: I must also admit my lack of discernment between using equivalence/equivalency. I have been going back and forth with it.
sostock commented 3 years ago

I am indeed using a global list of equivalencies, but I agree that it might lead to conflict. But it might be good to have an intermediate solution, allowing different lists. The advantage is that we don't need to keep remembering the name of the conversion function for each quantity. And depending on the field of application, it might be interesting to set a default list of equivalencies so we don't need to keep passing the list all the time.

One equivalency can include several conversion functions (like in astropy.units, where the spectral() equivalency can convert between energy, frequency, wavelength, and wavenumber), so one doesn’t have to remember individual conversion functions (those would be hidden from the user). Of course, one could still add the possibility to have lists of multiple equivalencies and a default list.

In my case, I defined a function econvert to handle the equivalencies, instead of adding another dispatch for uconvert.

I have done the opposite (defined a uconvert method with three arguments, the third being the equivalency), but I would be fine with econvert.

It is not clear to me whether it is best to use dimensions or units. I needed to use units since both °P and sg above are non-dimensional.

I chose dimensions, since these equivalencies aren’t tied to specific units (it doesn’t matter whether the energy is given in J or eV, it can be converted to a wavelength in any case). Your case is arguably a bit special – units that share the same dimension (in your case dimensionless), but with non-trivial conversion between them. I would argue that sg and °P should have their own dimensions to avoid such errors. Otherwise, how do you keep people from using uconvert on these units and getting a wrong result?

I haven't thought about derived units :-|

Since “derived equivalencies” are only well-defined for proportional equivalencies (I think), I would simply not include them for now.

PS: I must also admit my lack of discernment between using equivalence/equivalency. I have been going back and forth with it.

I just used equivalency because that’s what astropy.units uses, I didn’t give it much thought. Equivalence is shorter and seems like the more common word, so maybe we should use that instead.

sostock commented 3 years ago

I’ve uploaded my implementation here: https://github.com/sostock/UnitfulEquivalences.jl

rmsrosa commented 3 years ago

Great! I am learning from it!

I see you've only implemented proportional and anti-proportional equivalences. I don't know whether you plan to implement more general equivalences. The one we need between degrees Plato and specific gravity is a quadratic relation. I guess this case can be included in line 109 of your code. Instead of throwing an error if it is not of any of the two cases, it implements a different relation as given by the expression. In this case, of course, we would need to add two equivalences, the direct y = f(x) conversion and the inverse x = f^-1(y), since the code will not be able to figure this out in general.

As for my use of units instead of dimensions, at the end it is about the same, because I actually infer the dimension from the unit and use uconvert to change to the unit used in the conversion function. So, there is no limitation from using units.

And although I agree that using dimension might be more elegant, I am still not sure it is the best solution. I still need to think what to do in our case that both Plato and specific gravity are nondimensional. I think it is artificial to add different units to them, or at least to one of them, to make it work with your implementation. Using units avoids this.

Now, since I am new to Julia, there are things in your code that I don't know how they work. In line 47, you call edconvert for the equivalence conversion, while in line 28 you define edconvert but the only it has it to throw an ArgumentError. Can you explain me how it works, please?

rmsrosa commented 3 years ago

Now, since I am new to Julia, there are things in your code that I don't know how they work. In line 47, you call edconvert for the equivalence conversion, while in line 28 you define edconvert but the only it has it to throw an ArgumentError. Can you explain me how it works, please?

Ok, I think I got it. it is just another dispatch throwing an error in case the dispatch with the proper dimensions, defined in _eqrelation, is not found, right? Great.

sostock commented 3 years ago

Ok, I think I got it. it is just another dispatch throwing an error in case the dispatch with the proper dimensions, defined in _eqrelation, is not found, right? Great.

Yes, that's how it works. The macros @equivalence and @eqrelation both use _eqrelation to define two edconvert methods to convert between the equivalent quantities.

For equivalences that are not (anti-)proportional, you would not use the macros but instead just add the edconvert methods yourself. This would be properly documented once we agree on a design, so users know how to add their own equivalences.

Maybe we can continue the discussion at sostock/UnitfulEquivalences.jl?

rmsrosa commented 3 years ago

Yes, let us continue there. I will close the issue, then.