{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "c77f1777-ecb8-4cb0-9bf2-49d7c989b12a",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Améliorer la classification des caractéristiques à l'aide de noyaux quantiques projetés\"\n",
        "description: \"Tutoriel sur l'amélioration de la classification des caractéristiques à l'aide de noyaux quantiques projetés\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore braket sqrtm cytotoxicity  Nalm Cytotoxicity binarize Utro Filippo Hsin */}\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bee84331-1f3b-4a23-b691-4d3f7f51e76b",
      "metadata": {},
      "source": [
        "<span id=\"enhance-feature-classification-using-projected-quantum-kernels\" />\n",
        "\n",
        "# Améliorer la classification des caractéristiques à l'aide de noyaux quantiques projetés\n",
        "\n",
        "*Estimation de l'utilisation : 80 minutes sur un processeur Heron r3 (NOTE : Il s'agit uniquement d'une estimation. Votre durée d'exécution peut varier.)*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Acquis d'apprentissage\n",
        "\n",
        "À l'issue de ce tutoriel, les utilisateurs devraient avoir compris :\n",
        "\n",
        "* Comment fonctionnent les noyaux quantiques projetés (PQK) et dans quels cas ils offrent un avantage quantique potentiel.\n",
        "* Comment exécuter un PQK sur du matériel à l'aide d'un ensemble de données réelles.\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prérequis\n",
        "\n",
        "Nous recommandons aux utilisateurs de se familiariser avec les sujets suivants avant de suivre ce tutoriel :\n",
        "\n",
        "* [Noyaux quantiques](/learning/courses/quantum-machine-learning/quantum-kernel-methods) tirés du [cours](/learning/courses/quantum-machine-learning) sur l'apprentissage automatique quantique disponible sur IBM Quantum® Learning\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "Dans ce tutoriel, nous montrons comment exécuter un [noyau quantique projeté](https://www.nature.com/articles/s41467-021-22539-9) (PQK) avec Qiskit sur un ensemble de données biologiques réelles, sur la base de l'article [Enhanced Prediction of CAR T-Cell Cytotoxicity with Quantum-Kernel Methods](https://arxiv.org/abs/2507.22710) [\\[1\\].](#references)\n",
        "\n",
        "Le PQK est une méthode utilisée dans l'apprentissage automatique quantique (QML) pour encoder des données classiques dans un espace quantique de caractéristiques et les projeter à nouveau dans le domaine classique, en utilisant des ordinateurs quantiques pour améliorer la sélection des caractéristiques. Il s'agit d'encoder des données classiques en états quantiques à l'aide d'un circuit quantique, généralement par le biais d'un processus appelé \"feature mapping\", où les données sont transformées en un espace de Hilbert à haute dimension. L'aspect \"projeté\" consiste à extraire des informations classiques des états quantiques, en mesurant des observables spécifiques, afin de construire une matrice de noyau qui peut être utilisée dans des algorithmes classiques basés sur des noyaux, tels que les machines à vecteurs de support. Cette approche exploite les avantages des systèmes quantiques en matière de calcul afin d'améliorer les performances de certaines tâches par rapport aux méthodes classiques.\n",
        "\n",
        "Les principaux éléments constitutifs des PQK sont des matrices de densité réduite (RDM), obtenues par des mesures projectives de la carte de caractéristiques quantiques. On calcule notamment, en règle générale, les matrices de densité réduites à un seul qubit (1 RDM) pour chaque qubit. Ces grandeurs mesurées sont ensuite utilisées comme entrées d'une fonction noyau classique, telle qu'un noyau exponentiel, afin de construire la matrice noyau finale.\n",
        "\n",
        "Les PQK présentent des avantages potentiels par rapport [aux noyaux quantiques](/docs/tutorials/quantum-kernel-training) standard, en particulier pour le matériel quantique à court terme. Les noyaux quantiques standard reposent généralement sur l'estimation des chevauchements d'états globaux, qui deviennent de plus en plus difficiles à mesurer avec précision à mesure que le nombre de qubits augmente et qui sont très sensibles au bruit. En revanche, les PQK utilisent des observables locales telles que les matrices de densité réduites à un seul qubit (1 RDM), ce qui se traduit par une charge d'échantillonnage moindre, une meilleure résistance au bruit matériel et une meilleure évolutivité. En projetant les états quantiques sur des caractéristiques de mesure locales avant d'appliquer une fonction noyau classique, les PQK permettent de conserver des corrélations quantiques utiles tout en restant plus faciles à mettre en œuvre pour les dispositifs à court terme.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "542b8075-3b8c-476c-9513-c03de0f162b1",
      "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 v2.0 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",
        "* Encodeurs de catégorie 2.8.1 (`pip install category-encoders`)\n",
        "* NumPy 2.3.2 (`pip install numpy`)\n",
        "* Pandas 2.3.2 (`pip install pandas`)\n",
        "* Scikit-learn 1.7.1 (`pip install scikit-learn`)\n",
        "* Tqdm 4.67.1 (`pip install tqdm`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2c676996-1361-4b3a-9c94-4784376097b0",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "fe8a02d3-994a-45fe-823d-5e68ded0d717",
      "metadata": {},
      "outputs": [],
      "source": [
        "import warnings\n",
        "\n",
        "# Standard libraries\n",
        "import os\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "\n",
        "# Machine learning and data processing\n",
        "import category_encoders as ce\n",
        "from scipy.linalg import inv, sqrtm\n",
        "from sklearn.metrics.pairwise import rbf_kernel\n",
        "from sklearn.model_selection import GridSearchCV, StratifiedKFold\n",
        "from sklearn.svm import SVC\n",
        "\n",
        "# Qiskit and IBM Quantum Compute Service\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.circuit import ParameterVector\n",
        "from qiskit.circuit.library import UnitaryGate, ZZFeatureMap\n",
        "from qiskit.quantum_info import SparsePauliOp, random_unitary\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime import (\n",
        "    Batch,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        "    QiskitRuntimeService,\n",
        ")\n",
        "\n",
        "# Progress bar\n",
        "import tqdm\n",
        "\n",
        "warnings.filterwarnings(\"ignore\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd92ba56-e9eb-4841-b751-7627f47d756c",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemple de simulateur à petite échelle\n",
        "\n",
        "Nous ne présentons pas d'exemple de simulateur à petite échelle dans ce tutoriel, car notre objectif principal est de montrer comment les noyaux quantiques projetés peuvent s'adapter à des systèmes plus vastes et à du matériel réel.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0fb8a2fa-64b4-434f-8848-e89a098ba73d",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemple de matériel à grande échelle\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2f8775c8-81b5-4732-8325-3f12dc96b45d",
      "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"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb3db2e7-8a3d-4ab1-9e3b-ffc5587dee6d",
      "metadata": {},
      "source": [
        "<span id=\"dataset-preparation\" />\n",
        "\n",
        "### Préparation des ensembles de données\n",
        "\n",
        "Dans ce tutoriel, nous utilisons un ensemble de données biologiques réelles pour une tâche de classification binaire, qui est généré par Daniels et al. (2022) et peut être téléchargé à partir du [matériel supplémentaire](https://www.science.org/doi/full/10.1126/science.abq0225#supplementary-materials) inclus dans l'article. Les données concernent les cellules CAR T, qui sont des cellules T génétiquement modifiées utilisées en immunothérapie pour traiter certains cancers. Les lymphocytes T, un type de cellule immunitaire, sont modifiés en laboratoire pour exprimer des récepteurs antigéniques chimériques (CAR) qui ciblent des protéines spécifiques sur les cellules cancéreuses. Ces cellules T modifiées peuvent reconnaître et détruire les cellules cancéreuses plus efficacement. Les caractéristiques des données sont les motifs des cellules CAR T, qui se réfèrent à la composante structurelle ou fonctionnelle spécifique de la CAR incorporée dans les cellules T. Sur la base de ces motifs, notre tâche consiste à prédire la cytotoxicité d'une cellule CAR T donnée, en la qualifiant de toxique ou de non toxique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2ab0503-1cc9-4512-8f06-12223219cc3e",
      "metadata": {},
      "source": [
        "Les fonctions d'aide pour le prétraitement de cet ensemble de données sont présentées ci-dessous.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "f1ce0222-e7c7-451c-a11c-e61425a6bb8e",
      "metadata": {},
      "outputs": [],
      "source": [
        "def preprocess_data(dir_root, args):\n",
        "    \"\"\"\n",
        "    Preprocess the training and test data.\n",
        "    \"\"\"\n",
        "    # Read from the csv files\n",
        "    train_data = pd.read_csv(\n",
        "        os.path.join(dir_root, args[\"file_train_data\"]),\n",
        "        encoding=\"unicode_escape\",\n",
        "        sep=\",\",\n",
        "    )\n",
        "    test_data = pd.read_csv(\n",
        "        os.path.join(dir_root, args[\"file_test_data\"]),\n",
        "        encoding=\"unicode_escape\",\n",
        "        sep=\",\",\n",
        "    )\n",
        "\n",
        "    # Fix the last motif ID\n",
        "    train_data[train_data == 17] = 14\n",
        "    train_data.columns = [\n",
        "        \"Cell Number\",\n",
        "        \"motif\",\n",
        "        \"motif.1\",\n",
        "        \"motif.2\",\n",
        "        \"motif.3\",\n",
        "        \"motif.4\",\n",
        "        \"Nalm 6 Cytotoxicity\",\n",
        "    ]\n",
        "    test_data[test_data == 17] = 14\n",
        "    test_data.columns = [\n",
        "        \"Cell Number\",\n",
        "        \"motif\",\n",
        "        \"motif.1\",\n",
        "        \"motif.2\",\n",
        "        \"motif.3\",\n",
        "        \"motif.4\",\n",
        "        \"Nalm 6 Cytotoxicity\",\n",
        "    ]\n",
        "\n",
        "    # Adjust motif at the third position\n",
        "    if args[\"filter_for_spacer_motif_third_position\"]:\n",
        "        train_data = train_data[\n",
        "            (train_data[\"motif.2\"] == 14) | (train_data[\"motif.2\"] == 0)\n",
        "        ]\n",
        "        test_data = test_data[\n",
        "            (test_data[\"motif.2\"] == 14) | (test_data[\"motif.2\"] == 0)\n",
        "        ]\n",
        "\n",
        "    train_data = train_data[\n",
        "        args[\"motifs_to_use\"] + [args[\"label_name\"], \"Cell Number\"]\n",
        "    ]\n",
        "    test_data = test_data[\n",
        "        args[\"motifs_to_use\"] + [args[\"label_name\"], \"Cell Number\"]\n",
        "    ]\n",
        "\n",
        "    # Adjust motif at the last position\n",
        "    if not args[\"allow_spacer_motif_last_position\"]:\n",
        "        last_motif = args[\"motifs_to_use\"][len(args[\"motifs_to_use\"]) - 1]\n",
        "        train_data = train_data[\n",
        "            (train_data[last_motif] != 14) & (train_data[last_motif] != 0)\n",
        "        ]\n",
        "        test_data = test_data[\n",
        "            (test_data[last_motif] != 14) & (test_data[last_motif] != 0)\n",
        "        ]\n",
        "\n",
        "    # Get the labels\n",
        "    train_labels = np.array(train_data[args[\"label_name\"]])\n",
        "    test_labels = np.array(test_data[args[\"label_name\"]])\n",
        "\n",
        "    # For the classification task use the threshold to binarize labels\n",
        "    train_labels[train_labels > args[\"label_binarization_threshold\"]] = 1\n",
        "    train_labels[train_labels < 1] = args[\"min_label_value\"]\n",
        "    test_labels[test_labels > args[\"label_binarization_threshold\"]] = 1\n",
        "    test_labels[test_labels < 1] = args[\"min_label_value\"]\n",
        "\n",
        "    # Reduce data to just the motifs of interest\n",
        "    train_data = train_data[args[\"motifs_to_use\"]]\n",
        "    test_data = test_data[args[\"motifs_to_use\"]]\n",
        "\n",
        "    # Get the class and motif counts\n",
        "    min_class = np.min(np.unique(np.concatenate([train_data, test_data])))\n",
        "    max_class = np.max(np.unique(np.concatenate([train_data, test_data])))\n",
        "\n",
        "    num_class = max_class - min_class + 1\n",
        "    num_motifs = len(args[\"motifs_to_use\"])\n",
        "    print(str(max_class) + \":\" + str(min_class) + \":\" + str(num_class))\n",
        "\n",
        "    train_data = train_data - min_class\n",
        "    test_data = test_data - min_class\n",
        "\n",
        "    return (\n",
        "        train_data,\n",
        "        test_data,\n",
        "        train_labels,\n",
        "        test_labels,\n",
        "        num_class,\n",
        "        num_motifs,\n",
        "    )\n",
        "\n",
        "\n",
        "def data_encoder(args, train_data, test_data, num_class, num_motifs):\n",
        "    \"\"\"\n",
        "    Use one-hot or binary encoding for classical data representation.\n",
        "    \"\"\"\n",
        "    if args[\"encoder\"] == \"one-hot\":\n",
        "        # Transform to one-hot encoding\n",
        "        train_data = np.eye(num_class)[train_data]\n",
        "        test_data = np.eye(num_class)[test_data]\n",
        "\n",
        "        train_data = train_data.reshape(\n",
        "            train_data.shape[0], train_data.shape[1] * train_data.shape[2]\n",
        "        )\n",
        "        test_data = test_data.reshape(\n",
        "            test_data.shape[0], test_data.shape[1] * test_data.shape[2]\n",
        "        )\n",
        "\n",
        "    elif args[\"encoder\"] == \"binary\":\n",
        "        # Transform to binary encoding\n",
        "        encoder = ce.BinaryEncoder()\n",
        "\n",
        "        base_array = np.unique(np.concatenate([train_data, test_data]))\n",
        "        base = pd.DataFrame(base_array).astype(\"category\")\n",
        "        base.columns = [\"motif\"]\n",
        "        for motif_name in args[\"motifs_to_use\"][1:]:\n",
        "            base[motif_name] = base.loc[:, \"motif\"]\n",
        "        encoder.fit(base)\n",
        "\n",
        "        train_data = encoder.transform(train_data.astype(\"category\"))\n",
        "        test_data = encoder.transform(test_data.astype(\"category\"))\n",
        "\n",
        "        train_data = np.reshape(\n",
        "            train_data.values, (train_data.shape[0], num_motifs, -1)\n",
        "        )\n",
        "        test_data = np.reshape(\n",
        "            test_data.values, (test_data.shape[0], num_motifs, -1)\n",
        "        )\n",
        "\n",
        "        train_data = train_data.reshape(\n",
        "            train_data.shape[0], train_data.shape[1] * train_data.shape[2]\n",
        "        )\n",
        "        test_data = test_data.reshape(\n",
        "            test_data.shape[0], test_data.shape[1] * test_data.shape[2]\n",
        "        )\n",
        "\n",
        "    else:\n",
        "        raise ValueError(\"Invalid encoding type.\")\n",
        "\n",
        "    return train_data, test_data"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d72e1c86-4855-4a5a-865b-14ba6b15afb7",
      "metadata": {},
      "source": [
        "Vous pouvez exécuter ce tutoriel en lançant la cellule suivante, qui crée automatiquement la structure de dossier requise et télécharge les fichiers de formation et de test directement dans votre environnement. Si vous avez déjà ces fichiers localement, cette étape les écrasera en toute sécurité pour assurer la cohérence des versions.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "84495ee1-880a-48cb-a904-d83396e8b29e",
      "metadata": {},
      "outputs": [],
      "source": [
        "## Download dataset\n",
        "\n",
        "# Create data directory if it doesn't exist\n",
        "!mkdir -p data_tutorial/pqk\n",
        "\n",
        "# Download the training and test sets from the official Qiskit documentation repo\n",
        "!wget -q --show-progress -O data_tutorial/pqk/train_data.csv \\\n",
        "  https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/train_data.csv\n",
        "\n",
        "!wget -q --show-progress -O data_tutorial/pqk/test_data.csv \\\n",
        "  https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/test_data.csv\n",
        "\n",
        "!wget -q --show-progress -O data_tutorial/pqk/projections_train.csv \\\n",
        "  https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/projections_train.csv\n",
        "\n",
        "!wget -q --show-progress -O data_tutorial/pqk/projections_test.csv \\\n",
        "  https://raw.githubusercontent.com/Qiskit/documentation/main/datasets/tutorials/pqk/projections_test.csv\n",
        "\n",
        "# Check the files have been downloaded\n",
        "!echo \"Dataset files downloaded:\"\n",
        "!ls -lh data_tutorial/pqk/*.csv"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "35013bc5-6b5e-44c8-8a8c-3af313b00a82",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "14:0:15\n"
          ]
        }
      ],
      "source": [
        "args = {\n",
        "    \"file_train_data\": \"train_data.csv\",\n",
        "    \"file_test_data\": \"test_data.csv\",\n",
        "    \"motifs_to_use\": [\"motif\", \"motif.1\", \"motif.2\", \"motif.3\"],\n",
        "    \"label_name\": \"Nalm 6 Cytotoxicity\",\n",
        "    \"label_binarization_threshold\": 0.62,\n",
        "    \"filter_for_spacer_motif_third_position\": False,\n",
        "    \"allow_spacer_motif_last_position\": True,\n",
        "    \"min_label_value\": -1,\n",
        "    \"encoder\": \"one-hot\",\n",
        "}\n",
        "dir_root = \"./\"\n",
        "\n",
        "# Preprocess data\n",
        "train_data, test_data, train_labels, test_labels, num_class, num_motifs = (\n",
        "    preprocess_data(dir_root=dir_root, args=args)\n",
        ")\n",
        "\n",
        "# Encode the data\n",
        "train_data, test_data = data_encoder(\n",
        "    args, train_data, test_data, num_class, num_motifs\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "050515c2-64fe-43ce-8559-b58db58b76c3",
      "metadata": {},
      "source": [
        "Nous transformons également l'ensemble de données de manière à ce que $1$ soit représenté par $\\pi/2$ à des fins de mise à l'échelle.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "d40d9a0f-67d0-4704-8a94-cc0a466ffc92",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Change 1 to pi/2\n",
        "angle = np.pi / 2\n",
        "\n",
        "tmp = pd.DataFrame(train_data).astype(\"float64\")\n",
        "tmp[tmp == 1] = angle\n",
        "train_data = tmp.values\n",
        "\n",
        "tmp = pd.DataFrame(test_data).astype(\"float64\")\n",
        "tmp[tmp == 1] = angle\n",
        "test_data = tmp.values"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "726fbdfc-677b-41b7-86c8-dc11adb1946c",
      "metadata": {},
      "source": [
        "Nous vérifions la taille et la forme des ensembles de données de formation et de test.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "b98495f3-aeaa-4df1-a9fe-433e26aa7d4e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "(172, 60) (172,)\n",
            "(74, 60) (74,)\n"
          ]
        }
      ],
      "source": [
        "print(train_data.shape, train_labels.shape)\n",
        "print(test_data.shape, test_labels.shape)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0c828dc0-9bd1-44bc-b299-303766ae3d37",
      "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"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55afd26f-7a53-43bc-920d-88160a61688e",
      "metadata": {},
      "source": [
        "<span id=\"quantum-circuit\" />\n",
        "\n",
        "### Circuit quantique\n",
        "\n",
        "Nous construisons maintenant la carte de caractéristiques qui intègre notre ensemble de données classiques dans un espace de caractéristiques à plus haute dimension. Pour cette intégration, nous utilisons l'outil [`ZZFeatureMap`](/docs/api/qiskit/qiskit.circuit.library.ZZFeatureMap) de Qiskit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "45956df4-5472-4394-a3e1-5514c456791d",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/projected-quantum-kernels/extracted-outputs/45956df4-5472-4394-a3e1-5514c456791d-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "feature_dimension = train_data.shape[1]\n",
        "reps = 24\n",
        "insert_barriers = True\n",
        "entanglement = \"pairwise\"\n",
        "\n",
        "# ZZFeatureMap with linear entanglement and a repetition of 2\n",
        "embed = ZZFeatureMap(\n",
        "    feature_dimension=feature_dimension,\n",
        "    reps=reps,\n",
        "    entanglement=entanglement,\n",
        "    insert_barriers=insert_barriers,\n",
        "    name=\"ZZFeatureMap\",\n",
        ")\n",
        "embed.decompose().draw(output=\"mpl\", style=\"iqp\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bae2e554-2ca2-4e71-a313-b6aec0b25e6a",
      "metadata": {},
      "source": [
        "Une autre option d'intégration quantique est l'ansatz d'évolution du hamiltonien 1D-Heisenberg. Vous pouvez sauter cette section si vous souhaitez poursuivre avec le site `ZZFeatureMap`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "659dbf23-fd3f-4e01-94b4-33e6d672172c",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/projected-quantum-kernels/extracted-outputs/659dbf23-fd3f-4e01-94b4-33e6d672172c-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "feature_dimension = train_data.shape[1]\n",
        "num_qubits = feature_dimension + 1\n",
        "embed2 = QuantumCircuit(num_qubits)\n",
        "num_trotter_steps = 6\n",
        "pv_length = feature_dimension * num_trotter_steps\n",
        "pv = ParameterVector(\"theta\", pv_length)\n",
        "\n",
        "# Add Haar random single qubit unitary to each qubit as initial state\n",
        "np.random.seed(42)\n",
        "seeds_unitary = np.random.randint(0, 100, num_qubits)\n",
        "for i in range(num_qubits):\n",
        "    rand_gate = UnitaryGate(random_unitary(2, seed=seeds_unitary[i]))\n",
        "    embed2.append(rand_gate, [i])\n",
        "\n",
        "\n",
        "def trotter_circ(feature_dimension, num_trotter_steps):\n",
        "    num_qubits = feature_dimension + 1\n",
        "    circ = QuantumCircuit(num_qubits)\n",
        "    # Even\n",
        "    for i in range(0, feature_dimension, 2):\n",
        "        circ.rzz(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    for i in range(0, feature_dimension, 2):\n",
        "        circ.rxx(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    for i in range(0, feature_dimension, 2):\n",
        "        circ.ryy(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    # Odd\n",
        "    for i in range(1, feature_dimension, 2):\n",
        "        circ.rzz(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    for i in range(1, feature_dimension, 2):\n",
        "        circ.rxx(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    for i in range(1, feature_dimension, 2):\n",
        "        circ.ryy(2 * pv[i] / num_trotter_steps, i, i + 1)\n",
        "    return circ\n",
        "\n",
        "\n",
        "# Hamiltonian evolution ansatz\n",
        "for step in range(num_trotter_steps):\n",
        "    circ = trotter_circ(feature_dimension, num_trotter_steps)\n",
        "    if step % 2 == 0:\n",
        "        embed2 = embed2.compose(circ)\n",
        "    else:\n",
        "        reverse_circ = circ.reverse_ops()\n",
        "        embed2 = embed2.compose(reverse_circ)\n",
        "\n",
        "\n",
        "embed2.draw(output=\"mpl\", style=\"iqp\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8b27a3f8-5ee9-41b5-a430-25247545cbb9",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f202d995-8fc0-4af5-b11e-2ed72ac48c84",
      "metadata": {},
      "source": [
        "<span id=\"measure-1-rdms\" />\n",
        "\n",
        "### Mesure 1 - RDM\n",
        "\n",
        "Au cours de cette étape, nous obtenons toutes les matrices de densité réduites à un seul qubit (1-RDM) grâce à des mesures projectives de la carte de caractéristiques quantiques, qui seront ensuite intégrées à la fonction noyau exponentielle classique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea823c34-d7d7-42f8-989c-dfe78cdd489a",
      "metadata": {},
      "source": [
        "Voyons comment calculer le 1-RDM à partir d'un seul point de données de l'ensemble de données avant de passer en revue toutes les données. Les 1-RDM sont une collection de mesures sur un seul qubit des opérateurs Pauli `X`, `Y` et `Z` sur tous les qubits. En effet, un RDM à qubit unique peut être exprimé de la manière suivante : $\\rho = \\frac{1}{2} \\big( I + \\braket \\sigma_x \\sigma_x  + \\braket \\sigma_y \\sigma_y + \\braket \\sigma_z \\sigma_z  \\big)$\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "daa7608c-f482-4df5-a2be-6ca9625bb7e0",
      "metadata": {},
      "source": [
        "Tout d'abord, nous sélectionnons le backend à utiliser.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "e1ab9cea-42ef-478c-bb9d-02ed4cf23ea6",
      "metadata": {},
      "outputs": [],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=133\n",
        ")\n",
        "target = backend.target"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2ce0917f-3826-477b-b503-57e3b5e7e290",
      "metadata": {},
      "source": [
        "Ensuite, nous exécutons le circuit quantique et mesurons les projections. Notez que nous activons l'atténuation des erreurs, y compris l'extrapolation à bruit nul (ZNE).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "53b20cec-ef8a-4fdb-aeed-46546a32ea96",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Let's select the ZZFeatureMap embedding for this example\n",
        "qc = embed\n",
        "num_qubits = feature_dimension\n",
        "\n",
        "# Identity operator on all qubits\n",
        "id = \"I\" * num_qubits\n",
        "\n",
        "# Let's select the first training datapoint as an example\n",
        "parameters = train_data[0]\n",
        "\n",
        "# Bind parameter to the circuit and simplify it\n",
        "qc_bound = qc.assign_parameters(parameters)\n",
        "transpiler = generate_preset_pass_manager(\n",
        "    optimization_level=3, basis_gates=[\"u3\", \"cz\"]\n",
        ")\n",
        "transpiled_circuit = transpiler.run(qc_bound)\n",
        "\n",
        "# Transpile for hardware\n",
        "transpiler = generate_preset_pass_manager(optimization_level=3, target=target)\n",
        "transpiled_circuit = transpiler.run(transpiled_circuit)\n",
        "\n",
        "# We group all commuting observables\n",
        "# These groups are the Pauli X, Y and Z operators on individual qubits\n",
        "observables_x = [\n",
        "    SparsePauliOp(id[:i] + \"X\" + id[(i + 1) :]).apply_layout(\n",
        "        transpiled_circuit.layout\n",
        "    )\n",
        "    for i in range(num_qubits)\n",
        "]\n",
        "observables_y = [\n",
        "    SparsePauliOp(id[:i] + \"Y\" + id[(i + 1) :]).apply_layout(\n",
        "        transpiled_circuit.layout\n",
        "    )\n",
        "    for i in range(num_qubits)\n",
        "]\n",
        "observables_z = [\n",
        "    SparsePauliOp(id[:i] + \"Z\" + id[(i + 1) :]).apply_layout(\n",
        "        transpiled_circuit.layout\n",
        "    )\n",
        "    for i in range(num_qubits)\n",
        "]\n",
        "\n",
        "# We define the primitive unified blocs (PUBs) consisting of the embedding circuit,\n",
        "# set of observables and the circuit parameters\n",
        "pub_x = (transpiled_circuit, observables_x)\n",
        "pub_y = (transpiled_circuit, observables_y)\n",
        "pub_z = (transpiled_circuit, observables_z)\n",
        "\n",
        "# Experiment options for error mitigation\n",
        "num_randomizations = 300\n",
        "shots_per_randomization = 100\n",
        "noise_factors = [1, 3, 5]\n",
        "\n",
        "experimental_opts = {}\n",
        "experimental_opts[\"resilience\"] = {\n",
        "    \"measure_mitigation\": True,\n",
        "    \"zne_mitigation\": True,\n",
        "    \"zne\": {\n",
        "        \"noise_factors\": noise_factors,\n",
        "        \"amplifier\": \"gate_folding\",\n",
        "        \"extrapolated_noise_factors\": [0] + noise_factors,\n",
        "    },\n",
        "}\n",
        "experimental_opts[\"twirling\"] = {\n",
        "    \"num_randomizations\": num_randomizations,\n",
        "    \"shots_per_randomization\": shots_per_randomization,\n",
        "    \"strategy\": \"active-accum\",\n",
        "}\n",
        "\n",
        "# We define and run the estimator to obtain <X>, <Y> and <Z> on all qubits\n",
        "estimator = Estimator(mode=backend, options=experimental_opts)\n",
        "\n",
        "job = estimator.run([pub_x, pub_y, pub_z])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b84baa9d-22a7-410e-a424-2b34e57ff96e",
      "metadata": {},
      "source": [
        "Ensuite, nous récupérons les résultats.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "7a56eda8-3fb2-43e9-8a82-ec64be05b699",
      "metadata": {},
      "outputs": [],
      "source": [
        "job_result_x = job.result()[0].data.evs\n",
        "job_result_y = job.result()[1].data.evs\n",
        "job_result_z = job.result()[2].data.evs"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "1bf21466-ac70-4172-841b-08cedf835645",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "[ 3.67865951e-03  1.01158571e-02 -3.95790878e-02  6.33984326e-03\n",
            "  1.86035759e-02 -2.91533268e-02 -1.06374793e-01  4.48873518e-18\n",
            "  4.70201764e-02  3.53997968e-02  2.53130819e-02  3.23903401e-02\n",
            "  6.06327843e-03  1.16313667e-02 -1.12387504e-02 -3.18457725e-02\n",
            " -4.16445718e-04 -1.45609602e-03 -4.21737114e-01  2.83705669e-02\n",
            "  6.91332890e-03 -7.45363001e-02 -1.20139326e-02 -8.85566135e-02\n",
            " -3.22648394e-02 -3.24228074e-02  6.20431299e-04  3.04225434e-03\n",
            "  5.72795792e-03  1.11288428e-02  1.50395861e-01  9.18380197e-02\n",
            "  1.02553163e-01  2.98312847e-02 -3.30298912e-01 -1.13979648e-01\n",
            "  4.49159340e-03  8.63861493e-02  3.05666566e-02  2.21463145e-04\n",
            "  1.45946735e-02  8.54537275e-03 -8.09805979e-02 -2.92608104e-02\n",
            " -3.91243644e-02 -3.96632760e-02 -1.41187613e-01 -1.07363243e-01\n",
            "  1.81089440e-02  2.70778895e-02  1.45139414e-02  2.99480458e-02\n",
            "  4.99137134e-02  7.08789852e-02  4.30565759e-02  8.71287156e-02\n",
            "  1.04334798e-01  7.72191962e-02  7.10059720e-02  1.04650403e-01]\n",
            "[-7.31765102e-05  7.42669174e-03  9.82277344e-03  5.92638249e-02\n",
            "  4.24120486e-02 -9.06473416e-03  4.55057675e-03  8.43494094e-03\n",
            "  6.92097339e-02 -6.82234424e-02  6.13509008e-02  3.94200491e-02\n",
            " -1.24037979e-02  1.01976642e-01  7.90538600e-03 -7.19726160e-02\n",
            " -1.19501703e-16 -1.03796614e-02  7.37382463e-02  1.97238568e-01\n",
            " -3.59250635e-02 -2.67554009e-02  3.55010633e-02  7.68877990e-02\n",
            "  6.50677589e-05 -6.59298767e-03 -1.23719487e-02 -6.41938151e-02\n",
            "  1.95603072e-02 -2.48448551e-02  5.17784810e-02 -5.93767100e-02\n",
            "  3.11897681e-02 -3.91959720e-18 -4.47769148e-03  1.39202197e-01\n",
            " -6.56387523e-02 -5.85665483e-02  9.52905894e-03 -8.61460731e-02\n",
            "  3.91790656e-02 -1.27544375e-01  1.63712244e-01  3.36816934e-04\n",
            "  2.26230028e-02 -2.45023393e-05  4.95635588e-03  1.44779564e-01\n",
            "  3.71625177e-02  3.65675948e-03  2.83694017e-02 -7.10500602e-02\n",
            " -1.15467702e-01  6.21712129e-03 -4.80958959e-02  2.21021066e-02\n",
            "  7.99062499e-02 -1.87164076e-02 -3.67100369e-02 -2.38923731e-02]\n",
            "[ 6.85871605e-01  5.07725024e-01  8.71024642e-03  3.34823455e-02\n",
            "  4.58684961e-02  9.44384189e-17 -4.46829296e-02 -2.91296778e-02\n",
            "  4.15466461e-02  2.89628330e-02  1.88624017e-03  5.37110446e-02\n",
            "  2.59579053e-03  1.39327071e-02 -2.90781778e-02  5.07209866e-03\n",
            "  5.83403000e-02  2.60764440e-02  4.45999706e-17 -6.66701417e-03\n",
            "  3.03215873e-01  2.26172533e-02  2.43105960e-02  4.98861041e-18\n",
            " -2.45530791e-02  6.26940708e-02  1.21058073e-02  2.76675948e-04\n",
            "  2.63980996e-02  2.58302364e-02  7.47856723e-02  8.42728943e-02\n",
            "  5.70989097e-02  6.92955086e-02 -5.68313712e-03  1.32199452e-01\n",
            "  8.90511238e-02 -3.45204621e-02 -1.05445836e-01  6.03864150e-03\n",
            "  2.16291384e-02  8.22303162e-03  1.00856715e-02  6.28973151e-02\n",
            "  6.26727169e-02  6.15399206e-02  9.67320897e-02  1.03045269e-16\n",
            "  1.79688783e-01 -1.59960520e-02 -1.15422952e-02  9.60200470e-03\n",
            "  6.58396672e-02  7.78329830e-03  6.53226955e-02  2.45778685e-03\n",
            "  4.36694753e-03  5.75098762e-03 -2.48896201e-02  8.33740755e-05]\n"
          ]
        }
      ],
      "source": [
        "print(job_result_x)\n",
        "print(job_result_y)\n",
        "print(job_result_z)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d2772c67-e30b-4e4c-8884-d03c88fe078f",
      "metadata": {},
      "source": [
        "Nous imprimons la taille du circuit et la profondeur de la porte à deux qubits.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "4f573436-ec5c-451b-976c-ad718b3c201d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "qubits: 60\n",
            "2q-depth: 64\n",
            "2q-size: 1888\n",
            "Operator counts: OrderedDict({'rz': 6016, 'sx': 4576, 'cz': 1888, 'x': 896, 'barrier': 31})\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/projected-quantum-kernels/extracted-outputs/4f573436-ec5c-451b-976c-ad718b3c201d-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "print(f\"qubits: {qc.num_qubits}\")\n",
        "print(\n",
        "    f\"2q-depth: {transpiled_circuit.depth(lambda x: x.operation.num_qubits==2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"2q-size: {transpiled_circuit.size(lambda x: x.operation.num_qubits==2)}\"\n",
        ")\n",
        "print(f\"Operator counts: {transpiled_circuit.count_ops()}\")\n",
        "transpiled_circuit.draw(\"mpl\", fold=-1, style=\"clifford\", idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b03a7f5a-4dae-4773-b372-fe04570ad2cd",
      "metadata": {},
      "source": [
        "Nous pouvons à présent parcourir en boucle l'ensemble des données d'apprentissage pour obtenir tous les 1-RDM.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eabb625e-edc1-445f-ad14-5e472d8a2879",
      "metadata": {},
      "source": [
        "Nous présentons également les résultats d'une expérience que nous avons menée sur du matériel quantique. Vous pouvez soit exécuter la formation vous-même en réglant l'indicateur ci-dessous sur `True`, soit utiliser les résultats de la projection que nous fournissons.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "81835ec2-210f-4176-ba79-c8046cc57d92",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Set this to True if you want to run the training on hardware\n",
        "run_experiment = False"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "932201c0-178b-4a98-b2dc-5b4c81953d49",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Training data progress: 100%|██████████| 172/172 [13:03<00:00,  4.55s/it]\n"
          ]
        }
      ],
      "source": [
        "# Identity operator on all qubits\n",
        "id = \"I\" * num_qubits\n",
        "\n",
        "# projections_train[i][j][k] will be the expectation value of the j-th\n",
        "# Pauli operator (0: X, 1: Y, 2: Z) of datapoint i on qubit k\n",
        "projections_train = []\n",
        "jobs_train = []\n",
        "\n",
        "# Experiment options for error mitigation\n",
        "num_randomizations = 300\n",
        "shots_per_randomization = 100\n",
        "noise_factors = [1, 3, 5]\n",
        "\n",
        "experimental_opts = {}\n",
        "experimental_opts[\"resilience\"] = {\n",
        "    \"measure_mitigation\": True,\n",
        "    \"zne_mitigation\": True,\n",
        "    \"zne\": {\n",
        "        \"noise_factors\": noise_factors,\n",
        "        \"amplifier\": \"gate_folding\",\n",
        "        \"return_all_extrapolated\": True,\n",
        "        \"return_unextrapolated\": True,\n",
        "        \"extrapolated_noise_factors\": [0] + noise_factors,\n",
        "    },\n",
        "}\n",
        "experimental_opts[\"twirling\"] = {\n",
        "    \"num_randomizations\": num_randomizations,\n",
        "    \"shots_per_randomization\": shots_per_randomization,\n",
        "    \"strategy\": \"active-accum\",\n",
        "}\n",
        "options = EstimatorOptions(experimental=experimental_opts)\n",
        "\n",
        "if run_experiment:\n",
        "    with Batch(backend=backend):\n",
        "        for i in tqdm.tqdm(\n",
        "            range(len(train_data)), desc=\"Training data progress\"\n",
        "        ):\n",
        "            # Get training sample\n",
        "            parameters = train_data[i]\n",
        "\n",
        "            # Bind parameter to the circuit and simplify it\n",
        "            qc_bound = qc.assign_parameters(parameters)\n",
        "            transpiler = generate_preset_pass_manager(\n",
        "                optimization_level=3, basis_gates=[\"u3\", \"cz\"]\n",
        "            )\n",
        "            transpiled_circuit = transpiler.run(qc_bound)\n",
        "\n",
        "            # Transpile for hardware\n",
        "            transpiler = generate_preset_pass_manager(\n",
        "                optimization_level=3, target=target\n",
        "            )\n",
        "            transpiled_circuit = transpiler.run(transpiled_circuit)\n",
        "\n",
        "            # We group all commuting observables\n",
        "            # These groups are the Pauli X, Y and Z operators on individual qubits\n",
        "            observables_x = [\n",
        "                SparsePauliOp(id[:i] + \"X\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "            observables_y = [\n",
        "                SparsePauliOp(id[:i] + \"Y\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "            observables_z = [\n",
        "                SparsePauliOp(id[:i] + \"Z\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "\n",
        "            # We define the primitive unified blocs (PUBs) consisting\n",
        "            # of the embedding circuit,\n",
        "            # set of observables and the circuit parameters\n",
        "            pub_x = (transpiled_circuit, observables_x)\n",
        "            pub_y = (transpiled_circuit, observables_y)\n",
        "            pub_z = (transpiled_circuit, observables_z)\n",
        "\n",
        "            # We define and run the estimator to obtain <X>, <Y> and <Z>\n",
        "            # on all qubits\n",
        "            estimator = Estimator(options=options)\n",
        "\n",
        "            job = estimator.run([pub_x, pub_y, pub_z])\n",
        "            jobs_train.append(job)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b401f9a9-c2a2-4454-9708-c53e0cbf122b",
      "metadata": {},
      "source": [
        "Une fois les travaux terminés, nous pouvons récupérer les résultats.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "ccbd7603-1dd3-4aab-8ee8-8b0a98068b61",
      "metadata": {},
      "outputs": [],
      "source": [
        "if run_experiment:\n",
        "    for i in tqdm.tqdm(\n",
        "        range(len(train_data)), desc=\"Retrieving training data results\"\n",
        "    ):\n",
        "        # Completed job\n",
        "        job = jobs_train[i]\n",
        "\n",
        "        # Job results\n",
        "        job_result_x = job.result()[0].data.evs\n",
        "        job_result_y = job.result()[1].data.evs\n",
        "        job_result_z = job.result()[2].data.evs\n",
        "\n",
        "        # Record <X>, <Y> and <Z> on all qubits for the current datapoint\n",
        "        projections_train.append([job_result_x, job_result_y, job_result_z])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b03a29c5-c504-4ba9-9eda-4c8bc8817492",
      "metadata": {},
      "source": [
        "Nous répétons cette opération pour l'ensemble des tests.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "f9e77a9c-d295-4893-aebe-74cc59168e1f",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "Test data progress: 100%|██████████| 74/74 [00:13<00:00,  5.56it/s]\n"
          ]
        }
      ],
      "source": [
        "# Identity operator on all qubits\n",
        "id = \"I\" * num_qubits\n",
        "\n",
        "# projections_test[i][j][k] will be the expectation value of the\n",
        "# j-th Pauli operator (0: X, 1: Y, 2: Z) of datapoint i on qubit k\n",
        "projections_test = []\n",
        "jobs_test = []\n",
        "\n",
        "# Experiment options for error mitigation\n",
        "num_randomizations = 300\n",
        "shots_per_randomization = 100\n",
        "noise_factors = [1, 3, 5]\n",
        "\n",
        "experimental_opts = {}\n",
        "experimental_opts[\"resilience\"] = {\n",
        "    \"measure_mitigation\": True,\n",
        "    \"zne_mitigation\": True,\n",
        "    \"zne\": {\n",
        "        \"noise_factors\": noise_factors,\n",
        "        \"amplifier\": \"gate_folding\",\n",
        "        \"return_all_extrapolated\": True,\n",
        "        \"return_unextrapolated\": True,\n",
        "        \"extrapolated_noise_factors\": [0] + noise_factors,\n",
        "    },\n",
        "}\n",
        "experimental_opts[\"twirling\"] = {\n",
        "    \"num_randomizations\": num_randomizations,\n",
        "    \"shots_per_randomization\": shots_per_randomization,\n",
        "    \"strategy\": \"active-accum\",\n",
        "}\n",
        "options = EstimatorOptions(experimental=experimental_opts)\n",
        "\n",
        "if run_experiment:\n",
        "    with Batch(backend=backend):\n",
        "        for i in tqdm.tqdm(range(len(test_data)), desc=\"Test data progress\"):\n",
        "            # Get test sample\n",
        "            parameters = test_data[i]\n",
        "\n",
        "            # Bind parameter to the circuit and simplify it\n",
        "            qc_bound = qc.assign_parameters(parameters)\n",
        "            transpiler = generate_preset_pass_manager(\n",
        "                optimization_level=3, basis_gates=[\"u3\", \"cz\"]\n",
        "            )\n",
        "            transpiled_circuit = transpiler.run(qc_bound)\n",
        "\n",
        "            # Transpile for hardware\n",
        "            transpiler = generate_preset_pass_manager(\n",
        "                optimization_level=3, target=target\n",
        "            )\n",
        "            transpiled_circuit = transpiler.run(transpiled_circuit)\n",
        "\n",
        "            # We group all commuting observables\n",
        "            # These groups are the Pauli X, Y and Z operators on individual qubits\n",
        "            observables_x = [\n",
        "                SparsePauliOp(id[:i] + \"X\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "            observables_y = [\n",
        "                SparsePauliOp(id[:i] + \"Y\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "            observables_z = [\n",
        "                SparsePauliOp(id[:i] + \"Z\" + id[(i + 1) :]).apply_layout(\n",
        "                    transpiled_circuit.layout\n",
        "                )\n",
        "                for i in range(num_qubits)\n",
        "            ]\n",
        "\n",
        "            # We define the primitive unified blocs (PUBs) consisting of\n",
        "            # the embedding circuit,\n",
        "            # set of observables and the circuit parameters\n",
        "            pub_x = (transpiled_circuit, observables_x)\n",
        "            pub_y = (transpiled_circuit, observables_y)\n",
        "            pub_z = (transpiled_circuit, observables_z)\n",
        "\n",
        "            # We define and run the estimator to obtain <X>, <Y> and <Z> on all qubits\n",
        "            estimator = Estimator(options=options)\n",
        "\n",
        "            job = estimator.run([pub_x, pub_y, pub_z])\n",
        "            jobs_test.append(job)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7190af84-d419-4dad-b566-0d66c96e320d",
      "metadata": {},
      "source": [
        "Nous pouvons récupérer les résultats comme précédemment.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "721ed991-fee2-4f66-9bb6-a750ee935033",
      "metadata": {},
      "outputs": [],
      "source": [
        "if run_experiment:\n",
        "    for i in tqdm.tqdm(\n",
        "        range(len(test_data)), desc=\"Retrieving test data results\"\n",
        "    ):\n",
        "        # Completed job\n",
        "        job = jobs_test[i]\n",
        "\n",
        "        # Job results\n",
        "        job_result_x = job.result()[0].data.evs\n",
        "        job_result_y = job.result()[1].data.evs\n",
        "        job_result_z = job.result()[2].data.evs\n",
        "\n",
        "        # Record <X>, <Y> and <Z> on all qubits for the current datapoint\n",
        "        projections_test.append([job_result_x, job_result_y, job_result_z])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b87be787-8751-4093-9547-57315fa13c88",
      "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"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e10f152-d20c-4c70-9530-e9e71c309b59",
      "metadata": {},
      "source": [
        "<span id=\"define-the-projected-quantum-kernel\" />\n",
        "\n",
        "### Définir le noyau quantique projeté\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34150c16-e6ca-43a3-989d-444104dc60e5",
      "metadata": {},
      "source": [
        "Le noyau quantique projeté est défini par la fonction noyau suivante : $k^{\\textrm{PQ}}(x_i, x_j) = \\textrm{exp} \\Big(-\\gamma \\sum_k \\sum_{P \\in \\{ X,Y,Z \\}} (\\textrm{Tr}[P \\rho_k(x_i)] - \\textrm{Tr}[P \\rho_k(x_j)])^2 \\Big) $ Dans l'équation ci-dessus, $\\gamma>0$ est un hyperparamètre accordable. Les $K^{\\textrm{PQ}}_{ij} = k^{\\textrm{PQ}}(x_i, x_j)$ sont les entrées de la matrice du noyau $K^{\\textrm{PQ}}$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42cea21f-3178-4ac9-8826-db7ca7cdfe20",
      "metadata": {},
      "source": [
        "En utilisant la définition des 1-RDM, nous pouvons voir que les termes individuels de la fonction noyau peuvent être évalués comme $\\textrm{Tr}[P \\rho_k (x_i)] = \\braket P$, où $P \\in \\{ X,Y,Z \\}$. Ces valeurs attendues sont précisément celles que nous avons mesurées ci-dessus.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "22e41ed2-93be-4b30-a898-8d2832d1e0ab",
      "metadata": {},
      "source": [
        "En utilisant `scikit-learn`, nous pouvons en fait calculer le noyau encore plus facilement. Ceci est dû au noyau de fonction de base radiale (`'rbf'`) facilement disponible : $ \\textrm{exp} (-\\gamma \\lVert x - x' \\rVert^2)$. Tout d'abord, il suffit de remodeler les nouveaux ensembles de données d'apprentissage et de test projetés en tableaux bidimensionnels.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01abd8a-2194-41b9-adaf-7ec80f14e3b1",
      "metadata": {},
      "source": [
        "Notez que l'examen de l'ensemble des données peut prendre environ 80 minutes sur la QPU. Pour que le reste du didacticiel soit facilement exécutable, nous fournissons également des projections issues d'une expérience précédente (qui sont incluses dans les fichiers que vous avez téléchargés dans le bloc de code `Download dataset` ). Si vous avez effectué la formation vous-même, vous pouvez poursuivre le tutoriel avec vos propres résultats.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "a61e6c67-056b-4659-87a0-284bda432cfd",
      "metadata": {},
      "outputs": [],
      "source": [
        "if run_experiment:\n",
        "    projections_train = np.array(projections_train).reshape(\n",
        "        len(projections_train), -1\n",
        "    )\n",
        "    projections_test = np.array(projections_test).reshape(\n",
        "        len(projections_test), -1\n",
        "    )\n",
        "else:\n",
        "    projections_train = np.loadtxt(\"projections_train.txt\")\n",
        "    projections_test = np.loadtxt(\"projections_test.txt\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2c90c625-27c1-46a0-80a0-5ead9d3c64b7",
      "metadata": {},
      "source": [
        "<span id=\"support-vector-machine-svm\" />\n",
        "\n",
        "### Machine à vecteurs de support (SVM)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b2ea7dde-63ec-4e3d-b6dc-dbeab6a07fda",
      "metadata": {},
      "source": [
        "Nous pouvons maintenant exécuter un SVM classique sur ce noyau précalculé et utiliser le noyau entre les ensembles de test et d'apprentissage pour la prédiction.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "d2eef986-22a5-4528-8fa0-c7dbfd586071",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fitting 10 folds for each of 6622 candidates, totalling 66220 fits\n",
            "The best parameters are {'C': 8.5, 'gamma': 0.01} with a score of 0.6980\n",
            "Test accuracy with best model: 0.8108\n"
          ]
        }
      ],
      "source": [
        "# Range of 'C' and 'gamma' values as SVC hyperparameters\n",
        "C_range = [0.001, 0.005, 0.007]\n",
        "C_range.extend([x * 0.01 for x in range(1, 11)])\n",
        "C_range.extend([x * 0.25 for x in range(1, 60)])\n",
        "C_range.extend(\n",
        "    [\n",
        "        20,\n",
        "        50,\n",
        "        100,\n",
        "        200,\n",
        "        500,\n",
        "        700,\n",
        "        1000,\n",
        "        1100,\n",
        "        1200,\n",
        "        1300,\n",
        "        1400,\n",
        "        1500,\n",
        "        1700,\n",
        "        2000,\n",
        "    ]\n",
        ")\n",
        "\n",
        "gamma_range = [\"auto\", \"scale\", 0.001, 0.005, 0.007]\n",
        "gamma_range.extend([x * 0.01 for x in range(1, 11)])\n",
        "gamma_range.extend([x * 0.25 for x in range(1, 60)])\n",
        "gamma_range.extend([20, 50, 100])\n",
        "\n",
        "param_grid = dict(C=C_range, gamma=gamma_range)\n",
        "\n",
        "# Support vector classifier\n",
        "svc = SVC(kernel=\"rbf\")\n",
        "\n",
        "# Define the cross validation\n",
        "cv = StratifiedKFold(n_splits=10)\n",
        "\n",
        "# Grid search for hyperparameter tuning (q: quantum)\n",
        "grid_search_q = GridSearchCV(\n",
        "    svc, param_grid, cv=cv, verbose=1, n_jobs=-1, scoring=\"f1_weighted\"\n",
        ")\n",
        "grid_search_q.fit(projections_train, train_labels)\n",
        "\n",
        "# Best model with best parameters\n",
        "best_svc_q = grid_search_q.best_estimator_\n",
        "print(\n",
        "    f\"The best parameters are {grid_search_q.best_params_} with a score of {grid_search_q.best_score_:.4f}\"\n",
        ")\n",
        "\n",
        "# Test accuracy\n",
        "accuracy_q = best_svc_q.score(projections_test, test_labels)\n",
        "print(f\"Test accuracy with best model: {accuracy_q:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d54cb69a-8f53-43f1-870a-af273f91e47c",
      "metadata": {},
      "source": [
        "<span id=\"classical-benchmarking\" />\n",
        "\n",
        "### Benchmarking classique\n",
        "\n",
        "Nous pouvons exécuter un SVM classique avec la fonction de base radiale comme noyau sans faire de projection quantique. Ce résultat est notre référence classique.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "41867b5a-9091-4aa4-adab-a05cf6238966",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fitting 10 folds for each of 6622 candidates, totalling 66220 fits\n",
            "The best parameters are {'C': 10.75, 'gamma': 0.04} with a score of 0.7830\n",
            "Test accuracy with best model: 0.7432\n"
          ]
        }
      ],
      "source": [
        "# Support vector classifier\n",
        "svc = SVC(kernel=\"rbf\")\n",
        "\n",
        "# Grid search for hyperparameter tuning (c: classical)\n",
        "grid_search_c = GridSearchCV(\n",
        "    svc, param_grid, cv=cv, verbose=1, n_jobs=-1, scoring=\"f1_weighted\"\n",
        ")\n",
        "grid_search_c.fit(train_data, train_labels)\n",
        "\n",
        "# Best model with best parameters\n",
        "best_svc_c = grid_search_c.best_estimator_\n",
        "print(\n",
        "    f\"The best parameters are {grid_search_c.best_params_} with a score of {grid_search_c.best_score_:.4f}\"\n",
        ")\n",
        "\n",
        "# Test accuracy\n",
        "accuracy_c = best_svc_c.score(test_data, test_labels)\n",
        "print(f\"Test accuracy with best model: {accuracy_c:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b0d367f-4a15-4269-8012-9baef30422fa",
      "metadata": {},
      "source": [
        "<span id=\"appendix-verify-the-datasets-potential-quantum-advantage-in-learning-tasks\" />\n",
        "\n",
        "## Annexe : Vérifier l'avantage quantique potentiel de l'ensemble de données dans les tâches d'apprentissage\n",
        "\n",
        "Tous les ensembles de données ne présentent pas d'avantages potentiels liés à l'utilisation des CQP. Il existe certaines limites théoriques que l'on peut utiliser comme test préliminaire pour voir si un ensemble de données particulier peut bénéficier des PQK. Pour quantifier cela, les auteurs de [Power of data in quantum machine learning](https://www.nature.com/articles/s41467-021-22539-9) \\[2] définissent des quantités appelées complexité des modèles classique et quantique et séparation géométrique des modèles classique et quantique. Pour espérer un avantage quantique potentiel des PQK, la séparation géométrique entre les noyaux classiques et les noyaux projetés quantiquement doit être approximativement de l'ordre de $\\sqrt{N}$, où $N$ est le nombre d'échantillons d'apprentissage. Si cette condition est remplie, nous passons à la vérification de la complexité du modèle. Si la complexité du modèle classique est de l'ordre de $N$ alors que la complexité du modèle quantique projeté est nettement inférieure à $N$, on peut s'attendre à un avantage potentiel du PQK.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d8fae838-a34b-4cf5-ba98-579ec8527fde",
      "metadata": {},
      "source": [
        "La séparation géométrique est définie comme suit ( F19 dans [\\[2\\]](#references) ) : $g_{cq} = g(K^c \\Vert K^q) = \\sqrt{\\Vert \\sqrt{K^q} \\sqrt{K^c} (K^c + \\lambda I)^{-2} \\sqrt{K^c} \\sqrt{K^q}\\Vert_{\\infty}}$\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "bc67f5c0-5d79-4633-807e-02b8cc9d39f5",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Geometric separation between classical and quantum kernels is 1.5440\n",
            "13.114877048604\n"
          ]
        }
      ],
      "source": [
        "# Gamma values used in best models above\n",
        "gamma_c = grid_search_c.best_params_[\"gamma\"]\n",
        "gamma_q = grid_search_q.best_params_[\"gamma\"]\n",
        "\n",
        "# Regularization parameter used in the best classical model above\n",
        "C_c = grid_search_c.best_params_[\"C\"]\n",
        "l_c = 1 / C_c\n",
        "\n",
        "# Classical and quantum kernels used above\n",
        "K_c = rbf_kernel(train_data, train_data, gamma=gamma_c)\n",
        "K_q = rbf_kernel(projections_train, projections_train, gamma=gamma_q)\n",
        "\n",
        "# Intermediate matrices in the equation\n",
        "K_c_sqrt = sqrtm(K_c)\n",
        "K_q_sqrt = sqrtm(K_q)\n",
        "K_c_inv = inv(K_c + l_c * np.eye(K_c.shape[0]))\n",
        "K_multiplication = (\n",
        "    K_q_sqrt @ K_c_sqrt @ K_c_inv @ K_c_inv @ K_c_sqrt @ K_q_sqrt\n",
        ")\n",
        "\n",
        "# Geometric separation\n",
        "norm = np.linalg.norm(K_multiplication, ord=np.inf)\n",
        "g_cq = np.sqrt(norm)\n",
        "print(\n",
        "    f\"Geometric separation between classical and quantum kernels is {g_cq:.4f}\"\n",
        ")\n",
        "\n",
        "print(np.sqrt(len(train_data)))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "68ff51a9-d52e-4103-982b-c81bae17d6a9",
      "metadata": {},
      "source": [
        "La complexité du modèle est définie comme suit ( M1 dans [\\[2\\]](#references) ) : $ s_{K, \\lambda}(N) = \\sqrt{\\frac{\\lambda^2 \\sum_{i=1}^N \\sum_{j=1}^N (K+\\lambda I)^{-2}_{ij} y_i y_j}{N}} + \\sqrt{\\frac{\\sum_{i=1}^N \\sum_{j=1}^N ((K+\\lambda I)^{-1}K(K+\\lambda I)^{-1})_{ij} y_i y_j}{N}}$\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "877cf344-7324-4154-b0d9-7bcfa03b6ac0",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Classical model complexity is 1.3578\n"
          ]
        }
      ],
      "source": [
        "# Model complexity of the classical kernel\n",
        "\n",
        "# Number of training data\n",
        "N = len(train_data)\n",
        "\n",
        "# Predicted labels\n",
        "pred_labels = best_svc_c.predict(train_data)\n",
        "pred_matrix = np.outer(pred_labels, pred_labels)\n",
        "\n",
        "# Intermediate terms\n",
        "K_c_inv = inv(K_c + l_c * np.eye(K_c.shape[0]))\n",
        "\n",
        "# First term\n",
        "first_sum = np.sum((K_c_inv @ K_c_inv) * pred_matrix)\n",
        "first_term = l_c * np.sqrt(first_sum / N)\n",
        "\n",
        "# Second term\n",
        "second_sum = np.sum((K_c_inv @ K_c @ K_c_inv) * pred_matrix)\n",
        "second_term = np.sqrt(second_sum / N)\n",
        "\n",
        "# Model complexity\n",
        "s_c = first_term + second_term\n",
        "print(f\"Classical model complexity is {s_c:.4f}\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "90e9de08-cafc-4493-9358-581c148f3447",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Quantum model complexity is 1.5806\n"
          ]
        }
      ],
      "source": [
        "# Model complexity of the projected quantum kernel\n",
        "\n",
        "# Number of training data\n",
        "N = len(projections_train)\n",
        "\n",
        "# Predicted labels\n",
        "pred_labels = best_svc_q.predict(projections_train)\n",
        "pred_matrix = np.outer(pred_labels, pred_labels)\n",
        "\n",
        "# Regularization parameter used in the best classical model above\n",
        "C_q = grid_search_q.best_params_[\"C\"]\n",
        "l_q = 1 / C_q\n",
        "\n",
        "# Intermediate terms\n",
        "K_q_inv = inv(K_q + l_q * np.eye(K_q.shape[0]))\n",
        "\n",
        "# First term\n",
        "first_sum = np.sum((K_q_inv @ K_q_inv) * pred_matrix)\n",
        "first_term = l_q * np.sqrt(first_sum / N)\n",
        "\n",
        "# Second term\n",
        "second_sum = np.sum((K_q_inv @ K_q @ K_q_inv) * pred_matrix)\n",
        "second_term = np.sqrt(second_sum / N)\n",
        "\n",
        "# Model complexity\n",
        "s_q = first_term + second_term\n",
        "print(f\"Quantum model complexity is {s_q:.4f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e29581c7-ec49-4a03-837f-ee1fe9178846",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Etapes suivantes\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommandations\">\n",
        "  Si ce travail vous a paru intéressant, les documents suivants pourraient vous intéresser :\n",
        "\n",
        "  * [Cours](/learning/courses/quantum-machine-learning) approfondi sur l'apprentissage automatique quantique proposé par l' IBM Quantum Learning\n",
        "  * Tutoriel [sur l'apprentissage par noyaux quantiques](/docs/tutorials/quantum-kernel-training)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f082899c-b763-4df0-a81c-5efb3ca43451",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Références\n",
        "\n",
        "1. Utro, Filippo, et al. « [Prédiction améliorée de la cytotoxicité des cellules CAR T grâce aux méthodes à noyau quantique](https://arxiv.org/abs/2507.22710) ». arXiv preprint arXiv:2507.22710 (2025).\n",
        "2. Huang, Hsin-Yuan, et al. \"[Power of data in quantum machine learning](https://www.nature.com/articles/s41467-021-22539-9) \" Nature communications 12.1 (2021) : 2631.\n",
        "3. Daniels, Kyle G., et al. « [Décodage du phénotype des cellules CAR T à l'aide de bibliothèques de motifs de signalisation combinatoires et d'apprentissage automatique](https://www.science.org/doi/full/10.1126/science.abq0225) ». Science 378.6625 (2022) : 1194-1200.\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"
    },
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    },
    "hours": 2.5,
    "qpuSeconds": 4800
  },
  "nbformat": 4,
  "nbformat_minor": 4
}