{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "4ba97b35-a453-42fb-a59a-e9f0c129283f",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Treinamento de kernel quantum\"\n",
        "description: \"Crie um padrão Qiskit para avaliar entradas em uma matriz de kernel quântico usada para classificação binária.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore mapsto forall */}\n",
        "\n",
        "<span id=\"quantum-kernel-training\" />\n",
        "\n",
        "# Treinamento de kernel quantum\n",
        "\n",
        "*Estimativa de tempo de execução: menos de um minuto em um processador Heron r3 (NOTA: Trata-se apenas de uma estimativa.) (O tempo de execução pode variar.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd3bec67",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "Ao concluir este tutorial, você deverá compreender as seguintes informações:\n",
        "\n",
        "* Métodos do kernel e suas aplicações\n",
        "* Núcleos quânticos e como eles podem proporcionar espaços de características aprimorados\n",
        "* Construção de circuitos de kernel quântico\n",
        "* Como treinar um kernel quântico usando um [padrão do Qiskit](/docs/guides/intro-to-patterns) : mapear, otimizar, executar e pós-processar\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ba36136c",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "Recomenda-se que você se familiarize com os kernels quânticos, com a importância deles e com a forma como são utilizados na prática.\n",
        "\n",
        "* [Núcleos quânticos covariantes para dados com estrutura de grupo](https://arxiv.org/abs/2105.03406) (artigo)\n",
        "* [Introdução aos Kernels Quânticos e às Máquinas de Vetores de Suporte](https://www.youtube.com/watch?v=GVhCOTzAkCM) (vídeo)\n",
        "* [Núcleos quânticos na prática](https://www.youtube.com/watch?v=LmQcSxgINis) (vídeo)\n",
        "\n",
        "Também é útil ter um conhecimento básico de teoria dos grupos.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2d486a5",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "Os métodos de kernel são comuns em aplicações de aprendizado de máquina.\n",
        "Nesse contexto, \"kernel\" refere-se à matriz do kernel ou às entradas individuais nela contidas.\n",
        "Em geral, um kernel é uma medida de similaridade entre dados codificados em um *espaço de características* de alta dimensão e pode ser utilizado, por exemplo, em tarefas de classificação com máquinas de vetores de suporte.\n",
        "\n",
        "Os métodos de kernel quântico são aqueles que utilizam computadores quânticos para estimar um kernel.\n",
        "Sabe-se que os computadores quânticos podem codificar dados em espaços de características aprimorados quânticamente, substituindo efetivamente seus análogos clássicos.\n",
        "Para $\\vec{x} \\in \\mathbb{R}$ e $\\Psi(\\vec{x}) \\in \\mathbb{R}^{d'}$, normalmente com $d' >d$, $\\Psi(\\vec{x})$ é um mapa de características, $\\vec{x} \\mapsto \\Psi(\\vec{x})$.\n",
        "O objetivo de $\\Psi(\\vec{x})$ é fazer com que as categorias de dados sejam separadas por um hiperplano.\n",
        "Tomando os vetores no espaço mapeado por características como argumentos, a função kernel $K(\\vec{x}, \\vec{y}) = \\langle{\\Psi(\\vec{x}) | \\Psi(\\vec{y}) \\rangle{}}$ retorna seu produto escalar: $K: \\mathbb{R}^d \\rightarrow$ $\\mathbb{R}^d$.\n",
        "Classicamente, os mapas de características de interesse são aqueles nos quais a função kernel pode ser facilmente avaliada;\n",
        "ou seja, quando o produto escalar no espaço mapeado por características pode ser escrito em termos dos vetores de dados originais e não é necessário construir $\\Psi(\\vec{x})$ e $\\Psi(\\vec{y})$.\n",
        "No caso dos kernels quânticos, o mapeamento de características é realizado por um circuito quântico, e o kernel é estimado a partir das probabilidades de medição obtidas a partir do circuito.\n",
        "\n",
        "Este tutorial mostra como criar um padrão no Qiskit para avaliar entradas em uma matriz de kernel quântico utilizada para classificação binária.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad7bc6f9",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisitos\n",
        "\n",
        "Antes de iniciar este tutorial, verifique se você tem os seguintes itens instalados:\n",
        "\n",
        "* Qiskit SDK v2.3.1 ou posterior, com suporte [à visualização](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.44.0 ou posterior (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dfb22142-2377-40bf-801c-b2116f048a9a",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Instalação\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",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "Nesta seção, apresentamos as quatro etapas do padrão Qiskit em uma instância de sete qubits do problema de rotulagem de classes com erros e avaliamos uma única entrada da matriz do kernel utilizando a `StatevectorSampler` primitiva do Qiskit. Um simulador de vetor de estado é exato (com exceção do ruído de disparo) e nos mostra o método de ponta a ponta sem consumir tempo da QPU. Em seguida, repetimos o mesmo procedimento em hardware real na seção de exemplos 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",
        "### Passo 1: Mapear entradas clássicas para um problema quântico\n",
        "\n",
        "* Entrada: Conjunto de dados de treinamento.\n",
        "* Saída: Circuito abstrato para calcular uma entrada da matriz do kernel.\n",
        "\n",
        "O problema de classificação binária que pretendemos resolver aqui é conhecido como \" [*rotulagem de classes com erro*](https://arxiv.org/abs/2105.03406) \" O conjunto de dados de treinamento de entrada contém uma estrutura de grupo, composta por dois classes formadas por um grupo e um subgrupo.\n",
        "Considera-se que o grupo é o grupo de $G = SU(2)^{\\otimes n}$ es para qubits, que é o grupo unitário especial das\n",
        "matrizes $2 \\times 2$ es e tem ampla aplicabilidade na natureza; por exemplo, no Modelo Padrão da física de partículas.\n",
        "Consideramos o subgrupo (estabilizador do grafo) $S_\\text{graph} < G$, com $S_\\text{graph} = \\langle \\{ X_i \\otimes _{k:(k,i) \\in \\mathcal{E}} Z_k\\} _{i \\in \\mathcal{V}} \\} \\rangle$, para um grafo com arestas $\\mathcal{E}$ e vértices $\\mathcal{V}$.\n",
        "Observe que os estabilizadores fixam um estado do estabilizador tal que $D_s | \\psi \\rangle = | \\psi \\rangle,~ \\forall s \\in S_\\text{graph}$.\n",
        "Por fim, definimos dois cosets à esquerda $C_\\pm = c_\\pm S_\\text{graph}$, escolhendo aleatoriamente dois $c_\\pm \\in G$.\n",
        "\n",
        "Para obter mais detalhes sobre o conjunto de dados e como ele é gerado, consulte [este notebook](https://github.com/qiskit-community/prototype-quantum-kernel-training/blob/main/docs/background/qkernels_and_data_w_group_structure.ipynb) do [Quantum Kernel Training Toolkit](https://github.com/qiskit-community/prototype-quantum-kernel-training/tree/main).\n",
        "\n",
        "Criamos o circuito quântico utilizado para calcular um elemento da matriz do kernel.\n",
        "Os dados de entrada são utilizados para determinar os ângulos de rotação das portas parametrizadas do circuito.\n",
        "Para simplificar, utilizaremos as amostras `x1=14` de dados e `x2=19`.\n",
        "\n",
        "***Observação: o conjunto de dados usado neste tutorial pode ser baixado [aqui](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",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n",
        "* Entrada: Circuito abstrato, não otimizado para um backend específico.\n",
        "* Resultado: Circuito de destino, otimizado para a QPU selecionada.\n",
        "\n",
        "Para o caminho do simulador de vetor de estado utilizado nesta seção, não é necessária nenhuma otimização específica do backend: o circuito abstrato pode ser amostrado diretamente. Aplicamos essa etapa no exemplo de hardware abaixo, no qual o circuito é compilado para uma QPU real usando `generate_preset_pass_manager` com `optimization_level=3`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-small-md",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passo 3: Execute usando Qiskit primitives\n",
        "\n",
        "* Entrada: Circuito abstrato.\n",
        "* Resultado: Distribuição de quase-probabilidade.\n",
        "\n",
        "Use a `StatevectorSampler` primitiva do Qiskit para reconstruir uma distribuição de quase-probabilidade dos estados obtidos a partir da amostragem do circuito. Para a tarefa de gerar uma matriz do kernel, estamos particularmente interessados na probabilidade de medir o 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",
        "### Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado\n",
        "\n",
        "* Entrada: Distribuição de probabilidade.\n",
        "* Resultado: Um único elemento da matriz do kernel.\n",
        "\n",
        "Calcule a probabilidade de medir um valor de $|0 \\rangle$ no circuito de sobreposição e preencha a matriz do kernel na posição correspondente às amostras representadas por esse circuito de sobreposição específico (linha 15, coluna 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",
        "## Exemplo de hardware\n",
        "\n",
        "Uma matriz de kernel quântico possui $\\mathcal{O}(N^2)$ entradas para $N$ amostras de treinamento, e cada entrada requer a execução de um circuito de sobreposição cuja profundidade de portas de dois qubits aumenta com o tamanho do mapa de características. Consequentemente, a adaptação deste tutorial a um problema de maior escala acarreta dois custos cumulativos: o tempo de processamento da QPU por matriz do kernel cresce quadraticamente com o $N$ o, e a profundidade de `unitary_overlap` (que compõe o mapa de características com seu adjunto) prejudica a fidelidade, dadas as dimensões do sistema e a conectividade do hardware atual. Para manter a demonstração breve e fazer uma comparação clara, executamos, portanto, a **mesma** instância de sete qubits do exemplo em pequena escala em uma QPU real e comparamos a fidelidade de uma única entrada da matriz do kernel com o valor do simulador calculado acima.\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 preencher toda a matriz do kernel, teríamos que realizar um experimento quântico para cada uma de suas $N(N+1)/2$ entradas únicas. A figura abaixo mostra a matriz resultante para este conjunto de dados; o vermelho mais escuro indica fidelidades mais próximas de 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óximas etapas\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  Se você achou este trabalho interessante, talvez se interesse pelo seguinte material:\n",
        "\n",
        "  * [Kit de Ferramentas de Treinamento do Quantum Kernel](https://github.com/qiskit-community/prototype-quantum-kernel-training/tree/main) — o repositório de protótipos no qual este tutorial se baseia\n",
        "  * [Treinamento do Quantum Kernel para aplicações do Machine Learning](https://qiskit-community.github.io/qiskit-machine-learning/tutorials/08_quantum_kernel_trainer.html) — um tutorial do Qiskit sobre o Machine Learning que mostra como treinar o parâmetro treinável\n",
        "  * [Introdução ao](/learning/courses/quantum-machine-learning/introduction) Aprendizado de Máquinas Quântico ( Machine Learning ) — um curso sobre aprendizado de máquinas quântico\n",
        "  * [Quantum Machine Learning de IBM Research](https://research.ibm.com/topics/quantum-machine-learning) — uma visão geral da pesquisa sobre QML em IBM\n",
        "  * [Núcleos quânticos covariantes para dados com estrutura de grupo](https://arxiv.org/abs/2105.03406) — o artigo no qual este tutorial se baseia\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
}