ml-spectra-fitting
Advanced tools
Comparing version 3.1.0 to 4.0.0
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 |
105
lib/index.js
"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" | ||
} | ||
} |
134
README.md
@@ -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 @@ |
240
src/index.ts
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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
11
1939
194
98515
5
63
+ Addedbinary-search@1.3.6(transitive)
+ Addedd3-array@0.7.1(transitive)
+ Addedfft.js@4.0.4(transitive)
+ Addedml-spectra-processing@11.17.0(transitive)
+ Addedml-xsadd@2.0.0(transitive)
+ Addedspline-interpolator@1.0.0(transitive)
Updatedcheminfo-types@^1.1.0