Compare commits

..

18 Commits

Author SHA1 Message Date
0dbb95bbbe feat(polynomialRoot) (#57)
Implements a simply polynomial root finder function
polynomialRoot, intended to be used for benchmarking
against mathjs.

For this purpose, adds numerous other functions (e.g.
cbrt, arg, cis), refactors sqrt (so that you can
definitely get the complex square root when you want
it), and makes numerous enhancements to the core so
that a template can match after conversions.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #57
2022-12-01 17:47:20 +00:00
31add66f4c feat: Return type annotations (#53)
Provides the infrastructure to allow annotating the return types of functions, and does so for essentially every operation in the system (the only known exceptions being add, multiply, etc., on arbitrarily many arguments).

One main infrastructure enhancements are bounded template types, e.g. `T:number` being a template parameter where T can take on the type `number` or any subtype thereof.

A main internal enhancement is that base template types are no longer added to the typed universe; rather, there is a secondary, "meta" typed universe where they live. The primary point/purpose of this change is then the necessary search order for implementations can be much better modeled by typed-function's search order, using the `onMismatch` facility to redirect the search from fully instantiated implementations to the generic catchall implementations for each template (these catchalls live in the meta universe).

Numerous other small improvements and bugfixes were encountered along the way.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #53
2022-08-30 19:36:44 +00:00
207ac4330b doc(README): Add a note about adopting 3rd party types 2022-08-07 16:38:15 +00:00
9267a8df60 Merge pull request 'feat: Generic numerical types' (#50) from numeric_adapter into main
Reviewed-on: #50
2022-08-07 16:34:43 +00:00
bcbb24acd2 feat: Generic numerical types
Inspired by https://github.com/josdejong/mathjs/discussions/2212 and
  https://github.com/josdejong/mathjs/issues/2585.

  Provides a simple adapter function `adapted` which takes a class
  implementing an arithmetical datatype and returns a PocomathInstance
  with a new type for that class, invoking the methods of the class
  in a standard way for the Pocomath/mathjs operations.  (That instance
  can then be installed in another to add the new type to any instance
  you like, including the default one.)

  Uses this facility to bring fraction.js Fraction into Pocomath, and
  tests the resulting type.

  Currently the "standard" interface for an arithmetical type is heavily modeled
  after the design of fraction.js, but with experience with other 3rd-party types
  it could be streamlined to something pretty generic (and the Fraction
  adaptation could be patched to conform to the resulting "standard"). Or a
  proposal along the lines of https://github.com/josdejong/mathjs/discussions/2212
  could be adopted, and a shim could be added to fraction.js to conform to
  **that** standard.

  Resolves #30.
2022-08-07 09:19:27 -07:00
199ffd2654 doc(README): Add brief notes about new template facilities 2022-08-07 03:34:39 +00:00
28ccbf8d48 Merge pull request 'feat(quaternion): Add convenience quaternion creator function' (#48) from quaternion into main
Reviewed-on: #48
2022-08-07 03:27:46 +00:00
e26df5f4fc 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.
2022-08-06 20:13:50 -07:00
40619b9a2e Merge pull request 'refactor(Complex): Now a template type!' (#46) from template_complex into main
Reviewed-on: #46
2022-08-06 15:42:19 +00:00
1444b9828f refactor(Complex): Now a template type!
This means that the real and imaginary parts of a Complex must now be
  the same type. This seems like a real benefit: a Complex with a number real
  part and a bigint imaginary part does not seem sensible.

  Note that this is now straining typed-function in (at least) the following
  ways:
  (1) In this change, it was necessary to remove the logic that the square root
      of a negative number calls complex square root, which then calls back
      to the number square root in its algorithm. (This was creating a circular
      reference in the typed-function which the old implementation of Complex
      was somehow sidestepping.)
  (2) typed-function could not follow conversions that would be allowed by
      uninstantiated templates (e.g. number => Complex<number> if the latter
      template has not been instantiated) and so the facility for
      instantiating a template was surfaced (and for example is called explicitly
      in the demo loader `extendToComplex`. Similarly, this necessitated
      making the unary signature of the `complex` conversion function explicit,
      rather than just via implicit conversion to Complex.
  (3) I find the order of implementations is mattering more in typed-function
      definitions, implying that typed-function's sorting algorithm is having
      trouble distinguishing alternatives.

  But otherwise, the conversion went quite smoothly and I think is a good demo
  of the power of this approach. And I expect that it will work even more
  smoothly if some of the underlying facilities (subtypes, template types) are
  integrated into typed-function.
2022-08-06 08:27:44 -07:00
845a2354c9 feat: Template types (#45)
Includes a full implementation of a type-homogeneous Tuple type, using the template types
  feature, as a demonstration/check of its operation.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #45
2022-08-05 12:48:57 +00:00
fd32ee1f10 Merge pull request 'feat(install): Allow plain functions' (#44) from install_plain into main
Reviewed-on: #44
2022-08-02 07:50:14 +00:00
8418817f9f feat(install): Allow plain functions 2022-08-02 00:47:37 -07:00
0ce4de086c Merge pull request 'feat(Chain): add computation pipelines like mathjs' (#43) from chainify into main
Reviewed-on: #43
2022-08-01 23:28:32 +00:00
814f660000 feat(Chain): add computation pipelines like mathjs
Also adds a `mean()` operation so there will be at least one operation
  that takes only a rest parameter, to exercise the ban on splitting
  such a parameter between the stored value and new arguments.
  Adds various tests of chains.

  Resolves #32.
2022-08-01 16:24:20 -07:00
46ae7f78ab Merge pull request 'feat(floor): Provide example of op-centric organization' (#42) from op_centric into main
Reviewed-on: #42
2022-08-01 15:30:10 +00:00
7d1a435aa0 feat(floor): Provide example of op-centric organization 2022-08-01 08:28:21 -07:00
fe54bc6004 feat: Template operations (#41)
Relational functions are added using templates, and existing generic functions are made more strict with them. Also a new built-in typeOf function is added, that automatically updates itself.

Resolves #34.

Co-authored-by: Glen Whitney <glen@studioinfinity.org>
Reviewed-on: #41
2022-08-01 10:09:32 +00:00
109 changed files with 3147 additions and 460 deletions

View File

@ -25,3 +25,7 @@ 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. 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 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,6 +24,7 @@
}, },
dependencies: { dependencies: {
'bigint-isqrt': '^0.2.1', 'bigint-isqrt': '^0.2.1',
'fraction.js': '^4.2.0',
'typed-function': '^3.0.0', 'typed-function': '^3.0.0',
}, },
} }

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,30 +1,31 @@
import {Returns, returnTypeOf} from '../../core/Returns.mjs'
import PocomathInstance from '../../core/PocomathInstance.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') const Complex = new PocomathInstance('Complex')
Complex.installType('Complex', { // Now the template type: Complex numbers are actually always homogeneous
test: isComplex, // 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)]),
from: { from: {
number: x => ({re: x, im: 0}) T: t => ({re: t, im: t-t}), // hack: maybe need a way to call zero(T)
} U: convert => u => {
}) const t = convert(u)
Complex.installType('GaussianInteger', { return ({re: t, im: t-t})
test: z => typeof z.re == 'bigint' && typeof z.im == 'bigint', },
refines: 'Complex', 'Complex<U>': convert => cu => ({re: convert(cu.re), im: convert(cu.im)})
from: {
bigint: x => ({re: x, im: 0n})
} }
}) })
Complex.promoteUnary = { Complex.promoteUnary = {
Complex: ({self,complex}) => z => complex(self(z.re), self(z.im)) 'Complex<T>': ({
T,
'self(T)': me,
complex
}) => Returns(
`Complex<${returnTypeOf(me)}>`, z => complex(me(z.re), me(z.im)))
} }
export {Complex} export {Complex}

View File

@ -1,5 +1,20 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const abs = { export const abs = {
Complex: ({sqrt, 'absquare(Complex)': absq}) => z => sqrt(absq(z)) '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)))
}
} }

View File

@ -1,5 +1,27 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const absquare = { export const absquare = {
Complex: ({add, square}) => z => add(square(z.re), square(z.im)) '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)))
} }
}
/* 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,22 +1,10 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const add = { export const add = {
/* Relying on conversions for both complex + number and complex + bigint 'Complex<T>,Complex<T>': ({
* leads to an infinite loop when adding a number and a bigint, since they T,
* both convert to Complex. 'self(T,T)': me,
*/ 'complex(T,T)': cplx
'Complex,number': ({ }) => Returns(`Complex<${T}>`, (w,z) => cplx(me(w.re, z.re), me(w.im, z.im)))
'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,5 +1,6 @@
import PocomathInstance from '../core/PocomathInstance.mjs' import PocomathInstance from '../core/PocomathInstance.mjs'
import * as complexes from './native.mjs' import * as complexes from './native.mjs'
import * as generic from '../generic/arithmetic.mjs' import * as generic from '../generic/arithmetic.mjs'
import * as floor from '../ops/floor.mjs'
export default PocomathInstance.merge('complex', complexes, generic) export default PocomathInstance.merge('complex', complexes, generic, floor)

7
src/complex/arg.mjs Normal file
View File

@ -0,0 +1,7 @@
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))
}

