refactor(Complex): Now a template type! #46
src
bigint
complex
Types
abs.mjsabsquare.mjsadd.mjsassociate.mjscomplex.mjsconjugate.mjsequalTT.mjsextendToComplex.mjsgcd.mjsinvert.mjsisZero.mjsmultiply.mjsquotient.mjsroundquotient.mjssqrt.mjscore
generic
number
ops
tuple/Types
test
@ -2,14 +2,18 @@ export * from './Types/bigint.mjs'
|
||||
import isqrt from 'bigint-isqrt'
|
||||
|
||||
export const sqrt = {
|
||||
bigint: ({config, complex, 'self(Complex)': complexSqrt}) => {
|
||||
bigint: ({
|
||||
config,
|
||||
'complex(bigint,bigint)': cplx,
|
||||
'negate(bigint)': neg
|
||||
}) => {
|
||||
if (config.predictable) {
|
||||
// Don't just return the constant isqrt here because the object
|
||||
// gets decorated with info that might need to be different
|
||||
// for different PocomathInstancss
|
||||
return b => isqrt(b)
|
||||
}
|
||||
if (!complexSqrt) {
|
||||
if (!cplx) {
|
||||
return b => {
|
||||
if (b >= 0n) {
|
||||
const trial = isqrt(b)
|
||||
@ -19,12 +23,16 @@ export const sqrt = {
|
||||
}
|
||||
}
|
||||
return b => {
|
||||
if (b >= 0n) {
|
||||
const trial = isqrt(b)
|
||||
if (trial * trial === b) return trial
|
||||
return undefined
|
||||
if (b === undefined) return undefined
|
||||
let real = true
|
||||
if (b < 0n) {
|
||||
b = neg(b)
|
||||
real = false
|
||||
}
|
||||
return complexSqrt(complex(b))
|
||||
const trial = isqrt(b)
|
||||
if (trial * trial !== b) return undefined
|
||||
if (real) return trial
|
||||
return cplx(0n, trial)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,30 +1,27 @@
|
||||
import PocomathInstance from '../../core/PocomathInstance.mjs'
|
||||
|
||||
/* Use a plain object with keys re and im for a complex; note the components
|
||||
* can be any type (for this proof-of-concept; in reality we'd want to
|
||||
* insist on some numeric or scalar supertype).
|
||||
*/
|
||||
function isComplex(z) {
|
||||
return z && typeof z === 'object' && 're' in z && 'im' in z
|
||||
}
|
||||
|
||||
const Complex = new PocomathInstance('Complex')
|
||||
// Base type that should generally not be used directly
|
||||
Complex.installType('Complex', {
|
||||
test: isComplex,
|
||||
from: {
|
||||
number: x => ({re: x, im: 0})
|
||||
}
|
||||
test: z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||
})
|
||||
Complex.installType('GaussianInteger', {
|
||||
test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint',
|
||||
refines: 'Complex',
|
||||
// Now the template type: Complex numbers are actually always homeogeneous
|
||||
// in their component types.
|
||||
Complex.installType('Complex<T>', {
|
||||
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
|
||||
test: testT => z => testT(z.re) && testT(z.im),
|
||||
from: {
|
||||
bigint: x => ({re: x, im: 0n})
|
||||
T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
|
||||
U: convert => u => {
|
||||
const t = convert(u)
|
||||
return ({re: t, im: t-t})
|
||||
},
|
||||
'Complex<U>': convert => cu => ({re: convert(cu.re), im: convert(cu.im)})
|
||||
}
|
||||
})
|
||||
|
||||
Complex.promoteUnary = {
|
||||
Complex: ({self,complex}) => z => complex(self(z.re), self(z.im))
|
||||
'Complex<T>': ({'self(T)': me, complex}) => z => complex(me(z.re), me(z.im))
|
||||
}
|
||||
|
||||
export {Complex}
|
||||
|
@ -1,5 +1,8 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const abs = {
|
||||
Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z))
|
||||
'Complex<T>': ({
|
||||
'sqrt(T)': sqt,
|
||||
'absquare(Complex<T>)': absq
|
||||
}) => z => sqt(absq(z))
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const absquare = {
|
||||
Complex: ({add, square}) => z => add(square(z.re), square(z.im))
|
||||
'Complex<T>': ({
|
||||
'add(T,T)': plus,
|
||||
'square(T)': sqr
|
||||
}) => z => plus(sqr(z.re), sqr(z.im))
|
||||
}
|
||||
|
@ -1,22 +1,8 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const add = {
|
||||
/* Relying on conversions for both complex + number and complex + bigint
|
||||
* leads to an infinite loop when adding a number and a bigint, since they
|
||||
* both convert to Complex.
|
||||
*/
|
||||
'Complex,number': ({
|
||||
'self(number,number)': addNum,
|
||||
'complex(number,number)': cplx
|
||||
}) => (z,x) => cplx(addNum(z.re, x), z.im),
|
||||
|
||||
'Complex,bigint': ({
|
||||
'self(bigint,bigint)': addBigInt,
|
||||
'complex(bigint,bigint)': cplx
|
||||
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
|
||||
|
||||
'Complex,Complex': ({
|
||||
self,
|
||||
complex
|
||||
}) => (w,z) => complex(self(w.re, z.re), self(w.im, z.im))
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'self(T,T)': me,
|
||||
'complex(T,T)': cplx
|
||||
}) => (w,z) => cplx(me(w.re, z.re), me(w.im, z.im))
|
||||
}
|
||||
|
@ -2,16 +2,16 @@ export * from './Types/Complex.mjs'
|
||||
|
||||
/* Returns true if w is z multiplied by a complex unit */
|
||||
export const associate = {
|
||||
'Complex,Complex': ({
|
||||
'multiply(Complex,Complex)': times,
|
||||
'equalTT(Complex,Complex)': eq,
|
||||
zero,
|
||||
one,
|
||||
complex,
|
||||
'negate(Complex)': neg
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'multiply(Complex<T>,Complex<T>)': times,
|
||||
'equalTT(Complex<T>,Complex<T>)': eq,
|
||||
'zero(T)': zr,
|
||||
'one(T)': uno,
|
||||
'complex(T,T)': cplx,
|
||||
'negate(Complex<T>)': neg
|
||||
}) => (w,z) => {
|
||||
if (eq(w,z) || eq(w,neg(z))) return true
|
||||
const ti = times(z, complex(zero(z.re), one(z.im)))
|
||||
const ti = times(z, cplx(zr(z.re), uno(z.im)))
|
||||
return eq(w,ti) || eq(w,neg(ti))
|
||||
}
|
||||
}
|
||||
|
@ -12,5 +12,9 @@ export const complex = {
|
||||
'undefined,undefined': () => (u, v) => u,
|
||||
'T,T': () => (x, y) => ({re: x, im: y}),
|
||||
/* Take advantage of conversions in typed-function */
|
||||
Complex: () => z => z
|
||||
// 'Complex<T>': () => z => z
|
||||
/* But help out because without templates built in to typed-function,
|
||||
* type inference turns out to be too hard
|
||||
*/
|
||||
'T': ({'zero(T)': zr}) => x => ({re: x, im: zr(x)})
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const conjugate = {
|
||||
Complex: ({negate, complex}) => z => complex(z.re, negate(z.im))
|
||||
'Complex<T>': ({
|
||||
'negate(T)': neg,
|
||||
'complex(T,T)': cplx
|
||||
}) => z => cplx(z.re, neg(z.im))
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,26 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const equalTT = {
|
||||
'Complex,number': ({
|
||||
'isZero(number)': isZ,
|
||||
'self(number,number)': eqNum
|
||||
}) => (z, x) => eqNum(z.re, x) && isZ(z.im),
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'self(T,T)': me
|
||||
}) => (w,z) => me(w.re, z.re) && me(w.im, z.im),
|
||||
// NOTE: Although I do not understand exactly why, with typed-function@3.0's
|
||||
// matching algorithm, the above template must come first to ensure the
|
||||
// most specific match to a template call. I.e, if one of the below
|
||||
// comes first, a call with two complex numbers can match via conversions
|
||||
// with (Complex<Complex<number>>, Complex<number>) (!, hopefully in some
|
||||
// future iteration typed-function will be smart enough to prefer
|
||||
// Complex<T>, Complex<T>. Possibly the problem is in Pocomath's bolted-on
|
||||
// type resolution and the difficulty will go away when features are moved into
|
||||
// typed-function.
|
||||
'Complex<T>,T': ({
|
||||
'isZero(T)': isZ,
|
||||
'self(T,T)': eqReal
|
||||
}) => (z, x) => eqReal(z.re, x) && isZ(z.im),
|
||||
|
||||
'Complex,bigint': ({
|
||||
'isZero(bigint)': isZ,
|
||||
'self(bigint,bigint)': eqBigInt
|
||||
}) => (z, b) => eqBigInt(z.re, b) && isZ(z.im),
|
||||
'T,Complex<T>': ({
|
||||
'isZero(T)': isZ,
|
||||
'self(T,T)': eqReal
|
||||
}) => (b, z) => eqReal(z.re, b) && isZ(z.im),
|
||||
|
||||
'Complex,Complex': ({self}) => (w,z) => self(w.re, z.re) && self(w.im, z.im),
|
||||
|
||||
'GaussianInteger,GaussianInteger': ({
|
||||
'self(bigint,bigint)': eq
|
||||
}) => (a,b) => eq(a.re, b.re) && eq(a.im, b.im)
|
||||
}
|
||||
|
@ -15,4 +15,17 @@ export default async function extendToComplex(pmath) {
|
||||
// Guess it wasn't a method available in complex; no worries
|
||||
}
|
||||
}
|
||||
// Since extension to complex was specifically requested, instantiate
|
||||
// all of the templates so that the associated type conversions will
|
||||
// be available to make function calls work immediately:
|
||||
for (const baseType in pmath.Types) {
|
||||
if (baseType in pmath.Templates || baseType.includes('<')) {
|
||||
continue // don't mess with templates
|
||||
}
|
||||
const ignore = new Set(['undefined', 'any', 'T', 'ground'])
|
||||
if (ignore.has(baseType)) continue
|
||||
// (What we really want is a check for "numeric" types but we don't
|
||||
// have that concept (yet?)). If we did, we'd instantiate just for those...
|
||||
pmath.instantiateTemplate('Complex', baseType)
|
||||
}
|
||||
}
|
||||
|
@ -2,16 +2,20 @@ import PocomathInstance from '../core/PocomathInstance.mjs'
|
||||
import * as Complex from './Types/Complex.mjs'
|
||||
import gcdType from '../generic/gcdType.mjs'
|
||||
|
||||
const gcdComplexRaw = {}
|
||||
Object.assign(gcdComplexRaw, gcdType('Complex<bigint>'))
|
||||
Object.assign(gcdComplexRaw, gcdType('Complex<NumInt>'))
|
||||
const imps = {
|
||||
gcdGIRaw: gcdType('GaussianInteger'),
|
||||
gcdComplexRaw,
|
||||
gcd: { // Only return gcds with positive real part
|
||||
'GaussianInteger,GaussianInteger': ({
|
||||
'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw,
|
||||
'sign(bigint)': sgn,
|
||||
'negate(GaussianInteger)': neg
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
|
||||
'sign(T)': sgn,
|
||||
'one(T)': uno,
|
||||
'negate(Complex<T>)': neg
|
||||
}) => (z,m) => {
|
||||
const raw = gcdRaw(z, m)
|
||||
if (sgn(raw.re) === 1n) return raw
|
||||
if (sgn(raw.re) === uno(raw.re)) return raw
|
||||
return neg(raw)
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const invert = {
|
||||
Complex: ({conjugate, absquare, complex, divide}) => z => {
|
||||
const c = conjugate(z)
|
||||
const d = absquare(z)
|
||||
return complex(divide(c.re, d), divide(c.im, d))
|
||||
'Complex<T>': ({
|
||||
'conjugate(Complex<T>)': conj,
|
||||
'absquare(Complex<T>)': asq,
|
||||
'complex(T,T)': cplx,
|
||||
'divide(T,T)': div
|
||||
}) => z => {
|
||||
const c = conj(z)
|
||||
const d = asq(z)
|
||||
return cplx(div(c.re, d), div(c.im, d))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const isZero = {
|
||||
Complex: ({self}) => z => self(z.re) && self(z.im)
|
||||
'Complex<T>': ({'self(T)': me}) => z => me(z.re) && me(z.im)
|
||||
}
|
||||
|
@ -1,14 +1,15 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const multiply = {
|
||||
'Complex,Complex': ({
|
||||
'complex(any,any)': cplx,
|
||||
add,
|
||||
subtract,
|
||||
self
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'complex(T,T)': cplx,
|
||||
'add(T,T)': plus,
|
||||
'subtract(T,T)': sub,
|
||||
'self(T,T)': me,
|
||||
'conjugate(T)': conj // makes quaternion multiplication work
|
||||
}) => (w,z) => {
|
||||
return cplx(
|
||||
subtract(self(w.re, z.re), self(w.im, z.im)),
|
||||
add(self(w.re, z.im), self(w.im, z.re)))
|
||||
sub(me(w.re, z.re), me(conj(w.im), z.im)),
|
||||
plus(me(conj(w.re), z.im), me(w.im, z.re)))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
export * from './roundquotient.mjs'
|
||||
|
||||
export const quotient = {
|
||||
'Complex,Complex': ({roundquotient}) => (w,z) => roundquotient(w,z)
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'roundquotient(Complex<T>,Complex<T>)': rq
|
||||
}) => (w,z) => rq(w,z)
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const roundquotient = {
|
||||
'Complex,Complex': ({
|
||||
'isZero(Complex)': isZ,
|
||||
conjugate,
|
||||
'multiply(Complex,Complex)': mult,
|
||||
absquare,
|
||||
self,
|
||||
complex
|
||||
'Complex<T>,Complex<T>': ({
|
||||
'isZero(Complex<T>)': isZ,
|
||||
'conjugate(Complex<T>)': conj,
|
||||
'multiply(Complex<T>,Complex<T>)': mult,
|
||||
'absquare(Complex<T>)': asq,
|
||||
'self(T,T)': me,
|
||||
'complex(T,T)': cplx
|
||||
}) => (n,d) => {
|
||||
if (isZ(d)) return d
|
||||
const cnum = mult(n, conjugate(d))
|
||||
const dreal = absquare(d)
|
||||
return complex(self(cnum.re, dreal), self(cnum.im, dreal))
|
||||
const cnum = mult(n, conj(d))
|
||||
const dreal = asq(d)
|
||||
return cplx(me(cnum.re, dreal), me(cnum.im, dreal))
|
||||
}
|
||||
}
|
||||
|
@ -1,38 +1,39 @@
|
||||
export * from './Types/Complex.mjs'
|
||||
|
||||
export const sqrt = {
|
||||
Complex: ({
|
||||
'Complex<T>': ({
|
||||
config,
|
||||
isZero,
|
||||
sign,
|
||||
one,
|
||||
add,
|
||||
complex,
|
||||
multiply,
|
||||
self,
|
||||
divide,
|
||||
'abs(Complex)': abs,
|
||||
subtract
|
||||
'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,
|
||||
'abs(Complex<T>)': absC,
|
||||
'subtract(T,T)': sub
|
||||
}) => {
|
||||
if (config.predictable) {
|
||||
return z => {
|
||||
const reOne = one(z.re)
|
||||
if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re))
|
||||
const reTwo = add(reOne, reOne)
|
||||
return complex(
|
||||
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
|
||||
self(divide(subtract(abs(z),z.re), reTwo))
|
||||
const reOne = uno(z.re)
|
||||
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re))
|
||||
const reTwo = plus(reOne, reOne)
|
||||
return cplxB(
|
||||
mult(sgn(z.im), me(div(plus(absC(z),z.re), reTwo))),
|
||||
me(div(sub(absC(z),z.re), reTwo))
|
||||
)
|
||||
}
|
||||
}
|
||||
return z => {
|
||||
const reOne = one(z.re)
|
||||
if (isZero(z.im) && sign(z.re) === reOne) return self(z.re)
|
||||
const reTwo = add(reOne, reOne)
|
||||
return complex(
|
||||
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
|
||||
self(divide(subtract(abs(z),z.re), reTwo))
|
||||
)
|
||||
const reOne = uno(z.re)
|
||||
if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re)
|
||||
const reTwo = plus(reOne, reOne)
|
||||
const reSqrt = me(div(plus(absC(z),z.re), reTwo))
|
||||
const imSqrt = me(div(sub(absC(z),z.re), reTwo))
|
||||
if (reSqrt === undefined || imSqrt === undefined) return undefined
|
||||
return cplxB(mult(sgn(z.im), reSqrt), imSqrt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ export default class PocomathInstance {
|
||||
'importDependencies',
|
||||
'install',
|
||||
'installType',
|
||||
'instantiateTemplate',
|
||||
'joinTypes',
|
||||
'name',
|
||||
'self',
|
||||
@ -436,7 +437,12 @@ export default class PocomathInstance {
|
||||
}
|
||||
return
|
||||
}
|
||||
// Nothing actually happens until we match a template parameter
|
||||
// update the typeOf function
|
||||
const imp = {}
|
||||
imp[type] = {uses: new Set(['T']), does: ({T}) => () => `${base}<${T}>`}
|
||||
this._installFunctions({typeOf: imp})
|
||||
|
||||
// Nothing else actually happens until we match a template parameter
|
||||
this.Templates[base] = {type, spec}
|
||||
}
|
||||
|
||||
@ -767,8 +773,9 @@ export default class PocomathInstance {
|
||||
const subsig = substituteInSig(
|
||||
needsig, theTemplateParam, instantiateFor)
|
||||
let resname = simplifiedDep
|
||||
if (resname === 'self') resname = name
|
||||
innerRefs[dep] = self._pocoresolve(resname, subsig)
|
||||
if (resname == 'self') resname = name
|
||||
innerRefs[dep] = self._pocoresolve(
|
||||
resname, subsig, refs[simplifiedDep])
|
||||
} else {
|
||||
innerRefs[dep] = refs[simplifiedDep]
|
||||
}
|
||||
@ -782,7 +789,7 @@ export default class PocomathInstance {
|
||||
this._addTFimplementation(
|
||||
tf_imps, signature, {uses: outerUses, does: patch})
|
||||
}
|
||||
this._correctPartialSelfRefs(tf_imps)
|
||||
this._correctPartialSelfRefs(name, tf_imps)
|
||||
const tf = this._typed(name, tf_imps)
|
||||
Object.defineProperty(this, name, {configurable: true, value: tf})
|
||||
return tf
|
||||
@ -845,7 +852,7 @@ export default class PocomathInstance {
|
||||
} else {
|
||||
// can bundle up func, and grab its signature if need be
|
||||
let destination = this[func]
|
||||
if (needsig) {
|
||||
if (destination &&needsig) {
|
||||
destination = this._pocoresolve(func, needsig)
|
||||
}
|
||||
refs[dep] = destination
|
||||
@ -878,7 +885,7 @@ export default class PocomathInstance {
|
||||
imps[signature] = does(refs)
|
||||
}
|
||||
|
||||
_correctPartialSelfRefs(imps) {
|
||||
_correctPartialSelfRefs(name, imps) {
|
||||
for (const aSignature in imps) {
|
||||
if (!(imps[aSignature].deferred)) continue
|
||||
const part_self_references = imps[aSignature].psr
|
||||
@ -894,7 +901,7 @@ export default class PocomathInstance {
|
||||
// No exact match, try to get one that matches with
|
||||
// subtypes since the whole conversion thing in typed-function
|
||||
// is too complicated to reproduce
|
||||
const foundSig = this._findSubtypeImpl(imps, neededSig)
|
||||
const foundSig = this._findSubtypeImpl(name, imps, neededSig)
|
||||
if (foundSig) {
|
||||
corrected_self_references.push(foundSig)
|
||||
} else {
|
||||
@ -936,7 +943,7 @@ export default class PocomathInstance {
|
||||
}
|
||||
const resultingTypes = new Set()
|
||||
for (const iType of instantiations) {
|
||||
const resultType = this._maybeAddTemplateType(base, iType)
|
||||
const resultType = this.instantiateTemplate(base, iType)
|
||||
if (resultType) resultingTypes.add(resultType)
|
||||
}
|
||||
return resultingTypes
|
||||
@ -946,7 +953,7 @@ export default class PocomathInstance {
|
||||
* instantiator to the Types of this instance, if it hasn't happened already.
|
||||
* Returns the name of the type if added, false otherwise.
|
||||
*/
|
||||
_maybeAddTemplateType(base, instantiator) {
|
||||
instantiateTemplate(base, instantiator) {
|
||||
const wantsType = `${base}<${instantiator}>`
|
||||
if (wantsType in this.Types) return false
|
||||
// OK, need to generate the type from the template
|
||||
@ -956,7 +963,7 @@ export default class PocomathInstance {
|
||||
const template = this.Templates[base].spec
|
||||
if (!template) {
|
||||
throw new Error(
|
||||
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`)
|
||||
`Implementor error in instantiateTemplate(${base}, ${instantiator})`)
|
||||
}
|
||||
const instantiatorSpec = this.Types[instantiator]
|
||||
let beforeTypes = []
|
||||
@ -1010,7 +1017,7 @@ export default class PocomathInstance {
|
||||
return wantsType
|
||||
}
|
||||
|
||||
_findSubtypeImpl(imps, neededSig) {
|
||||
_findSubtypeImpl(name, imps, neededSig) {
|
||||
if (neededSig in imps) return neededSig
|
||||
let foundSig = false
|
||||
const typeList = typeListOfSignature(neededSig)
|
||||
@ -1047,6 +1054,13 @@ export default class PocomathInstance {
|
||||
|| this._subtypes[otherType].has(myType)) {
|
||||
continue
|
||||
}
|
||||
if (otherType in this.Templates) {
|
||||
if (this.instantiateTemplate(otherType, myType)) {
|
||||
let dummy
|
||||
dummy = this[name] // for side effects
|
||||
return this._findSubtypeImpl(name, this._imps[name], neededSig)
|
||||
}
|
||||
}
|
||||
allMatch = false
|
||||
break
|
||||
}
|
||||
@ -1058,17 +1072,28 @@ export default class PocomathInstance {
|
||||
return foundSig
|
||||
}
|
||||
|
||||
_pocoresolve(name, sig) {
|
||||
const typedfunc = this[name]
|
||||
_pocoresolve(name, sig, typedFunction) {
|
||||
if (!this._typed.isTypedFunction(typedFunction)) {
|
||||
typedFunction = this[name]
|
||||
}
|
||||
let result = undefined
|
||||
try {
|
||||
result = this._typed.find(typedfunc, sig, {exact: true})
|
||||
result = this._typed.find(typedFunction, sig, {exact: true})
|
||||
} catch {
|
||||
}
|
||||
if (result) return result
|
||||
const foundsig = this._findSubtypeImpl(this._imps[name], sig)
|
||||
if (foundsig) return this._typed.find(typedfunc, foundsig)
|
||||
return this._typed.find(typedfunc, sig)
|
||||
const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
|
||||
if (foundsig) return this._typed.find(typedFunction, foundsig)
|
||||
// Make sure bundle is up-to-date:
|
||||
typedFunction = this[name]
|
||||
try {
|
||||
result = this._typed.find(typedFunction, sig)
|
||||
} catch {
|
||||
}
|
||||
if (result) return result
|
||||
// total punt, revert to typed-function resolution on every call;
|
||||
// hopefully this happens rarely:
|
||||
return typedFunction
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ export const reducingOperation = {
|
||||
'undefined': () => u => u,
|
||||
'undefined,...any': () => (u, rest) => u,
|
||||
'any,undefined': () => (x, u) => u,
|
||||
'undefined,undefined': () => (u,v) => u,
|
||||
any: () => x => x,
|
||||
'any,any,...any': ({
|
||||
self
|
||||
|
@ -1,14 +1,17 @@
|
||||
export * from './Types/number.mjs'
|
||||
|
||||
export const sqrt = {
|
||||
number: ({config, complex, 'self(Complex)': complexSqrt}) => {
|
||||
if (config.predictable || !complexSqrt) {
|
||||
number: ({
|
||||
config,
|
||||
'complex(number,number)': cplx,
|
||||
'negate(number)': neg}) => {
|
||||
if (config.predictable || !cplx) {
|
||||
return n => isNaN(n) ? NaN : Math.sqrt(n)
|
||||
}
|
||||
return n => {
|
||||
if (isNaN(n)) return NaN
|
||||
if (n >= 0) return Math.sqrt(n)
|
||||
return complexSqrt(complex(n))
|
||||
return cplx(0, Math.sqrt(neg(n)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import {Complex} from '../complex/Types/Complex.mjs'
|
||||
export const floor = {
|
||||
bigint: () => x => x,
|
||||
NumInt: () => x => x, // Because Pocomath isn't part of typed-function, or
|
||||
GaussianInteger: () => x => x, // at least have access to the real
|
||||
'Complex<bigint>': () => x => x, // at least have access to the real
|
||||
// typed-function parse, we unfortunately can't coalesce these into one
|
||||
// entry with type `bigint|NumInt|GaussianInteger` because they couldn't
|
||||
// be separately activated then
|
||||
@ -17,7 +17,7 @@ export const floor = {
|
||||
return Math.floor(n)
|
||||
},
|
||||
|
||||
Complex: Complex.promoteUnary.Complex,
|
||||
'Complex<T>': Complex.promoteUnary['Complex<T>'],
|
||||
|
||||
// OK to include a type totally not in Pocomath yet, it'll never be
|
||||
// activated.
|
||||
|
@ -32,7 +32,12 @@ Tuple.installType('Tuple<T>', {
|
||||
})
|
||||
|
||||
Tuple.promoteUnary = {
|
||||
'Tuple<T>': ({'self(T)': me, tuple}) => t => tuple(...(t.elts.map(me)))
|
||||
'Tuple<T>': ({
|
||||
'self(T)': me,
|
||||
tuple
|
||||
}) => t => tuple(...(t.elts.map(x => me(x)))) // NOTE: this must use
|
||||
// the inner arrow function to drop additional arguments that Array.map
|
||||
// supplies, as otherwise the wrong signature of `me` might be used.
|
||||
}
|
||||
|
||||
Tuple.promoteBinaryUnary = {
|
||||
|
@ -16,8 +16,8 @@ describe('The default full pocomath instance "math"', () => {
|
||||
assert.strictEqual(math.typeOf(-1.5), 'number')
|
||||
assert.strictEqual(math.typeOf(-42n), 'bigint')
|
||||
assert.strictEqual(math.typeOf(undefined), 'undefined')
|
||||
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger')
|
||||
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex')
|
||||
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
|
||||
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
|
||||
})
|
||||
|
||||
it('can subtract numbers', () => {
|
||||
@ -105,11 +105,9 @@ describe('The default full pocomath instance "math"', () => {
|
||||
|
||||
it('calculates multi-way gcds and lcms', () => {
|
||||
assert.strictEqual(math.gcd(30,105,42), 3)
|
||||
assert.ok(
|
||||
math.associate(
|
||||
math.lcm(
|
||||
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)),
|
||||
math.complex(1n,3n)))
|
||||
const gaussianLCM = math.lcm(
|
||||
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n))
|
||||
assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true)
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -49,6 +49,15 @@ describe('complex', () => {
|
||||
assert.deepStrictEqual(
|
||||
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
|
||||
math.complex(4n, 5n))
|
||||
// And now works for NumInt, too!
|
||||
assert.deepStrictEqual(
|
||||
math.gcd(math.complex(53,56), math.complex(47, -13)),
|
||||
math.complex(4, 5))
|
||||
// But properly fails for general complex
|
||||
assert.throws(
|
||||
() => math.gcd(math.complex(5.3,5.6), math.complex(4.7, -1.3)),
|
||||
TypeError
|
||||
)
|
||||
})
|
||||
|
||||
it('computes floor', () => {
|
||||
|
@ -3,12 +3,14 @@ import math from '../src/pocomath.mjs'
|
||||
import PocomathInstance from '../src/core/PocomathInstance.mjs'
|
||||
import * as numbers from '../src/number/all.mjs'
|
||||
import * as numberAdd from '../src/number/add.mjs'
|
||||
import * as numberZero from '../src/number/zero.mjs'
|
||||
import {add as genericAdd} from '../src/generic/arithmetic.mjs'
|
||||
import * as complex from '../src/complex/all.mjs'
|
||||
import * as complexAdd from '../src/complex/add.mjs'
|
||||
import * as complexNegate from '../src/complex/negate.mjs'
|
||||
import * as complexComplex from '../src/complex/complex.mjs'
|
||||
import * as bigintAdd from '../src/bigint/add.mjs'
|
||||
import * as bigintZero from '../src/bigint/zero.mjs'
|
||||
import * as concreteSubtract from '../src/generic/subtract.concrete.mjs'
|
||||
import * as genericSubtract from '../src/generic/subtract.mjs'
|
||||
import extendToComplex from '../src/complex/extendToComplex.mjs'
|
||||
@ -54,9 +56,9 @@ describe('A custom instance', () => {
|
||||
math.complex(-5, -1))
|
||||
// And now floor has been activated for Complex as well, since the type
|
||||
// is present
|
||||
assert.deepStrictEqual(
|
||||
pm.floor(math.complex(1.9, 0)),
|
||||
math.complex(1))
|
||||
const fracComplex = math.complex(1.9, 0)
|
||||
const intComplex = math.complex(1)
|
||||
assert.deepStrictEqual(pm.floor(fracComplex), intComplex)
|
||||
// And the chain functions refresh themselves:
|
||||
assert.deepStrictEqual(
|
||||
pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7))
|
||||
@ -65,10 +67,11 @@ describe('A custom instance', () => {
|
||||
it("can defer definition of (even used) types", () => {
|
||||
const dt = new PocomathInstance('Deferred Types')
|
||||
dt.install(numberAdd)
|
||||
dt.install(numberZero) // for promoting numbers to complex, to fill in im
|
||||
dt.install({times: {
|
||||
'number,number': () => (m,n) => m*n,
|
||||
'Complex,Complex': ({complex}) => (w,z) => {
|
||||
return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re)
|
||||
'Complex<T>,Complex<T>': ({'complex(T,T)': cplx}) => (w,z) => {
|
||||
return cplx(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re)
|
||||
}
|
||||
}})
|
||||
// complex type not present but should still be able to add numbers:
|
||||
@ -82,6 +85,7 @@ describe('A custom instance', () => {
|
||||
it("can selectively import in cute ways", async function () {
|
||||
const cherry = new PocomathInstance('cherry')
|
||||
cherry.install(numberAdd)
|
||||
cherry.install(numberZero) // for complex promotion
|
||||
await extendToComplex(cherry)
|
||||
cherry.install({add: genericAdd})
|
||||
/* Now we have an instance that supports addition for number and complex
|
||||
@ -124,12 +128,13 @@ describe('A custom instance', () => {
|
||||
inst.install(complexAdd)
|
||||
inst.install(complexComplex)
|
||||
inst.install(bigintAdd)
|
||||
inst.install(bigintZero) // for complex promotion
|
||||
assert.strictEqual(
|
||||
inst.typeMerge(6n, inst.complex(3n, 2n)),
|
||||
'Merge to GaussianInteger')
|
||||
'Merge to Complex<bigint>')
|
||||
assert.strictEqual(
|
||||
inst.typeMerge(3, inst.complex(4.5,2.1)),
|
||||
'Merge to Complex')
|
||||
'Merge to Complex<number>')
|
||||
// The following is the current behavior, since 3 converts to 3+0i
|
||||
// which is technically the same Complex type as 3n+0ni.
|
||||
// This should clear up when Complex is templatized
|
||||
|
Loading…
Reference in New Issue
Block a user