diff --git a/src/__tests__/index.spec.ts b/src/__tests__/index.spec.ts index 1eaad11..0f45594 100644 --- a/src/__tests__/index.spec.ts +++ b/src/__tests__/index.spec.ts @@ -14,6 +14,7 @@ import { iteratedEuclid, lcm, mmod, + norm, PRIMES, valueToCents, } from '../index'; @@ -224,3 +225,18 @@ describe('Value clamper', () => { expect(clamped).toBe(12800); }); }); + +describe('Norm', () => { + it('calculates an euclidean norm (float32)', () => { + const a = new Float32Array([-3, 4]); + expect(norm(a)).toBeCloseTo(5); + }); + it('calculates a taxicab norm (int8)', () => { + const a = new Int8Array([3, -4]); + expect(norm(a, 'taxicab')).toBeCloseTo(7); + }); + it('calculates a max norm (number[])', () => { + const a = [-3, -4]; + expect(norm(a, 'maximum')).toBeCloseTo(4); + }); +}); diff --git a/src/index.ts b/src/index.ts index 373fb3e..e48314c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -540,3 +540,29 @@ export function dot(a: NumberArray, b: NumberArray): number { } return result; } + +/** + * Calculate the norm (vector length) of an array of real numbers. + * @param array The array to measure. + * @param type Type of measurement. + * @returns The length of the vector. + */ +export function norm( + array: NumberArray, + type: 'euclidean' | 'taxicab' | 'maximum' = 'euclidean' +) { + let result = 0; + for (let i = 0; i < array.length; ++i) { + if (type === 'taxicab') { + result += Math.abs(array[i]); + } else if (type === 'maximum') { + result = Math.max(result, Math.abs(array[i])); + } else { + result += array[i] * array[i]; + } + } + if (type === 'euclidean') { + return Math.sqrt(result); + } + return result; +} diff --git a/src/monzo.ts b/src/monzo.ts index 6f905cb..77c464b 100644 --- a/src/monzo.ts +++ b/src/monzo.ts @@ -72,6 +72,69 @@ export function sub(a: Monzo, b: Monzo): Monzo { return result; } +/** + * Scale a monzo by a scalar. + * @param monzo The monzo to scale. + * @param amount The amount to scale by. + * @returns The scalar multiple. + */ +export function scale(monzo: Monzo, amount: number) { + return monzo.map(component => component * amount); +} + +/** + * Multiply two monzos component-wise. + * @param monzo The first monzo. + * @param weights The second monzo. Missing values interpreted as 1 (no change). + * @returns The first monzo weighted by the second. + */ +export function applyWeights(monzo: Monzo, weights: Monzo) { + const result = [...monzo]; + for (let i = 0; i < Math.min(monzo.length, weights.length); ++i) { + result[i] *= weights[i]; + } + return result; +} + +/** + * Accumulate a monzo into the first one. + * @param target The monzo to accumulate into. + * @param source The monzo to add. + * @returns The (modified) target monzo. + */ +export function accumulate(target: Monzo, source: Monzo) { + for (let i = 0; i < Math.min(target.length, source.length); ++i) { + target[i] += source[i]; + } + return target; +} + +/** + * Decumulate a monzo into the first one. + * @param target The monzo to decumulate into. + * @param source The monzo to subtract. + * @returns The (modified) target monzo. + */ +export function decumulate(target: Monzo, source: Monzo) { + for (let i = 0; i < Math.min(target.length, source.length); ++i) { + target[i] -= source[i]; + } + return target; +} + +/** + * Rescale a monzo by a scalar. + * @param target The monzo to rescale. + * @param amount The amount to scale by. + * @returns The (modified) target monzo. + */ +export function rescale(target: Monzo, amount: number) { + for (let i = 0; i < target.length; ++i) { + target[i] *= amount; + } + return target; +} + /** * Extract the exponents of the prime factors of a rational number. * @param n Rational number to convert to a monzo.