{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "c77f1777-ecb8-4cb0-9bf2-49d7c989b12a",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Mejora de la clasificación de características mediante núcleos cuánticos proyectados\"\n",
        "description: \"Tutorial sobre la mejora de la clasificación de características mediante núcleos cuánticos proyectados\"\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",
        "# Mejora de la clasificación de características mediante núcleos cuánticos proyectados\n",
        "\n",
        "*Estimación de uso: 80 minutos en un procesador Heron r3 (NOTA: Esto es sólo una estimación. Su tiempo de ejecución puede variar)*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "Una vez completado este tutorial, los usuarios deberían comprender:\n",
        "\n",
        "* Cómo funcionan los núcleos cuánticos proyectados (PQK) y en qué casos ofrecen una posible ventaja cuántica.\n",
        "* Cómo ejecutar un PQK en hardware utilizando un conjunto de datos del mundo real.\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "Recomendamos a los usuarios que se familiaricen con los siguientes temas antes de seguir este tutorial:\n",
        "\n",
        "* [Núcleos cuánticos](/learning/courses/quantum-machine-learning/quantum-kernel-methods) del [curso de aprendizaje automático cuántico](/learning/courses/quantum-machine-learning) en IBM Quantum® Learning\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "En este tutorial, demostramos cómo ejecutar un [kernel cuántico proyectado](https://www.nature.com/articles/s41467-021-22539-9) (PQK) con Qiskit en un conjunto de datos biológicos del mundo real, basado en el artículo [Enhanced Prediction of CAR T-Cell Cytotoxicity with Quantum-Kernel Methods](https://arxiv.org/abs/2507.22710) [\\[1\\]](#references).\n",
        "\n",
        "PQK es un método utilizado en el aprendizaje automático cuántico (QML) para codificar datos clásicos en un espacio de características cuánticas y proyectarlos de nuevo en el dominio clásico, utilizando ordenadores cuánticos para mejorar la selección de características. Consiste en codificar datos clásicos en estados cuánticos mediante un circuito cuántico, normalmente a través de un proceso denominado mapeo de características, en el que los datos se transforman en un espacio de Hilbert de alta dimensión. El aspecto \"proyectado\" se refiere a la extracción de información clásica de los estados cuánticos, mediante la medición de observables específicos, para construir una matriz de núcleos que pueda utilizarse en algoritmos clásicos basados en núcleos, como las máquinas de vectores de soporte. Este enfoque aprovecha las ventajas computacionales de los sistemas cuánticos para lograr potencialmente un mejor rendimiento en determinadas tareas en comparación con los métodos clásicos.\n",
        "\n",
        "Los componentes fundamentales de los PQK son las matrices de densidad reducida (RDM), que se obtienen mediante mediciones proyectivas del mapa de características cuánticas. En concreto, lo habitual es calcular las matrices de densidad reducida de un solo qubit (1 RDM) para cada qubit. A continuación, estas magnitudes medidas se utilizan como entradas de una función de núcleo clásica, como un núcleo exponencial, para construir la matriz de núcleo final.\n",
        "\n",
        "Los PQK ofrecen ventajas potenciales con respecto a [los núcleos cuánticos](/docs/tutorials/quantum-kernel-training) estándar, especialmente para el hardware cuántico a corto plazo. Los núcleos cuánticos estándar suelen basarse en la estimación de los solapamientos globales de los estados, que resultan cada vez más difíciles de medir con precisión a medida que aumenta el número de qubits y que son muy sensibles al ruido. Por el contrario, los PQK utilizan observables locales, como las matrices de densidad reducidas de un solo qubit (1 RDM), lo que se traduce en una menor sobrecarga de muestreo, una mayor robustez frente al ruido del hardware y una mejor escalabilidad. Al proyectar los estados cuánticos sobre características de medición locales antes de aplicar una función kernel clásica, las PQK pueden conservar correlaciones cuánticas útiles sin dejar de ser más prácticas para los dispositivos a corto plazo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "542b8075-3b8c-476c-9513-c03de0f162b1",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de que tienes instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.0 o posterior, con soporte [de visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.40 o posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Codificadores de categoría 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",
        "## Configuración\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 Qiskit Runtime\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",
        "## Ejemplo de simulador a pequeña escala\n",
        "\n",
        "En este tutorial omitimos el ejemplo del simulador a pequeña escala, ya que nuestro objetivo principal es demostrar cómo los núcleos cuánticos proyectados pueden adaptarse a sistemas más grandes y a hardware real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0fb8a2fa-64b4-434f-8848-e89a098ba73d",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware a gran escala\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",
        "### Paso 1: Asignar entradas clásicas a un problema cuántico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb3db2e7-8a3d-4ab1-9e3b-ffc5587dee6d",
      "metadata": {},
      "source": [
        "<span id=\"dataset-preparation\" />\n",
        "\n",
        "### Preparación del conjunto de datos\n",
        "\n",
        "En este tutorial utilizamos un conjunto de datos biológicos del mundo real para una tarea de clasificación binaria, generado por Daniels et al. (2022) y que puede descargarse del [material suplementario](https://www.science.org/doi/full/10.1126/science.abq0225#supplementary-materials) incluido con el artículo. Los datos consisten en células T CAR, que son células T modificadas genéticamente que se utilizan en inmunoterapia para tratar determinados tipos de cáncer. Las células T, un tipo de célula inmunitaria, se modifican en el laboratorio para que expresen receptores de antígenos quiméricos (CAR) dirigidos a proteínas específicas de las células cancerosas. Estas células T modificadas pueden reconocer y destruir las células cancerosas con mayor eficacia. Las características de los datos son los motivos de las células T CAR, que se refieren al componente estructural o funcional específico de la CAR diseñada en las células T. Basándonos en estos motivos, nuestra tarea consiste en predecir la citotoxicidad de una determinada célula T CAR, etiquetándola como tóxica o no tóxica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2ab0503-1cc9-4512-8f06-12223219cc3e",
      "metadata": {},
      "source": [
        "A continuación se muestran las funciones de ayuda para preprocesar este conjunto de datos.\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": [
        "Puede ejecutar este tutorial ejecutando la siguiente celda, que crea automáticamente la estructura de carpetas necesaria y descarga los archivos de formación y de prueba directamente en su entorno. Si ya tiene estos archivos localmente, este paso los sobrescribirá de forma segura para garantizar la coherencia de la versión.\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": [
        "También transformamos el conjunto de datos de forma que $1$ se represente como $\\pi/2$ a efectos de escalado.\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": [
        "Verificamos los tamaños y las formas de los conjuntos de datos de entrenamiento y de prueba.\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",
        "### Paso 2: Optimizar el problema para la ejecución en hardware cuántico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55afd26f-7a53-43bc-920d-88160a61688e",
      "metadata": {},
      "source": [
        "<span id=\"quantum-circuit\" />\n",
        "\n",
        "### Circuito cuántico\n",
        "\n",
        "Ahora construimos el mapa de características que integra nuestro conjunto de datos clásico en un espacio de características de mayor dimensión. Para esta incrustación, utilizamos el [`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": [
        "Otra opción de incrustación cuántica es la 1D-Heisenberg Hamiltonian evolution ansatz. Puede omitir la ejecución de esta sección si desea continuar con la `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",
        "### Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f202d995-8fc0-4af5-b11e-2ed72ac48c84",
      "metadata": {},
      "source": [
        "<span id=\"measure-1-rdms\" />\n",
        "\n",
        "### Medida 1-RDM\n",
        "\n",
        "En este paso, obtenemos todas las matrices de densidad reducidas de un solo qubit (1-RDM) mediante mediciones proyectivas del mapa de características cuánticas, que posteriormente se introducirán en la función kernel exponencial clásica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea823c34-d7d7-42f8-989c-dfe78cdd489a",
      "metadata": {},
      "source": [
        "Veamos cómo calcular el 1-RDM a partir de un único punto de datos del conjunto de datos antes de ejecutar todos los datos. Los 1-RDM son una colección de medidas de un solo qubit de los operadores Pauli `X`, `Y` y `Z` en todos los qubits. Esto se debe a que un RDM de un solo qubit puede expresarse completamente como: $\\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": [
        "Primero seleccionamos el backend a utilizar.\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": [
        "A continuación, ejecutamos el circuito cuántico y medimos las proyecciones. Tenga en cuenta que activamos la mitigación de errores, incluida la Extrapolación de Ruido Cero (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": [
        "A continuación, recuperamos los resultados.\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": [
        "Imprimimos el tamaño del circuito y la profundidad de la puerta de dos 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": [
        "Ahora podemos hacer un bucle sobre todo el conjunto de datos de entrenamiento para obtener todos los 1-RDM.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eabb625e-edc1-445f-ad14-5e472d8a2879",
      "metadata": {},
      "source": [
        "También ofrecemos los resultados de un experimento que realizamos con hardware cuántico. Puede ejecutar el entrenamiento usted mismo configurando el indicador de abajo en `True`, o utilizar los resultados de proyección que le proporcionamos.\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": [
        "Una vez finalizados los trabajos, podemos recuperar los resultados.\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": [
        "Repetimos esta operación para el conjunto de pruebas.\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": [
        "Podemos recuperar los resultados como antes.\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",
        "### Paso 4: Procesamiento posterior y devolución del resultado en el formato clásico deseado\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e10f152-d20c-4c70-9530-e9e71c309b59",
      "metadata": {},
      "source": [
        "<span id=\"define-the-projected-quantum-kernel\" />\n",
        "\n",
        "### Definir el núcleo cuántico proyectado\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34150c16-e6ca-43a3-989d-444104dc60e5",
      "metadata": {},
      "source": [
        "El núcleo cuántico proyectado se define con la siguiente función de núcleo: $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) $ En la ecuación anterior, $\\gamma>0$ es un hiperparámetro sintonizable. $K^{\\textrm{PQ}}_{ij} = k^{\\textrm{PQ}}(x_i, x_j)$ son las entradas de la matriz del núcleo $K^{\\textrm{PQ}}$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42cea21f-3178-4ac9-8826-db7ca7cdfe20",
      "metadata": {},
      "source": [
        "Utilizando la definición de 1-RDMs, podemos ver que los términos individuales dentro de la función kernel pueden evaluarse como $\\textrm{Tr}[P \\rho_k (x_i)] = \\braket P$, donde $P \\in \\{ X,Y,Z \\}$. Estos valores esperados son precisamente los que hemos medido anteriormente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "22e41ed2-93be-4b30-a898-8d2832d1e0ab",
      "metadata": {},
      "source": [
        "De hecho, utilizando `scikit-learn`, podemos calcular el núcleo aún más fácilmente. Esto se debe al núcleo de la función de base radial (`'rbf'`) fácilmente disponible: $ \\textrm{exp} (-\\gamma \\lVert x - x' \\rVert^2)$. En primer lugar, sólo tenemos que remodelar los nuevos conjuntos de datos de entrenamiento y prueba proyectados en matrices bidimensionales.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01abd8a-2194-41b9-adaf-7ec80f14e3b1",
      "metadata": {},
      "source": [
        "Tenga en cuenta que repasar todo el conjunto de datos puede llevar unos 80 minutos en la QPU. Para asegurarnos de que el resto del tutorial es fácilmente ejecutable, proporcionamos además proyecciones de un experimento ejecutado previamente (que se incluyen en los archivos que ha descargado en el bloque de código `Download dataset` ). Si ha realizado el entrenamiento usted mismo, puede continuar el tutorial con sus propios resultados.\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",
        "### Máquina de vectores de soporte (SVM)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b2ea7dde-63ec-4e3d-b6dc-dbeab6a07fda",
      "metadata": {},
      "source": [
        "Ahora podemos ejecutar una SVM clásica en este kernel precalculado, y utilizar el kernel entre los conjuntos de prueba y entrenamiento para la predicción.\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 clásico\n",
        "\n",
        "Podemos ejecutar una SVM clásica con la función de base radial como núcleo sin hacer una proyección cuántica. Este resultado es nuestra referencia clásica.\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",
        "## Apéndice: Verificar la ventaja cuántica potencial del conjunto de datos en tareas de aprendizaje\n",
        "\n",
        "No todos los conjuntos de datos ofrecen ventajas potenciales con el uso de PQK. Existen algunos límites teóricos que se pueden utilizar como prueba preliminar para ver si un conjunto de datos concreto puede beneficiarse de las PQK. Para cuantificarlo, los autores de [Power of data in quantum machine learning](https://www.nature.com/articles/s41467-021-22539-9) \\[2] definen unas cantidades denominadas complejidades de los modelos clásico y cuántico y separación geométrica de los modelos clásico y cuántico. Para esperar una ventaja cuántica potencial de las PQK, la separación geométrica entre los núcleos clásicos y los proyectados cuánticamente debería ser aproximadamente del orden de $\\sqrt{N}$, donde $N$ es el número de muestras de entrenamiento. Si se cumple esta condición, pasamos a comprobar las complejidades del modelo. Si la complejidad del modelo clásico es del orden de $N$ mientras que la complejidad del modelo cuántico proyectado es sustancialmente menor que $N$, podemos esperar una ventaja potencial del PQK.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d8fae838-a34b-4cf5-ba98-579ec8527fde",
      "metadata": {},
      "source": [
        "La separación geométrica se define como sigue ( F19 en [\\[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 complejidad del modelo se define como sigue ( M1 en [\\[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",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  Si este trabajo te ha parecido interesante, quizá te interese el siguiente material:\n",
        "\n",
        "  * [Curso](/learning/courses/quantum-machine-learning) avanzado de aprendizaje automático cuántico de IBM Quantum Learning\n",
        "  * Tutorial [sobre el entrenamiento de kernels cuánticos](/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",
        "## Referencias\n",
        "\n",
        "1. Utro, Filippo, et al. \"[Enhanced Prediction of CAR T-Cell Cytotoxicity with Quantum-Kernel](https://arxiv.org/abs/2507.22710) Methods\" arXiv preprint arXiv:2507.22710 (2025).\n",
        "2. Huang, Hsin-Yuan, et al. \"[El poder de los datos en el aprendizaje automático cuántico](https://www.nature.com/articles/s41467-021-22539-9) \" Nature communications 12.1 (2021): 2631.\n",
        "3. Daniels, Kyle G., et al. \"[Decoding CAR T cell phenotype using combinatorial signaling motif libraries and machine learning](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
}