Spaces:
Sleeping
Sleeping
| """Run the H2 GP engine end-to-end and persist artefacts. | |
| This is the biology-aware orchestrator: it loads the processed cohort, | |
| restricts to the usable MSI-H vs MSS samples, anonymises the expression | |
| matrix via the airgap, then hands a binary y plus an opaque-ID matrix to | |
| the engine. The engine never sees the cohort labels' string values or any | |
| gene symbols. | |
| Usage: | |
| python -m scripts.run_h2 [--seed 42] [--population 150] [--generations 30] | |
| Outputs: | |
| data/processed/h2/evolution_log.json (anonymised, opaque IDs only) | |
| data/processed/h2/result.json (anonymised, opaque IDs only) | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import json | |
| import sys | |
| from airgap import anonymise | |
| from data_pipeline import schema | |
| from dsl import Load | |
| from engine import run_gp_pipeline | |
| from validate.h1 import POSITIVE_LABEL, usable_msi_cohort | |
| OUT_DIR = schema.PROCESSED_DIR / "h2" | |
| EVOLUTION_PATH = OUT_DIR / "evolution_log.json" | |
| RESULT_PATH = OUT_DIR / "result.json" | |
| def main(argv: list[str] | None = None) -> int: | |
| ap = argparse.ArgumentParser(description="Run the H2 GP discovery pipeline.") | |
| ap.add_argument("--seed", type=int, default=42) | |
| ap.add_argument("--population", type=int, default=150) | |
| ap.add_argument("--generations", type=int, default=30) | |
| ap.add_argument("--prefilter-n", type=int, default=2000) | |
| ap.add_argument("--permutations", type=int, default=200) | |
| args = ap.parse_args(argv) | |
| print("=" * 75) | |
| print("OncoDSL H2 — blind discovery via genetic programming") | |
| print("=" * 75) | |
| print(f"Seed : {args.seed}") | |
| print(f"Population size : {args.population}") | |
| print(f"Generations : {args.generations}") | |
| print(f"Prefilter top N : {args.prefilter_n}") | |
| print(f"Permutations : {args.permutations}") | |
| print() | |
| print("[1/3] Loading + anonymising cohort ...") | |
| cohort = usable_msi_cohort(Load("processed")) | |
| matrix = anonymise(cohort.expression) | |
| y = (cohort.labels["msi_status"] == POSITIVE_LABEL).astype(int).values | |
| print(f" {len(cohort.sample_ids)} samples, " | |
| f"{matrix.shape[1]} anonymised feature IDs " | |
| f"(MSI-H {int(y.sum())} / MSS {int((1 - y).sum())}).") | |
| print() | |
| print("[2/3] Running prefilter + GP + baseline + permutation null ...") | |
| evolution_log, result = run_gp_pipeline( | |
| matrix, y, | |
| seed=args.seed, | |
| prefilter_n=args.prefilter_n, | |
| population_size=args.population, | |
| n_generations=args.generations, | |
| n_permutations=args.permutations, | |
| ) | |
| print() | |
| print("[3/3] Writing artefacts ...") | |
| OUT_DIR.mkdir(parents=True, exist_ok=True) | |
| EVOLUTION_PATH.write_text(json.dumps(evolution_log, indent=2)) | |
| RESULT_PATH.write_text(json.dumps(result, indent=2)) | |
| print(f" wrote {EVOLUTION_PATH}") | |
| print(f" wrote {RESULT_PATH}") | |
| win = result["winning"] | |
| base = result["baseline"] | |
| perm = result["permutation_summary"] | |
| print() | |
| print("-" * 75) | |
| print("WINNER (anonymised)") | |
| print("-" * 75) | |
| print(f" program : {win['program_repr']}") | |
| print(f" feature sets : {win['feature_sets']}") | |
| print(f" n_genes : {len(win['gene_ids'])}") | |
| print(f" CV fitness : {win['cv_fitness']:.4f}") | |
| print(f" HELD-OUT AUROC : {win['holdout_auroc']:.4f}") | |
| print(f" Permutation p : {win['permutation_p']:.4f}") | |
| print() | |
| print(f"BASELINE ({len(base['gene_ids'])} top-prefilter genes) " | |
| f"AUROC: {base['holdout_auroc']:.4f}") | |
| print(f"NULL mean AUROC: {perm['null_auroc_mean']:.4f}, " | |
| f"p95: {perm['null_auroc_p95']:.4f}") | |
| print("-" * 75) | |
| print( | |
| "Start the API to serve these artefacts:\n" | |
| " uvicorn api.app:app --reload\n" | |
| "Then open the H2 tab in the Streamlit viewer." | |
| ) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |