ml-spectra-fitting
Version:
Fit spectra using gaussian or lorentzian
248 lines (228 loc) • 5.64 kB
text/typescript
import type { DataXY } from 'cheminfo-types';
import { generateSpectrum } from 'spectrum-generator';
import { describe, expect, it } from 'vitest';
import { optimize } from '../index.ts';
import type { Peak } from '../index.ts';
const nbPoints = 31;
const xFactor = 0.1;
const x = new Float64Array(nbPoints);
for (let i = 0; i < nbPoints; i++) {
x[i] = (i - nbPoints / 2) * xFactor;
}
describe('Optimize sum of Lorentzians', () => {
const peaks = [
{ x: -0.5, y: 1, shape: { kind: 'lorentzian' as const, fwhm: 0.05 } },
{ x: 0.5, y: 1, shape: { kind: 'lorentzian' as const, fwhm: 0.05 } },
];
const data: DataXY = generateSpectrum(peaks, {
generator: {
from: -5,
to: 5,
nbPoints: 1001,
shape: {
kind: 'lorentzian',
},
},
});
it('positive maxima peaks', () => {
const initialPeaks: Peak[] = [
{
x: -0.52,
y: 0.9,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
{
x: 0.52,
y: 0.9,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
];
const result = optimize(data, initialPeaks);
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(peaks[i], 3);
}
});
it('shifted baseline up by two', () => {
const shiftedPeaks = structuredClone(peaks);
for (const shiftedPeak of shiftedPeaks) {
shiftedPeak.y = shiftedPeak.y + 2;
}
const yShiftedData = {
x: data.x,
y: data.y.map((el: number) => el + 2),
};
const result = optimize(yShiftedData, [
{
x: -0.52,
y: 2.9,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
{
x: 0.52,
y: 2.9,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
]);
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(shiftedPeaks[i], 3);
}
});
it('negative maxima peaks', () => {
const shiftedPeaks = structuredClone(peaks);
for (const shiftedPeak of shiftedPeaks) {
shiftedPeak.y = shiftedPeak.y - 2;
}
const yShiftedPeaks = [
{
x: -0.52,
y: -1,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
{
x: 0.52,
y: -1,
shape: { kind: 'lorentzian' as const, fwhm: 0.08 },
},
];
const yShiftedData = {
x: data.x.slice(),
y: data.y.map((el: number) => el - 2),
};
const result = optimize(yShiftedData, yShiftedPeaks);
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(shiftedPeaks[i], 3);
}
});
});
describe('Optimize sum of Gaussians', () => {
const peaks = [
{ x: -0.5, y: 1, shape: { kind: 'gaussian' as const, fwhm: 0.05 } },
{ x: 0.5, y: 1, shape: { kind: 'gaussian' as const, fwhm: 0.05 } },
];
const data: DataXY = generateSpectrum(peaks, {
generator: {
from: -5,
to: 5,
nbPoints: 1001,
shape: {
kind: 'gaussian',
},
},
});
it('positive maxima peaks', () => {
const peakList: Peak[] = [
{
x: -0.52,
y: 0.9,
shape: { kind: 'gaussian' as const, fwhm: 0.08 },
},
{
x: 0.52,
y: 0.9,
shape: { kind: 'gaussian' as const, fwhm: 0.08 },
},
];
const result = optimize(data, peakList);
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(peaks[i], 3);
}
});
it('negative maxima peaks', () => {
const shiftedPeaks = structuredClone(peaks);
for (const shiftedPeak of shiftedPeaks) {
shiftedPeak.y = shiftedPeak.y - 2;
}
const yShiftedData = {
x: data.x.slice(),
y: data.y.map((el: number) => el - 2),
};
const result = optimize(
yShiftedData,
[
{
x: -0.52,
y: -1,
shape: { kind: 'gaussian' as const, fwhm: 0.08 },
},
{
x: 0.52,
y: -1,
shape: { kind: 'gaussian' as const, fwhm: 0.08 },
},
],
{
optimization: {
kind: 'lm',
options: { maxIterations: 500, damping: 0.5, errorTolerance: 1e-8 },
},
},
);
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(shiftedPeaks[i], 3);
}
});
});
describe('Sum of Pseudo Voigts', () => {
const peaks = [
{
x: -0.5,
y: 0.001,
shape: {
kind: 'pseudoVoigt' as const,
fwhm: 0.31,
mu: 0.5,
},
},
{
x: 0.5,
y: 0.001,
shape: {
kind: 'pseudoVoigt' as const,
fwhm: 0.31,
mu: 0.5,
},
},
];
// in order to correctly determine the mu we need to predict a huge width
const data: DataXY = generateSpectrum(peaks, {
generator: {
from: -50,
to: 50,
nbPoints: 1001,
shape: {
kind: 'pseudoVoigt',
},
},
});
it('positive maxima peaks', () => {
const peakList: Peak[] = [
{
x: -0.3,
y: 0.0009,
shape: {
kind: 'pseudoVoigt' as const,
fwhm: 0.29,
mu: 0.52,
},
},
{
x: 0.3,
y: 0.0009,
shape: {
kind: 'pseudoVoigt' as const,
mu: 0.52,
fwhm: 0.29,
},
},
];
const result = optimize(data, peakList, {
optimization: {
kind: 'lm',
options: { maxIterations: 100, damping: 0.5, errorTolerance: 1e-8 },
},
});
for (let i = 0; i < 2; i++) {
expect(result.peaks[i]).toMatchCloseTo(peaks[i], 3);
}
});
});