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/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)