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

isotopic-distribution

Package Overview
Dependencies
Maintainers
2
Versions
88
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

isotopic-distribution - npm Package Compare versions

Comparing version 3.0.1 to 3.1.0

src/__tests__/isotopicDistribution.array.test.js

210

lib/index.js

@@ -114,14 +114,14 @@ 'use strict';

function power(a, p, options = {}) {
function power(array, p, options = {}) {
if (p <= 0) throw new Error('power must be larger than 0');
if (p === 1) return a;
if (p === 1) return array;
if (p === 2) {
return a.square();
return array.square();
}
p--;
let base = a.copy(); // linear time
let base = array.copy(); // linear time
while (p !== 0) {
if ((p & 1) !== 0) {
a.multiply(base, options); // executed <= log2(p) times
array.multiply(base, options); // executed <= log2(p) times
}

@@ -132,3 +132,3 @@ p >>= 1;

return a;
return array;
}

@@ -140,14 +140,12 @@

class Distribution {
constructor(array) {
if (Array.isArray(array)) {
this.array = array;
this.xSorted = false;
this.ySorted = false;
} else {
this.array = [];
this.xSorted = true;
this.ySorted = true;
}
constructor(array = []) {
this.array = array;
this.cache = getEmptyCache();
}
emptyCache() {
if (this.cache.isEmpty) return;
this.cache = getEmptyCache();
}
get length() {

@@ -165,24 +163,69 @@ return this.array.length;

get sumY() {
if (!isNaN(this.cache.sumY)) return this.cache.sumY;
let sumY = 0;
for (let item of this.array) {
sumY += item.y;
}
this.cache.sumY = sumY;
this.cache.isEmpty = false;
return sumY;
}
get minX() {
if (!this.xSorted) this.sortX();
return this.array[0].x;
if (!isNaN(this.cache.minX)) return this.cache.minX;
let minX = Number.POSITIVE_INFINITY;
for (let item of this.array) {
if (item.x < minX) {
minX = item.x;
}
}
this.cache.minX = minX;
this.cache.isEmpty = false;
return minX;
}
get maxX() {
if (!this.xSorted) this.sortX();
return this.array[this.array.length - 1].x;
if (!isNaN(this.cache.maxX)) return this.cache.maxX;
let maxX = Number.NEGATIVE_INFINITY;
for (let item of this.array) {
if (item.x > maxX) {
maxX = item.x;
}
}
this.cache.maxX = maxX;
this.cache.isEmpty = false;
return maxX;
}
get minY() {
if (!this.ySorted) this.sortY();
return this.array[0].y;
if (!isNaN(this.cache.minY)) return this.cache.minY;
let minY = Number.POSITIVE_INFINITY;
for (let item of this.array) {
if (item.y < minY) {
minY = item.y;
}
}
this.cache.minY = minY;
this.cache.isEmpty = false;
return minY;
}
get maxY() {
if (!this.ySorted) this.sortY();
return this.array[this.array.length - 1];
if (!isNaN(this.cache.maxY)) return this.cache.maxY;
let maxY = Number.NEGATIVE_INFINITY;
for (let item of this.array) {
if (item.y > maxY) {
maxY = item.y;
}
}
this.cache.maxY = maxY;
this.cache.isEmpty = false;
return maxY;
}
multiplyY(value) {
this.array.forEach((item) => (item.y *= value));
for (const item of this.array) {
item.y *= value;
}
}

@@ -192,10 +235,8 @@

this.array = array;
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}
move(other) {
this.xSorted = other.xSorted;
this.ySorted = other.ySorted;
this.array = other.array;
this.emptyCache();
}

@@ -205,20 +246,28 @@

this.array.push(point);
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}
/**
* Sort by ASCENDING x values
* @returns {Distribution}
*/
sortX() {
this.ySorted = false;
if (this.xSorted) return this;
this.cache.ySorted = false;
if (this.cache.xSorted) return this;
this.array.sort((a, b) => a.x - b.x);
this.xSorted = true;
this.cache.xSorted = true;
this.cache.isEmpty = false;
return this;
}
/**
* Sort by DESCENDING y values
* @returns {Distribution}
*/
sortY() {
this.xSorted = false;
if (this.ySorted) return this;
this.cache.xSorted = false;
if (this.cache.ySorted) return this;
this.array.sort((a, b) => b.y - a.y);
this.ySorted = true;
this.cache.ySorted = true;
this.cache.isEmpty = false;
return this;

@@ -228,7 +277,4 @@ }

normalize() {
let sum = 0;
const sum = this.sumY;
for (let item of this.array) {
sum += item.y;
}
for (let item of this.array) {
item.y /= sum;

@@ -239,2 +285,7 @@ }

/**
* Only keep a defined number of peaks
* @param {number} limit
* @returns
*/
topY(limit) {

@@ -248,2 +299,13 @@ if (!limit) return this;

/**
* remove all the peaks under a defined relative threshold
* @param {number} [relativeValue=0] Should be between 0 and 1. 0 means no peak will be removed, 1 means all peaks will be removed
*/
threshold(relativeValue = 0) {
if (!relativeValue) return this;
const maxY = this.maxY;
const threshold = maxY * relativeValue;
this.array = this.array.filter((point) => point.y >= threshold);
}
square(options = {}) {

@@ -263,4 +325,3 @@ return this.multiply(this, options);

let distCopy = new Distribution();
distCopy.xSorted = this.xSorted;
distCopy.ySorted = this.ySorted;
distCopy.cache = { ...this.cache };
distCopy.array = JSON.parse(JSON.stringify(this.array));

@@ -272,7 +333,4 @@ return distCopy;

if (this.array.length === 0) return this;
let currentMax = this.array[0].y;
let currentMax = this.maxY;
for (let item of this.array) {
if (item.y > currentMax) currentMax = item.y;
}
for (let item of this.array) {
item.y /= currentMax;

@@ -291,4 +349,3 @@ }

}
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}

@@ -302,2 +359,15 @@

function getEmptyCache() {
return {
isEmpty: true,
xSorted: false,
ySorted: false,
minX: NaN,
maxX: NaN,
minY: NaN,
maxY: NaN,
sumY: NaN,
};
}
function getDerivedCompositionInfo(composition) {

@@ -352,7 +422,11 @@ const shortComposition = {};

* @param {number} [options.minY=1e-8] - Minimal signal height during calculations
* @param {number} [options.ensureCase=false] - Ensure uppercase / lowercase
* @param {number} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge
* @param {boolean} [options.ensureCase=false] - Ensure uppercase / lowercase
* @param {number} [options.threshold] - We can filter the result based on the relative height of the peaks
* @param {number} [options.limit] - We may define the maximum number of peaks to keep
* @param {boolean} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge
*/
constructor(value, options = {}) {
this.threshold = options.threshold;
this.limit = options.limit;
if (Array.isArray(value)) {

@@ -447,6 +521,6 @@ this.parts = JSON.parse(JSON.stringify(value));

}
this.confidence += totalDistribution.array.reduce(
(sum, value) => sum + value.y,
0,
);
this.confidence = 0;
for (const item of totalDistribution.array) {
this.confidence += item.y;
}

@@ -468,6 +542,7 @@ // we finally deal with the charge

if (
part.ms.target &&
part.ms.target.intensity &&
part.ms.target.intensity !== 1
if (part?.ms.similarity?.factor) {
totalDistribution.multiplyY(part.ms.similarity.factor);
} else if (
part.ms?.target?.intensity &&
part.ms?.target?.intensity !== 1
) {

@@ -483,3 +558,3 @@ // intensity is the value of the monoisotopic mass !

}
} else if (part.intensity && part.intensity !== 1) {
} else if (part?.intensity && part?.intensity !== 1) {
totalDistribution.multiplyY(part.intensity);

@@ -499,2 +574,15 @@ }

// if there is a threshold we will deal with it
// and we will correct the confidence
if (this.threshold || this.limit) {
const sumBefore = finalDistribution.sumY;
if (this.threshold) finalDistribution.threshold(this.threshold);
if (this.limit) {
finalDistribution.topY(this.limit);
finalDistribution.sortX();
}
const sumAfter = finalDistribution.sumY;
this.confidence = (this.confidence * sumAfter) / sumBefore;
}
for (let entry of finalDistribution.array) {

@@ -501,0 +589,0 @@ if (!entry.composition) continue;

{
"name": "isotopic-distribution",
"version": "3.0.1",
"version": "3.1.0",
"description": "Calculate the isotopic distribution of a molecular formula",

@@ -24,4 +24,4 @@ "main": "lib/index.js",

"chemical-elements": "^2.0.3",
"mf-parser": "^3.0.1",
"mf-utilities": "^3.0.1",
"mf-parser": "^3.1.0",
"mf-utilities": "^3.1.0",
"spectrum-generator": "^8.0.8"

@@ -32,3 +32,3 @@ },

},
"gitHead": "a731fb03357ed5adbae7f72c791547691333a88c"
"gitHead": "deb282f3f2d6ae966112bc520b28fb75af3c327b"
}

@@ -61,6 +61,11 @@ import { Distribution } from '../Distribution.js';

let distribution = new Distribution();
distribution.push({ x: 2, y: 3 });
distribution.push({ x: 1, y: 1 });
distribution.push({ x: 2, y: 3 });
distribution.push({ x: 2.25, y: 1 });
expect(distribution.minX).toStrictEqual(1);
expect(distribution.maxX).toStrictEqual(2.25);
expect(distribution.minY).toStrictEqual(1);
expect(distribution.maxY).toStrictEqual(3);
distribution.joinX(1);
expect(distribution.array).toStrictEqual([{ x: 1.85, y: 5 }]);

@@ -81,2 +86,12 @@ });

});
it('Check the threshold', () => {
let distribution = new Distribution();
distribution.push({ x: 1, y: 1 });
distribution.push({ x: 2, y: 3 });
distribution.push({ x: 2.25, y: 1 });
distribution.push({ x: 5, y: 1 });
distribution.threshold(0.5);
expect(distribution.array).toStrictEqual([{ x: 2, y: 3 }]);
});
});

