diff --git a/.gitignore b/.gitignore index 7dea02f3..bf34965b 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,4 @@ dist .DS_Store .eslintcache +.claude diff --git a/src/__tests__/__snapshots__/index.test.ts.snap b/src/__tests__/__snapshots__/index.test.ts.snap index 0107959e..f87c96fe 100644 --- a/src/__tests__/__snapshots__/index.test.ts.snap +++ b/src/__tests__/__snapshots__/index.test.ts.snap @@ -91,6 +91,7 @@ exports[`existence of exported functions 1`] = ` "xyEquallySpaced", "xyExtract", "xyFilter", + "xyFromInterleaved", "xyFilterMinYValue", "xyFilterTopYValues", "xyFilterX", @@ -124,6 +125,7 @@ exports[`existence of exported functions 1`] = ` "xyRollingCircleTransform", "xySetYValue", "xySortX", + "xyToInterleaved", "xyToXYArray", "xyToXYObject", "xyUniqueX", diff --git a/src/matrix/matrixPQN.ts b/src/matrix/matrixPQN.ts index f72cfa20..c1094ab3 100644 --- a/src/matrix/matrixPQN.ts +++ b/src/matrix/matrixPQN.ts @@ -32,7 +32,7 @@ export function matrixPQN( medianOfQuotients: number[]; } { const { max = 100 } = options; - const matrixB = new Matrix(matrix as number[][]); + const matrixB = new Matrix(matrix); for (let i = 0; i < matrixB.rows; i++) { const normalizationFactor = matrixB.getRowVector(i).norm('frobenius') / max; const row = matrixB.getRowVector(i).div(normalizationFactor); diff --git a/src/utils/__tests__/calculateAdaptiveWeights.test.ts b/src/utils/__tests__/calculateAdaptiveWeights.test.ts index 14c8fe2f..ca68c153 100644 --- a/src/utils/__tests__/calculateAdaptiveWeights.test.ts +++ b/src/utils/__tests__/calculateAdaptiveWeights.test.ts @@ -134,12 +134,7 @@ test('works with different array types', () => { const baseline = [1.1, 2.1, 3.1, 4.1, 5.1]; const weights = [1, 1, 1, 1, 1]; - const result = calculateAdaptiveWeights( - yData as any, - baseline as any, - weights as any, - {}, - ); + const result = calculateAdaptiveWeights(yData, baseline, weights, {}); expect(result).toBeDefined(); expect(result[0]).toBe(1); diff --git a/src/xy/__tests__/xyFromInterleaved.test.ts b/src/xy/__tests__/xyFromInterleaved.test.ts new file mode 100644 index 00000000..2ed0f5dc --- /dev/null +++ b/src/xy/__tests__/xyFromInterleaved.test.ts @@ -0,0 +1,30 @@ +import { expect, test } from 'vitest'; + +import { xyFromInterleaved } from '../index.ts'; + +test('basic', () => { + expect(xyFromInterleaved([1, 10, 2, 20, 3, 30])).toStrictEqual({ + x: new Float64Array([1, 2, 3]), + y: new Float64Array([10, 20, 30]), + }); +}); + +test('Float64Array input', () => { + expect(xyFromInterleaved(new Float64Array([1, 10, 2, 20]))).toStrictEqual({ + x: new Float64Array([1, 2]), + y: new Float64Array([10, 20]), + }); +}); + +test('empty array', () => { + expect(xyFromInterleaved([])).toStrictEqual({ + x: new Float64Array(0), + y: new Float64Array(0), + }); +}); + +test('odd length throws', () => { + expect(() => xyFromInterleaved([1, 2, 3])).toThrow( + /data length must be even/, + ); +}); diff --git a/src/xy/__tests__/xyToInterleaved.test.ts b/src/xy/__tests__/xyToInterleaved.test.ts new file mode 100644 index 00000000..37147502 --- /dev/null +++ b/src/xy/__tests__/xyToInterleaved.test.ts @@ -0,0 +1,21 @@ +import { expect, test } from 'vitest'; + +import { xyFromInterleaved, xyToInterleaved } from '../index.ts'; + +test('basic', () => { + expect(xyToInterleaved({ x: [1, 2, 3], y: [10, 20, 30] })).toStrictEqual( + new Float64Array([1, 10, 2, 20, 3, 30]), + ); +}); + +test('empty', () => { + expect(xyToInterleaved({ x: [], y: [] })).toStrictEqual(new Float64Array(0)); +}); + +test('round-trip with xyFromInterleaved', () => { + const interleaved = new Float64Array([1, 10, 2, 20, 3, 30]); + + expect(xyToInterleaved(xyFromInterleaved(interleaved))).toStrictEqual( + interleaved, + ); +}); diff --git a/src/xy/index.ts b/src/xy/index.ts index a8a9b914..472429d3 100644 --- a/src/xy/index.ts +++ b/src/xy/index.ts @@ -7,6 +7,7 @@ export * from './xyEnsureGrowingX.ts'; export * from './xyEquallySpaced.ts'; export * from './xyExtract.ts'; export * from './xyFilter.ts'; +export * from './xyFromInterleaved.ts'; export * from './xyFilterMinYValue.ts'; export * from './xyFilterTopYValues.ts'; export * from './xyFilterX.ts'; @@ -41,6 +42,7 @@ export * from './xyRolling.ts'; export * from './xyRollingCircleTransform.ts'; export * from './xySetYValue.ts'; export * from './xySortX.ts'; +export * from './xyToInterleaved.ts'; export * from './xyToXYArray.ts'; export * from './xyToXYObject.ts'; export * from './xyUniqueX.ts'; diff --git a/src/xy/xyFilterX.ts b/src/xy/xyFilterX.ts index aad0d0a9..1fecd9ec 100644 --- a/src/xy/xyFilterX.ts +++ b/src/xy/xyFilterX.ts @@ -47,11 +47,12 @@ export function xyFilterX( } const { from = x[0], - to = x.at(-1) as number, + to = x.at(-1), zones = [{ from, to }], exclusions = [], } = options; + // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here const normalizedZones = zonesNormalize(zones, { from, to, exclusions }); let currentZoneIndex = 0; diff --git a/src/xy/xyFromInterleaved.ts b/src/xy/xyFromInterleaved.ts new file mode 100644 index 00000000..63d11068 --- /dev/null +++ b/src/xy/xyFromInterleaved.ts @@ -0,0 +1,24 @@ +import type { DataXY } from 'cheminfo-types'; + +/** + * Convert a flat interleaved array [x, y, x, y, ...] to a DataXY object. + * @param data - Flat array alternating x and y values. + * @returns DataXY object with separate x and y arrays. + */ +export function xyFromInterleaved( + data: number[] | Float64Array, +): DataXY { + if (data.length % 2 !== 0) { + throw new RangeError( + `xyFromInterleaved: data length must be even, got ${data.length}`, + ); + } + const length = data.length / 2; + const x = new Float64Array(length); + const y = new Float64Array(length); + for (let i = 0; i < length; i++) { + x[i] = data[2 * i]; + y[i] = data[2 * i + 1]; + } + return { x, y }; +} diff --git a/src/xy/xyReduce.ts b/src/xy/xyReduce.ts index c370786c..9e219adc 100644 --- a/src/xy/xyReduce.ts +++ b/src/xy/xyReduce.ts @@ -69,13 +69,14 @@ export function xyReduce( const { x, y } = data; const { from = x[0], - to = x.at(-1) as number, + to = x.at(-1), nbPoints = 4001, optimize = false, } = options; let { zones = [] } = options; zones = zonesNormalize(zones, { from, to }); + // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here if (zones.length === 0) zones = [{ from, to }]; // we take everything const { internalZones, totalPoints } = getInternalZones(zones, x); diff --git a/src/xy/xyReduceNonContinuous.ts b/src/xy/xyReduceNonContinuous.ts index dca47b59..decb38bb 100644 --- a/src/xy/xyReduceNonContinuous.ts +++ b/src/xy/xyReduceNonContinuous.ts @@ -51,14 +51,11 @@ export function xyReduceNonContinuous( }; } const { x, y } = data; - const { - from = x[0], - to = x.at(-1) as number, - maxApproximateNbPoints = 4001, - } = options; + const { from = x[0], to = x.at(-1), maxApproximateNbPoints = 4001 } = options; let { zones = [] } = options; zones = zonesNormalize(zones, { from, to }); + // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here if (zones.length === 0) zones = [{ from, to }]; // we take everything const { internalZones, totalPoints } = getInternalZones(zones, x); @@ -68,6 +65,7 @@ export function xyReduceNonContinuous( return notEnoughPoints(x, y, internalZones, totalPoints); } + // @ts-expect-error -- x.at(-1) returns number | undefined but array is guaranteed non-empty here const deltaX = (to - from) / (maxApproximateNbPoints - 1); const newX: number[] = []; const newY: number[] = []; diff --git a/src/xy/xyToInterleaved.ts b/src/xy/xyToInterleaved.ts new file mode 100644 index 00000000..30330d79 --- /dev/null +++ b/src/xy/xyToInterleaved.ts @@ -0,0 +1,19 @@ +import type { DataXY } from 'cheminfo-types'; + +import { xyCheck } from './xyCheck.ts'; + +/** + * Convert a DataXY object to a flat interleaved array [x, y, x, y, ...]. + * @param data - DataXY object with x and y arrays. + * @returns Flat array alternating x and y values. + */ +export function xyToInterleaved(data: DataXY): Float64Array { + xyCheck(data); + const { x, y } = data; + const result = new Float64Array(x.length * 2); + for (let i = 0; i < x.length; i++) { + result[2 * i] = x[i]; + result[2 * i + 1] = y[i]; + } + return result; +}