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:
Glen Whitney 2025-05-03 19:59:44 -07:00
parent ec97b0e20a
commit cb3a93dd1c
26 changed files with 238 additions and 171 deletions

View file

@ -1,7 +1,7 @@
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'
@ -9,7 +9,7 @@ const {conservative, full, free} = ReturnTyping
export const normsq = match(Complex, (math, C, strategy) => {
const compNormsq = math.normsq.resolve(C.Component, full)
const R = compNormsq.returns
const R = ReturnType(compNormsq)
const add = math.add.resolve([R,R], strategy)
return ReturnsAs(add, z => add(compNormsq(z.re), compNormsq(z.im)))
})
@ -19,19 +19,21 @@ 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)))
})
]
@ -39,8 +41,9 @@ export const divide = [
export const invert = match(Complex, (math, C, strategy) => {
const conj = math.conj.resolve(C, full)
const normsq = math.normsq.resolve(C, full)
const div = math.divide.resolve([C.Component, normsq.returns], full)
const cplx = maybeComplex(math, strategy, div.returns, div.returns)
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 = normsq(z)
@ -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)