Skip to main content

Barnes-Hut N-body accelerator that is array-agnostic (NumPy/Dask)

Project description

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}
}

Project details


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.

Supported by

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