Spaces:
Sleeping
Sleeping
Commit
·
994fb49
1
Parent(s):
b1aa639
new experiments
Browse files- mvp/models/contrastive.py +42 -44
- mvp/models/mol_encoder.py +1 -2
- mvp/models/spec_encoder.py +5 -3
- mvp/params_formSpec.yaml +16 -13
- mvp/params_tmp.yaml +19 -15
- mvp/run.sh +3 -3
- mvp/tune.py +264 -0
- notebooks/2v1.ipynb +261 -0
- notebooks/UMAP_spectra_embeddings.ipynb +0 -0
- notebooks/attribute_viz.ipynb +56 -0
- notebooks/filip_viz.ipynb +706 -0
- notebooks/hyperparameter_tuning_result.ipynb +0 -0
- notebooks/magma_script.ipynb +146 -0
- notebooks/peak_embedding_UMAP.ipynb +0 -0
- notebooks/peak_formula_analysis.ipynb +370 -0
- notebooks/visualization.ipynb +201 -0
mvp/models/contrastive.py
CHANGED
|
@@ -27,17 +27,15 @@ class ContrastiveModel(RetrievalMassSpecGymModel):
|
|
| 27 |
super().__init__(**kwargs)
|
| 28 |
self.save_hyperparameters()
|
| 29 |
|
| 30 |
-
if 'use_fp' not in self.hparams:
|
| 31 |
-
self.hparams.use_fp = False
|
| 32 |
if 'use_fp' not in self.hparams:
|
| 33 |
self.hparams.use_fp = False
|
| 34 |
if 'use_NL_spec' not in self.hparams:
|
| 35 |
self.hparams.use_NL_spec = False
|
| 36 |
|
| 37 |
-
if 'loss_strategy' not in self.hparams:
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
|
| 42 |
self.spec_enc_model = model_utils.get_spec_encoder(self.hparams.spec_enc, self.hparams)
|
| 43 |
self.mol_enc_model = model_utils.get_mol_encoder(self.hparams.mol_enc, self.hparams)
|
|
@@ -58,26 +56,26 @@ class ContrastiveModel(RetrievalMassSpecGymModel):
|
|
| 58 |
self.result_dct = defaultdict(lambda: defaultdict(list))
|
| 59 |
|
| 60 |
|
| 61 |
-
def _loss_setup(self):
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
|
| 82 |
def forward(self, batch, stage):
|
| 83 |
g = batch['cand'] if stage == Stage.TEST else batch['mol']
|
|
@@ -99,11 +97,11 @@ class ContrastiveModel(RetrievalMassSpecGymModel):
|
|
| 99 |
def compute_loss(self, batch: dict, spec_enc, mol_enc, output):
|
| 100 |
loss = 0
|
| 101 |
losses = {}
|
| 102 |
-
contr_loss,
|
| 103 |
-
contr_loss = self.loss_wts['contr_wt'] *contr_loss
|
| 104 |
losses['contr_loss'] = contr_loss.detach().item()
|
| 105 |
-
losses['cong_loss'] = cong_loss.detach().item()
|
| 106 |
-
losses['noncong_loss'] = noncong_loss.detach().item()
|
| 107 |
|
| 108 |
loss+=contr_loss
|
| 109 |
if self.hparams.pred_fp:
|
|
@@ -217,7 +215,7 @@ class ContrastiveModel(RetrievalMassSpecGymModel):
|
|
| 217 |
on_epoch=True,
|
| 218 |
)
|
| 219 |
|
| 220 |
-
def test_step(self, batch):
|
| 221 |
# Unpack inputs
|
| 222 |
identifiers = batch['identifier']
|
| 223 |
cand_smiles = batch['cand_smiles']
|
|
@@ -274,17 +272,17 @@ class ContrastiveModel(RetrievalMassSpecGymModel):
|
|
| 274 |
]
|
| 275 |
return monitors
|
| 276 |
|
| 277 |
-
def _update_loss_weights(self)-> None:
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
def on_train_epoch_end(self) -> None:
|
| 287 |
-
|
| 288 |
|
| 289 |
class MultiViewContrastive(ContrastiveModel):
|
| 290 |
|
|
@@ -473,7 +471,7 @@ class FilipContrastive(ContrastiveModel):
|
|
| 473 |
|
| 474 |
return losses
|
| 475 |
|
| 476 |
-
def test_step(self, batch):
|
| 477 |
# Unpack inputs
|
| 478 |
identifiers = batch['identifier']
|
| 479 |
cand_smiles = batch['cand_smiles']
|
|
@@ -758,7 +756,7 @@ class CrossAttenContrastive(ContrastiveModel):
|
|
| 758 |
g_n_nodes = batch['mol_n_nodes']
|
| 759 |
|
| 760 |
# encode peaks and nodes
|
| 761 |
-
spec_enc = self.spec_enc_model(spec)
|
| 762 |
mol_enc = self.mol_enc_model(g)
|
| 763 |
|
| 764 |
# pad mol_enc and spec_enc to have the same length
|
|
|
|
| 27 |
super().__init__(**kwargs)
|
| 28 |
self.save_hyperparameters()
|
| 29 |
|
|
|
|
|
|
|
| 30 |
if 'use_fp' not in self.hparams:
|
| 31 |
self.hparams.use_fp = False
|
| 32 |
if 'use_NL_spec' not in self.hparams:
|
| 33 |
self.hparams.use_NL_spec = False
|
| 34 |
|
| 35 |
+
# if 'loss_strategy' not in self.hparams:
|
| 36 |
+
# self.hparams.loss_strategy = 'static'
|
| 37 |
+
# self.hparams.contr_wt = 1.0
|
| 38 |
+
# self.hparams.use_contr = True
|
| 39 |
|
| 40 |
self.spec_enc_model = model_utils.get_spec_encoder(self.hparams.spec_enc, self.hparams)
|
| 41 |
self.mol_enc_model = model_utils.get_mol_encoder(self.hparams.mol_enc, self.hparams)
|
|
|
|
| 56 |
self.result_dct = defaultdict(lambda: defaultdict(list))
|
| 57 |
|
| 58 |
|
| 59 |
+
# def _loss_setup(self):
|
| 60 |
+
# self.loss_wts = {}
|
| 61 |
+
# self.loss_updates = {}
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
# for p, loss in zip(['use_contr','pred_fp', 'use_cons_spec', 'aug_cands'], ['contr_wt','fp_wt','cons_spec_wt' ,'aug_cands_wt']):
|
| 65 |
+
# if p not in self.hparams:
|
| 66 |
+
# self.hparams[p] = False
|
| 67 |
+
# if self.hparams[p]:
|
| 68 |
+
# if self.hparams.loss_strategy == 'linear':
|
| 69 |
+
# start_wt = self.hparams[loss+'_update']['start']
|
| 70 |
+
# end_wt = self.hparams[loss+'_update']['end']
|
| 71 |
+
# change = (end_wt - start_wt)/self.hparams.max_epochs
|
| 72 |
+
# self.loss_updates[loss] = change
|
| 73 |
+
# self.loss_wts[loss] = start_wt
|
| 74 |
+
# elif self.hparams.loss_strategy == 'manual':
|
| 75 |
+
# self.loss_updates[loss] = self.hparams[loss+'_update']
|
| 76 |
+
# self.loss_wts[loss] = self.hparams[loss]
|
| 77 |
+
# else:
|
| 78 |
+
# self.loss_wts[loss] = self.hparams[loss]
|
| 79 |
|
| 80 |
def forward(self, batch, stage):
|
| 81 |
g = batch['cand'] if stage == Stage.TEST else batch['mol']
|
|
|
|
| 97 |
def compute_loss(self, batch: dict, spec_enc, mol_enc, output):
|
| 98 |
loss = 0
|
| 99 |
losses = {}
|
| 100 |
+
contr_loss, _, _ = contrastive_loss(spec_enc, mol_enc, self.hparams.contr_temp)
|
| 101 |
+
# contr_loss = self.loss_wts['contr_wt'] *contr_loss
|
| 102 |
losses['contr_loss'] = contr_loss.detach().item()
|
| 103 |
+
# losses['cong_loss'] = cong_loss.detach().item()
|
| 104 |
+
# losses['noncong_loss'] = noncong_loss.detach().item()
|
| 105 |
|
| 106 |
loss+=contr_loss
|
| 107 |
if self.hparams.pred_fp:
|
|
|
|
| 215 |
on_epoch=True,
|
| 216 |
)
|
| 217 |
|
| 218 |
+
def test_step(self, batch, batch_idx):
|
| 219 |
# Unpack inputs
|
| 220 |
identifiers = batch['identifier']
|
| 221 |
cand_smiles = batch['cand_smiles']
|
|
|
|
| 272 |
]
|
| 273 |
return monitors
|
| 274 |
|
| 275 |
+
# def _update_loss_weights(self)-> None:
|
| 276 |
+
# if self.hparams.loss_strategy == 'linear':
|
| 277 |
+
# for loss in self.loss_wts:
|
| 278 |
+
# self.loss_wts[loss] += self.loss_updates[loss]
|
| 279 |
+
# elif self.hparams.loss_strategy == 'manual':
|
| 280 |
+
# for loss in self.loss_wts:
|
| 281 |
+
# if self.current_epoch in self.loss_updates[loss]:
|
| 282 |
+
# self.loss_wts[loss] = self.loss_updates[loss][self.current_epoch]
|
| 283 |
+
|
| 284 |
+
# def on_train_epoch_end(self) -> None:
|
| 285 |
+
# self._update_loss_weights()
|
| 286 |
|
| 287 |
class MultiViewContrastive(ContrastiveModel):
|
| 288 |
|
|
|
|
| 471 |
|
| 472 |
return losses
|
| 473 |
|
| 474 |
+
def test_step(self, batch, batch_idx):
|
| 475 |
# Unpack inputs
|
| 476 |
identifiers = batch['identifier']
|
| 477 |
cand_smiles = batch['cand_smiles']
|
|
|
|
| 756 |
g_n_nodes = batch['mol_n_nodes']
|
| 757 |
|
| 758 |
# encode peaks and nodes
|
| 759 |
+
spec_enc = self.spec_enc_model(spec, spec_n_forms)
|
| 760 |
mol_enc = self.mol_enc_model(g)
|
| 761 |
|
| 762 |
# pad mol_enc and spec_enc to have the same length
|
mvp/models/mol_encoder.py
CHANGED
|
@@ -12,14 +12,13 @@ class MolEnc(nn.Module):
|
|
| 12 |
|
| 13 |
self.return_emb = False
|
| 14 |
|
| 15 |
-
if args.model in ('
|
| 16 |
self.return_emb = True
|
| 17 |
|
| 18 |
dropout = [args.gnn_dropout for _ in range(len(args.gnn_channels))]
|
| 19 |
batchnorm = [True for _ in range(len(args.gnn_channels))]
|
| 20 |
gnn_map = {
|
| 21 |
"gcn": GCN(in_dim, args.gnn_channels, batchnorm = batchnorm, dropout = dropout),
|
| 22 |
-
"gat": GAT(in_dim, args.gnn_channels, args.attn_heads)
|
| 23 |
}
|
| 24 |
self.GNN = gnn_map[args.gnn_type]
|
| 25 |
self.pool = dgl.nn.pytorch.glob.MaxPooling()
|
|
|
|
| 12 |
|
| 13 |
self.return_emb = False
|
| 14 |
|
| 15 |
+
if args.model in ('filipContrastive', 'crossAttenContrastive'):
|
| 16 |
self.return_emb = True
|
| 17 |
|
| 18 |
dropout = [args.gnn_dropout for _ in range(len(args.gnn_channels))]
|
| 19 |
batchnorm = [True for _ in range(len(args.gnn_channels))]
|
| 20 |
gnn_map = {
|
| 21 |
"gcn": GCN(in_dim, args.gnn_channels, batchnorm = batchnorm, dropout = dropout),
|
|
|
|
| 22 |
}
|
| 23 |
self.GNN = gnn_map[args.gnn_type]
|
| 24 |
self.pool = dgl.nn.pytorch.glob.MaxPooling()
|
mvp/models/spec_encoder.py
CHANGED
|
@@ -121,12 +121,14 @@ class SpecFormulaTransformer(nn.Module):
|
|
| 121 |
self.use_cls = args.use_cls
|
| 122 |
if args.use_cls:
|
| 123 |
self.cls_embed = torch.nn.Embedding(1,args.formula_dims[-1])
|
| 124 |
-
encoder_layer = nn.TransformerEncoderLayer(d_model=args.formula_dims[-1], nhead=
|
| 125 |
-
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=
|
| 126 |
|
| 127 |
if not out_dim:
|
| 128 |
out_dim = args.final_embedding_dim
|
| 129 |
-
|
|
|
|
|
|
|
| 130 |
|
| 131 |
def forward(self, spec, n_peaks):
|
| 132 |
h = self.formulaEnc(spec)
|
|
|
|
| 121 |
self.use_cls = args.use_cls
|
| 122 |
if args.use_cls:
|
| 123 |
self.cls_embed = torch.nn.Embedding(1,args.formula_dims[-1])
|
| 124 |
+
encoder_layer = nn.TransformerEncoderLayer(d_model=args.formula_dims[-1], nhead=args.formula_attn_heads, batch_first=True)
|
| 125 |
+
self.transformer = nn.TransformerEncoder(encoder_layer, num_layers=args.formula_transformer_layers)
|
| 126 |
|
| 127 |
if not out_dim:
|
| 128 |
out_dim = args.final_embedding_dim
|
| 129 |
+
|
| 130 |
+
if not self.returnEmb:
|
| 131 |
+
self.fc = nn.Linear(args.formula_dims[-1], out_dim)
|
| 132 |
|
| 133 |
def forward(self, spec, n_peaks):
|
| 134 |
h = self.formulaEnc(spec)
|
mvp/params_formSpec.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# Experiment setup
|
| 2 |
job_key: ''
|
| 3 |
-
run_name: '
|
| 4 |
run_details: ""
|
| 5 |
project_name: ''
|
| 6 |
wandb_entity_name: 'mass-spec-ml'
|
|
@@ -12,14 +12,14 @@ checkpoint_pth:
|
|
| 12 |
# Training setup
|
| 13 |
max_epochs: 2000
|
| 14 |
accelerator: 'gpu'
|
| 15 |
-
devices: [
|
| 16 |
log_every_n_steps: 250
|
| 17 |
val_check_interval: 1.0
|
| 18 |
|
| 19 |
# Data paths
|
| 20 |
candidates_pth: /r/hassounlab/spectra_data/msgym/molecules/MassSpecGym_retrieval_candidates_mass.json # "../data/MassSpecGym/data/molecules/MassSpecGym_retrieval_candidates_formula.json"
|
| 21 |
-
dataset_pth: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv #/data/yzhouc01/spectra_data/combined_msgym_nist23_multiplex.tsv # /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv # "../data/MassSpecGym/data/sample_data.tsv"
|
| 22 |
-
subformula_dir_pth: /data/yzhouc01/FILIP-MS/data/magma # /r/hassounlab/msgym_sirius # /data/yzhouc01/MVP/data/MassSpecGym/data/subformulae_default #/data/yzhouc01/spectra_data/subformulae #"../data/MassSpecGym/data/subformulae_default"
|
| 23 |
split_pth:
|
| 24 |
fp_dir_pth:
|
| 25 |
cons_spec_dir_pth:
|
|
@@ -28,9 +28,9 @@ partial_checkpoint: ""
|
|
| 28 |
|
| 29 |
# General hyperparameters
|
| 30 |
batch_size: 64
|
| 31 |
-
lr: 5.0e-05
|
| 32 |
-
weight_decay:
|
| 33 |
-
contr_temp: 0.05
|
| 34 |
early_stopping_patience: 300
|
| 35 |
loss_strategy: 'static'
|
| 36 |
num_workers: 50
|
|
@@ -39,7 +39,7 @@ num_workers: 50
|
|
| 39 |
############################## Data transforms ##############################
|
| 40 |
# - Spectra
|
| 41 |
spectra_view: SpecFormula #SpecMzIntTokens #SpecFormula
|
| 42 |
-
formula_source: '
|
| 43 |
# 1. Binner
|
| 44 |
max_mz: 1000
|
| 45 |
bin_width: 1
|
|
@@ -103,20 +103,23 @@ fc_dropout: 0.4
|
|
| 103 |
|
| 104 |
# - Spectra Token encoder
|
| 105 |
hidden_dims: [64, 128]
|
| 106 |
-
|
| 107 |
|
| 108 |
# - Formula-based spec encoders
|
| 109 |
-
formula_dropout:
|
| 110 |
-
formula_dims: [64, 128, 256]
|
| 111 |
cross_attn_heads: 2
|
| 112 |
use_cls: False
|
|
|
|
|
|
|
|
|
|
| 113 |
|
| 114 |
# -- GAT params
|
| 115 |
attn_heads: [12,12,12]
|
| 116 |
|
| 117 |
# - Molecule encoder (GNN)
|
| 118 |
-
gnn_channels: [64,128,
|
| 119 |
gnn_type: "gcn"
|
| 120 |
num_gnn_layers: 3
|
| 121 |
gnn_hidden_dim: 512
|
| 122 |
-
gnn_dropout: 0.3
|
|
|
|
| 1 |
# Experiment setup
|
| 2 |
job_key: ''
|
| 3 |
+
run_name: 'optimized_filip-model'
|
| 4 |
run_details: ""
|
| 5 |
project_name: ''
|
| 6 |
wandb_entity_name: 'mass-spec-ml'
|
|
|
|
| 12 |
# Training setup
|
| 13 |
max_epochs: 2000
|
| 14 |
accelerator: 'gpu'
|
| 15 |
+
devices: [1]
|
| 16 |
log_every_n_steps: 250
|
| 17 |
val_check_interval: 1.0
|
| 18 |
|
| 19 |
# Data paths
|
| 20 |
candidates_pth: /r/hassounlab/spectra_data/msgym/molecules/MassSpecGym_retrieval_candidates_mass.json # "../data/MassSpecGym/data/molecules/MassSpecGym_retrieval_candidates_formula.json"
|
| 21 |
+
dataset_pth: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv # /data/yzhouc01/MVP/data/sample/data.tsv #/r/hassounlab/spectra_data/msgym/MassSpecGym.tsv #/data/yzhouc01/spectra_data/combined_msgym_nist23_multiplex.tsv # /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv # "../data/MassSpecGym/data/sample_data.tsv"
|
| 22 |
+
subformula_dir_pth: /data/yzhouc01/MVP/data/MassSpecGym/data/subformulae_default # /data/yzhouc01/FILIP-MS/data/magma # /r/hassounlab/msgym_sirius # /data/yzhouc01/MVP/data/MassSpecGym/data/subformulae_default #/data/yzhouc01/spectra_data/subformulae #"../data/MassSpecGym/data/subformulae_default"
|
| 23 |
split_pth:
|
| 24 |
fp_dir_pth:
|
| 25 |
cons_spec_dir_pth:
|
|
|
|
| 28 |
|
| 29 |
# General hyperparameters
|
| 30 |
batch_size: 64
|
| 31 |
+
lr: 2.881339661302105e-05 # 5.0e-05
|
| 32 |
+
weight_decay: 1.1586679936312845e-05
|
| 33 |
+
contr_temp: 0.022772534845886608 # 0.05
|
| 34 |
early_stopping_patience: 300
|
| 35 |
loss_strategy: 'static'
|
| 36 |
num_workers: 50
|
|
|
|
| 39 |
############################## Data transforms ##############################
|
| 40 |
# - Spectra
|
| 41 |
spectra_view: SpecFormula #SpecMzIntTokens #SpecFormula
|
| 42 |
+
formula_source: 'default' # magma_1, magma_all, sirius, default
|
| 43 |
# 1. Binner
|
| 44 |
max_mz: 1000
|
| 45 |
bin_width: 1
|
|
|
|
| 103 |
|
| 104 |
# - Spectra Token encoder
|
| 105 |
hidden_dims: [64, 128]
|
| 106 |
+
|
| 107 |
|
| 108 |
# - Formula-based spec encoders
|
| 109 |
+
formula_dropout: 0.2
|
| 110 |
+
formula_dims: [512, 256, 512] #[64, 128, 256]
|
| 111 |
cross_attn_heads: 2
|
| 112 |
use_cls: False
|
| 113 |
+
peak_dropout: 0.414425691950033 # 0.2
|
| 114 |
+
formula_attn_heads: 4 # 2
|
| 115 |
+
formula_transformer_layers: 2
|
| 116 |
|
| 117 |
# -- GAT params
|
| 118 |
attn_heads: [12,12,12]
|
| 119 |
|
| 120 |
# - Molecule encoder (GNN)
|
| 121 |
+
gnn_channels: [64,128,512]
|
| 122 |
gnn_type: "gcn"
|
| 123 |
num_gnn_layers: 3
|
| 124 |
gnn_hidden_dim: 512
|
| 125 |
+
gnn_dropout: 0.23234950970370824 #0.3
|
mvp/params_tmp.yaml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# Experiment setup
|
| 2 |
job_key: ''
|
| 3 |
-
run_name: '
|
| 4 |
run_details: ""
|
| 5 |
project_name: ''
|
| 6 |
wandb_entity_name: 'mass-spec-ml'
|
|
@@ -10,16 +10,16 @@ debug: False
|
|
| 10 |
checkpoint_pth:
|
| 11 |
|
| 12 |
# Training setup
|
| 13 |
-
max_epochs:
|
| 14 |
accelerator: 'gpu'
|
| 15 |
devices: [1]
|
| 16 |
log_every_n_steps: 250
|
| 17 |
val_check_interval: 1.0
|
| 18 |
|
| 19 |
# Data paths
|
| 20 |
-
candidates_pth: /
|
| 21 |
-
dataset_pth: /data/yzhouc01/
|
| 22 |
-
subformula_dir_pth: /data/yzhouc01/
|
| 23 |
split_pth:
|
| 24 |
fp_dir_pth:
|
| 25 |
cons_spec_dir_pth:
|
|
@@ -28,9 +28,9 @@ partial_checkpoint: ""
|
|
| 28 |
|
| 29 |
# General hyperparameters
|
| 30 |
batch_size: 64
|
| 31 |
-
lr: 5.0e-05
|
| 32 |
-
weight_decay:
|
| 33 |
-
contr_temp: 0.05
|
| 34 |
early_stopping_patience: 300
|
| 35 |
loss_strategy: 'static'
|
| 36 |
num_workers: 50
|
|
@@ -39,6 +39,7 @@ num_workers: 50
|
|
| 39 |
############################## Data transforms ##############################
|
| 40 |
# - Spectra
|
| 41 |
spectra_view: SpecFormula #SpecMzIntTokens #SpecFormula
|
|
|
|
| 42 |
# 1. Binner
|
| 43 |
max_mz: 1000
|
| 44 |
bin_width: 1
|
|
@@ -91,7 +92,7 @@ use_NL: False
|
|
| 91 |
task: 'retrieval'
|
| 92 |
spec_enc: Transformer_Formula # Transformer_MzInt #Transformer_Formula
|
| 93 |
mol_enc: "GNN"
|
| 94 |
-
model:
|
| 95 |
contr_views: [['spec_enc', 'mol_enc']] #[['spec_enc', 'mol_enc'], ['spec_enc', 'NL_spec_enc'], ['mol_enc', 'NL_spec_enc']] #[['spec_enc', 'mol_enc'], ['mol_enc', 'cons_spec_enc'], ['cons_spec_enc', 'spec_enc'], ['fp_enc', 'mol_enc'], ['fp_enc', 'spec_enc'], ['fp_enc', 'cons_spec_enc']]
|
| 96 |
log_only_loss_at_stages: []
|
| 97 |
df_test_path: ""
|
|
@@ -102,20 +103,23 @@ fc_dropout: 0.4
|
|
| 102 |
|
| 103 |
# - Spectra Token encoder
|
| 104 |
hidden_dims: [64, 128]
|
| 105 |
-
|
| 106 |
|
| 107 |
# - Formula-based spec encoders
|
| 108 |
-
formula_dropout:
|
| 109 |
-
formula_dims: [64, 128, 256]
|
| 110 |
-
cross_attn_heads: 2
|
| 111 |
use_cls: False
|
|
|
|
|
|
|
|
|
|
| 112 |
|
| 113 |
# -- GAT params
|
| 114 |
attn_heads: [12,12,12]
|
| 115 |
|
| 116 |
# - Molecule encoder (GNN)
|
| 117 |
-
gnn_channels: [64,128,
|
| 118 |
gnn_type: "gcn"
|
| 119 |
num_gnn_layers: 3
|
| 120 |
gnn_hidden_dim: 512
|
| 121 |
-
gnn_dropout: 0.3
|
|
|
|
| 1 |
# Experiment setup
|
| 2 |
job_key: ''
|
| 3 |
+
run_name: 'crossAttnModel'
|
| 4 |
run_details: ""
|
| 5 |
project_name: ''
|
| 6 |
wandb_entity_name: 'mass-spec-ml'
|
|
|
|
| 10 |
checkpoint_pth:
|
| 11 |
|
| 12 |
# Training setup
|
| 13 |
+
max_epochs: 1000
|
| 14 |
accelerator: 'gpu'
|
| 15 |
devices: [1]
|
| 16 |
log_every_n_steps: 250
|
| 17 |
val_check_interval: 1.0
|
| 18 |
|
| 19 |
# Data paths
|
| 20 |
+
candidates_pth: /r/hassounlab/spectra_data/msgym/molecules/MassSpecGym_retrieval_candidates_mass.json # "../data/MassSpecGym/data/molecules/MassSpecGym_retrieval_candidates_formula.json"
|
| 21 |
+
dataset_pth: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv # /data/yzhouc01/MVP/data/sample/data.tsv #/r/hassounlab/spectra_data/msgym/MassSpecGym.tsv #/data/yzhouc01/spectra_data/combined_msgym_nist23_multiplex.tsv # /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv # "../data/MassSpecGym/data/sample_data.tsv"
|
| 22 |
+
subformula_dir_pth: /data/yzhouc01/MVP/data/MassSpecGym/data/subformulae_default # /data/yzhouc01/FILIP-MS/data/magma # /r/hassounlab/msgym_sirius # /data/yzhouc01/MVP/data/MassSpecGym/data/subformulae_default #/data/yzhouc01/spectra_data/subformulae #"../data/MassSpecGym/data/subformulae_default"
|
| 23 |
split_pth:
|
| 24 |
fp_dir_pth:
|
| 25 |
cons_spec_dir_pth:
|
|
|
|
| 28 |
|
| 29 |
# General hyperparameters
|
| 30 |
batch_size: 64
|
| 31 |
+
lr: 2.881339661302105e-05 # 5.0e-05
|
| 32 |
+
weight_decay: 1.1586679936312845e-05
|
| 33 |
+
contr_temp: 0.022772534845886608 # 0.05
|
| 34 |
early_stopping_patience: 300
|
| 35 |
loss_strategy: 'static'
|
| 36 |
num_workers: 50
|
|
|
|
| 39 |
############################## Data transforms ##############################
|
| 40 |
# - Spectra
|
| 41 |
spectra_view: SpecFormula #SpecMzIntTokens #SpecFormula
|
| 42 |
+
formula_source: 'default' # magma_1, magma_all, sirius, default
|
| 43 |
# 1. Binner
|
| 44 |
max_mz: 1000
|
| 45 |
bin_width: 1
|
|
|
|
| 92 |
task: 'retrieval'
|
| 93 |
spec_enc: Transformer_Formula # Transformer_MzInt #Transformer_Formula
|
| 94 |
mol_enc: "GNN"
|
| 95 |
+
model: crossAttenContrastive # "MultiviewContrastive"
|
| 96 |
contr_views: [['spec_enc', 'mol_enc']] #[['spec_enc', 'mol_enc'], ['spec_enc', 'NL_spec_enc'], ['mol_enc', 'NL_spec_enc']] #[['spec_enc', 'mol_enc'], ['mol_enc', 'cons_spec_enc'], ['cons_spec_enc', 'spec_enc'], ['fp_enc', 'mol_enc'], ['fp_enc', 'spec_enc'], ['fp_enc', 'cons_spec_enc']]
|
| 97 |
log_only_loss_at_stages: []
|
| 98 |
df_test_path: ""
|
|
|
|
| 103 |
|
| 104 |
# - Spectra Token encoder
|
| 105 |
hidden_dims: [64, 128]
|
| 106 |
+
|
| 107 |
|
| 108 |
# - Formula-based spec encoders
|
| 109 |
+
formula_dropout: 0.2
|
| 110 |
+
formula_dims: [128, 256, 512] #[64, 128, 256]
|
| 111 |
+
cross_attn_heads: 4 # 2
|
| 112 |
use_cls: False
|
| 113 |
+
peak_dropout: 0.414425691950033 # 0.2
|
| 114 |
+
formula_attn_heads: 4 # 2
|
| 115 |
+
formula_transformer_layers: 2
|
| 116 |
|
| 117 |
# -- GAT params
|
| 118 |
attn_heads: [12,12,12]
|
| 119 |
|
| 120 |
# - Molecule encoder (GNN)
|
| 121 |
+
gnn_channels: [64,128,512]
|
| 122 |
gnn_type: "gcn"
|
| 123 |
num_gnn_layers: 3
|
| 124 |
gnn_hidden_dim: 512
|
| 125 |
+
gnn_dropout: 0.23234950970370824 #0.3
|
mvp/run.sh
CHANGED
|
@@ -1,3 +1,3 @@
|
|
| 1 |
-
|
| 2 |
-
python test.py
|
| 3 |
-
python test.py --candidates_pth /r/hassounlab/spectra_data/msgym/molecules/MassSpecGym_retrieval_candidates_formula.json
|
|
|
|
| 1 |
+
python train.py --param_pth params_tmp.yaml
|
| 2 |
+
python test.py --param_pth params_tmp.yaml
|
| 3 |
+
python test.py --candidates_pth /r/hassounlab/spectra_data/msgym/molecules/MassSpecGym_retrieval_candidates_formula.json --param_pth params_tmp.yaml
|
mvp/tune.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import datetime
|
| 3 |
+
import os
|
| 4 |
+
import sys
|
| 5 |
+
import yaml
|
| 6 |
+
import optuna
|
| 7 |
+
import time
|
| 8 |
+
import logging
|
| 9 |
+
import pandas as pd
|
| 10 |
+
|
| 11 |
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
| 12 |
+
|
| 13 |
+
import pytorch_lightning as pl
|
| 14 |
+
from pytorch_lightning import Trainer
|
| 15 |
+
from optuna.integration import PyTorchLightningPruningCallback
|
| 16 |
+
from pytorch_lightning.callbacks import Callback
|
| 17 |
+
|
| 18 |
+
from mvp.data.data_module import ContrastiveDataModule
|
| 19 |
+
from mvp.data.datasets import ContrastiveDataset
|
| 20 |
+
from mvp.utils.data import get_ms_dataset, get_spec_featurizer, get_mol_featurizer
|
| 21 |
+
from mvp.utils.models import get_model
|
| 22 |
+
from mvp.definitions import TEST_RESULTS_DIR
|
| 23 |
+
from functools import partial
|
| 24 |
+
from rdkit import RDLogger
|
| 25 |
+
from massspecgym.models.base import Stage
|
| 26 |
+
|
| 27 |
+
# Suppress RDKit warnings
|
| 28 |
+
lg = RDLogger.logger()
|
| 29 |
+
lg.setLevel(RDLogger.CRITICAL)
|
| 30 |
+
|
| 31 |
+
parser = argparse.ArgumentParser()
|
| 32 |
+
parser.add_argument("--param_pth", type=str, default="params_formSpec.yaml")
|
| 33 |
+
parser.add_argument("--n_trials", type=int, default=20)
|
| 34 |
+
|
| 35 |
+
class EpochLossTracker(Callback):
|
| 36 |
+
def __init__(self, trial):
|
| 37 |
+
super().__init__()
|
| 38 |
+
self.trial = trial
|
| 39 |
+
self.history = {"train_loss": [], "val_loss": []}
|
| 40 |
+
|
| 41 |
+
def on_train_epoch_end(self, trainer, pl_module):
|
| 42 |
+
if "train_loss" in trainer.callback_metrics:
|
| 43 |
+
self.history["train_loss"].append(
|
| 44 |
+
float(trainer.callback_metrics["train_loss"].cpu().item())
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
def on_validation_epoch_end(self, trainer, pl_module):
|
| 48 |
+
val_key = f"{Stage.VAL.to_pref()}loss"
|
| 49 |
+
if val_key in trainer.callback_metrics:
|
| 50 |
+
self.history["val_loss"].append(
|
| 51 |
+
float(trainer.callback_metrics[val_key].cpu().item())
|
| 52 |
+
)
|
| 53 |
+
|
| 54 |
+
def on_fit_end(self, trainer, pl_module):
|
| 55 |
+
# Attach to trial so save_trial_result can access it
|
| 56 |
+
self.trial.set_user_attr("loss_history", self.history)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
class SafePruningCallback(PyTorchLightningPruningCallback, Callback):
|
| 61 |
+
"""Wraps Optuna pruning to make it a proper Lightning Callback."""
|
| 62 |
+
pass
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def setup_logging(log_path):
|
| 66 |
+
"""Setup logging without breaking tqdm progress bars."""
|
| 67 |
+
logger = logging.getLogger()
|
| 68 |
+
logger.setLevel(logging.INFO)
|
| 69 |
+
|
| 70 |
+
# Remove existing handlers (avoid duplicate or wrong outputs)
|
| 71 |
+
if logger.hasHandlers():
|
| 72 |
+
logger.handlers.clear()
|
| 73 |
+
|
| 74 |
+
# File handler
|
| 75 |
+
file_handler = logging.FileHandler(log_path, mode="a")
|
| 76 |
+
file_handler.setFormatter(
|
| 77 |
+
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Console handler (stderr so tqdm stays clean)
|
| 81 |
+
console_handler = logging.StreamHandler(sys.stderr)
|
| 82 |
+
console_handler.setFormatter(
|
| 83 |
+
logging.Formatter("%(asctime)s [%(levelname)s] %(message)s")
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
logger.addHandler(file_handler)
|
| 87 |
+
logger.addHandler(console_handler)
|
| 88 |
+
|
| 89 |
+
|
| 90 |
+
def save_trial_result(base_dir, trial, params, duration):
|
| 91 |
+
"""Append trial results to a CSV file after each trial."""
|
| 92 |
+
history_path = os.path.join(base_dir, "trial_history.csv")
|
| 93 |
+
|
| 94 |
+
# Fetch losses from trial user_attrs
|
| 95 |
+
loss_hist = trial.user_attrs.get("loss_history", {})
|
| 96 |
+
record = {
|
| 97 |
+
"number": trial.number,
|
| 98 |
+
"duration_sec": duration,
|
| 99 |
+
"train_loss": loss_hist.get("train_loss", []),
|
| 100 |
+
"val_loss": loss_hist.get("val_loss", []),
|
| 101 |
+
**trial.params,
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
# Append to CSV safely
|
| 105 |
+
if os.path.exists(history_path):
|
| 106 |
+
df = pd.read_csv(history_path)
|
| 107 |
+
df = pd.concat([df, pd.DataFrame([record])], ignore_index=True)
|
| 108 |
+
else:
|
| 109 |
+
df = pd.DataFrame([record])
|
| 110 |
+
df.to_csv(history_path, index=False)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def objective(trial: optuna.Trial, base_params, trial_times, base_dir, total_trials):
|
| 114 |
+
start_time = time.time()
|
| 115 |
+
params = base_params.copy()
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
# Training-related params
|
| 119 |
+
params["batch_size"] = trial.suggest_categorical("batch_size", [64, 128])
|
| 120 |
+
params["lr"] = trial.suggest_float("lr", 1e-6, 1e-3, log=True)
|
| 121 |
+
params["weight_decay"] = trial.suggest_float("weight_decay", 1e-6, 1e-2, log=True)
|
| 122 |
+
params["contr_temp"] = trial.suggest_float("contrastive_temp", 0.02, 0.1)
|
| 123 |
+
|
| 124 |
+
# Spectra encoder-related params
|
| 125 |
+
params['peak_dropout'] = trial.suggest_float("peak_dropout", 0.1, 0.5)
|
| 126 |
+
params['formula_attn_heads'] = trial.suggest_categorical("formula_attn_heads", [2, 4])
|
| 127 |
+
params['formula_transformer_layers'] = trial.suggest_categorical("formula_transformer_layers", [2, 4])
|
| 128 |
+
|
| 129 |
+
choice = trial.suggest_categorical(
|
| 130 |
+
"formula_dims",
|
| 131 |
+
["64,128", "512,256", "256,512", "128", "256"]
|
| 132 |
+
)
|
| 133 |
+
params["formula_dims"] = [int(x) for x in choice.split(",")]
|
| 134 |
+
|
| 135 |
+
# Molecule encoder-related params
|
| 136 |
+
params['gnn_dropout'] = trial.suggest_float("gnn_dropout", 0.1, 0.5)
|
| 137 |
+
choice = trial.suggest_categorical(
|
| 138 |
+
"gnn_channels",
|
| 139 |
+
["64,128", "128,256", "256,512", "64,128,128"]
|
| 140 |
+
)
|
| 141 |
+
params["gnn_channels"] = [int(x) for x in choice.split(",")]
|
| 142 |
+
|
| 143 |
+
# Ensure last layer matches final embedding dim
|
| 144 |
+
final_embedding_dim = trial.suggest_categorical("final_embedding_dim", [256, 512])
|
| 145 |
+
params['formula_dims'].append(final_embedding_dim)
|
| 146 |
+
params['gnn_channels'].append(final_embedding_dim)
|
| 147 |
+
|
| 148 |
+
logging.info(f"Formula dims: {params['formula_dims']}")
|
| 149 |
+
logging.info(f"GNN channels: {params['gnn_channels']}")
|
| 150 |
+
|
| 151 |
+
# Init seed
|
| 152 |
+
pl.seed_everything(params["seed"])
|
| 153 |
+
|
| 154 |
+
# Init dataset + datamodule
|
| 155 |
+
spec_featurizer = get_spec_featurizer(params["spectra_view"], params)
|
| 156 |
+
mol_featurizer = get_mol_featurizer(params["molecule_view"], params)
|
| 157 |
+
dataset = get_ms_dataset(params["spectra_view"], params["molecule_view"], spec_featurizer, mol_featurizer, params)
|
| 158 |
+
|
| 159 |
+
collate_fn = partial(
|
| 160 |
+
ContrastiveDataset.collate_fn,
|
| 161 |
+
spec_enc=params["spec_enc"],
|
| 162 |
+
spectra_view=params["spectra_view"],
|
| 163 |
+
mask_peak_ratio=params["mask_peak_ratio"],
|
| 164 |
+
aug_cands=params["aug_cands"],
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
data_module = ContrastiveDataModule(
|
| 168 |
+
dataset=dataset,
|
| 169 |
+
collate_fn=collate_fn,
|
| 170 |
+
split_pth=params["split_pth"],
|
| 171 |
+
batch_size=params["batch_size"],
|
| 172 |
+
num_workers=params["num_workers"],
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
# Init model
|
| 176 |
+
model = get_model(params["model"], params)
|
| 177 |
+
|
| 178 |
+
# Metric to optimize
|
| 179 |
+
callbacks = []
|
| 180 |
+
monitor_metric = f"{Stage.VAL.to_pref()}loss"
|
| 181 |
+
pruning_cb = SafePruningCallback(trial, monitor=monitor_metric)
|
| 182 |
+
callbacks.append(pruning_cb)
|
| 183 |
+
loss_tracker = EpochLossTracker(trial)
|
| 184 |
+
callbacks.append(loss_tracker)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
trainer = Trainer(
|
| 189 |
+
accelerator=params["accelerator"],
|
| 190 |
+
devices=params["devices"],
|
| 191 |
+
max_epochs=params["max_epochs"],
|
| 192 |
+
logger=False,
|
| 193 |
+
enable_checkpointing=False,
|
| 194 |
+
callbacks=callbacks,
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
data_module.prepare_data()
|
| 198 |
+
data_module.setup()
|
| 199 |
+
|
| 200 |
+
# Validate before training
|
| 201 |
+
trainer.validate(model, datamodule=data_module)
|
| 202 |
+
|
| 203 |
+
# Fit (may be pruned early)
|
| 204 |
+
trainer.fit(model, datamodule=data_module)
|
| 205 |
+
|
| 206 |
+
# Duration
|
| 207 |
+
duration = time.time() - start_time
|
| 208 |
+
trial_times.append(duration)
|
| 209 |
+
avg_time = sum(trial_times) / len(trial_times)
|
| 210 |
+
remaining = (total_trials - trial.number - 1) * avg_time
|
| 211 |
+
logging.info(f"[Trial {trial.number}] Duration: {duration/60:.2f} min | Avg: {avg_time/60:.2f} min | ETA: {remaining/60:.2f} min")
|
| 212 |
+
|
| 213 |
+
value = trainer.callback_metrics[monitor_metric].item()
|
| 214 |
+
trial.set_user_attr("duration", duration)
|
| 215 |
+
|
| 216 |
+
# Save progress
|
| 217 |
+
save_trial_result(base_dir, trial, base_params, duration, )
|
| 218 |
+
|
| 219 |
+
return value
|
| 220 |
+
|
| 221 |
+
except Exception as e:
|
| 222 |
+
duration = time.time() - start_time
|
| 223 |
+
logging.exception(f"Trial {trial.number} failed: {e}")
|
| 224 |
+
save_trial_result(base_dir, trial, base_params, duration)
|
| 225 |
+
raise
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
def main(args):
|
| 229 |
+
with open(args.param_pth) as f:
|
| 230 |
+
params = yaml.load(f, Loader=yaml.FullLoader)
|
| 231 |
+
|
| 232 |
+
now = datetime.datetime.now().strftime("%Y%m%d")
|
| 233 |
+
base_dir = str(TEST_RESULTS_DIR / f"{now}_{params['run_name']}_optuna")
|
| 234 |
+
os.makedirs(base_dir, exist_ok=True)
|
| 235 |
+
params["experiment_dir"] = base_dir
|
| 236 |
+
|
| 237 |
+
# Setup logging
|
| 238 |
+
log_path = os.path.join(base_dir, "optuna.log")
|
| 239 |
+
setup_logging(log_path)
|
| 240 |
+
|
| 241 |
+
trial_times = []
|
| 242 |
+
|
| 243 |
+
study = optuna.create_study(direction="minimize", pruner=optuna.pruners.MedianPruner())
|
| 244 |
+
study.optimize(lambda trial: objective(trial, params, trial_times, base_dir, args.n_trials), n_trials=args.n_trials)
|
| 245 |
+
|
| 246 |
+
# Print best trial
|
| 247 |
+
logging.info("\nBest trial:")
|
| 248 |
+
logging.info(study.best_trial.params)
|
| 249 |
+
|
| 250 |
+
# Merge base params with best trial
|
| 251 |
+
best_params = params.copy()
|
| 252 |
+
best_params.update(study.best_trial.params)
|
| 253 |
+
|
| 254 |
+
# Save best params to YAML
|
| 255 |
+
best_param_path = os.path.join(base_dir, "best_params.yaml")
|
| 256 |
+
with open(best_param_path, "w") as f:
|
| 257 |
+
yaml.dump(best_params, f)
|
| 258 |
+
logging.info(f"\nBest parameters saved to: {best_param_path}")
|
| 259 |
+
logging.info(f"Run training with: python train.py --param_pth {best_param_path}")
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
if __name__ == "__main__":
|
| 263 |
+
args = parser.parse_args([] if "__file__" not in globals() else None)
|
| 264 |
+
main(args)
|
notebooks/2v1.ipynb
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 33,
|
| 6 |
+
"id": "d3fe3363",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import pickle\n",
|
| 11 |
+
"from rdkit import Chem\n",
|
| 12 |
+
"import pandas\n",
|
| 13 |
+
"import matplotlib.pyplot as plt\n",
|
| 14 |
+
"import numpy as np\n",
|
| 15 |
+
"from rdkit.Chem.Draw import MolsToGridImage"
|
| 16 |
+
]
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"cell_type": "code",
|
| 20 |
+
"execution_count": 2,
|
| 21 |
+
"id": "efdf1cc0",
|
| 22 |
+
"metadata": {},
|
| 23 |
+
"outputs": [],
|
| 24 |
+
"source": [
|
| 25 |
+
"with open(\"/data/yzhouc01/FILIP-MS/experiments/20250824_filipContrastive/result_MassSpecGym_retrieval_candidates_formula.pkl\", 'rb') as f:\n",
|
| 26 |
+
" result = pickle.load(f)"
|
| 27 |
+
]
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"cell_type": "code",
|
| 31 |
+
"execution_count": null,
|
| 32 |
+
"id": "0113e869",
|
| 33 |
+
"metadata": {},
|
| 34 |
+
"outputs": [],
|
| 35 |
+
"source": []
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "code",
|
| 39 |
+
"execution_count": 8,
|
| 40 |
+
"id": "de9cacb6",
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"outputs": [
|
| 43 |
+
{
|
| 44 |
+
"data": {
|
| 45 |
+
"text/html": [
|
| 46 |
+
"<div>\n",
|
| 47 |
+
"<style scoped>\n",
|
| 48 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 49 |
+
" vertical-align: middle;\n",
|
| 50 |
+
" }\n",
|
| 51 |
+
"\n",
|
| 52 |
+
" .dataframe tbody tr th {\n",
|
| 53 |
+
" vertical-align: top;\n",
|
| 54 |
+
" }\n",
|
| 55 |
+
"\n",
|
| 56 |
+
" .dataframe thead th {\n",
|
| 57 |
+
" text-align: right;\n",
|
| 58 |
+
" }\n",
|
| 59 |
+
"</style>\n",
|
| 60 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 61 |
+
" <thead>\n",
|
| 62 |
+
" <tr style=\"text-align: right;\">\n",
|
| 63 |
+
" <th></th>\n",
|
| 64 |
+
" <th>identifier</th>\n",
|
| 65 |
+
" <th>candidates</th>\n",
|
| 66 |
+
" <th>scores</th>\n",
|
| 67 |
+
" <th>labels</th>\n",
|
| 68 |
+
" <th>rank</th>\n",
|
| 69 |
+
" </tr>\n",
|
| 70 |
+
" </thead>\n",
|
| 71 |
+
" <tbody>\n",
|
| 72 |
+
" <tr>\n",
|
| 73 |
+
" <th>17551</th>\n",
|
| 74 |
+
" <td>MassSpecGymID0414164</td>\n",
|
| 75 |
+
" <td>[CN(C)[C@H]1[C@@H]2[C@H]([C@@H]3C(=C)C4=C(C=CC...</td>\n",
|
| 76 |
+
" <td>[0.5793027877807617, 0.5367040038108826, 0.487...</td>\n",
|
| 77 |
+
" <td>[True, False, False, False, False, False, Fals...</td>\n",
|
| 78 |
+
" <td>7</td>\n",
|
| 79 |
+
" </tr>\n",
|
| 80 |
+
" <tr>\n",
|
| 81 |
+
" <th>17552</th>\n",
|
| 82 |
+
" <td>MassSpecGymID0414165</td>\n",
|
| 83 |
+
" <td>[CN(C)[C@H]1[C@@H]2[C@H]([C@@H]3C(=C)C4=C(C(=C...</td>\n",
|
| 84 |
+
" <td>[0.38384538888931274, 0.24421672523021698, 0.2...</td>\n",
|
| 85 |
+
" <td>[True, False, False, False, False, False, Fals...</td>\n",
|
| 86 |
+
" <td>31</td>\n",
|
| 87 |
+
" </tr>\n",
|
| 88 |
+
" <tr>\n",
|
| 89 |
+
" <th>17553</th>\n",
|
| 90 |
+
" <td>MassSpecGymID0414166</td>\n",
|
| 91 |
+
" <td>[C[C@H]1/C=C/C=C/2\\CO[C@H]3[C@@]2([C@@H](C=C([...</td>\n",
|
| 92 |
+
" <td>[0.6297411918640137, 0.5269991159439087, 0.183...</td>\n",
|
| 93 |
+
" <td>[True, False, False, False, False, False, Fals...</td>\n",
|
| 94 |
+
" <td>11</td>\n",
|
| 95 |
+
" </tr>\n",
|
| 96 |
+
" <tr>\n",
|
| 97 |
+
" <th>17554</th>\n",
|
| 98 |
+
" <td>MassSpecGymID0414167</td>\n",
|
| 99 |
+
" <td>[C[C@H]1/C=C/C=C/2\\CO[C@H]3[C@@]2([C@@H](C=C([...</td>\n",
|
| 100 |
+
" <td>[0.613699197769165, 0.554176390171051, 0.21911...</td>\n",
|
| 101 |
+
" <td>[True, False, False, False, False, False, Fals...</td>\n",
|
| 102 |
+
" <td>12</td>\n",
|
| 103 |
+
" </tr>\n",
|
| 104 |
+
" <tr>\n",
|
| 105 |
+
" <th>17555</th>\n",
|
| 106 |
+
" <td>MassSpecGymID0414171</td>\n",
|
| 107 |
+
" <td>[C[C@@]1([C@H]2C[C@H]3[C@@H](C(=O)C(=C([C@]3(C...</td>\n",
|
| 108 |
+
" <td>[0.5223979949951172, 0.5548790693283081, 0.512...</td>\n",
|
| 109 |
+
" <td>[True, False, False, False, False, False, Fals...</td>\n",
|
| 110 |
+
" <td>14</td>\n",
|
| 111 |
+
" </tr>\n",
|
| 112 |
+
" </tbody>\n",
|
| 113 |
+
"</table>\n",
|
| 114 |
+
"</div>"
|
| 115 |
+
],
|
| 116 |
+
"text/plain": [
|
| 117 |
+
" identifier \\\n",
|
| 118 |
+
"17551 MassSpecGymID0414164 \n",
|
| 119 |
+
"17552 MassSpecGymID0414165 \n",
|
| 120 |
+
"17553 MassSpecGymID0414166 \n",
|
| 121 |
+
"17554 MassSpecGymID0414167 \n",
|
| 122 |
+
"17555 MassSpecGymID0414171 \n",
|
| 123 |
+
"\n",
|
| 124 |
+
" candidates \\\n",
|
| 125 |
+
"17551 [CN(C)[C@H]1[C@@H]2[C@H]([C@@H]3C(=C)C4=C(C=CC... \n",
|
| 126 |
+
"17552 [CN(C)[C@H]1[C@@H]2[C@H]([C@@H]3C(=C)C4=C(C(=C... \n",
|
| 127 |
+
"17553 [C[C@H]1/C=C/C=C/2\\CO[C@H]3[C@@]2([C@@H](C=C([... \n",
|
| 128 |
+
"17554 [C[C@H]1/C=C/C=C/2\\CO[C@H]3[C@@]2([C@@H](C=C([... \n",
|
| 129 |
+
"17555 [C[C@@]1([C@H]2C[C@H]3[C@@H](C(=O)C(=C([C@]3(C... \n",
|
| 130 |
+
"\n",
|
| 131 |
+
" scores \\\n",
|
| 132 |
+
"17551 [0.5793027877807617, 0.5367040038108826, 0.487... \n",
|
| 133 |
+
"17552 [0.38384538888931274, 0.24421672523021698, 0.2... \n",
|
| 134 |
+
"17553 [0.6297411918640137, 0.5269991159439087, 0.183... \n",
|
| 135 |
+
"17554 [0.613699197769165, 0.554176390171051, 0.21911... \n",
|
| 136 |
+
"17555 [0.5223979949951172, 0.5548790693283081, 0.512... \n",
|
| 137 |
+
"\n",
|
| 138 |
+
" labels rank \n",
|
| 139 |
+
"17551 [True, False, False, False, False, False, Fals... 7 \n",
|
| 140 |
+
"17552 [True, False, False, False, False, False, Fals... 31 \n",
|
| 141 |
+
"17553 [True, False, False, False, False, False, Fals... 11 \n",
|
| 142 |
+
"17554 [True, False, False, False, False, False, Fals... 12 \n",
|
| 143 |
+
"17555 [True, False, False, False, False, False, Fals... 14 "
|
| 144 |
+
]
|
| 145 |
+
},
|
| 146 |
+
"execution_count": 8,
|
| 147 |
+
"metadata": {},
|
| 148 |
+
"output_type": "execute_result"
|
| 149 |
+
}
|
| 150 |
+
],
|
| 151 |
+
"source": [
|
| 152 |
+
"result.tail()"
|
| 153 |
+
]
|
| 154 |
+
},
|
| 155 |
+
{
|
| 156 |
+
"cell_type": "code",
|
| 157 |
+
"execution_count": 52,
|
| 158 |
+
"id": "f420c511",
|
| 159 |
+
"metadata": {},
|
| 160 |
+
"outputs": [
|
| 161 |
+
{
|
| 162 |
+
"name": "stderr",
|
| 163 |
+
"output_type": "stream",
|
| 164 |
+
"text": [
|
| 165 |
+
"[19:30:58] \n",
|
| 166 |
+
"\n",
|
| 167 |
+
"****\n",
|
| 168 |
+
"Pre-condition Violation\n",
|
| 169 |
+
"bad size\n",
|
| 170 |
+
"Violation occurred on line 183 in file /project/build/temp.linux-x86_64-cpython-311/rdkit/Code/GraphMol/MolDraw2D/MolDraw2D.cpp\n",
|
| 171 |
+
"Failed Expression: !legends || legends->size() == mols.size()\n",
|
| 172 |
+
"----------\n",
|
| 173 |
+
"Stacktrace:\n",
|
| 174 |
+
"----------\n",
|
| 175 |
+
"****\n",
|
| 176 |
+
"\n"
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
{
|
| 180 |
+
"ename": "RuntimeError",
|
| 181 |
+
"evalue": "Pre-condition Violation\n\tbad size\n\tViolation occurred on line 183 in file Code/GraphMol/MolDraw2D/MolDraw2D.cpp\n\tFailed Expression: !legends || legends->size() == mols.size()\n\tRDKIT: 2024.03.5\n\tBOOST: 1_85\n",
|
| 182 |
+
"output_type": "error",
|
| 183 |
+
"traceback": [
|
| 184 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
| 185 |
+
"\u001b[0;31mRuntimeError\u001b[0m Traceback (most recent call last)",
|
| 186 |
+
"Cell \u001b[0;32mIn[52], line 21\u001b[0m\n\u001b[1;32m 18\u001b[0m cand2 \u001b[38;5;241m=\u001b[39m Chem\u001b[38;5;241m.\u001b[39mMolFromSmiles(candidates[sorted_scores][\u001b[38;5;241m2\u001b[39m])\n\u001b[1;32m 20\u001b[0m mols \u001b[38;5;241m=\u001b[39m [target, cand, cand1]\n\u001b[0;32m---> 21\u001b[0m img \u001b[38;5;241m=\u001b[39m \u001b[43mMolsToGridImage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmols\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmolsPerRow\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m3\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msubImgSize\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m(\u001b[49m\u001b[38;5;241;43m200\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m200\u001b[39;49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlegends\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mtarget (\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mtarget_score\u001b[49m\u001b[38;5;132;43;01m:\u001b[39;49;00m\u001b[38;5;124;43m.3f\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m)\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mcand@1 (\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mcand1_score\u001b[49m\u001b[38;5;132;43;01m:\u001b[39;49;00m\u001b[38;5;124;43m.3f\u001b[39;49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m)\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 23\u001b[0m plt\u001b[38;5;241m.\u001b[39mshow()\n\u001b[1;32m 24\u001b[0m display(img)\n",
|
| 187 |
+
"File \u001b[0;32m/data/yzc-conda/spec/lib/python3.11/site-packages/rdkit/Chem/Draw/IPythonConsole.py:271\u001b[0m, in \u001b[0;36mShowMols\u001b[0;34m(mols, maxMols, **kwargs)\u001b[0m\n\u001b[1;32m 268\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdrawOptions\u001b[39m\u001b[38;5;124m\"\u001b[39m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m kwargs:\n\u001b[1;32m 269\u001b[0m kwargs[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdrawOptions\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m drawOptions\n\u001b[0;32m--> 271\u001b[0m res \u001b[38;5;241m=\u001b[39m \u001b[43mfn\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmols\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 272\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m InteractiveRenderer\u001b[38;5;241m.\u001b[39misEnabled():\n\u001b[1;32m 273\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m HTML(res)\n",
|
| 188 |
+
"File \u001b[0;32m/data/yzc-conda/spec/lib/python3.11/site-packages/rdkit/Chem/Draw/__init__.py:821\u001b[0m, in \u001b[0;36mMolsToGridImage\u001b[0;34m(mols, molsPerRow, subImgSize, legends, highlightAtomLists, highlightBondLists, useSVG, returnPNG, **kwargs)\u001b[0m\n\u001b[1;32m 817\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _MolsToGridSVG(mols, molsPerRow\u001b[38;5;241m=\u001b[39mmolsPerRow, subImgSize\u001b[38;5;241m=\u001b[39msubImgSize, legends\u001b[38;5;241m=\u001b[39mlegends,\n\u001b[1;32m 818\u001b[0m highlightAtomLists\u001b[38;5;241m=\u001b[39mhighlightAtomLists,\n\u001b[1;32m 819\u001b[0m highlightBondLists\u001b[38;5;241m=\u001b[39mhighlightBondLists, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[1;32m 820\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m--> 821\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_MolsToGridImage\u001b[49m\u001b[43m(\u001b[49m\u001b[43mmols\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmolsPerRow\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmolsPerRow\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msubImgSize\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msubImgSize\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlegends\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlegends\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 822\u001b[0m \u001b[43m \u001b[49m\u001b[43mhighlightAtomLists\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhighlightAtomLists\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 823\u001b[0m \u001b[43m \u001b[49m\u001b[43mhighlightBondLists\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhighlightBondLists\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mreturnPNG\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mreturnPNG\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
|
| 189 |
+
"File \u001b[0;32m/data/yzc-conda/spec/lib/python3.11/site-packages/rdkit/Chem/Draw/__init__.py:567\u001b[0m, in \u001b[0;36m_MolsToGridImage\u001b[0;34m(mols, molsPerRow, subImgSize, legends, highlightAtomLists, highlightBondLists, drawOptions, returnPNG, **kwargs)\u001b[0m\n\u001b[1;32m 565\u001b[0m \u001b[38;5;28msetattr\u001b[39m(dops, k, v)\n\u001b[1;32m 566\u001b[0m \u001b[38;5;28;01mdel\u001b[39;00m kwargs[k]\n\u001b[0;32m--> 567\u001b[0m \u001b[43md2d\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mDrawMolecules\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mlist\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mmols\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mlegends\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mlegends\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01mor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mNone\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mhighlightAtoms\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhighlightAtomLists\u001b[49m\u001b[43m,\u001b[49m\n\u001b[1;32m 568\u001b[0m \u001b[43m \u001b[49m\u001b[43mhighlightBonds\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mhighlightBondLists\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 569\u001b[0m d2d\u001b[38;5;241m.\u001b[39mFinishDrawing()\n\u001b[1;32m 570\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m returnPNG:\n",
|
| 190 |
+
"\u001b[0;31mRuntimeError\u001b[0m: Pre-condition Violation\n\tbad size\n\tViolation occurred on line 183 in file Code/GraphMol/MolDraw2D/MolDraw2D.cpp\n\tFailed Expression: !legends || legends->size() == mols.size()\n\tRDKIT: 2024.03.5\n\tBOOST: 1_85\n"
|
| 191 |
+
]
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"data": {
|
| 195 |
+
"image/png": "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3nuvBgwYoA4dOuiHH37QqlWrdNVVV130cQcNGqQXXnhBr732mlq2bKm7775bTZo04RkfFcjHXGbXlAoLCxUcHKyCggIFBQVVdjmA14oZk+myvndqn0qqBN5q/vz5Gjx4sAoKClSjRo3KLgeXOXc+v7nhFAAgSZo3b54aNmyounXr6quvvtLo0aN1zz33EDzgcYQPAICkU8+9mDBhgvLy8hQZGam7775bTz/9dGWXhSqI8AEAkCSNGjVKo0aNquwycAXghlMAgFsux9l04V0IHwAAjzhy5Iief/55de7cWREREapbt666du2q//7v/z5rCntJeuONN9SlSxcFBQXJx8dHhw4dKtd5f//9dz3wwAMKCgpSSEiIhgwZoiNHjlxwv+zsbHXt2lW1atVSUFCQbr75Zv373/8+q19RUZGuv/56+fj4KCcnx9m+d+9e+fj4nLWsX7++XK/jSkL4AABcsk2bNql58+ZasmSJkpKS9OGHH2rp0qVKTEzUnDlz1KFDB5cHfUmnfjrbq1cvPfHEE5d07gceeEDbtm1zTpK3du1aDR069Lz7ZGdnq1evXurRo4c2btyoL774QikpKWc9bEw69XXU+Z738cknnzgn0jt48KDatWt3Sa/nilDBM+y6zZ0peQGcW/3RS10WVD0nT540zz77rImNjTX+/v4mOjraPPXUU87to0aNMo0bNzY1atQwDRo0MOPGjTMnTpxwbp84caJp06aNmTdvnqlfv74JCgoy/fv3N4WFhc4+R44cMQMGDDC1atUyERER5rnnnjO33HKLeeyxx5x99u7da8LCwswbb7xRZp2lpaVm/Pjx5oYbbnA5/2mffvqpkWT++OMPt8dg+/btRpL54osvnG3/7//9P+Pj42MOHDhwzv3i4uLMuHHjLnj8ZcuWmaZNm5pt27YZSWbLli3ObXv27Dmr7Urmzuc3Vz4AwEulpaVp6tSpGj9+vLZv364FCxa4zOAaGBioOXPmaPv27Xr55Zf15ptv6sUXX3Q5xq5du7RkyRItXbpUS5cu1Zo1azR16lTn9pEjR2rNmjX64IMP9PHHH2v16tXavHmzyzHGjBmjwYMHKykpST/++KP+8z//U2FhYerZs6emTJmihx9+WE8++aRq1aqlf/7zn269xi5dumjQoEHn3J6dna2QkBC1b9/e2da9e3f5+vqeNZ/NaT///LM2bNigsLAw3XjjjQoPD9ctt9yizz77zKVffn6+kpKS9D//8z+qWbPmOWu4/fbbFRYWps6dO+vDDz906/VdqQgfAOCFDh8+rJdfflnTpk1TYmKiYmNj1blzZ/3tb39z9hk3bpxuvPFGxcTEKCEhQY8//rgWLlzocpzS0lLNmTNHLVu21E033aQBAwYoKytL0ql7ON5++20999xz6tatm1q1aqW5c+e63L9x5MgRZWZmOidlS0xMlJ+fn5YvX66BAwdq2rRpOn78uHPbihUr3Hqd9erVO+/Ebnl5eQoLC3Npq1atmkJDQ5WXl1fmPrt375YkTZo0SUlJSVq+fLluuOEGdevWTd9//72kU3O6DBo0SMOGDXMJNn9Wu3ZtPf/883rvvfeUmZmpzp07q2/fvgSQi8BPbQHAC3377bcqKipSt27dztnn3Xff1SuvvKJdu3bpyJEjKikpOevJkzExMQoMDHSuR0ZGOu/N2LVrl06cOKG4uDjn9tDQUDVp0sS5vmPHDsXExOjqq6/W0aNHtWrVKh04cEBRUVG64YYbtHr1aufj0CMjI/XHH3+49TrnzZvnVv+LcXq+mYceekiDBw+WJLVt21ZZWVl65513lJ6erldffVWHDx9WWlraOY9Tp04dpaamOtc7dOign376SdOnT9ftt9/u8bqrEq58AIAXutBTR7Ozs/XAAw/otttu09KlS7VlyxaNHTtWJ06ccOlXvXp1l3UfHx/nh/PFKCkpcdZyOmScnuFWOnV14LTNmzerUaNGF33sixEREXHWjawlJSX6/fffFRERUeY+p6+knDlbbrNmzZxz0axatUrZ2dlyOByqVq2as+727dsrMTHxnPXExcVp586d5X49VwrCBwB4ocaNG6tGjRrOr0jO9Pnnn6t+/foaO3as2rdvr8aNG+uHH35w6xyxsbGqXr26y70Tf/zxh3bs2OFcb9iwoXbs2KHi4mKFhISoRYsWevrpp1VcXKzvvvtOGRkZKi0tVWZmpmbMmKGUlJTyveBziI+P16FDh7Rp0yZn26pVq1RaWupyxebPYmJiFBUVpdzcXJf2HTt2qH79+pKkV155RV999ZVycnKUk5OjZcuWSTp1Nel8T33Nyck579dEOIWvXQDACwUEBGj06NEaNWqU/P391alTJ/3yyy/atm2bhgwZosaNG2vfvn3KyMhQhw4dlJmZqcWLF7t1jtq1a2vIkCEaOXKkrr76aoWFhWns2LEuP0etU6eOWrdurX/+858aPHiwZs+erTvvvFMvvPCCIiIidPvtt+vNN9/Utm3btHDhQjVr1sy5b15envLy8pxXCrZu3arAwEDVq1dPoaGhkqSBAweqbt26Sk9PL7PGZs2aqVevXkpKStKsWbNUXFyslJQU3Xvvvc6fxx44cEDdunXTvHnz1LFjR/n4+GjkyJGaOHGi2rRpo+uvv15z587Vd999p/fff1/SqXtNzhwL6VQgu/baayVJc+fOlb+/v9q2bStJWrRokd555x299dZbbo3zlYjwAQBeavz48apWrZomTJign376SZGRkRo2bJikU7/AGDFihFJSUlRUVKQ+ffpo/PjxmjRpklvnmD59uo4cOaKEhAQFBgbqH//4hwoKClz6pKenKyEhQW3atFGHDh20b98+HTx4UGFhYTp+/LieffZZhYSEnHXsWbNmafLkyc71m2++WZI0e/Zs5y9c9u3bV+azN/5s/vz5SklJUbdu3eTr66t+/frplVdecW4vLi5Wbm6ujh075mwbPny4jh8/rhEjRuj3339XmzZttHLlSsXGxro1PlOmTNEPP/ygatWqqWnTpnr33Xd11113uXWMK5GPMcZUdhF/5s6UvADOLWZMpsv63ql9KqkSXAnmzp2rxx57TH//+981cOBAxcbG6uTJk9q4caPS09PVtWtXjRgxorLLRAVy5/Obez4AAJcsMTFRa9eu1fbt29WmTRv5+/vL4XDowQcfVOfOnZWcnFzZJeIywtcuAJzOvFoiccUEF69169Z6//33VVJSovz8fDkcDtWpU6eyy8JliPABAPCoatWqqW7dupVdBi5jfO0CAACsInwAAACrCB8AAMAqwgcAALCK8AEAAKwifAAAAKvcCh/p6enq0KGDAgMDFRYWpr59+541MU+XLl3k4+Pjspx+3C8AAIBb4WPNmjVKTk7W+vXrtXLlShUXF6tHjx46evSoS7+kpCQdPHjQuUybNs2jRQMAAO/l1kPGli9f7rI+Z84chYWFadOmTc4JgSSpZs2aioiI8EyFAACgSrmkez5Oz2x4eurj0+bPn686deqoZcuWSktLc5lJ8ExFRUUqLCx0WQAAQNVV7serl5aWavjw4erUqZNatmzpbL///vtVv359RUVF6euvv9bo0aOVm5urRYsW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",
|
| 196 |
+
"text/plain": [
|
| 197 |
+
"<Figure size 640x480 with 1 Axes>"
|
| 198 |
+
]
|
| 199 |
+
},
|
| 200 |
+
"metadata": {},
|
| 201 |
+
"output_type": "display_data"
|
| 202 |
+
}
|
| 203 |
+
],
|
| 204 |
+
"source": [
|
| 205 |
+
"row = result[result['rank'] ==2].sample(1)\n",
|
| 206 |
+
"scores = row['scores'].iloc[0]\n",
|
| 207 |
+
"plt.hist(scores, bins=np.arange(0,1,0.01))\n",
|
| 208 |
+
"ax = plt.gca()\n",
|
| 209 |
+
"\n",
|
| 210 |
+
"target_score = scores[0]\n",
|
| 211 |
+
"cand1_score = np.max(scores)\n",
|
| 212 |
+
"\n",
|
| 213 |
+
"plt.text(0.665, 0.9, f'target: {target_score:.3f}',transform=ax.transAxes,)\n",
|
| 214 |
+
"plt.text(0.665, 0.86, f'cand@1: {cand1_score:.3f}',transform=ax.transAxes,)\n",
|
| 215 |
+
"\n",
|
| 216 |
+
"candidates = np.array(row['candidates'].iloc[0])\n",
|
| 217 |
+
"target = Chem.MolFromSmiles(candidates[0])\n",
|
| 218 |
+
"sorted_scores = np.argsort(scores)\n",
|
| 219 |
+
"\n",
|
| 220 |
+
"cand = Chem.MolFromSmiles(candidates[sorted_scores][0])\n",
|
| 221 |
+
"cand1 = Chem.MolFromSmiles(candidates[sorted_scores][1])\n",
|
| 222 |
+
"cand2 = Chem.MolFromSmiles(candidates[sorted_scores][2])\n",
|
| 223 |
+
"\n",
|
| 224 |
+
"mols = [target, cand, cand1]\n",
|
| 225 |
+
"img = MolsToGridImage(mols, molsPerRow=3, subImgSize=(200, 200), legends=[f'target ({target_score:.3f})', f'cand@1 ({cand1_score:.3f})'])\n",
|
| 226 |
+
"\n",
|
| 227 |
+
"plt.show()\n",
|
| 228 |
+
"display(img)"
|
| 229 |
+
]
|
| 230 |
+
},
|
| 231 |
+
{
|
| 232 |
+
"cell_type": "code",
|
| 233 |
+
"execution_count": null,
|
| 234 |
+
"id": "541cd229",
|
| 235 |
+
"metadata": {},
|
| 236 |
+
"outputs": [],
|
| 237 |
+
"source": []
|
| 238 |
+
}
|
| 239 |
+
],
|
| 240 |
+
"metadata": {
|
| 241 |
+
"kernelspec": {
|
| 242 |
+
"display_name": "spec",
|
| 243 |
+
"language": "python",
|
| 244 |
+
"name": "python3"
|
| 245 |
+
},
|
| 246 |
+
"language_info": {
|
| 247 |
+
"codemirror_mode": {
|
| 248 |
+
"name": "ipython",
|
| 249 |
+
"version": 3
|
| 250 |
+
},
|
| 251 |
+
"file_extension": ".py",
|
| 252 |
+
"mimetype": "text/x-python",
|
| 253 |
+
"name": "python",
|
| 254 |
+
"nbconvert_exporter": "python",
|
| 255 |
+
"pygments_lexer": "ipython3",
|
| 256 |
+
"version": "3.11.7"
|
| 257 |
+
}
|
| 258 |
+
},
|
| 259 |
+
"nbformat": 4,
|
| 260 |
+
"nbformat_minor": 5
|
| 261 |
+
}
|
notebooks/UMAP_spectra_embeddings.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
notebooks/attribute_viz.ipynb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": null,
|
| 6 |
+
"id": "76c4ed82",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"# preprocessing code\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"# input is a labeled formulas file from mist code\n",
|
| 13 |
+
"# molecule is smiles\n",
|
| 14 |
+
"\n"
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"cell_type": "code",
|
| 19 |
+
"execution_count": null,
|
| 20 |
+
"id": "04ea680e",
|
| 21 |
+
"metadata": {},
|
| 22 |
+
"outputs": [],
|
| 23 |
+
"source": [
|
| 24 |
+
"# load data"
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"cell_type": "code",
|
| 29 |
+
"execution_count": null,
|
| 30 |
+
"id": "990ccd94",
|
| 31 |
+
"metadata": {},
|
| 32 |
+
"outputs": [],
|
| 33 |
+
"source": [
|
| 34 |
+
"# load model"
|
| 35 |
+
]
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"cell_type": "code",
|
| 39 |
+
"execution_count": null,
|
| 40 |
+
"id": "77334bf5",
|
| 41 |
+
"metadata": {},
|
| 42 |
+
"outputs": [],
|
| 43 |
+
"source": [
|
| 44 |
+
"# encode spectra and molecules\n",
|
| 45 |
+
"# vilisualize attributes"
|
| 46 |
+
]
|
| 47 |
+
}
|
| 48 |
+
],
|
| 49 |
+
"metadata": {
|
| 50 |
+
"language_info": {
|
| 51 |
+
"name": "python"
|
| 52 |
+
}
|
| 53 |
+
},
|
| 54 |
+
"nbformat": 4,
|
| 55 |
+
"nbformat_minor": 5
|
| 56 |
+
}
|
notebooks/filip_viz.ipynb
ADDED
|
@@ -0,0 +1,706 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [],
|
| 8 |
+
"source": [
|
| 9 |
+
"import torch\n",
|
| 10 |
+
"import numpy as np\n",
|
| 11 |
+
"import plotly.graph_objects as go\n",
|
| 12 |
+
"from plotly.subplots import make_subplots\n",
|
| 13 |
+
"from rdkit import Chem\n",
|
| 14 |
+
"from rdkit.Chem import rdDepictor\n",
|
| 15 |
+
"from rdkit.Chem.Draw import rdMolDraw2D"
|
| 16 |
+
]
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"cell_type": "markdown",
|
| 20 |
+
"metadata": {},
|
| 21 |
+
"source": [
|
| 22 |
+
"## load model and dataset"
|
| 23 |
+
]
|
| 24 |
+
},
|
| 25 |
+
{
|
| 26 |
+
"cell_type": "code",
|
| 27 |
+
"execution_count": 2,
|
| 28 |
+
"metadata": {},
|
| 29 |
+
"outputs": [
|
| 30 |
+
{
|
| 31 |
+
"name": "stdout",
|
| 32 |
+
"output_type": "stream",
|
| 33 |
+
"text": [
|
| 34 |
+
"Data path: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv\n",
|
| 35 |
+
"Processing formula spectra\n"
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
{
|
| 39 |
+
"name": "stderr",
|
| 40 |
+
"output_type": "stream",
|
| 41 |
+
"text": [
|
| 42 |
+
"100%|██████████| 213548/213548 [00:16<00:00, 13048.41it/s]\n",
|
| 43 |
+
"/data/yzhouc01/FILIP-MS/mvp/data/datasets.py:221: SettingWithCopyWarning: \n",
|
| 44 |
+
"A value is trying to be set on a copy of a slice from a DataFrame.\n",
|
| 45 |
+
"Try using .loc[row_indexer,col_indexer] = value instead\n",
|
| 46 |
+
"\n",
|
| 47 |
+
"See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
|
| 48 |
+
" tmp_df['spec'] = tmp_df.apply(lambda row: data_utils.make_tmp_subformula_spectra(row), axis=1)\n"
|
| 49 |
+
]
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"name": "stdout",
|
| 53 |
+
"output_type": "stream",
|
| 54 |
+
"text": [
|
| 55 |
+
"Loaded Model from checkpoint\n"
|
| 56 |
+
]
|
| 57 |
+
}
|
| 58 |
+
],
|
| 59 |
+
"source": [
|
| 60 |
+
"import sys\n",
|
| 61 |
+
"sys.path.insert(0, \"/data/yzhouc01/MassSpecGym\")\n",
|
| 62 |
+
"sys.path.insert(0, \"/data/yzhouc01/FILIP-MS\")\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"from rdkit import RDLogger\n",
|
| 65 |
+
"import pytorch_lightning as pl\n",
|
| 66 |
+
"from pytorch_lightning import Trainer\n",
|
| 67 |
+
"from massspecgym.models.base import Stage\n",
|
| 68 |
+
"import os\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"from mvp.utils.data import get_spec_featurizer, get_mol_featurizer, get_ms_dataset\n",
|
| 71 |
+
"from mvp.utils.models import get_model\n",
|
| 72 |
+
"\n",
|
| 73 |
+
"from mvp.definitions import TEST_RESULTS_DIR\n",
|
| 74 |
+
"import yaml\n",
|
| 75 |
+
"from functools import partial\n",
|
| 76 |
+
"# Suppress RDKit warnings and errors\n",
|
| 77 |
+
"lg = RDLogger.logger()\n",
|
| 78 |
+
"lg.setLevel(RDLogger.CRITICAL)\n",
|
| 79 |
+
"\n",
|
| 80 |
+
"# Load model and data\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"param_pth = '/data/yzhouc01/FILIP-MS/experiments/20250913_optimized_filip-model/lightning_logs/version_0/hparams.yaml'\n",
|
| 83 |
+
"with open(param_pth) as f:\n",
|
| 84 |
+
" params = yaml.load(f, Loader=yaml.FullLoader)\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"spec_featurizer = get_spec_featurizer(params['spectra_view'], params)\n",
|
| 87 |
+
"mol_featurizer = get_mol_featurizer(params['molecule_view'], params)\n",
|
| 88 |
+
"dataset = get_ms_dataset(params['spectra_view'], params['molecule_view'], spec_featurizer, mol_featurizer, params)\n",
|
| 89 |
+
"\n",
|
| 90 |
+
"\n",
|
| 91 |
+
"# load model\n",
|
| 92 |
+
"import torch \n",
|
| 93 |
+
"checkpoint_pth = \"/data/yzhouc01/FILIP-MS/experiments/20250913_optimized_filip-model/epoch=1993-train_loss=0.10.ckpt\"\n",
|
| 94 |
+
"params['checkpoint_pth'] = checkpoint_pth\n",
|
| 95 |
+
"model = get_model(params['model'], params)"
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"cell_type": "markdown",
|
| 100 |
+
"metadata": {},
|
| 101 |
+
"source": [
|
| 102 |
+
"## mol/spec embeddings"
|
| 103 |
+
]
|
| 104 |
+
},
|
| 105 |
+
{
|
| 106 |
+
"cell_type": "code",
|
| 107 |
+
"execution_count": 7,
|
| 108 |
+
"metadata": {},
|
| 109 |
+
"outputs": [
|
| 110 |
+
{
|
| 111 |
+
"name": "stdout",
|
| 112 |
+
"output_type": "stream",
|
| 113 |
+
"text": [
|
| 114 |
+
"Atom 0: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 115 |
+
"Atom 1: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 116 |
+
"Atom 2: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
|
| 117 |
+
"Atom 3: N, Graph node features: tensor([0.1401, 0.0000, 0.0000, 0.0000, 1.0000])\n",
|
| 118 |
+
"Atom 4: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 119 |
+
"Atom 5: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 120 |
+
"Atom 6: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 121 |
+
"Atom 7: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 122 |
+
"Atom 8: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 123 |
+
"Atom 9: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 124 |
+
"Atom 10: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 125 |
+
"Atom 11: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 126 |
+
"Atom 12: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 127 |
+
"Atom 13: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 128 |
+
"Atom 14: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 129 |
+
"Atom 15: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 130 |
+
"Atom 16: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
|
| 131 |
+
"Atom 17: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
|
| 132 |
+
"Atom 18: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
|
| 133 |
+
"Atom 19: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
|
| 134 |
+
"Atom 20: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n"
|
| 135 |
+
]
|
| 136 |
+
}
|
| 137 |
+
],
|
| 138 |
+
"source": [
|
| 139 |
+
"# sanity check, rdkit order is preserved\n",
|
| 140 |
+
"i = 0\n",
|
| 141 |
+
"s = dataset.metadata.iloc[i]['smiles']\n",
|
| 142 |
+
"mol = Chem.MolFromSmiles(s, sanitize=True) \n",
|
| 143 |
+
"g = dataset[i]['mol']\n",
|
| 144 |
+
"\n",
|
| 145 |
+
"# Compare RDKit atoms vs graph nodes\n",
|
| 146 |
+
"for atom in mol.GetAtoms():\n",
|
| 147 |
+
" idx = atom.GetIdx()\n",
|
| 148 |
+
" print(f\"Atom {idx}: {atom.GetSymbol()}, Graph node features: {g.ndata['h'][idx][:5]}\")"
|
| 149 |
+
]
|
| 150 |
+
},
|
| 151 |
+
{
|
| 152 |
+
"cell_type": "code",
|
| 153 |
+
"execution_count": 5,
|
| 154 |
+
"metadata": {},
|
| 155 |
+
"outputs": [],
|
| 156 |
+
"source": [
|
| 157 |
+
"import numpy as np\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"# Atomic masses corresponding to your atom_labels\n",
|
| 160 |
+
"ATOM_LABELS = ['H', 'C', 'O', 'N', 'P', 'S', 'Cl', 'F', 'Br', 'I', 'B', 'As', 'Si', 'Se']\n",
|
| 161 |
+
"ATOM_MASSES = np.array([\n",
|
| 162 |
+
" 1.0078, 12.0000, 15.9949, 14.0031, 30.9738, 31.9721, \n",
|
| 163 |
+
" 35.45, 18.9984, 79.90, 126.90, 10.811, 74.9216, 28.085, 78.96\n",
|
| 164 |
+
"])\n",
|
| 165 |
+
"norm_vector = [102.0, 59.0, 25.0, 13.0, 3.0, 6.0, 6.0, 17.0, 4.0, 4.0, 1.0, 1.0, 5.0, 2.0]\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"def spectra_from_encoding(spectral_tensor, norm_vector=norm_vector):\n",
|
| 168 |
+
" \"\"\"\n",
|
| 169 |
+
" Convert encoded spectra (num_peaks x 15) into m/z, intensities, and molecular formulas.\n",
|
| 170 |
+
" Can undo normalization if a norm_vector is provided.\n",
|
| 171 |
+
" \n",
|
| 172 |
+
" Args:\n",
|
| 173 |
+
" spectral_tensor (np.ndarray or torch.Tensor): [num_peaks, 15]\n",
|
| 174 |
+
" norm_vector (np.ndarray or list): length 14, normalization factor for each atom\n",
|
| 175 |
+
" \n",
|
| 176 |
+
" Returns:\n",
|
| 177 |
+
" mzs (list of float): list of m/z values\n",
|
| 178 |
+
" intensities (list of float): list of intensities\n",
|
| 179 |
+
" formulas (list of str): molecular formula strings\n",
|
| 180 |
+
" \"\"\"\n",
|
| 181 |
+
" if hasattr(spectral_tensor, \"detach\"):\n",
|
| 182 |
+
" spectral_tensor = spectral_tensor.detach().cpu().numpy()\n",
|
| 183 |
+
" \n",
|
| 184 |
+
" counts = spectral_tensor[:, :14] # atom counts\n",
|
| 185 |
+
" intensities = spectral_tensor[:, 14] # last col = intensity\n",
|
| 186 |
+
" \n",
|
| 187 |
+
" # Undo normalization\n",
|
| 188 |
+
" if norm_vector is not None:\n",
|
| 189 |
+
" counts = counts * np.array(norm_vector)\n",
|
| 190 |
+
" \n",
|
| 191 |
+
" # Compute m/z\n",
|
| 192 |
+
" mzs = (counts * ATOM_MASSES).sum(axis=1)\n",
|
| 193 |
+
" \n",
|
| 194 |
+
" # Build molecular formula strings\n",
|
| 195 |
+
" formulas = []\n",
|
| 196 |
+
" for peak_counts in counts:\n",
|
| 197 |
+
" formula_parts = []\n",
|
| 198 |
+
" for elem, count in zip(ATOM_LABELS, peak_counts):\n",
|
| 199 |
+
" n = int(round(count))\n",
|
| 200 |
+
" if n > 0:\n",
|
| 201 |
+
" formula_parts.append(f\"{elem}{n if n > 1 else ''}\")\n",
|
| 202 |
+
" formulas.append(\"\".join(formula_parts) if formula_parts else \"Unknown\")\n",
|
| 203 |
+
" \n",
|
| 204 |
+
" return mzs.tolist(), intensities.tolist(), formulas"
|
| 205 |
+
]
|
| 206 |
+
},
|
| 207 |
+
{
|
| 208 |
+
"cell_type": "markdown",
|
| 209 |
+
"metadata": {},
|
| 210 |
+
"source": [
|
| 211 |
+
"## visualization"
|
| 212 |
+
]
|
| 213 |
+
},
|
| 214 |
+
{
|
| 215 |
+
"cell_type": "code",
|
| 216 |
+
"execution_count": 8,
|
| 217 |
+
"metadata": {},
|
| 218 |
+
"outputs": [],
|
| 219 |
+
"source": [
|
| 220 |
+
"\n",
|
| 221 |
+
"import torch.nn.functional as F\n",
|
| 222 |
+
"\n",
|
| 223 |
+
"def mol_to_graph_coords(mol):\n",
|
| 224 |
+
" \"\"\"Return atom coordinates and bond list for a molecule.\"\"\"\n",
|
| 225 |
+
" rdDepictor.Compute2DCoords(mol)\n",
|
| 226 |
+
" conf = mol.GetConformer()\n",
|
| 227 |
+
" coords = {i: conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())}\n",
|
| 228 |
+
" bonds = [(b.GetBeginAtomIdx(), b.GetEndAtomIdx()) for b in mol.GetBonds()]\n",
|
| 229 |
+
" return coords, bonds\n",
|
| 230 |
+
"\n",
|
| 231 |
+
"def interactive_attention_visualization(spectral_embeds, graph_embeds, \n",
|
| 232 |
+
" peak_mzs, peak_intensities, peak_formulas, mol):\n",
|
| 233 |
+
" \"\"\"\n",
|
| 234 |
+
" Interactive visualization of peak-node similarity with color scale legend.\n",
|
| 235 |
+
" - Clicking a peak recolors nodes by similarity\n",
|
| 236 |
+
" - Clicking a node recolors peaks by similarity\n",
|
| 237 |
+
" \"\"\"\n",
|
| 238 |
+
" # Similarity matrix\n",
|
| 239 |
+
" spectral_embeds = F.normalize(spectral_embeds, p=2, dim=-1)\n",
|
| 240 |
+
" graph_embeds = F.normalize(graph_embeds, p=2, dim=-1)\n",
|
| 241 |
+
" \n",
|
| 242 |
+
" similarity = torch.matmul(spectral_embeds, graph_embeds.T).detach().cpu().numpy()\n",
|
| 243 |
+
" sim_norm = (similarity - similarity.min()) / (similarity.max() - similarity.min() + 1e-8)\n",
|
| 244 |
+
" \n",
|
| 245 |
+
" num_peaks, num_nodes = similarity.shape\n",
|
| 246 |
+
" \n",
|
| 247 |
+
" # --- Molecule graph ---\n",
|
| 248 |
+
" coords, bonds = mol_to_graph_coords(mol)\n",
|
| 249 |
+
" atom_labels = [a.GetSymbol() for a in mol.GetAtoms()]\n",
|
| 250 |
+
" atom_x = [coords[i].x for i in range(num_nodes)]\n",
|
| 251 |
+
" atom_y = [coords[i].y for i in range(num_nodes)]\n",
|
| 252 |
+
" \n",
|
| 253 |
+
" # --- Spectrum trace ---\n",
|
| 254 |
+
" spectrum_trace = go.Bar(\n",
|
| 255 |
+
" x=peak_mzs,\n",
|
| 256 |
+
" y=peak_intensities,\n",
|
| 257 |
+
" name='peak',\n",
|
| 258 |
+
" marker=dict(color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
|
| 259 |
+
" colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
|
| 260 |
+
" hovertext=[f\"Formula {f}\" for f in peak_formulas],\n",
|
| 261 |
+
" customdata=list(range(num_peaks)) # peak index\n",
|
| 262 |
+
" )\n",
|
| 263 |
+
" \n",
|
| 264 |
+
" # --- Graph nodes ---\n",
|
| 265 |
+
" graph_nodes = go.Scatter(\n",
|
| 266 |
+
" x=atom_x, y=atom_y,\n",
|
| 267 |
+
" mode=\"markers+text\",\n",
|
| 268 |
+
" name='node',\n",
|
| 269 |
+
" text=atom_labels,\n",
|
| 270 |
+
" textposition=\"middle center\",\n",
|
| 271 |
+
" marker=dict(size=20, color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
|
| 272 |
+
" colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
|
| 273 |
+
" customdata=list(range(num_nodes)),\n",
|
| 274 |
+
" # hovertext=[f\"Atom {i} ({label})\" for i, label in enumerate(atom_labels)]\n",
|
| 275 |
+
" )\n",
|
| 276 |
+
" \n",
|
| 277 |
+
" # --- Graph bonds ---\n",
|
| 278 |
+
" edge_x, edge_y = [], []\n",
|
| 279 |
+
" for i, j in bonds:\n",
|
| 280 |
+
" edge_x += [coords[i].x, coords[j].x, None]\n",
|
| 281 |
+
" edge_y += [coords[i].y, coords[j].y, None]\n",
|
| 282 |
+
" graph_edges = go.Scatter(\n",
|
| 283 |
+
" x=edge_x, y=edge_y,\n",
|
| 284 |
+
" mode=\"lines\", line=dict(color=\"gray\", width=2),\n",
|
| 285 |
+
" hoverinfo=\"none\", showlegend=False\n",
|
| 286 |
+
" )\n",
|
| 287 |
+
" \n",
|
| 288 |
+
" # --- Subplots ---\n",
|
| 289 |
+
" fig = make_subplots(rows=1, cols=2, subplot_titles=(\"Spectrum\", \"Molecule\"), \n",
|
| 290 |
+
" column_widths=[0.6, 0.4])\n",
|
| 291 |
+
" \n",
|
| 292 |
+
" fig.add_trace(spectrum_trace, row=1, col=1)\n",
|
| 293 |
+
" fig.add_trace(graph_edges, row=1, col=2)\n",
|
| 294 |
+
" fig.add_trace(graph_nodes, row=1, col=2)\n",
|
| 295 |
+
" \n",
|
| 296 |
+
" fig.update_xaxes(title=\"m/z\", row=1, col=1)\n",
|
| 297 |
+
" fig.update_yaxes(title=\"Intensity\", row=1, col=1)\n",
|
| 298 |
+
" fig.update_xaxes(visible=False, row=1, col=2)\n",
|
| 299 |
+
" fig.update_yaxes(visible=False, row=1, col=2)\n",
|
| 300 |
+
" \n",
|
| 301 |
+
" fig.update_layout(title=\"Peak ↔ Node Similarity\", showlegend=False)\n",
|
| 302 |
+
" \n",
|
| 303 |
+
" # --- Interactivity ---\n",
|
| 304 |
+
" from ipywidgets import VBox\n",
|
| 305 |
+
" fw = go.FigureWidget(fig)\n",
|
| 306 |
+
"\n",
|
| 307 |
+
" def highlight_nodes(trace, points, selector):\n",
|
| 308 |
+
" \"\"\"Click on peak → recolor nodes\"\"\"\n",
|
| 309 |
+
" if points.point_inds:\n",
|
| 310 |
+
" peak_idx = points.point_inds[0]\n",
|
| 311 |
+
" scores = sim_norm[peak_idx, :]\n",
|
| 312 |
+
" with fw.batch_update():\n",
|
| 313 |
+
" fw.data[2].marker.color = scores\n",
|
| 314 |
+
" fw.data[0].marker.color = [\"red\" if i == peak_idx else \"lightgray\" for i in range(num_peaks)]\n",
|
| 315 |
+
"\n",
|
| 316 |
+
" def highlight_peaks(trace, points, selector):\n",
|
| 317 |
+
" \"\"\"Click on node → recolor peaks\"\"\"\n",
|
| 318 |
+
" if points.point_inds:\n",
|
| 319 |
+
" node_idx = points.point_inds[0]\n",
|
| 320 |
+
" scores = sim_norm[:, node_idx]\n",
|
| 321 |
+
" with fw.batch_update():\n",
|
| 322 |
+
" fw.data[0].marker.color = scores\n",
|
| 323 |
+
" fw.data[2].marker.color = [\"red\" if i == node_idx else \"lightgray\" for i in range(num_nodes)]\n",
|
| 324 |
+
" \n",
|
| 325 |
+
" fw.data[0].on_click(highlight_nodes) # spectrum\n",
|
| 326 |
+
" fw.data[2].on_click(highlight_peaks) # nodes\n",
|
| 327 |
+
" \n",
|
| 328 |
+
" return fw\n"
|
| 329 |
+
]
|
| 330 |
+
},
|
| 331 |
+
{
|
| 332 |
+
"cell_type": "code",
|
| 333 |
+
"execution_count": 7,
|
| 334 |
+
"metadata": {},
|
| 335 |
+
"outputs": [
|
| 336 |
+
{
|
| 337 |
+
"data": {
|
| 338 |
+
"text/html": [
|
| 339 |
+
"<div>\n",
|
| 340 |
+
"<style scoped>\n",
|
| 341 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 342 |
+
" vertical-align: middle;\n",
|
| 343 |
+
" }\n",
|
| 344 |
+
"\n",
|
| 345 |
+
" .dataframe tbody tr th {\n",
|
| 346 |
+
" vertical-align: top;\n",
|
| 347 |
+
" }\n",
|
| 348 |
+
"\n",
|
| 349 |
+
" .dataframe thead th {\n",
|
| 350 |
+
" text-align: right;\n",
|
| 351 |
+
" }\n",
|
| 352 |
+
"</style>\n",
|
| 353 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 354 |
+
" <thead>\n",
|
| 355 |
+
" <tr style=\"text-align: right;\">\n",
|
| 356 |
+
" <th></th>\n",
|
| 357 |
+
" <th>identifier</th>\n",
|
| 358 |
+
" <th>mzs</th>\n",
|
| 359 |
+
" <th>intensities</th>\n",
|
| 360 |
+
" <th>smiles</th>\n",
|
| 361 |
+
" <th>inchikey</th>\n",
|
| 362 |
+
" <th>formula</th>\n",
|
| 363 |
+
" <th>precursor_formula</th>\n",
|
| 364 |
+
" <th>parent_mass</th>\n",
|
| 365 |
+
" <th>precursor_mz</th>\n",
|
| 366 |
+
" <th>adduct</th>\n",
|
| 367 |
+
" <th>instrument_type</th>\n",
|
| 368 |
+
" <th>collision_energy</th>\n",
|
| 369 |
+
" <th>fold</th>\n",
|
| 370 |
+
" <th>simulation_challenge</th>\n",
|
| 371 |
+
" <th>formulas</th>\n",
|
| 372 |
+
" <th>formula_mzs</th>\n",
|
| 373 |
+
" <th>formula_intensities</th>\n",
|
| 374 |
+
" </tr>\n",
|
| 375 |
+
" </thead>\n",
|
| 376 |
+
" <tbody>\n",
|
| 377 |
+
" <tr>\n",
|
| 378 |
+
" <th>76895</th>\n",
|
| 379 |
+
" <td>MassSpecGymID0098304</td>\n",
|
| 380 |
+
" <td>65.0386,77.0387,79.0543,80.0621,82.0414,89.038...</td>\n",
|
| 381 |
+
" <td>0.07907907907907907,0.05905905905905906,0.1791...</td>\n",
|
| 382 |
+
" <td>COC1=C(C=CC(=C1)/C=C/C=O)O</td>\n",
|
| 383 |
+
" <td>DKZBBWMURDFHNE</td>\n",
|
| 384 |
+
" <td>C10H10O3</td>\n",
|
| 385 |
+
" <td>C10H11O3</td>\n",
|
| 386 |
+
" <td>178.063024</td>\n",
|
| 387 |
+
" <td>179.0703</td>\n",
|
| 388 |
+
" <td>[M+H]+</td>\n",
|
| 389 |
+
" <td>Orbitrap</td>\n",
|
| 390 |
+
" <td>34.023357</td>\n",
|
| 391 |
+
" <td>train</td>\n",
|
| 392 |
+
" <td>True</td>\n",
|
| 393 |
+
" <td>[C5H4, C6H4, C6H6, C6H7, C5H5O, C7H4, C7H5, C7...</td>\n",
|
| 394 |
+
" <td>[65.0386, 77.0387, 79.0543, 80.0621, 82.0414, ...</td>\n",
|
| 395 |
+
" <td>[0.281209886028212, 0.24302057526061452, 0.423...</td>\n",
|
| 396 |
+
" </tr>\n",
|
| 397 |
+
" <tr>\n",
|
| 398 |
+
" <th>72767</th>\n",
|
| 399 |
+
" <td>MassSpecGymID0092123</td>\n",
|
| 400 |
+
" <td>112.0506</td>\n",
|
| 401 |
+
" <td>1.0</td>\n",
|
| 402 |
+
" <td>C1[C@@H](O[C@@H](S1)CO)N2C=CC(=NC2=O)N</td>\n",
|
| 403 |
+
" <td>JTEGQNOMFQHVDC</td>\n",
|
| 404 |
+
" <td>C8H11N3O3S</td>\n",
|
| 405 |
+
" <td>C8H12N3O3S</td>\n",
|
| 406 |
+
" <td>229.052124</td>\n",
|
| 407 |
+
" <td>230.0594</td>\n",
|
| 408 |
+
" <td>[M+H]+</td>\n",
|
| 409 |
+
" <td>Orbitrap</td>\n",
|
| 410 |
+
" <td>15.000000</td>\n",
|
| 411 |
+
" <td>train</td>\n",
|
| 412 |
+
" <td>True</td>\n",
|
| 413 |
+
" <td>[C4H5N3O]</td>\n",
|
| 414 |
+
" <td>[112.0506]</td>\n",
|
| 415 |
+
" <td>[1.0]</td>\n",
|
| 416 |
+
" </tr>\n",
|
| 417 |
+
" <tr>\n",
|
| 418 |
+
" <th>221715</th>\n",
|
| 419 |
+
" <td>MassSpecGymID0401545</td>\n",
|
| 420 |
+
" <td>50.992828,51.670795,51.675632,51.678509,51.681...</td>\n",
|
| 421 |
+
" <td>0.0006785717253652819,0.001297853734957549,0.0...</td>\n",
|
| 422 |
+
" <td>CC(=O)N1CCC(CC1)C(=O)O</td>\n",
|
| 423 |
+
" <td>WFCLWJHOKCQYOQ</td>\n",
|
| 424 |
+
" <td>C8H13NO3</td>\n",
|
| 425 |
+
" <td>C8H14NO3</td>\n",
|
| 426 |
+
" <td>171.089724</td>\n",
|
| 427 |
+
" <td>172.0970</td>\n",
|
| 428 |
+
" <td>[M+H]+</td>\n",
|
| 429 |
+
" <td>Orbitrap</td>\n",
|
| 430 |
+
" <td>NaN</td>\n",
|
| 431 |
+
" <td>train</td>\n",
|
| 432 |
+
" <td>False</td>\n",
|
| 433 |
+
" <td>[C5H9N, C5H5NO2, C5H5NO2, C6H7O2, C6H7O2, C6H7...</td>\n",
|
| 434 |
+
" <td>[84.080452, 112.038101, 112.041489, 112.048691...</td>\n",
|
| 435 |
+
" <td>[0.11135079703351926, 0.060621778264910706, 0....</td>\n",
|
| 436 |
+
" </tr>\n",
|
| 437 |
+
" </tbody>\n",
|
| 438 |
+
"</table>\n",
|
| 439 |
+
"</div>"
|
| 440 |
+
],
|
| 441 |
+
"text/plain": [
|
| 442 |
+
" identifier \\\n",
|
| 443 |
+
"76895 MassSpecGymID0098304 \n",
|
| 444 |
+
"72767 MassSpecGymID0092123 \n",
|
| 445 |
+
"221715 MassSpecGymID0401545 \n",
|
| 446 |
+
"\n",
|
| 447 |
+
" mzs \\\n",
|
| 448 |
+
"76895 65.0386,77.0387,79.0543,80.0621,82.0414,89.038... \n",
|
| 449 |
+
"72767 112.0506 \n",
|
| 450 |
+
"221715 50.992828,51.670795,51.675632,51.678509,51.681... \n",
|
| 451 |
+
"\n",
|
| 452 |
+
" intensities \\\n",
|
| 453 |
+
"76895 0.07907907907907907,0.05905905905905906,0.1791... \n",
|
| 454 |
+
"72767 1.0 \n",
|
| 455 |
+
"221715 0.0006785717253652819,0.001297853734957549,0.0... \n",
|
| 456 |
+
"\n",
|
| 457 |
+
" smiles inchikey formula \\\n",
|
| 458 |
+
"76895 COC1=C(C=CC(=C1)/C=C/C=O)O DKZBBWMURDFHNE C10H10O3 \n",
|
| 459 |
+
"72767 C1[C@@H](O[C@@H](S1)CO)N2C=CC(=NC2=O)N JTEGQNOMFQHVDC C8H11N3O3S \n",
|
| 460 |
+
"221715 CC(=O)N1CCC(CC1)C(=O)O WFCLWJHOKCQYOQ C8H13NO3 \n",
|
| 461 |
+
"\n",
|
| 462 |
+
" precursor_formula parent_mass precursor_mz adduct instrument_type \\\n",
|
| 463 |
+
"76895 C10H11O3 178.063024 179.0703 [M+H]+ Orbitrap \n",
|
| 464 |
+
"72767 C8H12N3O3S 229.052124 230.0594 [M+H]+ Orbitrap \n",
|
| 465 |
+
"221715 C8H14NO3 171.089724 172.0970 [M+H]+ Orbitrap \n",
|
| 466 |
+
"\n",
|
| 467 |
+
" collision_energy fold simulation_challenge \\\n",
|
| 468 |
+
"76895 34.023357 train True \n",
|
| 469 |
+
"72767 15.000000 train True \n",
|
| 470 |
+
"221715 NaN train False \n",
|
| 471 |
+
"\n",
|
| 472 |
+
" formulas \\\n",
|
| 473 |
+
"76895 [C5H4, C6H4, C6H6, C6H7, C5H5O, C7H4, C7H5, C7... \n",
|
| 474 |
+
"72767 [C4H5N3O] \n",
|
| 475 |
+
"221715 [C5H9N, C5H5NO2, C5H5NO2, C6H7O2, C6H7O2, C6H7... \n",
|
| 476 |
+
"\n",
|
| 477 |
+
" formula_mzs \\\n",
|
| 478 |
+
"76895 [65.0386, 77.0387, 79.0543, 80.0621, 82.0414, ... \n",
|
| 479 |
+
"72767 [112.0506] \n",
|
| 480 |
+
"221715 [84.080452, 112.038101, 112.041489, 112.048691... \n",
|
| 481 |
+
"\n",
|
| 482 |
+
" formula_intensities \n",
|
| 483 |
+
"76895 [0.281209886028212, 0.24302057526061452, 0.423... \n",
|
| 484 |
+
"72767 [1.0] \n",
|
| 485 |
+
"221715 [0.11135079703351926, 0.060621778264910706, 0.... "
|
| 486 |
+
]
|
| 487 |
+
},
|
| 488 |
+
"execution_count": 7,
|
| 489 |
+
"metadata": {},
|
| 490 |
+
"output_type": "execute_result"
|
| 491 |
+
}
|
| 492 |
+
],
|
| 493 |
+
"source": [
|
| 494 |
+
"dataset.metadata[dataset.metadata['precursor_mz'] <=250].sample(3)"
|
| 495 |
+
]
|
| 496 |
+
},
|
| 497 |
+
{
|
| 498 |
+
"cell_type": "code",
|
| 499 |
+
"execution_count": 12,
|
| 500 |
+
"metadata": {},
|
| 501 |
+
"outputs": [
|
| 502 |
+
{
|
| 503 |
+
"data": {
|
| 504 |
+
"application/vnd.jupyter.widget-view+json": {
|
| 505 |
+
"model_id": "202cce9fe93e477e9e9b2e8775a33be3",
|
| 506 |
+
"version_major": 2,
|
| 507 |
+
"version_minor": 0
|
| 508 |
+
},
|
| 509 |
+
"text/plain": [
|
| 510 |
+
"FigureWidget({\n",
|
| 511 |
+
" 'data': [{'customdata': [0, 1, 2, 3, 4, 5, 6, 7],\n",
|
| 512 |
+
" 'hovertext': [Formula H8C8O, Formula H10C10O, Formula H10C11O,\n",
|
| 513 |
+
" Formula H16C13O, Formula H16C12ON, Formula H16C14O,\n",
|
| 514 |
+
" Formula H19C14ON, Formula H21C14O2N],\n",
|
| 515 |
+
" 'marker': {'cmax': 1,\n",
|
| 516 |
+
" 'cmin': 0,\n",
|
| 517 |
+
" 'color': 'lightgray',\n",
|
| 518 |
+
" 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
|
| 519 |
+
" 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
|
| 520 |
+
" '#482878'], [0.2222222222222222,\n",
|
| 521 |
+
" '#3e4989'], [0.3333333333333333,\n",
|
| 522 |
+
" '#31688e'], [0.4444444444444444,\n",
|
| 523 |
+
" '#26828e'], [0.5555555555555556,\n",
|
| 524 |
+
" '#1f9e89'], [0.6666666666666666,\n",
|
| 525 |
+
" '#35b779'], [0.7777777777777778,\n",
|
| 526 |
+
" '#6ece58'], [0.8888888888888888,\n",
|
| 527 |
+
" '#b5de2b'], [1.0, '#fde725']]},\n",
|
| 528 |
+
" 'name': 'peak',\n",
|
| 529 |
+
" 'type': 'bar',\n",
|
| 530 |
+
" 'uid': 'c5467361-ef75-4547-a2f0-d124d0db63ce',\n",
|
| 531 |
+
" 'x': [120.05730010663046, 146.07290266870038, 158.07289873479382,\n",
|
| 532 |
+
" 188.11970182247236, 190.1227957280129, 200.1196978885658,\n",
|
| 533 |
+
" 217.14619823001323, 235.15669775236026],\n",
|
| 534 |
+
" 'xaxis': 'x',\n",
|
| 535 |
+
" 'y': [0.46595707535743713, 0.095524862408638, 0.08919080346822739,\n",
|
| 536 |
+
" 1.0, 0.10595753788948059, 0.1503429412841797,\n",
|
| 537 |
+
" 0.1142716035246849, 1.100000023841858],\n",
|
| 538 |
+
" 'yaxis': 'y'},\n",
|
| 539 |
+
" {'hoverinfo': 'none',\n",
|
| 540 |
+
" 'line': {'color': 'gray', 'width': 2},\n",
|
| 541 |
+
" 'mode': 'lines',\n",
|
| 542 |
+
" 'showlegend': False,\n",
|
| 543 |
+
" 'type': 'scatter',\n",
|
| 544 |
+
" 'uid': '86756d4d-d34e-4422-8024-50e923982319',\n",
|
| 545 |
+
" 'x': [-5.436182348119566, -3.9950746378612374, None,\n",
|
| 546 |
+
" -3.9950746378612374, -2.9140954429243706, None,\n",
|
| 547 |
+
" -2.9140954429243706, -3.274223958245831, None,\n",
|
| 548 |
+
" -3.274223958245831, -2.193244763308963, None,\n",
|
| 549 |
+
" -2.193244763308963, -0.7521370530506348, None,\n",
|
| 550 |
+
" -0.7521370530506348, -0.39200853772917454, None,\n",
|
| 551 |
+
" -0.39200853772917454, -1.4729877326660425, None,\n",
|
| 552 |
+
" -0.39200853772917454, 1.0490991725291536, None,\n",
|
| 553 |
+
" 1.0490991725291536, 0.38899287090069107, None,\n",
|
| 554 |
+
" 0.38899287090069107, 1.225427933877235, None,\n",
|
| 555 |
+
" 1.225427933877235, 2.721969298482242, None, 2.721969298482242,\n",
|
| 556 |
+
" 3.3820756001107055, None, 3.3820756001107055,\n",
|
| 557 |
+
" 2.545640537134162, None, 2.545640537134162, 3.2057468387626264,\n",
|
| 558 |
+
" None, 3.2057468387626264, 4.702288203367634, None,\n",
|
| 559 |
+
" 1.0490991725291536, 1.2087140187413736, None,\n",
|
| 560 |
+
" -1.4729877326660425, -2.9140954429243706, None,\n",
|
| 561 |
+
" 2.545640537134162, 1.0490991725291536, None],\n",
|
| 562 |
+
" 'xaxis': 'x2',\n",
|
| 563 |
+
" 'y': [-0.8275311534181835, -1.243714487339663, None,\n",
|
| 564 |
+
" -1.243714487339663, -0.2037702676270663, None,\n",
|
| 565 |
+
" -0.2037702676270663, 1.2523572860070113, None,\n",
|
| 566 |
+
" 1.2523572860070113, 2.2923015057196072, None,\n",
|
| 567 |
+
" 2.2923015057196072, 1.8761181717981272, None,\n",
|
| 568 |
+
" 1.8761181717981272, 0.4199906181640502, None,\n",
|
| 569 |
+
" 0.4199906181640502, -0.6199536015485465, None,\n",
|
| 570 |
+
" 0.4199906181640502, 0.0038072842425705966, None,\n",
|
| 571 |
+
" 0.0038072842425705966, -1.343137284234691, None,\n",
|
| 572 |
+
" -1.343137284234691, -2.588278394881763, None,\n",
|
| 573 |
+
" -2.588278394881763, -2.4864749370515753, None,\n",
|
| 574 |
+
" -2.4864749370515753, -1.1395303685743154, None,\n",
|
| 575 |
+
" -1.1395303685743154, 0.10561074207275722, None,\n",
|
| 576 |
+
" 0.10561074207275722, 1.4525553105500184, None,\n",
|
| 577 |
+
" 1.4525553105500184, 1.554358768380205, None,\n",
|
| 578 |
+
" 0.0038072842425705966, 1.4952908077414555, None,\n",
|
| 579 |
+
" -0.6199536015485465, -0.2037702676270663, None,\n",
|
| 580 |
+
" 0.10561074207275722, 0.0038072842425705966, None],\n",
|
| 581 |
+
" 'yaxis': 'y2'},\n",
|
| 582 |
+
" {'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
|
| 583 |
+
" 16],\n",
|
| 584 |
+
" 'marker': {'cmax': 1,\n",
|
| 585 |
+
" 'cmin': 0,\n",
|
| 586 |
+
" 'color': 'lightgray',\n",
|
| 587 |
+
" 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
|
| 588 |
+
" 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
|
| 589 |
+
" '#482878'], [0.2222222222222222,\n",
|
| 590 |
+
" '#3e4989'], [0.3333333333333333,\n",
|
| 591 |
+
" '#31688e'], [0.4444444444444444,\n",
|
| 592 |
+
" '#26828e'], [0.5555555555555556,\n",
|
| 593 |
+
" '#1f9e89'], [0.6666666666666666,\n",
|
| 594 |
+
" '#35b779'], [0.7777777777777778,\n",
|
| 595 |
+
" '#6ece58'], [0.8888888888888888,\n",
|
| 596 |
+
" '#b5de2b'], [1.0, '#fde725']],\n",
|
| 597 |
+
" 'size': 20},\n",
|
| 598 |
+
" 'mode': 'markers+text',\n",
|
| 599 |
+
" 'name': 'node',\n",
|
| 600 |
+
" 'text': [C, O, C, C, C, C, C, C, C, C, C, C, C, C, C, N, O],\n",
|
| 601 |
+
" 'textposition': 'middle center',\n",
|
| 602 |
+
" 'type': 'scatter',\n",
|
| 603 |
+
" 'uid': '5c2fb088-99a4-4115-ada1-d9b560973db3',\n",
|
| 604 |
+
" 'x': [-5.436182348119566, -3.9950746378612374, -2.9140954429243706,\n",
|
| 605 |
+
" -3.274223958245831, -2.193244763308963, -0.7521370530506348,\n",
|
| 606 |
+
" -0.39200853772917454, -1.4729877326660425, 1.0490991725291536,\n",
|
| 607 |
+
" 0.38899287090069107, 1.225427933877235, 2.721969298482242,\n",
|
| 608 |
+
" 3.3820756001107055, 2.545640537134162, 3.2057468387626264,\n",
|
| 609 |
+
" 4.702288203367634, 1.2087140187413736],\n",
|
| 610 |
+
" 'xaxis': 'x2',\n",
|
| 611 |
+
" 'y': [-0.8275311534181835, -1.243714487339663, -0.2037702676270663,\n",
|
| 612 |
+
" 1.2523572860070113, 2.2923015057196072, 1.8761181717981272,\n",
|
| 613 |
+
" 0.4199906181640502, -0.6199536015485465, 0.0038072842425705966,\n",
|
| 614 |
+
" -1.343137284234691, -2.588278394881763, -2.4864749370515753,\n",
|
| 615 |
+
" -1.1395303685743154, 0.10561074207275722, 1.4525553105500184,\n",
|
| 616 |
+
" 1.554358768380205, 1.4952908077414555],\n",
|
| 617 |
+
" 'yaxis': 'y2'}],\n",
|
| 618 |
+
" 'layout': {'annotations': [{'font': {'size': 16},\n",
|
| 619 |
+
" 'showarrow': False,\n",
|
| 620 |
+
" 'text': 'Spectrum',\n",
|
| 621 |
+
" 'x': 0.27,\n",
|
| 622 |
+
" 'xanchor': 'center',\n",
|
| 623 |
+
" 'xref': 'paper',\n",
|
| 624 |
+
" 'y': 1.0,\n",
|
| 625 |
+
" 'yanchor': 'bottom',\n",
|
| 626 |
+
" 'yref': 'paper'},\n",
|
| 627 |
+
" {'font': {'size': 16},\n",
|
| 628 |
+
" 'showarrow': False,\n",
|
| 629 |
+
" 'text': 'Molecule',\n",
|
| 630 |
+
" 'x': 0.8200000000000001,\n",
|
| 631 |
+
" 'xanchor': 'center',\n",
|
| 632 |
+
" 'xref': 'paper',\n",
|
| 633 |
+
" 'y': 1.0,\n",
|
| 634 |
+
" 'yanchor': 'bottom',\n",
|
| 635 |
+
" 'yref': 'paper'}],\n",
|
| 636 |
+
" 'showlegend': False,\n",
|
| 637 |
+
" 'template': '...',\n",
|
| 638 |
+
" 'title': {'text': 'Peak ↔ Node Similarity'},\n",
|
| 639 |
+
" 'xaxis': {'anchor': 'y', 'domain': [0.0, 0.54], 'title': {'text': 'm/z'}},\n",
|
| 640 |
+
" 'xaxis2': {'anchor': 'y2', 'domain': [0.64, 1.0], 'visible': False},\n",
|
| 641 |
+
" 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Intensity'}},\n",
|
| 642 |
+
" 'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0], 'visible': False}}\n",
|
| 643 |
+
"})"
|
| 644 |
+
]
|
| 645 |
+
},
|
| 646 |
+
"execution_count": 12,
|
| 647 |
+
"metadata": {},
|
| 648 |
+
"output_type": "execute_result"
|
| 649 |
+
}
|
| 650 |
+
],
|
| 651 |
+
"source": [
|
| 652 |
+
"from rdkit import Chem\n",
|
| 653 |
+
"\n",
|
| 654 |
+
"# Data\n",
|
| 655 |
+
"# i = 40991\n",
|
| 656 |
+
"\n",
|
| 657 |
+
"ms_id = \"MassSpecGymID0033096\"\n",
|
| 658 |
+
"i = dataset.metadata[dataset.metadata['identifier'] == ms_id].index[0]\n",
|
| 659 |
+
"s = dataset.metadata.iloc[i]['smiles']\n",
|
| 660 |
+
"mol = Chem.MolFromSmiles(s)\n",
|
| 661 |
+
"g = dataset[i]['mol']\n",
|
| 662 |
+
"spec = dataset[i]['SpecFormula']\n",
|
| 663 |
+
"\n",
|
| 664 |
+
"peak_mzs, peak_intensities, peak_formulas = spectra_from_encoding(spec)\n",
|
| 665 |
+
"\n",
|
| 666 |
+
"# Embeddings\n",
|
| 667 |
+
"model = model.to(torch.device('cpu'))\n",
|
| 668 |
+
"model.eval()\n",
|
| 669 |
+
"with torch.no_grad():\n",
|
| 670 |
+
" spec_enc, mol_enc = model.forward(dataset[i], stage='test')\n",
|
| 671 |
+
"\n",
|
| 672 |
+
"fw = interactive_attention_visualization(spec_enc, mol_enc, peak_mzs, peak_intensities, peak_formulas, mol)\n",
|
| 673 |
+
"fw\n"
|
| 674 |
+
]
|
| 675 |
+
},
|
| 676 |
+
{
|
| 677 |
+
"cell_type": "code",
|
| 678 |
+
"execution_count": null,
|
| 679 |
+
"metadata": {},
|
| 680 |
+
"outputs": [],
|
| 681 |
+
"source": []
|
| 682 |
+
}
|
| 683 |
+
],
|
| 684 |
+
"metadata": {
|
| 685 |
+
"kernelspec": {
|
| 686 |
+
"display_name": "spec",
|
| 687 |
+
"language": "python",
|
| 688 |
+
"name": "python3"
|
| 689 |
+
},
|
| 690 |
+
"language_info": {
|
| 691 |
+
"codemirror_mode": {
|
| 692 |
+
"name": "ipython",
|
| 693 |
+
"version": 3
|
| 694 |
+
},
|
| 695 |
+
"file_extension": ".py",
|
| 696 |
+
"mimetype": "text/x-python",
|
| 697 |
+
"name": "python",
|
| 698 |
+
"nbconvert_exporter": "python",
|
| 699 |
+
"pygments_lexer": "ipython3",
|
| 700 |
+
"version": "3.11.7"
|
| 701 |
+
},
|
| 702 |
+
"orig_nbformat": 4
|
| 703 |
+
},
|
| 704 |
+
"nbformat": 4,
|
| 705 |
+
"nbformat_minor": 2
|
| 706 |
+
}
|
notebooks/hyperparameter_tuning_result.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
notebooks/magma_script.ipynb
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 12,
|
| 6 |
+
"id": "1205f9e4",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import pandas as pd\n",
|
| 11 |
+
"\n",
|
| 12 |
+
"\n",
|
| 13 |
+
"import numpy as np\n",
|
| 14 |
+
"from rdkit import Chem\n",
|
| 15 |
+
"from collections import defaultdict\n",
|
| 16 |
+
"import numpy as np\n",
|
| 17 |
+
"import sys\n",
|
| 18 |
+
"import json"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "code",
|
| 23 |
+
"execution_count": 3,
|
| 24 |
+
"id": "c1267f1b",
|
| 25 |
+
"metadata": {},
|
| 26 |
+
"outputs": [],
|
| 27 |
+
"source": [
|
| 28 |
+
"with open(\"/data/yzhouc01/FILIP-MS/data/magma/MassSpecGymID0191762.json\", 'r') as f:\n",
|
| 29 |
+
" data = json.load(f)"
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"cell_type": "code",
|
| 34 |
+
"execution_count": 7,
|
| 35 |
+
"id": "db06e7e6",
|
| 36 |
+
"metadata": {},
|
| 37 |
+
"outputs": [
|
| 38 |
+
{
|
| 39 |
+
"data": {
|
| 40 |
+
"text/plain": [
|
| 41 |
+
"dict_keys(['mz', 'intensities', 'subformulas', 'substructures'])"
|
| 42 |
+
]
|
| 43 |
+
},
|
| 44 |
+
"execution_count": 7,
|
| 45 |
+
"metadata": {},
|
| 46 |
+
"output_type": "execute_result"
|
| 47 |
+
}
|
| 48 |
+
],
|
| 49 |
+
"source": [
|
| 50 |
+
"data.keys()"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"cell_type": "code",
|
| 55 |
+
"execution_count": 11,
|
| 56 |
+
"id": "582e23d9",
|
| 57 |
+
"metadata": {},
|
| 58 |
+
"outputs": [
|
| 59 |
+
{
|
| 60 |
+
"data": {
|
| 61 |
+
"text/plain": [
|
| 62 |
+
"['C4H8O4']"
|
| 63 |
+
]
|
| 64 |
+
},
|
| 65 |
+
"execution_count": 11,
|
| 66 |
+
"metadata": {},
|
| 67 |
+
"output_type": "execute_result"
|
| 68 |
+
}
|
| 69 |
+
],
|
| 70 |
+
"source": [
|
| 71 |
+
"data['subformulas'][0]"
|
| 72 |
+
]
|
| 73 |
+
},
|
| 74 |
+
{
|
| 75 |
+
"cell_type": "code",
|
| 76 |
+
"execution_count": 14,
|
| 77 |
+
"id": "abe22e21",
|
| 78 |
+
"metadata": {},
|
| 79 |
+
"outputs": [],
|
| 80 |
+
"source": [
|
| 81 |
+
"np.random.seed(42)\n",
|
| 82 |
+
"\n",
|
| 83 |
+
"formulas = []\n",
|
| 84 |
+
"mzs = []\n",
|
| 85 |
+
"intensities = []\n",
|
| 86 |
+
"\n",
|
| 87 |
+
"for f, m, i in zip(data['subformulas'], data['mz'], data['intensities']):\n",
|
| 88 |
+
" if f:\n",
|
| 89 |
+
" formulas.append(np.random.choice(f))\n",
|
| 90 |
+
" mzs.append(m)\n",
|
| 91 |
+
" intensities.append(i)\n",
|
| 92 |
+
" "
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"cell_type": "code",
|
| 97 |
+
"execution_count": 15,
|
| 98 |
+
"id": "161f95f5",
|
| 99 |
+
"metadata": {},
|
| 100 |
+
"outputs": [
|
| 101 |
+
{
|
| 102 |
+
"data": {
|
| 103 |
+
"text/plain": [
|
| 104 |
+
"69"
|
| 105 |
+
]
|
| 106 |
+
},
|
| 107 |
+
"execution_count": 15,
|
| 108 |
+
"metadata": {},
|
| 109 |
+
"output_type": "execute_result"
|
| 110 |
+
}
|
| 111 |
+
],
|
| 112 |
+
"source": [
|
| 113 |
+
"len(data['mz'])"
|
| 114 |
+
]
|
| 115 |
+
},
|
| 116 |
+
{
|
| 117 |
+
"cell_type": "code",
|
| 118 |
+
"execution_count": null,
|
| 119 |
+
"id": "0c622b13",
|
| 120 |
+
"metadata": {},
|
| 121 |
+
"outputs": [],
|
| 122 |
+
"source": []
|
| 123 |
+
}
|
| 124 |
+
],
|
| 125 |
+
"metadata": {
|
| 126 |
+
"kernelspec": {
|
| 127 |
+
"display_name": "spec",
|
| 128 |
+
"language": "python",
|
| 129 |
+
"name": "python3"
|
| 130 |
+
},
|
| 131 |
+
"language_info": {
|
| 132 |
+
"codemirror_mode": {
|
| 133 |
+
"name": "ipython",
|
| 134 |
+
"version": 3
|
| 135 |
+
},
|
| 136 |
+
"file_extension": ".py",
|
| 137 |
+
"mimetype": "text/x-python",
|
| 138 |
+
"name": "python",
|
| 139 |
+
"nbconvert_exporter": "python",
|
| 140 |
+
"pygments_lexer": "ipython3",
|
| 141 |
+
"version": "3.11.7"
|
| 142 |
+
}
|
| 143 |
+
},
|
| 144 |
+
"nbformat": 4,
|
| 145 |
+
"nbformat_minor": 5
|
| 146 |
+
}
|
notebooks/peak_embedding_UMAP.ipynb
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
notebooks/peak_formula_analysis.ipynb
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "07d00685",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [],
|
| 9 |
+
"source": [
|
| 10 |
+
"import torch\n",
|
| 11 |
+
"import numpy as np\n",
|
| 12 |
+
"import plotly.graph_objects as go\n",
|
| 13 |
+
"from plotly.subplots import make_subplots\n",
|
| 14 |
+
"from rdkit import Chem\n",
|
| 15 |
+
"from rdkit.Chem import rdDepictor\n",
|
| 16 |
+
"from rdkit.Chem.Draw import rdMolDraw2D\n",
|
| 17 |
+
"import matplotlib.pyplot as plt\n",
|
| 18 |
+
"import json"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"cell_type": "code",
|
| 23 |
+
"execution_count": 4,
|
| 24 |
+
"id": "cd9e10c7",
|
| 25 |
+
"metadata": {},
|
| 26 |
+
"outputs": [],
|
| 27 |
+
"source": [
|
| 28 |
+
"import sys\n",
|
| 29 |
+
"sys.path.insert(0, \"/data/yzhouc01/MassSpecGym\")\n",
|
| 30 |
+
"sys.path.insert(0, \"/data/yzhouc01/FILIP-MS\")\n",
|
| 31 |
+
"\n",
|
| 32 |
+
"from rdkit import RDLogger\n",
|
| 33 |
+
"import pytorch_lightning as pl\n",
|
| 34 |
+
"from pytorch_lightning import Trainer\n",
|
| 35 |
+
"from massspecgym.models.base import Stage\n",
|
| 36 |
+
"import os\n",
|
| 37 |
+
"\n",
|
| 38 |
+
"from mvp.utils.data import get_spec_featurizer, get_mol_featurizer, get_ms_dataset,get_test_ms_dataset\n",
|
| 39 |
+
"from mvp.utils.models import get_model\n",
|
| 40 |
+
"\n",
|
| 41 |
+
"from mvp.definitions import TEST_RESULTS_DIR\n",
|
| 42 |
+
"import yaml\n",
|
| 43 |
+
"from functools import partial\n",
|
| 44 |
+
"# Suppress RDKit warnings and errors\n",
|
| 45 |
+
"lg = RDLogger.logger()\n",
|
| 46 |
+
"lg.setLevel(RDLogger.CRITICAL)"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
{
|
| 50 |
+
"cell_type": "code",
|
| 51 |
+
"execution_count": 5,
|
| 52 |
+
"id": "9ba93f86",
|
| 53 |
+
"metadata": {},
|
| 54 |
+
"outputs": [
|
| 55 |
+
{
|
| 56 |
+
"name": "stdout",
|
| 57 |
+
"output_type": "stream",
|
| 58 |
+
"text": [
|
| 59 |
+
"Data path: /data/yzhouc01/MVP/data/sample/data.tsv\n",
|
| 60 |
+
"Processing formula spectra\n"
|
| 61 |
+
]
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"name": "stderr",
|
| 65 |
+
"output_type": "stream",
|
| 66 |
+
"text": [
|
| 67 |
+
"100%|██████████| 10/10 [00:00<00:00, 5861.24it/s]"
|
| 68 |
+
]
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"name": "stderr",
|
| 72 |
+
"output_type": "stream",
|
| 73 |
+
"text": [
|
| 74 |
+
"\n"
|
| 75 |
+
]
|
| 76 |
+
}
|
| 77 |
+
],
|
| 78 |
+
"source": [
|
| 79 |
+
"# Load model and data\n",
|
| 80 |
+
"# param_pth = '/data/yzhouc01/FILIP-MS/experiments/20250824_filipContrastive/lightning_logs/version_0/hparams.yaml'\n",
|
| 81 |
+
"param_pth = \"/data/yzhouc01/FILIP-MS/mvp/params_formSpec.yaml\"\n",
|
| 82 |
+
"with open(param_pth) as f:\n",
|
| 83 |
+
" params = yaml.load(f, Loader=yaml.FullLoader)\n",
|
| 84 |
+
"params['dataset_pth'] = \"/data/yzhouc01/MVP/data/sample/data.tsv\"\n",
|
| 85 |
+
"\n",
|
| 86 |
+
"spec_featurizer = get_spec_featurizer(params['spectra_view'], params)\n",
|
| 87 |
+
"mol_featurizer = get_mol_featurizer(params['molecule_view'], params)\n",
|
| 88 |
+
"dataset = get_test_ms_dataset(params['spectra_view'], params['molecule_view'], spec_featurizer, mol_featurizer, params)"
|
| 89 |
+
]
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"cell_type": "code",
|
| 93 |
+
"execution_count": 7,
|
| 94 |
+
"id": "bcb28630",
|
| 95 |
+
"metadata": {},
|
| 96 |
+
"outputs": [
|
| 97 |
+
{
|
| 98 |
+
"data": {
|
| 99 |
+
"text/plain": [
|
| 100 |
+
"{'precursor_mz': 404.1241,\n",
|
| 101 |
+
" 'formulas': array(['C3HO2', 'C4H2N2', 'C6H4O', 'C4H2N2O', 'C7H4N', 'C7H4O', 'C8H5',\n",
|
| 102 |
+
" 'C4H2N2O2', 'C5H7O3', 'C7H4NO', 'C8H5O', 'C9H5O', 'C9H8O',\n",
|
| 103 |
+
" 'C10H8O', 'C9H5O2', 'C9H8O2', 'C10H5NO', 'C10H8O2', 'C10H6N2O',\n",
|
| 104 |
+
" 'C9H5O4', 'C11H6N2O', 'C10H6N2O2', 'C12H7N2O', 'C11H6N2O2',\n",
|
| 105 |
+
" 'C12H9O3', 'C11H8O4', 'C11H8NO3', 'C11H11O4', 'C11H6N3O2',\n",
|
| 106 |
+
" 'C12H11O4', 'C13H10N2O3', 'C14H10N2O4', 'C13H7N2O5', 'C15H13N2O5',\n",
|
| 107 |
+
" 'C19H11N3O3', 'C22H17N3O5'], dtype='<U10'),\n",
|
| 108 |
+
" 'precursor_formula': 'C22H18N3O5'}"
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
"execution_count": 7,
|
| 112 |
+
"metadata": {},
|
| 113 |
+
"output_type": "execute_result"
|
| 114 |
+
}
|
| 115 |
+
],
|
| 116 |
+
"source": [
|
| 117 |
+
"dataset.spectra[1].metadata"
|
| 118 |
+
]
|
| 119 |
+
},
|
| 120 |
+
{
|
| 121 |
+
"cell_type": "code",
|
| 122 |
+
"execution_count": 4,
|
| 123 |
+
"id": "fbebdab3",
|
| 124 |
+
"metadata": {},
|
| 125 |
+
"outputs": [
|
| 126 |
+
{
|
| 127 |
+
"data": {
|
| 128 |
+
"text/plain": [
|
| 129 |
+
"{'precursor_mz': 226.0716,\n",
|
| 130 |
+
" 'formulas': array(['C5H5O2', 'C6H6O', 'C3H4NO3', 'C7H6O', 'C6H3O2', 'C3H5NO4',\n",
|
| 131 |
+
" 'C7H6O2', 'C7H6NO2', 'C8H4NO2', 'C7H6NO3', 'C8H9NO3', 'C7H6NO5',\n",
|
| 132 |
+
" 'C9H8NO4', 'C10H10NO4', 'C10H11NO5'], dtype='<U9'),\n",
|
| 133 |
+
" 'precursor_formula': 'C10H12NO5'}"
|
| 134 |
+
]
|
| 135 |
+
},
|
| 136 |
+
"execution_count": 4,
|
| 137 |
+
"metadata": {},
|
| 138 |
+
"output_type": "execute_result"
|
| 139 |
+
}
|
| 140 |
+
],
|
| 141 |
+
"source": [
|
| 142 |
+
"dataset.spectra[0].metadata"
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"cell_type": "code",
|
| 147 |
+
"execution_count": 9,
|
| 148 |
+
"id": "268c6470",
|
| 149 |
+
"metadata": {},
|
| 150 |
+
"outputs": [
|
| 151 |
+
{
|
| 152 |
+
"data": {
|
| 153 |
+
"text/plain": [
|
| 154 |
+
"{'precursor_mz': 226.0716,\n",
|
| 155 |
+
" 'formulas': array(['C6H6O', 'C4H3O3', 'C5H5O2', 'C6H6O', 'C4H3O3', 'C5H5O2',\n",
|
| 156 |
+
" 'C3H4NO3', 'C7H6O', 'C7H6O', 'C6H3O2', 'C6H3O2', 'C3H5NO4',\n",
|
| 157 |
+
" 'C7H6O2', 'C7H6O2', 'C7H6O2', 'C7H6O2', 'C7H6NO2', 'C7H6NO2',\n",
|
| 158 |
+
" 'C7H6NO2', 'C8H4NO2', 'C8H4NO2', 'C7H6NO3', 'C8H9NO3', 'C8H9NO3',\n",
|
| 159 |
+
" 'C8H9NO3', 'C8H9NO3', 'C8H10NO4', 'C7H9NO5', 'C7H6NO5', 'C9H8NO4',\n",
|
| 160 |
+
" 'C10H10NO4', 'C9H8NO5', 'C10H11NO5'], dtype='<U9'),\n",
|
| 161 |
+
" 'precursor_formula': 'C10H12NO5'}"
|
| 162 |
+
]
|
| 163 |
+
},
|
| 164 |
+
"execution_count": 9,
|
| 165 |
+
"metadata": {},
|
| 166 |
+
"output_type": "execute_result"
|
| 167 |
+
}
|
| 168 |
+
],
|
| 169 |
+
"source": [
|
| 170 |
+
"dataset.spectra[0].metadata"
|
| 171 |
+
]
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"cell_type": "markdown",
|
| 175 |
+
"id": "4a9f0227",
|
| 176 |
+
"metadata": {},
|
| 177 |
+
"source": [
|
| 178 |
+
"# SIRIUS subformulas"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
{
|
| 182 |
+
"cell_type": "code",
|
| 183 |
+
"execution_count": 4,
|
| 184 |
+
"id": "5f40603f",
|
| 185 |
+
"metadata": {},
|
| 186 |
+
"outputs": [
|
| 187 |
+
{
|
| 188 |
+
"data": {
|
| 189 |
+
"image/png": "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P8eiRYvQokULXL58WdrO1atX0bhxY5QtWxYTJkxAyZIlsXnzZnh7e+PPP//El19+CeDVTTpNmzbFtWvX0K9fP9StWxcPHz7Ezp07ce/evUL5jH2TYcOGwdbWFtOnT8epU6ewcuVKWFhY4OTJkyhfvjxmzZqFPXv2YN68eahZsyZ69+791vZOnjyJmjVrvnEUwsnJCe7u7tiwYQM+//xzAMDevXuRnJyMbt265enzy9HREZmZmfj999/h4+OT/51+B3l9b2jo+hh/PZbu3bujZcuWmDNnDgDg2rVrOHHiBEaMGKFVt169etk+S3MlqMBWr14tAIizZ8++sY65ubn45JNPpOf+/v4ia7cvXLhQABAPHjx4Yxtnz54VAMTq1auzLfPw8BAARFBQUI7LPDw8pOeHDx8WAETZsmVFSkqKVL5582YBQCxatEgqc3R0FD4+Prm2+bbYfHx8hKOjo/R8x44dAoD4/vvvtep17txZKBQKcfPmTakMgDAyMtIqu3jxogAglixZkm1bWQUGBgoA4o8//pDK0tPThbu7uzA1NdXad0dHR9GuXbu3tpdT3b59+wpjY2MRExMjhPhf327ZsuWN+6/x+jGg2V+lUimioqKkshUrVggAwtbWVivmiRMnCgBadTXHwfz586WyFy9eiDp16ghra2uRnp4uhBDi999/F3p6euI///mP1vaDgoIEAHHixAmtmPT09MTVq1dz7ZuEhARhZGQkWrduLTIzM6XypUuXCgDit99+y7b/bzvmX6/7+kPTr+np6cLa2lrUrFlTPHv2TFpv165dAoCYNm2aVObj4yMAiAkTJmTbzrv2f17fL1FRUdneL0+fPs223oYNGwQAcezYManM3Nxc+Pr65tRNb6Xpwy+++EKrfOjQoQKAuHjxohBCiDt37gh9fX3xww8/aNW7fPmyMDAw0Cp/2+dOTn799VfpPd28eXMxdepU8Z///EfrWBHif/1jYmIi7t27J5WfPn1aABCjRo2Sylq2bClq1aolnj9/LpWp1WrRqFEjUalSJals2rRpAoDYtm1btrjUarUQouCfsQCEv79/tvLXjwfNd4WXl5e0TSGEcHd3FwqFQgwePFgqy8jIEOXKldM6bt6kXLlyolOnTtnKs343LV26VJiZmUnH2ddffy2aN28uxfn65x8AreMsLi5OWFlZCQCiatWqYvDgwWL9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cmXExMRIF+3Xr18f/v7+aNmyJVxcXNCtWzdkZGRgz549GD9+PIBXdz8eO3YM3bp1g1KplJK4nLRo0QKjR4/G7t274eLiggULFiApKSnP+0skB54ZIypCZsyYkW1YpFq1avj555+xbNkyuLq64syZMzneaVhQP/74I3788Ue4urri+PHj2Llzp/RlpzmblZmZidatW6NWrVoYOXIkLCwstK5Py4vhw4dj9OjR+O6771CrVi2EhIRg586dqFSpkk72oVOnTujVqxfq1q2LmzdvYt++fShVqtQ7t/260qVL448//sCePXtQq1YtbNiwIcdpOLL69ddf8fjxY9StWxe9evWSpuHQsLCwwKpVq9C4cWPUrl0bBw8exN9///3GudYAoGLFivjqq6/Qtm1btG7dGrVr19aaumLAgAH45ZdfsHr1atSqVQseHh4IDg7O95mxvMT23Xff4dy5c/jkk0/w/fffY8GCBdKdmQqFAnv27MFnn32Gvn37onLlyujWrRvu3r0rXZfVrFkzbNmyBTt37kSdOnXQokULnDlzRmp/xowZuHPnDlxcXGBlZfXWePv16wcfHx/07t0bHh4eqFChApo3b56vfSZ63xTi9cF1IiIq0gICArBjx44i8VNQTk5OGDlyZLa5vYgo73hmjIiIiEhGTMaIiIiIZMRhSiIiIiIZ8cwYERERkYyYjBERERHJiMkYERERkYyYjBERERHJiMkYERERkYyYjBERERHJiMkYERERkYyYjBERERHJiMkYERERkYz+D9gLCpMQmN2EAAAAAElFTkSuQmCC",
|
| 190 |
+
"text/plain": [
|
| 191 |
+
"<Figure size 640x480 with 1 Axes>"
|
| 192 |
+
]
|
| 193 |
+
},
|
| 194 |
+
"metadata": {},
|
| 195 |
+
"output_type": "display_data"
|
| 196 |
+
}
|
| 197 |
+
],
|
| 198 |
+
"source": [
|
| 199 |
+
"import numpy as np\n",
|
| 200 |
+
"\n",
|
| 201 |
+
"import matplotlib.pyplot as plt\n",
|
| 202 |
+
"\n",
|
| 203 |
+
"# Collect number of formulas per spectrum\n",
|
| 204 |
+
"n_formulas = [len(d.metadata['formulas']) for d in dataset.spectra]\n",
|
| 205 |
+
"\n",
|
| 206 |
+
"# Calculate mean and median\n",
|
| 207 |
+
"mean_n_formulas = np.mean(n_formulas)\n",
|
| 208 |
+
"median_n_formulas = np.median(n_formulas)\n",
|
| 209 |
+
"\n",
|
| 210 |
+
"# Plot histogram\n",
|
| 211 |
+
"plt.hist(n_formulas, bins=30, alpha=0.7, color='skyblue')\n",
|
| 212 |
+
"plt.axvline(mean_n_formulas, color='red', linestyle='dashed', linewidth=2, label=f'Mean: {mean_n_formulas:.2f}')\n",
|
| 213 |
+
"plt.axvline(median_n_formulas, color='green', linestyle='dashed', linewidth=2, label=f'Median: {median_n_formulas:.2f}')\n",
|
| 214 |
+
"plt.xlabel('Number of formulas per spectrum')\n",
|
| 215 |
+
"plt.ylabel('Count')\n",
|
| 216 |
+
"plt.title('Distribution of Number of Formulas per Spectrum (MIST labels)')\n",
|
| 217 |
+
"plt.legend()\n",
|
| 218 |
+
"plt.show()"
|
| 219 |
+
]
|
| 220 |
+
},
|
| 221 |
+
{
|
| 222 |
+
"cell_type": "markdown",
|
| 223 |
+
"id": "170af068",
|
| 224 |
+
"metadata": {},
|
| 225 |
+
"source": [
|
| 226 |
+
"# MIST subformulas"
|
| 227 |
+
]
|
| 228 |
+
},
|
| 229 |
+
{
|
| 230 |
+
"cell_type": "markdown",
|
| 231 |
+
"id": "4798447e",
|
| 232 |
+
"metadata": {},
|
| 233 |
+
"source": []
|
| 234 |
+
},
|
| 235 |
+
{
|
| 236 |
+
"cell_type": "code",
|
| 237 |
+
"execution_count": 17,
|
| 238 |
+
"id": "5612e3dc",
|
| 239 |
+
"metadata": {},
|
| 240 |
+
"outputs": [
|
| 241 |
+
{
|
| 242 |
+
"data": {
|
| 243 |
+
"image/png": "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SPd0ekUrPT0dkZGRiIyMBPBsAHxkZCRiYmKgUCgwZswYfPXVV9i9ezcuX76Mfv36wcXFRboz0cfHB+3atcPQoUNx5swZnDx5EsHBwejZsydcXFwAAB999BFMTEwwePBgXL16FVu2bMHixYsREhIixTF69Gjs378f8+fPx/Xr1zFt2jScO3cOwcHBr7pJiIiIqBzR6RWtc+fO4e2335aeq5Kf/v37Y926dfj888/x+PFjDBs2DMnJyWjevDn2798PU1NTaZsNGzYgODgYbdq0gYGBAbp164YlS5ZI662trXHw4EEEBQXBz88PdnZ2mDJlitpcW02bNsXGjRvx5Zdf4osvvkD16tWxa9cu1KlT5xW0AhEREZVXZWYeLX3HebReDy+bR6skXmnXIefRIiIdKmvDLF7bebSIiIiI9F2ZHQxPVBZ90nQVsvOyYGxgUnThsmr9eiAzE1AqdR0JEVG5x0SLSAO1nVroOoSSa9VK1xEQEb022HVIREREJBMmWkREREQyYdchkQauJvwpjdHS227Eo0f/N0aL3YhERLJiokWkgaV/fSzL9A6vVJ8+nN6BiOgVYdchERERkUyYaBERERHJhIkWERERkUyYaBERERHJhIkWERERkUyYaBERERHJhIkWERERkUyYaBERERHJhIkWERERkUw4MzyRBuSaDX7brRStt+3uZa3ZBpwNnojoleEVLSIiIiKZMNEiIiIikgkTLSIiIiKZcIwWkQa2XZqNjKxUmJtYoXu9CboORzvTpwMpKYC1NTB1qq6jISIq15hoEWkg7ObPeJgRB1tzF/1NtNasAWJjAVdXJlpERDJj1yERERGRTJhoEREREcmEiRYRERGRTJhoEREREcmEiRYRERGRTJhoEREREcmE0zsQ6TlNfyexY46AOYCM//+XiIjkwytaRERERDLhFS0iDdRyaIa0zAewVFbSdShau9ekGZQPHyDTthI8dB0MEVE5x0SLSAOjmq/RdQgldmbB/+rARIuISF7sOiQiIiKSCRMtIiIiIpkw0SIiIiKSCcdoEWlgemgnpDy9B2tTe0x953ddh6OVln06QXn/HjLt7IHw47oOh4ioXGOiRaSB+LRbeJgRh4zsVF2HojWL6FswT4xDRpr+1oGISF+w65CIiIhIJky0iIiIiGTCRIuIiIhIJky0iIiIiGTCRIuIiIhIJky0iIiI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xN7eHmlpaXj8+LG0rKjX4+TJkxg1ahQ6dOiA2rVrQ6lUqt1QADxLGjp16oQlS5bg6NGjCA8Px+XLlwvdZ0xMDOLi4qTnp06dgoGBAWrWrAlHR0e4uLjg33//zfe6eXp6alznomJ7/hxSPX/+HEpISICRkVG+WOzs7AA8u7pU2HQewLMrV7m5ucWK1d7eXm0cV25uLq5cuVLsuhLpAq9oEb0CdevWRe/evbFkyRK15a1atcK9e/cwd+5cfPDBB9i/fz/27dsHKyurUjnu8uXLUb16dfj4+GDhwoV49OgRBg0aBAAICgrCmjVr0KtXL3z++eewtbXFzZs3sXnzZnz//ff5ro68zLhx4zB16lR4eXmhfv36WLt2LSIjIwu8000TFSpUwIgRI6S7KytXroy5c+ciIyMDgwcPLtG+C9KkSROYm5vjiy++wKhRo3D69GmsW7fupdtUr14dv/zyCxo1aoTU1FSMGzdO7SrOunXrkJubK+17/fr1MDMzg4eHR6H7NDU1Rf/+/fHtt98iNTUVo0aNQo8ePeDk5AQAmD59OkaNGgVra2u0a9cOmZmZOHfuHB49eoSQkJBi17c4sZ08eRJz585F165dERoaim3btmHv3r0Ant1Z6+/vj65du2Lu3LmoUaMG4uLipAHwjRo1wtSpU9GmTRt4eXmhZ8+eyMnJwR9//IHx48cDeHaX4PHjx9GzZ08olUopQStI69atERISgr1798LLywsLFixAcnJysetLpAu8okX0isyYMSNfV4WPjw++++47LF++HL6+vjhz5kyBd+Rpa/bs2Zg9ezZ8fX1x4sQJ7N69W/oiU12Fys3NRdu2bVG3bl2MGTMGNjY2auPBimPUqFEICQnBp59+irp162L//v3YvXs3qlevXip16NatG/r27YuGDRvi5s2bOHDgACpWrFjifb/I1tYW69evxx9//IG6deti06ZNBU5F8bwffvgBjx49QsOGDdG3b19pKgoVGxsbrFmzBs2aNUO9evVw6NAh/P7774XOJQYA1apVw/vvv48OHTqgbdu2qFevntr0DUOGDMH333+PtWvXom7dumjZsiXWrVun8RWt4sT26aef4ty5c2jQoAG++uorLFiwQLqDUaFQ4I8//sBbb72FgQMHokaNGujZsyfu3LkjjYNq1aoVtm3bht27d6N+/fpo3bo1zpw5I+1/xowZuH37Nry8vGBvb//SeAcNGoT+/fujX79+aNmyJapWrYq3335bozoTvWoK8WKHNxER6cy0adOwa9euMvHzQ1WqVMGYMWPyzV1FRMXHK1pEREREMmGiRURERCQTdh0SERERyYRXtIiIiIhkwkSLiIiISCZMtIiIiIhkwkSLiIiISCZMtIiIiIhkwkSLiIiISCZMtIiIiIhkwkSLiIiISCZMtIiIiIhk8n+fG+5WhERGtAAAAABJRU5ErkJggg==",
|
| 244 |
+
"text/plain": [
|
| 245 |
+
"<Figure size 640x480 with 1 Axes>"
|
| 246 |
+
]
|
| 247 |
+
},
|
| 248 |
+
"metadata": {},
|
| 249 |
+
"output_type": "display_data"
|
| 250 |
+
}
|
| 251 |
+
],
|
| 252 |
+
"source": [
|
| 253 |
+
"import numpy as np\n",
|
| 254 |
+
"\n",
|
| 255 |
+
"import matplotlib.pyplot as plt\n",
|
| 256 |
+
"\n",
|
| 257 |
+
"# Collect number of formulas per spectrum\n",
|
| 258 |
+
"n_formulas = [len(d.metadata['formulas']) for d in dataset.spectra]\n",
|
| 259 |
+
"\n",
|
| 260 |
+
"# Calculate mean and median\n",
|
| 261 |
+
"mean_n_formulas = np.mean(n_formulas)\n",
|
| 262 |
+
"median_n_formulas = np.median(n_formulas)\n",
|
| 263 |
+
"\n",
|
| 264 |
+
"# Plot histogram\n",
|
| 265 |
+
"plt.hist(n_formulas, bins=30, alpha=0.7, color='skyblue')\n",
|
| 266 |
+
"plt.axvline(mean_n_formulas, color='red', linestyle='dashed', linewidth=2, label=f'Mean: {mean_n_formulas:.2f}')\n",
|
| 267 |
+
"plt.axvline(median_n_formulas, color='green', linestyle='dashed', linewidth=2, label=f'Median: {median_n_formulas:.2f}')\n",
|
| 268 |
+
"plt.xlabel('Number of formulas per spectrum')\n",
|
| 269 |
+
"plt.ylabel('Count')\n",
|
| 270 |
+
"plt.title('Distribution of Number of Formulas per Spectrum (MIST labels)')\n",
|
| 271 |
+
"plt.legend()\n",
|
| 272 |
+
"plt.show()"
|
| 273 |
+
]
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"cell_type": "code",
|
| 277 |
+
"execution_count": 7,
|
| 278 |
+
"id": "b35bb5a4",
|
| 279 |
+
"metadata": {},
|
| 280 |
+
"outputs": [
|
| 281 |
+
{
|
| 282 |
+
"data": {
|
| 283 |
+
"text/plain": [
|
| 284 |
+
"identifier MassSpecGymID0000001\n",
|
| 285 |
+
"mzs 91.0542,125.0233,154.0499,155.0577,185.0961,20...\n",
|
| 286 |
+
"intensities 0.24524524524524524,1.0,0.08008008008008008,0....\n",
|
| 287 |
+
"smiles CC(=O)N[C@@H](CC1=CC=CC=C1)C2=CC(=CC(=O)O2)OC\n",
|
| 288 |
+
"inchikey VFMQMACUYWGDOJ\n",
|
| 289 |
+
"formula C16H17NO4\n",
|
| 290 |
+
"precursor_formula C16H18NO4\n",
|
| 291 |
+
"parent_mass 287.115224\n",
|
| 292 |
+
"precursor_mz 288.1225\n",
|
| 293 |
+
"adduct [M+H]+\n",
|
| 294 |
+
"instrument_type Orbitrap\n",
|
| 295 |
+
"collision_energy 30.0\n",
|
| 296 |
+
"fold train\n",
|
| 297 |
+
"simulation_challenge True\n",
|
| 298 |
+
"formulas [C16H17NO4]\n",
|
| 299 |
+
"formula_mzs [288.1225]\n",
|
| 300 |
+
"formula_intensities [1.0]\n",
|
| 301 |
+
"Name: 0, dtype: object"
|
| 302 |
+
]
|
| 303 |
+
},
|
| 304 |
+
"execution_count": 7,
|
| 305 |
+
"metadata": {},
|
| 306 |
+
"output_type": "execute_result"
|
| 307 |
+
}
|
| 308 |
+
],
|
| 309 |
+
"source": [
|
| 310 |
+
"dataset.metadata.iloc[0]"
|
| 311 |
+
]
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"cell_type": "code",
|
| 315 |
+
"execution_count": 9,
|
| 316 |
+
"id": "da07f08a",
|
| 317 |
+
"metadata": {},
|
| 318 |
+
"outputs": [
|
| 319 |
+
{
|
| 320 |
+
"ename": "PermissionError",
|
| 321 |
+
"evalue": "[Errno 13] Permission denied: '/r/hassounlab/msgym_sirius/MassSpecGymID0000140.json'",
|
| 322 |
+
"output_type": "error",
|
| 323 |
+
"traceback": [
|
| 324 |
+
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
|
| 325 |
+
"\u001b[0;31mPermissionError\u001b[0m Traceback (most recent call last)",
|
| 326 |
+
"Cell \u001b[0;32mIn[9], line 4\u001b[0m\n\u001b[1;32m 2\u001b[0m spec_id \u001b[38;5;241m=\u001b[39m dataset\u001b[38;5;241m.\u001b[39mmetadata\u001b[38;5;241m.\u001b[39miloc[\u001b[38;5;241m123\u001b[39m][\u001b[38;5;124m'\u001b[39m\u001b[38;5;124midentifier\u001b[39m\u001b[38;5;124m'\u001b[39m]\n\u001b[1;32m 3\u001b[0m file \u001b[38;5;241m=\u001b[39m os\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(params[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124msubformula_dir_pth\u001b[39m\u001b[38;5;124m'\u001b[39m], spec_id\u001b[38;5;241m+\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m.json\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[38;5;28;43mopen\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m)\u001b[49m \u001b[38;5;28;01mas\u001b[39;00m f:\n\u001b[1;32m 5\u001b[0m data \u001b[38;5;241m=\u001b[39m json\u001b[38;5;241m.\u001b[39mload(f)\n",
|
| 327 |
+
"File \u001b[0;32m/data/yzc-conda/spec/lib/python3.11/site-packages/IPython/core/interactiveshell.py:324\u001b[0m, in \u001b[0;36m_modified_open\u001b[0;34m(file, *args, **kwargs)\u001b[0m\n\u001b[1;32m 317\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m file \u001b[38;5;129;01min\u001b[39;00m {\u001b[38;5;241m0\u001b[39m, \u001b[38;5;241m1\u001b[39m, \u001b[38;5;241m2\u001b[39m}:\n\u001b[1;32m 318\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\n\u001b[1;32m 319\u001b[0m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mIPython won\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mt let you open fd=\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mfile\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m by default \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 320\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mas it is likely to crash IPython. If you know what you are doing, \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 321\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124myou can use builtins\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;124m open.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 322\u001b[0m )\n\u001b[0;32m--> 324\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mio_open\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfile\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[38;5;241;43m*\u001b[39;49m\u001b[43mkwargs\u001b[49m\u001b[43m)\u001b[49m\n",
|
| 328 |
+
"\u001b[0;31mPermissionError\u001b[0m: [Errno 13] Permission denied: '/r/hassounlab/msgym_sirius/MassSpecGymID0000140.json'"
|
| 329 |
+
]
|
| 330 |
+
}
|
| 331 |
+
],
|
| 332 |
+
"source": [
|
| 333 |
+
"import json\n",
|
| 334 |
+
"spec_id = dataset.metadata.iloc[123]['identifier']\n",
|
| 335 |
+
"file = os.path.join(params['subformula_dir_pth'], spec_id+\".json\")\n",
|
| 336 |
+
"with open(file) as f:\n",
|
| 337 |
+
" data = json.load(f)"
|
| 338 |
+
]
|
| 339 |
+
},
|
| 340 |
+
{
|
| 341 |
+
"cell_type": "code",
|
| 342 |
+
"execution_count": null,
|
| 343 |
+
"id": "a1341478",
|
| 344 |
+
"metadata": {},
|
| 345 |
+
"outputs": [],
|
| 346 |
+
"source": []
|
| 347 |
+
}
|
| 348 |
+
],
|
| 349 |
+
"metadata": {
|
| 350 |
+
"kernelspec": {
|
| 351 |
+
"display_name": "spec",
|
| 352 |
+
"language": "python",
|
| 353 |
+
"name": "python3"
|
| 354 |
+
},
|
| 355 |
+
"language_info": {
|
| 356 |
+
"codemirror_mode": {
|
| 357 |
+
"name": "ipython",
|
| 358 |
+
"version": 3
|
| 359 |
+
},
|
| 360 |
+
"file_extension": ".py",
|
| 361 |
+
"mimetype": "text/x-python",
|
| 362 |
+
"name": "python",
|
| 363 |
+
"nbconvert_exporter": "python",
|
| 364 |
+
"pygments_lexer": "ipython3",
|
| 365 |
+
"version": "3.11.7"
|
| 366 |
+
}
|
| 367 |
+
},
|
| 368 |
+
"nbformat": 4,
|
| 369 |
+
"nbformat_minor": 5
|
| 370 |
+
}
|
notebooks/visualization.ipynb
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 2,
|
| 6 |
+
"metadata": {},
|
| 7 |
+
"outputs": [
|
| 8 |
+
{
|
| 9 |
+
"data": {
|
| 10 |
+
"image/svg+xml": [
|
| 11 |
+
"<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:rdkit=\"http://www.rdkit.org/xml\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" baseProfile=\"full\" xml:space=\"preserve\" width=\"400px\" height=\"400px\" viewBox=\"0 0 400 400\">\n",
|
| 12 |
+
"<!-- END OF HEADER -->\n",
|
| 13 |
+
"<rect style=\"opacity:1.0;fill:#FFFFFF;stroke:none\" width=\"400.0\" height=\"400.0\" x=\"0.0\" y=\"0.0\"> </rect>\n",
|
| 14 |
+
"<ellipse cx=\"38.2\" cy=\"220.1\" rx=\"18.2\" ry=\"18.2\" class=\"atom-0\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 15 |
+
"<ellipse cx=\"65.3\" cy=\"133.0\" rx=\"18.2\" ry=\"18.2\" class=\"atom-1\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 16 |
+
"<ellipse cx=\"154.2\" cy=\"113.0\" rx=\"18.2\" ry=\"18.2\" class=\"atom-2\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 17 |
+
"<ellipse cx=\"216.1\" cy=\"179.9\" rx=\"18.2\" ry=\"18.2\" class=\"atom-3\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 18 |
+
"<ellipse cx=\"189.0\" cy=\"267.0\" rx=\"18.2\" ry=\"18.2\" class=\"atom-4\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 19 |
+
"<ellipse cx=\"100.1\" cy=\"287.0\" rx=\"18.2\" ry=\"18.2\" class=\"atom-5\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 20 |
+
"<ellipse cx=\"305.0\" cy=\"159.9\" rx=\"18.2\" ry=\"18.2\" class=\"atom-6\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 21 |
+
"<ellipse cx=\"361.8\" cy=\"231.7\" rx=\"18.2\" ry=\"18.7\" class=\"atom-7\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 22 |
+
"<path class=\"bond-0 atom-0 atom-1\" d=\"M 38.2,220.1 L 65.3,133.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 23 |
+
"<path class=\"bond-0 atom-0 atom-1\" d=\"M 53.6,216.6 L 76.0,144.6\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 24 |
+
"<path class=\"bond-1 atom-1 atom-2\" d=\"M 65.3,133.0 L 154.2,113.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 25 |
+
"<path class=\"bond-2 atom-2 atom-3\" d=\"M 154.2,113.0 L 216.1,179.9\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 26 |
+
"<path class=\"bond-2 atom-2 atom-3\" d=\"M 149.5,128.0 L 200.7,183.4\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 27 |
+
"<path class=\"bond-3 atom-3 atom-4\" d=\"M 216.1,179.9 L 189.0,267.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 28 |
+
"<path class=\"bond-4 atom-4 atom-5\" d=\"M 189.0,267.0 L 100.1,287.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 29 |
+
"<path class=\"bond-4 atom-4 atom-5\" d=\"M 178.3,255.4 L 104.7,272.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 30 |
+
"<path class=\"bond-5 atom-3 atom-6\" d=\"M 216.1,179.9 L 305.0,159.9\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 31 |
+
"<path class=\"bond-6 atom-6 atom-7\" d=\"M 305.0,159.9 L 328.5,185.4\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 32 |
+
"<path class=\"bond-6 atom-6 atom-7\" d=\"M 328.5,185.4 L 352.0,210.8\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 33 |
+
"<path class=\"bond-6 atom-6 atom-7\" d=\"M 300.3,175.0 L 318.4,194.6\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 34 |
+
"<path class=\"bond-6 atom-6 atom-7\" d=\"M 318.4,194.6 L 341.9,220.1\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 35 |
+
"<path class=\"bond-7 atom-5 atom-0\" d=\"M 100.1,287.0 L 38.2,220.1\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
|
| 36 |
+
"<path d=\"M 39.6,215.7 L 38.2,220.1 L 41.3,223.4\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 37 |
+
"<path d=\"M 64.0,137.4 L 65.3,133.0 L 69.8,132.0\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 38 |
+
"<path d=\"M 149.8,114.0 L 154.2,113.0 L 157.3,116.3\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 39 |
+
"<path d=\"M 190.3,262.6 L 189.0,267.0 L 184.5,268.0\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 40 |
+
"<path d=\"M 104.5,286.0 L 100.1,287.0 L 97.0,283.7\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 41 |
+
"<path d=\"M 300.5,160.9 L 305.0,159.9 L 306.1,161.2\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
|
| 42 |
+
"<path class=\"atom-7\" d=\"M 349.9 231.6 Q 349.9 225.4, 353.0 221.9 Q 356.0 218.5, 361.8 218.5 Q 367.5 218.5, 370.6 221.9 Q 373.6 225.4, 373.6 231.6 Q 373.6 237.9, 370.5 241.4 Q 367.4 245.0, 361.8 245.0 Q 356.1 245.0, 353.0 241.4 Q 349.9 237.9, 349.9 231.6 M 361.8 242.0 Q 365.7 242.0, 367.8 239.4 Q 370.0 236.8, 370.0 231.6 Q 370.0 226.5, 367.8 224.0 Q 365.7 221.4, 361.8 221.4 Q 357.8 221.4, 355.7 223.9 Q 353.6 226.5, 353.6 231.6 Q 353.6 236.8, 355.7 239.4 Q 357.8 242.0, 361.8 242.0 \" fill=\"#FF0000\"/>\n",
|
| 43 |
+
"</svg>"
|
| 44 |
+
],
|
| 45 |
+
"text/plain": [
|
| 46 |
+
"<IPython.core.display.SVG object>"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
"execution_count": 2,
|
| 50 |
+
"metadata": {},
|
| 51 |
+
"output_type": "execute_result"
|
| 52 |
+
}
|
| 53 |
+
],
|
| 54 |
+
"source": [
|
| 55 |
+
"from rdkit import Chem\n",
|
| 56 |
+
"from rdkit.Chem import Draw\n",
|
| 57 |
+
"from rdkit.Chem.Draw import rdMolDraw2D\n",
|
| 58 |
+
"from IPython.display import SVG\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"# Example molecule\n",
|
| 61 |
+
"mol = Chem.MolFromSmiles(\"C1=CC=C(C=C1)C=O\") \n",
|
| 62 |
+
"Chem.rdDepictor.Compute2DCoords(mol)\n",
|
| 63 |
+
"\n",
|
| 64 |
+
"# Define colors for atom types\n",
|
| 65 |
+
"atom_colors = {\n",
|
| 66 |
+
" 6: (0.6, 0.6, 0.6), # Carbon = light gray\n",
|
| 67 |
+
" 8: (0.98, 0.6, 0.6), # Oxygen = soft red/pink\n",
|
| 68 |
+
" 7: (0.55, 0.63, 0.8), # Nitrogen = light blue\n",
|
| 69 |
+
" 16: (0.8, 0.8, 0.55), # Sulfur = soft yellow\n",
|
| 70 |
+
" 17: (0.65, 0.85, 0.65), # Chlorine = light green\n",
|
| 71 |
+
" 1: (0.9, 0.9, 0.9), # Hydrogen = very light gray\n",
|
| 72 |
+
"}\n",
|
| 73 |
+
"\n",
|
| 74 |
+
"\n",
|
| 75 |
+
"# Default = muted purple (for other atoms)\n",
|
| 76 |
+
"default_color = (0.8, 0.7, 0.9)\n",
|
| 77 |
+
"\n",
|
| 78 |
+
"# Assign highlight colors\n",
|
| 79 |
+
"highlight_atoms = [atom.GetIdx() for atom in mol.GetAtoms()]\n",
|
| 80 |
+
"highlight_colors = {\n",
|
| 81 |
+
" atom.GetIdx(): atom_colors.get(atom.GetAtomicNum(), default_color)\n",
|
| 82 |
+
" for atom in mol.GetAtoms()\n",
|
| 83 |
+
"}\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"# Draw with transparent background\n",
|
| 86 |
+
"drawer = rdMolDraw2D.MolDraw2DSVG(400, 400)\n",
|
| 87 |
+
"# drawer.drawOptions().clearBackground = False # 🔑 makes background transparent\n",
|
| 88 |
+
"rdMolDraw2D.PrepareAndDrawMolecule(\n",
|
| 89 |
+
" drawer,\n",
|
| 90 |
+
" mol,\n",
|
| 91 |
+
" highlightAtoms=highlight_atoms,\n",
|
| 92 |
+
" highlightAtomColors=highlight_colors\n",
|
| 93 |
+
")\n",
|
| 94 |
+
"drawer.FinishDrawing()\n",
|
| 95 |
+
"\n",
|
| 96 |
+
"# Clean up RDKit's extra XML headers\n",
|
| 97 |
+
"svg = drawer.GetDrawingText().replace(\"svg:\", \"\")\n",
|
| 98 |
+
"SVG(svg)\n"
|
| 99 |
+
]
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"cell_type": "code",
|
| 103 |
+
"execution_count": 9,
|
| 104 |
+
"metadata": {},
|
| 105 |
+
"outputs": [
|
| 106 |
+
{
|
| 107 |
+
"data": {
|
| 108 |
+
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAEuCAYAAAATAREiAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAABamElEQVR4nO3dd1jV5f/H8ScCoiii4sA9CAcOUHCbI1Mxt7i3phju3BmOnJGamnuhEioqiHvlyJnEEFEgUJy4UFRANpzz+6Pkl60vHA58DvJ+XFfXBcHnvl/niuB1PuO+9dRqtRohhBBC5FsFlA4ghBBCCGVJGRBCCCHyOSkDQgghRD4nZUAIIYTI56QMCCGEEPmclAEhhBAin5MyIIQQQuRzUgaEEEKIfE7KgBBCCJHPSRkQQggh8jkpA0IIIUQ+J2VACCGEyOcMlA6QXWq1moiICPz8/Hjw4AFJSUmo1WoKFy5MhQoVsLOzo0aNGhQoIL1HCCGE+Cd5sgzExcXh7u7Ofk9P/P39iY2JAaBoMVOMChcGICUpibiYNwAUKVqUBjYN6NmzB8OHD6dkyZJKRRdCCCF0jl5e2sI4NDSUtWvXstPNjcSEBGw+bkutBo2oXqc+FnXrUayE2Xvf/zbmDfdCbxFx6wbhNwLw//kMBvr69O/fn4kTJ9KgQQOFXokQQgihO/JEGUhOTmbRokUsXboU05JmfNJnIB36DsbMvHyWxnnz8gXnvDz4ae+PRD2JZOLEiSxZsoQiRYrkUHIhhBBC9+l8GfD392fYsOGEhYfR22kyPUaNw7BgwWyNmZ6ezgl3V3av/JYKFcqz3dWVVq1aaSmxEEIIkbfo9F11e/bsoWnTpiSqwcXzBH3GfpntIgCgr69Pl2GjWX7wJ4xMS9KmTRvWrVunhcRCCCFE3qOzZwa2bt2Ko6MjbXr04YsFyzAwNMyRedLT03H7biFHd27GxcWFGTNm5Mg8QgghhK7SyacJ9u3bh6OjIx36D2XUnMU5+ligvr4+w2fNo5CxMTNnzsTExAQnJ6ccm08IIYTQNTp3ZiA8PBxra2sat/+MCS4/5Nr6AGq1Gtclczm5azvXrl2jUaNGuTKvEEJ3qdVq7t+/j7+/P/7+/gQEBPD69WuSk5MxMDCgSJEi1K5dGzs7O2xtbalbty4FtXApU4jcplNlID09nVatWnP/8ZPfr+cXNs7V+dNSU5ndvwtGqAjw98fIyChX5xdC6IaXL1/i6urKhg0buH//PgBly5alVq1amJmZYWhoSHp6OvHx8dy5c4eIiAhUKhVFihRh0KBBjB07Fmtra2VfhBBZoFNlYOXKlUydOpUFPx7Ayq6JIhkehIUyo7c906dNY8mSJYpkEEIo4/79+8ydO5d9+/ahVquxt7enQ4cO1KlTh1KlSv3rcQkJCYSHh/PLL7/g6elJVFQUzZo1w9nZmc8++ywXX4EQmtGZMvDy5UsqV65M294D+fzrhYpm8dywir1rlnP79m2qV6+uaBYhRM5TqVRs2rSJ6dOnU7RoUQYNGkSPHj0oUaJElsdKTU3lwoUL7Nq1Cz8/P4YOHcqqVas0GkuI3KIzjxZu376ddJWKPmMnKx2FrsMdMS5qwqZNm5SOIoTIYZGRkbRv356xY8fSqVMnDh48yIgRIzT+421oaMinn36Kq6srCxYswNvbmzp16nDixAktJxdCe3TizIBKpcLio4+oUt+WiS4/KB0HgO1L53P1qBePIyMpVKiQ0nGEEDkgLCyMTz/9lLS0NL755huaN2+u9TmePXvGN998w9WrV9myZQsjR47U+hxCZJdOnBk4deoU9+/dw37AsCwfu3fNchxqlc/4x//C2fe+vmbW5IyvnfJwy/S4HfsP4VV0NPv3789yJiGE7gsPD6dVq1YYGRmxa9euHCkCAObm5qxdu5bevXvz+eefs3Xr1hyZR4js0IkycOLECcpVroqldcNsj+W1cbUWEkH5ahbUtLGVU3tCfICePXtGu3btMDExwdXVlTJlyuTofPr6+jg7O9OvXz8cHR3x9vbO0fmEyCqdKAO+vn5Y1LNBT08v22OFXffj5rXLWkgFFnWt+dXXTytjCSF0g1qtxtHRkcTERDZu3JhrW5rr6ekxe/Zs2rVrx+eff87Tp09zZV4hMkPxMpCWlsaNG4FY1KmvtTE9N2jn7ED1OvWJuHObmJgYrYwnhFDe7t27OXLkCHPmzMHc3DxX5y5QoABz586lQIECjBkzBh24ZUsIQAfKQFhYGImJiVTXQhmwqPv7Ih+3fK4Qdj377+jfFZTr169neywhhPKePXvGhAkT6NSpE+3atVMkQ4kSJZgzZw5Hjhxh9+7dimQQ4q8ULwNRUVEAlCpXPttj1WvWkhrWtgDs37Aq2+OZ/ZHpxYsX2R5LCKG8FStWkJ6ezldffaVojnbt2tG+fXucnZ1JT09XNIsQoANlIDExEQBDLS396/DFRACuXzxHxK2gbI1V8I9M7zIKIfKuxMREXF1d6dmzp04sADRixAju37/PqVOnlI4ihPK7Furr6wOg0lI7tmvbnmpWdbkXcgvPjaswLlpM47HeZXqXUQiRd+3bt49Xr17Rt29fjcdISEjA09OTs2fPEhERQWJiIqVLl8bCwgJ7e3vs7e0xzOR263Xr1qVOnTqsW7dOliwWilP8zEDhwoUBSNbiu2+HMZMA8D17ike3f9N4nOSk3zO9yyiEyLs2bdpEixYtqFy5skbHR0RE4ODgwLJlywgICCAmJoaUlBQeP37MxYsXmT17NhEREZkeT09Pj379+nHixAkePXqkUSYhtEXxMvDRRx8B8PjuHa2N2bTDZ1T8qAZqtZqIYM0vFURG/J7pXUYhRN6UlJSEr68vbdu21ej4mJgYnJyciIyMBKBMmTLMmDGDLVu2sGrVKgYPHoyJiUmWx23Tpg1qtZqrV69qlEsIbVG8DJQvX56y5ubZ+qP9V3p6ejiMmZjtce4GB1GoUCGsrKy0kEoIoZSbN2+Slpam8f/LO3bsyFgXwMTEhN27dzNkyBCaNm1Ku3btmDlzJkeOHKFcuXJZGrdEiRKUL18ef39/jXIJoS2KlwEAW1tb7t66odUxW3zWHfMq1bI1RkRwEPXrW2NgoPitFUKIbPD398fAwIAaNWpodPzJkyczPh4yZAhly5b92/eYmZlhamqa5bFr164tZUAoTifKQONGjbgddJ2U5CStjamvr08vx/EaH69SqfjNz4fGjRtpLZMQQhk3b96kWrVqGGnw1FJCQkLG5QGAhg2zv2z6n9WqVYugIO2dGRVCEzrxlnfAgAHMnz+fqyeP0qZ77ywd22/CNPpNmPaPX2vnMIB2DgM0yhR4+WeinkQyaNAgjY4XQuiO2NhYjd61A8TFxb33eenSpbURKUOxYsX+NocQuU0nzgzUqFGDTz9tz+k9O5WOkuHUnh3Y2DSgSZMmSkcRQmRTSkqKxpf7/npjoLYXITM0NCQ5OVmWJhaK0okyADBu3FjCAv25c1O79w5o4tmjB/j/fJZx48ZqZfMkIYSyChYsqPFKf8bGxlSsWDHjc20vT56WloaRkZH8rhGK0pky0KVLF2rUrMm2RV8rujynWq1m64LZlDU3Z8AAzS4xCCF0i4mJCbGxsRofb29vn/Gxm5tbxjLqfxYdHa3RpmaxsbEULVpU42xCaIPOlAEDAwN2bN/O7aDrHNm+UbEc5w54cP3SebZu2UKRIkUUyyGE0J66dety9+5dUlJSNDp++PDhGY8NxsXFMXDgQNzd3fHx8eHcuXO4uLjQtWtXjbYlDgsLo169ehrlEkJbdOIGwneaNWvGlClTWLNmObZt2lPpI80eA9LUy6eP2fntfIYNG0bnzp1zdW4hRM6xtbUlNTWV27dvU6dOnSwfb2pqyoYNGxg/fjyRkZE8f/4cFxcXrWQLDQ2lX79+WhlLCE3pzJmBdxYuXEj16tX51mkor54/y7V54968ZsmYIRQ3NWXlypW5Nq8QIufVr18ffX19goODNR7DwsICLy8vpk+fTsOGDTE1NcXQ0BBzc3NatGjB4sWLsbCwyNKYb968ITIyEltbW41zCaENemodvIX14cOHNG/RAr2ChZm73QOzsllb1Sur4l6/YtHoQbx6GsmlixdlxUEhPkBNmjTByMiI9evXKx0lw6FDh3B2dqZhw4YMHDgQBwcHqlatqnQskQ/p3JkBgMqVK3P+3DlITWLOoB7cDbmZY3NF3r3N3CG9ePP8CWd++kmKgBAfKEdHRy5fvvzeAkJK27t3L/Xq1aNixYp8/fXXVKtWDTs7O5YuXUp4eLjS8UQ+opNlAMDS0pKrV65gbmbGrL6d8fhhGaka3vzzT9LT0zm0bQPTe3agIGouXbxIgwYNtDa+EEK3DBgwgGLFirF//36lowAQHBzMzZs3WbRoEYcOHeLFixd4eHhQvXp1Fi1aRM2aNalXrx7z58/n1q1bsg6ByFE6eZngz1JSUliyZAmLFy+m4kc1GDLNmfrNW1GggGY9Rq1WE+r/Kzu+nc/d4CAmT57MokWLMDY21nJyIYSu+fLLL9m5cydHjhzReEVCbZkxYwa3bt3i3r176Ovrv/e1hIQETp06hZeXF0eOHCE2NpYaNWrg4OCAg4MDDRs2lHUJhFbpfBl4JzAwkFGjR+Pv50f5KtVo338obXv2xaR4iUwdn/j2LReOeHF6z04ehP+GWalSxL99S0REBOXLl8/h9EIIXfD48WPq1KlD69atWbx4sWI5Ll68yLhx43B1dWXEiBH/+b3JycmcPXsWLy8vDh48yKtXr6hatSq9evWid+/eNGnSROM3R0K8k2fKAPz+rv7KlSts2LCB/fv3k56eTiULS6pZ1ceibn3KVa1OQaNCAKQkJ/E88iF3g4O4eyuIh3fCUKtUdOvenXFjx9KgQQNq1apF586d2bFjh7IvTAiRa7Zv387IkSNZu3YtrVu3zvX5Y2Ji6NWrF7a2thw7dixL7/BTU1O5cOECXl5eeHt78/z5c8qXL0+vXr1wcHDg448//ttZBiEyI0+VgT+Liori0KFD+Pv74+vrx61bN/+2oIiBgQG1rerQuJEdtra2dOnShUqVKmV8fdOmTXzxxRf8+uuvNGokuxMKkR+o1Wo6d+6Mv78/e/fupVSpUrk698yZM7l69SrBwcFUqFBB47HS09O5evUqXl5eeHl5ERkZSenSpenRowe9e/embdu2GBoaajG9+JDl2TLwVykpKTx//pykpCTUajWFCxemdOnSFCpU6F+PSU9Pp2HDhhgbG3P16lW5BidEPvH48WMaN25M0aJFcXV1zZX7B9RqNcuXL8fNzQ0PDw+tLjSkUqnw9fXNKAZ3796lRIkSdOvWDQcHB9q3b/+fvwuF+GDKgKbOnz/PJ598gru7u2xXLEQ+EhISQqtWrShdujQbN27EzMwsx+ZSqVQsW7YMd3d31qxZw/jx43NsLrVazY0bN/D09MTLy4vffvsNExMTOnfuTO/evbG3t5el1sXf5PsyANC7d29++eUXwsLCZMMQIfKRW7du0b59ewwMDFi4cCF2dnZan+Ply5csXLiQ8+fPs27dOpycnLQ+x38JCQnJOGNw48YNChcuTKdOnXBwcKBLly4UK1YsV/MI3SRlALh37x61a9dm+vTpLFy4UOk4QohcFBYWRuPGjTM2IJo4caJWHjVWq9WcPHmSpUuXYmBgwJYtW+jevbsWEmvuzp07GcXA19eXggUL0qFDBxwcHOjWrRslS5ZUNJ9QjpSBPzg7O7NixQpCQ0NlOVAh8pFZs2axfPlyJkyYwKZNmzAzM2Pw4MF069YNExOTLI+Xnp7OlStX2LVrF1evXqVv376sXbuW0qVL50B6zT18+JADBw7g5eXFlStX0NfXp23btjg4ONCjRw/Kli2rdESRi6QM/OHt27fUqFGDli1bsm/fPqXjCCFywU8//USHDh1wcXFhxowZ3L59m6+//hpvb28MDQ3p3LkzHTt2pHbt2v95k+G7HRF/+eUXPD09iYyMxMbGhjlz5tCrV69cfEWaefr0Kd7e3nh5efHzzz8D8PHHH+Pg4ECvXr2y9dSDyBukDPzJjz/+yNChQ/n5558Vef5YCJF7oqKisLa2pm7dupw6deq9hXuePHnC1q1b2bx5M48fPwZ+3zOlVq1alCxZEiMjI9LS0oiPj+fOnTuEh4eTkpKCkZER/fr1Y+zYsTRu3DhPPqH08uVLDh06hKenJ2fPniU1NZWmTZtmrH5YrVo1pSOKHCBl4E9UKhXNmzcnKSkJf39/WbxDiA+USqWiS5cu+Pn5cePGDcqV++edUVUqFeHh4fj7++Pv709AQACvXr0iKSkJQ0NDihQpgpWVFba2ttjZ2WFtbf1BLW3+5s0bjhw5gpeXFydPniQ5OZmGDRtmFIOaNWsqHVFoiZSBv/Dx8aFp06Zs2rQJR0dHpeMIIXLAqlWr+PLLLzl27BifffaZ0nHyhLi4OI4fP46XlxfHjx8nPj6eOnXq4ODgQO/evalbt26ePBOSFampqbx69YrExET09PQoVKgQpUqV+iDeOEoZ+AfDhg3jxIkThIeHU7x4caXjCCG0KCAggKZNmzJ+/Hi+//57pePkSYmJiRkbKR0+fJjY2FgsLS0zzhjY2tp+EMXg2bNnHD9+HD8/P/z8/AgKCiI5Ofm97zE2Nsba2ppGjRphZ2dH586d8+RTGVIG/sGTJ0+oUaMGY8aMYcWKFUrHEUJoydu3b7G1taVIkSL88ssvGBkZKR0pz/vzRkqHDh0iOjqaKlWqZGyk1LRp0zy1kZJarebSpUusX78eLy8v0tPTqV69OlZWVlhZWVGhQoWMn5ukpCQePnxISEgIoaGh3L9/n0KFCtG/f3/Gjh2bp5a5lzLwL5YsWcK8efO4deuWXBcT4gMxcuRI9u3bR0BAADVq1FA6zgcnLS0tYyOlAwcOZGyk1LNnz4yNlAwMDJSO+a9+/fVXRo8eTVBQEFWrVqVfv3507do108tVv3z5koMHD+Lp6cnjx49p1qwZW7ZsoU6dOjmcPPukDPyLpKQkateujZWVFceOHVM6jhAim/bs2cPAgQPZvn07w4cPVzrOB++fNlIqVapURjH45JNPdGYjpaSkJObPn8+yZcuoXbs2kyZNomnTphpf6khPT+fSpUusXLmSyMhI5s+fz/Tp03W6CEkZ+A8HDhzAwcGB48eP06lTJ6XjCCE0dPfuXRo0aEDnzp3ZtWvXB3E9Oy9Rq9X4+vpm7Jdw9+5dihcvnrGRUocOHRTbSOnOnTt069aNO3fu4OTkxIgRI7T2Rzs5OZl169axc+dOGjZsyKFDhyhfvrxWxtY2KQP/Qa1W065dO548ecLNmzd1psUKITIvNTWVjz/+mKioKK5fv54rOxSKf/duI6V3ZwxCQ0MpWrQoXbp0wcHBgU6dOuXaRko3b96kffv2GBsbs2LFCiwtLXNknqCgIKZOnUqhQoU4e/Ys1atXz5F5skPKwP8QFBREgwYNWLFiBZMnT1Y6jhAii2bPns2yZcu4fPkyTZo0UTqO+It/2kjJ3t4+YyOlnCpvYWFhfPzxx5iZmbFp06YcfwLgyZMnjB49Gj09Pa5cuaJzqzpKGcgEJycn9uzZw+3bt3VufXEhxL87e/Ys7du3Z8mSJcyaNUvpOOJ/uHPnTsZ+Cb/++isFCxakffv2GRspaWub6ZiYGKytrTEwMGD79u2UKFFCK+P+L0+fPmXYsGGYmZnh6+ur2KWRfyJlIBNevHhBjRo16NevHxs3blQ6jhAiE168eIG1tTVWVlacPn06Tz3eJv6+kVKBAgUyNlLq2bNntjZSGjlyJPv378fT0zPX36GHhYUxYMAApk2bxpIlS3J17v8iZSCTVq9ezZQpUwgICMDa2lrpOEKI/6BWq+natSs+Pj7cuHFDZ2/aEpnz542ULly4gEqlem8jpYoVK2Z6rJMnT9KpUyfmzZtH7969czD1v9u8eTPr1q3j2rVrOrMWgZSBTEpNTaV+/fqYm5tz7tw5uRtZCB32ww8/MGnSJI4ePUrnzp2VjiO06N1GSl5eXpw5c4bU1FSaNGmSsfrhf92cl5CQQI0aNahcuTKbNm1S7Pd4amoqgwcPRk9Pjxs3bujEcsZSBrLgXaP09PTEwcFB6ThCiH8QGBhIkyZNcHJyYtWqVUrHETnozxspnTp1iqSkJBo0aJBRDGrVqvXe97u6ujJq1CiOHj1K5cqVFUr9u6CgIAYNGsSRI0fo0qWLollAykCWdenSheDgYEJCQihcuLDScYQQfxIfH4+trS2FCxfm2rVrstxwPvL27duMjZSOHTtGfHw8VlZW9O7dGwcHB+rWrYudnR1FixZl/fr1SscFYMCAAVSsWJETJ04oHUXKQFaFh4dTp04d5s+fz9dff610HCHEn4waNYo9e/bg7+//t3eFIv/480ZKR44cISYmhooVKxIZGcm6deto1aqVxmMnJCTg6enJ2bNniYiIIDExkdKlS2NhYYG9vT329vaZXpPG29ubefPmcfv2bSwsLDTOpA1SBjQwbdo0NmzYQHh4uM49KypEfrV371769+/Ptm3bGDlypNJxhI5ISUnh7NmzTJs2jRcvXvDTTz9pfI0+IiKC8ePHExkZ+a/fs3///kwX0aSkJNq2bcvMmTNxdnbWKJO2SBnQQExMDJaWltjb2+Pm5qZ0HCHyvXv37mFjY0OnTp3Ys2eP3OAr/qZp06aUKlWKb7/9VqPjY2Ji6NOnD0+fPgWgTJkyDB8+HEtLS+Lj4/Hz8+PQoUO4urpm6ayUo6MjpUuX5tChQxrl0hYpAxrasmULjo6O/PLLLzRt2lTpOELkW6mpqbRq1Ypnz55x/fp1ihcvrnQkoWPS0tIwMTFhwoQJDB06VKMxVq9ezdatWwEwMTHB29v7b2sdREdHY2BgkKVVE1etWsXx48d58uSJRrm0RVbh0NDIkSOxsbFh0qRJqFQqpeMIkW998803+Pr6snv3bikC4h+FhISQlJSElZWVxmOcPHky4+MhQ4b846JHZmZmWV4+2crKiqdPn/Ls2TONs2mDlAEN6evrs3r1an799Vfc3d2VjiNEvnT+/HmWLFnCggULaNasmdJxhI66c+cOgMY36SUkJLx3n0DDhg21kgv+P9Pt27e1NqYmpAxkQ6tWrejbty+zZs3i7du3SscRIl95+fIlgwcPpk2bNsycOVPpOEKHJSQkAGj8OHhcXNx7n2tzj5p3mRITE7U2piakDGTTd999x+vXr1m6dKnSUYTIN9RqNSNHjiQ5OZkff/xRJ1ZwE7orPT0dQOOfExMTk/c+f/HiRbYzvfNuz4x3GZUiZSCbqlSpwvTp01mxYgV3795VOo4Q+cK6des4cuQI27dvl8d7xf/0bnfA5ORkjY43NjZ+b/+D69evayUX/P7oI6D4DoZSBrRg5syZlCpViunTpysdRYgP3o0bN5g2bRoTJkyga9euSscReUCZMmUAiIqK0ngMe3v7jI/d3Nz+cazo6GhiYmKyNO7z588B7V560ISUAS0oUqQI3333HQcOHODcuXNKxxHigxUfH0///v2pWbMm3333ndJxRB5hY2MDQHBwsMZjDB8+nHLlygG/30MwcOBA3N3d8fHx4dy5c7i4uNC1a9eMdQgy693S9kqvmCnrDGiJWq2mZcuWxMXFERAQgIGBgdKRhPjgODo64u7ujr+/P7Vr11Y6jshDLCwsaN68ebZuNtX2CoTw+5nlV69e8csvv2icSxvkzICW6OnpsWrVKm7evJmxMIUQQnv279/Pli1b+OGHH6QIiCyzs7Pj1q1b2RrDwsICLy8vpk+fTsOGDTE1NcXQ0BBzc3NatGjB4sWLs/T4olqtJjg4GDs7u2zl0gY5M6BlI0aM4MiRI9y+fZsSJUooHUeID8KDBw+wtramQ4cO7N27V5YbFlm2Y8cORo4cybFjx6hUqZLScQC4efMmAwcO5OjRo3Tu3FnRLHJmQMuWLFlCcnIy33zzjdJRhPggpKWlMXDgQIoXL87mzZulCAiN9O3bl+LFi7N//36lo2Tw8PCgSpUq792cqBQpA1pWrlw5nJ2dWbt2LSEhIUrHESLPW7BgAT4+PrLcsMgWY2NjRowYgbe3N0lJSUrH4c2bN5w8eRInJyedWCdDykAOmDx5MlWrVmXKlCnIVRghNPfzzz+zaNEi5s+fT/PmzZWOI/K4L774gpiYGHbt2qV0FLZu3UqBAgV0ZrttKQM5wMjIiOXLl3Pq1CmOHz+udBwh8qTo6GgGDx5Mq1at+Oqrr5SOIz4AlpaWTJkyhfXr1yu6SFxgYCBubm4sWLBA8fUF3pEbCHOIWq2mffv2PHz4kFu3blGwYEGlIwmRZ6jVanr27MmlS5e4cePGe6u/CZEdiYmJ2NjYYGRkhJubW64/Bp6UlESfPn0oW7YsV65c0YlLBCBnBnLMu0cNIyIiWLNmjdJxhMhTNmzYwKFDh3B1dZUiILSqcOHC7Nixg1u3brF69epcnVulUrFo0SKePn3K9u3bdaYIgJSBHFW3bl2cnJxYsGBBxpKTQoj/dvPmTaZMmcK4cePo3r270nHEB6hZs2asXLmSHTt2sHnz5lyZU61Ws2zZMg4fPsy2bdt0bq0MuUyQw6Kjo7G0tMTBwYEtW7YoHUcInZaQkECjRo3Q19fHx8dH4y1nhciMhQsXMnfuXD7//HMmTpyYsYOgtqWlpbFo0SK8vLxYv349Tk5OOTJPdsiZgRxmZmbGN998w7Zt27S605UQH6IpU6Zw7949PDw8pAiIHDdnzhxWrFjBtm3bcHR05PHjx1qfIyIigqFDh+Lt7c2OHTt0sgiAnBnIFampqdjY2GBmZsaFCxdk0RQh/oGXlxe9e/dm06ZNODo6Kh1H5CNnzpxh5MiRREdHM2XKFPr06ZPtswRpaWns3LmT9evXU61aNXbs2EHTpk21lFj7pAzkkp9++iljKdW+ffsqHUcInfLw4UOsra1p164d+/fvl8Iscl1sbCzTpk1jy5YtVK5cmb59+9KjRw9MTU2zNM7Lly/x8vLC09OTqKgopk2bxjfffEOhQoVyKLl2SBnIRd27dycwMJDQ0FCMjY2VjiOETkhLS6Nt27Y8ePCAGzduyJ4eQlE+Pj6sWbMmo5S2bdsWa2trrKysqFWr1t9+d8fFxREaGkpISAiBgYFcuHABQ0NDBg4cyMSJE6lfv75CryRrpAzkojt37mBlZYWzszNz585VOo4QOmH+/PksXLiQCxcu0LJlS6XjCAFAVFQU27Ztw9vbm6CgIJKTk9HT08PMzIyCBQuSlpZGeno60dHRwO/LHdvY2NC3b1+GDRuW55bOljKQy2bMmMHatWsJCwvTmZ2zhFDKxYsXadu2LfPmzZOCLHRWamoqISEh+Pn58eTJE+7du8f27dv54osvaNq0KXZ2dtSqVUun1g3IKikDuSw2NhZLS0s+/fRTnVgfWwilvHr1Cmtra6pXr865c+fy9C9Skb8EBARga2uLv78/DRs2VDqOVsijhbmsWLFiLF26lN27d3PlyhWl4wihCLVazahRo4iPj8fd3V2KgBAKkzKggOHDh2Nra8ukSZNQqVRKxxEi123atAlvb2+2bdsml8uE0AFSBhRQoEABVq9ejb+/Pzt37lQ6jhC56tatW3z55Zc4OTnRs2dPpeMIIZAyoJgWLVrQv39/vvrqK2JjY5WOI0SuSExMpH///lhYWLBixQql4wgh/iBlQEEuLi7ExsayZMkSpaMIkSumTp1KRESELDcshI6RMqCgypUrM3PmTFauXMmdO3eUjiNEjvL29mbDhg2sXLmSunXrKh1HCPEnUgYUNn36dMqWLcu0adOUjiJEjnn06BGff/45PXv2ZMyYMUrHEUL8hZQBhRkbG7Ns2TIOHTrEmTNnlI4jhNalp6czaNAgihQpwtatW2XfASF0kJQBHdC3b19atmzJ5MmTSUtLUzqOEFq1ePFirly5wq5duyhZsqTScYQQ/0DKgA7Q09Nj1apVhISEsGnTJqXjCKE1ly9f5ptvvmHOnDm0atVK6ThCiH8hZUBH2NraMnLkSObMmZOx8YUQednr168ZNGgQzZs3x9nZWek4Qoj/IGVAhyxevJi0tDTmz5+vdBQhskWtVjN69GhiY2PZtWsXBgYGSkcSQvwHKQM6pGzZssydO5cNGzYQHBysdBwhNLZ161a8vLzYunUrlStXVjqOEOJ/kDKgYyZOnEi1atX48ssvkQ0lRV4UEhLCpEmTGDNmDA4ODkrHEUJkgpQBHVOwYEG+//57fvrpJ44cOaJ0HCGyJCkpif79+1OtWjW+//57peMIITJJyoAO6tKlCx06dGDKlCkkJycrHUeITJs+fTrh4eF4eHhgbGysdBwhRCZJGdBBenp6rFy5kvv377N69Wql4wiRKYcPH2bt2rV8//331KtXT+k4QogskDKgo6ysrBg3bhwLFy7k2bNnSscR4j89fvyYESNG0L17d5ycnJSOI4TIIikDOmzevHkULFiQr7/+WukoQvyr9PR0Bg8eTOHChdm2bZssNyxEHiRlQIeVLFmShQsXsn37dvz8/JSOI8Q/+vbbb7lw4QK7du3CzMxM6ThCCA1IGdBxjo6O1KlTh0mTJsmjhkLnXL16lXnz5uHs7Ezr1q2VjiOE0JCUAR1nYGDAqlWruHr1Kh4eHkrHESLDmzdvGDhwIE2aNGHu3LlKxxFCZIOUgTygXbt29OzZkxkzZhAfH690HCFQq9U4Ojry5s0bdu/eLcsNC5HHSRnII5YvX05UVBTLli1TOooQuLq6sn//frZs2UKVKlWUjiOEyCYpA3lE9erVmTJlCi4uLjx8+FDpOCIfCw0NZeLEiYwePZo+ffooHUcIoQV6arkrLc+Ii4ujRo0atG7d+h/vH3j79i2BgYH4+/tz8+ZN4uLiSElJwcjICBMTE+rXr4+trS02NjayOlw+ExMTQ0BAAP7+/oSEhBAfH09qaipGRkYUL14ca2trbG1tqV+/PkZGRv86TlJSEk2bNiUlJQU/Pz/5ORL5UkBAALa2tvj7+9OwYUOl42iFXOjLQ0xMTPj2228ZPnw448aN4+OPPyYuLo5du3axZcsWAgMDUalUFCxYEEtLS0xMTDA0NCQ1NZXY2Fjc3NxISUmhQIECNGzYEEdHRwYOHEiRIkWUfmkiB0RHR7Njxw62bXMlNDQEgEKFjalsWZPCJibo6xuQlppKXGAQW7ZuJT0tDQMDA5o1a84XX/y+ydBfi8HMmTP57bff8PHxkSIgxAdEzgzkMSqViqZNm5KQkEDr1q358ccfiY+Pp02bNrRu3RorKyssLCwwNDT827GpqancuXOHkJAQzp8/z8WLFylWrBjDhg1jxowZVKhQQYFXJLQtNDSU7777Dg8PD9JVKpp26IzNx22xqFOf8tUs0NfX/9sxKclJPAgLJeLWDa6dPs7Na5cpVbo0o0eNYtq0aZQsWZIjR47QrVs31qxZw/jx4xV4ZULohg/xzICUgTxGrVbz1VdfsWzZMkxNTenTpw99+vTB3Nw8y2M9fvwYT09PvLy8UKlUrFy5kuHDh8sKcnlUWloay5cvZ968eRQvVYb2/YfQzmEApmalsjxWZMRtTnm48bP3XkyKFuXbpUuZNm0azZs359ChQ/IzIvI1KQNCUY8ePWLkyJGcOXOGXr16MW3aNExMTLI9bkxMDN999x2HDx+mU6dObNmyRc4S5DGhoaEMHTqMgAB/uo38gn4TplHQqFC2x331/Bmb5s/A7/wZChsbE3TjBh999JEWEguRd0kZEIq5efMmHTp0AGD+/Pm0aNFC63NcuHCBBQsWYGhoyE8//UTt2rW1PofQvosXL9K5SxeKlzZn3JLvqWFjq9Xx1Wo1Fw574brImUoVK/DT6dNUrFhRq3MIkZd8iGVAHi3MA4KCgmjdujXFixfHw8MjR4oAkPGUgrGxMa1atSIkJCRH5hHac/78eTp27Ei1OtZ8u/+41osA/L6ldpvuvfl23zGi38TS8uOPiYyM1Po8QgjlSBnQcXfv3qVDhw6UK1eObdu25fhGMKVLl2b79u2YmZnRvn17Hjx4kKPzCc35+/vTtVs3ato2ZvYmNwrn8FMh5atZsHCXN4kpabTv0IFXr17l6HxCiNwjZUCHpaenM2jQIIyMjNiwYQPFihXLlXlNTU3ZuHEjAEOHDkWlUuXKvCLzEhIS6Ne/P+WqWjBjjatW7g/IjNLlKzJn2x4eP3nKOHmiQIgPhpQBHbZq1Sp8fHxYtGgRJUuWzNW5S5UqxYIFC7h48SLr16/P1bnF//b111/z6FEkE79bQ6Fcft6/fDULRjovwmPPHry9vXN1biFEzpAyoKPCwsJwdnZm8ODBNGjQQJEMTZo0oV+/fsycOZOIiAhFMoi/u3z5MqtXr2bApBlUqK7Mnf0fd+lJo0868IWTE9HR0YpkEEJoj5QBHeXs7Ezp0qWZMGGCojmmTJmCqampbFGrQ6ZNm45l/QZ0HjZasQx6enqM+eY74hMTcXFxUSyHEEI7pAzooCdPnuDt7c2QIUMoXLiwolmMjY0ZNGgQnp6eREVFKZpF/P5Ik4/PNXqMHvePKwnmphKly9C2Z3+2ubqSlJSkaBYhRPZIGdBBW7ZswcjIiK5du2o8RkJCAm5ubgwbNoyWLVtia2uLvb0948aN48iRI6SmpmZ6rO7du6Onp4erq6vGeYR2rF+/nlLm5bFr0z5Lx+1dsxyHWuUz/vG/cPa9r6+ZNTnja6c83DI9bsf+Q3gVHc3+/fuzlEcIoVukDOiY9PR0Nm3aRJcuXShatKhGY0RERODg4MCyZcsICAggJiaGlJQUHj9+zMWLF5k9e3aW7gEoXrw49vb2bNiwAVmjSjmxsbHs3r2b9v0Go2+QvT3GvDau1kqm8tUssG7Rig0bNmplPCGEMqQM6Jjw8HCePn1K+/ZZe+f3TkxMDE5OThmLwpQpU4YZM2awZcsWVq1axeDBgzVawrhDhw48fPiQe/fuaZRLZJ+Pjw+JiYk07dg522OFXffj5rXLWkgFzTp2xcfnGgkJCVoZTwiR+2QLYx3j7+8PgJWVlUbH79ixg6dPnwK/b3m8e/duypYtm/H1du3aMWrUKAyy+M7yXR5/f3+qV6+uUTaRPf7+/hgXLUr5qhZaGc9zw2rqNW2Z7XEs6tRDpVJx48YNmjVrpoVkQojcJmcGdIyfnx+VK1fWeIGhkydPZnw8ZMiQ94rAO2ZmZpiammZp3FKlSmFubo6fn59GuUT2+fn5Ua12XQoUyN7/thZ1rQG45XOFsOvZ/+9ZybIWhgULys+GEHmYlAEdc/36dWrVqqXRsQkJCe+tGa/tDTRq1arF9evXtTqmyLzrgTeoZlUv2+PUa9aSGta/72Gwf8OqbI9nWLAgVWrUIjAwMNtjCSGUIWVAx7x580bj1Qbj4uLe+7x06dLaiJShZMmSvHnzRqtjisx78+Y1piVLaWUshy8mAnD94jkibgVlezyTEma8fv062+MIIZQh9wzomKSkJAoWLKjRsX+9MfDFixdavb5fsGBB3rx5Q0BAgNbGFJmXmJiIgYY/G39l17Y91azqci/kFp4bV2FcNHv7XhgaGclaA0LkYVIGdIyhoSHp6ekaHWtsbEzFihUzLhVcv36dJk2aaC1bWloa9+7dw9ZW+9vkiv+tgL4+qvQ0rY3nMGYSyyeNxvfsKapn8/JDemoaBXN410QhRM6RMqBjihQpwtu3bzU+3t7enq1btwLg5uZGr169KFOmzHvfEx0djYGBQZZvIoyPj6d+/fps2bJF43xCc/adOpGQjZ+Nv2ra4TMqflSDyDvhRARn71JBYnwcxuW1e1lKCJF7pAzoGCsrK3x9fTU+fvjw4Rw7doynT58SFxfHwIEDGT58OJaWlsTHx+Pr68uhQ4dwdXXNchm4c+cOn3zyidZvTBSZU79efR6Gh2ptPD09PRzGTGT19OxtRaxWq3l0O4wB3btoKZkQIrfJDYQ6xs7OjvDwcFJSUjQ63tTUlA0bNlCxYkUAnj9/jouLC6NGjWLSpEm4u7v/7UbDzEhISCAiIgI7OzuNconsa9TIjrvZfAf/Vy0+6455lWrZGuN55EPiYt7Iz4YQeZiUAR1ja2tLamoqt2/f1ngMCwsLvLy8mD59Og0bNsTU1BRDQ0PMzc1p0aIFixcvxsIiawvXhIWFoVKp5H4BBdna2hL9/BmvX2hvwyh9fX16OWbvzEDErRsA8rMhRB6mp5bF5nVKYmIiJUuWZMyYMYwaNUrpOBnWrVvHjz/+yKtXrzR+2kFkz6NHj6hcuTITvl1Nmx59lI6TYb3zVO76+3D3bub3uxAiLwsICMDW1hZ/f/8P5rKpnBnQMYULF6Z///54enpq/FSBtqWmpnLgwAEGDx4sRUBBlSpV4tNP23M6C7sK5rT42BguHz3IyJEjlI4ihMgGKQM6aOzYsTx+/JjLl7WzkUx2nT9/nqioKMaOHat0lHxv3LixhAX6a/3eAU2dP7if9LRUnTqLJYTIOikDOqhRo0bY2dnh7u6u+JbBarWaXbt20aJFC+rXr69oFgFdunShQsWKHHPbpnQU0lJTObV7Bw4ODpibmysdRwiRDVIGdNScOXO4du0aR48eVTTHgQMHCAgIwNnZWdEc4ncGBgbM/uorfj60X2tbEGvqwKYfeP7oATNnzlQ0hxAi+6QM6Khu3boxcOBAXFxcePHihSIZnj59yvLlyxk+fDj29vaKZBB/98UXX9CqdWvWfz2VRC0uQpQV938LxnPjar766isaNGigSAYhhPZIGdBhP/zwA4UKFeKbb75BpVLl6tzp6enMmzcPU1NTVq5cmatzi/9WoEABtru68vZ1NDtcvsn1S0nJSYms++pLateqzZw5c3J1biFEzpAyoMPMzMzYunUrFy9eZOnSpbn2S1+tVrNgwQJ+/fVXXF1dKV68eK7MKzKvevXqrFq1ijP7d+G9eW2uzZuWmsr3X47h6f0Idu7cIU+XCPGBkOWIdVyXLl3YvHkzo0ePRk9Pj1mzZlGgQM51uPT0dBYtWsSBAwfYuXMnHTp0yLG5RPaMHj2aJ0+eMH/+fNRqNb3GTEBPTy/H5ktJTmLV1LHcuHyBI0eOfDDPVwshpAzkCaNGjUKlUvHFF1/w8uVL5syZQ4kSJbQ+T3R0NN988w0XLlxg+/btDB06VOtzCO2aO3cuAPPnz+fFk0iGzZxH4RzYPTAq8hFrZk3k7q0beHt707FjR63PIYRQjlwmyCMcHR3x9PTEz8+Pnj17cvbsWa2Of/LkSXr27ElQUBDe3t4MHz5cq+OLnKGnp8e8efPYtGkTl494Ma1HO275XNXa+Gq1mlMebkzq0oZ7wTc5efIknTt31tr4QgjdIGUgD+nVqxfBwcHY2toyefJkJk+eTGBgoMb3EqjVagICApg4cSLTp0/nk08+ISQkhG7dumk5uchpjo6OBAUF8VHVKswb1pt1s7/kfliIxuOpVCquX/qZ+cP6sHn+LDq0/5TExASdWQhLCKFdUgbymHLlylGuXDmKFi3KnTt3GDJkCH379sXT0zPTjyBGRUWxb98+evfuzbBhw3j48CEeHh7s37+fMmXK5PArEDnlo48+4sLPP7NmzRpCfrnI1O6f4jywOxePHCDu9av/ebxarSbqcSSHXTcy0b4li0YPpEBSPKdOneLI4cPMmTOHefPmcfWq9s48CCF0g2xUlMf4+vrSuHFjNmzYgKOjI6dPn2bdunUcO3YMtVpN2bJlqV27NjVr1qRYsWIYGhqSmppKTEwMYWFhhISE8OLFCwoUKEDXrl0ZN24c7dq1y9GbEkXuS01N5dChQ6zfsIHz584BULZCJapa1aNqLSuMi5qgb2BAakoKsa+iuRd6k7vBN4l9/ftGVH379mXs2LE0bdo046bEtLQ0WrduzePHjwkMDJSnTES+9SFuVCRlIA9Rq9U0b96chIQEAgIC0NfXz/jakydP+OWXX/Dz88PPz4+bN28SHx9PcnIyRkZGFC1alPr162NnZ4etrS3NmjWjXLlyCr4akVvu3bvHtWvX8Pf3x8/fn+DgYBISEkhNScGoUCFMi5li08AGO1tbbG1tad68OWZmZv841oMHD7C2tqZDhw7s3bs3R59eEEJXSRkQitq1axeDBw/m3LlztG3bVuk4Ip/av38/ffv2ZevWrXz++edKxxEi132IZUDODecR8fHxzJw5EwcHBykCQlF9+vRh9OjRTJw4kdDQUKXjCCG0QMpAHuHi4sLLly9ZtmyZ0lGEYNWqVVSpUoUBAwaQlJSkdBwhRDZJGcgD7t+/z7Jly5g6dSrVqlVTOo4QGBsbs2fPHn777TfZtVCID4CUgTxgxowZlChRgq+++krpKEJksLa2Zvny5fzwww8cOXJE6ThCiGyQMqDjLly4wP79+3FxcaFo0aJKxxHiPePGjaNr166MGDGCJ0+eKB1HCKEhKQM6LD09nUmTJtGkSRMGDRqkdBwh/kZPTw9XV1eMjIwYPHgw6enpSkcSQmhAyoAOc3V15caNG6xevVoWBRI6q1SpUri7u/Pzzz/z3XffKR1HCKEB+Qujo968ecPXX3/NkCFDaNKkidJxhPhPbdu2Zfbs2cyZM4dr164pHUcIkUVSBnTUwoULSUhIYOnSpUpHESJT5s2bR+PGjRkwYAAxMTFKxxFCZIGUAR0UFhbGDz/8wOzZs6lQoYLScYTIFENDQ3bv3s2rV68YM2aMxrtpCiFyn5QBHTRlyhQqVqzIlClTlI4iRJZUrVqVzZs3s3fvXnbs2KF0HCFEJhkoHUC878SJExw/fhxPT08KFSqkdBwhsqxfv3789NNPjB8/nubNm1OzZk2lIwkh/gfZqEiHpKamUq9ePcqVK8e5c+dkRziRZ8XHx2Nra0vhwoW5du0aRkZGSkcSQmtkoyKRo9atW8ft27dZtWqVFAGRpxUpUgQPDw9CQkKYNWuW0nGEEP+DlAEd8eLFC+bPn4+joyPW1tZKxxEi22xsbFi2bBmrVq3i2LFjSscRQvwHKQM6Ys6cOejp6bFgwQKlowihNRMmTKBz584MHz6cp0+fKh1HCPEvpAzogBs3brBlyxbmzZtH6dKllY4jhNbo6emxfft2DA0NGTJkCCqVSulIQoh/IGVAYWq1msmTJ1OjRg3GjRundBwhtK506dL8+OOPnDt3jmXLlikdRwjxD6QMKOzAgQP8/PPPrFy5EkNDQ6XjCJEj2rVrx6xZs3B2dsbHx0fpOEKIv5BHCxWUmJiIlZUVderU4ejRo0rHESJHpaam8vHHHxMVFcX169cxNTVVOpIQGpFHC4VWff/990RGRvL9998rHUWIHGdoaMiePXuIjo7GyclJlisWQodIGVDI48ePWbp0KRMnTqRGjRpKxxEiV1SrVo2NGzeyZ88e3NzclI4jhPiDlAGFfPXVVxgbGzNnzhylowiRqwYMGMCIESMYN24c4eHhSscRQiBlQBHXrl3jxx9/ZPHixRQvXlzpOELkuh9++IEKFSrQv39/kpOTlY4jRL4nZSCXqVQqJk2ahI2NDSNHjlQ6jhCKKFq0KB4eHgQHBzN79myl4wiR70kZyGXu7u78+uuvrF69Gn19faXjCKGYBg0a4OLiwvfff8+JEyeUjiPE//T8+XMCAgIIDAwEIDQ0lJcvXyobSkvk0cJc9PbtW2rUqEHLli3Zt2+f0nGEUJxaraZLly74+voSFBSEubm50pGEyBAUFMTBgwfx9/fH19f3X5fUrly5MnZ2dtjZ2eHg4JAnbwqXMpCLvv76a77//ntCQ0OpWrWq0nGE0AlRUVFYW1tTr149Tp48SYECcsJSKCc5OZkDBw6wdu1arl69iqmpacZ6MFZWVlSoUCFjS+6kpCQePnxISEhIxj9v377l008/Zdy4cXTp0gUDAwOFX1HmSBnIJXfv3sXKyooZM2bIZkRC/MWZM2fo0KEDLi4uTJ8+Xek4Ip86fPgwTk5OPHnyhCZNmtCvXz/atGmT6dVhk5OTOX36NPv27SMwMJDq1auzbds22rRpk7PBtUDKQC5xcHDAx8eHsLAwihQponQcIXTOrFmzWLFiBVevXqVRo0ZKxxH5yKtXr5g0aRLu7u60atWKqVOnUr169WyNGRoaynfffYefnx9jx47FxcWFokWLaimx9kkZyAXnz5/nk08+YdeuXQwcOFDpOELopNTUVFq2bEl0dDQBAQEUK1ZM6UgiH/D19aVbt24kJCQwc+ZMunbtip6enlbGVqlU7Nmzh9WrV2Nubs7x48epVauWVsbWNikDOSwtLQ1bW1uKFCnClStXtPZDJsSHKCIiggYNGtC9e3d+/PFHpeOID9yFCxfo0qULFhYWrFixgrJly+bIPA8fPmTSpEm8efOG06dP06BBgxyZJzvkTp0ctnXrVoKCgli9erUUASH+BwsLCzZs2IC7u7uUAZGjfHx86Ny5M3Xr1mXz5s05VgTg96cNtm/fTtmyZenQoQO//fZbjs2lKTkzkINev36NpaUlXbt2Zfv27UrHESLPGDZsGAcOHCAgIABLS0ul44gPzLNnz6hXrx6VKlVi48aNGBsb58q8MTExjBgxgpSUFIKCgnTqUpiUgRw0efJktm3bRnh4OOXKlVM6jhB5RlxcHA0bNsTU1JSrV69SsGBBpSOJD4RaraZnz55cunQJb29vSpYsmavzP378mF69ejF48GA2bdqUq3P/F7lMkENCQ0NZu3Ytzs7OUgSEyCITExM8PDwICgri66+/VjqO+IB4eHhw6NAhnJ2dc70IAFSoUIEpU6awefNmzpw5k+vz/xs5M5AD1Go1nTp14vbt24SEhGQsUCGEyJoVK1Ywbdo0Tp48SceOHZWOI/K4mJgYqlevTuPGjVm2bJliOVQqFY6Ojjx79ozw8HCdOPMlZwZywPHjxzl16hQrVqyQIiBENnz55Zd07NiRoUOH8vz5c6XjiDzOzc2N2NhYxRe2KlCgALNmzeLBgwd4e3srmuUdOTOgZSkpKdStW5fKlSvz008/yRMEQmTT8+fPqV+/Pg0aNOD48eOyXLHQiFqtpnbt2lStWpXly5crHQeAkSNHUqhQIS5evKh0FDkzoG1r1qwhIiKCVatWSREQQgvKli2Lm5sbp06dYuXKlUrHEXnUzz//TFhYGP369cvWOAkJCbi5uTFs2DBatmyJra0t9vb2jBs3jiNHjpCamprpsfr168elS5e4detWtjJpg5wZ0KKoqCgsLS0ZMmQIa9euVTqOEB+U6dOns3r1aq5evYqdnZ3ScUQeM2bMGE6dOsWRI0c0fqMWERHB+PHjiYyM/Nfv2b9/f6ZXGUxNTaVdu3aMGzeOhQsXapRJW/LGdkp5hLOzM/r6+nzzzTdKRxHig7N48WLOnz/PgAEDCAgIwMTEROlIIg/x9fXFxsZG4yIQExODk5NTxjbGZcqUYfjw4VhaWhIfH4+fnx+HDh3K0piGhobUq1cPX19fjTJpk5QBLbl+/Tpbt25l9erVmJmZKR1HiA9OwYIF2bNnDw0bNmT8+PHs3LlT6Ugij0hOTubWrVt06tRJ4zF27NiRUQRMTEzYvXv3e6sWtmvXjlGjRmV5y2IrKyu8vLxQq9WKXlqWewa0QK1WM2nSJGrXrs0XX3yhdBwhPliWlpasX78eNzc33N3dlY4j8oibN2+SmpqKlZWVxmOcPHky4+MhQ4b84/LFZmZmmJqaZmlcKysrXrx4waNHjzTOpg1SBrRg//79XLp0iVWrVmV632shhGaGDBnC4MGDcXJyIiIiQuk4Ig+4f/8+AFWrVtXo+ISEhPfuE2jYsKEWUv2uSpUqwP9nVIqUgWxKTExk+vTpdOvWjfbt2ysdR4h8Yd26dZQtW5YBAwaQkpKidByh4xITEwEoVKiQRsfHxcW993np0qWznemdwoULA/+fUSlSBrJp+fLlPH36VGeeWxUiPyhWrBh79uzh+vXrzJkzR+k4Qse9e2hO02vyf71Z9cWLF9nO9M67TCqVSmtjakLKQDY8evSIpUuXMnnyZNlZTYhc1qhRI5YsWcJ3333H6dOnlY4jdNi7MwLJyckaHW9sbEzFihUzPr9+/bpWcsH/Z3p3hkApUgayYdasWZiYmODs7Kx0FCHypalTp9K+fXuGDh1KVFSU0nGEjnq3WdyTJ080HsPe3j7jYzc3t3/8eYuOjiYmJiZL477LpPSGdlIGNHTlyhV2797N0qVLdWpPaiHykwIFCuDm5oZKpWLYsGGKn2oVuqlBgwbo6ekREhKi8RjDhw/P+IMdFxfHwIEDcXd3x8fHh3PnzuHi4kLXrl0zHj/MrODgYExMTBQ/uywrEGpApVLRuHFjAH799VdZK10IhZ04cYLPPvuM77//ni+//FLpOEIH1a5dG2tr62ydydX2CoQAU6ZMITk5mQsXLmicSxtk0SENuLm54e/vz6VLl6QICKEDOnXqxJQpU5g5cyatW7fW6qNfIu9LSEigbNmyBAYGZmscCwsLvLy88PT05OzZs0RERJCQkICZmRkWFhZ89tlnWFhYZHo8tVpNcHAw/fv3z1YubZAzA1kUGxtLjRo1aNu2LXv27FE6jhDiD8nJyTRv3py4uDgCAgIoWrSo0pGEguLi4jh27BheXl4cP36chIQEALy8vKhRo4bC6X7n4+PDqFGjOH/+PG3atFE0i7ytzaIlS5YQGxuLi4uL0lGEEH9iZGTEnj17ePLkCRMmTFA6jlDA69evcXNzo1u3bpQuXZoBAwZw//595syZQ3BwMObm5uzbt0/pmBn27t1L7dq1ad26tdJRpAxkxZ07d1i5ciUzZ86kcuXKSscRQvxFjRo1WLduHTt27GD37t1KxxG54MWLF2zZsgV7e3vKlCnDsGHDiI6OZsmSJdy7dw9fX19mzZqFlZUVjo6OHD16lLdv3yodm+fPn3Pu3DnGjRunE9vdy2WCLOjRowcBAQH89ttvGBsbKx1HCPEP1Go1gwcP5siRIwQGBlK9enWlIwkte/LkCd7e3nh6enLx4kUAWrVqRe/evenZsyfly5f/x+MiIyOxsLBg0KBBTJkyJTcj/82cOXM4d+4cjx490okn0qQMZNKZM2do3749e/bs0YmbPYQQ/y42NhYbGxtKly7N5cuXZc+QD8CDBw/w8vLCy8uLq1evYmBgQLt27XBwcKB79+6UKVMmU+MsXboUZ2dnfvzxR+rXr5/Dqf/ZxYsXGTduHFu3buXzzz9XJMNfSRnIhLS0NGxsbChevDiXLl3SiVM6Qoj/5uPjQ8uWLZk2bRpLly5VOo7QwO3btzPu3vf398fIyIgOHTrQu3dvunbtSokSJbI8ZlpaGs2aNePVq1fs27cPIyOjHEj+72JjY+nZsyc2NjacPHlSZ/6eSBnIhHXr1jFhwgR8fX2xtbVVOo4QIpNcXFz46quvOH36NJ9++qnSccT/oFarCQkJwdPTEy8vL27evImxsTGfffYZDg4OdO7c+W/7BGgiODiYhg0bYm9vz8KFC3PtEfHU1FQmT55MYGAgt27dolKlSrkyb2ZIGfgfoqOjsbS0pFevXmzdulXpOEKILFCpVHTs2JFbt24RFBSk1d3mhHao1WquX7+ecQkgLCwMExMTunbtSu/evenYsWOO3KPl4eHBwIED6du3L7Nnz87xQpCamsrs2bM5e/YsR44coWPHjjk6X1ZJGfgfJkyYwM6dO7l9+zZly5ZVOo4QIouePn1K/fr1ady4MUePHtWZ07L5mUql4tdff8XT05MDBw5w7949SpYsSffu3XFwcODTTz/NldP3W7duxdHRka5duzJv3jwKFiyYI/MkJCQwc+ZMrly5goeHB7169cqRebJDysB/CA4OxtramqVLlzJ9+nSl4wghNHT8+HE6d+7MqlWrmDRpktJx8qX09HQuX76Ml5cXBw4c4PHjx5QpU4aePXvSu3dvWrdurciNnnv27GHYsGFUrVqVhQsXUqdOHa2O7+vry9y5c3n16hVeXl7vbXikS6QM/Au1Wk3Hjh25d+8et27dyvWbTIQQ2vXll1+yfv16rl27RoMGDZSOky+kpqby888/4+npycGDB4mKiqJixYr06tULBwcHWrRogb6+vtIxCQoKYtiwYdy8eZORI0cyevTobG8pHBcXx5o1a9izZw8tW7Zk+/btfPTRR1pKrH1SBv7F4cOH6d69O4cPH6Zr165KxxFCZFNycjJNmzYlISEBf39/Wa44hyQnJ/PTTz/h5eXFoUOHeP36NdWqVcPBwYHevXvTqFEjndzTJTU1lW+//ZaFCxdSuHBhunfvTt++falatWqWxgkLC2Pv3r0cO3YMtVrNt99+y/jx43XyNf+ZlIF/kJycTJ06dbCwsNCpRz+EENkTFhZGw4YN6d+/P9u2bVM6zgcjISGBkydP4unpydGjR4mLi6NmzZr07t0bBwcHbGxs8szv0Xv37rFp0ya2bt1KdHQ0DRs2pH79+tSpUwcrKysqVKiQcTYjLS2Nhw8fEhwcTEhICDdu3ODmzZuUK1eOMWPGMHr06H9dAEnXSBn4B9999x2zZ88mKCgIKysrpeMIIbRo+/btjBw5UhYQy6bY2NiMjYBOnDhBQkIC9evXx8HBAQcHB6ysrPJMAfgnSUlJ7Nu3j4MHD+Ln58ejR48yvmZoaIharSYtLS3j31WvXh07Ozv69OlD9+7d89xCV1IG/uLZs2fUqFGD4cOH88MPPygdRwihZWq1moEDB3L8+HECAwOpVq2a0pHyjNevX3P48GG8vLw4deoUKSkp2NnZ0bt3b3r16oWlpaXSEXPMy5cv8ff358mTJyQmJqKnp0ehQoWoXLkyDRs21GgBJF0iZeAvPv/8cw4ePMjt27cpWbKk0nGEEDkgJiYGGxsbzM3NuXjx4v98F/fq1Sv8/f0JDAzk9evXJCcnY2BgQJEiRahVqxZ2dnZUq1YtT78T/jdRUVEcPHgQLy8vzp07R3p6Os2bN8fBwYFevXpRpUoVpSMKLZAy8Cd+fn40btyYtWvXMnbsWKXjCCFy0LVr12jZsiUzZ85k8eLFf/u6v78/69ev59y5c9y/fx8AY2NjSpYsiaGhIenp6bx9+5ZXr14BUKJECRo3bsyIESPo2bNnjj2znhseP36Mt7c3Xl5eGRsBtW7dOmMjoHLlyimcUGiblIE/qNVqWrZsSWxsLNevX8fAwEDpSEKIHLZ06VK+/vprzpw5wyeffEJaWhq7du1i3bp1+Pr6Ur58edq1a5dx81iVKlX+dlf4y5cvCQ0NJSQkhGvXruHn54e5uTmjR49m7NixmJubK/Tqsub+/fsZqwD+8ssvGBoavrcRkKze+GGTMvCHPXv2MHDgQM6cOUO7du2UjiOEyAXp6el06NCB0NBQ9u7dy+TJkwkICKBFixb069ePVq1aZfk5+Nu3b7N3716OHj2KoaEhq1evZsiQITp5CSE8PDyjALzbCKhjx44ZGwEVL15c6Ygil0gZ4PfHYmrWrImdnR3e3t5KxxFC5KKHDx9Sq1YtkpOTqVatGgsXLqRevXrZHvfNmze4uLhw9OhROnfuzObNmxV/zEytVhMcHJyxEdCtW7cwNjamc+fOODg48Nlnn2llIyCR90gZAObPn8/SpUsJCQnBwsJC6ThCiFwSHx9Pr169OHPmDCNGjMDJyUnrq42eP3+ehQsXoqenx+nTp7G2ttbq+P+LWq0mICAg4wxAeHg4xYoVo2vXrjg4OOTYRkAib8n3ZeDhw4fUrFmTyZMny57nQuQj8fHx2NvbExAQwOrVq2natGmOzfXq1SvGjh3L48ePOXPmTI5vha5SqfDx8cnYCOj+/fuULFmSHj164ODgQLt27WSJdfGefF8G+vfvz4ULFwgPD5fTY0LkEykpKXTp0oWrV6+yadOmXHm3/vbtW7744gsiIyO5dOkStWvX1ur46enpXLp0KWMjoCdPnlC2bFl69uyJg4ODYhsBibwhT5eBJ0+e4O/vj7+/P36+vjy8f5+kpCTUajWFChWiQqVK2NrZYWdnh62tLZUqVXrvJp5Lly7RqlUrduzYwbBhwxR8JUKI3OTs7IyLiwubNm2icePGuTZvTEwMw4YNo1ChQhk37GVHamoq58+fx8vLC29vb168eEHFihUzVgFs3ry5TmwEJHRfnisDycnJeHp6sn7tWq5euwaAWdGi2JibU71kSYz/aL6Jqak8ePOGwGfPeB4bC0ADa2vGjh/PgAEDKFSoEI0aNcLAwIBr167p/CYSQgjt8PPzo2nTpowZMwYnJ6dcnz8sLIz+/fszc+ZMFi1alOXjk5KSMjYCOnz4MK9fv6Z69eoZBUBXNwISui3PlIGkpCS+/fZb1q1Zw8tXr2htYcEQGxuaVKpERVPT/3xs52lsLL6Rkey6cYPT4eEUMzGhSdOmnD59mqtXr9KsWbNcfCVCCKUkJydja2uLSqVi165dip0237BhA5s2bcLHxydT9w/Ex8dz8uRJvLy8MjYCqlWrVsZGQNbW1jr56KLIO/JEGbh27Rojhg3j7t27jLC15XM7O2pouADG/dev2eHnx0YfHwyNjDhw8CCffvqplhMLIXTRDz/8wNSpU/Hw8KBmzZqK5UhNTWXgwIGULFmSK1eu/OP3xMbGcvTo0YyNgBITE7G2tn5vIyAhtEWny0BaWhpff/01y5cvp0GFCqzr2pVaZcpoZez7r18z4fBhLt27h6OjI6tXr6ZQoUJaGVsIoXvUajU1a9bEwsKCZcuWKR2HM2fO8OWXXxIYGJhxA+OrV68yNgI6ffo0KSkpNGrUKGMjoI8++kjh1OJDpbNlICkpif79+nHs2DGc27ZlfLNmGGj5RhiVSsV2f3+cf/qJps2bc+jwYXmiQIgP1JkzZ2jfvj3bt2/Hzs5OozESEhLw9PTk7NmzREREkJiYSOnSpbGwsMDe3h57e/tMX3pIS0ujY8eOdOzYkRYtWuDl5cX58+dJT0+nRYsWGRsBVa5cWaOsQmSFTpaBlJQUenTvzvmzZ3Hr04cONWrk6Hy/PHhAPw8P6lpbc/rMGYoUKZKj8wkhcp+DgwM3b97Ey8tLo+vrERERjB8/nsjIyH/9nv3791OrVq1Mj7lhwwY2btyIWq2mbdu2ODg4yEZAQhE6VwbUajVDhwxh39697B0wgLa5tCKgX2Qk3X/8kY/btOHY8eNyN64QHxCVSoWpqSmff/45o0aNyvLxMTEx9OnTh6dPnwJQpkwZhg8fjqWlJfHx8fj5+XHo0CFcXV2zVAYePXrEZ599xu7duxkwYECWcwmhLTq3NZ+Hhwfuu3axpVevXCsCAHYVK+LWpw8O7u6sXbuWiRMn5trcQoicFR4eztu3b6lbt65Gx+/YsSOjCJiYmLB7927Kli2b8fV27doxatSoLO92WrFiRUxNTblz545GuYTQFp16+/vs2TPGjxtHr7p16VO/fq7P3+6jjxjdqBGzZs6U/zmF+ID4+/sDaLzq38mTJzM+HjJkyHtF4B0zMzNMTU2zNK6enh61a9fOyCeEUnSqDIx1ckI/LY1lnToplmHep59SxtiYkcOHo1KpFMshhNCe69evU6lSpSz/sYbfbxr8830CDRs21GY0rKyspAwIxelMGQgICMD74EGWtG+PmYI38BU1MmJV585cunKFs2fPKpZDCKE9L168oLSGa5PExcW997mm4/ybMmXKEB0drdUxhcgqnSkD69evp0Lx4vSsU0fpKLSpXp065uasX7dO6ShCCC1ISkqiYMGCGh3718eNX7x4oY1IGQoWLJixp4oQStGJGwjfvHnD7l27mJKNtQSi3r5lw7VrnL59mwevX5OmUlG2aFFaVK2KU9Om1DM3z/RYenp6fG5ry7QjR3j06BGVKlXSKJMQQjfo6+trfNnP2NiYihUrZlwquH79Ok2aNNFaNpVKhb6+viwnLBSlE2cG9uzZQ2pqKkM1vBZ35f59mqxbx8rLlwl+/py3KSkkpaXx4M0bdgcG0nrTJjb+salRZvWpX58iBQuyfft2jTIJIXSHsbExiYmJGh9vb2+f8bGbmxtRUVF/+57o6GhiYmKyPHZiYiKFCxfWOJsQ2qATZeDy5cs0qFCBshqs/vc4JoZBHh68/uN/9GaVK7Ozb1+8Bg9mcIMGAKjUar46eZLT4eGZHtfEyIjmlStz5fLlLGcSQugWS0tL7t27p/HZgeHDh2csBBQXF8fAgQNxd3fHx8eHc+fO4eLiQteuXTMeP8yKO3fuYGlpqVEuIbRFJy4T+Pv60uofHtXJjNVXrvAmKQkASzMzDg4ditEfz/q2++gjVGo1uwMDUQPzz5zJ0mqGDcqXZ4ufH2q1Wk7hCZGH2dra8vbtWx48eEC1atWyfLypqSkbNmzIWIHw+fPnuLi4aCVbaGgobdq00cpYQmhK8TMDsbGxhN+5g0358hodf+y33zI+dmzSJKMIvDP+T9sTh0RFcf/Vq0yPbVOuHNGvX/Pw4UONsgkhdMO7xwFDQkI0HsPCwgIvLy+mT59Ow4YNMTU1xdDQEHNzc1q0aMHixYuxyOJCaUlJSURERGRqG2MhcpLiZwZCQkJQq9XUzcINfu/EJSfzODY24/N/ukmwdpkyGBYoQOofpwd/e/GCqiVLZmr8d+PdvHmTKlWqZDmfEEI3lCxZEgsLC3x9fencubPG4xgbGzN06FCGDh2qlVwBAQGkp6fTqFEjrYwnhKYUPzPw9u1bAIprsH1wXHLye5+XMjb+2/fo6elR8k//PvYvx/wX0z8yvcsohMi7hgwZwokTJ/62boCS9u/fj5WVFTY2NkpHEfmc4mUgJSUFgIIaPFJoYmT03ucvExL+9j1qtZpXf/r3xf5yzH95lyk5CwVCCKGbRo8eTUpKCocPH1Y6CvD78uvnz59n3Lhxck+SUJziZeDdQiDJ6elZPtbEyIgKxYplfH7z2bO/fc9vL15kXCIAqJWF1cPeZTLKQoEQQuim8uXL07NnT/bu3Uu6Br9vtG3fvn0ULlyYIUOGKB1FCOXLQLE//pi/0fAZ4M/+tF3o1l9/JSUt7b2vr/vll4yPrcqUyfT9An/OVOxPhUMIkXdNmzaN+/fv8+OPPyqa4+7du+zcuZOxY8f+bYVDIZSgeBmoU6cOBQoUIEiD53MBJrVokXFtP+zlS3r8+COHQ0I4e+cOEw8fxv369YzvnduuXZbGDvrjTEN9BXZQFEJoX+PGjfnyyy9Zs2YNd+/eVSRDeno6c+fOpUqVKsyfP1+RDEL8lZ5aBxbErlO7Nk2LFuX7Ll00Ov7ivXsM2buXmD/WG/irAnp6LOzQgXF/eswwMxadO4dbSAjPoqLkmp4QH4iEhASsra0pUqQIO3bswMAgdx+qcnV1ZdWqVVy6dIkWLVrk6txC/BvFzwwA2DVuTODz5xof36paNX4dN47JLVpgVaYMRQwNMdLXp3Lx4gywtua8o2OWiwDAjadPsWvUSIqAEB8QY2Njdu7cSXBwMHPnzs3VrcrPnDnD6tWrmTZtmhQBoVMUX2cA4OOPP8bd3Z3ImBgqarDfOEBZExPmt2/P/PbttZLpTWIilx88YO7IkVoZTwihO5o3b467uzsDBgzA0NCQuXPnoq/hJmmZdfbsWWbOnEnfvn1ZunRpjs4lRFbpxJmBfv36UcTYmB3+/kpHybDnxg1SVSqGDx+udBQhRA7o168fbm5uHDp0iKlTp2q0yVBmqFQqdu3axdSpU+nRowc7d+7M8eIhRFbpRBkwMTFh6LBhuAUG/u1pACWo1Wq2+fvj0KsX5hqsjCiEyBsGDx7MgQMH8Pf3p2fPnpw/f16r40dGRjJq1Ci+/fZbxo0bx+7duzMepxZCl+hEGQBwcnIiKjYWj6AgpaNwPCyMOy9e4DR2rNJRhBA5rFu3bgQHB9O4cWMmTpzIjBkzuHPnTrbGjImJwdXVFQcHB6Kiojh79iyrV6+WMwJCZ+nE0wTvDBk8mKPe3vzyxReUU+jZ/jeJiTTbuJH6TZpw4uRJuXlQiHxCrVbj7u7OjBkzePbsGY0aNaJv37588sknmXo3r1arCQkJYe/evZw4cYL09HRGjx7Nt99+K2sJCJ2nU2Xg1atXWNWqhU3Jknj076/IH+KxBw9yNCKCW8HBVKpUKdfnF0IoKyUlhYMHD7J27VouXbqEoaEhlpaWWFlZUbt2bczMzChYsCBpaWnEx8dz+/ZtQkJCCA0NJSYmhooVK+Lk5MTnn39OWQ23Zhcit+lUGQA4dOgQPXr0YGWXLoyws8vduUNCGLZvH1u3buXzzz/P1bmFELonJCSE8+fP4+/vj6+vL6GhoX9byrhChQrY2dlhZ2dHkyZN+OSTT+RygMhzdK4MwO/3D2zevJkdvXvTzcoqV+a8cPcufffsoVv37uzdt08uDwgh/iY5OZm3b9+SlJSEoaEhxsbGFC1aVOlYQmSbTpaB9PR0Bg0ciJeXF5t69MChXr0cne+n27cZun8/H7duzeEjR2RjIiGEEPmKTiw69Ff6+vq479qFkZERo9zdCXr2jK/atKGQoaFW50lLT2fVlSt8e+ECn332GXv37ZMiIIQQIt/RyTMD76hUKr777jvmzZ1LtRIlWN+tG7YVK2pl7JDnzxl7+DBBT58yY8YMFixYgKGWy4YQQgiRF+h0GXjn1q1bjBg2jIDAQPrUq8coOzvsKlbU6Lp+0NOnuPr5sSswkI8++ogdbm40btw4B1ILIYQQeUOeKAMAaWlprFu3jtUrV3LvwQPqly/PUBsbGleqRO0yZTD8l7t309LTCX/5Er/ISHYFBeHz4AHlzc0ZO348U6dOpdAf2x8LIYQQ+VWeKQPvpKenc+rUKdavW8eJkydRqVQYGRpS19wci+LFM+4rSEpN5UFsLDefPiUhJQWAT9q2Zdz48XTt2lUuCQghhBB/yHNl4M/evn1LYGAg/v7++Pn58fD+fZKSklCr1RQuXJjyFStia2uLnZ0dDRo0wFTDHRGFEEKID1meLgNCCCGEyD6d2ahICCGEEMqQMiCEEELkc1IGhBBCiHxOyoAQQgiRz0kZEEIIIfI5KQNCCCFEPidlQAghhMjnpAwIIYQQ+ZyUASGEECKfkzIghBBC5HNSBoQQQoh8TsqAEEIIkc9JGRBCCCHyuf8DvOixDxI3p8wAAAAASUVORK5CYII=",
|
| 109 |
+
"text/plain": [
|
| 110 |
+
"<Figure size 640x480 with 1 Axes>"
|
| 111 |
+
]
|
| 112 |
+
},
|
| 113 |
+
"metadata": {},
|
| 114 |
+
"output_type": "display_data"
|
| 115 |
+
}
|
| 116 |
+
],
|
| 117 |
+
"source": [
|
| 118 |
+
"import networkx as nx\n",
|
| 119 |
+
"import matplotlib.pyplot as plt\n",
|
| 120 |
+
"from rdkit import Chem\n",
|
| 121 |
+
"\n",
|
| 122 |
+
"# Example molecule\n",
|
| 123 |
+
"mol = Chem.MolFromSmiles(\"C1CCN(C1)C(=O)N\") \n",
|
| 124 |
+
"Chem.rdDepictor.Compute2DCoords(mol)\n",
|
| 125 |
+
"\n",
|
| 126 |
+
"# Define colors for atom types\n",
|
| 127 |
+
"atom_colors = {\n",
|
| 128 |
+
" 6: \"lightgray\", # Carbon\n",
|
| 129 |
+
" 8: \"lightcoral\", # Oxygen\n",
|
| 130 |
+
" 7: \"lightblue\", # Nitrogen\n",
|
| 131 |
+
" 16: \"khaki\", # Sulfur\n",
|
| 132 |
+
" 17: \"lightgreen\", # Chlorine\n",
|
| 133 |
+
" 1: \"whitesmoke\", # Hydrogen\n",
|
| 134 |
+
"}\n",
|
| 135 |
+
"default_color = \"plum\"\n",
|
| 136 |
+
"\n",
|
| 137 |
+
"# Convert RDKit Mol → NetworkX graph\n",
|
| 138 |
+
"G = nx.Graph()\n",
|
| 139 |
+
"for atom in mol.GetAtoms():\n",
|
| 140 |
+
" idx = atom.GetIdx()\n",
|
| 141 |
+
" pos = mol.GetConformer().GetAtomPosition(idx)\n",
|
| 142 |
+
" G.add_node(\n",
|
| 143 |
+
" idx,\n",
|
| 144 |
+
" label=atom.GetSymbol(),\n",
|
| 145 |
+
" color=atom_colors.get(atom.GetAtomicNum(), default_color),\n",
|
| 146 |
+
" pos=(pos.x, pos.y) # store RDKit 2D coords\n",
|
| 147 |
+
" )\n",
|
| 148 |
+
"for bond in mol.GetBonds():\n",
|
| 149 |
+
" G.add_edge(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), order=bond.GetBondTypeAsDouble())\n",
|
| 150 |
+
"\n",
|
| 151 |
+
"# Extract positions\n",
|
| 152 |
+
"pos = {n: (data[\"pos\"][0], data[\"pos\"][1]) for n, data in G.nodes(data=True)}\n",
|
| 153 |
+
"\n",
|
| 154 |
+
"# Draw nodes\n",
|
| 155 |
+
"node_colors = [G.nodes[n][\"color\"] for n in G.nodes]\n",
|
| 156 |
+
"nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=800, edgecolors=\"k\")\n",
|
| 157 |
+
"\n",
|
| 158 |
+
"# Draw edges with bond order as width\n",
|
| 159 |
+
"# edge_widths = [1.5 * G[u][v][\"order\"] for u, v in G.edges()]\n",
|
| 160 |
+
"nx.draw_networkx_edges(G, pos)\n",
|
| 161 |
+
"\n",
|
| 162 |
+
"# Draw atom labels\n",
|
| 163 |
+
"labels = {n: G.nodes[n][\"label\"] for n in G.nodes}\n",
|
| 164 |
+
"nx.draw_networkx_labels(G, pos, labels, font_size=12, font_weight=\"bold\")\n",
|
| 165 |
+
"\n",
|
| 166 |
+
"plt.axis(\"off\")\n",
|
| 167 |
+
"plt.gca().set_aspect(\"equal\", \"box\") # keep proportions\n",
|
| 168 |
+
"plt.show()\n"
|
| 169 |
+
]
|
| 170 |
+
},
|
| 171 |
+
{
|
| 172 |
+
"cell_type": "code",
|
| 173 |
+
"execution_count": null,
|
| 174 |
+
"metadata": {},
|
| 175 |
+
"outputs": [],
|
| 176 |
+
"source": []
|
| 177 |
+
}
|
| 178 |
+
],
|
| 179 |
+
"metadata": {
|
| 180 |
+
"kernelspec": {
|
| 181 |
+
"display_name": "spec",
|
| 182 |
+
"language": "python",
|
| 183 |
+
"name": "python3"
|
| 184 |
+
},
|
| 185 |
+
"language_info": {
|
| 186 |
+
"codemirror_mode": {
|
| 187 |
+
"name": "ipython",
|
| 188 |
+
"version": 3
|
| 189 |
+
},
|
| 190 |
+
"file_extension": ".py",
|
| 191 |
+
"mimetype": "text/x-python",
|
| 192 |
+
"name": "python",
|
| 193 |
+
"nbconvert_exporter": "python",
|
| 194 |
+
"pygments_lexer": "ipython3",
|
| 195 |
+
"version": "3.11.7"
|
| 196 |
+
},
|
| 197 |
+
"orig_nbformat": 4
|
| 198 |
+
},
|
| 199 |
+
"nbformat": 4,
|
| 200 |
+
"nbformat_minor": 2
|
| 201 |
+
}
|