Comparing version 0.5.0 to 0.5.2
{ | ||
"name": "mf-finder", | ||
"version": "0.5.0", | ||
"version": "0.5.2", | ||
"description": "Find a molecular formula from a monoisotopic mass", | ||
@@ -21,6 +21,7 @@ "main": "src/index.js", | ||
"dependencies": { | ||
"chemical-elements": "^0.5.0", | ||
"mf-parser": "^0.5.0", | ||
"chemical-elements": "^0.5.2", | ||
"mf-parser": "^0.5.2", | ||
"mf-utilities": "^0.5.2", | ||
"molecular-formula": "^1.1.0" | ||
} | ||
} |
@@ -18,2 +18,58 @@ 'use strict'; | ||
it('basic case with charge', () => { | ||
let result = findMFs(24, { | ||
ranges: [ | ||
{ mf: 'C', min: 1, max: 2 }, | ||
], | ||
precision: 1e5, | ||
ionizations: 'H+' | ||
}); | ||
expect(result.mfs).toHaveLength(1); | ||
expect(result.mfs[0]).toMatchObject({ em: 24, | ||
unsaturation: 3, | ||
mf: 'C2', | ||
charge: 0, | ||
ionization: { charge: 1, em: 1.00782503223, mf: 'H+' }, | ||
ms: { | ||
delta: 1.00727645232093, | ||
em: 25.00727645232093, | ||
ppm: 41969.85218003875, | ||
charge: 1 | ||
} | ||
}); | ||
}); | ||
it('basic case with charge and extreme error', () => { | ||
let result = findMFs(24, { | ||
ranges: [ | ||
{ mf: 'C', min: 1, max: 2 }, | ||
], | ||
precision: 1e6, | ||
ionizations: 'H+' | ||
}); | ||
expect(result.mfs).toHaveLength(2); | ||
}); | ||
it('basic case with double charge and extreme error', () => { | ||
let result = findMFs(24, { | ||
ranges: [ | ||
{ mf: 'C', min: 1, max: 2 }, | ||
], | ||
precision: 1e6, | ||
ionizations: 'H++' | ||
}); | ||
expect(result.mfs[1]).toMatchObject({ em: 12, | ||
unsaturation: 2, | ||
mf: 'C', | ||
charge: 0, | ||
ionization: { mf: 'H++', em: 1.00782503223, charge: 2 }, | ||
ms: | ||
{ em: 6.50336393620593, | ||
delta: -17.49663606379407, | ||
charge: 2, | ||
ppm: 729026.5026580864 } }); | ||
expect(result.mfs).toHaveLength(2); | ||
}); | ||
it('simple combinations', () => { | ||
@@ -90,3 +146,3 @@ let result = findMFs(24, { | ||
it('simple combinations from string ranges with modifications', () => { | ||
it('simple combinations from string ranges with ionizations', () => { | ||
let result = findMFs(12, { | ||
@@ -99,6 +155,6 @@ ranges: [ | ||
precision: 1e5, | ||
modifications: 'H+, H++' | ||
ionizations: 'H+, H++' | ||
}); | ||
expect(result.mfs).toHaveLength(4); | ||
expect(result.mfs[0].mf).toBe('C'); | ||
expect(result.mfs[0].mf).toBe('C2'); | ||
}); | ||
@@ -105,0 +161,0 @@ |
@@ -19,3 +19,3 @@ 'use strict'; | ||
expect(possibilities).toHaveLength(5); | ||
expect(possibilities[1]).toMatchObject({ isGroup: true, charge: 1, unsaturation: 0 }); | ||
expect(possibilities[1]).toMatchObject({ mf: 'H+', isGroup: true, charge: 1, unsaturation: -2 }); | ||
}); | ||
@@ -22,0 +22,0 @@ |
@@ -96,2 +96,19 @@ 'use strict'; | ||
}); | ||
it('check the result with extra default charge', () => { | ||
let possibilities = preprocessRanges( | ||
[ | ||
{ mf: 'C+', min: -1, max: 1 }, | ||
] | ||
); | ||
let cache = new TargetMassCache(100, possibilities, { charge: 1, minCharge: -1, maxCharge: 1, allowNeutral: false, precision: 1e6 }); | ||
expect(cache).toEqual({ | ||
minCharge: 0, | ||
maxCharge: 1, | ||
data: [ | ||
{ charge: 0, minMass: Number.MAX_SAFE_INTEGER, maxMass: Number.MIN_SAFE_INTEGER }, | ||
{ charge: 1, minMass: 0.00054857990907, maxMass: 200.00054857990906 } | ||
] | ||
}); | ||
}); | ||
}); |
138
src/index.js
'use strict'; | ||
const preprocessRanges = require('./preprocessRanges'); | ||
const preprocessModifications = require('./preprocessModifications'); | ||
const preprocessIonizations = require('mf-utilities/src/preprocessIonizations'); | ||
const getMsInfo = require('mf-utilities/src/getMsInfo'); | ||
const TargetMassCache = require('./TargetMassCache'); | ||
const ELECTRON_MASS = require('chemical-elements/src/constants').ELECTRON_MASS; | ||
/** | ||
* Returns possible combinations | ||
* | ||
* ranges=[{ | ||
* unaturation, | ||
* em, | ||
* min | ||
* max | ||
* }] | ||
* | ||
* @return {} | ||
* * @return {} | ||
*/ | ||
@@ -52,9 +44,16 @@ | ||
// we need to make the processing for all the modifications | ||
let modifications = preprocessModifications(options.modifications); | ||
for (let modification of modifications) { | ||
let possibilities = preprocessRanges(ranges, modification); | ||
// we need to make the processing for all the ionizations | ||
let ionizations = preprocessIonizations(options.ionizations); | ||
for (let ionization of ionizations) { | ||
let currentIonization = { | ||
currentMonoisotopicMass: ionization.em, | ||
currentCharge: ionization.charge, | ||
currentUnsaturation: 0 // we don't take into account the unsaturation of the ionization agent | ||
}; | ||
// if (DEBUG) console.log('new ionization', ionization.mf, ionization.em, ionization.charge); | ||
// ionization em and charge will be used to set the first atom value | ||
let possibilities = preprocessRanges(ranges); | ||
if (possibilities.length === 0) return { mfs: [] }; | ||
targetMassCache = new TargetMassCache(targetMass, possibilities, options); | ||
targetMassCache = new TargetMassCache(targetMass, possibilities, Object.assign({}, options, { charge: ionization.charge })); | ||
result.info = { | ||
@@ -74,18 +73,11 @@ numberMFEvaluated: 0, | ||
// console.debug('initializing the possibilities'); | ||
initializePossibilities(possibilities); | ||
initializePossibilities(possibilities, currentIonization); | ||
// if (DEBUG) console.log('possibilities', possibilities.map((a) => `${a.mf + a.originalMinCount}-${a.originalMaxCount}`)); | ||
let isValid = false; // designed so that the first time it is not a valid solution | ||
while (!theEnd) { | ||
/* | ||
console..debug( | ||
'Currently evaluating', | ||
result.info.numberMFEvaluated, | ||
possibilitiesToString(possibilities) | ||
); | ||
*/ | ||
if (result.info.numberMFEvaluated++ > maxIterations) { | ||
throw Error(`Iteration number is over the current maximum of: ${maxIterations}`); | ||
} | ||
let isValid = true; | ||
if (filterUnsaturation) { | ||
@@ -106,30 +98,24 @@ let unsaturation = lastPossibility.currentUnsaturation; | ||
if (isValid) { | ||
let minMass = targetMassCache.getMinMass(lastPossibility.currentCharge); | ||
let maxMass = targetMassCache.getMaxMass(lastPossibility.currentCharge); | ||
if ((lastPossibility.currentMonoisotopicMass < minMass) || (lastPossibility.currentMonoisotopicMass > maxMass)) { | ||
isValid = false; | ||
} | ||
// console.log('xxxxx', isValid, minMass, maxMass, lastPossibility); | ||
} | ||
if (isValid) { | ||
result.mfs.push(getResult(possibilities, targetMass, allowNeutral, modification)); | ||
result.mfs.push(getResult(possibilities, targetMass, allowNeutral, ionization)); | ||
result.info.numberResults++; | ||
} | ||
isValid = true; | ||
// we need to setup all the arrays if possible | ||
while (currentPosition < maxPosition && currentPosition >= 0) { | ||
currentAtom = possibilities[currentPosition]; | ||
previousAtom = (currentPosition === 0) ? undefined : possibilities[currentPosition - 1]; | ||
previousAtom = (currentPosition === 0) ? currentIonization : possibilities[currentPosition - 1]; | ||
if (currentAtom.currentCount < currentAtom.currentMaxCount) { | ||
// console.log('currentPosition', currentPosition, currentAtom.currentCount, currentAtom.currentMaxCount); | ||
currentAtom.currentCount++; | ||
updateCurrentAtom(currentAtom, previousAtom); | ||
// console.log('updated', currentAtom); | ||
if (currentPosition < lastPosition) { | ||
currentPosition++; | ||
setCurrentMinMax(possibilities[currentPosition], possibilities[currentPosition - 1]); | ||
// console..debug('MIN/MAX', currentPosition, possibilitiesToString(possibilities)); | ||
} else { | ||
@@ -143,4 +129,2 @@ break; | ||
// console..debug('After', possibilities.map((a) => [a.currentCount, a.currentMinCount, a.currentMaxCount])); | ||
if (currentPosition < 0) { | ||
@@ -152,3 +136,3 @@ theEnd = true; | ||
result.mfs.sort((a, b) => a.ppm - b.ppm); | ||
result.mfs.sort((a, b) => a.ms.ppm - b.ms.ppm); | ||
return result; | ||
@@ -158,58 +142,34 @@ }; | ||
function updateCurrentAtom(currentAtom, previousAtom) { | ||
// console.log('UPDATE'); | ||
if (previousAtom !== undefined) { | ||
currentAtom.currentMonoisotopicMass = previousAtom.currentMonoisotopicMass + currentAtom.em * currentAtom.currentCount; | ||
currentAtom.currentCharge = previousAtom.currentCharge + currentAtom.charge * currentAtom.currentCount; | ||
currentAtom.currentUnsaturation = previousAtom.currentUnsaturation + currentAtom.unsaturation * currentAtom.currentCount; | ||
// console.log('PREVIOUS', previousAtom.mf, previousAtom.charge, previousAtom.currentCount, previousAtom.currentCharge); | ||
} else { | ||
currentAtom.currentMonoisotopicMass = currentAtom.em * currentAtom.currentCount; | ||
currentAtom.currentCharge = currentAtom.charge * currentAtom.currentCount; | ||
currentAtom.currentUnsaturation = currentAtom.unsaturation * currentAtom.currentCount; | ||
} | ||
// console.log('CURRENT', currentAtom.mf, currentAtom.charge, currentAtom.currentCount, currentAtom.currentCharge); | ||
currentAtom.currentMonoisotopicMass = previousAtom.currentMonoisotopicMass + currentAtom.em * currentAtom.currentCount; | ||
currentAtom.currentCharge = previousAtom.currentCharge + currentAtom.charge * currentAtom.currentCount; | ||
currentAtom.currentUnsaturation = previousAtom.currentUnsaturation + currentAtom.unsaturation * currentAtom.currentCount; | ||
} | ||
function getResult(possibilities, targetMass, allowNeutralMolecules, modification) { | ||
function getResult(possibilities, targetMass, allowNeutralMolecules, ionization) { | ||
let lastPossibility = possibilities[possibilities.length - 1]; | ||
let result = { | ||
em: 0, | ||
unsaturation: 0, | ||
em: lastPossibility.currentMonoisotopicMass - ionization.em, | ||
unsaturation: lastPossibility.currentUnsaturation, | ||
mf: '', | ||
charge: 0, | ||
msem: 0, | ||
delta: 0, | ||
modification: modification ? modification.mf : undefined | ||
charge: lastPossibility.currentCharge - ionization.charge, | ||
ionization | ||
}; | ||
let firstModification = modification ? true : false; | ||
// we check that the first time we meet the ionization group it does not end | ||
// in the final result | ||
for (let possibility of possibilities) { | ||
if (possibility.currentCount !== 0) { | ||
result.charge += possibility.charge * possibility.currentCount; | ||
if (firstModification && modification.mf === possibility.mf) { | ||
firstModification = false; | ||
if (possibility.isGroup) { | ||
result.mf += `(${possibility.mf})`; | ||
if (possibility.currentCount !== 1) result.mf += possibility.currentCount; | ||
} else { | ||
result.em += possibility.em * possibility.currentCount; | ||
result.unsaturation += possibility.unsaturation * possibility.currentCount; | ||
if (possibility.isGroup) { | ||
result.mf += `(${possibility.mf})`; | ||
if (possibility.currentCount !== 1) result.mf += possibility.currentCount; | ||
} else { | ||
result.mf += possibility.mf; | ||
if (possibility.currentCount !== 1) result.mf += possibility.currentCount; | ||
} | ||
result.mf += possibility.mf; | ||
if (possibility.currentCount !== 1) result.mf += possibility.currentCount; | ||
} | ||
} | ||
} | ||
if (result.charge !== 0) { | ||
result.msem = (result.em - ELECTRON_MASS * result.charge) / result.charge; | ||
result.delta = result.msem - targetMass; | ||
result.ppm = Math.abs((targetMass - result.msem) / targetMass * 1e6); | ||
} else if (allowNeutralMolecules) { | ||
result.msem = result.em; | ||
result.delta = result.em - targetMass; | ||
result.ppm = Math.abs((targetMass - result.msem) / targetMass * 1e6); | ||
} | ||
result.unsaturation = (result.unsaturation + result.charge - modification.charge) / 2 + 1; | ||
result.unsaturation = (result.unsaturation + Math.abs(result.charge)) / 2 + 1; | ||
result.ms = getMsInfo(result, { targetMass, allowNeutralMolecules }); | ||
return result; | ||
@@ -220,3 +180,2 @@ } | ||
// the current min max can only be optimize if the charge will not change anymore | ||
if (currentAtom.innerCharge === true || currentAtom.charge !== 0) { | ||
@@ -232,14 +191,11 @@ currentAtom.currentMinCount = currentAtom.originalMinCount; | ||
currentAtom.currentCount = currentAtom.currentMinCount - 1; | ||
// console.log('After MIN / MAX', previousAtom.currentCount, currentAtom); | ||
} | ||
} | ||
function initializePossibilities(possibilities) { | ||
function initializePossibilities(possibilities, currentIonization) { | ||
for (let i = 0; i < possibilities.length; i++) { | ||
if (i === 0) { | ||
setCurrentMinMax(possibilities[i]); | ||
updateCurrentAtom(possibilities[i]); | ||
updateCurrentAtom(possibilities[i], currentIonization); | ||
setCurrentMinMax(possibilities[i], currentIonization); | ||
} else { | ||
@@ -246,0 +202,0 @@ updateCurrentAtom(possibilities[i], possibilities[i - 1]); |
@@ -7,3 +7,3 @@ 'use strict'; | ||
module.exports = function preprocessRanges(ranges, modification = { mf: '' }) { | ||
module.exports = function preprocessRanges(ranges) { | ||
ranges = JSON.parse(JSON.stringify(ranges)); | ||
@@ -40,10 +40,2 @@ if (typeof ranges === 'string') { | ||
if (modification && modification.mf) { | ||
ranges.push({ | ||
mf: modification.mf, | ||
min: 1, | ||
max: 1, | ||
}); | ||
} | ||
var possibilities = []; | ||
@@ -50,0 +42,0 @@ for (let i = 0; i < ranges.length; i++) { |
@@ -14,10 +14,10 @@ 'use strict'; | ||
maxCharge = Number.MAX_SAFE_INTEGER, | ||
charge = 0, | ||
precision = 100 | ||
} = options; | ||
if (!possibilities || possibilities.length === 0) return {}; | ||
let firstPossibility = possibilities[0]; | ||
let currentMinCharge = Math.max(minCharge, firstPossibility.minCharge); | ||
let currentMaxCharge = Math.min(maxCharge, firstPossibility.maxCharge); | ||
let currentMinCharge = Math.max(minCharge, firstPossibility.minCharge + charge); | ||
let currentMaxCharge = Math.min(maxCharge, firstPossibility.maxCharge + charge); | ||
@@ -24,0 +24,0 @@ this.minCharge = currentMinCharge; |
794
34039
4
9
+ Addedmf-utilities@^0.5.2
Updatedchemical-elements@^0.5.2
Updatedmf-parser@^0.5.2