Entanglement pe distanță lungă cu circuite dinamice
Estimare de utilizare: 4 minute pe un procesor Heron r2. (NOTĂ: Aceasta este doar o estimare. Timpul tău de execuție poate varia.)
Fundal
Entanglement-ul pe distanță lungă între qubiți aflați la distanță este dificil de realizat pe dispozitive cu conectivitate limitată. Acest tutorial arată cum circuitele dinamice pot genera un astfel de entanglement prin implementarea unei porți X controlate pe distanță lungă (LRCX), folosind un protocol bazat pe măsurători.
Urmând abordarea lui Elisa Bäumer et al. din 1, metoda utilizează măsurători mid-circuit și feedforward pentru a obține porți de adâncime constantă, indiferent de separarea dintre qubiți. Se creează perechi Bell intermediare, se măsoară câte un qubit din fiecare pereche și se aplică porți condiționate clasic pentru a propaga entanglement-ul prin dispozitiv. Aceasta evită lanțurile lungi de SWAP, reducând atât adâncimea circuitului, cât și expunerea la erori ale porților cu doi qubiți.
În acest notebook, adaptăm protocolul pentru hardware-ul IBM Quantum® și îl extindem pentru a rula mai multe operații LRCX în paralel, permițându-ne să explorăm cum performanța variază în funcție de numărul de operații condiționale simultane.
Cerințe
Înainte de a începe acest tutorial, asigură-te că ai instalat:
- Qiskit SDK v2.0 sau mai recent, cu suport pentru vizualizare
- Qiskit Runtime (
pip install qiskit-ibm-runtime) v0.37 sau mai recent
Configurare
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-ibm-runtime
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
Pasul 1: Maparea intrărilor clasice pe o problemă cuantică
Implementăm acum o poartă CNOT pe distanță lungă între doi qubiți aflați la distanță, urmând construcția cu circuite dinamice prezentată mai jos (adaptată din Fig. 1a din Ref. 1). Ideea cheie este de a folosi un „bus" de qubiți ancilă, inițializați în , pentru a media teleportarea porților pe distanță lungă.

Așa cum este ilustrat în figură, procesul funcționează astfel:
- Pregătim un lanț de perechi Bell care conectează qubiții de control și țintă prin ancile intermediare.
- Efectuăm măsurători Bell între qubiții vecini ne-entanglați, schimbând entanglement-ul pas cu pas până când qubiții de control și țintă împart o pereche Bell.
- Folosim această pereche Bell pentru teleportarea porților, transformând un CNOT local într-un CNOT determinist pe distanță lungă, de adâncime constantă.
Această abordare înlocuiește lanțurile lungi de SWAP cu un protocol de adâncime constantă, reducând expunerea la erori ale porților cu doi qubiți și făcând operația scalabilă cu dimensiunea dispozitivului.
În continuare, vom parcurge mai întâi implementarea cu circuite dinamice a circuitului LRCX. La final, vom oferi și o implementare bazată pe unitare pentru comparație, pentru a evidenția avantajele circuitelor dinamice în acest context.
(i) Inițializarea circuitului
Începem cu o problemă cuantică simplă care va servi drept bază de comparație. Mai precis, inițializăm un circuit cu un qubit de control la indexul 0 și îi aplicăm o poartă Hadamard. Aceasta produce o stare de superpoziție care, urmată de o operație X controlată, generează o stare Bell între qubiții de control și țintă.
În acest moment, nu construim încă LRCX-ul pe distanță lungă în sine. Scopul nostru este să definim un circuit inițial clar și minimal, care evidențiază rolul LRCX. În Pasul 2, vom arăta cum LRCX poate fi implementat ca o optimizare folosind circuite dinamice și vom compara performanța sa cu un echivalent unitar. Important, protocolul LRCX poate fi aplicat oricărui circuit inițial. Aici folosim această configurare simplă cu Hadamard pentru claritatea demonstrației.
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)
Pasul 2: Optimizarea problemei pentru execuția pe hardware cuantic
În acest pas, arătăm cum să construiești circuitul LRCX folosind circuite dinamice. Scopul este de a optimiza circuitul pentru execuția pe hardware, reducând adâncimea față de o implementare pur unitară. Pentru a ilustra beneficiile, vom afișa atât construcția dinamică LRCX, cât și echivalentul său unitar, și le vom compara ulterior performanța după transpilare. Important, deși aici aplicăm LRCX unei probleme simple inițializate cu Hadamard, protocolul poate fi aplicat oricărui circuit care necesită un CNOT pe distanță lungă.
(ii) Pregătirea perechilor Bell
Începem prin crearea unui lanț de perechi Bell de-a lungul traseului dintre qubiții de control și țintă. Dacă distanța este impară, aplicăm mai întâi un CNOT de la qubitele de control la vecinul său, care este CNOT-ul ce va fi teleportat. Pentru o distanță pară, acest CNOT va fi aplicat după pasul de pregătire a perechilor Bell. Lanțul de perechi Bell entanglează apoi perechi succesive de qubiți, stabilind resursa necesară pentru a transporta informația de control prin dispozitiv.
# 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)
(iii) Măsurarea perechilor de qubiți vecini în baza Bell
Urmează să măsurăm qubiții vecini ne-entanglați în baza Bell (măsurători cu doi qubiți ale lui și ). Aceasta creează o pereche Bell pe distanță lungă între qubitele țintă și qubitele adiacent controlului (cu corecții Pauli, care vor fi implementate prin feedforward în pasul următor). În paralel, implementăm măsurătoarea de entanglare care teleportează poarta CNOT pentru a acționa pe qubitele țintă vizat.
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)
(iv) Aplicarea corecțiilor feedforward pentru a corecta operatorii Pauli secundari
Măsurătorile în baza Bell introduc produse secundare Pauli care trebuie corectate folosind rezultatele înregistrate. Aceasta se face în doi pași. Mai întâi, trebuie să calculăm paritatea tuturor măsurătorilor , care este apoi folosită pentru a aplica condiționat o poartă qubitului țintă. De asemenea, paritatea măsurătorilor este calculată și folosită pentru a aplica condiționat o poartă qubitului de control.
Cu noul cadru de expresii clasice din Qiskit, aceste parități pot fi calculate direct în stratul de procesare clasică al circuitului. În loc să aplicăm o secvență de porți condiționale individuale pentru fiecare bit de măsurătoare, putem construi o singură expresie clasică ce reprezintă XOR-ul (paritatea) tuturor rezultatelor de măsurătoare relevante. Această expresie este apoi folosită ca și condiție într-un singur bloc if_test, permițând aplicarea porților de corecție de adâncime constantă. Această abordare atât simplifică circuitul, cât și asigură că corecțiile feedforward nu introduc latență suplimentară inutilă.
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)
(v) În final, măsoară qubiții de control și țintă
Definim o funcție auxiliară care permite măsurarea qubiților de control și țintă în bazele , sau . Pentru verificarea stării Bell , valorile așteptate ale și ar trebui să fie ambele , deoarece sunt stabilizatori ai stării. Măsurătoarea este de asemenea suportată aici și va fi folosită mai jos la calculul fidelității.
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")
display(
qc_YY.draw(output="mpl", fold=-1, scale=0.5)
) # Circuit for measuring in the YY basis
Asamblarea tuturor componentelor
Combinăm diferiții pași definiți mai sus pentru a crea o poartă CX pe distanță lungă la cele două capete ale unei linii 1D. Pașii includ:
- Inițializarea qubitului de control în
- Pregătirea perechilor Bell
- Măsurarea perechilor de qubiți vecini
- Aplicarea corecțiilor feedforward condiționate de MCM-uri
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"]
]
display(
qc_YY.draw(output="mpl", fold=-1, scale=0.5)
) # Circuit for measuring in the YY basis
Generează circuite pentru distanțe diferite
Generăm acum circuite CX cu rază lungă pentru o gamă de separații între qubiți. Pentru fiecare distanță, construim circuite care măsoară în bazele , și , care vor fi folosite ulterior pentru a calcula fidelitățile.
Lista distanțelor include atât separații scurte, cât și lungi, unde distance = 0 corespunde unui CX cu vecini apropiați. Aceleași distanțe vor fi folosite și pentru a genera circuitele unitare corespunzătoare mai târziu, pentru comparație.
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
) # Only works for distance larger than 2 because classical register cannot be empty
basis_list = ["XX", "YY", "ZZ"]
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)}")
circuits_dyn[14].draw(fold=-1, output="mpl", idle_wires=False)
Number of circuits: 39

Implementare unitară prin schimbarea qubiților la mijloc
Pentru comparație, examinăm mai întâi cazul în care o poartă CNOT cu rază lungă este implementată folosind conexiuni cu vecini apropiați și porți unitare. În figura de mai jos, în stânga se află un circuit pentru o poartă CNOT cu rază lungă ce traversează un lanț 1D de n-qubiți supus doar conexiunilor cu vecini apropiați. La mijloc se află o descompunere unitară echivalentă, implementabilă cu porți CNOT locale, adâncimea circuitului fiind .

Circuitul din mijloc poate fi implementat astfel:
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
Acum construiește toate circuitele unitare și construiește circuitele care măsoară în bazele , și , exact cum am făcut pentru circuitele dinamice de mai sus.
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)}")
circuits_uni[14].draw(fold=-1, output="mpl", idle_wires=False)
Number of circuits: 39

Acum că avem atât circuite dinamice, cât și unitare pentru o gamă de distanțe, suntem pregătiți pentru transpilare. Mai întâi trebuie să selectăm un dispozitiv backend.
# 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
)
Următorul pas se asigură că backend-ul acceptă instrucțiunea if_else, care este necesară pentru versiunea mai nouă a circuitelor dinamice. Deoarece această funcționalitate este încă în acces timpuriu, adăugăm explicit IfElseOp la ținta backend-ului, dacă nu este deja disponibil.
if "if_else" not in backend.target.operation_names:
backend.target.add_instruction(IfElseOp, name="if_else")
Folosește șirul Layer Fidelity pentru selectarea lanțului 1D
Deoarece dorim să comparăm performanța circuitelor dinamice și unitare pe un lanț 1D, folosim șirul Layer Fidelity pentru a selecta o topologie liniară cu cel mai bun lanț de qubiți de pe dispozitiv. Aceasta asigură că ambele tipuri de circuite sunt transpilate cu aceleași constrângeri de conectivitate, permițând o comparație corectă a performanței lor.
# 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
[10, 11, 12, 13, 14, 15, 19, 35, 34, 33, 39, 53, 54, 55, 59, 75, 74, 73, 72, 71, 58, 51, 50, 49, 48, 47, 46, 45, 44, 43, 56, 63, 62, 61, 76, 81, 82, 83, 84, 85, 77, 65, 66, 67, 68, 69, 78, 89, 90, 91, 98, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101]
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: {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

print(
f"2Q depth: {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

Vizualizează qubiții folosiți pentru circuitul LRCX
În această secțiune, examinăm modul în care circuitul LRCX este mapat pe hardware. Începem prin vizualizarea qubiților fizici utilizați în circuit, apoi studiem cum distanța control–țintă din layout afectează numărul de operații.
# 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(),
)

Pasul 3: Executare folosind primitivele Qiskit
În acest pas, executăm experimentul pe Backend-ul specificat. Folosim și gruparea în loturi (batching) pentru a rula eficient experimentul pe mai multe runde. Rularea de runde repetate ne permite să calculăm medii pentru o comparație mai precisă între metodele unitare și dinamice, precum și să cuantificăm variabilitatea acestora prin compararea abaterilor între rulări.
print(backend.name)
ibm_kingston
Selectează numărul de runde și efectuează execuția în lot.
num_trials = 10
jobs_uni = []
jobs_dyn = []
with Batch(backend=backend) as batch:
sampler = Sampler(mode=batch)
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))
Pasul 4: Post-procesare și returnarea rezultatului în formatul clasic dorit
După ce experimentele s-au executat cu succes, post-procesăm acum numărul de măsurători pentru a extrage metrici semnificative. În acest pas:
- Definim metrici de calitate pentru evaluarea performanței CX pe distanță lungă.
- Calculăm valorile așteptate ale operatorilor Pauli din rezultatele brute ale măsurătorilor.
- Le folosim pentru a calcula fidelitatea stării Bell generate.
Această analiză oferă o imagine clară despre cât de bine performează Circuit-urile dinamice față de implementarea unitară de referință.
Metrici de calitate
Pentru a evalua succesul protocolului CX pe distanță lungă, măsurăm cât de apropiată este starea de ieșire de starea Bell ideală. O modalitate convenabilă de a cuantifica acest lucru este calcularea fidelității stării folosind valorile așteptate ale operatorilor Pauli. Fidelitatea pentru o stare Bell pe starea de control și pe cea țintă poate fi calculată după ce se cunosc , și . În particular,
Pentru a calcula aceste valori așteptate din datele brute de măsurare, definim un set de funcții auxiliare:
compute_ZZ_expectation: Dată o distribuție de numărători, calculează valoarea așteptată a unui operator Pauli cu doi Qubiți în baza .compute_fidelity: Combină valorile așteptate ale , și în expresia de fidelitate de mai sus.get_counts_from_bitarray: Utilitar pentru extragerea numărătorilor din obiectele de rezultate ale Backend-ului.
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)
Calculăm fidelitatea pentru Circuit-urile CX dinamice pe distanță lungă. Pentru fiecare distanță, extragem rezultatele măsurătorilor în bazele , și . Aceste rezultate sunt combinate folosind funcțiile auxiliare definite anterior pentru a calcula fidelitatea conform . Aceasta oferă fidelitatea observată a protocolului executat dinamic la fiecare distanță.
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)
Acum calculăm fidelitatea pentru Circuit-urile CX unitare pe distanță lungă, la fel cum am procedat pentru Circuit-urile dinamice de mai sus.
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)
Reprezentarea grafică a rezultatelor
Pentru a aprecia vizual rezultatele, celula de mai jos trasează fidelitățile estimate ale Gate-ului măsurate la distanțe variate între Qubiții întrețesați, pentru fiecare metodă.
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()

Din graficul de fidelitate de mai sus, LRCX nu a depășit în mod constant implementarea unitară directă. De fapt, pentru separări mici între control și țintă, Circuit-ul unitar a obținut o fidelitate mai mare. Totuși, la separări mai mari, Circuit-ul dinamic începe să atingă o fidelitate mai bună decât implementarea unitară. Acest comportament nu este neașteptat pe hardware-ul actual: deși Circuit-urile dinamice reduc adâncimea circuitului evitând lanțurile lungi de SWAP, ele introduc timp suplimentar de circuit din cauza măsurătorilor mid-circuit, a feedforward-ului clasic și a întârzierilor căilor de control. Latența adăugată crește decoerența și erorile de citire, care pot depăși câștigurile de adâncime la distanțe mici.
Cu toate acestea, observăm un punct de intersecție în care abordarea dinamică o depășește pe cea unitară. Acesta este un rezultat direct al scalării diferite: adâncimea Circuit-ului unitar crește liniar cu distanța dintre Qubiți, în timp ce adâncimea Circuit-ului dinamic rămâne constantă.
Puncte cheie:
- Beneficiul imediat al Circuit-urilor dinamice: Principala motivație actuală este reducerea adâncimii cu doi Qubiți, nu neapărat îmbunătățirea fidelității.
- De ce fidelitatea poate fi mai mică astăzi: Timpul suplimentar de circuit datorat măsurătorilor și operațiunilor clasice domină adesea, în special când separarea control–țintă este mică.
- Privind înainte: Pe măsură ce hardware-ul se îmbunătățește — mai precis citire mai rapidă, latență mai scurtă a controlului clasic și reducerea supraîncărcării mid-circuit — ne așteptăm ca aceste reduceri de adâncime și durată să se traducă în câștiguri de fidelitate măsurabile.
# 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()

Acest grafic al adâncimii cu doi Qubiți evidențiază principalul avantaj al LRCX implementat cu Circuit-uri dinamice: performanța rămâne esențial constantă pe măsură ce separarea dintre Qubiții de control și țintă crește. În contrast, implementarea unitară crește liniar cu distanța din cauza lanțurilor de SWAP necesare. Adâncimea captează scalarea logică a operațiunilor cu doi Qubiți, în timp ce numărul de măsurători reflectă supraîncărcarea suplimentară pentru Circuit-urile dinamice. Aceste măsurători sunt eficiente, deoarece sunt efectuate în paralel, dar introduc totuși un cost fix pe hardware-ul actual.
De ce fidelitatea poate fi mai mică astăzi: Timpul suplimentar de circuit datorat măsurătorilor și operațiunilor clasice domină adesea, în special când separarea control-țintă este mică. De exemplu, lungimea medie de citire pe un procesor Heron r2 este de 2.280 ns, în timp ce lungimea sa de Gate cu 2 Qubiți este de doar 68 ns.
Pe măsură ce latențele de măsurare și cele clasice se îmbunătățesc, ne așteptăm ca scalarea cu adâncime constantă și număr constant de măsurători a Circuit-urilor dinamice să ofere avantaje clare de fidelitate și timp de execuție pe Circuit-uri mai mari.
Referințe
[1] Efficient Long-Range Entanglement using Dynamic Circuits, de 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