{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "dd701ecc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Compilazione quantistica approssimativa per circuiti di evoluzione temporale\"\n",
        "description: \"Scopri come utilizzare AQC-Tensor per comprimere i circuiti di evoluzione temporale trotterizzati, al fine di ottenere un'esecuzione efficiente su hardware quantistico.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore circo Néel */}\n",
        "\n",
        "<span id=\"approximate-quantum-compilation-for-time-evolution-circuits\" />\n",
        "\n",
        "# Compilazione quantistica approssimativa per circuiti di evoluzione temporale\n",
        "\n",
        "*Stima del tempo di esecuzione: 15 secondi su un processore Heron (NOTA: si tratta solo di una stima). (La durata potrebbe variare.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "12608301",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Risultati di apprendimento\n",
        "\n",
        "Una volta completato questo tutorial, avrai acquisito le seguenti conoscenze:\n",
        "\n",
        "* Come utilizzare l'add-on AQC-Tensor per Qiskit per comprimere circuiti di Trotter profondi in circuiti di ansatz superficiali\n",
        "* Come generare un ansatz parametrizzato a partire da un circuito di Trotter e ottimizzarne i parametri utilizzando metodi basati su reti di tensori (MPS)\n",
        "* Come valutare la fedeltà di un circuito compresso rispetto all'evoluzione target ed eseguirlo su hardware quantistico\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c3c5992a",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prerequisiti\n",
        "\n",
        "Si consiglia di approfondire i seguenti argomenti:\n",
        "\n",
        "* [Nozioni di base sui circuiti quantistici](/learning/courses/basics-of-quantum-information)\n",
        "* [Simulazione hamiltoniana e trotterizzazione](/learning/courses/utility-scale-quantum-computing/quantum-simulation)\n",
        "* [Introduzione alle primitive](/docs/guides/primitives)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "714adc39",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Sfondo\n",
        "\n",
        "Questo tutorial illustra come implementare **la compilazione quantistica approssimativa** tramite reti di tensori (AQC-Tensor) con Qiskit per migliorare le prestazioni dei circuiti quantistici. AQC-Tensor riduce i circuiti Trotter profondi a circuiti meno profondi e più compatibili con l'hardware, preservando al contempo la precisione della simulazione.\n",
        "\n",
        "<span id=\"how-aqc-tensor-works\" />\n",
        "\n",
        "### Come funziona AQC-Tensor\n",
        "\n",
        "Si consideri la simulazione di un hamiltoniano $H$ per un tempo totale di $t$ utilizzando $k$ passi di Trotter. Il percorso completo del Trotter è:\n",
        "\n",
        "$$\n",
        "U_{\\text{full}} = \\left[U_{\\text{Trotter}}(t/k)\\right]^k\n",
        "$$\n",
        "\n",
        "Un approccio ingenuo utilizza pochi passi di Trotter per mantenere la profondità del circuito a livelli gestibili, ma ciò comporta un errore di Trotter significativo. AQC-Tensor risolve questa tensione separando l'accuratezza dalla profondità:\n",
        "\n",
        "1. **Circuito di riferimento (alta precisione, profondo):** costruire un circuito di Trotter con molti passaggi — ad esempio, $10k$ — per lo stesso tempo di evoluzione. Questo circuito presenta un errore di Trotter molto inferiore, ma è troppo complesso per essere implementato su hardware. Poiché viene simulato solo in modo classico come stato di prodotto matriciale (MPS), la profondità non rappresenta un problema.\n",
        "\n",
        "2. **Circuito di Ansatz (a bassa profondità, parametrizzato):** definire un circuito parametrizzato $V(\\theta)$ con la stessa struttura di un circuito di Trotter a passo singolo. Inizializzarlo in modo che $V(\\theta_{\\text{init}}) = U_{\\text{Trotter}}(t/k)$, quindi ottimizzare iterativamente $\\theta$ in modo che $V(\\theta)$ riproduca il più fedelmente possibile lo stato target ad alta precisione.\n",
        "\n",
        "Il risultato è un circuito che conserva la profondità di un singolo passo di Trotter ma raggiunge la precisione di molti passi, rendendolo realizzabile per l'hardware quantistico nel breve termine.\n",
        "\n",
        "<span id=\"when-to-use-aqc-tensor\" />\n",
        "\n",
        "### Quando utilizzare AQC-Tensor\n",
        "\n",
        "AQC-Tensor è più efficace quando:\n",
        "\n",
        "* **La profondità del circuito supera i tempi di coerenza dell'hardware.** Se una simulazione di Trotter richiede un numero di passi di Trotter superiore a quello supportato dal dispositivo, AQC-Tensor è in grado di comprimere l'evoluzione in un circuito meno complesso.\n",
        "* **L'entanglement rimane gestibile dal punto di vista classico.** L'entanglement totale in uno stato evoluto nel tempo dipende principalmente dal tempo di evoluzione $t$, non dal numero di passi di Trotter $k$. Ciò significa che un circuito di destinazione con $10k$ passi non è in genere più difficile da rappresentare come MPS rispetto a uno con $k$ passi, purché $t$ sia sufficientemente breve da consentire di mantenere gestibili le dimensioni dei legami.\n",
        "* **Esiste un approccio naturale.** Poiché l'approccio rispecchia la struttura di un circuito di Trotter, esso fornisce un punto di partenza fisicamente motivato con parametri iniziali ben definiti, evitando i problemi di convergenza che possono affliggere approcci variazionali arbitrari.\n",
        "\n",
        "Questo approccio si contrappone alla compressione generica dei circuiti: anziché cercare di approssimare un operatore unitario arbitrario con un numero inferiore di porte logiche, AQC-Tensor mantiene la stessa struttura delle porte e ne ottimizza i parametri per ridurre l'errore di Trotter. Per ulteriori informazioni, consultare la [documentazione di AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/).\n",
        "\n",
        "Questo tutorial ti guida attraverso l'intero flusso di lavoro AQC-Tensor per la preparazione degli stati: definizione di un hamiltoniano, generazione di circuiti di Trotter, loro compressione tramite l'ottimizzazione delle reti tensoriali ed esecuzione del risultato su un hardware dell IBM Quantum®.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f8a05291",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Requisiti\n",
        "\n",
        "Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:\n",
        "\n",
        "* Qiskit SDK v2.0 o versioni successive, con supporto [alla visualizzazione](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 o più tardi (`pip install qiskit-ibm-runtime`)\n",
        "* Addon AQC-Tensor Qiskit (`pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]'`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f9410d21",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configura\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 1,
      "id": "ecdb92e0",
      "metadata": {},
      "outputs": [],
      "source": [
        "import numpy as np\n",
        "import quimb.tensor\n",
        "import datetime\n",
        "import matplotlib.pyplot as plt\n",
        "\n",
        "from scipy.linalg import expm\n",
        "from scipy.optimize import OptimizeResult, minimize\n",
        "\n",
        "from qiskit.quantum_info import SparsePauliOp, Pauli\n",
        "from qiskit.transpiler import CouplingMap\n",
        "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
        "from qiskit import QuantumCircuit\n",
        "from qiskit.synthesis import SuzukiTrotter\n",
        "\n",
        "from qiskit_addon_utils.problem_generators import (\n",
        "    generate_time_evolution_circuit,\n",
        ")\n",
        "from qiskit_addon_aqc_tensor.ansatz_generation import (\n",
        "    generate_ansatz_from_circuit,\n",
        ")\n",
        "from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity\n",
        "from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator\n",
        "from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit\n",
        "from qiskit_addon_aqc_tensor.simulation import compute_overlap\n",
        "\n",
        "from qiskit_ibm_runtime import QiskitRuntimeService\n",
        "from qiskit_ibm_runtime import EstimatorV2 as Estimator\n",
        "from qiskit_ibm_runtime.fake_provider import FakeKyiv\n",
        "\n",
        "from rustworkx.visualization import graphviz_draw"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6473f943",
      "metadata": {},
      "source": [
        "<span id=\"small-scale-simulator-example\" />\n",
        "\n",
        "## Esempio di simulatore su piccola scala\n",
        "\n",
        "In questa sezione viene utilizzato un sistema a 10 siti per illustrare passo dopo passo il flusso di lavoro di AQC-Tensor. Simuliamo la dinamica di una catena di spin XXZ a 10 siti, un modello ampiamente studiato per l'analisi delle interazioni di spin e delle proprietà magnetiche.\n",
        "\n",
        "L'hamiltoniano è il seguente:\n",
        "\n",
        "$$\n",
        "\\hat{\\mathcal{H}}_{XXZ} = \\sum_{i=1}^{L-1} J_{i,(i+1)}\\left(X_i X_{(i+1)}+Y_i Y_{(i+1)}+ 2\\cdot Z_i Z_{(i+1)} \\right) \\, ,\n",
        "$$\n",
        "\n",
        "dove $J_{i,(i+1)}$ è un coefficiente casuale per il bordo $(i, i+1)$ e $L=10$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6520161e",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Fase 1: mappare gli input classici su un problema quantistico\n",
        "\n",
        "In questa fase:\n",
        "\n",
        "1. Definire l'hamiltoniano, l'osservabile e lo stato iniziale.\n",
        "2. Calcolare il valore atteso esatto con il metodo classico per un confronto successivo.\n",
        "3. Generare un circuito di Trotter ad alta precisione (l'obiettivo AQC) e comprimerlo in un ansatz a bassa profondità utilizzando AQC-Tensor.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bbeb5729",
      "metadata": {},
      "source": [
        "<span id=\"set-up-the-hamiltonian-observable-and-initial-state\" />\n",
        "\n",
        "#### Definire l'hamiltoniano, l'osservabile e lo stato iniziale\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "id": "527dbada",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Hamiltonian: SparsePauliOp(['IIIIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII'],\n",
            "              coeffs=[1.        +0.j, 0.52440675+0.j, 0.52440675+0.j, 1.0488135 +0.j,\n",
            " 0.60759468+0.j, 0.60759468+0.j, 1.21518937+0.j, 0.55138169+0.j,\n",
            " 0.55138169+0.j, 1.10276338+0.j, 0.52244159+0.j, 0.52244159+0.j,\n",
            " 1.04488318+0.j, 0.4618274 +0.j, 0.4618274 +0.j, 0.9236548 +0.j,\n",
            " 0.57294706+0.j, 0.57294706+0.j, 1.14589411+0.j, 0.46879361+0.j,\n",
            " 0.46879361+0.j, 0.93758721+0.j, 0.6958865 +0.j, 0.6958865 +0.j,\n",
            " 1.391773  +0.j, 0.73183138+0.j, 0.73183138+0.j, 1.46366276+0.j])\n",
            "Observable: SparsePauliOp(['IIIIZZIIII'],\n",
            "              coeffs=[1.+0.j])\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/approximate-quantum-compilation-for-time-evolution/extracted-outputs/527dbada-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 2,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "# L is the number of sites in the 1D spin chain\n",
        "L = 10\n",
        "\n",
        "# Generate the coupling map\n",
        "edge_list = [(i - 1, i) for i in range(1, L)]\n",
        "even_edges = edge_list[::2]\n",
        "odd_edges = edge_list[1::2]\n",
        "coupling_map = CouplingMap(edge_list)\n",
        "\n",
        "# Generate random coefficients for our XXZ Hamiltonian\n",
        "np.random.seed(0)\n",
        "Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)\n",
        "hamiltonian = SparsePauliOp(Pauli(\"I\" * L))\n",
        "for i, edge in enumerate(even_edges + odd_edges):\n",
        "    hamiltonian += SparsePauliOp.from_sparse_list(\n",
        "        [\n",
        "            (\"XX\", (edge), Js[i] / 2),\n",
        "            (\"YY\", (edge), Js[i] / 2),\n",
        "            (\"ZZ\", (edge), Js[i]),\n",
        "        ],\n",
        "        num_qubits=L,\n",
        "    )\n",
        "\n",
        "# Generate a ZZ observable between the two middle qubits\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", (L // 2 - 1, L // 2), 1.0)], num_qubits=L\n",
        ")\n",
        "\n",
        "# Generate an initial Néel state |1010101010⟩\n",
        "initial_state_circuit = QuantumCircuit(L)\n",
        "for i in range(L):\n",
        "    if i % 2:\n",
        "        initial_state_circuit.x(i)\n",
        "\n",
        "print(\"Hamiltonian:\", hamiltonian)\n",
        "print(\"Observable:\", observable)\n",
        "graphviz_draw(coupling_map.graph, method=\"circo\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "54ad6963",
      "metadata": {},
      "source": [
        "<span id=\"compute-the-exact-expectation-value\" />\n",
        "\n",
        "#### Calcolare il valore esatto dell'aspettativa\n",
        "\n",
        "Per un sistema di queste dimensioni, possiamo calcolare direttamente il valore atteso nel tempo utilizzando l'esponenziazione delle matrici. Questo ci serve come riferimento per valutare la precisione del circuito AQC.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "id": "20c70651",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC evolution time: 0.2\n",
            "Subsequent evolution time: 0.066667\n",
            "Total evolution time: 0.266667\n",
            "Exact expectation value: -0.700899\n"
          ]
        }
      ],
      "source": [
        "aqc_evolution_time = 0.2\n",
        "\n",
        "# Each baseline Trotter step covers dt = aqc_evolution_time / 3\n",
        "# The subsequent (uncompressed) step covers 1 additional dt\n",
        "subsequent_evolution_time = aqc_evolution_time / 3\n",
        "total_evolution_time = aqc_evolution_time + subsequent_evolution_time\n",
        "\n",
        "# Compute exact expectation value via matrix exponentiation\n",
        "H_matrix = hamiltonian.to_matrix()\n",
        "U_exact = expm(-1j * H_matrix * total_evolution_time)\n",
        "\n",
        "# Build the initial state vector (Néel state)\n",
        "initial_state_vec = np.zeros(2**L)\n",
        "state_idx = sum(2**i for i in range(L) if i % 2)\n",
        "initial_state_vec[state_idx] = 1.0\n",
        "\n",
        "# Evolve and compute expectation value\n",
        "evolved_state = U_exact @ initial_state_vec\n",
        "obs_matrix = observable.to_matrix()\n",
        "exact_expval = (evolved_state.conj() @ obs_matrix @ evolved_state).real\n",
        "\n",
        "print(f\"AQC evolution time: {aqc_evolution_time}\")\n",
        "print(f\"Subsequent evolution time: {subsequent_evolution_time:.6f}\")\n",
        "print(f\"Total evolution time: {total_evolution_time:.6f}\")\n",
        "print(f\"Exact expectation value: {exact_expval:.6f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "a93c047c",
      "metadata": {},
      "source": [
        "<span id=\"generate-the-aqc-target-circuit\" />\n",
        "\n",
        "#### Generare il circuito di riferimento AQC\n",
        "\n",
        "Costruiamo ora il circuito di Trotter che fungerà da bersaglio per l'AQC. Questo circuito utilizza numerosi stadi di Trotter (32) per garantire un'elevata precisione. Poiché verrà simulato solo in modo classico come MPS — senza essere eseguito su hardware — la profondità elevata non rappresenta un problema.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "id": "db66bf00",
      "metadata": {},
      "outputs": [],
      "source": [
        "aqc_target_num_trotter_steps = 32\n",
        "\n",
        "aqc_target_circuit = initial_state_circuit.copy()\n",
        "aqc_target_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),\n",
        "        time=aqc_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bce1ef52",
      "metadata": {},
      "source": [
        "<span id=\"generate-an-ansatz-initial-parameters-subsequent-circuit-and-a-baseline-circuit\" />\n",
        "\n",
        "#### Generare un ansatz, i parametri iniziali, il circuito risultante e un circuito di riferimento\n",
        "\n",
        "Successivamente, costruiamo un circuito \"ottimale\" con lo stesso tempo di evoluzione del circuito quantistico analogico (AQC) di riferimento, ma con un numero di passi di Trotter molto inferiore (solo uno). Passiamo questo circuito a `generate_ansatz_from_circuit`, che restituisce:\n",
        "\n",
        "1. Un circuito **di riferimento** generico e parametrizzato con la stessa connettività a due qubit.\n",
        "2. **Parametri iniziali** che riproducono il circuito di ingresso quando collegato all'ansatz.\n",
        "\n",
        "Realizziamo inoltre:\n",
        "\n",
        "* Un **circuito successivo** con un passo di Trotter che verrà aggiunto (non compresso) dopo la porzione ottimizzata con AQC, seguendo l'approccio illustrato nel [tutorial sullo stato iniziale di AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/tutorials/01_initial_state_aqc.html).\n",
        "* **Un circuito Trotter di riferimento** che utilizza quattro passi di Trotter per l'intero tempo di evoluzione (`aqc_evolution_time + subsequent_evolution_time`). Questo serve come termine di paragone: rappresenta ciò che si otterrebbe su hardware senza AQC. L'approccio AQC (3 fasi di compressione + 1 fase senza compressione) garantisce una maggiore precisione a una profondità inferiore.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "id": "78f2665e",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Target circuit:      depth 384\n",
            "Baseline circuit:    depth 48 (4 Trotter steps, time=0.2667)\n",
            "Subsequent circuit:  depth 12 (1 Trotter step, time=0.0667)\n",
            "Ansatz circuit:      depth 3, with 156 parameters\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/approximate-quantum-compilation-for-time-evolution/extracted-outputs/78f2665e-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 5,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "aqc_ansatz_num_trotter_steps = 1\n",
        "\n",
        "aqc_good_circuit = initial_state_circuit.copy()\n",
        "aqc_good_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),\n",
        "        time=aqc_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "\n",
        "aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(\n",
        "    aqc_good_circuit\n",
        ")\n",
        "\n",
        "# Subsequent circuit: 1 non-compressed Trotter step appended after AQC\n",
        "subsequent_num_trotter_steps = 1\n",
        "subsequent_circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian,\n",
        "    synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),\n",
        "    time=subsequent_evolution_time,\n",
        ")\n",
        "\n",
        "# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC\n",
        "baseline_num_trotter_steps = 4\n",
        "baseline_circuit = initial_state_circuit.copy()\n",
        "baseline_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),\n",
        "        time=total_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "\n",
        "print(\n",
        "    f\"Target circuit:      depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"Baseline circuit:    depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} Trotter steps, time={total_evolution_time:.4f})\"\n",
        ")\n",
        "print(\n",
        "    f\"Subsequent circuit:  depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({subsequent_num_trotter_steps} Trotter step, time={subsequent_evolution_time:.4f})\"\n",
        ")\n",
        "print(\n",
        "    f\"Ansatz circuit:      depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters\"\n",
        ")\n",
        "aqc_ansatz.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "be1b4049",
      "metadata": {},
      "source": [
        "<span id=\"set-up-tensor-network-simulation-and-build-the-target-mps\" />\n",
        "\n",
        "#### Configurare la simulazione con rete tensoriale e generare l'MPS di destinazione\n",
        "\n",
        "Utilizziamo il simulatore di circuiti MPS (Matrix-Product State) [di Quimb](https://github.com/jcmgray/quimb), con JAX che fornisce la differenziazione automatica per l'ottimizzazione basata sui gradienti. Costruiamo quindi una rappresentazione MPS dello stato di destinazione e valutiamo la fedeltà iniziale tra l'ansatz iniziale e lo stato di destinazione. Poiché l'istanza in questione è un esempio relativamente piccolo, il livello di fedeltà iniziale è piuttosto elevato.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "id": "666fcf42",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Target MPS maximum bond dimension: 5\n",
            "Starting fidelity: 0.998246\n"
          ]
        }
      ],
      "source": [
        "simulator_settings = QuimbSimulator(\n",
        "    quimb.tensor.CircuitMPS, autodiff_backend=\"jax\"\n",
        ")\n",
        "\n",
        "aqc_target_mps = tensornetwork_from_circuit(\n",
        "    aqc_target_circuit, simulator_settings\n",
        ")\n",
        "print(\"Target MPS maximum bond dimension:\", aqc_target_mps.psi.max_bond())\n",
        "\n",
        "good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)\n",
        "starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2\n",
        "print(f\"Starting fidelity: {starting_fidelity:.6f}\")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "1f14eb3f",
      "metadata": {},
      "source": [
        "<span id=\"optimize-the-ansatz-parameters\" />\n",
        "\n",
        "#### Ottimizzare i parametri di ansatz\n",
        "\n",
        "Minimizziamo la `MaximizeStateFidelity` funzione di costo utilizzando l'ottimizzatore L-BFGS-B. L'ottimizzatore regola in modo iterativo i parametri dell'ansatz per massimizzare la corrispondenza tra il circuito dell'ansatz e l'MPS di riferimento.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "id": "6ad144d6",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "2026-05-18 13:14:49.731596 Intermediate result: Fidelity 0.99952882\n",
            "2026-05-18 13:14:49.734425 Intermediate result: Fidelity 0.99958531\n",
            "2026-05-18 13:14:49.737101 Intermediate result: Fidelity 0.99960093\n",
            "2026-05-18 13:14:49.739813 Intermediate result: Fidelity 0.99961046\n",
            "2026-05-18 13:14:49.742969 Intermediate result: Fidelity 0.99962560\n",
            "2026-05-18 13:14:49.745916 Intermediate result: Fidelity 0.99964395\n",
            "2026-05-18 13:14:49.748615 Intermediate result: Fidelity 0.99968150\n",
            "2026-05-18 13:14:49.753684 Intermediate result: Fidelity 0.99970569\n",
            "2026-05-18 13:14:49.756208 Intermediate result: Fidelity 0.99973788\n",
            "2026-05-18 13:14:49.759067 Intermediate result: Fidelity 0.99975385\n",
            "2026-05-18 13:14:49.762321 Intermediate result: Fidelity 0.99976458\n",
            "2026-05-18 13:14:49.765526 Intermediate result: Fidelity 0.99977661\n",
            "2026-05-18 13:14:49.768496 Intermediate result: Fidelity 0.99978663\n",
            "2026-05-18 13:14:49.771278 Intermediate result: Fidelity 0.99980236\n",
            "2026-05-18 13:14:49.773735 Intermediate result: Fidelity 0.99981607\n",
            "2026-05-18 13:14:49.776339 Intermediate result: Fidelity 0.99982811\n",
            "2026-05-18 13:14:49.779177 Intermediate result: Fidelity 0.99985827\n",
            "2026-05-18 13:14:49.782243 Intermediate result: Fidelity 0.99988354\n",
            "2026-05-18 13:14:49.784904 Intermediate result: Fidelity 0.99991608\n",
            "2026-05-18 13:14:49.787737 Intermediate result: Fidelity 0.99993336\n",
            "2026-05-18 13:14:49.790414 Intermediate result: Fidelity 0.99993956\n",
            "2026-05-18 13:14:49.793029 Intermediate result: Fidelity 0.99994421\n",
            "2026-05-18 13:14:49.795585 Intermediate result: Fidelity 0.99994743\n",
            "2026-05-18 13:14:49.835045 Intermediate result: Fidelity 0.99994791\n",
            "2026-05-18 13:14:49.839786 Intermediate result: Fidelity 0.99994803\n",
            "2026-05-18 13:14:49.842403 Intermediate result: Fidelity 0.99994898\n",
            "2026-05-18 13:14:49.873779 Intermediate result: Fidelity 0.99994898\n",
            "Done after 27 iterations.\n"
          ]
        }
      ],
      "source": [
        "aqc_stopping_fidelity = 1\n",
        "aqc_max_iterations = 500\n",
        "\n",
        "stopping_point = 1.0 - aqc_stopping_fidelity\n",
        "objective = MaximizeStateFidelity(\n",
        "    aqc_target_mps, aqc_ansatz, simulator_settings\n",
        ")\n",
        "\n",
        "\n",
        "def callback(intermediate_result: OptimizeResult):\n",
        "    fidelity = 1 - intermediate_result.fun\n",
        "    print(\n",
        "        f\"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}\"\n",
        "    )\n",
        "    if intermediate_result.fun < stopping_point:\n",
        "        raise StopIteration\n",
        "\n",
        "\n",
        "result = minimize(\n",
        "    objective,\n",
        "    aqc_initial_parameters,\n",
        "    method=\"L-BFGS-B\",\n",
        "    jac=True,\n",
        "    options={\"maxiter\": aqc_max_iterations},\n",
        "    callback=callback,\n",
        ")\n",
        "if result.status not in (0, 1, 99):\n",
        "    raise RuntimeError(\n",
        "        f\"Optimization failed: {result.message} (status={result.status})\"\n",
        "    )\n",
        "\n",
        "print(f\"Done after {result.nit} iterations.\")\n",
        "aqc_final_parameters = result.x"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "ea5b484a",
      "metadata": {},
      "source": [
        "<span id=\"assemble-the-final-aqc-circuit\" />\n",
        "\n",
        "#### Assemblare il circuito AQC definitivo\n",
        "\n",
        "Una volta ottenuti i parametri ottimizzati, li associamo all'ansatz e poi aggiungiamo il successivo passo di Trotter (non compresso). Il circuito risultante ha la profondità di un singolo passo di Trotter compresso più un passo non compresso, ma la porzione compressa si avvicina alla precisione di 32 passi di Trotter.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "id": "e09e40de",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/approximate-quantum-compilation-for-time-evolution/extracted-outputs/e09e40de-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "execution_count": 8,
          "metadata": {},
          "output_type": "execute_result"
        }
      ],
      "source": [
        "aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)\n",
        "aqc_final_circuit.compose(subsequent_circuit, inplace=True)\n",
        "aqc_final_circuit.draw(\"mpl\", fold=-1)"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "047511db",
      "metadata": {},
      "source": [
        "<span id=\"step-2-optimize-problem-for-quantum-hardware-execution\" />\n",
        "\n",
        "### Fase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico\n",
        "\n",
        "Per questo esempio su piccola scala, utilizziamo un backend fittizio (`FakeKyiv`) per simulare l'esecuzione hardware in locale. Trasformiamo sia il circuito ottimizzato con AQC (`aqc_final_circuit`) sia il circuito Trotter di riferimento (`baseline_circuit`, quattro passi di Trotter per l'intero tempo di evoluzione, senza AQC) nell'architettura del set di istruzioni (ISA) del backend, con `optimization_level=3` per ridurre ulteriormente la profondità del circuito.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 9,
      "id": "9e7556dd",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "AQC circuit depth: 15\n",
            "Baseline Trotter circuit depth: 27\n"
          ]
        }
      ],
      "source": [
        "backend = FakeKyiv()\n",
        "\n",
        "pass_manager = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=3\n",
        ")\n",
        "\n",
        "# Transpile the AQC-optimized circuit (compressed + subsequent step)\n",
        "isa_circuit = pass_manager.run(aqc_final_circuit)\n",
        "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
        "print(\n",
        "    \"AQC circuit depth:\",\n",
        "    isa_circuit.depth(lambda x: x.operation.num_qubits == 2),\n",
        ")\n",
        "\n",
        "# Transpile the baseline Trotter circuit (no AQC optimization)\n",
        "isa_baseline_circuit = pass_manager.run(baseline_circuit)\n",
        "isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)\n",
        "print(\n",
        "    \"Baseline Trotter circuit depth:\",\n",
        "    isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),\n",
        ")"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "60aec566",
      "metadata": {},
      "source": [
        "<span id=\"step-3-execute-using-qiskit-primitives\" />\n",
        "\n",
        "### Passaggio 3: eseguire utilizzando Qiskit primitives\n",
        "\n",
        "Utilizziamo la [`EstimatorV2`](/docs/api/qiskit-ibm-runtime/estimator-v2) primitiva con il backend fittizio per eseguire sia il circuito ottimizzato con AQC sia il circuito di Trotter di riferimento, misurando l'osservabile ZZ per ciascuno di essi.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 10,
      "id": "241e24f2",
      "metadata": {},
      "outputs": [],
      "source": [
        "estimator = Estimator(backend)\n",
        "\n",
        "# Run both circuits\n",
        "aqc_result = estimator.run([(isa_circuit, isa_observable)]).result()\n",
        "baseline_result = estimator.run(\n",
        "    [(isa_baseline_circuit, isa_baseline_observable)]\n",
        ").result()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "0b980055",
      "metadata": {},
      "source": [
        "<span id=\"step-4-post-process-and-return-result-in-desired-classical-format\" />\n",
        "\n",
        "### Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato\n",
        "\n",
        "Estraiamo i valori attesi da entrambe le simulazioni e li confrontiamo con il risultato esatto. Il circuito Trotter di riferimento mostra il risultato che otterremmo senza AQC alla stessa profondità del circuito, mentre il circuito AQC illustra il miglioramento ottenuto grazie all'ottimizzazione della rete tensoriale.\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 11,
      "id": "af07a1d9",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact:              -0.7009\n",
            "Baseline Trotter:   -0.5400, |Δ| = 0.1609  (depth 27, 4 steps)\n",
            "AQC (3+1):          -0.5728, |Δ| = 0.1281  (depth 15, compressed+subsequent)\n"
          ]
        }
      ],
      "source": [
        "aqc_expval = aqc_result[0].data.evs.tolist()\n",
        "baseline_expval = baseline_result[0].data.evs.tolist()\n",
        "\n",
        "print(f\"Exact:              {exact_expval:.4f}\")\n",
        "print(\n",
        "    f\"Baseline Trotter:   {baseline_expval:.4f}, |\\u0394| = {np.abs(exact_expval - baseline_expval):.4f}  (depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)}, {baseline_num_trotter_steps} steps)\"\n",
        ")\n",
        "print(\n",
        "    f\"AQC (3+1):          {aqc_expval:.4f}, |\\u0394| = {np.abs(exact_expval - aqc_expval):.4f}  (depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)}, compressed+subsequent)\"\n",
        ")"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 12,
      "id": "77c39ba8",
      "metadata": {},
      "outputs": [
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/approximate-quantum-compilation-for-time-evolution/extracted-outputs/77c39ba8-0.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "plt.style.use(\"seaborn-v0_8\")\n",
        "\n",
        "labels = [\n",
        "    f\"Baseline Trotter\\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})\",\n",
        "    f\"AQC (3+1)\\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})\",\n",
        "]\n",
        "values = [baseline_expval, aqc_expval]\n",
        "colors = [\"tab:orange\", \"tab:blue\"]\n",
        "\n",
        "plt.figure(figsize=(8, 5))\n",
        "bars = plt.bar(labels, values, color=colors, width=0.5)\n",
        "plt.axhline(\n",
        "    y=exact_expval,\n",
        "    color=\"tab:green\",\n",
        "    linestyle=\"--\",\n",
        "    linewidth=2,\n",
        "    label=f\"Exact ({exact_expval:.4f})\",\n",
        ")\n",
        "plt.ylabel(\"Expected Value\")\n",
        "plt.title(\n",
        "    \"AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (10-site XXZ)\"\n",
        ")\n",
        "plt.legend()\n",
        "for bar in bars:\n",
        "    y_val = bar.get_height()\n",
        "    plt.text(\n",
        "        bar.get_x() + bar.get_width() / 2.0,\n",
        "        y_val,\n",
        "        f\"{y_val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\" if y_val >= 0 else \"top\",\n",
        "    )\n",
        "plt.axhline(y=0, color=\"black\", linewidth=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "37062efa",
      "metadata": {},
      "source": [
        "<span id=\"large-scale-hardware-example\" />\n",
        "\n",
        "## Esempio di hardware su larga scala\n",
        "\n",
        "Passiamo ora a un modello XXZ con 50 siti per dimostrare l'efficacia di AQC-Tensor su un problema di dimensioni più realistiche. Il flusso di lavoro è lo stesso dell'esempio su piccola scala: comprimiamo tre passi di Trotter tramite AQC e aggiungiamo un passo non compresso.\n",
        "\n",
        "Per un sistema di queste dimensioni, l'esponenziazione matriciale non è fattibile (dimensioni $2^{50}$ ), pertanto calcoliamo il valore atteso di riferimento direttamente da un MPS ad alta precisione evoluto per l'intero intervallo di tempo.\n",
        "\n",
        "<span id=\"steps-1–4-combined\" />\n",
        "\n",
        "### Passaggi da 1 a 4 combinati\n",
        "\n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 13,
      "id": "b6c0f26a",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Target circuit:  depth 385\n",
            "Ansatz circuit:  depth 7, with 816 parameters\n",
            "Subsequent circuit: depth 12\n",
            "Baseline circuit:   depth 49 (4 steps, time=0.2667)\n",
            "Target MPS maximum bond dimension: 5\n",
            "Reference expectation value (from MPS): -0.738669\n",
            "2026-05-18 13:02:11.219150 Intermediate result: Fidelity 0.99795732\n",
            "2026-05-18 13:02:11.232256 Intermediate result: Fidelity 0.99822481\n",
            "2026-05-18 13:02:11.245160 Intermediate result: Fidelity 0.99829520\n",
            "2026-05-18 13:02:11.257765 Intermediate result: Fidelity 0.99832379\n",
            "2026-05-18 13:02:11.270280 Intermediate result: Fidelity 0.99836416\n",
            "2026-05-18 13:02:11.284116 Intermediate result: Fidelity 0.99840073\n",
            "2026-05-18 13:02:11.296856 Intermediate result: Fidelity 0.99846863\n",
            "2026-05-18 13:02:11.309602 Intermediate result: Fidelity 0.99865244\n",
            "2026-05-18 13:02:11.322012 Intermediate result: Fidelity 0.99872665\n",
            "2026-05-18 13:02:11.334195 Intermediate result: Fidelity 0.99892335\n",
            "2026-05-18 13:02:11.346570 Intermediate result: Fidelity 0.99901045\n",
            "2026-05-18 13:02:11.359202 Intermediate result: Fidelity 0.99907181\n",
            "2026-05-18 13:02:11.371511 Intermediate result: Fidelity 0.99911125\n",
            "2026-05-18 13:02:11.383870 Intermediate result: Fidelity 0.99918585\n",
            "2026-05-18 13:02:11.396184 Intermediate result: Fidelity 0.99921504\n",
            "2026-05-18 13:02:11.408543 Intermediate result: Fidelity 0.99924936\n",
            "2026-05-18 13:02:11.422557 Intermediate result: Fidelity 0.99929226\n",
            "2026-05-18 13:02:11.436275 Intermediate result: Fidelity 0.99933099\n",
            "2026-05-18 13:02:11.449511 Intermediate result: Fidelity 0.99935792\n",
            "2026-05-18 13:02:11.462093 Intermediate result: Fidelity 0.99937925\n",
            "2026-05-18 13:02:11.475783 Intermediate result: Fidelity 0.99940690\n",
            "2026-05-18 13:02:11.490254 Intermediate result: Fidelity 0.99944409\n",
            "2026-05-18 13:02:11.503292 Intermediate result: Fidelity 0.99946840\n",
            "2026-05-18 13:02:11.516064 Intermediate result: Fidelity 0.99949378\n",
            "2026-05-18 13:02:11.532861 Intermediate result: Fidelity 0.99951380\n",
            "2026-05-18 13:02:11.546182 Intermediate result: Fidelity 0.99955313\n",
            "2026-05-18 13:02:11.559168 Intermediate result: Fidelity 0.99955707\n",
            "2026-05-18 13:02:11.571753 Intermediate result: Fidelity 0.99959306\n",
            "2026-05-18 13:02:11.584257 Intermediate result: Fidelity 0.99960486\n",
            "2026-05-18 13:02:11.597610 Intermediate result: Fidelity 0.99961714\n",
            "2026-05-18 13:02:11.610106 Intermediate result: Fidelity 0.99962953\n",
            "2026-05-18 13:02:11.622515 Intermediate result: Fidelity 0.99963525\n",
            "2026-05-18 13:02:11.635543 Intermediate result: Fidelity 0.99964658\n",
            "2026-05-18 13:02:11.649044 Intermediate result: Fidelity 0.99965027\n",
            "2026-05-18 13:02:11.664148 Intermediate result: Fidelity 0.99965802\n",
            "2026-05-18 13:02:11.678033 Intermediate result: Fidelity 0.99966731\n",
            "2026-05-18 13:02:11.692714 Intermediate result: Fidelity 0.99967780\n",
            "2026-05-18 13:02:11.706753 Intermediate result: Fidelity 0.99968567\n",
            "2026-05-18 13:02:11.720780 Intermediate result: Fidelity 0.99969139\n",
            "2026-05-18 13:02:11.733471 Intermediate result: Fidelity 0.99969628\n",
            "2026-05-18 13:02:11.745998 Intermediate result: Fidelity 0.99970331\n",
            "2026-05-18 13:02:11.758424 Intermediate result: Fidelity 0.99970796\n",
            "2026-05-18 13:02:11.771986 Intermediate result: Fidelity 0.99971165\n",
            "2026-05-18 13:02:11.785841 Intermediate result: Fidelity 0.99971892\n",
            "2026-05-18 13:02:11.799105 Intermediate result: Fidelity 0.99972226\n",
            "2026-05-18 13:02:11.811623 Intermediate result: Fidelity 0.99972441\n",
            "2026-05-18 13:02:11.824114 Intermediate result: Fidelity 0.99972679\n",
            "2026-05-18 13:02:11.837179 Intermediate result: Fidelity 0.99972965\n",
            "2026-05-18 13:02:12.345479 Intermediate result: Fidelity 0.99972965\n",
            "Done after 49 iterations.\n",
            "<IBMBackend('ibm_pittsburgh')>\n",
            "AQC circuit depth: 71\n",
            "Baseline Trotter circuit depth: 111\n",
            "Job ID: d85kc6o0bvlc73d5nhn0\n"
          ]
        }
      ],
      "source": [
        "# -------------------------Step 1-------------------------\n",
        "\n",
        "# Define the 50-site spin chain\n",
        "L = 50\n",
        "edge_list = [(i - 1, i) for i in range(1, L)]\n",
        "even_edges = edge_list[::2]\n",
        "odd_edges = edge_list[1::2]\n",
        "coupling_map = CouplingMap(edge_list)\n",
        "\n",
        "# Random XXZ Hamiltonian\n",
        "np.random.seed(0)\n",
        "Js = np.random.rand(L - 1) + 0.5 * np.ones(L - 1)\n",
        "hamiltonian = SparsePauliOp(Pauli(\"I\" * L))\n",
        "for i, edge in enumerate(even_edges + odd_edges):\n",
        "    hamiltonian += SparsePauliOp.from_sparse_list(\n",
        "        [\n",
        "            (\"XX\", (edge), Js[i] / 2),\n",
        "            (\"YY\", (edge), Js[i] / 2),\n",
        "            (\"ZZ\", (edge), Js[i]),\n",
        "        ],\n",
        "        num_qubits=L,\n",
        "    )\n",
        "\n",
        "observable = SparsePauliOp.from_sparse_list(\n",
        "    [(\"ZZ\", (L // 2 - 1, L // 2), 1.0)], num_qubits=L\n",
        ")\n",
        "\n",
        "# Initial Néel state\n",
        "initial_state_circuit = QuantumCircuit(L)\n",
        "for i in range(L):\n",
        "    if i % 2:\n",
        "        initial_state_circuit.x(i)\n",
        "\n",
        "# Time parameters\n",
        "aqc_evolution_time = 0.2\n",
        "subsequent_evolution_time = aqc_evolution_time / 3\n",
        "total_evolution_time = aqc_evolution_time + subsequent_evolution_time\n",
        "\n",
        "# AQC target circuit (high-accuracy, 32 Trotter steps for AQC portion)\n",
        "aqc_target_num_trotter_steps = 32\n",
        "\n",
        "aqc_target_circuit = initial_state_circuit.copy()\n",
        "aqc_target_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),\n",
        "        time=aqc_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "\n",
        "# Generate ansatz from 1-step Trotter circuit\n",
        "aqc_good_circuit = initial_state_circuit.copy()\n",
        "aqc_good_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=1),\n",
        "        time=aqc_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "\n",
        "aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(\n",
        "    aqc_good_circuit\n",
        ")\n",
        "\n",
        "# Subsequent circuit: 1 non-compressed Trotter step\n",
        "subsequent_circuit = generate_time_evolution_circuit(\n",
        "    hamiltonian,\n",
        "    synthesis=SuzukiTrotter(reps=1),\n",
        "    time=subsequent_evolution_time,\n",
        ")\n",
        "\n",
        "# Baseline Trotter circuit: 4 Trotter steps over total evolution time, no AQC\n",
        "baseline_num_trotter_steps = 4\n",
        "baseline_circuit = initial_state_circuit.copy()\n",
        "baseline_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=baseline_num_trotter_steps),\n",
        "        time=total_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "print(\n",
        "    f\"Target circuit:  depth {aqc_target_circuit.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"Ansatz circuit:  depth {aqc_ansatz.depth(lambda x: x.operation.num_qubits == 2)}, with {len(aqc_initial_parameters)} parameters\"\n",
        ")\n",
        "print(\n",
        "    f\"Subsequent circuit: depth {subsequent_circuit.depth(lambda x: x.operation.num_qubits == 2)}\"\n",
        ")\n",
        "print(\n",
        "    f\"Baseline circuit:   depth {baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)} ({baseline_num_trotter_steps} steps, time={total_evolution_time:.4f})\"\n",
        ")\n",
        "\n",
        "# Build target MPS and compute reference expectation value\n",
        "simulator_settings = QuimbSimulator(\n",
        "    quimb.tensor.CircuitMPS, autodiff_backend=\"jax\"\n",
        ")\n",
        "aqc_target_mps = tensornetwork_from_circuit(\n",
        "    aqc_target_circuit, simulator_settings\n",
        ")\n",
        "print(\"Target MPS maximum bond dimension:\", aqc_target_mps.psi.max_bond())\n",
        "\n",
        "# For the reference expectation value, we need the full evolution (AQC + subsequent)\n",
        "# Build a high-accuracy full circuit for MPS reference\n",
        "full_target_circuit = initial_state_circuit.copy()\n",
        "full_target_circuit.compose(\n",
        "    generate_time_evolution_circuit(\n",
        "        hamiltonian,\n",
        "        synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),\n",
        "        time=total_evolution_time,\n",
        "    ),\n",
        "    inplace=True,\n",
        ")\n",
        "full_target_mps = tensornetwork_from_circuit(\n",
        "    full_target_circuit, simulator_settings\n",
        ")\n",
        "exact_expval = full_target_mps.local_expectation(\n",
        "    quimb.pauli(\"Z\") & quimb.pauli(\"Z\"), (L // 2 - 1, L // 2)\n",
        ").real.item()\n",
        "print(f\"Reference expectation value (from MPS): {exact_expval:.6f}\")\n",
        "\n",
        "# Optimize ansatz parameters\n",
        "objective = MaximizeStateFidelity(\n",
        "    aqc_target_mps, aqc_ansatz, simulator_settings\n",
        ")\n",
        "\n",
        "\n",
        "def callback(intermediate_result: OptimizeResult):\n",
        "    fidelity = 1 - intermediate_result.fun\n",
        "    print(\n",
        "        f\"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}\"\n",
        "    )\n",
        "\n",
        "\n",
        "result = minimize(\n",
        "    objective,\n",
        "    aqc_initial_parameters,\n",
        "    method=\"L-BFGS-B\",\n",
        "    jac=True,\n",
        "    options={\"maxiter\": 500},\n",
        "    callback=callback,\n",
        ")\n",
        "if result.status not in (0, 1, 99):\n",
        "    raise RuntimeError(\n",
        "        f\"Optimization failed: {result.message} (status={result.status})\"\n",
        "    )\n",
        "print(f\"Done after {result.nit} iterations.\")\n",
        "\n",
        "# Assemble the final AQC circuit: optimized ansatz + subsequent Trotter step\n",
        "aqc_final_circuit = aqc_ansatz.assign_parameters(result.x)\n",
        "aqc_final_circuit.compose(subsequent_circuit, inplace=True)\n",
        "\n",
        "# -------------------------Step 2-------------------------\n",
        "\n",
        "service = QiskitRuntimeService()\n",
        "backend = service.least_busy(min_num_qubits=127)\n",
        "print(backend)\n",
        "\n",
        "pass_manager = generate_preset_pass_manager(\n",
        "    backend=backend, optimization_level=3\n",
        ")\n",
        "isa_circuit = pass_manager.run(aqc_final_circuit)\n",
        "isa_observable = observable.apply_layout(isa_circuit.layout)\n",
        "print(\n",
        "    \"AQC circuit depth:\",\n",
        "    isa_circuit.depth(lambda x: x.operation.num_qubits == 2),\n",
        ")\n",
        "\n",
        "# Also transpile the baseline Trotter circuit (4 Trotter steps, no AQC)\n",
        "isa_baseline_circuit = pass_manager.run(baseline_circuit)\n",
        "isa_baseline_observable = observable.apply_layout(isa_baseline_circuit.layout)\n",
        "print(\n",
        "    \"Baseline Trotter circuit depth:\",\n",
        "    isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2),\n",
        ")\n",
        "\n",
        "# -------------------------Step 3-------------------------\n",
        "\n",
        "# Submit both circuits in a single job\n",
        "estimator = Estimator(backend)\n",
        "estimator.options.environment.job_tags = [\"TUT_AQCTE\"]\n",
        "\n",
        "job = estimator.run(\n",
        "    [\n",
        "        (isa_circuit, isa_observable),\n",
        "        (isa_baseline_circuit, isa_baseline_observable),\n",
        "    ]\n",
        ")\n",
        "print(\"Job ID:\", job.job_id())"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 15,
      "id": "a4dc23fd-494e-46cb-a8f5-d1cd444b96f4",
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "Exact (MPS):        -0.7387\n",
            "Baseline Trotter:   -0.5955, |Δ| = 0.1432\n",
            "AQC (3+1):          -0.6734, |Δ| = 0.0653\n"
          ]
        },
        {
          "data": {
            "text/plain": [
              "<Image src=\"/docs/images/tutorials/approximate-quantum-compilation-for-time-evolution/extracted-outputs/a4dc23fd-494e-46cb-a8f5-d1cd444b96f4-1.avif\" alt=\"Output of the previous code cell\" />"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "# -------------------------Step 4-------------------------\n",
        "\n",
        "hw_results = job.result()\n",
        "aqc_expval = hw_results[0].data.evs.tolist()\n",
        "baseline_expval = hw_results[1].data.evs.tolist()\n",
        "\n",
        "print(f\"Exact (MPS):        {exact_expval:.4f}\")\n",
        "print(\n",
        "    f\"Baseline Trotter:   {baseline_expval:.4f}, |\\u0394| = {np.abs(exact_expval - baseline_expval):.4f}\"\n",
        ")\n",
        "print(\n",
        "    f\"AQC (3+1):          {aqc_expval:.4f}, |\\u0394| = {np.abs(exact_expval - aqc_expval):.4f}\"\n",
        ")\n",
        "\n",
        "labels = [\n",
        "    f\"Baseline Trotter\\n({baseline_num_trotter_steps} steps, depth {isa_baseline_circuit.depth(lambda x: x.operation.num_qubits == 2)})\",\n",
        "    f\"AQC (3+1)\\n(depth {isa_circuit.depth(lambda x: x.operation.num_qubits == 2)})\",\n",
        "]\n",
        "values = [baseline_expval, aqc_expval]\n",
        "colors = [\"tab:orange\", \"tab:blue\"]\n",
        "\n",
        "plt.figure(figsize=(8, 5))\n",
        "bars = plt.bar(labels, values, color=colors, width=0.5)\n",
        "plt.axhline(\n",
        "    y=exact_expval,\n",
        "    color=\"tab:green\",\n",
        "    linestyle=\"--\",\n",
        "    linewidth=2,\n",
        "    label=f\"Exact ({exact_expval:.4f})\",\n",
        ")\n",
        "plt.ylabel(\"Expected Value\")\n",
        "plt.title(\n",
        "    \"AQC-Tensor (3 compressed + 1 uncompressed) vs Baseline Trotter (50-site XXZ)\"\n",
        ")\n",
        "plt.legend()\n",
        "for bar in bars:\n",
        "    y_val = bar.get_height()\n",
        "    plt.text(\n",
        "        bar.get_x() + bar.get_width() / 2.0,\n",
        "        y_val,\n",
        "        f\"{y_val:.4f}\",\n",
        "        ha=\"center\",\n",
        "        va=\"bottom\" if y_val >= 0 else \"top\",\n",
        "    )\n",
        "plt.axhline(y=0, color=\"black\", linewidth=0.3)\n",
        "plt.tight_layout()\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "278bc002",
      "metadata": {},
      "source": [
        "<span id=\"next-steps\" />\n",
        "\n",
        "## Passi successivi\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Suggerimenti\">\n",
        "  Se questo lavoro ti è sembrato interessante, potrebbero interessarti anche i seguenti contenuti:\n",
        "\n",
        "  * [Documentazione del componente aggiuntivo AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/) — include la tecnica **AQC unitaria** correlata, che ottimizza i circuiti parametrizzati per approssimare un operatore unitario di riferimento anziché uno stato preparato\n",
        "  * [Tecniche di mitigazione e soppressione degli errori](/docs/guides/error-mitigation-and-suppression-techniques)\n",
        "  * [Combinare tecniche di mitigazione degli errori](/docs/tutorials/combine-error-mitigation-techniques)\n",
        "</Admonition>\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "id": "a1b8767d",
      "source": "© IBM Corp., 2017-2026"
    }
  ],
  "metadata": {
    "description": "Learn how to use AQC-Tensor to compress Trotterized time-evolution circuits for efficient execution on quantum hardware.",
    "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"
    },
    "title": "Approximate quantum compilation for time evolution circuits",
    "hours": 1.5,
    "qpuSeconds": 15
  },
  "nbformat": 4,
  "nbformat_minor": 5
}