Skip to content

Commit 67c17ac

Browse files
feat: add support for selecting peak shape algorithm (lorentzian or gaussian)
1 parent 8e981d0 commit 67c17ac

File tree

3 files changed

+281
-236
lines changed

3 files changed

+281
-236
lines changed

app/scripts/nmr-cli/src/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,20 +25,21 @@ Options for 'parse-spectra' command:
2525
-p, --path Directory path
2626
-s, --capture-snapshot Capture snapshot
2727
28-
28+
2929
Options for 'predict' command:
30-
-n, --nucleus Predicted nucleus "1H" or "13C" (required)
30+
-ps,--peakShape Peak shape algorithm (default: "lorentzian") choices: ["gaussian", "lorentzian"]
31+
-n, --nucleus Predicted nucleus, choices: ["1H","13C"] (required)
3132
-i, --id Input ID (default: 1)
3233
-t, --type NMR type (default: "nmr;1H;1d")
3334
-s, --shifts Chemical shifts (default: "1")
3435
--solvent NMR solvent (default: "Dimethylsulphoxide-D6 (DMSO-D6, C2D6SO)")
3536
-m, --molText MOL text (required)
3637
--from From in (ppm)
3738
--to To in (ppm)
38-
--nbPoints Number of points (default: 128)
39+
--nbPoints Number of points (default: 1024)
3940
--lineWidth Line width (default: 1)
4041
--frequency NMR frequency (MHz) (default: 400)
41-
--tolerance Tolerance (default: 0.001)
42+
--tolerance Tolerance to group peaks with close shift (default: 0.001)
4243
4344
4445

app/scripts/nmr-cli/src/prediction/generatePredictedSpectrumData.ts

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,21 @@ export interface ShiftsItem {
88
spheres: number
99
}
1010

11-
interface LorentzianOptions {
11+
type PeakShape = 'gaussian' | 'lorentzian'
12+
13+
interface PeakShapeOptions {
1214
x: number
1315
fwhm: number
1416
}
1517

16-
function lorentzian2(options: LorentzianOptions) {
17-
const { x, fwhm } = options
18-
return fwhm ** 2 / (4 * x ** 2 + fwhm ** 2)
19-
}
20-
21-
interface GenerateSpectrumOptions {
18+
export interface GenerateSpectrumOptions {
2219
from?: number
2320
to?: number
2421
nbPoints?: number
2522
lineWidth?: number
2623
frequency?: number
2724
tolerance?: number
25+
peakShape?: PeakShape
2826
}
2927

3028
interface GroupItem {
@@ -38,7 +36,37 @@ interface Data1D {
3836
re: number[]
3937
}
4038

41-
const getLorentzianFactor = (area = 0.9999) => {
39+
const GAUSSIAN_EXP_FACTOR = -4 * Math.LN2
40+
41+
function lorentzian(options: PeakShapeOptions) {
42+
const { x, fwhm } = options
43+
return fwhm ** 2 / (4 * x ** 2 + fwhm ** 2)
44+
}
45+
46+
function gaussian(options: PeakShapeOptions) {
47+
const { x, fwhm } = options
48+
return Math.exp(GAUSSIAN_EXP_FACTOR * Math.pow(x / fwhm, 2))
49+
}
50+
51+
function erfinv(x: number): number {
52+
let a = 0.147
53+
if (x === 0) return 0
54+
let ln1MinusXSqrd = Math.log(1 - x * x)
55+
let lnEtcBy2Plus2 = ln1MinusXSqrd / 2 + 2 / (Math.PI * a)
56+
let firstSqrt = Math.sqrt(lnEtcBy2Plus2 ** 2 - ln1MinusXSqrd / a)
57+
let secondSqrt = Math.sqrt(firstSqrt - lnEtcBy2Plus2)
58+
return secondSqrt * (x > 0 ? 1 : -1)
59+
}
60+
61+
function getGaussianFactor(area = 0.9999) {
62+
if (area >= 1) {
63+
throw new Error('area should be (0 - 1)')
64+
}
65+
66+
return Math.sqrt(2) * erfinv(area)
67+
}
68+
69+
function getLorentzianFactor(area = 0.9999) {
4270
if (area >= 1) {
4371
throw new Error('area should be (0 - 1)')
4472
}
@@ -49,6 +77,22 @@ const getLorentzianFactor = (area = 0.9999) => {
4977
)
5078
}
5179

80+
function peakShapeFunction(options: PeakShapeOptions, shape: PeakShape) {
81+
if (shape === 'lorentzian') {
82+
return lorentzian(options)
83+
}
84+
85+
return gaussian(options)
86+
}
87+
88+
function getPeakShapeFactor(area: number, shape: PeakShape) {
89+
if (shape === 'lorentzian') {
90+
return getLorentzianFactor(area)
91+
}
92+
93+
return getGaussianFactor(area)
94+
}
95+
5296
function groupEquivalentShifts(shifts: ShiftsItem[], tolerance = 0.001) {
5397
const groups: GroupItem[] = []
5498

@@ -81,6 +125,7 @@ export function generatePredictedSpectrumData(
81125
frequency = 400,
82126
lineWidth = 1,
83127
tolerance = 0.001,
128+
peakShape = 'lorentzian',
84129
} = options
85130

86131
if (!shifts || shifts.length === 0) return []
@@ -107,24 +152,22 @@ export function generatePredictedSpectrumData(
107152
const stepSize = (to - from) / (nbPoints - 1)
108153
const groupedShifts = groupEquivalentShifts(acceptedShifts, tolerance)
109154

110-
console.log(groupedShifts)
111-
112-
const limit = (lineWidth * getLorentzianFactor(0.99)) / frequency
113-
console.log(limit)
155+
const limit = (lineWidth * getPeakShapeFactor(0.99, peakShape)) / frequency
114156
for (let i = 0; i < nbPoints; i++) {
115157
const x = from + i * stepSize
116158
let intensity = 0
117159
for (const { prediction, count } of groupedShifts) {
118160
if (Math.abs(x - prediction) <= limit) {
119161
intensity +=
120-
lorentzian2({ x: x - prediction, fwhm: lineWidth / frequency }) *
121-
count
162+
peakShapeFunction(
163+
{ x: x - prediction, fwhm: lineWidth / frequency },
164+
peakShape
165+
) * count
122166
}
123167
}
124-
if (intensity > 0) {
125-
data.x.push(x)
126-
data.re.push(intensity)
127-
}
168+
169+
data.x.push(x)
170+
data.re.push(intensity)
128171
}
129172

130173
return data

0 commit comments

Comments
 (0)