refactor(Complex): Now a template type! #46

Merged
glen merged 1 commits from template_complex into main 2022-08-06 15:42:19 +00:00
25 changed files with 240 additions and 157 deletions
Showing only changes of commit 1444b9828f - Show all commits

View File

@ -2,14 +2,18 @@ export * from './Types/bigint.mjs'
import isqrt from 'bigint-isqrt' import isqrt from 'bigint-isqrt'
export const sqrt = { export const sqrt = {
bigint: ({config, complex, 'self(Complex)': complexSqrt}) => { bigint: ({
config,
'complex(bigint,bigint)': cplx,
'negate(bigint)': neg
}) => {
if (config.predictable) { if (config.predictable) {
// Don't just return the constant isqrt here because the object // Don't just return the constant isqrt here because the object
// gets decorated with info that might need to be different // gets decorated with info that might need to be different
// for different PocomathInstancss // for different PocomathInstancss
return b => isqrt(b) return b => isqrt(b)
} }
if (!complexSqrt) { if (!cplx) {
return b => { return b => {
if (b >= 0n) { if (b >= 0n) {
const trial = isqrt(b) const trial = isqrt(b)
@ -19,12 +23,16 @@ export const sqrt = {
} }
} }
return b => { return b => {
if (b >= 0n) { if (b === undefined) return undefined
let real = true
if (b < 0n) {
b = neg(b)
real = false
}
const trial = isqrt(b) const trial = isqrt(b)
if (trial * trial === b) return trial if (trial * trial !== b) return undefined
return undefined if (real) return trial
} return cplx(0n, trial)
return complexSqrt(complex(b))
} }
} }
} }

View File

@ -1,30 +1,27 @@
import PocomathInstance from '../../core/PocomathInstance.mjs' 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') const Complex = new PocomathInstance('Complex')
// Base type that should generally not be used directly
Complex.installType('Complex', { Complex.installType('Complex', {
test: isComplex, test: z => z && typeof z === 'object' && 're' in z && 'im' in z
from: {
number: x => ({re: x, im: 0})
}
}) })
Complex.installType('GaussianInteger', { // Now the template type: Complex numbers are actually always homeogeneous
test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint', // in their component types.
refines: 'Complex', 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: { 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.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} export {Complex}

View File

@ -1,5 +1,8 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const abs = { 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))
} }

View File

@ -1,5 +1,8 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const absquare = { 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))
} }

View File

@ -1,22 +1,8 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const add = { export const add = {
/* Relying on conversions for both complex + number and complex + bigint 'Complex<T>,Complex<T>': ({
* leads to an infinite loop when adding a number and a bigint, since they 'self(T,T)': me,
* both convert to Complex. 'complex(T,T)': cplx
*/ }) => (w,z) => cplx(me(w.re, z.re), me(w.im, z.im))
'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))
} }

View File

@ -2,16 +2,16 @@ export * from './Types/Complex.mjs'
/* Returns true if w is z multiplied by a complex unit */ /* Returns true if w is z multiplied by a complex unit */
export const associate = { export const associate = {
'Complex,Complex': ({ 'Complex<T>,Complex<T>': ({
'multiply(Complex,Complex)': times, 'multiply(Complex<T>,Complex<T>)': times,
'equalTT(Complex,Complex)': eq, 'equalTT(Complex<T>,Complex<T>)': eq,
zero, 'zero(T)': zr,
one, 'one(T)': uno,
complex, 'complex(T,T)': cplx,
'negate(Complex)': neg 'negate(Complex<T>)': neg
}) => (w,z) => { }) => (w,z) => {
if (eq(w,z) || eq(w,neg(z))) return true 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)) return eq(w,ti) || eq(w,neg(ti))
} }
} }

View File

