{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "c77f1777-ecb8-4cb0-9bf2-49d7c989b12a",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Aprimore a classificação de recursos usando kernels quânticos projetados\"\n",
        "description: \"Tutorial sobre como melhorar a classificação de características usando kernels quânticos projetados\"\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",
        "# Aprimore a classificação de recursos usando kernels quânticos projetados\n",
        "\n",
        "*Estimativa de uso: 80 minutos em um processador Heron r3 (OBSERVAÇÃO: essa é apenas uma estimativa. Seu tempo de execução pode variar)*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Resultados do aprendizado\n",
        "\n",
        "Ao concluir este tutorial, os usuários deverão compreender:\n",
        "\n",
        "* Como funcionam os kernels quânticos projetados (PQK) e em que casos eles oferecem uma vantagem quântica potencial.\n",
        "* Como executar um PQK em hardware usando um conjunto de dados do mundo real.\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Pré-requisitos\n",
        "\n",
        "Sugerimos que os usuários estejam familiarizados com os seguintes tópicos antes de seguir com este tutorial:\n",
        "\n",
        "* [Núcleos quânticos](/learning/courses/quantum-machine-learning/quantum-kernel-methods) do [curso de aprendizado de máquina quântico](/learning/courses/quantum-machine-learning) em IBM Quantum® Learning\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Segundo plano\n",
        "\n",
        "Neste tutorial, demonstramos como executar um [kernel quântico projetado](https://www.nature.com/articles/s41467-021-22539-9) (PQK) com o Qiskit em um conjunto de dados biológicos do mundo real, com base no artigo [Enhanced Prediction of CAR T-Cell Cytotoxicity with Quantum-Kernel Methods](https://arxiv.org/abs/2507.22710) [\\[1\\]](#references).\n",
        "\n",
        "O PQK é um método usado no aprendizado de máquina quântica (QML) para codificar dados clássicos em um espaço de recursos quânticos e projetá-los de volta ao domínio clássico, usando computadores quânticos para aprimorar a seleção de recursos. Isso envolve a codificação de dados clássicos em estados quânticos usando um circuito quântico, normalmente por meio de um processo chamado mapeamento de recursos, em que os dados são transformados em um espaço de Hilbert de alta dimensão. O aspecto \"projetado\" refere-se à extração de informações clássicas dos estados quânticos, medindo observáveis específicos, para construir uma matriz de kernel que possa ser usada em algoritmos clássicos baseados em kernel, como máquinas de vetores de suporte. Essa abordagem aproveita as vantagens computacionais dos sistemas quânticos para obter um desempenho potencialmente melhor em determinadas tarefas em comparação com os métodos clássicos.\n",
        "\n",
        "Os principais componentes dos PQKs são as matrizes de densidade reduzida (RDMs), obtidas por meio de medições projetivas do mapa de características quânticas. Em particular, costuma-se calcular as matrizes de densidade reduzida de um único qubit (1 RDM) para cada qubit. Essas grandezas medidas são então utilizadas como entradas para uma função de kernel clássica, como um kernel exponencial, a fim de construir a matriz de kernel final.\n",
        "\n",
        "Os PQKs oferecem vantagens potenciais em relação [aos kernels quânticos](/docs/tutorials/quantum-kernel-training) padrão, especialmente para o hardware quântico de curto prazo. Os kernels quânticos padrão geralmente dependem da estimativa de sobreposições globais de estados, que se tornam cada vez mais difíceis de medir com precisão à medida que o número de qubits aumenta e são altamente sensíveis ao ruído. Em contrapartida, os PQKs utilizam observáveis locais, como matrizes de densidade reduzida de um único qubit (1 RDM), o que resulta em menor sobrecarga de amostragem, maior robustez contra ruídos de hardware e melhor escalabilidade. Ao projetar estados quânticos em características de medição locais antes de aplicar uma função kernel clássica, os PQKs conseguem preservar correlações quânticas úteis, mantendo-se, ao mesmo tempo, mais práticos para dispositivos a curto prazo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "542b8075-3b8c-476c-9513-c03de0f162b1",
      "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.0 ou posterior, com suporte [para visualização](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.40 ou posterior (`pip install qiskit-ibm-runtime`)\n",
        "* Codificadores de categoria 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",
        "## Instalação\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "fe8a02d3-994a-45fe-823d-5e68ded0d717",
      "metadata": {},
      "outputs": [],
      "source": [
        "import warnings\n",
        "\n",
        "# Standard libraries\n",
        "import os\n",
        "import numpy as np\n",
        "import pandas as pd\n",
        "\n",
        "# Machine learning and data processing\n",
        "import category_encoders as ce\n",
        "from scipy.linalg import inv, sqrtm\n",
        "from sklearn.metrics.pairwise import rbf_kernel\n",
        "from sklearn.model_selection import GridSearchCV, StratifiedKFold\n",
        "from sklearn.svm import SVC\n",
        "\n",
        "# Qiskit and IBM Quantum Compute Service\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.circuit import ParameterVector\n",
        "from qiskit.circuit.library import UnitaryGate, ZZFeatureMap\n",
        "from qiskit.quantum_info import SparsePauliOp, random_unitary\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit_ibm_runtime import (\n",
        "    Batch,\n",
        "    EstimatorOptions,\n",
        "    EstimatorV2 as Estimator,\n",
        "    QiskitRuntimeService,\n",
        ")\n",
        "\n",
        "# Progress bar\n",
        "import tqdm\n",
        "\n",
        "warnings.filterwarnings(\"ignore\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bd92ba56-e9eb-4841-b751-7627f47d756c",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Exemplo de simulador em pequena escala\n",
        "\n",
        "Omitimos o exemplo do simulador em pequena escala neste tutorial, pois nosso objetivo principal é demonstrar como os kernels quânticos projetados podem ser adaptados a sistemas maiores e a hardware real.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0fb8a2fa-64b4-434f-8848-e89a098ba73d",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Exemplo de hardware em grande 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",
        "### Passo 1: Mapear entradas clássicas para um problema quântico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb3db2e7-8a3d-4ab1-9e3b-ffc5587dee6d",
      "metadata": {},
      "source": [
        "<span id=\"dataset-preparation\" />\n",
        "\n",
        "### Preparação do conjunto de dados\n",
        "\n",
        "Neste tutorial, usamos um conjunto de dados biológicos do mundo real para uma tarefa de classificação binária, que foi gerado por Daniels et al. (2022) e pode ser baixado do [material suplementar](https://www.science.org/doi/full/10.1126/science.abq0225#supplementary-materials) incluído no artigo. Os dados consistem em células T CAR, que são células T geneticamente modificadas usadas em imunoterapia para tratar determinados tipos de câncer. As células T, um tipo de célula imunológica, são modificadas em laboratório para expressar receptores de antígenos quiméricos (CARs) que têm como alvo proteínas específicas nas células cancerígenas. Essas células T modificadas podem reconhecer e destruir as células cancerígenas com mais eficácia. Os recursos de dados são os motivos das células T CAR, que se referem ao componente estrutural ou funcional específico do CAR projetado nas células T. Com base nesses motivos, nossa tarefa é prever a citotoxicidade de uma determinada célula CAR T, rotulando-a como tóxica ou não tóxica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2ab0503-1cc9-4512-8f06-12223219cc3e",
      "metadata": {},
      "source": [
        "A seguir, são mostradas as funções auxiliares para pré-processar esse conjunto de dados.\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": [
        "Você pode executar este tutorial executando a seguinte célula, que cria automaticamente a estrutura de pastas necessária e faz o download dos arquivos de treinamento e teste diretamente em seu ambiente. Se você já tiver esses arquivos localmente, esta etapa os substituirá com segurança para garantir a consistência da versão.\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": [
        "Também transformamos o conjunto de dados de modo que $1$ seja representado como $\\pi/2$ para fins de escala.\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 os tamanhos e as formas dos conjuntos de dados de treinamento e teste.\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",
        "### Etapa 2: Otimizar o problema para execução em hardware quântico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55afd26f-7a53-43bc-920d-88160a61688e",
      "metadata": {},
      "source": [
        "<span id=\"quantum-circuit\" />\n",
        "\n",
        "### Circuito quântico\n",
        "\n",
        "Agora, construímos o mapa de recursos que incorpora nosso conjunto de dados clássico em um espaço de recursos de dimensão superior. Para essa incorporação, usamos o [`ZZFeatureMap`](/docs/api/qiskit/qiskit.circuit.library.ZZFeatureMap) do 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": [
        "Outra opção de incorporação quântica é o 1D-Heisenberg Hamiltonian evolution ansatz. Você pode pular a execução desta seção se quiser continuar com o `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",
        "### Passo 3: Execute usando Qiskit primitives\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f202d995-8fc0-4af5-b11e-2ed72ac48c84",
      "metadata": {},
      "source": [
        "<span id=\"measure-1-rdms\" />\n",
        "\n",
        "### Medida 1-RDMs\n",
        "\n",
        "Nesta etapa, obtemos todas as matrizes de densidade reduzida de um único qubit (1-RDMs) por meio de medições projetivas do mapa de características quânticas, que posteriormente serão inseridas na função kernel exponencial clássica.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea823c34-d7d7-42f8-989c-dfe78cdd489a",
      "metadata": {},
      "source": [
        "Vejamos como calcular o 1-RDM com um único ponto de dados do conjunto de dados antes de analisarmos todos os dados. Os 1-RDMs são uma coleção de medições de um único qubit dos operadores Pauli `X`, `Y` e `Z` em todos os qubits. Isso ocorre porque um RDM de um único qubit pode ser totalmente expresso 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": [
        "Primeiro, selecionamos o backend a ser usado.\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": [
        "Em seguida, executamos o circuito quântico e medimos as projeções. Observe que ativamos a atenuação de erros, incluindo a extrapolação de ruído zero (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": [
        "Em seguida, recuperamos os 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 o tamanho do circuito e a profundidade da porta de dois 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": [
        "Agora podemos percorrer todo o conjunto de dados de treinamento para obter todos os 1-RDMs.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eabb625e-edc1-445f-ad14-5e472d8a2879",
      "metadata": {},
      "source": [
        "Também fornecemos os resultados de um experimento que executamos em hardware quântico. Você pode executar o treinamento por conta própria, definindo o sinalizador abaixo como `True`, ou usar os resultados da projeção que fornecemos.\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": [
        "Quando os trabalhos forem concluídos, poderemos recuperar os 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 isso para o conjunto de teste.\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 os 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",
        "### Etapa 4: Pós-processamento e retorno do resultado no formato clássico desejado\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e10f152-d20c-4c70-9530-e9e71c309b59",
      "metadata": {},
      "source": [
        "<span id=\"define-the-projected-quantum-kernel\" />\n",
        "\n",
        "### Defina o kernel quântico projetado\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34150c16-e6ca-43a3-989d-444104dc60e5",
      "metadata": {},
      "source": [
        "O kernel quântico projetado é definido com a seguinte função de kernel: $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) $ Na equação acima, $\\gamma>0$ é um hiperparâmetro ajustável. O $K^{\\textrm{PQ}}_{ij} = k^{\\textrm{PQ}}(x_i, x_j)$ são as entradas da matriz do kernel $K^{\\textrm{PQ}}$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42cea21f-3178-4ac9-8826-db7ca7cdfe20",
      "metadata": {},
      "source": [
        "Usando a definição de 1-RDMs, podemos ver que os termos individuais dentro da função de kernel podem ser avaliados como $\\textrm{Tr}[P \\rho_k (x_i)] = \\braket P$, onde $P \\in \\{ X,Y,Z \\}$. Esses valores de expectativa são exatamente o que medimos acima.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "22e41ed2-93be-4b30-a898-8d2832d1e0ab",
      "metadata": {},
      "source": [
        "Usando `scikit-learn`, podemos, de fato, calcular o kernel com ainda mais facilidade. Isso se deve ao kernel de função de base radial (`'rbf'`) prontamente disponível: $ \\textrm{exp} (-\\gamma \\lVert x - x' \\rVert^2)$. Primeiro, precisamos simplesmente remodelar os novos conjuntos de dados de treinamento e teste projetados em matrizes bidimensionais.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01abd8a-2194-41b9-adaf-7ec80f14e3b1",
      "metadata": {},
      "source": [
        "Observe que a análise de todo o conjunto de dados pode levar cerca de 80 minutos na QPU. Para garantir que o restante do tutorial seja facilmente executável, fornecemos também projeções de um experimento executado anteriormente (que estão incluídas nos arquivos que você baixou no bloco de código `Download dataset` ). Se você mesmo realizou o treinamento, poderá continuar o tutorial com seus próprios 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 vetores de suporte (SVM)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b2ea7dde-63ec-4e3d-b6dc-dbeab6a07fda",
      "metadata": {},
      "source": [
        "Agora, podemos executar um SVM clássico nesse kernel pré-computado e usar o kernel entre os conjuntos de teste e treinamento para a previsão.\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ássico\n",
        "\n",
        "Podemos executar um SVM clássico com a função de base radial como o kernel sem fazer uma projeção quântica. Esse resultado é a nossa referência clássica.\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: Verifique a vantagem quântica potencial do conjunto de dados em tarefas de aprendizagem\n",
        "\n",
        "Nem todos os conjuntos de dados oferecem vantagens potenciais com o uso de PQKs. Existem alguns limites teóricos que podem ser usados como um teste preliminar para verificar se um determinado conjunto de dados pode se beneficiar dos PQKs. Para quantificar isso, os autores de [Power of data in quantum machine learning](https://www.nature.com/articles/s41467-021-22539-9) \\[2] definem quantidades denominadas complexidades de modelo clássico e quântico e separação geométrica dos modelos clássico e quântico. Para esperar uma possível vantagem quântica das PQKs, a separação geométrica entre os núcleos clássicos e projetados quânticos deve ser aproximadamente da ordem de $\\sqrt{N}$, em que $N$ é o número de amostras de treinamento. Se essa condição for atendida, passaremos a verificar as complexidades do modelo. Se a complexidade do modelo clássico for da ordem de $N$ enquanto a complexidade do modelo quântico projetado for substancialmente menor do que $N$, podemos esperar uma vantagem potencial do PQK.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d8fae838-a34b-4cf5-ba98-579ec8527fde",
      "metadata": {},
      "source": [
        "A separação geométrica é definida da seguinte forma ( F19 em [\\[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": [
        "A complexidade do modelo é definida da seguinte forma ( M1 em [\\[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óximas etapas\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recomendações\">\n",
        "  Se você achou este trabalho interessante, talvez se interesse pelo material a seguir:\n",
        "\n",
        "  * [Curso](/learning/courses/quantum-machine-learning) aprofundado sobre aprendizado de máquina quântico da IBM Quantum Learning\n",
        "  * Tutorial [sobre treinamento com kernel quântico](/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",
        "## Referências\n",
        "\n",
        "1. Utro, Filippo, et al. \" [Previsão aprimorada da citotoxicidade de células T CAR com métodos de kernel quântico](https://arxiv.org/abs/2507.22710).\" arXiv preprint arXiv:2507.22710 (2025).\n",
        "2. Huang, Hsin-Yuan, et al. \" [Poder dos dados na aprendizagem de máquina quântica](https://www.nature.com/articles/s41467-021-22539-9).\" Comunicações da natureza 12.1 (2021): 2631.\n",
        "3. Daniels, Kyle G., et al. \"[Decodificação do fenótipo de células T CAR usando bibliotecas combinatórias de motivos de sinalização e aprendizado de máquina](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
}