feat: Add return typing strategies and implement sqrt with them (#26)
All checks were successful
/ test (push) Successful in 17s
All checks were successful
/ test (push) Successful in 17s
Resolves #25 Reviewed-on: #26 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
aad62df8ac
commit
0765ba7202
35 changed files with 1125 additions and 152 deletions
|
|
@ -10,4 +10,9 @@ describe('the numbers-only bundle', () => {
|
|||
assert.strictEqual(math.isnan(-16.5), 0)
|
||||
assert.strictEqual(math.isnan(NaN), 1)
|
||||
})
|
||||
it('takes sqrt with NaN for negative', () => {
|
||||
assert.strictEqual(math.sqrt(25), 5)
|
||||
assert(math.isnan(math.sqrt(-25)))
|
||||
assert(math.isnan(math.sqrt(NaN)))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export * as typeDefinition from './BooleanT.js'
|
||||
export * as type from './type.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {BooleanT} from './BooleanT.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
|
||||
import {Returns, TypeOfTypes, Undefined} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const bool = f => Returns(BooleanT, f)
|
||||
|
|
|
|||
7
src/boolean/utils.js
Normal file
7
src/boolean/utils.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import {BooleanT} from './BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const clone = match(BooleanT, Returns(BooleanT, p => p))
|
||||
export const isnan = match(BooleanT, Returns(BooleanT, () => false))
|
||||
export const isfinite = match(BooleanT, Returns(BooleanT, () => true))
|
||||
|
|
@ -11,7 +11,7 @@ function complexSpecialize(ComponentType) {
|
|||
const typeName = `Complex(${ComponentType})`
|
||||
if (ComponentType.concrete) {
|
||||
const fromSpec = [match(
|
||||
ComponentType, math => r => ({re: r, im: ComponentType.zero}))]
|
||||
ComponentType, () => r => ({re: r, im: ComponentType.zero}))]
|
||||
for (const {pattern, does} of ComponentType.from) {
|
||||
fromSpec.push(match(
|
||||
this.specialize(pattern.type),
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Complex} from '../Complex.js'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const cplx = math.complex
|
||||
const CplxNum = Complex(NumberT)
|
||||
const {full} = ReturnTyping
|
||||
|
||||
describe('complex arithmetic operations', () => {
|
||||
it('computes absquare of complex numbers', () => {
|
||||
|
|
@ -25,23 +28,42 @@ describe('complex arithmetic operations', () => {
|
|||
assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z))
|
||||
assert.deepStrictEqual(
|
||||
add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
|
||||
assert.strictEqual(add(z, math.conj(z)), 6)
|
||||
const addFull = math.add.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(addFull(z, math.conj(z)), cplx(6, 0))
|
||||
})
|
||||
it('adds real and complex numbers', () => {
|
||||
const z = cplx(3, 4)
|
||||
const zp3 = cplx(6,4)
|
||||
const add = math.add
|
||||
assert.deepStrictEqual(add(z, 3), zp3)
|
||||
assert.deepStrictEqual(add(3, z), zp3)
|
||||
assert.deepStrictEqual(add(3, cplx(z, z)), cplx(zp3, z))
|
||||
assert.deepStrictEqual(add(cplx(z, z), 3), cplx(zp3, z))
|
||||
})
|
||||
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)))
|
||||
const r = cplx(3, 0)
|
||||
assert.strictEqual(conj(r), 3)
|
||||
const conjFull = math.conj.resolve(CplxNum, full)
|
||||
assert.deepStrictEqual(conjFull(r), cplx(3, -0))
|
||||
})
|
||||
it('divides complex numbers', () => {
|
||||
const div = math.divide
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3))
|
||||
assert(math.equal(div(z, z), 1))
|
||||
assert.strictEqual(div(z, z), 1)
|
||||
const divFull = math.divide.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(divFull(z, z), math.one(CplxNum))
|
||||
// should probably have a quaternion example, but it's intricate
|
||||
})
|
||||
it('inverts complex numbers', () => {
|
||||
const inv = math.invert
|
||||
assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1))
|
||||
assert.strictEqual(inv(cplx(2, 0)), 0.5)
|
||||
assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
|
||||
assert.deepStrictEqual(
|
||||
inv(cplx(cplx(1, 2), cplx(4, 2))),
|
||||
|
|
@ -51,8 +73,10 @@ describe('complex arithmetic operations', () => {
|
|||
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)))
|
||||
assert.strictEqual(mult(z, math.conj(z)), 25)
|
||||
const multFull = math.multiply.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(multFull(z, math.conj(z)), cplx(25, 0))
|
||||
const q0 = cplx(cplx(1, 1), math.zero(CplxNum))
|
||||
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)))
|
||||
|
|
@ -60,6 +84,25 @@ describe('complex arithmetic operations', () => {
|
|||
mult(q0, cplx(cplx(2, 0.1), cplx(1, 0.1))),
|
||||
cplx(cplx(1.9, 2.1), cplx(1.1, -0.9)))
|
||||
})
|
||||
it('takes the square roots of complex numbers', () => {
|
||||
const {sqrt, multiply} = math
|
||||
const rhalf = Math.sqrt(1 / 2)
|
||||
assert.deepStrictEqual(sqrt(cplx(1, 0)), 1)
|
||||
assert.deepStrictEqual(sqrt(cplx(-1, 0)), cplx(0, 1))
|
||||
assert(math.equal(sqrt(cplx(0, 1)), cplx(rhalf, rhalf)))
|
||||
assert(math.equal(sqrt(cplx(0, -1)), cplx(rhalf, -rhalf)))
|
||||
assert.deepStrictEqual(sqrt(cplx(5, 12)), cplx(3, 2))
|
||||
const z = cplx(3, 4)
|
||||
const rz = sqrt(z)
|
||||
assert.deepStrictEqual(multiply(rz, rz), z)
|
||||
// quaternions, too:
|
||||
assert.deepStrictEqual(
|
||||
sqrt(cplx(cplx(-46, 20), cplx(12, 16))),
|
||||
cplx(cplx(2, 5), cplx(3, 4)))
|
||||
const q = cplx(z, z)
|
||||
const rq = sqrt(q)
|
||||
assert(math.equal(multiply(rq, rq), q))
|
||||
})
|
||||
it('subtracts complex numbers', () => {
|
||||
const z = cplx(3, 4)
|
||||
const sub = math.subtract
|
||||
|
|
@ -67,8 +110,15 @@ describe('complex arithmetic operations', () => {
|
|||
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)))
|
||||
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), cplx(2.5, 3.5))
|
||||
assert.strictEqual(
|
||||
sub(cplx(z, z), cplx(cplx(0, 4), z)), 3)
|
||||
const subFull = math.subtract.resolve([
|
||||
Complex(CplxNum), Complex(CplxNum)
|
||||
], full)
|
||||
assert.deepStrictEqual(
|
||||
subFull(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||
cplx(cplx(2.5, 3.5), math.zero(CplxNum)))
|
||||
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))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Complex} from '../Complex.js'
|
||||
import {OneOf, ReturnTyping} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
|
||||
const cplx = math.complex
|
||||
|
||||
|
|
@ -33,7 +37,21 @@ describe('complex type operations', () => {
|
|||
assert(!assoc(cplx(0, 1), b))
|
||||
})
|
||||
it('computes cis of an angle', () => {
|
||||
assert(math.equal(math.cis(0), 1))
|
||||
assert(math.equal(math.cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
|
||||
const cis = math.cis.resolve(NumberT)
|
||||
assert.strictEqual(cis.returns, OneOf(Complex(NumberT), NumberT))
|
||||
assert.strictEqual(cis(0), 1)
|
||||
assert.strictEqual(cis(Math.PI), -1)
|
||||
assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
|
||||
math.config.returnTyping = ReturnTyping.full
|
||||
const ccis = math.cis.resolve(NumberT)
|
||||
assert.strictEqual(ccis.returns, Complex(NumberT))
|
||||
const one = ccis(0)
|
||||
assert(one !== 1)
|
||||
assert(math.equal(one, 1))
|
||||
assert(math.equal(ccis(Math.PI), cplx(-1)))
|
||||
math.config.returnTyping = ReturnTyping.free
|
||||
assert.strictEqual(math.cis.resolve(NumberT), cis)
|
||||
assert.strictEqual(math.cis.resolve(NumberT, ReturnTyping.full), ccis)
|
||||
assert.strictEqual(math.cis(2*Math.PI), 1)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
51
src/complex/__test__/utils.spec.js
Normal file
51
src/complex/__test__/utils.spec.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
const cplx = math.complex
|
||||
|
||||
describe('complex utilities', () => {
|
||||
it('clones a complex', () => {
|
||||
const z = cplx(3, 4)
|
||||
const cz = math.clone(z)
|
||||
assert(cz !== z)
|
||||
assert.deepStrictEqual(z, cz)
|
||||
const q = cplx(z, math.add(z, 1))
|
||||
const cq = math.clone(q)
|
||||
assert(cq !== q)
|
||||
assert.deepStrictEqual(q, cq)
|
||||
assert(q.re !== cq.re && q.im !== cq.im)
|
||||
})
|
||||
it('checks for nan', () => {
|
||||
assert(math.isnan(cplx(NaN, NaN)))
|
||||
assert(!math.isnan(cplx(NaN, 6.28)))
|
||||
})
|
||||
it('tests for finiteness', () => {
|
||||
const fin = math.isfinite
|
||||
assert(fin(cplx(3, 4)))
|
||||
assert(!fin(cplx(2, Infinity)))
|
||||
assert(!fin(cplx(NaN, 0)))
|
||||
})
|
||||
it('identifies Gaussian integers', () => {
|
||||
const isInt = math.isInteger
|
||||
assert(isInt(cplx(3, 4)))
|
||||
assert(isInt(cplx(-37,0)))
|
||||
assert(!isInt(cplx(99, -1.000001)))
|
||||
})
|
||||
it('identifies real numbers', () => {
|
||||
const {isReal} = math
|
||||
assert(isReal(cplx(5, 0)))
|
||||
assert(isReal(cplx(5, -1e-17)))
|
||||
assert(!isReal(cplx(5, 0.000001)))
|
||||
assert(isReal(cplx(cplx(5, 1e-16), cplx(-0, 0))))
|
||||
assert(!isReal(cplx(cplx(5, 2), cplx(0, 0))))
|
||||
assert(!isReal(cplx(cplx(5, 0), cplx(0, 0.00002))))
|
||||
})
|
||||
it('identifies complex numbers that only have a real part', () => {
|
||||
const noImag = math.nonImaginary
|
||||
assert(noImag(cplx(5, 0)))
|
||||
assert(noImag(cplx(5, -1e-17)))
|
||||
assert(!noImag(cplx(5, 0.000001)))
|
||||
assert(noImag(cplx(cplx(5, 2), cplx(0, 0))))
|
||||
assert(!noImag(cplx(cplx(5, 0), cplx(0, 0.00002))))
|
||||
})
|
||||
})
|
||||
|
|
@ -1,43 +1,46 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {Returns, ReturnTyping} from '#core/Type.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 {conservative, full, free} = ReturnTyping
|
||||
|
||||
export const absquare = match(Complex, (math, C, strategy) => {
|
||||
const compAbsq = math.absquare.resolve(C.Component, full)
|
||||
const R = compAbsq.returns
|
||||
const add = math.add.resolve([R,R])
|
||||
const add = math.add.resolve([R,R], strategy)
|
||||
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(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])
|
||||
export const conj = match(Complex, (math, C, strategy) => {
|
||||
const neg = math.negate.resolve(C.Component, full)
|
||||
const compConj = math.conj.resolve(C.Component, full)
|
||||
const cplx = maybeComplex(math, strategy, compConj.returns, neg.returns)
|
||||
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
|
||||
})
|
||||
|
||||
export const divide = [
|
||||
match([Complex, T => !T.complex], (math, [C, R]) => {
|
||||
const div = math.divide.resolve([C.Component, R])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
|
||||
const div = math.divide.resolve([C.Component, R], full)
|
||||
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
|
||||
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
|
||||
}),
|
||||
match([Complex, Complex], (math, [W, Z]) => {
|
||||
const inv = math.invert.resolve(Z)
|
||||
const mult = math.multiply.resolve([W, inv.returns])
|
||||
match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const inv = math.invert.resolve(Z, full)
|
||||
const mult = math.multiply.resolve([W, inv.returns], strategy)
|
||||
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
|
||||
})
|
||||
]
|
||||
|
||||
export const invert = match(Complex, (math, C) => {
|
||||
const conj = math.conj.resolve(C)
|
||||
const norm = math.absquare.resolve(C)
|
||||
const div = math.divide.resolve([C.Component, norm.returns])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
export const invert = match(Complex, (math, C, strategy) => {
|
||||
const conj = math.conj.resolve(C, full)
|
||||
const norm = math.absquare.resolve(C, full)
|
||||
const div = math.divide.resolve([C.Component, norm.returns], full)
|
||||
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
|
||||
return ReturnsAs(cplx, z => {
|
||||
const c = conj(z)
|
||||
const d = norm(z)
|
||||
|
|
@ -47,23 +50,87 @@ export const invert = match(Complex, (math, C) => {
|
|||
|
||||
// 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 multiply = [
|
||||
match([T => !T.complex, Complex], (math, [R, C], strategy) => {
|
||||
const mult = math.multiply.resolve([R, C.Component], full)
|
||||
const cplx = maybeComplex(math, strategy, mult.returns, mult.returns)
|
||||
return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im)))
|
||||
}),
|
||||
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
|
||||
const mult = math.multiply.resolve([R, C], strategy)
|
||||
return ReturnsAs(mult, (z, r) => mult(r, z))
|
||||
}),
|
||||
match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const conj = math.conj.resolve(W.Component, full)
|
||||
if (conj.returns !== W.Component) {
|
||||
throw new ResolutionError(
|
||||
`conjugation on ${W.Component} returns type (${conj.returns})`)
|
||||
}
|
||||
const mWZ = math.multiply.resolve([W.Component, Z.Component], full)
|
||||
const mZW = math.multiply.resolve([Z.Component, W.Component], full)
|
||||
const sub = math.subtract.resolve([mWZ.returns, mZW.returns], full)
|
||||
const add = math.add.resolve([mWZ.returns, mZW.returns], full)
|
||||
const cplx = maybeComplex(math, strategy, 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')
|
||||
|
||||
// Should work for complex, quaternions, octonions, etc., even with
|
||||
// integer coordinates.
|
||||
export const sqrt = match(Complex, (math, C, strategy) => {
|
||||
const re = math.re.resolve(C)
|
||||
const R = re.returns
|
||||
const isReal = math.isReal.resolve(C)
|
||||
// dependencies for the real case:
|
||||
const zComp = math.zero(C.Component)
|
||||
const sign = math.sign.resolve(R, conservative)
|
||||
const oneR = math.one(R)
|
||||
const zR = math.zero(R)
|
||||
const addRComp = math.add.resolve([R, C.Component], full)
|
||||
const sqrtR = math.sqrt.resolve(R, conservative)
|
||||
const neg = math.negate.resolve(R, conservative)
|
||||
const cplx = math.complex.resolve([C.Component, C.Component], full)
|
||||
// additional dependencies for the complex case
|
||||
const abs = math.abs.resolve(C, full)
|
||||
if (abs.returns !== R) {
|
||||
throw new TypeError(`abs on ${C} returns ${abs.returns}, not ${R}`)
|
||||
}
|
||||
const addRR = math.add.resolve([R, R], conservative)
|
||||
const twoR = addRR(oneR, oneR)
|
||||
const multRR = math.multiply.resolve([R, R], conservative)
|
||||
const im = math.im.resolve(C, full)
|
||||
const addRC = math.add.resolve([R, C], full)
|
||||
const divRR = math.divide.resolve([R, R], conservative)
|
||||
const divCR = math.divide.resolve([C, R], full)
|
||||
|
||||
// The guts of the computation:
|
||||
const sqrtImp = Returns(C, z => {
|
||||
const a = re(z)
|
||||
if (isReal(z)) { // always a special case
|
||||
let rp = zComp
|
||||
let ip = zComp
|
||||
const sgn = sign(a)
|
||||
if (sgn === oneR) rp = addRComp(sqrtR(a), rp)
|
||||
else if (sgn !== zR) ip = addRComp(sqrtR(neg(a)), ip)
|
||||
return cplx(rp, ip)
|
||||
}
|
||||
// Complex case:
|
||||
// We can write z = a + q where q is pure imaginary.
|
||||
// Let s = sqrt(2(|z|+a)). Then sqrt(z) = (s/2) + (q/s).
|
||||
const s = sqrtR(multRR(twoR, addRR(abs(z), a)))
|
||||
const q = im(z)
|
||||
return addRC(divRR(s, twoR), divCR(q, s))
|
||||
})
|
||||
|
||||
if (strategy != free) return sqrtImp
|
||||
const prune = math.pruneImaginary.resolve(C, free)
|
||||
return ReturnsAs(prune, z => prune(sqrtImp(z)))
|
||||
})
|
||||
|
||||
export const subtract = promoteBinary('subtract')
|
||||
|
|
|
|||
|
|
@ -1,20 +1,38 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const promoteUnary = name => match(
|
||||
const {free, full} = ReturnTyping
|
||||
|
||||
export const maybeComplex = (math, strategy, Real, Imag) => {
|
||||
if (strategy !== free) return math.complex.resolve([Real, Imag], strategy)
|
||||
const cplx = math.complex.resolve([Real, Imag], full)
|
||||
const prune = math.pruneImaginary.resolve(cplx.returns, full)
|
||||
return ReturnsAs(prune, (r, m) => prune(cplx(r, m)))
|
||||
}
|
||||
|
||||
export const promoteUnary = (name, overrideStrategy) => match(
|
||||
Complex,
|
||||
(math, C) => {
|
||||
const compOp = math.resolve(name, C.Component)
|
||||
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||
(math, C, strategy) => {
|
||||
const compOp = math.resolve(name, C.Component, full)
|
||||
if (overrideStrategy) strategy = overrideStrategy
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||
})
|
||||
|
||||
export const promotePredicateAnd = name => match(
|
||||
Complex,
|
||||
(math, C, strategy) => {
|
||||
const compPred = math.resolve(name, C.Component, strategy)
|
||||
return ReturnsAs(compPred, z => compPred(z.re) && compPred(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])
|
||||
(math, [W, Z], strategy) => {
|
||||
const compOp = math.resolve(name, [W.Component, Z.Component], full)
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
return ReturnsAs(
|
||||
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {Returns} from "#core/Type.js"
|
||||
import {OneOf, Returns, ReturnTyping, TypeOfTypes} from "#core/Type.js"
|
||||
import {Any, match} from "#core/TypePatterns.js"
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const {free, full} = ReturnTyping
|
||||
|
||||
export const complex = [
|
||||
match(Any, (math, T) => {
|
||||
const z = math.zero(T)
|
||||
|
|
@ -27,29 +29,81 @@ export const complex = [
|
|||
})
|
||||
]
|
||||
|
||||
export const arg = match(
|
||||
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
|
||||
export const arg = // [ // enable when we have atan2 in mathjs
|
||||
// match(Complex, (math, C) => {
|
||||
// const re = math.re.resolve(C)
|
||||
// const R = re.returns
|
||||
// const im = math.im.resolve(C)
|
||||
// const abs = math.abs.resolve(C)
|
||||
// const atan2 = math.atan2.resolve([R, R], conservative)
|
||||
// return Returns(R, z => atan2(abs(im(z)), re(z)))
|
||||
// }), // note always between 0 and tau/2; need to use in conjunction
|
||||
// // with a complex unit function that gives you the proper
|
||||
// // imaginary unit, ±i in the simple complex case, to restore the
|
||||
// // full circle of values for the direction of a complex number
|
||||
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))
|
||||
})
|
||||
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], full)
|
||||
const neg = math.negate.resolve(Z, full)
|
||||
const eqN = math.equal.resolve([W, neg.returns], full)
|
||||
const mult = math.multiply.resolve([Z, Z], full)
|
||||
const eqM = math.equal.resolve([W, mult.returns], full)
|
||||
const negM = math.negate.resolve(mult.returns, full)
|
||||
const eqNM = math.equal.resolve([W, negM.returns], full)
|
||||
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))
|
||||
})
|
||||
})
|
||||
|
||||
const _cis = t => ({re: Math.cos(t), im: Math.sin(t)})
|
||||
|
||||
export const cis = match(NumberT, (math, _type, strategy) => {
|
||||
if (strategy === free) {
|
||||
const intTest = math.isInteger.resolve(NumberT)
|
||||
return Returns(OneOf(NumberT, Complex(NumberT)), t => {
|
||||
let halfCycles = t / Math.PI
|
||||
if (intTest(halfCycles)) {
|
||||
halfCycles = Math.round(halfCycles)
|
||||
return halfCycles % 2 ? -1 : 1
|
||||
}
|
||||
return _cis(t)
|
||||
})
|
||||
}
|
||||
return Returns(Complex(NumberT), _cis)
|
||||
})
|
||||
|
||||
export const cis = match(NumberT, Returns(Complex(NumberT), t => ({
|
||||
re: Math.cos(t), im: Math.sin(t)})))
|
||||
// Returns the real part if the complex number supplied has no imaginary
|
||||
// part, otherwise leaves that complex number alone.
|
||||
// Since it is explicitly asking for varying type, it ignores strategy.
|
||||
export const pruneImaginary = match(Complex, (math, C) => {
|
||||
const outcomes = [C]
|
||||
let T = C
|
||||
while (T.complex) {
|
||||
T = T.Component
|
||||
outcomes.push(T)
|
||||
}
|
||||
const noImag = math.nonImaginary.resolve(C, full)
|
||||
if (C.Component.complex) {
|
||||
const compPrune = math.pruneImaginary.resolve(C.Component)
|
||||
return Returns(OneOf(...outcomes), z => noImag(z) ? compPrune(z.re) : z)
|
||||
}
|
||||
return Returns(OneOf(...outcomes), z => noImag(z) ? z.re : z)
|
||||
})
|
||||
|
||||
// Returns the type of re(z)
|
||||
export const Real = match(TypeOfTypes, Returns(TypeOfTypes, t => {
|
||||
while (t.complex) t = t.Component
|
||||
return t
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,54 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {promotePredicateAnd, promoteUnary} from './helpers.js'
|
||||
|
||||
import {Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
const {full} = ReturnTyping
|
||||
|
||||
export const clone = promoteUnary('clone', full)
|
||||
export const isnan = promotePredicateAnd('isnan')
|
||||
export const isfinite = promotePredicateAnd('isfinite')
|
||||
// Note: the followig predicate returns true for all Gaussian integers, not
|
||||
// just so-called rational integers.
|
||||
export const isInteger = promotePredicateAnd('isInteger')
|
||||
|
||||
// true if the Complex is truly a real number (nonImaginary and its
|
||||
// real part is a real number)
|
||||
export const isReal = match(Complex, (math, C) => {
|
||||
const eq = math.equal.resolve([C.Component, C.Component])
|
||||
const add = math.add.resolve([C.Component, C.Component])
|
||||
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
|
||||
const nonImag = math.nonImaginary.resolve(C, full)
|
||||
const realComp = math.isReal.resolve(C.Component)
|
||||
return ReturnsAs(realComp, z => nonImag(z) && realComp(z.re))
|
||||
})
|
||||
|
||||
// true if the imaginary part of a Complex is negligible compared to the real
|
||||
export const nonImaginary = match(Complex, (math, C) => {
|
||||
const eq = math.equal.resolve([C.Component, C.Component])
|
||||
const add = math.add.resolve([C.Component, C.Component], full)
|
||||
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
|
||||
})
|
||||
|
||||
// Always returns the "true" real part of a complex number z (i.e., the part
|
||||
// that is actually in the real numbers mathematically). In other words,
|
||||
// performs z.re recursively until it gets something not Complex.
|
||||
export const re = match(Complex, (math, C) => {
|
||||
return Returns(math.Real(C), z => {
|
||||
let T = C
|
||||
while (T.complex) {
|
||||
T = T.Component
|
||||
z = z.re
|
||||
}
|
||||
return z
|
||||
})
|
||||
})
|
||||
|
||||
// Returns everything but the real part of z. NOTE this is a complex
|
||||
// number with zero real part, _not_ the coefficient of the imaginary
|
||||
// component of z. If you want the latter, just use `z.im`
|
||||
|
||||
export const im = match(Complex, (math, C) => {
|
||||
const imComp = math.im.resolve(C.Component, full)
|
||||
const cplx = math.complex.resolve([C.Component, C.Component], full)
|
||||
return ReturnsAs(cplx, z => cplx(imComp(z.re), z.im))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -66,9 +66,30 @@ export const Undefined = new Type(
|
|||
t => typeof t === 'undefined',
|
||||
{zero: undefined, one: undefined, nan: undefined})
|
||||
export const TypeOfTypes = new Type(t => t instanceof Type)
|
||||
export const NotAType = new Type(t => true) // Danger, do not merge!
|
||||
export const NotAType = new Type(() => true) // Danger, do not merge!
|
||||
NotAType._doNotMerge = true
|
||||
|
||||
const unionDirectory = new ArrayKeyedMap() // make sure only one of each union
|
||||
export const OneOf = (...types) => {
|
||||
const nonType = types.findIndex(T => !(T instanceof Type))
|
||||
if (nonType >= 0) {
|
||||
throw new RangeError(
|
||||
`OneOf can only take type arguments, not ${types[nonType]}`)
|
||||
}
|
||||
const typeSet = new Set(types) // remove duplicates
|
||||
const typeList = Array.from(typeSet).sort() // canonical order
|
||||
const generic = typeList.find(T => !T.concrete)
|
||||
if (generic) {
|
||||
throw new RangeError(`OneOf can only take concrete types, not ${generic}`)
|
||||
}
|
||||
if (!unionDirectory.has(typeList)) {
|
||||
unionDirectory.set(typeList, new Type(
|
||||
t => typeList.some(T => T.test(t)),
|
||||
{typeName: typeList.join('|')}))
|
||||
}
|
||||
return unionDirectory.get(typeList)
|
||||
}
|
||||
|
||||
export const Returns = (type, f) => (f.returns = type, f)
|
||||
|
||||
export const whichType = typs => Returns(TypeOfTypes, item => {
|
||||
|
|
@ -84,3 +105,17 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
|
|||
}
|
||||
throw new TypeError(errorMsg)
|
||||
})
|
||||
|
||||
// The return typing strategies
|
||||
// MAKE SURE NONE ARE FALSY, so that code can easily test whether a strategy
|
||||
// has been specified.
|
||||
export const ReturnTyping = Object.freeze({
|
||||
free: 1,
|
||||
conservative: 2,
|
||||
full: 3,
|
||||
name(strat) {
|
||||
for (const key in this) {
|
||||
if (this[key] === strat) return key
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import ArrayKeyedMap from 'array-keyed-map'
|
|||
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
|
||||
import {Implementations, ImplementationsGenerator} from './Implementations.js'
|
||||
import {bootstrapTypes} from './type.js'
|
||||
import {Returns, whichType, Type} from './Type.js'
|
||||
import {Returns, ReturnTyping, whichType, Type} from './Type.js'
|
||||
import {
|
||||
matched, needsCollection, Passthru, Matcher, match
|
||||
} from './TypePatterns.js'
|
||||
|
|
@ -155,7 +155,8 @@ export class TypeDispatcher {
|
|||
const types = args.map(thisTypeOf)
|
||||
return this.resolve(key, types)(...args)
|
||||
}
|
||||
standard.resolve = (types) => this.resolve(key, types)
|
||||
standard.resolve =
|
||||
(types, strategy) => this.resolve(key, types, strategy)
|
||||
standard.isDispatcher = true
|
||||
Object.defineProperty(this, key, {
|
||||
enumerable: true,
|
||||
|
|
@ -167,14 +168,18 @@ export class TypeDispatcher {
|
|||
|
||||
if (typeof tryValue === 'object') {
|
||||
if (!('resolve' in tryValue)) {
|
||||
tryValue.resolve = types => this.resolve(key, types)
|
||||
tryValue.resolve =
|
||||
(types, strat) => this.resolve(key, types, strat)
|
||||
}
|
||||
const get = () => {
|
||||
const keyDeps = this._dependencies.get(key)
|
||||
if (!keyDeps) return tryValue
|
||||
const watch = Array.from(keyDeps.keys().filter(
|
||||
types => types.length === 0
|
||||
|| this.resolve(key, types) === tryValue
|
||||
bhvix => {
|
||||
if (bhvix.length < 2) return true
|
||||
const types = bhvix.slice(1)
|
||||
return this.resolve(key, types) === tryValue
|
||||
}
|
||||
))
|
||||
if (watch.length) {
|
||||
return DependencyWatcher(tryValue, key, watch, this)
|
||||
|
|
@ -221,7 +226,7 @@ export class TypeDispatcher {
|
|||
}
|
||||
}
|
||||
|
||||
_addToDeps(key, types) {
|
||||
_addToDeps(key, bhvix) {
|
||||
// Never depend on internal methods:
|
||||
if (key.startsWith('_')) return
|
||||
let depMap = this._dependencies.get(key)
|
||||
|
|
@ -229,11 +234,11 @@ export class TypeDispatcher {
|
|||
depMap = new ArrayKeyedMap()
|
||||
this._dependencies.set(key, depMap)
|
||||
}
|
||||
if (!depMap.has(types)) depMap.set(types, new Map())
|
||||
const depColl = depMap.get(types)
|
||||
for (const [dkey, types] of this.resolve._genDepsOf) {
|
||||
if (!depMap.has(bhvix)) depMap.set(bhvix, new Map())
|
||||
const depColl = depMap.get(bhvix)
|
||||
for (const [dkey, bhvix] of this.resolve._genDepsOf) {
|
||||
if (!depColl.has(dkey)) depColl.set(dkey, new ArrayKeyedMap())
|
||||
depColl.get(dkey).set(types, true)
|
||||
depColl.get(dkey).set(bhvix, true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -259,28 +264,42 @@ export class TypeDispatcher {
|
|||
return args => extractors.map(f => f(args))
|
||||
}
|
||||
|
||||
resolve(key, types) {
|
||||
// key is the identifier of the method being looked up
|
||||
// types is a single argument type or an array of actual argument types
|
||||
// strategy is a ReturnTyping value specifying how to choose the
|
||||
// return type for the operation.
|
||||
resolve(key, types, strategy) {
|
||||
if (!(key in this)) {
|
||||
throw new ReferenceError(`no method or value for key '${key}'`)
|
||||
}
|
||||
if (!Array.isArray(types)) types = [types]
|
||||
if (!strategy) {
|
||||
// Avoid recursing on obtaining config
|
||||
if (key === 'config') strategy = ReturnTyping.free
|
||||
else strategy = this.config.returnTyping
|
||||
}
|
||||
// The "behavior index": the return type strategy followed by the
|
||||
// types:
|
||||
const bhvix = [strategy, ...types]
|
||||
|
||||
const generatingDeps = this.resolve._genDepsOf?.length
|
||||
if (generatingDeps) this._addToDeps(key, types)
|
||||
if (generatingDeps) this._addToDeps(key, bhvix)
|
||||
|
||||
const behave = this._behaviors[key]
|
||||
// Return the cached resolution if it's there
|
||||
if (behave.has(types)) {
|
||||
const result = behave.get(types)
|
||||
if (behave.has(bhvix)) {
|
||||
const result = behave.get(bhvix)
|
||||
if (result === underResolution) {
|
||||
throw new ResolutionError(
|
||||
`recursive resolution of ${key} on ${types}`)
|
||||
`recursive resolution of ${key} on ${types} with return typing `
|
||||
+ ReturnTyping.name(strategy)
|
||||
)
|
||||
}
|
||||
if (generatingDeps
|
||||
&& typeof result === 'object'
|
||||
&& !(result instanceof Type)
|
||||
) {
|
||||
return DependencyRecorder(result, key, this, types)
|
||||
return DependencyRecorder(result, key, this, bhvix)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
@ -315,12 +334,12 @@ export class TypeDispatcher {
|
|||
}
|
||||
// If this key is producing a non-function value, we're done
|
||||
if (!isPlainFunction(item)) {
|
||||
behave.set(types, item)
|
||||
behave.set(bhvix, item)
|
||||
if (generatingDeps
|
||||
&& typeof item === 'object'
|
||||
&& !(item instanceof Type)
|
||||
) {
|
||||
return DependencyRecorder(item, key, this, types)
|
||||
return DependencyRecorder(item, key, this, bhvix)
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
|
@ -334,17 +353,19 @@ export class TypeDispatcher {
|
|||
|
||||
let theBehavior = () => undefined
|
||||
let finalBehavior
|
||||
this.resolve._genDepsOf.push([key, types])
|
||||
behave.set(types, underResolution)
|
||||
this.resolve._genDepsOf.push([key, bhvix])
|
||||
behave.set(bhvix, underResolution)
|
||||
try { // Used to make sure not to return without popping _genDepsOf
|
||||
if (!('returns' in item)) {
|
||||
// looks like a factory
|
||||
try {
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, '', this, []),
|
||||
matched(template, this))
|
||||
matched(template, this),
|
||||
strategy)
|
||||
} catch (e) {
|
||||
e.message = `Error in factory for ${key} on ${types} `
|
||||
+ `with return typing ${ReturnTyping.name(strategy)} `
|
||||
+ `(match data ${template}): ${e.message}`
|
||||
throw e
|
||||
}
|
||||
|
|
@ -355,7 +376,8 @@ export class TypeDispatcher {
|
|||
const returning = theBehavior.returns
|
||||
if (!returning) {
|
||||
throw new TypeError(
|
||||
`No return type specified for ${key} on ${types}`)
|
||||
`No return type specified for ${key} on ${types} with`
|
||||
+ ` return typing ${ReturnTyping.name(strategy)}`)
|
||||
}
|
||||
if (needsCollection(template)) {
|
||||
// have to wrap the behavior to collect the actual arguments
|
||||
|
|
@ -369,7 +391,7 @@ export class TypeDispatcher {
|
|||
} finally {
|
||||
this.resolve._genDepsOf.pop() // OK, now it's safe to return
|
||||
}
|
||||
behave.set(types, finalBehavior)
|
||||
behave.set(bhvix, finalBehavior)
|
||||
finalBehavior.template = template
|
||||
return finalBehavior
|
||||
}
|
||||
|
|
@ -380,8 +402,8 @@ export class TypeDispatcher {
|
|||
_invalidate(depColl) {
|
||||
if (!depColl) return
|
||||
for (const [key, typeMap] of depColl) {
|
||||
for (const types of typeMap.keys()) {
|
||||
this._behaviors[key].delete(types)
|
||||
for (const bhvix of typeMap.keys()) {
|
||||
this._behaviors[key].delete(bhvix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -389,25 +411,26 @@ export class TypeDispatcher {
|
|||
_disengageFallback(key) {
|
||||
// We need to find all of the behaviors that currently rely on the
|
||||
// fallback, invalidate their dependencies, and remove them.
|
||||
const fallTypes = []
|
||||
const fallIxes = []
|
||||
const behs = this._behaviors[key]
|
||||
const imps = this._implementations[key]
|
||||
const deps = this._dependencies.get(key)
|
||||
for (const types of behs.keys()) {
|
||||
for (const bhvix of behs.keys()) {
|
||||
let fallsback = true
|
||||
for (const [pattern] of imps) {
|
||||
const types = bhvix.length ? bhvix.slice(1) : bhvix
|
||||
const [finalIndex] = pattern.match(types)
|
||||
if (finalIndex === types.length) {
|
||||
fallsback = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (fallsback) fallTypes.push(types)
|
||||
if (fallsback) fallIxes.push(bhvix)
|
||||
}
|
||||
for (const types of fallTypes) {
|
||||
const depColl = deps?.get(types)
|
||||
for (const bhvix of fallIxes) {
|
||||
const depColl = deps?.get(bhvix)
|
||||
if (depColl?.size) this._invalidate(depColl)
|
||||
behs.delete(types)
|
||||
behs.delete(bhvix)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -415,20 +438,21 @@ export class TypeDispatcher {
|
|||
// like disengageFallback, just we have the offending pattern:
|
||||
const behs = this._behaviors[key]
|
||||
const deps = this._dependencies.get(key)
|
||||
const patTypes = behs.keys().filter(types => {
|
||||
const patIxes = behs.keys().filter(bhvix => {
|
||||
const types = bhvix.length ? bhvix.slice(1) : bhvix
|
||||
const [finalIndex] = pattern.match(types)
|
||||
return finalIndex === types.length
|
||||
})
|
||||
for (const types of patTypes) {
|
||||
const depColl = deps?.get(types)
|
||||
for (const bhvix of patIxes) {
|
||||
const depColl = deps?.get(bhvix)
|
||||
if (depColl?.size) this._invalidate(depColl)
|
||||
behs.delete(types)
|
||||
behs.delete(bhvix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy that traps accesses and records dependencies on them
|
||||
const DependencyRecorder = (object, path, repo, types) => new Proxy(object, {
|
||||
const DependencyRecorder = (object, path, repo, bhvix) => new Proxy(object, {
|
||||
get(target, prop, receiver) {
|
||||
const result = Reflect.get(target, prop, receiver)
|
||||
// pass internal methods through, as well as resolve calls,
|
||||
|
|
@ -443,27 +467,27 @@ const DependencyRecorder = (object, path, repo, types) => new Proxy(object, {
|
|||
// OK, it's not a method on a TypeDispatcher, it's some other kind of
|
||||
// value. So first record the dependency on prop at this path.
|
||||
const newPath = path ? [path, prop].join('.') : prop
|
||||
repo._addToDeps(newPath, types)
|
||||
repo._addToDeps(newPath, bhvix)
|
||||
// Now, if the result is an object, we may need to record further
|
||||
// dependencies on its properties (e.g. math.config.predictable)
|
||||
// So proxy the return value, except for types, which must maintain
|
||||
// strict referential identity:
|
||||
if (typeof result === 'object' && !(result instanceof Type)) {
|
||||
return DependencyRecorder(result, newPath, repo, types)
|
||||
return DependencyRecorder(result, newPath, repo, bhvix)
|
||||
} else return result
|
||||
}
|
||||
})
|
||||
|
||||
// The flip side: proxy that traps setting properties and invalidates things
|
||||
// that depend on them:
|
||||
const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, {
|
||||
const DependencyWatcher = (object, path, ixList, repo) => new Proxy(object, {
|
||||
set(target, prop, value, receiver) {
|
||||
// First see if this setting has any dependencies:
|
||||
const newPath = [path, prop].join('.')
|
||||
const depPerTypes = repo._dependencies.get(newPath)
|
||||
if (depPerTypes && Reflect.get(target, prop, receiver) !== value) {
|
||||
for (const types of typesList) {
|
||||
repo._invalidate(depPerTypes.get(types))
|
||||
for (const bhvix of ixList) {
|
||||
repo._invalidate(depPerTypes.get(bhvix))
|
||||
}
|
||||
}
|
||||
// Now we can just perform the setting
|
||||
|
|
@ -474,7 +498,7 @@ const DependencyWatcher = (object, path, typesList, repo) => new Proxy(object, {
|
|||
const result = Reflect.get(target, prop, receiver)
|
||||
if (typeof result === 'object' && !(result instanceof Type)) {
|
||||
const newPath = [path, prop].join('.')
|
||||
return DependencyWatcher(result, newPath, typesList, repo)
|
||||
return DependencyWatcher(result, newPath, ixList, repo)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {Type, Undefined} from './Type.js'
|
|||
import {isPlainFunction} from './helpers.js'
|
||||
|
||||
export class TypePattern {
|
||||
match(typeSequence, options={}) {
|
||||
match(_typeSequence, _options={}) {
|
||||
throw new Error('Specific TypePatterns must implement match')
|
||||
}
|
||||
sampleTypes() {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import assert from 'assert'
|
|||
import math from '#nanomath'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
import {Returns} from '../Type.js'
|
||||
import {Returns, ReturnTyping} from '../Type.js'
|
||||
import {isPlainFunction} from '../helpers.js'
|
||||
|
||||
describe('Core types', () => {
|
||||
|
|
@ -40,4 +40,7 @@ describe('Core types', () => {
|
|||
assert(isPlainFunction(labeledF))
|
||||
})
|
||||
|
||||
it('provides return typing strategies', () => {
|
||||
assert.strictEqual(ReturnTyping.name(ReturnTyping.full), 'full')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import * as numbers from '#number/all.js'
|
|||
import {NumberT} from '#number/NumberT.js'
|
||||
import {ResolutionError} from "#core/helpers.js"
|
||||
import {match, Any} from "#core/TypePatterns.js"
|
||||
import {Returns, NotAType} from "#core/Type.js"
|
||||
import {NotAType, Returns, ReturnTyping} from "#core/Type.js"
|
||||
import {plain} from "#number/helpers.js"
|
||||
|
||||
describe('TypeDispatcher', () => {
|
||||
|
|
@ -57,12 +57,15 @@ describe('TypeDispatcher', () => {
|
|||
it('detects dependencies on conversion operations', () => {
|
||||
const bgn = new TypeDispatcher(booleans, generics, numbers)
|
||||
const {BooleanT, NumberT} = bgn.types
|
||||
assert(!bgn._behaviors.negate.has([BooleanT]))
|
||||
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
|
||||
assert.strictEqual(bgn.negate(true), -1)
|
||||
assert(bgn._behaviors.negate.has([BooleanT]))
|
||||
const deps = bgn._dependencies.negate
|
||||
assert(bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
|
||||
const deps = bgn._dependencies
|
||||
.get('number')
|
||||
.get([ReturnTyping.free, BooleanT])
|
||||
assert(deps.has('negate'))
|
||||
bgn.merge({number: match([BooleanT], Returns(NumberT, b => b ? 2 : 0))})
|
||||
assert(!bgn._behaviors.negate.has([BooleanT]))
|
||||
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
|
||||
assert.strictEqual(bgn.negate(true), -2)
|
||||
})
|
||||
it('disallows merging NotAType', () => {
|
||||
|
|
|
|||
11
src/core/config.js
Normal file
11
src/core/config.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import {ImplementationsGenerator} from './Implementations.js'
|
||||
import {ReturnTyping} from './Type.js'
|
||||
import {match, Passthru} from './TypePatterns.js'
|
||||
|
||||
export const config = new ImplementationsGenerator(() => match(Passthru, {
|
||||
// default comparison tolerances:
|
||||
relTol: 1e-12,
|
||||
absTol: 1e-15,
|
||||
// Strategy for choosing operation return types:
|
||||
returnTyping: ReturnTyping.free,
|
||||
}))
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import {config} from './config.js'
|
||||
import {ImplementationsGenerator} from './Implementations.js'
|
||||
import {Type, TypeOfTypes, Undefined, whichType} from './Type.js'
|
||||
import {match, Passthru} from './TypePatterns.js'
|
||||
|
|
@ -22,5 +23,5 @@ export const types = new ImplementationsGenerator(() => match(Passthru, {}))
|
|||
// an explicitly ordered export of implementations for this sake:
|
||||
|
||||
export const bootstrapTypes = {
|
||||
types, Type, Undefined, TypeOfTypes, typeOf
|
||||
types, config, Type, Undefined, TypeOfTypes, typeOf
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,18 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
|
||||
const {Complex, NumberT} = math.types
|
||||
|
||||
describe('generic arithmetic', () => {
|
||||
it('squares anything', () => {
|
||||
assert.strictEqual(math.square(7), 49)
|
||||
assert.strictEqual(
|
||||
math.square.resolve([math.types.NumberT]).returns,
|
||||
math.types.NumberT)
|
||||
const sq = math.square
|
||||
assert.strictEqual(sq(7), 49)
|
||||
assert.strictEqual(math.square.resolve([NumberT]).returns, NumberT)
|
||||
assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24))
|
||||
const eyes = math.complex(0, 2)
|
||||
assert.strictEqual(sq(eyes), -4)
|
||||
const sqFull = math.square.resolve(Complex(NumberT), ReturnTyping.full)
|
||||
assert.deepStrictEqual(sqFull(eyes), math.complex(-4, 0))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,4 +22,7 @@ describe('generic utility functions', () => {
|
|||
assert(isReal(math.complex(-3.25, 4e-16)))
|
||||
assert(!isReal(math.complex(3, 4)))
|
||||
})
|
||||
it('tests for no imaginary part', () => {
|
||||
assert(math.nonImaginary(true))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
export * as arithmetic from './arithmetic.js'
|
||||
export * as configuration from './config.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import {Returns} from '#core/Type.js'
|
||||
import {ReturnsAs} from './helpers.js'
|
||||
|
||||
import {Returns, ReturnTyping} 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))
|
||||
export const abs = match(Any, (math, T) => {
|
||||
const absq = math.absquare.resolve(T)
|
||||
const sqrt = math.sqrt.resolve(absq.returns, ReturnTyping.conservative)
|
||||
return ReturnsAs(sqrt, t => sqrt(absq(t)))
|
||||
})
|
||||
export const conj = match(Any, (_math, T) => Returns(T, t => t))
|
||||
export const square = match(Any, (math, T, strategy) => {
|
||||
const mult = math.multiply.resolve([T, T], strategy)
|
||||
return Returns(mult.returns, t => mult(t, t))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
import {ImplementationsGenerator} from '#core/Implementations.js'
|
||||
import {match, Passthru} from '#core/TypePatterns.js'
|
||||
|
||||
export const config = new ImplementationsGenerator(
|
||||
() => match(Passthru, {relTol: 1e-12, absTol: 1e-15}))
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
const {full} = ReturnTyping
|
||||
|
||||
export const equal = match([Any, Any], (math, [T, U]) => {
|
||||
// Finding the correct signature of `indistinguishable` to use for
|
||||
// testing (approximate) equality is tricky, because T or U might
|
||||
|
|
@ -12,7 +14,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
|||
// the matching type, and then we look up with tolerances.
|
||||
let exactChecker
|
||||
try {
|
||||
exactChecker = math.indistinguishable.resolve([T, U])
|
||||
exactChecker = math.indistinguishable.resolve([T, U], full)
|
||||
} catch { // can't compare, so no way they can be equal
|
||||
return boolnum(() => false)(math)
|
||||
}
|
||||
|
|
@ -25,7 +27,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
|||
const {relTol, absTol} = typeConfig
|
||||
const RT = math.typeOf(relTol)
|
||||
const AT = math.typeOf(absTol)
|
||||
const approx = math.indistinguishable.resolve([T, U, RT, AT])
|
||||
const approx = math.indistinguishable.resolve([T, U, RT, AT], full)
|
||||
return ReturnsAs(
|
||||
approx, (t, u) => approx(t, u, relTol, absTol))
|
||||
} catch {} // fall through to case with no tolerances
|
||||
|
|
@ -86,6 +88,12 @@ export const larger = match([Any, Any], (math, [T, U]) => {
|
|||
return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math)
|
||||
})
|
||||
|
||||
export const isPositive = match(Any, (math, T) => {
|
||||
const zero = math.zero(T)
|
||||
const larger = math.larger.resolve([T, T])
|
||||
return boolnum(t => larger(t, zero))
|
||||
})
|
||||
|
||||
export const largerEq = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const bigger = math.exceeds.resolve([T, U])
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {Passthru, match} from '#core/TypePatterns.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {Any, Passthru, match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
// Most types are real. Have to make sure to redefine on all non-real types
|
||||
export const isReal = match(Passthru, boolnum(() => true))
|
||||
// Most types are real, so we just define them that way generically.
|
||||
// We have to make sure to redefine isReal on all non-real types.
|
||||
// We use Any here so that it will match before a specific type matches
|
||||
// with conversion; such a match runs the risk of not producing the correct
|
||||
// result, or worse, leading to a resolution loop.
|
||||
export const isReal = match(Any, boolnum(() => true))
|
||||
export const isZero = match(Passthru, (math, [T]) => {
|
||||
if (!T) { // called with no arguments
|
||||
throw new ResolutionError('isZero() requires one argument')
|
||||
|
|
@ -13,3 +18,9 @@ export const isZero = match(Passthru, (math, [T]) => {
|
|||
const eq = math.equal.resolve([T, T])
|
||||
return ReturnsAs(eq, x => eq(z, x))
|
||||
})
|
||||
export const nonImaginary = match(Passthru, boolnum(() => true))
|
||||
export const re = match(Any, (_math, T) => Returns(T, t => t))
|
||||
export const im = match(Any, (math, T) => {
|
||||
const z = math.zero(T)
|
||||
return Returns(T, () => z)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
|
||||
describe('number arithmetic', () => {
|
||||
it('supports basic operations', () => {
|
||||
|
|
@ -14,4 +15,18 @@ describe('number arithmetic', () => {
|
|||
assert.strictEqual(math.subtract(4, 2), 2)
|
||||
assert.strictEqual(math.quotient(7, 3), 2)
|
||||
})
|
||||
it('takes square root of numbers appropriately', () => {
|
||||
assert(isNaN(math.sqrt(NaN)))
|
||||
assert.strictEqual(math.sqrt(4), 2)
|
||||
assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2))
|
||||
math.config.returnTyping = ReturnTyping.conservative
|
||||
assert(isNaN(math.sqrt(NaN)))
|
||||
assert.strictEqual(math.sqrt(4), 2)
|
||||
assert(isNaN(math.sqrt(-4)))
|
||||
math.config.returnTyping = ReturnTyping.full
|
||||
assert(isNaN(math.sqrt(NaN)))
|
||||
assert.deepStrictEqual(math.sqrt(4), math.complex(2, 0))
|
||||
assert.deepStrictEqual(math.sqrt(-4), math.complex(0, 2))
|
||||
math.config.returnTyping = ReturnTyping.free
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -10,4 +10,11 @@ describe('number utilities', () => {
|
|||
assert.strictEqual(math.isnan(Infinity), false)
|
||||
assert.strictEqual(math.isnan(43), false)
|
||||
})
|
||||
it('tests if a number is an integer', () => {
|
||||
assert(math.isInteger(7))
|
||||
assert(math.isInteger(7+5e-16))
|
||||
assert(!math.isInteger(7.000001))
|
||||
assert(!math.isInteger(-Infinity))
|
||||
assert(!math.isInteger(NaN))
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,4 +1,10 @@
|
|||
import {plain} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
import {OneOf, Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {Complex} from '#complex/Complex.js'
|
||||
|
||||
const {conservative, full} = ReturnTyping
|
||||
|
||||
export const abs = plain(Math.abs)
|
||||
export const absquare = plain(a => a*a)
|
||||
|
|
@ -18,5 +24,25 @@ export const cbrt = plain(a => {
|
|||
export const invert = plain(a => 1/a)
|
||||
export const multiply = plain((a, b) => a * b)
|
||||
export const negate = plain(a => -a)
|
||||
|
||||
export const sqrt = match(NumberT, (math, _N, strategy) => {
|
||||
if (!math.types.Complex || strategy === conservative) {
|
||||
return Returns(NumberT, Math.sqrt)
|
||||
}
|
||||
const cplx = math.complex.resolve([NumberT, NumberT], full)
|
||||
if (strategy === full) {
|
||||
const cnan = math.nan(Complex(NumberT))
|
||||
return Returns(Complex(NumberT), a => {
|
||||
if (isNaN(a)) return cnan
|
||||
return a >= 0 ? cplx(Math.sqrt(a), 0) : cplx(0, Math.sqrt(-a))
|
||||
})
|
||||
}
|
||||
// strategy === free, return "best" type
|
||||
return Returns(OneOf(NumberT, Complex(NumberT)), a => {
|
||||
if (isNaN(a)) return NaN
|
||||
return a >= 0 ? Math.sqrt(a) : cplx(0, Math.sqrt(-a))
|
||||
})
|
||||
})
|
||||
|
||||
export const subtract = plain((a, b) => a - b)
|
||||
export const quotient = plain((a,b) => Math.floor(a/b))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import {Returns} from '#core/Type.js'
|
||||
import {match, Optional} from '#core/TypePatterns.js'
|
||||
import {boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,13 @@
|
|||
import {plain, boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const clone = plain(a => a)
|
||||
export const isfinite = match(NumberT, boolnum(isFinite))
|
||||
export const isInteger = match(NumberT, math => {
|
||||
const finite = math.isfinite.resolve(NumberT)
|
||||
const eq = math.equal.resolve([NumberT, NumberT])
|
||||
return boolnum(a => finite(a) && eq(a, Math.round(a)))(math)
|
||||
})
|
||||
export const isnan = match(NumberT, boolnum(isNaN))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"imports" : {
|
||||
"#nanomath": "./nanomath.js",
|
||||
"#boolean/*.js": "./boolean/*.js",
|
||||
"#core/*.js": "./core/*.js",
|
||||
"#boolean/*.js": "./boolean/*.js",
|
||||
"#complex/*.js": "./complex/*.js",
|
||||
"#generic/*.js": "./generic/*.js",
|
||||
"#number/*.js": "./number/*.js"
|
||||
},
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue