{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "periodic-00",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Taglio dei circuiti per condizioni al contorno periodiche\"\n",
        "description: \"Utilizzare il taglio dei gate per implementare condizioni al contorno periodiche, quindi confrontare le grandezze osservabili ricostruite con quelle di un circuito non sottoposto a taglio.\"\n",
        "---\n",
        "\n",
        "<span id=\"circuit-cutting-for-periodic-boundary-conditions\" />\n",
        "\n",
        "# Taglio dei circuiti per condizioni al contorno periodiche\n",
        "\n",
        "Stima della durata di *utilizzo: 2 minuti ( `ibm_boston` NOTA: si tratta solo di una stima. (La durata potrebbe variare.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-01",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "* Utilizza una connessione virtuale per traspilare una catena periodica senza instradare i suoi gate di confine.\n",
        "* Sostituisci quei cancelli con esperimenti di interruzione del circuito e ricostruisci i valori attesi dai risultati del Sampler.\n",
        "* Confrontare i circuiti con e senza taglio, tenendo conto della profondità del circuito e dei costi di campionamento.\n",
        "\n",
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Dovresti avere familiarità con [i modelli di Qiskit](/docs/guides/intro-to-patterns), [la transpilazione](/docs/guides/transpile) e la [primitiva Sampler](/docs/guides/primitives).\n",
        "\n",
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "Le condizioni al contorno periodiche collegano l'ultimo qubit di una catena al primo. Se questi qubit sono distanti tra loro all’interno di un processore quantistico, l’implementazione della loro interazione richiede un instradamento che può comportare l’aggiunta di porte a due qubit e aumentare la profondità del circuito.\n",
        "\n",
        "Il taglio dei gate sostituisce un gate a due qubit con una combinazione ponderata di operazioni su un singolo qubit e misurazioni. L'esecuzione di questi esperimenti e la combinazione dei relativi risultati consentono di ricostruire i valori attesi per il circuito originale. Ciò può ridurre il sovraccarico di instradamento, a scapito di un campionamento aggiuntivo. Si veda la [spiegazione relativa al taglio del circuito](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html) per la scomposizione in quasi-probabilità sottostante.\n",
        "\n",
        "Per prima cosa verificherai questo flusso di lavoro su un simulatore privo di rumore a sei qubit. Successivamente lo applicherai a una catena di almeno 30 qubit su hardware e confronterai i risultati con e senza taglio. Il circuito utilizza parametri con valori attesi noti analiticamente, pertanto il confronto a livello hardware non richiede una simulazione con vettori di stato di grandi dimensioni.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-02",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questo tutorial, installa:\n",
        "\n",
        "* Qiskit SDK v2.3 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.46 o versioni successive (`pip install 'qiskit-ibm-runtime~=0.46.1'`)\n",
        "* Componente aggiuntivo Qiskit per il disegno di circuiti, versio v0.10.0 (`pip install 'qiskit-addon-cutting~=0.10.0'`)\n",
        "* Qiskit Aer versio v0.17.2 o successive (`pip install 'qiskit-aer~=0.17.2'`)\n",
        "\n",
        "L'esempio relativo all'hardware richiede inoltre un [account IBM Quantum® Platform](/) con accesso a una QPU con almeno 120 qubit e [credenziali salvate](/docs/guides/save-credentials). L'esempio del simulatore funziona senza account.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-03",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Importa gli strumenti utilizzati in entrambi gli esempi. Aer supporta le misurazioni intermedie introdotte dal taglio a gate.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "periodic-04",
      "metadata": {},
      "outputs": [],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "import rustworkx as rx\n",
        "\n",
        "from qiskit.circuit.equivalence_library import SessionEquivalenceLibrary\n",
        "from qiskit.circuit.library import n_local\n",
        "from qiskit.providers.fake_provider import GenericBackendV2\n",
        "from qiskit.quantum_info import SparsePauliOp, Statevector\n",
        "from qiskit.result import sampled_expectation_value\n",
        "from qiskit.transpiler import CouplingMap, PassManager\n",
        "from qiskit.transpiler.passes import (\n",
        "    BasisTranslator,\n",
        "    GateDirection,\n",
        "    Optimize1qGatesDecomposition,\n",
        ")\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit_aer.primitives import SamplerV2 as AerSampler\n",
        "from qiskit_addon_cutting import (\n",
        "    cut_gates,\n",
        "    generate_cutting_experiments,\n",
        "    reconstruct_expectation_values,\n",
        ")\n",
        "from qiskit_ibm_runtime import (\n",
        "    Batch,\n",
        "    QiskitRuntimeService,\n",
        "    SamplerOptions,\n",
        "    SamplerV2,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-05",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "Utilizzare un anello a sei qubit con due livelli di porte di entanglement. Il dispositivo simulato presenta una connettività lineare, pertanto il bordo di chiusura dell'anello richiede un instradamento, a meno che non venga tagliato.\n",
        "\n",
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "La seguente funzione crea livelli di rotazione RX intercalati con porte CX sui fronti alternati dell'anello. Due ripetizioni danno come risultato due porte di confine da tagliare. Imposta i primi due livelli di rotazione a zero e l'ultimo livello a $\\pi$. Il risultato ideale è quindi $|1\\rangle^{\\otimes n}$, a parte una fase globale.\n",
        "\n",
        "Calcolare il valore atteso medio della componente Z di un singolo qubit e il valore atteso medio della componente ZZ tra qubit adiacenti, **inclusa la coppia periodica** :\n",
        "\n",
        "$$\n",
        "M_Z = \\frac{1}{n}\\sum_{i=0}^{n-1}\\langle Z_i\\rangle, \\qquad\n",
        "C_{ZZ} = \\frac{1}{n}\\sum_{i=0}^{n-1}\\langle Z_i Z_{(i+1)\\bmod n}\\rangle.\n",
        "$$\n",
        "\n",
        "I loro valori ideali sono, rispettivamente, $-1$ e $+1$. Questi parametri costituiscono un punto di riferimento per il flusso di lavoro; non rappresentano un calcolo classicamente complesso.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "periodic-06",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Ideal averages: [-1.  1.]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/periodic-06-1.avif\" alt=\"Six-qubit periodic circuit with two boundary CX gates\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "def build_periodic_problem(num_qubits, num_reps=2):\n",
        "    \"\"\"Build a periodic benchmark circuit and its Z and ZZ observables.\"\"\"\n",
        "    edges = [(i, (i + 1) % num_qubits) for i in range(num_qubits)]\n",
        "    circuit = n_local(\n",
        "        num_qubits,\n",
        "        rotation_blocks=\"rx\",\n",
        "        entanglement_blocks=\"cx\",\n",
        "        entanglement=edges[::2] + edges[1::2],\n",
        "        reps=num_reps,\n",
        "    )\n",
        "    parameters = np.zeros(circuit.num_parameters)\n",
        "    parameters[-num_qubits:] = np.pi\n",
        "    circuit = circuit.assign_parameters(parameters)\n",
        "\n",
        "    terms = [(\"Z\", [i], 1) for i in range(num_qubits)]\n",
        "    terms += [(\"ZZ\", [i, (i + 1) % num_qubits], 1) for i in range(num_qubits)]\n",
        "    observables = SparsePauliOp.from_sparse_list(terms, num_qubits)\n",
        "    return circuit, observables\n",
        "\n",
        "\n",
        "num_qubits = 6\n",
        "circuit, observables = build_periodic_problem(num_qubits)\n",
        "ideal_expvals = np.array(\n",
        "    [\n",
        "        Statevector(circuit).expectation_value(p).real\n",
        "        for p in observables.paulis\n",
        "    ]\n",
        ")\n",
        "print(\"Ideal averages:\", ideal_expvals.reshape(2, num_qubits).mean(axis=1))\n",
        "circuit.draw(\"mpl\", style=\"iqp\", fold=60)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-07",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Creare un modello di dispositivo con una mappa di accoppiamento lineare. Fornisce la destinazione della compilazione; l'esecuzione nella Fase 3 avverrà senza intoppi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "periodic-08",
      "metadata": {},
      "outputs": [],
      "source": [
        "simulation_backend = GenericBackendV2(\n",
        "    num_qubits=num_qubits,\n",
        "    basis_gates=[\"id\", \"rz\", \"sx\", \"x\", \"cx\"],\n",
        "    coupling_map=CouplingMap.from_line(num_qubits),\n",
        "    seed=1234,\n",
        ")\n",
        "initial_layout = list(range(num_qubits))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-09",
      "metadata": {},
      "source": [
        "Aggiungere una **connessione virtuale** tra i qubit fisici alle estremità della catena. Eseguire una volta la transpilazione utilizzando questa mappa di accoppiamento estesa e una base CX. La connessione virtuale impedisce al transpiler di instradare i gate di confine, ma il circuito risultante non è ancora in grado di funzionare sul dispositivo reale.\n",
        "\n",
        "Individua ed elimina i due gate CX di confine, applica il layout virtuale alle grandezze osservabili e genera tutti i termini di decomposizione con `num_samples=np.inf`. In questo modo i coefficienti di decomposizione risultano esatti; le misurazioni continuano comunque a basarsi su un numero finito di impulsi.\n",
        "\n",
        "Infine, traduci gli esperimenti nei gate nativi del dispositivo e correggi la loro direzione dove necessario. Questi passaggi mantengono la disposizione fisica dei qubit ed evitano di dover ripetere la selezione della disposizione e l'instradamento per ogni esperimento. L'helper restituisce gli osservabili mappati necessari per la ricostruzione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "periodic-10",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Boundary gates cut: 2\n",
            "Number of experiments: 36\n",
            "Sampling overhead: 81.0\n"
          ]
        }
      ],
      "source": [
        "def prepare_cut_experiments(circuit, observables, backend, initial_layout):\n",
        "    \"\"\"Compile with a virtual edge, cut it, and translate to native gates.\"\"\"\n",
        "    boundary = {initial_layout[0], initial_layout[-1]}\n",
        "    virtual_map = CouplingMap(list(backend.coupling_map.get_edges()))\n",
        "    virtual_map.make_symmetric()\n",
        "    virtual_map.add_edge(initial_layout[-1], initial_layout[0])\n",
        "    virtual_map.add_edge(initial_layout[0], initial_layout[-1])\n",
        "\n",
        "    virtual_pm = generate_preset_pass_manager(\n",
        "        optimization_level=1,\n",
        "        coupling_map=virtual_map,\n",
        "        basis_gates=[\"rz\", \"sx\", \"x\", \"cx\"],\n",
        "        initial_layout=initial_layout,\n",
        "        seed_transpiler=1234,\n",
        "    )\n",
        "    virtual_circuit = virtual_pm.run(circuit)\n",
        "    cut_indices = [\n",
        "        i\n",
        "        for i, instruction in enumerate(virtual_circuit.data)\n",
        "        if instruction.operation.name == \"cx\"\n",
        "        and {virtual_circuit.find_bit(q).index for q in instruction.qubits}\n",
        "        == boundary\n",
        "    ]\n",
        "    mapped_observables = observables.apply_layout(virtual_circuit.layout)\n",
        "    qpd_circuit, bases = cut_gates(virtual_circuit, cut_indices)\n",
        "    experiments, coefficients = generate_cutting_experiments(\n",
        "        circuits=qpd_circuit,\n",
        "        observables=mapped_observables.paulis,\n",
        "        num_samples=np.inf,\n",
        "    )\n",
        "    native_pm = PassManager(\n",
        "        [\n",
        "            BasisTranslator(\n",
        "                SessionEquivalenceLibrary,\n",
        "                target_basis=backend.operation_names,\n",
        "                target=backend.target,\n",
        "            ),\n",
        "            GateDirection(backend.coupling_map, target=backend.target),\n",
        "            BasisTranslator(\n",
        "                SessionEquivalenceLibrary,\n",
        "                target_basis=backend.operation_names,\n",
        "                target=backend.target,\n",
        "            ),\n",
        "            Optimize1qGatesDecomposition(target=backend.target),\n",
        "        ]\n",
        "    )\n",
        "    isa_experiments = native_pm.run(experiments)\n",
        "    print(f\"Boundary gates cut: {len(cut_indices)}\")\n",
        "    print(f\"Number of experiments: {len(isa_experiments)}\")\n",
        "    print(\n",
        "        f\"Sampling overhead: {np.prod([basis.overhead for basis in bases])}\"\n",
        "    )\n",
        "    return isa_experiments, coefficients, mapped_observables\n",
        "\n",
        "\n",
        "isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(\n",
        "    circuit, observables, simulation_backend, initial_layout\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-11",
      "metadata": {},
      "source": [
        "Ogni scomposizione CX è composta da sei termini. Con due tagli e una base di misura Z condivisa, si ottengono esperimenti di tipo $6^2=36$. Il sovraccarico di campionamento è una grandezza diversa: la somma al quadrato dei coefficienti di decomposizione assoluti è $9^2=81$. Descrive il costo di campionamento necessario per mantenere la precisione nella ricostruzione per quasi-probabilità. Si veda la [spiegazione relativa](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html) al sovraccarico di campionamento.\n",
        "\n",
        "A titolo di confronto, si esegua la transpilazione di una copia misurata del circuito non tagliato utilizzando la mappa di accoppiamento effettiva. `measure_all()` registra i qubit logici originali nel `meas` registro; la transpilazione preserva tale mappatura sui bit classici, pertanto i suoi conteggi possono essere valutati utilizzando gli osservabili originali.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "id": "periodic-12",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Uncut circuit depth: 15\n",
            "Cut circuit depths: [6, 7, 8, 9, 10, 11, 12, 15]\n"
          ]
        }
      ],
      "source": [
        "uncut_pm = generate_preset_pass_manager(\n",
        "    optimization_level=1,\n",
        "    backend=simulation_backend,\n",
        "    initial_layout=initial_layout,\n",
        "    seed_transpiler=1234,\n",
        ")\n",
        "isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))\n",
        "print(\"Uncut circuit depth:\", isa_uncut.depth())\n",
        "print(\"Cut circuit depths:\", sorted({qc.depth() for qc in isa_experiments}))"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-13",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n",
        "Esegui entrambe le versioni con la primitiva Aer Sampler V2. Ogni esperimento con taglio prevede 10.000 colpi, così come il singolo circuito senza taglio. In questo confronto, il flusso di lavoro di montaggio utilizza quindi 36 volte più riprese. Il numero di scatti corrispondenti per ogni circuito non corrisponde al budget totale di esecuzione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "periodic-14",
      "metadata": {},
      "outputs": [],
      "source": [
        "shots = 10_000\n",
        "sampler = AerSampler(seed=1234)\n",
        "cut_result = sampler.run(isa_experiments, shots=shots).result()\n",
        "uncut_result = sampler.run([isa_uncut], shots=shots).result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-15",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Ricostruire ciascun valore atteso di Pauli utilizzando le stesse grandezze osservabili mappate e lo stesso ordine degli esperimenti utilizzati durante la generazione. Quindi calcolare la media dei primi valori $n$ per $M_Z$ e dei restanti valori $n$ per $C_{ZZ}$. Per il circuito non tagliato, calcolare i valori attesi direttamente dai conteggi misurati.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "periodic-16",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Cut averages (Z, ZZ): [-1.00100833  0.99778333]\n",
            "Uncut averages (Z, ZZ): [-1.  1.]\n",
            "Absolute cut errors: [0.00100833 0.00221667]\n",
            "Absolute uncut errors: [0. 0.]\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/periodic-16-1.avif\" alt=\"Ideal, cut, and uncut expectation values from noiseless simulation\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "cut_expvals = reconstruct_expectation_values(\n",
        "    cut_result, coefficients, mapped_observables.paulis\n",
        ")\n",
        "uncut_counts = uncut_result[0].data.meas.get_counts()\n",
        "uncut_expvals = [\n",
        "    sampled_expectation_value(uncut_counts, p) for p in observables.paulis\n",
        "]\n",
        "\n",
        "\n",
        "def compare_results(cut_expvals, uncut_expvals, num_qubits):\n",
        "    \"\"\"Display average Z and periodic ZZ values against the ideal values.\"\"\"\n",
        "    cut_averages = np.asarray(cut_expvals).reshape(2, num_qubits).mean(axis=1)\n",
        "    uncut_averages = (\n",
        "        np.asarray(uncut_expvals).reshape(2, num_qubits).mean(axis=1)\n",
        "    )\n",
        "    print(\"Cut averages (Z, ZZ):\", cut_averages)\n",
        "    print(\"Uncut averages (Z, ZZ):\", uncut_averages)\n",
        "    print(\"Absolute cut errors:\", np.abs(cut_averages - [-1, 1]))\n",
        "    print(\"Absolute uncut errors:\", np.abs(uncut_averages - [-1, 1]))\n",
        "\n",
        "    fig, ax = plt.subplots(figsize=(7, 4))\n",
        "    positions = np.arange(2)\n",
        "    for offset, values, label, color in [\n",
        "        (-0.25, [-1, 1], \"Ideal\", \"#4589ff\"),\n",
        "        (0, cut_averages, \"Cut\", \"#a56eff\"),\n",
        "        (0.25, uncut_averages, \"Uncut\", \"#009d9a\"),\n",
        "    ]:\n",
        "        ax.bar(\n",
        "            positions + offset, values, width=0.25, label=label, color=color\n",
        "        )\n",
        "    ax.set_xticks(positions, [\"Average Z\", \"Average periodic ZZ\"])\n",
        "    ax.set_ylabel(\"Expectation value\")\n",
        "    ax.axhline(0, color=\"black\", linewidth=0.8)\n",
        "    ax.legend()\n",
        "    fig.tight_layout()\n",
        "    plt.show()\n",
        "\n",
        "\n",
        "compare_results(cut_expvals, uncut_expvals, num_qubits)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-17",
      "metadata": {},
      "source": [
        "I risultati senza rumore dovrebbero essere vicini ai valori ideali. La ricostruzione a numero finito di campioni può presentare fluttuazioni e una stima ricostruita può trovarsi al di fuori dell'intervallo $[-1,1]$. Il taglio non migliora un risultato già privo di rumore; questo esempio verifica la scomposizione, la mappatura del layout e la ricostruzione prima dell'esecuzione sull'hardware.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-18",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "Riutilizzare il flusso di lavoro sulla QPU operativa meno sollecitata con almeno 120 qubit. Scegliere il percorso più lungo e quello più breve nel relativo grafo di accoppiamento non orientato: ogni coppia consecutiva è collegata, mentre i vertici estremi sono distanti tra loro. In questo modo la catena viene adattata al dispositivo selezionato, anziché basarsi su un layout Eagle fisso. Il codice verifica che la catena contenga almeno 30 qubit e che i suoi estremi non siano adiacenti.\n",
        "\n",
        "Il blocco seguente riunisce i quattro passaggi. La rotazione dei gate, la rotazione delle misure e il disaccoppiamento dinamico sono disabilitati in entrambe le versioni. Entrambi i lavori vengono eseguiti nello stesso batch e ricevono il tag “tutorial `TUT_CCPBC`”. Gli esperimenti di taglio richiedono comunque un numero maggiore di fotogrammi complessivi, quindi confronta sia la precisione che l'utilizzo della QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "periodic-19",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Backend: ibm_boston; chain length: 33\n",
            "Initial layout: [0, 1, 2, 3, 16, 23, 24, 25, 37, 45, 44, 43, 56, 63, 64, 65, 77, 85, 86, 87, 97, 107, 106, 105, 117, 125, 124, 123, 136, 143, 142, 141, 140]\n",
            "Boundary gates cut: 2\n",
            "Number of experiments: 36\n",
            "Sampling overhead: 81.0\n",
            "Uncut circuit depth: 375\n",
            "Cut circuit depths: [20]\n",
            "Cut job ID: dagp7fj9k43c73admp40\n",
            "Uncut job ID: dagp7fj9k43c73admp5g\n",
            "Cut averages (Z, ZZ): [-0.92305152  0.89586667]\n",
            "Uncut averages (Z, ZZ): [-0.61106061  0.51606061]\n",
            "Absolute cut errors: [0.07694848 0.10413333]\n",
            "Absolute uncut errors: [0.38893939 0.48393939]\n",
            "Total QPU usage: 102.0 seconds (1.70 minutes)\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/periodic-boundary-conditions-with-circuit-cutting/extracted-outputs/periodic-19-1.avif\" alt=\"Ideal, cut, and uncut expectation values measured on ibm_boston\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(\n",
        "    operational=True, simulator=False, min_num_qubits=120\n",
        ")\n",
        "\n",
        "# ------------------------- Step 1 -------------------------\n",
        "graph = backend.coupling_map.graph.to_undirected(multigraph=False)\n",
        "paths = rx.all_pairs_dijkstra_shortest_paths(graph, lambda _: 1)\n",
        "initial_layout = list(\n",
        "    max(\n",
        "        (\n",
        "            path\n",
        "            for source_paths in paths.values()\n",
        "            for path in source_paths.values()\n",
        "        ),\n",
        "        key=len,\n",
        "    )\n",
        ")\n",
        "num_qubits = len(initial_layout)\n",
        "if num_qubits < 30:\n",
        "    raise ValueError(\n",
        "        \"Select a backend with a shortest path of at least 30 qubits.\"\n",
        "    )\n",
        "if graph.has_edge(initial_layout[0], initial_layout[-1]):\n",
        "    raise ValueError(\"The chain endpoints must not be directly connected.\")\n",
        "print(f\"Backend: {backend.name}; chain length: {num_qubits}\")\n",
        "print(\"Initial layout:\", initial_layout)\n",
        "circuit, observables = build_periodic_problem(num_qubits)\n",
        "\n",
        "# ------------------------- Step 2 -------------------------\n",
        "isa_experiments, coefficients, mapped_observables = prepare_cut_experiments(\n",
        "    circuit, observables, backend, initial_layout\n",
        ")\n",
        "uncut_pm = generate_preset_pass_manager(\n",
        "    optimization_level=1,\n",
        "    backend=backend,\n",
        "    initial_layout=initial_layout,\n",
        "    seed_transpiler=1234,\n",
        ")\n",
        "isa_uncut = uncut_pm.run(circuit.measure_all(inplace=False))\n",
        "print(\"Uncut circuit depth:\", isa_uncut.depth())\n",
        "print(\"Cut circuit depths:\", sorted({qc.depth() for qc in isa_experiments}))\n",
        "\n",
        "# ------------------------- Step 3 -------------------------\n",
        "options = SamplerOptions()\n",
        "options.dynamical_decoupling.enable = False\n",
        "options.twirling.enable_gates = False\n",
        "options.twirling.enable_measure = False\n",
        "options.environment.job_tags = [\"TUT_CCPBC\"]\n",
        "shots = 10_000\n",
        "with Batch(backend=backend) as batch:\n",
        "    sampler = SamplerV2(mode=batch, options=options)\n",
        "    cut_job = sampler.run(isa_experiments, shots=shots)\n",
        "    uncut_job = sampler.run([isa_uncut], shots=shots)\n",
        "print(\"Cut job ID:\", cut_job.job_id())\n",
        "print(\"Uncut job ID:\", uncut_job.job_id())\n",
        "cut_result = cut_job.result()\n",
        "uncut_result = uncut_job.result()\n",
        "\n",
        "# ------------------------- Step 4 -------------------------\n",
        "cut_expvals = reconstruct_expectation_values(\n",
        "    cut_result, coefficients, mapped_observables.paulis\n",
        ")\n",
        "uncut_counts = uncut_result[0].data.meas.get_counts()\n",
        "uncut_expvals = [\n",
        "    sampled_expectation_value(uncut_counts, p) for p in observables.paulis\n",
        "]\n",
        "compare_results(cut_expvals, uncut_expvals, num_qubits)\n",
        "qpu_seconds = sum(\n",
        "    job.metrics()[\"usage\"][\"quantum_seconds\"] for job in [cut_job, uncut_job]\n",
        ")\n",
        "print(\n",
        "    f\"Total QPU usage: {qpu_seconds:.1f} seconds ({qpu_seconds / 60:.2f} minutes)\"\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "periodic-20",
      "metadata": {},
      "source": [
        "Nella simulazione registrata su `ibm_boston`, le stime di taglio erano approssimativamente $M_Z=-0.923$ e $C_{ZZ}=0.896$, rispetto a $-0.611$ e $0.516$ per il circuito non tagliato. Entrambe le stime di taglio erano più vicine ai valori ideali di $-1$ e $+1$.\n",
        "\n",
        "Confronta ciascun risultato con il relativo valore ideale e verifica le profondità del circuito e l'utilizzo totale della QPU. Il taglio elimina la necessità di instradare i gate periodici, ma introduce operazioni intermedie e un’ulteriore incertezza statistica. Il rumore dell'hardware e la calibrazione influenzano il confronto; non è garantito alcun miglioramento. Sono necessarie ripetute analisi per valutare se una differenza osservata sia statisticamente significativa.\n",
        "\n",
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * Esplorare [il taglio del circuito per la riduzione della profondità](/docs/tutorials/depth-reduction-with-circuit-cutting) in relazione ad altre interazioni non locali.\n",
        "  * Prova a utilizzare [il metodo del taglio del filo per la stima dei valori attesi,](/docs/tutorials/wire-cutting) al fine di suddividere un circuito in sottosistemi più piccoli.\n",
        "  * Leggi il [materiale esplicativo sul taglio dei circuiti](https://qiskit.github.io/qiskit-addon-cutting/explanation/index.html) per comprendere il compromesso tra il taglio e il sovraccarico di campionamento.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "hours": 1.5,
    "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"
    },
    "qpuSeconds": 102
  },
  "nbformat": 4,
  "nbformat_minor": 5
}