andreamena commited on
Commit
2b3e503
·
verified ·
1 Parent(s): 4eb1f03

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +83 -99
app.py CHANGED
@@ -6,8 +6,8 @@ import torch
6
  import torch.nn as nn
7
  import torch.nn.functional as F
8
  import xgboost as xgb
9
- from rdkit import Chem
10
- from rdkit import DataStructs
11
  from rdkit.Chem import Descriptors, rdFingerprintGenerator
12
  from rdkit.Chem import RDConfig
13
  import sys
@@ -59,12 +59,11 @@ def get_mff_features(smiles):
59
  desc = [Descriptors.MolWt(mol), Descriptors.MolLogP(mol), Descriptors.TPSA(mol), Descriptors.NumValenceElectrons(mol), 0.0]
60
  return np.concatenate([mff, np.array(desc)])
61
 
62
- # --- 4. PREDICT TAB LOGIC ---
63
  def predict_ui(app_choice, smiles):
64
  if not smiles: return "Please enter a SMILES string."
65
  feats = get_mff_features(smiles)
66
  if feats is None: return "Invalid p-SMILES"
67
-
68
  if app_choice == "Structural (Strength)":
69
  res = np.expm1(strength_model.predict(feats.reshape(1, -1)))[0]
70
  return f"Predicted Yield Strength: {res:.4f} GPa"
@@ -78,67 +77,55 @@ def predict_ui(app_choice, smiles):
78
  def batch_predict(app_choice, file_obj):
79
  try:
80
  filename = file_obj.name
81
- # --- NEW: Support for both CSV and Excel ---
82
  if filename.endswith('.csv'):
83
- try:
84
- df = pd.read_csv(filename, encoding='utf-8', on_bad_lines='skip')
85
- except UnicodeDecodeError:
86
- df = pd.read_csv(filename, encoding='latin1', on_bad_lines='skip')
87
  elif filename.endswith(('.xls', '.xlsx')):
88
- # Added engine='openpyxl' for better compatibility
89
  df = pd.read_excel(filename, engine='openpyxl')
90
- else:
91
- err_path = tempfile.NamedTemporaryFile(delete=False, suffix=".txt").name
92
- with open(err_path, "w") as f: f.write("Unsupported format. Please upload .csv or .xlsx")
93
- return None, err_path
94
 
95
- # MODIFIED: Look for 'smile' OR 'smiles' (handles your SMILES.xlsx)
96
  s_col = next((c for c in df.columns if 'smile' in c.lower()), None)
97
-
98
- if not s_col:
99
- err_path = tempfile.NamedTemporaryFile(delete=False, suffix=".txt").name
100
- with open(err_path, "w") as f:
101
- f.write(f"Error: Could not find SMILES column. Found: {df.columns.tolist()}")
102
- return None, err_path
103
 
104
  results = []
105
  for s in df[s_col]:
106
  feats = get_mff_features(str(s))
107
  if feats is not None:
108
- if app_choice == "Structural (Strength)":
109
- p = np.expm1(strength_model.predict(feats.reshape(1, -1)))[0]
110
- elif app_choice == "Battery (Tg)":
111
- p = tg_model.predict(feats.reshape(1, -1))[0]
112
- else:
113
- p = degrad_model.predict(feats.reshape(1, -1))[0]
114
  results.append(p)
115
- else:
116
- results.append(np.nan)
117
 
118
  df[f'Predicted_{app_choice}'] = results
119
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
120
  df.to_csv(output_path, index=False)
121
  return df.head(10), output_path
122
-
123
- except Exception as e:
124
- err_path = tempfile.NamedTemporaryFile(delete=False, suffix=".txt").name
125
- with open(err_path, "w") as f: f.write(f"Error processing file: {str(e)}")
126
- return None, err_path
127
 
128
  # --- 5. AUTONOMOUS DESIGN LOGIC ---
129
- from rdkit import DataStructs
 
 
 
 
 
 
 
 
 
130
 
131
  def generate_and_screen(num_to_gen):
132
  candidates = []
133
- generated_fps = [] # Track fingerprints for diversity check
134
-
135
- # We will generate slightly more than requested to account for diversity filtering
136
- max_attempts = num_to_gen * 5
137
  attempts = 0
138
 
 
 
 
139
  while len(candidates) < num_to_gen and attempts < max_attempts:
140
  attempts += 1
141
- # 1. Generation (Same GRU logic)
142
  input_seq = torch.tensor([[char_to_int['<SOS>']]])
143
  h, generated = None, ""
144
  for _ in range(100):
@@ -150,124 +137,121 @@ def generate_and_screen(num_to_gen):
150
 
151
  mol = Chem.MolFromSmiles(generated.replace('*', 'C'))
152
  if mol:
153
- # --- NEW: Diversity Check ---
154
  fp = rdFingerprintGenerator.GetMorganGenerator(radius=2).GetFingerprint(mol)
155
- is_diverse = True
156
- for prev_fp in generated_fps:
157
- sim = DataStructs.TanimotoSimilarity(fp, prev_fp)
158
- if sim > 0.7: # Diversity threshold: skip if >70% similar to existing
159
- is_diverse = False
160
- break
161
- if not is_diverse: continue
162
 
163
- # 2. Predictions
164
  feats = get_mff_features(generated)
165
- s_raw = strength_model.predict(feats.reshape(1, -1))[0]
166
- s = np.expm1(s_raw)
167
  d = degrad_model.predict(feats.reshape(1, -1))[0]
168
  t = tg_model.predict(feats.reshape(1, -1))[0]
169
  sa = sascorer.calculateScore(mol) if sascorer else 5.0
170
 
171
- # --- NEW: Uncertainty Estimate (Standard Error Simulation) ---
172
- # Using a 5% heuristic error common in polymer XGBoost models
173
- s_err = s * 0.05
174
- t_err = 10.5 # +/- 10 degrees is standard for Tg models
175
-
176
- # --- NEW: Confidence Calculation ---
177
- # How many benchmarks does it pass even when considering the error?
178
- passes = 0
179
- if (s - s_err) > 0.075: passes += 1
180
- if (t - t_err) > 120.0: passes += 1
181
- if d > 0.303: passes += 1
182
- if sa < 5.0: passes += 1
183
 
 
 
184
  conf_pct = (passes / 4) * 100
185
-
186
  status = "Excellent" if passes >= 3 else "Review" if passes == 2 else "Bad"
187
 
188
  generated_fps.append(fp)
189
  candidates.append({
190
  "p-SMILES": generated,
191
- "Strength (GPa)": f"{s:.3f} ± {s_err:.3f}",
192
- "Tg (°C)": f"{t:.1f} ± {t_err:.1f}",
193
- "Degradability": round(d, 3),
194
- "SA Score": round(sa, 2),
195
  "Confidence": f"{conf_pct:.0f}%",
196
- "Classification": status
 
197
  })
198
 
199
  df = pd.DataFrame(candidates)
200
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
201
  df.to_csv(output_path, index=False)
202
- return df, output_path
 
 
 
 
 
 
 
203
 
204
  def calculate_universal_rank(df):
205
  if df is None or df.empty: return None, None
206
- def norm(col): return (col - col.min()) / (col.max() - col.min() + 1e-6)
207
- s_norm, t_norm, d_norm = norm(df['Strength (GPa)']), norm(df['Tg (°C)']), norm(df['Degradability'])
208
- sa_norm = 1 - norm(df['SA Score'])
209
- df['Universal Rank Score'] = ((s_norm + t_norm + d_norm + sa_norm) / 4).round(4)
 
 
 
 
 
 
 
210
  df = df.sort_values(by="Universal Rank Score", ascending=False)
 
211
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
212
  df.to_csv(output_path, index=False)
213
  return df, output_path
214
 
215
  # --- 6. GRADIO INTERFACE ---
216
  with gr.Blocks(title="PolymerDesignAI") as demo:
217
- gr.Markdown("<center><h1>🧪 PolymerDesignAI: Multi-Application Pipeline</h1></center>")
218
 
219
  with gr.Tab("Predict"):
220
  gr.Markdown("### Forward Property Prediction")
221
  with gr.Row():
222
  with gr.Column():
223
- pred_mode = gr.Radio(["Structural (Strength)", "Battery (Tg)", "Biomedical (Degradability)"], value="Structural (Strength)", label="Select Property")
224
- smiles_in = gr.Textbox(label="Enter p-SMILES manually", placeholder="*CC*")
225
- gr.Examples(examples=[["*CCCCCCNC(=O)NCCCCCCNC(=O)S*"], ["*Oc1ccc(C(C)(C)c2ccc(OC(=O)NCCCCCCNC(*)=O)cc2)cc1"], ["*[Se]c1ccc(C(=O)Nc2ccc(NC(=O)CCCCCCCC(=O)Nc3ccc(*)cc3)cc2)cc1"]], inputs=smiles_in, label="Quick Select Options")
 
226
  pred_btn = gr.Button("Predict Single", variant="primary")
227
  res_label = gr.Label(label="Prediction Result")
228
  with gr.Column():
229
  gr.Markdown("#### Batch Processing")
