{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Valutazione della fedeltà del processo QFT+M con Orbit, una funzione Qiskit sviluppata da Quantum Elements\"\n",
        "description: \"Stimare la fedeltà del processo QFT seguito dalla misurazione (QFT+M) in funzione delle dimensioni dei circuiti; confrontare le implementazioni unitarie grezze, dinamiche grezze e dinamiche potenziate da Orbit\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore minexp, succ, fontsize, labelsize */}\n",
        "\n",
        "<span id=\"benchmark-qft+m-process-fidelity-with-orbit-a-qiskit-function-by-quantum-elements\" />\n",
        "\n",
        "# Valutazione della fedeltà del processo QFT+M con Orbit, una funzione Qiskit sviluppata da Quantum Elements\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "warning",
      "metadata": {},
      "source": [
        "*Stima del tempo di esecuzione:* 2 minuti su un processore Heron r3. (NOTA: Si tratta solo di una stima. (La durata potrebbe variare.) Per impostazione predefinita, questo tutorial invia tre lavori della funzione Orbit in un unico carico di lavoro in modalità batch del servizio di calcolo \" IBM Quantum \", con 300 PUB per ogni lavoro, per un totale di 900 PUB e 921.600 scatti.\n",
        "\n",
        "*Avviso:* i circuiti dinamici sono attualmente una funzionalità sperimentale e sono soggetti a limitazioni in Quantum Compute [\\[3\\]](#references) che potrebbero causare il fallimento dei lavori. Ad esempio, l'errore 6073 indica che un processo ha superato il limite di memoria dell'hardware di controllo classico [\\[4\\]](#references). Questo notebook riduce tale rischio ripartendo le dimensioni dei circuiti tra tre processi di calcolo quantistico in un unico batch [\\[5\\]](#references). Ogni confronto di dimensioni fisse rimane all'interno di un unico processo, mentre le dimensioni grandi e piccole vengono accoppiate per bilanciare i carichi di lavoro del controllo classico dei processi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "learning",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Completando questo tutorial, imparerai a:\n",
        "\n",
        "* Preparare gli stati del prodotto $\\mathrm{QFT}^\\dagger|x\\rangle$ utilizzati dallo stimatore di fedeltà del processo campionato nella Figura 2a del Rif. [\\[1\\]](#references).\n",
        "* Realizzare implementazioni unitarie e dinamiche equivalenti della trasformata di Fourier quantistica seguita da misurazione (QFT+M).\n",
        "* Selezionare i qubit fisici per i circuiti dinamici utilizzando i dati attuali relativi alla calibrazione e alla connettività.\n",
        "* Confronta le stime di fedeltà del processo QFT+M (unitarie grezze, dinamiche grezze e dinamiche potenziate da Orbit) al variare delle dimensioni del circuito.\n",
        "* Utilizza l'API di transpilazione semplificata di Orbit con `mode=\"raw\"` e `transpilation_mode=\"validate\"`.\n",
        "* Invia più carichi di lavoro Orbit tramite l'API in modalità batch, mantenendo ogni confronto a tre strategie di dimensione fissa all'interno di un unico lavoro.\n",
        "* Esaminare i metadati di Orbit per verificare se siano stati applicati il disaccoppiamento dinamico (DD) e la mitigazione degli errori di misurazione (MEM).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "background",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "La figura 2a del riferimento [\\[1\\]](#references) mette a confronto la fedeltà di processo del canale ideale QFT+M con quella di implementazioni unitarie e dinamiche soggette a rumore. Per un'etichetta campionata sulla base computazionale $x$, il benchmark prepara $\\mathrm{QFT}^\\dagger|x\\rangle$, applica l'implementazione QFT+M con rumore e stima la probabilità $p_x$ di ottenere l'output ideale corrispondente. Questi stati della QFT inversa sono separabili e possono essere preparati in modo efficiente mediante porte di Hadamard e rotazioni di fase virtuali.\n",
        "\n",
        "Per le etichette campionate in modo indipendente $m$, il notebook utilizza lo stimatore non distorto derivato in [\\[1\\]](#references) :\n",
        "\n",
        "$$\n",
        "\\widehat{\\mathcal{F}}_{\\mathrm{proc}} = \\frac{m}{m-1}\\left(\\frac{1}{m}\\sum_{\\ell=1}^{m}\\sqrt{p_{x_\\ell}}\\right)^2 - \\frac{1}{m(m-1)}\\sum_{\\ell=1}^{m}p_{x_\\ell}.\n",
        "$$\n",
        "\n",
        "La costruzione dinamica sostituisce i gate a fase controllata della QFT+M unitaria con misurazioni a metà circuito e rotazioni di fase condizionate classicamente [\\[1\\]](#references). Con la misurazione differita, entrambi i circuiti presentano la stessa distribuzione di uscita ideale. La forma dinamica elimina il requisito del gate a due qubit \"tutti-con-tutti\" e utilizza invece misurazioni intermedie del circuito di tip $O(n)$, con feedforward e senza vincoli di connettività. Anche la misurazione e il feedforward comportano lunghi periodi di inattività per i qubit che non sono ancora stati misurati, rendendo il DD particolarmente rilevante.\n",
        "\n",
        "**Riferimento alla figura 2a.** Questo quaderno segue il protocollo di fedeltà al processo illustrato nell'articolo, ma si tratta di un adattamento didattico incentrato su Orbit piuttosto che di una riproduzione. Ad esempio, mentre nella Figura 2a è stato utilizzato `ibm_kyiv` un dispositivo con 2000 shot, noi utilizziamo un dispositivo `ibm_aachen` moderno con un numero inferiore di shot (1024) per risparmiare tempo di elaborazione della QPU.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "5ae4122d",
      "metadata": {},
      "source": [
        "<span id=\"example-results\" />\n",
        "\n",
        "## Esempi di risultati\n",
        "\n",
        "Il grafico statico riportato di seguito mostra le curve medie di fedeltà al processo ottenute da tre lavori di sviluppo eseguiti in sequenza utilizzando `ibm_aachen` il processo descritto di seguito. Come dimostrato in questo articolo, Orbit è in grado di migliorare notevolmente la qualità dei circuiti dinamici; la QFT dinamica raggiunge i livelli di qualità dei benchmark pubblicati e mostra un miglioramento rispetto alla QFT unitaria standard. Come vedremo, questi risultati derivano da una scelta ottimale e automatizzata dei qubit, dall’inserimento automatico di un meccanismo di disaccoppiamento dinamico (non ottimizzato manualmente per questo problema) e dalla mitigazione degli errori di misurazione. Per divertimento, assicurati di confrontare questi risultati con i tuoi alla fine, soprattutto se scegli un backend diverso.\n",
        "\n",
        "**Nota:** questi risultati rappresentano un esempio illustrativo di un'esecuzione precedente andata a buon fine con Orbit, non una garanzia di prestazioni. I risultati riportati di seguito dovrebbero apparire qualitativamente simili, ma i dettagli dipendono dal dispositivo scelto e dalle sue caratteristiche, in particolare dagli errori di misurazione e di inattività durante l'esecuzione.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1bb67ce9",
      "metadata": {},
      "source": [
        "![Fedeltà del processo QFT su \"ibm\\_aachen\"](https://quantum.cloud.ibm.com/docs/images/tutorials/quantum-elements-orbit/dynamic_qft_orbit_tutorial_aachen_notebook_3job_average.svg)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di seguire questo tutorial, installa le versioni più recenti dei seguenti pacchetti:\n",
        "\n",
        "* `numpy`\n",
        "* `matplotlib`\n",
        "* `qiskit`\n",
        "* `qiskit-ibm-runtime`\n",
        "* `qiskit-ibm-catalog`\n",
        "\n",
        "```bash\n",
        "pip install qiskit qiskit-ibm-runtime qiskit-ibm-catalog numpy matplotlib\n",
        "```\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "setup-md",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n",
        "Effettua l'autenticazione su [IBM Quantum® Platform](), carica `ibm_aachen`e avvia Quantum Elements Orbit dall' [Qiskit Functions Catalog](/functions). L'analisi predefinita valuta 15 dimensioni di circuito, 20 stringhe di bit campionate per ciascuna dimensione e tre strategie. `NUM_BATCH_JOBS=3` ripartisce le dimensioni tra tre lavori in un unico [batch](/docs/guides/run-jobs-batch). Ridurre `N_VALUES` o `M`, oppure aumentare `NUM_BATCH_JOBS`, se un singolo processo del circuito dinamico raggiunge comunque il limite di memoria del controllo classico del backend. Ridurre `SHOTS` quando l'obiettivo è quello di ridurre l'utilizzo in fase di esecuzione piuttosto che il numero o la complessità dei circuiti.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "setup-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stderr",
          "output_type": "stream",
          "text": [
            "qiskit_runtime_service._discover_account:WARNING:2026-07-21 15:57:39,310: Loading account with the given token. A saved account will not be used.\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "{'backend': 'ibm_aachen',\n",
              " 'num_qubits': 156,\n",
              " 'n_values': [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40],\n",
              " 'm': 20,\n",
              " 'shots': 1024,\n",
              " 'num_function_jobs': 3,\n",
              " 'n_groups': [[40, 2, 7, 15, 10], [35, 3, 6, 20, 9], [30, 4, 5, 25, 8]],\n",
              " 'pubs_per_job': [300, 300, 300],\n",
              " 'total_pubs': 900,\n",
              " 'total_shots': 921600}"
            ]
          },
          "execution_count": 15,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "import warnings\n",
        "from collections import Counter, defaultdict\n",
        "\n",
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from qiskit import (\n",
        "    ClassicalRegister,\n",
        "    QuantumCircuit,\n",
        "    QuantumRegister,\n",
        "    transpile,\n",
        ")\n",
        "from qiskit.circuit import IfElseOp\n",
        "from qiskit.synthesis.qft import synth_qft_full\n",
        "from qiskit_ibm_catalog import QiskitFunctionsCatalog\n",
        "from qiskit_ibm_runtime import Batch, QiskitRuntimeService\n",
        "\n",
        "IBM_BACKEND_NAME = \"ibm_aachen\"\n",
        "\n",
        "N_VALUES = [2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 20, 25, 30, 35, 40]\n",
        "M = 20\n",
        "SHOTS = 1024\n",
        "RNG_SEED = 12345\n",
        "OPTIMIZATION_LEVEL = 0\n",
        "NUM_BATCH_JOBS = 3\n",
        "STRATEGY_LABELS = (\"unitary/raw\", \"dynamic/raw\", \"dynamic/orbit\")\n",
        "\n",
        "\n",
        "def balanced_n_groups(\n",
        "    n_values: list[int], num_jobs: int = 3\n",
        ") -> list[list[int]]:\n",
        "    values = sorted(n_values)\n",
        "    if len(set(values)) != len(values):\n",
        "        raise ValueError(\"N_VALUES must not contain duplicates\")\n",
        "    if not 1 <= num_jobs <= len(values):\n",
        "        raise ValueError(\"NUM_BATCH_JOBS must be between 1 and len(N_VALUES)\")\n",
        "\n",
        "    max_group_size = (len(values) + num_jobs - 1) // num_jobs\n",
        "    groups = [[] for _ in range(num_jobs)]\n",
        "    loads = [0] * num_jobs\n",
        "    pair_counts = [0] * num_jobs\n",
        "    remaining = values.copy()\n",
        "\n",
        "    while len(remaining) >= 2:\n",
        "        candidates = [\n",
        "            i\n",
        "            for i, group in enumerate(groups)\n",
        "            if len(group) + 2 <= max_group_size\n",
        "        ]\n",
        "        if not candidates:\n",
        "            break\n",
        "        smallest = remaining.pop(0)\n",
        "        largest = remaining.pop()\n",
        "        job_index = min(\n",
        "            candidates, key=lambda i: (loads[i], len(groups[i]), i)\n",
        "        )\n",
        "        pair = (\n",
        "            [largest, smallest]\n",
        "            if pair_counts[job_index] % 2 == 0\n",
        "            else [smallest, largest]\n",
        "        )\n",
        "        groups[job_index].extend(pair)\n",
        "        loads[job_index] += smallest + largest\n",
        "        pair_counts[job_index] += 1\n",
        "\n",
        "    while remaining:\n",
        "        value = remaining.pop()\n",
        "        candidates = [\n",
        "            i for i, group in enumerate(groups) if len(group) < max_group_size\n",
        "        ]\n",
        "        job_index = min(\n",
        "            candidates, key=lambda i: (loads[i], len(groups[i]), i)\n",
        "        )\n",
        "        groups[job_index].append(value)\n",
        "        loads[job_index] += value\n",
        "\n",
        "    return groups\n",
        "\n",
        "\n",
        "N_GROUPS = balanced_n_groups(N_VALUES, NUM_BATCH_JOBS)\n",
        "\n",
        "service = QiskitRuntimeService(channel=\"ibm_quantum_platform\")\n",
        "backend = service.backend(IBM_BACKEND_NAME)\n",
        "if \"if_else\" not in backend.target.operation_names:\n",
        "    backend.target.add_instruction(IfElseOp, name=\"if_else\")\n",
        "\n",
        "catalog = QiskitFunctionsCatalog(channel=\"ibm_quantum_platform\")\n",
        "quantum_elements_orbit = catalog.load(\"quantum-elements/orbit\")\n",
        "if quantum_elements_orbit is None:\n",
        "    raise RuntimeError(\n",
        "        \"Quantum Elements Orbit is not enabled for this IBM Quantum instance.\"\n",
        "    )\n",
        "\n",
        "required_qubits = max(N_VALUES)\n",
        "if backend.num_qubits < required_qubits:\n",
        "    raise ValueError(\n",
        "        f\"Backend {backend.name} has {backend.num_qubits} qubits, \"\n",
        "        f\"but this benchmark needs at least {required_qubits}.\"\n",
        "    )\n",
        "\n",
        "{\n",
        "    \"backend\": backend.name,\n",
        "    \"num_qubits\": backend.num_qubits,\n",
        "    \"n_values\": N_VALUES,\n",
        "    \"m\": M,\n",
        "    \"shots\": SHOTS,\n",
        "    \"num_function_jobs\": NUM_BATCH_JOBS,\n",
        "    \"n_groups\": N_GROUPS,\n",
        "    \"pubs_per_job\": [\n",
        "        len(group) * M * len(STRATEGY_LABELS) for group in N_GROUPS\n",
        "    ],\n",
        "    \"total_pubs\": len(N_VALUES) * M * len(STRATEGY_LABELS),\n",
        "    \"total_shots\": len(N_VALUES) * M * len(STRATEGY_LABELS) * SHOTS,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "build-md",
      "metadata": {},
      "source": [
        "<span id=\"build-qft+m-circuits\" />\n",
        "\n",
        "## Costruire circuiti QFT+M\n",
        "\n",
        "Per ogni numero intero campionato $x$, `bit_inv_qft` si prepara lo stato di prodotto $\\mathrm{QFT}^\\dagger|x\\rangle$ mediante operazioni di Hadamard seguite da rotazioni di fase. Il manuale riporta quindi o la teoria quantistica dei campi unitaria standard oppure la sua equivalente teoria semiclassica dinamica QFT+M.\n",
        "\n",
        "Entrambe le implementazioni tralasciano la rete di scambio finale. L'ordine di visualizzazione dei bit classici in Qiskit fa sì che la stringa misurata prevista sia l'inverso della rappresentazione binaria con riempimento a zero di $x$, che è codificata da `format(x, f\"0{n}b\")[::-1]`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 16,
      "id": "circuits-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "def bit_inv_qft(circuit: QuantumCircuit, x: int, conv: str = \"LSB\") -> None:\n",
        "    num_qubits = circuit.num_qubits\n",
        "    circuit.h(range(num_qubits))\n",
        "    for j in range(num_qubits):\n",
        "        phase = (\n",
        "            2 * np.pi * x / 2 ** (num_qubits - j)\n",
        "            if conv == \"LSB\"\n",
        "            else 2 * np.pi * x / 2 ** (j + 1)\n",
        "        )\n",
        "        circuit.p(-phase, j)\n",
        "\n",
        "\n",
        "def build_unitary_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:\n",
        "    if not 0 <= x < 2**num_qubits:\n",
        "        raise ValueError(\n",
        "            f\"x={x} is outside the {num_qubits}-qubit basis range\"\n",
        "        )\n",
        "    qreg = QuantumRegister(num_qubits, \"q\")\n",
        "    creg = ClassicalRegister(num_qubits, \"c\")\n",
        "    circuit = QuantumCircuit(qreg, creg, name=f\"unitary_qft_{num_qubits}q\")\n",
        "    bit_inv_qft(circuit, x)\n",
        "    circuit.append(\n",
        "        synth_qft_full(num_qubits, do_swaps=False), range(num_qubits)\n",
        "    )\n",
        "    circuit.measure(range(num_qubits), range(num_qubits))\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def _warn_if_precision_loss(max_num_entanglements: int) -> None:\n",
        "    if max_num_entanglements > -np.finfo(float).minexp:\n",
        "        warnings.warn(\n",
        "            \"precision loss in QFT.\"\n",
        "            f\" The rotation needed to represent {max_num_entanglements} entanglements\"\n",
        "            \" is smaller than the smallest normal floating-point number.\",\n",
        "            category=RuntimeWarning,\n",
        "            stacklevel=4,\n",
        "        )\n",
        "\n",
        "\n",
        "def synth_dynamic_qft(\n",
        "    circuit: QuantumCircuit, *, do_swaps: bool = False\n",
        ") -> QuantumCircuit:\n",
        "    num_qubits = circuit.num_qubits\n",
        "    creg = circuit.cregs[0]\n",
        "    _warn_if_precision_loss(num_qubits - 1)\n",
        "\n",
        "    for j in reversed(range(num_qubits)):\n",
        "        circuit.h(j)\n",
        "        circuit.measure([j], [j])\n",
        "\n",
        "        if j > 0:\n",
        "            with circuit.if_test((creg[j], 1)):\n",
        "                for k in reversed(range(j)):\n",
        "                    circuit.p(np.pi * (2.0 ** (k - j)), k)\n",
        "\n",
        "    if do_swaps:\n",
        "        for i in range(num_qubits // 2):\n",
        "            circuit.swap(i, num_qubits - i - 1)\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def build_dynamic_qft_circuit(num_qubits: int, x: int) -> QuantumCircuit:\n",
        "    if not 0 <= x < 2**num_qubits:\n",
        "        raise ValueError(\n",
        "            f\"x={x} is outside the {num_qubits}-qubit basis range\"\n",
        "        )\n",
        "    qreg = QuantumRegister(num_qubits, \"q\")\n",
        "    creg = ClassicalRegister(num_qubits, \"c\")\n",
        "    circuit = QuantumCircuit(qreg, creg, name=f\"dynamic_qft_{num_qubits}q\")\n",
        "    bit_inv_qft(circuit, x)\n",
        "    synth_dynamic_qft(circuit, do_swaps=False)\n",
        "    return circuit\n",
        "\n",
        "\n",
        "def target_output_bitstring(x: int, n_qubits: int) -> str:\n",
        "    return format(int(x), f\"0{n_qubits}b\")[::-1]\n",
        "\n",
        "\n",
        "def process_fidelity_from_success_probabilities(\n",
        "    success_probabilities: list[float],\n",
        ") -> float:\n",
        "    m = len(success_probabilities)\n",
        "    if m <= 1:\n",
        "        raise ValueError(\n",
        "            \"m must be larger than 1 for the process-fidelity estimator\"\n",
        "        )\n",
        "    succ = np.asarray(success_probabilities, dtype=float)\n",
        "    return float(\n",
        "        (m / (m - 1)) * (np.mean(np.sqrt(succ)) ** 2)\n",
        "        - np.sum(succ) / (m * (m - 1))\n",
        "    )"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "select-md",
      "metadata": {},
      "source": [
        "<span id=\"select-dynamic-circuit-physical-qubits\" />\n",
        "\n",
        "## Seleziona i qubit fisici a circuito dinamico\n",
        "\n",
        "L'implementazione dinamica non richiede porte a due qubit, quindi i suoi qubit fisici non devono necessariamente formare un sottografo connesso. Per ciascuna dimensione del circuito, il selettore classifica i qubit del backend attuali utilizzando un punteggio ponderato per l’80% in base a un errore di lettura più basso e per il 10% ciascuno in base a valori più elevati di “ $T_1$ ” e “ $T_2$ ”. Innanzitutto, sceglie i qubit con il punteggio più alto che non presentino accoppiamenti diretti tra loro, quando possibile, in modo da ridurre l’esposizione al crosstalk tra vicini più prossimi, e poi riempie le posizioni rimanenti in base al punteggio.\n",
        "\n",
        "Le `dynamic/raw` varianti e `dynamic/orbit` utilizzano esattamente lo stesso layout selezionato per una determinata dimensione, rendendo il loro confronto dipendente dal layout. Il `unitary/raw` circuito viene invece mappato e instradato dal transpiler poiché richiede una connettività a due qubit. Questa selezione basata sulla calibrazione in fase di esecuzione è specifica di questo tutorial; non si tratta della configurazione fissa a 40 qubit `ibm_kyiv` utilizzata per gli esperimenti illustrati nella Figura 2a dell'articolo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 17,
      "id": "select-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "The top 3 qubits (according to our scoring): \n",
            "[{'qubit': 0, 't1': 0.0002514242577986401, 't2': 0.00037559012475638467, 'measurement_error': 0.0028076171875, 'score': 1.0}, {'qubit': 20, 't1': 0.0002526252407383437, 't2': 0.00038493543771861573, 'measurement_error': 0.00390625, 'score': 1.0}, {'qubit': 25, 't1': 0.0002712841332005567, 't2': 0.00025793268824583597, 'measurement_error': 0.0040283203125, 'score': 1.0}]\n",
            "Worst  3 qubits (according to our scoring): \n",
            "[{'qubit': 146, 't1': 7.619772882181663e-05, 't2': 0.00014166983578724752, 'measurement_error': 0.0802001953125, 'score': 0.05893378230453208}, {'qubit': 51, 't1': 0.00014200221819602618, 't2': 1.930870507157441e-06, 'measurement_error': 0.054443359375, 'score': 0.04600110909801309}, {'qubit': 35, 't1': 7.116087485472031e-05, 't2': 9.463696242928626e-05, 'measurement_error': 0.14501953125, 'score': 0.03289891864200328}]\n"
          ]
        }
      ],
      "source": [
        "def value_from_property(raw):\n",
        "    if raw is None:\n",
        "        return None\n",
        "    if isinstance(raw, tuple):\n",
        "        return raw[0]\n",
        "    return getattr(raw, \"value\", raw)\n",
        "\n",
        "\n",
        "def qubit_property_value(properties, qubit: int, *names: str) -> float | None:\n",
        "    for name in names:\n",
        "        try:\n",
        "            value = value_from_property(\n",
        "                properties.qubit_property(qubit, name)\n",
        "            )\n",
        "        except Exception:\n",
        "            value = None\n",
        "        if value is not None:\n",
        "            return float(value)\n",
        "    return None\n",
        "\n",
        "\n",
        "def measurement_error(properties, qubit: int) -> float | None:\n",
        "    readout = qubit_property_value(properties, qubit, \"readout_error\")\n",
        "    if readout is not None:\n",
        "        return readout\n",
        "    p01 = qubit_property_value(properties, qubit, \"prob_meas0_prep1\")\n",
        "    p10 = qubit_property_value(properties, qubit, \"prob_meas1_prep0\")\n",
        "    if p01 is not None and p10 is not None:\n",
        "        return 0.5 * (p01 + p10)\n",
        "    return None\n",
        "\n",
        "\n",
        "def coupling_edges(backend) -> list[tuple[int, int]]:\n",
        "    coupling_map = getattr(backend, \"coupling_map\", None)\n",
        "    if coupling_map is not None:\n",
        "        try:\n",
        "            return [(int(a), int(b)) for a, b in coupling_map.get_edges()]\n",
        "        except Exception:\n",
        "            pass\n",
        "    built = backend.target.build_coupling_map()\n",
        "    return [(int(a), int(b)) for a, b in built.get_edges()]\n",
        "\n",
        "\n",
        "def neighbor_map(backend) -> dict[int, set[int]]:\n",
        "    neighbors = {qubit: set() for qubit in range(backend.num_qubits)}\n",
        "    for a, b in coupling_edges(backend):\n",
        "        neighbors[a].add(b)\n",
        "        neighbors[b].add(a)\n",
        "    return neighbors\n",
        "\n",
        "\n",
        "def anchored_score(\n",
        "    value: float | None, *, good: float, bad: float, higher_is_better: bool\n",
        ") -> float:\n",
        "    if value is None:\n",
        "        return 0.0\n",
        "    if higher_is_better:\n",
        "        low, high = sorted((bad, good))\n",
        "        score = (value - low) / (high - low)\n",
        "    else:\n",
        "        low, high = sorted((good, bad))\n",
        "        score = (high - value) / (high - low)\n",
        "    return float(min(1.0, max(0.0, score)))\n",
        "\n",
        "\n",
        "def qubit_metrics(backend) -> list[dict]:\n",
        "    properties = backend.properties()\n",
        "    rows = []\n",
        "    for qubit in range(backend.num_qubits):\n",
        "        t1 = qubit_property_value(properties, qubit, \"T1\", \"t1\")\n",
        "        t2 = qubit_property_value(properties, qubit, \"T2\", \"t2\")\n",
        "        meas_error = measurement_error(properties, qubit)\n",
        "        measurement_score = anchored_score(\n",
        "            meas_error, good=0.005, bad=0.05, higher_is_better=False\n",
        "        )\n",
        "        t1_score = anchored_score(\n",
        "            t1, good=0.00025, bad=0.00005, higher_is_better=True\n",
        "        )\n",
        "        t2_score = anchored_score(\n",
        "            t2, good=0.00025, bad=0.00005, higher_is_better=True\n",
        "        )\n",
        "        rows.append(\n",
        "            {\n",
        "                \"qubit\": qubit,\n",
        "                \"t1\": t1,\n",
        "                \"t2\": t2,\n",
        "                \"measurement_error\": meas_error,\n",
        "                \"score\": 0.8 * measurement_score\n",
        "                + 0.1 * t1_score\n",
        "                + 0.1 * t2_score,\n",
        "            }\n",
        "        )\n",
        "    return sorted(rows, key=lambda row: row[\"score\"], reverse=True)\n",
        "\n",
        "\n",
        "def select_dynamic_qubits(backend, n_qubits: int) -> list[int]:\n",
        "    ranked = qubit_metrics(backend)\n",
        "    neighbors = neighbor_map(backend)\n",
        "    selected = []\n",
        "    blocked = set()\n",
        "    for row in ranked:\n",
        "        qubit = row[\"qubit\"]\n",
        "        if qubit in blocked:\n",
        "            continue\n",
        "        selected.append(qubit)\n",
        "        blocked.add(qubit)\n",
        "        blocked.update(neighbors.get(qubit, set()))\n",
        "        if len(selected) == n_qubits:\n",
        "            return selected\n",
        "\n",
        "    for row in ranked:\n",
        "        qubit = row[\"qubit\"]\n",
        "        if qubit not in selected:\n",
        "            selected.append(qubit)\n",
        "        if len(selected) == n_qubits:\n",
        "            return selected\n",
        "    raise RuntimeError(f\"Could not select {n_qubits} physical qubits\")\n",
        "\n",
        "\n",
        "print(\"The top 3 qubits (according to our scoring): \")\n",
        "print(qubit_metrics(backend)[0:3])\n",
        "print(\"Worst  3 qubits (according to our scoring): \")\n",
        "print(qubit_metrics(backend)[-3:])"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "prepare-md",
      "metadata": {},
      "source": [
        "<span id=\"prepare-the-benchmark-pubs\" />\n",
        "\n",
        "## Preparare i PUB di riferimento\n",
        "\n",
        "Per ogni `(N, x)` coppia, il notebook compila prima i circuiti logici e crea un Sampler PUB per ciascuna strategia:\n",
        "\n",
        "* `unitary/raw`: QFT+M unitaria sul layout selezionato dal transpiler, con instradamento secondo necessità e senza Orbit DD né MEM.\n",
        "* `dynamic/raw`: QFT+M dinamica sui qubit fisici selezionati tramite calibrazione, senza Orbit DD né MEM.\n",
        "* `dynamic/orbit`: lo stesso circuito dinamico transpilato sugli stessi qubit fisici, con Orbit DD e MEM abilitati.\n",
        "\n",
        "L' PUB, ottimizzato da Orbit, utilizza `transpilation_mode=\"validate\"` poiché la sua mappatura è già stata selezionata. Orbit convalida il circuito fisico fornito anziché rimapparlo, quindi applica la propria pipeline DD e MEM. Poiché solo la curva dinamica potenziata richiede l'uso del MEM, ciò non dovrebbe essere interpretato come un confronto isolato tra DD e non-DD.\n",
        "\n",
        "I PUB, le opzioni \"per- PUB \" e i record dei risultati vengono memorizzati in base all'indice dei lavori batch. `unitary/raw`Per ogni $N$ fisso, i PUB, `dynamic/raw`, e `dynamic/orbit` vengono raggruppati nello stesso lavoro. L'helper di raggruppamento abbina circuiti di grandi e piccole dimensioni, ne alterna l'ordine e bilancia la somma dell' $N$ e tra i tre lavori, come semplice indicatore del carico di lavoro del controllo classico.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 31,
      "id": "prepare-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'num_function_jobs': 3,\n",
              " 'n_groups': {0: [40, 2, 7, 15, 10],\n",
              "  1: [35, 3, 6, 20, 9],\n",
              "  2: [30, 4, 5, 25, 8]},\n",
              " 'n_load_per_job': {0: 74, 1: 73, 2: 72},\n",
              " 'pubs_per_job': {0: 300, 1: 300, 2: 300},\n",
              " 'expected_executions_per_job': {0: 307200, 1: 307200, 2: 307200},\n",
              " 'first_pub_record_by_job': {0: {'job_index': 0,\n",
              "   'n_qubits': 40,\n",
              "   'target_decimal': 853235401719,\n",
              "   'target_bitstring': '1110111111000000110100110001010101100011',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 4778,\n",
              "   'transpiled_size': 28259},\n",
              "  1: {'job_index': 1,\n",
              "   'n_qubits': 35,\n",
              "   'target_decimal': 26888951661,\n",
              "   'target_bitstring': '10110110111010110010110101000010011',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 3614,\n",
              "   'transpiled_size': 21204},\n",
              "  2: {'job_index': 2,\n",
              "   'n_qubits': 30,\n",
              "   'target_decimal': 620442965,\n",
              "   'target_bitstring': '101010101010110011011111001001',\n",
              "   'label': 'unitary/raw',\n",
              "   'pub_options': {'mode': 'raw'},\n",
              "   'dynamic_qubits': None,\n",
              "   'transpiled_depth': 2835,\n",
              "   'transpiled_size': 15078}},\n",
              " 'largest_dynamic_qubit_set': [0,\n",
              "  20,\n",
              "  25,\n",
              "  27,\n",
              "  33,\n",
              "  59,\n",
              "  74,\n",
              "  80,\n",
              "  95,\n",
              "  144,\n",
              "  151,\n",
              "  155,\n",
              "  79,\n",
              "  90,\n",
              "  60,\n",
              "  68,\n",
              "  114,\n",
              "  107,\n",
              "  13,\n",
              "  126,\n",
              "  133,\n",
              "  103,\n",
              "  3,\n",
              "  87,\n",
              "  53,\n",
              "  41,\n",
              "  130,\n",
              "  5,\n",
              "  98,\n",
              "  135,\n",
              "  153,\n",
              "  15,\n",
              "  116,\n",
              "  45,\n",
              "  7,\n",
              "  48,\n",
              "  136,\n",
              "  11,\n",
              "  147,\n",
              "  77]}"
            ]
          },
          "execution_count": 31,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "strategy_options = {\n",
        "    \"unitary/raw\": {\"mode\": \"raw\"},\n",
        "    \"dynamic/raw\": {\"mode\": \"raw\"},\n",
        "    \"dynamic/orbit\": {\"mode\": \"orbit\", \"transpilation_mode\": \"validate\"},\n",
        "}\n",
        "rng = np.random.default_rng(RNG_SEED)\n",
        "pubs_by_job = [[] for _ in N_GROUPS]\n",
        "pub_options_by_job = [[] for _ in N_GROUPS]\n",
        "pub_records_by_job = [[] for _ in N_GROUPS]\n",
        "layout_summary = {}\n",
        "\n",
        "target_decimals_by_n = {\n",
        "    n_qubits: [int(x) for x in rng.integers(0, 2**n_qubits, size=M)]\n",
        "    for n_qubits in N_VALUES\n",
        "}\n",
        "\n",
        "for job_index, n_group in enumerate(N_GROUPS):\n",
        "    for n_qubits in n_group:\n",
        "        dynamic_qubits = select_dynamic_qubits(backend, n_qubits)\n",
        "        layout_summary[str(n_qubits)] = {\"dynamic_qubits\": dynamic_qubits}\n",
        "\n",
        "        for x in target_decimals_by_n[n_qubits]:\n",
        "            target_bitstring = target_output_bitstring(x, n_qubits)\n",
        "            unitary_logical = build_unitary_qft_circuit(n_qubits, x)\n",
        "            dynamic_logical = build_dynamic_qft_circuit(n_qubits, x)\n",
        "\n",
        "            unitary_transpiled = transpile(\n",
        "                unitary_logical,\n",
        "                backend=backend,\n",
        "                optimization_level=OPTIMIZATION_LEVEL,\n",
        "                seed_transpiler=RNG_SEED,\n",
        "            )\n",
        "            dynamic_transpiled = transpile(\n",
        "                dynamic_logical,\n",
        "                backend=backend,\n",
        "                optimization_level=OPTIMIZATION_LEVEL,\n",
        "                seed_transpiler=RNG_SEED,\n",
        "                initial_layout=dynamic_qubits,\n",
        "            )\n",
        "\n",
        "            circuits_by_label = {\n",
        "                \"unitary/raw\": unitary_transpiled,\n",
        "                \"dynamic/raw\": dynamic_transpiled,\n",
        "                \"dynamic/orbit\": dynamic_transpiled,\n",
        "            }\n",
        "            for label in STRATEGY_LABELS:\n",
        "                circuit = circuits_by_label[label]\n",
        "                options = dict(strategy_options[label])\n",
        "                pubs_by_job[job_index].append((circuit, None, SHOTS))\n",
        "                pub_options_by_job[job_index].append(options)\n",
        "                pub_records_by_job[job_index].append(\n",
        "                    {\n",
        "                        \"job_index\": job_index,\n",
        "                        \"n_qubits\": n_qubits,\n",
        "                        \"target_decimal\": x,\n",
        "                        \"target_bitstring\": target_bitstring,\n",
        "                        \"label\": label,\n",
        "                        \"pub_options\": options,\n",
        "                        \"dynamic_qubits\": (\n",
        "                            dynamic_qubits\n",
        "                            if label.startswith(\"dynamic/\")\n",
        "                            else None\n",
        "                        ),\n",
        "                        \"transpiled_depth\": circuit.depth(),\n",
        "                        \"transpiled_size\": circuit.size(),\n",
        "                    }\n",
        "                )\n",
        "\n",
        "{\n",
        "    \"num_function_jobs\": len(N_GROUPS),\n",
        "    \"n_groups\": {\n",
        "        job_index: group for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"n_load_per_job\": {\n",
        "        job_index: sum(group) for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"pubs_per_job\": {\n",
        "        job_index: len(pubs) for job_index, pubs in enumerate(pubs_by_job)\n",
        "    },\n",
        "    \"expected_executions_per_job\": {\n",
        "        job_index: len(pubs) * SHOTS\n",
        "        for job_index, pubs in enumerate(pubs_by_job)\n",
        "    },\n",
        "    \"first_pub_record_by_job\": {\n",
        "        job_index: records[0]\n",
        "        for job_index, records in enumerate(pub_records_by_job)\n",
        "    },\n",
        "    \"largest_dynamic_qubit_set\": layout_summary[str(max(N_VALUES))][\n",
        "        \"dynamic_qubits\"\n",
        "    ],\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "run-md",
      "metadata": {},
      "source": [
        "<span id=\"run-the-benchmark\" />\n",
        "\n",
        "## Esegui il benchmark\n",
        "\n",
        "Crea un [batch](/docs/guides/run-jobs-batch), quindi invia tre lavori della funzione Orbit al suo interno.\n",
        "\n",
        "I lavori sono suddivisi in gruppi in base al numero di qubit, in modo da poter confrontare tutte e tre le strategie per un dato $N$. In altre parole, tutti i 60 PUB per un dato $N$ — 20 input campionati moltiplicati per le tre strategie — vengono quindi eseguiti nello stesso lavoro e possono così essere confrontati nel modo più equo possibile (altrimenti, se eseguiti in lavori diversi, il dispositivo potrebbe subire una deriva mentre è in coda). I gruppi predefiniti combinano circuiti grandi e piccoli e contengono ciascuno 300 PUB, riducendo la probabilità che un singolo lavoro accumuli tutti i programmi dinamici più grandi, pur preservando i confronti all'interno del singolo lavoro.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 32,
      "id": "run-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',\n",
              " 'jobs': {0: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [40, 2, 7, 15, 10],\n",
              "   'num_pubs': 300},\n",
              "  1: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '4f046fd4-80e4-460b-87c7-e7252691f764',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [35, 3, 6, 20, 9],\n",
              "   'num_pubs': 300},\n",
              "  2: {'backend': 'ibm_aachen',\n",
              "   'function_job_id': '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be',\n",
              "   'status': 'QUEUED',\n",
              "   'n_values': [30, 4, 5, 25, 8],\n",
              "   'num_pubs': 300}}}"
            ]
          },
          "execution_count": 32,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "runtime_batch = Batch(backend=backend)\n",
        "jobs = []\n",
        "try:\n",
        "    for job_index, pubs in enumerate(pubs_by_job):\n",
        "        jobs.append(\n",
        "            quantum_elements_orbit.run(\n",
        "                primitive=\"sampler\",\n",
        "                pubs=pubs,\n",
        "                backend_name=backend.name,\n",
        "                options={\n",
        "                    \"pub_options\": pub_options_by_job[job_index],\n",
        "                    \"save_backend_info\": True,\n",
        "                },\n",
        "            )\n",
        "        )\n",
        "except Exception:\n",
        "    runtime_batch.close()\n",
        "    raise\n",
        "\n",
        "{\n",
        "    \"runtime_batch_id\": runtime_batch.session_id,\n",
        "    \"jobs\": {\n",
        "        job_index: {\n",
        "            \"backend\": backend.name,\n",
        "            \"function_job_id\": job.job_id,\n",
        "            \"status\": job.status(),\n",
        "            \"n_values\": N_GROUPS[job_index],\n",
        "            \"num_pubs\": len(pubs_by_job[job_index]),\n",
        "        }\n",
        "        for job_index, job in enumerate(jobs)\n",
        "    },\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "results-md",
      "metadata": {},
      "source": [
        "<span id=\"retrieve-results-and-compute-process-fidelity\" />\n",
        "\n",
        "## Recuperare i risultati e calcolare la fedeltà del processo\n",
        "\n",
        "Recuperare e convalidare in modo indipendente il risultato di ciascun gruppo di qubit, quindi unire i tre flussi di lavoro tramite i relativi record indicizzati in base al job. Il batch rimane aperto finché non vengono richiesti tutti i risultati delle funzioni e viene chiuso in un `finally` blocco dopo che è stato effettuato il tentativo di esecuzione di ogni job. Per ogni PUB, $p_x$ è la probabilità assegnata alla stringa di bit attesa. `dynamic/orbit``extract_counts` legge i conteggi restituiti al chiamante; in questo caso, si tratta dei conteggi corretti in base al MEM quando la mitigazione ha esito positivo. `extract_raw_counts` recupera inoltre i conteggi non mitigati corrispondenti registrati nei metadati di Orbit. Il codice raggruppa i 20 valori di $p_x$ per ciascuna `(N, label)` coppia e applica lo stimatore presentato in precedenza.\n",
        "\n",
        "Il dizionario rappresentato `process_fidelity` graficamente utilizza quindi i conteggi grezzi per `unitary/raw` e `dynamic/raw`, ma i conteggi corretti con il metodo MEM per `dynamic/orbit`. Il dizionario parallelo `raw_process_fidelity` conserva un calcolo non attenuato per ogni strategia ed è utile quando si desidera isolare l'effetto del MEM dal resto della pipeline di Orbit. MEM corregge l'istogramma di uscita restituito; non può modificare retroattivamente il risultato di una misurazione effettuata a metà circuito che è già stato utilizzato dal feedforward in tempo reale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 35,
      "id": "results-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'runtime_batch_id': '80120e36-436d-46c7-96c9-597ec86060c1',\n",
              " 'function_job_ids': {0: '2b6b05ac-0136-40f0-94bf-ade5658f5f4f',\n",
              "  1: '4f046fd4-80e4-460b-87c7-e7252691f764',\n",
              "  2: '6d70d64d-fa38-4ca2-9cbd-ffda5d8c99be'},\n",
              " 'n_groups': {0: [40, 2, 7, 15, 10],\n",
              "  1: [35, 3, 6, 20, 9],\n",
              "  2: [30, 4, 5, 25, 8]},\n",
              " 'process_fidelity': {'2': {'dynamic/orbit': 0.9870551835473073,\n",
              "   'dynamic/raw': 0.9912537998030566,\n",
              "   'unitary/raw': 0.9884650767434809},\n",
              "  '3': {'dynamic/orbit': 0.9662998634131841,\n",
              "   'dynamic/raw': 0.9699631603283018,\n",
              "   'unitary/raw': 0.9388637172865901},\n",
              "  '4': {'dynamic/orbit': 0.9271266520750502,\n",
              "   'dynamic/raw': 0.7334377020091254,\n",
              "   'unitary/raw': 0.9010122207121433},\n",
              "  '5': {'dynamic/orbit': 0.8883501513887149,\n",
              "   'dynamic/raw': 0.6577660260669806,\n",
              "   'unitary/raw': 0.7806443417987445},\n",
              "  '6': {'dynamic/orbit': 0.8524225652033044,\n",
              "   'dynamic/raw': 0.4444025126308521,\n",
              "   'unitary/raw': 0.7167426842521228},\n",
              "  '7': {'dynamic/orbit': 0.832962085697061,\n",
              "   'dynamic/raw': 0.2253787798698553,\n",
              "   'unitary/raw': 0.5746335601063436},\n",
              "  '8': {'dynamic/orbit': 0.7881895956180588,\n",
              "   'dynamic/raw': 0.16909516699831612,\n",
              "   'unitary/raw': 0.5408263851227074},\n",
              "  '9': {'dynamic/orbit': 0.7422635627368794,\n",
              "   'dynamic/raw': 0.0242474245097341,\n",
              "   'unitary/raw': 0.4855953298367578},\n",
              "  '10': {'dynamic/orbit': 0.7002274273149545,\n",
              "   'dynamic/raw': 0.033718865729016285,\n",
              "   'unitary/raw': 0.3607634828181049},\n",
              "  '15': {'dynamic/orbit': 0.4694995355699914,\n",
              "   'dynamic/raw': 7.70970394736842e-05,\n",
              "   'unitary/raw': 0.054582117352985286},\n",
              "  '20': {'dynamic/orbit': 0.24118032284867608,\n",
              "   'dynamic/raw': 4.235164736271502e-22,\n",
              "   'unitary/raw': 0.0},\n",
              "  '25': {'dynamic/orbit': 0.027122712989729438,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '30': {'dynamic/orbit': 0.0003581886014704875,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}},\n",
              " 'mean_success_probability': {'2': {'dynamic/orbit': 0.987060546875,\n",
              "   'dynamic/raw': 0.991259765625,\n",
              "   'unitary/raw': 0.9884765625},\n",
              "  '3': {'dynamic/orbit': 0.96630859375,\n",
              "   'dynamic/raw': 0.969970703125,\n",
              "   'unitary/raw': 0.939013671875},\n",
              "  '4': {'dynamic/orbit': 0.9271484375,\n",
              "   'dynamic/raw': 0.7337890625,\n",
              "   'unitary/raw': 0.901318359375},\n",
              "  '5': {'dynamic/orbit': 0.88837890625,\n",
              "   'dynamic/raw': 0.657861328125,\n",
              "   'unitary/raw': 0.78115234375},\n",
              "  '6': {'dynamic/orbit': 0.85244140625,\n",
              "   'dynamic/raw': 0.44453125,\n",
              "   'unitary/raw': 0.71728515625},\n",
              "  '7': {'dynamic/orbit': 0.8330078125,\n",
              "   'dynamic/raw': 0.22568359375,\n",
              "   'unitary/raw': 0.575390625},\n",
              "  '8': {'dynamic/orbit': 0.788232421875,\n",
              "   'dynamic/raw': 0.169189453125,\n",
              "   'unitary/raw': 0.541796875},\n",
              "  '9': {'dynamic/orbit': 0.742333984375,\n",
              "   'dynamic/raw': 0.0244140625,\n",
              "   'unitary/raw': 0.487353515625},\n",
              "  '10': {'dynamic/orbit': 0.70029296875,\n",
              "   'dynamic/raw': 0.033935546875,\n",
              "   'unitary/raw': 0.363037109375},\n",
              "  '15': {'dynamic/orbit': 0.4697265625,\n",
              "   'dynamic/raw': 0.00029296875,\n",
              "   'unitary/raw': 0.055615234375},\n",
              "  '20': {'dynamic/orbit': 0.241357421875,\n",
              "   'dynamic/raw': 4.8828125e-05,\n",
              "   'unitary/raw': 0.0},\n",
              "  '25': {'dynamic/orbit': 0.041015625, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '30': {'dynamic/orbit': 0.0013671875,\n",
              "   'dynamic/raw': 0.0,\n",
              "   'unitary/raw': 0.0},\n",
              "  '35': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0},\n",
              "  '40': {'dynamic/orbit': 0.0, 'dynamic/raw': 0.0, 'unitary/raw': 0.0}}}"
            ]
          },
          "execution_count": 35,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "def extract_counts(pub_result) -> dict[str, int]:\n",
        "    data = getattr(pub_result, \"data\", None)\n",
        "    if data is None:\n",
        "        raise TypeError(\"pub_result.data is missing\")\n",
        "\n",
        "    for name in dir(data):\n",
        "        if name.startswith(\"_\"):\n",
        "            continue\n",
        "        register = getattr(data, name)\n",
        "        get_counts = getattr(register, \"get_counts\", None)\n",
        "        if callable(get_counts):\n",
        "            counts = get_counts()\n",
        "            if counts:\n",
        "                return counts\n",
        "\n",
        "    raise TypeError(\n",
        "        \"No classical register with get_counts() found in pub_result.data\"\n",
        "    )\n",
        "\n",
        "\n",
        "def extract_raw_counts(pub_result) -> dict[str, int]:\n",
        "    orbit_metadata = pub_result.metadata.get(\"quantum_elements_orbit\", {})\n",
        "    mem_report = orbit_metadata.get(\"measurementErrorMitigation\", {})\n",
        "    return mem_report.get(\"rawCounts\") or extract_counts(pub_result)\n",
        "\n",
        "\n",
        "def probability_for_bitstring(\n",
        "    counts: dict[str, int], bitstring: str, n_qubits: int\n",
        ") -> float:\n",
        "    total = sum(counts.values())\n",
        "    if total <= 0:\n",
        "        return 0.0\n",
        "    normalized = Counter()\n",
        "    for measured, count in counts.items():\n",
        "        key = measured.replace(\" \", \"\")[-n_qubits:].zfill(n_qubits)\n",
        "        normalized[key] += count\n",
        "    return float(normalized.get(bitstring, 0) / total)\n",
        "\n",
        "\n",
        "results_by_job = {}\n",
        "job_failures = []\n",
        "try:\n",
        "    for job_index, job in enumerate(jobs):\n",
        "        try:\n",
        "            job_result = job.result()\n",
        "        except Exception as exc:\n",
        "            job_logs = getattr(job, \"logs\", lambda: \"\")()\n",
        "            if job_logs:\n",
        "                print(f\"Logs for job {job_index} ({job.job_id}):\\n{job_logs}\")\n",
        "            job_failures.append(\n",
        "                f\"job {job_index} ({job.job_id}) failed: {type(exc).__name__}: {exc}\"\n",
        "            )\n",
        "            continue\n",
        "\n",
        "        expected_results = len(pub_records_by_job[job_index])\n",
        "        if len(job_result) != expected_results:\n",
        "            job_failures.append(\n",
        "                f\"job {job_index} ({job.job_id}) returned {len(job_result)} PUB results; \"\n",
        "                f\"expected {expected_results}\"\n",
        "            )\n",
        "            continue\n",
        "        results_by_job[job_index] = job_result\n",
        "finally:\n",
        "    runtime_batch.close()\n",
        "\n",
        "if job_failures:\n",
        "    raise RuntimeError(\n",
        "        \"One or more batched Orbit jobs failed:\\n\" + \"\\n\".join(job_failures)\n",
        "    )\n",
        "\n",
        "grouped_success = defaultdict(list)\n",
        "grouped_raw_success = defaultdict(list)\n",
        "pub_summaries = []\n",
        "\n",
        "for job_index, job_result in sorted(results_by_job.items()):\n",
        "    records = pub_records_by_job[job_index]\n",
        "    for record, pub_result in zip(records, job_result, strict=True):\n",
        "        label = record[\"label\"]\n",
        "        n_qubits = record[\"n_qubits\"]\n",
        "        counts = extract_counts(pub_result)\n",
        "        raw_counts = extract_raw_counts(pub_result)\n",
        "        success = probability_for_bitstring(\n",
        "            counts, record[\"target_bitstring\"], n_qubits\n",
        "        )\n",
        "        raw_success = probability_for_bitstring(\n",
        "            raw_counts, record[\"target_bitstring\"], n_qubits\n",
        "        )\n",
        "        key = (n_qubits, label)\n",
        "        grouped_success[key].append(success)\n",
        "        grouped_raw_success[key].append(raw_success)\n",
        "\n",
        "        orbit_report = pub_result.metadata.get(\"quantum_elements_orbit\", {})\n",
        "        mem_report = orbit_report.get(\"measurementErrorMitigation\", {})\n",
        "        pub_summaries.append(\n",
        "            {\n",
        "                **record,\n",
        "                \"function_job_id\": jobs[job_index].job_id,\n",
        "                \"runtime_batch_id\": runtime_batch.session_id,\n",
        "                \"success_probability\": success,\n",
        "                \"raw_success_probability\": raw_success,\n",
        "                \"orbit_mode\": orbit_report.get(\"mode\"),\n",
        "                \"transpilation_mode\": orbit_report.get(\"transpilationMode\"),\n",
        "                \"physical_layout\": orbit_report.get(\"physicalLayout\"),\n",
        "                \"dd_status\": orbit_report.get(\"status\", \"not_applied\"),\n",
        "                \"num_sequences_added\": orbit_report.get(\n",
        "                    \"numSequencesAdded\", 0\n",
        "                ),\n",
        "                \"num_gaps_filled\": orbit_report.get(\"numGapsFilled\", 0),\n",
        "                \"dynamic_dd_seq\": orbit_report.get(\"dynamicDdSeq\"),\n",
        "                \"mem_status\": mem_report.get(\"status\", \"not_requested\"),\n",
        "                \"warnings\": orbit_report.get(\"warnings\", [])\n",
        "                + mem_report.get(\"warnings\", []),\n",
        "            }\n",
        "        )\n",
        "\n",
        "process_fidelity = defaultdict(dict)\n",
        "raw_process_fidelity = defaultdict(dict)\n",
        "mean_success_probability = defaultdict(dict)\n",
        "raw_mean_success_probability = defaultdict(dict)\n",
        "\n",
        "for (n_qubits, label), probabilities in sorted(grouped_success.items()):\n",
        "    n_key = str(n_qubits)\n",
        "    process_fidelity[n_key][label] = (\n",
        "        process_fidelity_from_success_probabilities(probabilities)\n",
        "    )\n",
        "    mean_success_probability[n_key][label] = float(np.mean(probabilities))\n",
        "\n",
        "for (n_qubits, label), probabilities in sorted(grouped_raw_success.items()):\n",
        "    n_key = str(n_qubits)\n",
        "    raw_process_fidelity[n_key][label] = (\n",
        "        process_fidelity_from_success_probabilities(probabilities)\n",
        "    )\n",
        "    raw_mean_success_probability[n_key][label] = float(np.mean(probabilities))\n",
        "\n",
        "process_fidelity = dict(process_fidelity)\n",
        "raw_process_fidelity = dict(raw_process_fidelity)\n",
        "mean_success_probability = dict(mean_success_probability)\n",
        "raw_mean_success_probability = dict(raw_mean_success_probability)\n",
        "\n",
        "{\n",
        "    \"runtime_batch_id\": runtime_batch.session_id,\n",
        "    \"function_job_ids\": {\n",
        "        job_index: job.job_id for job_index, job in enumerate(jobs)\n",
        "    },\n",
        "    \"n_groups\": {\n",
        "        job_index: group for job_index, group in enumerate(N_GROUPS)\n",
        "    },\n",
        "    \"process_fidelity\": process_fidelity,\n",
        "    \"mean_success_probability\": mean_success_probability,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "metadata-md",
      "metadata": {},
      "source": [
        "<span id=\"inspect-orbit-dd-metadata\" />\n",
        "\n",
        "## Esamina i metadati di Orbit DD\n",
        "\n",
        "Il riepilogo riportato di seguito verifica i metadati `dynamic/orbit` di PUB, anziché dare per scontato che il DD richiesto sia stato inserito. Verificare lo stato, la sequenza DD dinamica segnalata, gli avvisi e il numero di lacune colmate e di sequenze aggiunte. Un inserimento riuscito dovrebbe generare conteggi diversi da zero per almeno alcuni PUB, ma i valori esatti dipendono dal circuito programmato, dai vincoli temporali del backend e dalle dimensioni del circuito. Questi metadati descrivono la sequenza applicata da Orbit; non dovrebbero essere indicati come il protocollo FC-DD dell'articolo, a meno che il rapporto non ne stabilisca esplicitamente l'equivalenza.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 36,
      "id": "metadata-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "{'dynamic_orbit_dd_status_counts': {'40': {'dd_inserted': 20},\n",
              "  '2': {'dd_inserted': 20},\n",
              "  '7': {'dd_inserted': 20},\n",
              "  '15': {'dd_inserted': 20},\n",
              "  '10': {'dd_inserted': 20},\n",
              "  '35': {'dd_inserted': 20},\n",
              "  '3': {'dd_inserted': 20},\n",
              "  '6': {'dd_inserted': 20},\n",
              "  '20': {'dd_inserted': 20},\n",
              "  '9': {'dd_inserted': 20},\n",
              "  '30': {'dd_inserted': 20},\n",
              "  '4': {'dd_inserted': 20},\n",
              "  '5': {'dd_inserted': 20},\n",
              "  '25': {'dd_inserted': 20},\n",
              "  '8': {'dd_inserted': 20}},\n",
              " 'dynamic_orbit_sequences_added': {'40': 31200,\n",
              "  '2': 40,\n",
              "  '7': 840,\n",
              "  '15': 4200,\n",
              "  '10': 1800,\n",
              "  '35': 23800,\n",
              "  '3': 120,\n",
              "  '6': 600,\n",
              "  '20': 7600,\n",
              "  '9': 1440,\n",
              "  '30': 17400,\n",
              "  '4': 240,\n",
              "  '5': 400,\n",
              "  '25': 12000,\n",
              "  '8': 1120},\n",
              " 'warning_examples': [{'n_qubits': 40,\n",
              "   'target_decimal': 853235401719,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 954673909846,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 524641045908,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 185651043478,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']},\n",
              "  {'n_qubits': 40,\n",
              "   'target_decimal': 587114273567,\n",
              "   'warnings': ['Post-DD x/y pulses are rewritten into the target basis (e.g. sx) for ISA compliance; this shifts exact pulse timing within each gap, so DD spacing is approximate. A future release will emit native pulses directly.',\n",
              "    'MEM was applied unconditionally to the returned output bitstring without inferring whether each bit came from a terminal measurement or a mid-circuit measurement. This is meaningful for bits intended as circuit outputs, but Orbit does not retroactively or in real time change conditional branches that used unmitigated measurement results.']}]}"
            ]
          },
          "execution_count": 36,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "dd_summary = defaultdict(lambda: Counter())\n",
        "sequence_totals = defaultdict(int)\n",
        "warning_examples = []\n",
        "\n",
        "for summary in pub_summaries:\n",
        "    if summary[\"label\"] != \"dynamic/orbit\":\n",
        "        continue\n",
        "    n_key = str(summary[\"n_qubits\"])\n",
        "    dd_summary[n_key][summary[\"dd_status\"]] += 1\n",
        "    sequence_totals[n_key] += int(summary.get(\"num_sequences_added\") or 0)\n",
        "    if summary.get(\"warnings\") and len(warning_examples) < 5:\n",
        "        warning_examples.append(\n",
        "            {\n",
        "                \"n_qubits\": summary[\"n_qubits\"],\n",
        "                \"target_decimal\": summary[\"target_decimal\"],\n",
        "                \"warnings\": summary[\"warnings\"],\n",
        "            }\n",
        "        )\n",
        "\n",
        "{\n",
        "    \"dynamic_orbit_dd_status_counts\": {\n",
        "        key: dict(value) for key, value in dd_summary.items()\n",
        "    },\n",
        "    \"dynamic_orbit_sequences_added\": dict(sequence_totals),\n",
        "    \"warning_examples\": warning_examples,\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "plot-md",
      "metadata": {},
      "source": [
        "<span id=\"plot-process-fidelity-curves\" />\n",
        "\n",
        "## Tracciare le curve di fedeltà al processo\n",
        "\n",
        "Il grafico mostra la stima puntuale della fedeltà del processo QFT+M, ottenuta tramite campionamento, in funzione del numero di qubit per le tre strategie. `dynamic/raw` e `dynamic/orbit` condivide lo stesso layout fisico per ciascuna dimensione; `unitary/raw` utilizza il layout e il routing del transpiler.\n",
        "\n",
        "A differenza della Figura 2a, questo grafico non mostra né la curva unitaria con DD né le bande di incertezza, e le sue curve grezze non sono state sottoposte a correzione della lettura. È preferibile interpretarlo come un confronto di scala “ Figure-2a-style ” per questo flusso di lavoro Orbit, piuttosto che come una riproduzione diretta delle curve pubblicate.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 51,
      "id": "1972ceb9",
      "metadata": {},
      "outputs": [],
      "source": [
        "from datetime import datetime\n",
        "from zoneinfo import ZoneInfo\n",
        "\n",
        "closed_at = runtime_batch.details()[\"closed_at\"]  # \"2026-07-22T00:08:54.89Z\"\n",
        "closed_dt = datetime.fromisoformat(closed_at.replace(\"Z\", \"+00:00\"))\n",
        "closed_local = closed_dt.astimezone(ZoneInfo(\"America/Los_Angeles\"))"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 53,
      "id": "plot-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/quantum-elements-orbit/extracted-outputs/plot-code-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "labels = [\"dynamic/orbit\", \"dynamic/raw\", \"unitary/raw\"]\n",
        "colors = {\n",
        "    \"dynamic/orbit\": \"#26735b\",\n",
        "    \"dynamic/raw\": \"#9b1c31\",\n",
        "    \"unitary/raw\": \"#6e6e6e\",\n",
        "}\n",
        "pretty_labels = {\n",
        "    \"dynamic/orbit\": \"Dynamic QFT+M with Orbit\",\n",
        "    \"dynamic/raw\": \"Dynamic QFT+M\",\n",
        "    \"unitary/raw\": \"Unitary QFT+M\",\n",
        "}\n",
        "\n",
        "series = []\n",
        "for label in labels:\n",
        "    values = [process_fidelity[str(n)][label] for n in N_VALUES]\n",
        "    log_values = [value if value > 0.0 else float(\"nan\") for value in values]\n",
        "    series.append((label, values, log_values))\n",
        "\n",
        "nonzero_values = [\n",
        "    value\n",
        "    for _, _, log_values in series\n",
        "    for value in log_values\n",
        "    if value > 0.0\n",
        "]\n",
        "if not nonzero_values:\n",
        "    raise RuntimeError(\n",
        "        \"No nonzero process-fidelity values found for log inset\"\n",
        "    )\n",
        "log_floor = min(nonzero_values) / 2\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(9.8, 5.6))\n",
        "for label, values, _ in series:\n",
        "    ax.plot(\n",
        "        N_VALUES,\n",
        "        values,\n",
        "        marker=\"o\",\n",
        "        linewidth=2.0,\n",
        "        markersize=5,\n",
        "        color=colors[label],\n",
        "        label=pretty_labels[label],\n",
        "    )\n",
        "\n",
        "ax.set_xlabel(\"N qubits\")\n",
        "ax.set_ylabel(\"Process fidelity\")\n",
        "finished_time_for_title = globals().get(\"finished_local\", closed_local)\n",
        "ax.set_title(\n",
        "    f\"Dynamic QFT Orbit results on {IBM_BACKEND_NAME}\\n\"\n",
        "    f\"Job finished {finished_time_for_title:%Y-%m-%d %H:%M %Z}\"\n",
        ")\n",
        "ax.set_xticks(N_VALUES)\n",
        "ax.set_ylim(bottom=0)\n",
        "ax.grid(axis=\"both\", alpha=0.25)\n",
        "ax.legend(loc=\"upper right\")\n",
        "\n",
        "inset = ax.inset_axes([0.53, 0.31, 0.44, 0.43])\n",
        "for label, _, log_values in series:\n",
        "    inset.plot(\n",
        "        N_VALUES,\n",
        "        log_values,\n",
        "        marker=\"o\",\n",
        "        linewidth=2.0,\n",
        "        markersize=5,\n",
        "        color=colors[label],\n",
        "    )\n",
        "inset.set_yscale(\"log\")\n",
        "inset.set_ylim(bottom=log_floor)\n",
        "inset.set_xlim(min(N_VALUES), max(N_VALUES))\n",
        "inset.set_title(\"Log scale; zeros omitted\", fontsize=9)\n",
        "inset.grid(axis=\"both\", alpha=0.25)\n",
        "inset.tick_params(axis=\"both\", labelsize=8)\n",
        "inset.patch.set_alpha(0.96)\n",
        "\n",
        "fig.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "refs",
      "metadata": {},
      "source": [
        "<span id=\"references\" />\n",
        "\n",
        "## Riferimenti\n",
        "\n",
        "1. [E. Bäumer *et al.*, \"Trasformata di Fourier quantistica mediante circuiti dinamici\", arXiv:2403.09514; *Physical Review Letters* **133**, 150602 (2024)](https://arxiv.org/abs/2403.09514)\n",
        "\n",
        "2. [Introduzione a \" Qiskit Functions \"](/docs/guides/functions)\n",
        "\n",
        "3. [Limiti del calcolo quantistico per le variabili estensibili](/docs/guides/stretch#qiskit-runtime-limitations)\n",
        "\n",
        "4. [IBM Quantum codici di errore: 6073](https://ibm.biz/error_codes#6073)\n",
        "\n",
        "5. [Eseguire i processi in batch](/docs/guides/run-jobs-batch)\n",
        "\n",
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "* Consulta la [guida di Orbit](/docs/guides/quantum-elements-orbit) e la documentazione [di riferimento dell'API](/docs/api/functions/quantum-elements-orbit).\n",
        "* Prova un backend diverso, un layout alternativo oppure sperimenta sequenze alternative di disaccoppiamento dinamico abilitate per Orbit modificando l'opzione `dd_strategy` . Si tenga presente che, data la natura sperimentale dei circuiti dinamici, è necessario prestare attenzione alle possibili modalità di malfunzionamento (cfr. [\\[3\\]](#references) e [\\[4\\]](#references) ). Se si riscontrano valori di allungamento negativi [\\[3\\]](#references), provare una sequenza DD più breve (con un numero inferiore di impulsi). Se si verifica la condizione [\\[4\\]](#references), aumentare `NUM_BATCH_JOBS`, diminuire `M`o ridurre i valori più grandi in `N_VALUES`.\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,
    "qpuSeconds": 120
  },
  "nbformat": 4,
  "nbformat_minor": 5
}