feat: Implement Vector type (#28)
All checks were successful
/ test (push) Successful in 18s
All checks were successful
/ test (push) Successful in 18s
The Vector type is simply a generic type for built-in JavaScript Arrays. The parameter type is the type of all of the entries of the Array. The Vector type also supports inhomogeneous arrays by using the special type `Unknown` as the argument type, but note that when computing with inhomogeneous arrays, method dispatch must be performed separately for every entry in a calculation, making all operations considerably slower than on homogeneous Vector instances. Note also that arithmetic operations on nested Vectors, e.g. `Vector(Vector(NumberT))` are defined so as to interpret such entities as ordinary matrices, represented in row-major format (i.e., the component `Vector(NumberT)` items of such an entity are the _rows_ of the matrix. Reviewed-on: #28 Co-authored-by: Glen Whitney <glen@studioinfinity.org> Co-committed-by: Glen Whitney <glen@studioinfinity.org>
This commit is contained in:
parent
0765ba7202
commit
95d81d0338
47 changed files with 1204 additions and 122 deletions
|
@ -11,7 +11,7 @@ describe('BooleanT Type', () => {
|
|||
})
|
||||
it('autoconverts to number type', () => {
|
||||
assert.strictEqual(math.abs(false), 0)
|
||||
assert.strictEqual(math.absquare(true), 1)
|
||||
assert.strictEqual(math.normsq(true), 1)
|
||||
assert.strictEqual(math.add(true, true), 2)
|
||||
assert.strictEqual(math.divide(false, true), 0)
|
||||
assert.strictEqual(math.cbrt(true), 1)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * as typeDefinition from './BooleanT.js'
|
||||
export * as logical from './logical.js'
|
||||
export * as type from './type.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
9
src/boolean/logical.js
Normal file
9
src/boolean/logical.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import {BooleanT} from './BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const not = match(BooleanT, Returns(BooleanT, p => !p))
|
||||
export const and = match(
|
||||
[BooleanT, BooleanT], Returns(BooleanT, (p, q) => p && q))
|
||||
export const or = match(
|
||||
[BooleanT, BooleanT], Returns(BooleanT, (p, q) => p || q))
|
|
@ -9,10 +9,17 @@ const CplxNum = Complex(NumberT)
|
|||
const {full} = ReturnTyping
|
||||
|
||||
describe('complex arithmetic operations', () => {
|
||||
it('computes absquare of complex numbers', () => {
|
||||
assert.strictEqual(math.absquare(cplx(3, 4)), 25)
|
||||
assert.strictEqual(math.absquare(cplx(cplx(2, 3), cplx(4,5))), 54)
|
||||
assert.strictEqual(math.absquare(cplx(true, true)), 2)
|
||||
it('computes norm-square of complex numbers', () => {
|
||||
assert.strictEqual(math.normsq(cplx(3, 4)), 25)
|
||||
assert.strictEqual(math.normsq(cplx(cplx(2, 3), cplx(4, 5))), 54)
|
||||
assert.strictEqual(math.normsq(cplx(true, true)), 2)
|
||||
})
|
||||
it('computes norm-magnitude of complex numbers', () => {
|
||||
assert.strictEqual(math.norm(cplx(-3, 4)), 5)
|
||||
assert.strictEqual(
|
||||
math.norm(cplx(cplx(2, -3), cplx(-4, 5))), Math.sqrt(54))
|
||||
const boolnorm = math.norm(cplx(true, true))
|
||||
assert(math.equal(boolnorm * boolnorm, 2))
|
||||
})
|
||||
it('adds complex numbers', () => {
|
||||
const z = cplx(3, 4)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Complex} from '../Complex.js'
|
||||
import {OneOf, ReturnTyping} from '#core/Type.js'
|
||||
import {OneOf, ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
|
||||
|
@ -38,13 +38,13 @@ describe('complex type operations', () => {
|
|||
})
|
||||
it('computes cis of an angle', () => {
|
||||
const cis = math.cis.resolve(NumberT)
|
||||
assert.strictEqual(cis.returns, OneOf(Complex(NumberT), NumberT))
|
||||
assert.strictEqual(ReturnType(cis), OneOf(Complex(NumberT), NumberT))
|
||||
assert.strictEqual(cis(0), 1)
|
||||
assert.strictEqual(cis(Math.PI), -1)
|
||||
assert(math.equal(cis(Math.PI/3), cplx(0.5, Math.sqrt(3)/2)))
|
||||
math.config.returnTyping = ReturnTyping.full
|
||||
const ccis = math.cis.resolve(NumberT)
|
||||
assert.strictEqual(ccis.returns, Complex(NumberT))
|
||||
assert.strictEqual(ReturnType(ccis), Complex(NumberT))
|
||||
const one = ccis(0)
|
||||
assert(one !== 1)
|
||||
assert(math.equal(one, 1))
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
const {conservative, full, free} = ReturnTyping
|
||||
|
||||
export const absquare = match(Complex, (math, C, strategy) => {
|
||||
const compAbsq = math.absquare.resolve(C.Component, full)
|
||||
const R = compAbsq.returns
|
||||
export const normsq = match(Complex, (math, C, strategy) => {
|
||||
const compNormsq = math.normsq.resolve(C.Component, full)
|
||||
const R = ReturnType(compNormsq)
|
||||
const add = math.add.resolve([R,R], strategy)
|
||||
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
|
||||
return ReturnsAs(add, z => add(compNormsq(z.re), compNormsq(z.im)))
|
||||
})
|
||||
|
||||
export const add = promoteBinary('add')
|
||||
|
@ -19,31 +19,34 @@ export const add = promoteBinary('add')
|
|||
export const conj = match(Complex, (math, C, strategy) => {
|
||||
const neg = math.negate.resolve(C.Component, full)
|
||||
const compConj = math.conj.resolve(C.Component, full)
|
||||
const cplx = maybeComplex(math, strategy, compConj.returns, neg.returns)
|
||||
const cplx = maybeComplex(
|
||||
math, strategy, ReturnType(compConj), ReturnType(neg))
|
||||
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
|
||||
})
|
||||
|
||||
export const divide = [
|
||||
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
|
||||
const div = math.divide.resolve([C.Component, R], full)
|
||||
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
|
||||
const NewComp = ReturnType(div)
|
||||
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
|
||||
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
|
||||
}),
|
||||
match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const inv = math.invert.resolve(Z, full)
|
||||
const mult = math.multiply.resolve([W, inv.returns], strategy)
|
||||
const mult = math.multiply.resolve([W, ReturnType(inv)], strategy)
|
||||
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
|
||||
})
|
||||
]
|
||||
|
||||
export const invert = match(Complex, (math, C, strategy) => {
|
||||
const conj = math.conj.resolve(C, full)
|
||||
const norm = math.absquare.resolve(C, full)
|
||||
const div = math.divide.resolve([C.Component, norm.returns], full)
|
||||
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
|
||||
const normsq = math.normsq.resolve(C, full)
|
||||
const div = math.divide.resolve([C.Component, ReturnType(normsq)], full)
|
||||
const NewComp = ReturnType(div)
|
||||
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
|
||||
return ReturnsAs(cplx, z => {
|
||||
const c = conj(z)
|
||||
const d = norm(z)
|
||||
const d = normsq(z)
|
||||
return cplx(div(c.re, d), div(c.im, d))
|
||||
})
|
||||
})
|
||||
|
@ -53,7 +56,8 @@ export const invert = match(Complex, (math, C, strategy) => {
|
|||
export const multiply = [
|
||||
match([T => !T.complex, Complex], (math, [R, C], strategy) => {
|
||||
const mult = math.multiply.resolve([R, C.Component], full)
|
||||
const cplx = maybeComplex(math, strategy, mult.returns, mult.returns)
|
||||
const NewComp = ReturnType(mult)
|
||||
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
|
||||
return ReturnsAs(cplx, (r, z) => cplx(mult(r, z.re), mult(r, z.im)))
|
||||
}),
|
||||
match([Complex, T => !T.complex], (math, [C, R], strategy) => {
|
||||
|
@ -62,15 +66,19 @@ export const multiply = [
|
|||
}),
|
||||
match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const conj = math.conj.resolve(W.Component, full)
|
||||
if (conj.returns !== W.Component) {
|
||||
if (ReturnType(conj) !== W.Component) {
|
||||
throw new ResolutionError(
|
||||
`conjugation on ${W.Component} returns type (${conj.returns})`)
|
||||
`conjugation on ${W.Component} returns different type `
|
||||
+ `(${ReturnType(conj)})`)
|
||||
}
|
||||
const mWZ = math.multiply.resolve([W.Component, Z.Component], full)
|
||||
const mZW = math.multiply.resolve([Z.Component, W.Component], full)
|
||||
const sub = math.subtract.resolve([mWZ.returns, mZW.returns], full)
|
||||
const add = math.add.resolve([mWZ.returns, mZW.returns], full)
|
||||
const cplx = maybeComplex(math, strategy, sub.returns, add.returns)
|
||||
const TWZ = ReturnType(mWZ)
|
||||
const TZW = ReturnType(mZW)
|
||||
const sub = math.subtract.resolve([TWZ, TZW], full)
|
||||
const add = math.add.resolve([TWZ, TZW], full)
|
||||
const cplx = maybeComplex(
|
||||
math, strategy, ReturnType(sub), ReturnType(add))
|
||||
return ReturnsAs(cplx, (w, z) => {
|
||||
const real = sub(mWZ( w.re, z.re), mZW(z.im, conj(w.im)))
|
||||
const imag = add(mWZ(conj(w.re), z.im), mZW(z.re, w.im))
|
||||
|
@ -85,7 +93,7 @@ export const negate = promoteUnary('negate')
|
|||
// integer coordinates.
|
||||
export const sqrt = match(Complex, (math, C, strategy) => {
|
||||
const re = math.re.resolve(C)
|
||||
const R = re.returns
|
||||
const R = ReturnType(re)
|
||||
const isReal = math.isReal.resolve(C)
|
||||
// dependencies for the real case:
|
||||
const zComp = math.zero(C.Component)
|
||||
|
@ -98,8 +106,8 @@ export const sqrt = match(Complex, (math, C, strategy) => {
|
|||
const cplx = math.complex.resolve([C.Component, C.Component], full)
|
||||
// additional dependencies for the complex case
|
||||
const abs = math.abs.resolve(C, full)
|
||||
if (abs.returns !== R) {
|
||||
throw new TypeError(`abs on ${C} returns ${abs.returns}, not ${R}`)
|
||||
if (ReturnType(abs) !== R) {
|
||||
throw new TypeError(`abs on ${C} returns ${ReturnType(abs)}, not ${R}`)
|
||||
}
|
||||
const addRR = math.add.resolve([R, R], conservative)
|
||||
const twoR = addRR(oneR, oneR)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
|
@ -8,7 +8,7 @@ const {free, full} = ReturnTyping
|
|||
export const maybeComplex = (math, strategy, Real, Imag) => {
|
||||
if (strategy !== free) return math.complex.resolve([Real, Imag], strategy)
|
||||
const cplx = math.complex.resolve([Real, Imag], full)
|
||||
const prune = math.pruneImaginary.resolve(cplx.returns, full)
|
||||
const prune = math.pruneImaginary.resolve(ReturnType(cplx), full)
|
||||
return ReturnsAs(prune, (r, m) => prune(cplx(r, m)))
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,8 @@ export const promoteUnary = (name, overrideStrategy) => match(
|
|||
(math, C, strategy) => {
|
||||
const compOp = math.resolve(name, C.Component, full)
|
||||
if (overrideStrategy) strategy = overrideStrategy
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
const NewComp = ReturnType(compOp)
|
||||
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
|
||||
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||
})
|
||||
|
||||
|
@ -32,7 +33,8 @@ export const promoteBinary = name => match(
|
|||
[Complex, Complex],
|
||||
(math, [W, Z], strategy) => {
|
||||
const compOp = math.resolve(name, [W.Component, Z.Component], full)
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
const NewComp = ReturnType(compOp)
|
||||
const cplx = maybeComplex(math, strategy, NewComp, NewComp)
|
||||
return ReturnsAs(
|
||||
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
|
||||
})
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {OneOf, Returns, ReturnTyping, TypeOfTypes} from "#core/Type.js"
|
||||
import {
|
||||
OneOf, Returns, ReturnType, ReturnTyping, TypeOfTypes
|
||||
} from "#core/Type.js"
|
||||
import {Any, match} from "#core/TypePatterns.js"
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
@ -32,7 +34,7 @@ export const complex = [
|
|||
export const arg = // [ // enable when we have atan2 in mathjs
|
||||
// match(Complex, (math, C) => {
|
||||
// const re = math.re.resolve(C)
|
||||
// const R = re.returns
|
||||
// const R = ReturnType(re)
|
||||
// const im = math.im.resolve(C)
|
||||
// const abs = math.abs.resolve(C)
|
||||
// const atan2 = math.atan2.resolve([R, R], conservative)
|
||||
|
@ -54,11 +56,11 @@ export const associate = match(
|
|||
}
|
||||
const eq = math.equal.resolve([W, Z], full)
|
||||
const neg = math.negate.resolve(Z, full)
|
||||
const eqN = math.equal.resolve([W, neg.returns], full)
|
||||
const eqN = math.equal.resolve([W, ReturnType(neg)], full)
|
||||
const mult = math.multiply.resolve([Z, Z], full)
|
||||
const eqM = math.equal.resolve([W, mult.returns], full)
|
||||
const negM = math.negate.resolve(mult.returns, full)
|
||||
const eqNM = math.equal.resolve([W, negM.returns], full)
|
||||
const eqM = math.equal.resolve([W, ReturnType(mult)], full)
|
||||
const negM = math.negate.resolve(ReturnType(mult), full)
|
||||
const eqNM = math.equal.resolve([W, ReturnType(negM)], full)
|
||||
const iZ = math.complex(math.zero(Z.Component), math.one(Z.Component))
|
||||
return Returns(BooleanT, (w, z) => {
|
||||
if (eq(w, z) || eqN(w, neg(z))) return true
|
||||
|
|
|
@ -13,12 +13,16 @@ As of this writing, the only two types required to be in a TypeDispatcher are
|
|||
Undefined (the type inhabited only by `undefined`) and TypeOfTypes (the type
|
||||
inhabited exactly by Type objects).
|
||||
|
||||
There is also a constant NotAType which is the type-world analogue of NaN for
|
||||
numbers. It is occasionally used for the rare behavior that truly does not
|
||||
There is also a constant Unknown which is the type that is used when it is
|
||||
not possible in advance to determine what the type of an entity is or will
|
||||
be. It is, for example, used for the occasional behavior that truly does not
|
||||
return any particular type, such as the method `zero` that takes a Type and
|
||||
returns its zero element. However, it does not really work as a Type, and in
|
||||
particular, do _not_ merge it into any TypeDispatcher -- it will disrupt the
|
||||
type and method resolution process.
|
||||
returns its zero element. It is also used for the component type of instances
|
||||
of containers, like Vector, that have inhomogeneous contents -- and therefore,
|
||||
as the return type of `sum` on a `Vector(Unknown)`. However, it lacks much of
|
||||
the machinery of other Type entities. In particular, do _not_ attempt to merge
|
||||
Unknown as a type into any TypeDispatcher -- it will disrupt the type and
|
||||
method resolution process.
|
||||
|
||||
## Core methods
|
||||
|
||||
|
|
|
@ -66,31 +66,43 @@ export const Undefined = new Type(
|
|||
t => typeof t === 'undefined',
|
||||
{zero: undefined, one: undefined, nan: undefined})
|
||||
export const TypeOfTypes = new Type(t => t instanceof Type)
|
||||
export const NotAType = new Type(() => true) // Danger, do not merge!
|
||||
NotAType._doNotMerge = true
|
||||
export const Unknown = new Type(() => true) // Danger, do not merge!
|
||||
Unknown._doNotMerge = true
|
||||
|
||||
const unionDirectory = new ArrayKeyedMap() // make sure only one of each union
|
||||
export const OneOf = (...types) => {
|
||||
if (!types.length) {
|
||||
throw new RangeError('cannot choose OneOf no types at all')
|
||||
}
|
||||
const nonType = types.findIndex(T => !(T instanceof Type))
|
||||
if (nonType >= 0) {
|
||||
throw new RangeError(
|
||||
`OneOf can only take type arguments, not ${types[nonType]}`)
|
||||
}
|
||||
const typeSet = new Set(types) // remove duplicates
|
||||
const typeSet = new Set() // remove duplicates:
|
||||
for (const type of types) {
|
||||
if (type.unifies) {
|
||||
type.unifies.forEach(t => typeSet.add(t))
|
||||
} else typeSet.add(type)
|
||||
}
|
||||
if (typeSet.size === 1) return types[0]
|
||||
const typeList = Array.from(typeSet).sort() // canonical order
|
||||
const generic = typeList.find(T => !T.concrete)
|
||||
if (generic) {
|
||||
throw new RangeError(`OneOf can only take concrete types, not ${generic}`)
|
||||
}
|
||||
if (!unionDirectory.has(typeList)) {
|
||||
unionDirectory.set(typeList, new Type(
|
||||
const unionType = new Type(
|
||||
t => typeList.some(T => T.test(t)),
|
||||
{typeName: typeList.join('|')}))
|
||||
{typeName: typeList.join('|')})
|
||||
unionType.unifies = typeList
|
||||
unionDirectory.set(typeList, unionType)
|
||||
}
|
||||
return unionDirectory.get(typeList)
|
||||
}
|
||||
|
||||
export const Returns = (type, f) => (f.returns = type, f)
|
||||
export const Returns = (type, f) => (f.returns = type, f.isBehavior = true, f)
|
||||
export const ReturnType = f => f.returns ?? Unknown
|
||||
|
||||
export const whichType = typs => Returns(TypeOfTypes, item => {
|
||||
for (const type of Object.values(typs)) {
|
||||
|
|
|
@ -3,7 +3,9 @@ import ArrayKeyedMap from 'array-keyed-map'
|
|||
import {ResolutionError, isPlainFunction, isPlainObject} from './helpers.js'
|
||||
import {Implementations, ImplementationsGenerator} from './Implementations.js'
|
||||
import {bootstrapTypes} from './type.js'
|
||||
import {Returns, ReturnTyping, whichType, Type} from './Type.js'
|
||||
import {
|
||||
Returns, ReturnType, ReturnTyping, Unknown, Type, whichType
|
||||
} from './Type.js'
|
||||
import {
|
||||
matched, needsCollection, Passthru, Matcher, match
|
||||
} from './TypePatterns.js'
|
||||
|
@ -253,7 +255,7 @@ export class TypeDispatcher {
|
|||
if ('actual' in template) { // incorporate conversion
|
||||
let convert = template.convertor
|
||||
// Check if it's a factory:
|
||||
if (!convert.returns) convert = convert(this, template.actual)
|
||||
if (!convert.isBehavior) convert = convert(this, template.actual)
|
||||
extractor = args => convert(args[from])
|
||||
}
|
||||
return state ? extractor : args => [extractor(args)]
|
||||
|
@ -273,6 +275,14 @@ export class TypeDispatcher {
|
|||
throw new ReferenceError(`no method or value for key '${key}'`)
|
||||
}
|
||||
if (!Array.isArray(types)) types = [types]
|
||||
if (types.some(T => T === Unknown)) {
|
||||
if (!strategy) return this[key]
|
||||
const thisTypeOf = whichType(this.types)
|
||||
return (...args) => {
|
||||
const types = args.map(thisTypeOf)
|
||||
return this.resolve(key, types, strategy)(...args)
|
||||
}
|
||||
}
|
||||
if (!strategy) {
|
||||
// Avoid recursing on obtaining config
|
||||
if (key === 'config') strategy = ReturnTyping.free
|
||||
|
@ -309,9 +319,9 @@ export class TypeDispatcher {
|
|||
let needItem = true
|
||||
let item
|
||||
let template
|
||||
let pattern
|
||||
if (imps.length) {
|
||||
for (const options of [{}, {convert: true}]) {
|
||||
let pattern
|
||||
for ([pattern, item] of imps) {
|
||||
let finalIndex
|
||||
;[finalIndex, template] = pattern.match(types, options)
|
||||
|
@ -327,11 +337,18 @@ export class TypeDispatcher {
|
|||
needItem = false
|
||||
item = this._fallbacks[key]
|
||||
template = types
|
||||
pattern = Passthru
|
||||
}
|
||||
if (needItem) {
|
||||
throw new ResolutionError(
|
||||
`no matching definition of '${key}' on '${types}'`)
|
||||
}
|
||||
/* The following message is often helpful in debugging, so left it
|
||||
in but commented out; likely at some point we should have some
|
||||
sort of trace facility that this would be a part of:
|
||||
*/
|
||||
// console.log(`RESOLVING ${key} on ${types} matches ${pattern}`)
|
||||
|
||||
// If this key is producing a non-function value, we're done
|
||||
if (!isPlainFunction(item)) {
|
||||
behave.set(bhvix, item)
|
||||
|
@ -373,12 +390,7 @@ export class TypeDispatcher {
|
|||
|
||||
finalBehavior = theBehavior
|
||||
if (typeof theBehavior === 'function') {
|
||||
const returning = theBehavior.returns
|
||||
if (!returning) {
|
||||
throw new TypeError(
|
||||
`No return type specified for ${key} on ${types} with`
|
||||
+ ` return typing ${ReturnTyping.name(strategy)}`)
|
||||
}
|
||||
const returning = ReturnType(theBehavior)
|
||||
if (needsCollection(template)) {
|
||||
// have to wrap the behavior to collect the actual arguments
|
||||
// in the way corresponding to the template. Generating that
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {Type, Undefined} from './Type.js'
|
||||
import {ReturnType, Type, Undefined, Unknown} from './Type.js'
|
||||
import {isPlainFunction} from './helpers.js'
|
||||
|
||||
export class TypePattern {
|
||||
|
@ -9,6 +9,7 @@ export class TypePattern {
|
|||
throw new Error('Specific TypePatterns must implement sampleTypes')
|
||||
}
|
||||
equal(other) {return other.constructor === this.constructor}
|
||||
toString() {return 'Abstract Pattern (?!)'}
|
||||
}
|
||||
|
||||
class MatchTypePattern extends TypePattern {
|
||||
|
@ -34,6 +35,7 @@ class MatchTypePattern extends TypePattern {
|
|||
}
|
||||
sampleTypes() {return [this.type]}
|
||||
equal(other) {return super.equal(other) && this.type === other.type}
|
||||
toString() {return `Match(${this.type})`}
|
||||
}
|
||||
|
||||
class SequencePattern extends TypePattern {
|
||||
|
@ -51,7 +53,8 @@ class SequencePattern extends TypePattern {
|
|||
const [newPos, newMatch] = pat.match(typeSequence, options)
|
||||
if (newPos < 0) return [-1, Undefined]
|
||||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
if (Array.isArray(newMatch)) matches.push(...newMatch)
|
||||
else matches.push(newMatch)
|
||||
}
|
||||
return [options.position, matches]
|
||||
}
|
||||
|
@ -63,6 +66,7 @@ class SequencePattern extends TypePattern {
|
|||
&& this.patterns.length === other.patterns.length
|
||||
&& this.patterns.every((elt, ix) => elt.equal(other.patterns[ix]))
|
||||
}
|
||||
toString() {return `[${this.patterns}]`}
|
||||
}
|
||||
|
||||
class PredicatePattern extends TypePattern {
|
||||
|
@ -83,6 +87,7 @@ class PredicatePattern extends TypePattern {
|
|||
equal(other) {
|
||||
return super.equal(other) && this.predicate === other.predicate
|
||||
}
|
||||
toString() {return `Test(${this.predicate})`}
|
||||
}
|
||||
|
||||
export const pattern = patternOrSpec => {
|
||||
|
@ -105,6 +110,7 @@ class AnyPattern extends TypePattern {
|
|||
: [-1, Undefined]
|
||||
}
|
||||
sampleTypes() {return [Undefined]}
|
||||
toString() {return 'Any'}
|
||||
}
|
||||
|
||||
export const Any = new AnyPattern()
|
||||
|
@ -125,12 +131,13 @@ class OptionalPattern extends TypePattern {
|
|||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
}
|
||||
return [options.position, matches]
|
||||
return [options.position, [matches]]
|
||||
}
|
||||
sampleTypes() {return []}
|
||||
equal(other) {
|
||||
return super.equal(other) && this.pattern.equal(other.pattern)
|
||||
}
|
||||
toString() {return `?${this.pattern}`}
|
||||
}
|
||||
|
||||
export const Optional = item => new OptionalPattern(item)
|
||||
|
@ -148,7 +155,7 @@ class MultiPattern extends TypePattern {
|
|||
const matches = []
|
||||
while (true) {
|
||||
const [newPos, newMatch] = this.pattern.match(typeSequence, options)
|
||||
if (newPos < 0) return [options.position, matches]
|
||||
if (newPos < 0) return [options.position, [matches]]
|
||||
options.position = newPos
|
||||
matches.push(newMatch)
|
||||
}
|
||||
|
@ -157,6 +164,7 @@ class MultiPattern extends TypePattern {
|
|||
equal(other) {
|
||||
return super.equal(other) && this.pattern.equal(other.pattern)
|
||||
}
|
||||
toString() {return `${this.pattern}*`}
|
||||
}
|
||||
|
||||
export const Multiple = item => new MultiPattern(item)
|
||||
|
@ -169,6 +177,7 @@ class PassthruPattern extends TypePattern {
|
|||
return [typeSequence.length, typeSequence.slice(position)]
|
||||
}
|
||||
sampleTypes() {return []}
|
||||
toString() {return 'Passthru'}
|
||||
}
|
||||
|
||||
export const Passthru = new PassthruPattern()
|
||||
|
@ -180,8 +189,10 @@ export const matched = (template, math) => {
|
|||
}
|
||||
if (template.matched) {
|
||||
let convert = template.convertor
|
||||
if (!convert.returns) convert = convert(math, template.actual)
|
||||
return convert.returns || template.matched
|
||||
if (!convert.isBehavior) convert = convert(math, template.actual)
|
||||
const ConvertsTo = ReturnType(convert)
|
||||
if (ConvertsTo !== Unknown) return ConvertsTo
|
||||
return template.matched
|
||||
}
|
||||
return template
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import assert from 'assert'
|
|||
import math from '#nanomath'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
import {Returns, ReturnTyping} from '../Type.js'
|
||||
import {Returns, ReturnType, ReturnTyping} from '../Type.js'
|
||||
import {isPlainFunction} from '../helpers.js'
|
||||
|
||||
describe('Core types', () => {
|
||||
|
@ -36,7 +36,7 @@ describe('Core types', () => {
|
|||
it('provides a return-value labeling', () => {
|
||||
const labeledF = Returns(math.types.Undefined, () => undefined)
|
||||
assert.strictEqual(typeof labeledF, 'function')
|
||||
assert.strictEqual(labeledF.returns, math.types.Undefined)
|
||||
assert.strictEqual(ReturnType(labeledF), math.types.Undefined)
|
||||
assert(isPlainFunction(labeledF))
|
||||
})
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as numbers from '#number/all.js'
|
|||
import {NumberT} from '#number/NumberT.js'
|
||||
import {ResolutionError} from "#core/helpers.js"
|
||||
import {match, Any} from "#core/TypePatterns.js"
|
||||
import {NotAType, Returns, ReturnTyping} from "#core/Type.js"
|
||||
import {Returns, ReturnType, ReturnTyping, Unknown} from "#core/Type.js"
|
||||
import {plain} from "#number/helpers.js"
|
||||
|
||||
describe('TypeDispatcher', () => {
|
||||
|
@ -18,7 +18,7 @@ describe('TypeDispatcher', () => {
|
|||
const {NumberT, TypeOfTypes, Undefined} = incremental.types
|
||||
assert(NumberT.test(7))
|
||||
assert.strictEqual(incremental.add(-1.5, 0.5), -1)
|
||||
assert.throws(() => incremental.add(7, undefined), ResolutionError)
|
||||
assert.throws(() => incremental.add(7, NumberT), ResolutionError)
|
||||
// Make Undefined act like zero:
|
||||
incremental.merge({add: [
|
||||
match([Undefined, Any], (_m, [_U, T]) => Returns(T, (_a, b) => b)),
|
||||
|
@ -26,11 +26,11 @@ describe('TypeDispatcher', () => {
|
|||
]})
|
||||
assert.strictEqual(incremental.add(7, undefined), 7)
|
||||
assert.strictEqual(
|
||||
incremental.resolve('add', [TypeOfTypes, Undefined]).returns,
|
||||
ReturnType(incremental.resolve('add', [TypeOfTypes, Undefined])),
|
||||
TypeOfTypes)
|
||||
assert.strictEqual(incremental.add(undefined, -3.25), -3.25)
|
||||
assert.strictEqual(
|
||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||
ReturnType(incremental.add.resolve([Undefined, NumberT])),
|
||||
NumberT)
|
||||
// Oops, changed my mind ;-), make it work like NaN with numbers:
|
||||
const alwaysNaN = Returns(NumberT, () => NaN)
|
||||
|
@ -40,7 +40,7 @@ describe('TypeDispatcher', () => {
|
|||
]})
|
||||
assert(isNaN(incremental.add(undefined, -3.25)))
|
||||
assert.strictEqual(
|
||||
incremental.add.resolve([Undefined, NumberT]).returns,
|
||||
ReturnType(incremental.add.resolve([Undefined, NumberT])),
|
||||
NumberT)
|
||||
assert.strictEqual(incremental.isnan(NaN), 1)
|
||||
incremental.merge(booleans)
|
||||
|
@ -68,9 +68,9 @@ describe('TypeDispatcher', () => {
|
|||
assert(!bgn._behaviors.negate.has([ReturnTyping.free, BooleanT]))
|
||||
assert.strictEqual(bgn.negate(true), -2)
|
||||
})
|
||||
it('disallows merging NotAType', () => {
|
||||
it('disallows merging Unknown', () => {
|
||||
const doomed = new TypeDispatcher()
|
||||
assert.throws(() => doomed.merge({NaT: NotAType}), TypeError)
|
||||
assert.throws(() => doomed.merge({Unknown}), TypeError)
|
||||
})
|
||||
it('supports generic types', () => {
|
||||
assert.throws(() => NumberT(0), TypeError)
|
||||
|
|
|
@ -53,6 +53,10 @@ describe('Type patterns', () => {
|
|||
midOpt.match([Undefined, TypeOfTypes, TypeOfTypes]),
|
||||
[-1, Undefined])
|
||||
assert.deepStrictEqual(midOpt.sampleTypes(), [Undefined, Undefined])
|
||||
const justMulti = pattern(Multiple(Undefined))
|
||||
assert.deepStrictEqual(
|
||||
justMulti.match([Undefined, Undefined]),
|
||||
[2, [[Undefined, Undefined]]])
|
||||
const midMulti = pattern([Undefined, Multiple(TypeOfTypes), Undefined])
|
||||
assert.deepStrictEqual(
|
||||
midMulti.match([Undefined, Undefined]),
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './arithmetic.js'
|
||||
export * from './relational.js'
|
||||
export * from './utils.js'
|
||||
|
|
6
src/coretypes/arithmetic.js
Normal file
6
src/coretypes/arithmetic.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {Returns, Undefined} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
export const isnan = match(Undefined, boolnum(() => true))
|
||||
export const negate = match(Undefined, Returns(Undefined, () => undefined))
|
|
@ -1,8 +1,14 @@
|
|||
import {TypeOfTypes, Undefined} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {Any, Multiple, match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
export const indistinguishable = [
|
||||
match([Undefined, Undefined], boolnum(() => true)),
|
||||
match([TypeOfTypes, TypeOfTypes], boolnum((t, u) => t === u))
|
||||
// I don't think there's any other type that should be indistinguishable
|
||||
// from undefined:
|
||||
match([Undefined, Any, Multiple(Any)], boolnum(() => false)),
|
||||
match([Any, Undefined, Multiple(Any)], boolnum(() => false)),
|
||||
match([Undefined, Undefined, Multiple(Any)], boolnum(() => true)),
|
||||
match(
|
||||
[TypeOfTypes, TypeOfTypes, Multiple(Any)],
|
||||
boolnum((t, u) => t === u))
|
||||
]
|
||||
|
|
|
@ -1,15 +1,23 @@
|
|||
import {NotAType, Returns, TypeOfTypes} from '#core/Type.js'
|
||||
import {Returns, TypeOfTypes, Unknown} from '#core/Type.js'
|
||||
import {match,Any} from "#core/TypePatterns.js"
|
||||
import {boolnum} from "#number/helpers.js"
|
||||
|
||||
export const haszero = [
|
||||
match(Any, (math, T) => {
|
||||
const answer = math.haszero(T)
|
||||
return Returns(math.typeOf(answer), () => answer)
|
||||
}),
|
||||
match(TypeOfTypes, boolnum(T => 'zero' in T))
|
||||
]
|
||||
|
||||
export const zero = [
|
||||
match(Any, (math, T) => {
|
||||
const z = math.zero(T)
|
||||
return Returns(T, () => z)
|
||||
}),
|
||||
match(TypeOfTypes, Returns(NotAType, t => {
|
||||
if ('zero' in t) return t.zero
|
||||
throw new RangeError(`type '${t}' has no zero element`)
|
||||
match(TypeOfTypes, Returns(Unknown, T => {
|
||||
if ('zero' in T) return T.zero
|
||||
throw new RangeError(`type '${T}' has no zero element`)
|
||||
}))
|
||||
]
|
||||
|
||||
|
@ -18,10 +26,10 @@ export const one = [
|
|||
const unit = math.one(T)
|
||||
return Returns(T, () => unit)
|
||||
}),
|
||||
match(TypeOfTypes, Returns(NotAType, t => {
|
||||
if ('one' in t) return t.one
|
||||
match(TypeOfTypes, Returns(Unknown, T => {
|
||||
if ('one' in T) return T.one
|
||||
throw new RangeError(
|
||||
`type '${t}' has no unit element designated as "one"`)
|
||||
`type '${T}' has no unit element designated as "one"`)
|
||||
}))
|
||||
]
|
||||
|
||||
|
@ -30,7 +38,7 @@ export const hasnan = [
|
|||
const answer = math.hasnan(T)
|
||||
return Returns(math.typeOf(answer), () => answer)
|
||||
}),
|
||||
match(TypeOfTypes, boolnum(t => 'nan' in t))
|
||||
match(TypeOfTypes, boolnum(T => 'nan' in T))
|
||||
]
|
||||
|
||||
export const nan = [
|
||||
|
@ -38,9 +46,9 @@ export const nan = [
|
|||
const notanum = math.nan(T)
|
||||
return Returns(T, () => notanum)
|
||||
}),
|
||||
match(TypeOfTypes, Returns(NotAType, t => {
|
||||
if ('nan' in t) return t.nan
|
||||
match(TypeOfTypes, Returns(Unknown, T => {
|
||||
if ('nan' in T) return T.nan
|
||||
throw new RangeError(
|
||||
`type '${t}' has no "not a number" element`)
|
||||
`type '${T}' has no "not a number" element`)
|
||||
}))
|
||||
]
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
|
||||
const {Complex, NumberT} = math.types
|
||||
|
||||
|
@ -8,7 +8,7 @@ describe('generic arithmetic', () => {
|
|||
it('squares anything', () => {
|
||||
const sq = math.square
|
||||
assert.strictEqual(sq(7), 49)
|
||||
assert.strictEqual(math.square.resolve([NumberT]).returns, NumberT)
|
||||
assert.strictEqual(ReturnType(math.square.resolve([NumberT])), NumberT)
|
||||
assert.deepStrictEqual(sq(math.complex(3, 4)), math.complex(-7, 24))
|
||||
const eyes = math.complex(0, 2)
|
||||
assert.strictEqual(sq(eyes), -4)
|
||||
|
|
|
@ -48,7 +48,7 @@ describe('generic relational functions', () => {
|
|||
assert.throws(() => compare(false, NaN), TypeError)
|
||||
assert(isNaN(compare(NaN, NaN)))
|
||||
assert.throws(() => compare(true, false), TypeError)
|
||||
assert.throws(() => compare(undefined, -1), ResolutionError)
|
||||
assert.throws(() => compare(math.types.BooleanT, -1), ResolutionError)
|
||||
})
|
||||
it('determines the sign of numeric values', () => {
|
||||
const {sign} = math
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * as arithmetic from './arithmetic.js'
|
||||
export * as logical from './logical.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
|
||||
import {Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
import {match, Any} from '#core/TypePatterns.js'
|
||||
|
||||
export const abs = match(Any, (math, T) => {
|
||||
const absq = math.absquare.resolve(T)
|
||||
const sqrt = math.sqrt.resolve(absq.returns, ReturnTyping.conservative)
|
||||
return ReturnsAs(sqrt, t => sqrt(absq(t)))
|
||||
export const norm = match(Any, (math, T) => {
|
||||
const normsq = math.normsq.resolve(T)
|
||||
const sqrt = math.sqrt.resolve(
|
||||
ReturnType(normsq), ReturnTyping.conservative)
|
||||
return ReturnsAs(sqrt, t => sqrt(normsq(t)))
|
||||
})
|
||||
|
||||
export const abs = norm // coincide for most types (scalars)
|
||||
|
||||
export const conj = match(Any, (_math, T) => Returns(T, t => t))
|
||||
|
||||
export const square = match(Any, (math, T, strategy) => {
|
||||
const mult = math.multiply.resolve([T, T], strategy)
|
||||
return Returns(mult.returns, t => mult(t, t))
|
||||
return ReturnsAs(mult, t => mult(t, t))
|
||||
})
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
export const ReturnsAs = (g, f) => (f.returns = g.returns, f)
|
||||
import {Returns, ReturnType} from '#core/Type.js'
|
||||
|
||||
export const ReturnsAs = (g, f) => Returns(ReturnType(g), f)
|
||||
|
|
40
src/generic/logical.js
Normal file
40
src/generic/logical.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
|
||||
import {OneOf, Returns, ReturnType} from '#core/Type.js'
|
||||
import {Any, Multiple, match} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
export const not = match(Any, (math, T) => {
|
||||
const bool = math.boolean.resolve(T)
|
||||
return ReturnsAs(bool, t => !bool(t))
|
||||
})
|
||||
|
||||
export const and = [
|
||||
match([], boolnum(() => true)),
|
||||
match(Any, (_math, T) => Returns(T, t => t)),
|
||||
match([Any, Any], (math, [T, U]) => {
|
||||
const bool = math.boolean.resolve(T)
|
||||
return Returns(OneOf(T, U), (t, u) => bool(t) ? u : t)
|
||||
}),
|
||||
match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => {
|
||||
const andRest = math.and.resolve([U, V, ...Rest])
|
||||
const andFirst = math.and.resolve([T, ReturnType(andRest)])
|
||||
return ReturnsAs(
|
||||
andFirst, (t, u, v, rest) => andFirst(t, andRest(u, v, ...rest)))
|
||||
})
|
||||
]
|
||||
|
||||
export const or = [
|
||||
match([], boolnum(() => false)),
|
||||
match(Any, (_math, T) => Returns(T, t => t)),
|
||||
match([Any, Any], (math, [T, U]) => {
|
||||
const bool = math.boolean.resolve(T)
|
||||
return Returns(OneOf(T, U), (t, u) => bool(t) ? t : u)
|
||||
}),
|
||||
match([Any, Any, Any, Multiple(Any)], (math, [T, U, V, Rest]) => {
|
||||
const orRest = math.or.resolve([U, V, ...Rest])
|
||||
const orFirst = math.and.resolve([T, ReturnType(orRest)])
|
||||
return ReturnsAs(
|
||||
orFirst, (t, u, v, rest) => orFirst(t, orRest(u, v, ...rest)))
|
||||
})
|
||||
]
|
|
@ -1,5 +1,5 @@
|
|||
import {ReturnsAs} from './helpers.js'
|
||||
import {Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {Returns, ReturnType, ReturnTyping} from '#core/Type.js'
|
||||
import {Any, Passthru, match, matched} from '#core/TypePatterns.js'
|
||||
import {boolnum} from '#number/helpers.js'
|
||||
|
||||
|
@ -39,6 +39,9 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
|||
// now that we have `equal` and `exceeds`, pretty much everything else should
|
||||
// be easy:
|
||||
|
||||
export const deepEqual = match(
|
||||
Passthru, (math, types, strategy) => math.equal.resolve(types, strategy))
|
||||
|
||||
export const compare = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const gt = math.exceeds.resolve([T, U])
|
||||
|
@ -79,35 +82,42 @@ export const sign = match(Any, (math, T) => {
|
|||
|
||||
export const unequal = match(Passthru, (math, types) => {
|
||||
const eq = math.equal.resolve(types)
|
||||
return ReturnsAs(eq, (...args) => !eq(...args))
|
||||
const not = math.not.resolve(ReturnType(eq))
|
||||
return ReturnsAs(not, (...args) => not(eq(...args)))
|
||||
})
|
||||
|
||||
export const larger = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const bigger = math.exceeds.resolve([T, U])
|
||||
return boolnum((t, u) => !eq(t, u) && bigger(t, u))(math)
|
||||
const not = math.not.resolve(ReturnType(eq))
|
||||
const and = math.and.resolve([ReturnType(not), ReturnType(bigger)])
|
||||
return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(t, u)))
|
||||
})
|
||||
|
||||
export const isPositive = match(Any, (math, T) => {
|
||||
const zero = math.zero(T)
|
||||
const larger = math.larger.resolve([T, T])
|
||||
return boolnum(t => larger(t, zero))
|
||||
return ReturnsAs(larger, t => larger(t, zero))
|
||||
})
|
||||
|
||||
export const largerEq = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const bigger = math.exceeds.resolve([T, U])
|
||||
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(t, u))
|
||||
const or = math.or.resolve([ReturnType(eq), ReturnType(bigger)])
|
||||
return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(t, u)))
|
||||
})
|
||||
|
||||
export const smaller = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const bigger = math.exceeds.resolve([U, T])
|
||||
return boolnum((t, u) => !eq(t, u) && bigger(u, t))(math)
|
||||
const not = math.not.resolve(ReturnType(eq))
|
||||
const and = math.and.resolve([ReturnType(not), ReturnType(bigger)])
|
||||
return ReturnsAs(and, (t, u) => and(not(eq(t, u)), bigger(u, t)))
|
||||
})
|
||||
|
||||
export const smallerEq = match([Any, Any], (math, [T, U]) => {
|
||||
const eq = math.equal.resolve([T, U])
|
||||
const bigger = math.exceeds.resolve([U, T])
|
||||
return ReturnsAs(bigger, (t, u) => eq(t, u) || bigger(u, t))
|
||||
const or = math.or.resolve([ReturnType(eq), ReturnType(bigger)])
|
||||
return ReturnsAs(or, (t, u) => or(eq(t, u), bigger(u, t)))
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ export const isZero = match(Passthru, (math, [T]) => {
|
|||
const eq = math.equal.resolve([T, T])
|
||||
return ReturnsAs(eq, x => eq(z, x))
|
||||
})
|
||||
export const nonImaginary = match(Passthru, boolnum(() => true))
|
||||
export const nonImaginary = match(Any, boolnum(() => true))
|
||||
export const re = match(Any, (_math, T) => Returns(T, t => t))
|
||||
export const im = match(Any, (math, T) => {
|
||||
const z = math.zero(T)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import * as booleans from './boolean/all.js'
|
||||
import * as coretypes from './coretypes/all.js'
|
||||
import * as booleans from './boolean/all.js'
|
||||
import * as complex from './complex/all.js'
|
||||
import * as generics from './generic/all.js'
|
||||
import * as numbers from './number/all.js'
|
||||
import * as complex from './complex/all.js'
|
||||
import * as vectors from './vector/all.js'
|
||||
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
||||
|
||||
// At the moment, since we are not sorting patterns in any way,
|
||||
|
@ -12,9 +13,10 @@ import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
|||
// whatever has been merged before.)
|
||||
// Hence, in building the math instance, we put generics first because
|
||||
// they should only kick in when there are not specific implementations,
|
||||
// and complex next becausewe want its conversion (which converts _any_
|
||||
// and complex next because we want its conversion (which converts _any_
|
||||
// non-complex type to complex, potentially making a poor overload choice)
|
||||
// to be tried last.
|
||||
const math = new TypeDispatcher(generics, complex, booleans, coretypes, numbers)
|
||||
const math = new TypeDispatcher(
|
||||
generics, complex, vectors, booleans, coretypes, numbers)
|
||||
|
||||
export default math
|
||||
|
|
|
@ -5,7 +5,8 @@ import {ReturnTyping} from '#core/Type.js'
|
|||
describe('number arithmetic', () => {
|
||||
it('supports basic operations', () => {
|
||||
assert.strictEqual(math.abs(-Infinity), Infinity)
|
||||
assert.strictEqual(math.absquare(-2), 4)
|
||||
assert(isNaN(math.norm(NaN)))
|
||||
assert.strictEqual(math.normsq(-2), 4)
|
||||
assert.strictEqual(math.add(2, 2), 4)
|
||||
assert.strictEqual(math.divide(6, 4), 1.5)
|
||||
assert.strictEqual(math.cbrt(-8), -2)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * as typeDefinition from './NumberT.js'
|
||||
export * as arithmetic from './arithmetic.js'
|
||||
export * as logical from './logical.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as type from './type.js'
|
||||
export * as utils from './utils.js'
|
||||
|
|
|
@ -1,14 +1,19 @@
|
|||
import {plain} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
import {OneOf, Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {OneOf, Returns, ReturnTyping, Undefined} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {Complex} from '#complex/Complex.js'
|
||||
|
||||
const {conservative, full} = ReturnTyping
|
||||
|
||||
export const abs = plain(Math.abs)
|
||||
export const absquare = plain(a => a*a)
|
||||
export const add = plain((a, b) => a + b)
|
||||
export const norm = abs
|
||||
export const normsq = plain(a => a*a)
|
||||
export const add = [
|
||||
plain((a, b) => a + b),
|
||||
match([Undefined, NumberT], Returns(NumberT, () => NaN)),
|
||||
match([NumberT, Undefined], Returns(NumberT, () => NaN))
|
||||
]
|
||||
export const divide = plain((a, b) => a / b)
|
||||
export const cbrt = plain(a => {
|
||||
if (a === 0) return a
|
||||
|
@ -22,7 +27,11 @@ export const cbrt = plain(a => {
|
|||
return negate ? -result : result
|
||||
})
|
||||
export const invert = plain(a => 1/a)
|
||||
export const multiply = plain((a, b) => a * b)
|
||||
export const multiply = [
|
||||
plain((a, b) => a * b),
|
||||
match([Undefined, NumberT], Returns(NumberT, () => NaN)),
|
||||
match([NumberT, Undefined], Returns(NumberT, () => NaN))
|
||||
]
|
||||
export const negate = plain(a => -a)
|
||||
|
||||
export const sqrt = match(NumberT, (math, _N, strategy) => {
|
||||
|
@ -44,5 +53,10 @@ export const sqrt = match(NumberT, (math, _N, strategy) => {
|
|||
})
|
||||
})
|
||||
|
||||
export const subtract = plain((a, b) => a - b)
|
||||
export const subtract = [
|
||||
plain((a, b) => a - b),
|
||||
match([Undefined, NumberT], Returns(NumberT, () => NaN)),
|
||||
match([NumberT, Undefined], Returns(NumberT, () => NaN))
|
||||
]
|
||||
|
||||
export const quotient = plain((a,b) => Math.floor(a/b))
|
||||
|
|
6
src/number/logical.js
Normal file
6
src/number/logical.js
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
|
||||
export const not = match(NumberT, boolnum(a => !a))
|
|
@ -1,7 +1,9 @@
|
|||
import {match, Optional} from '#core/TypePatterns.js'
|
||||
import {boolnum} from './helpers.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {Undefined} from '#core/Type.js'
|
||||
import {match, Optional} from '#core/TypePatterns.js'
|
||||
|
||||
// In nanomath, we take the point of view that two comparators are primitive:
|
||||
// indistinguishable(a, b, relTol, absTol), and exceeds(a, b). All others
|
||||
// are defined generically in terms of these. They typically return BooleanT,
|
||||
|
@ -32,4 +34,9 @@ export const indistinguishable = match(
|
|||
// Returns truthy if a (interpreted as completely precise) represents a
|
||||
// greater value than b (interpreted as completely precise). Note that even if
|
||||
// so, a and b might be indistinguishable() to some tolerances.
|
||||
export const exceeds = match([NumberT, NumberT], boolnum((a, b) => a > b))
|
||||
export const exceeds = [
|
||||
match([NumberT, NumberT], boolnum((a, b) => a > b)),
|
||||
// Needed to allow comparison of vectors of different lengths:
|
||||
match([Undefined, NumberT], boolnum(() => false)),
|
||||
match([NumberT, Undefined], boolnum(() => false))
|
||||
]
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
import {plain} from './helpers.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {Returns} from '#core/Type.js'
|
||||
import {NumberT} from './NumberT.js'
|
||||
|
||||
import {Returns, ReturnType} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {Complex} from '#complex/Complex.js'
|
||||
|
||||
const num = f => Returns(NumberT, f)
|
||||
|
||||
|
@ -10,5 +12,17 @@ export const number = [
|
|||
plain(a => a),
|
||||
// conversions from Boolean should be consistent with one and zero:
|
||||
match(BooleanT, num(p => p ? NumberT.one : NumberT.zero)),
|
||||
match([], num(() => 0))
|
||||
match([], num(() => 0)),
|
||||
match(Complex, (math, C) => {
|
||||
const im = math.im.resolve(C)
|
||||
const re = math.re.resolve(C)
|
||||
const compNum = math.number.resolve(ReturnType(re))
|
||||
const isZ = math.isZero.resolve(ReturnType(im))
|
||||
return num(z => {
|
||||
if (!isZ(im(z))) {
|
||||
throw new RangeError(`can't convert Complex ${z} to number`)
|
||||
}
|
||||
return compNum(re(z))
|
||||
})
|
||||
})
|
||||
]
|
||||
|
|
48
src/vector/Vector.js
Normal file
48
src/vector/Vector.js
Normal file
|
@ -0,0 +1,48 @@
|
|||
import {Type, Unknown} from '#core/Type.js'
|
||||
|
||||
const isVector = v => Array.isArray(v)
|
||||
|
||||
export const Vector = new Type(isVector, {
|
||||
specialize(CompType) {
|
||||
const compTest = CompType.test
|
||||
const specTest = v => isVector(v) && v.every(compTest)
|
||||
const typeName = `Vector(${CompType})`
|
||||
if (CompType.concrete) {
|
||||
const typeOptions = {typeName}
|
||||
if ('zero' in CompType) typeOptions.zero = [CompType.zero]
|
||||
const vectorCompType = new Type(specTest, typeOptions)
|
||||
vectorCompType.Component = CompType
|
||||
vectorCompType.vectorDepth = (CompType.vectorDepth ?? 0) + 1
|
||||
return vectorCompType
|
||||
}
|
||||
// Wrapping a generic type in Vector
|
||||
return new Type(specTest, {
|
||||
typeName,
|
||||
specialize: (...args) => this.specialize(CompType.specialize(...args)),
|
||||
specializesTo: VT => this.specializesTo(VT)
|
||||
&& CompType.specializesTo(VT.Component),
|
||||
refine: (v, typer) => {
|
||||
if (!v.length) {
|
||||
throw new RangeError(
|
||||
`no way to refine ${typeName} on an empty vector`)
|
||||
}
|
||||
const eltTypes = v.map(elt => CompType.refine(elt, typer))
|
||||
const newCompType = eltTypes[0]
|
||||
if (eltTypes.some(T => T !== newCompType)) {
|
||||
throw new TypeError(
|
||||
`can't refine ${typeName} to ${v}; `
|
||||
+ `not all entries have type ${newCompType}`)
|
||||
}
|
||||
return this.specialize(newCompType)
|
||||
}
|
||||
})
|
||||
},
|
||||
specializesTo: VT => 'vectorDepth' in VT && VT.vectorDepth > 0,
|
||||
refine(v, typer) {
|
||||
if (!v.length) return this.specialize(Unknown) // what else could we do?
|
||||
const eltTypes = v.map(elt => typer(elt))
|
||||
let compType = eltTypes[0]
|
||||
if (eltTypes.some(T => T !== compType)) compType = Unknown
|
||||
return this.specialize(compType)
|
||||
}
|
||||
})
|
35
src/vector/__test__/Vector.spec.js
Normal file
35
src/vector/__test__/Vector.spec.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
import assert from 'assert'
|
||||
import {Vector} from '../Vector.js'
|
||||
import {Unknown} from '#core/Type.js'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector Type', () => {
|
||||
it('correctly recognizes vectors', () => {
|
||||
assert(Vector.test([3, 4]))
|
||||
assert(!Vector.test({re: 3, im: 4}))
|
||||
})
|
||||
it('can fully type vectors', () => {
|
||||
const {BooleanT, Complex, NumberT} = math.types
|
||||
const typ = math.typeOf
|
||||
const cplx = math.complex
|
||||
assert.strictEqual(typ([3, 4]), Vector(NumberT))
|
||||
assert.strictEqual(typ([true, false]), Vector(BooleanT))
|
||||
assert.strictEqual(typ([3, false]), Vector(Unknown))
|
||||
assert.strictEqual(typ([cplx(3, 4), cplx(5)]), Vector(Complex(NumberT)))
|
||||
assert.strictEqual(typ([[3, 4], [5]]), Vector(Vector(NumberT)))
|
||||
})
|
||||
it('can refine when nested', () => {
|
||||
const {Complex, NumberT} = math.types
|
||||
const cplx = math.complex
|
||||
const VecComplex = Vector(Complex)
|
||||
const vcEx = [cplx(3, 4), cplx(5)]
|
||||
assert(VecComplex.test(vcEx))
|
||||
const vcType = VecComplex.refine(vcEx, math.typeOf)
|
||||
assert.strictEqual(vcType, Vector(Complex(NumberT)))
|
||||
const CplxVec = Complex(Vector)
|
||||
const cvEx = cplx([3,4], [5, 0])
|
||||
assert(CplxVec.test(cvEx))
|
||||
const cvType = CplxVec.refine(cvEx, math.typeOf)
|
||||
assert.strictEqual(cvType, Complex(Vector(NumberT)))
|
||||
})
|
||||
})
|
82
src/vector/__test__/arithmetic.spec.js
Normal file
82
src/vector/__test__/arithmetic.spec.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector arithmetic functions', () => {
|
||||
const cplx = math.complex
|
||||
const cV = [cplx(3, 4), cplx(4, -3), cplx(-3, -4)]
|
||||
it('distributes abs elementwise', () => {
|
||||
const abs = math.abs
|
||||
assert.deepStrictEqual(abs([-3, 4, -5]), [3, 4, 5])
|
||||
assert.deepStrictEqual(abs(cV), [5, 5, 5])
|
||||
assert.deepStrictEqual(abs([true, -4, cplx(4, 3)]), [1, 4, 5])
|
||||
})
|
||||
it('computes the norm of a vector or matrix', () => {
|
||||
const norm = math.norm
|
||||
assert.strictEqual(norm([-3, 4, -5]), Math.sqrt(50))
|
||||
assert.strictEqual(norm([[1, 2], [4, 2]]), 5)
|
||||
assert.strictEqual(norm(cV), Math.sqrt(75))
|
||||
})
|
||||
it('adds vectors and matrices', () => {
|
||||
const add = math.add
|
||||
assert.deepStrictEqual(add([-3, 4, -5], [8, 0, 2]), [5, 4, -3])
|
||||
assert.deepStrictEqual(
|
||||
add([[1, 2], [4, 2]], [[0, -1], [3, -4]]), [[1, 1], [7, -2]])
|
||||
assert.deepStrictEqual(
|
||||
add([[1, 2], [4, 2]], [0, -1]), [[1, 1], [4, 1]])
|
||||
})
|
||||
it('(pseudo)inverts matrices', () => {
|
||||
const inv = math.invert
|
||||
// inverses
|
||||
assert.deepStrictEqual(inv([3, 4, 5]), [3/50, 2/25, 1/10])
|
||||
assert.deepStrictEqual(inv([[4]]), [[0.25]])
|
||||
assert.deepStrictEqual(inv([[5, 2], [-7, -3]]), [[3, 2], [-7, -5]])
|
||||
assert(math.equal(
|
||||
inv([[3, 0, 2], [2, 1, 0], [1, 4, 2]]),
|
||||
[[1/10, 2/5, -1/10], [-1/5, 1/5, 1/5], [7/20, -3/5, 3/20]]))
|
||||
// pseudoinverses
|
||||
assert.deepStrictEqual(inv([[1, 0], [1, 0]]), [[1/2, 1/2], [0, 0]])
|
||||
assert.deepStrictEqual(
|
||||
inv([[1, 0], [0, 1], [0, 1]]), [[1, 0, 0], [0, 1/2, 1/2]])
|
||||
assert.deepStrictEqual(
|
||||
inv([[1, 0, 0, 0, 2],
|
||||
[0, 0, 3, 0, 0],
|
||||
[0, 0, 0, 0, 0],
|
||||
[0, 4, 0, 0, 0]]),
|
||||
[[1/5, 0, 0, 0],
|
||||
[ 0, 0, 0, 1/4],
|
||||
[ 0, 1/3, 0, 0],
|
||||
[ 0, 0, 0, 0],
|
||||
[2/5, 0, 0, 0]])
|
||||
})
|
||||
it('multiplies vectors and matrices', () => {
|
||||
const mult = math.multiply
|
||||
const pyth = [3, 4, 5]
|
||||
assert.deepStrictEqual(mult(pyth, 2), [6, 8, 10])
|
||||
assert.deepStrictEqual(mult(-3, pyth), [-9, -12, -15])
|
||||
assert.strictEqual(mult(pyth, pyth), 50)
|
||||
const mat23 = [[1, 2, 3], [-3, -2, -1]]
|
||||
assert.deepStrictEqual(mult(mat23, pyth), [26, -22])
|
||||
const mat32 = math.transpose(mat23)
|
||||
assert.deepStrictEqual(mult(pyth, mat32), [26, -22])
|
||||
assert.deepStrictEqual(mult(mat23, mat32), [[14, -10], [-10, 14]])
|
||||
assert.deepStrictEqual(
|
||||
mult(mat32, [[1, 2], [3, 4]]),
|
||||
[[-8, -10], [-4, -4], [0, 2]])
|
||||
assert(math.equal(
|
||||
mult([[3, 0, 2], [2, 1, 0], [1, 4, 2]],
|
||||
[[1/10, 2/5, -1/10], [-1/5, 1/5, 1/5], [7/20, -3/5, 3/20]]),
|
||||
[[1, 0, 0], [0, 1, 0], [0, 0, 1]]))
|
||||
})
|
||||
it('negates a vector', () => {
|
||||
assert.deepStrictEqual(math.negate([-3, 4, -5]), [3, -4, 5])
|
||||
})
|
||||
it('subtracts vectors', () => {
|
||||
assert.deepStrictEqual(math.subtract([-3, 4, -5], [8, 0, 2]), [-11, 4, -7])
|
||||
})
|
||||
it('computes the sum of a vector', () => {
|
||||
const sum = math.sum
|
||||
assert.strictEqual(sum([-3, 4, -5]), -4)
|
||||
assert.deepStrictEqual(sum(cV), cplx(4, -3))
|
||||
assert.strictEqual(sum([4, true, -2]), 3)
|
||||
})
|
||||
})
|
47
src/vector/__test__/relational.spec.js
Normal file
47
src/vector/__test__/relational.spec.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
const t = true
|
||||
const f = false
|
||||
describe('Vector relational functions', () => {
|
||||
it('can test two vectors for deep equality', () => {
|
||||
const dEq = math.deepEqual
|
||||
const pyth = [3, 4, 5]
|
||||
assert.strictEqual(dEq(pyth, [3, 4, 5]), t)
|
||||
assert.strictEqual(dEq(pyth, [3, 4, 6]), f)
|
||||
assert.strictEqual(dEq(pyth, [3, 4, [5]]), f)
|
||||
assert.strictEqual(dEq(3, 3 + 1e-13), t)
|
||||
assert.strictEqual(dEq(pyth, [3+1e-13, 4-1e-13, 5]), t)
|
||||
assert.strictEqual(dEq([pyth, pyth], [pyth, [3, 4, 5]]), t)
|
||||
assert.strictEqual(dEq([3], 3), f)
|
||||
})
|
||||
it('performs equal elementwise', () => {
|
||||
const eq = math.equal
|
||||
const pyth = [3, 4, 5]
|
||||
assert.deepStrictEqual(eq(pyth, 4), [f, t, f])
|
||||
assert.deepStrictEqual(eq(5, pyth), [f, f, t])
|
||||
assert.deepStrictEqual(eq(pyth, pyth), [t, t, t])
|
||||
assert.deepStrictEqual(eq(pyth, [3]), [t, f, f])
|
||||
assert.deepStrictEqual(eq([4 + 1e-14], pyth), [f, t, f])
|
||||
assert.deepStrictEqual(eq(pyth, [3, 4]), [t, t, f])
|
||||
assert.deepStrictEqual(eq([3, 2], pyth), [t, f, f])
|
||||
assert.deepStrictEqual(eq([pyth, pyth], [3, 4]), [[t, f, f], [f, t, f]])
|
||||
assert.deepStrictEqual(
|
||||
eq([pyth, pyth], [[5], pyth]), [[f, f, t], [t, t, t]])
|
||||
assert.deepStrictEqual(
|
||||
eq([[1, 2], [3, 4]], [[1, 3], [2, 4]]), [[t, f], [f, t]])
|
||||
})
|
||||
it('performs order comparisons elementwise', () => {
|
||||
const pyth = [3, 4, 5]
|
||||
const ans = [-1, 0, 1]
|
||||
const powers = [2, 4, 8]
|
||||
assert.deepStrictEqual(math.compare(pyth, [5, 4, 3]), ans)
|
||||
assert.deepStrictEqual(math.sign([-Infinity, 0, 3]), ans)
|
||||
assert.deepStrictEqual(math.unequal(pyth, [5, 4 + 1e-14, 5]), [t, f, f])
|
||||
assert.deepStrictEqual(math.larger(pyth, powers), [t, f, f])
|
||||
assert.deepStrictEqual(math.isPositive(ans), [f, f, t])
|
||||
assert.deepStrictEqual(math.largerEq(pyth, powers), [t, t, f])
|
||||
assert.deepStrictEqual(math.smaller(pyth, powers), [f, f, t])
|
||||
assert.deepStrictEqual(math.smallerEq(pyth, powers), [f, t, t])
|
||||
})
|
||||
})
|
45
src/vector/__test__/type.spec.js
Normal file
45
src/vector/__test__/type.spec.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import assert from 'assert'
|
||||
import {ReturnType, Unknown} from '#core/Type.js'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector type functions', () => {
|
||||
it('can construct a vector', () => {
|
||||
const vec = math.vector
|
||||
const {BooleanT, NumberT, Vector} = math.types
|
||||
assert.deepStrictEqual(vec(3, 4, 5), [3, 4, 5])
|
||||
assert.strictEqual(
|
||||
ReturnType(vec.resolve([NumberT, NumberT, NumberT])),
|
||||
Vector(NumberT))
|
||||
assert.deepStrictEqual(vec(3, true), [3, true])
|
||||
assert.strictEqual(
|
||||
ReturnType(vec.resolve([NumberT, BooleanT])),
|
||||
Vector(Unknown))
|
||||
})
|
||||
it('can transpose vectors and matrices', () => {
|
||||
const tsp = math.transpose
|
||||
assert.deepStrictEqual(tsp([3, 4, 5]), [[3], [4], [5]])
|
||||
assert.deepStrictEqual(tsp([[1, 2], [3, 4]]), [[1, 3], [2, 4]])
|
||||
assert.deepStrictEqual(
|
||||
tsp([[1, 2, 3], [4, 5, 6]]), [[1, 4], [2, 5], [3, 6]])
|
||||
})
|
||||
it('can take adjoint (conjugate transpose) of a matrix', () => {
|
||||
const cx = math.complex
|
||||
assert.deepStrictEqual(
|
||||
math.adjoint([[cx(1, 1), cx(2, 2)], [cx(3, 3), cx(4, 4)]]),
|
||||
[[cx(1, -1), cx(3, -3)], [cx(2, -2), cx(4, -4)]])
|
||||
})
|
||||
it('generates identity from an example matrix or a number of rows', () => {
|
||||
const id = math.identity
|
||||
const cx = math.complex
|
||||
assert.deepStrictEqual(id(2), [[1, 0], [0, 1]])
|
||||
assert.deepStrictEqual(id(cx(2)), [[cx(1), cx(0)], [cx(0), cx(1)]])
|
||||
assert.deepStrictEqual(
|
||||
id([[1, 2, 3]]),
|
||||
[[1, 0 , 0], [0, 1, 0], [0, 0, 1]])
|
||||
})
|
||||
it('takes the determinant of a matrix', () => {
|
||||
assert.strictEqual(
|
||||
math.determinant([[6, 1, 1], [4, -2, 5], [2, 8, 7]]),
|
||||
-306)
|
||||
})
|
||||
})
|
41
src/vector/__test__/utils.spec.js
Normal file
41
src/vector/__test__/utils.spec.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector utility functions', () => {
|
||||
it('can clone a vector', () => {
|
||||
const subj1 = [3, 4]
|
||||
const clone1 = math.clone(subj1)
|
||||
assert.deepStrictEqual(clone1, subj1)
|
||||
assert(clone1 !== subj1)
|
||||
const subj2 = [3, false]
|
||||
const clone2 = math.clone(subj2)
|
||||
assert.deepStrictEqual(clone2, subj2)
|
||||
assert(clone2 !== subj2)
|
||||
const subj3 = [[3, 4], [2, 5]]
|
||||
const clone3 = math.clone(subj3)
|
||||
assert.deepStrictEqual(clone3, subj3)
|
||||
assert(clone3 !== subj3)
|
||||
assert(clone3[0] !== subj3[0])
|
||||
assert(clone3[1] !== subj3[1])
|
||||
})
|
||||
it('performs utilities elementwise', () => {
|
||||
const cplx = math.complex
|
||||
const sink = math.vector(
|
||||
NaN, -Infinity, 7,
|
||||
cplx(3, 4), cplx(3.5, -2.5), cplx(2.2), cplx(cplx(3, 4))
|
||||
)
|
||||
const t = true, f = false
|
||||
assert.deepStrictEqual(math.isnan(sink), [t, f, f, f, f, f, f])
|
||||
assert.deepStrictEqual(math.isfinite(sink), [f, f, t, t, t, t, t])
|
||||
assert.deepStrictEqual(math.isInteger(sink), [f, f, t, t, f, f, t])
|
||||
assert.deepStrictEqual(math.isReal(sink), [t, t, t, f, f, t, f])
|
||||
assert.deepStrictEqual(math.nonImaginary(sink), [t, t, t, f, f, t, t])
|
||||
assert.deepStrictEqual(math.re(sink), [NaN, -Infinity, 7, 3, 3.5, 2.2, 3])
|
||||
assert.deepStrictEqual(
|
||||
math.im(sink),
|
||||
[0, 0, 0, cplx(0, 4), cplx(0, -2.5), cplx(0, 0), cplx(cplx(0, 4))])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
7
src/vector/all.js
Normal file
7
src/vector/all.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
export * as typeDefinition from './Vector.js'
|
||||
export * as arithmetic from './arithmetic.js'
|
||||
export * as logical from './logical.js'
|
||||
export * as relational from './relational.js'
|
||||
export * as type from './type.js'
|
||||
export * as utilities from './utils.js'
|
||||
|
266
src/vector/arithmetic.js
Normal file
266
src/vector/arithmetic.js
Normal file
|
@ -0,0 +1,266 @@
|
|||
import {
|
||||
distributeFirst, distributeSecond, promoteBinary, promoteUnary
|
||||
} from './helpers.js'
|
||||
import {Vector} from './Vector.js'
|
||||
|
||||
import {Returns, ReturnType} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const normsq = match(Vector, (math, V) => {
|
||||
const compNormsq = math.normsq.resolve(V.Component)
|
||||
const sum = math.sum.resolve(Vector(ReturnType(compNormsq)))
|
||||
return ReturnsAs(sum, v => sum(v.map(compNormsq)))
|
||||
})
|
||||
// abs and norm differ only on Vector (and perhaps other collections) --
|
||||
// norm computes overall by the generic formula, whereas abs distributes
|
||||
// elementwise:
|
||||
export const abs = promoteUnary('abs')
|
||||
export const add = promoteBinary('add')
|
||||
|
||||
export const dotMultiply = promoteBinary('multiply')
|
||||
export const invert = match(Vector, (math, V, strategy) => {
|
||||
if (V.vectorDepth > 2) {
|
||||
throw new TypeError(
|
||||
'invert not implemented for arrays of dimension > 2')
|
||||
}
|
||||
const normsq = math.normsq.resolve(V, strategy)
|
||||
const NormT = ReturnType(normsq)
|
||||
const zNorm = math.zero(NormT)
|
||||
const isRealZ = math.isZero.resolve(NormT, strategy)
|
||||
if (V.vectorDepth === 1) {
|
||||
const invNorm = math.invert.resolve(NormT, strategy)
|
||||
const scalarMult = math.multiply.resolve(
|
||||
[V, ReturnType(invNorm)], strategy)
|
||||
return ReturnsAs(scalarMult, v => {
|
||||
const nsq = normsq(v)
|
||||
if (isRealZ(nsq)) return Array(v.length).fill(zNorm)
|
||||
return scalarMult(v, invNorm(nsq))
|
||||
})
|
||||
}
|
||||
// usual matrix situation, want to find a matrix whose product with v
|
||||
// is the identity, or as close as we can get to that if the rank is
|
||||
// deficient. We use the Moore-Penrose pseudoinverse.
|
||||
const clone = math.clone.resolve(V, strategy)
|
||||
const VComp = V.Component
|
||||
const Elt = VComp.Component
|
||||
const invElt = math.invert.resolve(Elt, strategy)
|
||||
const det = math.determinant.resolve(V, strategy)
|
||||
const neg = math.negate.resolve(Elt, strategy)
|
||||
const multMM = math.multiply.resolve([V, V], strategy)
|
||||
const multMS = math.multiply.resolve([V, ReturnType(invElt)], strategy)
|
||||
const multVS = math.multiply.resolve([VComp, ReturnType(invElt)], strategy)
|
||||
const multSS = math.multiply.resolve([Elt, Elt], strategy)
|
||||
const sub = math.subtract.resolve([Elt, Elt], strategy)
|
||||
const id = math.identity.resolve(V, strategy)
|
||||
const abs = math.abs.resolve(Elt, strategy)
|
||||
const gt = math.larger.resolve([NormT, NormT], strategy)
|
||||
const isEltZ = math.isZero.resolve(Elt, strategy)
|
||||
const zElt = math.zero(Elt)
|
||||
const adj = math.adjoint.resolve(V, strategy)
|
||||
const methods = {
|
||||
invElt, det, isRealZ, neg, multMM, multMS, multVS, multSS,
|
||||
id, abs, isEltZ, zElt, gt, sub, adj, clone
|
||||
}
|
||||
if (ReturnType(abs) !== NormT) {
|
||||
throw new TypeError('type inconsistency in matrix invert')
|
||||
}
|
||||
return Returns(V, m => {
|
||||
const rows = m.length
|
||||
const cols = m[0].length
|
||||
const nsq = normsq(m)
|
||||
if (isRealZ(nsq)) { // all-zero matrix
|
||||
const retval = []
|
||||
for (let ix = 0; ix < cols; ++ix) {
|
||||
retval.push(Array(rows).fill(zNorm))
|
||||
}
|
||||
return retval
|
||||
}
|
||||
if (rows == cols) {
|
||||
// the inv helper will return falsy if not invertible
|
||||
const retval = inv(m, rows, methods)
|
||||
if (retval) return retval
|
||||
}
|
||||
|
||||
return pinv(m, rows, cols, methods)
|
||||
})
|
||||
})
|
||||
|
||||
// Returns the inverse of a rows×rows matrix or false if not invertible
|
||||
// Note: destroys m in the inversion process.
|
||||
function inv(
|
||||
origm,
|
||||
rows,
|
||||
{
|
||||
invElt, det, isRealZ, neg, multMS, multVS, multSS,
|
||||
id, abs, isEltZ, zElt, gt, sub, clone
|
||||
}
|
||||
) {
|
||||
switch (rows) {
|
||||
case 1: return [[invElt(origm[0][0])]]
|
||||
case 2: {
|
||||
const dt = det(origm)
|
||||
if (isRealZ(dt)) return false
|
||||
const divisor = invElt(dt)
|
||||
const [[a, b], [c, d]] = origm
|
||||
return multMS([[d, neg(b)], [neg(c), a]], divisor)
|
||||
}
|
||||
default: { // Gauss-Jordan elimination
|
||||
const m = clone(origm)
|
||||
const B = id(m)
|
||||
const cols = rows
|
||||
// Loop over columns, performing row reductions:
|
||||
for (let c = 0; c < cols; ++c) {
|
||||
// Pivot: Find row r that has the largest entry in column c, and
|
||||
// swap row c and r:
|
||||
let colMax = abs(m[c][c])
|
||||
let rMax = c
|
||||
for (let r = c + 1; r < rows; ++r) {
|
||||
const mag = abs(m[r][c])
|
||||
if (gt(mag, colMax)) {
|
||||
colMax = mag
|
||||
rMax = r
|
||||
}
|
||||
}
|
||||
if (isRealZ(colMax)) return false
|
||||
if (rMax !== c) {
|
||||
[m[c], m[rMax], B[c], B[rMax]]
|
||||
= [m[rMax], m[c], B[rMax], B[c]]
|
||||
}
|
||||
// Normalize the cth row:
|
||||
const normalizer = invElt(m[c][c])
|
||||
const mc = multVS(m[c], normalizer)
|
||||
m[c] = mc
|
||||
const Bc = multVS(B[c], normalizer)
|
||||
B[c] = Bc
|
||||
|
||||
// Eliminate nonzero values on other rows at column c
|
||||
for (let r = 0; r < rows; ++r) {
|
||||
if (r === c) continue
|
||||
const mr = m[r]
|
||||
const Br = B[r]
|
||||
const mrc = mr[c]
|
||||
if (!isEltZ(mr[c])) {
|
||||
// Subtract Arc times row c from row r to eliminate A[r][c]
|
||||
mr[c] = zElt
|
||||
for (let s = c + 1; s < cols; ++s) {
|
||||
mr[s] = sub(mr[s], multSS(mrc, mc[s]))
|
||||
}
|
||||
for (let s = 0; s < cols; ++s) {
|
||||
Br[s] = sub(Br[s], multSS(mrc, Bc[s]))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return B
|
||||
}}
|
||||
}
|
||||
|
||||
// Calculates Moore-Penrose pseudoinverse
|
||||
// uses rank factorization per mathjs; SVD appears to be considered better
|
||||
// but not worth the effort to implement for this prototype
|
||||
function pinv(m, rows, cols, methods) {
|
||||
const {C, F} = rankFactorization(m, rows, cols, methods)
|
||||
const {multMM, adj} = methods
|
||||
const Cstar = adj(C)
|
||||
const Cpinv = multMM(inv(multMM(Cstar, C), Cstar.length, methods), Cstar)
|
||||
const Fstar = adj(F)
|
||||
const Fpinv = multMM(Fstar, inv(multMM(F, Fstar), F.length, methods))
|
||||
return multMM(Fpinv, Cpinv)
|
||||
}
|
||||
|
||||
// warning: destroys m in computing the row-reduced echelon form
|
||||
// TODO: this code should be merged with inv to the extent possible. It's
|
||||
// a very similar process.
|
||||
function rref(origm, rows, cols, methods) {
|
||||
const {isEltZ, invElt, multVS, zElt, sub, multSS, clone} = methods
|
||||
const m = clone(origm)
|
||||
let lead = -1
|
||||
for (let r = 0; r < rows && ++lead < cols; ++r) {
|
||||
if (cols <= lead) return m
|
||||
let i = r
|
||||
while (isEltZ(m[i][lead])) {
|
||||
if (++i === rows) {
|
||||
i = r
|
||||
if (++lead === cols) return m
|
||||
}
|
||||
}
|
||||
|
||||
if (i !== r) [m[i], m[r]] = [m[r], m[i]]
|
||||
|
||||
let normalizer = invElt(m[r][lead])
|
||||
const mr = multVS(m[r], normalizer)
|
||||
m[r] = mr
|
||||
for (let i = 0; i < rows; ++i) {
|
||||
if (i === r) continue
|
||||
const mi = m[i]
|
||||
const toRemove = mi[lead]
|
||||
mi[lead] = zElt
|
||||
for (let j = lead + 1; j < cols; ++j) {
|
||||
mi[j] = sub(mi[j], multSS(toRemove, mr[j]))
|
||||
}
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
function rankFactorization(m, rows, cols, methods) {
|
||||
const RREF = rref(m, rows, cols, methods)
|
||||
const {isEltZ} = methods
|
||||
const rankRows = RREF.map(row => row.some(elt => !isEltZ(elt)))
|
||||
const C = m.map(row => row.filter((_, j) => j < rows && rankRows[j]))
|
||||
const F = RREF.filter((_, i) => rankRows[i])
|
||||
return {C, F}
|
||||
}
|
||||
|
||||
export const multiply = [
|
||||
distributeFirst('multiply'),
|
||||
distributeSecond('multiply'),
|
||||
match([Vector, Vector], (math, [V, W], strategy) => {
|
||||
const VComp = V.Component
|
||||
if (W.vectorDepth === 1) {
|
||||
if (V.vectorDepth === 1) {
|
||||
const eltWise = math.dotMultiply.resolve([V, W], strategy)
|
||||
const sum = math.sum.resolve(ReturnType(eltWise))
|
||||
return ReturnsAs(sum, (v, w) => sum(eltWise(v, w)))
|
||||
}
|
||||
const compMult = math.multiply.resolve([VComp, W], strategy)
|
||||
return ReturnsAs(
|
||||
Vector(ReturnType(compMult)),
|
||||
(v, w) => v.map(f => compMult(f, w)))
|
||||
}
|
||||
const transpose = math.transpose.resolve(W, strategy)
|
||||
const wrapV = V.vectorDepth === 1
|
||||
const RowV = wrapV ? V : VComp
|
||||
const rowMult = math.multiply.resolve([RowV, W.Component], strategy)
|
||||
let RetType = Vector(ReturnType(rowMult))
|
||||
if (!wrapV) RetType = Vector(RetType)
|
||||
return ReturnsAs(RetType, (v, w) => {
|
||||
if (wrapV) v = [v]
|
||||
w = transpose(w)
|
||||
let retval = v.map(vrow => w.map(wcol => rowMult(vrow, wcol)))
|
||||
if (wrapV) retval = retval[0]
|
||||
return retval
|
||||
})
|
||||
})
|
||||
]
|
||||
|
||||
export const negate = promoteUnary('negate')
|
||||
export const subtract = promoteBinary('subtract')
|
||||
|
||||
export const sum = match(Vector, (math, V) => {
|
||||
const add = math.add.resolve([V.Component, V.Component])
|
||||
const haszero = math.haszero(V.Component)
|
||||
const zero = haszero ? math.zero(V.Component) : undefined
|
||||
return ReturnsAs(add, v => {
|
||||
if (v.length === 0) {
|
||||
if (haszero) return zero
|
||||
throw new TypeError(`Can't sum empty ${V}: no zero element`)
|
||||
}
|
||||
let ix = 0
|
||||
let retval = v[ix]
|
||||
while (++ix < v.length) retval = add(retval, v[ix])
|
||||
return retval
|
||||
})
|
||||
})
|
70
src/vector/helpers.js
Normal file
70
src/vector/helpers.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {Returns, ReturnType, Undefined} from '#core/Type.js'
|
||||
import {Any, match} from '#core/TypePatterns.js'
|
||||
|
||||
export const promoteUnary = name => match(Vector, (math, V, strategy) => {
|
||||
const compOp = math.resolve(name, V.Component, strategy)
|
||||
return Returns(Vector(ReturnType(compOp)), v => v.map(elt => compOp(elt)))
|
||||
})
|
||||
|
||||
export const distributeFirst = name => match(
|
||||
[Vector, Any],
|
||||
(math, [V, E], strategy) => {
|
||||
const compOp = math.resolve(name, [V.Component, E], strategy)
|
||||
return Returns(
|
||||
Vector(ReturnType(compOp)), (v, e) => v.map(f => compOp(f, e)))
|
||||
})
|
||||
|
||||
export const distributeSecond = name => match(
|
||||
[Any, Vector],
|
||||
(math, [E, V], strategy) => {
|
||||
const compOp = math.resolve(name, [E, V.Component], strategy)
|
||||
return Returns(
|
||||
Vector(ReturnType(compOp)), (e, v) => v.map(f => compOp(e, f)))
|
||||
})
|
||||
|
||||
export const promoteBinary = name => [
|
||||
distributeFirst(name),
|
||||
distributeSecond(name),
|
||||
match([Vector, Vector], (math, [V, W], strategy) => {
|
||||
const VComp = V.Component
|
||||
const WComp = W.Component
|
||||
// special case: if the vector nesting depths do not match,
|
||||
// we operate between the elements of the deeper one and the entire
|
||||
// more shallow one:
|
||||
if (V.vectorDepth > W.vectorDepth) {
|
||||
const compOp = math.resolve(name, [VComp, W], strategy)
|
||||
return Returns(
|
||||
Vector(ReturnType(compOp)), (v, w) => v.map(f => compOp(f, w)))
|
||||
}
|
||||
if (V.vectorDepth < W.vectorDepth) {
|
||||
const compOp = math.resolve(name, [V, WComp], strategy)
|
||||
return Returns(
|
||||
Vector(ReturnType(compOp)), (v, w) => w.map(f => compOp(v, f)))
|
||||
}
|
||||
const compOp = math.resolve(name, [VComp, WComp], strategy)
|
||||
const opNoV = math.resolve(name, [Undefined, WComp], strategy)
|
||||
const opNoW = math.resolve(name, [VComp, Undefined], strategy)
|
||||
return Returns(
|
||||
Vector(ReturnType(compOp)),
|
||||
(v, w) => {
|
||||
const vInc = Number(v.length > 1)
|
||||
const wInc = Number(w.length >= v.length || w.length > 1)
|
||||
const retval = []
|
||||
let vIx = 0
|
||||
let wIx = 0
|
||||
while ((vInc && vIx < v.length)
|
||||
|| (wInc && wIx < w.length)
|
||||
) {
|
||||
if (vIx >= v.length) {
|
||||
retval.push(opNoV(undefined, w[wIx]))
|
||||
} else if (wIx >= w.length) {
|
||||
retval.push(opNoW(v[vIx], undefined))
|
||||
} else retval.push(compOp(v[vIx], w[wIx]))
|
||||
vIx += vInc
|
||||
wIx += wInc
|
||||
}
|
||||
return retval
|
||||
})
|
||||
})
|
||||
]
|
7
src/vector/logical.js
Normal file
7
src/vector/logical.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||
|
||||
export const not = promoteUnary('not')
|
||||
export const and = promoteBinary('and')
|
||||
export const or = promoteBinary('or')
|
||||
|
||||
|
95
src/vector/relational.js
Normal file
95
src/vector/relational.js
Normal file
|
@ -0,0 +1,95 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {promoteBinary} from './helpers.js'
|
||||
|
||||
import {Unknown, Returns, ReturnType, Undefined} from '#core/Type.js'
|
||||
import {Any, Optional, match} from '#core/TypePatterns.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
|
||||
export const deepEqual = [
|
||||
match([Vector, Any], Returns(BooleanT, () => false)),
|
||||
match([Any, Vector], Returns(BooleanT, () => false)),
|
||||
match([Vector, Vector], (math, [V, W]) => {
|
||||
const compDeep = math.deepEqual.resolve([V.Component, W.Component])
|
||||
return Returns(BooleanT, (v,w) => v === w
|
||||
|| (v.length === w.length
|
||||
&& v.every((e, i) => compDeep(e, w[i]))))
|
||||
})
|
||||
]
|
||||
|
||||
export const indistinguishable = [
|
||||
match([Vector, Any, Optional([Any, Any])], (math, [V, E, T]) => {
|
||||
const VComp = V.Component
|
||||
if (T.length === 0) { // no tolerances
|
||||
const same = math.indistinguishable.resolve([VComp, E])
|
||||
return Returns(
|
||||
Vector(ReturnType(same)), (v, e) => v.map(f => same(f, e)))
|
||||
}
|
||||
const [[RT, AT]] = T
|
||||
const same = math.indistinguishable.resolve([VComp, E, RT, AT])
|
||||
return Returns(
|
||||
Vector(ReturnType(same)),
|
||||
(v, e, [[rT, aT]]) => v.map(f => same(f, e, rT, aT)))
|
||||
}),
|
||||
match([Any, Vector, Optional([Any, Any])], (math, [E, V, T]) => {
|
||||
// reimplement to get other order in same so as not to assume
|
||||
// same is symmetric, even though it probably is
|
||||
const VComp = V.Component
|
||||
if (T.length === 0) { // no tolerances
|
||||
const same = math.indistinguishable.resolve([E, VComp])
|
||||
return Returns(
|
||||
Vector(ReturnType(same)), (e, v) => v.map(f => same(e, f)))
|
||||
}
|
||||
const [[RT, AT]] = T
|
||||
const same = math.indistinguishable.resolve([E, VComp, RT, AT])
|
||||
return Returns(
|
||||
Vector(ReturnType(same)),
|
||||
(e, v, [[rT, aT]]) => v.map(f => same(e, f, rT, aT)))
|
||||
}),
|
||||
match([Vector, Vector, Optional([Any, Any])], (math, [V, W, T]) => {
|
||||
const VComp = V.Component
|
||||
const WComp = W.Component
|
||||
const inhomogeneous = VComp === Unknown || WComp === Unknown
|
||||
let same
|
||||
let sameNoV
|
||||
let sameNoW
|
||||
if (inhomogeneous) {
|
||||
same = math.indistinguishable
|
||||
sameNoV = same
|
||||
sameNoW = same
|
||||
} else if (T.length === 0) { // no tolerances
|
||||
same = math.indistinguishable.resolve([VComp, WComp])
|
||||
sameNoV = math.indistinguishable.resolve([Undefined, WComp])
|
||||
sameNoW = math.indistinguishable.resolve([VComp, Undefined])
|
||||
} else {
|
||||
const [[RT, AT]] = T
|
||||
same = math.indistinguishable.resolve([VComp, WComp, RT, AT])
|
||||
sameNoV = math.indistinguishable.resolve([Undefined, WComp, RT, AT])
|
||||
sameNoW = math.indistinguishable.resolve([VComp, Undefined, RT, AT])
|
||||
}
|
||||
return Returns(
|
||||
inhomogeneous ? Vector(Unknown) : Vector(ReturnType(same)),
|
||||
(v, w, [tol = [0, 0]]) => {
|
||||
const [rT, aT] = tol
|
||||
const vInc = Number(v.length > 1)
|
||||
const wInc = Number(w.length >= v.length || w.length > 1)
|
||||
const retval = []
|
||||
let vIx = 0
|
||||
let wIx = 0
|
||||
while ((vInc && vIx < v.length)
|
||||
|| (wInc && wIx < w.length)
|
||||
) {
|
||||
if (vIx >= v.length) {
|
||||
retval.push(sameNoV(undefined, w[wIx], rT, aT))
|
||||
} else if (wIx >= w.length) {
|
||||
retval.push(sameNoW(v[vIx], undefined, rT, aT))
|
||||
} else retval.push(same(v[vIx], w[wIx], rT, aT))
|
||||
vIx += vInc
|
||||
wIx += wInc
|
||||
}
|
||||
return retval
|
||||
})
|
||||
})
|
||||
]
|
||||
|
||||
export const compare = promoteBinary('compare')
|
||||
export const exceeds = promoteBinary('exceeds')
|
133
src/vector/type.js
Normal file
133
src/vector/type.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {OneOf, Returns, ReturnType, Unknown} from '#core/Type.js'
|
||||
import {Any, Multiple, match} from '#core/TypePatterns.js'
|
||||
|
||||
export const vector = match(Multiple(Any), (math, [TV]) => {
|
||||
if (!TV.length) return Returns(Vector(Unknown), () => [])
|
||||
let CompType = TV[0]
|
||||
if (TV.some(T => T !== CompType)) CompType = Unknown
|
||||
return Returns(Vector(CompType), v => v)
|
||||
})
|
||||
|
||||
export const determinant = match(Vector(Vector), (math, M, strategy) => {
|
||||
const Elt = M.Component.Component
|
||||
const cloneElt = math.clone.resolve(Elt, strategy)
|
||||
const mult = math.multiply.resolve([Elt, Elt], strategy)
|
||||
const sub = math.subtract.resolve(
|
||||
[ReturnType(mult), ReturnType(mult)], strategy)
|
||||
const isZ = math.isZero.resolve(Elt, strategy)
|
||||
const zElt = math.zero(Elt)
|
||||
const clone = math.clone.resolve(M, strategy)
|
||||
const div = math.divide.resolve([ReturnType(sub), Elt], strategy)
|
||||
const neg = math.negate.resolve(Elt, strategy)
|
||||
return Returns(OneOf(ReturnType(clone), ReturnType(sub), Elt), origm => {
|
||||
const rows = origm.length
|
||||
switch (rows) {
|
||||
case 1: return cloneElt(origm[0][0])
|
||||
case 2: {
|
||||
const [[a, b], [c, d]] = origm
|
||||
return sub(mult(a, d), mult(b, c))
|
||||
}
|
||||
default: { // Bareiss algorithm
|
||||
const m = clone(origm)
|
||||
let negated = false
|
||||
const rowIndices = [...Array(rows).keys()] // track row indices
|
||||
// because the algorithm may swap rows
|
||||
for (let k = 0; k < rows; ++k) {
|
||||
let k_ = rowIndices[k]
|
||||
if (isZ(m[k_][k])) {
|
||||
let _k = k
|
||||
while (++_k < rows) {
|
||||
if (!isZ(m[rowIndices[_k]][k])) {
|
||||
k_ = rowIndices[_k]
|
||||
rowIndices[_k] = rowIndices[k]
|
||||
rowIndices[k] = k_
|
||||
negated = !negated
|
||||
break
|
||||
}
|
||||
}
|
||||
if (_k === rows) return zElt
|
||||
}
|
||||
const piv = m[k_][k] // we now know nonzero
|
||||
const piv_ = k === 0 ? 1 : m[rowIndices[k-1]][k-1]
|
||||
for (let i = k + 1; i < rows; ++i) {
|
||||
const i_ = rowIndices[i]
|
||||
for (let j = k + 1; j < rows; ++j) {
|
||||
m[i_][j] = div(
|
||||
sub(mult(m[i_][j], piv), mult(m[i_][k], m[k_][j])),
|
||||
piv_)
|
||||
}
|
||||
}
|
||||
}
|
||||
const det = m[rowIndices[rows - 1]][rows - 1]
|
||||
return negated ? neg(det) : det
|
||||
}}
|
||||
})
|
||||
})
|
||||
|
||||
function identitizer(cols, zero, one) {
|
||||
const retval = []
|
||||
for (let ix = 0; ix < cols; ++ix) {
|
||||
const row = Array(cols).fill(zero)
|
||||
row[ix] = one
|
||||
retval.push(row)
|
||||
}
|
||||
return retval
|
||||
}
|
||||
|
||||
export const identity = [
|
||||
match(Any, (math, V) => {
|
||||
const toNum = math.number.resolve(V)
|
||||
const zero = math.zero(V)
|
||||
const one = math.one(V)
|
||||
return Returns(Vector(Vector(V)), n => identitizer(toNum(n), zero, one))
|
||||
}),
|
||||
match(Vector, (math, V) => {
|
||||
switch (V.vectorDepth) {
|
||||
case 1: {
|
||||
const Elt = V.Component
|
||||
const one = math.one(Elt)
|
||||
return Returns(math.typeOf(one), () => one)
|
||||
}
|
||||
case 2: {
|
||||
const Elt = V.Component.Component
|
||||
const zero = math.zero(Elt)
|
||||
const one = math.one(Elt)
|
||||
return Returns(V, m => identitizer(
|
||||
m.length ? m[0].length : 0, zero, one))
|
||||
}
|
||||
default:
|
||||
throw new RangeError(
|
||||
`'identity' not implemented on ${V.vectorDepth} dimensional arrays`)
|
||||
}
|
||||
})
|
||||
]
|
||||
|
||||
// transposes a 2D matrix
|
||||
function transposer(wrap, eltFun) {
|
||||
return v => {
|
||||
if (wrap) v = [v]
|
||||
const cols = v.length ? v[0].length : 0
|
||||
const retval = []
|
||||
for (let ix = 0; ix < cols; ++ix) {
|
||||
retval.push(v.map(row => eltFun(row[ix])))
|
||||
}
|
||||
return retval
|
||||
}
|
||||
}
|
||||
|
||||
export const transpose = match(Vector, (_math, V) => {
|
||||
const wrapV = V.vectorDepth === 1
|
||||
const Mat = wrapV ? Vector(V) : V
|
||||
return Returns(Mat, transposer(wrapV, elt => elt))
|
||||
})
|
||||
|
||||
// or with conjugation:
|
||||
export const adjoint = match(Vector, (math, V, strategy) => {
|
||||
const wrapV = V.vectorDepth === 1
|
||||
const VComp = V.Component
|
||||
const Elt = wrapV ? VComp : VComp.Component
|
||||
const conj = math.conj.resolve(Elt, strategy)
|
||||
const Mat = Vector(Vector(ReturnType(conj)))
|
||||
return Returns(Mat, transposer(wrapV, conj))
|
||||
})
|
10
src/vector/utils.js
Normal file
10
src/vector/utils.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import {promoteUnary} from './helpers.js'
|
||||
|
||||
export const clone = promoteUnary('clone')
|
||||
export const isnan = promoteUnary('isnan')
|
||||
export const isfinite = promoteUnary('isfinite')
|
||||
export const isInteger = promoteUnary('isInteger')
|
||||
export const isReal = promoteUnary('isReal')
|
||||
export const nonImaginary = promoteUnary('nonImaginary')
|
||||
export const re = promoteUnary('re')
|
||||
export const im = promoteUnary('im')
|
Loading…
Add table
Add a link
Reference in a new issue