Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

ml-spectra-fitting

Package Overview
Dependencies
Maintainers
4
Versions
33
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ml-spectra-fitting - npm Package Compare versions

Comparing version 3.1.0 to 4.0.0

lib-esm/shapes/getSumOfShapes.d.ts

128

lib-esm/index.d.ts
import { DataXY, DoubleArray } from 'cheminfo-types';
import { Shape1D } from 'ml-peak-shape-generator';
import { Peak1D } from './spectra-fitting';
export interface InitialParameter {
init?: OptimizationParameter;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: OptimizationParameter;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: OptimizationParameter;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: OptimizationParameter;
}
export interface Peak {
x: number;
y: number;
shape?: Shape1D;
parameters?: Record<string, {
init?: number;
min?: number;
max?: number;
gradientDifference?: number;
}>;
}
declare type OptimizationParameter = number | ((peak: Peak) => number);
export interface OptimizationOptions {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: 'lm' | 'levenbergMarquardt';
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
}
export interface OptimizeOptions {
/**
* Kind of shape used for fitting.
**/
shape?: Shape1D;
/**
* options of each parameter to be optimized e.g. For a pseudovoigt shape
* it could have x, y, fwhm and mu properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: Record<string, InitialParameter>;
/**
* The kind and options of the algorithm use to optimize parameters.
*/
optimization?: OptimizationOptions;
}
/**

@@ -8,65 +71,12 @@ * Fits a set of points to the sum of a set of bell functions.

* @param data - An object containing the x and y data to be fitted.
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options.
* @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options for optimize
* @returns - An object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
export declare function optimize(data: DataXY<DoubleArray>, peakList: Peak1D[], options?: {
/**
* kind of shape used for fitting
**/
shape?: Shape1D;
/**
* the kind and options of the algorithm use to optimize parameters
*/
optimization?: {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: string;
/**
* options of each parameter to be optimized e.g. For a gaussian shape
* it could have x, y and width properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: {
/** options for x parameter */
x?: {
/** definition of the starting point of the parameter (the guess),
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on. */
init?: number;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: number;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: number;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: number;
};
};
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
};
}): {
export declare function optimize(data: DataXY<DoubleArray>, peaks: Peak[], options?: OptimizeOptions): {
error: number;
peaks: Peak1D[];
peaks: Peak[];
iterations: number;
};
export {};
//# sourceMappingURL=index.d.ts.map

@@ -1,2 +0,4 @@

import { checkInput } from './util/checkInput';
import { xMinMaxValues } from 'ml-spectra-processing';
import { getSumOfShapes } from './shapes/getSumOfShapes';
import { getInternalPeaks } from './util/internalPeaks/getInternalPeaks';
import { selectMethod } from './util/selectMethod';

@@ -7,59 +9,62 @@ /**

* @param data - An object containing the x and y data to be fitted.
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options.
* @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options for optimize
* @returns - An object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
export function optimize(data, peakList, options = {}) {
if (!options.shape) {
options = { ...options, ...{ shape: { kind: 'gaussian' } } };
export function optimize(data, peaks, options = {}) {
// rescale data
let temp = xMinMaxValues(data.y);
const minMaxY = { ...temp, range: temp.max - temp.min };
const internalPeaks = getInternalPeaks(peaks, minMaxY, options);
// need to rescale what is related to Y
let normalizedY = new Float64Array(data.y.length);
for (let i = 0; i < data.y.length; i++) {
normalizedY[i] = (data.y[i] - minMaxY.min) / minMaxY.range;
}
const { y, x, maxY, minY, peaks, paramsFunc, optimization } = checkInput(data, peakList, options);
let parameters = optimization.parameters;
let nbShapes = peaks.length;
let parameterKey = Object.keys(parameters);
let nbParams = nbShapes * parameterKey.length;
let pMin = new Float64Array(nbParams);
let pMax = new Float64Array(nbParams);
let pInit = new Float64Array(nbParams);
let gradientDifference = new Float64Array(nbParams);
for (let i = 0; i < nbShapes; i++) {
let peak = peaks[i];
for (let k = 0; k < parameterKey.length; k++) {
let key = parameterKey[k];
let init = parameters[key].init;
let min = parameters[key].min;
let max = parameters[key].max;
let gradientDifferenceValue = parameters[key].gradientDifference;
pInit[i + k * nbShapes] = init[i % init.length](peak);
pMin[i + k * nbShapes] = min[i % min.length](peak);
pMax[i + k * nbShapes] = max[i % max.length](peak);
gradientDifference[i + k * nbShapes] =
gradientDifferenceValue[i % gradientDifferenceValue.length](peak);
const nbParams = internalPeaks[internalPeaks.length - 1].toIndex + 1;
const minValues = new Float64Array(nbParams);
const maxValues = new Float64Array(nbParams);
const initialValues = new Float64Array(nbParams);
const gradientDifferences = new Float64Array(nbParams);
let index = 0;
for (const peak of internalPeaks) {
for (let i = 0; i < peak.parameters.length; i++) {
minValues[index] = peak.propertiesValues.min[i];
maxValues[index] = peak.propertiesValues.max[i];
initialValues[index] = peak.propertiesValues.init[i];
gradientDifferences[index] = peak.propertiesValues.gradientDifference[i];
index++;
}
}
let { algorithm, optimizationOptions } = selectMethod(optimization);
optimizationOptions.minValues = pMin;
optimizationOptions.maxValues = pMax;
optimizationOptions.initialValues = pInit;
optimizationOptions.gradientDifference = gradientDifference;
let pFit = algorithm({ x, y }, paramsFunc, optimizationOptions);
let { parameterError: error, iterations } = pFit;
let result = { error, iterations, peaks };
for (let i = 0; i < nbShapes; i++) {
for (let k = 0; k < parameterKey.length; k++) {
const key = parameterKey[k];
const value = pFit.parameterValues[i + k * nbShapes];
if (key === 'x' || key === 'fwhm') {
peaks[i][key] = value;
}
else if (key === 'y') {
peaks[i][key] = value * maxY + minY;
}
else {
peaks[i].shape[key] = value;
}
let { algorithm, optimizationOptions } = selectMethod(options.optimization);
let sumOfShapes = getSumOfShapes(internalPeaks);
let fitted = algorithm({ x: data.x, y: normalizedY }, sumOfShapes, {
minValues,
maxValues,
initialValues,
gradientDifference: gradientDifferences,
...optimizationOptions,
});
const fittedValues = fitted.parameterValues;
let newPeaks = [];
for (let peak of internalPeaks) {
const newPeak = {
x: 0,
y: 0,
shape: peak.shape,
};
newPeak.x = fittedValues[peak.fromIndex];
newPeak.y = fittedValues[peak.fromIndex + 1] * minMaxY.range + minMaxY.min;
for (let i = 2; i < peak.parameters.length; i++) {
//@ts-expect-error should be fixed once
newPeak.shape[peak.parameters[i]] = fittedValues[peak.fromIndex + i];
}
newPeaks.push(newPeak);
}
return result;
return {
error: fitted.parameterError,
iterations: fitted.iterations,
peaks: newPeaks,
};
}
//# sourceMappingURL=index.js.map
import { levenbergMarquardt } from 'ml-levenberg-marquardt';
import { OptimizationOptions } from '../spectra-fitting';
import { OptimizationOptions } from '../index';
/** Algorithm to select the method.

@@ -9,4 +9,9 @@ * @param optimizationOptions - Optimization options

algorithm: typeof levenbergMarquardt;
optimizationOptions: any;
optimizationOptions: {
timeout?: number | undefined;
damping: number;
maxIterations: number;
errorTolerance: number;
};
};
//# sourceMappingURL=selectMethod.d.ts.map
import { levenbergMarquardt } from 'ml-levenberg-marquardt';
const LEVENBERG_MARQUARDT = 1;
/** Algorithm to select the method.

@@ -8,38 +7,19 @@ * @param optimizationOptions - Optimization options

export function selectMethod(optimizationOptions = {}) {
let { kind, options } = optimizationOptions;
kind = getKind(kind);
let { kind = 'lm', options } = optimizationOptions;
switch (kind) {
case LEVENBERG_MARQUARDT:
case 'lm':
case 'levenbergMarquardt':
return {
algorithm: levenbergMarquardt,
optimizationOptions: checkOptions(kind, options),
optimizationOptions: {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
...options,
},
};
default:
throw new Error(`Unknown kind algorithm`);
throw new Error(`Unknown fitting algorithm`);
}
}
function checkOptions(kind, options = {}) {
switch (kind) {
case LEVENBERG_MARQUARDT:
return Object.assign({}, lmOptions, options);
default:
throw new Error(`unknown kind: ${kind}`);
}
}
function getKind(kind) {
if (typeof kind !== 'string')
return kind;
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) {
case 'lm':
case 'levenbergmarquardt':
return LEVENBERG_MARQUARDT;
default:
throw new Error(`Unknown kind algorithm`);
}
}
const lmOptions = {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
};
//# sourceMappingURL=selectMethod.js.map
import { DataXY, DoubleArray } from 'cheminfo-types';
import { Shape1D } from 'ml-peak-shape-generator';
import { Peak1D } from './spectra-fitting';
export interface InitialParameter {
init?: OptimizationParameter;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: OptimizationParameter;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: OptimizationParameter;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: OptimizationParameter;
}
export interface Peak {
x: number;
y: number;
shape?: Shape1D;
parameters?: Record<string, {
init?: number;
min?: number;
max?: number;
gradientDifference?: number;
}>;
}
declare type OptimizationParameter = number | ((peak: Peak) => number);
export interface OptimizationOptions {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: 'lm' | 'levenbergMarquardt';
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
}
export interface OptimizeOptions {
/**
* Kind of shape used for fitting.
**/
shape?: Shape1D;
/**
* options of each parameter to be optimized e.g. For a pseudovoigt shape
* it could have x, y, fwhm and mu properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: Record<string, InitialParameter>;
/**
* The kind and options of the algorithm use to optimize parameters.
*/
optimization?: OptimizationOptions;
}
/**

@@ -8,65 +71,12 @@ * Fits a set of points to the sum of a set of bell functions.

* @param data - An object containing the x and y data to be fitted.
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options.
* @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options for optimize
* @returns - An object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
export declare function optimize(data: DataXY<DoubleArray>, peakList: Peak1D[], options?: {
/**
* kind of shape used for fitting
**/
shape?: Shape1D;
/**
* the kind and options of the algorithm use to optimize parameters
*/
optimization?: {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: string;
/**
* options of each parameter to be optimized e.g. For a gaussian shape
* it could have x, y and width properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: {
/** options for x parameter */
x?: {
/** definition of the starting point of the parameter (the guess),
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on. */
init?: number;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: number;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: number;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: number;
};
};
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
};
}): {
export declare function optimize(data: DataXY<DoubleArray>, peaks: Peak[], options?: OptimizeOptions): {
error: number;
peaks: Peak1D[];
peaks: Peak[];
iterations: number;
};
export {};
//# sourceMappingURL=index.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.optimize = void 0;
const checkInput_1 = require("./util/checkInput");
const ml_spectra_processing_1 = require("ml-spectra-processing");
const getSumOfShapes_1 = require("./shapes/getSumOfShapes");
const getInternalPeaks_1 = require("./util/internalPeaks/getInternalPeaks");
const selectMethod_1 = require("./util/selectMethod");

@@ -10,60 +12,63 @@ /**

* @param data - An object containing the x and y data to be fitted.
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options.
* @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options for optimize
* @returns - An object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.
*/
function optimize(data, peakList, options = {}) {
if (!options.shape) {
options = { ...options, ...{ shape: { kind: 'gaussian' } } };
function optimize(data, peaks, options = {}) {
// rescale data
let temp = (0, ml_spectra_processing_1.xMinMaxValues)(data.y);
const minMaxY = { ...temp, range: temp.max - temp.min };
const internalPeaks = (0, getInternalPeaks_1.getInternalPeaks)(peaks, minMaxY, options);
// need to rescale what is related to Y
let normalizedY = new Float64Array(data.y.length);
for (let i = 0; i < data.y.length; i++) {
normalizedY[i] = (data.y[i] - minMaxY.min) / minMaxY.range;
}
const { y, x, maxY, minY, peaks, paramsFunc, optimization } = (0, checkInput_1.checkInput)(data, peakList, options);
let parameters = optimization.parameters;
let nbShapes = peaks.length;
let parameterKey = Object.keys(parameters);
let nbParams = nbShapes * parameterKey.length;
let pMin = new Float64Array(nbParams);
let pMax = new Float64Array(nbParams);
let pInit = new Float64Array(nbParams);
let gradientDifference = new Float64Array(nbParams);
for (let i = 0; i < nbShapes; i++) {
let peak = peaks[i];
for (let k = 0; k < parameterKey.length; k++) {
let key = parameterKey[k];
let init = parameters[key].init;
let min = parameters[key].min;
let max = parameters[key].max;
let gradientDifferenceValue = parameters[key].gradientDifference;
pInit[i + k * nbShapes] = init[i % init.length](peak);
pMin[i + k * nbShapes] = min[i % min.length](peak);
pMax[i + k * nbShapes] = max[i % max.length](peak);
gradientDifference[i + k * nbShapes] =
gradientDifferenceValue[i % gradientDifferenceValue.length](peak);
const nbParams = internalPeaks[internalPeaks.length - 1].toIndex + 1;
const minValues = new Float64Array(nbParams);
const maxValues = new Float64Array(nbParams);
const initialValues = new Float64Array(nbParams);
const gradientDifferences = new Float64Array(nbParams);
let index = 0;
for (const peak of internalPeaks) {
for (let i = 0; i < peak.parameters.length; i++) {
minValues[index] = peak.propertiesValues.min[i];
maxValues[index] = peak.propertiesValues.max[i];
initialValues[index] = peak.propertiesValues.init[i];
gradientDifferences[index] = peak.propertiesValues.gradientDifference[i];
index++;
}
}
let { algorithm, optimizationOptions } = (0, selectMethod_1.selectMethod)(optimization);
optimizationOptions.minValues = pMin;
optimizationOptions.maxValues = pMax;
optimizationOptions.initialValues = pInit;
optimizationOptions.gradientDifference = gradientDifference;
let pFit = algorithm({ x, y }, paramsFunc, optimizationOptions);
let { parameterError: error, iterations } = pFit;
let result = { error, iterations, peaks };
for (let i = 0; i < nbShapes; i++) {
for (let k = 0; k < parameterKey.length; k++) {
const key = parameterKey[k];
const value = pFit.parameterValues[i + k * nbShapes];
if (key === 'x' || key === 'fwhm') {
peaks[i][key] = value;
}
else if (key === 'y') {
peaks[i][key] = value * maxY + minY;
}
else {
peaks[i].shape[key] = value;
}
let { algorithm, optimizationOptions } = (0, selectMethod_1.selectMethod)(options.optimization);
let sumOfShapes = (0, getSumOfShapes_1.getSumOfShapes)(internalPeaks);
let fitted = algorithm({ x: data.x, y: normalizedY }, sumOfShapes, {
minValues,
maxValues,
initialValues,
gradientDifference: gradientDifferences,
...optimizationOptions,
});
const fittedValues = fitted.parameterValues;
let newPeaks = [];
for (let peak of internalPeaks) {
const newPeak = {
x: 0,
y: 0,
shape: peak.shape,
};
newPeak.x = fittedValues[peak.fromIndex];
newPeak.y = fittedValues[peak.fromIndex + 1] * minMaxY.range + minMaxY.min;
for (let i = 2; i < peak.parameters.length; i++) {
//@ts-expect-error should be fixed once
newPeak.shape[peak.parameters[i]] = fittedValues[peak.fromIndex + i];
}
newPeaks.push(newPeak);
}
return result;
return {
error: fitted.parameterError,
iterations: fitted.iterations,
peaks: newPeaks,
};
}
exports.optimize = optimize;
//# sourceMappingURL=index.js.map
import { levenbergMarquardt } from 'ml-levenberg-marquardt';
import { OptimizationOptions } from '../spectra-fitting';
import { OptimizationOptions } from '../index';
/** Algorithm to select the method.

@@ -9,4 +9,9 @@ * @param optimizationOptions - Optimization options

algorithm: typeof levenbergMarquardt;
optimizationOptions: any;
optimizationOptions: {
timeout?: number | undefined;
damping: number;
maxIterations: number;
errorTolerance: number;
};
};
//# sourceMappingURL=selectMethod.d.ts.map

@@ -5,3 +5,2 @@ "use strict";

const ml_levenberg_marquardt_1 = require("ml-levenberg-marquardt");
const LEVENBERG_MARQUARDT = 1;
/** Algorithm to select the method.

@@ -12,39 +11,20 @@ * @param optimizationOptions - Optimization options

function selectMethod(optimizationOptions = {}) {
let { kind, options } = optimizationOptions;
kind = getKind(kind);
let { kind = 'lm', options } = optimizationOptions;
switch (kind) {
case LEVENBERG_MARQUARDT:
case 'lm':
case 'levenbergMarquardt':
return {
algorithm: ml_levenberg_marquardt_1.levenbergMarquardt,
optimizationOptions: checkOptions(kind, options),
optimizationOptions: {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
...options,
},
};
default:
throw new Error(`Unknown kind algorithm`);
throw new Error(`Unknown fitting algorithm`);
}
}
exports.selectMethod = selectMethod;
function checkOptions(kind, options = {}) {
switch (kind) {
case LEVENBERG_MARQUARDT:
return Object.assign({}, lmOptions, options);
default:
throw new Error(`unknown kind: ${kind}`);
}
}
function getKind(kind) {
if (typeof kind !== 'string')
return kind;
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) {
case 'lm':
case 'levenbergmarquardt':
return LEVENBERG_MARQUARDT;
default:
throw new Error(`Unknown kind algorithm`);
}
}
const lmOptions = {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
};
//# sourceMappingURL=selectMethod.js.map
{
"name": "ml-spectra-fitting",
"version": "3.1.0",
"version": "4.0.0",
"description": "Fit spectra using gaussian or lorentzian",

@@ -20,6 +20,5 @@ "main": "./lib/index.js",

"prepack": "npm run tsc",
"test": "npm run test-only && npm run eslint && npm run check-types",
"test": "npm run test-only && npm run eslint && npm run check-types && npm run prettier",
"prettier": "prettier --check src",
"prettier-write": "prettier --write src",
"test-coverage": "jest --coverage",
"test-only": "jest --coverage",

@@ -51,22 +50,21 @@ "tsc": "npm run clean && npm run tsc-cjs && npm run tsc-esm",

"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
"@babel/plugin-transform-modules-commonjs": "^7.17.9",
"@babel/preset-typescript": "^7.16.7",
"@types/jest": "^27.4.1",
"cheminfo-build": "^1.1.11",
"eslint": "^8.9.0",
"eslint-config-cheminfo": "^7.2.2",
"eslint-config-cheminfo-typescript": "^10.3.0",
"esm": "^3.2.25",
"jest": "^27.5.1",
"eslint": "^8.14.0",
"eslint-config-cheminfo-typescript": "^10.4.0",
"jest": "^28.0.2",
"jest-matcher-deep-close-to": "^3.0.2",
"prettier": "^2.5.1",
"spectrum-generator": "^7.0.1",
"ts-jest": "^27.1.3",
"typescript": "^4.5.5"
"prettier": "^2.6.2",
"spectrum-generator": "^8.0.1",
"typescript": "^4.6.4"
},
"dependencies": {
"cheminfo-types": "^1.0.0",
"cheminfo-types": "^1.1.0",
"ml-array-max": "^1.2.4",
"ml-levenberg-marquardt": "^4.0.0",
"ml-peak-shape-generator": "^4.1.1"
"ml-levenberg-marquardt": "^4.1.0",
"ml-peak-shape-generator": "^4.1.1",
"ml-spectra-processing": "^11.5.0"
}
}

@@ -18,3 +18,3 @@ # ml-spectra-fitting

| <img src="https://tex.cheminfo.org/?tex=%5Cdelta%20%3D%20%5Cleft(t%20-%20x%5Cright)%5E2%0A"/> | <img src="https://tex.cheminfo.org/?tex=%5Csigma%20%3D%20%5Cfrac%7BFWHM%7D%7B2%5Csqrt%7B2%20%5Ccdot%20Ln(2)%7D%7D"/> | <img src="https://tex.cheminfo.org/?tex=%5Cgamma%3D%5Cleft(FWHM%5Cright)%5E2"/> |
| --------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------- |
| --------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------: | :------------------------------------------------------------------------------ |

@@ -32,49 +32,75 @@ It is a wrapper of [ml-levenberg-marquardt](https://github.com/mljs/levenberg-marquardt)

```js
// import library
import { optimizeSum } from 'ml-spectra-fitting';
import { generateSpectrum } from 'spectrum-generator';
import { optimize } from 'ml-spectra-fitting';
import { SpectrumGenerator } from 'spectrum-generator';
const peaks = [
{ x: 0.5, y: 0.2, fwhm: 0.2 },
{ x: -0.5, y: 0.2, fwhm: 0.3 },
];
const data = generateSpectrum(peaks, { from: -1, to: 1, nbPoints: 41 });
const generator = new SpectrumGenerator({
nbPoints: 101,
from: -1,
to: 1,
});
//the approximate values to be optimized, It could come from a peak picking with ml-gsd
// by default the kind of shape is gaussian;
generator.addPeak({ x: 0.5, y: 0.2 }, { fwhm: 0.2 });
generator.addPeak(
{ x: -0.5, y: 0.2 },
{
shape: {
kind: 'lorentzian',
fwhm: 0.1,
},
},
);
//points to fit {x, y};
let data = generator.getSpectrum();
console.log(JSON.stringify({ x: Array.from(data.x), y: Array.from(data.y) }));
//the approximate values to be optimized, It could coming from a peak picking with ml-gsd
let peaks = [
{
x: -0.5,
y: 0.18,
fwhm: 0.18,
y: 0.22,
shape: {
kind: 'gaussian',
fwhm: 0.25,
},
},
{
x: 0.52,
y: 0.17,
fwhm: 0.37,
y: 0.18,
shape: {
kind: 'gaussian',
fwhm: 0.18,
},
},
];
// the function receive an array of peaks {x, y, fwhm} as a guess
// and returns an array of peaks
// the function receive an array of peak with {x, y, fwhm} as a guess
// and return a list of objects
let fittedParams = optimize(data, peaks, { shape: { kind: 'pseudoVoigt' } });
let fittedPeaks = optimize(data, peaks);
console.log(fittedPeaks);
/**
{
error: 0.010502794375558983,
iterations: 15,
peaks: [
{
x: -0.49999760133593774,
y: 0.1999880261075537,
fwhm: 0.3000369491704072
console.log(fittedParams);
const result = {
error: 0.12361588652854476,
iterations: 100,
peaks: [
{
x: -0.5000014532421942,
y: 0.19995307937326137,
shape: {
kind: 'pseudoVoigt',
fwhm: 0.10007670374735196,
mu: 0.004731136777288483,
},
{
x: 0.5000084944744884,
y: 0.20004144804853427,
fwhm: 0.1999731186595336
}
]
}
*/
},
{
x: 0.5001051783652894,
y: 0.19960010175400406,
shape: {
kind: 'pseudoVoigt',
fwhm: 0.19935932346969124,
mu: 1,
},
},
],
};
```

@@ -99,5 +125,5 @@

{
fwhm: 0.1,
shape: {
kind: 'lorentzian',
fwhm: 0.1,
},

@@ -115,3 +141,6 @@ },

y: 0.22,
fwhm: 0.25,
shape: {
kind: 'gaussian',
fwhm: 0.25,
},
},

@@ -121,3 +150,6 @@ {

y: 0.18,
fwhm: 0.18,
shape: {
kind: 'gaussian',
fwhm: 0.18,
},
},

@@ -128,7 +160,6 @@ ];

// and return a list of objects
let fittedParams = optimize(data, peaks, { shape: { kind: 'pseudovoigt' } });
let fittedParams = optimize(data, peaks, { shape: { kind: 'pseudoVoigt' } });
console.log(fittedParams);
/**
{
const result = {
error: 0.12361588652854476,

@@ -140,4 +171,7 @@ iterations: 100,

y: 0.19995307937326137,
fwhm: 0.10007670374735196,
mu: 0.004731136777288483
shape: {
kind: 'pseudoVoigt',
fwhm: 0.10007670374735196,
mu: 0.004731136777288483,
},
},

@@ -147,8 +181,10 @@ {

y: 0.19960010175400406,
fwhm: 0.19935932346969124,
mu: 1
}
]
}
*/
shape: {
kind: 'pseudoVoigt',
fwhm: 0.19935932346969124,
mu: 1,
},
},
],
};
```

@@ -155,0 +191,0 @@

import { DataXY, DoubleArray } from 'cheminfo-types';
import { Shape1D } from 'ml-peak-shape-generator';
import { xMinMaxValues } from 'ml-spectra-processing';
import { Peak1D } from './spectra-fitting';
import { checkInput } from './util/checkInput';
import { getSumOfShapes } from './shapes/getSumOfShapes';
import { getInternalPeaks } from './util/internalPeaks/getInternalPeaks';
import { selectMethod } from './util/selectMethod';
export interface InitialParameter {
init?: OptimizationParameter;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: OptimizationParameter;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: OptimizationParameter;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: OptimizationParameter;
}
export interface Peak {
x: number;
y: number;
shape?: Shape1D;
parameters?: Record<
string,
{ init?: number; min?: number; max?: number; gradientDifference?: number }
>;
}
type OptimizationParameter = number | ((peak: Peak) => number);
export interface OptimizationOptions {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: 'lm' | 'levenbergMarquardt';
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
}
export interface OptimizeOptions {
/**
* Kind of shape used for fitting.
**/
shape?: Shape1D;
/**
* options of each parameter to be optimized e.g. For a pseudovoigt shape
* it could have x, y, fwhm and mu properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: Record<string, InitialParameter>;
/**
* The kind and options of the algorithm use to optimize parameters.
*/
optimization?: OptimizationOptions;
}
/**

@@ -12,4 +81,4 @@ * Fits a set of points to the sum of a set of bell functions.

* @param data - An object containing the x and y data to be fitted.
* @param peakList - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options.
* @param peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param options - Options for optimize
* @returns - An object with fitting error and the list of optimized parameters { parameters: [ {x, y, width} ], error } if the kind of shape is pseudoVoigt mu parameter is optimized.

@@ -19,124 +88,69 @@ */

data: DataXY<DoubleArray>,
peakList: Peak1D[],
options: {
/**
* kind of shape used for fitting
**/
shape?: Shape1D;
/**
* the kind and options of the algorithm use to optimize parameters
*/
optimization?: {
/**
* kind of algorithm. By default it's levenberg-marquardt
*/
kind?: string;
/**
* options of each parameter to be optimized e.g. For a gaussian shape
* it could have x, y and width properties, each of which could contain init, min, max and gradientDifference, those options will define the guess,
* the min and max value of the parameter (search space) and the step size to approximate the jacobian matrix respectively. Those options could be a number,
* array of numbers, callback, or array of callbacks. Each kind of shape has default parameters so it could be undefined
*/
parameters?: {
/** options for x parameter */
x?: {
/** definition of the starting point of the parameter (the guess),
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the guess of the first peak and so on. */
init?: number;
/** definition of the lower limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the min of the first peak and so on. */
min?: number;
/** definition of the upper limit of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the max of the first peak and so on. */
max?: number;
/** definition of the step size to approximate the jacobian matrix of the parameter,
* if it is a callback the method pass the peak as the unique input, if it is an array the first element define the gradientDifference of the first peak and so on. */
gradientDifference?: number;
};
};
/** options for the specific kind of algorithm */
options?: {
/** maximum time running before break in seconds */
timeout?: number;
/** damping factor
* @default 1.5
*/
damping?: number;
/** number of max iterations
* @default 100
*/
maxIterations?: number;
/** error tolerance
* @default 1e-8
*/
errorTolerance?: number;
};
};
} = {},
peaks: Peak[],
options: OptimizeOptions = {},
): {
error: number;
peaks: Peak1D[];
peaks: Peak[];
iterations: number;
} {
if (!options.shape) {
options = { ...options, ...{ shape: { kind: 'gaussian' } } };
// rescale data
let temp = xMinMaxValues(data.y);
const minMaxY = { ...temp, range: temp.max - temp.min };
const internalPeaks = getInternalPeaks(peaks, minMaxY, options);
// need to rescale what is related to Y
let normalizedY = new Float64Array(data.y.length);
for (let i = 0; i < data.y.length; i++) {
normalizedY[i] = (data.y[i] - minMaxY.min) / minMaxY.range;
}
const { y, x, maxY, minY, peaks, paramsFunc, optimization } = checkInput(
data,
peakList,
options,
);
let parameters = optimization.parameters;
let nbShapes = peaks.length;
let parameterKey = Object.keys(parameters);
let nbParams = nbShapes * parameterKey.length;
let pMin = new Float64Array(nbParams);
let pMax = new Float64Array(nbParams);
let pInit = new Float64Array(nbParams);
let gradientDifference = new Float64Array(nbParams);
for (let i = 0; i < nbShapes; i++) {
let peak = peaks[i];
for (let k = 0; k < parameterKey.length; k++) {
let key = parameterKey[k];
let init = parameters[key].init;
let min = parameters[key].min;
let max = parameters[key].max;
let gradientDifferenceValue = parameters[key].gradientDifference;
pInit[i + k * nbShapes] = init[i % init.length](peak);
pMin[i + k * nbShapes] = min[i % min.length](peak);
pMax[i + k * nbShapes] = max[i % max.length](peak);
gradientDifference[i + k * nbShapes] =
gradientDifferenceValue[i % gradientDifferenceValue.length](peak);
const nbParams = internalPeaks[internalPeaks.length - 1].toIndex + 1;
const minValues = new Float64Array(nbParams);
const maxValues = new Float64Array(nbParams);
const initialValues = new Float64Array(nbParams);
const gradientDifferences = new Float64Array(nbParams);
let index = 0;
for (const peak of internalPeaks) {
for (let i = 0; i < peak.parameters.length; i++) {
minValues[index] = peak.propertiesValues.min[i];
maxValues[index] = peak.propertiesValues.max[i];
initialValues[index] = peak.propertiesValues.init[i];
gradientDifferences[index] = peak.propertiesValues.gradientDifference[i];
index++;
}
}
let { algorithm, optimizationOptions } = selectMethod(options.optimization);
let { algorithm, optimizationOptions } = selectMethod(optimization);
let sumOfShapes = getSumOfShapes(internalPeaks);
optimizationOptions.minValues = pMin;
optimizationOptions.maxValues = pMax;
optimizationOptions.initialValues = pInit;
optimizationOptions.gradientDifference = gradientDifference;
let fitted = algorithm({ x: data.x, y: normalizedY }, sumOfShapes, {
minValues,
maxValues,
initialValues,
gradientDifference: gradientDifferences,
...optimizationOptions,
});
const fittedValues = fitted.parameterValues;
let newPeaks: Peak[] = [];
for (let peak of internalPeaks) {
const newPeak = {
x: 0,
y: 0,
shape: peak.shape,
};
newPeak.x = fittedValues[peak.fromIndex];
newPeak.y = fittedValues[peak.fromIndex + 1] * minMaxY.range + minMaxY.min;
for (let i = 2; i < peak.parameters.length; i++) {
//@ts-expect-error should be fixed once
newPeak.shape[peak.parameters[i]] = fittedValues[peak.fromIndex + i];
}
let pFit = algorithm({ x, y }, paramsFunc, optimizationOptions);
let { parameterError: error, iterations } = pFit;
let result = { error, iterations, peaks };
for (let i = 0; i < nbShapes; i++) {
for (let k = 0; k < parameterKey.length; k++) {
const key = parameterKey[k];
const value = pFit.parameterValues[i + k * nbShapes];
if (key === 'x' || key === 'fwhm') {
peaks[i][key] = value;
} else if (key === 'y') {
peaks[i][key] = value * maxY + minY;
} else {
(peaks[i].shape as any)[key] = value;
}
}
newPeaks.push(newPeak);
}
return result;
return {
error: fitted.parameterError,
iterations: fitted.iterations,
peaks: newPeaks,
};
}

@@ -5,7 +5,7 @@ import { selectMethod } from '../selectMethod';

it('throw errors', () => {
expect(selectMethod).toThrow('Unknown kind algorithm');
expect(() => {
//@ts-expect-error expected to fail
selectMethod({ kind: 'fail' });
}).toThrow('Unknown kind algorithm');
}).toThrow('Unknown fitting algorithm');
});
});
import { levenbergMarquardt } from 'ml-levenberg-marquardt';
import { OptimizationOptions } from '../spectra-fitting';
import { OptimizationOptions } from '../index';
const LEVENBERG_MARQUARDT = 1;
/** Algorithm to select the method.

@@ -12,47 +10,19 @@ * @param optimizationOptions - Optimization options

export function selectMethod(optimizationOptions: OptimizationOptions = {}) {
let { kind, options } = optimizationOptions;
kind = getKind(kind);
let { kind = 'lm', options } = optimizationOptions;
switch (kind) {
case LEVENBERG_MARQUARDT:
case 'lm':
case 'levenbergMarquardt':
return {
algorithm: levenbergMarquardt,
optimizationOptions: checkOptions(kind, options),
optimizationOptions: {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
...options,
},
};
default:
throw new Error(`Unknown kind algorithm`);
throw new Error(`Unknown fitting algorithm`);
}
}
function checkOptions(
kind: string | number,
options: {
timeout?: number;
damping?: number;
maxIterations?: number;
errorTolerance?: number;
} = {},
): any {
switch (kind) {
case LEVENBERG_MARQUARDT:
return Object.assign({}, lmOptions, options);
default:
throw new Error(`unknown kind: ${kind}`);
}
}
function getKind(kind?: string | number) {
if (typeof kind !== 'string') return kind;
switch (kind.toLowerCase().replace(/[^a-z]/g, '')) {
case 'lm':
case 'levenbergmarquardt':
return LEVENBERG_MARQUARDT;
default:
throw new Error(`Unknown kind algorithm`);
}
}
const lmOptions = {
damping: 1.5,
maxIterations: 100,
errorTolerance: 1e-8,
};

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc