Přeskočit na hlavní obsah
Tento projekt je v betě. Našel jsi chybu? Otevřít issue na GitHubu. Máš otázku nebo nápad? Zeptej se v Discussions — rádi si poslechneme tvou zpětnou vazbu!
Jaká je kvalita překladu na této stránce?

Provázání na dlouhou vzdálenost pomocí dynamických obvodů

Odhadovaná spotřeba: 4 minuty na procesoru Heron r2. (POZNÁMKA: Jde pouze o odhad. Skutečná doba běhu se může lišit.)

Co se naučíš​

  • Jak implementovat CNOT hradlo na dlouhou vzdálenost pomocí dynamických obvodů s měřeními uprostřed obvodu (MCM) a klasickou dopřednou vazbou (feedforward);

  • Jak implementovat ekvivalentní hradlo pomocí unitárního přístupu založeného na operacích SWAP;

  • Jak porovnat oba přístupy měřením věrnosti hradla jako funkce vzdálenosti qubitů.

Předpoklady​

Pozadí​

Provázání na dlouhou vzdálenost mezi vzdálenými Qubity je na zařízeních s omezenou konektivitou náročné. Tento tutoriál ukazuje, jak dynamické Circuit mohou takovéto provázání vytvořit implementací řízené operace X na dlouhou vzdálenost (LRCX) pomocí protokolu založeného na měření.

Podle přístupu Elisy Bäumer et al. v 1 metoda využívá měření uprostřed obvodu a dopřednou vazbu (feedforward) k dosažení obvodů konstantní hloubky bez ohledu na vzdálenost Qubitů. Vytváří mezilehlé Bellovy páry, změří jeden qubit z každého páru a aplikuje klasicky podmíněná hradla, aby propagovala provázání přes zařízení. Tím se vyhýbá dlouhým řetězcům SWAP, čímž snižuje jak hloubku obvodu, tak vystavení chybám dvoukubitových hradel.

V tomto notebooku přizpůsobujeme protokol pro hardware IBM Quantum a srovnáváme jeho výkon jako funkci vzdálenosti řídicí–cílový prvek oproti unitárnímu základu založenému na operacích SWAP.

Požadavky​

Před zahájením tohoto tutoriálu se ujisti, že máš nainstalované následující:

  • Qiskit SDK v2.0 nebo novější, s podporou vizualizace

  • Qiskit Runtime v0.37 nebo novější (pip install qiskit-ibm-runtime)

  • Qiskit Aer v0.17 nebo novější (pip install qiskit-aer)

Nastavení​

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.visualization import plot_circuit_layout
from qiskit_ibm_runtime import (
QiskitRuntimeService,
Batch,
SamplerV2 as Sampler,
)
import matplotlib.pyplot as plt
import numpy as np

Příklad s malým simulátorem​

Než spustíme na reálném QPU, ověříme, že dynamický i unitární obvod vytvoří ideální Bellův stav na bezhlučném simulátoru. Použijeme IBM Quantum Sampler s AerSimulator jako backendový režim, při malé vzdálenosti 6.

Krok 1: Mapování klasických vstupů na kvantový problém​

Nyní implementujeme CNOT hradlo na dlouhou vzdálenost mezi dvěma vzdálenými Qubity podle konstrukce s dynamickými Circuit zobrazenou níže (přizpůsobeno z obr. 1a v Ref. 1). Klíčovou myšlenkou je použití „sběrnice" ancilla Qubitů inicializovaných do ∣0⟩|0\rangle jako zprostředkovatelů teleportace hradla na dlouhou vzdálenost.

Long-range CNOT circuit

Jak je znázorněno na obrázku, postup funguje takto:

  1. Připrav řetěz Bellových párů spojující řídicí a cílový qubit přes mezilehlé ancilly.
  2. Proveď Bellova měření mezi neprovázanými sousedními Qubity, čímž se provázání krok za krokem přenáší, dokud řídicí a cílový qubit nesdílejí Bellův pár.
  3. Použij tento Bellův pár pro teleportaci hradla, čímž se lokální CNOT přemění v deterministický CNOT na dlouhou vzdálenost v konstantní hloubce.

Tento přístup nahrazuje dlouhé řetězce SWAP protokolem konstantní hloubky, čímž snižuje vystavení chybám dvoukubitových hradel a umožňuje škálování s velikostí zařízení.

V tom, co následuje, nejprve projdeme implementací Circuit LRCX pomocí dynamických Circuit. Na závěr poskytneme také unitárně založenou implementaci pro srovnání, abychom zdůraznili výhody dynamických Circuit v tomto kontextu.

Inicializace Circuit​

Začínáme jednoduchým kvantovým problémem, který poslouží jako základ pro srovnání. Konkrétně inicializujeme Circuit s řídicím Qubitem na indexu 0 a aplikujeme na něj Hadamardovo hradlo. To vytvoří superpozici, která po následné operaci controlled-X vytvoří Bellův stav (∣00⟩+∣11⟩)/2(|00\rangle + |11\rangle)/\sqrt{2} mezi řídicím a cílovým Qubitem.

