{
  "cells": [
    {
      "cell_type": "markdown",
      "id": "dd701ecc",
      "metadata": {},
      "source": [
        "---\n",
        "title: \"Compilation quantique approximative pour circuits d'évolution temporelle\"\n",
        "description: \"Découvrez comment utiliser AQC-Tensor pour compresser des circuits d'évolution temporelle de type Trotter afin d'optimiser leur exécution sur du matériel quantique.\"\n",
        "---\n",
        "\n",
        "{/* cspell:ignore circo Néel */}\n",
        "\n",
        "<span id=\"approximate-quantum-compilation-for-time-evolution-circuits\" />\n",
        "\n",
        "# Compilation quantique approximative pour circuits d'évolution temporelle\n",
        "\n",
        "*Estimation du temps d'exécution : 15 secondes sur un processeur Heron (REMARQUE : il s'agit uniquement d'une estimation.) (Votre temps d'exécution peut varier.)*\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "12608301",
      "metadata": {},
      "source": [
        "<span id=\"learning-outcomes\" />\n",
        "\n",
        "## Résultats d'apprentissage\n",
        "\n",
        "À l'issue de ce tutoriel, vous devriez être en mesure de comprendre les points suivants :\n",
        "\n",
        "* Comment utiliser le module complémentaire AQC-Tensor pour Qiskit afin de réduire des circuits de Trotter profonds en circuits d'ansatz peu profonds\n",
        "* Comment générer une approximation paramétrée à partir d'un circuit de Trotter et optimiser ses paramètres à l'aide de méthodes de réseaux de tenseurs (MPS)\n",
        "* Comment évaluer la fidélité d'un circuit compressé par rapport à l'évolution cible et l'exécuter sur un matériel quantique\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "c3c5992a",
      "metadata": {},
      "source": [
        "<span id=\"prerequisites\" />\n",
        "\n",
        "## Prérequis\n",
        "\n",
        "Nous vous recommandons de vous familiariser avec les sujets suivants :\n",
        "\n",
        "* [Les bases des circuits quantiques](/learning/courses/basics-of-quantum-information)\n",
        "* [Simulation hamiltonienne et trotterisation](/learning/courses/utility-scale-quantum-computing/quantum-simulation)\n",
        "* [Introduction aux primitives](/docs/guides/primitives)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "714adc39",
      "metadata": {},
      "source": [
        "<span id=\"background\" />\n",
        "\n",
        "## Arrière-plan\n",
        "\n",
        "Ce tutoriel explique comment mettre en œuvre **la compilation quantique approximative** à l'aide de réseaux de tenseurs (AQC-Tensor) avec Qiskit afin d'améliorer les performances des circuits quantiques. AQC-Tensor compresse les circuits de Trotter profonds en circuits moins profonds et mieux adaptés au matériel, tout en préservant la précision de la simulation.\n",
        "\n",
        "<span id=\"how-aqc-tensor-works\" />\n",
        "\n",
        "### Comment fonctionne AQC-Tensor\n",
        "\n",
        "Envisageons de simuler une é $H$ hamiltonienne pendant une durée totale de $t$, en utilisant des pas de Trotter de $k$. Le circuit complet de Trotter est le suivant :\n",
        "\n",
        "$$\n",
        "U_{\\text{full}} = \\left[U_{\\text{Trotter}}(t/k)\\right]^k\n",
        "$$\n",
        "\n",
        "Une approche naïve utilise un nombre réduit d'étapes de Trotter afin de maintenir la profondeur du circuit à un niveau raisonnable, mais cela entraîne une erreur de Trotter importante. AQC-Tensor résout ce dilemme en dissociant la précision de la profondeur :\n",
        "\n",
        "1. **Circuit cible (haute précision, profond) :** construire un circuit de Trotter comportant de nombreux étages — par exemple, $10k$ — pour un temps d'évolution identique. Ce circuit présente une erreur de Trotter bien moindre, mais il est trop complexe pour être mis en œuvre sur un circuit intégré. Comme elle n'est simulée de manière classique que sous la forme d'un état de produit matriciel (MPS), la profondeur n'est pas un problème.\n",
        "\n",
        "2. **Circuit d'Ansatz (faible profondeur, paramétré) :** Définit un circuit d' $V(\\theta)$ paramétré présentant la même structure qu'un circuit de Trotter à un pas. Initialisez-le de manière à ce que $V(\\theta_{\\text{init}}) = U_{\\text{Trotter}}(t/k)$, puis optimisez de manière itérative $\\theta$ afin que $V(\\theta)$ reproduise l'état cible de haute précision aussi fidèlement que possible.\n",
        "\n",
        "On obtient ainsi un circuit qui conserve la profondeur d'une seule étape de Trotter tout en atteignant la précision de plusieurs étapes, ce qui le rend viable pour le matériel quantique à court terme.\n",
        "\n",
        "<span id=\"when-to-use-aqc-tensor\" />\n",
        "\n",
        "### Quand utiliser AQC-Tensor\n",
        "\n",
        "AQC-Tensor est particulièrement efficace lorsque :\n",
        "\n",
        "* **La profondeur du circuit dépasse les temps de cohérence du matériel.** Si une simulation de Trotter nécessite plus d'étapes de Trotter que ce que l'appareil peut prendre en charge, AQC-Tensor peut compresser l'évolution en un circuit moins profond.\n",
        "* **L'intrication reste gérable d'un point de vue classique.** L'intrication totale dans un état ayant évolué dans le temps dépend principalement de la durée d'évolution $t$, et non du nombre d'étapes de Trotter $k$. Cela signifie qu'un circuit cible comportant $10k$ étapes n'est généralement pas plus difficile à représenter sous forme de MPS qu'un circuit comportant $k$ étapes, à condition que $t$ soit suffisamment court pour que les dimensions des liaisons restent gérables.\n",
        "* **Il existe une approche naturelle.** Comme cette approche reflète la structure d'un circuit de Trotter, elle offre un point de départ fondé sur la physique avec des paramètres initiaux bien définis, ce qui permet d'éviter les problèmes de convergence qui peuvent affecter les approches variationnelles arbitraires.\n",
        "\n",
        "Cette approche se distingue de la compression de circuits générique : plutôt que de chercher à approximer un opérateur unitaire arbitraire avec moins de portes logiques, AQC-Tensor conserve la même structure de portes et optimise ses paramètres afin de réduire l'erreur de Trotter. Pour plus d'informations, consultez la [documentation d'AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/).\n",
        "\n",
        "Ce tutoriel vous guide tout au long du processus complet de préparation d'états avec AQC-Tensor : définition d'un hamiltonien, génération de circuits de Trotter, compression de ceux-ci via l'optimisation par réseau de tenseurs, et exécution du résultat sur le matériel d' IBM Quantum®.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f8a05291",
      "metadata": {},
      "source": [
        "<span id=\"requirements\" />\n",
        "\n",
        "## Exigences\n",
        "\n",
        "Avant de commencer ce tutoriel, assurez-vous que les éléments suivants sont installés :\n",
        "\n",
        "* Qiskit SDK v2.0 ou version ultérieure, avec prise en charge de [la visualisation](/docs/api/qiskit/visualization)\n",
        "* Qiskit Runtime v0.22 ou plus tard (`pip install qiskit-ibm-runtime`)\n",
        "* AQC-Tensor Qiskit addon (`pip install 'qiskit-addon-aqc-tensor[aer,quimb-jax]'`)\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "f9410d21",
      "metadata": {},
      "source": [
        "<span id=\"setup\" />\n",
        "\n",
        "## Configuration\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",
        "## Exemple de simulateur à petite échelle\n",
        "\n",
        "Cette section utilise un système à 10 sites pour illustrer étape par étape le déroulement du processus AQC-Tensor. Nous simulons la dynamique d'une chaîne de spins XXZ à 10 sites, un modèle largement étudié pour l'analyse des interactions de spin et des propriétés magnétiques.\n",
        "\n",
        "L'hamiltonien s'écrit comme suit :\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",
        "où $J_{i,(i+1)}$ est un coefficient aléatoire pour l' $(i, i+1)$ de l'arête et $L=10$.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "6520161e",
      "metadata": {},
      "source": [
        "<span id=\"step-1-map-classical-inputs-to-a-quantum-problem\" />\n",
        "\n",
        "### Étape 1 : Mettre en correspondance les entrées classiques avec un problème quantique\n",
        "\n",
        "À cette étape, nous :\n",
        "\n",
        "1. Définissez l'hamiltonien, l'observable et l'état initial.\n",
        "2. Calculez la valeur attendue exacte de manière classique afin de pouvoir la comparer ultérieurement.\n",
        "3. Générez un circuit de Trotter de haute précision (la cible AQC) et compressez-le en un ansatz de faible profondeur à l'aide d'AQC-Tensor.\n",
        "\n"
      ]
    },
    {
      "cell_type": "markdown",
      "id": "bbeb5729",
      "metadata": {},
      "source": [
        "<span id=\"set-up-the-hamiltonian-observable-and-initial-state\" />\n",
        "\n",
        "#### Définir l'hamiltonien, l'observable et l'état initial\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",
        "#### Calculer la valeur exacte de l'espérance\n",
        "\n",
        "Pour un système de cette taille, nous pouvons calculer directement la valeur attendue évolutive exacte à l'aide de l'exponentiation matricielle. Cela nous sert de référence pour évaluer la précision du circuit 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",
        "#### Générer le circuit cible AQC\n",
        "\n",
        "Nous allons maintenant construire le circuit de Trotter qui servira de cible pour l'AQC. Ce circuit utilise de nombreux étages de Trotter (32) pour garantir une grande précision. Comme elle ne sera simulée que de manière classique en tant que MPS — et non exécutée sur du matériel —, sa grande profondeur ne pose pas de problème.\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",
        "#### Générer une hypothèse de départ, des paramètres initiaux, un circuit dérivé et un circuit de référence\n",
        "\n",
        "Nous construisons ensuite un « bon » circuit ayant le même temps d'évolution que la cible de l'AQC, mais comportant beaucoup moins d'étapes de Trotter (une seule). Nous transmettons ce circuit à `generate_ansatz_from_circuit`, qui renvoie :\n",
        "\n",
        "1. Un circuit **de référence** général et paramétré présentant la même connectivité à deux qubits.\n",
        "2. **Paramètres initiaux** permettant de reproduire le circuit d'entrée une fois connecté à l'ansatz.\n",
        "\n",
        "Nous construisons également :\n",
        "\n",
        "* Un **circuit supplémentaire** comportant une étape de Trotter qui sera ajouté (sans compression) après la partie optimisée par AQC, conformément à l'approche présentée dans le [tutoriel sur l'état initial d'AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/tutorials/01_initial_state_aqc.html).\n",
        "* **Un circuit de référence de Trotter** utilisant quatre étapes de Trotter sur toute la durée de l'évolution (`aqc_evolution_time + subsequent_evolution_time`). Cela sert de référence : il s'agit de ce que l'on obtiendrait sur du matériel sans AQC. L'approche AQC (3 étapes de compression + 1 étape sans compression) offre une meilleure précision à une profondeur moindre.\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",
        "#### Configurer la simulation par réseau de tenseurs et construire le MPS cible\n",
        "\n",
        "Nous utilisons le simulateur de circuits MPS (Matrix-Product State) [de Quimb](https://github.com/jcmgray/quimb), JAX assurant la différenciation automatique pour l'optimisation par gradient. Nous construisons ensuite une représentation MPS de l'état cible et évaluons la fidélité initiale entre l'hypothèse de départ et l'état cible. Comme l'instance en question est un exemple relativement simple, le niveau de fidélité de départ est assez élevé.\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",
        "#### Optimiser les paramètres de l'ansatz\n",
        "\n",
        "Nous minimisons la `MaximizeStateFidelity` fonction de coût à l'aide de l'optimiseur L-BFGS-B. L'optimiseur ajuste de manière itérative les paramètres de l'ansatz afin de maximiser la fidélité entre le circuit de l'ansatz et le MPS cible.\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",
        "#### Assembler le circuit AQC final\n",
        "\n",
        "Une fois les paramètres optimisés en main, nous les appliquons à l'ansatz, puis nous ajoutons l'étape de Trotter suivante (non compressée). Le circuit obtenu a la profondeur d'un seul pas de Trotter compressé plus un pas non compressé, mais la partie compressée offre une précision équivalente à celle de 32 pas de 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",
        "### Étape 2 : Optimiser le problème pour l'exécution sur du matériel quantique\n",
        "\n",
        "Pour cet exemple à petite échelle, nous utilisons un backend fictif (`FakeKyiv`) afin de simuler l'exécution matérielle en local. Nous transpilons à la fois le circuit optimisé par AQC (`aqc_final_circuit`) et le circuit Trotter de référence (`baseline_circuit`, quatre étapes de Trotter sur toute la durée de l'évolution, sans AQC) vers l'architecture du jeu d'instructions (ISA) du backend, avec `optimization_level=3` afin de réduire davantage la profondeur du circuit.\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",
        "### Étape 3 : Exécutez à l'aide d' Qiskit primitives\n",
        "\n",
        "Nous utilisons la [`EstimatorV2`](/docs/api/qiskit-ibm-runtime/estimator-v2) primitive avec le backend fictif pour exécuter à la fois le circuit optimisé par AQC et le circuit de référence de Trotter, en mesurant l'observable ZZ pour chacun d'eux.\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",
        "### Étape 4 : Post-traitement et restitution du résultat dans le format classique souhaité\n",
        "\n",
        "Nous extrayons les valeurs attendues des deux séries de simulations et les comparons au résultat exact. Le circuit Trotter de référence montre ce que l'on obtiendrait sans AQC à la même profondeur de circuit, tandis que le circuit AQC illustre l'amélioration apportée par l'optimisation par réseau de tenseurs.\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",
        "## Exemple de matériel à grande échelle\n",
        "\n",
        "Nous passons désormais à un modèle XXZ à 50 sites afin de présenter AQC-Tensor sur un problème d'une ampleur plus réaliste. La procédure est la même que dans l'exemple à petite échelle : nous compressons trois étapes de Trotter à l'aide de l'AQC et ajoutons une étape non compressée.\n",
        "\n",
        "Pour un système de cette taille, l'exponentiation matricielle n'est pas réalisable (dimensions $2^{50}$ ), nous calculons donc la valeur attendue de référence directement à partir d'un MPS de haute précision simulé sur toute la durée.\n",
        "\n",
        "<span id=\"steps-1–4-combined\" />\n",
        "\n",
        "### Étapes 1 à 4 combinées\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",
        "## Etapes suivantes\n",
        "\n",
        "<Admonition type=\"tip\" title=\"Recommandations\">\n",
        "  Si ce travail vous a paru intéressant, les ressources suivantes pourraient vous intéresser :\n",
        "\n",
        "  * [Documentation de l'extension AQC-Tensor](https://qiskit.github.io/qiskit-addon-aqc-tensor/) — comprend la technique **AQC unitaire** associée, qui optimise des circuits paramétrés afin d'approximer un opérateur unitaire cible plutôt qu'un état préparé\n",
        "  * [Techniques d'atténuation et de suppression des erreurs](/docs/guides/error-mitigation-and-suppression-techniques)\n",
        "  * [Combiner les techniques d'atténuation des erreurs](/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
}