feat: methods on Complex #24

Merged
glen merged 6 commits from more_complex into main 2025-04-25 14:17:35 +00:00
33 changed files with 428 additions and 106 deletions

View file

@ -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
})

View file

@ -1,5 +1,5 @@
import {BooleanT} from './BooleanT.js'
import {match} from '#core/helpers.js'
import {match} from '#core/TypePatterns.js'
import {Returns, Type, TypeOfTypes, Undefined} from '#core/Type.js'
import {NumberT} from '#number/NumberT.js'

View file

@ -1,5 +1,5 @@
import {Type} from '#core/Type.js'
import {match} from '#core/helpers.js'
import {Returns, Type} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
@ -66,6 +66,12 @@ function complexSpecialize(ComponentType) {
export const Complex = new Type(isComplex, {
specialize: complexSpecialize,
specializesTo,
from: [match( // can promote any non-complex type T to Complex(T) as needed
// but watch out, this should be tried late, because it can preclude
// other more reasonable conversions like bool => number.
T => !T.complex,
(math, T) => Returns(Complex(T), r => math.complex(r, math.zero(T)))
)],
refine: function(z, typer) {
const reType = typer(z.re)
const imType = typer(z.im)

View file

@ -0,0 +1,78 @@
import assert from 'assert'
import math from '#nanomath'
import {Complex} from '../Complex.js'
import {NumberT} from '#number/NumberT.js'
const cplx = math.complex
describe('complex arithmetic operations', () => {
it('computes absquare of complex numbers', () => {
assert.strictEqual(math.absquare(cplx(3, 4)), 25)
assert.strictEqual(math.absquare(cplx(cplx(2, 3), cplx(4,5))), 54)
assert.strictEqual(math.absquare(cplx(true, true)), 2)
})
it('adds complex numbers', () => {
const z = cplx(3, 4)
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(
add(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
cplx(cplx(3.5, 4.5), cplx(6, 8)))
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(
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('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))
// 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.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
assert.deepStrictEqual(
inv(cplx(cplx(1, 2), cplx(4, 2))),
cplx(cplx(1/25, -2/25), cplx(-4/25, -2/25)))
})
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)))
})
})

View file

@ -1,12 +0,0 @@
import assert from 'assert'
import math from '#nanomath'
describe('complex type operations', () => {
it('converts to number', () => {
assert.deepStrictEqual(math.complex(3), {re: 3, im: 0})
assert.deepStrictEqual(math.complex(NaN), {re: NaN, im: NaN})
assert.deepStrictEqual(math.complex(3, -1), {re: 3, im: -1})
assert.deepStrictEqual(math.complex(false, true), {re: false, im: true})
assert.throws(() => math.complex(3, false), RangeError)
})
})

View file

@ -0,0 +1,39 @@
import assert from 'assert'
import math from '#nanomath'
const cplx = math.complex
describe('complex type operations', () => {
it('converts to number', () => {
assert.deepStrictEqual(cplx(3), {re: 3, im: 0})
assert.deepStrictEqual(cplx(NaN), {re: NaN, im: NaN})
assert.deepStrictEqual(cplx(3, -1), {re: 3, im: -1})
assert.deepStrictEqual(cplx(false, true), {re: false, im: true})
assert.throws(() => cplx(3, false), RangeError)
})
it('calculates the argument of a complex number', () => {
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))
})
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)))
})
})

View file

@ -1,2 +1,5 @@
export * as typeDefinition from './Complex.js'
export * as arithmetic from './arithmetic.js'
export * as relational from './relational.js'
export * as type from './type.js'
export * as utilities from './utils.js'

69
src/complex/arithmetic.js Normal file
View file

@ -0,0 +1,69 @@
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)))
})
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)))
})
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])
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])
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])
return ReturnsAs(cplx, z => {
const c = conj(z)
const d = norm(z)
return cplx(div(c.re, d), div(c.im, d))
})
})
// 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
View 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
View 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)
})
})

View file

@ -1,7 +1,8 @@
import {Complex} from './Complex.js'
import {match} from "#core/helpers.js"
import {Returns} from "#core/Type.js"
import {Any} from "#core/TypePatterns.js"
import {Any, match} from "#core/TypePatterns.js"
import {BooleanT} from '#boolean/BooleanT.js'
import {NumberT} from '#number/NumberT.js'
export const complex = [
match(Any, (math, T) => {
@ -25,3 +26,30 @@ export const complex = [
return Returns(Complex(T), (r, m) => ({re: r, im: m}))
})
]
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))
})
})
export const cis = match(NumberT, Returns(Complex(NumberT), t => ({
re: Math.cos(t), im: Math.sin(t)})))

