Using the parameterized GeoCAS.Multivector class to perform Geometric Algebra operations with an arbitrary metric over an arbitrary field.
Copyright (c) 2016 David Geo Holmes.
function wedge(a: number[], b: number[]): number[] { | |
const result: number[] = []; | |
const aLen = a.length; | |
const bLen = b.length; | |
for (let i = 0; i < aLen; i++) { | |
result.push(a[i]); | |
} | |
for (let i = 0; i < bLen; i++) { | |
result.push(b[i]); | |
} | |
return result; | |
} | |
interface Metric { | |
(u: BasisBladeExpr, v: BasisBladeExpr): ScalarExpr; | |
} | |
export class Expr { | |
constructor(public g: Metric, public type: string) { | |
} | |
isChanged(): boolean { | |
throw new Error(`${this.type}.isChanged is not implemented.`); | |
} | |
copy(dirty: boolean): Expr { | |
throw new Error(`${this.type}.copy is not implemented.`); | |
} | |
reset(): Expr { | |
throw new Error(`${this.type}.reset is not implemented.`); | |
} | |
simplify(): Expr { | |
throw new Error(`${this.type}.simplify is not implemented.`); | |
} | |
toPrefix(): string { | |
throw new Error(`${this.type}.toPrefix is not implemented.`); | |
} | |
toString(): string { | |
throw new Error(`${this.type}.toString is not implemented.`); | |
} | |
__add__(rhs: Expr | number): Expr { | |
if (rhs instanceof Expr) { | |
return new AddExpr(this, rhs); | |
} | |
else if (typeof rhs === 'number') { | |
return new AddExpr(this, new ScalarExpr(this.g, rhs)); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__radd__(lhs: Expr | number): Expr { | |
if (lhs instanceof Expr) { | |
return new AddExpr(lhs, this); | |
} | |
else if (typeof lhs === 'number') { | |
return new AddExpr(new ScalarExpr(this.g, lhs), this); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__mul__(rhs: number | Expr): Expr { | |
if (rhs instanceof Expr) { | |
return new MulExpr(this, rhs); | |
} | |
else if (typeof rhs === 'number') { | |
return new MulExpr(this, new ScalarExpr(this.g, rhs)); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__rmul__(lhs: Expr | number): Expr { | |
if (lhs instanceof Expr) { | |
return new MulExpr(lhs, this); | |
} | |
else if (typeof lhs === 'number') { | |
return new MulExpr(new ScalarExpr(this.g, lhs), this); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__vbar__(rhs: Expr | number): Expr { | |
if (rhs instanceof Expr) { | |
return new VBarExpr(this, rhs); | |
} | |
else if (typeof rhs === 'number') { | |
return new VBarExpr(this, new ScalarExpr(this.g, rhs)); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__rvbar__(lhs: Expr | number): Expr { | |
if (lhs instanceof Expr) { | |
return new VBarExpr(lhs, this); | |
} | |
else if (typeof lhs === 'number') { | |
return new VBarExpr(new ScalarExpr(this.g, lhs), this); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__wedge__(rhs: Expr | number): Expr { | |
if (rhs instanceof Expr) { | |
return new WedgeExpr(this, rhs); | |
} | |
else if (typeof rhs === 'number') { | |
return new WedgeExpr(this, new ScalarExpr(this.g, rhs)); | |
} | |
else { | |
return void 0; | |
} | |
} | |
__rwedge__(lhs: Expr | number): Expr { | |
if (lhs instanceof Expr) { | |
return new VBarExpr(lhs, this); | |
} | |
else if (typeof lhs === 'number') { | |
return new VBarExpr(new ScalarExpr(this.g, lhs), this); | |
} | |
else { | |
return void 0; | |
} | |
} | |
} | |
export class BinaryExpr extends Expr { | |
constructor(public lhs: Expr, public rhs: Expr, type: string) { | |
super(lhs.g, type); | |
if (!(lhs instanceof Expr)) { | |
throw new Error(`${type}.lhs must be an Expr: ${typeof lhs}`); | |
} | |
if (!(rhs instanceof Expr)) { | |
throw new Error(`${type}.rhs must be an Expr: ${typeof rhs}`); | |
} | |
} | |
} | |
export class AddExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'AddExpr'); | |
} | |
isChanged(): boolean { | |
return this.dirty || this.lhs.isChanged() || this.rhs.isChanged(); | |
} | |
copy(dirty: boolean): Expr { | |
return new AddExpr(this.lhs, this.rhs, dirty); | |
} | |
reset(): Expr { | |
return new AddExpr(this.lhs.reset(), this.rhs.reset()); | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (b instanceof ScalarExpr && typeof b.value === 'number' && b.value === 0) { | |
return a.copy(true); | |
} | |
else if (a instanceof MulExpr && b instanceof MulExpr) { | |
if (a.lhs instanceof ScalarExpr && b.lhs instanceof ScalarExpr && a.rhs === b.rhs) { | |
const sa: ScalarExpr = <ScalarExpr>a.lhs; | |
const sb: ScalarExpr = <ScalarExpr>b.lhs; | |
if (typeof sa.value === 'number' && typeof sb.value === 'number') { | |
const s = new ScalarExpr(this.g, <number>sa.value + <number>sb.value); | |
return new MulExpr(s, a.rhs, true); | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
else if (a instanceof ScalarExpr && b instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && typeof b.value === 'number') { | |
return new ScalarExpr(this.g, <number>a.value + <number>b.value, true); | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
toPrefix(): string { | |
return `+(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} + ${this.rhs}`; | |
} | |
} | |
export class MulExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'MultiplyExpr'); | |
} | |
isChanged(): boolean { | |
return this.dirty || this.lhs.isChanged() || this.rhs.isChanged(); | |
} | |
copy(dirty: boolean): Expr { | |
return new MulExpr(this.lhs, this.rhs, dirty); | |
} | |
reset(): Expr { | |
return new MulExpr(this.lhs.reset(), this.rhs.reset()); | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && a.value === 0) { | |
return a.copy(true); | |
} | |
else if (typeof a.value === 'number' && a.value === 1) { | |
return b.copy(true); | |
} | |
else if (b instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && a.value === 1) { | |
return b; | |
} | |
else if (typeof a.value === 'number' && typeof b.value === 'number') { | |
return new ScalarExpr(this.g, <number>a.value * <number>b.value, true); | |
} | |
else if (typeof a.value !== 'number' && typeof b.value === 'number') { | |
return new MulExpr(b, a, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (a instanceof BasisBladeExpr) { | |
if (b instanceof MulExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
if (bL instanceof BasisBladeExpr) { | |
if (a.vectors[0] === bL.vectors[0]) { | |
return new MulExpr(new MulExpr(a, bL), bR, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (b instanceof BasisBladeExpr) { | |
if (a === b) { | |
return this.g(a, b); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (b instanceof ScalarExpr) { | |
return new MulExpr(b, a, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
toPrefix() { | |
return `*(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} * ${this.rhs}`; | |
} | |
} | |
/** | |
* A blade is the outer (wedge) product of a list of vectors. | |
* An empty list of vectors corresponds to the unit scalar. | |
*/ | |
export class BasisBladeExpr extends Expr { | |
constructor(g: Metric, public vectors: number[], public dirty = false) { | |
super(g, 'BasisBladeExpr'); | |
} | |
isChanged(): boolean { | |
return this.dirty; | |
} | |
copy(dirty: boolean): Expr { | |
return new BasisBladeExpr(this.g, this.vectors, dirty); | |
} | |
reset(): Expr { | |
if (this.dirty) { | |
return this.copy(false); | |
} | |
else { | |
return this; | |
} | |
} | |
simplify(): Expr { | |
// console.log(`${this.type}, ${this.dirty}`) | |
return this; | |
} | |
toPrefix(): string { | |
if (this.vectors.length > 0) { | |
return this.vectors.map((i) => `e${i + 1}`).join(' ^ '); | |
} | |
else { | |
return "1"; | |
} | |
} | |
toString(): string { | |
if (this.vectors.length > 0) { | |
return this.vectors.map((i) => `e${i + 1}`).join(' ^ '); | |
} | |
else { | |
return "1"; | |
} | |
} | |
} | |
export class ScalarExpr extends Expr { | |
constructor(g: Metric, public value: number | string, public dirty = false) { | |
super(g, 'ScalarExpr'); | |
} | |
isChanged(): boolean { | |
return false; | |
} | |
copy(dirty: boolean): Expr { | |
return new ScalarExpr(this.g, this.value, dirty); | |
} | |
reset(): Expr { | |
if (this.dirty) { | |
return this.copy(false); | |
} | |
else { | |
return this; | |
} | |
} | |
simplify(): Expr { | |
return this; | |
} | |
toPrefix(): string { | |
return `${this.value}`; | |
} | |
toPrefixLong(): string { | |
return `ScalarExpr('${this.value}')`; | |
} | |
toString(): string { | |
return `${this.value}`; | |
} | |
} | |
export class VBarExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'VBarExpr'); | |
} | |
isChanged(): boolean { | |
return this.dirty || this.lhs.isChanged() || this.rhs.isChanged(); | |
} | |
reset(): Expr { | |
return new VBarExpr(this.lhs.reset(), this.rhs.reset()); | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof BasisBladeExpr && b instanceof BasisBladeExpr) { | |
return this.g(a, b); | |
} | |
else if (a instanceof AddExpr && b instanceof BasisBladeExpr) { | |
const aL = a.lhs; | |
const aR = a.rhs; | |
return new AddExpr(new VBarExpr(aL, b), new VBarExpr(aR, b), true); | |
} | |
else if (a instanceof BasisBladeExpr && b instanceof AddExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
return new AddExpr(new VBarExpr(a, bL), new VBarExpr(a, bR), true); | |
} | |
else if (a instanceof MulExpr && b instanceof Expr) { | |
const aL = a.lhs; | |
const aR = a.rhs; | |
if (aL instanceof ScalarExpr && aR instanceof BasisBladeExpr) { | |
return new MulExpr(aL, new VBarExpr(aR, b), true); | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
else if (a instanceof BasisBladeExpr && b instanceof MulExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
if (bL instanceof ScalarExpr && bR instanceof BasisBladeExpr) { | |
return new MulExpr(bL, new VBarExpr(a, bR), true); | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
toPrefix(): string { | |
return `scp(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} | ${this.rhs}`; | |
} | |
} | |
export class WedgeExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'WedgeExpr'); | |
} | |
isChanged(): boolean { | |
return this.dirty || this.lhs.isChanged() || this.rhs.isChanged(); | |
} | |
reset(): Expr { | |
return new WedgeExpr(this.lhs.reset(), this.rhs.reset()); | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof ScalarExpr) { | |
if (b instanceof ScalarExpr) { | |
return new ScalarExpr(this.g, 0, true); | |
} | |
else if (typeof a.value === 'number' && a.value === 1) { | |
// return new WedgeExpr(a, b); | |
// return a.copy(true) | |
return b.copy(true); | |
} | |
else { | |
return new WedgeExpr(a, b); | |
} | |
} | |
else if (b instanceof ScalarExpr) { | |
if (a instanceof ScalarExpr) { | |
return new ScalarExpr(this.g, 0, true); | |
} | |
else if (typeof b.value === 'number' && b.value === 1) { | |
if (a.hasOwnProperty('copy')) { | |
return a.copy(true); | |
} | |
else { | |
if (a instanceof BasisBladeExpr) { | |
console.log(`a ${a.type} does not have copy property (I) ${Object.keys(a)}`); | |
// return new BasisBladeExpr(this.g, a.vectors, true); | |
return new WedgeExpr(a, b); | |
} | |
else { | |
console.log(`a ${a.type} does not have copy property (II)`); | |
return new WedgeExpr(a, b); | |
} | |
} | |
// return a.copy(true) | |
// return a.copy(true); | |
} | |
else { | |
return new WedgeExpr(a, b); | |
} | |
} | |
else if (a instanceof BasisBladeExpr && b instanceof BasisBladeExpr) { | |
return new BasisBladeExpr(this.g, wedge(a.vectors, b.vectors), true); | |
} | |
else if (a instanceof BasisBladeExpr && b instanceof BasisBladeExpr) { | |
if (a === b) { | |
return new ScalarExpr(this.g, 0, true); | |
} | |
else { | |
return new AddExpr(new MulExpr(a, b), new MulExpr(new ScalarExpr(this.g, -1), new VBarExpr(a, b)), true); | |
} | |
} | |
else { | |
return new WedgeExpr(a, b); | |
} | |
} | |
toPrefix() { | |
return `^(${this.lhs}, ${this.rhs})`; | |
} | |
toString() { | |
return `${this.lhs} ^ ${this.rhs}`; | |
} | |
} | |
export default class Algebra { | |
basis: { [index: number]: Expr } = {}; | |
index: { [name: string]: number } = {}; | |
private metric: Metric; | |
constructor(names: string[], g: number[][]) { | |
this.metric = (u: BasisBladeExpr, v: BasisBladeExpr): ScalarExpr => { | |
const i = u.vectors[0]; | |
const j = v.vectors[0]; | |
return new ScalarExpr(this.metric, g[i][j]); | |
}; | |
// Insert the basis blade corresponding to unity. | |
this.basis[0] = new BasisBladeExpr(this.metric, []); | |
this.index['1'] = 0; | |
// Insert the basis blades corresponding to the basis vectors. | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
const index = Math.pow(2, i); | |
this.basis[index] = new BasisBladeExpr(this.metric, [i]); | |
this.index[name] = index; | |
} | |
} | |
scalar(value: number | string): Expr { | |
return new ScalarExpr(this.metric, value); | |
} | |
simplify(expr: Expr): Expr { | |
if (expr instanceof Expr) { | |
debugger; | |
expr = expr.reset(); | |
expr = expr.simplify(); | |
while (expr.isChanged()) { | |
console.log("simplify") | |
expr = expr.simplify(); | |
} | |
return expr; | |
} | |
else { | |
throw new Error("expr must be an Expr"); | |
} | |
} | |
} |
import GeoCAS from './GeoCAS'; | |
export default function() { | |
const ga = new GeoCAS(['i','j', 'k'], [[1,0,0],[0,1,0],[0,0,1]]); | |
const s5 = ga.scalar(5); | |
const e1 = ga.basis[0] | |
const e2 = ga.basis[1] | |
const e3 = ga.basis[2] | |
describe("scalar", function() { | |
it("toPrefix should be correct", function() { | |
expect(s5.toPrefix()).toBe("5") | |
}) | |
it("toString should be correct", function() { | |
expect(s5.toString()).toBe("5") | |
}) | |
}) | |
describe("basis", function() { | |
describe("vector", function() { | |
it("toPrefix should be correct", function() { | |
expect(e1.toPrefix()).toBe("i") | |
expect(e2.toPrefix()).toBe("j") | |
expect(e3.toPrefix()).toBe("k") | |
}) | |
it("toString should be defined", function() { | |
expect(e1.toString()).toBe("i") | |
expect(e2.toString()).toBe("j") | |
expect(e3.toString()).toBe("k") | |
}) | |
}) | |
}) | |
describe("+", function() { | |
describe("(vector, vector)", function() { | |
const M = e1 + e2 | |
it("toPrefix should be defined", function() { | |
expect(M.toPrefix()).toBe("+(i, j)") | |
}) | |
it("toString should be defined", function() { | |
expect(M.toString()).toBe("i + j") | |
}) | |
}) | |
}) | |
describe("^", function() { | |
const M = e1 ^ e2 | |
it("toPrefix should be defined", function() { | |
expect(M.toPrefix()).toBe("^(i, j)") | |
}) | |
it("toString should be defined", function() { | |
expect(M.toString()).toBe("i ^ j") | |
}) | |
}) | |
describe("*", function() { | |
describe("(scalar, vector)", function() { | |
const M = 5 * e1 | |
it("toPrefix should be defined", function() { | |
expect(M.toPrefix()).toBe("*(5, i)") | |
}) | |
it("toString should be defined", function() { | |
expect(M.toString()).toBe("5 * i") | |
}) | |
}) | |
describe("(vector, vector)", function() { | |
const M = e1 * e2 | |
it("toPrefix should be defined", function() { | |
expect(M.toPrefix()).toBe("*(i, j)") | |
}) | |
it("toString should be defined", function() { | |
expect(M.toString()).toBe("i * j") | |
}) | |
}) | |
}) | |
} |
interface Metric { | |
(u: VectorExpr, v: VectorExpr): ScalarExpr | |
} | |
export class Expr { | |
constructor(public g: Metric, public type: string) { | |
} | |
get changed(): boolean { | |
throw new Error(`${this.type}.changed is not implemented.`) | |
} | |
copy(dirty: boolean): Expr { | |
throw new Error(`${this.type}.copy is not implemented.`) | |
} | |
reset(): Expr { | |
throw new Error(`${this.type}.reset is not implemented.`) | |
} | |
simplify(): Expr { | |
throw new Error(`${this.type}.simplify is not implemented.`) | |
} | |
toPrefix(): string { | |
throw new Error(`${this.type}.toPrefix is not implemented.`) | |
} | |
toString(): string { | |
throw new Error(`${this.type}.toString is not implemented.`) | |
} | |
__add__(rhs: Expr) { | |
throw new Error(`${this.type}.__add__ is not implemented.`) | |
} | |
__radd__(rhs: Expr) { | |
throw new Error(`${this.type}.__radd__ is not implemented.`) | |
} | |
__mul__(rhs: Expr) { | |
throw new Error(`${this.type}.__mul__ is not implemented.`) | |
} | |
__rmul__(rhs: Expr) { | |
throw new Error(`${this.type}.__rmul__ is not implemented.`) | |
} | |
__vbar__(rhs: Expr) { | |
throw new Error(`${this.type}.__vbar__ is not implemented.`) | |
} | |
__rvbar__(rhs: Expr) { | |
throw new Error(`${this.type}.__rvbar__ is not implemented.`) | |
} | |
} | |
class UnaryExpr extends Expr { | |
constructor(g: Metric, type: string) { | |
super(g, type); | |
} | |
__add__(rhs: Expr| number): Expr { | |
if (rhs instanceof Expr) { | |
return new AddExpr(this, rhs) | |
} | |
else if (typeof rhs === 'number') { | |
return new AddExpr(this, new ScalarExpr(this.g, rhs)) | |
} | |
else { | |
return void 0 | |
} | |
} | |
__radd__(lhs: Expr) { | |
if (lhs instanceof Expr) { | |
return new AddExpr(lhs, this) | |
} | |
else { | |
return void 0; | |
} | |
} | |
} | |
class BinaryExpr extends Expr { | |
constructor(public lhs: Expr, public rhs: Expr, type: string) { | |
super(lhs.g, type); | |
if (!(lhs instanceof Expr)) { | |
throw new Error(`${type}.lhs must be an Expr: ${typeof lhs}`); | |
} | |
if (!(rhs instanceof Expr)) { | |
throw new Error(`${type}.rhs must be an Expr: ${typeof rhs}`); | |
} | |
} | |
__add__(rhs: Expr): Expr { | |
if (rhs instanceof Expr) { | |
return new AddExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__radd__(lhs: Expr | number) { | |
if (lhs instanceof Expr) { | |
return new AddExpr(lhs, this) | |
} | |
else if (typeof lhs === 'number') { | |
return new AddExpr(new ScalarExpr(this.g, lhs), this) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__mul__(rhs: Expr | number) { | |
if (rhs instanceof Expr) { | |
return new MulExpr(this, rhs) | |
} | |
else if (typeof rhs === 'number') { | |
return new MulExpr(this, new ScalarExpr(this.g, rhs)) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__rmul__(lhs: Expr | number) { | |
if (lhs instanceof Expr) { | |
return new MulExpr(lhs, this) | |
} | |
else if (typeof lhs === 'number') { | |
return new MulExpr(new ScalarExpr(this.g, lhs), this) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__vbar__(rhs: Expr) { | |
if (rhs instanceof Expr) { | |
return new VBarExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
} | |
class AddExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'AddExpr'); | |
} | |
__add__(rhs: Expr) { | |
if (rhs instanceof Expr) { | |
return new AddExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__vbar__(rhs: Expr) { | |
if (rhs instanceof Expr) { | |
return new VBarExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
get changed(): boolean { | |
return this.dirty || this.lhs.changed || this.rhs.changed; | |
} | |
copy(dirty: boolean): Expr { | |
return new AddExpr(this.lhs, this.rhs, dirty); | |
} | |
reset(): Expr { | |
return new AddExpr(this.lhs.reset(), this.rhs.reset()) | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (b instanceof ScalarExpr && typeof b.value === 'number' && b.value === 0) { | |
return a.copy(true); | |
} | |
else if (a instanceof MulExpr && b instanceof MulExpr) { | |
if (a.lhs instanceof ScalarExpr && b.lhs instanceof ScalarExpr && a.rhs === b.rhs) { | |
const sa: ScalarExpr = <ScalarExpr>a.lhs; | |
const sb: ScalarExpr = <ScalarExpr>b.lhs; | |
const s = new ScalarExpr(this.g, sa.value + sb.value); | |
return new MulExpr(s, a.rhs, true); | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
else if (a instanceof ScalarExpr && b instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && typeof b.value === 'number') { | |
return new ScalarExpr(this.g, a.value + b.value, true); | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
else { | |
return new AddExpr(a, b); | |
} | |
} | |
toPrefix(): string { | |
return `+(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} + ${this.rhs}`; | |
} | |
} | |
class MulExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'MultiplyExpr'); | |
} | |
get changed(): boolean { | |
return this.dirty || this.lhs.changed || this.rhs.changed; | |
} | |
copy(dirty: boolean): Expr { | |
return new MulExpr(this.lhs, this.rhs, dirty); | |
} | |
reset(): Expr { | |
return new MulExpr(this.lhs.reset(), this.rhs.reset()) | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && a.value === 0) { | |
return a.copy(true); | |
} | |
else if (typeof a.value === 'number' && a.value === 1) { | |
return b.copy(true); | |
} | |
else if (b instanceof ScalarExpr) { | |
if (typeof a.value === 'number' && a.value === 1) { | |
return b; | |
} | |
else if (typeof a.value === 'number' && typeof b.value === 'number') { | |
return new ScalarExpr(this.g, a.value * b.value, true); | |
} | |
else if (typeof a.value !== 'number' && typeof b.value === 'number') { | |
return new MulExpr(b, a, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (a instanceof VectorExpr) { | |
if (b instanceof MulExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
if (bL instanceof VectorExpr) { | |
if (a.name === bL.name) { | |
return new MulExpr(new MulExpr(a, bL), bR, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (b instanceof VectorExpr) { | |
if (a === b) { | |
return this.g(a, b); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else if (b instanceof ScalarExpr) { | |
return new MulExpr(b, a, true); | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
else { | |
return new MulExpr(a, b); | |
} | |
} | |
toPrefix() { | |
return `*(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} * ${this.rhs}`; | |
} | |
} | |
class ScalarExpr extends UnaryExpr { | |
constructor(g: Metric, public value: number | string, public dirty = false) { | |
super(g, 'ScalarExpr'); | |
} | |
get changed(): boolean { | |
return false; | |
} | |
copy(dirty: boolean): Expr { | |
return new ScalarExpr(this.g, this.value, dirty); | |
} | |
reset(): Expr { | |
if (this.dirty) { | |
return new ScalarExpr(this.g, this.value); | |
} | |
else { | |
return this; | |
} | |
} | |
simplify(): Expr { | |
return this; | |
} | |
toPrefix(): string { | |
return `${this.value}`; | |
} | |
toPrefixLong(): string { | |
return `ScalarExpr('${this.value}')`; | |
} | |
toString(): string { | |
return `${this.value}`; | |
} | |
__mul__(rhs: Expr| number): Expr { | |
if (rhs instanceof Expr) { | |
return new MulExpr(this, rhs) | |
} | |
else if (typeof rhs === 'number') { | |
return new MulExpr(this, new ScalarExpr(this.g, rhs)) | |
} | |
else { | |
return void 0 | |
} | |
} | |
__rmul__(lhs: Expr| number): Expr { | |
if (lhs instanceof Expr) { | |
return new MulExpr(lhs, this) | |
} | |
else if (typeof lhs === 'number') { | |
return new MulExpr(new ScalarExpr(this.g, lhs), this) | |
} | |
else { | |
return void 0 | |
} | |
} | |
} | |
class VBarExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'VBarExpr'); | |
} | |
get changed(): boolean { | |
return this.dirty || this.lhs.changed || this.rhs.changed; | |
} | |
reset(): Expr { | |
return new VBarExpr(this.lhs.reset(), this.rhs.reset()) | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof VectorExpr && b instanceof VectorExpr) { | |
return this.g(a, b); | |
} | |
else if (a instanceof AddExpr && b instanceof VectorExpr) { | |
const aL = a.lhs; | |
const aR = a.rhs; | |
return new AddExpr(new VBarExpr(aL, b), new VBarExpr(aR, b), true); | |
} | |
else if (a instanceof VectorExpr && b instanceof AddExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
return new AddExpr(new VBarExpr(a, bL), new VBarExpr(a, bR), true); | |
} | |
else if (a instanceof MulExpr && b instanceof Expr) { | |
const aL = a.lhs; | |
const aR = a.rhs; | |
if (aL instanceof ScalarExpr && aR instanceof VectorExpr) { | |
return new MulExpr(aL, new VBarExpr(aR, b), true); | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
else if (a instanceof VectorExpr && b instanceof MulExpr) { | |
const bL = b.lhs; | |
const bR = b.rhs; | |
if (bL instanceof ScalarExpr && bR instanceof VectorExpr) { | |
return new MulExpr(bL, new VBarExpr(a, bR), true); | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
else { | |
return new VBarExpr(a, b); | |
} | |
} | |
toPrefix(): string { | |
return `scp(${this.lhs.toPrefix()}, ${this.rhs.toPrefix()})`; | |
} | |
toString() { | |
return `${this.lhs} | ${this.rhs}`; | |
} | |
} | |
class VectorExpr extends UnaryExpr { | |
constructor(g: Metric, public name: string, public dirty = false) { | |
super(g, 'VectorExpr'); | |
} | |
__mul__(rhs: number | Expr) { | |
if (rhs instanceof Expr) { | |
return new MulExpr(this, rhs) | |
} | |
else if (typeof rhs === 'number') { | |
return new MulExpr(this, new ScalarExpr(this.g, rhs)) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__rmul__(lhs: number | Expr) { | |
if (lhs instanceof Expr) { | |
return new MulExpr(lhs, this) | |
} | |
else if (typeof lhs === 'number') { | |
return new MulExpr(new ScalarExpr(this.g, lhs), this) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__vbar__(rhs: Expr) { | |
if (rhs instanceof Expr) { | |
return new VBarExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
__wedge__(rhs: Expr) { | |
if (rhs instanceof Expr) { | |
return new WedgeExpr(this, rhs) | |
} | |
else { | |
return void 0; | |
} | |
} | |
get changed(): boolean { | |
return false; | |
} | |
copy(dirty: boolean): Expr { | |
return new VectorExpr(this.g, this.name, dirty); | |
} | |
reset(): Expr { | |
return this; | |
} | |
simplify(): Expr { | |
return this; | |
} | |
toPrefix() { | |
return `${this.name}`; | |
} | |
toPrefixLong() { | |
return `VectorExpr('${this.name}')`; | |
} | |
toString() { | |
return this.name; | |
} | |
} | |
class WedgeExpr extends BinaryExpr { | |
constructor(lhs: Expr, rhs: Expr, public dirty = false) { | |
super(lhs, rhs, 'WedgeExpr'); | |
} | |
get changed(): boolean { | |
return this.dirty || this.lhs.changed || this.rhs.changed; | |
} | |
reset(): Expr { | |
return new WedgeExpr(this.lhs.reset(), this.rhs.reset()) | |
} | |
simplify(): Expr { | |
const a = this.lhs.simplify(); | |
const b = this.rhs.simplify(); | |
if (a instanceof VectorExpr && b instanceof VectorExpr) { | |
if (a === b) { | |
return new ScalarExpr(this.g, 0, true); | |
} | |
else { | |
return new AddExpr(new MulExpr(a, b), new MulExpr(new ScalarExpr(this.g, -1), new VBarExpr(a, b)), true); | |
} | |
} | |
else { | |
return new WedgeExpr(a, b); | |
} | |
} | |
toPrefix() { | |
return `^(${this.lhs}, ${this.rhs})`; | |
} | |
toString() { | |
return `${this.lhs} ^ ${this.rhs}`; | |
} | |
} | |
export default class GeoCAS { | |
basis: {[index: number]: Expr} = {}; | |
index: {[name: string]: number} = {}; | |
private metric: Metric; | |
constructor(names: string[], g: number[][]) { | |
this.metric = (u: VectorExpr, v: VectorExpr): ScalarExpr => { | |
const i = this.index[u.name]; | |
const j = this.index[v.name]; | |
return new ScalarExpr(this.metric, g[i][j]); | |
} | |
for (let i = 0; i < names.length; i++) { | |
const name = names[i]; | |
this.basis[i] = new VectorExpr(this.metric, name); | |
this.index[name] = i; | |
} | |
} | |
scalar(value: number | string): Expr { | |
return new ScalarExpr(this.metric, value); | |
} | |
simplify(expr: Expr): Expr { | |
if (expr instanceof Expr) { | |
expr = expr.reset(); | |
expr = expr.simplify(); | |
while(expr.changed) { | |
expr = expr.simplify(); | |
} | |
return expr; | |
} | |
else { | |
throw new Error("expr must be an Expr") | |
} | |
} | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<!-- STYLES-MARKER --> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<script src='https://jspm.io/system.js'></script> | |
<!-- SHADERS-MARKER --> | |
<!-- SCRIPTS-MARKER --> | |
</head> | |
<body> | |
<pre id='info'></pre> | |
<script> | |
// CODE-MARKER | |
</script> | |
<script> | |
System.import('./index.js') | |
</script> | |
</body> | |
</html> |
import UnitFieldAdapter from './UnitFieldAdapter'; | |
// const fa = new GeoCAS.NumberFieldAdapter(); | |
const fa = new UnitFieldAdapter(); | |
const diag = [1, -1]; | |
const ex = GeoCAS.getBasisVector(0, diag, fa) | |
const ey = GeoCAS.getBasisVector(1, diag, fa) | |
// It should not allow me to go beyond the metric. | |
const ez = GeoCAS.getBasisVector(2, diag, fa) | |
const I = ex + ex * ey | |
println(`I => '${I}'`) | |
println(`ez => '${ez}'`) | |
println(`ex * ex => '${(ex * ex)}'`) | |
println(`ey * ey => '${(ey * ey)}'`) | |
println(`ex | ex => '${(ex | ex)}'`) | |
println(`${(ex ^ ey).extractGrade(2)}`) | |
println(`${ex.scale(UNITS.Unit.METER.scale(5))}`) | |
/** | |
* Print the HTML string without a line ending. | |
*/ | |
export function print(html: string): void { | |
const element = document.getElementById('info'); | |
element.innerHTML = element.innerHTML + html; | |
} | |
/** | |
* Print the HTML string and go to the next line. | |
*/ | |
export function println(html: string): void { | |
print(html + '\n'); | |
} | |
/** | |
* Print the value of a variable, coerced to string | |
*/ | |
export function printvar(name: string, value): void { | |
println('<b>' + name + '</b> => ' + value); | |
} |
/** | |
* The Multivector<T> type has a parameter - the field. | |
* In this case we use ordinary JavaScript number as the field. | |
* We could use a unit of measure type, or something else. | |
*/ | |
import UnitFieldAdapter from './UnitFieldAdapter'; | |
// const fa = new UnitFieldAdapter(); | |
const fa = new GeoCAS.NumberFieldAdapter(); | |
/** | |
* The metric parameter may be either a number, number[], or Metric<T>. | |
* number is used to specify the dimensionality of a Euclidean vector space. | |
* number[] is used to specify a diagonal matrix, which may be non Euclidean. | |
* Matrix<T> is used for more general metrics. | |
*/ | |
const diag = [1, 1, 1]; | |
export const e1 = GeoCAS.getBasisVector(0, diag, fa); | |
export const e2 = GeoCAS.getBasisVector(1, diag, fa); | |
export const e3 = GeoCAS.getBasisVector(2, diag, fa); | |
{ | |
"description": "GeoCAS Multivector", | |
"dependencies": { | |
"DomReady": "1.0.0", | |
"jasmine": "2.4.1", | |
"geocas": "1.1.0", | |
"davinci-units": "1.4.0" | |
}, | |
"name": "geocas-multivector", | |
"version": "0.1.0", | |
"operatorOverloading": true, | |
"keywords": [ | |
"CAS", | |
"Geometric", | |
"Algebra", | |
"Symbolic", | |
"Multivector" | |
], | |
"author": "David Geo Holmes" | |
} |
body { | |
background-color: #000000; | |
} | |
#info { | |
position: absolute; | |
left: 20px; | |
top: 20px; | |
font-size: 26px; | |
color: #00FF00; | |
} |
<!DOCTYPE html> | |
<html> | |
<head> | |
<!-- STYLES-MARKER --> | |
<style> | |
/* STYLE-MARKER */ | |
</style> | |
<script src='https://jspm.io/system.js'></script> | |
<!-- SCRIPTS-MARKER --> | |
</head> | |
<body> | |
<script> | |
// CODE-MARKER | |
</script> | |
<script> | |
System.import('./tests.js') | |
</script> | |
</body> | |
</html> |
import GeoCAS from './GeoCAS.spec' | |
window['jasmine'] = jasmineRequire.core(jasmineRequire) | |
jasmineRequire.html(window['jasmine']) | |
const env = jasmine.getEnv() | |
const jasmineInterface = jasmineRequire.interface(window['jasmine'], env) | |
extend(window, jasmineInterface) | |
const htmlReporter = new jasmine.HtmlReporter({ | |
env: env, | |
getContainer: function() { return document.body }, | |
createElement: function() { return document.createElement.apply(document, arguments) }, | |
createTextNode: function() { return document.createTextNode.apply(document, arguments) }, | |
timer: new jasmine.Timer() | |
}) | |
env.addReporter(htmlReporter) | |
DomReady.ready(function() { | |
htmlReporter.initialize() | |
describe("GeoCAS", GeoCAS) | |
env.execute() | |
}) | |
/* | |
* Helper function for extending the properties on objects. | |
*/ | |
export default function extend<T>(destination: T, source: any): T { | |
for (let property in source) { | |
destination[property] = source[property] | |
} | |
return destination | |
} |
export default class UnitFieldAdapter implements GeoCAS.FieldAdapter<UNITS.Unit> { | |
add(lhs: UNITS.Unit, rhs: UNITS.Unit): UNITS.Unit { | |
return lhs.add(rhs); | |
} | |
sub(lhs: UNITS.Unit, rhs: UNITS.Unit): UNITS.Unit { | |
return lhs.sub(rhs); | |
} | |
mul(lhs: UNITS.Unit, rhs: UNITS.Unit): UNITS.Unit { | |
return lhs.mul(rhs); | |
} | |
div(lhs: UNITS.Unit, rhs: UNITS.Unit): UNITS.Unit { | |
return lhs.div(rhs); | |
} | |
neg(arg: UNITS.Unit): UNITS.Unit { | |
return arg.neg(); | |
} | |
asString(arg: UNITS.Unit): string { | |
return arg.toString(); | |
} | |
cos(arg: UNITS.Unit): UNITS.Unit { | |
if (arg.dimensions.isOne()) { | |
return UNITS.Unit.ONE.scale(Math.cos(arg.multiplier)); | |
} | |
else { | |
throw new Error("arg must be dimensionless"); | |
} | |
} | |
isField(arg: UNITS.Unit): arg is UNITS.Unit { | |
return arg instanceof UNITS.Unit; | |
} | |
isZero(arg: UNITS.Unit): boolean { | |
return arg.isZero(); | |
} | |
one(): UNITS.Unit { | |
return UNITS.Unit.ONE; | |
} | |
scale(arg: UNITS.Unit, multiplier: number): UNITS.Unit { | |
return arg.scale(multiplier); | |
} | |
sin(arg: UNITS.Unit): UNITS.Unit { | |
if (arg.dimensions.isOne()) { | |
return UNITS.Unit.ONE.scale(Math.sin(arg.multiplier)); | |
} | |
else { | |
throw new Error("arg must be dimensionless"); | |
} | |
} | |
sqrt(arg: UNITS.Unit): UNITS.Unit { | |
// TODO: arg must be dimensionless | |
return arg.sqrt(); | |
} | |
zero(): UNITS.Unit { | |
return UNITS.Unit.ZERO; | |
} | |
} |