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:
parent
40619b9a2e
commit
e26df5f4fc
6
src/bigint/absquare.mjs
Normal file
6
src/bigint/absquare.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
|
/* Absolute value squared */
|
||||||
|
export const absquare = {
|
||||||
|
bigint: ({'square(bigint)': sqb}) => b => sqb(b)
|
||||||
|
}
|
@ -3,6 +3,7 @@ import {identity} from '../generic/identity.mjs'
|
|||||||
|
|
||||||
export * from './Types/bigint.mjs'
|
export * from './Types/bigint.mjs'
|
||||||
|
|
||||||
|
export {absquare} from './absquare.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {compare} from './compare.mjs'
|
export {compare} from './compare.mjs'
|
||||||
export const conjugate = {bigint: () => identity}
|
export const conjugate = {bigint: () => identity}
|
||||||
|
@ -2,7 +2,9 @@ export * from './Types/Complex.mjs'
|
|||||||
|
|
||||||
export const abs = {
|
export const abs = {
|
||||||
'Complex<T>': ({
|
'Complex<T>': ({
|
||||||
'sqrt(T)': sqt,
|
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
|
'absquare(Complex<T>)': absq
|
||||||
}) => z => sqt(absq(z))
|
}) => z => sqrt(absq(z))
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,8 @@ export * from './Types/Complex.mjs'
|
|||||||
|
|
||||||
export const absquare = {
|
export const absquare = {
|
||||||
'Complex<T>': ({
|
'Complex<T>': ({
|
||||||
'add(T,T)': plus,
|
add, // Calculation of exact type needed in add (underlying numeric of T)
|
||||||
'square(T)': sqr
|
// is (currently) too involved for Pocomath
|
||||||
}) => z => plus(sqr(z.re), sqr(z.im))
|
'self(T)': absq
|
||||||
|
}) => z => add(absq(z.re), absq(z.im))
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ export {invert} from './invert.mjs'
|
|||||||
export {isZero} from './isZero.mjs'
|
export {isZero} from './isZero.mjs'
|
||||||
export {multiply} from './multiply.mjs'
|
export {multiply} from './multiply.mjs'
|
||||||
export {negate} from './negate.mjs'
|
export {negate} from './negate.mjs'
|
||||||
|
export {quaternion} from './quaternion.mjs'
|
||||||
export {quotient} from './quotient.mjs'
|
export {quotient} from './quotient.mjs'
|
||||||
export {roundquotient} from './roundquotient.mjs'
|
export {roundquotient} from './roundquotient.mjs'
|
||||||
export {sqrt} from './sqrt.mjs'
|
export {sqrt} from './sqrt.mjs'
|
||||||
|
5
src/complex/quaternion.mjs
Normal file
5
src/complex/quaternion.mjs
Normal 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))
|
||||||
|
}
|
@ -64,6 +64,7 @@ export default class PocomathInstance {
|
|||||||
*/
|
*/
|
||||||
this._priorTypes = {}
|
this._priorTypes = {}
|
||||||
this._seenTypes = new Set() // all types that have occurred in a signature
|
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._invalid = new Set() // methods that are currently invalid
|
||||||
this._config = {predictable: false, epsilon: 1e-12}
|
this._config = {predictable: false, epsilon: 1e-12}
|
||||||
const self = this
|
const self = this
|
||||||
@ -336,6 +337,7 @@ export default class PocomathInstance {
|
|||||||
let nextSuper = type
|
let nextSuper = type
|
||||||
while (nextSuper) {
|
while (nextSuper) {
|
||||||
if (this._priorTypes[nextSuper].has(from)) break
|
if (this._priorTypes[nextSuper].has(from)) break
|
||||||
|
if (from === nextSuper) break
|
||||||
this._typed.addConversion(
|
this._typed.addConversion(
|
||||||
{from, to: nextSuper, convert: spec.from[from]})
|
{from, to: nextSuper, convert: spec.from[from]})
|
||||||
this._invalidateDependents(':' + nextSuper)
|
this._invalidateDependents(':' + nextSuper)
|
||||||
@ -360,12 +362,16 @@ export default class PocomathInstance {
|
|||||||
}
|
}
|
||||||
let nextSuper = to
|
let nextSuper = to
|
||||||
while (nextSuper) {
|
while (nextSuper) {
|
||||||
|
if (type === nextSuper) break
|
||||||
|
try { // may already be a conversion, and no way to ask
|
||||||
this._typed.addConversion({
|
this._typed.addConversion({
|
||||||
from: type,
|
from: type,
|
||||||
to: nextSuper,
|
to: nextSuper,
|
||||||
convert: this.Types[to].from[fromtype]
|
convert: this.Types[to].from[fromtype]
|
||||||
})
|
})
|
||||||
this._invalidateDependents(':' + nextSuper)
|
this._invalidateDependents(':' + nextSuper)
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
this._priorTypes[nextSuper].add(type)
|
this._priorTypes[nextSuper].add(type)
|
||||||
nextSuper = this.Types[nextSuper].refines
|
nextSuper = this.Types[nextSuper].refines
|
||||||
}
|
}
|
||||||
@ -609,6 +615,14 @@ export default class PocomathInstance {
|
|||||||
substituteInSig(rawSignature, theTemplateParam, instType)
|
substituteInSig(rawSignature, theTemplateParam, instType)
|
||||||
/* Don't override an explicit implementation: */
|
/* Don't override an explicit implementation: */
|
||||||
if (signature in imps) continue
|
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()
|
const uses = new Set()
|
||||||
for (const dep of behavior.uses) {
|
for (const dep of behavior.uses) {
|
||||||
if (this._templateParam(dep)) continue
|
if (this._templateParam(dep)) continue
|
||||||
@ -718,6 +732,10 @@ export default class PocomathInstance {
|
|||||||
+ 'supertype of at least one of them')
|
+ '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 */
|
/* Generate the list of actual wanted types */
|
||||||
const wantTypes = parTypes.map(type => substituteInSig(
|
const wantTypes = parTypes.map(type => substituteInSig(
|
||||||
type, theTemplateParam, instantiateFor))
|
type, theTemplateParam, instantiateFor))
|
||||||
@ -790,6 +808,28 @@ export default class PocomathInstance {
|
|||||||
tf_imps, signature, {uses: outerUses, does: patch})
|
tf_imps, signature, {uses: outerUses, does: patch})
|
||||||
}
|
}
|
||||||
this._correctPartialSelfRefs(name, tf_imps)
|
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)
|
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
|
||||||
@ -928,8 +968,8 @@ export default class PocomathInstance {
|
|||||||
* in the instance.
|
* in the instance.
|
||||||
*/
|
*/
|
||||||
_ensureTemplateTypes(template, type) {
|
_ensureTemplateTypes(template, type) {
|
||||||
let [base, arg] = template.split('<', 2)
|
const base = template.split('<', 1)[0]
|
||||||
arg = arg.slice(0,-1)
|
const arg = template.slice(base.length + 1, -1)
|
||||||
if (!arg) {
|
if (!arg) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Implementation error in _ensureTemplateTypes', template, type)
|
'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
|
/* Maybe add the instantiation of template type base with argument tyoe
|
||||||
* 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 if it was already there,
|
||||||
|
* and undefined if the type is declined (because of being nested too deep).
|
||||||
*/
|
*/
|
||||||
instantiateTemplate(base, instantiator) {
|
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}>`
|
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
|
||||||
|
6
src/number/absquare.mjs
Normal file
6
src/number/absquare.mjs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export * from './Types/number.mjs'
|
||||||
|
|
||||||
|
/* Absolute value squared */
|
||||||
|
export const absquare = {
|
||||||
|
number: ({'square(number)': sqn}) => n => sqn(n)
|
||||||
|
}
|
@ -4,6 +4,7 @@ import {identity} from '../generic/identity.mjs'
|
|||||||
export * from './Types/number.mjs'
|
export * from './Types/number.mjs'
|
||||||
|
|
||||||
export {abs} from './abs.mjs'
|
export {abs} from './abs.mjs'
|
||||||
|
export {absquare} from './absquare.mjs'
|
||||||
export {add} from './add.mjs'
|
export {add} from './add.mjs'
|
||||||
export {compare} from './compare.mjs'
|
export {compare} from './compare.mjs'
|
||||||
export const conjugate = {number: () => identity}
|
export const conjugate = {number: () => identity}
|
||||||
|
@ -68,4 +68,23 @@ describe('complex', () => {
|
|||||||
assert.strictEqual(math.floor(gi), gi) // literally a no-op
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user