{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Quickstart\"\n",
        "description: \"Quickstart for the latest version of Shaded lightcones\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "38de8ef2",
      "metadata": {},
      "source": [
        "---\n",
        "title: Quickstart\n",
        "description: A quickstart guide for the Shaded lightcones Qiskit addon (qiskit-addon-slc)\n",
        "---\n",
        "\n",
        "# Quickstart\n",
        "\n",
        "This guide demonstrates a minimal working example of the `qiskit-addon-slc` package. We calculate a shaded lightcone to reduce the sampling cost of probabilistic error cancellation (PEC).\n",
        "\n",
        "PEC mitigates gate noise by sampling from a quasiprobability decomposition of the inverse noise channel. Its sampling cost grows with every error term it must mitigate; however, not every error affects the observable equally. An error outside the observable's causal lightcone cannot influence the measured expectation value at all, and even within the lightcone some errors are more detrimental than others. A shaded lightcone quantifies this by bounding the effect each Pauli error term has on the observable. Truncating the error terms with the smallest effect shrinks the noise model that PEC has to mitigate, reducing sampling cost in exchange for a small, bounded bias.\n",
        "\n",
        "To see how to build a realistic workflow and run on quantum hardware, check out the [Probabilistic error cancellation with shaded lightcones](/docs/tutorials/pec-with-shaded-lightcones) tutorial on the IBM Quantum Platform.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "71bc0134",
      "metadata": {},
      "source": [
        "## 1. Prepare the inputs for SLC\n",
        "\n",
        "Here we build a 6-qubit Trotterized transverse-field Ising circuit and choose a single-qubit $Z$ observable on the middle qubit.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "0fff4e37",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Observable: IIZIII\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-slc/guides/quickstart/extracted-outputs/0fff4e37-1.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.quantum_info import Pauli\n",
        "\n",
        "\n",
        "def trotter_ising_circuit(num_qubits, num_steps, rx_angle, rzz_angle):\n",
        "    \"\"\"Trotterized transverse-field Ising evolution on a 1D chain.\"\"\"\n",
        "    circuit = QuantumCircuit(num_qubits)\n",
        "    for _ in range(num_steps):\n",
        "        circuit.rx(rx_angle, range(num_qubits))\n",
        "        circuit.barrier()\n",
        "        for start in (0, 1):  # even then odd bonds\n",
        "            for i in range(start, num_qubits - 1, 2):\n",
        "                circuit.rzz(rzz_angle, i, i + 1)\n",
        "        circuit.barrier()\n",
        "    return circuit\n",
        "\n",
        "\n",
        "num_qubits = 6\n",
        "circuit = trotter_ising_circuit(\n",
        "    num_qubits, num_steps=2, rx_angle=np.pi / 16, rzz_angle=-np.pi / 2\n",
        ")\n",
        "\n",
        "# Measure <Z> on the middle qubit\n",
        "observable = Pauli(\"I\" * num_qubits).compose(\"Z\", [num_qubits // 2])\n",
        "\n",
        "print(f\"Observable: {observable}\")\n",
        "circuit.draw(\"mpl\", fold=-1, scale=0.7)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "9a732427",
      "metadata": {},
      "source": [
        "SLC operates on the noisy two-qubit-gate layers of the circuit. Here we use `samplomatic` to group the gates into annotated boxes and attach a noise-injection annotation to each two-qubit layer. `generate_noise_model_paulis` then enumerates the Pauli error terms of every unique noisy layer.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "3f4301ac",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Noisy layers: 2\n",
            "Pauli error terms across all layers: 102\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_slc.utils import generate_noise_model_paulis\n",
        "from samplomatic.transpiler import generate_boxing_pass_manager\n",
        "from samplomatic.utils import find_unique_box_instructions\n",
        "\n",
        "# Group gates into boxes and annotate each two-qubit layer with a noise-injection point\n",
        "boxing_pass = generate_boxing_pass_manager(\n",
        "    inject_noise_targets=\"all\",\n",
        "    inject_noise_strategy=\"individual_modification\",\n",
        "    inject_noise_site=\"after\",\n",
        "    twirling_strategy=\"active\",\n",
        "    remove_barriers=\"never\",\n",
        ")\n",
        "boxed_circuit = boxing_pass.run(circuit)\n",
        "\n",
        "# Enumerate the 1- and 2-weight Pauli error terms of each unique noisy layer\n",
        "noise_model_paulis = generate_noise_model_paulis(\n",
        "    find_unique_box_instructions(boxed_circuit)\n",
        ")\n",
        "\n",
        "num_terms = sum(len(paulis) for paulis in noise_model_paulis.values())\n",
        "print(f\"Noisy layers: {len(noise_model_paulis)}\")\n",
        "print(f\"Pauli error terms across all layers: {num_terms}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "97101eed",
      "metadata": {},
      "source": [
        "## 2. Compute the shaded lightcone\n",
        "\n",
        "The shaded lightcone is assembled by assigning a scale to each Pauli error term in the noise model based on how much effect it has on the observable expectation value. These scales are derived from each error term's forward and backward error bounds (described below), as well as its error rate:\n",
        "\n",
        "* `compute_forward_bounds` evolves each error term *forward* to the end of the circuit to bound its effect on the observable measured there.\n",
        "* `compute_backward_bounds` evolves each error term *backward* to the start of the circuit to bound its effect on the initial state.\n",
        "\n",
        "`merge_bounds` combines the two into a single bound per error term. Merging scales each bound by the term's error rate. These rates typically come from a noise-learning experiment (e.g. `NoiseLearnerV3`); here we use random rates for simplicity.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "82205ea3",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit.quantum_info import PauliLindbladMap, QubitSparsePauliList\n",
        "from qiskit_addon_slc.bounds import (\n",
        "    compute_backward_bounds,\n",
        "    compute_forward_bounds,\n",
        "    merge_bounds,\n",
        ")\n",
        "\n",
        "forward_bounds = compute_forward_bounds(\n",
        "    boxed_circuit, noise_model_paulis, observable\n",
        ")\n",
        "backward_bounds = compute_backward_bounds(boxed_circuit, noise_model_paulis)\n",
        "\n",
        "# Stand-in for rates that would be measured by a noise-learning experiment on hardware\n",
        "rng = np.random.default_rng(42)\n",
        "noise_rates = {\n",
        "    layer_id: PauliLindbladMap.from_components(\n",
        "        rng.random(len(paulis)) * 5e-3,\n",
        "        QubitSparsePauliList.from_sparse_list(\n",
        "            paulis.to_sparse_list(), paulis.num_qubits\n",
        "        ),\n",
        "    )\n",
        "    for layer_id, paulis in noise_model_paulis.items()\n",
        "}\n",
        "\n",
        "merged_bounds = merge_bounds(\n",
        "    boxed_circuit, forward_bounds, backward_bounds, noise_rates\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c324ebd6",
      "metadata": {},
      "source": [
        "In the shaded lightcone visualization below, each box is shaded by how strongly the errors at that site can affect the observable: bright boxes carry the largest bounds, while boxes that fade to the background contain error terms which have little effect on the calculation -- these errors are natural candidates to drop from the noise model. The values in the visualization below represent sums of the error bounds of all Pauli errors at that site, which is why some of the values grow larger than `2.0` -- the bound for any single Pauli error.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "a2201cec",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-slc/guides/quickstart/extracted-outputs/a2201cec-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 4,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_addon_slc.visualization import draw_shaded_lightcone\n",
        "\n",
        "draw_shaded_lightcone(boxed_circuit, merged_bounds, noise_model_paulis)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "42483e78",
      "metadata": {},
      "source": [
        "## 3. Reduce the sampling cost\n",
        "\n",
        "`compute_local_scales` turns the shaded lightcone into a concrete PEC configuration. It prioritizes error terms by their bounded effect on the observable and truncates the least impactful ones until the requested `bias_tolerance` is reached. It returns scales for each error term -- `0.0` for terms to ignore during mitigation and `-1.0` for terms which should be mitigated. The function also returns the resulting **sampling cost** overhead ($\\gamma^2$) and a bound on the **residual bias** incurred by the truncation.\n",
        "\n",
        "Setting `bias_tolerance=0.0` mitigates every error term within the causal lightcone of the observable and gives a baseline sampling cost. Allowing a small bias lets SLC drop the low-impact terms and reduce that cost further.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "31fe87d9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Full PEC (bias_tolerance=0.0):  sampling cost 1.923, residual bias 0.000\n",
            "Shaded    (bias_tolerance=0.05): sampling cost 1.441, residual bias 0.044\n",
            "\n",
            "Sampling-cost reduction: 25% for <= 0.05 bias\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_slc.bounds import compute_local_scales\n",
        "\n",
        "_, full_cost, full_bias = compute_local_scales(\n",
        "    boxed_circuit, merged_bounds, noise_rates, bias_tolerance=0.0\n",
        ")\n",
        "local_scales, reduced_cost, reduced_bias = compute_local_scales(\n",
        "    boxed_circuit, merged_bounds, noise_rates, bias_tolerance=0.05\n",
        ")\n",
        "\n",
        "print(\n",
        "    f\"Full PEC (bias_tolerance=0.0):  sampling cost {full_cost:.3f}, residual bias {full_bias:.3f}\"\n",
        ")\n",
        "print(\n",
        "    f\"Shaded    (bias_tolerance=0.05): sampling cost {reduced_cost:.3f}, residual bias {reduced_bias:.3f}\"\n",
        ")\n",
        "print(\n",
        "    f\"\\nSampling-cost reduction: {(1 - reduced_cost / full_cost):.0%} for <= 0.05 bias\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}