Skip to content

Conversation

@mdboom
Copy link
Contributor

@mdboom mdboom commented Feb 28, 2013

Based an idea by @adrn in #794, this adds "automatic" members to
Quantity that allow the user to easily convert to other units.

This involved adding a more complex UnitRegistry class, that also keeps
a dictionary of all registered units sorted by their physical
type.

From this new registry, a compose can also limit the set of units
that it needs to search through for a match, making it faster, so
this PR has the added bonus of being a performance improvement.

@ghost ghost assigned mdboom Feb 28, 2013
@mdboom mdboom mentioned this pull request Feb 28, 2013
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor grammer: I think this is "A list of equivalence pairs allow conversions between." Or maybe a more just "A list of unit equivalencies. See :ref:unit_equivalencies for details."

@eteq
Copy link
Member

eteq commented Feb 28, 2013

Once this is merged, we should create an issue to adapt Angle to this (in addition to #794 for Distance).

@embray
Copy link
Member

embray commented Mar 1, 2013

This is some evil genius stuff right here. I'll have to mull over it a bit more but this looks pretty much like what I had in mind.

@adrn
Copy link
Member

adrn commented Mar 5, 2013

Given this, maybe it's time to reopen the discussion about Angle being a Quantity subclass? @eteq

@astrofrog
Copy link
Member

By the way, should we mention this PR on the mailing list, given that it is quite an important addition?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "no" should be "now".

@mdboom
Copy link
Contributor Author

mdboom commented Mar 5, 2013

I'm out sick, so just popping in, but my two bits: yes, let's point this out on mailing list and I think this does make angle as subclass of quantity a lot more appealing.

@mdboom
Copy link
Contributor Author

mdboom commented Mar 14, 2013

Shall I continue to refactor Angle and Distance to build on this -- or open a subsequent pull request?

@astrofrog
Copy link
Member

@mdboom - I think separate pull requests might be best. I will test this one out now.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be worth mentioning that the downside of this is that it only works for single units, i.e. if you have a velocity, you can't convert to e.g. km/s with this interface.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, not out-of-the-box, but you can do:

>>> u.def_unit('kms', u.km / u.s, register=True)
Unit("kms")
>>> x = 1.0 * (u.mile / u.hr)
>>> x.kms
0.00044704

In other words, it works with any unit in the registry, whether built-in or not.

@astrofrog
Copy link
Member

I've tested this out, and it works nicely!

For beginners, it might not be obvious why q.pc returns a float, and q.to(u.pc) returns a quantity. There's nothing that indicates either behavior in the syntax. I think that the former should be regarded much more as a convenience than the norm. So just to be clear, I'm ok with the behavior, but I think that the docs should mention this just as shorthand for q.to(u.pc).value. Note that it also won't work for composite units that are not available under a single name. So for example (1. * u.m / u.s) cannot be converted in this way (hence why it should not be the norm, but a convenience). The docs should clarify that the correct way is not q.value or q.pc, but the full explicit q.to(u.pc).value.

My second concern is that tab-complete for quantities is now cluttered for things like distances. In the spirit of my above comment, I think that this way of concerting should be only regarded as a convenience, and I don't think all the units should appear when one tab-completes, otherwise things like si, cgs, and to are hidden.

What does everyone else think?

@mdboom
Copy link
Contributor Author

mdboom commented Mar 21, 2013

I'm not sure how to deal with the fact that the autocompletion is much more cluttered now. On the one hand, at least there is some autocompletion, unlike the lack of autocompletion one gets with q.to(u.in<Tab>). We could have these special attributes all be prefixed with, for example, to_, so one would to (1.0 * u.m).to_pc. I'm not sure that's the best prefix because it overloads the subtlely different to method, but you get the idea.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 8, 2013

What's the consensus here? I think I would summarize as: this is handy, and will make other parts of the code simpler (once Distance, Angle etc. inherit on Quantity). However, there is an explosion of members here. Any thoughts on that? I personally don't mind, but I'd like to hear from those that do. Once we have this finalized, I'd like to go pulling out swathes of code elsewhere ;)

@astrofrog
Copy link
Member

@mdboom - could we not overload __dir__ to not show all these units as attributes by default?

@mdboom
Copy link
Contributor Author

mdboom commented Apr 8, 2013

@astrofrog -- that's certainly a possibility. Another is to put all this in a subobject, e.g.:

q.as.m

(Of course, we can't use as because it's a keyword, but that's the most natural short English word I could think of).

@eteq
Copy link
Member

eteq commented Apr 8, 2013

I like how the subobject approach works for tab completion purposes. My only concern is that it might make Quantity a bit harder to manage if each instance has a subobject that's always associated with it. (although I'm sure there're ways to make that clever/elegant.)

Either that or using a prefix (without the subobject) might also help make the to vs. value distinction clearer. I think it's worth thinking a bit about what word to use for that, because I forsee a fair amount of confusing given the subtle distinction.

I thinkin is the short word that leaps to mind for me, but that also suffers from the python keyword problem. That's not a problem for prefixes (e.g. q.in_meters), but the subobject approach is out.

Another option might be q.value_in.meters or q.val_in.meters or something? Then it's clear you're getting the value, and not another Quantity like for to.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 8, 2013

@eteq: I like the q.value_in.meters suggestion because it reads well in English and is correctly analogous with .value. However, I anticipate confusion (at least for myself) around remembering where the periods versus underscores go. Maybe just using a prefix of value_in_ would be clearer?

@eteq
Copy link
Member

eteq commented Apr 8, 2013

That's a good point. It's also easier, so perhaps we should start there, and if anyone is feeling ambitions they can try the subobject approach?

@mhvk
Copy link
Contributor

mhvk commented Apr 9, 2013

With q.value_in.meters, you're almost back to q.value_in(u.m) or equivalent (suggested by @astrofrog and discussed at some length in #669). I actually thought that was a nice suggestion although in the thread the conclusion seemed to be that one should stick with q.to(u.m).value.
My two cents would be to keep Quantity itself simple and uncluttered, perhaps defining the machinery for the automatic properties, but use it only in specific sub-classes, like angles or distances, where the unit is known (and where it does seem convenient).

@taldcroft
Copy link
Member

FWIW I read through this discussion and spent a little time thinking about the options here. In the end I have a slight preference for the q.value_in.meters. The value_in property should always be available, and from there you get the full set of available units (and nothing more) from q.value_in.<TAB>. Particularly if / when Quantity becomes an ndarray and has a bunch of atttributes/methods already, it will be nice to have a distinct and clean namespace for conversion units.

@adrn
Copy link
Member

adrn commented Apr 9, 2013

I agree with what's been suggested, e.g. the value_in approach.

@astrofrog
Copy link
Member

@mhvk raises a good point about q.value_in(units) - in fact, it's IMHO a better solution than q.value_in. because the latter only works for a very small subset of quantity types, whereas the first (for a couple of extra characters) is available for all unit types. With the latter, you'd have to tell users that to convert quantities like distances, they should use q.value_in.pc but for speeds they should use q.to(u.m/u.s).value - seems a bit inconsistent!

So q.value_in(units) is better than q.value_in.units, and is not many more characters. I also think that q.value_in(units) is better than q.to(units).value, but I think we can have both.

However, we are missing the opportunity to simply define a shorthand convenience of the form q.pc which was the point of this proposal in the first place - I personally think such a convenience would be great, but I'm just advocating dropping it from the tab completion. So my vote is to have:

q.to(<units>).value  # works anyway because all quantities have a ``value`` attribute
q.value_in(<units>)  # cleaner syntactically, supports all units and combination of units
q.<units>  # for convenience, no tab completion, and advertised as a convenience, not as main way

Because the last one is a convenience, it would only work for certain kinds of quantities, but that's ok, because it's just a convenience.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 9, 2013

@astrofrog: I see your point about q.value_in(units) being more powerful than q.value_in.units.

To step back a bit -- the initial motivation for this PR for me was to bring some of the features of the specialized quantity classes into the generic Quantity class. I found it confusing/limiting that the quantity types that we've bothered to specialize have more features and conveniences than the generic Quantity class, especially since all of the information is there. I'd like the specialized quantities (Distance, Angle etc.) to become less special and really just become Quantity subclasses with special parser/generators and built-in equivalency mappings.

So the .unit syntax was convenient for the specialized quantities, but not for all quantities, because of the possibility of namespace explosion. I'd like the make the counter argument that the specialized quantities are annoyingly rigid by hard-coding what can be done. I can't, for instance, define a binary degrees unit and have the Angles class automatically know how to convert to/from it, like I can with the Quantity class (and this PR). I think the power and flexibility of that overshadows the fact that we may end up with a lot of members.

I'm not crazy about the idea about adding (virtual) members that don't show up with tab completion. Most intro Python tutorials with IPython extoll the virtues of using completion (or even just dir()) to find the members of an object, and I'd consider it a bug if any magic we were doing didn't also work that way. It seems like it breaks the standard Python introspection model.

Is there a way we could instead limit the number of metric (SI) prefixes we include? Some of them are in common usage and are really handy, but others are just there for pedantic completeness. If we stored with the units some sort of metadata about which prefixes are truly handy, we could reduce the number of members dramatically. If we did that, I'd say we make dir and completion work, and still have a value_in method as you suggest.

@astrofrog
Copy link
Member

@mdboom - I'd be happy with keeping tab-completion if we can reduce the number of units proposed. So then you are suggesting we implement:

q.<units>  # where not all units show up
q.value_in(<units>)  # can be used for anything

? I'd be fine with that.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 9, 2013

Yeah, the question just is how to determine which units prefixes show up in a way that isn't annoying and adding a lot of metadata wrangling for us. One could probably go a long way with just including every third one: nano, micro, milli, kilo, mega, tera etc. and skipping the less commonly used ones.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 11, 2013

Ok -- I've whittled this down so it only will use full names, e.g. kilometer rather than km for this purpose. I think this whittles it down considerably, and also makes the non-unit names stand out a little better. Let me know what you think.

@eteq
Copy link
Member

eteq commented Apr 17, 2013

There's a single failure in travis w/ scipy that says

AttributeError: type object 'UnitBase' has no attribute '_get_namespace'

astropy/units/core.py:1021: AttributeError

Based on the diff, I think you just want _get_namespace -> _normalize_equivalencies, is that right, @mdboom?

Also, @mdboom - can you clarify what behavior I should be expecting in this version? If I do u.m.<tab>, should I see options like u.m.kilometer? If so, something's wrong, because I don't see those.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 17, 2013

@eteq: That's weird. That Travis failure is referencing code on master that doesn't (yet) exist on this branch. Is it possible Travis is testing the wrong thing?

The "magic" members are added to the Quantity class, not the Unit class, so, no, u.m.<tab> should not return kilometer. I suppose we could do that, but without the ability to pass a quantity (numeric value) to Unit.to that way, I don't see it as terribly useful.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 17, 2013

I see what was going on. This is one of those rare times where the code needed to be updated to work with master but there wasn't actually a merge conflict. I've rebased on master, and we should get the tests passing again.

@embray
Copy link
Member

embray commented Apr 17, 2013

I've had that happen to me before too. I'm pretty sure Travis tries to merge your PR with upstream/master when doing the tests, which is sensible.

@mdboom
Copy link
Contributor Author

mdboom commented Apr 17, 2013

Travis failure appears to be network related (false positive).

@eteq
Copy link
Member

eteq commented Apr 18, 2013

@mdboom - Duh... I don't know why I thought this was supposed to work on units. Anyway, it now looks just as I thought. As far as you're concerned this is good, right? Looks all set to me. I re-started the Travis job that was a problem, so hopefully it will pass once Travis works out their current problem (http://status.travis-ci.com/incidents/h2mtcw1llvnc).

On a slightly related note: are yau and zau astronomical units with very large SI prefixes? At first I was trying to understand why this started putting transliterations of chinese names in as some of our units...

@mdboom
Copy link
Contributor Author

mdboom commented Apr 18, 2013

Yeah, I think it's good to go. It can still create fairly large namespaces on the objects in some cases, but about 1/3 of what it was originally.

😁 yau and zau: actually very small SI prefixes. zau is ~ 0.14 nanometers.

@eteq
Copy link
Member

eteq commented Apr 21, 2013

oops, look like something recently merged conflicts, @mdboom ... needs a rebase (or you can merge it manually)

mdboom added 7 commits April 22, 2013 11:19
Quantity that allow the user to easily convert to other units.

This involved add a more complex UnitRegistry class, that also keeps
a dictionary of all registered units sorted by their physical
type.

From this new registry, a `compose` can also limit the set of units
that it needs to search through for a match, making it faster, so
this PR has the added bonus of being a performance improvement.
mdboom added a commit that referenced this pull request Apr 22, 2013
@mdboom mdboom merged commit 26df93a into astropy:master Apr 22, 2013
@mdboom mdboom deleted the unit/conversions-as-properties branch May 21, 2014 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants