msfiddle 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- msfiddle/__init__.py +44 -0
- msfiddle/config/fiddle_tcn_casmi.yml +113 -0
- msfiddle/config/fiddle_tcn_demo.yml +121 -0
- msfiddle/config/fiddle_tcn_demo_wo_da.yml +121 -0
- msfiddle/config/fiddle_tcn_embl.yml +100 -0
- msfiddle/config/fiddle_tcn_orbitrap.yml +163 -0
- msfiddle/config/fiddle_tcn_qtof.yml +196 -0
- msfiddle/dataset.py +152 -0
- msfiddle/demo/input_msms.mgf +491 -0
- msfiddle/demo/output_buddy.csv +22 -0
- msfiddle/demo/output_fiddle.csv +22 -0
- msfiddle/demo/output_fiddle_all.csv +22 -0
- msfiddle/demo/output_sirius.csv +22 -0
- msfiddle/download.py +194 -0
- msfiddle/main.py +356 -0
- msfiddle/model_tcn.py +222 -0
- msfiddle/utils/__init__.py +40 -0
- msfiddle/utils/mol_utils.py +138 -0
- msfiddle/utils/msms_utils.py +312 -0
- msfiddle/utils/pkl_utils.py +372 -0
- msfiddle/utils/refine_utils.py +246 -0
- msfiddle-0.1.0.dist-info/METADATA +151 -0
- msfiddle-0.1.0.dist-info/RECORD +27 -0
- msfiddle-0.1.0.dist-info/WHEEL +5 -0
- msfiddle-0.1.0.dist-info/entry_points.txt +4 -0
- msfiddle-0.1.0.dist-info/licenses/LICENSE +661 -0
- msfiddle-0.1.0.dist-info/top_level.txt +1 -0
msfiddle/model_tcn.py
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
from decimal import *
|
|
2
|
+
|
|
3
|
+
import torch
|
|
4
|
+
import torch.nn as nn
|
|
5
|
+
import torch.nn.functional as F
|
|
6
|
+
import torch.nn.init as init
|
|
7
|
+
from torch.nn.utils import weight_norm
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------
|
|
12
|
+
# formula prediction
|
|
13
|
+
# ---------------------------------------------
|
|
14
|
+
class Chomp1d(nn.Module):
|
|
15
|
+
def __init__(self, chomp_size):
|
|
16
|
+
super(Chomp1d, self).__init__()
|
|
17
|
+
self.chomp_size = chomp_size
|
|
18
|
+
|
|
19
|
+
def forward(self, x):
|
|
20
|
+
return x[:, :, :-self.chomp_size].contiguous()
|
|
21
|
+
|
|
22
|
+
class TemporalBlock(nn.Module):
|
|
23
|
+
def __init__(self, n_inputs, n_outputs, kernel_size, stride, dilation, padding, dropout=0.2):
|
|
24
|
+
super(TemporalBlock, self).__init__()
|
|
25
|
+
self.conv1 = weight_norm(nn.Conv1d(n_inputs, n_outputs, kernel_size, stride=stride, padding=padding, dilation=dilation))
|
|
26
|
+
self.chomp1 = Chomp1d(padding)
|
|
27
|
+
self.relu1 = nn.ReLU()
|
|
28
|
+
self.dropout1 = nn.Dropout(dropout)
|
|
29
|
+
|
|
30
|
+
self.conv2 = weight_norm(nn.Conv1d(n_outputs, n_outputs, kernel_size, stride=stride, padding=padding, dilation=dilation))
|
|
31
|
+
self.chomp2 = Chomp1d(padding)
|
|
32
|
+
self.relu2 = nn.ReLU()
|
|
33
|
+
self.dropout2 = nn.Dropout(dropout)
|
|
34
|
+
|
|
35
|
+
self.net = nn.Sequential(self.conv1, self.chomp1, self.relu1, self.dropout1,
|
|
36
|
+
self.conv2, self.chomp2, self.relu2, self.dropout2)
|
|
37
|
+
self.downsample = weight_norm(nn.Conv1d(n_inputs, n_outputs, 1)) if n_inputs != n_outputs else None
|
|
38
|
+
|
|
39
|
+
self.relu = nn.ReLU()
|
|
40
|
+
self.init_weights()
|
|
41
|
+
|
|
42
|
+
def init_weights(self):
|
|
43
|
+
# Kaiming (He) initialization for Conv1D layers
|
|
44
|
+
init.kaiming_normal_(self.conv1.weight, mode='fan_out', nonlinearity='relu')
|
|
45
|
+
init.kaiming_normal_(self.conv2.weight, mode='fan_out', nonlinearity='relu')
|
|
46
|
+
|
|
47
|
+
# Initialize biases to zero if they exist
|
|
48
|
+
if self.conv1.bias is not None:
|
|
49
|
+
init.zeros_(self.conv1.bias)
|
|
50
|
+
if self.conv2.bias is not None:
|
|
51
|
+
init.zeros_(self.conv2.bias)
|
|
52
|
+
|
|
53
|
+
# Downsample layer initialization (if it exists)
|
|
54
|
+
if self.downsample is not None:
|
|
55
|
+
init.kaiming_normal_(self.downsample.weight, mode='fan_out', nonlinearity='relu')
|
|
56
|
+
if self.downsample.bias is not None:
|
|
57
|
+
init.zeros_(self.downsample.bias)
|
|
58
|
+
|
|
59
|
+
def forward(self, x):
|
|
60
|
+
out = self.net(x)
|
|
61
|
+
res = x if self.downsample is None else self.downsample(x)
|
|
62
|
+
return self.relu(out + res)
|
|
63
|
+
|
|
64
|
+
class MS2FNet_tcn(nn.Module):
|
|
65
|
+
def __init__(self, config):
|
|
66
|
+
super(MS2FNet_tcn, self).__init__()
|
|
67
|
+
self.env_embedding_dim = config['add_embedding_dim'] + config['ce_embedding_dim'] + config['mass_embedding_dim']
|
|
68
|
+
|
|
69
|
+
layers = []
|
|
70
|
+
tcn_channels = config['tcn_channels']
|
|
71
|
+
num_levels = len(tcn_channels)
|
|
72
|
+
for i in range(num_levels):
|
|
73
|
+
dilation_size = config['tcn_dilations'][i]
|
|
74
|
+
in_channels = config['input_channels'] if i == 0 else tcn_channels[i-1]
|
|
75
|
+
out_channels = tcn_channels[i]
|
|
76
|
+
kernel_size = config['tcn_kernel_sizes'][i]
|
|
77
|
+
padding = (kernel_size - 1) * dilation_size
|
|
78
|
+
layers += [TemporalBlock(in_channels, out_channels, kernel_size, stride=1, dilation=dilation_size, padding=padding, dropout=config['tcn_dropout'])]
|
|
79
|
+
if i < num_levels - 1: # don't add pooling for the last layer
|
|
80
|
+
layers += [nn.MaxPool1d(kernel_size=2, stride=2)]
|
|
81
|
+
# self.encoder_ms = nn.Sequential(*layers)
|
|
82
|
+
self.encoder_ms = nn.ModuleList(layers)
|
|
83
|
+
|
|
84
|
+
self.embedding_m = weight_norm(nn.Linear(in_features=1,
|
|
85
|
+
out_features=config['mass_embedding_dim']))
|
|
86
|
+
self.embedding_ce = weight_norm(nn.Linear(in_features=1,
|
|
87
|
+
out_features=config['ce_embedding_dim']))
|
|
88
|
+
self.embedding_add = weight_norm(nn.Embedding(num_embeddings=config['num_add']+1,
|
|
89
|
+
embedding_dim=config['add_embedding_dim']))
|
|
90
|
+
|
|
91
|
+
self.fc = nn.Sequential(
|
|
92
|
+
weight_norm(nn.Linear(in_features=int(tcn_channels[-1]*2 + self.env_embedding_dim),
|
|
93
|
+
out_features=config['embedding_dim'])),
|
|
94
|
+
nn.ReLU(),
|
|
95
|
+
|
|
96
|
+
weight_norm(nn.Linear(in_features=config['embedding_dim'],
|
|
97
|
+
out_features=config['embedding_dim']))
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.global_pool = nn.AdaptiveAvgPool1d(1)
|
|
101
|
+
|
|
102
|
+
self.decoder_formula = self._build_decoder(config, 'formula')
|
|
103
|
+
self.decoder_mass = self._build_decoder(config, 'mass')
|
|
104
|
+
self.decoder_atomnum = self._build_decoder(config, 'atomnum')
|
|
105
|
+
self.decoder_hcnum = self._build_decoder(config, 'hcnum')
|
|
106
|
+
|
|
107
|
+
self.init_weights()
|
|
108
|
+
|
|
109
|
+
def init_weights(self):
|
|
110
|
+
for m in self.modules():
|
|
111
|
+
if isinstance(m, (nn.Conv1d, nn.Conv2d, nn.Linear)):
|
|
112
|
+
# Kaiming initialization for convolutional and linear layers with ReLU activation
|
|
113
|
+
nn.init.kaiming_normal_(m.weight, mode='fan_in', nonlinearity='relu')
|
|
114
|
+
if m.bias is not None:
|
|
115
|
+
nn.init.zeros_(m.bias)
|
|
116
|
+
elif isinstance(m, (nn.BatchNorm1d, nn.BatchNorm2d, nn.GroupNorm, nn.LayerNorm)):
|
|
117
|
+
# Batch normalization layers: Initialize weights to 1 and biases to 0
|
|
118
|
+
nn.init.constant_(m.weight, 1)
|
|
119
|
+
nn.init.constant_(m.bias, 0)
|
|
120
|
+
elif isinstance(m, nn.Embedding):
|
|
121
|
+
# Initialize embeddings with a normal distribution
|
|
122
|
+
nn.init.normal_(m.weight, mean=0, std=0.01)
|
|
123
|
+
|
|
124
|
+
def _build_decoder(self, config, decoder_type):
|
|
125
|
+
layers = []
|
|
126
|
+
decoder_layers = config[f'{decoder_type}_decoder_layers']
|
|
127
|
+
input_dim = config['embedding_dim']
|
|
128
|
+
for layer_dim in decoder_layers:
|
|
129
|
+
layers.append(weight_norm(nn.Linear(input_dim, layer_dim)))
|
|
130
|
+
layers.append(nn.LeakyReLU(negative_slope=0.2))
|
|
131
|
+
input_dim = layer_dim
|
|
132
|
+
|
|
133
|
+
output_dim = config['output_dim'] if decoder_type == 'formula' else 1
|
|
134
|
+
layers.append(nn.Linear(input_dim, output_dim))
|
|
135
|
+
layers.append(nn.LeakyReLU(negative_slope=0.2))
|
|
136
|
+
|
|
137
|
+
return nn.Sequential(*layers)
|
|
138
|
+
|
|
139
|
+
def forward(self, x, env):
|
|
140
|
+
# Adjust input tensors
|
|
141
|
+
x = x.unsqueeze(2) if len(x.size()) == 2 else x
|
|
142
|
+
x = torch.permute(x, (0, 2, 1))
|
|
143
|
+
|
|
144
|
+
# Spectra embedding
|
|
145
|
+
xs = []
|
|
146
|
+
for i, layer in enumerate(self.encoder_ms):
|
|
147
|
+
x = layer(x)
|
|
148
|
+
if i % 2 == 0: # TemporalBlock layer
|
|
149
|
+
xp = self.global_pool(x).squeeze(-1)
|
|
150
|
+
xs.append(xp)
|
|
151
|
+
|
|
152
|
+
# Metadata embedding
|
|
153
|
+
m = self.embedding_m(env[:, 0].unsqueeze(1))
|
|
154
|
+
ce = self.embedding_ce(env[:, 1].unsqueeze(1))
|
|
155
|
+
add = self.embedding_add(env[:, 2].int())
|
|
156
|
+
env = torch.cat([m, ce, add], dim=1)
|
|
157
|
+
|
|
158
|
+
# Feature fusion and projection
|
|
159
|
+
x = torch.cat(xs+[env], dim=1)
|
|
160
|
+
# print(x.min(), x.max())
|
|
161
|
+
encoded_x = self.fc(x)
|
|
162
|
+
|
|
163
|
+
# Decoders
|
|
164
|
+
f = self.decoder_formula(encoded_x)
|
|
165
|
+
mass = self.decoder_mass(encoded_x).squeeze(dim=1)
|
|
166
|
+
atomnum = self.decoder_atomnum(encoded_x).squeeze(dim=1)
|
|
167
|
+
hcnum = self.decoder_hcnum(encoded_x).squeeze(dim=1)
|
|
168
|
+
|
|
169
|
+
return encoded_x, f, mass, atomnum, hcnum
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------
|
|
172
|
+
# false discovery rate prediction
|
|
173
|
+
# ---------------------------------------------
|
|
174
|
+
class FDRNet(MS2FNet_tcn):
|
|
175
|
+
def __init__(self, config):
|
|
176
|
+
super(FDRNet, self).__init__(config)
|
|
177
|
+
self.decoder_fdr = self._build_decoder(config, 'fdr')
|
|
178
|
+
self.init_weights()
|
|
179
|
+
|
|
180
|
+
def _build_decoder(self, config, decoder_type):
|
|
181
|
+
layers = []
|
|
182
|
+
decoder_layers = config[f'{decoder_type}_decoder_layers']
|
|
183
|
+
input_dim = config['embedding_dim'] + config['output_dim']
|
|
184
|
+
for layer_dim in decoder_layers:
|
|
185
|
+
layers.append(weight_norm(nn.Linear(input_dim, layer_dim)))
|
|
186
|
+
layers.append(nn.LeakyReLU(negative_slope=0.2))
|
|
187
|
+
input_dim = layer_dim
|
|
188
|
+
|
|
189
|
+
output_dim = config['output_dim'] if decoder_type == 'formula' else 1
|
|
190
|
+
layers.append(nn.Linear(input_dim, output_dim))
|
|
191
|
+
layers.append(nn.LeakyReLU(negative_slope=0.2))
|
|
192
|
+
|
|
193
|
+
return nn.Sequential(*layers)
|
|
194
|
+
|
|
195
|
+
def forward(self, x, env, f):
|
|
196
|
+
# Adjust input tensors
|
|
197
|
+
x = x.unsqueeze(2) if len(x.size()) == 2 else x
|
|
198
|
+
x = torch.permute(x, (0, 2, 1))
|
|
199
|
+
|
|
200
|
+
# Spectra embedding
|
|
201
|
+
xs = []
|
|
202
|
+
for i, layer in enumerate(self.encoder_ms):
|
|
203
|
+
x = layer(x)
|
|
204
|
+
if i % 2 == 0: # TemporalBlock layer
|
|
205
|
+
xp = self.global_pool(x).squeeze(-1)
|
|
206
|
+
xs.append(xp)
|
|
207
|
+
|
|
208
|
+
# Metadata embedding
|
|
209
|
+
m = self.embedding_m(env[:, 0].unsqueeze(1))
|
|
210
|
+
ce = self.embedding_ce(env[:, 1].unsqueeze(1))
|
|
211
|
+
add = self.embedding_add(env[:, 2].int())
|
|
212
|
+
env = torch.cat([m, ce, add], dim=1)
|
|
213
|
+
|
|
214
|
+
# Feature fusion and projection
|
|
215
|
+
x = torch.cat(xs+[env], dim=1)
|
|
216
|
+
x = self.fc(x)
|
|
217
|
+
|
|
218
|
+
# Decoder for fdr prediction
|
|
219
|
+
x = torch.cat((x, f), dim=1)
|
|
220
|
+
fdr = self.decoder_fdr(x).squeeze(dim=1)
|
|
221
|
+
|
|
222
|
+
return fdr
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from .msms_utils import MSLEVEL_MAP
|
|
2
|
+
from .msms_utils import sdf2mgf, filter_spec, mgf_key_order, mass_calculator, precursor_mz_calculator
|
|
3
|
+
|
|
4
|
+
from .pkl_utils import spec2arr, spec2pair, generate_ms, parse_collision_energy, unify_precursor_type
|
|
5
|
+
|
|
6
|
+
from .mol_utils import ATOMS_WEIGHT, ATOMS_VALENCE, ATOMS_INDEX, ATOMS_INDEX_re
|
|
7
|
+
from .mol_utils import formula_to_vector, vector_to_formula, formula_to_dict, dict_to_formula, monoisotopic_mass_calculator
|
|
8
|
+
|
|
9
|
+
from .refine_utils import formula_refinement
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# From msms_utils
|
|
13
|
+
'MSLEVEL_MAP',
|
|
14
|
+
'sdf2mgf',
|
|
15
|
+
'filter_spec',
|
|
16
|
+
'mgf_key_order',
|
|
17
|
+
'mass_calculator',
|
|
18
|
+
'precursor_mz_calculator',
|
|
19
|
+
|
|
20
|
+
# From pkl_utils
|
|
21
|
+
'spec2arr',
|
|
22
|
+
'spec2pair',
|
|
23
|
+
'generate_ms',
|
|
24
|
+
'parse_collision_energy',
|
|
25
|
+
'unify_precursor_type',
|
|
26
|
+
|
|
27
|
+
# From mol_utils
|
|
28
|
+
'ATOMS_WEIGHT',
|
|
29
|
+
'ATOMS_VALENCE',
|
|
30
|
+
'ATOMS_INDEX',
|
|
31
|
+
'ATOMS_INDEX_re',
|
|
32
|
+
'formula_to_vector',
|
|
33
|
+
'vector_to_formula',
|
|
34
|
+
'formula_to_dict',
|
|
35
|
+
'dict_to_formula',
|
|
36
|
+
'monoisotopic_mass_calculator',
|
|
37
|
+
|
|
38
|
+
# From refine_utils
|
|
39
|
+
'formula_refinement'
|
|
40
|
+
]
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
from rdkit.Chem.rdMolDescriptors import CalcMolFormula
|
|
5
|
+
|
|
6
|
+
# monoisotopic mass
|
|
7
|
+
ATOMS_WEIGHT = {
|
|
8
|
+
'C': 12.000000,
|
|
9
|
+
'H': 1.007825,
|
|
10
|
+
'O': 15.994915,
|
|
11
|
+
'N': 14.003074,
|
|
12
|
+
'F': 18.998403,
|
|
13
|
+
'S': 31.972071,
|
|
14
|
+
'Cl': 34.968853,
|
|
15
|
+
'P': 30.973762,
|
|
16
|
+
'B': 10.012937,
|
|
17
|
+
'Br': 78.918337,
|
|
18
|
+
'I': 126.904473,
|
|
19
|
+
'Na': 22.989770,
|
|
20
|
+
'K': 38.963707,
|
|
21
|
+
}
|
|
22
|
+
ATOMS_VALENCE = {
|
|
23
|
+
'C': 4,
|
|
24
|
+
'H': 1,
|
|
25
|
+
'O': 2,
|
|
26
|
+
'N': 3,
|
|
27
|
+
'F': 1,
|
|
28
|
+
'S': 2,
|
|
29
|
+
'Cl': 1,
|
|
30
|
+
'P': 3,
|
|
31
|
+
'B': 3,
|
|
32
|
+
'Br': 1,
|
|
33
|
+
'I': 1,
|
|
34
|
+
'Na': 1,
|
|
35
|
+
'K': 1,
|
|
36
|
+
}
|
|
37
|
+
ATOMS_INDEX = {
|
|
38
|
+
'C': 0,
|
|
39
|
+
'H': 1,
|
|
40
|
+
'O': 2,
|
|
41
|
+
'N': 3,
|
|
42
|
+
'F': 4,
|
|
43
|
+
'S': 5,
|
|
44
|
+
'Cl': 6,
|
|
45
|
+
'P': 7,
|
|
46
|
+
'B': 8,
|
|
47
|
+
'Br': 9,
|
|
48
|
+
'I': 10,
|
|
49
|
+
'Na': 11,
|
|
50
|
+
'K': 12,
|
|
51
|
+
}
|
|
52
|
+
ATOMS_INDEX_re = {
|
|
53
|
+
0: 'C',
|
|
54
|
+
1: 'H',
|
|
55
|
+
2: 'O',
|
|
56
|
+
3: 'N',
|
|
57
|
+
4: 'F',
|
|
58
|
+
5: 'S',
|
|
59
|
+
6: 'Cl',
|
|
60
|
+
7: 'P',
|
|
61
|
+
8: 'B',
|
|
62
|
+
9: 'Br',
|
|
63
|
+
10: 'I',
|
|
64
|
+
11: 'Na',
|
|
65
|
+
12: 'K'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def monoisotopic_mass_calculator(x, mode):
|
|
69
|
+
assert mode in ['mol', 'f'], 'Invalid mode: {}'.format(mode)
|
|
70
|
+
|
|
71
|
+
if mode == 'mol':
|
|
72
|
+
x = CalcMolFormula(x)
|
|
73
|
+
|
|
74
|
+
f_dict = formula_to_dict(x)
|
|
75
|
+
iso_mass = np.sum([ATOMS_WEIGHT.get(k, 0) * v for k, v in f_dict.items()]).item()
|
|
76
|
+
return iso_mass
|
|
77
|
+
|
|
78
|
+
def dict_to_formula(formula_dict):
|
|
79
|
+
formula = ''
|
|
80
|
+
for k, v in formula_dict.items():
|
|
81
|
+
if v == 1:
|
|
82
|
+
formula += k
|
|
83
|
+
elif v > 1:
|
|
84
|
+
formula += k + str(v)
|
|
85
|
+
return formula
|
|
86
|
+
|
|
87
|
+
def formula_to_dict(formula):
|
|
88
|
+
if not isinstance(formula, str): # it is possible that all the predicted formula is None in BUDDY & SIRIUS
|
|
89
|
+
return {}
|
|
90
|
+
atom_counts = re.findall(r'([A-Z][a-z]*)(\d*)', formula)
|
|
91
|
+
formula_dict = {atom: int(count) if count else 1 for atom, count in atom_counts}
|
|
92
|
+
return formula_dict
|
|
93
|
+
|
|
94
|
+
def formula_to_vector(formula):
|
|
95
|
+
vector = [0] * len(ATOMS_INDEX)
|
|
96
|
+
formula_dict = formula_to_dict(formula)
|
|
97
|
+
|
|
98
|
+
for atom, count in formula_dict.items():
|
|
99
|
+
index = ATOMS_INDEX.get(atom, None)
|
|
100
|
+
if index is not None:
|
|
101
|
+
vector[index] = int(count) if count else 1
|
|
102
|
+
return vector
|
|
103
|
+
|
|
104
|
+
def vector_to_formula(vec, withH=True):
|
|
105
|
+
formula = ''
|
|
106
|
+
for idx, v in enumerate(vec):
|
|
107
|
+
v = round(float(v))
|
|
108
|
+
|
|
109
|
+
if v <= 0:
|
|
110
|
+
continue
|
|
111
|
+
elif not withH and ATOMS_INDEX_re[idx] == 'H':
|
|
112
|
+
continue
|
|
113
|
+
elif v == 1:
|
|
114
|
+
formula += ATOMS_INDEX_re[idx]
|
|
115
|
+
else:
|
|
116
|
+
formula += ATOMS_INDEX_re[idx] + str(v)
|
|
117
|
+
return formula
|
|
118
|
+
|
|
119
|
+
if __name__ == '__main__':
|
|
120
|
+
from rdkit import Chem
|
|
121
|
+
# ignore the warning
|
|
122
|
+
from rdkit import RDLogger
|
|
123
|
+
RDLogger.DisableLog('rdApp.*')
|
|
124
|
+
|
|
125
|
+
formula = 'C10H12N2O3'
|
|
126
|
+
vec = formula_to_vector(formula)
|
|
127
|
+
print(formula)
|
|
128
|
+
print(vec)
|
|
129
|
+
print(vector_to_formula(vec))
|
|
130
|
+
|
|
131
|
+
smiles = 'CC(C)C1=CC(=C(C=C1)O)C(=O)O'
|
|
132
|
+
mol = Chem.MolFromSmiles(smiles)
|
|
133
|
+
mass = monoisotopic_mass_calculator(mol, mode='mol')
|
|
134
|
+
print(smiles, mass)
|
|
135
|
+
|
|
136
|
+
formula = 'C10H12N2O3'
|
|
137
|
+
mass = monoisotopic_mass_calculator(formula, mode='f')
|
|
138
|
+
print(formula, mass)
|