@@ -0,3 +1,7 @@

import { toBeDeepCloseTo } from 'jest-matcher-deep-close-to';
import { IsotopicDistribution } from '..';
expect.extend({ toBeDeepCloseTo });
describe('test isotopicDistribution', () => {

@@ -199,2 +203,23 @@ it('create distribution of CH0', () => {

it('create distribution of C10 with threshold', () => {
let isotopicDistribution = new IsotopicDistribution('C10', {
threshold: 0.1,
});
let xy = isotopicDistribution.getXY({ sumValue: 100 });
expect(isotopicDistribution.confidence).toBeGreaterThan(0.95);
expect(xy.x[0]).toBe(120);
expect(xy.y.reduce((previous, current) => previous + current, 0)).toBe(100);
});
it('create distribution of C10 with limit', () => {
let isotopicDistribution = new IsotopicDistribution('C10', {
limit: 2,
});
let xy = isotopicDistribution.getXY();
expect(xy).toBeDeepCloseTo({
x: [120, 121.00335483507],
y: [100, 10.815728292732235],
});
});
it('create distribution of C10 and getVariables with maxValue to 100', () => {

@@ -201,0 +226,0 @@ let isotopicDistribution = new IsotopicDistribution('C10');

@@ -10,14 +10,12 @@ import { closestPointX } from './utils/closestPointX.js';

export class Distribution {
constructor(array) {
if (Array.isArray(array)) {
this.array = array;
this.xSorted = false;
this.ySorted = false;
} else {
this.array = [];
this.xSorted = true;
this.ySorted = true;
}
constructor(array = []) {
this.array = array;
this.cache = getEmptyCache();
}
emptyCache() {
if (this.cache.isEmpty) return;
this.cache = getEmptyCache();
}
get length() {

@@ -35,24 +33,69 @@ return this.array.length;

get sumY() {
if (!isNaN(this.cache.sumY)) return this.cache.sumY;
let sumY = 0;
for (let item of this.array) {
sumY += item.y;
}
this.cache.sumY = sumY;
this.cache.isEmpty = false;
return sumY;
}
get minX() {
if (!this.xSorted) this.sortX();
return this.array[0].x;
if (!isNaN(this.cache.minX)) return this.cache.minX;
let minX = Number.POSITIVE_INFINITY;
for (let item of this.array) {
if (item.x < minX) {
minX = item.x;
}
}
this.cache.minX = minX;
this.cache.isEmpty = false;
return minX;
}
get maxX() {
if (!this.xSorted) this.sortX();
return this.array[this.array.length - 1].x;
if (!isNaN(this.cache.maxX)) return this.cache.maxX;
let maxX = Number.NEGATIVE_INFINITY;
for (let item of this.array) {
if (item.x > maxX) {
maxX = item.x;
}
}
this.cache.maxX = maxX;
this.cache.isEmpty = false;
return maxX;
}
get minY() {
if (!this.ySorted) this.sortY();
return this.array[0].y;
if (!isNaN(this.cache.minY)) return this.cache.minY;
let minY = Number.POSITIVE_INFINITY;
for (let item of this.array) {
if (item.y < minY) {
minY = item.y;
}
}
this.cache.minY = minY;
this.cache.isEmpty = false;
return minY;
}
get maxY() {
if (!this.ySorted) this.sortY();
return this.array[this.array.length - 1];
if (!isNaN(this.cache.maxY)) return this.cache.maxY;
let maxY = Number.NEGATIVE_INFINITY;
for (let item of this.array) {
if (item.y > maxY) {
maxY = item.y;
}
}
this.cache.maxY = maxY;
this.cache.isEmpty = false;
return maxY;
}
multiplyY(value) {
this.array.forEach((item) => (item.y *= value));
for (const item of this.array) {
item.y *= value;
}
}

@@ -62,10 +105,8 @@

this.array = array;
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}
move(other) {
this.xSorted = other.xSorted;
this.ySorted = other.ySorted;
this.array = other.array;
this.emptyCache();
}

@@ -75,20 +116,28 @@

this.array.push(point);
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}
/**
* Sort by ASCENDING x values
* @returns {Distribution}
*/
sortX() {
this.ySorted = false;
if (this.xSorted) return this;
this.cache.ySorted = false;
if (this.cache.xSorted) return this;
this.array.sort((a, b) => a.x - b.x);
this.xSorted = true;
this.cache.xSorted = true;
this.cache.isEmpty = false;
return this;
}
/**
* Sort by DESCENDING y values
* @returns {Distribution}
*/
sortY() {
this.xSorted = false;
if (this.ySorted) return this;
this.cache.xSorted = false;
if (this.cache.ySorted) return this;
this.array.sort((a, b) => b.y - a.y);
this.ySorted = true;
this.cache.ySorted = true;
this.cache.isEmpty = false;
return this;

@@ -98,7 +147,4 @@ }

normalize() {
let sum = 0;
const sum = this.sumY;
for (let item of this.array) {
sum += item.y;
}
for (let item of this.array) {
item.y /= sum;

@@ -109,2 +155,7 @@ }

/**
* Only keep a defined number of peaks
* @param {number} limit
* @returns
*/
topY(limit) {

@@ -118,2 +169,13 @@ if (!limit) return this;

/**
* remove all the peaks under a defined relative threshold
* @param {number} [relativeValue=0] Should be between 0 and 1. 0 means no peak will be removed, 1 means all peaks will be removed
*/
threshold(relativeValue = 0) {
if (!relativeValue) return this;
const maxY = this.maxY;
const threshold = maxY * relativeValue;
this.array = this.array.filter((point) => point.y >= threshold);
}
square(options = {}) {

@@ -133,4 +195,3 @@ return this.multiply(this, options);

let distCopy = new Distribution();
distCopy.xSorted = this.xSorted;
distCopy.ySorted = this.ySorted;
distCopy.cache = { ...this.cache };
distCopy.array = JSON.parse(JSON.stringify(this.array));

@@ -142,7 +203,4 @@ return distCopy;

if (this.array.length === 0) return this;
let currentMax = this.array[0].y;
let currentMax = this.maxY;
for (let item of this.array) {
if (item.y > currentMax) currentMax = item.y;
}
for (let item of this.array) {
item.y /= currentMax;

@@ -161,4 +219,3 @@ }

}
this.xSorted = false;
this.ySorted = false;
this.emptyCache();
}

@@ -171,1 +228,14 @@

}
function getEmptyCache() {
return {
isEmpty: true,
xSorted: false,
ySorted: false,
minX: NaN,
maxX: NaN,
minY: NaN,
maxY: NaN,
sumY: NaN,
};
}

@@ -30,7 +30,11 @@ import { ELECTRON_MASS } from 'chemical-elements';

* @param {number} [options.minY=1e-8] - Minimal signal height during calculations
* @param {number} [options.ensureCase=false] - Ensure uppercase / lowercase
* @param {number} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge
* @param {boolean} [options.ensureCase=false] - Ensure uppercase / lowercase
* @param {number} [options.threshold] - We can filter the result based on the relative height of the peaks
* @param {number} [options.limit] - We may define the maximum number of peaks to keep
* @param {boolean} [options.allowNeutral=true] - Should we keep the distribution if the molecule has no charge
*/
constructor(value, options = {}) {
this.threshold = options.threshold;
this.limit = options.limit;
if (Array.isArray(value)) {

@@ -125,6 +129,6 @@ this.parts = JSON.parse(JSON.stringify(value));

}
this.confidence += totalDistribution.array.reduce(
(sum, value) => sum + value.y,
0,
);
this.confidence = 0;
for (const item of totalDistribution.array) {
this.confidence += item.y;
}

@@ -146,6 +150,7 @@ // we finally deal with the charge

if (
part.ms.target &&
part.ms.target.intensity &&
part.ms.target.intensity !== 1
if (part?.ms.similarity?.factor) {
totalDistribution.multiplyY(part.ms.similarity.factor);
} else if (
part.ms?.target?.intensity &&
part.ms?.target?.intensity !== 1
) {

@@ -161,3 +166,3 @@ // intensity is the value of the monoisotopic mass !

}
} else if (part.intensity && part.intensity !== 1) {
} else if (part?.intensity && part?.intensity !== 1) {
totalDistribution.multiplyY(part.intensity);

@@ -177,2 +182,15 @@ }

// if there is a threshold we will deal with it
// and we will correct the confidence
if (this.threshold || this.limit) {
const sumBefore = finalDistribution.sumY;
if (this.threshold) finalDistribution.threshold(this.threshold);
if (this.limit) {
finalDistribution.topY(this.limit);
finalDistribution.sortX();
}
const sumAfter = finalDistribution.sumY;
this.confidence = (this.confidence * sumAfter) / sumBefore;
}
for (let entry of finalDistribution.array) {

@@ -179,0 +197,0 @@ if (!entry.composition) continue;

// https://en.wikipedia.org/wiki/Exponentiation_by_squaring
export function power(a, p, options = {}) {
export function power(array, p, options = {}) {
if (p <= 0) throw new Error('power must be larger than 0');
if (p === 1) return a;
if (p === 1) return array;
if (p === 2) {
return a.square();
return array.square();
}
p--;
let base = a.copy(); // linear time
let base = array.copy(); // linear time
while (p !== 0) {
if ((p & 1) !== 0) {
a.multiply(base, options); // executed <= log2(p) times
array.multiply(base, options); // executed <= log2(p) times
}

@@ -20,3 +20,3 @@ p >>= 1;

return a;
return array;
}

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