@ -12,5 +12,9 @@ export const complex = {
'undefined,undefined': () => (u, v) => u, 'undefined,undefined': () => (u, v) => u,
'T,T': () => (x, y) => ({re: x, im: y}), 'T,T': () => (x, y) => ({re: x, im: y}),
/* Take advantage of conversions in typed-function */ /* 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)})
} }

View File

@ -1,6 +1,9 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const conjugate = { 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))
} }

View File

@ -1,19 +1,26 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const equalTT = { export const equalTT = {
'Complex,number': ({ 'Complex<T>,Complex<T>': ({
'isZero(number)': isZ, 'self(T,T)': me
'self(number,number)': eqNum }) => (w,z) => me(w.re, z.re) && me(w.im, z.im),
}) => (z, x) => eqNum(z.re, x) && isZ(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': ({ 'T,Complex<T>': ({
'isZero(bigint)': isZ, 'isZero(T)': isZ,
'self(bigint,bigint)': eqBigInt 'self(T,T)': eqReal
}) => (z, b) => eqBigInt(z.re, b) && isZ(z.im), }) => (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)
} }

View File

@ -15,4 +15,17 @@ export default async function extendToComplex(pmath) {
// Guess it wasn't a method available in complex; no worries // 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)
}
} }

View File

@ -2,16 +2,20 @@ import PocomathInstance from '../core/PocomathInstance.mjs'
import * as Complex from './Types/Complex.mjs' import * as Complex from './Types/Complex.mjs'
import gcdType from '../generic/gcdType.mjs' import gcdType from '../generic/gcdType.mjs'
const gcdComplexRaw = {}
Object.assign(gcdComplexRaw, gcdType('Complex<bigint>'))
Object.assign(gcdComplexRaw, gcdType('Complex<NumInt>'))
const imps = { const imps = {
gcdGIRaw: gcdType('GaussianInteger'), gcdComplexRaw,
gcd: { // Only return gcds with positive real part gcd: { // Only return gcds with positive real part
'GaussianInteger,GaussianInteger': ({ 'Complex<T>,Complex<T>': ({
'gcdGIRaw(GaussianInteger,GaussianInteger)': gcdRaw, 'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
'sign(bigint)': sgn, 'sign(T)': sgn,
'negate(GaussianInteger)': neg 'one(T)': uno,
'negate(Complex<T>)': neg
}) => (z,m) => { }) => (z,m) => {
const raw = gcdRaw(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) return neg(raw)
} }
} }

View File

@ -1,9 +1,14 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const invert = { export const invert = {
Complex: ({conjugate, absquare, complex, divide}) => z => { 'Complex<T>': ({
const c = conjugate(z) 'conjugate(Complex<T>)': conj,
const d = absquare(z) 'absquare(Complex<T>)': asq,
return complex(divide(c.re, d), divide(c.im, d)) '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))
} }
} }

View File

@ -1,5 +1,5 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const isZero = { export const isZero = {
Complex: ({self}) => z => self(z.re) && self(z.im) 'Complex<T>': ({'self(T)': me}) => z => me(z.re) && me(z.im)
} }

View File

@ -1,14 +1,15 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const multiply = { export const multiply = {
'Complex,Complex': ({ 'Complex<T>,Complex<T>': ({
'complex(any,any)': cplx, 'complex(T,T)': cplx,
add, 'add(T,T)': plus,
subtract, 'subtract(T,T)': sub,
self 'self(T,T)': me,
'conjugate(T)': conj // makes quaternion multiplication work
}) => (w,z) => { }) => (w,z) => {
return cplx( return cplx(
subtract(self(w.re, z.re), self(w.im, z.im)), sub(me(w.re, z.re), me(conj(w.im), z.im)),
add(self(w.re, z.im), self(w.im, z.re))) plus(me(conj(w.re), z.im), me(w.im, z.re)))
} }
} }

View File

@ -1,5 +1,7 @@
export * from './roundquotient.mjs' export * from './roundquotient.mjs'
export const quotient = { 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)
} }

View File

@ -1,17 +1,17 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const roundquotient = { export const roundquotient = {
'Complex,Complex': ({ 'Complex<T>,Complex<T>': ({
'isZero(Complex)': isZ, 'isZero(Complex<T>)': isZ,
conjugate, 'conjugate(Complex<T>)': conj,
'multiply(Complex,Complex)': mult, 'multiply(Complex<T>,Complex<T>)': mult,
absquare, 'absquare(Complex<T>)': asq,
self, 'self(T,T)': me,
complex 'complex(T,T)': cplx
}) => (n,d) => { }) => (n,d) => {
if (isZ(d)) return d if (isZ(d)) return d
const cnum = mult(n, conjugate(d)) const cnum = mult(n, conj(d))
const dreal = absquare(d) const dreal = asq(d)
return complex(self(cnum.re, dreal), self(cnum.im, dreal)) return cplx(me(cnum.re, dreal), me(cnum.im, dreal))
} }
} }

View File

@ -1,38 +1,39 @@
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const sqrt = { export const sqrt = {
Complex: ({ 'Complex<T>': ({
config, config,
isZero, 'isZero(T)': isZ,
sign, 'sign(T)': sgn,
one, 'one(T)': uno,
add, 'add(T,T)': plus,
complex, 'complex(T)': cplxU,
multiply, 'complex(T,T)': cplxB,
self, 'multiply(T,T)': mult,
divide, 'self(T)': me,
'abs(Complex)': abs, 'divide(T,T)': div,
subtract 'abs(Complex<T>)': absC,
'subtract(T,T)': sub
}) => { }) => {
if (config.predictable) { if (config.predictable) {
return z => { return z => {
const reOne = one(z.re) const reOne = uno(z.re)
if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re)) if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(me(z.re))
const reTwo = add(reOne, reOne) const reTwo = plus(reOne, reOne)
return complex( return cplxB(
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), mult(sgn(z.im), me(div(plus(absC(z),z.re), reTwo))),
self(divide(subtract(abs(z),z.re), reTwo)) me(div(sub(absC(z),z.re), reTwo))
) )
} }
} }
return z => { return z => {
const reOne = one(z.re) const reOne = uno(z.re)
if (isZero(z.im) && sign(z.re) === reOne) return self(z.re) if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re)
const reTwo = add(reOne, reOne) const reTwo = plus(reOne, reOne)
return complex( const reSqrt = me(div(plus(absC(z),z.re), reTwo))
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))), const imSqrt = me(div(sub(absC(z),z.re), reTwo))
self(divide(subtract(abs(z),z.re), reTwo)) if (reSqrt === undefined || imSqrt === undefined) return undefined
) return cplxB(mult(sgn(z.im), reSqrt), imSqrt)
} }
} }
} }

View File

@ -28,6 +28,7 @@ export default class PocomathInstance {
'importDependencies', 'importDependencies',
'install', 'install',
'installType', 'installType',
'instantiateTemplate',
'joinTypes', 'joinTypes',
'name', 'name',
'self', 'self',
@ -436,7 +437,12 @@ export default class PocomathInstance {
} }
return 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} this.Templates[base] = {type, spec}
} }
@ -767,8 +773,9 @@ export default class PocomathInstance {
const subsig = substituteInSig( const subsig = substituteInSig(
needsig, theTemplateParam, instantiateFor) needsig, theTemplateParam, instantiateFor)
let resname = simplifiedDep let resname = simplifiedDep
if (resname === 'self') resname = name if (resname == 'self') resname = name
innerRefs[dep] = self._pocoresolve(resname, subsig) innerRefs[dep] = self._pocoresolve(
resname, subsig, refs[simplifiedDep])
} else { } else {
innerRefs[dep] = refs[simplifiedDep] innerRefs[dep] = refs[simplifiedDep]
} }
@ -782,7 +789,7 @@ export default class PocomathInstance {
this._addTFimplementation( this._addTFimplementation(
tf_imps, signature, {uses: outerUses, does: patch}) tf_imps, signature, {uses: outerUses, does: patch})
} }
this._correctPartialSelfRefs(tf_imps) this._correctPartialSelfRefs(name, tf_imps)
const tf = this._typed(name, tf_imps) const tf = this._typed(name, tf_imps)
Object.defineProperty(this, name, {configurable: true, value: tf}) Object.defineProperty(this, name, {configurable: true, value: tf})
return tf return tf
@ -845,7 +852,7 @@ export default class PocomathInstance {
} else { } 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 (needsig) { if (destination &&needsig) {
destination = this._pocoresolve(func, needsig) destination = this._pocoresolve(func, needsig)
} }
refs[dep] = destination refs[dep] = destination
@ -878,7 +885,7 @@ export default class PocomathInstance {
imps[signature] = does(refs) imps[signature] = does(refs)
} }
_correctPartialSelfRefs(imps) { _correctPartialSelfRefs(name, imps) {
for (const aSignature in imps) { for (const aSignature in imps) {
if (!(imps[aSignature].deferred)) continue if (!(imps[aSignature].deferred)) continue
const part_self_references = imps[aSignature].psr 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 // No exact match, try to get one that matches with
// subtypes since the whole conversion thing in typed-function // subtypes since the whole conversion thing in typed-function
// is too complicated to reproduce // is too complicated to reproduce
const foundSig = this._findSubtypeImpl(imps, neededSig) const foundSig = this._findSubtypeImpl(name, imps, neededSig)
if (foundSig) { if (foundSig) {
corrected_self_references.push(foundSig) corrected_self_references.push(foundSig)
} else { } else {
@ -936,7 +943,7 @@ export default class PocomathInstance {
} }
const resultingTypes = new Set() const resultingTypes = new Set()
for (const iType of instantiations) { for (const iType of instantiations) {
const resultType = this._maybeAddTemplateType(base, iType) const resultType = this.instantiateTemplate(base, iType)
if (resultType) resultingTypes.add(resultType) if (resultType) resultingTypes.add(resultType)
} }
return resultingTypes return resultingTypes
@ -946,7 +953,7 @@ export default class PocomathInstance {
* instantiator to the Types of this instance, if it hasn't happened already. * instantiator to the Types of this instance, if it hasn't happened already.
* Returns the name of the type if added, false otherwise. * Returns the name of the type if added, false otherwise.
*/ */
_maybeAddTemplateType(base, instantiator) { instantiateTemplate(base, instantiator) {
const wantsType = `${base}<${instantiator}>` const wantsType = `${base}<${instantiator}>`
if (wantsType in this.Types) return false if (wantsType in this.Types) return false
// OK, need to generate the type from the template // OK, need to generate the type from the template
@ -956,7 +963,7 @@ export default class PocomathInstance {
const template = this.Templates[base].spec const template = this.Templates[base].spec
if (!template) { if (!template) {
throw new Error( throw new Error(
`Implementor error in _maybeAddTemplateType ${base} ${instantiator}`) `Implementor error in instantiateTemplate(${base}, ${instantiator})`)
} }
const instantiatorSpec = this.Types[instantiator] const instantiatorSpec = this.Types[instantiator]
let beforeTypes = [] let beforeTypes = []
@ -1010,7 +1017,7 @@ export default class PocomathInstance {
return wantsType return wantsType
} }
_findSubtypeImpl(imps, neededSig) { _findSubtypeImpl(name, imps, neededSig) {
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)
@ -1047,6 +1054,13 @@ export default class PocomathInstance {
|| this._subtypes[otherType].has(myType)) { || this._subtypes[otherType].has(myType)) {
continue 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 allMatch = false
break break
} }
@ -1058,17 +1072,28 @@ export default class PocomathInstance {
return foundSig return foundSig
} }
_pocoresolve(name, sig) { _pocoresolve(name, sig, typedFunction) {
const typedfunc = this[name] if (!this._typed.isTypedFunction(typedFunction)) {
typedFunction = this[name]
}
let result = undefined let result = undefined
try { try {
result = this._typed.find(typedfunc, sig, {exact: true}) result = this._typed.find(typedFunction, sig, {exact: true})
} catch { } catch {
} }
if (result) return result if (result) return result
const foundsig = this._findSubtypeImpl(this._imps[name], sig) const foundsig = this._findSubtypeImpl(name, this._imps[name], sig)
if (foundsig) return this._typed.find(typedfunc, foundsig) if (foundsig) return this._typed.find(typedFunction, foundsig)
return this._typed.find(typedfunc, sig) // 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
} }
} }

