Skip to main content

bhut

bhut logo

A high-performance Barnes-Hut N-body accelerator that is array-agnostic, supporting both NumPy and Dask arrays for distributed computation.

CI PyPI Python

Documentation

Full documentation is available at:

https://maroba.github.io/bhut/

Installation

pip install bhut

For acceleration features and optional dependencies:

pip install bhut[accel]
pip install 'dask[array]'  # For distributed computation
pip install numba         # For JIT acceleration (recommended)

Quick Start

Functional API

import numpy as np
import bhut

# Generate random N-body system
N = 10000
positions = np.random.randn(N, 3)
masses = np.ones(N)

# Compute gravitational accelerations using Barnes-Hut algorithm
accelerations = bhut.accelerations(
    positions, masses, 
    theta=0.5,        # Opening angle criterion
    softening=0.01,   # Gravitational softening
    G=1.0            # Gravitational constant
)

print(f"Acceleration shape: {accelerations.shape}")  # (10000, 3)

Object-Oriented API

import numpy as np
from bhut import Tree

# Create and build tree
positions = np.random.randn(1000, 3)
masses = np.ones(1000)

tree = Tree(positions, masses, leaf_size=32)
tree.build()

# Query accelerations for same particles
self_accel = tree.accelerations(theta=0.5, softening=0.01)

# Query accelerations at different target positions
targets = np.random.randn(500, 3)
target_accel = tree.accelerations(targets, theta=0.5, softening=0.01)

# Efficient tree updates for time evolution
new_positions = positions + 0.1 * np.random.randn(1000, 3)

# Refit: fast update when particles move but topology is similar
tree.refit(new_positions)
accel_refit = tree.accelerations(theta=0.5, softening=0.01)

# Rebuild: complete reconstruction when topology changes significantly
tree.rebuild(new_positions, new_masses=masses * 1.1)
accel_rebuild = tree.accelerations(theta=0.5, softening=0.01)

Distributed Computing with Dask

import numpy as np
import dask.array as da
import bhut

# Create large dataset distributed across chunks
N = 1_000_000
positions_np = np.random.randn(N, 3).astype(np.float64)
masses_np = np.ones(N, dtype=np.float64)

# Convert to Dask arrays with chunking
positions_da = da.from_array(positions_np, chunks=(100_000, 3))
masses_da = da.from_array(masses_np, chunks=(100_000,))

# Compute accelerations in parallel (auto-detects Dask backend)
accelerations_da = bhut.accelerations(positions_da, masses_da, theta=0.5)

# Result preserves chunking structure for efficient downstream processing
print(f"Input chunks: {positions_da.chunks}")
print(f"Output chunks: {accelerations_da.chunks}")

# Compute final result when needed
result = accelerations_da.compute()

Numba JIT Acceleration

For maximum performance with NumPy arrays, install Numba for automatic JIT compilation:

import numpy as np
import bhut

# Numba automatically detected and used if available
positions = np.random.randn(10000, 3)
masses = np.ones(10000)

# First call includes compilation overhead (~1s)
accel1 = bhut.accelerations(positions, masses, theta=0.5)

# Subsequent calls use cached compiled code (~27x faster)
accel2 = bhut.accelerations(positions, masses, theta=0.5)

Performance comparison (10,000 particles):

  • Without Numba: ~4.5 seconds (pure Python)
  • With Numba: ~0.16 seconds after compilation (~27x speedup)
  • Memory: No additional memory overhead
  • Compatibility: Falls back gracefully when Numba unavailable

When Numba is used:

  • Automatically detected when import numba succeeds
  • Only accelerates compute-intensive leaf node interactions
  • Pure Python fallback always available
  • Works with NumPy arrays (Dask arrays use pure Python)
# Check if Numba acceleration is active
from bhut.traverse.bh import HAVE_NUMBA
print(f"Numba acceleration: {'✓ Available' if HAVE_NUMBA else '✗ Not available'}")

Features

  • ** High Performance**: O(N log N) tree construction, O(M log N) force evaluation
  • ** Numba Acceleration**: Optional JIT compilation for ~27x speedup in particle interactions
  • ** Array-Agnostic**: Seamless support for NumPy and Dask arrays
  • ** Distributed**: Built-in Dask integration for large-scale computation
  • ** Accurate**: Configurable Barnes-Hut approximation with error control
  • ** Efficient Updates**: Tree refit/rebuild for time-stepping simulations

Parameter Tuning Guide

Opening Angle (theta)

Controls the accuracy-performance tradeoff:

Parameter Tuning Guide

Opening Angle (theta)

Controls the accuracy-performance tradeoff:

# High accuracy, slow computation
accel_accurate = bhut.accelerations(pos, masses, theta=0.1)

# Balanced (recommended for most applications)
accel_balanced = bhut.accelerations(pos, masses, theta=0.5)

# Fast approximation, lower accuracy
accel_fast = bhut.accelerations(pos, masses, theta=1.0)

# Direct summation (exact but O(N²))
accel_exact = bhut.accelerations(pos, masses, theta=0.0)

Guidelines:

  • theta = 0.0: Exact O(N²) calculation (small systems only)
  • theta = 0.1-0.3: High accuracy for precision-critical applications
  • theta = 0.5: Good balance for most scientific simulations
  • theta = 0.7-1.0: Fast approximation for large-scale surveys
  • theta > 1.0: Very approximate, mainly for prototyping

Leaf Size (leaf_size)

Controls tree granularity and performance:

# Fine-grained tree (more memory, potentially faster for large queries)
tree_fine = Tree(pos, masses, leaf_size=16)
tree_fine.build()

# Balanced (recommended default)
tree_balanced = Tree(pos, masses, leaf_size=32)
tree_balanced.build()

# Coarse-grained tree (less memory, faster construction)
tree_coarse = Tree(pos, masses, leaf_size=64)
tree_coarse.build()

Guidelines:

  • leaf_size = 8-16: Best for systems with many small query sets
  • leaf_size = 32: Recommended default for most applications
  • leaf_size = 64-128: Better for large query sets or memory-constrained systems
  • leaf_size > 128: May degrade performance due to increased direct summation

Performance Optimization

Tree Refit vs Rebuild

For time-stepping simulations:

tree = Tree(positions, masses)
tree.build()

for timestep in range(num_steps):
    # Compute forces
    accel = tree.accelerations(theta=0.5, softening=0.01)
    
    # Update positions
    positions += velocity * dt + 0.5 * accel * dt**2
    velocity += accel * dt
    
    # Decide whether to refit or rebuild
    if should_refit_vs_rebuild(tree, positions):
        tree.refit(positions)  # Fast: O(N), preserves tree structure
    else:
        tree.rebuild(positions)  # Slower: O(N log N), rebuilds from scratch

Chunking Strategy for Dask

# Good chunking: ~100K-1M particles per chunk
positions_da = da.from_array(positions, chunks=(500_000, 3))

# Avoid: too small chunks (overhead dominates)
positions_bad = da.from_array(positions, chunks=(1_000, 3))

# Avoid: too large chunks (memory issues, poor parallelization)
positions_bad = da.from_array(positions, chunks=(10_000_000, 3))

Performance Characteristics

System Size Construction Time Memory Usage Recommended θ Numba Speedup
N < 10³ < 1ms < 1MB 0.0 (exact) ~2x
N ~ 10⁴ ~ 10ms ~ 10MB 0.3 ~10x
N ~ 10⁵ ~ 100ms ~ 100MB 0.5 ~27x
N ~ 10⁶ ~ 1s ~ 1GB 0.5 ~27x
N > 10⁶ Use Dask Distributed 0.7 N/A*

*Dask arrays use pure Python (Numba optimizations planned for future releases)

Contributing

Contributions are welcome! Please see CONTRIBUTING.md for guidelines.

License

MIT License - see LICENSE file for details.

Citation

If you use bhut in your research, please cite:

@software{bhut,
  title={bhut: A Barnes-Hut N-body Accelerator},
  author={Matthias Baer},
  url={https://github.com/your-org/bhut},
  year={2025}
}

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bhut-0.1.3.tar.gz (32.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bhut-0.1.3-py3-none-any.whl (34.4 kB view details)

Uploaded Python 3

File details

Details for the file bhut-0.1.3.tar.gz.

File metadata

  • Download URL: bhut-0.1.3.tar.gz
  • Upload date:
  • Size: 32.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for bhut-0.1.3.tar.gz
Algorithm Hash digest
SHA256 e451ab079469c751fe426365d5f8d73d480d35b036f1b15c208b8d9d53c8d5a5
MD5 5094d8d14fe3bd55cfa9d2bff4494510
BLAKE2b-256 6661a5b2856eeb50caf5fff9ffa1b1ad0952905a4df3e61c231b853f54215442

See more details on using hashes here.

File details

Details for the file bhut-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: bhut-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 34.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.9

File hashes

Hashes for bhut-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 6221da8fbdf5501e5a63d664c51a87d38ffc152ecfe39b4f11cd0914b5b7239a
MD5 adb5330219517a3e741bb788465c2537
BLAKE2b-256 5e4142682c12a6389c007ac359fdb2c5c3723fe3c41a3a71115573c7462f8b91

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page