Quaternion algebra library with vector/matrix/tensor collections, linear algebra (SVD), interpolation (SLERP/squad), signal processing (QFFT/QConv), distance metrics, kinematics, statistics, and optimized einsum kernels.
Project description
quat
Quaternion Algebra for NumPy
quat brings first-class quaternion support to NumPy. Scalar quaternions, vector/matrix/tensor collections, SVD, SLERP interpolation, QFFT signal processing, serialization — with a three-tier-einsum Hamilton kernel that picks the fastest strategy at every data size. All in pure Python, zero C extensions.
pip install quat-numpy
Why quat?
Most quaternion libraries stop at rotating 3D vectors. quat gives you the full toolbox:
| What | How |
|---|---|
| Scalar quaternions | Full algebra — +, *, /, exp, log, pow, Hamilton product |
| Collections | QuatVector (1D), QuatMatrix (2D), QuatTensor (3D) — broadcast-aware |
| Linear algebra | SVD, rank, condition number, pseudo-inverse, determinant — on quaternion matrices |
| Interpolation | SLERP (shortest arc on S³), squad cubic spline, batch SLERP, angular velocity estimation |
| Distance & statistics | 4 geodesic/chordal distance metrics, quaternion vector mean, Karcher mean, PCA |
| Signal processing | 1D/2D QFFT, quaternion convolution, FIR filter design |
| Random generation | Reproducible generators for all four types |
| Serialization | JSON + compact binary roundtrip for all types; SciPy Rotation interop |
| Performance | Three-tier Hamilton kernel (component-wise → einsum), ~20x faster SVD fast-path |
Quickstart
import numpy as np
from quat import Quaternion, QuatVector, QuatMatrix
from quat import slerp, squad, random_unit_quat
# ---- Construction -------------------------------------------------
q = Quaternion(1, 2, 3, 4) # a + bi + cj + dk
r = Quaternion(5.0) # pure real
u = Quaternion.from_axis_angle((0,0,1), np.pi/2) # 90 deg around z
e = Quaternion.from_euler((0.1, 0.2, 0.3)) # intrinsic ZYX
# ---- Hamilton product ---------------------------------------------
i, j, k = Quaternion(0,1,0,0), Quaternion(0,0,1,0), Quaternion(0,0,0,1)
print(i * j) # Quaternion(0, 0, 0, 1) — i*j = k
print(i * j * k) # Quaternion(-1, 0, 0, 0) — i² = j² = k² = ijk = -1
# ---- Algebra ------------------------------------------------------
q.conjugate() # Quaternion(1, -2, -3, -4)
q.norm() # Euclidean norm
q.inverse() * q # ≈ identity
q.exp() # quaternion exponential
q.log() # quaternion logarithm
# ---- 3D rotation --------------------------------------------------
u = Quaternion.from_axis_angle((0, 0, 1), np.pi / 2)
u.rotate_vector((1, 0, 0)) # ≈ (0, 1, 0) — 90° rotation
axis, angle = u.to_axis_angle() # roundtrip (axis, angle)
# ---- Smooth interpolation -----------------------------------------
a = Quaternion(1, 0, 0, 0)
b = Quaternion(0, 1, 0, 0)
mid = slerp(a, b, 0.5) # shortest arc on the 3-sphere
q0, q1, q2, q3 = [random_unit_quat() for _ in range(4)]
curve = squad(q0, q1, q2, q3, 0.75) # cubic spline (Shoemake 1987)
# ---- Collections --------------------------------------------------
v = QuatVector([
Quaternion(1,0,0,0), Quaternion(0,1,0,0), Quaternion(0,0,1,0)
])
v.real # array([1., 0., 0.])
v.data.shape # (3, 4)
A = QuatMatrix.eye(3)
B = QuatMatrix(np.random.randn(3, 4, 4))
C = A * B # quaternion matrix multiply
C.shape # (3, 4)
# ---- Linear algebra -----------------------------------------------
from quat.linalg import svd, pseudo_inverse, solve
U, s, Vh = svd(C)
A_pinv = pseudo_inverse(C)
x = solve(A_pinv, QuatVector(np.ones((3, 4))))
# ---- Signal processing --------------------------------------------
from quat.signal import qfft, qconv, lowpass
X = qfft(np.random.randn(256, 4)) # 1D quaternion FFT
k = lowpass(16, cutoff=0.2) # FIR lowpass filter
y = qconv(np.random.randn(128, 4), k._data) # quaternion convolution
# ---- Serialization ------------------------------------------------
s = q.to_json() # → '{"type":"Quaternion","data":[...]}'
b = q.to_bytes() # → compact binary
q2 = Quaternion.from_json(s)
q3 = Quaternion.from_bytes(b)
# ---- Basis constants ----------------------------------------------
from quat import _I, _J, _K, _R, _ZERO
print(_I * _J * _K) # -1 (Hamilton's fundamental identity)
Multiplication
All * operations in quat use the Hamilton product (not component-wise
multiplication). The only exception is scalar multiplication (* s), which
multiplies each of the 4 components by a real/complex number.
Multiplication is always Hamilton — never component-wise
q1 * q2 → Hamilton product q₁·q₂
There is no * operator that multiplies real parts together and imaginary
parts separately. Every non-scalar * invokes the full quaternion Hamilton
product in some form.
Two families
| family | forms | what it does |
|---|---|---|
| Scalar multiply | q * s, v * s, A * s, T * s |
Each component × scalar (real × real, i × i, j × j, k × k) |
| Hamilton multiply | everything else below | Full quaternion product |
Four semantic patterns
① Quat × Quat — Hamilton product (one result)
q1 * q2 → Quaternion
Classic quaternion multiplication (a+bi+cj+dk)(e+fi+gj+hk). Non-commutative.
② Collection × Quaternion — element-wise Hamilton
v * q → v[i]·q (right-multiply each element)
q * v → q·v[i] (left-multiply each element, via __rmul__)
A * q → A[i,j]·q (right-multiply each element)
q * A → q·A[i,j] (left-multiply each element)
T * q → T[i,j,k]·q (right-multiply each element)
Every slot in the collection is multiplied individually by the same quaternion.
③ Collection × Collection (same shape) — element-wise Hamilton
v1 * v2 → v1[i]·v2[i] — QuatVector of same length
Each pair (v1[i], v2[i]) is multiplied via Hamilton product independently.
Requires matching shapes.
④ Matrix × Matrix / Vector — quaternion matrix multiplication
A * B → (A·B)[i,j] = Σₖ A[i,k] · B[k,j] — QuatMatrix (m×p)
A * v → y[i] = Σⱼ A[i,j] · v[j] — QuatVector (m)
Standard matrix multiplication, but with Hamilton product replacing scalar
multiplication. Not element-wise — equivalent to C[i,j] = Σₖ A[i,k]·B[k,j].
Cross-type reference
| expression | semantics | result |
|---|---|---|
q1 * q2 |
Hamilton product | Quaternion |
q * 2.5 |
component-wise scalar × | Quaternion |
3.0 * q |
scalar × (via __rmul__) |
Quaternion |
v1 * v2 |
element-wise Hamilton | QuatVector |
v * q |
element-wise Hamilton, right-multiply | QuatVector |
q * v |
element-wise Hamilton, left-multiply | QuatVector |
v * s |
scalar × each element (component-wise) | QuatVector |
A * B |
matrix multiplication (Σ Hamilton) | QuatMatrix |
A * v |
matrix-vector multiplication (Σ Hamilton) | QuatVector |
A * q |
element-wise Hamilton, right-multiply | QuatMatrix |
q * A |
element-wise Hamilton, left-multiply | QuatMatrix |
A * s |
scalar × each element (component-wise) | QuatMatrix |
T * q |
element-wise Hamilton, right-multiply | QuatTensor |
T * s |
scalar × each element (component-wise) | QuatTensor |
T.mode_k_product(A) |
tensor mode-k product (A·ₖ T) |
QuatTensor |
v1.inner(v2) |
inner product ∑ v[i]⁻ · v[i] |
Quaternion |
q1.component_wise_mul(q2) |
component-wise a·e, b·f, c·g, d·h (no cross-terms) |
Quaternion |
v1.component_wise_mul(v2) |
component-wise, same shape required | QuatVector |
A.component_wise_mul(B) |
component-wise, same shape required | QuatMatrix |
component_wise_mul(p, q) |
raw ndarray batch, shape (..., 4) |
ndarray |
Examples
# ---- ① Scalar × Scalar (Hamilton product) --------------------------
p = Quaternion(2, 3, -1, 4)
q = Quaternion(1, -2, 0, 5)
p * q # Hamilton product
p.commutator(q) # [p, q] = pq − qp
# ---- ② Element-wise Hamilton (collection × quaternion) -------------
v = QuatVector([_I, _J, _K])
A = QuatMatrix([[_I, _J], [_K, _R]])
q = Quaternion(0, 1, 0, 0)
v * q # QuatVector([-1, -k, j]) — v[i]·q right-multiply
q * v # QuatVector([-1, k, -j]) — q·v[i] left-multiply
A * q # each A[i,j]·q
# ---- ③ Element-wise Hamilton (vector × vector) ---------------------
v1 = QuatVector([_I, _J, _K])
v2 = QuatVector([_J, _K, _I])
v1 * v2 # QuatVector([ k, i, j]) — element-wise Hamilton
# ---- ④ Matrix multiplication (NOT element-wise) --------------------
A = QuatMatrix([[_I, _J], [_K, _R]])
B = QuatMatrix([[_R, _K], [_J, _I]])
A * B # shape (2,2) — C[i,j] = Σ_k A[i,k]·B[k,j]
# ---- ④ Matrix-vector multiplication --------------------------------
A = QuatMatrix(2, 3)
x = QuatVector(3)
A * x # length-2 vector — y[i] = Σ_j A[i,j]·x[j]
# ---- Tensor mode-k product -----------------------------------------
T = QuatTensor(2, 3, 4)
T.mode_1_product(A) # unfold(1) → A * unfolded → fold back
T.mode_2_product(A)
T.mode_3_product(A)
# ---- Inner products -------------------------------------------------
v1.inner(v2) # Quaternion — sum of v₁[i]⁻ · v₂[i]
T.inner(T) # Quaternion — Frobenius norm squared
# ---- Component-wise multiply (no cross-terms) -----------------------
q1 = Quaternion(1, 2, 3, 4)
q2 = Quaternion(5, 6, 7, 8)
q1.component_wise_mul(q2) # Quaternion(5, 12, 21, 32) — a·e, b·f, c·g, d·h
v1 = QuatVector([_I, _J, _K])
v2 = QuatVector([_J, _K, _I])
v1.component_wise_mul(v2) # QuatVector([0, 0, 0]) — no cross-terms → all zero
Key points
- All non-scalar
*is Hamilton. There is no*operator for component-wise multiply (a·e, b·f, c·g, d·h). Use.component_wise_mul()instead. A * Bis matrix multiplication, not element-wise. The matrix dimensions must be compatible (m×n·n×p).A * vis matrix-vector, not element-wise.v1 * v2is element-wise — eachv1[i]is Hamilton-multiplied byv2[i]independently.- Left vs right matters.
v * q(right-multiply) ≠q * v(left-multiply) because Hamilton product is non-commutative. QuatTensor * QuatTensoris not supported. Use mode-kproducts or.inner()instead.
API Reference
Quaternion — scalar quaternion
| category | methods |
|---|---|
| constructors | zero(), one_q(), from_axis_angle(), from_euler(), from_complex_matrix(), from_real_matrix_left() |
| components | .w, .r, .i, .j, .k, .real, .imag, .components, .data |
| arithmetic | +, -, *, /, -q (Hamilton product via *; @ not overridden) |
| algebra | .conjugate(), .norm(), .normalize(), .normalized, .inverse(), .exp(), .log(), .pow(t), .re_inner(q), .commutator(q), .minimal(), .component_wise_mul(q) |
| rotation | .rotate_vector(v), .to_axis_angle(), .to_euler(seq='zyx'), .angle, .axis |
| validation | .isnan(), .isinf(), .isfinite(), .isclose(q) |
| serialization | .to_json(), .from_json(s), .to_bytes(), .from_bytes(b) |
| matrices | .to_complex_matrix(), .to_real_matrix_left(), .to_real_matrix_right() |
| conversion | float(q), int(q), complex(q), abs(q), np.asarray(q) |
QuatVector / QuatMatrix / QuatTensor
| QuatVector | QuatMatrix | QuatTensor | |
|---|---|---|---|
| ndim | 1D | 2D | 3D |
| shape | (n,) |
(m, n) |
(p, q, r) |
| create | zeros, ones |
zeros, eye |
zeros |
| access | .data, .real/.i/.j/.k |
.data, .real/.i/.j/.k, .row(i), .col(j) |
.data, .real/.i/.j/.k |
| ops | .inner(v), .norm(), .component_wise_mul(v) |
.norm(), .T, .H, .conjugate(), .component_wise_mul(A) |
.inner(T), .unfold(mode), .mode_n_product(A), .component_wise_mul(T) |
Module overview
| module | exports |
|---|---|
quat.random |
random_quat, random_unit_quat, random_quat_vector, random_quat_matrix, random_quat_tensor |
quat.interpolate |
slerp, slerp_vector, squad, angular_velocity, integrate_angular_velocity, rotate_frame |
quat.linalg |
svd, svd_values, rank, condition_number, pseudo_inverse, trace, det, norm, solve |
quat.serialization |
to_json, from_json, to_bytes, from_bytes, to_scipy_rotation, from_scipy_rotation |
quat.signal |
qfft, iqfft, qfft2, iqfft2, qconv, qconv2, lowpass, highpass, bandpass, bandstop |
quat.stats |
rotation.intrinsic, rotation.chordal, rotor.intrinsic, rotor.chordal, mean_rotation, approximate_karcher_mean, quaternion_mean, quaternion_cov, quaternion_pca |
quat.algebra |
hamilton_einsum, quat_matmul, component_wise_mul, conjugate_batch, norm_squared_batch, normalize_batch |
Performance
The Hamilton product kernel uses a three-tier dispatch that selects the fastest strategy based on data size:
| size | kernel | strategy |
|---|---|---|
| ≤ 500 elements | _hamilton_component |
direct float arithmetic |
| 500 – 5000 | _hamilton_einsum_noopt |
einsum without contraction-path optimisation |
| > 5000 | _hamilton_einsum |
einsum with cached contraction-path optimisation |
SVD fast-path: rank(), condition_number(), and norm(A, 2) compute
only singular values via the complex (2×2) representation — ~20× faster
than the full 4m×4n real SVD.
Zero-copy interop: q.to_numpy(copy=False) returns the internal buffer
without copying. .to_complex_matrix() and .to_real_matrix_left() use
np.empty to skip zero-initialisation.
Install
pip install quat-numpy
Requires Python ≥ 3.9, NumPy ≥ 1.21. Optional scipy for
to_scipy_rotation / from_scipy_rotation.
For development:
git clone https://github.com/gealachlee/quat.git
cd quat
pip install -e .
pytest tests/
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 quat_numpy-1.0.0b2.tar.gz.
File metadata
- Download URL: quat_numpy-1.0.0b2.tar.gz
- Upload date:
- Size: 50.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f517d27d1e2caab881a8b5b67ac15a0c16b374c0c366cf23b2b3052eda87e2d
|
|
| MD5 |
03f3a6f38ca5ca2f0c176b5be426a2cc
|
|
| BLAKE2b-256 |
204375aeef27216bc8d4325d4ede5b6e049784b1540802acc493c4b3b14bf933
|
Provenance
The following attestation bundles were made for quat_numpy-1.0.0b2.tar.gz:
Publisher:
publish.yml on gealachlee/quat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quat_numpy-1.0.0b2.tar.gz -
Subject digest:
5f517d27d1e2caab881a8b5b67ac15a0c16b374c0c366cf23b2b3052eda87e2d - Sigstore transparency entry: 2020400883
- Sigstore integration time:
-
Permalink:
gealachlee/quat@939feb316da0ffdfa3c04d12cab0289bb4cd2ec2 -
Branch / Tag:
refs/tags/1.0.0b2 - Owner: https://github.com/gealachlee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@939feb316da0ffdfa3c04d12cab0289bb4cd2ec2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file quat_numpy-1.0.0b2-py3-none-any.whl.
File metadata
- Download URL: quat_numpy-1.0.0b2-py3-none-any.whl
- Upload date:
- Size: 39.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0520211efcb73eebf31a91c5088043340f9ba438ab1809a5439560eb715297e1
|
|
| MD5 |
b27bf43ccdbff5165ebd0e314a0caf37
|
|
| BLAKE2b-256 |
3bd27c068938900a52b131fd9e828cf34283b7419f115571381627d571df0198
|
Provenance
The following attestation bundles were made for quat_numpy-1.0.0b2-py3-none-any.whl:
Publisher:
publish.yml on gealachlee/quat
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
quat_numpy-1.0.0b2-py3-none-any.whl -
Subject digest:
0520211efcb73eebf31a91c5088043340f9ba438ab1809a5439560eb715297e1 - Sigstore transparency entry: 2020401030
- Sigstore integration time:
-
Permalink:
gealachlee/quat@939feb316da0ffdfa3c04d12cab0289bb4cd2ec2 -
Branch / Tag:
refs/tags/1.0.0b2 - Owner: https://github.com/gealachlee
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@939feb316da0ffdfa3c04d12cab0289bb4cd2ec2 -
Trigger Event:
push
-
Statement type: