Skip to content

Speed up distance calculations by ~10-20x - #3767

Merged
eteq merged 9 commits into
astropy:masterfrom
aconley:cosmo_vtable
May 28, 2015
Merged

eteq merged 9 commits into
astropy:masterfrom
aconley:cosmo_vtable

Conversation

@aconley

@aconley aconley commented May 11, 2015

Copy link
Copy Markdown
Contributor
  • Break out some internal functions for the integrators to call
    directly
  • Tests pass, distances are about 10x faster than before, now
    about 5x faster than cosmolopy.
  • Related to slow comoving_distance in astropy.cosmology #3764, but doesn't necessarily close it.

This is only done for a) distances, b) FlatLambdaCDM. The idea
is to show exactly how ugly this solution is so we can decide
if it's worth implementing for the other cosmologies. Sure
does speed things up though...

Alex Conley added 3 commits May 11, 2015 10:26
* Break out some internal functions for the integrators to call
  directly
* Tests pass, distances are about 10x faster than before, now
  about 5x faster than cosmolopy.

This is only done for a) distances, b) FlatLambdaCDM.  The idea
is to show exactly how ugly this solution is so we can decide
if it's worth implementing for the other cosmologies.  Sure
does speed things up though...
* Unwrap the distance function to integrate for other classes
* Note that cosmologies with massive neutrinos are still very
  slow, as nu_relative_density is apparently quite expensive
  to calculate.  Added a note to this effect to the docs.
* Remove an unused assignment to Ok0
@nhmc

nhmc commented May 11, 2015

Copy link
Copy Markdown
Contributor

Would changing all isiterable() tests to a try-except structure also work? If so, that would be a cleaner solution.

@aconley

aconley commented May 12, 2015

Copy link
Copy Markdown
Contributor Author

Short answer: No.

That's what isiterable is -- a try-except. I guess those are expensive... so it isn't asarray, that's not being called ever in the quad loop.

Note that getting rid of the isiterables is only about a factor of 2-3x speedup. Having individual functions that avoid calculating things they don't need to is another 2-3x speedup.

@aconley

aconley commented May 12, 2015

Copy link
Copy Markdown
Contributor Author

To provide a slightly more nuanced option: a big chunk of the expense is that np.sqrt and np.exp are much more expensive than the math versions on scalars. Maybe they are doing a wrap, unwrap. Also, the massive neutrino calculation is very expensive, as is testing conditions inside the inner function. So the important things are:

  • Have a scalar only version of the integrand that doesn't need to test for being a scalar and can call the math versions of functions.
  • Have a branch outside the integrand to test if we need to compute neutrino stuff.

So we could only have two "special" versions of the scalar integrand: with massive neutrinos, and without, and could eliminate the no-radiation version. That's only like 5-10% slower than the fast version. My thinking was once I'm committed to having two ugly internal versions of inv_efunc, might as well squeeze that extra bit and have three.

@nhmc

nhmc commented May 12, 2015

Copy link
Copy Markdown
Contributor

About isiterable: there's no overhead for code executed in the try part of a try-except statement, but a large overhead associated with catching an exception. isiterable throws an exception every time it runs on a scalar, which is slow - on my machine it's ~7 times slower for a scalar compared to an array. So if we use a try-except that tries to do an integral assuming the redshift is a scalar, then that will avoid the expensive exception in isiterable.

I see your point about the individual functions to avoid unnecessary calculations, though. I guess we can't avoid making special versions of integrands for that.

@aconley

aconley commented May 12, 2015

Copy link
Copy Markdown
Contributor Author

Wouldn't the computation that fails on an array just be the "special" functions that use math.sqrt? That wouldn't really avoid any of the ugliness... and avoiding doing if/then tests inside those functions makes a pretty big difference too. So it seems kinda like the same thing in a different order, unless you have something cleverer in mind.

Comment thread astropy/cosmology/core.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why make this change? The function gets only passed on once.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That's a leftover from an earlier attempt to make it faster.

@mhvk

mhvk commented May 12, 2015

Copy link
Copy Markdown
Contributor

@aconley - Speed-ups for common cases are a good idea, and I think that by factoring out some of the boilerplate code, this could be made to look a lot less ugly (see inline comments).

@pllim

pllim commented May 12, 2015

Copy link
Copy Markdown
Member

In case you want to profile the code, you can see if https://jiffyclub.github.io/snakeviz/ works for you.

@aconley

aconley commented May 12, 2015

Copy link
Copy Markdown
Contributor Author

@pllim - I was just using cProfile, which I think told me everything I needed to know.

@mhvk -- let's not kid ourselves. This is ugly as hell either way.

The idea here is not to yet put this in it's final form, just to see if people are perhaps willing to pay this price.

@pllim

pllim commented May 12, 2015

Copy link
Copy Markdown
Member

If it is 10x improvement, I think speed outweighs ugliness. Pretty codes that no one uses are useless.

@aconley

aconley commented May 12, 2015

Copy link
Copy Markdown
Contributor Author

I should point out: the Planck cosmologies, which I imagine will become the default fairly soon, are still very slow. That's because they include massive neutrinos, and that turns out to be quite expensive. So the next effort -- even if this one is rejected -- should be to try to figure out how to make that faster.

* Add internal variables _scalar_inv_efunc and _scalar_inv_efunc_args
  set by the constructor that are used for integrands.  This allows
  us to remove overloading of comoving_distance.
* Minor tweaks to specialized integral forms to make them faster.
* Add some unit tests of a wider range of cosmologies to ensure
  all the fancy new bits work.
* Add vectorize_if_needed helper function.
@aconley aconley changed the title Speed up distance calculations by ~10x. Only done for FlatLambdaCDM. Speed up distance calculations by ~10-20x May 12, 2015
Comment thread astropy/cosmology/core.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It will be much faster for scalars if we assume a scalar and catch the exception instead of using isiterable:

def vectorize_if_needed(func, x):
    """ Helper function to vectorize functions on array inputs"""
    try:
        return func(x)
    except TypeError:
        return np.vectorize(func)(x)

This will be a little slower for array inputs. But I think that's ok, because in that case there typically won't be a large number of function calls.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Unfortunately that doesn't work, because you can't guarantee that func will fail with a TypeError. For example, if I try your suggested change change, scipy.integrate.quad fails with ValueErrors. A lot. And I'm leery of just catching any exception.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ok. I also tested this and the speedup is not so important, so it's fine as you have it.

@nhmc

nhmc commented May 13, 2015

Copy link
Copy Markdown
Contributor

I completely agree a 10x speedup is worth any ugliness.

Note the current version breaks backwards compatibility for _tfunc and _xfunc, since they no longer accept array input. In theory this is ok because the leading underscore is supposed to tell people "don't use this, it's an internal method which might change in the future". But in practice people are probably using them anyway, and will get annoyed when it's changed. This could be avoided by making new functions (for example) _xfunc_scalar and then having _xfunc wrap it with the extra isiterable check (or the try/except example I gave in the in-line comment). I strongly prefer doing this to avoid breaking backwards compatibility.

@aconley

aconley commented May 13, 2015

Copy link
Copy Markdown
Contributor Author

Hmm... I don't like people using _tfunc, _xfunc, and I don't think people should have much of a right to be annoyed at us changing them.

Would it be acceptable to create promoted non-scalar versions xfunc and tfunc, where that creates an implicit promise not to mess with them except at major version number boundaries?

…gelog.

Changes to external visibility of absorption and time integrands.
The idea is to eventually turn _xfunc, _tfunc into pure scalar
versions used in the integrations, but provide a vectorized version
publicly for anyone who needs it.
* Add externally visible xfunc, tfunc to FLRW that can still handle vectors
  for people that use them.
* Keep _xfunc _tfunc as able to handle vectors in case anybody out
  there was being bad and using things beginning with _.
  Add deprecation warning telling people to use xfunc, tfunc instead.
* Add _xfunc_scalar, _tfunc_scalar for integrals.

Some internal plumbing changes:
* rename scalar inv efunc functions to include class name.  This is to
  avoid pylint from complaining about overloaded functions with different
  numbers of variables.
* Make Flat cosmologies call their superclass constructors instead of
  FLRW.__init__.
* Remove some dead code in angular_diameter_distance_z1z2 -- it was
  swapping z1, z2 if they were out of order, but that couldn't be
  reached because the previous line was raising an error for that
  condition anyways (since swapping doesn't work for array z1, z2 anyways!).

Add some tests
* Add some tests of the different distance integrands to make
  sure nothing got broken.  These just compare to what HEAD got
  before the speedup rather than external sources -- but caught
  some bugs, and so are totally worth including!
* Some additional minor unit tests, mostly to get the coverage checker
  to shut up about reduced coverage.  If the idea of adding that
  was to shame people into adding more tests, well, apparently it works
  on me.
@aconley

aconley commented May 13, 2015

Copy link
Copy Markdown
Contributor Author

@nhmc -- the recent update returns _tfunc and _xfunc to their previous behavior, adds new public versions (tfunc and xfunc), and throws deprecation warnings on the underscore versions. After all, we do provide public access to the distance integrand through efunc and inv_efunc.

You don't want to use np.vectorize (or, rather, the new wrapper) to go between them -- and between the scalar and ndarray versions in general -- because it can be much slower than just computing on the array. vectorize is just a for loop, and therefore doesn't take full advantage of numpy's capabilities. We use it in the integrals because quad already kills the ndarray advantage, and because the individual integrations are pretty expensive already.

Comment thread astropy/cosmology/core.py Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you change this to _inv_efunc_scalar for consistency with _tfunc_scalar and _xfunc_scalar?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sure, will do.

@nhmc

nhmc commented May 14, 2015

Copy link
Copy Markdown
Contributor

They definitely have no right to be annoyed, but that doesn't stop them from getting annoyed and tweeting 'astropy broke my code!'

I left some inline comments, but everything else looks fine to me.

…xt]func

* Change depracation warnings on `_tfunc`, `_xfunc` to no longer say
  they will become scalar.
* Added non-standard subclass test for efunc, inv_efunc
@aconley

aconley commented May 16, 2015

Copy link
Copy Markdown
Contributor Author

Hmm... something went screwy with Travis. It doesn't seem to have scipy.integrate installed.

@astrofrog

Copy link
Copy Markdown
Member

@aconley - remember that only some of the travis builds have optional dependencies (including scipy) installed - you need to make sure the tests are skipped if scipy is not installed.

* It turns out that one of the new tests requires scipy
  because it -- unlike the built-in-subclasses- actually calls
  de_density_scale.
@aconley

aconley commented May 18, 2015

Copy link
Copy Markdown
Contributor Author

@astrofrog -- yeah, I knew that. I just didn't think I had added any tests that required scipy, and it turns out I had in a slightly subtle fashion. Anyways, finally should be ready to go.

@eteq eteq added this to the v1.1.0 milestone May 22, 2015
@eteq

eteq commented May 22, 2015

Copy link
Copy Markdown
Member

I also agree this is enough of a speedup to justify the ugliness, especially given the importance of this to plenty of modern astro.

I'm happy with most of it, but two relatively straightforward items:

  • I'm fine with the publicizing of xfunc and tfunc, but I think they should get more descriptive names. E.g., xfunc -> abs_distance_integrand tfunc -> lookback_time_integrand (or lb_time_integ if that seems to awkward, although this is still mostly internal so I tend towards explicit names if it's not going to be typed alot). To complement this, it might make sense to replace efunc with hubble_scaling (although it should probably call efunc to maintain backwards compatibility for the foreseeable future).
  • It might make sense to link to this issue or even write a brief note somewhere in the class docstrings about the key speedups made here. It might help possible future contributors (or future cosmologies...) have an easier time of it if they want to take similar roads.

And just to make sure we're all on the same page, you'd rather address any possible optimizations for the massive neutrinos in a later separate PR, right @aconley?

@aconley

aconley commented May 22, 2015

Copy link
Copy Markdown
Contributor Author

@eteq -- definitely. I looked at the neutrinos briefly, and it seems like it will be harder, so it makes sense to do this in stages.

I don't think we should rename efunc -- people may actually use that. And the kind of people looking for it will not be surprised by the name. The E(z) notation is incredibly common.

Alex Conley added 2 commits May 22, 2015 15:53
* xfunc -> abs_distance_integrand
* tfunc -> loockback_time_integrand
* Fix call in age to use _scalar version
* Add a long note to the top of cosmology/core.py describing
  how to use the scalar distance integrand speedups for interested parties.
* Briefly point users to that note from sphinx cosmology docs.
@eteq

eteq commented May 28, 2015

Copy link
Copy Markdown
Member

Awesome. This looks great and everything is passing, so I'll go ahead and merge. Thanks @aconley !

eteq added a commit that referenced this pull request May 28, 2015
Speed up some cosmology distance calculations by ~10-20x
@eteq
eteq merged commit c349407 into astropy:master May 28, 2015
aconley pushed a commit to aconley/astropy that referenced this pull request Jun 3, 2015
* Follow on to astropy#3767, which is partially addressing astropy#3764
* Add scalar function to compute massive neutrino densities in
  integrals to get speedup for arbitrary number of neutrinos.
  This buys about 20% speedup.
* Add 1, 2, and 3 massive neutrino specializations for much larger
  speedups by avoiding the (surprisingly) expensive numpy.sum
  operations.  These should be by far the most common cases.
* Some unit tests added for the new scalar functions
@aconley

aconley commented Jun 3, 2015

Copy link
Copy Markdown
Contributor Author

@eteq -- There is now a first attempt at the neutrinos are slow problem: #3814

ntessore pushed a commit to glass-dev/cosmology that referenced this pull request Dec 7, 2020
Speed up some cosmology distance calculations by ~10-20x
ntessore pushed a commit to glass-dev/cosmology that referenced this pull request Dec 8, 2020
Speed up some cosmology distance calculations by ~10-20x
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants