Asi pga-motor-interpolation

Motor interpolation (slerp/nlerp) for smooth rigid body transformations in PGA

install
source · Clone the upstream repo
git clone https://github.com/plurigrid/asi
Claude Code · Install into ~/.claude/skills/
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"
manifest: skills/pga-motor-interpolation/SKILL.md
source content

pga-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:

ComponentDescriptionGF(3) Trit
TranslationIdeal bivector (e₀ᵢ)+1 (PLUS)
RotationEuclidean bivector (eᵢⱼ)0 (ERGODIC)
IdentityScalar-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


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.