Better performance of the beam interpolation with faster domain checking and spline reuse - #1716
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1716 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 71 71
Lines 23898 23912 +14
=========================================
+ Hits 23898 23912 +14 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
This looks like a great improvement, thank you! It looks like We could try it out on a branch off of this one to make sure there's no issues in CI (the one thing I'm a little worried about is intel mac builds, some packages are not available for intel macs...) |
|
@tyler-a-cox I made a branch ( |
|
@bhazelton thanks for taking a look at this and for checking the versioning! I'll go ahead and bump the version requirement. That said, dropping the So I think it's either keep that bit of the |
| not reuse_spline | ||
| and spline_opts.get("s", 0) == 0 | ||
| and not set(spline_opts).difference({"kx", "ky", "s"}) | ||
| and hasattr(interpolate, "NdBSpline") |
There was a problem hiding this comment.
This selection criteria is pretty hidden from the user. They might prefer to use this new approach but not realize that setting reuse_spline=True (which sounds like it will save time from the current docstring) will prevent it.
We can fix this in two ways:
- one is to add a bunch of info to the docs to help people pick which parameters and spline opts to use.
- another option would be to make the NdBSpline approach have an entirely separate function that users can choose to use. This is what we did with the
map_coordinateoption. This would still require some significant docs updates but it might be more transparent to a user. Given that this generally gives the same results, I'm fine with changing the default to be this if the spline_opts are compatible.
I'm not at all sure which of these is better, I don't want to duplicate a lot of code and I also want to make this both easy and clear for users with good defaults. I think there's not great guidance in the existing docs about the choice between az_za_simple and az_za_map_coordinates.
I am not aware that anyone uses any spline opts beyond kx and ky, but I agree that keeping around the full flexibility is nice.
There was a problem hiding this comment.
Thanks for taking a look! I agree, that behavior made things unclear for the user. I've made some changes based on your feedback.
I decided to keep the NdBSpline and RectBivariateSpline paths in the same function, and to make NdBSpline the default whenever only kx and ky are passed. If anything else is passed, it falls back to RectBivariateSpline. I also added spline caching to the NdBSpline path, so reuse_spline=True now works for both and no longer decides which one you get.
The one decision I wanted your input on is bumping the minimum scipy to 1.15. Even spline orders above 2 aren't supported before that, so kx=4 raises an error on 1.12. The other option is to stay at 1.12 and fall back to RectBivariateSpline for the orders that aren't supported, though that means carrying a special case for order 4. Curious to hear your thoughts.
There was a problem hiding this comment.
I like the approach you took and I love the new docs, thank you!!
I'm ok with going to scipy 1.15 since all the CI works with it and it's > 1 year old. Much better than maintaining complicated work-arounds.
| ### Changed | ||
| - Improved `UVBeam` azimuth/zenith spline interpolation performance by sharing the | ||
| tensor-product basis across beam data slices and using vectorized domain checks. | ||
| `scipy.interpolate.NdBSpline` is now used whenever `spline_opts` requests only the | ||
| spline orders `kx`/`ky` (the default), independent of `reuse_spline`; | ||
| `scipy.interpolate.RectBivariateSpline` remains in use for smoothing (`s != 0`) and | ||
| for any other RectBivariateSpline-only option. Both engines solve the same | ||
| interpolating spline for a given `kx`/`ky` with `s = 0`. | ||
| - Minimum supported version of scipy is now 1.15, required for | ||
| `scipy.interpolate.NdBSpline` (added in 1.12) and for interpolating splines of even | ||
| order above 2 (added in 1.15), which `spline_opts` has always accepted. |
There was a problem hiding this comment.
We just put out a new version to get a bug fix out to our users, so this branch needs to be rebased on main and all of this needs to end up in the unreleased section.
Also, please note the scipy dependency change, you can see other examples of that lower in this changelog.
ee57bb6 to
0668518
Compare
|
@bhazelton Okay, I just changed the CHANGELOG so I think this should be good to go! One of the tests is failing, but it passed before I made this last commit so I think it might just need to be re-run. |
|
Ack! I'm so sorry! I merged another PR which made another changelog conflict. 🤦♀️ I should have merged yours first. Can you please rebase again? |
This reverts commit fe82125.
0668518 to
15ca6d0
Compare
|
Ahhh gotcha, no problem! Just rebased and it looks like tests are passing |
bhazelton
left a comment
There was a problem hiding this comment.
Thank you @tyler-a-cox ! This is fantastic.
Description
This PR improves the performance of
UVBeamazimuth/zenith spline interpolation without changing the public API.The changes:
np.searchsorted.s=0withkx/kyoptions), construct the tensor-product spline coefficients once and evaluate all beam data slices together usingNdBSpline.RectBivariateSplineimplementation as a fallback when spline reuse is requested, smoothing or other custom options are used, orNdBSplineis unavailable.RectBivariateSpline’s existing boundary-value behavior.reuse_spline=True. The new fast path does not create a persistent cache.Motivation and Context
Interpolating a
UVBeamcan be slow when evaluating large numbers of source positions. The previous implementation repeatedly scanned the coordinate grid during domain checking and constructed and evaluated separate bivariate splines for each beam data slice.The new implementation addresses these costs independently:
np.searchsorted, avoiding repeated scans of either the interpolation points or coordinate grid.A representative local benchmark used 10 million interpolation points, a
91 × 360beam grid, one frequency, and a complex E-field beam with two vector axes and two feeds:RectBivariateSplinenp.searchsortedRectBivariateSplinenp.searchsortedNdBSplineThe intermediate measurement changes only the domain checker, reducing the end-to-end runtime from 81.6 to 33.2 seconds. Changing only the spline implementation after that reduces the runtime by a further factor of approximately 3.4, from 33.2 to 9.9 seconds.
A warm run using the existing
reuse_spline=Truecache and the optimized domain checker took approximately 33.2 seconds in the same benchmark. This indicates that the additional improvement from the batched path comes primarily from faster spline evaluation rather than caching.Testing
The added tests:
RectBivariateSplineimplementation for both real power beams and complex E-field beams.kx/kyorders.1e-12, including points near the grid boundaries.The new interpolation and domain-check test set passes with 34 tests.
Types of changes
Checklist
Other: