UNPKG

sph-ga

Version:

fundamental calculations in euclidean and conformal geometric algebras

928 lines (867 loc) 28.2 kB
// Generated by CoffeeScript 2.7.0 var sph_ga, indexOf = [].indexOf; sph_ga = (function() { class sph_ga { constructor(metric, options = {}) { var ref, ref1, rotation_axis_combinations; metric = Number.isInteger(metric) ? Array(metric).fill(1) : metric; this.n = metric.length; this.is_conformal = !!options.conformal; if (!Array.isArray(metric[0])) { if (this.is_conformal) { this.n += 2; metric.push(0, 0); } metric = this.flat_metric_to_full(metric, this.n); if (this.is_conformal) { metric[this.n - 1][this.n - 2] = -1; metric[this.n - 2][this.n - 1] = -1; } } [this.is_symmetric, this.is_diagonal, this.null_vector_count] = this.metric_properties(metric, this.n); if (!this.is_symmetric) { throw new Error("the metric must be symmetric in the non-null part"); } if (this.is_conformal) { if (2 !== this.null_vector_count) { throw new Error("only two null vectors are allowed with \"conformal: true\". use a custom metric instead"); } if (!this.is_diagonal) { throw new Error("only diagonal metrics are allowed with \"conformal: true\". use a custom metric instead"); } } if (this.null_vector_count) { this.null_vector_start = this.n - this.null_vector_count; this.id_null = this.id_from_indices((function() { var results = []; for (var l = ref = this.null_vector_start + 1, ref1 = this.n; ref <= ref1 ? l <= ref1 : l >= ref1; ref <= ref1 ? l++ : l--){ results.push(l); } return results; }).apply(this)); } this.pseudoscalar_id = (1 << this.n) - 1; this.metric = metric; if (this.is_diagonal) { this.ip_metric = function(indices) { var i; return this.array_product((function() { var l, len, results; results = []; for (l = 0, len = indices.length; l < len; l++) { i = indices[l]; results.push(this.metric[i][i]); } return results; }).call(this)); }; } else { this.ip_metric = function(indices) { var i, j; if (!indices.length) { return 1; } return this.determinant((function() { var l, len, results; results = []; for (l = 0, len = indices.length; l < len; l++) { i = indices[l]; results.push((function() { var len1, o, results1; results1 = []; for (o = 0, len1 = indices.length; o < len1; o++) { j = indices[o]; results1.push(this.metric[i][j]); } return results1; }).call(this)); } return results; }).call(this)); }; } if (this.is_conformal) { this.no_bit_index = this.n - 2; this.ni_bit_index = this.n - 1; this.no_id = 1 << this.no_bit_index; this.ni_id = 1 << this.ni_bit_index; this.no_index = this.no_bit_index + 1; this.ni_index = this.ni_bit_index + 1; this.no = function(coeff) { return [[this.no_id, coeff, 1]]; }; this.ni = function(coeff) { return [[this.ni_id, coeff, 1]]; }; rotation_axis_combinations = function(n) { var combinations, i, j, l, o, ref2, ref3, ref4; combinations = []; for (i = l = 0, ref2 = n; (0 <= ref2 ? l < ref2 : l > ref2); i = 0 <= ref2 ? ++l : --l) { for (j = o = ref3 = i + 1, ref4 = n; (ref3 <= ref4 ? o < ref4 : o > ref4); j = ref3 <= ref4 ? ++o : --o) { combinations.push([i + 1, j + 1]); } } return combinations; }; this.rotation_axes = rotation_axis_combinations(this.n - 2); this.rotor = function(coeffs) { var a, first, i, rest; [first, ...rest] = coeffs; return this.mv([[[0], first]].concat((function() { var l, len, results; results = []; for (i = l = 0, len = rest.length; l < len; i = ++l) { a = rest[i]; results.push([this.rotation_axes[i], a]); } return results; }).call(this))); }; this.point = function(euclidean_coeffs) { var a, ni_coeff; ni_coeff = this.array_sum((function() { var l, len, results; results = []; for (l = 0, len = euclidean_coeffs.length; l < len; l++) { a = euclidean_coeffs[l]; results.push(a * a); } return results; })()) / 2; return this.vector([0].concat(euclidean_coeffs).concat([1, ni_coeff])); }; this.point_euclidean = function(a) { var b, c, d, l, len, n0, results; n0 = this.blade_coeff(this.get(a, this.no_id)); c = (function() { var l, len, results; results = []; for (l = 0, len = a.length; l < len; l++) { b = a[l]; if (1 === b[2] && !(b[0] === this.no_id || b[0] === this.ni_id)) { results.push(b[1]); } } return results; }).call(this); results = []; for (l = 0, len = c.length; l < len; l++) { d = c[l]; results.push(d / n0); } return results; }; this.normal = this.vector([0].concat(Array(this.n).fill(1 / Math.sqrt(this.n)))); } } add_one(a, b) { return this.combine(a, b, 1); } array_diff(a, b) { return a.filter(function(c) { return !(indexOf.call(b, c) >= 0); }); } array_product(a) { return a.reduce((function(b, a) { return a * b; }), 1); } array_sum(a) { return a.reduce((function(b, a) { return a + b; }), 0); } basis_blade(i, coeff) { if (i) { return [1 << (i - 1), coeff, 1]; } else { return [0, coeff, 0]; } } basis(i, coeff = 1) { return [this.basis_blade(i, coeff)]; } blade_coeff(a) { return a[1]; } blade_grade(a) { return a[2]; } blade_id(a) { return a[0]; } coeffs_add(coeffs, id, coeff, grade) { if (coeffs[id] != null) { return coeffs[id][0] += coeff; } else { return coeffs[id] = [coeff, grade]; } } conjugate(a) { return this.map_grade_factor(a, function(grade) { return (-1) ** ((grade * (grade + 1)) >> 1); }); } get(a, id) { var b, l, len; for (l = 0, len = a.length; l < len; l++) { b = a[l]; if (id === b[0]) { return b; } } } grade(a) { return a[a.length - 1][2]; } id_from_bit_indices(indices) { return indices.reduce((function(id, i) { return id |= 1 << i; }), 0); } id_from_indices(indices) { return indices.reduce((function(id, i) { return id |= 1 << (i - 1); }), 0); } involute(a) { return this.map_grade_factor(a, function(grade) { return (-1) ** grade; }); } map_grade_factor(a, f) { var coeff, grade, id, l, len, results; results = []; for (l = 0, len = a.length; l < len; l++) { [id, coeff, grade] = a[l]; results.push([id, coeff * f(grade), grade]); } return results; } mv(terms) { var coeff, indices, l, len, results; results = []; for (l = 0, len = terms.length; l < len; l++) { [indices, coeff] = terms[l]; results.push(this.blade(indices, coeff)); } return results; } negate(a) { return this.scale(a, -1); } pseudoscalar() { var ref; return [ this.blade((function() { var results = []; for (var l = 1, ref = this.n; 1 <= ref ? l <= ref : l >= ref; 1 <= ref ? l++ : l--){ results.push(l); } return results; }).apply(this), 1) ]; } reverse(a) { return this.map_grade_factor(a, function(grade) { return (-1) ** ((grade * (grade - 1)) >> 1); }); } scale(mv, a) { var coeff, grade, id, l, len, results; results = []; for (l = 0, len = mv.length; l < len; l++) { [id, coeff, grade] = mv[l]; results.push([id, coeff * a, grade]); } return results; } s(coeff) { return [[0, coeff, 0]]; } sp(a, b) { return this.gp(this.gp(a, b), this.inverse(a)); } subtract_one(a, b) { return this.combine(a, b, -1); } vector(coeffs) { var a, i, l, len, results; results = []; for (i = l = 0, len = coeffs.length; l < len; i = ++l) { a = coeffs[i]; if (a) { results.push(this.basis_blade(i, a)); } } return results; } ep(...a) { return a.reduce((c, b) => { return this.ep_one(c, b); }); } ip(...a) { return a.reduce((c, b) => { return this.ip_one(c, b); }); } gp(...a) { return a.reduce((c, b) => { return this.gp_one(c, b); }); } add(...a) { return a.reduce((c, b) => { return this.add_one(c, b); }); } subtract(...a) { return a.reduce((c, b) => { return this.subtract_one(c, b); }); } mv_to_string(a) { var b; return ((function() { var l, len, results; results = []; for (l = 0, len = a.length; l < len; l++) { b = a[l]; results.push(this.blade_to_string(b)); } return results; }).call(this)).join(" + "); } blade(indices, coeff) { if (indices[0]) { return [this.id_from_indices(indices), coeff, indices.length]; } else { return [this.id_from_indices(indices.slice(1)), coeff, indices.length - 1]; } } coeffs_to_mv(coeffs) { var a, coeff, grade, id; a = (function() { var results; results = []; for (id in coeffs) { [coeff, grade] = coeffs[id]; if (coeff !== 0) { results.push([parseInt(id), coeff, grade]); } } return results; })(); if (a.length) { return a; } else { return [[0, 0, 0]]; } } id_indices(id) { var a, l, len, ref, results; if (id) { ref = this.id_bit_indices(id); results = []; for (l = 0, len = ref.length; l < len; l++) { a = ref[l]; results.push(1 + a); } return results; } else { return [0]; } } id_bit_indices(id) { var a, i; if (id in this.id_bit_indices_cache) { return this.id_bit_indices_cache[id]; } a = (function() { var l, ref, results; results = []; for (i = l = 0, ref = this.n; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { if (id & (1 << i)) { results.push(i); } } return results; }).call(this); this.id_bit_indices_cache[id] = a; return a; } flat_metric_to_full(metric, n) { var a, b, i, l, ref; a = Array(n); for (i = l = 0, ref = n; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { b = Array(n).fill(0); b[i] = metric[i]; a[i] = b; } return a; } metric_properties(metric, n) { var di, first_zero, i, is_diagonal, j, k, l, null_vectors, o, p, ref, ref1, ref2, ref3, ref4, ref5; null_vectors = 0; first_zero = n; is_diagonal = true; for (i = l = 0, ref = n; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { di = metric[i][i]; if (di === 0) { null_vectors += 1; if (first_zero === n) { first_zero = i; } } else { if (first_zero < n || ((ref1 = !di) === 1 || ref1 === (-1))) { is_diagonal = false; break; } } for (j = o = ref2 = i + 1, ref3 = n; (ref2 <= ref3 ? o < ref3 : o > ref3); j = ref2 <= ref3 ? ++o : --o) { if (i < first_zero) { if (metric[i][j] !== metric[j][i]) { return [false, false, null_vectors]; } if (metric[i][j] !== 0) { is_diagonal = false; break; } } } if (!is_diagonal) { break; } } for (k = p = ref4 = first_zero, ref5 = n; (ref4 <= ref5 ? p < ref5 : p > ref5); k = ref4 <= ref5 ? ++p : --p) { if (metric[k][k] !== 0) { return [false, false, null_vectors]; } } return [true, is_diagonal, null_vectors]; } id_grade(a) { var b, n; if (a in this.id_grade_cache) { return this.id_grade_cache[a]; } n = 0; b = a; while (b !== 0) { b &= b - 1; n += 1; } this.id_grade_cache[a] = n; return n; } determinant_generic(a, n) { var b, c, i, j, l, o, ref, ref1, sign; if (n === 1) { return a[0][0]; } b = 0; for (j = l = 0, ref = n; (0 <= ref ? l < ref : l > ref); j = 0 <= ref ? ++l : --l) { for (i = o = 1, ref1 = n; (1 <= ref1 ? o < ref1 : o > ref1); i = 1 <= ref1 ? ++o : --o) { c = a[i].slice(0, j).concat(a[i].slice(j + 1)); } sign = 0 === j % 2 ? 1 : -1; b += sign * a[0][j] * this.determinant_generic(c); } return b; } determinant(matrix) { var n; n = matrix.length; if (1 === n) { return matrix[0][0]; } else if (2 === n) { return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]; } else if (3 === n) { return matrix[0][0] * (matrix[1][1] * matrix[2][2] - matrix[1][2] * matrix[2][1]) - matrix[0][1] * (matrix[1][0] * matrix[2][2] - matrix[1][2] * matrix[2][0]) + matrix[0][2] * (matrix[1][0] * matrix[2][1] - matrix[1][1] * matrix[2][0]); } else if (4 === n) { return matrix[0][0] * (matrix[1][1] * (matrix[2][2] * matrix[3][3] - matrix[2][3] * matrix[3][2]) - matrix[1][2] * (matrix[2][1] * matrix[3][3] - matrix[2][3] * matrix[3][1]) + matrix[1][3] * (matrix[2][1] * matrix[3][2] - matrix[2][2] * matrix[3][1])) - matrix[0][1] * (matrix[1][0] * (matrix[2][2] * matrix[3][3] - matrix[2][3] * matrix[3][2]) - matrix[1][2] * (matrix[2][0] * matrix[3][3] - matrix[2][3] * matrix[3][0]) + matrix[1][3] * (matrix[2][0] * matrix[3][2] - matrix[2][2] * matrix[3][0])) + matrix[0][2] * (matrix[1][0] * (matrix[2][1] * matrix[3][3] - matrix[2][3] * matrix[3][1]) - matrix[1][1] * (matrix[2][0] * matrix[3][3] - matrix[2][3] * matrix[3][0]) + matrix[1][3] * (matrix[2][0] * matrix[3][1] - matrix[2][1] * matrix[3][0])) - matrix[0][3] * (matrix[1][0] * (matrix[2][1] * matrix[3][2] - matrix[2][2] * matrix[3][1]) - matrix[1][1] * (matrix[2][0] * matrix[3][2] - matrix[2][2] * matrix[3][0]) + matrix[1][2] * (matrix[2][0] * matrix[3][1] - matrix[2][1] * matrix[3][0])); } else { return this.determinant_generic(matrix, n); } } array_remove_pair(a, i, j) { if (i < j) { a.splice(j, 1); return a.splice(i, 1); } else { a.splice(i, 1); return a.splice(j, 1); } } sign(indices) { var c, i, j, l, o, ref, ref1, ref2; c = 0; for (i = l = 0, ref = indices.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { for (j = o = ref1 = i + 1, ref2 = indices.length; (ref1 <= ref2 ? o < ref2 : o > ref2); j = ref1 <= ref2 ? ++o : --o) { if (indices[i] > indices[j]) { c += 1; } } } return (-1) ** c; } sign_sorted(a, b) { var c, i, j; c = 0; i = 0; j = 0; while (i < a.length && j < b.length) { if (a[i] <= b[j]) { i += 1; } else { c += 1; j += 1; } } return (-1) ** c; } ip_one(a, b) { var c, choose, coeff_a, coeff_b, coeff_c, coeffs, grade_a, grade_b, grade_c, id_a, id_b, id_c, indices_a, indices_b, inner_product_terms, ip_equal_grade, l, len, len1, len2, o, p, sign_of_choice; choose = function(a, k) { // return all combinations of "k" elements from "a". var head, tail, with_head, without_head; if (k === 0) { return [[]]; } if (a.length < k) { return []; } head = a[0]; tail = a.slice(1); with_head = choose(tail, k - 1); with_head = with_head.map(function(combo) { return [head].concat(combo); }); without_head = choose(tail, k); return with_head.concat(without_head); }; sign_of_choice = function(combo) { var i, l, ref, s; s = 0; for (i = l = 0, ref = combo.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { s += combo[i] - i; } return (-1) ** s; }; inner_product_terms = (a, b) => { var c, chosen, coeff, combo, i, id, indices, l, len, n, o, ref, ref1, ref2, results; n = a.length; indices = (function() { var results = []; for (var l = 0, ref = b.length; 0 <= ref ? l < ref : l > ref; 0 <= ref ? l++ : l--){ results.push(l); } return results; }).apply(this); ref1 = choose(indices, n); results = []; for (l = 0, len = ref1.length; l < len; l++) { combo = ref1[l]; chosen = (function() { var len1, o, results1; results1 = []; for (o = 0, len1 = combo.length; o < len1; o++) { i = combo[o]; results1.push(b[i]); } return results1; })(); c = indices.filter(function(i) { return !(indexOf.call(combo, i) >= 0); }); coeff = sign_of_choice(combo); for (i = o = 0, ref2 = a.length; (0 <= ref2 ? o < ref2 : o > ref2); i = 0 <= ref2 ? ++o : --o) { coeff *= this.metric[a[i]][chosen[i]]; } id = this.id_from_bit_indices((function() { var len1, p, results1; results1 = []; for (p = 0, len1 = c.length; p < len1; p++) { i = c[p]; results1.push(b[i]); } return results1; })()); results.push([id, coeff, c.length]); } return results; }; ip_equal_grade = (a, b) => { var i, j, m, s; // for 1-blades use the metric directly. if (a.length === 1) { return this.s(this.metric[a[0]][b[0]]); } else { m = (function() { var l, ref, results; // -> gram_matrix results = []; for (i = l = 0, ref = a.length; (0 <= ref ? l < ref : l > ref); i = 0 <= ref ? ++l : --l) { results.push((function() { var o, ref1, results1; results1 = []; for (j = o = 0, ref1 = b.length; (0 <= ref1 ? o < ref1 : o > ref1); j = 0 <= ref1 ? ++o : --o) { results1.push(this.metric[a[i]][b[j]]); } return results1; }).call(this)); } return results; }).call(this); s = (-1) ** (a.length * (a.length - 1) / 2); return this.s(-s * this.determinant(m)); } }; coeffs = {}; for (l = 0, len = a.length; l < len; l++) { [id_a, coeff_a, grade_a] = a[l]; indices_a = this.id_bit_indices(id_a); for (o = 0, len1 = b.length; o < len1; o++) { [id_b, coeff_b, grade_b] = b[o]; if (grade_b < grade_a) { continue; } indices_b = this.id_bit_indices(id_b); if (grade_a === grade_b) { c = ip_equal_grade(indices_a, indices_b); } else { c = inner_product_terms(indices_a, indices_b); } for (p = 0, len2 = c.length; p < len2; p++) { [id_c, coeff_c, grade_c] = c[p]; this.coeffs_add(coeffs, id_c, coeff_a * coeff_b * coeff_c, grade_c); } } } return this.coeffs_to_mv(coeffs); } gp_one(a, b) { var coeff_a, coeff_b, coeffs, combined, final_coeff, grade_a, grade_b, id_a, id_b, indices_a, indices_b, l, len, len1, m_factor, merge_indices, merged, o, sign; merge_indices = (indices) => { var changed, factor, i, j, m; // merge the basis vector indices from two blades while incorporating metric factors. factor = 1; changed = true; // first pass: process pairs of distinct indices with non-zero off-diagonal metric terms. while (changed) { changed = false; i = 0; while (i < indices.length) { j = i + 1; while (j < indices.length) { // check if indices differ; if so, see if their off-diagonal metric element contributes. if (indices[i] !== indices[j]) { m = this.metric[indices[i]][indices[j]]; if (m !== 0) { factor *= m; // remove the pair because their metric factor has been factored out. this.array_remove_pair(indices, i, j); changed = true; break; } else { j += 1; } } else { j += 1; } } i += 1; } } changed = true; // second pass: process duplicate indices using the corresponding diagonal metric factors. while (changed) { changed = false; i = 0; while (i < indices.length) { j = i + 1; while (j < indices.length) { // when two identical indices are found, multiply by the square from the metric. if (indices[i] === indices[j]) { m = this.metric[indices[i]][indices[i]]; factor *= m; // remove the duplicate pair to simplify the blade. this.array_remove_pair(indices, i, j); changed = true; break; } else { j += 1; } } i += 1; } } return [indices, factor]; }; coeffs = {}; for (l = 0, len = a.length; l < len; l++) { [id_a, coeff_a, grade_a] = a[l]; indices_a = this.id_bit_indices(id_a); for (o = 0, len1 = b.length; o < len1; o++) { [id_b, coeff_b, grade_b] = b[o]; indices_b = this.id_bit_indices(id_b); combined = indices_a.concat(indices_b); sign = this.sign(combined); [merged, m_factor] = merge_indices(combined.slice()); final_coeff = coeff_a * coeff_b * sign * m_factor; if (!final_coeff) { continue; } this.coeffs_add(coeffs, this.id_from_bit_indices(merged), final_coeff, merged.length); } } return this.coeffs_to_mv(coeffs); } ep_one(a, b) { var coeff_a, coeff_b, coeffs, grade_a, grade_b, id, id_a, id_b, indices_a, l, len, len1, o, sign; coeffs = {}; for (l = 0, len = a.length; l < len; l++) { [id_a, coeff_a, grade_a] = a[l]; indices_a = this.id_bit_indices(id_a); for (o = 0, len1 = b.length; o < len1; o++) { [id_b, coeff_b, grade_b] = b[o]; if (!(!(id_a & id_b))) { continue; } id = id_a | id_b; if (!id) { continue; } sign = id_b ? this.sign_sorted(indices_a, this.id_bit_indices(id_b)) : 1; this.coeffs_add(coeffs, id, sign * coeff_a * coeff_b, grade_a + grade_b); } } return this.coeffs_to_mv(coeffs); } combine(a, b, scalar = 1) { var c, coeff, coeffs, grade, id, l, len, len1, o; coeffs = {}; for (l = 0, len = a.length; l < len; l++) { [id, coeff, grade] = a[l]; coeffs[id] = [coeff, grade]; } for (o = 0, len1 = b.length; o < len1; o++) { [id, coeff, grade] = b[o]; if (coeffs[id] != null) { coeffs[id][0] += coeff * scalar; } else { coeffs[id] = [coeff * scalar, grade]; } } c = (function() { var results; results = []; for (id in coeffs) { [coeff, grade] = coeffs[id]; if (coeff !== 0) { results.push([parseInt(id), coeff, grade]); } } return results; })(); if (c.length) { return c; } else { return this.null_scalar; } } inverse(a) { var a_reverse, coeff, denom, denom_mv, grade, id, l, len, len1, o, results; a_reverse = this.reverse(a); denom_mv = this.gp(a, a_reverse); denom = 0; for (l = 0, len = denom_mv.length; l < len; l++) { [id, coeff, grade] = denom_mv[l]; if (id === 0) { denom = coeff; break; } } if (denom === 0) { throw new Error("multivector is not invertible (denominator is zero)."); } results = []; for (o = 0, len1 = a_reverse.length; o < len1; o++) { [id, coeff, grade] = a_reverse[o]; results.push([parseInt(id), coeff / denom, grade]); } return results; } blade_to_string(a) { var base, coeff, grade, id; [id, coeff, grade] = a; if (id) { base = "e" + this.id_bit_indices(id).map(function(b) { return b + 1; }).join("_"); if (1 === coeff) { return base; } else if (-1 === coeff) { return `-${base}`; } else { return coeff + base; } } else { return coeff; } } blade_from_string(a) { var coeff, id, indices, left_number, letters, match, n, result, right_numbers; match = a.match(/^(?:(\d+(?:\.\d+)?))?([a-z]+)?(?:([\d_]+))?$/); if (!match) { return null; } left_number = match[1] != null ? parseFloat(match[1]) : null; letters = match[2] != null ? match[2] : null; right_numbers = match[3] != null ? (function() { var l, len, ref, results; ref = match[3].split("_"); results = []; for (l = 0, len = ref.length; l < len; l++) { n = ref[l]; results.push(parseInt(n)); } return results; })() : null; result = []; coeff = left_number != null ? left_number : 1; indices = right_numbers != null ? right_numbers : []; if (letters != null) { switch (letters) { case "no": id = c3.no_id; break; case "ni": id = c3.ni_id; break; default: id = this.id_from_indices(right_numbers); } } else { id = 0; } return [id, coeff, this.id_grade(id)]; } mv_from_string(a) { var b, l, len, ref, results; ref = a.split(" + "); results = []; for (l = 0, len = ref.length; l < len; l++) { b = ref[l]; results.push(this.blade_from_string(b)); } return results; } }; sph_ga.prototype.id_bit_indices_cache = {}; sph_ga.prototype.id_grade_cache = {}; sph_ga.prototype.null_scalar = [[0, 0, 0]]; return sph_ga; }).call(this); if (typeof module !== "undefined" && module.exports) { module.exports = sph_ga; } else { window.sph_ga = sph_ga; }