* Adds associate, conj, multiply, negate, subtract, indistinguishable * As a result equal is now supported * Adds a check for recursive loops in resolve (a key/signature method depending on itself
This commit is contained in:
parent
8da23a84be
commit
7daa621571
12 changed files with 182 additions and 21 deletions
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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)))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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')
|
||||
|
|
20
src/complex/helpers.js
Normal file
20
src/complex/helpers.js
Normal file
|
@ -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)))
|
||||
})
|
24
src/complex/relational.js
Normal file
24
src/complex/relational.js
Normal file
|
@ -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)
|
||||
})
|
||||
})
|
||||
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue