Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,30 @@ test('stableIsotopesObject', () => {
mass: 12,
symbol: 'C',
mostAbundant: true,
deltaNeutrons: 0,
});

expect(data['13C']).toStrictEqual({
name: 'Carbon',
mass: 13.00335483507,
symbol: 'C',
mostAbundant: false,
deltaNeutrons: 1,
});

expect(data['10B']).toStrictEqual({
name: 'Boron',
mass: 10.01293695,
symbol: 'B',
mostAbundant: false,
deltaNeutrons: -1,
});

expect(data['11B']).toStrictEqual({
name: 'Boron',
mass: 11.00930536,
symbol: 'B',
mostAbundant: true,
deltaNeutrons: 0,
});
});
7 changes: 7 additions & 0 deletions packages/chemical-elements/src/stableIsotopesObject.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { elementsAndIsotopes } from './elementsAndIsotopes.js';

interface StableIsotope {
/** Element name (e.g., "Carbon"). */
name: string;
/** Exact mass of the isotope in Da. */
mass: number;
/** Element symbol (e.g., "C"). */
symbol: string;
/** Whether this is the most abundant stable isotope of the element. */
mostAbundant: boolean;
/** Difference in neutron count relative to the most abundant isotope (e.g., +1 for 13C, -1 for 10B). */
deltaNeutrons: number;
}

export const stableIsotopesObject: Record<string, StableIsotope | undefined> =
Expand All @@ -31,6 +37,7 @@ for (const element of elementsAndIsotopes) {
mass: isotope.mass,
symbol: element.symbol,
mostAbundant: false,
deltaNeutrons: isotope.nominal - mostAbundant,
};
if (isotope.nominal === mostAbundant) {
entry.mostAbundant = true;
Expand Down
18 changes: 9 additions & 9 deletions packages/isotopic-distribution/src/IsotopicDistribution.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
// we calculate information for each part
for (const ionization of ionizations) {
let part = structuredClone(partOriginal);
part.em = part.monoisotopicMass; // TODO: To remove !!! we change the name !?

Check warning on line 53 in packages/isotopic-distribution/src/IsotopicDistribution.js

View workflow job for this annotation

GitHub Actions / nodejs / lint-eslint

Unexpected 'todo' comment: 'TODO: To remove !!! we change the name...'
part.isotopesInfo = new MF(
ionization.mf ? `${part.mf}(${ionization.mf})` : part.mf || '',
).getIsotopesInfo();
Expand Down Expand Up @@ -94,7 +94,7 @@
};
let finalDistribution = new Distribution();
this.confidence = 0;
// TODO need to cache each part without ionization

Check warning on line 97 in packages/isotopic-distribution/src/IsotopicDistribution.js

View workflow job for this annotation

GitHub Actions / nodejs / lint-eslint

Unexpected 'todo' comment: 'TODO need to cache each part without...'
// in case of many ionization we don't need to recalculate everything !
for (let part of this.parts) {
let totalDistribution = new Distribution([
Expand All @@ -104,6 +104,12 @@
composition: this.fwhm === MINIMAL_FWHM ? {} : undefined, // should we calculate composition in isotopes of each peak
},
]);
let partNaturalDeltaNeutrons = 0;
for (let isotope of part.isotopesInfo.isotopes) {
partNaturalDeltaNeutrons +=
(isotope.naturalDeltaNeutrons ?? 0) * isotope.number;
}

let charge = part.ms?.charge || part.isotopesInfo.charge || 0;
let absoluteCharge = Math.abs(charge);
if (charge || this.allowNeutral) {
Expand Down Expand Up @@ -162,17 +168,11 @@

part.isotopicDistribution = totalDistribution.array;

const absoluteChargeOrOne = absoluteCharge || 1;

for (let entry of totalDistribution.array) {
if (!entry.composition) continue;
const deltaNeutrons =
Math.round(entry.x * absoluteChargeOrOne - part.monoisotopicMass) +
0; // +0 to avoid -0
Object.assign(entry, {
...getDerivedCompositionInfo(entry.composition),
deltaNeutrons,
});
const info = getDerivedCompositionInfo(entry.composition);
info.deltaNeutrons -= partNaturalDeltaNeutrons;
Object.assign(entry, info);
}

if (finalDistribution.array.length === 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports[`test isotopicDistribution > create distribution for many ionizations, C
"y": 0.0107,
},
],
"naturalDeltaNeutrons": 0,
"number": 1,
},
],
Expand Down Expand Up @@ -94,6 +95,7 @@ exports[`test isotopicDistribution > create distribution for many ionizations, C
"y": 0.0107,
},
],
"naturalDeltaNeutrons": 0,
"number": 1,
},
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,65 @@ describe('isotopicDistribution with array', () => {
],
2,
);

const peaks = isotopicDistribution
.getPeaks({ maxValue: 100 })
.toSorted((a, b) => b.y - a.y);
const deltaNeutrons = peaks.slice(0, 4).map((p) => p.deltaNeutrons);

expect(deltaNeutrons).toStrictEqual([0, 0, 0, 1]);
});

