Optimalizace transpilace pomocí SABRE
Odhadovaná doba běhu: méně než jedna minuta na procesoru Heron r2 (POZNÁMKA: Jedná se pouze o odhad. Skutečná doba běhu se může lišit.)
Pozadí
Transpilace je klíčovým krokem v Qiskit, který převádí kvantové obvody do forem kompatibilních s konkrétním kvantovým hardwarem. Zahrnuje dvě hlavní fáze: rozmístění qubitů (mapování logických qubitů na fyzické qubity na zařízení) a směrování hradel (zajištění, že vícequbitová hradla respektují konektivitu zařízení vkládáním SWAP hradel podle potřeby).
SABRE (SWAP-Based Bidirectional heuristic search algorithm) je výkonný optimalizační nástroj pro obě fáze – rozmístění i směrování. Je zvláště účinný pro rozsáhlé obvody (100+ qubitů) a zařízení se složitými coupling mapami, jako je IBM® Heron, kde exponenciální růst možných mapování qubitů vyžaduje efektivní řešení.
Proč používat SABRE?
SABRE minimalizuje počet SWAP hradel a snižuje hloubku obvodu, čímž zlepšuje výkon obvodu na reálném hardwaru. Jeho přístup založený na heuristice ho dělá ideálním pro pokročilý hardware a velké, složité obvody. Nedávná vylepšení zavedená v algoritmu LightSABRE dále optimalizují výkon SABRE a nabízejí rychlejší doby běhu a méně SWAP hradel. Tato vylepšení ho dělají ještě účinnějším pro rozsáhlé obvody.
Co se naučíš
Tento tutoriál je rozdělen do dvou částí:
- Naučíš se používat SABRE s Qiskit patterns pro pokročilou optimalizaci velkých obvodů.
- Využiješ qiskit_serverless k maximalizaci potenciálu SABRE pro škálovatelnou a efektivní transpilaci.
Budeš:
- Optimalizovat SABRE pro obvody se 100+ qubity, překračující výchozí nastavení transpilace jako
optimization_level=3. - Zkoumat vylepšení LightSABRE, která zlepšují dobu běhu a snižují počty hradel.
- Přizpůsobovat klíčové parametry SABRE (
swap_trials,layout_trials,max_iterations,heuristic) pro vyvážení kvality obvodu a doby transpilace.
Požadavky
Před zahájením tohoto tutoriálu se ujisti, že máš nainstalováno:
- Qiskit SDK v1.0 nebo novější, s podporou vizualizace
- Qiskit Runtime v0.28 nebo novější (
pip install qiskit-ibm-runtime) - Serverless (
pip install qiskit-ibm-catalog qiskit_serverless)
Nastavení
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-catalog qiskit-ibm-runtime qiskit-serverless
from qiskit import QuantumCircuit
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_catalog import QiskitServerless, QiskitFunction
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorOptions
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit.transpiler import CouplingMap
from qiskit.transpiler.passes import SabreLayout, SabreSwap
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
import matplotlib.pyplot as plt
import numpy as np
import time
Část I. Použití SABRE s Qiskit patterns
SABRE lze použít v Qiskit k optimalizaci kvantových obvodů tím, že se stará o obě fáze – rozmístění qubitů i směrování hradel. V této části tě provedeme minimálním příkladem použití SABRE s Qiskit patterns, přičemž hlavní zaměření bude na krok 2 optimalizace.
Pro spuštění SABRE potřebuješ:
- Reprezentaci kvantového obvodu jako DAG (Directed Acyclic Graph).
- Coupling map z backendu, která specifikuje, jak jsou qubity fyzicky propojeny.
- SABRE pass, který aplikuje algoritmus k optimalizaci rozmístění a směrování.
V této části se zaměříme na pass SabreLayout. Provádí pokusy s rozmístěním i směrováním a snaží se najít nejefektivnější počáteční rozmístění při minimalizaci počtu potřebných SWAP hradel. Je důležité poznamenat, že SabreLayout interně optimalizuje jak rozmístění, tak směrování tím, že ukládá řešení s nejmenším počtem SWAP hradel. Všimni si, že při použití pouze SabreLayout nemůžeme měnit heuristiku SABRE, ale můžeme přizpůsobit počet layout_trials.
Krok 1: Mapování klasických vstupů na kvantový problém
GHZ (Greenberger-Horne-Zeilinger) Circuit je kvantový obvod, který připravuje provázaný stav, kde jsou všechny qubity buď ve stavu |0...0⟩ nebo |1...1⟩. GHZ stav pro qubitů je matematicky vyjádřen jako:
Je konstruován aplikací:
- Hadamardova hradla na první Qubit pro vytvoření superpozice.
- Série CNOT hradel pro provázání zbývajících qubitů s prvním.
V tomto příkladu záměrně konstruujeme GHZ Circuit s hvězdicovou topologií místo lineární topologie. V hvězdicové topologii první Qubit funguje jako „uzel" a všechny ostatní qubity jsou s ním přímo provázány pomocí CNOT hradel. Tato volba je záměrná, protože zatímco GHZ stav s lineární topologií lze teoreticky implementovat s hloubkou na lineární coupling mapě bez jakýchkoli SWAP hradel, SABRE by triviálně našel optimální řešení mapováním 100-qubitového GHZ Circuit na podgraf heavy-hex coupling mapy backendu.
GHZ Circuit s hvězdicovou topologií představuje výrazně náročnější problém. Ačkoli jej lze stále teoreticky provést s hloubkou bez SWAP hradel, nalezení tohoto řešení vyžaduje identifikaci optimálního počátečního rozmístění, což je mnohem obtížnější kvůli nelineární konektivitě obvodu. Tato topologie slouží jako lepší testovací případ pro hodnocení SABRE, protože ukazuje, jak konfigurační parametry ovlivňují výkon rozmístění a směrování za složitějších podmínek.

Je třeba poznamenat:
- Nástroj HighLevelSynthesis může vytvořit optimální řešení s hloubkou pro GHZ Circuit s hvězdicovou topologií bez přidávání SWAP hradel, jak je znázorněno na obrázku výše.
- Alternativně může pass StarPrerouting dále snížit hloubku tím, že řídí rozhodnutí o směrování SABRE, i když může stále zavádět některá SWAP hradla. StarPrerouting však zvyšuje dobu běhu a vyžaduje integraci do počátečního procesu transpilace.
Pro účely tohoto tutoriálu vylučujeme HighLevelSynthesis i StarPrerouting, abychom izolovali a zdůraznili přímý dopad konfigurace SABRE na dobu běhu a hloubku obvodu. Měřením očekávané hodnoty pro každý pár qubitů analyzujeme:
- Jak dobře SABRE snižuje SWAP hradla a hloubku obvodu.
- Vliv těchto optimalizací na věrnost provedeného obvodu, kde odchylky od naznačují ztrátu provázání.!
# set seed for reproducibility
seed = 42
num_qubits = 110
# Create GHZ circuit
qc = QuantumCircuit(num_qubits)
qc.h(0)
for i in range(1, num_qubits):
qc.cx(0, i)
qc.measure_all()
Dále namapujeme operátory zájmu pro hodnocení chování systému. Konkrétně použijeme operátory ZZ mezi qubity k prozkoumání toho, jak provázání degraduje s tím, jak se qubity od sebe vzdalují. Tato analýza je kritická, protože nepřesnosti v očekávaných hodnotách pro vzdálené qubity mohou odhalit dopad šumu a chyb při provádění obvodu. Studiem těchto odchylek získáme přehled o tom, jak dobře obvod zachovává provázání při různých konfiguracích SABRE a jak efektivně SABRE minimalizuje dopad hardwarových omezení.
# ZZII...II, ZIZI...II, ... , ZIII...IZ
operator_strings = [
"Z" + "I" * i + "Z" + "I" * (num_qubits - 2 - i)
for i in range(num_qubits - 1)
]
print(operator_strings)
print(len(operator_strings))
operators = [SparsePauliOp(operator) for operator in operator_strings]
['ZZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZIII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZII', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZI', 'ZIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIZ']
109
Krok 2: Optimalizace problému pro spuštění na kvantovém hardwaru
V tomto kroku se zaměřujeme na optimalizaci rozmístění obvodu pro spuštění na konkrétním kvantovém hardwarovém zařízení s 127 qubity. Toto je hlavní zaměření tutoriálu, protože provádíme optimalizace SABRE a transpilaci pro dosažení nejlepšího výkonu obvodu. Pomocí passu SabreLayout určujeme počáteční mapování qubitů, které minimalizuje potřebu SWAP hradel během směrování. Předáním coupling_map cílového backendu SabreLayout přizpůsobuje rozmístění omezením konektivity zařízení.
Budeme používat generate_preset_pass_manager s optimization_level=3 pro proces transpilace a přizpůsobíme pass SabreLayout různými konfiguracemi. Cílem je najít nastavení, které produkuje transpilovaný Circuit s nejnižší velikostí a/nebo hloubkou, což demonstruje dopad optimalizací SABRE.
Proč jsou velikost a hloubka Circuit důležité?
- Nižší velikost (počet hradel): Snižuje počet operací, čímž minimalizuje příležitosti pro akumulaci chyb.
- Nižší hloubka: Zkracuje celkovou dobu provádění, což je klíčové pro vyhnutí se dekoherenci a zachování věrnosti kvantového stavu.
Optimalizací těchto metrik zlepšujeme spolehlivost a přesnost provádění obvodu na hlučném kvantovém hardwaru. Vyber Backend.
service = QiskitRuntimeService()
# backend = service.least_busy(
# operational=True, simulator=False, min_num_qubits=127
# )
backend = service.backend("ibm_boston")
print(f"Using backend: {backend.name}")
Using backend: ibm_boston
Pro hodnocení dopadu různých konfigurací na optimalizaci obvodu vytvoříme tři pass managery, každý s jedinečnými nastaveními pro pass SabreLayout. Tyto konfigurace pomáhají analyzovat kompromis mezi kvalitou obvodu a dobou transpilace.
Klíčové parametry
max_iterations: Počet iterací směrování vpřed-vzad pro upřesnění rozmístění a snížení nákladů na směrování.layout_trials: Počet testovaných náhodných počátečních rozmístění, přičemž se vybere to, které minimalizuje SWAP hradla.swap_trials: Počet pokusů směrování pro každé rozmístění, upřesňující umístění hradel pro lepší směrování.
Zvýšením layout_trials a swap_trials provedeš důkladnější optimalizaci za cenu zvýšené doby transpilace.
Konfigurace v tomto tutoriálu
-
pm_1: Výchozí nastavení soptimization_level=3.max_iterations=4layout_trials=20swap_trials=20
-
pm_2: Zvyšuje počet pokusů pro lepší průzkum.max_iterations=4layout_trials=200swap_trials=200
-
pm_3: Rozšiřujepm_2zvýšením počtu iterací pro další upřesnění.max_iterations=8layout_trials=200swap_trials=200
Porovnáním výsledků těchto konfigurací chceme určit, která dosahuje nejlepší rovnováhy mezi kvalitou obvodu (například velikostí a hloubkou) a výpočetními náklady.
# Get the coupling map from the backend
cmap = CouplingMap(backend().configuration().coupling_map)
# Create the SabreLayout passes for the custom configurations
sl_2 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=4,
layout_trials=200,
swap_trials=200,
)
sl_3 = SabreLayout(
coupling_map=cmap,
seed=seed,
max_iterations=8,
layout_trials=200,
swap_trials=200,
)
# Create the pass managers, need to first create then configure the SabreLayout passes
pm_1 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_2 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_3 = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
Nyní můžeme nakonfigurovat pass SabreLayout ve vlastních pass managerech. K tomu víme, že pro výchozí generate_preset_pass_manager na optimization_level=3 je pass SabreLayout na indexu 2, protože SabreLayout nastupuje po passech SetLayout a VF2Layout. Tento pass můžeme zpřístupnit a upravit jeho parametry.
pm_2.layout.replace(index=2, passes=sl_2)
pm_3.layout.replace(index=2, passes=sl_3)
S každým nakonfigurovaným pass managerem nyní spustíme proces transpilace pro každý z nich. Pro porovnání výsledků budeme sledovat klíčové metriky, včetně doby transpilace, hloubky obvodu (měřené jako hloubka dvouqubitových hradel) a celkového počtu hradel v transpilovaných obvodech.
# Transpile the circuit with each pass manager and measure the time
t0 = time.time()
tqc_1 = pm_1.run(qc)
t1 = time.time() - t0
t0 = time.time()
tqc_2 = pm_2.run(qc)
t2 = time.time() - t0
t0 = time.time()
tqc_3 = pm_3.run(qc)
t3 = time.time() - t0
# Obtain the depths and the total number of gates (circuit size)
depth_1 = tqc_1.depth(lambda x: x.operation.num_qubits == 2)
depth_2 = tqc_2.depth(lambda x: x.operation.num_qubits == 2)
depth_3 = tqc_3.depth(lambda x: x.operation.num_qubits == 2)
size_1 = tqc_1.size()
size_2 = tqc_2.size()
size_3 = tqc_3.size()
# Transform the observables to match the backend's ISA
operators_list_1 = [op.apply_layout(tqc_1.layout) for op in operators]
operators_list_2 = [op.apply_layout(tqc_2.layout) for op in operators]
operators_list_3 = [op.apply_layout(tqc_3.layout) for op in operators]
# Compute improvements compared to pass manager 1 (default)
depth_improvement_2 = ((depth_1 - depth_2) / depth_1) * 100
depth_improvement_3 = ((depth_1 - depth_3) / depth_1) * 100
size_improvement_2 = ((size_1 - size_2) / size_1) * 100
size_improvement_3 = ((size_1 - size_3) / size_1) * 100
time_increase_2 = ((t2 - t1) / t1) * 100
time_increase_3 = ((t3 - t1) / t1) * 100
print(
f"Pass manager 1 (4,20,20) : Depth {depth_1}, Size {size_1}, Time {t1:.4f} s"
)
print(
f"Pass manager 2 (4,200,200): Depth {depth_2}, Size {size_2}, Time {t2:.4f} s"
)
print(f" - Depth improvement: {depth_improvement_2:.2f}%")
print(f" - Size improvement: {size_improvement_2:.2f}%")
print(f" - Time increase: {time_increase_2:.2f}%")
print(
f"Pass manager 3 (8,200,200): Depth {depth_3}, Size {size_3}, Time {t3:.4f} s"
)
print(f" - Depth improvement: {depth_improvement_3:.2f}%")
print(f" - Size improvement: {size_improvement_3:.2f}%")
print(f" - Time increase: {time_increase_3:.2f}%")
Pass manager 1 (4,20,20) : Depth 439, Size 2346, Time 0.5775 s
Pass manager 2 (4,200,200): Depth 395, Size 2070, Time 3.9927 s
- Depth improvement: 10.02%
- Size improvement: 11.76%
- Time increase: 591.43%
Pass manager 3 (8,200,200): Depth 375, Size 1873, Time 2.3079 s
- Depth improvement: 14.58%
- Size improvement: 20.16%
- Time increase: 299.67%
Výsledky ukazují, že zvýšení počtu pokusů (layout_trials a swap_trials) může výrazně zlepšit kvalitu obvodu snížením hloubky i velikosti. Toto zlepšení však často přichází za cenu zvýšené doby běhu kvůli dodatečné výpočetní zátěži potřebné pro prozkoumání více potenciálních rozmístění a cest směrování.
Zvýšení max_iterations může dále posílit optimalizaci upřesněním rozmístění prostřednictvím více cyklů směrování vpřed-vzad. V tomto případě vedlo zvýšení max_iterations k nejvýznamnějšímu snížení hloubky a velikosti obvodu, přičemž dokonce snížilo dobu běhu ve srovnání s pm_2, pravděpodobně zefektivněním následujících optimalizačních fází. Je však důležité poznamenat, že účinnost zvyšování max_iterations se může výrazně lišit v závislosti na obvodu. Ačkoli více iterací může přinést lepší volby rozmístění a směrování, neposkytují žádné záruky a závisí silně na struktuře obvodu a složitosti omezení konektivity.
# Plot the results of the metrics
times = [t1, t2, t3]
depths = [depth_1, depth_2, depth_3]
sizes = [size_1, size_2, size_3]
pm_names = [
"pm_1 (4 iter, 20 trials)",
"pm_2 (4 iter, 200 trials)",
"pm_3 (8 iter, 200 trials)",
]
colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(pm_names)))
# Create a figure with three subplots
fig, axs = plt.subplots(3, 1, figsize=(6, 9), sharex=True)
axs[0].bar(pm_names, times, color=colors)
axs[0].set_ylabel("Time (s)", fontsize=12)
axs[0].set_title("Transpilation Time", fontsize=14)
axs[0].grid(axis="y", linestyle="--", alpha=0.7)
axs[1].bar(pm_names, depths, color=colors)
axs[1].set_ylabel("Depth", fontsize=12)
axs[1].set_title("Circuit Depth", fontsize=14)
axs[1].grid(axis="y", linestyle="--", alpha=0.7)
axs[2].bar(pm_names, sizes, color=colors)
axs[2].set_ylabel("Size", fontsize=12)
axs[2].set_title("Circuit Size", fontsize=14)
axs[2].set_xticks(range(len(pm_names)))
axs[2].set_xticklabels(pm_names, fontsize=10, rotation=15)
axs[2].grid(axis="y", linestyle="--", alpha=0.7)
# Add some spacing between subplots
plt.tight_layout()
plt.show()
Krok 3: Spuštění pomocí primitiv Qiskit
V tomto kroku používáme primitivum Estimator k výpočtu střední hodnot pro operátory ZZ, čímž vyhodnocujeme provázanost a kvalitu spuštění transpilovaných Circuit. Aby byl postup v souladu s typickými uživatelskými pracovními postupy, odešleme úlohu ke spuštění a aplikujeme potlačení chyb pomocí dynamického odspřahování — techniky, která zmírňuje dekoherenci vkládáním sekvencí Gate pro zachování stavů Qubit. Dále specifikujeme úroveň odolnosti pro potlačení šumu; vyšší úrovně poskytují přesnější výsledky za cenu delší doby zpracování. Tento přístup hodnotí výkon každé konfigurace správce průchodů za realistických podmínek spuštění.
options = EstimatorOptions()
options.resilience_level = 2
options.dynamical_decoupling.enable = True
options.dynamical_decoupling.sequence_type = "XY4"
# Create an Estimator object
estimator = Estimator(backend, options=options)
# Submit the circuit to Estimator
job_1 = estimator.run([(tqc_1, operators_list_1)])
job_1_id = job_1.job_id()
print(job_1_id)
job_2 = estimator.run([(tqc_2, operators_list_2)])
job_2_id = job_2.job_id()
print(job_2_id)
job_3 = estimator.run([(tqc_3, operators_list_3)])
job_3_id = job_3.job_id()
print(job_3_id)
d5k0qs7853es738dab6g
d5k0qsf853es738dab70
d5k0qsf853es738dab7g
# Run the jobs
result_1 = job_1.result()[0]
print("Job 1 done")
result_2 = job_2.result()[0]
print("Job 2 done")
result_3 = job_3.result()[0]
print("Job 3 done")
Job 1 done
Job 2 done
Job 3 done
Krok 4: Následné zpracování a vrácení výsledku v požadovaném klasickém formátu
Jakmile úloha dokončí běh, analyzujeme výsledky vykreslením středních hodnot pro každý Qubit. V ideální simulaci by všechny hodnoty měly být 1, což odráží dokonalé provázání napříč Qubity. Kvůli šumu a omezením hardwaru však střední hodnoty obvykle klesají s rostoucím i, což ukazuje, jak se provázanost snižuje se vzdáleností.
V tomto kroku porovnáme výsledky každé konfigurace správce průchodů s ideální simulací. Zkoumáním odchylky od hodnoty 1 pro každou konfiguraci můžeme kvantifikovat, jak dobře každý správce průchodů zachovává provázanost a zmírňuje vliv šumu. Tato analýza přímo hodnotí dopad optimalizací SABRE na věrnost spuštění a ukazuje, která konfigurace nejlépe vyvažuje kvalitu optimalizace a výkon spuštění.
Výsledky budou vizualizovány tak, aby vynikly rozdíly mezi správci průchodů a ukázalo se, jak vylepšení rozmístění a routování ovlivňují konečné spuštění Circuit na zašuměném kvantovém hardwaru.
data = list(range(1, len(operators) + 1)) # Distance between the Z operators
values_1 = list(result_1.data.evs)
values_2 = list(result_2.data.evs)
values_3 = list(result_3.data.evs)
plt.plot(
data,
values_1,
marker="o",
label="pm_1 (iters=4, swap_trials=20, layout_trials=20)",
)
plt.plot(
data,
values_2,
marker="s",
label="pm_2 (iters=4, swap_trials=200, layout_trials=200)",
)
plt.plot(
data,
values_3,
marker="^",
label="pm_3 (iters=8, swap_trials=200, layout_trials=200)",
)
plt.xlabel("Distance between qubits $i$")
plt.ylabel(r"$\langle Z_i Z_0 \rangle / \langle Z_1 Z_0 \rangle $")
plt.legend()
plt.show()

Analýza výsledků
Graf zobrazuje střední hodnoty v závislosti na vzdálenosti mezi Qubity pro tři konfigurace správce průchodů s rostoucí mírou optimalizace. V ideálním případě zůstávají tyto hodnoty blízko 1, což svědčí o silných korelacích napříč Circuit. S rostoucí vzdáleností vedou šum a nahromaděné chyby k poklesu korelací, čímž se odhaluje, jak dobře každá transpilační strategie zachovává základní strukturu stavu.
Ze tří konfigurací si pm_1 jednoznačně vede nejhůře. Hodnoty korelací rychle klesají s rostoucí vzdáleností a blíží se nule mnohem dříve než u zbývajících dvou konfigurací. Toto chování odpovídá větší hloubce Circuit a počtu Gate, kde nahromaděný šum rychle degraduje korelace na dlouhé vzdálenosti.
pm_2 i pm_3 představují výrazné zlepšení oproti pm_1 prakticky pro všechny vzdálenosti. V průměru vykazuje pm_3 nejsilnější celkový výkon — udržuje vyšší hodnoty korelací na delší vzdálenosti a vykazuje pozvolnější pokles. To odpovídá jeho agresivnější optimalizaci, která vytváří mělčí Circuit, jež jsou obecně odolnější vůči hromadění šumu.
Přesto pm_2 vykazuje výrazně lepší přesnost na krátké vzdálenosti ve srovnání s pm_3, přestože má o něco větší hloubku a počet Gate. To naznačuje, že hloubka Circuit sama o sobě plně neurčuje výkon; důležitou roli hraje také konkrétní struktura vytvořená transpilací, včetně způsobu uspořádání provazujících Gate a způsobu šíření chyb Circuit. V některých případech se zdá, že transformace aplikované pm_2 lépe zachovávají lokální korelace, i když se dobře neškálují na delší vzdálenosti.
Celkově tyto výsledky poukazují na kompromis mezi kompaktností Circuit a jeho strukturou. Ačkoli vyšší úroveň optimalizace obecně zlepšuje stabilitu na dlouhé vzdálenosti, nejlepší výkon pro danou pozorovatelnou veličinu závisí jak na snížení hloubky Circuit, tak na vytvoření struktury, která dobře odpovídá charakteristikám šumu hardwaru.
Část II. Konfigurace heuristiky v SABRE a použití Serverless
Kromě úpravy počtu pokusů podporuje SABRE také přizpůsobení směrovací heuristiky používané během transpilace. Ve výchozím nastavení používá SabreLayout heuristiku decay, která dynamicky váží qubity podle pravděpodobnosti jejich přepnutí. Chceš-li použít jinou heuristiku (například heuristiku lookahead), můžeš vytvořit vlastní průchod SabreSwap a připojit ho k SabreLayout spuštěním PassManager s FullAncillaAllocation, EnlargeWithAncilla a ApplyLayout. Při použití SabreSwap jako parametru pro SabreLayout se ve výchozím nastavení provede pouze jeden pokus o rozložení. K efektivnímu spuštění více pokusů o rozložení využíváme serverless runtime pro paralelizaci. Více informací o serverless najdeš v dokumentaci Serverless.
Jak změnit směrovací heuristiku
- Vytvoř vlastní průchod
SabreSwaps požadovanou heuristikou. - Použij tento vlastní
SabreSwapjako metodu směrování pro průchodSabreLayout.
I když je možné spustit více pokusů o rozložení pomocí smyčky, serverless runtime je lepší volbou pro rozsáhlé a náročnější experimenty. Serverless podporuje paralelní provádění pokusů o rozložení, což výrazně urychluje optimalizaci větších Circuit a rozsáhlých experimentálních přehledů. To ho činí obzvláště cenným při práci s výpočetně náročnými úlohami nebo tam, kde je důležitá časová efektivita.
Tato část se zaměřuje výhradně na krok 2 optimalizace: minimalizaci velikosti a hloubky Circuit za účelem dosažení nejlepšího možného transpilovaného Circuit. Navazujíce na dřívější výsledky, zkoumáme nyní, jak přizpůsobení heuristiky a serverless paralelizace mohou dále zlepšit výkon optimalizace a učinit ji vhodnou pro rozsáhlou transpilaci kvantových Circuit.
Výsledky bez serverless runtime (1 pokus o rozložení):
swap_trials = 1000
# Default PassManager with `SabreLayout` and `SabreSwap`, using heuristic "decay"
sr_default = SabreSwap(
coupling_map=cmap, heuristic="decay", trials=swap_trials, seed=seed
)
sl_default = SabreLayout(
coupling_map=cmap, routing_pass=sr_default, seed=seed
)
pm_default = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_default.layout.replace(index=2, passes=sl_default)
pm_default.routing.replace(index=1, passes=sr_default)
t0 = time.time()
tqc_default = pm_default.run(qc)
t_default = time.time() - t0
size_default = tqc_default.size()
depth_default = tqc_default.depth(lambda x: x.operation.num_qubits == 2)
# Custom PassManager with `SabreLayout` and `SabreSwap`, using heuristic "lookahead"
sr_custom = SabreSwap(
coupling_map=cmap, heuristic="lookahead", trials=swap_trials, seed=seed
)
sl_custom = SabreLayout(coupling_map=cmap, routing_pass=sr_custom, seed=seed)
pm_custom = generate_preset_pass_manager(
optimization_level=3, backend=backend, seed_transpiler=seed
)
pm_custom.layout.replace(index=2, passes=sl_custom)
pm_custom.routing.replace(index=1, passes=sr_custom)
t0 = time.time()
tqc_custom = pm_custom.run(qc)
t_custom = time.time() - t0
size_custom = tqc_custom.size()
depth_custom = tqc_custom.depth(lambda x: x.operation.num_qubits == 2)
print(
f"Default (heuristic='decay') : Depth {depth_default}, Size {size_default}, Time {t_default}"
)
print(
f"Custom (heuristic='lookahead'): Depth {depth_custom}, Size {size_custom}, Time {t_custom}"
)
Default (heuristic='decay') : Depth 443, Size 3115, Time 1.034372091293335
Custom (heuristic='lookahead'): Depth 432, Size 2856, Time 0.6669301986694336
Vidíme, že heuristika lookahead si vede lépe než heuristika decay z hlediska hloubky, velikosti a času Circuit. Toto zlepšení ukazuje, jak můžeme SABRE optimalizovat i jinak než jen pomocí pokusů a iterací pro konkrétní Circuit a hardwarová omezení. Všimni si, že tyto výsledky jsou založeny na jediném pokusu o rozložení. Pro dosažení přesnějších výsledků doporučujeme spustit více pokusů o rozložení, což lze efektivně provést pomocí serverless runtime.
Výsledky se serverless runtime (více pokusů o rozložení)
Qiskit Serverless vyžaduje nastavení souborů .py tvé úlohy do vyhrazeného adresáře. Následující buňka kódu je Python soubor v adresáři source_files s názvem transpile_remote.py. Tento soubor obsahuje funkci, která spouští proces transpilace.
# This cell is hidden from users, it makes sure the `source_files` directory exists
from pathlib import Path
Path("source_files").mkdir(exist_ok=True)
%%writefile source_files/transpile_remote.py
import time
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.transpiler.passes import SabreLayout, SabreSwap
from qiskit.transpiler import CouplingMap
from qiskit_serverless import get_arguments, save_result, distribute_task, get
from qiskit_ibm_runtime import QiskitRuntimeService
@distribute_task(target={
"cpu": 1,
"mem": 1024 * 1024 * 1024
})
def transpile_remote(qc, optimization_level, backend_name, seed, swap_trials, heuristic):
"""Transpiles an abstract circuit into an ISA circuit for a given backend."""
service = QiskitRuntimeService()
backend = service.backend(backend_name)
pm = generate_preset_pass_manager(
optimization_level=optimization_level,
backend=backend,
seed_transpiler=seed
)
# Changing the `SabreLayout` and `SabreSwap` passes to use the custom configurations
cmap = CouplingMap(backend().configuration().coupling_map)
sr = SabreSwap(coupling_map=cmap, heuristic=heuristic, trials=swap_trials, seed=seed)
sl = SabreLayout(coupling_map=cmap, routing_pass=sr, seed=seed)
pm.layout.replace(index=2, passes=sl)
pm.routing.replace(index=1, passes=sr)
# Measure the transpile time
start_time = time.time() # Start timer
tqc = pm.run(qc) # Transpile the circuit
end_time = time.time() # End timer
transpile_time = end_time - start_time # Calculate the elapsed time
return tqc, transpile_time # Return both the transpiled circuit and the transpile time
# Get program arguments
arguments = get_arguments()
circuit = arguments.get("circuit")
backend_name = arguments.get("backend_name")
optimization_level = arguments.get("optimization_level")
seed_list = arguments.get("seed_list")
swap_trials = arguments.get("swap_trials")
heuristic = arguments.get("heuristic")
# Transpile the circuits
transpile_worker_references = [
transpile_remote(circuit, optimization_level, backend_name, seed, swap_trials, heuristic)
for seed in seed_list
]
results_with_times = get(transpile_worker_references)
# Separate the transpiled circuits and their transpile times
transpiled_circuits = [result[0] for result in results_with_times]
transpile_times = [result[1] for result in results_with_times]
# Save both results and transpile times
save_result({"transpiled_circuits": transpiled_circuits, "transpile_times": transpile_times})
Overwriting source_files/transpile_remote.py
Následující buňka nahraje soubor transpile_remote.py jako program Qiskit Serverless pod názvem transpile_remote_serverless.
serverless = QiskitServerless()
transpile_remote_demo = QiskitFunction(
title="transpile_remote_serverless",
entrypoint="transpile_remote.py",
working_dir="./source_files/",
)
serverless.upload(transpile_remote_demo)
transpile_remote_serverless = serverless.load("transpile_remote_serverless")
Vygeneruj 20 různých seedů reprezentujících 20 různých pokusů o rozložení.
num_seeds = 20 # represents the different layout trials
seed_list = [seed + i for i in range(num_seeds)]
Spusť nahraný program a předej vstupy pro heuristiku lookahead.
job_lookahead = transpile_remote_serverless.run(
circuit=qc,
backend_name=backend.name,
optimization_level=3,
seed_list=seed_list,
swap_trials=swap_trials,
heuristic="lookahead",
)
job_lookahead.job_id
'15767dfc-e71d-4720-94d6-9212f72334c2'
job_lookahead.status()
'QUEUED'
Získej logy a výsledky ze serverless runtime.
logs_lookahead = job_lookahead.logs()
print(logs_lookahead)
No logs yet.
Jakmile je program ve stavu DONE, můžeš použít job.results() k načtení výsledku uloženého v save_result().
# Run the job with lookahead heuristic
start_time = time.time()
results_lookahead = job_lookahead.result()
end_time = time.time()
job_lookahead_time = end_time - start_time
Nyní proveď totéž pro heuristiku decay.
job_decay = transpile_remote_serverless.run(
circuit=qc,
backend_name=backend.name,
optimization_level=3,
seed_list=seed_list,
swap_trials=swap_trials,
heuristic="decay",
)
job_decay.job_id
'00418c76-d6ec-4bd8-9f70-05d0fa14d4eb'
logs_decay = job_decay.logs()
print(logs_decay)
No logs yet.
# Run the job with the decay heuristic
start_time = time.time()
results_decay = job_decay.result()
end_time = time.time()
job_decay_time = end_time - start_time
# Extract transpilation times
transpile_times_decay = results_decay["transpile_times"]
transpile_times_lookahead = results_lookahead["transpile_times"]
# Calculate total transpilation time for serial execution
total_transpile_time_decay = sum(transpile_times_decay)
total_transpile_time_lookahead = sum(transpile_times_lookahead)
# Print total transpilation time
print("=== Total Transpilation Time (Serial Execution) ===")
print(f"Decay Heuristic : {total_transpile_time_decay:.2f} seconds")
print(f"Lookahead Heuristic: {total_transpile_time_lookahead:.2f} seconds")
# Print serverless job time (parallel execution)
print("\n=== Serverless Job Time (Parallel Execution) ===")
print(f"Decay Heuristic : {job_decay_time:.2f} seconds")
print(f"Lookahead Heuristic: {job_lookahead_time:.2f} seconds")
# Calculate and print average runtime per transpilation
avg_transpile_time_decay = total_transpile_time_decay / num_seeds
avg_transpile_time_lookahead = total_transpile_time_lookahead / num_seeds
avg_job_time_decay = job_decay_time / num_seeds
avg_job_time_lookahead = job_lookahead_time / num_seeds
print("\n=== Average Time Per Transpilation ===")
print(f"Decay Heuristic (Serial) : {avg_transpile_time_decay:.2f} seconds")
print(f"Decay Heuristic (Serverless): {avg_job_time_decay:.2f} seconds")
print(
f"Lookahead Heuristic (Serial) : {avg_transpile_time_lookahead:.2f} seconds"
)
print(
f"Lookahead Heuristic (Serverless): {avg_job_time_lookahead:.2f} seconds"
)
# Calculate and print serverless improvement percentage
decay_improvement_percentage = (
(total_transpile_time_decay - job_decay_time) / total_transpile_time_decay
) * 100
lookahead_improvement_percentage = (
(total_transpile_time_lookahead - job_lookahead_time)
/ total_transpile_time_lookahead
) * 100
print("\n=== Serverless Improvement ===")
print(f"Decay Heuristic : {decay_improvement_percentage:.2f}%")
print(f"Lookahead Heuristic: {lookahead_improvement_percentage:.2f}%")
=== Total Transpilation Time (Serial Execution) ===
Decay Heuristic : 112.37 seconds
Lookahead Heuristic: 85.37 seconds
=== Serverless Job Time (Parallel Execution) ===
Decay Heuristic : 5.72 seconds
Lookahead Heuristic: 5.85 seconds
=== Average Time Per Transpilation ===
Decay Heuristic (Serial) : 5.62 seconds
Decay Heuristic (Serverless): 0.29 seconds
Lookahead Heuristic (Serial) : 4.27 seconds
Lookahead Heuristic (Serverless): 0.29 seconds
=== Serverless Improvement ===
Decay Heuristic : 94.91%
Lookahead Heuristic: 93.14%
Tyto výsledky ukazují podstatné zisky v efektivitě při použití serverless provádění pro transpilaci kvantových Circuit. Ve srovnání se sériovým prováděním serverless provádění dramaticky zkracuje celkový čas běhu pro heuristiky decay i lookahead tím, že paralelizuje nezávislé pokusy o transpilaci. Zatímco sériové provádění odráží celkové kumulativní náklady na prozkoumání více pokusů o rozložení, časy serverless úloh ukazují, jak paralelní provádění tyto náklady snižuje na výrazně kratší čas na hodinách. Výsledkem je, že efektivní čas na transpilaci se zkrátí na malý zlomek toho, co je potřeba v sériovém nastavení, a to prakticky nezávisle na použité heuristice. Tato schopnost je obzvláště důležitá pro plnou optimalizaci SABRE. Mnohé z nejsilnějších výkonnostních zisků SABRE pocházejí ze zvýšení počtu pokusů o rozložení a směrování, což může být při sekvenčním provádění nepřijatelně nákladné. Serverless provádění odstraňuje toto úzké hrdlo a umožňuje rozsáhlé přehledy parametrů a hlubší zkoumání konfigurací heuristik s minimální režií.
Celkově tato zjištění ukazují, že serverless provádění je klíčem ke škálování optimalizace SABRE, čímž se agresivní experimentování a zdokonalování stává praktickým ve srovnání se sériovým prováděním. Získej výsledky ze serverless runtime a porovnej výsledky heuristik lookahead a decay. Budeme porovnávat velikosti a hloubky.
# Extract sizes and depths
sizes_lookahead = [
circuit.size() for circuit in results_lookahead["transpiled_circuits"]
]
depths_lookahead = [
circuit.depth(lambda x: x.operation.num_qubits == 2)
for circuit in results_lookahead["transpiled_circuits"]
]
sizes_decay = [
circuit.size() for circuit in results_decay["transpiled_circuits"]
]
depths_decay = [
circuit.depth(lambda x: x.operation.num_qubits == 2)
for circuit in results_decay["transpiled_circuits"]
]
def create_scatterplot(x, y1, y2, xlabel, ylabel, title, labels, colors):
plt.figure(figsize=(8, 5))
plt.scatter(
x, y1, label=labels[0], color=colors[0], alpha=0.8, edgecolor="k"
)
plt.scatter(
x, y2, label=labels[1], color=colors[1], alpha=0.8, edgecolor="k"
)
plt.xlabel(xlabel, fontsize=12)
plt.ylabel(ylabel, fontsize=12)
plt.title(title, fontsize=14)
plt.legend(fontsize=10)
plt.grid(axis="y", linestyle="--", alpha=0.7)
plt.tight_layout()
plt.show()
create_scatterplot(
seed_list,
sizes_lookahead,
sizes_decay,
"Seed",
"Size",
"Circuit Size",
["lookahead", "Decay"],
["blue", "red"],
)
create_scatterplot(
seed_list,
depths_lookahead,
depths_decay,
"Seed",
"Depth",
"Circuit Depth",
["lookahead", "Decay"],
["blue", "red"],
)


Každý bod ve výše uvedených bodových grafech představuje jeden pokus o rozložení, přičemž osa x udává hloubku Circuit a osa y udává velikost Circuit. Výsledky ukazují, že heuristika lookahead obecně překonává heuristiku decay při minimalizaci hloubky a velikosti Circuit. V praktických aplikacích je cílem identifikovat optimální pokus o rozložení pro zvolenou heuristiku, ať již upřednostňuješ hloubku nebo velikost. Toho lze dosáhnout výběrem pokusu s nejnižší hodnotou pro požadovanou metriku. Důležité je, že zvýšení počtu pokusů o rozložení zlepšuje šance na dosažení lepšího výsledku z hlediska velikosti nebo hloubky, ale za cenu vyšší výpočetní zátěže.
min_depth_lookahead = min(depths_lookahead)
min_depth_decay = min(depths_decay)
min_size_lookahead = min(sizes_lookahead)
min_size_decay = min(sizes_decay)
print(
"Lookahead: Min Depth",
min_depth_lookahead,
"Min Size",
min_size_lookahead,
)
print("Decay: Min Depth", min_depth_decay, "Min Size", min_size_decay)
Lookahead: Min Depth 399 Min Size 2452
Decay: Min Depth 415 Min Size 2611
V našem počátečním srovnání s jediným pokusem o rozložení vykazovala heuristika lookahead mírně lepší výkon jak v hloubce, tak ve velikosti Circuit. Rozšířením tohoto studia na více pokusů o rozložení pomocí QiskitServerless jsme byli schopni prozkoumat mnohem širší prostor inicializací SABRE, což umožnilo reprezentativnější srovnání heuristik.
Z bodových grafů a nejlepších pozorovaných výsledků je zřejmé, že výkon se výrazně liší v závislosti na náhodném seedu použitém v SABRE. Obě heuristiky vykazují velký rozptyl hloubky a velikosti Circuit napříč seedy, což naznačuje, že jedno spuštění je často nedostatečné pro zachycení téměř optimálních výsledků. Tato variabilita zdůrazňuje důležitost spuštění mnoha pokusů s různými seedy při snaze o minimalizaci hloubky a/nebo počtu Gate. Napříč celou sadou pokusů byly obě heuristiky lookahead i decay schopny produkovat konkurenceschopné výsledky. V některých případech heuristika decay dosáhla nebo dokonce překonala heuristiku lookahead pro konkrétní seedy. Pro tento konkrétní Circuit však nejlepší celkové výsledky přinesla heuristika lookahead, byť s malým rozdílem. To naznačuje, že ačkoli lookahead poskytl zde nejsilnější výsledek, jeho výhoda nad decay není absolutní.
Celkově tyto výsledky potvrzují dva klíčové body. Za prvé, využití mnoha seedů je nezbytné pro dosažení nejlepšího možného výkonu z SABRE bez ohledu na použitou heuristiku. Za druhé, ačkoli volba heuristiky záleží, struktura Circuit hraje dominantní roli a relativní výkon lookahead a decay se může lišit pro jiné Circuit. Proto je rozsáhlá experimentace s více seedy kritická pro robustní a efektivní transpilaci kvantových Circuit.
# This cell is hidden from users, it cleans up the `source_files` directory
from pathlib import Path
Path("source_files/transpile_remote.py").unlink()
Path("source_files").rmdir()
Závěr
V tomto tutoriálu jsme prozkoumali, jak optimalizovat velké Circuit pomocí SABRE v Qiskit. Ukázali jsme, jak konfigurovat průchod SabreLayout s různými parametry pro vyvážení kvality Circuit a doby transpilace. Také jsme ukázali, jak přizpůsobit směrovací heuristiku v SABRE a použít runtime QiskitServerless k efektivní paralelizaci pokusů o rozložení pro případ, kdy je zapojen SabreSwap. Úpravou těchto parametrů a heuristik můžeš optimalizovat rozložení a směrování velkých Circuit a zajistit jejich efektivní provádění na kvantovém hardwaru.
Průzkum tutoriálu
Prosím, vyplň tento krátký průzkum a poskytni nám zpětnou vazbu k tomuto tutoriálu. Tvoje poznatky nám pomohou zlepšit naši nabídku obsahu a uživatelský zážitek.
Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.