V této fázi ještě nesestavujeme samotnou řízenou operaci X na dlouhou vzdálenost (LRCX). Naším cílem je definovat jasný a minimální počáteční Circuit, který zdůrazní roli LRCX. V Kroku 2 ukážeme, jak lze LRCX implementovat jako optimalizaci pomocí dynamických Circuit, a porovnáme jeho výkon s unitárním ekvivalentem. Je důležité poznamenat, že protokol LRCX lze aplikovat na libovolný počáteční Circuit. Zde pro přehlednost používáme toto jednoduché Hadamardovo nastavení.

distance = 6 # The distance of the CNOT gate, with the convention that a distance of zero is a nearest-neighbor CNOT.

def initialize_circuit(distance):
assert distance >= 0
control = 0 # control qubit
n = distance # number of qubits between target and control

qr = QuantumRegister(
n + 2, name="q"
) # Circuit with n qubits between control and target
cr = ClassicalRegister(
2, name="cr"
) # Classical register for measuring control and target qubits

k = int(n / 2) # Number of Bell States to be used

allcr = [cr]
if (
distance > 1
): # This classical register will be used to store ZZ measurements.
# It is only used for long-range CX gates with distance > 1
c1 = ClassicalRegister(
k, name="c1"
) # Classical register needed for post processing
allcr.append(c1)
if (
distance > 0
): # This classical register will be used to store XX measurements.
# It is only used if distance > 0
c2 = ClassicalRegister(
n - k, name="c2"
) # Classical register needed for post processing
allcr.append(c2)

qc = QuantumCircuit(qr, *allcr, name="CNOT")

# Apply a Hadamard gate to the control qubit such that the
# long-range CNOT gate will prepare a
# Bell state (|00> + |11>)/sqrt(2)
qc.h(control)

return qc

qc = initialize_circuit(distance)
qc.draw(fold=-1, output="mpl", scale=0.5)

Output of the previous code cell

Krok 2: Optimalizace problému pro spuštění na kvantovém hardware​

V tomto kroku ukážeme, jak sestavit Circuit LRCX pomocí dynamických Circuit. Cílem je optimalizovat Circuit pro spuštění na hardware snížením hloubky ve srovnání s čistě unitární implementací. Pro ilustraci výhod zobrazíme jak dynamickou konstrukci LRCX, tak její unitární ekvivalent, a porovnáme jejich výkon po transpilaci. Je důležité poznamenat, že ačkoli zde aplikujeme LRCX na jednoduchý problém inicializovaný Hadamardovým hradlem, protokol lze aplikovat na libovolný Circuit, kde je vyžadován CNOT na dlouhou vzdálenost.

Příprava Bellových párů​

Začínáme vytvořením řetězu Bellových párů podél cesty mezi řídicím a cílovým Qubitem. Pokud je vzdálenost lichá, nejprve aplikujeme CNOT z řídicího Qubitu na jeho souseda, což je CNOT, který bude teleportován. Pro sudou vzdálenost bude tento CNOT aplikován po kroku přípravy Bellových párů. Řetěz Bellových párů pak propojí po sobě jdoucí páry Qubitů a vytvoří zdroj potřebný k přenosu řídicí informace přes zařízení.

# Determine where to start the Bell pair chain and add an extra CNOT when n is odd
def check_even(n: int) -> int:
"""Return 1 if n is even, else 2."""
return 1 if n % 2 == 0 else 2

def prepare_bell_pairs(qc, add_barriers=True):
n = qc.num_qubits - 2 # number of qubits between target and control
k = int(n / 2)

if add_barriers:
qc.barrier()

x0 = check_even(n)
if n % 2 != 0:
qc.cx(0, 1)

# Create k Bell pairs
for i in range(k):
qc.h(x0 + 2 * i)
qc.cx(x0 + 2 * i, x0 + 2 * i + 1)
return qc

qc = prepare_bell_pairs(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

Měření sousedních párů Qubitů v Bellově bázi​

Dále měříme neprovázané sousední Qubity v Bellově bázi (dvoukubitová měření XXXX a ZZZZ). Tím vznikne Bellův pár na dlouhou vzdálenost mezi cílovým Qubitem a Qubitem sousedícím s řídicím (až na Pauliho korekce, které budou implementovány dopřednou vazbou (feedforward) v dalším kroku). Paralelně implementujeme provazující měření, které teleportuje hradlo CNOT tak, aby působilo na zamýšlený cílový qubit.

def measure_bell_basis(qc, add_barriers=True):
n = qc.num_qubits - 2 # number of qubits between target and control
k = int(n / 2)

if n > 1:
_, c1, c2 = qc.cregs
elif n > 0:
_, c2 = qc.cregs

# Determine where to start the Bell pair chain and add an extra CNOT
# when n is odd
x0 = 1 if n % 2 == 0 else 2

# Entangling layer that implements the Bell measurement
# (and additionally adds the CNOT to be
# teleported, if n is even)
for i in range(k + 1):
qc.cx(x0 - 1 + 2 * i, x0 + 2 * i)

for i in range(1, k + x0):
if i == 1:
qc.h(2 * i + 1 - x0)
else:
qc.h(2 * i + 1 - x0)

if add_barriers:
qc.barrier()

# Map the ZZ measurements onto classical register c1
for i in range(k):
if i == 0:
qc.measure(2 * i + x0, c1[i])
else:
qc.measure(2 * i + x0, c1[i])

# Map the XX measurements onto classical register c2
for i in range(1, k + x0):
if i == 1:
qc.measure(2 * i + 1 - x0, c2[i - 1])
else:
qc.measure(2 * i + 1 - x0, c2[i - 1])
return qc

qc = measure_bell_basis(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

Aplikace korekcí dopřednou vazbou (feedforward) pro opravu Pauliho vedlejších operátorů​

Měření v Bellově bázi zavádí Pauliho vedlejší produkty, které je nutné opravit pomocí zaznamenaných výsledků. To se provádí ve dvou krocích. Nejprve je třeba vypočítat paritu všech ZZZZ měření, která se pak používá pro podmíněné aplikování hradla XX na cílový qubit. Podobně se vypočítá parita XXXX měření a použije se pro podmíněné aplikování hradla ZZ na řídicí qubit.

S novým frameworkem klasických výrazů v Qiskitu lze tyto parity vypočítat přímo ve vrstvě klasického zpracování Circuit. Místo aplikování sekvence jednotlivých podmíněných hradel pro každý bit měření lze sestavit jediný klasický výraz reprezentující XOR (paritu) všech relevantních výsledků měření. Tento výraz se pak použije jako podmínka v jediném bloku if_test, což umožňuje aplikovat korekční hradla v konstantní hloubce. Tento přístup zjednodušuje Circuit a zajišťuje, že korekce dopřednou vazbou (feedforward) nezavádějí zbytečnou dodatečnou latenci.

def apply_ffwd_corrections(qc):
control = 0 # control qubit
target = qc.num_qubits - 1 # target qubit
n = qc.num_qubits - 2 # number of qubits between target and control

k = int(n / 2)
x0 = check_even(n)

if n > 1:
_, c1, c2 = qc.cregs
elif n > 0:
_, c2 = qc.cregs

# First, let's compute the parity of all ZZ measurements
for i in range(k):
if i == 0:
parity_ZZ = expr.lift(
c1[i]
) # Store the value of the first ZZ measurement in parity_ZZ
else:
parity_ZZ = expr.bit_xor(
c1[i], parity_ZZ
) # Successively compute the parity via XOR operations

for i in range(1, k + x0):
if i == 1:
parity_XX = expr.lift(
c2[i - 1]
) # Store the value of the first XX measurement in parity_XX
else:
parity_XX = expr.bit_xor(
c2[i - 1], parity_XX
) # Successively compute the parity via XOR operations

if n > 0:
with qc.if_test(parity_XX):
qc.z(control)

if n > 1:
with qc.if_test(parity_ZZ):
qc.x(target)
return qc

qc = apply_ffwd_corrections(qc)
qc.draw(output="mpl", fold=-1, scale=0.5)

Output of the previous code cell

Měření řídicího a cílového qubitu​

Definujeme pomocnou funkci, která umožňuje měření řídicího a cílového Qubitu v bázích XXXX, YYYY nebo ZZZZ. Pro ověření Bellova stavu (∣00⟩+∣11⟩)/2(|00\rangle + |11\rangle)/\sqrt{2} by střední hodnoty XXXX i ZZZZ měly být +1+1, protože jsou to stabilizátory tohoto stavu. Měření YYYY je zde také podporováno a bude použito níže při výpočtu věrnosti.

def measure_in_basis(qc, basis="XX", add_barrier=True):
control = 0 # control qubit
target = qc.num_qubits - 1 # target qubit

assert basis in ["XX", "YY", "ZZ"]

qc = (
qc.copy()
) # We copy the circuit because we want to measure in different bases
cr = qc.cregs[0]

if add_barrier:
qc.barrier()

if basis == "XX":
qc.h(control)
qc.h(target)
elif basis == "YY":
qc.sdg(control)
qc.sdg(target)
qc.h(control)
qc.h(target)

qc.measure(control, cr[0])
qc.measure(target, cr[1])
return qc

qc_YY = measure_in_basis(qc.copy(), basis="YY")
qc_YY.draw(
output="mpl", fold=-1, scale=0.5
) # Circuit for measuring in the YY basis

Output of the previous code cell

Vše dohromady​

Kombinujeme různé kroky definované výše a vytváříme hradlo CX na dlouhou vzdálenost na dvou koncích jednorozměrné (1D) linie. Kroky zahrnují následující:

  • Inicializaci řídicího Qubitu do ∣+⟩|+\rangle
  • Přípravu Bellových párů
  • Měření sousedních párů Qubitů
  • Aplikaci korekcí dopřednou vazbou (feedforward) závislých na MCM
def lrcx(distance, prep_barrier=True, pre_measure_barrier=True):
qc = initialize_circuit(distance)
qc = prepare_bell_pairs(qc, prep_barrier)
qc = measure_bell_basis(qc, pre_measure_barrier)
qc = apply_ffwd_corrections(qc)
return qc

qc = lrcx(distance)
# Apply the measurement in the XX, YY, and ZZ bases
qc_XX, qc_YY, qc_ZZ = [
measure_in_basis(qc, basis=basis) for basis in ["XX", "YY", "ZZ"]
]

qc_YY.draw(
output="mpl", fold=-1, scale=0.5
) # Circuit for measuring in the YY basis

Output of the previous code cell

Unitární implementace přesunutím qubitů do středu​

Pro porovnání nejprve prozkoumáme případ, kdy je hradlo CNOT na dlouhou vzdálenost implementováno pomocí spojení nejbližších sousedů a unitárních hradel. Na následujícím obrázku je vlevo obvod pro hradlo CNOT na dlouhou vzdálenost procházející 1D řetězcem n-qubitů podléhajícím pouze spojení nejbližších sousedů. Uprostřed je ekvivalentní unitární dekompozice implementovatelná s lokálními hradly CNOT, hloubka obvodu O(n)O(n).

Long-range CNOT circuit

Obvod uprostřed lze implementovat následovně:

def cnot_unitary(distance):
"""Generate a long range CNOT gate using local CNOTs on a 1D
chain of qubits subject to n
nearest-neighbor connections only.

Args:
distance (int) : The distance of the CNOT gate,
with the convention that
a distance of 0 is a nearest-neighbor CNOT.

Returns:
QuantumCircuit: A Quantum Circuit implementing a
long-range CNOT gate
between qubit 0 and qubit distance+1
"""
assert distance >= 0
n = distance # number of qubits between target and control

qr = QuantumRegister(
n + 2, name="q"
) # Circuit with n qubits between control and target
cr = ClassicalRegister(
2, name="cr"
) # Classical register for measuring control and target qubits

qc = QuantumCircuit(qr, cr, name="CNOT_unitary")

control_qubit = 0

qc.h(control_qubit) # Prepare the control qubit in the |+> state

k = int(n / 2)
qc.barrier()
for i in range(control_qubit, control_qubit + k):
qc.cx(i, i + 1)
qc.cx(i + 1, i)
qc.cx(-i - 1, -i - 2)
qc.cx(-i - 2, -i - 1)
if n % 2 == 1:
qc.cx(k + 2, k + 1)
qc.cx(k + 1, k + 2)
qc.barrier()
qc.cx(k, k + 1)
for i in range(control_qubit, control_qubit + k):
qc.cx(k - i, k - 1 - i)
qc.cx(k - 1 - i, k - i)
qc.cx(k + i + 1, k + i + 2)
qc.cx(k + i + 2, k + i + 1)
if n % 2 == 1:
qc.cx(-2, -1)
qc.cx(-1, -2)

return qc

qc_uni = cnot_unitary(distance)

Nyní sestav obvody, které měří v bázích XXXX, YYYY a ZZZZ, stejně jako jsme to udělali pro dynamické obvody výše.

# Apply the measurement in the XX, YY, and ZZ bases
qc_uni_XX, qc_uni_YY, qc_uni_ZZ = [
measure_in_basis(qc_uni, basis=basis) for basis in ["XX", "YY", "ZZ"]
]

qc_uni_YY.draw(
output="mpl", fold=-1, scale=0.5
) # Circuit for measuring in the YY basis

Output of the previous code cell

Nyní, když jsme sestavili dynamické i unitární obvody pro malý příklad s distance=6, transpilujeme je pro spuštění nejprve na bezhlučném simulátoru.

from qiskit_aer import AerSimulator

aer_backend = AerSimulator()
pm_sim = generate_preset_pass_manager(
optimization_level=0, backend=aer_backend
)

# Dynamic circuits
isa_sim_dyn = pm_sim.run([qc_XX, qc_YY, qc_ZZ])

# Unitary circuits
isa_sim_uni = pm_sim.run([qc_uni_XX, qc_uni_YY, qc_uni_ZZ])

Krok 3: Spuštění pomocí Qiskit primitives​

Nyní můžeme spustit experiment na bezhlučném simulátorovém backendu. Ke spuštění obvodů použijeme Qiskit Runtime Sampler s AerSimulator jako backendový režim.

sampler_sim = Sampler(mode=aer_backend)
sim_job = sampler_sim.run(isa_sim_dyn + isa_sim_uni)
sim_results = sim_job.result()

Krok 4: Post-processing a vrácení výsledku v požadovaném klasickém formátu​

Poté, co experimenty úspěšně proběhly, zpracujeme naměřené počty, abychom z nich získali smysluplné metriky. V tomto kroku:

  • Definujeme metriky kvality pro vyhodnocení výkonu CX na dlouhou vzdálenost.

  • Vypočítáme střední hodnoty Pauliho operátorů z nezpracovaných výsledků měření.

  • Použijeme je k výpočtu věrnosti vygenerovaného Bellova stavu.

V bezhlučné simulaci ověříme, že metrika věrnosti je pro sestavené obvody 11. V experimentech na reálných QPU tato analýza poskytne jasný přehled o tom, jak dobře dynamické Circuit fungují ve srovnání s unitární základní implementací.

Metriky kvality​

Abychom vyhodnotili úspěšnost protokolu CX na dlouhou vzdálenost, měříme, jak blízko je výstupní stav ideálnímu Bellovu stavu. Pohodlným způsobem, jak to kvantifikovat, je výpočet věrnosti stavu pomocí středních hodnot Pauliho operátorů. Věrnost pro Bellův stav na řídicím a cílovém stavu lze vypočítat po znalosti ⟨XX⟩\braket{XX}, ⟨YY⟩\braket{YY} a ⟨ZZ⟩\braket{ZZ}. Konkrétně:

F=14(1+⟨XX⟩−⟨YY⟩+⟨ZZ⟩) F = \frac{1}{4} (1 + \braket{XX} - \braket{YY} + \braket{ZZ})

Pro výpočet těchto středních hodnot z nezpracovaných dat měření definujeme sadu pomocných funkcí:

  • compute_ZZ_expectation: Na základě naměřených počtů vypočítá střední hodnotu dvouqubitového Pauliho operátoru v bázi ZZ.

  • compute_fidelity: Kombinuje střední hodnoty XXXX, YYYY a ZZZZ do výše uvedeného výrazu pro věrnost.

  • get_counts_from_bitarray: Nástroj pro extrakci počtů z objektů výsledků backendu.

def compute_ZZ_expectation(counts):
total = sum(counts.values())
expectation = 0
for bitstring, count in counts.items():
# Ensure bitstring is 2 bits
z1 = (-1) ** (int(bitstring[-1]))
z2 = (-1) ** (int(bitstring[-2]))
expectation += z1 * z2 * count
return expectation / total

def compute_fidelity(counts_xx, counts_yy, counts_zz):
xx, yy, zz = [
compute_ZZ_expectation(c) for c in [counts_xx, counts_yy, counts_zz]
]
return 1 / 4 * (1 + xx - yy + zz)
# Dynamic fidelity
counts_xx = sim_results[0].data.cr.get_counts()
counts_yy = sim_results[1].data.cr.get_counts()
counts_zz = sim_results[2].data.cr.get_counts()
fidelity_dyn = compute_fidelity(counts_xx, counts_yy, counts_zz)

# Unitary fidelity
counts_xx = sim_results[3].data.cr.get_counts()
counts_yy = sim_results[4].data.cr.get_counts()
counts_zz = sim_results[5].data.cr.get_counts()
fidelity_uni = compute_fidelity(counts_xx, counts_yy, counts_zz)

print(f"Dynamic fidelity (distance={distance}): {fidelity_dyn:.4f}")
print(f"Unitary fidelity (distance={distance}): {fidelity_uni:.4f}")
Dynamic fidelity (distance=6): 1.0000
Unitary fidelity (distance=6): 1.0000

Jak se očekávalo v bezhlučné simulaci, věrnosti u dynamických i unitárních obvodů jsou 11.

Příklad s reálným hardware ve velkém měřítku​

Nyní spojíme všechny tyto detaily do jediného pracovního postupu ve větším měřítku, který bude spuštěn na reálném kvantovém hardware.

Generování obvodů pro různé vzdálenosti​

Nyní generujeme CX obvody na dlouhou vzdálenost pro různé vzdálenosti oddělení qubitů až do 60 qubitů. Pro každou vzdálenost sestavíme obvody, které měří v bázích XXXX, YYYY a ZZZZ, které budou později použity k výpočtu věrnosti.

Seznam vzdáleností zahrnuje jak krátké, tak dlouhé vzdálenosti oddělení, přičemž distance = 0 odpovídá CX na nejbližší sousedy. Tyto stejné vzdálenosti budou také použity pro generování odpovídajících unitárních obvodů pro porovnání.

# -------------------------Step 1-------------------------
distances = [
0,
1,
2,
3,
6,
11,
16,
21,
28,
35,
44,
55,
60,
] # Distances for long range CX. distance of 0 is a nearest-neighbor CX
distances.sort()
assert min(distances) >= 0
basis_list = ["XX", "YY", "ZZ"]

# Dynamic circuits
circuits_dyn = []
for distance in distances:
for basis in basis_list:
circuits_dyn.append(
measure_in_basis(lrcx(distance, prep_barrier=False), basis=basis)
)
print(f"Number of circuits: {len(circuits_dyn)}")

# Unitary circuits
circuits_uni = []
for distance in distances:
for basis in basis_list:
circuits_uni.append(
measure_in_basis(cnot_unitary(distance), basis=basis)
)

print(f"Number of circuits: {len(circuits_uni)}")

Nyní, když máme dynamické i unitární obvody pro řadu vzdáleností, jsme připraveni na transpilaci. Nejprve musíme vybrat backendové zařízení.

# -------------------------Step 2-------------------------
# Set up access to IBM Quantum devices
from qiskit.circuit import IfElseOp

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=156
)

Následující krok zajišťuje, že backend podporuje instrukci if_else, která je vyžadována pro novější verzi dynamických obvodů. Protože je tato funkce stále v raném přístupu, explicitně přidáme IfElseOp do cíle backendu, pokud ještě není k dispozici.

if "if_else" not in backend.target.operation_names:
backend.target.add_instruction(IfElseOp, name="if_else")

Použití řetězce Layer Fidelity pro výběr 1D řetězce​

Protože chceme porovnat výkon dynamických a unitárních obvodů na 1D řetězci, použijeme řetězec Layer Fidelity k výběru lineární topologie nejlepšího řetězce qubitů ze zařízení. Tím zajistíme, že oba typy obvodů budou transpilovány za stejných omezení konektivity, což umožní spravedlivé porovnání jejich výkonu.

# This selects best qubits for longest distance and uses
# the same control for all lengths
lf_qubits = backend.properties().to_dict()[
"general_qlists"
] # best linear chain qubits
chosen_layouts = {
distance: [
val["qubits"]
for val in lf_qubits
if val["name"] == f"lf_{distances[-1] + 2}"
][0][: distance + 2]
for distance in distances
}
print(chosen_layouts[max(distances)]) # best qubits at each distance
[11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 76, 81, 82, 83, 84, 85, 86, 87, 97, 107, 108, 109, 110, 111, 98, 91, 92, 93, 94, 95, 99, 115, 114, 113, 119, 133, 132, 131, 138, 151, 150, 149, 148]
isa_circuits_dyn = []
isa_circuits_uni = []

# Using the same initial layouts for both circuits for better
# apples to apples comparison
for qc in circuits_dyn:
pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=chosen_layouts[qc.num_qubits - 2],
)
isa_circuits_dyn.append(pm.run(qc))