View File

@ -4,6 +4,7 @@ export const reducingOperation = {
'undefined': () => u => u, 'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u, 'undefined,...any': () => (u, rest) => u,
'any,undefined': () => (x, u) => u, 'any,undefined': () => (x, u) => u,
'undefined,undefined': () => (u,v) => u,
any: () => x => x, any: () => x => x,
'any,any,...any': ({ 'any,any,...any': ({
self self

View File

@ -1,14 +1,17 @@
export * from './Types/number.mjs' export * from './Types/number.mjs'
export const sqrt = { export const sqrt = {
number: ({config, complex, 'self(Complex)': complexSqrt}) => { number: ({
if (config.predictable || !complexSqrt) { config,
'complex(number,number)': cplx,
'negate(number)': neg}) => {
if (config.predictable || !cplx) {
return n => isNaN(n) ? NaN : Math.sqrt(n) return n => isNaN(n) ? NaN : Math.sqrt(n)
} }
return n => { return n => {
if (isNaN(n)) return NaN if (isNaN(n)) return NaN
if (n >= 0) return Math.sqrt(n) if (n >= 0) return Math.sqrt(n)
return complexSqrt(complex(n)) return cplx(0, Math.sqrt(neg(n)))
} }
} }
} }

View File

@ -7,7 +7,7 @@ import {Complex} from '../complex/Types/Complex.mjs'
export const floor = { export const floor = {
bigint: () => x => x, bigint: () => x => x,
NumInt: () => x => x, // Because Pocomath isn't part of typed-function, or 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 // typed-function parse, we unfortunately can't coalesce these into one
// entry with type `bigint|NumInt|GaussianInteger` because they couldn't // entry with type `bigint|NumInt|GaussianInteger` because they couldn't
// be separately activated then // be separately activated then
@ -17,7 +17,7 @@ export const floor = {
return Math.floor(n) 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 // OK to include a type totally not in Pocomath yet, it'll never be
// activated. // activated.

View File

@ -32,7 +32,12 @@ Tuple.installType('Tuple<T>', {
}) })
Tuple.promoteUnary = { 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 = { Tuple.promoteBinaryUnary = {

View File

@ -16,8 +16,8 @@ describe('The default full pocomath instance "math"', () => {
assert.strictEqual(math.typeOf(-1.5), 'number') assert.strictEqual(math.typeOf(-1.5), 'number')
assert.strictEqual(math.typeOf(-42n), 'bigint') assert.strictEqual(math.typeOf(-42n), 'bigint')
assert.strictEqual(math.typeOf(undefined), 'undefined') assert.strictEqual(math.typeOf(undefined), 'undefined')
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger') assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex') assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
}) })
it('can subtract numbers', () => { it('can subtract numbers', () => {
@ -105,11 +105,9 @@ describe('The default full pocomath instance "math"', () => {
it('calculates multi-way gcds and lcms', () => { it('calculates multi-way gcds and lcms', () => {
assert.strictEqual(math.gcd(30,105,42), 3) assert.strictEqual(math.gcd(30,105,42), 3)
assert.ok( const gaussianLCM = math.lcm(
math.associate( math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n))
math.lcm( assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true)
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n)),
math.complex(1n,3n)))
}) })
}) })

View File

@ -49,6 +49,15 @@ describe('complex', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)), math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
math.complex(4n, 5n)) 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', () => { it('computes floor', () => {

View File

@ -3,12 +3,14 @@ import math from '../src/pocomath.mjs'
import PocomathInstance from '../src/core/PocomathInstance.mjs' import PocomathInstance from '../src/core/PocomathInstance.mjs'
import * as numbers from '../src/number/all.mjs' import * as numbers from '../src/number/all.mjs'
import * as numberAdd from '../src/number/add.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 {add as genericAdd} from '../src/generic/arithmetic.mjs'
import * as complex from '../src/complex/all.mjs' import * as complex from '../src/complex/all.mjs'
import * as complexAdd from '../src/complex/add.mjs' import * as complexAdd from '../src/complex/add.mjs'
import * as complexNegate from '../src/complex/negate.mjs' import * as complexNegate from '../src/complex/negate.mjs'
import * as complexComplex from '../src/complex/complex.mjs' import * as complexComplex from '../src/complex/complex.mjs'
import * as bigintAdd from '../src/bigint/add.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 concreteSubtract from '../src/generic/subtract.concrete.mjs'
import * as genericSubtract from '../src/generic/subtract.mjs' import * as genericSubtract from '../src/generic/subtract.mjs'
import extendToComplex from '../src/complex/extendToComplex.mjs' import extendToComplex from '../src/complex/extendToComplex.mjs'
@ -54,9 +56,9 @@ describe('A custom instance', () => {
math.complex(-5, -1)) math.complex(-5, -1))
// And now floor has been activated for Complex as well, since the type // And now floor has been activated for Complex as well, since the type
// is present // is present
assert.deepStrictEqual( const fracComplex = math.complex(1.9, 0)
pm.floor(math.complex(1.9, 0)), const intComplex = math.complex(1)
math.complex(1)) assert.deepStrictEqual(pm.floor(fracComplex), intComplex)
// And the chain functions refresh themselves: // And the chain functions refresh themselves:
assert.deepStrictEqual( assert.deepStrictEqual(
pm.chain(5).add(pm.chain(0).complex(7).value).value, math.complex(5,7)) 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", () => { it("can defer definition of (even used) types", () => {
const dt = new PocomathInstance('Deferred Types') const dt = new PocomathInstance('Deferred Types')
dt.install(numberAdd) dt.install(numberAdd)
dt.install(numberZero) // for promoting numbers to complex, to fill in im
dt.install({times: { dt.install({times: {
'number,number': () => (m,n) => m*n, 'number,number': () => (m,n) => m*n,
'Complex,Complex': ({complex}) => (w,z) => { 'Complex<T>,Complex<T>': ({'complex(T,T)': cplx}) => (w,z) => {
return complex(w.re*z.re - w.im*z.im, w.re*z.im + w.im*z.re) 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: // 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 () { it("can selectively import in cute ways", async function () {
const cherry = new PocomathInstance('cherry') const cherry = new PocomathInstance('cherry')
cherry.install(numberAdd) cherry.install(numberAdd)
cherry.install(numberZero) // for complex promotion
await extendToComplex(cherry) await extendToComplex(cherry)
cherry.install({add: genericAdd}) cherry.install({add: genericAdd})
/* Now we have an instance that supports addition for number and complex /* 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(complexAdd)
inst.install(complexComplex) inst.install(complexComplex)
inst.install(bigintAdd) inst.install(bigintAdd)
inst.install(bigintZero) // for complex promotion
assert.strictEqual( assert.strictEqual(
inst.typeMerge(6n, inst.complex(3n, 2n)), inst.typeMerge(6n, inst.complex(3n, 2n)),
'Merge to GaussianInteger') 'Merge to Complex<bigint>')
assert.strictEqual( assert.strictEqual(
inst.typeMerge(3, inst.complex(4.5,2.1)), 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 // The following is the current behavior, since 3 converts to 3+0i
// which is technically the same Complex type as 3n+0ni. // which is technically the same Complex type as 3n+0ni.
// This should clear up when Complex is templatized // This should clear up when Complex is templatized