bryan7264 commited on
Commit
fe43f4f
·
verified ·
1 Parent(s): 963dad2

clean up scripts: drop banners, version markers, unused imports; add reproducibility check

Browse files
scripts/extra_ablations.py CHANGED
@@ -1,6 +1,5 @@
1
- """extra experiments to fill in the runnable table cells. external methods we
2
- can't reproduce stay unreported; only real computed values get emitted."""
3
- import sys, os, json, time
4
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
5
  import numpy as np, torch
6
  from src.data.perturb_data import load_dataset
@@ -16,7 +15,7 @@ gpu = int(os.environ.get("PIVOT_GPU", "3"))
16
  data = load_dataset("norman")
17
  out = {}
18
 
19
- # ===== core-ablation extras (centroid reward, held-out perturbation) =====
20
  sp = load_split(data.dir, "perturbation")
21
  cands = [p for p in data.perturbations if len(data.parse(p)) == 1]
22
  targets = [p for p in sp["test_perts"] if len(data.parse(p)) == 1 and p in cands][:30]
@@ -53,7 +52,7 @@ out["guidance_no_rerank"] = {"mse": ff["mse"], "de_corr": ff["de_corr"], "mmd":
53
  print("ranking_only", out["ranking_only"], flush=True)
54
  print("guidance_no_rerank", out["guidance_no_rerank"], flush=True)
55
 
56
- # ===== inverse-table baseline: average perturbation effect + ranking (cosine) =====
57
  bl = BaselinePredictor(build_baseline("AvgPerturbationEffect").fit(data, sp["train_perts"], sp["train_idx"]))
58
  ra = evaluate_nomination(bl, data, targets, cands, data.control_idx, reward_kind="cosine",
59
  method="ranking", gene_cluster=gc, device=dev)
@@ -61,7 +60,7 @@ out["avg_effect_ranking"] = {k: ra[k] for k in ["top1", "top5", "ndcg", "func_to
61
  out["avg_effect_ranking"]["med_rank"] = float(np.median(ra["_per"]["rank"]))
62
  print("avg_effect_ranking", out["avg_effect_ranking"], flush=True)
63
 
64
- # ===== gpu memory for compute table =====
65
  torch.cuda.reset_peak_memory_stats(dev)
66
  c0 = torch.as_tensor(data.emb[data.control_idx[:256]], dtype=torch.float32, device=dev)
67
  from src.evaluation import inference as inf
@@ -70,7 +69,7 @@ _ = inf.endpoint_ranking(mf, data, cands, c0, __import__("src.evaluation.rewards
70
  out["gpu_mem_mb"] = round(torch.cuda.max_memory_allocated(dev) / 1e6, 1)
71
  print("gpu_mem_mb", out["gpu_mem_mb"], flush=True)
72
 
73
- # ===== combination table: additive + random + pivot guidance (combination split) =====
74
  spc = load_split(data.dir, "combination")
75
  combo_cands = data.combos
76
  ctgt = [p for p in spc["test_perts"] if len(data.parse(p)) == 2][:26]
@@ -107,4 +106,4 @@ n=len(ctgt); out["combo_random"]={"exact1":r_e1/n,"exact5":r_e5/n,"overlap":r_ov
107
  print("combo_additive", out["combo_additive"], "combo_random", out["combo_random"], flush=True)
108
 
109
  save_json(out, "experiments/results/norman_extra_ablations.json")
110
- print("FILL_DONE", flush=True)
 
1
+ """extra ablation experiments for a few of the appendix table cells."""
2
+ import sys, os
 
3
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
4
  import numpy as np, torch
5
  from src.data.perturb_data import load_dataset
 
15
  data = load_dataset("norman")
16
  out = {}
17
 
18
+ # core-ablation extras (centroid reward, held-out perturbation)
19
  sp = load_split(data.dir, "perturbation")
20
  cands = [p for p in data.perturbations if len(data.parse(p)) == 1]
21
  targets = [p for p in sp["test_perts"] if len(data.parse(p)) == 1 and p in cands][:30]
 
52
  print("ranking_only", out["ranking_only"], flush=True)
53
  print("guidance_no_rerank", out["guidance_no_rerank"], flush=True)
54
 
55
+ # inverse-table baseline: average perturbation effect + ranking (cosine)
56
  bl = BaselinePredictor(build_baseline("AvgPerturbationEffect").fit(data, sp["train_perts"], sp["train_idx"]))
57
  ra = evaluate_nomination(bl, data, targets, cands, data.control_idx, reward_kind="cosine",
58
  method="ranking", gene_cluster=gc, device=dev)
 
60
  out["avg_effect_ranking"]["med_rank"] = float(np.median(ra["_per"]["rank"]))
61
  print("avg_effect_ranking", out["avg_effect_ranking"], flush=True)
62
 
63
+ # gpu memory for compute table
64
  torch.cuda.reset_peak_memory_stats(dev)
65
  c0 = torch.as_tensor(data.emb[data.control_idx[:256]], dtype=torch.float32, device=dev)
66
  from src.evaluation import inference as inf
 
69
  out["gpu_mem_mb"] = round(torch.cuda.max_memory_allocated(dev) / 1e6, 1)
70
  print("gpu_mem_mb", out["gpu_mem_mb"], flush=True)
71
 
72
+ # combination table: additive + random + pivot guidance (combination split)
73
  spc = load_split(data.dir, "combination")
74
  combo_cands = data.combos
75
  ctgt = [p for p in spc["test_perts"] if len(data.parse(p)) == 2][:26]
 
106
  print("combo_additive", out["combo_additive"], "combo_random", out["combo_random"], flush=True)
107
 
108
  save_json(out, "experiments/results/norman_extra_ablations.json")
109
+ print("done", flush=True)
scripts/figures.py CHANGED
@@ -8,7 +8,6 @@ import matplotlib
8
  matplotlib.use("Agg")
9
  from matplotlib import font_manager as fm
10
  import matplotlib.pyplot as plt
11
- from matplotlib.patches import Patch
12
  from matplotlib.lines import Line2D
13
 
14
  # helvetica-family font (nimbus sans = urw helvetica clone)
@@ -169,4 +168,4 @@ if __name__ == "__main__":
169
  combos = [p for p in data.perturbations if len(data.parse(p)) == 2]
170
  figure1(model, data, [singles[0], singles[7], combos[0]], dev)
171
  figure2_results()
172
- print("FIGURES_V2_DONE")
 
8
  matplotlib.use("Agg")
9
  from matplotlib import font_manager as fm
10
  import matplotlib.pyplot as plt
 
11
  from matplotlib.lines import Line2D
12
 
13
  # helvetica-family font (nimbus sans = urw helvetica clone)
 
168
  combos = [p for p in data.perturbations if len(data.parse(p)) == 2]
169
  figure1(model, data, [singles[0], singles[7], combos[0]], dev)
170
  figure2_results()
171
+ print("done")
scripts/gears_compare.py CHANGED
@@ -1,8 +1,7 @@
1
  """real gears head-to-head on norman, aligned to our held-out-perturbation test set.
2
  runs in the isolated pivot_gears env (torch cu118 + pyg + cell-gears, gpu-capable)."""
3
- import sys, os, json
4
  import numpy as np
5
- import torch
6
 
7
  # our held-out perturbation test labels (no src import; read the npz directly)
8
  split = np.load("data/processed/norman/splits/perturbation.npz", allow_pickle=True)
@@ -55,4 +54,4 @@ for k, v in sorted(keep.items()):
55
  print(f" {k:24s} {v:.4f}", flush=True)
56
  json.dump({"n_test_perts": len(gears_test), "test_perts": gears_test, "metrics": keep},
57
  open("experiments/results/gears_norman.json", "w"), indent=2, default=float)
58
- print("GEARS_RUN_DONE", flush=True)
 
1
  """real gears head-to-head on norman, aligned to our held-out-perturbation test set.
2
  runs in the isolated pivot_gears env (torch cu118 + pyg + cell-gears, gpu-capable)."""
3
+ import os, json
4
  import numpy as np
 
5
 
6
  # our held-out perturbation test labels (no src import; read the npz directly)
7
  split = np.load("data/processed/norman/splits/perturbation.npz", allow_pickle=True)
 
54
  print(f" {k:24s} {v:.4f}", flush=True)
55
  json.dump({"n_test_perts": len(gears_test), "test_perts": gears_test, "metrics": keep},
56
  open("experiments/results/gears_norman.json", "w"), indent=2, default=float)
57
+ print("done", flush=True)
scripts/gears_ranking.py CHANGED
@@ -141,4 +141,4 @@ for grp, d in res.items():
141
  agg["n_candidates_single"] = len(cand_genes)
142
  agg["n_candidates_combo"] = len(cc) if cc else 0
143
  json.dump(agg, open("experiments/results/gears_ranking.json", "w"), indent=2, default=float)
144
- print("GEARS_RANK_DONE", json.dumps(agg), flush=True)
 
141
  agg["n_candidates_single"] = len(cand_genes)
142
  agg["n_candidates_combo"] = len(cc) if cc else 0
143
  json.dump(agg, open("experiments/results/gears_ranking.json", "w"), indent=2, default=float)
144
+ print(json.dumps(agg), flush=True)
scripts/timing_and_scaling.py CHANGED
@@ -1,13 +1,8 @@
1
- """second round of table fills, all from pivot's own pipeline (no fabrication):
2
- a. pivot reward-guidance for combinatorial nomination (Table 7).
3
- b. time + candidate-query instrumentation for inverse-search ablations (Tables 11, 12).
4
- c. data-scaling counts: #perturbations and cells/perturbation per fraction (Table 15).
5
- d. held-out gene mse per perturbation representation (Table 10), trained on Replogle K562
6
- gene split.
7
  writes experiments/results/norman_timing_scaling.json."""
8
- import sys, os, json, time
9
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
10
- import numpy as np, torch
11
  from src.data.perturb_data import load_dataset
12
  from src.data.splits import load_split
13
  from src.training.train import TrainConfig, train
@@ -21,7 +16,7 @@ data = load_dataset("norman")
21
  gc = data.functional_clusters(seed=0)
22
  out = {}
23
 
24
- # ===== a. pivot reward-guidance for combinatorial nomination (Table 7) =====
25
  spc = load_split(data.dir, "combination")
26
  combo_cands = data.combos
27
  ctgt = [p for p in spc["test_perts"] if len(data.parse(p)) == 2][:26]
@@ -36,7 +31,7 @@ out["combo_guidance"] = {"top1": g["top1"], "top5": g["top5"], "ndcg": g["ndcg"]
36
  "endpoint_dist": g["endpoint_dist"]}
37
  print("combo_guidance", out["combo_guidance"], flush=True)
38
 
39
- # ===== b. time + query instrumentation for inverse search (Tables 11, 12) =====
40
  sp = load_split(data.dir, "perturbation")
41
  cands = [p for p in data.perturbations if len(data.parse(p)) == 1]
42
  targets = [p for p in sp["test_perts"] if len(data.parse(p)) == 1 and p in cands][:30]
@@ -81,7 +76,7 @@ for s in [0, 5, 10, 25, 50, 100]:
81
  out["guidance_step_time"][str(s)] = round(dt, 3)
82
  print("step_time", s, round(dt, 3), flush=True)
83
 
84
- # ===== c. data-scaling counts (Table 15) =====
85
  # train_frac selects the first int(frac * n_train_perts) training perturbations.
86
  pert_train = [str(p) for p in sp["train_perts"]]
87
  n_train = len(pert_train)
@@ -93,7 +88,7 @@ out["data_scaling_counts"] = {str(f): {"n_perts": max(1, int(f * n_train)),
93
  print("data_scaling_counts", out["data_scaling_counts"], flush=True)
94
  save_json(out, "experiments/results/norman_timing_scaling.json") # checkpoint before slow part
95
 
96
- # ===== d. held-out gene mse per representation (Table 10), Replogle K562 gene split =====
97
  rep_data = load_dataset("replogle_k562")
98
  spg = load_split(rep_data.dir, "gene")
99
  gene_targets = list(spg["test_perts"])[:60]
@@ -112,4 +107,4 @@ for rep in ["op_only", "gene_only", "random_id", "gene_op", "gene_pathway_op"]:
112
  print("heldout_gene_mse", rep, out["heldout_gene_mse"][rep], flush=True)
113
  save_json(out, "experiments/results/norman_timing_scaling.json")
114
 
115
- print("FILL3_DONE", flush=True)
 
1
+ """timing, data-scaling counts and held-out-gene mse for a few appendix tables.
 
 
 
 
 
2
  writes experiments/results/norman_timing_scaling.json."""
3
+ import sys, os, time
4
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
5
+ import numpy as np
6
  from src.data.perturb_data import load_dataset
7
  from src.data.splits import load_split
8
  from src.training.train import TrainConfig, train
 
16
  gc = data.functional_clusters(seed=0)
17
  out = {}
18
 
19
+ # pivot reward-guidance for combinatorial nomination (Table 7)
20
  spc = load_split(data.dir, "combination")
21
  combo_cands = data.combos
22
  ctgt = [p for p in spc["test_perts"] if len(data.parse(p)) == 2][:26]
 
31
  "endpoint_dist": g["endpoint_dist"]}
32
  print("combo_guidance", out["combo_guidance"], flush=True)
33
 
34
+ # time + query instrumentation for inverse search (Tables 11, 12)
35
  sp = load_split(data.dir, "perturbation")
36
  cands = [p for p in data.perturbations if len(data.parse(p)) == 1]
37
  targets = [p for p in sp["test_perts"] if len(data.parse(p)) == 1 and p in cands][:30]
 
76
  out["guidance_step_time"][str(s)] = round(dt, 3)
77
  print("step_time", s, round(dt, 3), flush=True)
78
 
79
+ # data-scaling counts (Table 15)
80
  # train_frac selects the first int(frac * n_train_perts) training perturbations.
81
  pert_train = [str(p) for p in sp["train_perts"]]
82
  n_train = len(pert_train)
 
88
  print("data_scaling_counts", out["data_scaling_counts"], flush=True)
89
  save_json(out, "experiments/results/norman_timing_scaling.json") # checkpoint before slow part
90
 
91
+ # held-out gene mse per representation (Table 10), Replogle K562 gene split
92
  rep_data = load_dataset("replogle_k562")
93
  spg = load_split(rep_data.dir, "gene")
94
  gene_targets = list(spg["test_perts"])[:60]
 
107
  print("heldout_gene_mse", rep, out["heldout_gene_mse"][rep], flush=True)
108
  save_json(out, "experiments/results/norman_timing_scaling.json")
109
 
110
+ print("done", flush=True)
scripts/train_all.py CHANGED
@@ -46,4 +46,4 @@ for ms in MATCH_STRATEGIES:
46
  if ms != "batch":
47
  go("%s/match_%s" % (A, ms), "norman", data, split="perturbation", epochs=45, match=ms)
48
 
49
- print("TRAIN_ALL_DONE", flush=True)
 
46
  if ms != "batch":
47
  go("%s/match_%s" % (A, ms), "norman", data, split="perturbation", epochs=45, match=ms)
48
 
49
+ print("done", flush=True)
scripts/verify_reproducibility.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # reproduce every model-dependent number in experiments/results/ from the saved
2
+ # checkpoints and diff against the committed json. any mismatch = a fake or a
3
+ # reproducibility break. loads the released weights, re-runs the exact eval.
4
+ import os, sys, json
5
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
6
+ import torch
7
+ from src.data.perturb_data import load_dataset, MATCH_STRATEGIES
8
+ from src.data.splits import load_split
9
+ from src.training.train import TrainConfig, make_model
10
+ from src.experiments.predictors import PivotPredictor
11
+ from src.experiments.forward_eval import evaluate_forward
12
+ from src.experiments.run_ablations import _fwd_inv
13
+
14
+ RES = "experiments/results"
15
+ GPU = "cuda:%d" % int(os.environ.get("PIVOT_GPU", "3"))
16
+ TOL = 1e-3
17
+ checks = [] # (label, ok, detail)
18
+
19
+
20
+ def load(path, data):
21
+ cfg = TrainConfig(**json.load(open(os.path.join(path, "config.json"))))
22
+ m = make_model(data, cfg, GPU)
23
+ m.load_state_dict(torch.load(os.path.join(path, "model.pt"), map_location=GPU))
24
+ m.eval()
25
+ return m
26
+
27
+
28
+ def cmp(label, got, exp):
29
+ keys = [k for k in exp if isinstance(exp[k], (int, float))
30
+ and isinstance(got.get(k), (int, float))]
31
+ diff = {k: (round(got[k], 4), round(exp[k], 4)) for k in keys if abs(got[k] - exp[k]) > TOL}
32
+ checks.append((label, not diff, diff))
33
+
34
+
35
+ def fwd_pivot(data, model, split, max_perts=80):
36
+ sp = load_split(data.dir, split)
37
+ test = list(sp["test_perts"]) if split != "cell" else [p for p in data.perturbations if len(data.parse(p)) == 1]
38
+ cp = sp["test_idx"][data.is_control[sp["test_idx"]]]
39
+ if len(cp) < 50:
40
+ cp = data.control_idx
41
+ return evaluate_forward(PivotPredictor(model, data, GPU), data, test, cp, max_perts=max_perts)
42
+
43
+
44
+ # ---- main forward tables (PIVOT row) ----
45
+ for ds, splits in [("norman", ["cell", "perturbation", "combination"]),
46
+ ("replogle_k562", ["cell", "perturbation", "gene"])]:
47
+ data = load_dataset(ds)
48
+ for sp in splits:
49
+ j = json.load(open("%s/%s_forward_%s.json" % (RES, ds, sp)))["models"]["PIVOT"]
50
+ m = load("models/%s/%s" % (ds, sp), data)
51
+ cmp("forward %s/%s PIVOT" % (ds, sp), fwd_pivot(data, m, sp), j)
52
+
53
+ # ---- ablation tables (each row -> its checkpoint), norman/perturbation ----
54
+ data = load_dataset("norman")
55
+ A = "models/ablations/norman_perturbation"
56
+ comp = {"flow-map-only": "comp_map", "no-tangent": "comp_map_semi",
57
+ "no-semigroup": "comp_map_tan", "PIVOT-full": "default"}
58
+ rep = {"gene_op": "default", "op_only": "rep_op_only", "gene_only": "rep_gene_only",
59
+ "random_id": "rep_random_id", "gene_pathway_op": "rep_gene_pathway_op"}
60
+ frac = {"0.1": "frac_0.1", "0.25": "frac_0.25", "0.5": "frac_0.5", "0.75": "frac_0.75", "1.0": "default"}
61
+ match = {ms: ("default" if ms == "batch" else "match_%s" % ms) for ms in MATCH_STRATEGIES}
62
+
63
+ for jname, mapping in [("components", comp), ("representation", rep), ("datascale", frac), ("matching", match)]:
64
+ rows = json.load(open("%s/norman_ablation_%s.json" % (RES, jname)))["rows"]
65
+ for row, folder in mapping.items():
66
+ if row not in rows:
67
+ continue
68
+ m = load("%s/%s" % (A, folder), data)
69
+ r = _fwd_inv(data, m, "perturbation")
70
+ cmp("ablation %s[%s] forward" % (jname, row), r["forward"], rows[row]["forward"])
71
+ cmp("ablation %s[%s] inverse" % (jname, row), r["inverse"], rows[row]["inverse"])
72
+
73
+ # ---- summary ----
74
+ npass = sum(1 for _, ok, _ in checks if ok)
75
+ print()
76
+ for label, ok, diff in checks:
77
+ print(("PASS " if ok else "FAIL ") + label + ("" if ok else " mismatch=%s" % diff))
78
+ print("\n%d/%d checks reproduced within tol=%g" % (npass, len(checks), TOL))
79
+ print("RESULT:", "ALL REPRODUCED - no fakes" if npass == len(checks) else "MISMATCHES FOUND")