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.
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 (
DiscreteQuaternionIteratorwithDiscreteOrientationSet).
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d6ed54d46fe1090a64f732ff1a6676f3fe7d4b925e617fc874b45e07ba0c6afa
|
|
| MD5 |
d4e206531e0c9a1604088e138176d831
|
|
| BLAKE2b-256 |
0a8a0842d76a4c9d8e07aca6203424295f57a790ccab1db6632aba7f7f357def
|
Provenance
The following attestation bundles were made for spinstep-0.3.1a1.tar.gz:
Publisher:
publish.yml on VoxleOne/SpinStep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spinstep-0.3.1a1.tar.gz -
Subject digest:
d6ed54d46fe1090a64f732ff1a6676f3fe7d4b925e617fc874b45e07ba0c6afa - Sigstore transparency entry: 1203513670
- Sigstore integration time:
-
Permalink:
VoxleOne/SpinStep@ab201fa5b3904b538c0349c7cb5aacbc18f6c030 -
Branch / Tag:
refs/tags/v0.3.1-alpha.1 - Owner: https://github.com/VoxleOne
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab201fa5b3904b538c0349c7cb5aacbc18f6c030 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a634cfab2b79c4193e775eb82d3c54f421cd66ba4c3d8dc4a2227596673dd559
|
|
| MD5 |
6e5613fdba8e202ccc29284639724645
|
|
| BLAKE2b-256 |
a224b91989ffd0a6e13d232276ddaf087cc98852c3e0e3f69c5537e6ebad9be5
|
Provenance
The following attestation bundles were made for spinstep-0.3.1a1-py3-none-any.whl:
Publisher:
publish.yml on VoxleOne/SpinStep
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
spinstep-0.3.1a1-py3-none-any.whl -
Subject digest:
a634cfab2b79c4193e775eb82d3c54f421cd66ba4c3d8dc4a2227596673dd559 - Sigstore transparency entry: 1203513672
- Sigstore integration time:
-
Permalink:
VoxleOne/SpinStep@ab201fa5b3904b538c0349c7cb5aacbc18f6c030 -
Branch / Tag:
refs/tags/v0.3.1-alpha.1 - Owner: https://github.com/VoxleOne
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@ab201fa5b3904b538c0349c7cb5aacbc18f6c030 -
Trigger Event:
release
-
Statement type: