yzhouchen001 commited on
Commit
ca910dd
·
verified ·
1 Parent(s): 1dc5a31

Delete notebooks

Browse files
notebooks/UMAP_spectra_embeddings.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/diff_rankers.ipynb DELETED
@@ -1,221 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 15,
6
- "id": "b1c8f5ac",
7
- "metadata": {},
8
- "outputs": [],
9
- "source": [
10
- "import pickle\n",
11
- "import numpy as np\n",
12
- "import pandas as pd\n",
13
- "import matplotlib.pyplot as plt"
14
- ]
15
- },
16
- {
17
- "cell_type": "code",
18
- "execution_count": 2,
19
- "id": "2856583d",
20
- "metadata": {},
21
- "outputs": [],
22
- "source": [
23
- "result_paths = [\"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/main_result/result_MassSpecGym_retrieval_candidates_mass.pkl\",\n",
24
- "\"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/result_top5.pkl\",\n",
25
- "\"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/result_softmax05.pkl\",\n",
26
- "\"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/result_geom1e-6.pkl\"]\n",
27
- "\n",
28
- "methods = ['standard', 'top5', 'softmax', 'geom']"
29
- ]
30
- },
31
- {
32
- "cell_type": "code",
33
- "execution_count": 4,
34
- "id": "ad1083b0",
35
- "metadata": {},
36
- "outputs": [],
37
- "source": [
38
- "result_dict = {}\n",
39
- "for p, m in zip(result_paths, methods):\n",
40
- " with open(p, 'rb') as f:\n",
41
- " result_dict[m] = pickle.load(f)"
42
- ]
43
- },
44
- {
45
- "cell_type": "code",
46
- "execution_count": 9,
47
- "id": "42f87b88",
48
- "metadata": {},
49
- "outputs": [
50
- {
51
- "data": {
52
- "text/html": [
53
- "<div>\n",
54
- "<style scoped>\n",
55
- " .dataframe tbody tr th:only-of-type {\n",
56
- " vertical-align: middle;\n",
57
- " }\n",
58
- "\n",
59
- " .dataframe tbody tr th {\n",
60
- " vertical-align: top;\n",
61
- " }\n",
62
- "\n",
63
- " .dataframe thead th {\n",
64
- " text-align: right;\n",
65
- " }\n",
66
- "</style>\n",
67
- "<table border=\"1\" class=\"dataframe\">\n",
68
- " <thead>\n",
69
- " <tr style=\"text-align: right;\">\n",
70
- " <th></th>\n",
71
- " <th>1</th>\n",
72
- " <th>5</th>\n",
73
- " <th>20</th>\n",
74
- " </tr>\n",
75
- " </thead>\n",
76
- " <tbody>\n",
77
- " <tr>\n",
78
- " <th>standard</th>\n",
79
- " <td>0.431533</td>\n",
80
- " <td>0.755924</td>\n",
81
- " <td>0.928913</td>\n",
82
- " </tr>\n",
83
- " <tr>\n",
84
- " <th>top5</th>\n",
85
- " <td>0.237810</td>\n",
86
- " <td>0.538164</td>\n",
87
- " <td>0.807986</td>\n",
88
- " </tr>\n",
89
- " <tr>\n",
90
- " <th>softmax</th>\n",
91
- " <td>0.112041</td>\n",
92
- " <td>0.352301</td>\n",
93
- " <td>0.730178</td>\n",
94
- " </tr>\n",
95
- " <tr>\n",
96
- " <th>geom</th>\n",
97
- " <td>0.192413</td>\n",
98
- " <td>0.378332</td>\n",
99
- " <td>0.502335</td>\n",
100
- " </tr>\n",
101
- " </tbody>\n",
102
- "</table>\n",
103
- "</div>"
104
- ],
105
- "text/plain": [
106
- " 1 5 20\n",
107
- "standard 0.431533 0.755924 0.928913\n",
108
- "top5 0.237810 0.538164 0.807986\n",
109
- "softmax 0.112041 0.352301 0.730178\n",
110
- "geom 0.192413 0.378332 0.502335"
111
- ]
112
- },
113
- "execution_count": 9,
114
- "metadata": {},
115
- "output_type": "execute_result"
116
- }
117
- ],
118
- "source": [
119
- "topk = [1,5,20]\n",
120
- "result_data = []\n",
121
- "for m in result_dict:\n",
122
- " curr_result = [] \n",
123
- " for k in topk:\n",
124
- " curr_result.append(len(result_dict[m][result_dict[m]['rank'] <= k])/len(result_dict[m]))\n",
125
- " result_data.append(curr_result)\n",
126
- "\n",
127
- "result_df = pd.DataFrame(result_data, index=result_dict.keys(), columns=topk)\n",
128
- "result_df"
129
- ]
130
- },
131
- {
132
- "cell_type": "code",
133
- "execution_count": 18,
134
- "id": "252767dd",
135
- "metadata": {},
136
- "outputs": [],
137
- "source": [
138
- "def get_target_score(scores, labels):\n",
139
- " return np.array(scores)[np.array(labels)][0]\n",
140
- "\n",
141
- "def get_avg_cand_scores(scores, labels):\n",
142
- " return np.mean(np.array(scores)[~np.array(labels)])\n",
143
- "\n",
144
- "for m, r in result_dict.items():\n",
145
- " r['target_score'] = r.apply(lambda row: get_target_score(row['scores'], row['labels']), axis=1)\n",
146
- " r['avg_cand_scores'] = r.apply(lambda row: get_avg_cand_scores(row['scores'], row['labels']), axis=1)\n"
147
- ]
148
- },
149
- {
150
- "cell_type": "code",
151
- "execution_count": 40,
152
- "id": "26b609ca",
153
- "metadata": {},
154
- "outputs": [
155
- {
156
- "data": {
157
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAA90AAAPdCAYAAACXzguGAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAviJJREFUeJzs3X98jvX////7zHbuh53nkP3KsEwYRlRMkSKj8VLxLuWFinzUVCiT10uSfiglFPFK5UfZSyp6vbMyC6MXS6jlR1I0pmZWaZuf22zH94++O96dNjKdx879uF0vl/NS53E8j+N4HGe79Djv5/HLwzAMQwAAAAAAwOXquLsAAAAAAABqKkI3AAAAAAAWIXQDAAAAAGARQjcAAAAAABYhdAMAAAAAYBFCNwAAAAAAFiF0AwAAAABgkbruLqA6KCkpUVZWlgICAuTh4eHucgAAcDnDMHT8+HGFhYWpTp2//ps8vRMAUNNdbO8kdF+ErKwshYeHu7sMAAAsd/jwYTVu3Pgvr4feCQCoLf6sdxK6L0JAQICk3z9Mu93u5moAAHC9/Px8hYeHmz3vr6J3AgBquovtnYTui1B6WpzdbueLAwCgRnPVqeD0TgBAbfFnvZMbqQEAAAAAYBFCNwAAAAAAFiF0AwAAAABgEa7pBoAqori4WEVFRe4uAzWUl5eXPD093V0GALgUvRNWclXvJHQDgJsZhqHs7Gzl5ua6uxTUcIGBgQoJCeG52QCqPXonKosreiehGwDcrPRLQ1BQkPz8/AhEcDnDMHTq1Cnl5ORIkkJDQ91cEQD8NfROWM2VvZPQDQBuVFxcbH5paNiwobvLQQ3m6+srScrJyVFQUBCnmgOotuidqCyu6p3cSA0A3Kj0OjQ/Pz83V4LaoPTvjOsfAVRn9E5UJlf0TkI3AFQBnBaHysDfGYCahP+noTK44u+M0A0AAAAAgEUI3QAAAAAAWIQbqQFAFZX0QValbi9uYFiFxvfo0UMdOnTQ7NmzrSnoElTFmgAAlacyeyd9ExeLI90AALcqLCx0dwmWqun7BwCoXDW9r9TE/SN0AwAq7J577tHGjRs1Z84ceXh4yMPDQwcPHlRxcbFGjBihiIgI+fr6qmXLlpozZ06ZZW+99VY9++yzCgsLU8uWLSVJW7ZsUYcOHeTj46Orr75aH374oTw8PJSenm4uu3v3bvXt21f16tVTcHCwhg4dql9++eWCNZXntddeU4sWLeTj46Pg4GANGjTInFdSUqIZM2YoMjJSNptNTZo00bPPPmvO37Vrl2666Sb5+vqqYcOGGjVqlE6cOPGn+3f48GHdcccdCgwMVIMGDTRgwIDz1gcAqFnom7W7b3J6OQCgwubMmaPvvvtObdu21bRp0yRJjRo1UklJiRo3bqz33ntPDRs21JYtWzRq1CiFhobqjjvuMJdft26d7Ha7UlJSJEn5+fnq37+/brnlFiUmJurQoUMaO3as0zZzc3N10003aeTIkZo1a5ZOnz6tiRMn6o477tD69evPW9O5tm/frocfflhvv/22unbtqmPHjumzzz4z50+aNEkLFy7UrFmzdP311+vIkSP69ttvJUknT55UbGysYmJitG3bNuXk5GjkyJEaM2aMFi9efN79KyoqMpf77LPPVLduXT3zzDPq06ePdu7cKW9v77/+HwUAUGXRN2t33yR0AwAqzOFwyNvbW35+fgoJCTGne3p66qmnnjLfR0REKC0tTStWrHD68uDv76833njDbJoLFiyQh4eHFi5cKB8fH0VFRemnn37S/fffby4zd+5cXXXVVXruuefMaW+99ZbCw8P13Xff6corryy3pnNlZmbK399f/fr1U0BAgJo2baqrrrpKknT8+HHNmTNHc+fO1fDhwyVJzZs31/XXXy9JSkxM1JkzZ7R06VL5+/ubdfXv318vvPCCgoODy92/d955RyUlJXrjjTfMR48sWrRIgYGBSk1NVe/evSv6nwAAUI3QN2t33yR0AwBcat68eXrrrbeUmZmp06dPq7CwUB06dHAa065dO6dfqfft26fo6Gj5+PiY06699lqnZb7++mtt2LBB9erVK7PNAwcO6Morr7yo+m6++WY1bdpUV1xxhfr06aM+ffrotttuk5+fn/bu3auCggL17Nmz3GX37t2r9u3bm18cJOm6665TSUmJ9u3bZ355OHf/vv76a+3fv18BAQFO6ztz5owOHDhwUXUDAGom+mbN75uEbgCAyyxfvlyPPfaYZs6cqZiYGAUEBOjFF1/U1q1bncb9sflerBMnTpi/jJ8rNDT0otcTEBCgL7/8UqmpqVq7dq2mTJmiqVOnatu2bfL19a1wXeU5d/9OnDihTp06admyZWXGlncqHwCgdqBv/q6m901CNwDgknh7e6u4uNhp2ubNm9W1a1c9+OCD5rSL+UW6ZcuWeuedd1RQUCCbzSZJ2rZtm9OYjh076oMPPlCzZs1Ut2757au8mspTt25d9erVS7169dKTTz6pwMBArV+/Xrfccot8fX21bt06jRw5ssxyrVu31uLFi3Xy5EnzC8LmzZtVp04d88Yv5enYsaPeffddBQUFyW63/2l9AICah75Ze/smdy8HAFySZs2aaevWrTp48KB++eUXlZSUqEWLFtq+fbuSk5P13Xff6YknnijzJaA8d999t0pKSjRq1Cjt3btXycnJeumllyTJvJYrPj5ex44d01133aVt27bpwIEDSk5O1r333mt+YSivpnOtXr1ar7zyitLT03Xo0CEtXbpUJSUlatmypXx8fDRx4kQlJCRo6dKlOnDggD7//HO9+eabkqQhQ4bIx8dHw4cP1+7du7VhwwY99NBDGjp0qHmKXHmGDBmiyy67TAMGDNBnn32mjIwMpaam6uGHH9aPP/5Y4c8eAFD90Ddrb98kdAMALsljjz0mT09PRUVFqVGjRsrMzNT/+3//T7fffrvuvPNOde7cWb/++qvTr/fnY7fb9dFHHyk9PV0dOnTQP//5T02ZMkWSzOvVwsLCtHnzZhUXF6t3795q166dxo4dq8DAQNWpU+e8NZ0rMDBQK1eu1E033aTWrVtrwYIF+ve//602bdpIkp544gk9+uijmjJlilq3bq0777xTOTk5kiQ/Pz8lJyfr2LFjuuaaazRo0CD17NlTc+fOveD++fn5adOmTWrSpIluv/12tW7dWiNGjNCZM2dqxC/4AIA/R9+svX3TwzAMw91FVHX5+flyOBzKy8urlv+RAVRdZ86cUUZGhiIiIpxuhgJp2bJluvfee5WXl+eya8Zquwv9vbm619E7AViF3lk++qY1XNE7uaYbAFAlLF26VFdccYUuv/xyff311+azRPniAABAWfTN6oPQDQCoErKzszVlyhRlZ2crNDRU//M//6Nnn33W3WUBAFAl0TerD0I3AKBKSEhIUEJCgrvLAACgWqBvVh/cSA0AAAAAAItUmdD9/PPPy8PDQ2PHjjWnnTlzRvHx8WrYsKHq1aungQMH6ujRo07LZWZmKi4uTn5+fgoKCtKECRN09uxZpzGpqanq2LGjbDabIiMjtXjx4krYIwAAAABAbVclQve2bdv0r3/9S9HR0U7Tx40bp48++kjvvfeeNm7cqKysLN1+++3m/OLiYsXFxamwsFBbtmzRkiVLtHjxYvN2+ZKUkZGhuLg43XjjjUpPT9fYsWM1cuRIJScnV9r+AQAAAABqJ7eH7hMnTmjIkCFauHCh6tevb07Py8vTm2++qZdfflk33XSTOnXqpEWLFmnLli36/PPPJUlr167VN998o3feeUcdOnRQ37599fTTT2vevHkqLCyUJC1YsEARERGaOXOmWrdurTFjxmjQoEGaNWuWW/YXAAAAAFB7uD10x8fHKy4uTr169XKavmPHDhUVFTlNb9WqlZo0aaK0tDRJUlpamtq1a6fg4GBzTGxsrPLz87Vnzx5zzLnrjo2NNddRnoKCAuXn5zu9AADA+dE7AQAon1tD9/Lly/Xll19q+vTpZeZlZ2fL29tbgYGBTtODg4OVnZ1tjvlj4C6dXzrvQmPy8/N1+vTpcuuaPn26HA6H+QoPD7+k/QMAoLagdwIAUD63he7Dhw/rkUce0bJly+Tj4+OuMso1adIk5eXlma/Dhw+7uyQAgJvdc889uvXWW91dRpVF7wQA/BF98/+47TndO3bsUE5Ojjp27GhOKy4u1qZNmzR37lwlJyersLBQubm5Tke7jx49qpCQEElSSEiIvvjiC6f1lt7d/I9jzr3j+dGjR2W32+Xr61tubTabTTab7S/vIwD8FcWplXvDR88esZW6PdQs9E4AVUFl9k76Ji6W24509+zZU7t27VJ6err5uvrqqzVkyBDz3728vLRu3TpzmX379ikzM1MxMTGSpJiYGO3atUs5OTnmmJSUFNntdkVFRZlj/riO0jGl6wAAoDoqKipydwkAAFQb7uybbgvdAQEBatu2rdPL399fDRs2VNu2beVwODRixAiNHz9eGzZs0I4dO3TvvfcqJiZGXbp0kST17t1bUVFRGjp0qL7++mslJydr8uTJio+PN39tHz16tH744QclJCTo22+/1WuvvaYVK1Zo3Lhx7tp1AKgR1qxZo+uvv16BgYFq2LCh+vXrpwMHDpjzu3btqokTJzot8/PPP8vLy0ubNm2SJB05ckRxcXHy9fVVRESEEhMT1axZM82ePfuC237rrbfUpk0b2Ww2hYaGasyYMea8l19+We3atZO/v7/Cw8P14IMP6sSJE+b8xYsXKzAwUMnJyWrdurXq1aunPn366MiRI+aY4uJijR8/3ty3hIQEGYZxwZoOHTqk/v37q379+vL391ebNm308ccfm/P37Nmjfv36yW63KyAgQN26dTM/r5KSEk2bNk2NGzeWzWZThw4dtGbNGnPZgwcPysPDQ++++65uuOEG+fj4aNmyZZKkN954Q61bt5aPj49atWql11577YJ1AgDcg77prDb1TbffvfxCZs2apX79+mngwIHq3r27QkJCtHLlSnO+p6enVq9eLU9PT8XExOjvf/+7hg0bpmnTppljIiIilJSUpJSUFLVv314zZ87UG2+8odhYTgcBgL/i5MmTGj9+vLZv365169apTp06uu2221RSUiJJGjJkiJYvX+7UdN99912FhYWpW7dukqRhw4YpKytLqamp+uCDD/T66687nb1Unvnz5ys+Pl6jRo3Srl279L//+7+KjIw059epU0evvPKK9uzZoyVLlmj9+vVKSEhwWsepU6f00ksv6e2339amTZuUmZmpxx57zJw/c+ZMLV68WG+99Zb++9//6tixY1q1atUF64qPj1dBQYE2bdqkXbt26YUXXlC9evUkST/99JO6d+8um82m9evXa8eOHbrvvvt09uxZSdKcOXM0c+ZMvfTSS9q5c6diY2P1t7/9Td9//73TNh5//HE98sgj2rt3r2JjY7Vs2TJNmTJFzz77rPbu3avnnntOTzzxhJYsWXLBWgEAlY++6axW9U0DfyovL8+QZOTl5bm7FAA1zOnTp41vvvnGOH36dJl5ZzesqdTXX/Xzzz8bkoxdu3YZhmEYOTk5Rt26dY1NmzaZY2JiYoyJEycahmEYe/fuNSQZ27ZtM+d///33hiRj1qxZ591OWFiY8c9//vOi63rvvfeMhg0bmu8XLVpkSDL2799vTps3b54RHBxsvg8NDTVmzJhhvi8qKjIaN25sDBgw4LzbadeunTF16tRy502aNMmIiIgwCgsLy50fFhZmPPvss07TrrnmGuPBBx80DMMwMjIyDEnG7NmzncY0b97cSExMdJr29NNPGzExMeVu50J/b67udfROAFapKr3zr6JvVv2+aRiu6Z1V+kg3AKDq+v7773XXXXfpiiuukN1uV7NmzSRJmZmZkqRGjRqpd+/e5ulcGRkZSktL05AhQyT9fp+OunXrOt1QMzIyUvXr1z/vNnNycpSVlaWePXued8ynn36qnj176vLLL1dAQICGDh2q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Hy9J8vDw0BNPPCE/Pz9zXnFxsbZu3aoOHTq4vEAAqC4SExOVk5OjsWPHursUAABqlC1btmjt2rUaO3asAgMDLdvOwYMHz3v27r///W8NHjzYsm2j5rro0P3VV19J+v1I965du+Tt7W3O8/b2Vvv27fXYY4+5vkIAqCYSExO1e/duQjcAAC62ZcsWPfXUU7rnnnssDd2l7rrrLt1yyy1O02JiYizfLmqmiw7dGzZskCTde++9mjNnjux2u2VFAQDcr6SkRIWFhVw6BACodTp27Ki///3v7i4DNUSFr+letGiRywL39OnTdc011yggIEBBQUG69dZbtW/fPqcxPXr0kIeHh9Nr9OjRTmMyMzMVFxcnPz8/BQUFacKECTp79qzTmNTUVHXs2FE2m02RkZFavHixS/YBQO1x/PhxjR07Vs2aNZPNZlNQUJBuvvlmffnll+rRo4eSkpJ06NAh8/9VpddWFxYWasqUKerUqZMcDof8/f3VrVs388fMUgcPHpSHh4deeuklvf7662revLlsNpuuueYabdu2rUw9H374odq2bSsfHx+1bdtWq1atKrful156SV27dlXDhg3l6+urTp066f333y8zzsPDQ2PGjNGyZcvUpk0b2Ww2rVmzRpK0Z88e3XTTTfL19VXjxo31zDPPqKSk5C9+ogAA/LmpU6dqwoQJkn6/qXNpnz148KDOnj2rp59+2uyZzZo10z/+8Q8VFBQ4raNZs2bq16+f1q5dqw4dOsjHx0dRUVFauXLlebd78uRJFRYWWrpvqB08jPKe/3UBJ0+e1PPPP69169YpJyenzJeuH3744aLX1adPHw0ePFjXXHONzp49q3/84x/avXu3vvnmG/n7+0v6PXRfeeWVmjZtmrmcn5+fGfyLi4vVoUMHhYSE6MUXX9SRI0c0bNgw3X///XruueckSRkZGWrbtq1Gjx6tkSNHat26dRo7dqySkpIUGxv7p3Xm5+fL4XAoLy+PI/xALTZkyBC9//77GjNmjKKiovTrr7/qv//9r+68804FBQUpISFBP/74o2bNmiVJqlevnm699Vb98ssvio6O1l133aUWLVro+PHjevPNN/XDDz/oiy++MO+HUXod2VVXXaXjx4/r/vvvl4eHh2bMmCEfHx/98MMP8vLykiStXbtWffv2VVRUlO677z79+uuvmjt3rho3bqwTJ0443dwsPDxcf/vb3xQVFaXCwkItX75cX3zxhVavXq24uDhznIeHh1q3bq1ffvlFY8aM0WWXXaauXbsqJCRE0dHROnv2rB555BH5+/vr9ddfl6+vr3bu3KmMjIxKu3kbrOPqXkfvBOAqO3fu1PPPP69///vfmjVrli677DJJ0m233ab4+HgtWbJEgwYN0o033qitW7dq6dKluvXWW51+jC79wTwnJ0ejR49WUFCQFi1apD179mjNmjW6+eabJf1fL65Xr55OnDghDw8PderUSc8++6x69+7tlv1H1XXRvc6ooMGDBxuhoaFGQkKCMWvWLGP27NlOr78iJyfHkGRs3LjRnHbDDTcYjzzyyHmX+fjjj406deoY2dnZ5rT58+cbdrvdKCgoMAzDMBISEow2bdo4LXfnnXcasbGxF1VXXl6eIcnIy8urwN4AqGkcDocRHx9/3vlxcXFG06ZNy0w/e/as+f+jUr/99psRHBxs3Hfffea0jIwMQ5LRsGFD49ixY+b0//znP4Yk46OPPjKndejQwQgNDTVyc3PNaWvXrjUklanh1KlTTu8LCwuNtm3bGjfddJPTdElGnTp1jD179jhNHzt2rCHJ2Lp1qzktJyfHcDgchiQjIyOj/A8E1Yqrex29E4Arvfjii2V6Tnp6uiHJGDlypNPYxx57zJBkrF+/3pzWtGlTQ5LxwQcfmNPy8vKM0NBQ46qrrjKnHTp0yOjdu7cxf/5843//93+N2bNnG02aNDHq1KljrF692rodRLV0sb3uoq/pLvXJJ58oKSlJ1113XUUX/VN5eXmSpAYNGjhNX7Zsmd555x2FhISof//+TndPT0tLU7t27RQcHGyOj42N1QMPPKA9e/boqquuUlpamnr16uW0ztjY2PPe7KigoMDplJT8/HxX7B6Aai4wMFBbt25VVlaWwsLCLno5T09PeXp6Svr9Ounc3FyVlJTo6quv1pdffllm/J133qn69eub77t16ybp/84kOnLkiNLT0/X444/L4XCY426++WZFRUXp5MmTTuvz9fU1//23335TcXGxunXrpn//+99ltn3DDTcoKirKadrHH3+sLl266NprrzWnNWrUSEOGDNFrr7120Z8DajZ6J4DK9vHHH0v6v6cslXr00Uf10ksvKSkpSTfeeKM5PSwsTLfddpv53m63a9iwYXrhhReUnZ2tkJAQNWnSRMnJyU7rGzp0qKKiovToo486nSEGXKwKX9Ndv379MqHYFUpKSjR27Fhdd911atu2rTn97rvv1jvvvKMNGzZo0qRJevvtt51uapCdne0UuCWZ77Ozsy84Jj8/X6dPny5Ty/Tp0+VwOMxXeHi4y/YTQPU1Y8YM7d69W+Hh4br22ms1derUi76kZsmSJYqOjpaPj48aNmyoRo0aKSkpyfyx8Y+aNGni9L40gP/222+SpEOHDkmSWrRoUWbZli1blpm2evVqdenSRT4+PmrQoIEaNWqk+fPnl7vt8h6TcujQoYveFmoveieAynbo0CHVqVNHkZGRTtNDQkIUGBho9stSkZGR8vDwcJp25ZVXSpLTZVnnatCgge69917t27dPP/74o2uKR61S4dD99NNPa8qUKTp16pRLC4mPj9fu3bu1fPlyp+mjRo1SbGys2rVrpyFDhmjp0qVatWqVDhw44NLt/9GkSZOUl5dnvg4fPmzZtgBUH3fccYd++OEHvfrqqwoLC9OLL76oNm3a6JNPPrngcu+8847uueceNW/eXG+++abWrFmjlJQU3XTTTeXejKz0qPi5jIrdgkOS9Nlnn+lvf/ubfHx89Nprr+njjz9WSkqK7r777nLX98ej4kBF0DsBuMu5QdoKpT8kHjt2zPJtoeap8OnlM2fO1IEDBxQcHKxmzZqZN/UpVd6pkn9mzJgxWr16tTZt2qTGjRtfcGznzp0lSfv371fz5s0VEhKiL774wmnM0aNHJf3+K1fpP0un/XGM3W4v9wumzWaTzWar8H4AqPlCQ0P14IMP6sEHH1ROTo46duyoZ599Vn379j1v03///fd1xRVXaOXKlU5jnnzyyUuqoWnTppKk77//vsy8c58A8cEHH8jHx0fJyclO/19btGhRhbZ3MdtC7UbvBGCl8nps06ZNVVJSou+//16tW7c2px89elS5ublmvyy1f/9+GYbhtK7vvvtOkv70hqClZ7Y1atToUncBtViFj3TfeuutevTRR/XYY49p0KBBGjBggNOrIgzD0JgxY7Rq1SqtX7++3NMaz5Weni7p9y++0u8Pqd+1a5dycnLMMSkpKbLb7eZ1iTExMVq3bp3TelJSUnjAPYCLVlxcXOZ07KCgIIWFhZnXsfr7+5d7ynbpkes/HlneunWr0tLSLqmW0NBQdejQQUuWLHHaXkpKir755psy2/bw8FBxcbE57eDBg/rwww8venu33HKLPv/8c6cfOH/++WctW7bskuoHAKCiSp9slJuba0675ZZbJEmzZ892Gvvyyy9LUpnrr7OyspzuaJ6fn6+lS5eaT0KSfu9v5/rpp5/01ltvKTo62swgQEVU+Ej3pR6ZKU98fLwSExP1n//8RwEBAeY12A6HQ76+vjpw4IASExN1yy23qGHDhtq5c6fGjRun7t27Kzo6WpLUu3dvRUVFaejQoZoxY4ays7M1efJkxcfHm7+4jx49WnPnzlVCQoLuu+8+rV+/XitWrFBSUpLL9gVAzXb8+HE1btxYgwYNUvv27VWvXj19+umn2rZtm2bOnClJ6tSpk959912NHz9e11xzjerVq6f+/furX79+WrlypW677TbFxcUpIyNDCxYsUFRUlE6cOHFJ9UyfPl1xcXG6/vrrdd999+nYsWN69dVX1aZNG6d1xsXF6eWXX1afPn109913KycnR/PmzVNkZKR27tx5UdtKSEjQ22+/rT59+jg9Mqxp06YXvQ4AAP6KTp06SZL++c9/avDgwfLy8lL//v01fPhwvf7668rNzdUNN9ygL774QkuWLNGtt97qdBM16ffrt0eMGKFt27YpODhYb731lo4ePep09ldCQoIOHDignj17KiwsTAcPHtS//vUvnTx5UnPmzKnUfUYNYv2N1M9PUrmvRYsWGYZhGJmZmUb37t2NBg0aGDabzYiMjDQmTJhQ5pbsBw8eNPr27Wv4+voal112mfHoo48aRUVFTmM2bNhgdOjQwfD29jauuOIKcxsXg8eeACgoKDAmTJhgtG/f3ggICDD8/f2N9u3bG6+99po55sSJE8bdd99tBAYGOj26q6SkxHjuueeMpk2bGjabzbjqqquM1atXG8OHD3d6vFfpI8NefPHFMtuXZDz55JNO0z744AOjdevWhs1mM6KiooyVK1eWWadhGMabb75ptGjRwrDZbEarVq2MRYsWGU8++aRxbguQdN5Hou3cudO44YYbDB8fH+Pyyy83nn76aePNN9/kkWE1CI8MA1DVPf3008bll19u1KlTx+w/RUVFxlNPPWVEREQYXl5eRnh4uDFp0iTjzJkzTss2bdrUiIuLM5KTk43o6GizJ7733ntO4xITE43u3bsbjRo1MurWrWtcdtllxm233Wbs2LGjMncV1cTF9joPw6jY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/BQSEmJO9/T01FNPPWW+j4iIUFpamlasWOH05cHf319vvPGG2TQXLFggDw8PLVy4UD4+PoqKitJPP/2k+++/31xm7ty5uuqqq/Tcc8+Z09566y2Fh4fru+++05VXXlluTefKzMyUv7+/+vXrp4CAADVt2lRXXXWVJOn48eOaM2eO5s6dq+HDh0uSmjdvruuvv16SlJiYqDNnzmjp0qXy9/c36+rfv79eeOEFBQcHl7t/77zzjkpKSvTGG2+Yz/tctGiRAgMDlZqaqt69e1f0PwEAoBqhb9buvknoBgC41Lx58/TWW28pMzNTp0+fVmFhoTp06OA0pl27dk6/Uu/bt0/R0dHy8fExp1177bVOy3z99dfasGGD6tWrV2abBw4c0JVXXnlR9d18881q2rSprrjiCvXp00d9+vTRbbfdJj8/P+3du1cFBQXq2bNnucvu3btX7du3N784SNJ1112nkpIS7du3z/zycO7+ff3119q/f78CAgKc1nfmzBkdOHDgouoGANRM9M2a3zcJ3QAAl1m+fLkee+wxzZw5UzExMQoICNCLL76orVu3Oo37Y/O9WCdOnDB/GT9XaGjoRa8nICBAX375pVJTU7V27VpNmTJFU6dO1bZt2+Tr61vhuspz7v6dOHFCnTp10rJly8qMLe9UPgBA7UDf/F1N75uEbgDAJfH29lZxcbHTtM2bN6tr16568MEHzWkX84t0y5Yt9c4776igoEA2m02StG3bNqcxHTt21AcffKBmzZqpbt3y21d5NZWnbt266tWrl3r16qUnn3xSgYGBWr9+vW655Rb5+vpq3bp1GjlyZJnlWrdurcWLF+vkyZPmF4TNmzerTp065o1fytOxY0e9++67CgoKkt1u/9P6gKqoODX5vPM8e8RWYiVA9UTfrL19k7uXAwAuSbNmzbR161YdPHhQv/zyi0pKStSiRQtt375dycnJ+u677/TEE0+U+RJQnrvvvlslJSUaNWqU9u7dq+TkZL300kuSZF7LFR8fr2PHjumuu+7Stm3bdODAASUnJ+vee+81vzCUV9O5Vq9erVdeeUXp6ek6dOiQli5dqpKSErVs2VI+Pj6aOHGiEhIStHTpUh04cECff/653nzzTUnSkCFD5OPjo+HDh2v37t3asGGDHnroIQ0dOtQ8Ra48Q4YM0WWXXaYBAwbos88+U0ZGhlJTU/Xwww/rxx9/rPBnDwCofuibtbhvGvhTeXl5hiQjLy/P3aUAqGFOnz5tfPPNN8bp06fdXUqF7du3z+jSpYvh6+trSDIyMjKMM2fOGPfcc4/hcDiMwMBA44EHHjAef/xxo3379uZyw4cPNwYMGFBmfZs3bzaio6MNb29vo1OnTkZiYqIhyfj222/NMd99951x2223GYGBgYavr6/RqlUrY+zYsUZJScl5azrXZ599Ztxwww1G/fr1DV9fXyM6Otp49913zfnFxcXGM888YzRt2tTw8vIymjRpYjz33HPm/J07dxo33nij4ePjYzRo0MC4//77jePHj//p/h05csQYNmyYcdlllxk2m8244oorjPvvv79Se8uF/t5c3evonTXP2Q1rzvsCKlN17Z30zerXNw3DNb3TwzAMw12Bv7rIz8+Xw+FQXl5ejTi9AUDVcebMGWVkZCgiIsLpZiiQli1bpnvvvVd5eXkuu2astrvQ35urex29s+bh9HJUFfTO8tE3reGK3sk13QCAKmHp0qW64oordPnll+vrr782nyXKFwcAAMqib1YfhG4AQJWQnZ2tKVOmKDs7W6Ghofqf//kfPfvss+4uCwCAKom+WX0QugEAVUJCQoISEhLcXQYAANUCfbP6qDJ3L3/++efl4eGhsWPHmtPOnDmj+Ph4NWzYUPXq1dPAgQN19OhRp+UyMzMVFxcnPz8/BQUFacKECTp79qzTmNTUVHXs2FE2m02RkZFavHhxJewRAAAAAKC2qxKhe9u2bfrXv/6l6Ohop+njxo3TRx99pPfee08bN25UVlaWbr/9dnN+cXGx4uLiVFhYqC1btmjJkiVavHixpkyZYo7JyMhQXFycbrzxRqWnp2vs2LEaOXKkkpPPfzMQAAAAAABcwe2h+8SJExoyZIgWLlyo+vXrm9Pz8vL05ptv6uWXX9ZNN92kTp06adGiRdqyZYs+//xzSdLatWv1zTff6J133lGHDh3Ut29fPf3005o3b54KCwslSQsWLFBERIRmzpyp1q1ba8yYMRo0aJBmzZrllv0FAAAAANQebg/d8fHxiouLU69evZym79ixQ0VFRU7TW7VqpSZNmigtLU2SlJaWpnbt2jk9WD02Nlb5+fnas2ePOebcdcfGxprrKE9BQYHy8/OdXgAA4PzonQAAlM+toXv58uX68ssvNX369DLzsrOz5e3trcDAQKfpwcHBys7ONsf8MXCXzi+dd6Ex+fn5On36dLl1TZ8+XQ6Hw3yFh4df0v4BAFBb0DsBACif20L34cOH9cgjj2jZsmVV7qH2kyZNUl5envk6fPiwu0sCAKBKo3cCAFA+tz0ybMeOHcrJyVHHjh3NacXFxdq0aZPmzp2r5ORkFRYWKjc31+lo99GjRxUSEiJJCgkJ0RdffOG03tK7m/9xzLl3PD969Kjsdvt5Hxxvs9lks9n+8j4CwF9RnFq5N3z07BFbqdurbu655x7l5ubqww8/dHcpVRK9E0BVUJm9k755YfTN/+O2I909e/bUrl27lJ6ebr6uvvpqDRkyxPx3Ly8vrVu3zlxm3759yszMVExMjCQpJiZGu3btUk5OjjkmJSVFdrtdUVFR5pg/rqN0TOk6AAAAAACwituOdAcEBKht27ZO0/z9/dWwYUNz+ogRIzR+/Hg1aNBAdrtdDz30kGJiYtSlSxdJUu/evRUVFaWhQ4dqxowZys7O1uTJkxUfH2/+2j569GjNnTtXCQkJuu+++7R+/XqtWLFCSUlJlbvDAAC4UFFRkby8vNxdBgAA1YI7+6bb715+IbNmzVK/fv00cOBAde/eXSEhIVq5cqU539PTU6tXr5anp6diYmL097//XcOGDdO0adPMMREREUpKSlJKSorat2+vmTNn6o033lBsLKeDAMBfsWbNGl1//fUKDAxUw4YN1a9fPx04cMCc37VrV02cONFpmZ9//lleXl7atGmTJOnIkSOKi4uTr6+vIiIilJiYqGbNmmn27NkX3PZbb72lNm3ayGazKTQ0VGPGjDHnvfzyy2rXrp38/f0VHh6uBx98UCdOnDDnL168WIGBgUpOTlbr1q1Vr1499enTR0eOHDHHFBcXa/z48ea+JSQkyDCMC9Z06NAh9e/fX/Xr15e/v7/atGmjjz/+2Jy/Z88e9evXT3a7XQEBAerWrZv5eZWUlGjatGlq3LixbDabOnTooDVr1pjLHjx4UB4eHnr33Xd1ww03yMfHR8uWLZMkvfHGG2rdurV8fHzUqlUrvfbaaxesEwDgHvRNZ7Wpb1ap0J2amur0B+Pj46N58+bp2LFjOnnypFauXGleq12qadOm+vjjj3Xq1Cn9/PPPeumll1S3rvMB/B49euirr75SQUGBDhw4oHvuuacS9gYAaraTJ09q/Pjx2r59u9atW6c6derotttuU0lJiSRpyJAhWr58uVPTfffddxUWFqZu3bpJkoYNG6asrCylpqbqgw8+0Ouvv+50yVB55s+fr/j4eI0aNUq7du3S//7v/yoyMtKcX6dOHb3yyivas2ePlixZovXr1yshIcFpHadOndJLL72kt99+W5s2bVJmZqYee+wxc/7MmTO1ePFivfXWW/rvf/+rY8eOadWqVResKz4+XgUFBdq0aZN27dqlF154QfXq1ZMk/fTTT+revbtsNpvWr1+vHTt26L777tPZs2clSXPmzNHMmTP10ksvaefOnYqNjdXf/vY3ff/9907bePzxx/XII49o7969io2N1bJlyzRlyhQ9++yz2rt3r5577jk98cQTWrJkyQVrBQBUPvqms9rUNz2MP/sJAsrPz5fD4VBeXp7sdru7ywFQg5w5c0YZGRmKiIgo8ySH6nYjtV9++UWNGjXSrl271LZtW/38888KCwvT+vXrzS8LXbt2Vffu3fX888/r22+/VevWrbVt2zZdffXVkqT9+/erRYsWmjVrlsaOHVvudi6//HLde++9euaZZy6qrvfff1+jR4/WL7/8Iun3X+zvvfde7d+/X82bN5ckvfbaa5o2bZr5uMmwsDCNGzdOEyZMkCSdPXtWERER6tSp03lvCBMdHa2BAwfqySefLDPvH//4h5YvX659+/aVe2rb5Zdfrvj4eP3jH/8wp1177bW65pprNG/ePB08eFARERGaPXu2HnnkEXNMZGSknn76ad11113mtGeeeUYff/yxtmzZUmY7F/p7c3Wvo3fWPBf6fxI3lEJlqiq9k75Z8/um5JreWaWOdAMAqo/vv/9ed911l6644grZ7XY1a9ZMkpSZmSlJatSokXr37m2ezpWRkaG0tDQNGTJE0u83x6xbt67TUywiIyNVv379824zJydHWVlZ6tmz53nHfPrpp+rZs6cuv/xyBQQEaOjQofr111916tQpc4yfn5/5xUGSQkNDzSMFeXl5OnLkiDp37mzOr1u3rvkF53wefvhhPfPMM7ruuuv05JNPaufOnea89PR0devWrdwvDvn5+crKytJ1113nNP26667T3r17nab9sYaTJ0/qwIEDGjFihOrVq2e+nnnmGafTFQEAVQN901lt6puEbgDAJenfv7+OHTumhQsXauvWrdq6daskqbCw0BwzZMgQvf/+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U+Xt7a0FCxYoIiJCM2fOlCS1bt1a//3vfzVr1qzzhu6CggIVFBSY7/Pz812xuwAA1Fj0TgAAyldlrukuLi7W8uXLdfLkScXExJjTly1bpssuu0xt27bVpEmTdOrUKXNeWlqa2rVrp+DgYHNabGys8vPztWfPHnNMr169nLYVGxurtLS089Yyffp0ORwO8xUeHu6q3QQAoEaidwIAUD63h+5du3apXr16stlsGj16tFatWqWoqChJ0t1336133nlHGzZs0KRJk/T222/r73//u7lsdna2U+CWZL7Pzs6+4Jj8/HydPn263JomTZqkvLw883X48GGX7S8AADURvRMAgPK59fRySWrZsqXS09OVl5en999/X8OHD9fGjRsVFRWlUaNGmePatWun0NBQ9ezZUwcOHFDz5s0tq8lms8lms1m2fgAAahp6JwAA5XP7kW5vb29FRkaqU6dOmj59utq3b685c+aUO7Zz586SpP3790uSQkJCdPToUacxpe9LrwM/3xi73S5fX1+X7gsAAAAAAH/k9tB9rpKSEqcbsfxRenq6JCk0NFSSFBMTo127diknJ8cck5KSIrvdbp6iHhMTo3Xr1jmtJyUlxem6cQAAAAAArODW08snTZqkvn37qkmTJjp+/LgSExOVmpqq5ORkHThwQImJibrlllvUsGFD7dy5U+PGjVP37t0VHR0tSerdu7eioqI0dOhQzZgxQ9nZ2Zo8ebLi4+PNU9xGjx6tuXPnKiEhQffdd5/Wr1+vFStWKCkpyZ27DgAAAACoBdwaunNycjRs2DAdOXJEDodD0dHRSk5O1s0336zDhw/r008/1ezZs3Xy5EmFh4dr4MCBmjx5srm8p6enVq9erQceeEAxMTHy9/fX8OHDnZ7rHRERoaSkJI0bN05z5sxR48aN9cYbb/CMbgAAAACA5TwMwzDcXURVl5+fL4fDoby8PNntdneXAwCAy7m619E7q6fi1ORLWs6zBwczANQ+F9vrqtw13QAAAAAA1BSEbgAAAAAALELoBgAAAADAIoRuAAAAAAAsQugGAAAAAMAihG4AAAAAACxC6AYAAAAAwCKEbgAAAAAALELoBgAAAADAIoRuAAAAAAAsQugGAAAAAMAihG4AAAAAACxC6AYAAAAAwCKEbgAAAAAALELoBgAAAADAIoRuAAAAAAAsQugGAAAAAMAihG4A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",
158
- "text/plain": [
159
- "<Figure size 1000x1000 with 4 Axes>"
160
- ]
161
- },
162
- "metadata": {},
163
- "output_type": "display_data"
164
- }
165
- ],
166
- "source": [
167
- "fig, axs = plt.subplots(2, 2, figsize=(10, 10), sharex=True, sharey=True)\n",
168
- "i =0\n",
169
- "j = 0\n",
170
- "for m, r in result_dict.items():\n",
171
- " axs[i, j].hist(r['target_score'], bins=np.arange(-0.2, 1, 0.02), label=f'target score', alpha=0.5, color='slateblue')\n",
172
- " axs[i, j].hist(r['avg_cand_scores'], bins=np.arange(-0.2, 1, 0.02), label=f'avg cand score', alpha=0.5, color='salmon')\n",
173
- " axs[i, j].annotate(m, (0.6, 2500), ha='center', va='center', fontsize=12)\n",
174
- " axs[i, j].legend()\n",
175
- "\n",
176
- " if i % 2 == 0 and j == 1:\n",
177
- " i += 1\n",
178
- " j = 0\n",
179
- " else:\n",
180
- " j += 1\n",
181
- "\n",
182
- "axs[0, 0].set_ylabel('spectra count')\n",
183
- "axs[1, 0].set_ylabel('spectra count')\n",
184
- "axs[1, 0].set_xlabel('target score - avg cand score')\n",
185
- "axs[1, 1].set_xlabel('target score - avg cand score')\n",
186
- "\n",
187
- "plt.tight_layout()\n",
188
- "plt.show()"
189
- ]
190
- },
191
- {
192
- "cell_type": "code",
193
- "execution_count": null,
194
- "id": "1307a597",
195
- "metadata": {},
196
- "outputs": [],
197
- "source": []
198
- }
199
- ],
200
- "metadata": {
201
- "kernelspec": {
202
- "display_name": "Python (spec)",
203
- "language": "python",
204
- "name": "spec"
205
- },
206
- "language_info": {
207
- "codemirror_mode": {
208
- "name": "ipython",
209
- "version": 3
210
- },
211
- "file_extension": ".py",
212
- "mimetype": "text/x-python",
213
- "name": "python",
214
- "nbconvert_exporter": "python",
215
- "pygments_lexer": "ipython3",
216
- "version": "3.11.7"
217
- }
218
- },
219
- "nbformat": 4,
220
- "nbformat_minor": 5
221
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/eval_bad_instances.ipynb DELETED
@@ -1,1036 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 53,
6
- "id": "1e8a4ffb",
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 pickle\n",
18
- "import copy"
19
- ]
20
- },
21
- {
22
- "cell_type": "markdown",
23
- "id": "4c716c1a",
24
- "metadata": {},
25
- "source": [
26
- "## Ranking result"
27
- ]
28
- },
29
- {
30
- "cell_type": "code",
31
- "execution_count": 2,
32
- "id": "f9555d50",
33
- "metadata": {},
34
- "outputs": [],
35
- "source": [
36
- "ranking_file = \"/data/yzhouc01/FILIP-MS/experiments/20250913_optimized_filip-model/result_MassSpecGym_retrieval_candidates_formula.pkl\"\n",
37
- "with open(ranking_file, 'rb') as f:\n",
38
- " ranking = pickle.load(f)"
39
- ]
40
- },
41
- {
42
- "cell_type": "code",
43
- "execution_count": null,
44
- "id": "78cfd902",
45
- "metadata": {},
46
- "outputs": [
47
- {
48
- "data": {
49
- "text/html": [
50
- "<div>\n",
51
- "<style scoped>\n",
52
- " .dataframe tbody tr th:only-of-type {\n",
53
- " vertical-align: middle;\n",
54
- " }\n",
55
- "\n",
56
- " .dataframe tbody tr th {\n",
57
- " vertical-align: top;\n",
58
- " }\n",
59
- "\n",
60
- " .dataframe thead th {\n",
61
- " text-align: right;\n",
62
- " }\n",
63
- "</style>\n",
64
- "<table border=\"1\" class=\"dataframe\">\n",
65
- " <thead>\n",
66
- " <tr style=\"text-align: right;\">\n",
67
- " <th></th>\n",
68
- " <th>1</th>\n",
69
- " <th>5</th>\n",
70
- " <th>20</th>\n",
71
- " </tr>\n",
72
- " </thead>\n",
73
- " <tbody>\n",
74
- " <tr>\n",
75
- " <th>rank</th>\n",
76
- " <td>20.688</td>\n",
77
- " <td>47.391</td>\n",
78
- " <td>72.368</td>\n",
79
- " </tr>\n",
80
- " </tbody>\n",
81
- "</table>\n",
82
- "</div>"
83
- ],
84
- "text/plain": [
85
- " 1 5 20\n",
86
- "rank 20.688 47.391 72.368"
87
- ]
88
- },
89
- "execution_count": 5,
90
- "metadata": {},
91
- "output_type": "execute_result"
92
- }
93
- ],
94
- "source": [
95
- "r='rank'\n",
96
- "result = []\n",
97
- "\n",
98
- "top_k = [1, 5, 20]\n",
99
- "rank_result = {}\n",
100
- "for k in top_k:\n",
101
- " result.append(round(len(ranking[ranking[r]<=k])/len(ranking)*100, 3))\n",
102
- "rank_result[r] = result\n",
103
- "\n",
104
- "pd.DataFrame.from_dict(rank_result, orient='index', columns=['1', '5', '20'])"
105
- ]
106
- },
107
- {
108
- "cell_type": "code",
109
- "execution_count": 23,
110
- "id": "11bbc0d4",
111
- "metadata": {},
112
- "outputs": [],
113
- "source": [
114
- "def get_target(candidates, labels):\n",
115
- " return np.array(candidates)[labels][0]\n",
116
- "\n",
117
- "def get_cand_at_1(candidates, scores):\n",
118
- " return candidates[np.argmax(scores)]\n",
119
- "\n",
120
- "def get_top_score(scores):\n",
121
- " return np.max(scores)\n",
122
- "\n",
123
- "def get_target_score(labels, scores):\n",
124
- " return np.array(scores)[labels][0]\n",
125
- "\n",
126
- "def get_n_heavy_atoms(smiles):\n",
127
- " mol = Chem.MolFromSmiles(smiles)\n",
128
- " return mol.GetNumHeavyAtoms()\n",
129
- "\n",
130
- "ranking['target'] = ranking.apply(lambda x: get_target(x['candidates'], x['labels']), axis=1)\n",
131
- "ranking['target_score'] = ranking.apply(lambda x: get_target_score(x['labels'], x['scores']), axis=1)\n",
132
- "\n",
133
- "ranking['cand@1'] = ranking.apply(lambda x: get_cand_at_1(x['candidates'], x['scores']), axis=1)\n",
134
- "ranking['top_score'] = ranking.apply(lambda x: get_top_score(x['scores']), axis=1)\n",
135
- "\n",
136
- "ranking['n_heavy_atoms'] = ranking['target'].apply(get_n_heavy_atoms)"
137
- ]
138
- },
139
- {
140
- "cell_type": "code",
141
- "execution_count": 24,
142
- "id": "763ea617",
143
- "metadata": {},
144
- "outputs": [
145
- {
146
- "data": {
147
- "text/html": [
148
- "<div>\n",
149
- "<style scoped>\n",
150
- " .dataframe tbody tr th:only-of-type {\n",
151
- " vertical-align: middle;\n",
152
- " }\n",
153
- "\n",
154
- " .dataframe tbody tr th {\n",
155
- " vertical-align: top;\n",
156
- " }\n",
157
- "\n",
158
- " .dataframe thead th {\n",
159
- " text-align: right;\n",
160
- " }\n",
161
- "</style>\n",
162
- "<table border=\"1\" class=\"dataframe\">\n",
163
- " <thead>\n",
164
- " <tr style=\"text-align: right;\">\n",
165
- " <th></th>\n",
166
- " <th>identifier</th>\n",
167
- " <th>candidates</th>\n",
168
- " <th>scores</th>\n",
169
- " <th>labels</th>\n",
170
- " <th>rank</th>\n",
171
- " <th>target</th>\n",
172
- " <th>target_score</th>\n",
173
- " <th>cand@1</th>\n",
174
- " <th>top_score</th>\n",
175
- " <th>n_heavy_atoms</th>\n",
176
- " </tr>\n",
177
- " </thead>\n",
178
- " <tbody>\n",
179
- " <tr>\n",
180
- " <th>0</th>\n",
181
- " <td>MassSpecGymID0000201</td>\n",
182
- " <td>[CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(...</td>\n",
183
- " <td>[0.17369578778743744, 0.12611594796180725, 0.2...</td>\n",
184
- " <td>[True, False, False, False, False, False, Fals...</td>\n",
185
- " <td>17</td>\n",
186
- " <td>CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([...</td>\n",
187
- " <td>0.173696</td>\n",
188
- " <td>COCCCN1C(=O)COc2ccc(N(C(=O)[C@H]3CN(C(=O)OC(C)...</td>\n",
189
- " <td>0.259878</td>\n",
190
- " <td>57</td>\n",
191
- " </tr>\n",
192
- " <tr>\n",
193
- " <th>1</th>\n",
194
- " <td>MassSpecGymID0000202</td>\n",
195
- " <td>[CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(...</td>\n",
196
- " <td>[0.05142267048358917, 0.07289629429578781, 0.1...</td>\n",
197
- " <td>[True, False, False, False, False, False, Fals...</td>\n",
198
- " <td>24</td>\n",
199
- " <td>CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([...</td>\n",
200
- " <td>0.051423</td>\n",
201
- " <td>COC(=O)/C(C)=C\\CC1(O)C(=O)C2CC(C(C)C)C13Oc1c(C...</td>\n",
202
- " <td>0.237195</td>\n",
203
- " <td>57</td>\n",
204
- " </tr>\n",
205
- " <tr>\n",
206
- " <th>2</th>\n",
207
- " <td>MassSpecGymID0000203</td>\n",
208
- " <td>[CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(...</td>\n",
209
- " <td>[0.09354929625988007, 0.0947718694806099, 0.10...</td>\n",
210
- " <td>[True, False, False, False, False, False, Fals...</td>\n",
211
- " <td>23</td>\n",
212
- " <td>CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([...</td>\n",
213
- " <td>0.093549</td>\n",
214
- " <td>C=CCOC12Oc3ccc(OC(=O)NCC)cc3C3C(CCCCO)C(CCCCO)...</td>\n",
215
- " <td>0.238268</td>\n",
216
- " <td>57</td>\n",
217
- " </tr>\n",
218
- " </tbody>\n",
219
- "</table>\n",
220
- "</div>"
221
- ],
222
- "text/plain": [
223
- " identifier candidates \\\n",
224
- "0 MassSpecGymID0000201 [CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(... \n",
225
- "1 MassSpecGymID0000202 [CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(... \n",
226
- "2 MassSpecGymID0000203 [CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N(... \n",
227
- "\n",
228
- " scores \\\n",
229
- "0 [0.17369578778743744, 0.12611594796180725, 0.2... \n",
230
- "1 [0.05142267048358917, 0.07289629429578781, 0.1... \n",
231
- "2 [0.09354929625988007, 0.0947718694806099, 0.10... \n",
232
- "\n",
233
- " labels rank \\\n",
234
- "0 [True, False, False, False, False, False, Fals... 17 \n",
235
- "1 [True, False, False, False, False, False, Fals... 24 \n",
236
- "2 [True, False, False, False, False, False, Fals... 23 \n",
237
- "\n",
238
- " target target_score \\\n",
239
- "0 CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([... 0.173696 \n",
240
- "1 CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([... 0.051423 \n",
241
- "2 CC(C)[C@@H]1C(=O)N([C@H](C(=O)O[C@@H](C(=O)N([... 0.093549 \n",
242
- "\n",
243
- " cand@1 top_score n_heavy_atoms \n",
244
- "0 COCCCN1C(=O)COc2ccc(N(C(=O)[C@H]3CN(C(=O)OC(C)... 0.259878 57 \n",
245
- "1 COC(=O)/C(C)=C\\CC1(O)C(=O)C2CC(C(C)C)C13Oc1c(C... 0.237195 57 \n",
246
- "2 C=CCOC12Oc3ccc(OC(=O)NCC)cc3C3C(CCCCO)C(CCCCO)... 0.238268 57 "
247
- ]
248
- },
249
- "execution_count": 24,
250
- "metadata": {},
251
- "output_type": "execute_result"
252
- }
253
- ],
254
- "source": [
255
- "ranking.head(3)"
256
- ]
257
- },
258
- {
259
- "cell_type": "markdown",
260
- "id": "93ef333e",
261
- "metadata": {},
262
- "source": [
263
- "## model"
264
- ]
265
- },
266
- {
267
- "cell_type": "code",
268
- "execution_count": 12,
269
- "id": "0b4e4250",
270
- "metadata": {},
271
- "outputs": [
272
- {
273
- "name": "stdout",
274
- "output_type": "stream",
275
- "text": [
276
- "Data path: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv\n",
277
- "Processing formula spectra\n"
278
- ]
279
- },
280
- {
281
- "name": "stderr",
282
- "output_type": "stream",
283
- "text": [
284
- "100%|██████████| 231104/231104 [00:18<00:00, 12309.47it/s]\n",
285
- "/data/yzhouc01/FILIP-MS/flare/data/datasets.py:221: SettingWithCopyWarning: \n",
286
- "A value is trying to be set on a copy of a slice from a DataFrame.\n",
287
- "Try using .loc[row_indexer,col_indexer] = value instead\n",
288
- "\n",
289
- "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
290
- " tmp_df['spec'] = tmp_df.apply(lambda row: data_utils.make_tmp_subformula_spectra(row), axis=1)\n"
291
- ]
292
- },
293
- {
294
- "name": "stdout",
295
- "output_type": "stream",
296
- "text": [
297
- "Loaded Model from checkpoint\n"
298
- ]
299
- }
300
- ],
301
- "source": [
302
- "import sys\n",
303
- "sys.path.insert(0, \"/data/yzhouc01/MassSpecGym\")\n",
304
- "sys.path.insert(0, \"/data/yzhouc01/FILIP-MS\")\n",
305
- "\n",
306
- "from rdkit import RDLogger\n",
307
- "import pytorch_lightning as pl\n",
308
- "from pytorch_lightning import Trainer\n",
309
- "from massspecgym.models.base import Stage\n",
310
- "import os\n",
311
- "\n",
312
- "from flare.utils.data import get_spec_featurizer, get_mol_featurizer, get_ms_dataset\n",
313
- "from flare.utils.models import get_model\n",
314
- "\n",
315
- "from flare.definitions import TEST_RESULTS_DIR\n",
316
- "import yaml\n",
317
- "from functools import partial\n",
318
- "# Suppress RDKit warnings and errors\n",
319
- "lg = RDLogger.logger()\n",
320
- "lg.setLevel(RDLogger.CRITICAL)\n",
321
- "\n",
322
- "# Load model and data\n",
323
- "\n",
324
- "param_pth = '/data/yzhouc01/FILIP-MS/experiments/20250913_optimized_filip-model/lightning_logs/version_0/hparams.yaml'\n",
325
- "with open(param_pth) as f:\n",
326
- " params = yaml.load(f, Loader=yaml.FullLoader)\n",
327
- "\n",
328
- "spec_featurizer = get_spec_featurizer(params['spectra_view'], params)\n",
329
- "mol_featurizer = get_mol_featurizer(params['molecule_view'], params)\n",
330
- "dataset = get_ms_dataset(params['spectra_view'], params['molecule_view'], spec_featurizer, mol_featurizer, params)\n",
331
- "\n",
332
- "\n",
333
- "# load model\n",
334
- "import torch \n",
335
- "checkpoint_pth = \"/data/yzhouc01/FILIP-MS/experiments/20250913_optimized_filip-model/epoch=1993-train_loss=0.10.ckpt\"\n",
336
- "params['checkpoint_pth'] = checkpoint_pth\n",
337
- "model = get_model(params['model'], params)"
338
- ]
339
- },
340
- {
341
- "cell_type": "markdown",
342
- "id": "bd9dc380",
343
- "metadata": {},
344
- "source": [
345
- "## visualization function"
346
- ]
347
- },
348
- {
349
- "cell_type": "code",
350
- "execution_count": 16,
351
- "id": "0883a1a2",
352
- "metadata": {},
353
- "outputs": [],
354
- "source": [
355
- "\n",
356
- "import torch.nn.functional as F\n",
357
- "import numpy as np\n",
358
- "\n",
359
- "# Atomic masses corresponding to your atom_labels\n",
360
- "ATOM_LABELS = ['H', 'C', 'O', 'N', 'P', 'S', 'Cl', 'F', 'Br', 'I', 'B', 'As', 'Si', 'Se']\n",
361
- "ATOM_MASSES = np.array([\n",
362
- " 1.0078, 12.0000, 15.9949, 14.0031, 30.9738, 31.9721, \n",
363
- " 35.45, 18.9984, 79.90, 126.90, 10.811, 74.9216, 28.085, 78.96\n",
364
- "])\n",
365
- "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",
366
- "\n",
367
- "def spectra_from_encoding(spectral_tensor, norm_vector=norm_vector):\n",
368
- " \"\"\"\n",
369
- " Convert encoded spectra (num_peaks x 15) into m/z, intensities, and molecular formulas.\n",
370
- " Can undo normalization if a norm_vector is provided.\n",
371
- " \n",
372
- " Args:\n",
373
- " spectral_tensor (np.ndarray or torch.Tensor): [num_peaks, 15]\n",
374
- " norm_vector (np.ndarray or list): length 14, normalization factor for each atom\n",
375
- " \n",
376
- " Returns:\n",
377
- " mzs (list of float): list of m/z values\n",
378
- " intensities (list of float): list of intensities\n",
379
- " formulas (list of str): molecular formula strings\n",
380
- " \"\"\"\n",
381
- " if hasattr(spectral_tensor, \"detach\"):\n",
382
- " spectral_tensor = spectral_tensor.detach().cpu().numpy()\n",
383
- " \n",
384
- " counts = spectral_tensor[:, :14] # atom counts\n",
385
- " intensities = spectral_tensor[:, 14] # last col = intensity\n",
386
- " \n",
387
- " # Undo normalization\n",
388
- " if norm_vector is not None:\n",
389
- " counts = counts * np.array(norm_vector)\n",
390
- " \n",
391
- " # Compute m/z\n",
392
- " mzs = (counts * ATOM_MASSES).sum(axis=1)\n",
393
- " \n",
394
- " # Build molecular formula strings\n",
395
- " formulas = []\n",
396
- " for peak_counts in counts:\n",
397
- " formula_parts = []\n",
398
- " for elem, count in zip(ATOM_LABELS, peak_counts):\n",
399
- " n = int(round(count))\n",
400
- " if n > 0:\n",
401
- " formula_parts.append(f\"{elem}{n if n > 1 else ''}\")\n",
402
- " formulas.append(\"\".join(formula_parts) if formula_parts else \"Unknown\")\n",
403
- " \n",
404
- " return mzs.tolist(), intensities.tolist(), formulas\n",
405
- "\n",
406
- "\n",
407
- "def mol_to_graph_coords(mol):\n",
408
- " \"\"\"Return atom coordinates and bond list for a molecule.\"\"\"\n",
409
- " rdDepictor.Compute2DCoords(mol)\n",
410
- " conf = mol.GetConformer()\n",
411
- " coords = {i: conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())}\n",
412
- " bonds = [(b.GetBeginAtomIdx(), b.GetEndAtomIdx()) for b in mol.GetBonds()]\n",
413
- " return coords, bonds\n",
414
- "\n",
415
- "def interactive_attention_visualization(spectral_embeds, graph_embeds, \n",
416
- " peak_mzs, peak_intensities, peak_formulas, mol):\n",
417
- " \"\"\"\n",
418
- " Interactive visualization of peak-node similarity with color scale legend.\n",
419
- " - Clicking a peak recolors nodes by similarity\n",
420
- " - Clicking a node recolors peaks by similarity\n",
421
- " \"\"\"\n",
422
- " # Similarity matrix\n",
423
- " spectral_embeds = F.normalize(spectral_embeds, p=2, dim=-1)\n",
424
- " graph_embeds = F.normalize(graph_embeds, p=2, dim=-1)\n",
425
- " \n",
426
- " similarity = torch.matmul(spectral_embeds, graph_embeds.T).detach().cpu().numpy()\n",
427
- " sim_norm = (similarity - similarity.min()) / (similarity.max() - similarity.min() + 1e-8)\n",
428
- " \n",
429
- " num_peaks, num_nodes = similarity.shape\n",
430
- " \n",
431
- " # --- Molecule graph ---\n",
432
- " coords, bonds = mol_to_graph_coords(mol)\n",
433
- " atom_labels = [a.GetSymbol() for a in mol.GetAtoms()]\n",
434
- " atom_x = [coords[i].x for i in range(num_nodes)]\n",
435
- " atom_y = [coords[i].y for i in range(num_nodes)]\n",
436
- " \n",
437
- " # --- Spectrum trace ---\n",
438
- " spectrum_trace = go.Bar(\n",
439
- " x=peak_mzs,\n",
440
- " y=peak_intensities,\n",
441
- " name='peak',\n",
442
- " marker=dict(color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
443
- " colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
444
- " hovertext=[f\"Formula {f}\" for f in peak_formulas],\n",
445
- " customdata=list(range(num_peaks)) # peak index\n",
446
- " )\n",
447
- " \n",
448
- " # --- Graph nodes ---\n",
449
- " graph_nodes = go.Scatter(\n",
450
- " x=atom_x, y=atom_y,\n",
451
- " mode=\"markers+text\",\n",
452
- " name='node',\n",
453
- " text=atom_labels,\n",
454
- " textposition=\"middle center\",\n",
455
- " marker=dict(size=20, color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
456
- " colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
457
- " customdata=list(range(num_nodes)),\n",
458
- " # hovertext=[f\"Atom {i} ({label})\" for i, label in enumerate(atom_labels)]\n",
459
- " )\n",
460
- " \n",
461
- " # --- Graph bonds ---\n",
462
- " edge_x, edge_y = [], []\n",
463
- " for i, j in bonds:\n",
464
- " edge_x += [coords[i].x, coords[j].x, None]\n",
465
- " edge_y += [coords[i].y, coords[j].y, None]\n",
466
- " graph_edges = go.Scatter(\n",
467
- " x=edge_x, y=edge_y,\n",
468
- " mode=\"lines\", line=dict(color=\"gray\", width=2),\n",
469
- " hoverinfo=\"none\", showlegend=False\n",
470
- " )\n",
471
- " \n",
472
- " # --- Subplots ---\n",
473
- " fig = make_subplots(rows=1, cols=2, subplot_titles=(\"Spectrum\", \"Molecule\"), \n",
474
- " column_widths=[0.6, 0.4])\n",
475
- " \n",
476
- " fig.add_trace(spectrum_trace, row=1, col=1)\n",
477
- " fig.add_trace(graph_edges, row=1, col=2)\n",
478
- " fig.add_trace(graph_nodes, row=1, col=2)\n",
479
- " \n",
480
- " fig.update_xaxes(title=\"m/z\", row=1, col=1)\n",
481
- " fig.update_yaxes(title=\"Intensity\", row=1, col=1)\n",
482
- " fig.update_xaxes(visible=False, row=1, col=2)\n",
483
- " fig.update_yaxes(visible=False, row=1, col=2)\n",
484
- " \n",
485
- " fig.update_layout(title=\"Peak ↔ Node Similarity\", showlegend=False)\n",
486
- " \n",
487
- " # --- Interactivity ---\n",
488
- " from ipywidgets import VBox\n",
489
- " fw = go.FigureWidget(fig)\n",
490
- "\n",
491
- " def highlight_nodes(trace, points, selector):\n",
492
- " \"\"\"Click on peak → recolor nodes\"\"\"\n",
493
- " if points.point_inds:\n",
494
- " peak_idx = points.point_inds[0]\n",
495
- " scores = sim_norm[peak_idx, :]\n",
496
- " with fw.batch_update():\n",
497
- " fw.data[2].marker.color = scores\n",
498
- " fw.data[0].marker.color = [\"red\" if i == peak_idx else \"lightgray\" for i in range(num_peaks)]\n",
499
- "\n",
500
- " def highlight_peaks(trace, points, selector):\n",
501
- " \"\"\"Click on node → recolor peaks\"\"\"\n",
502
- " if points.point_inds:\n",
503
- " node_idx = points.point_inds[0]\n",
504
- " scores = sim_norm[:, node_idx]\n",
505
- " with fw.batch_update():\n",
506
- " fw.data[0].marker.color = scores\n",
507
- " fw.data[2].marker.color = [\"red\" if i == node_idx else \"lightgray\" for i in range(num_nodes)]\n",
508
- " \n",
509
- " fw.data[0].on_click(highlight_nodes) # spectrum\n",
510
- " fw.data[2].on_click(highlight_peaks) # nodes\n",
511
- " \n",
512
- " return fw\n"
513
- ]
514
- },
515
- {
516
- "cell_type": "markdown",
517
- "id": "7d1b98d3",
518
- "metadata": {},
519
- "source": [
520
- "## Visualization"
521
- ]
522
- },
523
- {
524
- "cell_type": "code",
525
- "execution_count": 92,
526
- "id": "78f85d94",
527
- "metadata": {},
528
- "outputs": [
529
- {
530
- "name": "stdout",
531
- "output_type": "stream",
532
- "text": [
533
- "MS ID: MassSpecGymID0396247, Target:0.423, Cand@1: 0.49\n",
534
- "Target rank: 25\n"
535
- ]
536
- }
537
- ],
538
- "source": [
539
- "# sample a case where targte is ranked at 2\n",
540
- "sample = ranking[(ranking['rank']>20) & (ranking['n_heavy_atoms'] <=20)].sample(1).iloc[0]\n",
541
- "ms_id = sample['identifier']\n",
542
- "target = sample['target']\n",
543
- "cand_at_1 = sample['cand@1']\n",
544
- "print(f\"MS ID: {ms_id}, Target:{sample['target_score']:.3}, Cand@1: {sample['top_score']:.3}\")\n",
545
- "print(f\"Target rank: {sample['rank']}\")"
546
- ]
547
- },
548
- {
549
- "cell_type": "code",
550
- "execution_count": 96,
551
- "id": "20437eb8",
552
- "metadata": {},
553
- "outputs": [
554
- {
555
- "name": "stdout",
556
- "output_type": "stream",
557
- "text": [
558
- "CCCCCCCC(=O)NC1=C2C(=CSS2)NC1=O\n",
559
- "23\n"
560
- ]
561
- },
562
- {
563
- "data": {
564
- "application/vnd.jupyter.widget-view+json": {
565
- "model_id": "c60a8c857d4041cdaf96a7f4ba2f4f61",
566
- "version_major": 2,
567
- "version_minor": 0
568
- },
569
- "text/plain": [
570
- "FigureWidget({\n",
571
- " 'data': [{'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
572
- " 16, 17, 18, 19, 20, 21, 22],\n",
573
- " 'hovertext': [Formula H10C7, Formula H8C3O2N2, Formula H12C8,\n",
574
- " Formula H3C3NS2, Formula H14C8O, Formula HC7NS, Formula\n",
575
- " HC4ONS2, Formula C7N2S, Formula H3C6ONS2, Formula\n",
576
- " H2C5ON2S2, Formula H3C5ON2S2, Formula H4C5ON2S2,\n",
577
- " Formula HC8ON2S, Formula H2C6ON2S2, Formula H5C6ON2S2,\n",
578
- " Formula H6C6ON2S2, Formula H6C9ON2S, Formula H4C7ON2S2,\n",
579
- " Formula H5C7ON2S2, Formula H2C10ON2S, Formula\n",
580
- " H8C9ON2S2, Formula H16C13ON2S2, Formula H18C13O2N2S2],\n",
581
- " 'marker': {'cmax': 1,\n",
582
- " 'cmin': 0,\n",
583
- " 'color': 'lightgray',\n",
584
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
585
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
586
- " '#482878'], [0.2222222222222222,\n",
587
- " '#3e4989'], [0.3333333333333333,\n",
588
- " '#31688e'], [0.4444444444444444,\n",
589
- " '#26828e'], [0.5555555555555556,\n",
590
- " '#1f9e89'], [0.6666666666666666,\n",
591
- " '#35b779'], [0.7777777777777778,\n",
592
- " '#6ece58'], [0.8888888888888888,\n",
593
- " '#b5de2b'], [1.0, '#fde725']]},\n",
594
- " 'name': 'peak',\n",
595
- " 'type': 'bar',\n",
596
- " 'uid': 'c92eb0ab-8030-4f20-a4b0-126e19bad926',\n",
597
- " 'x': [94.07799900290073, 104.05839936191737, 108.0936002238661,\n",
598
- " 116.97070118690729, 126.10410051210002, 130.98300034787468,\n",
599
- " 142.95000219490117, 143.9783008338645, 168.96559957769588,\n",
600
- " 169.96090409332814, 170.96870403325858, 171.97650416466072,\n",
601
- " 172.98100185312143, 181.96090015942156, 184.9843001706846,\n",
602
- " 185.99210011061504, 190.01999790607465, 195.97650157185868,\n",
603
- " 196.98430151178914, 197.98880457099676, 224.00769912172186,\n",
604
- " 280.07010477147026, 298.08060429381726],\n",
605
- " 'xaxis': 'x',\n",
606
- " 'y': [0.24127991497516632, 0.16632197797298431, 0.16255460679531097,\n",
607
- " 0.2183758169412613, 0.16817252337932587, 0.14077642560005188,\n",
608
- " 0.24170850217342377, 0.23078560829162598, 0.12851069867610931,\n",
609
- " 0.22988910973072052, 0.35106268525123596, 1.0,\n",
610
- " 0.2844732105731964, 0.14581838250160217, 0.1700058877468109,\n",
611
- " 0.3804142475128174, 0.13246509432792664, 0.18231017887592316,\n",
612
- " 0.24256132543087006, 0.2150418609380722, 0.14223572611808777,\n",
613
- " 0.3020711839199066, 1.100000023841858],\n",
614
- " 'yaxis': 'y'},\n",
615
- " {'hoverinfo': 'none',\n",
616
- " 'line': {'color': 'gray', 'width': 2},\n",
617
- " 'mode': 'lines',\n",
618
- " 'showlegend': False,\n",
619
- " 'type': 'scatter',\n",
620
- " 'uid': 'ef26d482-9ee9-47f4-823a-834c4cdab648',\n",
621
- " 'x': [9.232981638284745, 7.913365912100706, None, 7.913365912100706,\n",
622
- " 6.635932959325772, None, 6.635932959325772, 5.316317233141732,\n",
623
- " None, 5.316317233141732, 4.038884280366799, None,\n",
624
- " 4.038884280366799, 2.7192685541827593, None,\n",
625
- " 2.7192685541827593, 1.4418356014078257, None,\n",
626
- " 1.4418356014078257, 0.12221987522378566, None,\n",
627
- " 0.12221987522378566, 0.08003710181467982, None,\n",
628
- " 0.12221987522378566, -1.155213077551148, None,\n",
629
- " -1.155213077551148, -2.474828803735188, None,\n",
630
- " -2.474828803735188, -3.8274477532630358, None,\n",
631
- " -3.8274477532630358, -4.862094590572173, None,\n",
632
- " -4.862094590572173, -6.214713553764019, None,\n",
633
- " -6.214713553764019, -6.016031209535085, None,\n",
634
- " -6.016031209535085, -4.540619804645251, None,\n",
635
- " -4.862094590572173, -4.148922589885585, None,\n",
636
- " -4.148922589885585, -2.6735111792957427, None,\n",
637
- " -2.6735111792957427, -1.587460593601559, None,\n",
638
- " -2.6735111792957427, -2.474828803735188, None,\n",
639
- " -4.540619804645251, -3.8274477532630358, None],\n",
640
- " 'xaxis': 'x2',\n",
641
- " 'y': [0.4812527731327094, -0.2319192504406855, None,\n",
642
- " -0.2319192504406855, 0.5543154798814447, None,\n",
643
- " 0.5543154798814447, -0.1588565436919498, None,\n",
644
- " -0.1588565436919498, 0.6273781866301811, None,\n",
645
- " 0.6273781866301811, -0.08579383694321274, None,\n",
646
- " -0.08579383694321274, 0.7004408933789189, None,\n",
647
- " 0.7004408933789189, -0.012731130194475332, None,\n",
648
- " -0.012731130194475332, -1.5121378840900008, None,\n",
649
- " -0.012731130194475332, 0.7735036001276558, None,\n",
650
- " 0.7735036001276558, 0.06033157655426158, None,\n",
651
- " 0.06033157655426158, 0.708731127277566, None,\n",
652
- " 0.708731127277566, -0.3773194802201689, None,\n",
653
- " -0.3773194802201689, 0.27108004199882174, None,\n",
654
- " 0.27108004199882174, 1.757863592517086, None,\n",
655
- " 1.757863592517086, 2.028346838432628, None,\n",
656
- " -0.3773194802201689, -1.6969351846006568, None,\n",
657
- " -1.6969351846006568, -1.426451969777085, None,\n",
658
- " -1.426451969777085, -2.461098829973027, None,\n",
659
- " -1.426451969777085, 0.06033157655426158, None,\n",
660
- " 2.028346838432628, 0.708731127277566, None],\n",
661
- " 'yaxis': 'y2'},\n",
662
- " {'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
663
- " 16, 17, 18],\n",
664
- " 'marker': {'cmax': 1,\n",
665
- " 'cmin': 0,\n",
666
- " 'color': 'lightgray',\n",
667
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
668
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
669
- " '#482878'], [0.2222222222222222,\n",
670
- " '#3e4989'], [0.3333333333333333,\n",
671
- " '#31688e'], [0.4444444444444444,\n",
672
- " '#26828e'], [0.5555555555555556,\n",
673
- " '#1f9e89'], [0.6666666666666666,\n",
674
- " '#35b779'], [0.7777777777777778,\n",
675
- " '#6ece58'], [0.8888888888888888,\n",
676
- " '#b5de2b'], [1.0, '#fde725']],\n",
677
- " 'size': 20},\n",
678
- " 'mode': 'markers+text',\n",
679
- " 'name': 'node',\n",
680
- " 'text': [C, C, C, C, C, C, C, C, O, N, C, C, C, C, S, S, N, C, O],\n",
681
- " 'textposition': 'middle center',\n",
682
- " 'type': 'scatter',\n",
683
- " 'uid': 'e157b647-8df0-4d9e-a022-9a8e503c555e',\n",
684
- " 'x': [9.232981638284745, 7.913365912100706, 6.635932959325772,\n",
685
- " 5.316317233141732, 4.038884280366799, 2.7192685541827593,\n",
686
- " 1.4418356014078257, 0.12221987522378566, 0.08003710181467982,\n",
687
- " -1.155213077551148, -2.474828803735188, -3.8274477532630358,\n",
688
- " -4.862094590572173, -6.214713553764019, -6.016031209535085,\n",
689
- " -4.540619804645251, -4.148922589885585, -2.6735111792957427,\n",
690
- " -1.587460593601559],\n",
691
- " 'xaxis': 'x2',\n",
692
- " 'y': [0.4812527731327094, -0.2319192504406855, 0.5543154798814447,\n",
693
- " -0.1588565436919498, 0.6273781866301811, -0.08579383694321274,\n",
694
- " 0.7004408933789189, -0.012731130194475332, -1.5121378840900008,\n",
695
- " 0.7735036001276558, 0.06033157655426158, 0.708731127277566,\n",
696
- " -0.3773194802201689, 0.27108004199882174, 1.757863592517086,\n",
697
- " 2.028346838432628, -1.6969351846006568, -1.426451969777085,\n",
698
- " -2.461098829973027],\n",
699
- " 'yaxis': 'y2'}],\n",
700
- " 'layout': {'annotations': [{'font': {'size': 16},\n",
701
- " 'showarrow': False,\n",
702
- " 'text': 'Spectrum',\n",
703
- " 'x': 0.27,\n",
704
- " 'xanchor': 'center',\n",
705
- " 'xref': 'paper',\n",
706
- " 'y': 1.0,\n",
707
- " 'yanchor': 'bottom',\n",
708
- " 'yref': 'paper'},\n",
709
- " {'font': {'size': 16},\n",
710
- " 'showarrow': False,\n",
711
- " 'text': 'Molecule',\n",
712
- " 'x': 0.8200000000000001,\n",
713
- " 'xanchor': 'center',\n",
714
- " 'xref': 'paper',\n",
715
- " 'y': 1.0,\n",
716
- " 'yanchor': 'bottom',\n",
717
- " 'yref': 'paper'}],\n",
718
- " 'showlegend': False,\n",
719
- " 'template': '...',\n",
720
- " 'title': {'text': 'Peak ↔ Node Similarity'},\n",
721
- " 'xaxis': {'anchor': 'y', 'domain': [0.0, 0.54], 'title': {'text': 'm/z'}},\n",
722
- " 'xaxis2': {'anchor': 'y2', 'domain': [0.64, 1.0], 'visible': False},\n",
723
- " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Intensity'}},\n",
724
- " 'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0], 'visible': False}}\n",
725
- "})"
726
- ]
727
- },
728
- "execution_count": 96,
729
- "metadata": {},
730
- "output_type": "execute_result"
731
- }
732
- ],
733
- "source": [
734
- "# Target Molecule\n",
735
- "i = dataset.metadata[dataset.metadata['identifier'] == ms_id].index[0]\n",
736
- "s = target\n",
737
- "print(s)\n",
738
- "mol = Chem.MolFromSmiles(s)\n",
739
- "g = dataset[i]['mol']\n",
740
- "spec = dataset[i]['SpecFormula']\n",
741
- "\n",
742
- "peak_mzs, peak_intensities, peak_formulas = spectra_from_encoding(spec)\n",
743
- "\n",
744
- "print(len(peak_formulas))\n",
745
- "# Embeddings\n",
746
- "model = model.to(torch.device('cpu'))\n",
747
- "model.eval()\n",
748
- "with torch.no_grad():\n",
749
- " spec_enc, mol_enc = model.forward(dataset[i], stage='test')\n",
750
- "\n",
751
- "fw = interactive_attention_visualization(spec_enc, mol_enc, peak_mzs, peak_intensities, peak_formulas, mol)\n",
752
- "fw\n"
753
- ]
754
- },
755
- {
756
- "cell_type": "code",
757
- "execution_count": 98,
758
- "id": "ce3a20ae",
759
- "metadata": {},
760
- "outputs": [
761
- {
762
- "name": "stdout",
763
- "output_type": "stream",
764
- "text": [
765
- "H10C7\n",
766
- "H8C3O2N2\n",
767
- "H12C8\n",
768
- "H3C3NS2\n",
769
- "H14C8O\n",
770
- "HC7NS\n",
771
- "HC4ONS2\n",
772
- "C7N2S\n",
773
- "H3C6ONS2\n",
774
- "H2C5ON2S2\n",
775
- "H3C5ON2S2\n",
776
- "H4C5ON2S2\n",
777
- "HC8ON2S\n",
778
- "H2C6ON2S2\n",
779
- "H5C6ON2S2\n",
780
- "H6C6ON2S2\n",
781
- "H6C9ON2S\n",
782
- "H4C7ON2S2\n",
783
- "H5C7ON2S2\n",
784
- "H2C10ON2S\n",
785
- "H8C9ON2S2\n",
786
- "H16C13ON2S2\n",
787
- "H18C13O2N2S2\n"
788
- ]
789
- }
790
- ],
791
- "source": [
792
- "for f in peak_formulas:\n",
793
- " print(f)"
794
- ]
795
- },
796
- {
797
- "cell_type": "code",
798
- "execution_count": 99,
799
- "id": "da93666c",
800
- "metadata": {},
801
- "outputs": [
802
- {
803
- "name": "stdout",
804
- "output_type": "stream",
805
- "text": [
806
- "Cc1sc2[nH]c(=S)n(CCOC(C)C)c(=O)c2c1C\n"
807
- ]
808
- },
809
- {
810
- "data": {
811
- "application/vnd.jupyter.widget-view+json": {
812
- "model_id": "be11e39bfd484f238a5643ffbdff0d78",
813
- "version_major": 2,
814
- "version_minor": 0
815
- },
816
- "text/plain": [
817
- "FigureWidget({\n",
818
- " 'data': [{'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
819
- " 16, 17, 18, 19, 20, 21, 22],\n",
820
- " 'hovertext': [Formula H10C7, Formula H8C3O2N2, Formula H12C8,\n",
821
- " Formula H3C3NS2, Formula H14C8O, Formula HC7NS, Formula\n",
822
- " HC4ONS2, Formula C7N2S, Formula H3C6ONS2, Formula\n",
823
- " H2C5ON2S2, Formula H3C5ON2S2, Formula H4C5ON2S2,\n",
824
- " Formula HC8ON2S, Formula H2C6ON2S2, Formula H5C6ON2S2,\n",
825
- " Formula H6C6ON2S2, Formula H6C9ON2S, Formula H4C7ON2S2,\n",
826
- " Formula H5C7ON2S2, Formula H2C10ON2S, Formula\n",
827
- " H8C9ON2S2, Formula H16C13ON2S2, Formula H18C13O2N2S2],\n",
828
- " 'marker': {'cmax': 1,\n",
829
- " 'cmin': 0,\n",
830
- " 'color': 'lightgray',\n",
831
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
832
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
833
- " '#482878'], [0.2222222222222222,\n",
834
- " '#3e4989'], [0.3333333333333333,\n",
835
- " '#31688e'], [0.4444444444444444,\n",
836
- " '#26828e'], [0.5555555555555556,\n",
837
- " '#1f9e89'], [0.6666666666666666,\n",
838
- " '#35b779'], [0.7777777777777778,\n",
839
- " '#6ece58'], [0.8888888888888888,\n",
840
- " '#b5de2b'], [1.0, '#fde725']]},\n",
841
- " 'name': 'peak',\n",
842
- " 'type': 'bar',\n",
843
- " 'uid': 'ef26bc6b-dc8e-4cbb-b681-e2343d82ea94',\n",
844
- " 'x': [94.07799900290073, 104.05839936191737, 108.0936002238661,\n",
845
- " 116.97070118690729, 126.10410051210002, 130.98300034787468,\n",
846
- " 142.95000219490117, 143.9783008338645, 168.96559957769588,\n",
847
- " 169.96090409332814, 170.96870403325858, 171.97650416466072,\n",
848
- " 172.98100185312143, 181.96090015942156, 184.9843001706846,\n",
849
- " 185.99210011061504, 190.01999790607465, 195.97650157185868,\n",
850
- " 196.98430151178914, 197.98880457099676, 224.00769912172186,\n",
851
- " 280.07010477147026, 298.08060429381726],\n",
852
- " 'xaxis': 'x',\n",
853
- " 'y': [0.24127991497516632, 0.16632197797298431, 0.16255460679531097,\n",
854
- " 0.2183758169412613, 0.16817252337932587, 0.14077642560005188,\n",
855
- " 0.24170850217342377, 0.23078560829162598, 0.12851069867610931,\n",
856
- " 0.22988910973072052, 0.35106268525123596, 1.0,\n",
857
- " 0.2844732105731964, 0.14581838250160217, 0.1700058877468109,\n",
858
- " 0.3804142475128174, 0.13246509432792664, 0.18231017887592316,\n",
859
- " 0.24256132543087006, 0.2150418609380722, 0.14223572611808777,\n",
860
- " 0.3020711839199066, 1.100000023841858],\n",
861
- " 'yaxis': 'y'},\n",
862
- " {'hoverinfo': 'none',\n",
863
- " 'line': {'color': 'gray', 'width': 2},\n",
864
- " 'mode': 'lines',\n",
865
- " 'showlegend': False,\n",
866
- " 'type': 'scatter',\n",
867
- " 'uid': 'aaaf4294-9093-49bb-905c-261137ec6e91',\n",
868
- " 'x': [6.049790084552722, 4.550200591327338, None, 4.550200591327338,\n",
869
- " 3.6971529128998446, None, 3.6971529128998446,\n",
870
- " 2.260114954813708, None, 2.260114954813708, 0.9789776698290348,\n",
871
- " None, 0.9789776698290348, -0.33725023794912345, None,\n",
872
- " -0.33725023794912345, -1.6183875229337956, None,\n",
873
- " -0.33725023794912345, -0.3723408607426099, None,\n",
874
- " -0.3723408607426099, -1.6885687685207686, None,\n",
875
- " -1.6885687685207686, -2.9697060535054405, None,\n",
876
- " -2.9697060535054405, -4.2859339612835985, None,\n",
877
- " -4.2859339612835985, -5.567071246268271, None,\n",
878
- " -5.567071246268271, -6.88329915404643, None,\n",
879
- " -5.567071246268271, -5.531980623474783, None,\n",
880
- " -0.3723408607426099, 0.9087964242420628, None,\n",
881
- " 0.9087964242420628, 0.8737058014485767, None,\n",
882
- " 0.9087964242420628, 2.225024332020221, None, 2.225024332020221,\n",
883
- " 3.640375092533582, None, 3.640375092533582, 4.07040056505774,\n",
884
- " None, 3.640375092533582, 4.550200591327338, None,\n",
885
- " 2.225024332020221, 2.260114954813708, None],\n",
886
- " 'xaxis': 'x2',\n",
887
- " 'y': [0.3527856091535654, 0.38787623194705184, None,\n",
888
- " 0.38787623194705184, 1.6216953671242724, None,\n",
889
- " 1.6216953671242724, 1.1916698946001143, None,\n",
890
- " 1.1916698946001143, 1.9718540119865837, None,\n",
891
- " 1.9718540119865837, 1.2524486361476674, None,\n",
892
- " 1.2524486361476674, 2.032632753534136, None,\n",
893
- " 1.2524486361476674, -0.24714085707771702, None,\n",
894
- " -0.24714085707771702, -0.9665462329166336, None,\n",
895
- " -0.9665462329166336, -0.18636211553016513, None,\n",
896
- " -0.18636211553016513, -0.9057674913690814, None,\n",
897
- " -0.9057674913690814, -0.1255833739826133, None,\n",
898
- " -0.1255833739826133, -0.8449887498215296, None,\n",
899
- " -0.1255833739826133, 1.3740061192427715, None,\n",
900
- " -0.24714085707771702, -1.027324974464186, None,\n",
901
- " -1.027324974464186, -2.5269144676895703, None,\n",
902
- " -1.027324974464186, -0.30791959862527024, None,\n",
903
- " -0.30791959862527024, -0.8046914020866298, None,\n",
904
- " -0.8046914020866298, -2.2417293601727675, None,\n",
905
- " -0.8046914020866298, 0.38787623194705184, None,\n",
906
- " -0.30791959862527024, 1.1916698946001143, None],\n",
907
- " 'yaxis': 'y2'},\n",
908
- " {'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
909
- " 16, 17, 18],\n",
910
- " 'marker': {'cmax': 1,\n",
911
- " 'cmin': 0,\n",
912
- " 'color': 'lightgray',\n",
913
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
914
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
915
- " '#482878'], [0.2222222222222222,\n",
916
- " '#3e4989'], [0.3333333333333333,\n",
917
- " '#31688e'], [0.4444444444444444,\n",
918
- " '#26828e'], [0.5555555555555556,\n",
919
- " '#1f9e89'], [0.6666666666666666,\n",
920
- " '#35b779'], [0.7777777777777778,\n",
921
- " '#6ece58'], [0.8888888888888888,\n",
922
- " '#b5de2b'], [1.0, '#fde725']],\n",
923
- " 'size': 20},\n",
924
- " 'mode': 'markers+text',\n",
925
- " 'name': 'node',\n",
926
- " 'text': [C, C, S, C, N, C, S, N, C, C, O, C, C, C, C, O, C, C, C],\n",
927
- " 'textposition': 'middle center',\n",
928
- " 'type': 'scatter',\n",
929
- " 'uid': '03337be1-04c1-4602-b8b2-bfbe9950f45e',\n",
930
- " 'x': [6.049790084552722, 4.550200591327338, 3.6971529128998446,\n",
931
- " 2.260114954813708, 0.9789776698290348, -0.33725023794912345,\n",
932
- " -1.6183875229337956, -0.3723408607426099, -1.6885687685207686,\n",
933
- " -2.9697060535054405, -4.2859339612835985, -5.567071246268271,\n",
934
- " -6.88329915404643, -5.531980623474783, 0.9087964242420628,\n",
935
- " 0.8737058014485767, 2.225024332020221, 3.640375092533582,\n",
936
- " 4.07040056505774],\n",
937
- " 'xaxis': 'x2',\n",
938
- " 'y': [0.3527856091535654, 0.38787623194705184, 1.6216953671242724,\n",
939
- " 1.1916698946001143, 1.9718540119865837, 1.2524486361476674,\n",
940
- " 2.032632753534136, -0.24714085707771702, -0.9665462329166336,\n",
941
- " -0.18636211553016513, -0.9057674913690814, -0.1255833739826133,\n",
942
- " -0.8449887498215296, 1.3740061192427715, -1.027324974464186,\n",
943
- " -2.5269144676895703, -0.30791959862527024, -0.8046914020866298,\n",
944
- " -2.2417293601727675],\n",
945
- " 'yaxis': 'y2'}],\n",
946
- " 'layout': {'annotations': [{'font': {'size': 16},\n",
947
- " 'showarrow': False,\n",
948
- " 'text': 'Spectrum',\n",
949
- " 'x': 0.27,\n",
950
- " 'xanchor': 'center',\n",
951
- " 'xref': 'paper',\n",
952
- " 'y': 1.0,\n",
953
- " 'yanchor': 'bottom',\n",
954
- " 'yref': 'paper'},\n",
955
- " {'font': {'size': 16},\n",
956
- " 'showarrow': False,\n",
957
- " 'text': 'Molecule',\n",
958
- " 'x': 0.8200000000000001,\n",
959
- " 'xanchor': 'center',\n",
960
- " 'xref': 'paper',\n",
961
- " 'y': 1.0,\n",
962
- " 'yanchor': 'bottom',\n",
963
- " 'yref': 'paper'}],\n",
964
- " 'showlegend': False,\n",
965
- " 'template': '...',\n",
966
- " 'title': {'text': 'Peak ↔ Node Similarity'},\n",
967
- " 'xaxis': {'anchor': 'y', 'domain': [0.0, 0.54], 'title': {'text': 'm/z'}},\n",
968
- " 'xaxis2': {'anchor': 'y2', 'domain': [0.64, 1.0], 'visible': False},\n",
969
- " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Intensity'}},\n",
970
- " 'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0], 'visible': False}}\n",
971
- "})"
972
- ]
973
- },
974
- "execution_count": 99,
975
- "metadata": {},
976
- "output_type": "execute_result"
977
- }
978
- ],
979
- "source": [
980
- "# Cand@1\n",
981
- "\n",
982
- "# Target Molecule\n",
983
- "i = dataset.metadata[dataset.metadata['identifier'] == ms_id].index[0]\n",
984
- "s = cand_at_1\n",
985
- "print(s)\n",
986
- "mol = Chem.MolFromSmiles(s)\n",
987
- "g = dataset[i]['mol']\n",
988
- "spec = dataset[i]['SpecFormula']\n",
989
- "cand_mol= mol_featurizer(cand_at_1)\n",
990
- "\n",
991
- "peak_mzs, peak_intensities, peak_formulas = spectra_from_encoding(spec)\n",
992
- "\n",
993
- "# Embeddings\n",
994
- "model = model.to(torch.device('cpu'))\n",
995
- "model.eval()\n",
996
- "with torch.no_grad():\n",
997
- " input = copy.deepcopy(dataset[i])\n",
998
- " input['mol'] = cand_mol\n",
999
- " \n",
1000
- " spec_enc, mol_enc = model.forward(input, stage='test')\n",
1001
- "\n",
1002
- "fw = interactive_attention_visualization(spec_enc, mol_enc, peak_mzs, peak_intensities, peak_formulas, mol)\n",
1003
- "fw\n"
1004
- ]
1005
- },
1006
- {
1007
- "cell_type": "code",
1008
- "execution_count": null,
1009
- "id": "aed9d517",
1010
- "metadata": {},
1011
- "outputs": [],
1012
- "source": []
1013
- }
1014
- ],
1015
- "metadata": {
1016
- "kernelspec": {
1017
- "display_name": "Python (spec)",
1018
- "language": "python",
1019
- "name": "spec"
1020
- },
1021
- "language_info": {
1022
- "codemirror_mode": {
1023
- "name": "ipython",
1024
- "version": 3
1025
- },
1026
- "file_extension": ".py",
1027
- "mimetype": "text/x-python",
1028
- "name": "python",
1029
- "nbconvert_exporter": "python",
1030
- "pygments_lexer": "ipython3",
1031
- "version": "3.11.7"
1032
- }
1033
- },
1034
- "nbformat": 4,
1035
- "nbformat_minor": 5
1036
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/filip_viz.ipynb DELETED
@@ -1,800 +0,0 @@
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
- "Loaded Model from checkpoint\n",
35
- "Data path: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv\n",
36
- "Processing formula spectra\n"
37
- ]
38
- },
39
- {
40
- "name": "stderr",
41
- "output_type": "stream",
42
- "text": [
43
- "100%|██████████| 231104/231104 [00:15<00:00, 14902.24it/s]\n",
44
- "/data/yzhouc01/FILIP-MS/flare/data/datasets.py:221: SettingWithCopyWarning: \n",
45
- "A value is trying to be set on a copy of a slice from a DataFrame.\n",
46
- "Try using .loc[row_indexer,col_indexer] = value instead\n",
47
- "\n",
48
- "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n",
49
- " tmp_df['spec'] = tmp_df.apply(lambda row: data_utils.make_tmp_subformula_spectra(row), axis=1)\n"
50
- ]
51
- }
52
- ],
53
- "source": [
54
- "import sys\n",
55
- "sys.path.insert(0, \"/data/yzhouc01/MassSpecGym\")\n",
56
- "sys.path.insert(0, \"/data/yzhouc01/FILIP-MS\")\n",
57
- "\n",
58
- "from rdkit import RDLogger\n",
59
- "import pytorch_lightning as pl\n",
60
- "from pytorch_lightning import Trainer\n",
61
- "from massspecgym.models.base import Stage\n",
62
- "import os\n",
63
- "\n",
64
- "from flare.utils.data import get_spec_featurizer, get_mol_featurizer, get_ms_dataset\n",
65
- "from flare.utils.models import get_model\n",
66
- "\n",
67
- "from flare.definitions import TEST_RESULTS_DIR\n",
68
- "import yaml\n",
69
- "from functools import partial\n",
70
- "# Suppress RDKit warnings and errors\n",
71
- "lg = RDLogger.logger()\n",
72
- "lg.setLevel(RDLogger.CRITICAL)\n",
73
- "\n",
74
- "# Load model and data\n",
75
- "\n",
76
- "param_pth = \"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/lightning_logs/version_0/hparams.yaml\"\n",
77
- "with open(param_pth) as f:\n",
78
- " params = yaml.load(f, Loader=yaml.FullLoader)\n",
79
- "\n",
80
- "spec_featurizer = get_spec_featurizer(params['spectra_view'], params)\n",
81
- "mol_featurizer = get_mol_featurizer(params['molecule_view'], params)\n",
82
- "\n",
83
- "# load model\n",
84
- "import torch \n",
85
- "checkpoint_pth = \"/data/yzhouc01/FILIP-MS/experiments/20250930_optimized_flare_42/epoch=1959-train_loss=0.08.ckpt\"\n",
86
- "params['checkpoint_pth'] = checkpoint_pth\n",
87
- "model = get_model(params['model'], params)\n",
88
- "\n",
89
- "dataset = get_ms_dataset(params['spectra_view'], params['molecule_view'], spec_featurizer, mol_featurizer, params)"
90
- ]
91
- },
92
- {
93
- "cell_type": "markdown",
94
- "metadata": {},
95
- "source": [
96
- "## mol/spec embeddings"
97
- ]
98
- },
99
- {
100
- "cell_type": "code",
101
- "execution_count": 7,
102
- "metadata": {},
103
- "outputs": [
104
- {
105
- "name": "stdout",
106
- "output_type": "stream",
107
- "text": [
108
- "Atom 0: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
109
- "Atom 1: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
110
- "Atom 2: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
111
- "Atom 3: N, Graph node features: tensor([0.1401, 0.0000, 0.0000, 0.0000, 1.0000])\n",
112
- "Atom 4: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
113
- "Atom 5: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
114
- "Atom 6: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
115
- "Atom 7: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
116
- "Atom 8: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
117
- "Atom 9: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
118
- "Atom 10: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
119
- "Atom 11: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
120
- "Atom 12: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
121
- "Atom 13: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
122
- "Atom 14: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
123
- "Atom 15: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
124
- "Atom 16: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n",
125
- "Atom 17: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
126
- "Atom 18: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
127
- "Atom 19: O, Graph node features: tensor([0.1600, 0.0000, 0.0000, 1.0000, 0.0000])\n",
128
- "Atom 20: C, Graph node features: tensor([0.1201, 0.0000, 1.0000, 0.0000, 0.0000])\n"
129
- ]
130
- }
131
- ],
132
- "source": [
133
- "# sanity check, rdkit order is preserved\n",
134
- "i = 0\n",
135
- "s = dataset.metadata.iloc[i]['smiles']\n",
136
- "mol = Chem.MolFromSmiles(s, sanitize=True) \n",
137
- "g = dataset[i]['mol']\n",
138
- "\n",
139
- "# Compare RDKit atoms vs graph nodes\n",
140
- "for atom in mol.GetAtoms():\n",
141
- " idx = atom.GetIdx()\n",
142
- " print(f\"Atom {idx}: {atom.GetSymbol()}, Graph node features: {g.ndata['h'][idx][:5]}\")"
143
- ]
144
- },
145
- {
146
- "cell_type": "code",
147
- "execution_count": 4,
148
- "metadata": {},
149
- "outputs": [],
150
- "source": [
151
- "import numpy as np\n",
152
- "\n",
153
- "# Atomic masses corresponding to your atom_labels\n",
154
- "ATOM_LABELS = ['H', 'C', 'O', 'N', 'P', 'S', 'Cl', 'F', 'Br', 'I', 'B', 'As', 'Si', 'Se']\n",
155
- "ATOM_MASSES = np.array([\n",
156
- " 1.0078, 12.0000, 15.9949, 14.0031, 30.9738, 31.9721, \n",
157
- " 35.45, 18.9984, 79.90, 126.90, 10.811, 74.9216, 28.085, 78.96\n",
158
- "])\n",
159
- "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",
160
- "\n",
161
- "def spectra_from_encoding(spectral_tensor, norm_vector=norm_vector):\n",
162
- " \"\"\"\n",
163
- " Convert encoded spectra (num_peaks x 15) into m/z, intensities, and molecular formulas.\n",
164
- " Can undo normalization if a norm_vector is provided.\n",
165
- " \n",
166
- " Args:\n",
167
- " spectral_tensor (np.ndarray or torch.Tensor): [num_peaks, 15]\n",
168
- " norm_vector (np.ndarray or list): length 14, normalization factor for each atom\n",
169
- " \n",
170
- " Returns:\n",
171
- " mzs (list of float): list of m/z values\n",
172
- " intensities (list of float): list of intensities\n",
173
- " formulas (list of str): molecular formula strings\n",
174
- " \"\"\"\n",
175
- " if hasattr(spectral_tensor, \"detach\"):\n",
176
- " spectral_tensor = spectral_tensor.detach().cpu().numpy()\n",
177
- " \n",
178
- " counts = spectral_tensor[:, :14] # atom counts\n",
179
- " intensities = spectral_tensor[:, 14] # last col = intensity\n",
180
- " \n",
181
- " # Undo normalization\n",
182
- " if norm_vector is not None:\n",
183
- " counts = counts * np.array(norm_vector)\n",
184
- " \n",
185
- " # Compute m/z\n",
186
- " mzs = (counts * ATOM_MASSES).sum(axis=1)\n",
187
- " \n",
188
- " # Build molecular formula strings\n",
189
- " formulas = []\n",
190
- " for peak_counts in counts:\n",
191
- " formula_parts = []\n",
192
- " for elem, count in zip(ATOM_LABELS, peak_counts):\n",
193
- " n = int(round(count))\n",
194
- " if n > 0:\n",
195
- " formula_parts.append(f\"{elem}{n if n > 1 else ''}\")\n",
196
- " formulas.append(\"\".join(formula_parts) if formula_parts else \"Unknown\")\n",
197
- " \n",
198
- " return mzs.tolist(), intensities.tolist(), formulas"
199
- ]
200
- },
201
- {
202
- "cell_type": "markdown",
203
- "metadata": {},
204
- "source": [
205
- "## visualization"
206
- ]
207
- },
208
- {
209
- "cell_type": "code",
210
- "execution_count": 5,
211
- "metadata": {},
212
- "outputs": [],
213
- "source": [
214
- "\n",
215
- "import torch.nn.functional as F\n",
216
- "\n",
217
- "def mol_to_graph_coords(mol):\n",
218
- " \"\"\"Return atom coordinates and bond list for a molecule.\"\"\"\n",
219
- " rdDepictor.Compute2DCoords(mol)\n",
220
- " conf = mol.GetConformer()\n",
221
- " coords = {i: conf.GetAtomPosition(i) for i in range(mol.GetNumAtoms())}\n",
222
- " bonds = [(b.GetBeginAtomIdx(), b.GetEndAtomIdx()) for b in mol.GetBonds()]\n",
223
- " return coords, bonds\n",
224
- "\n",
225
- "def interactive_attention_visualization(spectral_embeds, graph_embeds, \n",
226
- " peak_mzs, peak_intensities, peak_formulas, mol):\n",
227
- " \"\"\"\n",
228
- " Interactive visualization of peak-node similarity with color scale legend.\n",
229
- " - Clicking a peak recolors nodes by similarity\n",
230
- " - Clicking a node recolors peaks by similarity\n",
231
- " \"\"\"\n",
232
- " # Similarity matrix\n",
233
- " spectral_embeds = F.normalize(spectral_embeds, p=2, dim=-1)\n",
234
- " graph_embeds = F.normalize(graph_embeds, p=2, dim=-1)\n",
235
- " \n",
236
- " similarity = torch.matmul(spectral_embeds, graph_embeds.T).detach().cpu().numpy()\n",
237
- " sim_norm = (similarity - similarity.min()) / (similarity.max() - similarity.min() + 1e-8)\n",
238
- " \n",
239
- " num_peaks, num_nodes = similarity.shape\n",
240
- " \n",
241
- " # --- Molecule graph ---\n",
242
- " coords, bonds = mol_to_graph_coords(mol)\n",
243
- " atom_labels = [a.GetSymbol() for a in mol.GetAtoms()]\n",
244
- " atom_x = [coords[i].x for i in range(num_nodes)]\n",
245
- " atom_y = [coords[i].y for i in range(num_nodes)]\n",
246
- " \n",
247
- " # --- Spectrum trace ---\n",
248
- " spectrum_trace = go.Bar(\n",
249
- " x=peak_mzs,\n",
250
- " y=peak_intensities,\n",
251
- " name='peak',\n",
252
- " marker=dict(color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
253
- " colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
254
- " hovertext=[f\"Formula {f}\" for f in peak_formulas],\n",
255
- " customdata=list(range(num_peaks)) # peak index\n",
256
- " )\n",
257
- " \n",
258
- " # --- Graph nodes ---\n",
259
- " graph_nodes = go.Scatter(\n",
260
- " x=atom_x, y=atom_y,\n",
261
- " mode=\"markers+text\",\n",
262
- " name='node',\n",
263
- " text=atom_labels,\n",
264
- " textposition=\"middle center\",\n",
265
- " marker=dict(size=20, color=\"lightgray\", colorscale=\"Viridis\", cmin=0, cmax=1,\n",
266
- " colorbar=dict(title=\"Similarity\", len=0.8, y=0.5)),\n",
267
- " customdata=list(range(num_nodes)),\n",
268
- " # hovertext=[f\"Atom {i} ({label})\" for i, label in enumerate(atom_labels)]\n",
269
- " )\n",
270
- " \n",
271
- " # --- Graph bonds ---\n",
272
- " edge_x, edge_y = [], []\n",
273
- " for i, j in bonds:\n",
274
- " edge_x += [coords[i].x, coords[j].x, None]\n",
275
- " edge_y += [coords[i].y, coords[j].y, None]\n",
276
- " graph_edges = go.Scatter(\n",
277
- " x=edge_x, y=edge_y,\n",
278
- " mode=\"lines\", line=dict(color=\"gray\", width=2),\n",
279
- " hoverinfo=\"none\", showlegend=False\n",
280
- " )\n",
281
- " \n",
282
- " # --- Subplots ---\n",
283
- " fig = make_subplots(rows=1, cols=2, subplot_titles=(\"Spectrum\", \"Molecule\"), \n",
284
- " column_widths=[0.6, 0.4])\n",
285
- " \n",
286
- " fig.add_trace(spectrum_trace, row=1, col=1)\n",
287
- " fig.add_trace(graph_edges, row=1, col=2)\n",
288
- " fig.add_trace(graph_nodes, row=1, col=2)\n",
289
- " \n",
290
- " fig.update_xaxes(title=\"m/z\", row=1, col=1)\n",
291
- " fig.update_yaxes(title=\"Intensity\", row=1, col=1)\n",
292
- " fig.update_xaxes(visible=False, row=1, col=2)\n",
293
- " fig.update_yaxes(visible=False, row=1, col=2)\n",
294
- " \n",
295
- " fig.update_layout(title=\"Peak ↔ Node Similarity\", showlegend=False)\n",
296
- " \n",
297
- " # --- Interactivity ---\n",
298
- " from ipywidgets import VBox\n",
299
- " fw = go.FigureWidget(fig)\n",
300
- "\n",
301
- " def highlight_nodes(trace, points, selector):\n",
302
- " \"\"\"Click on peak → recolor nodes\"\"\"\n",
303
- " if points.point_inds:\n",
304
- " peak_idx = points.point_inds[0]\n",
305
- " scores = sim_norm[peak_idx, :]\n",
306
- " with fw.batch_update():\n",
307
- " fw.data[2].marker.color = scores\n",
308
- " fw.data[0].marker.color = [\"red\" if i == peak_idx else \"lightgray\" for i in range(num_peaks)]\n",
309
- "\n",
310
- " def highlight_peaks(trace, points, selector):\n",
311
- " \"\"\"Click on node → recolor peaks\"\"\"\n",
312
- " if points.point_inds:\n",
313
- " node_idx = points.point_inds[0]\n",
314
- " scores = sim_norm[:, node_idx]\n",
315
- " with fw.batch_update():\n",
316
- " fw.data[0].marker.color = scores\n",
317
- " fw.data[2].marker.color = [\"red\" if i == node_idx else \"lightgray\" for i in range(num_nodes)]\n",
318
- " \n",
319
- " fw.data[0].on_click(highlight_nodes) # spectrum\n",
320
- " fw.data[2].on_click(highlight_peaks) # nodes\n",
321
- " \n",
322
- " return fw\n"
323
- ]
324
- },
325
- {
326
- "cell_type": "code",
327
- "execution_count": 6,
328
- "metadata": {},
329
- "outputs": [
330
- {
331
- "data": {
332
- "text/html": [
333
- "<div>\n",
334
- "<style scoped>\n",
335
- " .dataframe tbody tr th:only-of-type {\n",
336
- " vertical-align: middle;\n",
337
- " }\n",
338
- "\n",
339
- " .dataframe tbody tr th {\n",
340
- " vertical-align: top;\n",
341
- " }\n",
342
- "\n",
343
- " .dataframe thead th {\n",
344
- " text-align: right;\n",
345
- " }\n",
346
- "</style>\n",
347
- "<table border=\"1\" class=\"dataframe\">\n",
348
- " <thead>\n",
349
- " <tr style=\"text-align: right;\">\n",
350
- " <th></th>\n",
351
- " <th>identifier</th>\n",
352
- " <th>mzs</th>\n",
353
- " <th>intensities</th>\n",
354
- " <th>smiles</th>\n",
355
- " <th>inchikey</th>\n",
356
- " <th>formula</th>\n",
357
- " <th>precursor_formula</th>\n",
358
- " <th>parent_mass</th>\n",
359
- " <th>precursor_mz</th>\n",
360
- " <th>adduct</th>\n",
361
- " <th>instrument_type</th>\n",
362
- " <th>collision_energy</th>\n",
363
- " <th>fold</th>\n",
364
- " <th>simulation_challenge</th>\n",
365
- " <th>formulas</th>\n",
366
- " <th>formula_mzs</th>\n",
367
- " <th>formula_intensities</th>\n",
368
- " </tr>\n",
369
- " </thead>\n",
370
- " <tbody>\n",
371
- " <tr>\n",
372
- " <th>178176</th>\n",
373
- " <td>MassSpecGymID0253457</td>\n",
374
- " <td>51.023201,53.013199,53.0383,53.041401,55.01419...</td>\n",
375
- " <td>0.002549437908838331,0.0017867526000527193,0.0...</td>\n",
376
- " <td>C(CCCCC(=O)O)CCC/C=C/C(=O)O</td>\n",
377
- " <td>MAZWDMBCPDUFDJ</td>\n",
378
- " <td>C12H20O4</td>\n",
379
- " <td>C12H21O4</td>\n",
380
- " <td>228.135724</td>\n",
381
- " <td>229.143</td>\n",
382
- " <td>[M+H]+</td>\n",
383
- " <td>Orbitrap</td>\n",
384
- " <td>NaN</td>\n",
385
- " <td>train</td>\n",
386
- " <td>False</td>\n",
387
- " <td>[C4H4, C3H2O, C3H2O, C4H6, C4H6, C3H4O, C4H8, ...</td>\n",
388
- " <td>[53.0383, 55.017502, 55.020901, 55.053902, 55....</td>\n",
389
- " <td>[0.12179901477434044, 0.5270370005986297, 0.22...</td>\n",
390
- " </tr>\n",
391
- " <tr>\n",
392
- " <th>221801</th>\n",
393
- " <td>MassSpecGymID0401651</td>\n",
394
- " <td>50.287594,52.302277,52.304676,52.307671,52.313...</td>\n",
395
- " <td>0.0027461495504881362,0.011530528389442945,0.0...</td>\n",
396
- " <td>C(C(CC(=O)O)O)C(=O)O</td>\n",
397
- " <td>ZQHYXNSQOIDNTL</td>\n",
398
- " <td>C5H8O5</td>\n",
399
- " <td>C5H9O5</td>\n",
400
- " <td>148.036724</td>\n",
401
- " <td>149.044</td>\n",
402
- " <td>[M+H]+</td>\n",
403
- " <td>Orbitrap</td>\n",
404
- " <td>NaN</td>\n",
405
- " <td>train</td>\n",
406
- " <td>False</td>\n",
407
- " <td>[C3H2O2, C4H8O, C4H4O2, C3H4O3, C5H4O3, C3H8O5...</td>\n",
408
- " <td>[71.012451, 73.064476, 85.028152, 89.022858, 1...</td>\n",
409
- " <td>[0.0945515732285825, 0.15281688388394785, 0.19...</td>\n",
410
- " </tr>\n",
411
- " <tr>\n",
412
- " <th>115831</th>\n",
413
- " <td>MassSpecGymID0173164</td>\n",
414
- " <td>56.964001,75.023003,76.029999,89.038002,90.985...</td>\n",
415
- " <td>0.027,0.044,0.15,0.615,0.028,1.0,0.103,0.082,0...</td>\n",
416
- " <td>COC1=C(C=CC(=C1)C=O)O</td>\n",
417
- " <td>MWOOGOJBHIARFG</td>\n",
418
- " <td>C8H8O3</td>\n",
419
- " <td>C8H8NaO3</td>\n",
420
- " <td>152.050782</td>\n",
421
- " <td>175.040</td>\n",
422
- " <td>[M+Na]+</td>\n",
423
- " <td>Orbitrap</td>\n",
424
- " <td>NaN</td>\n",
425
- " <td>train</td>\n",
426
- " <td>False</td>\n",
427
- " <td>[C4H4, C4H5, C5H6, C5H8, C6H6, C5H5O, C6H6O, C...</td>\n",
428
- " <td>[75.023003, 76.029999, 89.038002, 91.054001, 1...</td>\n",
429
- " <td>[0.20976176963403032, 0.3872983346207417, 0.78...</td>\n",
430
- " </tr>\n",
431
- " </tbody>\n",
432
- "</table>\n",
433
- "</div>"
434
- ],
435
- "text/plain": [
436
- " identifier \\\n",
437
- "178176 MassSpecGymID0253457 \n",
438
- "221801 MassSpecGymID0401651 \n",
439
- "115831 MassSpecGymID0173164 \n",
440
- "\n",
441
- " mzs \\\n",
442
- "178176 51.023201,53.013199,53.0383,53.041401,55.01419... \n",
443
- "221801 50.287594,52.302277,52.304676,52.307671,52.313... \n",
444
- "115831 56.964001,75.023003,76.029999,89.038002,90.985... \n",
445
- "\n",
446
- " intensities \\\n",
447
- "178176 0.002549437908838331,0.0017867526000527193,0.0... \n",
448
- "221801 0.0027461495504881362,0.011530528389442945,0.0... \n",
449
- "115831 0.027,0.044,0.15,0.615,0.028,1.0,0.103,0.082,0... \n",
450
- "\n",
451
- " smiles inchikey formula \\\n",
452
- "178176 C(CCCCC(=O)O)CCC/C=C/C(=O)O MAZWDMBCPDUFDJ C12H20O4 \n",
453
- "221801 C(C(CC(=O)O)O)C(=O)O ZQHYXNSQOIDNTL C5H8O5 \n",
454
- "115831 COC1=C(C=CC(=C1)C=O)O MWOOGOJBHIARFG C8H8O3 \n",
455
- "\n",
456
- " precursor_formula parent_mass precursor_mz adduct instrument_type \\\n",
457
- "178176 C12H21O4 228.135724 229.143 [M+H]+ Orbitrap \n",
458
- "221801 C5H9O5 148.036724 149.044 [M+H]+ Orbitrap \n",
459
- "115831 C8H8NaO3 152.050782 175.040 [M+Na]+ Orbitrap \n",
460
- "\n",
461
- " collision_energy fold simulation_challenge \\\n",
462
- "178176 NaN train False \n",
463
- "221801 NaN train False \n",
464
- "115831 NaN train False \n",
465
- "\n",
466
- " formulas \\\n",
467
- "178176 [C4H4, C3H2O, C3H2O, C4H6, C4H6, C3H4O, C4H8, ... \n",
468
- "221801 [C3H2O2, C4H8O, C4H4O2, C3H4O3, C5H4O3, C3H8O5... \n",
469
- "115831 [C4H4, C4H5, C5H6, C5H8, C6H6, C5H5O, C6H6O, C... \n",
470
- "\n",
471
- " formula_mzs \\\n",
472
- "178176 [53.0383, 55.017502, 55.020901, 55.053902, 55.... \n",
473
- "221801 [71.012451, 73.064476, 85.028152, 89.022858, 1... \n",
474
- "115831 [75.023003, 76.029999, 89.038002, 91.054001, 1... \n",
475
- "\n",
476
- " formula_intensities \n",
477
- "178176 [0.12179901477434044, 0.5270370005986297, 0.22... \n",
478
- "221801 [0.0945515732285825, 0.15281688388394785, 0.19... \n",
479
- "115831 [0.20976176963403032, 0.3872983346207417, 0.78... "
480
- ]
481
- },
482
- "execution_count": 6,
483
- "metadata": {},
484
- "output_type": "execute_result"
485
- }
486
- ],
487
- "source": [
488
- "dataset.metadata[dataset.metadata['precursor_mz'] <=250].sample(3)"
489
- ]
490
- },
491
- {
492
- "cell_type": "code",
493
- "execution_count": 11,
494
- "metadata": {},
495
- "outputs": [
496
- {
497
- "data": {
498
- "application/vnd.jupyter.widget-view+json": {
499
- "model_id": "257b01d1c7674ffb9c02851ef9e955c0",
500
- "version_major": 2,
501
- "version_minor": 0
502
- },
503
- "text/plain": [
504
- "FigureWidget({\n",
505
- " 'data': [{'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
506
- " 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,\n",
507
- " 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,\n",
508
- " 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,\n",
509
- " 55, 56, 57, 58, 59, 60],\n",
510
- " 'hovertext': [Formula H8C5, Formula H8C6, Formula H2C4O2, Formula\n",
511
- " H10C6, Formula H8C7, Formula H10C7, Formula H8C6O,\n",
512
- " Formula H8C8, Formula H10C8, Formula H12C8, Formula\n",
513
- " H10C9, Formula H12C9, Formula H10C8O, Formula H14C9,\n",
514
- " Formula H10C10, Formula H12C10, Formula H10C9O, Formula\n",
515
- " H14C10, Formula H12C9O, Formula H12C11, Formula\n",
516
- " H10C10O, Formula H14C11, Formula H12C10O, Formula\n",
517
- " H16C11, Formula H6C8O3, Formula H14C10O, Formula\n",
518
- " H12C12, Formula H14C12, Formula H12C11O, Formula\n",
519
- " H16C12, Formula H14C11O, Formula H18C12, Formula\n",
520
- " H12C10O2, Formula H16C11O, Formula H8C8O4, Formula\n",
521
- " H14C13, Formula H16C13, Formula H14C12O, Formula\n",
522
- " H18C13, Formula H12C11O2, Formula H16C12O, Formula\n",
523
- " H10C10O3, Formula H16C14, Formula H18C14, Formula\n",
524
- " H12C12O2, Formula H16C13O, Formula H14C12O2, Formula\n",
525
- " H18C13O, Formula H12C11O3, Formula H14C13O2, Formula\n",
526
- " H18C14O, Formula H16C13O2, Formula H20C14O, Formula\n",
527
- " H10C11O4, Formula H14C11O4, Formula H16C14O2, Formula\n",
528
- " H22C17, Formula H18C15O2, Formula H24C17O, Formula\n",
529
- " H32C24O3, Formula H36C26O5],\n",
530
- " 'marker': {'cmax': 1,\n",
531
- " 'cmin': 0,\n",
532
- " 'color': 'lightgray',\n",
533
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
534
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
535
- " '#482878'], [0.2222222222222222,\n",
536
- " '#3e4989'], [0.3333333333333333,\n",
537
- " '#31688e'], [0.4444444444444444,\n",
538
- " '#26828e'], [0.5555555555555556,\n",
539
- " '#1f9e89'], [0.6666666666666666,\n",
540
- " '#35b779'], [0.7777777777777778,\n",
541
- " '#6ece58'], [0.8888888888888888,\n",
542
- " '#b5de2b'], [1.0, '#fde725']]},\n",
543
- " 'name': 'peak',\n",
544
- " 'type': 'bar',\n",
545
- " 'uid': '403be33c-16b4-47eb-854b-9dd044f4d253',\n",
546
- " 'x': [68.06240171584189, 80.06239778193533, 82.00539944571182,\n",
547
- " 82.07799766179622, 92.06239912303984, 94.07799900290073,\n",
548
- " 96.05729742442145, 104.06240046414435, 106.07800034400523,\n",
549
- " 108.0936002238661, 118.07799641009868, 120.09359628995955,\n",
550
- " 122.07289998649136, 122.10919693570733, 130.07800302621425,\n",
551
- " 132.09360290607512, 134.0728960525848, 134.1092035518229,\n",
552
- " 136.08849593244568, 144.09359897216856, 146.07290266870038,\n",
553
- " 146.10919961791635, 148.08850254856125, 148.12479949777722,\n",
554
- " 150.0314991287984, 150.10410319430903, 156.093595038262,\n",
555
- " 158.1091956840098, 160.0884986146547, 160.12479556387066,\n",
556
- " 162.10409926040248, 162.14039544373153, 164.08340219104736,\n",
557
- " 164.11969914026335, 168.04199903408886, 170.10920230012536,\n",
558
- " 172.12480217998623, 174.10409532649592, 174.1404020598471,\n",
559
- " 176.0833982571408, 176.1196952063568, 178.06270195367262,\n",
560
- " 184.12479824607968, 186.14039812594055, 188.08339432323424,\n",
561
- " 188.11970182247236, 190.09899496898203, 190.13530170233324,\n",
562
- " 192.07829789962693, 202.0990015850976, 202.13529776842668,\n",
563
- " 204.11460146495847, 204.15089764828758, 206.0575976622522,\n",
564
- " 210.08879818786085, 216.1145975310519, 226.17159663396478,\n",
565
- " 230.13020402702836, 244.1820961563118, 368.2342900551997,\n",
566
- " 428.25530531140714],\n",
567
- " 'xaxis': 'x',\n",
568
- " 'y': [0.535057008266449, 0.5284685492515564, 0.6902723908424377,\n",
569
- " 0.40269342064857483, 0.5650832056999207, 0.6598749756813049,\n",
570
- " 0.3621201515197754, 0.48294201493263245, 0.9612096548080444,\n",
571
- " 0.7318032383918762, 0.7540351748466492, 1.0,\n",
572
- " 0.4338064193725586, 0.5893615484237671, 0.41372817754745483,\n",
573
- " 0.9374854564666748, 0.5265709757804871, 0.7560238242149353,\n",
574
- " 0.5331829190254211, 0.6711556911468506, 0.3809790015220642,\n",
575
- " 0.6613901853561401, 0.5919036865234375, 0.38619813323020935,\n",
576
- " 0.8819172382354736, 0.5150388479232788, 0.4185391366481781,\n",
577
- " 0.8670363426208496, 0.42916664481163025, 0.6460781693458557,\n",
578
- " 0.913145124912262, 0.47982287406921387, 0.3621201515197754,\n",
579
- " 0.6859081387519836, 0.564196765422821, 0.5935924649238586,\n",
580
- " 0.6460781693458557, 0.4870698153972626, 0.3756873607635498,\n",
581
- " 0.5275205969810486, 0.49824491143226624, 0.7922297716140747,\n",
582
- " 0.5052276849746704, 0.6232078075408936, 0.4418087899684906,\n",
583
- " 0.5169787406921387, 0.5322433710098267, 0.41372817754745483,\n",
584
- " 0.43149274587631226, 0.37966302037239075, 0.38489997386932373,\n",
585
- " 0.6343516111373901, 0.535057008266449, 0.3989473581314087,\n",
586
- " 0.38749194145202637, 0.7440121173858643, 0.6159374713897705,\n",
587
- " 0.40517157316207886, 0.5650832056999207, 0.36349964141845703,\n",
588
- " 1.100000023841858],\n",
589
- " 'yaxis': 'y'},\n",
590
- " {'hoverinfo': 'none',\n",
591
- " 'line': {'color': 'gray', 'width': 2},\n",
592
- " 'mode': 'lines',\n",
593
- " 'showlegend': False,\n",
594
- " 'type': 'scatter',\n",
595
- " 'uid': 'bafe82b8-b282-4e95-ae37-012553da7f51',\n",
596
- " 'x': [2.5583421472905834, 1.7753548365655238, None,\n",
597
- " 1.7753548365655238, 0.27584576911133163, None,\n",
598
- " 0.27584576911133163, -0.5071415416137278, None,\n",
599
- " -0.5071415416137278, -2.006650609067919, None,\n",
600
- " -2.006650609067919, -2.7231723657970504, None,\n",
601
- " -2.7231723657970504, -4.222681433251242, None,\n",
602
- " -4.222681433251242, -5.005668743976302, None,\n",
603
- " -5.005668743976302, -6.505177811430492, None,\n",
604
- " -5.005668743976302, -4.28914698724717, None, -4.28914698724717,\n",
605
- " -2.7896379197929786, None, -2.7896379197929786,\n",
606
- " -2.0731161630638466, None, -2.0731161630638466,\n",
607
- " -0.5736070956096556, None, -0.5736070956096556,\n",
608
- " 0.2093802151154039, None, 0.2093802151154039,\n",
609
- " 1.7088892825695952, None, 1.7088892825695952,\n",
610
- " 2.4918765932946547, None, 2.4918765932946547,\n",
611
- " 3.9506394063295636, None, 3.9506394063295636,\n",
612
- " 4.069217095584485, None, 4.069217095584485, 2.6837393248165426,\n",
613
- " None, 2.6837393248165426, 2.3344385156183947, None,\n",
614
- " 4.069217095584485, 5.348643088459236, None, 3.9506394063295636,\n",
615
- " 5.0906689652143875, None, 2.4918765932946547,\n",
616
- " 3.4043110505061365, None, 1.7088892825695952,\n",
617
- " 1.1340403052652162, None, 1.1340403052652162,\n",
618
- " 0.2181749852801769, None, 1.1340403052652162, 2.0464747624767,\n",
619
- " None, 2.0464747624767, 1.4716257851723231, None,\n",
620
- " 0.2093802151154039, 0.9259019718445355, None,\n",
621
- " -4.28914698724717, -4.066550889929987, None, -4.28914698724717,\n",
622
- " -5.711349241391562, None, -2.006650609067919,\n",
623
- " -1.2236632983428586, None, 2.4918765932946547,\n",
624
- " 1.7753548365655238, None, 0.2093802151154039,\n",
625
- " -0.5071415416137278, None, -2.7896379197929786,\n",
626
- " -2.006650609067919, None, 2.6837393248165426,\n",
627
- " 1.7088892825695952, None],\n",
628
- " 'xaxis': 'x2',\n",
629
- " 'y': [2.9569768914725025, 1.677550898597754, None,\n",
630
- " 1.677550898597754, 1.7159248040891406, None,\n",
631
- " 1.7159248040891406, 0.43649881121439116, None,\n",
632
- " 0.43649881121439116, 0.4748727167057778, None,\n",
633
- " 0.4748727167057778, 1.7926726150719152, None,\n",
634
- " 1.7926726150719152, 1.8310465205633017, None,\n",
635
- " 1.8310465205633017, 0.5516205276885522, None,\n",
636
- " 0.5516205276885522, 0.5899944331799385, None,\n",
637
- " 0.5516205276885522, -0.7661793706775843, None,\n",
638
- " -0.7661793706775843, -0.8045532761689715, None,\n",
639
- " -0.8045532761689715, -2.122353174535109, None,\n",
640
- " -2.122353174535109, -2.1607270800264953, None,\n",
641
- " -2.1607270800264953, -0.8813010871517453, None,\n",
642
- " -0.8813010871517453, -0.9196749926431328, None,\n",
643
- " -0.9196749926431328, 0.35975100023161755, None,\n",
644
- " 0.35975100023161755, 0.010450191033470002, None,\n",
645
- " 0.010450191033470002, -1.484855574223577, None,\n",
646
- " -1.484855574223577, -2.059704551527956, None,\n",
647
- " -2.059704551527956, -3.5184673645628646, None,\n",
648
- " -1.484855574223577, -2.2678428849486365, None,\n",
649
- " 0.010450191033470002, 0.9853002332804177, None,\n",
650
- " 0.35975100023161755, 1.5503237033006856, None,\n",
651
- " -0.9196749926431328, 0.4658027781248113, None,\n",
652
- " 0.4658027781248113, 0.8259998604959788, None,\n",
653
- " 0.4658027781248113, 1.6563754811938778, None,\n",
654
- " 1.6563754811938778, 3.0418532519618227, None,\n",
655
- " -0.8813010871517453, -2.199100985517882, None,\n",
656
- " -0.7661793706775843, -2.24957108477712, None,\n",
657
- " -0.7661793706775843, -1.2429820010254153, None,\n",
658
- " 0.4748727167057778, 1.754298709580529, None,\n",
659
- " 0.35975100023161755, 1.677550898597754, None,\n",
660
- " -0.8813010871517453, 0.43649881121439116, None,\n",
661
- " -0.8045532761689715, 0.4748727167057778, None,\n",
662
- " -2.059704551527956, -0.9196749926431328, None],\n",
663
- " 'yaxis': 'y2'},\n",
664
- " {'customdata': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,\n",
665
- " 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28,\n",
666
- " 29, 30],\n",
667
- " 'marker': {'cmax': 1,\n",
668
- " 'cmin': 0,\n",
669
- " 'color': 'lightgray',\n",
670
- " 'colorbar': {'len': 0.8, 'title': {'text': 'Similarity'}, 'y': 0.5},\n",
671
- " 'colorscale': [[0.0, '#440154'], [0.1111111111111111,\n",
672
- " '#482878'], [0.2222222222222222,\n",
673
- " '#3e4989'], [0.3333333333333333,\n",
674
- " '#31688e'], [0.4444444444444444,\n",
675
- " '#26828e'], [0.5555555555555556,\n",
676
- " '#1f9e89'], [0.6666666666666666,\n",
677
- " '#35b779'], [0.7777777777777778,\n",
678
- " '#6ece58'], [0.8888888888888888,\n",
679
- " '#b5de2b'], [1.0, '#fde725']],\n",
680
- " 'size': 20},\n",
681
- " 'mode': 'markers+text',\n",
682
- " 'name': 'node',\n",
683
- " 'text': [C, C, C, C, C, C, C, C, O, C, C, C, C, C, C, C, C, C, C, O,\n",
684
- " C, O, C, C, O, O, C, C, C, C, C],\n",
685
- " 'textposition': 'middle center',\n",
686
- " 'type': 'scatter',\n",
687
- " 'uid': '07c41a52-ebdc-4197-bfba-0b0c5eb1e54c',\n",
688
- " 'x': [2.5583421472905834, 1.7753548365655238, 0.27584576911133163,\n",
689
- " -0.5071415416137278, -2.006650609067919, -2.7231723657970504,\n",
690
- " -4.222681433251242, -5.005668743976302, -6.505177811430492,\n",
691
- " -4.28914698724717, -2.7896379197929786, -2.0731161630638466,\n",
692
- " -0.5736070956096556, 0.2093802151154039, 1.7088892825695952,\n",
693
- " 2.4918765932946547, 3.9506394063295636, 4.069217095584485,\n",
694
- " 2.6837393248165426, 2.3344385156183947, 5.348643088459236,\n",
695
- " 5.0906689652143875, 3.4043110505061365, 1.1340403052652162,\n",
696
- " 0.2181749852801769, 2.0464747624767, 1.4716257851723231,\n",
697
- " 0.9259019718445355, -4.066550889929987, -5.711349241391562,\n",
698
- " -1.2236632983428586],\n",
699
- " 'xaxis': 'x2',\n",
700
- " 'y': [2.9569768914725025, 1.677550898597754, 1.7159248040891406,\n",
701
- " 0.43649881121439116, 0.4748727167057778, 1.7926726150719152,\n",
702
- " 1.8310465205633017, 0.5516205276885522, 0.5899944331799385,\n",
703
- " -0.7661793706775843, -0.8045532761689715, -2.122353174535109,\n",
704
- " -2.1607270800264953, -0.8813010871517453, -0.9196749926431328,\n",
705
- " 0.35975100023161755, 0.010450191033470002, -1.484855574223577,\n",
706
- " -2.059704551527956, -3.5184673645628646, -2.2678428849486365,\n",
707
- " 0.9853002332804177, 1.5503237033006856, 0.4658027781248113,\n",
708
- " 0.8259998604959788, 1.6563754811938778, 3.0418532519618227,\n",
709
- " -2.199100985517882, -2.24957108477712, -1.2429820010254153,\n",
710
- " 1.754298709580529],\n",
711
- " 'yaxis': 'y2'}],\n",
712
- " 'layout': {'annotations': [{'font': {'size': 16},\n",
713
- " 'showarrow': False,\n",
714
- " 'text': 'Spectrum',\n",
715
- " 'x': 0.27,\n",
716
- " 'xanchor': 'center',\n",
717
- " 'xref': 'paper',\n",
718
- " 'y': 1.0,\n",
719
- " 'yanchor': 'bottom',\n",
720
- " 'yref': 'paper'},\n",
721
- " {'font': {'size': 16},\n",
722
- " 'showarrow': False,\n",
723
- " 'text': 'Molecule',\n",
724
- " 'x': 0.8200000000000001,\n",
725
- " 'xanchor': 'center',\n",
726
- " 'xref': 'paper',\n",
727
- " 'y': 1.0,\n",
728
- " 'yanchor': 'bottom',\n",
729
- " 'yref': 'paper'}],\n",
730
- " 'showlegend': False,\n",
731
- " 'template': '...',\n",
732
- " 'title': {'text': 'Peak ↔ Node Similarity'},\n",
733
- " 'xaxis': {'anchor': 'y', 'domain': [0.0, 0.54], 'title': {'text': 'm/z'}},\n",
734
- " 'xaxis2': {'anchor': 'y2', 'domain': [0.64, 1.0], 'visible': False},\n",
735
- " 'yaxis': {'anchor': 'x', 'domain': [0.0, 1.0], 'title': {'text': 'Intensity'}},\n",
736
- " 'yaxis2': {'anchor': 'x2', 'domain': [0.0, 1.0], 'visible': False}}\n",
737
- "})"
738
- ]
739
- },
740
- "execution_count": 11,
741
- "metadata": {},
742
- "output_type": "execute_result"
743
- }
744
- ],
745
- "source": [
746
- "from rdkit import Chem\n",
747
- "\n",
748
- "# Data\n",
749
- "# i = 40991\n",
750
- "\n",
751
- "ms_id = \"MassSpecGymID0001014\"\n",
752
- "i = dataset.metadata[dataset.metadata['identifier'] == ms_id].index[0]\n",
753
- "s = dataset.metadata.iloc[i]['smiles']\n",
754
- "mol = Chem.MolFromSmiles(s)\n",
755
- "g = dataset[i]['mol']\n",
756
- "spec = dataset[i]['SpecFormula']\n",
757
- "\n",
758
- "peak_mzs, peak_intensities, peak_formulas = spectra_from_encoding(spec)\n",
759
- "\n",
760
- "# Embeddings\n",
761
- "model = model.to(torch.device('cpu'))\n",
762
- "model.eval()\n",
763
- "with torch.no_grad():\n",
764
- " spec_enc, mol_enc = model.forward(dataset[i], stage='test')\n",
765
- "\n",
766
- "fw = interactive_attention_visualization(spec_enc, mol_enc, peak_mzs, peak_intensities, peak_formulas, mol)\n",
767
- "fw\n"
768
- ]
769
- },
770
- {
771
- "cell_type": "code",
772
- "execution_count": null,
773
- "metadata": {},
774
- "outputs": [],
775
- "source": []
776
- }
777
- ],
778
- "metadata": {
779
- "kernelspec": {
780
- "display_name": "spec",
781
- "language": "python",
782
- "name": "python3"
783
- },
784
- "language_info": {
785
- "codemirror_mode": {
786
- "name": "ipython",
787
- "version": 3
788
- },
789
- "file_extension": ".py",
790
- "mimetype": "text/x-python",
791
- "name": "python",
792
- "nbconvert_exporter": "python",
793
- "pygments_lexer": "ipython3",
794
- "version": "3.11.7"
795
- },
796
- "orig_nbformat": 4
797
- },
798
- "nbformat": 4,
799
- "nbformat_minor": 2
800
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/fine-grained_vs_global.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/good_vs_bad_instances.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/hyperparameter_tuning_result.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/magma_script.ipynb DELETED
@@ -1,146 +0,0 @@
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/mol-spec_visualization.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/mol_visualization.ipynb DELETED
@@ -1,211 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 11,
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
- "<ellipse cx=\"361.6\" cy=\"246.9\" rx=\"18.4\" ry=\"18.4\" 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",
14
- "<ellipse cx=\"361.6\" cy=\"155.1\" rx=\"18.4\" ry=\"18.4\" 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",
15
- "<ellipse cx=\"274.3\" cy=\"126.7\" rx=\"18.4\" ry=\"18.4\" 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",
16
- "<ellipse cx=\"220.3\" cy=\"201.0\" rx=\"18.4\" ry=\"18.4\" 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",
17
- "<ellipse cx=\"274.3\" cy=\"275.3\" rx=\"18.4\" ry=\"18.4\" 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",
18
- "<ellipse cx=\"128.5\" cy=\"201.0\" rx=\"18.4\" ry=\"18.4\" 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",
19
- "<ellipse cx=\"82.6\" cy=\"280.7\" rx=\"18.4\" ry=\"18.9\" 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",
20
- "<ellipse cx=\"62.5\" cy=\"128.0\" rx=\"42.5\" ry=\"27.6\" class=\"atom-7\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
21
- "<path class=\"bond-0 atom-0 atom-1\" d=\"M 361.6,246.9 L 361.6,155.1\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
22
- "<path class=\"bond-1 atom-1 atom-2\" d=\"M 361.6,155.1 L 274.3,126.7\" 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-2 atom-2 atom-3\" d=\"M 274.3,126.7 L 232.0,185.0\" 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-3 atom-3 atom-4\" d=\"M 232.0,217.0 L 274.3,275.3\" 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-4 atom-3 atom-5\" d=\"M 207.5,201.0 L 128.5,201.0\" 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-5 atom-5 atom-6\" d=\"M 124.6,194.1 L 86.0,260.9\" 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-5 atom-5 atom-6\" d=\"M 136.5,201.0 L 97.9,267.8\" 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-6 atom-5 atom-7\" d=\"M 128.5,201.0 L 91.9,137.5\" 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-7 atom-4 atom-0\" d=\"M 274.3,275.3 L 361.6,246.9\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
30
- "<path d=\"M 361.6,242.3 L 361.6,246.9 L 357.3,248.3\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
31
- "<path d=\"M 361.6,159.7 L 361.6,155.1 L 357.3,153.7\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
32
- "<path d=\"M 278.7,128.1 L 274.3,126.7 L 272.2,129.6\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
33
- "<path d=\"M 272.2,272.4 L 274.3,275.3 L 278.7,273.9\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
34
- "<path d=\"M 132.5,201.0 L 128.5,201.0 L 126.7,197.8\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
35
- "<path class=\"atom-3\" d=\"M 214.6 188.0 L 223.1 201.8 Q 224.0 203.1, 225.3 205.6 Q 226.7 208.1, 226.8 208.2 L 226.8 188.0 L 230.2 188.0 L 230.2 214.0 L 226.6 214.0 L 217.5 198.9 Q 216.4 197.2, 215.3 195.2 Q 214.2 193.1, 213.9 192.5 L 213.9 214.0 L 210.5 214.0 L 210.5 188.0 L 214.6 188.0 \" fill=\"#000000\"/>\n",
36
- "<path class=\"atom-6\" d=\"M 70.7 280.6 Q 70.7 274.3, 73.8 270.9 Q 76.9 267.4, 82.6 267.4 Q 88.4 267.4, 91.5 270.9 Q 94.6 274.3, 94.6 280.6 Q 94.6 286.9, 91.4 290.5 Q 88.3 294.1, 82.6 294.1 Q 76.9 294.1, 73.8 290.5 Q 70.7 286.9, 70.7 280.6 M 82.6 291.1 Q 86.6 291.1, 88.7 288.5 Q 90.9 285.8, 90.9 280.6 Q 90.9 275.5, 88.7 272.9 Q 86.6 270.3, 82.6 270.3 Q 78.7 270.3, 76.5 272.9 Q 74.4 275.4, 74.4 280.6 Q 74.4 285.8, 76.5 288.5 Q 78.7 291.1, 82.6 291.1 \" fill=\"#000000\"/>\n",
37
- "<path class=\"atom-7\" d=\"M 32.4 108.5 L 36.0 108.5 L 36.0 119.5 L 49.3 119.5 L 49.3 108.5 L 52.8 108.5 L 52.8 134.5 L 49.3 134.5 L 49.3 122.5 L 36.0 122.5 L 36.0 134.5 L 32.4 134.5 L 32.4 108.5 \" fill=\"#000000\"/>\n",
38
- "<path class=\"atom-7\" d=\"M 57.9 133.6 Q 58.5 131.9, 60.0 131.1 Q 61.5 130.1, 63.6 130.1 Q 66.2 130.1, 67.6 131.5 Q 69.1 132.9, 69.1 135.4 Q 69.1 138.0, 67.2 140.4 Q 65.3 142.7, 61.5 145.5 L 69.4 145.5 L 69.4 147.5 L 57.8 147.5 L 57.8 145.9 Q 61.0 143.6, 62.9 141.9 Q 64.8 140.2, 65.8 138.7 Q 66.7 137.1, 66.7 135.6 Q 66.7 133.9, 65.8 133.0 Q 65.0 132.1, 63.6 132.1 Q 62.2 132.1, 61.3 132.6 Q 60.4 133.2, 59.7 134.4 L 57.9 133.6 \" fill=\"#000000\"/>\n",
39
- "<path class=\"atom-7\" d=\"M 76.9 108.5 L 85.4 122.3 Q 86.2 123.6, 87.6 126.1 Q 89.0 128.5, 89.0 128.7 L 89.0 108.5 L 92.5 108.5 L 92.5 134.5 L 88.9 134.5 L 79.8 119.4 Q 78.7 117.7, 77.6 115.6 Q 76.5 113.6, 76.1 113.0 L 76.1 134.5 L 72.8 134.5 L 72.8 108.5 L 76.9 108.5 \" fill=\"#000000\"/>\n",
40
- "</svg>"
41
- ],
42
- "text/plain": [
43
- "<IPython.core.display.SVG object>"
44
- ]
45
- },
46
- "execution_count": 11,
47
- "metadata": {},
48
- "output_type": "execute_result"
49
- }
50
- ],
51
- "source": [
52
- "from rdkit import Chem\n",
53
- "from rdkit.Chem import Draw\n",
54
- "from rdkit.Chem.Draw import rdMolDraw2D\n",
55
- "from IPython.display import SVG\n",
56
- "\n",
57
- "# Example molecule\n",
58
- "mol = Chem.MolFromSmiles(\"C1CCN(C1)C(=O)N\") \n",
59
- "Chem.rdDepictor.Compute2DCoords(mol)\n",
60
- "\n",
61
- "# Define colors for atom types\n",
62
- "atom_colors = {\n",
63
- " 6: (0.6, 0.6, 0.6), # Carbon = light gray\n",
64
- " 8: (0.98, 0.6, 0.6), # Oxygen = soft red/pink\n",
65
- " 7: (0.55, 0.63, 0.8), # Nitrogen = light blue\n",
66
- " 16: (0.8, 0.8, 0.55), # Sulfur = soft yellow\n",
67
- " 17: (0.65, 0.85, 0.65), # Chlorine = light green\n",
68
- " 1: (0.9, 0.9, 0.9), # Hydrogen = very light gray\n",
69
- "}\n",
70
- "\n",
71
- "\n",
72
- "# Default = muted purple (for other atoms)\n",
73
- "default_color = (0.8, 0.7, 0.9)\n",
74
- "\n",
75
- "# Assign highlight colors\n",
76
- "highlight_atoms = [atom.GetIdx() for atom in mol.GetAtoms()]\n",
77
- "highlight_colors = {\n",
78
- " atom.GetIdx(): atom_colors.get(atom.GetAtomicNum(), default_color)\n",
79
- " for atom in mol.GetAtoms()\n",
80
- "}\n",
81
- "\n",
82
- "highlight_colors = {atom.GetIdx():(0.6, 0.6, 0.6) for atom in mol.GetAtoms()}\n",
83
- "\n",
84
- "# Draw with transparent background\n",
85
- "drawer = rdMolDraw2D.MolDraw2DSVG(400, 400)\n",
86
- "drawer.drawOptions().clearBackground = False # 🔑 makes background transparent\n",
87
- "\n",
88
- "black = (0, 0, 0)\n",
89
- "drawer.drawOptions().setAtomPalette({\n",
90
- " 1: black, # Hydrogen\n",
91
- " 6: black, # Carbon\n",
92
- " 7: black, # Nitrogen\n",
93
- " 8: black, # Oxygen\n",
94
- " 9: black, # Fluorine\n",
95
- " 17: black, # Chlorine\n",
96
- " 16: black, # Sulfur\n",
97
- "})\n",
98
- "rdMolDraw2D.PrepareAndDrawMolecule(\n",
99
- " drawer,\n",
100
- " mol,\n",
101
- " highlightAtoms=highlight_atoms,\n",
102
- " highlightAtomColors=highlight_colors\n",
103
- ")\n",
104
- "drawer.FinishDrawing()\n",
105
- "\n",
106
- "# Clean up RDKit's extra XML headers\n",
107
- "svg = drawer.GetDrawingText().replace(\"svg:\", \"\")\n",
108
- "SVG(svg)\n"
109
- ]
110
- },
111
- {
112
- "cell_type": "code",
113
- "execution_count": 5,
114
- "metadata": {},
115
- "outputs": [
116
- {
117
- "data": {
118
- "image/png": "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",
119
- "text/plain": [
120
- "<Figure size 640x480 with 1 Axes>"
121
- ]
122
- },
123
- "metadata": {},
124
- "output_type": "display_data"
125
- }
126
- ],
127
- "source": [
128
- "import networkx as nx\n",
129
- "import matplotlib.pyplot as plt\n",
130
- "from rdkit import Chem\n",
131
- "\n",
132
- "# Example molecule\n",
133
- "mol = Chem.MolFromSmiles(\"C1CCN(C1)C(=O)N\") \n",
134
- "Chem.rdDepictor.Compute2DCoords(mol)\n",
135
- "\n",
136
- "# Define colors for atom types\n",
137
- "atom_colors = {\n",
138
- " 6: \"lightgray\", # Carbon\n",
139
- " 8: \"lightcoral\", # Oxygen\n",
140
- " 7: \"lightblue\", # Nitrogen\n",
141
- " 16: \"khaki\", # Sulfur\n",
142
- " 17: \"lightgreen\", # Chlorine\n",
143
- " 1: \"whitesmoke\", # Hydrogen\n",
144
- "}\n",
145
- "default_color = \"plum\"\n",
146
- "\n",
147
- "# Convert RDKit Mol → NetworkX graph\n",
148
- "G = nx.Graph()\n",
149
- "for atom in mol.GetAtoms():\n",
150
- " idx = atom.GetIdx()\n",
151
- " pos = mol.GetConformer().GetAtomPosition(idx)\n",
152
- " G.add_node(\n",
153
- " idx,\n",
154
- " label=atom.GetSymbol(),\n",
155
- " color=atom_colors.get(atom.GetAtomicNum(), default_color),\n",
156
- " pos=(pos.x, pos.y) # store RDKit 2D coords\n",
157
- " )\n",
158
- "for bond in mol.GetBonds():\n",
159
- " G.add_edge(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), order=bond.GetBondTypeAsDouble())\n",
160
- "\n",
161
- "# Extract positions\n",
162
- "pos = {n: (data[\"pos\"][0], data[\"pos\"][1]) for n, data in G.nodes(data=True)}\n",
163
- "\n",
164
- "# Draw nodes\n",
165
- "node_colors = [G.nodes[n][\"color\"] for n in G.nodes]\n",
166
- "nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=800, edgecolors=\"k\")\n",
167
- "\n",
168
- "# Draw edges with bond order as width\n",
169
- "# edge_widths = [1.5 * G[u][v][\"order\"] for u, v in G.edges()]\n",
170
- "nx.draw_networkx_edges(G, pos)\n",
171
- "\n",
172
- "# Draw atom labels\n",
173
- "labels = {n: G.nodes[n][\"label\"] for n in G.nodes}\n",
174
- "nx.draw_networkx_labels(G, pos, labels, font_size=12, font_weight=\"bold\")\n",
175
- "\n",
176
- "plt.axis(\"off\")\n",
177
- "plt.gca().set_aspect(\"equal\", \"box\") # keep proportions\n",
178
- "plt.show()\n"
179
- ]
180
- },
181
- {
182
- "cell_type": "code",
183
- "execution_count": null,
184
- "metadata": {},
185
- "outputs": [],
186
- "source": []
187
- }
188
- ],
189
- "metadata": {
190
- "kernelspec": {
191
- "display_name": "Python (spec)",
192
- "language": "python",
193
- "name": "spec"
194
- },
195
- "language_info": {
196
- "codemirror_mode": {
197
- "name": "ipython",
198
- "version": 3
199
- },
200
- "file_extension": ".py",
201
- "mimetype": "text/x-python",
202
- "name": "python",
203
- "nbconvert_exporter": "python",
204
- "pygments_lexer": "ipython3",
205
- "version": "3.11.7"
206
- },
207
- "orig_nbformat": 4
208
- },
209
- "nbformat": 4,
210
- "nbformat_minor": 2
211
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/msgym_result.ipynb DELETED
@@ -1,799 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "markdown",
5
- "metadata": {},
6
- "source": [
7
- "Result for all models on the MSGym benchmark"
8
- ]
9
- },
10
- {
11
- "cell_type": "markdown",
12
- "metadata": {},
13
- "source": [
14
- "1. Load data\n",
15
- "2. Compute MCES\n",
16
- "3. Compute confidence interval"
17
- ]
18
- },
19
- {
20
- "cell_type": "code",
21
- "execution_count": 1,
22
- "metadata": {},
23
- "outputs": [],
24
- "source": [
25
- "import pickle\n",
26
- "import pandas as pd\n",
27
- "import numpy as np"
28
- ]
29
- },
30
- {
31
- "cell_type": "code",
32
- "execution_count": 2,
33
- "metadata": {},
34
- "outputs": [],
35
- "source": [
36
- "import sys\n",
37
- "sys.path.append('..')\n",
38
- "import flare.utils.eval as eval_utils"
39
- ]
40
- },
41
- {
42
- "cell_type": "code",
43
- "execution_count": 5,
44
- "metadata": {},
45
- "outputs": [],
46
- "source": [
47
- "from tqdm import tqdm"
48
- ]
49
- },
50
- {
51
- "cell_type": "markdown",
52
- "metadata": {},
53
- "source": [
54
- "## Candidates by Mass"
55
- ]
56
- },
57
- {
58
- "cell_type": "markdown",
59
- "metadata": {},
60
- "source": [
61
- "### load data"
62
- ]
63
- },
64
- {
65
- "cell_type": "code",
66
- "execution_count": 27,
67
- "metadata": {},
68
- "outputs": [],
69
- "source": [
70
- "flare_pth = \"../experiments/20250930_optimized_flare_42/result_MassSpecGym_retrieval_candidates_mass.pkl\"\n",
71
- "flare_w_sirius_pth = \"../experiments/20251001_flare_sirius_labels_42/result_MassSpecGym_retrieval_candidates_mass.pkl\"\n",
72
- "mzint_token_pth = \"../experiments/20250929_flare_mzIntTokens/result_MassSpecGym_retrieval_candidates_mass.pkl\"\n",
73
- "crossatt_pth = \"../experiments/20250914_crossAttnModel/result_MassSpecGym_retrieval_candidates_mass.pkl\"\n",
74
- "\n",
75
- "def load_data(pth):\n",
76
- " with open(pth, \"rb\") as f:\n",
77
- " data = pickle.load(f)\n",
78
- " return data\n",
79
- "\n",
80
- "flare = load_data(flare_pth)\n",
81
- "sirius_labels = load_data(flare_w_sirius_pth)\n",
82
- "mzint_token = load_data(mzint_token_pth)\n",
83
- "crossatt = load_data(crossatt_pth)"
84
- ]
85
- },
86
- {
87
- "cell_type": "code",
88
- "execution_count": 5,
89
- "metadata": {},
90
- "outputs": [],
91
- "source": [
92
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
93
- " \n",
94
- " # get target\n",
95
- " result['target'] = result.apply(lambda row: eval_utils.get_target(row['candidates'], row['labels']), axis=1)\n",
96
- "\n",
97
- " # get cand@1\n",
98
- " result['cand@1'] = result.apply(lambda row: eval_utils.get_top_cand(row['candidates'], row['scores']), axis=1)\n",
99
- "\n",
100
- " # convert rank to hit rates\n",
101
- " result[['hit_rate@1', 'hit_rate@5', 'hit_rate@20']] = result.apply(lambda row: eval_utils.convert_rank_to_hit_rates(row, 'rank'), axis=1, result_type='expand')\n",
102
- " \n"
103
- ]
104
- },
105
- {
106
- "cell_type": "markdown",
107
- "metadata": {},
108
- "source": [
109
- "### compute mces"
110
- ]
111
- },
112
- {
113
- "cell_type": "code",
114
- "execution_count": 6,
115
- "metadata": {},
116
- "outputs": [
117
- {
118
- "name": "stdout",
119
- "output_type": "stream",
120
- "text": [
121
- "Number of unique target_cand combo: 29247\n"
122
- ]
123
- }
124
- ],
125
- "source": [
126
- "target_cand_dict = {}\n",
127
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
128
- " target_cand_list = list(zip(result['target'].tolist(), result['cand@1'].tolist()))\n",
129
- " for target, cand in target_cand_list:\n",
130
- " pair = f'{target}::{cand}'\n",
131
- " if pair in target_cand_dict:\n",
132
- " continue\n",
133
- "\n",
134
- " target_cand_dict[pair] = [target, cand]\n",
135
- " \n",
136
- "target_cand_list = list(target_cand_dict.values())\n",
137
- "print(\"Number of unique target_cand combo: \", len(target_cand_list))"
138
- ]
139
- },
140
- {
141
- "cell_type": "code",
142
- "execution_count": 7,
143
- "metadata": {},
144
- "outputs": [
145
- {
146
- "name": "stderr",
147
- "output_type": "stream",
148
- "text": [
149
- "100%|██████████| 29247/29247 [09:35<00:00, 50.84it/s] \n"
150
- ]
151
- }
152
- ],
153
- "source": [
154
- "mces_result = eval_utils.Compute_Myopic_MCES.compute_mces_parallel(target_cand_list)"
155
- ]
156
- },
157
- {
158
- "cell_type": "code",
159
- "execution_count": 8,
160
- "metadata": {},
161
- "outputs": [],
162
- "source": [
163
- "# Assign MCES to ranking\n",
164
- "tar_cand_mces_dict = {}\n",
165
- "for tar_cand, mces in mces_result:\n",
166
- " tar, can = tar_cand\n",
167
- " if f'{tar}::{can}' not in tar_cand_mces_dict:\n",
168
- " tar_cand_mces_dict[f'{tar}::{can}'] = mces\n",
169
- " \n",
170
- "def assign_mces(tar, can):\n",
171
- " return tar_cand_mces_dict[f'{tar}::{can}']"
172
- ]
173
- },
174
- {
175
- "cell_type": "code",
176
- "execution_count": 9,
177
- "metadata": {},
178
- "outputs": [],
179
- "source": [
180
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
181
- " result['mces'] = result.apply(lambda row: assign_mces(row['target'], row['cand@1']), axis=1)"
182
- ]
183
- },
184
- {
185
- "cell_type": "code",
186
- "execution_count": 11,
187
- "metadata": {},
188
- "outputs": [],
189
- "source": [
190
- "# save result\n",
191
- "for result, pth in zip([flare, sirius_labels, mzint_token, crossatt], [flare_pth, flare_w_sirius_pth, mzint_token_pth, crossatt_pth]):\n",
192
- " result.to_pickle(pth)"
193
- ]
194
- },
195
- {
196
- "cell_type": "markdown",
197
- "metadata": {},
198
- "source": [
199
- "### compute confidence interval"
200
- ]
201
- },
202
- {
203
- "cell_type": "code",
204
- "execution_count": null,
205
- "metadata": {},
206
- "outputs": [],
207
- "source": [
208
- "tqdm.pandas()"
209
- ]
210
- },
211
- {
212
- "cell_type": "code",
213
- "execution_count": 28,
214
- "metadata": {},
215
- "outputs": [
216
- {
217
- "name": "stderr",
218
- "output_type": "stream",
219
- "text": [
220
- "100%|██████████| 4/4 [00:35<00:00, 8.79s/it]\n"
221
- ]
222
- },
223
- {
224
- "data": {
225
- "text/plain": [
226
- "hit_rate@1 41.92-44.38\n",
227
- "hit_rate@5 74.54-76.66\n",
228
- "hit_rate@20 92.21-93.50\n",
229
- "mces 7.63-8.10\n",
230
- "dtype: object"
231
- ]
232
- },
233
- "metadata": {},
234
- "output_type": "display_data"
235
- },
236
- {
237
- "name": "stdout",
238
- "output_type": "stream",
239
- "text": [
240
- "===========================================================\n"
241
- ]
242
- },
243
- {
244
- "name": "stderr",
245
- "output_type": "stream",
246
- "text": [
247
- "100%|██████████| 4/4 [00:35<00:00, 8.79s/it]\n"
248
- ]
249
- },
250
- {
251
- "data": {
252
- "text/plain": [
253
- "hit_rate@1 42.14-44.57\n",
254
- "hit_rate@5 72.11-74.31\n",
255
- "hit_rate@20 91.51-92.87\n",
256
- "mces 7.87-8.36\n",
257
- "dtype: object"
258
- ]
259
- },
260
- "metadata": {},
261
- "output_type": "display_data"
262
- },
263
- {
264
- "name": "stdout",
265
- "output_type": "stream",
266
- "text": [
267
- "===========================================================\n"
268
- ]
269
- },
270
- {
271
- "name": "stderr",
272
- "output_type": "stream",
273
- "text": [
274
- "100%|██████████| 4/4 [00:35<00:00, 8.78s/it]\n"
275
- ]
276
- },
277
- {
278
- "data": {
279
- "text/plain": [
280
- "hit_rate@1 6.55-7.82\n",
281
- "hit_rate@5 21.14-23.13\n",
282
- "hit_rate@20 42.98-45.48\n",
283
- "mces 25.71-26.51\n",
284
- "dtype: object"
285
- ]
286
- },
287
- "metadata": {},
288
- "output_type": "display_data"
289
- },
290
- {
291
- "name": "stdout",
292
- "output_type": "stream",
293
- "text": [
294
- "===========================================================\n"
295
- ]
296
- },
297
- {
298
- "name": "stderr",
299
- "output_type": "stream",
300
- "text": [
301
- "100%|██████████| 4/4 [00:35<00:00, 8.80s/it]\n"
302
- ]
303
- },
304
- {
305
- "data": {
306
- "text/plain": [
307
- "hit_rate@1 0.47-0.86\n",
308
- "hit_rate@5 2.55-3.39\n",
309
- "hit_rate@20 11.84-13.50\n",
310
- "mces 26.61-27.22\n",
311
- "dtype: object"
312
- ]
313
- },
314
- "metadata": {},
315
- "output_type": "display_data"
316
- },
317
- {
318
- "name": "stdout",
319
- "output_type": "stream",
320
- "text": [
321
- "===========================================================\n"
322
- ]
323
- }
324
- ],
325
- "source": [
326
- "hit_rate_metrics = ['hit_rate@1', 'hit_rate@5', 'hit_rate@20', 'mces']\n",
327
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
328
- " result[hit_rate_metrics[:-1]]*=100\n",
329
- " df_ci = result[hit_rate_metrics].progress_apply(eval_utils.get_ci)\n",
330
- " display(df_ci)\n",
331
- " print(\"===========================================================\")"
332
- ]
333
- },
334
- {
335
- "cell_type": "code",
336
- "execution_count": 30,
337
- "metadata": {},
338
- "outputs": [
339
- {
340
- "data": {
341
- "text/html": [
342
- "<div>\n",
343
- "<style scoped>\n",
344
- " .dataframe tbody tr th:only-of-type {\n",
345
- " vertical-align: middle;\n",
346
- " }\n",
347
- "\n",
348
- " .dataframe tbody tr th {\n",
349
- " vertical-align: top;\n",
350
- " }\n",
351
- "\n",
352
- " .dataframe thead th {\n",
353
- " text-align: right;\n",
354
- " }\n",
355
- "</style>\n",
356
- "<table border=\"1\" class=\"dataframe\">\n",
357
- " <thead>\n",
358
- " <tr style=\"text-align: right;\">\n",
359
- " <th></th>\n",
360
- " <th>hit_rate@1</th>\n",
361
- " <th>hit_rate@5</th>\n",
362
- " <th>hit_rate@20</th>\n",
363
- " <th>mces</th>\n",
364
- " </tr>\n",
365
- " </thead>\n",
366
- " <tbody>\n",
367
- " <tr>\n",
368
- " <th>flare</th>\n",
369
- " <td>43.15</td>\n",
370
- " <td>75.59</td>\n",
371
- " <td>92.89</td>\n",
372
- " <td>7.85</td>\n",
373
- " </tr>\n",
374
- " <tr>\n",
375
- " <th>flare_w_sirius_labels</th>\n",
376
- " <td>43.33</td>\n",
377
- " <td>73.22</td>\n",
378
- " <td>92.25</td>\n",
379
- " <td>8.11</td>\n",
380
- " </tr>\n",
381
- " <tr>\n",
382
- " <th>mzint_tokens</th>\n",
383
- " <td>7.17</td>\n",
384
- " <td>22.12</td>\n",
385
- " <td>44.24</td>\n",
386
- " <td>26.10</td>\n",
387
- " </tr>\n",
388
- " <tr>\n",
389
- " <th>crossatten</th>\n",
390
- " <td>0.64</td>\n",
391
- " <td>2.94</td>\n",
392
- " <td>12.64</td>\n",
393
- " <td>26.91</td>\n",
394
- " </tr>\n",
395
- " </tbody>\n",
396
- "</table>\n",
397
- "</div>"
398
- ],
399
- "text/plain": [
400
- " hit_rate@1 hit_rate@5 hit_rate@20 mces\n",
401
- "flare 43.15 75.59 92.89 7.85\n",
402
- "flare_w_sirius_labels 43.33 73.22 92.25 8.11\n",
403
- "mzint_tokens 7.17 22.12 44.24 26.10\n",
404
- "crossatten 0.64 2.94 12.64 26.91"
405
- ]
406
- },
407
- "execution_count": 30,
408
- "metadata": {},
409
- "output_type": "execute_result"
410
- }
411
- ],
412
- "source": [
413
- "result_data = {}\n",
414
- "for result, method in zip([flare, sirius_labels, mzint_token, crossatt], ['flare', 'flare_w_sirius_labels', 'mzint_tokens', 'crossatten']):\n",
415
- " result_data[method] = []\n",
416
- " for m in hit_rate_metrics:\n",
417
- " val = np.mean(result[m])\n",
418
- " result_data[method].append(round(val,2))\n",
419
- "\n",
420
- "\n",
421
- "result_df = pd.DataFrame.from_dict(result_data, orient='index', columns=hit_rate_metrics)\n",
422
- "result_df"
423
- ]
424
- },
425
- {
426
- "cell_type": "markdown",
427
- "metadata": {},
428
- "source": [
429
- "## Candidates by Formula"
430
- ]
431
- },
432
- {
433
- "cell_type": "code",
434
- "execution_count": 42,
435
- "metadata": {},
436
- "outputs": [],
437
- "source": [
438
- "flare_pth = \"../experiments/20250930_optimized_flare_42/result_MassSpecGym_retrieval_candidates_formula.pkl\"\n",
439
- "flare_w_sirius_pth = \"../experiments/20251001_flare_sirius_labels_42/result_MassSpecGym_retrieval_candidates_formula.pkl\"\n",
440
- "mzint_token_pth = \"../experiments/20250929_flare_mzIntTokens/result_MassSpecGym_retrieval_candidates_formula.pkl\"\n",
441
- "crossatt_pth = \"../experiments/20250914_crossAttnModel/result_MassSpecGym_retrieval_candidates_formula.pkl\"\n",
442
- "\n",
443
- "def load_data(pth):\n",
444
- " with open(pth, \"rb\") as f:\n",
445
- " data = pickle.load(f)\n",
446
- " return data\n",
447
- "\n",
448
- "flare = load_data(flare_pth)\n",
449
- "sirius_labels = load_data(flare_w_sirius_pth)\n",
450
- "mzint_token = load_data(mzint_token_pth)\n",
451
- "crossatt = load_data(crossatt_pth)"
452
- ]
453
- },
454
- {
455
- "cell_type": "code",
456
- "execution_count": 32,
457
- "metadata": {},
458
- "outputs": [],
459
- "source": [
460
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
461
- " \n",
462
- " # get target\n",
463
- " result['target'] = result.apply(lambda row: eval_utils.get_target(row['candidates'], row['labels']), axis=1)\n",
464
- "\n",
465
- " # get cand@1\n",
466
- " result['cand@1'] = result.apply(lambda row: eval_utils.get_top_cand(row['candidates'], row['scores']), axis=1)\n",
467
- "\n",
468
- " # convert rank to hit rates\n",
469
- " result[['hit_rate@1', 'hit_rate@5', 'hit_rate@20']] = result.apply(lambda row: eval_utils.convert_rank_to_hit_rates(row, 'rank'), axis=1, result_type='expand')\n",
470
- " \n"
471
- ]
472
- },
473
- {
474
- "cell_type": "markdown",
475
- "metadata": {},
476
- "source": [
477
- "### compute mces"
478
- ]
479
- },
480
- {
481
- "cell_type": "code",
482
- "execution_count": 33,
483
- "metadata": {},
484
- "outputs": [
485
- {
486
- "name": "stdout",
487
- "output_type": "stream",
488
- "text": [
489
- "Number of unique target_cand combo: 28985\n"
490
- ]
491
- }
492
- ],
493
- "source": [
494
- "target_cand_dict = {}\n",
495
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
496
- " target_cand_list = list(zip(result['target'].tolist(), result['cand@1'].tolist()))\n",
497
- " for target, cand in target_cand_list:\n",
498
- " pair = f'{target}::{cand}'\n",
499
- " if pair in target_cand_dict:\n",
500
- " continue\n",
501
- "\n",
502
- " target_cand_dict[pair] = [target, cand]\n",
503
- " \n",
504
- "target_cand_list = list(target_cand_dict.values())\n",
505
- "print(\"Number of unique target_cand combo: \", len(target_cand_list))"
506
- ]
507
- },
508
- {
509
- "cell_type": "code",
510
- "execution_count": null,
511
- "metadata": {},
512
- "outputs": [
513
- {
514
- "name": "stderr",
515
- "output_type": "stream",
516
- "text": [
517
- "100%|██████████| 28985/28985 [16:12<00:00, 29.82it/s] \n"
518
- ]
519
- }
520
- ],
521
- "source": [
522
- "# 16 minutes\n",
523
- "mces_result = eval_utils.Compute_Myopic_MCES.compute_mces_parallel(target_cand_list)"
524
- ]
525
- },
526
- {
527
- "cell_type": "code",
528
- "execution_count": 38,
529
- "metadata": {},
530
- "outputs": [],
531
- "source": [
532
- "# Assign MCES to ranking\n",
533
- "tar_cand_mces_dict = {}\n",
534
- "for tar_cand, mces in mces_result:\n",
535
- " tar, can = tar_cand\n",
536
- " if f'{tar}::{can}' not in tar_cand_mces_dict:\n",
537
- " tar_cand_mces_dict[f'{tar}::{can}'] = mces\n",
538
- " \n",
539
- "def assign_mces(tar, can):\n",
540
- " return tar_cand_mces_dict[f'{tar}::{can}']\n",
541
- "\n",
542
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
543
- " result['mces'] = result.apply(lambda row: assign_mces(row['target'], row['cand@1']), axis=1)"
544
- ]
545
- },
546
- {
547
- "cell_type": "code",
548
- "execution_count": 39,
549
- "metadata": {},
550
- "outputs": [],
551
- "source": [
552
- "# save result\n",
553
- "for result, pth in zip([flare, sirius_labels, mzint_token, crossatt], [flare_pth, flare_w_sirius_pth, mzint_token_pth, crossatt_pth]):\n",
554
- " result.to_pickle(pth)"
555
- ]
556
- },
557
- {
558
- "cell_type": "markdown",
559
- "metadata": {},
560
- "source": [
561
- "### compute confidence interval"
562
- ]
563
- },
564
- {
565
- "cell_type": "code",
566
- "execution_count": 48,
567
- "metadata": {},
568
- "outputs": [
569
- {
570
- "name": "stderr",
571
- "output_type": "stream",
572
- "text": [
573
- "100%|██████████| 4/4 [00:34<00:00, 8.54s/it]\n"
574
- ]
575
- },
576
- {
577
- "data": {
578
- "text/plain": [
579
- "hit_rate@1 21.65-23.72\n",
580
- "hit_rate@5 48.75-51.20\n",
581
- "hit_rate@20 74.06-76.20\n",
582
- "mces 8.82-9.18\n",
583
- "dtype: object"
584
- ]
585
- },
586
- "metadata": {},
587
- "output_type": "display_data"
588
- },
589
- {
590
- "name": "stdout",
591
- "output_type": "stream",
592
- "text": [
593
- "===========================================================\n"
594
- ]
595
- },
596
- {
597
- "name": "stderr",
598
- "output_type": "stream",
599
- "text": [
600
- "100%|██████████| 4/4 [00:34<00:00, 8.75s/it]\n"
601
- ]
602
- },
603
- {
604
- "data": {
605
- "text/plain": [
606
- "hit_rate@1 21.39-23.42\n",
607
- "hit_rate@5 47.43-49.88\n",
608
- "hit_rate@20 73.32-75.40\n",
609
- "mces 8.89-9.25\n",
610
- "dtype: object"
611
- ]
612
- },
613
- "metadata": {},
614
- "output_type": "display_data"
615
- },
616
- {
617
- "name": "stdout",
618
- "output_type": "stream",
619
- "text": [
620
- "===========================================================\n"
621
- ]
622
- },
623
- {
624
- "name": "stderr",
625
- "output_type": "stream",
626
- "text": [
627
- "100%|██████████| 4/4 [00:35<00:00, 8.78s/it]\n"
628
- ]
629
- },
630
- {
631
- "data": {
632
- "text/plain": [
633
- "hit_rate@1 8.29-9.71\n",
634
- "hit_rate@5 24.35-26.48\n",
635
- "hit_rate@20 50.80-53.32\n",
636
- "mces 12.96-13.31\n",
637
- "dtype: object"
638
- ]
639
- },
640
- "metadata": {},
641
- "output_type": "display_data"
642
- },
643
- {
644
- "name": "stdout",
645
- "output_type": "stream",
646
- "text": [
647
- "===========================================================\n"
648
- ]
649
- },
650
- {
651
- "name": "stderr",
652
- "output_type": "stream",
653
- "text": [
654
- "100%|██████████| 4/4 [00:34<00:00, 8.75s/it]\n"
655
- ]
656
- },
657
- {
658
- "data": {
659
- "text/plain": [
660
- "hit_rate@1 3.17-4.09\n",
661
- "hit_rate@5 11.44-13.04\n",
662
- "hit_rate@20 26.98-29.23\n",
663
- "mces 14.41-14.71\n",
664
- "dtype: object"
665
- ]
666
- },
667
- "metadata": {},
668
- "output_type": "display_data"
669
- },
670
- {
671
- "name": "stdout",
672
- "output_type": "stream",
673
- "text": [
674
- "===========================================================\n"
675
- ]
676
- }
677
- ],
678
- "source": [
679
- "hit_rate_metrics = ['hit_rate@1', 'hit_rate@5', 'hit_rate@20', 'mces']\n",
680
- "for result in [flare, sirius_labels, mzint_token, crossatt]:\n",
681
- " result[hit_rate_metrics[:-1]]*=100\n",
682
- " df_ci = result[hit_rate_metrics].progress_apply(eval_utils.get_ci)\n",
683
- " display(df_ci)\n",
684
- " print(\"===========================================================\")"
685
- ]
686
- },
687
- {
688
- "cell_type": "code",
689
- "execution_count": 49,
690
- "metadata": {},
691
- "outputs": [
692
- {
693
- "data": {
694
- "text/html": [
695
- "<div>\n",
696
- "<style scoped>\n",
697
- " .dataframe tbody tr th:only-of-type {\n",
698
- " vertical-align: middle;\n",
699
- " }\n",
700
- "\n",
701
- " .dataframe tbody tr th {\n",
702
- " vertical-align: top;\n",
703
- " }\n",
704
- "\n",
705
- " .dataframe thead th {\n",
706
- " text-align: right;\n",
707
- " }\n",
708
- "</style>\n",
709
- "<table border=\"1\" class=\"dataframe\">\n",
710
- " <thead>\n",
711
- " <tr style=\"text-align: right;\">\n",
712
- " <th></th>\n",
713
- " <th>hit_rate@1</th>\n",
714
- " <th>hit_rate@5</th>\n",
715
- " <th>hit_rate@20</th>\n",
716
- " <th>mces</th>\n",
717
- " </tr>\n",
718
- " </thead>\n",
719
- " <tbody>\n",
720
- " <tr>\n",
721
- " <th>flare</th>\n",
722
- " <td>22.66</td>\n",
723
- " <td>50.00</td>\n",
724
- " <td>75.15</td>\n",
725
- " <td>9.00</td>\n",
726
- " </tr>\n",
727
- " <tr>\n",
728
- " <th>flare_w_sirius_labels</th>\n",
729
- " <td>22.45</td>\n",
730
- " <td>48.64</td>\n",
731
- " <td>74.37</td>\n",
732
- " <td>9.07</td>\n",
733
- " </tr>\n",
734
- " <tr>\n",
735
- " <th>mzint_tokens</th>\n",
736
- " <td>8.98</td>\n",
737
- " <td>25.38</td>\n",
738
- " <td>52.04</td>\n",
739
- " <td>13.14</td>\n",
740
- " </tr>\n",
741
- " <tr>\n",
742
- " <th>crossatten</th>\n",
743
- " <td>3.62</td>\n",
744
- " <td>12.25</td>\n",
745
- " <td>28.10</td>\n",
746
- " <td>14.56</td>\n",
747
- " </tr>\n",
748
- " </tbody>\n",
749
- "</table>\n",
750
- "</div>"
751
- ],
752
- "text/plain": [
753
- " hit_rate@1 hit_rate@5 hit_rate@20 mces\n",
754
- "flare 22.66 50.00 75.15 9.00\n",
755
- "flare_w_sirius_labels 22.45 48.64 74.37 9.07\n",
756
- "mzint_tokens 8.98 25.38 52.04 13.14\n",
757
- "crossatten 3.62 12.25 28.10 14.56"
758
- ]
759
- },
760
- "execution_count": 49,
761
- "metadata": {},
762
- "output_type": "execute_result"
763
- }
764
- ],
765
- "source": [
766
- "result_data = {}\n",
767
- "for result, method in zip([flare, sirius_labels, mzint_token, crossatt], ['flare', 'flare_w_sirius_labels', 'mzint_tokens', 'crossatten']):\n",
768
- " result_data[method] = []\n",
769
- " for m in hit_rate_metrics:\n",
770
- " val = np.mean(result[m])\n",
771
- " result_data[method].append(round(val,2))\n",
772
- "\n",
773
- "\n",
774
- "result_df = pd.DataFrame.from_dict(result_data, orient='index', columns=hit_rate_metrics)\n",
775
- "result_df"
776
- ]
777
- },
778
- {
779
- "cell_type": "code",
780
- "execution_count": null,
781
- "metadata": {},
782
- "outputs": [],
783
- "source": []
784
- }
785
- ],
786
- "metadata": {
787
- "kernelspec": {
788
- "display_name": "spec",
789
- "language": "python",
790
- "name": "python3"
791
- },
792
- "language_info": {
793
- "name": "python",
794
- "version": "3.11.7"
795
- }
796
- },
797
- "nbformat": 4,
798
- "nbformat_minor": 2
799
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/peak_embedding_UMAP.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/peak_formula_analysis.ipynb DELETED
@@ -1,347 +0,0 @@
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": 2,
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 flare.utils.data import get_spec_featurizer, get_mol_featurizer, get_ms_dataset,get_test_ms_dataset\n",
39
- "from flare.utils.models import get_model\n",
40
- "\n",
41
- "from flare.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": 3,
52
- "id": "9ba93f86",
53
- "metadata": {},
54
- "outputs": [
55
- {
56
- "name": "stdout",
57
- "output_type": "stream",
58
- "text": [
59
- "Data path: /r/hassounlab/spectra_data/msgym/MassSpecGym.tsv\n",
60
- "Processing formula spectra\n"
61
- ]
62
- },
63
- {
64
- "name": "stderr",
65
- "output_type": "stream",
66
- "text": [
67
- "100%|██████████| 231104/231104 [50:06<00:00, 76.87it/s] \n"
68
- ]
69
- }
70
- ],
71
- "source": [
72
- "# Load model and data\n",
73
- "# param_pth = '/data/yzhouc01/FILIP-MS/experiments/20250824_filipContrastive/lightning_logs/version_0/hparams.yaml'\n",
74
- "param_pth = \"/data/yzhouc01/FILIP-MS/flare/params_formSpec.yaml\"\n",
75
- "with open(param_pth) as f:\n",
76
- " params = yaml.load(f, Loader=yaml.FullLoader)\n",
77
- "# params['dataset_pth'] = \"/data/yzhouc01/MVP/data/sample/data.tsv\"\n",
78
- "\n",
79
- "spec_featurizer = get_spec_featurizer(params['spectra_view'], params)\n",
80
- "mol_featurizer = get_mol_featurizer(params['molecule_view'], params)\n",
81
- "dataset = get_test_ms_dataset(params['spectra_view'], params['molecule_view'], spec_featurizer, mol_featurizer, params)"
82
- ]
83
- },
84
- {
85
- "cell_type": "code",
86
- "execution_count": 4,
87
- "id": "bcb28630",
88
- "metadata": {},
89
- "outputs": [
90
- {
91
- "data": {
92
- "text/plain": [
93
- "{'precursor_mz': 288.1225,\n",
94
- " 'formulas': array(['C7H6', 'C6H4O3', 'C13H12O', 'C14H12O3', 'C14H15NO3', 'C16H17NO4'],\n",
95
- " dtype='<U9'),\n",
96
- " 'precursor_formula': 'C16H18NO4'}"
97
- ]
98
- },
99
- "execution_count": 4,
100
- "metadata": {},
101
- "output_type": "execute_result"
102
- }
103
- ],
104
- "source": [
105
- "dataset.spectra[1].metadata"
106
- ]
107
- },
108
- {
109
- "cell_type": "code",
110
- "execution_count": 4,
111
- "id": "fbebdab3",
112
- "metadata": {},
113
- "outputs": [
114
- {
115
- "data": {
116
- "text/plain": [
117
- "{'precursor_mz': 226.0716,\n",
118
- " 'formulas': array(['C5H5O2', 'C6H6O', 'C3H4NO3', 'C7H6O', 'C6H3O2', 'C3H5NO4',\n",
119
- " 'C7H6O2', 'C7H6NO2', 'C8H4NO2', 'C7H6NO3', 'C8H9NO3', 'C7H6NO5',\n",
120
- " 'C9H8NO4', 'C10H10NO4', 'C10H11NO5'], dtype='<U9'),\n",
121
- " 'precursor_formula': 'C10H12NO5'}"
122
- ]
123
- },
124
- "execution_count": 4,
125
- "metadata": {},
126
- "output_type": "execute_result"
127
- }
128
- ],
129
- "source": [
130
- "dataset.spectra[0].metadata"
131
- ]
132
- },
133
- {
134
- "cell_type": "code",
135
- "execution_count": 9,
136
- "id": "268c6470",
137
- "metadata": {},
138
- "outputs": [
139
- {
140
- "data": {
141
- "text/plain": [
142
- "{'precursor_mz': 226.0716,\n",
143
- " 'formulas': array(['C6H6O', 'C4H3O3', 'C5H5O2', 'C6H6O', 'C4H3O3', 'C5H5O2',\n",
144
- " 'C3H4NO3', 'C7H6O', 'C7H6O', 'C6H3O2', 'C6H3O2', 'C3H5NO4',\n",
145
- " 'C7H6O2', 'C7H6O2', 'C7H6O2', 'C7H6O2', 'C7H6NO2', 'C7H6NO2',\n",
146
- " 'C7H6NO2', 'C8H4NO2', 'C8H4NO2', 'C7H6NO3', 'C8H9NO3', 'C8H9NO3',\n",
147
- " 'C8H9NO3', 'C8H9NO3', 'C8H10NO4', 'C7H9NO5', 'C7H6NO5', 'C9H8NO4',\n",
148
- " 'C10H10NO4', 'C9H8NO5', 'C10H11NO5'], dtype='<U9'),\n",
149
- " 'precursor_formula': 'C10H12NO5'}"
150
- ]
151
- },
152
- "execution_count": 9,
153
- "metadata": {},
154
- "output_type": "execute_result"
155
- }
156
- ],
157
- "source": [
158
- "dataset.spectra[0].metadata"
159
- ]
160
- },
161
- {
162
- "cell_type": "markdown",
163
- "id": "4a9f0227",
164
- "metadata": {},
165
- "source": [
166
- "# SIRIUS subformulas"
167
- ]
168
- },
169
- {
170
- "cell_type": "code",
171
- "execution_count": null,
172
- "id": "5f40603f",
173
- "metadata": {},
174
- "outputs": [],
175
- "source": [
176
- "import numpy as np\n",
177
- "\n",
178
- "import matplotlib.pyplot as plt\n",
179
- "\n",
180
- "# Collect number of formulas per spectrum\n",
181
- "n_formulas = [len(d.metadata['formulas']) for d in dataset.spectra]\n",
182
- "\n",
183
- "# Calculate mean and median\n",
184
- "mean_n_formulas = np.mean(n_formulas)\n",
185
- "median_n_formulas = np.median(n_formulas)\n",
186
- "\n",
187
- "# Plot histogram\n",
188
- "plt.hist(n_formulas, bins=30, alpha=0.7, color='skyblue')\n",
189
- "plt.axvline(mean_n_formulas, color='red', linestyle='dashed', linewidth=2, label=f'Mean: {mean_n_formulas:.2f}')\n",
190
- "plt.axvline(median_n_formulas, color='green', linestyle='dashed', linewidth=2, label=f'Median: {median_n_formulas:.2f}')\n",
191
- "plt.xlabel('Number of formulas per spectrum')\n",
192
- "plt.ylabel('Count')\n",
193
- "plt.title('Distribution of Number of Formulas per Spectrum (SIRIUS labels)')\n",
194
- "plt.legend()\n",
195
- "plt.show()"
196
- ]
197
- },
198
- {
199
- "cell_type": "markdown",
200
- "id": "170af068",
201
- "metadata": {},
202
- "source": [
203
- "# MIST subformulas"
204
- ]
205
- },
206
- {
207
- "cell_type": "markdown",
208
- "id": "4798447e",
209
- "metadata": {},
210
- "source": []
211
- },
212
- {
213
- "cell_type": "code",
214
- "execution_count": 17,
215
- "id": "5612e3dc",
216
- "metadata": {},
217
- "outputs": [
218
- {
219
- "data": {
220
- "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==",
221
- "text/plain": [
222
- "<Figure size 640x480 with 1 Axes>"
223
- ]
224
- },
225
- "metadata": {},
226
- "output_type": "display_data"
227
- }
228
- ],
229
- "source": [
230
- "import numpy as np\n",
231
- "\n",
232
- "import matplotlib.pyplot as plt\n",
233
- "\n",
234
- "# Collect number of formulas per spectrum\n",
235
- "n_formulas = [len(d.metadata['formulas']) for d in dataset.spectra]\n",
236
- "\n",
237
- "# Calculate mean and median\n",
238
- "mean_n_formulas = np.mean(n_formulas)\n",
239
- "median_n_formulas = np.median(n_formulas)\n",
240
- "\n",
241
- "# Plot histogram\n",
242
- "plt.hist(n_formulas, bins=30, alpha=0.7, color='skyblue')\n",
243
- "plt.axvline(mean_n_formulas, color='red', linestyle='dashed', linewidth=2, label=f'Mean: {mean_n_formulas:.2f}')\n",
244
- "plt.axvline(median_n_formulas, color='green', linestyle='dashed', linewidth=2, label=f'Median: {median_n_formulas:.2f}')\n",
245
- "plt.xlabel('Number of formulas per spectrum')\n",
246
- "plt.ylabel('Count')\n",
247
- "plt.title('Distribution of Number of Formulas per Spectrum (MIST labels)')\n",
248
- "plt.legend()\n",
249
- "plt.show()"
250
- ]
251
- },
252
- {
253
- "cell_type": "code",
254
- "execution_count": 7,
255
- "id": "b35bb5a4",
256
- "metadata": {},
257
- "outputs": [
258
- {
259
- "data": {
260
- "text/plain": [
261
- "identifier MassSpecGymID0000001\n",
262
- "mzs 91.0542,125.0233,154.0499,155.0577,185.0961,20...\n",
263
- "intensities 0.24524524524524524,1.0,0.08008008008008008,0....\n",
264
- "smiles CC(=O)N[C@@H](CC1=CC=CC=C1)C2=CC(=CC(=O)O2)OC\n",
265
- "inchikey VFMQMACUYWGDOJ\n",
266
- "formula C16H17NO4\n",
267
- "precursor_formula C16H18NO4\n",
268
- "parent_mass 287.115224\n",
269
- "precursor_mz 288.1225\n",
270
- "adduct [M+H]+\n",
271
- "instrument_type Orbitrap\n",
272
- "collision_energy 30.0\n",
273
- "fold train\n",
274
- "simulation_challenge True\n",
275
- "formulas [C16H17NO4]\n",
276
- "formula_mzs [288.1225]\n",
277
- "formula_intensities [1.0]\n",
278
- "Name: 0, dtype: object"
279
- ]
280
- },
281
- "execution_count": 7,
282
- "metadata": {},
283
- "output_type": "execute_result"
284
- }
285
- ],
286
- "source": [
287
- "dataset.metadata.iloc[0]"
288
- ]
289
- },
290
- {
291
- "cell_type": "code",
292
- "execution_count": 9,
293
- "id": "da07f08a",
294
- "metadata": {},
295
- "outputs": [
296
- {
297
- "ename": "PermissionError",
298
- "evalue": "[Errno 13] Permission denied: '/r/hassounlab/msgym_sirius/MassSpecGymID0000140.json'",
299
- "output_type": "error",
300
- "traceback": [
301
- "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
302
- "\u001b[0;31mPermissionError\u001b[0m Traceback (most recent call last)",
303
- "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",
304
- "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",
305
- "\u001b[0;31mPermissionError\u001b[0m: [Errno 13] Permission denied: '/r/hassounlab/msgym_sirius/MassSpecGymID0000140.json'"
306
- ]
307
- }
308
- ],
309
- "source": [
310
- "import json\n",
311
- "spec_id = dataset.metadata.iloc[123]['identifier']\n",
312
- "file = os.path.join(params['subformula_dir_pth'], spec_id+\".json\")\n",
313
- "with open(file) as f:\n",
314
- " data = json.load(f)"
315
- ]
316
- },
317
- {
318
- "cell_type": "code",
319
- "execution_count": null,
320
- "id": "a1341478",
321
- "metadata": {},
322
- "outputs": [],
323
- "source": []
324
- }
325
- ],
326
- "metadata": {
327
- "kernelspec": {
328
- "display_name": "spec",
329
- "language": "python",
330
- "name": "python3"
331
- },
332
- "language_info": {
333
- "codemirror_mode": {
334
- "name": "ipython",
335
- "version": 3
336
- },
337
- "file_extension": ".py",
338
- "mimetype": "text/x-python",
339
- "name": "python",
340
- "nbconvert_exporter": "python",
341
- "pygments_lexer": "ipython3",
342
- "version": "3.11.7"
343
- }
344
- },
345
- "nbformat": 4,
346
- "nbformat_minor": 5
347
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/results.ipynb DELETED
@@ -1,233 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 1,
6
- "id": "2cd3303a",
7
- "metadata": {},
8
- "outputs": [],
9
- "source": [
10
- "import pickle\n",
11
- "import pandas as pd"
12
- ]
13
- },
14
- {
15
- "cell_type": "code",
16
- "execution_count": 2,
17
- "id": "8ccc0bc1",
18
- "metadata": {},
19
- "outputs": [],
20
- "source": [
21
- "with open(\"/data/yzhouc01/FILIP-MS/experiments/20251110_filip-global/result_MassSpecGym_retrieval_candidates_formula.pkl\", \"rb\") as f:\n",
22
- " result = pickle.load(f)"
23
- ]
24
- },
25
- {
26
- "cell_type": "code",
27
- "execution_count": 3,
28
- "id": "8e517777",
29
- "metadata": {},
30
- "outputs": [
31
- {
32
- "data": {
33
- "text/html": [
34
- "<div>\n",
35
- "<style scoped>\n",
36
- " .dataframe tbody tr th:only-of-type {\n",
37
- " vertical-align: middle;\n",
38
- " }\n",
39
- "\n",
40
- " .dataframe tbody tr th {\n",
41
- " vertical-align: top;\n",
42
- " }\n",
43
- "\n",
44
- " .dataframe thead th {\n",
45
- " text-align: right;\n",
46
- " }\n",
47
- "</style>\n",
48
- "<table border=\"1\" class=\"dataframe\">\n",
49
- " <thead>\n",
50
- " <tr style=\"text-align: right;\">\n",
51
- " <th></th>\n",
52
- " <th>rank_fine</th>\n",
53
- " <th>rank_global</th>\n",
54
- " <th>rank_sum</th>\n",
55
- " <th>rank_weighted</th>\n",
56
- " <th>rank_avg</th>\n",
57
- " </tr>\n",
58
- " </thead>\n",
59
- " <tbody>\n",
60
- " <tr>\n",
61
- " <th>R@1</th>\n",
62
- " <td>0.214571</td>\n",
63
- " <td>0.163306</td>\n",
64
- " <td>0.192869</td>\n",
65
- " <td>0.191274</td>\n",
66
- " <td>0.192869</td>\n",
67
- " </tr>\n",
68
- " <tr>\n",
69
- " <th>R@5</th>\n",
70
- " <td>0.483140</td>\n",
71
- " <td>0.403566</td>\n",
72
- " <td>0.447425</td>\n",
73
- " <td>0.444862</td>\n",
74
- " <td>0.447425</td>\n",
75
- " </tr>\n",
76
- " <tr>\n",
77
- " <th>R@20</th>\n",
78
- " <td>0.747095</td>\n",
79
- " <td>0.694350</td>\n",
80
- " <td>0.728355</td>\n",
81
- " <td>0.726361</td>\n",
82
- " <td>0.728355</td>\n",
83
- " </tr>\n",
84
- " </tbody>\n",
85
- "</table>\n",
86
- "</div>"
87
- ],
88
- "text/plain": [
89
- " rank_fine rank_global rank_sum rank_weighted rank_avg\n",
90
- "R@1 0.214571 0.163306 0.192869 0.191274 0.192869\n",
91
- "R@5 0.483140 0.403566 0.447425 0.444862 0.447425\n",
92
- "R@20 0.747095 0.694350 0.728355 0.726361 0.728355"
93
- ]
94
- },
95
- "execution_count": 3,
96
- "metadata": {},
97
- "output_type": "execute_result"
98
- }
99
- ],
100
- "source": [
101
- "data = []\n",
102
- "for i in [1, 5, 20]:\n",
103
- " curr_d = {}\n",
104
- " for c in result.columns.tolist():\n",
105
- " if c.startswith('rank'):\n",
106
- " curr_d[c] = result[result[c] <= i].shape[0] / result.shape[0]\n",
107
- " data.append(curr_d)\n",
108
- "\n",
109
- "data_df = pd.DataFrame(data, index=['R@1', 'R@5', 'R@20'])\n",
110
- "data_df\n"
111
- ]
112
- },
113
- {
114
- "cell_type": "code",
115
- "execution_count": 7,
116
- "id": "10493857",
117
- "metadata": {},
118
- "outputs": [
119
- {
120
- "data": {
121
- "text/html": [
122
- "<div>\n",
123
- "<style scoped>\n",
124
- " .dataframe tbody tr th:only-of-type {\n",
125
- " vertical-align: middle;\n",
126
- " }\n",
127
- "\n",
128
- " .dataframe tbody tr th {\n",
129
- " vertical-align: top;\n",
130
- " }\n",
131
- "\n",
132
- " .dataframe thead th {\n",
133
- " text-align: right;\n",
134
- " }\n",
135
- "</style>\n",
136
- "<table border=\"1\" class=\"dataframe\">\n",
137
- " <thead>\n",
138
- " <tr style=\"text-align: right;\">\n",
139
- " <th></th>\n",
140
- " <th>rank_fine</th>\n",
141
- " <th>rank_global</th>\n",
142
- " <th>rank_sum</th>\n",
143
- " <th>rank_weighted</th>\n",
144
- " <th>rank_avg</th>\n",
145
- " </tr>\n",
146
- " </thead>\n",
147
- " <tbody>\n",
148
- " <tr>\n",
149
- " <th>R@1</th>\n",
150
- " <td>0.420882</td>\n",
151
- " <td>0.369731</td>\n",
152
- " <td>0.412907</td>\n",
153
- " <td>0.411939</td>\n",
154
- " <td>0.412907</td>\n",
155
- " </tr>\n",
156
- " <tr>\n",
157
- " <th>R@5</th>\n",
158
- " <td>0.744475</td>\n",
159
- " <td>0.707052</td>\n",
160
- " <td>0.738893</td>\n",
161
- " <td>0.737412</td>\n",
162
- " <td>0.738893</td>\n",
163
- " </tr>\n",
164
- " <tr>\n",
165
- " <th>R@20</th>\n",
166
- " <td>0.927660</td>\n",
167
- " <td>0.916325</td>\n",
168
- " <td>0.926407</td>\n",
169
- " <td>0.926122</td>\n",
170
- " <td>0.926407</td>\n",
171
- " </tr>\n",
172
- " </tbody>\n",
173
- "</table>\n",
174
- "</div>"
175
- ],
176
- "text/plain": [
177
- " rank_fine rank_global rank_sum rank_weighted rank_avg\n",
178
- "R@1 0.420882 0.369731 0.412907 0.411939 0.412907\n",
179
- "R@5 0.744475 0.707052 0.738893 0.737412 0.738893\n",
180
- "R@20 0.927660 0.916325 0.926407 0.926122 0.926407"
181
- ]
182
- },
183
- "execution_count": 7,
184
- "metadata": {},
185
- "output_type": "execute_result"
186
- }
187
- ],
188
- "source": [
189
- "data = []\n",
190
- "for i in [1, 5, 20]:\n",
191
- " curr_d = {}\n",
192
- " for c in result.columns.tolist():\n",
193
- " if c.startswith('rank'):\n",
194
- " curr_d[c] = result[result[c] <= i].shape[0] / result.shape[0]\n",
195
- " data.append(curr_d)\n",
196
- "\n",
197
- "data_df = pd.DataFrame(data, index=['R@1', 'R@5', 'R@20'])\n",
198
- "data_df\n"
199
- ]
200
- },
201
- {
202
- "cell_type": "code",
203
- "execution_count": null,
204
- "id": "1e4201db",
205
- "metadata": {},
206
- "outputs": [],
207
- "source": [
208
- "x"
209
- ]
210
- }
211
- ],
212
- "metadata": {
213
- "kernelspec": {
214
- "display_name": "spec",
215
- "language": "python",
216
- "name": "python3"
217
- },
218
- "language_info": {
219
- "codemirror_mode": {
220
- "name": "ipython",
221
- "version": 3
222
- },
223
- "file_extension": ".py",
224
- "mimetype": "text/x-python",
225
- "name": "python",
226
- "nbconvert_exporter": "python",
227
- "pygments_lexer": "ipython3",
228
- "version": "3.11.7"
229
- }
230
- },
231
- "nbformat": 4,
232
- "nbformat_minor": 5
233
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
notebooks/scaffold_identification.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/spectra_sim.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/substructure_extraction.ipynb DELETED
The diff for this file is too large to render. See raw diff
 