9
src/complex/utils.js Normal file
View file

@ -0,0 +1,9 @@
import {Complex} from './Complex.js'
import {match} from '#core/TypePatterns.js'
import {ReturnsAs} from '#generic/helpers.js'
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)))
})

View file

@ -0,0 +1,13 @@
export class Implementations {
constructor(impOrImps) {
if (Array.isArray(impOrImps)) {
this.matchers = impOrImps
} else this.matchers = [impOrImps]
}
}
export class ImplementationsGenerator {
constructor(f) {
this.generate = f
}
}

View file

@ -1,6 +1,4 @@
import ArrayKeyedMap from 'array-keyed-map'
import {match} from './helpers.js'
import {Passthru} from './TypePatterns.js'
// Generic types are callable, so we have no choice but to extend Function
export class Type extends Function {
@ -12,9 +10,8 @@ export class Type extends Function {
// let the proxy out of this function, never `this`.
const rewired = new Proxy(this, {
apply: (target, thisForCall, args) => {
const callThrough = thisForCall ?? target
if (callThrough.specialize) return callThrough.specialize(...args)
throw new TypeError(`Type ${callThrough} is not generic`)
if (target.specialize) return target.specialize(...args)
throw new TypeError(`Type ${target} is not generic`)
},
get: (target, prop, receiver) => {
if (prop === 'isAproxy') return true
@ -87,13 +84,3 @@ export const whichType = typs => Returns(TypeOfTypes, item => {
}
throw new TypeError(errorMsg)
})
export const typeOf = match(Passthru, math => whichType(math.types))
// bootstrapping order matters, but order of exports in a module isn't
// simply the order that the items are listed in the module. So we make
// an explicitly ordered export of implementations for this sake:
export const bootstrapTypes = {
Type, Undefined, TypeOfTypes, typeOf
}

View file

@ -1,11 +1,14 @@
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 {
Implementations, ImplementationsGenerator, Matcher, ResolutionError,
isPlainFunction, isPlainObject, match, types
} from './helpers.js'
import {bootstrapTypes, Returns, whichType, Type} from './Type.js'
import {matched, needsCollection, Passthru} from './TypePatterns.js'
matched, needsCollection, Passthru, Matcher, match
} from './TypePatterns.js'
const underResolution = Symbol('underResolution')
export class TypeDispatcher {
constructor(...specs) {
@ -14,7 +17,6 @@ export class TypeDispatcher {
this._behaviors = {} // maps key to a map from type vectors to results
this._fallbacks = {} // maps key to a catchall result
// bootstrap the instance
this.merge({types})
this.merge(bootstrapTypes)
for (const spec of specs) this.merge(spec)
}
@ -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,13 +335,14 @@ 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
try {
theBehavior = item(
DependencyRecorder(this, '', this, []),
matched(template))
matched(template, this))
} catch (e) {
e.message = `Error in factory for ${key} on ${types} `
+ `(match data ${template}): ${e.message}`

View file

@ -1,4 +1,5 @@
import {Type, Undefined} from './Type.js'
import {isPlainFunction} from './helpers.js'
export class TypePattern {
match(typeSequence, options={}) {
@ -64,12 +65,35 @@ class SequencePattern extends TypePattern {
}
}
class PredicatePattern extends TypePattern {
constructor(predicate) {
super()
this.predicate = predicate
}
match(typeSequence, options={}) {
const position = options.position ?? 0
if (position >= typeSequence.length) return [-1, undefined]
const actual = typeSequence[position]
if (this.predicate(actual)) return [position + 1, actual]
return [-1, Undefined]
}
sampleTypes() {
throw new Error('sampleTypes() not yet implemented for PredicatePattern')
}
equal(other) {
return super.equal(other) && this.predicate === other.predicate
}
}
export const pattern = patternOrSpec => {
if (patternOrSpec instanceof TypePattern) return patternOrSpec
if (patternOrSpec instanceof Type) {
return new MatchTypePattern(patternOrSpec)
}
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
if (isPlainFunction(patternOrSpec)) {
return new PredicatePattern(patternOrSpec)
}
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
}
@ -150,9 +174,16 @@ class PassthruPattern extends TypePattern {
export const Passthru = new PassthruPattern()
// returns the template just of matched types, dropping any actual types
export const matched = (template) => {
if (Array.isArray(template)) return template.map(matched)
return template.matched ?? template
export const matched = (template, math) => {
if (Array.isArray(template)) {
return template.map(pattern => matched(pattern, math))
}
if (template.matched) {
let convert = template.convertor
if (!convert.returns) convert = convert(math, template.actual)
return convert.returns || template.matched
}
return template
}
// checks if the template is just pass-through or needs collection
@ -163,3 +194,12 @@ export const needsCollection = (template) => {
}
return 'actual' in template
}
export class Matcher {
constructor(spec, facOrBehave) {
this.pattern = pattern(spec)
this.does = facOrBehave
}
}
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)

View file

@ -4,8 +4,8 @@ import * as booleans from '#boolean/all.js'
import * as generics from '#generic/all.js'
import * as numbers from '#number/all.js'
import {NumberT} from '#number/NumberT.js'
import {match, ResolutionError} from "#core/helpers.js"
import {Any} from "#core/TypePatterns.js"
import {ResolutionError} from "#core/helpers.js"
import {match, Any} from "#core/TypePatterns.js"
import {Returns, NotAType} from "#core/Type.js"
import {plain} from "#number/helpers.js"

View file

@ -1,9 +1,8 @@
import assert from 'assert'
import {
Matcher, match, isPlainObject, isPlainFunction, Implementations
} from '../helpers.js'
import {isPlainObject, isPlainFunction} from '../helpers.js'
import {Implementations} from '../Implementations.js'
import {Type, Undefined, TypeOfTypes} from '../Type.js'
import {TypePattern} from '../TypePatterns.js'
import {match, Matcher, TypePattern} from '../TypePatterns.js'
describe('Core helpers', () => {
it('defines what Matchers are', () => {

View file

@ -1,41 +1,3 @@
import {pattern, Passthru} from './TypePatterns.js'
export class Matcher {
constructor(spec, facOrBehave) {
this.pattern = pattern(spec)
this.does = facOrBehave
}
}
export class Implementations {
constructor(impOrImps) {
if (Array.isArray(impOrImps)) {
this.matchers = impOrImps
} else this.matchers = [impOrImps]
}
}
export const match = (spec, facOrBehave) => new Matcher(spec, facOrBehave)
export class ImplementationsGenerator {
constructor(f) {
this.generate = f
}
}
// the archetypal example of needing an ImplementationsGenerator:
// each TypeDispatcher must have a types property, which will be a
// plain object of types. This must be a different object for each
// TypeDispatcher, but the same object regardless of the types vector
// passed to resolve. So an ordinary factory won't work, because it
// would make a new plain object for each different types vector that
// the property `types` was resolved with. And just a plain object
// wouldn't work, because then every TypeDispatcher would have the same
// collection of types (and modifying the types in one would affect them
// all). Hence we do:
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
export class ResolutionError extends TypeError {
constructor(...args) {
super(...args)

26
src/core/type.js Normal file
View file

@ -0,0 +1,26 @@
import {ImplementationsGenerator} from './Implementations.js'
import {Type, TypeOfTypes, Undefined, whichType} from './Type.js'
import {match, Passthru} from './TypePatterns.js'
export const typeOf = match(Passthru, math => whichType(math.types))
// And now the types object itself: This property provides the archetypal
// example of needing an ImplementationsGenerator. Each TypeDispatcher must
// have a types property, which will be a plain object of types. This object
// must be different for each TypeDispatcher, but within a TypeDispatcher,
// it must be the same object regardless of the types vector passed to resolve.
// So an ordinary factory won't work, because it would make a new plain object
// for each different types vector that the property `types` was resolved with.
// And just a plain object wouldn't work, because then every TypeDispatcher
// would have the same collection of types (and modifying the types in one
// would affect them all). Hence we do:
export const types = new ImplementationsGenerator(() => match(Passthru, {}))
// bootstrapping order matters, but order of exports in a module isn't
// simply the order that the items are listed in the module. So we make
// an explicitly ordered export of implementations for this sake:
export const bootstrapTypes = {
types, Type, Undefined, TypeOfTypes, typeOf
}

View file

@ -1,5 +1,5 @@
import {match} from '#core/helpers.js'
import {TypeOfTypes, Undefined} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js'
export const indistinguishable = [

View file

@ -1,6 +1,5 @@
import {match} from '#core/helpers.js'
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
import {Any} from "#core/TypePatterns.js"
import {match,Any} from "#core/TypePatterns.js"
import {boolnum} from "#number/helpers.js"
export const zero = [

View file

@ -1,5 +1,6 @@
import assert from 'assert'
import math from '#nanomath'
import {NumberT} from '#number/NumberT.js'
describe('generic utility functions', () => {
it('tests whether an element is zero', () => {
@ -9,5 +10,16 @@ describe('generic utility functions', () => {
assert(isZero(false))
assert(!isZero(true))
assert(isZero(undefined))
assert(isZero(math.types.Complex(NumberT).zero))
assert(isZero(math.complex(-2e-16, 4e-17)))
assert(!isZero(math.complex(true)))
})
it('tests whether an element is real', () => {
const {isReal} = math
assert(isReal(Infinity))
assert(isReal(false))
assert(isReal(math.types.Complex(NumberT).one))
assert(isReal(math.complex(-3.25, 4e-16)))
assert(!isReal(math.complex(3, 4)))
})
})

View file

@ -1,7 +1,7 @@
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {Any} from '#core/TypePatterns.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))

View file

@ -1,5 +1,5 @@
import {ImplementationsGenerator, match} from '#core/helpers.js'
import {Passthru} from '#core/TypePatterns.js'
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}))

View file

@ -1,7 +1,6 @@
import {ReturnsAs} from './helpers.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {Any, Passthru, matched} from '#core/TypePatterns.js'
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
import {boolnum} from '#number/helpers.js'
export const equal = match([Any, Any], (math, [T, U]) => {
@ -18,7 +17,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
return boolnum(() => false)(math)
}
// Get the type of the first argument to the matching checker:
const ByType = matched(exactChecker.template).flat()[0]
const ByType = matched(exactChecker.template, math).flat()[0]
// Now see if there are tolerances for that type:
const typeConfig = math.resolve('config', [ByType])
if ('relTol' in typeConfig) {

View file

@ -1,8 +1,10 @@
import {ReturnsAs} from './helpers.js'
import {ResolutionError, match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {Passthru} from "#core/TypePatterns.js"
import {ResolutionError} from '#core/helpers.js'
import {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))
export const isZero = match(Passthru, (math, [T]) => {
if (!T) { // called with no arguments
throw new ResolutionError('isZero() requires one argument')

View file

@ -5,6 +5,16 @@ import * as numbers from './number/all.js'
import * as complex from './complex/all.js'
import {TypeDispatcher} from '#core/TypeDispatcher.js'
const math = new TypeDispatcher(booleans, coretypes, generics, numbers, complex)
// At the moment, since we are not sorting patterns in any way,
// order matters in the construction. Patterns that come later in
// 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 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

View file

@ -1,9 +1,10 @@
import {Type} from '#core/Type.js'
import {match} from '#core/helpers.js'
import {match} from '#core/TypePatterns.js'
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

View file

@ -1,7 +1,7 @@
import {NumberT} from './NumberT.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
export const plain = f => match(
Array(f.length).fill(NumberT), Returns(NumberT, f))

View file

@ -1,6 +1,5 @@
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {Optional} from '#core/TypePatterns.js'
import {match, Optional} from '#core/TypePatterns.js'
import {boolnum} from './helpers.js'
import {NumberT} from './NumberT.js'

View file

@ -1,7 +1,7 @@
import {plain} from './helpers.js'
import {BooleanT} from '#boolean/BooleanT.js'
import {match} from '#core/helpers.js'
import {Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {NumberT} from '#number/NumberT.js'
const num = f => Returns(NumberT, f)

View file

@ -2,7 +2,7 @@ import {plain, boolnum} from './helpers.js'
import {NumberT} from './NumberT.js'
import {Returns} from '#core/Type.js'
import {match} from '#core/helpers.js'
import {match} from '#core/TypePatterns.js'
export const clone = plain(a => a)
export const isnan = match(NumberT, boolnum(isNaN))