Skip to main content

Quaternion-based tree traversal for orientation-aware structures.

Project description

SpinStep

SpinStep is a quaternion-driven traversal framework for trees and orientation-based data structures. Instead of traversing by position or order, SpinStep uses 3D rotation math to navigate trees based on orientation — making it ideal for robotics, spatial reasoning, 3D scene graphs, and anywhere quaternion math naturally applies.

A 3D Graph concept image

Overview

SpinStep provides two traversal modes:

  • Continuous traversal — apply a single rotation step at each node and visit children whose orientation falls within an angular threshold (QuaternionDepthIterator).
  • Discrete traversal — try every rotation from a predefined symmetry group or custom set and visit reachable children (DiscreteQuaternionIterator with DiscreteOrientationSet).

Both modes are built on a simple Node class that stores a name, a unit quaternion orientation [x, y, z, w], and a list of children.

Installation

Requirements: Python 3.9+

Install from source:

git clone https://github.com/VoxleOne/SpinStep.git
cd SpinStep
pip install .

Or install in editable mode for development:

pip install -e ".[dev]"

Quick Start

from spinstep import Node, QuaternionDepthIterator

# Build a small tree with quaternion orientations [x, y, z, w]
root = Node("root", [0, 0, 0, 1], [
    Node("child", [0.2588, 0, 0, 0.9659])  # ~30° rotation around Z
])

# Traverse using a 30° rotation step
iterator = QuaternionDepthIterator(root, [0.2588, 0, 0, 0.9659])

for node in iterator:
    print("Visited:", node.name)
# Output:
# Visited: root
# Visited: child

Core Concepts

Node

A tree node with a quaternion-based orientation. Each node stores a name, a unit quaternion [x, y, z, w] (automatically normalised), and a list of children.

from spinstep import Node

root = Node("root", [0, 0, 0, 1])
child = Node("child", [0, 0, 0.3827, 0.9239])  # ~45° around Z
root.children.append(child)

QuaternionDepthIterator

Depth-first iterator that applies a continuous rotation step at each node. Only children within an angular threshold of the rotated state are visited.

from spinstep import Node, QuaternionDepthIterator

root = Node("root", [0, 0, 0, 1], [
    Node("close", [0.2588, 0, 0, 0.9659]),   # ~30° — will be visited
    Node("far", [0.7071, 0, 0, 0.7071]),      # ~90° — too far
])

for node in QuaternionDepthIterator(root, [0.2588, 0, 0, 0.9659]):
    print(node.name)
# Output:
# root
# close

DiscreteOrientationSet

A set of discrete quaternion orientations with spatial querying. Comes with factory methods for common symmetry groups.

from spinstep import DiscreteOrientationSet

cube_set = DiscreteOrientationSet.from_cube()          # 24 orientations
icosa_set = DiscreteOrientationSet.from_icosahedron()   # 60 orientations
grid_set = DiscreteOrientationSet.from_sphere_grid(200) # 200 Fibonacci-sampled

DiscreteQuaternionIterator

Depth-first iterator that tries every rotation in a DiscreteOrientationSet at each node. Children reachable by any rotation within the angular threshold are visited.

import numpy as np
from spinstep import Node, DiscreteOrientationSet, DiscreteQuaternionIterator

root = Node("root", [0, 0, 0, 1], [
    Node("child1", [0, 0, 0.3827, 0.9239]),
    Node("child2", [0, 0.7071, 0, 0.7071]),
])

orientation_set = DiscreteOrientationSet.from_cube()
it = DiscreteQuaternionIterator(root, orientation_set, angle_threshold=np.pi / 4)

for node in it:
    print(node.name)
# Output:
# root
# child1
# child2

Examples

Continuous Traversal with Custom Threshold

import numpy as np
from spinstep import Node, QuaternionDepthIterator

root = Node("origin", [0, 0, 0, 1], [
    Node("alpha", [0.1305, 0, 0, 0.9914]),  # ~15°
])

# Use explicit angle threshold of 20° (in radians)
it = QuaternionDepthIterator(root, [0.1305, 0, 0, 0.9914], angle_threshold=np.deg2rad(20))
print([n.name for n in it])
# Output: ['origin', 'alpha']

Query Orientations by Angle

import numpy as np
from spinstep import DiscreteOrientationSet

dos = DiscreteOrientationSet.from_cube()
indices = dos.query_within_angle([0, 0, 0, 1], np.deg2rad(10))
print(f"Found {len(indices)} orientations within 10° of identity")

Optional Dependencies

SpinStep works out of the box with NumPy, SciPy, and scikit-learn. Two optional dependencies unlock additional features:

Package Install Feature
CuPy pip install cupy-cuda12x GPU-accelerated orientation storage and angular distance computation
healpy pip install healpy HEALPix-based unique relative spin detection via get_unique_relative_spins()

GPU example:

import numpy as np
from spinstep import DiscreteOrientationSet

orientations = np.random.randn(1000, 4)
# Store orientations on GPU (requires CuPy)
gpu_set = DiscreteOrientationSet(orientations, use_cuda=True)

Development

# Install with development dependencies
pip install -e ".[dev]"

# Run tests
python -m pytest tests/ -v

# Run linter
ruff check spinstep/

Documentation

Full documentation is available in the docs/ directory:

License

MIT — see LICENSE for details.

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

spinstep-0.3.1a1.tar.gz (14.6 kB view details)

Uploaded Source

Built Distribution

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

spinstep-0.3.1a1-py3-none-any.whl (16.0 kB view details)

Uploaded Python 3

File details

Details for the file spinstep-0.3.1a1.tar.gz.

File metadata

  • Download URL: spinstep-0.3.1a1.tar.gz
  • Upload date:
  • Size: 14.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spinstep-0.3.1a1.tar.gz
Algorithm Hash digest
SHA256 d6ed54d46fe1090a64f732ff1a6676f3fe7d4b925e617fc874b45e07ba0c6afa
MD5 d4e206531e0c9a1604088e138176d831
BLAKE2b-256 0a8a0842d76a4c9d8e07aca6203424295f57a790ccab1db6632aba7f7f357def

See more details on using hashes here.

Provenance

The following attestation bundles were made for spinstep-0.3.1a1.tar.gz:

Publisher: publish.yml on VoxleOne/SpinStep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file spinstep-0.3.1a1-py3-none-any.whl.

File metadata

  • Download URL: spinstep-0.3.1a1-py3-none-any.whl
  • Upload date:
  • Size: 16.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for spinstep-0.3.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 a634cfab2b79c4193e775eb82d3c54f421cd66ba4c3d8dc4a2227596673dd559
MD5 6e5613fdba8e202ccc29284639724645
BLAKE2b-256 a224b91989ffd0a6e13d232276ddaf087cc98852c3e0e3f69c5537e6ebad9be5

See more details on using hashes here.

Provenance

The following attestation bundles were made for spinstep-0.3.1a1-py3-none-any.whl:

Publisher: publish.yml on VoxleOne/SpinStep

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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