feat: Start arithmetic functions for complex
All checks were successful
/ test (pull_request) Successful in 17s
All checks were successful
/ test (pull_request) Successful in 17s
So far, adds absquare and add. To get these working, especially on mixed types of arguments, this also adds some additional features: * Allows conversions to generic types, with the matched type determined from the return value of the built convertor * Adds predicate-based type patterns * Adds conversion from any non-complex type T to Complex(T) * Starts tests for complex arithmetic
This commit is contained in:
parent
0ff00ff8cb
commit
474cc53d68
9 changed files with 95 additions and 7 deletions
|
@ -1,4 +1,4 @@
|
||||||
import {Type} from '#core/Type.js'
|
import {Returns, Type} from '#core/Type.js'
|
||||||
import {match} from '#core/helpers.js'
|
import {match} from '#core/helpers.js'
|
||||||
|
|
||||||
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
const isComplex = z => z && typeof z === 'object' && 're' in z && 'im' in z
|
||||||
|
@ -66,6 +66,12 @@ function complexSpecialize(ComponentType) {
|
||||||
export const Complex = new Type(isComplex, {
|
export const Complex = new Type(isComplex, {
|
||||||
specialize: complexSpecialize,
|
specialize: complexSpecialize,
|
||||||
specializesTo,
|
specializesTo,
|
||||||
|
from: [match( // can promote any non-complex type T to Complex(T) as needed
|
||||||
|
// but watch out, this should be tried late, because it can preclude
|
||||||
|
// other more reasonable conversions like bool => number.
|
||||||
|
T => !T.complex,
|
||||||
|
(math, T) => Returns(Complex(T), r => math.complex(r, math.zero(T)))
|
||||||
|
)],
|
||||||
refine: function(z, typer) {
|
refine: function(z, typer) {
|
||||||
const reType = typer(z.re)
|
const reType = typer(z.re)
|
||||||
const imType = typer(z.im)
|
const imType = typer(z.im)
|
||||||
|
|
26
src/complex/__test__/arithmetic.spec.js
Normal file
26
src/complex/__test__/arithmetic.spec.js
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import assert from 'assert'
|
||||||
|
import math from '#nanomath'
|
||||||
|
|
||||||
|
const cplx = math.complex
|
||||||
|
|
||||||
|
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('adds complex numbers', () => {
|
||||||
|
const z = cplx(3, 4)
|
||||||
|
assert.deepStrictEqual(math.add(z, cplx(-1, 1)), cplx(2, 5))
|
||||||
|
assert.deepStrictEqual(math.add(z, cplx(true, false)), cplx(4, 4))
|
||||||
|
assert.deepStrictEqual(math.add(cplx(false, true), z), cplx(3, 5))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(cplx(z, z), cplx(cplx(0.5, 0.5), z)),
|
||||||
|
cplx(cplx(3.5, 4.5), cplx(6, 8)))
|
||||||
|
assert.deepStrictEqual(math.add(z, 5), cplx(8, 4))
|
||||||
|
assert.deepStrictEqual(math.add(true, z), cplx(4, 4))
|
||||||
|
assert.deepStrictEqual(math.add(cplx(z,z), 10), cplx(cplx(13, 4), z))
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
math.add(cplx(z,z), cplx(10,20)), cplx(cplx(13, 4), cplx(23, 4)))
|
||||||
|
})
|
||||||
|
})
|
|
@ -1,2 +1,3 @@
|
||||||
export * as typeDefinition from './Complex.js'
|
export * as typeDefinition from './Complex.js'
|
||||||
|
export * as arithmetic from './arithmetic.js'
|
||||||
export * as type from './type.js'
|
export * as type from './type.js'
|
||||||
|
|
17
src/complex/arithmetic.js
Normal file
17
src/complex/arithmetic.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import {Complex} from './Complex.js'
|
||||||
|
import {match} from '#core/helpers.js'
|
||||||
|
import {ReturnsAs} from '#generic/helpers.js'
|
||||||
|
|
||||||
|
export const absquare = match(Complex, (math, C) => {
|
||||||
|
const compAbsq = math.absquare.resolve([C.Component])
|
||||||
|
const R = compAbsq.returns
|
||||||
|
const add = math.add.resolve([R,R])
|
||||||
|
return ReturnsAs(add, z => add(compAbsq(z.re), compAbsq(z.im)))
|
||||||
|
})
|
||||||
|
|
||||||
|
export const add = match([Complex, Complex], (math, [C, D]) => {
|
||||||
|
const addComps = math.add.resolve([C.Component, D.Component])
|
||||||
|
const cplx = math.complex.resolve([addComps.returns, addComps.returns])
|
||||||
|
return ReturnsAs(
|
||||||
|
cplx, (w,z) => cplx(addComps(w.re, z.re), addComps(w.im, z.im)))
|
||||||
|
})
|
|
@ -334,7 +334,7 @@ export class TypeDispatcher {
|
||||||
try {
|
try {
|
||||||
theBehavior = item(
|
theBehavior = item(
|
||||||
DependencyRecorder(this, '', this, []),
|
DependencyRecorder(this, '', this, []),
|
||||||
matched(template))
|
matched(template, this))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
e.message = `Error in factory for ${key} on ${types} `
|
e.message = `Error in factory for ${key} on ${types} `
|
||||||
+ `(match data ${template}): ${e.message}`
|
+ `(match data ${template}): ${e.message}`
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import {Type, Undefined} from './Type.js'
|
import {Type, Undefined} from './Type.js'
|
||||||
|
import {isPlainFunction} from './helpers.js'
|
||||||
|
|
||||||
export class TypePattern {
|
export class TypePattern {
|
||||||
match(typeSequence, options={}) {
|
match(typeSequence, options={}) {
|
||||||
|
@ -64,12 +65,34 @@ class SequencePattern extends TypePattern {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class PredicatePattern extends TypePattern {
|
||||||
|
constructor(predicate) {
|
||||||
|
super()
|
||||||
|
this.predicate = predicate
|
||||||
|
}
|
||||||
|
match(typeSequence, options={}) {
|
||||||
|
const position = options.position ?? 0
|
||||||
|
const actual = typeSequence[position]
|
||||||
|
if (this.predicate(actual)) return [position + 1, actual]
|
||||||
|
return [-1, Undefined]
|
||||||
|
}
|
||||||
|
sampleTypes() {
|
||||||
|
throw new Error('sampleTypes() not yet implemented for PredicatePattern')
|
||||||
|
}
|
||||||
|
equal(other) {
|
||||||
|
return super.equal(other) && this.predicate === other.predicate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const pattern = patternOrSpec => {
|
export const pattern = patternOrSpec => {
|
||||||
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
||||||
if (patternOrSpec instanceof Type) {
|
if (patternOrSpec instanceof Type) {
|
||||||
return new MatchTypePattern(patternOrSpec)
|
return new MatchTypePattern(patternOrSpec)
|
||||||
}
|
}
|
||||||
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
|
if (Array.isArray(patternOrSpec)) return new SequencePattern(patternOrSpec)
|
||||||
|
if (isPlainFunction(patternOrSpec)) {
|
||||||
|
return new PredicatePattern(patternOrSpec)
|
||||||
|
}
|
||||||
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
|
throw new TypeError(`Can't interpret '${patternOrSpec}' as a type pattern`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,9 +173,16 @@ class PassthruPattern extends TypePattern {
|
||||||
export const Passthru = new PassthruPattern()
|
export const Passthru = new PassthruPattern()
|
||||||
|
|
||||||
// returns the template just of matched types, dropping any actual types
|
// returns the template just of matched types, dropping any actual types
|
||||||
export const matched = (template) => {
|
export const matched = (template, math) => {
|
||||||
if (Array.isArray(template)) return template.map(matched)
|
if (Array.isArray(template)) {
|
||||||
return template.matched ?? template
|
return template.map(pattern => matched(pattern, math))
|
||||||
|
}
|
||||||
|
if (template.matched) {
|
||||||
|
let convert = template.convertor
|
||||||
|
if (!convert.returns) convert = convert(math, template.actual)
|
||||||
|
return convert.returns || template.matched
|
||||||
|
}
|
||||||
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if the template is just pass-through or needs collection
|
// checks if the template is just pass-through or needs collection
|
||||||
|
|
|
@ -18,7 +18,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
||||||
return boolnum(() => false)(math)
|
return boolnum(() => false)(math)
|
||||||
}
|
}
|
||||||
// Get the type of the first argument to the matching checker:
|
// Get the type of the first argument to the matching checker:
|
||||||
const ByType = matched(exactChecker.template).flat()[0]
|
const ByType = matched(exactChecker.template, math).flat()[0]
|
||||||
// Now see if there are tolerances for that type:
|
// Now see if there are tolerances for that type:
|
||||||
const typeConfig = math.resolve('config', [ByType])
|
const typeConfig = math.resolve('config', [ByType])
|
||||||
if ('relTol' in typeConfig) {
|
if ('relTol' in typeConfig) {
|
||||||
|
|
|
@ -5,6 +5,14 @@ import * as numbers from './number/all.js'
|
||||||
import * as complex from './complex/all.js'
|
import * as complex from './complex/all.js'
|
||||||
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
import {TypeDispatcher} from '#core/TypeDispatcher.js'
|
||||||
|
|
||||||
const math = new TypeDispatcher(booleans, coretypes, generics, numbers, complex)
|
// At the moment, since we are not sorting patterns in any way,
|
||||||
|
// order matters in the construction. Patterns that come later in
|
||||||
|
// the following list will be tried earlier. (The rationale for that
|
||||||
|
// ordering is that any time you merge something, it should supersede
|
||||||
|
// whatever has been merged before.)
|
||||||
|
// Hence, in building the math instance, we put complex first 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(complex, generics, booleans, coretypes, numbers)
|
||||||
|
|
||||||
export default math
|
export default math
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue