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:
parent
31add66f4c
commit
0dbb95bbbe
@ -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
7
src/complex/arg.mjs
Normal 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
28
src/complex/cbrtc.mjs
Normal 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
9
src/complex/cis.mjs
Normal 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
7
src/complex/isReal.mjs
Normal 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)))
|
||||||
|
}
|
@ -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'
|
||||||
|
|
||||||
|
118
src/complex/polynomialRoot.mjs
Normal file
118
src/complex/polynomialRoot.mjs
Normal 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)}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
@ -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
41
src/complex/sqrtc.mjs
Normal 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))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
} else {
|
|| (reasons.has(func) && !(sig in this._TFimps[func]))
|
||||||
for (const dep of behavior.uses) {
|
})) {
|
||||||
if (dep.includes(reason)) {
|
// Invalidate this implementation:
|
||||||
invalidated = true
|
delete this._TFimps[name][signature]
|
||||||
break
|
const behavior = imp.fromBehavior
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (invalidated) {
|
|
||||||
if (behavior.explicit) {
|
if (behavior.explicit) {
|
||||||
if (behavior.resolved) delete this._TFimps[signature]
|
|
||||||
behavior.resolved = false
|
behavior.resolved = false
|
||||||
} else {
|
} else {
|
||||||
for (const fullSig
|
delete behavior.hasInstantiations[imp.instance]
|
||||||
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)
|
||||||
}
|
}
|
||||||
@ -901,14 +940,13 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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,60 +1320,43 @@ 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]
|
||||||
} else {
|
continue
|
||||||
if (full_self_referential) {
|
|
||||||
throw new SyntaxError(
|
|
||||||
'typed-function does not support mixed full and '
|
|
||||||
+ 'partial self-reference')
|
|
||||||
}
|
}
|
||||||
const needTypes = typesOfSignature(needsig)
|
const needTypes = typesOfSignature(needsig)
|
||||||
const mergedTypes = Object.assign(
|
const mergedTypes = Object.assign(
|
||||||
{}, this.Types, this.Templates)
|
{}, this.Types, this.Templates)
|
||||||
if (subsetOfKeys(needTypes, mergedTypes)) {
|
if (subsetOfKeys(needTypes, mergedTypes)) {
|
||||||
part_self_references.push(needsig)
|
func = name // just resolve it in limbo
|
||||||
}
|
} else {
|
||||||
|
// uses an unknown type, so will get an undefined impl
|
||||||
|
console.log(
|
||||||
|
'WARNING: partial self-reference for', name, 'to',
|
||||||
|
needsig, 'uses an unknown type')
|
||||||
|
refs[dep] = undefined
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
} 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') {
|
if (this[func] === 'limbo') {
|
||||||
/* We are in the midst of bundling func */
|
/* We are in the midst of bundling func (which may be ourself) */
|
||||||
let fallback = true
|
|
||||||
/* So the first thing we can do is try the tf_imps we are
|
/* So the first thing we can do is try the tf_imps we are
|
||||||
* accumulating:
|
* accumulating:
|
||||||
*/
|
*/
|
||||||
if (needsig) {
|
if (needsig) {
|
||||||
let typedUniverse
|
const candidate = this.resolve(func, needsig)
|
||||||
let tempTF
|
if (typeof candidate === 'function') {
|
||||||
if (Object.keys(this._TFimps[func]).length > 0) {
|
refs[dep] = candidate
|
||||||
typedUniverse = this._typed
|
continue
|
||||||
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
|
/* Either we need the whole function or the signature
|
||||||
* we need is not available yet, so we have to use
|
* we need is not available yet, so we have to use
|
||||||
* an indirect reference to func. And given that, there's
|
* an indirect reference to func. And given that, there's
|
||||||
@ -1318,125 +1369,50 @@ export default class PocomathInstance {
|
|||||||
Object.defineProperty(redirect, 'name', {value: func})
|
Object.defineProperty(redirect, 'name', {value: func})
|
||||||
Object.defineProperty(redirect, 'fromInstance', {value: this})
|
Object.defineProperty(redirect, 'fromInstance', {value: this})
|
||||||
refs[dep] = redirect
|
refs[dep] = redirect
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// can bundle up func, and grab its signature if need be
|
// can bundle up func, and grab its signature if need be
|
||||||
let destination = this[func]
|
let destination = this[func]
|
||||||
if (destination && needsig) {
|
if (needsig) {
|
||||||
destination = this.resolve(func, 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
|
refs[dep] = destination
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
if (full_self_referential) {
|
if (full_self_referential) {
|
||||||
imps[signature] = this._typed.referToSelf(self => {
|
imps[signature] = this._typed.referToSelf(self => {
|
||||||
refs.self = self
|
refs.self = self
|
||||||
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
|
||||||
|
if (detemplate) {
|
||||||
otherTypeList[k] = otherBound[1].replaceAll(
|
otherTypeList[k] = otherBound[1].replaceAll(
|
||||||
theTemplateParam, paramBound)
|
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 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const instantiationMatcher =
|
||||||
|
'^'
|
||||||
|
+ 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 {
|
try {
|
||||||
return this._metaTyped.findSignature(this._meta[name], foundsig)
|
return this._metaTyped.findSignature(
|
||||||
|
this._meta[name], catchallSig)
|
||||||
} catch {
|
} 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(/[()]/)
|
||||||
|
if (sig) {
|
||||||
needs[dep] = this.resolve(base, 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:
|
||||||
|
@ -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}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
19
src/number/cbrt.mjs
Normal 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -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')
|
||||||
|
@ -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)),
|
||||||
|
63
test/complex/_polynomialRoot.mjs
Normal file
63
test/complex/_polynomialRoot.mjs
Normal 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
46
tools/approx.mjs
Normal 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)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user