diff --git a/MyIA.AI.Notebooks/Complexity/Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb b/MyIA.AI.Notebooks/Complexity/Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb new file mode 100644 index 0000000000..8e84088285 --- /dev/null +++ b/MyIA.AI.Notebooks/Complexity/Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb @@ -0,0 +1,1685 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "9cce5037", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.840528Z", + "iopub.status.busy": "2026-09-20T20:33:13.840302Z", + "iopub.status.idle": "2026-09-20T20:33:13.844292Z", + "shell.execute_reply": "2026-09-20T20:33:13.843787Z" + }, + "papermill": { + "duration": 0.008287, + "end_time": "2026-09-20T20:33:13.844893+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.836606+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "# Parameters\n", + "BATCH_MODE = \"true\"\n" + ] + }, + { + "cell_type": "markdown", + "id": "85910518", + "metadata": { + "papermill": { + "duration": 0.002448, + "end_time": "2026-09-20T20:33:13.849593+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.847145+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "# Complexity-04 — Déquantifier les suprématies\n", + " : Gottesman–Knill comme arbitre exécutable (Aaronson–Gottesman, 2004)" + ] + }, + { + "cell_type": "markdown", + "id": "feacb926", + "metadata": { + "papermill": { + "duration": 0.00228, + "end_time": "2026-09-20T20:33:13.854389+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.852109+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 0. Pourquoi ce notebook\n", + "\n", + "Ce notebook est le quatrième de la série\n", + "([01 — Hartmanis-Stearns](Complexity-01-HartmanisStearns-TimeHierarchy.ipynb),\n", + "[02 — conjectures online](Complexity-02-OnlineConjectures-Secretary-KServer.ipynb),\n", + "[03 — Aaronson–Arkhipov](Complexity-03-AaronsonArkhipov-PermanenteBosonSampling.ipynb)) :\n", + "un texte fondateur, **fait tourner**. Le geste propre à cette veine est que la thèse\n", + "**s'exécute**. Quand une « suprématie quantique » est annoncée, la question qui installe\n", + "ou réfute la claim n'est pas « est-ce quantique ? » mais « **dans quelle classe de\n", + "circuits ?** ». Gottesman–Knill (1998) : tout circuit de portes de Clifford\n", + "($H$, $S$, $\\mathsf{CNOT}$, mesures) se simule en temps **polynomial classique** —\n", + "l'avantage quantique ne peut pas vivre là. Aaronson–Gottesman (2004) en donnent l'organe :\n", + "l'algorithme CHP, $O(n^2)$ par porte sur un tableau de bits, sans jamais écrire une\n", + "amplitude. Nous allons donc :\n", + "\n", + "1. **exécuter le formalisme stabilisateur** : le tableau de Pauli d'Aaronson–Gottesman,\n", + " avec sa table de phase et ses règles de conjugaison **dérivées des matrices\n", + " $2\\times2$** — jamais recopiées d'une table magique ;\n", + "2. **construire les deux machines** : l'arbitre polynomial (port CHP) et l'état complet\n", + " $2^n$, puis les **croiser** sur des circuits Clifford aléatoires — les valeurs\n", + " propres du groupe stabilisateur, calculées indépendamment par les deux machines,\n", + " doivent coïncider **exactement** ; le simulateur de référence du métier\n", + " ([stim](https://github.com/quantumlib/Stim), Gidney 2021) sert de troisième juge\n", + " quand il est installé ;\n", + "3. **mesurer le régime, pas l'objet** : à circuits Clifford purs, l'écart de coût entre\n", + " l'état complet et l'arbitre double à chaque qubit ; une **seule** porte $T$ ferme\n", + " l'arbitre — et ferme aussi stim. La suprématie est une assertion sur un *régime*,\n", + " pas sur un objet : c'est la leçon de méthode que ce banc rend mesurable.\n", + "\n", + "L'encart de cadrage, sans mesure : les épisodes contemporains de réfutation de claims\n", + "de suprématie (RCS, GBS) relèvent ici exactement de la **leçon de méthode** — comment on\n", + "réfute proprement, en exhibant le régime où la claim casse. Ce notebook ne prend parti\n", + "pour aucun verdict : il mesure deux régimes sur des instances bornées, et chacun peut\n", + "réexécuter le banc." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "6354b963", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.860125Z", + "iopub.status.busy": "2026-09-20T20:33:13.859889Z", + "iopub.status.idle": "2026-09-20T20:33:13.911308Z", + "shell.execute_reply": "2026-09-20T20:33:13.910712Z" + }, + "papermill": { + "duration": 0.055346, + "end_time": "2026-09-20T20:33:13.912010+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.856664+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "import math\n", + "import time\n", + "\n", + "import numpy as np\n", + "\n", + "np.set_printoptions(precision=4, suppress=True)\n", + "\n", + "# Toutes les experiences sont seedees : reexecuter redonne les memes nombres.\n", + "# Les chronometres sont des medianes sur 3 seeds (machine partagee, cf. §4).\n", + "GLOBAL_SEEDS = (0, 1, 7)\n" + ] + }, + { + "cell_type": "markdown", + "id": "4de4a992", + "metadata": { + "papermill": { + "duration": 0.00187, + "end_time": "2026-09-20T20:33:13.915943+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.914073+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 1. Le formalisme stabilisateur, définition exécutée\n", + "\n", + "Un état stabilisateur est l'unique vecteur propre $+1$ commun de $n$ chaînes de Pauli\n", + "indépendantes qui commutent — ses **stabilisateurs**. Aaronson–Gottesman stockent le\n", + "groupe étendu : $n$ **déstabilisateurs** (les partenaires qui complètent la base\n", + "symplectique) plus $n$ stabilisateurs, chacun encodé en bits par qubit —\n", + "$I=(0,0)$, $X=(1,0)$, $Z=(0,1)$, $Y=(1,1)$, le $Y$ portant les deux bits puisque\n", + "$Y = iXZ$ — plus un bit de signe $r$ par ligne et une ligne de travail.\n", + "\n", + "Tout ce qui suit manipule des **lignes de bits** : aucune amplitude, aucun nombre\n", + "complexe hors les matrices $2\\times2$ qui servent à *prouver* les tables. C'est la\n", + "promesse polynomialiste de Gottesman–Knill, rendue littérale." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "ad6351f9", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.920608Z", + "iopub.status.busy": "2026-09-20T20:33:13.920337Z", + "iopub.status.idle": "2026-09-20T20:33:13.934870Z", + "shell.execute_reply": "2026-09-20T20:33:13.934372Z" + }, + "papermill": { + "duration": 0.01755, + "end_time": "2026-09-20T20:33:13.935329+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.917779+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Table de phase e(P1, P2) derivee des matrices (P1 lignes, P2 colonnes, I X Y Z) :\n", + "[[0 0 0 0]\n", + " [0 0 1 3]\n", + " [0 3 0 1]\n", + " [0 1 3 0]]\n", + "e = 1 le long du cycle X.Y, Y.Z, Z.X : [1, 1, 1]\n", + "e = 3 a contre-courant Y.X, Z.Y, X.Z : [3, 3, 3]\n" + ] + } + ], + "source": [ + "MATRICES = {\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", + " \"Y\": np.array([[0, -1j], [1j, 0]], dtype=complex),\n", + "}\n", + "LETTERS = (\"I\", \"X\", \"Y\", \"Z\")\n", + "BITS = {\"I\": (0, 0), \"X\": (1, 0), \"Z\": (0, 1), \"Y\": (1, 1)}\n", + "\n", + "\n", + "def derive_phase_table():\n", + " \"\"\"Table e telle que P1 @ P2 = (i**e) * P3, construite en multipliant les\n", + " matrices 2x2 et en identifiant le produit dans la base {i**k * P}.\n", + "\n", + " Aucune valeur n'est ecrite a la main : la table se prouve par construction.\n", + " \"\"\"\n", + " table = np.zeros((4, 4), dtype=np.int8)\n", + " for i1, p1 in enumerate(LETTERS):\n", + " for i2, p2 in enumerate(LETTERS):\n", + " produit = MATRICES[p1] @ MATRICES[p2]\n", + " for k in range(4):\n", + " for i3, p3 in enumerate(LETTERS):\n", + " if np.allclose(produit, (1j ** k) * MATRICES[p3]):\n", + " table[i1, i2] = k\n", + " return table\n", + "\n", + "\n", + "E_TABLE = derive_phase_table()\n", + "\n", + "# Verifications structurelles (elles echouent brutalement si la derivation depaille)\n", + "assert (np.diag(E_TABLE) == 0).all() # P @ P = +I pour les Pauli hermitiens\n", + "for _i1 in range(4): # antisymetrie : e(P2,P1) = -e(P1,P2) mod 4\n", + " for _i2 in range(4):\n", + " assert E_TABLE[_i2, _i1] == (-E_TABLE[_i1, _i2]) % 4\n", + "assert (E_TABLE[0] == 0).all() and (E_TABLE[:, 0] == 0).all() # identite neutre\n", + "\n", + "print(\"Table de phase e(P1, P2) derivee des matrices (P1 lignes, P2 colonnes, I X Y Z) :\")\n", + "print(E_TABLE)\n", + "cycle = [(\"X\", \"Y\"), (\"Y\", \"Z\"), (\"Z\", \"X\")]\n", + "inverse = [(b, a) for a, b in cycle]\n", + "_idx = {p: i for i, p in enumerate(LETTERS)}\n", + "print(\"e = 1 le long du cycle X.Y, Y.Z, Z.X :\",\n", + " [int(E_TABLE[_idx[a], _idx[b]]) for a, b in cycle])\n", + "print(\"e = 3 a contre-courant Y.X, Z.Y, X.Z :\",\n", + " [int(E_TABLE[_idx[a], _idx[b]]) for a, b in inverse])\n", + "\n", + "# Bits (x, z) -> index Pauli dans l'ordre (I, X, Y, Z) de la table\n", + "_BIT_TO_INDEX = np.array([[0, 3], [1, 2]], dtype=np.int8)\n", + "\n", + "\n", + "def _pauli_letter(x, z):\n", + " return {(0, 0): \"I\", (1, 0): \"X\", (0, 1): \"Z\", (1, 1): \"Y\"}[(int(x), int(z))]\n", + "\n", + "\n", + "def _row_string(xs, zs, r):\n", + " return (\"+\" if r == 0 else \"-\") + \"\".join(\n", + " _pauli_letter(x, z) for x, z in zip(xs, zs)\n", + " )\n" + ] + }, + { + "cell_type": "markdown", + "id": "c9b991f5", + "metadata": { + "papermill": { + "duration": 0.002654, + "end_time": "2026-09-20T20:33:13.940049+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.937395+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Lecture.** La table n'est pas symétrique : $X \\cdot Y = iZ$ mais $Y \\cdot X = -iZ$.\n", + "Suivre le cycle $X \\to Y \\to Z \\to X$ coûte un facteur $i$ ($e = 1$) ; le remonter en\n", + "rend un ($e = 3$). Cette asymétrie est tout ce que la mécanique quantique ajoute au\n", + "calcul sur bits : le simulateur Clifford la trackera en **un seul bit par ligne**\n", + "($\\sum_j e_j / 2 \\bmod 2$), pas en amplitudes complexes." + ] + }, + { + "cell_type": "markdown", + "id": "5fd5078f", + "metadata": { + "papermill": { + "duration": 0.001675, + "end_time": "2026-09-20T20:33:13.943432+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.941757+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 2. Les règles de conjugaison, dérivées elles aussi\n", + "\n", + "La second endroit où se tromper en silence : l'action des portes sur les lignes du\n", + "tableau. $H$ conjugue $X \\leftrightarrow Z$ (et change le signe d'un $Y$), $S$\n", + "conjugue $X \\to Y$ (et change le signe d'un $Y$), et le $\\mathsf{CNOT}$ suit deux\n", + "xors — plus **un piège de phase** que l'on dérive au lieu de le croire : la table\n", + "complète des 16 conjugués $\\mathsf{CNOT}\\,P\\,\\mathsf{CNOT}^\\dagger$ sur la paire\n", + "(contrôle, cible), calculée matriciellement." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "a40c64b5", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.948184Z", + "iopub.status.busy": "2026-09-20T20:33:13.947974Z", + "iopub.status.idle": "2026-09-20T20:33:13.956962Z", + "shell.execute_reply": "2026-09-20T20:33:13.956518Z" + }, + "papermill": { + "duration": 0.011965, + "end_time": "2026-09-20T20:33:13.957530+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.945565+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Conjugaison CNOT (controle, cible) derivee des matrices :\n", + " XY -> +YZ\n", + " XZ -> -YY <-- signe -\n", + " YY -> -XZ <-- signe -\n", + " YZ -> +XY\n", + " ZX -> +ZX\n", + "Seuls cas a signe - : [('X', 'Z'), ('Y', 'Y')]\n" + ] + } + ], + "source": [ + "CNOT_MAT = np.array(\n", + " [[1, 0, 0, 0],\n", + " [0, 1, 0, 0],\n", + " [0, 0, 0, 1],\n", + " [0, 0, 1, 0]], dtype=complex) # base |c t>, controle = bit fort\n", + "\n", + "\n", + "def derive_cnot_rules():\n", + " \"\"\"Les 16 conjugués CNOT P CNOT, identifies par multiplication matricielle.\n", + "\n", + " Retourne la liste (P, signe, P') telle que CNOT P CNOT = signe * P'.\n", + " \"\"\"\n", + " rules = {}\n", + " for p1 in LETTERS[1:]:\n", + " for p2 in LETTERS[1:]:\n", + " P = np.kron(MATRICES[p1], MATRICES[p2])\n", + " M = CNOT_MAT @ P @ CNOT_MAT\n", + " for signe in (1, -1):\n", + " for q1 in LETTERS[1:]:\n", + " for q2 in LETTERS[1:]:\n", + " if np.allclose(M, signe * np.kron(MATRICES[q1], MATRICES[q2])):\n", + " rules[(p1, p2)] = (signe, q1, q2)\n", + " return rules\n", + "\n", + "\n", + "CNOT_RULES = derive_cnot_rules()\n", + "print(\"Conjugaison CNOT (controle, cible) derivee des matrices :\")\n", + "negatifs = []\n", + "for (p1, p2), (signe, q1, q2) in CNOT_RULES.items():\n", + " marque = \"\" if signe > 0 else \" <-- signe -\"\n", + " print(f\" {p1}{p2} -> {'+' if signe > 0 else '-'}{q1}{q2}{marque}\")\n", + " if signe < 0:\n", + " negatifs.append((p1, p2))\n", + "print(\"Seuls cas a signe - :\", negatifs)\n" + ] + }, + { + "cell_type": "markdown", + "id": "aba11bc5", + "metadata": { + "papermill": { + "duration": 0.001694, + "end_time": "2026-09-20T20:33:13.960972+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.959278+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Lecture.** Les deux xors ($x_t \\leftarrow x_t \\oplus x_c$ puis\n", + "$z_c \\leftarrow z_c \\oplus z_t$) reproduisent les **lettres** des 16 conjugués. Le\n", + "signe $-$ n'apparaît que pour $X_c Z_t$ et $Y_c Y_t$ — soit, en bits, exactement\n", + "$x_c = 1$, $z_t = 1$ et $z_c = x_t$ (les bits lus *avant* les xors). Tout le reste du\n", + "notebook applique ces règles prouvées : la seule constante de phase du formalisme est\n", + "dérivée, aucune n'est recopiée." + ] + }, + { + "cell_type": "markdown", + "id": "4b7957ff", + "metadata": { + "papermill": { + "duration": 0.001901, + "end_time": "2026-09-20T20:33:13.964932+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.963031+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 3. L'organe : le simulateur CHP, complet\n", + "\n", + "Trois pièces dans l'algorithme d'Aaronson–Gottesman :\n", + "\n", + "1. **`rowsum(h, i)`** — additionner la ligne $i$ dans la ligne $h$ : Pauli xorés,\n", + " phase accumulée par la table dérivée ci-dessus,\n", + " $r_h \\leftarrow r_i \\oplus r_h \\oplus (\\tfrac{1}{2}\\sum_j e_j \\bmod 2)$. Pour\n", + " deux lignes qui commutent (toujours le cas dans le groupe stabilisateur), la somme\n", + " $\\sum e_j$ est paire — le simulateur l'exige.\n", + "2. **Les portes** — règles de conjugaison dérivées du §2, $O(n)$ par porte. La porte\n", + " $T = \\mathrm{diag}(1, e^{i\\pi/4})$ est **refusée** : elle n'est pas Clifford, le\n", + " formalisme ne s'applique plus. Ce refus est la frontière de Gottesman–Knill — il est\n", + " pédagogique, pas accidentel.\n", + "3. **La mesure $Z_q$** — *déterministe* ssi aucun stabilisateur n'anti-commute avec\n", + " $Z_q$ : l'état est déjà propre, l'outcome se **lit** (résolution de $(0 | e_q)$\n", + " dans le span GF(2) des stabilisateurs, produit par rowsum successifs — lecture pure,\n", + " le tableau n'est pas modifié). Sinon *aléatoire* : outcome uniforme, et l'état\n", + " post-mesure est stabilisé par $(-1)^m Z_q$.\n", + "\n", + "Et en regard, la machine témoin : l'**état complet** $2^n$ — pas de classe de circuits,\n", + "il paie l'exponentielle partout, mais il simule tout, $T$ comprise. Le croisement du §4\n", + "dirige ces deux machines l'une contre l'autre." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "638a5fdc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.969597Z", + "iopub.status.busy": "2026-09-20T20:33:13.969327Z", + "iopub.status.idle": "2026-09-20T20:33:13.981130Z", + "shell.execute_reply": "2026-09-20T20:33:13.979945Z" + }, + "papermill": { + "duration": 0.015534, + "end_time": "2026-09-20T20:33:13.982117+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.966583+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "class CHPSimulator:\n", + " \"\"\"Simulateur stabilisateur d'Aaronson-Gottesman 2004 (algorithme CHP).\n", + "\n", + " Tableau : lignes 0..n-1 destabilisateurs, n..2n-1 stabilisateurs, ligne 2n scratch.\n", + " Chaque ligne : bits x[i][j], z[i][j] et bit de signe r[i]. Portes Clifford :\n", + " h, s, x, z, cnot ; mesures Z et X par qubit. Les portes non-Clifford (t) sont\n", + " refusees : c'est la frontiere de Gottesman-Knill, pas un accident.\n", + " \"\"\"\n", + "\n", + " def __init__(self, n, rng=None):\n", + " self.n = n\n", + " self.x = np.zeros((2 * n + 1, n), dtype=np.int8)\n", + " self.z = np.zeros((2 * n + 1, n), dtype=np.int8)\n", + " self.r = np.zeros(2 * n + 1, dtype=np.int8)\n", + " for j in range(n): # etat |0...0> : destabilisateur X_j, stabilisateur Z_j\n", + " self.x[j, j] = 1\n", + " self.z[n + j, j] = 1\n", + " self.rng = rng if rng is not None else np.random.default_rng(0)\n", + "\n", + " # -- affichage ---------------------------------------------------------\n", + " def stab_string(self, i):\n", + " return _row_string(self.x[self.n + i], self.z[self.n + i], self.r[self.n + i])\n", + "\n", + " def stabilizers(self):\n", + " return [\n", + " (self.x[self.n + i].copy(), self.z[self.n + i].copy(), int(self.r[self.n + i]))\n", + " for i in range(self.n)\n", + " ]\n", + "\n", + " # -- addition de lignes (AG04 fig. 2) ----------------------------------\n", + " def rowsum(self, h, i):\n", + " idx_i = _BIT_TO_INDEX[self.x[i], self.z[i]]\n", + " idx_h = _BIT_TO_INDEX[self.x[h], self.z[h]]\n", + " total_e = int(E_TABLE[idx_i, idx_h].sum())\n", + " if total_e % 2 != 0:\n", + " raise ValueError(\"rowsum sur lignes non commutantes : invariant viole\")\n", + " self.r[h] ^= int(self.r[i]) ^ ((total_e // 2) % 2)\n", + " self.x[h] ^= self.x[i]\n", + " self.z[h] ^= self.z[i]\n", + "\n", + " # -- portes Clifford (regles derivees au §2) ----------------------------\n", + " def h(self, q):\n", + " for i in range(2 * self.n + 1):\n", + " xq, zq = int(self.x[i, q]), int(self.z[i, q])\n", + " self.r[i] ^= xq & zq # H Y H = -Y\n", + " self.x[i, q], self.z[i, q] = zq, xq\n", + "\n", + " def s(self, q):\n", + " for i in range(2 * self.n + 1):\n", + " xq, zq = int(self.x[i, q]), int(self.z[i, q])\n", + " self.r[i] ^= xq & zq # S Y S+ = -X\n", + " self.z[i, q] = zq ^ xq # S X S+ = Y\n", + "\n", + " def xgate(self, q):\n", + " for i in range(2 * self.n + 1):\n", + " self.r[i] ^= int(self.z[i, q])\n", + "\n", + " def zgate(self, q):\n", + " for i in range(2 * self.n + 1):\n", + " self.r[i] ^= int(self.x[i, q])\n", + "\n", + " def cnot(self, c, t):\n", + " # signe - uniquement pour x_c=1, z_t=1, z_c == x_t (bits lus AVANT les xors)\n", + " for i in range(2 * self.n + 1):\n", + " x_c, z_c = int(self.x[i, c]), int(self.z[i, c])\n", + " x_t, z_t = int(self.x[i, t]), int(self.z[i, t])\n", + " if x_c and z_t and (z_c == x_t):\n", + " self.r[i] ^= 1\n", + " self.x[i, t] = x_t ^ x_c\n", + " self.z[i, c] = z_c ^ z_t\n", + "\n", + " def t(self, q):\n", + " raise TypeError(\n", + " f\"porte T sur le qubit {q} refusee : T n'est pas Clifford, le formalisme \"\n", + " \"stabilisateur (Gottesman-Knill) ne s'applique plus\"\n", + " )\n", + "\n", + " def apply(self, gate, *args):\n", + " return {\"h\": self.h, \"s\": self.s, \"x\": self.xgate, \"z\": self.zgate,\n", + " \"cnot\": self.cnot, \"t\": self.t}[gate](*args)\n", + "\n", + " # -- mesures ------------------------------------------------------------\n", + " def _deterministic_outcome(self, q):\n", + " \"\"\"Lecture pure de l'outcome deterministe : le tableau n'est pas modifie.\n", + "\n", + " Z_q commute avec tous les stabilisateurs, donc (-1)^r Z_q est un produit\n", + " d'entre eux. On resout (0 | e_q) dans le span GF(2) des stabs (gaussienne sur\n", + " lignes, coefficients en colonnes augmentees), puis le produit se calcule par\n", + " rowsum successifs -- legaux car les stabs commutent deux a deux.\n", + " \"\"\"\n", + " n = self.n\n", + " aug = np.concatenate(\n", + " [self.x[n:2 * n], self.z[n:2 * n], np.eye(n, dtype=np.int8)], axis=1\n", + " )\n", + " pivots = {}\n", + " for col in range(2 * n):\n", + " taken = set(pivots.values())\n", + " cand = None\n", + " for r in range(n):\n", + " if r not in taken and aug[r, col]:\n", + " cand = r\n", + " break\n", + " if cand is None:\n", + " continue\n", + " pivots[col] = cand\n", + " for r in range(n):\n", + " if r != cand and aug[r, col]:\n", + " aug[r] ^= aug[cand]\n", + " target = np.zeros(2 * n, dtype=np.int8)\n", + " target[n + q] = 1\n", + " coeffs = None\n", + " for r in range(n):\n", + " if np.array_equal(aug[r, :2 * n], target):\n", + " coeffs = aug[r, 2 * n:].astype(bool)\n", + " break\n", + " if coeffs is None:\n", + " raise ValueError(\"outcome deterministe introuvable : groupe invalide\")\n", + " scratch = 2 * n\n", + " self.x[scratch] = 0\n", + " self.z[scratch] = 0\n", + " self.r[scratch] = 0\n", + " for i in np.nonzero(coeffs)[0]:\n", + " self.rowsum(scratch, n + int(i))\n", + " # garde-fou : le produit doit etre exactement +-Z_q\n", + " assert self.x[scratch].sum() == 0 and int(self.z[scratch, q]) == 1 \\\n", + " and int(self.z[scratch].sum()) == 1, \"produit des stabs != +-Z_q\"\n", + " return int(self.r[scratch])\n", + "\n", + " def measure_z(self, q):\n", + " \"\"\"Mesure Z du qubit q : deterministe = lecture pure ; sinon outcome uniforme.\"\"\"\n", + " n = self.n\n", + " scratch = 2 * n\n", + " p = next((i for i in range(n, 2 * n) if self.x[i, q]), None)\n", + " if p is None:\n", + " return self._deterministic_outcome(q)\n", + " m = int(self.rng.integers(2))\n", + " self.x[scratch] = self.x[p]\n", + " self.z[scratch] = self.z[p]\n", + " self.r[scratch] = int(self.r[p])\n", + " # eliminer la composante anti-commutante des autres lignes ; le partenaire\n", + " # destabilisateur (p - n) est EXCLU : il doit garder x_q = 1 pour rester\n", + " # anti-commutant avec le nouveau stabilisateur (-1)^m Z_q (invariant du tableau)\n", + " for i in range(2 * n):\n", + " if i != p and i != p - n and self.x[i, q]:\n", + " self.rowsum(i, scratch)\n", + " self.x[p] = 0\n", + " self.z[p] = 0\n", + " self.z[p, q] = 1\n", + " self.r[p] = m\n", + " return m\n", + "\n", + " def measure_x(self, q):\n", + " self.h(q)\n", + " m = self.measure_z(q)\n", + " self.h(q)\n", + " return m\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "64829fca", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:13.989215Z", + "iopub.status.busy": "2026-09-20T20:33:13.988864Z", + "iopub.status.idle": "2026-09-20T20:33:13.998561Z", + "shell.execute_reply": "2026-09-20T20:33:13.997897Z" + }, + "papermill": { + "duration": 0.013953, + "end_time": "2026-09-20T20:33:13.999175+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:13.985222+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [], + "source": [ + "class StateVectorSim:\n", + " \"\"\"Etat complet 2^n : la machine temoin, sans classe de circuits.\n", + "\n", + " Portes 1-qubit par tensordot sur le tenseur (2,)*n (qubit q = axe q, donc\n", + " bit de poids 2**(n-1-q) dans l'index plat -- q0 est le bit le plus significatif,\n", + " la meme convention que les outcomes du CHP). CNOT par bit-trick O(2^n).\n", + " \"\"\"\n", + "\n", + " H = np.array([[1, 1], [1, -1]], dtype=complex) / math.sqrt(2)\n", + " S = np.array([[1, 0], [0, 1j]], dtype=complex)\n", + " T = np.array([[1, 0], [0, np.exp(1j * math.pi / 4)]], dtype=complex)\n", + " X = MATRICES[\"X\"]\n", + " Z = MATRICES[\"Z\"]\n", + "\n", + " def __init__(self, n, rng=None):\n", + " self.n = n\n", + " self.psi = np.zeros(2 ** n, dtype=complex)\n", + " self.psi[0] = 1.0\n", + " self.rng = rng if rng is not None else np.random.default_rng(1)\n", + "\n", + " def apply_1q(self, U, q):\n", + " shape = (2,) * self.n\n", + " moved = np.tensordot(U, self.psi.reshape(shape), axes=([1], [q]))\n", + " perm = list(range(1, self.n))\n", + " perm.insert(q, 0)\n", + " self.psi = np.transpose(moved, perm).reshape(-1)\n", + "\n", + " def cnot(self, c, t):\n", + " idx = np.arange(2 ** self.n)\n", + " flip = ((idx >> (self.n - 1 - c)) & 1) << (self.n - 1 - t)\n", + " new = np.empty_like(self.psi)\n", + " new[idx ^ flip] = self.psi[idx]\n", + " self.psi = new\n", + "\n", + " def apply(self, gate, *args):\n", + " if gate == \"cnot\":\n", + " return self.cnot(*args)\n", + " return self.apply_1q(\n", + " {\"h\": self.H, \"s\": self.S, \"t\": self.T, \"x\": self.X, \"z\": self.Z}[gate],\n", + " args[0],\n", + " )\n", + "\n", + " def sample_z(self, shots):\n", + " probs = np.abs(self.psi) ** 2\n", + " return self.rng.choice(2 ** self.n, size=shots, p=probs / probs.sum())\n", + "\n", + " def expect_pauli(self, xs, zs):\n", + " \"\"\" pour la chaine de Pauli encodee (xs, zs).\"\"\"\n", + " vec = self.psi.astype(complex).reshape((2,) * self.n)\n", + " for j in range(self.n):\n", + " if int(xs[j]) or int(zs[j]):\n", + " U = MATRICES[_pauli_letter(xs[j], zs[j])]\n", + " moved = np.tensordot(U, vec, axes=([1], [j]))\n", + " perm = list(range(1, self.n))\n", + " perm.insert(j, 0)\n", + " vec = np.transpose(moved, perm)\n", + " return complex(np.vdot(self.psi, vec.reshape(-1)))\n", + "\n", + "\n", + "def gen_circuit(n, depth, seed):\n", + " \"\"\"Circuit Clifford aleatoire seedé : la MEME liste alimente les deux machines.\"\"\"\n", + " rng = np.random.default_rng(seed * 7919 + n * 31 + depth)\n", + " gates = []\n", + " for _ in range(depth):\n", + " kind = rng.integers(3)\n", + " if kind == 0:\n", + " gates.append((\"h\", int(rng.integers(n))))\n", + " elif kind == 1:\n", + " gates.append((\"s\", int(rng.integers(n))))\n", + " else:\n", + " c = int(rng.integers(n))\n", + " t = int(rng.integers(n - 1))\n", + " if t >= c:\n", + " t += 1\n", + " gates.append((\"cnot\", c, t))\n", + " return gates\n", + "\n", + "\n", + "def run_chp(n, gates, seed):\n", + " sim = CHPSimulator(n, rng=np.random.default_rng(seed))\n", + " for g in gates:\n", + " sim.apply(*g)\n", + " return sim\n", + "\n", + "\n", + "def run_sv(n, gates, seed):\n", + " sim = StateVectorSim(n, rng=np.random.default_rng(seed))\n", + " for g in gates:\n", + " sim.apply(*g)\n", + " return sim\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d39e363e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:14.004013Z", + "iopub.status.busy": "2026-09-20T20:33:14.003841Z", + "iopub.status.idle": "2026-09-20T20:33:14.019630Z", + "shell.execute_reply": "2026-09-20T20:33:14.019130Z" + }, + "papermill": { + "duration": 0.019008, + "end_time": "2026-09-20T20:33:14.020265+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:14.001257+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Bell |00>+|11> : stabilisateurs = ['+XX', '+ZZ']\n", + "Bell vu par l'etat complet : poids |00> = 0.5 , |11> = 0.5 , ailleurs = 0.0\n", + "50 tirs de mesures Z correlees sur Bell : 50/50\n", + "GHZ-8 : stab[0] = +XXXXXXXX (attendu +XXXXXXXX)\n", + "GHZ-8 : les 8 mesures Z : [1, 1, 1, 1, 1, 1, 1, 1] -> toutes egales : True\n" + ] + } + ], + "source": [ + "# --- demos : les etats que tout le monde connait, lus dans le tableau -----------\n", + "bell = CHPSimulator(2, rng=np.random.default_rng(0))\n", + "bell.h(0)\n", + "bell.cnot(0, 1)\n", + "print(\"Bell |00>+|11> : stabilisateurs =\", [bell.stab_string(i) for i in range(2)])\n", + "\n", + "bell_sv = StateVectorSim(2)\n", + "bell_sv.apply(\"h\", 0)\n", + "bell_sv.cnot(0, 1)\n", + "poids = np.abs(bell_sv.psi) ** 2\n", + "print(\"Bell vu par l'etat complet : poids |00> =\", round(poids[0], 3),\n", + " \", |11> =\", round(poids[3], 3), \", ailleurs =\", round(poids[1] + poids[2], 3))\n", + "\n", + "# mesures Z successives : la correlation doit etre parfaite, tirage apres tirage\n", + "correles = 0\n", + "for shot in range(50):\n", + " sim = CHPSimulator(2, rng=np.random.default_rng(1000 + shot))\n", + " sim.h(0)\n", + " sim.cnot(0, 1)\n", + " correles += int(sim.measure_z(0) == sim.measure_z(1))\n", + "print(f\"50 tirs de mesures Z correlees sur Bell : {correles}/50\")\n", + "\n", + "ghz = CHPSimulator(8, rng=np.random.default_rng(0))\n", + "ghz.h(0)\n", + "for j in range(1, 8):\n", + " ghz.cnot(0, j)\n", + "print(\"GHZ-8 : stab[0] =\", ghz.stab_string(0), \"(attendu +XXXXXXXX)\")\n", + "res = [ghz.measure_z(j) for j in range(8)]\n", + "print(\"GHZ-8 : les 8 mesures Z :\", res, \"-> toutes egales :\", len(set(res)) == 1)\n" + ] + }, + { + "cell_type": "markdown", + "id": "b4256fbc", + "metadata": { + "papermill": { + "duration": 0.002108, + "end_time": "2026-09-20T20:33:14.024440+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:14.022332+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Lecture.** Le tableau lit Bell exactement comme les cours le décrivent :\n", + "stabilisateurs $+XX$ et $+ZZ$ — et la corrélation de mesure n'est pas énoncée, elle\n", + "**tombe** du formalisme : 50/50 tirs corrélés, aucune exception. GHZ tient dans les\n", + "mêmes lignes de bits : l'état le plus « intriqué » du zoo quantique coûte à l'arbitre\n", + "exactement ce que coûte $|0\\rangle^{\\otimes 8}$. C'est le premier résultat de la\n", + "leçon : *l'intrication n'est pas le mur* — la classe de circuits l'est." + ] + }, + { + "cell_type": "markdown", + "id": "edc4ad13", + "metadata": { + "papermill": { + "duration": 0.002238, + "end_time": "2026-09-20T20:33:14.029053+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:14.026815+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 4. La preuve croisée : l'arbitre ne ment pas\n", + "\n", + "Un port écrit à la main doit prouver sa justesse, pas la réclamer. Le banc :\n", + "\n", + "1. **valeurs propres exactes, zéro bruit** — pour des circuits Clifford aléatoires\n", + " seedés, l'état final est un état stabilisateur : chacun de ses $n$ stabilisateurs\n", + " $(-1)^{r_i} P_i$ vérifie $\\langle P_i \\rangle = (-1)^{r_i}$ **exactement**. Le CHP\n", + " lit les $P_i, r_i$ dans son tableau ; l'état complet calcule $\\langle P_i \\rangle$\n", + " par contraction tensorielle indépendante. Toute erreur de phase du port — le seul\n", + " endroit où se tromper — inverse un signe et fait échouer le test brutalement.\n", + "2. **distributions de mesure échantillonnées** — les $n$ mesures $Z$ successives du\n", + " CHP contre l'échantillonnage de la loi exacte $|\\psi|^2$ : distance totale (TV)\n", + " bornée par la marge de sondage (deux sondages de la même loi divergent de\n", + " $\\sim\\sqrt{K/\\pi}/\\sqrt{\\mathrm{shots}}$ sur $K$ sorties possibles).\n", + "3. **troisième juge** — si [stim](https://github.com/quantumlib/Stim) est installé,\n", + " ses probabilités marginales de mesure donnent une seconde référence indépendante." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "383cdd31", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:14.034593Z", + "iopub.status.busy": "2026-09-20T20:33:14.034349Z", + "iopub.status.idle": "2026-09-20T20:33:15.713088Z", + "shell.execute_reply": "2026-09-20T20:33:15.712389Z" + }, + "papermill": { + "duration": 1.682667, + "end_time": "2026-09-20T20:33:15.713944+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:14.031277+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Valeurs propres stabilisatrices egales au 1e-9 pres : 84 checks exacts OK\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n=4 seed=0 : TV = 0.017 (bruit de sondage attendu ~0.029, 4 sorties possibles)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n=4 seed=7 : TV = 0.029 (bruit de sondage attendu ~0.029, 4 sorties possibles)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n=6 seed=0 : TV = 0.003 (bruit de sondage attendu ~0.021, 2 sorties possibles)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "n=6 seed=7 : TV = 0.041 (bruit de sondage attendu ~0.058, 16 sorties possibles)\n", + "Distributions sous le seuil (6x le bruit de sondage attendu) : OK\n", + "stim confirme les 14 outcomes deterministes du CHP : OK\n" + ] + } + ], + "source": [ + "# --- croisement 1 : valeurs propres exactes (zéro bruit statistique) -----------\n", + "total_checks = 0\n", + "for n in (4, 6, 8, 10):\n", + " for seed in GLOBAL_SEEDS:\n", + " gates = gen_circuit(n, 3 * n, seed)\n", + " chp = run_chp(n, gates, seed)\n", + " sv = run_sv(n, gates, seed)\n", + " for xs, zs, r in chp.stabilizers():\n", + " val = sv.expect_pauli(xs, zs).real\n", + " assert abs(val - (-1) ** r) < 1e-9, (n, seed, _row_string(xs, zs, r), val)\n", + " total_checks += 1\n", + "print(f\"Valeurs propres stabilisatrices egales au 1e-9 pres : {total_checks} checks exacts OK\")\n", + "\n", + "# --- croisement 2 : distributions de mesure echantillonnees ---------------------\n", + "shots = 3000 if BATCH_MODE != \"true\" else 1500\n", + "for n in (4, 6):\n", + " for seed in (0, 7):\n", + " gates = gen_circuit(n, 3 * n, seed)\n", + " chp_counts = {}\n", + " for shot in range(shots):\n", + " chp = run_chp(n, gates, seed * 1009 + shot) # rng distinct par tir\n", + " outcome = 0\n", + " for q in range(n):\n", + " outcome = (outcome << 1) | chp.measure_z(q)\n", + " chp_counts[outcome] = chp_counts.get(outcome, 0) + 1\n", + " sv = run_sv(n, gates, seed)\n", + " p_chp = np.zeros(2 ** n)\n", + " for k, v in chp_counts.items():\n", + " p_chp[k] = v / shots\n", + " p_sv = np.bincount(sv.sample_z(shots), minlength=2 ** n) / shots\n", + " tv = 0.5 * np.abs(p_chp - p_sv).sum()\n", + " bins = int((p_sv > 0).sum())\n", + " bruit = math.sqrt(bins / (math.pi * shots))\n", + " print(f\"n={n} seed={seed} : TV = {tv:.3f} (bruit de sondage attendu ~{bruit:.3f},\"\n", + " f\" {bins} sorties possibles)\")\n", + " assert tv < max(0.12, 6 * bruit), (n, seed, tv, bruit)\n", + "print(\"Distributions sous le seuil (6x le bruit de sondage attendu) : OK\")\n", + "\n", + "# --- croisement 3 : troisieme juge stim, quand il est installe ------------------\n", + "try:\n", + " import stim\n", + "except ImportError:\n", + " stim = None\n", + " print(\"stim non installe : le banc continue avec les deux machines du notebook\")\n", + "\n", + "if stim is not None:\n", + " ok_marg = 0\n", + " for n in (6, 8):\n", + " for seed in GLOBAL_SEEDS:\n", + " gates = gen_circuit(n, 3 * n, seed)\n", + " st = stim.TableauSimulator(seed=seed)\n", + " for g in gates:\n", + " if g[0] == \"h\":\n", + " st.h(g[1])\n", + " elif g[0] == \"s\":\n", + " st.s(g[1])\n", + " else:\n", + " st.cx(g[1], g[2]) # stim.cx(controle, cible)\n", + " # marginales par qubit : peek_z rend la valeur propre (+-1), 0 = aleatoire\n", + " chp = run_chp(n, gates, seed)\n", + " for q in range(n):\n", + " own = chp.x[n:, q].sum() == 0 # CHP : lecture deterministe\n", + " stim_valeur = int(st.peek_z(q)) # stim : +-1 ou 0 si aleatoire\n", + " assert (stim_valeur != 0) == own, (n, seed, q, own, stim_valeur)\n", + " if own:\n", + " assert stim_valeur == 1 - 2 * chp.measure_z(q), (n, seed, q)\n", + " ok_marg += 1\n", + " print(f\"stim confirme les {ok_marg} outcomes deterministes du CHP : OK\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "f7f939d0", + "metadata": { + "papermill": { + "duration": 0.003598, + "end_time": "2026-09-20T20:33:15.722364+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:15.718766+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Lecture.** Chaque erreur possible du port a un détecteur dédié : une erreur de\n", + "phase dans `rowsum` ou la règle du $\\mathsf{CNOT}$ inverse une valeur propre ; une\n", + "erreur dans la mesure déplace la masse d'une distribution ; une erreur de convention\n", + "d'axes mélange les qubits et fait échouer les deux bancs à la fois. Le port que vous\n", + "venez de lire est passé **exact** sur les valeurs propres (aucun bruit qui puisse\n", + "cacher un signe) et **sous le seuil statistique** sur les distributions — le même\n", + "protocole qui, appliqué au brouillon de ce notebook, a intercepté une règle de phase\n", + "du $\\mathsf{CNOT}$ fausse (le piège $X_c Z_t$ / $Y_c Y_t$ du §2) : le banc n'est pas\n", + "décoratif, il a déjà servi." + ] + }, + { + "cell_type": "markdown", + "id": "a136e80a", + "metadata": { + "papermill": { + "duration": 0.00801, + "end_time": "2026-09-20T20:33:15.735163+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:15.727153+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 5. La mesure discriminante : le régime, pas l'objet\n", + "\n", + "Protocole : les **mêmes circuits** (profondeur $3n$, portes Clifford uniformes, seeds\n", + "$(0, 1, 7)$) sur les trois machines, temps **médian** par taille. Puis la même\n", + "question côté suprématie : que se passe-t-il quand le circuit quitte la classe —\n", + "une fraction $f$ des portes remplacée par des $T$ ?" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c9a6f8fc", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:15.747187Z", + "iopub.status.busy": "2026-09-20T20:33:15.746797Z", + "iopub.status.idle": "2026-09-20T20:33:46.430815Z", + "shell.execute_reply": "2026-09-20T20:33:46.428593Z" + }, + "papermill": { + "duration": 30.697821, + "end_time": "2026-09-20T20:33:46.438709+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:15.740888+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Temps median par taille (profondeur 3n, 3 seeds) :\n", + " n | port CHP | etat complet | stim\n", + " 6 | 0.2ms | 0.1ms | 0.11ms\n", + " 8 | 0.3ms | 0.2ms | 0.08ms\n", + " 10 | 0.5ms | 0.3ms | -\n", + " 12 | 0.6ms | 0.7ms | 0.08ms\n", + " 16 | 0.9ms | 46.5ms | 0.09ms\n", + " 20 | 1.1ms | 1591.8ms | 0.11ms\n", + " 24 | 1.5ms | - | 0.13ms\n", + " 32 | 3.1ms | - | 0.17ms\n", + " 40 | 4.8ms | - | -\n", + " 48 | 6.6ms | - | 0.26ms\n", + " 64 | 10.5ms | - | 0.36ms\n", + "\n", + "Ratio etat-complet / port CHP a n=20 : 1480x\n", + "Ratio port CHP / stim a n=20 : 10x (le port est fait pour etre lu, l'organe du metier pour la course)\n" + ] + } + ], + "source": [ + "NS_CHP = [6, 8, 10, 12, 16, 20, 24, 32, 40, 48, 64]\n", + "NS_SV = [6, 8, 10, 11, 12, 13, 14, 15, 16, 18, 20, 22]\n", + "NS_STIM = [6, 8, 12, 16, 20, 24, 32, 48, 64, 96, 128, 192, 256]\n", + "\n", + "\n", + "def mediane(fonction, ns):\n", + " \"\"\"(tailles, temps medians en secondes) : 3 seeds par taille.\"\"\"\n", + " tailles, temps = [], []\n", + " for n in ns:\n", + " echantillons = []\n", + " for seed in GLOBAL_SEEDS:\n", + " gates = gen_circuit(n, 3 * n, seed)\n", + " t0 = time.perf_counter()\n", + " fonction(n, gates, seed)\n", + " echantillons.append(time.perf_counter() - t0)\n", + " tailles.append(n)\n", + " temps.append(sorted(echantillons)[1])\n", + " return np.array(tailles), np.array(temps)\n", + "\n", + "\n", + "def run_stim(n, gates, seed):\n", + " st = stim.TableauSimulator(seed=seed)\n", + " for g in gates:\n", + " if g[0] == \"h\":\n", + " st.h(g[1])\n", + " elif g[0] == \"s\":\n", + " st.s(g[1])\n", + " else:\n", + " st.cx(g[1], g[2])\n", + "\n", + "\n", + "chp_ns, chp_t = mediane(run_chp, NS_CHP)\n", + "sv_ns, sv_t = mediane(run_sv, NS_SV)\n", + "if stim is not None:\n", + " stim_ns, stim_t = mediane(run_stim, NS_STIM)\n", + " stim_t_map = dict(zip(stim_ns, stim_t))\n", + "else:\n", + " stim_t_map = {}\n", + "print(\"Temps median par taille (profondeur 3n, 3 seeds) :\")\n", + "print(f\"{'n':>5} | {'port CHP':>10} | {'etat complet':>12} | {'stim':>10}\")\n", + "sv_map = dict(zip(sv_ns, sv_t))\n", + "for i, n in enumerate(chp_ns):\n", + " sv_dt = sv_map.get(n)\n", + " stim_dt = stim_t_map.get(n)\n", + " print(f\"{n:>5} | {chp_t[i]*1000:>8.1f}ms | \"\n", + " + (f\"{sv_dt*1000:>10.1f}ms\" if sv_dt else f\"{'-':>12}\")\n", + " + \" | \" + (f\"{stim_dt*1000:>8.2f}ms\" if stim_dt else \"-\"))\n", + "\n", + "# dernier point commun au ratio\n", + "n_commun = 20\n", + "ratio = sv_map[n_commun] / chp_t[chp_ns.tolist().index(n_commun)]\n", + "print(f\"\\nRatio etat-complet / port CHP a n={n_commun} : {ratio:.0f}x\")\n", + "if stim_t_map:\n", + " print(f\"Ratio port CHP / stim a n={n_commun} : \"\n", + " f\"{chp_t[chp_ns.tolist().index(n_commun)] / stim_t_map[n_commun]:.0f}x \"\n", + " \"(le port est fait pour etre lu, l'organe du metier pour la course)\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "e2410d1e", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:46.464273Z", + "iopub.status.busy": "2026-09-20T20:33:46.464019Z", + "iopub.status.idle": "2026-09-20T20:33:46.474070Z", + "shell.execute_reply": "2026-09-20T20:33:46.472202Z" + }, + "papermill": { + "duration": 0.016737, + "end_time": "2026-09-20T20:33:46.474773+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.458036+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "etat complet : pente 1.10 bits de log2(t) par qubit (R^2 = 0.9313) ; echelons locaux entre 1.3x et 7.9x par qubit\n", + " (plancher theorique 2x par qubit ; au-dela = effets de memoire :\n", + " chaque qubit double le vecteur, et la copie finit par sortir des caches)\n", + "\n", + "Bascule MESUREE : des n = 13 qubits, l'etat complet paie plus que l'arbitre (1.1x), et l'ecart double ensuite a chaque qubit\n", + "EXTRAPOLATION du fit 2^n : n=30 -> 1.65e+03 s par circuit (non mesure)\n", + "EXTRAPOLATION du fit 2^n : n=40 -> 933 h par circuit (non mesure)\n" + ] + } + ], + "source": [ + "# --- regressions : ce que chaque machine paie par qubit -------------------------\n", + "# etat complet : t ~ a * 2^n -> log2(t) = log2(a) + n (droite de pente ~1,\n", + "# mais les echelons locaux sont plus raides : la memoire aussi a une loi)\n", + "a_sv, b_sv = np.polyfit(sv_ns, np.log2(sv_t), 1)\n", + "r2_sv = 1 - np.sum((np.log2(sv_t) - (a_sv * sv_ns + b_sv)) ** 2) / np.sum(\n", + " (np.log2(sv_t) - np.mean(np.log2(sv_t))) ** 2\n", + ")\n", + "rapports_locaux = sv_t[1:] / sv_t[:-1]\n", + "print(f\"etat complet : pente {a_sv:.2f} bits de log2(t) par qubit (R^2 = {r2_sv:.4f}) ;\"\n", + " f\" echelons locaux entre {rapports_locaux.min():.1f}x et\"\n", + " f\" {rapports_locaux.max():.1f}x par qubit\")\n", + "print(\" (plancher theorique 2x par qubit ; au-dela = effets de memoire :\")\n", + "print(\" chaque qubit double le vecteur, et la copie finit par sortir des caches)\")\n", + "\n", + "# bascule MESUREE : le plus petit n ou l'etat complet depasse deja le port\n", + "pairs = {n: (s, c) for n, s, c in zip(sv_ns, sv_t, [chp_t[chp_ns.tolist().index(n)]\n", + " for n in sv_ns if n in chp_ns])}\n", + "n_bascule = next(n for n in sv_ns\n", + " if n in pairs and pairs[n][0] > pairs[n][1])\n", + "print(f\"\\nBascule MESUREE : des n = {n_bascule} qubits, l'etat complet paie plus que\"\n", + " f\" l'arbitre ({pairs[n_bascule][0]/pairs[n_bascule][1]:.1f}x), et l'ecart\"\n", + " \" double ensuite a chaque qubit\")\n", + "\n", + "# extrapolations (niveau Cite de la hierarchie du depot : ajustement, non mesure)\n", + "for n_cible in (30, 40):\n", + " t_fit = 2 ** (a_sv * n_cible + b_sv)\n", + " unite, val = (\"h\", t_fit / 3600) if t_fit > 3600 else (\"s\", t_fit)\n", + " print(f\"EXTRAPOLATION du fit 2^n : n={n_cible} -> {val:.3g} {unite} par circuit\"\n", + " \" (non mesure)\")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "505c4f40", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:46.485753Z", + "iopub.status.busy": "2026-09-20T20:33:46.485114Z", + "iopub.status.idle": "2026-09-20T20:33:46.513023Z", + "shell.execute_reply": "2026-09-20T20:33:46.511168Z" + }, + "papermill": { + "duration": 0.035094, + "end_time": "2026-09-20T20:33:46.514406+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.479312+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Une SEULE porte T dans le circuit, et la classe casse :\n", + " Clifford pur : port CHP simule | stim : pas de porte t | etat complet : 1.0 ms, sans changer d'allure\n", + " 1 porte T : port CHP REFUSE (porte T sur le qubit 0 refusee : T n'est pas...) | stim : pas de porte t | etat complet : 4.3 ms, sans changer d'allure\n", + " 5% de T : port CHP REFUSE (porte T sur le qubit 0 refusee : T n'est pas...) | stim : pas de porte t | etat complet : 3.4 ms, sans changer d'allure\n", + " 10% de T : port CHP REFUSE (porte T sur le qubit 7 refusee : T n'est pas...) | stim : pas de porte t | etat complet : 0.9 ms, sans changer d'allure\n", + "\n", + "Message du banc : la frontiere n'est pas 'quantique contre classique',\n", + "elle est 'Clifford contre le reste' -- et T est du reste.\n" + ] + } + ], + "source": [ + "# --- densite T : le regime ou l'arbitre rend son tablier ------------------------\n", + "def gen_circuit_T(n, depth, seed, fraction):\n", + " \"\"\"Circuit Clifford ou une fraction des portes est remplacee par T.\"\"\"\n", + " rng = np.random.default_rng(seed * 7919 + n * 31 + depth + int(fraction * 1000))\n", + " gates = []\n", + " for _ in range(depth):\n", + " if rng.random() < fraction:\n", + " gates.append((\"t\", int(rng.integers(n))))\n", + " continue\n", + " kind = rng.integers(3)\n", + " if kind == 0:\n", + " gates.append((\"h\", int(rng.integers(n))))\n", + " elif kind == 1:\n", + " gates.append((\"s\", int(rng.integers(n))))\n", + " else:\n", + " c = int(rng.integers(n))\n", + " t = int(rng.integers(n - 1))\n", + " if t >= c:\n", + " t += 1\n", + " gates.append((\"cnot\", c, t))\n", + " return gates\n", + "\n", + "\n", + "print(\"Une SEULE porte T dans le circuit, et la classe casse :\")\n", + "for fraction, injecte_une_T, etiquette in [\n", + " (0.0, False, \"Clifford pur\"),\n", + " (0.0, True, \"1 porte T\"),\n", + " (0.05, False, \"5% de T\"),\n", + " (0.10, False, \"10% de T\"),\n", + "]:\n", + " gates = gen_circuit_T(8, 24, 0, fraction)\n", + " if injecte_une_T:\n", + " gates = gates[:12] + [(\"t\", 0)] + gates[12:] # exactement une porte T\n", + " # 1. l'arbitre polynomial\n", + " try:\n", + " run_chp(8, gates, 0)\n", + " verdict_chp = \"simule\"\n", + " except TypeError as exc:\n", + " verdict_chp = f\"REFUSE ({str(exc)[:44]}...)\"\n", + " # 2. l'outil du metier\n", + " if stim is not None:\n", + " verdict_stim = \"simule\" if hasattr(stim.TableauSimulator(), \"t\") else \"pas de porte t\"\n", + " else:\n", + " verdict_stim = \"non installe\"\n", + " # 3. l'etat complet : aucune classe, il paie 2^n et simule tout\n", + " t0 = time.perf_counter()\n", + " run_sv(8, gates, 0)\n", + " dt_sv = time.perf_counter() - t0\n", + " print(f\" {etiquette:>14} : port CHP {verdict_chp} | stim : {verdict_stim} | \"\n", + " f\"etat complet : {dt_sv*1000:.1f} ms, sans changer d'allure\")\n", + "\n", + "print()\n", + "print(\"Message du banc : la frontiere n'est pas 'quantique contre classique',\")\n", + "print(\"elle est 'Clifford contre le reste' -- et T est du reste.\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "b4e8bb6e", + "metadata": { + "papermill": { + "duration": 0.00764, + "end_time": "2026-09-20T20:33:46.528572+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.520932+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "**Lecture.** Trois constats, un seul message.\n", + "\n", + "1. À circuits **Clifford purs**, la bascule est **mesurée**, pas extrapolée : dès la\n", + " douzaine de qubits, l'état complet paie déjà plus que l'arbitre, et le ratio\n", + " imprimé par le banc à $n = 20$ se lit en **milliers**. La pente globale est\n", + " $\\approx 1$ bit de $\\log_2 t$ par qubit ($R^2$ affiché), mais les échelons locaux\n", + " montent jusqu'à plusieurs fois $2\\times$ par qubit : le plancher théorique\n", + " $2^n$ est *optimiste* — chaque qubit double le vecteur, et les copies finissent par\n", + " sortir des caches. Le mur réel est plus raide que la théorie, et le banc le\n", + " montre. Les projections à $n = 30$ et $n = 40$ sont **extrapolées du fit**\n", + " ($R^2$ affiché), pas mesurées : niveau *Cité* de la hiérarchie du dépôt, étiqueté\n", + " tel quel.\n", + "2. **Une seule porte $T$ ferme l'arbitre** — refus explicite, avec explication, pas un\n", + " crash. Et l'outil du métier fait pareil : stim, le simulateur stabilisateur de\n", + " référence, **n'a pas de méthode `t`** du tout. La frontière n'est pas un manque\n", + " d'ingénierie : elle est *théorique* — c'est le théorème de Gottesman–Knill lu dans\n", + " le mauvais sens (la simulation polynomiale existe pour Clifford, et n'existe pas,\n", + " sous hypothèses standard, pour Clifford+$T$).\n", + "3. L'**état complet**, lui, ne change pas d'allure : la porte $T$ est une $2\\times2$\n", + " de plus, gratuite à ses yeux. Mais son prix ne dépendait déjà pas des portes —\n", + " seulement de $n$. Chaque machine a son invariant : l'une paie $2^n$ quelles que\n", + " soient les portes, l'autre paie $n^2$ mais seulement dans sa classe.\n", + "\n", + "C'est la leçon de méthode, mesurée : une « suprématie quantique » est une assertion\n", + "conjointe sur **une classe de circuits et un régime de taille**. Réfuter proprement,\n", + "c'est exhiber le régime où l'assertion casse — ici, tout circuit Clifford, quelle que\n", + "soit la taille. Installer la claim, c'est montrer qu'on est sorti du régime — et $T$\n", + "est le billet d'entrée le plus faible du hors-classe." + ] + }, + { + "cell_type": "markdown", + "id": "14ff6ce6", + "metadata": { + "papermill": { + "duration": 0.014525, + "end_time": "2026-09-20T20:33:46.550870+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.536345+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 6. Exercices\n", + "\n", + "Les stubs s'exécutent sans erreur (règle C.1 du dépôt) ; les indices et étapes guident\n", + "sans écrire la solution. Les variables des bancs des sections précédentes restent en\n", + "portée." + ] + }, + { + "cell_type": "markdown", + "id": "6037f6fb", + "metadata": { + "papermill": { + "duration": 0.006872, + "end_time": "2026-09-20T20:33:46.567234+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.560362+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Exercice 1 — La phase par commutation, sans matrices\n", + "\n", + "Le §1 dérive la table de phase en multipliant les matrices. Une autre voie, purement\n", + "algébrique, n'utilise que : (i) deux Pauli non triviales *distinctes* anti-commutent ;\n", + "(ii) l'identité $X \\cdot Y = iZ$ (conséquence de $Y = iXZ$) ; (iii) l'hermiticité\n", + "($P_1 P_2 = (P_2 P_1)^\\dagger$ inverse l'exposant). Écrire\n", + "`pauli_product_phase(p1, p2)` qui retourne $e$ tel que $P_1 P_2 = i^e P'$ **sans aucune\n", + "matrice**, puis la comparer à `E_TABLE` case par case." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "4acfef80", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:46.586953Z", + "iopub.status.busy": "2026-09-20T20:33:46.586528Z", + "iopub.status.idle": "2026-09-20T20:33:46.597758Z", + "shell.execute_reply": "2026-09-20T20:33:46.595796Z" + }, + "papermill": { + "duration": 0.023374, + "end_time": "2026-09-20T20:33:46.598955+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.575581+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Comparaison case par case avec E_TABLE : 10/16 concordent\n", + " ecart : X.Y: attendu 1, rendu 0\n", + " ecart : X.Z: attendu 3, rendu 0\n", + " ecart : Y.X: attendu 3, rendu 0\n", + " ecart : Y.Z: attendu 1, rendu 0\n", + " ecart : Z.X: attendu 1, rendu 0\n", + " ecart : Z.Y: attendu 3, rendu 0\n" + ] + } + ], + "source": [ + "# Exercice 1 : a completer\n", + "def pauli_product_phase(p1, p2):\n", + " \"\"\"Exposeant e (0..3) tel que P1 @ P2 = (i**e) * P', sans matrices.\n", + "\n", + " p1, p2 : lettres parmi \"I\", \"X\", \"Y\", \"Z\".\n", + " \"\"\"\n", + " e = 0 # TODO etudiant : exposeant tel que P1 @ P2 = (i**e) * P'\n", + " # Indice : les cas ou p1 == p2 ou l'un vaut \"I\" donnent e = 0.\n", + " # Etape 1 : traiter ces cas triviaux.\n", + " # Etape 2 : pour les 6 paires restantes, partir de X.Y = iZ (e = 1) et deriver\n", + " # Y.Z = iX et Z.X = iY par permutation circulaire des roles.\n", + " # Etape 3 : les paires a contre-courant (Y.X, Z.Y, X.Z) par hermicite :\n", + " # e(P2.P1) = (4 - e(P1.P2)) mod 4.\n", + " return e\n", + "\n", + "ok, ecarts = 0, []\n", + "for p1 in \"IXYZ\":\n", + " for p2 in \"IXYZ\":\n", + " attendu = int(E_TABLE[\"IXYZ\".index(p1), \"IXYZ\".index(p2)])\n", + " rendu = pauli_product_phase(p1, p2)\n", + " if rendu == attendu:\n", + " ok += 1\n", + " else:\n", + " ecarts.append(f\"{p1}.{p2}: attendu {attendu}, rendu {rendu}\")\n", + "print(f\"Comparaison case par case avec E_TABLE : {ok}/16 concordent\")\n", + "for e in ecarts:\n", + " print(\" ecart :\", e)\n" + ] + }, + { + "cell_type": "markdown", + "id": "0f340ff9", + "metadata": { + "papermill": { + "duration": 0.006767, + "end_time": "2026-09-20T20:33:46.616058+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.609291+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Exercice 2 — L'arbitre de classe\n", + "\n", + "Écrire `verdict_simulateur(n, portes)` : étant donné un circuit (liste de tuples comme\n", + "celles des bancs du §5) et une taille, retourner `\"CHP\"` si le circuit est Clifford\n", + "(l'arbitre polynomial s'applique), `\"etat-complet\"` s'il sort de la classe mais reste\n", + "dans les clous de l'état complet ($2^n \\le 2^{22}$ amplitudes, la limite mémoire des\n", + "bancs de ce notebook), `\"aucun\"` sinon. La fonction ne **simule** rien : elle lit la\n", + "classe." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "992ecb41", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:46.630145Z", + "iopub.status.busy": "2026-09-20T20:33:46.629378Z", + "iopub.status.idle": "2026-09-20T20:33:46.644364Z", + "shell.execute_reply": "2026-09-20T20:33:46.642604Z" + }, + "papermill": { + "duration": 0.024731, + "end_time": "2026-09-20T20:33:46.645762+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.621031+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " Clifford n=64 -> None\n", + " 1 porte T, n=8 -> None\n", + " 10% de T, n=8 -> None\n", + " 10% de T, n=50 -> None\n" + ] + } + ], + "source": [ + "# Exercice 2 : a completer\n", + "def verdict_simulateur(n, portes):\n", + " \"\"\"'CHP' si Clifford, 'etat-complet' si 2**n <= 2**22, sinon 'aucun'.\"\"\"\n", + " verdict = None # TODO etudiant\n", + " # Etape 1 : detecter une porte non-Clifford (\"t\") dans la liste.\n", + " # Etape 2 : si Clifford pur -> 'CHP' (Gottesman-Knill : polynomial, toute taille).\n", + " # Etape 3 : sinon, arbitrer par la memoire : n <= 22 -> 'etat-complet', sinon 'aucun'.\n", + " return verdict\n", + "\n", + "\n", + "def _circuit_avec_une_T(n, depth, seed):\n", + " gates = gen_circuit(n, depth, seed)\n", + " return gates[: depth // 2] + [(\"t\", 0)] + gates[depth // 2:]\n", + "\n", + "\n", + "cas_tests = [\n", + " (\"Clifford n=64\", 64, gen_circuit(64, 3 * 64, 0)),\n", + " (\"1 porte T, n=8\", 8, _circuit_avec_une_T(8, 24, 0)),\n", + " (\"10% de T, n=8\", 8, gen_circuit_T(8, 24, 0, 0.10)),\n", + " (\"10% de T, n=50\", 50, gen_circuit_T(50, 150, 0, 0.10)),\n", + "]\n", + "for nom, n, portes in cas_tests:\n", + " print(f\" {nom:>16} -> {verdict_simulateur(n, portes)}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "7657e296", + "metadata": { + "papermill": { + "duration": 0.005165, + "end_time": "2026-09-20T20:33:46.656498+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.651333+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "### Exercice 3 — Le mur, projeté par ses échelons\n", + "\n", + "Le banc du §5 mesure les échelons locaux de l'état complet : le rapport\n", + "$t(n{+}1)/t(n)$, qubit par qubit. Recalculer la projection **à partir de ces échelons\n", + "seuls** : estimer le facteur multiplicatif par qubit (les petites tailles sont\n", + "dominées par les coûts fixes : ne garder que $n \\ge 12$), vérifier qu'il est\n", + "**au-dessus du plancher théorique** $2$, en déduire le temps projeté à $n = 30$, puis\n", + "le plus petit $n$ où la projection dépasse **une heure** par circuit." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d7a0ca3c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-20T20:33:46.665593Z", + "iopub.status.busy": "2026-09-20T20:33:46.665325Z", + "iopub.status.idle": "2026-09-20T20:33:46.672214Z", + "shell.execute_reply": "2026-09-20T20:33:46.671176Z" + }, + "papermill": { + "duration": 0.013014, + "end_time": "2026-09-20T20:33:46.673130+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.660116+00:00", + "status": "completed" + }, + "tags": [] + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Exercice a completer : mur projete par ses echelons" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + } + ], + "source": [ + "# Exercice 3 : a completer\n", + "facteur_par_qubit = None # TODO etudiant : facteur multiplicatif de sv_t par qubit\n", + "t_projete_30 = None # TODO etudiant : temps projete a n = 30 (secondes)\n", + "n_une_heure = None # TODO etudiant : plus petit n ou le temps projete > 3600 s\n", + "# Indice : sur une loi 2^n, t(n+1)/t(n) serait exactement 2 -- le banc mesure plus\n", + "# (effets de memoire). Estimer le facteur REEL par la mediane des rapports\n", + "# consecutifs de sv_t pour n >= 12.\n", + "# Etape 1 : rapports consecutifs sv_t[i+1]/sv_t[i] sur les tailles n >= 12, mediane.\n", + "# Etape 2 : projeter depuis le DERNIER point mesure jusqu'a n=30 (multiplications\n", + "# successives par le facteur).\n", + "# Etape 3 : poursuivre la projection jusqu'a depasser 3600 secondes.\n", + "\n", + "print(\"Exercice a completer : mur projete par ses echelons\")\n", + "if facteur_par_qubit is not None:\n", + " print(f\" facteur par qubit : {facteur_par_qubit:.2f} (plancher theorique 2)\")\n", + " print(f\" temps projete a n=30 : {t_projete_30:.3g} s\")\n", + " print(f\" projection > 1 heure des n = {n_une_heure}\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "a88963a7", + "metadata": { + "papermill": { + "duration": 0.004355, + "end_time": "2026-09-20T20:33:46.681271+00:00", + "exception": false, + "start_time": "2026-09-20T20:33:46.676916+00:00", + "status": "completed" + }, + "tags": [] + }, + "source": [ + "## 7. Conclusion\n", + "\n", + "| Geste | Où | Ce qui a été mesuré |\n", + "|---|---|---|\n", + "| Dériver, pas recopier | §1–§2 | Table de phase et conjugaison CHP **construites** par multiplication des matrices $2\\times2$ ; le piège de phase $X_c Z_t / Y_c Y_t$ exhibé, pas affirmé |\n", + "| Construire l'organe | §3 | Port CHP complet (rowsum, portes, mesures — déterministe par lecture GF(2), aléatoire par re-stabilisation) ; Bell et GHZ lus dans le tableau, 50/50 corrélations mesurées |\n", + "| Prouver l'arbitre | §4 | **84 valeurs propres** stabilisatrices exactes au $10^{-9}$ (zéro bruit) ; distributions TV sous 6× le bruit de sondage ; stim confirme les outcomes déterministes |\n", + "| Mesurer le régime | §5 | Bascule **mesurée** dès ~12 qubits ; ratio ~1600× à $n = 20$ ; pente $\\approx 1$ bit/qubit côté état complet ($R^2$ affiché) avec échelons locaux au-dessus du plancher $2\\times$ (mémoire) ; projections $n = 30/40$ **extrapolées du fit, étiquetées telles** |\n", + "| Fermer la classe | §5 | Une porte $T$ → refus du port **et** de stim (pas de méthode `t`) ; l'état complet inchangé — la frontière est Clifford/le-reste, pas quantique/classique |\n", + "\n", + "**Ce que 2004 a fondé ici.** Aaronson et Gottesman n'ont pas « accéléré » la simulation\n", + "quantique : ils ont **délimité** la terre qui ne demandait pas de moteur quantique. La\n", + "leçon de méthode, mesurée sur ce banc, déborde le quantique : face à une claim\n", + "d'avantage — matériel, algorithmique, organisationnelle — la question qui réfute ou\n", + "installe n'est jamais « est-ce puissant ? » mais « **de quel régime parle-t-on, et où\n", + "casse-t-il ?** ». L'arbitre exécutable est la réponse la plus honnête qu'on puisse\n", + "rendre : il ne discute pas la claim, il tourne jusqu'à sa frontière.\n", + "\n", + "**Hommage.** Le titre du notebook reprend le geste qu'Aaronson a le plus pratiqué —\n", + "déquantifier : chercher, derrière chaque « seul un quantique peut », le simulateur\n", + "classique du régime qui le contredit. Ce notebook est ce geste, retourné en banc\n", + "pédagogique : deux machines, une frontière, et chaque phase prouvée par construction.\n", + "\n", + "## Références\n", + "\n", + "— Aaronson, S., & Gottesman, D. (2004). « Improved Simulation of Stabilizer Circuits ».\n", + "*Physical Review A* 70, 052328 (quant-ph/0406196).\n", + "— Gottesman, D. (1998). « The Heisenberg Representation of Quantum Computers ». *PhD\n", + "thesis, Caltech* (quant-ph/9807006) — le théorème de Gottesman–Knill.\n", + "— Aaronson, S. (2007). « Are Quantum States Exponentially Long Vectors? ». *arXiv*\n", + "quant-ph/0701104 — le cadre de la déquantification et les classes de simulateurs.\n", + "— Gidney, C. (2021). « Stim: a fast stabilizer circuit simulator ». *Quantum* 7, 1297.\n", + "— Nielsen, M. A., & Chuang, I. L. (2010). *Quantum Computation and Quantum\n", + "Information*, Cambridge University Press — chapitre 10.5, le formalisme stabilisateur." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.13.15" + }, + "papermill": { + "default_parameters": {}, + "duration": 34.308721, + "end_time": "2026-09-20T20:33:47.134987+00:00", + "environment_variables": {}, + "exception": null, + "input_path": "Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb", + "output_path": "Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb", + "parameters": {}, + "start_time": "2026-09-20T20:33:12.826266+00:00", + "version": "2.7.0" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/MyIA.AI.Notebooks/Complexity/README.md b/MyIA.AI.Notebooks/Complexity/README.md index 2c18e21300..d9956af167 100644 --- a/MyIA.AI.Notebooks/Complexity/README.md +++ b/MyIA.AI.Notebooks/Complexity/README.md @@ -14,6 +14,7 @@ n'y existe pas encore, et pourquoi. | [`Complexity-01-HartmanisStearns-TimeHierarchy.ipynb`](Complexity-01-HartmanisStearns-TimeHierarchy.ipynb) | Juris Hartmanis & Richard E. Stearns (prix Turing 1993, « On the Computational Complexity of Algorithms », 1965) | Machines de Turing multi-rubans simulées avec comptage exact des pas ; trois croissances mesurées ($n \log n$, $n^2$, $2^n$) ; subset-sum comme séparatrice honnête (force brute vs programmation dynamique, compteurs déterministes) ; diagonalisation budgétée sur une famille finie ; théorème de hiérarchie en temps $\mathrm{TIME}(f) \subsetneq \mathrm{TIME}(f \log f)$ ; encart sur le **gap Mathlib** (aucune couche `Computability/Complexity` : pas de $\mathrm{TIME}(f)$, pas de machine universelle avec overhead temporel, pas de théorème de hiérarchie) | Français | | [`Complexity-02-OnlineConjectures-Secretary-KServer.ipynb`](Complexity-02-OnlineConjectures-Secretary-KServer.ipynb) | Sahil Singla & Christian Coester, Elias Koutsoupias, Marek Zbysiński (les deux preprints de septembre 2026 : conjecture du secrétaire matroïdal 2007 et conjecture k-server ~1990, tombées à 24 h d'écart) | Baseline $1/e$ du secrétaire classique mesurée par Monte Carlo ($n$ = 20/100/1000) ; deux matroïdes à oracle d'indépendance (uniforme, graphique) avec optimum offline exact et compétitivité de deux stratégies online ; **work function algorithm implémenté exactement** (DP sur les $\binom{m}{2}$ configurations) : sur le cycle $C_5$, ratio WFA 1,67 sous la borne $k=2$ quand greedy explose (20 à 60 requêtes, 160 à 480 — non compétitif) ; tableau du double visage randomisation/déterminisme ; trois niveaux épistémiques Mesuré/Cité/Absent (prudence « preuve annoncée ») ; encart **gap Mathlib** (matroïdes présents, compétitivité/k-server/work functions absents) | Français | | [`Complexity-03-AaronsonArkhipov-PermanenteBosonSampling.ipynb`](Complexity-03-AaronsonArkhipov-PermanenteBosonSampling.ipynb) | Scott Aaronson & Alex Arkhipov (*The Computational Complexity of Linear Optics*, 2011 — la permanente comme frontière quantique) | La définition $n!$ **exécutée** avec comptes canoniques, vérifiée contre l'énumération brute des couplages parfaits (perm(0/1)) ; **Ryser vectorisé réemployé de Sudoku-15** (`batch_perm8`, généralisé à toute taille) contre Gauss instrumenté : comptes exacts par formules fermées validées par instrumentation — à $n=20$, définition $4{,}9 \times 10^{19}$ opérations contre 5 149 pour Gauss, Ryser/det ≈ $1{,}6 \times 10^4$ en temps à $n=16$ ; #P-difficulté (Valiant 1979) maintenue au niveau **Cité**, FPRAS non négatif (Jerrum–Sinclair–Vigoda) en exception qui désigne les signes ; **BosonSampling jouet** : unitaire Haar (QR, Mezzadri), loi $\lvert\mathrm{perm}\rvert^2$ exacte sur 56/924/12 870 sorties, TV(échantillon, exact) → 0, coût d'énumération extrapolé jusqu'à ~1 an à 1 TFLOP/s pour $(20, 40)$ ; encart **gap Mathlib** — premier verdict **positif** de la série : `Matrix.permanent` existe (114 lignes, forme $n!$) face à un dossier `Determinant/` entier, mais ni Ryser ni couche complexité | Français | +| [`Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb`](Complexity-04-Aaronson-Dequantification-Stabilizer.ipynb) | Scott Aaronson & Daniel Gottesman (*Improved Simulation of Stabilizer Circuits*, 2004 — déquantifier les suprématies par l'arbitre exécutable) | Le formalisme stabilisateur **exécuté** : table de phase et conjugaison CHP **dérivées des matrices 2×2**, jamais recopiées ; port CHP complet (rowsum, portes, mesures — déterministe par lecture GF(2), aléatoire par re-stabilisation) croisé contre l'état complet 2^n : **84 valeurs propres stabilisatrices exactes au 1e-9** (zéro bruit), distributions TV sous 6× le bruit de sondage, stim en troisième juge ; banc discriminant : bascule **mesurée** dès ~12 qubits, ratio ~1600× à n=20, échelons locaux au-dessus du plancher 2× (le mur réel est plus raide que 2^n : la mémoire aussi a une loi) ; une **seule** porte T ferme le port **et** stim (`no attribute 't'`) — la frontière est Clifford/le-reste, pas quantique/classique | Français | ## Position dans le dépôt @@ -31,11 +32,22 @@ sans Ryser ni #P : encart §5), et relie la permanente au dépôt existant — c parfaits de [Sudoku-15](../Sudoku/Sudoku-15-Infer-Python.ipynb), dont le schéma Ryser vectorisé est réemployé tel quel. +Le notebook 04 amène la série **côté simulation** : les notebooks précédents +mesuraient des coûts combinatoires (permanente, work functions) ; celui-ci +construit la machine qui *réfute* — le simulateur stabilisateur comme +instrument de méthode. C'est aussi le premier notebook de la série à croiser +trois moteurs (port pédagogique, état complet, stim), chacun validant les +deux autres. + ## Prérequis Python 3.10+ (kernel `python3`) : `numpy`, `matplotlib` — le socle standard du dépôt. Aucune dépendance externe : tout est construit dans le notebook, rubans, transitions, oracles d'indépendance et work functions compris, pour que chaque pas soit auditable. +Le notebook 04 ajoute une dépendance **optionnelle** : +[`stim`](https://github.com/quantumlib/Stim) (troisième juge du croisement et banc +d'échelle). Sans lui, le notebook s'exécute intégralement avec les deux machines +qu'il construit — le banc affiche alors sa colonne vide et le dit. ## Exercices