{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "4ba97b35-a453-42fb-a59a-e9f0c129283f",
      "metadata": {},
      "source": [
        "---\n",
        "title: Quantum kernel training\n",
        "description: Build a Qiskit pattern for evaluating entries into a quantum kernel matrix used for binary classification.\n",
        "---\n",
        "\n",
        "# Quantum kernel training\n",
        "\n",
        "*Usage estimate: under one minute on an Eagle r3 processor (NOTE: This is an estimate only. Your runtime might vary.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c2d486a5",
      "metadata": {},
      "source": [
        "## Background\n",
        "\n",
        "This tutorial shows how to build a `Qiskit pattern` for evaluating entries into a quantum kernel matrix used for binary classification. For more information on `Qiskit patterns` and how `Qiskit Serverless` can be used to deploy them to the cloud for managed execution, visit our [docs page on IBM Quantum® Platform](/docs/guides/serverless).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ad7bc6f9",
      "metadata": {},
      "source": [
        "## Requirements\n",
        "\n",
        "Before starting this tutorial, be sure you have the following installed:\n",
        "\n",
        "* Qiskit SDK v1.0 or later, with [visualization](/docs/api/qiskit/visualization) support\n",
        "* Qiskit Runtime v0.22 or later (`pip install qiskit-ibm-runtime`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "dfb22142-2377-40bf-801c-b2116f048a9a",
      "metadata": {},
      "source": [
        "## Setup\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "4405f2da-ce6e-4b97-960a-bb04730cfb6d",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "\n",
            "\n",
            "\u001b7\u001b[1A\u001b[1G\u001b[27G[Files: 0  Bytes: 0  [0 B/s] Re]\u001b8\u001b7\u001b[2A\u001b[1G\u001b[27G[https://raw.githubusercontent.]\u001b8\u001b7\u001b[1S\u001b[3A\u001b[1G\u001b[0JSaving 'dataset_graph7.csv.1'\n",
            "\u001b8\u001b7\u001b[2A\u001b[1Gdataset_graph7.csv.1 100% [=============================>]   20.25K    --.-KB/s\u001b8\u001b7\u001b[1S\u001b[3A\u001b[1G\u001b[0JHTTP response 200  [https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv]\n",
            "\u001b8\u001b7\u001b[2A\u001b[1Gdataset_graph7.csv.1 100% [=============================>]   20.25K    --.-KB/s\u001b8\u001b7\u001b[1A\u001b[1G\u001b[27G[Files: 1  Bytes: 20.25K [93.33]\u001b8\u001b[m\u001b[m\u001b[m\u001b[m"
          ]
        }
      ],
      "source": [
        "!wget https://raw.githubusercontent.com/qiskit-community/prototype-quantum-kernel-training/main/data/dataset_graph7.csv\n",
        "\n",
        "# General Imports and helper functions\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 UnitaryOverlap\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService, Sampler\n",
        "\n",
        "# from qiskit_serverless import IBMServerlessClient, QiskitFunction\n",
        "from qiskit_ibm_catalog import QiskitServerless, QiskitFunction\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": "6ddce42e-a365-451d-972d-24c1306ac47b",
      "metadata": {},
      "source": [
        "## Step 1: Map classical inputs to a quantum problem\n",
        "\n",
        "* Input: Training dataset.\n",
        "* Output: Abstract circuit for calculating a kernel matrix entry.\n",
        "\n",
        "Create the quantum circuit used to evaluate one entry in the kernel matrix. We use the input data to determine the rotation angles for the circuit's parametrized gates. We will use data samples `x1=14` and `x2=19`.\n",
        "\n",
        "***Note: The dataset used in this tutorial can be downloaded [here](https://github.com/qiskit-community/prototype-quantum-kernel-training/blob/main/data/dataset_graph7.csv).***\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "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": 3,
          "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 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 = UnitaryOverlap(unitary1, unitary2)\n",
        "overlap_circ.measure_all()\n",
        "overlap_circ.draw(\"mpl\", scale=0.6, style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "158d8cec-1d92-4105-ac64-6f801dec4259",
      "metadata": {},
      "source": [
        "## Step 2: Optimize problem for quantum hardware execution\n",
        "\n",
        "* Input: Abstract circuit, not optimized for a particular backend\n",
        "* Output: Target circuit and observable, optimized for the selected QPU\n",
        "\n",
        "Use the `generate_preset_pass_manager` function from Qiskit to specify an optimization routine for our circuit with respect to the QPU on which we plan to run the experiment. We set `optimization_level=3` , which means we will use the preset pass manager which provides the highest level of optimization.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "49607b34-9723-493d-85da-bd97c1351104",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-kernel-training/extracted-outputs/49607b34-9723-493d-85da-bd97c1351104-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=overlap_circ.num_qubits\n",
        ")\n",
        "pm = generate_preset_pass_manager(optimization_level=3, backend=backend)\n",
        "overlap_ibm = pm.run(overlap_circ)\n",
        "overlap_ibm.draw(\"mpl\", scale=0.6, idle_wires=False, fold=-1, style=\"iqp\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9359b92c-a130-44d1-ba11-752f8f4935a0",
      "metadata": {},
      "source": [
        "## Step 3: Execute using Qiskit primitives\n",
        "\n",
        "* Input: Target circuit\n",
        "* Output: Quasi-probability distribution\n",
        "\n",
        "Use the `Sampler` primitive from Qiskit Runtime to reconstruct a quasi-probability distribution of states yielded from sampling the circuit. For the task of generating a kernel matrix, we are particularly interested in the probability of measuring the |0> state.\n",
        "\n",
        "For this demo, we will run on a QPU with `qiskit-ibm-runtime` primitives. To run on `qiskit` statevector-based primitives, replace the block of code using Qiskit IBM® Runtime primitives with the commented block.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "d2f4f6cf-067e-4d53-aa04-7ca9c803d3e1",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-kernel-training/extracted-outputs/d2f4f6cf-067e-4d53-aa04-7ca9c803d3e1-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "num_shots = 10_000\n",
        "\n",
        "## Evaluate the problem using statevector-based primitives from Qiskit\n",
        "# from qiskit.primitives import StatevectorSampler\n",
        "\n",
        "# sampler = StatevectorSampler()\n",
        "# results = sampler.run([overlap_circ]).result()\n",
        "# counts = results[0].data.meas.get_int_counts()\n",
        "\n",
        "# Evaluate the problem using a QPU via Qiskit IBM Runtime\n",
        "\n",
        "sampler = Sampler(mode=backend)\n",
        "results = sampler.run([overlap_ibm]).result()\n",
        "counts = results[0].data.meas.get_int_counts()\n",
        "\n",
        "visualize_counts(counts, num_qubits, num_shots)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1b750103-c369-4651-9092-db0385294c46",
      "metadata": {},
      "source": [
        "## Step 4: Post-process and return result in desired classical format\n",
        "\n",
        "* Input: Probability distribution\n",
        "* Output: A single kernel matrix element\n",
        "\n",
        "Calculate the probability of measuring |0> on the overlap circuit, and populate the kernel matrix in the position corresponding to the samples represented by this particular overlap circuit (row 15, column 20). In this visualization, darker red indicates fidelities closer to 1.0. To fill out the entire kernel matrix, we need to run a quantum experiment for each entry.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "8efcb466-3110-4e60-82a6-185f0dca1771",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fidelity: 0.1279\n"
          ]
        }
      ],
      "source": [
        "# Calculate the fidelity, or the probability to measure 0\n",
        "kernel_matrix[x1, x2] = counts.get(0, 0.0) / num_shots\n",
        "print(f\"Fidelity: {kernel_matrix[x1, x2]}\")"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "dec33657-9055-4fdc-b146-dba0e5882719",
      "metadata": {},
      "source": [
        "![kernel\\_matrix.png](https://quantum.cloud.ibm.com/docs/images/tutorials/quantum-kernel-training/kernel_matrix.avif)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5b0397d6-6673-45cb-9d38-7e9faadba4a1",
      "metadata": {},
      "source": [
        "## Deploy the Qiskit pattern to the cloud\n",
        "\n",
        "To do this, move the source code above to a file, `./source/generate_kernel_entry.py`, wrap the code in a script which takes inputs returns the final solution, and finally upload it to a remote cluster using the `QiskitFunction` class from `Qiskit Serverless`. For guidance on specifying external dependencies, passing input arguments, and more, check out the [Qiskit Serverless guides](https://qiskit.github.io/qiskit-serverless/getting_started/index.html).\n",
        "\n",
        "The input to the Pattern is a pair of data samples, `x1` and `x2`. The output is the fidelity between the two samples. This value will be used to populate the kernel matrix entry corresponding to these two samples.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "8ce74c3c-4212-4c75-ae55-1394813c89a6",
      "metadata": {},
      "outputs": [],
      "source": [
        "serverless = QiskitServerless()\n",
        "\n",
        "kernel_entry_pattern = QiskitFunction(\n",
        "    title=\"generate-kernel-entry\",\n",
        "    entrypoint=\"generate_kernel_entry.py\",\n",
        "    working_dir=\"./source/\",\n",
        ")\n",
        "\n",
        "serverless.upload(kernel_entry_pattern)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f5b1e64a-881e-459e-a425-7876e56d73de",
      "metadata": {},
      "source": [
        "## Run the Qiskit pattern as a managed service\n",
        "\n",
        "Once we have uploaded the pattern to the cloud, we can easily run it using the `IBMServerlessProvider` client. For simplicity, we will use an exact quantum simulator in the cloud environment, so the fidelity we calculate will be exact.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "c70725fe-57e9-42ed-a0a4-daf0222ddc3a",
      "metadata": {},
      "outputs": [],
      "source": [
        "generate_kernel_entry = serverless.load(\"generate-kernel-entry\")\n",
        "job = generate_kernel_entry.run(\n",
        "    sample1=list(X_train[x1]), sample2=list(X_train[x2])\n",
        ")\n",
        "\n",
        "kernel_matrix[x1, x2] = job.result()[\"fidelity\"]\n",
        "print(f\"fidelity: {kernel_matrix[x1, x2]}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6847b837",
      "metadata": {},
      "source": [
        "## Tutorial survey\n",
        "\n",
        "Please take this short survey to provide feedback on this tutorial. Your insights will help us improve our content offerings and user experience.\n",
        "\n",
        "[Link to survey](https://your.feedback.ibm.com/jfe/form/SV_6xsFvUYV1pNHCqW)\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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}