feat: Start adding vector arithmetic
So far, abs, add, norm, normsq, and sum are supported. To get them to work, also implements the following: * refactor: Use ReturnType function rather than just accessing .returns * feat: distinguish marking a function as a behavior from its return type * refactor: Rename `NotAType` to `Unknown` because it must be made closer to a bona fide type for the sake of inhomogeneous vectors * feat: make resolving a TypeDispatcher method on a type vector including `Unknown` into a no-op; that simplifies a number of generic behaviors * feat: add `haszero` method parallel to `hasnan` * feat: track the Vector nesting depth of Vector specializations
This commit is contained in:
parent
ec97b0e20a
commit
cb3a93dd1c
26 changed files with 238 additions and 171 deletions
|
@ -1,4 +1,4 @@
|
|||
import {NotAType, Type} from '#core/Type.js'
|
||||
import {Type, Unknown} from '#core/Type.js'
|
||||
|
||||
const isVector = v => Array.isArray(v)
|
||||
|
||||
|
@ -12,7 +12,7 @@ export const Vector = new Type(isVector, {
|
|||
if ('zero' in CompType) typeOptions.zero = [CompType.zero]
|
||||
const vectorCompType = new Type(specTest, typeOptions)
|
||||
vectorCompType.Component = CompType
|
||||
vectorCompType.vector = true
|
||||
vectorCompType.vectorDepth = (CompType.vectorDepth ?? 0) + 1
|
||||
return vectorCompType
|
||||
}
|
||||
// Wrapping a generic type in Vector
|
||||
|
@ -37,12 +37,12 @@ export const Vector = new Type(isVector, {
|
|||
}
|
||||
})
|
||||
},
|
||||
specializesTo: VT => VT.vector,
|
||||
specializesTo: VT => 'vectorDepth' in VT && VT.vectorDepth > 0,
|
||||
refine(v, typer) {
|
||||
if (!v.length) return this.specialize(NotAType) // what else could we do?
|
||||
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 = NotAType
|
||||
if (eltTypes.some(T => T !== compType)) compType = Unknown
|
||||
return this.specialize(compType)
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import assert from 'assert'
|
||||
import {Vector} from '../Vector.js'
|
||||
import {NotAType} from '#core/Type.js'
|
||||
import {Unknown} from '#core/Type.js'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector Type', () => {
|
||||
|
@ -14,7 +14,7 @@ describe('Vector Type', () => {
|
|||
const cplx = math.complex
|
||||
assert.strictEqual(typ([3, 4]), Vector(NumberT))
|
||||
assert.strictEqual(typ([true, false]), Vector(BooleanT))
|
||||
assert.strictEqual(typ([3, false]), Vector(NotAType))
|
||||
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)))
|
||||
})
|
||||
|
|
33
src/vector/__test__/arithmetic.spec.js
Normal file
33
src/vector/__test__/arithmetic.spec.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
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('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)
|
||||
})
|
||||
})
|
|
@ -1,5 +1,5 @@
|
|||
import assert from 'assert'
|
||||
import {NotAType} from '#core/Type.js'
|
||||
import {ReturnType, Unknown} from '#core/Type.js'
|
||||
import math from '#nanomath'
|
||||
|
||||
describe('Vector type functions', () => {
|
||||
|
@ -8,9 +8,11 @@ describe('Vector type functions', () => {
|
|||
const {BooleanT, NumberT, Vector} = math.types
|
||||
assert.deepStrictEqual(vec(3, 4, 5), [3, 4, 5])
|
||||
assert.strictEqual(
|
||||
vec.resolve([NumberT, NumberT, NumberT]).returns, Vector(NumberT))
|
||||
ReturnType(vec.resolve([NumberT, NumberT, NumberT])),
|
||||
Vector(NumberT))
|
||||
assert.deepStrictEqual(vec(3, true), [3, true])
|
||||
assert.strictEqual(
|
||||
vec.resolve([NumberT, BooleanT]).returns, Vector(NotAType))
|
||||
ReturnType(vec.resolve([NumberT, BooleanT])),
|
||||
Vector(Unknown))
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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'
|
||||
|
|
33
src/vector/arithmetic.js
Normal file
33
src/vector/arithmetic.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {Vector} from './Vector.js'
|
||||
|
||||
import {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 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
|
||||
})
|
||||
})
|
|
@ -1,61 +1,44 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {NotAType, Returns, Undefined} from '#core/Type.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) => {
|
||||
if (V.Component === NotAType) {
|
||||
// have to resolve element by element :-(
|
||||
return Returns(V, v => v.map(
|
||||
elt => math.resolve(name, math.typeOf(elt), strategy)(elt)))
|
||||
}
|
||||
const compOp = math.resolve(name, V.Component, strategy)
|
||||
return Returns(Vector(compOp.returns), v => v.map(elt => compOp(elt)))
|
||||
return Returns(Vector(ReturnType(compOp)), v => v.map(elt => compOp(elt)))
|
||||
})
|
||||
|
||||
export const promoteBinary = name => [
|
||||
match([Vector, Any], (math, [V, E], strategy) => {
|
||||
const VComp = V.Component
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (v, e) => v.map(
|
||||
f => math.resolve(name, [math.typeOf(f), E], strategy)(f, e)))
|
||||
}
|
||||
const compOp = math.resolve(name, [VComp, E], strategy)
|
||||
const compOp = math.resolve(name, [V.Component, E], strategy)
|
||||
return Returns(
|
||||
Vector(compOp.returns), (v, e) => v.map(f => compOp(f, e)))
|
||||
Vector(ReturnType(compOp)), (v, e) => v.map(f => compOp(f, e)))
|
||||
}),
|
||||
match([Any, Vector], (math, [E, V], strategy) => {
|
||||
const VComp = V.Component
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (e, v) => v.map(
|
||||
f => math.resolve(name, [E, math.typeOf(f)], strategy)(e, f)))
|
||||
}
|
||||
const compOp = math.resolve(name, [E, VComp], strategy)
|
||||
const compOp = math.resolve(name, [E, V.Component], strategy)
|
||||
return Returns(
|
||||
Vector(compOp.returns, (e, v) => v.map(f => compOp(e, f))))
|
||||
Vector(ReturnType(compOp)), (e, v) => v.map(f => compOp(e, f)))
|
||||
}),
|
||||
match([Vector, Vector], (math, [V, W], strategy) => {
|
||||
const VComp = V.Component
|
||||
const WComp = W.Component
|
||||
let compOp
|
||||
let opNoV
|
||||
let opNoW
|
||||
let ReturnType
|
||||
if (VComp === NotAType || WComp === NotAType) {
|
||||
const typ = math.typeOf
|
||||
compOp = (v, w) => {
|
||||
return math.resolve(name, [typ(v), typ(w)], strategy)(v, w)
|
||||
}
|
||||
opNoV = compOp
|
||||
opNoW = compOp
|
||||
ReturnType = Vector(NotAType)
|
||||
} else {
|
||||
compOp = math.resolve(name, [VComp, WComp], strategy)
|
||||
opNoV = math.resolve(name, [Undefined, WComp], strategy)
|
||||
opNoW = math.resolve(name, [VComp, Undefined], strategy)
|
||||
ReturnType = Vector(compOp.returns)
|
||||
// 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(
|
||||
ReturnType,
|
||||
Vector(ReturnType(compOp)),
|
||||
(v, w) => {
|
||||
const vInc = Number(v.length > 1)
|
||||
const wInc = Number(w.length >= v.length || w.length > 1)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {promoteBinary} from './helpers.js'
|
||||
|
||||
import {NotAType, Returns, Undefined} from '#core/Type.js'
|
||||
import {Unknown, Returns, ReturnType, Undefined} from '#core/Type.js'
|
||||
import {Any, Optional, match} from '#core/TypePatterns.js'
|
||||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
|
||||
|
@ -9,11 +9,6 @@ export const deepEqual = [
|
|||
match([Vector, Any], Returns(BooleanT, () => false)),
|
||||
match([Any, Vector], Returns(BooleanT, () => false)),
|
||||
match([Vector, Vector], (math, [V, W]) => {
|
||||
if (V.Component === NotAType || W.Component === NotAType) {
|
||||
return Returns(BooleanT, (v, w) => v === w
|
||||
|| (v.length === w.length
|
||||
&& v.every((e, i) => math.deepEqual(e, w[i]))))
|
||||
}
|
||||
const compDeep = math.deepEqual.resolve([V.Component, W.Component])
|
||||
return Returns(BooleanT, (v,w) => v === w
|
||||
|| (v.length === w.length
|
||||
|
@ -25,24 +20,14 @@ export const indistinguishable = [
|
|||
match([Vector, Any, Optional([Any, Any])], (math, [V, E, T]) => {
|
||||
const VComp = V.Component
|
||||
if (T.length === 0) { // no tolerances
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (v, e) => v.map(f => {
|
||||
return math.indistinguishable.resolve([math.typeOf(f), E])(f, e)
|
||||
}))
|
||||
}
|
||||
const same = math.indistinguishable.resolve([VComp, E])
|
||||
return Returns(
|
||||
Vector(same.returns), (v, e) => v.map(f => same(f, e)))
|
||||
Vector(ReturnType(same)), (v, e) => v.map(f => same(f, e)))
|
||||
}
|
||||
const [[RT, AT]] = T
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (v, e, [[rT, aT]]) => v.map(f => {
|
||||
return math.indistinguishable(f, e, rT, aT)
|
||||
}))
|
||||
}
|
||||
const same = math.indistinguishable.resolve([VComp, E, RT, AT])
|
||||
return Returns(
|
||||
Vector(same.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]) => {
|
||||
|
@ -50,30 +35,20 @@ export const indistinguishable = [
|
|||
// same is symmetric, even though it probably is
|
||||
const VComp = V.Component
|
||||
if (T.length === 0) { // no tolerances
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (e, v) => v.map(f => {
|
||||
return math.indistinguishable.resolve([E, math.typeOf(f)])(e, f)
|
||||
}))
|
||||
}
|
||||
const same = math.indistinguishable.resolve([E, VComp])
|
||||
return Returns(
|
||||
Vector(same.returns), (e, v) => v.map(f => same(e, f)))
|
||||
Vector(ReturnType(same)), (e, v) => v.map(f => same(e, f)))
|
||||
}
|
||||
const [[RT, AT]] = T
|
||||
if (VComp === NotAType) {
|
||||
return Returns(V, (e, v, [[rT, aT]]) => v.map(f => {
|
||||
return math.indistiguishable(e, f, rT, aT)
|
||||
}))
|
||||
}
|
||||
const same = math.indistinguishable.resolve([E, VComp, RT, AT])
|
||||
return Returns(
|
||||
Vector(same.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 === NotAType || WComp === NotAType
|
||||
const inhomogeneous = VComp === Unknown || WComp === Unknown
|
||||
let same
|
||||
let sameNoV
|
||||
let sameNoW
|
||||
|
@ -92,7 +67,7 @@ export const indistinguishable = [
|
|||
sameNoW = math.indistinguishable.resolve([VComp, Undefined, RT, AT])
|
||||
}
|
||||
return Returns(
|
||||
inhomogeneous ? Vector(NotAType) : Vector(same.returns),
|
||||
inhomogeneous ? Vector(Unknown) : Vector(ReturnType(same)),
|
||||
(v, w, [tol = [0, 0]]) => {
|
||||
const [rT, aT] = tol
|
||||
const vInc = Number(v.length > 1)
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import {Vector} from './Vector.js'
|
||||
import {NotAType, Returns} from '#core/Type.js'
|
||||
import {Returns, 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(NotAType), () => [])
|
||||
if (!TV.length) return Returns(Vector(Unknown), () => [])
|
||||
let CompType = TV[0]
|
||||
if (TV.some(T => T !== CompType)) CompType = NotAType
|
||||
if (TV.some(T => T !== CompType)) CompType = Unknown
|
||||
return Returns(Vector(CompType), v => v)
|
||||
})
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue