From a2f76a55b84242460d4639c8110e416f8e488d21 Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Fri, 26 Aug 2022 23:54:32 -0400 Subject: [PATCH] feat(return types): Add more return types for complex functions These changes greatly increased the need for precision in generating implementations for signatures of operations whenever possible. So this commit also includes a refactor that basically caches all of the conversions of Pocomath implementations to typed-function implementatios so that they are more often externally available (i.e., not disrupted so much after invalidation). --- src/bigint/divide.mjs | 2 +- src/complex/Types/Complex.mjs | 2 +- src/complex/abs.mjs | 23 +++- src/complex/absquare.mjs | 28 ++++- src/complex/multiply.mjs | 9 +- src/complex/sqrt.mjs | 43 ++++--- src/core/PocomathInstance.mjs | 229 +++++++++++++++++++++++++--------- src/generic/absquare.mjs | 5 +- src/generic/square.mjs | 5 +- src/number/absquare.mjs | 3 +- src/number/multiply.mjs | 4 +- src/number/sqrt.mjs | 17 +-- test/complex/_all.mjs | 1 + 13 files changed, 273 insertions(+), 98 deletions(-) diff --git a/src/bigint/divide.mjs b/src/bigint/divide.mjs index 028d3e6..492893a 100644 --- a/src/bigint/divide.mjs +++ b/src/bigint/divide.mjs @@ -3,7 +3,7 @@ export * from './Types/bigint.mjs' export const divide = { 'bigint,bigint': ({config, 'quotient(bigint,bigint)': quot}) => { - if (config.predictable) return quot + if (config.predictable) return Returns('bigint', (n,d) => quot(n,d)) return Returns('bigint|undefined', (n, d) => { const q = n/d if (q * d == n) return q diff --git a/src/complex/Types/Complex.mjs b/src/complex/Types/Complex.mjs index c76341c..6dbaa60 100644 --- a/src/complex/Types/Complex.mjs +++ b/src/complex/Types/Complex.mjs @@ -6,7 +6,7 @@ const Complex = new PocomathInstance('Complex') Complex.installType('Complex', { test: z => z && typeof z === 'object' && 're' in z && 'im' in z }) -// Now the template type: Complex numbers are actually always homeogeneous +// Now the template type: Complex numbers are actually always homogeneous // in their component types. Complex.installType('Complex', { infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]), diff --git a/src/complex/abs.mjs b/src/complex/abs.mjs index 536b8b4..d793d33 100644 --- a/src/complex/abs.mjs +++ b/src/complex/abs.mjs @@ -1,10 +1,25 @@ +import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const abs = { 'Complex': ({ - sqrt, // Calculation of the type needed in the square root (the - // underlying numeric type of T, whatever T is, is beyond Pocomath's - // (current) template abilities, so punt and just do full resolution + sqrt, // Unfortunately no notation yet for the needed signature + 'absquare(T)': baseabsq, 'absquare(Complex)': absq - }) => z => sqrt(absq(z)) + }) => { + const pm = sqrt.fromInstance + if (typeof pm === 'undefined') { + // Just checking for the dependencies, return value is irrelevant + return undefined + } + const midType = returnTypeOf(baseabsq) + const sqrtImp = pm.resolve('sqrt', midType, sqrt) + let retType = returnTypeOf(sqrtImp) + if (retType.includes('|')) { + // This is a bit of a hack, as it relies on all implementations of + // sqrt returning the "typical" return type as the first option + retType = retType.split('|',1)[0] + } + return Returns(retType, z => sqrtImp(absq(z))) + } } diff --git a/src/complex/absquare.mjs b/src/complex/absquare.mjs index bb4677f..b013c4e 100644 --- a/src/complex/absquare.mjs +++ b/src/complex/absquare.mjs @@ -1,9 +1,31 @@ +import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const absquare = { 'Complex': ({ - add, // Calculation of exact type needed in add (underlying numeric of T) - // is (currently) too involved for Pocomath + add, // no easy way to write the needed signature; if T is number + // it is number,number; but if T is Complex, it is just + // bigint,bigint. So unfortunately we depend on all of add, and + // we extract the needed implementation below. 'self(T)': absq - }) => z => add(absq(z.re), absq(z.im)) + }) => { + const pm = add.fromInstance + if (typeof pm === 'undefined') { + // Just checking the dependencies, return value irrelevant + return undefined + } + const midType = returnTypeOf(absq) + const addImp = pm.resolve('add', `${midType},${midType}`, add) + return Returns( + returnTypeOf(addImp), z => addImp(absq(z.re), absq(z.im))) + } } + +/* We could imagine notations that Pocomath could support that would simplify + * the above, maybe something like + * 'Complex': ({ + * 'self(T): U': absq, + * 'add(U,U):V': plus, + * V + * }) => Returns(V, z => plus(absq(z.re), absq(z.im))) + */ diff --git a/src/complex/multiply.mjs b/src/complex/multiply.mjs index e059a91..db59a44 100644 --- a/src/complex/multiply.mjs +++ b/src/complex/multiply.mjs @@ -1,15 +1,18 @@ +import Returns from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const multiply = { 'Complex,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( + }) => Returns( + `Complex<${T}>`, + (w,z) => cplx( sub(me(w.re, z.re), me(conj(w.im), z.im)), plus(me(conj(w.re), z.im), me(w.im, z.re))) - } + ) } diff --git a/src/complex/sqrt.mjs b/src/complex/sqrt.mjs index d60ed6a..7f4044b 100644 --- a/src/complex/sqrt.mjs +++ b/src/complex/sqrt.mjs @@ -1,3 +1,4 @@ +import {Returns, returnTypeOf} from '../core/Returns.mjs' export * from './Types/Complex.mjs' export const sqrt = { @@ -12,29 +13,41 @@ export const sqrt = { 'multiply(T,T)': mult, 'self(T)': me, 'divide(T,T)': div, - 'abs(Complex)': absC, + 'absquare(Complex)': absqC, 'subtract(T,T)': sub }) => { + let baseReturns = returnTypeOf(me) + 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] + } + if (config.predictable) { - return z => { + return Returns(`Complex<${baseReturns}>`, 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(absC(z),z.re), reTwo))), - me(div(sub(absC(z),z.re), reTwo)) + mult(sgn(z.im), me(div(plus(myabs, z.re), reTwo))), + me(div(sub(myabs, z.re), reTwo)) ) + }) + } + + return Returns( + `Complex<${baseReturns}>|${baseReturns}|undefined`, + z => { + const reOne = uno(z.re) + if (isZ(z.im) && sgn(z.re) === reOne) return me(z.re) + const reTwo = plus(reOne, reOne) + const myabs = me(absqC(z)) + const reSqrt = me(div(plus(myabs, z.re), reTwo)) + const imSqrt = me(div(sub(myabs, z.re), reTwo)) + if (reSqrt === undefined || imSqrt === undefined) return undefined + return cplxB(mult(sgn(z.im), reSqrt), imSqrt) } - } - return z => { - 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) - } + ) } } - diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index 041177c..25e3d18 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -38,6 +38,7 @@ export default class PocomathInstance { 'joinTypes', 'name', 'returnTypeOf', + 'resolve', 'self', 'subtypesOf', 'supertypesOf', @@ -49,7 +50,8 @@ export default class PocomathInstance { constructor(name) { this.name = name - this._imps = {} + this._imps = {} // Pocomath implementations, with dependencies + this._TFimps = {} // typed-function implementations, dependencies resolved this._affects = {} this._typed = typed.create() this._typed.clear() @@ -220,7 +222,6 @@ export default class PocomathInstance { if (details) { return returnTypeOf(details.fn, signature, this) } - console.log('Checking return type of', operation) return returnTypeOf(this[operation], signature, this) }) @@ -564,8 +565,28 @@ export default class PocomathInstance { `Conflicting definitions of ${signature} for ${name}`) } } else { - // Must avoid aliasing into another instance: - opImps[signature] = {uses: behavior.uses, does: behavior.does} + /* Check if it's an ordinary non-template signature */ + let explicit = true + for (const type of typesOfSignature(signature)) { + for (const word of type.split(/[<>:\s]/)) { + if (this._templateParam(word)) { + explicit = false + break + } + } + if (!explicit) break + } + opImps[signature] = { + explicit, + uses: behavior.uses, + does: behavior.does + } + if (explicit) { + opImps[signature].resolved = false + } else { + opImps[signature].hasInstantiations = {} + opImps[signature].needsInstantiations = new Set() + } for (const dep of behavior.uses) { const depname = dep.split('(', 1)[0] if (depname === 'self' || this._templateParam(depname)) { @@ -604,11 +625,40 @@ export default class PocomathInstance { * Reset an operation to require creation of typed-function, * and if it has no implementations so far, set them up. */ - _invalidate(name) { - if (this._invalid.has(name)) return + _invalidate(name, reason) { if (!(name in this._imps)) { this._imps[name] = {} } + if (reason) { + // Make sure no TF imps that depend on reason remain: + for (const [signature, behavior] of Object.entries(this._imps[name])) { + let invalidated = false + if (reason.charAt(0) === ':') { + const badType = reason.slice(1) + if (signature.includes(badType)) invalidated = true + } else { + for (const dep of behavior.uses) { + if (dep.includes(reason)) { + invalidated = true + break + } + } + } + if (invalidated) { + if (behavior.explicit) { + if (behavior.resolved) delete this._TFimps[signature] + behavior.resolved = false + } else { + for (const fullSig + of Object.values(behavior.hasInstantiations)) { + delete this._TFimps[fullSig] + } + behavior.hasInstantiations = {} + } + } + } + } + if (this._invalid.has(name)) return this._invalid.add(name) this._invalidateDependents(name) const self = this @@ -628,7 +678,7 @@ export default class PocomathInstance { _invalidateDependents(name) { if (name in this._affects) { for (const ancestor of this._affects[name]) { - this._invalidate(ancestor) + this._invalidate(ancestor, name) } } } @@ -642,6 +692,10 @@ export default class PocomathInstance { if (!imps) { throw new SyntaxError(`No implementations for ${name}`) } + if (!(this._TFimps[name])) { + this._TFimps[name] = {} + } + const tf_imps = this._TFimps[name] /* Collect the entries we know the types for */ const usableEntries = [] for (const entry of Object.entries(imps)) { @@ -664,20 +718,13 @@ export default class PocomathInstance { * in the midst of being reassembled */ Object.defineProperty(this, name, {configurable: true, value: 'limbo'}) - const tf_imps = {} for (const [rawSignature, behavior] of usableEntries) { - /* Check if it's an ordinary non-template signature */ - let explicit = true - for (const type of typesOfSignature(rawSignature)) { - for (const word of type.split(/[<>:\s]/)) { - if (this._templateParam(word)) { - explicit = false - break - } + if (behavior.explicit) { + if (!(behavior.resolved)) { + this._addTFimplementation(tf_imps, rawSignature, behavior) + tf_imps[rawSignature]._pocoSignature = rawSignature + behavior.resolved = true } - } - if (explicit) { - this._addTFimplementation(tf_imps, rawSignature, behavior) continue } /* It's a template, have to instantiate */ @@ -693,12 +740,9 @@ export default class PocomathInstance { } } /* First, add the known instantiations, gathering all types needed */ - if (!('instantiations' in behavior)) { - behavior.instantiations = new Set() - if (ubType) behavior.instantiations.add(ubType) - } + if (ubType) behavior.needsInstantiations.add(ubType) let instantiationSet = new Set() - for (const instType of behavior.instantiations) { + for (const instType of behavior.needsInstantiations) { instantiationSet.add(instType) const otherTypes = ubType ? this.subtypesOf(instType) : this._priorTypes[instType] @@ -710,6 +754,7 @@ export default class PocomathInstance { for (const instType of instantiationSet) { if (!(instType in this.Types)) continue if (this.Types[instType] === anySpec) continue + if (instType in behavior.hasInstantiations) continue const signature = substituteInSignature(rawSignature, theTemplateParam, instType) /* Don't override an explicit implementation: */ @@ -742,10 +787,14 @@ export default class PocomathInstance { } this._addTFimplementation( tf_imps, signature, {uses, does: patch}) + tf_imps[signature]._pocoSignature = rawSignature + tf_imps[signature]._pocoInstance = instType + behavior.hasInstantiations[instType] = signature } /* Now add the catchall signature */ /* (Not needed if if it's a bounded template) */ if (ubType) continue + if ('_catchall_' in behavior.hasInstantiations) continue let templateCall = `<${theTemplateParam}>` /* Relying here that the base of 'Foo' is 'Foo': */ let baseSignature = rawSignature.replaceAll(templateCall, '') @@ -886,11 +935,11 @@ export default class PocomathInstance { /* Arrange that the desired instantiation will be there next * time so we don't have to go through that again for this type */ - refs[theTemplateParam] = instantiateFor - behavior.instantiations.add(instantiateFor) + behavior.needsInstantiations.add(instantiateFor) self._invalidate(name) // And update refs because we now know the type we're instantiating // for: + refs[theTemplateParam] = instantiateFor const innerRefs = {} for (const dep in simplifiedUses) { const simplifiedDep = simplifiedUses[dep] @@ -903,7 +952,7 @@ export default class PocomathInstance { needsig, theTemplateParam, instantiateFor) let resname = simplifiedDep if (resname == 'self') resname = name - innerRefs[dep] = self._pocoresolve( + innerRefs[dep] = self.resolve( resname, subsig, refs[simplifiedDep]) } else { innerRefs[dep] = refs[simplifiedDep] @@ -913,13 +962,15 @@ export default class PocomathInstance { // Finally ready to make the call. const implementation = behavior.does(innerRefs) // We can access return type information here - // And in particular, if it's a template, we should try to + // And in particular, if it might be a template, we should try to // instantiate it: const returnType = returnTypeOf(implementation, wantSig, self) - const instantiated = self._maybeInstantiate(returnType) - if (instantiated) { - const tempBase = instantiated.split('<',1)[0] - self._invalidateDependents(':' + tempBase) + for (const possibility of returnType.split('|')) { + const instantiated = self._maybeInstantiate(possibility) + if (instantiated) { + const tempBase = instantiated.split('<',1)[0] + self._invalidateDependents(':' + tempBase) + } } return implementation(...args) } @@ -932,6 +983,7 @@ export default class PocomathInstance { const outerUses = new Set(Object.values(simplifiedUses)) this._addTFimplementation( tf_imps, signature, {uses: outerUses, does: patch}) + behavior.hasInstantiations._catchall_ = rawSignature } this._correctPartialSelfRefs(name, tf_imps) // Make sure we have all of the needed (template) types; and if they @@ -946,9 +998,17 @@ export default class PocomathInstance { } } for (const badSig of badSigs) { + const imp = tf_imps[badSig] delete tf_imps[badSig] + const fromBehavior = this._imps[name][imp._pocoSignature] + if (fromBehavior.explicit) { + fromBehavior.resolved = false + } else { + delete fromBehavior.hasInstantiations[imp._pocoInstance] + } } const tf = this._typed(name, tf_imps) + Object.defineProperty(tf, 'fromInstance', {value: this}) Object.defineProperty(this, name, {configurable: true, value: tf}) return tf } @@ -1006,13 +1066,20 @@ export default class PocomathInstance { } if (func === 'self') { if (needsig) { - if (full_self_referential) { - throw new SyntaxError( - 'typed-function does not support mixed full and ' - + 'partial self-reference') - } - if (subsetOfKeys(typesOfSignature(needsig), this.Types)) { - part_self_references.push(needsig) + /* Maybe we can resolve the self reference without troubling + * typed-function: + */ + if (needsig in imps && typeof imps[needsig] == 'function') { + refs[dep] = imps[needsig] + } else { + if (full_self_referential) { + throw new SyntaxError( + 'typed-function does not support mixed full and ' + + 'partial self-reference') + } + if (subsetOfKeys(typesOfSignature(needsig), this.Types)) { + part_self_references.push(needsig) + } } } else { if (part_self_references.length) { @@ -1024,19 +1091,41 @@ export default class PocomathInstance { } } else { if (this[func] === 'limbo') { - /* We are in the midst of bundling func, so have to use - * an indirect reference to func. And given that, there's - * really no helpful way to extract a specific signature + /* We are in the midst of bundling func */ + let fallback = true + /* So the first thing we can do is try the tf_imps we are + * accumulating: */ - const self = this - refs[dep] = function () { // is this the most efficient? - return self[func].apply(this, arguments) + if (needsig) { + const tempTF = this._typed('dummy_' + func, this._TFimps[func]) + let result = undefined + try { + result = this._typed.find(tempTF, needsig, {exact: true}) + } catch {} + if (result) { + refs[dep] = result + fallback = false + } + } + if (fallback) { + /* Either we need the whole function or the signature + * we need is not available yet, so we have to use + * an indirect reference to func. And given that, there's + * really no helpful way to extract a specific signature + */ + const self = this + const redirect = function () { // is this the most efficient? + return self[func].apply(this, arguments) + } + Object.defineProperty(redirect, 'name', {value: func}) + Object.defineProperty(redirect, 'fromInstance', {value: this}) + refs[dep] = redirect } } else { // can bundle up func, and grab its signature if need be let destination = this[func] if (destination && needsig) { - destination = this._pocoresolve(func, needsig) + destination = this.resolve(func, needsig) } refs[dep] = destination } @@ -1076,7 +1165,8 @@ export default class PocomathInstance { _correctPartialSelfRefs(name, imps) { for (const aSignature in imps) { if (!(imps[aSignature].deferred)) continue - const part_self_references = imps[aSignature].psr + const deferral = imps[aSignature] + const part_self_references = deferral.psr const corrected_self_references = [] for (const neededSig of part_self_references) { // Have to find a match for neededSig among the other signatures @@ -1098,8 +1188,8 @@ export default class PocomathInstance { + `${name}(${neededSig})`) } } - const refs = imps[aSignature].builtRefs - const does = imps[aSignature].sigDoes + const refs = deferral.builtRefs + const does = deferral.sigDoes imps[aSignature] = this._typed.referTo( ...corrected_self_references, (...impls) => { for (let i = 0; i < part_self_references.length; ++i) { @@ -1110,6 +1200,8 @@ export default class PocomathInstance { return implementation } ) + imps[aSignature]._pocoSignature = deferral._pocoSignature + imps[aSignature]._pocoInstance = deferral._pocoInstance } } @@ -1282,16 +1374,30 @@ export default class PocomathInstance { typedFunction = this[name] } let result = undefined - if (!this._typed.isTypedFunction(typedFunction)) { - return result + const haveTF = this._typed.isTypedFunction(typedFunction) + if (haveTF) { + try { + result = this._typed.findSignature(typedFunction, sig, {exact: true}) + } catch { + } } - try { - result = this._typed.findSignature(typedFunction, sig, {exact: true}) - } catch { - } - if (result) return result + if (result || !(this._imps[name])) return result const foundsig = this._findSubtypeImpl(name, this._imps[name], sig) - if (foundsig) return this._typed.findSignature(typedFunction, foundsig) + if (foundsig) { + if (haveTF) { + return this._typed.findSignature(typedFunction, foundsig) + } + // We have an implementation but not a typed function. Do the best + // we can: + const foundImpl = this._imps[name][foundsig] + const needs = {} + for (const dep of foundImpl.uses) { + const [base, sig] = dep.split('()') + needs[dep] = this.resolve(base, sig) + } + const pseudoImpl = foundImpl.does(needs) + return {fn: pseudoImpl, implementation: pseudoImpl} + } const wantTypes = typeListOfSignature(sig) for (const [implSig, details] of typedFunction._typedFunctionData.signatureMap) { @@ -1314,7 +1420,12 @@ export default class PocomathInstance { return result } - _pocoresolve(name, sig, typedFunction) { + /* Returns a function that implements the operation with the given name + * when called with the given signature. The optional third argument is + * the typed function that provides the operation name, which can be + * passed in for efficiency if it is already available. + */ + resolve = Returns('function', function (name, sig, typedFunction) { if (!this._typed.isTypedFunction(typedFunction)) { typedFunction = this[name] } @@ -1323,6 +1434,6 @@ export default class PocomathInstance { // total punt, revert to typed-function resolution on every call; // hopefully this happens rarely: return typedFunction - } + }) } diff --git a/src/generic/absquare.mjs b/src/generic/absquare.mjs index 26d6717..052131d 100644 --- a/src/generic/absquare.mjs +++ b/src/generic/absquare.mjs @@ -1,6 +1,9 @@ +import Returns from '../core/Returns.mjs' + export const absquare = { T: ({ + T, 'square(T)': sq, 'abs(T)': abval - }) => t => sq(abval(t)) + }) => Returns(T, t => sq(abval(t))) } diff --git a/src/generic/square.mjs b/src/generic/square.mjs index 53fd6c2..2619c29 100644 --- a/src/generic/square.mjs +++ b/src/generic/square.mjs @@ -1,3 +1,6 @@ +import {Returns, returnTypeOf} from '../core/Returns.mjs' + export const square = { - T: ({'multiply(T,T)': multT}) => x => multT(x,x) + T: ({'multiply(T,T)': multT}) => Returns( + returnTypeOf(multT), x => multT(x,x)) } diff --git a/src/number/absquare.mjs b/src/number/absquare.mjs index d6ab55a..31a417d 100644 --- a/src/number/absquare.mjs +++ b/src/number/absquare.mjs @@ -1,6 +1,7 @@ +import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' /* Absolute value squared */ export const absquare = { - number: ({'square(number)': sqn}) => n => sqn(n) + 'T:number': ({T, 'square(T)': sqn}) => Returns(T, n => sqn(n)) } diff --git a/src/number/multiply.mjs b/src/number/multiply.mjs index 80573d1..5951f22 100644 --- a/src/number/multiply.mjs +++ b/src/number/multiply.mjs @@ -1,3 +1,5 @@ +import Returns from '../core/Returns.mjs' + export * from './Types/number.mjs' -export const multiply = {'number,number': () => (m,n) => m*n} +export const multiply = {'T:number,T': ({T}) => Returns(T, (m,n) => m*n)} diff --git a/src/number/sqrt.mjs b/src/number/sqrt.mjs index 3017e82..2e5e734 100644 --- a/src/number/sqrt.mjs +++ b/src/number/sqrt.mjs @@ -1,3 +1,4 @@ +import Returns from '../core/Returns.mjs' export * from './Types/number.mjs' export const sqrt = { @@ -5,13 +6,13 @@ export const sqrt = { config, 'complex(number,number)': cplx, 'negate(number)': neg}) => { - if (config.predictable || !cplx) { - return n => isNaN(n) ? NaN : Math.sqrt(n) + if (config.predictable || !cplx) { + return Returns('number', n => isNaN(n) ? NaN : Math.sqrt(n)) + } + return Returns('number|Complex', n => { + if (isNaN(n)) return NaN + if (n >= 0) return Math.sqrt(n) + return cplx(0, Math.sqrt(neg(n))) + }) } - return n => { - if (isNaN(n)) return NaN - if (n >= 0) return Math.sqrt(n) - return cplx(0, Math.sqrt(neg(n))) - } - } } diff --git a/test/complex/_all.mjs b/test/complex/_all.mjs index 531a28d..884350a 100644 --- a/test/complex/_all.mjs +++ b/test/complex/_all.mjs @@ -83,6 +83,7 @@ describe('complex', () => { assert.deepStrictEqual( math.multiply(q0, math.quaternion(2, 1, 0.1, 0.1)), math.quaternion(1.9, 1.1, 2.1, -0.9)) + math.absquare(math.complex(1.25, 2.5)) //HACK: need absquare(Complex) assert.strictEqual(math.abs(q0), Math.sqrt(2)) assert.strictEqual(math.abs(q1), Math.sqrt(33)/4) })