{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "d2c31ae8",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Drahtschneiden zur Schätzung von Erwartungswerten\"\n",
        "description: \"Verwenden Sie Drahtschneiden, um Schaltkreise in viele kleinere Teilschaltkreise zu unterteilen.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore edgecolor Cutqc forall fontsize */}\n",
        "\n",
        "<span id=\"wire-cutting-for-expectation-values-estimation\" />\n",
        "\n",
        "# Drahtschneiden zur Schätzung von Erwartungswerten\n",
        "\n",
        "*Geschätzte Ausführungszeit: 22 Sekunden auf einem Heron-Prozessor (HINWEIS: Dies ist lediglich eine Schätzung. (Die Laufzeit kann variieren.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8bf80006",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Lernziele\n",
        "\n",
        "* Wie man nutzt [`qiskit-addon-cutting`](https://github.com/Qiskit/qiskit-addon-cutting) , um eine große Schaltung in kleinere Teilschaltungen zu unterteilen und so die Auswirkungen von Rauschen zu verringern\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Voraussetzungen\n",
        "\n",
        "* Verwendung der [Sampler-Primitive](/docs/api/qiskit-ibm-runtime/executor-sampler-sampler), die in diesem Arbeitsablauf zum Einsatz kommt\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Hintergrund\n",
        "\n",
        "„Circuit-Knitting“ ist ein Oberbegriff, der verschiedene Methoden umfasst, bei denen eine Schaltung in mehrere kleinere Teilschaltungen mit weniger Logikgattern oder Qubits unterteilt wird. Jede der Teilschaltungen kann unabhängig ausgeführt werden, und das Endergebnis ergibt sich aus einer klassischen Nachbearbeitung der Ergebnisse der einzelnen Teilschaltungen. Diese Technik ist im [Qiskit-Add-on](https://qiskit.github.io/qiskit-addon-cutting/index.html) „Circuit cutting“ verfügbar; eine ausführliche Erläuterung der Technik finden Sie in der [Dokumentation](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html) sowie in weiteren [Einführungsmaterialien](https://qiskit.github.io/qiskit-addon-cutting/tutorials/index.html).\n",
        "\n",
        "Dieses Tutorial befasst sich mit einer Methode namens **„Wire Cutting“**, bei der die Schaltung entlang der Leiterbahn unterteilt wird [\\[1\\], \\[2\\]](#references). Beachten Sie, dass die Teilung in klassischen Schaltungen einfach ist, da das Ergebnis am Teilungspunkt deterministisch bestimmt werden kann und entweder 0 oder 1 ist. Der Zustand des Qubits zum Zeitpunkt des Schnitts ist jedoch im Allgemeinen ein gemischter Zustand. Daher muss jeder Teilkreis mehrfach in verschiedenen Basiszuständen (in der Regel in einer tomographisch vollständigen Basis, wie beispielsweise der Pauli-Basis [\\[3\\], \\[4\\]](#references) ) gemessen und entsprechend in seinem Eigenzustand vorbereitet werden. Die folgende Abbildung (Quelle: [\\[7\\]](#references) ) zeigt ein Beispiel für die Aufteilung eines GHZ-Zustands mit vier Qubits in drei Teilschaltungen. Hier bezeichnet $M_j$ eine Menge von Basisvektoren (in der Regel Pauli X, Y und Z), und $P_i$ bezeichnet eine Menge von Eigenzuständen (in der Regel $|0\\rangle$, $|1\\rangle$, $|+\\rangle$ und $|+i\\rangle$ ).\n",
        "\n",
        "![wc-1.png](https://quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/0ce8857b-7f5f-400e-8536-6a496c724d50.avif)\n",
        "![wc-2.png](https://quantum.cloud.ibm.com/docs/images/tutorials/wire-cutting/cbce4455-4794-4c81-8630-3e3993e1b29f.avif)\n",
        "\n",
        "Da jede Teilschaltung über weniger Qubits und Gatter verfügt, ist davon auszugehen, dass sie weniger anfällig für Rauschen ist. Dieses Tutorial zeigt ein Beispiel, wie diese Methode eingesetzt werden kann, um das Rauschen im System wirksam zu unterdrücken.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "55b94021",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Anforderungen\n",
        "\n",
        "Bevor Sie mit diesem Tutorial beginnen, sollten Sie sicherstellen, dass Sie Folgendes installiert haben:\n",
        "\n",
        "* Qiskit SDK v2.0 oder später, mit [Visual](/docs/api/qiskit/visualization) isierungsunterstützung\n",
        "* Qiskit Runtime v0.22 oder später ( `pip install qiskit-ibm-runtime` )\n",
        "* Schaltkreis-Erweiterung für Qiskit v0.10.0 oder höher (`pip install qiskit-addon-cutting`)\n",
        "* Qiskit-Add-on „utils“ 0.3 oder höher (`pip install qiskit-addon-utils`)\n",
        "* Qiskit Aer (`pip install qiskit-aer` )\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "7db2e559",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Einrichtung\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "bc380c46",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit\n",
        "from qiskit.quantum_info import PauliList, SparsePauliOp\n",
        "from qiskit.transpiler import generate_preset_pass_manager\n",
        "from qiskit_aer import AerSimulator\n",
        "from qiskit.result import sampled_expectation_value\n",
        "\n",
        "from qiskit_addon_cutting.instructions import CutWire\n",
        "from qiskit_addon_cutting import (\n",
        "    cut_wires,\n",
        "    expand_observables,\n",
        "    partition_problem,\n",
        "    generate_cutting_experiments,\n",
        "    reconstruct_expectation_values,\n",
        ")\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import SamplerV2, Batch"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "431a5bd2-e6ed-471b-ad9e-c4edd27784a8",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Beispiel für einen Simulator im kleinen Maßstab\n",
        "\n",
        "In diesem Tutorial wird ein [Qiskit-Muster](/docs/guides/intro-to-patterns) zur Simulation einer eindimensionalen ( 1D en) Many-Body-Localization-Schaltung (MBL) implementiert. Der MBL-Schaltkreis ist hardwareeffizient und wird durch die beiden Parameter $\\theta$ und $\\vec{\\phi}$ parametrisiert. Wenn $\\theta$ auf $0$ gesetzt und der Anfangszustand für alle Qubits auf $|0\\rangle$ vorbereitet wird, beträgt der ideale Erwartungswert von $\\langle Z_i \\rangle$ für jedes Qubit $i$ $+1$ } unabhängig von den Werten von $\\vec{\\phi}$. Weitere Details zu diesem Schaltkreis finden Sie in diesem [Artikel](https://www.nature.com/articles/s41467-025-57623-x).\n",
        "\n",
        "Beachten Sie, dass in einem rauschfreien Simulator der Erwartungswert mit und ohne Schaltungsunterbrechung identisch ist.\n",
        "\n"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "988ee237",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Schritt 1: Klassische Eingaben einem Quantenproblem zuordnen\n",
        "\n",
        "<span id=\"construct-the-1d-mbl-circuit\" />\n",
        "\n",
        "#### Bauen Sie die MBL-Schaltung „ 1D “ auf\n",
        "\n",
        "Zunächst stellen wir eine Funktion zur Erstellung der MBL-Schaltung nach „ 1D “ vor.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "d2aa988a",
      "metadata": {},
      "outputs": [],
      "source": [
        "class MBLChainCircuit(QuantumCircuit):\n",
        "    def __init__(\n",
        "        self, num_qubits: int, depth: int, use_cut: bool = False\n",
        "    ) -> None:\n",
        "        super().__init__(\n",
        "            num_qubits, name=f\"MBLChainCircuit<{num_qubits}, {depth}>\"\n",
        "        )\n",
        "        evolution = MBLChainEvolution(num_qubits, depth, use_cut)\n",
        "        self.compose(evolution, inplace=True)\n",
        "\n",
        "\n",
        "class MBLChainEvolution(QuantumCircuit):\n",
        "    def __init__(self, num_qubits: int, depth: int, use_cut) -> None:\n",
        "        super().__init__(\n",
        "            num_qubits, name=f\"MBLChainEvolution<{num_qubits}, {depth}>\"\n",
        "        )\n",
        "\n",
        "        theta = Parameter(\"θ\")\n",
        "        phis = ParameterVector(\"φ\", num_qubits)\n",
        "\n",
        "        for layer in range(depth):\n",
        "            layer_parity = layer % 2\n",
        "            # print(\"layer parity\", layer_parity)\n",
        "            for qubit in range(layer_parity, num_qubits - 1, 2):\n",
        "                # print(qubit)\n",
        "                self.cz(qubit, qubit + 1)\n",
        "                self.u(theta, 0, np.pi, qubit)\n",
        "                self.u(theta, 0, np.pi, qubit + 1)\n",
        "                if (\n",
        "                    use_cut\n",
        "                    and layer_parity == 0\n",
        "                    and (\n",
        "                        qubit == num_qubits // 2 - 1\n",
        "                        or qubit == num_qubits // 2\n",
        "                    )\n",
        "                ):\n",
        "                    self.append(CutWire(), [num_qubits // 2])\n",
        "                if use_cut and layer < depth - 1 and layer_parity == 1:\n",
        "                    if qubit == num_qubits // 2:\n",
        "                        self.append(CutWire(), [qubit])\n",
        "            for qubit in range(num_qubits):\n",
        "                self.p(phis[qubit], qubit)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "a3debf65-06df-4277-933e-14b6f6170756",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/a3debf65-06df-4277-933e-14b6f6170756-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "num_qubits = 10\n",
        "depth = 2\n",
        "mbl = MBLChainCircuit(num_qubits, depth)\n",
        "mbl.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ef13f367",
      "metadata": {},
      "source": [
        "Wir berechnen den durchschnittlichen Erwartungswert $O = \\frac{1}{n} \\sum_i Z_i$ über alle Qubits für $\\theta = 0$. Da der ideale Erwartungswert von $\\langle Z_i \\rangle = 1$ $\\forall$ $i$ ist, beträgt der ideale Erwartungswert von $O$ ebenfalls $1$. Die Parameter $\\phi$ werden zufällig ausgewählt.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "b0f1e5fd",
      "metadata": {},
      "outputs": [],
      "source": [
        "np.random.seed(42)\n",
        "phis = list(np.random.rand(mbl.num_parameters - 1))\n",
        "theta = [0]\n",
        "params = theta + phis"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a81f0346",
      "metadata": {},
      "source": [
        "Um die Schaltung zu unterteilen, muss sie durch Einfügen von „ **CutWire** “ an den gewünschten Stellen kommentiert werden. Für dieses Tutorial entscheiden wir uns für eine gleichmäßige Aufteilung. Der MBL-Schaltkreis ist so ausgelegt, dass die Einstellung `use_cut=True` in der Funktion die Anmerkung korrekt nach $\\frac{n}{2}$ Qubits einfügt, wobei $n$ die Anzahl der Qubits im ursprünglichen Schaltkreis ist. Außerdem haben wir die zufällig generierten Parameter der Schaltung zugewiesen.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "5208e0a8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/5208e0a8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)\n",
        "mbl_cut.assign_parameters(params, inplace=True)\n",
        "mbl_cut.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ac6f36e3",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Schritt 2: Optimierung des Problems für die Ausführung auf Quantenhardware\n",
        "\n",
        "<span id=\"cut-the-circuit-into-smaller-subcircuits\" />\n",
        "\n",
        "#### Schneiden Sie den Schaltkreis in kleinere Teilkreise\n",
        "\n",
        "[`qiskit-addon-cutting`](https://qiskit.github.io/qiskit-addon-cutting/)Nun unterteilen wir den Schaltkreis mithilfe von in zwei kleinere Teilschaltkreise. `qiskit-addon-cutting` fügt ein virtuelles `Move` Gatter hinzu, um die Stelle des Drahtschnitts durch entsprechende Anpassung der Anzahl der Qubits zu teilen. Nun erstellen wir die Schaltung mit diesem virtuellen Gatter. Da ein Draht durchtrennt ist, erhöht sich die Anzahl der zugehörigen Qubits um 1.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "1834cb22",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/1834cb22-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 6,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "mbl_move = cut_wires(mbl_cut)\n",
        "mbl_move.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1df16e6b",
      "metadata": {},
      "source": [
        "<span id=\"construct-and-expand-the-observable\" />\n",
        "\n",
        "#### Den Observablen konstruieren und erweitern\n",
        "\n",
        "Die zuvor definierte Observable ist der Mittelwert von „ $Z$ “ für jedes Qubit. Durch das Einfügen des virtuellen `Move` Gatters erhöht sich jedoch die effektive Anzahl der Qubits in der Schaltung. Der Observabel muss ebenfalls entsprechend erweitert werden, um dieser Änderung der Anzahl der Qubits Rechnung zu tragen. Beachte, dass die Beobachtungsgröße auf das für das virtuelle `Move` Gatter hinzugefügte zusätzliche Qubit stets trivial wirkt (wie in $I$ ).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "3074b173",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',\n",
              "           'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',\n",
              "           'IIIIIIIIZI', 'IIIIIIIIIZ'])"
            ]
          },
          "execution_count": 7,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "observable = PauliList(\n",
        "    [\"I\" * i + \"Z\" + \"I\" * (num_qubits - i - 1) for i in range(num_qubits)]\n",
        ")\n",
        "observable"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "32b7081b",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',\n",
              "           'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',\n",
              "           'IIIIIIIIIZI', 'IIIIIIIIIIZ'])"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "new_obs = expand_observables(observable, mbl, mbl_move)\n",
        "new_obs"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0ad0886b",
      "metadata": {},
      "source": [
        "Nun kann die Schaltung entlang des `Move` Gates unterteilt werden, und wir erhalten die Teilschaltungen sowie die Teilobservable, also den Teil der ursprünglichen Observable, der jeder Teilschaltung zugeordnet ist.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "c8894a06",
      "metadata": {},
      "outputs": [],
      "source": [
        "partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)\n",
        "subcircuits = partitioned_problem.subcircuits\n",
        "subobservables = partitioned_problem.subobservables"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "8f47ab33",
      "metadata": {},
      "source": [
        "Hier stellen wir die beiden Teilschaltungen dar:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "1b0e779f",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/1b0e779f-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 13,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "subcircuits[0].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 14,
      "id": "3c802f28",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/3c802f28-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 14,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "subcircuits[1].draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0e4c03fb",
      "metadata": {},
      "source": [
        "Um das Beobachtbare mithilfe der `Move` Operation zu erweitern, ist eine `PauliList` Datenstruktur erforderlich. Um den Erwartungswert der ursprünglichen Schaltung zu rekonstruieren, benötigen wir die Observable im `SparsePauliOp` Format.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "7dc51220",
      "metadata": {},
      "outputs": [],
      "source": [
        "M_z = SparsePauliOp(\n",
        "    [\"I\" * i + \"Z\" + \"I\" * (num_qubits - i - 1) for i in range(num_qubits)],\n",
        "    coeffs=[1 / num_qubits] * num_qubits,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "2b5a5746",
      "metadata": {},
      "source": [
        "Wie bereits erwähnt, muss für jeden Schnitt der vorgelagerte Kreis in einer Pauli-Basis gemessen werden, und der nachgelagerte Kreis muss im Eigenzustand dieser Basis vorbereitet werden. Die Funktion `generate_cutting_experiments` erstellt alle erforderlichen Schaltungen sowie die zu jeder Schaltung gehörenden Koeffizienten, die für die Rekonstruktion benötigt werden. Weitere Einzelheiten finden Sie in [diesem Artikel](https://journals.aps.org/prl/abstract/10.1103/PhysRevLett.125.150504).\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "648519f0",
      "metadata": {},
      "outputs": [],
      "source": [
        "subexperiments, coefficients = generate_cutting_experiments(\n",
        "    circuits=subcircuits,\n",
        "    observables=subobservables,\n",
        "    num_samples=np.inf,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "43f58cfb",
      "metadata": {},
      "source": [
        "<span id=\"transpile-the-circuits-onto-the-backend\" />\n",
        "\n",
        "#### Die Schaltkreise auf das Backend transpilieren\n",
        "\n",
        "Für das erste Beispiel, bei dem es ausschließlich um die Simulation geht, transpilieren wir die Schaltung in den Basis-Gatesatz des Backends:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "29d71cd3",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "<IBMBackend('ibm_fez')>\n"
          ]
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=133\n",
        ")\n",
        "\n",
        "print(backend)"
      ]
    },
    {
      "attachments": {},
      "cell_type": "markdown",
      "id": "b4d480b3",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Schritt 3: Mit „ Qiskit primitives “ ausführen\n",
        "\n",
        "Führen Sie nun jedes Teilversuch durch:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 20,
      "id": "05fabb27",
      "metadata": {},
      "outputs": [],
      "source": [
        "pm_basis = generate_preset_pass_manager(\n",
        "    optimization_level=2, basis_gates=backend.configuration().basis_gates\n",
        ")\n",
        "basis_subexperiments = {\n",
        "    label: pm_basis.run(partition_subexpts)\n",
        "    for label, partition_subexpts in subexperiments.items()\n",
        "}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 21,
      "id": "b22a1b00",
      "metadata": {},
      "outputs": [],
      "source": [
        "sampler = SamplerV2(mode=AerSimulator())\n",
        "jobs = {\n",
        "    label: sampler.run(subsystem_subexpts, shots=2**12)\n",
        "    for label, subsystem_subexpts in basis_subexperiments.items()\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "50b94af2",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Schritt 4: Nachbearbeitung und Rückgabe des Ergebnisses im gewünschten klassischen Format\n",
        "\n",
        "Nun holen wir das Ergebnis jedes einzelnen Durchlaufs der Teilversuche ab und berechnen den Erwartungswert der ungeschnittenen Schaltung:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 22,
      "id": "31dc35ea-6554-4ca7-9c3b-0b5394c46e4e",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Retrieve results\n",
        "results = {label: job.result() for label, job in jobs.items()}"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 23,
      "id": "4972e4e4",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "np.float64(0.9953821063041687)"
            ]
          },
          "execution_count": 23,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "reconstructed_expval_terms = reconstruct_expectation_values(\n",
        "    results,\n",
        "    coefficients,\n",
        "    subobservables,\n",
        ")\n",
        "reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real\n",
        "reconstructed_expval"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "fb5f955a",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "Text(0, 0.5, '$M_Z$')"
            ]
          },
          "execution_count": 32,
          "metadata": {},
          "output_type": "execute_result"
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/fb5f955a-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "methods = [\n",
        "    \"Uncut\",\n",
        "    \"Wire cut\",\n",
        "]\n",
        "values = [\n",
        "    1,\n",
        "    reconstructed_expval,\n",
        "]  # since the ideal expectation value in noiseless simulation is +1\n",
        "\n",
        "ax = plt.gca()\n",
        "plt.bar(methods, values, color=\"#a56eff\", width=0.4, edgecolor=\"#8a3ffc\")\n",
        "ax.set_ylabel(r\"$M_Z$\", fontsize=12)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0d6db390-e7a8-4efe-902c-8d9a312170c6",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Beispiel für groß angelegte Hardware\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ae69c5e0-32b1-4f03-ab13-7b95a9acfd25",
      "metadata": {},
      "source": [
        "Nun demonstrieren wir das Drahtschneiden für eine MBL-Schaltung mit 60 Qubits. Sowohl die ungeschnittenen als auch die geschnittenen Schaltkreise werden auf „ IBM Quantum® “-Hardware ausgeführt:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "37834c72",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/wire-cutting/extracted-outputs/37834c72-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "num_qubits = 60\n",
        "depth = 2\n",
        "\n",
        "# construct the circuit\n",
        "mbl = MBLChainCircuit(num_qubits, depth)\n",
        "\n",
        "# create parameters\n",
        "phis = list(np.random.rand(mbl.num_parameters - 1))\n",
        "theta = [0]\n",
        "params = theta + phis\n",
        "\n",
        "# construct the cut circuit\n",
        "mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)\n",
        "mbl_cut.assign_parameters(params, inplace=True)\n",
        "mbl_move = cut_wires(mbl_cut)\n",
        "\n",
        "# Define observable and expand to account for the wire cut\n",
        "observable = PauliList(\n",
        "    [\"I\" * i + \"Z\" + \"I\" * (num_qubits - i - 1) for i in range(num_qubits)]\n",
        ")\n",
        "new_obs = expand_observables(observable, mbl, mbl_move)\n",
        "\n",
        "# Construct a SparsePauliOp version of the observable for later use in reconstruction\n",
        "M_z = SparsePauliOp(\n",
        "    [\"I\" * i + \"Z\" + \"I\" * (num_qubits - i - 1) for i in range(num_qubits)],\n",
        "    coeffs=[1 / num_qubits] * num_qubits,\n",
        ")\n",
        "\n",
        "# Partition the circuit and get subcircuits and subobservables\n",
        "partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)\n",
        "subcircuits = partitioned_problem.subcircuits\n",
        "subobservables = partitioned_problem.subobservables\n",
        "\n",
        "# Obtain subexperiments and coefficients\n",
        "subexperiments, coefficients = generate_cutting_experiments(\n",
        "    circuits=subcircuits,\n",
        "    observables=subobservables,\n",
        "    num_samples=np.inf,\n",
        ")\n",
        "\n",
        "# Transpile the subexperiments to the backend\n",
        "pm = generate_preset_pass_manager(optimization_level=2, backend=backend)\n",
        "isa_subexperiments = {\n",
        "    label: pm.run(partition_subexpts)\n",
        "    for label, partition_subexpts in subexperiments.items()\n",
        "}\n",
        "\n",
        "# Execute the subexperiments and retrieve results\n",
        "with Batch(backend=backend) as batch:\n",
        "    sampler = SamplerV2(mode=batch)\n",
        "    sampler.options.environment.job_tags = [\"TUT_WC\"]\n",
        "    jobs = {\n",
        "        label: sampler.run(subsystem_subexpts, shots=2**12)\n",
        "        for label, subsystem_subexpts in isa_subexperiments.items()\n",
        "    }\n",
        "results = {label: job.result() for label, job in jobs.items()}\n",
        "\n",
        "# Reconstruct the expectation value of the original observable\n",
        "reconstructed_expval_terms = reconstruct_expectation_values(\n",
        "    results,\n",
        "    coefficients,\n",
        "    subobservables,\n",
        ")\n",
        "reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real\n",
        "\n",
        "# Compute the uncut circuit to obtain the noisy expectation value for comparison\n",
        "sampler = SamplerV2(mode=backend)\n",
        "sampler.options.environment.job_tags = [\"TUT_WC\"]\n",
        "\n",
        "if mbl.num_clbits == 0:\n",
        "    mbl.measure_all()\n",
        "isa_mbl = pm.run(mbl)\n",
        "\n",
        "pub = (isa_mbl, params)\n",
        "uncut_job = sampler.run([pub])\n",
        "\n",
        "uncut_counts = uncut_job.result()[0].data.meas.get_counts()\n",
        "uncut_expval = sampled_expectation_value(uncut_counts, M_z)\n",
        "\n",
        "# visualize the results\n",
        "ax = plt.gca()\n",
        "methods = [\"uncut\", \"cut\"]\n",
        "values = [uncut_expval, reconstructed_expval]\n",
        "\n",
        "plt.bar(methods, values, color=\"#a56eff\", width=0.4, edgecolor=\"#8a3ffc\")\n",
        "plt.axhline(y=1, color=\"k\", linestyle=\"--\")\n",
        "plt.text(0.3, 0.95, \"Exact result\")\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 29,
      "id": "1445d099",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "0.9202473958333336"
            ]
          },
          "execution_count": 29,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "uncut_expval"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "75f48e6a-c7e4-46f3-9d39-a7a877427a04",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Nächste Schritte\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Empfehlungen\">\n",
        "  Wenn Sie diese Arbeit interessant fanden, könnten Sie auch an folgenden Materialien interessiert sein:\n",
        "\n",
        "  * [Periodische Randbedingungen mit Schaltkreisschnitt](/docs/tutorials/periodic-boundary-conditions-with-circuit-cutting)\n",
        "  * [Schneiden von Schaltkreisen zur Reduzierung der Tiefe](/docs/tutorials/depth-reduction-with-circuit-cutting)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "23cd3042",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Referenzen\n",
        "\n",
        "\\[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulation großer Quantenschaltungen auf einem kleinen Quantencomputer. Physical Review Letters, 125(15), 150504.\n",
        "\n",
        "\\[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: Verwendung kleiner Quantencomputer für die Bewertung großer Quantenschaltungen. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (S. 473-486).\n",
        "\n",
        "\\[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum Circuit Cutting mit Maximum-Likelihood-Tomographie. npj Quantum Information, 7(1), 64.\n",
        "\n",
        "\\[4] Majumdar, R., & Wood, C. J. (2022). Fehlervermindertes Schneiden von Quantenschaltungen. arXiv vorabdruck arXiv:2211.13431.\n",
        "\n",
        "\\[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelisierung quantenklassischer Workloads: Profiling der Auswirkungen von Splitting-Techniken. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.\n",
        "\n",
        "\\[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Verteiltes Scheduling von Quantenschaltungen mit Rauschen und Zeitoptimierung. arXiv vorabdruck arXiv:2309.06005.\n",
        "\n",
        "\\[7] Majumdar, R. (2024). Effiziente Reduzierung von Ressourcen und Rauschen in Schaltkreisen für diskrete Quantencomputer (Doktorarbeit, Indian Statistical Institute – Kalkutta). [https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar\\&cbl=2026366\\&diss=y](https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar\\&cbl=2026366\\&diss=y)\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": 1.5,
    "qpuSeconds": 22
  },
  "nbformat": 4,
  "nbformat_minor": 5
}