feat: respect return typing strategy for all complex functions
This commit is contained in:
parent
87f5a0ed2a
commit
c42249e561
5 changed files with 124 additions and 62 deletions
|
@ -1,9 +1,12 @@
|
|||
import assert from 'assert'
|
||||
import math from '#nanomath'
|
||||
import {Complex} from '../Complex.js'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const cplx = math.complex
|
||||
const CplxNum = Complex(NumberT)
|
||||
const {full} = ReturnTyping
|
||||
|
||||
describe('complex arithmetic operations', () => {
|
||||
it('computes absquare of complex numbers', () => {
|
||||
|
@ -25,23 +28,33 @@ describe('complex arithmetic operations', () => {
|
|||
assert.deepStrictEqual(add(cplx(z,z), 10), cplx(cplx(13, 4), z))
|
||||
assert.deepStrictEqual(
|
||||
add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
|
||||
assert.strictEqual(add(z, math.conj(z)), 6)
|
||||
const addFull = math.add.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(addFull(z, math.conj(z)), cplx(6, 0))
|
||||
})
|
||||
it('conjugates complex numbers', () => {
|
||||
const conj = math.conj
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(conj(z), cplx(3, -4))
|
||||
assert.deepStrictEqual(conj(cplx(z,z)), cplx(cplx(3, -4), cplx(-3, -4)))
|
||||
const r = cplx(3, 0)
|
||||
assert.strictEqual(conj(r), 3)
|
||||
const conjFull = math.conj.resolve(CplxNum, full)
|
||||
assert.deepStrictEqual(conjFull(r), cplx(3, -0))
|
||||
})
|
||||
it('divides complex numbers', () => {
|
||||
const div = math.divide
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(div(z, cplx(0, 1)), cplx(4, -3))
|
||||
assert(math.equal(div(z, z), 1))
|
||||
assert.strictEqual(div(z, z), 1)
|
||||
const divFull = math.divide.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(divFull(z, z), math.one(CplxNum))
|
||||
// should probably have a quaternion example, but it's intricate
|
||||
})
|
||||
it('inverts complex numbers', () => {
|
||||
const inv = math.invert
|
||||
assert.deepStrictEqual(inv(cplx(0, 1)), cplx(0, -1))
|
||||
assert.strictEqual(inv(cplx(2, 0)), 0.5)
|
||||
assert.deepStrictEqual(inv(cplx(3, 4)), cplx(3/25, -4/25))
|
||||
assert.deepStrictEqual(
|
||||
inv(cplx(cplx(1, 2), cplx(4, 2))),
|
||||
|
@ -51,8 +64,10 @@ describe('complex arithmetic operations', () => {
|
|||
const mult = math.multiply
|
||||
const z = cplx(3, 4)
|
||||
assert.deepStrictEqual(mult(z, z), cplx(-7, 24))
|
||||
assert(math.equal(mult(z, math.conj(z)), 25))
|
||||
const q0 = cplx(cplx(1, 1), math.zero(Complex(NumberT)))
|
||||
assert.strictEqual(mult(z, math.conj(z)), 25)
|
||||
const multFull = math.multiply.resolve([CplxNum, CplxNum], full)
|
||||
assert.deepStrictEqual(multFull(z, math.conj(z)), cplx(25, 0))
|
||||
const q0 = cplx(cplx(1, 1), math.zero(CplxNum))
|
||||
const q1 = cplx(cplx(1, 0.5), cplx(0.5, 0.75))
|
||||
assert.deepStrictEqual(
|
||||
mult(q0, q1), cplx(cplx(0.5, 1.5), cplx(1.25, 0.25)))
|
||||
|
@ -67,8 +82,15 @@ describe('complex arithmetic operations', () => {
|
|||
assert.deepStrictEqual(sub(z, cplx(true, false)), cplx(2, 4))
|
||||
assert.deepStrictEqual(sub(cplx(false, true), z), cplx(-3, -3))
|
||||
assert.deepStrictEqual(
|
||||
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||
cplx(cplx(2.5, 3.5), cplx(0, 0)))
|
||||
sub(cplx(z, z), cplx(cplx(0.5, 0.5), z)), cplx(2.5, 3.5))
|
||||
assert.strictEqual(
|
||||
sub(cplx(z, z), cplx(cplx(0, 4), z)), 3)
|
||||
const subFull = math.subtract.resolve([
|
||||
Complex(CplxNum), Complex(CplxNum)
|
||||
], full)
|
||||
assert.deepStrictEqual(
|
||||
subFull(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||
cplx(cplx(2.5, 3.5), math.zero(CplxNum)))
|
||||
assert.deepStrictEqual(sub(z, 5), cplx(-2, 4))
|
||||
assert.deepStrictEqual(sub(true, z), cplx(-2, -4))
|
||||
assert.deepStrictEqual(sub(cplx(z,z), 10), cplx(cplx(-7, 4), z))
|
||||
|
|
|
@ -1,43 +1,46 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {maybeComplex, promoteBinary, promoteUnary} from './helpers.js'
|
||||
import {ResolutionError} from '#core/helpers.js'
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const absquare = match(Complex, (math, C) => {
|
||||
const compAbsq = math.absquare.resolve([C.Component])
|
||||
const {full} = ReturnTyping
|
||||
|
||||
export const absquare = match(Complex, (math, C, strategy) => {
|
||||
const compAbsq = math.absquare.resolve(C.Component, full)
|
||||
const R = compAbsq.returns
|
||||
const add = math.add.resolve([R,R])
|
||||
const add = math.add.resolve([R,R], strategy)
|
||||
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
|
||||
})
|
||||
|
||||
export const add = promoteBinary('add')
|
||||
|
||||
export const conj = match(Complex, (math, C) => {
|
||||
const neg = math.negate.resolve(C.Component)
|
||||
const compConj = math.conj.resolve(C.Component)
|
||||
const cplx = math.complex.resolve([compConj.returns, neg.returns])
|
||||
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)
|
||||
return ReturnsAs(cplx, z => cplx(compConj(z.re), neg(z.im)))
|
||||
})
|
||||
|
||||
export const divide = [
|
||||
match([Complex, T => !T.complex], (math, [C, R]) => {
|
||||
const div = math.divide.resolve([C.Component, R])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
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)
|
||||
return ReturnsAs(cplx, (z, r) => cplx(div(z.re, r), div(z.im, r)))
|
||||
}),
|
||||
match([Complex, Complex], (math, [W, Z]) => {
|
||||
const inv = math.invert.resolve(Z)
|
||||
const mult = math.multiply.resolve([W, inv.returns])
|
||||
match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const inv = math.invert.resolve(Z, full)
|
||||
const mult = math.multiply.resolve([W, inv.returns], strategy)
|
||||
return ReturnsAs(mult, (w, z) => mult(w, inv(z)))
|
||||
})
|
||||
]
|
||||
|
||||
export const invert = match(Complex, (math, C) => {
|
||||
const conj = math.conj.resolve(C)
|
||||
const norm = math.absquare.resolve(C)
|
||||
const div = math.divide.resolve([C.Component, norm.returns])
|
||||
const cplx = math.complex.resolve([div.returns, div.returns])
|
||||
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)
|
||||
return ReturnsAs(cplx, z => {
|
||||
const c = conj(z)
|
||||
const d = norm(z)
|
||||
|
@ -47,17 +50,17 @@ export const invert = match(Complex, (math, C) => {
|
|||
|
||||
// We want this to work for complex numbers, quaternions, octonions, etc
|
||||
// See https://math.ucr.edu/home/baez/octonions/node5.html
|
||||
export const multiply = match([Complex, Complex], (math, [W, Z]) => {
|
||||
const conj = math.conj.resolve(W.Component)
|
||||
export const multiply = match([Complex, Complex], (math, [W, Z], strategy) => {
|
||||
const conj = math.conj.resolve(W.Component, full)
|
||||
if (conj.returns !== W.Component) {
|
||||
throw new ResolutionError(
|
||||
`conjugation on ${W.Component} returns other type (${conj.returns})`)
|
||||
}
|
||||
const mWZ = math.multiply.resolve([W.Component, Z.Component])
|
||||
const mZW = math.multiply.resolve([Z.Component, W.Component])
|
||||
const sub = math.subtract.resolve([mWZ.returns, mZW.returns])
|
||||
const add = math.add.resolve([mWZ.returns, mZW.returns])
|
||||
const cplx = math.complex.resolve([sub.returns, add.returns])
|
||||
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)
|
||||
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))
|
||||
|
|
|
@ -1,27 +1,38 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {OneOf, Returns, ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
export const promoteUnary = name => match(
|
||||
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)
|
||||
return ReturnsAs(prune, (r, m) => prune(cplx(r, m)))
|
||||
}
|
||||
|
||||
export const promoteUnary = (name, overrideStrategy) => match(
|
||||
Complex,
|
||||
(math, C) => {
|
||||
const compOp = math.resolve(name, C.Component)
|
||||
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||
(math, C, strategy) => {
|
||||
const compOp = math.resolve(name, C.Component, full)
|
||||
if (overrideStrategy) strategy = overrideStrategy
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
return ReturnsAs(cplx, z => cplx(compOp(z.re), compOp(z.im)))
|
||||
})
|
||||
|
||||
export const promotePredicateAnd = name => match(
|
||||
Complex,
|
||||
(math, C) => {
|
||||
const compPred = math.resolve(name, C.Component)
|
||||
(math, C, strategy) => {
|
||||
const compPred = math.resolve(name, C.Component, strategy)
|
||||
return ReturnsAs(compPred, z => compPred(z.re) && compPred(z.im))
|
||||
})
|
||||
|
||||
export const promoteBinary = name => match(
|
||||
[Complex, Complex],
|
||||
(math, [W, Z]) => {
|
||||
const compOp = math.resolve(name, [W.Component, Z.Component])
|
||||
const cplx = math.complex.resolve([compOp.returns, compOp.returns])
|
||||
(math, [W, Z], strategy) => {
|
||||
const compOp = math.resolve(name, [W.Component, Z.Component], full)
|
||||
const cplx = maybeComplex(math, strategy, compOp.returns, compOp.returns)
|
||||
return ReturnsAs(
|
||||
cplx, (w, z) => cplx(compOp(w.re, z.re), compOp(w.im, z.im)))
|
||||
})
|
||||
|
|
|
@ -4,6 +4,8 @@ import {Any, match} from "#core/TypePatterns.js"
|
|||
import {BooleanT} from '#boolean/BooleanT.js'
|
||||
import {NumberT} from '#number/NumberT.js'
|
||||
|
||||
const {free, full} = ReturnTyping
|
||||
|
||||
export const complex = [
|
||||
match(Any, (math, T) => {
|
||||
const z = math.zero(T)
|
||||
|
@ -31,30 +33,32 @@ export const arg = match(
|
|||
Complex(NumberT), Returns(NumberT, z => Math.atan2(z.im, z.re)))
|
||||
|
||||
/* Returns true if w is z multiplied by a complex unit */
|
||||
export const associate = match([Complex, Complex], (math, [W, Z]) => {
|
||||
if (Z.Component.complex) {
|
||||
throw new Error(
|
||||
export const associate = match(
|
||||
[Complex, Complex],
|
||||
(math, [W, Z], strategy) => {
|
||||
if (Z.Component.complex) {
|
||||
throw new Error(
|
||||
`The group of units of type ${Z} is not yet implemented`)
|
||||
}
|
||||
const eq = math.equal.resolve([W, Z])
|
||||
const neg = math.negate.resolve(Z)
|
||||
const eqN = math.equal.resolve([W, neg.returns])
|
||||
const mult = math.multiply.resolve([Z, Z])
|
||||
const eqM = math.equal.resolve([W, mult.returns])
|
||||
const negM = math.negate.resolve(mult.returns)
|
||||
const eqNM = math.equal.resolve([W, negM.returns])
|
||||
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
|
||||
const iz = mult(iZ, z)
|
||||
return eqM(w, iz) || eqNM(w, negM(iz))
|
||||
}
|
||||
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 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 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
|
||||
const iz = mult(iZ, z)
|
||||
return eqM(w, iz) || eqNM(w, negM(iz))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const _cis = t => ({re: Math.cos(t), im: Math.sin(t)})
|
||||
|
||||
export const cis = match(NumberT, (math, _type, strategy) => {
|
||||
if (strategy === ReturnTyping.free) {
|
||||
if (strategy === free) {
|
||||
const intTest = math.isInteger.resolve(NumberT)
|
||||
return Returns(OneOf(NumberT, Complex(NumberT)), t => {
|
||||
let halfCycles = t / Math.PI
|
||||
|
@ -67,3 +71,21 @@ export const cis = match(NumberT, (math, _type, strategy) => {
|
|||
}
|
||||
return Returns(Complex(NumberT), _cis)
|
||||
})
|
||||
|
||||
// Returns the real part if the complex number supplied has no imaginary
|
||||
// part, otherwise leaves that complex number alone.
|
||||
// Since it is explicitly asking for varying type, it ignores strategy.
|
||||
export const pruneImaginary = match(Complex, (math, C) => {
|
||||
const outcomes = [C]
|
||||
let T = C
|
||||
while (T.complex) {
|
||||
T = T.Component
|
||||
outcomes.push(T)
|
||||
}
|
||||
const real = math.isReal.resolve(C, full)
|
||||
if (C.Component.complex) {
|
||||
const compPrune = math.pruneImaginary.resolve(C.Component)
|
||||
return Returns(OneOf(...outcomes), z => real(z) ? compPrune(z.re) : z)
|
||||
}
|
||||
return Returns(OneOf(...outcomes), z => real(z) ? z.re : z)
|
||||
})
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import {Complex} from './Complex.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
import {promotePredicateAnd, promoteUnary} from './helpers.js'
|
||||
|
||||
export const clone = promoteUnary('clone')
|
||||
import {ReturnTyping} from '#core/Type.js'
|
||||
import {match} from '#core/TypePatterns.js'
|
||||
import {ReturnsAs} from '#generic/helpers.js'
|
||||
|
||||
const {full} = ReturnTyping
|
||||
|
||||
export const clone = promoteUnary('clone', full)
|
||||
export const isnan = promotePredicateAnd('isnan')
|
||||
export const isfinite = promotePredicateAnd('isfinite')
|
||||
// Note: the followig predicate returns true for all Gaussian integers, not
|
||||
|
@ -12,6 +16,6 @@ export const isInteger = promotePredicateAnd('isInteger')
|
|||
|
||||
export const isReal = match(Complex, (math, C) => {
|
||||
const eq = math.equal.resolve([C.Component, C.Component])
|
||||
const add = math.add.resolve([C.Component, C.Component])
|
||||
const add = math.add.resolve([C.Component, C.Component], full)
|
||||
return ReturnsAs(eq, z => eq(z.re, add(z.re, z.im)))
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue