{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "4ba97b35-a453-42fb-a59a-e9f0c129283f",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Formación sobre kernel cuántico\"\n",
        "description: \"Crear un patrón Qiskit para evaluar entradas en una matriz de kernel cuántico utilizada para la clasificación binaria.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore mapsto forall */}\n",
        "\n",
        "<span id=\"quantum-kernel-training\" />\n",
        "\n",
        "# Formación sobre kernel cuántico\n",
        "\n",
        "*Tiempo estimado de ejecución: menos de un minuto en un procesador Heron r3 (NOTA: Se trata solo de una estimación). (El tiempo de ejecución puede variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd3bec67",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados del aprendizaje\n",
        "\n",
        "Una vez completado este tutorial, habrás adquirido los siguientes conocimientos:\n",
        "\n",
        "* Métodos del núcleo y sus aplicaciones\n",
        "* Los núcleos cuánticos y cómo pueden proporcionar espacios de características mejorados\n",
        "* Construcción de circuitos de núcleo cuántico\n",
        "* Cómo entrenar un kernel cuántico utilizando un [patrón de Qiskit](/docs/guides/intro-to-patterns) : mapear, optimizar, ejecutar y posprocesar\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba36136c",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Requisitos previos\n",
        "\n",
        "Se recomienda que te familiarices con los núcleos cuánticos, por qué son importantes y cómo se utilizan en la práctica.\n",
        "\n",
        "* [Núcleos cuánticos covariantes para datos con estructura de grupo](https://arxiv.org/abs/2105.03406) (artículo)\n",
        "* [Introducción a los núcleos cuánticos y las máquinas de vectores de soporte](https://www.youtube.com/watch?v=GVhCOTzAkCM) (vídeo)\n",
        "* [Núcleos cuánticos en la práctica](https://www.youtube.com/watch?v=LmQcSxgINis) (vídeo)\n",
        "\n",
        "También es útil tener unos conocimientos básicos de teoría de grupos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2d486a5",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## En segundo plano\n",
        "\n",
        "Los métodos de kernel son habituales en las aplicaciones de aprendizaje automático.\n",
        "En este contexto, el término «núcleo» se refiere a la matriz del núcleo o a las entradas individuales que la componen.\n",
        "En general, un kernel es una medida de similitud entre datos codificados en un *espacio de características* de alta dimensión y puede utilizarse, por ejemplo, en tareas de clasificación con máquinas de vectores de soporte.\n",
        "\n",
        "Los métodos de kernel cuántico son aquellos que utilizan ordenadores cuánticos para estimar un kernel.\n",
        "Se sabe que los ordenadores cuánticos pueden codificar datos en espacios de características mejorados cuánticamente, sustituyendo de hecho a sus análogos clásicos.\n",
        "Para $\\vec{x} \\in \\mathbb{R}$ y $\\Psi(\\vec{x}) \\in \\mathbb{R}^{d'}$, normalmente junto con $d' >d$, $\\Psi(\\vec{x})$ es un mapa de características, $\\vec{x} \\mapsto \\Psi(\\vec{x})$.\n",
        "El objetivo de $\\Psi(\\vec{x})$ es separar las categorías de datos mediante un hiperplano.\n",
        "Tomando como argumentos los vectores en el espacio de mapas de características, la función de núcleo $K(\\vec{x}, \\vec{y}) = \\langle{\\Psi(\\vec{x}) | \\Psi(\\vec{y}) \\rangle{}}$ devuelve su producto interno: $K: \\mathbb{R}^d \\rightarrow$ $\\mathbb{R}^d$.\n",
        "Tradicionalmente, los mapas de características de interés son aquellos en los que la función de núcleo puede evaluarse fácilmente;\n",
        "es decir, cuando el producto interno en el espacio de mapas de características puede expresarse en términos de los vectores de datos originales y no es necesario construir $\\Psi(\\vec{x})$ ni $\\Psi(\\vec{y})$.\n",
        "En el caso de los núcleos cuánticos, la correspondencia de características se lleva a cabo mediante un circuito cuántico, y el núcleo se estima utilizando las probabilidades de medición obtenidas del circuito.\n",
        "\n",
        "Este tutorial muestra cómo crear un patrón de Qiskit para evaluar las entradas en una matriz de núcleo cuántico utilizada para la clasificación binaria.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad7bc6f9",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de empezar este tutorial, asegúrate de tener instalado lo siguiente:\n",
        "\n",
        "* Qiskit SDK v2.3.1 o posterior, con soporte [para visualización](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.44.0 o posterior (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dfb22142-2377-40bf-801c-b2116f048a9a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuración\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "4405f2da-ce6e-4b97-960a-bb04730cfb6d",
      "metadata": {},
      "outputs": [],
      "source": [
        "# General Imports and helper functions\n",
        "import urllib.request\n",
        "\n",
        "import pandas as pd\n",
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "\n",
        "from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit\n",
        "from qiskit.circuit.library import unitary_overlap\n",
        "from qiskit.primitives import StatevectorSampler\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, Sampler\n",
        "\n",
        "# Download the dataset (portable across platforms)\n",
        "urllib.request.urlretrieve(\n",
        "    \"https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv\",\n",
        "    \"dataset_graph7.csv\",\n",
        ")\n",
        "\n",
        "\n",
        "def visualize_counts(res_counts, num_qubits, num_shots):\n",
        "    \"\"\"Visualize the outputs from the Qiskit Sampler primitive.\"\"\"\n",
        "    zero_prob = res_counts.get(0, 0.0)\n",
        "    top_10 = dict(\n",
        "        sorted(res_counts.items(), key=lambda item: item[1], reverse=True)[\n",
        "            :10\n",
        "        ]\n",
        "    )\n",
        "    top_10.update({0: zero_prob})\n",
        "    by_key = dict(sorted(top_10.items(), key=lambda item: item[0]))\n",
        "    x_vals, y_vals = list(zip(*by_key.items()))\n",
        "    x_vals = [bin(x_val)[2:].zfill(num_qubits) for x_val in x_vals]\n",
        "    y_vals_prob = []\n",
        "    for t in range(len(y_vals)):\n",
        "        y_vals_prob.append(y_vals[t] / num_shots)\n",
        "    y_vals = y_vals_prob\n",
        "    plt.bar(x_vals, y_vals)\n",
        "    plt.xticks(rotation=75)\n",
        "    plt.title(\"Results of sampling\")\n",
        "    plt.xlabel(\"Measured bitstring\")\n",
        "    plt.ylabel(\"Probability\")\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "def get_training_data():\n",
        "    \"\"\"Read the training data.\"\"\"\n",
        "    df = pd.read_csv(\"dataset_graph7.csv\", sep=\",\", header=None)\n",
        "    training_data = df.values[:20, :]\n",
        "    ind = np.argsort(training_data[:, -1])\n",
        "    X_train = training_data[ind][:, :-1]\n",
        "\n",
        "    return X_train"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-scale-intro",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Ejemplo de simulador a pequeña escala\n",
        "\n",
        "En esta sección, repasamos los cuatro pasos del patrón de Qiskit en una instancia de siete qubits del problema de etiquetado de clases de resto con error y evaluamos una sola entrada de la matriz del núcleo utilizando la `StatevectorSampler` primitiva de Qiskit. Un simulador de vectores de estado es exacto (salvo por el ruido de disparo) y nos muestra el método de principio a fin sin consumir tiempo de la QPU. A continuación, repetimos el mismo ejemplo en hardware real en la sección de ejemplos de hardware.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6ddce42e-a365-451d-972d-24c1306ac47b",
      "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",
        "* Entrada: Conjunto de datos de entrenamiento.\n",
        "* Salida: Circuito abstracto para calcular una entrada de la matriz kernel.\n",
        "\n",
        "El problema de clasificación binaria que pretendemos resolver aquí se conoce como [*«etiquetado de clases con error*](https://arxiv.org/abs/2105.03406) » El conjunto de datos de entrenamiento de entrada contiene una estructura de grupo, compuesta por dos clases formadas por un grupo y un subgrupo.\n",
        "Se considera que el grupo es un grupo de $G = SU(2)^{\\otimes n}$ es para los qubits, que es el grupo unitario especial de\n",
        "matrices $2 \\times 2$ es y tiene una amplia aplicabilidad en la naturaleza; por ejemplo, en el Modelo Estándar de la física de partículas.\n",
        "Tomamos el subgrupo (estabilizador del grafo) $S_\\text{graph} < G$, donde $S_\\text{graph} = \\langle \\{ X_i \\otimes _{k:(k,i) \\in \\mathcal{E}} Z_k\\} _{i \\in \\mathcal{V}} \\} \\rangle$, para un grafo con aristas $\\mathcal{E}$ y vértices $\\mathcal{V}$.\n",
        "Obsérvese que los estabilizadores fijan un estado de estabilizador tal que $D_s | \\psi \\rangle = | \\psi \\rangle,~ \\forall s \\in S_\\text{graph}$.\n",
        "Por último, definimos dos cosets a la izquierda $C_\\pm = c_\\pm S_\\text{graph}$ seleccionando al azar dos $c_\\pm \\in G$.\n",
        "\n",
        "Para obtener más información sobre el conjunto de datos y cómo se genera, consulta [este cuaderno de trabajo](https://github.com/qiskit-community/prototype-quantum-kernel-training/blob/main/docs/background/qkernels_and_data_w_group_structure.ipynb) del [Quantum Kernel Training Toolkit](https://github.com/qiskit-community/prototype-quantum-kernel-training/tree/main).\n",
        "\n",
        "Creamos el circuito cuántico que se utiliza para evaluar una entrada de la matriz del núcleo.\n",
        "Los datos de entrada se utilizan para determinar los ángulos de rotación de las puertas parametrizadas del circuito.\n",
        "Para simplificar, utilizaremos las muestras `x1=14` de datos y `x2=19`.\n",
        "\n",
        "***Nota: El conjunto de datos utilizado en este tutorial puede descargarse [aquí](https://github.com/qiskit-community/prototype-quantum-kernel-training/blob/main/data/dataset_graph7.csv).***\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "70d6faff-9a56-44bb-b26f-f573a8c90889",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-kernel-training/extracted-outputs/70d6faff-9a56-44bb-b26f-f573a8c90889-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# Prepare training data\n",
        "X_train = get_training_data()\n",
        "\n",
        "# Empty kernel matrix\n",
        "num_samples = np.shape(X_train)[0]\n",
        "kernel_matrix = np.full((num_samples, num_samples), np.nan)\n",
        "\n",
        "# Prepare feature map for computing overlap\n",
        "num_features = np.shape(X_train)[1]\n",
        "num_qubits = int(num_features / 2)\n",
        "entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]\n",
        "fm = QuantumCircuit(num_qubits)\n",
        "training_param = Parameter(\"θ\")\n",
        "feature_params = ParameterVector(\"x\", num_qubits * 2)\n",
        "fm.ry(training_param, fm.qubits)\n",
        "for cz in entangler_map:\n",
        "    fm.cz(cz[0], cz[1])\n",
        "for i in range(num_qubits):\n",
        "    fm.rz(-2 * feature_params[2 * i + 1], i)\n",
        "    fm.rx(-2 * feature_params[2 * i], i)\n",
        "\n",
        "# Assign tunable parameter to known optimal value and set the data params for\n",
        "# first two samples\n",
        "x1 = 14\n",
        "x2 = 19\n",
        "unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])\n",
        "unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])\n",
        "\n",
        "# Create the overlap circuit\n",
        "overlap_circ = unitary_overlap(unitary1, unitary2)\n",
        "overlap_circ.measure_all()\n",
        "overlap_circ.draw(\"mpl\", scale=0.6, style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-small-md",
      "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",
        "* Entrada: Circuito abstracto, no optimizado para un backend concreto.\n",
        "* Resultado: Circuito de destino, optimizado para la QPU seleccionada.\n",
        "\n",
        "En el caso de la ruta del simulador de vectores de estado utilizada en esta sección, no se requiere ninguna optimización específica del backend: el circuito abstracto puede muestrearse directamente. Aplicamos este paso en el ejemplo de hardware que se muestra a continuación, en el que el circuito se compila para una QPU real utilizando `generate_preset_pass_manager` con `optimization_level=3`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-small-md",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Paso 3: Ejecutar utilizando Qiskit primitives\n",
        "\n",
        "* Entrada: Circuito abstracto.\n",
        "* Resultado: Distribución cuasiprobabilística.\n",
        "\n",
        "Utiliza la `StatevectorSampler` función de Qiskit para reconstruir una distribución cuasiprobabilística de los estados obtenidos al muestrear el circuito. Para la tarea de generar una matriz de núcleo, nos interesa especialmente la probabilidad de observar el estado |0>.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "step3-small-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-kernel-training/extracted-outputs/step3-small-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "sampler = StatevectorSampler()\n",
        "\n",
        "# Execute and get counts\n",
        "num_shots = 10_000\n",
        "results = sampler.run([overlap_circ], shots=num_shots).result()\n",
        "counts = results[0].data.meas.get_int_counts()\n",
        "\n",
        "# Plot counts\n",
        "visualize_counts(counts, num_qubits, num_shots)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-small-md",
      "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",
        "* Entrada: Distribución de probabilidad.\n",
        "* Resultado: un único elemento de la matriz del núcleo.\n",
        "\n",
        "Calcula la probabilidad de obtener un valor de $|0 \\rangle$ en el circuito de solapamiento y rellena la matriz del núcleo en la posición correspondiente a las muestras representadas por este circuito de solapamiento concreto (fila 15, columna 20).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "step4-small-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fidelity (simulator): 0.8261\n"
          ]
        }
      ],
      "source": [
        "kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots\n",
        "print(f\"Fidelity (simulator): {kernel_matrix[x1, x2]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "large-scale-intro",
      "metadata": {},
      "source": [
        "<span id=\"hardware-example\" />\n",
        "\n",
        "## Ejemplo de hardware\n",
        "\n",
        "Una matriz de núcleo cuántico tiene un $\\mathcal{O}(N^2)$ es de entradas para un $N$ es de muestras de entrenamiento, y cada entrada requiere ejecutar un circuito de superposición cuya profundidad de puertas de dos qubits aumenta con el tamaño del mapa de características. En consecuencia, ampliar este tutorial a un problema de mayor envergadura conlleva dos costes que se acumulan: el tiempo de la QPU por matriz del núcleo crece cuadráticamente con $N$, y la profundidad de `unitary_overlap` (que compone el mapa de características con su adjunto) reduce la fidelidad debido al tamaño del sistema y a la conectividad del hardware actual. Para que la demostración sea breve y poder realizar una comparación clara, ejecutamos la **misma** instancia de siete qubits del ejemplo a pequeña escala en una QPU real y comparamos la fidelidad de una sola entrada de la matriz del núcleo con el valor del simulador calculado anteriormente.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d2f4f6cf-067e-4d53-aa04-7ca9c803d3e1",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Using backend: ibm_pittsburgh\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-kernel-training/extracted-outputs/d2f4f6cf-067e-4d53-aa04-7ca9c803d3e1-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fidelity (hardware): 0.7517\n"
          ]
        }
      ],
      "source": [
        "# ------------------------------ Step 1 ------------------------------\n",
        "# Prepare training data\n",
        "X_train = get_training_data()\n",
        "\n",
        "# Empty kernel matrix\n",
        "num_samples = np.shape(X_train)[0]\n",
        "kernel_matrix = np.full((num_samples, num_samples), np.nan)\n",
        "\n",
        "# Prepare feature map for computing overlap\n",
        "num_features = np.shape(X_train)[1]\n",
        "num_qubits = int(num_features / 2)\n",
        "entangler_map = [[0, 2], [3, 4], [2, 5], [1, 4], [2, 3], [4, 6]]\n",
        "fm = QuantumCircuit(num_qubits)\n",
        "training_param = Parameter(\"θ\")\n",
        "feature_params = ParameterVector(\"x\", num_qubits * 2)\n",
        "fm.ry(training_param, fm.qubits)\n",
        "for cz in entangler_map:\n",
        "    fm.cz(cz[0], cz[1])\n",
        "for i in range(num_qubits):\n",
        "    fm.rz(-2 * feature_params[2 * i + 1], i)\n",
        "    fm.rx(-2 * feature_params[2 * i], i)\n",
        "\n",
        "# Assign tunable parameter to known optimal value and\n",
        "# set the data params for first two samples\n",
        "x1 = 14\n",
        "x2 = 19\n",
        "unitary1 = fm.assign_parameters(list(X_train[x1]) + [np.pi / 2])\n",
        "unitary2 = fm.assign_parameters(list(X_train[x2]) + [np.pi / 2])\n",
        "\n",
        "# Create the overlap circuit\n",
        "overlap_circ = unitary_overlap(unitary1, unitary2)\n",
        "overlap_circ.measure_all()\n",
        "\n",
        "# ------------------------------ Step 2 ------------------------------\n",
        "service = QiskitRuntimeService()\n",
        "# backend = service.least_busy(\n",
        "#    operational=True, simulator=False, min_num_qubits=overlap_circ.num_qubits\n",
        "# )\n",
        "backend = service.backend(\"ibm_pittsburgh\")\n",
        "print(f\"Using backend: {backend.name}\")\n",
        "pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n",
        "overlap_ibm = pm.run(overlap_circ)\n",
        "\n",
        "# ------------------------------ Step 3 ------------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.environment.job_tags = [\"TUT_QKT\"]\n",
        "\n",
        "num_shots = 10_000\n",
        "results = sampler.run([overlap_ibm], shots=num_shots).result()\n",
        "counts = results[0].data.meas.get_int_counts()\n",
        "visualize_counts(counts, num_qubits, num_shots)\n",
        "\n",
        "# ------------------------------ Step 4 ------------------------------\n",
        "kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots\n",
        "print(f\"Fidelity (hardware): {kernel_matrix[x1, x2]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dec33657-9055-4fdc-b146-dba0e5882719",
      "metadata": {},
      "source": [
        "Para completar toda la matriz del núcleo, tendríamos que realizar un experimento cuántico para cada una de sus entradas únicas $N(N+1)/2$. La siguiente figura muestra la matriz resultante para este conjunto de datos; el rojo más oscuro indica fidelidades más cercanas a 1.0.\n",
        "\n",
        "![kernel\\_matrix.png](https://quantum.cloud.ibm.com/docs/images/tutorials/quantum-kernel-training/kernel_matrix.avif)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6847b837",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Próximos pasos\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendaciones\">\n",
        "  Si te ha parecido interesante este trabajo, quizá te interese el siguiente material:\n",
        "\n",
        "  * [Quantum Kernel Training Toolkit](https://github.com/qiskit-community/prototype-quantum-kernel-training/tree/main) : el repositorio de prototipos en el que se basa este tutorial\n",
        "  * [Entrenamiento de Quantum Kernel para aplicaciones de « Machine Learning](https://qiskit-community.github.io/qiskit-machine-learning/tutorials/08_quantum_kernel_trainer.html) »: un tutorial de Qiskit sobre « Machine Learning » que muestra cómo entrenar el parámetro entrenable\n",
        "  * [Introducción al](/learning/courses/quantum-machine-learning/introduction) aprendizaje automático cuántico ( Machine Learning ): un curso sobre el aprendizaje automático cuántico\n",
        "  * « [Machine Learning](https://research.ibm.com/topics/quantum-machine-learning) » de Quantum, disponible en IBM Research : una visión general de la investigación sobre QML en IBM\n",
        "  * [Núcleos cuánticos covariantes para datos con estructura de grupo](https://arxiv.org/abs/2105.03406) : el artículo en el que se basa este tutorial\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 1,
    "qpuSeconds": 60
  },
  "nbformat": 4,
  "nbformat_minor": 5
}