18
src/complex/associate.mjs Normal file
View File

@ -0,0 +1,18 @@
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))
})
}

28
src/complex/cbrtc.mjs Normal file
View File

@ -0,0 +1,28 @@
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))
)
})
}

9
src/complex/cis.mjs Normal file
View File

@ -0,0 +1,9 @@
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,3 +1,4 @@
import Returns from '../core/Returns.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export * from '../generic/Types/generic.mjs' export * from '../generic/Types/generic.mjs'
@ -6,10 +7,16 @@ export const complex = {
* have a numeric/scalar type, e.g. by implementing subtypes in * have a numeric/scalar type, e.g. by implementing subtypes in
* typed-function * typed-function
*/ */
'undefined': () => u => u, 'undefined': () => Returns('undefined', u => u),
'undefined,any': () => (u, y) => u, 'undefined,any': () => Returns('undefined', (u, y) => u),
'any,undefined': () => (x, u) => u, 'any,undefined': () => Returns('undefined', (x, u) => u),
'any,any': () => (x, y) => ({re: x, im: y}), 'undefined,undefined': () => Returns('undefined', (u, v) => u),
'T,T': ({T}) => Returns(`Complex<${T}>`, (x, y) => ({re: x, im: y})),
/* Take advantage of conversions in typed-function */ /* Take advantage of conversions in typed-function */
Complex: () => z => z // '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)}))
} }