for qc in circuits_uni:
pm = generate_preset_pass_manager(
optimization_level=1,
backend=backend,
initial_layout=chosen_layouts[qc.num_qubits - 2],
)
isa_circuits_uni.append(pm.run(qc))
print(
f"2Q depth: "
f"{isa_circuits_dyn[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_dyn[14].draw("mpl", fold=-1, idle_wires=0)
2Q depth: 2

Output of the previous code cell

print(
f"2Q depth: "
f"{isa_circuits_uni[14].depth(lambda x: x.operation.num_qubits == 2)}"
)
isa_circuits_uni[14].draw("mpl", fold=-1, idle_wires=False)
2Q depth: 13

Output of the previous code cell

Vizualizace qubitů použitých v obvodu LRCX​

V této sekci zkoumáme, jak je obvod LRCX namapován na hardware. Začneme vizualizací fyzických qubitů použitých v obvodu a poté studujeme, jak vzdálenost řídicí–cílový qubit v layoutu ovlivňuje počet operací.

# Note: the qubit coordinates must be hard-coded.
# The backend API does not currently provide this information directly.
# If using a different backend, you will need to
# adjust the coordinates accordingly,
# or set the qubit_coordinates = None to use the default layout coordinates.

def _heron_coords_r2():
"""Generate coordinates for the Heron layout in R2. Note"""
cord_map = np.array(
[
[
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
1,
5,
9,
13,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
3,
7,
11,
15,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
],
-1
* np.array([j for i in range(15) for j in [i] * [16, 4][i % 2]]),
],
dtype=int,
)

hcords = []
ycords = cord_map[0]
xcords = cord_map[1]
for i in range(156):
hcords.append([xcords[i] + 1, np.abs(ycords[i]) + 1])

return hcords

# Visualize the active qubits in the circuit layout
plot_circuit_layout(
circuit=isa_circuits_uni[-1],
backend=backend,
view="physical",
qubit_coordinates=_heron_coords_r2(),
)

Output of the previous code cell

Dále spustíme experiment na reálném backendu. Využijeme také dávkování (batching) k efektivnímu provedení experimentu napříč více pokusy. Opakované pokusy nám umožní vypočítat průměry pro přesnější srovnání unitárních a dynamických metod a také kvantifikovat jejich variabilitu porovnáním odchylek napříč spuštěními.

# -------------------------Step 3-------------------------
num_trials = 10
jobs_uni = []
jobs_dyn = []
with Batch(backend=backend) as batch:
sampler = Sampler(mode=batch)
sampler.options.environment.job_tags = ["TUT_LRE"]
for _ in range(num_trials):
jobs_uni.append(sampler.run(isa_circuits_uni, shots=1024))
jobs_dyn.append(sampler.run(isa_circuits_dyn, shots=1024))

Vypočítáme věrnost pro dynamické CX Circuit na dlouhou vzdálenost. Pro každou vzdálenost extrahujeme výsledky měření v bázích ⟨XX⟩\braket{XX}, ⟨YY⟩\braket{YY} a ⟨ZZ⟩\braket{ZZ}. Tyto výsledky jsou kombinovány pomocí dříve definovaných pomocných funkcí k výpočtu věrnosti podle F=14(1+⟨XX⟩−⟨YY⟩+⟨ZZ⟩)F = \tfrac{1}{4} \big( 1 + \langle XX \rangle - \langle YY \rangle + \langle ZZ \rangle \big). Tím získáme pozorovanou věrnost dynamicky provedeného protokolu při každé vzdálenosti.

# -------------------------Step 4-------------------------
fidelities_dyn = []

# loop over trials
for job in jobs_dyn:
result_dyn = job.result()
trial_fidelities = []
# loop over all distances
for ind, dist in enumerate(distances):
counts_xx = result_dyn[ind * 3].data.cr.get_counts()
counts_yy = result_dyn[ind * 3 + 1].data.cr.get_counts()
counts_zz = result_dyn[ind * 3 + 2].data.cr.get_counts()
trial_fidelities.append(
compute_fidelity(counts_xx, counts_yy, counts_zz)
)
fidelities_dyn.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_dyn = np.mean(fidelities_dyn, axis=0)
std_fidelities_dyn = np.std(fidelities_dyn, axis=0)

Nyní vypočítáme věrnost pro unitární CX Circuit na dlouhou vzdálenost a uděláme to stejným způsobem jako pro dynamické Circuit výše.

fidelities_uni = []

# loop over trials
for job in jobs_uni:
result_uni = job.result()
trial_fidelities = []
# loop over all distances
for ind, dist in enumerate(distances):
counts_xx = result_uni[ind * 3].data.cr.get_counts()
counts_yy = result_uni[ind * 3 + 1].data.cr.get_counts()
counts_zz = result_uni[ind * 3 + 2].data.cr.get_counts()
trial_fidelities.append(
compute_fidelity(counts_xx, counts_yy, counts_zz)
)
fidelities_uni.append(trial_fidelities)
# average over trials for each distance
avg_fidelities_uni = np.mean(fidelities_uni, axis=0)
std_fidelities_uni = np.std(fidelities_uni, axis=0)

Vizualizace výsledků​

Abys mohl(a) výsledky ocenit vizuálně, buňka níže vykreslí odhadované věrnosti hradel naměřené při různých vzdálenostech mezi provázanými qubit pro každou metodu.

fig, ax = plt.subplots()

# Unitary with error bars
ax.errorbar(
distances,
avg_fidelities_uni,
yerr=std_fidelities_uni,
fmt="o-.",
color="c",
ecolor="c",
elinewidth=1,
capsize=4,
label="Unitary",
)
# Dynamic with error bars
ax.errorbar(
distances,
avg_fidelities_dyn,
yerr=std_fidelities_dyn,
fmt="o-.",
color="m",
ecolor="m",
elinewidth=1,
capsize=4,
label="Dynamic",
)
# Random gate baseline
ax.axhline(y=1 / 4, linestyle="--", color="gray", label="Random gate")

legend = ax.legend(frameon=True)
for text in legend.get_texts():
text.set_color("black")
legend.get_frame().set_facecolor("white")
legend.get_frame().set_edgecolor("black")
ax.set_title(
"Bell State Fidelity vs Control–Target Separation", color="black"
)
ax.set_xlabel("Distance", color="black")
ax.set_ylabel("Bell state fidelity", color="black")
ax.grid(linestyle=":", linewidth=0.6, alpha=0.4, color="gray")
ax.set_ylim((0.2, 1))
ax.set_facecolor("white")
fig.patch.set_facecolor("white")
for spine in ax.spines.values():
spine.set_visible(True)
spine.set_color("black")
ax.tick_params(axis="x", colors="black")
ax.tick_params(axis="y", colors="black")
plt.show()

Výstup předchozí buňky s kódem

Z grafu věrnosti výše je vidět, že LRCX ne vždy překonalo přímou unitární implementaci. Ve skutečnosti pro krátké separace řídicího a cílového prvku dosáhlo unitární Circuit vyšší věrnosti. Nicméně při větších separacích začíná dynamické Circuit dosahovat lepší věrnosti než unitární implementace. Toto chování není na současném hardwaru neočekávané: ačkoli dynamické Circuit snižují hloubku Circuit tím, že se vyhýbají dlouhým řetězcům operací SWAP, zavádějí dodatečný čas Circuit z měření uprostřed obvodu, klasické dopředné vazby (feedforward) a zpoždění řídicích cest. Přidaná latence zvyšuje dekoherenci a chyby čtení, které mohou při krátkých vzdálenostech převážit nad výhodami menší hloubky.

Přesto pozorujeme přechodový bod, kde dynamický přístup překonává unitární. To je přímým důsledkem odlišného škálování: hloubka unitárního Circuit roste lineárně se vzdáleností mezi qubit, zatímco hloubka dynamického Circuit zůstává konstantní.

Klíčové body:

  • Okamžitá výhoda dynamických Circuit: Hlavní současnou motivací je snížená hloubka dvouqubitových operací, nikoli nutně lepší věrnost.
  • Proč může být věrnost dnes horší: Zvýšený čas Circuit z měření a klasických operací často dominuje, zejména když je separace řídicího a cílového prvku malá.
  • Výhled do budoucna: S tím, jak se hardware zlepšuje — konkrétně rychlejší čtení, kratší latence klasického řízení a snížená režie měření uprostřed obvodu — bychom měli očekávat, že tato snížení hloubky a doby trvání se projeví jako měřitelné zisky věrnosti.
# Compute metrics for each distance, skipping the basis circuits since
# they are identical for each distance
depths_2q_dyn = [
c.depth(lambda x: x.operation.num_qubits == 2)
for c in isa_circuits_dyn[::3]
]
meas_dyn = [
sum(1 for instr in c.data if instr.operation.name == "measure")
for c in isa_circuits_dyn[::3]
]

depths_2q_uni = [
c.depth(lambda x: x.operation.num_qubits == 2)
for c in isa_circuits_uni[::3]
]
meas_uni = [
sum(1 for instr in c.data if instr.operation.name == "measure")
for c in isa_circuits_uni[::3]
]

fig, axes = plt.subplots(1, 2, figsize=(12, 5))

axes[0].plot(
distances, depths_2q_uni, "o-.", color="c", label="Unitary (2Q depth)"
)
axes[0].plot(
distances, depths_2q_dyn, "o-.", color="m", label="Dynamic (2Q depth)"
)
axes[0].set_xlabel("Number of qubits between control and target")
axes[0].set_ylabel("Two-qubit depth")
axes[0].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[0].legend()

axes[1].plot(
distances, meas_uni, "o-.", color="c", label="Unitary (# measurements)"
)
axes[1].plot(
distances, meas_dyn, "o-.", color="m", label="Dynamic (# measurements)"
)
axes[1].set_xlabel("Number of qubits between control and target")
axes[1].set_ylabel("Number of measurements")
axes[1].grid(True, linestyle=":", linewidth=0.6, alpha=0.4)
axes[1].legend()

fig.suptitle("Scaling of Unitary vs Dynamic LRCX with Distance", fontsize=12)

plt.tight_layout()
plt.show()

Výstup předchozí buňky s kódem

Tento graf hloubky dvouqubitových operací zdůrazňuje hlavní výhodu LRCX implementovaného s dynamickými Circuit: výkon zůstává v podstatě konstantní s rostoucí separací mezi řídicím a cílovým qubit. Naproti tomu unitární implementace roste lineárně se vzdáleností kvůli nutným řetězcům operací SWAP. Hloubka zachycuje logické škálování dvouqubitových operací, zatímco počet měření odráží dodatečnou režii u dynamických Circuit. Tato měření jsou efektivní, protože jsou prováděna paralelně, ale stále zavádějí fixní náklady na současném hardwaru.

Proč může být věrnost dnes horší: Zvýšený čas Circuit z měření a klasických operací často dominuje, zejména když je separace řídicího a cílového prvku malá. Například průměrná délka čtení na procesoru Heron r2 je 2 280 ns, zatímco délka jeho 2Q hradla je pouze 68 ns.

S tím, jak se latence měření a klasických operací zlepší, očekáváme, že konstantní škálování hloubky a měření dynamických Circuit přinese jasné výhody věrnosti a doby běhu u větších Circuit.

Další kroky​

Pokud tě tato práce zaujala, možná tě budou zajímat i následující materiály:

Reference​

[1] Efficient Long-Range Entanglement using Dynamic Circuits, od Elisa Bäumer, Vinay Tripathi, Derek S. Wang, Patrick Rall, Edward H. Chen, Swarnadeep Majumder, Alireza Seif, Zlatko K. Minev. IBM Quantum, (2023). https://arxiv.org/abs/2308.13065