230
- file_in = gr.File(label="Upload CSV with 'SMILES' column")
231
  batch_btn = gr.Button("Run Batch Prediction")
232
- batch_df = gr.DataFrame(label="Preview (Top 10)")
233
- file_out = gr.File(label="Download Full Results")
234
- pred_btn.click(predict_ui, [pred_mode, smiles_in], res_label)
235
- batch_btn.click(batch_predict, [pred_mode, file_in], [batch_df, file_out])
236
 
237
  with gr.Tab("Autonomous Design"):
238
  gr.Markdown("### Integrated Generation + Screening")
239
  with gr.Row():
240
  with gr.Column():
241
- gr.Markdown("""**Application Targets:**
242
- * **Structural (Aerospace)**: Optimizing for High Yield Strength.
243
- * **Battery Electrolytes**: Optimizing for High Glass Transition Temperature (Tg).
244
- * **Biomedical**: Optimizing for High Degradability score.
245
 
246
  **🧪 Universal Benchmarks:**
247
- * Strength > 0.075 GPa
248
- * Tg > 120°C
249
- * Degradability > 0.303
250
- * SA < 5.0""")
251
  with gr.Column():
252
- gr.Markdown("""**📊 Classification Logic:**
253
- * **✅ Excellent**: Passes **3 or 4** benchmarks simultaneously.
254
- * **⚠️ Review**: Passes exactly **2** benchmarks.
255
- * **❌ Bad**: Passes **0 or 1** benchmark. (Fails 3+ targets)""")
256
 
257
  with gr.Row():
258
  num = gr.Slider(1, 50, 10, step=1, label="Quantity to Generate")
259
  run = gr.Button("Run Design Pipeline", variant="primary")
260
 
261
- results_df = gr.DataFrame(label="AI-Generated Candidates")
 
262
 
263
  gr.Markdown("### Multi-Objective Ranking")
264
- gr.Markdown(r"""**Universal Rank Score Formula:** $$\text{Rank Score} = \frac{(\text{Norm. Strength}) + (\text{Norm. Tg}) + (\text{Norm. Deg.}) + (1 - \text{Norm. SA})}{4}$$
265
- *This score identifies the best 'Total Package' polymers across all three goals.* """)
266
 
267
  rank_btn = gr.Button("Calculate Universal Rank Score", variant="secondary")
268
  csv_out = gr.File(label="Download Full Results")
269
 
270
- run.click(generate_and_screen, num, [results_df, csv_out])
271
  rank_btn.click(calculate_universal_rank, results_df, [results_df, csv_out])
272
 
273
  demo.launch(auth=(os.environ.get("app_user"), os.environ.get("app_pass")), ssr_mode=False)
 
6
  import torch.nn as nn
7
  import torch.nn.functional as F
8
  import xgboost as xgb
9
+ import plotly.express as px
10
+ from rdkit import Chem, DataStructs
11
  from rdkit.Chem import Descriptors, rdFingerprintGenerator
12
  from rdkit.Chem import RDConfig
13
  import sys
 
59
  desc = [Descriptors.MolWt(mol), Descriptors.MolLogP(mol), Descriptors.TPSA(mol), Descriptors.NumValenceElectrons(mol), 0.0]
60
  return np.concatenate([mff, np.array(desc)])
61
 
62
+ # --- 4. PREDICT & BATCH LOGIC ---
63
  def predict_ui(app_choice, smiles):
64
  if not smiles: return "Please enter a SMILES string."
65
  feats = get_mff_features(smiles)
66
  if feats is None: return "Invalid p-SMILES"
 
67
  if app_choice == "Structural (Strength)":
68
  res = np.expm1(strength_model.predict(feats.reshape(1, -1)))[0]
69
  return f"Predicted Yield Strength: {res:.4f} GPa"
 
77
  def batch_predict(app_choice, file_obj):
78
  try:
79
  filename = file_obj.name
 
80
  if filename.endswith('.csv'):
81
+ try: df = pd.read_csv(filename, encoding='utf-8', on_bad_lines='skip')
82
+ except UnicodeDecodeError: df = pd.read_csv(filename, encoding='latin1', on_bad_lines='skip')
 
 
83
  elif filename.endswith(('.xls', '.xlsx')):
 
84
  df = pd.read_excel(filename, engine='openpyxl')
85
+ else: return None, "Unsupported format"
 
 
 
86
 
 
87
  s_col = next((c for c in df.columns if 'smile' in c.lower()), None)
88
+ if not s_col: return None, "No SMILES column found"
 
 
 
 
 
89
 
90
  results = []
91
  for s in df[s_col]:
92
  feats = get_mff_features(str(s))
93
  if feats is not None:
94
+ if app_choice == "Structural (Strength)": p = np.expm1(strength_model.predict(feats.reshape(1, -1)))[0]
95
+ elif app_choice == "Battery (Tg)": p = tg_model.predict(feats.reshape(1, -1))[0]
96
+ else: p = degrad_model.predict(feats.reshape(1, -1))[0]
 
 
 
97
  results.append(p)
98
+ else: results.append(np.nan)
 
99
 
100
  df[f'Predicted_{app_choice}'] = results
101
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
102
  df.to_csv(output_path, index=False)
103
  return df.head(10), output_path
104
+ except Exception as e: return None, str(e)
 
 
 
 
105
 
106
  # --- 5. AUTONOMOUS DESIGN LOGIC ---
107
+ def get_status_icon(val, target, is_higher_better=True):
108
+ margin = target * 0.1 # 10% Margin for Yellow status
109
+ if is_higher_better:
110
+ if val >= target: return "🟢"
111
+ if val >= (target - margin): return "🟡"
112
+ return "🔴"
113
+ else: # Lower is better (SA Score)
114
+ if val <= target: return "🟢"
115
+ if val <= (target + margin): return "🟡"
116
+ return "🔴"
117
 
118
  def generate_and_screen(num_to_gen):
119
  candidates = []
120
+ generated_fps = []
121
+ max_attempts = num_to_gen * 15
 
 
122
  attempts = 0
123
 
124
+ # Benchmarks
125
+ T_STRENGTH, T_TG, T_DEG, T_SA = 0.075, 120.0, 0.30, 5.0
126
+
127
  while len(candidates) < num_to_gen and attempts < max_attempts:
128
  attempts += 1
 
129
  input_seq = torch.tensor([[char_to_int['<SOS>']]])
130
  h, generated = None, ""
131
  for _ in range(100):
 
137
 
138
  mol = Chem.MolFromSmiles(generated.replace('*', 'C'))
139
  if mol:
140
+ # Diversity Check (Tanimoto Filtering)
141
  fp = rdFingerprintGenerator.GetMorganGenerator(radius=2).GetFingerprint(mol)
142
+ if any(DataStructs.TanimotoSimilarity(fp, prev_fp) > 0.7 for prev_fp in generated_fps): continue
 
 
 
 
 
 
143
 
 
144
  feats = get_mff_features(generated)
145
+ s = np.expm1(strength_model.predict(feats.reshape(1, -1))[0])
 
146
  d = degrad_model.predict(feats.reshape(1, -1))[0]
147
  t = tg_model.predict(feats.reshape(1, -1))[0]
148
  sa = sascorer.calculateScore(mol) if sascorer else 5.0
149
 
150
+ # Uncertainty Estimates
151
+ s_err, t_err, d_err = s * 0.05, 10.5, 0.05
 
 
 
 
 
 
 
 
 
 
152
 
153
+ # Confidence Calculation
154
+ passes = sum([(s-s_err) > T_STRENGTH, (t-t_err) > T_TG, (d-d_err) > T_DEG, sa < T_SA])
155
  conf_pct = (passes / 4) * 100
 
156
  status = "Excellent" if passes >= 3 else "Review" if passes == 2 else "Bad"
157
 
158
  generated_fps.append(fp)
159
  candidates.append({
160
  "p-SMILES": generated,
161
+ "Strength (GPa)": f"{get_status_icon(s, T_STRENGTH)} {s:.3f} ± {s_err:.3f}",
162
+ "Tg (°C)": f"{get_status_icon(t, T_TG)} {t:.1f} ± {t_err:.1f}",
163
+ "Degradability": f"{get_status_icon(d, T_DEG)} {d:.2f} ± {d_err:.2f}",
164
+ "SA Score": f"{get_status_icon(sa, T_SA, False)} {sa:.2f}",
165
  "Confidence": f"{conf_pct:.0f}%",
166
+ "Classification": status,
167
+ "_s": s, "_t": t # Raw for plotting
168
  })
169
 
170
  df = pd.DataFrame(candidates)
171
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
172
  df.to_csv(output_path, index=False)
173
+
174
+ # Pareto Front Viz
175
+ fig = px.scatter(df, x="_s", y="_t", color="Classification",
176
+ hover_data=["p-SMILES"], labels={"_s": "Strength (GPa)", "_t": "Tg (°C)"},
177
+ title="Pareto Front Exploration: Strength vs Thermal Stability",
178
+ template="plotly_white")
179
+
180
+ return df.drop(columns=["_s", "_t"]), output_path, fig
181
 
182
  def calculate_universal_rank(df):
183
  if df is None or df.empty: return None, None
184
+ def clean(x): return float(str(x).split(' ')[1])
185
+
186
+ s_norm = (df['Strength (GPa)'].apply(clean) - 0.01) / (0.15 - 0.01)
187
+ t_norm = (df['Tg (°C)'].apply(clean) - 50) / (300 - 50)
188
+ d_norm = df['Degradability'].apply(clean)
189
+ sa_val = df['SA Score'].apply(clean)
190
+
191
+ # Continuous SA Penalty
192
+ sa_penalty = np.exp(-0.4 * (sa_val - 1))
193
+
194
+ df['Universal Rank Score'] = ((s_norm + t_norm + d_norm + sa_penalty) / 4).round(4)
195
  df = df.sort_values(by="Universal Rank Score", ascending=False)
196
+
197
  output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
198
  df.to_csv(output_path, index=False)
199
  return df, output_path
200
 
201
  # --- 6. GRADIO INTERFACE ---
202
  with gr.Blocks(title="PolymerDesignAI") as demo:
203
+ gr.Markdown("<center><h1>🧪 PolymerDesignAI: Advanced Scientific Discovery</h1></center>")
204
 
205
  with gr.Tab("Predict"):
206
  gr.Markdown("### Forward Property Prediction")
207
  with gr.Row():
208
  with gr.Column():
209
+ pred_mode = gr.Radio(["Structural (Strength)", "Battery (Tg)", "Biomedical (Degradability)"],
210
+ value="Structural (Strength)", label="Select Property")
211
+ sm_in = gr.Textbox(label="Enter p-SMILES", placeholder="*CC*")
212
+ gr.Examples(examples=[["*CCCCCCNC(=O)NCCCCCCNC(=O)S*"], ["*Oc1ccc(C(C)(C)c2ccc(OC(=O)NCCCCCCNC(*)=O)cc2)cc1"]], inputs=sm_in)
213
  pred_btn = gr.Button("Predict Single", variant="primary")
214
  res_label = gr.Label(label="Prediction Result")
215
  with gr.Column():
216
  gr.Markdown("#### Batch Processing")
217
+ file_in = gr.File(label="Upload CSV/XLSX")
218
  batch_btn = gr.Button("Run Batch Prediction")
219
+ batch_df = gr.DataFrame(); batch_file = gr.File()
220
+ pred_btn.click(predict_ui, [pred_mode, sm_in], res_label)
221
+ batch_btn.click(batch_predict, [pred_mode, file_in], [batch_df, batch_file])
 
222
 
223
  with gr.Tab("Autonomous Design"):
224
  gr.Markdown("### Integrated Generation + Screening")
225
  with gr.Row():
226
  with gr.Column():
227
+ gr.Markdown("""**Application Mapping:**
228
+ * **Aerospace Mode**: Optimized Yield Strength.
229
+ * **Battery Mode**: Optimized Glass Transition (Tg).
230
+ * **Biomedical Mode**: Optimized Degradability.
231
 
232
  **🧪 Universal Benchmarks:**
233
+ * Strength > 0.075 GPa | Tg > 120°C
234
+ * Degradability > 0.30 | SA < 5.0""")
 
 
235
  with gr.Column():
236
+ gr.Markdown("""**📊 Status Key:**
237
+ * 🟢 Pass | 🟡 Near Threshold (10%) | 🔴 Fail
238
+ * **✅ Excellent**: Passes 3+ benchmarks with Uncertainty margin.""")
 
239
 
240
  with gr.Row():
241
  num = gr.Slider(1, 50, 10, step=1, label="Quantity to Generate")
242
  run = gr.Button("Run Design Pipeline", variant="primary")
243
 
244
+ pareto_plot = gr.Plot(label="Pareto Frontier")
245
+ results_df = gr.DataFrame(label="AI-Generated Candidates (Ranked by Score)")
246
 
247
  gr.Markdown("### Multi-Objective Ranking")
248
+ gr.Markdown(r"""**Universal Rank Score Formula:** Includes **Continuous SA Penalty** for feasibility.
249
+ *This score identifies the best 'Total Package' polymers.* """)
250
 
251
  rank_btn = gr.Button("Calculate Universal Rank Score", variant="secondary")
252
  csv_out = gr.File(label="Download Full Results")
253
 
254
+ run.click(generate_and_screen, num, [results_df, csv_out, pareto_plot])
255
  rank_btn.click(calculate_universal_rank, results_df, [results_df, csv_out])
256
 
257
  demo.launch(auth=(os.environ.get("app_user"), os.environ.get("app_pass")), ssr_mode=False)