{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from importlib.metadata import PackageNotFoundError, version\nfrom pathlib import Path\nimport os\nimport shutil\nimport subprocess\nimport sys\nimport time\nimport tomllib\n\nimport torch\nfrom packaging.requirements import Requirement\n\n\n# Change only this section between runs.\nREPO_URL = \"https://github.com/inori6/rxrx1.git\"\nGIT_REF = \"b26a17e\"\nRUN_MODE = \"train\"\n\nCONFIGS = [\n    \"configs/metric/classification_best_d2.yaml\"\n]\n\nHPO_SCRIPT = \"scripts/hpo_fusion.py\"\nHPO_STUDY_NAME = \"well_film_lr_hpo\"\nHPO_TIMEOUT_HOURS = 5.5\nHPO_MAX_TRIALS = 10\n\nWORK_DIR = Path(\"/kaggle/working\")\nPROJECT_DIR = WORK_DIR / \"rxrx1\"\nWANDB_KEY_CANDIDATES = [\n    Path(\"/kaggle/input/datasets/maributa/rxrx1-wandb-secret/wandb_api_key.txt\"),\n    Path(\"/kaggle/input/rxrx1-wandb-secret/wandb_api_key.txt\"),\n]\n\n\ndef section(title):\n    print(f\"\\n{'=' * 80}\\n{title}\\n{'=' * 80}\", flush=True)\n\n\ndef run(command, *, cwd=PROJECT_DIR, check=True):\n    print(\"Running:\", \" \".join(map(str, command)), flush=True)\n    return subprocess.run(command, cwd=cwd, check=check)\n\n\ndef setup_wandb_key():\n    paths = [path for path in WANDB_KEY_CANDIDATES if path.is_file()]\n    if len(paths) != 1:\n        raise RuntimeError(\n            f\"Expected exactly one W&B key; found {len(paths)}. \"\n            f\"Checked: {WANDB_KEY_CANDIDATES}\"\n        )\n    key = paths[0].read_text(encoding=\"utf-8\").strip()\n    if not key:\n        raise RuntimeError(f\"W&B API key file is empty: {paths[0]}\")\n    os.environ[\"WANDB_API_KEY\"] = key\n    print(f\"W&B key loaded from: {paths[0]}\", flush=True)\n\n\ndef prepare_repository():\n    if PROJECT_DIR.exists():\n        shutil.rmtree(PROJECT_DIR)\n    run([\"git\", \"clone\", REPO_URL, str(PROJECT_DIR)], cwd=WORK_DIR)\n    run([\"git\", \"checkout\", GIT_REF])\n    run([\"git\", \"rev-parse\", \"HEAD\"])\n\n\ndef install_missing_dependencies():\n    pyproject_path = PROJECT_DIR / \"pyproject.toml\"\n    if not pyproject_path.is_file():\n        raise FileNotFoundError(f\"pyproject.toml not found: {pyproject_path}\")\n\n    with pyproject_path.open(\"rb\") as file:\n        requirements = tomllib.load(file).get(\"project\", {}).get(\"dependencies\", [])\n\n    missing = []\n    for text in requirements:\n        requirement = Requirement(text)\n        try:\n            installed = version(requirement.name)\n            print(f\"FOUND   | {requirement.name}=={installed}\", flush=True)\n            if requirement.specifier and installed not in requirement.specifier:\n                print(f\"WARNING | keeping {installed}; project requests {text}\", flush=True)\n        except PackageNotFoundError:\n            print(f\"MISSING | {text}\", flush=True)\n            missing.append(text)\n\n    if missing:\n        run([sys.executable, \"-m\", \"pip\", \"install\", *missing])\n\n    src_path = str(PROJECT_DIR / \"src\")\n    previous = os.environ.get(\"PYTHONPATH\")\n    os.environ[\"PYTHONPATH\"] = (\n        f\"{src_path}{os.pathsep}{previous}\" if previous else src_path\n    )\n\n\ndef print_environment():\n    gpu = torch.cuda.get_device_name(0) if torch.cuda.is_available() else \"CPU\"\n    print(f\"Python : {sys.executable}\", flush=True)\n    print(f\"PyTorch: {torch.__version__}\", flush=True)\n    print(f\"CUDA   : {torch.version.cuda}\", flush=True)\n    print(f\"cuDNN  : {torch.backends.cudnn.version()}\", flush=True)\n    print(f\"GPU    : {gpu} ({torch.cuda.device_count()} device(s))\", flush=True)\n\n\ndef build_experiment_command(config_path):\n    common = [\"--config\", config_path]\n    if RUN_MODE == \"train\":\n        return [sys.executable, \"-u\", \"scripts/train.py\", *common]\n    if RUN_MODE == \"hpo\":\n        return [\n            sys.executable,\n            \"-u\",\n            HPO_SCRIPT,\n            *common,\n            \"--study-name\",\n            HPO_STUDY_NAME,\n            \"--timeout-hours\",\n            str(HPO_TIMEOUT_HOURS),\n            \"--max-trials\",\n            str(HPO_MAX_TRIALS),\n        ]\n    raise ValueError(f\"RUN_MODE must be 'train' or 'hpo', got {RUN_MODE!r}\")\n\n\ndef run_experiment(config_path):\n    config_file = PROJECT_DIR / config_path\n    if not config_file.is_file():\n        print(f\"CONFIG NOT FOUND: {config_file}\", flush=True)\n        return False, None\n\n    section(f\"STARTING: {config_path}\")\n    started = time.time()\n    result = run(build_experiment_command(config_path), check=False)\n    minutes = (time.time() - started) / 60\n    status = \"SUCCESS\" if result.returncode == 0 else \"FAILED\"\n    print(f\"{status}: {config_path} ({minutes:.2f} min)\", flush=True)\n    return result.returncode == 0, minutes\n\n\ndef main():\n    section(\"RxRx1 Kaggle Experiment Runner\")\n    setup_wandb_key()\n    prepare_repository()\n    section(\"Environment\")\n    print_environment()\n    install_missing_dependencies()\n\n    print(f\"Repository : {REPO_URL}\", flush=True)\n    print(f\"Git ref    : {GIT_REF}\", flush=True)\n    print(f\"Run mode   : {RUN_MODE}\", flush=True)\n    print(f\"Experiments: {len(CONFIGS)}\", flush=True)\n\n    results = []\n    started = time.time()\n    for index, config_path in enumerate(CONFIGS, 1):\n        section(f\"EXPERIMENT {index}/{len(CONFIGS)}\")\n        try:\n            success, minutes = run_experiment(config_path)\n        except Exception as exc:\n            print(f\"UNEXPECTED ERROR: {exc}\", flush=True)\n            success, minutes = False, None\n        results.append((config_path, success, minutes))\n\n    section(\"SUMMARY\")\n    for config_path, success, minutes in results:\n        status = \"SUCCESS\" if success else \"FAILED\"\n        runtime = \"N/A\" if minutes is None else f\"{minutes:.2f} min\"\n        print(f\"{status:8} | {runtime:12} | {config_path}\", flush=True)\n    print(f\"Total runtime: {(time.time() - started) / 60:.2f} min\", flush=True)\n\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}