Skip to content

Commit 4e7848b

Browse files
committed
feat: add optimizePeaksWithLogs to be able to debug
1 parent 9b8e0b6 commit 4e7848b

File tree

4 files changed

+171
-65
lines changed

4 files changed

+171
-65
lines changed

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export * from './gsd';
22
export * from './post/optimizePeaks';
3+
export * from './post/optimizePeaksWithLogs';
34
export * from './post/joinBroadPeaks';
45
export * from './post/broadenPeaks';
56
export * from './utils/appendShapeAndFWHM';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { toMatchCloseTo } from 'jest-matcher-deep-close-to';
2+
import { generateSpectrum } from 'spectrum-generator';
3+
4+
import { optimizePeaksWithLogs } from '../optimizePeaksWithLogs';
5+
6+
expect.extend({ toMatchCloseTo });
7+
8+
describe('optimizePeaksWithLogs', () => {
9+
it('Should throw because execution time is over timeout', () => {
10+
const peaks = [{ x: 0, y: 1, width: 0.12 }];
11+
12+
const data = generateSpectrum(peaks, {
13+
generator: {
14+
from: -0.5,
15+
to: 0.5,
16+
nbPoints: 101,
17+
shape: {
18+
kind: 'gaussian',
19+
},
20+
},
21+
});
22+
23+
let result = optimizePeaksWithLogs(data, [
24+
{
25+
x: 0.01,
26+
y: 0.9,
27+
width: 0.11,
28+
},
29+
]);
30+
expect(result.logs).toMatchObject([
31+
{
32+
iterations: 100,
33+
error: 0.000017852930772995625,
34+
parameters: { kind: 'lm', options: { timeout: 10 } },
35+
message: 'optimization successful',
36+
groupSize: 1,
37+
},
38+
]);
39+
expect(result.optimizedPeaks).toMatchCloseTo([
40+
{
41+
x: 0,
42+
y: 1,
43+
width: 0.12,
44+
shape: {
45+
kind: 'gaussian',
46+
fwhm: 0.14128970668640126,
47+
},
48+
},
49+
]);
50+
51+
const options = {
52+
optimization: {
53+
kind: 'lm' as const,
54+
options: {
55+
timeout: 0,
56+
},
57+
},
58+
};
59+
expect(() =>
60+
optimizePeaksWithLogs(data, [{ x: 0.1, y: 0.9, width: 0.11 }], options),
61+
).toThrow('The execution time is over to 0 seconds');
62+
});
63+
});

src/post/optimizePeaks.ts

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import type { DataXY, PeakXYWidth } from 'cheminfo-types';
2-
import { getShape1D, Shape1D } from 'ml-peak-shape-generator';
3-
import { optimize } from 'ml-spectra-fitting';
2+
import { Shape1D } from 'ml-peak-shape-generator';
43
import type { OptimizationOptions } from 'ml-spectra-fitting';
5-
import { xGetFromToIndex } from 'ml-spectra-processing';
64

75
import { GSDPeakOptimized } from '../GSDPeakOptimized';
8-
import { appendShapeAndFWHM } from '../utils/appendShapeAndFWHM';
9-
import { groupPeaks } from '../utils/groupPeaks';
6+
7+
import { optimizePeaksWithLogs } from './optimizePeaksWithLogs';
108

119
export interface OptimizePeaksOptions {
1210
/**
@@ -41,64 +39,5 @@ export function optimizePeaks(
4139
peakList: PeakXYWidth[],
4240
options: OptimizePeaksOptions = {},
4341
): GSDPeakOptimized[] {
44-
const {
45-
shape = { kind: 'gaussian' },
46-
groupingFactor = 1,
47-
factorLimits = 2,
48-
optimization = {
49-
kind: 'lm',
50-
options: {
51-
timeout: 10,
52-
},
53-
},
54-
}: OptimizePeaksOptions = options;
55-
56-
/*
57-
The optimization algorithm will take some group of peaks.
58-
We can not simply optimize everything because there would be too many variables to optimize
59-
and it would be too time consuming.
60-
*/
61-
let groups = groupPeaks(peakList, { factor: groupingFactor });
62-
63-
let results: GSDPeakOptimized[] = [];
64-
65-
groups.forEach((peakGroup) => {
66-
// In order to make optimization we will add fwhm and shape on all the peaks
67-
const peaks = appendShapeAndFWHM(peakGroup, { shape });
68-
69-
const firstPeak = peaks[0];
70-
const lastPeak = peaks[peaks.length - 1];
71-
72-
const from = firstPeak.x - firstPeak.width * factorLimits;
73-
const to = lastPeak.x + lastPeak.width * factorLimits;
74-
const { fromIndex, toIndex } = xGetFromToIndex(data.x, { from, to });
75-
76-
const x =
77-
data.x instanceof Float64Array
78-
? data.x.subarray(fromIndex, toIndex)
79-
: data.x.slice(fromIndex, toIndex);
80-
const y =
81-
data.y instanceof Float64Array
82-
? data.y.subarray(fromIndex, toIndex)
83-
: data.y.slice(fromIndex, toIndex);
84-
85-
if (x.length > 5) {
86-
let { peaks: optimizedPeaks } = optimize({ x, y }, peaks, {
87-
shape,
88-
optimization,
89-
});
90-
for (let i = 0; i < peaks.length; i++) {
91-
results.push({
92-
...optimizedPeaks[i],
93-
width: getShape1D(peaks[i].shape).fwhmToWidth(
94-
optimizedPeaks[i].shape.fwhm,
95-
),
96-
});
97-
}
98-
} else {
99-
results = results.concat(peaks);
100-
}
101-
});
102-
103-
return results;
42+
return optimizePeaksWithLogs(data, peakList, options).optimizedPeaks;
10443
}

src/post/optimizePeaksWithLogs.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import type { DataXY, PeakXYWidth } from 'cheminfo-types';
2+
import { getShape1D } from 'ml-peak-shape-generator';
3+
import { optimize } from 'ml-spectra-fitting';
4+
import { xGetFromToIndex } from 'ml-spectra-processing';
5+
6+
import { GSDPeakOptimized } from '../GSDPeakOptimized';
7+
import { appendShapeAndFWHM } from '../utils/appendShapeAndFWHM';
8+
import { groupPeaks } from '../utils/groupPeaks';
9+
10+
import { OptimizePeaksOptions } from './optimizePeaks';
11+
12+
/**
13+
* Optimize the position (x), max intensity (y), full width at half maximum (fwhm)
14+
* and the ratio of gaussian contribution (mu) if it's required. It currently supports three kind of shapes: gaussian, lorentzian and pseudovoigt
15+
* @param data - An object containing the x and y data to be fitted.
16+
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
17+
*/
18+
export function optimizePeaksWithLogs(
19+
data: DataXY,
20+
peakList: PeakXYWidth[],
21+
options: OptimizePeaksOptions = {},
22+
): { logs: any[]; optimizedPeaks: GSDPeakOptimized[] } {
23+
const {
24+
shape = { kind: 'gaussian' },
25+
groupingFactor = 1,
26+
factorLimits = 2,
27+
optimization = {
28+
kind: 'lm',
29+
options: {
30+
timeout: 10,
31+
},
32+
},
33+
}: OptimizePeaksOptions = options;
34+
35+
/*
36+
The optimization algorithm will take some group of peaks.
37+
We can not simply optimize everything because there would be too many variables to optimize
38+
and it would be too time consuming.
39+
*/
40+
let groups = groupPeaks(peakList, { factor: groupingFactor });
41+
let logs: any[] = [];
42+
let results: GSDPeakOptimized[] = [];
43+
44+
groups.forEach((peakGroup) => {
45+
const start = Date.now();
46+
// In order to make optimization we will add fwhm and shape on all the peaks
47+
const peaks = appendShapeAndFWHM(peakGroup, { shape });
48+
49+
const firstPeak = peaks[0];
50+
const lastPeak = peaks[peaks.length - 1];
51+
52+
const from = firstPeak.x - firstPeak.width * factorLimits;
53+
const to = lastPeak.x + lastPeak.width * factorLimits;
54+
const { fromIndex, toIndex } = xGetFromToIndex(data.x, { from, to });
55+
56+
const x =
57+
data.x instanceof Float64Array
58+
? data.x.subarray(fromIndex, toIndex)
59+
: data.x.slice(fromIndex, toIndex);
60+
const y =
61+
data.y instanceof Float64Array
62+
? data.y.subarray(fromIndex, toIndex)
63+
: data.y.slice(fromIndex, toIndex);
64+
65+
if (x.length > 5) {
66+
const {
67+
iterations,
68+
error,
69+
peaks: optimizedPeaks,
70+
} = optimize({ x, y }, peaks, {
71+
shape,
72+
optimization,
73+
});
74+
75+
for (let i = 0; i < peaks.length; i++) {
76+
results.push({
77+
...optimizedPeaks[i],
78+
width: getShape1D(peaks[i].shape).fwhmToWidth(
79+
optimizedPeaks[i].shape.fwhm,
80+
),
81+
});
82+
}
83+
logs.push({
84+
iterations,
85+
error,
86+
parameters: optimization,
87+
message: 'optimization successful',
88+
groupSize: peakGroup.length,
89+
time: `${Date.now() - start}ms`,
90+
});
91+
} else {
92+
results = results.concat(peaks);
93+
logs.push({
94+
iterations: 0,
95+
message: 'x length too small for optimization',
96+
groupSize: peakGroup.length,
97+
time: `${Date.now() - start}ms`,
98+
});
99+
}
100+
});
101+
102+
return { logs, optimizedPeaks: results };
103+
}

0 commit comments

Comments
 (0)