{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"시공간 코드를 이용한 저오버헤드 오류 검출\"\n",
        "description: \"qiskit-paulice를 사용하여 시공간 파울리 검사를 자동으로 삽입하고, 검사 증후군에 대한 사후 선별을 통해 샘플링된 클리포드 회로의 충실도를 높입니다.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore postsel, frameon, virt */}\n",
        "\n",
        "<span id=\"low-overhead-error-detection-with-spacetime-codes\" />\n",
        "\n",
        "# 시공간 코드를 이용한 저오버헤드 오류 검출\n",
        "\n",
        "예상 *소요 시간: 헤론 프로세서(`ibm_kingston` 또는 이에 상응하는 프로세서)에서 4분 (참고: 이는 단지 추정치일 뿐입니다.) (실행 시간은 다를 수 있습니다.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning-outcomes",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## 학습 성과\n",
        "\n",
        "* 시공간 파울리 검사가 클리포드 회로의 논리적 오류를 어떻게 탐지하는지, 그리고 그 증후군에 대한 사후 선별이 표본 분포의 충실도를 어떻게 향상시키는지에 대해 살펴본다.\n",
        "* 패키지를 `qiskit-paulice` 사용하여 `get_check_qubits`, `NoiseModel`, 및 을 통해 하드웨어 효율적인 검사 항목을 자동으로 찾아 삽입하는 방법 `add_pauli_checks`.\n",
        "* 안정화 상태의 안정화 인자를 표본 추출하고 체크 신드롬을 기반으로 사후 선별을 수행하여 안정화 상태의 충실도를 추정하는 방법.\n",
        "* IBM Quantum® 하드웨어에서 전체 오류 탐지 워크플로를 실행하고, 노이즈가 포함된 정확도와 사후 선별된 정확도를 비교하는 방법.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prerequisites",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## 전제조건\n",
        "\n",
        "* 대규모 양자 컴퓨팅을 위한 [하드웨어 기초](/learning/courses/utility-scale-quantum-computing/hardware).\n",
        "* 클리포드 및 안정자 형식론, 특히 안정자군이 순수 안정자 상태를 어떻게 기술하는지를 다룬다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## 배경\n",
        "\n",
        "Simon Martiel과 Ali Javadi-Abhari가 저술한 [“시공간 코드를 이용한 저오버헤드 오류 검출](https://arxiv.org/abs/2504.15725) [\\[1\\]](#references) ”은 완전한 오류 정정 방식과 경량 오류 완화 방식의 중간 단계에 해당하는, 클리포드 지배 회로에서 논리적 오류를 검출하는 방법을 제시한다. 이 아이디어는 반 덴 베르그(van den Berg) 등이 발표한 “코히어런트 [파울리](https://journals.aps.org/prresearch/abstract/10.1103/PhysRevResearch.5.033193) 검증을 통한 단일 샷 오류 완화(Single-shot error mitigation by coherent Pauli checks)” [\\[2\\]](#references) 에서 제시된 코히어런트 파울리 검증(CPC)을 바탕으로 합니다. 두 접근법 모두에서, 클리포드 “페이로드” 회로는 보조 큐비트와 얽힘 상태를 형성하여 특정 불변량을 검증합니다. ancillas를 측정하면 실행 중에 오류가 감지되었는지 여부를 알려주는 신호가 생성됩니다. 오류가 감지되지 않은 샘플만 유지하면, 사후 선택률이 낮아지는 대가를 치르긴 하지만, 샘플링된 분포의 정확도는 향상됩니다.\n",
        "\n",
        "코히어런트 파울리 검사와 시공간 검사의 주요 차이점은 측정 대상이 되는 연산자입니다. 코히어런트 파울리 검사는 시간에 국한되고 가중치가 높은 연산자를 측정합니다. 헤비 헥스(heavy hex)와 같이 연결성이 제한된 큐비트 토폴로지의 경우, 이러한 검사에는 많은 SWAP 게이트가 필요하며, 이로 인해 회로가 너무 복잡해져 실제로 실행하기 어려운 경우가 많습니다. 대신 검사 기능을 시공간 코드로 구현하면, 각 검사가 시공간에 걸쳐 페이로드 회로 전반에 분산됩니다. 이를 통해 큐비트 및 실행 깊이 오버헤드를 낮게 유지하면서도 논리적 오류를 효과적으로 탐지할 수 있는 하드웨어 효율적인 인코딩 방식을 얻을 수 있다.\n",
        "\n",
        "<span id=\"what-the-qiskit-paulice-package-does\" />\n",
        "\n",
        "### 이 `qiskit-paulice` 패키지의 기능\n",
        "\n",
        "이 패키지는 `qiskit-paulice` 이러한 검사 기능을 자동으로 생성해 주므로, 사용자가 직접 만들 필요가 없습니다. 이 알고리즘의 주된 역할은 회로 내의 특정 위치에 유효한 시공간 파울리 검사 항을 찾아 삽입함으로써, 오류 탐지율을 극대화하는 동시에 큐비트 오버헤드를 최소화하는 것입니다. 체크는 그 연산자가 페이로드 회로의 논리적 동작을 변경하지 않을 때 *유효하며*, 얽힘 게이트를 적게 사용할 때 *저부하이고*, 체크 자체가 유발하는 노이즈에 비해 오류의 상당 부분을 감지할 때 *효과적이다*. 이 패키지는 후보 검사 결과를 노이즈 모델에 대조하여 평가하고, 그중 가장 우수한 결과를 회로에 반영합니다. 이 튜토리얼에서는 다음 세 가지 API 메서드를 사용합니다:\n",
        "\n",
        "* `get_check_qubits` 백엔드 결합 맵을 검사하여 대상 큐비트와 보조 큐비트 쌍을 반환합니다. 에서 확인 시 를 사용합니다 `target_qubits[i]``ancilla_qubits[i]` .\n",
        "* `NoiseModel.from_backend` 백엔드 벤치마크 데이터를 바탕으로 대략적인 노이즈 모델을 구축합니다. 이 모델은 후보 검사를 평가하므로, 학습된 정확한 노이즈 모델이 반드시 필요한 것은 아닙니다. 학습된 파울리-린드블라드 모델에 대해서는 다음을 참조하십시오 [`NoiseModel.from_pauli_lindblad_maps`](/docs/api/qiskit-addon-paulice/qiskit-paulice-noise-models#from_pauli_lindblad_maps).\n",
        "* `add_pauli_checks` 회로에서 체크를 찾아서 삽입합니다. 이 함수는 검사 횟수가 점차 증가하는 `CheckedCircuit` 객체 시퀀스를 반환하며, 각 객체는 측정된 비트열을 증후 벡터로 매핑하는 `get_postselection_method` 함수를 제공합니다. 이 `cost` 인수는 체크 점수를 산출하는 함수(`gamma`, 사후 선택된 역잡음 채널의 표본 추출 오버헤드, 또는 `LER`, 논리 오류율)를 선택합니다. 이 `method` 인수는 검색 전략(`windowed`, `genetic`, 또는 `windowed_genetic`)을 선택합니다. 이 튜토리얼에서는 와 `cost=\"gamma\"` 를 사용하며 `method=\"windowed\"`, 이 두 가지를 함께 사용하면 결정론적이고 재현 가능한 체크 선택이 가능합니다.\n",
        "\n",
        "<span id=\"estimate-fidelity-from-stabilizer-sampling\" />\n",
        "\n",
        "### 안정화 샘플링을 통한 충실도 추정\n",
        "\n",
        "오류 검출 기능이 얼마나 잘 작동하는지 측정하기 위해, 회로가 이상적으로 준비한 안정화 상태 $|\\psi\\rangle = U|0\\rangle^{\\otimes n}$ 와 하드웨어가 실제로 출력한 잡음이 섞인 상태 $\\rho$ 간의 충실도를 추정할 수 있습니다. 순수 안정자 상태 $|\\psi\\rangle$ 에 대한 투영은, 해당 안정자 군 $\\mathcal{S}$ 의 $2^n$ 원소들에 대한 균일한 평균과 같다:\n",
        "\n",
        "$|\\psi\\rangle\\langle\\psi| = \\frac{1}{2^n}\\sum_{G \\in \\mathcal{S}} G.$\n",
        "\n",
        "이를 충실도에 대입하면, $\\rho$ 의 충실도는 $\\rho$ 에 대한 모든 안정화자 $G \\in \\mathcal{S}$ 의 평균 기대값으로 주어집니다:\n",
        "\n",
        "$F = \\mathrm{Tr}(\\rho|\\psi\\rangle\\langle\\psi|) = \\frac{1}{2^n} \\sum_{G \\in \\mathcal{S}} \\mathrm{Tr}(\\rho G) = \\frac{1}{2^n} \\sum_{G \\in \\mathcal{S}} \\langle G \\rangle_\\rho.$\n",
        "\n",
        "규모가 큰 문제의 경우, 모든 $2^n$ 안정화자를 모두 열거하는 것은 사실상 불가능하므로, 무작위 표본을 통해 충실도를 추정할 수 있습니다. $\\mathcal{S}$ 에서 $M$ 안정화 인자 $G_1, \\ldots, G_M$ 를 균일한 확률로 무작위로 추출하면, 다음과 같은 편향 없는 추정치를 얻을 수 있다:\n",
        "\n",
        "$\\hat F_M = \\frac{1}{M} \\sum_{i=1}^M \\langle G_i \\rangle_\\rho.$\n",
        "\n",
        "클리포드 회로는 안정화 상태를 생성하기 때문에, 안정화 연산자들의 표본 기대값을 통해 그 충실도를 직접 추정할 수 있다. 이 튜토리얼에서는 먼저 간단한 회로를 사용하여 시뮬레이터에서 워크플로를 단계별로 살펴본 다음, 더 크고 복잡한 회로를 사용하여 하드웨어에서 동일한 워크플로를 실행합니다. 회로에 클리포드 연산이 아닌 연산이 더 많이 포함될수록 유효한 검사 횟수가 급격히 줄어들기 때문에, 이 방법은 클리포드 연산이 주를 이루는 회로에서 가장 효과적이다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "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",
        "* Qiskit Aer v0.17 이상 (`pip install qiskit-aer`)\n",
        "* Qiskit Paulice (`pip install qiskit-paulice`)\n",
        "* tqdm (`pip install tqdm`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## 설정\n",
        "\n",
        "필요한 라이브러리를 불러오고, 임포트로 사용할 수 없는 헬퍼 함수를 정의합니다. 이 `random_clifford_circuit` 함수는 브릭워크 방식의 무작위 클리포드 페이로드를 생성하고, 백엔드 결합 맵을 `find_check_layout` 검색하여 사용 가능한 보조 큐비트가 많은 저오차 큐비트 경로를 찾은 뒤, `NoiseLearner` 출력을 노이즈 `qiskit-paulice` 모델로 변환하고 `learned_noise_model` , 안정화기가 계산 기저에서 측정되도록 회로를 `append_basis_rotation` 회전시킨 다음, 샘플링된 카운트 값을 바탕으로 안정화기의 기대값을 `expectation` 계산하며, 실행 중인 충실도 추정치를 `cum_mean_sem` 추적합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "setup-imports",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Standard library imports\n",
        "import random\n",
        "import time\n",
        "\n",
        "# External libraries\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from tqdm import tqdm\n",
        "\n",
        "# Qiskit\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.quantum_info import Clifford, Pauli, PauliLindbladMap, PauliList\n",
        "from qiskit.result import sampled_expectation_value\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit.visualization import plot_coupling_map\n",
        "\n",
        "# Qiskit Aer\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit_aer.noise import NoiseModel as AerNoiseModel\n",
        "from qiskit_aer.noise import ReadoutError, depolarizing_error\n",
        "\n",
        "# Qiskit IBM Runtime\n",
        "from qiskit_ibm_runtime import NoiseLearner, QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import SamplerV2 as Sampler\n",
        "\n",
        "# Qiskit Paulice\n",
        "from qiskit_paulice import add_pauli_checks\n",
        "from qiskit_paulice.layout import get_check_qubits\n",
        "from qiskit_paulice.noise_models import NoiseModel"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "setup-helpers",
      "metadata": {},
      "outputs": [],
      "source": [
        "def random_clifford_circuit(\n",
        "    num_qubits: int, depth: int, rng: np.random.Generator\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Brickwork random Clifford on `num_qubits`, with `depth` CZ layers.\"\"\"\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    qc.h(range(num_qubits))\n",
        "    for d in range(depth):\n",
        "        for i in range(d % 2, num_qubits - 1, 2):\n",
        "            qc.cz(i, i + 1)\n",
        "        for q in range(num_qubits):\n",
        "            if rng.integers(0, 2):\n",
        "                qc.sx(q)\n",
        "            if rng.integers(0, 2):\n",
        "                qc.s(q)\n",
        "            if rng.integers(0, 2):\n",
        "                qc.sx(q)\n",
        "    return qc\n",
        "\n",
        "\n",
        "def find_check_layout(\n",
        "    backend,\n",
        "    num_qubits: int,\n",
        "    rng: np.random.Generator,\n",
        "    num_trials: int = 200,\n",
        "    max_gate_error: float = 0.03,\n",
        "    max_readout_error: float = 0.2,\n",
        ") -> list[int]:\n",
        "    \"\"\"Find a low-error path of `num_qubits` qubits with many available ancillas.\n",
        "\n",
        "    Builds random self-avoiding walks on the coupling map, excluding the qubits\n",
        "    and two-qubit gates whose reported errors exceed the thresholds, and keeps\n",
        "    the path that offers the most target and ancilla pairs. Ties are broken by\n",
        "    the lower average two-qubit gate error along the path.\n",
        "    \"\"\"\n",
        "    target = backend.target\n",
        "    gate_2q = next(\n",
        "        name for name in (\"cz\", \"ecr\", \"cx\") if name in target.operation_names\n",
        "    )\n",
        "\n",
        "    # Collect per-edge gate errors and per-qubit readout errors\n",
        "    edge_error = {}\n",
        "    for qubits, props in target[gate_2q].items():\n",
        "        edge = tuple(sorted(qubits))\n",
        "        if props is not None and props.error is not None:\n",
        "            edge_error[edge] = min(edge_error.get(edge, 1.0), props.error)\n",
        "    readout_error = {\n",
        "        qubit: target[\"measure\"][(qubit,)].error\n",
        "        for (qubit,) in target[\"measure\"]\n",
        "    }\n",
        "\n",
        "    # Keep only the edges whose gate and readout errors are acceptable\n",
        "    adjacency = {}\n",
        "    for (q1, q2), error in edge_error.items():\n",
        "        if (\n",
        "            error <= max_gate_error\n",
        "            and readout_error.get(q1, 1.0) <= max_readout_error\n",
        "            and readout_error.get(q2, 1.0) <= max_readout_error\n",
        "        ):\n",
        "            adjacency.setdefault(q1, set()).add(q2)\n",
        "            adjacency.setdefault(q2, set()).add(q1)\n",
        "\n",
        "    # Random self-avoiding walks; keep the path with the most check pairs\n",
        "    starts = sorted(adjacency)\n",
        "    best_path = None\n",
        "    best_score = (-1, float(\"inf\"))\n",
        "    for _ in range(num_trials):\n",
        "        path = [starts[rng.integers(len(starts))]]\n",
        "        while len(path) < num_qubits:\n",
        "            options = sorted(adjacency[path[-1]] - set(path))\n",
        "            if not options:\n",
        "                break\n",
        "            path.append(options[rng.integers(len(options))])\n",
        "        if len(path) < num_qubits:\n",
        "            continue\n",
        "        num_pairs = len(get_check_qubits(backend.coupling_map, path)[0])\n",
        "        mean_error = float(\n",
        "            np.mean(\n",
        "                [edge_error[tuple(sorted(e))] for e in zip(path, path[1:])]\n",
        "            )\n",
        "        )\n",
        "        if num_pairs > best_score[0] or (\n",
        "            num_pairs == best_score[0] and mean_error < best_score[1]\n",
        "        ):\n",
        "            best_path, best_score = path, (num_pairs, mean_error)\n",
        "\n",
        "    if best_path is None:\n",
        "        raise RuntimeError(\n",
        "            \"No connected low-error path found. Relax the error thresholds.\"\n",
        "        )\n",
        "    return best_path\n",
        "\n",
        "\n",
        "def learned_noise_model(layer_errors, layout: list[int]) -> NoiseModel:\n",
        "    \"\"\"Build a `NoiseModel` from `NoiseLearner` results.\n",
        "\n",
        "    `NoiseLearner` reports one `PauliLindbladError` per entangling layer, whose\n",
        "    generators are indexed against that layer's own physical qubits, while\n",
        "    `NoiseModel.from_pauli_lindblad_maps` expects `PauliLindbladMap`s indexed the\n",
        "    way `NoiseModel.from_backend` indexes them: by position in `layout`. This\n",
        "    translates between the two and drops generators that fall outside `layout`.\n",
        "    \"\"\"\n",
        "    phys_to_virt = {phys: virt for virt, phys in enumerate(layout)}\n",
        "    maps = []\n",
        "    for layer in layer_errors:\n",
        "        if layer.error is None:\n",
        "            continue\n",
        "        terms = []\n",
        "        for pauli, rate in zip(\n",
        "            layer.error.generators, layer.error.rates, strict=True\n",
        "        ):\n",
        "            label, indices = [], []\n",
        "            for local, phys in enumerate(layer.qubits):\n",
        "                x, z = bool(pauli.x[local]), bool(pauli.z[local])\n",
        "                if not (x or z):\n",
        "                    continue\n",
        "                if phys not in phys_to_virt:\n",
        "                    break  # generator reaches outside the layout, so skip it\n",
        "                label.append(\"Y\" if x and z else \"X\" if x else \"Z\")\n",
        "                indices.append(phys_to_virt[phys])\n",
        "            else:\n",
        "                if label:\n",
        "                    terms.append(\n",
        "                        (\"\".join(label), tuple(indices), float(rate))\n",
        "                    )\n",
        "        # Each map needs a 2-qubit generator to define an entangling layer\n",
        "        if any(len(t[1]) == 2 for t in terms):\n",
        "            maps.append(\n",
        "                PauliLindbladMap.from_sparse_list(\n",
        "                    terms, num_qubits=len(layout)\n",
        "                )\n",
        "            )\n",
        "    if not maps:\n",
        "        raise RuntimeError(\n",
        "            \"No usable layer errors. Check that the learner ran on this layout.\"\n",
        "        )\n",
        "    return NoiseModel.from_pauli_lindblad_maps(maps)\n",
        "\n",
        "\n",
        "def append_basis_rotation(\n",
        "    circuit: QuantumCircuit, pauli: Pauli\n",
        ") -> QuantumCircuit:\n",
        "    \"\"\"Strip measurements, append basis rotations for `pauli`, and re-measure.\"\"\"\n",
        "    out = circuit.remove_final_measurements(inplace=False)\n",
        "    for q in range(pauli.num_qubits):\n",
        "        if pauli.x[q]:\n",
        "            if pauli.z[q]:\n",
        "                out.sdg(q)\n",
        "            out.h(q)\n",
        "    out.measure_all()\n",
        "    return out\n",
        "\n",
        "\n",
        "def expectation(counts: dict, pauli: Pauli) -> float:\n",
        "    \"\"\"Expectation value of `pauli` from counts measured in the Z basis.\n",
        "\n",
        "    Pads with identity on any qubits beyond the support of `pauli`, such as the\n",
        "    check ancillas that appear in the postselected counts.\n",
        "    \"\"\"\n",
        "    if not counts:\n",
        "        return float(\"nan\")\n",
        "    n = pauli.num_qubits\n",
        "    sign = -1 if int(pauli.phase) % 4 == 2 else 1\n",
        "    total = len(next(iter(counts)))\n",
        "    label = \"\".join(\n",
        "        \"Z\" if q < n and (pauli.x[q] or pauli.z[q]) else \"I\"\n",
        "        for q in range(total - 1, -1, -1)\n",
        "    )\n",
        "    return sign * sampled_expectation_value(counts, label)\n",
        "\n",
        "\n",
        "def cum_mean_sem(values: np.ndarray):\n",
        "    \"\"\"Cumulative mean and standard error of the mean, ignoring NaNs.\"\"\"\n",
        "    valid = ~np.isnan(values)\n",
        "    total = np.cumsum(np.where(valid, values, 0.0))\n",
        "    total_sq = np.cumsum(np.where(valid, values**2, 0.0))\n",
        "    count = np.maximum(np.cumsum(valid).astype(float), 1)\n",
        "    mean = total / count\n",
        "    sem = np.sqrt(np.maximum(total_sq / count - mean**2, 0) / count)\n",
        "    return np.where(np.cumsum(valid) > 0, mean, np.nan), sem"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "small-scale-header",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## 소규모 시뮬레이터 예시\n",
        "\n",
        "이 섹션에서는 노이즈가 있는 시뮬레이터에서 전체 워크플로를 단계별로 안내합니다. 이 방법은 백엔드 벤치마크 데이터를 활용하여 큐비트 배열과 노이즈 모델을 선택하고, 체크를 자동으로 찾아내며, 샘플링된 분포에 대해 사후 선별을 적용하여 충실도 향상을 보여줍니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step1-header",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### 1단계: 고전적 입력을 양자 문제에 매핑하기\n",
        "\n",
        "이 페이로드 회로는 얕은 1차원 벽돌 구조의 무작위 클리포드 회로입니다. 이 회로는 클리포드 회로이기 때문에, 샘플링된 안정화기 기대값을 통해 그 충실도를 직접 추정할 수 있는 안정화기 상태를 생성합니다. 다음 단계에서 확인 사항을 쉽게 파악할 수 있도록, 우선 얕은 회로부터 시작하세요.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "step1-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step1-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 12\n",
        "depth = 4\n",
        "seed = 1764\n",
        "rng = np.random.default_rng(seed)\n",
        "np.random.seed(seed)\n",
        "\n",
        "circuit = random_clifford_circuit(num_qubits, depth, rng)\n",
        "circuit.measure_all()\n",
        "circuit.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-header",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-for-quantum-hardware-execution\" />\n",
        "\n",
        "### 2단계: 양자 하드웨어 실행에 최적화하기\n",
        "\n",
        "회로를 하드웨어에 매핑하면 물리적 큐비트 배열, 후보 체크를 평가하는 노이즈 모델, 그리고 체크 자체가 설정됩니다.\n",
        "\n",
        "먼저, 백엔드를 선택하고, 해당 백엔드의 커플링 맵에서 ‘설정’ 섹션에 정의된 `find_check_layout` 헬퍼가 포함된 1차원 큐비트 배치를 검색합니다. 이 헬퍼는 오류가 가장 큰 게이트와 판독 단계를 피하는 무작위 자기 회피 경로를 생성하며, 대상과 보조 장치 쌍을 가장 많이 제공하는 경로를 유지합니다. 이 검색 기능은 백엔드 자체에서 연결 상태 및 오류 데이터를 읽어오기 때문에, 동일한 코드가 어떤 IBM Quantum QPU에서든 실행됩니다. 그런 다음 이 `get_check_qubits` 함수는 대상과 보조체 쌍을 반환하며, 이때 에 대한 검사는 를 사용합니다 `target_qubits[i]``ancilla_qubits[i]` .\n",
        "\n",
        "다음의 결합 그래프에서, 녹색 큐비트는 페이로드 큐비트이고, 주황색 큐비트는 검증을 수행하는 보조 큐비트입니다. 인접한 보조 큐비트를 가진 큐비트들은 검증을 위한 대상 큐비트로 사용된다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "step2-layout",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: ibm_boston\n",
            "Target qubits:  [105, 107, 108, 123, 125, 141, 143]\n",
            "Ancilla qubits: [104, 97, 109, 122, 126, 140, 144]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-layout-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(operational=True, simulator=False)\n",
        "\n",
        "print(f\"Backend: {backend.name}\")\n",
        "\n",
        "# Search for a low-error path, then pair each target qubit with a neighboring ancilla\n",
        "layout = find_check_layout(backend, num_qubits, rng)\n",
        "target_qubits, ancilla_qubits = get_check_qubits(backend, layout)\n",
        "num_checks = len(target_qubits)\n",
        "\n",
        "print(f\"Target qubits:  {target_qubits}\")\n",
        "print(f\"Ancilla qubits: {ancilla_qubits}\")\n",
        "plot_coupling_map(\n",
        "    num_qubits=backend.num_qubits,\n",
        "    qubit_coordinates=getattr(\n",
        "        backend.configuration(), \"qubit_coordinates\", None\n",
        "    ),\n",
        "    coupling_map=backend.configuration().coupling_map,\n",
        "    figsize=(12, 12),\n",
        "    qubit_color=[\n",
        "        \"#4CAF50\"\n",
        "        if i in set(layout)\n",
        "        else \"#FF9800\"\n",
        "        if i in set(ancilla_qubits)\n",
        "        else \"#DDDDDD\"\n",
        "        for i in backend.coupling_map.graph.node_indices()\n",
        "    ],\n",
        "    qubit_size=220,\n",
        "    line_width=2,\n",
        "    font_size=90,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-transpile-md",
      "metadata": {},
      "source": [
        "백엔드와 레이아웃을 선택한 후, 페이로드를 명령어 집합 아키텍처(ISA) 회로로 트랜스파일합니다. 레이아웃을 설정하고 게이트를 백엔드의 기본 게이트 세트로 변환하기만 하면 됩니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "step2-transpile",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-transpile-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "pm = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=backend, initial_layout=layout\n",
        ")\n",
        "circuit_isa = pm.run(circuit)\n",
        "circuit_isa.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-noise-md",
      "metadata": {},
      "source": [
        "다음으로, 백엔드의 게이트 노이즈와 판독 노이즈가 실행에 어떤 영향을 미치는지 모델링해 보겠습니다. 노이즈 모델은 회로 내 어디에서 검사가 가장 많은 오류를 포착하는지 결정합니다. 더 정확한 모델을 사용하면 탐지 성능이 향상되지만, 일반적으로 QPU를 샘플링하여 모델을 학습할 필요는 없습니다. 다음 모델은 벤치마크 `qiskit-ibm-runtime` 데이터를 바탕으로 게이트 노이즈와 판독 노이즈에 대해 균일한 탈분극 채널을 추정합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "step2-noise",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "NoiseModel(gate_noise=0.001079865281450939, readout_noise=0.006001790364583333, idling_noise=None)\n"
          ]
        }
      ],
      "source": [
        "noise_model = NoiseModel.from_backend(\n",
        "    backend, layout, uniform_gate_noise=True\n",
        ")\n",
        "print(noise_model)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step2-checks-md",
      "metadata": {},
      "source": [
        "이제 회로에 체크 기능을 추가하세요. 이 `add_pauli_checks` 함수는 클리포드 페이로드, 대상 큐비트 목록, 그리고 노이즈 모델을 입력으로 받습니다. 이 `ancilla_qubits` 인수는 함수에게 각 대상과 어떤 물리적 보조 시스템을 짝지어야 하는지 알려줍니다. 체크는 대상 큐비트가 나타나는 순서대로 추가되므로, 체크된 회로의 최종 레이아웃은 다음과 같습니다 `layout + ancilla_qubits`. (`i`) 검사가 더 적은 출력 회로를 구동하려면, 최종 레이아웃은 다음과 같습니다 `layout + ancilla_qubits[:i]`.\n",
        "\n",
        "의 출력은 검사 횟수가 점차 증가하는 일련의 회로로, 검사가 전혀 없는 경우부터 각 대상 큐비트마다 한 번씩 검사하는 경우까지를 포함합니다 `add_pauli_checks` . 이 시각화 결과를 통해 검사가 지정된 대상 및 보조체 쌍을 사용한다는 사실이 확인됩니다. 좋은 검정 방법을 찾는 방법에 대한 자세한 내용은 참고문헌 [\\[1\\]](#references) 의 보충 정보 중 제II\\~IV절을 참조하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "step2-checks",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Physical layout of payload and ancillas: [108, 107, 106, 105, 117, 125, 124, 123, 136, 143, 142, 141, 104, 97, 109, 122, 126, 140, 144]\n",
            "Checked circuit:\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step2-checks-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "checked = add_pauli_checks(\n",
        "    circuit_isa,\n",
        "    target_qubits,\n",
        "    noise_model,\n",
        "    ancilla_qubits=ancilla_qubits,\n",
        "    cost=\"gamma\",\n",
        "    method=\"windowed\",\n",
        "    seed=seed,\n",
        ")\n",
        "\n",
        "print(f\"Physical layout of payload and ancillas: {layout + ancilla_qubits}\")\n",
        "print(\"Checked circuit:\")\n",
        "checked[-1].circuit.draw(\"mpl\", fold=-1, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-header",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### 3단계: `Qiskit primitives`을 사용하여 실행하기\n",
        "\n",
        "게이트 노이즈의 영향을 시각화하려면 페이로드의 깊이를 늘리고, 그 안정화 회로 중 일부를 샘플링하십시오. 일반적으로 각 안정화자는 다른 안정화자와 큐비트 단위로 교환 관계를 이루지 않으므로, 하나의 검증 방식이 서로 다른 두 안정화자에 모두 적용될 수는 없습니다. 안정화자를 통근 집합으로 묶기보다는, 각 안정화자에 대해 독립적으로 적절한 검사 집합을 찾아야 한다. 안정화기를 균일하게 무작위로 표본 추출하면 편향 없는 충실도 추정치를 얻을 수 있다.\n",
        "\n",
        "더 깊은 회로를 구축하고, 그 회로의 안정화기 중에서 무작위로 표본을 추출하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "step3-stabilizers",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Sampled 20 stabilizers of a 12-qubit circuit with two-qubit depth 24: {ZXIIXZYYXIZZ, XXXYIIZYXIII, ...}\n"
          ]
        }
      ],
      "source": [
        "depth = 24\n",
        "num_stabilizers = 20\n",
        "num_shots = 1_000\n",
        "\n",
        "circuit = random_clifford_circuit(num_qubits, depth, rng)\n",
        "\n",
        "# Build the full stabilizer group, then sample from it uniformly at random\n",
        "circ_no_meas = circuit.remove_final_measurements(inplace=False)\n",
        "stabilizer_group = PauliList([Pauli(\"I\" * num_qubits)])\n",
        "for generator in (\n",
        "    Pauli(label) for label in Clifford(circ_no_meas).to_labels(mode=\"S\")\n",
        "):\n",
        "    stabilizer_group = stabilizer_group + stabilizer_group.compose(generator)\n",
        "\n",
        "keep = np.where(\n",
        "    stabilizer_group.x.any(axis=1) | stabilizer_group.z.any(axis=1)\n",
        ")[0]\n",
        "chosen = np.random.default_rng(seed).choice(\n",
        "    keep, size=min(num_stabilizers, len(keep)), replace=False\n",
        ")\n",
        "stabilizers = [stabilizer_group[int(i)] for i in chosen]\n",
        "\n",
        "two_qubit_depth = circuit.depth(lambda x: x.operation.num_qubits == 2)\n",
        "print(\n",
        "    f\"Sampled {len(stabilizers)} stabilizers of a {circuit.num_qubits}-qubit \"\n",
        "    f\"circuit with two-qubit depth {two_qubit_depth}: \"\n",
        "    f\"{{{stabilizers[0]}, {stabilizers[1]}, ...}}\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-findchecks-md",
      "metadata": {},
      "source": [
        "표본으로 추출된 각 안정화기에 대해, 해당 안정화기가 계산 기저에서 측정되도록 회로를 회전시키고, 이를 백엔드로 변환한 다음, 적절한 검증 세트를 찾아야 한다. 각 안정화 장치에 대해 표적과 보조 장치 쌍을 함께 섞어, 각 표적이 자신의 보조 장치를 유지하도록 합니다. 체크는 대상 큐비트가 지정된 순서대로 순차적으로 확정되며, 일단 확정된 체크는 이후 체크가 추가되어도 변경되지 않는다는 점을 기억하십시오.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "step3-findchecks",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 20/20 [00:15<00:00,  1.29it/s]"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Added 7 checks to 20 circuits in 15s.\n",
            "On average, two-qubit depth increased from 24 to 33 when adding 7 checks.\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "\n"
          ]
        }
      ],
      "source": [
        "noisy_circuits = []\n",
        "checked_circuits = []\n",
        "depths_2q = []\n",
        "t0 = time.time()\n",
        "for i, pauli in enumerate(tqdm(stabilizers)):\n",
        "    noisy_circuits.append(pm.run(append_basis_rotation(circuit, pauli)))\n",
        "    # Shuffle target and ancilla pairs together so each target keeps its ancilla\n",
        "    targets, ancillas = zip(\n",
        "        *random.sample(\n",
        "            list(zip(target_qubits, ancilla_qubits, strict=True)),\n",
        "            k=len(target_qubits),\n",
        "        ),\n",
        "        strict=True,\n",
        "    )\n",
        "    checked_circuits.append(\n",
        "        add_pauli_checks(\n",
        "            noisy_circuits[-1],\n",
        "            list(targets),\n",
        "            noise_model,\n",
        "            ancilla_qubits=list(ancillas),\n",
        "            cost=\"gamma\",\n",
        "            method=\"windowed\",\n",
        "            seed=seed + 1 + i,\n",
        "        )\n",
        "    )\n",
        "    depths_2q.append(\n",
        "        checked_circuits[-1][-1].circuit.depth(lambda x: len(x.qubits) == 2)\n",
        "    )\n",
        "\n",
        "print(\n",
        "    f\"Added {num_checks} checks to {len(stabilizers)} circuits \"\n",
        "    f\"in {(time.time() - t0):.0f}s.\"\n",
        ")\n",
        "print(\n",
        "    f\"On average, two-qubit depth increased from \"\n",
        "    f\"{circuit.depth(lambda x: len(x.qubits) == 2)} to {int(np.mean(depths_2q))} \"\n",
        "    f\"when adding {num_checks} checks.\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step3-sample-md",
      "metadata": {},
      "source": [
        "Qiskit Aer를 사용하여 기본 페이로드와 검증된 회로를 테스트해 보세요. 이 시뮬레이터는 채점 시 사용된 것과 동일한 탈분극 모델을 사용하므로, 채점 대상이 되는 잡음은 시뮬레이터가 적용하는 잡음과 동일합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "step3-sample",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 20/20 [00:17<00:00,  1.13it/s]\n"
          ]
        }
      ],
      "source": [
        "aer_nm = AerNoiseModel()\n",
        "aer_nm.add_all_qubit_quantum_error(\n",
        "    depolarizing_error(noise_model.gate_noise, 2), [\"cz\"]\n",
        ")\n",
        "p = noise_model.readout_noise\n",
        "aer_nm.add_all_qubit_readout_error(ReadoutError([[1 - p, p], [p, 1 - p]]))\n",
        "noisy_sim = AerSimulator(method=\"stabilizer\", noise_model=aer_nm)\n",
        "\n",
        "counts = []\n",
        "for i, checked_circ_result in enumerate(tqdm(checked_circuits)):\n",
        "    noisy_counts = (\n",
        "        noisy_sim.run(\n",
        "            noisy_circuits[i], shots=num_shots, seed_simulator=seed * i + 1\n",
        "        )\n",
        "        .result()\n",
        "        .get_counts()\n",
        "    )\n",
        "    checked_counts_per_variant = []\n",
        "    for k, ck in enumerate(checked_circ_result):\n",
        "        variant_counts = (\n",
        "            noisy_sim.run(\n",
        "                ck.circuit, shots=num_shots, seed_simulator=seed * i + 2 + k\n",
        "            )\n",
        "            .result()\n",
        "            .get_counts()\n",
        "        )\n",
        "        checked_counts_per_variant.append(variant_counts)\n",
        "    counts.append((noisy_counts, checked_counts_per_variant))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-header",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### 4단계: 후처리를 수행하고 원하는 기존 형식으로 결과를 반환합니다\n",
        "\n",
        "각 검사에서는 하나의 보조 시스템과 하나의 대상 시스템 사이에 얽힘 게이트를 사용합니다. 이 보조 함수는 $|0\\rangle$ 에서 시작하므로, $Z_\\text{anc}$ 는 해당 입력값을 안정화합니다. $Z_\\text{anc}$ 를 검사 회로를 통해 전방으로 전파하면, 출력에서 파울리 연산자가 생성되며, 이 연산자의 비항등원 항들이 검사의 지지 집합을 정의한다. 체크의 지지부에 포함된 비트들의 패리티가 짝수일 때, 해당 체크는 통과로 간주됩니다. 모든 검사에 합격한 경우에만 해당 샘플이 보관됩니다.\n",
        "\n",
        "각 `get_postselection_method` 의 는 측정된 비트열을 증후 벡터로 매핑하는 함수를 반환합니다 `CheckedCircuit` . 모든 검사에서 증후군 값이 0인 표본만 남기고, 나머지는 제외하십시오. 다음 차트에서 볼 수 있듯이, 검수 횟수를 늘리면 사후 선정률이 낮아집니다. 포스트선택률이 낮을수록 목표 정확도에 도달하기 위해 더 많은 샷이 필요하므로, 탐지 능력과 샘플링 비용 사이에는 상충 관계가 존재한다. 이 비율은 수렴하는 것으로 보이며, 이는 추가적인 검사가 탐지 능력 향상에 기여하는 정도가 줄어든다는 것을 시사합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "step4-psr",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-psr-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "rate_per_variant = []\n",
        "kept_per_stab = []\n",
        "for i, (_, checked_counts_per_variant) in enumerate(counts):\n",
        "    rates = []\n",
        "    kept_at_num_checks = None\n",
        "    for k, variant_counts in enumerate(checked_counts_per_variant):\n",
        "        ps_fn = checked_circuits[i][k].get_postselection_method()\n",
        "        kept = {\n",
        "            bs: n for bs, n in variant_counts.items() if not ps_fn(bs).any()\n",
        "        }\n",
        "        rates.append(sum(kept.values()) / num_shots)\n",
        "        if k == num_checks:\n",
        "            kept_at_num_checks = kept\n",
        "    rate_per_variant.append(rates)\n",
        "    kept_per_stab.append(kept_at_num_checks)\n",
        "\n",
        "max_len = max(len(s) for s in rate_per_variant)\n",
        "rates_arr = np.full((len(rate_per_variant), max_len), np.nan)\n",
        "for i, s in enumerate(rate_per_variant):\n",
        "    rates_arr[i, : len(s)] = s\n",
        "ks = np.arange(max_len)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.plot(ks, rates_arr.T, color=\"#ff8c00\", alpha=0.15, linewidth=1)\n",
        "ax.plot(\n",
        "    ks,\n",
        "    np.nanmedian(rates_arr, axis=0),\n",
        "    color=\"black\",\n",
        "    linewidth=1,\n",
        "    linestyle=\"--\",\n",
        "    label=\"median\",\n",
        ")\n",
        "ax.set_xlabel(\"Checks committed\")\n",
        "ax.set_ylabel(\"Postselection rate\")\n",
        "ax.set_ylim((0, 1.05))\n",
        "ax.set_title(\n",
        "    f\"Per-stabilizer postselection rate ({len(rates_arr)} stabilizers)\"\n",
        ")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-fidelity-md",
      "metadata": {},
      "source": [
        "이제 잡음이 섞인 순수 상태와 사후선택된 상태의 충실도를 비교해 보자. 오류가 감지되지 않은 샘플만을 사후 선별하면 모든 안정화기의 기대값이 높아지며, 결과적으로 추정된 충실도도 높아진다. 사후 선택된 값은 원시 값보다 더 적은 표본을 사용하지만, 기대값은 더 정확하고 표본 분산은 더 낮습니다. 또한, 선택 후 평균 비율이 잡음이 섞인 충실도에 근접한다는 점에도 주목하십시오. 검사가 오류가 있는 샘플을 거의 모두 탐지할 때 예상되는 현상은 바로 이것입니다. 모든 검사를 통과하는 샘플의 비율이 오류가 없는 샘플의 비율에 가까워지는데, 이것이 바로 잡음이 있는 상태의 충실도입니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "step4-fidelity",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ideal fidelity:          1.0\n",
            "noisy fidelity:          0.7899\n",
            "postselected fidelity:   0.9679\n",
            "mean postselection rate: 0.780\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-fidelity-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-fidelity-2.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "results = []\n",
        "for i, ((noisy_counts, _), kept) in enumerate(\n",
        "    zip(counts, kept_per_stab, strict=True)\n",
        "):\n",
        "    results.append(\n",
        "        (\n",
        "            expectation(noisy_counts, stabilizers[i]),\n",
        "            expectation(kept, stabilizers[i]),\n",
        "            sum(kept.values()) / num_shots,\n",
        "        )\n",
        "    )\n",
        "\n",
        "fidelity_noisy = float(np.nanmean([r[0] for r in results]))\n",
        "fidelity_postsel = float(np.nanmean([r[1] for r in results]))\n",
        "psr = float(np.mean([r[2] for r in results]))\n",
        "print(\n",
        "    f\"ideal fidelity:          1.0\\n\"\n",
        "    f\"noisy fidelity:          {fidelity_noisy:.4f}\\n\"\n",
        "    f\"postselected fidelity:   {fidelity_postsel:.4f}\\n\"\n",
        "    f\"mean postselection rate: {psr:.3f}\"\n",
        ")\n",
        "\n",
        "evs_ideal = np.ones(len(results))\n",
        "evs_noisy = np.array([r[0] for r in results])\n",
        "evs_post = np.array([r[1] for r in results])\n",
        "idx = np.arange(len(results))\n",
        "\n",
        "\n",
        "def strip(ax, ys, color, label):\n",
        "    m, s = np.nanmean(ys), np.nanstd(ys)\n",
        "    ax.axhspan(\n",
        "        m - s, m + s, color=color, alpha=0.15, label=f\"{label} mean and std\"\n",
        "    )\n",
        "    ax.axhline(\n",
        "        m, color=color, linewidth=1, linestyle=\"--\", label=f\"{label} fidelity\"\n",
        "    )\n",
        "\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.axhline(np.nanmean(evs_ideal), color=\"black\", linewidth=1.5, label=\"ideal\")\n",
        "strip(ax, evs_noisy, \"red\", \"noisy\")\n",
        "strip(ax, evs_post, \"green\", \"postselected\")\n",
        "ax.scatter(idx, evs_noisy, color=\"red\", s=22, alpha=0.7, label=\"noisy EVs\")\n",
        "ax.scatter(\n",
        "    idx,\n",
        "    evs_post,\n",
        "    color=\"green\",\n",
        "    s=22,\n",
        "    alpha=0.7,\n",
        "    label=\"postselected EVs\",\n",
        ")\n",
        "ax.set_xlabel(\"stabilizer index\")\n",
        "ax.set_ylabel(r\"$\\langle G \\rangle$\")\n",
        "ax.set_ylim((-0.1, 1.1))\n",
        "ax.set_title(\"Per-stabilizer expectation values\")\n",
        "ax.legend(loc=\"lower left\")\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()\n",
        "\n",
        "M = np.arange(1, len(results) + 1)\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "for ys, color, label in [\n",
        "    (evs_ideal, \"black\", \"ideal\"),\n",
        "    (evs_noisy, \"red\", \"noisy\"),\n",
        "    (evs_post, \"green\", \"postselected\"),\n",
        "]:\n",
        "    cm, sem = cum_mean_sem(ys)\n",
        "    ax.plot(M, cm, color=color, linewidth=1.5, label=label)\n",
        "    ax.fill_between(M, cm - sem, cm + sem, color=color, alpha=0.15)\n",
        "ax.set_xlabel(\"number of stabilizers averaged\")\n",
        "ax.set_ylabel(\"running fidelity estimate\")\n",
        "ax.set_title(\"Fidelity convergence versus number of stabilizers\")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "step4-gamma-md",
      "metadata": {},
      "source": [
        "감마 점수는 모델링된 노이즈 채널 중 검증을 통해 탐지되지 않은 부분이 어느 정도인지 나타냅니다. 감마 점수를 완료된 검사 건수에 따라 그래프로 나타내면, 검사가 하나씩 추가될 때마다 탐지 능력이 어떻게 향상되는지 알 수 있습니다. 값이 인 경우, 이 검사가 모델링된 잡음을 모두 포착함을 의미합니다 `1.0` . 체크가 더 많이 커밋될수록 `1.0` 곡선은 쪽으로 내려가는데, 이는 추가되는 각 체크가 남아 있는 미탐지 오류의 일부를 포착함을 보여줍니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "step4-gamma",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/step4-gamma-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "stab_scores = [\n",
        "    [variant.cost for variant in checked_circ_result]\n",
        "    for checked_circ_result in checked_circuits\n",
        "]\n",
        "max_len = max(len(s) for s in stab_scores)\n",
        "scores = np.full((len(stab_scores), max_len), np.nan)\n",
        "for i, s in enumerate(stab_scores):\n",
        "    scores[i, : len(s)] = s\n",
        "ks = np.arange(max_len)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 4))\n",
        "ax.plot(ks, scores.T, color=\"#4682b4\", alpha=0.15, linewidth=1)\n",
        "ax.plot(\n",
        "    ks,\n",
        "    np.nanmedian(scores, axis=0),\n",
        "    color=\"black\",\n",
        "    linewidth=1,\n",
        "    linestyle=\"--\",\n",
        "    label=\"median\",\n",
        ")\n",
        "ax.set_xlabel(\"Checks committed\")\n",
        "ax.set_ylabel(\"Gamma\")\n",
        "ax.set_yscale(\"log\")\n",
        "ax.set_title(f\"Per-stabilizer gamma curves ({len(scores)} stabilizers)\")\n",
        "ax.legend()\n",
        "ax.grid(True, alpha=0.3, which=\"both\")\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-header",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## 대규모 하드웨어 예시\n",
        "\n",
        "더 크고 용량이 더 큰 페이로드를 갖춘 하드웨어에서도 동일한 워크플로가 실행됩니다. 이 섹션에서는 시뮬레이터 예제의 백엔드를 재사용하되, 고유한 타깃 및 보조 시스템 쌍과 패스 관리자를 갖춘 새로운 20-큐비트 레이아웃을 구축한 다음, 해당 회로들을 단일 작업으로 QPU에 제출합니다. 이 정도 규모에서는 대부분의 샷이 적어도 한 번의 검사를 유발하므로, 샷 후 선별률이 낮고, 각 회로가 생존하기 위해 충분한 샘플을 확보하려면 많은 샷 예산이 필요합니다. 따라서 이 예시는 예산을 소수의 표본 안정기에 집중하고 있습니다. 이는 여전히 편향되지 않은 충실도 추정치이지만, 시뮬레이터 예시에서 여러 안정기에 대한 평균을 산출한 것보다 정밀도가 낮습니다.\n",
        "\n",
        "시뮬레이터 예제와 비교해 한 가지 달라진 점이 있습니다. 바로 보정 데이터로부터 균일한 탈분극 채널을 추론하는 대신, 이 절에서는 를 사용하여 노이즈 모델을 \\*\\*학습하고 \\*\\*`NoiseLearner` , 그 결과를 바탕으로 를 사용하여 `qiskit-paulice` 모델을 구축합니다 `NoiseModel.from_pauli_lindblad_maps`. 학습된 파울리-린드블라드 모델은 모든 에지가 동일한 수준의 노이즈를 가진다고 가정하기보다는, 이 특정 레이아웃에서 나타나는 노이즈의 공간적 구조를 반영하므로, 체크 배치에 대한 평가는 QPU에 영향을 미치는 노이즈와 더 유사한 노이즈를 기준으로 이루어집니다. 노이즈를 학습하려면 QPU 샘플링이 필요하며, 이는 전체 QPU 샘플링 할당량에 반드시 반영되어야 합니다.\n",
        "\n",
        "다음 매개변수들은 큐비트 수, 실행 횟수, 안정화 연산자 수, 그리고 실행 횟수를 설정합니다. 포스트셀렉션 비율의 역수에 따라 `hw_num_shots` 규모가 결정됩니다. 3%의 비율일 경우, 40,000회의 촬영으로 회로당 약 1,200개의 포스트셀렉션 샘플이 남게 됩니다. 이 값을 높이면 작업당 필요한 회로 수가 늘어나는 대가로, 더 정밀한 충실도 추정치를 얻을 `hw_num_stabilizers` 수 있습니다. 각 회로에는 동일한 샷 예산이 필요합니다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-steps-header",
      "metadata": {},
      "source": [
        "<span id=\"steps-1-4-compressed-into-a-single-code-block\" />\n",
        "\n",
        "### 1\\~4단계 (단일 코드 블록으로 통합됨)\n",
        "\n",
        "다음 셀은 시뮬레이터 예제와 동일한 네 단계를 실행합니다. 더 큰 페이로드를 구축하고 몇 가지 안정화 회로를 샘플링합니다(1단계); 레이아웃을 선택하고, 해당 레이아웃에 대한 노이즈 모델을 학습한 뒤, 각 안정화 회로에 대해 완전히 검증된 회로를 찾아냅니다(2단계); 기본 회로와 검증된 회로를 모두 포함하는 하나의 Sampler 작업을 제출합니다(3단계); 그리고 안정화 회로별로 및 평균적으로, 노이즈가 포함된 추정값과 사후 선별된 추정값을 비교하기 위해 검증된 카운트를 사후 선별합니다(4단계). 이 정도 규모에서는 시뮬레이터 예시와 같이 전체 안정화자 군을 모두 열거하는 것이 불가능하므로, 셀은 안정화자 중 무작위로 선택된 부분 집합을 표본 추출하여 충실도 추정치를 계산합니다.\n",
        "\n",
        "여기서 2단계는 시뮬레이터 예제보다 더 많은 작업을 수행한다는 점에 유의하십시오. 노이즈 모델 학습 작업은 샘플러 작업보다 먼저 자체 `NoiseLearner` 작업을 제출하므로, 이 셀에서는 총 **두** 개의 작업이 실행됩니다. 나중에 쉽게 찾을 수 `TUT_ASPC` 있도록 와 `TUT_ASPC_LEARN` 태그가 달려 있습니다. 구인 공고에 [태그를](/docs/guides/add-job-tags) 지정하는 방법에 대한 자세한 내용은 ‘구인 공고 태그로 정리 및 검색하기’를 참조하세요.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "hardware-submit",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Layout with 11 check pairs: [153, 152, 151, 138, 131, 130, 129, 118, 109, 110, 111, 98, 91, 90, 89, 78, 69, 70, 71, 58]\n",
            "Submitted noise-learner job d9f4mncjeosc73fjfmkg\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Learned 4 layers; readout noise 0.00470\n"
          ]
        },
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "100%|██████████| 10/10 [01:01<00:00,  6.18s/it]\n"
          ]
        },
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Submitted job d9f4s04jeosc73fjftkg with 20 circuits\n",
            "noisy fidelity estimate:        0.3685\n",
            "postselected fidelity estimate: 0.6869\n",
            "mean postselection rate:        0.2851 (~11404 kept shots per circuit)\n",
            "\n",
            "per-stabilizer results:\n",
            "idx     noisy   postsel      psr  kept shots\n",
            "  0    0.3769    0.6918   0.3247       12987\n",
            "  1    0.3745    0.6760   0.2999       11995\n",
            "  2    0.3659    0.6389   0.3549       14196\n",
            "  3    0.3821    0.7060   0.2660       10641\n",
            "  4    0.3653    0.7475   0.2531       10124\n",
            "  5    0.3752    0.7022   0.2698       10791\n",
            "  6    0.3508    0.7144   0.2711       10842\n",
            "  7    0.3485    0.7087   0.2381        9523\n",
            "  8    0.3825    0.6289   0.2928       11711\n",
            "  9    0.3630    0.6549   0.2808       11232\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/spacetime-codes/extracted-outputs/hardware-submit-5.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# -------------------------Step 1: build a larger payload and sample stabilizers-------------------------\n",
        "hw_num_qubits = 20\n",
        "hw_depth = 36\n",
        "hw_num_stabilizers = 10\n",
        "hw_num_shots = 40_000\n",
        "\n",
        "hw_circuit = random_clifford_circuit(hw_num_qubits, hw_depth, rng)\n",
        "hw_no_meas = hw_circuit.remove_final_measurements(inplace=False)\n",
        "\n",
        "# Enumerating all 2^n stabilizers is infeasible at this size, so draw each\n",
        "# stabilizer by composing a random subset of the group generators\n",
        "hw_generators = [\n",
        "    Pauli(label) for label in Clifford(hw_no_meas).to_labels(mode=\"S\")\n",
        "]\n",
        "sample_rng = np.random.default_rng(seed)\n",
        "hw_stabilizers = []\n",
        "while len(hw_stabilizers) < hw_num_stabilizers:\n",
        "    mask = sample_rng.integers(0, 2, hw_num_qubits).astype(bool)\n",
        "    if not mask.any():\n",
        "        continue  # skip the identity\n",
        "    stabilizer = Pauli(\"I\" * hw_num_qubits)\n",
        "    for generator, chosen in zip(hw_generators, mask, strict=True):\n",
        "        if chosen:\n",
        "            stabilizer = stabilizer.compose(generator)\n",
        "    hw_stabilizers.append(stabilizer)\n",
        "\n",
        "# -------------------------Step 2: find a 20-qubit layout, learn its noise, and add checks-------------------------\n",
        "# A single bad coupler or bad-readout qubit on the path drags every\n",
        "# stabilizer down, so search harder and with tighter error thresholds\n",
        "hw_layout = find_check_layout(\n",
        "    backend,\n",
        "    hw_num_qubits,\n",
        "    rng,\n",
        "    num_trials=500,\n",
        "    max_gate_error=0.015,\n",
        "    max_readout_error=0.05,\n",
        ")\n",
        "hw_target_qubits, hw_ancilla_qubits = get_check_qubits(backend, hw_layout)\n",
        "hw_pm = generate_preset_pass_manager(\n",
        "    optimization_level=0, backend=backend, initial_layout=hw_layout\n",
        ")\n",
        "print(f\"Layout with {len(hw_target_qubits)} check pairs: {hw_layout}\")\n",
        "\n",
        "# ----- learn a Pauli-Lindblad noise model on this layout -----\n",
        "# The simulator example scored checks against a uniform depolarizing channel\n",
        "# inferred from calibration data. Here, learn the noise instead: NoiseLearner\n",
        "# runs its own job on the QPU and returns a Pauli-Lindblad channel per unique\n",
        "# entangling layer, so the checks are placed against the noise this layout\n",
        "# actually has, including its spatial structure. All the sampled stabilizers\n",
        "# share the same entangling layers and differ only in their final basis\n",
        "# rotation, so learning on the bare payload covers all of them.\n",
        "learner = NoiseLearner(\n",
        "    mode=backend,\n",
        "    options={\n",
        "        \"max_layers_to_learn\": 4,\n",
        "        \"num_randomizations\": 32,\n",
        "        \"shots_per_randomization\": 128,\n",
        "        \"environment\": {\"job_tags\": [\"TUT_ASPC_LEARN\"]},\n",
        "    },\n",
        ")\n",
        "learner_job = learner.run([hw_pm.run(hw_circuit)])\n",
        "print(f\"Submitted noise-learner job {learner_job.job_id()}\")\n",
        "hw_layer_errors = learner_job.result().data\n",
        "\n",
        "# To see how much the learned model helps, swap the next line for the\n",
        "# simulator example's uniform model - a one-line change:\n",
        "# hw_noise_model = NoiseModel.from_backend(backend, hw_layout, uniform_gate_noise=True)\n",
        "hw_noise_model = learned_noise_model(hw_layer_errors, hw_layout)\n",
        "# NoiseLearner characterizes gate noise only, so keep the readout estimate\n",
        "# from calibration data rather than leaving it unset\n",
        "hw_noise_model.readout_noise = NoiseModel.from_backend(\n",
        "    backend, hw_layout, uniform_gate_noise=True\n",
        ").readout_noise\n",
        "print(\n",
        "    f\"Learned {len(hw_layer_errors)} layers; \"\n",
        "    f\"readout noise {hw_noise_model.readout_noise:.5f}\"\n",
        ")\n",
        "\n",
        "# ----- add the fully checked circuit per stabilizer -----\n",
        "hw_noisy_circuits = []\n",
        "hw_checked_circuits = []\n",
        "for i, pauli in enumerate(tqdm(hw_stabilizers)):\n",
        "    bare = hw_pm.run(append_basis_rotation(hw_circuit, pauli))\n",
        "    hw_noisy_circuits.append(bare)\n",
        "    variants = add_pauli_checks(\n",
        "        bare,\n",
        "        hw_target_qubits,\n",
        "        hw_noise_model,\n",
        "        ancilla_qubits=hw_ancilla_qubits,\n",
        "        cost=\"gamma\",\n",
        "        method=\"windowed\",\n",
        "        seed=seed + 1 + i,\n",
        "    )\n",
        "    hw_checked_circuits.append(variants[-1])  # keep the fully checked circuit\n",
        "\n",
        "# -------------------------Step 3: submit one Sampler job with the bare and checked circuits-------------------------\n",
        "sampler = Sampler(mode=backend)\n",
        "sampler.options.default_shots = hw_num_shots\n",
        "sampler.options.environment.job_tags = [\"TUT_ASPC\"]\n",
        "\n",
        "pubs = hw_noisy_circuits + [cc.circuit for cc in hw_checked_circuits]\n",
        "job = sampler.run(pubs)\n",
        "print(f\"Submitted job {job.job_id()} with {len(pubs)} circuits\")\n",
        "\n",
        "# -------------------------Step 4: postselect and compare fidelity-------------------------\n",
        "result = job.result()\n",
        "n_stab = len(hw_stabilizers)\n",
        "\n",
        "hw_results = []\n",
        "for i in range(n_stab):\n",
        "    noisy_counts = result[i].join_data().get_counts()\n",
        "    checked_counts = result[n_stab + i].join_data().get_counts()\n",
        "    ps_fn = hw_checked_circuits[i].get_postselection_method()\n",
        "    kept = {bs: c for bs, c in checked_counts.items() if not ps_fn(bs).any()}\n",
        "    hw_results.append(\n",
        "        (\n",
        "            expectation(noisy_counts, hw_stabilizers[i]),\n",
        "            expectation(kept, hw_stabilizers[i]),\n",
        "            sum(kept.values()) / sum(checked_counts.values()),\n",
        "        )\n",
        "    )\n",
        "\n",
        "hw_fidelity_noisy = float(np.nanmean([r[0] for r in hw_results]))\n",
        "hw_fidelity_postsel = float(np.nanmean([r[1] for r in hw_results]))\n",
        "hw_psr = float(np.mean([r[2] for r in hw_results]))\n",
        "print(\n",
        "    f\"noisy fidelity estimate:        {hw_fidelity_noisy:.4f}\\n\"\n",
        "    f\"postselected fidelity estimate: {hw_fidelity_postsel:.4f}\\n\"\n",
        "    f\"mean postselection rate:        {hw_psr:.4f} \"\n",
        "    f\"(~{int(round(hw_psr * hw_num_shots))} kept shots per circuit)\"\n",
        ")\n",
        "\n",
        "# Per-stabilizer breakdown. The postselection rate varies from stabilizer to\n",
        "# stabilizer, so a stabilizer whose postselected value barely moves is usually\n",
        "# one whose checks rejected little; the kept-shot count says how much of the\n",
        "# gap is statistics rather than signal.\n",
        "print(\"\\nper-stabilizer results:\")\n",
        "print(\n",
        "    f\"{'idx':>3}  {'noisy':>8}  {'postsel':>8}  {'psr':>7}  {'kept shots':>10}\"\n",
        ")\n",
        "for i, (noisy, post, psr_i) in enumerate(hw_results):\n",
        "    print(\n",
        "        f\"{i:>3}  {noisy:>8.4f}  {post:>8.4f}  {psr_i:>7.4f}  \"\n",
        "        f\"{int(round(psr_i * hw_num_shots)):>10}\"\n",
        "    )\n",
        "\n",
        "hw_noisy = np.array([r[0] for r in hw_results])\n",
        "hw_post = np.array([r[1] for r in hw_results])\n",
        "idx = np.arange(n_stab)\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(9, 4))\n",
        "ax.axhline(1.0, color=\"black\", linewidth=1.5, label=\"ideal\")\n",
        "strip(ax, hw_noisy, \"red\", \"noisy\")\n",
        "strip(ax, hw_post, \"green\", \"postselected\")\n",
        "ax.scatter(idx, hw_noisy, color=\"red\", s=22, alpha=0.7, label=\"noisy EVs\")\n",
        "ax.scatter(\n",
        "    idx,\n",
        "    hw_post,\n",
        "    color=\"green\",\n",
        "    s=22,\n",
        "    alpha=0.7,\n",
        "    label=\"postselected EVs\",\n",
        ")\n",
        "ax.set_xlabel(\"stabilizer index\")\n",
        "ax.set_ylabel(r\"$\\langle G \\rangle$\")\n",
        "ax.set_ylim((-0.1, 1.1))\n",
        "ax.set_xticks(idx)\n",
        "ax.set_title(\"Per-stabilizer expectation values on hardware\")\n",
        "# Outside the axes so it cannot hide a data point\n",
        "ax.legend(loc=\"center left\", bbox_to_anchor=(1.02, 0.5), frameon=False)\n",
        "ax.grid(True, alpha=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hardware-results-md",
      "metadata": {},
      "source": [
        "이 정도 규모의 회로의 경우, 대부분의 샘플에서 적어도 하나의 오류가 감지되므로, 사후 선별 비율은 낮고 사후 선별 과정에서 대부분의 샷이 제외됩니다. 모든 검증을 통과한 샘플들은 기본 회로보다 훨씬 더 나은 기대값을 보여주며, 각 안정화기별 값들은 잡음이 섞인 기준선과 명확하게 구분됩니다. 정확도 추정치를 더 정밀하게 하려면, 회로당 샷 할당량을 동일하게 유지한 채 더 많은 안정화기를 표본으로 추출하십시오. 사후 선별률을 높이려면 회로 깊이를 줄이거나 체크 수를 줄여야 하며, 더 큰 페이로드를 처리하려면 사후 선별률의 역수에 비례하여 샷 예산을 늘려야 한다.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "next-steps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## 다음 단계\n",
        "\n",
        "<Admonition type=\"tip\" title=\"권장사항\">\n",
        "  이 글이 흥미로웠다면, 다음 자료도 참고해 보시기 바랍니다:\n",
        "\n",
        "  * 양자 오류 정정 입문을 위한 [반복 코드](/docs/tutorials/repetition-codes) 튜토리얼.\n",
        "  * 전체 check-finding API에 대한 [문서와 `qiskit-paulice`](/docs/addons/qiskit-addon-paulice) , 소스 코드가 포함된 패키지의 [GitHub 저장소](https://github.com/Qiskit/qiskit-paulice).\n",
        "  * 검증의 이론적 근거에 관한 논문 “[시공간 코드를 이용한 낮은 오버헤드의 오류 검출](https://arxiv.org/abs/2504.15725) ”.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "references",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## 참조\n",
        "\n",
        "* \\[1] Martiel, S., & Javadi-Abhari, A. (2025). 시공간 코드를 이용한 낮은 오버헤드의 오류 검출. *arXiv 사전 출판본* [arXiv:2504.15725](https://arxiv.org/abs/2504.15725).\n",
        "* \\[2] 반 덴 베르그, E., Bravyi, S., Gambetta, J. M., Jurcevic, P., Maslov, D., & Temme, K. (2023). 코히어런트 파울리 검사를 통한 단일 샷 오류 완화. *Physical Review Research*, 5(3), 033193.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    },
    "hours": 2.5,
    "qpuSeconds": 10
  },
  "nbformat": 4,
  "nbformat_minor": 5
}