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.
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.
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]"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: childA 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)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
# closeA 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-sampledDepth-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
# child2import 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']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")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)# Install with development dependencies
pip install -e ".[dev]"
# Run tests
python -m pytest tests/ -v
# Run linter
ruff check spinstep/Full documentation is available in the docs/ directory:
MIT — see LICENSE for details.
