Trương Gia Bảo commited on
Commit
975be11
·
1 Parent(s): e9a9e23
Files changed (6) hide show
  1. app.py +363 -55
  2. app_backup.py +437 -0
  3. mcf.csv +23 -0
  4. requirements.txt +5 -1
  5. style.css +4 -4
  6. wehi_pains.csv +0 -0
app.py CHANGED
@@ -1,9 +1,11 @@
1
  from pathlib import Path
2
  import torch
3
- from st_on_hover_tabs import on_hover_tabs
4
  import streamlit as st
5
  st.set_page_config(layout="wide")
6
 
 
 
7
  import sys, os
8
  import rdkit
9
  import rdkit.Chem as Chem
@@ -13,6 +15,8 @@ import sascorer
13
  import networkx as nx
14
  from stqdm import stqdm
15
  import base64, io
 
 
16
 
17
  os.environ['KMP_DUPLICATE_LIB_OK']='True'
18
 
@@ -35,11 +39,17 @@ css='''
35
  [data-testid="metric-container"] label {
36
  width: fit-content;
37
  margin: auto;
 
 
 
 
 
38
  }
39
  '''
40
 
41
  st.markdown(f'<style>{css}</style>',unsafe_allow_html=True)
42
 
 
43
  def img_to_bytes(img_path):
44
  img_bytes = Path(img_path).read_bytes()
45
  encoded = base64.b64encode(img_bytes).decode()
@@ -50,6 +60,47 @@ def img_to_html(img_path):
50
  )
51
  return img_html
52
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  def penalized_logp_standard(mol):
54
 
55
  logP_mean = 2.4399606244103639873799239
@@ -78,22 +129,140 @@ def penalized_logp_standard(mol):
78
  standardized_log_p = (log_p - logP_mean) / logP_std
79
  standardized_SA = (SA - SA_mean) / SA_std
80
  standardized_cycle = (cycle_score - cycle_mean) / cycle_std
81
- return standardized_log_p + standardized_SA + standardized_cycle
 
 
 
 
 
 
 
 
 
 
 
 
 
 
82
 
83
  lg = rdkit.RDLogger.logger()
84
  lg.setLevel(rdkit.RDLogger.CRITICAL)
85
 
86
- st.markdown("<h1 style='text-align: center;'>Junction Tree Variational Autoencoder for Molecular Graph Generation (JTVAE)</h1>",unsafe_allow_html=True)
87
- st.markdown("<h3 style='text-align: center;'>Wengong Jin, Regina Barzilay, Tommi Jaakkola</h3>",unsafe_allow_html=True)
88
- st.markdown('<style>' + open('./style.css').read() + '</style>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- with st.sidebar:
91
- # st.header('+')
92
- st.markdown("<h5 style='text-align: center; color:grey;'>Explore</h5>",unsafe_allow_html=True)
93
- tabs = on_hover_tabs(tabName=['Optimize a molecule', 'Optimize batch', 'About'],
94
- iconName=['science', 'batch_prediction', 'info'], default_choice=0)
 
 
95
 
96
- if tabs == 'About':
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  descrip = '''
98
  We seek to automate the design of molecules based on specific chemical properties. In computational terms, this task involves continuous embedding and generation of molecular graphs. Our primary contribution is the direct realization of molecular graphs, a task previously approached by generating linear SMILES strings instead of graphs. Our junction tree variational autoencoder generates molecular graphs in two phases, by first generating a tree-structured scaffold over chemical substructures, and then combining them into a molecule with a graph message passing network. This approach allows us to incrementally expand molecules while maintaining chemical validity at every step. We evaluate our model on multiple tasks ranging from molecular generation to optimization. Across these tasks, our model outperforms previous state-of-the-art baselines by a significant margin.
99
 
@@ -102,47 +271,127 @@ We seek to automate the design of molecules based on specific chemical propertie
102
  st.markdown("<p style='text-align: center;'>"+
103
  img_to_html('about.png')+
104
  "</p>", unsafe_allow_html=True)
105
- elif tabs == 'Optimize a molecule':
 
106
  st.markdown("<h2 style='text-align: center;'>Optimize a molecule</h2>",unsafe_allow_html=True)
107
- st.text_input('Enter a SMILES string:','CNC(=O)C1=NC=CC(=C1)OC2=CC=C(C=C2)NC(=O)NC3=CC(=C(C=C3)Cl)C(F)(F)F',key='smiles')
108
-
109
- mol = Chem.MolFromSmiles(st.session_state.smiles)
110
- if mol is None:
111
- st.markdown("<p style='text-align: center; color: red;'>SMILES is invalid. Please enter a valid SMILES.</p>",unsafe_allow_html=True)
112
- else:
113
- score = penalized_logp_standard(mol)
114
- # with st.columns(3)[1]:
115
- # st.markdown("<style>{text-align: center;}</style>",unsafe_allow_html=True)
116
- imgByteArr = io.BytesIO()
117
- MolToImage(mol,size=(400,400)).save(imgByteArr,format='PNG')
118
- st.markdown("<p style='text-align: center;'>"+
119
- f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
120
- "</p>", unsafe_allow_html=True)
121
- # st.image(MolToImage(mol,size=(300,300)))
122
- st.metric('Penalized logP score', '%.5f' % (score))
123
-
124
- if mol is not None:
125
- # col1, col2, col3 = st.columns(3)
126
- st.slider('Choose learning rate: ',0.0,10.0,0.4,key='lr')
127
- st.slider('Choose similarity cutoff: ',0.0,3.0,0.4,key='sim_cutoff')
128
- st.slider('Choose number of iterations: ',1,100,80,key='n_iter')
129
- vocab = [x.strip("\r\n ") for x in open('./vocab.txt')]
130
- vocab = Vocab(vocab)
131
- if st.button('Optimize'):
132
- # st.write('Testing')
133
-
134
- # with st.columns(3)[1]:
135
- with st.spinner("Operation in progress. Please wait."):
136
-
137
- model = JTPropVAE(vocab, 450, 56, 20, 3)
138
-
139
- model.load_state_dict(torch.load('./model.iter-685000',map_location=torch.device('cpu')))
140
-
141
- new_smiles,sim = model.optimize(st.session_state.smiles, sim_cutoff=st.session_state.sim_cutoff, lr=st.session_state.lr, num_iter=st.session_state.n_iter)
142
 
143
- del model
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
144
  if new_smiles is None:
145
- st.markdown("<p style='text-align: center; color: red;'>Cannot optimize! Please choose another setting.</p>",unsafe_allow_html=True)
146
  else:
147
  st.markdown("<b style='text-align: center;'>New SMILES</b>",unsafe_allow_html=True)
148
  st.code(new_smiles)
@@ -157,20 +406,79 @@ elif tabs == 'Optimize a molecule':
157
  st.markdown("<p style='text-align: center;'>"+
158
  f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
159
  "</p>", unsafe_allow_html=True)
160
- new_score = penalized_logp_standard(new_mol)
 
 
 
 
 
 
161
  # st.write('New penalized logP score: %.5f' % (new_score))
162
- st.metric('New penalized logP score','%.5f' % (new_score), '%.5f'%(new_score-score))
 
 
 
 
 
163
  st.metric('Similarity','%.5f' % (sim))
164
  # st.write('Caching ZINC20 if necessary...')
165
  with st.spinner("Caching ZINC20 if necessary..."):
166
  if buy(new_smiles, catalog='zinc20',canonicalize=True):
167
  st.write('This molecule exists.')
168
- st.markdown("<h3 style='text-align: center; color: cyan;'><b>This molecule exists.</h3>",unsafe_allow_html=True)
169
  else:
170
  # st.write('THIS MOLECULE DOES NOT EXIST!')
171
- st.markdown("<h3 style='text-align: center; color: lightgreen;'>THIS MOLECULE DOES NOT EXIST!</h3>",unsafe_allow_html=True)
172
  st.markdown("<p style='text-align: center; color: grey;'>Checked using molbloom</p>",unsafe_allow_html=True)
173
- elif tabs == 'Optimize batch':
174
- st.write('Incoming...')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
 
 
 
1
  from pathlib import Path
2
  import torch
3
+ # from st_on_hover_tabs import on_hover_tabs
4
  import streamlit as st
5
  st.set_page_config(layout="wide")
6
 
7
+ model_path = './model.iter-685000'
8
+
9
  import sys, os
10
  import rdkit
11
  import rdkit.Chem as Chem
 
15
  import networkx as nx
16
  from stqdm import stqdm
17
  import base64, io
18
+ import pandas as pd
19
+ import streamlit_ext as ste
20
 
21
  os.environ['KMP_DUPLICATE_LIB_OK']='True'
22
 
 
39
  [data-testid="metric-container"] label {
40
  width: fit-content;
41
  margin: auto;
42
+
43
+ [data-testid="stDataFrameResizable"] {
44
+ width: fit-content;
45
+ margin: auto;
46
+ }
47
  }
48
  '''
49
 
50
  st.markdown(f'<style>{css}</style>',unsafe_allow_html=True)
51
 
52
+ s_buff = io.StringIO()
53
  def img_to_bytes(img_path):
54
  img_bytes = Path(img_path).read_bytes()
55
  encoded = base64.b64encode(img_bytes).decode()
 
60
  )
61
  return img_html
62
 
63
+ _mcf = pd.read_csv('./mcf.csv')
64
+ _pains = pd.read_csv('./wehi_pains.csv',
65
+ names=['smarts', 'names'])
66
+ _mcf_filters = [Chem.MolFromSmarts(x) for x in
67
+ _mcf['smarts'].values]
68
+ _pains_filters = [Chem.MolFromSmarts(x) for x in
69
+ _pains['smarts'].values]
70
+
71
+ def mol_passes_filters_custom(mol,
72
+ allowed=None,
73
+ isomericSmiles=False):
74
+ """
75
+ Checks if mol
76
+ * passes MCF and PAINS filters,
77
+ * has only allowed atoms
78
+ * is not charged
79
+ """
80
+ allowed = allowed or {'C', 'N', 'S', 'O', 'F', 'Cl', 'Br', 'H'}
81
+ if mol is None:
82
+ return 'NoMol'
83
+ ring_info = mol.GetRingInfo()
84
+ if ring_info.NumRings() != 0 and any(
85
+ len(x) >= 8 for x in ring_info.AtomRings()
86
+ ):
87
+ return 'ManyRings'
88
+ h_mol = Chem.AddHs(mol)
89
+ if any(atom.GetFormalCharge() != 0 for atom in mol.GetAtoms()):
90
+ return 'Charged'
91
+ if any(atom.GetSymbol() not in allowed for atom in mol.GetAtoms()):
92
+ return 'AtomNotAllowed'
93
+ if any(h_mol.HasSubstructMatch(smarts) for smarts in _mcf_filters):
94
+ return 'MCF'
95
+ if any(h_mol.HasSubstructMatch(smarts) for smarts in _pains_filters):
96
+ return 'PAINS'
97
+ smiles = Chem.MolToSmiles(mol, isomericSmiles=isomericSmiles)
98
+ if smiles is None or len(smiles) == 0:
99
+ return 'Isomeric'
100
+ if Chem.MolFromSmiles(smiles) is None:
101
+ return 'Isomeric'
102
+ return 'YES'
103
+
104
  def penalized_logp_standard(mol):
105
 
106
  logP_mean = 2.4399606244103639873799239
 
129
  standardized_log_p = (log_p - logP_mean) / logP_std
130
  standardized_SA = (SA - SA_mean) / SA_std
131
  standardized_cycle = (cycle_score - cycle_mean) / cycle_std
132
+ return log_p,SA,cycle_score,standardized_log_p + standardized_SA + standardized_cycle
133
+
134
+ def df_to_file(df):
135
+ s_buff.seek(0)
136
+ df.to_csv(s_buff)
137
+ return s_buff.getvalue().encode()
138
+
139
+ def download_df(df,id):
140
+ with st.expander(':arrow_down: Download this dataframe'):
141
+ st.markdown("<h4 style='color:tomato;'>Select column(s) to save:</h4>",unsafe_allow_html=True)
142
+ for col in df.columns:
143
+ st.checkbox(col,key=str(id)+'_col_'+str(col))
144
+ st.text_input('File name (.csv):','dataframe',key=str(id)+'_file_name')
145
+
146
+ ste.download_button('Download',df_to_file(df[[col for col in df.columns if st.session_state[str(id)+'_col_'+str(col)]]]),st.session_state[str(id)+'_file_name']+'.csv')
147
 
148
  lg = rdkit.RDLogger.logger()
149
  lg.setLevel(rdkit.RDLogger.CRITICAL)
150
 
151
+
152
+ if 'current_view' not in st.session_state:
153
+ st.session_state['current_view'] = 0
154
+
155
+ if 'current_step' not in st.session_state:
156
+ st.session_state['current_step'] = 0
157
+
158
+ def set_page_view(id):
159
+ st.session_state['current_view'] = id
160
+
161
+ def get_page_view():
162
+ return st.session_state['current_view']
163
+
164
+ def set_step(id):
165
+ st.session_state['current_step'] = id
166
+
167
+ def get_step():
168
+ return st.session_state['current_step']
169
 
170
+ @st.cache_resource
171
+ def load_model():
172
+ vocab = [x.strip("\r\n ") for x in open('./vocab.txt')]
173
+ vocab = Vocab(vocab)
174
+ model = JTPropVAE(vocab, 450, 56, 20, 3)
175
+ model.load_state_dict(torch.load(model_path,map_location=torch.device('cpu')))
176
+ return model
177
 
