{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "b6d1e3ec",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Modèle d'Ising à champ transversal avec gestion des performances Q-CTRL\"\n",
        "description: \"Construire et résoudre le modèle d'Ising à champ transversal à l'aide de Fire Opal et Qiskit\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore fontsize sharey rmse RMSE boxstyle */}\n",
        "\n",
        "<span id=\"transverse-field-ising-model-with-q-ctrls-performance-management\" />\n",
        "\n",
        "# Modèle d'Ising à champ transversal avec gestion des performances Q-CTRL\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "a6f69b77",
      "metadata": {},
      "source": [
        "*Estimation d'utilisation : 2 minutes sur un processeur Heron r2. (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "Le modèle d'Ising à champs transversaux (TFIM) est important pour l'étude du magnétisme quantique et des transitions de phase. Il décrit un ensemble de spins disposés sur un réseau, où chaque spin interagit avec ses voisins tout en étant influencé par un champ magnétique externe qui entraîne des fluctuations quantiques.\n",
        "\n",
        "Une approche courante pour simuler ce modèle consiste à utiliser la décomposition de Trotter pour approximer l'opérateur d'évolution temporelle, en construisant des circuits qui alternent entre les rotations d'un seul qubit et les interactions de deux qubits enchevêtrés. Cependant, cette simulation sur du matériel réel est difficile en raison du bruit et de la décohérence, qui entraînent des écarts par rapport à la dynamique réelle. Pour y remédier, nous utilisons les outils de suppression des erreurs et de gestion des performances Fire Opal de Q-CTRL, proposés en tant que fonction Qiskit (voir la [documentation Fire Opal](/docs/guides/q-ctrl-performance-management)). Fire Opal optimise automatiquement l'exécution du circuit en appliquant un découplage dynamique, une disposition avancée, un routage et d'autres techniques de suppression des erreurs, toutes destinées à réduire le bruit. Grâce à ces améliorations, les résultats matériels s'alignent plus étroitement sur les simulations sans bruit, ce qui nous permet d'étudier la dynamique de l'aimantation du TFIM avec une plus grande fidélité.\n",
        "\n",
        "Dans ce tutoriel, nous allons :\n",
        "\n",
        "* Construire l'hamiltonien TFIM sur un graphe de triangles de spin connectés\n",
        "* Simuler l'évolution temporelle avec des circuits troptérisés à différentes profondeurs\n",
        "* Calculer et visualiser les magnétisations d'un qubit unique $\\langle Z_i \\rangle$ au fil du temps\n",
        "* Comparer les simulations de base avec les résultats d'exécutions matérielles à l'aide de la gestion des performances Fire Opal de Q-CTRL\n",
        "\n",
        "<span id=\"overview\" />\n",
        "\n",
        "## Aperçu\n",
        "\n",
        "Le modèle d'Ising à champ transverse (TFIM) est un modèle de spin quantique qui capture les caractéristiques essentielles des transitions de phase quantiques. L'hamiltonien est défini comme suit\n",
        "\n",
        "$$\n",
        "H = -J \\sum_{i} Z_i Z_{i+1} - h \\sum_{i} X_i\n",
        "$$\n",
        "\n",
        "où $Z_i$ et $X_i$ sont des opérateurs de Pauli agissant sur le qubit $i$, $J$ est la force de couplage entre les spins voisins, et $h$ est la force du champ magnétique transversal. Le premier terme représente les interactions ferromagnétiques classiques, tandis que le second introduit des fluctuations quantiques à travers le champ transversal. Pour simuler la dynamique de TFIM, vous utilisez une décomposition de Trotter de l'opérateur d'évolution unitaire $e^{-iHt}$, mise en œuvre par des couches de portes RX et RZZ basées sur un graphe personnalisé de triangles de spin connectés. La simulation explore la façon dont l'aimantation $\\langle Z \\rangle$ évolue avec l'augmentation des pas de Trotter.\n",
        "\n",
        "La performance de l'implémentation TFIM proposée est évaluée en comparant des simulations sans bruit avec des backends bruyants. Les fonctions améliorées d'exécution et de suppression des erreurs de Fire Opal sont utilisées pour atténuer l'effet du bruit dans le matériel réel, ce qui permet d'obtenir des estimations plus fiables des observables de spin comme $\\langle Z_i \\rangle$ et les corrélateurs $\\langle Z_i Z_j \\rangle$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Exigences\n",
        "\n",
        "Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :\n",
        "\n",
        "* Qiskit SDK v1.4 ou plus tard, avec prise en charge de [la visualisation](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.40 ou plus tard (`pip install qiskit-ibm-runtime`)\n",
        "* Qiskit Functions Catalog v0.9.0 (`pip install qiskit-ibm-catalog`)\n",
        "* Fire Opal SDK v9.0.2 ou plus récent (`pip install fire-opal`)\n",
        "* Q-CTRL Visualizer v8.0.2 ou plus récent (`pip install qctrl-visualizer`)\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ecaa518f",
      "metadata": {},
      "source": [
        "Commencez par vous authentifier à l'aide de votre [clé API IBM Quantum](http://quantum.cloud.ibm.com/). Ensuite, sélectionnez la fonction Qiskit comme suit. (Ce code part du principe que vous avez déjà [enregistré votre compte](/docs/guides/functions-get-started#install-qiskit-functions-catalog-client) dans votre environnement local.)\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "from qiskit.quantum_info import SparsePauliOp\n",
        "from qiskit_aer import AerSimulator\n",
        "\n",
        "import numpy as np\n",
        "import networkx as nx\n",
        "import matplotlib.pyplot as plt\n",
        "import qctrlvisualizer as qv"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e9d916b6",
      "metadata": {},
      "outputs": [],
      "source": [
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "\n",
        "# Access Function\n",
        "perf_mgmt = catalog.load(\"q-ctrl/performance-management\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "988ee237",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "## Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique\n",
        "\n",
        "<span id=\"generate-tfim-graph\" />\n",
        "\n",
        "### Générer un graphique TFIM\n",
        "\n",
        "Nous commençons par définir le réseau de spins et les couplages entre eux. Dans ce tutoriel, le treillis est construit à partir de triangles connectés disposés en chaîne linéaire. Chaque triangle est constitué de trois nœuds reliés en boucle fermée, et la chaîne est formée en reliant un nœud de chaque triangle au triangle précédent.\n",
        "\n",
        "La fonction d'aide `connected_triangles_adj_matrix` construit la matrice d'adjacence pour cette structure. Pour une chaîne de $n$ triangles, le graphe résultant contient $2n+1$ nœuds.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "829f1b6d",
      "metadata": {},
      "outputs": [],
      "source": [
        "def connected_triangles_adj_matrix(n):\n",
        "    \"\"\"\n",
        "    Generate the adjacency matrix for 'n' connected triangles in a chain.\n",
        "    \"\"\"\n",
        "    num_nodes = 2 * n + 1\n",
        "    adj_matrix = np.zeros((num_nodes, num_nodes), dtype=int)\n",
        "\n",
        "    for i in range(n):\n",
        "        a, b, c = i * 2, i * 2 + 1, i * 2 + 2  # Nodes of the current triangle\n",
        "\n",
        "        # Connect the three nodes in a triangle\n",
        "        adj_matrix[a, b] = adj_matrix[b, a] = 1\n",
        "        adj_matrix[b, c] = adj_matrix[c, b] = 1\n",
        "        adj_matrix[a, c] = adj_matrix[c, a] = 1\n",
        "\n",
        "        # If not the first triangle, connect to the previous triangle\n",
        "        if i > 0:\n",
        "            adj_matrix[a, a - 1] = adj_matrix[a - 1, a] = 1\n",
        "\n",
        "    return adj_matrix"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "53869b13",
      "metadata": {},
      "source": [
        "Pour visualiser le treillis que nous venons de définir, nous pouvons tracer la chaîne de triangles connectés et étiqueter chaque nœud. La fonction ci-dessous construit le graphique pour un nombre choisi de triangles et l'affiche.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "2fc293aa",
      "metadata": {},
      "outputs": [],
      "source": [
        "def plot_triangle_chain(n, side=1.0):\n",
        "    \"\"\"\n",
        "    Plot a horizontal chain of n equilateral triangles.\n",
        "    Baseline: even nodes (0,2,4,...,2n) on y=0\n",
        "    Apexes: odd nodes (1,3,5,...,2n-1) above the midpoint.\n",
        "    \"\"\"\n",
        "    # Build graph\n",
        "    A = connected_triangles_adj_matrix(n)\n",
        "    G = nx.from_numpy_array(A)\n",
        "\n",
        "    h = np.sqrt(3) / 2 * side\n",
        "    pos = {}\n",
        "\n",
        "    # Place baseline nodes\n",
        "    for k in range(n + 1):\n",
        "        pos[2 * k] = (k * side, 0.0)\n",
        "\n",
        "    # Place apex nodes\n",
        "    for k in range(n):\n",
        "        x_left = pos[2 * k][0]\n",
        "        x_right = pos[2 * k + 2][0]\n",
        "        pos[2 * k + 1] = ((x_left + x_right) / 2, h)\n",
        "\n",
        "    # Draw\n",
        "    fig, ax = plt.subplots(figsize=(1.5 * n, 2.5))\n",
        "    nx.draw(\n",
        "        G,\n",
        "        pos,\n",
        "        ax=ax,\n",
        "        with_labels=True,\n",
        "        font_size=10,\n",
        "        font_color=\"white\",\n",
        "        node_size=600,\n",
        "        node_color=qv.QCTRL_STYLE_COLORS[0],\n",
        "        edge_color=\"black\",\n",
        "        width=2,\n",
        "    )\n",
        "    ax.set_aspect(\"equal\")\n",
        "    ax.margins(0.2)\n",
        "    plt.show()\n",
        "\n",
        "    return G, pos"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8abc0622",
      "metadata": {},
      "source": [
        "Pour ce tutoriel, nous utiliserons une chaîne de 20 triangles.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "861ab6e3",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transverse-field-ising-model/extracted-outputs/861ab6e3-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "n_triangles = 20\n",
        "n_qubits = 2 * n_triangles + 1\n",
        "plot_triangle_chain(n_triangles, side=1.0)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac6f36e3",
      "metadata": {},
      "source": [
        "<span id=\"coloring-graph-edges\" />\n",
        "\n",
        "### Coloration des arêtes d'un graphe\n",
        "\n",
        "Pour mettre en œuvre le couplage spin-spin, il est utile de regrouper les arêtes qui ne se chevauchent pas. Cela nous permet d'appliquer des portes à deux qubits en parallèle. Pour ce faire, nous utilisons une procédure simple de coloration des arêtes [\\[1\\]](#references), qui attribue une couleur à chaque arête de manière à ce que les arêtes qui se rencontrent au même nœud soient placées dans des groupes différents.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c893b88f",
      "metadata": {},
      "outputs": [],
      "source": [
        "def edge_coloring(graph):\n",
        "    \"\"\"\n",
        "    Takes a NetworkX graph and returns a list of lists\n",
        "    where each inner list contains\n",
        "    the edges assigned the same color.\n",
        "    \"\"\"\n",
        "    line_graph = nx.line_graph(graph)\n",
        "    edge_colors = nx.coloring.greedy_color(line_graph)\n",
        "\n",
        "    color_groups = {}\n",
        "    for edge, color in edge_colors.items():\n",
        "        if color not in color_groups:\n",
        "            color_groups[color] = []\n",
        "        color_groups[color].append(edge)\n",
        "\n",
        "    return list(color_groups.values())"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "b4d480b3",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "## Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique\n",
        "\n",
        "<span id=\"generate-trotterized-circuits-on-spin-graphs\" />\n",
        "\n",
        "### Générer des circuits trotterisés sur des graphes de spin\n",
        "\n",
        "Pour simuler la dynamique du TFIM, nous construisons des circuits qui se rapprochent de l'opérateur d'évolution temporelle.\n",
        "\n",
        "$$\n",
        "U(t) = e^{-i H t}, \\quad \\text{where} \\quad H = -J \\sum_{\\langle i,j \\rangle} Z_i Z_j - h \\sum_i X_i .\n",
        "$$\n",
        "\n",
        "Nous utilisons une décomposition de Trotter du second ordre :\n",
        "\n",
        "$$\n",
        "e^{-i H \\Delta t} \\approx e^{-i H_X \\Delta t / 2}\\, e^{-i H_Z \\Delta t}\\, e^{-i H_X \\Delta t / 2},\n",
        "$$\n",
        "\n",
        "où $H_X = -h \\sum_i X_i$ et $H_Z = -J \\sum_{\\langle i,j \\rangle} Z_i Z_j$.\n",
        "\n",
        "* Le terme $H_X$ est mis en œuvre avec des couches de rotations `RX` .\n",
        "* Le terme $H_Z$ est implémenté avec des couches de portes `RZZ` le long des arêtes du graphe d'interaction.\n",
        "\n",
        "Les angles de ces portes sont déterminés par le champ transversal $h$, la constante de couplage $J$, et le pas de temps $\\Delta t$. En empilant plusieurs étapes de Trotter, nous générons des circuits de profondeur croissante qui se rapprochent de la dynamique du système. Les fonctions `generate_tfim_circ_custom_graph` et `trotter_circuits` construisent un circuit quantique Trotterisé à partir d'un graphe d'interaction de spin arbitraire.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ebe5c712",
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_tfim_circ_custom_graph(\n",
        "    steps, h, J, dt, psi0, graph: nx.graph.Graph, meas_basis=\"Z\", mirror=False\n",
        "):\n",
        "    \"\"\"\n",
        "    Generate a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2)\n",
        "    for simulating a transverse field ising model:\n",
        "    e^{-i H t} where the Hamiltonian H = -J \\\\sum_i Z_i Z_{i+1} + h \\\\sum_i X_i.\n",
        "\n",
        "    steps: Number of trotter steps\n",
        "    theta_x: Angle for layer of X rotations\n",
        "    theta_zz: Angle for layer of ZZ rotations\n",
        "    theta_x: Angle for second layer of X rotations\n",
        "    J: Coupling between nearest neighbor spins\n",
        "    h: The transverse magnetic field strength\n",
        "    dt: t/total_steps\n",
        "    psi0: initial state (assumed to be prepared in the computational basis).\n",
        "    meas_basis: basis to measure all correlators in\n",
        "\n",
        "    This is a second order trotter of the form e^(a+b) ~ e^(b/2) e^a e^(b/2)\n",
        "    \"\"\"\n",
        "    theta_x = h * dt\n",
        "    theta_zz = -2 * J * dt\n",
        "    nq = graph.number_of_nodes()\n",
        "    color_edges = edge_coloring(graph)\n",
        "    circ = QuantumCircuit(nq, nq)\n",
        "    # Initial state, for typical cases in the computational basis\n",
        "    for i, b in enumerate(psi0):\n",
        "        if b == \"1\":\n",
        "            circ.x(i)\n",
        "    # Trotter steps\n",
        "    for step in range(steps):\n",
        "        for i in range(nq):\n",
        "            circ.rx(theta_x, i)\n",
        "        if mirror:\n",
        "            color_edges = [sublist[::-1] for sublist in color_edges[::-1]]\n",
        "        for edge_list in color_edges:\n",
        "            for edge in edge_list:\n",
        "                circ.rzz(theta_zz, edge[0], edge[1])\n",
        "        for i in range(nq):\n",
        "            circ.rx(theta_x, i)\n",
        "\n",
        "    # some typically used basis rotations\n",
        "    if meas_basis == \"X\":\n",
        "        for b in range(nq):\n",
        "            circ.h(b)\n",
        "    elif meas_basis == \"Y\":\n",
        "        for b in range(nq):\n",
        "            circ.sdg(b)\n",
        "            circ.h(b)\n",
        "\n",
        "    for i in range(nq):\n",
        "        circ.measure(i, i)\n",
        "\n",
        "    return circ\n",
        "\n",
        "\n",
        "def trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, mirror=True):\n",
        "    \"\"\"\n",
        "    Generates a sequence of Trotterized circuits, each with increasing depth.\n",
        "    Given a spin interaction graph and Hamiltonian parameters, it constructs\n",
        "    a list of circuits with 1 to d_ind_tot Trotter steps\n",
        "\n",
        "    G: Graph defining spin interactions (edges = ZZ couplings)\n",
        "    d_ind_tot: Number of Trotter steps (maximum depth)\n",
        "    J: Coupling between nearest neighboring spins\n",
        "    h: Transverse magnetic field strength\n",
        "    dt: (t / total_steps\n",
        "    meas_basis: Basis to measure all correlators in\n",
        "    mirror: If True, mirror the Trotter layers\n",
        "    \"\"\"\n",
        "    qubit_count = len(G)\n",
        "    circuits = []\n",
        "    psi0 = \"0\" * qubit_count\n",
        "\n",
        "    for steps in range(1, d_ind_tot + 1):\n",
        "        circuits.append(\n",
        "            generate_tfim_circ_custom_graph(\n",
        "                steps, h, J, dt, psi0, G, meas_basis, mirror\n",
        "            )\n",
        "        )\n",
        "    return circuits"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50b94af2",
      "metadata": {},
      "source": [
        "<span id=\"estimate-single-qubit-magnetizations-$langle-z_i-rangle$\" />\n",
        "\n",
        "### Estimer les magnétisations d'un seul qubit $\\langle Z_i \\rangle$\n",
        "\n",
        "Pour étudier la dynamique du modèle, nous voulons mesurer l'aimantation de chaque qubit, définie par la valeur d'espérance $\\langle Z_i \\rangle = \\langle \\psi | Z_i | \\psi \\rangle$.\n",
        "\n",
        "Dans les simulations, nous pouvons le calculer directement à partir des résultats des mesures. La fonction `z_expectation` traite les comptages de chaînes de bits et renvoie la valeur de $\\langle Z_i \\rangle$ pour un indice de qubit choisi. Sur le matériel réel, nous évaluons la même quantité en spécifiant l'opérateur de Pauli à l'aide de la fonction `generate_z_observables`, puis le backend calcule la valeur de l'espérance.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "3fc929e0",
      "metadata": {},
      "outputs": [],
      "source": [
        "def z_expectation(counts, index):\n",
        "    \"\"\"\n",
        "    counts: Dict of mitigated bitstrings.\n",
        "    index: Index i in the single operator expectation value < II...Z_i...I >\n",
        "        to be calculated.\n",
        "    return:  < Z_i >\n",
        "    \"\"\"\n",
        "    z_exp = 0\n",
        "    tot = 0\n",
        "    for bitstring, value in counts.items():\n",
        "        bit = int(bitstring[index])\n",
        "        sign = 1\n",
        "        if bit % 2 == 1:\n",
        "            sign = -1\n",
        "        z_exp += sign * value\n",
        "        tot += value\n",
        "\n",
        "    return z_exp / tot"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "c9e79716",
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_z_observables(nq):\n",
        "    observables = []\n",
        "    for i in range(nq):\n",
        "        pauli_string = \"\".join([\"Z\" if j == i else \"I\" for j in range(nq)])\n",
        "        observables.append(SparsePauliOp(pauli_string))\n",
        "    return observables"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "598d11cf",
      "metadata": {},
      "outputs": [],
      "source": [
        "observables = generate_z_observables(n_qubits)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "397d9b05",
      "metadata": {},
      "source": [
        "Nous définissons maintenant les paramètres de génération des circuits trotterisés. Dans ce tutoriel, le treillis est une chaîne de 20 triangles connectés, ce qui correspond à un système de 41 qubits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "2853d21b",
      "metadata": {},
      "outputs": [],
      "source": [
        "all_circs_mirror = []\n",
        "for num_triangles in [n_triangles]:\n",
        "    for meas_basis in [\"Z\"]:\n",
        "        A = connected_triangles_adj_matrix(num_triangles)\n",
        "        G = nx.from_numpy_array(A)\n",
        "        nq = len(G)\n",
        "        d_ind_tot = 22\n",
        "        dt = 2 * np.pi * 1 / 30 * 0.25\n",
        "        J = 1\n",
        "        h = -7\n",
        "        all_circs_mirror.extend(\n",
        "            trotter_circuits(G, d_ind_tot, J, h, dt, meas_basis, True)\n",
        "        )\n",
        "circs = all_circs_mirror"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a4b0476d",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "## Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n",
        "<span id=\"run-mps-simulation\" />\n",
        "\n",
        "### Lancer la simulation MPS\n",
        "\n",
        "La liste des circuits Trotterisés est exécutée à l'aide du simulateur `matrix_product_state` avec un choix arbitraire de plans $4096$. La méthode MPS fournit une approximation efficace de la dynamique du circuit, avec une précision déterminée par la dimension de liaison choisie. Pour les tailles de système considérées ici, la dimension de liaison par défaut est suffisante pour capturer la dynamique de l'aimantation avec une grande fidélité. Les comptes bruts sont normalisés et, à partir de ceux-ci, nous calculons les valeurs d'espérance d'un qubit unique $\\langle Z_i \\rangle$ à chaque étape de Trotter. Enfin, nous calculons la moyenne sur l'ensemble des qubits pour obtenir une courbe unique qui montre l'évolution de l'aimantation dans le temps.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "3709531f",
      "metadata": {},
      "outputs": [],
      "source": [
        "backend_sim = AerSimulator(method=\"matrix_product_state\")\n",
        "\n",
        "\n",
        "def normalize_counts(counts_list, shots):\n",
        "    new_counts_list = []\n",
        "    for counts in counts_list:\n",
        "        a = {k: v / shots for k, v in counts.items()}\n",
        "        new_counts_list.append(a)\n",
        "    return new_counts_list\n",
        "\n",
        "\n",
        "def run_sim(circ_list):\n",
        "    shots = 4096\n",
        "    res = backend_sim.run(circ_list, shots=shots)\n",
        "    normed = normalize_counts(res.result().get_counts(), shots)\n",
        "    return normed\n",
        "\n",
        "\n",
        "sim_counts = run_sim(circs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5895aa46",
      "metadata": {},
      "source": [
        "<span id=\"run-on-hardware\" />\n",
        "\n",
        "### Fonctionner sur du matériel\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "4b5a2f87",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.backend(\"ibm_marrakesh\")\n",
        "\n",
        "\n",
        "def run_qiskit(circ_list):\n",
        "    shots = 4096\n",
        "    pm = generate_preset_pass_manager(backend=backend)\n",
        "    isa_circuits = [pm.run(qc) for qc in circ_list]\n",
        "    sampler = Sampler(mode=backend)\n",
        "    res = sampler.run(isa_circuits, shots=shots)\n",
        "    res = [r.data.c.get_counts() for r in res.result()]\n",
        "    normed = normalize_counts(res, shots)\n",
        "    return normed\n",
        "\n",
        "\n",
        "qiskit_counts = run_qiskit(circs)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d30269ee",
      "metadata": {},
      "source": [
        "<span id=\"run-on-hardware-with-fire-opal\" />\n",
        "\n",
        "### Fonctionne sur du matériel équipé de Fire Opal\n",
        "\n",
        "Nous évaluons la dynamique de magnétisation sur du matériel quantique réel. Fire Opal propose une fonction Qiskit qui étend la primitive standard « IBM Quantum Estimator » en y ajoutant des fonctionnalités de suppression automatique des erreurs et de gestion des performances. Nous soumettons les circuits « trotterisés » directement à un backend « IBM® », tandis que Fire Opal se charge de l'exécution tenant compte du bruit.\n",
        "\n",
        "Nous préparons une liste de `pubs`, où chaque élément contient un circuit et les observables Pauli-Z correspondantes. Ces valeurs sont transmises à la fonction d'estimation de Fire Opal, qui renvoie les valeurs d'espérance $\\langle Z_i \\rangle$ pour chaque qubit à chaque étape de Trotter. Les résultats peuvent ensuite être moyennés sur les qubits pour obtenir la courbe de magnétisation à partir du matériel.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ca8348c9",
      "metadata": {},
      "outputs": [],
      "source": [
        "backend_name = \"ibm_marrakesh\"\n",
        "estimator_pubs = [(qc, observables) for qc in all_circs_mirror[:]]\n",
        "\n",
        "# Run the circuit using the estimator\n",
        "qctrl_estimator_job = perf_mgmt.run(\n",
        "    primitive=\"estimator\",\n",
        "    pubs=estimator_pubs,\n",
        "    backend_name=backend_name,\n",
        "    options={\"default_shots\": 4096},\n",
        ")\n",
        "\n",
        "result_qctrl = qctrl_estimator_job.result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "aa081308",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "## Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité\n",
        "\n",
        "Enfin, nous comparons la courbe d'aimantation du simulateur avec les résultats obtenus sur le matériel réel. Le tracé des deux côte à côte montre à quel point l'exécution matérielle avec Fire Opal correspond à la ligne de base sans bruit à travers les étapes de Trotter.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 102,
      "id": "91dd23a5",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_correlators(test_counts, nq, d_ind_tot):\n",
        "    mz = np.empty((nq, d_ind_tot))\n",
        "    for d_ind in range(d_ind_tot):\n",
        "        counts = test_counts[d_ind]\n",
        "        for i in range(nq):\n",
        "            mz[i, d_ind] = z_expectation(counts, i)\n",
        "    average_z = np.mean(mz, axis=0)\n",
        "    return np.concatenate((np.array([1]), average_z), axis=0)\n",
        "\n",
        "\n",
        "sim_exp = make_correlators(sim_counts[0:22], nq=nq, d_ind_tot=22)\n",
        "qiskit_exp = make_correlators(qiskit_counts[0:22], nq=nq, d_ind_tot=22)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 103,
      "id": "1581f9f0",
      "metadata": {},
      "outputs": [],
      "source": [
        "qctrl_exp = [ev.data.evs for ev in result_qctrl[:]]\n",
        "qctrl_exp_mean = np.concatenate(\n",
        "    (np.array([1]), np.mean(qctrl_exp, axis=1)), axis=0\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 26,
      "id": "d1f90811",
      "metadata": {},
      "outputs": [],
      "source": [
        "def make_expectations_plot(\n",
        "    sim_z,\n",
        "    depths,\n",
        "    exp_qctrl=None,\n",
        "    exp_qctrl_error=None,\n",
        "    exp_qiskit=None,\n",
        "    exp_qiskit_error=None,\n",
        "    plot_from=0,\n",
        "    plot_upto=23,\n",
        "):\n",
        "    import numpy as np\n",
        "    import matplotlib.pyplot as plt\n",
        "\n",
        "    depth_ticks = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22]\n",
        "\n",
        "    d = np.asarray(depths)[plot_from:plot_upto]\n",
        "    sim = np.asarray(sim_z)[plot_from:plot_upto]\n",
        "\n",
        "    qk = (\n",
        "        None\n",
        "        if exp_qiskit is None\n",
        "        else np.asarray(exp_qiskit)[plot_from:plot_upto]\n",
        "    )\n",
        "    qc = (\n",
        "        None\n",
        "        if exp_qctrl is None\n",
        "        else np.asarray(exp_qctrl)[plot_from:plot_upto]\n",
        "    )\n",
        "\n",
        "    qk_err = (\n",
        "        None\n",
        "        if exp_qiskit_error is None\n",
        "        else np.asarray(exp_qiskit_error)[plot_from:plot_upto]\n",
        "    )\n",
        "    qc_err = (\n",
        "        None\n",
        "        if exp_qctrl_error is None\n",
        "        else np.asarray(exp_qctrl_error)[plot_from:plot_upto]\n",
        "    )\n",
        "\n",
        "    # ---- helper(s) ----\n",
        "    def rmse(a, b):\n",
        "        if a is None or b is None:\n",
        "            return None\n",
        "        a = np.asarray(a, dtype=float)\n",
        "        b = np.asarray(b, dtype=float)\n",
        "        mask = np.isfinite(a) & np.isfinite(b)\n",
        "        if not np.any(mask):\n",
        "            return None\n",
        "        diff = a[mask] - b[mask]\n",
        "        return float(np.sqrt(np.mean(diff**2)))\n",
        "\n",
        "    def plot_panel(ax, method_y, method_err, color, label, band_color=None):\n",
        "        # Noiseless reference\n",
        "        ax.plot(d, sim, color=\"grey\", label=\"Noiseless simulation\")\n",
        "\n",
        "        # Method line + band\n",
        "        if method_y is not None:\n",
        "            ax.plot(d, method_y, color=color, label=label)\n",
        "            if method_err is not None:\n",
        "                lo = np.clip(method_y - method_err, -1.05, 1.05)\n",
        "                hi = np.clip(method_y + method_err, -1.05, 1.05)\n",
        "                ax.fill_between(\n",
        "                    d,\n",
        "                    lo,\n",
        "                    hi,\n",
        "                    alpha=0.18,\n",
        "                    color=band_color if band_color else color,\n",
        "                    label=f\"{label} ± error\",\n",
        "                )\n",
        "        else:\n",
        "            ax.text(\n",
        "                0.5,\n",
        "                0.5,\n",
        "                \"No data\",\n",
        "                transform=ax.transAxes,\n",
        "                ha=\"center\",\n",
        "                va=\"center\",\n",
        "                fontsize=10,\n",
        "                color=\"0.4\",\n",
        "            )\n",
        "\n",
        "        # RMSE box (vs sim)\n",
        "        r = rmse(method_y, sim)\n",
        "        if r is not None:\n",
        "            ax.text(\n",
        "                0.98,\n",
        "                0.02,\n",
        "                f\"RMSE: {r:.4f}\",\n",
        "                transform=ax.transAxes,\n",
        "                va=\"bottom\",\n",
        "                ha=\"right\",\n",
        "                fontsize=8,\n",
        "                bbox=dict(\n",
        "                    boxstyle=\"round,pad=0.35\", fc=\"white\", ec=\"0.7\", alpha=0.9\n",
        "                ),\n",
        "            )\n",
        "        # Axes\n",
        "        ax.set_xticks(depth_ticks)\n",
        "        ax.set_ylim(-1.05, 1.05)\n",
        "        ax.grid(True, which=\"both\", linewidth=0.4, alpha=0.4)\n",
        "        ax.set_axisbelow(True)\n",
        "        ax.legend(prop={\"size\": 8}, loc=\"best\")\n",
        "\n",
        "    fig, axes = plt.subplots(1, 2, figsize=(10, 4), dpi=300, sharey=True)\n",
        "\n",
        "    axes[0].set_title(\"Fire Opal (Q-CTRL)\", fontsize=10)\n",
        "    plot_panel(\n",
        "        axes[0],\n",
        "        qc,\n",
        "        qc_err,\n",
        "        color=\"#680CE9\",\n",
        "        label=\"Fire Opal\",\n",
        "        band_color=\"#680CE9\",\n",
        "    )\n",
        "    axes[0].set_xlabel(\"Trotter step\")\n",
        "    axes[0].set_ylabel(r\"$\\langle Z \\rangle$\")\n",
        "    axes[1].set_title(\"Qiskit\", fontsize=10)\n",
        "    plot_panel(\n",
        "        axes[1], qk, qk_err, color=\"blue\", label=\"Qiskit\", band_color=\"blue\"\n",
        "    )\n",
        "    axes[1].set_xlabel(\"Trotter step\")\n",
        "\n",
        "    plt.tight_layout()\n",
        "    plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 27,
      "id": "415760ad",
      "metadata": {},
      "outputs": [],
      "source": [
        "depths = list(range(d_ind_tot + 1))\n",
        "errors = np.abs(np.array(qctrl_exp_mean) - np.array(sim_exp))\n",
        "\n",
        "errors_qiskit = np.abs(np.array(qiskit_exp) - np.array(sim_exp))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 28,
      "id": "d4902d14",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/transverse-field-ising-model/extracted-outputs/d4902d14-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "make_expectations_plot(\n",
        "    sim_exp,\n",
        "    depths,\n",
        "    exp_qctrl=qctrl_exp_mean,\n",
        "    exp_qctrl_error=errors,\n",
        "    exp_qiskit=qiskit_exp,\n",
        "    exp_qiskit_error=errors_qiskit,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ee41a301",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Références\n",
        "\n",
        "\\[1] Coloration du graphique. Wikipedia. Consulté le 15 septembre 2025, à l' [adresse suivante : https://en.wikipedia.org/wiki/Graph](https://en.wikipedia.org/wiki/Graph_coloring) \\_coloring\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2cb5785c",
      "metadata": {},
      "source": [
        "<span id=\"tutorial-survey\" />\n",
        "\n",
        "## Enquête tutorielle\n",
        "\n",
        "Veuillez prendre une minute pour nous faire part de vos commentaires sur ce tutoriel. Vos commentaires nous aideront à améliorer notre offre de contenu et l'expérience des utilisateurs.\n",
        "\n",
        "[Lien vers l'enquête](https://your.feedback.ibm.com/jfe/form/SV_3BLFkNVEuh0QBWm)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "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"
    },
    "hours": 1,
    "qpuSeconds": 120
  },
  "nbformat": 4,
  "nbformat_minor": 5
}