{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "60d4ae5b",
   "metadata": {},
   "source": [
    "# Matched-Control Benchmarking of Quantum Machine Learning Models for Hierarchical Protein Function Prediction\n",
    "\n",
    "Code accompanying the manuscript. Five quantum models are compared with classical controls matched on the property each model is meant to exploit, on a 180-term CAFA6 subset.\n",
    "\n",
    "**Data.** Precomputed CAFA6 split: 480-dimensional ESM-2 (`esm2_t12_35M_UR50D`) embeddings, 180 GO terms (60 per aspect), train / validation / test = 28,000 / 6,000 / 6,000 proteins, cluster-disjoint. The term vocabulary and the information content are fitted on the training split only.\n",
    "\n",
    "**Environment.** Python 3.12, PyTorch 2.10, PennyLane, scikit-learn. A GPU is recommended.\n",
    "\n",
    "**Order.** Run the cells from top to bottom in one session. Every later section depends on sections 1–7.\n",
    "\n",
    "**Models.** Prior, LR, MLP, RF, Cos-kNN, RBF-kNN-480 (classical baselines); ATGO and InterLabelGO+ (reimplementations); VQNN, QRes, CQKR, QK-NN, RUQK-NN, QFE-kNN (quantum models); PM-MLP, RBF-KR, RBF-kNN-10, RBF-kNN-480, CSF-kNN (matched controls); K-Mix, QCF, CG-QCF (fusion).\n",
    "\n",
    "**Approximate run times.** Section 9 (VQNN, QRes, PM-MLP): about 40 min. Section 20 (five-seed matched comparisons): about 2.5 h. Every other section: seconds to a few minutes.\n",
    "\n",
    "**Reproducibility.** Seeds 42, 0, 1, 2, 3. Sections 20–22 check their seed-42 results against the reference values and report any mismatch.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3f3fe240",
   "metadata": {},
   "source": [
    "## 1. Environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4c59ee40",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:44:55.350367Z",
     "iopub.status.busy": "2026-09-27T15:44:55.349923Z",
     "iopub.status.idle": "2026-09-27T15:45:11.730597Z",
     "shell.execute_reply": "2026-09-27T15:45:11.729800Z",
     "shell.execute_reply.started": "2026-09-27T15:44:55.350334Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\u001b[2K     \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m57.3/57.3 kB\u001b[0m \u001b[31m2.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m5.4/5.4 MB\u001b[0m \u001b[31m66.8 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m0:01\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m937.5/937.5 kB\u001b[0m \u001b[31m38.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m25.5/25.5 MB\u001b[0m \u001b[31m62.2 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m2.4/2.4 MB\u001b[0m \u001b[31m71.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m167.2/167.2 kB\u001b[0m \u001b[31m8.4 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m\n",
      "\u001b[2K   \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8.8/8.8 MB\u001b[0m \u001b[31m92.3 MB/s\u001b[0m eta \u001b[36m0:00:00\u001b[0m:00:01\u001b[0m00:01\u001b[0m\n",
      "\u001b[?25h"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Environment: install PennyLane and scikit-learn.\n",
    "# ============================================================================\n",
    "!pip install -q pennylane\n",
    "!pip install -q scikit-learn"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e5b8b90",
   "metadata": {},
   "source": [
    "## 2. Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "f98a9b9c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:45:24.794645Z",
     "iopub.status.busy": "2026-09-27T15:45:24.794317Z",
     "iopub.status.idle": "2026-09-27T15:45:24.972081Z",
     "shell.execute_reply": "2026-09-27T15:45:24.971405Z",
     "shell.execute_reply.started": "2026-09-27T15:45:24.794622Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ DATA_DIR = /kaggle/input/datasets/farzadmajidi/datacafa6  (newdatasetcafa6)\n",
      "ℹ️ mycafadata هم وصل است ولی نادیده گرفته می‌شود.\n",
      "Xtr: (28000, 480) | Xva: (6000, 480) | Xte: (6000, 480)\n",
      "تعداد اصطلاح GO: 180 | چگالی برچسب آموزش: 0.1059\n",
      "IN_DIM = 480 | N_LABELS = 180\n",
      "✅ نگهبان دیتاست: datacafa6 تأیید شد.\n",
      "   مسیر: /kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "   Features: 480\n",
      "   GO Terms: 180\n",
      "   Train / Val / Test: 28000 / 6000 / 6000\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Data: load the precomputed CAFA6 split (480-d ESM-2 embeddings, 180 GO terms;\n",
    "# train / validation / test = 28,000 / 6,000 / 6,000 proteins).\n",
    "# ============================================================================\n",
    "\n",
    "import os, glob, json, time, itertools, warnings\n",
    "from collections import defaultdict\n",
    "\n",
    "warnings.filterwarnings(\"ignore\")\n",
    "\n",
    "import numpy as np\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.utils.data import DataLoader, TensorDataset\n",
    "\n",
    "import pennylane as qml\n",
    "\n",
    "# ---------------- بازتولیدپذیری ----------------\n",
    "GLOBAL_SEED = 42\n",
    "np.random.seed(GLOBAL_SEED)\n",
    "torch.manual_seed(GLOBAL_SEED)\n",
    "if torch.cuda.is_available():\n",
    "    torch.cuda.manual_seed_all(GLOBAL_SEED)\n",
    "\n",
    "\n",
    "# ---------------- یافتن خودکار مسیر داده ----------------\n",
    "def find_one(pattern, roots=(\"/kaggle/input\", \"/content\", \".\")):\n",
    "    for root in roots:\n",
    "        hits = sorted(glob.glob(f\"{root}/**/{pattern}\", recursive=True))\n",
    "        if hits:\n",
    "            return hits[0]\n",
    "    return None\n",
    "\n",
    "\n",
    "# هر کلید = نامِ متعارفِ داخلی؛ مقدار = فهرستِ نام‌فایل‌های شناخته‌شده به\n",
    "# ترتیبِ اولویت (اول قراردادِ قدیم، بعد قراردادِ جدیدِ newdatasetcafa6).\n",
    "FILE_ALIASES = {\n",
    "    # ⚠️ ترتیب عمداً برعکس شد: اول قراردادِ جدیدِ newdatasetcafa6 (۴۸۰ بعد، ۱۸۰ اصطلاح).\n",
    "    # اگر mycafadata (۳۴۴ بعد) هم به نوت‌بوک وصل باشد، دیگر آن را انتخاب نمی‌کند.\n",
    "    \"Xtr.npy\": (\"X_train.npy\", \"Xtr.npy\"),\n",
    "    \"Ytr.npy\": (\"Y_train.npy\", \"Ytr.npy\"),\n",
    "    \"Xva.npy\": (\"X_val.npy\", \"Xva.npy\"),\n",
    "    \"Yva.npy\": (\"Y_val.npy\", \"Yva.npy\"),\n",
    "    \"Xte.npy\": (\"X_test.npy\", \"Xte.npy\"),\n",
    "    \"Yte.npy\": (\"Y_test.npy\", \"Yte.npy\"),\n",
    "}\n",
    "\n",
    "\n",
    "def find_any(canonical_name, roots=(\"/kaggle/input\", \"/content\", \".\")):\n",
    "    \"\"\"اولین نام‌فایلِ شناخته‌شده برای canonical_name را پیدا می‌کند.\"\"\"\n",
    "    for pat in FILE_ALIASES[canonical_name]:\n",
    "        hit = find_one(pat, roots)\n",
    "        if hit:\n",
    "            return hit, pat\n",
    "    return None, None\n",
    "\n",
    "\n",
    "# ---- مسیرِ داده فقط از newdatasetcafa6؛ هیچ دیتاستِ دیگری (مثل mycafadata) انتخاب نمی‌شود ----\n",
    "_cands = sorted(set(\n",
    "    glob.glob(\"/kaggle/input/**/datacafa6/**/X_train.npy\", recursive=True)\n",
    "    + glob.glob(\"/kaggle/input/**/datacafa6/X_train.npy\", recursive=True)\n",
    "    + glob.glob(\"/kaggle/input/datacafa6*/**/X_train.npy\", recursive=True)))\n",
    "_cands = [p for p in _cands if \"datacafa6\" in p]\n",
    "if not _cands:\n",
    "    _all = sorted(glob.glob(\"/kaggle/input/**/*.npy\", recursive=True))[:20]\n",
    "    raise FileNotFoundError(\n",
    "        \"❌ دیتاستِ newdatasetcafa6 وصل نیست. از Add Input آن را اضافه کنید \"\n",
    "        \"(farzadmajidi/newdatasetcafa6).\\n   فایل‌های npy موجود:\\n   \" + \"\\n   \".join(_all))\n",
    "_xtr, _xtr_pat = _cands[0], \"X_train.npy\"\n",
    "DATA_DIR = os.path.dirname(_xtr)\n",
    "print(f\"✅ DATA_DIR = {DATA_DIR}  (newdatasetcafa6)\")\n",
    "if glob.glob(\"/kaggle/input/**/datacafa6\", recursive=True):\n",
    "    print(\"ℹ️ mycafadata هم وصل است ولی نادیده گرفته می‌شود.\")\n",
    "\n",
    "def _load(canonical_name):\n",
    "    for pat in FILE_ALIASES[canonical_name][:1]:   # فقط نام‌های newdatasetcafa6\n",
    "        p = os.path.join(DATA_DIR, pat)\n",
    "        if os.path.exists(p):\n",
    "            return np.load(p)\n",
    "    return None\n",
    "\n",
    "\n",
    "Xtr_np = _load(\"Xtr.npy\").astype(np.float32)\n",
    "Ytr_np = _load(\"Ytr.npy\").astype(np.float32)\n",
    "Xva_np = _load(\"Xva.npy\").astype(np.float32)\n",
    "Yva_np = _load(\"Yva.npy\").astype(np.float32)\n",
    "Xte_np = _load(\"Xte.npy\")\n",
    "Yte_np = _load(\"Yte.npy\")\n",
    "\n",
    "for _nm, _arr in ((\"Xtr\", Xtr_np), (\"Ytr\", Ytr_np), (\"Xva\", Xva_np), (\"Yva\", Yva_np)):\n",
    "    assert _arr is not None, (\n",
    "        f\"❌ {_nm} کنار Xtr.npy/X_train.npy پیدا نشد — DATA_DIR = {DATA_DIR} را دستی بررسی کن.\"\n",
    "    )\n",
    "\n",
    "# terms.txt (هر دو قرارداد از همین نام استفاده می‌کنند)\n",
    "terms_path = os.path.join(DATA_DIR, \"terms.txt\")\n",
    "TERMS = open(terms_path).read().splitlines() if os.path.exists(terms_path) else \\\n",
    "        [f\"GO:{i}\" for i in range(Ytr_np.shape[1])]\n",
    "\n",
    "if Xte_np is None:\n",
    "    Xte_np, Yte_np = Xva_np, Yva_np\n",
    "Xte_np = Xte_np.astype(np.float32)\n",
    "Yte_np = Yte_np.astype(np.float32)\n",
    "\n",
    "# آیا مجموعهٔ آزمون همان اعتبارسنجی است؟\n",
    "TEST_IS_VAL = (Xte_np.shape == Xva_np.shape) and bool(np.allclose(Xte_np[:50], Xva_np[:50]))\n",
    "\n",
    "print(f\"Xtr: {Xtr_np.shape} | Xva: {Xva_np.shape} | Xte: {Xte_np.shape}\")\n",
    "print(f\"تعداد اصطلاح GO: {len(TERMS)} | چگالی برچسب آموزش: {Ytr_np.mean():.4f}\")\n",
    "if TEST_IS_VAL:\n",
    "    print(\"⚠️ مجموعهٔ آزمون با اعتبارسنجی یکسان است.\")\n",
    "\n",
    "\n",
    "# ---------------- نمونه‌گیری اختیاری برای اجرای سریع ----------------\n",
    "N_TRAIN_MAX = None      # مثلاً 12000 برای اجرای سریع؛ None یعنی همهٔ داده\n",
    "if N_TRAIN_MAX is not None and N_TRAIN_MAX < len(Xtr_np):\n",
    "    rng = np.random.default_rng(GLOBAL_SEED)\n",
    "    sel = rng.choice(len(Xtr_np), size=N_TRAIN_MAX, replace=False)\n",
    "    Xtr_np, Ytr_np = Xtr_np[sel], Ytr_np[sel]\n",
    "    print(f\"زیرمجموعهٔ آموزش: {Xtr_np.shape}\")\n",
    "\n",
    "Xtr = torch.tensor(Xtr_np)\n",
    "Ytr = torch.tensor(Ytr_np)\n",
    "Xva = torch.tensor(Xva_np)\n",
    "Xte = torch.tensor(Xte_np)\n",
    "\n",
    "IN_DIM = Xtr.shape[1]\n",
    "N_LABELS = Ytr.shape[1]\n",
    "print(f\"IN_DIM = {IN_DIM} | N_LABELS = {N_LABELS}\")\n",
    "\n",
    "# ---------------- نگهبان نهایی دیتاست ----------------\n",
    "# در Kaggle نام mount شده ممکن است با نام Dataset اصلی یکسان نباشد.\n",
    "# بنابراین صحت دیتاست را بر اساس مسیر datacafa6 و ابعاد مورد انتظار بررسی می‌کنیم.\n",
    "\n",
    "assert os.path.basename(os.path.normpath(DATA_DIR)) == \"datacafa6\", (\n",
    "    f\"❌ مسیر غیرمنتظره دیتاست: {DATA_DIR}\"\n",
    ")\n",
    "\n",
    "assert (\n",
    "    IN_DIM == 480\n",
    "    and N_LABELS == 180\n",
    "    and len(Xtr_np) == 28000\n",
    "    and len(Xva_np) == 6000\n",
    "    and len(Xte_np) == 6000\n",
    "), (\n",
    "    \"❌ ابعاد دیتاست با ابعادِ مورد انتظار مطابقت ندارد.\\n\"\n",
    "    f\"DATA_DIR = {DATA_DIR}\\n\"\n",
    "    f\"IN_DIM = {IN_DIM}, N_LABELS = {N_LABELS}\\n\"\n",
    "    f\"Train = {len(Xtr_np)}, Val = {len(Xva_np)}, Test = {len(Xte_np)}\"\n",
    ")\n",
    "\n",
    "print(\"✅ نگهبان دیتاست: datacafa6 تأیید شد.\")\n",
    "print(f\"   مسیر: {DATA_DIR}\")\n",
    "print(f\"   Features: {IN_DIM}\")\n",
    "print(f\"   GO Terms: {N_LABELS}\")\n",
    "print(f\"   Train / Val / Test: {len(Xtr_np)} / {len(Xva_np)} / {len(Xte_np)}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ac9a089",
   "metadata": {},
   "source": [
    "## 3. GO aspect mapping"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "81bd7971",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:45:43.172709Z",
     "iopub.status.busy": "2026-09-27T15:45:43.172004Z",
     "iopub.status.idle": "2026-09-27T15:45:44.899292Z",
     "shell.execute_reply": "2026-09-27T15:45:44.898587Z",
     "shell.execute_reply.started": "2026-09-27T15:45:43.172678Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "   منبع «جدول حاشیه‌نویسی train_terms.tsv» → پوشش 135/180\n",
      "   45 اصطلاح هنوز جنبه ندارند؛ تلاش برای تکمیل از منابعِ دیگر...\n",
      "   + 45 اصطلاح از «آنتولوژی محلی go-basic.obo» تکمیل شد\n",
      "\n",
      "✅ منبع جنبهٔ پایه: جدول حاشیه‌نویسی train_terms.tsv\n",
      "جنبه       اصطلاح   پروتئینِ محک   میانگین IC    چگالی برچسب\n",
      "------------------------------------------------------------\n",
      "MF             60           4094            —        0.05438\n",
      "BP             60           4298            —        0.14094\n",
      "CC             60           4365            —        0.12225\n",
      "------------------------------------------------------------\n",
      "💾 نگاشت هم‌ترتیب ذخیره شد: /kaggle/working/aspects.txt  (به دیتاست mycafadata اضافه‌اش کن)\n",
      "\n",
      "✅ نگاشتِ جنبه کامل (180/180): MFO=60, BPO=60, CCO=60\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# GO term -> aspect (MFO / BPO / CCO) mapping, from the annotation table with\n",
    "# go-basic.obo as fallback, so that every metric is reported per aspect.\n",
    "# ============================================================================\n",
    "\n",
    "import re, csv, urllib.request\n",
    "\n",
    "ASPECTS   = (\"MFO\", \"BPO\", \"CCO\")\n",
    "ASPECT_FA = {\"MFO\": \"کارکرد مولکولی\", \"BPO\": \"فرایند زیستی\", \"CCO\": \"جزء سلولی\"}\n",
    "ASPECT_EN = {\"MFO\": \"MF\", \"BPO\": \"BP\", \"CCO\": \"CC\"}\n",
    "\n",
    "_CANON = {\n",
    "    \"mfo\": \"MFO\", \"mf\": \"MFO\", \"f\": \"MFO\", \"molecular_function\": \"MFO\", \"molecular function\": \"MFO\",\n",
    "    \"bpo\": \"BPO\", \"bp\": \"BPO\", \"p\": \"BPO\", \"biological_process\": \"BPO\", \"biological process\": \"BPO\",\n",
    "    \"cco\": \"CCO\", \"cc\": \"CCO\", \"c\": \"CCO\", \"cellular_component\": \"CCO\", \"cellular component\": \"CCO\",\n",
    "}\n",
    "\n",
    "def _canon(v):\n",
    "    return _CANON.get(str(v).strip().strip('\"').lower())\n",
    "\n",
    "def _norm_term(t):\n",
    "    t = str(t).strip().strip('\"')\n",
    "    if t and not t.upper().startswith(\"GO:\") and t.replace(\"_\", \"\").isdigit():\n",
    "        t = \"GO:\" + t.zfill(7)\n",
    "    return t.upper().replace(\"GO_\", \"GO:\")\n",
    "\n",
    "TERMS_N = [_norm_term(t) for t in TERMS]\n",
    "\n",
    "WORK = \"/kaggle/working\" if os.path.isdir(\"/kaggle/working\") else \".\"\n",
    "OBO_CACHE = os.path.join(WORK, \"go-basic.obo\")\n",
    "# مسیر دستی: اگر فایل نگاشت را جای دیگری داری، اینجا بگذار (وگرنه None)\n",
    "MANUAL_ASPECT_PATH = None\n",
    "\n",
    "\n",
    "def _from_manual():\n",
    "    \"\"\"۰) مسیر دستیِ صریح، اگر MANUAL_ASPECT_PATH مقدار داشته باشد.\"\"\"\n",
    "    p = MANUAL_ASPECT_PATH\n",
    "    if not p or not os.path.exists(p):\n",
    "        return None, None\n",
    "    if p.endswith(\".obo\"):\n",
    "        with open(p, encoding=\"utf-8\", errors=\"ignore\") as fh:\n",
    "            m = _parse_obo(fh)\n",
    "    elif p.endswith(\".json\"):\n",
    "        m = {_norm_term(k): _canon(v) for k, v in json.load(open(p, encoding=\"utf-8\")).items()}\n",
    "    else:\n",
    "        m = {}\n",
    "        for ln in open(p, encoding=\"utf-8\"):\n",
    "            parts = re.split(r\"[\\t,;]\", ln.strip())\n",
    "            if len(parts) >= 2 and _canon(parts[1]):\n",
    "                m[_norm_term(parts[0])] = _canon(parts[1])\n",
    "    return (m, f\"مسیر دستی {os.path.basename(p)}\") if m else (None, None)\n",
    "\n",
    "\n",
    "def _from_sidecar():\n",
    "    \"\"\"۱) فایل کناریِ اختصاصی: aspects.txt (هم‌ترتیب با terms.txt) یا نگاشت دوستونی/JSON.\"\"\"\n",
    "    for pat in (\"term_aspects.json\", \"aspects.json\", \"term_aspects.txt\", \"term_aspect.txt\",\n",
    "                \"aspects.txt\", \"aspect.txt\", \"terms_aspect.tsv\", \"term_aspects.tsv\"):\n",
    "        p = find_one(pat, roots=(DATA_DIR,))\n",
    "        if not p:\n",
    "            continue\n",
    "        if p.endswith(\".json\"):\n",
    "            obj = json.load(open(p, encoding=\"utf-8\"))\n",
    "            m = {_norm_term(k): _canon(v) for k, v in obj.items()}\n",
    "            if any(m.values()):\n",
    "                return m, f\"فایل کناری {os.path.basename(p)}\"\n",
    "            continue\n",
    "        rows = [ln.rstrip(\"\\n\") for ln in open(p, encoding=\"utf-8\") if ln.strip()]\n",
    "        first = re.split(r\"[\\t,;]\", rows[0])\n",
    "        if len(first) >= 2:                                   # نگاشت دوستونی\n",
    "            m = {}\n",
    "            for ln in rows:\n",
    "                parts = re.split(r\"[\\t,;]\", ln)\n",
    "                a = _canon(parts[1])\n",
    "                if a:\n",
    "                    m[_norm_term(parts[0])] = a\n",
    "            if m:\n",
    "                return m, f\"فایل کناری {os.path.basename(p)}\"\n",
    "        elif len(rows) == len(TERMS_N):                       # فقط جنبه، هم‌ترتیب\n",
    "            vals = [_canon(r) for r in rows]\n",
    "            if all(vals):\n",
    "                return dict(zip(TERMS_N, vals)), f\"فایل کناری {os.path.basename(p)} (هم‌ترتیب)\"\n",
    "    return None, None\n",
    "\n",
    "\n",
    "def _from_terms_file():\n",
    "    \"\"\"۲) خودِ terms.txt چندستونی باشد.\"\"\"\n",
    "    if not os.path.exists(terms_path):\n",
    "        return None, None\n",
    "    rows = [ln.rstrip(\"\\n\") for ln in open(terms_path, encoding=\"utf-8\") if ln.strip()]\n",
    "    m = {}\n",
    "    for ln in rows:\n",
    "        parts = re.split(r\"[\\t,;]\", ln)\n",
    "        if len(parts) < 2:\n",
    "            return None, None\n",
    "        for q in parts[1:]:\n",
    "            a = _canon(q)\n",
    "            if a:\n",
    "                m[_norm_term(parts[0])] = a\n",
    "                break\n",
    "    return (m, \"ستون جنبه در terms.txt\") if m else (None, None)\n",
    "\n",
    "\n",
    "def _from_annotation_table():\n",
    "    \"\"\"۳) قالب Kaggle: train_terms.tsv با ستون‌های EntryID / term / aspect.\"\"\"\n",
    "    cands = []\n",
    "    for pat in (\"train_terms.tsv\", \"*_terms.tsv\", \"*terms*.tsv\", \"*terms*.csv\"):\n",
    "        for root in (DATA_DIR, \"/kaggle/input\", \"/content\", \".\"):\n",
    "            cands += sorted(glob.glob(f\"{root}/**/{pat}\", recursive=True))\n",
    "    seen, m, src = set(), {}, None\n",
    "    for p in cands:\n",
    "        if p in seen or os.path.basename(p) == \"terms.txt\":\n",
    "            continue\n",
    "        seen.add(p)\n",
    "        try:\n",
    "            with open(p, encoding=\"utf-8\", errors=\"ignore\") as fh:\n",
    "                head = fh.readline()\n",
    "                delim = \"\\t\" if \"\\t\" in head else \",\"\n",
    "                cols = [c.strip().lower() for c in head.rstrip(\"\\n\").split(delim)]\n",
    "                if not any(\"aspect\" in c or \"ontolog\" in c or \"namespace\" in c for c in cols):\n",
    "                    continue\n",
    "                ci_t = next((i for i, c in enumerate(cols) if c in (\"term\", \"go\", \"go_id\", \"goid\", \"term_id\")), None)\n",
    "                ci_a = next((i for i, c in enumerate(cols) if \"aspect\" in c or \"ontolog\" in c or \"namespace\" in c), None)\n",
    "                if ci_t is None or ci_a is None:\n",
    "                    continue\n",
    "                for ln in fh:\n",
    "                    parts = ln.rstrip(\"\\n\").split(delim)\n",
    "                    if len(parts) <= max(ci_t, ci_a):\n",
    "                        continue\n",
    "                    a = _canon(parts[ci_a])\n",
    "                    if a:\n",
    "                        m[_norm_term(parts[ci_t])] = a\n",
    "            if m:\n",
    "                src = f\"جدول حاشیه‌نویسی {os.path.basename(p)}\"\n",
    "                break\n",
    "        except Exception as e:\n",
    "            print(\"   (رد شد:\", os.path.basename(p), \"→\", e, \")\")\n",
    "    return (m, src) if m else (None, None)\n",
    "\n",
    "\n",
    "def _parse_obo(lines):\n",
    "    \"\"\"۴/۵) استخراج namespace از فایل OBO، همراه با alt_id ها.\"\"\"\n",
    "    m, cur, ns, alts, in_term = {}, None, None, [], False\n",
    "\n",
    "    def flush():\n",
    "        if cur and ns:\n",
    "            m[cur] = ns\n",
    "            for a in alts:\n",
    "                m.setdefault(a, ns)\n",
    "\n",
    "    for raw in lines:\n",
    "        ln = raw.strip()\n",
    "        if ln.startswith(\"[\"):\n",
    "            flush()\n",
    "            cur, ns, alts = None, None, []\n",
    "            in_term = (ln == \"[Term]\")\n",
    "        elif not in_term:\n",
    "            continue\n",
    "        elif ln.startswith(\"id: \"):\n",
    "            cur = _norm_term(ln[4:])\n",
    "        elif ln.startswith(\"alt_id: \"):\n",
    "            alts.append(_norm_term(ln[8:]))\n",
    "        elif ln.startswith(\"namespace: \"):\n",
    "            ns = _canon(ln[11:])\n",
    "    flush()\n",
    "    return m\n",
    "\n",
    "\n",
    "def _from_obo_local():\n",
    "    if os.path.exists(OBO_CACHE):                       # نسخهٔ کش‌شده\n",
    "        with open(OBO_CACHE, encoding=\"utf-8\", errors=\"ignore\") as fh:\n",
    "            m = _parse_obo(fh)\n",
    "        if m:\n",
    "            return m, \"آنتولوژی ذخیره‌شده در working\"\n",
    "    for pat in (\"go-basic.obo\", \"*.obo\", \"*.obo.gz\"):\n",
    "        p = find_one(pat, roots=(DATA_DIR, \"/kaggle/input\", \"/content\", \".\"))\n",
    "        if p:\n",
    "            with open(p, encoding=\"utf-8\", errors=\"ignore\") as fh:\n",
    "                m = _parse_obo(fh)\n",
    "            if m:\n",
    "                return m, f\"آنتولوژی محلی {os.path.basename(p)}\"\n",
    "    return None, None\n",
    "\n",
    "\n",
    "def _from_obo_download():\n",
    "    \"\"\"۵) دانلود آنتولوژی.\n",
    "\n",
    "    نکتهٔ عملی: نسخهٔ پیشین با HTTP 403 شکست می‌خورد. علت، بستهبودن اینترنت نبود —\n",
    "    اینترنت باز بود — بلکه دو چیز بود: نشانی purl روی http که تغییرمسیر می‌خورد،\n",
    "    و User-Agent پیش‌فرض urllib که فایروال مقصد آن را رد می‌کند. اینجا مستقیم\n",
    "    به میزبان اصلیِ ژن‌آنتولوژی روی https درخواست می‌رود، با یک User-Agent معمولی.\n",
    "    \"\"\"\n",
    "    urls = [\n",
    "        \"https://current.geneontology.org/ontology/go-basic.obo\",\n",
    "        \"https://release.geneontology.org/2024-06-17/ontology/go-basic.obo\",\n",
    "        \"https://purl.obolibrary.org/obo/go/go-basic.obo\",\n",
    "    ]\n",
    "    hdr = {\"User-Agent\": \"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 \"\n",
    "                         \"(KHTML, like Gecko) Chrome/124.0 Safari/537.36\",\n",
    "           \"Accept\": \"text/plain,*/*\"}\n",
    "    for url in urls:\n",
    "        try:\n",
    "            req = urllib.request.Request(url, headers=hdr)\n",
    "            with urllib.request.urlopen(req, timeout=120) as fh:\n",
    "                raw = fh.read()\n",
    "            if raw[:2] == b\"\\x1f\\x8b\":                       # فایل gz\n",
    "                import gzip\n",
    "                raw = gzip.decompress(raw)\n",
    "            text = raw.decode(\"utf-8\", \"ignore\")\n",
    "            if \"[Term]\" not in text:\n",
    "                print(\"   (پاسخ\", url, \"فایل OBO نبود)\")\n",
    "                continue\n",
    "            try:\n",
    "                open(OBO_CACHE, \"w\", encoding=\"utf-8\").write(text)\n",
    "                print(\"   (نسخهٔ محلی ذخیره شد:\", OBO_CACHE, \")\")\n",
    "            except Exception:\n",
    "                pass\n",
    "            m = _parse_obo(text.splitlines())\n",
    "            if m:\n",
    "                return m, f\"دانلود {url.split('/')[2]}\"\n",
    "        except Exception as e:\n",
    "            print(\"   (دانلود ناموفق از\", url.split(\"/\")[2], \"→\", e, \")\")\n",
    "    return None, None\n",
    "\n",
    "\n",
    "# ---------------- مرحلهٔ ۱: انتخابِ منبعِ پایه (اولین با پوشش ≥۵۰٪) ----------------\n",
    "_SOURCES = (_from_manual, _from_sidecar, _from_terms_file, _from_annotation_table,\n",
    "            _from_obo_local, _from_obo_download)\n",
    "\n",
    "TERM_ASPECT, ASPECT_SOURCE = None, None\n",
    "_tried = []\n",
    "for _fn in _SOURCES:\n",
    "    _m, _src = _fn()\n",
    "    _tried.append(_fn)\n",
    "    if _m:\n",
    "        _cov = sum(1 for t in TERMS_N if _m.get(t))\n",
    "        print(f\"   منبع «{_src}» → پوشش {_cov}/{len(TERMS_N)}\")\n",
    "        if _cov >= 0.5 * len(TERMS_N):\n",
    "            TERM_ASPECT, ASPECT_SOURCE = dict(_m), _src\n",
    "            break\n",
    "\n",
    "assert TERM_ASPECT is not None, (\n",
    "    \"❌ نگاشت «اصطلاح GO → جنبه» پیدا نشد.\\n\"\n",
    "    \"   ساده‌ترین راه‌حل‌ها، به ترتیب سهولت:\\n\"\n",
    "    \"   (۱) در پنل Add Input نوت‌بوک، دیتاست رسمی مسابقه را اضافه کن:\\n\"\n",
    "    \"       cafa-5-protein-function-prediction  →  شامل Train/go-basic.obo و\\n\"\n",
    "    \"       Train/train_terms.tsv (ستون aspect). همین سلول خودکار پیدایش می‌کند.\\n\"\n",
    "    \"   (۲) اینترنت نوت‌بوک را روشن کن (Settings → Internet: On) تا go-basic.obo\\n\"\n",
    "    \"       مستقیم از current.geneontology.org دانلود شود.\\n\"\n",
    "    \"   (۳) یا فایل نگاشت را کنار Xtr.npy در دیتاست mycafadata بگذار:\\n\"\n",
    "    \"       aspects.txt هم‌ترتیب با terms.txt، یا دوستونی  GO:0003674<TAB>MFO\\n\"\n",
    "    \"   (۴) یا MANUAL_ASPECT_PATH را در بالای همین سلول مقداردهی کن.\\n\"\n",
    "    \"   بدون این نگاشت، تفکیک MF/BP/CC ممکن نیست و حدس‌زدن مجاز نیست.\"\n",
    ")\n",
    "\n",
    "# ---------------- مرحلهٔ ۲: تکمیلِ باقی‌مانده‌ها از منابعِ دیگر ----------------\n",
    "# منبعِ پایه معمولاً کامل نیست (مثلاً train_terms.tsv فقط اصطلاحاتی را دارد که\n",
    "# واقعاً در train حاشیه‌نویسی شده‌اند). اصطلاحاتی که هنوز جنبه ندارند را با\n",
    "# بقیهٔ منابع (به‌ویژه OBO، که alt_id های منسوخ را هم پوشش می‌دهد) پر می‌کنیم.\n",
    "_missing = [t for t in TERMS_N if t not in TERM_ASPECT]\n",
    "if _missing:\n",
    "    print(f\"   {len(_missing)} اصطلاح هنوز جنبه ندارند؛ تلاش برای تکمیل از منابعِ دیگر...\")\n",
    "    for _fn in _SOURCES:\n",
    "        if not _missing:\n",
    "            break\n",
    "        if _fn in _tried and _fn not in (_from_obo_local, _from_obo_download):\n",
    "            # منابعی که قبلاً در مرحلهٔ ۱ امتحان شدند و رد شدند را دوباره صدا نزن،\n",
    "            # به‌جز OBO که ارزشِ تلاشِ دوباره دارد (ممکن است در مرحلهٔ ۱ اصلاً\n",
    "            # نوبتش نرسیده باشد یا این بار محلی/دانلودی موجود باشد).\n",
    "            continue\n",
    "        _m2, _src2 = _fn()\n",
    "        if not _m2:\n",
    "            continue\n",
    "        _filled = 0\n",
    "        for t in _missing:\n",
    "            if t in _m2:\n",
    "                TERM_ASPECT[t] = _m2[t]\n",
    "                _filled += 1\n",
    "        if _filled:\n",
    "            print(f\"   + {_filled} اصطلاح از «{_src2}» تکمیل شد\")\n",
    "            _missing = [t for t in TERMS_N if t not in TERM_ASPECT]\n",
    "\n",
    "ASPECT = np.array([TERM_ASPECT.get(t, \"UNK\") for t in TERMS_N], dtype=object)\n",
    "ASPECT_COLS = {a: np.where(ASPECT == a)[0] for a in ASPECTS}\n",
    "N_UNK = int((ASPECT == \"UNK\").sum())\n",
    "\n",
    "print(f\"\\n✅ منبع جنبهٔ پایه: {ASPECT_SOURCE}\")\n",
    "print(f\"{'جنبه':<8}{'اصطلاح':>9}{'پروتئینِ محک':>15}{'میانگین IC':>13}{'چگالی برچسب':>15}\")\n",
    "print(\"-\" * 60)\n",
    "_Yte_tmp = Yte_np.astype(bool)\n",
    "for a in ASPECTS:\n",
    "    idx = ASPECT_COLS[a]\n",
    "    if len(idx) == 0:\n",
    "        print(f\"{ASPECT_EN[a]:<8}{0:>9}{'—':>15}{'—':>13}{'—':>15}\")\n",
    "        continue\n",
    "    bench = int((_Yte_tmp[:, idx].sum(axis=1) > 0).sum())\n",
    "    print(f\"{ASPECT_EN[a]:<8}{len(idx):>9}{bench:>15}{'—':>13}{Ytr_np[:, idx].mean():>15.5f}\")\n",
    "print(\"-\" * 60)\n",
    "if N_UNK:\n",
    "    print(f\"⚠️ {N_UNK} اصطلاح هنوز جنبهٔ نامشخص دارند (احتمالاً منسوخ یا در هیچ منبعی نبودند) \"\n",
    "          \"و از ارزیابیِ تفکیکی کنار می‌روند.\")\n",
    "\n",
    "# ---------------- ذخیرهٔ نگاشت برای اجراهای بعدی ----------------\n",
    "# یک فایل کوچک هم‌ترتیب با terms.txt در working نوشته می‌شود؛ آن را به دیتاست\n",
    "# mycafadata اضافه کن تا دفعهٔ بعد نه اینترنت لازم باشد نه دیتاست دوم.\n",
    "try:\n",
    "    _ap = os.path.join(WORK, \"aspects.txt\")\n",
    "    with open(_ap, \"w\", encoding=\"utf-8\") as _fh:\n",
    "        _fh.write(\"\\n\".join(str(a) for a in ASPECT))\n",
    "    print(f\"💾 نگاشت هم‌ترتیب ذخیره شد: {_ap}  (به دیتاست mycafadata اضافه‌اش کن)\")\n",
    "except Exception as _e:\n",
    "    print(\"   (ذخیرهٔ aspects.txt ناموفق:\", _e, \")\")\n",
    "\n",
    "\n",
    "# ---- نگهبانِ نگاشتِ جنبه: هشدار در صورت پوشش ناقص، نه توقفِ کامل ----\n",
    "# نکته: نگاشتِ ۱۰۰٪ (۱۸۰/۱۸۰) فقط وقتی تضمین می‌شود که term_aspects.json از\n",
    "# newdatasetcafa6 وصل باشد یا OBO کامل در دسترس باشد. اگر این‌طور نیست،\n",
    "# متوقف‌کردنِ کل سلول برای چند اصطلاحِ ناقص (که خودشان به‌صراحت UNK علامت\n",
    "# خورده و از ارزیابیِ تفکیکی کنار گذاشته می‌شوند) موجه نیست؛ به‌جایش فقط\n",
    "# هشدار بده تا کاربر آگاهانه تصمیم بگیرد ادامه دهد یا منبعِ کامل‌تری وصل کند.\n",
    "_n_res = sum(len(ASPECT_COLS[a]) for a in ASPECTS)\n",
    "if _n_res < N_LABELS:\n",
    "    print(f\"\\n⚠️ فقط {_n_res} از {N_LABELS} اصطلاح جنبه گرفتند \"\n",
    "          f\"({N_LABELS - _n_res} اصطلاح UNK).\\n\"\n",
    "          \"   برای نگاشتِ کامل، یکی از این‌ها را انجام بده:\\n\"\n",
    "          \"   • term_aspects.json از دیتاست newdatasetcafa6 را به Input اضافه کن، یا\\n\"\n",
    "          \"   • اینترنت نوت‌بوک را روشن کن (Settings → Internet: On) تا go-basic.obo\\n\"\n",
    "          \"     دانلود و alt_idهای منسوخ هم پوشش داده شوند.\\n\"\n",
    "          \"   ارزیابیِ کلی (سطرهای غیر-UNK) همچنان معتبر است؛ فقط اصطلاحاتِ UNK از\\n\"\n",
    "          \"   تفکیکِ MF/BP/CC کنار گذاشته می‌شوند.\")\n",
    "else:\n",
    "    print(f\"\\n✅ نگاشتِ جنبه کامل ({_n_res}/{N_LABELS}): \"\n",
    "          + \", \".join(f\"{a}={len(ASPECT_COLS[a])}\" for a in ASPECTS))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "820c4a46",
   "metadata": {},
   "source": [
    "## 4. Variational quantum circuit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "d4b16ca3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:45:53.293575Z",
     "iopub.status.busy": "2026-09-27T15:45:53.292918Z",
     "iopub.status.idle": "2026-09-27T15:45:54.003004Z",
     "shell.execute_reply": "2026-09-27T15:45:54.002243Z",
     "shell.execute_reply.started": "2026-09-27T15:45:53.293545Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "هم‌ارزیِ موتور سریع و PennyLane [linear/CZ]: بیشینهٔ اختلاف = 4.17e-07\n",
      "هم‌ارزیِ موتور سریع و PennyLane [tree/CZ]: بیشینهٔ اختلاف = 6.56e-07\n",
      "هم‌ارزیِ موتور سریع و PennyLane [ring/CNOT]: بیشینهٔ اختلاف = 2.98e-07\n",
      "\n",
      "گزارش پارامتر مدار:\n",
      "{\n",
      "  \"n_qubits\": 10,\n",
      "  \"n_layers\": 3,\n",
      "  \"bases\": [\n",
      "    \"Z\",\n",
      "    \"X\",\n",
      "    \"Y\"\n",
      "  ],\n",
      "  \"topology\": \"linear\",\n",
      "  \"two_qubit_gate\": \"CZ\",\n",
      "  \"two_qubit_gate_count\": 27,\n",
      "  \"input_angles\": 20,\n",
      "  \"observables\": 55,\n",
      "  \"quantum_features\": 165,\n",
      "  \"trainable_pqc_parameters\": 90,\n",
      "  \"backend\": \"fast\"\n",
      "}\n",
      "شکل خروجی: (8, 165) | گرادیان θ: (3, 10, 3)\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Variational quantum circuit: 10 qubits, RY.RZ angle encoding, 3 layers of\n",
    "# [CZ chain -> RX.RY.RZ], one- and two-body read-out in the Z, X and Y bases\n",
    "# (165 features); state-vector simulator verified against PennyLane.\n",
    "# ============================================================================\n",
    "\n",
    "def diagonal_readout_matrix(n_qubits: int, max_body: int = 2, dtype=torch.float32):\n",
    "    \"\"\"ماتریس خوانش: ⟨Z_i⟩ و ⟨Z_i Z_j⟩ به‌صورت ترکیب خطی احتمال حالت‌ها (بخش ۸-۲).\"\"\"\n",
    "    dim = 1 << n_qubits\n",
    "    idx = np.arange(dim)\n",
    "    bits = (idx[:, None] >> np.arange(n_qubits - 1, -1, -1)) & 1\n",
    "    signs = 1.0 - 2.0 * bits                      # |0⟩ → +1 ، |1⟩ → −1\n",
    "\n",
    "    cols, labels = [], []\n",
    "    for i in range(n_qubits):\n",
    "        cols.append(signs[:, i]); labels.append((i,))\n",
    "    if max_body >= 2:\n",
    "        for i, j in itertools.combinations(range(n_qubits), 2):\n",
    "            cols.append(signs[:, i] * signs[:, j]); labels.append((i, j))\n",
    "    return torch.tensor(np.stack(cols, axis=1), dtype=dtype), labels\n",
    "\n",
    "\n",
    "def hardware_graph(n_qubits: int, topology: str = \"linear\"):\n",
    "    \"\"\"گرافِ دروازه‌های دوکیوبیتیِ ثابت.\n",
    "\n",
    "    linear : همسایگیِ نزدیک — کم‌هزینه‌ترین الگو پس از ترنسپایل\n",
    "    tree   : زنجیره + دو «پل»، به سبک سامانهٔ Aspen در شکل ۸-۳ و ۸-۴\n",
    "    ring   : حلقهٔ کامل\n",
    "    \"\"\"\n",
    "    if topology == \"linear\":\n",
    "        return [(i, i + 1) for i in range(n_qubits - 1)]\n",
    "    if topology == \"ring\":\n",
    "        return [(i, (i + 1) % n_qubits) for i in range(n_qubits)]\n",
    "    if topology == \"tree\":\n",
    "        half = n_qubits // 2\n",
    "        edges = [(i, i + 1) for i in range(n_qubits - 1)] + [(0, half), (half - 1, n_qubits - 1)]\n",
    "        return [e for e in edges if e[0] != e[1]]\n",
    "    raise ValueError(f\"topology ناشناخته: {topology}\")\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# موتور سریع: شبیه‌سازِ بردارِ حالت با torch\n",
    "# ------------------------------------------------------------\n",
    "CDTYPE = torch.complex64\n",
    "\n",
    "\n",
    "class TorchStatevector:\n",
    "    \"\"\"شبیه‌سازِ بردارِ حالت مشتق‌پذیر؛ فقط دروازه‌هایی که این مدار نیاز دارد.\"\"\"\n",
    "\n",
    "    def __init__(self, n_qubits, edges, two_qubit_gate=\"CZ\"):\n",
    "        self.n = n_qubits\n",
    "        self.gate = two_qubit_gate\n",
    "        idx = torch.arange(1 << n_qubits)\n",
    "        self.signs, self.perms = [], []\n",
    "        for (i, j) in edges:\n",
    "            if two_qubit_gate == \"CZ\":                      # قطری → ضرب علامت\n",
    "                bi = (idx >> (n_qubits - 1 - i)) & 1\n",
    "                bj = (idx >> (n_qubits - 1 - j)) & 1\n",
    "                self.signs.append(torch.where((bi & bj).bool(), -1.0, 1.0).to(CDTYPE))\n",
    "            else:                                           # CNOT → جایگشت\n",
    "                bi = (idx >> (n_qubits - 1 - i)) & 1\n",
    "                self.perms.append(idx ^ (bi << (n_qubits - 1 - j)))\n",
    "\n",
    "    # ---- دروازه‌های تک‌کیوبیتی ----\n",
    "    def _split(self, psi, w):\n",
    "        B = psi.shape[0]\n",
    "        return psi.reshape(B, 1 << w, 2, 1 << (self.n - w - 1)), B\n",
    "\n",
    "    def ry(self, psi, t, w):\n",
    "        x, B = self._split(psi, w)\n",
    "        c = torch.cos(t / 2).to(CDTYPE)[:, None, None]\n",
    "        s = torch.sin(t / 2).to(CDTYPE)[:, None, None]\n",
    "        a, b = x[:, :, 0, :], x[:, :, 1, :]\n",
    "        return torch.stack([c * a - s * b, s * a + c * b], dim=2).reshape(B, -1)\n",
    "\n",
    "    def rz(self, psi, t, w):\n",
    "        x, B = self._split(psi, w)\n",
    "        e = torch.exp(-0.5j * t.to(CDTYPE))[:, None, None]\n",
    "        return torch.stack([e * x[:, :, 0, :], torch.conj(e) * x[:, :, 1, :]], dim=2).reshape(B, -1)\n",
    "\n",
    "    def apply_1q(self, psi, U, w):\n",
    "        x, B = self._split(psi, w)\n",
    "        return torch.einsum('st,bltr->blsr', U, x).reshape(B, -1)\n",
    "\n",
    "    def entangle(self, psi):\n",
    "        if self.gate == \"CZ\":\n",
    "            for s in self.signs:\n",
    "                psi = psi * s.to(psi.device)\n",
    "        else:\n",
    "            for p in self.perms:\n",
    "                psi = psi[:, p.to(psi.device)]\n",
    "        return psi\n",
    "\n",
    "\n",
    "def _RX(t):\n",
    "    c, s = torch.cos(t / 2), torch.sin(t / 2)\n",
    "    return torch.stack([torch.stack([c + 0j, -1j * s]), torch.stack([-1j * s, c + 0j])]).to(CDTYPE)\n",
    "\n",
    "def _RY(t):\n",
    "    c, s = torch.cos(t / 2), torch.sin(t / 2)\n",
    "    return torch.stack([torch.stack([c + 0j, -s + 0j]), torch.stack([s + 0j, c + 0j])]).to(CDTYPE)\n",
    "\n",
    "def _RZ(t):\n",
    "    e = torch.exp(-0.5j * t.to(torch.complex64)); z = torch.zeros((), dtype=torch.complex64)\n",
    "    return torch.stack([torch.stack([e, z]), torch.stack([z, torch.conj(e)])]).to(CDTYPE)\n",
    "\n",
    "\n",
    "# ماتریس‌های تغییرِ پایهٔ اندازه‌گیری\n",
    "_H = (torch.tensor([[1, 1], [1, -1]], dtype=CDTYPE) / np.sqrt(2))\n",
    "_SDAG = torch.tensor([[1, 0], [0, -1j]], dtype=CDTYPE)\n",
    "BASIS_U = {\"Z\": None, \"X\": _H, \"Y\": _H @ _SDAG}\n",
    "\n",
    "\n",
    "class Chapter8QNN(nn.Module):\n",
    "    \"\"\"VQNN: کدگذاری دوتایی + گراف سخت‌افزاری + خوانش سه‌پایه‌ای.\"\"\"\n",
    "\n",
    "    def __init__(self, n_qubits=10, n_layers=3, max_body=2, bases=(\"Z\", \"X\", \"Y\"),\n",
    "                 topology=\"linear\", two_qubit_gate=\"CZ\", backend=\"fast\", use_entangler=True):\n",
    "        super().__init__()\n",
    "        self.use_entangler = use_entangler\n",
    "        self.n_qubits, self.n_layers = int(n_qubits), int(n_layers)\n",
    "        self.bases, self.topology = tuple(bases), topology\n",
    "        self.two_qubit_gate, self.backend = two_qubit_gate, backend\n",
    "        self.edges = hardware_graph(self.n_qubits, topology)\n",
    "\n",
    "        M, labels = diagonal_readout_matrix(self.n_qubits, max_body=max_body)\n",
    "        self.register_buffer(\"readout\", M)\n",
    "        self.obs_labels = labels\n",
    "        self.n_features = M.shape[1] * len(self.bases)\n",
    "        self.n_angles = 2 * self.n_qubits\n",
    "\n",
    "        # مقداردهیِ اولیهٔ کوچک برای پرهیز از «فلات‌های بایر» (بخش ۸-۱)\n",
    "        self.theta = nn.Parameter(0.02 * torch.randn(self.n_layers, self.n_qubits, 3))\n",
    "\n",
    "        self.sim = TorchStatevector(self.n_qubits, self.edges, two_qubit_gate)\n",
    "        self._qnode = self._make_qnode()          # مرجع PennyLane\n",
    "\n",
    "    # ---------------- مرجع PennyLane ----------------\n",
    "    def _make_qnode(self):\n",
    "        n, L, edges = self.n_qubits, self.n_layers, self.edges\n",
    "        G = qml.CZ if self.two_qubit_gate == \"CZ\" else qml.CNOT\n",
    "        dev = qml.device(\"default.qubit\", wires=n)\n",
    "\n",
    "        @qml.qnode(dev, interface=\"torch\", diff_method=\"backprop\")\n",
    "        def qnode(a, b, w):\n",
    "            for l in range(L):\n",
    "                for q in range(n):                       # (۱) کدگذاری دوتایی\n",
    "                    qml.RY(a[..., q], wires=q)\n",
    "                    qml.RZ(b[..., q], wires=q)\n",
    "                if self.use_entangler:                   # (۲) گراف سخت‌افزار\n",
    "                    for (i, j) in edges:\n",
    "                        G(wires=[i, j])\n",
    "                for q in range(n):                       # (۴) چرخش پس از درهم‌تنیدگی\n",
    "                    qml.RX(w[l, q, 0], wires=q)\n",
    "                    qml.RY(w[l, q, 1], wires=q)\n",
    "                    qml.RZ(w[l, q, 2], wires=q)\n",
    "            return qml.state()\n",
    "        return qnode\n",
    "\n",
    "    # ---------------- تحول حالت ----------------\n",
    "    def _state_fast(self, a, b):\n",
    "        B = a.shape[0]\n",
    "        psi = torch.zeros(B, 1 << self.n_qubits, dtype=CDTYPE, device=a.device)\n",
    "        psi[:, 0] = 1.0\n",
    "        th = self.theta\n",
    "        for l in range(self.n_layers):\n",
    "            for w in range(self.n_qubits):\n",
    "                psi = self.sim.ry(psi, a[:, w], w)\n",
    "                psi = self.sim.rz(psi, b[:, w], w)\n",
    "            if self.use_entangler:\n",
    "                psi = self.sim.entangle(psi)\n",
    "            for w in range(self.n_qubits):\n",
    "                psi = self.sim.apply_1q(psi, _RX(th[l, w, 0]), w)\n",
    "                psi = self.sim.apply_1q(psi, _RY(th[l, w, 1]), w)\n",
    "                psi = self.sim.apply_1q(psi, _RZ(th[l, w, 2]), w)\n",
    "        return psi\n",
    "\n",
    "    def _rotate_basis(self, psi, U):\n",
    "        \"\"\"اعمال U روی همهٔ کیوبیت‌ها — تغییر پایهٔ اندازه‌گیری.\"\"\"\n",
    "        if U is None:\n",
    "            return psi\n",
    "        B = psi.shape[0]\n",
    "        x = psi.reshape(B, *([2] * self.n_qubits))\n",
    "        U = U.to(psi.device)\n",
    "        for w in range(self.n_qubits):\n",
    "            x = torch.tensordot(x, U, dims=([w + 1], [1])).movedim(-1, w + 1)\n",
    "        return x.reshape(B, -1)\n",
    "\n",
    "    # ---------------- forward ----------------\n",
    "    def forward(self, angles):\n",
    "        \"\"\"angles: (B, 2n) — نیمهٔ اول زاویهٔ RY، نیمهٔ دوم زاویهٔ RZ.\"\"\"\n",
    "        out_dev = angles.device\n",
    "        z = angles.to(torch.device(\"cpu\"))\n",
    "        a, b = z[:, : self.n_qubits], z[:, self.n_qubits :]\n",
    "\n",
    "        if self.backend == \"fast\":\n",
    "            psi = self._state_fast(a, b)\n",
    "        else:\n",
    "            psi = self._qnode(a, b, self.theta.cpu()).to(CDTYPE)\n",
    "            if psi.dim() == 1:\n",
    "                psi = psi.unsqueeze(0)\n",
    "\n",
    "        R = self.readout.to(torch.device(\"cpu\"))\n",
    "        feats = []\n",
    "        for basis in self.bases:\n",
    "            probs = (self._rotate_basis(psi, BASIS_U[basis]).abs() ** 2).to(R.dtype)\n",
    "            feats.append(probs @ R)\n",
    "        return torch.cat(feats, dim=1).to(out_dev)\n",
    "\n",
    "    # ---------------- گزارش ----------------\n",
    "    def parameter_report(self):\n",
    "        return {\"n_qubits\": self.n_qubits, \"n_layers\": self.n_layers,\n",
    "                \"bases\": list(self.bases), \"topology\": self.topology,\n",
    "                \"two_qubit_gate\": self.two_qubit_gate if self.use_entangler else \"بدون درهم‌تنیدگی\",\n",
    "                \"two_qubit_gate_count\": len(self.edges) * self.n_layers,\n",
    "                \"input_angles\": self.n_angles, \"observables\": len(self.obs_labels),\n",
    "                \"quantum_features\": self.n_features,\n",
    "                \"trainable_pqc_parameters\": self.theta.numel(),\n",
    "                \"backend\": self.backend}\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# معماریِ قدیمی — اختیاری، در نتایجِ گزارش‌شده استفاده نمی‌شود\n",
    "# ============================================================\n",
    "#\n",
    "#   • رمزگذار کلاسیک  d-256-64-10  با LayerNorm و GELU\n",
    "#   • ۱۰ کیوبیت، ۴ لایه، کدگذاری زاویه‌ای «یک ویژگی به‌ازای هر کیوبیت»\n",
    "#   • درهم‌تنیدگی حلقوی با CNOT، بازآپلود داده\n",
    "#   • ۸۰ پارامتر کوانتومی  =  2 × n × L\n",
    "#   • خوانش: فقط ⟨Z_i⟩ تک‌کیوبیتی → گذرگاه ۱۰-بُعدی\n",
    "#   • لایهٔ خروجی: Linear(10, n_labels)\n",
    "#\n",
    "# این مدل را با «همان دستورِ آموزشِ» مدل جدید اجرا می‌کنیم تا تنها\n",
    "# متغیرِ متفاوت، معماری باشد.\n",
    "# ============================================================\n",
    "\n",
    "class LegacyVQC(nn.Module):\n",
    "    \"\"\"مدارِ قدیمی (اختیاری؛ در نتایجِ گزارش‌شده استفاده نمی‌شود): کدگذاری تکی، حلقهٔ CNOT، خوانش ⟨Z⟩.\"\"\"\n",
    "\n",
    "    def __init__(self, n_qubits=10, n_layers=4):\n",
    "        super().__init__()\n",
    "        self.n_qubits, self.n_layers = n_qubits, n_layers\n",
    "        self.n_angles = n_qubits                      # یک ویژگی به‌ازای هر کیوبیت\n",
    "        self.n_features = n_qubits                    # فقط ⟨Z_i⟩\n",
    "        self.edges = hardware_graph(n_qubits, \"ring\")\n",
    "        self.theta = nn.Parameter(0.02 * torch.randn(n_layers, n_qubits, 2))\n",
    "        self.sim = TorchStatevector(n_qubits, self.edges, \"CNOT\")\n",
    "        M, _ = diagonal_readout_matrix(n_qubits, max_body=1)\n",
    "        self.register_buffer(\"readout\", M)\n",
    "\n",
    "    def forward(self, angles):\n",
    "        out_dev = angles.device\n",
    "        a = angles.to(torch.device(\"cpu\"))\n",
    "        B = a.shape[0]\n",
    "        psi = torch.zeros(B, 1 << self.n_qubits, dtype=CDTYPE)\n",
    "        psi[:, 0] = 1.0\n",
    "        for l in range(self.n_layers):\n",
    "            for w in range(self.n_qubits):                # کدگذاری + بازآپلود\n",
    "                psi = self.sim.ry(psi, a[:, w], w)\n",
    "            psi = self.sim.entangle(psi)                  # حلقهٔ CNOT\n",
    "            for w in range(self.n_qubits):                # چرخش پس از درهم‌تنیدگی\n",
    "                psi = self.sim.apply_1q(psi, _RY(self.theta[l, w, 0]), w)\n",
    "                psi = self.sim.apply_1q(psi, _RZ(self.theta[l, w, 1]), w)\n",
    "        R = self.readout\n",
    "        probs = (psi.abs() ** 2).to(R.dtype)\n",
    "        return (probs @ R).to(out_dev)\n",
    "\n",
    "    def parameter_report(self):\n",
    "        return {\"n_qubits\": self.n_qubits, \"n_layers\": self.n_layers,\n",
    "                \"topology\": \"ring\", \"two_qubit_gate\": \"CNOT\",\n",
    "                \"two_qubit_gate_count\": len(self.edges) * self.n_layers,\n",
    "                \"input_angles\": self.n_angles, \"quantum_features\": self.n_features,\n",
    "                \"trainable_pqc_parameters\": self.theta.numel()}\n",
    "\n",
    "\n",
    "class LegacyHybrid(nn.Module):\n",
    "    \"\"\"خط‌لولهٔ قدیمی (اختیاری): رمزگذار d-256-64-10 → VQC → Linear(10, n_labels).\"\"\"\n",
    "\n",
    "    def __init__(self, in_dim, n_labels, n_qubits=10, n_layers=4):\n",
    "        super().__init__()\n",
    "        self.encoder = nn.Sequential(\n",
    "            nn.Linear(in_dim, 256), nn.LayerNorm(256), nn.GELU(),\n",
    "            nn.Linear(256, 64), nn.LayerNorm(64), nn.GELU(),\n",
    "            nn.Linear(64, n_qubits), nn.LayerNorm(n_qubits),\n",
    "        )\n",
    "        self.pqc = LegacyVQC(n_qubits, n_layers)\n",
    "        self.head = nn.Linear(n_qubits, n_labels)      # گذرگاه ۱۰-بُعدی → قید رتبه\n",
    "\n",
    "    def forward(self, x):\n",
    "        z = np.pi * torch.tanh(self.encoder(x))\n",
    "        return self.head(self.pqc(z))\n",
    "\n",
    "    def parameter_report(self):\n",
    "        return {\"encoder\": sum(p.numel() for p in self.encoder.parameters()),\n",
    "                \"pqc\": self.pqc.theta.numel(),\n",
    "                \"head\": sum(p.numel() for p in self.head.parameters()),\n",
    "                \"total\": sum(p.numel() for p in self.parameters())}\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# آزمونِ هم‌ارزیِ موتور سریع با مرجع PennyLane\n",
    "# ============================================================\n",
    "def verify_backends(n_qubits=10, n_layers=3, topology=\"linear\", gate=\"CZ\", n_samples=6):\n",
    "    m = Chapter8QNN(n_qubits=n_qubits, n_layers=n_layers, topology=topology,\n",
    "                    two_qubit_gate=gate, backend=\"fast\")\n",
    "    x = torch.randn(n_samples, m.n_angles)\n",
    "    with torch.no_grad():\n",
    "        m.backend = \"fast\";      f = m(x)\n",
    "        m.backend = \"pennylane\"; p = m(x)\n",
    "    m.backend = \"fast\"\n",
    "    return float((f - p).abs().max())\n",
    "\n",
    "\n",
    "for _topo, _gate in [(\"linear\", \"CZ\"), (\"tree\", \"CZ\"), (\"ring\", \"CNOT\")]:\n",
    "    _d = verify_backends(topology=_topo, gate=_gate)\n",
    "    print(f\"هم‌ارزیِ موتور سریع و PennyLane [{_topo}/{_gate}]: بیشینهٔ اختلاف = {_d:.2e}\")\n",
    "\n",
    "_pqc = Chapter8QNN(n_qubits=10, n_layers=3, topology=\"linear\")\n",
    "_x = torch.randn(8, _pqc.n_angles); _y = _pqc(_x); _y.sum().backward()\n",
    "print(\"\\nگزارش پارامتر مدار:\")\n",
    "print(json.dumps(_pqc.parameter_report(), ensure_ascii=False, indent=2))\n",
    "print(\"شکل خروجی:\", tuple(_y.shape), \"| گرادیان θ:\", tuple(_pqc.theta.grad.shape))\n",
    "del _pqc, _x, _y"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "22cb1bd7",
   "metadata": {},
   "source": [
    "## 5. Model and CAFA metrics"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "382ce302",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:46:03.235039Z",
     "iopub.status.busy": "2026-09-27T15:46:03.234603Z",
     "iopub.status.idle": "2026-09-27T15:46:03.279960Z",
     "shell.execute_reply": "2026-09-27T15:46:03.278961Z",
     "shell.execute_reply.started": "2026-09-27T15:46:03.235009Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "محتوای اطلاعاتی محاسبه شد | میانگین IC = 3.877\n",
      "   میانگین IC در MF: 4.803  (60 اصطلاح)\n",
      "   میانگین IC در BP: 3.010  (60 اصطلاح)\n",
      "   میانگین IC در CC: 3.817  (60 اصطلاح)\n",
      "حالت انتخاب مدل روی اعتبارسنجی: FMAX_MODE = 'aspect_mean'\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Hybrid model, loss and CAFA metrics: protein-centric F_max per aspect,\n",
    "# information-weighted F_max, S_min with conditional information content,\n",
    "# AUPR and AUROC.\n",
    "# ============================================================================\n",
    "\n",
    "class HybridFunctionPredictor(nn.Module):\n",
    "    \"\"\"خط‌لولهٔ هیبرید: ESM-2 ← رمزگذار کلاسیک ← QNN ← سر دسته‌بندی.\n",
    "\n",
    "    بازنماییِ کلاسیکِ ۲۵۶-بُعدی با ۱۶۵ ویژگیِ کوانتومی الحاق می‌شود، پس\n",
    "    رتبهٔ خروجی تا ۴۲۱ می‌رسد و از تنگنای یک گذرگاهِ ۱۰-بُعدی پرهیز می‌شود.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, in_dim, n_labels, n_qubits=10, n_layers=3,\n",
    "                 hidden=512, classical_dim=256, dropout=0.15,\n",
    "                 use_quantum=True, topology=\"linear\", two_qubit_gate=\"CZ\",\n",
    "                 backend=\"fast\", use_entangler=True, freeze_surrogate=False,\n",
    "                 match_params_when_classical=True):\n",
    "        super().__init__()\n",
    "        self.use_quantum = use_quantum\n",
    "\n",
    "        self.trunk = nn.Sequential(\n",
    "            nn.Linear(in_dim, hidden), nn.LayerNorm(hidden), nn.GELU(), nn.Dropout(dropout),\n",
    "            nn.Linear(hidden, classical_dim), nn.LayerNorm(classical_dim), nn.GELU(), nn.Dropout(dropout),\n",
    "        )\n",
    "\n",
    "        n_angles = 2 * n_qubits          # دو ویژگی به‌ازای هر رجیستر\n",
    "        self.to_angles = nn.Linear(classical_dim, n_angles)\n",
    "\n",
    "        if use_quantum:\n",
    "            self.pqc = Chapter8QNN(n_qubits=n_qubits, n_layers=n_layers, max_body=2,\n",
    "                                   bases=(\"Z\", \"X\", \"Y\"), topology=topology,\n",
    "                                   two_qubit_gate=two_qubit_gate, backend=backend,\n",
    "                                   use_entangler=use_entangler)\n",
    "            q_dim = self.pqc.n_features\n",
    "        else:\n",
    "            self.pqc = None\n",
    "            q_dim = 3 * (n_qubits + n_qubits * (n_qubits - 1) // 2) if match_params_when_classical else 0\n",
    "            if q_dim:\n",
    "                # جانشین کلاسیکِ هم‌بُعد — مطالعهٔ حذفیِ کنترل‌شده (بخش ۸-۷)\n",
    "                self.classical_surrogate = nn.Sequential(nn.Linear(n_angles, q_dim), nn.Tanh())\n",
    "                if freeze_surrogate:          # جانشینِ تصادفیِ ثابت — قرینهٔ مخزن کوانتومی\n",
    "                    for prm in self.classical_surrogate.parameters():\n",
    "                        prm.requires_grad_(False)\n",
    "\n",
    "        fused = classical_dim + q_dim\n",
    "        self.head = nn.Sequential(\n",
    "            nn.LayerNorm(fused), nn.Linear(fused, 512), nn.GELU(),\n",
    "            nn.Dropout(dropout), nn.Linear(512, n_labels),\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        h = self.trunk(x)\n",
    "        z = np.pi * torch.tanh(self.to_angles(h))\n",
    "        if self.use_quantum:\n",
    "            q = self.pqc(z)\n",
    "        elif getattr(self, \"classical_surrogate\", None) is not None:\n",
    "            q = self.classical_surrogate(z)\n",
    "        else:\n",
    "            q = None\n",
    "        fused = h if q is None else torch.cat([h, q], dim=1)\n",
    "        return self.head(fused)\n",
    "\n",
    "    def parameter_report(self):\n",
    "        rep = {name: sum(p.numel() for p in mod.parameters())\n",
    "               for name, mod in [(\"trunk\", self.trunk), (\"to_angles\", self.to_angles), (\"head\", self.head)]}\n",
    "        if self.pqc is not None:\n",
    "            rep[\"pqc\"] = self.pqc.theta.numel()\n",
    "        rep[\"total\"] = sum(p.numel() for p in self.parameters())\n",
    "        return rep\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# معیارهای رسمی CAFA — نسخهٔ تفکیک‌شده بر حسب MFO / BPO / CCO\n",
    "# ============================================================\n",
    "#\n",
    "# سه تغییر نسبت به نسخهٔ پیشین:\n",
    "#\n",
    "# (۱) ارزیابی بر پایهٔ جنبه. برای هر جنبهٔ a:\n",
    "#       • فقط ستون‌های C_a (اصطلاح‌های همان جنبه) دیده می‌شوند؛\n",
    "#       • مجموعهٔ محک N_a = پروتئین‌هایی که دست‌کم یک حاشیه‌نویسی\n",
    "#         واقعی در جنبهٔ a دارند (پروتکل CAFA)؛\n",
    "#       • آستانهٔ بهینه τ*_a برای هر جنبه جداگانه جست‌وجو می‌شود.\n",
    "#     یک آستانهٔ سراسریِ واحد برای سه جنبه با آمارِ کاملاً متفاوت\n",
    "#     بهینه نیست.\n",
    "#\n",
    "# (۲) wFmax وزن‌دار با محتوای اطلاعاتی — همان معیاری که InterLabelGO+\n",
    "#     گزارش می‌کند؛ بدون آن مقایسهٔ عدد‌به‌عدد با آن مقاله بی‌معناست:\n",
    "#         wPr_i(τ) = Σ_{c∈P_i(τ)} IC_c·1[c∈T_i] / Σ_{c∈P_i(τ)} IC_c\n",
    "#         wRc_i(τ) = Σ_{c∈T_i}    IC_c·1[c∈P_i(τ)] / Σ_{c∈T_i}    IC_c\n",
    "#     (Fmax ساده حالت خاصِ IC ≡ ۱ است.)\n",
    "#\n",
    "# (۳) پیاده‌سازی هیستوگرامی. به‌جای پیمایش ۹۹ آستانه × ماتریس کامل،\n",
    "#     برای هر پروتئین یک هیستوگرام وزنی روی ۱۰۱ سطل امتیاز ساخته و\n",
    "#     جمع تجمعیِ معکوس گرفته می‌شود؛ همهٔ آستانه‌ها در یک گذر O(N·C)\n",
    "#     به‌دست می‌آیند. برای ۱۲ مدل × ۴ گروه ستون، این تفاوتِ چند ثانیه\n",
    "#     در برابر چند ده دقیقه است.\n",
    "# ============================================================\n",
    "\n",
    "N_BINS = 101                                    # τ ∈ {0.00, 0.01, …, 1.00}\n",
    "TAUS   = np.arange(N_BINS) / (N_BINS - 1.0)\n",
    "\n",
    "\n",
    "def _cumulative_mass(y_true, y_score, w, chunk=4096):\n",
    "    \"\"\"جرمِ پیش‌بینی‌شده و جرمِ درستِ پیش‌بینی‌شده، برای همهٔ آستانه‌ها یکجا.\n",
    "\n",
    "    خروجی: Sp[i,k] = Σ_{c: score_ic ≥ τ_k} w_c\n",
    "            St[i,k] = Σ_{c: score_ic ≥ τ_k و y_ic=1} w_c\n",
    "    \"\"\"\n",
    "    N, C = y_score.shape\n",
    "    Sp = np.empty((N, N_BINS), dtype=np.float64)\n",
    "    St = np.empty((N, N_BINS), dtype=np.float64)\n",
    "    w = np.asarray(w, dtype=np.float64).ravel()\n",
    "    for s in range(0, N, chunk):\n",
    "        sc = y_score[s:s + chunk]\n",
    "        n = sc.shape[0]\n",
    "        b = np.clip((np.asarray(sc, dtype=np.float64) * (N_BINS - 1) + 1e-9).astype(np.int64), 0, N_BINS - 1)\n",
    "        off = (np.arange(n, dtype=np.int64)[:, None] * N_BINS + b).ravel()\n",
    "        ww = np.broadcast_to(w, sc.shape).ravel()\n",
    "        hp = np.bincount(off, weights=ww, minlength=n * N_BINS).reshape(n, N_BINS)\n",
    "        ht = np.bincount(off, weights=ww * np.asarray(y_true[s:s + chunk], dtype=np.float64).ravel(),\n",
    "                         minlength=n * N_BINS).reshape(n, N_BINS)\n",
    "        Sp[s:s + n] = np.cumsum(hp[:, ::-1], axis=1)[:, ::-1]\n",
    "        St[s:s + n] = np.cumsum(ht[:, ::-1], axis=1)[:, ::-1]\n",
    "    return Sp, St\n",
    "\n",
    "\n",
    "def protein_centric(y_true, y_score, ic, cols=None, min_bench=1):\n",
    "    \"\"\"F_max ، wF_max و S_min پروتئین‌محور روی یک زیرمجموعه از ستون‌ها.\n",
    "\n",
    "    y_true : (N, C) دودویی      y_score : (N, C) در [۰,۱]\n",
    "    cols   : اندیس ستون‌های جنبه؛ None یعنی همهٔ ستون‌ها (حالت تجمیعی)\n",
    "    \"\"\"\n",
    "    if cols is not None:\n",
    "        y_true = y_true[:, cols]\n",
    "        y_score = y_score[:, cols]\n",
    "        ic = np.asarray(ic)[cols]\n",
    "    yb = np.asarray(y_true) > 0.5\n",
    "    n_true = yb.sum(axis=1)\n",
    "    bench = n_true >= min_bench                       # مجموعهٔ محکِ همین جنبه\n",
    "    out = {\"n_terms\": int(yb.shape[1]), \"n_bench\": int(bench.sum()),\n",
    "           \"mean_ic\": float(np.mean(ic)) if len(ic) else float(\"nan\")}\n",
    "    if out[\"n_bench\"] == 0 or out[\"n_terms\"] == 0:\n",
    "        out.update(fmax=float(\"nan\"), fmax_threshold=float(\"nan\"),\n",
    "                   wfmax=float(\"nan\"), wfmax_threshold=float(\"nan\"),\n",
    "                   smin=float(\"nan\"), smin_threshold=float(\"nan\"),\n",
    "                   precision_at_fmax=float(\"nan\"), recall_at_fmax=float(\"nan\"),\n",
    "                   coverage_at_fmax=float(\"nan\"))\n",
    "        return out\n",
    "\n",
    "    yb, sc = yb[bench], np.asarray(y_score)[bench]\n",
    "    nb = yb.shape[0]\n",
    "\n",
    "    def _curve(w):\n",
    "        Sp, St = _cumulative_mass(yb, sc, w)\n",
    "        total = (yb * np.asarray(w, dtype=np.float64)).sum(axis=1)     # جرم واقعی هر پروتئین\n",
    "        covered = Sp > 0                                               # پروتئین‌های دارای پیش‌بینی\n",
    "        m = covered.sum(axis=0).astype(np.float64)                     # m(τ) در فرمول CAFA\n",
    "        with np.errstate(divide=\"ignore\", invalid=\"ignore\"):\n",
    "            pr_i = np.where(covered, St / np.maximum(Sp, 1e-12), 0.0)\n",
    "            rc_i = St / np.maximum(total[:, None], 1e-12)\n",
    "        P = np.where(m > 0, pr_i.sum(axis=0) / np.maximum(m, 1e-12), 0.0)   # میانگین روی دارای‌پیش‌بینی\n",
    "        R = rc_i.sum(axis=0) / nb                                          # میانگین روی کل محک\n",
    "        F = np.where(P + R > 0, 2.0 * P * R / np.maximum(P + R, 1e-12), 0.0)\n",
    "        return F, P, R, m / nb, Sp, St, total\n",
    "\n",
    "    # ---- Fmax ساده (وزن یکسان) ----\n",
    "    F, P, R, cov, _, _, _ = _curve(np.ones(yb.shape[1]))\n",
    "    k = int(np.argmax(F))\n",
    "    out.update(fmax=float(F[k]), fmax_threshold=float(TAUS[k]),\n",
    "               precision_at_fmax=float(P[k]), recall_at_fmax=float(R[k]),\n",
    "               coverage_at_fmax=float(cov[k]))\n",
    "\n",
    "    # ---- wFmax وزن‌دار با IC + S_min از همان جرم‌های وزنی ----\n",
    "    icw = np.asarray(ic, dtype=np.float64)\n",
    "    Fw, Pw, Rw, covw, Sp, St, total = _curve(icw)\n",
    "    kw = int(np.argmax(Fw))\n",
    "    out.update(wfmax=float(Fw[kw]), wfmax_threshold=float(TAUS[kw]),\n",
    "               wprecision_at_wfmax=float(Pw[kw]), wrecall_at_wfmax=float(Rw[kw]))\n",
    "\n",
    "    ru = (total[:, None] - St).mean(axis=0)      # remaining uncertainty\n",
    "    mi = (Sp - St).mean(axis=0)                  # misinformation\n",
    "    S = np.sqrt(ru ** 2 + mi ** 2)\n",
    "    ks = int(np.argmin(S))\n",
    "    out.update(smin=float(S[ks]), smin_threshold=float(TAUS[ks]),\n",
    "               ru_at_smin=float(ru[ks]), mi_at_smin=float(mi[ks]))\n",
    "    return out\n",
    "\n",
    "\n",
    "def _rank_metrics(y_true, y_score, cols=None):\n",
    "    \"\"\"AUROC و AUPR خرد/کلان روی همان زیرمجموعهٔ ستون‌ها.\"\"\"\n",
    "    from sklearn.metrics import roc_auc_score, average_precision_score\n",
    "    yt = (np.asarray(y_true) > 0.5).astype(int)\n",
    "    ys = np.asarray(y_score)\n",
    "    if cols is not None:\n",
    "        yt, ys = yt[:, cols], ys[:, cols]\n",
    "    keep = yt.sum(axis=0) > 0\n",
    "    rep = {}\n",
    "    if keep.sum() == 0:\n",
    "        return rep\n",
    "    try:\n",
    "        rep[\"auroc_micro\"] = float(roc_auc_score(yt[:, keep].ravel(), ys[:, keep].ravel()))\n",
    "        rep[\"auroc_macro\"] = float(roc_auc_score(yt[:, keep], ys[:, keep], average=\"macro\"))\n",
    "        rep[\"aupr_micro\"] = float(average_precision_score(yt[:, keep].ravel(), ys[:, keep].ravel()))\n",
    "        rep[\"aupr_macro\"] = float(average_precision_score(yt[:, keep], ys[:, keep], average=\"macro\"))\n",
    "    except Exception as e:\n",
    "        print(\"هشدار در محاسبهٔ AUROC/AUPR:\", e)\n",
    "    return rep\n",
    "\n",
    "\n",
    "# ---------- fmax(): میانگینِ جنبه‌ای یا حالتِ تجمیعی، بسته به FMAX_MODE ----------\n",
    "# FMAX_MODE = \"global\"      → دقیقاً رفتار نسخهٔ قبل (یک آستانهٔ سراسری)\n",
    "# FMAX_MODE = \"aspect_mean\" → میانگین سه Fmax تفکیکی (انتخاب مدل مطابق CAFA)\n",
    "FMAX_MODE = \"aspect_mean\"\n",
    "\n",
    "def fmax(y_true, y_score, thresholds=None, return_threshold=False):\n",
    "    \"\"\"F_max پروتئین‌محور؛ در حالت aspect_mean میانگین سه جنبه را برمی‌گرداند.\"\"\"\n",
    "    if FMAX_MODE == \"aspect_mean\":\n",
    "        fs, ts = [], []\n",
    "        for a in ASPECTS:\n",
    "            idx = ASPECT_COLS[a]\n",
    "            if len(idx) == 0:\n",
    "                continue\n",
    "            r = protein_centric(y_true, y_score, IC, cols=idx)\n",
    "            if r[\"fmax\"] == r[\"fmax\"]:\n",
    "                fs.append(r[\"fmax\"]); ts.append(r[\"fmax_threshold\"])\n",
    "        if fs:\n",
    "            f, t = float(np.mean(fs)), float(np.median(ts))\n",
    "            return (f, t) if return_threshold else f\n",
    "    r = protein_centric(y_true, y_score, IC, cols=None)\n",
    "    return (r[\"fmax\"], r[\"fmax_threshold\"]) if return_threshold else r[\"fmax\"]\n",
    "\n",
    "\n",
    "def smin(y_true, y_score, ic, thresholds=None, return_threshold=False):\n",
    "    r = protein_centric(y_true, y_score, ic, cols=None)\n",
    "    return (r[\"smin\"], r[\"smin_threshold\"]) if return_threshold else r[\"smin\"]\n",
    "\n",
    "\n",
    "def information_content(Y_train, max_ic=25.0):\n",
    "    \"\"\"محتوای اطلاعاتی بر پایهٔ گراف تخت.\"\"\"\n",
    "    n = Y_train.shape[0]\n",
    "    p = (Y_train.sum(axis=0) + 1.0) / (n + 2.0)\n",
    "    return np.minimum(-np.log2(p), max_ic)\n",
    "\n",
    "\n",
    "def full_report(y_true, y_score, ic, by_aspect=True):\n",
    "    \"\"\"گزارش کامل: یک بلوک تجمیعی + یک بلوک برای هر یک از MFO/BPO/CCO.\"\"\"\n",
    "    from sklearn.metrics import f1_score\n",
    "    yt = (np.asarray(y_true) > 0.5).astype(int)\n",
    "    rep = protein_centric(y_true, y_score, ic, cols=None)\n",
    "    rep.update(_rank_metrics(yt, y_score, cols=None))\n",
    "    pred05 = (np.asarray(y_score) >= 0.5).astype(int)\n",
    "    rep[\"f1_micro@0.5\"] = float(f1_score(yt, pred05, average=\"micro\", zero_division=0))\n",
    "    rep[\"f1_macro@0.5\"] = float(f1_score(yt, pred05, average=\"macro\", zero_division=0))\n",
    "    rep[\"prevalence\"] = float(yt.mean())\n",
    "\n",
    "    if by_aspect:\n",
    "        rep[\"by_aspect\"] = {}\n",
    "        for a in ASPECTS:\n",
    "            idx = ASPECT_COLS[a]\n",
    "            sub = protein_centric(y_true, y_score, ic, cols=idx)\n",
    "            sub.update(_rank_metrics(yt, y_score, cols=idx))\n",
    "            if len(idx):\n",
    "                sub[\"f1_micro@0.5\"] = float(f1_score(yt[:, idx], pred05[:, idx],\n",
    "                                                     average=\"micro\", zero_division=0))\n",
    "                sub[\"f1_macro@0.5\"] = float(f1_score(yt[:, idx], pred05[:, idx],\n",
    "                                                     average=\"macro\", zero_division=0))\n",
    "                sub[\"prevalence\"] = float(yt[:, idx].mean())\n",
    "            rep[\"by_aspect\"][a] = sub\n",
    "        vals = [rep[\"by_aspect\"][a][\"fmax\"] for a in ASPECTS if rep[\"by_aspect\"][a][\"fmax\"] == rep[\"by_aspect\"][a][\"fmax\"]]\n",
    "        wvals = [rep[\"by_aspect\"][a][\"wfmax\"] for a in ASPECTS if rep[\"by_aspect\"][a][\"wfmax\"] == rep[\"by_aspect\"][a][\"wfmax\"]]\n",
    "        rep[\"fmax_aspect_mean\"] = float(np.mean(vals)) if vals else float(\"nan\")\n",
    "        rep[\"wfmax_aspect_mean\"] = float(np.mean(wvals)) if wvals else float(\"nan\")\n",
    "    return rep\n",
    "\n",
    "\n",
    "def _f(v, nd=4):\n",
    "    return f\"{v:.{nd}f}\" if isinstance(v, float) and v == v else \"—\"\n",
    "\n",
    "\n",
    "def print_report(tag, rep):\n",
    "    print(f\"\\n--- {tag} ---\")\n",
    "    print(f\"  [تجمیعی]  F_max = {_f(rep.get('fmax'))} (τ={_f(rep.get('fmax_threshold'),2)})\"\n",
    "          f\" | wF_max = {_f(rep.get('wfmax'))} | S_min = {_f(rep.get('smin'))}\")\n",
    "    ba = rep.get(\"by_aspect\")\n",
    "    if not ba:\n",
    "        return\n",
    "    print(f\"  {'جنبه':<6}{'F_max':>9}{'τ*':>7}{'wF_max':>9}{'τ*w':>7}{'S_min':>9}\"\n",
    "          f\"{'AUPR-خرد':>11}{'AUROC-خرد':>11}{'#اصطلاح':>9}{'#پروتئین':>10}\")\n",
    "    for a in ASPECTS:\n",
    "        r = ba[a]\n",
    "        print(f\"  {ASPECT_EN[a]:<6}{_f(r.get('fmax')):>9}{_f(r.get('fmax_threshold'),2):>7}\"\n",
    "              f\"{_f(r.get('wfmax')):>9}{_f(r.get('wfmax_threshold'),2):>7}{_f(r.get('smin')):>9}\"\n",
    "              f\"{_f(r.get('aupr_micro')):>11}{_f(r.get('auroc_micro')):>11}\"\n",
    "              f\"{r.get('n_terms',0):>9}{r.get('n_bench',0):>10}\")\n",
    "    print(f\"  {'میانگین':<6}{_f(rep.get('fmax_aspect_mean')):>9}{'':>7}\"\n",
    "          f\"{_f(rep.get('wfmax_aspect_mean')):>9}\")\n",
    "\n",
    "\n",
    "IC = information_content(Ytr.numpy())\n",
    "print(\"محتوای اطلاعاتی محاسبه شد | میانگین IC =\", round(float(IC.mean()), 3))\n",
    "for a in ASPECTS:\n",
    "    idx = ASPECT_COLS[a]\n",
    "    if len(idx):\n",
    "        print(f\"   میانگین IC در {ASPECT_EN[a]}: {IC[idx].mean():.3f}  ({len(idx)} اصطلاح)\")\n",
    "print(f\"حالت انتخاب مدل روی اعتبارسنجی: FMAX_MODE = {FMAX_MODE!r}\")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8cbcf08e",
   "metadata": {},
   "source": [
    "## 6. Training utilities"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "a203e02c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:46:10.474651Z",
     "iopub.status.busy": "2026-09-27T15:46:10.473817Z",
     "iopub.status.idle": "2026-09-27T15:46:10.854829Z",
     "shell.execute_reply": "2026-09-27T15:46:10.854111Z",
     "shell.execute_reply.started": "2026-09-27T15:46:10.474621Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "دستگاه بخش کلاسیک: cuda | شبیه‌سازِ کوانتومی: CPU\n",
      "برآورد زمان آموزش مدل کوانتومی: حدود 0.8 ساعت (5.4 میلی‌ثانیه بر نمونه)\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Training utilities: EMA, cosine warm-up schedule, loss, and early stopping\n",
    "# on validation aspect-mean F_max.\n",
    "# ============================================================================\n",
    "\n",
    "N_QUBITS       = 10\n",
    "N_LAYERS       = 3\n",
    "TOPOLOGY       = \"linear\"      # linear | tree | ring\n",
    "TWO_QUBIT      = \"CZ\"\n",
    "BACKEND        = \"fast\"        # fast | pennylane\n",
    "EPOCHS         = 20\n",
    "PATIENCE       = 5             # توقف زودهنگام بر پایهٔ F_max اعتبارسنجی\n",
    "BATCH_SIZE     = 256\n",
    "LR             = 2e-3\n",
    "WEIGHT_DECAY   = 1e-5\n",
    "DROPOUT        = 0.15\n",
    "POS_WEIGHT_CAP = 20.0          # سقف وزن کلاس مثبت؛ None یعنی BCE ساده\n",
    "\n",
    "DEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n",
    "print(\"دستگاه بخش کلاسیک:\", DEVICE, \"| شبیه‌سازِ کوانتومی: CPU\")\n",
    "\n",
    "# ---- برآورد زمان پیش از شروع ----\n",
    "_t0 = time.time()\n",
    "_probe = Chapter8QNN(N_QUBITS, N_LAYERS, topology=TOPOLOGY,\n",
    "                     two_qubit_gate=TWO_QUBIT, backend=BACKEND)\n",
    "_xp = torch.randn(64, _probe.n_angles, requires_grad=True)\n",
    "_probe(_xp).sum().backward()\n",
    "_per_sample = (time.time() - _t0) / 64\n",
    "_est = _per_sample * len(Xtr) * EPOCHS / 3600\n",
    "print(f\"برآورد زمان آموزش مدل کوانتومی: حدود {_est:.1f} ساعت \"\n",
    "      f\"({_per_sample*1000:.1f} میلی‌ثانیه بر نمونه)\")\n",
    "if _est > 8:\n",
    "    print(\"⚠️ برای اجرای سریع‌تر می‌توان N_TRAIN_MAX یا EPOCHS را کاهش داد.\")\n",
    "del _probe, _xp\n",
    "\n",
    "\n",
    "THETA_DRIFT = {}\n",
    "\n",
    "\n",
    "class EMA:\n",
    "    \"\"\"میانگین متحرک نمایی وزن‌ها — پایدارسازی ارزیابی.\"\"\"\n",
    "    def __init__(self, model, decay=0.995):\n",
    "        self.decay = decay\n",
    "        self.shadow = {k: v.detach().clone()\n",
    "                       for k, v in model.state_dict().items() if v.dtype.is_floating_point}\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def update(self, model):\n",
    "        for k, v in model.state_dict().items():\n",
    "            if k in self.shadow:\n",
    "                self.shadow[k].mul_(self.decay).add_(v.detach(), alpha=1.0 - self.decay)\n",
    "\n",
    "    def copy_to(self, model):\n",
    "        sd = model.state_dict(); sd.update(self.shadow); model.load_state_dict(sd)\n",
    "\n",
    "\n",
    "def cosine_warmup(optimizer, warmup_steps, total_steps):\n",
    "    def fn(step):\n",
    "        if step < warmup_steps:\n",
    "            return step / max(1, warmup_steps)\n",
    "        prog = (step - warmup_steps) / max(1, total_steps - warmup_steps)\n",
    "        return 0.5 * (1.0 + np.cos(np.pi * min(prog, 1.0)))\n",
    "    return torch.optim.lr_scheduler.LambdaLR(optimizer, fn)\n",
    "\n",
    "\n",
    "def make_loss(Y_train, cap=POS_WEIGHT_CAP):\n",
    "    if cap is None:\n",
    "        return nn.BCEWithLogitsLoss()\n",
    "    pos = Y_train.sum(axis=0)\n",
    "    w = np.clip((len(Y_train) - pos) / np.maximum(pos, 1.0), 1.0, cap)\n",
    "    return nn.BCEWithLogitsLoss(pos_weight=torch.tensor(w, dtype=torch.float32, device=DEVICE))\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def predict(model, X, batch=512):\n",
    "    model.eval()\n",
    "    outs = []\n",
    "    for s in range(0, len(X), batch):\n",
    "        outs.append(torch.sigmoid(model(X[s : s + batch].to(DEVICE))).float().cpu().numpy())\n",
    "    return np.concatenate(outs)\n",
    "\n",
    "\n",
    "def train_model(use_quantum=True, seed=42, topology=TOPOLOGY, two_qubit=TWO_QUBIT,\n",
    "                epochs=EPOCHS, patience=PATIENCE, tag=None, _freeze=False, _entangler=True):\n",
    "    torch.manual_seed(seed); np.random.seed(seed)\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.manual_seed_all(seed)\n",
    "\n",
    "    model = HybridFunctionPredictor(\n",
    "        in_dim=IN_DIM, n_labels=N_LABELS, n_qubits=N_QUBITS, n_layers=N_LAYERS,\n",
    "        dropout=DROPOUT, use_quantum=use_quantum, topology=topology,\n",
    "        two_qubit_gate=two_qubit, backend=BACKEND, use_entangler=_entangler).to(DEVICE)\n",
    "    if use_quantum and model.pqc is not None:\n",
    "        model.pqc.cpu()                       # شبیه‌ساز کوانتومی روی CPU می‌ماند\n",
    "        if _freeze:                           # مخزن کوانتومی: θ آموزش نمی‌بیند\n",
    "            model.pqc.theta.requires_grad_(False)\n",
    "    theta0 = (model.pqc.theta.detach().cpu().clone()\n",
    "              if (use_quantum and model.pqc is not None) else None)\n",
    "\n",
    "    name = tag or (\"Quantum-QNN\" if use_quantum else \"Classical\")\n",
    "    print(f\"\\n{'='*66}\\n{name}\")\n",
    "    print(\"بودجهٔ پارامتر:\", model.parameter_report())\n",
    "    print(\"=\" * 66)\n",
    "\n",
    "    criterion = make_loss(Ytr.numpy())\n",
    "    optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n",
    "    steps = max(1, len(Xtr) // BATCH_SIZE)\n",
    "    scheduler = cosine_warmup(optimizer, max(1, epochs * steps // 20), epochs * steps)\n",
    "    ema = EMA(model, decay=0.995)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=BATCH_SIZE,\n",
    "                        shuffle=True, drop_last=True, pin_memory=(DEVICE == \"cuda\"))\n",
    "\n",
    "    history, best_fmax, best_state, best_epoch, stale = [], -1.0, None, 0, 0\n",
    "\n",
    "    for epoch in range(1, epochs + 1):\n",
    "        model.train(); t0 = time.time(); running = 0.0\n",
    "        for xb, yb in loader:\n",
    "            xb = xb.to(DEVICE, non_blocking=True); yb = yb.to(DEVICE, non_blocking=True)\n",
    "            optimizer.zero_grad(set_to_none=True)\n",
    "            loss = criterion(model(xb), yb)\n",
    "            loss.backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=5.0)\n",
    "            optimizer.step(); scheduler.step(); ema.update(model)\n",
    "            running += loss.item() * xb.size(0)\n",
    "        epoch_loss = running / (len(loader) * BATCH_SIZE)\n",
    "\n",
    "        backup = {k: v.detach().clone() for k, v in model.state_dict().items()}\n",
    "        ema.copy_to(model)\n",
    "        scores = predict(model, Xva)\n",
    "        f, ft = fmax(Yva_np, scores, return_threshold=True)\n",
    "        s = smin(Yva_np, scores, IC)\n",
    "        dt = time.time() - t0\n",
    "        history.append({\"epoch\": epoch, \"loss\": float(epoch_loss), \"fmax\": float(f),\n",
    "                        \"threshold\": float(ft), \"smin\": float(s), \"seconds\": round(dt, 1)})\n",
    "\n",
    "        improved = f > best_fmax\n",
    "        if improved:\n",
    "            best_fmax, best_epoch, stale = float(f), epoch, 0\n",
    "            best_state = {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}\n",
    "        else:\n",
    "            stale += 1\n",
    "        model.load_state_dict(backup)         # ادامهٔ آموزش با وزن‌های اصلی\n",
    "\n",
    "        print(f\"[{name}] Ep {epoch:3d}/{epochs} | loss={epoch_loss:.4f} | \"\n",
    "              f\"val_Fmax={f:.4f} (tau={ft:.2f}) | val_Smin={s:.4f} | {dt:.1f}s\"\n",
    "              + (\" ★\" if improved else \"\"))\n",
    "        if stale >= patience:\n",
    "            print(f\"⏹️ توقف زودهنگام: {patience} دوره بدون بهبود.\")\n",
    "            break\n",
    "\n",
    "    model.load_state_dict(best_state)          # بهترین نقطهٔ وارسی\n",
    "    if theta0 is not None:\n",
    "        drift = float((model.pqc.theta.detach().cpu() - theta0).norm())\n",
    "        rel = drift / float(theta0.norm())\n",
    "        print(f\"   جابه‌جاییِ پارامترهای کوانتومی: ||θ − θ₀|| = {drift:.3e}  \"\n",
    "              f\"(نسبی: {rel:.1%})\")\n",
    "        THETA_DRIFT[name] = {\"abs\": drift, \"relative\": rel}\n",
    "    return model, history, best_fmax, best_epoch\n",
    "\n",
    "\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# معماریِ قدیمی روی همین داده — اختیاری (run_hybrid(..., legacy=True))؛\n",
    "# در نتایجِ گزارش‌شده استفاده نمی‌شود.\n",
    "# ============================================================\n",
    "\n",
    "\n",
    "def train_legacy(seed=42, epochs=EPOCHS, patience=PATIENCE):\n",
    "    torch.manual_seed(seed); np.random.seed(seed)\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.manual_seed_all(seed)\n",
    "\n",
    "    model = LegacyHybrid(IN_DIM, N_LABELS, N_QUBITS, n_layers=4).to(DEVICE)\n",
    "    model.pqc.cpu()\n",
    "    print(f\"\\n{'='*66}\\nLegacy-VQC (معماریِ قدیمی)\")\n",
    "    print(\"بودجهٔ پارامتر:\", model.parameter_report())\n",
    "    print(\"=\" * 66)\n",
    "\n",
    "    criterion = make_loss(Ytr.numpy())\n",
    "    optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n",
    "    steps = max(1, len(Xtr) // BATCH_SIZE)\n",
    "    scheduler = cosine_warmup(optimizer, max(1, epochs * steps // 20), epochs * steps)\n",
    "    ema = EMA(model, decay=0.995)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=BATCH_SIZE,\n",
    "                        shuffle=True, drop_last=True, pin_memory=(DEVICE == \"cuda\"))\n",
    "\n",
    "    history, best, best_state, best_epoch, stale = [], -1.0, None, 0, 0\n",
    "    for epoch in range(1, epochs + 1):\n",
    "        model.train(); t0 = time.time(); running = 0.0\n",
    "        for xb, yb in loader:\n",
    "            xb = xb.to(DEVICE, non_blocking=True); yb = yb.to(DEVICE, non_blocking=True)\n",
    "            optimizer.zero_grad(set_to_none=True)\n",
    "            loss = criterion(model(xb), yb); loss.backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n",
    "            optimizer.step(); scheduler.step(); ema.update(model)\n",
    "            running += loss.item() * xb.size(0)\n",
    "        epoch_loss = running / (len(loader) * BATCH_SIZE)\n",
    "\n",
    "        backup = {k: v.detach().clone() for k, v in model.state_dict().items()}\n",
    "        ema.copy_to(model)\n",
    "        sc = predict(model, Xva)\n",
    "        f, ft = fmax(Yva_np, sc, return_threshold=True)\n",
    "        s = smin(Yva_np, sc, IC)\n",
    "        dt = time.time() - t0\n",
    "        history.append({\"epoch\": epoch, \"loss\": float(epoch_loss), \"fmax\": float(f),\n",
    "                        \"threshold\": float(ft), \"smin\": float(s), \"seconds\": round(dt, 1)})\n",
    "        improved = f > best\n",
    "        if improved:\n",
    "            best, best_epoch, stale = float(f), epoch, 0\n",
    "            best_state = {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}\n",
    "        else:\n",
    "            stale += 1\n",
    "        model.load_state_dict(backup)\n",
    "        print(f\"[Legacy-VQC] Ep {epoch:3d}/{epochs} | loss={epoch_loss:.4f} | \"\n",
    "              f\"val_Fmax={f:.4f} (tau={ft:.2f}) | val_Smin={s:.4f} | {dt:.1f}s\"\n",
    "              + (\" ★\" if improved else \"\"))\n",
    "        if stale >= patience:\n",
    "            print(f\"⏹️ توقف زودهنگام: {patience} دوره بدون بهبود.\"); break\n",
    "\n",
    "    model.load_state_dict(best_state)\n",
    "    return model, history, best, best_epoch"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "20f7b3fa",
   "metadata": {},
   "source": [
    "## 7. Classical baselines"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "81755a13",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:46:16.199353Z",
     "iopub.status.busy": "2026-09-27T15:46:16.198891Z",
     "iopub.status.idle": "2026-09-27T15:47:43.528121Z",
     "shell.execute_reply": "2026-09-27T15:47:43.527239Z",
     "shell.execute_reply.started": "2026-09-27T15:46:16.199326Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "--- Naïve (بسامدمحور) ---\n",
      "  [تجمیعی]  F_max = 0.4465 (τ=0.23) | wF_max = 0.3316 | S_min = 38.6974\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5666   0.11   0.4591   0.11  12.0668     0.2717     0.7702       60      4094\n",
      "  BP       0.4193   0.18   0.3549   0.15  23.9369     0.2617     0.6715       60      4298\n",
      "  CC       0.6184   0.23   0.4598   0.17  15.4184     0.4471     0.8270       60      4365\n",
      "  میانگین   0.5348          0.4246\n",
      "  زمان آموزش  = 0.0 ثانیه\n",
      "[رگرسیون لجستیک] بهترین val_Fmax = 0.5385\n",
      "\n",
      "--- رگرسیون لجستیک چندبرچسبی ---\n",
      "  [تجمیعی]  F_max = 0.4367 (τ=0.59) | wF_max = 0.3816 | S_min = 36.9334\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5301   0.61   0.4916   0.68   9.7828     0.3520     0.9041       60      4094\n",
      "  BP       0.5039   0.53   0.4734   0.54  21.1629     0.3453     0.7804       60      4298\n",
      "  CC       0.5755   0.54   0.4770   0.62  16.3835     0.3988     0.8499       60      4365\n",
      "  میانگین   0.5365          0.4807\n",
      "  زمان آموزش  = 10.9 ثانیه\n",
      "\n",
      "--- k نزدیک‌ترین همسایه (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.5533 (τ=0.34) | wF_max = 0.4816 | S_min = 32.3032\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7293   0.27   0.6769   0.27   8.3536     0.5126     0.9062       60      4094\n",
      "  BP       0.5624   0.21   0.5206   0.20  19.6910     0.4259     0.8033       60      4298\n",
      "  CC       0.7073   0.27   0.5994   0.27  12.9286     0.5852     0.8873       60      4365\n",
      "  میانگین   0.6663          0.5990\n",
      "  زمان آموزش  = 0.4 ثانیه\n",
      "\n",
      "--- جنگل تصادفی (30 درخت، عمق 10) ---\n",
      "  [تجمیعی]  F_max = 0.5097 (τ=0.26) | wF_max = 0.4210 | S_min = 35.0089\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6622   0.18   0.5872   0.18   9.7461     0.4353     0.8971       60      4094\n",
      "  BP       0.5237   0.22   0.4743   0.18  20.4424     0.3877     0.7881       60      4298\n",
      "  CC       0.6721   0.23   0.5490   0.19  13.8762     0.5494     0.8797       60      4365\n",
      "  میانگین   0.6193          0.5368\n",
      "  زمان آموزش  = 42.5 ثانیه\n",
      "[پرسپترون چندلایه] بهترین val_Fmax = 0.5732\n",
      "\n",
      "--- پرسپترون چندلایهٔ کم‌عمق ---\n",
      "  [تجمیعی]  F_max = 0.4671 (τ=0.58) | wF_max = 0.4105 | S_min = 35.4320\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5771   0.51   0.5219   0.62   9.1475     0.4048     0.9216       60      4094\n",
      "  BP       0.5210   0.48   0.4898   0.51  20.2780     0.3831     0.8024       60      4298\n",
      "  CC       0.6073   0.50   0.5109   0.59  15.7250     0.4325     0.8718       60      4365\n",
      "  میانگین   0.5685          0.5075\n",
      "  زمان آموزش  = 13.1 ثانیه\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{'n_terms': 180,\n",
       " 'n_bench': 5991,\n",
       " 'mean_ic': 3.876572608947754,\n",
       " 'fmax': 0.46706709818495484,\n",
       " 'fmax_threshold': 0.58,\n",
       " 'precision_at_fmax': 0.3780477195034062,\n",
       " 'recall_at_fmax': 0.6109215823673388,\n",
       " 'coverage_at_fmax': 0.9976631614087799,\n",
       " 'wfmax': 0.41048405549229156,\n",
       " 'wfmax_threshold': 0.65,\n",
       " 'wprecision_at_wfmax': 0.34035696649449354,\n",
       " 'wrecall_at_wfmax': 0.5170083678975214,\n",
       " 'smin': 35.43197295416262,\n",
       " 'smin_threshold': 0.83,\n",
       " 'ru_at_smin': 32.18373838786867,\n",
       " 'mi_at_smin': 14.819976073048824,\n",
       " 'auroc_micro': 0.8695049035206308,\n",
       " 'auroc_macro': 0.8391838637654151,\n",
       " 'aupr_micro': 0.4030714618157561,\n",
       " 'aupr_macro': 0.3623001172017646,\n",
       " 'f1_micro@0.5': 0.44502416570074477,\n",
       " 'f1_macro@0.5': 0.37177877291233996,\n",
       " 'prevalence': 0.09744814814814814,\n",
       " 'by_aspect': {'MFO': {'n_terms': 60,\n",
       "   'n_bench': 4094,\n",
       "   'mean_ic': 4.802529811859131,\n",
       "   'fmax': 0.577148089591796,\n",
       "   'fmax_threshold': 0.51,\n",
       "   'precision_at_fmax': 0.48108089844942703,\n",
       "   'recall_at_fmax': 0.7211559386319583,\n",
       "   'coverage_at_fmax': 0.966780654616512,\n",
       "   'wfmax': 0.5218791710826062,\n",
       "   'wfmax_threshold': 0.62,\n",
       "   'wprecision_at_wfmax': 0.46106484594478286,\n",
       "   'wrecall_at_wfmax': 0.6011738326582808,\n",
       "   'smin': 9.147475840993035,\n",
       "   'smin_threshold': 0.83,\n",
       "   'ru_at_smin': 7.985884193463854,\n",
       "   'mi_at_smin': 4.461162170347028,\n",
       "   'auroc_micro': 0.921573412182551,\n",
       "   'auroc_macro': 0.889957239025646,\n",
       "   'aupr_micro': 0.40484921994491546,\n",
       "   'aupr_macro': 0.37299605263701563,\n",
       "   'f1_micro@0.5': 0.43402313051237174,\n",
       "   'f1_macro@0.5': 0.3782765250446601,\n",
       "   'prevalence': 0.04906666666666667},\n",
       "  'BPO': {'n_terms': 60,\n",
       "   'n_bench': 4298,\n",
       "   'mean_ic': 3.0103819370269775,\n",
       "   'fmax': 0.5210187515284895,\n",
       "   'fmax_threshold': 0.48,\n",
       "   'precision_at_fmax': 0.4244351643150296,\n",
       "   'recall_at_fmax': 0.6745085119651458,\n",
       "   'coverage_at_fmax': 0.9867380176826431,\n",
       "   'wfmax': 0.48977319345659776,\n",
       "   'wfmax_threshold': 0.51,\n",
       "   'wprecision_at_wfmax': 0.39990146167653023,\n",
       "   'wrecall_at_wfmax': 0.6317491423118843,\n",
       "   'smin': 20.27804257242028,\n",
       "   'smin_threshold': 0.73,\n",
       "   'ru_at_smin': 17.99127851668598,\n",
       "   'mi_at_smin': 9.354833397977904,\n",
       "   'auroc_micro': 0.8023587037988005,\n",
       "   'auroc_macro': 0.7924864222336964,\n",
       "   'aupr_micro': 0.3831364429464233,\n",
       "   'aupr_macro': 0.37334802253605914,\n",
       "   'f1_micro@0.5': 0.41427332864564487,\n",
       "   'f1_macro@0.5': 0.3873944422851688,\n",
       "   'prevalence': 0.12802777777777777},\n",
       "  'CCO': {'n_terms': 60,\n",
       "   'n_bench': 4365,\n",
       "   'mean_ic': 3.8168070316314697,\n",
       "   'fmax': 0.6072981082311455,\n",
       "   'fmax_threshold': 0.5,\n",
       "   'precision_at_fmax': 0.5081793083829841,\n",
       "   'recall_at_fmax': 0.7544515449037199,\n",
       "   'coverage_at_fmax': 0.9928980526918672,\n",
       "   'wfmax': 0.5109458811070272,\n",
       "   'wfmax_threshold': 0.59,\n",
       "   'wprecision_at_wfmax': 0.4279764740200605,\n",
       "   'wrecall_at_wfmax': 0.6338212679384482,\n",
       "   'smin': 15.725018516544285,\n",
       "   'smin_threshold': 0.8,\n",
       "   'ru_at_smin': 14.027611964442613,\n",
       "   'mi_at_smin': 7.106497725369867,\n",
       "   'auroc_micro': 0.871790750661679,\n",
       "   'auroc_macro': 0.8351079300369034,\n",
       "   'aupr_micro': 0.4324753803175785,\n",
       "   'aupr_macro': 0.340556276432219,\n",
       "   'f1_micro@0.5': 0.48597074785542627,\n",
       "   'f1_macro@0.5': 0.3496653514071912,\n",
       "   'prevalence': 0.11525}},\n",
       " 'fmax_aspect_mean': 0.5684883164504769,\n",
       " 'wfmax_aspect_mean': 0.5075327485487438}"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Classical baselines: frequency prior, logistic regression, cosine k-NN\n",
    "# (k = 15), random forest and a shallow MLP.\n",
    "# ============================================================================\n",
    "\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "\n",
    "RESULTS = {}          # نام مدل → گزارش معیارها\n",
    "TIMING  = {}          # نام مدل → زمان آموزش (ثانیه)\n",
    "PARAMS  = {}          # نام مدل → تعداد پارامتر یا ظرفیت\n",
    "SCORES  = {}          # نام مدل → ماتریس امتیاز آزمون\n",
    "\n",
    "def record(name, scores, seconds, n_params=None):\n",
    "    rep = full_report(Yte_np, scores, IC)\n",
    "    RESULTS[name] = rep\n",
    "    SCORES[name] = np.asarray(scores, dtype=np.float32)\n",
    "    TIMING[name] = float(seconds)\n",
    "    if n_params is not None:\n",
    "        PARAMS[name] = int(n_params)\n",
    "    print_report(name, rep)\n",
    "    print(f\"  زمان آموزش  = {seconds:.1f} ثانیه\")\n",
    "    return rep\n",
    "\n",
    "\n",
    "# ---------------- (۱) Naïve — کف رسمی CAFA ----------------\n",
    "t0 = time.time()\n",
    "prior = Ytr.numpy().mean(axis=0)\n",
    "scores_naive = np.tile(prior, (len(Yte_np), 1))\n",
    "record(\"Naïve (بسامدمحور)\", scores_naive, time.time() - t0, n_params=N_LABELS)\n",
    "\n",
    "\n",
    "# ---------------- (۲) رگرسیون لجستیک چندبرچسبی ----------------\n",
    "# معادلِ دقیقِ ۱٬۰۰۰ رگرسیون لجستیک دودوییِ یک-در-برابر-بقیه،\n",
    "# اما به‌صورت یک لایهٔ خطی با هزینهٔ BCE تا روی GPU در چند ثانیه آموزش ببیند.\n",
    "def train_linear(epochs=30, lr=3e-3, tag=\"رگرسیون لجستیک\"):\n",
    "    torch.manual_seed(GLOBAL_SEED)\n",
    "    model = nn.Linear(IN_DIM, N_LABELS).to(DEVICE)\n",
    "    crit = make_loss(Ytr.numpy())\n",
    "    opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-5)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=512, shuffle=True, drop_last=True)\n",
    "    t0 = time.time(); best, best_sc = -1, None\n",
    "    for ep in range(1, epochs + 1):\n",
    "        model.train()\n",
    "        for xb, yb in loader:\n",
    "            xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n",
    "            opt.zero_grad(set_to_none=True)\n",
    "            crit(model(xb), yb).backward(); opt.step()\n",
    "        sc = predict(model, Xva)\n",
    "        f = fmax(Yva_np, sc)\n",
    "        if f > best:\n",
    "            best = f\n",
    "            best_sc = predict(model, Xte)\n",
    "    print(f\"[{tag}] بهترین val_Fmax = {best:.4f}\")\n",
    "    return best_sc, time.time() - t0, sum(p.numel() for p in model.parameters())\n",
    "\n",
    "sc, sec, npar = train_linear()\n",
    "record(\"رگرسیون لجستیک چندبرچسبی\", sc, sec, npar)\n",
    "\n",
    "\n",
    "# ---------------- (۳) k نزدیک‌ترین همسایه (شباهت کسینوسی) ----------------\n",
    "def knn_scores(K=15, chunk=256):\n",
    "    t0 = time.time()\n",
    "    A = torch.tensor(Xtr_np, device=DEVICE)\n",
    "    A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    Y = torch.tensor(Ytr_np, device=DEVICE)\n",
    "    out = []\n",
    "    Q = torch.tensor(Xte_np, device=DEVICE)\n",
    "    Q = Q / (Q.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    for s in range(0, len(Q), chunk):\n",
    "        sim = Q[s:s + chunk] @ A.T                      # (b, Ntr)\n",
    "        w, idx = torch.topk(sim, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Y[idx]).cpu().numpy())\n",
    "    del A, Y, Q\n",
    "    if DEVICE == \"cuda\": torch.cuda.empty_cache()\n",
    "    return np.concatenate(out), time.time() - t0\n",
    "\n",
    "sc, sec = knn_scores()\n",
    "record(\"k نزدیک‌ترین همسایه (k=۱۵)\", sc, sec, n_params=0)\n",
    "\n",
    "\n",
    "# ---------------- (۴) جنگل تصادفی چندخروجی ----------------\n",
    "# محدودیت حافظه: هر برگ باید بردار احتمالِ ۱٬۰۰۰-بُعدی نگه دارد، پس عمق و\n",
    "# تعداد درخت را محدود می‌کنیم. این محدودیت، خودش یافته‌ای دربارهٔ\n",
    "# نامناسب‌بودنِ درخت‌های تصمیم برای دسته‌بندی چندبرچسبیِ بسیار بزرگ است.\n",
    "RUN_RF = True\n",
    "RF_TREES, RF_DEPTH, RF_SUBSET = 30, 10, 12000\n",
    "\n",
    "if RUN_RF:\n",
    "    t0 = time.time()\n",
    "    rng = np.random.default_rng(GLOBAL_SEED)\n",
    "    sel = rng.choice(len(Xtr_np), size=min(RF_SUBSET, len(Xtr_np)), replace=False)\n",
    "    rf = RandomForestClassifier(n_estimators=RF_TREES, max_depth=RF_DEPTH,\n",
    "                                n_jobs=-1, random_state=GLOBAL_SEED, min_samples_leaf=5)\n",
    "    rf.fit(Xtr_np[sel], Ytr_np[sel])\n",
    "    proba = rf.predict_proba(Xte_np)\n",
    "    # predict_proba برای چندخروجی، فهرستی از آرایه‌های (N, 2) بازمی‌گرداند\n",
    "    sc = np.stack([p[:, 1] if p.shape[1] == 2 else np.zeros(len(Xte_np)) for p in proba], axis=1)\n",
    "    record(f\"جنگل تصادفی ({RF_TREES} درخت، عمق {RF_DEPTH})\", sc, time.time() - t0,\n",
    "           n_params=RF_TREES * (2 ** RF_DEPTH))\n",
    "    del rf, proba\n",
    "\n",
    "\n",
    "# ---------------- (۵) پرسپترون چندلایهٔ کم‌عمق ----------------\n",
    "def train_mlp(hidden=512, epochs=25, lr=2e-3, tag=\"پرسپترون چندلایه\"):\n",
    "    torch.manual_seed(GLOBAL_SEED)\n",
    "    model = nn.Sequential(\n",
    "        nn.Linear(IN_DIM, hidden), nn.LayerNorm(hidden), nn.GELU(), nn.Dropout(0.15),\n",
    "        nn.Linear(hidden, N_LABELS)).to(DEVICE)\n",
    "    crit = make_loss(Ytr.numpy())\n",
    "    opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-5)\n",
    "    steps = max(1, len(Xtr) // 256)\n",
    "    sched = cosine_warmup(opt, max(1, epochs * steps // 20), epochs * steps)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=256, shuffle=True, drop_last=True)\n",
    "    t0 = time.time(); best, best_sc = -1, None\n",
    "    for ep in range(1, epochs + 1):\n",
    "        model.train()\n",
    "        for xb, yb in loader:\n",
    "            xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n",
    "            opt.zero_grad(set_to_none=True)\n",
    "            crit(model(xb), yb).backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n",
    "            opt.step(); sched.step()\n",
    "        f = fmax(Yva_np, predict(model, Xva))\n",
    "        if f > best:\n",
    "            best = f; best_sc = predict(model, Xte)\n",
    "    print(f\"[{tag}] بهترین val_Fmax = {best:.4f}\")\n",
    "    return best_sc, time.time() - t0, sum(p.numel() for p in model.parameters())\n",
    "\n",
    "sc, sec, npar = train_mlp()\n",
    "record(\"پرسپترون چندلایهٔ کم‌عمق\", sc, sec, npar)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0db4a419",
   "metadata": {},
   "source": [
    "## 8. Literature reimplementations: ATGO and InterLabelGO+"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "cab2c63a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T16:14:06.630002Z",
     "iopub.status.busy": "2026-09-26T16:14:06.629095Z",
     "iopub.status.idle": "2026-09-26T16:14:56.150040Z",
     "shell.execute_reply": "2026-09-26T16:14:56.149201Z",
     "shell.execute_reply.started": "2026-09-26T16:14:06.629975Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "[ATGO] بهترین val_Fmax شبکه = 0.5661 | w بهینه = 0.95 | val_Fmax پس از ادغام = 0.6889\n",
      "\n",
      "--- ATGO (۲۰۲۲) — بازپیاده‌سازی ---\n",
      "  [تجمیعی]  F_max = 0.5727 (τ=0.33) | wF_max = 0.5058 | S_min = 31.1687\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7512   0.27   0.7055   0.29   7.6982     0.5475     0.9403       60      4094\n",
      "  BP       0.5887   0.25   0.5481   0.22  18.4406     0.4585     0.8280       60      4298\n",
      "  CC       0.7230   0.30   0.6216   0.29  12.2937     0.6256     0.9113       60      4365\n",
      "  میانگین   0.6877          0.6251\n",
      "  زمان آموزش  = 23.5 ثانیه\n",
      "[InterLabelGO] بهترین val_Fmax = 0.6758\n",
      "\n",
      "--- InterLabelGO+ (۲۰۲۴) — بازپیاده‌سازی ---\n",
      "  [تجمیعی]  F_max = 0.5596 (τ=0.80) | wF_max = 0.5004 | S_min = 32.3799\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7343   0.40   0.6925   0.40   7.9563     0.4284     0.9017       60      4094\n",
      "  BP       0.5891   0.04   0.5481   0.04  18.6027     0.4136     0.8111       60      4298\n",
      "  CC       0.7124   0.69   0.6169   0.31  12.4485     0.5361     0.8931       60      4365\n",
      "  میانگین   0.6786          0.6192\n",
      "  زمان آموزش  = 19.3 ثانیه\n",
      "\n",
      "انحراف‌های اجباری از مقالات اصلی:\n",
      "  ATGO: مقاله از سه لایهٔ پنهانِ آخرِ ESM-1b با ابعاد ۱۲۸۰ استفاده می‌کند؛\n",
      "        ما یک بردار ۳۴۴-بُعدی کش‌شده داریم، پس یک شاخه به‌جای سه شاخه.\n",
      "        مؤلفهٔ هم‌ترازیِ ATGO+ (SAGP) اجرا نشده چون توالی خام در کش نیست.\n",
      "  InterLabelGO+: مقاله سه MLP موازی روی سه لایهٔ ESM2 دارد؛ ما یک MLP.\n",
      "        پس‌پردازش سلسله‌مراتبی و مؤلفهٔ DIAMOND اجرا نشده (گراف GO و توالی در کش نیست).\n",
      "        هستهٔ روش—هزینهٔ InterLabel—عیناً پیاده شده است.\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "\n",
    "# ============================================================================\n",
    "# Reimplementations of ATGO and InterLabelGO+ on the shared 480-d features.\n",
    "# Deviations from the original systems are printed at the end of the cell.\n",
    "# ============================================================================\n",
    "\n",
    "IA = torch.tensor(IC, dtype=torch.float32, device=DEVICE)      # وزن محتوای اطلاعاتی\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# [A] ATGO (۲۰۲۲)\n",
    "# ------------------------------------------------------------\n",
    "class ATGONet(nn.Module):\n",
    "    \"\"\"شبکهٔ سه‌گانه + سر دسته‌بندی، مطابق شکل ۱ مقالهٔ ATGO.\"\"\"\n",
    "    def __init__(self, in_dim, n_labels, n1=1024, n2=1024, dropout=0.15):\n",
    "        super().__init__()\n",
    "        self.branch = nn.Sequential(nn.Linear(in_dim, n1), nn.LayerNorm(n1),\n",
    "                                    nn.ReLU(), nn.Dropout(dropout))\n",
    "        self.fc_a   = nn.Sequential(nn.Linear(n1, n2), nn.LayerNorm(n2), nn.ReLU())\n",
    "        self.fc_o   = nn.Linear(n2, n_labels)\n",
    "\n",
    "    def forward(self, x):\n",
    "        e = self.fc_a(self.branch(x))          # تعبیهٔ شبکهٔ سه‌گانه\n",
    "        return e, self.fc_o(e)\n",
    "\n",
    "\n",
    "def batch_hard_triplet(emb, y, margin=1.0, cf=0.5):\n",
    "    \"\"\"هزینهٔ سه‌گانه با نمونه‌گیریِ «سخت‌ترین در دسته» (batch-hard).\n",
    "\n",
    "    شباهت عملکردی میان دو پروتئین را با امتیاز F1 میان مجموعه اصطلاحاتشان\n",
    "    می‌سنجیم؛ آستانهٔ c_f مثبت و منفی را جدا می‌کند (بخش روش‌های ATGO).\n",
    "    \"\"\"\n",
    "    inter = y @ y.t()\n",
    "    n = y.sum(dim=1, keepdim=True)\n",
    "    f1 = 2 * inter / (n + n.t() + 1e-8)\n",
    "    pos = (f1 >= cf).float(); neg = (f1 < cf).float()\n",
    "    pos = pos - torch.diag(torch.diag(pos))                    # حذف قطر\n",
    "\n",
    "    d = torch.cdist(emb, emb, p=2)\n",
    "    hardest_pos = (d * pos).max(dim=1).values                  # دورترین مثبت\n",
    "    d_neg = d + (1 - neg) * 1e6\n",
    "    hardest_neg = d_neg.min(dim=1).values                      # نزدیک‌ترین منفی\n",
    "    valid = (pos.sum(dim=1) > 0) & (neg.sum(dim=1) > 0)\n",
    "    if valid.sum() == 0:\n",
    "        return emb.sum() * 0.0\n",
    "    return torch.clamp(hardest_pos[valid] - hardest_neg[valid] + margin, min=0).mean()\n",
    "\n",
    "\n",
    "def train_atgo(epochs=25, lr=2e-3, alpha=0.1, batch=256, seed=42, K=30):\n",
    "    torch.manual_seed(seed); np.random.seed(seed)\n",
    "    model = ATGONet(IN_DIM, N_LABELS).to(DEVICE)\n",
    "    crit = make_loss(Ytr.numpy())\n",
    "    opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-5)\n",
    "    steps = max(1, len(Xtr) // batch)\n",
    "    sched = cosine_warmup(opt, max(1, epochs * steps // 20), epochs * steps)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=batch, shuffle=True,\n",
    "                        drop_last=True, pin_memory=(DEVICE == \"cuda\"))\n",
    "    t0 = time.time(); best, best_state = -1.0, None\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def embed_and_score(X, bs=512):\n",
    "        model.eval(); E, S = [], []\n",
    "        for s in range(0, len(X), bs):\n",
    "            e, o = model(X[s:s + bs].to(DEVICE))\n",
    "            E.append(e.cpu()); S.append(torch.sigmoid(o).cpu().numpy())\n",
    "        return torch.cat(E), np.concatenate(S)\n",
    "\n",
    "    for ep in range(1, epochs + 1):\n",
    "        model.train()\n",
    "        for xb, yb in loader:\n",
    "            xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n",
    "            opt.zero_grad(set_to_none=True)\n",
    "            e, logits = model(xb)\n",
    "            loss = alpha * batch_hard_triplet(e, yb) + (1 - alpha) * crit(logits, yb)\n",
    "            loss.backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n",
    "            opt.step(); sched.step()\n",
    "        _, s_val = embed_and_score(Xva)\n",
    "        f = fmax(Yva_np, s_val)\n",
    "        if f > best:\n",
    "            best = f\n",
    "            best_state = {k: v.detach().cpu().clone() for k, v in model.state_dict().items()}\n",
    "    model.load_state_dict(best_state)\n",
    "\n",
    "    # --- امتیاز «همنشینی» (guilt-by-association) در فضای تعبیه ---\n",
    "    E_tr, _ = embed_and_score(Xtr)\n",
    "    E_tr = (E_tr / (E_tr.norm(dim=1, keepdim=True) + 1e-8)).to(DEVICE)\n",
    "    Ytr_d = Ytr.to(DEVICE)\n",
    "\n",
    "    def gba(X):\n",
    "        E, S = embed_and_score(X)\n",
    "        E = (E / (E.norm(dim=1, keepdim=True) + 1e-8)).to(DEVICE)\n",
    "        out = []\n",
    "        for s in range(0, len(E), 256):\n",
    "            sim = E[s:s + 256] @ E_tr.t()\n",
    "            w, idx = torch.topk(sim, K, dim=1)\n",
    "            w = torch.clamp(w, min=0); w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "            out.append(torch.einsum('bk,bkc->bc', w, Ytr_d[idx]).cpu().numpy())\n",
    "        return np.concatenate(out), S\n",
    "\n",
    "    g_val, s_val = gba(Xva)\n",
    "    best_w, best_f = 0.0, -1.0\n",
    "    for w in np.arange(0, 1.01, 0.05):                 # تنظیم w روی اعتبارسنجی\n",
    "        f = fmax(Yva_np, w * g_val + (1 - w) * s_val)\n",
    "        if f > best_f: best_w, best_f = float(w), f\n",
    "    g_te, s_te = gba(Xte)\n",
    "    del E_tr, Ytr_d\n",
    "    if DEVICE == \"cuda\": torch.cuda.empty_cache()\n",
    "    print(f\"[ATGO] بهترین val_Fmax شبکه = {best:.4f} | w بهینه = {best_w:.2f} | \"\n",
    "          f\"val_Fmax پس از ادغام = {best_f:.4f}\")\n",
    "    return best_w * g_te + (1 - best_w) * s_te, time.time() - t0, \\\n",
    "           sum(p.numel() for p in model.parameters())\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# [B] InterLabelGO+ (۲۰۲۴)\n",
    "# ------------------------------------------------------------\n",
    "def soft_f1_loss(logits, y, w, dim):\n",
    "    \"\"\"هزینهٔ F1 نرم و وزن‌دار؛ dim=1 پروتئین‌محور، dim=0 اصطلاح‌محور.\"\"\"\n",
    "    p = torch.sigmoid(logits)\n",
    "    tp = (w * p * y).sum(dim=dim)\n",
    "    den = (w * p).sum(dim=dim) + (w * y).sum(dim=dim)\n",
    "    return (1 - 2 * tp / (den + 1e-8)).mean()\n",
    "\n",
    "\n",
    "def zlpr_loss(logits, y):\n",
    "    \"\"\"هزینهٔ رتبه‌ایِ ZLPR: بیشینه‌کردن فاصلهٔ کمینه‌ترین مثبت از بیشینه‌ترین منفی.\"\"\"\n",
    "    pos = logits.masked_fill(y < 0.5, -1e9)\n",
    "    neg = logits.masked_fill(y > 0.5, -1e9)\n",
    "    return (torch.logsumexp(torch.cat([-pos, torch.zeros_like(pos[:, :1])], 1), 1) +\n",
    "            torch.logsumexp(torch.cat([neg, torch.zeros_like(neg[:, :1])], 1), 1)).mean()\n",
    "\n",
    "\n",
    "def train_interlabelgo(epochs=25, lr=2e-3, batch=256, hidden=1024, seed=42):\n",
    "    torch.manual_seed(seed); np.random.seed(seed)\n",
    "    model = nn.Sequential(\n",
    "        nn.Linear(IN_DIM, hidden), nn.LayerNorm(hidden), nn.ReLU(), nn.Dropout(0.15),\n",
    "        nn.Linear(hidden, hidden), nn.LayerNorm(hidden), nn.ReLU(), nn.Dropout(0.15),\n",
    "        nn.Linear(hidden, N_LABELS)).to(DEVICE)\n",
    "    opt = torch.optim.AdamW(model.parameters(), lr=lr, weight_decay=1e-5)\n",
    "    steps = max(1, len(Xtr) // batch)\n",
    "    sched = cosine_warmup(opt, max(1, epochs * steps // 20), epochs * steps)\n",
    "    loader = DataLoader(TensorDataset(Xtr, Ytr), batch_size=batch, shuffle=True,\n",
    "                        drop_last=True, pin_memory=(DEVICE == \"cuda\"))\n",
    "    w_ia = IA.view(1, -1)\n",
    "    t0 = time.time(); best, best_sc = -1.0, None\n",
    "    for ep in range(1, epochs + 1):\n",
    "        model.train()\n",
    "        for xb, yb in loader:\n",
    "            xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n",
    "            opt.zero_grad(set_to_none=True)\n",
    "            lg = model(xb)\n",
    "            l = (soft_f1_loss(lg, yb, w_ia, 1) *          # F1 پروتئین‌محور\n",
    "                 soft_f1_loss(lg, yb, w_ia, 0) *          # F1 اصطلاح‌محور\n",
    "                 zlpr_loss(lg, yb))                       # هزینهٔ رتبه‌ای\n",
    "            l.backward()\n",
    "            torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)\n",
    "            opt.step(); sched.step()\n",
    "        f = fmax(Yva_np, predict(model, Xva))\n",
    "        if f > best:\n",
    "            best = f; best_sc = predict(model, Xte)\n",
    "    print(f\"[InterLabelGO] بهترین val_Fmax = {best:.4f}\")\n",
    "    return best_sc, time.time() - t0, sum(p.numel() for p in model.parameters())\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "RUN_PUBLISHED = True\n",
    "if RUN_PUBLISHED:\n",
    "    sc, sec, npar = train_atgo()\n",
    "    record(\"ATGO (۲۰۲۲) — بازپیاده‌سازی\", sc, sec, npar)\n",
    "    sc, sec, npar = train_interlabelgo()\n",
    "    record(\"InterLabelGO+ (۲۰۲۴) — بازپیاده‌سازی\", sc, sec, npar)\n",
    "\n",
    "    print(\"\\nانحراف‌های اجباری از مقالات اصلی:\")\n",
    "    print(\"  ATGO: مقاله از سه لایهٔ پنهانِ آخرِ ESM-1b با ابعاد ۱۲۸۰ استفاده می‌کند؛\")\n",
    "    print(\"        ما یک بردار ۳۴۴-بُعدی کش‌شده داریم، پس یک شاخه به‌جای سه شاخه.\")\n",
    "    print(\"        مؤلفهٔ هم‌ترازیِ ATGO+ (SAGP) اجرا نشده چون توالی خام در کش نیست.\")\n",
    "    print(\"  InterLabelGO+: مقاله سه MLP موازی روی سه لایهٔ ESM2 دارد؛ ما یک MLP.\")\n",
    "    print(\"        پس‌پردازش سلسله‌مراتبی و مؤلفهٔ DIAMOND اجرا نشده (گراف GO و توالی در کش نیست).\")\n",
    "    print(\"        هستهٔ روش—هزینهٔ InterLabel—عیناً پیاده شده است.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1d82d5e",
   "metadata": {},
   "source": [
    "## 9. VQNN, QRes and PM-MLP"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fdc0904b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T16:14:56.151625Z",
     "iopub.status.busy": "2026-09-26T16:14:56.151269Z",
     "iopub.status.idle": "2026-09-26T16:53:23.291627Z",
     "shell.execute_reply": "2026-09-26T16:53:23.290903Z",
     "shell.execute_reply.started": "2026-09-26T16:14:56.151585Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "\n",
      "==================================================================\n",
      "VQNN\n",
      "بودجهٔ پارامتر: {'trunk': 379136, 'to_angles': 5140, 'head': 309246, 'pqc': 90, 'total': 693612}\n",
      "==================================================================\n",
      "[VQNN] Ep   1/20 | loss=0.9733 | val_Fmax=0.4181 (tau=0.51) | val_Smin=40.1749 | 63.0s ★\n",
      "[VQNN] Ep   2/20 | loss=0.8477 | val_Fmax=0.4805 (tau=0.53) | val_Smin=38.2525 | 62.4s ★\n",
      "[VQNN] Ep   3/20 | loss=0.8153 | val_Fmax=0.4968 (tau=0.53) | val_Smin=37.4374 | 62.2s ★\n",
      "[VQNN] Ep   4/20 | loss=0.7949 | val_Fmax=0.5141 (tau=0.55) | val_Smin=36.7401 | 62.2s ★\n",
      "[VQNN] Ep   5/20 | loss=0.7789 | val_Fmax=0.5243 (tau=0.54) | val_Smin=36.4270 | 61.8s ★\n",
      "[VQNN] Ep   6/20 | loss=0.7637 | val_Fmax=0.5317 (tau=0.55) | val_Smin=36.1733 | 62.6s ★\n",
      "[VQNN] Ep   7/20 | loss=0.7498 | val_Fmax=0.5375 (tau=0.52) | val_Smin=35.9834 | 61.9s ★\n",
      "[VQNN] Ep   8/20 | loss=0.7369 | val_Fmax=0.5424 (tau=0.54) | val_Smin=35.8550 | 62.5s ★\n",
      "[VQNN] Ep   9/20 | loss=0.7220 | val_Fmax=0.5454 (tau=0.54) | val_Smin=35.7500 | 62.4s ★\n",
      "[VQNN] Ep  10/20 | loss=0.7079 | val_Fmax=0.5500 (tau=0.54) | val_Smin=35.6410 | 61.9s ★\n",
      "[VQNN] Ep  11/20 | loss=0.6961 | val_Fmax=0.5536 (tau=0.54) | val_Smin=35.5452 | 61.5s ★\n",
      "[VQNN] Ep  12/20 | loss=0.6826 | val_Fmax=0.5562 (tau=0.53) | val_Smin=35.4833 | 61.8s ★\n",
      "[VQNN] Ep  13/20 | loss=0.6689 | val_Fmax=0.5583 (tau=0.55) | val_Smin=35.4756 | 61.7s ★\n",
      "[VQNN] Ep  14/20 | loss=0.6573 | val_Fmax=0.5599 (tau=0.53) | val_Smin=35.3825 | 61.7s ★\n",
      "[VQNN] Ep  15/20 | loss=0.6473 | val_Fmax=0.5618 (tau=0.52) | val_Smin=35.3710 | 61.9s ★\n",
      "[VQNN] Ep  16/20 | loss=0.6382 | val_Fmax=0.5638 (tau=0.52) | val_Smin=35.3501 | 61.5s ★\n",
      "[VQNN] Ep  17/20 | loss=0.6310 | val_Fmax=0.5646 (tau=0.50) | val_Smin=35.3227 | 61.9s ★\n",
      "[VQNN] Ep  18/20 | loss=0.6262 | val_Fmax=0.5657 (tau=0.50) | val_Smin=35.3002 | 62.6s ★\n",
      "[VQNN] Ep  19/20 | loss=0.6225 | val_Fmax=0.5669 (tau=0.50) | val_Smin=35.2867 | 61.4s ★\n",
      "[VQNN] Ep  20/20 | loss=0.6211 | val_Fmax=0.5673 (tau=0.50) | val_Smin=35.2933 | 61.6s ★\n",
      "   جابه‌جاییِ پارامترهای کوانتومی: ||θ − θ₀|| = 9.295e-01  (نسبی: 550.4%)\n",
      "\n",
      "--- VQNN ---\n",
      "  [تجمیعی]  F_max = 0.4616 (τ=0.59) | wF_max = 0.4043 | S_min = 35.6218\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5578   0.59   0.5093   0.62   9.2567     0.3939     0.9182       60      4094\n",
      "  BP       0.5242   0.52   0.4943   0.52  20.0852     0.3852     0.8041       60      4298\n",
      "  CC       0.6027   0.52   0.5030   0.60  15.7463     0.4286     0.8695       60      4365\n",
      "  میانگین   0.5616          0.5022\n",
      "  زمان آموزش  = 1240.6 ثانیه\n",
      "\n",
      "==================================================================\n",
      "QRes\n",
      "بودجهٔ پارامتر: {'trunk': 379136, 'to_angles': 5140, 'head': 309246, 'pqc': 90, 'total': 693612}\n",
      "==================================================================\n",
      "[QRes] Ep   1/20 | loss=0.9733 | val_Fmax=0.4164 (tau=0.51) | val_Smin=40.0634 | 52.3s ★\n",
      "[QRes] Ep   2/20 | loss=0.8471 | val_Fmax=0.4776 (tau=0.53) | val_Smin=38.2367 | 52.2s ★\n",
      "[QRes] Ep   3/20 | loss=0.8143 | val_Fmax=0.4970 (tau=0.54) | val_Smin=37.3567 | 52.2s ★\n",
      "[QRes] Ep   7/20 | loss=0.7495 | val_Fmax=0.5356 (tau=0.53) | val_Smin=36.0204 | 52.3s ★\n",
      "[QRes] Ep   8/20 | loss=0.7343 | val_Fmax=0.5397 (tau=0.53) | val_Smin=35.8880 | 52.2s ★\n",
      "[QRes] Ep   9/20 | loss=0.7211 | val_Fmax=0.5433 (tau=0.54) | val_Smin=35.7903 | 51.1s ★\n",
      "[QRes] Ep  10/20 | loss=0.7069 | val_Fmax=0.5469 (tau=0.53) | val_Smin=35.6842 | 51.7s ★\n",
      "[QRes] Ep  16/20 | loss=0.6369 | val_Fmax=0.5632 (tau=0.51) | val_Smin=35.3731 | 50.6s ★\n",
      "[QRes] Ep  17/20 | loss=0.6302 | val_Fmax=0.5650 (tau=0.52) | val_Smin=35.3427 | 50.7s ★\n",
      "[QRes] Ep  18/20 | loss=0.6246 | val_Fmax=0.5661 (tau=0.52) | val_Smin=35.3312 | 51.0s ★\n",
      "[QRes] Ep  19/20 | loss=0.6214 | val_Fmax=0.5667 (tau=0.51) | val_Smin=35.3262 | 51.8s ★\n",
      "[QRes] Ep  20/20 | loss=0.6201 | val_Fmax=0.5671 (tau=0.52) | val_Smin=35.3252 | 51.4s ★\n",
      "   جابه‌جاییِ پارامترهای کوانتومی: ||θ − θ₀|| = 1.863e-09  (نسبی: 0.0%)\n",
      "\n",
      "--- QRes ---\n",
      "  [تجمیعی]  F_max = 0.4638 (τ=0.59) | wF_max = 0.4039 | S_min = 35.5495\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5597   0.55   0.5088   0.63   9.1948     0.3965     0.9184       60      4094\n",
      "  BP       0.5243   0.53   0.4971   0.54  20.1778     0.3846     0.8038       60      4298\n",
      "  CC       0.6013   0.52   0.5013   0.58  15.7631     0.4307     0.8694       60      4365\n",
      "  میانگین   0.5618          0.5024\n",
      "  زمان آموزش  = 1033.0 ثانیه\n",
      "\n",
      "==================================================================\n",
      "PM-MLP\n",
      "بودجهٔ پارامتر: {'trunk': 379136, 'to_angles': 5140, 'head': 309246, 'total': 696987}\n",
      "==================================================================\n",
      "[PM-MLP] Ep   1/20 | loss=0.9659 | val_Fmax=0.4121 (tau=0.51) | val_Smin=40.8192 | 0.8s ★\n",
      "[PM-MLP] Ep   2/20 | loss=0.8478 | val_Fmax=0.4784 (tau=0.54) | val_Smin=38.4311 | 0.8s ★\n",
      "[PM-MLP] Ep   3/20 | loss=0.8157 | val_Fmax=0.4985 (tau=0.54) | val_Smin=37.4308 | 1.0s ★\n",
      "[PM-MLP] Ep   4/20 | loss=0.7950 | val_Fmax=0.5125 (tau=0.55) | val_Smin=36.8431 | 0.8s ★\n",
      "[PM-MLP] Ep   5/20 | loss=0.7774 | val_Fmax=0.5221 (tau=0.56) | val_Smin=36.4976 | 0.8s ★\n",
      "[PM-MLP] Ep   6/20 | loss=0.7609 | val_Fmax=0.5297 (tau=0.55) | val_Smin=36.1883 | 0.8s ★\n",
      "[PM-MLP] Ep   7/20 | loss=0.7456 | val_Fmax=0.5359 (tau=0.55) | val_Smin=36.0111 | 0.8s ★\n",
      "[PM-MLP] Ep   8/20 | loss=0.7323 | val_Fmax=0.5401 (tau=0.54) | val_Smin=35.8438 | 0.8s ★\n",
      "[PM-MLP] Ep   9/20 | loss=0.7173 | val_Fmax=0.5456 (tau=0.53) | val_Smin=35.7104 | 0.8s ★\n",
      "[PM-MLP] Ep  10/20 | loss=0.7032 | val_Fmax=0.5483 (tau=0.52) | val_Smin=35.6047 | 0.8s ★\n",
      "[PM-MLP] Ep  11/20 | loss=0.6898 | val_Fmax=0.5528 (tau=0.51) | val_Smin=35.5172 | 0.8s ★\n",
      "[PM-MLP] Ep  12/20 | loss=0.6762 | val_Fmax=0.5547 (tau=0.52) | val_Smin=35.4522 | 0.8s ★\n",
      "[PM-MLP] Ep  13/20 | loss=0.6639 | val_Fmax=0.5584 (tau=0.53) | val_Smin=35.3984 | 0.8s ★\n",
      "[PM-MLP] Ep  14/20 | loss=0.6517 | val_Fmax=0.5605 (tau=0.50) | val_Smin=35.3524 | 0.8s ★\n",
      "[PM-MLP] Ep  15/20 | loss=0.6407 | val_Fmax=0.5622 (tau=0.51) | val_Smin=35.3531 | 0.9s ★\n",
      "[PM-MLP] Ep  16/20 | loss=0.6321 | val_Fmax=0.5634 (tau=0.50) | val_Smin=35.3518 | 0.8s ★\n",
      "[PM-MLP] Ep  17/20 | loss=0.6244 | val_Fmax=0.5649 (tau=0.51) | val_Smin=35.3022 | 0.8s ★\n",
      "[PM-MLP] Ep  18/20 | loss=0.6198 | val_Fmax=0.5665 (tau=0.51) | val_Smin=35.3021 | 0.8s ★\n",
      "[PM-MLP] Ep  19/20 | loss=0.6163 | val_Fmax=0.5671 (tau=0.51) | val_Smin=35.2927 | 0.8s ★\n",
      "[PM-MLP] Ep  20/20 | loss=0.6151 | val_Fmax=0.5680 (tau=0.51) | val_Smin=35.2732 | 0.8s ★\n",
      "\n",
      "--- PM-MLP ---\n",
      "  [تجمیعی]  F_max = 0.4638 (τ=0.58) | wF_max = 0.4055 | S_min = 35.5733\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.5639   0.55   0.5079   0.65   9.2304     0.3969     0.9188       60      4094\n",
      "  BP       0.5245   0.50   0.4954   0.54  20.0842     0.3878     0.8062       60      4298\n",
      "  CC       0.6001   0.50   0.4982   0.62  15.7866     0.4318     0.8692       60      4365\n",
      "  میانگین   0.5628          0.5005\n",
      "  زمان آموزش  = 16.4 ثانیه\n",
      "\n",
      "============================================================\n",
      "اثر خالص لایهٔ کوانتومی      : -0.0022\n",
      "اثر آموزش‌پذیریِ مدار        : -0.0022\n",
      "============================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "\n",
    "# ============================================================================\n",
    "# VQNN (trainable circuit), QRes (circuit frozen at initialisation) and\n",
    "# PM-MLP (classical control matched in parameters, trunk, head and schedule).\n",
    "# ============================================================================\n",
    "\n",
    "def run_hybrid(tag, **kw):\n",
    "    freeze = kw.pop(\"freeze_quantum\", False)\n",
    "    ent = kw.pop(\"use_entangler\", True)\n",
    "    legacy = kw.pop(\"legacy\", False)\n",
    "    t0 = time.time()\n",
    "    if legacy:\n",
    "        model, hist, best, ep = train_legacy()\n",
    "    else:\n",
    "        model, hist, best, ep = train_model(tag=tag, _freeze=freeze, _entangler=ent, **kw)\n",
    "    sec = time.time() - t0\n",
    "    sc = predict(model, Xte)\n",
    "    rep = record(tag, sc, sec, n_params=sum(p.numel() for p in model.parameters()))\n",
    "    HISTORIES[tag] = hist\n",
    "    return model, sc, rep\n",
    "\n",
    "\n",
    "HISTORIES = {}\n",
    "\n",
    "\n",
    "# VQNN\n",
    "model_q, sc_q, rep_q = run_hybrid(\"VQNN\", use_quantum=True, seed=42)\n",
    "\n",
    "# QRes — θ منجمد\n",
    "model_r, sc_r, rep_r = run_hybrid(\"QRes\", use_quantum=True, seed=42,\n",
    "                                  freeze_quantum=True)\n",
    "\n",
    "\n",
    "# PM-MLP\n",
    "model_c, sc_c, rep_c = run_hybrid(\"PM-MLP\", use_quantum=False, seed=42)\n",
    "\n",
    "print(\"\\n\" + \"=\" * 60)\n",
    "print(f\"اثر خالص لایهٔ کوانتومی      : {rep_q['fmax'] - rep_c['fmax']:+.4f}\")\n",
    "print(f\"اثر آموزش‌پذیریِ مدار        : {rep_q['fmax'] - rep_r['fmax']:+.4f}\")\n",
    "print(\"=\" * 60)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "dfe626be",
   "metadata": {},
   "source": [
    "## 10. CQKR and Gate-0"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "e49900db",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:47:43.530701Z",
     "iopub.status.busy": "2026-09-27T15:47:43.529663Z",
     "iopub.status.idle": "2026-09-27T15:49:00.427027Z",
     "shell.execute_reply": "2026-09-27T15:49:00.426249Z",
     "shell.execute_reply.started": "2026-09-27T15:47:43.530669Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "==============================================================================\n",
      "CQKR — پیکربندی: {\"n_qubits\": 10, \"depth\": 2, \"bandwidth\": 0.35, \"entangler\": true, \"covariant\": true, \"n_cosets\": 8, \"n_landmarks\": 1024, \"ridge\": 1.0, \"seed\": 42, \"gate0_sample\": 512, \"bandwidth_grid\": [0.05, 0.1, 0.2, 0.35, 0.6, 1.0], \"gate0_min_std\": 0.01, \"gate0_min_kta_ratio\": 0.8}\n",
      "==============================================================================\n",
      "\n",
      "------------------------------------------------------------------------------------------------\n",
      "دروازهٔ صفر — آزمون تمرکز نمایی (پیش‌ثبت‌شده)، با جاروب bandwidth\n",
      "------------------------------------------------------------------------------------------------\n",
      "  مرجع کلاسیک: KTA هستهٔ RBF = 0.0984   |   کف تمرکز 2^-n = 0.000977\n",
      "   bandwidth   میانگین K      std K   std/2^-n      KTA   KTA/RBF   shots لازم   حکم\n",
      "  --------------------------------------------------------------------------------------------\n",
      "        0.05     0.83135    0.05645       57.8   0.0750      0.76        7,844   ❌ رد\n",
      "        0.10     0.48142    0.13276      135.9   0.0839      0.85        1,418   ✅ عبور\n",
      "        0.20     0.07359    0.08541       87.5   0.1056      1.07        3,427   ✅ عبور\n",
      "        0.35     0.00425    0.01163       11.9   0.1169      1.19      184,961   ✅ عبور\n",
      "        0.60     0.00121    0.00264        2.7   0.1152      1.17    3,575,234   ❌ رد\n",
      "        1.00     0.00104    0.00190        1.9   0.1141      1.16    6,934,585   ❌ رد\n",
      "------------------------------------------------------------------------------------------------\n",
      "✅ دروازهٔ صفر عبور شد. bandwidth انتخابی = 0.35 (بر پایهٔ KTA روی دادهٔ آموزش، بدون نگاه به آزمون)\n",
      "   برای اجرای واقعی روی سخت‌افزار، دست‌کم 184,961 shot به ازای هر درایهٔ\n",
      "   هسته لازم است تا سیگنال از نویز نمونه‌برداری تفکیک شود.\n",
      "------------------------------------------------------------------------------------------------\n",
      "\n",
      "--- CQKR — هستهٔ کوانتومی هم‌وردا ---\n",
      "  [تجمیعی]  F_max = 0.4823 (τ=0.23) | wF_max = 0.3902 | S_min = 36.9526\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6307   0.19   0.5493   0.19  10.8958     0.3700     0.8498       60      4094\n",
      "  BP       0.4875   0.17   0.4365   0.16  22.4863     0.3288     0.7352       60      4298\n",
      "  CC       0.6492   0.22   0.5182   0.19  14.7249     0.4888     0.8523       60      4365\n",
      "  میانگین   0.5891          0.5013\n",
      "  زمان آموزش  = 0.4 ثانیه\n",
      "\n",
      "--- کنترل: هستهٔ RBF روی همان ویژگی‌ها ---\n",
      "  [تجمیعی]  F_max = 0.5212 (τ=0.29) | wF_max = 0.4322 | S_min = 34.8298\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6685   0.22   0.5933   0.22  10.0486     0.4431     0.8964       60      4094\n",
      "  BP       0.5351   0.20   0.4872   0.19  20.6374     0.3899     0.7924       60      4298\n",
      "  CC       0.6903   0.28   0.5711   0.22  13.5988     0.5611     0.8842       60      4365\n",
      "  میانگین   0.6313          0.5505\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "اثر خالصِ هستهٔ کوانتومی (کوانتومی منهای RBF، ویژگی و خط لولهٔ یکسان): -0.0422\n",
      "زمان: کوانتومی 0.4s | کلاسیک 0.1s | کل سلول 9.4s\n",
      "این عدد تنها چیزی است که قابل نسبت‌دادن به نگاشت کوانتومی است.\n",
      "پیش از هر ادعا: پنج بذر + بوت‌استرپ زوجی + تفکیک آزمونِ مستقل.\n",
      "==============================================================================\n",
      "γ انتخاب‌شده توسط دروازه برای همهٔ بازوهای بعدی: 0.35\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی — کوانتومی منهای RBF (ویژگی، لندمارک و رگرسیونِ یکسان)\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0422   CI95 = [-0.0453, -0.0389]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0378   CI95 = [-0.0442, -0.0324]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0476   CI95 = [-0.0519, -0.0425]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0411   CI95 = [-0.0456, -0.0369]   p ≈ 0.000   معنادار\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "# ============================================================================\n",
    "# CQKR: covariant quantum kernel ridge regression (Nystrom, 1,024 landmarks)\n",
    "# against RBF-KR on the same ten PCA features.\n",
    "# Gate-0 admits a bandwidth only if std(K_offdiag) >= 0.010, std(K_offdiag) >= 2^-n\n",
    "# and KTA(K_q) >= 0.8 |KTA(K_RBF)| on 512 training proteins; among admitted\n",
    "# bandwidths the one with the largest KTA is used. Shots per kernel entry\n",
    "# for 5-sigma resolution: (5 / std(K_offdiag))^2.\n",
    "# ============================================================================\n",
    "\n",
    "import numpy as np, torch, time, json\n",
    "\n",
    "CQKR_CFG = dict(\n",
    "    n_qubits      = 10,\n",
    "    depth         = 2,       # بازآپلود داده (ZZFeatureMap استاندارد)\n",
    "    bandwidth     = 0.35,    # ⚠️ مهم‌ترین اهرم ضدِ تمرکز: زوایا × bandwidth\n",
    "    entangler     = True,    # لایهٔ ZZ\n",
    "    covariant     = True,    # ساختار هم‌دسته‌ای (هم‌وردا)\n",
    "    n_cosets      = 8,\n",
    "    n_landmarks   = 1024,    # نیستروم — ماتریس کامل N² شدنی نیست\n",
    "    ridge         = 1.0,\n",
    "    seed          = 42,\n",
    "    gate0_sample  = 512,\n",
    "    bandwidth_grid = (0.05, 0.10, 0.20, 0.35, 0.60, 1.00),\n",
    "    gate0_min_std        = 0.010,   # پراکندگی مطلقِ درایه‌های هسته\n",
    "    gate0_min_kta_ratio  = 0.80,    # نسبت به هستهٔ RBF روی همان ویژگی‌ها\n",
    ")\n",
    "\n",
    "_dev = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "_CD = torch.complex64\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۱) کاهش بُعد کلاسیک (PCA روی آموزش) — بدون نشت\n",
    "# ------------------------------------------------------------\n",
    "def _pca_fit(X, k, seed=42):\n",
    "    Xm = X.mean(axis=0, keepdims=True)\n",
    "    Xc = X - Xm\n",
    "    # SVD تصادفی‌شده برای سرعت\n",
    "    g = np.random.default_rng(seed)\n",
    "    Om = g.normal(size=(Xc.shape[1], k + 10)).astype(np.float32)\n",
    "    Q, _ = np.linalg.qr(Xc @ Om)\n",
    "    B = Q.T @ Xc\n",
    "    Ub, S, Vt = np.linalg.svd(B, full_matrices=False)\n",
    "    V = Vt[:k].T.astype(np.float32)\n",
    "    # استانداردسازیِ هر مؤلفه فقط با آمارِ آموزش (در V ادغام می‌شود)\n",
    "    s = np.std(Xc @ V, axis=0, keepdims=True); s[s < 1e-8] = 1.0\n",
    "    return Xm, (V / s).astype(np.float32)\n",
    "\n",
    "\n",
    "def _pca_apply(X, Xm, V):\n",
    "    return ((X - Xm) @ V).astype(np.float32)\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۲) نگاشت ویژگی ثابت → بردار حالت\n",
    "# ------------------------------------------------------------\n",
    "def _z_sign_table(n, device):\n",
    "    \"\"\"S[k, q] = ±1  — علامت Z کیوبیت q روی پایهٔ محاسباتی k.\"\"\"\n",
    "    idx = torch.arange(1 << n, device=device)\n",
    "    return torch.stack([1.0 - 2.0 * ((idx >> (n - 1 - q)) & 1).float()\n",
    "                        for q in range(n)], dim=1)          # (2^n, n)\n",
    "\n",
    "\n",
    "def _rz_phase_table(n, angles, device):\n",
    "    \"\"\"فاز قطریِ یک لایه Rz با زوایای ثابت — عنصر گروهیِ U_g.\"\"\"\n",
    "    S = _z_sign_table(n, device)\n",
    "    ph = (S * torch.as_tensor(angles, device=device, dtype=torch.float32)).sum(dim=1) * 0.5\n",
    "    return torch.exp(-1j * ph.to(_CD))\n",
    "\n",
    "\n",
    "def _hadamard_layer(psi, n):\n",
    "    \"\"\"اعمال H روی همهٔ کیوبیت‌ها (تبدیل هادامارد سریع).\"\"\"\n",
    "    B = psi.shape[0]\n",
    "    H = (torch.tensor([[1.0, 1.0], [1.0, -1.0]], dtype=_CD, device=psi.device)\n",
    "         / np.sqrt(2.0))\n",
    "    x = psi.reshape(B, *([2] * n))\n",
    "    for q in range(n):\n",
    "        x = torch.tensordot(x, H, dims=([q + 1], [1])).movedim(-1, q + 1)\n",
    "    return x.reshape(B, -1)\n",
    "\n",
    "\n",
    "class CQKRFeatureMap:\n",
    "    \"\"\"نگاشت ویژگیِ ثابت به سبک ZZFeatureMap:  [ U_Φ(x) · H^⊗n ]^depth\n",
    "\n",
    "    U_Φ(x) کاملاً قطری است:\n",
    "        Φ(x)_k = Σ_q φ_q(x)·z_q(k)  +  Σ_{(a,b)∈E} φ_ab(x)·z_a(k)·z_b(k)\n",
    "    با  φ_q = bw·x_q   و   φ_ab = bw²·x_a·x_b.\n",
    "    هیچ پارامتر آموزش‌پذیری وجود ندارد ⇒ بدون فلات بایر، آموزش محدب.\n",
    "    bandwidth تنها اهرم مؤثر علیه تمرکز نمایی است.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, cfg):\n",
    "        self.n = int(cfg[\"n_qubits\"]); self.depth = int(cfg[\"depth\"])\n",
    "        self.bw = float(cfg[\"bandwidth\"]); self.use_ent = bool(cfg[\"entangler\"])\n",
    "        self.cov = bool(cfg[\"covariant\"]); self.n_cosets = int(cfg[\"n_cosets\"])\n",
    "        rng = np.random.default_rng(cfg[\"seed\"])\n",
    "\n",
    "        self.edges = [(i, i + 1) for i in range(self.n - 1)] + [(0, self.n - 1)]\n",
    "        S = _z_sign_table(self.n, _dev)                      # (2^n, n)\n",
    "        self.S = S\n",
    "        self.Spair = torch.stack([S[:, a] * S[:, b] for (a, b) in self.edges], dim=1)  # (2^n, |E|)\n",
    "        self.coset_ph = (torch.stack([_rz_phase_table(self.n, rng.uniform(0, 2 * np.pi, self.n), _dev)\n",
    "                                      for _ in range(self.n_cosets)], dim=0)\n",
    "                         if self.cov else None)              # (n_cosets, 2^n)\n",
    "\n",
    "    def _diag(self, z):\n",
    "        \"\"\"z: (B, n) → فاز قطریِ داده-وابسته (B, 2^n).\"\"\"\n",
    "        phi1 = self.bw * z                                                  # (B, n)\n",
    "        ph = phi1 @ self.S.T                                                # (B, 2^n)\n",
    "        if self.use_ent:\n",
    "            phi2 = (self.bw ** 2) * torch.stack(\n",
    "                [z[:, a] * z[:, b] for (a, b) in self.edges], dim=1)        # (B, |E|)\n",
    "            ph = ph + phi2 @ self.Spair.T\n",
    "        return torch.exp(1j * ph.to(_CD))\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def states(self, Z, coset=None, batch=2048):\n",
    "        \"\"\"Z: (N, n) → Ψ: (N, 2^n) مختلط و نرمال.\"\"\"\n",
    "        out = []\n",
    "        for s in range(0, len(Z), batch):\n",
    "            z = torch.as_tensor(Z[s:s + batch], device=_dev, dtype=torch.float32)\n",
    "            z = torch.tanh(z) * np.pi                     # مهار دامنه پیش از bandwidth\n",
    "            B = z.shape[0]\n",
    "            psi = torch.zeros(B, 1 << self.n, dtype=_CD, device=_dev)\n",
    "            psi[:, 0] = 1.0\n",
    "            D = self._diag(z)\n",
    "            for _ in range(self.depth):\n",
    "                psi = _hadamard_layer(psi, self.n)\n",
    "                psi = psi * D\n",
    "            if self.coset_ph is not None and coset is not None:\n",
    "                g = torch.as_tensor(np.asarray(coset)[s:s + batch], device=_dev, dtype=torch.long)\n",
    "                psi = psi * self.coset_ph[g]\n",
    "            psi = psi / psi.norm(dim=1, keepdim=True).clamp_min(1e-12)\n",
    "            out.append(psi)\n",
    "        return torch.cat(out, dim=0)\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def fidelity_kernel(PA, PB, batch=4096):\n",
    "    \"\"\"K = |Ψ_A Ψ_B†|²  — کل ماتریس با یک ضرب ماتریسی.\"\"\"\n",
    "    outs = []\n",
    "    for s in range(0, PA.shape[0], batch):\n",
    "        ov = PA[s:s + batch] @ PB.conj().T\n",
    "        outs.append((ov.real ** 2 + ov.imag ** 2).float())\n",
    "    return torch.cat(outs, dim=0)\n",
    "\n",
    "\n",
    "def rbf_kernel(A, B, gamma):\n",
    "    d = torch.cdist(A, B) ** 2\n",
    "    return torch.exp(-gamma * d)\n",
    "\n",
    "\n",
    "def kernel_target_alignment(K, Y):\n",
    "    \"\"\"KTA = ⟨K, YYᵀ⟩ / (‖K‖_F ‖YYᵀ‖_F) — پیش‌بینی‌کنندهٔ ارزانِ کیفیت هسته.\"\"\"\n",
    "    T = Y @ Y.T\n",
    "    K = K - K.mean(); T = T - T.mean()\n",
    "    return float((K * T).sum() / (K.norm() * T.norm() + 1e-12))\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# اجرا\n",
    "# ============================================================\n",
    "def run_cqkr(cfg=None, tag_q=\"CQKR — هستهٔ کوانتومی هم‌وردا\",\n",
    "             tag_c=\"کنترل: هستهٔ RBF روی همان ویژگی‌ها\"):\n",
    "    cfg = dict(CQKR_CFG, **(cfg or {}))\n",
    "    rng = np.random.default_rng(cfg[\"seed\"])\n",
    "    t_all = time.time()\n",
    "    print(\"=\" * 78); print(\"CQKR — پیکربندی:\", json.dumps(cfg, ensure_ascii=False)); print(\"=\" * 78)\n",
    "\n",
    "    # --- ویژگی مشترک برای هر دو هسته (کنترل کامل) ---\n",
    "    Xm, V = _pca_fit(Xtr.numpy(), cfg[\"n_qubits\"], cfg[\"seed\"])\n",
    "    Ztr = _pca_apply(Xtr.numpy(), Xm, V)\n",
    "    Zte = _pca_apply(Xte.numpy(), Xm, V)\n",
    "\n",
    "    # --- هم‌دسته‌ها فقط از x محاسبه می‌شوند (بدون نشت برچسب) ---\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztr)\n",
    "    ctr, cte = km.labels_, km.predict(Zte)\n",
    "\n",
    "\n",
    "    # ------------------------------------------------------------\n",
    "    # دروازهٔ صفر — جاروب bandwidth و آزمون تمرکز، پیش از هر آموزشی\n",
    "    # ------------------------------------------------------------\n",
    "    m = min(cfg[\"gate0_sample\"], len(Ztr))\n",
    "    sub = rng.choice(len(Ztr), size=m, replace=False)\n",
    "    A = torch.as_tensor(Ztr[sub], device=_dev)\n",
    "    g_med = 1.0 / (2.0 * (torch.cdist(A, A) ** 2).median().clamp_min(1e-9))\n",
    "    Kc = rbf_kernel(A, A, g_med)\n",
    "    Ysub = torch.as_tensor(Ytr.numpy()[sub], device=_dev)\n",
    "    kta_c = kernel_target_alignment(Kc, Ysub)\n",
    "    off = ~torch.eye(m, dtype=torch.bool, device=_dev)\n",
    "    floor = 2.0 ** (-cfg[\"n_qubits\"])\n",
    "\n",
    "    print(\"\\n\" + \"-\" * 96)\n",
    "    print(\"دروازهٔ صفر — آزمون تمرکز نمایی (پیش‌ثبت‌شده)، با جاروب bandwidth\")\n",
    "    print(\"-\" * 96)\n",
    "    print(f\"  مرجع کلاسیک: KTA هستهٔ RBF = {kta_c:.4f}   |   کف تمرکز 2^-n = {floor:.6f}\")\n",
    "    print(f\"  {'bandwidth':>10}{'میانگین K':>12}{'std K':>11}{'std/2^-n':>11}\"\n",
    "          f\"{'KTA':>9}{'KTA/RBF':>10}{'shots لازم':>13}   حکم\")\n",
    "    print(\"  \" + \"-\" * 92)\n",
    "\n",
    "    rows, best = [], None\n",
    "    for bw in cfg[\"bandwidth_grid\"]:\n",
    "        fm_b = CQKRFeatureMap(dict(cfg, bandwidth=bw))\n",
    "        Pb = fm_b.states(Ztr[sub], ctr[sub])\n",
    "        Kq = fidelity_kernel(Pb, Pb)\n",
    "        mu, sd = Kq[off].mean().item(), Kq[off].std().item()\n",
    "        kta_q = kernel_target_alignment(Kq, Ysub)\n",
    "        shots = int((5.0 / max(sd, 1e-9)) ** 2)          # برای تفکیک ۵σ روی سخت‌افزار\n",
    "        ok_abs = sd >= cfg[\"gate0_min_std\"]\n",
    "        ok_flr = sd >= floor\n",
    "        ok_kta = kta_q >= cfg[\"gate0_min_kta_ratio\"] * abs(kta_c)\n",
    "        ok = ok_abs and ok_flr and ok_kta\n",
    "        rows.append(dict(bw=bw, mean=mu, std=sd, kta=kta_q, shots=shots, ok=ok,\n",
    "                         ok_abs=ok_abs, ok_flr=ok_flr, ok_kta=ok_kta))\n",
    "        print(f\"  {bw:>10.2f}{mu:>12.5f}{sd:>11.5f}{sd/floor:>11.1f}\"\n",
    "              f\"{kta_q:>9.4f}{kta_q/max(abs(kta_c),1e-12):>10.2f}{shots:>13,}\"\n",
    "              f\"   {'✅ عبور' if ok else '❌ رد'}\")\n",
    "        if ok and (best is None or kta_q > best[\"kta\"]):\n",
    "            best = rows[-1]\n",
    "        del Pb, Kq\n",
    "\n",
    "    print(\"-\" * 96)\n",
    "    if best is None:\n",
    "        worst = max(rows, key=lambda r: r[\"kta\"])\n",
    "        print(\"❌ دروازهٔ صفر رد شد — هیچ bandwidth ای هر سه معیار را برآورده نکرد.\")\n",
    "        for nm, k in ((\"پراکندگی مطلق ≥ %.3f\" % cfg[\"gate0_min_std\"], \"ok_abs\"),\n",
    "                      (\"پراکندگی ≥ کف تمرکز\", \"ok_flr\"),\n",
    "                      (\"KTA ≥ %.0f%% RBF\" % (100 * cfg[\"gate0_min_kta_ratio\"]), \"ok_kta\")):\n",
    "            print(f\"   {'✅' if worst[k] else '❌'} {nm}   (در بهترین bandwidth = {worst['bw']})\")\n",
    "        print(\"\\n   این یک نتیجهٔ منفیِ قابل انتشار است، نه یک شکست اجرایی:\")\n",
    "        print(\"   «هستهٔ کوانتومی روی این بازنمایی متمرکز می‌شود / با هدف هم‌تراز نیست».\")\n",
    "        print(\"   گزینه‌های جایگزین: کاهش n_qubits، یا نگاشت ویژگیِ\")\n",
    "        print(\"   واقعاً هم‌وردا با یک گروهِ اثبات‌شده در داده — نه تنظیم بیشترِ bandwidth.\")\n",
    "        print(\"=\" * 96)\n",
    "        return {\"passed\": False, \"sweep\": rows, \"kta_c\": kta_c}\n",
    "\n",
    "    print(f\"✅ دروازهٔ صفر عبور شد. bandwidth انتخابی = {best['bw']} \"\n",
    "          f\"(بر پایهٔ KTA روی دادهٔ آموزش، بدون نگاه به آزمون)\")\n",
    "    print(f\"   برای اجرای واقعی روی سخت‌افزار، دست‌کم {best['shots']:,} shot به ازای هر درایهٔ\")\n",
    "    print(\"   هسته لازم است تا سیگنال از نویز نمونه‌برداری تفکیک شود.\")\n",
    "    print(\"-\" * 96)\n",
    "\n",
    "    cfg[\"bandwidth\"] = best[\"bw\"]\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    Ptr = fm.states(Ztr, ctr); Pte = fm.states(Zte, cte)\n",
    "    disp, kta_q = best[\"std\"] / max(best[\"mean\"], 1e-12), best[\"kta\"]\n",
    "\n",
    "    # ------------------------------------------------------------\n",
    "    # نیستروم + رگرسیون ستیغی (محدب، جواب بسته)\n",
    "    # ------------------------------------------------------------\n",
    "    M = min(cfg[\"n_landmarks\"], len(Ztr))\n",
    "    lm = rng.choice(len(Ztr), size=M, replace=False)\n",
    "    Ytr_t = torch.as_tensor(Ytr.numpy(), device=_dev)\n",
    "\n",
    "    def fit_predict(Kmm, Knm_tr, Knm_te, name):\n",
    "        S, U = torch.linalg.eigh(Kmm.double() + 1e-6 * torch.eye(M, device=_dev, dtype=torch.float64))\n",
    "        S = S.clamp_min(1e-8)\n",
    "        Wht = (U @ torch.diag(S.rsqrt()) @ U.T).float()      # K_mm^{-1/2}\n",
    "        Ztr_f, Zte_f = Knm_tr @ Wht, Knm_te @ Wht\n",
    "        G = Ztr_f.T @ Ztr_f + cfg[\"ridge\"] * torch.eye(M, device=_dev)\n",
    "        W = torch.linalg.solve(G, Ztr_f.T @ Ytr_t)\n",
    "        return (Zte_f @ W).clamp(0, 1).cpu().numpy()\n",
    "\n",
    "    # هستهٔ کوانتومی\n",
    "    t0 = time.time()\n",
    "    sc_q = fit_predict(fidelity_kernel(Ptr[lm], Ptr[lm]),\n",
    "                       fidelity_kernel(Ptr, Ptr[lm]),\n",
    "                       fidelity_kernel(Pte, Ptr[lm]), \"q\")\n",
    "    sec_q = time.time() - t0\n",
    "\n",
    "    # کنترل کلاسیک — عیناً همان ویژگی، همان لندمارک، همان رگرسیون\n",
    "    t0 = time.time()\n",
    "    Atr, Ate, Alm = (torch.as_tensor(Ztr, device=_dev), torch.as_tensor(Zte, device=_dev),\n",
    "                     torch.as_tensor(Ztr[lm], device=_dev))\n",
    "    gm = 1.0 / (2.0 * (torch.cdist(Alm, Alm) ** 2).median().clamp_min(1e-9))\n",
    "    sc_c = fit_predict(rbf_kernel(Alm, Alm, gm), rbf_kernel(Atr, Alm, gm),\n",
    "                       rbf_kernel(Ate, Alm, gm), \"c\")\n",
    "    sec_c = time.time() - t0\n",
    "\n",
    "    rep_q = record(tag_q, sc_q, sec_q, n_params=M * N_LABELS)\n",
    "    rep_c = record(tag_c, sc_c, sec_c, n_params=M * N_LABELS)\n",
    "\n",
    "    K = \"fmax_aspect_mean\"\n",
    "    d = rep_q.get(K, rep_q[\"fmax\"]) - rep_c.get(K, rep_c[\"fmax\"])\n",
    "    print(\"\\n\" + \"=\" * 78)\n",
    "    print(f\"اثر خالصِ هستهٔ کوانتومی (کوانتومی منهای RBF، ویژگی و خط لولهٔ یکسان): {d:+.4f}\")\n",
    "    print(f\"زمان: کوانتومی {sec_q:.1f}s | کلاسیک {sec_c:.1f}s | کل سلول {time.time()-t_all:.1f}s\")\n",
    "    print(\"این عدد تنها چیزی است که قابل نسبت‌دادن به نگاشت کوانتومی است.\")\n",
    "    print(\"پیش از هر ادعا: پنج بذر + بوت‌استرپ زوجی + تفکیک آزمونِ مستقل.\")\n",
    "    print(\"=\" * 78)\n",
    "    return {\"passed\": True, \"sweep\": rows, \"bandwidth\": cfg[\"bandwidth\"], \"delta\": d, \"dispersion\": disp,\n",
    "            \"kta_q\": kta_q, \"kta_c\": kta_c, \"rep_q\": rep_q, \"rep_c\": rep_c}\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# آزمون آماری — بوت‌استرپ زوجی روی پروتئین‌ها\n",
    "# ============================================================\n",
    "#\n",
    "# چرا زوجی: دو مدل روی *همان* پروتئین‌ها ارزیابی می‌شوند و به‌شدت\n",
    "# همبسته‌اند، پس خطای معیارِ «تفاوت» بسیار کوچک‌تر از خطای معیارِ هر\n",
    "# کدام به‌تنهایی است. این تنها آزمونی است که می‌تواند اثرهای در حد\n",
    "# ۰٫۰۰۵ را از نویز جدا کند.\n",
    "#\n",
    "# آستانهٔ بهینه τ در *هر* بازنمونه از نو جست‌وجو می‌شود؛ در غیر این صورت\n",
    "# سوگیریِ «انتخاب آستانه روی همان داده» وارد بازهٔ اطمینان می‌شود.\n",
    "# ============================================================\n",
    "def paired_bootstrap(name_a, name_b, cols=None, B=200, seed=0, label=\"\"):\n",
    "    ya, yb = SCORES[name_a], SCORES[name_b]\n",
    "    Yt = np.asarray(Yte_np)\n",
    "    g = np.random.default_rng(seed)\n",
    "    N = len(Yt)\n",
    "\n",
    "    def score(idx, sc):\n",
    "        if cols is None:\n",
    "            vals = [protein_centric(Yt[idx], sc[idx], IC, cols=ASPECT_COLS[a])[\"fmax\"]\n",
    "                    for a in ASPECTS if len(ASPECT_COLS[a])]\n",
    "            vals = [v for v in vals if v == v]\n",
    "            return float(np.mean(vals)) if vals else float(\"nan\")\n",
    "        return protein_centric(Yt[idx], sc[idx], IC, cols=cols)[\"fmax\"]\n",
    "\n",
    "    base_idx = np.arange(N)\n",
    "    obs = score(base_idx, ya) - score(base_idx, yb)\n",
    "    diffs = np.empty(B)\n",
    "    for b in range(B):\n",
    "        idx = g.integers(0, N, size=N)\n",
    "        diffs[b] = score(idx, ya) - score(idx, yb)\n",
    "    lo, hi = np.percentile(diffs, [2.5, 97.5])\n",
    "    p = 2 * min((diffs <= 0).mean(), (diffs >= 0).mean())\n",
    "    tag = f\" [{label}]\" if label else \"\"\n",
    "    print(f\"  Δ{tag} = {obs:+.4f}   CI95 = [{lo:+.4f}, {hi:+.4f}]   p ≈ {p:.3f}   \"\n",
    "          + (\"معنادار\" if lo * hi > 0 else \"غیرمعنادار — صفر داخل بازه است\"))\n",
    "    return dict(obs=float(obs), lo=float(lo), hi=float(hi), p=float(p))\n",
    "\n",
    "\n",
    "CQKR_RESULT = run_cqkr()\n",
    "if CQKR_RESULT.get(\"passed\"):\n",
    "    CQKR_CFG[\"bandwidth\"] = CQKR_RESULT[\"bandwidth\"]   # همهٔ سلول‌های بعدی همین γ را به کار می‌برند\n",
    "    print(f\"γ انتخاب‌شده توسط دروازه برای همهٔ بازوهای بعدی: {CQKR_CFG['bandwidth']}\")\n",
    "\n",
    "if CQKR_RESULT.get(\"passed\"):\n",
    "    A, Bn = \"CQKR — هستهٔ کوانتومی هم‌وردا\", \"کنترل: هستهٔ RBF روی همان ویژگی‌ها\"\n",
    "    print(\"\\n\" + \"=\" * 96)\n",
    "    print(\"بوت‌استرپ زوجی — کوانتومی منهای RBF (ویژگی، لندمارک و رگرسیونِ یکسان)\")\n",
    "    print(\"=\" * 96)\n",
    "    BOOT = {\"کلی\": paired_bootstrap(A, Bn, None, label=\"میانگین سه جنبه\")}\n",
    "    for a in ASPECTS:\n",
    "        if len(ASPECT_COLS[a]):\n",
    "            BOOT[a] = paired_bootstrap(A, Bn, ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "    if \"TERM_STRATA\" in globals():\n",
    "        print(\"  \" + \"-\" * 92)\n",
    "        print(\"  به تفکیک کم‌یابیِ اصطلاح — همان‌جا که فرضیهٔ روش می‌گوید اثر باید دیده شود:\")\n",
    "        for k in [\"خیلی کم‌تکرار\", \"کم‌تکرار\", \"متوسط\", \"پرتکرار\"]:\n",
    "            if k in TERM_STRATA and len(TERM_STRATA[k]):\n",
    "                BOOT[k] = paired_bootstrap(A, Bn, TERM_STRATA[k], label=k)\n",
    "    print(\"=\" * 96)\n",
    "    print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\")\n",
    "    print(\"=\" * 96)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5e80d90b",
   "metadata": {},
   "source": [
    "## 11. QK-NN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "dee48545",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T16:54:42.273276Z",
     "iopub.status.busy": "2026-09-26T16:54:42.272588Z",
     "iopub.status.idle": "2026-09-26T16:55:59.341059Z",
     "shell.execute_reply": "2026-09-26T16:55:59.340279Z",
     "shell.execute_reply.started": "2026-09-26T16:54:42.273250Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "\n",
      "--- QK-NN — k-NN با هستهٔ کوانتومی هم‌وردا (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.4977 (τ=0.33) | wF_max = 0.4050 | S_min = 36.0332\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6486   0.27   0.5741   0.27  10.3087     0.3957     0.8471       60      4094\n",
      "  BP       0.5015   0.24   0.4486   0.23  21.7848     0.3415     0.7475       60      4298\n",
      "  CC       0.6675   0.29   0.5395   0.26  14.3245     0.5123     0.8543       60      4365\n",
      "  میانگین   0.6059          0.5207\n",
      "  زمان آموزش  = 0.4 ثانیه\n",
      "\n",
      "--- کنترل: k-NN با هستهٔ RBF روی همان ویژگیِ PCA-۱۰بعدی (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.5161 (τ=0.33) | wF_max = 0.4308 | S_min = 34.9142\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6692   0.27   0.5983   0.27   9.8923     0.4271     0.8708       60      4094\n",
      "  BP       0.5251   0.21   0.4759   0.21  21.0556     0.3715     0.7694       60      4298\n",
      "  CC       0.6842   0.27   0.5623   0.27  13.8734     0.5456     0.8689       60      4365\n",
      "  میانگین   0.6262          0.5455\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "اثر خالصِ هستهٔ کوانتومی در k-NN (کوانتومی منهای RBF، همان k، همان بُعد): -0.0203\n",
      "مرجع غیرهم‌بُعد — k-NN کسینوسی روی ۳۴۴بعدی خام: 0.6663  (این ردیف برای زمینه است، نه برای ادعای علّی)\n",
      "زمان: کوانتومی 0.4s | کنترل RBF 0.1s\n",
      "==============================================================================\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی — QK-NN منهای کنترل RBF (همان k، همان بُعد، همان الگوریتمِ وزن‌دهی)\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0203   CI95 = [-0.0240, -0.0169]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0206   CI95 = [-0.0263, -0.0144]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0236   CI95 = [-0.0299, -0.0180]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0167   CI95 = [-0.0212, -0.0122]   p ≈ 0.000   معنادار\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\n",
      "هشدار: k=۱۵ و bandwidth=۰٫۳۵ هر دو پیش از دیدن این نتیجه ثابت شده بودند؛ هیچ\n",
      "جست‌وجوی ابرپارامتری تازه‌ای برای این آزمایش انجام نشد. با این حال این هنوز یک\n",
      "بذر و یک تفکیک است (همان Xte=Xva شناخته‌شده) — برای ادعای نهایی با ۵ بذر متفاوت\n",
      "(CQKR_CFG['seed']) تکرار و پراکندگیِ نتیجه گزارش شود.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "\n",
    "# ============================================================================\n",
    "# QK-NN: similarity-weighted k-NN (k = 15) on the covariant quantum kernel,\n",
    "# against RBF-kNN-10 on the same ten features.\n",
    "# ============================================================================\n",
    "\n",
    "assert \"CQKR_RESULT\" in globals() and CQKR_RESULT.get(\"passed\"), (\n",
    "    \"این سلول باید بعد از سلولِ CQKR اجرا شود و دروازهٔ صفر باید از قبل عبور کرده باشد.\"\n",
    ")\n",
    "\n",
    "QKNN_CFG = dict(CQKR_CFG)     # bandwidth عبورکرده از Gate-0 در سلولِ CQKR — تنظیم‌نشده اینجا\n",
    "K_KNN = 15                                     # ثابت، همان k خط‌پایه — تنظیم‌نشده\n",
    "\n",
    "\n",
    "def _build_states(cfg):\n",
    "    \"\"\"عیناً همان سه گامِ CQKR: PCA بدون‌نشت → هم‌دسته‌های KMeans → حالت‌های کوانتومی.\"\"\"\n",
    "    Xm, V = _pca_fit(Xtr.numpy(), cfg[\"n_qubits\"], cfg[\"seed\"])\n",
    "    Ztr = _pca_apply(Xtr.numpy(), Xm, V)\n",
    "    Zte = _pca_apply(Xte.numpy(), Xm, V)\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztr)\n",
    "    ctr, cte = km.labels_, km.predict(Zte)\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    Ptr = fm.states(Ztr, ctr)\n",
    "    Pte = fm.states(Zte, cte)\n",
    "    return Ztr, Zte, Ptr, Pte\n",
    "\n",
    "\n",
    "Ztr_q, Zte_q, Ptr_q, Pte_q = _build_states(QKNN_CFG)\n",
    "\n",
    "\n",
    "def weighted_knn_from_similarity(sim, Ytr_t, K, chunk=256):\n",
    "    \"\"\"عیناً الگوریتم knn_scores: وزن = شباهتِ نامنفی، نرمال‌شده به یک.\n",
    "\n",
    "    sim   : (Nte, Ntr) — از پیش محاسبه‌شده (کسینوسی/RBF/کوانتومی، فرقی نمی‌کند)\n",
    "    Ytr_t : (Ntr, N_LABELS) روی همان device\n",
    "    \"\"\"\n",
    "    out = []\n",
    "    N = sim.shape[0]\n",
    "    for s in range(0, N, chunk):\n",
    "        block = sim[s:s + chunk]\n",
    "        w, idx = torch.topk(block, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Ytr_t[idx]).cpu().numpy())\n",
    "    return np.concatenate(out)\n",
    "\n",
    "\n",
    "Ytr_t = torch.as_tensor(Ytr.numpy(), device=_dev)\n",
    "\n",
    "# ---------------- (الف) QK-NN: شباهت = هستهٔ کوانتومی هم‌وردا ----------------\n",
    "t0 = time.time()\n",
    "Kte_tr_q = fidelity_kernel(Pte_q, Ptr_q)                          # (Nte, Ntr) در [۰,۱]\n",
    "sc_qknn = weighted_knn_from_similarity(Kte_tr_q, Ytr_t, K_KNN)\n",
    "sec_qknn = time.time() - t0\n",
    "NAME_QKNN = \"QK-NN — k-NN با هستهٔ کوانتومی هم‌وردا (k=۱۵)\"\n",
    "record(NAME_QKNN, sc_qknn, sec_qknn, n_params=0)\n",
    "del Kte_tr_q\n",
    "\n",
    "# ---------------- (ب) کنترل هم‌بُعد: شباهت = RBF روی عیناً همان ویژگیِ PCA-۱۰بعدی ----------------\n",
    "# gamma با میانهٔ فاصله روی یک زیرنمونهٔ تصادفی هم‌تراز می‌شود — عیناً روش کنترلِ CQKR،\n",
    "# نه یک انتخابِ جدید برای این آزمایش.\n",
    "rng_q = np.random.default_rng(QKNN_CFG[\"seed\"])\n",
    "sub_q = rng_q.choice(len(Ztr_q), size=min(1024, len(Ztr_q)), replace=False)\n",
    "Atr_q = torch.as_tensor(Ztr_q, device=_dev)\n",
    "Ate_q = torch.as_tensor(Zte_q, device=_dev)\n",
    "A_sub = Atr_q[sub_q]\n",
    "g_med_q = 1.0 / (2.0 * (torch.cdist(A_sub, A_sub) ** 2).median().clamp_min(1e-9))\n",
    "\n",
    "t0 = time.time()\n",
    "Kte_tr_rbf = rbf_kernel(Ate_q, Atr_q, g_med_q)\n",
    "sc_rbfknn = weighted_knn_from_similarity(Kte_tr_rbf, Ytr_t, K_KNN)\n",
    "sec_rbfknn = time.time() - t0\n",
    "NAME_RBFKNN = \"کنترل: k-NN با هستهٔ RBF روی همان ویژگیِ PCA-۱۰بعدی (k=۱۵)\"\n",
    "record(NAME_RBFKNN, sc_rbfknn, sec_rbfknn, n_params=0)\n",
    "del Kte_tr_rbf\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# اثر خالص + مرجع غیرهم‌بُعد\n",
    "# ------------------------------------------------------------\n",
    "d_qknn = RESULTS[NAME_QKNN][\"fmax_aspect_mean\"] - RESULTS[NAME_RBFKNN][\"fmax_aspect_mean\"]\n",
    "ref_cos = RESULTS.get(\"k نزدیک‌ترین همسایه (k=۱۵)\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 78)\n",
    "print(f\"اثر خالصِ هستهٔ کوانتومی در k-NN (کوانتومی منهای RBF، همان k، همان بُعد): {d_qknn:+.4f}\")\n",
    "if ref_cos is not None:\n",
    "    print(f\"مرجع غیرهم‌بُعد — k-NN کسینوسی روی ۳۴۴بعدی خام: \"\n",
    "          f\"{ref_cos['fmax_aspect_mean']:.4f}  (این ردیف برای زمینه است، نه برای ادعای علّی)\")\n",
    "print(f\"زمان: کوانتومی {sec_qknn:.1f}s | کنترل RBF {sec_rbfknn:.1f}s\")\n",
    "print(\"=\" * 78)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آزمون آماری — بوت‌استرپ زوجی (عیناً روش CQKR)\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"=\" * 96)\n",
    "print(\"بوت‌استرپ زوجی — QK-NN منهای کنترل RBF (همان k، همان بُعد، همان الگوریتمِ وزن‌دهی)\")\n",
    "print(\"=\" * 96)\n",
    "BOOT_QKNN = {\"کلی\": paired_bootstrap(NAME_QKNN, NAME_RBFKNN, None, label=\"میانگین سه جنبه\")}\n",
    "for a in ASPECTS:\n",
    "    if len(ASPECT_COLS[a]):\n",
    "        BOOT_QKNN[a] = paired_bootstrap(NAME_QKNN, NAME_RBFKNN, ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "if \"TERM_STRATA\" in globals():\n",
    "    print(\"  \" + \"-\" * 92)\n",
    "    print(\"  به تفکیک کم‌یابیِ اصطلاح:\")\n",
    "    for k in [\"خیلی کم‌تکرار\", \"کم‌تکرار\", \"متوسط\", \"پرتکرار\"]:\n",
    "        if k in TERM_STRATA and len(TERM_STRATA[k]):\n",
    "            BOOT_QKNN[k] = paired_bootstrap(NAME_QKNN, NAME_RBFKNN, TERM_STRATA[k], label=k)\n",
    "print(\"=\" * 96)\n",
    "print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\")\n",
    "print(\"هشدار: k=۱۵ و bandwidth=۰٫۳۵ هر دو پیش از دیدن این نتیجه ثابت شده بودند؛ هیچ\")\n",
    "print(\"جست‌وجوی ابرپارامتری تازه‌ای برای این آزمایش انجام نشد. با این حال این هنوز یک\")\n",
    "print(\"بذر و یک تفکیک است (همان Xte=Xva شناخته‌شده) — برای ادعای نهایی با ۵ بذر متفاوت\")\n",
    "print(\"(CQKR_CFG['seed']) تکرار و پراکندگیِ نتیجه گزارش شود.\")\n",
    "print(\"=\" * 96)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "959a2eff",
   "metadata": {},
   "source": [
    "## 12. QFE-kNN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "81749fd8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T16:55:59.342648Z",
     "iopub.status.busy": "2026-09-26T16:55:59.342311Z",
     "iopub.status.idle": "2026-09-26T16:57:56.892268Z",
     "shell.execute_reply": "2026-09-26T16:57:56.891484Z",
     "shell.execute_reply.started": "2026-09-26T16:55:59.342624Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "\n",
      "--- QFE-kNN — ویژگیِ کوانتومیِ آموزش‌دیده (مدارِ VQNN) + k-NN (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.4787 (τ=0.33) | wF_max = 0.3860 | S_min = 36.6786\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6259   0.27   0.5521   0.27  10.4388     0.3694     0.8405       60      4094\n",
      "  BP       0.4820   0.26   0.4279   0.26  22.2042     0.3238     0.7316       60      4298\n",
      "  CC       0.6441   0.27   0.5091   0.27  14.9266     0.4748     0.8385       60      4365\n",
      "  میانگین   0.5840          0.4964\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "--- کنترل: ویژگیِ جانشینِ کلاسیکِ هم‌بُعد (هم‌پارامتر) + k-NN (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.5455 (τ=0.33) | wF_max = 0.4736 | S_min = 32.7066\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7215   0.27   0.6674   0.27   8.4897     0.4832     0.8957       60      4094\n",
      "  BP       0.5600   0.26   0.5172   0.26  19.4134     0.4207     0.7992       60      4298\n",
      "  CC       0.7016   0.27   0.5934   0.27  13.2670     0.5774     0.8832       60      4365\n",
      "  میانگین   0.6610          0.5927\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "--- اکتشافی: ویژگیِ QRes + k-NN (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.4756 (τ=0.33) | wF_max = 0.3837 | S_min = 36.8295\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6324   0.27   0.5580   0.27  10.5407     0.3704     0.8379       60      4094\n",
      "  BP       0.4763   0.21   0.4247   0.20  22.6369     0.3137     0.7214       60      4298\n",
      "  CC       0.6476   0.27   0.5149   0.27  14.8428     0.4923     0.8437       60      4365\n",
      "  میانگین   0.5854          0.4992\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "اثر خالصِ ویژگیِ کوانتومیِ آموزش‌دیده در k-NN (کوانتومی منهای جانشینِ کلاسیکِ هم‌بُعد): -0.0771\n",
      "مرجع — همان مدار، با سرِ آموزش‌دیدهٔ اصلی (VQNN): 0.5616  (نشان می‌دهد سرِ آموزش‌دیده در برابر k-NN چه می‌کند — نه ادعای علّیِ کوانتومی)\n",
      "مرجع — k-NN کسینوسی روی ۴۸۰ ویژگیِ خام (بدون مهندسیِ کوانتومی): 0.6663\n",
      "زمان: کوانتومی 0.1s | کنترل کلاسیک 0.1s | اکتشافیِ مخزن 0.1s\n",
      "==============================================================================\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی — QFE-kNN منهای کنترل جانشینِ کلاسیکِ هم‌بُعد\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0771   CI95 = [-0.0819, -0.0721]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0957   CI95 = [-0.1030, -0.0869]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0781   CI95 = [-0.0875, -0.0701]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0575   CI95 = [-0.0638, -0.0511]   p ≈ 0.000   معنادار\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\n",
      "هشدار: این ویژگی‌ها برای مصرفِ یک سرِ خطیِ آموزش‌پذیر بهینه شده‌اند، نه برای k-NN —\n",
      "پس این «استفادهٔ دوبارهٔ» یک بازنماییِ از‌پیش‌آموخته‌شده است، نه بهینه‌سازیِ مستقیم برای\n",
      "k-NN. و مثل سلول‌های ۱۱ و ۱۲، این هنوز یک بذر و یک تفکیک است — برای ادعای نهایی با\n",
      "چند بذرِ متفاوت تکرار و پراکندگیِ نتیجه گزارش شود.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "# ============================================================================\n",
    "# QFE-kNN: k-NN on the 165 quantum features of the trained VQNN, against\n",
    "# CSF-kNN on classical surrogate features of the same width.\n",
    "# ============================================================================\n",
    "\n",
    "assert all(v in globals() for v in (\"model_q\", \"model_c\", \"model_r\")), (\n",
    "    \"این سلول باید بعد از سلولِ VQNN، QRes و PM-MLP اجرا شود.\"\n",
    ")\n",
    "assert \"paired_bootstrap\" in globals(), (\n",
    "    \"این سلول به paired_bootstrap از سلولِ CQKR نیاز دارد.\"\n",
    ")\n",
    "\n",
    "K_QFE = 15   # همان k خط‌پایه — تنظیم‌نشده\n",
    "_Ytr_t_qfe = torch.as_tensor(Ytr_np, device=DEVICE)\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def extract_block(model, X, mode, batch=512):\n",
    "    \"\"\"خروجیِ بلوکِ میانیِ مدل (کوانتومی یا جانشینِ کلاسیک) را استخراج می‌کند —\n",
    "    بدون عبور از سرِ دسته‌بندیِ آموزش‌دیدهٔ آن.\"\"\"\n",
    "    model.eval()\n",
    "    outs = []\n",
    "    for s in range(0, len(X), batch):\n",
    "        xb = X[s:s + batch].to(DEVICE)\n",
    "        h = model.trunk(xb)\n",
    "        z = np.pi * torch.tanh(model.to_angles(h))\n",
    "        q = model.pqc(z) if mode == \"quantum\" else model.classical_surrogate(z)\n",
    "        outs.append(q.detach().cpu())\n",
    "    return torch.cat(outs, dim=0)\n",
    "\n",
    "\n",
    "def cosine_knn_scores(Ftr, Fte, K=K_QFE, chunk=256):\n",
    "    \"\"\"عیناً الگوریتمِ knn_scores — فقط روی ویژگیِ داده‌شده، نه Xtr خام.\"\"\"\n",
    "    A = Ftr.to(DEVICE); A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    Q = Fte.to(DEVICE); Q = Q / (Q.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    t0 = time.time()\n",
    "    out = []\n",
    "    for s in range(0, len(Q), chunk):\n",
    "        sim = Q[s:s + chunk] @ A.T\n",
    "        w, idx = torch.topk(sim, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, _Ytr_t_qfe[idx]).cpu().numpy())\n",
    "    return np.concatenate(out), time.time() - t0\n",
    "\n",
    "\n",
    "# ---------------- (الف) QFE-kNN: ویژگیِ کوانتومیِ آموزش‌دیده (model_q) ----------------\n",
    "Qtr_quant = extract_block(model_q, Xtr, \"quantum\")\n",
    "Qte_quant = extract_block(model_q, Xte, \"quantum\")\n",
    "sc_qfe, sec_qfe = cosine_knn_scores(Qtr_quant, Qte_quant)\n",
    "NAME_QFE = \"QFE-kNN — ویژگیِ کوانتومیِ آموزش‌دیده (مدارِ VQNN) + k-NN (k=۱۵)\"\n",
    "record(NAME_QFE, sc_qfe, sec_qfe, n_params=0)\n",
    "\n",
    "# ---------------- (ب) کنترل هم‌بُعد: جانشین PM-MLP (model_c) ----------------\n",
    "Qtr_surr = extract_block(model_c, Xtr, \"surrogate\")\n",
    "Qte_surr = extract_block(model_c, Xte, \"surrogate\")\n",
    "sc_cfe, sec_cfe = cosine_knn_scores(Qtr_surr, Qte_surr)\n",
    "NAME_CFE = \"کنترل: ویژگیِ جانشینِ کلاسیکِ هم‌بُعد (هم‌پارامتر) + k-NN (k=۱۵)\"\n",
    "record(NAME_CFE, sc_cfe, sec_cfe, n_params=0)\n",
    "\n",
    "# ---------------- (پ) اکتشافی: مخزن کوانتومی با θ منجمد (model_r) + k-NN ----------------\n",
    "# کنترلِ کاملاً هم‌شرط برای این ردیف در دسترس نیست (هیچ «جانشینِ کلاسیکِ\n",
    "# فریزشده»‌ای در سلول‌های قبلی آموزش داده نشده) — فقط برای زمینه بخوان.\n",
    "Qtr_res = extract_block(model_r, Xtr, \"quantum\")\n",
    "Qte_res = extract_block(model_r, Xte, \"quantum\")\n",
    "sc_rfe, sec_rfe = cosine_knn_scores(Qtr_res, Qte_res)\n",
    "NAME_RFE = \"اکتشافی: ویژگیِ QRes + k-NN (k=۱۵)\"\n",
    "record(NAME_RFE, sc_rfe, sec_rfe, n_params=0)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# اثر خالص + مرجع‌ها\n",
    "# ------------------------------------------------------------\n",
    "d_qfe = RESULTS[NAME_QFE][\"fmax_aspect_mean\"] - RESULTS[NAME_CFE][\"fmax_aspect_mean\"]\n",
    "ref_head    = RESULTS.get(\"VQNN\")\n",
    "ref_knn_raw = RESULTS.get(\"k نزدیک‌ترین همسایه (k=۱۵)\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 78)\n",
    "print(f\"اثر خالصِ ویژگیِ کوانتومیِ آموزش‌دیده در k-NN (کوانتومی منهای جانشینِ کلاسیکِ هم‌بُعد): {d_qfe:+.4f}\")\n",
    "if ref_head is not None:\n",
    "    print(f\"مرجع — همان مدار، با سرِ آموزش‌دیدهٔ اصلی (VQNN): {ref_head['fmax_aspect_mean']:.4f}  \"\n",
    "          f\"(نشان می‌دهد سرِ آموزش‌دیده در برابر k-NN چه می‌کند — نه ادعای علّیِ کوانتومی)\")\n",
    "if ref_knn_raw is not None:\n",
    "    print(f\"مرجع — k-NN کسینوسی روی ۴۸۰ ویژگیِ خام (بدون مهندسیِ کوانتومی): {ref_knn_raw['fmax_aspect_mean']:.4f}\")\n",
    "print(f\"زمان: کوانتومی {sec_qfe:.1f}s | کنترل کلاسیک {sec_cfe:.1f}s | اکتشافیِ مخزن {sec_rfe:.1f}s\")\n",
    "print(\"=\" * 78)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آزمون آماری — بوت‌استرپ زوجی (عیناً روش CQKR و QK-NN)\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"=\" * 96)\n",
    "print(\"بوت‌استرپ زوجی — QFE-kNN منهای کنترل جانشینِ کلاسیکِ هم‌بُعد\")\n",
    "print(\"=\" * 96)\n",
    "BOOT_QFE = {\"کلی\": paired_bootstrap(NAME_QFE, NAME_CFE, None, label=\"میانگین سه جنبه\")}\n",
    "for a in ASPECTS:\n",
    "    if len(ASPECT_COLS[a]):\n",
    "        BOOT_QFE[a] = paired_bootstrap(NAME_QFE, NAME_CFE, ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "print(\"=\" * 96)\n",
    "print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\")\n",
    "print(\"هشدار: این ویژگی‌ها برای مصرفِ یک سرِ خطیِ آموزش‌پذیر بهینه شده‌اند، نه برای k-NN —\")\n",
    "print(\"پس این «استفادهٔ دوبارهٔ» یک بازنماییِ از‌پیش‌آموخته‌شده است، نه بهینه‌سازیِ مستقیم برای\")\n",
    "print(\"k-NN. و مثل سلول‌های ۱۱ و ۱۲، این هنوز یک بذر و یک تفکیک است — برای ادعای نهایی با\")\n",
    "print(\"چند بذرِ متفاوت تکرار و پراکندگیِ نتیجه گزارش شود.\")\n",
    "print(\"=\" * 96)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "433987a3",
   "metadata": {},
   "source": [
    "## 13. K-Mix (kernel-level fusion)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "b261cb8b",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T16:57:56.893738Z",
     "iopub.status.busy": "2026-09-26T16:57:56.893363Z",
     "iopub.status.idle": "2026-09-26T16:59:13.879339Z",
     "shell.execute_reply": "2026-09-26T16:59:13.878495Z",
     "shell.execute_reply.started": "2026-09-26T16:57:56.893715Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "محاسبهٔ شباهتِ کسینوسیِ خام (اعتبارسنجی و آزمون) ...\n",
      "محاسبهٔ هستهٔ کوانتومی (اعتبارسنجی و آزمون) ...\n",
      "  زمانِ محاسبهٔ هر دو هسته: 1.7 ثانیه\n",
      "\n",
      "جست‌وجوی وزنِ ترکیب α روی Xva/Yva:\n",
      "  α=0.00 → val F_max(میانگین سه جنبه) = 0.6041\n",
      "  α=0.25 → val F_max(میانگین سه جنبه) = 0.6103\n",
      "  α=0.50 → val F_max(میانگین سه جنبه) = 0.6195\n",
      "  α=0.75 → val F_max(میانگین سه جنبه) = 0.6389\n",
      "  α=1.00 → val F_max(میانگین سه جنبه) = 0.6684\n",
      "بهترین α روی اعتبارسنجی: 1.00\n",
      "\n",
      "--- K-Mix — ترکیب در سطحِ هسته (α=1.00، انتخاب‌شده روی اعتبارسنجی) ---\n",
      "  [تجمیعی]  F_max = 0.5533 (τ=0.34) | wF_max = 0.4816 | S_min = 32.3032\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7293   0.27   0.6769   0.27   8.3536     0.5126     0.9062       60      4094\n",
      "  BP       0.5624   0.21   0.5206   0.20  19.6910     0.4259     0.8033       60      4298\n",
      "  CC       0.7073   0.27   0.5994   0.27  12.9286     0.5852     0.8873       60      4365\n",
      "  میانگین   0.6663          0.5990\n",
      "  زمان آموزش  = 0.0 ثانیه\n",
      "\n",
      "--- کنترل: k-NN فقط کسینوسیِ خام (α=۱، بدون کوانتومی، همان بُعدِ PCA-۱۰) ---\n",
      "  [تجمیعی]  F_max = 0.5533 (τ=0.34) | wF_max = 0.4816 | S_min = 32.3032\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7293   0.27   0.6769   0.27   8.3536     0.5126     0.9062       60      4094\n",
      "  BP       0.5624   0.21   0.5206   0.20  19.6910     0.4259     0.8033       60      4298\n",
      "  CC       0.7073   0.27   0.5994   0.27  12.9286     0.5852     0.8873       60      4365\n",
      "  میانگین   0.6663          0.5990\n",
      "  زمان آموزش  = 0.1 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "اثر خالصِ افزودنِ کوانتومی به کسینوسیِ خام (بلند منهای کنترلِ فقط-کسینوسی): +0.0000\n",
      "مرجع — k-NN کسینوسی روی ۴۸۰ ویژگیِ خام، بدون کاهشِ PCA: 0.6663\n",
      "زمان: محاسبهٔ هر دو هسته 1.7s | k-NN نهایی 0.0s\n",
      "==============================================================================\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی — بلند منهای کنترلِ فقط-کسینوسی (همان بُعدِ PCA، همان k)\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = +0.0000   CI95 = [+0.0000, +0.0000]   p ≈ 2.000   غیرمعنادار — صفر داخل بازه است\n",
      "  Δ [MF] = +0.0000   CI95 = [+0.0000, +0.0000]   p ≈ 2.000   غیرمعنادار — صفر داخل بازه است\n",
      "  Δ [BP] = +0.0000   CI95 = [+0.0000, +0.0000]   p ≈ 2.000   غیرمعنادار — صفر داخل بازه است\n",
      "  Δ [CC] = +0.0000   CI95 = [+0.0000, +0.0000]   p ≈ 2.000   غیرمعنادار — صفر داخل بازه است\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\n",
      "هشدار: α روی Xva/Yva انتخاب شد، نه Xte — بدون نشت. اما این هنوز یک بذر و یک\n",
      "تفکیک است؛ برای ادعای نهایی با چند بذرِ متفاوت تکرار و پراکندگیِ α و نتیجه گزارش شود.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "# ============================================================================\n",
    "# K-Mix: kernel-level mixing K_alpha = alpha K_cos + (1 - alpha) K_q,\n",
    "# against Cos-kNN.\n",
    "# ============================================================================\n",
    "\n",
    "assert all(v in globals() for v in\n",
    "           (\"CQKRFeatureMap\", \"fidelity_kernel\", \"_pca_fit\", \"_pca_apply\",\n",
    "            \"CQKR_CFG\", \"CQKR_RESULT\", \"paired_bootstrap\")), (\n",
    "    \"این سلول باید بعد از سلولِ CQKR در همین کرنل اجرا شود.\"\n",
    ")\n",
    "assert CQKR_RESULT.get(\"passed\"), \"دروازهٔ صفر رد شده — این آزمایش را نمی‌توان معتبر انجام داد.\"\n",
    "\n",
    "BLEND_ALPHAS = [0.0, 0.25, 0.5, 0.75, 1.0]   # ۰ = فقط کوانتومی، ۱ = فقط کسینوسیِ خام\n",
    "K_BLEND = 15                                  # همان k خط‌پایه — تنظیم‌نشده\n",
    "\n",
    "\n",
    "def _raw_cos_sim(Xa, Xb):\n",
    "    \"\"\"شباهتِ کسینوسیِ (len(Xa), len(Xb)) — عیناً knn_scores.\"\"\"\n",
    "    A = torch.as_tensor(Xa, device=DEVICE); A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    B = torch.as_tensor(Xb, device=DEVICE); B = B / (B.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    return A @ B.T\n",
    "\n",
    "\n",
    "def _quantum_kernel_sim(Zquery, Ztrain, cfg):\n",
    "    \"\"\"هستهٔ کوانتومیِ (len(Zquery), len(Ztrain)) — عیناً _build_states در QK-NN،\n",
    "    با این تفاوت که هم‌دسته‌ها همیشه روی Ztrain آموزش/برازش می‌شوند.\"\"\"\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztrain)\n",
    "    c_train = km.labels_\n",
    "    c_query = km.predict(Zquery)\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    P_train = fm.states(Ztrain, c_train)\n",
    "    P_query = fm.states(Zquery, c_query)\n",
    "    return fidelity_kernel(P_query, P_train)\n",
    "\n",
    "\n",
    "def _knn_from_sim(sim_qt, Ytr_t, K=K_BLEND, chunk=256):\n",
    "    \"\"\"sim_qt: (N_query, N_train) شباهت — هرچه بزرگ‌تر، نزدیک‌تر.\"\"\"\n",
    "    out = []\n",
    "    for s in range(0, sim_qt.shape[0], chunk):\n",
    "        block = sim_qt[s:s + chunk]\n",
    "        w, idx = torch.topk(block, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Ytr_t[idx]).cpu().numpy())\n",
    "    return np.concatenate(out)\n",
    "\n",
    "\n",
    "# ---------------- آماده‌سازی: PCA بدون‌نشت + شباهتِ کسینوسیِ خام + هستهٔ کوانتومی ----------------\n",
    "Xm_b, V_b = _pca_fit(Xtr.numpy(), CQKR_CFG[\"n_qubits\"], CQKR_CFG[\"seed\"])\n",
    "Ztr_b = _pca_apply(Xtr.numpy(), Xm_b, V_b)\n",
    "Zva_b = _pca_apply(Xva.numpy(), Xm_b, V_b)\n",
    "Zte_b = _pca_apply(Xte.numpy(), Xm_b, V_b)\n",
    "\n",
    "Ytr_t_b = torch.as_tensor(Ytr_np, device=DEVICE)\n",
    "\n",
    "print(\"محاسبهٔ شباهتِ کسینوسیِ خام (اعتبارسنجی و آزمون) ...\")\n",
    "Sva_cos = torch.clamp(_raw_cos_sim(Xva.numpy(), Xtr.numpy()), min=0.0)\n",
    "Ste_cos = torch.clamp(_raw_cos_sim(Xte.numpy(), Xtr.numpy()), min=0.0)\n",
    "\n",
    "print(\"محاسبهٔ هستهٔ کوانتومی (اعتبارسنجی و آزمون) ...\")\n",
    "t0 = time.time()\n",
    "Sva_q = _quantum_kernel_sim(Zva_b, Ztr_b, CQKR_CFG)\n",
    "Ste_q = _quantum_kernel_sim(Zte_b, Ztr_b, CQKR_CFG)\n",
    "sec_kernels = time.time() - t0\n",
    "print(f\"  زمانِ محاسبهٔ هر دو هسته: {sec_kernels:.1f} ثانیه\")\n",
    "\n",
    "# ---------------- جست‌وجوی α روی اعتبارسنجی (بدون نگاه به آزمون) ----------------\n",
    "print(\"\\nجست‌وجوی وزنِ ترکیب α روی Xva/Yva:\")\n",
    "best_alpha, best_f = None, -1.0\n",
    "for a in BLEND_ALPHAS:\n",
    "    sim = a * Sva_cos + (1 - a) * Sva_q\n",
    "    sc = _knn_from_sim(sim, Ytr_t_b)\n",
    "    f = fmax(Yva_np, sc)\n",
    "    print(f\"  α={a:.2f} → val F_max(میانگین سه جنبه) = {f:.4f}\")\n",
    "    if f > best_f:\n",
    "        best_f, best_alpha = f, a\n",
    "print(f\"بهترین α روی اعتبارسنجی: {best_alpha:.2f}\")\n",
    "\n",
    "# ---------------- ارزیابی نهایی روی آزمون با α انتخاب‌شده ----------------\n",
    "t0 = time.time()\n",
    "sim_te = best_alpha * Ste_cos + (1 - best_alpha) * Ste_q\n",
    "sc_blend = _knn_from_sim(sim_te, Ytr_t_b)\n",
    "sec_blend = time.time() - t0\n",
    "NAME_BLEND = f\"K-Mix — ترکیب در سطحِ هسته (α={best_alpha:.2f}، انتخاب‌شده روی اعتبارسنجی)\"\n",
    "record(NAME_BLEND, sc_blend, sec_blend, n_params=0)\n",
    "\n",
    "# ---------------- کنترل: فقط کسینوسیِ خام (α=۱، همان بُعدِ PCA-۱۰) ----------------\n",
    "t0 = time.time()\n",
    "sc_pure_cos = _knn_from_sim(Ste_cos, Ytr_t_b)\n",
    "sec_pure_cos = time.time() - t0\n",
    "NAME_PURECOS = \"کنترل: k-NN فقط کسینوسیِ خام (α=۱، بدون کوانتومی، همان بُعدِ PCA-۱۰)\"\n",
    "record(NAME_PURECOS, sc_pure_cos, sec_pure_cos, n_params=0)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# اثر خالص + مرجع\n",
    "# ------------------------------------------------------------\n",
    "d_blend = RESULTS[NAME_BLEND][\"fmax_aspect_mean\"] - RESULTS[NAME_PURECOS][\"fmax_aspect_mean\"]\n",
    "ref_raw480 = RESULTS.get(\"k نزدیک‌ترین همسایه (k=۱۵)\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 78)\n",
    "print(f\"اثر خالصِ افزودنِ کوانتومی به کسینوسیِ خام (بلند منهای کنترلِ فقط-کسینوسی): {d_blend:+.4f}\")\n",
    "if ref_raw480 is not None:\n",
    "    print(f\"مرجع — k-NN کسینوسی روی ۴۸۰ ویژگیِ خام، بدون کاهشِ PCA: \"\n",
    "          f\"{ref_raw480['fmax_aspect_mean']:.4f}\")\n",
    "print(f\"زمان: محاسبهٔ هر دو هسته {sec_kernels:.1f}s | k-NN نهایی {sec_blend:.1f}s\")\n",
    "print(\"=\" * 78)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آزمون آماری — بوت‌استرپ زوجی\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"=\" * 96)\n",
    "print(\"بوت‌استرپ زوجی — بلند منهای کنترلِ فقط-کسینوسی (همان بُعدِ PCA، همان k)\")\n",
    "print(\"=\" * 96)\n",
    "BOOT_BLEND = {\"کلی\": paired_bootstrap(NAME_BLEND, NAME_PURECOS, None, label=\"میانگین سه جنبه\")}\n",
    "for a in ASPECTS:\n",
    "    if len(ASPECT_COLS[a]):\n",
    "        BOOT_BLEND[a] = paired_bootstrap(NAME_BLEND, NAME_PURECOS, ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "print(\"=\" * 96)\n",
    "print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل ادعاست.\")\n",
    "print(\"هشدار: α روی Xva/Yva انتخاب شد، نه Xte — بدون نشت. اما این هنوز یک بذر و یک\")\n",
    "print(\"تفکیک است؛ برای ادعای نهایی با چند بذرِ متفاوت تکرار و پراکندگیِ α و نتیجه گزارش شود.\")\n",
    "print(\"=\" * 96)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4537d77d",
   "metadata": {},
   "source": [
    "## 14. RUQK-NN"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "4f52bffb",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T05:16:34.539305Z",
     "iopub.status.busy": "2026-09-27T05:16:34.538661Z",
     "iopub.status.idle": "2026-09-27T05:19:12.770516Z",
     "shell.execute_reply": "2026-09-27T05:19:12.769595Z",
     "shell.execute_reply.started": "2026-09-27T05:16:34.539280Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "==============================================================================\n",
      "RUQK-NN — پیکربندی: {\"n_qubits\": 10, \"bandwidth\": 0.35, \"entangler\": true, \"covariant\": false, \"seed\": 42, \"gate0_sample\": 512, \"bandwidth_grid\": [0.05, 0.1, 0.2, 0.35, 0.6, 1.0], \"gate0_min_std\": 0.01, \"gate0_min_kta_ratio\": 0.8, \"K_KNN\": 15}\n",
      "==============================================================================\n",
      "\n",
      "------------------------------------------------------------------------------------------------\n",
      "دروازهٔ صفر RUQK-NN — آزمونِ تمرکزِ نمایی، با ۴۸ لایهٔ بازآپلود (نه ۲ مثلِ CQKR)\n",
      "------------------------------------------------------------------------------------------------\n",
      "  مرجعِ کلاسیک: KTA هستهٔ RBF روی همان ۴۸۰ ویژگی = 0.1161   |   کفِ تمرکز 2^-n = 0.000977\n",
      "   bandwidth   میانگین K      std K   std/2^-n      KTA   KTA/RBF   حکم\n",
      "  --------------------------------------------------------------------------------------------\n",
      "        0.05     0.03270    0.05258       53.8   0.1067      0.92   ✅ عبور\n",
      "        0.10     0.00138    0.00481        4.9   0.1166      1.00   ❌ رد\n",
      "        0.20     0.00098    0.00116        1.2   0.1160      1.00   ❌ رد\n",
      "        0.35     0.00098    0.00098        1.0   0.1162      1.00   ❌ رد\n",
      "        0.60     0.00098    0.00098        1.0   0.1159      1.00   ❌ رد\n",
      "        1.00     0.00098    0.00097        1.0   0.1160      1.00   ❌ رد\n",
      "------------------------------------------------------------------------------------------------\n",
      "✅ دروازهٔ صفر عبور شد. bandwidth انتخابی = 0.05 (بر پایهٔ KTA روی دادهٔ آموزش، بدون نگاه به آزمون)\n",
      "------------------------------------------------------------------------------------------------\n",
      "ساختِ بردارهایِ حالت برایِ کلِ داده: 8.1 ثانیه\n",
      "\n",
      "--- RUQK-NN — هستهٔ کوانتومیِ بازآپلودشده روی ۴۸۰ ویژگیِ خام ---\n",
      "  [تجمیعی]  F_max = 0.5038 (τ=0.34) | wF_max = 0.4148 | S_min = 35.6118\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6629   0.27   0.5933   0.27   9.9551     0.4148     0.8579       60      4094\n",
      "  BP       0.5024   0.22   0.4541   0.21  21.8079     0.3469     0.7485       60      4298\n",
      "  CC       0.6685   0.28   0.5432   0.27  14.1908     0.5198     0.8567       60      4365\n",
      "  میانگین   0.6113          0.5302\n",
      "  زمان آموزش  = 8.5 ثانیه\n",
      "\n",
      "--- کنترل: RBF k-NN روی عیناً همان ۴۸۰ ویژگیِ خامِ استانداردشده (k=۱۵) ---\n",
      "  [تجمیعی]  F_max = 0.5549 (τ=0.34) | wF_max = 0.4858 | S_min = 32.1862\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7372   0.27   0.6873   0.27   8.1215     0.5160     0.9080       60      4094\n",
      "  BP       0.5689   0.27   0.5259   0.26  19.4783     0.4277     0.8057       60      4298\n",
      "  CC       0.7098   0.27   0.6047   0.27  12.8352     0.5856     0.8873       60      4365\n",
      "  میانگین   0.6719          0.6060\n",
      "  زمان آموزش  = 0.2 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "RUQK-NN منهای کنترلِ اصلی (کسینوسیِ خام، همان k): -0.0550\n",
      "RUQK-NN منهای کنترلِ منصفانهٔ دوم (RBF روی همان ۴۸۰ ویژگی، همان k): -0.0607\n",
      "زمان: RUQK-NN 8.5s | RBF-480 0.2s | کلِ سلول 15.5s\n",
      "این دو عدد تنها چیزی‌اند که قابل‌نسبت‌دادن به فرضیهٔ «رفعِ گلوگاهِ اطلاعات» است.\n",
      "==============================================================================\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی (۱) — RUQK-NN منهای کنترلِ اصلی (کسینوسیِ خام)\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0550   CI95 = [-0.0589, -0.0507]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0663   CI95 = [-0.0735, -0.0580]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0600   CI95 = [-0.0658, -0.0531]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0388   CI95 = [-0.0431, -0.0340]   p ≈ 0.000   معنادار\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی (۲) — RUQK-NN منهای کنترلِ منصفانهٔ دوم (RBF روی همان ۴۸۰ ویژگی)\n",
      "================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0607   CI95 = [-0.0648, -0.0562]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0742   CI95 = [-0.0827, -0.0658]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0664   CI95 = [-0.0730, -0.0598]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0413   CI95 = [-0.0464, -0.0359]   p ≈ 0.000   معنادار\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل‌ادعاست. این یک بذر و یک تفکیک\n",
      "است — برای هر ادعای نهایی با چند بذرِ متفاوت (RUQK_CFG['seed']) تکرار و\n",
      "پراکندگیِ نتیجه گزارش شود. رفعِ گلوگاهِ اطلاعات شرطِ لازم است، نه شرطِ کافی.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "# ============================================================================\n",
    "# RUQK-NN: 48-layer data re-uploading kernel over all 480 standardised\n",
    "# features (fixed permutation, 48 chunks of 10, no cosets), screened by\n",
    "# Gate-0, against RBF-kNN-480 and Cos-kNN.\n",
    "# ============================================================================\n",
    "\n",
    "assert all(v in globals() for v in\n",
    "           (\"CQKRFeatureMap\", \"_z_sign_table\", \"_rz_phase_table\", \"_hadamard_layer\",\n",
    "            \"fidelity_kernel\", \"rbf_kernel\", \"kernel_target_alignment\",\n",
    "            \"CQKR_CFG\", \"CQKR_RESULT\", \"paired_bootstrap\", \"_dev\", \"_CD\")), (\n",
    "    \"این سلول باید بعد از سلولِ CQKR در همین کرنل اجرا شود.\"\n",
    ")\n",
    "assert CQKR_RESULT.get(\"passed\"), \"دروازهٔ صفر CQKR رد شده — این آزمایش را نمی‌توان معتبر انجام داد.\"\n",
    "assert \"k نزدیک‌ترین همسایه (k=۱۵)\" in SCORES, (\n",
    "    \"کنترلِ اصلی (Cos-kNN) در SCORES پیدا نشد — \"\n",
    "    \"سلول‌های ۲ تا ۵ باید در همین کرنل اجرا شده باشند.\"\n",
    ")\n",
    "\n",
    "import numpy as np, torch, time, json\n",
    "\n",
    "RUQK_CFG = dict(\n",
    "    n_qubits      = CQKR_CFG[\"n_qubits\"],          # ۱۰ — عیناً همان CQKR، تنظیمِ تازه‌ای نیست\n",
    "    bandwidth     = CQKR_CFG[\"bandwidth\"],          # مقدارِ اولیه؛ دروازهٔ صفر آن را جاروب می‌کند\n",
    "    entangler     = CQKR_CFG[\"entangler\"],          # همان ساختارِ درون‌قطعه‌ایِ ZZ\n",
    "    covariant     = False,                          # عمداً خاموش — نبود تا با یک متغیرِ دومِ\n",
    "                                                     # آزمون‌نشده نتیجه را قاطی نکند\n",
    "    seed          = CQKR_CFG[\"seed\"],\n",
    "    gate0_sample  = CQKR_CFG[\"gate0_sample\"],\n",
    "    bandwidth_grid       = CQKR_CFG[\"bandwidth_grid\"],       # عیناً همان شبکه — بدون تنظیمِ تازه\n",
    "    gate0_min_std        = CQKR_CFG[\"gate0_min_std\"],        # عیناً همان آستانه\n",
    "    gate0_min_kta_ratio  = CQKR_CFG[\"gate0_min_kta_ratio\"],  # عیناً همان آستانه\n",
    "    K_KNN         = 15,      # همان k خط‌پایه — تنظیم‌نشده\n",
    ")\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۱) استانداردسازیِ کلاسیکِ کل ۴۸۰ ویژگی (بدون PCA، بدون نشت) + جایگشتِ ثابت\n",
    "# ------------------------------------------------------------\n",
    "def _std_fit(X):\n",
    "    mu = X.mean(axis=0, keepdims=True)\n",
    "    sd = X.std(axis=0, keepdims=True)\n",
    "    sd[sd < 1e-8] = 1.0\n",
    "    return mu.astype(np.float32), sd.astype(np.float32)\n",
    "\n",
    "\n",
    "def _std_apply(X, mu, sd, perm):\n",
    "    Z = (X - mu) / sd\n",
    "    return Z[:, perm].astype(np.float32)\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۲) نگاشتِ ویژگیِ «بازآپلودشده» — همان بلوکِ CQKR، ۴۸ بار با قطعه‌های متفاوت\n",
    "# ------------------------------------------------------------\n",
    "class RUQKFeatureMap:\n",
    "    \"\"\"Ψ(x) = [U_L(x^(L))·H^⊗n] ⋯ [U_1(x^(1))·H^⊗n] |0⟩\n",
    "\n",
    "    x^(1)..x^(L) پارتیشن‌های متوالیِ ۴۸۰ ویژگیِ خامِ استانداردشده‌اند\n",
    "    (بعد از یک جایگشتِ ثابت)، هرکدام دقیقاً n_qubits بُعد. هر Uˡ عیناً\n",
    "    همان نگاشتِ قطریِ CQKRFeatureMap._diag است. برخلافِ `depth` در CQKR\n",
    "    (تکرارِ همان بردار)، این‌جا هر لایه یک قطعهٔ *متفاوت* می‌بیند — پس\n",
    "    صددرصدِ ۴۸۰ بُعد از مدار عبور می‌کند، نه فقط ۱۰ بُعدِ فشرده‌شده.\n",
    "    هیچ پارامترِ آموزش‌پذیری وجود ندارد؛ bandwidth تنها اهرمِ ضدِ تمرکز است.\n",
    "    \"\"\"\n",
    "\n",
    "    def __init__(self, cfg, n_features):\n",
    "        self.n = int(cfg[\"n_qubits\"])\n",
    "        self.bw = float(cfg[\"bandwidth\"])\n",
    "        self.use_ent = bool(cfg[\"entangler\"])\n",
    "        assert n_features % self.n == 0, (\n",
    "            f\"تعدادِ ویژگی‌ها ({n_features}) باید مضربِ n_qubits ({self.n}) باشد.\"\n",
    "        )\n",
    "        self.n_layers = n_features // self.n\n",
    "\n",
    "        self.edges = [(i, i + 1) for i in range(self.n - 1)] + [(0, self.n - 1)]\n",
    "        S = _z_sign_table(self.n, _dev)\n",
    "        self.S = S\n",
    "        self.Spair = torch.stack([S[:, a] * S[:, b] for (a, b) in self.edges], dim=1)\n",
    "\n",
    "    def _diag(self, z_chunk):\n",
    "        \"\"\"z_chunk: (B, n) قطعهٔ یک لایه → فازِ قطریِ داده‌وابسته (B, 2^n).\"\"\"\n",
    "        phi1 = self.bw * z_chunk\n",
    "        ph = phi1 @ self.S.T\n",
    "        if self.use_ent:\n",
    "            phi2 = (self.bw ** 2) * torch.stack(\n",
    "                [z_chunk[:, a] * z_chunk[:, b] for (a, b) in self.edges], dim=1)\n",
    "            ph = ph + phi2 @ self.Spair.T\n",
    "        return torch.exp(1j * ph.to(_CD))\n",
    "\n",
    "    @torch.no_grad()\n",
    "    def states(self, Zfull, batch=1024):\n",
    "        \"\"\"Zfull: (N, n_layers*n) استانداردشده (پیش از تانژانتِ هذلولی) → Ψ: (N, 2^n).\"\"\"\n",
    "        out = []\n",
    "        for s in range(0, len(Zfull), batch):\n",
    "            zf = torch.as_tensor(Zfull[s:s + batch], device=_dev, dtype=torch.float32)\n",
    "            zf = torch.tanh(zf) * np.pi                    # مهارِ دامنه، عیناً CQKR\n",
    "            B = zf.shape[0]\n",
    "            psi = torch.zeros(B, 1 << self.n, dtype=_CD, device=_dev)\n",
    "            psi[:, 0] = 1.0\n",
    "            for l in range(self.n_layers):\n",
    "                chunk = zf[:, l * self.n:(l + 1) * self.n]\n",
    "                psi = _hadamard_layer(psi, self.n)\n",
    "                psi = psi * self._diag(chunk)\n",
    "            psi = psi / psi.norm(dim=1, keepdim=True).clamp_min(1e-12)\n",
    "            out.append(psi)\n",
    "        return torch.cat(out, dim=0)\n",
    "\n",
    "\n",
    "def _knn_topk_predict(sim_qt, Ytr_t, K, chunk=256):\n",
    "    \"\"\"sim_qt: (N_query, N_train) شباهت — هرچه بزرگ‌تر، نزدیک‌تر.\"\"\"\n",
    "    out = []\n",
    "    for s in range(0, sim_qt.shape[0], chunk):\n",
    "        block = sim_qt[s:s + chunk]\n",
    "        w, idx = torch.topk(block, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Ytr_t[idx]).cpu().numpy())\n",
    "    return np.concatenate(out)\n",
    "\n",
    "\n",
    "# ============================================================\n",
    "# اجرا\n",
    "# ============================================================\n",
    "def run_ruqk(cfg=None):\n",
    "    cfg = dict(RUQK_CFG, **(cfg or {}))\n",
    "    rng = np.random.default_rng(cfg[\"seed\"])\n",
    "    t_all = time.time()\n",
    "    print(\"=\" * 78); print(\"RUQK-NN — پیکربندی:\", json.dumps(cfg, ensure_ascii=False)); print(\"=\" * 78)\n",
    "\n",
    "    # --- استانداردسازیِ کلِ ۴۸۰ ویژگی (برازش فقط روی آموزش) + جایگشتِ ثابت ---\n",
    "    mu, sd = _std_fit(Xtr.numpy())\n",
    "    perm = rng.permutation(IN_DIM)\n",
    "    Ztr_full = _std_apply(Xtr.numpy(), mu, sd, perm)\n",
    "    Zte_full = _std_apply(Xte.numpy(), mu, sd, perm)\n",
    "\n",
    "    # ------------------------------------------------------------\n",
    "    # دروازهٔ صفر — عیناً همان آستانه‌ها و شبکهٔ bandwidthِ CQKR\n",
    "    # ------------------------------------------------------------\n",
    "    m = min(cfg[\"gate0_sample\"], len(Ztr_full))\n",
    "    sub = rng.choice(len(Ztr_full), size=m, replace=False)\n",
    "    A_raw = torch.as_tensor(Ztr_full[sub], device=_dev)\n",
    "    g_med = 1.0 / (2.0 * (torch.cdist(A_raw, A_raw) ** 2).median().clamp_min(1e-9))\n",
    "    Kc = rbf_kernel(A_raw, A_raw, g_med)                     # کنترل: RBF روی عیناً همان ۴۸۰ ویژگی\n",
    "    Ysub = torch.as_tensor(Ytr.numpy()[sub], device=_dev)\n",
    "    kta_c = kernel_target_alignment(Kc, Ysub)\n",
    "    off = ~torch.eye(m, dtype=torch.bool, device=_dev)\n",
    "    floor = 2.0 ** (-cfg[\"n_qubits\"])\n",
    "\n",
    "    print(\"\\n\" + \"-\" * 96)\n",
    "    print(\"دروازهٔ صفر RUQK-NN — آزمونِ تمرکزِ نمایی، با ۴۸ لایهٔ بازآپلود (نه ۲ مثلِ CQKR)\")\n",
    "    print(\"-\" * 96)\n",
    "    print(f\"  مرجعِ کلاسیک: KTA هستهٔ RBF روی همان ۴۸۰ ویژگی = {kta_c:.4f}   |   کفِ تمرکز 2^-n = {floor:.6f}\")\n",
    "    print(f\"  {'bandwidth':>10}{'میانگین K':>12}{'std K':>11}{'std/2^-n':>11}\"\n",
    "          f\"{'KTA':>9}{'KTA/RBF':>10}   حکم\")\n",
    "    print(\"  \" + \"-\" * 92)\n",
    "\n",
    "    rows, best = [], None\n",
    "    for bw in cfg[\"bandwidth_grid\"]:\n",
    "        fm_b = RUQKFeatureMap(dict(cfg, bandwidth=bw), IN_DIM)\n",
    "        Pb = fm_b.states(Ztr_full[sub])\n",
    "        Kq = fidelity_kernel(Pb, Pb)\n",
    "        muv, sdv = Kq[off].mean().item(), Kq[off].std().item()\n",
    "        kta_q = kernel_target_alignment(Kq, Ysub)\n",
    "        ok_abs = sdv >= cfg[\"gate0_min_std\"]\n",
    "        ok_flr = sdv >= floor\n",
    "        ok_kta = kta_q >= cfg[\"gate0_min_kta_ratio\"] * abs(kta_c)\n",
    "        ok = ok_abs and ok_flr and ok_kta\n",
    "        rows.append(dict(bw=bw, mean=muv, std=sdv, kta=kta_q, ok=ok,\n",
    "                         ok_abs=ok_abs, ok_flr=ok_flr, ok_kta=ok_kta))\n",
    "        print(f\"  {bw:>10.2f}{muv:>12.5f}{sdv:>11.5f}{sdv/floor:>11.1f}\"\n",
    "              f\"{kta_q:>9.4f}{kta_q/max(abs(kta_c),1e-12):>10.2f}   {'✅ عبور' if ok else '❌ رد'}\")\n",
    "        if ok and (best is None or kta_q > best[\"kta\"]):\n",
    "            best = rows[-1]\n",
    "        del Pb, Kq\n",
    "\n",
    "    print(\"-\" * 96)\n",
    "    if best is None:\n",
    "        worst = max(rows, key=lambda r: r[\"kta\"])\n",
    "        print(\"❌ دروازهٔ صفر رد شد — هیچ bandwidth ای هر سه معیار را برآورده نکرد.\")\n",
    "        for nm, k in ((\"پراکندگی مطلق ≥ %.3f\" % cfg[\"gate0_min_std\"], \"ok_abs\"),\n",
    "                      (\"پراکندگی ≥ کفِ تمرکز\", \"ok_flr\"),\n",
    "                      (\"KTA ≥ %.0f%% RBF\" % (100 * cfg[\"gate0_min_kta_ratio\"]), \"ok_kta\")):\n",
    "            print(f\"   {'✅' if worst[k] else '❌'} {nm}   (در بهترین bandwidth = {worst['bw']})\")\n",
    "        print(\"\\n   این یک نتیجهٔ منفیِ قابل‌انتشار است: در ۱۰ کیوبیت، ۴۸ لایهٔ بازآپلود\")\n",
    "        print(\"   تمرکزِ نمایی را از حدی که CQKR (۲ لایه) تحمل می‌کرد عبور می‌دهد — رفعِ گلوگاهِ\")\n",
    "        print(\"   اطلاعات به‌تنهایی کافی نبود؛ باید با تعدادِ کمترِ لایه یا n_qubits بیشتر بازبینی شود.\")\n",
    "        print(\"=\" * 96)\n",
    "        return {\"passed\": False, \"sweep\": rows, \"kta_c\": kta_c}\n",
    "\n",
    "    print(f\"✅ دروازهٔ صفر عبور شد. bandwidth انتخابی = {best['bw']} \"\n",
    "          f\"(بر پایهٔ KTA روی دادهٔ آموزش، بدون نگاه به آزمون)\")\n",
    "    print(\"-\" * 96)\n",
    "\n",
    "    cfg[\"bandwidth\"] = best[\"bw\"]\n",
    "    fm = RUQKFeatureMap(cfg, IN_DIM)\n",
    "    t0 = time.time()\n",
    "    Ptr = fm.states(Ztr_full)\n",
    "    Pte = fm.states(Zte_full)\n",
    "    sec_states = time.time() - t0\n",
    "    print(f\"ساختِ بردارهایِ حالت برایِ کلِ داده: {sec_states:.1f} ثانیه\")\n",
    "\n",
    "    # ------------------------------------------------------------\n",
    "    # k-NN مستقیم روی هستهٔ کوانتومی — عیناً همان الگوریتمِ کنترل‌های زیر\n",
    "    # ------------------------------------------------------------\n",
    "    Ytr_t_r = torch.as_tensor(Ytr_np, device=_dev)\n",
    "    t0 = time.time()\n",
    "    Kq_te = fidelity_kernel(Pte, Ptr)\n",
    "    sc_ruqk = _knn_topk_predict(Kq_te, Ytr_t_r, cfg[\"K_KNN\"])\n",
    "    sec_ruqk = time.time() - t0 + sec_states\n",
    "\n",
    "    NAME_RUQK = \"RUQK-NN — هستهٔ کوانتومیِ بازآپلودشده روی ۴۸۰ ویژگیِ خام\"\n",
    "    record(NAME_RUQK, sc_ruqk, sec_ruqk, n_params=0)\n",
    "\n",
    "    # --- کنترلِ منصفانهٔ دوم: RBF روی عیناً همان ۴۸۰ ویژگیِ استانداردشده، همان k-NN ---\n",
    "    t0 = time.time()\n",
    "    Atr_full = torch.as_tensor(Ztr_full, device=_dev)\n",
    "    Ate_full = torch.as_tensor(Zte_full, device=_dev)\n",
    "    Kr_te = rbf_kernel(Ate_full, Atr_full, g_med)\n",
    "    sc_rbf480 = _knn_topk_predict(Kr_te, Ytr_t_r, cfg[\"K_KNN\"])\n",
    "    sec_rbf480 = time.time() - t0\n",
    "    NAME_RBF480 = \"کنترل: RBF k-NN روی عیناً همان ۴۸۰ ویژگیِ خامِ استانداردشده (k=۱۵)\"\n",
    "    record(NAME_RBF480, sc_rbf480, sec_rbf480, n_params=0)\n",
    "\n",
    "    NAME_COS480 = \"k نزدیک‌ترین همسایه (k=۱۵)\"   # کنترلِ اصلی — از قبل در SCORES موجود است\n",
    "\n",
    "    K = \"fmax_aspect_mean\"\n",
    "    d_vs_cos = RESULTS[NAME_RUQK][K] - RESULTS[NAME_COS480][K]\n",
    "    d_vs_rbf = RESULTS[NAME_RUQK][K] - RESULTS[NAME_RBF480][K]\n",
    "    print(\"\\n\" + \"=\" * 78)\n",
    "    print(f\"RUQK-NN منهای کنترلِ اصلی (کسینوسیِ خام، همان k): {d_vs_cos:+.4f}\")\n",
    "    print(f\"RUQK-NN منهای کنترلِ منصفانهٔ دوم (RBF روی همان ۴۸۰ ویژگی، همان k): {d_vs_rbf:+.4f}\")\n",
    "    print(f\"زمان: RUQK-NN {sec_ruqk:.1f}s | RBF-480 {sec_rbf480:.1f}s | کلِ سلول {time.time()-t_all:.1f}s\")\n",
    "    print(\"این دو عدد تنها چیزی‌اند که قابل‌نسبت‌دادن به فرضیهٔ «رفعِ گلوگاهِ اطلاعات» است.\")\n",
    "    print(\"=\" * 78)\n",
    "    return {\"passed\": True, \"sweep\": rows, \"bandwidth\": cfg[\"bandwidth\"], \"delta_vs_cos\": d_vs_cos, \"delta_vs_rbf\": d_vs_rbf,\n",
    "            \"kta_q\": best[\"kta\"], \"kta_c\": kta_c,\n",
    "            \"name_ruqk\": NAME_RUQK, \"name_rbf480\": NAME_RBF480, \"name_cos480\": NAME_COS480}\n",
    "\n",
    "\n",
    "RUQK_RESULT = run_ruqk()\n",
    "\n",
    "if RUQK_RESULT.get(\"passed\"):\n",
    "    A = RUQK_RESULT[\"name_ruqk\"]\n",
    "    print(\"\\n\" + \"=\" * 96)\n",
    "    print(\"بوت‌استرپ زوجی (۱) — RUQK-NN منهای کنترلِ اصلی (کسینوسیِ خام)\")\n",
    "    print(\"=\" * 96)\n",
    "    BOOT_RUQK_COS = {\"کلی\": paired_bootstrap(A, RUQK_RESULT[\"name_cos480\"], None, label=\"میانگین سه جنبه\")}\n",
    "    for a in ASPECTS:\n",
    "        if len(ASPECT_COLS[a]):\n",
    "            BOOT_RUQK_COS[a] = paired_bootstrap(A, RUQK_RESULT[\"name_cos480\"], ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "\n",
    "    print(\"\\n\" + \"=\" * 96)\n",
    "    print(\"بوت‌استرپ زوجی (۲) — RUQK-NN منهای کنترلِ منصفانهٔ دوم (RBF روی همان ۴۸۰ ویژگی)\")\n",
    "    print(\"=\" * 96)\n",
    "    BOOT_RUQK_RBF = {\"کلی\": paired_bootstrap(A, RUQK_RESULT[\"name_rbf480\"], None, label=\"میانگین سه جنبه\")}\n",
    "    for a in ASPECTS:\n",
    "        if len(ASPECT_COLS[a]):\n",
    "            BOOT_RUQK_RBF[a] = paired_bootstrap(A, RUQK_RESULT[\"name_rbf480\"], ASPECT_COLS[a], label=ASPECT_EN[a])\n",
    "\n",
    "    print(\"=\" * 96)\n",
    "    print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل‌ادعاست. این یک بذر و یک تفکیک\")\n",
    "    print(\"است — برای هر ادعای نهایی با چند بذرِ متفاوت (RUQK_CFG['seed']) تکرار و\")\n",
    "    print(\"پراکندگیِ نتیجه گزارش شود. رفعِ گلوگاهِ اطلاعات شرطِ لازم است، نه شرطِ کافی.\")\n",
    "    print(\"=\" * 96)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4ffb1fe2",
   "metadata": {},
   "source": [
    "## 15. Late fusion: QCF and CG-QCF"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a99fcb9d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:01:48.541668Z",
     "iopub.status.busy": "2026-09-26T17:01:48.541296Z",
     "iopub.status.idle": "2026-09-26T17:04:54.684340Z",
     "shell.execute_reply": "2026-09-26T17:04:54.683208Z",
     "shell.execute_reply.started": "2026-09-26T17:01:48.541630Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "محاسبهٔ شباهتِ کسینوسیِ خام (اعتبارسنجی و آزمون) ...\n",
      "محاسبهٔ هستهٔ کوانتومیِ CQKR (اعتبارسنجی و آزمون) ...\n",
      "  زمان: 1.7 ثانیه\n",
      "محاسبهٔ پیش‌بینیِ k-NN جداگانه از هر هسته (پیش از هر ترکیبی) ...\n",
      "\n",
      "جست‌وجوی α سراسری (ترکیبِ متأخر، شبکهٔ ریز) روی Xva/Yva:\n",
      "  بهترین α سراسری روی اعتبارسنجی: 0.80  (val F_max=0.6709)\n",
      "\n",
      "--- QCF — ترکیبِ متأخر، α سراسری (α=0.80) ---\n",
      "  [تجمیعی]  F_max = 0.5556 (τ=0.33) | wF_max = 0.4820 | S_min = 32.3683\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7334   0.25   0.6806   0.25   8.3271     0.5124     0.9138       60      4094\n",
      "  BP       0.5665   0.24   0.5227   0.22  19.3072     0.4265     0.8061       60      4298\n",
      "  CC       0.7099   0.28   0.6038   0.27  12.7386     0.5915     0.8929       60      4365\n",
      "  میانگین   0.6699          0.6024\n",
      "  زمان آموزش  = 4.4 ثانیه\n",
      "\n",
      "جست‌وجوی α به‌ازای هر چارکِ اطمینانِ کسینوسی (روی Xva/Yva):\n",
      "  چارکِ اطمینانِ 0 (n=1500, حاشیهٔ میانه≈0.005): بهترین α=0.65  (val F_max=0.6463)\n",
      "  چارکِ اطمینانِ 1 (n=1500, حاشیهٔ میانه≈0.010): بهترین α=0.85  (val F_max=0.6707)\n",
      "  چارکِ اطمینانِ 2 (n=1500, حاشیهٔ میانه≈0.019): بهترین α=0.75  (val F_max=0.6832)\n",
      "  چارکِ اطمینانِ 3 (n=1500, حاشیهٔ میانه≈0.038): بهترین α=0.85  (val F_max=0.6879)\n",
      "\n",
      "--- CG-QCF — ترکیبِ متأخرِ وفق‌پذیر (α به‌ازای چارکِ اطمینان) ---\n",
      "  [تجمیعی]  F_max = 0.5556 (τ=0.34) | wF_max = 0.4823 | S_min = 32.3699\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.7334   0.25   0.6806   0.25   8.3044     0.5124     0.9136       60      4094\n",
      "  BP       0.5665   0.25   0.5226   0.23  19.3090     0.4260     0.8054       60      4298\n",
      "  CC       0.7091   0.29   0.6034   0.26  12.7611     0.5904     0.8927       60      4365\n",
      "  میانگین   0.6696          0.6022\n",
      "  زمان آموزش  = 9.3 ثانیه\n",
      "\n",
      "==============================================================================\n",
      "QCF (ترکیبِ متأخر، α سراسری) منهای کنترلِ کسینوسی: +0.0036\n",
      "CG-QCF (ترکیبِ متأخرِ وفق‌پذیر) منهای کنترلِ کسینوسی: +0.0033\n",
      "CG-QCF منهای QCF (سهمِ خالصِ وفق‌پذیریِ چارکی): -0.0003\n",
      "زمان کلِ سلول: 12.3 ثانیه\n",
      "==============================================================================\n",
      "\n",
      "================================================================================================\n",
      "بوت‌استرپ زوجی — QCF و CG-QCF\n",
      "================================================================================================\n",
      "  Δ [۲الف منهای کنترل] = +0.0036   CI95 = [+0.0022, +0.0051]   p ≈ 0.000   معنادار\n",
      "  Δ [۲ب منهای کنترل] = +0.0033   CI95 = [+0.0016, +0.0047]   p ≈ 0.000   معنادار\n",
      "  Δ [۲ب منهای ۲الف] = -0.0003   CI95 = [-0.0012, +0.0003]   p ≈ 0.330   غیرمعنادار — صفر داخل بازه است\n",
      "  Δ [MF] = +0.0041   CI95 = [+0.0006, +0.0067]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = +0.0041   CI95 = [+0.0009, +0.0065]   p ≈ 0.020   معنادار\n",
      "  Δ [BP] = +0.0041   CI95 = [+0.0019, +0.0067]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = +0.0040   CI95 = [+0.0011, +0.0065]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = +0.0027   CI95 = [+0.0008, +0.0052]   p ≈ 0.010   معنادار\n",
      "  Δ [CC] = +0.0018   CI95 = [+0.0004, +0.0045]   p ≈ 0.030   معنادار\n",
      "================================================================================================\n",
      "خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل‌ادعاست. اگر α انتخاب‌شده در همهٔ\n",
      "چارک‌ها ۱٫۰۰ درآمد، یعنی حتی وفق‌پذیریِ محلی هم چیزی برای افزودن پیدا نکرد — و این\n",
      "خودش شواهدِ بیشتری‌ست که مشکل از کیفیتِ ذاتیِ هستهٔ کوانتومی است، نه از روشِ ترکیب.\n",
      "این هنوز یک بذر و یک تفکیک است؛ برای ادعای نهایی با چند بذرِ متفاوت تکرار شود.\n",
      "================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "# ============================================================================\n",
    "# Late fusion of Cos-kNN and QK-NN scores: QCF (one global weight) and\n",
    "# CG-QCF (one weight per quartile of classical confidence). Weights are\n",
    "# fitted on validation only.\n",
    "# ============================================================================\n",
    "\n",
    "assert all(v in globals() for v in\n",
    "           (\"CQKRFeatureMap\", \"fidelity_kernel\", \"_pca_fit\", \"_pca_apply\",\n",
    "            \"CQKR_CFG\", \"CQKR_RESULT\", \"paired_bootstrap\")), (\n",
    "    \"این سلول باید بعد از سلولِ CQKR در همین کرنل اجرا شود.\"\n",
    ")\n",
    "assert CQKR_RESULT.get(\"passed\"), \"دروازهٔ صفر رد شده — این آزمایش را نمی‌توان معتبر انجام داد.\"\n",
    "assert \"k نزدیک‌ترین همسایه (k=۱۵)\" in SCORES, (\n",
    "    \"کنترلِ اصلی (Cos-kNN) در SCORES پیدا نشد — \"\n",
    "    \"سلول‌های ۲ تا ۵ باید در همین کرنل اجرا شده باشند.\"\n",
    ")\n",
    "\n",
    "import numpy as np, torch, time\n",
    "\n",
    "K_BLEND2 = 15                                            # همان k خط‌پایه\n",
    "ALPHA_GRID_FINE = np.round(np.linspace(0.0, 1.0, 21), 2)  # ۲۱ نقطه\n",
    "N_CONF_BINS = 4    # چارک‌های اطمینانِ کسینوسی؛ نامِ جدا از N_BINS (شبکهٔ آستانهٔ τ در سلولِ معیارها)\n",
    "\n",
    "\n",
    "def _raw_cos_sim2(Xa, Xb):\n",
    "    \"\"\"شباهتِ کسینوسیِ (len(Xa), len(Xb)) — عیناً knn_scores.\"\"\"\n",
    "    A = torch.as_tensor(Xa, device=DEVICE); A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    B = torch.as_tensor(Xb, device=DEVICE); B = B / (B.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    return torch.clamp(A @ B.T, min=0.0)\n",
    "\n",
    "\n",
    "def _quantum_kernel_sim2(Zquery, Ztrain, cfg):\n",
    "    \"\"\"هستهٔ کوانتومیِ (len(Zquery), len(Ztrain)) — عیناً K-Mix،\n",
    "    با این تفاوت که هم‌دسته‌ها همیشه روی Ztrain آموزش/برازش می‌شوند.\"\"\"\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztrain)\n",
    "    c_train = km.labels_\n",
    "    c_query = km.predict(Zquery)\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    P_train = fm.states(Ztrain, c_train)\n",
    "    P_query = fm.states(Zquery, c_query)\n",
    "    return fidelity_kernel(P_query, P_train)\n",
    "\n",
    "\n",
    "def _knn_predict2(sim_qt, Ytr_t, K=K_BLEND2, chunk=256):\n",
    "    \"\"\"sim_qt: (N_query, N_train) شباهت → پیش‌بینیِ k-NNِ کاملِ همان هسته.\"\"\"\n",
    "    out = []\n",
    "    for s in range(0, sim_qt.shape[0], chunk):\n",
    "        block = sim_qt[s:s + chunk]\n",
    "        w, idx = torch.topk(block, K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Ytr_t[idx]).cpu().numpy())\n",
    "    return np.concatenate(out)\n",
    "\n",
    "\n",
    "def _topk_margin(sim_qt, K=K_BLEND2):\n",
    "    \"\"\"حاشیهٔ اطمینان: شباهتِ نزدیک‌ترین همسایه منهای شباهتِ Kاُمین همسایه.\"\"\"\n",
    "    vals, _ = torch.topk(sim_qt, K, dim=1)\n",
    "    return (vals[:, 0] - vals[:, -1]).cpu().numpy()\n",
    "\n",
    "\n",
    "t_cell_start = time.time()\n",
    "\n",
    "# ---------------- آماده‌سازی: همان PCA-۱۰ و همان هستهٔ کوانتومیِ CQKR ----------------\n",
    "Xm_b2, V_b2 = _pca_fit(Xtr.numpy(), CQKR_CFG[\"n_qubits\"], CQKR_CFG[\"seed\"])\n",
    "Ztr_b2 = _pca_apply(Xtr.numpy(), Xm_b2, V_b2)\n",
    "Zva_b2 = _pca_apply(Xva.numpy(), Xm_b2, V_b2)\n",
    "Zte_b2 = _pca_apply(Xte.numpy(), Xm_b2, V_b2)\n",
    "Ytr_t_b2 = torch.as_tensor(Ytr_np, device=DEVICE)\n",
    "\n",
    "print(\"محاسبهٔ شباهتِ کسینوسیِ خام (اعتبارسنجی و آزمون) ...\")\n",
    "Sva_cos2 = _raw_cos_sim2(Xva.numpy(), Xtr.numpy())\n",
    "Ste_cos2 = _raw_cos_sim2(Xte.numpy(), Xtr.numpy())\n",
    "\n",
    "print(\"محاسبهٔ هستهٔ کوانتومیِ CQKR (اعتبارسنجی و آزمون) ...\")\n",
    "t0 = time.time()\n",
    "Sva_q2 = _quantum_kernel_sim2(Zva_b2, Ztr_b2, CQKR_CFG)\n",
    "Ste_q2 = _quantum_kernel_sim2(Zte_b2, Ztr_b2, CQKR_CFG)\n",
    "print(f\"  زمان: {time.time() - t0:.1f} ثانیه\")\n",
    "\n",
    "# ---------------- پیش‌بینیِ k-NN جداگانه از هر دو هسته (ترکیبِ متأخر) ----------------\n",
    "print(\"محاسبهٔ پیش‌بینیِ k-NN جداگانه از هر هسته (پیش از هر ترکیبی) ...\")\n",
    "sc_cos_va2 = _knn_predict2(Sva_cos2, Ytr_t_b2)\n",
    "sc_q_va2 = _knn_predict2(Sva_q2, Ytr_t_b2)\n",
    "sc_cos_te2 = _knn_predict2(Ste_cos2, Ytr_t_b2)\n",
    "sc_q_te2 = _knn_predict2(Ste_q2, Ytr_t_b2)\n",
    "\n",
    "# ============================================================\n",
    "# ارتقای الف — همان α سراسری، اما ترکیبِ متأخر + شبکهٔ ریزتر\n",
    "# ============================================================\n",
    "print(\"\\nجست‌وجوی α سراسری (ترکیبِ متأخر، شبکهٔ ریز) روی Xva/Yva:\")\n",
    "best_alpha_g, best_f_g = None, -1.0\n",
    "for a in ALPHA_GRID_FINE:\n",
    "    pred = a * sc_cos_va2 + (1 - a) * sc_q_va2\n",
    "    f = fmax(Yva_np, pred)\n",
    "    if f > best_f_g:\n",
    "        best_f_g, best_alpha_g = f, a\n",
    "print(f\"  بهترین α سراسری روی اعتبارسنجی: {best_alpha_g:.2f}  (val F_max={best_f_g:.4f})\")\n",
    "\n",
    "pred_te_a = best_alpha_g * sc_cos_te2 + (1 - best_alpha_g) * sc_q_te2\n",
    "NAME_BLEND_V2A = f\"QCF — ترکیبِ متأخر، α سراسری (α={best_alpha_g:.2f})\"\n",
    "record(NAME_BLEND_V2A, pred_te_a, time.time() - t_cell_start, n_params=0)\n",
    "\n",
    "# ============================================================\n",
    "# ارتقای ب — وزنِ α وابسته به «اطمینانِ» کسینوسی (چارک‌بندی روی حاشیهٔ\n",
    "# شباهتِ کسینوسی). چارک‌ها فقط از Xva برازش می‌شوند؛ به Xte فقط اعمال\n",
    "# می‌شوند (بدون نشت — هر پروتئینِ آزمون فقط از رویِ حاشیهٔ خودش دسته‌بندی می‌شود).\n",
    "# ============================================================\n",
    "margin_va = _topk_margin(Sva_cos2)\n",
    "margin_te = _topk_margin(Ste_cos2)\n",
    "edges = np.quantile(margin_va, [0.25, 0.5, 0.75])\n",
    "bin_va = np.digitize(margin_va, edges)   # 0..3\n",
    "bin_te = np.digitize(margin_te, edges)\n",
    "\n",
    "print(\"\\nجست‌وجوی α به‌ازای هر چارکِ اطمینانِ کسینوسی (روی Xva/Yva):\")\n",
    "ALPHA_BINS = {}\n",
    "for b in range(N_CONF_BINS):\n",
    "    idx = np.where(bin_va == b)[0]\n",
    "    if len(idx) < 20:\n",
    "        ALPHA_BINS[b] = best_alpha_g\n",
    "        print(f\"  چارکِ اطمینانِ {b}: نمونهٔ ناکافی (n={len(idx)}) — از α سراسری استفاده شد.\")\n",
    "        continue\n",
    "    best_a, best_f = None, -1.0\n",
    "    for a in ALPHA_GRID_FINE:\n",
    "        pred = a * sc_cos_va2[idx] + (1 - a) * sc_q_va2[idx]\n",
    "        f = fmax(Yva_np[idx], pred)\n",
    "        if f > best_f:\n",
    "            best_f, best_a = f, a\n",
    "    ALPHA_BINS[b] = best_a\n",
    "    print(f\"  چارکِ اطمینانِ {b} (n={len(idx)}, حاشیهٔ میانه≈{np.median(margin_va[idx]):.3f}): \"\n",
    "          f\"بهترین α={best_a:.2f}  (val F_max={best_f:.4f})\")\n",
    "\n",
    "alpha_vec_te = np.array([ALPHA_BINS[b] for b in bin_te])\n",
    "pred_te_b = alpha_vec_te[:, None] * sc_cos_te2 + (1 - alpha_vec_te[:, None]) * sc_q_te2\n",
    "NAME_BLEND_V2B = \"CG-QCF — ترکیبِ متأخرِ وفق‌پذیر (α به‌ازای چارکِ اطمینان)\"\n",
    "record(NAME_BLEND_V2B, pred_te_b, time.time() - t_cell_start, n_params=0)\n",
    "\n",
    "NAME_COS_CONTROL = \"k نزدیک‌ترین همسایه (k=۱۵)\"   # از قبل در SCORES موجود است\n",
    "\n",
    "METRIC_KEY = \"fmax_aspect_mean\"   # نامِ محلی‌تر به‌جای K، برای پرهیز از هرگونه\n",
    "                                   # برخوردِ احتمالیِ نامِ سراسری در آینده\n",
    "d_a = RESULTS[NAME_BLEND_V2A][METRIC_KEY] - RESULTS[NAME_COS_CONTROL][METRIC_KEY]\n",
    "d_b = RESULTS[NAME_BLEND_V2B][METRIC_KEY] - RESULTS[NAME_COS_CONTROL][METRIC_KEY]\n",
    "d_ba = RESULTS[NAME_BLEND_V2B][METRIC_KEY] - RESULTS[NAME_BLEND_V2A][METRIC_KEY]\n",
    "print(\"\\n\" + \"=\" * 78)\n",
    "print(f\"QCF (ترکیبِ متأخر، α سراسری) منهای کنترلِ کسینوسی: {d_a:+.4f}\")\n",
    "print(f\"CG-QCF (ترکیبِ متأخرِ وفق‌پذیر) منهای کنترلِ کسینوسی: {d_b:+.4f}\")\n",
    "print(f\"CG-QCF منهای QCF (سهمِ خالصِ وفق‌پذیریِ چارکی): {d_ba:+.4f}\")\n",
    "print(f\"زمان کلِ سلول: {time.time() - t_cell_start:.1f} ثانیه\")\n",
    "print(\"=\" * 78)\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آزمون آماری — بوت‌استرپ زوجی\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"=\" * 96)\n",
    "print(\"بوت‌استرپ زوجی — QCF و CG-QCF\")\n",
    "print(\"=\" * 96)\n",
    "BOOT_V2A = {\"کلی\": paired_bootstrap(NAME_BLEND_V2A, NAME_COS_CONTROL, None, label=\"۲الف منهای کنترل\")}\n",
    "BOOT_V2B = {\"کلی\": paired_bootstrap(NAME_BLEND_V2B, NAME_COS_CONTROL, None, label=\"۲ب منهای کنترل\")}\n",
    "BOOT_V2B_VS_A = {\"کلی\": paired_bootstrap(NAME_BLEND_V2B, NAME_BLEND_V2A, None, label=\"۲ب منهای ۲الف\")}\n",
    "for a_ in ASPECTS:\n",
    "    if len(ASPECT_COLS[a_]):\n",
    "        BOOT_V2A[a_] = paired_bootstrap(NAME_BLEND_V2A, NAME_COS_CONTROL, ASPECT_COLS[a_], label=ASPECT_EN[a_])\n",
    "        BOOT_V2B[a_] = paired_bootstrap(NAME_BLEND_V2B, NAME_COS_CONTROL, ASPECT_COLS[a_], label=ASPECT_EN[a_])\n",
    "print(\"=\" * 96)\n",
    "print(\"خوانش: تنها بازه‌ای که صفر را در بر نمی‌گیرد قابل‌ادعاست. اگر α انتخاب‌شده در همهٔ\")\n",
    "print(\"چارک‌ها ۱٫۰۰ درآمد، یعنی حتی وفق‌پذیریِ محلی هم چیزی برای افزودن پیدا نکرد — و این\")\n",
    "print(\"خودش شواهدِ بیشتری‌ست که مشکل از کیفیتِ ذاتیِ هستهٔ کوانتومی است، نه از روشِ ترکیب.\")\n",
    "print(\"این هنوز یک بذر و یک تفکیک است؛ برای ادعای نهایی با چند بذرِ متفاوت تکرار شود.\")\n",
    "print(\"=\" * 96)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91634b48",
   "metadata": {},
   "source": [
    "## 16. CG-QCF over five seeds"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "4252cc50",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:04:54.686562Z",
     "iopub.status.busy": "2026-09-26T17:04:54.686097Z",
     "iopub.status.idle": "2026-09-26T17:05:25.252629Z",
     "shell.execute_reply": "2026-09-26T17:05:25.251825Z",
     "shell.execute_reply.started": "2026-09-26T17:04:54.686508Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "================================================================================================\n",
      "CG-QCF روی 5 بذر: [42, 0, 1, 2, 3]   |   bandwidth ثابت γ = 0.35\n",
      "================================================================================================\n",
      "\n",
      "[۱/۳] بخشِ مستقل از بذر (هستهٔ کسینوسی، رأی، حاشیه، لبه‌های چارک) ...\n",
      "      بیشینهٔ اختلاف با کنترلِ ذخیره‌شده = 0.00e+00  ✅\n",
      "      لبه‌های چارک (از اعتبارسنجی) = [0.0076, 0.0139, 0.0255]\n",
      "      شمارِ پروتئینِ آزمون در چارک‌ها = [1560, 1495, 1526, 1419]\n",
      "      کنترلِ کلاسیک (قطعی، مستقل از بذر): MFO=0.7293  BPO=0.5624  CCO=0.7073  میانگین=0.6663\n",
      "\n",
      "[۲/۳] حلقهٔ بذرها ...\n",
      "  بذر  42: α سراسری=0.80  α چارکی=[0.65, 0.85, 0.75, 0.85]  →  ۲ب میانگین=0.6696  Δ=+0.0033  (کوانتومیِ خالص=0.6059)  5.8s | std/کف=11.9 shots=184,961\n",
      "  بذر   0: α سراسری=0.75  α چارکی=[0.8, 0.8, 0.8, 0.75]  →  ۲ب میانگین=0.6700  Δ=+0.0037  (کوانتومیِ خالص=0.6027)  5.9s | std/کف=11.7 shots=191,021\n",
      "  بذر   1: α سراسری=0.95  α چارکی=[0.7, 0.85, 0.9, 0.95]  →  ۲ب میانگین=0.6700  Δ=+0.0037  (کوانتومیِ خالص=0.6016)  6.1s | std/کف=10.8 shots=223,039\n",
      "  بذر   2: α سراسری=0.75  α چارکی=[0.7, 0.85, 0.75, 0.8]  →  ۲ب میانگین=0.6701  Δ=+0.0037  (کوانتومیِ خالص=0.6060)  6.1s | std/کف=13.7 shots=139,348\n",
      "  بذر   3: α سراسری=0.75  α چارکی=[0.75, 0.8, 0.7, 0.95]  →  ۲ب میانگین=0.6687  Δ=+0.0024  (کوانتومیِ خالص=0.6049)  6.2s | std/کف=11.2 shots=210,013\n",
      "\n",
      "[۳/۳] جمع‌بندی (30.5 ثانیه در کل)\n",
      "================================================================================================\n",
      "\n",
      "جدولِ به‌ازای بذر — F_max (میانگینِ جنبه‌ای):\n",
      "    بذر     کنترل      ۲الف        ۲ب     Δ(۲ب−کنترل)                   α چارکی\n",
      "  ------------------------------------------------------------------------------\n",
      "     42    0.6663    0.6699    0.6696         +0.0033  [0.65, 0.85, 0.75, 0.85]\n",
      "      0    0.6663    0.6703    0.6700         +0.0037     [0.8, 0.8, 0.8, 0.75]\n",
      "      1    0.6663    0.6691    0.6700         +0.0037    [0.7, 0.85, 0.9, 0.95]\n",
      "      2    0.6663    0.6704    0.6701         +0.0037    [0.7, 0.85, 0.75, 0.8]\n",
      "      3    0.6663    0.6695    0.6687         +0.0024    [0.75, 0.8, 0.7, 0.95]\n",
      "\n",
      "آمارِ روی بذرها (کنترل قطعی است، پس آزمونِ زوجی = آزمونِ تک‌نمونه‌ایِ Δ):\n",
      "              کمیت     کنترل     ۲ب میانگین±انحراف   Δ میانگین        t     p (t)     p (W)       d\n",
      "  -------------------------------------------------------------------------------------------------\n",
      "                MF    0.7293        0.7334±0.0011      +0.0041     8.38    0.0011    0.0625    3.75\n",
      "                BP    0.5624        0.5663±0.0008      +0.0039    10.89    0.0004    0.0625    4.87\n",
      "                CC    0.7073        0.7094±0.0006      +0.0021     7.33    0.0018    0.0625    3.28\n",
      "  میانگینِ جنبه‌ای    0.6663        0.6697±0.0006      +0.0034    13.10    0.0002    0.0625    5.86\n",
      "\n",
      "================================================================================================\n",
      "خوانش:\n",
      "  • با n=۵ کوچک‌ترین p دوطرفهٔ ویلکاکسون ۰٫۰۶۲۵ است؛ پس p(W) هرگز به ۰٫۰۵ نمی‌رسد.\n",
      "    آزمونِ t آزمونِ اصلی است و ویلکاکسون فقط بررسیِ استحکام.\n",
      "  • اگر انحرافِ Δ روی بذرها از خودِ Δ بزرگ‌تر بود، اثرِ تک‌بذر\n",
      "    از نوسانِ مقداردهیِ اولیه جدا نمی‌شود.\n",
      "  • بردارِ α در هر بذر چاپ شده: ادعای «یکنواییِ وزن با اطمینان» را همین‌جا\n",
      "    می‌شود رد یا تأیید کرد، مستقل از این‌که Δ معنادار بشود یا نشود.\n",
      "================================================================================================\n",
      "\n",
      "نتیجهٔ کامل در متغیرِ SEED5_RESULT ذخیره شد.\n",
      "{\n",
      " \"MFO\": {\n",
      "  \"control\": 0.7292699420662442,\n",
      "  \"mean\": 0.7334011533138998,\n",
      "  \"sd\": 0.001102548449295675,\n",
      "  \"delta\": 0.004131211247655542,\n",
      "  \"delta_sd\": 0.001102548449295675,\n",
      "  \"t\": 8.378470066390893,\n",
      "  \"p_t\": 0.0011100279846243042,\n",
      "  \"p_w\": 0.0625,\n",
      "  \"cohen_d\": 3.746965723179443\n",
      " },\n",
      " \"BPO\": {\n",
      "  \"control\": 0.5624361663299319,\n",
      "  \"mean\": 0.5662968030723272,\n",
      "  \"sd\": 0.0007925520339236371,\n",
      "  \"delta\": 0.0038606367423953403,\n",
      "  \"delta_sd\": 0.0007925520339236371,\n",
      "  \"t\": 10.892213789033171,\n",
      "  \"p_t\": 0.000403337320145175,\n",
      "  \"p_w\": 0.0625,\n",
      "  \"cohen_d\": 4.871146091547745\n",
      " },\n",
      " \"CCO\": {\n",
      "  \"control\": 0.7072605258287433,\n",
      "  \"mean\": 0.7093798436904055,\n",
      "  \"sd\": 0.000646123744325498,\n",
      "  \"delta\": 0.0021193178616621287,\n",
      "  \"delta_sd\": 0.000646123744325498,\n",
      "  \"t\": 7.334413641698143,\n",
      "  \"p_t\": 0.001839486598459018,\n",
      "  \"p_w\": 0.0625,\n",
      "  \"cohen_d\": 3.2800494955877664\n",
      " },\n",
      " \"mean\": {\n",
      "  \"control\": 0.6663222114083065,\n",
      "  \"mean\": 0.6696926000255442,\n",
      "  \"sd\": 0.000575437222708773,\n",
      "  \"delta\": 0.003370388617237663,\n",
      "  \"delta_sd\": 0.0005754372227087731,\n",
      "  \"t\": 13.096855332469673,\n",
      "  \"p_t\": 0.0001962408591805983,\n",
      "  \"p_w\": 0.0625,\n",
      "  \"cohen_d\": 5.85709176297656\n",
      " }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "\n",
    "# ============================================================================\n",
    "# CG-QCF over five seeds (42, 0, 1, 2, 3); paired t-test and Wilcoxon test.\n",
    "# ============================================================================\n",
    "\n",
    "import numpy as np, torch, time, json\n",
    "\n",
    "assert all(v in globals() for v in\n",
    "           (\"CQKRFeatureMap\", \"fidelity_kernel\", \"_pca_fit\", \"_pca_apply\",\n",
    "            \"CQKR_CFG\", \"CQKR_RESULT\", \"protein_centric\", \"ASPECT_COLS\",\n",
    "            \"ASPECTS\", \"IC\", \"SCORES\", \"Xtr\", \"Xva\", \"Xte\",\n",
    "            \"Ytr_np\", \"Yva_np\", \"Yte_np\", \"DEVICE\")), (\n",
    "    \"این سلول باید بعد از سلولِ CQKR در همین کرنل اجرا شود.\"\n",
    ")\n",
    "assert CQKR_RESULT.get(\"passed\"), \"دروازهٔ صفر رد شده — این آزمایش را نمی‌توان معتبر انجام داد.\"\n",
    "\n",
    "NAME_COS_CONTROL = \"k نزدیک‌ترین همسایه (k=۱۵)\"\n",
    "assert NAME_COS_CONTROL in SCORES, \"کنترلِ اصلی در SCORES پیدا نشد (سلول‌های ۲ تا ۵ لازم‌اند).\"\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# پیکربندی\n",
    "# ------------------------------------------------------------\n",
    "SEEDS_S5        = [42, 0, 1, 2, 3]          # بذر ۴۲ = اجرای مرجع\n",
    "K_S5            = 15                         # همان k خط‌پایه\n",
    "ALPHA_GRID_S5   = np.round(np.linspace(0.0, 1.0, 21), 2)\n",
    "N_CONF_BINS_S5  = 4                          # عمداً N_BINS نیست (تصادمِ سراسری)\n",
    "MIN_BIN_S5      = 20\n",
    "RECHECK_GATE_S5 = True                       # بررسیِ دوبارهٔ دروازهٔ تمرکز در هر بذر\n",
    "GATE_SAMPLE_S5  = 512\n",
    "\n",
    "print(\"=\" * 96)\n",
    "print(f\"CG-QCF روی {len(SEEDS_S5)} بذر: {SEEDS_S5}   |   bandwidth ثابت γ = {CQKR_CFG['bandwidth']}\")\n",
    "print(\"=\" * 96)\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# توابعِ محلی (پسوندِ _s5 تا با هیچ نامِ سراسری‌ای تصادم نکند)\n",
    "# ------------------------------------------------------------\n",
    "def _cos_sim_s5(Xa, Xb, chunk=2048):\n",
    "    \"\"\"شباهتِ کسینوسیِ (len(Xa), len(Xb)) — عیناً knn_scores.\"\"\"\n",
    "    B = torch.as_tensor(Xb, device=DEVICE)\n",
    "    B = B / (B.norm(dim=1, keepdim=True) + 1e-8)\n",
    "    out = []\n",
    "    for s in range(0, len(Xa), chunk):\n",
    "        A = torch.as_tensor(Xa[s:s + chunk], device=DEVICE)\n",
    "        A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.clamp(A @ B.T, min=0.0))\n",
    "    del B\n",
    "    return torch.cat(out, dim=0)\n",
    "\n",
    "\n",
    "def _knn_vote_s5(sim_qt, Ytr_t, K=K_S5, chunk=256):\n",
    "    \"\"\"sim_qt: (N_query, N_train) → رأیِ k-NNِ وزنیِ همان هسته.\"\"\"\n",
    "    out = []\n",
    "    for s in range(0, sim_qt.shape[0], chunk):\n",
    "        w, idx = torch.topk(sim_qt[s:s + chunk], K, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        out.append(torch.einsum('bk,bkc->bc', w, Ytr_t[idx]).cpu().numpy())\n",
    "    return np.concatenate(out)\n",
    "\n",
    "\n",
    "def _margin_s5(sim_qt, K=K_S5):\n",
    "    \"\"\"حاشیهٔ اطمینان: شباهتِ نزدیک‌ترین همسایه منهای شباهتِ Kاُمین.\"\"\"\n",
    "    vals, _ = torch.topk(sim_qt, K, dim=1)\n",
    "    return (vals[:, 0] - vals[:, -1]).cpu().numpy()\n",
    "\n",
    "\n",
    "def _aspect_fmax_s5(Y_true, P):\n",
    "    \"\"\"F_max به‌ازای هر جنبه + میانگینِ جنبه‌ای؛ همان تعریفِ fmax(aspect_mean).\"\"\"\n",
    "    out = {}\n",
    "    vals = []\n",
    "    for a in ASPECTS:\n",
    "        idx = ASPECT_COLS[a]\n",
    "        if len(idx) == 0:\n",
    "            continue\n",
    "        r = protein_centric(Y_true, P, IC, cols=idx)\n",
    "        if r[\"fmax\"] == r[\"fmax\"]:\n",
    "            out[a] = float(r[\"fmax\"])\n",
    "            vals.append(r[\"fmax\"])\n",
    "    out[\"mean\"] = float(np.mean(vals)) if vals else float(\"nan\")\n",
    "    return out\n",
    "\n",
    "\n",
    "def _quantum_votes_s5(seed, Ytr_t):\n",
    "    \"\"\"کلِ بازویِ کوانتومی برای یک بذر → (رأیِ اعتبارسنجی، رأیِ آزمون، تشخیص).\"\"\"\n",
    "    from sklearn.cluster import KMeans\n",
    "    cfg = dict(CQKR_CFG, seed=int(seed))\n",
    "\n",
    "    Xm, V = _pca_fit(Xtr.numpy(), cfg[\"n_qubits\"], cfg[\"seed\"])\n",
    "    Ztr = _pca_apply(Xtr.numpy(), Xm, V)\n",
    "    Zva = _pca_apply(Xva.numpy(), Xm, V)\n",
    "    Zte = _pca_apply(Xte.numpy(), Xm, V)\n",
    "\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztr)\n",
    "    c_tr, c_va, c_te = km.labels_, km.predict(Zva), km.predict(Zte)\n",
    "\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    P_tr = fm.states(Ztr, c_tr)\n",
    "\n",
    "    diag = {}\n",
    "    if RECHECK_GATE_S5:\n",
    "        g = np.random.default_rng(cfg[\"seed\"])\n",
    "        sub = g.choice(len(Ztr), size=min(GATE_SAMPLE_S5, len(Ztr)), replace=False)\n",
    "        Ksub = fidelity_kernel(P_tr[sub], P_tr[sub])\n",
    "        off = ~torch.eye(len(sub), dtype=torch.bool, device=Ksub.device)\n",
    "        sd = float(Ksub[off].std())\n",
    "        floor = 2.0 ** (-cfg[\"n_qubits\"])\n",
    "        diag = {\"std\": sd, \"std_over_floor\": sd / floor,\n",
    "                \"shots\": int((5.0 / max(sd, 1e-9)) ** 2),\n",
    "                \"passes_floor\": sd >= max(cfg.get(\"gate0_min_std\", 0.0), floor)}\n",
    "        if \"kernel_target_alignment\" in globals():\n",
    "            try:\n",
    "                diag[\"kta\"] = float(kernel_target_alignment(Ksub, Ytr_np[sub]))\n",
    "            except Exception:\n",
    "                pass\n",
    "        del Ksub, off\n",
    "\n",
    "    votes = []\n",
    "    for Z, c in ((Zva, c_va), (Zte, c_te)):\n",
    "        P_q = fm.states(Z, c)\n",
    "        S = fidelity_kernel(P_q, P_tr)\n",
    "        votes.append(_knn_vote_s5(S, Ytr_t))\n",
    "        del P_q, S\n",
    "        if torch.cuda.is_available():\n",
    "            torch.cuda.empty_cache()\n",
    "    del P_tr\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.empty_cache()\n",
    "    return votes[0], votes[1], diag\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# بخشِ مستقل از بذر — یک بار محاسبه می‌شود\n",
    "# ------------------------------------------------------------\n",
    "t_all = time.time()\n",
    "Ytr_t_s5 = torch.as_tensor(Ytr_np, device=DEVICE)\n",
    "\n",
    "print(\"\\n[۱/۳] بخشِ مستقل از بذر (هستهٔ کسینوسی، رأی، حاشیه، لبه‌های چارک) ...\")\n",
    "S_va_cos = _cos_sim_s5(Xva.numpy(), Xtr.numpy())\n",
    "sc_cos_va_s5 = _knn_vote_s5(S_va_cos, Ytr_t_s5)\n",
    "margin_va_s5 = _margin_s5(S_va_cos)\n",
    "del S_va_cos\n",
    "if torch.cuda.is_available():\n",
    "    torch.cuda.empty_cache()\n",
    "\n",
    "S_te_cos = _cos_sim_s5(Xte.numpy(), Xtr.numpy())\n",
    "sc_cos_te_s5 = _knn_vote_s5(S_te_cos, Ytr_t_s5)\n",
    "margin_te_s5 = _margin_s5(S_te_cos)\n",
    "del S_te_cos\n",
    "if torch.cuda.is_available():\n",
    "    torch.cuda.empty_cache()\n",
    "\n",
    "# بررسیِ سلامت: رأیِ کسینوسیِ این‌جا باید عیناً همان کنترلِ جدول باشد\n",
    "_dev_ctrl = float(np.max(np.abs(sc_cos_te_s5 - SCORES[NAME_COS_CONTROL])))\n",
    "print(f\"      بیشینهٔ اختلاف با کنترلِ ذخیره‌شده = {_dev_ctrl:.2e}\"\n",
    "      + (\"  ✅\" if _dev_ctrl < 1e-4 else \"  ⚠️ بازتولیدِ کنترل دقیق نیست — قبل از ادامه بررسی کنید\"))\n",
    "\n",
    "edges_s5 = np.quantile(margin_va_s5, [0.25, 0.50, 0.75])\n",
    "bin_va_s5 = np.digitize(margin_va_s5, edges_s5)\n",
    "bin_te_s5 = np.digitize(margin_te_s5, edges_s5)\n",
    "print(f\"      لبه‌های چارک (از اعتبارسنجی) = {np.round(edges_s5, 4).tolist()}\")\n",
    "print(f\"      شمارِ پروتئینِ آزمون در چارک‌ها = {np.bincount(bin_te_s5, minlength=4).tolist()}\")\n",
    "\n",
    "CONTROL_FMAX_S5 = _aspect_fmax_s5(Yte_np, sc_cos_te_s5)\n",
    "print(f\"      کنترلِ کلاسیک (قطعی، مستقل از بذر): \"\n",
    "      + \"  \".join(f\"{a}={CONTROL_FMAX_S5[a]:.4f}\" for a in ASPECTS if a in CONTROL_FMAX_S5)\n",
    "      + f\"  میانگین={CONTROL_FMAX_S5['mean']:.4f}\")\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# حلقهٔ بذرها\n",
    "# ------------------------------------------------------------\n",
    "print(f\"\\n[۲/۳] حلقهٔ بذرها ...\")\n",
    "ROWS_S5 = []\n",
    "for si, seed in enumerate(SEEDS_S5):\n",
    "    t0 = time.time()\n",
    "    sc_q_va, sc_q_te, diag = _quantum_votes_s5(seed, Ytr_t_s5)\n",
    "\n",
    "    # --- ۲الف: α سراسری ---\n",
    "    best_a_g, best_f_g = None, -1.0\n",
    "    for a in ALPHA_GRID_S5:\n",
    "        f = _aspect_fmax_s5(Yva_np, a * sc_cos_va_s5 + (1 - a) * sc_q_va)[\"mean\"]\n",
    "        if f > best_f_g:\n",
    "            best_f_g, best_a_g = f, float(a)\n",
    "\n",
    "    # --- ۲ب: α به‌ازای هر چارکِ اطمینان ---\n",
    "    alphas, val_f_bin = [], []\n",
    "    for b in range(N_CONF_BINS_S5):\n",
    "        idx = np.where(bin_va_s5 == b)[0]\n",
    "        if len(idx) < MIN_BIN_S5:\n",
    "            alphas.append(best_a_g); val_f_bin.append(float(\"nan\")); continue\n",
    "        best_a, best_f = None, -1.0\n",
    "        for a in ALPHA_GRID_S5:\n",
    "            f = _aspect_fmax_s5(Yva_np[idx], a * sc_cos_va_s5[idx] + (1 - a) * sc_q_va[idx])[\"mean\"]\n",
    "            if f > best_f:\n",
    "                best_f, best_a = f, float(a)\n",
    "        alphas.append(best_a); val_f_bin.append(best_f)\n",
    "\n",
    "    alpha_vec_te = np.asarray([alphas[b] for b in bin_te_s5])[:, None]\n",
    "    pred_te_a = best_a_g * sc_cos_te_s5 + (1 - best_a_g) * sc_q_te\n",
    "    pred_te_b = alpha_vec_te * sc_cos_te_s5 + (1 - alpha_vec_te) * sc_q_te\n",
    "\n",
    "    f_a = _aspect_fmax_s5(Yte_np, pred_te_a)\n",
    "    f_b = _aspect_fmax_s5(Yte_np, pred_te_b)\n",
    "    f_q = _aspect_fmax_s5(Yte_np, sc_q_te)          # بازویِ کوانتومیِ خالص (α=۰)\n",
    "\n",
    "    ROWS_S5.append(dict(seed=int(seed), alpha_global=best_a_g, alphas=alphas,\n",
    "                        val_fmax_bin=val_f_bin, val_fmax_global=best_f_g,\n",
    "                        v2a=f_a, v2b=f_b, quantum_only=f_q, diag=diag,\n",
    "                        seconds=time.time() - t0))\n",
    "\n",
    "    d_b = f_b[\"mean\"] - CONTROL_FMAX_S5[\"mean\"]\n",
    "    gate_txt = \"\"\n",
    "    if diag:\n",
    "        gate_txt = (f\" | std/کف={diag['std_over_floor']:.1f}\"\n",
    "                    + (f\" KTA={diag['kta']:.4f}\" if \"kta\" in diag else \"\")\n",
    "                    + f\" shots={diag['shots']:,}\")\n",
    "    print(f\"  بذر {seed:>3}: α سراسری={best_a_g:.2f}  α چارکی={alphas}\"\n",
    "          f\"  →  ۲ب میانگین={f_b['mean']:.4f}  Δ={d_b:+.4f}\"\n",
    "          f\"  (کوانتومیِ خالص={f_q['mean']:.4f})  {time.time() - t0:.1f}s{gate_txt}\")\n",
    "\n",
    "    del sc_q_va, sc_q_te\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.empty_cache()\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# جمع‌بندیِ آماری روی بذرها\n",
    "# ------------------------------------------------------------\n",
    "print(f\"\\n[۳/۳] جمع‌بندی ({time.time() - t_all:.1f} ثانیه در کل)\")\n",
    "print(\"=\" * 96)\n",
    "\n",
    "KEYS_S5 = [a for a in ASPECTS if a in CONTROL_FMAX_S5] + [\"mean\"]\n",
    "LBL_S5 = {**{a: (ASPECT_EN[a] if \"ASPECT_EN\" in globals() else a) for a in ASPECTS},\n",
    "          \"mean\": \"میانگینِ جنبه‌ای\"}\n",
    "\n",
    "try:\n",
    "    from scipy import stats as _st\n",
    "except Exception:\n",
    "    _st = None\n",
    "\n",
    "print(\"\\nجدولِ به‌ازای بذر — F_max (میانگینِ جنبه‌ای):\")\n",
    "print(f\"  {'بذر':>5}{'کنترل':>10}{'۲الف':>10}{'۲ب':>10}{'Δ(۲ب−کنترل)':>16}{'α چارکی':>26}\")\n",
    "print(\"  \" + \"-\" * 78)\n",
    "for r in ROWS_S5:\n",
    "    print(f\"  {r['seed']:>5}{CONTROL_FMAX_S5['mean']:>10.4f}{r['v2a']['mean']:>10.4f}\"\n",
    "          f\"{r['v2b']['mean']:>10.4f}{r['v2b']['mean'] - CONTROL_FMAX_S5['mean']:>+16.4f}\"\n",
    "          f\"{str(r['alphas']):>26}\")\n",
    "\n",
    "print(\"\\nآمارِ روی بذرها (کنترل قطعی است، پس آزمونِ زوجی = آزمونِ تک‌نمونه‌ایِ Δ):\")\n",
    "print(f\"  {'کمیت':>16}{'کنترل':>10}{'۲ب میانگین±انحراف':>22}{'Δ میانگین':>12}{'t':>9}{'p (t)':>10}{'p (W)':>10}{'d':>8}\")\n",
    "print(\"  \" + \"-\" * 97)\n",
    "SUMMARY_S5 = {}\n",
    "for k in KEYS_S5:\n",
    "    vals = np.array([r[\"v2b\"][k] for r in ROWS_S5], dtype=float)\n",
    "    d = vals - CONTROL_FMAX_S5[k]\n",
    "    m, sd = float(d.mean()), float(d.std(ddof=1)) if len(d) > 1 else float(\"nan\")\n",
    "    t = p_t = p_w = coh = float(\"nan\")\n",
    "    if _st is not None and len(d) > 1 and sd > 0:\n",
    "        t, p_t = _st.ttest_1samp(d, 0.0)\n",
    "        coh = m / sd\n",
    "        try:\n",
    "            p_w = _st.wilcoxon(d).pvalue\n",
    "        except Exception:\n",
    "            pass\n",
    "    SUMMARY_S5[k] = dict(control=CONTROL_FMAX_S5[k], mean=float(vals.mean()),\n",
    "                         sd=float(vals.std(ddof=1)), delta=m, delta_sd=sd,\n",
    "                         t=float(t), p_t=float(p_t), p_w=float(p_w), cohen_d=float(coh))\n",
    "    print(f\"  {LBL_S5.get(k, k):>16}{CONTROL_FMAX_S5[k]:>10.4f}\"\n",
    "          f\"{vals.mean():>14.4f}±{vals.std(ddof=1):<7.4f}{m:>+12.4f}\"\n",
    "          f\"{t:>9.2f}{p_t:>10.4f}{p_w:>10.4f}{coh:>8.2f}\")\n",
    "\n",
    "print(\"\\n\" + \"=\" * 96)\n",
    "print(\"خوانش:\")\n",
    "print(\"  • با n=۵ کوچک‌ترین p دوطرفهٔ ویلکاکسون ۰٫۰۶۲۵ است؛ پس p(W) هرگز به ۰٫۰۵ نمی‌رسد.\")\n",
    "print(\"    آزمونِ t آزمونِ اصلی است و ویلکاکسون فقط بررسیِ استحکام.\")\n",
    "print(\"  • اگر انحرافِ Δ روی بذرها از خودِ Δ بزرگ‌تر بود، اثرِ تک‌بذر\")\n",
    "print(\"    از نوسانِ مقداردهیِ اولیه جدا نمی‌شود.\")\n",
    "print(\"  • بردارِ α در هر بذر چاپ شده: ادعای «یکنواییِ وزن با اطمینان» را همین‌جا\")\n",
    "print(\"    می‌شود رد یا تأیید کرد، مستقل از این‌که Δ معنادار بشود یا نشود.\")\n",
    "print(\"=\" * 96)\n",
    "\n",
    "SEED5_RESULT = dict(seeds=SEEDS_S5, control=CONTROL_FMAX_S5, rows=ROWS_S5,\n",
    "                    summary=SUMMARY_S5, edges=edges_s5.tolist(),\n",
    "                    bandwidth=CQKR_CFG[\"bandwidth\"], k=K_S5,\n",
    "                    alpha_grid=ALPHA_GRID_S5.tolist(), bins=N_CONF_BINS_S5)\n",
    "print(\"\\nنتیجهٔ کامل در متغیرِ SEED5_RESULT ذخیره شد.\")\n",
    "print(json.dumps({k: SUMMARY_S5[k] for k in SUMMARY_S5}, indent=1, ensure_ascii=False))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5adec3c5",
   "metadata": {},
   "source": [
    "## 17. Per-aspect fusion weights"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "eb15fca7",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:05:25.254338Z",
     "iopub.status.busy": "2026-09-26T17:05:25.253955Z",
     "iopub.status.idle": "2026-09-26T17:05:46.338529Z",
     "shell.execute_reply": "2026-09-26T17:05:46.337790Z",
     "shell.execute_reply.started": "2026-09-26T17:05:25.254312Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "✅ نگهبانِ دیتاست عبور کرد: DATA_DIR=/kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "====================================================================================================\n",
      "دروازهٔ جنبه‌ای روی 5 بذر  |  جنبه‌ها: ['MFO', 'BPO', 'CCO']  |  ستون‌های بی‌جنبه: 0 (وزنِ تجمیعی می‌گیرند)\n",
      "====================================================================================================\n",
      "  بذر  42: MFO:[0.65, 0.85, 0.95, 1.0]  BPO:[0.8, 0.8, 0.95, 0.75]  CCO:[0.65, 0.65, 0.8, 0.65]\n",
      "          میانگینِ جنبه‌ای  جنبه‌ای=0.6689 (Δ=+0.0026)   تجمیعی=0.6696 (Δ=+0.0033)   اختلاف=-0.0007   4.1s\n",
      "  بذر   0: MFO:[0.75, 0.85, 0.95, 1.0]  BPO:[0.9, 0.8, 0.95, 0.75]  CCO:[0.55, 0.8, 0.8, 0.7]\n",
      "          میانگینِ جنبه‌ای  جنبه‌ای=0.6694 (Δ=+0.0031)   تجمیعی=0.6700 (Δ=+0.0037)   اختلاف=-0.0006   4.2s\n",
      "  بذر   1: MFO:[0.7, 0.95, 0.85, 1.0]  BPO:[0.7, 0.8, 0.95, 0.95]  CCO:[0.75, 0.7, 0.8, 0.65]\n",
      "          میانگینِ جنبه‌ای  جنبه‌ای=0.6694 (Δ=+0.0031)   تجمیعی=0.6700 (Δ=+0.0037)   اختلاف=-0.0006   4.2s\n",
      "  بذر   2: MFO:[0.7, 0.85, 0.75, 1.0]  BPO:[0.7, 0.75, 0.95, 0.75]  CCO:[0.6, 0.8, 0.75, 0.6]\n",
      "          میانگینِ جنبه‌ای  جنبه‌ای=0.6700 (Δ=+0.0037)   تجمیعی=0.6701 (Δ=+0.0037)   اختلاف=-0.0001   4.2s\n",
      "  بذر   3: MFO:[0.75, 0.95, 0.75, 1.0]  BPO:[0.75, 0.75, 0.7, 0.75]  CCO:[0.55, 0.75, 0.8, 0.7]\n",
      "          میانگینِ جنبه‌ای  جنبه‌ای=0.6693 (Δ=+0.0030)   تجمیعی=0.6687 (Δ=+0.0024)   اختلاف=+0.0006   4.3s\n",
      "\n",
      "====================================================================================================\n",
      "جمع‌بندی (21.0 ثانیه)\n",
      "====================================================================================================\n",
      "\n",
      "(الف) دروازهٔ جنبه‌ای در برابر کنترلِ کلاسیک:\n",
      "              کمیت     کنترل  جنبه‌ای میانگین±انحراف          Δ        t         p       d\n",
      "  ----------------------------------------------------------------------------------------\n",
      "                MF    0.7293          0.7325±0.0010     +0.0032     7.51    0.0017    3.36\n",
      "                BP    0.5624          0.5660±0.0004     +0.0035    17.73    0.0001    7.93\n",
      "                CC    0.7073          0.7098±0.0009     +0.0025     6.29    0.0033    2.81\n",
      "  میانگینِ جنبه‌ای    0.6663          0.6694±0.0004     +0.0031    18.42    0.0001    8.24\n",
      "\n",
      "(ب) دروازهٔ جنبه‌ای در برابر دروازهٔ تجمیعی (زوجیِ هم‌بذر):\n",
      "              کمیت      تجمیعی     جنبه‌ای          Δ        t         p       d\n",
      "  --------------------------------------------------------------------------------\n",
      "                MF      0.7334      0.7325    -0.0009    -3.24    0.0317   -1.45\n",
      "                BP      0.5663      0.5660    -0.0003    -0.72    0.5122   -0.32\n",
      "                CC      0.7094      0.7098    +0.0004     0.96    0.3928    0.43\n",
      "  میانگینِ جنبه‌ای      0.6697      0.6694    -0.0003    -1.11    0.3292   -0.50\n",
      "\n",
      "====================================================================================================\n",
      "آزمونِ سه پیش‌بینیِ فرضیه:\n",
      "  (۱) وزن‌های BPO به ۱٫۰۰ میل می‌کنند؟   خیر   (سهمِ وزن‌های دقیقاً ۱٫۰۰: 0%)\n",
      "        بذر  42: [0.8, 0.8, 0.95, 0.75]\n",
      "        بذر   0: [0.9, 0.8, 0.95, 0.75]\n",
      "        بذر   1: [0.7, 0.8, 0.95, 0.95]\n",
      "        بذر   2: [0.7, 0.75, 0.95, 0.75]\n",
      "        بذر   3: [0.75, 0.75, 0.7, 0.75]\n",
      "  (۲) زیانِ BPO حذف یا بسیار کوچک شد؟   بله   (Δ از +0.0039 به +0.0035)\n",
      "  (۳) میانگینِ جنبه‌ای بالاتر رفت؟   خیر   (Δ از +0.0034 به +0.0031)\n",
      "====================================================================================================\n",
      "خوانش: اگر هر سه «بله» شد، فرضیه تأیید می‌شود. اگر (۱) بله ولی (۳) خیر شد،\n",
      "یعنی دروازه درست تشخیص داده ولی\n",
      "۱۲ پارامتر روی اعتبارسنجی بیش‌برازش کرده‌اند. اگر (۱) خیر شد، فرضیهٔ\n",
      "«هدفِ تجمیعی مقصر است» رد شده و علتِ زیانِ BP جای دیگری است.\n",
      "====================================================================================================\n",
      "\n",
      "نتیجهٔ کامل در متغیرِ PERASPECT_RESULT ذخیره شد.\n",
      "{\n",
      " \"vs_control\": {\n",
      "  \"MFO\": {\n",
      "   \"control\": 0.7292699420662442,\n",
      "   \"mean\": 0.7324720539181492,\n",
      "   \"sd\": 0.0009532865396455456,\n",
      "   \"delta\": 0.0032021118519050206,\n",
      "   \"delta_sd\": 0.0009532865396455456,\n",
      "   \"t\": 7.511004797235125,\n",
      "   \"p\": 0.0016815300715175916,\n",
      "   \"cohen_d\": 3.359023461188953\n",
      "  },\n",
      "  \"BPO\": {\n",
      "   \"control\": 0.5624361663299319,\n",
      "   \"mean\": 0.5659807572035473,\n",
      "   \"sd\": 0.0004470582263231803,\n",
      "   \"delta\": 0.003544590873615494,\n",
      "   \"delta_sd\": 0.00044705822632318026,\n",
      "   \"t\": 17.729113746583675,\n",
      "   \"p\": 5.946310606764652e-05,\n",
      "   \"cohen_d\": 7.928700703637415\n",
      "  },\n",
      "  \"CCO\": {\n",
      "   \"control\": 0.7072605258287433,\n",
      "   \"mean\": 0.7097947352130157,\n",
      "   \"sd\": 0.0009009479473235595,\n",
      "   \"delta\": 0.00253420938427249,\n",
      "   \"delta_sd\": 0.0009009479473235595,\n",
      "   \"t\": 6.289669086083274,\n",
      "   \"p\": 0.003264225531494978,\n",
      "   \"cohen_d\": 2.8128255264922357\n",
      "  },\n",
      "  \"mean\": {\n",
      "   \"control\": 0.6663222114083065,\n",
      "   \"mean\": 0.6694158487782375,\n",
      "   \"sd\": 0.0003755717876914699,\n",
      "   \"delta\": 0.003093637369930935,\n",
      "   \"delta_sd\": 0.0003755717876914699,\n",
      "   \"t\": 18.418804829349938,\n",
      "   \"p\": 5.1123310595055045e-05,\n",
      "   \"cohen_d\": 8.237139932545574\n",
      "  }\n",
      " },\n",
      " \"vs_pooled\": {\n",
      "  \"MFO\": {\n",
      "   \"pooled\": 0.7334011533138998,\n",
      "   \"per_aspect\": 0.7324720539181492,\n",
      "   \"delta\": -0.0009290993957505212,\n",
      "   \"t\": -3.238914424449718,\n",
      "   \"p\": 0.03170653760097428,\n",
      "   \"cohen_d\": -1.4484865652748353\n",
      "  },\n",
      "  \"BPO\": {\n",
      "   \"pooled\": 0.5662968030723272,\n",
      "   \"per_aspect\": 0.5659807572035473,\n",
      "   \"delta\": -0.0003160458687798462,\n",
      "   \"t\": -0.7184166540427555,\n",
      "   \"p\": 0.5122277804106768,\n",
      "   \"cohen_d\": -0.3212856949215101\n",
      "  },\n",
      "  \"CCO\": {\n",
      "   \"pooled\": 0.7093798436904055,\n",
      "   \"per_aspect\": 0.7097947352130157,\n",
      "   \"delta\": 0.0004148915226103611,\n",
      "   \"t\": 0.9570021512768188,\n",
      "   \"p\": 0.39276006094158106,\n",
      "   \"cohen_d\": 0.4279843729737008\n",
      "  },\n",
      "  \"mean\": {\n",
      "   \"pooled\": 0.6696926000255442,\n",
      "   \"per_aspect\": 0.6694158487782375,\n",
      "   \"delta\": -0.00027675124730672793,\n",
      "   \"t\": -1.1101275297288737,\n",
      "   \"p\": 0.3291815606113275,\n",
      "   \"cohen_d\": -0.4964641240335361\n",
      "  }\n",
      " }\n",
      "}\n"
     ]
    }
   ],
   "source": [
    "# Dataset guard: expected feature and label dimensions.\n",
    "_ACCEPTABLE_DATASET_NAMES = (\"datacafa6\", \"newdatasetcafa6\")\n",
    "\n",
    "_name_ok = any(n in DATA_DIR for n in _ACCEPTABLE_DATASET_NAMES)\n",
    "assert IN_DIM == 480 and N_LABELS == 180 and _name_ok, (\n",
    "    f\"❌ دیتاستِ اشتباه یا حافظهٔ کهنه (IN_DIM={IN_DIM}, N_LABELS={N_LABELS}, DATA_DIR={DATA_DIR}).\\n\"\n",
    "    f\"   نام‌های پذیرفته‌شده: {_ACCEPTABLE_DATASET_NAMES}.\\n\"\n",
    "    \"   یکی از این دیتاست‌ها را در پنل Input نگه دارید و از بالا Restart & Run All بزنید؛ \"\n",
    "    \"اجرای دوبارهٔ همین یک سلول کافی نیست.\"\n",
    ")\n",
    "print(f\"✅ نگهبانِ دیتاست عبور کرد: DATA_DIR={DATA_DIR}\")\n",
    "\n",
    "# ============================================================================\n",
    "# Per-aspect fusion weights for CG-QCF compared with a single pooled weight,\n",
    "# over the same five seeds.\n",
    "# ============================================================================\n",
    "\n",
    "import numpy as np, torch, time, json\n",
    "\n",
    "assert all(v in globals() for v in\n",
    "           (\"sc_cos_va_s5\", \"sc_cos_te_s5\", \"bin_va_s5\", \"bin_te_s5\",\n",
    "            \"CONTROL_FMAX_S5\", \"_quantum_votes_s5\", \"_aspect_fmax_s5\",\n",
    "            \"SEEDS_S5\", \"ALPHA_GRID_S5\", \"N_CONF_BINS_S5\", \"MIN_BIN_S5\",\n",
    "            \"Ytr_t_s5\", \"ROWS_S5\", \"ASPECTS\", \"ASPECT_COLS\", \"protein_centric\",\n",
    "            \"IC\", \"Yva_np\", \"Yte_np\")), (\n",
    "    \"سلولِ CG-QCF پنج‌بذری باید پیش از این سلول در همین کرنل اجرا شده باشد.\"\n",
    ")\n",
    "\n",
    "N_LABELS_PA = sc_cos_te_s5.shape[1]\n",
    "ASP_LIST_PA = [a for a in ASPECTS if len(ASPECT_COLS[a]) > 0]\n",
    "\n",
    "# ستون‌هایی که جنبهٔ مشخصی ندارند (اصطلاحاتِ منسوخ) → وزنِ تجمیعی\n",
    "_assigned = np.concatenate([np.asarray(ASPECT_COLS[a], dtype=int) for a in ASP_LIST_PA])\n",
    "UNMAPPED_PA = np.setdiff1d(np.arange(N_LABELS_PA), _assigned)\n",
    "print(\"=\" * 100)\n",
    "print(f\"دروازهٔ جنبه‌ای روی {len(SEEDS_S5)} بذر  |  جنبه‌ها: {ASP_LIST_PA}  |  \"\n",
    "      f\"ستون‌های بی‌جنبه: {len(UNMAPPED_PA)} (وزنِ تجمیعی می‌گیرند)\")\n",
    "print(\"=\" * 100)\n",
    "\n",
    "\n",
    "def _fmax_cols_pa(Y_true, P, cols):\n",
    "    \"\"\"F_max فقط روی ستون‌های یک جنبه.\"\"\"\n",
    "    r = protein_centric(Y_true, P, IC, cols=cols)\n",
    "    return float(r[\"fmax\"]) if r[\"fmax\"] == r[\"fmax\"] else float(\"nan\")\n",
    "\n",
    "\n",
    "def _fit_gate_pa(sc_cos_va, sc_q_va, alpha_pooled):\n",
    "    \"\"\"α را به‌ازای (جنبه، چارک) روی اعتبارسنجی برازش می‌کند.\"\"\"\n",
    "    alphas = {}\n",
    "    val_f = {}\n",
    "    for a in ASP_LIST_PA:\n",
    "        cols = np.asarray(ASPECT_COLS[a], dtype=int)\n",
    "        row, fr = [], []\n",
    "        for b in range(N_CONF_BINS_S5):\n",
    "            idx = np.where(bin_va_s5 == b)[0]\n",
    "            if len(idx) < MIN_BIN_S5:\n",
    "                row.append(alpha_pooled[b]); fr.append(float(\"nan\")); continue\n",
    "            best_a, best_f = None, -1.0\n",
    "            for al in ALPHA_GRID_S5:\n",
    "                blend = al * sc_cos_va[idx] + (1 - al) * sc_q_va[idx]\n",
    "                f = _fmax_cols_pa(Yva_np[idx], blend, cols)\n",
    "                if f == f and f > best_f:\n",
    "                    best_f, best_a = f, float(al)\n",
    "            row.append(best_a if best_a is not None else alpha_pooled[b])\n",
    "            fr.append(best_f)\n",
    "        alphas[a] = row\n",
    "        val_f[a] = fr\n",
    "    return alphas, val_f\n",
    "\n",
    "\n",
    "def _alpha_matrix_pa(alphas, alpha_pooled, bin_vec):\n",
    "    \"\"\"ماتریسِ (N, C) وزن: هر ستون وزنِ جنبهٔ خودش، بی‌جنبه‌ها وزنِ تجمیعی.\"\"\"\n",
    "    A = np.empty((len(bin_vec), N_LABELS_PA), dtype=np.float32)\n",
    "    for a in ASP_LIST_PA:\n",
    "        cols = np.asarray(ASPECT_COLS[a], dtype=int)\n",
    "        A[:, cols] = np.asarray(alphas[a], dtype=np.float32)[bin_vec][:, None]\n",
    "    if len(UNMAPPED_PA):\n",
    "        A[:, UNMAPPED_PA] = np.asarray(alpha_pooled, dtype=np.float32)[bin_vec][:, None]\n",
    "    return A\n",
    "\n",
    "\n",
    "t_pa = time.time()\n",
    "ROWS_PA = []\n",
    "for r17 in ROWS_S5:\n",
    "    seed = r17[\"seed\"]\n",
    "    t0 = time.time()\n",
    "    sc_q_va, sc_q_te, _ = _quantum_votes_s5(seed, Ytr_t_s5)\n",
    "\n",
    "    alpha_pooled = list(r17[\"alphas\"])                       # دروازهٔ تجمیعی\n",
    "    alphas_pa, val_f_pa = _fit_gate_pa(sc_cos_va_s5, sc_q_va, alpha_pooled)\n",
    "\n",
    "    A_te = _alpha_matrix_pa(alphas_pa, alpha_pooled, bin_te_s5)\n",
    "    pred_pa = A_te * sc_cos_te_s5 + (1 - A_te) * sc_q_te\n",
    "    f_pa = _aspect_fmax_s5(Yte_np, pred_pa)\n",
    "\n",
    "    ROWS_PA.append(dict(seed=seed, alphas=alphas_pa, val_fmax=val_f_pa,\n",
    "                        pa=f_pa, pooled=r17[\"v2b\"], seconds=time.time() - t0))\n",
    "\n",
    "    d_pa = f_pa[\"mean\"] - CONTROL_FMAX_S5[\"mean\"]\n",
    "    d_po = r17[\"v2b\"][\"mean\"] - CONTROL_FMAX_S5[\"mean\"]\n",
    "    print(f\"  بذر {seed:>3}: \" + \"  \".join(f\"{a}:{alphas_pa[a]}\" for a in ASP_LIST_PA))\n",
    "    print(f\"          میانگینِ جنبه‌ای  جنبه‌ای={f_pa['mean']:.4f} (Δ={d_pa:+.4f})   \"\n",
    "          f\"تجمیعی={r17['v2b']['mean']:.4f} (Δ={d_po:+.4f})   \"\n",
    "          f\"اختلاف={f_pa['mean'] - r17['v2b']['mean']:+.4f}   {time.time() - t0:.1f}s\")\n",
    "\n",
    "    del sc_q_va, sc_q_te\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.empty_cache()\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آمارِ روی بذرها\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"=\" * 100)\n",
    "print(f\"جمع‌بندی ({time.time() - t_pa:.1f} ثانیه)\")\n",
    "print(\"=\" * 100)\n",
    "\n",
    "try:\n",
    "    from scipy import stats as _st\n",
    "except Exception:\n",
    "    _st = None\n",
    "\n",
    "KEYS_PA = ASP_LIST_PA + [\"mean\"]\n",
    "LBL_PA = {**{a: (ASPECT_EN[a] if \"ASPECT_EN\" in globals() else a) for a in ASPECTS},\n",
    "          \"mean\": \"میانگینِ جنبه‌ای\"}\n",
    "\n",
    "\n",
    "def _t1(d):\n",
    "    \"\"\"آزمونِ t تک‌نمونه‌ای + d کوهن؛ (t, p, d).\"\"\"\n",
    "    d = np.asarray(d, dtype=float)\n",
    "    if _st is None or len(d) < 2 or d.std(ddof=1) == 0:\n",
    "        return float(\"nan\"), float(\"nan\"), float(\"nan\")\n",
    "    t, p = _st.ttest_1samp(d, 0.0)\n",
    "    return float(t), float(p), float(d.mean() / d.std(ddof=1))\n",
    "\n",
    "\n",
    "print(\"\\n(الف) دروازهٔ جنبه‌ای در برابر کنترلِ کلاسیک:\")\n",
    "print(f\"  {'کمیت':>16}{'کنترل':>10}{'جنبه‌ای میانگین±انحراف':>24}{'Δ':>11}{'t':>9}{'p':>10}{'d':>8}\")\n",
    "print(\"  \" + \"-\" * 88)\n",
    "SUMMARY_PA = {}\n",
    "for k in KEYS_PA:\n",
    "    vals = np.array([r[\"pa\"][k] for r in ROWS_PA], dtype=float)\n",
    "    d = vals - CONTROL_FMAX_S5[k]\n",
    "    t, p, coh = _t1(d)\n",
    "    SUMMARY_PA[k] = dict(control=CONTROL_FMAX_S5[k], mean=float(vals.mean()),\n",
    "                         sd=float(vals.std(ddof=1)), delta=float(d.mean()),\n",
    "                         delta_sd=float(d.std(ddof=1)), t=t, p=p, cohen_d=coh)\n",
    "    print(f\"  {LBL_PA.get(k, k):>16}{CONTROL_FMAX_S5[k]:>10.4f}\"\n",
    "          f\"{vals.mean():>16.4f}±{vals.std(ddof=1):<7.4f}{d.mean():>+11.4f}{t:>9.2f}{p:>10.4f}{coh:>8.2f}\")\n",
    "\n",
    "print(\"\\n(ب) دروازهٔ جنبه‌ای در برابر دروازهٔ تجمیعی (زوجیِ هم‌بذر):\")\n",
    "print(f\"  {'کمیت':>16}{'تجمیعی':>12}{'جنبه‌ای':>12}{'Δ':>11}{'t':>9}{'p':>10}{'d':>8}\")\n",
    "print(\"  \" + \"-\" * 80)\n",
    "SUMMARY_VS = {}\n",
    "for k in KEYS_PA:\n",
    "    a_pa = np.array([r[\"pa\"][k] for r in ROWS_PA], dtype=float)\n",
    "    a_po = np.array([r[\"pooled\"][k] for r in ROWS_PA], dtype=float)\n",
    "    d = a_pa - a_po\n",
    "    t, p, coh = _t1(d)\n",
    "    SUMMARY_VS[k] = dict(pooled=float(a_po.mean()), per_aspect=float(a_pa.mean()),\n",
    "                         delta=float(d.mean()), t=t, p=p, cohen_d=coh)\n",
    "    print(f\"  {LBL_PA.get(k, k):>16}{a_po.mean():>12.4f}{a_pa.mean():>12.4f}\"\n",
    "          f\"{d.mean():>+11.4f}{t:>9.2f}{p:>10.4f}{coh:>8.2f}\")\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# آزمونِ صریحِ سه پیش‌بینی\n",
    "# ------------------------------------------------------------\n",
    "BP_KEY = \"BPO\" if \"BPO\" in ASP_LIST_PA else ASP_LIST_PA[min(1, len(ASP_LIST_PA) - 1)]\n",
    "bp_alphas = [r[\"alphas\"][BP_KEY] for r in ROWS_PA]\n",
    "bp_all_one = all(all(abs(x - 1.0) < 1e-9 for x in row) for row in bp_alphas)\n",
    "bp_frac_one = float(np.mean([abs(x - 1.0) < 1e-9 for row in bp_alphas for x in row]))\n",
    "p1 = bp_all_one or bp_frac_one >= 0.75\n",
    "p2 = SUMMARY_PA[BP_KEY][\"delta\"] > -0.0005          # زیان حذف یا زیرِ نصفِ هزارم\n",
    "p3 = SUMMARY_PA[\"mean\"][\"delta\"] > SUMMARY_VS[\"mean\"][\"pooled\"] - CONTROL_FMAX_S5[\"mean\"]\n",
    "\n",
    "print(\"\\n\" + \"=\" * 100)\n",
    "print(\"آزمونِ سه پیش‌بینیِ فرضیه:\")\n",
    "print(f\"  (۱) وزن‌های {BP_KEY} به ۱٫۰۰ میل می‌کنند؟   \"\n",
    "      f\"{'بله' if p1 else 'خیر'}   (سهمِ وزن‌های دقیقاً ۱٫۰۰: {bp_frac_one:.0%})\")\n",
    "for r in ROWS_PA:\n",
    "    print(f\"        بذر {r['seed']:>3}: {r['alphas'][BP_KEY]}\")\n",
    "print(f\"  (۲) زیانِ {BP_KEY} حذف یا بسیار کوچک شد؟   {'بله' if p2 else 'خیر'}   \"\n",
    "      f\"(Δ از {SUMMARY_VS[BP_KEY]['pooled'] - CONTROL_FMAX_S5[BP_KEY]:+.4f} به \"\n",
    "      f\"{SUMMARY_PA[BP_KEY]['delta']:+.4f})\")\n",
    "print(f\"  (۳) میانگینِ جنبه‌ای بالاتر رفت؟   {'بله' if p3 else 'خیر'}   \"\n",
    "      f\"(Δ از {SUMMARY_VS['mean']['pooled'] - CONTROL_FMAX_S5['mean']:+.4f} به \"\n",
    "      f\"{SUMMARY_PA['mean']['delta']:+.4f})\")\n",
    "print(\"=\" * 100)\n",
    "print(\"خوانش: اگر هر سه «بله» شد، فرضیه تأیید می‌شود. اگر (۱) بله ولی (۳) خیر شد،\")\n",
    "print(\"یعنی دروازه درست تشخیص داده ولی\")\n",
    "print(\"۱۲ پارامتر روی اعتبارسنجی بیش‌برازش کرده‌اند. اگر (۱) خیر شد، فرضیهٔ\")\n",
    "print(\"«هدفِ تجمیعی مقصر است» رد شده و علتِ زیانِ BP جای دیگری است.\")\n",
    "print(\"=\" * 100)\n",
    "\n",
    "PERASPECT_RESULT = dict(seeds=SEEDS_S5, rows=ROWS_PA, vs_control=SUMMARY_PA,\n",
    "                        vs_pooled=SUMMARY_VS, bp_key=BP_KEY,\n",
    "                        predictions=dict(bp_weights_to_one=bool(p1),\n",
    "                                         bp_harm_removed=bool(p2),\n",
    "                                         aggregate_improved=bool(p3)))\n",
    "print(\"\\nنتیجهٔ کامل در متغیرِ PERASPECT_RESULT ذخیره شد.\")\n",
    "print(json.dumps({\"vs_control\": SUMMARY_PA, \"vs_pooled\": SUMMARY_VS},\n",
    "                 indent=1, ensure_ascii=False))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "93e2141a",
   "metadata": {},
   "source": [
    "## 18. Dataset audit"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "e61040fe-bb3a-4bd0-9c77-5b964b052360",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:05:46.339924Z",
     "iopub.status.busy": "2026-09-26T17:05:46.339642Z",
     "iopub.status.idle": "2026-09-26T17:05:47.384555Z",
     "shell.execute_reply": "2026-09-26T17:05:47.383894Z",
     "shell.execute_reply.started": "2026-09-26T17:05:46.339901Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "پوشهٔ داده: /kaggle/input/datasets/farzadmajidi/datacafa6\n",
      "\n",
      "----------------------------------------------------------------------------------------\n",
      "(۱) شکل تفکیک‌ها\n",
      "----------------------------------------------------------------------------------------\n",
      "  تفکیک                        X                 Y         ids     dtype X\n",
      "  train             (28000, 480)      (28000, 180)       28000     float32\n",
      "  val                (6000, 480)       (6000, 180)        6000     float32\n",
      "  test               (6000, 480)       (6000, 180)        6000     float32\n",
      "\n",
      "  X_val و X_test یکسان‌اند؟  ✅ خیر\n",
      "     (هم‌شکل‌اند ولی محتوا متفاوت است؛ بیشینهٔ اختلاف = 4.855)\n",
      "  Y_val و Y_test یکسان‌اند؟  ✅ خیر\n",
      "\n",
      "----------------------------------------------------------------------------------------\n",
      "(۲) ناهم‌پوشانیِ شناسهٔ پروتئین بین تفکیک‌ها\n",
      "----------------------------------------------------------------------------------------\n",
      "  train ∩ val  :      0 مشترک   ✅\n",
      "  train ∩ test :      0 مشترک   ✅\n",
      "    val ∩ test :      0 مشترک   ✅\n",
      "\n",
      "----------------------------------------------------------------------------------------\n",
      "(۳) تخصیصِ خوشه — آیا هیچ خوشه‌ای بین تفکیک‌ها شکسته شده؟\n",
      "----------------------------------------------------------------------------------------\n",
      "  خوشهٔ یکتا: 35,803   |   شناسهٔ نگاشت‌شده: 40,000\n",
      "  train ∩ val  :      0 خوشهٔ مشترک   ✅ خوشه‌ناهم‌پوشان\n",
      "  train ∩ test :      0 خوشهٔ مشترک   ✅ خوشه‌ناهم‌پوشان\n",
      "    val ∩ test :      0 خوشهٔ مشترک   ✅ خوشه‌ناهم‌پوشان\n",
      "\n",
      "  ✅ تفکیک خوشه‌ناهم‌پوشان است\n",
      "\n",
      "----------------------------------------------------------------------------------------\n",
      "(۴) آمار برچسب\n",
      "----------------------------------------------------------------------------------------\n",
      "  تفکیک      پروتئین   اصطلاح     چگالی   برچسب/پروتئین اصطلاحِ بی‌مثبت\n",
      "  train       28,000      180    0.1059           19.05               0\n",
      "  val          6,000      180    0.0969           17.43               0\n",
      "  test         6,000      180    0.0974           17.54               0\n",
      "\n",
      "  terms.txt: 180 اصطلاح   ✅ با Y هم‌اندازه\n",
      "  پنج اصطلاح اول: ['GO:0000166', 'GO:0000785', 'GO:0000976', 'GO:0000977', 'GO:0000981']\n",
      "  duplicate_aliases.json: 40000 مدخل (توالی‌های تکراری ادغام‌شده)\n",
      "\n",
      "----------------------------------------------------------------------------------------\n",
      "(۵) شناسنامه — برای بخش «داده» و بازتولیدپذیری\n",
      "----------------------------------------------------------------------------------------\n",
      "\n",
      "  ▸ manifest.json\n",
      "      protocol: prep-v2-training-only-vocabulary\n",
      "      seed: 42\n",
      "      per_aspect_requested: 60\n",
      "      min_train_frequency: 250\n",
      "      residue_limit: 1022\n",
      "      n_terms: 180\n",
      "      term_selection_source: train\n",
      "      ic_fit_source_required: train_ann\n",
      "      minhash: <dict، طول 4>\n",
      "      claim_boundary: Approximate k-mer clustering plus exact effective-prefix grouping; not a certified sequence-identity threshold.\n",
      "      test_history: Retrospective repartition; prospective independence is not established.\n",
      "      inputs_sha256: <dict، طول 3>\n",
      "      split_audit: <dict، طول 3>\n",
      "      aspect_cols: <dict، طول 3>\n",
      "      … و 2 کلید دیگر\n",
      "\n",
      "  ▸ extraction_summary.json\n",
      "      status: complete\n",
      "      model_name: facebook/esm2_t12_35M_UR50D\n",
      "      model_revision: 6fbf070e65b0b7291e7bbcd451118c216cff79d8\n",
      "      hidden_size: 480\n",
      "      splits: <dict، طول 3>\n",
      "      pooling: last_hidden_state_mean_residue_tokens_only\n",
      "      fallback_used: False\n",
      "\n",
      "  ▸ extraction_manifest.json\n",
      "      schema: esm-verified-v2\n",
      "      inputs: <dict، طول 7>\n",
      "      model_name: facebook/esm2_t12_35M_UR50D\n",
      "      model_revision: 6fbf070e65b0b7291e7bbcd451118c216cff79d8\n",
      "      hidden_size: 480\n",
      "      dtype: float32\n",
      "      residue_limit: 1022\n",
      "      pooling: last_hidden_state_mean_residue_tokens_only\n",
      "      special_tokens_excluded: True\n",
      "      fallback_used: False\n",
      "      normalization: none; fit any later scaler on training only\n",
      "      torch: 2.10.0+cu128\n",
      "      transformers: 5.0.0\n",
      "      numpy: 2.0.2\n",
      "      … و 6 کلید دیگر\n",
      "\n",
      "  ▸ splits.json\n",
      "      train: <list، طول 28000>\n",
      "      val: <list، طول 6000>\n",
      "      test: <list، طول 6000>\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Dataset audit: disjointness of the splits at protein and cluster level,\n",
    "# label statistics and the preparation manifests.\n",
    "# ============================================================================\n",
    "\n",
    "import os, json, glob\n",
    "import numpy as np\n",
    "\n",
    "# --- پیدا کردن پوشه بدون تکیه بر نام دقیق ---\n",
    "_CANDS = [p for p in glob.glob(\"/kaggle/input/**/X_train.npy\", recursive=True)]\n",
    "if \"DATA_DIR\" in globals() and os.path.exists(os.path.join(DATA_DIR, \"X_train.npy\")):\n",
    "    _DIR = DATA_DIR\n",
    "elif _CANDS:\n",
    "    _DIR = os.path.dirname(_CANDS[0])\n",
    "else:\n",
    "    raise FileNotFoundError(\"X_train.npy پیدا نشد — مسیر را دستی بدهید.\")\n",
    "print(f\"پوشهٔ داده: {_DIR}\\n\")\n",
    "\n",
    "\n",
    "def _load(name):\n",
    "    p = os.path.join(_DIR, name)\n",
    "    return np.load(p, allow_pickle=True) if os.path.exists(p) else None\n",
    "\n",
    "\n",
    "def _json(name):\n",
    "    p = os.path.join(_DIR, name)\n",
    "    if not os.path.exists(p):\n",
    "        return None\n",
    "    with open(p, encoding=\"utf-8\") as f:\n",
    "        return json.load(f)\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۱) شکل‌ها\n",
    "# ------------------------------------------------------------\n",
    "X = {s: _load(f\"X_{s}.npy\") for s in (\"train\", \"val\", \"test\")}\n",
    "Y = {s: _load(f\"Y_{s}.npy\") for s in (\"train\", \"val\", \"test\")}\n",
    "ID = {s: _load(f\"ids_{s}.npy\") for s in (\"train\", \"val\", \"test\")}\n",
    "\n",
    "print(\"-\" * 88)\n",
    "print(\"(۱) شکل تفکیک‌ها\")\n",
    "print(\"-\" * 88)\n",
    "print(f\"  {'تفکیک':<8}{'X':>22}{'Y':>18}{'ids':>12}{'dtype X':>12}\")\n",
    "for s in (\"train\", \"val\", \"test\"):\n",
    "    ids_n = \"—\" if ID[s] is None else str(len(ID[s]))\n",
    "    print(f\"  {s:<8}{str(X[s].shape):>22}{str(Y[s].shape):>18}{ids_n:>12}{str(X[s].dtype):>12}\")\n",
    "\n",
    "# آیا val و test تصادفاً یکی‌اند؟ (هشدار قدیمیِ «Xte=Xva» را قطعی می‌بندد)\n",
    "same_shape = X[\"val\"].shape == X[\"test\"].shape\n",
    "identical = bool(same_shape and np.array_equal(X[\"val\"], X[\"test\"]))\n",
    "print(f\"\\n  X_val و X_test یکسان‌اند؟  {'❌ بله — نشت جدی' if identical else '✅ خیر'}\")\n",
    "if same_shape and not identical:\n",
    "    d = float(np.abs(X[\"val\"] - X[\"test\"]).max())\n",
    "    print(f\"     (هم‌شکل‌اند ولی محتوا متفاوت است؛ بیشینهٔ اختلاف = {d:.4g})\")\n",
    "if Y[\"val\"].shape == Y[\"test\"].shape:\n",
    "    print(f\"  Y_val و Y_test یکسان‌اند؟  \"\n",
    "          f\"{'❌ بله' if np.array_equal(Y['val'], Y['test']) else '✅ خیر'}\")\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۲) ناهم‌پوشانیِ شناسه‌ها\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"-\" * 88)\n",
    "print(\"(۲) ناهم‌پوشانیِ شناسهٔ پروتئین بین تفکیک‌ها\")\n",
    "print(\"-\" * 88)\n",
    "if all(ID[s] is not None for s in ID):\n",
    "    S = {s: set(map(str, ID[s].tolist())) for s in ID}\n",
    "    for a, b in ((\"train\", \"val\"), (\"train\", \"test\"), (\"val\", \"test\")):\n",
    "        inter = S[a] & S[b]\n",
    "        print(f\"  {a:>5} ∩ {b:<5}: {len(inter):>6} مشترک   \"\n",
    "              f\"{'✅' if not inter else '❌ نشت'}\")\n",
    "        if inter:\n",
    "            print(f\"        نمونه: {list(inter)[:5]}\")\n",
    "    for s in S:\n",
    "        dup = len(ID[s]) - len(S[s])\n",
    "        if dup:\n",
    "            print(f\"  ⚠️ {s}: {dup} شناسهٔ تکراری درون خودِ تفکیک\")\n",
    "else:\n",
    "    print(\"  فایل ids_*.npy موجود نیست — این بررسی رد شد.\")\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۳) ناهم‌پوشانیِ خوشه\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"-\" * 88)\n",
    "print(\"(۳) تخصیصِ خوشه — آیا هیچ خوشه‌ای بین تفکیک‌ها شکسته شده؟\")\n",
    "print(\"-\" * 88)\n",
    "clusters = _json(\"clusters.json\")\n",
    "if clusters is None:\n",
    "    print(\"  clusters.json موجود نیست.\")\n",
    "elif all(ID[s] is not None for s in ID):\n",
    "    # ساختار ناشناخته است؛ هر دو حالت رایج را پوشش می‌دهیم\n",
    "    id2c = None\n",
    "    if isinstance(clusters, dict):\n",
    "        vals = list(clusters.values())[:3]\n",
    "        if vals and not isinstance(vals[0], (list, tuple, set)):\n",
    "            id2c = {str(k): v for k, v in clusters.items()}          # id → cluster\n",
    "        else:\n",
    "            id2c = {str(i): c for c, mem in clusters.items() for i in mem}  # cluster → [ids]\n",
    "    if id2c is None:\n",
    "        print(f\"  ساختار clusters.json شناخته نشد؛ نوع = {type(clusters).__name__}\")\n",
    "    else:\n",
    "        print(f\"  خوشهٔ یکتا: {len(set(id2c.values())):,}   |   شناسهٔ نگاشت‌شده: {len(id2c):,}\")\n",
    "        cl = {s: {id2c[i] for i in map(str, ID[s].tolist()) if i in id2c} for s in ID}\n",
    "        missing = {s: sum(1 for i in map(str, ID[s].tolist()) if i not in id2c) for s in ID}\n",
    "        for s in ID:\n",
    "            if missing[s]:\n",
    "                print(f\"  ⚠️ {s}: {missing[s]} شناسه در clusters.json نیست\")\n",
    "        ok = True\n",
    "        for a, b in ((\"train\", \"val\"), (\"train\", \"test\"), (\"val\", \"test\")):\n",
    "            sh = cl[a] & cl[b]\n",
    "            ok &= not sh\n",
    "            print(f\"  {a:>5} ∩ {b:<5}: {len(sh):>6} خوشهٔ مشترک   \"\n",
    "                  f\"{'✅ خوشه‌ناهم‌پوشان' if not sh else '❌ خوشه شکسته'}\")\n",
    "        if ok:\n",
    "            print(\"\\n  ✅ تفکیک خوشه‌ناهم‌پوشان است\")\n",
    "        else:\n",
    "            print(\"\\n  ❌ خوشه بین تفکیک‌ها شکسته — ادعای کنترلِ نشت قابل دفاع نیست.\")\n",
    "else:\n",
    "    print(\"  برای این بررسی هم clusters.json و هم ids_*.npy لازم است.\")\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۴) آمار برچسب\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"-\" * 88)\n",
    "print(\"(۴) آمار برچسب\")\n",
    "print(\"-\" * 88)\n",
    "print(f\"  {'تفکیک':<8}{'پروتئین':>10}{'اصطلاح':>9}{'چگالی':>10}\"\n",
    "      f\"{'برچسب/پروتئین':>16}{'اصطلاحِ بی‌مثبت':>16}\")\n",
    "for s in (\"train\", \"val\", \"test\"):\n",
    "    Ys = (np.asarray(Y[s]) > 0.5)\n",
    "    dens = Ys.mean()\n",
    "    per = Ys.sum(axis=1).mean()\n",
    "    empty = int((Ys.sum(axis=0) == 0).sum())\n",
    "    print(f\"  {s:<8}{Ys.shape[0]:>10,}{Ys.shape[1]:>9}{dens:>10.4f}{per:>16.2f}{empty:>16}\")\n",
    "\n",
    "terms = None\n",
    "p_terms = os.path.join(_DIR, \"terms.txt\")\n",
    "if os.path.exists(p_terms):\n",
    "    terms = [l.strip() for l in open(p_terms, encoding=\"utf-8\") if l.strip()]\n",
    "    print(f\"\\n  terms.txt: {len(terms)} اصطلاح   \"\n",
    "          f\"{'✅ با Y هم‌اندازه' if len(terms) == Y['train'].shape[1] else '❌ ناهم‌اندازه با Y'}\")\n",
    "    print(f\"  پنج اصطلاح اول: {terms[:5]}\")\n",
    "\n",
    "dup_al = _json(\"duplicate_aliases.json\")\n",
    "if dup_al is not None:\n",
    "    n = len(dup_al) if hasattr(dup_al, \"__len__\") else \"?\"\n",
    "    print(f\"  duplicate_aliases.json: {n} مدخل (توالی‌های تکراری ادغام‌شده)\")\n",
    "\n",
    "# ------------------------------------------------------------\n",
    "# ۵) شناسنامهٔ ساخت داده\n",
    "# ------------------------------------------------------------\n",
    "print(\"\\n\" + \"-\" * 88)\n",
    "print(\"(۵) شناسنامه — برای بخش «داده» و بازتولیدپذیری\")\n",
    "print(\"-\" * 88)\n",
    "\n",
    "\n",
    "def _show(name, obj, depth=0, maxitems=14):\n",
    "    if obj is None:\n",
    "        print(f\"  {name}: موجود نیست\")\n",
    "        return\n",
    "    print(f\"\\n  ▸ {name}\")\n",
    "    if isinstance(obj, dict):\n",
    "        for i, (k, v) in enumerate(obj.items()):\n",
    "            if i >= maxitems:\n",
    "                print(f\"      … و {len(obj) - maxitems} کلید دیگر\")\n",
    "                break\n",
    "            if isinstance(v, (dict, list)):\n",
    "                print(f\"      {k}: <{type(v).__name__}، طول {len(v)}>\")\n",
    "            else:\n",
    "                print(f\"      {k}: {v}\")\n",
    "    elif isinstance(obj, list):\n",
    "        print(f\"      <list، طول {len(obj)}>؛ نمونه: {obj[:3]}\")\n",
    "    else:\n",
    "        print(f\"      {obj}\")\n",
    "\n",
    "\n",
    "for nm in (\"manifest.json\", \"extraction_summary.json\",\n",
    "           \"extraction_manifest.json\", \"splits.json\"):\n",
    "    _show(nm, _json(nm))\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc3bceec",
   "metadata": {},
   "source": [
    "## 19. Regularisation check for CQKR"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "9b78c604-a36e-47e1-9470-5f785a5abbcf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:05:47.385967Z",
     "iopub.status.busy": "2026-09-26T17:05:47.385659Z",
     "iopub.status.idle": "2026-09-26T17:07:13.723652Z",
     "shell.execute_reply": "2026-09-26T17:07:13.722984Z",
     "shell.execute_reply.started": "2026-09-26T17:05:47.385947Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "==================================================================================================\n",
      "تشخیص ridge — bandwidth=0.35، لندمارک=1024، کیوبیت=10\n",
      "==================================================================================================\n",
      "\n",
      "--------------------------------------------------------------------------------------------------\n",
      "(۱) مقیاس هسته — اگر ستون «نسبت» از ۱ دور باشد، ridge ثابت دو منظم‌سازیِ متفاوت است\n",
      "--------------------------------------------------------------------------------------------------\n",
      "  کمیت                                     کوانتومی              RBF      نسبت q/RBF\n",
      "  میانگین K_mm خارج‌قطری                   0.004073         0.595050          0.0068\n",
      "  انحراف معیار K_mm خارج‌قطری              0.011741         0.161072          0.0729\n",
      "  میانگین K_nm آموزش                       0.004156         0.596309          0.0070\n",
      "  میانگین قطر K_mm                         0.999999         1.000000          1.0000\n",
      "\n",
      "--------------------------------------------------------------------------------------------------\n",
      "(۲) جاروب ridge — انتخاب روی اعتبارسنجی. ستون آزمون عمداً اینجا نیست.\n",
      "    «جریمهٔ مؤثر» = ridge ÷ میانگین قطر گرام؛ همین باید بین دو بازو یکی باشد.\n",
      "--------------------------------------------------------------------------------------------------\n",
      "      ridge    val کوانتومی       val RBF   جریمهٔ مؤثر q جریمهٔ مؤثر RBF\n",
      "      1e-05          0.5840        0.6295        2.86e-06        3.66e-07\n",
      "     0.0001          0.5840        0.6296        2.86e-05        3.66e-06\n",
      "      0.001          0.5840        0.6303        2.86e-04        3.66e-05\n",
      "       0.01          0.5840        0.6330        2.86e-03        3.66e-04\n",
      "        0.1          0.5844        0.6330        2.86e-02        3.66e-03\n",
      "          1          0.5848        0.6294        2.86e-01        3.66e-02\n",
      "         10          0.5749        0.6200        2.86e+00        3.66e-01\n",
      "        100          0.5495        0.6067        2.86e+01        3.66e+00\n",
      "\n",
      "==================================================================================================\n",
      "(۳) عملکرد روی آزمون — فقط برای دو پیکربندی محاسبه شد\n",
      "==================================================================================================\n",
      "  ridge ثابت ۱٫۰ (پروتکلِ اصلی):   کوانتومی=0.5891  RBF=0.6313  Δ=-0.0422\n",
      "  بازتولید «CQKR — هستهٔ کوانتومی هم‌وردا»: 0.5891 در برابر 0.5891 (اختلاف +0.00000)  ✅ وفادار\n",
      "  ridge تنظیم‌شدهٔ جداگانه:       کوانتومی=0.5891 (ridge=1)  RBF=0.6357 (ridge=0.1)  Δ=-0.0466\n",
      "  تغییر Δ پس از تطبیقِ منظم‌سازی: -0.0044\n",
      "\n",
      "--------------------------------------------------------------------------------------------------\n",
      "(۴) اصلاح مستقل — هر دو هسته به میانگین خارج‌قطریِ ۱ مقیاس، سپس ridge دوباره تنظیم\n",
      "--------------------------------------------------------------------------------------------------\n",
      "  پس از هم‌مقیاس‌سازی: کوانتومی=0.5891 (ridge=100)  RBF=0.6368 (ridge=0.1)  Δ=-0.0476\n",
      "  توافق دو اصلاح: اختلاف Δ = 0.0010  ✅ هم‌خوان\n",
      "\n",
      "--- CQKR کوانتومی (ridge=1 تنظیم‌شده) ---\n",
      "  [تجمیعی]  F_max = 0.4823 (τ=0.23) | wF_max = 0.3902 | S_min = 36.9526\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6307   0.19   0.5493   0.19  10.8958     0.3700     0.8498       60      4094\n",
      "  BP       0.4875   0.17   0.4365   0.16  22.4863     0.3288     0.7352       60      4298\n",
      "  CC       0.6492   0.22   0.5182   0.19  14.7249     0.4888     0.8523       60      4365\n",
      "  میانگین   0.5891          0.5013\n",
      "  زمان آموزش  = 0.0 ثانیه\n",
      "\n",
      "--- کنترل RBF (ridge=0.1 تنظیم‌شده) ---\n",
      "  [تجمیعی]  F_max = 0.5260 (τ=0.29) | wF_max = 0.4395 | S_min = 34.5654\n",
      "  جنبه      F_max     τ*   wF_max    τ*w    S_min   AUPR-خرد  AUROC-خرد  #اصطلاح  #پروتئین\n",
      "  MF       0.6748   0.25   0.6007   0.24   9.8601     0.4505     0.8964       60      4094\n",
      "  BP       0.5390   0.22   0.4911   0.20  20.4415     0.3925     0.7915       60      4298\n",
      "  CC       0.6933   0.27   0.5754   0.23  13.4649     0.5656     0.8845       60      4365\n",
      "  میانگین   0.6357          0.5558\n",
      "  زمان آموزش  = 0.0 ثانیه\n",
      "\n",
      "==================================================================================================\n",
      "(۵) بوت‌استرپ زوجی — کوانتومی منهای RBF، هر دو با ridge تنظیم‌شده\n",
      "==================================================================================================\n",
      "  Δ [میانگین سه جنبه] = -0.0466   CI95 = [-0.0498, -0.0433]   p ≈ 0.000   معنادار\n",
      "  Δ [MF] = -0.0442   CI95 = [-0.0508, -0.0379]   p ≈ 0.000   معنادار\n",
      "  Δ [BP] = -0.0515   CI95 = [-0.0566, -0.0456]   p ≈ 0.000   معنادار\n",
      "  Δ [CC] = -0.0442   CI95 = [-0.0491, -0.0396]   p ≈ 0.000   معنادار\n",
      "\n",
      "==================================================================================================\n",
      "خوانش:\n",
      "  • اگر خودآزماییِ بازتولید ❌ بود، بقیهٔ اعداد بی‌اعتبارند؛ اول آن را درست کنید.\n",
      "  • اگر Δ پس از هر دو اصلاح تقریباً بی‌تغییر ماند، نتیجهٔ منفی تأیید و\n",
      "    در برابر ایرادِ «منظم‌سازیِ نابرابر» مقاوم می‌شود.\n",
      "  • اگر Δ به صفر نزدیک یا مثبت شد، کسریِ CQKR مصنوعِ جریمه است.\n",
      "زمان کل: 86.1 ثانیه\n",
      "==================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Regularisation check for CQKR: ridge selected on validation separately for\n",
    "# each kernel, and an independent rescaling of both kernels.\n",
    "# ============================================================================\n",
    "\n",
    "import numpy as np, torch, time\n",
    "\n",
    "for _n in (\"CQKR_CFG\", \"CQKRFeatureMap\", \"fidelity_kernel\", \"rbf_kernel\",\n",
    "           \"_pca_fit\", \"_pca_apply\", \"record\", \"paired_bootstrap\",\n",
    "           \"protein_centric\", \"ASPECTS\", \"ASPECT_COLS\", \"IC\", \"_dev\"):\n",
    "    assert _n in globals(), f\"«{_n}» تعریف نشده — اول سلول CQKR را اجرا کنید.\"\n",
    "\n",
    "# --- آرایه‌های برچسب: نوت‌بوک بعضی را فقط به شکل numpy دارد ---\n",
    "_YTR = Ytr.numpy() if hasattr(Ytr, \"numpy\") else np.asarray(Ytr)\n",
    "_YVA = np.asarray(Yva_np, dtype=np.float32)\n",
    "_YTE = np.asarray(Yte_np, dtype=np.float32)\n",
    "_XVA = Xva.numpy() if hasattr(Xva, \"numpy\") else np.asarray(Xva)\n",
    "_XTE = Xte.numpy() if hasattr(Xte, \"numpy\") else np.asarray(Xte)\n",
    "\n",
    "if globals().get(\"TEST_IS_VAL\", False):\n",
    "    print(\"⛔ TEST_IS_VAL=True — مجموعهٔ آزمون همان اعتبارسنجی است. تنظیم ridge روی\")\n",
    "    print(\"   اعتبارسنجی در این حالت یعنی تنظیم روی آزمون، و نتیجه بی‌اعتبار است.\")\n",
    "    raise SystemExit(\"ابتدا X_test/Y_test واقعی را بارگذاری کنید.\")\n",
    "\n",
    "RIDGE_GRID = (1e-5, 1e-4, 1e-3, 1e-2, 1e-1, 1.0, 10.0, 100.0)\n",
    "_ROW = \"{:>11}{:>16}{:>14}{:>16}{:>16}\"\n",
    "\n",
    "\n",
    "def _fmax_am(Y_true, scores):\n",
    "    \"\"\"میانگین جنبه‌ایِ F_max — همان معیار انتخابِ مدل.\"\"\"\n",
    "    v = [protein_centric(Y_true, scores, IC, cols=ASPECT_COLS[a])[\"fmax\"]\n",
    "         for a in ASPECTS if len(ASPECT_COLS[a])]\n",
    "    return float(np.mean(v)) if v else float(\"nan\")\n",
    "\n",
    "\n",
    "def _solve(Kmm, Knm_tr, Knm_eval, Ytr_t, ridge, M):\n",
    "    \"\"\"عیناً fit_predict سلول CQKR، با ridge به‌عنوان آرگومان.\n",
    "\n",
    "    برمی‌گرداند: پیش‌بینی برای هر ماتریس Knm_eval، میانگین قطر گرام\n",
    "    (مقیاس واقعی مسئله، که جریمه باید با آن سنجیده شود)، و خطا در صورت تکین بودن.\n",
    "    \"\"\"\n",
    "    S, U = torch.linalg.eigh(Kmm.double() + 1e-6 * torch.eye(M, device=_dev, dtype=torch.float64))\n",
    "    Wht = (U @ torch.diag(S.clamp_min(1e-8).rsqrt()) @ U.T).float()     # K_mm^{-1/2}\n",
    "    Ztr_f = Knm_tr @ Wht\n",
    "    Gram = Ztr_f.T @ Ztr_f\n",
    "    gram_diag = float(torch.diagonal(Gram).mean())\n",
    "    try:\n",
    "        W = torch.linalg.solve(Gram + ridge * torch.eye(M, device=_dev), Ztr_f.T @ Ytr_t)\n",
    "    except Exception as e:\n",
    "        return None, gram_diag, str(e)[:50]\n",
    "    return [((K @ Wht) @ W).clamp(0, 1).cpu().numpy() for K in Knm_eval], gram_diag, None\n",
    "\n",
    "\n",
    "def _build_kernels(cfg):\n",
    "    \"\"\"ویژگی، هم‌دسته و لندمارکِ دقیقاً یکسان با سلول CQKR، به‌اضافهٔ اعتبارسنجی.\"\"\"\n",
    "    rng = np.random.default_rng(cfg[\"seed\"])\n",
    "\n",
    "    Xm, V = _pca_fit(Xtr.numpy(), cfg[\"n_qubits\"], cfg[\"seed\"])\n",
    "    Ztr = _pca_apply(Xtr.numpy(), Xm, V)\n",
    "    Zva = _pca_apply(_XVA, Xm, V)\n",
    "    Zte = _pca_apply(_XTE, Xm, V)\n",
    "\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg[\"n_cosets\"], n_init=10, random_state=cfg[\"seed\"]).fit(Ztr)\n",
    "    ctr, cva, cte = km.labels_, km.predict(Zva), km.predict(Zte)\n",
    "\n",
    "    # ⚠️ بازپخشِ وفادار — سلول اصلی این کشش را برای دروازهٔ صفر انجام می‌دهد.\n",
    "    # بدون تکرارش لندمارک‌ها با لندمارک‌های سلولِ CQKR یکی نمی‌شوند.\n",
    "    if \"gate0_sample\" in cfg:\n",
    "        _ = rng.choice(len(Ztr), size=min(cfg[\"gate0_sample\"], len(Ztr)), replace=False)\n",
    "\n",
    "    M = min(cfg[\"n_landmarks\"], len(Ztr))\n",
    "    lm = rng.choice(len(Ztr), size=M, replace=False)\n",
    "\n",
    "    fm = CQKRFeatureMap(cfg)\n",
    "    Ptr, Pva, Pte = fm.states(Ztr, ctr), fm.states(Zva, cva), fm.states(Zte, cte)\n",
    "    Plm = Ptr[lm]\n",
    "    Kq = dict(mm=fidelity_kernel(Plm, Plm), tr=fidelity_kernel(Ptr, Plm),\n",
    "              va=fidelity_kernel(Pva, Plm), te=fidelity_kernel(Pte, Plm))\n",
    "    del Ptr, Pva, Pte, Plm\n",
    "    if getattr(_dev, \"type\", \"cpu\") == \"cuda\":\n",
    "        torch.cuda.empty_cache()\n",
    "\n",
    "    Atr, Ava = torch.as_tensor(Ztr, device=_dev), torch.as_tensor(Zva, device=_dev)\n",
    "    Ate, Alm = torch.as_tensor(Zte, device=_dev), torch.as_tensor(Ztr[lm], device=_dev)\n",
    "    gm = 1.0 / (2.0 * (torch.cdist(Alm, Alm) ** 2).median().clamp_min(1e-9))\n",
    "    Kc = dict(mm=rbf_kernel(Alm, Alm, gm), tr=rbf_kernel(Atr, Alm, gm),\n",
    "              va=rbf_kernel(Ava, Alm, gm), te=rbf_kernel(Ate, Alm, gm))\n",
    "    return Kq, Kc, M\n",
    "\n",
    "\n",
    "def diagnose_ridge(cfg=None, gamma=None, grid=RIDGE_GRID, register=True):\n",
    "    cfg = dict(CQKR_CFG, **(cfg or {}))\n",
    "    if gamma is not None:\n",
    "        cfg[\"bandwidth\"] = float(gamma)\n",
    "    elif \"GAMMA_SELECTED\" in globals():\n",
    "        cfg[\"bandwidth\"] = float(GAMMA_SELECTED)\n",
    "    t0 = time.time()\n",
    "\n",
    "    print(\"=\" * 98)\n",
    "    print(f\"تشخیص ridge — bandwidth={cfg['bandwidth']}، لندمارک={cfg['n_landmarks']}، \"\n",
    "          f\"کیوبیت={cfg['n_qubits']}\")\n",
    "    print(\"=\" * 98)\n",
    "\n",
    "    Kq, Kc, M = _build_kernels(cfg)\n",
    "    Ytr_t = torch.as_tensor(_YTR, device=_dev)\n",
    "    off = ~torch.eye(M, dtype=torch.bool, device=_dev)\n",
    "\n",
    "    # ---------------- (۱) مقیاس هسته ----------------\n",
    "    print(\"\\n\" + \"-\" * 98)\n",
    "    print(\"(۱) مقیاس هسته — اگر ستون «نسبت» از ۱ دور باشد، ridge ثابت دو منظم‌سازیِ متفاوت است\")\n",
    "    print(\"-\" * 98)\n",
    "    print(f\"  {'کمیت':<32}{'کوانتومی':>17}{'RBF':>17}{'نسبت q/RBF':>16}\")\n",
    "    for nm, a, b in (\n",
    "        (\"میانگین K_mm خارج‌قطری\", Kq[\"mm\"][off].mean().item(), Kc[\"mm\"][off].mean().item()),\n",
    "        (\"انحراف معیار K_mm خارج‌قطری\", Kq[\"mm\"][off].std().item(), Kc[\"mm\"][off].std().item()),\n",
    "        (\"میانگین K_nm آموزش\", Kq[\"tr\"].mean().item(), Kc[\"tr\"].mean().item()),\n",
    "        (\"میانگین قطر K_mm\", float(torch.diagonal(Kq[\"mm\"]).mean()),\n",
    "                             float(torch.diagonal(Kc[\"mm\"]).mean())),\n",
    "    ):\n",
    "        print(f\"  {nm:<32}{a:>17.6f}{b:>17.6f}{a / max(b, 1e-12):>16.4f}\")\n",
    "\n",
    "    # ---------------- (۲) جاروب: فقط اعتبارسنجی ----------------\n",
    "    print(\"\\n\" + \"-\" * 98)\n",
    "    print(\"(۲) جاروب ridge — انتخاب روی اعتبارسنجی. ستون آزمون عمداً اینجا نیست.\")\n",
    "    print(\"    «جریمهٔ مؤثر» = ridge ÷ میانگین قطر گرام؛ همین باید بین دو بازو یکی باشد.\")\n",
    "    print(\"-\" * 98)\n",
    "    print(_ROW.format(\"ridge\", \"val کوانتومی\", \"val RBF\", \"جریمهٔ مؤثر q\", \"جریمهٔ مؤثر RBF\"))\n",
    "\n",
    "    sweep, keep = [], {}\n",
    "    best = {\"q\": (None, -1.0), \"c\": (None, -1.0)}\n",
    "    for r in grid:\n",
    "        pq, gq, eq = _solve(Kq[\"mm\"], Kq[\"tr\"], (Kq[\"va\"], Kq[\"te\"]), Ytr_t, r, M)\n",
    "        pc, gc, ec = _solve(Kc[\"mm\"], Kc[\"tr\"], (Kc[\"va\"], Kc[\"te\"]), Ytr_t, r, M)\n",
    "        fq = _fmax_am(_YVA, pq[0]) if pq else float(\"nan\")\n",
    "        fc = _fmax_am(_YVA, pc[0]) if pc else float(\"nan\")\n",
    "        sweep.append(dict(ridge=r, val_q=fq, val_c=fc, gram_q=gq, gram_c=gc, err_q=eq, err_c=ec))\n",
    "        print(_ROW.format(f\"{r:g}\", f\"{fq:.4f}\" if pq else \"تکین\",\n",
    "                          f\"{fc:.4f}\" if pc else \"تکین\",\n",
    "                          f\"{r / max(gq, 1e-30):.2e}\", f\"{r / max(gc, 1e-30):.2e}\"))\n",
    "        if pq and fq > best[\"q\"][1]:\n",
    "            best[\"q\"] = (r, fq); keep[\"q_best\"] = pq[1]\n",
    "        if pc and fc > best[\"c\"][1]:\n",
    "            best[\"c\"] = (r, fc); keep[\"c_best\"] = pc[1]\n",
    "        if abs(r - 1.0) < 1e-12:\n",
    "            if pq: keep[\"q_fixed\"] = pq[1]\n",
    "            if pc: keep[\"c_fixed\"] = pc[1]\n",
    "\n",
    "    # ---------------- (۳) آزمون: فقط دو نقطه ----------------\n",
    "    print(\"\\n\" + \"=\" * 98)\n",
    "    print(\"(۳) عملکرد روی آزمون — فقط برای دو پیکربندی محاسبه شد\")\n",
    "    print(\"=\" * 98)\n",
    "    res = {}\n",
    "    if \"q_fixed\" in keep and \"c_fixed\" in keep:\n",
    "        fq0, fc0 = _fmax_am(_YTE, keep[\"q_fixed\"]), _fmax_am(_YTE, keep[\"c_fixed\"])\n",
    "        res[\"fixed\"] = dict(fmax_q=fq0, fmax_c=fc0, delta=fq0 - fc0)\n",
    "        print(f\"  ridge ثابت ۱٫۰ (پروتکلِ اصلی):   کوانتومی={fq0:.4f}  RBF={fc0:.4f}  \"\n",
    "              f\"Δ={fq0 - fc0:+.4f}\")\n",
    "        ref = next((k for k in RESULTS if \"CQKR\" in k and \"ridge\" not in k), None)\n",
    "        if ref and \"fmax_aspect_mean\" in RESULTS[ref]:\n",
    "            gap = fq0 - RESULTS[ref][\"fmax_aspect_mean\"]\n",
    "            print(f\"  بازتولید «{ref[:44]}»: {RESULTS[ref]['fmax_aspect_mean']:.4f} \"\n",
    "                  f\"در برابر {fq0:.4f} (اختلاف {gap:+.5f})  \"\n",
    "                  f\"{'✅ وفادار' if abs(gap) < 5e-4 else '❌ وفادار نیست — اعداد را باور نکنید'}\")\n",
    "        else:\n",
    "            print(\"  بازوی اصلی CQKR در RESULTS نبود — خودآزمایی بازتولید انجام نشد.\")\n",
    "\n",
    "    rq, rc = best[\"q\"][0], best[\"c\"][0]\n",
    "    fq1, fc1 = _fmax_am(_YTE, keep[\"q_best\"]), _fmax_am(_YTE, keep[\"c_best\"])\n",
    "    res[\"tuned\"] = dict(fmax_q=fq1, fmax_c=fc1, delta=fq1 - fc1, ridge_q=rq, ridge_c=rc)\n",
    "    print(f\"  ridge تنظیم‌شدهٔ جداگانه:       کوانتومی={fq1:.4f} (ridge={rq:g})  \"\n",
    "          f\"RBF={fc1:.4f} (ridge={rc:g})  Δ={fq1 - fc1:+.4f}\")\n",
    "    if \"fixed\" in res:\n",
    "        print(f\"  تغییر Δ پس از تطبیقِ منظم‌سازی: {res['tuned']['delta'] - res['fixed']['delta']:+.4f}\")\n",
    "        if rq != 1.0:\n",
    "            print(f\"  ⚠️ ridge بهینهٔ کوانتومی {rq:g} است، نه ۱٫۰.\")\n",
    "\n",
    "    # ---------------- (۴) اصلاح مستقل: هم‌مقیاس‌سازی ----------------\n",
    "    print(\"\\n\" + \"-\" * 98)\n",
    "    print(\"(۴) اصلاح مستقل — هر دو هسته به میانگین خارج‌قطریِ ۱ مقیاس، سپس ridge دوباره تنظیم\")\n",
    "    print(\"-\" * 98)\n",
    "    sq = Kq[\"mm\"][off].mean().clamp_min(1e-12)\n",
    "    sc = Kc[\"mm\"][off].mean().clamp_min(1e-12)\n",
    "    Kqn = {k: v / sq for k, v in Kq.items()}\n",
    "    Kcn = {k: v / sc for k, v in Kc.items()}\n",
    "    bq, bc, pqb, pcb = (None, -1.0), (None, -1.0), None, None\n",
    "    for r in grid:\n",
    "        pq, _, _ = _solve(Kqn[\"mm\"], Kqn[\"tr\"], (Kqn[\"va\"], Kqn[\"te\"]), Ytr_t, r, M)\n",
    "        pc, _, _ = _solve(Kcn[\"mm\"], Kcn[\"tr\"], (Kcn[\"va\"], Kcn[\"te\"]), Ytr_t, r, M)\n",
    "        if pq:\n",
    "            f = _fmax_am(_YVA, pq[0])\n",
    "            if f > bq[1]:\n",
    "                bq, pqb = (r, f), pq[1]\n",
    "        if pc:\n",
    "            f = _fmax_am(_YVA, pc[0])\n",
    "            if f > bc[1]:\n",
    "                bc, pcb = (r, f), pc[1]\n",
    "    fq2, fc2 = _fmax_am(_YTE, pqb), _fmax_am(_YTE, pcb)\n",
    "    res[\"rescaled\"] = dict(fmax_q=fq2, fmax_c=fc2, delta=fq2 - fc2,\n",
    "                           ridge_q=bq[0], ridge_c=bc[0])\n",
    "    agree = abs(res[\"tuned\"][\"delta\"] - res[\"rescaled\"][\"delta\"])\n",
    "    print(f\"  پس از هم‌مقیاس‌سازی: کوانتومی={fq2:.4f} (ridge={bq[0]:g})  \"\n",
    "          f\"RBF={fc2:.4f} (ridge={bc[0]:g})  Δ={fq2 - fc2:+.4f}\")\n",
    "    print(f\"  توافق دو اصلاح: اختلاف Δ = {agree:.4f}  \"\n",
    "          f\"{'✅ هم‌خوان' if agree < 0.005 else '⚠️ ناهم‌خوان — به هیچ‌کدام تکیه نکنید'}\")\n",
    "\n",
    "    # ---------------- (۵) بوت‌استرپ زوجی ----------------\n",
    "    if register:\n",
    "        NQ = f\"CQKR کوانتومی (ridge={rq:g} تنظیم‌شده)\"\n",
    "        NC = f\"کنترل RBF (ridge={rc:g} تنظیم‌شده)\"\n",
    "        record(NQ, keep[\"q_best\"], 0.0, n_params=0)\n",
    "        record(NC, keep[\"c_best\"], 0.0, n_params=0)\n",
    "        print(\"\\n\" + \"=\" * 98)\n",
    "        print(\"(۵) بوت‌استرپ زوجی — کوانتومی منهای RBF، هر دو با ridge تنظیم‌شده\")\n",
    "        print(\"=\" * 98)\n",
    "        res[\"boot\"] = {\"کلی\": paired_bootstrap(NQ, NC, None, label=\"میانگین سه جنبه\")}\n",
    "        for a in ASPECTS:\n",
    "            if len(ASPECT_COLS[a]):\n",
    "                res[\"boot\"][a] = paired_bootstrap(NQ, NC, ASPECT_COLS[a],\n",
    "                                                  label=globals().get(\"ASPECT_EN\", {}).get(a, a))\n",
    "        res[\"names\"] = (NQ, NC)\n",
    "\n",
    "    print(\"\\n\" + \"=\" * 98)\n",
    "    print(\"خوانش:\")\n",
    "    print(\"  • اگر خودآزماییِ بازتولید ❌ بود، بقیهٔ اعداد بی‌اعتبارند؛ اول آن را درست کنید.\")\n",
    "    print(\"  • اگر Δ پس از هر دو اصلاح تقریباً بی‌تغییر ماند، نتیجهٔ منفی تأیید و\")\n",
    "    print(\"    در برابر ایرادِ «منظم‌سازیِ نابرابر» مقاوم می‌شود.\")\n",
    "    print(\"  • اگر Δ به صفر نزدیک یا مثبت شد، کسریِ CQKR مصنوعِ جریمه است.\")\n",
    "    print(f\"زمان کل: {time.time() - t0:.1f} ثانیه\")\n",
    "    print(\"=\" * 98)\n",
    "\n",
    "    return dict(cfg=cfg, sweep=sweep, best_q=best[\"q\"], best_c=best[\"c\"], results=res)\n",
    "\n",
    "\n",
    "RIDGE_DIAG = diagnose_ridge()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5a2293b1",
   "metadata": {},
   "source": [
    "## 20. Five-seed matched comparisons"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "00d6d195-026a-4ab5-95a3-3ed73be65429",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-26T17:08:41.632744Z",
     "iopub.status.busy": "2026-09-26T17:08:41.632480Z",
     "iopub.status.idle": "2026-09-26T19:43:08.222929Z",
     "shell.execute_reply": "2026-09-26T19:43:08.222073Z",
     "shell.execute_reply.started": "2026-09-26T17:08:41.632720Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "نقطهٔ بازیابی: /kaggle/working/seed5_standalone.json  (0 نتیجهٔ ذخیره‌شده از قبل)\n",
      "\n",
      "──────────────────────────  هسته‌ها — بذر 42  ──────────────────────────\n",
      "   ✔ CQKR vs RBF-KR (ridge 1.0)       بذر 42:  Q=0.5891  C=0.6313  Δ=-0.0422\n",
      "   ✔ CQKR vs RBF-KR (tuned ridge)     بذر 42:  Q=0.5891  C=0.6357  Δ=-0.0466\n",
      "   ✔ QK-NN vs RBF-kNN-10              بذر 42:  Q=0.6059  C=0.6262  Δ=-0.0203\n",
      "     (هسته‌ها، بذر 42: 19 ثانیه)\n",
      "   ✔ RUQK-NN vs RBF-kNN-480           بذر 42:  Q=0.6113  C=0.6719  Δ=-0.0607\n",
      "   ✔ RUQK-NN vs Cos-kNN               بذر 42:  Q=0.6113  C=0.6663  Δ=-0.0550\n",
      "     (RUQK، بذر 42: 17 ثانیه)\n",
      "\n",
      "──────────────────────────  هسته‌ها — بذر 0  ──────────────────────────\n",
      "   ✔ CQKR vs RBF-KR (ridge 1.0)       بذر  0:  Q=0.6168  C=0.6294  Δ=-0.0126\n",
      "   ✔ CQKR vs RBF-KR (tuned ridge)     بذر  0:  Q=0.6168  C=0.6321  Δ=-0.0153\n",
      "   ✔ QK-NN vs RBF-kNN-10              بذر  0:  Q=0.6145  C=0.6257  Δ=-0.0112\n",
      "     (هسته‌ها، بذر 0: 18 ثانیه)\n",
      "   ✘ بذر 0: دروازهٔ صفرِ RUQK رد کرد — P5 برای این بذر ثبت نمی‌شود.\n",
      "\n",
      "──────────────────────────  هسته‌ها — بذر 1  ──────────────────────────\n",
      "   ✔ CQKR vs RBF-KR (ridge 1.0)       بذر  1:  Q=0.5823  C=0.6264  Δ=-0.0441\n",
      "   ✔ CQKR vs RBF-KR (tuned ridge)     بذر  1:  Q=0.5823  C=0.6286  Δ=-0.0463\n",
      "   ✔ QK-NN vs RBF-kNN-10              بذر  1:  Q=0.6016  C=0.6234  Δ=-0.0218\n",
      "     (هسته‌ها، بذر 1: 18 ثانیه)\n",
      "   ✘ بذر 1: دروازهٔ صفرِ RUQK رد کرد — P5 برای این بذر ثبت نمی‌شود.\n",
      "\n",
      "──────────────────────────  هسته‌ها — بذر 2  ──────────────────────────\n",
      "   ✔ CQKR vs RBF-KR (ridge 1.0)       بذر  2:  Q=0.5879  C=0.6306  Δ=-0.0427\n",
      "   ✔ CQKR vs RBF-KR (tuned ridge)     بذر  2:  Q=0.5879  C=0.6336  Δ=-0.0457\n",
      "   ✔ QK-NN vs RBF-kNN-10              بذر  2:  Q=0.6060  C=0.6251  Δ=-0.0191\n",
      "     (هسته‌ها، بذر 2: 18 ثانیه)\n",
      "   ✘ بذر 2: دروازهٔ صفرِ RUQK رد کرد — P5 برای این بذر ثبت نمی‌شود.\n",
      "\n",
      "──────────────────────────  هسته‌ها — بذر 3  ──────────────────────────\n",
      "   ✔ CQKR vs RBF-KR (ridge 1.0)       بذر  3:  Q=0.5872  C=0.6306  Δ=-0.0434\n",
      "   ✔ CQKR vs RBF-KR (tuned ridge)     بذر  3:  Q=0.5875  C=0.6345  Δ=-0.0471\n",
      "   ✔ QK-NN vs RBF-kNN-10              بذر  3:  Q=0.6049  C=0.6256  Δ=-0.0207\n",
      "     (هسته‌ها، بذر 3: 19 ثانیه)\n",
      "   ✔ RUQK-NN vs RBF-kNN-480           بذر  3:  Q=0.6119  C=0.6718  Δ=-0.0600\n",
      "   ✔ RUQK-NN vs Cos-kNN               بذر  3:  Q=0.6119  C=0.6663  Δ=-0.0544\n",
      "     (RUQK، بذر 3: 17 ثانیه)\n",
      "\n",
      "──────────────────────────  مدل‌های هیبریدی — بذر 42  ──────────────────────────\n",
      "   ✔ VQNN vs PM-MLP                   بذر 42:  Q=0.5616  C=0.5628  Δ=-0.0013\n",
      "   ✔ QRes vs VQNN                     بذر 42:  Q=0.5618  C=0.5616  Δ=+0.0002\n",
      "   ✔ QFE-kNN vs CSF-kNN               بذر 42:  Q=0.5840  C=0.6610  Δ=-0.0771\n",
      "\n",
      "──────────────────────────  مدل‌های هیبریدی — بذر 0  ──────────────────────────\n",
      "   … آموزشِ VQNN، بذر 0 (حدود ۲۲ دقیقه)\n",
      "   … آموزشِ QRes، بذر 0 (حدود ۱۸ دقیقه)\n",
      "   ✔ VQNN vs PM-MLP                   بذر  0:  Q=0.5586  C=0.5624  Δ=-0.0038\n",
      "   ✔ QRes vs VQNN                     بذر  0:  Q=0.5612  C=0.5586  Δ=+0.0026\n",
      "   ✔ QFE-kNN vs CSF-kNN               بذر  0:  Q=0.5782  C=0.6616  Δ=-0.0834\n",
      "     (مدل‌های هیبریدی، بذر 0: 38.5 دقیقه)\n",
      "\n",
      "──────────────────────────  مدل‌های هیبریدی — بذر 1  ──────────────────────────\n",
      "   … آموزشِ VQNN، بذر 1 (حدود ۲۲ دقیقه)\n",
      "   … آموزشِ QRes، بذر 1 (حدود ۱۸ دقیقه)\n",
      "   ✔ VQNN vs PM-MLP                   بذر  1:  Q=0.5593  C=0.5628  Δ=-0.0035\n",
      "   ✔ QRes vs VQNN                     بذر  1:  Q=0.5615  C=0.5593  Δ=+0.0022\n",
      "   ✔ QFE-kNN vs CSF-kNN               بذر  1:  Q=0.5743  C=0.6633  Δ=-0.0890\n",
      "     (مدل‌های هیبریدی، بذر 1: 38.0 دقیقه)\n",
      "\n",
      "──────────────────────────  مدل‌های هیبریدی — بذر 2  ──────────────────────────\n",
      "   … آموزشِ VQNN، بذر 2 (حدود ۲۲ دقیقه)\n",
      "   … آموزشِ QRes، بذر 2 (حدود ۱۸ دقیقه)\n",
      "   ✔ VQNN vs PM-MLP                   بذر  2:  Q=0.5607  C=0.5632  Δ=-0.0025\n",
      "   ✔ QRes vs VQNN                     بذر  2:  Q=0.5597  C=0.5607  Δ=-0.0010\n",
      "   ✔ QFE-kNN vs CSF-kNN               بذر  2:  Q=0.5799  C=0.6629  Δ=-0.0830\n",
      "     (مدل‌های هیبریدی، بذر 2: 37.9 دقیقه)\n",
      "\n",
      "──────────────────────────  مدل‌های هیبریدی — بذر 3  ──────────────────────────\n",
      "   … آموزشِ VQNN، بذر 3 (حدود ۲۲ دقیقه)\n",
      "   … آموزشِ QRes، بذر 3 (حدود ۱۸ دقیقه)\n",
      "   ✔ VQNN vs PM-MLP                   بذر  3:  Q=0.5615  C=0.5617  Δ=-0.0002\n",
      "   ✔ QRes vs VQNN                     بذر  3:  Q=0.5612  C=0.5615  Δ=-0.0003\n",
      "   ✔ QFE-kNN vs CSF-kNN               بذر  3:  Q=0.5718  C=0.6630  Δ=-0.0913\n",
      "     (مدل‌های هیبریدی، بذر 3: 37.9 دقیقه)\n",
      "\n",
      "زمانِ کلِ این اجرا: 154.4 دقیقه\n",
      "\n",
      "======================================================================================================================\n",
      "نتیجهٔ پنج‌بذری — میانگین ± انحراف معیار روی بذرها، F_max میانگینِ سه جنبه، مجموعهٔ آزمون\n",
      "======================================================================================================================\n",
      "  مقایسه                            n                Q                C                  Δ   علامت     p(t)    p(W)     d_z\n",
      "----------------------------------------------------------------------------------------------------------------------\n",
      "  VQNN vs PM-MLP                    5    0.5603±0.0013    0.5626±0.0006    -0.0023±0.0015   5−/0+   0.0303  0.0625   -1.47\n",
      "  QRes vs VQNN                      5    0.5611±0.0008    0.5603±0.0013    +0.0008±0.0016   2−/3+   0.3528  0.6250   +0.47\n",
      "  CQKR vs RBF-KR (ridge 1.0)        5    0.5927±0.0137    0.6297±0.0019    -0.0370±0.0136   5−/0+   0.0037  0.0625   -2.71\n",
      "  CQKR vs RBF-KR (tuned ridge)      5    0.5927±0.0137    0.6329±0.0027    -0.0402±0.0139   5−/0+   0.0029  0.0625   -2.89\n",
      "  QK-NN vs RBF-kNN-10               5    0.6066±0.0048    0.6252±0.0011    -0.0186±0.0043   5−/0+   0.0006  0.0625   -4.35\n",
      "  RUQK-NN vs RBF-kNN-480            2    0.6116±0.0004    0.6719±0.0001    -0.0603±0.0005   2−/0+   0.0037  0.5000 -122.72\n",
      "  RUQK-NN vs Cos-kNN                2    0.6116±0.0004    0.6663±0.0000    -0.0547±0.0004   2−/0+   0.0035  0.5000 -127.30\n",
      "  QFE-kNN vs CSF-kNN                5    0.5776±0.0048    0.6624±0.0010    -0.0847±0.0056   5−/0+   0.0000  0.0625  -15.17\n",
      "----------------------------------------------------------------------------------------------------------------------\n",
      "  خودآزماییِ بازتولید (بذرِ ۴۲ در برابرِ مقادیرِ مرجع):\n",
      "    VQNN vs PM-MLP                   بیشینهٔ اختلاف = 0.00004  ✅\n",
      "    QRes vs VQNN                     بیشینهٔ اختلاف = 0.00004  ✅\n",
      "    CQKR vs RBF-KR (ridge 1.0)       بیشینهٔ اختلاف = 0.00002  ✅\n",
      "    QK-NN vs RBF-kNN-10              بیشینهٔ اختلاف = 0.00004  ✅\n",
      "    RUQK-NN vs RBF-kNN-480           بیشینهٔ اختلاف = 0.00003  ✅\n",
      "    RUQK-NN vs Cos-kNN               بیشینهٔ اختلاف = 0.00002  ✅\n",
      "    QFE-kNN vs CSF-kNN               بیشینهٔ اختلاف = 0.00004  ✅\n",
      "\n",
      "  ردیف‌های جدول (Markdown):\n",
      "  | Comparison | n | Quantum | Control | Δ (mean ± s.d.) | Negative seeds | p (t) | p (Wilcoxon) | d_z |\n",
      "  | VQNN vs PM-MLP | 5 | 0.5603 ± 0.0013 | 0.5626 ± 0.0006 | -0.0023 ± 0.0015 | 5/5 | 0.0303 | 0.0625 | -1.47 |\n",
      "  | QRes vs VQNN | 5 | 0.5611 ± 0.0008 | 0.5603 ± 0.0013 | +0.0008 ± 0.0016 | 2/5 | 0.3528 | 0.6250 | +0.47 |\n",
      "  | CQKR vs RBF-KR (ridge 1.0) | 5 | 0.5927 ± 0.0137 | 0.6297 ± 0.0019 | -0.0370 ± 0.0136 | 5/5 | 0.0037 | 0.0625 | -2.71 |\n",
      "  | CQKR vs RBF-KR (tuned ridge) | 5 | 0.5927 ± 0.0137 | 0.6329 ± 0.0027 | -0.0402 ± 0.0139 | 5/5 | 0.0029 | 0.0625 | -2.89 |\n",
      "  | QK-NN vs RBF-kNN-10 | 5 | 0.6066 ± 0.0048 | 0.6252 ± 0.0011 | -0.0186 ± 0.0043 | 5/5 | 0.0006 | 0.0625 | -4.35 |\n",
      "  | RUQK-NN vs RBF-kNN-480 | 2 | 0.6116 ± 0.0004 | 0.6719 ± 0.0001 | -0.0603 ± 0.0005 | 2/2 | 0.0037 | 0.5000 | -122.72 |\n",
      "  | RUQK-NN vs Cos-kNN | 2 | 0.6116 ± 0.0004 | 0.6663 ± 0.0000 | -0.0547 ± 0.0004 | 2/2 | 0.0035 | 0.5000 | -127.30 |\n",
      "  | QFE-kNN vs CSF-kNN | 5 | 0.5776 ± 0.0048 | 0.6624 ± 0.0010 | -0.0847 ± 0.0056 | 5/5 | 0.0000 | 0.0625 | -15.17 |\n",
      "======================================================================================================================\n",
      "خوانش:\n",
      "  • n=5 ⇒ کمترین p ویلکاکسونِ دوطرفه 0.0625 است؛ t-test معیارِ اصلی است.\n",
      "  • اگر همهٔ Δها منفی و p(t) < 0.05 بود، نتیجهٔ منفیِ آن مقایسه دیگر تک‌بذری نیست.\n",
      "  • برای P2 (QRes منهای VQNN) انتظار داریم Δ حولِ صفر باشد: یعنی آموزشِ مدار بی‌اثر است.\n",
      "  • همهٔ اعدادِ خام در /kaggle/working/seed5_standalone.json\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Five-seed repetition of the six standalone matched comparisons, with\n",
    "# checkpointing and a reproduction check against seed 42.\n",
    "# ============================================================================\n",
    "\n",
    "import os, io, json, time, contextlib\n",
    "import numpy as np, torch\n",
    "from scipy import stats\n",
    "\n",
    "SEEDS = [42, 0, 1, 2, 3]\n",
    "RUN_HYBRID = True          # P1, P2, P6 — گران؛ برای آزمودنِ سریعِ بقیه False کنید\n",
    "RUN_TUNED_RIDGE = True     # P3 با ridgeِ تنظیم‌شده روی اعتبارسنجی (به diagnose_ridge نیاز دارد)\n",
    "REUSE_SEED42 = True        # نتایجِ بذرِ ۴۲ را از RESULTS/SCORES موجود بردار، دوباره آموزش نده\n",
    "VERBOSE = False            # True = گزارشِ کاملِ هر اجرا چاپ شود\n",
    "\n",
    "CKPT = (\"/kaggle/working\" if os.path.isdir(\"/kaggle/working\") else \".\") + \"/seed5_standalone.json\"\n",
    "\n",
    "# مقادیرِ مرجعِ بذرِ ۴۲ — برای خودآزمایی\n",
    "PAPER_SEED42 = {\n",
    "    \"P1\": (0.5616, 0.5628), \"P2\": (0.5618, 0.5616), \"P3\": (0.5891, 0.6313),\n",
    "    \"P4\": (0.6059, 0.6262), \"P5_rbf\": (0.6113, 0.6719), \"P5_cos\": (0.6113, 0.6663),\n",
    "    \"P6\": (0.5840, 0.6610),\n",
    "}\n",
    "PAIR_LABEL = {\n",
    "    \"P1\": \"VQNN vs PM-MLP\", \"P2\": \"QRes vs VQNN\", \"P3\": \"CQKR vs RBF-KR (ridge 1.0)\",\n",
    "    \"P3_tuned\": \"CQKR vs RBF-KR (tuned ridge)\", \"P4\": \"QK-NN vs RBF-kNN-10\",\n",
    "    \"P5_rbf\": \"RUQK-NN vs RBF-kNN-480\", \"P5_cos\": \"RUQK-NN vs Cos-kNN\",\n",
    "    \"P6\": \"QFE-kNN vs CSF-kNN\",\n",
    "}\n",
    "\n",
    "# ------------------------------------------------------------ پیش‌شرط‌ها\n",
    "_need = [\"run_hybrid\", \"run_cqkr\", \"run_ruqk\", \"CQKR_CFG\", \"RUQK_CFG\", \"QKNN_CFG\",\n",
    "         \"_build_states\", \"fidelity_kernel\", \"rbf_kernel\", \"weighted_knn_from_similarity\",\n",
    "         \"extract_block\", \"cosine_knn_scores\", \"protein_centric\", \"ASPECTS\",\n",
    "         \"ASPECT_COLS\", \"IC\", \"RESULTS\", \"SCORES\", \"_dev\", \"K_KNN\"]\n",
    "_miss = [n for n in _need if n not in globals()]\n",
    "assert not _miss, f\"اول سلول‌های قبلی را اجرا کنید؛ تعریف‌نشده: {_miss}\"\n",
    "if RUN_TUNED_RIDGE and \"diagnose_ridge\" not in globals():\n",
    "    print(\"⚠️ diagnose_ridge تعریف نشده — P3 با ridge تنظیم‌شده رد می‌شود.\")\n",
    "    RUN_TUNED_RIDGE = False\n",
    "\n",
    "_YTE = np.asarray(Yte_np, dtype=np.float32)\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------ ابزارها\n",
    "def aspect_fmax(scores):\n",
    "    \"\"\"F_max هر جنبه و میانگینِ آن‌ها — برای همهٔ بازوها یکسان.\"\"\"\n",
    "    out = {a: float(protein_centric(_YTE, scores, IC, cols=ASPECT_COLS[a])[\"fmax\"])\n",
    "           for a in ASPECTS if len(ASPECT_COLS[a])}\n",
    "    out[\"mean\"] = float(np.mean(list(out.values())))\n",
    "    return out\n",
    "\n",
    "\n",
    "@contextlib.contextmanager\n",
    "def quiet():\n",
    "    if VERBOSE:\n",
    "        yield\n",
    "    else:\n",
    "        with contextlib.redirect_stdout(io.StringIO()):\n",
    "            yield\n",
    "\n",
    "\n",
    "def load_ckpt():\n",
    "    if os.path.exists(CKPT):\n",
    "        with open(CKPT, encoding=\"utf-8\") as f:\n",
    "            return json.load(f)\n",
    "    return {}\n",
    "\n",
    "\n",
    "def save_ckpt(db):\n",
    "    tmp = CKPT + \".tmp\"\n",
    "    with open(tmp, \"w\", encoding=\"utf-8\") as f:\n",
    "        json.dump(db, f, ensure_ascii=False, indent=1)\n",
    "    os.replace(tmp, CKPT)                                  # نوشتنِ اتمی: قطعِ برق فایل را خراب نمی‌کند\n",
    "\n",
    "\n",
    "def put(db, pair, seed, q, c, extra=None):\n",
    "    db.setdefault(pair, {})[str(seed)] = dict(q=q, c=c, **(extra or {}))\n",
    "    save_ckpt(db)\n",
    "    print(f\"   ✔ {PAIR_LABEL[pair]:<32} بذر {seed:>2}:  Q={q['mean']:.4f}  \"\n",
    "          f\"C={c['mean']:.4f}  Δ={q['mean'] - c['mean']:+.4f}\")\n",
    "\n",
    "\n",
    "def done(db, pair, seed):\n",
    "    return str(seed) in db.get(pair, {})\n",
    "\n",
    "\n",
    "def free():\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.empty_cache()\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------ بازوها\n",
    "def run_kernel_arms(seed, db):\n",
    "    \"\"\"P3، P3_tuned و P4 — هر سه به یک دروازهٔ صفرِ مشترک برای این بذر تکیه دارند.\"\"\"\n",
    "    need = [p for p in (\"P3\", \"P4\") if not done(db, p, seed)]\n",
    "    if RUN_TUNED_RIDGE and not done(db, \"P3_tuned\", seed):\n",
    "        need.append(\"P3_tuned\")\n",
    "    if not need:\n",
    "        return\n",
    "    t0 = time.time()\n",
    "    tq, tc = f\"CQKR [بذر {seed}]\", f\"RBF-KR [بذر {seed}]\"\n",
    "    with quiet():\n",
    "        res = run_cqkr(cfg=dict(seed=seed), tag_q=tq, tag_c=tc)\n",
    "    if not res.get(\"passed\"):\n",
    "        print(f\"   ✘ بذر {seed}: دروازهٔ صفر هیچ bandwidthی را نپذیرفت — P3 و P4 برای این بذر ثبت نمی‌شوند.\")\n",
    "        for p in need:\n",
    "            db.setdefault(p, {})[str(seed)] = {\"gate_rejected\": True}\n",
    "        save_ckpt(db)\n",
    "        return\n",
    "    bw = float(res[\"bandwidth\"])\n",
    "    if \"P3\" in need:\n",
    "        put(db, \"P3\", seed, aspect_fmax(SCORES[tq]), aspect_fmax(SCORES[tc]), {\"bandwidth\": bw})\n",
    "\n",
    "    if \"P3_tuned\" in need:\n",
    "        with quiet():\n",
    "            r = diagnose_ridge(cfg=dict(seed=seed), gamma=bw, register=False)\n",
    "        tu = r[\"results\"][\"tuned\"]\n",
    "        db.setdefault(\"P3_tuned\", {})[str(seed)] = dict(\n",
    "            q={\"mean\": tu[\"fmax_q\"]}, c={\"mean\": tu[\"fmax_c\"]},\n",
    "            ridge_q=tu[\"ridge_q\"], ridge_c=tu[\"ridge_c\"], bandwidth=bw)\n",
    "        save_ckpt(db)\n",
    "        print(f\"   ✔ {PAIR_LABEL['P3_tuned']:<32} بذر {seed:>2}:  Q={tu['fmax_q']:.4f}  \"\n",
    "              f\"C={tu['fmax_c']:.4f}  Δ={tu['fmax_q'] - tu['fmax_c']:+.4f}\")\n",
    "\n",
    "    if \"P4\" in need:\n",
    "        cfg = dict(QKNN_CFG, seed=seed, bandwidth=bw)\n",
    "        with quiet():\n",
    "            Ztr_s, Zte_s, Ptr_s, Pte_s = _build_states(cfg)\n",
    "        Ytr_dev = torch.as_tensor(Ytr.numpy(), device=_dev)\n",
    "        K = fidelity_kernel(Pte_s, Ptr_s)\n",
    "        sc_q = weighted_knn_from_similarity(K, Ytr_dev, K_KNN)\n",
    "        del K, Ptr_s, Pte_s; free()\n",
    "        rng = np.random.default_rng(seed)\n",
    "        sub = rng.choice(len(Ztr_s), size=min(1024, len(Ztr_s)), replace=False)\n",
    "        Atr = torch.as_tensor(Ztr_s, device=_dev); Ate = torch.as_tensor(Zte_s, device=_dev)\n",
    "        As = Atr[sub]\n",
    "        g = 1.0 / (2.0 * (torch.cdist(As, As) ** 2).median().clamp_min(1e-9))\n",
    "        K = rbf_kernel(Ate, Atr, g)\n",
    "        sc_c = weighted_knn_from_similarity(K, Ytr_dev, K_KNN)\n",
    "        del K, Atr, Ate, As; free()\n",
    "        put(db, \"P4\", seed, aspect_fmax(sc_q), aspect_fmax(sc_c), {\"bandwidth\": bw})\n",
    "    print(f\"     (هسته‌ها، بذر {seed}: {time.time() - t0:.0f} ثانیه)\")\n",
    "\n",
    "\n",
    "def run_ruqk_arm(seed, db):\n",
    "    if done(db, \"P5_rbf\", seed) and done(db, \"P5_cos\", seed):\n",
    "        return\n",
    "    t0 = time.time()\n",
    "    with quiet():\n",
    "        r = run_ruqk(cfg=dict(seed=seed))\n",
    "    if not r.get(\"passed\"):\n",
    "        print(f\"   ✘ بذر {seed}: دروازهٔ صفرِ RUQK رد کرد — P5 برای این بذر ثبت نمی‌شود.\")\n",
    "        for p in (\"P5_rbf\", \"P5_cos\"):\n",
    "            db.setdefault(p, {})[str(seed)] = {\"gate_rejected\": True}\n",
    "        save_ckpt(db)\n",
    "        return\n",
    "    # run_ruqk با نام‌های ثابت ثبت می‌کند؛ پیش از اجرای بعدی بلافاصله خوانده می‌شوند\n",
    "    q = aspect_fmax(SCORES[r[\"name_ruqk\"]])\n",
    "    put(db, \"P5_rbf\", seed, q, aspect_fmax(SCORES[r[\"name_rbf480\"]]), {\"bandwidth\": r.get(\"bandwidth\")})\n",
    "    put(db, \"P5_cos\", seed, q, aspect_fmax(SCORES[r[\"name_cos480\"]]), {\"bandwidth\": r.get(\"bandwidth\")})\n",
    "    print(f\"     (RUQK، بذر {seed}: {time.time() - t0:.0f} ثانیه)\")\n",
    "\n",
    "\n",
    "TAG_Q42, TAG_R42, TAG_C42 = \"VQNN\", \"QRes\", \"PM-MLP\"\n",
    "\n",
    "\n",
    "def run_hybrid_arms(seed, db):\n",
    "    if all(done(db, p, seed) for p in (\"P1\", \"P2\", \"P6\")):\n",
    "        return\n",
    "    if seed == 42 and REUSE_SEED42 and all(t in SCORES for t in (TAG_Q42, TAG_R42, TAG_C42)) \\\n",
    "            and \"NAME_QFE\" in globals() and \"NAME_CFE\" in globals() and NAME_QFE in SCORES:\n",
    "        sq, sr, sc = (aspect_fmax(SCORES[t]) for t in (TAG_Q42, TAG_R42, TAG_C42))\n",
    "        put(db, \"P1\", seed, sq, sc, {\"reused\": True})\n",
    "        put(db, \"P2\", seed, sr, sq, {\"reused\": True})\n",
    "        put(db, \"P6\", seed, aspect_fmax(SCORES[NAME_QFE]), aspect_fmax(SCORES[NAME_CFE]), {\"reused\": True})\n",
    "        return\n",
    "\n",
    "    t0 = time.time()\n",
    "    print(f\"   … آموزشِ VQNN، بذر {seed} (حدود ۲۲ دقیقه)\")\n",
    "    with quiet():\n",
    "        mq, sc_q, _ = run_hybrid(f\"VQNN [بذر {seed}]\", use_quantum=True, seed=seed)\n",
    "    print(f\"   … آموزشِ QRes، بذر {seed} (حدود ۱۸ دقیقه)\")\n",
    "    with quiet():\n",
    "        mr, sc_r, _ = run_hybrid(f\"QRes [بذر {seed}]\", use_quantum=True, seed=seed, freeze_quantum=True)\n",
    "    with quiet():\n",
    "        mc, sc_c, _ = run_hybrid(f\"PM-MLP [بذر {seed}]\", use_quantum=False, seed=seed)\n",
    "    fq, fr, fc = aspect_fmax(sc_q), aspect_fmax(sc_r), aspect_fmax(sc_c)\n",
    "    put(db, \"P1\", seed, fq, fc)\n",
    "    put(db, \"P2\", seed, fr, fq)\n",
    "\n",
    "    # P6 از همین مدل‌های این بذر ساخته می‌شود — دقیقاً مانند سلولِ QFE-kNN\n",
    "    Qtr = extract_block(mq, Xtr, \"quantum\"); Qte = extract_block(mq, Xte, \"quantum\")\n",
    "    s_qfe, _ = cosine_knn_scores(Qtr, Qte)\n",
    "    Str = extract_block(mc, Xtr, \"surrogate\"); Ste = extract_block(mc, Xte, \"surrogate\")\n",
    "    s_cfe, _ = cosine_knn_scores(Str, Ste)\n",
    "    put(db, \"P6\", seed, aspect_fmax(s_qfe), aspect_fmax(s_cfe))\n",
    "    del mq, mr, mc, Qtr, Qte, Str, Ste; free()\n",
    "    print(f\"     (مدل‌های هیبریدی، بذر {seed}: {(time.time() - t0) / 60:.1f} دقیقه)\")\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------ آمار\n",
    "def summarise(db):\n",
    "    rows = []\n",
    "    for pair in PAIR_LABEL:\n",
    "        recs = [(int(s), v) for s, v in db.get(pair, {}).items() if not v.get(\"gate_rejected\")]\n",
    "        if not recs:\n",
    "            continue\n",
    "        recs.sort(key=lambda t: SEEDS.index(t[0]) if t[0] in SEEDS else 99)\n",
    "        q = np.array([v[\"q\"][\"mean\"] for _, v in recs])\n",
    "        c = np.array([v[\"c\"][\"mean\"] for _, v in recs])\n",
    "        d = q - c\n",
    "        n = len(d)\n",
    "        row = dict(pair=pair, label=PAIR_LABEL[pair], n=n, seeds=[s for s, _ in recs],\n",
    "                   q_mean=float(q.mean()), q_sd=float(q.std(ddof=1)) if n > 1 else float(\"nan\"),\n",
    "                   c_mean=float(c.mean()), c_sd=float(c.std(ddof=1)) if n > 1 else float(\"nan\"),\n",
    "                   d_mean=float(d.mean()), d_sd=float(d.std(ddof=1)) if n > 1 else float(\"nan\"),\n",
    "                   n_neg=int((d < 0).sum()), n_pos=int((d > 0).sum()), deltas=d.tolist())\n",
    "        if n >= 2 and np.std(d) > 0:\n",
    "            t, p_t = stats.ttest_1samp(d, 0.0)\n",
    "            row.update(t=float(t), p_t=float(p_t), d_z=float(d.mean() / d.std(ddof=1)))\n",
    "        else:\n",
    "            row.update(t=float(\"nan\"), p_t=float(\"nan\"), d_z=float(\"nan\"))\n",
    "        try:\n",
    "            row[\"p_w\"] = float(stats.wilcoxon(d).pvalue) if n >= 2 and np.any(d != 0) else float(\"nan\")\n",
    "        except ValueError:\n",
    "            row[\"p_w\"] = float(\"nan\")\n",
    "        # بذرِ ۴۲ در برابر عددِ منتشرشده\n",
    "        ref = PAPER_SEED42.get(pair)\n",
    "        r42 = db.get(pair, {}).get(\"42\")\n",
    "        if ref and r42 and not r42.get(\"gate_rejected\"):\n",
    "            gap = max(abs(r42[\"q\"][\"mean\"] - ref[0]), abs(r42[\"c\"][\"mean\"] - ref[1]))\n",
    "            row[\"repro_gap\"] = float(gap)\n",
    "        rows.append(row)\n",
    "    return rows\n",
    "\n",
    "\n",
    "def report(rows):\n",
    "    print(\"\\n\" + \"=\" * 118)\n",
    "    print(\"نتیجهٔ پنج‌بذری — میانگین ± انحراف معیار روی بذرها، F_max میانگینِ سه جنبه، مجموعهٔ آزمون\")\n",
    "    print(\"=\" * 118)\n",
    "    print(f\"  {'مقایسه':<32}{'n':>3}{'Q':>17}{'C':>17}{'Δ':>19}{'علامت':>8}{'p(t)':>9}{'p(W)':>8}{'d_z':>8}\")\n",
    "    print(\"-\" * 118)\n",
    "    for r in rows:\n",
    "        sign = f\"{r['n_neg']}−/{r['n_pos']}+\"\n",
    "        print(f\"  {r['label']:<32}{r['n']:>3}\"\n",
    "              f\"{r['q_mean']:>10.4f}±{r['q_sd']:.4f}{r['c_mean']:>10.4f}±{r['c_sd']:.4f}\"\n",
    "              f\"{r['d_mean']:>+11.4f}±{r['d_sd']:.4f}{sign:>8}\"\n",
    "              f\"{r['p_t']:>9.4f}{r['p_w']:>8.4f}{r['d_z']:>+8.2f}\")\n",
    "    print(\"-\" * 118)\n",
    "    print(\"  خودآزماییِ بازتولید (بذرِ ۴۲ در برابرِ مقادیرِ مرجع):\")\n",
    "    for r in rows:\n",
    "        if \"repro_gap\" in r:\n",
    "            ok = r[\"repro_gap\"] < 5e-4\n",
    "            print(f\"    {r['label']:<32} بیشینهٔ اختلاف = {r['repro_gap']:.5f}  \"\n",
    "                  f\"{'✅' if ok else '❌ نمی‌خواند — پیش از استفاده بررسی کنید'}\")\n",
    "    print(\"\\n  ردیف‌های جدول (Markdown):\")\n",
    "    print(\"  | Comparison | n | Quantum | Control | Δ (mean ± s.d.) | Negative seeds | p (t) | p (Wilcoxon) | d_z |\")\n",
    "    for r in rows:\n",
    "        print(f\"  | {r['label']} | {r['n']} | {r['q_mean']:.4f} ± {r['q_sd']:.4f} | \"\n",
    "              f\"{r['c_mean']:.4f} ± {r['c_sd']:.4f} | {r['d_mean']:+.4f} ± {r['d_sd']:.4f} | \"\n",
    "              f\"{r['n_neg']}/{r['n']} | {r['p_t']:.4f} | {r['p_w']:.4f} | {r['d_z']:+.2f} |\")\n",
    "    print(\"=\" * 118)\n",
    "    print(\"خوانش:\")\n",
    "    print(\"  • n=5 ⇒ کمترین p ویلکاکسونِ دوطرفه 0.0625 است؛ t-test معیارِ اصلی است.\")\n",
    "    print(\"  • اگر همهٔ Δها منفی و p(t) < 0.05 بود، نتیجهٔ منفیِ آن مقایسه دیگر تک‌بذری نیست.\")\n",
    "    print(\"  • برای P2 (QRes منهای VQNN) انتظار داریم Δ حولِ صفر باشد: یعنی آموزشِ مدار بی‌اثر است.\")\n",
    "    print(f\"  • همهٔ اعدادِ خام در {CKPT}\")\n",
    "\n",
    "\n",
    "# ------------------------------------------------------------ اجرا\n",
    "DB = load_ckpt()\n",
    "_already = sum(len(v) for v in DB.values())\n",
    "print(f\"نقطهٔ بازیابی: {CKPT}  ({_already} نتیجهٔ ذخیره‌شده از قبل)\")\n",
    "T_ALL = time.time()\n",
    "# مرحلهٔ ۱ — بازوهای ارزان برای همهٔ بذرها (حدود ۲ دقیقه برای هر بذر)\n",
    "for s in SEEDS:\n",
    "    print(f\"\\n{'─' * 26}  هسته‌ها — بذر {s}  {'─' * 26}\")\n",
    "    run_kernel_arms(s, DB)\n",
    "    run_ruqk_arm(s, DB)\n",
    "# مرحلهٔ ۲ — بازوهای گران. اگر نشست اینجا قطع شد، چهار مقایسهٔ اول کامل‌اند.\n",
    "if RUN_HYBRID:\n",
    "    for s in SEEDS:\n",
    "        print(f\"\\n{'─' * 26}  مدل‌های هیبریدی — بذر {s}  {'─' * 26}\")\n",
    "        run_hybrid_arms(s, DB)\n",
    "print(f\"\\nزمانِ کلِ این اجرا: {(time.time() - T_ALL) / 60:.1f} دقیقه\")\n",
    "\n",
    "SEED5_STANDALONE = summarise(DB)\n",
    "report(SEED5_STANDALONE)\n",
    "with open(CKPT.replace(\".json\", \"_summary.json\"), \"w\", encoding=\"utf-8\") as f:\n",
    "    json.dump(SEED5_STANDALONE, f, ensure_ascii=False, indent=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8992fa5b",
   "metadata": {},
   "source": [
    "## 21. Gate-0 per seed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "ff8db0a3-aeda-4dec-aacd-ef48083788d0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T05:19:12.771971Z",
     "iopub.status.busy": "2026-09-27T05:19:12.771653Z",
     "iopub.status.idle": "2026-09-27T05:20:28.384132Z",
     "shell.execute_reply": "2026-09-27T05:20:28.383388Z",
     "shell.execute_reply.started": "2026-09-27T05:19:12.771946Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "========================================================================================================\n",
      "Gate-0 به تفکیکِ بذر\n",
      "========================================================================================================\n",
      "\n",
      "────────────────────────────────────────  بذر 42  ────────────────────────────────────────\n",
      "\n",
      "  CQKR   (KTA مرجعِ RBF = 0.0984)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05645     57.8   0.0750     0.76        7,844        ✓      ✓        ✗   رد\n",
      "    0.10   0.13276    135.9   0.0839     0.85        1,418        ✓      ✓        ✓   عبور\n",
      "    0.20   0.08541     87.5   0.1056     1.07        3,427        ✓      ✓        ✓   عبور\n",
      "    0.35   0.01163     11.9   0.1169     1.19      184,961        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.60   0.00264      2.7   0.1152     1.17    3,575,234        ✗      ✓        ✓   رد\n",
      "    1.00   0.00190      1.9   0.1141     1.16    6,934,585        ✗      ✓        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.35   F_max کوانتومی = 0.5891   (اجرای پنج‌بذری: 0.5891)  ✅ همان مسیر\n",
      "\n",
      "  RUQK-NN   (KTA مرجعِ RBF = 0.1161)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05258     53.8   0.1067     0.92        9,042        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.10   0.00481      4.9   0.1166     1.00    1,080,136        ✗      ✓        ✓   رد\n",
      "    0.20   0.00116      1.2   0.1160     1.00   18,478,380        ✗      ✓        ✓   رد\n",
      "    0.35   0.00098      1.0   0.1162     1.00   26,259,125        ✗      ✗        ✓   رد\n",
      "    0.60   0.00098      1.0   0.1159     1.00   26,150,081        ✗      ✓        ✓   رد\n",
      "    1.00   0.00097      1.0   0.1160     1.00   26,352,225        ✗      ✗        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.05\n",
      "\n",
      "────────────────────────────────────────  بذر 0  ────────────────────────────────────────\n",
      "\n",
      "  CQKR   (KTA مرجعِ RBF = 0.1247)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05316     54.4   0.1024     0.82        8,846        ✓      ✓        ✓   عبور\n",
      "    0.10   0.12717    130.2   0.1087     0.87        1,545        ✓      ✓        ✓   عبور\n",
      "    0.20   0.08147     83.4   0.1250     1.00        3,766        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.35   0.01144     11.7   0.1236     0.99      191,021        ✓      ✓        ✓   عبور\n",
      "    0.60   0.00266      2.7   0.1192     0.96    3,530,825        ✗      ✓        ✓   رد\n",
      "    1.00   0.00183      1.9   0.1186     0.95    7,501,488        ✗      ✓        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.2   F_max کوانتومی = 0.6168   (اجرای پنج‌بذری: 0.6168)  ✅ همان مسیر\n",
      "\n",
      "  RUQK-NN   (KTA مرجعِ RBF = 0.1720)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05716     58.5   0.0877     0.51        7,651        ✓      ✓        ✗   رد\n",
      "    0.10   0.00479      4.9   0.1186     0.69    1,089,359        ✗      ✓        ✗   رد\n",
      "    0.20   0.00115      1.2   0.1172     0.68   19,033,754        ✗      ✓        ✗   رد\n",
      "    0.35   0.00098      1.0   0.1173     0.68   26,234,448        ✗      ✗        ✗   رد\n",
      "    0.60   0.00097      1.0   0.1173     0.68   26,616,169        ✗      ✗        ✗   رد\n",
      "    1.00   0.00098      1.0   0.1173     0.68   26,258,135        ✗      ✗        ✗   رد\n",
      "  ⇒ رد شد: هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\n",
      "\n",
      "────────────────────────────────────────  بذر 1  ────────────────────────────────────────\n",
      "\n",
      "  CQKR   (KTA مرجعِ RBF = 0.1296)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05191     53.2   0.0705     0.54        9,276        ✓      ✓        ✗   رد\n",
      "    0.10   0.12228    125.2   0.0772     0.60        1,671        ✓      ✓        ✗   رد\n",
      "    0.20   0.07845     80.3   0.0962     0.74        4,061        ✓      ✓        ✗   رد\n",
      "    0.35   0.01059     10.8   0.1148     0.89      223,039        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.60   0.00250      2.6   0.1134     0.88    4,014,638        ✗      ✓        ✓   رد\n",
      "    1.00   0.00184      1.9   0.1133     0.87    7,374,193        ✗      ✓        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.35   F_max کوانتومی = 0.5823   (اجرای پنج‌بذری: 0.5823)  ✅ همان مسیر\n",
      "\n",
      "  RUQK-NN   (KTA مرجعِ RBF = 0.1770)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.06231     63.8   0.1272     0.72        6,439        ✓      ✓        ✗   رد\n",
      "    0.10   0.00586      6.0   0.1192     0.67      728,632        ✗      ✓        ✗   رد\n",
      "    0.20   0.00163      1.7   0.1170     0.66    9,425,211        ✗      ✓        ✗   رد\n",
      "    0.35   0.00098      1.0   0.1169     0.66   25,985,108        ✗      ✓        ✗   رد\n",
      "    0.60   0.00098      1.0   0.1169     0.66   26,175,593        ✗      ✓        ✗   رد\n",
      "    1.00   0.00097      1.0   0.1170     0.66   26,316,984        ✗      ✗        ✗   رد\n",
      "  ⇒ رد شد: هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\n",
      "\n",
      "────────────────────────────────────────  بذر 2  ────────────────────────────────────────\n",
      "\n",
      "  CQKR   (KTA مرجعِ RBF = 0.0889)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05204     53.3   0.0724     0.81        9,232        ✓      ✓        ✓   عبور\n",
      "    0.10   0.12550    128.5   0.0773     0.87        1,587        ✓      ✓        ✓   عبور\n",
      "    0.20   0.08865     90.8   0.0912     1.03        3,180        ✓      ✓        ✓   عبور\n",
      "    0.35   0.01339     13.7   0.1223     1.38      139,348        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.60   0.00313      3.2   0.1183     1.33    2,555,248        ✗      ✓        ✓   رد\n",
      "    1.00   0.00196      2.0   0.1175     1.32    6,524,013        ✗      ✓        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.35   F_max کوانتومی = 0.5879   (اجرای پنج‌بذری: 0.5879)  ✅ همان مسیر\n",
      "\n",
      "  RUQK-NN   (KTA مرجعِ RBF = 0.1666)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05942     60.8   0.1130     0.68        7,080        ✓      ✓        ✗   رد\n",
      "    0.10   0.00648      6.6   0.1159     0.70      596,147        ✗      ✓        ✗   رد\n",
      "    0.20   0.00368      3.8   0.1147     0.69    1,844,440        ✗      ✓        ✗   رد\n",
      "    0.35   0.00211      2.2   0.1148     0.69    5,640,977        ✗      ✓        ✗   رد\n",
      "    0.60   0.00099      1.0   0.1145     0.69   25,450,020        ✗      ✓        ✗   رد\n",
      "    1.00   0.00097      1.0   0.1145     0.69   26,410,430        ✗      ✗        ✗   رد\n",
      "  ⇒ رد شد: هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\n",
      "\n",
      "────────────────────────────────────────  بذر 3  ────────────────────────────────────────\n",
      "\n",
      "  CQKR   (KTA مرجعِ RBF = 0.1258)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05167     52.9   0.0744     0.59        9,363        ✓      ✓        ✗   رد\n",
      "    0.10   0.12298    125.9   0.0852     0.68        1,652        ✓      ✓        ✗   رد\n",
      "    0.20   0.08319     85.2   0.1096     0.87        3,612        ✓      ✓        ✓   عبور\n",
      "    0.35   0.01091     11.2   0.1214     0.96      210,013        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.60   0.00236      2.4   0.1188     0.94    4,475,826        ✗      ✓        ✓   رد\n",
      "    1.00   0.00186      1.9   0.1188     0.94    7,236,145        ✗      ✓        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.35   F_max کوانتومی = 0.5872   (اجرای پنج‌بذری: 0.5872)  ✅ همان مسیر\n",
      "\n",
      "  RUQK-NN   (KTA مرجعِ RBF = 0.1016)\n",
      "       γ       σ_K     σ/کف      KTA  KTA/RBF        shots  σ≥0.010   σ≥کف  KTA≥0.8   نتیجه\n",
      "    0.05   0.05421     55.5   0.0905     0.89        8,506        ✓      ✓        ✓   عبور  ← انتخاب\n",
      "    0.10   0.00482      4.9   0.1182     1.16    1,077,109        ✗      ✓        ✓   رد\n",
      "    0.20   0.00114      1.2   0.1169     1.15   19,231,748        ✗      ✓        ✓   رد\n",
      "    0.35   0.00097      1.0   0.1168     1.15   26,439,042        ✗      ✗        ✓   رد\n",
      "    0.60   0.00097      1.0   0.1169     1.15   26,674,404        ✗      ✗        ✓   رد\n",
      "    1.00   0.00097      1.0   0.1168     1.15   26,310,541        ✗      ✗        ✓   رد\n",
      "  ⇒ γ انتخابی = 0.05\n",
      "\n",
      "========================================================================================================\n",
      "خلاصه\n",
      "========================================================================================================\n",
      "    بذر      CQKR: γ     F_max   RUQK                                                        \n",
      "     42         0.35    0.5891   γ = 0.05                                                    \n",
      "      0         0.20    0.6168   رد — هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید            \n",
      "      1         0.35    0.5823   رد — هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید            \n",
      "      2         0.35    0.5879   رد — هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید            \n",
      "      3         0.35    0.5872   γ = 0.05                                                    \n",
      "\n",
      "  زمان: 76 ثانیه\n",
      "\n",
      "========================================================================================================\n",
      "GATE0_BY_SEED_JSON:\n",
      "{\"cqkr\":{\"42\":{\"passed\":true,\"kta_rbf\":0.09844,\"sweep\":[{\"bw\":0.05,\"std\":0.05645,\"kta\":0.07497,\"shots\":7844,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.13276,\"kta\":0.08386,\"shots\":1418,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.2,\"std\":0.08541,\"kta\":0.10562,\"shots\":3427,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.01163,\"kta\":0.11687,\"shots\":184961,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00264,\"kta\":0.11521,\"shots\":3575234,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.0019,\"kta\":0.11411,\"shots\":6934585,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true}],\"gamma\":0.35,\"fmax_q\":0.5891},\"0\":{\"passed\":true,\"kta_rbf\":0.12467,\"sweep\":[{\"bw\":0.05,\"std\":0.05316,\"kta\":0.10241,\"shots\":8846,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.1,\"std\":0.12717,\"kta\":0.10869,\"shots\":1545,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.2,\"std\":0.08147,\"kta\":0.12501,\"shots\":3766,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.01144,\"kta\":0.12362,\"shots\":191021,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00266,\"kta\":0.11917,\"shots\":3530825,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00183,\"kta\":0.11864,\"shots\":7501488,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true}],\"gamma\":0.2,\"fmax_q\":0.61679},\"1\":{\"passed\":true,\"kta_rbf\":0.12956,\"sweep\":[{\"bw\":0.05,\"std\":0.05191,\"kta\":0.07046,\"shots\":9276,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.12228,\"kta\":0.07724,\"shots\":1671,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.2,\"std\":0.07845,\"kta\":0.09619,\"shots\":4061,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.35,\"std\":0.01059,\"kta\":0.11478,\"shots\":223039,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.0025,\"kta\":0.11339,\"shots\":4014638,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00184,\"kta\":0.11333,\"shots\":7374193,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true}],\"gamma\":0.35,\"fmax_q\":0.58232},\"2\":{\"passed\":true,\"kta_rbf\":0.08887,\"sweep\":[{\"bw\":0.05,\"std\":0.05204,\"kta\":0.07237,\"shots\":9232,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.1,\"std\":0.1255,\"kta\":0.07729,\"shots\":1587,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.2,\"std\":0.08865,\"kta\":0.09121,\"shots\":3180,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.01339,\"kta\":0.12228,\"shots\":139348,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00313,\"kta\":0.11826,\"shots\":2555248,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00196,\"kta\":0.11753,\"shots\":6524013,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true}],\"gamma\":0.35,\"fmax_q\":0.58791},\"3\":{\"passed\":true,\"kta_rbf\":0.12584,\"sweep\":[{\"bw\":0.05,\"std\":0.05167,\"kta\":0.0744,\"shots\":9363,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.12298,\"kta\":0.08517,\"shots\":1652,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.2,\"std\":0.08319,\"kta\":0.10961,\"shots\":3612,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.01091,\"kta\":0.1214,\"shots\":210013,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00236,\"kta\":0.11877,\"shots\":4475826,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00186,\"kta\":0.1188,\"shots\":7236145,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true}],\"gamma\":0.35,\"fmax_q\":0.58724}},\"ruqk\":{\"42\":{\"passed\":true,\"kta_rbf\":0.11609,\"sweep\":[{\"bw\":0.05,\"std\":0.05258,\"kta\":0.10668,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.1,\"std\":0.00481,\"kta\":0.11663,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.2,\"std\":0.00116,\"kta\":0.11604,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.00098,\"kta\":0.11616,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00098,\"kta\":0.11595,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00097,\"kta\":0.11602,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":true}],\"gamma\":0.05},\"0\":{\"passed\":false,\"kta_rbf\":0.17203,\"sweep\":[{\"bw\":0.05,\"std\":0.05716,\"kta\":0.08774,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.00479,\"kta\":0.11862,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.2,\"std\":0.00115,\"kta\":0.11719,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.35,\"std\":0.00098,\"kta\":0.11735,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":false},{\"bw\":0.6,\"std\":0.00097,\"kta\":0.11733,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":false},{\"bw\":1.0,\"std\":0.00098,\"kta\":0.11725,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":false}],\"why\":\"هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\"},\"1\":{\"passed\":false,\"kta_rbf\":0.17702,\"sweep\":[{\"bw\":0.05,\"std\":0.06231,\"kta\":0.12719,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.00586,\"kta\":0.11916,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.2,\"std\":0.00163,\"kta\":0.117,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.35,\"std\":0.00098,\"kta\":0.11691,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.6,\"std\":0.00098,\"kta\":0.11694,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":1.0,\"std\":0.00097,\"kta\":0.11696,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":false}],\"why\":\"هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\"},\"2\":{\"passed\":false,\"kta_rbf\":0.16658,\"sweep\":[{\"bw\":0.05,\"std\":0.05942,\"kta\":0.11297,\"ok\":false,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.1,\"std\":0.00648,\"kta\":0.11591,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.2,\"std\":0.00368,\"kta\":0.11467,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.35,\"std\":0.00211,\"kta\":0.11481,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":0.6,\"std\":0.00099,\"kta\":0.11453,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":false},{\"bw\":1.0,\"std\":0.00097,\"kta\":0.11448,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":false}],\"why\":\"هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\"},\"3\":{\"passed\":true,\"kta_rbf\":0.10159,\"sweep\":[{\"bw\":0.05,\"std\":0.05421,\"kta\":0.09054,\"ok\":true,\"ok_abs\":true,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.1,\"std\":0.00482,\"kta\":0.11817,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.2,\"std\":0.00114,\"kta\":0.11687,\"ok\":false,\"ok_abs\":false,\"ok_flr\":true,\"ok_kta\":true},{\"bw\":0.35,\"std\":0.00097,\"kta\":0.11682,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":true},{\"bw\":0.6,\"std\":0.00097,\"kta\":0.11694,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":true},{\"bw\":1.0,\"std\":0.00097,\"kta\":0.11682,\"ok\":false,\"ok_abs\":false,\"ok_flr\":false,\"ok_kta\":true}],\"gamma\":0.05}}}\n",
      "========================================================================================================\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Gate-0 per seed for CQKR and RUQK-NN.\n",
    "# ============================================================================\n",
    "\n",
    "import io, json, contextlib, time\n",
    "import numpy as np\n",
    "\n",
    "SEEDS = [42, 0, 1, 2, 3]\n",
    "# F_max کوانتومیِ CQKR (ridge = ۱٫۰) در اجرای پنج‌بذری — برای خودآزمایی\n",
    "SEED5_CQKR_Q = {42: 0.5891, 0: 0.6168, 1: 0.5823, 2: 0.5879, 3: 0.5872}\n",
    "\n",
    "_miss = [n for n in (\"run_cqkr\", \"run_ruqk\", \"CQKR_CFG\", \"RUQK_CFG\", \"record\") if n not in globals()]\n",
    "if _miss:\n",
    "    raise RuntimeError(f\"تعریف‌نشده: {_miss} — سلول‌های داده، معیارها، خط‌پایه، CQKR و RUQK-NN باید پیش از این سلول اجرا شوند.\")\n",
    "\n",
    "FLOOR = 2.0 ** -CQKR_CFG[\"n_qubits\"]\n",
    "MIN_STD = CQKR_CFG[\"gate0_min_std\"]\n",
    "MIN_RATIO = CQKR_CFG[\"gate0_min_kta_ratio\"]\n",
    "\n",
    "\n",
    "def _quiet(fn, **kw):\n",
    "    with contextlib.redirect_stdout(io.StringIO()):\n",
    "        return fn(**kw)\n",
    "\n",
    "\n",
    "def _table(title, res):\n",
    "    kta_c = res.get(\"kta_c\")\n",
    "    print(f\"\\n  {title}   (KTA مرجعِ RBF = {kta_c:.4f})\")\n",
    "    print(f\"  {'γ':>6}{'σ_K':>10}{'σ/کف':>9}{'KTA':>9}{'KTA/RBF':>9}{'shots':>13}\"\n",
    "          f\"{'σ≥0.010':>9}{'σ≥کف':>7}{'KTA≥0.8':>9}   نتیجه\")\n",
    "    for r in res[\"sweep\"]:\n",
    "        shots = r.get(\"shots\", int((5.0 / max(r[\"std\"], 1e-9)) ** 2))\n",
    "        flag = lambda b: \"✓\" if b else \"✗\"\n",
    "        sel = \"  ← انتخاب\" if res.get(\"passed\") and abs(r[\"bw\"] - res.get(\"bandwidth\", -1)) < 1e-12 else \"\"\n",
    "        print(f\"  {r['bw']:>6.2f}{r['std']:>10.5f}{r['std'] / FLOOR:>9.1f}{r['kta']:>9.4f}\"\n",
    "              f\"{r['kta'] / max(abs(kta_c), 1e-12):>9.2f}{shots:>13,}\"\n",
    "              f\"{flag(r['ok_abs']):>9}{flag(r['ok_flr']):>7}{flag(r['ok_kta']):>9}\"\n",
    "              f\"   {'عبور' if r['ok'] else 'رد'}{sel}\")\n",
    "\n",
    "\n",
    "def _why(res):\n",
    "    \"\"\"علتِ رد: کدام شرط در هیچ γای برقرار نشد، یا چه ترکیبی کم آمد.\"\"\"\n",
    "    rows = res[\"sweep\"]\n",
    "    parts = []\n",
    "    if not any(r[\"ok_abs\"] and r[\"ok_flr\"] for r in rows):\n",
    "        parts.append(\"پراکندگی در هیچ γای کافی نبود (تمرکز)\")\n",
    "    if not any(r[\"ok_kta\"] for r in rows):\n",
    "        parts.append(\"هم‌ترازی در هیچ γای به ۰٫۸ برابرِ RBF نرسید\")\n",
    "    if not parts:\n",
    "        parts.append(\"γهای با پراکندگیِ کافی هم‌ترازیِ کم داشتند و γهای هم‌تراز متمرکز بودند\")\n",
    "    return \"؛ \".join(parts)\n",
    "\n",
    "\n",
    "OUT = {\"cqkr\": {}, \"ruqk\": {}}\n",
    "t0 = time.time()\n",
    "print(\"=\" * 104)\n",
    "print(\"Gate-0 به تفکیکِ بذر\")\n",
    "print(\"=\" * 104)\n",
    "\n",
    "for s in SEEDS:\n",
    "    print(f\"\\n{'─' * 40}  بذر {s}  {'─' * 40}\")\n",
    "    rc = _quiet(run_cqkr, cfg=dict(seed=s), tag_q=f\"_gate0 CQKR s{s}\", tag_c=f\"_gate0 RBF s{s}\")\n",
    "    _table(\"CQKR\", rc)\n",
    "    rec = dict(passed=bool(rc.get(\"passed\")), kta_rbf=rc.get(\"kta_c\"), sweep=rc[\"sweep\"])\n",
    "    if rc.get(\"passed\"):\n",
    "        rec[\"gamma\"] = rc[\"bandwidth\"]\n",
    "        q = rc[\"rep_q\"].get(\"fmax_aspect_mean\")\n",
    "        rec[\"fmax_q\"] = q\n",
    "        ref = SEED5_CQKR_Q.get(s)\n",
    "        ok = ref is not None and q is not None and abs(q - ref) < 5e-4\n",
    "        print(f\"  ⇒ γ انتخابی = {rc['bandwidth']}   F_max کوانتومی = {q:.4f}   \"\n",
    "              f\"(اجرای پنج‌بذری: {ref})  {'✅ همان مسیر' if ok else '❌ متفاوت با اجرای پنج‌بذری'}\")\n",
    "    else:\n",
    "        print(f\"  ⇒ رد شد: {_why(rc)}\")\n",
    "    OUT[\"cqkr\"][s] = rec\n",
    "\n",
    "    rr = _quiet(run_ruqk, cfg=dict(seed=s))\n",
    "    _table(\"RUQK-NN\", rr)\n",
    "    rec = dict(passed=bool(rr.get(\"passed\")), kta_rbf=rr.get(\"kta_c\"), sweep=rr[\"sweep\"])\n",
    "    if rr.get(\"passed\"):\n",
    "        rec[\"gamma\"] = rr[\"bandwidth\"]\n",
    "        print(f\"  ⇒ γ انتخابی = {rr['bandwidth']}\")\n",
    "    else:\n",
    "        rec[\"why\"] = _why(rr)\n",
    "        print(f\"  ⇒ رد شد: {rec['why']}\")\n",
    "    OUT[\"ruqk\"][s] = rec\n",
    "\n",
    "# ------------------------------------------------------------ خلاصه\n",
    "print(\"\\n\" + \"=\" * 104)\n",
    "print(\"خلاصه\")\n",
    "print(\"=\" * 104)\n",
    "print(f\"  {'بذر':>5}   {'CQKR: γ':>10}   {'F_max':>7}   {'RUQK':<60}\")\n",
    "for s in SEEDS:\n",
    "    c, r = OUT[\"cqkr\"][s], OUT[\"ruqk\"][s]\n",
    "    cg = f\"{c['gamma']:.2f}\" if c[\"passed\"] else \"رد\"\n",
    "    cf = f\"{c.get('fmax_q', float('nan')):.4f}\" if c[\"passed\"] else \"—\"\n",
    "    rg = f\"γ = {r['gamma']:.2f}\" if r[\"passed\"] else f\"رد — {r['why']}\"\n",
    "    print(f\"  {s:>5}   {cg:>10}   {cf:>7}   {rg:<60}\")\n",
    "print(f\"\\n  زمان: {time.time() - t0:.0f} ثانیه\")\n",
    "\n",
    "# همهٔ اعداد در خودِ خروجی — تا بدونِ فایل هم قابلِ بازیابی باشد\n",
    "print(\"\\n\" + \"=\" * 104)\n",
    "print(\"GATE0_BY_SEED_JSON:\")\n",
    "def _r(o):\n",
    "    if isinstance(o, float): return round(o, 5)\n",
    "    if isinstance(o, dict):  return {k: _r(v) for k, v in o.items() if k != \"mean\"}\n",
    "    if isinstance(o, list):  return [_r(v) for v in o]\n",
    "    return o\n",
    "print(json.dumps(_r(OUT), ensure_ascii=False, default=float, separators=(\",\", \":\")))\n",
    "print(\"=\" * 104)\n",
    "try:\n",
    "    with open(\"/kaggle/working/gate0_by_seed.json\", \"w\", encoding=\"utf-8\") as f:\n",
    "        json.dump(OUT, f, ensure_ascii=False, indent=1, default=float)\n",
    "except OSError:\n",
    "    pass\n",
    "GATE0_BY_SEED = OUT\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "24d5f6f7",
   "metadata": {},
   "source": [
    "## 22. Finite-shot simulation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "b23eb192-2a53-4c98-96dc-e71ff8abe299",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-09-27T15:49:00.428842Z",
     "iopub.status.busy": "2026-09-27T15:49:00.428195Z",
     "iopub.status.idle": "2026-09-27T15:51:52.288319Z",
     "shell.execute_reply": "2026-09-27T15:51:52.287638Z",
     "shell.execute_reply.started": "2026-09-27T15:49:00.428818Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================================================================================================\n",
      "کنترل‌های کلاسیک (بی‌نویز):\n",
      "  Cos-kNN      0.6663   (مرجع: 0.6663)  ✅\n",
      "  RBF-kNN-10   0.6262   (مرجع: 0.6262)  ✅\n",
      "  RBF-KR       0.6313   (مرجع: 0.6313)  ✅\n",
      "\n",
      "============================================================================================================\n",
      "γ = 0.35   (بودجهٔ ۵σِ Gate-0: 184,961 shot)\n",
      "============================================================================================================\n",
      "  M=       100  تکرار 1/3:  QK-NN=0.6040  QCF=0.6698 (α=0.75)  CG-QCF=0.6698  CQKR=0.5801\n",
      "  M=       100  تکرار 2/3:  QK-NN=0.6039  QCF=0.6699 (α=0.75)  CG-QCF=0.6692  CQKR=0.5782\n",
      "  M=       100  تکرار 3/3:  QK-NN=0.6042  QCF=0.6693 (α=0.80)  CG-QCF=0.6692  CQKR=0.5792\n",
      "  M=     1,000  تکرار 1/3:  QK-NN=0.6063  QCF=0.6700 (α=0.75)  CG-QCF=0.6697  CQKR=0.5873\n",
      "  M=     1,000  تکرار 2/3:  QK-NN=0.6046  QCF=0.6703 (α=0.75)  CG-QCF=0.6699  CQKR=0.5879\n",
      "  M=     1,000  تکرار 3/3:  QK-NN=0.6061  QCF=0.6702 (α=0.75)  CG-QCF=0.6697  CQKR=0.5883\n",
      "  M=    10,000  تکرار 1/3:  QK-NN=0.6056  QCF=0.6699 (α=0.80)  CG-QCF=0.6697  CQKR=0.5893\n",
      "  M=    10,000  تکرار 2/3:  QK-NN=0.6052  QCF=0.6697 (α=0.80)  CG-QCF=0.6696  CQKR=0.5892\n",
      "  M=    10,000  تکرار 3/3:  QK-NN=0.6058  QCF=0.6698 (α=0.80)  CG-QCF=0.6696  CQKR=0.5888\n",
      "  M=   100,000  تکرار 1/3:  QK-NN=0.6062  QCF=0.6699 (α=0.80)  CG-QCF=0.6696  CQKR=0.5891\n",
      "  M=   100,000  تکرار 2/3:  QK-NN=0.6058  QCF=0.6702 (α=0.75)  CG-QCF=0.6699  CQKR=0.5892\n",
      "  M=   100,000  تکرار 3/3:  QK-NN=0.6059  QCF=0.6702 (α=0.75)  CG-QCF=0.6698  CQKR=0.5893\n",
      "  M= 1,000,000  تکرار 1/3:  QK-NN=0.6061  QCF=0.6700 (α=0.80)  CG-QCF=0.6698  CQKR=0.5891\n",
      "  M= 1,000,000  تکرار 2/3:  QK-NN=0.6056  QCF=0.6699 (α=0.80)  CG-QCF=0.6698  CQKR=0.5890\n",
      "  M= 1,000,000  تکرار 3/3:  QK-NN=0.6057  QCF=0.6699 (α=0.80)  CG-QCF=0.6696  CQKR=0.5891\n",
      "  M=         ∞  تکرار 1/1:  QK-NN=0.6059  QCF=0.6699 (α=0.80)  CG-QCF=0.6696  CQKR=0.5891\n",
      "\n",
      "============================================================================================================\n",
      "γ = 0.2   (بودجهٔ ۵σِ Gate-0: 3,427 shot)\n",
      "============================================================================================================\n",
      "  M=       100  تکرار 1/3:  QK-NN=0.6159  QCF=0.6706 (α=0.75)  CG-QCF=0.6695  CQKR=0.2598\n",
      "  M=       100  تکرار 2/3:  QK-NN=0.6154  QCF=0.6700 (α=0.80)  CG-QCF=0.6701  CQKR=0.3085\n",
      "  M=       100  تکرار 3/3:  QK-NN=0.6131  QCF=0.6708 (α=0.75)  CG-QCF=0.6700  CQKR=0.3184\n",
      "  M=     1,000  تکرار 1/3:  QK-NN=0.6158  QCF=0.6709 (α=0.75)  CG-QCF=0.6695  CQKR=0.2501\n",
      "  M=     1,000  تکرار 2/3:  QK-NN=0.6160  QCF=0.6710 (α=0.75)  CG-QCF=0.6701  CQKR=0.3128\n",
      "  M=     1,000  تکرار 3/3:  QK-NN=0.6152  QCF=0.6706 (α=0.75)  CG-QCF=0.6695  CQKR=0.3313\n",
      "  M=    10,000  تکرار 1/3:  QK-NN=0.6157  QCF=0.6706 (α=0.75)  CG-QCF=0.6696  CQKR=0.4148\n",
      "  M=    10,000  تکرار 2/3:  QK-NN=0.6157  QCF=0.6707 (α=0.75)  CG-QCF=0.6693  CQKR=0.4275\n",
      "  M=    10,000  تکرار 3/3:  QK-NN=0.6163  QCF=0.6709 (α=0.75)  CG-QCF=0.6698  CQKR=0.3208\n",
      "  M=   100,000  تکرار 1/3:  QK-NN=0.6159  QCF=0.6705 (α=0.75)  CG-QCF=0.6694  CQKR=0.6174\n",
      "  M=   100,000  تکرار 2/3:  QK-NN=0.6161  QCF=0.6698 (α=0.80)  CG-QCF=0.6695  CQKR=0.6174\n",
      "  M=   100,000  تکرار 3/3:  QK-NN=0.6161  QCF=0.6697 (α=0.80)  CG-QCF=0.6696  CQKR=0.6171\n",
      "  M= 1,000,000  تکرار 1/3:  QK-NN=0.6158  QCF=0.6704 (α=0.75)  CG-QCF=0.6695  CQKR=0.6178\n",
      "  M= 1,000,000  تکرار 2/3:  QK-NN=0.6161  QCF=0.6705 (α=0.75)  CG-QCF=0.6696  CQKR=0.6177\n",
      "  M= 1,000,000  تکرار 3/3:  QK-NN=0.6159  QCF=0.6698 (α=0.80)  CG-QCF=0.6697  CQKR=0.6178\n",
      "  M=         ∞  تکرار 1/1:  QK-NN=0.6160  QCF=0.6698 (α=0.80)  CG-QCF=0.6697  CQKR=0.6178\n",
      "\n",
      "======================================================================================================================\n",
      "نتیجه — میانگین ± s.d. روی نمونه‌گیری‌های نویز؛ Δ نسبت به کنترلِ کلاسیکِ بی‌نویز\n",
      "======================================================================================================================\n",
      "      γ           M |           QK-NN  Δ vs RBF10 |             QCF  Δ vs Cos |          CG-QCF  Δ vs Cos |            CQKR   Δ vs KR\n",
      "   0.35         100 | 0.6040±0.0002     -0.0221 | 0.6696±0.0003   +0.0033 | 0.6694±0.0004   +0.0031 | 0.5792±0.0009   -0.0521\n",
      "   0.35       1,000 | 0.6057±0.0010     -0.0205 | 0.6702±0.0001   +0.0038 | 0.6698±0.0002   +0.0034 | 0.5878±0.0005   -0.0434\n",
      "   0.35      10,000 | 0.6055±0.0003     -0.0206 | 0.6698±0.0001   +0.0035 | 0.6697±0.0001   +0.0033 | 0.5891±0.0003   -0.0422\n",
      "   0.35     100,000 | 0.6060±0.0002     -0.0202 | 0.6701±0.0002   +0.0038 | 0.6698±0.0001   +0.0034 | 0.5892±0.0001   -0.0421\n",
      "   0.35   1,000,000 | 0.6058±0.0002     -0.0203 | 0.6699±0.0000   +0.0036 | 0.6697±0.0001   +0.0034 | 0.5891±0.0001   -0.0422\n",
      "   0.35           ∞ | 0.6059±0.0000     -0.0203 | 0.6699±0.0000   +0.0036 | 0.6696±0.0000   +0.0033 | 0.5891±0.0000   -0.0422\n",
      "  ------------------------------------------------------------------------------------------------------------------\n",
      "   0.20         100 | 0.6148±0.0015     -0.0114 | 0.6705±0.0004   +0.0041 | 0.6699±0.0003   +0.0036 | 0.2956±0.0313   -0.3357\n",
      "   0.20       1,000 | 0.6157±0.0004     -0.0105 | 0.6709±0.0002   +0.0045 | 0.6697±0.0004   +0.0034 | 0.2981±0.0426   -0.3332\n",
      "   0.20      10,000 | 0.6159±0.0003     -0.0103 | 0.6707±0.0002   +0.0044 | 0.6696±0.0002   +0.0033 | 0.3877±0.0583   -0.2436\n",
      "   0.20     100,000 | 0.6160±0.0001     -0.0101 | 0.6700±0.0004   +0.0037 | 0.6695±0.0001   +0.0032 | 0.6173±0.0002   -0.0139\n",
      "   0.20   1,000,000 | 0.6159±0.0001     -0.0102 | 0.6702±0.0004   +0.0039 | 0.6696±0.0001   +0.0033 | 0.6178±0.0001   -0.0135\n",
      "   0.20           ∞ | 0.6160±0.0000     -0.0102 | 0.6698±0.0000   +0.0035 | 0.6697±0.0000   +0.0033 | 0.6178±0.0000   -0.0135\n",
      "  ------------------------------------------------------------------------------------------------------------------\n",
      "\n",
      "  خودآزمایی (γ = 0.35، M = ∞ در برابرِ مقادیرِ مرجع):\n",
      "    QK-NN    0.6059   (مرجع: 0.6059)  ✅\n",
      "    QCF      0.6699   (مرجع: 0.6699)  ✅\n",
      "    CG-QCF   0.6696   (مرجع: 0.6696)  ✅\n",
      "    CQKR     0.5891   (مرجع: 0.5891)  ✅\n",
      "\n",
      "  سودِ CG-QCF بر Cos-kNN — کمترین M که سود هنوز مثبت است:\n",
      "    γ = 0.35:  سود تا M = 100 مثبت می‌ماند\n",
      "    γ = 0.20:  سود تا M = 100 مثبت می‌ماند\n",
      "\n",
      "  حجمِ کارِ سخت‌افزاری برای یک بار پیش‌بینی (هر درایه M بار):\n",
      "    QK-NN / QCF / CG-QCF: 336,000,000 درایهٔ هسته (اعتبارسنجی + آزمون در برابرِ آموزش)\n",
      "    CQKR:                 35,339,776 درایهٔ هسته\n",
      "    در بودجهٔ ۵σِ γ = 0.35 (184,961 shot):  QK-NN ≈ 6.21e+13 shot ،  CQKR ≈ 6.54e+12 shot\n",
      "    در بودجهٔ ۵σِ γ = 0.20 (3,427 shot):  QK-NN ≈ 1.15e+12 shot ،  CQKR ≈ 1.21e+11 shot\n",
      "\n",
      "  زمانِ کل: 2.9 دقیقه\n",
      "\n",
      "======================================================================================================================\n",
      "SHOT_SWEEP_JSON:\n",
      "{\"controls\":{\"Cos-kNN\":0.66632,\"RBF-kNN-10\":0.62617,\"RBF-KR\":0.63128},\"summary\":[{\"gamma\":0.35,\"shots\":100,\"qknn\":0.60405,\"qknn_sd\":0.00017,\"qcf\":0.66964,\"qcf_sd\":0.00032,\"cg\":0.66937,\"cg_sd\":0.00035,\"cqkr\":0.57916,\"cqkr_sd\":0.00095,\"alphas\":[0.75,0.75,0.8]},{\"gamma\":0.35,\"shots\":1000,\"qknn\":0.60566,\"qknn_sd\":0.00096,\"qcf\":0.67017,\"qcf_sd\":0.00012,\"cg\":0.66975,\"cg_sd\":0.00015,\"cqkr\":0.58783,\"cqkr_sd\":0.00047,\"alphas\":[0.75,0.75,0.75]},{\"gamma\":0.35,\"shots\":10000,\"qknn\":0.60553,\"qknn_sd\":0.00026,\"qcf\":0.66982,\"qcf_sd\":0.00011,\"cg\":0.66965,\"cg_sd\":6e-05,\"cqkr\":0.58911,\"cqkr_sd\":0.00028,\"alphas\":[0.8,0.8,0.8]},{\"gamma\":0.35,\"shots\":100000,\"qknn\":0.60597,\"qknn_sd\":0.00021,\"qcf\":0.67011,\"qcf_sd\":0.00017,\"cg\":0.66975,\"cg_sd\":0.00015,\"cqkr\":0.58921,\"cqkr_sd\":7e-05,\"alphas\":[0.8,0.75,0.75]},{\"gamma\":0.35,\"shots\":1000000,\"qknn\":0.60582,\"qknn_sd\":0.00023,\"qcf\":0.66991,\"qcf_sd\":5e-05,\"cg\":0.6697,\"cg_sd\":0.00012,\"cqkr\":0.58909,\"cqkr_sd\":6e-05,\"alphas\":[0.8,0.8,0.8]},{\"gamma\":0.35,\"shots\":null,\"qknn\":0.60586,\"qknn_sd\":0.0,\"qcf\":0.66994,\"qcf_sd\":0.0,\"cg\":0.66964,\"cg_sd\":0.0,\"cqkr\":0.5891,\"cqkr_sd\":0.0,\"alphas\":[0.8]},{\"gamma\":0.2,\"shots\":100,\"qknn\":0.61478,\"qknn_sd\":0.00152,\"qcf\":0.67045,\"qcf_sd\":0.00037,\"cg\":0.66987,\"cg_sd\":0.00032,\"cqkr\":0.29556,\"cqkr_sd\":0.03135,\"alphas\":[0.75,0.8,0.75]},{\"gamma\":0.2,\"shots\":1000,\"qknn\":0.61567,\"qknn_sd\":0.0004,\"qcf\":0.67085,\"qcf_sd\":0.0002,\"cg\":0.66969,\"cg_sd\":0.00039,\"cqkr\":0.29807,\"cqkr_sd\":0.04259,\"alphas\":[0.75,0.75,0.75]},{\"gamma\":0.2,\"shots\":10000,\"qknn\":0.61589,\"qknn_sd\":0.00034,\"qcf\":0.6707,\"qcf_sd\":0.00016,\"cg\":0.66957,\"cg_sd\":0.00022,\"cqkr\":0.38772,\"cqkr_sd\":0.05828,\"alphas\":[0.75,0.75,0.75]},{\"gamma\":0.2,\"shots\":100000,\"qknn\":0.61604,\"qknn_sd\":0.00012,\"qcf\":0.67003,\"qcf_sd\":0.00045,\"cg\":0.66953,\"cg_sd\":0.00011,\"cqkr\":0.61733,\"cqkr_sd\":0.00017,\"alphas\":[0.75,0.8,0.8]},{\"gamma\":0.2,\"shots\":1000000,\"qknn\":0.61593,\"qknn_sd\":0.00013,\"qcf\":0.67023,\"qcf_sd\":0.00037,\"cg\":0.66961,\"cg_sd\":9e-05,\"cqkr\":0.61776,\"cqkr_sd\":8e-05,\"alphas\":[0.75,0.75,0.8]},{\"gamma\":0.2,\"shots\":null,\"qknn\":0.61599,\"qknn_sd\":0.0,\"qcf\":0.66985,\"qcf_sd\":0.0,\"cg\":0.66965,\"cg_sd\":0.0,\"cqkr\":0.61777,\"cqkr_sd\":0.0,\"alphas\":[0.8]}],\"entries\":{\"knn\":336000000,\"cqkr\":35339776}}\n",
      "======================================================================================================================\n",
      "نگهبانِ فضای نام: ✅ هیچ متغیرِ سراسری‌ای بازنویسی نشد\n"
     ]
    }
   ],
   "source": [
    "# ============================================================================\n",
    "# Finite-shot simulation: each fidelity-kernel entry is estimated as\n",
    "# Binomial(M, K) / M for M = 10^2 ... 10^6, for QK-NN, QCF, CG-QCF and CQKR.\n",
    "# Shot noise only (no gate, decoherence or readout error).\n",
    "# ============================================================================\n",
    "\n",
    "import io, json, time, math, contextlib\n",
    "import numpy as np, torch\n",
    "\n",
    "# ---- نگهبانِ فضای نامِ سراسری -------------------------------------------\n",
    "# همهٔ محاسبات داخلِ یک تابع است؛ متغیرهای سراسری (از جمله N_BINS ِ\n",
    "# سلولِ معیارها، یعنی شبکهٔ آستانهٔ τ) پیش و پس از اجرا بررسی می‌شوند.\n",
    "if globals().get(\"N_BINS\") != 101:\n",
    "    raise RuntimeError(f\"N_BINS = {globals().get('N_BINS')} است، نه ۱۰۱ — این نشست آلوده است. \"\n",
    "                       \"نشست را از نو آغاز کنید.\")\n",
    "_SNAPSHOT = {k: id(v) for k, v in globals().items() if not k.startswith(\"_\")}\n",
    "\n",
    "\n",
    "def _run_shot_sweep():\n",
    "\n",
    "    SEED = 42\n",
    "    GAMMAS = [0.35, 0.20]\n",
    "    SHOTS = [100, 1_000, 10_000, 100_000, 1_000_000, None]      # None = ∞، بی‌نویز\n",
    "    REPS = 3                                                    # نمونه‌گیری‌های مستقلِ نویز برای هر M\n",
    "    K_NN = 15\n",
    "    ALPHA_GRID = np.round(np.linspace(0.0, 1.0, 21), 2)         # همان شبکهٔ ریزِ نوت‌بوک\n",
    "    N_CONF = 4          # عمداً نامِ دیگری — N_BINS متعلق به سلولِ معیارهاست (۱۰۱ آستانهٔ τ)\n",
    "    RIDGE = 1.0                                                 # همان پروتکلِ اصلیِ CQKR\n",
    "    CHUNK = 512\n",
    "\n",
    "    # مقادیرِ مرجعِ بذرِ ۴۲، γ = 0.35، بی‌نویز — برای خودآزمایی\n",
    "    PAPER = {\"QK-NN\": 0.6059, \"QCF\": 0.6699, \"CG-QCF\": 0.6696, \"CQKR\": 0.5891,\n",
    "             \"Cos-kNN\": 0.6663, \"RBF-kNN-10\": 0.6262, \"RBF-KR\": 0.6313}\n",
    "    GATE0_SHOTS = {0.35: 184_961, 0.20: 3_427}                  # بودجهٔ ۵σِ Gate-0، بذرِ ۴۲\n",
    "\n",
    "    # ------------------------------------------------------------ پیش‌نیازها\n",
    "    _need = {\"_pca_fit\": 8, \"_pca_apply\": 8, \"CQKRFeatureMap\": 8, \"fidelity_kernel\": 8,\n",
    "             \"rbf_kernel\": 8, \"CQKR_CFG\": 8, \"fmax\": 5, \"Xtr\": 2, \"Xva\": 2, \"Xte\": 2,\n",
    "             \"Ytr_np\": 2, \"Yva_np\": 2, \"Yte_np\": 2}\n",
    "    _miss = [n for n in _need if n not in globals()]\n",
    "    if _miss:\n",
    "        raise RuntimeError(f\"پیش‌نیازها تعریف نشده‌اند: {_miss}\")\n",
    "    DEV = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
    "    if DEV.type != \"cuda\":\n",
    "        print(\"⚠️ GPU در دسترس نیست — اجرا ممکن است چند برابر طول بکشد.\")\n",
    "\n",
    "    Xtr_n, Xva_n, Xte_n = (np.asarray(x.numpy() if hasattr(x, \"numpy\") else x, dtype=np.float32)\n",
    "                           for x in (Xtr, Xva, Xte))\n",
    "    Ytr_t = torch.as_tensor(np.asarray(Ytr_np, dtype=np.float32), device=DEV)\n",
    "    YVA, YTE = np.asarray(Yva_np, dtype=np.float32), np.asarray(Yte_np, dtype=np.float32)\n",
    "    NTR, NVA, NTE = len(Xtr_n), len(Xva_n), len(Xte_n)\n",
    "\n",
    "\n",
    "    # ------------------------------------------------------------ ابزارها\n",
    "    def knn_from_block(sim, k=K_NN):\n",
    "        w, idx = torch.topk(sim, k, dim=1)\n",
    "        w = torch.clamp(w, min=0.0)\n",
    "        w = w / (w.sum(dim=1, keepdim=True) + 1e-8)\n",
    "        return torch.einsum(\"bk,bkc->bc\", w, Ytr_t[idx])\n",
    "\n",
    "\n",
    "    def shot_noise(K, M, gen):\n",
    "        \"\"\"K̂ = Binomial(M, K)/M ، درایه به درایه. M=None یعنی بی‌نویز.\"\"\"\n",
    "        if M is None:\n",
    "            return K\n",
    "        p = K.clamp(0.0, 1.0)\n",
    "        return torch.binomial(torch.full_like(p, float(M)), p, generator=gen) / float(M)\n",
    "\n",
    "\n",
    "    def noisy_knn(Kfull, M, gen):\n",
    "        out = []\n",
    "        for s in range(0, Kfull.shape[0], CHUNK):\n",
    "            out.append(knn_from_block(shot_noise(Kfull[s:s + CHUNK], M, gen)).cpu().numpy())\n",
    "        return np.concatenate(out)\n",
    "\n",
    "\n",
    "    def cosine_sim(Xa, Xb):\n",
    "        A = torch.as_tensor(Xa, device=DEV); A = A / (A.norm(dim=1, keepdim=True) + 1e-8)\n",
    "        B = torch.as_tensor(Xb, device=DEV); B = B / (B.norm(dim=1, keepdim=True) + 1e-8)\n",
    "        return torch.clamp(A @ B.T, min=0.0)\n",
    "\n",
    "\n",
    "    def knn_and_margin(S):\n",
    "        sc, mg = [], []\n",
    "        for s in range(0, S.shape[0], CHUNK):\n",
    "            blk = S[s:s + CHUNK]\n",
    "            sc.append(knn_from_block(blk).cpu().numpy())\n",
    "            v, _ = torch.topk(blk, K_NN, dim=1)\n",
    "            mg.append((v[:, 0] - v[:, -1]).cpu().numpy())\n",
    "        return np.concatenate(sc), np.concatenate(mg)\n",
    "\n",
    "\n",
    "    def best_alpha(y, a_cos, a_q):\n",
    "        best_a, best_f = None, -1.0\n",
    "        for a in ALPHA_GRID:\n",
    "            f = fmax(y, a * a_cos + (1 - a) * a_q)\n",
    "            if f > best_f:\n",
    "                best_f, best_a = f, a\n",
    "        return best_a\n",
    "\n",
    "\n",
    "    def nystrom(Kmm, Knm, Ktm, Y, ridge=RIDGE):\n",
    "        \"\"\"عیناً fit_predictِ سلولِ CQKR.\"\"\"\n",
    "        m = Kmm.shape[0]\n",
    "        S, U = torch.linalg.eigh(Kmm.double() + 1e-6 * torch.eye(m, device=DEV, dtype=torch.float64))\n",
    "        Wht = (U @ torch.diag(S.clamp_min(1e-8).rsqrt()) @ U.T).float()\n",
    "        Z = Knm @ Wht\n",
    "        W = torch.linalg.solve(Z.T @ Z + ridge * torch.eye(m, device=DEV), Z.T @ Y)\n",
    "        return ((Ktm @ Wht) @ W).clamp(0, 1).cpu().numpy()\n",
    "\n",
    "\n",
    "    def sym_noise(Kmm, M, gen):\n",
    "        if M is None:\n",
    "            return Kmm\n",
    "        N = shot_noise(Kmm, M, gen)\n",
    "        N = torch.triu(N, 1); N = N + N.T\n",
    "        return N + torch.eye(Kmm.shape[0], device=DEV)       # ⟨ψ|ψ⟩ = 1 دقیق است\n",
    "\n",
    "\n",
    "    # ------------------------------------------------------------ بخشِ بی‌نویز و مشترک\n",
    "    t_all = time.time()\n",
    "    cfg0 = dict(CQKR_CFG, seed=SEED)\n",
    "    Xm, V = _pca_fit(Xtr_n, cfg0[\"n_qubits\"], cfg0[\"seed\"])\n",
    "    Ztr, Zva, Zte = (_pca_apply(x, Xm, V) for x in (Xtr_n, Xva_n, Xte_n))\n",
    "    from sklearn.cluster import KMeans\n",
    "    km = KMeans(n_clusters=cfg0[\"n_cosets\"], n_init=10, random_state=cfg0[\"seed\"]).fit(Ztr)\n",
    "    ctr, cva, cte = km.labels_, km.predict(Zva), km.predict(Zte)\n",
    "\n",
    "    # لندمارک‌ها با همان مصرفِ rngِ سلولِ CQKR (کششِ دروازهٔ صفر پیش از لندمارک)\n",
    "    rng = np.random.default_rng(cfg0[\"seed\"])\n",
    "    _ = rng.choice(NTR, size=min(cfg0[\"gate0_sample\"], NTR), replace=False)\n",
    "    m_lm = min(cfg0[\"n_landmarks\"], NTR)\n",
    "    lm = rng.choice(NTR, size=m_lm, replace=False)\n",
    "\n",
    "    # کنترل‌های کلاسیک\n",
    "    S_va_cos, S_te_cos = cosine_sim(Xva_n, Xtr_n), cosine_sim(Xte_n, Xtr_n)\n",
    "    sc_cos_va, mg_va = knn_and_margin(S_va_cos)\n",
    "    sc_cos_te, mg_te = knn_and_margin(S_te_cos)\n",
    "    del S_va_cos, S_te_cos\n",
    "    edges = np.quantile(mg_va, [0.25, 0.5, 0.75])\n",
    "    bin_va, bin_te = np.digitize(mg_va, edges), np.digitize(mg_te, edges)\n",
    "\n",
    "    rng_q = np.random.default_rng(cfg0[\"seed\"])\n",
    "    sub = rng_q.choice(NTR, size=min(1024, NTR), replace=False)\n",
    "    Atr, Ate = torch.as_tensor(Ztr, device=DEV), torch.as_tensor(Zte, device=DEV)\n",
    "    g10 = 1.0 / (2.0 * (torch.cdist(Atr[sub], Atr[sub]) ** 2).median().clamp_min(1e-9))\n",
    "    sc_rbf10 = np.concatenate([knn_from_block(rbf_kernel(Ate[s:s + CHUNK], Atr, g10)).cpu().numpy()\n",
    "                               for s in range(0, NTE, CHUNK)])\n",
    "    Alm = Atr[lm]\n",
    "    gkr = 1.0 / (2.0 * (torch.cdist(Alm, Alm) ** 2).median().clamp_min(1e-9))\n",
    "    sc_rbfkr = nystrom(rbf_kernel(Alm, Alm, gkr), rbf_kernel(Atr, Alm, gkr), rbf_kernel(Ate, Alm, gkr), Ytr_t)\n",
    "    del Atr, Ate, Alm\n",
    "\n",
    "    CTRL = {\"Cos-kNN\": fmax(YTE, sc_cos_te), \"RBF-kNN-10\": fmax(YTE, sc_rbf10), \"RBF-KR\": fmax(YTE, sc_rbfkr)}\n",
    "    print(\"=\" * 108)\n",
    "    print(\"کنترل‌های کلاسیک (بی‌نویز):\")\n",
    "    for k, v in CTRL.items():\n",
    "        ok = abs(v - PAPER[k]) < 5e-4\n",
    "        print(f\"  {k:<12} {v:.4f}   (مرجع: {PAPER[k]:.4f})  {'✅' if ok else '❌'}\")\n",
    "\n",
    "    # ------------------------------------------------------------ جاروب\n",
    "    RESULTS_SHOT = {}\n",
    "    for gamma in GAMMAS:\n",
    "        cfg = dict(cfg0, bandwidth=gamma)\n",
    "        fm = CQKRFeatureMap(cfg)\n",
    "        Ptr, Pva, Pte = fm.states(Ztr, ctr), fm.states(Zva, cva), fm.states(Zte, cte)\n",
    "        K_va, K_te = fidelity_kernel(Pva, Ptr), fidelity_kernel(Pte, Ptr)\n",
    "        Plm = Ptr[lm]\n",
    "        K_mm, K_nm, K_tm = fidelity_kernel(Plm, Plm), fidelity_kernel(Ptr, Plm), fidelity_kernel(Pte, Plm)\n",
    "        del Ptr, Pva, Pte, Plm\n",
    "        if DEV.type == \"cuda\":\n",
    "            torch.cuda.empty_cache()\n",
    "\n",
    "        print(\"\\n\" + \"=\" * 108)\n",
    "        print(f\"γ = {gamma}   (بودجهٔ ۵σِ Gate-0: {GATE0_SHOTS.get(gamma, float('nan')):,} shot)\")\n",
    "        print(\"=\" * 108)\n",
    "        for M in SHOTS:\n",
    "            reps = 1 if M is None else REPS\n",
    "            rows = []\n",
    "            for r in range(reps):\n",
    "                gen = torch.Generator(device=DEV)\n",
    "                gen.manual_seed(10_000 * r + (0 if M is None else int(round(math.log10(M)))) + int(gamma * 1000))\n",
    "                sq_va, sq_te = noisy_knn(K_va, M, gen), noisy_knn(K_te, M, gen)\n",
    "                ag = best_alpha(YVA, sc_cos_va, sq_va)\n",
    "                qcf_te = ag * sc_cos_te + (1 - ag) * sq_te\n",
    "                ab = {}\n",
    "                for b in range(N_CONF):\n",
    "                    idx = np.where(bin_va == b)[0]\n",
    "                    ab[b] = best_alpha(YVA[idx], sc_cos_va[idx], sq_va[idx]) if len(idx) >= 20 else ag\n",
    "                av = np.array([ab[b] for b in bin_te])[:, None]\n",
    "                cg_te = av * sc_cos_te + (1 - av) * sq_te\n",
    "                cq_te = nystrom(sym_noise(K_mm, M, gen), shot_noise(K_nm, M, gen), shot_noise(K_tm, M, gen), Ytr_t)\n",
    "                rows.append(dict(qknn=fmax(YTE, sq_te), qcf=fmax(YTE, qcf_te), cg=fmax(YTE, cg_te),\n",
    "                                 cqkr=fmax(YTE, cq_te), alpha=float(ag)))\n",
    "                lab = \"∞\" if M is None else f\"{M:,}\"\n",
    "                print(f\"  M={lab:>10}  تکرار {r + 1}/{reps}:  QK-NN={rows[-1]['qknn']:.4f}  QCF={rows[-1]['qcf']:.4f} \"\n",
    "                      f\"(α={ag:.2f})  CG-QCF={rows[-1]['cg']:.4f}  CQKR={rows[-1]['cqkr']:.4f}\")\n",
    "            RESULTS_SHOT[(gamma, M)] = rows\n",
    "        del K_va, K_te, K_mm, K_nm, K_tm\n",
    "        if DEV.type == \"cuda\":\n",
    "            torch.cuda.empty_cache()\n",
    "\n",
    "\n",
    "    # ------------------------------------------------------------ گزارش\n",
    "    def ms(vals):\n",
    "        v = np.asarray(vals)\n",
    "        return v.mean(), (v.std(ddof=1) if len(v) > 1 else 0.0)\n",
    "\n",
    "\n",
    "    print(\"\\n\" + \"=\" * 118)\n",
    "    print(\"نتیجه — میانگین ± s.d. روی نمونه‌گیری‌های نویز؛ Δ نسبت به کنترلِ کلاسیکِ بی‌نویز\")\n",
    "    print(\"=\" * 118)\n",
    "    print(f\"  {'γ':>5} {'M':>11} | {'QK-NN':>15} {'Δ vs RBF10':>11} | {'QCF':>15} {'Δ vs Cos':>9} | \"\n",
    "          f\"{'CG-QCF':>15} {'Δ vs Cos':>9} | {'CQKR':>15} {'Δ vs KR':>9}\")\n",
    "    SUMMARY = []\n",
    "    for gamma in GAMMAS:\n",
    "        for M in SHOTS:\n",
    "            rows = RESULTS_SHOT[(gamma, M)]\n",
    "            q, qs = ms([r[\"qknn\"] for r in rows]); f, fs = ms([r[\"qcf\"] for r in rows])\n",
    "            c, cs = ms([r[\"cg\"] for r in rows]); k, ks = ms([r[\"cqkr\"] for r in rows])\n",
    "            lab = \"∞\" if M is None else f\"{M:,}\"\n",
    "            print(f\"  {gamma:>5.2f} {lab:>11} | {q:.4f}±{qs:.4f} {q - CTRL['RBF-kNN-10']:>+11.4f} | \"\n",
    "                  f\"{f:.4f}±{fs:.4f} {f - CTRL['Cos-kNN']:>+9.4f} | {c:.4f}±{cs:.4f} {c - CTRL['Cos-kNN']:>+9.4f} | \"\n",
    "                  f\"{k:.4f}±{ks:.4f} {k - CTRL['RBF-KR']:>+9.4f}\")\n",
    "            SUMMARY.append(dict(gamma=gamma, shots=M, qknn=q, qknn_sd=qs, qcf=f, qcf_sd=fs, cg=c, cg_sd=cs,\n",
    "                                cqkr=k, cqkr_sd=ks, alphas=[r[\"alpha\"] for r in rows]))\n",
    "        print(\"  \" + \"-\" * 114)\n",
    "\n",
    "    print(\"\\n  خودآزمایی (γ = 0.35، M = ∞ در برابرِ مقادیرِ مرجع):\")\n",
    "    inf = {r[\"gamma\"]: r for r in SUMMARY if r[\"shots\"] is None}\n",
    "    for key, name in ((\"qknn\", \"QK-NN\"), (\"qcf\", \"QCF\"), (\"cg\", \"CG-QCF\"), (\"cqkr\", \"CQKR\")):\n",
    "        v = inf[0.35][key]\n",
    "        print(f\"    {name:<8} {v:.4f}   (مرجع: {PAPER[name]:.4f})  {'✅' if abs(v - PAPER[name]) < 5e-4 else '❌'}\")\n",
    "\n",
    "    print(\"\\n  سودِ CG-QCF بر Cos-kNN — کمترین M که سود هنوز مثبت است:\")\n",
    "    for gamma in GAMMAS:\n",
    "        finite = [r for r in SUMMARY if r[\"gamma\"] == gamma and r[\"shots\"] is not None]\n",
    "        alive = [r[\"shots\"] for r in finite if r[\"cg\"] - CTRL[\"Cos-kNN\"] > 0]\n",
    "        print(f\"    γ = {gamma:.2f}:  \"\n",
    "              + (f\"سود تا M = {min(alive):,} مثبت می‌ماند\" if alive else \"در هیچ M محدودی مثبت نیست\"))\n",
    "\n",
    "    n_knn = (NVA + NTE) * NTR\n",
    "    n_kr = m_lm * (m_lm - 1) // 2 + (NTR + NTE) * m_lm\n",
    "    print(\"\\n  حجمِ کارِ سخت‌افزاری برای یک بار پیش‌بینی (هر درایه M بار):\")\n",
    "    print(f\"    QK-NN / QCF / CG-QCF: {n_knn:,} درایهٔ هسته (اعتبارسنجی + آزمون در برابرِ آموزش)\")\n",
    "    print(f\"    CQKR:                 {n_kr:,} درایهٔ هسته\")\n",
    "    for gamma, b in GATE0_SHOTS.items():\n",
    "        print(f\"    در بودجهٔ ۵σِ γ = {gamma:.2f} ({b:,} shot):  QK-NN ≈ {n_knn * b:.2e} shot ،  CQKR ≈ {n_kr * b:.2e} shot\")\n",
    "    print(f\"\\n  زمانِ کل: {(time.time() - t_all) / 60:.1f} دقیقه\")\n",
    "\n",
    "    print(\"\\n\" + \"=\" * 118)\n",
    "    print(\"SHOT_SWEEP_JSON:\")\n",
    "    print(json.dumps(dict(controls={k: round(v, 5) for k, v in CTRL.items()},\n",
    "                          summary=[{k: (round(v, 5) if isinstance(v, float) else v) for k, v in r.items()}\n",
    "                                   for r in SUMMARY],\n",
    "                          entries=dict(knn=n_knn, cqkr=n_kr)), ensure_ascii=False, separators=(\",\", \":\")))\n",
    "    print(\"=\" * 118)\n",
    "    return dict(controls=CTRL, summary=SUMMARY)\n",
    "\n",
    "\n",
    "SHOT_SWEEP = _run_shot_sweep()\n",
    "\n",
    "_changed = [k for k, i in _SNAPSHOT.items()\n",
    "            if k in globals() and id(globals()[k]) != i and k not in (\"In\", \"Out\", \"SHOT_SWEEP\")]\n",
    "assert globals().get(\"N_BINS\") == 101, \"N_BINS تغییر کرده است!\"\n",
    "print(\"نگهبانِ فضای نام:\", \"✅ هیچ متغیرِ سراسری‌ای بازنویسی نشد\" if not _changed else f\"⚠️ تغییرکرده: {_changed}\")\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
