{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "c77f1777-ecb8-4cb0-9bf2-49d7c989b12a",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"투영 양자 커널을 이용한 특징 분류 향상\"\n",
        "description: \"투영 양자 커널을 이용한 특징 분류 향상 방법에 관한 튜토리얼\"\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",
        "# 투영 양자 커널을 이용한 특징 분류 향상\n",
        "\n",
        "*사용 예상 시간: Heron 프로세서( r3 )에서 80분(참고: 이는 예상치일 뿐입니다. 런타임은 다를 수 있습니다.)*\n",
        "\n",
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "* 투영 양자 커널(PQK)의 작동 원리와, 어떤 경우에 잠재적인 양자 이점을 제공하는지.\n",
        "* 실제 데이터셋을 사용하여 하드웨어에서 PQK를 실행하는 방법.\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* IBM Quantum® Learning 의 [양자 머신러닝 강좌](/learning/courses/quantum-machine-learning) 에 나오는 [양자 커널](/learning/courses/quantum-machine-learning/quantum-kernel-methods)\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "이 튜토리얼에서는 [퀀텀 커널 방법으로 CAR T 세포 세포 독성 예측 향상](https://arxiv.org/abs/2507.22710) [\\[1\\]](#references) 논문을 기반으로 실제 생물학적 데이터 세트에서 Qiskit으로 [투영된 양자 커널](https://www.nature.com/articles/s41467-021-22539-9) (PQK)을 실행하는 방법을 시연합니다.\n",
        "\n",
        "PQK는 양자 컴퓨터로 특징 선택을 강화하여 클래식 데이터를 양자 특징 공간으로 인코딩하고 이를 다시 클래식 영역으로 투영하는 양자 머신러닝(QML)에 사용되는 방법입니다. 여기에는 일반적으로 데이터를 고차원 힐버트 공간으로 변환하는 피처 매핑이라는 프로세스를 통해 양자 회로를 사용하여 기존 데이터를 양자 상태로 인코딩하는 작업이 포함됩니다. \"투영\" 측면은 특정 관측값을 측정하여 양자 상태에서 고전적인 정보를 추출하여 서포트 벡터 머신과 같은 고전적인 커널 기반 알고리즘에 사용할 수 있는 커널 행렬을 구성하는 것을 말합니다. 이 접근 방식은 양자 시스템의 계산적 이점을 활용하여 기존 방식에 비해 특정 작업에서 잠재적으로 더 나은 성능을 달성할 수 있습니다.\n",
        "\n",
        "PQK의 주요 구성 요소는 양자 특징 맵에 대한 투영 측정을 통해 얻어지는 환원 밀도 행렬(RDM)입니다. 특히, 일반적으로 각 큐비트에 대해 단일 큐비트 축소 밀도 행렬(1 RDM)을 계산한다. 그런 다음, 이러한 측정된 값들은 지수 커널과 같은 고전적인 커널 함수의 입력값으로 사용되어 최종 커널 행렬을 구성합니다.\n",
        "\n",
        "PQK는 표준 [양자 커널에](/docs/tutorials/quantum-kernel-training) 비해 잠재적인 이점을 제공하며, 특히 단기적으로 도입될 양자 하드웨어의 경우 그 이점이 두드러집니다. 표준 양자 커널은 일반적으로 전역 상태 중첩을 추정하는 방식에 의존하는데, 큐비트 수가 증가함에 따라 이를 정확하게 측정하기가 점점 더 어려워지며, 노이즈에 매우 민감합니다. 반면, PQK는 단일 큐비트 축소 밀도 행렬(1 RDM)과 같은 국소 관측량을 사용하므로, 샘플링 오버헤드가 줄어들고 하드웨어 노이즈에 대한 내성이 향상되며 확장성도 더 우수합니다. PQK는 고전적 커널 함수를 적용하기 전에 양자 상태를 국소적 측정 특징에 투영함으로써, 유용한 양자 상관관계를 유지하면서도 단기적으로 상용화될 장치에 더 실용적인 방식을 제공할 수 있다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "542b8075-3b8c-476c-9513-c03de0f162b1",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## 요구사항\n",
        "\n",
        "이 튜토리얼을 시작하기 전에 다음이 설치되어 있는지 확인하세요:\n",
        "\n",
        "* Qiskit SDK v2.0 또는 이후 버전, [시각화](/docs/api/qiskit/visualization) 지원 기능 포함\n",
        "* Qiskit Runtime v0.40 이상 (`pip install qiskit-ibm-runtime`)\n",
        "* 카테고리 인코더 2.8.1 (`pip install category-encoders`)\n",
        "* NumPy 2.3.2 (`pip install numpy`)\n",
        "* 판다스 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",
        "## 설정\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "fe8a02d3-994a-45fe-823d-5e68ded0d717",
      "metadata": {},
      "outputs": [],
      "source": [
        "import warnings\n",
        "\n",
        "# Standard libraries\n",
        "import os\n",
        "import urllib.request\n",
        "from pathlib import Path\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",
        "## 소규모 시뮬레이터 예시\n",
        "\n",
        "이 튜토리얼에서는 소규모 시뮬레이터 예제를 생략합니다. 우리의 주된 목표는 투영된 양자 커널이 더 큰 시스템과 실제 하드웨어로 어떻게 확장될 수 있는지 보여주는 것이기 때문입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0fb8a2fa-64b4-434f-8848-e89a098ba73d",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 대규모 하드웨어 예시\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",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "cb3db2e7-8a3d-4ab1-9e3b-ffc5587dee6d",
      "metadata": {},
      "source": [
        "<span id=\"dataset-preparation\" />\n",
        "\n",
        "### 데이터셋 준비\n",
        "\n",
        "이 튜토리얼에서는 2진 분류 작업에 실제 생물학적 데이터 세트를 사용하며, 이 데이터 세트는 Daniels 등(2022)에서 생성하고 논문과 함께 제공되는 [보충 자료에서](https://www.science.org/doi/full/10.1126/science.abq0225#supplementary-materials) 다운로드할 수 있습니다. 이 데이터는 특정 암을 치료하기 위해 면역 요법에 사용되는 유전자 조작 T 세포인 CAR T 세포로 구성되어 있습니다. 면역 세포의 일종인 T세포는 실험실에서 암세포의 특정 단백질을 표적으로 하는 키메라 항원 수용체(CAR)를 발현하도록 변형됩니다. 이렇게 변형된 T세포는 암세포를 더 효과적으로 인식하고 파괴할 수 있습니다. 데이터의 특징은 CAR T세포 모티프로, T세포로 설계된 CAR의 특정 구조적 또는 기능적 구성 요소를 나타냅니다. 이러한 모티프를 기반으로 주어진 CAR T 세포의 세포 독성을 예측하여 독성 또는 무독성으로 분류하는 것이 우리의 과제입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a2ab0503-1cc9-4512-8f06-12223219cc3e",
      "metadata": {},
      "source": [
        "다음은 이 데이터 집합을 전처리하는 헬퍼 함수를 보여줍니다.\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",
        "        sep=\",\",\n",
        "    )\n",
        "    test_data = pd.read_csv(\n",
        "        os.path.join(dir_root, args[\"file_test_data\"]),\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": [
        "다음 셀을 실행하면 필요한 폴더 구조를 자동으로 생성하고 교육 및 테스트 파일을 모두 사용자 환경에 직접 다운로드하여 이 튜토리얼을 실행할 수 있습니다. 이러한 파일이 이미 로컬에 있는 경우 이 단계를 수행하면 버전 일관성을 유지하기 위해 안전하게 덮어쓰게 됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "84495ee1-880a-48cb-a904-d83396e8b29e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "  train_data.csv ... OK (5,012 bytes)\n",
            "  test_data.csv ... OK (2,194 bytes)\n",
            "  projections_train.csv ... OK (779,730 bytes)\n",
            "  projections_test.csv ... OK (335,529 bytes)\n",
            "\n",
            "All files saved to data_tutorial/pqk/\n"
          ]
        }
      ],
      "source": [
        "## Download dataset\n",
        "\n",
        "\n",
        "def download_pqk_dataset(data_dir=\"data_tutorial/pqk\"):\n",
        "    \"\"\"Download the four CSV files from the Qiskit documentation repo.\"\"\"\n",
        "    data_dir = Path(data_dir)\n",
        "    data_dir.mkdir(parents=True, exist_ok=True)\n",
        "\n",
        "    base_url = (\n",
        "        \"https://raw.githubusercontent.com/Qiskit/documentation/main/\"\n",
        "        \"datasets/tutorials/pqk\"\n",
        "    )\n",
        "    files = [\n",
        "        \"train_data.csv\",\n",
        "        \"test_data.csv\",\n",
        "        \"projections_train.csv\",\n",
        "        \"projections_test.csv\",\n",
        "    ]\n",
        "\n",
        "    for filename in files:\n",
        "        url = f\"{base_url}/{filename}\"\n",
        "        dest = data_dir / filename\n",
        "        print(f\"  {filename} ...\", end=\" \", flush=True)\n",
        "        urllib.request.urlretrieve(url, dest)\n",
        "        print(f\"OK ({dest.stat().st_size:,} bytes)\")\n",
        "\n",
        "    print(f\"\\nAll files saved to {data_dir}/\")\n",
        "    return data_dir\n",
        "\n",
        "\n",
        "DATA_DIR = download_pqk_dataset()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "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",
        "\n",
        "# dir_root points to the folder where the downloaded CSVs live\n",
        "dir_root = str(DATA_DIR)\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": [
        "또한 확장을 위해 데이터 집합을 변환하여 $1$ 을 $\\pi/2$ 으로 표시합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "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": [
        "학습 및 테스트 데이터 세트의 크기와 모양을 확인합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "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",
        "### 2단계: 양자 하드웨어 실행을 위한 문제 최적화\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55afd26f-7a53-43bc-920d-88160a61688e",
      "metadata": {},
      "source": [
        "<span id=\"quantum-circuit\" />\n",
        "\n",
        "### 양자 회로\n",
        "\n",
        "이제 기존 데이터 집합을 더 높은 차원의 특징 공간에 포함시키는 특징 맵을 구성합니다. 이 임베딩을 위해, 우리는 [`ZZFeatureMap`](/docs/api/qiskit/qiskit.circuit.library.ZZFeatureMap) 를 사용합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "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": 7,
          "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": [
        "또 다른 양자 임베딩 옵션은 1D-Heisenberg 해밀턴 진화 안사츠입니다. `ZZFeatureMap` 를 계속 진행하려면 이 섹션 실행을 건너뛰셔도 됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "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": 8,
          "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",
        "### 3단계: `Qiskit primitives` 명령어로 실행합니다\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f202d995-8fc0-4af5-b11e-2ed72ac48c84",
      "metadata": {},
      "source": [
        "<span id=\"measure-1-rdms\" />\n",
        "\n",
        "### 조치 1-RDMs\n",
        "\n",
        "이 단계에서는 양자 특징 맵에 대한 투영 측정을 통해 모든 단일 큐비트 축소 밀도 행렬(1-RDM)을 구하며, 이 결과는 나중에 고전적 지수 커널 함수에 입력될 것입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea823c34-d7d7-42f8-989c-dfe78cdd489a",
      "metadata": {},
      "source": [
        "모든 데이터를 실행하기 전에 데이터 세트에서 단일 데이터 포인트가 주어졌을 때 1-RDM을 계산하는 방법을 살펴보겠습니다. 1-RDM은 모든 큐비트에 대한 폴리 `X`, `Y` 및 `Z` 연산자의 단일 큐비트 측정값 모음입니다. 이는 단일 큐비트 RDM을 다음과 같이 완전히 표현할 수 있기 때문입니다: $\\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": [
        "먼저 사용할 백엔드를 선택합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "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": [
        "그런 다음 양자 회로를 실행하고 투영을 측정합니다. ZNE(제로 노이즈 추정)를 포함한 오류 완화 기능을 사용하도록 설정합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "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": [
        "다음으로 결과를 검색합니다.\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": [
            "[ 0.03530987 -0.06207794 -0.03529884 -0.1418671   0.00209782  0.0045834\n",
            "  0.00407694  0.02528003  0.00233791  0.01800766  0.00718357  0.01927931\n",
            "  0.0073651  -0.02009021  0.01144208  0.01333925  0.00521008  0.00535276\n",
            " -0.04354042 -0.0383848  -0.04472125  0.00641964 -0.03954627  0.03207479\n",
            "  0.01823132  0.02546267 -0.          0.16288225  0.03246113  0.\n",
            "  0.06107868  0.01082782  0.00240078  0.13147612  0.14033432  0.14925945\n",
            "  0.11577918  0.00016128 -0.          0.00604693  0.02433089  0.02033885\n",
            "  0.01492506  0.00494294  0.00926954  0.00569533  0.09867722  0.05662552\n",
            " -0.00001734  0.          0.          0.04625459 -0.02480763  0.01360688\n",
            "  0.11511306  0.01260572 -0.01656313 -0.02510078 -0.03256272  0.00058607]\n",
            "[-0.0756078  -0.05445208 -0.0228333  -0.00015029  0.00006226  0.02925132\n",
            " -0.00325556 -0.00889965  0.0177611  -0.00437065  0.01682502 -0.00229805\n",
            " -0.01041899 -0.03208967 -0.03515749  0.17477371  0.03783633  0.2126005\n",
            "  0.          0.          0.00754466 -0.08242599  0.          0.03263675\n",
            "  0.00399151 -0.01984418 -0.02106749 -0.02580491  0.03973411 -0.02037816\n",
            " -0.01769352 -0.09720746  0.00098896 -0.11840454  0.14392615  0.13647983\n",
            "  0.08683845  0.04492138  0.0046172   0.04171398 -0.0000869  -0.00270916\n",
            " -0.0019876  -0.00440696  0.0307905  -0.0284622   0.11237189  0.15042867\n",
            "  0.1020601  -0.03812461  0.00302523 -0.05240398 -0.01304566 -0.00403933\n",
            " -0.01324601 -0.03658085  0.00934269 -0.00105112 -0.          0.01761827]\n",
            "[ 0.57921657  0.2865493   0.          0.00028356  0.03177571  0.01152152\n",
            "  0.00843001  0.02320127  0.00273558  0.00976802  0.00060077  0.00942531\n",
            "  0.00096361 -0.03950026  0.00560635  0.00591487  0.00788236  0.01346192\n",
            "  0.60752971  0.80203507  0.65649176  0.00069473  0.06010304  0.05922109\n",
            "  0.01670672  0.02900743  0.0162253   0.0668811   0.01573204 -0.00288162\n",
            "  0.04216451  0.00848301  0.00052577 -0.33798808  0.68075471  0.89471233\n",
            "  0.72272544  0.08096828  0.02387351  0.01723619  0.00774532  0.05513527\n",
            "  0.08285531  0.08102448  0.10677406  0.27778995  0.28883482  0.21497224\n",
            "  0.17569826  0.00063149  0.0320076   0.06735008 -0.00053637 -0.0006907\n",
            "  0.00991596  0.00414575 -0.08425133 -0.09569482  0.00219474  0.00241873]\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": [
        "회로 크기와 2쿼비트 게이트 깊이를 출력합니다.\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: 96\n",
            "2q-size: 2832\n",
            "Operator counts: OrderedDict([('rz', 8640), ('sx', 7104), ('cz', 2832), ('x', 720), ('barrier', 47)])\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": [
        "이제 전체 학습 데이터 세트를 반복하여 모든 1-RDM을 얻을 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "eabb625e-edc1-445f-ad14-5e472d8a2879",
      "metadata": {},
      "source": [
        "또한 양자 하드웨어에서 실행한 실험의 결과도 제공합니다. 아래 플래그( `True`)를 설정하여 직접 교육을 실행하거나 저희가 제공하는 투영 결과를 사용할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "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": 15,
      "id": "932201c0-178b-4a98-b2dc-5b4c81953d49",
      "metadata": {},
      "outputs": [],
      "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": [
        "작업이 완료되면 결과를 검색할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "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": [
        "테스트 세트에 대해 이 과정을 반복합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "f9e77a9c-d295-4893-aebe-74cc59168e1f",
      "metadata": {},
      "outputs": [],
      "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": [
        "이전과 마찬가지로 결과를 검색할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 18,
      "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",
        "### 4단계: 후처리 수행 및 원하는 클래식 형식으로 결과 반환\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "7e10f152-d20c-4c70-9530-e9e71c309b59",
      "metadata": {},
      "source": [
        "<span id=\"define-the-projected-quantum-kernel\" />\n",
        "\n",
        "### 예측된 양자 커널 정의\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "34150c16-e6ca-43a3-989d-444104dc60e5",
      "metadata": {},
      "source": [
        "투영된 양자 커널은 다음 커널 함수로 정의됩니다: $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) $ 위의 방정식에서 $\\gamma>0$ 는 조정 가능한 하이퍼파라미터입니다. $K^{\\textrm{PQ}}_{ij} = k^{\\textrm{PQ}}(x_i, x_j)$ 은 커널 매트릭스 $K^{\\textrm{PQ}}$ 의 항목입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42cea21f-3178-4ac9-8826-db7ca7cdfe20",
      "metadata": {},
      "source": [
        "1-RDM의 정의를 사용하면 커널 함수 내의 개별 용어는 $\\textrm{Tr}[P \\rho_k (x_i)] = \\braket P$ 로 평가할 수 있으며, 여기서 $P \\in \\{ X,Y,Z \\}$. 이러한 기대값은 위에서 측정한 것과 정확히 일치합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "22e41ed2-93be-4b30-a898-8d2832d1e0ab",
      "metadata": {},
      "source": [
        "`scikit-learn` 을 사용하면 실제로 커널을 훨씬 더 쉽게 계산할 수 있습니다. 이는 쉽게 사용할 수 있는 방사형 기준 함수(`'rbf'`) 커널( $ \\textrm{exp} (-\\gamma \\lVert x - x' \\rVert^2)$ ) 때문입니다. 먼저, 새로 투영된 훈련 및 테스트 데이터 세트를 2차원 배열로 재구성하기만 하면 됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "e01abd8a-2194-41b9-adaf-7ec80f14e3b1",
      "metadata": {},
      "source": [
        "전체 데이터 집합을 검토하는 데는 QPU에서 약 80분이 소요될 수 있습니다. 튜토리얼의 나머지 부분을 쉽게 실행할 수 있도록 이전에 실행한 실험의 예제를 추가로 제공합니다( `Download dataset` 코드 블록에서 다운로드한 파일에 포함되어 있음). 직접 교육을 수행한 경우 자신의 결과를 가지고 튜토리얼을 계속 진행할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 19,
      "id": "a61e6c67-056b-4659-87a0-284bda432cfd",
      "metadata": {},
      "outputs": [],
      "source": [
        "# ---------------------------------------------------------------------------\n",
        "# Load projections — either from the hardware run above, or from the\n",
        "# pre-computed CSVs that were downloaded alongside the motif data.\n",
        "# ---------------------------------------------------------------------------\n",
        "\n",
        "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(DATA_DIR / \"projections_train.csv\")\n",
        "    projections_test = np.loadtxt(DATA_DIR / \"projections_test.csv\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2c90c625-27c1-46a0-80a0-5ead9d3c64b7",
      "metadata": {},
      "source": [
        "<span id=\"support-vector-machine-svm\" />\n",
        "\n",
        "### 지원 벡터 기계(SVM)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b2ea7dde-63ec-4e3d-b6dc-dbeab6a07fda",
      "metadata": {},
      "source": [
        "이제 이 사전 계산된 커널에서 클래식 SVM을 실행하고 테스트 세트와 학습 세트 사이에서 커널을 사용하여 예측할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "d2eef986-22a5-4528-8fa0-c7dbfd586071",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fitting 10 folds for each of 154 candidates, totalling 1540 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",
        "#\n",
        "# This is a reduced grid so the tutorial runs quickly (154 candidates).\n",
        "# The optimal (C, gamma) reported below lie within it, so the results\n",
        "# are unchanged. The full grid used originally had 6622 candidates and\n",
        "# took roughly one hours to search:\n",
        "#\n",
        "#   C_range = [0.001, 0.005, 0.007]\n",
        "#   C_range.extend([x * 0.01 for x in range(1, 11)])   # 0.01 .. 0.10\n",
        "#   C_range.extend([x * 0.25 for x in range(1, 60)])   # 0.25 .. 14.75\n",
        "#   C_range.extend([20, 50, 100, 200, 500, 700, 1000,\n",
        "#                   1100, 1200, 1300, 1400, 1500, 1700, 2000])\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",
        "C_range = [\n",
        "    0.001,\n",
        "    0.01,\n",
        "    0.1,\n",
        "    0.5,\n",
        "    1.0,\n",
        "    2.0,\n",
        "    4.0,\n",
        "    6.0,\n",
        "    8.5,\n",
        "    10.75,\n",
        "    14.0,\n",
        "    20,\n",
        "    50,\n",
        "    100,\n",
        "]\n",
        "gamma_range = [\n",
        "    0.001,\n",
        "    0.005,\n",
        "    0.007,\n",
        "    0.01,\n",
        "    0.02,\n",
        "    0.03,\n",
        "    0.04,\n",
        "    0.05,\n",
        "    0.1,\n",
        "    0.5,\n",
        "    1.0,\n",
        "]\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",
        "### 고전적 벤치마킹\n",
        "\n",
        "양자 투영을 하지 않고도 방사형 기저 함수를 커널로 사용하는 고전적인 SVM을 실행할 수 있습니다. 이 결과는 저희의 대표적인 벤치마크입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "41867b5a-9091-4aa4-adab-a05cf6238966",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Fitting 10 folds for each of 154 candidates, totalling 1540 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",
        "## 부록: 학습 작업에서 데이터셋의 잠재적 양자 우위 검증\n",
        "\n",
        "모든 데이터 세트가 PQK 사용으로 잠재적인 이점을 제공하는 것은 아닙니다. 특정 데이터 세트가 PQK의 이점을 누릴 수 있는지 확인하기 위한 예비 테스트로 사용할 수 있는 몇 가지 이론적 한계가 있습니다. 이를 정량화하기 위해 [양자 머신 러닝에서 데이터의 힘](https://www.nature.com/articles/s41467-021-22539-9) \\[2의] 저자는 고전적 모델과 양자 모델의 복잡도와 고전적 모델과 양자 모델의 기하학적 분리라는 수치를 정의합니다. PQK에서 잠재적인 양자 이점을 기대하려면 기존 커널과 양자 투영 커널 사이의 기하학적 분리가 대략 $\\sqrt{N}$ 정도여야 하며, 여기서 $N$ 은 훈련 샘플의 수입니다. 이 조건이 충족되면 모델 복잡도를 확인하는 단계로 넘어갑니다. 고전적 모델 복잡도가 $N$ 정도이고 양자 투영 모델 복잡도가 $N$ 보다 훨씬 작다면, PQK의 잠재적 이점을 기대할 수 있습니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "d8fae838-a34b-4cf5-ba98-579ec8527fde",
      "metadata": {},
      "source": [
        "기하학적 분리는 다음과 같이 정의됩니다( [\\[2\\]](#references) 의 F19 ): $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": 22,
      "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": [
        "모델 복잡도는 다음과 같이 정의됩니다( [\\[2\\]](#references) 의 M1 ): $ 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": 23,
      "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": 24,
      "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",
        "## 다음 단계\n",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:\n",
        "\n",
        "  * IBM Quantum Learning 의 심층 [양자 머신러닝 강좌](/learning/courses/quantum-machine-learning)\n",
        "  * [양자 커널 학습](/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",
        "## 참조\n",
        "\n",
        "1. 우트로, 필리포 외. \"[양자 커널 방법을 통한 CAR T세포 세포 독성 예측 향상](https://arxiv.org/abs/2507.22710).\" arXiv 사전 인쇄 arXiv:2507.22710 (2025).\n",
        "2. \"[양자 머신 러닝에서 데이터의 힘](https://www.nature.com/articles/s41467-021-22539-9).\" 네이처 커뮤니케이션즈 12.1 (2021): 2631.\n",
        "3. 다니엘스, 카일 G., \"[조합 신호 모티프 라이브러리와 머신 러닝을 이용한 CAR T 세포 표현형 해독](https://www.science.org/doi/full/10.1126/science.abq0225).\" 과학 378.6625 (2022): 1194-1200.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "hours": 2.5,
    "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"
    },
    "qpuSeconds": 4800,
    "widgets": {
      "application/vnd.jupyter.widget-state+json": {
        "state": {},
        "version_major": 2,
        "version_minor": 0
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 4
}