Skip to content

Commit 4b125ab

Browse files
committed
Dashboard cleanup: correct retrieval numbers, wire test-test/test-train hexbins, drop unused panels
1 parent 7be9212 commit 4b125ab

5 files changed

Lines changed: 39 additions & 251 deletions

File tree

-314 KB
Loading
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
split model n hit@1 hit@5 hit@20
2+
test msg_exact_mces_1020_no_meta_own_val_weights_bs2048_v2 17556 0.04665071770334928 0.12593984962406016 0.265436318067897

tools/analyze_calibration.py

Lines changed: 5 additions & 201 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,6 @@
1616

1717
import matplotlib.pyplot as plt
1818
import numpy as np
19-
from rdkit import Chem
20-
from rdkit.Chem import Descriptors
21-
from tqdm.auto import tqdm
22-
23-
24-
def _exact_mass(smi: str) -> float:
25-
mol = Chem.MolFromSmiles(smi)
26-
return Descriptors.ExactMolWt(mol) if mol else float("nan")
27-
28-
29-
def _heavy_atoms(smi: str) -> int:
30-
mol = Chem.MolFromSmiles(smi)
31-
return mol.GetNumHeavyAtoms() if mol else 0
3219

3320

3421
def main():
@@ -39,7 +26,6 @@ def main():
3926

4027
out = Path(args.output)
4128
out.parent.mkdir(parents=True, exist_ok=True)
42-
fig2_path = out.with_name(out.stem + "_pop.png")
4329

4430
print("Loading CSV ...")
4531
rows = []
@@ -53,7 +39,6 @@ def main():
5339
simba_pred = np.array([float(r["simba_pred_mces"]) for r in rows])
5440
simba_gt = np.array([float(r["simba_gt_mces"]) for r in rows])
5541
oracle_pred = np.array([float(r["oracle_pred_mces"]) for r in rows])
56-
oracle_gt = np.array([float(r["oracle_gt_mces"]) for r in rows])
5742
simba_tani = np.array([float(r["simba_tanimoto"]) for r in rows])
5843
covered = np.array([r["covered"] == "True" for r in rows])
5944

@@ -71,22 +56,6 @@ def main():
7156
cosine_oracle = 1.0 - oracle_pred / 40.0
7257
cosine_label = "Cosine sim (embedding, 1−pred/40) ← re-run diagnose_retrieval to get spectral"
7358

74-
# ── RDKit properties (cached) ─────────────────────────────────────────────
75-
print("Computing molecular properties ...")
76-
mass_cache: dict[str, float] = {}
77-
atom_cache: dict[str, int] = {}
78-
unique_smi = {r["test_smi"] for r in rows} | {r["simba_smi"] for r in rows}
79-
for s in tqdm(unique_smi, desc="RDKit"):
80-
mass_cache[s] = _exact_mass(s)
81-
atom_cache[s] = _heavy_atoms(s)
82-
83-
mass_test = np.array([mass_cache[r["test_smi"]] for r in rows])
84-
mass_simba = np.array([mass_cache[r["simba_smi"]] for r in rows])
85-
atoms_test = np.array([atom_cache[r["test_smi"]] for r in rows])
86-
atoms_simba = np.array([atom_cache[r["simba_smi"]] for r in rows])
87-
mass_diff = np.abs(mass_test - mass_simba)
88-
atoms_diff = np.abs(atoms_test - atoms_simba).astype(float)
89-
9059
om = covered # covered mask
9160

9261
# ── Print summary stats ───────────────────────────────────────────────────
@@ -97,30 +66,13 @@ def main():
9766
print(
9867
f" {'spectral' if has_spectral else 'embedding'} cosine oracle pick: mean={cosine_oracle[om].mean():.3f} median={np.median(cosine_oracle[om]):.3f}"
9968
)
100-
print(
101-
f" mass_diff pair: mean={mass_diff[om].mean():.1f} Da median={np.median(mass_diff[om]):.1f} Da"
102-
)
103-
print(
104-
f" atoms_diff pair: mean={atoms_diff[om].mean():.1f} median={np.median(atoms_diff[om]):.1f}"
105-
)
10669
print(f" Tanimoto SIMBA pair: mean={simba_tani[om].mean():.3f}")
10770

108-
HIGH = om & (simba_err > 25)
109-
LOW = om & (simba_err < 5)
110-
print(
111-
f"\n High error (>25): n={HIGH.sum():,} mass_diff mean={mass_diff[HIGH].mean():.1f} Da "
112-
f"atoms_diff mean={atoms_diff[HIGH].mean():.1f} tani mean={simba_tani[HIGH].mean():.3f}"
113-
)
114-
print(
115-
f" Low error (<5): n={LOW.sum():,} mass_diff mean={mass_diff[LOW].mean():.1f} Da "
116-
f"atoms_diff mean={atoms_diff[LOW].mean():.1f} tani mean={simba_tani[LOW].mean():.3f}"
117-
)
118-
119-
# ── Figure 1: Error anatomy (2×3) ─────────────────────────────────────────
120-
fig, axes = plt.subplots(2, 3, figsize=(18, 11))
71+
# ── Figure 1: panels 1 + 6 ────────────────────────────────────────────────
72+
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
12173

12274
# 1. Cosine sim distribution: SIMBA pick vs oracle pick
123-
ax = axes[0, 0]
75+
ax = axes[0]
12476
bins1 = np.linspace(0, 1, 51)
12577
ax.hist(
12678
cosine_oracle[om],
@@ -144,50 +96,8 @@ def main():
14496
ax.legend(fontsize=9)
14597
ax.grid(True, alpha=0.2)
14698

147-
# 2. simba_err vs mass_diff (hexbin)
148-
ax = axes[0, 1]
149-
hb = ax.hexbin(mass_diff[om], simba_err[om], gridsize=30, cmap="Reds", mincnt=1)
150-
plt.colorbar(hb, ax=ax, label="count")
151-
ax.axhline(0, color="k", lw=1, ls="--")
152-
ax.set_xlabel("|mass_test − mass_SIMBA_pick| (Da)")
153-
ax.set_ylabel("simba_err (GT MCES − pred MCES)")
154-
ax.set_title("2 · Calibration error vs mass difference", fontweight="bold")
155-
ax.grid(True, alpha=0.2)
156-
157-
# 3. simba_err vs atoms_diff
158-
ax = axes[0, 2]
159-
hb = ax.hexbin(atoms_diff[om], simba_err[om], gridsize=30, cmap="Purples", mincnt=1)
160-
plt.colorbar(hb, ax=ax, label="count")
161-
ax.axhline(0, color="k", lw=1, ls="--")
162-
ax.set_xlabel("|atoms_test − atoms_SIMBA_pick|")
163-
ax.set_ylabel("simba_err")
164-
ax.set_title("3 · Calibration error vs atom count diff", fontweight="bold")
165-
ax.grid(True, alpha=0.2)
166-
167-
# 4. simba_err vs atoms_test
168-
ax = axes[1, 0]
169-
hb = ax.hexbin(
170-
atoms_test[om].astype(float), simba_err[om], gridsize=30, cmap="Blues", mincnt=1
171-
)
172-
plt.colorbar(hb, ax=ax, label="count")
173-
ax.axhline(0, color="k", lw=1, ls="--")
174-
ax.set_xlabel("Heavy atom count of test molecule")
175-
ax.set_ylabel("simba_err")
176-
ax.set_title("4 · Calibration error vs test mol size", fontweight="bold")
177-
ax.grid(True, alpha=0.2)
178-
179-
# 5. simba_err vs Tanimoto(test, SIMBA pick)
180-
ax = axes[1, 1]
181-
hb = ax.hexbin(simba_tani[om], simba_err[om], gridsize=30, cmap="Oranges", mincnt=1)
182-
plt.colorbar(hb, ax=ax, label="count")
183-
ax.axhline(0, color="k", lw=1, ls="--")
184-
ax.set_xlabel("Tanimoto similarity (test, SIMBA pick)")
185-
ax.set_ylabel("simba_err")
186-
ax.set_title("5 · Calibration error vs structural similarity", fontweight="bold")
187-
ax.grid(True, alpha=0.2)
188-
18999
# 6. GT MCES distribution of SIMBA picks with pred mean
190-
ax = axes[1, 2]
100+
ax = axes[1]
191101
bins6 = np.arange(0, 42.5, 2.5)
192102
ax.hist(simba_gt[om], bins=bins6, color="#5B8DB8", edgecolor="none", alpha=0.85)
193103
ax.axvline(
@@ -218,113 +128,7 @@ def main():
218128
)
219129
plt.tight_layout()
220130
plt.savefig(out, dpi=140, bbox_inches="tight")
221-
print(f"\nFigure 1 saved → {out}")
222-
223-
# ── Figure 2: Population analysis (2×2) ──────────────────────────────────
224-
fig2, axes2 = plt.subplots(2, 2, figsize=(14, 11))
225-
226-
# A. mass_diff distribution: high vs low error
227-
ax = axes2[0, 0]
228-
bins_m = np.linspace(0, np.percentile(mass_diff[om], 99), 60)
229-
ax.hist(
230-
mass_diff[LOW],
231-
bins=bins_m,
232-
color="#4E9A7A",
233-
alpha=0.75,
234-
density=True,
235-
edgecolor="none",
236-
label=f"err<5 n={LOW.sum():,} μ={mass_diff[LOW].mean():.1f} Da",
237-
)
238-
ax.hist(
239-
mass_diff[HIGH],
240-
bins=bins_m,
241-
color="#E07B54",
242-
alpha=0.75,
243-
density=True,
244-
edgecolor="none",
245-
label=f"err>25 n={HIGH.sum():,} μ={mass_diff[HIGH].mean():.1f} Da",
246-
)
247-
ax.set_xlabel("|mass_test − mass_SIMBA_pick| (Da)")
248-
ax.set_ylabel("density")
249-
ax.set_title("A · Mass diff: low-error vs high-error pairs", fontweight="bold")
250-
ax.legend(fontsize=9)
251-
ax.grid(True, alpha=0.2)
252-
253-
# B. 2D hexbin (mass_test, mass_simba) colored by mean simba_err
254-
ax = axes2[0, 1]
255-
lim = np.percentile(np.concatenate([mass_test[om], mass_simba[om]]), 99)
256-
hb = ax.hexbin(
257-
mass_test[om],
258-
mass_simba[om],
259-
C=simba_err[om],
260-
gridsize=35,
261-
cmap="RdYlGn_r",
262-
reduce_C_function=np.mean,
263-
mincnt=3,
264-
)
265-
plt.colorbar(hb, ax=ax, label="mean simba_err")
266-
ax.plot([0, lim], [0, lim], "k--", lw=1.2, alpha=0.7, label="test = SIMBA pick")
267-
ax.set_xlim(0, lim)
268-
ax.set_ylim(0, lim)
269-
ax.set_xlabel("Exact mass — test molecule (Da)")
270-
ax.set_ylabel("Exact mass — SIMBA pick (Da)")
271-
ax.set_title("B · Mass pairs colored by mean calibration error", fontweight="bold")
272-
ax.legend(fontsize=8)
273-
ax.grid(True, alpha=0.2)
274-
275-
# C. Tanimoto distribution: high vs low error
276-
ax = axes2[1, 0]
277-
bins_t = np.linspace(0, 1, 41)
278-
ax.hist(
279-
simba_tani[LOW],
280-
bins=bins_t,
281-
color="#4E9A7A",
282-
alpha=0.75,
283-
density=True,
284-
edgecolor="none",
285-
label=f"err<5 μ={simba_tani[LOW].mean():.3f}",
286-
)
287-
ax.hist(
288-
simba_tani[HIGH],
289-
bins=bins_t,
290-
color="#E07B54",
291-
alpha=0.75,
292-
density=True,
293-
edgecolor="none",
294-
label=f"err>25 μ={simba_tani[HIGH].mean():.3f}",
295-
)
296-
ax.set_xlabel("Tanimoto similarity (test, SIMBA pick)")
297-
ax.set_ylabel("density")
298-
ax.set_title("C · Tanimoto: low-error vs high-error pairs", fontweight="bold")
299-
ax.legend(fontsize=9)
300-
ax.grid(True, alpha=0.2)
301-
302-
# D. SIMBA pick GT vs oracle GT colored by simba_err
303-
ax = axes2[1, 1]
304-
hb = ax.hexbin(
305-
oracle_gt[om],
306-
simba_gt[om],
307-
C=simba_err[om],
308-
gridsize=30,
309-
cmap="RdYlGn_r",
310-
reduce_C_function=np.mean,
311-
mincnt=3,
312-
)
313-
plt.colorbar(hb, ax=ax, label="mean simba_err")
314-
ax.plot([0, 40], [0, 40], "k--", lw=1.2, alpha=0.7, label="SIMBA = oracle")
315-
ax.set_xlabel("Oracle GT MCES")
316-
ax.set_ylabel("SIMBA pick GT MCES")
317-
ax.set_title("D · SIMBA GT vs oracle GT (colored by error)", fontweight="bold")
318-
ax.legend(fontsize=8)
319-
ax.grid(True, alpha=0.2)
320-
321-
fig2.suptitle(
322-
"Population Analysis: High-error (err>25) vs Low-error (err<5) SIMBA Retrieval Pairs",
323-
fontsize=12,
324-
)
325-
plt.tight_layout()
326-
plt.savefig(fig2_path, dpi=140, bbox_inches="tight")
327-
print(f"Figure 2 saved → {fig2_path}")
131+
print(f"\nSaved → {out}")
328132

329133

330134
if __name__ == "__main__":

tools/dashboard/app.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -392,18 +392,19 @@ def show_hexbin(path: Path | None, caption: str = ""):
392392
show(_p, _p.stem)
393393

394394
_ret8104 = Path(
395-
"/home/nkubrakov/simba/results/simba_retrieval_bs2048_v2_step44k.tsv"
395+
"/home/nkubrakov/simba/results/simba_retrieval_bs2048_v2_step44k_fixed_all.tsv"
396396
)
397397
if _ret8104.exists():
398-
st.markdown("**Retrieval benchmark · step 44k (all 194k train spectra)**")
398+
st.markdown("**Retrieval benchmark · step 44k · fixed preprocessing**")
399399
_df = pd.read_csv(_ret8104, sep="\t")
400400
_row = _df.iloc[0]
401401
_c1, _c2, _c3 = st.columns(3)
402402
_c1.metric("SIMBA hit@1", f"{_row['hit@1'] * 100:.2f}%")
403403
_c2.metric("SIMBA hit@5", f"{_row['hit@5'] * 100:.2f}%")
404404
_c3.metric("SIMBA hit@20", f"{_row['hit@20'] * 100:.2f}%")
405405
st.caption(
406-
f"n={int(_row['n'])} · cosine-NN → Morgan FP transfer → Tanimoto ranking"
406+
f"n={int(_row['n'])} · cosine-NN → Morgan FP transfer → Tanimoto ranking "
407+
f"(n_layers=5 · top-N intensity · sqrt+L2 norm)"
407408
)
408409

409410
_cos_hex = Path(
@@ -460,27 +461,40 @@ def show_hexbin(path: Path | None, caption: str = ""):
460461
)
461462
show(_diag8104)
462463
if _diag8104_mol.exists():
463-
st.markdown(
464-
"**Molecular property analysis · step 44k** — Tanimoto, SIMBA GT vs oracle GT"
465-
)
466-
show(_diag8104_mol)
464+
st.markdown("**Tanimoto analysis · step 44k** — oracle vs SIMBA pick")
465+
img_mol = Image.open(_diag8104_mol)
466+
w_mol, h_mol = img_mol.size
467+
cropped_mol = img_mol.crop((0, 0, w_mol // 3, h_mol))
468+
buf_mol = io.BytesIO()
469+
cropped_mol.save(buf_mol, format="PNG")
470+
st.image(buf_mol.getvalue(), use_container_width=False, width=500)
467471

468472
_cal = Path(
469473
"/home/nkubrakov/simba/results/calibration_analysis_bs2048_v2_step44k.png"
470474
)
471-
_cal_pop = Path(
472-
"/home/nkubrakov/simba/results/calibration_analysis_bs2048_v2_step44k_pop.png"
473-
)
474475
if _cal.exists():
475476
st.markdown(
476-
"**Calibration error anatomy · step 44k** — cosine sim distributions, error vs mass/atoms/Tanimoto"
477+
"**Calibration error anatomy · step 44k** — spectral cosine distributions + GT MCES of picks"
477478
)
478479
show(_cal)
479-
if _cal_pop.exists():
480+
481+
_tt_hex = Path(
482+
"/home/nkubrakov/simba/results/test_test_mces_hexbin_bs2048_v2_step44k_fixed_all.png"
483+
)
484+
if _tt_hex.exists():
485+
st.markdown(
486+
"**Test-test hexbin · step 44k** — GT-balanced (rho=0.574) vs unbalanced (rho=0.692)"
487+
)
488+
show(_tt_hex)
489+
490+
_ttr_hex = Path(
491+
"/home/nkubrakov/simba/results/test_train_mces_hexbin_bs2048_v2_step44k_fixed_all.png"
492+
)
493+
if _ttr_hex.exists():
480494
st.markdown(
481-
"**Calibration error population analysis** — high-error vs low-error pair properties"
495+
"**Test-train hexbin · step 44k** — 5-panel cross-split analysis (rho=0.680)"
482496
)
483-
show(_cal_pop)
497+
show(_ttr_hex)
484498

485499
st.markdown("---")
486500

tools/diagnose_retrieval.py

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -628,11 +628,8 @@ def get_fp(smi):
628628
plt.savefig(out_path, dpi=140, bbox_inches="tight")
629629
print(f"Figure 1 saved → {out_path}")
630630

631-
# ── Figure 2: molecular properties ───────────────────────────────────────
632-
fig2, axes2 = plt.subplots(1, 3, figsize=(16, 5))
633-
634-
# 5. Tanimoto distributions
635-
ax = axes2[0]
631+
# ── Figure 2: Tanimoto distributions (panel 5) ───────────────────────────
632+
fig2, ax = plt.subplots(1, 1, figsize=(6, 5))
636633
bins5 = np.linspace(0, 1, 41)
637634
ax.hist(
638635
oracle_tanis[om],
@@ -652,40 +649,11 @@ def get_fp(smi):
652649
)
653650
ax.set_xlabel("Tanimoto similarity (Morgan FP, r=2)")
654651
ax.set_ylabel("# test spectra")
655-
ax.set_title("5 · Tanimoto: oracle vs SIMBA pair", fontweight="bold")
652+
ax.set_title("Tanimoto: oracle vs SIMBA pair", fontweight="bold")
656653
ax.legend(fontsize=9)
657654
ax.grid(True, alpha=0.2)
658655

659-
# 6. Oracle rank vs Oracle GT MCES scatter (hexbin)
660-
ax = axes2[1]
661-
valid = om & (oracle_ranks <= 10000)
662-
hb = ax.hexbin(
663-
oracle_gt_arr[valid],
664-
np.log10(oracle_ranks[valid].astype(float) + 1),
665-
gridsize=30,
666-
cmap="Blues",
667-
mincnt=1,
668-
)
669-
plt.colorbar(hb, ax=ax, label="count")
670-
ax.set_xlabel("Oracle GT MCES")
671-
ax.set_ylabel("log10(rank of oracle mol + 1)")
672-
ax.set_title("6 · Oracle rank vs GT MCES", fontweight="bold")
673-
ax.grid(True, alpha=0.2)
674-
675-
# 7. SIMBA retrieved GT vs Oracle GT MCES
676-
ax = axes2[2]
677-
hb2 = ax.hexbin(
678-
oracle_gt_arr[om], simba_gt_arr[om], gridsize=30, cmap="Oranges", mincnt=1
679-
)
680-
plt.colorbar(hb2, ax=ax, label="count")
681-
ax.plot([0, 40], [0, 40], "r--", lw=1, label="ideal (SIMBA = oracle)")
682-
ax.set_xlabel("Oracle GT MCES")
683-
ax.set_ylabel("SIMBA retrieved GT MCES")
684-
ax.set_title("7 · SIMBA GT vs Oracle GT MCES", fontweight="bold")
685-
ax.legend(fontsize=8)
686-
ax.grid(True, alpha=0.2)
687-
688-
fig2.suptitle("SIMBA Retrieval — Molecular Property Analysis", fontsize=12)
656+
fig2.suptitle("SIMBA Retrieval — Tanimoto Analysis", fontsize=12)
689657
plt.tight_layout()
690658
plt.savefig(fig2_path, dpi=140, bbox_inches="tight")
691659
print(f"Figure 2 saved → {fig2_path}")

0 commit comments

Comments
 (0)