Skip to content

frostburn/ts-geometric-algebra

Repository files navigation

ts-geometric-algebra

Ts-geometric-algebra is a Clifford Algebra Generator for TypeScript and JavaScript. It generates Geometric Algebras of any signature and is mostly combatible with Ganja.js. There is no operator overloading or inline functions yet due to lack of support in TypeScript.

(Mathematically, an algebra generated by ts-geometric-algebra is a graded exterior (Grassmann) algebra with a non-metric outer product, extended (Clifford) with geometric and contraction inner products, a Poincare duality operator and the main involutions and morphisms.)

(Technically, ts-geometric-algebra is a code generator producing classes that reificate algebraic literals.)

(Practically, ts-geometric-algebra enables algebraic operations over reals, complex numbers, dual numbers, hyperbolic numbers, vectors, spacetime events, quaternions, dual quaternions, biquaternions or any other Clifford Algebra with full type support from TypeScript.)

Discourse and Discord

Visit bivector.net for our forum and chat - the perfect place for questions and support.

Using ts-geometric-algebra for the first time

Install ts-geometric-algebra using npm :

npm install ts-geometric-algebra

And require it in your script :

const Algebra = require('ts-geometric-algebra').default;

The Algebra Function

To create an Algebra, call the Algebra function specifying the metric signature (number of positive, negative and zero dimensions). The result is an ES6 class implementing the requested clifford algebra.

function Algebra( p, q, r );
  // p    = number of positive dimensions.
  // q    = optional number of negative dimensions.
  // r    = optional number of zero dimensions.

An extended syntax is also available that allows you to further tweak the created Algebra.

function Algebra( p, q, r, {baseType, metric, mulTable, disableUnroll} )
  // p        = number of positive dimensions.
  // q        = optional number of negative dimensions.
  // r        = optional number of zero dimensions.
  // baseType = Float32Array or similar
  // metric   = basis vector inner products in custom order, must agree with p, q and r
  // mulTable = Custom signs for the Caley table
  // disableUnroll = (boolean) prevent loop unrolling of performance critical methods

Here are some examples :

// Basic
const Hyper   = Algebra(1);         // Hyperbolic numbers.
const Complex = Algebra(0, 1);      // Complex numbers.
const Dual    = Algebra(0, 0, 1);   // Dual numbers.
const H       = Algebra(0, 2);      // Quaternions.

// Clifford
const Cl2 = Algebra(2);             // Clifford algebra for 2D vector space.
const Cl3 = Algebra(3);             // Clifford algebra for 3D vector space.
const TimeSpace = Algebra(1, 3);    // Clifford algebra for timespace vectors.

// Geometric
const PGA2D = Algebra(2, 0, 1);     // Projective Euclidean 2D plane. (dual)
const PGA3D = Algebra(3, 0, 1);     // Projective Euclidean 3D space. (dual)
const CGA2D = Algebra(3, 1);        // Conformal 2D space.
const CGA3D = Algebra(4, 1);        // Conformal 3D space.

// High-Dimensional GA
const DCGA3D = Algebra(6, 2);       // Double Conformal 3D Space.
const TCGA3D = Algebra(9, 3);       // Triple Conformal 3D Space.
const DCGSTA = Algebra(4, 8);       // Double Conformal Geometric Space Time Algebra.
const QCGA   = Algebra(9, 6);       // Quadric Conformal Geometric Algebra.

You can now use these classes to generate algebraic elements. Those elements will have all of the expected properties. (norm, blade access, dot, wedge, mul, dual, inverse, etc ...)

Unlike Ganja.js you must use them in a 'classic' programming style syntax like the example below.

const Complex = Algebra(0, 1);       // Complex numbers.
const a = Complex.fromGanja([3, 2]); // 3 + 2i
const b = Complex.fromGanja([1, 4]); // 1 + 4i
return a.mul(b);                     // returns [-5, 14]

Altough not as pretty or fun as Ganja.js you have the full advantage of types and autocompletion.

Methods

Object oriented Ganja Equivalent Explanation
x.equals(y) N/A Strict equality
x.closeTo(y, tol?) N/A Equality within given tolerance
x.hasNaN() N/A Check for Not-a-Numbers
x.hasInfinity() N/A Check for (negative) infinity
x.isNil(tol?) N/A Equal or close to zero
x.isGrade(g, tol?) N/A Only has components of grade g
x.s x.s Scalar part (get/set)
x.ps N/A Pseudoscalar part (get/set)
x.getAt(...idx) N/A Metric-aware coefficient of the product of basis factors defined by idx
x.setAt(...idx, a) N/A Set coefficient of product of basis factors idx as a
x.norm() x.Length Conjugate norm (metric-aware)
x.vnorm() x.VLength Vector norm (ignores metric) {x.length is array length}
x.taxicabNorm() N/A L1 norm (ignores metric)
x.maxNorm() N/A Linfinity norm (ignores metric)
x.neg() N/A Negation (additive inverse)
x.cwAbs() N/A Component-wise absolute value
x.involute() x.Involute Negation of basis factors
x.rev() x.Reverse Reversal of basis factors {x.reverse is array reversal}
x.conjugate() x.Conjugate Conjugation (combined involution and reversal)
x.adjugate() N/A Unscaled multiplicative inverse (suitable for int base class)
x.inverse() x.Inverse Multiplicative inverse
x.square() x.Mul(x) Multiplicative squaring (**)
x.normalize(a?) x.Normalize x with norm set to a (default 1)
x.rotorNormalize() N/A Normalize rotor x
x.sqrt() N/A Square root. Currently reliable only in dimensions < 2
x.rotorSqrt() N/A Rotor square root. Available in certain metrics.
x.exp() x.Exp Exponential function
x.bivectorExp() x.Exp Bivector exponential function (optimized)
x.log() x.Log (Motor) Logarithm. Generic exp inverse available only in dimensions < 2
x.rotorLog() x.Log Rotor Logarithm
x.clone() x.Scale(1) Independent copy
x.dual() x.Dual (*) Metric independent dual: x.mul(x.dual()) = Cl.pseudoscalar()
x.undual() N/A Inverse of x.dual()
x.scale(a) x.Scale(a) Scalar multiplication
x.pow(n) x.Pow(n) Multiply x with itself n times
x.applyWeights(ws) N/A Replace each basis factor with a weighted copy for every weight in ws
x.negateGrades(...gs) x.Map(...gs) Negate the given grades
x.add(y) x.Add(y) Component-wise addition (**)
x.sub(y) x.Sub(y) Component-wise subtraction (**)
x.mul(y) x.Mul(y) Geometric product of x and y (**)
x.lmul(y) y.Mul(x) Geometric product from the left (**)
x.div(y) x.Div(y) Geometric division from the right
x.ldiv(y) y.Inverse.Mul(x) Left inverse product
x.ldivs(y) x.Inverse.Mul(y) Geometric division from the left
x.wedge(y) x.Wedge(y) Wedge (outer) product. Metric independent (**)
x.lwedge(y) y.Wedge(x) Wedge product from the left (**)
x.vee(y) x.Vee(y) Vee (dual) product. Metric indepentend. x.vee(y) = y.dual().wedge(x.dual()).undual() (**)
x.lvee(y) y.Vee(x) Vee product from the left (**)
x.rotorMean(y) N/A Geometric mean of rotors x and y
x.contract(y, ctn) N/A Contract x by y using criterion ctn
x.dot(y) x.Dot(y) Dot product. Symmetric criterion (**)
x.dotL(y) x.LDot(y) Left contraction of x by y (**)
x.ldotL(y) y.LDot(x) Left contraction of y by x (**)
x.dotR(y) N/A Right contraction of x by y
x.ldotR(y) N/A Right contraction of y by x
x.dotS(y) N/A Scalar product. Nil criterion
x.imag() N/A Filter out grade 0
x.even() x.Even() Filter out odd grades
x.grade(n) x.Grade(n) Keep only grade n components
x.vector() x.Vector Array of vector components
x.vector(n) N/A Array of n-vector components
x.rotor() N/A Array of components of even grade
x.ganja() x Array of all components in lexicographic order
x.invScale(y, t?) N/A Ratio of weights between coincident x and y. Returs NaN if non-coincidence exteeds threshold t.
x.grades(t?) N/A Array of grades present in x exceeding threshold t (default 0)
x.meetJoin(y, t?) N/A True (of threshold t) meet and join of blades x and y
x.star(y) N/A Scalar product with number result
Cl.zero() Cl.Scalar(0) Zero element
Cl.scalar(a?) Cl.Scalar(a) Scalar element of size a (default 1)
Cl.pseudoscalar(a?) N/A Pseudoscalar element of size a (default 1)
Cl.basisBlade(...idx) N/A Unit basis blade given by the product of the given basis factors
Cl.fromVector(vs) Cl.Vector(...vs) Vector element with given components (from an array)
Cl.fromVector(vs, 2) Cl.Bivector(...vs) Bivector element with given components (lexicographic order)
Cl.fromVector(vs, 3) Cl.Trivector(...vs) Trivector element with given components
Cl.fromVector(vs, g) Cl.nVector(g, ...vs) g-vector element with given components
Cl.fromRotor(vs) N/A Even grade element with given components
Cl.fromGanja(vs) new Cl(vs) New element with components given in lexicographic order
Cl.dimensions --> Math.log(Cl.describe().basis.length)/Math.LN2 number of dimensions
Cl.size --> Cl.describe().basis.length algebra size
Cl.metric --> Cl.describe().metric.slice(1, n+1) basis vector metric
Cl.mulTable --> Similar to Cl.describe().mulTable but signs only
new Cl(vs) N/A Not recommended, use fromGanja instead. New element with components given in bit field order

(*) Only in degenerate metrics (**) Loop unrolled for maximum performance

Dual Zoo

Object oriented Function Oriented Ganja Equivalent Explanation
x.podge() podge(x) x.Mul(ps) Right-multiplication by Cl.pseudoscalar()
x.unpodge() unpodge(x) x.Div(ps) Right-division by Cl.pseudoscalar()
x.podgeL() podgeL(x) ps.Mul(x) Left-multiplication by Cl.pseudoscalar()
x.unpodgeL() unpodgeL(x) ps.Inverse.Mul(x) Left-division by Cl.pseudoscalar()
x.star() star(x) N/A Non-degenerate x.podge()
x.unstar() unstar(x) N/A Inverse of x.star()
x.starL(x) starL(x) x.Dual (*) Non-degenerate x.podgeL()
x.unstarL(x) unstarL(x) N/A Inverse of x.starL()
x.hodge() hodge(x) N/A Hodge dual
x.unhodge() unhodge(x) N/A Inverse of x.hodge()
x.hodgeL() hodgeL(x) N/A Left Hodge dual
x.unhodgeL() unhodgeL(x) N/A Inverse of x.hodgeL()

(*) Only in non-degenerate metrics

Rotor operation availability

Metric (pqr) Available operations
p+q+r <= 2 (all)
400 rotorSqrt, rotorNormalize
310 rotorSqrt, rotorNormalize
301 rotorLog, rotorSqrt, rotorNormalize
410 rotorSqrt, rotorNormalize

Functions

Function Oriented Ganja Equivalent Explanation
equals(x, y) N/A Strict equality
closeTo(x, y, tol?) N/A Equality within given tolerance
hasNaN(x) N/A Check for Not-a-Numbers
hasInfinity(x) N/A Check for (negative) infinity
isNil(x, tol?) N/A Equal or close to zero
isGrade(x, g, tol?) N/A Only has components of grade g
norm(x) x.Length Conjugate norm (metric-aware)
vnorm(x) x.VLength Vector norm (ignores metric) {x.length is array length}
taxicabNorm(x) N/A L1 norm (ignores metric)
maxNorm(x) N/A Linfinity norm (ignores metric)
neg(x) N/A Negation (additive inverse)
cwAbs(x) N/A Component-wise absolute value
involute(x) x.Involute Negation of basis factors
rev(x) x.Reverse Reversal of basis factors {x.reverse is array reversal}
conjugate(x) x.Conjugate Conjugation (combined involution and reversal)
adjugate(x) x.Inverse Unscaled multiplicative inverse
inverse(x) x.Inverse Multiplicative inverse
square(x) x.Mul(x) Multiplicative squaring (**)
normalize(x, a?) x.Normalize x with norm set to a (default 1)
rotorNormalize(x) N/A Normalize rotor x
sqrt(x) N/A Square root. Currently reliable only in dimensions < 2
rotorSqrt(x) N/A Rotor square root. Available in certain metrics.
exp(x) x.Exp Exponential function
bivectorExp(x) x.Exp Bivector exponential function (optimized)
log(x) x.Log (Motor) Logarithm. Generic exp inverse available only in dimensions < 2
rotorLog(x) x.Log Rotor Logarithm
clone(x) x.Scale(1) Independent copy
dual(x) x.Dual (*) Metric independent dual: x.mul(x.dual()) = Cl.pseudoscalar()
undual(x) N/A Inverse of x.dual()
scale(x, a) x.Scale(a) Scalar multiplication
pow(x, n) x.Pow(n) Multiply x with itself n times
applyWeights(x, ws) N/A Replace each basis factor with a weighted copy for every weight in ws
negateGrades(x, ...gs) x.Map(...gs) Negate the given grades
add(...args) x.Add(y) Component-wise addition (**)
sub(x, y) x.Sub(y) Component-wise subtraction (**)
mul(...args) x.Mul(y) Geometric product of x and y (**)
div(x, y) x.Div(y) Geometric division from the right
ldivs(x, y) x.Inverse.Mul(y) Geometric division from the left
wedge(...args) x.Wedge(y) Wedge (outer) product. Metric independent (**)
vee(...args) x.Vee(y) Vee (dual) product. Metric indepentend. vee(x, y) = undual(dual(y), dual(x)) (**)
rotorMean(x, y) N/A Geometric mean of rotors x and y
contract(x, y, ctn) N/A Contract x by y using criterion ctn
dot(x, y) x.Dot(y) Dot product. Symmetric criterion (**)
dotL(x, y) x.LDot(y) Left contraction of x by y (**)
dotR(x, y) N/A Right contraction of x by y
dotS(x, y) N/A Scalar product. Nil criterion
imag(x) N/A Filter out grade 0
even(x) x.Even() Filter out odd grades
grade(x, n) x.Grade(n) Keep only grade n components
invScale(x, y, t?) N/A Ratio of weights between coincident x and y. Returs NaN if non-coincidence exteeds threshold t.
grades(x, t?) N/A Array of grades present in x exceeding threshold t (default 0)
meetJoin(x, y, t?) N/A True (of threshold t) meet and join of blades x and y
star(x, y) N/A Scalar product with number result

(*) Only in degenerate metrics (**) Loop unrolled for maximum performance

The functions equals, closeTo, add, sub, mul, div, ldivs, wedge, vee also work on the number type.

Linear equation solver

You can solver equations of the form (for the moment pretending that javascript has scalar multiplication of arrays)

x = coeffs[0] * basis[0] + coeffs[1] * basis[1] + ... + coeffs[n-1] * basis[n-1]

for the unknown coefficients coeffs.

const coeffs = vLinSolve(x, basis);

This comes with the added cost of algebra creation on each call. If you need to do a lot of solving create your algebra beforehand.

import {Algebra, linSolve} from 'ts-geometric-algebra';
const Grassmann = Algebra(0, 0, 3);
const x = Grassmann.fromVector([1, 2, 3]);
const basis = [
  Grassmann.fromVector([1, 1, 1]),
  Grassmann.fromVector([0, 1, -1]),
  Grassmann.fromVector([-1, 2, -2])
];
const coeffs = linSolve(x, basis);  // [ 2.5, -3.5, 1.5 ]

Octonions

While all generated geometric algebras are associative it's possible to produce non-associative algebras using custom multiplication tables. This package comes with a pre-configured table for Octonions and monkey-patches conjugate, inverse, sqrt, exp and log to work correctly.

import {makeOctonion} from 'ts-geometric-algebra';
const O = makeOctonion();
const e1 = Octonion.basisBlade(0);
const e2 = Octonion.basisBlade(1);
const e4 = Octonion.basisBlade(2);

e1.mul(e2).mul(e4)  // [0, 0, 0, 0, 0, 0, +1]
e1.mul(e2.mul(e4))  // [0, 0, 0, 0, 0, 0, -1]