diff --git a/src/boolean/BooleanT.js b/src/boolean/BooleanT.js index f2792ed..6f4ede6 100644 --- a/src/boolean/BooleanT.js +++ b/src/boolean/BooleanT.js @@ -1,6 +1,7 @@ import {Type} from '#core/Type.js' export const BooleanT = new Type(n => typeof n === 'boolean', { + typeName: 'BooleanT', zero: false, one: true }) diff --git a/src/complex/__test__/arithmetic.spec.js b/src/complex/__test__/arithmetic.spec.js index 02dd56b..d72b335 100644 --- a/src/complex/__test__/arithmetic.spec.js +++ b/src/complex/__test__/arithmetic.spec.js @@ -1,5 +1,7 @@ import assert from 'assert' import math from '#nanomath' +import {Complex} from '../Complex.js' +import {NumberT} from '#number/NumberT.js' const cplx = math.complex @@ -11,16 +13,51 @@ describe('complex arithmetic operations', () => { }) it('adds complex numbers', () => { const z = cplx(3, 4) - assert.deepStrictEqual(math.add(z, cplx(-1, 1)), cplx(2, 5)) - assert.deepStrictEqual(math.add(z, cplx(true, false)), cplx(4, 4)) - assert.deepStrictEqual(math.add(cplx(false, true), z), cplx(3, 5)) + const add = math.add + assert.deepStrictEqual(add(z, cplx(-1, 1)), cplx(2, 5)) + assert.deepStrictEqual(add(z, cplx(true, false)), cplx(4, 4)) + assert.deepStrictEqual(add(cplx(false, true), z), cplx(3, 5)) assert.deepStrictEqual( - math.add(cplx(z, z), cplx(cplx(0.5, 0.5), z)), + add(cplx(z, z), cplx(cplx(0.5, 0.5), z)), cplx(cplx(3.5, 4.5), cplx(6, 8))) - assert.deepStrictEqual(math.add(z, 5), cplx(8, 4)) - assert.deepStrictEqual(math.add(true, z), cplx(4, 4)) - assert.deepStrictEqual(math.add(cplx(z,z), 10), cplx(cplx(13, 4), z)) + assert.deepStrictEqual(add(z, 5), cplx(8, 4)) + assert.deepStrictEqual(add(true, z), cplx(4, 4)) + assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z)) assert.deepStrictEqual( - math.add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4))) + add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4))) + }) + it('conjugates complex numbers', () => { + const conj = math.conj + const z = cplx(3, 4) + assert.deepStrictEqual(conj(z), cplx(3, -4)) + assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4))) + }) + it('multiplies complex numbers', () => { + const mult = math.multiply + const z = cplx(3, 4) + assert.deepStrictEqual(mult(z, z), cplx(-7, 24)) + assert(math.equal(mult(z, math.conj(z)), 25)) + const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT))) + const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75)) + assert.deepStrictEqual( + mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25))) + assert.deepStrictEqual( + mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))), + cplx(cplx(1.9, 2.1), cplx(1.1, -0.9))) + }) + it('subtracts complex numbers', () => { + const z = cplx(3, 4) + const sub = math.subtract + assert.deepStrictEqual(sub(z, cplx(-1, 1)), cplx(4, 3)) + assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4)) + assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3)) + assert.deepStrictEqual( + sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), + cplx(cplx(2.5, 3.5), cplx(0, 0))) + assert.deepStrictEqual(sub(z, 5), cplx(-2, 4)) + assert.deepStrictEqual(sub(true, z), cplx(-2, -4)) + assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z)) + assert.deepStrictEqual( + sub(cplx(z,z), cplx(10,20)), cplx(cplx(-7, 4), cplx(-17, 4))) }) }) diff --git a/src/complex/__test__/type.spec.js b/src/complex/__test__/type.spec.js index a0c42cf..b85797f 100644 --- a/src/complex/__test__/type.spec.js +++ b/src/complex/__test__/type.spec.js @@ -15,4 +15,21 @@ describe('complex type operations', () => { assert.strictEqual(math.arg(cplx(1, Math.sqrt(3))), Math.PI/3) assert.strictEqual(math.arg(cplx(true, true)), Math.PI/4) }) + it('detects associates of a complex number', () => { + const z = cplx(3, 4) + const assoc = math.associate + assert(assoc(z, z)) + assert(assoc(z, cplx(-3, -4))) + assert(assoc(z, cplx(-4, 3))) + assert(assoc(z, cplx(4, -3))) + assert(!assoc(z, math.conj(z))) + const b = cplx(true, true) + assert(assoc(b, cplx(-1, 1))) + assert(assoc(cplx(1, 1), b)) + assert(assoc(cplx(-1, -1), b)) + assert(assoc(cplx(1, -1), b)) + assert(assoc(cplx(-1, 1), b)) + assert(!assoc(b, cplx(false, true))) + assert(!assoc(cplx(0, 1), b)) + }) }) diff --git a/src/complex/all.js b/src/complex/all.js index 5c261d4..8ccff17 100644 --- a/src/complex/all.js +++ b/src/complex/all.js @@ -1,3 +1,4 @@ export * as typeDefinition from './Complex.js' export * as arithmetic from './arithmetic.js' +export * as relational from './relational.js' export * as type from './type.js' diff --git a/src/complex/arithmetic.js b/src/complex/arithmetic.js index 75a91ea..70fafae 100644 --- a/src/complex/arithmetic.js +++ b/src/complex/arithmetic.js @@ -1,17 +1,44 @@ import {Complex} from './Complex.js' +import {promoteBinary, promoteUnary} from './helpers.js' +import {ResolutionError} from '#core/helpers.js' import {match} from '#core/TypePatterns.js' import {ReturnsAs} from '#generic/helpers.js' export const absquare = match(Complex, (math, C) => { - const compAbsq = math.absquare.resolve([C.Component]) - const R = compAbsq.returns - const add = math.add.resolve([R,R]) - return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im))) + const compAbsq = math.absquare.resolve([C.Component]) + const R = compAbsq.returns + const add = math.add.resolve([R,R]) + return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im))) }) -export const add = match([Complex, Complex], (math, [C, D]) => { - const addComps = math.add.resolve([C.Component, D.Component]) - const cplx = math.complex.resolve([addComps.returns, addComps.returns]) - return ReturnsAs( - cplx, (w,z) => cplx(addComps(w.re, z.re), addComps(w.im, z.im))) +export const add = promoteBinary('add') + +export const conj = match(Complex, (math, C) => { + const neg = math.negate.resolve(C.Component) + const compConj = math.conj.resolve(C.Component) + const cplx = math.complex.resolve([compConj.returns, neg.returns]) + return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im))) }) + +// We want this to work for complex numbers, quaternions, octonions, etc +// See https://math.ucr.edu/home/baez/octonions/node5.html +export const multiply = match([Complex, Complex], (math, [W, Z]) => { + const conj = math.conj.resolve(W.Component) + if (conj.returns !== W.Component) { + throw new ResolutionError( + `conjugation on ${W.Component} returns other type (${conj.returns})`) + } + const mWZ = math.multiply.resolve([W.Component, Z.Component]) + const mZW = math.multiply.resolve([Z.Component, W.Component]) + const sub = math.subtract.resolve([mWZ.returns, mZW.returns]) + const add = math.add.resolve([mWZ.returns, mZW.returns]) + const cplx = math.complex.resolve([sub.returns, add.returns]) + return ReturnsAs(cplx, (w, z) => { + const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im))) + const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im)) + return cplx(real, imag) + }) +}) + +export const negate = promoteUnary('negate') +export const subtract = promoteBinary('subtract') diff --git a/src/complex/helpers.js b/src/complex/helpers.js new file mode 100644 index 0000000..84e828f --- /dev/null +++ b/src/complex/helpers.js @@ -0,0 +1,20 @@ +import {Complex} from './Complex.js' +import {match} from '#core/TypePatterns.js' +import {ReturnsAs} from '#generic/helpers.js' + +export const promoteUnary = name => match( + Complex, + (math, C) => { + const compOp = math.resolve(name, C.Component) + const cplx = math.complex.resolve([compOp.returns, compOp.returns]) + return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im))) + }) + +export const promoteBinary = name => match( + [Complex, Complex], + (math, [W, Z]) => { + const compOp = math.resolve(name, [W.Component, Z.Component]) + const cplx = math.complex.resolve([compOp.returns, compOp.returns]) + return ReturnsAs( + cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im))) + }) diff --git a/src/complex/relational.js b/src/complex/relational.js new file mode 100644 index 0000000..4d6a8af --- /dev/null +++ b/src/complex/relational.js @@ -0,0 +1,24 @@ +import {Complex} from './Complex.js' +import {BooleanT} from '#boolean/BooleanT.js' +import {Returns} from '#core/Type.js' +import {match, Any, Optional} from '#core/TypePatterns.js' + +export const indistinguishable = match( + [Complex, Complex, Optional([Any, Any])], + (math, [W, Z, T]) => { + let WComp = W.Component + let ZComp = Z.Component + if (T.length === 0) { // no tolerances + const same = math.indistinguishable.resolve([WComp, ZComp]) + return Returns( + BooleanT, (w, z) => same(w.re, z.re) && same(w.im, z.im)) + } + const [RT, AT] = T + const same = math.indistinguishable.resolve([WComp, ZComp, RT, AT]) + return Returns( + BooleanT, + (w, z, [rT, aT]) => { + return same(w.re, z.re, rT, aT) && same(w.im, z.im, rT, aT) + }) + }) + diff --git a/src/complex/type.js b/src/complex/type.js index 6c0f410..fbe7d8e 100644 --- a/src/complex/type.js +++ b/src/complex/type.js @@ -1,6 +1,7 @@ import {Complex} from './Complex.js' import {Returns} from "#core/Type.js" import {Any, match} from "#core/TypePatterns.js" +import {BooleanT} from '#boolean/BooleanT.js' import {NumberT} from '#number/NumberT.js' export const complex = [ @@ -28,3 +29,24 @@ export const complex = [ export const arg = match( Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re))) + +/* Returns true if w is z multiplied by a complex unit */ +export const associate = match([Complex, Complex], (math, [W, Z]) => { + if (Z.Component.complex) { + throw new Error( + `The group of units of type ${Z} is not yet implemented`) + } + const eq = math.equal.resolve([W, Z]) + const neg = math.negate.resolve(Z) + const eqN = math.equal.resolve([W, neg.returns]) + const mult = math.multiply.resolve([Z, Z]) + const eqM = math.equal.resolve([W, mult.returns]) + const negM = math.negate.resolve(mult.returns) + const eqNM = math.equal.resolve([W, negM.returns]) + const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component)) + return Returns(BooleanT, (w, z) => { + if (eq(w, z) || eqN(w, neg(z))) return true + const iz = mult(iZ, z) + return eqM(w, iz) || eqNM(w, negM(iz)) + }) +}) diff --git a/src/core/TypeDispatcher.js b/src/core/TypeDispatcher.js index 9eaa6da..2b997e6 100644 --- a/src/core/TypeDispatcher.js +++ b/src/core/TypeDispatcher.js @@ -8,6 +8,8 @@ import { matched, needsCollection, Passthru, Matcher, match } from './TypePatterns.js' +const underResolution = Symbol('underResolution') + export class TypeDispatcher { constructor(...specs) { this._implementations = {} // maps key to list of [pattern, result] pairs @@ -261,6 +263,7 @@ export class TypeDispatcher { if (!(key in this)) { throw new ReferenceError(`no method or value for key '${key}'`) } + if (!Array.isArray(types)) types = [types] const generatingDeps = this.resolve._genDepsOf?.length if (generatingDeps) this._addToDeps(key, types) @@ -269,6 +272,10 @@ export class TypeDispatcher { // Return the cached resolution if it's there if (behave.has(types)) { const result = behave.get(types) + if (result === underResolution) { + throw new ResolutionError( + `recursive resolution of ${key} on ${types}`) + } if (generatingDeps && typeof result === 'object' && !(result instanceof Type) @@ -328,6 +335,7 @@ export class TypeDispatcher { let theBehavior = () => undefined let finalBehavior this.resolve._genDepsOf.push([key, types]) + behave.set(types, underResolution) try { // Used to make sure not to return without popping _genDepsOf if (!('returns' in item)) { // looks like a factory diff --git a/src/generic/arithmetic.js b/src/generic/arithmetic.js index 7c7c327..cf67fdc 100644 --- a/src/generic/arithmetic.js +++ b/src/generic/arithmetic.js @@ -1,6 +1,7 @@ import {Returns} from '#core/Type.js' import {match, Any} from '#core/TypePatterns.js' +export const conj = match(Any, (_math, T) => Returns(T, a => a)) export const square = match(Any, (math, T) => { const mult = math.multiply.resolve([T, T]) return Returns(mult.returns, a => mult(a, a)) diff --git a/src/nanomath.js b/src/nanomath.js index 3038472..ddeeeba 100644 --- a/src/nanomath.js +++ b/src/nanomath.js @@ -10,9 +10,11 @@ import {TypeDispatcher} from '#core/TypeDispatcher.js' // the following list will be tried earlier. (The rationale for that // ordering is that any time you merge something, it should supersede // whatever has been merged before.) -// Hence, in building the math instance, we put complex first because -// we want its conversion (which converts _any_ non-complex type to -// complex, potentially making a poor overload choice) to be tried last. -const math = new TypeDispatcher(complex, generics, booleans, coretypes, numbers) +// Hence, in building the math instance, we put generics first because +// they should only kick in when there are not specific implementations, +// and complex next becausewe want its conversion (which converts _any_ +// non-complex type to complex, potentially making a poor overload choice) +// to be tried last. +const math = new TypeDispatcher(generics, complex, booleans, coretypes, numbers) export default math diff --git a/src/number/NumberT.js b/src/number/NumberT.js index b915b85..415a37d 100644 --- a/src/number/NumberT.js +++ b/src/number/NumberT.js @@ -4,6 +4,7 @@ import {BooleanT} from '#boolean/BooleanT.js' export const NumberT = new Type(n => typeof n === 'number', { from: match(BooleanT, math => math.number.resolve([BooleanT])), + typeName: 'NumberT', // since used before ever put in a TypeDispatcher one: 1, zero: 0, nan: NaN