{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "frontmatter",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Implementare ed eseguire un modello di funzione Qiskit per la dinamica dell'Hamiltoniano AQC + Trotter\"\n",
        "description: \"Implementare il modello della funzione dinamica hamiltoniana AQC + Trotter su Qiskit Serverless, quindi eseguirlo su un simulatore e su una QPU.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore Trotter Trotterization quimb cotengra cotengrust Suzuki fidelities isa */}\n",
        "\n",
        "<span id=\"deploy-and-run-a-qiskit-function-template-for-aqc-+-trotter-hamiltonian-dynamics\" />\n",
        "\n",
        "# Implementare ed eseguire un modello di funzione Qiskit per la dinamica dell'Hamiltoniano AQC + Trotter\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "version-info",
      "metadata": {
        "tags": [
          "version-info"
        ]
      },
      "source": [
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "overview",
      "metadata": {},
      "source": [
        "<span id=\"overview\" />\n",
        "\n",
        "## Panoramica\n",
        "\n",
        "Questo è un modello di funzione Qiskit indipendente dall'esperimento per la dinamica hamiltoniana. Dato un Hamiltoniano di Pauli dei vicini più prossimi di tipo “ 1D ”, uno stato iniziale preparato (facoltativo) e un insieme di osservabili, l’algoritmo esegue l’evoluzione temporale di Trotter, la compressione dei circuiti tramite compilazione quantistica approssimativa (AQC) e l’esecuzione mitigata, per poi restituire le serie temporali di ciascun osservabile. Se si scambiano la fase di preparazione (PRE) e quella di analisi (POST), lo stesso nucleo dà origine a un esperimento diverso:\n",
        "\n",
        "| PRE (la tua configurazione)                                                          | FUNZIONE (implementata qui)                                                                                                          | PUBBLICA (la tua analisi)                                                                                                    |\n",
        "| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- |\n",
        "| Preparare uno stato, come circuito o stato di prodotto, con un kick locale opzionale | Sintesi Trotter → compressione AQC → esecuzione su `statevector`, `fake`, o `runtime`, con restituzione di un $\\langle O \\rangle(t)$ | $S(q, \\omega)$ per la diffusione dei neutroni, oppure per la magnetizzazione, il trasporto, la dinamica di quench e così via |\n",
        "\n",
        "Il modello è pubblicato nel [repository dei modelli di funzione di](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter) Qiskit, insieme agli altri modelli di applicazione. Questo notebook lo distribuisce sul tuo account Qiskit Serverless. Eseguila una volta e qualsiasi notebook potrà poi richiamare la funzione con `serverless.load(\"aqc-dynamics-function\")`.\n",
        "\n",
        "Per un esempio scientifico concreto, si veda [“Simulazione della diffusione dei neutroni con un flusso di lavoro serverless basato su AQC e dinamica di Trotter”](/docs/tutorials/simulate-neutron-scattering-with-a-serverless-workflow), che richiama questa funzione per calcolare il fattore di struttura dinamico di KCuF$_3$. Questo notebook tratta invece l’implementazione e il contratto di input.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "requirements",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare, assicurati che nell'ambiente del kernel di questo notebook siano presenti i seguenti elementi:\n",
        "\n",
        "* Qiskit SDK v2.0 o versioni successive (`pip install qiskit`).\n",
        "* Il client Qiskit IBM Catalog (`pip install qiskit-ibm-catalog`), che distribuisce ed esegue carichi di lavoro su Qiskit Serverless.\n",
        "\n",
        "Non è necessario installare localmente le dipendenze scientifiche della funzione stessa (`qiskit-addon-aqc-tensor`, `cotengrust`, `qiskit-aer`).\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "source-files",
      "metadata": {},
      "source": [
        "<span id=\"get-the-template-source-files\" />\n",
        "\n",
        "## Scarica i file sorgente del modello\n",
        "\n",
        "La funzione è un piccolo pacchetto Python che Qiskit Serverless viene eseguito nel cloud, quindi il suo codice sorgente deve essere presente sotto forma di file locali che vengono caricati al momento della distribuzione. Il pacchetto è pubblicato nel repository dei modelli di funzione di Qiskit.\n",
        "\n",
        "Scarica **[`source_files`](https://download-directory.github.io/?url=https%3A%2F%2Fgithub.com%2Fqiskit-community%2Fqiskit-function-templates%2Ftree%2Fmain%2Fphysics%2Faqc_trotter%2Fsource_files)**\n",
        "\n",
        "Il file da scaricare è un unico file ZIP, il cui nome corrisponde al percorso completo della directory presente nel repository:\n",
        "\n",
        "`qiskit-community qiskit-function-templates main physics aqc_trotter source_files.zip`\n",
        "\n",
        "1. Decomprimilo nella cartella in cui si trova questo notebook.\n",
        "2. Rinomina la cartella estratta, sostituendo quel nome lungo con `source_files`.\n",
        "\n",
        "La tua directory di lavoro avrà quindi questo aspetto:\n",
        "\n",
        "```\n",
        "your-working-directory/\n",
        "├── function-template-aqc-trotter.ipynb    <- this notebook\n",
        "└── source_files/                         <- the renamed folder\n",
        "    ├── __init__.py\n",
        "    ├── program.py\n",
        "    └── source/\n",
        "        ├── __init__.py\n",
        "        ├── _serverless.py\n",
        "        ├── app_function.py\n",
        "        ├── aqc.py\n",
        "        ├── build.py\n",
        "        ├── execute.py\n",
        "        └── hamiltonian.py\n",
        "```\n",
        "\n",
        "Il nome deve corrispondere esattamente `source_files`, perché si tratta dei file caricati nella Fase `working_dir` 3.\n",
        "\n",
        "`program.py` è il punto di ingresso che il gateway richiama. Tutto ciò che segue è `source/` dedicato all'implementazione, suddivisa per fasi: sintesi dell'Hamiltoniano e di Trotter, compressione AQC ed esecuzione. Non è necessario apportare alcuna modifica per eseguire gli esempi che seguono. Il passaggio 3 carica l'intera directory, quindi ripeti quel passaggio ogni volta che modifichi un file.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "auth-md",
      "metadata": {},
      "source": [
        "<span id=\"1-authentication\" />\n",
        "\n",
        "## 1. Autenticazione\n",
        "\n",
        "Utilizza `qiskit-ibm-catalog` per effettuare l'autenticazione su `QiskitServerless` con la tua chiave API (token) e l'istanza \" CRN \", che puoi trovare nella dashboard di [IBM Quantum® Platform](). Con queste credenziali è possibile avviare localmente il client serverless per caricare o eseguire la funzione selezionata:\n",
        "\n",
        "```python\n",
        "from qiskit_ibm_catalog import QiskitServerless\n",
        "serverless = QiskitServerless(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")\n",
        "```\n",
        "\n",
        "Se lo desideri, puoi utilizzare `save_account()` per salvare le tue credenziali nel tuo ambiente locale (consulta la guida [“Configurazione dell’account IBM Cloud® ”](/docs/guides/cloud-setup#cloud-save) ). Si noti che questa operazione salva le credenziali nello stesso file di [`QiskitRuntimeService.save_account()`](/docs/api/qiskit-ibm-runtime/qiskit-runtime-service#save_account):\n",
        "\n",
        "```python\n",
        "QiskitServerless.save_account(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")\n",
        "```\n",
        "\n",
        "Se l'account è stato salvato, non è necessario fornire il token per l'autenticazione:\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 76,
      "id": "auth-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "from qiskit_ibm_catalog import QiskitServerless\n",
        "\n",
        "# Authenticate to the remote cluster\n",
        "# In this case, loading a saved account\n",
        "serverless = QiskitServerless()\n",
        "\n",
        "# REPLACE WITH YOUR OWN CREDENTIALS or SAVED ACCOUNT\n",
        "# serverless = QiskitServerless(channel=\"ibm_quantum_platform\", token=\"MY_TOKEN\", instance=\"MY_CRN\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "deps-md",
      "metadata": {},
      "source": [
        "<span id=\"2-declare-dependencies\" />\n",
        "\n",
        "## 2. Dichiarare le dipendenze\n",
        "\n",
        "Pacchetti necessari alla funzione oltre all'immagine serverless di base gestita.\n",
        "\n",
        "<Admonition type=\"note\">\n",
        "  Il gateway installa solo i nomi presenti nella propria lista di autorizzazioni ([`requirements-dynamic-dependencies.txt`](https://github.com/Qiskit/qiskit-serverless/blob/main/docker-images/requirements-dynamic-dependencies.txt)), identificati in base al nome del pacchetto e associati alla versione consentita tramite `==`. Tutto il resto deve arrivare in modo transitivo (come dipendenza di un pacchetto presente nell'elenco dei pacchetti consentiti). La sintassi `[extras]` viene rispettata: `qiskit-addon-aqc-tensor[quimb-jax]` è ciò che installa e `quimb` `jax`. `cotengrust` è necessario per garantire l'efficienza della memoria durante la simulazione delle reti tensoriali. `qiskit-aer` è indicato separatamente per il backend `fake` (simulazione locale con rumore).\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "deps-code",
      "metadata": {},
      "outputs": [],
      "source": [
        "DEPENDENCIES = [\n",
        "    \"qiskit-addon-aqc-tensor[quimb-jax]==0.3.1\",\n",
        "    \"qiskit-aer==0.17.2\",\n",
        "    \"cotengrust==0.2.0\",\n",
        "]"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "upload-md",
      "metadata": {},
      "source": [
        "<span id=\"3-define-and-upload-the-function\" />\n",
        "\n",
        "## 3. Definire e caricare la funzione\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "upload-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "QiskitFunction(aqc-dynamics-function)"
            ]
          },
          "execution_count": 119,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "from qiskit_ibm_catalog import QiskitFunction\n",
        "\n",
        "fn = QiskitFunction(\n",
        "    title=\"aqc-dynamics-function\",\n",
        "    entrypoint=\"program.py\",\n",
        "    working_dir=\"source_files/\",\n",
        "    dependencies=DEPENDENCIES,\n",
        ")\n",
        "serverless.upload(fn)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "verify-md",
      "metadata": {},
      "source": [
        "<span id=\"4-verify-it-registered\" />\n",
        "\n",
        "## 4. Verificare che sia stato registrato\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "verify-code",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "QiskitFunction(aqc-dynamics-function)"
            ]
          },
          "execution_count": 3,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "next(p for p in serverless.list() if p.title == \"aqc-dynamics-function\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "reference-md",
      "metadata": {},
      "source": [
        "<span id=\"function-reference\" />\n",
        "\n",
        "## Riferimento alle funzioni\n",
        "\n",
        "Questa è una breve introduzione. Ogni campo è documentato in modo esaustivo nel file [README del modello AQC Dynamics](https://github.com/qiskit-community/qiskit-function-templates/blob/main/physics/aqc_trotter/README.md) : la tabella completa degli input con le relative regole di validazione, i campi di output, i backend di esecuzione e ulteriori esempi pratici. Quella che segue è la versione sintetica, sufficiente per comprendere gli esempi riportati di seguito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "inputs-md",
      "metadata": {},
      "source": [
        "<span id=\"inputs\" />\n",
        "\n",
        "### Input\n",
        "\n",
        "Ogni esecuzione corrisponde a una singola chiamata `fn.run(...)` . Sono obbligatori solo i primi tre campi della tabella: `hamiltonian`, `t_steps`, e `aqc_segments`. Tutto ciò che segue è facoltativo e, in assenza di indicazioni, si ricorre al valore predefinito indicato; pertanto, una chiamata minima prevede tre argomenti, mentre il resto della tabella descrive le funzionalità che è possibile attivare a propria discrezione. L'Hamiltoniano `num_qubits` determina la lunghezza della catena, quindi non è previsto alcun parametro di input separato per la dimensione.\n",
        "\n",
        "| Input                | Predefinito                 | Descrizione                                                                                                                                                               |                                                                                               |\n",
        "| -------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- |\n",
        "| `hamiltonian`        | obbligatorio                | 1D Hamiltoniano di Pauli del vicino più prossimo come `SparsePauliOp`. Le stringhe sono operatori di Pauli, quindi non vi è alcun fattore implicito pari a mezzo.         |                                                                                               |\n",
        "| `t_steps`            | obbligatorio                | Totale passi di Trotter. Si evolve in e `T = t_steps * dt` riporta ogni grandezza osservabile in ogni `t_k = k * dt`.                                                     |                                                                                               |\n",
        "| `aqc_segments`       | obbligatorio                | Piano di compressione: un elenco di `{\"n_steps\": k, \"ansatz_steps\": m}`. `sum(n_steps)` i passi sono compressi; il resto viene eseguito come nel metodo Trotter standard. |                                                                                               |\n",
        "| `dt`                 | `0.2`                       | Il tempo fisico è avanzato di un passo di Trotter.                                                                                                                        |                                                                                               |\n",
        "| `initial_state`      | \\`                          | 0...0>\\`                                                                                                                                                                  | Un individuo pronto `QuantumCircuit` a evolversi. Aggiungi un tocco locale a questo circuito. |\n",
        "| `observables`        | per sito `Z`                | Qualsiasi cosa `EstimatorV2` accetta come argomento `observables` . Un dato osservabile per ogni colonna di output.                                                       |                                                                                               |\n",
        "| `trotter_options`    | Suzuki di secondo ordine    | `{\"method\": ..., \"synthesis_settings\": {...}}`. e `reps` `time` appartengono alla funzione.                                                                               |                                                                                               |\n",
        "| `aqc_options`        | Vedi la descrizione         | `max_bond` (`32`), `cutoff` (`1e-8`), `autodiff_backend` (`\"jax\"`), `fidelity_target` (`None`), `optimizer_settings` (L-BFGS-B, `jac=True`, `maxiter=300`).               |                                                                                               |\n",
        "| `estimator_options`  | DD, piroette, TREX          | `EstimatorV2.options`, approvato così com'è. Un dizionario fornito sostituisce integralmente quelli predefiniti, anziché fondersi con essi.                               |                                                                                               |\n",
        "| `transpiler_options` | `{\"optimization_level\": 3}` | `generate_preset_pass_manager` argomenti chiave. `backend` e `target` vengono scartati, poiché appartengono al percorso di esecuzione.                                    |                                                                                               |\n",
        "| `backend`            | `\"runtime\"`                 | `\"statevector\"`, `\"fake\"`, oppure `\"runtime\"`.                                                                                                                            |                                                                                               |\n",
        "| `backend_name`       | meno trafficato             | IBM® nome del backend per `runtime`, oppure un backend fittizio con un nome specifico.                                                                                    |                                                                                               |\n",
        "| `batches`            | `1`                         | Dividere i circuiti tra N processi di esecuzione. Un batch invia un singolo processo e non crea alcuna sessione.                                                          |                                                                                               |\n",
        "| `parallel_sim`       | `False`                     | Distribuisci i percorsi del simulatore locale su tutti i core disponibili con Ray. Nessun effetto su `runtime`.                                                           |                                                                                               |\n",
        "| `return_circuits`    | `False`                     | Restituire i circuiti logici AQC + Trotter nel risultato insieme alle serie osservabili.                                                                                  |                                                                                               |\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "backends-md",
      "metadata": {},
      "source": [
        "<span id=\"execution-backends\" />\n",
        "\n",
        "### Backend di esecuzione\n",
        "\n",
        "Tutti e tre i percorsi condividono lo stesso codice e le stesse impostazioni di mitigazione. Si differenziano solo per il percorso dei circuiti.\n",
        "\n",
        "| `backend`                 | Che cos’è                                                                     | Credenziali                                          | Note                                                                                                                                                |\n",
        "| ------------------------- | ----------------------------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |\n",
        "| `\"statevector\"`           | Esatta `StatevectorEstimator`                                                 | Solo per account serverless                          | Il percorso di riferimento esatto. Nessun tempo QPU.                                                                                                |\n",
        "| `\"fake\"`                  | Simulazione locale rumorosa su un backend fittizio di Qiskit                  | Solo per account serverless                          | Una fedele riproduzione del percorso `runtime` attenuato. Esigenze `qiskit-aer`. Il valore predefinito è il sistema da 127 qubit `fake_sherbrooke`. |\n",
        "| `\"runtime\"` (predefinito) | Le misure di mitigazione adottate `EstimatorV2` nei confronti di una vera QPU | Account serverless e un'istanza con accesso alla QPU | `backend_name` facoltativo; se omesso, viene selezionato il dispositivo meno occupato.                                                              |\n",
        "\n",
        "Entrambi i percorsi del simulatore continuano a richiamare la funzione distribuita, quindi richiedono un account Serverless salvato anche se non utilizzano tempo di elaborazione QPU. I due esempi che seguono eseguono lo stesso carico di lavoro prima `statevector` su, poi su `runtime`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "output-md",
      "metadata": {},
      "source": [
        "<span id=\"output\" />\n",
        "\n",
        "### Output\n",
        "\n",
        "`job.result()` restituisce un semplice dizionario:\n",
        "\n",
        "```python\n",
        "{\n",
        "    \"times\": [...],                  # length t_steps + 1, t_k = k * dt (t=0 is the prepared state)\n",
        "    \"expectation_values\": [[...]],   # shape (n_times, n_observables)\n",
        "    \"observable_labels\": [...],      # for example: [\"Z_0\", \"ZZ_0_1\"]\n",
        "    \"metadata\": {\n",
        "        \"n\", \"t_steps\", \"dt\", \"tier\",\n",
        "        \"aqc_compressed_steps\": 5,   # total compressed steps (= sum of segment n_steps)\n",
        "        \"aqc_segments\": [            # per segment: the plan plus its own results\n",
        "            {\"n_steps\": 3, \"ansatz_steps\": 1, \"steps\": [1, 2, 3], \"n_params\": 133,\n",
        "             \"fidelities\": {\"1\": ..., \"2\": ..., \"3\": ...}},\n",
        "            {\"n_steps\": 2, \"ansatz_steps\": 2, \"steps\": [4, 5], \"n_params\": 245,\n",
        "             \"fidelities\": {\"4\": ..., \"5\": ...}},\n",
        "        ],\n",
        "        \"execution_backend\",\n",
        "        \"aqc_fidelities\": {\"1\": ..., \"2\": ...},  # flat per-step fidelity, all compressed steps\n",
        "        \"circuit_stats\": {           # per-step 2q depth and gate count, full Trotter vs AQC\n",
        "            \"1\": {\"full_trotter\": {\"depth_2q\": ..., \"num_2q_gates\": ...},\n",
        "                \"aqc_trotter\":  {\"depth_2q\": ..., \"num_2q_gates\": ...}},\n",
        "            \"2\": {...},\n",
        "        },\n",
        "        \"warnings\": [...],           # non-fatal notices; for example, a cotengrust fallback\n",
        "        \"resource_usage\": {          # per stage; QPU_TIME is the charged QPU time\n",
        "            \"RUNNING: OPTIMIZING_FOR_HARDWARE\": {\"CPU_TIME\": ...},\n",
        "            \"RUNNING: WAITING_FOR_QPU\": {\"CPU_TIME\": ...},\n",
        "            \"RUNNING: EXECUTING_QPU\": {\"QPU_TIME\": ...},\n",
        "        },\n",
        "    },\n",
        "    # present only when return_circuits=True\n",
        "    \"circuits\": [QuantumCircuit, ...],  # one per evolved step; circuits[i] is at times[i + 1]\n",
        "}\n",
        "```\n",
        "\n",
        "`aqc_fidelities` e sono `circuit_stats` i due da leggere per primi: insieme ti dicono se la compressione è rimasta fedele all’originale e se ha effettivamente conservato la profondità. Il `runtime`sistema riporta `resource_usage` il tempo di attesa in coda separatamente dal tempo di utilizzo della QPU che ti viene addebitato. Un input non valido viene rifiutato rapidamente come un `ServerlessError` codice strutturato `4615`.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "sim-md",
      "metadata": {},
      "source": [
        "<span id=\"simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore\n",
        "\n",
        "Esegui prima la funzione proprio su quel backend `statevector` . Non richiede tempo di elaborazione da parte della QPU e verifica la distribuzione dall'inizio alla fine. Il modello qui considerato è una catena di Ising a campo trasversale a otto qubit, e `observables` è stato omesso in modo che la funzione misuri l’ $Z$ predefinito per sito.\n",
        "\n",
        "Il piano di compressione è l'elemento che vale la pena comprendere. Ogni segmento `{\"n_steps\": k, \"ansatz_steps\": m}` comprime i passi consecutivi `k` di Trotter in un ansatz costruito a partire da un obiettivo di Trotter a `m`-passi, mentre eventuali passi successivi vengono `sum(n_steps)` eseguiti come metodo di Trotter standard. Le fasi iniziali, caratterizzate da un basso livello di entanglement, si comprimono bene in un ansatz a singolo strato poco profondo; le fasi successive, caratterizzate da un maggiore entanglement, richiedono invece un ansatz più profondo.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "sim-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID: ee1f3793-e995-427d-81d1-5924549beb38\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "fn = serverless.load(\"aqc-dynamics-function\")\n",
        "\n",
        "n = 8\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", [i, i + 1], 1.0) for i in range(n - 1)]\n",
        "    + [(\"X\", [i], 0.8) for i in range(n)],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "job = fn.run(\n",
        "    t_steps=8,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 4,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 2,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    hamiltonian=H,\n",
        "    aqc_options={\"max_bond\": 32},\n",
        "    backend=\"statevector\",\n",
        ")\n",
        "print(\"job ID:\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "follow-md",
      "metadata": {},
      "source": [
        "<span id=\"follow-the-run-and-read-the-result\" />\n",
        "\n",
        "### Segui la corsa e leggi il risultato\n",
        "\n",
        "`status()` riporta sia il ciclo di vita generale del processo sia gli stati parziali relativi a ciascuna fase che la funzione pubblica durante la sua esecuzione. Le stesse fasi valgono anche per la configurazione hardware descritta più avanti in questa guida:\n",
        "\n",
        "`QUEUED -> INITIALIZING -> RUNNING: OPTIMIZING_FOR_HARDWARE -> RUNNING: WAITING_FOR_QPU -> RUNNING: EXECUTING_QPU -> RUNNING: POST_PROCESSING -> DONE`\n",
        "\n",
        "| `status()` Valore                  | Fase                                                                   |\n",
        "| ---------------------------------- | ---------------------------------------------------------------------- |\n",
        "| `RUNNING: OPTIMIZING_FOR_HARDWARE` | preparazione dello stato, costruzione Trotter, compressione AQC        |\n",
        "| `RUNNING: WAITING_FOR_QPU`         | in coda sulla QPU (`runtime` solo backend)                             |\n",
        "| `RUNNING: EXECUTING_QPU`           | circuiti in esecuzione (i simulatori locali lo segnalano direttamente) |\n",
        "| `RUNNING: POST_PROCESSING`         | creazione del dizionario dei risultati                                 |\n",
        "\n",
        "Gli stati terminali sono `DONE`, `ERROR`, e `CANCELED`. Questa esecuzione `statevector` non ha una coda QPU, quindi salta `RUNNING: WAITING_FOR_QPU`. Utilizza `job.logs()` in qualsiasi momento per visualizzare i registri relativi a ciascuna fase, compresa la fedeltà AQC raggiunta in ogni fase.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "status-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "print(job.status())  # re-run until this reports DONE"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 113,
      "id": "result-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "observables: ['Z_0', 'Z_1', 'Z_2', 'Z_3', 'Z_4', 'Z_5', 'Z_6', 'Z_7']\n",
            "shape: (9, 8) -> (n_times, n_observables)\n",
            "first row (t = 0, the prepared state): [1. 1. 1. 1. 1. 1. 1. 1.]\n",
            "last row (t = t_steps * dt): [0.1442 0.2956 0.4686 0.4877 0.4869 0.4686 0.2963 0.1441]\n",
            "AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 1.0, '6': 0.9999}\n",
            "2q depth at the final step: 210 (full Trotter) -> 79 (AQC + Trotter)\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "result = job.result()\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "\n",
        "print(\"observables:\", result[\"observable_labels\"])\n",
        "print(\"shape:\", ev.shape, \"-> (n_times, n_observables)\")\n",
        "print(\"first row (t = 0, the prepared state):\", np.round(ev[0], 4))\n",
        "print(\"last row (t = t_steps * dt):\", np.round(ev[-1], 4))\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "# What the compression bought: 2-qubit depth at the final time step.\n",
        "stats = result[\"metadata\"][\"circuit_stats\"][\n",
        "    str(result[\"metadata\"][\"t_steps\"])\n",
        "]\n",
        "print(\n",
        "    \"2q depth at the final step:\",\n",
        "    stats[\"full_trotter\"][\"depth_2q\"],\n",
        "    \"(full Trotter) ->\",\n",
        "    stats[\"aqc_trotter\"][\"depth_2q\"],\n",
        "    \"(AQC + Trotter)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hw-md",
      "metadata": {},
      "source": [
        "<span id=\"hardware-example\" />\n",
        "\n",
        "## Esempio di hardware\n",
        "\n",
        "Una chiamata di funzione viene transposta `backend=\"runtime\"` ed eseguita su un processore reale IBM Quantum, con le tecniche di mitigazione degli errori integrate nella funzione: disaccoppiamento dinamico ( XY4 ), gate twirling ed estinzione degli errori di lettura twirled (TREX). `backend_name` seleziona il dispositivo; se non viene specificato, la funzione sceglie quello meno occupato.\n",
        "\n",
        "Non cambia nulla riguardo al codice scientifico. Ciò che differisce dall'esempio del simulatore è la lunghezza della catena, il numero di passi di Trotter, il piano di compressione, il backend e le impostazioni di mitigazione esplicita illustrate nella sezione seguente.\n",
        "\n",
        "<span id=\"sizing-the-job-for-the-control-hardware\" />\n",
        "\n",
        "### Determinazione delle dimensioni del lavoro per l’hardware di controllo\n",
        "\n",
        "`estimator_options` Vale la pena impostare deliberatamente questo parametro? Il \"gate twirling\" crea circuiti casuali distinti `num_randomizations` per ogni PUB, e l'intero processo, ogni PUB con tutte le sue casualità, deve rientrare nella memoria di istruzioni del sistema di controllo classico della QPU. La funzione è impostata di default su 1000 randomizzazioni, quindi un’evoluzione in 10 fasi invia 11 PUB da 1000 circuiti ciascuno: circa 11.000 istanze di circuiti in un unico processo.\n",
        "\n",
        "Se si supera il limite previsto dal sistema di controllo, l'operazione fallisce con [l'errore 6073](https://ibm.biz/error_codes#6073). [La sezione \"Limiti per job\"](/docs/guides/job-limits) indica le soglie e le modalità di calcolo, tra cui la principale è quella dell' 26.8 milione di istruzioni del sistema di controllo per qubit, applicata per singolo job anziché per singolo \" PUB \". Il disaccoppiamento dinamico aggiunge porte che contribuiscono a determinarlo.\n",
        "\n",
        "Due parametri determinano le dimensioni:\n",
        "\n",
        "* `estimator_options` stabilisce il budget per le riprese. Il numero totale di tentativi è `num_randomizations * shots_per_randomization`, quindi è possibile scambiare le randomizzazioni con il numero di tentativi per randomizzazione, mantenere le statistiche e ridurre comunque le dimensioni del programma. La cella seguente utilizza 100 randomizzazioni da 200 shot ciascuna, per un totale di 20.000 shot per ogni osservabile e circa un decimo delle istanze di circuito che verrebbero generate con le impostazioni predefinite. Per l'elenco completo dei campi, consultare [le opzioni](/docs/guides/estimator-options) \" [TwirlingOptions](/docs/api/qiskit-ibm-runtime/options-models-twirling-options) \" e \"Estimator\".\n",
        "* `batches` suddivide i PUB in altrettanti processi di esecuzione distinti, il che è proprio la soluzione suggerita dal messaggio di errore 6073 e spiega perché la suddivisione per processo sia importante. Questa impostazione invia `batches=4` circa tre PUB per ogni processo invece di undici contemporaneamente, e i processi vengono inviati insieme in un unico batch, in modo che il gruppo venga messo in coda una sola volta anziché che ogni processo venga messo in coda separatamente.\n",
        "\n",
        "Si ricordi che un valore specificato sostituisce `estimator_options` completamente i valori predefiniti della funzione, anziché fondersi con essi; pertanto, il disaccoppiamento dinamico e TREX vengono ridefiniti nella cella seguente per mantenerli attivi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "hw-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "job ID (save this to reconnect later): 7229a8bf-9f83-4785-8dd4-489844abc2d9\n"
          ]
        }
      ],
      "source": [
        "from qiskit.quantum_info import SparsePauliOp\n",
        "\n",
        "fn = serverless.load(\"aqc-dynamics-function\")\n",
        "\n",
        "n = 10\n",
        "H = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", [i, i + 1], 1.0) for i in range(n - 1)]\n",
        "    + [(\"X\", [i], 0.8) for i in range(n)],\n",
        "    num_qubits=n,\n",
        ")\n",
        "\n",
        "job = fn.run(\n",
        "    t_steps=10,\n",
        "    aqc_segments=[\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 1,\n",
        "        },  # early steps -> shallow 1-layer ansatz\n",
        "        {\n",
        "            \"n_steps\": 3,\n",
        "            \"ansatz_steps\": 2,\n",
        "        },  # later steps -> deeper 2-layer ansatz\n",
        "    ],\n",
        "    hamiltonian=H,\n",
        "    aqc_options={\"max_bond\": 32},\n",
        "    backend=\"runtime\",\n",
        "    backend_name=\"ibm_marrakesh\",\n",
        "    # The function defaults to 1000 twirling randomizations, which was too large\n",
        "    # for this device. Total shots is num_randomizations *\n",
        "    # shots_per_randomization, so this is 20,000 shots per observable.\n",
        "    estimator_options={\n",
        "        \"dynamical_decoupling\": {\"enable\": True, \"sequence_type\": \"XY4\"},\n",
        "        \"twirling\": {\n",
        "            \"enable_gates\": True,\n",
        "            \"num_randomizations\": 100,\n",
        "            \"shots_per_randomization\": 200,\n",
        "        },\n",
        "        \"resilience\": {\"measure_mitigation\": True},\n",
        "    },\n",
        ")\n",
        "print(\"job ID (save this to reconnect later):\", job.job_id)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "hw-reconnect-md",
      "metadata": {},
      "source": [
        "<Admonition type=\"note\" title=\"Ripristinare la connessione a un processo in esecuzione da molto tempo\">\n",
        "  Un'esecuzione hardware non è veloce e, nella maggior parte dei casi, avviene in modo classico anziché sulla QPU. La compressione AQC viene eseguita all'interno della funzione prima che qualsiasi dato raggiunga la QPU, mentre la coda della QPU si trova a livello superiore rispetto a essa. Non è necessario tenere aperto questo notebook o questo kernel mentre è in esecuzione.\n",
        "\n",
        "  Copia l'ID del lavoro visualizzato nella cella precedente e salvalo. Le tre caselle successive ti consentono di riprendere la sessione in un secondo momento:\n",
        "\n",
        "  1. Riconnettere, operazione necessaria solo in una nuova sessione del kernel: rieseguire la cella [di autenticazione](#1-authentication) per ricrearla `serverless`, quindi ricostruire l'handle `job` a partire dall'ID salvato. Salta questa cella se sei ancora nella sessione in cui hai inviato la richiesta, poiché l'handle è già attivo.\n",
        "  2. Verifica lo stato: ripeti l'operazione finché non viene segnalato `DONE`.\n",
        "  3. Recupera il risultato: esegui l'operazione solo quando lo stato è `DONE`.\n",
        "\n",
        "  Incolla l'ID che hai salvato al posto del segnaposto nella seguente cella di ricollegamento.\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "hw-reconnect",
      "metadata": {},
      "outputs": [],
      "source": [
        "# Reconnect to a previously submitted job by its ID. Only needed in a NEW kernel\n",
        "# session; if you are still in the session where you submitted, the `job` handle\n",
        "# from the preceding cell is already live, so skip this cell. Replace the ID that follows with your own.\n",
        "job = serverless.get_job_by_id(\"<your job ID>\")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 116,
      "id": "hw-status",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "DONE\n"
          ]
        }
      ],
      "source": [
        "# Re-run this until it reports DONE, then fetch the result in the following cell.\n",
        "print(job.status())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 118,
      "id": "hw-result-code",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "backend: runtime\n",
            "shape: (11, 10) -> (n_times, n_observables)\n",
            "last row (t = t_steps * dt): [0.1504 0.1361 0.218  0.2144 0.2275 0.1783 0.1749 0.1599 0.0915 0.0922]\n",
            "AQC fidelities: {'1': 1.0, '2': 1.0, '3': 1.0, '4': 1.0, '5': 0.9999, '6': 0.9999}\n",
            "2q depth at the final step: 342 (full Trotter) -> 171 (AQC + Trotter)\n"
          ]
        }
      ],
      "source": [
        "import numpy as np\n",
        "\n",
        "# Run this only once the preceding status cell reports DONE. result() blocks until\n",
        "# the job finishes, so calling it earlier just waits.\n",
        "result = job.result()\n",
        "ev = np.array(result[\"expectation_values\"])\n",
        "\n",
        "print(\"backend:\", result[\"metadata\"][\"execution_backend\"])\n",
        "print(\"shape:\", ev.shape, \"-> (n_times, n_observables)\")\n",
        "print(\"last row (t = t_steps * dt):\", np.round(ev[-1], 4))\n",
        "print(\n",
        "    \"AQC fidelities:\",\n",
        "    {k: round(v, 4) for k, v in result[\"metadata\"][\"aqc_fidelities\"].items()},\n",
        ")\n",
        "\n",
        "# What the compression bought: 2-qubit depth at the final time step.\n",
        "stats = result[\"metadata\"][\"circuit_stats\"][\n",
        "    str(result[\"metadata\"][\"t_steps\"])\n",
        "]\n",
        "print(\n",
        "    \"2q depth at the final step:\",\n",
        "    stats[\"full_trotter\"][\"depth_2q\"],\n",
        "    \"(full Trotter) ->\",\n",
        "    stats[\"aqc_trotter\"][\"depth_2q\"],\n",
        "    \"(AQC + Trotter)\",\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "nextsteps",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  * Segui il flusso di lavoro [Simulazione della diffusione neutronica con AQC + dinamica di Trotter](/docs/tutorials/simulate-neutron-scattering-with-a-serverless-workflow) (Serverless), l’esempio di accompagnamento che richiama questa funzione distribuita per calcolare il fattore di struttura dinamico di KCuF$_3$.\n",
        "  * Si prega di consultare il [modello AQC Dynamics su GitHub](https://github.com/qiskit-community/qiskit-function-templates/blob/main/physics/aqc_trotter/) per il contratto completo relativo a input e output, ulteriori esempi e i dettagli relativi alla citazione.\n",
        "  * Esplora il [repository dei modelli di Qiskit Function](https://github.com/qiskit-community/qiskit-function-templates/tree/main/physics/aqc_trotter) per trovare altri modelli di applicazioni realizzati con lo stesso metodo.\n",
        "  * Leggi la [guida “ Qiskit Serverless ” per la gestione delle funzioni distribuite.](/docs/guides/serverless)\n",
        "  * Approfondisci la fase di compressione AQC con [l'add-on](https://qiskit.github.io/qiskit-addon-aqc-tensor/) Qiskit: documentazione di AQC-Tensor.\n",
        "</Admonition>\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"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 5
}