From e26df5f4fc253eade722eb02ab8caa49dc06934c Mon Sep 17 00:00:00 2001 From: Glen Whitney Date: Sat, 6 Aug 2022 20:13:50 -0700 Subject: [PATCH] feat(quaternion): Add convenience quaternion creator function Even in the setup just prior to this commit, a quaternion with entries of type `number` is simply a `Complex>` So if we provide a convenience wrapper to create sucha thing, we instantly have a quaternion data type. All of the operations come for "free" if they were properly defined for the `Complex` template. Multiplication already was, `abs` needed a little tweak, but there is absolutely no "extra" code to support quaternions. (This commit does not go through and check all arithmetic functions for proper operation and tweak those that still need some generalization.) Note that with the recursive template instantiation, a limit had to be placed on template instantiation depth. The limit moves deeper as actual arguments that are deeper nested instantiations are seen, so as long as one doesn't immediately invoke a triply-nested template, for example, the limit will never prevent an actual computation. It just prevents a runaway in the types that Pocomath thinks it needs to know about. (Basically before, using the quaternion creator would produce `Complex>`. Then when you called it again, Pocomath would think "Maybe I will need `Complex>>`?!" and create that, even though it had never seen that, and then another level next time, and so on. The limit just stops this progression one level beyond any nesting depth that's actually been observed. --- src/bigint/absquare.mjs | 6 ++++ src/bigint/native.mjs | 1 + src/complex/abs.mjs | 10 +++--- src/complex/absquare.mjs | 7 ++-- src/complex/native.mjs | 1 + src/complex/quaternion.mjs | 5 +++ src/core/PocomathInstance.mjs | 64 ++++++++++++++++++++++++++++++----- src/number/absquare.mjs | 6 ++++ src/number/native.mjs | 1 + test/complex/_all.mjs | 19 +++++++++++ 10 files changed, 104 insertions(+), 16 deletions(-) create mode 100644 src/bigint/absquare.mjs create mode 100644 src/complex/quaternion.mjs create mode 100644 src/number/absquare.mjs diff --git a/src/bigint/absquare.mjs b/src/bigint/absquare.mjs new file mode 100644 index 0000000..4c2040a --- /dev/null +++ b/src/bigint/absquare.mjs @@ -0,0 +1,6 @@ +export * from './Types/bigint.mjs' + +/* Absolute value squared */ +export const absquare = { + bigint: ({'square(bigint)': sqb}) => b => sqb(b) +} diff --git a/src/bigint/native.mjs b/src/bigint/native.mjs index 6458912..6cc76d4 100644 --- a/src/bigint/native.mjs +++ b/src/bigint/native.mjs @@ -3,6 +3,7 @@ import {identity} from '../generic/identity.mjs' export * from './Types/bigint.mjs' +export {absquare} from './absquare.mjs' export {add} from './add.mjs' export {compare} from './compare.mjs' export const conjugate = {bigint: () => identity} diff --git a/src/complex/abs.mjs b/src/complex/abs.mjs index 47fc88d..536b8b4 100644 --- a/src/complex/abs.mjs +++ b/src/complex/abs.mjs @@ -1,8 +1,10 @@ export * from './Types/Complex.mjs' export const abs = { - 'Complex': ({ - 'sqrt(T)': sqt, - 'absquare(Complex)': absq - }) => z => sqt(absq(z)) + '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 + 'absquare(Complex)': absq + }) => z => sqrt(absq(z)) } diff --git a/src/complex/absquare.mjs b/src/complex/absquare.mjs index 913124b..bb4677f 100644 --- a/src/complex/absquare.mjs +++ b/src/complex/absquare.mjs @@ -2,7 +2,8 @@ export * from './Types/Complex.mjs' export const absquare = { 'Complex': ({ - 'add(T,T)': plus, - 'square(T)': sqr - }) => z => plus(sqr(z.re), sqr(z.im)) + add, // Calculation of exact type needed in add (underlying numeric of T) + // is (currently) too involved for Pocomath + 'self(T)': absq + }) => z => add(absq(z.re), absq(z.im)) } diff --git a/src/complex/native.mjs b/src/complex/native.mjs index 420ce88..93c26e4 100644 --- a/src/complex/native.mjs +++ b/src/complex/native.mjs @@ -12,6 +12,7 @@ export {invert} from './invert.mjs' export {isZero} from './isZero.mjs' export {multiply} from './multiply.mjs' export {negate} from './negate.mjs' +export {quaternion} from './quaternion.mjs' export {quotient} from './quotient.mjs' export {roundquotient} from './roundquotient.mjs' export {sqrt} from './sqrt.mjs' diff --git a/src/complex/quaternion.mjs b/src/complex/quaternion.mjs new file mode 100644 index 0000000..4f35d30 --- /dev/null +++ b/src/complex/quaternion.mjs @@ -0,0 +1,5 @@ +export * from './Types/Complex.mjs' + +export const quaternion = { + 'T,T,T,T': ({complex}) => (r,i,j,k) => complex(complex(r,j), complex(i,k)) +} diff --git a/src/core/PocomathInstance.mjs b/src/core/PocomathInstance.mjs index b01c41f..bb013d1 100644 --- a/src/core/PocomathInstance.mjs +++ b/src/core/PocomathInstance.mjs @@ -64,6 +64,7 @@ export default class PocomathInstance { */ this._priorTypes = {} this._seenTypes = new Set() // all types that have occurred in a signature + this._maxDepthSeen = 1 // deepest template nesting we've actually encountered this._invalid = new Set() // methods that are currently invalid this._config = {predictable: false, epsilon: 1e-12} const self = this @@ -336,6 +337,7 @@ export default class PocomathInstance { let nextSuper = type while (nextSuper) { if (this._priorTypes[nextSuper].has(from)) break + if (from === nextSuper) break this._typed.addConversion( {from, to: nextSuper, convert: spec.from[from]}) this._invalidateDependents(':' + nextSuper) @@ -360,12 +362,16 @@ export default class PocomathInstance { } let nextSuper = to while (nextSuper) { - this._typed.addConversion({ - from: type, - to: nextSuper, - convert: this.Types[to].from[fromtype] - }) - this._invalidateDependents(':' + nextSuper) + if (type === nextSuper) break + try { // may already be a conversion, and no way to ask + this._typed.addConversion({ + from: type, + to: nextSuper, + convert: this.Types[to].from[fromtype] + }) + this._invalidateDependents(':' + nextSuper) + } catch { + } this._priorTypes[nextSuper].add(type) nextSuper = this.Types[nextSuper].refines } @@ -609,6 +615,14 @@ export default class PocomathInstance { substituteInSig(rawSignature, theTemplateParam, instType) /* Don't override an explicit implementation: */ if (signature in imps) continue + /* Don't go too deep */ + let maxdepth = 0 + for (const argType in typeListOfSignature(signature)) { + const depth = argType.split('<').length + if (depth > maxdepth) maxdepth = depth + } + if (maxdepth > this._maxDepthSeen + 1) continue + /* All right, go ahead and instantiate */ const uses = new Set() for (const dep of behavior.uses) { if (this._templateParam(dep)) continue @@ -718,6 +732,10 @@ export default class PocomathInstance { + 'supertype of at least one of them') } } + const depth = instantiateFor.split('<').length + if (depth > self._maxDepthSeen) { + self._maxDepthSeen = depth + } /* Generate the list of actual wanted types */ const wantTypes = parTypes.map(type => substituteInSig( type, theTemplateParam, instantiateFor)) @@ -790,6 +808,28 @@ export default class PocomathInstance { tf_imps, signature, {uses: outerUses, does: patch}) } this._correctPartialSelfRefs(name, tf_imps) + // Make sure we have all of the needed (template) types; and if they + // can't be added (because they have been instantiated too deep), + // ditch the signature: + const badSigs = new Set() + for (const sig in tf_imps) { + for (const type of typeListOfSignature(sig)) { + if (type.includes('<')) { + // it's a template type, turn it into a template and an arg + let base = type.split('<',1)[0] + const arg = type.slice(base.length+1, -1) + if (base.slice(0,3) === '...') { + base = base.slice(3) + } + if (this.instantiateTemplate(base, arg) === undefined) { + badSigs.add(sig) + } + } + } + } + for (const badSig of badSigs) { + delete tf_imps[badSig] + } const tf = this._typed(name, tf_imps) Object.defineProperty(this, name, {configurable: true, value: tf}) return tf @@ -928,8 +968,8 @@ export default class PocomathInstance { * in the instance. */ _ensureTemplateTypes(template, type) { - let [base, arg] = template.split('<', 2) - arg = arg.slice(0,-1) + const base = template.split('<', 1)[0] + const arg = template.slice(base.length + 1, -1) if (!arg) { throw new Error( 'Implementation error in _ensureTemplateTypes', template, type) @@ -951,9 +991,15 @@ export default class PocomathInstance { /* Maybe add the instantiation of template type base with argument tyoe * 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 if it was already there, + * and undefined if the type is declined (because of being nested too deep). */ instantiateTemplate(base, instantiator) { + const depth = instantiator.split('<').length + if (depth > this._maxDepthSeen ) { + // don't bother with types much deeper thant we have seen + return undefined + } const wantsType = `${base}<${instantiator}>` if (wantsType in this.Types) return false // OK, need to generate the type from the template diff --git a/src/number/absquare.mjs b/src/number/absquare.mjs new file mode 100644 index 0000000..d6ab55a --- /dev/null +++ b/src/number/absquare.mjs @@ -0,0 +1,6 @@ +export * from './Types/number.mjs' + +/* Absolute value squared */ +export const absquare = { + number: ({'square(number)': sqn}) => n => sqn(n) +} diff --git a/src/number/native.mjs b/src/number/native.mjs index d095574..6746408 100644 --- a/src/number/native.mjs +++ b/src/number/native.mjs @@ -4,6 +4,7 @@ import {identity} from '../generic/identity.mjs' export * from './Types/number.mjs' export {abs} from './abs.mjs' +export {absquare} from './absquare.mjs' export {add} from './add.mjs' export {compare} from './compare.mjs' export const conjugate = {number: () => identity} diff --git a/test/complex/_all.mjs b/test/complex/_all.mjs index 801aa99..531a28d 100644 --- a/test/complex/_all.mjs +++ b/test/complex/_all.mjs @@ -68,4 +68,23 @@ describe('complex', () => { assert.strictEqual(math.floor(gi), gi) // literally a no-op }) + it('performs rudimentary quaternion calculations', () => { + const q0 = math.quaternion(1, 0, 1, 0) + const q1 = math.quaternion(1, 0.5, 0.5, 0.75) + assert.deepStrictEqual( + q1, + math.complex(math.complex(1, 0.5), math.complex(0.5, 0.75))) + assert.deepStrictEqual( + math.add(q0,q1), + math.quaternion(2, 0.5, 1.5, 0.75)) + assert.deepStrictEqual( + math.multiply(q0, q1), + math.quaternion(0.5, 1.25, 1.5, 0.25)) + assert.deepStrictEqual( + math.multiply(q0, math.quaternion(2, 1, 0.1, 0.1)), + math.quaternion(1.9, 1.1, 2.1, -0.9)) + assert.strictEqual(math.abs(q0), Math.sqrt(2)) + assert.strictEqual(math.abs(q1), Math.sqrt(33)/4) + }) + })