Compare commits

..

10 Commits

Author SHA1 Message Date
Glen Whitney 36e7b750ce refactor: Make generics more strict via templates
This commit also adds a built-in `typeOf` function to every PocomathInstance.
  It also adds a notation (prefix '!') for "eager" templeates that
  instantiate themselves for all types immediately upon definition.
2022-08-01 03:02:38 -07:00
Glen Whitney fd3d6b2eb3 feat: Template implementations 2022-07-31 23:33:58 -07:00
Glen Whitney 1076c3c727 feat: add built-in typeOf operator to PocomathInstance 2022-07-31 21:26:16 -07:00
Glen Whitney 7bbdc049ce refactor: add stub for patch to generic template behavior 2022-07-31 21:08:28 -07:00
Glen Whitney 171b6941f4 refactor: add stub for instantiating the template 2022-07-31 11:10:41 -07:00
Glen Whitney 5cea71cb25 refactor: isolate in the code the point of template instantiation 2022-07-31 11:10:41 -07:00
Glen Whitney 0fbcbf661e refactor: separate the generation of a typed-function implementation 2022-07-31 11:10:41 -07:00
Glen Whitney 5c3716ff99 refactor: Avoid use of a "magic string " 'T' 2022-07-31 11:10:41 -07:00
Glen Whitney d4a4d8f331 refactor: track all the 'prior types' of a type as types are installed
These are exactly the types that might match before the given type, and
  hence which require to have their template instantiations defined when
  an instantiation with the given type is made.
2022-07-31 11:10:41 -07:00
Glen Whitney 883a351aa8 refactor: Add stubs for function templates
The stubs are so far all no-ops so basically at the moment the only
  allowed template parameter 'T' is just a synonym for 'any'. But at least
  this allows us to put the definition of the generic `subtract` into
  the exact form we actually want to support.
2022-07-31 11:10:41 -07:00
109 changed files with 454 additions and 3141 deletions

View File

@ -25,7 +25,3 @@ Hopefully this shows promise. It is an evolution of the concept first prototyped
Note that Pocomath allows one implementation to depend just on a specific signature of another function, for efficiency's sake (if for example 'bar(Matrix)' knows it will only call 'foo(Matrix)', it avoids another type-dispatch). That capability is used in sqrt, for example.
Pocomath also lazily reloads operations that depend on the config when that changes, and if an operation has a signature mentioning an undefined type, that signature is ignored until the type is installed, at which point the function lazily redefines itself to use the additional signature.
Pocomath now also allows template operations and template types, also built on top of typed-function (but candidates for integration therein). This is used to make many operations more specific, implement a type-homogeneous Tuple type, and make Complex numbers be type-homogeneous (which it seems like it always should be). One of the cutest consequences of this approach is that with careful definitions of the `Complex<T>` templates, one gets a working quaternion data type absolutely for free as `Complex<Complex<number>>` (and integral quaternions as `Complex<Complex<bigint>>`, etc.)
It also now has a facility to adapt a third-party numeric class as a type in Pocomath, see `src/generic/all.mjs` and `src/generic/Types/adapted.mjs`, which it uses by way of example to incorporate fraction.js Fraction objects into Pocomath.

View File

@ -24,7 +24,6 @@
},
dependencies: {
'bigint-isqrt': '^0.2.1',
'fraction.js': '^4.2.0',
'typed-function': '^3.0.0',
},
}

View File

@ -2,13 +2,11 @@ lockfileVersion: 5.4
specifiers:
bigint-isqrt: ^0.2.1
fraction.js: ^4.2.0
mocha: ^10.0.0
typed-function: ^3.0.0
dependencies:
bigint-isqrt: 0.2.1
fraction.js: 4.2.0
typed-function: 3.0.0
devDependencies:
@ -194,10 +192,6 @@ packages:
hasBin: true
dev: true
/fraction.js/4.2.0:
resolution: {integrity: sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==}
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
dev: true

View File

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

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const add = {'bigint,bigint': () => Returns('bigint', (a,b) => a+b)}
export const add = {'bigint,bigint': () => (a,b) => a+b}

View File

@ -1,6 +1,5 @@
import PocomathInstance from '../core/PocomathInstance.mjs'
import * as bigints from './native.mjs'
import * as generic from '../generic/all.mjs'
import * as ops from '../ops/all.mjs'
export default PocomathInstance.merge('bigint', bigints, generic, ops)
export default PocomathInstance.merge('bigint', bigints, generic)

View File

@ -1,7 +1,5 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const compare = {
'bigint,bigint': () => Returns(
'boolean', (a,b) => a === b ? 0n : (a > b ? 1n : -1n))
'bigint,bigint': () => (a,b) => a === b ? 0n : (a > b ? 1n : -1n)
}

View File

@ -1,13 +1,12 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const divide = {
'bigint,bigint': ({config, 'quotient(bigint,bigint)': quot}) => {
if (config.predictable) return Returns('bigint', (n,d) => quot(n,d))
return Returns('bigint|undefined', (n, d) => {
if (config.predictable) return quot
return (n, d) => {
const q = n/d
if (q * d == n) return q
return undefined
})
}
}
}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const isZero = {bigint: () => Returns('boolean', b => b === 0n)}
export const isZero = {bigint: () => b => b === 0n}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const multiply = {'bigint,bigint': () => Returns('bigint', (a,b) => a*b)}
export const multiply = {'bigint,bigint': () => (a,b) => a*b}

View File

@ -1,12 +1,9 @@
import gcdType from '../generic/gcdType.mjs'
import {identityType} 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: identityType('bigint')}
export {divide} from './divide.mjs'
export const gcd = gcdType('bigint')
export {isZero} from './isZero.mjs'

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const negate = {bigint: () => Returns('bigint', b => -b)}
export const negate = {bigint: () => b => -b}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const one = {bigint: () => Returns('bigint', () => 1n)}
export const one = {bigint: () => () => 1n}

View File

@ -1,14 +1,13 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
/* Returns the floor integer approximation to n/d */
/* Returns the best integer approximation to n/d */
export const quotient = {
'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (n, d) => {
'bigint,bigint': ({'sign(bigint)': sgn}) => (n, d) => {
const dSgn = sgn(d)
if (dSgn === 0n) return 0n
if (sgn(n) === dSgn) return n/d
const quot = n/d
if (quot * d == n) return quot
return quot - 1n
})
}
}

View File

@ -1,9 +1,8 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
/* Returns the closest integer approximation to n/d */
export const roundquotient = {
'bigint,bigint': ({'sign(bigint)': sgn}) => Returns('bigint', (n, d) => {
'bigint,bigint': ({'sign(bigint)': sgn}) => (n, d) => {
const dSgn = sgn(d)
if (dSgn === 0n) return 0n
const candidate = n/d
@ -12,5 +11,5 @@ export const roundquotient = {
if (2n * rem > absd) return candidate + dSgn
if (-2n * rem >= absd) return candidate - dSgn
return candidate
})
}
}

View File

@ -1,10 +1,9 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const sign = {
bigint: () => Returns('bigint', b => {
bigint: () => b => {
if (b === 0n) return 0n
if (b > 0n) return 1n
return -1n
})
}
}

View File

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

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/bigint.mjs'
export const zero = {bigint: () => Returns('bigint', () => 0n)}
export const zero = {bigint: () => () => 0n}

View File

@ -1,31 +1,30 @@
import {Returns, returnTypeOf} from '../../core/Returns.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')
// Now the template type: Complex numbers are actually always homogeneous
// in their component types. For an explanation of the meanings of the
// properties, see ../../tuple/Types/Tuple.mjs
Complex.installType('Complex<T>', {
base: z => z && typeof z === 'object' && 're' in z && 'im' in z,
test: testT => z => testT(z.re) && testT(z.im),
infer: ({typeOf, joinTypes}) => z => joinTypes([typeOf(z.re), typeOf(z.im)]),
Complex.installType('Complex', {
test: isComplex,
from: {
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)})
number: x => ({re: x, im: 0})
}
})
Complex.installType('GaussianInteger', {
test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint',
refines: 'Complex',
from: {
bigint: x => ({re: x, im: 0n})
}
})
Complex.promoteUnary = {
'Complex<T>': ({
T,
'self(T)': me,
complex
}) => Returns(
`Complex<${returnTypeOf(me)}>`, z => complex(me(z.re), me(z.im)))
Complex: ({self,complex}) => z => complex(self(z.re), self(z.im))
}
export {Complex}

View File

@ -1,20 +1,5 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const abs = {
'Complex<T>': ({
sqrt, // Unfortunately no notation yet for the needed signature
'absquare(T)': baseabsq,
'absquare(Complex<T>)': absq
}) => {
const midType = returnTypeOf(baseabsq)
const sqrtImp = sqrt.fromInstance.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)))
}
Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z))
}

View File

@ -1,27 +1,5 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const absquare = {
'Complex<T>': ({
add, // no easy way to write the needed signature; if T is number
// it is number,number; but if T is Complex<bigint>, it is just
// bigint,bigint. So unfortunately we depend on all of add, and
// we extract the needed implementation below.
'self(T)': absq
}) => {
const midType = returnTypeOf(absq)
const addImp = add.fromInstance.resolve(
'add', `${midType},${midType}`, add)
return Returns(
returnTypeOf(addImp), z => addImp(absq(z.re), absq(z.im)))
}
Complex: ({add, square}) => z => add(square(z.re), square(z.im))
}
/* We could imagine notations that Pocomath could support that would simplify
* the above, maybe something like
* 'Complex<T>': ({
* 'self(T): U': absq,
* 'add(U,U):V': plus,
* V
* }) => Returns(V, z => plus(absq(z.re), absq(z.im)))
*/

View File

@ -1,10 +1,22 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const add = {
'Complex<T>,Complex<T>': ({
T,
'self(T,T)': me,
'complex(T,T)': cplx
}) => Returns(`Complex<${T}>`, (w,z) => cplx(me(w.re, z.re), me(w.im, z.im)))
/* Relying on conversions for both complex + number and complex + bigint
* leads to an infinite loop when adding a number and a bigint, since they
* both convert to Complex.
*/
'Complex,number': ({
'self(number,number)': addNum,
'complex(any,any)': cplx
}) => (z,x) => cplx(addNum(z.re, x), z.im),
'Complex,bigint': ({
'self(bigint,bigint)': addBigInt,
'complex(any,any)': cplx
}) => (z,x) => cplx(addBigInt(z.re, x), z.im),
'Complex,Complex': ({
self,
'complex(any,any)': cplx
}) => (w,z) => cplx(self(w.re, z.re), self(w.im, z.im))
}

View File

@ -1,6 +1,5 @@
import PocomathInstance from '../core/PocomathInstance.mjs'
import * as complexes from './native.mjs'
import * as generic from '../generic/arithmetic.mjs'
import * as floor from '../ops/floor.mjs'
export default PocomathInstance.merge('complex', complexes, generic, floor)
export default PocomathInstance.merge('complex', complexes, generic)

View File

@ -1,7 +0,0 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* arg is the "argument" or angle theta of z in its form r cis theta */
export const arg = {
'Complex<number>': () => Returns('number', z => Math.atan2(z.im, z.re))
}

View File

@ -1,18 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* Returns true if w is z multiplied by a complex unit */
export const associate = {
'Complex<T>,Complex<T>': ({
'multiply(Complex<T>,Complex<T>)': times,
'equalTT(Complex<T>,Complex<T>)': eq,
'zero(T)': zr,
'one(T)': uno,
'complex(T,T)': cplx,
'negate(Complex<T>)': neg
}) => Returns('boolean', (w,z) => {
if (eq(w,z) || eq(w,neg(z))) return true
const ti = times(z, cplx(zr(z.re), uno(z.im)))
return eq(w,ti) || eq(w,neg(ti))
})
}

View File

@ -1,28 +0,0 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
const TAU3 = 2 * Math.PI / 3
/* Complex cube root that returns all three roots as a tuple of complex. */
/* follows the implementation in mathjs */
/* Really only works for T = number at the moment because of arg and cbrt */
export const cbrtc = {
'Complex<T>': ({
'arg(T)': theta,
'divide(T,T)': div,
'abs(Complex<T>)': absval,
'complex(T)': cplx,
'cbrt(T)': cbrtT,
'multiply(Complex<T>,Complex<T>)': mult,
'cis(T)': cisT,
'tuple(...Complex<T>)': tup
}) => Returns('Tuple<Complex<T>>', z => {
const arg3 = div(theta(z), 3)
const r = cplx(cbrtT(absval(z)))
return tup(
mult(r, cisT(arg3)),
mult(r, cisT(arg3 + TAU3)),
mult(r, cisT(arg3 - TAU3))
)
})
}

View File

@ -1,9 +0,0 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
/* Returns cosine plus i sin theta */
export const cis = {
'number': ({'complex(number,number)': cplx}) => Returns(
'Complex<number>', t => cplx(Math.cos(t), Math.sin(t))
)
}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export * from '../generic/Types/generic.mjs'
@ -7,16 +6,10 @@ export const complex = {
* have a numeric/scalar type, e.g. by implementing subtypes in
* typed-function
*/
'undefined': () => Returns('undefined', u => u),
'undefined,any': () => Returns('undefined', (u, y) => u),
'any,undefined': () => Returns('undefined', (x, u) => u),
'undefined,undefined': () => Returns('undefined', (u, v) => u),
'T,T': ({T}) => Returns(`Complex<${T}>`, (x, y) => ({re: x, im: y})),
'undefined': () => u => u,
'undefined,any': () => (u, y) => u,
'any,undefined': () => (x, u) => u,
'any,any': () => (x, y) => ({re: x, im: y}),
/* Take advantage of conversions in typed-function */
// 'Complex<T>': () => z => z
/* But help out because without templates built in to typed-function,
* type inference turns out to be too hard
*/
'T': ({T, 'zero(T)': zr}) => Returns(
`Complex<${T}>`, x => ({re: x, im: zr(x)}))
Complex: () => z => z
}

View File

@ -1,11 +1,6 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const conjugate = {
'Complex<T>': ({
T,
'negate(T)': neg,
'complex(T,T)': cplx
}) => Returns(`Complex<${T}>`, z => cplx(z.re, neg(z.im)))
Complex: ({negate, complex}) => z => complex(z.re, negate(z.im))
}

19
src/complex/equal.mjs Normal file
View File

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

@ -1,28 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const equalTT = {
'Complex<T>,Complex<T>': ({
T,
'self(T,T)': me
}) => Returns('boolean', (w, z) => me(w.re, z.re) && me(w.im, 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
}) => Returns('boolean', (z, x) => eqReal(z.re, x) && isZ(z.im)),
'T,Complex<T>': ({
'isZero(T)': isZ,
'self(T,T)': eqReal
}) => Returns('boolean', (b, z) => eqReal(z.re, b) && isZ(z.im)),
}

View File

@ -15,17 +15,4 @@ export default async function extendToComplex(pmath) {
// 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

@ -1,26 +1,17 @@
import PocomathInstance from '../core/PocomathInstance.mjs'
import Returns from '../core/Returns.mjs'
import * as Complex from './Types/Complex.mjs'
import * as Complex from './Types/Complex.mjs'
import gcdType from '../generic/gcdType.mjs'
const gcdComplexRaw = {}
Object.assign(gcdComplexRaw, gcdType('Complex<bigint>'))
Object.assign(gcdComplexRaw, gcdType('Complex<NumInt>'))
const imps = {
gcdComplexRaw,
gcdComplexRaw: gcdType('Complex'),
gcd: { // Only return gcds with positive real part
'Complex<T>,Complex<T>': ({
T,
'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
'sign(T)': sgn,
'one(T)': uno,
'negate(Complex<T>)': neg
}) => Returns(`Complex<${T}>`, (z,m) => {
const raw = gcdRaw(z, m)
if (sgn(raw.re) === uno(raw.re)) return raw
return neg(raw)
})
'Complex, Complex': ({gcdComplexRaw, sign, one, negate}) => (z,m) => {
const raw = gcdComplexRaw(z, m)
if (sign(raw.re) === one(raw.re)) return raw
return negate(raw)
}
}
}
export const gcd = PocomathInstance.merge(Complex, imps)

View File

@ -1,16 +1,9 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const invert = {
'Complex<T>': ({
T,
'conjugate(Complex<T>)': conj,
'absquare(Complex<T>)': asq,
'complex(T,T)': cplx,
'divide(T,T)': div
}) => Returns(`Complex<${T}>`, z => {
const c = conj(z)
const d = asq(z)
return cplx(div(c.re, d), div(c.im, d))
})
Complex: ({conjugate, absquare, complex, divide}) => z => {
const c = conjugate(z)
const d = absquare(z)
return complex(divide(c.re, d), divide(c.im, d))
}
}

View File

@ -1,7 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const isReal = {
'Complex<T>': ({'equal(T,T)': eq, 'add(T,T)': plus}) => Returns(
'boolean', z => eq(z.re, plus(z.re, z.im)))
}

View File

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

View File

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

View File

@ -1,26 +1,20 @@
import gcdType from '../generic/gcdType.mjs'
export * from './Types/Complex.mjs'
export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs'
export {add} from './add.mjs'
export {arg} from './arg.mjs'
export {associate} from './associate.mjs'
export {cbrtc} from './cbrtc.mjs'
export {cis} from './cis.mjs'
export {complex} from './complex.mjs'
export {conjugate} from './conjugate.mjs'
export {equalTT} from './equalTT.mjs'
export {complex} from './complex.mjs'
export {equal} from './equal.mjs'
export {gcd} from './gcd.mjs'
export {invert} from './invert.mjs'
export {isReal} from './isReal.mjs'
export {isZero} from './isZero.mjs'
export {multiply} from './multiply.mjs'
export {negate} from './negate.mjs'
export {polynomialRoot} from './polynomialRoot.mjs'
export {quaternion} from './quaternion.mjs'
export {quotient} from './quotient.mjs'
export {roundquotient} from './roundquotient.mjs'
export {sqrt} from './sqrt.mjs'
export {sqrtc} from './sqrtc.mjs'
export {zero} from './zero.mjs'

View File

@ -1,118 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const polynomialRoot = {
'Complex<T>,...Complex<T>': ({
T,
'tuple(...Complex<T>)': tupCplx,
'tuple(...T)': tupReal,
'isZero(Complex<T>)': zero,
'complex(T)': C,
'multiply(Complex<T>,Complex<T>)': mul,
'divide(Complex<T>,Complex<T>)': div,
'negate(Complex<T>)': neg,
'isReal(Complex<T>)': real,
'equalTT(Complex<T>,Complex<T>)': eq,
'add(Complex<T>,Complex<T>)': plus,
'subtract(Complex<T>,Complex<T>)': sub,
'sqrtc(Complex<T>)': sqt,
'cbrtc(Complex<T>)': cbt
}) => Returns(`Tuple<${T}>|Tuple<Complex<${T}>>`, (constant, rest) => {
// helper to convert results to appropriate tuple type
const typedTup = arr => {
if (arr.every(real)) {
return tupReal.apply(tupReal, arr.map(z => z.re))
}
return tupCplx.apply(tupCplx, arr)
}
const coeffs = [constant, ...rest]
while (coeffs.length > 0 && zero(coeffs[coeffs.length - 1])) {
coeffs.pop()
}
if (coeffs.length < 2) {
}
switch (coeffs.length) {
case 0: case 1:
throw new RangeError(
`Polynomial [${constant}, ${rest}] must have at least one`
+ 'non-zero non-constant coefficient')
case 2: // linear
return typedTup([neg(div(coeffs[0], coeffs[1]))])
case 3: { // quadratic
const [c, b, a] = coeffs
const denom = mul(C(2), a)
const d1 = mul(b, b)
const d2 = mul(C(4), mul(a, c))
if (eq(d1, d2)) {
return typedTup([div(neg(b), denom)])
}
let discriminant = sqt(sub(d1, d2))
return typedTup([
div(sub(discriminant, b), denom),
div(sub(neg(discriminant), b), denom)
])
}
case 4: { // cubic, cf. https://en.wikipedia.org/wiki/Cubic_equation
const [d, c, b, a] = coeffs
const denom = neg(mul(C(3), a))
const asqrd = mul(a, a)
const D0_1 = mul(b, b)
const bcubed = mul(D0_1, b)
const D0_2 = mul(C(3), mul(a, c))
const D1_1 = plus(
mul(C(2), bcubed), mul(C(27), mul(asqrd, d)))
const abc = mul(a, mul(b, c))
const D1_2 = mul(C(9), abc)
// Check for a triple root
if (eq(D0_1, D0_2) && eq(D1_1, D1_2)) {
return typedTup([div(b, denom)])
}
const Delta0 = sub(D0_1, D0_2)
const Delta1 = sub(D1_1, D1_2)
const csqrd = mul(c, c)
const discriminant1 = plus(
mul(C(18), mul(abc, d)), mul(D0_1, csqrd))
const discriminant2 = plus(
mul(C(4), mul(bcubed, d)),
plus(
mul(C(4), mul(a, mul(csqrd, c))),
mul(C(27), mul(asqrd, mul(d, d)))))
// See if we have a double root
if (eq(discriminant1, discriminant2)) {
return typedTup([
div(
sub(
mul(C(4), abc),
plus(mul(C(9), mul(asqrd, d)), bcubed)),
mul(a, Delta0)), // simple root
div(
sub(mul(C(9), mul(a, d)), mul(b, c)),
mul(C(2), Delta0)) // double root
])
}
// OK, we have three distinct roots
let Ccubed
if (eq(D0_1, D0_2)) {
Ccubed = Delta1
} else {
Ccubed = div(
plus(
Delta1,
sqt(sub(
mul(Delta1, Delta1),
mul(C(4), mul(Delta0, mul(Delta0, Delta0)))))
),
C(2))
}
const croots = cbt(Ccubed)
return typedTup(cbt(Ccubed).elts.map(
C => div(plus(b, plus(C, div(Delta0, C))), denom)))
}
default:
throw new RangeError(
'only implemented for cubic or lower-order polynomials, '
+ `not ${JSON.stringify(coeffs)}`)
}
})
}

View File

@ -1,14 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
// Might be nice to have type aliases!
export const quaternion = {
'T,T,T,T': ({
T,
'complex(T,T)': cplxT,
'complex(Complex<T>,Complex<T>)': quat
}) => Returns(
`Complex<Complex<${T}>>`,
(r,i,j,k) => quat(cplxT(r,j), cplxT(i,k))
)
}

View File

@ -1,9 +1,5 @@
import Returns from '../core/Returns.mjs'
export * from './roundquotient.mjs'
export const quotient = {
'Complex<T>,Complex<T>': ({
T,
'roundquotient(Complex<T>,Complex<T>)': rq
}) => Returns(`Complex<${T}>`, (w,z) => rq(w,z))
'Complex,Complex': ({roundquotient}) => (w,z) => roundquotient(w,z)
}

View File

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

View File

@ -1,34 +1,39 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const sqrt = {
'Complex<T>': ({
Complex: ({
config,
'sqrtc(Complex<T>)': predictableSqrt,
'isZero(T)': isZ,
isZero,
sign,
one,
add,
complex,
multiply,
self,
divide,
'abs(Complex)': abs,
subtract
}) => {
if (config.checkingDependency) return undefined
const complexReturns = returnTypeOf(predictableSqrt)
const baseReturns = complexReturns.slice(8, -1); // Complex<WhatWeWant>
if (config.predictable) {
return Returns(complexReturns, z => predictableSqrt(z))
}
return Returns(
`Complex<${baseReturns}>|${baseReturns}|undefined`,
z => {
let complexSqrt
try {
complexSqrt = predictableSqrt(z)
} catch (e) {
return undefined
}
if (complexSqrt.re === undefined || complexSqrt.im === undefined) {
return undefined
}
if (isZ(complexSqrt.im)) return complexSqrt.re
return complexSqrt
return z => {
const reOne = one(z.re)
if (isZero(z.im) && sign(z.re) === reOne) return complex(self(z.re))
const reTwo = add(reOne, reOne)
return complex(
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
self(divide(subtract(abs(z),z.re), reTwo))
)
}
)
}
return z => {
const reOne = one(z.re)
if (isZero(z.im) && sign(z.re) === reOne) return self(z.re)
const reTwo = add(reOne, reOne)
return complex(
multiply(sign(z.im), self(divide(add(abs(z),z.re), reTwo))),
self(divide(subtract(abs(z),z.re), reTwo))
)
}
}
}

View File

@ -1,41 +0,0 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs'
export const sqrtc = {
'Complex<T>': ({
'isZero(T)': isZ,
'sign(T)': sgn,
'one(T)': uno,
'add(T,T)': plus,
'complex(T)': cplxU,
'complex(T,T)': cplxB,
'multiply(T,T)': mult,
'sqrt(T)': sqt,
'divide(T,T)': div,
'absquare(Complex<T>)': absqC,
'subtract(T,T)': sub
}) => {
if (isZ.checkingDependency) return undefined
let baseReturns = returnTypeOf(sqt)
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]
}
return Returns(`Complex<${baseReturns}>`, z => {
const reOne = uno(z.re)
if (isZ(z.im) && sgn(z.re) === reOne) return cplxU(sqt(z.re))
const myabs = sqt(absqC(z))
const reTwo = plus(reOne, reOne)
const reQuot = div(plus(myabs, z.re), reTwo)
const imQuot = div(sub(myabs, z.re), reTwo)
if (reQuot === undefined || imQuot === undefined) {
throw new TypeError(`Cannot compute sqrt of ${z.re} + {z.im}i`)
}
return cplxB(
mult(sgn(z.im), sqt(div(plus(myabs, z.re), reTwo))),
sqt(div(sub(myabs, z.re), reTwo))
)
})
}
}

View File

@ -1,70 +0,0 @@
/* An object that holds a value and a reference to a PocomathInstance for
* applying operations to that value. Since the operations need to be wrapped,
* that instance is supposed to provide a place where wrapped operations can
* be stored, known as the repository.
*/
class Chain {
constructor(value, instance, repository) {
this.value = value
this.instance = instance
this.repository = repository
if (!('_wrapped' in this.repository)) {
this.repository._wrapped = {}
}
}
/* This is the wrapper for func which calls it with the chain's
* current value inserted as the first argument.
*/
_chainify(func, typed) {
return function () {
// Here `this` is the proxied Chain instance
if (arguments.length === 0) {
this.value = func(this.value)
} else {
const args = [this.value, ...arguments]
if (typed.isTypedFunction(func)) {
const sigObject = typed.resolve(func, args)
if (sigObject.params.length === 1) {
throw new Error(
`chain function ${func.name} attempting to split a rest`
+ 'parameter between chain value and other arguments')
}
this.value = sigObject.implementation.apply(func, args)
} else {
this.value = func.apply(func, args)
}
}
return this
}
}
}
export function makeChain(value, instance, repository) {
const chainObj = new Chain(value, instance, repository)
/* Rather than using the chainObj directly, we Proxy it to
* ensure we only get back wrapped, current methods of the instance.
*/
return new Proxy(chainObj, {
get: (target, property) => {
if (property === 'value') return target.value
if (!(property in target.instance)) {
throw new SyntaxError(`Unknown operation ${property}`)
}
if (property.charAt(0) === '_') {
throw new SyntaxError(`No access to private ${property}`)
}
const curval = target.instance[property]
if (typeof curval !== 'function') {
throw new SyntaxError(
`Property ${property} does not designate an operation`)
}
if (curval != target.repository._wrapped[property]) {
target.repository._wrapped[property] = curval
target.repository[property] = target._chainify(
curval, target.instance._typed)
}
return target.repository[property]
}
})
}

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +0,0 @@
/* Annotate a function with its return type */
/* Unfortunately JavaScript is missing a way to cleanly clone a function
* object, see https://stackoverflow.com/questions/1833588
*/
const clonedFrom = Symbol('the original function this one was cloned from')
function cloneFunction(fn) {
const behavior = fn[clonedFrom] || fn // don't nest clones
const theClone = function () { return behavior.apply(this, arguments) }
Object.assign(theClone, fn)
theClone[clonedFrom] = body
Object.defineProperty(
theClone, 'name', {value: fn.name, configurable: true })
return theClone
}
export function Returns(type, fn) {
if ('returns' in fn) fn = cloneFunction(fn)
fn.returns = type
return fn
}
export function returnTypeOf(fn, signature, pmInstance) {
const typeOfReturns = typeof fn.returns
if (typeOfReturns === 'undefined') return 'any'
if (typeOfReturns === 'string') return fn.returns
// not sure if we will need a function to determine the return type,
// but allow it for now:
return fn.returns(signature, pmInstance)
}
export default Returns

View File

@ -0,0 +1,12 @@
/* Call this with an empty Set object S, and it returns an entity E
* from which properties can be extracted, and at any time S will
* contain all of the property names that have been extracted from E.
*/
export default function dependencyExtractor(destinationSet) {
return new Proxy({}, {
get: (target, property) => {
destinationSet.add(property)
return {}
}
})
}

View File

@ -1,41 +0,0 @@
/* Call this with an empty Set object S, and it returns an entity E
* from which properties can be extracted, and at any time S will
* contain all of the property names that have been extracted from E.
*/
export function dependencyExtractor(destinationSet) {
return new Proxy({}, {
get: (target, property) => {
destinationSet.add(property)
return {checkingDependency: true}
}
})
}
/* Given a (template) type name, what the template parameter is,
* a top level typer, and a library of templates,
* produces a function that will extract the instantantion type from an
* instance. Currently relies heavily on there being only unary templates.
*
* We should really be using the typed-function parser to do the
* manipulations below, but at the moment we don't have access.
*/
export function generateTypeExtractor(
type, param, topTyper, typeJoiner, templates)
{
type = type.trim()
if (type.slice(0,3) === '...') {
type = type.slice(3).trim()
}
if (type === param) return topTyper
if (!(type.includes('<'))) return false // no template type to extract
const base = type.split('<',1)[0]
if (!(base in templates)) return false // unknown template
const arg = type.slice(base.length+1, -1)
const argExtractor = generateTypeExtractor(
arg, param, topTyper, typeJoiner, templates)
if (!argExtractor) return false
return templates[base].spec.infer({
typeOf: argExtractor,
joinTypes: typeJoiner
})
}

View File

@ -6,13 +6,6 @@ export function subsetOfKeys(set, obj) {
return true
}
/* Returns a list of the types mentioned in a typed-function signature */
export function typeListOfSignature(signature) {
signature = signature.trim()
if (!signature) return []
return signature.split(',').map(s => s.trim())
}
/* Returns a set of all of the types mentioned in a typed-function signature */
export function typesOfSignature(signature) {
return new Set(signature.split(/[^\w\d]/).filter(s => s.length))

View File

@ -1,91 +0,0 @@
import PocomathInstance from '../../core/PocomathInstance.mjs'
import Returns from '../../core/Returns.mjs'
/* creates a PocomathInstance incorporating a new numeric type encapsulated
* as a class. (This instance can the be `install()ed` in another to add the
* type so created.)
*
* @param {string} name The name of the new type
* @param {class} Thing The class implementing the new type
* @param {object} overrides Patches to the auto-generated adaptation
*/
export default function adapted(name, Thing, overrides) {
const thing = new PocomathInstance('Adapted Thing')
const test = overrides.isa || Thing.isa || (x => x instanceof Thing)
thing.installType(name, {
test,
from: overrides.from || {},
before: overrides.before || [],
refines: overrides.refines || undefined
})
// Build the operations for Thing
const operations = {}
// first a creator function, with name depending on the name of the thing:
const creatorName = overrides.creatorName || name.toLowerCase()
const creator = overrides[creatorName]
? overrides[creatorName]['']
: Thing[creatorName]
? (Thing[creatorName])
: ((...args) => new Thing(...args))
const defaultCreatorImps = {
'': () => Returns(name, () => creator()),
'...any': () => Returns(name, args => creator(...args))
}
defaultCreatorImps[name] = () => Returns(name, x => x) // x.clone(x)?
operations[creatorName] = overrides[creatorName] || defaultCreatorImps
// We make the default instance, just as a place to check for methods
const instance = overrides.instance || creator()
// Now adapt the methods to typed-function:
const unaryOps = {
abs: 'abs',
ceiling: 'ceil',
floor: 'floor',
invert: 'inverse',
round: 'round',
sqrt: 'sqrt',
negate: 'neg'
}
const binaryOps = {
add: ['add', name],
compare: ['compare', name],
divide: ['div', name],
equalTT: ['equals', 'boolean'],
gcd: ['gcd', name],
lcm: ['lcm', name],
mod: ['mod', name],
multiply: ['mul', name],
subtract: ['sub', name]
}
for (const [mathname, standardname] of Object.entries(unaryOps)) {
if (standardname in instance) {
operations[mathname] = {}
operations[mathname][name] = () => Returns(name, t => t[standardname]())
}
}
operations.zero = {}
operations.zero[name] = () => Returns(name, t => creator())
operations.one = {}
operations.one[name] = () => Returns(name, t => creator(1))
operations.conjugate = {}
operations.conjugate[name] = () => Returns(name, t => t) // or t.clone() ??
const binarySignature = `${name},${name}`
for (const [mathname, spec] of Object.entries(binaryOps)) {
if (spec[0] in instance) {
operations[mathname] = {}
operations[mathname][binarySignature] = () => Returns(
spec[1], (t,u) => t[spec[0]](u))
}
}
if ('operations' in overrides) {
Object.assign(operations, overrides.operations)
}
thing.install(operations)
return thing
}
export {adapted}

View File

@ -1,9 +0,0 @@
import Returns from '../core/Returns.mjs'
export const abs = {
T: ({
T,
'smaller(T,T)': lt,
'negate(T)': neg,
'zero(T)': zr
}) => Returns(T, t => (smaller(t, zr(t)) ? neg(t) : t))
}

View File

@ -1,9 +0,0 @@
import Returns from '../core/Returns.mjs'
export const absquare = {
T: ({
T,
'square(T)': sq,
'abs(T)': abval
}) => Returns(T, t => sq(abval(t)))
}

View File

@ -1,26 +1,2 @@
import {adapted} from './Types/adapted.mjs'
import Fraction from 'fraction.js/bigfraction.js'
import Returns from '../core/Returns.mjs'
export * from './arithmetic.mjs'
export * from './relational.mjs'
export const fraction = adapted('Fraction', Fraction, {
before: ['Complex'],
from: {number: n => new Fraction(n)},
operations: {
compare: {
'Fraction,Fraction': () => Returns(
'Fraction', (f,g) => new Fraction(f.compare(g)))
},
mod: {
'Fraction,Fraction': () => Returns('Fraction', (n,d) => {
// patch for "mathematician's modulus"
// OK to use full public API of Fraction here
const fmod = n.mod(d)
if (fmod.s === -1n) return fmod.add(d.abs())
return fmod
})
}
}
})

View File

@ -2,18 +2,11 @@ import {reducingOperation} from './reducingOperation.mjs'
export * from './Types/generic.mjs'
export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs'
export const add = reducingOperation
export {divide} from './divide.mjs'
export const gcd = reducingOperation
export {identity} from './identity.mjs'
export {lcm} from './lcm.mjs'
export {mean} from './mean.mjs'
export {mod} from './mod.mjs'
export const multiply = reducingOperation
export {quotient} from './quotient.mjs'
export {roundquotient} from './roundquotient.mjs'
export {divide} from './divide.mjs'
export {sign} from './sign.mjs'
export {sqrt} from './sqrt.mjs'
export {square} from './square.mjs'

View File

@ -1,10 +1,7 @@
import Returns from '../core/Returns.mjs'
export const divide = {
'T,T': ({
T,
'multiply(T,T)': multT,
'invert(T)': invT
}) => Returns(T, (x, y) => multT(x, invT(y)))
}) => (x, y) => multT(x, invT(y))
}

View File

@ -1,5 +1,3 @@
import Returns from '../core/Returns.mjs'
/* Note we do not use a template here so that we can explicitly control
* which types this is instantiated for, namely the "integer" types, and
* not simply allow Pocomath to generate instances for any type it encounters.
@ -9,14 +7,14 @@ export default function(type) {
const producer = refs => {
const modder = refs[`mod(${type},${type})`]
const zeroTester = refs[`isZero(${type})`]
return Returns(type, (a,b) => {
return (a,b) => {
while (!zeroTester(b)) {
const r = modder(a,b)
a = b
b = r
}
return a
})
}
}
const retval = {}
retval[`${type},${type}`] = producer

View File

@ -1,11 +0,0 @@
import Returns from '../core/Returns.mjs'
export function identityType(type) {
return () => Returns(type, x => x)
}
export function identitySubTypes(type) {
return ({T}) => Returns(T, x => x)
}
export const identity = {T: ({T}) => Returns(T, x => x)}

View File

@ -1,12 +1,7 @@
import Returns from '../core/Returns.mjs'
import {reducingOperation} from './reducingOperation.mjs'
export const lcm = {
'T,T': ({
T,
'multiply(T,T)': multT,
'quotient(T,T)': quotT,
'gcd(T,T)': gcdT
}) => Returns(T, (a,b) => multT(quotT(a, gcdT(a,b)), b))
}) => (a,b) => multT(quotT(a, gcdT(a,b)), b)
}
Object.assign(lcm, reducingOperation)

View File

@ -1,8 +0,0 @@
import Returns from '../core/Returns.mjs'
export const mean = {
'...T': ({
T,
add,
'divide(T,NumInt)': div
}) => Returns(T, args => div(add(...args), args.length))
}

View File

@ -1,10 +1,7 @@
import Returns from '../core/Returns.mjs'
export const mod = {
'T,T': ({
T,
'subtract(T,T)': subT,
'multiply(T,T)': multT,
'quotient(T,T)': quotT
}) => Returns(T, (a,m) => subT(a, multT(m, quotT(a,m))))
}) => (a,m) => subT(a, multT(m, quotT(a,m)))
}

13
src/generic/multiply.mjs Normal file
View File

@ -0,0 +1,13 @@
export * from './Types/generic.mjs'
export const multiply = {
'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u,
any: () => x => x,
'any,undefined': () => (x, u) => u,
'any,any,...any': ({self}) => (a,b,rest) => {
const later = [b, ...rest]
return later.reduce((x,y) => self(x,y), a)
}
}

View File

@ -1,9 +0,0 @@
import Returns from '../core/Returns.mjs'
export const quotient = {
'T,T': ({
T,
'floor(T)': flr,
'divide(T,T)': div
}) => Returns(T, (n,d) => flr(div(n,d)))
}

View File

@ -1,16 +1,12 @@
import Returns from '../core/Returns.mjs'
export * from './Types/generic.mjs'
export const reducingOperation = {
'undefined': () => Returns('undefined', u => u),
'undefined,...any': () => Returns('undefined', (u, rest) => u),
'any,undefined': () => Returns('undefined', (x, u) => u),
'undefined,undefined': () => Returns('undefined', (u,v) => u),
T: ({T}) => Returns(T, x => x),
// Unfortunately the type language of Pocomath is not (yet?) expressive
// enough to properly type the full reduction signature here:
'undefined': () => u => u,
'undefined,...any': () => (u, rest) => u,
'any,undefined': () => (x, u) => u,
any: () => x => x,
'any,any,...any': ({
self
}) => Returns('any', (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a))
}) => (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a)
}

View File

@ -1,83 +1,54 @@
import Returns from '../core/Returns.mjs'
export const compare = {
'undefined,undefined': () => Returns('NumInt', () => 0)
'undefined,undefined': () => () => 0
}
export const isZero = {
'undefined': () => Returns('boolean', u => u === 0),
T: ({
T,
'equal(T,T)': eq,
'zero(T)': zr
}) => Returns('boolean', t => eq(t, zr(t)))
'undefined': () => u => u === 0
}
export const equal = {
'any,any': ({
equalTT,
joinTypes,
Templates,
typeOf
}) => Returns('boolean', (x,y) => {
const resultant = joinTypes([typeOf(x), typeOf(y)], 'convert')
if (resultant === 'any' || resultant in Templates) {
return false
}
return equalTT(x,y)
})
}
export const equalTT = {
'T,T': ({
'!T,T': ({
'compare(T,T)': cmp,
'isZero(T)': isZ
}) => Returns('boolean', (x,y) => isZ(cmp(x,y)))
// If templates were native to typed-function, we should be able to
// do something like:
// 'any,any': () => () => false // should only be hit for different types
}) => (x,y) => isZ(cmp(x,y))
}
export const unequal = {
'any,any': ({equal}) => Returns('boolean', (x,y) => !(equal(x,y)))
'T,T': ({'equal(T.T)': eq}) => (x,y) => !(eq(x,y))
}
export const larger = {
'T,T': ({
'compare(T,T)': cmp,
'one(T)' : uno,
'equalTT(T,T)' : eq
}) => Returns('boolean', (x,y) => eq(cmp(x,y), uno(y)))
'one(T)' : uno
}) => (x,y) => cmp(x,y) === uno(y)
}
export const largerEq = {
'T,T': ({
'compare(T,T)': cmp,
'one(T)' : uno,
'isZero(T)' : isZ,
'equalTT(T,T)': eq
}) => Returns('boolean', (x,y) => {
'isZero(T)' : isZ
}) => (x,y) => {
const c = cmp(x,y)
return isZ(c) || eq(c, uno(y))
})
return isZ(c) || c === uno(y)
}
}
export const smaller = {
'T,T': ({
'compare(T,T)': cmp,
'one(T)' : uno,
'isZero(T)' : isZ,
unequal
}) => Returns('boolean', (x,y) => {
'isZero(T)' : isZ
}) => (x,y) => {
const c = cmp(x,y)
return !isZ(c) && unequal(c, uno(y))
})
return !isZ(c) && c !== uno(y)
}
}
export const smallerEq = {
'T,T': ({
'compare(T,T)': cmp,
'one(T)' : uno,
unequal
}) => Returns('boolean', (x,y) => unequal(cmp(x,y), uno(y)))
'one(T)' : uno
}) => (x,y) => cmp(x,y) !== uno(y)
}

View File

@ -1,9 +0,0 @@
import Returns from '../core/Returns.mjs'
export const roundquotient = {
'T,T': ({
T,
'round(T)': rnd,
'divide(T,T)':div
}) => Returns(T, (n,d) => rnd(div(n,d)))
}

View File

@ -1,9 +1,3 @@
import Returns from '../core/Returns.mjs'
export const sign = {
T: ({
T,
'compare(T,T)': cmp,
'zero(T)': Z
}) => Returns(T, x => cmp(x, Z(x)))
T: ({'compare(T,T)': cmp, 'zero(T)': Z}) => x => cmp(x, Z(x))
}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/generic.mjs'
export const sqrt = {undefined: () => Returns('undefined', () => undefined)}
export const sqrt = {undefined: () => () => undefined}

View File

@ -1,6 +1,3 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export const square = {
T: ({'multiply(T,T)': multT}) => Returns(
returnTypeOf(multT), x => multT(x,x))
T: ({'multiply(T,T)': multT}) => x => multT(x,x)
}

View File

@ -1,9 +1,3 @@
import Returns from '../core/Returns.mjs'
export const subtract = {
'T,T': ({
T,
'add(T,T)': addT,
'negate(T)': negT
}) => Returns(T, (x,y) => addT(x, negT(y)))
'T,T': ({'add(T,T)': addT, 'negate(T)': negT}) => (x,y) => addT(x, negT(y))
}

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const abs = {'T:number': ({T}) => Returns(T, n => Math.abs(n))}
export const abs = {number: () => n => Math.abs(n)}

View File

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

View File

@ -1,8 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const add = {
// Note the below assumes that all subtypes of number that will be defined
// are closed under addition!
'T:number,T': ({T}) => Returns(T, (m,n) => m+n)
}
export const add = {'number,number': () => (m,n) => m+n}

View File

@ -1,7 +1,6 @@
import PocomathInstance from '../core/PocomathInstance.mjs'
import * as numbers from './native.mjs'
import * as generic from '../generic/all.mjs'
import * as ops from '../ops/all.mjs'
export default PocomathInstance.merge('number', numbers, generic, ops)
export default PocomathInstance.merge('number', numbers, generic)

View File

@ -1,19 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
/* Returns just the real cube root, following mathjs implementation */
export const cbrt = {
number: ({'negate(number)': neg}) => Returns('number', x => {
if (x === 0) return x
const negate = x < 0
if (negate) x = neg(x)
let result = x
if (isFinite(x)) {
result = Math.exp(Math.log(x) / 3)
result = (x / (result * result) + (2 * result)) / 3
}
if (negate) return neg(result)
return result
})
}

