{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Quickstart\"\n",
        "description: \"Quickstart for the latest version of Qiskit Paulice\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "intro",
      "metadata": {},
      "source": [
        "# Quickstart\n",
        "\n",
        "This guide demonstrates a minimal working example of the `qiskit-paulice` package. We use [spacetime Pauli checks](https://arxiv.org/abs/2504.15725) to detect errors during execution of a Clifford circuit, then postselect only samples for which no error was detected. For a more explanatory end-to-end workflow that quantifies the fidelity improvement, see the [in-depth tutorial](/docs/addons/qiskit-addon-paulice/guides/low-overhead-error-detection-using-spacetime-codes).\n",
        "\n",
        "**Workflow steps**\n",
        "\n",
        "1. Prepare the inputs: a Clifford payload circuit, the target/ancilla qubits to host checks, and a noise model.\n",
        "2. Find good spacetime Pauli checks and add them to the circuit.\n",
        "3. Sample the checked circuit.\n",
        "4. Postselect only the samples for which no error was detected.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "build-circuit",
      "metadata": {},
      "source": [
        "## 1. Prepare the inputs\n",
        "\n",
        "`add_pauli_checks` takes a Clifford **circuit** with at least one terminal measurement, the **target qubits** that will be used to implement checks, and a **noise model** used to score candidate checks. Here we build a shallow random Clifford circuit, lay it out on a 1D chain of physical qubits on `ibm_boston`, pair each payload qubit with a neighboring ancilla (so a check needs no SWAP gates), and infer a rough depolarizing noise model from backend benchmark data.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "make-circuit",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-paulice/guides/quickstart/extracted-outputs/make-circuit-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 1,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import numpy as np\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_paulice.layout import get_check_qubits\n",
        "from qiskit_paulice.noise_models import NoiseModel\n",
        "\n",
        "# Backend and a 1D chain of physical qubits to run on\n",
        "backend = QiskitRuntimeService().backend(\"ibm_boston\")\n",
        "layout = [68, 69, 78, 89, 90, 91, 98, 111, 112, 113, 119, 133]\n",
        "\n",
        "# A shallow brickwork random Clifford payload circuit\n",
        "rng = np.random.default_rng(1764)\n",
        "circuit = QuantumCircuit(len(layout))\n",
        "circuit.h(range(circuit.num_qubits))\n",
        "for d in range(4):\n",
        "    for i in range(d % 2, circuit.num_qubits - 1, 2):\n",
        "        circuit.cz(i, i + 1)\n",
        "    for q in range(circuit.num_qubits):\n",
        "        if rng.integers(0, 2):\n",
        "            circuit.sx(q)\n",
        "        if rng.integers(0, 2):\n",
        "            circuit.s(q)\n",
        "circuit.measure_all()\n",
        "\n",
        "# Pair each payload qubit with a neighboring ancilla to host a check (no SWAPs needed)\n",
        "target_qubits, ancilla_qubits = get_check_qubits(backend.coupling_map, layout)\n",
        "\n",
        "# A rough depolarizing noise model from backend benchmark data, used to score checks\n",
        "noise_model = NoiseModel.from_backend(\n",
        "    backend, layout, uniform_gate_noise=True\n",
        ")\n",
        "\n",
        "circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "add-checks-md",
      "metadata": {},
      "source": [
        "## 2. Find and add spacetime Pauli checks\n",
        "\n",
        "`add_pauli_checks` searches for effective, low-weight checks on each target qubit and adds them to the circuit, returning a sequence of `CheckedCircuit` instances. The `CheckedCircuit` instances contain increasing numbers of checks, from `0` checks up to one check per target qubit. Each check improves error detection capability at the cost of a slightly increased circuit depth. For this example we will use the circuit with a check on every target qubit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "add-checks",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-paulice/guides/quickstart/extracted-outputs/add-checks-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_paulice import add_pauli_checks\n",
        "\n",
        "# The circuit has virtual qubits, so we specify our target qubits with virtual indices\n",
        "target_qubits_v = [layout.index(q) for q in target_qubits]\n",
        "\n",
        "# Add spacetime Pauli checks\n",
        "checked_circuit = add_pauli_checks(circuit, target_qubits_v, noise_model)[-1]\n",
        "checked_circuit.circuit.draw(\"mpl\", fold=-1, scale=0.4, idle_wires=False)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "postselect-md",
      "metadata": {},
      "source": [
        "## 3. Sample the checked circuit\n",
        "\n",
        "Before we sample, we transpile the checked circuit onto physical qubits (`initial_layout = layout + ancilla_qubits`) and into `ibm_boston`'s native basis gate set. To emulate a noisy QPU, we sample with a noisy Aer stabilizer backend whose error rates match the `noise_model` used to pick checks.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "sample",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "sampled 1000 shots\n"
          ]
        }
      ],
      "source": [
        "from qiskit import transpile\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",
        "# Transpile once: lay the checked circuit out on our qubits and into the native basis\n",
        "isa_circuit = transpile(\n",
        "    checked_circuit.circuit,\n",
        "    backend,\n",
        "    initial_layout=layout + ancilla_qubits,\n",
        "    optimization_level=0,\n",
        ")\n",
        "\n",
        "# Build an Aer noise model matching the depolarizing model used to pick checks\n",
        "aer_noise = AerNoiseModel()\n",
        "aer_noise.add_all_qubit_quantum_error(\n",
        "    depolarizing_error(noise_model.gate_noise, 2), [\"cz\"]\n",
        ")\n",
        "p = noise_model.readout_noise\n",
        "aer_noise.add_all_qubit_readout_error(ReadoutError([[1 - p, p], [p, 1 - p]]))\n",
        "simulator = AerSimulator(method=\"stabilizer\", noise_model=aer_noise)\n",
        "\n",
        "counts = (\n",
        "    simulator.run(isa_circuit, shots=1000, seed_simulator=1764)\n",
        "    .result()\n",
        "    .get_counts()\n",
        ")\n",
        "print(f\"sampled {sum(counts.values())} shots\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "postselect-step-md",
      "metadata": {},
      "source": [
        "## 4. Postselect samples with no detected error\n",
        "\n",
        "`get_postselection_method` maps each shot to its syndrome vector; we keep only the shots with an all-zero syndrome — those in which no check detected an error. Discarding the flagged shots removes detected errors from the surviving distribution, improving its fidelity at the cost of a lower sampling rate. See the [tutorial](/docs/addons/qiskit-addon-paulice/guides/low-overhead-error-detection-using-spacetime-codes) for a quantitative fidelity comparison.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "postselect",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "kept 927 of 1000 shots (93%)\n"
          ]
        }
      ],
      "source": [
        "# Keep only the shots in which no check reported an error\n",
        "ps_fn = checked_circuit.get_postselection_method()\n",
        "counts_postselected = {\n",
        "    bs: n for bs, n in counts.items() if not ps_fn(bs).any()\n",
        "}\n",
        "\n",
        "kept, total = sum(counts_postselected.values()), sum(counts.values())\n",
        "print(f\"kept {kept} of {total} shots ({kept / total:.0%})\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}