feat(polynomialRoot) (#57)

Implements a simply polynomial root finder function
polynomialRoot, intended to be used for benchmarking
against mathjs.

For this purpose, adds numerous other functions (e.g.
cbrt, arg, cis), refactors sqrt (so that you can
definitely get the complex square root when you want
it), and makes numerous enhancements to the core so
that a template can match after conversions.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #57
This commit is contained in:
Glen Whitney 2022-12-01 17:47:20 +00:00
parent 31add66f4c
commit 0dbb95bbbe
18 changed files with 634 additions and 264 deletions

View File

@ -3,7 +3,6 @@ export * from './Types/Complex.mjs'
export const abs = { export const abs = {
'Complex<T>': ({ 'Complex<T>': ({
T,
sqrt, // Unfortunately no notation yet for the needed signature sqrt, // Unfortunately no notation yet for the needed signature
'absquare(T)': baseabsq, 'absquare(T)': baseabsq,
'absquare(Complex<T>)': absq 'absquare(Complex<T>)': absq

7
src/complex/arg.mjs Normal file
View File

@ -0,0 +1,7 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* arg is the "argument" or angle theta of z in its form r cis theta */
export const arg = {
'Complex<number>': () => Returns('number', z => Math.atan2(z.im, z.re))
}

28
src/complex/cbrtc.mjs Normal file
View File

@ -0,0 +1,28 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
const TAU3 = 2 * Math.PI / 3
/* Complex cube root that returns all three roots as a tuple of complex. */
/* follows the implementation in mathjs */
/* Really only works for T = number at the moment because of arg and cbrt */
export const cbrtc = {
'Complex<T>': ({
'arg(T)': theta,
'divide(T,T)': div,
'abs(Complex<T>)': absval,
'complex(T)': cplx,
'cbrt(T)': cbrtT,
'multiply(Complex<T>,Complex<T>)': mult,
'cis(T)': cisT,
'tuple(...Complex<T>)': tup
}) => Returns('Tuple<Complex<T>>', z => {
const arg3 = div(theta(z), 3)
const r = cplx(cbrtT(absval(z)))
return tup(
mult(r, cisT(arg3)),
mult(r, cisT(arg3 + TAU3)),
mult(r, cisT(arg3 - TAU3))
)
})
}

9
src/complex/cis.mjs Normal file
View File

@ -0,0 +1,9 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* Returns cosine plus i sin theta */
export const cis = {
'number': ({'complex(number,number)': cplx}) => Returns(
'Complex<number>', t => cplx(Math.cos(t), Math.sin(t))
)
}

7
src/complex/isReal.mjs Normal file
View File

@ -0,0 +1,7 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const isReal = {
'Complex<T>': ({'equal(T,T)': eq, 'add(T,T)': plus}) => Returns(
'boolean', z => eq(z.re, plus(z.re, z.im)))
}

View File

@ -3,18 +3,24 @@ export * from './Types/Complex.mjs'
export {abs} from './abs.mjs' export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs' export {absquare} from './absquare.mjs'
export {add} from './add.mjs' export {add} from './add.mjs'
export {arg} from './arg.mjs'
export {associate} from './associate.mjs' export {associate} from './associate.mjs'
export {cbrtc} from './cbrtc.mjs'
export {cis} from './cis.mjs'
export {complex} from './complex.mjs' export {complex} from './complex.mjs'
export {conjugate} from './conjugate.mjs' export {conjugate} from './conjugate.mjs'
export {equalTT} from './equalTT.mjs' export {equalTT} from './equalTT.mjs'
export {gcd} from './gcd.mjs' export {gcd} from './gcd.mjs'
export {invert} from './invert.mjs' export {invert} from './invert.mjs'
export {isReal} from './isReal.mjs'
export {isZero} from './isZero.mjs' export {isZero} from './isZero.mjs'
export {multiply} from './multiply.mjs' export {multiply} from './multiply.mjs'
export {negate} from './negate.mjs' export {negate} from './negate.mjs'
export {polynomialRoot} from './polynomialRoot.mjs'
export {quaternion} from './quaternion.mjs' export {quaternion} from './quaternion.mjs'
export {quotient} from './quotient.mjs' export {quotient} from './quotient.mjs'
export {roundquotient} from './roundquotient.mjs' export {roundquotient} from './roundquotient.mjs'
export {sqrt} from './sqrt.mjs' export {sqrt} from './sqrt.mjs'
export {sqrtc} from './sqrtc.mjs'
export {zero} from './zero.mjs' export {zero} from './zero.mjs'

View File

@ -0,0 +1,118 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const polynomialRoot = {
'Complex<T>,...Complex<T>': ({
T,
'tuple(...Complex<T>)': tupCplx,
'tuple(...T)': tupReal,
'isZero(Complex<T>)': zero,
'complex(T)': C,
'multiply(Complex<T>,Complex<T>)': mul,
'divide(Complex<T>,Complex<T>)': div,
'negate(Complex<T>)': neg,
'isReal(Complex<T>)': real,
'equalTT(Complex<T>,Complex<T>)': eq,
'add(Complex<T>,Complex<T>)': plus,
'subtract(Complex<T>,Complex<T>)': sub,
'sqrtc(Complex<T>)': sqt,
'cbrtc(Complex<T>)': cbt
}) => Returns(`Tuple<${T}>|Tuple<Complex<${T}>>`, (constant, rest) => {
// helper to convert results to appropriate tuple type
const typedTup = arr => {
if (arr.every(real)) {
return tupReal.apply(tupReal, arr.map(z => z.re))
}
return tupCplx.apply(tupCplx, arr)
}
const coeffs = [constant, ...rest]
while (coeffs.length > 0 && zero(coeffs[coeffs.length - 1])) {
coeffs.pop()
}
if (coeffs.length < 2) {
}
switch (coeffs.length) {
case 0: case 1:
throw new RangeError(
`Polynomial [${constant}, ${rest}] must have at least one`
+ 'non-zero non-constant coefficient')
case 2: // linear
return typedTup([neg(div(coeffs[0], coeffs[1]))])
case 3: { // quadratic
const [c, b, a] = coeffs
const denom = mul(C(2), a)
const d1 = mul(b, b)
const d2 = mul(C(4), mul(a, c))
if (eq(d1, d2)) {
return typedTup([div(neg(b), denom)])
}
let discriminant = sqt(sub(d1, d2))
return typedTup([
div(sub(discriminant, b), denom),
div(sub(neg(discriminant), b), denom)
])
}
case 4: { // cubic, cf. https://en.wikipedia.org/wiki/Cubic_equation
const [d, c, b, a] = coeffs
const denom = neg(mul(C(3), a))
const asqrd = mul(a, a)
const D0_1 = mul(b, b)
const bcubed = mul(D0_1, b)
const D0_2 = mul(C(3), mul(a, c))
const D1_1 = plus(
mul(C(2), bcubed), mul(C(27), mul(asqrd, d)))
const abc = mul(a, mul(b, c))
const D1_2 = mul(C(9), abc)
// Check for a triple root
if (eq(D0_1, D0_2) && eq(D1_1, D1_2)) {
return typedTup([div(b, denom)])
}
const Delta0 = sub(D0_1, D0_2)
const Delta1 = sub(D1_1, D1_2)
const csqrd = mul(c, c)
const discriminant1 = plus(
mul(C(18), mul(abc, d)), mul(D0_1, csqrd))
const discriminant2 = plus(
mul(C(4), mul(bcubed, d)),
plus(
mul(C(4), mul(a, mul(csqrd, c))),
mul(C(27), mul(asqrd, mul(d, d)))))
// See if we have a double root
if (eq(discriminant1, discriminant2)) {
return typedTup([
div(
sub(
mul(C(4), abc),
plus(mul(C(9), mul(asqrd, d)), bcubed)),
mul(a, Delta0)), // simple root
div(
sub(mul(C(9), mul(a, d)), mul(b, c)),
mul(C(2), Delta0)) // double root
])
}
// OK, we have three distinct roots
let Ccubed
if (eq(D0_1, D0_2)) {
Ccubed = Delta1
} else {
Ccubed = div(
plus(
Delta1,
sqt(sub(
mul(Delta1, Delta1),
mul(C(4), mul(Delta0, mul(Delta0, Delta0)))))
),
C(2))
}
const croots = cbt(Ccubed)
return typedTup(cbt(Ccubed).elts.map(
C => div(plus(b, plus(C, div(Delta0, C))), denom)))
}
default:
throw new RangeError(
'only implemented for cubic or lower-order polynomials, '
+ `not ${JSON.stringify(coeffs)}`)
}
})
}

View File

@ -4,49 +4,30 @@ export * from './Types/Complex.mjs'
export const sqrt = { export const sqrt = {
'Complex<T>': ({ 'Complex<T>': ({
config, config,
'sqrtc(Complex<T>)': predictableSqrt,
'isZero(T)': isZ, 'isZero(T)': isZ,
'sign(T)': sgn,
'one(T)': uno,
'add(T,T)': plus,
'complex(T)': cplxU,
'complex(T,T)': cplxB,
'multiply(T,T)': mult,
'self(T)': me,
'divide(T,T)': div,
'absquare(Complex<T>)': absqC,
'subtract(T,T)': sub
}) => { }) => {
let baseReturns = returnTypeOf(me) if (config.checkingDependency) return undefined
if (baseReturns.includes('|')) { const complexReturns = returnTypeOf(predictableSqrt)
// Bit of a hack, because it is relying on other implementations const baseReturns = complexReturns.slice(8, -1); // Complex<WhatWeWant>
// to list the "typical" value of sqrt first
baseReturns = baseReturns.split('|', 1)[0]
}
if (config.predictable) { if (config.predictable) {
return Returns(`Complex<${baseReturns}>`, z => { return Returns(complexReturns, z => predictableSqrt(z))
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re))
const reTwo = plus(reOne, reOne)
const myabs = me(absqC(z))
return cplxB(
mult(sgn(z.im), me(div(plus(myabs, z.re), reTwo))),
me(div(sub(myabs, z.re), reTwo))
)
})
} }
return Returns( return Returns(
`Complex<${baseReturns}>|${baseReturns}|undefined`, `Complex<${baseReturns}>|${baseReturns}|undefined`,
z => { z => {
const reOne = uno(z.re) let complexSqrt
if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re) try {
const reTwo = plus(reOne, reOne) complexSqrt = predictableSqrt(z)
const myabs = me(absqC(z)) } catch (e) {
const reSqrt = me(div(plus(myabs, z.re), reTwo)) return undefined
const imSqrt = me(div(sub(myabs, z.re), reTwo)) }
if (reSqrt === undefined || imSqrt === undefined) return undefined if (complexSqrt.re === undefined || complexSqrt.im === undefined) {
return cplxB(mult(sgn(z.im), reSqrt), imSqrt) return undefined
}
if (isZ(complexSqrt.im)) return complexSqrt.re
return complexSqrt
} }
) )
} }

41
src/complex/sqrtc.mjs Normal file
View File

@ -0,0 +1,41 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const sqrtc = {
'Complex<T>': ({
'isZero(T)': isZ,
'sign(T)': sgn,
'one(T)': uno,
'add(T,T)': plus,
'complex(T)': cplxU,
'complex(T,T)': cplxB,
'multiply(T,T)': mult,
'sqrt(T)': sqt,
'divide(T,T)': div,
'absquare(Complex<T>)': absqC,
'subtract(T,T)': sub
}) => {
if (isZ.checkingDependency) return undefined
let baseReturns = returnTypeOf(sqt)
if (baseReturns.includes('|')) {
// Bit of a hack, because it is relying on other implementations
// to list the "typical" value of sqrt first
baseReturns = baseReturns.split('|', 1)[0]
}
return Returns(`Complex<${baseReturns}>`, z => {
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(sqt(z.re))
const myabs = sqt(absqC(z))
const reTwo = plus(reOne, reOne)
const reQuot = div(plus(myabs, z.re), reTwo)
const imQuot = div(sub(myabs, z.re), reTwo)
if (reQuot === undefined || imQuot === undefined) {
throw new TypeError(`Cannot compute sqrt of ${z.re} + {z.im}i`)
}
return cplxB(
mult(sgn(z.im), sqt(div(plus(myabs, z.re), reTwo))),
sqt(div(sub(myabs, z.re), reTwo))
)
})
}
}

View File

@ -7,6 +7,12 @@ import {typeListOfSignature, typesOfSignature, subsetOfKeys} from './utils.mjs'
const anySpec = {} // fixed dummy specification of 'any' type const anySpec = {} // fixed dummy specification of 'any' type
/* Like `.some(predicate)` but for collections */
function exists(collection, predicate) {
for (const item of collection) if (predicate(item)) return true;
return false;
}
/* Template/signature parsing stuff; should probably be moved to a /* Template/signature parsing stuff; should probably be moved to a
* separate file, but it's a bit interleaved at the moment * separate file, but it's a bit interleaved at the moment
*/ */
@ -87,6 +93,7 @@ function substituteInSignature(signature, parameter, type) {
return sig.replaceAll(pattern, type) return sig.replaceAll(pattern, type)
} }
const UniversalType = 'ground' // name for a type that matches anything
let lastWhatToDo = null // used in an infinite descent check let lastWhatToDo = null // used in an infinite descent check
export default class PocomathInstance { export default class PocomathInstance {
@ -97,6 +104,7 @@ export default class PocomathInstance {
static reserved = new Set([ static reserved = new Set([
'chain', 'chain',
'config', 'config',
'convert',
'importDependencies', 'importDependencies',
'install', 'install',
'installType', 'installType',
@ -128,16 +136,30 @@ export default class PocomathInstance {
// its onMismatch function, below: // its onMismatch function, below:
this._metaTyped = typed.create() this._metaTyped = typed.create()
this._metaTyped.clear() this._metaTyped.clear()
this._metaTyped.addTypes([{name: UniversalType, test: () => true}])
// And these are the meta bindings: (I think we don't need separate // And these are the meta bindings: (I think we don't need separate
// invalidation for them as they are only accessed through a main call.) // invalidation for them as they are only accessed through a main call.)
this._meta = {} // The resulting typed-functions this._meta = {} // The resulting typed-functions
this._metaTFimps = {} // and their implementations this._metaTFimps = {} // and their implementations
const me = this const me = this
const myTyped = this._typed
this._typed.onMismatch = (name, args, sigs) => { this._typed.onMismatch = (name, args, sigs) => {
if (me._invalid.has(name)) { if (me._invalid.has(name)) {
if (this._fixing === name) {
this._fixingCount += 1
if (this._fixingCount > this._maxDepthSeen + 2) {
throw new ReferenceError(
`Infinite descent rebuilding ${name} on ${args}`)
}
} else {
this._fixingCount = 0
}
// rebuild implementation and try again // rebuild implementation and try again
return me[name](...args) const lastFixing = this._fixing
this._fixing = name
const value = me[name](...args)
this._fix = lastFixing
return value
} }
const metaversion = me._meta[name] const metaversion = me._meta[name]
if (metaversion) { if (metaversion) {
@ -183,6 +205,8 @@ export default class PocomathInstance {
this._plainFunctions = new Set() // the names of the plain functions this._plainFunctions = new Set() // the names of the plain functions
this._chainRepository = {} // place to store chainified functions this._chainRepository = {} // place to store chainified functions
this.joinTypes = this.joinTypes.bind(me) this.joinTypes = this.joinTypes.bind(me)
// Provide access to typed function conversion:
this.convert = this._typed.convert.bind(this._typed)
} }
/** /**
@ -523,6 +547,10 @@ export default class PocomathInstance {
} }
} }
} }
// Need to metafy ground types
if (type === base) {
this._metafy(type)
}
// update the typeOf function // update the typeOf function
const imp = {} const imp = {}
imp[type] = {uses: new Set(), does: () => Returns('string', () => type)} imp[type] = {uses: new Set(), does: () => Returns('string', () => type)}
@ -640,7 +668,7 @@ export default class PocomathInstance {
} }
// install the "base type" in the meta universe: // install the "base type" in the meta universe:
let beforeType = 'any' let beforeType = UniversalType
for (const other of spec.before || []) { for (const other of spec.before || []) {
if (other in this.templates) { if (other in this.templates) {
beforeType = other beforeType = other
@ -648,6 +676,13 @@ export default class PocomathInstance {
} }
} }
this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType) this._metaTyped.addTypes([{name: base, test: spec.base}], beforeType)
// Add conversions to the base type:
if (spec.from && spec.from[theTemplateParam]) {
for (const ground of this._metafiedTypes) {
this._metaTyped.addConversion(
{from: ground, to: base, convert: spec.from[theTemplateParam]})
}
}
this._instantiationsOf[base] = new Set() this._instantiationsOf[base] = new Set()
// update the typeOf function // update the typeOf function
@ -750,45 +785,39 @@ export default class PocomathInstance {
/** /**
* Reset an operation to require creation of typed-function, * Reset an operation to require creation of typed-function,
* and if it has no implementations so far, set them up. * and if it has no implementations so far, set them up.
* name is the name of the operation, badType is a type that has been
* invalidated, and reasons is a set of specific operations/signatures
* that have been invalidated
*/ */
_invalidate(name, reason) { _invalidate(name, badType = '', reasons = new Set()) {
if (!(name in this._imps)) { if (!(name in this._imps)) {
this._imps[name] = {} this._imps[name] = {}
this._TFimps[name] = {} this._TFimps[name] = {}
this._metaTFimps[name] = {} this._metaTFimps[name] = {}
} }
if (reason) { // Go through each TF imp and invalidate it if need be
// Make sure no TF imps that depend on reason remain: for (const [signature, imp] of Object.entries(this._TFimps[name])) {
for (const [signature, behavior] of Object.entries(this._imps[name])) { if (imp.deferred
let invalidated = false || (badType && signature.includes(badType))
if (reason.charAt(0) === ':') { || exists(imp.uses, dep => {
const badType = reason.slice(1) const [func, sig] = dep.split(/[()]/)
if (signature.includes(badType)) invalidated = true return reasons.has(dep)
|| (reasons.has(func) && !(sig in this._TFimps[func]))
})) {
// Invalidate this implementation:
delete this._TFimps[name][signature]
const behavior = imp.fromBehavior
if (behavior.explicit) {
behavior.resolved = false
} else { } else {
for (const dep of behavior.uses) { delete behavior.hasInstantiations[imp.instance]
if (dep.includes(reason)) {
invalidated = true
break
}
}
}
if (invalidated) {
if (behavior.explicit) {
if (behavior.resolved) delete this._TFimps[signature]
behavior.resolved = false
} else {
for (const fullSig
of Object.values(behavior.hasInstantiations)) {
delete this._TFimps[fullSig]
}
behavior.hasInstantiations = {}
}
} }
reasons.add(`${name}(${signature})`)
} }
} }
if (this._invalid.has(name)) return if (this._invalid.has(name)) return
this._invalid.add(name) this._invalid.add(name)
this._invalidateDependents(name) this._invalidateDependents(name, badType, reasons)
const self = this const self = this
Object.defineProperty(this, name, { Object.defineProperty(this, name, {
configurable: true, configurable: true,
@ -802,11 +831,14 @@ export default class PocomathInstance {
/** /**
* Invalidate all the dependents of a given property of the instance * Invalidate all the dependents of a given property of the instance
* reasons is a set of invalidated signatures
*/ */
_invalidateDependents(name) { _invalidateDependents(name, badType, reasons = new Set()) {
if (name.charAt(0) === ':') badType = name.slice(1)
else reasons.add(name)
if (name in this._affects) { if (name in this._affects) {
for (const ancestor of this._affects[name]) { for (const ancestor of this._affects[name]) {
this._invalidate(ancestor, name) this._invalidate(ancestor, badType, reasons)
} }
} }
} }
@ -847,7 +879,7 @@ export default class PocomathInstance {
for (const [rawSignature, behavior] of usableEntries) { for (const [rawSignature, behavior] of usableEntries) {
if (behavior.explicit) { if (behavior.explicit) {
if (!(behavior.resolved)) { if (!(behavior.resolved)) {
this._addTFimplementation(tf_imps, rawSignature, behavior) this._addTFimplementation(name, tf_imps, rawSignature, behavior)
tf_imps[rawSignature]._pocoSignature = rawSignature tf_imps[rawSignature]._pocoSignature = rawSignature
behavior.resolved = true behavior.resolved = true
} }
@ -867,11 +899,18 @@ export default class PocomathInstance {
} }
/* First, add the known instantiations, gathering all types needed */ /* First, add the known instantiations, gathering all types needed */
if (ubType) behavior.needsInstantiations.add(ubType) if (ubType) behavior.needsInstantiations.add(ubType)
const nargs = typeListOfSignature(rawSignature).length
let instantiationSet = new Set() let instantiationSet = new Set()
const ubTypes = new Set() const ubTypes = new Set()
if (!ubType) { if (!ubType) {
// Collect all upper-bound types for this signature // Collect all upper-bound types for this signature
for (const othersig in imps) { for (const othersig in imps) {
const otherNargs = typeListOfSignature(othersig).length
if (nargs !== otherNargs) {
// crude criterion that it won't match, that ignores
// rest args, but hopefully OK for prototype
continue
}
const thisUB = upperBounds.exec(othersig) const thisUB = upperBounds.exec(othersig)
if (thisUB) ubTypes.add(thisUB[2]) if (thisUB) ubTypes.add(thisUB[2])
let basesig = othersig.replaceAll(templateCall, '') let basesig = othersig.replaceAll(templateCall, '')
@ -881,7 +920,7 @@ export default class PocomathInstance {
basesig, theTemplateParam, '') basesig, theTemplateParam, '')
if (testsig === basesig) { if (testsig === basesig) {
// that is not also top-level // that is not also top-level
for (const templateType of typeListOfSignature(basesig)) { for (let templateType of typeListOfSignature(basesig)) {
if (templateType.slice(0,3) === '...') { if (templateType.slice(0,3) === '...') {
templateType = templateType.slice(3) templateType = templateType.slice(3)
} }
@ -894,21 +933,20 @@ export default class PocomathInstance {
for (const instType of behavior.needsInstantiations) { for (const instType of behavior.needsInstantiations) {
instantiationSet.add(instType) instantiationSet.add(instType)
const otherTypes = const otherTypes =
ubType ? this.subtypesOf(instType) : this._priorTypes[instType] ubType ? this.subtypesOf(instType) : this._priorTypes[instType]
for (const other of otherTypes) { for (const other of otherTypes) {
if (!(this._atOrBelowSomeType(other, ubTypes))) { if (!(this._atOrBelowSomeType(other, ubTypes))) {
instantiationSet.add(other) instantiationSet.add(other)
} }
} }
} }
/* Prevent other existing signatures from blocking use of top-level /* Prevent other existing signatures from blocking use of top-level
* templates via conversions: * templates via conversions:
*/ */
let baseSignature = rawSignature.replaceAll(templateCall, '') let baseSignature = rawSignature.replaceAll(templateCall, '')
/* Any remaining template params are top-level */ /* Any remaining template params are top-level */
const signature = substituteInSignature( const signature = substituteInSignature(
baseSignature, theTemplateParam, 'any') baseSignature, theTemplateParam, UniversalType)
const hasTopLevel = (signature !== baseSignature) const hasTopLevel = (signature !== baseSignature)
if (!ubType && hasTopLevel) { if (!ubType && hasTopLevel) {
for (const othersig in imps) { for (const othersig in imps) {
@ -939,9 +977,9 @@ export default class PocomathInstance {
} }
} }
} }
for (const instType of instantiationSet) { for (const instType of instantiationSet) {
this._instantiateTemplateImplementation(name, rawSignature, instType) this._instantiateTemplateImplementation(
name, rawSignature, instType)
} }
/* Now add the catchall signature */ /* Now add the catchall signature */
/* (Not needed if if it's a bounded template) */ /* (Not needed if if it's a bounded template) */
@ -996,6 +1034,7 @@ export default class PocomathInstance {
`Type inference failed for argument ${j} of ${name}`) `Type inference failed for argument ${j} of ${name}`)
} }
if (argType === 'any') { if (argType === 'any') {
console.log('INCOMPATIBLE ARGUMENTS are', args)
throw TypeError( throw TypeError(
`In call to ${name}, ` `In call to ${name}, `
+ 'incompatible template arguments:' + 'incompatible template arguments:'
@ -1018,9 +1057,10 @@ export default class PocomathInstance {
usedConversions = true usedConversions = true
instantiateFor = self.joinTypes(argTypes, usedConversions) instantiateFor = self.joinTypes(argTypes, usedConversions)
if (instantiateFor === 'any') { if (instantiateFor === 'any') {
let argDisplay = args.map(toString).join(', ')
throw TypeError( throw TypeError(
`In call to ${name}, no type unifies arguments ` `In call to ${name}, no type unifies arguments `
+ args.toString() + '; of types ' + argTypes.toString() + argDisplay + '; of types ' + argTypes.toString()
+ '; note each consecutive pair must unify to a ' + '; note each consecutive pair must unify to a '
+ 'supertype of at least one of them') + 'supertype of at least one of them')
} }
@ -1042,7 +1082,9 @@ export default class PocomathInstance {
for (j = 0; j < parTypes.length; ++j) { for (j = 0; j < parTypes.length; ++j) {
if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) { if (wantTypes[j] !== parTypes[j] && parTypes[j].includes('<')) {
// actually used the param and is a template // actually used the param and is a template
self._ensureTemplateTypes(parTypes[j], instantiateFor) const strippedType = parTypes[j].substr(
parTypes[j].lastIndexOf('.') + 1)
self._ensureTemplateTypes(strippedType, instantiateFor)
} }
} }
@ -1050,12 +1092,19 @@ export default class PocomathInstance {
// But possibly since this resolution was grabbed, the proper // But possibly since this resolution was grabbed, the proper
// instantiation has been added (like if there are multiple // instantiation has been added (like if there are multiple
// uses in the implementation of another method. // uses in the implementation of another method.
if (!(behavior.needsInstantiations.has(instantiateFor))) { let whatToDo
behavior.needsInstantiations.add(instantiateFor) if (!(instantiateFor in behavior.hasInstantiations)) {
const newImp = self._instantiateTemplateImplementation(
name, rawSignature, instantiateFor)
if (newImp) {
whatToDo = {fn: newImp, implementation: newImp}
}
self._invalidate(name) self._invalidate(name)
} }
const brandNewMe = self[name] const brandNewMe = self[name]
const whatToDo = self._typed.resolve(brandNewMe, args) const betterToDo = self._typed.resolve(brandNewMe, args)
whatToDo = betterToDo || whatToDo
// We can access return type information here // We can access return type information here
// And in particular, if it might be a template, we should try to // And in particular, if it might be a template, we should try to
// instantiate it: // instantiate it:
@ -1069,8 +1118,13 @@ export default class PocomathInstance {
} }
if (whatToDo === lastWhatToDo) { if (whatToDo === lastWhatToDo) {
throw new Error( throw new Error(
`Infinite recursion in resolving $name called on` `Infinite recursion in resolving ${name} called on `
+ args.map(x => x.toString()).join(',')) + args.map(x =>
(typeof x === 'object'
? JSON.stringify(x)
: x.toString())
).join(', ')
+ ` inferred to be ${wantSig}`)
} }
lastWhatToDo = whatToDo lastWhatToDo = whatToDo
const retval = whatToDo.implementation(...args) const retval = whatToDo.implementation(...args)
@ -1088,15 +1142,19 @@ export default class PocomathInstance {
// correct return type a priori. Deferring because unclear what // correct return type a priori. Deferring because unclear what
// aspects will be merged into typed-function. // aspects will be merged into typed-function.
this._addTFimplementation( this._addTFimplementation(
meta_imps, signature, {uses: new Set(), does: patch}) name, meta_imps, signature,
{uses: new Set(), does: patch},
behavior)
behavior.resolved = true behavior.resolved = true
} }
this._correctPartialSelfRefs(name, tf_imps)
// Make sure we have all of the needed (template) types; and if they // Make sure we have all of the needed (template) types; and if they
// can't be added (because they have been instantiated too deep), // can't be added (because they have been instantiated too deep),
// ditch the signature: // ditch the signature:
const badSigs = new Set() const badSigs = new Set()
for (const sig in tf_imps) { for (const sig in tf_imps) {
if (!tf_imps[sig].uses) {
throw new ReferenceError(`MONKEY WRENCH: ${name} ${sig}`)
}
for (const type of typeListOfSignature(sig)) { for (const type of typeListOfSignature(sig)) {
if (this._maybeInstantiate(type) === undefined) { if (this._maybeInstantiate(type) === undefined) {
badSigs.add(sig) badSigs.add(sig)
@ -1117,11 +1175,13 @@ export default class PocomathInstance {
if (Object.keys(tf_imps).length > 0) { if (Object.keys(tf_imps).length > 0) {
tf = this._typed(name, tf_imps) tf = this._typed(name, tf_imps)
tf.fromInstance = this tf.fromInstance = this
tf.isMeta = false
} }
let metaTF let metaTF
if (Object.keys(meta_imps).length > 0) { if (Object.keys(meta_imps).length > 0) {
metaTF = this._metaTyped(name, meta_imps) metaTF = this._metaTyped(name, meta_imps)
metaTF.fromInstance = this metaTF.fromInstance = this
metaTF.isMeta = true
} }
this._meta[name] = metaTF this._meta[name] = metaTF
@ -1215,10 +1275,12 @@ export default class PocomathInstance {
return behavior.does(innerRefs) return behavior.does(innerRefs)
} }
const tf_imps = this._TFimps[name] const tf_imps = this._TFimps[name]
this._addTFimplementation(tf_imps, signature, {uses, does: patch}) this._addTFimplementation(
name, tf_imps, signature, {uses, does: patch}, behavior, instanceType)
tf_imps[signature]._pocoSignature = templateSignature tf_imps[signature]._pocoSignature = templateSignature
tf_imps[signature]._pocoInstance = instanceType tf_imps[signature]._pocoInstance = instanceType
behavior.hasInstantiations[instanceType] = signature behavior.hasInstantiations[instanceType] = signature
behavior.needsInstantiations.add(instanceType) // once we have it, keep it
return tf_imps[signature] return tf_imps[signature]
} }
@ -1226,17 +1288,23 @@ export default class PocomathInstance {
* to typed-function implementations and inserts the result into plain * to typed-function implementations and inserts the result into plain
* object imps * object imps
*/ */
_addTFimplementation(imps, signature, behavior) { _addTFimplementation(
const {uses, does} = behavior name, imps, signature, specificBehavior, fromImp, asInstance)
{
if (!fromImp) fromImp = specificBehavior
const {uses, does} = specificBehavior
if (uses.length === 0) { if (uses.length === 0) {
const implementation = does() const implementation = does()
implementation.uses = uses
implementation.fromInstance = this
implementation.fromBehavior = fromImp
implementation.instance = asInstance
// could do something with return type information here // could do something with return type information here
imps[signature] = implementation imps[signature] = implementation
return return
} }
const refs = {} const refs = {}
let full_self_referential = false let full_self_referential = false
let part_self_references = []
for (const dep of uses) { for (const dep of uses) {
let [func, needsig] = dep.split(/[()]/) let [func, needsig] = dep.split(/[()]/)
/* Safety check that can perhaps be removed: /* Safety check that can perhaps be removed:
@ -1252,82 +1320,71 @@ export default class PocomathInstance {
} }
if (func === 'self') { if (func === 'self') {
if (needsig) { if (needsig) {
/* Maybe we can resolve the self reference without troubling /* We now resolve all specific-signature self references
* typed-function: * here, without resorting to the facility in typed-function:
*/ */
if (needsig in imps && typeof imps[needsig] == 'function') { if (needsig in imps && typeof imps[needsig] == 'function') {
refs[dep] = imps[needsig] refs[dep] = imps[needsig]
continue
}
const needTypes = typesOfSignature(needsig)
const mergedTypes = Object.assign(
{}, this.Types, this.Templates)
if (subsetOfKeys(needTypes, mergedTypes)) {
func = name // just resolve it in limbo
} else { } else {
if (full_self_referential) { // uses an unknown type, so will get an undefined impl
throw new SyntaxError( console.log(
'typed-function does not support mixed full and ' 'WARNING: partial self-reference for', name, 'to',
+ 'partial self-reference') needsig, 'uses an unknown type')
} refs[dep] = undefined
const needTypes = typesOfSignature(needsig) continue
const mergedTypes = Object.assign(
{}, this.Types, this.Templates)
if (subsetOfKeys(needTypes, mergedTypes)) {
part_self_references.push(needsig)
}
} }
} else { } else {
if (part_self_references.length) {
throw new SyntaxError(
'typed-function does not support mixed full and '
+ 'partial self-reference')
}
full_self_referential = true full_self_referential = true
} continue
} else {
if (this[func] === 'limbo') {
/* We are in the midst of bundling func */
let fallback = true
/* So the first thing we can do is try the tf_imps we are
* accumulating:
*/
if (needsig) {
let typedUniverse
let tempTF
if (Object.keys(this._TFimps[func]).length > 0) {
typedUniverse = this._typed
tempTF = typedUniverse('dummy_' + func, this._TFimps[func])
} else {
typedUniverse = this._metaTyped
tempTF = typedUniverse(
'dummy_' + func, this._metaTFimps[func])
}
let result = undefined
try {
result = typedUniverse.find(tempTF, needsig, {exact: true})
} catch {}
if (result) {
refs[dep] = result
fallback = false
}
}
if (fallback) {
/* Either we need the whole function or the signature
* we need is not available yet, so we have to use
* an indirect reference to func. And given that, there's
* really no helpful way to extract a specific signature
*/
const self = this
const redirect = function () { // is this the most efficient?
return self[func].apply(this, arguments)
}
Object.defineProperty(redirect, 'name', {value: func})
Object.defineProperty(redirect, 'fromInstance', {value: this})
refs[dep] = redirect
}
} else {
// can bundle up func, and grab its signature if need be
let destination = this[func]
if (destination && needsig) {
destination = this.resolve(func, needsig)
}
refs[dep] = destination
} }
} }
if (this[func] === 'limbo') {
/* We are in the midst of bundling func (which may be ourself) */
/* So the first thing we can do is try the tf_imps we are
* accumulating:
*/
if (needsig) {
const candidate = this.resolve(func, needsig)
if (typeof candidate === 'function') {
refs[dep] = candidate
continue
}
}
/* Either we need the whole function or the signature
* we need is not available yet, so we have to use
* an indirect reference to func. And given that, there's
* really no helpful way to extract a specific signature
*/
const self = this
const redirect = function () { // is this the most efficient?
return self[func].apply(this, arguments)
}
Object.defineProperty(redirect, 'name', {value: func})
Object.defineProperty(redirect, 'fromInstance', {value: this})
refs[dep] = redirect
continue
}
// can bundle up func, and grab its signature if need be
let destination = this[func]
if (needsig) {
destination = this.resolve(func, needsig)
}
if (!destination) {
// Unresolved reference. This is allowed so that
// you can bundle up just some portions of the library,
// but let's warn.
console.log(
'WARNING: No definition found for dependency',
dep, 'needed by', name, '(', signature, ')')
}
refs[dep] = destination
} }
if (full_self_referential) { if (full_self_referential) {
imps[signature] = this._typed.referToSelf(self => { imps[signature] = this._typed.referToSelf(self => {
@ -1335,108 +1392,27 @@ export default class PocomathInstance {
const implementation = does(refs) const implementation = does(refs)
Object.defineProperty(implementation, 'name', {value: does.name}) Object.defineProperty(implementation, 'name', {value: does.name})
implementation.fromInstance = this implementation.fromInstance = this
implementation.uses = uses
implementation.instance = asInstance
implementation.fromBehavior = fromImp
// What are we going to do with the return type info in here? // What are we going to do with the return type info in here?
return implementation return implementation
}) })
return imps[signature].uses = uses
} imps[signature].fromInstance = this
if (part_self_references.length) { imps[signature].instance = asInstance
/* There is an obstruction here. The list part_self_references imps[signature].fromBehavior = fromImp
* might contain a signature that requires conversion for self to
* handle. But I advocated this not be allowed in typed.referTo, which
* made sense for human-written functions, but is unfortunate now.
* So we have to defer creating these and correct them later, at
* least until we can add an option to typed-function.
*/
imps[signature] = {
deferred: true,
builtRefs: refs,
sigDoes: does,
fromInstance: this,
psr: part_self_references
}
return return
} }
const implementation = does(refs) const implementation = does(refs)
implementation.fromInstance = this implementation.fromInstance = this
implementation.fromBehavior = fromImp
implementation.instance = asInstance
implementation.uses = uses
// could do something with return type information here? // could do something with return type information here?
imps[signature] = implementation imps[signature] = implementation
} }
_correctPartialSelfRefs(name, imps) {
for (const aSignature in imps) {
if (!(imps[aSignature].deferred)) continue
const deferral = imps[aSignature]
const part_self_references = deferral.psr
const corrected_self_references = []
const remaining_self_references = []
const refs = deferral.builtRefs
for (const neededSig of part_self_references) {
// Have to find a match for neededSig among the other signatures
// of this function. That's a job for typed-function, but we will
// try here:
if (neededSig in imps) { // the easy case
corrected_self_references.push(neededSig)
remaining_self_references.push(neededSig)
continue
}
// No exact match, try to get one that matches with
// subtypes since the whole conversion thing in typed-function
// is too complicated to reproduce
let foundSig = this._findSubtypeImpl(name, imps, neededSig)
if (foundSig) {
corrected_self_references.push(foundSig)
remaining_self_references.push(neededSig)
} else {
// Maybe it's a template instance we don't yet have
foundSig = this._findSubtypeImpl(
name, this._imps[name], neededSig)
if (foundSig) {
const match = this._pocoFindSignature(name, neededSig)
const neededTemplate = match.fn._pocoSignature
const neededInstance = whichSigInstance(
neededSig, neededTemplate)
const neededImplementation =
this._instantiateTemplateImplementation(
name, neededTemplate, neededInstance)
if (!neededImplementation) {
refs[`self(${neededSig})`] = match.implementation
} else {
if (typeof neededImplementation === 'function') {
refs[`self(${neededSig})`] = neededImplementation
} else {
corrected_self_references.push(neededSig)
remaining_self_references.push(neededSig)
}
}
} else {
throw new Error(
'Implement inexact self-reference in typed-function for '
+ `${name}(${neededSig})`)
}
}
}
const does = deferral.sigDoes
if (remaining_self_references.length > 0) {
imps[aSignature] = this._typed.referTo(
...corrected_self_references, (...impls) => {
for (let i = 0; i < remaining_self_references.length; ++i) {
refs[`self(${remaining_self_references[i]})`] = impls[i]
}
const implementation = does(refs)
// What will we do with the return type info in here?
return implementation
}
)
} else {
imps[aSignature] = does(refs)
}
imps[aSignature]._pocoSignature = deferral._pocoSignature
imps[aSignature]._pocoInstance = deferral._pocoInstance
imps[aSignature].fromInstance = deferral.fromInstance
}
}
/* This function analyzes the template and makes sure the /* This function analyzes the template and makes sure the
* instantiations of it for type and all prior types of type are present * instantiations of it for type and all prior types of type are present
* in the instance. * in the instance.
@ -1542,7 +1518,8 @@ export default class PocomathInstance {
return wantsType return wantsType
}) })
_findSubtypeImpl(name, imps, neededSig) { _findSubtypeImpl(name, imps, neededSig, raw = false) {
const detemplate = !raw
if (neededSig in imps) return neededSig if (neededSig in imps) return neededSig
let foundSig = false let foundSig = false
const typeList = typeListOfSignature(neededSig) const typeList = typeListOfSignature(neededSig)
@ -1550,21 +1527,21 @@ export default class PocomathInstance {
const otherTypeList = typeListOfSignature(otherSig) const otherTypeList = typeListOfSignature(otherSig)
if (typeList.length !== otherTypeList.length) continue if (typeList.length !== otherTypeList.length) continue
let allMatch = true let allMatch = true
let paramBound = 'any' let paramBound = UniversalType
for (let k = 0; k < typeList.length; ++k) { for (let k = 0; k < typeList.length; ++k) {
let myType = typeList[k] let myType = typeList[k]
let otherType = otherTypeList[k] let otherType = otherTypeList[k]
if (otherType === theTemplateParam) { if (otherType === theTemplateParam) {
otherTypeList[k] = paramBound if (detemplate) otherTypeList[k] = paramBound
otherType = paramBound otherType = paramBound
} }
if (otherType === restTemplateParam) { if (otherType === restTemplateParam) {
otherTypeList[k] = `...${paramBound}` if (detemplate) otherTypeList[k] = `...${paramBound}`
otherType = paramBound otherType = paramBound
} }
const adjustedOtherType = otherType.replaceAll(templateCall, '') const adjustedOtherType = otherType.replaceAll(templateCall, '')
if (adjustedOtherType !== otherType) { if (adjustedOtherType !== otherType) {
otherTypeList[k] = adjustedOtherType if (detemplate) otherTypeList[k] = adjustedOtherType
otherType = adjustedOtherType otherType = adjustedOtherType
} }
if (myType.slice(0,3) === '...') myType = myType.slice(3) if (myType.slice(0,3) === '...') myType = myType.slice(3)
@ -1573,10 +1550,13 @@ export default class PocomathInstance {
if (otherBound) { if (otherBound) {
paramBound = otherBound[2] paramBound = otherBound[2]
otherType = paramBound otherType = paramBound
otherTypeList[k] = otherBound[1].replaceAll( if (detemplate) {
theTemplateParam, paramBound) otherTypeList[k] = otherBound[1].replaceAll(
theTemplateParam, paramBound)
}
} }
if (otherType === 'any') continue if (otherType === 'any') continue
if (otherType === UniversalType) continue
if (myType === otherType) continue if (myType === otherType) continue
if (otherType in this.Templates) { if (otherType in this.Templates) {
const [myBase] = splitTemplate(myType) const [myBase] = splitTemplate(myType)
@ -1584,7 +1564,7 @@ export default class PocomathInstance {
if (this.instantiateTemplate(otherType, myType)) { if (this.instantiateTemplate(otherType, myType)) {
let dummy let dummy
dummy = this[name] // for side effects dummy = this[name] // for side effects
return this._findSubtypeImpl(name, this._imps[name], neededSig) return this._findSubtypeImpl(name, this._imps[name], neededSig, raw)
} }
} }
if (!(otherType in this.Types)) { if (!(otherType in this.Types)) {
@ -1608,6 +1588,7 @@ export default class PocomathInstance {
typedFunction = this[name] typedFunction = this[name]
} }
const haveTF = this._typed.isTypedFunction(typedFunction) const haveTF = this._typed.isTypedFunction(typedFunction)
&& !(typedFunction.isMeta)
if (haveTF) { if (haveTF) {
// First try a direct match // First try a direct match
let result let result
@ -1622,6 +1603,10 @@ export default class PocomathInstance {
of typedFunction._typedFunctionData.signatureMap) { of typedFunction._typedFunctionData.signatureMap) {
let allMatched = true let allMatched = true
const implTypes = typeListOfSignature(implSig) const implTypes = typeListOfSignature(implSig)
if (implTypes.length > wantTypes.length) {
// Not enough arguments for that implementation
continue
}
for (let i = 0; i < wantTypes.length; ++i) { for (let i = 0; i < wantTypes.length; ++i) {
const implIndex = Math.min(i, implTypes.length - 1) const implIndex = Math.min(i, implTypes.length - 1)
let implType = implTypes[implIndex] let implType = implTypes[implIndex]
@ -1646,7 +1631,7 @@ export default class PocomathInstance {
} }
} }
if (!(this._imps[name])) return undefined if (!(this._imps[name])) return undefined
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig) const foundsig = this._findSubtypeImpl(name, this._imps[name], sig, 'raw')
if (foundsig) { if (foundsig) {
if (haveTF) { if (haveTF) {
try { try {
@ -1654,19 +1639,74 @@ export default class PocomathInstance {
} catch { } catch {
} }
} }
try { const instantiationMatcher =
return this._metaTyped.findSignature(this._meta[name], foundsig) '^'
} catch { + substituteInSignature(foundsig, theTemplateParam, '(.*)')
.replaceAll(UniversalType, '(.*)')
+ '$'
const instanceMatch = sig.match(instantiationMatcher)
let possibleInstantiator = false
if (instanceMatch) {
possibleInstantiator = instanceMatch[1]
for (let i = 2; i < instanceMatch.length; ++i) {
if (possibleInstantiator !== instanceMatch[i]) {
possibleInstantiator = false
break
}
}
}
if (possibleInstantiator) {
const behavior = this._imps[name][foundsig]
let newInstance
if (behavior) {
if (!(possibleInstantiator in behavior.hasInstantiations)) {
newInstance = this._instantiateTemplateImplementation(
name, foundsig, possibleInstantiator)
} else {
// OK, so we actually have the instantiation. Let's get it
newInstance = this._TFimps[name][sig]
}
// But we may not have taken advantage of conversions
this._invalidate(name)
const tryAgain = this[name]
let betterInstance
if (this._typed.isTypedFunction(tryAgain)) {
betterInstance = this._typed.findSignature(tryAgain, sig)
}
if (betterInstance) {
newInstance = betterInstance
} else {
newInstance = {
fn: newInstance,
implementation: newInstance
}
}
if (newInstance) return newInstance
}
}
const catchallSig = this._findSubtypeImpl(name, this._imps[name], sig)
if (catchallSig !== foundsig) {
try {
return this._metaTyped.findSignature(
this._meta[name], catchallSig)
} catch {
}
} }
// We have an implementation but not a typed function. Do the best // We have an implementation but not a typed function. Do the best
// we can: // we can:
const foundImpl = this._imps[name][foundsig] const restoredSig = foundsig.replaceAll('ground', theTemplateParam)
const foundImpl = this._imps[name][restoredSig]
const needs = {} const needs = {}
for (const dep of foundImpl.uses) { for (const dep of foundImpl.uses) {
const [base, sig] = dep.split('()') const [base, sig] = dep.split(/[()]/)
needs[dep] = this.resolve(base, sig) if (sig) {
needs[dep] = this.resolve(base, sig)
} else {
needs[dep] = this[dep]
}
} }
const pseudoImpl = foundImpl.does(needs) const pseudoImpl = foundImpl.does(needs)
pseudoImpl.fromInstance = this
return {fn: pseudoImpl, implementation: pseudoImpl} return {fn: pseudoImpl, implementation: pseudoImpl}
} }
// Hmm, no luck. Make sure bundle is up-to-date and retry: // Hmm, no luck. Make sure bundle is up-to-date and retry:

View File

@ -6,7 +6,7 @@ export function dependencyExtractor(destinationSet) {
return new Proxy({}, { return new Proxy({}, {
get: (target, property) => { get: (target, property) => {
destinationSet.add(property) destinationSet.add(property)
return {} return {checkingDependency: true}
} }
}) })
} }

View File

@ -4,5 +4,5 @@ export * from './Types/number.mjs'
export const add = { export const add = {
// Note the below assumes that all subtypes of number that will be defined // Note the below assumes that all subtypes of number that will be defined
// are closed under addition! // are closed under addition!
'T:number, T': ({T}) => Returns(T, (m,n) => m+n) 'T:number,T': ({T}) => Returns(T, (m,n) => m+n)
} }

19
src/number/cbrt.mjs Normal file
View File

@ -0,0 +1,19 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
/* Returns just the real cube root, following mathjs implementation */
export const cbrt = {
number: ({'negate(number)': neg}) => Returns('number', x => {
if (x === 0) return x
const negate = x < 0
if (negate) x = neg(x)
let result = x
if (isFinite(x)) {
result = Math.exp(Math.log(x) / 3)
result = (x / (result * result) + (2 * result)) / 3
}
if (negate) return neg(result)
return result
})
}

View File

@ -6,6 +6,7 @@ export * from './Types/number.mjs'
export {abs} from './abs.mjs' export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs' export {absquare} from './absquare.mjs'
export {add} from './add.mjs' export {add} from './add.mjs'
export {cbrt} from './cbrt.mjs'
export {compare} from './compare.mjs' export {compare} from './compare.mjs'
export const conjugate = {'T:number': identitySubTypes('number')} export const conjugate = {'T:number': identitySubTypes('number')}
export const gcd = gcdType('NumInt') export const gcd = gcdType('NumInt')

View File

@ -6,5 +6,5 @@ export {Tuple} from './Types/Tuple.mjs'
* are convertible to the same type. * are convertible to the same type.
*/ */
export const tuple = { export const tuple = {
'...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args})) '...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args}))
} }

View File

@ -45,6 +45,11 @@ describe('complex', () => {
assert.ok(!(math.equal(math.complex(45n, 3n), 45n))) assert.ok(!(math.equal(math.complex(45n, 3n), 45n)))
}) })
it('tests for reality', () => {
assert.ok(math.isReal(math.complex(3, 0)))
assert.ok(!(math.isReal(math.complex(3, 2))))
})
it('computes gcd', () => { it('computes gcd', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)), math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),

View File

@ -0,0 +1,63 @@
import assert from 'assert'
import * as approx from '../../tools/approx.mjs'
import math from '../../src/pocomath.mjs'
describe('polynomialRoot', () => {
it('should solve a linear equation with real coefficients', function () {
assert.deepEqual(math.polynomialRoot(6, 3), math.tuple(-2))
assert.deepEqual(
math.polynomialRoot(math.complex(-3, 2), 2),
math.tuple(math.complex(1.5, -1)))
assert.deepEqual(
math.polynomialRoot(math.complex(3, 1), math.complex(-1, -1)),
math.tuple(math.complex(2, -1)))
})
// Should be safe now to capture the functions:
const complex = math.complex
const pRoot = math.polynomialRoot
const tup = math.tuple
it('should solve a quadratic equation with a double root', function () {
assert.deepEqual(pRoot(4, 4, 1), tup(-2))
assert.deepEqual(
pRoot(complex(0, 2), complex(2, 2), 1), tup(complex(-1, -1)))
})
it('should solve a quadratic with two distinct roots', function () {
assert.deepEqual(pRoot(-3, 2, 1), tup(1, -3))
assert.deepEqual(pRoot(-2, 0, 1), tup(math.sqrt(2), -math.sqrt(2)))
assert.deepEqual(
pRoot(4, 2, 1),
tup(complex(-1, math.sqrt(3)), complex(-1, -math.sqrt(3))))
assert.deepEqual(
pRoot(complex(3, 1), -3, 1), tup(complex(1, 1), complex(2, -1)))
})
it('should solve a cubic with a triple root', function () {
assert.deepEqual(pRoot(8, 12, 6, 1), tup(-2))
assert.deepEqual(
pRoot(complex(-2, 11), complex(9, -12), complex(-6, 3), 1),
tup(complex(2, -1)))
})
it('should solve a cubic with one simple and one double root', function () {
assert.deepEqual(pRoot(4, 0, -3, 1), tup(-1, 2))
assert.deepEqual(
pRoot(complex(9, 9), complex(15, 6), complex(7, 1), 1),
tup(complex(-1, -1), -3))
assert.deepEqual(
pRoot(complex(0, 6), complex(6, 8), complex(5, 2), 1),
tup(-3, complex(-1, -1)))
assert.deepEqual(
pRoot(complex(2, 6), complex(8, 6), complex(5, 1), 1),
tup(complex(-3, 1), complex(-1, -1)))
})
it('should solve a cubic with three distinct roots', function () {
approx.deepEqual(pRoot(6, 11, 6, 1), tup(-3, -1, -2))
approx.deepEqual(
pRoot(-1, -2, 0, 1),
tup(-1, (1 + math.sqrt(5)) / 2, (1 - math.sqrt(5)) / 2))
approx.deepEqual(
pRoot(1, 1, 1, 1),
tup(-1, complex(0, -1), complex(0, 1)))
approx.deepEqual(
pRoot(complex(0, -10), complex(8, 12), complex(-6, -3), 1),
tup(complex(1, 1), complex(3, 1), complex(2, 1)))
})
})

46
tools/approx.mjs Normal file
View File

@ -0,0 +1,46 @@
import assert from 'assert'
export const epsilon = 1e-12
const isNumber = entity => (typeof entity === 'number')
export function equal(a, b) {
if (isNumber(a) && isNumber(b)) {
if (a === b) return true
if (isNaN(a)) return assert.strictEqual(a.toString(), b.toString())
const message = `${a} ~= ${b} (to ${epsilon})`
if (a === 0) return assert.ok(Math.abs(b) < epsilon, message)
if (b === 0) return assert.ok(Math.abs(a) < epsilon, message)
const diff = Math.abs(a - b)
const maxDiff = Math.abs(epsilon * Math.max(Math.abs(a), Math.abs(b)))
return assert.ok(diff <= maxDiff, message)
}
return assert.strictEqual(a, b)
}
export function deepEqual(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
const alen = a.length
assert.strictEqual(alen, b.length, `${a} ~= ${b}`)
for (let i = 0; i < alen; ++i) deepEqual(a[i], b[i])
return true
}
if (typeof a === 'object' && typeof b === 'object') {
for (const prop in a) {
if (a.hasOwnProperty(prop)) {
assert.ok(
b.hasOwnProperty(prop), `a[${prop}] = ${a[prop]} ~= ${b[prop]}`)
deepEqual(a[prop], b[prop])
}
}
for (const prop in b) {
if (b.hasOwnProperty(prop)) {
assert.ok(
a.hasOwnProperty(prop), `${a[prop]} ~= ${b[prop]} = b[${prop}]`)
}
}
return true
}
return equal(a, b)
}