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'
|
||||
|
||||
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, {
|
||||
specialize: complexSpecialize,
|
||||
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) {
|
||||
const reType = typer(z.re)
|
||||
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 arithmetic from './arithmetic.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 {
|
||||
theBehavior = item(
|
||||
DependencyRecorder(this, '', this, []),
|
||||
matched(template))
|
||||
matched(template, this))
|
||||
} catch (e) {
|
||||
e.message = `Error in factory for ${key} on ${types} `
|
||||
+ `(match data ${template}): ${e.message}`
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {Type, Undefined} from './Type.js'
|
||||
import {isPlainFunction} from './helpers.js'
|
||||
|
||||
export class TypePattern {
|
||||
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 => {
|
||||
if (patternOrSpec instanceof TypePattern) return patternOrSpec
|
||||
if (patternOrSpec instanceof Type) {
|
||||
return new MatchTypePattern(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`)
|
||||
}
|
||||
|
||||
|
@ -150,9 +173,16 @@ class PassthruPattern extends TypePattern {
|
|||
export const Passthru = new PassthruPattern()
|
||||
|
||||
// returns the template just of matched types, dropping any actual types
|
||||
export const matched = (template) => {
|
||||
if (Array.isArray(template)) return template.map(matched)
|
||||
return template.matched ?? template
|
||||
export const matched = (template, math) => {
|
||||
if (Array.isArray(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
|
||||
|
|
|
@ -18,7 +18,7 @@ export const equal = match([Any, Any], (math, [T, U]) => {
|
|||
return boolnum(() => false)(math)
|
||||
}
|
||||
// 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:
|
||||
const typeConfig = math.resolve('config', [ByType])
|
||||
if ('relTol' in typeConfig) {
|
||||
|
|
|
@ -5,6 +5,14 @@ import * as numbers from './number/all.js'
|
|||
import * as complex from './complex/all.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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue