Skip to content

Add "compose" method to units. - #579

Merged
mdboom merged 15 commits into
astropy:masterfrom
mdboom:unit/better_cgs_handling
Jan 25, 2013
Merged

mdboom merged 15 commits into
astropy:masterfrom
mdboom:unit/better_cgs_handling

Conversation

@mdboom

@mdboom mdboom commented Dec 20, 2012

Copy link
Copy Markdown
Contributor

Addresses #576.

Adds "compose" functionality to units -- it's the converse of
"decompose" and tries to reduce the number of components in a
CompositeUnit.

The Quantity.si and Quantity.cgs methods can then much more easily be
implemented in terms of this functionality.

Also addresses #576 be providing a way for the user to decompose into
a particular system.

Other changes along the way:

  • PEP8-ify quantity.py
  • Privatize the "_simplify" member on units, since it's confusing in
    relation to "compose" and was never meant to be public anyway.

This doesn't have to make it into 0.2, but it doesn't really change the API in any meaningful way (note that none of the tests changed to make this all work). If it doesn't go into 0.2, it might be a good idea to at least privatize "simplify" on 0.2 so that users won't rely on it as a public method.

@mdboom

mdboom commented Dec 20, 2012

Copy link
Copy Markdown
Contributor Author

False positive on the Travis build failure.

@astrofrog

Copy link
Copy Markdown
Member

This looks great! I need to look over it in detail, but at first glance, this looks like a very useful addition.

@embray

embray commented Dec 20, 2012

Copy link
Copy Markdown
Member

Fantastic--I'm tempted to say we should try to get this into 0.2, though it would be good to get more commentary on it first.

@astrofrog

Copy link
Copy Markdown
Member

Small bug:

In [21]: unit.decompose([u.N])
ERROR: UnitsException: s can not be reduced into the requested bases [astropy.units.core]
---------------------------------------------------------------------------
UnitsException                            Traceback (most recent call last)
<ipython-input-21-6367a62f42e6> in <module>()
----> 1 unit.decompose([u.N])

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in decompose(self, bases)
   1144     def decompose(self, bases=[]):
   1145         x = CompositeUnit(self.scale, self.bases, self.powers)
-> 1146         x._expand_and_gather(True, bases=bases)
   1147         return x
   1148     decompose.__doc__ = UnitBase.decompose.__doc__

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in _expand_and_gather(self, decompose, bases)
   1115         for b, p in zip(self.bases, self.powers):
   1116             if decompose and (not len(bases) or b not in bases):
-> 1117                 b = b.decompose(bases=bases)
   1118 
   1119             if isinstance(b, CompositeUnit):

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in decompose(self, bases)
    798             raise UnitsException(
    799                 "{0} can not be reduced into the requested bases".format(
--> 800                     self))
    801 
    802         return CompositeUnit(1, [self], [1])

UnitsException: s can not be reduced into the requested bases

I don't think it should be s in the error message.

EDIT: I think I understand - s is 'seconds', right? Maybe 'Unit s can not be...' to make it clear s is the unit.

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - I haven't looked at the code in detail, but is there a reason this doesn't work?

In [33]: unit
Out[33]: Unit("kg s2 / (m)")

In [34]: (u.N * u.s**4 / u.m**2).decompose() == unit
Out[34]: True

In [35]: unit.decompose([u.N, u.s, u.m])
ERROR: UnitsException: kg can not be reduced into the requested bases [astropy.units.core]
---------------------------------------------------------------------------
UnitsException                            Traceback (most recent call last)
<ipython-input-35-73e875807862> in <module>()
----> 1 unit.decompose([u.N, u.s, u.m])

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in decompose(self, bases)
   1144     def decompose(self, bases=[]):
   1145         x = CompositeUnit(self.scale, self.bases, self.powers)
-> 1146         x._expand_and_gather(True, bases=bases)
   1147         return x
   1148     decompose.__doc__ = UnitBase.decompose.__doc__

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in _expand_and_gather(self, decompose, bases)
   1115         for b, p in zip(self.bases, self.powers):
   1116             if decompose and (not len(bases) or b not in bases):
-> 1117                 b = b.decompose(bases=bases)
   1118 
   1119             if isinstance(b, CompositeUnit):

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in decompose(self, bases)
    798             raise UnitsException(
    799                 "{0} can not be reduced into the requested bases".format(
--> 800                     self))
    801 
    802         return CompositeUnit(1, [self], [1])

UnitsException: kg can not be reduced into the requested bases

@mdboom

mdboom commented Dec 21, 2012

Copy link
Copy Markdown
Contributor Author

decompose always decomposes to base units, i.e. it only splits units into parts, it can't recompose them back into units (as you're expecting to do with Newton -- not a base unit -- here), so I think what you want to use is compose.

Unfortunately, the current version of compose always aims to make a simpler unit expression (where simple is defined as minimizing sum(abs(x) for x in powers)).

The first thing compose does is to try to factor N to see if it advances the ball. Since it doesn't, it throws it out:

In [3]: unit / u.N
Out[3]: Unit("kg s2 / (m N)")

I'll experiment with removing the simplification restriction and see if this can be made to work.

@mdboom

mdboom commented Dec 21, 2012

Copy link
Copy Markdown
Contributor Author

Try the latest commit here. And let me know if there's anything else worth doing that this can't do -- this feedback is really helpful.

@mdboom

mdboom commented Jan 2, 2013

Copy link
Copy Markdown
Contributor Author

@astrofrog : Have you had a chance to experiment with this latest version? I think this is a pretty significant upgrade to the unit capabilities -- we should get it right. Also @adrn: Any issues wrt to quantities?

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - I'll see if I can try this out this week (I'm still out of town, but on the computer from time to time).

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - I'm playing around with this a bit more, and ran into the following:

In [3]: (u.kg / u.s**3 * u.au ** 2.5 / u.yr ** 0.5 / u.sr ** 2).compose(units=u.cgs.bases)
ERROR: UnitsException: Cannot represent unit 1.540871e+27 m2.5 / (s3.5 sr2) in terms of the given units [astropy.units.core]
---------------------------------------------------------------------------
UnitsException                            Traceback (most recent call last)
<ipython-input-3-5ea583a8594a> in <module>()
----> 1 (u.kg / u.s**3 * u.au ** 2.5 / u.yr ** 0.5 / u.sr ** 2).compose(units=u.cgs.bases)

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in compose(self, equivs, units, max_depth)
    530         return self._compose(
    531             equivs=equivs, namespace=units,
--> 532             max_depth=max_depth, depth=0)
    533 
    534     def to_system(self, system):

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in _compose(self, equivs, namespace, max_depth, depth)
    457                 composed_list = composed._compose(
    458                     equivs=equivs, namespace=namespace,
--> 459                     max_depth=max_depth, depth=depth + 1)
    460                 for subcomposed in composed_list:
    461                     results.append(

/Users/tom/Library/Python/2.7/lib/python/site-packages/astropy/units/core.pyc in _compose(self, equivs, namespace, max_depth, depth)
    480         raise UnitsException(
    481             "Cannot represent unit {0} in terms of the given "
--> 482             "units".format(unit))
    483 
    484     def compose(self, equivs=[], units=None, max_depth=2):

UnitsException: Cannot represent unit 1.540871e+27 m2.5 / (s3.5 sr2) in terms of the given units

is this expected? SI works:

In [4]: (u.kg / u.s**3 * u.au ** 2.5 / u.yr ** 0.5 / u.sr ** 2).compose(units=u.si.bases)
Out[4]: [Unit("1.540871e+24 kg m2.5 / (s3.5 sr2)")]

Also, could you clarify what the difference is between e.g. decompose(bases=u.si.bases) and compose(units=u.si.bases)? Or are those the same? (if bases and units are specified).

@mdboom

mdboom commented Jan 2, 2013

Copy link
Copy Markdown
Contributor Author

Ah, I see. The thing that's throwing it off is the fractional powers.

In [8]: (u.m ** 2.5) / u.cm
Out[8]: Unit("m2.5 / (cm)")

In [9]: _.decompose()
Out[9]: Unit("1.000000e+02 m1.5")

In [10]: _ / u.cm
Out[10]: Unit("1.000000e+02 m1.5 / (cm)")

In [11]: _.decompose()
Out[11]: Unit("1.000000e+04 m0.5")

In [12]: _ / u.cm
Out[12]: Unit("1.000000e+04 m0.5 / (cm)")

In [13]: _.decompose()
Out[13]: Unit("1.000000e+06  / (m0.5)")

It never gets to anything that looks "simpler" so it sort of gives up. Of course, if it tried cm ** 2.5, it would arrive at a good answer, but it doesn't currently try fractional powers. I suspect we need to take a fundamentally different approach to handle this. Let me look into it.

To clarify the difference between decompose and compose: decompose splits composite units into their constituent parts -- this is a very straightforward operation. compose pattern matches (factors out) composite units against other known composite units. It's not surprising they arrive at the same result, but that's not guaranteed to be the case, and they take fundamentally different approaches.

@mdboom

mdboom commented Jan 3, 2013

Copy link
Copy Markdown
Contributor Author

Ok -- I have a fix for the fractional powers problem. The nice thing is that compose gives a lot more candidates than it used to.

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - the example I was posting before works nicely now!

We may want to think in future about whether we really want to allow any set of units for compose by default, since things like:

 Unit("1.793142e+72 m(5/2) M_p / (fortnight(7/2) rad4)"),

are a bit silly (and it can be pretty slow). For example, maybe compose should default to a certain basis such as SI or cgs.

I noticed one more bug - there appears to be duplicates:

>>> new = u.kg / u.s**3 * u.au ** 2.5 / u.yr ** 0.5 / u.sr ** 2 
>>> new.compose(units=u.cgs.bases)
[Unit("1.540871e+32 cm(5/2) g / (rad4 s(7/2))"),
 Unit("1.540871e+32 cm(5/2) g / (rad4 s(7/2))")]

Other than that, this looks good to me, so once that's fixed, I think this is fine to merge (since it doesn't change the existing API).

@mdboom

mdboom commented Jan 11, 2013

Copy link
Copy Markdown
Contributor Author

I've attached a commit to deal with the duplicate result problem.

I'm not opposed to the default base being SI for compose, but it is a little inconsistent with other things where this default is always "everything" (including user-defined units). I wonder if there's any other ideas... will have to think on that.

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - I agree, it's something to ponder about, though we don't need to hold up this PR. The current behavior is perfectly fine.

@mdboom

mdboom commented Jan 14, 2013

Copy link
Copy Markdown
Contributor Author

Ok. In my view the only thing remaining for this PR is a quick look-over from @adrn w.r.t. Quantities, since it completely replaces the si and cgs methods there.

@astrofrog

Copy link
Copy Markdown
Member

@adrn - could you review this PR as requested by @mdboom above?

@mdboom - could you rebase?

@adrn

adrn commented Jan 20, 2013

Copy link
Copy Markdown
Member

@mdboom This looks great! I like the new stuff in Quantity.decomposed_unit. I didn't take a close look at the documentation, but once this PR is through I may have some suggestions there. Thanks for the work on this!

@mdboom

mdboom commented Jan 23, 2013

Copy link
Copy Markdown
Contributor Author

@astrofrog: Rebased.

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - can we merge this once the tests pass, or was there anything else you wanted to add?

@mdboom

mdboom commented Jan 23, 2013

Copy link
Copy Markdown
Contributor Author

I think this is good to go, if everyone else agrees.

@astrofrog

Copy link
Copy Markdown
Member

Ok, great! Feel free to merge.

@embray

embray commented Jan 23, 2013

Copy link
Copy Markdown
Member

I've already started rebasing the work @astrofrog and I started in #572 off of this. Hopefully it will lead quickly to a solution to that issue.

@embray

embray commented Jan 23, 2013

Copy link
Copy Markdown
Member

One problem I'm getting now is this:

>>> from astropy.cosmology import core
[Unit("C")] [Unit("C")]
ERROR: UnitsException: Unit K can not be decomposed into the requested bases [astropy.units.core]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "astropy/cosmology/__init__.py", line 9, in <module>
    from core import *
  File "astropy/cosmology/core.py", line 45, in <module>
    a_B_c2 = 4 * const.sigma_sb.cgs.value / const.c.cgs.value**3
  File "astropy/units/quantity.py", line 136, in cgs
    cgs_unit = self.unit.to_system(cgs)[0]
  File "astropy/units/core.py", line 611, in to_system
    x = self.decompose(bases=bases)
  File "astropy/units/core.py", line 1229, in decompose
    x._expand_and_gather(True, bases=bases)
  File "astropy/units/core.py", line 1200, in _expand_and_gather
    b = b.decompose(bases=bases)
  File "astropy/units/core.py", line 878, in decompose
    "bases".format(self))
astropy.units.core.UnitsException: Unit K can not be decomposed into the requested bases

Should Kelvin be one of the CGS base units?

@embray

embray commented Jan 23, 2013

Copy link
Copy Markdown
Member

It looks like we already add C and cd as bases in CGS to avoid this sort of problem, so I guess we can do the same for K, even though it's really a derived unit?

@astrofrog

Copy link
Copy Markdown
Member

Hmm, would it be overkill to have a test to check that all units can be decomposed into cgs and SI? (how slow would it be?)

Comment thread astropy/units/core.py Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks like a debug statement that was left in?

@embray

embray commented Jan 23, 2013

Copy link
Copy Markdown
Member

Well there's no reason you can't use K in cgs--the problem is that SI lists it as a base unit and cgs doesn't. You could convert to K to erg by way of Boltzmann's constant and then decompose that to cgs base units, but I don't think anybody would actually want to do that.

@mdboom

mdboom commented Jan 24, 2013

Copy link
Copy Markdown
Contributor Author

I'm fine with adding K as a base unit for CGS.

As for checking that all units can be decomposed into CGS and SI, I don't think that's possible. The CGS system has no equivalent of the Ampere in SI, meaning the conversion from Coulomb to Statcoulomb depends on context:

http://en.wikipedia.org/wiki/Statcoulomb

We would need to provide equivalencies for this, and the user would need to select which equivalency applied.

We could, however, test some subset of units that we know should work.

Aside from this, there are also units in our system that FITS defines (such as pixel and voxel) that don't really have SI or CGS equivalents (and are arguably not physical units anyway).

@embray

embray commented Jan 25, 2013

Copy link
Copy Markdown
Member

According to Wikipedia anyways mole is actually an SI base unit (which to me seems a bit odd but okay).

@astrofrog

Copy link
Copy Markdown
Member

@iguananaut - I saw that too, but I think it's nonsense :-)

@astrofrog

Copy link
Copy Markdown
Member

@mdboom - this seems fine to me! Let's make sure the tests pass on Travis, and if they do, then we can go ahead and merge!

@embray

embray commented Jan 25, 2013

Copy link
Copy Markdown
Member

+1 from me

mdboom added 15 commits January 25, 2013 16:00
"decompose" and tries to reduce the number of components in a
CompositeUnit.

The Quantity.si and Quantity.cgs methods can then much more easily be
implemented in terms of this functionality.

Also addresses astropy#576 be providing a way for the user to decompose into
a particular system.

Other changes along the way:

  - PEP8-ify quantity.py

  - Privatize the "_simplify" member on units, since it's confusing in
    relation to "compose" and was never meant to be public anyway.
… complexity of the unit as it works. This allows expressions to me composed into more complex expressions when the set of desired units requires it. (Point raised by @astrofrog in the PR).  This is a slightly back-to-the-drawing-board approach for compose.
…that is most useful (simplest results first).
and cgs to include some other tertiary units.  Add tests that make
sure that all SI units can convert to CGS and vice verse (with the
exception of units containing Ampere).
mdboom added a commit that referenced this pull request Jan 25, 2013
@mdboom
mdboom merged commit d3f5edc into astropy:master Jan 25, 2013
mdboom added a commit that referenced this pull request Jan 25, 2013
@eteq

eteq commented Jan 25, 2013

Copy link
Copy Markdown
Member

I think the mole is an SI unit because chemists want SI units for things like molarity and molality or whatever. So probably not too much of an issue for us

keflavich pushed a commit to keflavich/astropy that referenced this pull request Oct 9, 2013
@mdboom
mdboom deleted the unit/better_cgs_handling 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.

5 participants