notebooks/visualization.ipynb DELETED
@@ -1,217 +0,0 @@
1
- {
2
- "cells": [
3
- {
4
- "cell_type": "code",
5
- "execution_count": 1,
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=\"42.5\" cy=\"174.3\" rx=\"22.5\" ry=\"12.1\" class=\"atom-0\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
15
- "<ellipse cx=\"108.9\" cy=\"165.3\" rx=\"11.8\" ry=\"11.8\" 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=\"145.7\" cy=\"211.3\" rx=\"11.8\" ry=\"11.8\" 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=\"116.1\" cy=\"266.3\" rx=\"22.5\" ry=\"12.1\" class=\"atom-3\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
18
- "<ellipse cx=\"203.9\" cy=\"202.4\" rx=\"11.8\" ry=\"11.8\" 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=\"245.4\" cy=\"244.2\" rx=\"11.8\" ry=\"11.8\" 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=\"243.7\" cy=\"302.5\" rx=\"21.9\" ry=\"12.1\" class=\"atom-6\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
21
- "<ellipse cx=\"298.0\" cy=\"217.8\" rx=\"11.8\" ry=\"11.8\" class=\"atom-7\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
22
- "<ellipse cx=\"358.1\" cy=\"244.9\" rx=\"21.9\" ry=\"12.1\" class=\"atom-8\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
23
- "<ellipse cx=\"289.1\" cy=\"159.5\" rx=\"11.8\" ry=\"11.8\" class=\"atom-9\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
24
- "<ellipse cx=\"338.7\" cy=\"118.3\" rx=\"21.9\" ry=\"12.1\" class=\"atom-10\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
25
- "<ellipse cx=\"230.9\" cy=\"150.0\" rx=\"11.8\" ry=\"11.8\" class=\"atom-11\" style=\"fill:#999999;fill-rule:evenodd;stroke:#999999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
26
- "<ellipse cx=\"196.3\" cy=\"97.5\" rx=\"22.5\" ry=\"12.1\" class=\"atom-12\" style=\"fill:#F99999;fill-rule:evenodd;stroke:#F99999;stroke-width:1.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
27
- "<path class=\"bond-0 atom-0 atom-1\" d=\"M 60.3,172.7 L 84.6,169.0\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
28
- "<path class=\"bond-0 atom-0 atom-1\" d=\"M 84.6,169.0 L 108.9,165.3\" 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-1 atom-1 atom-2\" d=\"M 108.9,165.3 L 145.7,211.3\" 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-2 atom-2 atom-3\" d=\"M 145.7,211.3 L 137.1,233.5\" 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-2 atom-2 atom-3\" d=\"M 137.1,233.5 L 128.4,255.7\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
32
- "<path class=\"bond-3 atom-2 atom-4\" d=\"M 145.7,211.3 L 203.9,202.4\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
33
- "<path class=\"bond-4 atom-4 atom-5\" d=\"M 203.9,202.4 L 245.4,244.2\" 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-5 atom-5 atom-6\" d=\"M 245.4,244.2 L 241.4,268.1\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
35
- "<path class=\"bond-5 atom-5 atom-6\" d=\"M 241.4,268.1 L 237.5,291.9\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
36
- "<path class=\"bond-6 atom-5 atom-7\" d=\"M 245.4,244.2 L 298.0,217.8\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
37
- "<path class=\"bond-7 atom-7 atom-8\" d=\"M 298.0,217.8 L 319.3,228.8\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
38
- "<path class=\"bond-7 atom-7 atom-8\" d=\"M 319.3,228.8 L 340.7,239.8\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
39
- "<path class=\"bond-8 atom-7 atom-9\" d=\"M 298.0,217.8 L 289.1,159.5\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
40
- "<path class=\"bond-9 atom-9 atom-10\" d=\"M 289.1,159.5 L 305.2,143.6\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
41
- "<path class=\"bond-9 atom-9 atom-10\" d=\"M 305.2,143.6 L 321.3,127.6\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
42
- "<path class=\"bond-10 atom-9 atom-11\" d=\"M 289.1,159.5 L 230.9,150.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
43
- "<path class=\"bond-11 atom-11 atom-12\" d=\"M 230.9,150.0 L 220.4,129.0\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
44
- "<path class=\"bond-11 atom-11 atom-12\" d=\"M 220.4,129.0 L 209.8,108.1\" style=\"fill:none;fill-rule:evenodd;stroke:#FF0000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
45
- "<path class=\"bond-12 atom-11 atom-4\" d=\"M 230.9,150.0 L 203.9,202.4\" style=\"fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1\"/>\n",
46
- "<path d=\"M 107.7,165.5 L 108.9,165.3 L 110.8,167.6\" style=\"fill:none;stroke:#000000;stroke-width:2.0px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;\"/>\n",
47
- "<path class=\"atom-0\" d=\"M 26.6 166.0 L 28.8 166.0 L 28.8 173.1 L 37.4 173.1 L 37.4 166.0 L 39.6 166.0 L 39.6 182.6 L 37.4 182.6 L 37.4 174.9 L 28.8 174.9 L 28.8 182.6 L 26.6 182.6 L 26.6 166.0 \" fill=\"#FF0000\"/>\n",
48
- "<path class=\"atom-0\" d=\"M 43.1 174.3 Q 43.1 170.3, 45.0 168.0 Q 47.0 165.8, 50.7 165.8 Q 54.4 165.8, 56.4 168.0 Q 58.4 170.3, 58.4 174.3 Q 58.4 178.3, 56.4 180.6 Q 54.4 182.9, 50.7 182.9 Q 47.0 182.9, 45.0 180.6 Q 43.1 178.3, 43.1 174.3 M 50.7 181.0 Q 53.2 181.0, 54.6 179.3 Q 56.0 177.6, 56.0 174.3 Q 56.0 171.0, 54.6 169.3 Q 53.2 167.7, 50.7 167.7 Q 48.2 167.7, 46.8 169.3 Q 45.4 171.0, 45.4 174.3 Q 45.4 177.6, 46.8 179.3 Q 48.2 181.0, 50.7 181.0 \" fill=\"#FF0000\"/>\n",
49
- "<path class=\"atom-3\" d=\"M 100.2 257.9 L 102.5 257.9 L 102.5 265.0 L 111.0 265.0 L 111.0 257.9 L 113.3 257.9 L 113.3 274.6 L 111.0 274.6 L 111.0 266.9 L 102.5 266.9 L 102.5 274.6 L 100.2 274.6 L 100.2 257.9 \" fill=\"#FF0000\"/>\n",
50
- "<path class=\"atom-3\" d=\"M 116.7 266.2 Q 116.7 262.2, 118.7 259.9 Q 120.6 257.7, 124.3 257.7 Q 128.0 257.7, 130.0 259.9 Q 132.0 262.2, 132.0 266.2 Q 132.0 270.2, 130.0 272.5 Q 128.0 274.8, 124.3 274.8 Q 120.7 274.8, 118.7 272.5 Q 116.7 270.2, 116.7 266.2 M 124.3 272.9 Q 126.9 272.9, 128.2 271.2 Q 129.6 269.5, 129.6 266.2 Q 129.6 262.9, 128.2 261.2 Q 126.9 259.6, 124.3 259.6 Q 121.8 259.6, 120.4 261.2 Q 119.0 262.9, 119.0 266.2 Q 119.0 269.5, 120.4 271.2 Q 121.8 272.9, 124.3 272.9 \" fill=\"#FF0000\"/>\n",
51
- "<path class=\"atom-6\" d=\"M 228.2 302.4 Q 228.2 298.4, 230.2 296.1 Q 232.1 293.9, 235.8 293.9 Q 239.5 293.9, 241.5 296.1 Q 243.5 298.4, 243.5 302.4 Q 243.5 306.4, 241.5 308.7 Q 239.5 311.0, 235.8 311.0 Q 232.2 311.0, 230.2 308.7 Q 228.2 306.4, 228.2 302.4 M 235.8 309.1 Q 238.4 309.1, 239.7 307.4 Q 241.1 305.7, 241.1 302.4 Q 241.1 299.1, 239.7 297.4 Q 238.4 295.8, 235.8 295.8 Q 233.3 295.8, 231.9 297.4 Q 230.5 299.1, 230.5 302.4 Q 230.5 305.7, 231.9 307.4 Q 233.3 309.1, 235.8 309.1 \" fill=\"#FF0000\"/>\n",
52
- "<path class=\"atom-6\" d=\"M 246.1 294.1 L 248.4 294.1 L 248.4 301.2 L 256.9 301.2 L 256.9 294.1 L 259.1 294.1 L 259.1 310.8 L 256.9 310.8 L 256.9 303.1 L 248.4 303.1 L 248.4 310.8 L 246.1 310.8 L 246.1 294.1 \" fill=\"#FF0000\"/>\n",
53
- "<path class=\"atom-8\" d=\"M 342.6 244.8 Q 342.6 240.8, 344.6 238.6 Q 346.6 236.3, 350.3 236.3 Q 354.0 236.3, 356.0 238.6 Q 357.9 240.8, 357.9 244.8 Q 357.9 248.9, 355.9 251.2 Q 353.9 253.5, 350.3 253.5 Q 346.6 253.5, 344.6 251.2 Q 342.6 248.9, 342.6 244.8 M 350.3 251.6 Q 352.8 251.6, 354.2 249.9 Q 355.6 248.2, 355.6 244.8 Q 355.6 241.5, 354.2 239.9 Q 352.8 238.2, 350.3 238.2 Q 347.7 238.2, 346.4 239.9 Q 345.0 241.5, 345.0 244.8 Q 345.0 248.2, 346.4 249.9 Q 347.7 251.6, 350.3 251.6 \" fill=\"#FF0000\"/>\n",
54
- "<path class=\"atom-8\" d=\"M 360.5 236.5 L 362.8 236.5 L 362.8 243.6 L 371.3 243.6 L 371.3 236.5 L 373.6 236.5 L 373.6 253.2 L 371.3 253.2 L 371.3 245.5 L 362.8 245.5 L 362.8 253.2 L 360.5 253.2 L 360.5 236.5 \" fill=\"#FF0000\"/>\n",
55
- "<path class=\"atom-10\" d=\"M 323.3 118.2 Q 323.3 114.2, 325.2 111.9 Q 327.2 109.7, 330.9 109.7 Q 334.6 109.7, 336.6 111.9 Q 338.6 114.2, 338.6 118.2 Q 338.6 122.2, 336.6 124.5 Q 334.6 126.8, 330.9 126.8 Q 327.2 126.8, 325.2 124.5 Q 323.3 122.2, 323.3 118.2 M 330.9 124.9 Q 333.5 124.9, 334.8 123.2 Q 336.2 121.5, 336.2 118.2 Q 336.2 114.9, 334.8 113.3 Q 333.5 111.6, 330.9 111.6 Q 328.4 111.6, 327.0 113.2 Q 325.6 114.9, 325.6 118.2 Q 325.6 121.5, 327.0 123.2 Q 328.4 124.9, 330.9 124.9 \" fill=\"#FF0000\"/>\n",
56
- "<path class=\"atom-10\" d=\"M 341.2 109.9 L 343.4 109.9 L 343.4 117.0 L 352.0 117.0 L 352.0 109.9 L 354.2 109.9 L 354.2 126.6 L 352.0 126.6 L 352.0 118.9 L 343.4 118.9 L 343.4 126.6 L 341.2 126.6 L 341.2 109.9 \" fill=\"#FF0000\"/>\n",
57
- "<path class=\"atom-12\" d=\"M 180.4 89.2 L 182.6 89.2 L 182.6 96.3 L 191.2 96.3 L 191.2 89.2 L 193.4 89.2 L 193.4 105.9 L 191.2 105.9 L 191.2 98.2 L 182.6 98.2 L 182.6 105.9 L 180.4 105.9 L 180.4 89.2 \" fill=\"#FF0000\"/>\n",
58
- "<path class=\"atom-12\" d=\"M 196.8 97.5 Q 196.8 93.5, 198.8 91.2 Q 200.8 89.0, 204.5 89.0 Q 208.2 89.0, 210.2 91.2 Q 212.1 93.5, 212.1 97.5 Q 212.1 101.5, 210.1 103.8 Q 208.1 106.1, 204.5 106.1 Q 200.8 106.1, 198.8 103.8 Q 196.8 101.5, 196.8 97.5 M 204.5 104.2 Q 207.0 104.2, 208.4 102.5 Q 209.8 100.8, 209.8 97.5 Q 209.8 94.2, 208.4 92.5 Q 207.0 90.9, 204.5 90.9 Q 201.9 90.9, 200.6 92.5 Q 199.2 94.2, 199.2 97.5 Q 199.2 100.8, 200.6 102.5 Q 201.9 104.2, 204.5 104.2 \" fill=\"#FF0000\"/>\n",
59
- "</svg>"
60
- ],
61
- "text/plain": [
62
- "<IPython.core.display.SVG object>"
63
- ]
64
- },
65
- "execution_count": 1,
66
- "metadata": {},
67
- "output_type": "execute_result"
68
- }
69
- ],
70
- "source": [
71
- "from rdkit import Chem\n",
72
- "from rdkit.Chem import Draw\n",
73
- "from rdkit.Chem.Draw import rdMolDraw2D\n",
74
- "from IPython.display import SVG\n",
75
- "\n",
76
- "# Example molecule\n",
77
- "mol = Chem.MolFromSmiles(\"OCC(O)C1C(O)C(O)C(O)C(O)1\") \n",
78
- "Chem.rdDepictor.Compute2DCoords(mol)\n",
79
- "\n",
80
- "# Define colors for atom types\n",
81
- "atom_colors = {\n",
82
- " 6: (0.6, 0.6, 0.6), # Carbon = light gray\n",
83
- " 8: (0.98, 0.6, 0.6), # Oxygen = soft red/pink\n",
84
- " 7: (0.55, 0.63, 0.8), # Nitrogen = light blue\n",
85
- " 16: (0.8, 0.8, 0.55), # Sulfur = soft yellow\n",
86
- " 17: (0.65, 0.85, 0.65), # Chlorine = light green\n",
87
- " 1: (0.9, 0.9, 0.9), # Hydrogen = very light gray\n",
88
- "}\n",
89
- "\n",
90
- "\n",
91
- "# Default = muted purple (for other atoms)\n",
92
- "default_color = (0.8, 0.7, 0.9)\n",
93
- "\n",
94
- "# Assign highlight colors\n",
95
- "highlight_atoms = [atom.GetIdx() for atom in mol.GetAtoms()]\n",
96
- "highlight_colors = {\n",
97
- " atom.GetIdx(): atom_colors.get(atom.GetAtomicNum(), default_color)\n",
98
- " for atom in mol.GetAtoms()\n",
99
- "}\n",
100
- "\n",
101
- "# Draw with transparent background\n",
102
- "drawer = rdMolDraw2D.MolDraw2DSVG(400, 400)\n",
103
- "# drawer.drawOptions().clearBackground = False # 🔑 makes background transparent\n",
104
- "rdMolDraw2D.PrepareAndDrawMolecule(\n",
105
- " drawer,\n",
106
- " mol,\n",
107
- " highlightAtoms=highlight_atoms,\n",
108
- " highlightAtomColors=highlight_colors\n",
109
- ")\n",
110
- "drawer.FinishDrawing()\n",
111
- "\n",
112
- "# Clean up RDKit's extra XML headers\n",
113
- "svg = drawer.GetDrawingText().replace(\"svg:\", \"\")\n",
114
- "SVG(svg)\n"
115
- ]
116
- },
117
- {
118
- "cell_type": "code",
119
- "execution_count": 9,
120
- "metadata": {},
121
- "outputs": [
122
- {
123
- "data": {
124
- "image/png": "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",
125
- "text/plain": [
126
- "<Figure size 640x480 with 1 Axes>"
127
- ]
128
- },
129
- "metadata": {},
130
- "output_type": "display_data"
131
- }
132
- ],
133
- "source": [
134
- "import networkx as nx\n",
135
- "import matplotlib.pyplot as plt\n",
136
- "from rdkit import Chem\n",
137
- "\n",
138
- "# Example molecule\n",
139
- "mol = Chem.MolFromSmiles(\"C1CCN(C1)C(=O)N\") \n",
140
- "Chem.rdDepictor.Compute2DCoords(mol)\n",
141
- "\n",
142
- "# Define colors for atom types\n",
143
- "atom_colors = {\n",
144
- " 6: \"lightgray\", # Carbon\n",
145
- " 8: \"lightcoral\", # Oxygen\n",
146
- " 7: \"lightblue\", # Nitrogen\n",
147
- " 16: \"khaki\", # Sulfur\n",
148
- " 17: \"lightgreen\", # Chlorine\n",
149
- " 1: \"whitesmoke\", # Hydrogen\n",
150
- "}\n",
151
- "default_color = \"plum\"\n",
152
- "\n",
153
- "# Convert RDKit Mol → NetworkX graph\n",
154
- "G = nx.Graph()\n",
155
- "for atom in mol.GetAtoms():\n",
156
- " idx = atom.GetIdx()\n",
157
- " pos = mol.GetConformer().GetAtomPosition(idx)\n",
158
- " G.add_node(\n",
159
- " idx,\n",
160
- " label=atom.GetSymbol(),\n",
161
- " color=atom_colors.get(atom.GetAtomicNum(), default_color),\n",
162
- " pos=(pos.x, pos.y) # store RDKit 2D coords\n",
163
- " )\n",
164
- "for bond in mol.GetBonds():\n",
165
- " G.add_edge(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), order=bond.GetBondTypeAsDouble())\n",
166
- "\n",
167
- "# Extract positions\n",
168
- "pos = {n: (data[\"pos\"][0], data[\"pos\"][1]) for n, data in G.nodes(data=True)}\n",
169
- "\n",
170
- "# Draw nodes\n",
171
- "node_colors = [G.nodes[n][\"color\"] for n in G.nodes]\n",
172
- "nx.draw_networkx_nodes(G, pos, node_color=node_colors, node_size=800, edgecolors=\"k\")\n",
173
- "\n",
174
- "# Draw edges with bond order as width\n",
175
- "# edge_widths = [1.5 * G[u][v][\"order\"] for u, v in G.edges()]\n",
176
- "nx.draw_networkx_edges(G, pos)\n",
177
- "\n",
178
- "# Draw atom labels\n",
179
- "labels = {n: G.nodes[n][\"label\"] for n in G.nodes}\n",
180
- "nx.draw_networkx_labels(G, pos, labels, font_size=12, font_weight=\"bold\")\n",
181
- "\n",
182
- "plt.axis(\"off\")\n",
183
- "plt.gca().set_aspect(\"equal\", \"box\") # keep proportions\n",
184
- "plt.show()\n"
185
- ]
186
- },
187
- {
188
- "cell_type": "code",
189
- "execution_count": null,
190
- "metadata": {},
191
- "outputs": [],
192
- "source": []
193
- }
194
- ],
195
- "metadata": {
196
- "kernelspec": {
197
- "display_name": "Python (spec)",
198
- "language": "python",
199
- "name": "spec"
200
- },
201
- "language_info": {
202
- "codemirror_mode": {
203
- "name": "ipython",
204
- "version": 3
205
- },
206
- "file_extension": ".py",
207
- "mimetype": "text/x-python",
208
- "name": "python",
209
- "nbconvert_exporter": "python",
210
- "pygments_lexer": "ipython3",
211
- "version": "3.11.7"
212
- },
213
- "orig_nbformat": 4
214
- },
215
- "nbformat": 4,
216
- "nbformat_minor": 2
217
- }