feat: Complete relational functions for vectors

Toward its goal, this commit also:
  * Adds a new section of logical functions, and defines `not`, `and`,
    `or` for all current types.
  * Extends `OneOf` choice/union type to allow argument types that are
    themselves `OneOf` types.
  * Adds a readable .toString() method for TypePatterns.
  * Defines negate (as a no-op) and isnan (as always true) for the Undefined
    type
  * Extends comparisons to the Undefined type (to handle comparing vectors
    of different lengths)
This commit is contained in:
Glen Whitney 2025-05-02 19:03:54 -07:00
parent c3c2bbbf78
commit edfba089e3
19 changed files with 238 additions and 19 deletions

View file

@ -31,4 +31,17 @@ describe('Vector relational functions', () => {
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])
})
})

View file

@ -1,4 +1,5 @@
export * as typeDefinition from './Vector.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'

View file

@ -1,6 +1,6 @@
import {Vector} from './Vector.js'
import {NotAType, Returns} from '#core/Type.js'
import {match} from '#core/TypePatterns.js'
import {NotAType, Returns, 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) {
@ -12,4 +12,68 @@ export const promoteUnary = name => match(Vector, (math, V, strategy) => {
return Returns(Vector(compOp.returns), 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)
return Returns(
Vector(compOp.returns), (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)
return Returns(
Vector(compOp.returns, (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)
}
return Returns(
ReturnType,
(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
View 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')

View file

@ -1,4 +1,6 @@
import {Vector} from './Vector.js'
import {promoteBinary} from './helpers.js'
import {NotAType, Returns, Undefined} from '#core/Type.js'
import {Any, Optional, match} from '#core/TypePatterns.js'
import {BooleanT} from '#boolean/BooleanT.js'
@ -23,11 +25,21 @@ 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)))
}
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),
@ -38,11 +50,21 @@ 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)))
}
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),
@ -51,10 +73,15 @@ export const indistinguishable = [
match([Vector, Vector, Optional([Any, Any])], (math, [V, W, T]) => {
const VComp = V.Component
const WComp = W.Component
const inhomogeneous = VComp === NotAType || WComp === NotAType
let same
let sameNoV
let sameNoW
if (T.length === 0) { // no tolerances
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])
@ -65,7 +92,7 @@ export const indistinguishable = [
sameNoW = math.indistinguishable.resolve([VComp, Undefined, RT, AT])
}
return Returns(
Vector(same.returns),
inhomogeneous ? Vector(NotAType) : Vector(same.returns),
(v, w, [tol = [0, 0]]) => {
const [rT, aT] = tol
const vInc = Number(v.length > 1)
@ -73,7 +100,6 @@ export const indistinguishable = [
const retval = []
let vIx = 0
let wIx = 0
let remainder = vIx < v.length || wIx < w.length
while ((vInc && vIx < v.length)
|| (wInc && wIx < w.length)
) {
@ -89,3 +115,6 @@ export const indistinguishable = [
})
})
]
export const compare = promoteBinary('compare')
export const exceeds = promoteBinary('exceeds')