feat: Make cis(theta)
respect return typing strategy
To accomplish this, also * implements `OneOf` union types * passes desired return typing strategy to behavior factory functions * adds formerly missing BooleanT utils * adds isInteger utility function, along with its dependency isfinite * adds several new and formerly missing Complex utils In addition, it adds several Complex tests, including careful checking of the behavior and return type of `math.cis` under different `math.config.returnTyping` settings.
This commit is contained in:
parent
47370cec9e
commit
722a2724a0
11 changed files with 156 additions and 27 deletions
|
@ -1,2 +1,3 @@
|
|||
export * as typeDefinition from './BooleanT.js'
|
||||
export * as type from './type.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
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))
|
|
@ -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,20 @@ 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(2*Math.PI), 1)
|
||||
})
|
||||
})
|
||||
|
|
40
src/complex/__test__/utils.spec.js
Normal file
40
src/complex/__test__/utils.spec.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
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)))
|
||||
})
|
||||
})
|
|
@ -10,6 +10,13 @@ export const promoteUnary = name => match(
|
|||
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||
})
|
||||
|
||||
export const promotePredicateAnd = name => match(
|
||||
Complex,
|
||||
(math, C) => {
|
||||
const compPred = math.resolve(name, C.Component)
|
||||
return ReturnsAs(compPred, z => compPred(z.re) && compPred(z.im))
|
||||
})
|
||||
|
||||
export const promoteBinary = name => match(
|
||||
[Complex, Complex],
|
||||
(math, [W, Z]) => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {Returns} from "#core/Type.js"
|
||||
import {OneOf, Returns, ReturnTyping} from "#core/Type.js"
|
||||
import {Any, match} from "#core/TypePatterns.js"
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
@ -28,28 +28,42 @@ export const complex = [
|
|||
]
|
||||
|
||||
export const arg = match(
|
||||
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
|
||||
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))
|
||||
})
|
||||
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 cis = match(NumberT, Returns(Complex(NumberT), t => ({
|
||||
re: Math.cos(t), im: Math.sin(t)})))
|
||||
const _cis = t => ({re: Math.cos(t), im: Math.sin(t)})
|
||||
|
||||
export const cis = match(NumberT, (math, _type, strategy) => {
|
||||
if (strategy === ReturnTyping.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)
|
||||
})
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
import {promotePredicateAnd, promoteUnary} from './helpers.js'
|
||||
|
||||
export const clone = promoteUnary('clone')
|
||||
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')
|
||||
|
||||
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 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)))
|
||||
})
|
||||
|
|
|
@ -69,6 +69,27 @@ export const TypeOfTypes = new Type(t => t instanceof Type)
|
|||
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 => {
|
||||
|
|
|
@ -360,7 +360,8 @@ export class TypeDispatcher {
|
|||
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)} `
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4,4 +4,10 @@ import {NumberT} from './NumberT.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))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue