diff --git a/docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits.ipynb b/docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits.ipynb index 0502a0363f9..c256dc88e9c 100644 --- a/docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits.ipynb +++ b/docs/tutorials/compilation-methods-for-hamiltonian-simulation-circuits.ipynb @@ -2,74 +2,92 @@ "cells": [ { "cell_type": "markdown", - "id": "f056ca4a-fbe4-4051-bbcd-79c2d7848cd0", + "id": "a1b2c3d4", "metadata": {}, "source": [ "---\n", "title: Compilation methods for Hamiltonian simulation circuits\n", - "description: This tutorial provides a comparative overview of three compilation methods in Qiskit for Hamiltonian simulation workloads.\n", + "description: Compare SABRE, AI transpiler, and Rustiq compilation methods on Hamiltonian simulation circuits from Hamlib.\n", "---\n", "\n", "\n", "# Compilation methods for Hamiltonian simulation circuits\n", - "Estimated QPU usage: no execution was done in this tutorial because it is focused on the transpilation process.\n", + "*Usage estimate: less than 1 minute on IBM Heron*\n", "\n", - "{/* cspell:ignore Rustiq, nshuffles, edgecolors, edgecolor, Hamlib, Benchpress, Brugière, Goubault, Martiel, Dubal, Lishman, Ivrii, fontweight, fontsize, textprops, wedgeprops, startangle, autopct */}" + "{/* cspell:ignore Rustiq, nshuffles, edgecolors, edgecolor, Hamlib, Benchpress, Brugiere, Goubault, Martiel, Dubal, Lishman, Ivrii, fontweight, fontsize, textprops, wedgeprops, startangle, autopct, Hellinger, iloc, ylabel, frameon, ylims, CMHSC */}" ] }, { "cell_type": "markdown", - "id": "d960a90b-1487-4310-a222-b95b8a77a080", + "id": "b1c2d3e4", + "metadata": {}, + "source": [ + "## Learning outcomes\n", + "After going through this tutorial, you will understand:\n", + "- How to use the Qiskit transpiler with SABRE for layout and routing optimization\n", + "- How to leverage the AI transpiler for advanced circuit optimization\n", + "- How to use the Rustiq plugin for synthesizing `PauliEvolutionGate` operations in Hamiltonian simulation circuits\n", + "- How to benchmark and compare compilation methods using two-qubit depth, total gate count, and runtime\n", + "\n", + "## Prerequisites\n", + "We suggest that you are familiar with the following topics before going through this tutorial:\n", + "- [Transpilation concepts](/docs/guides/transpile)\n", + "- [Transpiler stages](/docs/guides/transpiler-stages)\n", + "- [Transpile with pass managers](/docs/guides/transpile-with-pass-managers)" + ] + }, + { + "cell_type": "markdown", + "id": "c1d2e3f4", "metadata": {}, "source": [ "## Background\n", "\n", - "Quantum circuit compilation is a crucial step in the quantum computing workflow. It involves transforming a high-level quantum algorithm into a physical quantum circuit that adheres to the constraints of the target quantum hardware. Effective compilation can significantly impact the performance of quantum algorithms by reducing circuit depth, gate count, and execution time. This tutorial explores three distinct approaches to quantum circuit compilation in Qiskit, showcasing their strengths and applications through practical examples.\n", + "Quantum circuit compilation transforms a high-level quantum algorithm into a physical circuit that respects the constraints of the target hardware. Effective compilation can significantly reduce circuit depth and gate count, both of which directly impact the quality of results on near-term quantum devices.\n", "\n", - "The goal of this tutorial is to teach users how to apply and evaluate three compilation methods in Qiskit: the SABRE transpiler, the AI-powered transpiler, and the Rustiq plugin. Users will learn how to use each method effectively and how to benchmark their performance across different quantum circuits. By the end of this tutorial, users will be able to choose and tailor compilation strategies based on specific optimization goals such as reducing circuit depth, minimizing gate count, or improving runtime.\n", + "This tutorial benchmarks three compilation methods on Hamiltonian simulation circuits built with `PauliEvolutionGate`. These circuits model pairwise qubit interactions (such as $ZZ$, $XX$, and $YY$ terms) and are common in quantum chemistry, condensed matter physics, and materials science.\n", "\n", - "### What you will learn\n", - "- **How to use the Qiskit transpiler with SABRE for layout and routing optimization.**\n", - "- **How to leverage the AI transpiler for advanced, automated circuit optimization.**\n", - "- **How to employ the Rustiq plugin for circuits requiring precise synthesis of operations, particularly in Hamiltonian simulation tasks.**\n", - "\n", - "This tutorial uses three example circuits following the [Qiskit patterns](/docs/guides/intro-to-patterns) workflow to illustrate the performance of each compilation method. By the end of this tutorial, users will be equipped to choose the appropriate compilation strategy based on their specific requirements and constraints.\n", + "The benchmark circuits come from the [Hamlib](https://github.com/SRI-International/QC-App-Oriented-Benchmarks/tree/master/hamlib) collection, accessed through the [Benchpress](https://github.com/Qiskit/benchpress) repository. Hamlib provides a standardized set of representative Hamiltonians, making it possible to compare compilation strategies on realistic simulation workloads.\n", "\n", "### Compilation methods overview\n", "\n", - "#### 1. **Qiskit transpiler with SABRE**\n", - "The Qiskit transpiler uses the SABRE (SWAP-based BidiREctional heuristic search) algorithm to optimize circuit layout and routing. SABRE focuses on minimizing SWAP gates and their impact on circuit depth while adhering to hardware connectivity constraints. This method is highly versatile and suitable for general-purpose circuit optimization, providing a balance between performance and computation time. To take advantage of the latest improvements in SABRE, detailed in [\\[1\\]](https://arxiv.org/abs/2409.08368), you can increase the number of trials (for example, `layout_trials=400, swap_trials=400`). For the purposes of this tutorial, we will use the default values for the number of trials in order to compare to Qiskit's default transpiler. The advantages and parameter exploration of SABRE are covered in a separate [deep-dive tutorial](/docs/tutorials/transpilation-optimizations-with-sabre).\n", - "\n", - "#### 2. **AI transpiler**\n", + "#### Qiskit transpiler with SABRE\n", + "The Qiskit transpiler uses the SABRE (SWAP-based BidiREctional heuristic search) algorithm to optimize circuit layout and routing. SABRE focuses on minimizing SWAP gates and their impact on circuit depth while respecting hardware connectivity constraints. It is a general-purpose method that provides a good balance between performance and compilation time. For more details, see [\\[1\\]](https://arxiv.org/abs/2409.08368). The advantages and parameter exploration of SABRE are covered in a separate [deep-dive tutorial](/docs/tutorials/transpilation-optimizations-with-sabre).\n", "\n", - "The AI-powered transpiler in Qiskit uses machine learning to predict optimal transpilation strategies by analyzing patterns in circuit structure and hardware constraints to select the best sequence of optimizations for a given input. This method is particularly effective for large-scale quantum circuits, offering a high degree of automation and adaptability to diverse problem types. In addition to general circuit optimization, the AI transpiler can be used with the `AIPauliNetworkSynthesis` pass, which targets Pauli network circuits — blocks composed of H, S, SX, CX, RX, RY, and RZ gates — and applies a reinforcement learning-based synthesis approach. For more information on the AI transpiler and its synthesis strategies, see [\\[2\\]](https://arxiv.org/abs/2405.13196) and [\\[3\\]](https://arxiv.org/abs/2503.14448).\n", + "#### AI transpiler\n", + "The AI-powered transpiler uses machine learning to predict optimal transpilation strategies by analyzing patterns in circuit structure and hardware constraints. It can also apply the `AIPauliNetworkSynthesis` pass, which targets Pauli network circuits using a reinforcement learning-based synthesis approach. For more information, see [\\[2\\]](https://arxiv.org/abs/2405.13196) and [\\[3\\]](https://arxiv.org/abs/2503.14448).\n", "\n", + "#### Rustiq plugin\n", + "The Rustiq plugin provides advanced synthesis techniques specifically for `PauliEvolutionGate` operations, which represent Pauli rotations commonly used in Trotterized dynamics. It is designed to produce low-depth circuit decompositions for Hamiltonian simulation workloads. For more details, see [\\[4\\]](https://arxiv.org/abs/2404.03280).\n", "\n", + "### Key metrics\n", "\n", - "#### 3. **Rustiq plugin**\n", - "The Rustiq plugin introduces advanced synthesis techniques specifically for `PauliEvolutionGate` operations, which represent Pauli rotations commonly used in Trotterized dynamics. This plugin is valuable for circuits implementing Hamiltonian simulation, such as those used in quantum chemistry and physics problems, where accurate Pauli rotations are essential for simulating problem Hamiltonians effectively. Rustiq offers precise, low-depth circuit synthesis for these specialized operations. For more details about the implementation and performance of Rustiq, please refer to [\\[4\\]](https://arxiv.org/abs/2404.03280).\n", - "\n", - "By exploring these compilation methods in depth, this tutorial provides users with the tools to enhance the performance of their quantum circuits, paving the way for more efficient and practical quantum computations." + "We compare the three methods on the following metrics:\n", + "- **Two-qubit depth**: The depth of the circuit counting only two-qubit gates. This is often the bottleneck for fidelity on real hardware.\n", + "- **Circuit size (total gate count)**: The total number of gates in the transpiled circuit.\n", + "- **Runtime**: The wall-clock time for transpilation." ] }, { "cell_type": "markdown", - "id": "53c589f4-c63c-47f3-8642-1f189e445307", + "id": "d1e2f3a4", "metadata": {}, "source": [ "## Requirements\n", "\n", "Before starting this tutorial, be sure you have the following installed:\n", - "- Qiskit SDK v1.3 or later, with [visualization](/docs/api/qiskit/visualization) support\n", - "- Qiskit Runtime v0.28 or later (`pip install qiskit-ibm-runtime`)\n", + "\n", + "- Qiskit SDK v2.0 or later, with [visualization](/docs/api/qiskit/visualization) support\n", + "- Qiskit Runtime v0.22 or later (`pip install qiskit-ibm-runtime`)\n", + "- Qiskit Aer (`pip install qiskit-aer`)\n", "- Qiskit IBM Transpiler (`pip install qiskit-ibm-transpiler`)\n", "- Qiskit AI Transpiler local mode (`pip install qiskit_ibm_ai_local_transpiler`)\n", - "- Networkx graph library (`pip install networkx`)" + "- Networkx (`pip install networkx`)" ] }, { "cell_type": "markdown", - "id": "4f79e4a8-48bc-4af8-a172-f0057fa851eb", + "id": "e1f2a3b4", "metadata": {}, "source": [ "## Setup" @@ -78,184 +96,131 @@ { "cell_type": "code", "execution_count": 1, - "id": "1ecd9900-e511-486e-97be-aac5f75b1917", + "id": "f1a2b3c4", "metadata": {}, "outputs": [], "source": [ "from qiskit.circuit import QuantumCircuit\n", - "from qiskit_ibm_runtime import QiskitRuntimeService\n", - "from qiskit.circuit.library import (\n", - " efficient_su2,\n", - " PauliEvolutionGate,\n", - ")\n", + "from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2\n", + "from qiskit.circuit.library import PauliEvolutionGate\n", "from qiskit_ibm_transpiler import generate_ai_pass_manager\n", "from qiskit.quantum_info import SparsePauliOp\n", "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", "from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig\n", + "from qiskit_aer import AerSimulator\n", + "from qiskit_aer.noise import NoiseModel, depolarizing_error\n", "from collections import Counter\n", - "from IPython.display import display\n", + "from statistics import mean, stdev\n", + "from scipy.sparse import SparseEfficiencyWarning\n", "import time\n", - "import pandas as pd\n", + "import warnings\n", "import matplotlib.pyplot as plt\n", + "import matplotlib.ticker as ticker\n", "import numpy as np\n", "import json\n", "import requests\n", "import logging\n", "\n", - "# Suppress noisy loggers\n", + "# Suppress noisy loggers and warnings\n", "logging.getLogger(\n", " \"qiskit_ibm_transpiler.wrappers.ai_local_synthesis\"\n", ").setLevel(logging.ERROR)\n", + "warnings.filterwarnings(\"ignore\", category=FutureWarning)\n", + "warnings.filterwarnings(\"ignore\", category=SparseEfficiencyWarning)\n", "\n", "seed = 42 # Seed for reproducibility" ] }, { "cell_type": "markdown", - "id": "c3a31b4d-d679-4657-b372-ba19bcaf8eca", - "metadata": {}, - "source": [ - "## Part 1: Efficient SU2 Circuit\n", - "\n", - "### Step 1: Map classical inputs to a quantum problem\n", - "\n", - "In this section, we explore the `efficient_su2` circuit, a hardware-efficient ansatz commonly used in variational quantum algorithms (such as VQE) and quantum machine-learning tasks. The circuit consists of alternating layers of single-qubit rotations and entangling gates arranged in a circular pattern, designed to explore the quantum state space effectively while maintaining manageable depth.\n", - "\n", - "We will begin by constructing one `efficient_su2` circuit to demonstrate how to compare different compilation methods. After Part 1, we will expand our analysis to a larger set of circuits, enabling a comprehensive benchmark for evaluating the performance of various compilation techniques." - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "f362cdac-94d8-4cc5-85f4-015c3d9eba3a", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "execution_count": 2, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "qubit_size = list(range(10, 101, 10))\n", - "qc_su2_list = [\n", - " efficient_su2(n, entanglement=\"circular\", reps=1)\n", - " .decompose()\n", - " .copy(name=f\"SU2_{n}\")\n", - " for n in qubit_size\n", - "]\n", - "\n", - "# Draw the first circuit\n", - "qc_su2_list[0].draw(output=\"mpl\")" - ] - }, - { - "cell_type": "markdown", - "id": "d6671456-9b17-42bb-b94a-d42a29e6fad9", + "id": "a2b3c4d5", "metadata": {}, "source": [ - "### Step 2: Optimize problem for quantum hardware execution\n", + "### Connect to a backend\n", "\n", - "This step is the main focus of the tutorial. Here, we aim to optimize quantum circuits for efficient execution on real quantum hardware. Our primary objective is to reduce circuit depth and gate count, which are key factors in improving execution fidelity and mitigating hardware noise.\n", - "\n", - "- **SABRE transpiler**: Uses Qiskit’s default transpiler with the SABRE layout and routing algorithm.\n", - "- **AI transpiler (local mode)**: The standard AI-powered transpiler using local inference and the default synthesis strategy.\n", - "- **Rustiq plugin**: A transpiler plugin designed for low-depth compilation tailored to Hamiltonian simulation tasks.\n", - "\n", - "The goal of this step is to compare the results of these methods in terms of the transpiled circuit’s depth and gate count. Another important metric we consider is the transpilation runtime. By analyzing these metrics, we can evaluate the relative strengths of each method and determine which produces the most efficient circuit for execution on the selected hardware.\n", - "\n", - "Note: For the initial SU2 circuit example, we will only compare the SABRE transpiler to the default AI transpiler. However, in the subsequent benchmark using Hamlib circuits, we will compare all three transpilation methods." + "Select a backend that will be used for both the small-scale and large-scale examples. The backend determines the coupling map and basis gates that the transpiler targets." ] }, { "cell_type": "code", "execution_count": null, - "id": "c1ce1ad9-d529-49a1-91df-b540603ceb88", + "id": "b2c3d4e5", "metadata": {}, "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "qiskit_runtime_service._get_crn_from_instance_name:WARNING:2025-07-30 21:46:30,843: Multiple instances found. Using all matching instances.\n" - ] - }, { "name": "stdout", "output_type": "stream", "text": [ - "Using backend: \n" + "Using backend: ibm_pittsburgh\n" ] } ], "source": [ "# QiskitRuntimeService.save_account(channel=\"ibm_quantum_platform\", token=\"\", overwrite=True, set_as_default=True)\n", "service = QiskitRuntimeService(channel=\"ibm_quantum_platform\")\n", - "backend = service.backend(\"ibm_torino\")\n", - "print(f\"Using backend: {backend}\")" + "backend = service.least_busy(operational=True, simulator=False)\n", + "print(f\"Using backend: {backend.name}\")" ] }, { "cell_type": "markdown", - "id": "381cfaca-103b-41bc-b3b3-f76808f44638", + "id": "c2d3e4f5", "metadata": {}, "source": [ - "Qiskit transpiler with SABRE:" + "### Define pass managers\n", + "\n", + "Then set up the three compilation methods." ] }, { "cell_type": "code", - "execution_count": 4, - "id": "92a8f12d-3f97-400d-b6b6-a8c717f9ff0f", + "execution_count": 3, + "id": "d2e3f4a5", "metadata": {}, "outputs": [], "source": [ + "# SABRE pass manager (Qiskit default at optimization level 3)\n", "pm_sabre = generate_preset_pass_manager(\n", " optimization_level=3, backend=backend, seed_transpiler=seed\n", ")" ] }, - { - "cell_type": "markdown", - "id": "d1ae6e70-f276-41f3-8e40-68a9523eae06", - "metadata": {}, - "source": [ - "AI transpiler:" - ] - }, { "cell_type": "code", - "execution_count": 5, - "id": "6c27960f-e1cd-4cb5-b807-4dace42ea970", + "execution_count": 4, + "id": "e2f3a4b5", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "e14d6bb64d3e4a399a645a5437a4a1cb", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Fetching 127 files: 0%| | 0/127 [00:00{col_w}}\" for _, label in metrics\n", + " )\n", "\n", - "tqc_sabre = capture_transpilation_metrics(\n", - " results_su2, pm_sabre, qc_su2_list, \"sabre\"\n", - ")\n", - "tqc_ai = capture_transpilation_metrics(results_su2, pm_ai, qc_su2_list, \"ai\")" + " print(\"Mean +/- std per compilation method\")\n", + " print(header)\n", + " print(\"-\" * len(header))\n", + " for method in methods:\n", + " cells = []\n", + " for key, _ in metrics:\n", + " values = [r[key] for r in by_method[method]]\n", + " std = stdev(values) if len(values) > 1 else 0.0\n", + " cells.append(f\"{mean(values):,.1f} +/- {std:,.1f}\")\n", + " print(\n", + " f\"{method:<{name_w}}\" + \"\".join(f\" {c:>{col_w}}\" for c in cells)\n", + " )\n", + "\n", + " others = [m for m in methods if m != \"SABRE\"]\n", + " if others and sabre_by_index:\n", + " print()\n", + " print(\"Mean % improvement vs SABRE (positive = better than SABRE)\")\n", + " print(header)\n", + " print(\"-\" * len(header))\n", + " for method in others:\n", + " cells = []\n", + " for key, _ in metrics:\n", + " pct = [\n", + " (sabre_by_index[r[\"qc_index\"]][key] - r[key])\n", + " / sabre_by_index[r[\"qc_index\"]][key]\n", + " * 100\n", + " for r in by_method[method]\n", + " if sabre_by_index.get(r[\"qc_index\"])\n", + " and sabre_by_index[r[\"qc_index\"]][key]\n", + " ]\n", + " if pct:\n", + " std = stdev(pct) if len(pct) > 1 else 0.0\n", + " cells.append(f\"{mean(pct):+.1f}% +/- {std:.1f}%\")\n", + " else:\n", + " cells.append(\"n/a\")\n", + " print(\n", + " f\"{method:<{name_w}}\"\n", + " + \"\".join(f\" {c:>{col_w}}\" for c in cells)\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "per_circuit_table_fn", + "metadata": {}, + "outputs": [], + "source": [ + "def print_per_circuit_comparison(results, num_rows=5):\n", + " \"\"\"\n", + " Print a per-metric comparison of the compilation methods for the\n", + " first ``num_rows`` circuits (sorted by qubit count). The best\n", + " (lowest) value for each metric is marked with an asterisk.\n", + " \"\"\"\n", + " metrics = [\n", + " (\"two_qubit_depth\", \"2Q Depth\"),\n", + " (\"size\", \"Gate Count\"),\n", + " (\"runtime\", \"Runtime (s)\"),\n", + " ]\n", + " methods = _method_order(results)\n", + "\n", + " by_index = {}\n", + " for r in results:\n", + " by_index.setdefault(r[\"qc_index\"], {})[r[\"method\"]] = r\n", + " ordered = sorted(\n", + " by_index.items(),\n", + " key=lambda kv: (next(iter(kv[1].values()))[\"num_qubits\"], kv[0]),\n", + " )[:num_rows]\n", + "\n", + " for key, label in metrics:\n", + " print(f\"{label} (first {num_rows} circuits by qubit count); * = best\")\n", + " header = f\"{'Idx':>3} {'Circuit':<16} {'Q':>3}\" + \"\".join(\n", + " f\"{m:>9}\" for m in methods\n", + " )\n", + " print(header)\n", + " print(\"-\" * len(header))\n", + " for idx, method_map in ordered:\n", + " any_record = next(iter(method_map.values()))\n", + " present = {\n", + " m: method_map[m][key] for m in methods if m in method_map\n", + " }\n", + " best = min(present.values())\n", + " line = (\n", + " f\"{idx:>3} {any_record['qc_name'][:16]:<16} \"\n", + " f\"{any_record['num_qubits']:>3}\"\n", + " )\n", + " for m in methods:\n", + " value = method_map[m][key]\n", + " text = f\"{value:.2f}\" if key == \"runtime\" else f\"{int(value)}\"\n", + " if value == best:\n", + " text += \"*\"\n", + " line += f\"{text:>9}\"\n", + " print(line)\n", + " print()" ] }, { "cell_type": "markdown", - "id": "a2d68a18-6656-4858-b439-7f9acbb516bb", + "id": "c3d4e5f6", "metadata": {}, "source": [ - "Display transpiled results of one of the circuit." + "### Load Hamiltonian circuits from Hamlib\n", + "\n", + "We load a representative set of Hamiltonians from the Benchpress repository and construct `PauliEvolutionGate` circuits. Circuits that exceed the backend's qubit count are removed, along with circuits whose decomposed size exceeds 1,500 gates (to keep transpilation times reasonable)." ] }, { "cell_type": "code", "execution_count": 9, - "id": "37924fc2-8fb6-451a-b8f9-cd79573f2384", + "id": "d3e4f5a6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Sabre transpilation\n" - ] - }, - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "AI transpilation\n" + "Total Hamiltonian circuits loaded: 42\n", + "Qubit range: 2 to 112\n" ] - }, - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "metadata": {}, - "output_type": "display_data" } ], "source": [ - "print(\"Sabre transpilation\")\n", - "display(tqc_sabre[0].draw(\"mpl\", fold=-1, idle_wires=False))\n", - "print(\"AI transpilation\")\n", - "display(tqc_ai[0].draw(\"mpl\", fold=-1, idle_wires=False))" + "# Obtain the Hamiltonian JSON from the benchpress repository\n", + "url = \"https://raw.githubusercontent.com/Qiskit/benchpress/e7b29ef7be4cc0d70237b8fdc03edbd698908eff/benchpress/hamiltonian/hamlib/100_representative.json\"\n", + "response = requests.get(url)\n", + "response.raise_for_status()\n", + "ham_records = json.loads(response.text)\n", + "\n", + "# Remove circuits that are too large for the backend\n", + "ham_records = [\n", + " h for h in ham_records if h[\"ham_qubits\"] <= backend.num_qubits\n", + "]\n", + "\n", + "# Build PauliEvolutionGate circuits\n", + "qc_ham_list = []\n", + "for h in ham_records:\n", + " terms = h[\"ham_hamlib_hamiltonian_terms\"]\n", + " coeff = h[\"ham_hamlib_hamiltonian_coefficients\"]\n", + " num_qubits = h[\"ham_qubits\"]\n", + " name = h[\"ham_problem\"]\n", + "\n", + " evo_gate = PauliEvolutionGate(SparsePauliOp(terms, coeff))\n", + " qc = QuantumCircuit(num_qubits)\n", + " qc.name = name\n", + " qc.append(evo_gate, range(num_qubits))\n", + " qc_ham_list.append(qc)\n", + "\n", + "# Remove circuits whose decomposed size exceeds 1500 gates so that transpilation completes in a reasonable time frame\n", + "qc_ham_list = [qc for qc in qc_ham_list if qc.decompose().size() <= 1500]\n", + "\n", + "print(f\"Total Hamiltonian circuits loaded: {len(qc_ham_list)}\")\n", + "print(\n", + " f\"Qubit range: {min(qc.num_qubits for qc in qc_ham_list)} to {max(qc.num_qubits for qc in qc_ham_list)}\"\n", + ")" ] }, { "cell_type": "markdown", - "id": "1633a4f4-7b01-4eb5-9764-0959f209a077", + "id": "e3f4a5b6", "metadata": {}, "source": [ - "Results table:" + "Split circuits into small-scale (fewer than 20 qubits) and large-scale (20 or more qubits) groups." ] }, { "cell_type": "code", "execution_count": 10, - "id": "a5911224-3d1d-490d-b730-9cb90f954498", + "id": "f3a4b5c6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - " depth size runtime\n", - "method \n", - "ai 56.4 852.5 45.89\n", - "sabre 64.6 864.9 52.57\n" + "Small-scale circuits (<20 qubits): 20\n", + "Large-scale circuits (>=20 qubits): 22\n" ] - }, - { - "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
methodqc_nameqc_indexnum_qubitsopsdepthsizeruntime
0sabreSU2_10010{'rz': 81, 'sx': 70, 'cz': 16}131670.058845
1sabreSU2_20120{'rz': 160, 'sx': 119, 'cz': 20}202990.238217
2sabreSU2_30230{'sx': 295, 'rz': 242, 'cz': 90}7262710.723922
3sabreSU2_40340{'rz': 320, 'sx': 239, 'cz': 40}4059916.159262
4sabreSU2_50450{'rz': 402, 'sx': 367, 'cz': 86}7785576.886604
5sabreSU2_60560{'rz': 480, 'sx': 359, 'cz': 60}6089986.118255
6sabreSU2_70670{'rz': 562, 'sx': 441, 'cz': 82}79108594.458287
7sabreSU2_80780{'rz': 640, 'sx': 479, 'cz': 80}80119969.048184
8sabreSU2_90890{'rz': 721, 'sx': 585, 'cz': 114}105142088.254809
9sabreSU2_1009100{'rz': 800, 'sx': 599, 'cz': 100}100149983.795482
10aiSU2_10010{'rz': 81, 'sx': 71, 'cz': 16}101680.171532
11aiSU2_20120{'rz': 160, 'sx': 119, 'cz': 20}202990.291691
12aiSU2_30230{'sx': 243, 'rz': 242, 'cz': 63}3654813.555931
13aiSU2_40340{'rz': 320, 'sx': 239, 'cz': 40}4059915.952733
14aiSU2_50450{'rz': 403, 'sx': 346, 'cz': 74}5482380.702141
15aiSU2_60560{'rz': 480, 'sx': 359, 'cz': 60}6089975.993404
16aiSU2_70670{'rz': 563, 'sx': 442, 'cz': 82}74108764.960162
17aiSU2_80780{'rz': 640, 'sx': 479, 'cz': 80}80119968.253280
18aiSU2_90890{'rz': 721, 'sx': 575, 'cz': 108}90140475.072412
19aiSU2_1009100{'rz': 800, 'sx': 599, 'cz': 100}100149963.967446
\n", - "
" - ], - "text/plain": [ - " method qc_name qc_index num_qubits ops \\\n", - "0 sabre SU2_10 0 10 {'rz': 81, 'sx': 70, 'cz': 16} \n", - "1 sabre SU2_20 1 20 {'rz': 160, 'sx': 119, 'cz': 20} \n", - "2 sabre SU2_30 2 30 {'sx': 295, 'rz': 242, 'cz': 90} \n", - "3 sabre SU2_40 3 40 {'rz': 320, 'sx': 239, 'cz': 40} \n", - "4 sabre SU2_50 4 50 {'rz': 402, 'sx': 367, 'cz': 86} \n", - "5 sabre SU2_60 5 60 {'rz': 480, 'sx': 359, 'cz': 60} \n", - "6 sabre SU2_70 6 70 {'rz': 562, 'sx': 441, 'cz': 82} \n", - "7 sabre SU2_80 7 80 {'rz': 640, 'sx': 479, 'cz': 80} \n", - "8 sabre SU2_90 8 90 {'rz': 721, 'sx': 585, 'cz': 114} \n", - "9 sabre SU2_100 9 100 {'rz': 800, 'sx': 599, 'cz': 100} \n", - "10 ai SU2_10 0 10 {'rz': 81, 'sx': 71, 'cz': 16} \n", - "11 ai SU2_20 1 20 {'rz': 160, 'sx': 119, 'cz': 20} \n", - "12 ai SU2_30 2 30 {'sx': 243, 'rz': 242, 'cz': 63} \n", - "13 ai SU2_40 3 40 {'rz': 320, 'sx': 239, 'cz': 40} \n", - "14 ai SU2_50 4 50 {'rz': 403, 'sx': 346, 'cz': 74} \n", - "15 ai SU2_60 5 60 {'rz': 480, 'sx': 359, 'cz': 60} \n", - "16 ai SU2_70 6 70 {'rz': 563, 'sx': 442, 'cz': 82} \n", - "17 ai SU2_80 7 80 {'rz': 640, 'sx': 479, 'cz': 80} \n", - "18 ai SU2_90 8 90 {'rz': 721, 'sx': 575, 'cz': 108} \n", - "19 ai SU2_100 9 100 {'rz': 800, 'sx': 599, 'cz': 100} \n", - "\n", - " depth size runtime \n", - "0 13 167 0.058845 \n", - "1 20 299 0.238217 \n", - "2 72 627 10.723922 \n", - "3 40 599 16.159262 \n", - "4 77 855 76.886604 \n", - "5 60 899 86.118255 \n", - "6 79 1085 94.458287 \n", - "7 80 1199 69.048184 \n", - "8 105 1420 88.254809 \n", - "9 100 1499 83.795482 \n", - "10 10 168 0.171532 \n", - "11 20 299 0.291691 \n", - "12 36 548 13.555931 \n", - "13 40 599 15.952733 \n", - "14 54 823 80.702141 \n", - "15 60 899 75.993404 \n", - "16 74 1087 64.960162 \n", - "17 80 1199 68.253280 \n", - "18 90 1404 75.072412 \n", - "19 100 1499 63.967446 " - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ - "summary_su2 = (\n", - " results_su2.groupby(\"method\")[[\"depth\", \"size\", \"runtime\"]]\n", - " .mean()\n", - " .round(2)\n", - ")\n", - "print(summary_su2)\n", + "qc_small = [qc for qc in qc_ham_list if qc.num_qubits < 20]\n", + "qc_large = [qc for qc in qc_ham_list if qc.num_qubits >= 20]\n", "\n", - "results_su2" + "print(f\"Small-scale circuits (<20 qubits): {len(qc_small)}\")\n", + "print(f\"Large-scale circuits (>=20 qubits): {len(qc_large)}\")" ] }, { "cell_type": "markdown", - "id": "167834f1-00a7-4190-99e4-d221d1952357", + "id": "a4b5c6d7", "metadata": {}, "source": [ - "#### Results graph\n", - "\n", - "As we define a function to consistently capture metrics, we will also define one to graph the metrics. Here, we will plot the two-qubit depth, gate count, and runtime for each compilation method across the circuits." + "Preview one of the small-scale Hamiltonian circuits before transpilation." ] }, { "cell_type": "code", "execution_count": 11, - "id": "d90fb4fa-e031-40f6-90e6-e1208a855bec", - "metadata": {}, - "outputs": [], - "source": [ - "def plot_transpilation_metrics(results, overall_title, x_axis=\"qc_index\"):\n", - " \"\"\"\n", - " Plots transpilation metrics (depth, size, runtime) for different transpilation methods.\n", - "\n", - " Parameters:\n", - " results (DataFrame): Data containing columns ['num_qubits', 'method', 'depth', 'size', 'runtime']\n", - " overall_title (str): The title of the overall figure.\n", - " x_axis (str): The x-axis label, either 'num_qubits' or 'qc_index'.\n", - " \"\"\"\n", - "\n", - " fig, axs = plt.subplots(1, 3, figsize=(24, 6))\n", - " metrics = [\"depth\", \"size\", \"runtime\"]\n", - " titles = [\"Circuit Depth\", \"Circuit Size\", \"Transpilation Runtime\"]\n", - " y_labels = [\"Depth\", \"Size (Gate Count)\", \"Runtime (s)\"]\n", - "\n", - " methods = results[\"method\"].unique()\n", - " colors = plt.colormaps[\"tab10\"]\n", - " markers = [\"o\", \"^\", \"s\", \"D\", \"P\", \"*\", \"X\", \"v\"]\n", - " color_list = [colors(i % colors.N) for i in range(len(methods))]\n", - " color_map = {method: color_list[i] for i, method in enumerate(methods)}\n", - " marker_map = {\n", - " method: markers[i % len(markers)] for i, method in enumerate(methods)\n", - " }\n", - " jitter_factor = 0.1 # Small x-axis jitter for visibility\n", - " handles, labels = [], [] # Unique handles for legend\n", - "\n", - " # Plot each metric\n", - " for i, metric in enumerate(metrics):\n", - " for method in methods:\n", - " method_data = results[results[\"method\"] == method]\n", - "\n", - " # Introduce slight jitter to avoid exact overlap\n", - " jitter = np.random.uniform(\n", - " -jitter_factor, jitter_factor, len(method_data)\n", - " )\n", - "\n", - " scatter = axs[i].scatter(\n", - " method_data[x_axis] + jitter,\n", - " method_data[metric],\n", - " color=color_map[method],\n", - " label=method,\n", - " marker=marker_map[method],\n", - " alpha=0.7,\n", - " edgecolors=\"black\",\n", - " s=80,\n", - " )\n", - "\n", - " if method not in labels:\n", - " handles.append(scatter)\n", - " labels.append(method)\n", - "\n", - " axs[i].set_title(titles[i])\n", - " axs[i].set_xlabel(x_axis)\n", - " axs[i].set_ylabel(y_labels[i])\n", - " axs[i].grid(axis=\"y\", linestyle=\"--\", alpha=0.7)\n", - " axs[i].tick_params(axis=\"x\", rotation=45)\n", - " axs[i].set_xticks(sorted(results[x_axis].unique()))\n", - "\n", - " fig.suptitle(overall_title, fontsize=16)\n", - " fig.legend(\n", - " handles=handles,\n", - " labels=labels,\n", - " loc=\"upper right\",\n", - " bbox_to_anchor=(1.05, 1),\n", - " )\n", - "\n", - " plt.tight_layout()\n", - " plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "7f7b502a-8ed6-45fa-a698-02977149e283", + "id": "b4c5d6e7", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, + "execution_count": 11, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "plot_transpilation_metrics(\n", - " results_su2, \"Transpilation Metrics for SU2 Circuits\", x_axis=\"num_qubits\"\n", - ")" + "# We decompose the circuit here, otherwise it would just be a PauliEvolutionGate box,\n", + "# which isn't very informative to look at!\n", + "qc_small[0].decompose().draw(\"mpl\", fold=-1)" ] }, { "cell_type": "markdown", - "id": "f75e8e3a-803f-4386-a60a-b088faa3c81a", + "id": "sec-small-scale", "metadata": {}, "source": [ - "#### Analysis of SU2 circuit compilation results\n", - "\n", - "In this experiment, we compare two transpilation methods — Qiskit's SABRE transpiler and the AI-powered transpiler — on a set of `efficient_su2` circuits. Since these circuits do not include any `PauliEvolutionGate` operations, the Rustiq plugin is not included in this comparison.\n", + "## Small-scale example\n", "\n", - "On average, the AI transpiler performs better in terms of circuit depth, with a greater than 10% improvement across the full range of SU2 circuits. For gate count (circuit size) and transpilation runtime, both methods yield similar results overall.\n", - "\n", - "However, inspecting the individual data points reveals a deeper insight:\n", - "- For most qubit sizes, both SABRE and AI produce nearly identical results, suggesting that in many cases, both methods converge to similarly efficient solutions.\n", - "- For certain circuit sizes, specifically at 30, 50, 70, and 90 qubits, the AI transpiler finds significantly shallower circuits than SABRE. This indicates that AI's learning-based approach is able to discover more optimal layouts or routing paths in cases where the SABRE heuristic does not.\n", - "\n", - "This behavior highlights an important takeaway:\n", - "> While SABRE and AI often produce comparable results, the AI transpiler can occasionally discover much better solutions, particularly in terms of depth, which can lead to significantly improved performance on hardware." + "In this section, we benchmark the three compilation methods on Hamiltonian circuits with fewer than 20 qubits. These circuits transpile quickly and provide a clear view of how each method handles circuits of moderate complexity." ] }, { "cell_type": "markdown", - "id": "4c3b2aa8-8187-488a-8e5b-197cf26085bb", + "id": "c4d5e6f7", "metadata": {}, "source": [ - "## Part 2: Hamiltonian simulation circuit\n", - "\n", - "### Step 1: Investigate circuits with `PauliEvolutionGate`\n", - "\n", - "In this section, we investigate quantum circuits constructed using `PauliEvolutionGate`, which enables efficient simulation of Hamiltonians. We will analyze how different compilation methods optimize these circuits across various Hamiltonians.\n", - "\n", - "#### Hamiltonians used in the benchmark\n", - "\n", - "The Hamiltonians used in this benchmark describe pairwise interactions between qubits, including terms such as $ZZ$, $XX$, and $YY$. These Hamiltonians are commonly used in quantum chemistry, condensed matter physics, and materials science, where they model systems of interacting particles.\n", - "\n", - "For reference, users can explore a broader set of Hamiltonians in this paper: [Efficient Hamiltonian Simulation on Noisy Quantum Devices](https://arxiv.org/pdf/2306.13126).\n", - "\n", - "#### Benchmark source: Hamlib and Benchpress\n", - "\n", - "The circuits used in this benchmark are drawn from the [Hamlib benchmark repository](https://github.com/SRI-International/QC-App-Oriented-Benchmarks/tree/master/qedcbench/hamiltonian_simulation), which contains realistic Hamiltonian simulation workloads.\n", + "### Step 1: Map classical inputs to a quantum problem\n", "\n", - "These same circuits were previously benchmarked using [Benchpress](https://github.com/Qiskit/benchpress), an open-source framework for evaluating quantum transpilation performance. By using this standardized set of circuits, we can directly compare the effectiveness of different compilation strategies on representative simulation problems.\n", + "Each Hamiltonian is encoded as a `PauliEvolutionGate` circuit. The circuits were already constructed in the setup section from the Hamlib benchmark data." + ] + }, + { + "cell_type": "markdown", + "id": "d4e5f6a7", + "metadata": {}, + "source": [ + "### Step 2: Optimize problem for quantum hardware execution\n", "\n", - "Hamiltonian simulation is a foundational task in quantum computing, with applications in molecular simulations, optimization problems, and quantum many-body physics. Understanding how different compilation methods optimize these circuits can help users improve practical execution of such circuits on near-term quantum devices." + "We transpile all small-scale circuits using each of the three pass managers and collect the metrics." ] }, { "cell_type": "code", - "execution_count": 13, - "id": "66347c00-1607-4405-bb76-610690adf6b8", + "execution_count": 12, + "id": "e4f5a6b7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Number of Hamiltonian circuits: 35\n" + "[SABRE] Circuit 0 (all-vib-bh): 2Q depth=3, size=30, time=2.09s\n", + "[SABRE] Circuit 1 (all-vib-c2h): 2Q depth=18, size=111, time=0.01s\n", + "[SABRE] Circuit 2 (all-vib-o3): 2Q depth=6, size=58, time=0.00s\n", + "[SABRE] Circuit 3 (all-vib-c2h): 2Q depth=2, size=37, time=0.01s\n", + "[SABRE] Circuit 4 (graph-gnp_k-2): 2Q depth=24, size=126, time=0.01s\n", + "[SABRE] Circuit 5 (LiH): 2Q depth=66, size=285, time=0.01s\n", + "[SABRE] Circuit 6 (all-vib-fccf): 2Q depth=66, size=339, time=0.01s\n", + "[SABRE] Circuit 7 (all-vib-ch2): 2Q depth=88, size=413, time=0.01s\n", + "[SABRE] Circuit 8 (all-vib-f2): 2Q depth=180, size=1000, time=0.02s\n", + "[SABRE] Circuit 9 (all-vib-bhf2): 2Q depth=18, size=223, time=0.03s\n", + "[SABRE] Circuit 10 (graph-gnp_k-4): 2Q depth=122, size=675, time=0.02s\n", + "[SABRE] Circuit 11 (Be2): 2Q depth=343, size=1628, time=0.03s\n", + "[SABRE] Circuit 12 (all-vib-fccf): 2Q depth=14, size=134, time=0.00s\n", + "[SABRE] Circuit 13 (uf20-ham): 2Q depth=50, size=341, time=0.01s\n", + "[SABRE] Circuit 14 (TSP_Ncity-4): 2Q depth=118, size=615, time=0.01s\n", + "[SABRE] Circuit 15 (graph-complete_bipart): 2Q depth=232, size=1420, time=0.03s\n", + "[SABRE] Circuit 16 (all-vib-cyclo_propene): 2Q depth=18, size=354, time=0.93s\n", + "[SABRE] Circuit 17 (all-vib-hno): 2Q depth=6, size=174, time=0.14s\n", + "[SABRE] Circuit 18 (all-vib-fccf): 2Q depth=30, size=286, time=0.01s\n", + "[SABRE] Circuit 19 (tfim): 2Q depth=31, size=232, time=0.03s\n", + "[AI] Circuit 0 (all-vib-bh): 2Q depth=3, size=30, time=0.01s\n" ] }, { "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "cf50b939bf4141bcbc610275a514d6fd", + "version_major": 2, + "version_minor": 0 + }, "text/plain": [ - "\"Output" + "Fetching 4 files: 0%| | 0/4 [00:00" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_transpilation_comparison(\n", + " results_small,\n", + " \"Small-Scale Hamiltonian Circuits: Compilation Comparison\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "pct_improvement_small", + "metadata": {}, + "outputs": [ { "data": { - "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
methodqc_nameqc_indexnum_qubitsopsdepthsizeruntime
0sabreall-vib-o304{'rz': 28, 'sx': 24, 'cz': 6}6580.016597
1sabreall-vib-c2h14{'rz': 17, 'sx': 16, 'cz': 4, 'x': 2}2391.102089
2sabreall-vib-bh22{'sx': 14, 'rz': 13, 'cz': 3}3300.011042
3sabreall-vib-c2h33{'sx': 46, 'rz': 45, 'cz': 18, 'x': 6}181150.025816
4sabregraph-gnp_k-244{'sx': 49, 'rz': 47, 'cz': 24, 'x': 9}241290.023077
...........................
100rustiqflat100-ham3090{'sx': 2709, 'cz': 1379, 'rz': 817, 'x': 5}27949100.309448
101rustiquf100-ham3146{'sx': 6180, 'cz': 3120, 'rz': 1303, 'x': 4}1138106070.380977
102rustiqOH3210{'sx': 3330, 'cz': 1704, 'rz': 1455, 'x': 23}114865120.383564
103rustiqHF3310{'sx': 3213, 'cz': 1620, 'rz': 1406, 'x': 17}109062560.368578
104rustiqBH3410{'sx': 3331, 'cz': 1704, 'rz': 1447, 'x': 19}114865010.374822
\n", - "

105 rows × 8 columns

\n", - "
" - ], "text/plain": [ - " method qc_name qc_index num_qubits \\\n", - "0 sabre all-vib-o3 0 4 \n", - "1 sabre all-vib-c2h 1 4 \n", - "2 sabre all-vib-bh 2 2 \n", - "3 sabre all-vib-c2h 3 3 \n", - "4 sabre graph-gnp_k-2 4 4 \n", - ".. ... ... ... ... \n", - "100 rustiq flat100-ham 30 90 \n", - "101 rustiq uf100-ham 31 46 \n", - "102 rustiq OH 32 10 \n", - "103 rustiq HF 33 10 \n", - "104 rustiq BH 34 10 \n", - "\n", - " ops depth size runtime \n", - "0 {'rz': 28, 'sx': 24, 'cz': 6} 6 58 0.016597 \n", - "1 {'rz': 17, 'sx': 16, 'cz': 4, 'x': 2} 2 39 1.102089 \n", - "2 {'sx': 14, 'rz': 13, 'cz': 3} 3 30 0.011042 \n", - "3 {'sx': 46, 'rz': 45, 'cz': 18, 'x': 6} 18 115 0.025816 \n", - "4 {'sx': 49, 'rz': 47, 'cz': 24, 'x': 9} 24 129 0.023077 \n", - ".. ... ... ... ... \n", - "100 {'sx': 2709, 'cz': 1379, 'rz': 817, 'x': 5} 279 4910 0.309448 \n", - "101 {'sx': 6180, 'cz': 3120, 'rz': 1303, 'x': 4} 1138 10607 0.380977 \n", - "102 {'sx': 3330, 'cz': 1704, 'rz': 1455, 'x': 23} 1148 6512 0.383564 \n", - "103 {'sx': 3213, 'cz': 1620, 'rz': 1406, 'x': 17} 1090 6256 0.368578 \n", - "104 {'sx': 3331, 'cz': 1704, 'rz': 1447, 'x': 19} 1148 6501 0.374822 \n", - "\n", - "[105 rows x 8 columns]" + "\"Output" ] }, - "execution_count": 15, "metadata": {}, - "output_type": "execute_result" + "output_type": "display_data" } ], "source": [ - "summary_ham = (\n", - " results_ham.groupby(\"method\")[[\"depth\", \"size\", \"runtime\"]]\n", - " .mean()\n", - " .round(2)\n", - ")\n", - "print(summary_ham)\n", + "plot_pct_improvement_vs_sabre(\n", + " results_small,\n", + " \"Small-Scale Hamiltonian Circuits\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "d2a29d04", + "metadata": {}, + "source": [ + "At this scale, all three pass managers perform well, and their average results are close to each other. This is largely because small circuits leave limited room for further optimization, so the methods tend to converge on similar solutions.\n", + "\n", + "In this example, Rustiq produces the most variable results, with the largest outliers in both two-qubit depth and gate count. While this variability means it sometimes falls behind, it also means Rustiq occasionally finds better solutions than the other two methods. The AI transpiler is more stable in its results relative to SABRE and Rustiq, tracking closely on most circuits without many outliers.\n", "\n", - "results_ham" + "For runtime, SABRE and Rustiq are both fast, while the AI transpiler is noticeably slower on certain circuits." ] }, { "cell_type": "markdown", - "id": "17e7b228-01b0-4fcc-b659-9f9958f5477e", + "id": "e5f6a7b8", "metadata": {}, "source": [ - "Visualize performance based on circuit index:" + "#### Best-performing method by metric\n", + "\n", + "The chart below shows how often each method achieved the best (lowest) value for each metric. Ties are possible: for simpler circuits, multiple methods can reach the same optimal two-qubit depth or gate count. When a tie occurs, all tied methods receive credit, so the percentages for a given metric may sum to more than 100%." ] }, { "cell_type": "code", - "execution_count": 16, - "id": "4c6b810d-a4cf-4de3-aae6-c6c1cdd83d8f", + "execution_count": 19, + "id": "f5a6b7c8", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_best_method_bars(results, metrics_list=None):\n", + " \"\"\"\n", + " Plot a grouped bar chart showing the percentage of circuits\n", + " where each method achieved the best (lowest) value for each metric.\n", + "\n", + " Ties are counted for all tied methods, so percentages per metric\n", + " can sum to more than 100%.\n", + " \"\"\"\n", + " if metrics_list is None:\n", + " metrics_list = [\"two_qubit_depth\", \"size\", \"runtime\"]\n", + "\n", + " labels = {\n", + " \"two_qubit_depth\": \"2Q Depth\",\n", + " \"size\": \"Gate Count\",\n", + " \"runtime\": \"Runtime\",\n", + " }\n", + " methods = _method_order(results)\n", + " palette = {\"SABRE\": \"#1f77b4\", \"AI\": \"#ff7f0e\", \"Rustiq\": \"#2ca02c\"}\n", + "\n", + " by_index = {}\n", + " for r in results:\n", + " by_index.setdefault(r[\"qc_index\"], []).append(r)\n", + " n_circuits = len(by_index)\n", + "\n", + " win_data = {m: [] for m in methods}\n", + " tie_counts = []\n", + " metric_labels = []\n", + "\n", + " for metric in metrics_list:\n", + " metric_labels.append(\n", + " labels.get(metric, metric.replace(\"_\", \" \").title())\n", + " )\n", + " counts = Counter()\n", + " ties = 0\n", + " for group in by_index.values():\n", + " min_val = min(r[metric] for r in group)\n", + " best = [r[\"method\"] for r in group if r[metric] == min_val]\n", + " if len(best) > 1:\n", + " ties += 1\n", + " counts.update(best)\n", + " tie_counts.append(ties)\n", + " for m in methods:\n", + " win_data[m].append(counts.get(m, 0) / n_circuits * 100)\n", + "\n", + " x = np.arange(len(metric_labels))\n", + " width = 0.22\n", + " fig, ax = plt.subplots(figsize=(8, 5))\n", + "\n", + " for i, method in enumerate(methods):\n", + " bars = ax.bar(\n", + " x + i * width,\n", + " win_data[method],\n", + " width,\n", + " label=method,\n", + " color=palette.get(method, None),\n", + " edgecolor=\"black\",\n", + " linewidth=0.5,\n", + " )\n", + " for bar in bars:\n", + " height = bar.get_height()\n", + " if height > 0:\n", + " ax.text(\n", + " bar.get_x() + bar.get_width() / 2,\n", + " height + 1.5,\n", + " f\"{height:.0f}%\",\n", + " ha=\"center\",\n", + " va=\"bottom\",\n", + " fontsize=9,\n", + " )\n", + "\n", + " # Annotate tie counts below each metric label\n", + " for j, ties in enumerate(tie_counts):\n", + " if ties > 0:\n", + " ax.text(\n", + " x[j] + width,\n", + " -8,\n", + " f\"({ties} tie{'s' if ties != 1 else ''})\",\n", + " ha=\"center\",\n", + " va=\"top\",\n", + " fontsize=8,\n", + " color=\"gray\",\n", + " )\n", + "\n", + " ax.set_xticks(x + width)\n", + " ax.set_xticklabels(metric_labels, fontsize=11)\n", + " ax.set_ylabel(\"Circuits with best value (%)\", fontsize=11)\n", + " ax.set_title(\n", + " \"Best-Performing Method by Metric (ties counted for all tied methods)\",\n", + " fontsize=12,\n", + " fontweight=\"bold\",\n", + " )\n", + " ax.legend(frameon=True, fontsize=10)\n", + " ax.set_ylim(-12, 120)\n", + " ax.yaxis.set_major_formatter(ticker.PercentFormatter())\n", + " ax.grid(axis=\"y\", linestyle=\"--\", alpha=0.4)\n", + "\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "a6b7c8d9", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, "metadata": {}, @@ -1427,58 +1157,172 @@ } ], "source": [ - "plot_transpilation_metrics(\n", - " results_ham, \"Transpilation Metrics for Hamiltonian Circuits\"\n", - ")" + "plot_best_method_bars(results_small)" ] }, { "cell_type": "markdown", - "id": "7ad6f4ee-c9d1-4e8b-ae25-cf39672acbec", + "id": "30687d7b", "metadata": {}, "source": [ - "Visualize the percentage of circuits for which each method performed best." + "In this example, the three methods perform very similarly on the small-scale circuits. On two-qubit depth and gate count, the share of circuits where each method is best is close (roughly 35–55%), and many circuits end in ties because the simplest circuits often have a single optimal solution that multiple methods find. The clearest difference is runtime: SABRE and Rustiq are each fastest on about half the circuits, while the AI transpiler is rarely the quickest. Considering all three metrics together, Rustiq has a slight overall edge — it is the most frequent winner on two-qubit depth and stays competitive on gate count and runtime." + ] + }, + { + "cell_type": "markdown", + "id": "b6c7d8e9", + "metadata": {}, + "source": [ + "### Step 3: Execute using Qiskit primitives\n", + "\n", + "To evaluate how transpilation quality affects execution under noise, we use a **mirror circuit** technique. For each transpiled circuit $U$, we append its inverse $U^\\dagger$ so the combined circuit $U^\\dagger U$ is theoretically the identity. Starting from the $|0\\rangle$ state, a perfect (noiseless) execution would return the all-zeros bitstring with probability 1.\n", + "\n", + "In practice, gate errors accumulate throughout the circuit, so the probability of recovering $|0\\rangle^{\\otimes n}$ drops. A compilation method that produces a shallower circuit with fewer gates will accumulate less noise.\n", + "\n", + "The mirror circuit approach is appealingly simple and scales to any circuit size, since the expected output is always $|0\\rangle^{\\otimes n}$ and no classical simulation of the ideal state is required. However, there are caveats worth noting: the mirror circuit is a proxy for the actual circuit (not the circuit itself), it doubles the gate count (which exaggerates the effect of noise), and it can underestimate certain errors when noise cancels symmetrically across the mirror boundary.\n", + "\n", + "We pick circuit index 6 from the small-scale set and run the mirror circuits on an Aer simulator with a simple depolarizing noise model." ] }, { "cell_type": "code", - "execution_count": 17, - "id": "01b4644e-ac91-483c-944b-924f8b41718d", + "execution_count": 21, + "id": "c6d7e8f9", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Best-performing methods based on depth:\n", - " ai: 16 circuit(s)\n", - " rustiq: 16 circuit(s)\n", - " sabre: 10 circuit(s)\n" + "Test circuit: all-vib-fccf, 4 qubits\n", + "\n", + "Transpilation metrics for circuit index 6:\n", + " SABRE 2Q depth= 66 size= 339\n", + " AI 2Q depth= 65 size= 300\n", + " Rustiq 2Q depth= 34 size= 193\n" ] - }, - { - "data": { - "text/plain": [ - "\"Output" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, + } + ], + "source": [ + "# Select circuit index 6 from the small-scale transpiled circuits\n", + "test_idx = 6\n", + "test_circuit = qc_small[test_idx]\n", + "print(f\"Test circuit: {test_circuit.name}, {test_circuit.num_qubits} qubits\")\n", + "\n", + "# Get the transpiled versions\n", + "tqc_methods_small = {\n", + " \"SABRE\": tqc_sabre_small[test_idx],\n", + " \"AI\": tqc_ai_small[test_idx],\n", + " \"Rustiq\": tqc_rustiq_small[test_idx],\n", + "}\n", + "\n", + "# Show transpilation metrics for this circuit\n", + "print(f\"\\nTranspilation metrics for circuit index {test_idx}:\")\n", + "for method, tqc in tqc_methods_small.items():\n", + " depth_2q = tqc.depth(lambda x: x.operation.num_qubits == 2)\n", + " size = tqc.size()\n", + " print(f\" {method:8s} 2Q depth={depth_2q:5d} size={size:6d}\")" + ] + }, + { + "cell_type": "markdown", + "id": "d6e7f8a9", + "metadata": {}, + "source": [ + "Build the mirror circuits (append $U^\\dagger$), remap to contiguous qubit indices so the simulator only handles the active qubits, and run on a noisy Aer simulator." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "e6f7a8b9", + "metadata": {}, + "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "Best-performing methods based on size:\n", - " sabre: 18 circuit(s)\n", - " rustiq: 14 circuit(s)\n", - " ai: 10 circuit(s)\n" + "SABRE P(|00...0>) = 0.7796 (7796/10000)\n", + "AI P(|00...0>) = 0.8073 (8073/10000)\n", + "Rustiq P(|00...0>) = 0.8923 (8923/10000)\n" ] - }, + } + ], + "source": [ + "def remap_to_contiguous(tqc):\n", + " \"\"\"Remap a transpiled circuit to use contiguous qubit indices.\n", + "\n", + " Transpiled circuits target specific physical qubits (e.g., qubit 45, 67)\n", + " on a large backend. This remaps them to 0, 1, 2, ... so Aer only\n", + " simulates the active qubits.\n", + " \"\"\"\n", + " active = sorted(\n", + " {tqc.find_bit(q).index for inst in tqc.data for q in inst.qubits}\n", + " )\n", + " qubit_map = {old: new for new, old in enumerate(active)}\n", + " new_qc = QuantumCircuit(len(active))\n", + " for inst in tqc.data:\n", + " old_indices = [tqc.find_bit(q).index for q in inst.qubits]\n", + " new_qc.append(inst.operation, [qubit_map[i] for i in old_indices])\n", + " return new_qc\n", + "\n", + "\n", + "def build_mirror_circuit(tqc):\n", + " \"\"\"Build a mirror circuit: U followed by U-dagger, with measurements.\n", + "\n", + " The combined circuit U-dagger @ U should be the identity, so measuring\n", + " all zeros indicates a noise-free execution.\n", + " \"\"\"\n", + " tqc_compact = remap_to_contiguous(tqc)\n", + " mirror = tqc_compact.compose(tqc_compact.inverse())\n", + " mirror.measure_all()\n", + " return mirror\n", + "\n", + "\n", + "# Build a simple depolarizing noise model\n", + "noise_model = NoiseModel()\n", + "noise_model.add_all_qubit_quantum_error(\n", + " depolarizing_error(0.001, 1),\n", + " [\"sx\", \"x\", \"rz\"], # ~0.1% per 1Q gate\n", + ")\n", + "noise_model.add_all_qubit_quantum_error(\n", + " depolarizing_error(0.01, 2),\n", + " [\"cx\", \"ecr\"], # ~1% per 2Q gate\n", + ")\n", + "\n", + "aer_sim = AerSimulator(noise_model=noise_model)\n", + "\n", + "shots = 10000\n", + "fidelities = {}\n", + "\n", + "for method, tqc in tqc_methods_small.items():\n", + " mirror = build_mirror_circuit(tqc)\n", + "\n", + " sampler = SamplerV2(mode=aer_sim)\n", + " job = sampler.run([mirror], shots=shots)\n", + " result = job.result()\n", + " counts = result[0].data.meas.get_counts()\n", + "\n", + " # Fidelity = fraction of all-zeros (error-free) outcomes\n", + " n_qubits = mirror.num_qubits - mirror.num_clbits # active qubits\n", + " all_zeros = \"0\" * mirror.num_qubits\n", + " fidelity = counts.get(all_zeros, 0) / shots\n", + " fidelities[method] = fidelity\n", + " print(\n", + " f\"{method:8s} P(|00...0>) = {fidelity:.4f} ({counts.get(all_zeros, 0)}/{shots})\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "small_step4_plot", + "metadata": {}, + "outputs": [ { "data": { "text/plain": [ - "\"Output" + "\"Output" ] }, "metadata": {}, @@ -1486,120 +1330,598 @@ } ], "source": [ - "def analyze_and_plot_best_methods(results, metric):\n", + "def plot_mirror_results(tqc_methods, fidelities, circuit_name):\n", " \"\"\"\n", - " Analyze the best-performing methods for a given metric and plot a pie chart.\n", - "\n", - " Parameters:\n", - " results (DataFrame): The input DataFrame containing method performance data.\n", - " metric (str): The metric to evaluate (\"depth\" or \"size\").\n", + " Plot a three-panel comparison: fidelity, 2Q depth,\n", + " and gate count for each compilation method.\n", " \"\"\"\n", - " method_counts = Counter()\n", - " for qc_idx, group in results.groupby(\"qc_index\"):\n", - " min_value = group[metric].min()\n", - "\n", - " # Find all methods that achieved this minimum value\n", - " best_methods = group[group[metric] == min_value][\"method\"]\n", - " # Update counts for all best methods (handling ties)\n", - " method_counts.update(best_methods)\n", - " best_method_counts = dict(\n", - " sorted(method_counts.items(), key=lambda x: x[1], reverse=True)\n", + " methods = list(tqc_methods.keys())\n", + " palette = {\"SABRE\": \"#1f77b4\", \"AI\": \"#ff7f0e\", \"Rustiq\": \"#2ca02c\"}\n", + " colors = [palette.get(m, \"gray\") for m in methods]\n", + "\n", + " fidelity_vals = [fidelities[m] for m in methods]\n", + " depth_vals = [\n", + " tqc_methods[m].depth(lambda x: x.operation.num_qubits == 2)\n", + " for m in methods\n", + " ]\n", + " size_vals = [tqc_methods[m].size() for m in methods]\n", + "\n", + " fig, axes = plt.subplots(1, 3, figsize=(16, 5))\n", + " fig.suptitle(\n", + " f\"Mirror Circuit Results: {circuit_name}\",\n", + " fontsize=14,\n", + " fontweight=\"bold\",\n", + " y=1.02,\n", " )\n", "\n", - " # Print summary\n", - " print(f\"Best-performing methods based on {metric}:\")\n", - " for method, count in best_method_counts.items():\n", - " print(f\" {method}: {count} circuit(s)\")\n", - "\n", - " # Plot pie chart\n", - " num_methods = len(best_method_counts)\n", - " colors = plt.cm.viridis_r(range(0, 256, 256 // num_methods))\n", - " plt.figure(figsize=(5, 5))\n", - " plt.pie(\n", - " best_method_counts.values(),\n", - " labels=best_method_counts.keys(),\n", - " autopct=\"%1.1f%%\",\n", - " startangle=140,\n", - " wedgeprops={\"edgecolor\": \"black\"},\n", - " textprops={\"fontsize\": 10},\n", - " colors=colors,\n", + " def _annotate_bars(ax, bars, values, fmt=\"{}\"):\n", + " ymax = ax.get_ylim()[1]\n", + " for bar, val in zip(bars, values):\n", + " label = fmt.format(val)\n", + " y = val + ymax * 0.03\n", + " ax.text(\n", + " bar.get_x() + bar.get_width() / 2,\n", + " y,\n", + " label,\n", + " ha=\"center\",\n", + " va=\"bottom\",\n", + " fontsize=10,\n", + " fontweight=\"bold\",\n", + " )\n", + "\n", + " # Panel 1: Survival Probability\n", + " bars = axes[0].bar(\n", + " methods, fidelity_vals, color=colors, edgecolor=\"black\", linewidth=0.5\n", " )\n", - " plt.title(\n", - " f\"Percentage of Circuits Method Performed Best for {metric.capitalize()}\",\n", - " fontsize=12,\n", - " fontweight=\"bold\",\n", + " axes[0].set_ylabel(\"Fidelity P(|00...0>)\", fontsize=11)\n", + " axes[0].set_title(\"Fidelity (higher is better)\", fontsize=12)\n", + " axes[0].set_ylim(\n", + " 0, max(fidelity_vals) * 1.18 if max(fidelity_vals) > 0 else 1.0\n", + " )\n", + " axes[0].grid(axis=\"y\", linestyle=\"--\", alpha=0.4)\n", + " _annotate_bars(axes[0], bars, fidelity_vals, fmt=\"{:.4f}\")\n", + "\n", + " # Panel 2: Two-Qubit Depth\n", + " bars = axes[1].bar(\n", + " methods, depth_vals, color=colors, edgecolor=\"black\", linewidth=0.5\n", + " )\n", + " axes[1].set_ylabel(\"Two-Qubit Depth\", fontsize=11)\n", + " axes[1].set_title(\"2Q Depth (lower is better)\", fontsize=12)\n", + " axes[1].set_ylim(0, max(depth_vals) * 1.18)\n", + " axes[1].grid(axis=\"y\", linestyle=\"--\", alpha=0.4)\n", + " _annotate_bars(axes[1], bars, depth_vals)\n", + "\n", + " # Panel 3: Gate Count\n", + " bars = axes[2].bar(\n", + " methods, size_vals, color=colors, edgecolor=\"black\", linewidth=0.5\n", " )\n", + " axes[2].set_ylabel(\"Total Gate Count\", fontsize=11)\n", + " axes[2].set_title(\"Gate Count (lower is better)\", fontsize=12)\n", + " axes[2].set_ylim(0, max(size_vals) * 1.18)\n", + " axes[2].grid(axis=\"y\", linestyle=\"--\", alpha=0.4)\n", + " _annotate_bars(axes[2], bars, size_vals)\n", + "\n", + " plt.tight_layout()\n", " plt.show()\n", "\n", "\n", - "analyze_and_plot_best_methods(results_ham, \"depth\")\n", - "analyze_and_plot_best_methods(results_ham, \"size\")" + "plot_mirror_results(tqc_methods_small, fidelities, test_circuit.name)" ] }, { "cell_type": "markdown", - "id": "e4f6aa23-d528-4d66-92e0-31d29c522792", + "id": "small_mirror_commentary", "metadata": {}, "source": [ - "#### Analysis of Hamiltonian circuit compilation results\n", + "#### Observations\n", "\n", - "In this section, we evaluate the performance of three transpilation methods — SABRE, the AI-powered transpiler, and Rustiq — on quantum circuits constructed with `PauliEvolutionGate`, which are commonly used in Hamiltonian simulation tasks.\n", + "The method with the lowest two-qubit depth and fewest gates achieves the highest fidelity, consistent with the expectation that shorter circuits accumulate less noise. Even modest differences in depth and gate count translate into measurable differences in fidelity under the depolarizing noise model.\n", "\n", - "Rustiq performed best on average in terms of circuit depth**, achieving approximately 20% lower depth than SABRE. This is expected, as Rustiq is specifically designed to synthesize `PauliEvolutionGate` operations with optimized, low-depth decomposition strategies. Furthermore, the depth plot shows that as the circuits scale in size and complexity, Rustiq scales most effectively, maintaining significantly lower depth than both AI and SABRE on larger circuits.\n", + "Keep in mind that these results are for a single circuit. The relative ranking of the methods can shift from circuit to circuit depending on the Hamiltonian structure." + ] + }, + { + "cell_type": "markdown", + "id": "sec-large-scale", + "metadata": {}, + "source": [ + "## Large-scale hardware example\n", "\n", - "AI transpiler showed strong and consistent performance for circuit depth, consistently outperforming SABRE across most circuits. However, it incurred the highest runtime, especially on larger circuits, which may limit its practicality in time-sensitive workloads. Its scalability in runtime remains a key limitation, even though it offers solid improvements in depth.\n", + "In this section, we benchmark the same three compilation methods on Hamiltonian circuits with 20 or more qubits. These circuits are more representative of practical Hamiltonian simulation workloads and test how each method scales in terms of circuit quality and compilation time." + ] + }, + { + "cell_type": "markdown", + "id": "f6a7b8c9", + "metadata": {}, + "source": [ + "### Steps 1-4 combined\n", "\n", - "SABRE, while producing the highest average depth, achieved the lowest average gate count, closely followed by the AI transpiler. This aligns with the design of SABRE’s heuristic, which prioritizes minimizing gate count directly. Rustiq, despite its strength in lowering depth, had the highest average gate count, which is a notable trade-off to consider in applications where circuit size matters more than circuit duration.\n", + "The workflow follows the same structure as the small-scale example. We transpile all large-scale circuits with each method, collect metrics, and then submit a mirror circuit to real quantum hardware." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "a7b8c9d0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[SABRE] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=258, time=0.16s\n", + "[SABRE] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=345, size=4036, time=0.08s\n", + "[SABRE] Circuit 2 (TSP_Ncity-5): 2Q depth=187, size=2045, time=0.04s\n", + "[SABRE] Circuit 3 (tfim): 2Q depth=100, size=489, time=0.21s\n", + "[SABRE] Circuit 4 (all-vib-h2co): 2Q depth=30, size=570, time=0.18s\n", + "[SABRE] Circuit 5 (uuf100-ham): 2Q depth=414, size=4779, time=0.09s\n", + "[SABRE] Circuit 6 (uuf100-ham): 2Q depth=523, size=5667, time=0.11s\n", + "[SABRE] Circuit 7 (graph-gnp_k-4): 2Q depth=3028, size=24885, time=0.39s\n", + "[SABRE] Circuit 8 (uf100-ham): 2Q depth=700, size=8271, time=0.15s\n", + "[SABRE] Circuit 9 (uf100-ham): 2Q depth=698, size=8957, time=0.15s\n", + "[SABRE] Circuit 10 (TSP_Ncity-7): 2Q depth=432, size=6353, time=0.12s\n", + "[SABRE] Circuit 11 (all-vib-cyclo_propene): 2Q depth=30, size=1144, time=0.20s\n", + "[SABRE] Circuit 12 (TSP_Ncity-8): 2Q depth=704, size=10287, time=0.18s\n", + "[SABRE] Circuit 13 (uf100-ham): 2Q depth=2454, size=30195, time=0.46s\n", + "[SABRE] Circuit 14 (tfim): 2Q depth=245, size=3670, time=0.08s\n", + "[SABRE] Circuit 15 (flat100-ham): 2Q depth=154, size=3836, time=0.12s\n", + "[SABRE] Circuit 16 (graph-regular_reg-4): 2Q depth=863, size=14063, time=0.22s\n", + "[SABRE] Circuit 17 (tfim): 2Q depth=581, size=8810, time=0.15s\n", + "[SABRE] Circuit 18 (FH_D-1): 2Q depth=1704, size=9528, time=0.35s\n", + "[SABRE] Circuit 19 (TSP_Ncity-10): 2Q depth=1091, size=22041, time=0.38s\n", + "[SABRE] Circuit 20 (TSP_Ncity-10): 2Q depth=1091, size=22005, time=0.38s\n", + "[SABRE] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=224, size=8321, time=0.17s\n", + "[AI] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=258, time=0.17s\n", + "[AI] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=323, size=4418, time=3.13s\n", + "[AI] Circuit 2 (TSP_Ncity-5): 2Q depth=161, size=2229, time=1.47s\n", + "[AI] Circuit 3 (tfim): 2Q depth=20, size=402, time=0.34s\n", + "[AI] Circuit 4 (all-vib-h2co): 2Q depth=38, size=661, time=0.19s\n", + "[AI] Circuit 5 (uuf100-ham): 2Q depth=391, size=5130, time=3.27s\n", + "[AI] Circuit 6 (uuf100-ham): 2Q depth=463, size=6095, time=4.23s\n", + "[AI] Circuit 7 (graph-gnp_k-4): 2Q depth=3207, size=25641, time=15.15s\n", + "[AI] Circuit 8 (uf100-ham): 2Q depth=637, size=8267, time=5.87s\n", + "[AI] Circuit 9 (uf100-ham): 2Q depth=632, size=9330, time=7.29s\n", + "[AI] Circuit 10 (TSP_Ncity-7): 2Q depth=452, size=7418, time=6.02s\n", + "[AI] Circuit 11 (all-vib-cyclo_propene): 2Q depth=38, size=1323, time=0.27s\n", + "[AI] Circuit 12 (TSP_Ncity-8): 2Q depth=609, size=11131, time=10.07s\n", + "[AI] Circuit 13 (uf100-ham): 2Q depth=2251, size=31128, time=38.77s\n", + "[AI] Circuit 14 (tfim): 2Q depth=165, size=3460, time=1.64s\n", + "[AI] Circuit 15 (flat100-ham): 2Q depth=91, size=3497, time=2.49s\n", + "[AI] Circuit 16 (graph-regular_reg-4): 2Q depth=664, size=15256, time=12.35s\n", + "[AI] Circuit 17 (tfim): 2Q depth=583, size=9157, time=6.28s\n", + "[AI] Circuit 18 (FH_D-1): 2Q depth=1193, size=7754, time=4.54s\n", + "[AI] Circuit 19 (TSP_Ncity-10): 2Q depth=1134, size=22577, time=25.64s\n", + "[AI] Circuit 20 (TSP_Ncity-10): 2Q depth=1172, size=23851, time=28.97s\n", + "[AI] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=219, size=8600, time=8.85s\n", + "[Rustiq] Circuit 0 (all-vib-hc3h2cn): 2Q depth=2, size=257, time=0.16s\n", + "[Rustiq] Circuit 1 (ham-graph-gnp_k-5): 2Q depth=640, size=5831, time=0.13s\n", + "[Rustiq] Circuit 2 (TSP_Ncity-5): 2Q depth=408, size=3985, time=0.08s\n", + "[Rustiq] Circuit 3 (tfim): 2Q depth=31, size=688, time=0.07s\n", + "[Rustiq] Circuit 4 (all-vib-h2co): 2Q depth=65, size=1058, time=2.91s\n", + "[Rustiq] Circuit 5 (uuf100-ham): 2Q depth=633, size=6757, time=0.14s\n", + "[Rustiq] Circuit 6 (uuf100-ham): 2Q depth=795, size=8495, time=0.17s\n", + "[Rustiq] Circuit 7 (graph-gnp_k-4): 2Q depth=13768, size=139793, time=2.92s\n", + "[Rustiq] Circuit 8 (uf100-ham): 2Q depth=1099, size=11878, time=0.25s\n", + "[Rustiq] Circuit 9 (uf100-ham): 2Q depth=911, size=11111, time=0.22s\n", + "[Rustiq] Circuit 10 (TSP_Ncity-7): 2Q depth=1183, size=13197, time=0.27s\n", + "[Rustiq] Circuit 11 (all-vib-cyclo_propene): 2Q depth=67, size=2491, time=13.56s\n", + "[Rustiq] Circuit 12 (TSP_Ncity-8): 2Q depth=1615, size=21358, time=0.48s\n", + "[Rustiq] Circuit 13 (uf100-ham): 2Q depth=2920, size=40465, time=0.91s\n", + "[Rustiq] Circuit 14 (tfim): 2Q depth=489, size=6552, time=0.15s\n", + "[Rustiq] Circuit 15 (flat100-ham): 2Q depth=378, size=5906, time=0.14s\n", + "[Rustiq] Circuit 16 (graph-regular_reg-4): 2Q depth=12163, size=168679, time=2.94s\n", + "[Rustiq] Circuit 17 (tfim): 2Q depth=1208, size=17042, time=0.36s\n", + "[Rustiq] Circuit 18 (FH_D-1): 2Q depth=1061, size=24000, time=0.47s\n", + "[Rustiq] Circuit 19 (TSP_Ncity-10): 2Q depth=2565, size=41340, time=1.38s\n", + "[Rustiq] Circuit 20 (TSP_Ncity-10): 2Q depth=2565, size=41275, time=1.38s\n", + "[Rustiq] Circuit 21 (ham-unary-color02-queen13_13_k-4): 2Q depth=808, size=17548, time=0.42s\n" + ] + } + ], + "source": [ + "results_large = []\n", "\n", - "### Summary\n", + "tqc_sabre_large = capture_transpilation_metrics(\n", + " results_large, pm_sabre, qc_large, \"SABRE\"\n", + ")\n", + "tqc_ai_large = capture_transpilation_metrics(\n", + " results_large, pm_ai, qc_large, \"AI\"\n", + ")\n", + "tqc_rustiq_large = capture_transpilation_metrics(\n", + " results_large, pm_rustiq, qc_large, \"Rustiq\"\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "b7c8d9e0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Mean +/- std per compilation method\n", + "Method 2Q Depth Gate Count Runtime (s)\n", + "------------------------------------------------------------------------------\n", + "SABRE 709.1 +/- 783.8 9,100.5 +/- 8,493.1 0.2 +/- 0.1\n", + "AI 656.6 +/- 777.5 9,435.6 +/- 8,853.0 8.5 +/- 10.2\n", + "Rustiq 2,062.5 +/- 3,631.1 26,804.8 +/- 43,403.1 1.3 +/- 2.9\n", + "\n", + "Mean % improvement vs SABRE (positive = better than SABRE)\n", + "Method 2Q Depth Gate Count Runtime (s)\n", + "------------------------------------------------------------------------------\n", + "AI +9.6% +/- 22.8% -3.4% +/- 9.4% -3620.0% +/- 2405.5%\n", + "Rustiq -154.5% +/- 273.9% -137.1% +/- 233.2% -527.0% +/- 1405.5%\n" + ] + } + ], + "source": [ + "print_summary_table(results_large)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "per_circuit_large_code", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "2Q Depth (first 8 circuits by qubit count); * = best\n", + "Idx Circuit Q SABRE AI Rustiq\n", + "----------------------------------------------------\n", + " 0 all-vib-hc3h2cn 24 2* 2* 2*\n", + " 1 ham-graph-gnp_k- 24 345 323* 640\n", + " 2 TSP_Ncity-5 25 187 161* 408\n", + " 3 tfim 26 100 20* 31\n", + " 4 all-vib-h2co 32 30* 38 65\n", + " 5 uuf100-ham 40 414 391* 633\n", + " 6 uuf100-ham 40 523 463* 795\n", + " 7 graph-gnp_k-4 40 3028* 3207 13768\n", + "\n", + "Gate Count (first 8 circuits by qubit count); * = best\n", + "Idx Circuit Q SABRE AI Rustiq\n", + "----------------------------------------------------\n", + " 0 all-vib-hc3h2cn 24 258 258 257*\n", + " 1 ham-graph-gnp_k- 24 4036* 4418 5831\n", + " 2 TSP_Ncity-5 25 2045* 2229 3985\n", + " 3 tfim 26 489 402* 688\n", + " 4 all-vib-h2co 32 570* 661 1058\n", + " 5 uuf100-ham 40 4779* 5130 6757\n", + " 6 uuf100-ham 40 5667* 6095 8495\n", + " 7 graph-gnp_k-4 40 24885* 25641 139793\n", + "\n", + "Runtime (s) (first 8 circuits by qubit count); * = best\n", + "Idx Circuit Q SABRE AI Rustiq\n", + "----------------------------------------------------\n", + " 0 all-vib-hc3h2cn 24 0.16 0.17 0.16*\n", + " 1 ham-graph-gnp_k- 24 0.08* 3.13 0.13\n", + " 2 TSP_Ncity-5 25 0.04* 1.47 0.08\n", + " 3 tfim 26 0.21 0.34 0.07*\n", + " 4 all-vib-h2co 32 0.18* 0.19 2.91\n", + " 5 uuf100-ham 40 0.09* 3.27 0.14\n", + " 6 uuf100-ham 40 0.11* 4.23 0.17\n", + " 7 graph-gnp_k-4 40 0.39* 15.15 2.92\n", + "\n" + ] + } + ], + "source": [ + "print_per_circuit_comparison(results_large, num_rows=8)" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "c7d8e9f0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_transpilation_comparison(\n", + " results_large,\n", + " \"Large-Scale Hamiltonian Circuits: Compilation Comparison\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "pct_improvement_large", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_pct_improvement_vs_sabre(\n", + " results_large,\n", + " \"Large-Scale Hamiltonian Circuits\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "d7e8f9a0", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plot_best_method_bars(results_large)" + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "f7a8b9c0", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Test circuit: tfim, 26 qubits\n", + "\n", + "Transpilation metrics for circuit index 3:\n", + " SABRE 2Q depth= 100 size= 489\n", + " AI 2Q depth= 20 size= 402\n", + " Rustiq 2Q depth= 31 size= 688\n" + ] + } + ], + "source": [ + "# Select circuit index 3 from the large-scale transpiled circuits\n", + "test_idx_large = 3\n", + "test_circuit_large = qc_large[test_idx_large]\n", + "print(\n", + " f\"Test circuit: {test_circuit_large.name}, {test_circuit_large.num_qubits} qubits\"\n", + ")\n", "\n", - "While the AI transpiler generally delivers better results than SABRE, particularly in circuit depth, the takeaway should not simply be \"always use the AI transpiler.\" There are important nuances to consider:\n", + "tqc_methods_large = {\n", + " \"SABRE\": tqc_sabre_large[test_idx_large],\n", + " \"AI\": tqc_ai_large[test_idx_large],\n", + " \"Rustiq\": tqc_rustiq_large[test_idx_large],\n", + "}\n", + "\n", + "print(f\"\\nTranspilation metrics for circuit index {test_idx_large}:\")\n", + "for method, tqc in tqc_methods_large.items():\n", + " depth_2q = tqc.depth(lambda x: x.operation.num_qubits == 2)\n", + " size = tqc.size()\n", + " print(f\" {method:8s} 2Q depth={depth_2q:5d} size={size:6d}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "3cd3afa2", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "SABRE transpiled circuit:\n", + "OrderedDict({'sx': 211, 'rz': 163, 'cz': 104, 'x': 11})\n", + "SABRE mirror circuit count ops:\n", + "OrderedDict({'rz': 1170, 'sx': 422, 'cz': 208, 'measure': 156, 'x': 22, 'barrier': 1})\n", + "\n", + "AI transpiled circuit:\n", + "OrderedDict({'sx': 165, 'rz': 162, 'cz': 68, 'x': 7})\n", + "AI mirror circuit count ops:\n", + "OrderedDict({'rz': 984, 'sx': 330, 'measure': 156, 'cz': 136, 'x': 14, 'barrier': 1})\n", + "\n", + "Rustiq transpiled circuit:\n", + "OrderedDict({'sx': 316, 'rz': 225, 'cz': 140, 'x': 7})\n", + "Rustiq mirror circuit count ops:\n", + "OrderedDict({'rz': 1714, 'sx': 632, 'cz': 280, 'measure': 156, 'x': 14, 'barrier': 1})\n" + ] + } + ], + "source": [ + "pm_mirror = generate_preset_pass_manager(\n", + " optimization_level=0, backend=backend\n", + ")\n", "\n", - "- **AI transpiler** is typically reliable and provides depth-optimized circuits, but it comes with trade-offs in runtime, and also has other limitations, including supported coupling maps and synthesis capabilities. These are detailed in the [Qiskit Transpiler Service documentation](/docs/guides/qiskit-transpiler-service).\n", + "for method, tqc in tqc_methods_large.items():\n", + " # print the count ops for each circuit\n", + " mirror = tqc.copy()\n", + " mirror.compose(tqc.inverse(), inplace=True)\n", + " mirror.measure_all()\n", + " mirror = pm_mirror.run(mirror)\n", + " print(f\"\\n{method} transpiled circuit:\")\n", + " print(tqc.count_ops())\n", + " print(f\"{method} mirror circuit count ops:\")\n", + " print(mirror.count_ops())" + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "id": "large_hw_submit", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SABRE: submitted job d8gvgq66983c73dqe5og\n", + "AI: submitted job d8gvgqe6983c73dqe5pg\n", + "Rustiq: submitted job d8gvgqm6983c73dqe5q0\n" + ] + } + ], + "source": [ + "# Build mirror circuits and submit to real hardware\n", + "# The inverse may introduce gates (e.g., sxdg) not in the backend's\n", + "# basis gate set, so we re-transpile the mirror circuit.\n", + "pm_mirror = generate_preset_pass_manager(\n", + " optimization_level=0, backend=backend\n", + ")\n", + "\n", + "shots_hw = 10000\n", + "hw_jobs = {}\n", "\n", - "- In some cases, particularly with very large or hardware-specific circuits, the AI transpiler may not be as effective. In these cases, the default SABRE transpiler remains extremely reliable and can be further optimized by adjusting its parameters (see the [SABRE optimization tutorial](/docs/tutorials/transpilation-optimizations-with-sabre)).\n", + "for method, tqc in tqc_methods_large.items():\n", + " mirror = tqc.copy()\n", + " mirror.compose(tqc.inverse(), inplace=True)\n", + " mirror.measure_all()\n", "\n", - "- It's also important to consider circuit structure when choosing a method. For example, `rustiq` is purpose-built for circuits involving `PauliEvolutionGate` and often yields the best performance for Hamiltonian simulation problems.\n", + " # Re-transpile at opt level 0 to decompose into basis gates\n", + " # without changing the layout or routing\n", + " mirror = pm_mirror.run(mirror)\n", "\n", - "**Recommendation:**\n", - "> There is no one-size-fits-all transpilation strategy. Users are encouraged to understand the structure of their circuit and test multiple transpilation methods — including AI, SABRE, and specialized tools like Rustiq — to find the most efficient solution for their specific problem and hardware constraints." + " sampler = SamplerV2(mode=backend)\n", + " sampler.options.environment.job_tags = [\"TUT_CMHSC\"]\n", + " job = sampler.run([mirror], shots=shots_hw)\n", + " hw_jobs[method] = job\n", + " print(f\"{method}: submitted job {job.job_id()}\")" ] }, { - "cell_type": "markdown", - "id": "9a747477-1dc1-4706-b844-d29570fb5844", + "cell_type": "code", + "execution_count": 33, + "id": "large_hw_results", "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "SABRE P(|00...0>) = 0.0005 (5/10000)\n", + "AI P(|00...0>) = 0.3267 (3267/10000)\n", + "Rustiq P(|00...0>) = 0.1845 (1845/10000)\n" + ] + } + ], "source": [ - "### Step 3: Execute using Qiskit primitives" + "# Retrieve results and compute fidelities\n", + "fidelities_large = {}\n", + "\n", + "for method, job in hw_jobs.items():\n", + " result = job.result()\n", + " counts = result[0].data.meas.get_counts()\n", + "\n", + " n_qubits = backend.num_qubits\n", + " all_zeros = \"0\" * n_qubits\n", + " fidelity = counts.get(all_zeros, 0) / shots_hw\n", + " fidelities_large[method] = fidelity\n", + " print(\n", + " f\"{method:8s} P(|00...0>) = {fidelity:.4f} ({counts.get(all_zeros, 0)}/{shots_hw})\"\n", + " )" ] }, { - "cell_type": "markdown", - "id": "d3d6d473-7508-4e28-82ad-eee09c9a1acf", + "cell_type": "code", + "execution_count": 34, + "id": "large_hw_plot", "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "\"Output" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "As this tutorial focuses on transpilation, no experiments are executed on a quantum device. The goal is to leverage the optimizations from Step 2 to obtain a transpiled circuit with reduced depth and gate count." + "plot_mirror_results(\n", + " tqc_methods_large, fidelities_large, test_circuit_large.name\n", + ")" ] }, { "cell_type": "markdown", - "id": "bb4c4c67-a7e8-4ca1-a5d6-908a2e1d27e5", + "id": "sec-analysis", "metadata": {}, "source": [ - "### Step 4: Post-process and return result in desired classical format" + "## Analysis of compilation results\n", + "\n", + "The benchmarks above compare SABRE, the AI transpiler, and Rustiq on Hamiltonian simulation circuits from the Hamlib collection at both small and large scale.\n", + "\n", + "### Two-qubit depth and gate count\n", + "\n", + "At large scale, SABRE and the AI transpiler are the two strongest performers, and each leads on a different metric. As the *best-performing method by metric* chart shows, SABRE produces the lowest gate count on the large majority of circuits and is the fastest method on almost all of them, consistent with a heuristic designed to minimize inserted SWAP gates and with recent optimizations to its layout and routing. The AI transpiler produces the lowest two-qubit depth on most circuits, consistent with the part of its reinforcement learning objective that targets circuit depth. The summary table reflects the same split: SABRE has the lower mean gate count, while the AI transpiler has the lower mean two-qubit depth. Both methods are consistent and reliable across the full range of circuits.\n", + "\n", + "Rustiq, which is purpose-built for `PauliEvolutionGate` synthesis, produces the single best result on only a small fraction of the large-scale circuits. Its average metrics are heavily skewed by a handful of significant outliers, visible as large spikes in the compilation comparison plot, where Rustiq produces substantially higher depth and gate count than the other methods. Without these outliers, its average performance would be much closer to SABRE and the AI transpiler.\n", + "\n", + "The key observation is that no single method dominates on every circuit. There are specific cases where each method outperforms the others, which makes it worthwhile to try all available tools and select the best result for each circuit.\n", + "\n", + "### Runtime\n", + "\n", + "SABRE is consistently the fastest method. Rustiq generally runs at a similar speed, but it can produce outliers where the compilation takes significantly longer. This is especially visible in the large-scale results, where a few circuits cause Rustiq's runtime to spike. These outliers heavily impact the average runtime, so the median may be a more representative summary for Rustiq. The AI transpiler is the slowest of the three, with runtime that grows notably on larger and more complex circuits.\n", + "\n", + "### Mirror circuit results\n", + "\n", + "The mirror circuit experiments confirm the expected trend: methods that produce lower two-qubit depth and fewer gates achieve higher fidelity under noise. This holds on both the noisy simulator (small-scale) and real hardware (large-scale).\n", + "\n", + "Keep in mind that each mirror-circuit plot reflects a single circuit, not the aggregate. The hardware example above uses one 26-qubit `tfim` circuit, which happens to be a case where SABRE produces a much higher two-qubit depth than the AI transpiler and Rustiq, so its fidelity is correspondingly much lower. This is not representative of the broader results: across the full set of large-scale circuits, SABRE's two-qubit depth is usually close to the AI transpiler's, and the two methods each lead on different metrics (the AI transpiler on two-qubit depth, SABRE on gate count and runtime). A single mirror result tests a doubled version of one circuit rather than the full workload, so it should not be read as a verdict on overall method quality.\n", + "\n", + "### Recommendations\n", + "\n", + "There is no single best transpilation strategy for all circuits. The best choice depends on the circuit structure, the optimization goal, and the available compilation time budget:\n", + "\n", + "- **SABRE** is the recommended default. It is fast, reliable, and produces strong results across a wide range of circuits. For further tuning, users can increase layout and routing trials (see the [SABRE optimization tutorial](/docs/tutorials/transpilation-optimizations-with-sabre)).\n", + "- **AI transpiler** is worth trying when compilation time is not a constraint, especially when minimizing two-qubit depth is the priority: it produced the lowest two-qubit depth on most of the large-scale circuits in this benchmark.\n", + "- **Rustiq** is purpose-built for `PauliEvolutionGate` circuits and can find very low-depth, low-gate-count solutions, particularly on smaller circuits. On larger circuits it can occasionally produce much larger results, so it is best used as one of several methods to try rather than as a default.\n", + "\n", + "In practice, the best approach is to run all available methods and pick the best result for each circuit. The compilation overhead of trying multiple methods is small compared to the potential improvement in execution quality on real hardware." ] }, { "cell_type": "markdown", - "id": "4aa45929-f8d9-4be8-9008-0588615fa45e", + "id": "sec-next-steps", "metadata": {}, "source": [ - "Since there is no execution for this notebook, there are no results to post-process." + "## Next steps\n", + "\n", + "If you found this tutorial useful, you might be interested in the following:\n", + "\n", + "\n", + "\n", + "- [Transpilation optimizations with SABRE](/docs/tutorials/transpilation-optimizations-with-sabre)\n", + "- [AI transpiler passes](/docs/guides/ai-transpiler-passes)\n", + "- [Create a transpiler plugin](/docs/guides/create-transpiler-plugin)\n", + "" ] }, { "cell_type": "markdown", - "id": "552ef44b-e567-4870-9699-d152806b9505", + "id": "sec-references", "metadata": {}, "source": [ "## References\n", @@ -1608,9 +1930,9 @@ "\n", "[2] \"Practical and efficient quantum circuit synthesis and transpiling with Reinforcement Learning\". D. Kremer, V. Villar, H. Paik, I. Duran, I. Faro, J. Cruz-Benito et al. https://arxiv.org/abs/2405.13196\n", "\n", - "[3] \"Pauli Network Circuit Synthesis with Reinforcement Learning\". A. Dubal, D. Kremer, S. Martiel, V. Villar, D. Wang, J. Cruz-Benito et al. https://arxiv.org/abs/2503.14448\n", + "[3] \"Pauli Network Circuit Synthesis with Reinforcement Learning\". A. Dubal, D. Kremer, S. Martiel, V. Villar, D. Wang, J. Cruz-Benito et al. https://arxiv.org/abs/2503.14448\n", "\n", - "[4] \"Faster and shorter synthesis of Hamiltonian simulation circuits\". T. Goubault de Brugière, S. Martiel et al. https://arxiv.org/abs/2404.03280" + "[4] \"Faster and shorter synthesis of Hamiltonian simulation circuits\". T. Goubault de Brugiere, S. Martiel et al. https://arxiv.org/abs/2404.03280" ] } ], @@ -1634,5 +1956,5 @@ } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 } diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-1.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-1.avif deleted file mode 100644 index 4541fb68046..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-1.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-3.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-3.avif deleted file mode 100644 index beb2b3a1ff5..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/01b4644e-ac91-483c-944b-924f8b41718d-3.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-1.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-1.avif deleted file mode 100644 index 1c263985a9a..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-1.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-3.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-3.avif deleted file mode 100644 index f85c49281d7..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/37924fc2-8fb6-451a-b8f9-cd79573f2384-3.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/4c6b810d-a4cf-4de3-aae6-c6c1cdd83d8f-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/4c6b810d-a4cf-4de3-aae6-c6c1cdd83d8f-0.avif deleted file mode 100644 index b46f913b8b9..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/4c6b810d-a4cf-4de3-aae6-c6c1cdd83d8f-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/66347c00-1607-4405-bb76-610690adf6b8-1.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/66347c00-1607-4405-bb76-610690adf6b8-1.avif deleted file mode 100644 index 40779e134ed..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/66347c00-1607-4405-bb76-610690adf6b8-1.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/7f7b502a-8ed6-45fa-a698-02977149e283-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/7f7b502a-8ed6-45fa-a698-02977149e283-0.avif deleted file mode 100644 index 6f7b6183949..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/7f7b502a-8ed6-45fa-a698-02977149e283-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/a6b7c8d9-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/a6b7c8d9-0.avif new file mode 100644 index 00000000000..6f8c982bfa4 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/a6b7c8d9-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/b4c5d6e7-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/b4c5d6e7-0.avif new file mode 100644 index 00000000000..6184ea7b4a5 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/b4c5d6e7-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/c7d8e9f0-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/c7d8e9f0-0.avif new file mode 100644 index 00000000000..cdb06888812 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/c7d8e9f0-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d5e6f7a8-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d5e6f7a8-0.avif new file mode 100644 index 00000000000..57bd12e5056 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d5e6f7a8-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d7e8f9a0-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d7e8f9a0-0.avif new file mode 100644 index 00000000000..c88ae38dc89 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/d7e8f9a0-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/f362cdac-94d8-4cc5-85f4-015c3d9eba3a-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/f362cdac-94d8-4cc5-85f4-015c3d9eba3a-0.avif deleted file mode 100644 index 91da846530e..00000000000 Binary files a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/f362cdac-94d8-4cc5-85f4-015c3d9eba3a-0.avif and /dev/null differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/large_hw_plot-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/large_hw_plot-0.avif new file mode 100644 index 00000000000..86fcd693872 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/large_hw_plot-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_large-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_large-0.avif new file mode 100644 index 00000000000..768a200c932 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_large-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_small-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_small-0.avif new file mode 100644 index 00000000000..4e18fbd4ad8 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/pct_improvement_small-0.avif differ diff --git a/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/small_step4_plot-0.avif b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/small_step4_plot-0.avif new file mode 100644 index 00000000000..27252155977 Binary files /dev/null and b/public/docs/images/tutorials/compilation-methods-for-hamiltonian-simulation-circuits/extracted-outputs/small_step4_plot-0.avif differ