Skip to content

Commit

Permalink
Merge branch 'main' into test
Browse files Browse the repository at this point in the history
  • Loading branch information
lpatiny authored Nov 22, 2024
2 parents 7301987 + 22a6640 commit 3d0cb74
Show file tree
Hide file tree
Showing 5 changed files with 381 additions and 31 deletions.
1 change: 1 addition & 0 deletions src/__tests__/__snapshots__/index.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ exports[`test existence of exported functions 1`] = `
"xyRealMaxYPoint",
"xyRealMinYPoint",
"xyReduce",
"xyReduceNonContinuous",
"xyRolling",
"xySetYValue",
"xySortX",
Expand Down
229 changes: 229 additions & 0 deletions src/xy/__tests__/xyReduceNonContinuous.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
import { expect, test } from 'vitest';

import { xyReduceNonContinuous } from '../xyReduceNonContinuous';

const x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const y = [0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0];
test('All', () => {
const result = xyReduceNonContinuous(
{ x, y },
{ maxApproximateNbPoints: 20 },
);
expect(result).toStrictEqual({
x: Float64Array.from(x),
y: Float64Array.from(y),
});
});

test('Over sized', () => {
const x2 = [1, 2];
const y2 = [2, 3];
const result = xyReduceNonContinuous(
{ x: x2, y: y2 },
{ maxApproximateNbPoints: 10 },
);
expect(result).toStrictEqual({
x: Float64Array.from([1, 2]),
y: Float64Array.from([2, 3]),
});
});

test('Too large', () => {
const result = {
x: new Float64Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]),
y: new Float64Array([0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0]),
};
expect(
xyReduceNonContinuous(
{ x, y },
{ maxApproximateNbPoints: 20, from: -10, to: 20 },
),
).toStrictEqual(result);
});

test('Part exact', () => {
const result = {
x: new Float64Array([3, 4, 5]),
y: new Float64Array([3, 4, 5]),
};
expect(
xyReduceNonContinuous(
{ x, y },
{ from: 3, to: 5, maxApproximateNbPoints: 20 },
),
).toStrictEqual(result);
});

test('Part rounded close', () => {
const result = {
x: new Float64Array([3, 4, 5]),
y: new Float64Array([3, 4, 5]),
};
expect(
xyReduceNonContinuous(
{ x, y },
{ from: 3.1, to: 4.9, maxApproximateNbPoints: 20 },
),
).toStrictEqual(result);
});

test('Part rounded far', () => {
const result = {
x: new Float64Array([3, 4, 5]),
y: new Float64Array([3, 4, 5]),
};
expect(
xyReduceNonContinuous(
{ x, y },
{ from: 3.6, to: 4.4, maxApproximateNbPoints: 20 },
),
).toStrictEqual(result);
});

test('Part rounded far 2', () => {
const result = xyReduceNonContinuous({ x, y }, { maxApproximateNbPoints: 5 });
expect(result).toStrictEqual({ x: [0, 3, 6, 8], y: [2, 5, 4, 2] });
});

test('Part rounded big data', () => {
const x2 = [];
const y2 = [];
for (let i = 0; i < 5000000; i++) {
x2.push(i);
y2.push(i);
}
const result = xyReduceNonContinuous(
{ x: x2, y: y2 },
{ maxApproximateNbPoints: 4000 },
);
expect(result.x).toHaveLength(4000);
expect(result.y).toHaveLength(4000);
});

test('Part rounded big data 2', () => {
const x2 = [];
const y2 = [];
for (let i = 0; i < 5000000; i++) {
x2.push(i);
y2.push(i);
}
const result = xyReduceNonContinuous(
{ x: x2, y: y2 },
{ maxApproximateNbPoints: 4000, from: 10, to: 20 },
);
expect(result.x).toStrictEqual(
Float64Array.from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]),
);
expect(result.y).toStrictEqual(
Float64Array.from([10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]),
);
});

test('xyCheck non-linear x', () => {
const xs = [];
const ys = [];
for (let i = 0; i < 11; i++) {
xs.push(i * 1.2 ** i);
ys.push(i);
}
const result = xyReduceNonContinuous(
{ x: xs, y: ys },
{ maxApproximateNbPoints: 5 },
);
expect(result.y).toStrictEqual([5, 7, 8, 10]);
});

test('xyCheck extreme non-linear x', () => {
const xs = [];
const ys = [];
for (let i = 0; i < 11; i++) {
xs.push(i * 2 ** i);
ys.push(i);
}
const result = xyReduceNonContinuous(
{ x: xs, y: ys },
{ maxApproximateNbPoints: 5 },
);
expect(result).toStrictEqual({ x: [0, 4608, 10240], y: [8, 9, 10] });
});

test('xyReduceNonContinuous with zones enough points', () => {
const result = xyReduceNonContinuous(
{ x, y },
{
maxApproximateNbPoints: 5,
zones: [
{ from: 0, to: 1 },
{ from: 5, to: 7 },
],
},
);

expect(result).toStrictEqual({
x: new Float64Array([0, 1, 5, 6, 7]),
y: new Float64Array([0, 1, 5, 4, 3]),
});
});

test('xyReduceNonContinuous with zones not enough points edge cases', () => {
const result = xyReduceNonContinuous(
{ x, y },
{
maxApproximateNbPoints: 3,
zones: [
{ from: 0, to: 1 },
{ from: 5, to: 8 },
],
},
);
expect(result).toStrictEqual({ x: [0, 1, 5], y: [0, 1, 5] });
});

test('xyReduceNonContinuous with zones not enough points', () => {
const result = xyReduceNonContinuous(
{ x, y },
{
maxApproximateNbPoints: 4,
zones: [
{ from: 0, to: 1 },
{ from: 5, to: 8 },
],
},
);
// the second zone will have only one point because deltaX is 3.3333
expect(result).toStrictEqual({ x: [0, 1, 5], y: [0, 1, 5] });
});

test('xyReduceNonContinuous with one zone not enough points', () => {
const result = xyReduceNonContinuous(
{ x, y },
{
maxApproximateNbPoints: 4,
zones: [
{ from: -1, to: -1 },
{ from: 3, to: 8 },
],
},
);
expect(result).toStrictEqual({ x: [3, 7], y: [5, 3] });
});

test('Large data with zones', () => {
const x2 = [];
const y2 = [];
for (let i = 0; i < 5000001; i++) {
x2.push(i);
y2.push(i);
}
const result = xyReduceNonContinuous(
{ x: x2, y: y2 },
{
maxApproximateNbPoints: 6,
zones: [
{ from: 0, to: 1000 },
{ from: 1000000, to: 1001000 },
],
},
);
expect(result).toStrictEqual({ x: [0, 1000000], y: [1000, 1001000] });
});
3 changes: 2 additions & 1 deletion src/xy/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export * from './xyMinYPoint';
export * from './xyPeakInfo';
export * from './xyRealMaxYPoint';
export * from './xyRealMinYPoint';
export * from './xyReduce';
export { xyReduce } from './xyReduce';
export * from './xyReduceNonContinuous';
export * from './xyRolling';
export * from './xySetYValue';
export * from './xySortX';
Expand Down
66 changes: 36 additions & 30 deletions src/xy/xyReduce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import { xyCheck } from './xyCheck';
interface InternalZone {
from: number;
to: number;
fromIndex?: number;
toIndex?: number;
nbPoints?: number;
fromIndex: number;
toIndex: number;
nbPoints: number;
}

export interface XYReduceOptions {
Expand Down Expand Up @@ -52,12 +52,14 @@ export interface XYReduceOptions {
* You should rather use ml-xy-equally-spaced to make further processing
* @param data - Object that contains property x (an ordered increasing array) and y (an array)
* @param options - options
* @returns Object with x and y arrays
*/
export function xyReduce(
data: DataXY,
options: XYReduceOptions = {},
): DataXY<DoubleArray> {
xyCheck(data);
// todo we should check that the single point is really in the range and the zones
if (data.x.length < 2) {
return {
x: Float64Array.from(data.x),
Expand All @@ -76,22 +78,8 @@ export function xyReduce(
zones = zonesNormalize(zones, { from, to });
if (zones.length === 0) zones = [{ from, to }]; // we take everything

// for each zone we should know the first index, the last index and the number of points
const internalZones: InternalZone[] = zones;
let totalPoints = 0;
for (const zone of internalZones) {
zone.fromIndex = xFindClosestIndex(x, zone.from);
zone.toIndex = xFindClosestIndex(x, zone.to);
if (zone.fromIndex > 0 && x[zone.fromIndex] > zone.from) {
zone.fromIndex--;
}
if (zone.toIndex < x.length - 1 && x[zone.toIndex] < zone.to) {
zone.toIndex++;
}
const { internalZones, totalPoints } = getInternalZones(zones, x);

zone.nbPoints = zone.toIndex - zone.fromIndex + 1;
totalPoints += zone.nbPoints;
}
// we calculate the number of points per zone that we should keep
if (totalPoints <= nbPoints) {
return notEnoughPoints(x, y, internalZones, totalPoints);
Expand All @@ -101,7 +89,7 @@ export function xyReduce(
let currentTotal = 0;
for (let i = 0; i < internalZones.length - 1; i++) {
const zone = internalZones[i];
zone.nbPoints = Math.round((zone.nbPoints as number) * ratio);
zone.nbPoints = Math.round(zone.nbPoints * ratio);
currentTotal += zone.nbPoints;
}
(internalZones.at(-1) as InternalZone).nbPoints = nbPoints - currentTotal;
Expand All @@ -110,11 +98,7 @@ export function xyReduce(
const newY: number[] = [];
for (const zone of internalZones) {
if (!zone.nbPoints) continue;
appendFromTo(
zone.fromIndex as number,
zone.toIndex as number,
zone.nbPoints,
);
appendFromTo(zone.fromIndex, zone.toIndex, zone.nbPoints);
}
return { x: newX, y: newY };

Expand Down Expand Up @@ -189,7 +173,7 @@ export function xyReduce(
}
}

function notEnoughPoints(
export function notEnoughPoints(
x: NumberArray,
y: NumberArray,
internalZones: InternalZone[],
Expand All @@ -199,11 +183,7 @@ function notEnoughPoints(
const newY = new Float64Array(totalPoints);
let index = 0;
for (const zone of internalZones) {
for (
let i = zone.fromIndex as number;
i < (zone.toIndex as number) + 1;
i++
) {
for (let i = zone.fromIndex; i < zone.toIndex + 1; i++) {
newX[index] = x[i];
newY[index] = y[i];
index++;
Expand All @@ -214,3 +194,29 @@ function notEnoughPoints(
y: newY,
};
}

export function getInternalZones(zones: FromTo[], x: NumberArray) {
// for each zone we should know the first index, the last index and the number of points
const internalZones: InternalZone[] = [];
let totalPoints = 0;
for (const zone of zones) {
let fromIndex = xFindClosestIndex(x, zone.from);
let toIndex = xFindClosestIndex(x, zone.to);
if (fromIndex > 0 && x[fromIndex] > zone.from) {
fromIndex--;
}
if (toIndex < x.length - 1 && x[toIndex] < zone.to) {
toIndex++;
}
const nbPoints = toIndex - fromIndex + 1;
internalZones.push({
from: zone.from,
to: zone.to,
fromIndex,
toIndex,
nbPoints,
});
totalPoints += nbPoints;
}
return { internalZones, totalPoints };
}
Loading

0 comments on commit 3d0cb74

Please sign in to comment.