Skip to content

Instantly share code, notes, and snippets.

@hugohadfield
Created November 27, 2019 17:46
Show Gist options
  • Save hugohadfield/6632b5469bc6474800f77592ac673612 to your computer and use it in GitHub Desktop.
Save hugohadfield/6632b5469bc6474800f77592ac673612 to your computer and use it in GitHub Desktop.
circle_fitting
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "from clifford.g3c import *\nfrom clifford.tools.g3c import *\nfrom clifford.tools.g3c.object_fitting import *\nfrom pyganja import *\nimport numba",
"execution_count": 1,
"outputs": []
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "@numba.njit\ndef val_fit_circle(point_list):\n \"\"\"\n Performs Leo Dorsts circle fitting technique\n \"\"\"\n # Check if there are just 3 points\n if point_list.shape[0] == 3:\n best_obj = point_list[0, :]\n for i in range(1, 3):\n best_obj = omt_func(best_obj, point_list[i, :])\n return val_normalised(best_obj)\n # Loop over our points and construct the matrix\n accumulator_matrix = np.zeros((32, 32))\n for i in range(point_list.shape[0]):\n # Get the point as a left gmt matrix\n P_i_l = get_left_gmt_matrix(point_list[i, :])\n # Multiply and add\n accumulator_matrix += P_i_l @ mask0 @ P_i_l\n accumulator_matrix = accumulator_matrix @ mask1\n # Find the eigenvalues of the matrix\n e_vals, e_vecs = np.linalg.eig(accumulator_matrix)\n# print(e_vals)\n # Find the smallest and second smallest non negative eigenvalues\n min_eval = np.inf\n min_eval2 = np.inf\n min_eval_index = -1\n min_eval_index2 = -1\n for i in range(len(e_vals)):\n this_e_val = e_vals[i]\n if this_e_val > 0:\n if this_e_val < min_eval:\n min_eval2 = min_eval\n min_eval = this_e_val\n min_eval_index2 = min_eval_index\n min_eval_index = i\n else:\n if this_e_val < min_eval2:\n min_eval2 = this_e_val\n min_eval_index2 = i\n# print(min_eval_index, min_eval)\n# print(min_eval_index2, min_eval2)\n best_sphere = val_normalised(mask1@np.real(e_vecs[:, min_eval_index]))\n second_best_sphere = val_normalised(mask1@np.real(e_vecs[:, min_eval_index2]))\n best_circle = val_normalised(mask3@dual_func(omt_func(best_sphere, second_best_sphere)))\n return best_circle\n\n\ndef fit_circle(point_list):\n \"\"\"\n Performs Leo Dorsts circle fitting technique\n \"\"\"\n return layout.MultiVector(value=val_fit_circle(np.array([p.value for p in point_list])))\n",
"execution_count": 2,
"outputs": []
},
{
"metadata": {},
"cell_type": "markdown",
"source": "# Test the algorithms"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "noise = 0.1\nnpnts = 10\n\ntrue_circle = random_circle()\npoint_list = project_points_to_circle([random_conformal_point() for i in range(npnts)], true_circle)\npoint_list = [up(down(P) + noise * random_euc_mv()) for P in point_list]\nsphere = fit_sphere(point_list)\nplane = fit_plane(point_list)\ncircle = meet(sphere,plane).normal()\nprint(true_circle.normal())\nprint(circle.normal())\n\ngs = GanjaScene()\ngs.add_objects(point_list, color=Color.BLACK, static=True)\ngs.add_objects([true_circle], color=Color.GREEN)\ngs.add_objects([circle], color=Color.RED)\ngs.add_objects([fit_circle(point_list)], color=Color.BLUE)\ndraw(gs, scale=0.05)",
"execution_count": 3,
"outputs": [
{
"output_type": "stream",
"text": "(0.58049^e123) + (12.14244^e124) + (12.18716^e125) + (0.21685^e134) + (0.22322^e135) + (0.11658^e145) - (6.69146^e234) - (6.70045^e235) + (0.32736^e245) + (0.07009^e345)\n(0.6578^e123) + (12.16264^e124) + (12.20989^e125) - (0.02278^e134) - (0.01596^e135) + (0.12766^e145) - (7.29606^e234) - (7.30503^e235) + (0.35821^e245) + (0.07591^e345)\n",
"name": "stdout"
},
{
"output_type": "display_data",
"data": {
"text/plain": "<IPython.core.display.Javascript object>",
"application/javascript": "/** Ganja.js - Geometric Algebra - Not Just Algebra. \n * @author Enki\n * @link https://github.com/enkimute/ganja.js\n */\n\n/*********************************************************************************************************************/\n// \n// Ganja.js is an Algebra generator for javascript. It generates a wide variety of Algebra's and supports operator\n// overloading, algebraic literals and a variety of graphing options.\n//\n// Ganja.js is designed with prototyping and educational purposes in mind. Clean mathematical syntax is the primary\n// target.\n//\n// Ganja.js exports only one function called *Algebra*. This function is used to generate Algebra classes. (say complex\n// numbers, minkowski or 3D CGA). The returned class can be used to create, add, multiply etc, but also to upgrade\n// javascript functions with algebraic literals, operator overloading, vectors, matrices and much more.\n//\n// As a simple example, multiplying two complex numbers 3+2i and 1+4i could be done like this :\n//\n// var complex = Algebra(0,1);\n// var a = new complex([3,2]);\n// var b = new complex([1,3]); \n// var result = a.Mul(b);\n// \n// But the same can be written using operator overloading and algebraic literals. (where scientific notation with\n// lowercase e is overloaded to directly specify generators (e1, e2, e12, ...))\n//\n// var result = Algebra(0,1,()=>(3+2e1)*(1+4e1));\n//\n// Please see github for user documentation and examples. \n//\n/*********************************************************************************************************************/\n\n// Documentation below is for implementors. I'll assume you know about Clifford Algebra's, grades, its products, etc ..\n// I'll also assume you are familiar with ES6. My style may feel a bith mathematical, advise is to read slow. \n\n(function (name, context, definition) {\n if (typeof module != 'undefined' && module.exports) module.exports = definition();\n else if (typeof define == 'function' && define.amd) define(name, definition);\n else context[name] = definition();\n}('Algebra', this, function () {\n\n/** The Algebra class generator. Possible calling signatures : \n * Algebra([func]) => algebra with no dimensions, i.e. R. Optional function for the translator.\n * Algebra(p,[func]) => 'p' positive dimensions and an optional function to pass to the translator.\n * Algebra(p,q,[func]) => 'p' positive and 'q' negative dimensions and optional function.\n * Algebra(p,q,r,[func]) => 'p' positive, 'q' negative and 'r' zero dimensions and optional function.\n * Algebra({ => for custom basis, cayley, mixing, etc pass in an object as first parameter.\n * [p:p], => optional 'p' for # of positive dimensions\n * [q:q], => optional 'q' for # of negative dimensions\n * [r:r], => optional 'r' for # of zero dimensions\n * [metric:array], => alternative for p,q,r. e.g. ([1,1,1,-1] for spacetime)\n * [basis:array], => array of strings with basis names. (e.g. ['1','e1','e2','e12'])\n * [Cayley:Cayley], => optional custom Cayley table (strings). (e.g. [['1','e1'],['e1','-1']]) \n * [mix:boolean], => Allows mixing of various algebras. (for space efficiency).\n * [graded:boolean], => Use a graded algebra implementation. (automatic for +6D)\n * [baseType:Float32Array] => optional basetype to use. (only for flat generator)\n * },[func]) => optional function for the translator.\n **/ \n return function Algebra(p,q,r) {\n // Resolve possible calling signatures so we know the numbers for p,q,r. Last argument can always be a function.\n var fu=arguments[arguments.length-1],options=p; if (options instanceof Object) {\n q = (p.q || (p.metric && p.metric.filter(x=>x==-1).length))| 0;\n r = (p.r || (p.metric && p.metric.filter(x=>x==0).length)) | 0;\n p = p.p === undefined ? (p.metric && p.metric.filter(x=>x==1).length) || 0 : p.p || 0;\n } else { options={}; p=p|0; r=r|0; q=q|0; };\n\n // Support for multi-dual-algebras\n if (p==0 && q==0 && r<0) { r=-r; // Create a dual number algebra if r>1 .. consider more explicit syntax\n options.basis = [...Array(r+1)].map((a,i)=>i?'e0'+i:'1'); options.metric = [1,...Array(r)]; options.tot=r+1;\n options.Cayley = [...Array(r+1)].map((a,i)=>[...Array(r+1)].map((y,j)=>i*j==0?((i+j)?'e0'+(i+j):'1'):'0'));\n }\n \n\n // Calculate the total number of dimensions.\n var tot = options.tot = (options.tot||(p||0)+(q||0)+(r||0)||(options.basis&&options.basis.length))|0;\n \n // Unless specified, generate a full set of Clifford basis names. We generate them as an array of strings by starting\n // from numbers in binary representation and changing the set bits into their relative position. \n // Basis names are ordered first per grade, then lexically (not cyclic!). \n // For 10 or more dimensions all names will be double digits ! 1e01 instead of 1e1 .. \n var basis=options.basis||[...Array(2**tot)] // => [undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined]\n .map((x,xi)=>(((1<<30)+xi).toString(2)).slice(-tot||-1) // => [\"000\", \"001\", \"010\", \"011\", \"100\", \"101\", \"110\", \"111\"] (index of array in base 2)\n .replace(/./g,(a,ai)=>a=='0'?'':String.fromCharCode(66+ai-(r!=0)))) // => [\"\", \"3\", \"2\", \"23\", \"1\", \"13\", \"12\", \"123\"] (1 bits replaced with their positions, 0's removed)\n .sort((a,b)=>(a.toString().length==b.toString().length)?(a>b?1:b>a?-1:0):a.toString().length-b.toString().length) // => [\"\", \"1\", \"2\", \"3\", \"12\", \"13\", \"23\", \"123\"] (sorted numerically)\n .map(x=>x&&'e'+(x.replace(/./g,x=>('0'+(x.charCodeAt(0)-65)).slice(tot>9?-2:-1) ))||'1') // => [\"1\", \"e1\", \"e2\", \"e3\", \"e12\", \"e13\", \"e23\", \"e123\"] (converted to commonly used basis names)\n \n // See if the basis names start from 0 or 1, store grade per component and lowest component per grade. \n var low=basis.length==1?1:basis[1].match(/\\d+/g)[0]*1,\n grades=options.grades||basis.map(x=>tot>9?(x.length-1)/2:x.length-1),\n grade_start=grades.map((a,b,c)=>c[b-1]!=a?b:-1).filter(x=>x+1).concat([basis.length]);\n\n // String-simplify a concatenation of two basis blades. (and supports custom basis names e.g. e21 instead of e12) \n // This is the function that implements e1e1 = +1/-1/0 and e1e2=-e2e1. The brm function creates the remap dictionary.\n var simplify = (s,p,q,r)=>{\n var sign=1,c,l,t=[],f=true,ss=s.match(tot>9?/(\\d\\d)/g:/(\\d)/g);if (!ss) return s; s=ss; l=s.length;\n while (f) { f=false;\n // implement Ex*Ex = metric.\n for (var i=0; i<l;) if (s[i]===s[i+1]) { if ((s[i]-low)>=(p+r)) sign*=-1; else if ((s[i]-low)<r) sign=0; i+=2; f=true; } else t.push(s[i++]);\n // implement Ex*Ey = -Ey*Ex while sorting basis vectors. \n for (var i=0; i<t.length-1; i++) if (t[i]>t[i+1]) { c=t[i];t[i]=t[i+1];t[i+1]=c;sign*=-1;f=true; break;} if (f) { s=t;t=[];l=s.length; }\n }\n var ret=(sign==0)?'0':((sign==1)?'':'-')+(t.length?'e'+t.join(''):'1'); return (brm&&brm[ret])||(brm&&brm['-'+ret]&&'-'+brm['-'+ret])||ret;\n },\n brm=(x=>{ var ret={}; for (var i in basis) ret[basis[i]=='1'?'1':simplify(basis[i],p,q,r)] = basis[i]; return ret; })(basis);\n \n // As an alternative to the string fiddling, one can also bit-fiddle. In this case the basisvectors are represented by integers with 1 bit per generator set.\n var simplify_bits = (A,B,p2)=>{ var n=p2||(p+q+r),t=0,ab=A&B,res=A^B; if (ab&((1<<r)-1)) return [0,0]; while (n--) t^=(A=A>>1); t&=B; t^=ab>>(p+r); t^=t>>16; t^=t>>8; t^=t>>4; return [1-2*(27030>>(t&15)&1),res]; },\n bc = (v)=>{ v=v-((v>>1)& 0x55555555); v=(v&0x33333333)+((v>>2)&0x33333333); c=((v+(v>>4)&0xF0F0F0F)*0x1010101)>>24; return c }; \n \n if (!options.graded && tot <= 6 || options.graded===false || options.Cayley) {\n // Faster and degenerate-metric-resistant dualization. (a remapping table that maps items into their duals). \n var drm=basis.map((a,i)=>{ return {a:a,i:i} })\n .sort((a,b)=>a.a.length>b.a.length?1:a.a.length<b.a.length?-1:(+a.a.slice(1).split('').sort().join(''))-(+b.a.slice(1).split('').sort().join('')) )\n .map(x=>x.i).reverse(),\n drms=drm.map((x,i)=>(x==0||i==0)?1:simplify(basis[x]+basis[i])[0]=='-'?-1:1);\n \n /// Store the full metric (also for bivectors etc ..) \n var metric = basis.map((x,xi)=>simplify(x+x,p,q,r)|0);\n \n /// Generate multiplication tables for the outer and geometric products. \n var mulTable = options.Cayley||basis.map(x=>basis.map(y=>(x==1)?y:(y==1)?x:simplify(x+y,p,q,r)));\n\n /// Convert Cayley table to product matrices. The outer product selects the strict sum of the GP (but without metric), the inner product\n /// is the left contraction. \n var gp=basis.map(x=>basis.map(x=>'0')), cp=gp.map(x=>gp.map(x=>'0')), cps=gp.map(x=>gp.map(x=>'0')), op=gp.map(x=>gp.map(x=>'0')), gpo={}; // Storage for our product tables.\n basis.forEach((x,xi)=>basis.forEach((y,yi)=>{ var n = mulTable[xi][yi].replace(/^-/,''); if (!gpo[n]) gpo[n]=[]; gpo[n].push([xi,yi]); }));\n basis.forEach((o,oi)=>{\n gpo[o].forEach(([xi,yi])=>op[oi][xi]=(grades[oi]==grades[xi]+grades[yi])?((mulTable[xi][yi]=='0')?'0':((mulTable[xi][yi][0]!='-')?'':'-')+'b['+yi+']*this['+xi+']'):'0');\n gpo[o].forEach(([xi,yi])=>{\n gp[oi][xi] =((gp[oi][xi]=='0')?'':gp[oi][xi]+'+') + ((mulTable[xi][yi]=='0')?'0':((mulTable[xi][yi][0]!='-')?'':'-')+'b['+yi+']*this['+xi+']');\n cp[oi][xi] =((cp[oi][xi]=='0')?'':cp[oi][xi]+'+') + ((grades[oi]==grades[yi]-grades[xi])?gp[oi][xi]:'0'); \n cps[oi][xi]=((cps[oi][xi]=='0')?'':cps[oi][xi]+'+') + ((grades[oi]==Math.abs(grades[yi]-grades[xi]))?gp[oi][xi]:'0'); \n });\n });\n \n /// Flat Algebra Multivector Base Class.\n var generator = class MultiVector extends (options.baseType||Float32Array) {\n /// constructor - create a floating point array with the correct number of coefficients.\n constructor(a) { super(a||basis.length); return this; }\n \n /// grade selection - return a only the part of the input with the specified grade. \n Grade(grade,res) { res=res||new this.constructor(); for (var i=0,l=res.length; i<l; i++) if (grades[i]==grade) res[i]=this[i]; else res[i]=0; return res; }\n Even(res) { res=res||new this.constructor(); for (var i=0,l=res.length; i<l; i++) if (grades[i]%2==0) res[i]=this[i]; else res[i]=0; return res; }\n \n /// grade creation - convert array with just one grade to full multivector.\n nVector(grade,...args) { this.set(args,grade_start[grade]); return this; }\n \n /// Fill in coordinates (accepts sequence of index,value as arguments)\n Coeff() { for (var i=0,l=arguments.length; i<l; i+=2) this[arguments[i]]=arguments[i+1]; return this; }\n \n /// Negates specific grades (passed in as args)\n Map(res, ...a) { for (var i=0, l=res.length; i<l; i++) res[i] = (~a.indexOf(grades[i]))?-this[i]:this[i]; return res; }\n\n /// Returns the vector grade only.\n get Vector () { return this.slice(grade_start[1],grade_start[2]); };\n\n toString() { var res=[]; for (var i=0; i<basis.length; i++) if (Math.abs(this[i])>1e-10) res.push(((this[i]==1)&&i?'':((this[i]==-1)&&i)?'-':(this[i].toFixed(10)*1))+(i==0?'':tot==1&&q==1?'i':basis[i].replace('e','e_'))); return res.join('+').replace(/\\+-/g,'-')||'0'; }\n } \n \n /// Convert symbolic matrices to code. (skipping zero's on dot and wedge matrices).\n /// These all do straightforward string fiddling. If the 'mix' option is set they reference basis components using e.g. '.e1' instead of eg '[3]' .. so that\n /// it will work for elements of subalgebras etc.\n generator.prototype.Add = new Function('b,res','res=res||new this.constructor();\\n'+basis.map((x,xi)=>'res['+xi+']=b['+xi+']+this['+xi+']').join(';\\n').replace(/(b|this)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';\\nreturn res')\n generator.prototype.Scale = new Function('b,res','res=res||new this.constructor();\\n'+basis.map((x,xi)=>'res['+xi+']=b*this['+xi+']').join(';\\n').replace(/(b|this)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';\\nreturn res')\n generator.prototype.Sub = new Function('b,res','res=res||new this.constructor();\\n'+basis.map((x,xi)=>'res['+xi+']=this['+xi+']-b['+xi+']').join(';\\n').replace(/(b|this)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';\\nreturn res')\n generator.prototype.Mul = new Function('b,res','res=res||new this.constructor();\\n'+gp.map((r,ri)=>'res['+ri+']='+r.join('+').replace(/\\+\\-/g,'-').replace(/(\\w*?)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a).replace(/\\+0/g,'')+';').join('\\n')+'\\nreturn res;');\n generator.prototype.LDot = new Function('b,res','res=res||new this.constructor();\\n'+cp.map((r,ri)=>'res['+ri+']='+r.join('+').replace(/\\+\\-/g,'-').replace(/\\+0/g,'').replace(/(\\w*?)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';').join('\\n')+'\\nreturn res;');\n generator.prototype.Dot = new Function('b,res','res=res||new this.constructor();\\n'+cps.map((r,ri)=>'res['+ri+']='+r.join('+').replace(/\\+\\-/g,'-').replace(/\\+0/g,'').replace(/(\\w*?)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';').join('\\n')+'\\nreturn res;');\n generator.prototype.Wedge = new Function('b,res','res=res||new this.constructor();\\n'+op.map((r,ri)=>'res['+ri+']='+r.join('+').replace(/\\+\\-/g,'-').replace(/\\+0/g,'').replace(/(\\w*?)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';').join('\\n')+'\\nreturn res;');\n generator.prototype.Vee = new Function('b,res','res=res||new this.constructor();\\n'+op.map((r,ri)=>'res['+drm[ri]+']='+r.map(x=>x.replace(/\\[(.*?)\\]/g,function(a,b){return '['+(drm[b|0])+']'})).join('+').replace(/\\+\\-/g,'-').replace(/\\+0/g,'').replace(/(\\w*?)\\[(.*?)\\]/g,(a,b,c)=>options.mix?'('+b+'.'+(c|0?basis[c]:'s')+'||0)':a)+';').join('\\n')+'\\nreturn res;');\n\n /// Add getter and setters for the basis vectors/bivectors etc .. \n basis.forEach((b,i)=>{generator.prototype.__defineGetter__(i?b:'s',function(){ return this[i] }); }); \n basis.forEach((b,i)=>{generator.prototype.__defineSetter__(i?b:'s',function(x){ this[i]=x; }); });\n \n /// Reversion, Involutions, Conjugation for any number of grades, component acces shortcuts.\n generator.prototype.__defineGetter__('Negative', function(){ var res = new this.constructor(); for (var i=0; i<this.length; i++) res[i]= -this[i]; return res; });\n generator.prototype.__defineGetter__('Reverse', function(){ var res = new this.constructor(); for (var i=0; i<this.length; i++) res[i]= this[i]*[1,1,-1,-1][grades[i]%4]; return res; });\n generator.prototype.__defineGetter__('Involute', function(){ var res = new this.constructor(); for (var i=0; i<this.length; i++) res[i]= this[i]*[1,-1,1,-1][grades[i]%4]; return res; });\n generator.prototype.__defineGetter__('Conjugate',function(){ var res = new this.constructor(); for (var i=0; i<this.length; i++) res[i]= this[i]*[1,-1,-1,1][grades[i]%4]; return res; });\n \n /// The Dual, Length, non-metric length and normalized getters. \n generator.prototype.__defineGetter__('Dual',function(){ if (r) return this.map((x,i,a)=>a[drm[i]]*drms[i]); var res = new this.constructor(); res[res.length-1]=1; return res.Mul(this); });\n generator.prototype.__defineGetter__('Length', function(){ return Math.sqrt(Math.abs(this.Mul(this.Conjugate).s)); }); \n generator.prototype.__defineGetter__('VLength', function(){ var res = 0; for (var i=0; i<this.length; i++) res += this[i]*this[i]; return Math.sqrt(res); });\n generator.prototype.__defineGetter__('Normalized', function(){ var res = new this.constructor(),l=this.Length; if (!l) return this; l=1/l; for (var i=0; i<this.length; i++) res[i]=this[i]*l; return res; });\n \n /// Graded generator for high-dimensional algebras.\n } else {\n \n /// extra graded lookups.\n var basisg = grade_start.slice(0,grade_start.length-1).map((x,i)=>basis.slice(x,grade_start[i+1]));\n var counts = grade_start.map((x,i,a)=>i==a.length-1?0:a[i+1]-x).slice(0,tot+1);\n var basis_bits = basis.map(x=>x=='1'?0:x.slice(1).match(tot>9?/\\d\\d/g:/\\d/g).reduce((a,b)=>a+(1<<(b-low)),0)),\n bits_basis = []; basis_bits.forEach((b,i)=>bits_basis[b]=i); \n var metric = basisg.map((x,xi)=>x.map((y,yi)=>simplify_bits(basis_bits[grade_start[xi]+yi],basis_bits[grade_start[xi]+yi])[0]));\n var drms = basisg.map((x,xi)=>x.map((y,yi)=>simplify_bits(basis_bits[grade_start[xi]+yi],(~basis_bits[grade_start[xi]+yi])&((1<<tot)-1))[0]));\n \n /// Flat Algebra Multivector Base Class.\n var generator = class MultiVector extends Array {\n /// constructor - create a floating point array with the correct number of coefficients.\n constructor(a) { super(a||tot); return this; }\n \n /// grade selection - return a only the part of the input with the specified grade. \n Grade(grade,res) { res=new this.constructor(); res[grade] = this[grade]; return res; }\n \n /// grade creation - convert array with just one grade to full multivector.\n nVector(grade,...args) { this[grade]=args; return this; }\n \n /// Fill in coordinates (accepts sequence of index,value as arguments)\n Coeff() { \n for (var i=0,l=arguments.length; i<l; i+=2) { \n var gi = grades[arguments[i]];\n if (this[gi]==undefined) this[gi]=[];\n this[gi][arguments[i]-grade_start[gi]]=arguments[i+1]; \n } \n return this; \n }\n \n /// Negates specific grades (passed in as args)\n Map(res, ...a) { /* tbc */ }\n\n /// Returns the vector grade only.\n get Vector () { return this[1] };\n \n /// multivector addition, subtraction and scalar multiplication.\n Add(b,r) {\n r=r||new this.constructor();\n for (var i=0,l=Math.max(this.length,b.length);i<l;i++) \n if (!this[i] || !b[i]) r[i] = (!this[i]) ? b[i]:this[i];\n else { if (r[i]==undefined) r[i]=[]; for(var j=0,m=Math.max(this[i].length,b[i].length);j<m;j++) \n {\n if (typeof this[i][j]==\"string\" || typeof r[i][j]==\"string\" || typeof b[i][j]==\"string\") {\n if (!this[i][j]) r[i][j] = \"\"+b[i][j];\n else if (!b[i][j]) r[i][j] = \"\"+this[i][j];\n else r[i][j]=\"(\"+(this[i][j]||\"0\")+(b[i][j][0]==\"-\"?\"\":\"+\")+(b[i][j]||\"0\")+\")\"; \n } else r[i][j]=(this[i][j]||0)+(b[i][j]||0); \n }}\n return r;\n }\n Sub(b,r) {\n r=r||new this.constructor();\n for (var i=0,l=Math.max(this.length,b.length);i<l;i++) \n if (!this[i] || !b[i]) r[i] = (!this[i]) ? (b[i]?b[i].map(x=>(typeof x==\"string\")?\"-\"+x:-x):undefined):this[i];\n else { if (r[i]==undefined) r[i]=[]; for(var j=0,m=Math.max(this[i].length,b[i].length);j<m;j++) \n if (typeof this[i][j]==\"string\" || typeof r[i][j]==\"string\" || typeof b[i][j]==\"string\") r[i][j]=\"(\"+(this[i][j]||\"0\")+\"-\"+(b[i][j]||\"0\")+\")\"; \n else r[i][j]=(this[i][j]||0)-(b[i][j]||0); \n } \n return r;\n }\n Scale(s) { return this.map(x=>x&&x.map(y=>typeof y==\"string\"?y+\"*\"+s:y*s)); } \n \n // geometric product.\n Mul(b,r) {\n r=r||new this.constructor(); var gotstring=false;\n for (var i=0,x,gsx; gsx=grade_start[i],x=this[i],i<this.length; i++) if (x) for (var j=0,y,gsy;gsy=grade_start[j],y=b[j],j<b.length; j++) if (y) for (var a=0; a<x.length; a++) if (x[a]) for (var bb=0; bb<y.length; bb++) if (y[bb]) {\n if (i==j && a==bb) { r[0] = r[0]||(typeof x[0]==\"string\" || typeof y[bb]==\"string\"?[\"\"]:[0]); \n if (typeof x[a]==\"string\" || typeof r[0][0]==\"string\" || typeof y[bb]==\"string\") {\n r[0][0] = (r[0][0]?(r[0][0]+(x[a][0]==\"-\"?\"\":\"+\")):\"\")+ x[a]+\"*\"+y[bb]+(metric[i][a]!=1?\"*\"+metric[i][a]:\"\"); gotstring=true;\n } else r[0][0] += x[a]*y[bb]*metric[i][a]; \n } else { \n var rn=simplify_bits(basis_bits[gsx+a],basis_bits[gsy+bb]), g=bc(rn[1]), e=bits_basis[rn[1]]-grade_start[g]; \n if (!r[g])r[g]=[]; \n if (typeof r[g][e]==\"string\"||typeof x[a]==\"string\"||typeof y[bb]==\"string\") { \n r[g][e] = (r[g][e]?r[g][e]+\"+\":\"\") + (rn[0]!=1?rn[0]+\"*\":\"\")+ x[a]+(y[bb]!=1?\"*\"+y[bb]:\"\"); gotstring=true;\n } else r[g][e] = (r[g][e]||0) + rn[0]*x[a]*y[bb];\n } \n }\n if (gotstring) return r.map(g=>g.map(e=>e&&'('+e+')'))\n return r;\n } \n // outer product. \n Wedge(b,r) {\n r=r||new this.constructor();\n for (var i=0,x,gsx; gsx=grade_start[i],x=this[i],i<this.length; i++) if (x) for (var j=0,y,gsy;gsy=grade_start[j],y=b[j],j<b.length; j++) if (y) for (var a=0; a<x.length; a++) if (x[a]) for (var bb=0; bb<y.length; bb++) if (y[bb]) {\n if (i!=j || a!=bb) { \n var n1=basis_bits[gsx+a], n2=basis_bits[gsy+bb], rn=simplify_bits(n1,n2,tot), g=bc(rn[1]), e=bits_basis[rn[1]]-grade_start[g]; \n if (g == i+j) { if (!r[g]) r[g]=[]; r[g][e] = (r[g][e]||0) + rn[0]*x[a]*y[bb]; }\n } \n }\n return r;\n } \n // outer product glsl output. \n OPNS_GLSL(b,point_source) {\n var r='',count=0,curg;\n for (var i=0,x,gsx; gsx=grade_start[i],x=this[i],i<this.length; i++) if (x) for (var j=0,y,gsy;gsy=grade_start[j],y=b[j],j<b.length; j++) if (y) for (var a=0; a<counts[i]; a++) for (var bb=0; bb<counts[j]; bb++) {\n if (i!=j || a!=bb) { \n var n1=basis_bits[gsx+a], n2=basis_bits[gsy+bb], rn=simplify_bits(n1,n2,tot), g=bc(rn[1]), e=bits_basis[rn[1]]-grade_start[g]; \n if (g == i+j) { curg=g; r += `res[${e}]${rn[0]=='1'?\"+=\":\"-=\"}(${point_source[a]})*b[${bb}]; //${count++}\\n`; }\n } \n }\n r=r.split('\\n').filter(x=>x).sort((a,b)=>((a.match(/\\d+/)[0]|0)-(b.match(/\\d+/)[0]|0))||((a.match(/\\d+$/)[0]|0)-(b.match(/\\d+$/)[0]|0))).map(x=>x.replace(/\\/\\/\\d+$/,''));\n var r2 = 'float sum=0.0; float res=0.0;\\n', g=0;\n r.forEach(x=>{\n var cg = x.match(/\\d+/)[0]|0;\n if (cg != g) r2 += \"sum \"+(((metric[curg][g]==-1))?\"-=\":\"+=\")+\" res*res;\\nres = 0.0;\\n\";\n r2 += x.replace(/\\[\\d+\\]/,'') + '\\n';\n g=cg;\n });\n r2+= \"sum \"+((metric[curg][g]==-1)?\"-=\":\"+=\")+\" res*res;\\n\";\n return r2;\n } \n // Left contraction.\n LDot(b,r) {\n r=r||new this.constructor();\n for (var i=0,x,gsx; gsx=grade_start[i],x=this[i],i<this.length; i++) if (x) for (var j=0,y,gsy;gsy=grade_start[j],y=b[j],j<b.length; j++) if (y) for (var a=0; a<x.length; a++) if (x[a]) for (var bb=0; bb<y.length; bb++) if (y[bb]) {\n if (i==j && a==bb) { r[0] = r[0]||[0]; r[0][0] += x[a]*y[bb]*metric[i][a]; } \n else { \n var rn=simplify_bits(basis_bits[gsx+a],basis_bits[gsy+bb]), g=bc(rn[1]), e=bits_basis[rn[1]]-grade_start[g]; \n if (g == j-i) { if (!r[g])r[g]=[]; r[g][e] = (r[g][e]||0) + rn[0]*x[a]*y[bb]; }\n } \n }\n return r;\n } \n // Symmetric contraction.\n Dot(b,r) {\n r=r||new this.constructor();\n for (var i=0,x,gsx; gsx=grade_start[i],x=this[i],i<this.length; i++) if (x) for (var j=0,y,gsy;gsy=grade_start[j],y=b[j],j<b.length; j++) if (y) for (var a=0; a<x.length; a++) if (x[a]) for (var bb=0; bb<y.length; bb++) if (y[bb]) {\n if (i==j && a==bb) { r[0] = r[0]||[0]; r[0][0] += x[a]*y[bb]*metric[i][a]; } \n else { \n var rn=simplify_bits(basis_bits[gsx+a],basis_bits[gsy+bb]), g=bc(rn[1]), e=bits_basis[rn[1]]-grade_start[g]; \n if (g == Math.abs(j-i)) { if (!r[g])r[g]=[]; r[g][e] = (r[g][e]||0) + rn[0]*x[a]*y[bb]; }\n } \n }\n return r;\n } \n // Should be optimized.. \n Vee(b,r) { return (this.Dual.Wedge(b.Dual)).Dual; }\n // Output, lengths, involutions, normalized, dual. \n toString() { return [...this].map((g,gi)=>g&&g.map((c,ci)=>!c?undefined:c+basisg[gi][ci]).filter(x=>x).join('+')).filter(x=>x).join('+').replace(/\\+\\-/g,'-'); } \n get s () { if (this[0]) return this[0][0]||0; return 0; }\n get Length () { var res=0; this.forEach((g,gi)=>g&&g.forEach((e,ei)=>res+=(e||0)**2*metric[gi][ei])); return Math.abs(res)**.5; }\n get VLength () { var res=0; this.forEach((g,gi)=>g&&g.forEach((e,ei)=>res+=(e||0)**2)); return Math.abs(res)**.5; }\n get Reverse () { var r=new this.constructor(); this.forEach((x,gi)=>x&&x.forEach((e,ei)=>{if(!r[gi])r[gi]=[]; r[gi][ei] = this[gi][ei]*[1,1,-1,-1][gi%4]; })); return r; }\n get Involute () { var r=new this.constructor(); this.forEach((x,gi)=>x&&x.forEach((e,ei)=>{if(!r[gi])r[gi]=[]; r[gi][ei] = this[gi][ei]*[1,-1,1,-1][gi%4]; })); return r; }\n get Conjugate () { var r=new this.constructor(); this.forEach((x,gi)=>x&&x.forEach((e,ei)=>{if(!r[gi])r[gi]=[]; r[gi][ei] = this[gi][ei]*[1,-1,-1,1][gi%4]; })); return r; }\n get Dual() { var r=new this.constructor(); this.forEach((g,gi)=>{ if (!g) return; r[tot-gi]=[]; g.forEach((e,ei)=>r[tot-gi][counts[gi]-1-ei]=drms[gi][ei]*e); }); return r; }\n get Normalized () { return this.Scale(1/this.Length); }\n } \n\n \n // This generator is UNDER DEVELOPMENT - I'm publishing it so I can test on observable.\n }\n \n // Generate a new class for our algebra. It extends the javascript typed arrays (default float32 but can be specified in options).\n var res = class Element extends generator {\n \n // constructor - create a floating point array with the correct number of coefficients.\n constructor(a) { super(a); return this; }\n \n // Grade selection. (implemented by parent class). \n Grade(grade,res) { res=res||new Element(); return super.Grade(grade,res); }\n \n // Right and Left divide - Defined on the elements, shortcuts to multiplying with the inverse. \n Div (b,res) { return this.Mul(b.Inverse,res); }\n LDiv (b,res) { return b.Inverse.Mul(this,res); }\n \n // Taylor exp - I will replace this with something smarter for elements of the even subalgebra's and other pure blades. \n Exp () { \n if (r==1 && tot<=4 && this[0]==0) { \n var sq = (tot==4)?-(this[8]**2+this[9]**2+this[10]**2):this.Mul(this).s; if (sq==0) { var res = this.slice();res[0]+=1; return res; }\n var l = Math.sqrt(Math.abs(sq)); if (sq<0) { var res = this.Scale( Math.sin(l)/l ); res[0]=Math.cos(l); return res; }\n var res = this.Scale( Math.sinh(l)/l ); res[0]=Math.cosh(l); return res;\n }\n var res = Element.Scalar(1), y=1, M= new Element(this), N=new Element(this); for (var x=1; x<25; x++) { res=res.Add(M.Mul(Element.Scalar(1/y))); M=M.Mul(N); y=y*(x+1); }; return res; \n }\n \n // Helper for efficient inverses. (custom involutions - negates grades in arguments). \n Map () { var res=new Element(); return super.Map(res,...arguments); }\n \n // Factories - Make it easy to generate vectors, bivectors, etc when using the functional API. None of the examples use this but\n // users that have used other GA libraries will expect these calls. The Coeff() is used internally when translating algebraic literals.\n static Element() { return new Element([...arguments]); };\n static Coeff() { return (new Element()).Coeff(...arguments); }\n static Scalar(x) { return (new Element()).Coeff(0,x); }\n static Vector() { return (new Element()).nVector(1,...arguments); }\n static Bivector() { return (new Element()).nVector(2,...arguments); }\n static Trivector() { return (new Element()).nVector(3,...arguments); }\n static nVector(n) { return (new Element()).nVector(...arguments); }\n \n // Static operators. The parser will always translate operators to these static calls so that scalars, vectors, matrices and other non-multivectors can also be handled.\n // The static operators typically handle functions and matrices, calling through to element methods for multivectors. They are intended to be flexible and allow as many\n // types of arguments as possible. If performance is a consideration, one should use the generated element methods instead. (which only accept multivector arguments)\n static toEl(x) { if (x instanceof Function) x=x(); if (!(x instanceof Element)) x=Element.Scalar(x); return x; }\n \n // Addition and subtraction. Subtraction with only one parameter is negation. \n static Add(a,b,res) { \n // Resolve expressions passed in.\n while(a.call)a=a(); while(b.call)b=b(); if (a.Add && b.Add) return a.Add(b,res); \n // If either is a string, the result is a string. \n if ((typeof a=='string')||(typeof b=='string')) return a.toString()+b.toString(); \n // If only one is an array, add the other element to each of the elements. \n if ((a instanceof Array)^(b instanceof Array)) return (a instanceof Array)?a.map(x=>Element.Add(x,b)):b.map(x=>Element.Add(a,x)); \n // If both are equal length arrays, add elements one-by-one \n if ((a instanceof Array)&&(b instanceof Array)&&a.length==b.length) return a.map((x,xi)=>Element.Add(x,b[xi])); \n // If they're both not elements let javascript resolve it. \n if (!(a instanceof Element || b instanceof Element)) return a+b; \n // Here we're left with scalars and multivectors, call through to generated code. \n a=Element.toEl(a); b=Element.toEl(b); return a.Add(b,res);\n }\n \n static Sub(a,b,res) { \n // Resolve expressions passed in.\n while(a.call)a=a(); while(b&&b.call) b=b(); if (a.Sub && b && b.Sub) return a.Sub(b,res);\n // If only one is an array, add the other element to each of the elements. \n if (b&&((a instanceof Array)^(b instanceof Array))) return (a instanceof Array)?a.map(x=>Element.Sub(x,b)):b.map(x=>Element.Sub(a,x)); \n // If both are equal length arrays, add elements one-by-one \n if (b&&(a instanceof Array)&&(b instanceof Array)&&a.length==b.length) return a.map((x,xi)=>Element.Sub(x,b[xi])); \n // Negation \n if (arguments.length==1) return Element.Mul(a,-1); \n // If none are elements here, let js do it. \n if (!(a instanceof Element || b instanceof Element)) return a-b; \n // Here we're left with scalars and multivectors, call through to generated code. \n a=Element.toEl(a); b=Element.toEl(b); return a.Sub(b,res);\n }\n \n // The geometric product. (or matrix*matrix, matrix*vector, vector*vector product if called with 1D and 2D arrays)\n static Mul(a,b,res) {\n // Resolve expressions \n while(a.call&&!a.length)a=a(); while(b.call&&!b.length)b=b(); if (a.Mul && b.Mul) return a.Mul(b,res);\n // still functions -> experimental curry style (dont use this.)\n if (a.call && b.call) return (ai,bi)=>Element.Mul(a(ai),b(bi)); \n // scalar mul.\n if (Number.isFinite(a) && b.Scale) return b.Scale(a); else if (Number.isFinite(b) && a.Scale) return a.Scale(b); \n // Handle matrices and vectors. \n if ((a instanceof Array)&&(b instanceof Array)) { \n // vector times vector performs a dot product. (which internally uses the GP on each component)\n if((!(a[0] instanceof Array) || (a[0] instanceof Element)) &&(!(b[0] instanceof Array) || (b[0] instanceof Element))) { var r=tot?Element.Scalar(0):0; a.forEach((x,i)=>r=Element.Add(r,Element.Mul(x,b[i]),r)); return r; } \n // Array times vector \n if(!(b[0] instanceof Array)) return a.map((x,i)=>Element.Mul(a[i],b)); \n // Array times Array \n var r=a.map((x,i)=>b[0].map((y,j)=>{ var r=tot?Element.Scalar(0):0; x.forEach((xa,k)=>r=Element.Add(r,Element.Mul(xa,b[k][j]))); return r; })); \n // Return resulting array or scalar if 1 by 1. \n if (r.length==1 && r[0].length==1) return r[0][0]; else return r; \n } \n // Only one is an array multiply each of its elements with the other. \n if ((a instanceof Array)^(b instanceof Array)) return (a instanceof Array)?a.map(x=>Element.Mul(x,b)):b.map(x=>Element.Mul(a,x)); \n // Try js multiplication, else call through to geometric product. \n var r=a*b; if (!isNaN(r)) return r; \n a=Element.toEl(a); b=Element.toEl(b); return a.Mul(b,res);\n } \n \n // The inner product. (default is left contraction). \n static LDot(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); //if (a.LDot) return a.LDot(b,res);\n // Map elements in array \n if (b instanceof Array && !(a instanceof Array)) return b.map(x=>Element.LDot(a,x)); \n if (a instanceof Array && !(b instanceof Array)) return a.map(x=>Element.LDot(x,b)); \n // js if numbers, else contraction product. \n if (!(a instanceof Element || b instanceof Element)) return a*b; \n a=Element.toEl(a);b=Element.toEl(b); return a.LDot(b,res); \n } \n \n // The symmetric inner product. (default is left contraction). \n static Dot(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); //if (a.LDot) return a.LDot(b,res);\n // js if numbers, else contraction product. \n if (!(a instanceof Element || b instanceof Element)) return a|b; \n a=Element.toEl(a);b=Element.toEl(b); return a.Dot(b,res); \n } \n \n // The outer product. (Grassman product - no use of metric) \n static Wedge(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); if (a.Wedge) return a.Wedge(Element.toEl(b),res); \n // The outer product of two vectors is a matrix .. internally Mul not Wedge ! \n if (a instanceof Array && b instanceof Array) return a.map(xa=>b.map(xb=>Element.Mul(xa,xb)));\n // js, else generated wedge product.\n if (!(a instanceof Element || b instanceof Element)) return a*b; \n a=Element.toEl(a);b=Element.toEl(b); return a.Wedge(b,res); \n } \n \n // The regressive product. (Dual of the outer product of the duals). \n static Vee(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); if (a.Vee) return a.Vee(Element.toEl(b),res);\n // js, else generated vee product. (shortcut for dual of wedge of duals)\n if (!(a instanceof Element || b instanceof Element)) return 0; \n a=Element.toEl(a);b=Element.toEl(b); return a.Vee(b,res); \n } \n \n // The sandwich product. Provided for convenience (>>> operator) \n static sw(a,b) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); if (a.sw) return a.sw(b);\n // Map elements in array \n if (b instanceof Array) return b.map(x=>Element.sw(a,x)); \n // Call through. no specific generated code for it so just perform the muls. \n a=Element.toEl(a); b=Element.toEl(b); return a.Mul(b).Mul(a.Conjugate); \n }\n\n // Division - scalars or cal through to element method.\n static Div(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b();\n // js or call through to element divide. \n if (!(a instanceof Element || b instanceof Element)) return a/b; \n a=Element.toEl(a);\n if (Number.isFinite(b)) { return a.Scale(1/b,res); }\n b=Element.toEl(b); return a.Div(b,res); \n } \n \n // Pow - needs obvious extensions for natural powers. (exponentiation by squaring) \n static Pow(a,b,res) { \n // Expressions\n while(a.call)a=a(); while(b.call)b=b(); if (a.Pow) return a.Pow(b,res); \n // Exponentiation. \n if (a==Math.E && b.Exp) return b.Exp(); \n // Squaring \n if (b===2) return this.Mul(a,a,res);\n // No elements, call through to js \n if (!(a instanceof Element || b instanceof Element)) return a**b; \n // Inverse \n if (b===-1) return a.Inverse; \n // Call through to element pow. \n a=Element.toEl(a); return a.Pow(b); \n } \n \n // Handles scalars and calls through to element method. \n static exp(a) { \n // Expressions.\n while(a.call)a=a(); \n // If it has an exp callthrough, use it, else call through to math. \n if (a.Exp) return a.Exp(); \n return Math.exp(a); \n }\n \n // Dual, Involute, Reverse, Conjugate, Normalize and length, all direct call through. Conjugate handles matrices.\n static Dual(a) { return Element.toEl(a).Dual; }; \n static Involute(a) { return Element.toEl(a).Involute; }; \n static Reverse(a) { return Element.toEl(a).Reverse; }; \n static Conjugate(a) { if (a.Conjugate) return a.Conjugate; if (a instanceof Array) return a[0].map((c,ci)=>a.map((r,ri)=>Element.Conjugate(a[ri][ci]))); return Element.toEl(a).Conjugate; }\n static Normalize(a) { return Element.toEl(a).Normalized; }; \n static Length(a) { return Element.toEl(a).Length };\n \n // Comparison operators always use length. Handle expressions, then js or length comparison \n static eq(a,b) { if (!(a instanceof Element)||!(b instanceof Element)) return a==b; while(a.call)a=a(); while(b.call)b=b(); for (var i=0; i<a.length; i++) if (a[i]!=b[i]) return false; return true; }\n static neq(a,b) { if (!(a instanceof Element)||!(b instanceof Element)) return a!=b; while(a.call)a=a(); while(b.call)b=b(); for (var i=0; i<a.length; i++) if (a[i]!=b[i]) return true; return false; }\n static lt(a,b) { while(a.call)a=a(); while(b.call)b=b(); return (a instanceof Element?a.Length:a)<(b instanceof Element?b.Length:b); }\n static gt(a,b) { while(a.call)a=a(); while(b.call)b=b(); return (a instanceof Element?a.Length:a)>(b instanceof Element?b.Length:b); }\n static lte(a,b) { while(a.call)a=a(); while(b.call)b=b(); return (a instanceof Element?a.Length:a)<=(b instanceof Element?b.Length:b); }\n static gte(a,b) { while(a.call)a=a(); while(b.call)b=b(); return (a instanceof Element?a.Length:a)>=(b instanceof Element?b.Length:b); }\n \n // Debug output and printing multivectors. \n static describe(x) { if (x===true) console.log(`Basis\\n${basis}\\nMetric\\n${metric.slice(1,1+tot)}\\nCayley\\n${mulTable.map(x=>(x.map(x=>(' '+x).slice(-2-tot)))).join('\\n')}\\nMatrix Form:\\n`+gp.map(x=>x.map(x=>x.match(/(-*b\\[\\d+\\])/)).map(x=>x&&((x[1].match(/-/)||' ')+String.fromCharCode(65+1*x[1].match(/\\d+/)))||' 0')).join('\\n')); return {basis:basisg||basis,metric,mulTable} } \n \n // Direct sum of algebras - experimental\n static sum(B){\n var A = Element;\n // Get the multiplication tabe and basis.\n var T1 = A.describe().mulTable, T2 = B.describe().mulTable;\n var B1 = A.describe().basis, B2 = B.describe().basis;\n // Get the maximum index of T1, minimum of T2 and rename T2 if needed.\n var max_T1 = B1.filter(x=>x.match(/e/)).map(x=>x.match(/\\d/g)).flat().map(x=>x|0).sort((a,b)=>b-a)[0];\n var max_T2 = B2.filter(x=>x.match(/e/)).map(x=>x.match(/\\d/g)).flat().map(x=>x|0).sort((a,b)=>b-a)[0];\n var min_T2 = B2.filter(x=>x.match(/e/)).map(x=>x.match(/\\d/g)).flat().map(x=>x|0).sort((a,b)=>a-b)[0];\n // remapping ..\n T2 = T2.map(x=>x.map(y=>y.match(/e/)?y.replace(/(\\d)/g,(x)=>(x|0)+max_T1):y.replace(\"1\",\"e\"+(1+max_T2+max_T1))));\n B2 = B2.map((y,i)=>i==0?y.replace(\"1\",\"e\"+(1+max_T2+max_T1)):y.replace(/(\\d)/g,(x)=>(x|0)+max_T1));\n // Build the new basis and multable..\n var basis = [...B1,...B2];\n var Cayley = T1.map((x,i)=>[...x,...T2[0].map(x=>\"0\")]).concat(T2.map((x,i)=>[...T1[0].map(x=>\"0\"),...x]))\n // Build the new algebra.\n var grades = [...B1.map(x=>x==\"1\"?0:x.length-1),...B2.map((x,i)=>i?x.length-1:0)];\n var a = Algebra({basis,Cayley,grades,tot:Math.log2(B1.length)+Math.log2(B2.length)})\n // And patch up ..\n a.Scalar = function(x) {\n var res = new a();\n for (var i=0; i<res.length; i++) res[i] = basis[i] == Cayley[i][i] ? x:0;\n return res;\n }\n return a;\n } \n \n // The graphing function supports several modes. It can render 1D functions and 2D functions on canvas, and PGA2D, PGA3D and CGA2D functions using SVG.\n // It handles animation and interactivity.\n // graph(function(x)) => function of 1 parameter will be called with that parameter from -1 to 1 and graphed on a canvas. Returned values should also be in the [-1 1] range\n // graph(function(x,y)) => functions of 2 parameters will be called from -1 to 1 on both arguments. Returned values can be 0-1 for greyscale or an array of three RGB values.\n // graph(array) => array of algebraic elements (points, lines, circles, segments, texts, colors, ..) is graphed.\n // graph(function=>array) => same as above, for animation scenario's this function is called each frame.\n // An optional second parameter is an options object { width, height, animate, camera, scale, grid, canvas } \n static graph(f,options) { \n // Store the original input\n if (!f) return; var origf=f; \n // generate default options. \n options=options||{}; options.scale=options.scale||1; options.camera=options.camera||(tot<4?Element.Scalar(1):new Element([0.7071067690849304, 0, 0, 0, 0, 0, 0, 0, 0, 0.7071067690849304, 0, 0, 0, 0, 0, 0])); \n var ww=options.width, hh=options.height, cvs=options.canvas, tpcam=new Element([0,0,0,0,0,0,0,0,0,0,0,-5,0,0,1,0]),tpy=this.Coeff(4,1),tp=new Element(), \n // project 3D to 2D. This allows to render 3D and 2D PGA with the same code. \n project=(o)=>{ if (!o) return o; while (o.call) o=o(); return (tot==4 && (o.length==16))?(tpcam).Vee(options.camera.Mul(o).Mul(options.camera.Conjugate)).Wedge(tpy):(o.length==2**tot)?Element.sw(options.camera,o):o;};\n // gl escape.\n if (options.gl) return Element.graphGL(f,options); if (options.up) return Element.graphGL2(f,options);\n // if we get an array or function without parameters, we render c2d or p2d SVG points/lines/circles/etc\n if (!(f instanceof Function) || f.length===0) { \n // Our current cursor, color, animation state and 2D mapping.\n var lx,ly,lr,color,res,anim=false,to2d=(tot==3)?[0,1,2,3,4,5,6,7]:[0,7,9,10,13,12,14,15];\n // Make sure we have an array of elements. (if its an object, convert to array with elements and names.) \n if (f instanceof Function) f=f(); if (!(f instanceof Array)) f=[].concat.apply([],Object.keys(f).map((k)=>typeof f[k]=='number'?[f[k]]:[f[k],k])); \n // The build function generates the actual SVG. It will be called everytime the user interacts or the anim flag is set. \n function build(f,or) {\n // Make sure we have an aray. \n if (or && f && f instanceof Function) f=f(); \n // Reset position and color for cursor. \n lx=-2;ly=-1.85;lr=0;color='#444'; \n // Create the svg element. (master template string till end of function) \n var svg=new DOMParser().parseFromString(`<SVG onmousedown=\"if(evt.target==this)this.sel=undefined\" viewBox=\"-2 -${2*(hh/ww||1)} 4 ${4*(hh/ww||1)}\" style=\"width:${ww||512}px; height:${hh||512}px; background-color:#eee; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; user-select:none\">\n // Add a grid (option)\n ${options.grid?[...Array(21)].map((x,xi)=>`<line x1=\"-10\" y1=\"${((xi-10)/2-(tot<4?2*options.camera.e02:0))*options.scale}\" x2=\"10\" y2=\"${((xi-10)/2-(tot<4?2*options.camera.e02:0))*options.scale}\" stroke-width=\"0.005\" stroke=\"#CCC\"/><line y1=\"-10\" x1=\"${((xi-10)/2-(tot<4?2*options.camera.e01:0))*options.scale}\" y2=\"10\" x2=\"${((xi-10)/2-(tot<4?2*options.camera.e01:0))*options.scale}\" stroke-width=\"0.005\" stroke=\"#CCC\"/>`):''}\n // Handle conformal 2D elements. \n ${options.conformal?f.map&&f.map((o,oidx)=>{ \n // Optional animation handling.\n if((o==Element.graph && or!==false)||(oidx==0&&options.animate&&or!==false)) { anim=true; requestAnimationFrame(()=>{var r=build(origf,(!res)||(document.body.contains(res))).innerHTML; if (res) res.innerHTML=r; }); if (!options.animate) return; }\n // Resolve expressions passed in. \n while (o.call) o=o();\n // Arrays are rendered as segments or polygons. (2 or more elements) \n if (o instanceof Array) { lx=ly=lr=0; o=o.map(o=>{ while(o.call)o=o(); return o; }); o.forEach((o)=>{lx+=o.e1;ly+=-o.e2});lx/=o.length;ly/=o.length; return o.length>2?`<POLYGON STYLE=\"pointer-events:none; fill:${color};opacity:0.7\" points=\"${o.map(o=>(o.e1+','+(-o.e2)+' '))}\"/>`:`<LINE style=\"pointer-events:none\" x1=${o[0].e1} y1=${-o[0].e2} x2=${o[1].e1} y2=${-o[1].e2} stroke-width=\"${options.lineWidth*0.005||0.005}\" stroke=\"${color||'#888'}\"/>`; }\n // Strings are rendered at the current cursor position. \n if (typeof o =='string') { var res2=(o[0]=='_')?'':`<text x=\"${lx}\" y=\"${ly}\" font-family=\"Verdana\" font-size=\"${options.fontSize*0.1||0.1}\" style=\"pointer-events:none\" fill=\"${color||'#333'}\" transform=\"rotate(${lr},${lx},${ly})\">&nbsp;${o}&nbsp;</text>`; ly+=0.14; return res2; }\n // Numbers change the current color. \n if (typeof o =='number') { color='#'+(o+(1<<25)).toString(16).slice(-6); return ''; };\n // All other elements are rendered .. \n var b1=o.Grade(1).VLength>0.001,b2=o.Grade(2).VLength>0.001,b3=o.Grade(3).VLength>0.001; \n // Points \n if (b1 && !b2 && !b3) { lx=o.e1; ly=-o.e2; lr=0; return res2=`<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${options.pointRadius*0.03||0.03}\" fill=\"${color||'green'}\"/>`; }\n else if (!b1 && !b2 && b3) { var isLine=Element.Coeff(4,1,3,-1).LDot(o).Length==0; \n // Lines.\n if (isLine) { var loc=((Element.Coeff(4,-.5).Add(Element.Coeff(3,-.5))).LDot(o)).Div(o), att=(Element.Coeff(4,1,3,-1)).LDot(o); lx=-loc.e1; ly=loc.e2; lr=Math.atan2(att[8],att[7])/Math.PI*180; return `<LINE style=\"pointer-events:none\" x1=${lx-10} y1=${ly} x2=${lx+10} y2=${ly} stroke-width=\"${options.lineWidth*0.005||0.005}\" stroke=\"${color||'#888'}\" transform=\"rotate(${lr},${lx},${ly})\"/>`;};\n // Circles. \n var loc=o.Div((Element.Coeff(4,1,3,-1)).LDot(o)); lx=-loc.e1; ly=loc.e2; var r=-o.Mul(o.Conjugate).s/(Element.Pow((Element.Coeff(4,1,3,-1)).LDot(o),2).s); r=r**0.5; return `<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${r}\" stroke-width=\"${options.lineWidth*0.005||0.005}\" fill=\"none\" stroke=\"${color||'green'}\"/>`; \n } else if (!b1 && b2 &&!b3) { \n // Point Pairs.\n lr=0; var ei=Element.Coeff(4,1,3,-1),eo=Element.Coeff(4,.5,3,.5), nix=o.Wedge(ei), sqr=o.LDot(o).s/nix.LDot(nix).s, r=Math.sqrt(Math.abs(sqr)), attitude=((ei.Wedge(eo)).LDot(nix)).Normalized.Mul(Element.Scalar(r)), pos=o.Div(nix); pos=pos.Div( pos.LDot(Element.Sub(ei))); \n lx=pos.e1; ly=-pos.e2; if (sqr<0) return `<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${options.pointRadius*0.03||0.03}\" stroke-width=\"0.005\" fill=\"none\" stroke=\"${color||'green'}\"/>`;\n lx=pos.e1+attitude.e1; ly=-pos.e2-attitude.e2; var res2=`<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${options.pointRadius*0.03||0.03}\" fill=\"${color||'green'}\"/>`;\n lx=pos.e1-attitude.e1; ly=-pos.e2+attitude.e2; return res2+`<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${options.pointRadius*0.03||0.03}\" fill=\"${color||'green'}\"/>`;\n }\n // Handle projective 2D and 3D elements. \n }):f.map&&f.map((o,oidx)=>{ if((o==Element.graph && or!==false)||(oidx==0&&options.animate&&or!==false)) { anim=true; requestAnimationFrame(()=>{var r=build(origf,(!res)||(document.body.contains(res))).innerHTML; if (res) res.innerHTML=r; }); if (!options.animate) return; } while (o instanceof Function) o=o(); o=(o instanceof Array)?o.map(project):project(o); if (o===undefined) return; \n // line segments and polygons\n if (o instanceof Array && o.length) { lx=ly=lr=0; o.forEach((o)=>{while (o.call) o=o(); lx+=options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[drm[2]]/o[drm[1]];ly+=options.scale*o[drm[3]]/o[drm[1]]});lx/=o.length;ly/=o.length; return o.length>2?`<POLYGON STYLE=\"pointer-events:none; fill:${color};opacity:0.7\" points=\"${o.map(o=>((drm[1]==6||drm[1]==14)?-1:1)*options.scale*o[drm[2]]/o[drm[1]]+','+options.scale*o[drm[3]]/o[drm[1]]+' ')}\"/>`:`<LINE style=\"pointer-events:none\" x1=${options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[0][drm[2]]/o[0][drm[1]]} y1=${options.scale*o[0][drm[3]]/o[0][drm[1]]} x2=${options.scale*((drm[1]==6||drm[1]==14)?-1:1)*o[1][drm[2]]/o[1][drm[1]]} y2=${options.scale*o[1][drm[3]]/o[1][drm[1]]} stroke-width=\"${options.lineWidth*0.005||0.005}\" stroke=\"${color||'#888'}\"/>`; }\n // svg\n if (typeof o =='string' && o[0]=='<') { return o; } \n // Labels \n if (typeof o =='string') { var res2=(o[0]=='_')?'':`<text x=\"${lx}\" y=\"${ly}\" font-family=\"Verdana\" font-size=\"${options.fontSize*0.1||0.1}\" style=\"pointer-events:none\" fill=\"${color||'#333'}\" transform=\"rotate(${lr},0,0)\">&nbsp;${o}&nbsp;</text>`; ly+=0.14; return res2; }\n // Colors \n if (typeof o =='number') { color='#'+(o+(1<<25)).toString(16).slice(-6); return ''; };\n // Points \n if (o[to2d[6]]**2 >0.0001) { lx=options.scale*o[drm[2]]/o[drm[1]]; if (drm[1]==6||drm[1]==14) lx*=-1; ly=options.scale*o[drm[3]]/o[drm[1]]; lr=0; var res2=`<CIRCLE onmousedown=\"this.parentElement.sel=${oidx}\" cx=\"${lx}\" cy=\"${ly}\" r=\"${options.pointRadius*0.03||0.03}\" fill=\"${color||'green'}\"/>`; ly-=0.05; lx-=0.1; return res2; }\n // Lines \n if (o[to2d[2]]**2+o[to2d[3]]**2>0.0001) { var l=Math.sqrt(o[to2d[2]]**2+o[to2d[3]]**2); o[to2d[2]]/=l; o[to2d[3]]/=l; o[to2d[1]]/=l; lx=0.5; ly=options.scale*((drm[1]==6)?-1:-1)*o[to2d[1]]; lr=-Math.atan2(o[to2d[2]],o[to2d[3]])/Math.PI*180; var res2=`<LINE style=\"pointer-events:none\" x1=-10 y1=${ly} x2=10 y2=${ly} stroke-width=\"${options.lineWidth*0.005||0.005}\" stroke=\"${color||'#888'}\" transform=\"rotate(${lr},0,0)\"/>`; ly-=0.05; return res2; }\n // Vectors \n if (o[to2d[4]]**2+o[to2d[5]]**2>0.0001) { lr=0; ly+=0.05; lx+=0.1; var res2=`<LINE style=\"pointer-events:none\" x1=${lx} y1=${ly} x2=${lx-o.e02} y2=${ly+o.e01} stroke-width=\"0.005\" stroke=\"${color||'#888'}\"/>`; ly=ly+o.e01/4*3-0.05; lx=lx-o.e02/4*3; return res2; }\n }).join()}`,'text/html').body; \n // return the inside of the created svg element. \n return svg.removeChild(svg.firstChild); \n };\n // Create the initial svg and install the mousehandlers. \n res=build(f); res.value=f; res.options=options;\n res.onmousemove=(e)=>{ if (res.sel===undefined || !e.buttons) return;var resx=res.getBoundingClientRect().width,resy=res.getBoundingClientRect().height,x=((e.clientX-res.getBoundingClientRect().left)/(resx/4||128)-2)*(resx>resy?resx/resy:1),y=((e.clientY-res.getBoundingClientRect().top)/(resy/4||128)-2)*(resy>resx?resy/resx:1);x/=options.scale;y/=options.scale; if (options.conformal) {f[res.sel][1]=x; f[res.sel][2]=-y; var l=x*x+y*y; f[res.sel][3]=0.5-l*0.5; f[res.sel][4]=0.5+l*0.5; } else {f[res.sel][drm[2]]=((drm[1]==6)?-x:x)-((tot<4)?2*options.camera.e01:0); f[res.sel][drm[3]]=y+((tot<4)?2*options.camera.e02:0); f[res.sel][drm[1]]=1;} if (!anim) res.innerHTML=build(f).innerHTML; res.dispatchEvent(new CustomEvent('input')) }; \n return res;\n } \n // 1d and 2d functions are rendered on a canvas. \n cvs=cvs||document.createElement('canvas'); if(ww)cvs.width=ww; if(hh)cvs.height=hh; var w=cvs.width,h=cvs.height,context=cvs.getContext('2d'), data=context.getImageData(0,0,w,h);\n // two parameter functions .. evaluate for both and set resulting color. \n if (f.length==2) for (var px=0; px<w; px++) for (var py=0; py<h; py++) { var res=f(px/w*2-1, py/h*2-1); res=res.buffer?[].slice.call(res):res.slice?res:[res,res,res]; data.data.set(res.map(x=>x*255).concat([255]),py*w*4+px*4); }\n // one parameter function.. go over x range, use result as y. \n else if (f.length==1) for (var px=0; px<w; px++) { var res=f(px/w*2-1); res=Math.round((res/2+0.5)*h); if (res > 0 && res < h-1) data.data.set([0,0,0,255],res*w*4+px*4); }\n return context.putImageData(data,0,0),cvs; \n }\n \n // webGL2 Graphing function. (for OPNS/IPNS implicit 2D and 1D surfaces in 3D space).\n static graphGL2(f,options) {\n // Create canvas, get webGL2 context.\n var canvas=document.createElement('canvas'); canvas.style.width=options.width||''; canvas.style.height=options.height||''; canvas.style.backgroundColor='#EEE';\n if (options.width && options.width.match && options.width.match(/px/i)) canvas.width = parseFloat(options.width); if (options.height && options.height.match && options.height.match(/px/i)) canvas.height = parseFloat(options.height);\n var gl=canvas.getContext('webgl2',{alpha:options.alpha||false,preserveDrawingBuffer:true,antialias:true,powerPreference:'high-performance'});\n var gl2=!!gl; if (!gl) gl=canvas.getContext('webgl',{alpha:options.alpha||false,preserveDrawingBuffer:true,antialias:true,powerPreference:'high-performance'});\n gl.clearColor(240/255,240/255,240/255,1.0); gl.enable(gl.DEPTH_TEST); if (!gl2) { gl.getExtension(\"EXT_frag_depth\"); gl.va = gl.getExtension('OES_vertex_array_object'); }\n else gl.va = { createVertexArrayOES : gl.createVertexArray.bind(gl), bindVertexArrayOES : gl.bindVertexArray.bind(gl), deleteVertexArrayOES : gl.deleteVertexArray.bind(gl) }\n // Compile vertex and fragment shader, return program.\n var compile=(vs,fs)=>{\n var s=[gl.VERTEX_SHADER,gl.FRAGMENT_SHADER].map((t,i)=>{\n var r=gl.createShader(t); gl.shaderSource(r,[vs,fs][i]); gl.compileShader(r);\n return gl.getShaderParameter(r, gl.COMPILE_STATUS)&&r||console.error(gl.getShaderInfoLog(r));\n });\n var p = gl.createProgram(); gl.attachShader(p, s[0]); gl.attachShader(p, s[1]); gl.linkProgram(p);\n gl.getProgramParameter(p, gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(p));\n return p;\n };\n // Create vertex array and buffers, upload vertices and optionally texture coordinates.\n var createVA=function(vtx) {\n var r = gl.va.createVertexArrayOES(); gl.va.bindVertexArrayOES(r);\n var b = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, b);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vtx), gl.STATIC_DRAW);\n gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(0);\n return {r,b}\n },\n // Destroy Vertex array and delete buffers.\n destroyVA=function(va) {\n if (va.b) gl.deleteBuffer(va.b); if (va.r) gl.va.deleteVertexArrayOES(va.r);\n }\n // Drawing function \n var M=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,5,1];\n var draw=function(p, tp, vtx, color, color2, ratio, texc, va, b,color3,r,g){\n gl.useProgram(p); gl.uniformMatrix4fv(gl.getUniformLocation(p, \"mv\"),false,M);\n gl.uniformMatrix4fv(gl.getUniformLocation(p, \"p\"),false, [5,0,0,0,0,5*(ratio||1),0,0,0,0,1,2,0,0,-1,0])\n gl.uniform3fv(gl.getUniformLocation(p, \"color\"),new Float32Array(color));\n gl.uniform3fv(gl.getUniformLocation(p, \"color2\"),new Float32Array(color2));\n if (color3) gl.uniform3fv(gl.getUniformLocation(p, \"color3\"),new Float32Array(color3));\n if (b) gl.uniform1fv(gl.getUniformLocation(p, \"b\"),(new Float32Array(counts[g])).map((x,i)=>b[g][i]||0));\n if (texc) gl.uniform1i(gl.getUniformLocation(p, \"texc\"),0);\n if (r) gl.uniform1f(gl.getUniformLocation(p,\"ratio\"),r);\n var v; if (!va) v = createVA(vtx); else gl.va.bindVertexArrayOESOES(va.r);\n gl.drawArrays(tp, 0, (va&&va.tcount)||vtx.length/3);\n if (v) destroyVA(v);\n }\n // Compile the OPNS renderer. (sphere tracing)\n var programs = [], genprog = grade=>compile(`${gl2?\"#version 300 es\":\"\"}\n ${gl2?\"in\":\"attribute\"} vec4 position; ${gl2?\"out\":\"varying\"} vec4 Pos; uniform mat4 mv; uniform mat4 p;\n void main() { Pos=mv*position; gl_Position = p*Pos; }`,\n `${!gl2?\"#extension GL_EXT_frag_depth : enable\":\"#version 300 es\"}\n precision highp float; \n uniform vec3 color; uniform vec3 color2; \n uniform vec3 color3; uniform float b[${counts[grade]}];\n uniform float ratio; ${gl2?\"out vec4 col;\":\"\"}\n ${gl2?\"in\":\"varying\"} vec4 Pos; \n float dist (in float z, in float y, in float x, in float[${counts[grade]}] b) {\n ${this.nVector(1,[]).OPNS_GLSL(this.nVector(grade,[]), options.up)}\n return ${grade!=tot-1?\"sign(sum)*sqrt(abs(sum))\":\"res\"};\n }\n vec3 trace_depth (in vec3 start, vec3 dir, in float thresh) {\n vec3 orig=start; float lastd = 1000.0; const int count=${(options.maxSteps||64)};\n float s = sign(dist(start[0],start[1],start[2],b));\n for (int i=0; i<count; i++) {\n float d = s*dist(start[0],start[1],start[2],b);\n if (d < thresh) return start - lastd*${(options.stepSize||0.25)}*dir*(thresh-d)/(lastd-d);\n lastd = d; start += dir*${(options.stepSize||0.25)}*d;\n }\n return orig;\n }\n void main() { \n vec3 p = -5.0*normalize(color2); \n vec3 dir = normalize((-Pos[0]/5.0)*color + color2 + vec3(0.0,Pos[1]/5.0*ratio,0.0)); p += 1.0*dir;\n vec3 L = 5.0*normalize( -0.5*color + 0.85*color2 + vec3(0.0,-0.5,0.0) );\n vec3 d2 = trace_depth( p , dir, ${grade!=tot-1?(options.thresh||0.2):\"0.0075\"} );\n float dl2 = dot(d2-p,d2-p); const float h=0.1; \n if (dl2>0.0) {\n vec3 n = normalize(vec3(\n dist(d2[0]+h,d2[1],d2[2],b)-dist(d2[0]-h,d2[1],d2[2],b),\n dist(d2[0],d2[1]+h,d2[2],b)-dist(d2[0],d2[1]-h,d2[2],b),\n dist(d2[0],d2[1],d2[2]+h,b)-dist(d2[0],d2[1],d2[2]-h,b)\n ));\n ${gl2?\"gl_FragDepth\":\"gl_FragDepthEXT\"} = dl2/50.0;\n ${gl2?\"col\":\"gl_FragColor\"} = vec4(max(0.2,abs(dot(n,normalize(L-d2))))*color3 + pow(abs(dot(n,normalize(normalize(L-d2)+dir))),100.0),1.0);\n } else discard; \n }`),genprog2D = grade=>compile(`${gl2?\"#version 300 es\":\"\"}\n ${gl2?\"in\":\"attribute\"} vec4 position; ${gl2?\"out\":\"varying\"} vec4 Pos; uniform mat4 mv; uniform mat4 p;\n void main() { Pos=mv*position; gl_Position = p*Pos; }`,\n `${!gl2?\"#extension GL_EXT_frag_depth : enable\":\"#version 300 es\"}\n precision highp float; \n uniform vec3 color; uniform vec3 color2; \n uniform vec3 color3; uniform float b[${counts[grade]}];\n uniform float ratio; ${gl2?\"out vec4 col;\":\"\"}\n ${gl2?\"in\":\"varying\"} vec4 Pos; \n float dist (in float z, in float y, in float x, in float[${counts[grade]}] b) {\n ${this.nVector(1,[]).OPNS_GLSL(this.nVector(grade,[]), options.up)}\n return ${grade!=tot-1?\"sqrt(abs(sum))\":\"res\"};\n }\n float trace_depth (in vec3 start, vec3 dir, in float thresh) {\n vec3 orig=start; float lastd = 1000.0; const int count=${(options.maxSteps||64)};\n float s = dist(start[0]*5.0,start[1]*5.0,start[2]*5.0,b);\n s=s*s;\n return 1.0-s*150.0;\n }\n void main() { \n vec3 p = -5.0*normalize(color2); \n vec3 dir = normalize((-Pos[0]/5.0)*color + color2 + vec3(0.0,Pos[1]/5.0*ratio,0.0)); p += 1.0*dir;\n vec3 L = 5.0*normalize( -0.5*color + 0.85*color2 + vec3(0.0,-0.5,0.0) );\n float d2 = trace_depth( p , dir, ${grade!=tot-1?(options.thresh||0.2):\"0.0075\"} );\n if (d2>0.0) {\n ${gl2?\"gl_FragDepth\":\"gl_FragDepthEXT\"} = d2/50.0;\n ${gl2?\"col\":\"gl_FragColor\"} = vec4(d2*color3,d2);\n } else discard; \n }`)\n // canvas update will (re)render the content. \n var armed=0;\n canvas.update = (x)=>{\n // Start by updating canvas size if needed and viewport.\n var s = getComputedStyle(canvas); if (s.width) { canvas.width = parseFloat(s.width); canvas.height = parseFloat(s.height); }\n gl.viewport(0,0, canvas.width|0,canvas.height|0); var r=canvas.width/canvas.height;\n // Defaults, resolve function input \n var a,p=[],l=[],t=[],c=[.5,.5,.5],alpha=0,lastpos=[-2,2,0.2]; gl.clear(gl.COLOR_BUFFER_BIT+gl.DEPTH_BUFFER_BIT); while (x.call) x=x();\n // Loop over all items to render. \n for (var i=0,ll=x.length;i<ll;i++) { \n var e=x[i]; while (e&&e.call) e=e(); if (e==undefined) continue;\n if (typeof e == \"number\") { alpha=((e>>>24)&0xff)/255; c[0]=((e>>>16)&0xff)/255; c[1]=((e>>>8)&0xff)/255; c[2]=(e&0xff)/255; }\n if (e instanceof Element){\n var tt = options.spin?-performance.now()*options.spin/1000:-options.h||0; tt+=Math.PI/2; var r = canvas.height/canvas.width;\n var g=tot-1; while(!e[g]&&g>1) g--;\n if (!programs[tot-1-g]) programs[tot-1-g] = (options.up.find(x=>x.match&&x.match(\"z\")))?genprog(g):genprog2D(g);\n gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);\n draw(programs[tot-1-g],gl.TRIANGLES,[-2,-2,0,-2,2,0,2,-2,0,-2,2,0,2,-2,0,2,2,0],[Math.cos(tt),0,-Math.sin(tt)],[Math.sin(tt),0,Math.cos(tt)],undefined,undefined,undefined,e,c,r,g);\n gl.disable(gl.BLEND);\n }\n }\n // if we're no longer in the page .. stop doing the work.\n armed++; if (document.body.contains(canvas)) armed=0; if (armed==2) return;\n canvas.value=x; if (options&&!options.animate) canvas.dispatchEvent(new CustomEvent('input'));\n if (options&&options.animate) { requestAnimationFrame(canvas.update.bind(canvas,f,options)); }\n if (options&&options.still) { canvas.value=x; canvas.dispatchEvent(new CustomEvent('input')); canvas.im.width=canvas.width; canvas.im.height=canvas.height; canvas.im.src = canvas.toDataURL(); }\n }\n // Basic mouse interactivity. needs more love.\n var sel=-1; canvas.oncontextmenu = canvas.onmousedown = (e)=>{ e.preventDefault(); e.stopPropagation(); sel=-2;\n var rc = canvas.getBoundingClientRect(), mx=(e.x-rc.left)/(rc.right-rc.left)*2-1, my=((e.y-rc.top)/(rc.bottom-rc.top)*-4+2)*canvas.height/canvas.width;\n canvas.onwheel=e=>{e.preventDefault(); e.stopPropagation(); options.z = (options.z||5)+e.deltaY/100; if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options));}\n canvas.onmouseup=e=>sel=-1; canvas.onmouseleave=e=>sel=-1;\n canvas.onmousemove=(e)=>{ \n var rc = canvas.getBoundingClientRect(); \n var mx =(e.movementX)/(rc.right-rc.left)*2, my=((e.movementY)/(rc.bottom-rc.top)*-2)*canvas.height/canvas.width;\n if (sel==-2) { options.h = (options.h||0)+mx; if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options)); return; }; if (sel < 0) return;\n }\n }\n canvas.value = f.call?f():f; canvas.options = options;\n if (options&&options.still) {\n var i=new Image(); canvas.im = i; return requestAnimationFrame(canvas.update.bind(canvas,f,options)),i;\n } else return requestAnimationFrame(canvas.update.bind(canvas,f,options)),canvas;\n \n }\n \n \n // webGL Graphing function. (for parametric defined objects)\n static graphGL(f,options) {\n // Create a canvas, webgl2 context and set some default GL options.\n var canvas=document.createElement('canvas'); canvas.style.width=options.width||''; canvas.style.height=options.height||''; canvas.style.backgroundColor='#EEE';\n if (options.width && options.width.match && options.width.match(/px/i)) canvas.width = parseFloat(options.width); if (options.height && options.height.match && options.height.match(/px/i)) canvas.height = parseFloat(options.height);\n var gl=canvas.getContext('webgl',{alpha:options.alpha||false,antialias:true,preserveDrawingBuffer:options.still||true,powerPreference:'high-performance'}); \n gl.enable(gl.DEPTH_TEST); gl.depthFunc(gl.LEQUAL); if (!options.alpha) gl.clearColor(240/255,240/255,240/255,1.0); gl.getExtension(\"OES_standard_derivatives\"); gl.va=gl.getExtension(\"OES_vertex_array_object\");\n // Compile vertex and fragment shader, return program. \n var compile=(vs,fs)=>{ \n var s=[gl.VERTEX_SHADER,gl.FRAGMENT_SHADER].map((t,i)=>{\n var r=gl.createShader(t); gl.shaderSource(r,[vs,fs][i]); gl.compileShader(r);\n return gl.getShaderParameter(r, gl.COMPILE_STATUS)&&r||console.error(gl.getShaderInfoLog(r));\n });\n var p = gl.createProgram(); gl.attachShader(p, s[0]); gl.attachShader(p, s[1]); gl.linkProgram(p);\n gl.getProgramParameter(p, gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(p));\n return p;\n };\n // Create vertex array and buffers, upload vertices and optionally texture coordinates. \n var createVA=function(vtx, texc, idx, clr) {\n var r = gl.va.createVertexArrayOES(); gl.va.bindVertexArrayOES(r);\n var b = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, b); \n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vtx), gl.STATIC_DRAW);\n gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(0);\n if (texc){\n var b2=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, b2);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texc), gl.STATIC_DRAW);\n gl.vertexAttribPointer(1, 2, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(1);\n }\n if (clr){\n var b3=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, b3);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(clr), gl.STATIC_DRAW);\n gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0); gl.enableVertexAttribArray(1);\n }\n if (idx) {\n var b4=gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, b4);\n gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(idx), gl.STATIC_DRAW);\n }\n return {r,b,b2,b4,b3}\n },\n // Destroy Vertex array and delete buffers.\n destroyVA=function(va) {\n [va.b,va.b2,va.b4,va.b3].forEach(x=>{if(x) gl.deleteBuffer(x)}); if (va.r) gl.va.deleteVertexArrayOES(va.r);\n }\n // Default modelview matrix, convert camera to matrix (biquaternion->matrix) \n var M=[1,0,0,0,0,1,0,0,0,0,1,0,0,0,5,1], mtx = x=>{ var t=options.spin?performance.now()*options.spin/1000:options.h||0, t2=options.p||0;\n var ct = Math.cos(t), st= Math.sin(t), ct2 = Math.cos(t2), st2 = Math.sin(t2), xx=options.posx||0, y=options.posy||0, z=options.posz||0, zoom=options.z||5;\n if (tot==5) return [ct,st*-st2,st*ct2,0,0,ct2,st2,0,-st,ct*-st2,ct*ct2,0,xx*ct+z*-st,y*ct2+(xx*st+z*ct)*-st2,y*st2+xx*st+z*ct*ct2+zoom,1];\n x=x.Normalized; var y=x.Mul(x.Dual),X=-x.e23,Y=-x.e13,Z=x.e12,W=x.s,m=Array(16);\n var xx = X*X, xy = X*Y, xz = X*Z, xw = X*W, yy = Y*Y, yz = Y*Z, yw = Y*W, zz = Z*Z, zw = Z*W;\n return [ 1-2*(yy+zz), 2*(xy+zw), 2*(xz-yw), 0, 2*(xy-zw), 1-2*(xx+zz), 2*(yz+xw), 0, 2*(xz+yw), 2*(yz-xw), 1-2*(xx+yy), 0, -2*y.e23, -2*y.e13, 2*y.e12+5, 1];\n }\n // Render the given vertices. (autocreates/destroys vertex array if not supplied). \n var draw=function(p, tp, vtx, color, color2, ratio, texc, va){\n gl.useProgram(p); gl.uniformMatrix4fv(gl.getUniformLocation(p, \"mv\"),false,M); \n gl.uniformMatrix4fv(gl.getUniformLocation(p, \"p\"),false, [5,0,0,0,0,5*(ratio||2),0,0,0,0,1,2,0,0,-1,0])\n gl.uniform3fv(gl.getUniformLocation(p, \"color\"),new Float32Array(color));\n gl.uniform3fv(gl.getUniformLocation(p, \"color2\"),new Float32Array(color2));\n if (texc) gl.uniform1i(gl.getUniformLocation(p, \"texc\"),0);\n var v; if (!va) v = createVA(vtx, texc); else gl.va.bindVertexArrayOES(va.r);\n if (va && va.b4) {\n gl.drawElements(tp, va.tcount, gl.UNSIGNED_SHORT, 0);\n } else {\n gl.drawArrays(tp, 0, (va&&va.tcount)||vtx.length/3);\n } \n if (v) destroyVA(v);\n }\n // Program for the geometry. Derivative based normals. Basic lambert shading. \n var program = compile(`attribute vec4 position; varying vec4 Pos; uniform mat4 mv; uniform mat4 p; \n void main() { gl_PointSize=6.0; Pos=mv*position; gl_Position = p*Pos; }`,\n `#extension GL_OES_standard_derivatives : enable\n precision highp float; uniform vec3 color; uniform vec3 color2; varying vec4 Pos; \n void main() { vec3 ldir = normalize(Pos.xyz - vec3(1.0,1.0,2.0));\n vec3 normal = normalize(cross(dFdx(Pos.xyz), dFdy(Pos.xyz))); float l=dot(normal,ldir);\n vec3 E = normalize(-Pos.xyz); vec3 R = normalize(reflect(ldir,normal)); \n gl_FragColor = vec4(max(0.0,l)*color+vec3(0.5*pow(max(dot(R,E),0.0),20.0))+color2, 1.0); }`);\n var programcol = compile(`attribute vec4 position; attribute vec3 col; varying vec3 Col; varying vec4 Pos; uniform mat4 mv; uniform mat4 p; \n void main() { gl_PointSize=6.0; Pos=mv*position; gl_Position = p*Pos; Col=col; }`,\n `#extension GL_OES_standard_derivatives : enable\n precision highp float; uniform vec3 color; uniform vec3 color2; varying vec4 Pos; varying vec3 Col; \n void main() { vec3 ldir = normalize(Pos.xyz - vec3(1.0,1.0,2.0));\n vec3 normal = normalize(cross(dFdx(Pos.xyz), dFdy(Pos.xyz))); float l=dot(normal,ldir);\n vec3 E = normalize(-Pos.xyz); vec3 R = normalize(reflect(ldir,normal)); \n gl_FragColor = vec4(max(0.3,l)*Col+vec3(0.5*pow(max(dot(R,E),0.0),20.0))+color2, 1.0); }`);\n // Create a font texture, lucida console or otherwise monospaced.\n var fw=22, font = Object.assign(document.createElement('canvas'),{width:94*fw,height:32}), \n ctx = Object.assign(font.getContext('2d'),{font:'bold 32px lucida console, monospace'}),\n ftx = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, ftx);\n for (var i=33; i<127; i++) ctx.fillText(String.fromCharCode(i),(i-33)*fw,26);\n // 2.0 gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,94*fw,32,0,gl.RGBA,gl.UNSIGNED_BYTE,font);\n gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, font);\n\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);\n gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); \n // Font rendering program. Renders billboarded fonts, transforms offset passed as color2.\n var program2 = compile(`attribute vec4 position; attribute vec2 texc; varying vec2 tex; varying vec4 Pos; uniform mat4 mv; uniform mat4 p; uniform vec3 color2; \n void main() { tex=texc; gl_PointSize=6.0; vec4 o=mv*vec4(color2,0.0); Pos=(-1.0/(o.z-mv[3][2]))*position+vec4(mv[3][0],mv[3][1],mv[3][2],0.0)+o; gl_Position = p*Pos; }`,\n `precision highp float; uniform vec3 color; varying vec4 Pos; varying vec2 tex; \n uniform sampler2D texm; void main() { vec4 c = texture2D(texm,tex); if (c.a<0.01) discard; gl_FragColor = vec4(color,c.a);}`);\n // Conformal space needs a bit extra magic to extract euclidean parametric representations.\n if (tot==5 && options.conformal) var ninf = Element.Coeff(4,1).Add(Element.Coeff(5,1)), no = Element.Coeff(4,0.5).Sub(Element.Coeff(5,0.5));\n var interprete = (x)=>{\n if (!(x instanceof Element)) return { tp:0 };\n // tp = { 0:unknown 1:point 2:line, 3:plane, 4:circle, 5:sphere\n var X2 = (x.Mul(x)).s, tp=0, weight2, opnix = ninf.Wedge(x), ipnix = ninf.LDot(x), \n attitude, pos, normal, tg,btg,epsilon = 0.001/(options.scale||1), I3=Element.Coeff(16,-1);\n var x2zero = Math.abs(X2) < epsilon, ipnixzero = ipnix.VLength < epsilon, opnixzero = opnix.VLength < epsilon;\n if (opnixzero && ipnixzero) { // free flat\n } else if (opnixzero && !ipnixzero) { // bound flat (lines)\n attitude = no.Wedge(ninf).LDot(x); \n weight2 = Math.abs(attitude.LDot(attitude).s)**.5;\n pos = attitude.LDot(x.Reverse); //Inverse);\n pos = [-pos.e15/pos.e45,-pos.e25/pos.e45,-pos.e34/pos.e45];\n if (x.Grade(3).VLength) {\n normal = [attitude.e1/weight2,attitude.e2/weight2,attitude.e3/weight2]; tp=2; \n } else {\n normal = Element.LDot(Element.Mul(attitude,1/weight2),I3).Normalized;\n var r=normal.Mul(Element.Coeff(3,1)); if (r[0]==-1) r[0]=1; else {r[0]+=1; r=r.Normalized;}\n tg = [...r.Mul(Element.Coeff(1,1)).Mul(r.Conjugate)].slice(1,4);\n btg = [...r.Mul(Element.Coeff(2,1)).Mul(r.Conjugate)].slice(1,4);\n normal = [...normal.slice(1,4)]; tp=3;\n }\n } else if (!opnixzero && ipnixzero) { // dual bound flat\n } else if (x2zero) { // bound vec,biv,tri (points)\n attitude = ninf.Wedge(no).LDot(ninf.Wedge(x)); \n pos = [...(Element.LDot(1/(ninf.LDot(x)).s,x)).slice(1,4)].map(x=>-x);\n tp=1; \n } else if (!x2zero) { // round (point pair,circle,sphere)\n tp = x.Grade(3).VLength?4:x.Grade(2).VLength?6:5; \n var nix = ninf.Wedge(x), nix2 = (nix.Mul(nix)).s;\n attitude = ninf.Wedge(no).LDot(nix);\n pos = [...(x.Mul(ninf).Mul(x)).slice(1,4)].map(x=>-x/(2.0*nix2));\n weight2 = Math.abs((x.LDot(x)).s / nix2)**.5;\n if (tp==4) {\n if (x.LDot(x).s < 0) { weight2 = -weight2; }\n normal = Element.LDot(Element.Mul(attitude,1/weight2),I3).Normalized;\n var r=normal.Mul(Element.Coeff(3,1)); if (r[0]==-1) r[0]=1; else {r[0]+=1; r=r.Normalized;}\n tg = [...r.Mul(Element.Coeff(1,1)).Mul(r.Conjugate)].slice(1,4);\n btg = [...r.Mul(Element.Coeff(2,1)).Mul(r.Conjugate)].slice(1,4);\n normal = [...normal.slice(1,4)]; \n } else if (tp==6) {\n weight2 = (x.LDot(x).s < 0)?-(weight2):weight2;\n normal = Element.Mul(attitude.Normalized,weight2).slice(1,4);\n } else {\n normal = [...((Element.LDot(Element.Mul(attitude,1/weight2),I3)).Normalized).slice(1,4)];\n }\n }\n return {tp,pos:pos?pos.map(x=>x*(options.scale||1)):[0,0,0],normal,tg,btg,weight2:weight2*(options.scale||1)}\n }; \n // canvas update will (re)render the content. \n var armed=0,sphere,e14 = Element.Coeff(14,1);\n canvas.update = (x)=>{\n // restore from still..\n if (options && !options.still && canvas.im && canvas.im.parentElement) { canvas.im.parentElement.insertBefore(canvas,canvas.im); canvas.im.parentElement.removeChild(canvas.im); }\n // Start by updating canvas size if needed and viewport.\n var s = getComputedStyle(canvas); if (s.width) { canvas.width = parseFloat(s.width); canvas.height = parseFloat(s.height); }\n gl.viewport(0,0, canvas.width|0,canvas.height|0); var r=canvas.width/canvas.height;\n // Defaults, resolve function input \n var a,p=[],l=[],t=[],c=[.5,.5,.5],alpha=0,lastpos=[-2,2,0.2]; gl.clear(gl.COLOR_BUFFER_BIT+gl.DEPTH_BUFFER_BIT); while (x.call) x=x();\n // Create default camera matrix and initial lastposition (contra-compensated for camera) \n M = mtx(options.camera); lastpos = options.camera.Normalized.Conjugate.Mul(((a=new this()).set(lastpos,11),a)).Mul(options.camera.Normalized).slice(11,14);\n // Grid.\n if (options.grid) {\n if (!options.gridLines) { options.gridLines=[[],[],[]]; for (var i=-5; i<=5; i++) {\n options.gridLines[0].push(i,0,5, i,0,-5, 5,0,i, -5,0,i); options.gridLines[1].push(i,5,0, i,-5,0, 5,i,0, -5,i,0); options.gridLines[2].push(0,i,5, 0,i,-5, 0,5,i, 0,-5,i);\n }}\n gl.depthMask(false);\n draw(program,gl.LINES,options.gridLines[0],[0,0,0],[.6,1,.6],r); draw(program,gl.LINES,options.gridLines[1],[0,0,0],[1,.8,.8],r); draw(program,gl.LINES,options.gridLines[2],[0,0,0],[.8,.8,1],r);\n gl.depthMask(true);\n }\n // Z-buffer override.\n if (options.noZ) gl.depthMask(false); \n // Loop over all items to render. \n for (var i=0,ll=x.length;i<ll;i++) { \n var e=x[i]; while (e&&e.call&&e.length==0) e=e(); if (e==undefined) continue;\n // CGA\n if (tot==5 && options.conformal) {\n if (e instanceof Array && e.length==2) { e.forEach(x=>{ while (x.call) x=x.call(); x=interprete(x);l.push.apply(l,x.pos); }); var d = {tp:-1}; }\n else if (e instanceof Array && e.length==3) { e.forEach(x=>{ while (x.call) x=x.call(); x=interprete(x);t.push.apply(t,x.pos); }); var d = {tp:-1}; }\n else var d = interprete(e);\n if (d.tp) lastpos=d.pos;\n if (d.tp==1) p.push.apply(p,d.pos);\n if (d.tp==2) { l.push.apply(l,d.pos.map((x,i)=>x-d.normal[i]*10)); l.push.apply(l,d.pos.map((x,i)=>x+d.normal[i]*10)); }\n if (d.tp==3) { t.push.apply(t,d.pos.map((x,i)=>x+d.tg[i]+d.btg[i])); t.push.apply(t,d.pos.map((x,i)=>x-d.tg[i]+d.btg[i])); t.push.apply(t,d.pos.map((x,i)=>x+d.tg[i]-d.btg[i])); \n t.push.apply(t,d.pos.map((x,i)=>x-d.tg[i]+d.btg[i])); t.push.apply(t,d.pos.map((x,i)=>x+d.tg[i]-d.btg[i])); t.push.apply(t,d.pos.map((x,i)=>x-d.tg[i]-d.btg[i])); }\n if (d.tp==4) {\n var ne=0,la=0;\n if (d.weight2<0) { c[0]=1;c[1]=0;c[2]=0; }\n for (var j=0; j<65; j++) {\n ne = d.pos.map((x,i)=>x+Math.cos(j/32*Math.PI)*d.weight2*d.tg[i]+Math.sin(j/32*Math.PI)*d.weight2*d.btg[i]); if (ne&&la&&(d.weight2>0||j%2==0)) { l.push.apply(l,la); l.push.apply(l,ne); }; la=ne;\n }\n } \n if (d.tp==6) {\n if (d.weight2<0) { c[0]=1;c[1]=0;c[2]=0; }\n if (options.useUnnaturalLineDisplayForPointPairs) {\n l.push.apply(l,d.pos.map((x,i)=>x-d.normal[i]*(options.scale||1)));\n l.push.apply(l,d.pos.map((x,i)=>x+d.normal[i]*(options.scale||1)));\n } \n p.push.apply(p,d.pos.map((x,i)=>x-d.normal[i]*(options.scale||1)));\n p.push.apply(p,d.pos.map((x,i)=>x+d.normal[i]*(options.scale||1)));\n }\n if (d.tp==5) {\n if (!sphere) {\n var pnts = [], tris=[], S=Math.sin, C=Math.cos, pi=Math.PI, W=96, H=48;\n for (var j=0; j<W+1; j++) for (var k=0; k<H; k++) {\n pnts.push( [S(2*pi*j/W)*S(pi*k/(H-1)), C(2*pi*j/W)*S(pi*k/(H-1)), C(pi*k/(H-1))]);\n if (j && k) {\n tris.push.apply(tris, pnts[(j-1)*H+k-1]);tris.push.apply(tris, pnts[(j-1)*H+k]);tris.push.apply(tris, pnts[j*H+k-1]);\n tris.push.apply(tris, pnts[j*H+k-1]); tris.push.apply(tris, pnts[(j-1)*H+k]); tris.push.apply(tris, pnts[j*H+k]);\n }}\n sphere = { va : createVA(tris,undefined) }; sphere.va.tcount = tris.length/3;\n }\n var oldM = M;\n M=[].concat.apply([],Element.Mul([[d.weight2,0,0,0],[0,d.weight2,0,0],[0,0,d.weight2,0],[d.pos[0],d.pos[1],d.pos[2],1]],[[M[0],M[1],M[2],M[3]],[M[4],M[5],M[6],M[7]],[M[8],M[9],M[10],M[11]],[M[12],M[13],M[14],M[15]]])).map(x=>x.s);\n gl.enable(gl.BLEND); gl.blendFunc(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA); gl.blendColor(1,1,1,0.5); gl.enable(gl.CULL_FACE)\n draw(program,gl.TRIANGLES,undefined,c,[0,0,0],r,undefined,sphere.va);\n gl.disable(gl.BLEND); gl.disable(gl.CULL_FACE);\n M = oldM;\n }\n if (i==ll-1 || d.tp==0) {\n // render triangles, lines, points.\n if (alpha) { gl.enable(gl.BLEND); gl.blendFunc(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA); gl.blendColor(1,1,1,1-alpha); }\n if (t.length) { draw(program,gl.TRIANGLES,t,c,[0,0,0],r); t.forEach((x,i)=>{ if (i%9==0) lastpos=[0,0,0]; lastpos[i%3]+=x/3; }); t=[]; }\n if (l.length) { draw(program,gl.LINES,l,[0,0,0],c,r); var l2=l.length-1; lastpos=[(l[l2-2]+l[l2-5])/2,(l[l2-1]+l[l2-4])/2+0.1,(l[l2]+l[l2-3])/2]; l=[]; }\n if (p.length) { draw(program,gl.POINTS,p,[0,0,0],c,r); lastpos = p.slice(-3); lastpos[0]-=0.075; lastpos[1]+=0.075; p=[]; }\n if (alpha) gl.disable(gl.BLEND);\n // we could also be an object with cached vertex array of triangles .. \n if (e instanceof Object && e.data) {\n // Create the vertex array and store it for re-use.\n if (!e.va) {\n var et=[],et2=[],et3=[],lc=0,pc=0,tc=0; e.data.forEach(e=>{ \n if (e instanceof Array && e.length==3) { tc++; e.forEach(x=>{ while (x.call) x=x.call(); x=interprete(x);et3.push.apply(et3,x.pos); }); var d = {tp:-1}; }\n else {\n var d = interprete(e); \n if (d.tp==1) { pc++; et.push(...d.pos); }\n if (d.tp==2) { lc++; et2.push(...d.pos.map((x,i)=>x-d.normal[i]*10),...d.pos.map((x,i)=>x+d.normal[i]*10)); }\n }\n });\n e.va = createVA(et,undefined); e.va.tcount = pc;\n e.va2 = createVA(et2,undefined); e.va2.tcount = lc*2;\n e.va3 = createVA(et3,undefined); e.va3.tcount = tc*3;\n }\n // render the vertex array.\n if (e.va.tcount) draw(program,gl.POINTS,undefined,[0,0,0],c,r,undefined,e.va);\n if (e.va2.tcount) draw(program,gl.LINES,undefined,[0,0,0],c,r,undefined,e.va2);\n if (e.va3.tcount) draw(program,gl.TRIANGLES,undefined,[0,0,0],c,r,undefined,e.va3);\n }\n // setup a new color \n if (typeof e == \"number\") { alpha=((e>>>24)&0xff)/255; c[0]=((e>>>16)&0xff)/255; c[1]=((e>>>8)&0xff)/255; c[2]=(e&0xff)/255; }\n if (typeof(e)=='string') {\n gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA); \n draw(program2,gl.TRIANGLES, \n [...Array(e.length*6*3)].map((x,i)=>{ var x=0,z=-0.2, o=x+(i/18|0)*1.1; return (0.05*(options.z||5))*[o,-1,z,o+1.2,-1,z,o,1,z,o+1.2,-1,z,o+1.2,1,z,o,1,z][i%18]}),c,lastpos,r,\n [...Array(e.length*6*2)].map((x,i)=>{ var o=(e.charCodeAt(i/12|0)-33)/94; return [o,1,o+1/94,1,o,0,o+1/94,1,o+1/94,0,o,0][i%12]})); gl.disable(gl.BLEND); lastpos[1]-=0.18;\n }\n }\n continue;\n }\n // PGA \n // Convert planes to polygons.\n if (e instanceof Element && e.Grade(1).Length) {\n var m = Element.Add(1, Element.Mul(e.Normalized, Element.Coeff(3,1))).Normalized, e0 = 0;\n e=Element.sw(m,[[-1,-1],[-1,1],[1,1],[-1,-1],[1,1],[1,-1]].map(([x,z])=>Element.Trivector(x,e0,z,1)));\n }\n // Convert lines to line segments. \n if (e instanceof Element && e.Grade(2).Length) \n e=[e.LDot(e14).Wedge(e).Add(e.Wedge(Element.Coeff(1,1)).Mul(Element.Coeff(0,-500))),e.LDot(e14).Wedge(e).Add(e.Wedge(Element.Coeff(1,1)).Mul(Element.Coeff(0,500)))];\n // If euclidean point, store as point, store line segments and triangles.\n if (e.e123) p.push.apply(p,e.slice(11,14).map((y,i)=>(i==0?1:-1)*y/e[14]).reverse());\n if (e instanceof Array && e.length==2) l=l.concat.apply(l,e.map(x=>[...x.slice(11,14).map((y,i)=>(i==0?1:-1)*y/x[14]).reverse()])); \n if (e instanceof Array && e.length%3==0) t=t.concat.apply(t,e.map(x=>[...x.slice(11,14).map((y,i)=>(i==0?1:-1)*y/x[14]).reverse()]));\n // Render orbits of parametrised motors\n function sw_mot_orig(A,R){\n var a0=A[0],a1=A[5],a2=A[6],a3=A[7],a4=A[8],a5=A[9],a6=A[10],a7=A[15];\n R[2] = -2*(a0*a3+a4*a7-a6*a2-a5*a1);\n R[1] = -2*(a4*a1-a0*a2-a6*a3+a5*a7);\n R[0] = 2*(a0*a1+a4*a2+a5*a3+a6*a7);\n return R\n }\n if ( e.call && e.length==1) { var count=e.dx||64;\n for (var xx,o=new Float32Array(3),ii=0; ii<count; ii++) {\n if (ii>1) l.push(xx[0],xx[1],xx[2]);\n xx = sw_mot_orig(e(ii/(count-1)),o); //Element.sw(e(ii/(count-1)),o);\n l.push(xx[0],xx[1],xx[2]);\n }\n }\n if ( e.call && e.length==2 && !e.va) { var countx=e.dx||64,county=e.dy||32;\n var temp=new Float32Array(3*countx*county),o=new Float32Array(3),et=[];\n for (var pp=0,ii=0; ii<countx; ii++) for (var jj=0; jj<county; jj++,pp+=3) temp.set(sw_mot_orig(e(ii/(countx-1),jj/(county-1)),o),pp);\n for (ii=0; ii<countx-1; ii++) for (var jj=0; jj<county; jj++) et.push((ii+0)*county+(jj+0),(ii+0)*county+(jj+1),(ii+1)*county+(jj+1),(ii+0)*county+(jj+0),(ii+1)*county+(jj+1),(ii+1)*county+(jj+0));\n e.va = createVA(temp,undefined,et.map(x=>x%(countx*county))); e.va.tcount = (countx-1)*county*2*3;\n }\n // we could also be an object with cached vertex array of triangles .. \n if (e.va || (e instanceof Object && e.data)) {\n // Create the vertex array and store it for re-use.\n if (!e.va) {\n if (e.idx) {\n var et = e.data.map(x=>[...x.slice(11,14).map((y,i)=>(i==0?1:-1)*y/x[14]).reverse()]).flat();\n } else {\n var et=[]; e.data.forEach(e=>{if (e instanceof Array && e.length==3) et=et.concat.apply(et,e.map(x=>[...x.slice(11,14).map((y,i)=>(i==0?1:-1)*y/x[14]).reverse()]));});\n }\n e.va = createVA(et,undefined,e.idx,e.color?new Float32Array(e.color):undefined); e.va.tcount = (e.idx && e.idx.length)?e.idx.length:e.data.length*3;\n }\n // render the vertex array.\n if (e.transform) { M=mtx(options.camera.Mul(e.transform)); }\n if (alpha) { gl.enable(gl.BLEND); gl.blendFunc(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA); gl.blendColor(1,1,1,1-alpha); }\n draw(e.color?programcol:program,gl.TRIANGLES,t,c,[0,0,0],r,undefined,e.va);\n if (alpha) gl.disable(gl.BLEND);\n if (e.transform) { M=mtx(options.camera); }\n }\n // if we're a number (color), label or the last item, we output the collected items. \n else if (!isNaN(e) || i==ll-1 || typeof e == 'string') {\n // render triangles, lines, points.\n if (alpha) { gl.enable(gl.BLEND); gl.blendFunc(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA); gl.blendColor(1,1,1,1-alpha); }\n if (t.length) { draw(program,gl.TRIANGLES,t,c,[0,0,0],r); t.forEach((x,i)=>{ if (i%9==0) lastpos=[0,0,0]; lastpos[i%3]+=x/3; }); t=[]; }\n if (l.length) { draw(program,gl.LINES,l,[0,0,0],c,r); var l2=l.length-1; lastpos=[(l[l2-2]+l[l2-5])/2,(l[l2-1]+l[l2-4])/2+0.1,(l[l2]+l[l2-3])/2]; l=[]; }\n if (p.length) { draw(program,gl.POINTS,p,[0,0,0],c,r); lastpos = p.slice(-3); lastpos[0]-=0.075; lastpos[1]+=0.075; p=[]; }\n if (alpha) gl.disable(gl.BLEND);\n // setup a new color \n if (typeof e == \"number\") { alpha=((e>>>24)&0xff)/255; c[0]=((e>>>16)&0xff)/255; c[1]=((e>>>8)&0xff)/255; c[2]=(e&0xff)/255; }\n // render a label \n if (typeof(e)=='string') {\n gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA); \n draw(program2,gl.TRIANGLES, \n [...Array(e.length*6*3)].map((x,i)=>{ var x=0,z=-0.2, o=x+(i/18|0)*1.1; return 0.25*[o,-1,z,o+1.2,-1,z,o,1,z,o+1.2,-1,z,o+1.2,1,z,o,1,z][i%18]}),c,lastpos,r,\n [...Array(e.length*6*2)].map((x,i)=>{ var o=(e.charCodeAt(i/12|0)-33)/94; return [o,1,o+1/94,1,o,0,o+1/94,1,o+1/94,0,o,0][i%12]})); gl.disable(gl.BLEND); lastpos[1]-=0.18;\n }\n } \n }; \n // if we're no longer in the page .. stop doing the work.\n armed++; if (document.body.contains(canvas)) armed=0; if (armed==2) return;\n canvas.value=x; if (options&&!options.animate) canvas.dispatchEvent(new CustomEvent('input')); canvas.options=options;\n if (options&&options.animate) { requestAnimationFrame(canvas.update.bind(canvas,f,options)); }\n if (options&&options.still) { canvas.value=x; canvas.dispatchEvent(new CustomEvent('input')); canvas.im.style.width=canvas.style.width; canvas.im.style.height=canvas.style.height; canvas.im.src = canvas.toDataURL(); \n var p=canvas.parentElement; if (p) { p.insertBefore(canvas.im,canvas); p.removeChild(canvas); }\n }\n }\n // Basic mouse interactivity. needs more love.\n var sel=-1; canvas.oncontextmenu = canvas.onmousedown = (e)=>{e.preventDefault(); e.stopPropagation(); if (e.detail===0) return; \n var rc = canvas.getBoundingClientRect(), mx=(e.x-rc.left)/(rc.right-rc.left)*2-1, my=((e.y-rc.top)/(rc.bottom-rc.top)*-4+2)*canvas.height/canvas.width;\n sel = (e.button==2)?-3:-2; canvas.value.forEach((x,i)=>{\n x = interprete(x); if (x.tp==1) {\n var pos2 = Element.Mul( [[M[0],M[4],M[8],M[12]],[M[1],M[5],M[9],M[13]],[M[2],M[6],M[10],M[14]],[M[3],M[7],M[11],M[15]]], [...x.pos,1]).map(x=>x.s);\n pos2 = Element.Mul( [[5,0,0,0],[0,5*(r||2),0,0],[0,0,1,-1],[0,0,2,0]], pos2).map(x=>x.s).map((x,i,a)=>x/a[3]);\n if ((mx-pos2[0])**2 + (my-pos2[1])**2 < 0.01) sel=i;\n }\n });\n canvas.onwheel=e=>{e.preventDefault(); e.stopPropagation(); options.z = (options.z||5)+e.deltaY/100; if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options));}\n canvas.onmouseup=e=>sel=-1; canvas.onmouseleave=e=>sel=-1;\n var tx,ty; canvas.ontouchstart = (e)=>{e.preventDefault(); canvas.focus(); var x = e.changedTouches[0].pageX, y = e.changedTouches[0].pageY; tx=x; ty=y; }\n canvas.ontouchmove = function (e) { e.preventDefault();\n var x = e.changedTouches[0].pageX, y = e.changedTouches[0].pageY, mx = (x-(tx||x))/1000, my = -(y-(ty||y))/1000; tx=x; ty=y; \n options.h = (options.h||0)+mx; options.p = Math.max(-Math.PI/2,Math.min(Math.PI/2, (options.p||0)+my)); if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options)); return; \n };\n canvas.onmousemove=(e)=>{ \n var rc = canvas.getBoundingClientRect(), x=interprete(canvas.value[sel]);\n var mx =(e.movementX)/(rc.right-rc.left)*2, my=((e.movementY)/(rc.bottom-rc.top)*-2)*canvas.height/canvas.width;\n if (sel==-2) { options.h = (options.h||0)+mx; options.p = Math.max(-Math.PI/2,Math.min(Math.PI/2, (options.p||0)+my)); if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options)); return; }; \n if (sel==-3) { var ct = Math.cos(options.h||0), st= Math.sin(options.h||0), ct2 = Math.cos(options.p||0), st2 = Math.sin(options.p||0);\n if (e.shiftKey) { options.posy = (options.posy||0)+my; } else { options.posx = (options.posx||0)+mx*ct+my*st; options.posz = (options.posz||0)+mx*-st+my*ct*ct2; } if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options));return; }; if (sel < 0) return;\n x.pos[0] += (e.buttons!=2)?Math.cos(-(options.h||0))*mx:Math.sin((options.h||0))*my; x.pos[1]+=(e.buttons!=2)?my:0; x.pos[2]+=(e.buttons!=2)?Math.sin(-(options.h||0))*mx:Math.cos((options.h||0))*my;\n canvas.value[sel].set(Element.Mul(ninf,(x.pos[0]**2+x.pos[1]**2+x.pos[2]**2)*0.5).Sub(no)); canvas.value[sel].set(x.pos,1);\n if (!options.animate) requestAnimationFrame(canvas.update.bind(canvas,f,options));\n }\n }\n canvas.value = f.call?f():f; canvas.options=options;\n if (options&&options.still) {\n var i=new Image(); canvas.im = i; return requestAnimationFrame(canvas.update.bind(canvas,f,options)),canvas;\n } else return requestAnimationFrame(canvas.update.bind(canvas,f,options)),canvas;\n } \n \n // The inline function is a js to js translator that adds operator overloading and algebraic literals.\n // It can be called with a function, a string, or used as a template function. \n static inline(intxt) {\n // If we are called as a template function. \n if (arguments.length>1 || intxt instanceof Array) {\n var args=[].slice.call(arguments,1);\n return res.inline(new Function(args.map((x,i)=>'_template_'+i).join(),'return ('+intxt.map((x,i)=>(x||'')+(args[i]&&('_template_'+i)||'')).join('')+')')).apply(res,args);\n }\n // Get the source input text. \n var txt = (intxt instanceof Function)?intxt.toString():`function(){return (${intxt})}`;\n // Our tokenizer reads the text token by token and stores it in the tok array (as type/token tuples). \n var tok = [], resi=[], t, tokens = [/^[\\s\\uFFFF]|^[\\u000A\\u000D\\u2028\\u2029]|^\\/\\/[^\\n]*\\n|^\\/\\*[\\s\\S]*?\\*\\//g, // 0: whitespace/comments\n /^\\\"\\\"|^\\'\\'|^\\\".*?[^\\\\]\\\"|^\\'.*?[^\\\\]\\'|^\\`[\\s\\S]*?[^\\\\]\\`/g, // 1: literal strings\n /^\\d+[.]{0,1}\\d*[ei][\\+\\-_]{0,1}\\d*|^\\.\\d+[ei][\\+\\-_]{0,1}\\d*|^e_\\d*/g, // 2: literal numbers in scientific notation (with small hack for i and e_ asciimath)\n /^\\d+[.]{0,1}\\d*[E][+-]{0,1}\\d*|^\\.\\d+[E][+-]{0,1}\\d*|^0x\\d+|^\\d+[.]{0,1}\\d*|^\\.\\d+|^\\(\\/.*[^\\\\]\\/\\)/g, // 3: literal hex, nonsci numbers and regex (surround regex with extra brackets!)\n /^(\\.Normalized|\\.Length|\\.\\.\\.|>>>=|===|!==|>>>|<<=|>>=|=>|\\|\\||[<>\\+\\-\\*%&|^\\/!\\=]=|\\*\\*|\\+\\+|\\-\\-|<<|>>|\\&\\&|\\^\\^|^[{}()\\[\\];.,<>\\+\\-\\*%|&^!~?:=\\/]{1})/g, // 4: punctuator\n /^[A-Za-z0-9_]*/g] // 5: identifier\n while (txt.length) for(t in tokens) if(resi=txt.match(tokens[t])){ tok.push([t|0,resi[0]]); txt=txt.slice(resi[0].length); break;} // tokenise \n // Translate algebraic literals. (scientific e-notation to \"this.Coeff\"\n tok=tok.map(t=>(t[0]==2)?[2,'Element.Coeff('+basis.indexOf('e'+(t[1].split(/e_|e|i/)[1]||1))+','+parseFloat(t[1][0]=='e'?1:t[1].split(/e_|e|i/)[0])+')']:t);\n // We support two syntaxes, standard js or if you pass in a text, asciimath. \n var syntax = (intxt instanceof Function)?[[['.Normalized','Normalize',2],['.Length','Length',2]],[['~','Conjugate',1],['!','Dual',1]],[['**','Pow',0,1]],[['^','Wedge'],['&','Vee'],['<<','LDot']],[['*','Mul'],['/','Div']],[['|','Dot']],[['>>>','sw',0,1]],[['-','Sub'],['+','Add']],[['==','eq'],['!=','neq'],['<','lt'],['>','gt'],['<=','lte'],['>=','gte']]]\n :[[['pi','Math.PI'],['sin','Math.sin']],[['ddot','this.Reverse'],['tilde','this.Involute'],['hat','this.Conjugate'],['bar','this.Dual']],[['^','Pow',0,1]],[['^^','Wedge'],['*','LDot']],[['**','Mul'],['/','Div']],[['-','Sub'],['+','Add']],[['<','lt'],['>','gt'],['<=','lte'],['>=','gte']]];\n // For asciimath, some fixed translations apply (like pi->Math.PI) etc .. \n tok=tok.map(t=>(t[0]!=5)?t:[].concat.apply([],syntax).filter(x=>x[0]==t[1]).length?[5,[].concat.apply([],syntax).filter(x=>x[0]==t[1])[0][1]]:t); \n // Now the token-stream is translated recursively. \n function translate(tokens) {\n // helpers : first token to the left of x that is not of a type in the skip list.\n var left = (x=ti-1,skip=[0])=>{ while(x>=0&&~skip.indexOf(tokens[x][0])) x--; return x; },\n // first token to the right of x that is not of a type in the skip list.\n right= (x=ti+1,skip=[0])=>{ while(x<tokens.length&&~skip.indexOf(tokens[x][0])) x++; return x; },\n // glue from x to y as new type, optionally replace the substring with sub. \n glue = (x,y,tp=5,sub)=>{tokens.splice(x,y-x+1,[tp,...(sub||tokens.slice(x,y+1))])},\n // match O-C pairs. returns the 'matching bracket' position \n match = (O=\"(\",C=\")\")=>{var o=1,x=ti+1; while(o){if(tokens[x][1]==O)o++;if(tokens[x][1]==C)o--; x++;}; return x-1;};\n // grouping (resolving brackets). \n for (var ti=0,t,si;t=tokens[ti];ti++) if (t[1]==\"(\") glue(ti,si=match(),6,[[4,\"(\"],...translate(tokens.slice(ti+1,si)),[4,\")\"]]); \n // [] dot call and new\n for (var ti=0,t,si; t=tokens[ti];ti++) {\n if (t[1]==\"[\") { glue(ti,si=match(\"[\",\"]\"),6,[[4,\"[\"],...translate(tokens.slice(ti+1,si)),[4,\"]\"]]); if (ti)ti--;} // matching []\n else if (t[1]==\".\") { glue(left(),right()); ti--; } // dot operator\n else if (t[0]==6 && ti && left()>=0 && tokens[left()][0]>=5 && tokens[left()][1]!=\"return\") { glue(left(),ti--) } // collate ( and [\n else if (t[1]=='new') { glue(ti,right()) }; // collate new keyword\n }\n // ++ and --\n for (var ti=0,t; t=tokens[ti];ti++) if (t[1]==\"++\" || t[1]==\"--\") glue(left(),ti); \n // unary - and + are handled seperately from syntax ..\n for (var ti=0,t,si; t=tokens[ti];ti++) \n if (t[1]==\"-\" && (left()<0 || (tokens[left()]||[4])[0]==4)) glue(ti,right(),5,[\"Element.Sub(\",tokens[right()],\")\"]); // unary minus works on all types.\n else if (t[1]==\"+\" && (tokens[left()]||[0])[0]==4) glue(ti,ti+1); // unary plus is glued, only on scalars.\n // now process all operators in the syntax list .. \n for (var si=0,s; s=syntax[si]; si++) for (var ti=s[0][3]?tokens.length-1:0,t; t=tokens[ti];s[0][3]?ti--:ti++) for (var opi=0,op; op=s[opi]; opi++) if (t[1]==op[0]) {\n // exception case .. \".Normalized\" and \".Length\" properties are re-routed (so they work on scalars etc ..)\n if (op[2]==2) { var arg=tokens[left()]; glue(ti-1,ti,5,[\"Element.\"+op[1],\"(\",arg,\")\"]); } \n // unary operators (all are to the left) \n else if (op[2]) { var arg=tokens[right()]; glue(ti, right(), 5, [\"Element.\"+op[1],\"(\",arg,\")\"]); }\n // binary operators \n else { var l=left(),r=right(),a1=tokens[l],a2=tokens[r]; glue(l,r,5,[\"Element.\"+op[1],\"(\",a1,\",\",a2,\")\"]); ti--; }\n }\n return tokens;\n } \n // Glue all back together and return as bound function. \n return eval( ('('+(function f(t){return t.map(t=>t instanceof Array?f(t):typeof t == \"string\"?t:\"\").join('');})(translate(tok))+')') );\n }\n }\n \n \n // Matrix-free inverses up to 5D. Should translate this to an inline call for readability.\n // http://repository.essex.ac.uk/17282/1/TechReport_CES-534.pdf \n res.prototype.__defineGetter__('Inverse', function(){ \n return (tot==0)?new this.constructor.Scalar([1/this[0]]):\n (tot==1)?this.Involute.Mul(this.constructor.Scalar(1/this.Mul(this.Involute)[0])):\n (tot==2)?this.Conjugate.Mul(this.constructor.Scalar(1/this.Mul(this.Conjugate)[0])):\n (tot==3)?this.Reverse.Mul(this.Involute).Mul(this.Conjugate).Mul( this.constructor.Scalar(1/this.Mul(this.Conjugate).Mul(this.Involute).Mul(this.Reverse)[0])):\n (tot==4)?this.Conjugate.Mul(this.Mul(this.Conjugate).Map(3,4)).Mul( this.constructor.Scalar(1/this.Mul(this.Conjugate).Mul(this.Mul(this.Conjugate).Map(3,4))[0])):\n this.Conjugate.Mul(this.Involute).Mul(this.Reverse).Mul(this.Mul(this.Conjugate).Mul(this.Involute).Mul(this.Reverse).Map(1,4)).Mul(this.constructor.Scalar(1/this.Mul(this.Conjugate).Mul(this.Involute).Mul(this.Reverse).Mul(this.Mul(this.Conjugate).Mul(this.Involute).Mul(this.Reverse).Map(1,4))[0]));\n });\n \n // If a function was passed in, translate, call and return its result. Else just return the Algebra. \n if (fu instanceof Function) return res.inline(fu)(); else return res; \n }\n}));\n\n function add_graph_to_notebook(Algebra){\n var output = Algebra({p:4,q:1,r:0,baseType:Float64Array},()=>{\n // When we get a file, we load and display.\n var canvas;\n var h=0, p=0;\n // convert arrays of floats back to CGA elements.\n var data = [0, {data:[[0.0, 2.9411635777003005, 16.575365852710323, 17.074564383213925, 286.9669726092519, 287.9669726092519, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.840493862435234e-14, 3.1425630951060477e-13, 3.1425630951060477e-13, -3.053009837860811e-13, -3.053009837860811e-13, 0.0, 5.54876769962813e-14, 5.54876769962813e-14, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, -17.74964232619511, -17.382699467468377, 8.296485688625085, 342.5198591328716, 343.5198591328716, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.902259007827684e-13, 2.407855032312562e-12, 2.407855032312562e-12, 5.051986944797036e-12, 5.051986944797036e-12, 0.0, -5.147867669797527e-12, -5.147867669797527e-12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 10.059454050071313, -1.6362200299057186, 15.910162268580523, 178.00154759216207, 179.00154759216207, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -6.168350758509988e-14, -9.813946149741565e-13, -9.813946149741565e-13, -1.0730731417359889e-13, -1.0730731417359889e-13, 0.0, -6.211136519948125e-13, -6.211136519948125e-13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 7.764469102554897, -9.9621273984128, 13.879219950839312, 175.58185459525632, 176.58185459525632, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.230267640781833e-13, -1.7075155184811227e-12, -1.7075155184811227e-12, -1.2238709013343548e-12, -1.2238709013343548e-12, 0.0, -9.607878145530505e-13, -9.607878145530505e-13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, -18.926001924439852, 18.66851117686507, 13.088120140897335, 438.5028736135998, 439.5028736135998, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -1.603519071673545e-13, -2.098705025828352e-12, -2.098705025828352e-12, 2.988905430688904e-12, 2.988905430688904e-12, 0.0, 3.0379717063450904e-12, 3.0379717063450904e-12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 3.749118757761751, 17.593179526282555, 16.2529285917069, 293.3667725554661, 294.3667725554661, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.5334960575735212e-13, 2.4923801919406497e-12, 2.4923801919406497e-12, -2.6989847701310645e-12, -2.6989847701310645e-12, 0.0, 5.716800360241692e-13, 5.716800360241692e-13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 7.5366619809381294, -11.169047416322057, 12.75784736438073, 171.6557816879025, 172.6557816879025, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -4.9054708338539577e-14, -6.258324814873026e-13, -6.258324814873026e-13, -5.510588486599378e-13, -5.510588486599378e-13, 0.0, -3.6959548895816597e-13, -3.6959548895816597e-13, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, -11.882567539962201, -20.58822454912611, 8.14589236015862, 315.21298188514646, 316.21298188514646, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -2.018652604271175e-13, -1.6443726826946864e-12, -1.6443726826946864e-12, -4.152808443590151e-12, -4.152808443590151e-12, 0.0, 2.3930505915756095e-12, 2.3930505915756095e-12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 10.708656243917957, 10.016465066593248, 16.579794819308823, 244.4472436155328, 245.4472436155328, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 7.913914480366857e-15, 1.312110783020395e-13, 1.312110783020395e-13, -8.226728811190655e-14, -8.226728811190655e-14, 0.0, 8.500625900261265e-14, 8.500625900261265e-14, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, -23.446517822856446, -15.582746926750287, 6.574214450599988, 417.3907477215741, 418.3907477215741, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 3.213336050959161e-13, 2.1125160300849616e-12, 2.1125160300849616e-12, 5.004740553510541e-12, 5.004740553510541e-12, 0.0, -7.532953549010588e-12, -7.532953549010588e-12, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]].map(x=>x.length==32?new Element(x):x).map(x=>x.length==32?new Element(x):x)}, 65280, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.5804935388215091, 12.142436157116785, 12.187157184042686, 0.21684567109922143, 0.2232174797363051, 0.11657617864712576, -6.691459470249603, -6.700454387901287, 0.32735717645423734, 0.07008896322114992, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 16711680, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6577975512778201, 12.162639898466134, 12.209889688880983, -0.022776734618162132, -0.01595948405271484, 0.12766228693489323, -7.296059327799348, -7.305030810363966, 0.3582056342222838, 0.07591058293103932, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 255, [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.6234240943222209, 12.347746243120179, 12.393667737724648, 0.2145462422514287, 0.22133394901992573, 0.11863608948987134, -6.861510060675536, -6.869938827696023, 0.3384766851975262, 0.07180594782524109, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]].map(x=>x.length==32?new Element(x):x);\n data = data.map(x=>x.length==32?new Element(x):x);\n // add the graph to the page.\n canvas = this.graph(data,{gl:true,conformal:true,grid:true,scale:0.05,useUnnaturalLineDisplayForPointPairs:true});\n canvas.options.h = h; canvas.options.p = p;\n // make it big.\n canvas.style.width = '100%';\n canvas.style.height = '50vh';\n return canvas;\n });\n element.append(output);\n\n var a = document.createElement(\"button\");\n var t = document.createTextNode(\"💾 Save\");\n a.appendChild(t);\n function screenshot(){\n //output.width = 1920; output.height = 1080;\n output.update(output.value);\n output.toBlob(function(blob) {\n var url = URL.createObjectURL(blob);\n window.open(url, '_blank');\n });\n }\n a.onclick = screenshot\n var butnelem = element.append(a);\n }\n // requirejs works in sphinx, require works in jupyter\n (requirejs || require)(['Algebra'],function(Algebra){add_graph_to_notebook(Algebra)});\n "
},
"metadata": {}
}
]
},
{
"metadata": {},
"cell_type": "markdown",
"source": "# Test them several time"
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "noise = 0.1\nnpnts = 10\nntests = 1000\n\ncdiff_array = np.zeros((ntests, 2))\nndiff_array = np.zeros((ntests, 2))\nrdiff_array = np.zeros((ntests, 2))\nr_array = np.zeros(ntests)\n\nfor i in range(ntests):\n # The true circle\n true_circle = random_circle()\n point_list = project_points_to_circle([random_conformal_point() for i in range(npnts)], true_circle)\n point_list = [up(down(P) + noise * random_euc_mv()) for P in point_list]\n\n # Circle from plane sphere intersection\n sphere = fit_sphere(point_list)\n plane = fit_plane(point_list)\n plane_circle = meet(sphere,plane).normal()\n\n # The circle from leos method\n leo_circle = fit_circle(point_list)\n\n ct, mt, rt = get_circle_in_euc(true_circle)\n cp, mp, rp = get_circle_in_euc(plane_circle)\n cl, ml, rl = get_circle_in_euc(leo_circle)\n\n center_diff = [((cp-ct)**2)[0], ((cl-ct)**2)[0]]\n cdiff_array[i, :] = center_diff\n normal_diff = [((mp-mt)**2)[0], ((ml-mt)**2)[0]]\n ndiff_array[i, :] = normal_diff\n radius_diff = [((rp-rt)**2), ((rl-rt)**2)]\n rdiff_array[i, :] = radius_diff\n r_array[i] = rt\n\nprint('Comparison rms error')\nprint('for circles of average radius')\nprint(np.mean(r_array))\nprint('l p')\nprint('centres')\nprint(np.sqrt(np.mean(cdiff_array, axis=0)))\nprint(np.sqrt(np.median(cdiff_array, axis=0)))\nprint('normals')\nprint(np.sqrt(np.mean(ndiff_array, axis=0)))\nprint(np.sqrt(np.median(ndiff_array, axis=0)))\nprint('radii')\nprint(np.sqrt(np.mean(rdiff_array, axis=0)))\nprint(np.sqrt(np.median(rdiff_array, axis=0)))",
"execution_count": 4,
"outputs": [
{
"output_type": "stream",
"text": "Comparison rms error\nfor circles of average radius\n18.65239054046665\nl p\ncentres\n[20.37996876 18.32594386]\n[1.18569829 1.05548757]\nnormals\n[1.44558323 1.38409866]\n[1.94952453 0.39131832]\nradii\n[9.26278359 9.80583646]\n[0.5003353 0.44704945]\n",
"name": "stdout"
}
]
},
{
"metadata": {
"trusted": true
},
"cell_type": "code",
"source": "",
"execution_count": null,
"outputs": []
}
],
"metadata": {
"kernelspec": {
"name": "python3",
"display_name": "Python 3",
"language": "python"
},
"language_info": {
"name": "python",
"version": "3.7.4",
"mimetype": "text/x-python",
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"pygments_lexer": "ipython3",
"nbconvert_exporter": "python",
"file_extension": ".py"
},
"gist": {
"id": "",
"data": {
"description": "circle_fitting",
"public": true
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment