-
-
Notifications
You must be signed in to change notification settings - Fork 107
Home
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.
- 0.4 : Prototype - extend on PGA2D, PGA3D, CGA2D and CGA3D examples and adjust features and API as required.
- 0.5 : Optimization - test mixed mode (several subalgebra's interoperating) - allows space reduction for e.g. vector lists.
- 0.6 : Optimization - Basic optimisations on AST (avoid creating to many inbetween multivectors)
- 0.7 : Optimization - GPU and Threading
Ganja.js differs greatly in architecture when compared to other Geometric Algebra implementations (like for example versor.js). These other implementations (typically originaly modeled for c++) often provide in many classes, strictly typed multivectors, and heaps of generated code. These choices are imho not ideal for a JIT language like javascript, and as a result ganja.js provides a different and unified approach counting on current (and future) JIT optimisation techniques to select, compile and optimize specific code as required by a given application or even data-set.
As a result, Ganja.js provides in a very compact (yet complete) implementation that is well suited for use on the web. A large part of ganja.js internals are doing symbolic math resulting in javascript code that ultimately provides in the desired (Numerical) Algebra implementation.
The design of Ganja.js is top-down and reflects as such the ideology behind Geometric Algebra itself. One ring to rule them all.
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 dependant 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 degenrate 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.
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 (hyperreal ?) 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.
Ganja.js provides in matrix-free inverses for all metric signatures up to 5D. (I'm not aware of any other javascript GA implementations that have this feature). Inverses are good to have. Not having to go over 8x8 or 16x16 or 32x32 matrices is desirable.
For degenerate metrics where an inverse obviously does not exist for ideal elements, it could be interesting to investigate what the (if any) geometric meaning is of the matrix pseudo-inverse on the matrix form of a multivector.
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.
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 */}