Skip to content
Steven De Keninck edited this page May 20, 2019 · 10 revisions

Ganja.js Wiki

Read on for some background information, motivation on design choices, roadmap and as I get to it perhaps some extra generic GA information or a guide to available literature.

Roadmap

Ganja.js has been getting some traction in the GA community and as a result, the roadmap has shifted to incorporate features that are higher on the general wish list.

These requests are focused on incorporating other common GA dialects, support for higher dimenional algebras, support for non-parametric visualizations, and a GA-viewer style playground.

A number of student projects at upem Paris are on the board under supervision of Dr Nozick (upem), Hugo Adfield (Cambridge) and myself. Details will be made available shortly.

Architecture

Ganja.js hosts and integrates three big components :

component function varieties
Generator Generate algebra implementation
  • precompiled, flat (-10D)
  • graded (-32D)
Translator Translate dialects to JavaScript
  • javascript (default)
  • AsciiMath
Visualizer Visualize algebraic elements
  • Canvas for 1D and 2D functions
  • SVG for 2D PGA
  • SVG for 3D PGA
  • SVG for 2D CGA
  • webGL for 3D PGA
  • webGL for 3D CGA

The algebra Generator creates an ES6 class implementing the generated algebra. (up to 32D, with support for +1, -1 and 0 metric). It picks an appropriate generater based on the total number of dimensions.

The translator can currently handle two dialects. Javascript with operator overloading and algebraic literals, and ascii-math. It is responsible for translating into compliant JS (using the calls provided by the Algebra class created by the generator). The translator is not dependent on the number of dimensions or metric and stays generically usable.

The visualizer allows displaying algebraic elements. It has a so called parametric implementation for the most popular spaces (projective and conformal 2D and 3D), and is currently being extended with a generic OPNS/IPNS visualizer to help display objects in high-dimensional spaces or when a parametric evaluation is non trivial.

Flat vs Graded

Ganja.js currently implements two Algebra generators. One produces flat (typed array) multivectors, and has fully unrolled and precompiled operators. This generator is used by default for all spaces of 6 or less dimensions.

For high dimensional spaces, ganja.js switches to a sparse graded storage model and dynamic operators. This happens automatically when using spaces of 7 or more dimensions, or by passing in the 'graded' option when the Algebra is created.

Parametric vs Implicit visualisations

For 2D PGA, 3D PGA, 2D CGA and 3D CGA ganja.js has parametric visualization modes that are capable of rendering all elements common to those spaces. (points, lines, planes, circles, spheres). These visualizer come with full camera control and interactivity.

The SVG visualizer can be used for 2D PGA, 3D PGA and 2D CGA, while the webGL visualizer can be used for 3D PGA and 3D CGA.

For other spaces, an implicit OPNS visualizer is available. It renders using sphere-tracing in webGL2 and does not require a parametric specification of your GA objects.

Degenerate Metric.

Correct handling of the degenerate metric (enabling Euclidean PGA) was one of the prerequisites for ganja.js. As a result, it's duality operator is non-metric, and GA formulas dependent on an invertible pseudo scalar are modified to work without, AFAIK, ganja.js is the only JavaScript GA implementation that provides this functionality.

Classic texts in the area of GA have typically ignored the degenerate metric as inconvenient, but it's use for PGA is an absolute must. We refer the reader to the excellent work of Charles Gunn for further details.

For example, his overview page on projective geometric algebra at researchgate.

Custom Cayley table.

Ganja.js currently also supports using a custom Cayley table, allowing for exotic combinations. (in the examples, you'll find an automatic differentiation example that calculates numerically accurate 1st, 2nd, 3rd, .. degree derivatives of any polynomial function using such a custom Algebra.

On my checklist still is to investigate how some of these other interesting algebra's could be formulated by using a metric where basis generators are allowed to square to other generators and not just 1,-1 or zero.

// first basis vector squares to second basis vector. second basis vector squares to 0. 
Algebra({ basis:['1','e1','e2'], metric:['e2',0] }); 

which could generate the following Cayley table :

  1  e1  e2
 e1  e2   0
 e2   0   0 

Which can be used to calculate 1st and 2nd derivatives in automatic differentiation.

Matrix Free inverses.

Ganja.js provides in matrix-free inverses for all metric signatures up to 5D. (I'm not aware of any other javascript GA implementation that has this feature). Inverses are good to have. Not having to go over 8x8 or 16x16 or 32x32 or ... matrices is desirable.

Syntax over Performance

Ganja.js primary focus is enabling programmers and engineers to adapt to this new, easier and unified mathematical language. As such, a clean and accessible syntax is the primary motivation behind ganja.js. In those places where syntax and performance are conflicting interests, the clean and minimal syntax will take precedence.

About the ganja.js translator.

The ganja.js translator that extends javascript with algebraic literals and operator overloading is a great example of how tasks that are typically considered complex and unfit for javascript can actually be implemented with less code and more elegance than in many other languages. Using reflection (the ability of code to inspect/modify itself), has as a consequence that any new syntax needs to be valid javascript syntax. (the js parser will parse the code before the ganja.js parser). That in turn means that we can use a tokenizer and translator that does not have to handle invalid syntax. (the js parser does that for us.)

The result is an nice simplification. The Ganja.js translator is just a handful of code, and it only needs to understand those parts it translates - so it is future safe for new javascript constructions. (That would pass-through unmodified).

For example, the following snippet is used to tokenize a javascript function :

var res=[], tokens=[/^[\s\uFFFF]|^[\u000A\u000D\u2028\u2029]|^\/\/[^\n]*\n|^\/\*[\s\S]*?\*\//g,         
/^\"\"|^\'\'|^\".*?[^\\]\"|^\'.*?[^\\]\'|^\`[\s\S]*?[^\\]\`/g,                                                                
/^\d+[.]{0,1}\d*[eEi][\+\-_]{0,1}\d*|^\.\d+[eEi][\+\-_]{0,1}\d*|^e_\d*/g,                                                     
/^0x\d+|^\d+[.]{0,1}\d*|^\.\d+|^\(\/.*[^\\]\/\)/g,                                                                            
/^(>>>=|===|!==|>>>|<<=|>>=|=>|[<>\+\-\*%&|^\/!\=]=|\*\*|\+\+|\-\-|<<|>>|\&\&|\^\^|^[{}()\[\];.,<>\+\-\*%|&^!~?:=\/]{1})/g,
/^[A-Za-z0-9_]*/g]
while (txt.length) for(t in tokens) if(res=txt.match(tokens[t])){tok.push([t|0,res[0]]);txt=txt.slice(res[0].length);break}

It will turn any javascript source :

()=>{result=thing**2+(1e3); /* comment */}

into an array of tokens (whitespace=0,string-literal=1,blade-literal=2,numeric-literal=3,punctuator=4,identifier=5)

[[4,"("],
[4,")"],
[4,"=>"],
[4,"{"],
[5,"result"],
[4,"="],
[5,"thing"],
[4,"**"],
[3,"2"],
[4,"+"],
[4,"("],
[2,"1e3"],
[4,")"],
[4,";"],
[0," "],
[0,"/* comment */"],
[4,"}"]]

The only gotcha is dealing with regular expressions. There's no way at the tokenize level to distinguish a regex literal from a division. To get around the problem, when you use a regular expressions inside a function that is to be translated by ganja.js make sure it's preceded by a '('. This trick automatically covers many of the use cases for regular expressions like .match(/.../), .test(/.../), etc ..

The known constructs in this array are now transformed by ganja.js (the unknown ones are left unmodified). The result is a new array :

[
 [[2,"("],[2,")"]],
 [4,"=>"],
 [4,"{"],
 [5,"result"],
 [4,"="],
 [
   [1,"this.Add("],
   [
     [1,"this.Pow("],
     [5,"thing"],
     [1,","],
     [3,"2"],
     [1,")"]
   ],
   [1,","],
   [
     [2,"("],
     [2,"this.Coeff(1,1)"],
     [2,")"]
   ],
   [1,")"]
 ],
 [4,";"],
 [0," "],
 [0,"/* comment */"],
 [4,"}"]
]

Which is then collapsed into the translated function :

()=>{result=this.Add(this.Pow(thing,2),(this.Coeff(1,1))); /* comment */}