178
+ from streamlit_lottie import st_lottie
179
+ import requests
180
+
181
+ def render_animation():
182
+ animation_response = requests.get('https://assets1.lottiefiles.com/packages/lf20_vykpwt8b.json')
183
+ animation_json = dict()
184
+
185
+ if animation_response.status_code == 200:
186
+ animation_json = animation_response.json()
187
+ else:
188
+ print("Error in the URL")
189
+ return st_lottie(animation_json,height=200,width=300)
190
+
191
+ def oam_sidebar(step):
192
+ st.title('**Optimize a molecule**')
193
+ prog_bar = st.progress(0)
194
+ # cur_step = get_step()
195
+ if step == 0: prog_bar.progress(0)
196
+ if step == 1: prog_bar.progress(33)
197
+ if step == 2: prog_bar.progress(67)
198
+ if step == 3: prog_bar.progress(100)
199
+ st.markdown('\n')
200
+
201
+ # st.markdown(get_step())
202
+ color_ls = colorize_step(4,step)
203
+
204
+ st.markdown("<h4 style='color: "+color_ls[0]+"'>Choose a molecule</h4>",unsafe_allow_html=True)
205
+ st.markdown('|')
206
+ st.markdown("<h4 style='color: "+color_ls[1]+"'>Choose settings</h4>",unsafe_allow_html=True)
207
+ st.markdown('|')
208
+ st.markdown("<h4 style='color: "+color_ls[2]+"'>Optimizing a molecule</h4>",unsafe_allow_html=True)
209
+ st.markdown('|')
210
+ st.markdown("<h4 style='color: "+color_ls[3]+"'>Finished</h4>",unsafe_allow_html=True)
211
+
212
+ # @st.cache_data(experimental_allow_widgets=True)
213
+
214
+ # if 'sidebar_con' not in st.session_state:
215
+ # sidebar_con = st.empty()
216
+ # def render_sidebar(page,step):
217
+ # sidebar_con.empty()
218
+ # with sidebar_con.container():
219
+ # if page == 0:
220
+ # with st.sidebar():
221
+ # oam_sidebar(step)
222
+
223
+ def colorize_step(n_step,cur_step):
224
+ color_list = ['grey']*n_step
225
+ for i in range(cur_step):
226
+ color_list[i] = 'mediumseagreen'
227
+ color_list[cur_step] = 'tomato'
228
+ if cur_step == n_step-1:
229
+ color_list[cur_step] = 'mediumseagreen'
230
+ return color_list
231
+
232
+ def form_header():
233
+ st.markdown("<h1 style='text-align: center;'>Junction Tree Variational Autoencoder for Molecular Graph Generation (JTVAE)</h1>",unsafe_allow_html=True)
234
+ st.markdown("<h3 style='text-align: center;'>Wengong Jin, Regina Barzilay, Tommi Jaakkola</h3>",unsafe_allow_html=True)
235
+
236
+ # determines button color which should be red when user is on that given step
237
+ oam_type = 'primary' if st.session_state['current_view'] == 0 else 'secondary'
238
+ oab_type = 'primary' if st.session_state['current_view'] == 1 else 'secondary'
239
+ ab_type = 'primary' if st.session_state['current_view'] == 2 else 'secondary'
240
+
241
+ step_cols = st.columns([.85,.5,.5,.5,.85])
242
+ step_cols[1].button('Optimize a molecule',on_click=set_page_view,args=[0],type=oam_type,use_container_width=True)
243
+ step_cols[2].button('Optimize a batch',on_click=set_page_view,args=[1],type=oab_type,use_container_width=True)
244
+ step_cols[3].button('About',on_click=set_page_view,args=[2],type=ab_type,use_container_width=True)
245
+ st.empty()
246
+
247
+ def form_body():
248
+ body_container = st.empty()
249
+ ###### Optimize a molecule ######
250
+ if st.session_state['current_view'] == 0:
251
+ body_container.empty()
252
+ with body_container.container():
253
+ Optimize_a_molecule()
254
+ ###### Optimize a batch ######
255
+ if st.session_state['current_view'] == 1:
256
+ body_container.empty()
257
+ with body_container.container():
258
+ Optimize_a_batch()
259
+ ###### About ######
260
+ if st.session_state['current_view'] == 2:
261
+ body_container.empty()
262
+ with body_container.container():
263
+ About()
264
+
265
+ def About():
266
  descrip = '''
267
  We seek to automate the design of molecules based on specific chemical properties. In computational terms, this task involves continuous embedding and generation of molecular graphs. Our primary contribution is the direct realization of molecular graphs, a task previously approached by generating linear SMILES strings instead of graphs. Our junction tree variational autoencoder generates molecular graphs in two phases, by first generating a tree-structured scaffold over chemical substructures, and then combining them into a molecule with a graph message passing network. This approach allows us to incrementally expand molecules while maintaining chemical validity at every step. We evaluate our model on multiple tasks ranging from molecular generation to optimization. Across these tasks, our model outperforms previous state-of-the-art baselines by a significant margin.
268
 
 
271
  st.markdown("<p style='text-align: center;'>"+
272
  img_to_html('about.png')+
273
  "</p>", unsafe_allow_html=True)
274
+
275
+ def Optimize_a_molecule():
276
  st.markdown("<h2 style='text-align: center;'>Optimize a molecule</h2>",unsafe_allow_html=True)
277
+ with st.expander(':snowman: :blue[Instruction]'):
278
+ guide = """<h4 style='color:tomato;'>Steps to optimize a molucule</h4>
279
+ 1. Select from examples, or manually enter a valid SMILES string of a molecule.</br>
280
+ 2. Configure the settings to generate a new molecule. The new molecule should have a higher penalized LogP value.</br>
281
+ - Learning rate: How 'far' from the molecule that you want to search.</br>
282
+ - Similarity cutoff: How 'similar' to the molecule that you want to search.</br>
283
+ - Number of iterations: Number of generation trials.</br>
284
+ <h4 style='color:darkturquoise;'>Annotation</h4>
285
+ <b>SMILES</b> - Simplified molecular-input line-entry system</br>
286
+ <b>LogP</b> - The log of the partition coefficient of a solute between octanol and water, at near infinite dilution</br>
287
+ <b>SA score</b> - Synthetic Accessibility Score (lower is better)</br>
288
+ <b>Cycle score</b> - A number of carbon rings of size larger than 6 (lower is better)</br>
289
+ <b>Penalized LogP</b> - Standardized score of <i>LogP - SA score - Cycle score</i></br>
290
+ <b>Similarity</b> - Molecular similarity is calculated via Morgan fingerprint of radius 2 with Tanimoto similarity</br>
291
+ """
292
+ st.markdown(guide,unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
293
 
294
+ with st.sidebar:
295
+ sidebar_con = st.empty()
296
+ # sidebar_con.empty()
297
+ with sidebar_con.container():
298
+ set_step(0)
299
+ oam_sidebar(0)
300
+ oab_sel_container = st.empty()
301
+ if 'checked_single' not in st.session_state:
302
+ st.session_state.checked_single = 'NO'
303
+ with oab_sel_container.container():
304
+ oam_sel_col = st.columns([3,7])
305
+ oam_sel_col[0].selectbox("Select an example",options=['-','Sorafenib','Pazopanib','Sunitinib'],key='mode',on_change=reset_oam_state)
306
+ if st.session_state.mode == '-':
307
+ st.session_state.smiles = oam_sel_col[1].text_input('Enter a SMILES string (max 200 chars):',max_chars=200)
308
+ elif st.session_state.mode == 'Sorafenib':
309
+ st.session_state.smiles = 'CNC(=O)C1=NC=CC(=C1)OC2=CC=C(C=C2)NC(=O)NC3=CC(=C(C=C3)Cl)C(F)(F)F'
310
+ oam_sel_col[1].text_input('Enter a SMILES string (max 200 chars):','CNC(=O)C1=NC=CC(=C1)OC2=CC=C(C=C2)NC(=O)NC3=CC(=C(C=C3)Cl)C(F)(F)F',max_chars=200,disabled=True)
311
+ elif st.session_state.mode == 'Pazopanib':
312
+ st.session_state.smiles = 'CC1=C(C=C(C=C1)NC2=NC=CC(=N2)N(C)C3=CC4=NN(C(=C4C=C3)C)C)S(=O)(=O)N'
313
+ oam_sel_col[1].text_input('Enter a SMILES string (max 200 chars):','CC1=C(C=C(C=C1)NC2=NC=CC(=N2)N(C)C3=CC4=NN(C(=C4C=C3)C)C)S(=O)(=O)N',max_chars=200,disabled=True)
314
+ elif st.session_state.mode == 'Sunitinib':
315
+ st.session_state.smiles = 'CCN(CC)CCNC(=O)C1=C(NC(=C1C)C=C2C3=C(C=CC(=C3)F)NC2=O)C'
316
+ oam_sel_col[1].text_input('Enter a SMILES string (max 200 chars):','CCN(CC)CCNC(=O)C1=C(NC(=C1C)C=C2C3=C(C=CC(=C3)F)NC2=O)C',max_chars=200,disabled=True)
317
+ st.columns([4,1,4])[1].button('Check SMILES',on_click=check_single,args=[st.session_state.smiles])
318
+
319
+ check_single_con = st.empty()
320
+ if 'smiles_selected' in st.session_state:
321
+ if st.session_state.smiles_selected:
322
+ with check_single_con.container():
323
+ if 'checked_single' in st.session_state:
324
+ if st.session_state.checked_single == 'EnterError':
325
+ st.markdown("<p style='text-align: center; color: red;'><b>Please enter a SMILES string.</b></p>",unsafe_allow_html=True)
326
+ sidebar_con.empty()
327
+ with sidebar_con.container():
328
+ set_step(0)
329
+ oam_sidebar(0)
330
+ elif st.session_state.checked_single == 'MolError':
331
+ st.markdown("<p style='text-align: center; color: red;'><b>SMILES is invalid. Please enter a valid SMILES.</b></p>",unsafe_allow_html=True)
332
+ sidebar_con.empty()
333
+ with sidebar_con.container():
334
+ set_step(0)
335
+ oam_sidebar(0)
336
+ elif st.session_state.checked_single == 'YES':
337
+ st.markdown("<b>Canonicalized SMILES</b>",unsafe_allow_html=True)
338
+ st.code(st.session_state.smiles)
339
+ st.markdown("<p style='text-align: center; color: mediumseagreen'>MOSES filters passed successfully.</p>",unsafe_allow_html=True)
340
+ mol = Chem.MolFromSmiles(st.session_state.smiles)
341
+ imgByteArr = io.BytesIO()
342
+ MolToImage(mol,size=(400,400)).save(imgByteArr,format='PNG')
343
+ st.markdown("<p style='text-align: center;'>"+
344
+ f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
345
+ "</p>", unsafe_allow_html=True)
346
+ # st.image(MolToImage(mol,size=(300,300)))
347
+ col1, col2, col3, col4 = st.columns(4)
348
+ col1.metric('LogP', '%.5f' % (st.session_state.logp))
349
+ col2.metric('SA score', '%.5f' % (-st.session_state.sa))
350
+ col3.metric('Cycle score', '%d' % (-st.session_state.cycle))
351
+ col4.metric('Penalized LogP', '%.5f' % (st.session_state.pen_p))
352
+
353
+ st.session_state.smiles_checked = True
354
+ # render_sidebar()
355
+ # col1, col2, col3 = st.columns(3)
356
+ sidebar_con.empty()
357
+ with sidebar_con.container():
358
+ set_step(1)
359
+ oam_sidebar(1)
360
+ with st.form(":gear: Settings"):
361
+ st.slider('Choose learning rate: ',0.0,5.0,0.4,key='lr_s')
362
+ st.slider('Choose similarity cutoff: ',0.0,1.0,0.4,key='sim_cutoff_s')
363
+ st.slider('Choose number of iterations: ',1,100,80,key='n_iter_s')
364
+ st.session_state.optim_single_butt = st.form_submit_button("Optimize")
365
+ else:
366
+ st.markdown("<b>Canonicalized SMILES</b>",unsafe_allow_html=True)
367
+ st.code(st.session_state.smiles)
368
+ st.markdown("<p style='text-align: center; color: red;'><b>MOSES filters passed failed. Please use another molecule.</b></p>",unsafe_allow_html=True)
369
+ sidebar_con.empty()
370
+ with sidebar_con.container():
371
+ set_step(0)
372
+ oam_sidebar(0)
373
+ else: check_single_con.empty()
374
+
375
+ optim_single_con = st.empty()
376
+ if 'optim_single_butt' in st.session_state:
377
+ if st.session_state.optim_single_butt and st.session_state.smiles_checked:
378
+ sidebar_con.empty()
379
+ with sidebar_con.container():
380
+ set_step(2)
381
+ oam_sidebar(2)
382
+ with optim_single_con.container():
383
+ ani_con = st.empty()
384
+ with ani_con.container():
385
+ st.markdown('Operation in progress. Please wait...')
386
+ render_animation()
387
+ new_smiles,sim = optim_single(st.session_state.smiles,st.session_state.lr_s,st.session_state.sim_cutoff_s,st.session_state.n_iter_s)
388
+ ani_con.empty()
389
+ sidebar_con.empty()
390
+ with sidebar_con.container():
391
+ set_step(3)
392
+ oam_sidebar(3)
393
  if new_smiles is None:
394
+ st.markdown("<h4 style='text-align: center; color: red;'>Cannot optimize! Please choose another setting.</h4>",unsafe_allow_html=True)
395
  else:
396
  st.markdown("<b style='text-align: center;'>New SMILES</b>",unsafe_allow_html=True)
397
  st.code(new_smiles)
 
406
  st.markdown("<p style='text-align: center;'>"+
407
  f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
408
  "</p>", unsafe_allow_html=True)
409
+
410
+ new_moses_passed = mol_passes_filters_custom(new_mol)
411
+ if new_moses_passed=='YES':
412
+ st.markdown("<p style='text-align: center; color: mediumseagreen'>MOSES filters passed successfully.</p>",unsafe_allow_html=True)
413
+ else:
414
+ st.markdown("<p style='text-align: center; color: red;'><b>MOSES filters passed failed.</b></p>",unsafe_allow_html=True)
415
+ st.session_state.new_logp,st.session_state.new_sa,st.session_state.new_cycle,st.session_state.new_pen_p = penalized_logp_standard(new_mol)
416
  # st.write('New penalized logP score: %.5f' % (new_score))
417
+ col12, col22, col32, col42 = st.columns(4)
418
+ col12.metric('LogP', '%.5f' % (st.session_state.new_logp),'%.5f'%(st.session_state.new_logp-st.session_state.logp))
419
+ col22.metric('SA score', '%.5f' % (-st.session_state.new_sa),'%.5f'%(-st.session_state.new_sa+st.session_state.sa),delta_color='inverse')
420
+ col32.metric('Cycle score', '%d' % (-st.session_state.new_cycle),'%d'%(-st.session_state.new_cycle+st.session_state.cycle),delta_color='inverse')
421
+ col42.metric('Penalized LogP', '%.5f' % (st.session_state.new_pen_p),'%.5f'%(st.session_state.new_pen_p-st.session_state.pen_p))
422
+ # st.metric('New penalized logP score','%.5f' % (new_score), '%.5f'%(new_score-score))
423
  st.metric('Similarity','%.5f' % (sim))
424
  # st.write('Caching ZINC20 if necessary...')
425
  with st.spinner("Caching ZINC20 if necessary..."):
426
  if buy(new_smiles, catalog='zinc20',canonicalize=True):
427
  st.write('This molecule exists.')
428
+ st.markdown("<h3 style='text-align: center; color: darkturquoise;'><b>This molecule exists.</h3>",unsafe_allow_html=True)
429
  else:
430
  # st.write('THIS MOLECULE DOES NOT EXIST!')
431
+ st.markdown("<h3 style='text-align: center; color: mediumseagreen;'>THIS MOLECULE DOES NOT EXIST!</h3>",unsafe_allow_html=True)
432
  st.markdown("<p style='text-align: center; color: grey;'>Checked using molbloom</p>",unsafe_allow_html=True)
433
+ else: optim_single_con.empty()
434
+
435
+ def check_single(smiles):
436
+ # render_view()
437
+ st.session_state.smiles_selected = True
438
+ check_single_con = st.empty()
439
+
440
+ # optim = False
441
+ with check_single_con.container():
442
+ if len(smiles) == 0:
443
+ st.session_state.checked_single = 'EnterError'
444
+ else:
445
+ mol = Chem.MolFromSmiles(smiles)
446
+ if mol is None:
447
+ st.session_state.checked_single = 'MolError'
448
+ else:
449
+ st.session_state.smiles = Chem.MolToSmiles(mol)
450
+ st.session_state.logp,st.session_state.sa,st.session_state.cycle,st.session_state.pen_p = penalized_logp_standard(mol)
451
+ moses_passed = mol_passes_filters_custom(mol)
452
+ st.session_state.checked_single = moses_passed
453
+
454
+
455
+ def optim_single(smiles,lr,sim_cutoff,n_iter):
456
+ # render_view()
457
+
458
+
459
+ model = load_model()
460
+
461
+ new_smiles,sim = model.optimize(smiles, sim_cutoff=sim_cutoff, lr=lr, num_iter=n_iter)
462
+
463
+ return new_smiles,sim
464
+
465
+
466
+
467
+ def Optimize_a_batch():
468
+ st.empty()
469
+
470
+ def reset_oam_state():
471
+ st.session_state.smiles_selected = False
472
+ st.session_state.checked_single = 'NO'
473
+ st.session_state.smiles_checked = False
474
+ set_step(0)
475
+
476
+ def rerun():
477
+ st.experimental_rerun()
478
+ def render_view():
479
+ # render_sidebar(st.session_state.current_view,st.session_state.current_step)
480
+ form_header()
481
+ form_body()
482
 
483
 
484
+ render_view()
app_backup.py ADDED
@@ -0,0 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pathlib import Path
2
+ import torch
3
+ from st_on_hover_tabs import on_hover_tabs
4
+ import streamlit as st
5
+ st.set_page_config(layout="wide")
6
+
7
+ model_path = './model.iter-685000'
8
+
9
+ import sys, os
10
+ import rdkit
11
+ import rdkit.Chem as Chem
12
+ from rdkit.Chem.Draw import MolToImage
13
+ from rdkit.Chem import Descriptors
14
+ import sascorer
15
+ import networkx as nx
16
+ from stqdm import stqdm
17
+ import base64, io
18
+ import pandas as pd
19
+ import streamlit_ext as ste
20
+
21
+ os.environ['KMP_DUPLICATE_LIB_OK']='True'
22
+
23
+ sys.path.append('%s/fast_jtnn/' % os.path.dirname(os.path.realpath(__file__)))
24
+ from mol_tree import Vocab, MolTree
25
+ from jtprop_vae import JTPropVAE
26
+ from molbloom import buy
27
+
28
+ css='''
29
+ [data-testid="metric-container"] {
30
+ width: fit-content;
31
+ margin: auto;
32
+ }
33
+
34
+ [data-testid="metric-container"] > div {
35
+ width: fit-content;
36
+ margin: auto;
37
+ }
38
+
39
+ [data-testid="metric-container"] label {
40
+ width: fit-content;
41
+ margin: auto;
42
+
43
+ [data-testid="stDataFrameResizable"] {
44
+ width: fit-content;
45
+ margin: auto;
46
+ }
47
+ }
48
+ '''
49
+
50
+ st.markdown(f'<style>{css}</style>',unsafe_allow_html=True)
51
+
52
+ s_buff = io.StringIO()
53
+ def img_to_bytes(img_path):
54
+ img_bytes = Path(img_path).read_bytes()
55
+ encoded = base64.b64encode(img_bytes).decode()
56
+ return encoded
57
+ def img_to_html(img_path):
58
+ img_html = "<img src='data:image/png;base64,{}' class='img-fluid' style='max-width: 500px;'>".format(
59
+ img_to_bytes(img_path)
60
+ )
61
+ return img_html
62
+
63
+ _mcf = pd.read_csv('./mcf.csv')
64
+ _pains = pd.read_csv('./wehi_pains.csv',
65
+ names=['smarts', 'names'])
66
+ _mcf_filters = [Chem.MolFromSmarts(x) for x in
67
+ _mcf['smarts'].values]
68
+ _pains_filters = [Chem.MolFromSmarts(x) for x in
69
+ _pains['smarts'].values]
70
+
71
+ def mol_passes_filters_custom(mol,
72
+ allowed=None,
73
+ isomericSmiles=False):
74
+ """
75
+ Checks if mol
76
+ * passes MCF and PAINS filters,
77
+ * has only allowed atoms
78
+ * is not charged
79
+ """
80
+ allowed = allowed or {'C', 'N', 'S', 'O', 'F', 'Cl', 'Br', 'H'}
81
+ if mol is None:
82
+ return 'NoMol'
83
+ ring_info = mol.GetRingInfo()
84
+ if ring_info.NumRings() != 0 and any(
85
+ len(x) >= 8 for x in ring_info.AtomRings()
86
+ ):
87
+ return 'ManyRings'
88
+ h_mol = Chem.AddHs(mol)
89
+ if any(atom.GetFormalCharge() != 0 for atom in mol.GetAtoms()):
90
+ return 'Charged'
91
+ if any(atom.GetSymbol() not in allowed for atom in mol.GetAtoms()):
92
+ return 'AtomNotAllowed'
93
+ if any(h_mol.HasSubstructMatch(smarts) for smarts in _mcf_filters):
94
+ return 'MCF'
95
+ if any(h_mol.HasSubstructMatch(smarts) for smarts in _pains_filters):
96
+ return 'PAINS'
97
+ smiles = Chem.MolToSmiles(mol, isomericSmiles=isomericSmiles)
98
+ if smiles is None or len(smiles) == 0:
99
+ return 'Isomeric'
100
+ if Chem.MolFromSmiles(smiles) is None:
101
+ return 'Isomeric'
102
+ return 'YES'
103
+
104
+ def penalized_logp_standard(mol):
105
+
106
+ logP_mean = 2.4399606244103639873799239
107
+ logP_std = 0.9293197802518905481505840
108
+ SA_mean = -2.4485512208785431553792478
109
+ SA_std = 0.4603110476923852334429910
110
+ cycle_mean = -0.0307270378623088931402396
111
+ cycle_std = 0.2163675785228087178335699
112
+
113
+ log_p = Descriptors.MolLogP(mol)
114
+ SA = -sascorer.calculateScore(mol)
115
+
116
+ # cycle score
117
+ cycle_list = nx.cycle_basis(nx.Graph(Chem.rdmolops.GetAdjacencyMatrix(mol)))
118
+ if len(cycle_list) == 0:
119
+ cycle_length = 0
120
+ else:
121
+ cycle_length = max([len(j) for j in cycle_list])
122
+ if cycle_length <= 6:
123
+ cycle_length = 0
124
+ else:
125
+ cycle_length = cycle_length - 6
126
+ cycle_score = -cycle_length
127
+ # print(logP_mean)
128
+
129
+ standardized_log_p = (log_p - logP_mean) / logP_std
130
+ standardized_SA = (SA - SA_mean) / SA_std
131
+ standardized_cycle = (cycle_score - cycle_mean) / cycle_std
132
+ return log_p,SA,cycle_score,standardized_log_p + standardized_SA + standardized_cycle
133
+
134
+ lg = rdkit.RDLogger.logger()
135
+ lg.setLevel(rdkit.RDLogger.CRITICAL)
136
+
137
+ def About():
138
+ descrip = '''
139
+ We seek to automate the design of molecules based on specific chemical properties. In computational terms, this task involves continuous embedding and generation of molecular graphs. Our primary contribution is the direct realization of molecular graphs, a task previously approached by generating linear SMILES strings instead of graphs. Our junction tree variational autoencoder generates molecular graphs in two phases, by first generating a tree-structured scaffold over chemical substructures, and then combining them into a molecule with a graph message passing network. This approach allows us to incrementally expand molecules while maintaining chemical validity at every step. We evaluate our model on multiple tasks ranging from molecular generation to optimization. Across these tasks, our model outperforms previous state-of-the-art baselines by a significant margin.
140
+
141
+ [https://arxiv.org/abs/1802.04364](https://arxiv.org/abs/1802.04364)'''
142
+ st.markdown(descrip)
143
+ st.markdown("<p style='text-align: center;'>"+
144
+ img_to_html('about.png')+
145
+ "</p>", unsafe_allow_html=True)
146
+
147
+ def Optimize_a_molecule():
148
+ st.markdown("<h2 style='text-align: center;'>Optimize a molecule</h2>",unsafe_allow_html=True)
149
+ with st.expander(':snowman: :blue[Instruction]'):
150
+ guide = """<h4 style='color:tomato;'>Steps to optimize a molucule</h4>
151
+ 1. Select from examples, or manually enter a valid SMILES string of a molecule.</br>
152
+ 2. Configure the settings to generate a new molecule. The new molecule should have a higher penalized LogP value.</br>
153
+ - Learning rate: How 'far' from the molecule that you want to search.</br>
154
+ - Similarity cutoff: How 'similar' to the molecule that you want to search.</br>
155
+ - Number of iterations: Number of generation trials.</br>
156
+ <h4 style='color:darkturquoise;'>Annotation</h4>
157
+ <b>SMILES</b> - Simplified molecular-input line-entry system</br>
158
+ <b>LogP</b> - The log of the partition coefficient of a solute between octanol and water, at near infinite dilution</br>
159
+ <b>SA score</b> - Synthetic Accessibility Score (lower is better)</br>
160
+ <b>Cycle score</b> - A number of carbon rings of size larger than 6 (lower is better)</br>
161
+ <b>Penalized LogP</b> - Standardized score of <i>LogP - SA score - Cycle score</i></br>
162
+ <b>Similarity</b> - Molecular similarity is calculated via Morgan fingerprint of radius 2 with Tanimoto similarity</br>
163
+ """
164
+ st.markdown(guide,unsafe_allow_html=True)
165
+
166
+ st.selectbox("Select an example",options=['-','Sorafenib','Pazopanib','Sunitinib'],key='mode')
167
+ if st.session_state.mode == '-':
168
+ smiles = st.text_input('Enter a SMILES string (max 200 chars):',max_chars=200)
169
+ elif st.session_state.mode == 'Sorafenib':
170
+ smiles = 'CNC(=O)C1=NC=CC(=C1)OC2=CC=C(C=C2)NC(=O)NC3=CC(=C(C=C3)Cl)C(F)(F)F'
171
+ elif st.session_state.mode == 'Pazopanib':
172
+ smiles = 'CC1=C(C=C(C=C1)NC2=NC=CC(=N2)N(C)C3=CC4=NN(C(=C4C=C3)C)C)S(=O)(=O)N'
173
+ elif st.session_state.mode == 'Sunitinib':
174
+ smiles = 'CCN(CC)CCNC(=O)C1=C(NC(=C1C)C=C2C3=C(C=CC(=C3)F)NC2=O)C'
175
+
176
+ if len(smiles) > 0:
177
+ mol = Chem.MolFromSmiles(smiles)
178
+ if mol is None:
179
+ st.markdown("<p style='text-align: center; color: red;'><b>SMILES is invalid. Please enter a valid SMILES.</b></p>",unsafe_allow_html=True)
180
+ else:
181
+ canon_smiles = Chem.MolToSmiles(mol)
182
+ st.markdown("<b>Canonicalized SMILES</b>",unsafe_allow_html=True)
183
+ st.code(canon_smiles)
184
+ logp,sa,cycle,pen_p = penalized_logp_standard(mol)
185
+ moses_passed = mol_passes_filters_custom(mol)
186
+ if moses_passed=='YES':
187
+ st.markdown("<p style='text-align: center; color: mediumseagreen'>MOSES filters passed successfully.</p>",unsafe_allow_html=True)
188
+ else:
189
+ st.markdown("<p style='text-align: center; color: red;'><b>MOSES filters passed failed. Please use another molecule.</b></p>",unsafe_allow_html=True)
190
+ # with st.columns(3)[1]:
191
+ # st.markdown("<style>{text-align: center;}</style>",unsafe_allow_html=True)
192
+ imgByteArr = io.BytesIO()
193
+ MolToImage(mol,size=(400,400)).save(imgByteArr,format='PNG')
194
+ st.markdown("<p style='text-align: center;'>"+
195
+ f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
196
+ "</p>", unsafe_allow_html=True)
197
+ # st.image(MolToImage(mol,size=(300,300)))
198
+ col1, col2, col3, col4 = st.columns(4)
199
+ col1.metric('LogP', '%.5f' % (logp))
200
+ col2.metric('SA score', '%.5f' % (-sa))
201
+ col3.metric('Cycle score', '%d' % (-cycle))
202
+ col4.metric('Penalized LogP', '%.5f' % (pen_p))
203
+
204
+ if (mol is not None) and (moses_passed=='YES'):
205
+ # col1, col2, col3 = st.columns(3)
206
+ with st.form(":gear:Settings"):
207
+ st.slider('Choose learning rate: ',0.0,5.0,0.4,key='lr')
208
+ st.slider('Choose similarity cutoff: ',0.0,1.0,0.4,key='sim_cutoff')
209
+ st.slider('Choose number of iterations: ',1,100,80,key='n_iter')
210
+ optim = st.form_submit_button("Optimize")
211
+ vocab = [x.strip("\r\n ") for x in open('./vocab.txt')]
212
+ vocab = Vocab(vocab)
213
+ if optim:
214
+ # st.write('Testing')
215
+ # canon_smiles = Chem.MolToSmiles(mol)
216
+
217
+ # with st.columns(3)[1]:
218
+ with st.spinner("Operation in progress. Please wait."):
219
+
220
+ model = JTPropVAE(vocab, 450, 56, 20, 3)
221
+
222
+ model.load_state_dict(torch.load(model_path,map_location=torch.device('cpu')))
223
+
224
+ new_smiles,sim = model.optimize(canon_smiles, sim_cutoff=st.session_state.sim_cutoff, lr=st.session_state.lr, num_iter=st.session_state.n_iter)
225
+
226
+ del model
227
+ if new_smiles is None:
228
+ st.markdown("<h4 style='text-align: center; color: red;'>Cannot optimize! Please choose another setting.</h4>",unsafe_allow_html=True)
229
+ else:
230
+ st.markdown("<b style='text-align: center;'>New SMILES</b>",unsafe_allow_html=True)
231
+ st.code(new_smiles)
232
+ new_mol = Chem.MolFromSmiles(new_smiles)
233
+ if new_mol is None:
234
+ st.markdown("<p style='text-align: center; color: red;'>New SMILES is invalid! Please choose another setting.</p>",unsafe_allow_html=True)
235
+ # st.write('New SMILES is invalid.')
236
+ else:
237
+ # st.write('New SMILES molecule:')
238
+ imgByteArr = io.BytesIO()
239
+ MolToImage(new_mol,size=(400,400)).save(imgByteArr,format='PNG')
240
+ st.markdown("<p style='text-align: center;'>"+
241
+ f"<img src='data:image/png;base64,{base64.b64encode(imgByteArr.getvalue()).decode()}' class='img-fluid'>"+
242
+ "</p>", unsafe_allow_html=True)
243
+
244
+ new_moses_passed = mol_passes_filters_custom(new_mol)
245
+ if new_moses_passed=='YES':
246
+ st.markdown("<p style='text-align: center; color: mediumseagreen'>MOSES filters passed successfully.</p>",unsafe_allow_html=True)
247
+ else:
248
+ st.markdown("<p style='text-align: center; color: red;'><b>MOSES filters passed failed.</b></p>",unsafe_allow_html=True)
249
+ new_logp,new_sa,new_cycle,new_pen_p = penalized_logp_standard(new_mol)
250
+ # st.write('New penalized logP score: %.5f' % (new_score))
251
+ col12, col22, col32, col42 = st.columns(4)
252
+ col12.metric('LogP', '%.5f' % (new_logp),'%.5f'%(new_logp-logp))
253
+ col22.metric('SA score', '%.5f' % (-new_sa),'%.5f'%(-new_sa+sa),delta_color='inverse')
254
+ col32.metric('Cycle score', '%d' % (-new_cycle),'%d'%(-new_cycle+cycle),delta_color='inverse')
255
+ col42.metric('Penalized LogP', '%.5f' % (new_pen_p),'%.5f'%(new_pen_p-pen_p))
256
+ # st.metric('New penalized logP score','%.5f' % (new_score), '%.5f'%(new_score-score))
257
+ st.metric('Similarity','%.5f' % (sim))
258
+ # st.write('Caching ZINC20 if necessary...')
259
+ with st.spinner("Caching ZINC20 if necessary..."):
260
+ if buy(new_smiles, catalog='zinc20',canonicalize=True):
261
+ st.write('This molecule exists.')
262
+ st.markdown("<h3 style='text-align: center; color: darkturquoise;'><b>This molecule exists.</h3>",unsafe_allow_html=True)
263
+ else:
264
+ # st.write('THIS MOLECULE DOES NOT EXIST!')
265
+ st.markdown("<h3 style='text-align: center; color: mediumseagreen;'>THIS MOLECULE DOES NOT EXIST!</h3>",unsafe_allow_html=True)
266
+ st.markdown("<p style='text-align: center; color: grey;'>Checked using molbloom</p>",unsafe_allow_html=True)
267
+
268
+ def Optimize_a_batch():
269
+ st.markdown("<h2 style='text-align: center;'>Optimize a batch</h2>",unsafe_allow_html=True)
270
+ # st.text_input('Enter a SMILES string:','CNC(=O)C1=NC=CC(=C1)OC2=CC=C(C=C2)NC(=O)NC3=CC(=C(C=C3)Cl)C(F)(F)F',key='smiles')
271
+ with st.expander(':snowman: :blue[Instruction]'):
272
+ guide = """<h4 style='color:tomato;'>Steps to optimize a batch</h4>
273
+ 1. Upload a text file with SMILES string on each line.</br>
274
+ 2. Check the SMILES strings to make sure that they are valid and pass MOSES filters.</br>
275
+ 3. Select scores to calculate (penalized LogP included). Keep passed SMILES and calculate selected scores.</br>
276
+ 4. Configure the settings to generate new molecules. The new molecules should have higher penalized LogP values.</br>
277
+ - Learning rate: How 'far' from each molecule that you want to search</br>
278
+ - Similarity cutoff: How 'similar' to each molecule that you want to search</br>
279
+ - Number of iterations: Number of generation trials per molecule</br>
280
+ 5. <i>(Optional)</i> You can download the dataframe at any steps as *.csv file.</br>
281
+ <h4 style='color:darkturquoise;'>Annotation</h4>
282
+ <b>SMILES</b> - Simplified molecular-input line-entry system</br>
283
+ <b>LogP</b> - The log of the partition coefficient of a solute between octanol and water, at near infinite dilution</br>
284
+ <b>SA score</b> - Synthetic Accessibility Score (lower is better)</br>
285
+ <b>Cycle score</b> - A number of carbon rings of size larger than 6 (lower is better)</br>
286
+ <b>Penalized LogP</b> - Standardized score of <i>LogP - SA score - Cycle score</i></br>
287
+ <b>Similarity</b> - Molecular similarity is calculated via Morgan fingerprint of radius 2 with Tanimoto similarity.</br>
288
+ """
289
+ st.markdown(guide,unsafe_allow_html=True)
290
+ st.session_state['smiles_file'] = st.file_uploader("Upload a text file with SMILES on each line :sparkles:")
291
+ if st.session_state.smiles_file is not None:
292
+ # smiles_list = [str(line).rstrip('\n') for line in smiles_file]
293
+ smiles_list = io.StringIO(st.session_state.smiles_file.getvalue().decode("utf-8"))
294
+ smiles_list = list(smiles_list.getvalue().rstrip().split('\n'))
295
+ st.markdown('Number of SMILES: '+str(len(smiles_list)))
296
+ if len(smiles_list) == 1:
297
+ st.markdown("<p style='text-align: center; color: red;'><b>Please use <i>Optimize a molecule</i> tab.</b></p>",unsafe_allow_html=True)
298
+ else:
299
+ def check_smiles():
300
+ check = []
301
+ for smi in stqdm(smiles_list):
302
+ mol = Chem.MolFromSmiles(smi)
303
+ if (mol is not None) and (mol_passes_filters_custom(mol) == 'YES'):
304
+ check.append(Chem.MolToSmiles(mol))
305
+ else:
306
+ check.append('invalid')
307
+ st.session_state['df'] = pd.concat([df,pd.DataFrame({'checked':check})],axis=1)
308
+ df = pd.DataFrame({'SMILES':smiles_list})
309
+ st.dataframe(df,use_container_width=True)
310
+ st.button('Check SMILES',key='check',on_click=check_smiles)
311
+
312
+ if 'df' in st.session_state:
313
+ # st.markdown('Number of SMILES: '+str(len(st.session_state.smiles_list)))
314
+ df = st.session_state['df']
315
+ st.markdown('Number of passed SMILES: '+str(df[df.checked != 'invalid'].shape[0]))
316
+ st.dataframe(df,use_container_width=True)
317
+ download_df(df,0)
318
+ with st.form("Choose score to calculate"):
319
+ st.caption('Penalized LogP is always calculated.')
320
+ logp_cal= st.checkbox('LogP',key='logp_cal')
321
+ sa_cal= st.checkbox('SA score',key='sa_cal')
322
+ cycle_cal= st.checkbox('Cycle score',key='cycle_cal')
323
+ calc = st.form_submit_button("Keep passed SMILES and calculate scores")
324
+ # st.session_state['pen_logp_cal'] = True
325
+ if calc:
326
+ smiles_list = list(df[df.checked != 'invalid'].SMILES)
327
+ # st.write(smiles_list)
328
+ score_df = pd.DataFrame({'SMILES':smiles_list})
329
+ scores =[]
330
+ # pen_logp_ls = logp_ls = sa_ls = cycle_ls =[]
331
+ st.session_state.sc_name = ['logp','sa','cycle','pen_logp']
332
+ for smi in stqdm(smiles_list):
333
+ logp,sa,cycle,pen_logp = penalized_logp_standard(Chem.MolFromSmiles(smi))
334
+ # st.write(f"{logp}\t{sa}\t{cycle}\t{pen_logp}")
335
+ scores+=[(logp,sa,cycle,pen_logp)]
336
+ s_df = pd.DataFrame(scores,columns=st.session_state.sc_name)
337
+ # st.write(st.session_state.log_ls)
338
+ # to_concat = [score_df] + [d for d,checked in zip(dfs,[logp_cal,sa_cal,cycle_cal]) if checked] + [pd.DataFrame({'pen_logp':pen_logp_ls})]
339
+ for n, checked in zip(st.session_state.sc_name,[logp_cal,sa_cal,cycle_cal,True]):
340
+ if checked:
341
+ score_df = pd.concat([score_df,s_df[n]],axis=1)
342
+ # score_df['pen_logp'] = st.session_state.pen_logp_ls
343
+ st.session_state.score_df = score_df
344
+ st.dataframe(score_df,use_container_width=True)
345
+ download_df(score_df,1)
346
+ def batch_generate(smiles_list,df):
347
+ vocab = [x.strip("\r\n ") for x in open('./vocab.txt')]
348
+ vocab = Vocab(vocab)
349
+ container = st.empty()
350
+ with container.container():
351
+
352
+ st_lottie(get_ani_json(), height=200, width=300)
353
+ st.markdown('Please wait...')
354
+
355
+ model = JTPropVAE(vocab, 450, 56, 20, 3)
356
+
357
+ model.load_state_dict(torch.load(model_path,map_location=torch.device('cpu')))
358
+
359
+ new_scores =[]
360
+ for canon_smiles in stqdm(smiles_list):
361
+ new_smiles,sim = model.optimize(canon_smiles, sim_cutoff=st.session_state.sim_cutoff, lr=st.session_state.lr, num_iter=st.session_state.n_iter)
362
+ if new_smiles is None:
363
+ new_scores+=[('invalid',-100.0,-100.0,-100.0,-100.0,-100.0)]
364
+ else:
365
+ new_mol = Chem.MolFromSmiles(new_smiles)
366
+ if new_mol is None:
367
+ new_scores+=[('invalid',-100.0,-100.0,-100.0,-100.0,-100.0)]
368
+ else:
369
+ logp,sa,cycle,pen_logp = penalized_logp_standard(new_mol)
370
+ new_scores+=[(new_smiles,sim,logp,sa,cycle,pen_logp)]
371
+
372
+ del model
373
+ with container.container():
374
+ sc_name = ['new_'+n for n in st.session_state.sc_name]
375
+ s_df = pd.DataFrame(new_scores,columns=['new_smiles','sim']+sc_name)
376
+ new_score_df = df
377
+ # st.write(st.session_state.log_ls)
378
+ # to_concat = [score_df] + [d for d,checked in zip(dfs,[logp_cal,sa_cal,cycle_cal]) if checked] + [pd.DataFrame({'pen_logp':pen_logp_ls})]
379
+ for n, checked in zip(['new_smiles','sim']+sc_name,[True, True,st.session_state.logp_cal,st.session_state.sa_cal,st.session_state.cycle_cal,True]):
380
+ if checked:
381
+ new_score_df = pd.concat([new_score_df,s_df[n]],axis=1)
382
+ st.markdown("<h3 style='text-align: center; color: mediumseagreen;'>RESULTS</h3>",unsafe_allow_html=True)
383
+ st.dataframe(new_score_df,use_container_width=True)
384
+ download_df(new_score_df,2)
385
+ with st.form(":gear: Settings",clear_on_submit=False):
386
+ st.slider('Choose learning rate: ',0.0,5.0,0.4,key='lr')
387
+ st.slider('Choose similarity cutoff: ',0.0,1.0,0.4,key='sim_cutoff')
388
+ st.slider('Choose number of iterations: ',1,100,80,key='n_iter')
389
+ optim = st.form_submit_button("Optimize",on_click=batch_generate,args=(smiles_list,score_df))
390
+
391
+
392
+ from streamlit_lottie import st_lottie
393
+ import requests
394
+
395
+ def get_ani_json():
396
+ animation_response = requests.get('https://assets1.lottiefiles.com/packages/lf20_vykpwt8b.json')
397
+ animation_json = dict()
398
+
399
+ if animation_response.status_code == 200:
400
+ animation_json = animation_response.json()
401
+ else:
402
+ print("Error in the URL")
403
+
404
+ return animation_json
405
+
406
+ def download_df(df,id):
407
+ with st.expander(':arrow_down: Download this dataframe'):
408
+ st.markdown("<h4 style='color:tomato;'>Select column(s) to save:</h4>",unsafe_allow_html=True)
409
+ for col in df.columns:
410
+ st.checkbox(col,key=str(id)+'_col_'+str(col))
411
+ st.text_input('File name (.csv):','dataframe',key=str(id)+'_file_name')
412
+
413
+ ste.download_button('Download',df_to_file(df[[col for col in df.columns if st.session_state[str(id)+'_col_'+str(col)]]]),st.session_state[str(id)+'_file_name']+'.csv')
414
+
415
+ def df_to_file(df):
416
+ s_buff.seek(0)
417
+ df.to_csv(s_buff)
418
+ return s_buff.getvalue().encode()
419
+
420
+ st.markdown("<h1 style='text-align: center;'>Junction Tree Variational Autoencoder for Molecular Graph Generation (JTVAE)</h1>",unsafe_allow_html=True)
421
+ st.markdown("<h3 style='text-align: center;'>Wengong Jin, Regina Barzilay, Tommi Jaakkola</h3>",unsafe_allow_html=True)
422
+ # st.markdown("<-- Use the sidebar to explore --")
423
+ st.markdown("<h4 style='text-align: center;'>-- Use the sidebar to explore --</h4>",unsafe_allow_html=True)
424
+ st.markdown('<style>' + open('./style.css').read() + '</style>', unsafe_allow_html=True)
425
+
426
+ with st.sidebar:
427
+ # st.header('+')
428
+ st.markdown("<h5 style='text-align: center; color:grey;'>Explore</h5>",unsafe_allow_html=True)
429
+ tabs = on_hover_tabs(tabName=['Optimize a molecule', 'Optimize a batch', 'About'],
430
+ iconName=['science', 'batch_prediction', 'info'], default_choice=0)
431
+
432
+ page_names_to_funcs = {
433
+ "About": About,
434
+ "Optimize a molecule":Optimize_a_molecule,
435
+ "Optimize a batch":Optimize_a_batch
436
+ }
437
+ page_names_to_funcs[tabs]()
mcf.csv ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ names,smarts
2
+ MCF1,[#6]=&!@[#6]-[#6]#[#7]
3
+ MCF2,[#6]=&!@[#6]-[#16](=[#8])=[#8]
4
+ MCF3,[#6]=&!@[#6&!H0]-&!@[#6](=[#8])-&!@[#7]
5
+ MCF4,"[H]C([H])([#6])[F,Cl,Br,I]"
6
+ MCF5,[#6]1-[#8]-[#6]-1
7
+ MCF6,[#6]-[#7]=[#6]=[#8]
8
+ MCF7,[#6&!H0]=[#8]
9
+ MCF8,"[#6](=&!@[#7&!H0])-&!@[#6,#7,#8,#16]"
10
+ MCF9,[#6]1-[#7]-[#6]-1
11
+ MCF10,[#6]~&!@[#7]~&!@[#7]~&!@[#6]
12
+ MCF11,[#7]=&!@[#7]
13
+ MCF12,[H][#6]-1=[#6]([H])-[#6]=[#6](-*)-[#8]-1
14
+ MCF13,[H][#6]-1=[#6]([H])-[#6]=[#6](-*)-[#16]-1
15
+ MCF14,"[#17,#35,#53]-c(:*):[!#1!#6]:*"
16
+ MCF15,[H][#7]([H])-[#6]-1=[#6]-[#6]=[#6]-[#6]=[#6]-1
17
+ MCF16,[#16]~[#16]
18
+ MCF17,[#7]~&!@[#7]~&!@[#7]
19
+ MCF18,[#7]-&!@[#6&!H0&!H1]-&!@[#7]
20
+ MCF19,[#6&!H0](-&!@[#8])-&!@[#8]
21
+ MCF20,[#35].[#35].[#35]
22
+ MCF21,[#17].[#17].[#17].[#17]
23
+ MCF22,[#9].[#9].[#9].[#9].[#9].[#9].[#9]
requirements.txt CHANGED
@@ -6,4 +6,8 @@ networkx
6
  scipy
7
  molbloom
8
  streamlit-on-Hover-tabs
9
- stqdm
 
 
 
 
 
6
  scipy
7
  molbloom
8
  streamlit-on-Hover-tabs
9
+ stqdm
10
+ pandas
11
+ streamlit_ext
12
+ streamlit_lottie
13
+ requests
style.css CHANGED
@@ -8,14 +8,14 @@ section[data-testid='stSidebar'] {
8
 
9
  button[kind="header"] {
10
  background-color: transparent;
11
- color:rgb(180, 167, 141)
12
  }
13
 
14
  @media(hover){
15
  /* header element to be removed */
16
- header[data-testid="stHeader"] {
17
- display:none;
18
- }
19
 
20
  /* The navigation menu specs and size */
21
  section[data-testid='stSidebar'] > div {
 
8
 
9
  button[kind="header"] {
10
  background-color: transparent;
11
+ color:rgb(251, 177, 125)
12
  }
13
 
14
  @media(hover){
15
  /* header element to be removed */
16
+ /* header[data-testid="stHeader"] {
17
+ display:none; */
18
+ /* } */
19
 
20
  /* The navigation menu specs and size */
21
  section[data-testid='stSidebar'] > div {
wehi_pains.csv ADDED
The diff for this file is too large to render. See raw diff