View File

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

View File

@ -1,19 +0,0 @@
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)
}

28
src/complex/equalTT.mjs Normal file
View File

@ -0,0 +1,28 @@
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,4 +15,17 @@ export default async function extendToComplex(pmath) {
// Guess it wasn't a method available in complex; no worries // 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,17 +1,26 @@
import PocomathInstance from '../core/PocomathInstance.mjs' 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' import gcdType from '../generic/gcdType.mjs'
const gcdComplexRaw = {}
Object.assign(gcdComplexRaw, gcdType('Complex<bigint>'))
Object.assign(gcdComplexRaw, gcdType('Complex<NumInt>'))
const imps = { const imps = {
gcdComplexRaw: gcdType('Complex'), gcdComplexRaw,
gcd: { // Only return gcds with positive real part gcd: { // Only return gcds with positive real part
'Complex, Complex': ({gcdComplexRaw, sign, one, negate}) => (z,m) => { 'Complex<T>,Complex<T>': ({
const raw = gcdComplexRaw(z, m) T,
if (sign(raw.re) === one(raw.re)) return raw 'gcdComplexRaw(Complex<T>,Complex<T>)': gcdRaw,
return negate(raw) '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)
})
} }
} }
export const gcd = PocomathInstance.merge(Complex, imps) export const gcd = PocomathInstance.merge(Complex, imps)

View File

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

7
src/complex/isReal.mjs Normal file
View File

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

View File

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

View File

@ -1,20 +1,26 @@
import gcdType from '../generic/gcdType.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export {abs} from './abs.mjs' export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs' export {absquare} from './absquare.mjs'
export {add} from './add.mjs' export {add} from './add.mjs'
export {conjugate} from './conjugate.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 {complex} from './complex.mjs'
export {equal} from './equal.mjs' export {conjugate} from './conjugate.mjs'
export {equalTT} from './equalTT.mjs'
export {gcd} from './gcd.mjs' export {gcd} from './gcd.mjs'
export {invert} from './invert.mjs' export {invert} from './invert.mjs'
export {isReal} from './isReal.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 {polynomialRoot} from './polynomialRoot.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'
export {sqrtc} from './sqrtc.mjs'
export {zero} from './zero.mjs' export {zero} from './zero.mjs'

View File

@ -0,0 +1,118 @@
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

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

View File

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

View File

@ -1,39 +1,34 @@
import {Returns, returnTypeOf} from '../core/Returns.mjs'
export * from './Types/Complex.mjs' export * from './Types/Complex.mjs'
export const sqrt = { export const sqrt = {
Complex: ({ 'Complex<T>': ({
config, config,
isZero, 'sqrtc(Complex<T>)': predictableSqrt,
sign, 'isZero(T)': isZ,
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) { if (config.predictable) {
return z => { return Returns(complexReturns, z => predictableSqrt(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))
)
}
}
} }
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
}
)
}
}

41
src/complex/sqrtc.mjs Normal file
View File

@ -0,0 +1,41 @@
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))
)
})
}
}

70
src/core/Chain.mjs Normal file
View File

@ -0,0 +1,70 @@
/* 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

34
src/core/Returns.mjs Normal file
View File

@ -0,0 +1,34 @@
/* 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

@ -1,12 +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 default function dependencyExtractor(destinationSet) {
return new Proxy({}, {
get: (target, property) => {
destinationSet.add(property)
return {}
}
})
}

41
src/core/extractors.mjs Normal file
View File

@ -0,0 +1,41 @@
/* 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,6 +6,13 @@ export function subsetOfKeys(set, obj) {
return true 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 */ /* Returns a set of all of the types mentioned in a typed-function signature */
export function typesOfSignature(signature) { export function typesOfSignature(signature) {
return new Set(signature.split(/[^\w\d]/).filter(s => s.length)) return new Set(signature.split(/[^\w\d]/).filter(s => s.length))

View File

@ -0,0 +1,91 @@
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}

9
src/generic/abs.mjs Normal file
View File

@ -0,0 +1,9 @@
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))
}

9
src/generic/absquare.mjs Normal file
View File

@ -0,0 +1,9 @@
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,2 +1,26 @@
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 './arithmetic.mjs'
export * from './relational.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,11 +2,18 @@ import {reducingOperation} from './reducingOperation.mjs'
export * from './Types/generic.mjs' export * from './Types/generic.mjs'
export {abs} from './abs.mjs'
export {absquare} from './absquare.mjs'
export const add = reducingOperation export const add = reducingOperation
export {divide} from './divide.mjs'
export const gcd = reducingOperation
export {identity} from './identity.mjs'
export {lcm} from './lcm.mjs' export {lcm} from './lcm.mjs'
export {mean} from './mean.mjs'
export {mod} from './mod.mjs' export {mod} from './mod.mjs'
export const multiply = reducingOperation export const multiply = reducingOperation
export {divide} from './divide.mjs' export {quotient} from './quotient.mjs'
export {roundquotient} from './roundquotient.mjs'
export {sign} from './sign.mjs' export {sign} from './sign.mjs'
export {sqrt} from './sqrt.mjs' export {sqrt} from './sqrt.mjs'
export {square} from './square.mjs' export {square} from './square.mjs'

View File

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

View File

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

11
src/generic/identity.mjs Normal file
View File

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

8
src/generic/mean.mjs Normal file
View File

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

View File

@ -1,13 +0,0 @@
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)
}
}

9
src/generic/quotient.mjs Normal file
View File

@ -0,0 +1,9 @@
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,12 +1,16 @@
import Returns from '../core/Returns.mjs'
export * from './Types/generic.mjs' export * from './Types/generic.mjs'
export const reducingOperation = { export const reducingOperation = {
'undefined': () => u => u, 'undefined': () => Returns('undefined', u => u),
'undefined,...any': () => (u, rest) => u, 'undefined,...any': () => Returns('undefined', (u, rest) => u),
'any,undefined': () => (x, u) => u, 'any,undefined': () => Returns('undefined', (x, u) => u),
any: () => x => x, '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:
'any,any,...any': ({ 'any,any,...any': ({
self self
}) => (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a) }) => Returns('any', (a,b,rest) => [b, ...rest].reduce((x,y) => self(x,y), a))
} }

View File

@ -1,54 +1,83 @@
import Returns from '../core/Returns.mjs'
export const compare = { export const compare = {
'undefined,undefined': () => () => 0 'undefined,undefined': () => Returns('NumInt', () => 0)
} }
export const isZero = { export const isZero = {
'undefined': () => u => u === 0 'undefined': () => Returns('boolean', u => u === 0),
T: ({
T,
'equal(T,T)': eq,
'zero(T)': zr
}) => Returns('boolean', t => eq(t, zr(t)))
} }
export const equal = { export const equal = {
'!T,T': ({ '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': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'isZero(T)': isZ 'isZero(T)': isZ
}) => (x,y) => isZ(cmp(x,y)) }) => 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
} }
export const unequal = { export const unequal = {
'T,T': ({'equal(T.T)': eq}) => (x,y) => !(eq(x,y)) 'any,any': ({equal}) => Returns('boolean', (x,y) => !(equal(x,y)))
} }
export const larger = { export const larger = {
'T,T': ({ 'T,T': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno 'one(T)' : uno,
}) => (x,y) => cmp(x,y) === uno(y) 'equalTT(T,T)' : eq
}) => Returns('boolean', (x,y) => eq(cmp(x,y), uno(y)))
} }
export const largerEq = { export const largerEq = {
'T,T': ({ 'T,T': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno, 'one(T)' : uno,
'isZero(T)' : isZ 'isZero(T)' : isZ,
}) => (x,y) => { 'equalTT(T,T)': eq
}) => Returns('boolean', (x,y) => {
const c = cmp(x,y) const c = cmp(x,y)
return isZ(c) || c === uno(y) return isZ(c) || eq(c, uno(y))
} })
} }
export const smaller = { export const smaller = {
'T,T': ({ 'T,T': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno, 'one(T)' : uno,
'isZero(T)' : isZ 'isZero(T)' : isZ,
}) => (x,y) => { unequal
}) => Returns('boolean', (x,y) => {
const c = cmp(x,y) const c = cmp(x,y)
return !isZ(c) && c !== uno(y) return !isZ(c) && unequal(c, uno(y))
} })
} }
export const smallerEq = { export const smallerEq = {
'T,T': ({ 'T,T': ({
'compare(T,T)': cmp, 'compare(T,T)': cmp,
'one(T)' : uno 'one(T)' : uno,
}) => (x,y) => cmp(x,y) !== uno(y) unequal
}) => Returns('boolean', (x,y) => unequal(cmp(x,y), uno(y)))
} }

View File

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

View File

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

View File

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

View File

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

View File

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

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

@ -0,0 +1,7 @@
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,3 +1,8 @@
import Returns from '../core/Returns.mjs'
export * from './Types/number.mjs' export * from './Types/number.mjs'
export const add = {'number,number': () => (m,n) => m+n} 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)
}

View File

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

19
src/number/cbrt.mjs Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

4
src/ops/all.mjs Normal file
View File

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

13
src/ops/choose.mjs Normal file
View File

@ -0,0 +1,13 @@
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)))
}

15
src/ops/factorial.mjs Normal file
View File

@ -0,0 +1,15 @@
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}

33
src/ops/floor.mjs Normal file
View File

@ -0,0 +1,33 @@
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,8 +3,11 @@ import PocomathInstance from './core/PocomathInstance.mjs'
import * as numbers from './number/native.mjs' import * as numbers from './number/native.mjs'
import * as bigints from './bigint/native.mjs' import * as bigints from './bigint/native.mjs'
import * as complex from './complex/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 generic from './generic/all.mjs'
import * as ops from './ops/all.mjs'
const math = PocomathInstance.merge('math', numbers, bigints, complex, generic) const math = PocomathInstance.merge(
'math', numbers, bigints, complex, tuple, generic, ops)
export default math export default math

100
src/tuple/Types/Tuple.mjs Normal file
View File

@ -0,0 +1,100 @@
/* 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}

16
src/tuple/equalTT.mjs Normal file
View File

@ -0,0 +1,16 @@
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
})
}

11
src/tuple/isZero.mjs Normal file
View File

@ -0,0 +1,11 @@
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`
}

4
src/tuple/length.mjs Normal file
View File

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

21
src/tuple/native.mjs Normal file
View File

@ -0,0 +1,21 @@
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}

10
src/tuple/tuple.mjs Normal file
View File

@ -0,0 +1,10 @@
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' import math from '../src/pocomath.mjs'
describe('The default full pocomath instance "math"', () => { describe('The default full pocomath instance "math"', () => {
it('has no undefined types', () => { it('has no unexpected undefined types', () => {
const undef = math.undefinedTypes() const undef = math.undefinedTypes()
if (undef.length) { if (undef.length) {
console.log('Probable typo: found undefined types', undef) console.log('NOTE: Found undefined types', undef)
} }
assert.strictEqual(undef.length, 0) assert.strictEqual(undef.length, 1) // Mentioning 'Fraction' but not using
}) })
it('has a built-in typeOf operator', () => { it('has a built-in typeOf operator', () => {
@ -16,13 +16,60 @@ describe('The default full pocomath instance "math"', () => {
assert.strictEqual(math.typeOf(-1.5), 'number') assert.strictEqual(math.typeOf(-1.5), 'number')
assert.strictEqual(math.typeOf(-42n), 'bigint') assert.strictEqual(math.typeOf(-42n), 'bigint')
assert.strictEqual(math.typeOf(undefined), 'undefined') assert.strictEqual(math.typeOf(undefined), 'undefined')
assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'GaussianInteger') assert.strictEqual(math.typeOf({re: 15n, im: -2n}), 'Complex<bigint>')
assert.strictEqual(math.typeOf({re: 6.28, im: 2.72}), 'Complex') 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')
}) })
it('can subtract numbers', () => { it('can subtract numbers', () => {
assert.strictEqual(math.subtract(12, 5), 7) assert.strictEqual(math.subtract(12, 5), 7)
//assert.strictEqual(math.subtract(3n, 1.5), 1.5) assert.throws(() => math.subtract(3n, 1.5), 'TypeError')
}) })
it('can add numbers', () => { it('can add numbers', () => {
@ -82,4 +129,32 @@ describe('The default full pocomath instance "math"', () => {
math.complex(11n, -4n)) math.complex(11n, -4n))
assert.strictEqual(math.negate(math.complex(3n, 8n)).im, -8n) 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,4 +64,8 @@ describe('bigint', () => {
assert.strictEqual(math.lcm(0n, 0n), 0n) assert.strictEqual(math.lcm(0n, 0n), 0n)
}) })
it('allows floor as a no-op', () => {
assert.strictEqual(math.floor(-333n), -333n)
})
}) })

View File

@ -45,10 +45,52 @@ describe('complex', () => {
assert.ok(!(math.equal(math.complex(45n, 3n), 45n))) 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', () => { it('computes gcd', () => {
assert.deepStrictEqual( assert.deepStrictEqual(
math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)), math.gcd(math.complex(53n, 56n), math.complex(47n, -13n)),
math.complex(4n, 5n)) 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