{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":116062,"databundleVersionId":14875579,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":5499219,"sourceType":"datasetVersion","datasetId":3167603},{"sourceId":5549164,"sourceType":"datasetVersion","datasetId":3197305},{"sourceId":5607816,"sourceType":"datasetVersion","datasetId":3225525},{"sourceId":5792099,"sourceType":"datasetVersion","datasetId":3327296},{"sourceId":6247561,"sourceType":"datasetVersion","datasetId":3590060},{"sourceId":294750156,"sourceType":"kernelVersion"},{"sourceId":295300660,"sourceType":"kernelVersion"},{"sourceId":295435642,"sourceType":"kernelVersion"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-02T21:54:23.478660Z","iopub.execute_input":"2026-02-02T21:54:23.479145Z","iopub.status.idle":"2026-02-02T21:54:23.501352Z","shell.execute_reply.started":"2026-02-02T21:54:23.479115Z","shell.execute_reply":"2026-02-02T21:54:23.499987Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CAFA6 LAST-SUBMISSION ENSEMBLE (Conservative, IA + Agreement)\n# Uses: SOTA + ESM + SUB1\n# - Auto-detect logits vs prob\n# - No parent propagation (evaluator propagates) ✅ smaller & safer\n# - Enforces: score in (0,1], <=1500 terms/protein, 3 significant figures\n# ============================================================\n\nimport os, math, gc\nimport numpy as np\nimport pandas as pd\nfrom collections import defaultdict\nfrom tqdm.auto import tqdm\n\n# -------------------------\n# Required competition files\n# -------------------------\nTEST_FASTA = \"/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta\"\nOBO_PATH   = \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\"\nIA_PATH    = \"/kaggle/input/cafa-6-protein-function-prediction/IA.tsv\"\n\n# -------------------------\n# AUTO-DISCOVERY KEYWORDS\n# (Add those notebook outputs as Kaggle datasets to your notebook inputs)\n# -------------------------\nKW_SOTA = \"cafa6-sota-solution-27jan\"                # jakupymeraj output dataset slug\nKW_ESM  = \"cafa-6-esm-2-embedding-inference-0-386\"   # datasciencegrad output dataset slug\nKW_SUB1 = \"cafa-6-protein-function-prediction\"                      # your base/previous best dataset slug\n\n# If auto-discovery fails, set manual paths here:\nMANUAL_PATHS = {\n    \"SOTA\": None,\n    \"ESM\" : None,\n    \"SUB1\": None,\n}\n\nOUT_ENSEMBLE = \"/kaggle/working/submission.tsv\"\nOUT_SAFE     = \"/kaggle/working/submission_safe.tsv\"\n\n# -------------------------\n# Ensemble knobs (safe defaults)\n# -------------------------\nBASE_PRIORITY = [\"SUB1\", \"SOTA\", \"ESM\"]  # base = first available\n\nTOPK_BASE_READ = 700\nTOPK_SUPP_READ = 120\n\nTOPK_FINAL = 650\nHARD_CAP_1500 = 1500\n\nIA_MIN_KEEP = 0.8\nKEEP_IF_SCORE_GE = 0.78\nIA_EPS = 0.10\n\nADD_MIN_SCORE = 0.70\nADD_MIN_IA    = 1.0\n\nAGREE_MIN_MODELS = 2\nAGREE_BOOST = {2: 1.03, 3: 1.06}  # mild boost\n\nTEMP = {\"SOTA\": 2.0, \"ESM\": 2.0, \"SUB1\": 2.0}  # only used if logits detected\n\nMIN_OUT = 1e-3\nSIGFIG  = 3\n\n\n# ============================================================\n# Helpers\n# ============================================================\ndef parse_fasta_ids(path):\n    ids = []\n    with open(path, \"r\") as f:\n        for line in f:\n            if line.startswith(\">\"):\n                ids.append(line[1:].split()[0].strip())\n    return ids\n\ndef parse_obo_valid_and_alt(obo_path):\n    alt2id = {}\n    valid = set()\n    cur_id = None\n    cur_alts = []\n\n    def flush():\n        nonlocal cur_id, cur_alts\n        if cur_id is not None:\n            valid.add(cur_id)\n            for a in cur_alts:\n                alt2id[a] = cur_id\n\n    with open(obo_path, \"r\") as f:\n        for line in f:\n            line = line.strip()\n            if line == \"[Term]\":\n                flush()\n                cur_id = None\n                cur_alts = []\n                continue\n            if not line:\n                continue\n            if line.startswith(\"id: GO:\"):\n                cur_id = line.split(\"id: \", 1)[1].strip()\n            elif line.startswith(\"alt_id: GO:\"):\n                cur_alts.append(line.split(\"alt_id: \", 1)[1].strip())\n\n    flush()\n    return valid, alt2id\n\ndef load_ia(ia_path):\n    df = pd.read_csv(ia_path, sep=\"\\t\", header=None, names=[\"term\",\"ia\"])\n    df[\"term\"] = df[\"term\"].astype(str)\n    df[\"ia\"] = pd.to_numeric(df[\"ia\"], errors=\"coerce\").fillna(0.0).astype(np.float32)\n    return dict(zip(df[\"term\"].values, df[\"ia\"].values))\n\ndef sigmoid(x):\n    x = np.clip(x, -60, 60)\n    return 1.0 / (1.0 + np.exp(-x))\n\ndef detect_mode(path, sample_lines=300000):\n    mx = -1e9\n    seen = 0\n    with open(path, \"r\") as f:\n        for line in f:\n            seen += 1\n            if seen > sample_lines:\n                break\n            sp = line.rstrip(\"\\n\").split(\"\\t\")\n            if len(sp) < 3:\n                continue\n            if sp[1] == \"Text\":\n                continue\n            try:\n                v = float(sp[2])\n            except:\n                continue\n            if v > mx:\n                mx = v\n    mode = \"logit\" if mx > 1.2 else \"prob\"\n    return mode, float(mx)\n\ndef harmonize_scores(arr, mode, temp):\n    arr = arr.astype(np.float32)\n    if mode == \"logit\":\n        p = sigmoid(arr / float(temp))\n    else:\n        p = arr\n    return np.clip(p, MIN_OUT, 1.0).astype(np.float32)\n\ndef sigfig(x, k=3):\n    return format(float(x), f\".{k}g\")\n\ndef discover_submission_files():\n    candidates = []\n    for root, _, files in os.walk(\"/kaggle/input\"):\n        for fn in files:\n            if fn == \"submission.tsv\":\n                fp = os.path.join(root, fn)\n                try:\n                    sz = os.path.getsize(fp)\n                except:\n                    sz = 0\n                candidates.append((sz, fp))\n    candidates.sort(reverse=True)\n    return candidates\n\ndef pick_by_keyword(candidates, keyword):\n    kw = keyword.lower()\n    for _, fp in candidates:\n        if kw in fp.lower():\n            return fp\n    return None\n\ndef load_preds(path, targets_set, valid_terms, alt2id, mode, temp, topk_keep,\n              is_supp=False, term_ia=None):\n    pred = defaultdict(dict)\n    chunksize = 2_000_000\n\n    for chunk in pd.read_csv(\n        path, sep=\"\\t\", header=None,\n        names=[\"protein\",\"term\",\"score\"],\n        dtype={\"protein\": str, \"term\": str},\n        chunksize=chunksize, on_bad_lines=\"skip\"\n    ):\n        chunk = chunk[chunk[\"protein\"].isin(targets_set)]\n        if chunk.empty:\n            continue\n\n        chunk = chunk[chunk[\"term\"].astype(str).str.startswith(\"GO:\")]\n        if chunk.empty:\n            continue\n\n        s = pd.to_numeric(chunk[\"score\"], errors=\"coerce\").values\n        ok = ~np.isnan(s)\n        if not ok.any():\n            continue\n        chunk = chunk.iloc[np.where(ok)[0]].copy()\n        s = s[ok]\n\n        p = harmonize_scores(s, mode, temp)\n        chunk[\"prob\"] = p\n\n        chunk[\"term\"] = chunk[\"term\"].map(lambda t: alt2id.get(t, t))\n        chunk = chunk[chunk[\"term\"].isin(valid_terms)]\n        if chunk.empty:\n            continue\n\n        if is_supp:\n            ia = chunk[\"term\"].map(lambda t: float(term_ia.get(t, 0.0))).values\n            chunk = chunk[(chunk[\"prob\"] >= ADD_MIN_SCORE) & (ia >= ADD_MIN_IA)]\n            if chunk.empty:\n                continue\n\n        chunk = chunk.groupby([\"protein\",\"term\"], as_index=False)[\"prob\"].max()\n\n        for prot, term, prob in chunk.itertuples(index=False, name=None):\n            d = pred[prot]\n            prev = d.get(term)\n            if (prev is None) or (prob > prev):\n                d[term] = float(prob)\n\n        # prune occasionally\n        if len(pred) % 5000 == 0:\n            for prot in list(pred.keys())[:2000]:\n                d = pred[prot]\n                if len(d) > int(1.5 * topk_keep):\n                    items = sorted(d.items(), key=lambda x: x[1], reverse=True)[:topk_keep]\n                    pred[prot] = dict(items)\n\n        del chunk\n        gc.collect()\n\n    # final prune\n    for prot in list(pred.keys()):\n        d = pred[prot]\n        if len(d) > topk_keep:\n            items = sorted(d.items(), key=lambda x: x[1], reverse=True)[:topk_keep]\n            pred[prot] = dict(items)\n\n    return pred\n\n\n# ============================================================\n# MAIN\n# ============================================================\nprint(\"Scanning /kaggle/input for submission.tsv ...\")\ncands = discover_submission_files()\nfor i, (sz, fp) in enumerate(cands[:20], 1):\n    print(f\" {i:2d}. {sz/1e6:8.1f} MB | {fp}\")\n\npaths = {}\npaths[\"SOTA\"] = MANUAL_PATHS[\"SOTA\"] or pick_by_keyword(cands, KW_SOTA)\npaths[\"ESM\"]  = MANUAL_PATHS[\"ESM\"]  or pick_by_keyword(cands, KW_ESM)\npaths[\"SUB1\"] = MANUAL_PATHS[\"SUB1\"] or pick_by_keyword(cands, KW_SUB1)\n\nprint(\"\\nSelected inputs:\")\nfor k,v in paths.items():\n    print(f\"  {k:4s}: {v}\")\n\nmissing = [k for k,v in paths.items() if v is None]\nif missing:\n    raise RuntimeError(f\"Missing inputs: {missing}. Add those outputs as Kaggle datasets or set MANUAL_PATHS.\")\n\nBASE = None\nfor key in BASE_PRIORITY:\n    if paths.get(key):\n        BASE = key\n        break\nprint(\"\\nBASE =\", BASE, \"=>\", paths[BASE])\n\ntargets = parse_fasta_ids(TEST_FASTA)\ntargets_set = set(targets)\n\nvalid_terms, alt2id = parse_obo_valid_and_alt(OBO_PATH)\nterm_ia = load_ia(IA_PATH)\n\nmodes = {}\nfor k, fp in paths.items():\n    mode, mx = detect_mode(fp)\n    modes[k] = mode\n    print(f\"{k}: mode={mode} (sample max {mx:.3f})\")\n\nsupp_keys = [k for k in [\"SOTA\",\"ESM\",\"SUB1\"] if k != BASE]\nsupp_preds = {}\n\nprint(\"\\nLoading supplements (filtered) ...\")\nfor k in supp_keys:\n    supp_preds[k] = load_preds(paths[k], targets_set, valid_terms, alt2id,\n                               modes[k], TEMP[k], TOPK_SUPP_READ,\n                               is_supp=True, term_ia=term_ia)\n    print(f\"  {k}: proteins stored {len(supp_preds[k]):,}\")\n\nprint(\"\\nLoading base ...\")\nbase_pred = load_preds(paths[BASE], targets_set, valid_terms, alt2id,\n                       modes[BASE], TEMP[BASE], TOPK_BASE_READ,\n                       is_supp=False, term_ia=term_ia)\nprint(\"Base proteins stored:\", len(base_pred))\n\n# SAFE base-only backup\nprint(\"\\nWriting SAFE base-only submission:\", OUT_SAFE)\nwith open(OUT_SAFE, \"w\") as f:\n    for prot in tqdm(targets, desc=\"SAFE\"):\n        d = base_pred.get(prot, {})\n        if not d:\n            continue\n        items = sorted(d.items(), key=lambda x: x[1], reverse=True)[:min(TOPK_FINAL, HARD_CAP_1500)]\n        for term, s in items:\n            f.write(f\"{prot}\\t{term}\\t{sigfig(np.clip(s, MIN_OUT, 1.0), SIGFIG)}\\n\")\n\n# ENSEMBLE (base + agreement/high-IA adds)\nprint(\"\\nWriting ENSEMBLE submission:\", OUT_ENSEMBLE)\nwith open(OUT_ENSEMBLE, \"w\") as f:\n    for prot in tqdm(targets, desc=\"ENSEMBLE\"):\n        base = base_pred.get(prot, {}) or {}\n\n        # union dictionary (term -> score)\n        union = dict(base)\n\n        # vote counts\n        vote = defaultdict(int)\n        for t in base.keys():\n            vote[t] += 1\n\n        for mk in supp_keys:\n            d = supp_preds[mk].get(prot, {})\n            for t, s in d.items():\n                vote[t] += 1\n                if (t not in union) or (s > union[t]):\n                    union[t] = float(s)\n\n        if not union:\n            continue\n\n        items = []\n        for t, s0 in union.items():\n            ia = float(term_ia.get(t, 0.0))\n            n_models = vote.get(t, 0)\n\n            s = float(s0)\n            if n_models >= AGREE_MIN_MODELS:\n                s = min(1.0, s * AGREE_BOOST.get(n_models, AGREE_BOOST[3]))\n\n            # prune generic unless very confident or agreement\n            if ia < IA_MIN_KEEP and s < KEEP_IF_SCORE_GE and n_models < AGREE_MIN_MODELS:\n                continue\n\n            rank = s * (ia + IA_EPS) * (1.0 + 0.04 * max(0, n_models - 1))\n            items.append((t, s, rank))\n\n        if not items:\n            continue\n\n        items.sort(key=lambda x: x[2], reverse=True)\n        items = items[:min(TOPK_FINAL, HARD_CAP_1500)]\n\n        for t, s, _ in items[:HARD_CAP_1500]:\n            s = float(np.clip(s, MIN_OUT, 1.0))\n            f.write(f\"{prot}\\t{t}\\t{sigfig(s, SIGFIG)}\\n\")\n\nprint(\"\\nDONE\")\nprint(\"SAFE :\", OUT_SAFE, \"MB:\", os.path.getsize(OUT_SAFE)/1e6)\nprint(\"FINAL:\", OUT_ENSEMBLE, \"MB:\", os.path.getsize(OUT_ENSEMBLE)/1e6)\nprint(\"\\nSubmit:\", OUT_ENSEMBLE)\nprint(\"Backup:\", OUT_SAFE)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-02T21:55:02.982530Z","iopub.execute_input":"2026-02-02T21:55:02.982891Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}