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.0.4 to 3.1.0

lib-esm/index.d.ts

442

lib/index.js

@@ -1,390 +0,68 @@

'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var getMaxValue = require('ml-array-max');
var mlPeakShapeGenerator = require('ml-peak-shape-generator');
var mlLevenbergMarquardt = require('ml-levenberg-marquardt');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var getMaxValue__default = /*#__PURE__*/_interopDefaultLegacy(getMaxValue);
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.optimize = void 0;
const checkInput_1 = require("./util/checkInput");
const selectMethod_1 = require("./util/selectMethod");
/**
* This function calculates the spectrum as a sum of linear combination of gaussian and lorentzian functions. The pseudo voigt
* parameters are divided in 4 batches. 1st: centers; 2nd: heights; 3th: widths; 4th: mu's ;
* @param t Ordinate value
* @param p Lorentzian parameters
* @returns {*}
* 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.
* @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.
*/
// const pseudoVoigtFct = PseudoVoigt.fct;
function sumOfGaussianLorentzians(p) {
const pseudoVoigt = new mlPeakShapeGenerator.PseudoVoigt();
return (t) => {
let nL = p.length / 4;
let result = 0;
for (let i = 0; i < nL; i++) {
pseudoVoigt.fwhm = p[i + nL * 2];
pseudoVoigt.mu = p[i + nL * 3];
result += p[i + nL] * pseudoVoigt.fct(t - p[i]);
function optimize(data, peakList, options = {}) {
if (!options.shape) {
options = { ...options, ...{ shape: { kind: 'gaussian' } } };
}
return result;
};
}
/**
* This function calculates the spectrum as a sum of gaussian functions. The Gaussian
* parameters are divided in 3 batches. 1st: centers; 2nd: height; 3th: widths;
* @param t Ordinate values
* @param p Gaussian parameters
* @returns {*}
*/
function sumOfGaussians(p) {
const nL = p.length / 3;
const gaussian = new mlPeakShapeGenerator.Gaussian();
return (t) => {
let result = 0;
for (let i = 0; i < nL; i++) {
gaussian.fwhm = p[i + nL * 2];
result += p[i + nL] * gaussian.fct(t - p[i]);
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);
}
}
return result;
};
}
/**
* This function calculates the spectrum as a sum of lorentzian functions. The Lorentzian
* parameters are divided in 3 batches. 1st: centers; 2nd: heights; 3th: widths;
* @param t Ordinate values
* @param p Lorentzian parameters
* @returns {*}
*/
// const lorentzianFct = Lorentzian.fct;
function sumOfLorentzians(p) {
const lorentzian = new mlPeakShapeGenerator.Lorentzian();
return (t) => {
let nL = p.length / 3;
let result = 0;
for (let i = 0; i < nL; i++) {
lorentzian.fwhm = p[i + nL * 2];
result += p[i + nL] * lorentzian.fct(t - p[i]);
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;
}
}
}
return result;
};
}
const isValidKey = (key) => {
return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
};
const isObject = (val) => {
return typeof val === 'object';
};
const isPrimitive = (val) => {
return typeof val === 'object' ? val === null : typeof val !== 'function';
};
function assignDeep(target, ...args) {
let index = 0;
if (isPrimitive(target)) target = args[index++];
if (!target) target = {};
for (; index < args.length; index++) {
if (!isObject(args[index])) continue;
for (const key in args[index]) {
if (!isValidKey(key)) continue;
if (isObject(target[key]) && isObject(args[index][key])) {
assignDeep(target[key], args[index][key]);
} else {
target[key] = args[index][key];
}
}
}
return target;
}
function checkInput(data, peakList, options) {
let {
shape = { kind: 'gaussian' },
optimization = {
kind: 'lm',
},
} = options;
let peaks = JSON.parse(JSON.stringify(peakList));
if (typeof shape.kind !== 'string') {
throw new Error('kind should be a string');
}
let kind = shape.kind.toLowerCase().replace(/[^a-z]/g, '');
let paramsFunc;
let defaultParameters;
switch (kind) {
case 'gaussian':
paramsFunc = sumOfGaussians;
defaultParameters = {
x: {
init: (peak) => peak.x,
max: (peak) => peak.x + peak.fwhm * 2,
min: (peak) => peak.x - peak.fwhm * 2,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
y: {
init: (peak) => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3,
},
fwhm: {
init: (peak) => peak.fwhm,
max: (peak) => peak.fwhm * 4,
min: (peak) => peak.fwhm * 0.25,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
};
break;
case 'lorentzian':
paramsFunc = sumOfLorentzians;
defaultParameters = {
x: {
init: (peak) => peak.x,
max: (peak) => peak.x + peak.fwhm * 2,
min: (peak) => peak.x - peak.fwhm * 2,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
y: {
init: (peak) => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3,
},
fwhm: {
init: (peak) => peak.fwhm,
max: (peak) => peak.fwhm * 4,
min: (peak) => peak.fwhm * 0.25,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
};
break;
case 'pseudovoigt':
paramsFunc = sumOfGaussianLorentzians;
defaultParameters = {
x: {
init: (peak) => peak.x,
max: (peak) => peak.x + peak.fwhm * 2,
min: (peak) => peak.x - peak.fwhm * 2,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
y: {
init: (peak) => peak.y,
max: () => 1.5,
min: () => 0,
gradientDifference: () => 1e-3,
},
fwhm: {
init: (peak) => peak.fwhm,
max: (peak) => peak.fwhm * 4,
min: (peak) => peak.fwhm * 0.25,
gradientDifference: (peak) => peak.fwhm * 2e-3,
},
mu: {
init: (peak) =>
peak.shape && peak.shape.mu !== undefined ? peak.shape.mu : 0.5,
min: () => 0,
max: () => 1,
gradientDifference: () => 0.01,
},
};
break;
default:
throw new Error('kind of shape is not supported');
}
let x = data.x;
let maxY = getMaxValue__default["default"](data.y);
let y = new Array(x.length);
for (let i = 0; i < x.length; i++) {
y[i] = data.y[i] / maxY;
}
for (let i = 0; i < peaks.length; i++) {
peaks[i].y /= maxY;
peaks[i].shape = {
kind: shape.kind,
...peaks[i].shape,
};
}
let parameters = assignDeep({}, defaultParameters, optimization.parameters);
for (let key in parameters) {
for (let par in parameters[key]) {
if (!Array.isArray(parameters[key][par])) {
parameters[key][par] = [parameters[key][par]];
}
if (
parameters[key][par].length !== 1 &&
parameters[key][par].length !== peaks.length
) {
throw new Error(`The length of ${key}-${par} is not correct`);
}
for (let index = 0; index < parameters[key][par].length; index++) {
if (typeof parameters[key][par][index] === 'number') {
let value = parameters[key][par][index];
parameters[key][par][index] = () => value;
}
}
}
}
optimization.parameters = parameters;
return {
y,
x,
maxY,
peaks,
paramsFunc,
optimization,
};
}
const LEVENBERG_MARQUARDT = 1;
function selectMethod(optimizationOptions = {}) {
let { kind, options } = optimizationOptions;
kind = getKind(kind);
switch (kind) {
case LEVENBERG_MARQUARDT:
return {
algorithm: mlLevenbergMarquardt.levenbergMarquardt,
optimizationOptions: checkOptions(kind, options),
};
default:
throw new Error(`Unknown kind algorithm`);
}
}
function checkOptions(kind, options = {}) {
// eslint-disable-next-line default-case
switch (kind) {
case LEVENBERG_MARQUARDT:
return Object.assign({}, lmOptions, options);
}
}
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,
};
/**
* Fits a set of points to the sum of a set of bell functions.
* @param {object} data - An object containing the x and y data to be fitted.
* @param {array} peaks - A list of initial parameters to be optimized. e.g. coming from a peak picking [{x, y, width}].
* @param {object} [options = {}]
* @param {object} [options.shape={}] - it's specify the kind of shape used to fitting.
* @param {string} [options.shape.kind = 'gaussian'] - kind of shape; lorentzian, gaussian and pseudovoigt are supported.
* @param {object} [options.optimization = {}] - it's specify the kind and options of the algorithm use to optimize parameters.
* @param {object} [options.optimization.kind = 'lm'] - kind of algorithm. By default it's levenberg-marquardt.
* @param {object} [options.optimization.parameters] - options of each parameter to be optimized e.g. For a gaussian shape
* it could have x, y and with 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.
* @param {object} [options.optimization.parameters.x] - options for x parameter.
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.init] - 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.
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.min] - 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.
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.max] - 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.
* @param {number|callback|array<number|callback>} [options.optimization.parameters.x.gradientDifference] - 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.
* @param {object} [options.optimization.options = {}] - options for the specific kind of algorithm.
* @param {number} [options.optimization.options.timeout] - maximum time running before break in seconds.
* @param {number} [options.optimization.options.damping=1.5]
* @param {number} [options.optimization.options.maxIterations=100]
* @param {number} [options.optimization.options.errorTolerance=1e-8]
* @returns {object} - A 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 = {}) {
const { y, x, maxY, 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);
}
}
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];
// we modify the optimized parameters
if (key === 'x' || key === 'fwhm') {
peaks[i][parameterKey[k]] = value;
} else if (key === 'y') {
peaks[i][parameterKey[k]] = value * maxY;
} else {
peaks[i].shape[parameterKey[k]] = value;
}
}
}
return result;
}
exports.optimize = optimize;
//# sourceMappingURL=index.js.map
{
"name": "ml-spectra-fitting",
"version": "3.0.4",
"version": "3.1.0",
"description": "Fit spectra using gaussian or lorentzian",
"main": "lib/index.js",
"module": "src/index.js",
"types": "src/spectra-fitting.d.ts",
"main": "./lib/index.js",
"module": "./lib-esm/index.js",
"types": "./lib/index.d.ts",
"files": [
"lib",
"src"
"src",
"lib-esm"
],
"scripts": {
"build": "cheminfo-build --entry src/index.js --root SpectraFitting",
"build": "npm run tsc && cheminfo-build --entry lib-esm/index.js --root SpectraFitting",
"check-types": "tsc --noEmit",
"clean": "rimraf lib lib-esm",
"eslint": "eslint src",
"eslint-fix": "npm run eslint -- --fix",
"compile": "rollup -c",
"prepublishOnly": "npm run compile",
"test": "npm run test-coverage && npm run eslint",
"prepack": "npm run tsc",
"test": "npm run test-only && npm run eslint && npm run check-types",
"prettier": "prettier --check src",
"prettier-write": "prettier --write src",
"test-coverage": "jest --coverage",
"test-only": "jest",
"test-only": "jest --coverage",
"tsc": "npm run clean && npm run tsc-cjs && npm run tsc-esm",
"tsc-cjs": "tsc --project tsconfig.cjs.json",
"tsc-esm": "tsc --project tsconfig.esm.json",
"debug": "npm run prepublishOnly && node src/debug.js"

@@ -44,23 +49,23 @@ },

"homepage": "https://github.com/mljs/spectra-fitting",
"jest": {
"testEnvironment": "node"
},
"devDependencies": {
"@babel/plugin-transform-modules-commonjs": "^7.16.8",
"@types/jest": "^27.4.0",
"@types/jest": "^27.4.1",
"cheminfo-build": "^1.1.11",
"eslint": "^8.7.0",
"eslint-config-cheminfo": "^7.2.1",
"eslint": "^8.9.0",
"eslint-config-cheminfo": "^7.2.2",
"eslint-config-cheminfo-typescript": "^10.3.0",
"esm": "^3.2.25",
"jest": "^27.4.7",
"jest": "^27.5.1",
"jest-matcher-deep-close-to": "^3.0.2",
"prettier": "^2.5.1",
"rollup": "^2.66.0",
"spectrum-generator": "^6.0.2"
"spectrum-generator": "^7.0.1",
"ts-jest": "^27.1.3",
"typescript": "^4.5.5"
},
"dependencies": {
"cheminfo-types": "^0.9.1",
"cheminfo-types": "^1.0.0",
"ml-array-max": "^1.2.4",
"ml-levenberg-marquardt": "^4.0.0",
"ml-peak-shape-generator": "^4.0.1"
"ml-peak-shape-generator": "^4.1.1"
}
}

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

y: 0.19995307937326137,
width: 0.10007670374735196,
fwhm: 0.10007670374735196,
mu: 0.004731136777288483

@@ -141,3 +141,3 @@ },

y: 0.19960010175400406,
width: 0.19935932346969124,
fwhm: 0.19935932346969124,
mu: 1

@@ -144,0 +144,0 @@ }

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