it('C,[13C] simplified', () => {
let isotopicDistribution = new IsotopicDistribution(
[
{
mf: 'C',
ionization: { mf: 'H+' },
intensity: 1,
},
{
mf: '[13C]',
intensity: 2,
},
],
{
fwhm: 1e-10,
},
);

const peaks = isotopicDistribution
.getPeaks({ maxValue: 100 })
.toSorted((a, b) => b.y - a.y);

expect(peaks.slice(0, 3)).toBeDeepCloseTo([
{
x: 13.00335483507,
y: 100,
composition: { '13C': 1 },
label: '¹³C',
shortComposition: { '13C': 1 },
shortLabel: '¹³C',
deltaNeutrons: 0,
},
{
x: 13.00727645232093,
y: 49.459311525,
composition: { '12C': 1, '1H': 1 },
label: '¹²C¹H',
shortComposition: {},
shortLabel: '',
deltaNeutrons: 0,
},
{
x: 14.01063128739093,
y: 0.534938475,
composition: { '13C': 1, '1H': 1 },
label: '¹³C¹H',
shortComposition: { '13C': 1 },
shortLabel: '¹³C',
deltaNeutrons: 1,
},
]);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ describe('test isotopicDistribution', () => {
});
const peaks = isotopicDistribution.getXY();

expect(peaks.deltaNeutrons).toStrictEqual([0]);
expect(peaks).toBeDeepCloseTo({
x: [13.00335483507],
y: [100],
Expand All @@ -66,6 +67,46 @@ describe('test isotopicDistribution', () => {
});
});

it('[13C] getPeaks deltaNeutrons', () => {
const isotopicDistribution = new IsotopicDistribution('[13C]', {
fwhm: 0,
});
const peaks = isotopicDistribution.getPeaks();

expect(peaks).toStrictEqual([
{
x: 13.00335483507,
y: 100,
composition: { '13C': 1 },
label: '¹³C',
shortComposition: { '13C': 1 },
deltaNeutrons: 0,
shortLabel: '¹³C',
},
]);
});

it('C[13C] getPeaks deltaNeutrons', () => {
const isotopicDistribution = new IsotopicDistribution('C[13C]', {
fwhm: 0,
});
const peaks = isotopicDistribution.getPeaks({ maxValue: 1 });

expect(peaks[0]).toMatchObject({ deltaNeutrons: 0 });
expect(peaks[1]).toMatchObject({ deltaNeutrons: 1 });
});

it('C2[13C]2 getPeaks deltaNeutrons', () => {
const isotopicDistribution = new IsotopicDistribution('C2[13C]2', {
fwhm: 0,
});
const peaks = isotopicDistribution.getPeaks({ maxValue: 1 });

expect(peaks[0]).toMatchObject({ deltaNeutrons: 0 });
expect(peaks[1]).toMatchObject({ deltaNeutrons: 1 });
expect(peaks[2]).toMatchObject({ deltaNeutrons: 2 });
});

it('create distribution of C Ag+', () => {
const isotopicDistribution = new IsotopicDistribution('C', {
ionizations: 'Ag+',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,6 @@ test('getDerivedCompositionInfo', () => {
label: '¹²C₂₉₄¹³C₆¹H₅₀₀¹⁴N₁₀₀¹⁶O₁₀₀³²S₈₇³⁴S₁₂³³S',
shortComposition: { '13C': 6, '34S': 12, '33S': 1 },
shortLabel: '¹³C₆³⁴S₁₂³³S',
deltaNeutrons: 31,
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function getDerivedCompositionInfo(composition) {
const shortComposition = {};
let label = '';
let shortLabel = '';
let deltaNeutrons = 0;
for (let key in composition) {
let isotopeLabel = '';
for (let i = 0; i < key.length; i++) {
Expand All @@ -26,10 +27,12 @@ export function getDerivedCompositionInfo(composition) {
}
}
label += isotopeLabel;
if (stableIsotopesObject[key].mostAbundant) continue;
const isotope = stableIsotopesObject[key];
deltaNeutrons += isotope.deltaNeutrons * composition[key];
if (isotope.mostAbundant) continue;
shortLabel += isotopeLabel;
shortComposition[key] = composition[key];
}

return { label, shortComposition, shortLabel };
return { label, shortComposition, shortLabel, deltaNeutrons };
}
31 changes: 31 additions & 0 deletions packages/mf-parser/src/__tests__/getIsotopesInfo.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,36 @@ test('getIsotopesInfo from [13C]', () => {
atom: 'C',
number: 1,
distribution: [{ x: 12, y: 1 }],
naturalDeltaNeutrons: 0,
});
});

test('naturalDeltaNeutrons for Kind.ATOM', () => {
let info = new MF('C').getIsotopesInfo();

expect(info.isotopes[0].naturalDeltaNeutrons).toBe(0);
});

test('naturalDeltaNeutrons for Kind.ISOTOPE', () => {
let info = new MF('[13C]').getIsotopesInfo();

expect(info.isotopes[0].naturalDeltaNeutrons).toBe(1);
});

test('naturalDeltaNeutrons for Kind.ISOTOPE [12C]', () => {
let info = new MF('[12C]').getIsotopesInfo();

expect(info.isotopes[0].naturalDeltaNeutrons).toBe(0);
});

test('naturalDeltaNeutrons for Kind.ISOTOPE_RATIO C{50,50}', () => {
let info = new MF('C{50,50}').getIsotopesInfo();

expect(info.isotopes[0].naturalDeltaNeutrons).toBe(0);
});

test('naturalDeltaNeutrons for Kind.ISOTOPE_RATIO C{10,90}', () => {
let info = new MF('C{10,90}').getIsotopesInfo();

expect(info.isotopes[0].naturalDeltaNeutrons).toBe(1);
});
17 changes: 16 additions & 1 deletion packages/mf-parser/src/util/getIsotopesInfo.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
elementsAndStableIsotopesObject as elements,
isotopesObject as isotopes,
stableIsotopesObject,
} from 'chemical-elements';

import { Kind } from '../Kind';
Expand Down Expand Up @@ -33,7 +34,8 @@ function getProcessedPart(part) {
for (let line of part) {
switch (line.kind) {
case Kind.ISOTOPE: {
let isotope = isotopes[line.value.isotope + line.value.atom];
let isotopeKey = line.value.isotope + line.value.atom;
let isotope = isotopes[isotopeKey];
if (!isotope) {
throw new Error(
'unknown isotope:',
Expand All @@ -45,6 +47,8 @@ function getProcessedPart(part) {
atom: line.value.atom,
number: line.multiplier,
distribution: [{ x: isotope.mass, y: 1 }],
naturalDeltaNeutrons:
stableIsotopesObject[isotopeKey]?.deltaNeutrons ?? 0,
});
break;
}
Expand All @@ -57,10 +61,20 @@ function getProcessedPart(part) {
element.isotopes,
line.value.ratio,
);
let maxIndex = 0;
for (let i = 1; i < distribution.length; i++) {
if (distribution[i].y > distribution[maxIndex].y) {
maxIndex = i;
}
}
let mostAbundantKey =
Math.round(distribution[maxIndex].x) + line.value.atom;
result.isotopes.push({
atom: line.value.atom,
number: line.multiplier,
distribution,
naturalDeltaNeutrons:
stableIsotopesObject[mostAbundantKey]?.deltaNeutrons ?? 0,
});
}
break;
Expand All @@ -74,6 +88,7 @@ function getProcessedPart(part) {
x: e.mass,
y: e.abundance,
})),
naturalDeltaNeutrons: 0,
});
break;
}
Expand Down
Loading