View File

@ -1,5 +1,3 @@
import Returns from '../core/Returns.mjs'
/* Lifted from mathjs/src/utils/number.js */
/**
* Minimum number added to one that makes the result different than one
@ -50,6 +48,5 @@ function nearlyEqual (x, y, epsilon) {
export const compare = {
'number,number': ({
config
}) => Returns(
'NumInt', (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1))
}) => (x,y) => nearlyEqual(x, y, config.epsilon) ? 0 : (x > y ? 1 : -1)
}

View File

@ -1,5 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const invert = {number: () => Returns('number', n => 1/n)}
export const invert = {number: () => n => 1/n}

View File

@ -1,6 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const isZero = {
'T:number': () => Returns('boolean', n => n === 0)
}
export const isZero = {number: () => n => n === 0}

View File

@ -1,5 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const multiply = {'T:number,T': ({T}) => Returns(T, (m,n) => m*n)}
export const multiply = {'number,number': () => (m,n) => m*n}

View File

@ -1,14 +1,10 @@
import gcdType from '../generic/gcdType.mjs'
import {identitySubTypes} 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 {cbrt} from './cbrt.mjs'
export {compare} from './compare.mjs'
export const conjugate = {'T:number': identitySubTypes('number')}
export const gcd = gcdType('NumInt')
export {invert} from './invert.mjs'
export {isZero} from './isZero.mjs'

View File

@ -1,6 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const negate = {
'T:number': ({T}) => Returns(T, n => -n)
}
export const negate = {number: () => n => -n}

View File

@ -1,5 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const one = {number: () => Returns('NumInt', () => 1)}
export const one = {number: () => () => 1}

View File

@ -1,10 +1,8 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const quotient = {
'T:number,T': () => Returns('NumInt', (n,d) => {
'number,number': () => (n,d) => {
if (d === 0) return d
return Math.floor(n/d)
})
}
}

View File

@ -1,10 +1,8 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const roundquotient = {
'number,number': () => Returns('NumInt', (n,d) => {
'number,number': () => (n,d) => {
if (d === 0) return d
return Math.round(n/d)
})
}
}

View File

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

View File

@ -1,4 +1,3 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs'
export const zero = {number: () => Returns('NumInt', () => 0)}
export const zero = {number: () => () => 0}

View File

@ -1,4 +0,0 @@
export * from './choose.mjs'
export * from './factorial.mjs'
export * from './floor.mjs'

View File

@ -1,13 +0,0 @@
import Returns from '../core/Returns.mjs'
/* Note this is _not_ a good algorithm for computing binomial coefficients,
* it's just for demonstration purposes
*/
export const choose = {
'NumInt,NumInt': ({factorial}) => Returns(
'NumInt', (n,k) => Number(factorial(n) / (factorial(k)*factorial(n-k)))),
'bigint,bigint': ({
factorial
}) => Returns('bigint', (n,k) => factorial(n) / (factorial(k)*factorial(n-k)))
}

View File

@ -1,15 +0,0 @@
import Returns from '../core/Returns.mjs'
/* Plain functions are OK, too, and they can be decorated with a return type
* just like an implementation.
*/
const factorial = Returns('bigint', function factorial(n) {
n = BigInt(n)
let prod = 1n
for (let i = n; i > 1n; --i) {
prod *= i
}
return prod
})
export {factorial}

View File

@ -1,33 +0,0 @@
import Returns from '../core/Returns.mjs'
import {Complex} from '../complex/Types/Complex.mjs'
/* Note we don't **export** any types here, so that only the options
* below that correspond to types that have been installed are activated.
*/
export const floor = {
/* Because Pocomath isn't part of typed-function, nor does it have access
* to the real typed-function parse, we unfortunately can't coalesce the
* first several implementations into one entry with type
* `bigint|NumInt|GaussianInteger` because then they couldn't
* be separately activated
*/
bigint: () => Returns('bigint', x => x),
NumInt: () => Returns('NumInt', x => x),
'Complex<bigint>': () => Returns('Complex<bigint>', x => x),
number: ({'equalTT(number,number)': eq}) => Returns('NumInt', n => {
if (eq(n, Math.round(n))) return Math.round(n)
return Math.floor(n)
}),
'Complex<T>': Complex.promoteUnary['Complex<T>'],
// OK to include a type totally not in Pocomath yet, it'll never be
// activated.
BigNumber: ({
'round(BigNumber)': rnd,
'equal(BigNumber,BigNumber)': eq
}) => Returns('BigNumber', x => eq(x,round(x)) ? round(x) : x.floor())
}

View File

@ -3,11 +3,8 @@ import PocomathInstance from './core/PocomathInstance.mjs'
import * as numbers from './number/native.mjs'
import * as bigints from './bigint/native.mjs'
import * as complex from './complex/native.mjs'
import * as tuple from './tuple/native.mjs'
import * as generic from './generic/all.mjs'
import * as ops from './ops/all.mjs'
const math = PocomathInstance.merge(
'math', numbers, bigints, complex, tuple, generic, ops)
const math = PocomathInstance.merge('math', numbers, bigints, complex, generic)
export default math

View File

@ -1,100 +0,0 @@
/* A template type representing a homeogeneous tuple of elements */
import PocomathInstance from '../../core/PocomathInstance.mjs'
import {Returns, returnTypeOf} from '../../core/Returns.mjs'
const Tuple = new PocomathInstance('Tuple')
Tuple.installType('Tuple<T>', {
// A test that "defines" the "base type", which is not really a type
// (only fully instantiated types are added to the universe)
base: t => t && typeof t === 'object' && 'elts' in t && Array.isArray(t.elts),
// The template portion of the test; it takes the test for T as
// input and returns the test for an entity _that already passes
// the base test_ to be a Tuple<T>:
test: testT => t => t.elts.every(testT),
// And we need there to be a way to determine the (instantiation)
// type of an tuple (that has already passed the base test):
infer: ({typeOf, joinTypes}) => t => joinTypes(t.elts.map(typeOf)),
// Conversions. Parametrized conversions are only invoked for types
// U such that there is already a conversion from U to T, and that
// conversion is passed as an input, and we have to return the conversion
// function from the indicated template in terms of U to Tuple<T>:
from: {
'Tuple<U>': convert => tu => ({elts: tu.elts.map(convert)}),
// Here since there is no U it's a straight conversion:
T: t => ({elts: [t]}), // singleton promotion
// Whereas the following will let you go directly from an element
// convertible to T to a singleton Tuple<T>. Not sure if we really
// want that, but we'll try it just for kicks.
U: convert => u => ({elts: [convert(u)]})
}
})
Tuple.promoteUnary = {
'Tuple<T>': ({
'self(T)': me,
tuple
}) => {
const compType = me.fromInstance.joinTypes(
returnTypeOf(me).split('|'), 'convert')
return Returns(
`Tuple<${compType}>`, t => tuple(...(t.elts.map(x => me(x)))))
}
}
Tuple.promoteBinaryUnary = {
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, 'self(T)': meU, tuple}) => {
const compTypes = returnTypeOf(meB).split('|').concat(
returnTypeOf(meU).split('|'))
const compType = meU.fromInstance.joinTypes(compTypes, 'convert')
return Returns(`Tuple<${compType}>`, (s,t) => {
let i = -1
let result = []
while (true) {
i += 1
if (i < s.elts.length) {
if (i < t.elts.length) result.push(meB(s.elts[i], t.elts[i]))
else result.push(meU(s.elts[i]))
continue
}
if (i < t.elts.length) result.push(meU(t.elts[i]))
else break
}
return tuple(...result)
})
}
}
Tuple.promoteBinary = {
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, tuple}) => {
const compType = meB.fromInstance.joinTypes(
returnTypeOf(meB).split('|'))
return Returns(`Tuple<${compType}>`, (s,t) => {
const lim = Math.max(s.elts.length, t.elts.length)
const result = []
for (let i = 0; i < lim; ++i) {
result.push(meB(s.elts[i], t.elts[i]))
}
return tuple(...result)
})
}
}
Tuple.promoteBinaryStrict = {
'Tuple<T>,Tuple<T>': ({'self(T,T)': meB, tuple}) => {
const compType = meB.fromInstance.joinTypes(
returnTypeOf(meB).split('|'))
return Returns(`Tuple<${compType}>`, (s,t) => {
if (s.elts.length !== t.elts.length) {
throw new RangeError('Tuple length mismatch') // get name of self ??
}
const result = []
for (let i = 0; i < s.elts.length; ++i) {
result.push(meB(s.elts[i], t.elts[i]))
}
return tuple(...result)
})
}
}
export {Tuple}

View File

@ -1,16 +0,0 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Tuple.mjs'
export const equalTT = {
'Tuple<T>,Tuple<T>': ({
'self(T,T)': me,
'length(Tuple)': len
}) => Returns('boolean', (s,t) => {
if (len(s) !== len(t)) return false
for (let i = 0; i < len(s); ++i) {
if (!me(s.elts[i], t.elts[i])) return false
}
return true
})
}

View File

@ -1,11 +0,0 @@
import Returns from '../core/Returns.mjs'
export {Tuple} from './Types/Tuple.mjs'
export const isZero = {
'Tuple<T>': ({'self(T)': me}) => Returns(
'boolean', t => t.elts.every(e => me(e)))
// Note we can't just say `every(me)` above since every invokes its
// callback with more arguments, which then violates typed-function's
// signature for `me`
}

View File

@ -1,4 +0,0 @@
import Returns from '../core/Returns.mjs'
export {Tuple} from './Types/Tuple.mjs'
export const length = {'Tuple<T>': () => Returns('NumInt', t => t.elts.length)}

View File

@ -1,21 +0,0 @@
import {Tuple} from './Types/Tuple.mjs'
export const add = Tuple.promoteBinaryUnary
export const complex = Tuple.promoteBinaryStrict
export const conjugate = Tuple.promoteUnary
export const divide = Tuple.promoteBinaryStrict
export {equalTT} from './equalTT.mjs'
export const invert = Tuple.promoteUnary
export {isZero} from './isZero.mjs'
export {length} from './length.mjs'
export const multiply = Tuple.promoteBinaryUnary
export const negate = Tuple.promoteUnary
export const one = Tuple.promoteUnary
export const quotient = Tuple.promoteBinaryStrict
export const roundquotient = Tuple.promoteBinaryStrict
export const sqrt = Tuple.promoteUnary
export const subtract = Tuple.promoteBinaryStrict
export {tuple} from './tuple.mjs'
export const zero = Tuple.promoteUnary
export {Tuple}

View File

@ -1,10 +0,0 @@
import Returns from '../core/Returns.mjs'
export {Tuple} from './Types/Tuple.mjs'
/* The purpose of the template argument is to ensure that all of the args
* are convertible to the same type.
*/
export const tuple = {
'...T': ({T}) => Returns(`Tuple<${T}>`, args => ({elts: args}))
}

View File

@ -3,12 +3,12 @@ import PocomathInstance from '../src/core/PocomathInstance.mjs'
import math from '../src/pocomath.mjs'
describe('The default full pocomath instance "math"', () => {
it('has no unexpected undefined types', () => {
it('has no undefined types', () => {
const undef = math.undefinedTypes()
if (undef.length) {
console.log('NOTE: Found undefined types', undef)
console.log('Probable typo: found undefined types', undef)
}
assert.strictEqual(undef.length, 1) // Mentioning 'Fraction' but not using
assert.strictEqual(undef.length, 0)
})
it('has a built-in typeOf operator', () => {
@ -16,60 +16,13 @@ describe('The default full pocomath instance "math"', () => {
assert.strictEqual(math.typeOf(-1.5), 'number')
assert.strictEqual(math.typeOf(-42n), 'bigint')
assert.strictEqual(math.typeOf(undefined), 'undefined')
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex<number>')
})
it('can determine the return types of operations', () => {
assert.strictEqual(math.returnTypeOf('negate', 'number'), 'number')
assert.strictEqual(math.returnTypeOf('negate', 'NumInt'), 'NumInt')
math.negate(math.complex(1.2, 2.8)) // TODO: make this call unnecessary
assert.strictEqual(
math.returnTypeOf('negate', 'Complex<number>'), 'Complex<number>')
assert.strictEqual(math.returnTypeOf('add', 'number,number'), 'number')
assert.strictEqual(math.returnTypeOf('add', 'NumInt,NumInt'), 'NumInt')
assert.strictEqual(math.returnTypeOf('add', 'NumInt,number'), 'number')
assert.strictEqual(math.returnTypeOf('add', 'number,NumInt'), 'number')
assert.deepStrictEqual( // TODO: ditto
math.add(3, math.complex(2.5, 1)), math.complex(5.5, 1))
assert.strictEqual(
math.returnTypeOf('add', 'Complex<number>,NumInt'), 'Complex<number>')
// The following is not actually what we want, but the Pocomath type
// language isn't powerful enough at this point to capture the true
// return type:
assert.strictEqual(
math.returnTypeOf('add', 'number,NumInt,Complex<number>'), 'any')
assert.strictEqual(
math.returnTypeOf('chain', 'bigint'), 'Chain<bigint>')
assert.strictEqual(
math.returnTypeOf('returnTypeOf', 'string,string'), 'string')
assert.strictEqual(
math.returnTypeOf('conjugate', 'bigint'), 'bigint')
assert.strictEqual(
math.returnTypeOf('gcd', 'bigint,bigint'), 'bigint')
math.identity(math.fraction(3,5)) // TODO: ditto
assert.strictEqual(math.returnTypeOf('identity', 'Fraction'), 'Fraction')
assert.strictEqual(
math.returnTypeOf('quotient', 'bigint,bigint'), 'bigint')
math.abs(math.complex(2,1)) //TODO: ditto
assert.strictEqual(
math.returnTypeOf('abs','Complex<NumInt>'), 'number')
math.multiply(math.quaternion(1,1,1,1), math.quaternion(1,-1,1,-1)) // dit
const quatType = math.returnTypeOf(
'quaternion', 'NumInt,NumInt,NumInt,NumInt')
assert.strictEqual(quatType, 'Complex<Complex<NumInt>>')
assert.strictEqual(
math.returnTypeOf('multiply', quatType + ',' + quatType), quatType)
assert.strictEqual(math.returnTypeOf('isZero', 'NumInt'), 'boolean')
assert.strictEqual(
math.returnTypeOf('roundquotient', 'NumInt,number'), 'NumInt')
assert.strictEqual(
math.returnTypeOf('factorial', 'NumInt'), 'bigint')
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex')
})
it('can subtract numbers', () => {
assert.strictEqual(math.subtract(12, 5), 7)
assert.throws(() => math.subtract(3n, 1.5), 'TypeError')
//assert.strictEqual(math.subtract(3n, 1.5), 1.5)
})
it('can add numbers', () => {
@ -129,32 +82,4 @@ describe('The default full pocomath instance "math"', () => {
math.complex(11n, -4n))
assert.strictEqual(math.negate(math.complex(3n, 8n)).im, -8n)
})
it('creates chains', () => {
const mychain = math.chain(7).negate()
assert.strictEqual(mychain.value, -7)
mychain.add(23).sqrt().lcm(10)
assert.strictEqual(mychain.value, 20)
assert.strictEqual(math.mean(3,4,5), 4)
assert.throws(() => math.chain(3).mean(4,5), /chain function.*split/)
assert.throws(() => math.chain(3).foo(), /Unknown operation/)
})
it('calls plain factorial function', () => {
assert.strictEqual(math.factorial(4), 24n)
assert.strictEqual(math.factorial(7n), 5040n)
})
it('calculates binomial coefficients', () => {
assert.strictEqual(math.choose(6, 3), 20)
assert.strictEqual(math.choose(21n, 2n), 210n)
})
it('calculates multi-way gcds and lcms', () => {
assert.strictEqual(math.gcd(30,105,42), 3)
const gaussianLCM = math.lcm(
math.complex(2n,1n), math.complex(1n,1n), math.complex(0n,1n))
assert.strictEqual(math.associate(gaussianLCM, math.complex(1n,3n)), true)
})
})

View File

@ -64,8 +64,4 @@ describe('bigint', () => {
assert.strictEqual(math.lcm(0n, 0n), 0n)
})
it('allows floor as a no-op', () => {
assert.strictEqual(math.floor(-333n), -333n)
})
})

View File

@ -39,58 +39,16 @@ describe('complex', () => {
})
it('checks for equality', () => {
assert.ok(math.equal(math.complex(3, 0), 3))
assert.ok(math.equal(math.complex(3, 2), math.complex(3, 2)))
assert.ok(math.equal(math.complex(3,0), 3))
assert.ok(math.equal(math.complex(3,2), math.complex(3, 2)))
assert.ok(!(math.equal(math.complex(45n, 3n), math.complex(45n, -3n))))
assert.ok(!(math.equal(math.complex(45n, 3n), 45n)))
})
it('tests for reality', () => {
assert.ok(math.isReal(math.complex(3, 0)))
assert.ok(!(math.isReal(math.complex(3, 2))))
})
it('computes gcd', () => {
assert.deepStrictEqual(
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
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', () => {
assert.deepStrictEqual(
math.floor(math.complex(19, 22.7)),
math.complex(19, 22))
const gi = math.complex(-1n, 1n)
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))
math.absquare(math.complex(1.25, 2.5)) //HACK: need absquare(Complex<number>)
assert.strictEqual(math.abs(q0), Math.sqrt(2))
assert.strictEqual(math.abs(q1), Math.sqrt(33)/4)
})
})

Some files were not shown because too many files have changed in this diff Show More