Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -238,7 +238,7 @@ def generate_and_screen(num_to_gen, app_mode, toggle_view, motifs=[], limit_aro=
|
|
| 238 |
"SA Score": f"{get_status_icon(sa, T_SA, False)} {sa:.2f}",
|
| 239 |
"Confidence": f"{conf_pct:.0f}%",
|
| 240 |
"Classification": status,
|
| 241 |
-
"_s": s, "_t": t, "_status": "Excellent" if "Excellent" in status else "Review" if "Review" in status else "Bad"
|
| 242 |
})
|
| 243 |
|
| 244 |
if len(candidates) == 0:
|
|
@@ -270,7 +270,7 @@ def generate_and_screen(num_to_gen, app_mode, toggle_view, motifs=[], limit_aro=
|
|
| 270 |
|
| 271 |
output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
|
| 272 |
df.to_csv(output_path, index=False)
|
| 273 |
-
return df
|
| 274 |
|
| 275 |
def calculate_weighted_rank(df, app_mode, w_s, w_t, w_d, w_sa): # Added 4 weight arguments
|
| 276 |
if df is None or df.empty: return None, None
|
|
@@ -465,7 +465,7 @@ def check_constraints(mol, motifs, limit_aromatic, max_mw):
|
|
| 465 |
"Cyclic-Aliphatic": "C1CCCCC1"
|
| 466 |
}
|
| 467 |
|
| 468 |
-
for m in
|
| 469 |
if m in pattern_map:
|
| 470 |
p = Chem.MolFromSmarts(pattern_map[m])
|
| 471 |
if not mol.HasSubstructMatch(p):
|
|
@@ -480,6 +480,49 @@ def check_constraints(mol, motifs, limit_aromatic, max_mw):
|
|
| 480 |
|
| 481 |
return True
|
| 482 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 483 |
def generate_exports(df):
|
| 484 |
if df is None or df.empty:
|
| 485 |
return [None] * 4
|
|
@@ -530,6 +573,17 @@ def generate_exports(df):
|
|
| 530 |
|
| 531 |
pdf.output(pdf_path)
|
| 532 |
return csv_path, json_path, smiles_path, pdf_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 533 |
|
| 534 |
# --- 6. GRADIO INTERFACE ---
|
| 535 |
with gr.Blocks(title="PolymerDesignAI") as demo:
|
|
@@ -613,7 +667,18 @@ with gr.Blocks(title="PolymerDesignAI") as demo:
|
|
| 613 |
label="Sort Candidates By",
|
| 614 |
interactive=True
|
| 615 |
)
|
| 616 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 617 |
results_df = gr.DataFrame(label="Candidate Table (Click headers to sort)")
|
| 618 |
full_results_state = gr.State()
|
| 619 |
|
|
@@ -735,8 +800,20 @@ with gr.Blocks(title="PolymerDesignAI") as demo:
|
|
| 735 |
def clean_and_explain(choice, prop):
|
| 736 |
smiles = choice.split(" ")[1] if choice and " " in choice else choice
|
| 737 |
return explain_polymer(smiles, prop)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 738 |
|
| 739 |
-
|
|
|
|
|
|
|
|
|
|
| 740 |
|
| 741 |
filter_btn_all.click(lambda x: filter_table(x, "All"), full_results_state, results_df)
|
| 742 |
filter_btn_exc.click(lambda x: filter_table(x, "Excellent"), full_results_state, results_df)
|
|
@@ -764,10 +841,20 @@ with gr.Blocks(title="PolymerDesignAI") as demo:
|
|
| 764 |
sort_dropdown.change(sort_table, [results_df, sort_dropdown], results_df)
|
| 765 |
rank_btn.click(calculate_weighted_rank, [results_df, app_mode, w_strength, w_tg, w_degrad, w_sa], [results_df, download_csv])
|
| 766 |
refine_btn.click(
|
| 767 |
-
|
| 768 |
-
[selection_dropdown, num, app_mode, toggle_view, must_have, exclude_aromatic, max_mol_wt],
|
| 769 |
-
[results_df, pareto_plot, selection_dropdown, full_results_state, explain_img, explain_bar]
|
| 770 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
# Automatically update all download links whenever the results table changes
|
| 772 |
results_df.change(
|
| 773 |
fn=update_exports,
|
|
|
|
| 238 |
"SA Score": f"{get_status_icon(sa, T_SA, False)} {sa:.2f}",
|
| 239 |
"Confidence": f"{conf_pct:.0f}%",
|
| 240 |
"Classification": status,
|
| 241 |
+
"_s": s, "_t": t, "Degradability_raw": d, "_status": "Excellent" if "Excellent" in status else "Review" if "Review" in status else "Bad"
|
| 242 |
})
|
| 243 |
|
| 244 |
if len(candidates) == 0:
|
|
|
|
| 270 |
|
| 271 |
output_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv").name
|
| 272 |
df.to_csv(output_path, index=False)
|
| 273 |
+
return df, output_path, fig
|
| 274 |
|
| 275 |
def calculate_weighted_rank(df, app_mode, w_s, w_t, w_d, w_sa): # Added 4 weight arguments
|
| 276 |
if df is None or df.empty: return None, None
|
|
|
|
| 465 |
"Cyclic-Aliphatic": "C1CCCCC1"
|
| 466 |
}
|
| 467 |
|
| 468 |
+
for m in active_motifs:
|
| 469 |
if m in pattern_map:
|
| 470 |
p = Chem.MolFromSmarts(pattern_map[m])
|
| 471 |
if not mol.HasSubstructMatch(p):
|
|
|
|
| 480 |
|
| 481 |
return True
|
| 482 |
|
| 483 |
+
def run_sensitivity_analysis(raw_df, t_s, t_t, t_d):
|
| 484 |
+
if raw_df is None or raw_df.empty:
|
| 485 |
+
return None, "No data to analyze", []
|
| 486 |
+
|
| 487 |
+
processed_df = raw_df.copy()
|
| 488 |
+
|
| 489 |
+
# Re-classification logic based on NEW thresholds
|
| 490 |
+
def reclassify(row):
|
| 491 |
+
s, t, d = row["_s"], row["_t"], row["Degradability_raw"]
|
| 492 |
+
failed = []
|
| 493 |
+
if s < t_s: failed.append("Strength")
|
| 494 |
+
if t < t_t: failed.append("Tg")
|
| 495 |
+
if d < t_d: failed.append("Degradability")
|
| 496 |
+
|
| 497 |
+
if not failed: return "Excellent β
"
|
| 498 |
+
if len(failed) <= 2: return f"Review (Failed {', '.join(failed)}) β οΈ"
|
| 499 |
+
return "Bad β"
|
| 500 |
+
|
| 501 |
+
processed_df["Classification"] = processed_df.apply(reclassify, axis=1)
|
| 502 |
+
|
| 503 |
+
# Calculate Market Readiness
|
| 504 |
+
total = len(processed_df)
|
| 505 |
+
passed = len(processed_df[processed_df["Classification"] == "Excellent β
"])
|
| 506 |
+
pass_pct = (passed / total) * 100 if total > 0 else 0
|
| 507 |
+
|
| 508 |
+
# Update icons in the visible columns for the user
|
| 509 |
+
def update_icons(row):
|
| 510 |
+
row["Strength (GPa)"] = f"{'π’' if row['_s'] >= t_s else 'π΄'} {row['_s']:.3f}"
|
| 511 |
+
row["Tg (Β°C)"] = f"{'π’' if row['_t'] >= t_t else 'π΄'} {row['_t']:.1f}"
|
| 512 |
+
row["Degradability"] = f"{'π’' if row['Degradability_raw'] >= t_d else 'π΄'} {row['Degradability_raw']:.2f}"
|
| 513 |
+
return row
|
| 514 |
+
|
| 515 |
+
display_df = processed_df.apply(update_icons, axis=1)
|
| 516 |
+
|
| 517 |
+
status_msg = f"**{pass_pct:.1f}%** of candidates ({passed}/{total}) meet these industrial benchmarks."
|
| 518 |
+
# Color-coded badge for Gradio AnnotatedText
|
| 519 |
+
badge_color = "green" if pass_pct > 25 else "orange" if pass_pct > 5 else "red"
|
| 520 |
+
annotated_data = [(f"{pass_pct:.0f}% Pass Rate", badge_color)]
|
| 521 |
+
|
| 522 |
+
# Drop hidden columns before returning to the UI
|
| 523 |
+
final_cols = [c for c in display_df.columns if not c.startswith('_') and c != "Degradability_raw"]
|
| 524 |
+
return display_df[final_cols], status_msg, annotated_data
|
| 525 |
+
|
| 526 |
def generate_exports(df):
|
| 527 |
if df is None or df.empty:
|
| 528 |
return [None] * 4
|
|
|
|
| 573 |
|
| 574 |
pdf.output(pdf_path)
|
| 575 |
return csv_path, json_path, smiles_path, pdf_path
|
| 576 |
+
|
| 577 |
+
def on_refine_wrapper(seed_with_icon, num, mode, view, motifs, limit_aro, max_mw):
|
| 578 |
+
# 1. Call existing refine logic (returns 6 items)
|
| 579 |
+
df, fig, dropdown, state, img, bar = on_refine_start(seed_with_icon, num, mode, view, motifs, limit_aro, max_mw)
|
| 580 |
+
|
| 581 |
+
# 2. Prepare the 2 extra items
|
| 582 |
+
init_msg = "Move sliders above to re-analyze refined batch."
|
| 583 |
+
init_anno = [("0% Pass Rate", "Awaiting Analysis")]
|
| 584 |
+
|
| 585 |
+
# 3. Return all 8 items
|
| 586 |
+
return df, fig, dropdown, state, img, bar, init_msg, init_anno
|
| 587 |
|
| 588 |
# --- 6. GRADIO INTERFACE ---
|
| 589 |
with gr.Blocks(title="PolymerDesignAI") as demo:
|
|
|
|
| 667 |
label="Sort Candidates By",
|
| 668 |
interactive=True
|
| 669 |
)
|
| 670 |
+
# --- NEW: BENCHMARK SENSITIVITY ANALYSIS ---
|
| 671 |
+
with gr.Accordion("π Benchmark Sensitivity Analysis (Industrial What-if)", open=False):
|
| 672 |
+
gr.Markdown("Adjust thresholds to dynamically re-classify candidates and see market readiness.")
|
| 673 |
+
with gr.Row():
|
| 674 |
+
threshold_s = gr.Slider(0, 0.2, value=0.08, step=0.005, label="Min Strength (GPa)")
|
| 675 |
+
threshold_t = gr.Slider(0, 300, value=150, step=5, label="Min Tg (Β°C)")
|
| 676 |
+
threshold_d = gr.Slider(0, 1.0, value=0.10, step=0.05, label="Min Degradability")
|
| 677 |
+
|
| 678 |
+
with gr.Row():
|
| 679 |
+
pass_metric = gr.AnnotatedText(label="Market Readiness")
|
| 680 |
+
stats_box = gr.Markdown("### π Impact Summary")
|
| 681 |
+
# --------------------------------------------
|
| 682 |
results_df = gr.DataFrame(label="Candidate Table (Click headers to sort)")
|
| 683 |
full_results_state = gr.State()
|
| 684 |
|
|
|
|
| 800 |
def clean_and_explain(choice, prop):
|
| 801 |
smiles = choice.split(" ")[1] if choice and " " in choice else choice
|
| 802 |
return explain_polymer(smiles, prop)
|
| 803 |
+
|
| 804 |
+
def on_gen_complete_wrapper(num, mode, view, motifs, limit_aro, max_mw, progress=gr.Progress()):
|
| 805 |
+
# 1. This function MUST return 6 items based on your code:
|
| 806 |
+
# (df, fig, dropdown, state_df, img, bar)
|
| 807 |
+
df, fig, dropdown, state, img, bar = on_gen_complete(num, mode, view, motifs, limit_aro, max_mw, progress)
|
| 808 |
+
|
| 809 |
+
# 2. Add the 2 sensitivity items
|
| 810 |
+
init_msg = "### π Impact Summary\nMove sliders above to see analysis."
|
| 811 |
+
init_anno = [("0% Pass Rate", "Awaiting Analysis")]
|
| 812 |
|
| 813 |
+
# 3. Return 8 items
|
| 814 |
+
return df, fig, dropdown, state, img, bar, init_msg, init_anno
|
| 815 |
+
|
| 816 |
+
run.click(fn=on_gen_complete_wrapper, inputs=[num, app_mode, toggle_view, must_have, exclude_aromatic, max_mol_wt], outputs=[results_df, pareto_plot, selection_dropdown, full_results_state, explain_img, explain_bar, stats_box, pass_metric])
|
| 817 |
|
| 818 |
filter_btn_all.click(lambda x: filter_table(x, "All"), full_results_state, results_df)
|
| 819 |
filter_btn_exc.click(lambda x: filter_table(x, "Excellent"), full_results_state, results_df)
|
|
|
|
| 841 |
sort_dropdown.change(sort_table, [results_df, sort_dropdown], results_df)
|
| 842 |
rank_btn.click(calculate_weighted_rank, [results_df, app_mode, w_strength, w_tg, w_degrad, w_sa], [results_df, download_csv])
|
| 843 |
refine_btn.click(
|
| 844 |
+
fn=on_refine_wrapper, # Points to the new 8-output wrapper
|
| 845 |
+
inputs=[selection_dropdown, num, app_mode, toggle_view, must_have, exclude_aromatic, max_mol_wt],
|
| 846 |
+
outputs=[results_df, pareto_plot, selection_dropdown, full_results_state, explain_img, explain_bar, stats_box, pass_metric]
|
| 847 |
)
|
| 848 |
+
|
| 849 |
+
# Sensitivity Triggers
|
| 850 |
+
slider_inputs = [full_results_state, threshold_s, threshold_t, threshold_d]
|
| 851 |
+
slider_outputs = [results_df, stats_box, pass_metric]
|
| 852 |
+
|
| 853 |
+
threshold_s.change(fn=run_sensitivity_analysis, inputs=slider_inputs, outputs=slider_outputs)
|
| 854 |
+
threshold_t.change(fn=run_sensitivity_analysis, inputs=slider_inputs, outputs=slider_outputs)
|
| 855 |
+
threshold_d.change(fn=run_sensitivity_analysis, inputs=slider_inputs, outputs=slider_outputs)
|
| 856 |
+
|
| 857 |
+
|
| 858 |
# Automatically update all download links whenever the results table changes
|
| 859 |
results_df.change(
|
| 860 |
fn=update_exports,
|