Skip to content

Commit bd9dc3e

Browse files
authored
fix(xNoiseSanplot): use analytical expression for Raylegh inverse CDF (#289)
* fix(xNoiseSanplot): use analytical expression for Raylegh inverse cumulative distribution * chore: reverse the use of callback for sort descending * refactor: ensure typed array use * refactor: avoid use let * chore: add default values in tsdocs * chore: test scaledFactor options * refactor: enhance xNoiseSanPlot functionality and improve median calculation
1 parent 28c5a24 commit bd9dc3e

File tree

5 files changed

+169
-217
lines changed

5 files changed

+169
-217
lines changed

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,6 @@
108108
"fft.js": "^4.0.4",
109109
"is-any-array": "^2.0.1",
110110
"ml-matrix": "^6.12.0",
111-
"ml-xsadd": "^3.0.1",
112-
"spline-interpolator": "^1.0.0"
111+
"ml-xsadd": "^3.0.1"
113112
}
114113
}

src/x/__tests__/xNoiseSanPlot.test.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1021,10 +1021,29 @@ test('get noise level', () => {
10211021
],
10221022
};
10231023

1024-
const noise = xNoiseSanPlot(data.re, { magnitudeMode: false });
1024+
const noise = xNoiseSanPlot(data.re, { fixOffset: true });
10251025
const noiseFromMagnitude = xNoiseSanPlot(reimAbsolute(data), {
10261026
magnitudeMode: true,
10271027
});
1028-
expect(noise.snr).toBeCloseTo(56.8, 1);
1029-
expect(noiseFromMagnitude.snr).toBeCloseTo(46.4, 1);
1028+
expect(noise.snr).toBeCloseTo(56.85, 1);
1029+
expect(noiseFromMagnitude.snr).toBeCloseTo(40.2, 1);
1030+
1031+
const noiseScaled = xNoiseSanPlot(data.re, {
1032+
scaleFactor: 2,
1033+
});
1034+
// the positive and negative noise level should be twice
1035+
expect(noiseScaled.positive).toBeCloseTo(noise.positive * 2, 0);
1036+
expect(noiseScaled.negative).toBeCloseTo(noise.negative * 2, 0);
1037+
//the signal to noise ratio should the same
1038+
expect(noiseScaled.snr).toBeCloseTo(noise.snr, 1);
1039+
//use a mask to ignore the bigger peak
1040+
const length = data.re.length;
1041+
const mask = Float64Array.from({ length }).fill(0);
1042+
mask.set(Float64Array.from({ length: 21 }).fill(1), length - 21);
1043+
const noiseWithoutBigPeaks = xNoiseSanPlot(data.re, {
1044+
mask,
1045+
fixOffset: true,
1046+
});
1047+
//the SNR should be less because the biggest peak is not present.
1048+
expect(noiseWithoutBigPeaks.snr).toBeLessThan(noise.snr);
10301049
});
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
import { simpleNormInvNumber } from '../simpleNormInv';
4+
5+
describe('simpleNormInvNumber', () => {
6+
it('should handle normal mode correctly', () => {
7+
const result = simpleNormInvNumber(0.25);
8+
expect(result).toBeCloseTo(-0.6744897495, 4);
9+
const result2 = simpleNormInvNumber(0.025);
10+
expect(result2).toBeCloseTo(-1.96, 4);
11+
});
12+
13+
it('should handle magnitude mode correctly', () => {
14+
const result = simpleNormInvNumber(0.9, { magnitudeMode: true });
15+
// value take from Matlab raylinv function
16+
// https://www.mathworks.com/help/stats/raylinv.html
17+
expect(result).toBeCloseTo(-2.146, 4);
18+
});
19+
});

src/x/utils/simpleNormInv.ts

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import type { NumberArray } from 'cheminfo-types';
2+
3+
import erfcinv from './erfcinv';
4+
5+
interface SimpleNormInvOptions {
6+
/**
7+
* If true the returns values will be calculated from the Rayleigh inverse cumulative distribution.
8+
* it returns
9+
* @default false
10+
*/
11+
magnitudeMode?: boolean;
12+
}
13+
14+
/**
15+
* Applies a simple normalization inverse transformation to the input data.
16+
* @param data - The input array of numbers to be transformed.
17+
* @param options - Optional parameters for the transformation.
18+
* @returns A new Float64Array containing the transformed data.
19+
*/
20+
export function simpleNormInv(
21+
data: NumberArray,
22+
options: SimpleNormInvOptions = {},
23+
): Float64Array {
24+
const { magnitudeMode = false } = options;
25+
26+
const result = new Float64Array(data.length);
27+
if (magnitudeMode) {
28+
for (let i = 0; i < result.length; i++) {
29+
result[i] = -Math.sqrt(-2 * Math.log(1 - data[i]));
30+
}
31+
} else {
32+
for (let i = 0; i < result.length; i++) {
33+
result[i] = -1 * Math.SQRT2 * erfcinv(2 * data[i]);
34+
}
35+
}
36+
return result;
37+
}
38+
39+
/**
40+
* Convenience wrapper for single number processing by simpleNormInv function.
41+
* @param data - The number to be normalized.
42+
* @param options - The options for the normalization process.
43+
* @returns The normalized number.
44+
*/
45+
export function simpleNormInvNumber(
46+
data: number,
47+
options: SimpleNormInvOptions = {},
48+
): number {
49+
return simpleNormInv([data], options)[0];
50+
}

0 commit comments

Comments
 (0)