Asi pga-motor-interpolation
Motor interpolation (slerp/nlerp) for smooth rigid body transformations in PGA
git clone https://github.com/plurigrid/asi
T=$(mktemp -d) && git clone --depth=1 https://github.com/plurigrid/asi "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/pga-motor-interpolation" ~/.claude/skills/plurigrid-asi-pga-motor-interpolation && rm -rf "$T"
skills/pga-motor-interpolation/SKILL.mdpga-motor-interpolation
Smooth interpolation of rotations and translations via motor logarithms
Version: 1.0.0
Trit: 0 (ERGODIC - transport between states)
Overview
Motors in Projective Geometric Algebra (PGA) unify rotations and translations into a single algebraic object. This skill provides interpolation methods for smooth animation and trajectory planning.
Motor Structure
A motor M ∈ Cl(3,0,1)⁺ (even subalgebra) has the form:
M = s + B + I·t where: s = scalar (cos(θ/2)) B = bivector (sin(θ/2)·axis) I = pseudoscalar t = translation bivector
Decomposition
// ganja.js PGA3D motor Algebra(3,0,1, () => { // Rotor: pure rotation about line l var rotor = (angle, l) => Math.cos(angle/2) + Math.sin(angle/2)*l.Normalized; // Translator: pure translation along ideal line var translator = (dist, dir) => 1 + (dist/2)*(dir.e01*1e01 + dir.e02*1e02 + dir.e03*1e03); // Motor: composition of rotor and translator var motor = (R, T) => T * R; // Order matters! });
Exp/Log Maps
The exponential map converts bivectors (infinitesimal transformations) to motors:
Exp: 𝔪 → M b ↦ e^b = cos|b| + sin|b|·b/|b| + ... Log: M → 𝔪 M ↦ log(M) = arccos(s)·B/|B| + ...
ganja.js Implementation
// From ganja.js source (lines 489-547) Exp(taylor = false) { // Closed form for 3D PGA if (tot==4 && r==1) { var u = Math.sqrt(Math.abs(this.Dot(this).s)); if (Math.abs(u) < 1E-5) return this.Add(Element.Scalar(1)); var v = this.Wedge(this).Scale(-1/(2*u)); return Element.Add( Element.Sub(Math.cos(u), v.Scale(Math.sin(u))), Element.Div( Element.Mul(Element.Add(Math.sin(u), v.Scale(Math.cos(u))), this), Element.Add(u, v) ) ); } // Taylor series fallback var res = Element.Scalar(1), y=1, M=this.Scale(1), N=this.Scale(1); for (var x=1; x<15; x++) { res = res.Add(M.Scale(1/y)); M = M.Mul(N); y = y*(x+1); } return res; } Log(compat = false) { // Inverse of Exp - extract bivector from motor // See: https://www.researchgate.net/publication/360528787 }
Interpolation Methods
1. NLERP (Normalized Linear Interpolation)
Fast but not constant-speed:
function nlerp(m1, m2, t) { return m1.Scale(1-t).Add(m2.Scale(t)).Normalized; }
2. SLERP (Spherical Linear Interpolation)
Constant angular velocity:
function slerp(m1, m2, t) { var omega = Math.acos(m1.Dot(m2).s); if (Math.abs(omega) < 1e-6) return m1; return m1.Scale(Math.sin((1-t)*omega)/Math.sin(omega)) .Add(m2.Scale(Math.sin(t*omega)/Math.sin(omega))); }
3. Screw Linear Interpolation
Uses Log/Exp for true geodesic on motor manifold:
function screwLerp(m1, m2, t) { // Geodesic on motor manifold var delta = m1.Reverse.Mul(m2); // Relative motor var logDelta = delta.Log(); // To Lie algebra return m1.Mul(logDelta.Scale(t).Exp()); // Interpolate in algebra }
Sandwich Product
The sandwich product applies a motor to any element:
X' = M · X · M̃ where M̃ = reverse of M
Implementation
// ganja.js uses >>> operator var transformedPoint = motor >>> point; // Explicit form var transformedPoint = motor.Mul(point).Mul(motor.Reverse);
Application: Skeletal Animation
From klein documentation:
// C++ with klein library kln::motor interpolate_joint( kln::motor const& rest_pose, kln::motor const& animated_pose, float weight ) { // Screw linear interpolation kln::motor delta = ~rest_pose * animated_pose; kln::line log_delta = delta.log(); return rest_pose * (weight * log_delta).exp(); }
GF(3) Motor Decomposition
Motors decompose into three trit-aligned components:
| Component | Description | GF(3) Trit |
|---|---|---|
| Translation | Ideal bivector (e₀ᵢ) | +1 (PLUS) |
| Rotation | Euclidean bivector (eᵢⱼ) | 0 (ERGODIC) |
| Identity | Scalar | -1 (MINUS) |
Conservation Check
function verifyMotorGF3(motor) { var translationWeight = motor.Grade(2).filter(isIdeal).length > 0 ? 1 : 0; var rotationWeight = motor.Grade(2).filter(isEuclidean).length > 0 ? 0 : 0; var identityWeight = Math.abs(motor.s) > 0.01 ? -1 : 0; return (translationWeight + rotationWeight + identityWeight) % 3 === 0; }
Integration with Open Games
-- Motor interpolation as open game MotorGame : OpenGame (Motor, Motor) (Motor, Float) play (m1, m2) t = screwLerp m1 m2 t coplay _ (result, reward) = if result.isNormalized then reward + 10 else reward - 5
GF(3) Triads
ganja-wedge-game (+1) ⊗ pga-motor-interpolation (0) ⊗ temporal-coalgebra (-1) = 0 ✓ clifford-acset-bridge (0) ⊗ pga-motor-interpolation (0) ⊗ bisimulation-game (0) → need rebalance
Commands
# ganja.js motor demo node -e "var A=require('ganja.js'); A(3,0,1,()=>{ var R = (1+0.5*1e12).Normalized; // Rotor var T = 1 + 0.5*1e01; // Translator var M = T.Mul(R); // Motor console.log('Motor:', M); console.log('Log:', M.Log()); })()" # Julia equivalent julia -e 'using Grassmann; @basis ℝ^(3,0,1); R = exp(π/4 * v12); println("Rotor: ", R)'
References
- ganja.js Exp/Log
- klein motor interpolation
- Normalization, Square Roots, and the Exponential Map
- Grassmann.jl algebra of space
Autopoietic Marginalia
The interaction IS the skill improving itself.
Every use of this skill is an opportunity for worlding:
- MEMORY (-1): Record what was learned
- REMEMBERING (0): Connect patterns to other skills
- WORLDING (+1): Evolve the skill based on use
Add Interaction Exemplars here as the skill is used.