Skip to main content

packingcubes

PyPI Supported Python Versions

Tested with Hypothesis Ruff

Compact octree implementation for fast search.

packingcubes aims to provide a fast, minimal-memory-usage octree implementation, specialized for use in astronomical/astrophysical contexts. It's written in pure python, with Numba-based acceleration of the critical code paths.

View the documentation at packingcubes.readthedocs.io!

Requirements

Supported Python Versions is required. Python versions outside this range may work, but their usage is not supported, and at least some features are >Python 3.12.

Python packages

  • numpy - required for core routines
  • numba - required for core routines (and most of the speed)
  • h5py - required for reading snapshot data and saving Cubes and PackedTrees
  • xxhash - required for core routines (packed metadata format)

Optional packages

Visualization (the viz group):

  • matplotlib - to use basic octree visualization and plotting (see Finding Particles Within a Shape or Basic_Usage in the Examples)
  • pygfx - to do interactive octree visualization (see Example_PackedTree in the Examples)
  • rendercanvas - to do interactive octree visualization
  • pyside6 - performant interactive octree visualization (uses Qt, other options include wgpu-py, pyodide. See the rendercanvas backends documentation for more details

Jupyter (the jupyter group):

  • jupyter-rfb - for interactive octree visualization in a notebook (see the Visualization section, above)

The all group combines both of the above.

Basic Usage

Installation

We're on PyPI, so installation is as simple as

pip install packingcubes

or

uv pip install packingcubes

or

pixi add packingcubes --pypi

Additional package requirements can be installed via optional dependencies (see the requirements section for the lists). Examples:

pip install "packingcubes[viz, jupyter]"

or

pixi add packingcubes[all] --pypi

Construction

From a snapshot on disk

From the command line:

packcubes SNAPSHOT OUTPUT

will generate a Cubes data structure and store it in the OUTPUT hdf5 file along with the sorted positions and shuffle-list.

Alternatively:

import packingcubes

cubes = packingcubes.Cubes("path/to/snapshot.hdf5", save_dataset=True)

cubes.save("path/to/output.hdf5")

# sorted positions/indices are stored in snapshot_sorted.hdf5 by default
# to save elsewhere, use
cubes = packingcubes.Cubes(
    "path/to/snapshot.hdf5", 
    save_dataset=True,
    sorted_filepath="path/to/output.hdf5"
)

From positions in memory

If you already have positions_data as an Nx3 matrix, you can use

cubes = packingcubes.Cubes(positions_data)

Note: this data will be sorted in place! You may want to make a copy first.

You can also do the following for easy saving. The data will still not be copied (by default)

cubes = packingcubes.Cubes(
    positions_data,
    sorted_filepath="path/to/dataset_output.hdf5",
    # filepath would also work
    save_dataset=True
)

Several configuration options are available, see packcubes --help or help(packingcubes.Cubes) for more information.

Loading

You can load the saved Cubes with

import packingcubes

cubes = packingcubes.Cubes("path/to/saved_cubes.hdf5")

Searching

Currently, packingcubes provides multiple public methods for searching your dataset. Some examples:

indices = cubes.get_indices_in_sphere(center, radius)
# center is anything that can be converted by numpy's array method to an (3,)
#   array, and is the sphere's center
# radius is a float

or

indices = cubes.get_index_list_in_box(box)
# box is anything that can be converted by numpy's array method to an (6,)
#   float array where the first 3 elements are the front-left-bottom corner,
#   and the second 3 elements are the box width, depth, and height 
#   (aka [x, y, z, dx, dy, dz])

or

sphere = cubes.Sphere(center, radius, fields="all")
# center and radius have the same meaning as above

For the first method, the returned object is an array with 3 columns. Each row can be considered a chunk of data, which looks like [start, stop, partial] representing the start and stop indices of contiguous data in the sorted dataset. The third column, partial denotes whether the chunk was partially (1) or entirely (0) contained within the sphere/box.

In the second method, the returned object is a list of the actual particle indices.

In the third, the returned object is a subdataset of all the fields present that are attached to the particles in the defined sphere.

So the search pipeline might go something like:

dataset = packingcubes.GadgetishHDF5Dataset(orig_dataset_file)
cubes = packingcubes.Cubes(
    dataset,
    particle_type="PartType0",
    extras={"v":"Velocity"}
)

sphere = cubes.Sphere(center, radius, fields="all")

velocities = sphere.v

or as a "one-liner"1:

velocities = packingcubes.Cubes(
    orig_dataset_file,
    particle_type="PartType0",
    extras={"v":"Velocity"}
).Sphere(center, radius, fields="all").v

If this seems like a hassle, consider the GUSTEAU project, which among many other improvements, will do all of this additional field sorting (and the original cubing) for you!

KDTree

We have also reimplemented some of the API from the KDTree in scipy.spatial, notably the query_ball_point method (This is also what the benchmarks compare against). Example usage modified from the scipy.spatial.KDTree documentation:

>>> import numpy as np
>>> from packingcubes import OpTree as KDTree # this is the only change you need to make
>>> x, y = np.mgrid[0:5, 0:5]
>>> points = np.c_[x.ravel(), y.ravel()]
>>> tree = KDTree(points)
>>> sorted(tree.query_ball_point([2, 0], 1))
[5, 10, 11, 15]

Caveats:

  • The provided dataset may be sorted in-place. See the OpTree constructor's data and copy_data arguments for more information on when that occurs.
  • Only query and query_ball_point are fully supported. query_ball_tree, query_pairs and count_neighbors are a work in progress and will raise NotImplementedErrors until fully implemented. sparse_distance_matrix is not planned and will always raise NotImplementedErrors.
  • Only query_ball_point has been benchmarked and is guaranteed to be performant. We are currently working on query's performance, but will not plan on beating scipy.
  • A number of optional constructor and method arguments are not supported (for example, setting the distance metric p to a number other than 2). Some of these may be supported in the future (like p=3), others will not (like balanced_tree). For the most part, we emit warnings if an argument or its value does not make sense in the packingcubes context and try to only raise errors if there is no possible analog and/or a significant change in behavior.
  • Only 1, 2, and 3D data is supported. PackedTrees specifically expect 3D data (since it's an octree implementation), thus 1 and 2D data will be copied and padded with 0s (e.g. [[1, 2], [3, 4]] will become [[1, 2, 0], [3, 4, 0]] ). This should not signficantly impact memory usage (beyond the copy) and the PackedTree should just function as a binary or quadtree.
  • For performance reasons, some of the packingcube default output formats may not match the scipy output formats (arrays instead of lists, for example). For those methods where that's a possibility, additional arguments can be provided which will enforce the the "proper" format (e.g. by setting return_lists=True) at a small performance penalty.
  • Likewise for performance reasons, some of the the default packingcube output indices will be in terms of the sorted dataset (remember, the dataset will be sorted in-place unless specified otherwise). For those methods, you may be able to specify return_data_inds=False to get indices into the unsorted dataset. Alternatively, reference the sort_index property.

Development requirements

uv

To get ready for development, create a virtual enviroment and install the package:

uv venv --python=3.12
source .venv/bin/activate
uv pip install -e ".[dev]"
pre-commit install

We use ruff for formatting. When you go to commit your code, it will automatically be formatted thanks to the pre-commit hook.

Tests are performed using pytest.

pixi

Using with pixi is pretty easy, simply

pixi shell

To look at visualizations, run tests, or develop, simply specify the corresponding environment

# visualizations
pixi shell -e viz
# testing (also includes viz)
pixi shell -e test
# developing (also includes viz & test)
pixi shell -e dev
pre-commit install

and e.g. to run tests, say

pixi run test

which runs pytest --cov=packingcubes in the dev environment.

  1. Okay, it's technically 5 lines. But only because it's broken up for clarity! ↩

Metadata

Release files for packingcubes 0.6.5

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for packingcubes 0.6.5
File Size Uploaded
packingcubes-0.6.5.tar.gz 1.1 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for packingcubes 0.6.5
File Interpreter ABI Platform
packingcubes-0.6.5-py3-none-any.whl Python 3 none any Details

Total release size: 1.3 MB

Release files / packingcubes-0.6.5.tar.gz

Download URL packingcubes-0.6.5.tar.gz
Size 1.1 MB
Tags Source
SHA-256 checksum
How to use checksums
8391a886312057a656ed99a4ba01b60a0b722a0a572f6ba620067ea4bd9632a5
BLAKE2b-256 checksum
How to use checksums
a209b37a95c56538a1f33353cc3e4d7bcf8d71e8a7223fec46a3f8eff52de198
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release files / packingcubes-0.6.5-py3-none-any.whl

Download URL packingcubes-0.6.5-py3-none-any.whl
Size 108.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1fb7fa1bbf9f8fa01d1556386a054c13c20ab07245bd34d7d02d88e13453e2ce
BLAKE2b-256 checksum
How to use checksums
bf8c5efb59382005849e7681afee53d758b04e65ee49b5c95cd180aa6a229bd7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 10, 2026.

Transparency log

Release history Release notifications | RSS feed

0.6.6

2 release files

This release

0.6.5 This release

2 release files

0.6.4

2 release files

0.6.3

2 release files

0.6.2

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.3

2 release files

0.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page