{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Quickstart\"\n",
        "description: \"Quickstart for the latest version of Qiskit propagated noise absorption\"\n",
        "---\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b3182b6",
      "metadata": {},
      "source": [
        "# Quickstart\n",
        "\n",
        "This guide demonstrates a minimal working example of the `qiskit-addon-pna` package. We use propagated noise absorption (PNA) to build a noise-mitigating observable. Given a circuit and a Pauli-Lindblad noise model, PNA classically propagates the observable through the inverse noise channel. Measuring the resulting observable on the noisy QPU mitigates the learned gate noise.\n",
        "\n",
        "To see how to build a realistic workflow and run on quantum hardware with the [directed execution model](/docs/guides/directed-execution-model), including learning the noise model with `NoiseLearnerV3`, check out the [PNA tutorial](/docs/tutorials/propagated-noise-absorption) on the IBM Quantum Platform.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a4068f26",
      "metadata": {},
      "source": [
        "## 1. Prepare the inputs for PNA\n",
        "\n",
        "PNA takes as input a circuit, noise model, and observable. Here we build a 10-qubit Trotterized transverse-field Ising model on a 1D chain. We generate a random 2-local Pauli-Lindblad noise model for each entangling gate and embed it as a Qiskit Aer `PauliLindbladError` instruction just before that gate. We choose a weight-4 Pauli-Z observable to measure.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "4a9a3ced",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-pna/guides/quickstart/extracted-outputs/4a9a3ced-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.quantum_info import SparsePauliOp, pauli_basis\n",
        "from qiskit_aer.noise import PauliLindbladError\n",
        "\n",
        "\n",
        "def random_pauli_lindblad_noise(generators, seed, noise_scale=2e-3):\n",
        "    rates = np.random.default_rng(seed).random(len(generators)) * noise_scale\n",
        "    return PauliLindbladError(generators, rates)\n",
        "\n",
        "\n",
        "def ising_circuit(\n",
        "    num_qubits,\n",
        "    layers,\n",
        "    edge_noise=None,\n",
        "    *,\n",
        "    num_steps=3,\n",
        "    rx_angle=np.pi / 8,\n",
        "    rzz_angle=-np.pi / 2,\n",
        "):\n",
        "    \"\"\"Trotterized transverse-field Ising model; edge_noise=None gives the noiseless circuit.\"\"\"\n",
        "    qc = QuantumCircuit(num_qubits)\n",
        "    for _ in range(num_steps):\n",
        "        qc.rx(rx_angle, range(num_qubits))\n",
        "        for layer in layers:\n",
        "            for edge in layer:\n",
        "                if edge_noise is not None:\n",
        "                    qc.append(\n",
        "                        edge_noise[edge], edge\n",
        "                    )  # inject synthetic gate noise\n",
        "                qc.rzz(rzz_angle, *edge)\n",
        "    return qc\n",
        "\n",
        "\n",
        "num_qubits = 10\n",
        "\n",
        "# Two entangling layers per Trotter step: even and odd bonds of a 1D chain\n",
        "layers = [\n",
        "    [(i, i + 1) for i in range(0, num_qubits - 1, 2)],\n",
        "    [(i, i + 1) for i in range(1, num_qubits - 1, 2)],\n",
        "]\n",
        "edges = [edge for layer in layers for edge in layer]\n",
        "\n",
        "# Random 2-local Pauli-Lindblad noise, one instance per entangling gate\n",
        "two_qubit_paulis = SparsePauliOp(\n",
        "    [p for p in pauli_basis(2) if np.sum(p.x + p.z)]\n",
        ").paulis\n",
        "edge_noise = {\n",
        "    edge: random_pauli_lindblad_noise(two_qubit_paulis, seed=1234 + j)\n",
        "    for j, edge in enumerate(edges)\n",
        "}\n",
        "\n",
        "noisy_circuit = ising_circuit(num_qubits, layers, edge_noise)\n",
        "\n",
        "# A single weight-4 observable: <Z3 Z4 Z5 Z6>\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZZZ\", [3, 4, 5, 6], 1.0)], num_qubits=num_qubits\n",
        ")\n",
        "\n",
        "noisy_circuit.draw(\"mpl\", fold=-1, scale=0.6)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "b99617c3",
      "metadata": {},
      "source": [
        "## 2. Generate the noise-mitigating observable\n",
        "\n",
        "[generate\\_noise\\_mitigating\\_observable](/docs/api/qiskit-addon-pna/qiskit-addon-pna#generate_noise_mitigating_observable) propagates each Pauli generator of the inverse noise channel forward to the end of the circuit. The observable is then back-propagated through the inverse noise channel, returning a new observable $\\tilde{O}$. Three key parameters affect the computation cost:\n",
        "\n",
        "* `max_err_terms`: the number of terms kept in each anti-noise generator as it is propagated forward.\n",
        "* `max_obs_terms`: the number of terms kept in $\\tilde{O}$.\n",
        "* `atol`: terms with coefficient magnitude below this threshold are dropped.\n",
        "\n",
        "For this small, near-Clifford circuit we set the term limits high and use a modest `atol`, so $\\tilde{O}$ stays small and we can measure all of its terms.\n",
        "\n",
        "***Note: This function uses Python `multiprocessing`. When running as a script, call it inside an `if __name__ == \"__main__\":` guard.***\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "39891ec7",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Original observable:         1 term\n",
            "Noise-mitigating observable: 207 terms\n"
          ]
        }
      ],
      "source": [
        "from qiskit_addon_pna import generate_noise_mitigating_observable\n",
        "\n",
        "mitigating_observable = generate_noise_mitigating_observable(\n",
        "    noisy_circuit,\n",
        "    observable,\n",
        "    max_err_terms=100_000,\n",
        "    max_obs_terms=100_000,\n",
        "    atol=1e-5,\n",
        "    num_processes=4,\n",
        ")\n",
        "\n",
        "print(f\"Original observable:         {len(observable)} term\")\n",
        "print(f\"Noise-mitigating observable: {len(mitigating_observable)} terms\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f7881a9e",
      "metadata": {},
      "source": [
        "## 3. Mitigate gate errors by measuring the noise-mitigating observable\n",
        "\n",
        "Here we see that the new observable effectively mitigates the gate noise affecting the experiment.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "a56dd204",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ideal (noiseless):   0.8073\n",
            "Noisy (unmitigated): 0.6431\n",
            "Mitigated (PNA):     0.8071\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/addons/qiskit-addon-pna/guides/quickstart/extracted-outputs/a56dd204-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "from qiskit_aer.primitives import EstimatorV2\n",
        "\n",
        "noiseless_circuit = ising_circuit(num_qubits, layers)\n",
        "\n",
        "# density_matrix method at zero precision -> exact expectation values (no shot noise)\n",
        "estimator = EstimatorV2(\n",
        "    options={\n",
        "        \"backend_options\": {\"method\": \"density_matrix\"},\n",
        "        \"default_precision\": 0.0,\n",
        "    }\n",
        ")\n",
        "\n",
        "ideal, noisy, mitigated = (\n",
        "    result.data.evs\n",
        "    for result in estimator.run(\n",
        "        [\n",
        "            (noiseless_circuit, observable),\n",
        "            (noisy_circuit, observable),\n",
        "            (noisy_circuit, mitigating_observable),\n",
        "        ]\n",
        "    ).result()\n",
        ")\n",
        "\n",
        "print(f\"Ideal (noiseless):   {ideal:.4f}\")\n",
        "print(f\"Noisy (unmitigated): {noisy:.4f}\")\n",
        "print(f\"Mitigated (PNA):     {mitigated:.4f}\")\n",
        "\n",
        "fig, ax = plt.subplots()\n",
        "ax.bar(\n",
        "    [\"Noisy\", \"Mitigated\"],\n",
        "    [noisy, mitigated],\n",
        "    width=0.6,\n",
        "    color=[\"#b0b0b0\", \"#4c4c4c\"],\n",
        ")\n",
        "ax.axhline(ideal, color=\"green\", linestyle=\"--\", label=\"Ideal (noiseless)\")\n",
        "ax.set_ylabel(r\"$\\langle Z_3 Z_4 Z_5 Z_6 \\rangle$\")\n",
        "ax.legend()\n",
        "plt.show()"
      ]
    },
    {
      "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
}