Skip to content

Commit 29fc8a6

Browse files
authored
Implementing morton and hilbert for Grid and GridBatch ijk (#311)
This PR is the work of Hexu Zhao. Functions have been added to Grid and GridBatch which produce a uint64_t tensor of the morton or hilbert encodings of the ijk or kji coordinate orderings. Signed-off-by: Christopher Horvath <chorvath@nvidia.com>
1 parent c2974d9 commit 29fc8a6

File tree

13 files changed

+883
-0
lines changed

13 files changed

+883
-0
lines changed

fvdb/_Cpp.pyi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,10 @@ class GridBatch:
356356
def inject_to(self, dst_grid: GridBatch, src: JaggedTensor, dst: JaggedTensor) -> None: ...
357357
def dual_bbox_at(self, arg0: int) -> torch.Tensor: ...
358358
def dual_grid(self, exclude_border: bool = ...) -> GridBatch: ...
359+
def hilbert(self, offset: torch.Tensor) -> JaggedTensor: ...
360+
def hilbert_zyx(self, offset: torch.Tensor) -> JaggedTensor: ...
361+
def morton(self, offset: torch.Tensor) -> JaggedTensor: ...
362+
def morton_zyx(self, offset: torch.Tensor) -> JaggedTensor: ...
359363
def grid_to_world(self, ijk: JaggedTensor) -> JaggedTensor: ...
360364
def ijk_to_index(self, ijk: JaggedTensor, cumulative: bool = False) -> JaggedTensor: ...
361365
def ijk_to_inv_index(self, ijk: JaggedTensor, cumulative: bool = False) -> JaggedTensor: ...

fvdb/grid.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2296,6 +2296,83 @@ def ijk(self) -> torch.Tensor:
22962296
"""
22972297
return self._impl.ijk.jdata
22982298

2299+
def morton(self, offset: torch.Tensor | None = None) -> torch.Tensor:
2300+
"""
2301+
Return Morton codes (Z-order curve) for active voxels in this grid.
2302+
2303+
Morton codes use xyz bit interleaving to create a space-filling curve that
2304+
preserves spatial locality. This is useful for serialization, sorting, and
2305+
spatial data structures.
2306+
2307+
Args:
2308+
offset: Optional offset to apply to voxel coordinates before encoding.
2309+
If None, uses the negative minimum coordinate across all voxels.
2310+
2311+
Returns:
2312+
torch.Tensor: A tensor of shape `[num_voxels, 1]` containing
2313+
the Morton codes for each active voxel.
2314+
"""
2315+
if offset is None:
2316+
offset = -torch.min(self.ijk, dim=0).values
2317+
return self._impl.morton(offset).jdata
2318+
2319+
def morton_zyx(self, offset: torch.Tensor | None = None) -> torch.Tensor:
2320+
"""
2321+
Return transposed Morton codes (Z-order curve) for active voxels in this grid.
2322+
2323+
Transposed Morton codes use zyx bit interleaving to create a space-filling curve.
2324+
This variant can provide better spatial locality for certain access patterns.
2325+
2326+
Args:
2327+
offset: Optional offset to apply to voxel coordinates before encoding.
2328+
If None, uses the negative minimum coordinate across all voxels.
2329+
2330+
Returns:
2331+
torch.Tensor: A tensor of shape `[num_voxels, 1]` containing
2332+
the transposed Morton codes for each active voxel.
2333+
"""
2334+
if offset is None:
2335+
offset = -torch.min(self.ijk, dim=0).values
2336+
return self._impl.morton_zyx(offset).jdata
2337+
2338+
def hilbert(self, offset: torch.Tensor | None = None) -> torch.Tensor:
2339+
"""
2340+
Return Hilbert curve codes for active voxels in this grid.
2341+
2342+
Hilbert curves provide better spatial locality than Morton codes by ensuring
2343+
that nearby points in 3D space are also nearby in the 1D curve ordering.
2344+
2345+
Args:
2346+
offset: Optional offset to apply to voxel coordinates before encoding.
2347+
If None, uses the negative minimum coordinate across all voxels.
2348+
2349+
Returns:
2350+
torch.Tensor: A tensor of shape `[num_voxels, 1]` containing
2351+
the Hilbert codes for each active voxel.
2352+
"""
2353+
if offset is None:
2354+
offset = -torch.min(self.ijk, dim=0).values
2355+
return self._impl.hilbert(offset).jdata
2356+
2357+
def hilbert_zyx(self, offset: torch.Tensor | None = None) -> torch.Tensor:
2358+
"""
2359+
Return transposed Hilbert curve codes for active voxels in this grid.
2360+
2361+
Transposed Hilbert curves use zyx ordering instead of xyz. This variant can
2362+
provide better spatial locality for certain access patterns.
2363+
2364+
Args:
2365+
offset: Optional offset to apply to voxel coordinates before encoding.
2366+
If None, uses the negative minimum coordinate across all voxels.
2367+
2368+
Returns:
2369+
torch.Tensor: A tensor of shape `[num_voxels, 1]` containing
2370+
the transposed Hilbert codes for each active voxel.
2371+
"""
2372+
if offset is None:
2373+
offset = -torch.min(self.ijk, dim=0).values
2374+
return self._impl.hilbert_zyx(offset).jdata
2375+
22992376
@property
23002377
def num_bytes(self) -> int:
23012378
"""

fvdb/grid_batch.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2585,6 +2585,95 @@ def ijk(self) -> JaggedTensor:
25852585
"""
25862586
return JaggedTensor(impl=self._impl.ijk)
25872587

2588+
def morton(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
2589+
"""
2590+
Return Morton codes (Z-order curve) for active voxels in this grid batch.
2591+
2592+
Morton codes use xyz bit interleaving to create a space-filling curve that
2593+
preserves spatial locality. This is useful for serialization, sorting, and
2594+
spatial data structures.
2595+
2596+
Args:
2597+
offset: Optional offset to apply to voxel coordinates before encoding.
2598+
If None, uses the negative minimum coordinate across all voxels.
2599+
2600+
Returns:
2601+
JaggedTensor: A JaggedTensor of shape `[num_grids, -1, 1]` containing
2602+
the Morton codes for each active voxel in the batch.
2603+
"""
2604+
if offset is None:
2605+
offset = -torch.min(self.ijk.jdata, dim=0).values
2606+
else:
2607+
offset = to_Vec3i(offset)
2608+
2609+
return self._impl.morton(offset)
2610+
2611+
def morton_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
2612+
"""
2613+
Return transposed Morton codes (Z-order curve) for active voxels in this grid batch.
2614+
2615+
Transposed Morton codes use zyx bit interleaving to create a space-filling curve.
2616+
This variant can provide better spatial locality for certain access patterns.
2617+
2618+
Args:
2619+
offset: Optional offset to apply to voxel coordinates before encoding.
2620+
If None, uses the negative minimum coordinate across all voxels.
2621+
2622+
Returns:
2623+
JaggedTensor: A JaggedTensor of shape `[num_grids, -1, 1]` containing
2624+
the transposed Morton codes for each active voxel in the batch.
2625+
"""
2626+
if offset is None:
2627+
offset = -torch.min(self.ijk.jdata, dim=0).values
2628+
else:
2629+
offset = to_Vec3i(offset)
2630+
2631+
return self._impl.morton_zyx(offset)
2632+
2633+
def hilbert(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
2634+
"""
2635+
Return Hilbert curve codes for active voxels in this grid batch.
2636+
2637+
Hilbert curves provide better spatial locality than Morton codes by ensuring
2638+
that nearby points in 3D space are also nearby in the 1D curve ordering.
2639+
2640+
Args:
2641+
offset: Optional offset to apply to voxel coordinates before encoding.
2642+
If None, uses the negative minimum coordinate across all voxels.
2643+
2644+
Returns:
2645+
JaggedTensor: A JaggedTensor of shape `[num_grids, -1, 1]` containing
2646+
the Hilbert codes for each active voxel in the batch.
2647+
"""
2648+
if offset is None:
2649+
offset = -torch.min(self.ijk.jdata, dim=0).values
2650+
else:
2651+
offset = to_Vec3i(offset)
2652+
2653+
return self._impl.hilbert(offset)
2654+
2655+
def hilbert_zyx(self, offset: NumericMaxRank1 | None = None) -> JaggedTensor:
2656+
"""
2657+
Return transposed Hilbert curve codes for active voxels in this grid batch.
2658+
2659+
Transposed Hilbert curves use zyx ordering instead of xyz. This variant can
2660+
provide better spatial locality for certain access patterns.
2661+
2662+
Args:
2663+
offset: Optional offset to apply to voxel coordinates before encoding.
2664+
If None, uses the negative minimum coordinate across all voxels.
2665+
2666+
Returns:
2667+
JaggedTensor: A JaggedTensor of shape `[num_grids, -1, 1]` containing
2668+
the transposed Hilbert codes for each active voxel in the batch.
2669+
"""
2670+
if offset is None:
2671+
offset = -torch.min(self.ijk.jdata, dim=0).values
2672+
else:
2673+
offset = to_Vec3i(offset)
2674+
2675+
return self._impl.hilbert_zyx(offset)
2676+
25882677
@property
25892678
def jidx(self) -> torch.Tensor:
25902679
"""

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ set(FVDB_CU_FILES
124124
fvdb/detail/ops/PointsInGrid.cu
125125
fvdb/detail/ops/IjkToIndex.cu
126126
fvdb/detail/ops/ActiveGridGoords.cu
127+
fvdb/detail/ops/SerializeEncode.cu
127128
fvdb/detail/ops/IjkToInvIndex.cu
128129
fvdb/detail/ops/ReadFromDense.cu
129130
fvdb/detail/ops/NearestIjkForPoints.cu

src/fvdb/GridBatch.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,20 @@
3131
#include <fvdb/detail/ops/RayImplicitIntersection.h>
3232
#include <fvdb/detail/ops/SampleRaysUniform.h>
3333
#include <fvdb/detail/ops/SegmentsAlongRays.h>
34+
#include <fvdb/detail/ops/SerializeEncode.h>
3435
#include <fvdb/detail/ops/VoxelNeighborhood.h>
3536
#include <fvdb/detail/ops/VoxelsAlongRays.h>
3637
#include <fvdb/detail/ops/convolution/pack_info/BrickHaloBuffer.h>
3738
#include <fvdb/detail/ops/convolution/pack_info/ConvolutionKernelMap.h>
3839
#include <fvdb/detail/ops/convolution/pack_info/IGEMMBitOperations.h>
3940
#include <fvdb/detail/utils/Utils.h>
41+
#include <fvdb/detail/utils/nanovdb/TorchNanoConversions.h>
4042

4143
#include <torch/types.h>
4244

45+
#include <tuple>
46+
#include <vector>
47+
4348
namespace fvdb {
4449

4550
GridBatch::GridBatch() : mImpl() {}
@@ -1080,6 +1085,46 @@ GridBatch::ijk() const {
10801085
});
10811086
}
10821087

1088+
JaggedTensor
1089+
GridBatch::morton(const torch::Tensor &offset) const {
1090+
c10::DeviceGuard guard(device());
1091+
const nanovdb::Coord offsetCoord = tensorToCoord(offset);
1092+
return FVDB_DISPATCH_KERNEL(this->device(), [&]() {
1093+
return fvdb::detail::ops::dispatchSerializeEncode<DeviceTag>(
1094+
*mImpl, SpaceFillingCurveType::ZOrder, offsetCoord);
1095+
});
1096+
}
1097+
1098+
JaggedTensor
1099+
GridBatch::morton_zyx(const torch::Tensor &offset) const {
1100+
c10::DeviceGuard guard(device());
1101+
const nanovdb::Coord offsetCoord = tensorToCoord(offset);
1102+
return FVDB_DISPATCH_KERNEL(this->device(), [&]() {
1103+
return fvdb::detail::ops::dispatchSerializeEncode<DeviceTag>(
1104+
*mImpl, SpaceFillingCurveType::ZOrderTransposed, offsetCoord);
1105+
});
1106+
}
1107+
1108+
JaggedTensor
1109+
GridBatch::hilbert(const torch::Tensor &offset) const {
1110+
c10::DeviceGuard guard(device());
1111+
const nanovdb::Coord offsetCoord = tensorToCoord(offset);
1112+
return FVDB_DISPATCH_KERNEL(this->device(), [&]() {
1113+
return fvdb::detail::ops::dispatchSerializeEncode<DeviceTag>(
1114+
*mImpl, SpaceFillingCurveType::Hilbert, offsetCoord);
1115+
});
1116+
}
1117+
1118+
JaggedTensor
1119+
GridBatch::hilbert_zyx(const torch::Tensor &offset) const {
1120+
c10::DeviceGuard guard(device());
1121+
const nanovdb::Coord offsetCoord = tensorToCoord(offset);
1122+
return FVDB_DISPATCH_KERNEL(this->device(), [&]() {
1123+
return fvdb::detail::ops::dispatchSerializeEncode<DeviceTag>(
1124+
*mImpl, SpaceFillingCurveType::HilbertTransposed, offsetCoord);
1125+
});
1126+
}
1127+
10831128
std::vector<JaggedTensor>
10841129
GridBatch::viz_edge_network(bool returnVoxelCoordinates) const {
10851130
c10::DeviceGuard guard(device());

src/fvdb/GridBatch.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,28 @@ struct GridBatch : torch::CustomClassHolder {
472472
/// @return A JaggedTensor of voxel coordinates indexed by this grid batch (shape [B, -1, 3])
473473
JaggedTensor ijk() const;
474474

475+
/// @brief Return Morton codes (Z-order curve) for active voxels in this grid batch (xyz bit
476+
/// interleaving)
477+
/// @param offset Offset to apply to voxel coordinates before encoding
478+
/// @return A JaggedTensor of Morton codes for active voxels (shape [B, -1, 1])
479+
JaggedTensor morton(const torch::Tensor &offset) const;
480+
481+
/// @brief Return transposed Morton codes (Z-order curve) for active voxels in this grid batch
482+
/// (zyx bit interleaving)
483+
/// @param offset Offset to apply to voxel coordinates before encoding
484+
/// @return A JaggedTensor of transposed Morton codes for active voxels (shape [B, -1, 1])
485+
JaggedTensor morton_zyx(const torch::Tensor &offset) const;
486+
487+
/// @brief Return Hilbert curve codes for active voxels in this grid batch (xyz)
488+
/// @param offset Offset to apply to voxel coordinates before encoding
489+
/// @return A JaggedTensor of Hilbert codes for active voxels (shape [B, -1, 1])
490+
JaggedTensor hilbert(const torch::Tensor &offset) const;
491+
492+
/// @brief Return transposed Hilbert curve codes for active voxels in this grid batch (zyx)
493+
/// @param offset Offset to apply to voxel coordinates before encoding
494+
/// @return A JaggedTensor of transposed Hilbert codes for active voxels (shape [B, -1, 1])
495+
JaggedTensor hilbert_zyx(const torch::Tensor &offset) const;
496+
475497
/// @brief Find the intersection between a collection of rays and the zero level set of a scalar
476498
/// field
477499
/// at each voxel in the grid batch

src/fvdb/Types.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@
88

99
namespace fvdb {
1010

11+
/// @brief Enum class for space-filling curve encoding types
12+
enum class SpaceFillingCurveType {
13+
ZOrder, ///< Regular z-order curve (xyz)
14+
ZOrderTransposed, ///< Transposed z-order curve (zyx)
15+
Hilbert, ///< Regular Hilbert curve (xyz)
16+
HilbertTransposed ///< Transposed Hilbert curve (zyx)
17+
};
18+
1119
// These are union types that can be constructed from nanovdb types, torch tensors, std::vectors,
1220
// single scalars, etc... They are used to allow the user to pass in a variety of types to the API,
1321
// and then convert them to the correct type

0 commit comments

Comments
 (0)