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<Complex<number>>`
  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<Complex<number>>`. Then when you
  called it again, Pocomath would think "Maybe I will need
  `Complex<Complex<Complex<number>>>`?!" 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.
This commit is contained in:
Glen Whitney 2022-08-06 20:13:50 -07:00
parent 40619b9a2e
commit e26df5f4fc
10 changed files with 104 additions and 16 deletions

6
src/bigint/absquare.mjs Normal file
View File

@ -0,0 +1,6 @@
export * from './Types/bigint.mjs'
/* Absolute value squared */
export const absquare = {
bigint: ({'square(bigint)': sqb}) => b => sqb(b)
}

View File

@ -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}

View File

@ -1,8 +1,10 @@
export * from './Types/Complex.mjs'
export const abs = {
'Complex<T>': ({
'sqrt(T)': sqt,
'absquare(Complex<T>)': absq
}) => z => sqt(absq(z))
'Complex<T>': ({
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<T>)': absq
}) => z => sqrt(absq(z))
}

View File

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

View File

@ -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'

View File

@ -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))
}

View File

@ -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

6
src/number/absquare.mjs Normal file
View File

@ -0,0 +1,6 @@
export * from './Types/number.mjs'
/* Absolute value squared */
export const absquare = {
number: ({'square(number)': sqn}) => n => sqn(n)
}

View File

@ -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}

View File

@ -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)
})
})