Add nested multi-resolution volume render backend - #337
Conversation
Implement efficient Sparse Multi-Scale Grid algorithm (Benitez-Llambay 2025) for 3D volume rendering of SPH data. Particles are hierarchically assigned to grid levels based on kernel smoothing length, then scattered onto coarse-to-fine grids and upsampled back to target resolution. This bounds per-particle work regardless of smoothing length, providing significant speedups for simulations with wide kernel distributions. Features: - New nested grid backend accessible via backend="nested" parameter - Adaptive hierarchy with configurable target voxels per kernel (ntarget) and number of levels (nlevels) - Kernel arithmetic matches scatter.py implementation (Wendland-C2) - Parallel support via Numba prange - Trilinear upsampling for hierarchy collapse - Efficient bounding-box tracking to skip empty regions API additions: - render_voxel_grid() and render_gas() now accept backend, ntarget, nlevels - backends dict updated to include "nested" alongside "scatter" - Added validation and warnings for backend-specific parameters Tests: - Added TestNestedVolumeRender class with serial/parallel matching tests - Tests cover periodic boundary conditions and edge cases - Comparison against single-resolution backend validates correctness Documentation: - Updated volume_render.rst with nested backend description and examples - Added parameter documentation for ntarget and nlevels Code quality: - Passes ruff linter - Passes numpydoc validation - All existing tests continue to pass Refs: Benitez-Llambay A., 2025, MNRAS (in prep) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Thanks Owen, looks promising. Presumably this can be trivially translated to the 2D ("project") case? And maybe slice, too? Or do you think it's not worth it because that scales as N^2 instead of N^3? |
|
Yes this could be trivially extended to 2d projection and slice and would scale much better than the current implementations. I just haven't had a chance to implement this and ensure all the formatting etc is correct and the documentation written. |
|
Great, and not a problem - marking this as draft for now if you plan to add that (if not feel free to mark ready for review). |
|
i plan to add eventually but not sure when I can get around to it. I will submit this ready for review for now. |
kyleaoman
left a comment
There was a problem hiding this comment.
Hi Owen,
Sorry to leave this hanging for a while, for some reason I thought that you were still planning edits. Anyway some suggestions for you here, all fairly straightforward. Will want to make sure Josh is happy to merge this before we do, too.
| resolution=256, | ||
| project="masses", | ||
| parallel=True, | ||
| backend="scatter", |
There was a problem hiding this comment.
| backend="scatter", | |
| backend="scatter", # the default |
|
|
||
| data = load("cosmo_volume_example.hdf5") | ||
|
|
||
| # Standard backend — identical results, familiar behaviour. |
There was a problem hiding this comment.
| # Standard backend — identical results, familiar behaviour. | |
| # Standard backend |
| :math:`\mathcal{O}(n_\mathrm{target}^3)` regardless of smoothing length by | ||
| scattering large particles onto a coarser grid and trilinearly upsampling | ||
| the result back to the finest grid afterwards. This follows the Sparse | ||
| Multi-Scale Grid approach described in Benitez-Llambay (2025). |
There was a problem hiding this comment.
| Multi-Scale Grid approach described in Benitez-Llambay (2025). | |
| Multi-Scale Grid approach described in `Benítez-Llambay (2025)`_. | |
| _Benítez-Llambay (2025): https://iopscience.iop.org/article/10.3847/2515-5172/addab2 |
| received mass is upsampled at each level, so the collapse cost scales with | ||
| the number of particles rather than the grid volume. |
There was a problem hiding this comment.
| received mass is upsampled at each level, so the collapse cost scales with | |
| the number of particles rather than the grid volume. | |
| received mass is upsampled at each level, so the collapse cost scales | |
| approximately with the number of particles rather than the grid volume. |
| out = backends["nested"]( | ||
| x=x, y=y, z=z, h=h, m=m, res=res, | ||
| ntarget=6, | ||
| nlevels=4, | ||
| ) |
There was a problem hiding this comment.
| out = backends["nested"]( | |
| x=x, y=y, z=z, h=h, m=m, res=res, | |
| ntarget=6, | |
| nlevels=4, | |
| ) | |
| out = backends["nested"]( | |
| x=x, | |
| y=y, | |
| z=z, | |
| h=h, | |
| m=m, | |
| res=res, | |
| ntarget=6, | |
| nlevels=4, | |
| ) |
| Controls the accuracy/speed trade-off. Default is 6. | ||
|
|
||
| nlevels : int | ||
| Maximum number of coarsening levels in the hierarchy. Default is 4. |
There was a problem hiding this comment.
| Maximum number of coarsening levels in the hierarchy. Default is 4. | |
| Maximum number of coarsening levels in the hierarchy. Default is ``4``. |
|
|
||
| Returns | ||
| ------- | ||
| np.ndarray[np.float32, np.float32, np.float32] |
There was a problem hiding this comment.
Is content of brackets correct? Thought it was type then shape?
|
|
||
| ntarget : int | ||
| Target number of voxels spanned by each kernel at its assigned level. | ||
| Controls the accuracy/speed trade-off. Default is 6. |
There was a problem hiding this comment.
| Controls the accuracy/speed trade-off. Default is 6. | |
| Controls the accuracy/speed trade-off. Default is ``6``. |
| Controls the accuracy/speed trade-off. Default is 6. | ||
|
|
||
| nlevels : int | ||
| Maximum number of coarsening levels in the hierarchy. Default is 4. |
There was a problem hiding this comment.
| Maximum number of coarsening levels in the hierarchy. Default is 4. | |
| Maximum number of coarsening levels in the hierarchy. Default is ``4``. |
|
|
||
| Returns | ||
| ------- | ||
| np.ndarray[np.float32, np.float32, np.float32] |
There was a problem hiding this comment.
Again hint on the dtype and shape is incorrectly formatted?
Implement efficient Sparse Multi-Scale Grid algorithm (Benitez-Llambay 2025)
for 3D volume rendering of SPH data. Particles are hierarchically assigned
to grid levels based on kernel smoothing length, then scattered onto
coarse-to-fine grids and upsampled back to target resolution. This bounds
per-particle work regardless of smoothing length, providing significant
speedups for simulations with wide kernel distributions.
Features:
and number of levels (nlevels)
API additions:
Tests:
Documentation:
Code quality: