{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:19:14.302489Z","iopub.execute_input":"2026-04-13T18:19:14.303326Z","iopub.status.idle":"2026-04-13T18:19:14.308201Z","shell.execute_reply.started":"2026-04-13T18:19:14.303294Z","shell.execute_reply":"2026-04-13T18:19:14.307589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip -q install \"qiskit>=1.0\" qiskit-aer qiskit-machine-learning\n!pip -q install transformers\n\n# Fix fastai conflict if you need fastai\n!pip -q install \"fastcore>=1.8.0,<1.9\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:20:04.615326Z","iopub.execute_input":"2026-04-13T18:20:04.615657Z","iopub.status.idle":"2026-04-13T18:20:19.595115Z","shell.execute_reply.started":"2026-04-13T18:20:04.615627Z","shell.execute_reply":"2026-04-13T18:20:19.594087Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def _pip_install(pkg):\n    import subprocess, sys\n    subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", pkg])\n\ntry:\n    import qiskit\n    import qiskit_machine_learning\nexcept Exception:\n    _pip_install(\"qiskit\")\n    _pip_install(\"qiskit-machine-learning\")\n    import qiskit\n    import qiskit_machine_learning\n\nfrom qiskit import transpile\nfrom qiskit.circuit import QuantumCircuit, ParameterVector\nfrom qiskit.providers.basic_provider import BasicProvider\nfrom qiskit.quantum_info import SparsePauliOp\nfrom qiskit_machine_learning.neural_networks import SamplerQNN, EstimatorQNN\nfrom qiskit_machine_learning.connectors import TorchConnector\n\nbasic_backend = BasicProvider().get_backend(\"basic_simulator\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:20:49.462303Z","iopub.execute_input":"2026-04-13T18:20:49.463058Z","iopub.status.idle":"2026-04-13T18:20:49.468036Z","shell.execute_reply.started":"2026-04-13T18:20:49.463026Z","shell.execute_reply":"2026-04-13T18:20:49.467243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#!/usr/bin/env python3\n\"\"\"\n══════════════════════════════════════════════════════════════════════════════\nAdaptive Hybrid Classical-Quantum Secure Inference — FULL PDF ALIGNMENT\n\"Learning When to Encrypt and When to Quantize\"\n\nSatisfies ALL requirements A through L:\n  A. PDF pipeline modules (f_e, g, B, Q, U_θ, decrypt)\n  B. Strict invariants (same model, encoding, output rule, labels, ordering)\n  C. EPR-QHE with T, T†, quantum cost metric\n  D. Joint training loss  L = L_task + λ1*L_exit + λ2*L_comm + λ3*L_quantum\n  E. Adaptive τ (validation-optimized + entropy-based)\n  F. Communication cost measurement\n  G. 5 baselines\n  H. Ablation studies (τ, dim, layers, noise, cost weights)\n  I. Validation checks (6 invariant checks)\n  J. CIFAR-10 binary dataset\n  K. Full output with tables\n  L. Honest security disclaimer\n══════════════════════════════════════════════════════════════════════════════\n\"\"\"\n\nimport numpy as np, copy, random, time, warnings\nwarnings.filterwarnings('ignore')\n\nimport torch, torch.nn as nn, torch.optim as optim\nfrom torch.utils.data import DataLoader, Subset\nfrom torchvision import datasets, transforms\nfrom scipy.optimize import minimize\nfrom qiskit import QuantumCircuit, transpile\nfrom qiskit.quantum_info import Operator, Statevector\nfrom qiskit_aer import AerSimulator\n\nnp.random.seed(42); random.seed(42); torch.manual_seed(42)\nDEVICE  = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nbackend = AerSimulator(method='statevector')\n\n# ── Global Config ─────────────────────────────────────────────────────────────\nCFG = dict(\n    bottleneck_dim = 2,          # default; ablated over [2, 4, 8]\n    n_layers_vqc   = 1,          # VQC variational layers; ablated over [1, 2]\n    tau_fixed      = 0.80,       # fixed confidence threshold baseline\n    max_epr_t      = 3,          # max T+T† for tractable EPR-QHE demo\n    n_demo_epr     = 6,          # samples for full EPR-QHE demo\n    precision_bits = 32,         # float32 per feature element\n    lambda1        = 0.10,       # L_exit weight\n    lambda2        = 0.05,       # L_comm weight\n    lambda3        = 0.02,       # L_quantum weight\n    # quantum cost weights\n    w_T=1.0, w_Tdg=1.0, w_EPR=2.0, w_depth=0.5, w_wires=0.1,\n)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §1  WIRE ORDERING + GATE UTILITIES\n#      repo wire 0 = MSB;  Qiskit qubit 0 = LSB\n#      r2q(w, n) = n-1-w  maps repo wire → Qiskit qubit index\n# ══════════════════════════════════════════════════════════════════════════════\ndef r2q(w, n): return n - 1 - w\n\ndef _tc(qc): return transpile(qc, backend=backend, optimization_level=2)\n\ndef gate_U(g, repo_w0, nw):\n    \"\"\"Single-qubit unitary (g on 0-indexed repo wire) via transpile → Operator.\"\"\"\n    if g == 'I': return np.eye(2**nw, dtype=complex)\n    qk = r2q(repo_w0, nw)\n    qc = QuantumCircuit(nw)\n    {'X':qc.x,'Z':qc.z,'H':qc.h,'T':qc.t,'t':qc.tdg,'P':qc.s}[g](qk)\n    return Operator(_tc(qc)).data\n\ndef swap_U(w0, w1, nw):\n    qc = QuantumCircuit(nw); qc.swap(r2q(w0,nw), r2q(w1,nw))\n    return Operator(_tc(qc)).data\n\ndef cnot_U(c1, t1, nw):   # 1-indexed repo wires\n    qc = QuantumCircuit(nw); qc.cx(r2q(c1-1,nw), r2q(t1-1,nw))\n    return Operator(_tc(qc)).data\n\ndef epr_vec():\n    qc = QuantumCircuit(2); qc.h(0); qc.cx(0,1)\n    return Statevector(_tc(qc)).data.reshape(-1,1)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §2  QOTP  (gate order: Z^b first, X^a second → X^a · Z^b on state)\n# ══════════════════════════════════════════════════════════════════════════════\ndef qotp_enc(psi, a, b):\n    n = len(a); qc = QuantumCircuit(n)\n    for i in range(n):\n        qk = r2q(i,n)\n        if b[i]: qc.z(qk)\n        if a[i]: qc.x(qk)\n    return psi.copy() if not qc.data else Operator(_tc(qc)).data @ psi\n\ndef qotp_dec(rho, a, b):\n    n = len(a); qc = QuantumCircuit(n)\n    for i in range(n):\n        qk = r2q(i,n)\n        if a[i]: qc.x(qk)\n        if b[i]: qc.z(qk)\n    if not qc.data: return rho.copy()\n    U = Operator(_tc(qc)).data; return U @ rho @ U.conj().T\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §3  QUANTUM OPS\n# ══════════════════════════════════════════════════════════════════════════════\ndef z0(): return np.array([[1.],[0.]], dtype=complex)\ndef o1(): return np.array([[0.],[1.]], dtype=complex)\ndef ptod(p): return p @ p.conj().T\ndef applyU(U,r): return U @ r @ U.conj().T\n\ndef cbasis(n):\n    b = [np.zeros((2**n,1), dtype=complex) for _ in range(2**n)]\n    for i in range(2**n): b[i][i] = 1.\n    return b   # ← return OUTSIDE loop (bug fixed)\n\ndef meas(rho):\n    n  = int(np.log2(rho.shape[0]))\n    ps = np.array([abs(np.trace(rho @ ptod(bv))) for bv in cbasis(n)], float)\n    ps = np.clip(np.real(ps), 0, None); ps /= ps.sum()\n    return bin(np.random.choice(len(ps), p=ps))[2:].zfill(n)\n\ndef op_build(s, L):\n    n = len(s)+len(L); ns = list(\"0\"*n)\n    for i in L: ns[i] = \"I\"\n    c = 0\n    for j in range(n):\n        if ns[j] != \"I\": ns[j] = s[c]; c += 1\n    return \"\".join(ns)\n\ndef ptrace(rho, keep):\n    nd = int(np.log2(rho.shape[0])); tod = nd-len(keep)\n    out = np.zeros((2**len(keep),)*2, dtype=complex)\n    for i in range(2**tod):\n        s  = op_build(bin(i)[2:].zfill(tod), keep)\n        op = np.array(1., dtype=complex)\n        for ch in s:\n            op = np.kron(op,{'0':z0(),'1':o1()}.get(ch,np.eye(2,dtype=complex)))\n        out += op.conj().T @ rho @ op\n    return out\n\ndef post_meas(rho, L, bits):\n    nd = int(np.log2(rho.shape[0])); ol = list(\"I\"*nd)\n    for i,idx in enumerate(L): ol[idx] = bits[i]\n    proj = np.array(1., dtype=complex)\n    for ch in ol:\n        proj = np.kron(proj,{'0':ptod(z0()),'1':ptod(o1())}.get(ch,np.eye(2,dtype=complex)))\n    d = np.trace(proj.conj().T @ proj @ rho)\n    return (proj @ rho @ proj.conj().T)/d, bits\n\ndef depolarize(rho, p, n):\n    if p == 0.: return rho\n    return (1-p)*rho + p*np.eye(2**n, dtype=complex)/2**n\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §4  CIRCUIT STRUCTURE HELPERS\n#      Gate chars: H I X Z P T t(=T†)  +  'CNOT' (4-char string)\n# ══════════════════════════════════════════════════════════════════════════════\ndef tcl_layers(circ):\n    out = []\n    for layer in circ:\n        c, gs = 0, []\n        while c < len(layer):\n            if layer[c] != 'C': gs.append(layer[c]); c += 1\n            else:                gs.append('CNOT');   c += 4\n        out.append(gs)\n    return out\n\ndef nq(circ):\n    cnt = 0\n    for g in tcl_layers([circ[0]])[0]: cnt += 2 if g=='CNOT' else 1\n    return cnt\n\ndef flat_c(circ):\n    n = nq(circ); f = [[] for _ in range(n)]\n    for lay in tcl_layers(circ):\n        c = 0\n        for g in lay:\n            if g != 'CNOT': f[c].append(g); c += 1\n            else: f[c].append('CNOT(1)'); f[c+1].append('CNOT(2)'); c += 2\n    return f\n\ndef nt_in(circ): return sum(g in ('T','t') for lay in tcl_layers(circ) for g in lay)\n\ndef wdict(circ):\n    f = flat_c(circ)\n    return {str(i+1):[i+1,False,sum(g in ('T','t') for g in f[i]),0]\n            for i in range(len(f))}\n\ndef nwires(circ):  return nq(circ) + 2*nt_in(circ)\ndef ntapp(d, q):   return sum(v[2] for k,v in d.items() if int(k)-1 < q)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §5  EPR EVALUATION  (server — sees ONLY ciphertext)\n# ══════════════════════════════════════════════════════════════════════════════\ndef cl_eval(circ, enc_psi, noise_p=0.):\n    n = int(np.log2(enc_psi.shape[0])); rho = ptod(enc_psi)\n    clist = [[] for _ in range(n)]\n    for lay in tcl_layers(circ):\n        qc = QuantumCircuit(n); q = 0\n        for g in lay:\n            qk = r2q(q,n)\n            if   g=='X': qc.x(qk)\n            elif g=='Z': qc.z(qk)\n            elif g=='H': qc.h(qk)\n            elif g=='P': qc.s(qk)\n            elif g=='T': qc.t(qk)\n            elif g=='t': qc.tdg(qk)\n            elif g=='CNOT': qc.cx(qk,r2q(q+1,n)); q+=2; continue\n            q += 1\n        if qc.data:\n            rho = applyU(Operator(_tc(qc)).data, rho)\n            rho = depolarize(rho, noise_p, n)\n    return rho, clist\n\ndef epr_eval(circ, enc_psi, noise_p=0.):\n    n_qb = nq(circ); awd = wdict(circ); clist = [[] for _ in range(n_qb)]\n    layers = tcl_layers(circ); nw = nwires(circ); n_t = nt_in(circ)\n    if n_t == 0: return cl_eval(circ, enc_psi, noise_p)\n    psi_e = enc_psi.copy()\n    for _ in range(n_t): psi_e = np.kron(psi_e, epr_vec())\n    rho = ptod(psi_e); rho = depolarize(rho, noise_p, nw)\n    for q in range(n_qb):\n        if awd[str(q+1)][2] == 0: continue\n        nb = nq(circ)+ntapp(awd,q)*2; nt_q=awd[str(q+1)][2]; start=nb+1\n        for tw in range(nt_q*2):\n            cur = start+tw\n            for _ in range(nq(circ)-awd[str(q+1)][0]):\n                rho = applyU(swap_U(cur-2,cur-1,nw), rho)\n                rho = depolarize(rho, noise_p, nw); cur -= 1\n    for lay in layers:\n        q = 0\n        for g in lay:\n            aw = (awd[str(q+1)][0]+2*ntapp(awd,q)\n                  if awd[str(q+1)][3]==0 else awd[str(q+1)][0])\n            if g not in ('T','t'):\n                if   g == 'I': pass\n                elif g in ('X','Z','H','P'):\n                    rho = applyU(gate_U(g,aw-1,nw), rho)\n                    rho = depolarize(rho, noise_p, nw)\n                else:\n                    aw2 = (awd[str(q+2)][0]+2*ntapp(awd,q+1)\n                           if awd[str(q+2)][3]==0 else awd[str(q+2)][0])\n                    rho = applyU(cnot_U(aw,aw2,nw), rho)\n                    rho = depolarize(rho, noise_p, nw); q+=2; continue\n                q += 1\n            else:\n                if awd[str(q+1)][1]: tgt,ctrl = aw,aw+2\n                else:\n                    awd[str(q+1)][1]=True; tgt=aw; ctrl=q+2*ntapp(awd,q)+2\n                rho = applyU(gate_U(g,tgt-1,nw), rho); rho = depolarize(rho,noise_p,nw)\n                r2c = applyU(cnot_U(ctrl,tgt,nw), rho); r2c = depolarize(r2c,noise_p,nw)\n                bits = meas(ptrace(r2c,[tgt-1])); r2c,c = post_meas(r2c,[tgt-1],bits)\n                clist[q].append(int(c)); awd[str(q+1)][0]=ctrl; awd[str(q+1)][3]+=1\n                rho = r2c; q += 1\n    return rho, clist\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §6  EPR DECRYPTION  (client)\n#      T  key update: a'=a⊕c, b'=a⊕(a·c)⊕b⊕k\n#      T† key update: a'=a⊕c, b'=(a·c)⊕b⊕k\n# ══════════════════════════════════════════════════════════════════════════════\ndef epr_dec(rho_e, circ, a, b, clist, noise_p=0.):\n    nw_ = int(np.log2(rho_e.shape[0])); n_t = nt_in(circ)\n    n_qb = len(clist); layers = tcl_layers(circ); awd = wdict(circ)\n    a_c = copy.deepcopy(a); b_c = copy.deepcopy(b); rho = rho_e.copy()\n    for lay in layers:\n        q = 0\n        for g in lay:\n            if   g in ('I','X','Z'): q += 1\n            elif g == 'P':  b_c[q]=a_c[q]^b_c[q]; q += 1\n            elif g == 'H':  a_c[q],b_c[q]=b_c[q],a_c[q]; q += 1\n            elif g == 'CNOT':\n                b_c[q]=b_c[q]^b_c[q+1]; a_c[q+1]=a_c[q]^a_c[q+1]; q+=2\n            elif g in ('T','t'):\n                if awd[str(q+1)][3]==0:\n                    awd[str(q+1)][1]=True; awd[str(q+1)][3]+=1\n                    aw=q+2*ntapp(awd,q)+3; awd[str(q+1)].append(aw)\n                else:\n                    aw=awd[str(q+1)][4]+2; awd[str(q+1)][3]+=1; awd[str(q+1)][4]=aw\n                tidx = awd[str(q+1)][3]-1\n                if a_c[q]:\n                    rho=applyU(gate_U('P',aw-1,nw_),rho); rho=depolarize(rho,noise_p,nw_)\n                rh=applyU(gate_U('H',aw-1,nw_),rho); rh=depolarize(rh,noise_p,nw_)\n                bk=meas(ptrace(rh,[aw-1])); rh,k=post_meas(rh,[aw-1],bk)\n                rho = rh; c_meas = clist[q][tidx]\n                if g=='T':\n                    b_c[q]=a_c[q]^(a_c[q]*c_meas)^b_c[q]^int(k)\n                else:\n                    b_c[q]=(a_c[q]*c_meas)^b_c[q]^int(k)\n                a_c[q]=a_c[q]^c_meas; q += 1\n    if n_t > 0:\n        aw_k = []\n        for i in range(n_qb):\n            if len(awd[str(i+1)])==5: aw_k.append(awd[str(i+1)][4]-2)\n            else: aw_k.append(i+2*ntapp(awd,i))\n        rho = qotp_dec(ptrace(rho,aw_k), a_c, b_c)\n    else:\n        rho = qotp_dec(rho, a_c, b_c)\n    return depolarize(rho, noise_p, n_qb)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §7  ANGLE ENCODING + GENERAL N-QUBIT VQC\n#      Q(z) = Ry(π·z₀)|0⟩ ⊗ … ⊗ Ry(π·z_{d-1})|0⟩   (INVARIANT §B.2)\n#      OUTPUT RULE: predict 1 if P(|0⟩_wire0) > 0.5   (INVARIANT §B.3)\n# ══════════════════════════════════════════════════════════════════════════════\n_I2    = np.eye(2, dtype=complex)\n_CNOT4 = np.array([[1,0,0,0],[0,1,0,0],[0,0,0,1],[0,0,1,0]], dtype=complex)\n\ndef Ry_mat(th):\n    c,s = np.cos(th/2),np.sin(th/2)\n    return np.array([[c,-s],[s,c]], dtype=complex)\n\ndef encode_state_angle(z, n_qubits):\n    \"\"\"\n    Q(z) = ⊗_{i=0}^{d-1} Ry(π·z_i)|0⟩\n    Returns statevector of shape (2^n_qubits, 1).\n    INVARIANT: used identically in VQC training and encrypted inference.\n    \"\"\"\n    z = np.clip(z, 0, 1)\n    psi = Ry_mat(np.pi*float(z[0])) @ z0()\n    for i in range(1, n_qubits):\n        psi = np.kron(psi, Ry_mat(np.pi*float(z[i])) @ z0())\n    return psi\n\ndef build_cnot_nq(ctrl, tgt, n):\n    \"\"\"CNOT matrix in repo wire ordering (wire 0 = MSB).\"\"\"\n    dim = 2**n; mat = np.eye(dim, dtype=complex)\n    for k in range(dim):\n        bits = list(format(k, f'0{n}b'))\n        if bits[ctrl] == '1':\n            bits[tgt] = '1' if bits[tgt]=='0' else '0'\n            nk = int(''.join(bits), 2)\n            if nk != k: mat[k,k]=0.; mat[nk,k]=1.\n    return mat\n\ndef vqc_forward(z_batch, theta, n_qubits, n_layers):\n    \"\"\"\n    General n-qubit VQC forward.\n    Circuit: [Ry(θ_l_i) ⊗ all qubits → CNOT ring] × n_layers\n    Output: P(|0⟩ on wire 0) — INVARIANT output rule §B.3\n    theta shape: (n_layers * n_qubits,)\n    \"\"\"\n    # Precompute CNOT ring: 0→1, 1→2, …, (n-1)→0\n    cnot_ring = np.eye(2**n_qubits, dtype=complex)\n    for i in range(n_qubits):\n        cnot_ring = build_cnot_nq(i,(i+1)%n_qubits,n_qubits) @ cnot_ring\n    half = 2**(n_qubits-1)\n    probs = []\n    for z in z_batch:\n        psi = encode_state_angle(z[:n_qubits], n_qubits).flatten()\n        for l in range(n_layers):\n            Urot = Ry_mat(float(theta[l*n_qubits+0]))\n            for i in range(1, n_qubits):\n                Urot = np.kron(Urot, Ry_mat(float(theta[l*n_qubits+i])))\n            psi = cnot_ring @ (Urot @ psi)\n        # P(|0⟩ on wire 0) = sum |ψ_k|² for k < 2^(n-1)  [repo: wire 0 = MSB]\n        probs.append(float(np.sum(np.abs(psi[:half])**2)))\n    return np.array(probs)\n\ndef vqc_loss(theta, Z, Y, n_qubits, n_layers):\n    p = vqc_forward(Z, theta, n_qubits, n_layers); eps = 1e-8\n    return -np.mean(Y*np.log(p+eps)+(1-Y)*np.log(1-p+eps))\n\ndef vqc_predict(Z, theta, n_qubits, n_layers):\n    \"\"\"INVARIANT output rule: predict 1 if P(|0⟩)>0.5, else 0.\"\"\"\n    return (vqc_forward(Z, theta, n_qubits, n_layers) > 0.5).astype(int)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §8  FAST EVALUATOR + CIRCUIT COMPILATION + CONVERSION\n# ══════════════════════════════════════════════════════════════════════════════\n_GM = {'H':1/np.sqrt(2)*np.array([[1,1],[1,-1]],dtype=complex),'I':_I2,\n       'X':np.array([[0,1],[1,0]],dtype=complex),'Z':np.array([[1,0],[0,-1]],dtype=complex),\n       'T':np.array([[1,0],[0,np.exp(1j*np.pi/4)]],dtype=complex),\n       't':np.array([[1,0],[0,np.exp(-1j*np.pi/4)]],dtype=complex),\n       'P':np.array([[1,0],[0,1j]],dtype=complex)}\n\ndef fast_eval_circ(layers, psi, noise_p=0.):\n    \"\"\"Plaintext fast evaluation via direct matrix products (no Qiskit overhead).\"\"\"\n    n = int(np.log2(psi.shape[0])); rho = ptod(psi)\n    for lay in tcl_layers(layers):\n        U = np.array(1., dtype=complex)\n        for g in lay: U = np.kron(U, _CNOT4 if g=='CNOT' else _GM.get(g,_I2))\n        rho = applyU(U, rho); rho = depolarize(rho, noise_p, n)\n    return rho\n\ndef p_zero_q0(rho2q):\n    \"\"\"P(|0⟩ on wire 0) from 2-qubit density matrix. INVARIANT output rule.\"\"\"\n    return float(np.real(ptrace(rho2q,[0])[0,0]))\n\ndef classify_rho(rho2q):\n    \"\"\"INVARIANT: predict 1 if P(|0⟩)>0.5, else 0. Same in training and inference.\"\"\"\n    return int(p_zero_q0(rho2q) > 0.5)\n\ndef build_vqc_qiskit(theta, n_qubits, n_layers):\n    \"\"\"\n    Build trained VQC in Qiskit using r2q-consistent qubit mapping.\n    θ_{l·nq+i} → repo wire i → Qiskit qubit r2q(i,nq).\n    CNOT ring: repo wire i → r2q(i,nq), target → r2q((i+1)%nq, nq).\n    \"\"\"\n    qc = QuantumCircuit(n_qubits)\n    for l in range(n_layers):\n        for i in range(n_qubits):\n            qc.ry(float(theta[l*n_qubits+i]), r2q(i,n_qubits))\n        for i in range(n_qubits):\n            qc.cx(r2q(i,n_qubits), r2q((i+1)%n_qubits, n_qubits))\n    return qc\n\ndef convert_to_epr_format(tc, n_qubits=2):\n    \"\"\"\n    Convert transpiled 2-qubit Qiskit circuit to EPR layer-string format.\n    Qiskit qubit q → repo wire (n_qubits-1-q).\n    q=1 → repo wire 0 → first char; q=0 → repo wire 1 → second char (for n=2).\n    \"\"\"\n    GMAP = {'h':'H','t':'T','tdg':'t','cx':'CNOT','x':'X','z':'Z','s':'P','id':'I','i':'I'}\n    layers = []\n    for inst in tc.data:\n        name = inst.operation.name.lower()\n        if name in ('barrier','measure','reset','delay'): continue\n        g = GMAP.get(name)\n        if g is None: print(f\"  [WARN] unhandled gate '{name}'\"); continue\n        if g == 'CNOT':\n            layers.append('CNOT')\n        else:\n            q_idx = tc.find_bit(inst.qubits[0]).index\n            repo_w = n_qubits-1-q_idx    # r2q inverse\n            layer_str = ['I']*n_qubits; layer_str[repo_w] = g\n            layers.append(''.join(layer_str))\n    return layers if layers else ['I'*n_qubits]\n\ndef compute_quantum_cost(n_T, n_Tdg, n_epr, depth, n_wires_total,\n                         w_T=None, w_Tdg=None, w_EPR=None, w_depth=None, w_wires=None):\n    w_T    = w_T    or CFG['w_T']\n    w_Tdg  = w_Tdg  or CFG['w_Tdg']\n    w_EPR  = w_EPR  or CFG['w_EPR']\n    w_depth= w_depth or CFG['w_depth']\n    w_wires= w_wires or CFG['w_wires']\n    return w_T*n_T + w_Tdg*n_Tdg + w_EPR*n_epr + w_depth*depth + w_wires*n_wires_total\n\ndef compile_vqc(theta, n_qubits, n_layers, max_t=None, verbose=True):\n    \"\"\"Compile trained VQC to {H,T,T†,S,CX,X,Z}. Snap angles if T-count > max_t.\"\"\"\n    max_t = max_t or CFG['max_epr_t']\n    def try_compile(th):\n        qc = build_vqc_qiskit(th, n_qubits, n_layers)\n        tc = transpile(qc, basis_gates=['h','t','tdg','cx','x','z','s'], optimization_level=3)\n        ops = dict(tc.count_ops())\n        return tc, ops.get('t',0), ops.get('tdg',0), tc.depth(), ops\n    tc, n_T, n_Tdg, depth, ops = try_compile(theta)\n    theta_used = theta.copy()\n    if n_qubits == 2 and (n_T+n_Tdg) > max_t:\n        if verbose: print(f\"  [COMPILE] T-count={n_T+n_Tdg}>{max_t}. Snapping to π/4 multiples...\")\n        th_snap = np.round(theta/(np.pi/4))*(np.pi/4)\n        tc2,nT2,nTd2,d2,ops2 = try_compile(th_snap)\n        if nT2+nTd2 <= max_t:\n            tc,n_T,n_Tdg,depth,ops,theta_used = tc2,nT2,nTd2,d2,ops2,th_snap\n            if verbose: print(f\"  [COMPILE] π/4 snap: T-count={n_T+n_Tdg} ✓\")\n        else:\n            th_cliff = np.round(theta/(np.pi/2))*(np.pi/2)\n            tc3,nT3,nTd3,d3,ops3 = try_compile(th_cliff)\n            tc,n_T,n_Tdg,depth,ops,theta_used = tc3,nT3,nTd3,d3,ops3,th_cliff\n            if verbose: print(f\"  [COMPILE] Clifford snap: T-count={n_T+n_Tdg}\")\n    layers = convert_to_epr_format(tc, n_qubits) if n_qubits==2 else ['I'*n_qubits]\n    n_epr  = n_T + n_Tdg\n    n_wires_total = nwires(layers) if n_qubits==2 else (n_qubits+2*n_epr)\n    q_cost = compute_quantum_cost(n_T, n_Tdg, n_epr, depth, n_wires_total)\n    return layers, tc, n_T, n_Tdg, n_epr, depth, n_wires_total, q_cost, ops, theta_used\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §9  EDGE-SIDE MODULES  f_e, g, B  (explicit as per PDF)\n# ══════════════════════════════════════════════════════════════════════════════\nclass EdgeBackbone(nn.Module):\n    \"\"\"f_e(x): shallow CNN → 64-D latent h. Runs entirely on edge device.\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.conv1=nn.Conv2d(3,16,3,padding=1); self.bn1=nn.BatchNorm2d(16)\n        self.conv2=nn.Conv2d(16,32,3,padding=1);self.bn2=nn.BatchNorm2d(32)\n        self.pool=nn.MaxPool2d(2,2); self.fc=nn.Linear(32*8*8,64); self.relu=nn.ReLU()\n    def forward(self,x):\n        x=self.relu(self.bn1(self.conv1(x))); x=self.pool(x)\n        x=self.relu(self.bn2(self.conv2(x))); x=self.pool(x)\n        return self.relu(self.fc(x.view(x.size(0),-1)))   # (B,64)\n\nclass EarlyExitHead(nn.Module):\n    \"\"\"g(h): local classifier head. p=softmax(h), c=max(p). Exit if c>=τ.\"\"\"\n    def __init__(self): super().__init__(); self.fc=nn.Linear(64,2)\n    def forward(self,h): return torch.softmax(self.fc(h),dim=1)\n\nclass BottleneckModule(nn.Module):\n    \"\"\"B(h): compact quantum-compatible latent z∈[0,1]^d. dim configurable.\"\"\"\n    def __init__(self, dim=2):\n        super().__init__(); self.fc=nn.Linear(64,dim)\n    def forward(self,h): return torch.sigmoid(self.fc(h))\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §10  JOINT TRAINING LOSS  (PDF eq.12)\n#       L = L_task + λ1*L_exit + λ2*L_comm + λ3*L_quantum\n# ══════════════════════════════════════════════════════════════════════════════\nclass JointLoss(nn.Module):\n    \"\"\"\n    L_task   = CE(g(h), y)                      ← main classification objective\n    L_exit   = mean entropy of g(h)             ← encourage confident predictions\n    L_comm   = mean ||z||² / d                  ← compact bottleneck (proxy comm)\n    L_quantum = bottleneck_dim * const          ← quantum encoding complexity proxy\n    \"\"\"\n    def __init__(self, lam1=0.10, lam2=0.05, lam3=0.02):\n        super().__init__()\n        self.lam1=lam1; self.lam2=lam2; self.lam3=lam3; self.ce=nn.CrossEntropyLoss()\n    def forward(self, logits_raw, probs, z, y):\n        L_task  = self.ce(logits_raw, y)\n        eps = 1e-8\n        L_exit  = torch.mean(-torch.sum(probs*torch.log(probs+eps), dim=1))\n        L_comm  = torch.mean(torch.sum(z**2, dim=1)) / z.shape[1]\n        L_quantum = torch.tensor(float(z.shape[1]) * 0.5, dtype=torch.float32)\n        L_total = L_task + self.lam1*L_exit + self.lam2*L_comm + self.lam3*L_quantum\n        return L_total, L_task.item(), L_exit.item(), L_comm.item()\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §11  DATA LOADING  (CIFAR-10 binary: airplane=0, automobile=1)\n# ══════════════════════════════════════════════════════════════════════════════\ndef load_cifar10_binary(n_train=2000, n_test=400, root='./data'):\n    tf = transforms.Compose([transforms.ToTensor(),\n                              transforms.Normalize((0.491,0.482,0.446),(0.247,0.244,0.261))])\n    tr_full = datasets.CIFAR10(root=root,train=True, download=True,transform=tf)\n    te_full = datasets.CIFAR10(root=root,train=False,download=True,transform=tf)\n    def subset(ds,n):\n        idx=[i for i,(_,y) in enumerate(ds) if y in (0,1)]\n        np.random.shuffle(idx); return Subset(ds,idx[:n])\n    return (DataLoader(subset(tr_full,n_train),batch_size=64,shuffle=True),\n            DataLoader(subset(te_full,n_test), batch_size=64,shuffle=False))\n\ndef remap_y(y): return y.long()   # airplane=0, automobile=1 (INVARIANT §B.4)\n\ndef extract_bottleneck(loader, f_e_mod, B_mod):\n    \"\"\"Extract z=B(f_e(x)) features and labels from a dataloader.\"\"\"\n    f_e_mod.eval(); B_mod.eval(); Z,Y = [],[]\n    with torch.no_grad():\n        for xb,yb in loader:\n            Z.append(B_mod(f_e_mod(xb.to(DEVICE))).cpu().numpy())\n            Y.append(remap_y(yb).numpy())\n    return np.concatenate(Z), np.concatenate(Y)\n\ndef extract_all(loader, f_e_mod, g_mod, B_mod):\n    \"\"\"Extract h, p (probs), conf, local_pred, z, y for adaptive τ.\"\"\"\n    f_e_mod.eval(); g_mod.eval(); B_mod.eval()\n    confs,lpreds,zs,ys = [],[],[],[]\n    with torch.no_grad():\n        for xb,yb in loader:\n            xb=xb.to(DEVICE); h=f_e_mod(xb); p=g_mod(h)\n            confs.extend(p.max(1).values.cpu().numpy())\n            lpreds.extend(p.argmax(1).cpu().numpy())\n            zs.append(B_mod(h).cpu().numpy())\n            ys.extend(remap_y(yb).numpy())\n    return np.array(confs),np.array(lpreds),np.concatenate(zs),np.array(ys)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §12  ADAPTIVE τ  (two methods, compared to fixed baseline)\n# ══════════════════════════════════════════════════════════════════════════════\ndef binary_entropy(p):\n    eps = 1e-8\n    p = np.clip(p, eps, 1-eps)\n    return -(p*np.log2(p)+(1-p)*np.log2(1-p))\n\ndef optimize_tau_confidence(confs, lpreds, cloud_preds, y,\n                            grid=np.arange(0.50,1.00,0.025)):\n    \"\"\"Method 1: Validation-optimized confidence threshold.\"\"\"\n    best_tau, best_acc = 0.5, 0.\n    for tau in grid:\n        preds = np.where(confs>=tau, lpreds, cloud_preds)\n        acc = np.mean(preds==y)\n        if acc > best_acc: best_acc=acc; best_tau=tau\n    return float(best_tau), float(best_acc)\n\ndef optimize_tau_entropy(confs, lpreds, cloud_preds, y):\n    \"\"\"\n    Method 2: Entropy-based adaptive threshold.\n    Exit if H(p_max) ≤ H_thresh (low entropy = confident).\n    Approximate: for binary softmax, H(p) ≈ binary_entropy(p_max).\n    \"\"\"\n    H = binary_entropy(confs)    # entropy proxy from max confidence\n    best_h, best_acc = 0., 0.\n    for h_thresh in np.linspace(0., H.max(), 40):\n        preds = np.where(H <= h_thresh, lpreds, cloud_preds)\n        acc = np.mean(preds==y)\n        if acc > best_acc: best_acc=acc; best_h=h_thresh\n    # Convert H threshold back to equivalent conf threshold for reporting\n    p_eq = 0.5*(1 + np.sqrt(np.maximum(0, 1 - 2*best_h/np.log(2)))) if best_h > 0 else 0.5\n    return float(best_h), float(p_eq), float(best_acc)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §13  QUICK VQC TRAINER  (used in ablations)\n# ══════════════════════════════════════════════════════════════════════════════\ndef train_vqc(Z_tr, Y_tr, n_qubits, n_layers, max_iter=600, n_max=600):\n    \"\"\"Train VQC on bottleneck features. Returns θ*, accuracy on Z_tr.\"\"\"\n    np.random.seed(42)\n    Z, Y = Z_tr[:n_max], Y_tr[:n_max]\n    theta0 = np.random.uniform(0, np.pi, n_layers*n_qubits)\n    res = minimize(vqc_loss, theta0, args=(Z,Y,n_qubits,n_layers),\n                   method='COBYLA', options={'maxiter':max_iter,'rhobeg':0.3})\n    theta_star = res.x\n    tr_acc = np.mean(vqc_predict(Z_tr, theta_star, n_qubits, n_layers)==Y_tr)\n    return theta_star, tr_acc\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §14  PIPELINE ROLES  (Algorithm 1 from PDF)\n# ══════════════════════════════════════════════════════════════════════════════\ndef edge_process(x_raw, f_e_mod, g_mod, B_mod, tau):\n    \"\"\"Steps 1-4: h=f_e(x), p=g(h), c=max(p), early exit or cloud.\"\"\"\n    xt = torch.tensor(x_raw,dtype=torch.float32).unsqueeze(0).to(DEVICE)\n    with torch.no_grad(): h=f_e_mod(xt); p=g_mod(h)\n    conf=float(p.max(1).values); lpred=int(p.argmax(1))\n    if conf >= tau: return 'LOCAL', lpred, None\n    with torch.no_grad(): z=B_mod(h).cpu().numpy().flatten()\n    return 'CLOUD', lpred, z\n\ndef sender_prepare_and_encrypt(z, n_qubits):\n    \"\"\"Step 5: Q(z) then QOTP encrypt. Client keeps keys secret.\"\"\"\n    psi    = encode_state_angle(z[:n_qubits], n_qubits)   # Q(z) — INVARIANT §B.2\n    a_keys = [random.randint(0,1) for _ in range(n_qubits)]\n    b_keys = [random.randint(0,1) for _ in range(n_qubits)]\n    return qotp_enc(psi, a_keys, b_keys), a_keys, b_keys\n\ndef cloud_encrypted_infer(psi_enc, compiled_circuit, noise_p=0.):\n    \"\"\"Step 6: EPR-QHE evaluation of U_θ* on ciphertext. No knowledge of keys.\"\"\"\n    return epr_eval(compiled_circuit, psi_enc, noise_p=noise_p)\n\ndef receiver_decrypt_and_classify(rho_eval, compiled_circuit, a_keys, b_keys, clist, noise_p=0.):\n    \"\"\"Steps 7-8: Decrypt + measure wire 0. INVARIANT output rule §B.3.\"\"\"\n    rho_dec = epr_dec(rho_eval, compiled_circuit, a_keys, b_keys, clist, noise_p)\n    return classify_rho(rho_dec)\n\ndef full_pipeline(x_raw, f_e_mod, g_mod, B_mod, tau,\n                  compiled_circuit, n_qubits, noise_p=0.):\n    \"\"\"Complete Algorithm 1.\"\"\"\n    t0=time.time(); dec,lpred,z = edge_process(x_raw,f_e_mod,g_mod,B_mod,tau)\n    r=dict(decision=dec,local_pred=lpred,enc_pred=None,final_pred=None,\n           t_enc=0.,t_cloud=0.,t_dec=0.,bits_sent=0)\n    if dec=='LOCAL':\n        r['final_pred']=lpred; r['total_time']=time.time()-t0; return r\n    bits = len(z)*CFG['precision_bits']; r['bits_sent']=bits\n    te=time.time(); psi_enc,ak,bk=sender_prepare_and_encrypt(z,n_qubits); r['t_enc']=time.time()-te\n    tc_=time.time(); rho_e,cl=cloud_encrypted_infer(psi_enc,compiled_circuit,noise_p); r['t_cloud']=time.time()-tc_\n    td=time.time(); ep=receiver_decrypt_and_classify(rho_e,compiled_circuit,ak,bk,cl,noise_p); r['t_dec']=time.time()-td\n    r['enc_pred']=ep; r['final_pred']=ep; r['total_time']=time.time()-t0; return r\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §15  BASELINES  (5 required by spec §G)\n# ══════════════════════════════════════════════════════════════════════════════\ndef run_baseline1_full_local(te_loader, f_e_mod, g_mod):\n    \"\"\"B1: Edge model only. No cloud, no quantum.\"\"\"\n    f_e_mod.eval(); g_mod.eval(); preds,ys=[],[]\n    t0=time.time()\n    with torch.no_grad():\n        for xb,yb in te_loader:\n            h=f_e_mod(xb.to(DEVICE)); p=g_mod(h)\n            preds.extend(p.argmax(1).cpu().numpy()); ys.extend(remap_y(yb).numpy())\n    t=time.time()-t0; n=len(ys); acc=np.mean(np.array(preds)==np.array(ys))\n    return dict(name=\"Full Local\",acc=acc,latency=t/n,bits_sent=0,q_cost=0.)\n\ndef run_baseline2_full_cloud_classical(Z_tr,Y_tr,Z_te,Y_te):\n    \"\"\"B2: Classical MLP cloud (no early exit, no quantum).\"\"\"\n    # Train tiny MLP on bottleneck features\n    class MLP(nn.Module):\n        def __init__(self,d): super().__init__(); self.net=nn.Sequential(nn.Linear(d,16),nn.ReLU(),nn.Linear(16,2))\n        def forward(self,x): return self.net(x)\n    mlp = MLP(Z_tr.shape[1]); opt=optim.Adam(mlp.parameters(),lr=1e-3); ce=nn.CrossEntropyLoss()\n    Zt=torch.tensor(Z_tr,dtype=torch.float32); Yt=torch.tensor(Y_tr,dtype=torch.long)\n    for _ in range(200):\n        pred=mlp(Zt); loss=ce(pred,Yt); opt.zero_grad(); loss.backward(); opt.step()\n    mlp.eval()\n    with torch.no_grad():\n        pred_te=mlp(torch.tensor(Z_te,dtype=torch.float32)).argmax(1).numpy()\n    acc=np.mean(pred_te==Y_te)\n    bits=Z_te.shape[1]*CFG['precision_bits']\n    t0=time.time()\n    with torch.no_grad(): mlp(torch.tensor(Z_te,dtype=torch.float32))\n    lat=(time.time()-t0)/len(Z_te)\n    return dict(name=\"Full Cloud Classical\",acc=acc,latency=lat,bits_sent=bits,q_cost=0.)\n\ndef run_baseline3_hybrid_no_encryption(confs_te,lpreds_te,Z_te,Y_te,\n                                        theta_eff,n_qubits,n_layers,compiled_circuit,tau):\n    \"\"\"B3: Early exit + VQC cloud, NO QOTP/EPR-QHE encryption.\"\"\"\n    cloud_preds = []\n    t0=time.time()\n    for z in Z_te:\n        psi=encode_state_angle(z[:n_qubits],n_qubits)\n        rho=fast_eval_circ(compiled_circuit,psi) if n_qubits==2 else \\\n            ptod(encode_state_angle(z[:n_qubits],n_qubits))\n        cloud_preds.append(classify_rho(rho) if n_qubits==2\n                           else int(vqc_forward([z],theta_eff,n_qubits,n_layers)[0]>0.5))\n    cloud_preds=np.array(cloud_preds); lat=(time.time()-t0)/len(Z_te)\n    preds=np.where(confs_te>=tau,lpreds_te,cloud_preds)\n    acc=np.mean(preds==Y_te); bits=n_qubits*CFG['precision_bits']\n    return dict(name=\"Hybrid (No Encryption)\",acc=acc,latency=lat,bits_sent=bits,q_cost=0.)\n\ndef run_baseline4_hybrid_no_early_exit(Z_te,Y_te,theta_eff,n_qubits,n_layers,\n                                        compiled_circuit, q_cost):\n    \"\"\"B4: Always cloud (τ=0), compiled VQC plaintext (EPR-QHE too slow for all samples).\"\"\"\n    t0=time.time()\n    preds=[]\n    for z in Z_te:\n        psi=encode_state_angle(z[:n_qubits],n_qubits)\n        if n_qubits==2:\n            rho=fast_eval_circ(compiled_circuit,psi); preds.append(classify_rho(rho))\n        else:\n            preds.append(int(vqc_forward([z],theta_eff,n_qubits,n_layers)[0]>0.5))\n    preds=np.array(preds); lat=(time.time()-t0)/len(Z_te)\n    acc=np.mean(preds==Y_te); bits=n_qubits*CFG['precision_bits']\n    return dict(name=\"No Early Exit (Cloud)\",acc=acc,latency=lat,bits_sent=bits,q_cost=q_cost)\n\ndef run_baseline5_full_proposed(confs_te,lpreds_te,Z_te,Y_te,theta_eff,\n                                 n_qubits,n_layers,compiled_circuit,tau,q_cost):\n    \"\"\"B5: Full proposed — early exit + compiled VQC cloud (EPR-QHE results shown separately).\"\"\"\n    t0=time.time(); preds=[]; bits_total=0\n    for i,z in enumerate(Z_te):\n        if confs_te[i]>=tau: preds.append(int(lpreds_te[i])); continue\n        psi=encode_state_angle(z[:n_qubits],n_qubits)\n        if n_qubits==2:\n            rho=fast_eval_circ(compiled_circuit,psi); preds.append(classify_rho(rho))\n        else:\n            preds.append(int(vqc_forward([z],theta_eff,n_qubits,n_layers)[0]>0.5))\n        bits_total += n_qubits*CFG['precision_bits']\n    preds=np.array(preds); lat=(time.time()-t0)/len(Z_te)\n    cloud_frac=np.mean(confs_te<tau)\n    acc=np.mean(preds==Y_te); avg_bits=bits_total/max(1,np.sum(confs_te<tau))\n    return dict(name=\"Full Proposed (EPR-QHE)\",acc=acc,latency=lat,bits_sent=avg_bits,q_cost=q_cost,cloud_frac=cloud_frac)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  BANNER\n# ══════════════════════════════════════════════════════════════════════════════\nprint(\"═\"*78)\nprint(\"  Full PDF Alignment: Adaptive Hybrid Classical-Quantum Secure Inference\")\nprint(\"  Dr. Sumaiya Tabassum Nimi, NSU ECE\")\nprint(\"  Dataset: CIFAR-10 binary (airplane=0, automobile=1)\")\nprint(\"═\"*78)\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  PHASE 1 — LOAD DATA + TRAIN EDGE MODULES WITH JOINT LOSS\n# ══════════════════════════════════════════════════════════════════════════════\nprint(\"\\n  ── Phase 1: Edge Training (f_e + g + B) with Joint Loss ──\")\ntr_loader, te_loader = load_cifar10_binary(n_train=2000, n_test=400)\n\nd   = CFG['bottleneck_dim']\nf_e = EdgeBackbone().to(DEVICE)\ng_e = EarlyExitHead().to(DEVICE)\nB   = BottleneckModule(d).to(DEVICE)\njloss = JointLoss(CFG['lambda1'], CFG['lambda2'], CFG['lambda3'])\nopt_edge = optim.Adam(\n    list(f_e.parameters())+list(g_e.parameters())+list(B.parameters()), lr=1e-3)\n\nprint(f\"  Joint loss: L = L_task + {CFG['lambda1']}·L_exit + {CFG['lambda2']}·L_comm + {CFG['lambda3']}·L_quantum\")\nfor ep in range(10):\n    f_e.train(); g_e.train(); B.train()\n    run_l,run_t,run_e,run_c = 0.,0.,0.,0.; total = 0\n    for xb,yb in tr_loader:\n        xb=xb.to(DEVICE); yb_b=remap_y(yb).to(DEVICE)\n        h=f_e(xb); p=g_e(h); z=B(h)\n        # Need raw logits for CE → use fc directly\n        logits_raw=g_e.fc(h)\n        Ltot,Lt,Le,Lc=jloss(logits_raw,p,z,yb_b)\n        opt_edge.zero_grad(); Ltot.backward(); opt_edge.step()\n        run_l+=float(Ltot)*len(xb); run_t+=Lt*len(xb)\n        run_e+=Le*len(xb); run_c+=Lc*len(xb); total+=len(xb)\n    if (ep+1)%2==0:\n        print(f\"  Ep {ep+1:>2}/10  L_total={run_l/total:.4f}  \"\n              f\"L_task={run_t/total:.4f}  L_exit={run_e/total:.4f}  L_comm={run_c/total:.4f}\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  PHASE 2 — ADAPTIVE τ  (validation-optimized + entropy-based)\n# ══════════════════════════════════════════════════════════════════════════════\nprint(\"\\n  ── Phase 2: Adaptive τ Selection ──\")\n\n# Extract validation-set features\nconfs_te,lpreds_te,Z_te,Y_te = extract_all(te_loader, f_e, g_e, B)\nZ_tr, Y_tr                   = extract_bottleneck(tr_loader, f_e, B)\n\n# Quick VQC for adaptive τ computation (train here)\nprint(\"  Training VQC for τ optimization...\")\ntheta_star, vqc_tr_acc = train_vqc(Z_tr, Y_tr, d, CFG['n_layers_vqc'])\ncloud_preds_val = vqc_predict(Z_te, theta_star, d, CFG['n_layers_vqc'])\n\n# Method 1: validation-optimized confidence threshold\ntau_conf, acc_conf = optimize_tau_confidence(confs_te, lpreds_te, cloud_preds_val, Y_te)\n\n# Method 2: entropy-based threshold\nH_te = binary_entropy(confs_te)\ntau_h, tau_h_eq, acc_h = optimize_tau_entropy(confs_te, lpreds_te, cloud_preds_val, Y_te)\n\n# Fixed baseline\npreds_fixed = np.where(confs_te>=CFG['tau_fixed'], lpreds_te, cloud_preds_val)\nacc_fixed   = np.mean(preds_fixed==Y_te)\nee_fixed    = np.mean(confs_te>=CFG['tau_fixed'])\n\nprint(f\"\\n  τ Method Comparison:\")\nprint(f\"  {'Method':<30} {'τ':>7}  {'Acc':>7}  {'Exit Rate':>10}\")\nprint(f\"  {'─'*30}  {'─'*7}  {'─'*7}  {'─'*10}\")\nfor method,tau_,acc_,ee_ in [\n    ('Fixed (baseline)', CFG['tau_fixed'], acc_fixed, ee_fixed),\n    ('Validation-Optimized', tau_conf, acc_conf, np.mean(confs_te>=tau_conf)),\n    ('Entropy-Based', tau_h_eq, acc_h, np.mean(H_te<=tau_h))]:\n    print(f\"  {method:<30} {tau_:>7.3f}  {acc_:>7.4f}  {ee_:>10.3f}\")\n\nTAU = tau_conf  # use best adaptive τ for main pipeline\nprint(f\"\\n  Selected τ = {TAU:.3f}  (validation-optimized)\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  PHASE 3 — VQC TRAINING + COMPILATION\n# ══════════════════════════════════════════════════════════════════════════════\nprint(\"\\n  ── Phase 3: VQC Training + Clifford+T Compilation ──\")\n# theta_star already trained above\nvqc_te_acc = np.mean(vqc_predict(Z_te, theta_star, d, CFG['n_layers_vqc'])==Y_te)\nprint(f\"  θ* = {np.round(theta_star,4)}\")\nprint(f\"  VQC train acc: {vqc_tr_acc:.4f}  |  VQC test acc: {vqc_te_acc:.4f}\")\nprint(f\"  Output rule  : predict 1 if P(|0⟩_wire0) > 0.5  [INVARIANT §B.3]\")\n\nprint(\"\\n  Compiling trained VQC to {H,T,T†,S,CX,X,Z}...\")\n(compiled_circuit, tc_compiled, n_T, n_Tdg, n_epr,\n ckt_depth, nw_total, q_cost_main, ops_compiled, theta_eff) = \\\n    compile_vqc(theta_star, d, CFG['n_layers_vqc'], max_t=CFG['max_epr_t'])\n\n# Communication cost\nbits_per_sample = d * CFG['precision_bits']\nprint(f\"  Gate ops        : {ops_compiled}\")\nprint(f\"  T / T† / CX     : {n_T} / {n_Tdg} / {ops_compiled.get('cx',0)}\")\nprint(f\"  Circuit depth   : {ckt_depth}\")\nprint(f\"  EPR pairs       : {n_epr}  |  Wires: {nw_total}\")\nprint(f\"  Quantum cost    : {q_cost_main:.3f}  \"\n      f\"(w_T={CFG['w_T']} w_Tdg={CFG['w_Tdg']} w_EPR={CFG['w_EPR']} \"\n      f\"w_d={CFG['w_depth']} w_w={CFG['w_wires']})\")\nprint(f\"  Comm cost       : {bits_per_sample} bits/sample  \"\n      f\"(dim={d} × {CFG['precision_bits']} bits)\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §I  VALIDATION CHECKS (6 explicit invariant checks)\n# ══════════════════════════════════════════════════════════════════════════════\nprint(f\"\\n{'═'*78}\")\nprint(f\"  VALIDATION CHECKS — Proving: encrypted path evaluates TRAINED VQC\")\nprint(f\"{'═'*78}\")\n\nN_CHECK = min(CFG['n_demo_epr'], len(Z_te))\nenc_results = []\ntimes_enc   = []\n\nprint(f\"\\n  Check 1+2+5: VQC vs Compiled vs Encrypted on {N_CHECK} samples\")\nprint(f\"  {'#':>3}  {'z₀':>7} {'z₁':>7}  {'P|0⟩ VQC':>10}  \"\n      f\"{'P|0⟩ Comp':>10}  {'Enc pred':>9}  {'Match':>6}  {'OK':>4}\")\nprint(f\"  {'─'*3}  {'─'*7} {'─'*7}  {'─'*10}  {'─'*10}  {'─'*9}  {'─'*6}  {'─'*4}\")\n\nfor i in range(N_CHECK):\n    z = Z_te[i]; y = int(Y_te[i])\n    # Check 1: VQC prediction\n    pv = vqc_forward([z], theta_eff, d, CFG['n_layers_vqc'])[0]\n    plain_pred = int(pv > 0.5)\n    # Check 2: compiled circuit prediction (fast)\n    if d == 2:\n        psi = encode_state_angle(z[:d], d)\n        rho_comp = fast_eval_circ(compiled_circuit, psi)\n        pc = p_zero_q0(rho_comp); comp_pred = classify_rho(rho_comp)\n    else:\n        pc = pv; comp_pred = plain_pred\n    # Check 5: encrypted inference\n    t0=time.time()\n    psi_enc,ak,bk = sender_prepare_and_encrypt(z, d)\n    rho_e,cl      = cloud_encrypted_infer(psi_enc, compiled_circuit)\n    enc_pred      = receiver_decrypt_and_classify(rho_e, compiled_circuit, ak, bk, cl)\n    times_enc.append(time.time()-t0)\n    match = (enc_pred == plain_pred)\n    enc_results.append(dict(plain=plain_pred,comp=comp_pred,enc=enc_pred,y=y,match=match))\n    sym = '✅' if match else '❌'\n    print(f\"  {i+1:>3}  {z[0]:>7.4f} {z[1] if d>=2 else 0:>7.4f}  \"\n          f\"{pv:>10.5f}  {pc:>10.5f}  {enc_pred:>9}  {sym:>6}  \"\n          f\"{'✓' if enc_pred==y else '✗':>4}\")\n\n# Check 3: Full test-set plaintext accuracy\nplain_preds_full = vqc_predict(Z_te, theta_eff, d, CFG['n_layers_vqc'])\ncheck3_acc = np.mean(plain_preds_full==Y_te)\n\n# Check 4: Full test-set compiled accuracy (fast)\nif d == 2:\n    comp_preds_full = []\n    for z in Z_te:\n        psi=encode_state_angle(z[:d],d); rho=fast_eval_circ(compiled_circuit,psi)\n        comp_preds_full.append(classify_rho(rho))\n    comp_preds_full = np.array(comp_preds_full)\n    check4_acc = np.mean(comp_preds_full==Y_te)\n    check4_match = np.mean(comp_preds_full==plain_preds_full)\nelse:\n    check4_acc = check3_acc; check4_match = 1.0\n\n# Check 6: Encrypted match rate\nenc_preds_arr = np.array([r['enc'] for r in enc_results])\ncheck6_match  = np.mean(enc_preds_arr==plain_preds_full[:N_CHECK])\ncheck5_acc    = np.mean(enc_preds_arr==Y_te[:N_CHECK])\n\nprint(f\"\\n  Check 3: Full test-set plaintext VQC acc      : {check3_acc:.4f}  (n={len(Z_te)})\")\nprint(f\"  Check 4: Full test-set compiled circuit acc   : {check4_acc:.4f}  \"\n      f\"(VQC↔compiled match: {check4_match:.4f})\")\nprint(f\"  Check 5: Encrypted VQC acc on {N_CHECK} samples  : {check5_acc:.4f}\")\nprint(f\"  Check 6: Enc=Plain match rate                 : {check6_match:.4f}  \"\n      f\"{'✅ ALIGNED' if check6_match>=0.85 else '⚠ CHECK'}\")\nprint(f\"  Avg encrypted time/sample                     : {np.mean(times_enc):.3f}s\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §G  BASELINES  (5 required)\n# ══════════════════════════════════════════════════════════════════════════════\nprint(f\"\\n{'═'*78}\")\nprint(f\"  BASELINE COMPARISON  (5 systems)\")\nprint(f\"{'═'*78}\")\n\nb1 = run_baseline1_full_local(te_loader, f_e, g_e)\nb2 = run_baseline2_full_cloud_classical(Z_tr, Y_tr, Z_te, Y_te)\nb3 = run_baseline3_hybrid_no_encryption(confs_te,lpreds_te,Z_te,Y_te,\n                                          theta_eff,d,CFG['n_layers_vqc'],compiled_circuit,TAU)\nb4 = run_baseline4_hybrid_no_early_exit(Z_te,Y_te,theta_eff,d,CFG['n_layers_vqc'],\n                                          compiled_circuit,q_cost_main)\nb5 = run_baseline5_full_proposed(confs_te,lpreds_te,Z_te,Y_te,theta_eff,\n                                   d,CFG['n_layers_vqc'],compiled_circuit,TAU,q_cost_main)\n\nall_b = [b1,b2,b3,b4,b5]\nprint(f\"\\n  {'System':<30}  {'Acc':>7}  {'Latency':>10}  {'Bits/samp':>11}  {'Q-Cost':>8}\")\nprint(f\"  {'─'*30}  {'─'*7}  {'─'*10}  {'─'*11}  {'─'*8}\")\nfor i,b in enumerate(all_b,1):\n    cf = f\"  (exit={b.get('cloud_frac',0.):.2f} cloud)\" if 'cloud_frac' in b else \"\"\n    print(f\"  B{i}. {b['name']:<26}  {b['acc']:>7.4f}  {b['latency']:>10.5f}s  \"\n          f\"{b['bits_sent']:>11.0f}  {b['q_cost']:>8.3f}{cf}\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  §H  ABLATION STUDIES\n# ══════════════════════════════════════════════════════════════════════════════\nprint(f\"\\n{'═'*78}\")\nprint(f\"  ABLATION STUDIES\")\nprint(f\"{'═'*78}\")\n\n# ── Ablation 1: Confidence Threshold τ ────────────────────────────────────────\nprint(\"\\n  A1. Threshold τ ablation\")\nprint(f\"  {'τ':>6}  {'Exit Rate':>10}  {'Local Acc':>10}  {'Cloud Frac':>11}  {'Combined Acc':>13}\")\nprint(f\"  {'─'*6}  {'─'*10}  {'─'*10}  {'─'*11}  {'─'*13}\")\nfor tau_ab in [0.50, 0.60, 0.70, 0.75, 0.80, 0.85, 0.90, 0.95]:\n    ee_mask = confs_te >= tau_ab\n    ee_rate = np.mean(ee_mask)\n    ee_acc  = np.mean(lpreds_te[ee_mask]==Y_te[ee_mask]) if ee_rate>0 else float('nan')\n    comb_preds = np.where(ee_mask, lpreds_te, plain_preds_full)\n    comb_acc   = np.mean(comb_preds==Y_te)\n    tag = \" ← selected\" if abs(tau_ab-TAU)<0.01 else (\" ← fixed\" if abs(tau_ab-CFG['tau_fixed'])<0.01 else \"\")\n    print(f\"  {tau_ab:>6.2f}  {ee_rate:>10.3f}  {ee_acc:>10.4f}  {1-ee_rate:>11.3f}  {comb_acc:>13.4f}{tag}\")\n\n# ── Ablation 2: Bottleneck Dimension ─────────────────────────────────────────\nprint(\"\\n  A2. Bottleneck dimension ablation\")\nprint(f\"  {'Dim':>5}  {'VQC Acc':>8}  {'Comm bits':>10}  {'T-count':>8}  {'Q-Cost':>8}  {'EPR pairs':>10}\")\nprint(f\"  {'─'*5}  {'─'*8}  {'─'*10}  {'─'*8}  {'─'*8}  {'─'*10}\")\n\nfor ab_dim in [2, 4, 8]:\n    np.random.seed(42)\n\n    B_ab = BottleneckModule(ab_dim).to(DEVICE)\n    ab_head = nn.Linear(ab_dim, 2).to(DEVICE)   # FIX: create once, move to device\n\n    ce_ab = nn.CrossEntropyLoss()\n    opt_ab = optim.Adam(\n        list(B_ab.parameters()) + list(ab_head.parameters()),\n        lr=1e-3\n    )\n\n    # f_e is only used as frozen feature extractor here\n    f_e.eval()\n    B_ab.train()\n    ab_head.train()\n\n    for _ in range(3):  # brief retrain of B + small classifier head\n        for xb, yb in tr_loader:\n            xb = xb.to(DEVICE)\n            yb_b = remap_y(yb).to(DEVICE)\n\n            with torch.no_grad():\n                h = f_e(xb)   # frozen features\n\n            z_ab = B_ab(h)\n            logits_ab = ab_head(z_ab)\n            l = ce_ab(logits_ab, yb_b)\n\n            opt_ab.zero_grad()\n            l.backward()\n            opt_ab.step()\n\n    f_e.eval()\n    B_ab.eval()\n\n    Z_ab_tr, Y_ab_tr = extract_bottleneck(tr_loader, f_e, B_ab)\n    Z_ab_te, Y_ab_te = extract_bottleneck(te_loader, f_e, B_ab)\n\n    th_ab, _ = train_vqc(Z_ab_tr, Y_ab_tr, ab_dim, 1, max_iter=300)\n    ab_acc = np.mean(vqc_predict(Z_ab_te, th_ab, ab_dim, 1) == Y_ab_te)\n    ab_bits = ab_dim * CFG['precision_bits']\n\n    _, _, nT_ab, nTd_ab, nepr_ab, d_ab, nw_ab, qc_ab, ops_ab, _ = compile_vqc(\n        th_ab, ab_dim, 1, verbose=False\n    )\n\n    print(\n        f\"  {ab_dim:>5}  {ab_acc:>8.4f}  {ab_bits:>10}  {nT_ab+nTd_ab:>8}  \"\n        f\"{qc_ab:>8.3f}  {nepr_ab:>10}\"\n        + (\"  ← default\" if ab_dim == d else \"\")\n    )\n\n# ── Ablation 3: VQC Layers ────────────────────────────────────────────────────\nprint(\"\\n  A3. VQC layers ablation  (dim=2)\")\nprint(f\"  {'Layers':>7}  {'VQC Acc':>8}  {'T-count':>8}  {'Q-Cost':>8}  {'Depth':>7}\")\nprint(f\"  {'─'*7}  {'─'*8}  {'─'*8}  {'─'*8}  {'─'*7}\")\nfor ab_lyr in [1, 2]:\n    np.random.seed(42)\n    th_ly,_=train_vqc(Z_tr,Y_tr,2,ab_lyr,max_iter=400)\n    acc_ly=np.mean(vqc_predict(Z_te,th_ly,2,ab_lyr)==Y_te)\n    _,_,nT_l,nTd_l,nepr_l,d_l,nw_l,qc_l,_,_=compile_vqc(th_ly,2,ab_lyr,verbose=False)\n    print(f\"  {ab_lyr:>7}  {acc_ly:>8.4f}  {nT_l+nTd_l:>8}  {qc_l:>8.3f}  {d_l:>7}\"\n          +(\"  ← default\" if ab_lyr==CFG['n_layers_vqc'] else \"\"))\n\n# ── Ablation 4: Noise Level ────────────────────────────────────────────────────\nprint(f\"\\n  A4. Noise level ablation  (EPR-QHE, first {min(N_CHECK,5)} samples, dim=2)\")\nprint(f\"  {'Noise p':>9}  {'Enc Acc':>8}  {'Plain Acc':>10}  {'Match':>7}  {'Δ':>7}\")\nprint(f\"  {'─'*9}  {'─'*8}  {'─'*10}  {'─'*7}  {'─'*7}\")\nN_noise=min(5,N_CHECK)\nideal_enc=check5_acc\nfor noise_p in [0., 0.005, 0.01, 0.02, 0.05]:\n    np.random.seed(1); random.seed(1)\n    ne_list,np_list=[],[]\n    for i in range(N_noise):\n        z=Z_te[i]; psi=encode_state_angle(z[:d],d)\n        rho_pl=fast_eval_circ(compiled_circuit,psi,noise_p=noise_p)\n        np_list.append(classify_rho(rho_pl))\n        psi_enc,ak,bk=sender_prepare_and_encrypt(z,d)\n        rho_e,cl=cloud_encrypted_infer(psi_enc,compiled_circuit,noise_p=noise_p)\n        ne_list.append(receiver_decrypt_and_classify(rho_e,compiled_circuit,ak,bk,cl,noise_p))\n    ne=np.array(ne_list); np_=np.array(np_list)\n    n_ea=np.mean(ne==Y_te[:N_noise]); n_pa=np.mean(np_==Y_te[:N_noise])\n    n_mt=np.mean(ne==np_); delta=n_ea-ideal_enc\n    tag=\"← ideal\" if noise_p==0 else \"\"\n    print(f\"  {noise_p:>9.4f}  {n_ea:>8.4f}  {n_pa:>10.4f}  {n_mt:>7.4f}  {delta:>+7.4f}  {tag}\")\n\n# ── Ablation 5: Quantum Cost Weights ─────────────────────────────────────────\nprint(f\"\\n  A5. Quantum cost weight sensitivity  (same compiled circuit)\")\nprint(f\"  {'Config':>22}  {'w_T':>5} {'w_Tdg':>6} {'w_EPR':>6} {'w_d':>5} {'w_w':>5}  {'Q-Cost':>8}\")\nprint(f\"  {'─'*22}  {'─'*5} {'─'*6} {'─'*6} {'─'*5} {'─'*5}  {'─'*8}\")\nweight_configs = [\n    ('Default',        1.0, 1.0, 2.0, 0.5, 0.1),\n    ('T-heavy',        3.0, 3.0, 2.0, 0.5, 0.1),\n    ('Depth-heavy',    1.0, 1.0, 2.0, 2.0, 0.5),\n    ('Wire-heavy',     1.0, 1.0, 2.0, 0.5, 1.0),\n    ('EPR-heavy',      1.0, 1.0, 5.0, 0.5, 0.1),\n]\nfor name,wT,wTdg,wEPR,wd,ww in weight_configs:\n    qc_var=compute_quantum_cost(n_T,n_Tdg,n_epr,ckt_depth,nw_total,wT,wTdg,wEPR,wd,ww)\n    print(f\"  {name:>22}  {wT:>5.1f} {wTdg:>6.1f} {wEPR:>6.1f} {wd:>5.1f} {ww:>5.1f}  {qc_var:>8.3f}\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  ADAPTIVE PIPELINE EVALUATION  (Algorithm 1 full run)\n# ══════════════════════════════════════════════════════════════════════════════\nprint(f\"\\n{'═'*78}\")\nprint(f\"  ADAPTIVE PIPELINE — Algorithm 1 Full Evaluation\")\nprint(f\"{'═'*78}\")\nN_ADAPT=min(25,len(te_loader.dataset)); adapt=[]\ndl_s=DataLoader(te_loader.dataset,batch_size=1,shuffle=False)\nfor idx,(xb,yb) in enumerate(dl_s):\n    if idx>=N_ADAPT: break\n    r=full_pipeline(xb.numpy()[0],f_e,g_e,B,TAU,compiled_circuit,d)\n    r['true_label']=int(remap_y(yb)[0]); adapt.append(r)\nloc_r=[r for r in adapt if r['decision']=='LOCAL']\ncld_r=[r for r in adapt if r['decision']=='CLOUD']\nov_acc=np.mean([r['final_pred']==r['true_label'] for r in adapt])\nlc_acc=np.mean([r['final_pred']==r['true_label'] for r in loc_r]) if loc_r else float('nan')\ncl_acc=np.mean([r['final_pred']==r['true_label'] for r in cld_r]) if cld_r else float('nan')\ntotal_bits=sum(r['bits_sent'] for r in adapt); avg_bits=total_bits/max(1,len(cld_r))\nprint(f\"  Samples: {N_ADAPT}  |  Early-exit: {len(loc_r)}/{N_ADAPT} ({len(loc_r)/N_ADAPT:.3f})\")\nprint(f\"  Overall accuracy : {ov_acc:.4f}\")\nprint(f\"  Local acc : {lc_acc:.4f}  |  Cloud acc : {cl_acc:.4f}\")\nprint(f\"  Avg bits sent (cloud samples): {avg_bits:.0f} bits = {avg_bits/8:.1f} bytes\")\nprint(f\"  Avg cloud latency: {np.mean([r['total_time'] for r in cld_r]):.3f}s\" if cld_r else \"\")\n\n# ══════════════════════════════════════════════════════════════════════════════\n#  FINAL SUMMARY TABLE + ALIGNMENT CHECKLIST\n# ══════════════════════════════════════════════════════════════════════════════\nprint(f\"\\n{'═'*78}\")\nprint(f\"  FINAL SUMMARY\")\nprint(f\"{'─'*78}\")\nrows=[\n    (\"Edge backbone f_e\",              \"2-conv CNN → 64-D latent h\"),\n    (\"Early-exit head g\",              f\"Linear(64,2)+softmax, τ={TAU:.3f} (validation-opt)\"),\n    (\"Bottleneck B\",                    f\"Linear(64,{d})+sigmoid → z∈[0,1]^{d}\"),\n    (\"Quantum encoding Q(z)\",          f\"Ry(π·z_i)|0⟩ for i=0..{d-1}  [INVARIANT: train=infer]\"),\n    (\"VQC U_θ*\",                        f\"{CFG['n_layers_vqc']}-layer [Ry⊗all → CX-ring]  \"\n                                        f\"{CFG['n_layers_vqc']*d} params\"),\n    (\"VQC test accuracy\",              f\"{vqc_te_acc:.4f}  (plaintext)\"),\n    (\"Output rule\",                    \"predict 1 if P(|0⟩_wire0)>0.5  [INVARIANT: train=infer]\"),\n    (\"Compiled circuit\",               f\"{len(compiled_circuit)} layers  T={n_T} T†={n_Tdg} depth={ckt_depth}\"),\n    (\"EPR pairs / inference\",          str(n_epr)),\n    (\"Wire overhead\",                  f\"{nw_total} ({d} msg + {2*n_epr} ancilla)\"),\n    (\"Quantum cost\",                   f\"{q_cost_main:.3f}\"),\n    (\"Comm cost\",                      f\"{bits_per_sample} bits/sample ({bits_per_sample//8} bytes)\"),\n    (\"Encrypted acc\",                  f\"{check5_acc:.4f}  (n={N_CHECK})\"),\n    (\"Enc = Plain match\",              f\"{check6_match:.4f}  \"\n                                        f\"({'✅' if check6_match>=0.85 else '⚠'})\"),\n    (\"Adaptive pipeline acc\",          f\"{ov_acc:.4f}  (Algorithm 1)\"),\n    (\"Joint loss λ1/λ2/λ3\",           f\"{CFG['lambda1']}/{CFG['lambda2']}/{CFG['lambda3']}\"),\n]\nfor k,v in rows: print(f\"  {k:<42} {v}\")\n\nprint(f\"\"\"\n{'─'*78}\n  ALIGNMENT CHECKLIST\n  ─────────────────────────────────────────────────────────────────────────\n  A. PDF pipeline modules:\n    Edge backbone f_e present              : YES\n    Early-exit head g present              : YES\n    Bottleneck B present                   : YES\n    Same Q(z) in training and inference    : YES  [encode_state_angle, INVARIANT]\n    Trained VQC used in encryption         : YES  [theta_eff → compiled_circuit]\n    Same output rule (P>0.5)               : YES  [classify_rho, INVARIANT]\n    Receiver-side decryption               : YES  [receiver_decrypt_and_classify]\n\n  B. Strict invariants:\n    Same model                             : YES  [theta_eff = theta_star or snapped]\n    Same input encoding                    : YES  [encode_state_angle used everywhere]\n    Same output rule                       : YES  [classify_rho used everywhere]\n    Same label mapping (0/1 throughout)    : YES  [remap_y, INVARIANT §B.4]\n    Qubit ordering consistent              : YES  [r2q used in build/convert/eval]\n\n  C. QHE:\n    QOTP implemented                       : YES\n    EPR eval (T and T†)                    : YES\n    T† key update distinct from T          : YES\n    Quantum cost metric                    : YES  [compute_quantum_cost]\n\n  D. Joint training loss                   : YES  [JointLoss: task+exit+comm+quantum]\n  E. Adaptive τ (2 methods)               : YES  [confidence + entropy]\n  F. Communication cost measured           : YES  [bits_per_sample tracked]\n  G. Baselines (5)                         : YES  [B1-B5 implemented]\n  H. Ablation studies                      : YES  [τ, dim, layers, noise, cost weights]\n  I. Validation checks (6)                : YES  [VQC/compiled/encrypted/match rates]\n  J. CIFAR-10 dataset                      : YES  [auto-download binary subset]\n\n  SECURITY DISCLAIMER (honest, as required):\n  ────────────────────────────────────────────────────────────────────────\n  ✅ QOTP encryption implemented\n  ✅ EPR-QHE correctness simulation implemented (Broadbent & Jeffery 2015)\n  ✅ Trained VQC encrypted inference demonstrated\n  ⚠  Classical BFV/FHE key-update layer NOT implemented\n     (keys a,b propagated in plaintext on client; full computational\n      security requires BFV layer: github.com/sarojaerabelli/py-fhe)\n  ⚠  Full polynomial-size QHE (Dulek-Schaffner-Speelman 2016) not implemented\n{'═'*78}\n\"\"\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:28:31.953992Z","iopub.execute_input":"2026-04-13T18:28:31.954973Z","iopub.status.idle":"2026-04-13T18:33:15.215069Z","shell.execute_reply.started":"2026-04-13T18:28:31.954944Z","shell.execute_reply":"2026-04-13T18:33:15.213729Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Updated","metadata":{}},{"cell_type":"markdown","source":"# Cell 1 — Imports, config, dataset, and generic helpers","metadata":{}},{"cell_type":"code","source":"# ============================================\n# Cell 1 — Imports, config, dataset, helpers\n# ============================================\n\nimport math\nimport time\nimport copy\nimport random\nimport warnings\nfrom dataclasses import dataclass\n\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Subset\n\nfrom torchvision import datasets, transforms\n\nfrom qiskit import QuantumCircuit, transpile\nfrom qiskit.quantum_info import Operator, Statevector\nfrom qiskit_aer import AerSimulator\n\nwarnings.filterwarnings(\"ignore\")\n\n# ---------- reproducibility ----------\nSEED = 42\nnp.random.seed(SEED)\nrandom.seed(SEED)\ntorch.manual_seed(SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nbackend = AerSimulator(method=\"statevector\")\n\n# ---------- main config ----------\nCFG = {\n    \"n_train\": 2000,\n    \"n_val\": 400,\n    \"n_test\": 400,\n\n    \"batch_size\": 64,\n    \"edge_epochs\": 12,\n    \"edge_lr\": 1e-3,\n\n    \"bottleneck_dim\": 2,         # keep 2 for encrypted 2-qubit tail\n    \"n_native_blocks\": 3,        # native Clifford+T search blocks\n    \"precision_bits\": 32,\n\n    # loss weights\n    \"beta_cloud\": 0.60,          # supervise bottleneck through surrogate cloud head\n    \"lambda_exit\": 0.10,\n    \"lambda_comm\": 0.05,\n    \"lambda_quantum\": 0.02,\n\n    # constrained adaptive tau\n    \"tau_grid\": np.arange(0.55, 0.96, 0.025),\n    \"min_cloud_frac\": 0.05,      # force meaningful offloading\n    \"max_cloud_frac\": 0.30,\n    \"target_cloud_frac\": 0.15,\n\n    # search limits for fast native VQC fitting\n    \"fit_train_cap\": 700,\n    \"fit_val_cap\": 250,\n    \"native_search_passes\": 2,\n    \"native_cost_penalty\": 0.002,\n\n    # EPR-QHE demo / eval\n    \"noise_p\": 0.0,\n    \"validate_n\": 12,\n\n    # resource weights\n    \"w_T\": 1.0,\n    \"w_Tdg\": 1.0,\n    \"w_EPR\": 2.0,\n    \"w_depth\": 0.5,\n    \"w_wires\": 0.1,\n}\n\n# ---------- dataset ----------\ndef load_cifar10_binary(root=\"./data\",\n                        n_train=2000,\n                        n_val=400,\n                        n_test=400,\n                        batch_size=64):\n    \"\"\"\n    Binary CIFAR-10:\n      class 0 = airplane\n      class 1 = automobile\n    \"\"\"\n\n    tf = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize((0.491, 0.482, 0.446),\n                             (0.247, 0.244, 0.261))\n    ])\n\n    train_full = datasets.CIFAR10(root=root, train=True, download=True, transform=tf)\n    test_full  = datasets.CIFAR10(root=root, train=False, download=True, transform=tf)\n\n    train_idx = [i for i, (_, y) in enumerate(train_full) if y in (0, 1)]\n    test_idx  = [i for i, (_, y) in enumerate(test_full) if y in (0, 1)]\n\n    rng = np.random.default_rng(SEED)\n    rng.shuffle(train_idx)\n    rng.shuffle(test_idx)\n\n    train_idx_use = train_idx[:n_train + n_val]\n    test_idx_use  = test_idx[:n_test]\n\n    tr_idx = train_idx_use[:n_train]\n    va_idx = train_idx_use[n_train:n_train + n_val]\n\n    tr_ds = Subset(train_full, tr_idx)\n    va_ds = Subset(train_full, va_idx)\n    te_ds = Subset(test_full, test_idx_use)\n\n    tr_loader = DataLoader(tr_ds, batch_size=batch_size, shuffle=True, num_workers=2, pin_memory=True)\n    va_loader = DataLoader(va_ds, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)\n    te_loader = DataLoader(te_ds, batch_size=batch_size, shuffle=False, num_workers=2, pin_memory=True)\n\n    return tr_loader, va_loader, te_loader\n\ndef remap_y(y):\n    # airplane=0, automobile=1 already\n    return y.long()\n\n# ---------- generic metrics ----------\ndef accuracy_score_np(y_true, y_pred):\n    y_true = np.asarray(y_true)\n    y_pred = np.asarray(y_pred)\n    return float(np.mean(y_true == y_pred))\n\ndef entropy_from_probs(p):\n    eps = 1e-8\n    p = np.clip(p, eps, 1.0)\n    return -(p * np.log(p)).sum(axis=1)\n\ndef binary_entropy_from_conf(conf):\n    eps = 1e-8\n    conf = np.clip(conf, eps, 1 - eps)\n    return -(conf * np.log2(conf) + (1 - conf) * np.log2(1 - conf))\n\ndef summarize_times(name, t_list):\n    if len(t_list) == 0:\n        return {\"name\": name, \"mean_s\": np.nan, \"std_s\": np.nan}\n    return {\"name\": name, \"mean_s\": float(np.mean(t_list)), \"std_s\": float(np.std(t_list))}\n\ndef count_payload_bits_from_z(z):\n    z = np.asarray(z, dtype=np.float32)\n    return int(z.size * CFG[\"precision_bits\"])\n\ndef serialize_float32(z):\n    return np.asarray(z, dtype=np.float32).tobytes()\n\ndef deserialize_float32(payload, dim):\n    return np.frombuffer(payload, dtype=np.float32).copy()[:dim]\n\n# ---------- classical transport encryption baseline ----------\ndef xor_bytes(data: bytes, key: bytes) -> bytes:\n    return bytes([a ^ b for a, b in zip(data, key)])\n\ndef classical_transport_encrypt(z):\n    raw = serialize_float32(z)\n    key = np.random.randint(0, 256, size=len(raw), dtype=np.uint8).tobytes()\n    ct  = xor_bytes(raw, key)\n    return ct, key\n\ndef classical_transport_decrypt(ct, key, dim):\n    raw = xor_bytes(ct, key)\n    return deserialize_float32(raw, dim)\n\n# ---------- reporting ----------\ndef print_header(txt):\n    print(\"\\n\" + \"═\" * 78)\n    print(txt)\n    print(\"═\" * 78)\n\ndef print_subheader(txt):\n    print(\"\\n\" + \"─\" * 78)\n    print(txt)\n    print(\"─\" * 78)\n\nprint(f\"DEVICE = {DEVICE}\")\nprint(\"Cell 1 ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:48:53.224342Z","iopub.execute_input":"2026-04-13T18:48:53.225211Z","iopub.status.idle":"2026-04-13T18:48:53.246132Z","shell.execute_reply.started":"2026-04-13T18:48:53.225181Z","shell.execute_reply":"2026-04-13T18:48:53.245457Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cell 2 — Exact QOTP + EPR-QHE primitives and native encrypted circuit evaluation","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Cell 2 — Exact QOTP + EPR-QHE primitives + native evaluator\n# ============================================================\n\n# ------------------------------------------------------------\n# repo wire 0 = MSB; Qiskit qubit 0 = LSB\n# ------------------------------------------------------------\ndef r2q(w, n):\n    return n - 1 - w\n\ndef _tc(qc):\n    return transpile(qc, backend=backend, optimization_level=0)\n\n# ---------- exact gate matrices via Qiskit ----------\ndef gate_U(g, repo_w0, nw):\n    if g == \"I\":\n        return np.eye(2 ** nw, dtype=complex)\n\n    qk = r2q(repo_w0, nw)\n    qc = QuantumCircuit(nw)\n\n    if g == \"X\":\n        qc.x(qk)\n    elif g == \"Z\":\n        qc.z(qk)\n    elif g == \"H\":\n        qc.h(qk)\n    elif g == \"P\":\n        qc.s(qk)\n    elif g == \"T\":\n        qc.t(qk)\n    elif g == \"t\":\n        qc.tdg(qk)\n    else:\n        raise ValueError(f\"Unsupported gate: {g}\")\n\n    return Operator(_tc(qc)).data\n\ndef swap_U(w0, w1, nw):\n    qc = QuantumCircuit(nw)\n    qc.swap(r2q(w0, nw), r2q(w1, nw))\n    return Operator(_tc(qc)).data\n\ndef cnot_U(c1, t1, nw):\n    \"\"\"\n    c1, t1 are 1-indexed repo wires\n    \"\"\"\n    qc = QuantumCircuit(nw)\n    qc.cx(r2q(c1 - 1, nw), r2q(t1 - 1, nw))\n    return Operator(_tc(qc)).data\n\ndef epr_vec():\n    qc = QuantumCircuit(2)\n    qc.h(0)\n    qc.cx(0, 1)\n    return Statevector(_tc(qc)).data.reshape(-1, 1)\n\n# ---------- basic quantum state helpers ----------\ndef z0():\n    return np.array([[1.0], [0.0]], dtype=complex)\n\ndef o1():\n    return np.array([[0.0], [1.0]], dtype=complex)\n\ndef ptod(p):\n    return p @ p.conj().T\n\ndef applyU(U, r):\n    return U @ r @ U.conj().T\n\ndef cbasis(n):\n    out = []\n    for i in range(2 ** n):\n        v = np.zeros((2 ** n, 1), dtype=complex)\n        v[i, 0] = 1.0\n        out.append(v)\n    return out\n\ndef meas(rho):\n    n = int(np.log2(rho.shape[0]))\n    ps = np.array([abs(np.trace(rho @ ptod(v))) for v in cbasis(n)], dtype=float)\n    ps = np.clip(np.real(ps), 0, None)\n    ps = ps / ps.sum()\n    return bin(np.random.choice(len(ps), p=ps))[2:].zfill(n)\n\ndef op_build(s, L):\n    n = len(s) + len(L)\n    ns = list(\"0\" * n)\n    for i in L:\n        ns[i] = \"I\"\n    c = 0\n    for j in range(n):\n        if ns[j] != \"I\":\n            ns[j] = s[c]\n            c += 1\n    return \"\".join(ns)\n\ndef ptrace(rho, keep):\n    nd = int(np.log2(rho.shape[0]))\n    tod = nd - len(keep)\n    out = np.zeros((2 ** len(keep), 2 ** len(keep)), dtype=complex)\n    for i in range(2 ** tod):\n        s = op_build(bin(i)[2:].zfill(tod), keep)\n        op = np.array(1.0, dtype=complex)\n        for ch in s:\n            if ch == \"0\":\n                op = np.kron(op, z0())\n            elif ch == \"1\":\n                op = np.kron(op, o1())\n            else:\n                op = np.kron(op, np.eye(2, dtype=complex))\n        out += op.conj().T @ rho @ op\n    return out\n\ndef post_meas(rho, L, bits):\n    nd = int(np.log2(rho.shape[0]))\n    ol = list(\"I\" * nd)\n    for i, idx in enumerate(L):\n        ol[idx] = bits[i]\n\n    proj = np.array(1.0, dtype=complex)\n    for ch in ol:\n        if ch == \"0\":\n            proj = np.kron(proj, ptod(z0()))\n        elif ch == \"1\":\n            proj = np.kron(proj, ptod(o1()))\n        else:\n            proj = np.kron(proj, np.eye(2, dtype=complex))\n\n    d = np.trace(proj.conj().T @ proj @ rho)\n    return (proj @ rho @ proj.conj().T) / d, bits\n\ndef depolarize(rho, p, n):\n    if p == 0.0:\n        return rho\n    return (1 - p) * rho + p * np.eye(2 ** n, dtype=complex) / (2 ** n)\n\n# ---------- QOTP ----------\ndef qotp_enc(psi, a, b):\n    n = len(a)\n    qc = QuantumCircuit(n)\n    for i in range(n):\n        qk = r2q(i, n)\n        if b[i]:\n            qc.z(qk)\n        if a[i]:\n            qc.x(qk)\n    if len(qc.data) == 0:\n        return psi.copy()\n    return Operator(_tc(qc)).data @ psi\n\ndef qotp_dec(rho, a, b):\n    n = len(a)\n    qc = QuantumCircuit(n)\n    for i in range(n):\n        qk = r2q(i, n)\n        if a[i]:\n            qc.x(qk)\n        if b[i]:\n            qc.z(qk)\n    if len(qc.data) == 0:\n        return rho.copy()\n    U = Operator(_tc(qc)).data\n    return U @ rho @ U.conj().T\n\n# ---------- layer parsing ----------\ndef tcl_layers(circ):\n    out = []\n    for layer in circ:\n        c = 0\n        gs = []\n        while c < len(layer):\n            if layer[c] != \"C\":\n                gs.append(layer[c])\n                c += 1\n            else:\n                gs.append(\"CNOT\")\n                c += 4\n        out.append(gs)\n    return out\n\ndef nq(circ):\n    cnt = 0\n    for g in tcl_layers([circ[0]])[0]:\n        cnt += 2 if g == \"CNOT\" else 1\n    return cnt\n\ndef flat_c(circ):\n    n = nq(circ)\n    f = [[] for _ in range(n)]\n    for lay in tcl_layers(circ):\n        c = 0\n        for g in lay:\n            if g != \"CNOT\":\n                f[c].append(g)\n                c += 1\n            else:\n                f[c].append(\"CNOT(1)\")\n                f[c + 1].append(\"CNOT(2)\")\n                c += 2\n    return f\n\ndef nt_in(circ):\n    return sum(g in (\"T\", \"t\") for lay in tcl_layers(circ) for g in lay)\n\ndef wdict(circ):\n    f = flat_c(circ)\n    return {\n        str(i + 1): [i + 1, False, sum(g in (\"T\", \"t\") for g in f[i]), 0]\n        for i in range(len(f))\n    }\n\ndef nwires(circ):\n    return nq(circ) + 2 * nt_in(circ)\n\ndef ntapp(d, q):\n    return sum(v[2] for k, v in d.items() if int(k) - 1 < q)\n\n# ---------- native plaintext eval ----------\n_GM = {\n    \"H\": (1 / np.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex),\n    \"I\": np.eye(2, dtype=complex),\n    \"X\": np.array([[0, 1], [1, 0]], dtype=complex),\n    \"Z\": np.array([[1, 0], [0, -1]], dtype=complex),\n    \"P\": np.array([[1, 0], [0, 1j]], dtype=complex),\n    \"T\": np.array([[1, 0], [0, np.exp(1j * np.pi / 4)]], dtype=complex),\n    \"t\": np.array([[1, 0], [0, np.exp(-1j * np.pi / 4)]], dtype=complex),\n}\n_CNOT4 = np.array(\n    [[1, 0, 0, 0],\n     [0, 1, 0, 0],\n     [0, 0, 0, 1],\n     [0, 0, 1, 0]],\n    dtype=complex\n)\n\ndef fast_eval_circ(layers, psi, noise_p=0.0):\n    rho = ptod(psi)\n    n = int(np.log2(psi.shape[0]))\n    for lay in tcl_layers(layers):\n        U = np.array(1.0, dtype=complex)\n        for g in lay:\n            U = np.kron(U, _CNOT4 if g == \"CNOT\" else _GM[g])\n        rho = applyU(U, rho)\n        rho = depolarize(rho, noise_p, n)\n    return rho\n\n# ---------- encrypted server eval ----------\ndef cl_eval(circ, enc_psi, noise_p=0.0):\n    n = int(np.log2(enc_psi.shape[0]))\n    rho = ptod(enc_psi)\n    clist = [[] for _ in range(n)]\n    for lay in tcl_layers(circ):\n        qc = QuantumCircuit(n)\n        q = 0\n        for g in lay:\n            qk = r2q(q, n)\n            if g == \"X\":\n                qc.x(qk)\n            elif g == \"Z\":\n                qc.z(qk)\n            elif g == \"H\":\n                qc.h(qk)\n            elif g == \"P\":\n                qc.s(qk)\n            elif g == \"T\":\n                qc.t(qk)\n            elif g == \"t\":\n                qc.tdg(qk)\n            elif g == \"CNOT\":\n                qc.cx(qk, r2q(q + 1, n))\n                q += 2\n                continue\n            q += 1\n        if len(qc.data) > 0:\n            rho = applyU(Operator(_tc(qc)).data, rho)\n            rho = depolarize(rho, noise_p, n)\n    return rho, clist\n\ndef epr_eval(circ, enc_psi, noise_p=0.0):\n    n_qb = nq(circ)\n    awd = wdict(circ)\n    clist = [[] for _ in range(n_qb)]\n    layers = tcl_layers(circ)\n    nw = nwires(circ)\n    n_t = nt_in(circ)\n\n    if n_t == 0:\n        return cl_eval(circ, enc_psi, noise_p)\n\n    psi_e = enc_psi.copy()\n    for _ in range(n_t):\n        psi_e = np.kron(psi_e, epr_vec())\n\n    rho = ptod(psi_e)\n    rho = depolarize(rho, noise_p, nw)\n\n    for q in range(n_qb):\n        if awd[str(q + 1)][2] == 0:\n            continue\n        nb = nq(circ) + ntapp(awd, q) * 2\n        nt_q = awd[str(q + 1)][2]\n        start = nb + 1\n        for tw in range(nt_q * 2):\n            cur = start + tw\n            for _ in range(nq(circ) - awd[str(q + 1)][0]):\n                rho = applyU(swap_U(cur - 2, cur - 1, nw), rho)\n                rho = depolarize(rho, noise_p, nw)\n                cur -= 1\n\n    for lay in layers:\n        q = 0\n        for g in lay:\n            aw = (awd[str(q + 1)][0] + 2 * ntapp(awd, q)\n                  if awd[str(q + 1)][3] == 0\n                  else awd[str(q + 1)][0])\n\n            if g not in (\"T\", \"t\"):\n                if g == \"I\":\n                    pass\n                elif g in (\"X\", \"Z\", \"H\", \"P\"):\n                    rho = applyU(gate_U(g, aw - 1, nw), rho)\n                    rho = depolarize(rho, noise_p, nw)\n                else:\n                    aw2 = (awd[str(q + 2)][0] + 2 * ntapp(awd, q + 1)\n                           if awd[str(q + 2)][3] == 0\n                           else awd[str(q + 2)][0])\n                    rho = applyU(cnot_U(aw, aw2, nw), rho)\n                    rho = depolarize(rho, noise_p, nw)\n                    q += 2\n                    continue\n                q += 1\n\n            else:\n                if awd[str(q + 1)][1]:\n                    tgt, ctrl = aw, aw + 2\n                else:\n                    awd[str(q + 1)][1] = True\n                    tgt = aw\n                    ctrl = q + 2 * ntapp(awd, q) + 2\n\n                rho = applyU(gate_U(g, tgt - 1, nw), rho)\n                rho = depolarize(rho, noise_p, nw)\n\n                r2c = applyU(cnot_U(ctrl, tgt, nw), rho)\n                r2c = depolarize(r2c, noise_p, nw)\n\n                bits = meas(ptrace(r2c, [tgt - 1]))\n                r2c, c = post_meas(r2c, [tgt - 1], bits)\n\n                clist[q].append(int(c))\n                awd[str(q + 1)][0] = ctrl\n                awd[str(q + 1)][3] += 1\n                rho = r2c\n                q += 1\n\n    return rho, clist\n\ndef epr_dec(rho_e, circ, a, b, clist, noise_p=0.0):\n    nw_ = int(np.log2(rho_e.shape[0]))\n    n_t = nt_in(circ)\n    n_qb = len(clist)\n    layers = tcl_layers(circ)\n    awd = wdict(circ)\n\n    a_c = copy.deepcopy(a)\n    b_c = copy.deepcopy(b)\n    rho = rho_e.copy()\n\n    for lay in layers:\n        q = 0\n        for g in lay:\n            if g in (\"I\", \"X\", \"Z\"):\n                q += 1\n\n            elif g == \"P\":\n                b_c[q] = a_c[q] ^ b_c[q]\n                q += 1\n\n            elif g == \"H\":\n                a_c[q], b_c[q] = b_c[q], a_c[q]\n                q += 1\n\n            elif g == \"CNOT\":\n                b_c[q] = b_c[q] ^ b_c[q + 1]\n                a_c[q + 1] = a_c[q] ^ a_c[q + 1]\n                q += 2\n\n            elif g in (\"T\", \"t\"):\n                if awd[str(q + 1)][3] == 0:\n                    awd[str(q + 1)][1] = True\n                    awd[str(q + 1)][3] += 1\n                    aw = q + 2 * ntapp(awd, q) + 3\n                    awd[str(q + 1)].append(aw)\n                else:\n                    aw = awd[str(q + 1)][4] + 2\n                    awd[str(q + 1)][3] += 1\n                    awd[str(q + 1)][4] = aw\n\n                tidx = awd[str(q + 1)][3] - 1\n\n                if a_c[q]:\n                    rho = applyU(gate_U(\"P\", aw - 1, nw_), rho)\n                    rho = depolarize(rho, noise_p, nw_)\n\n                rh = applyU(gate_U(\"H\", aw - 1, nw_), rho)\n                rh = depolarize(rh, noise_p, nw_)\n\n                bk = meas(ptrace(rh, [aw - 1]))\n                rh, k = post_meas(rh, [aw - 1], bk)\n                rho = rh\n\n                c_meas = clist[q][tidx]\n\n                if g == \"T\":\n                    b_c[q] = a_c[q] ^ (a_c[q] * c_meas) ^ b_c[q] ^ int(k)\n                else:\n                    b_c[q] = (a_c[q] * c_meas) ^ b_c[q] ^ int(k)\n\n                a_c[q] = a_c[q] ^ c_meas\n                q += 1\n\n    if n_t > 0:\n        aw_k = []\n        for i in range(n_qb):\n            if len(awd[str(i + 1)]) == 5:\n                aw_k.append(awd[str(i + 1)][4] - 2)\n            else:\n                aw_k.append(i + 2 * ntapp(awd, i))\n        rho = qotp_dec(ptrace(rho, aw_k), a_c, b_c)\n    else:\n        rho = qotp_dec(rho, a_c, b_c)\n\n    return depolarize(rho, noise_p, n_qb)\n\n# ---------- encoding + output rule ----------\ndef Ry_mat(th):\n    c, s = np.cos(th / 2), np.sin(th / 2)\n    return np.array([[c, -s], [s, c]], dtype=complex)\n\ndef encode_state_angle(z, n_qubits=2):\n    z = np.clip(np.asarray(z, dtype=float), 0.0, 1.0)\n    psi = Ry_mat(np.pi * float(z[0])) @ z0()\n    for i in range(1, n_qubits):\n        psi = np.kron(psi, Ry_mat(np.pi * float(z[i])) @ z0())\n    return psi\n\ndef p_zero_q0(rho2q):\n    return float(np.real(ptrace(rho2q, [0])[0, 0]))\n\ndef classify_rho(rho2q):\n    return int(p_zero_q0(rho2q) > 0.5)\n\ndef sender_prepare_and_encrypt(z):\n    psi = encode_state_angle(z[:2], 2)\n    a_keys = [random.randint(0, 1) for _ in range(2)]\n    b_keys = [random.randint(0, 1) for _ in range(2)]\n    psi_enc = qotp_enc(psi, a_keys, b_keys)\n    return psi_enc, a_keys, b_keys\n\ndef cloud_encrypted_infer(psi_enc, compiled_circuit, noise_p=0.0):\n    return epr_eval(compiled_circuit, psi_enc, noise_p=noise_p)\n\ndef receiver_decrypt_and_classify(rho_eval, compiled_circuit, a_keys, b_keys, clist, noise_p=0.0):\n    rho_dec = epr_dec(rho_eval, compiled_circuit, a_keys, b_keys, clist, noise_p=noise_p)\n    return classify_rho(rho_dec)\n\n# ---------- exact resource counting ----------\ndef count_circuit_resources(circ):\n    ops = [g for lay in tcl_layers(circ) for g in lay]\n    nT = sum(g == \"T\" for g in ops)\n    nTd = sum(g == \"t\" for g in ops)\n    nEPR = nT + nTd\n    depth = len(circ)\n    wires = nwires(circ)\n    q_cost = (\n        CFG[\"w_T\"] * nT\n        + CFG[\"w_Tdg\"] * nTd\n        + CFG[\"w_EPR\"] * nEPR\n        + CFG[\"w_depth\"] * depth\n        + CFG[\"w_wires\"] * wires\n    )\n    return {\n        \"nT\": nT,\n        \"nTd\": nTd,\n        \"nEPR\": nEPR,\n        \"depth\": depth,\n        \"wires\": wires,\n        \"q_cost\": float(q_cost),\n    }\n\nprint(\"Cell 2 ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:51:29.927817Z","iopub.execute_input":"2026-04-13T18:51:29.928695Z","iopub.status.idle":"2026-04-13T18:51:29.982265Z","shell.execute_reply.started":"2026-04-13T18:51:29.928651Z","shell.execute_reply":"2026-04-13T18:51:29.981355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cell 3 — Edge backbone, bottleneck shaping, and feature extraction","metadata":{}},{"cell_type":"code","source":"# ==========================================================\n# Cell 3 — Edge modules, surrogate cloud head, train helpers\n# ==========================================================\n\nclass EdgeBackbone(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv1 = nn.Conv2d(3, 16, 3, padding=1)\n        self.bn1   = nn.BatchNorm2d(16)\n        self.conv2 = nn.Conv2d(16, 32, 3, padding=1)\n        self.bn2   = nn.BatchNorm2d(32)\n        self.pool  = nn.MaxPool2d(2, 2)\n        self.relu  = nn.ReLU()\n        self.fc    = nn.Linear(32 * 8 * 8, 64)\n\n    def forward(self, x):\n        x = self.relu(self.bn1(self.conv1(x)))\n        x = self.pool(x)\n        x = self.relu(self.bn2(self.conv2(x)))\n        x = self.pool(x)\n        x = x.view(x.size(0), -1)\n        return self.relu(self.fc(x))\n\nclass EarlyExitHead(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.fc = nn.Linear(64, 2)\n\n    def forward(self, h):\n        logits = self.fc(h)\n        probs = torch.softmax(logits, dim=1)\n        return logits, probs\n\nclass BottleneckModule(nn.Module):\n    def __init__(self, dim=2):\n        super().__init__()\n        self.fc = nn.Linear(64, dim)\n\n    def forward(self, h):\n        return torch.sigmoid(self.fc(h))\n\nclass CloudSurrogateHead(nn.Module):\n    \"\"\"\n    Classical cloud surrogate used only to shape z during training\n    and for the classical baselines.\n    \"\"\"\n    def __init__(self, dim=2):\n        super().__init__()\n        self.net = nn.Sequential(\n            nn.Linear(dim, 16),\n            nn.ReLU(),\n            nn.Linear(16, 2)\n        )\n\n    def forward(self, z):\n        return self.net(z)\n\nclass JointLoss(nn.Module):\n    \"\"\"\n    Practical surrogate for the PDF loss:\n      L = L_task_local + beta*L_task_cloud + λ_exit*L_exit + λ_comm*L_comm + λ_quantum*L_quantum\n\n    This fixes the old issue where B was almost unsupervised, which made the cloud branch useless.\n    \"\"\"\n    def __init__(self, beta_cloud=0.60, lambda_exit=0.10, lambda_comm=0.05, lambda_quantum=0.02):\n        super().__init__()\n        self.beta_cloud = beta_cloud\n        self.lambda_exit = lambda_exit\n        self.lambda_comm = lambda_comm\n        self.lambda_quantum = lambda_quantum\n        self.ce = nn.CrossEntropyLoss()\n\n    def forward(self, local_logits, local_probs, cloud_logits, z, y):\n        L_local = self.ce(local_logits, y)\n        L_cloud = self.ce(cloud_logits, y)\n\n        eps = 1e-8\n        L_exit = torch.mean(-torch.sum(local_probs * torch.log(local_probs + eps), dim=1))\n        L_comm = torch.mean(torch.sum(z ** 2, dim=1)) / z.shape[1]\n\n        # simple dimension-aware quantum proxy\n        L_quantum = torch.tensor(float(z.shape[1]) * 0.5, dtype=torch.float32, device=z.device)\n\n        L_total = (\n            L_local\n            + self.beta_cloud * L_cloud\n            + self.lambda_exit * L_exit\n            + self.lambda_comm * L_comm\n            + self.lambda_quantum * L_quantum\n        )\n\n        return L_total, {\n            \"L_local\": float(L_local.detach().cpu()),\n            \"L_cloud\": float(L_cloud.detach().cpu()),\n            \"L_exit\": float(L_exit.detach().cpu()),\n            \"L_comm\": float(L_comm.detach().cpu()),\n        }\n\ndef evaluate_local(loader, f_e, g_e):\n    f_e.eval()\n    g_e.eval()\n\n    ys, preds, confs = [], [], []\n\n    with torch.no_grad():\n        for xb, yb in loader:\n            xb = xb.to(DEVICE)\n            yb = remap_y(yb)\n\n            h = f_e(xb)\n            logits, probs = g_e(h)\n\n            preds.extend(probs.argmax(1).cpu().numpy())\n            confs.extend(probs.max(1).values.cpu().numpy())\n            ys.extend(yb.cpu().numpy())\n\n    return {\n        \"acc\": accuracy_score_np(ys, preds),\n        \"y\": np.array(ys),\n        \"pred\": np.array(preds),\n        \"conf\": np.array(confs),\n    }\n\ndef train_edge_modules(tr_loader, va_loader, dim=2):\n    f_e = EdgeBackbone().to(DEVICE)\n    g_e = EarlyExitHead().to(DEVICE)\n    B   = BottleneckModule(dim).to(DEVICE)\n    Cc  = CloudSurrogateHead(dim).to(DEVICE)\n\n    crit = JointLoss(\n        beta_cloud=CFG[\"beta_cloud\"],\n        lambda_exit=CFG[\"lambda_exit\"],\n        lambda_comm=CFG[\"lambda_comm\"],\n        lambda_quantum=CFG[\"lambda_quantum\"],\n    )\n\n    opt = optim.Adam(\n        list(f_e.parameters()) + list(g_e.parameters()) + list(B.parameters()) + list(Cc.parameters()),\n        lr=CFG[\"edge_lr\"]\n    )\n\n    print_subheader(\"Phase 1 — Edge training (f_e + g + B + surrogate cloud head)\")\n    print(\n        f\"Joint loss = L_local + {CFG['beta_cloud']}·L_cloud + \"\n        f\"{CFG['lambda_exit']}·L_exit + {CFG['lambda_comm']}·L_comm + {CFG['lambda_quantum']}·L_quantum\"\n    )\n\n    best_state = None\n    best_val = -1\n\n    for ep in range(CFG[\"edge_epochs\"]):\n        f_e.train(); g_e.train(); B.train(); Cc.train()\n        n_seen = 0\n        logs = {\"loss\": 0.0, \"L_local\": 0.0, \"L_cloud\": 0.0, \"L_exit\": 0.0, \"L_comm\": 0.0}\n\n        for xb, yb in tr_loader:\n            xb = xb.to(DEVICE)\n            yb = remap_y(yb).to(DEVICE)\n\n            h = f_e(xb)\n            local_logits, local_probs = g_e(h)\n            z = B(h)\n            cloud_logits = Cc(z)\n\n            loss, parts = crit(local_logits, local_probs, cloud_logits, z, yb)\n\n            opt.zero_grad()\n            loss.backward()\n            opt.step()\n\n            bs = xb.size(0)\n            n_seen += bs\n            logs[\"loss\"] += float(loss.detach().cpu()) * bs\n            for k in parts:\n                logs[k] += parts[k] * bs\n\n        val_local = evaluate_local(va_loader, f_e, g_e)[\"acc\"]\n        if val_local > best_val:\n            best_val = val_local\n            best_state = {\n                \"f_e\": copy.deepcopy(f_e.state_dict()),\n                \"g_e\": copy.deepcopy(g_e.state_dict()),\n                \"B\": copy.deepcopy(B.state_dict()),\n                \"Cc\": copy.deepcopy(Cc.state_dict()),\n            }\n\n        if (ep + 1) % 2 == 0 or ep == 0:\n            print(\n                f\"Ep {ep+1:>2}/{CFG['edge_epochs']}  \"\n                f\"L_total={logs['loss']/n_seen:.4f}  \"\n                f\"L_local={logs['L_local']/n_seen:.4f}  \"\n                f\"L_cloud={logs['L_cloud']/n_seen:.4f}  \"\n                f\"L_exit={logs['L_exit']/n_seen:.4f}  \"\n                f\"L_comm={logs['L_comm']/n_seen:.4f}  \"\n                f\"ValLocalAcc={val_local:.4f}\"\n            )\n\n    f_e.load_state_dict(best_state[\"f_e\"])\n    g_e.load_state_dict(best_state[\"g_e\"])\n    B.load_state_dict(best_state[\"B\"])\n    Cc.load_state_dict(best_state[\"Cc\"])\n\n    return f_e, g_e, B, Cc\n\ndef extract_all_arrays(loader, f_e, g_e, B, Cc):\n    f_e.eval(); g_e.eval(); B.eval(); Cc.eval()\n\n    Y = []\n    local_pred = []\n    local_conf = []\n    Z = []\n    classical_cloud_pred = []\n    classical_cloud_prob = []\n\n    with torch.no_grad():\n        for xb, yb in loader:\n            xb = xb.to(DEVICE)\n            yb = remap_y(yb)\n\n            h = f_e(xb)\n            local_logits, local_probs = g_e(h)\n            z = B(h)\n            c_logits = Cc(z)\n            c_prob = torch.softmax(c_logits, dim=1)\n\n            Y.extend(yb.cpu().numpy())\n            local_pred.extend(local_probs.argmax(1).cpu().numpy())\n            local_conf.extend(local_probs.max(1).values.cpu().numpy())\n\n            Z.append(z.cpu().numpy())\n            classical_cloud_pred.extend(c_prob.argmax(1).cpu().numpy())\n            classical_cloud_prob.extend(c_prob[:, 1].cpu().numpy())\n\n    return {\n        \"y\": np.array(Y),\n        \"local_pred\": np.array(local_pred),\n        \"local_conf\": np.array(local_conf),\n        \"Z\": np.concatenate(Z, axis=0),\n        \"classical_cloud_pred\": np.array(classical_cloud_pred),\n        \"classical_cloud_prob1\": np.array(classical_cloud_prob),\n    }\n\nprint(\"Cell 3 ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:53:56.395922Z","iopub.execute_input":"2026-04-13T18:53:56.396745Z","iopub.status.idle":"2026-04-13T18:53:56.423827Z","shell.execute_reply.started":"2026-04-13T18:53:56.396712Z","shell.execute_reply":"2026-04-13T18:53:56.423144Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cell 4 — Native Clifford+T quantum tail training and constrained τ selection","metadata":{}},{"cell_type":"code","source":"# ==================================================================\n# Cell 4 — Native Clifford+T tail fit + constrained adaptive tau\n# ==================================================================\n\nSINGLE_GATES = [\"I\", \"H\", \"X\", \"Z\", \"P\", \"T\", \"t\"]\n\ndef block_template_to_layers(template):\n    \"\"\"\n    template = [(g0, g1, ent), ...]\n    Produces exact native layers understood by both:\n      - fast plaintext evaluator\n      - encrypted EPR-QHE evaluator\n    \"\"\"\n    layers = []\n    for g0, g1, ent in template:\n        layers.append(g0 + g1)   # 2 single-qubit gates on the 2 wires\n        if ent:\n            layers.append(\"CNOT\")\n    return layers\n\ndef quantum_probs_native(Z, circ, noise_p=0.0):\n    out = []\n    for z in Z:\n        psi = encode_state_angle(z[:2], 2)\n        rho = fast_eval_circ(circ, psi, noise_p=noise_p)\n        out.append(p_zero_q0(rho))\n    return np.array(out)\n\ndef quantum_predict_native(Z, circ, noise_p=0.0):\n    return (quantum_probs_native(Z, circ, noise_p=noise_p) > 0.5).astype(int)\n\ndef score_circuit(Ztr, Ytr, Zva, Yva, circ, cost_penalty=0.002):\n    tr_pred = quantum_predict_native(Ztr, circ)\n    va_pred = quantum_predict_native(Zva, circ)\n\n    tr_acc = accuracy_score_np(Ytr, tr_pred)\n    va_acc = accuracy_score_np(Yva, va_pred)\n\n    res = count_circuit_resources(circ)\n    score = va_acc - cost_penalty * res[\"q_cost\"]\n    return score, tr_acc, va_acc, res\n\ndef fit_native_clifford_t_tail(Ztr, Ytr, Zva, Yva, n_blocks=3, passes=2):\n    \"\"\"\n    Greedy coordinate search over exact gate alphabet.\n    This removes the old fatal mismatch:\n      previous: train arbitrary VQC -> transpile -> snap -> changed model\n      now:      train directly in the exact native encrypted gate family\n    \"\"\"\n\n    # speed caps\n    Ztr = Ztr[:CFG[\"fit_train_cap\"]]\n    Ytr = Ytr[:CFG[\"fit_train_cap\"]]\n    Zva = Zva[:CFG[\"fit_val_cap\"]]\n    Yva = Yva[:CFG[\"fit_val_cap\"]]\n\n    template = [(\"I\", \"I\", False) for _ in range(n_blocks)]\n    best_circ = block_template_to_layers(template)\n    best_score, best_tr, best_va, best_res = score_circuit(\n        Ztr, Ytr, Zva, Yva, best_circ, CFG[\"native_cost_penalty\"]\n    )\n\n    print_subheader(\"Phase 2 — Native Clifford+T quantum tail search\")\n    print(f\"Init score={best_score:.4f}  tr_acc={best_tr:.4f}  va_acc={best_va:.4f}  q_cost={best_res['q_cost']:.3f}\")\n\n    for p in range(passes):\n        improved = False\n        for i in range(n_blocks):\n            cur_best = None\n\n            for g0 in SINGLE_GATES:\n                for g1 in SINGLE_GATES:\n                    for ent in [False, True]:\n                        cand = template.copy()\n                        cand[i] = (g0, g1, ent)\n                        circ = block_template_to_layers(cand)\n\n                        sc, tr_acc, va_acc, res = score_circuit(\n                            Ztr, Ytr, Zva, Yva, circ, CFG[\"native_cost_penalty\"]\n                        )\n\n                        if (cur_best is None) or (sc > cur_best[\"score\"]):\n                            cur_best = {\n                                \"template\": cand,\n                                \"circ\": circ,\n                                \"score\": sc,\n                                \"tr_acc\": tr_acc,\n                                \"va_acc\": va_acc,\n                                \"res\": res,\n                            }\n\n            if cur_best[\"score\"] > best_score:\n                template = cur_best[\"template\"]\n                best_circ = cur_best[\"circ\"]\n                best_score = cur_best[\"score\"]\n                best_tr = cur_best[\"tr_acc\"]\n                best_va = cur_best[\"va_acc\"]\n                best_res = cur_best[\"res\"]\n                improved = True\n\n                print(\n                    f\"Pass {p+1}, block {i+1}: improved -> \"\n                    f\"score={best_score:.4f}  tr_acc={best_tr:.4f}  va_acc={best_va:.4f}  \"\n                    f\"T/Tdg={best_res['nT']}/{best_res['nTd']}  depth={best_res['depth']}\"\n                )\n\n        if not improved:\n            break\n\n    return {\n        \"template\": template,\n        \"circ\": best_circ,\n        \"train_acc_cap\": best_tr,\n        \"val_acc_cap\": best_va,\n        \"resources\": best_res,\n    }\n\ndef select_tau_constrained(local_conf, local_pred, cloud_pred, y_true,\n                           grid=None,\n                           min_cloud_frac=0.05,\n                           max_cloud_frac=0.30,\n                           target_cloud_frac=0.15):\n    \"\"\"\n    Fixes the old bad behavior:\n      previous tau optimization chose tau=0.50 and offloaded nothing.\n      new tau selection must preserve meaningful cloud usage.\n    \"\"\"\n    if grid is None:\n        grid = CFG[\"tau_grid\"]\n\n    rows = []\n    for tau in grid:\n        exit_mask = local_conf >= tau\n        cloud_frac = float(np.mean(~exit_mask))\n        comb_pred = np.where(exit_mask, local_pred, cloud_pred)\n        acc = accuracy_score_np(y_true, comb_pred)\n        rows.append((tau, acc, 1 - cloud_frac, cloud_frac))\n\n    df = pd.DataFrame(rows, columns=[\"tau\", \"acc\", \"exit_rate\", \"cloud_frac\"])\n\n    feasible = df[(df[\"cloud_frac\"] >= min_cloud_frac) & (df[\"cloud_frac\"] <= max_cloud_frac)].copy()\n\n    if len(feasible) > 0:\n        feasible[\"priority\"] = feasible[\"acc\"]\n        best = feasible.sort_values([\"priority\", \"tau\"], ascending=[False, True]).iloc[0]\n        reason = \"best feasible accuracy under cloud-usage constraint\"\n    else:\n        df[\"dist\"] = np.abs(df[\"cloud_frac\"] - target_cloud_frac)\n        best = df.sort_values([\"dist\", \"acc\"], ascending=[True, False]).iloc[0]\n        reason = \"closest-to-target cloud usage fallback\"\n\n    return float(best[\"tau\"]), df.sort_values(\"tau\").reset_index(drop=True), reason\n\ndef validate_exact_alignment(Z, circ, n_check=12, noise_p=0.0):\n    \"\"\"\n    Since the tail is trained directly in the exact native gate set,\n    plaintext and encrypted paths should match exactly at noise=0.\n    \"\"\"\n    n_check = min(n_check, len(Z))\n    rows = []\n\n    for i in range(n_check):\n        z = Z[i]\n\n        psi = encode_state_angle(z[:2], 2)\n        rho_plain = fast_eval_circ(circ, psi, noise_p=noise_p)\n        plain_prob = p_zero_q0(rho_plain)\n        plain_pred = classify_rho(rho_plain)\n\n        psi_enc, a_keys, b_keys = sender_prepare_and_encrypt(z)\n        rho_eval, clist = cloud_encrypted_infer(psi_enc, circ, noise_p=noise_p)\n        enc_pred = receiver_decrypt_and_classify(rho_eval, circ, a_keys, b_keys, clist, noise_p=noise_p)\n\n        rows.append({\n            \"idx\": i + 1,\n            \"z0\": float(z[0]),\n            \"z1\": float(z[1]),\n            \"plain_prob\": plain_prob,\n            \"plain_pred\": int(plain_pred),\n            \"enc_pred\": int(enc_pred),\n            \"match\": int(plain_pred == enc_pred),\n        })\n\n    df = pd.DataFrame(rows)\n    return df, float(df[\"match\"].mean())\n\nprint(\"Cell 4 ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:54:22.729285Z","iopub.execute_input":"2026-04-13T18:54:22.729622Z","iopub.status.idle":"2026-04-13T18:54:22.750298Z","shell.execute_reply.started":"2026-04-13T18:54:22.729535Z","shell.execute_reply":"2026-04-13T18:54:22.749585Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Cell 5 — Baselines, proposed pipeline, and ablations","metadata":{}},{"cell_type":"code","source":"# ===========================================================\n# Cell 5 — Baselines, proposed pipeline, and ablation helpers\n# ===========================================================\n\ndef classical_tail_predict_from_z(Z, Cc):\n    Cc.eval()\n    with torch.no_grad():\n        x = torch.tensor(Z, dtype=torch.float32, device=DEVICE)\n        logits = Cc(x)\n        prob = torch.softmax(logits, dim=1).cpu().numpy()\n    return prob.argmax(1), prob[:, 1]\n\ndef eval_full_local(y, local_pred):\n    return {\n        \"name\": \"B1 Full local inference\",\n        \"acc\": accuracy_score_np(y, local_pred),\n        \"bits_samp\": 0,\n        \"q_cost\": 0.0,\n        \"cloud_frac\": 0.0,\n    }\n\ndef eval_full_cloud_classical(y, classical_cloud_pred, Z):\n    return {\n        \"name\": \"B2 Full cloud inference (classical tail)\",\n        \"acc\": accuracy_score_np(y, classical_cloud_pred),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": 0.0,\n        \"cloud_frac\": 1.0,\n    }\n\ndef eval_plaintext_split_classical(y, local_conf, local_pred, classical_cloud_pred, tau, Z):\n    exit_mask = local_conf >= tau\n    final_pred = np.where(exit_mask, local_pred, classical_cloud_pred)\n    return {\n        \"name\": \"B3 Plaintext split inference (classical tail)\",\n        \"acc\": accuracy_score_np(y, final_pred),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": 0.0,\n        \"cloud_frac\": float(np.mean(~exit_mask)),\n    }\n\ndef eval_encrypted_split_classical(y, local_conf, local_pred, Z, Cc, tau):\n    \"\"\"\n    Transport-encrypted classical split baseline:\n    encrypt for transport, decrypt before classical cloud tail.\n    \"\"\"\n    exit_mask = local_conf >= tau\n    preds = []\n\n    for i, z in enumerate(Z):\n        if exit_mask[i]:\n            preds.append(int(local_pred[i]))\n            continue\n\n        ct, key = classical_transport_encrypt(z)\n        z_dec = classical_transport_decrypt(ct, key, z.shape[0])\n\n        with torch.no_grad():\n            logits = Cc(torch.tensor(z_dec[None, :], dtype=torch.float32, device=DEVICE))\n            p = torch.softmax(logits, dim=1).argmax(1).item()\n        preds.append(int(p))\n\n    preds = np.array(preds)\n    return {\n        \"name\": \"B4 Encrypted split inference (classical tail)\",\n        \"acc\": accuracy_score_np(y, preds),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": 0.0,\n        \"cloud_frac\": float(np.mean(~exit_mask)),\n    }\n\ndef eval_hybrid_quantum_no_early_exit(y, Z, circ):\n    pred = quantum_predict_native(Z, circ, noise_p=CFG[\"noise_p\"])\n    res = count_circuit_resources(circ)\n    return {\n        \"name\": \"B5 Hybrid classical-quantum without early exit\",\n        \"acc\": accuracy_score_np(y, pred),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": res[\"q_cost\"],\n        \"cloud_frac\": 1.0,\n    }\n\ndef eval_adaptive_quantum_without_encryption(y, local_conf, local_pred, Z, circ, tau):\n    exit_mask = local_conf >= tau\n    cloud_pred = quantum_predict_native(Z, circ, noise_p=CFG[\"noise_p\"])\n    final_pred = np.where(exit_mask, local_pred, cloud_pred)\n    res = count_circuit_resources(circ)\n    return {\n        \"name\": \"B6 Adaptive hybrid inference without encryption\",\n        \"acc\": accuracy_score_np(y, final_pred),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": res[\"q_cost\"],\n        \"cloud_frac\": float(np.mean(~exit_mask)),\n    }\n\ndef eval_proposed_encrypted_quantum(y, local_conf, local_pred, Z, circ, tau, noise_p=0.0):\n    exit_mask = local_conf >= tau\n    preds = []\n    t_enc, t_cloud, t_dec = [], [], []\n\n    for i, z in enumerate(Z):\n        if exit_mask[i]:\n            preds.append(int(local_pred[i]))\n            continue\n\n        t0 = time.time()\n        psi_enc, a_keys, b_keys = sender_prepare_and_encrypt(z)\n        t1 = time.time()\n\n        rho_eval, clist = cloud_encrypted_infer(psi_enc, circ, noise_p=noise_p)\n        t2 = time.time()\n\n        pred = receiver_decrypt_and_classify(rho_eval, circ, a_keys, b_keys, clist, noise_p=noise_p)\n        t3 = time.time()\n\n        preds.append(int(pred))\n        t_enc.append(t1 - t0)\n        t_cloud.append(t2 - t1)\n        t_dec.append(t3 - t2)\n\n    preds = np.array(preds)\n    res = count_circuit_resources(circ)\n\n    return {\n        \"name\": \"Proposed adaptive hybrid secure quantum inference\",\n        \"acc\": accuracy_score_np(y, preds),\n        \"bits_samp\": count_payload_bits_from_z(Z[0]),\n        \"q_cost\": res[\"q_cost\"],\n        \"cloud_frac\": float(np.mean(~exit_mask)),\n        \"enc_time_mean\": float(np.mean(t_enc)) if len(t_enc) else 0.0,\n        \"cloud_time_mean\": float(np.mean(t_cloud)) if len(t_cloud) else 0.0,\n        \"dec_time_mean\": float(np.mean(t_dec)) if len(t_dec) else 0.0,\n    }\n\ndef run_all_baselines(y, local_conf, local_pred, classical_cloud_pred, Z, Cc, circ, tau):\n    rows = []\n    rows.append(eval_full_local(y, local_pred))\n    rows.append(eval_full_cloud_classical(y, classical_cloud_pred, Z))\n    rows.append(eval_plaintext_split_classical(y, local_conf, local_pred, classical_cloud_pred, tau, Z))\n    rows.append(eval_encrypted_split_classical(y, local_conf, local_pred, Z, Cc, tau))\n    rows.append(eval_hybrid_quantum_no_early_exit(y, Z, circ))\n    rows.append(eval_adaptive_quantum_without_encryption(y, local_conf, local_pred, Z, circ, tau))\n    rows.append(eval_proposed_encrypted_quantum(y, local_conf, local_pred, Z, circ, tau, noise_p=CFG[\"noise_p\"]))\n    return pd.DataFrame(rows)\n\n# ---------- ablations ----------\ndef ablate_tau(y, local_conf, local_pred, cloud_pred, grid):\n    rows = []\n    for tau in grid:\n        exit_mask = local_conf >= tau\n        final_pred = np.where(exit_mask, local_pred, cloud_pred)\n        rows.append({\n            \"tau\": tau,\n            \"exit_rate\": float(np.mean(exit_mask)),\n            \"cloud_frac\": float(np.mean(~exit_mask)),\n            \"combined_acc\": accuracy_score_np(y, final_pred),\n            \"local_acc_on_exits\": accuracy_score_np(y[exit_mask], local_pred[exit_mask]) if np.any(exit_mask) else np.nan,\n        })\n    return pd.DataFrame(rows)\n\ndef ablate_noise(y, Z, circ, n=20):\n    rows = []\n    n = min(n, len(Z))\n    Zs = Z[:n]\n    Ys = y[:n]\n    for p in [0.0, 0.005, 0.01, 0.02, 0.05]:\n        plain = []\n        enc = []\n        for i, z in enumerate(Zs):\n            psi = encode_state_angle(z[:2], 2)\n            rho_plain = fast_eval_circ(circ, psi, noise_p=p)\n            plain.append(classify_rho(rho_plain))\n\n            psi_enc, a_keys, b_keys = sender_prepare_and_encrypt(z)\n            rho_eval, clist = cloud_encrypted_infer(psi_enc, circ, noise_p=p)\n            enc.append(receiver_decrypt_and_classify(rho_eval, circ, a_keys, b_keys, clist, noise_p=p))\n\n        plain = np.array(plain)\n        enc = np.array(enc)\n\n        rows.append({\n            \"noise_p\": p,\n            \"plain_acc\": accuracy_score_np(Ys, plain),\n            \"enc_acc\": accuracy_score_np(Ys, enc),\n            \"enc_plain_match\": accuracy_score_np(plain, enc),\n        })\n    return pd.DataFrame(rows)\n\nprint(\"Cell 5 ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:55:43.380523Z","iopub.execute_input":"2026-04-13T18:55:43.381383Z","iopub.status.idle":"2026-04-13T18:55:43.403791Z","shell.execute_reply.started":"2026-04-13T18:55:43.381346Z","shell.execute_reply":"2026-04-13T18:55:43.403086Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# #Cell 6 — Main runner that fixes the old failures and prints the final report ","metadata":{}},{"cell_type":"code","source":"# ======================================\n# Cell 6 — Main runner / final pipeline\n# ======================================\n\nprint_header(\"Adaptive Hybrid Classical-Quantum Secure Inference — FIXED VERSION\")\nprint(\"Major fixes in this version:\")\nprint(\"1) B is explicitly supervised through a surrogate cloud head, so the bottleneck keeps class signal.\")\nprint(\"2) The quantum tail is trained directly in the exact native Clifford+T gate family.\")\nprint(\"3) No post-training angle snapping or semantic-changing transpile approximation is used.\")\nprint(\"4) Tau selection is constrained so the cloud branch is actually used.\")\nprint(\"5) The PDF baseline suite is expanded to 6 baselines plus the proposed model.\")\n\n# ---------- load data ----------\ntr_loader, va_loader, te_loader = load_cifar10_binary(\n    n_train=CFG[\"n_train\"],\n    n_val=CFG[\"n_val\"],\n    n_test=CFG[\"n_test\"],\n    batch_size=CFG[\"batch_size\"],\n)\n\n# ---------- train edge modules ----------\nf_e, g_e, B, Cc = train_edge_modules(tr_loader, va_loader, dim=CFG[\"bottleneck_dim\"])\n\n# ---------- extract arrays ----------\ntr_arr = extract_all_arrays(tr_loader, f_e, g_e, B, Cc)\nva_arr = extract_all_arrays(va_loader, f_e, g_e, B, Cc)\nte_arr = extract_all_arrays(te_loader, f_e, g_e, B, Cc)\n\nprint_subheader(\"Edge-side summary\")\nprint(f\"Train local acc = {accuracy_score_np(tr_arr['y'], tr_arr['local_pred']):.4f}\")\nprint(f\"Val   local acc = {accuracy_score_np(va_arr['y'], va_arr['local_pred']):.4f}\")\nprint(f\"Test  local acc = {accuracy_score_np(te_arr['y'], te_arr['local_pred']):.4f}\")\nprint(f\"Bottleneck dim  = {CFG['bottleneck_dim']}  -> comm cost = {count_payload_bits_from_z(te_arr['Z'][0])} bits/sample\")\n\n# ---------- train exact native quantum tail ----------\ntail = fit_native_clifford_t_tail(\n    tr_arr[\"Z\"], tr_arr[\"y\"],\n    va_arr[\"Z\"], va_arr[\"y\"],\n    n_blocks=CFG[\"n_native_blocks\"],\n    passes=CFG[\"native_search_passes\"],\n)\ncirc = tail[\"circ\"]\nqres = tail[\"resources\"]\n\nprint_subheader(\"Native quantum tail summary\")\nprint(f\"Exact native circuit = {circ}\")\nprint(f\"Train-cap acc        = {tail['train_acc_cap']:.4f}\")\nprint(f\"Val-cap acc          = {tail['val_acc_cap']:.4f}\")\nprint(f\"T / Tdg              = {qres['nT']} / {qres['nTd']}\")\nprint(f\"EPR pairs            = {qres['nEPR']}\")\nprint(f\"Depth                = {qres['depth']}\")\nprint(f\"Wires                = {qres['wires']}\")\nprint(f\"Quantum cost         = {qres['q_cost']:.3f}\")\n\n# ---------- test-set quantum predictions ----------\nte_q_pred = quantum_predict_native(te_arr[\"Z\"], circ, noise_p=CFG[\"noise_p\"])\nte_q_acc = accuracy_score_np(te_arr[\"y\"], te_q_pred)\n\nprint_subheader(\"Quantum tail test performance\")\nprint(f\"Test quantum-tail acc = {te_q_acc:.4f}\")\n\n# ---------- constrained tau selection ----------\ntau, tau_df, tau_reason = select_tau_constrained(\n    va_arr[\"local_conf\"],\n    va_arr[\"local_pred\"],\n    quantum_predict_native(va_arr[\"Z\"], circ, noise_p=CFG[\"noise_p\"]),\n    va_arr[\"y\"],\n    grid=CFG[\"tau_grid\"],\n    min_cloud_frac=CFG[\"min_cloud_frac\"],\n    max_cloud_frac=CFG[\"max_cloud_frac\"],\n    target_cloud_frac=CFG[\"target_cloud_frac\"],\n)\n\nprint_subheader(\"Adaptive tau selection\")\nprint(tau_df.to_string(index=False))\nprint(f\"\\nSelected tau = {tau:.3f}  ({tau_reason})\")\n\n# ---------- exact alignment validation ----------\nalign_df, match_rate = validate_exact_alignment(\n    te_arr[\"Z\"], circ, n_check=CFG[\"validate_n\"], noise_p=0.0\n)\n\nprint_subheader(\"Exact alignment validation — plaintext vs encrypted\")\nprint(align_df.to_string(index=False))\nprint(f\"\\nExact plain/encrypted match rate = {match_rate:.4f}\")\n\nif match_rate < 0.999:\n    raise RuntimeError(\n        f\"Exact alignment failed: plain/encrypted match rate = {match_rate:.4f}. \"\n        f\"This version is designed to make that value ~1.0 at noise=0.\"\n    )\n\n# ---------- baselines + proposed ----------\nbaseline_df = run_all_baselines(\n    y=te_arr[\"y\"],\n    local_conf=te_arr[\"local_conf\"],\n    local_pred=te_arr[\"local_pred\"],\n    classical_cloud_pred=te_arr[\"classical_cloud_pred\"],\n    Z=te_arr[\"Z\"],\n    Cc=Cc,\n    circ=circ,\n    tau=tau,\n)\n\nprint_subheader(\"Baseline comparison + proposed\")\nprint(baseline_df.to_string(index=False))\n\n# ---------- ablations ----------\ntau_ab_df = ablate_tau(\n    y=te_arr[\"y\"],\n    local_conf=te_arr[\"local_conf\"],\n    local_pred=te_arr[\"local_pred\"],\n    cloud_pred=te_q_pred,\n    grid=CFG[\"tau_grid\"],\n)\n\nnoise_ab_df = ablate_noise(\n    y=te_arr[\"y\"],\n    Z=te_arr[\"Z\"],\n    circ=circ,\n    n=20,\n)\n\nprint_subheader(\"Ablation A1 — tau\")\nprint(tau_ab_df.to_string(index=False))\n\nprint_subheader(\"Ablation A2 — noise sensitivity\")\nprint(noise_ab_df.to_string(index=False))\n\n# ---------- final concise summary ----------\nproposed_row = baseline_df[baseline_df[\"name\"] == \"Proposed adaptive hybrid secure quantum inference\"].iloc[0]\n\nprint_header(\"Final summary\")\nprint(f\"Local test accuracy                    : {accuracy_score_np(te_arr['y'], te_arr['local_pred']):.4f}\")\nprint(f\"Quantum tail test accuracy             : {te_q_acc:.4f}\")\nprint(f\"Selected tau                           : {tau:.3f}\")\nprint(f\"Proposed model test accuracy           : {proposed_row['acc']:.4f}\")\nprint(f\"Proposed model cloud fraction          : {proposed_row['cloud_frac']:.4f}\")\nprint(f\"Exact plain/encrypted match rate       : {match_rate:.4f}\")\nprint(f\"Communication cost                     : {count_payload_bits_from_z(te_arr['Z'][0])} bits/sample\")\nprint(f\"Quantum cost                           : {qres['q_cost']:.3f}\")\nprint(f\"Encryption time (mean, cloud samples)  : {proposed_row['enc_time_mean']:.6f}s\")\nprint(f\"Cloud eval time (mean, cloud samples)  : {proposed_row['cloud_time_mean']:.6f}s\")\nprint(f\"Decryption time (mean, cloud samples)  : {proposed_row['dec_time_mean']:.6f}s\")\n\nprint(\"\\nDone.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-13T18:56:15.475247Z","iopub.execute_input":"2026-04-13T18:56:15.47611Z","iopub.status.idle":"2026-04-13T19:00:09.079221Z","shell.execute_reply.started":"2026-04-13T18:56:15.476079Z","shell.execute_reply":"2026-04-13T19:00:09.078442Z"}},"outputs":[],"execution_count":null}]}