Zom Hauptinhalt springe

Langstrecke-Verschränkung met dynamische Schaltkreise

Schätzung för d'Nutzung: 4 Minute op enem Heron r2-Prozessor. (OPJEPASS: Dat es bloß ä Schätzung. Et kann bei üch länger oder kööter doore.)

Hintergrund

Langstrecke-Verschränkung zwesche wiet ussenander liggende Qubits es ä risije Herausforderung op Jeräte met bejränzte Konnektivität. Hee zeije mir, wie dynamische Schaltkreise su jet jeneriere künne, indem m'r ä Langstrecke-Controlled-X-Gatter (LRCX) övver ä messungsbaseet Protokoll implementeere.

Noch däm Ansatz vun Elisa Bäumer et al. en 1 bruht die Method Mid-Circuit-Messunge un Feedforward, öm konstanti Gatterdeefe ze krije, egal wie wiet de Qubits ussenander sin. Se erzeugt Zweschenschritt-Bell-Paare, mess ä Qubit vun jedem Paar un wende klassisch bedingti Gatter aan, öm de Verschränkung övver et Jerät ze verbreite. Dat vermeidet lang SWAP-Kette un reduzeet domet de Schaltkreisdeefe un d'Anfälligkeit för Zwoi-Qubit-Gatterfähler.

En desem Notebook passe mir et Protokoll för IBM Quantum®-Hardware aan un erweitere et, öm miehre LRCX-Operatione parallel ze lofe ze losse, wat uns erlöv ze ongersöke, wie de Performance met dä Zahl vun gliechzeitige bedingge Operatione skaleet.

Vorraussetzunge

Bövör ehr met desem Tutorial aanfangt, stellt secker, dat ehr Foljendes installeet habt:

  • Qiskit SDK v2.0 oder spääter, met Visualisierung
  • Qiskit Runtime ( pip install qiskit-ibm-runtime ) v0.37 oder spääter

Setup

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

Schritt 1: Klassische Ingaabe op ä Quante-Problem afbeelde

Mir implementeere jetz ä Langstrecke-CNOT-Gatter zwesche zwei weite Qubits, noch dä dynamische Schaltkreis-Konstruktion, die unge jezeich es (aanjepasst vun Fig. 1a us Ref. 1). De zentrale Idee es, ä "Bus" vun Ancilla-Qubits, initialiseet met 0|0\rangle, ze bruche, öm langstrecke Gate-Teleportation ze vermettele.

Long-range CNOT circuit

Wie em Beld jezeich, lövt dä Prozess su:

  1. Bereitet ä Kettche vun Bell-Paare vör, dat de Control- un Target-Qubits övver Zwescheancillas verbenge deit.
  2. Föhrt Bell-Messunge zwesche net-verschränkte Nochbar-Qubits durch, swapped Verschränkung Schritt för Schritt, bes Control un Target ä Bell-Paar dele.
  3. Bruht dat Bell-Paar för Gate-Teleportation, wandelt ä lokales CNOT en ä deterministisches Langstrecke-CNOT en konstanter Deefe öm.

Dä Ansatz ersetz lang SWAP-Kette met enem konstante Deefe-Protokoll, reduzeet de Anfälligkeit för Zwoi-Qubit-Gatterfähler un määt de Operation skaleerbaa met dä Gerätsjrüüß.

Em Folggende wandere mir zuerst dörch de dynamische Schaltkreis-Implementierung vum LRCX-Schaltkreis. Am Engk stelle mir och ä unitäre Implementation för Verglich vör, öm de Vördeile vun dynamische Schaltkreise en desem Setting ze zoije.

(i) Schaltkreis initialisiere

Mir fange met enem einfache Quante-Problem aan, dat als Basis för Verglich deent. Konkret initialisiere mir ä Schaltkreis met enem Control-Qubit bei Index 0 un leije ä Hadamard-Gatter drop aan. Dat produzeet ä Superpositions-Zohstand, dä, wann hinger ä Controlled-X-Operation jefolg, ä Bell-Zohstand (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} zwesche Control- un Target-Qubits jenereet.

An desem Punkt konstrueere mir noch nit et Langstrecke-Controlled-X (LRCX) selbst. Stattdesse es uns Ziel, ä kloare un minimale Aanfangsschaltkreis ze definiere, dä de Rolle vum LRCX opzeich. En Schritt 2 zeije mir, wie et LRCX als Optimierung met dynamische Schaltkreise implementeet wääde kann un verglieche singe Performance jäge ä unitäres Äquivalent. Wichtich es, dat et LRCX-Protokoll op jevede Aanfangsschaltkreis aanjewandt wääde kann. Hee bruche mir dat einfache Hadamard-Setup för klare Demonstration.

distance = 6  # De Distanz vum CNOT-Gatter, met dä Konvention, dat ä Distanz vun null ä nöchsti-Nochbar-CNOT es.

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

Schritt 2: Problem för Quantehardware-Usföhrung optimiere

En desem Schritt zeije mir, wie mer dä LRCX-Schaltkreis met dynamische Schaltkreise konstrueere. Et Ziel es, dä Schaltkreis för de Usföhrung op Hardware ze optimiere, indem m'r de Deefe em Verglich zo ener rein unitäre Implementation reduziere. Öm de Vördeile ze illustreere, zeije mir sowohl de dynamische LRCX-Konstruktion als och ehr unitäres Äquivalent un verglieche spääter hör Performance noh Transpilation. Wichtich es, dat mir hee et LRCX op ä einfaches Hadamard-initialiseet Problem aanwende, et Protokoll kann ävver op jevede Schaltkreis aanjewandt wääde, woh ä Langstrecke-CNOT nüüdich es.

(ii) Bell-Paare vörbereide

Mir fange demit aan, ä Kettche vun Bell-Paare längs däm Paad zwesche Control- un Target-Qubits ze erstelle. Falls de Distanz ongerad es, wende mir zuerst ä CNOT vum Control ze singem Nochbar aan, dat es et CNOT, dat teleporteet weed. För ä jerade Distanz weed dat CNOT noh däm Bell-Paar-Vörbereitung-Schritt aanjewandt. Dat Bell-Paar-Kettche verschränkt dann nopenander liggende Qubit-Paare un etableet de Ressource, die jebrucht weed, öm de Control-Information övver et Jerät ze dreije.

# 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

(iii) Nochbar-Qubit-Paare en dä Bell-Basis messe

Als nöchstes messe mir net-verschränkti Nochbar-Qubits en dä Bell-Basis (Zwoi-Qubit-Messunge vun XXXX un ZZZZ). Dat kreeet ä Langstrecke-Bell-Paar zwesche däm Target-Qubit un däm Qubit näve däm Control (bes op Pauli-Korrektore, die övver Feedforward em nöchste Schritt implementeet wääde). Parallel dozo implementeere mir de verschränkende Messung, die et CNOT-Gatter teleporteet, öm op et beabsichtigti Target-Qubit ze werke.

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

(iv) Als nöchstes Feedforward-Korrektore aanwende, öm Pauli-Byprodukt-Operatore ze korrijeere

De Bell-Basis-Messunge föhre Pauli-Byprodukte en, die met de opjenommene Erjebnisse korrijeet wääde mösse. Dat jeschitt en zwei Schritte. Zuerst mösse mir de Parität vun all däne ZZZZ-Messunge berechne, die dann jenutzt weed, öm bedingt ä XX-Gatter op et Target-Qubit aanzeweende. Jenauso weed de Parität vun däne XXXX-Messunge berechnet un jenutzt, öm bedingt ä ZZ-Gatter op et Control-Qubit aanzeweende.

Met däm neue klassische Expression-Framework en Qiskit künne dees Paritäte direkt en dä klassische Verarbeitungs-Schicht vum Schaltkreis berechnet wääde. Anstatt ä Reihenfolg vun einzelne bedingge Gatter för jedes Messungs-Bit aanzeweende, künne mir ä einzelni klassische Expression boue, die et XOR (Parität) vun all relevante Messungs-Erjebnisse darstellt. Dees Expression weed dann als Bedingung en enem einzelne if_test-Block jebrucht, wat et de Korrektur-Gatter erlöv, en konstanter Deefe aanjewandt ze wääde. Dä Ansatz vereinfacht sowohl dä Schaltkreis als och stellt secker, dat de Feedforward-Korrektore kein onnüüdije zusätzliche Latenz enföhre.

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

(v) Schließlich Control- un Target-Qubits messe

Mir defineere ä Helfer-Funktion, die et ermöglisch, Control- un Target-Qubits en däne XXXX-, YYYY- oder ZZZZ-Base ze messe. För de Verifizierung vum Bell-Zohstand (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2} sollte de Erwartungswääte vun XXXX un ZZZZ beide +1+1 sin, do se Stabilisatore vum Zohstand sin. De YYYY-Messung weed hee och ongerstötzt un weed unge jenutzt, wann m'r de Fidelity berechne.

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

Output of the previous code cell

Dat janze zosamme setze

Mir kombiniere de verschiedene Schritte vun bovve, öm ä Langstrecke-CX-Gatter op zwei Enge vun ener 1D-Linie ze kreëere. De Schritte sin

  • Dä Control-Qubit en ket+\\ket{+} initialisiere
  • Bell-Paare vörbereide
  • Nochbar-Qubit-Paare messe
  • Feedforward-Korrektore abhängich vun däne MCMs aanwende
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

Output of the previous code cell

Schaltkreise för verschiedene Distanze jeneriere

Mir jeneriere jetz Langstrecke-CX-Schaltkreise för ä Bereich vun Qubit-Trennunge. För jede Distanz boue mir Schaltkreise, die en däne XXXX-, YYYY- un ZZZZ-Base messe, die spääter jenutzt wääde, öm Fidelitys ze berechne.

De Leest vun Distanze enthält sowohl koote als och lang Trennunge, wobei distance = 0 enem nöchsti-Nochbar-CX entsprich. Dees selve Distanze wääde och spääter jenutzt, öm de entsprechende unitäre Schaltkreise för Verglich ze jeneriere.

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

Output of the previous code cell

Unitäre Implementation, die de Qubits en de Mett swapped

För Verglich ongersöke mir zuerst dä Fall, woh ä Langstrecke-CNOT-Gatter met nöchsti-Nochbar-Verbindunge un unitäre Gatter implementeet weed. Em folggende Beld es links ä Schaltkreis för ä Langstrecke-CNOT-Gatter, dat ä 1D-Kettche vun n-Qubits överspannt, wobei bloß nöchsti-Nochbar-Verbindunge jenutzt wääde. En dä Mett es ä äquivalente unitäre Zerleijung, die met lokale CNOT-Gatter implementeerbar es, Schaltkreisdeefe O(n)O(n).

Long-range CNOT circuit

Dä Schaltkreis en dä Mett kann su implementeet wääde:

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

Jetz boue mir all unitäre Schaltkreise un erstelle de Schaltkreise, die en däne XXXX-, YYYY- un ZZZZ-Base messe, jenauso wie m'r et för de dynamische Schaltkreise bovve jemaat han.

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

Output of the previous code cell

Jetz, wo mir sowohl dynamische als och unitäre Schaltkreise för ä Bereich vun Distanze han, sin mir paraat för Transpilation. Mir mösse zuerst ä Backend-Jerät ußwähle.

# 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
)

Dä folggende Schritt stellt secker, dat et Backend de if_else-Instrukzjon ongerstötzt, die för de neuere Version vun dynamische Schaltkreise jebrucht weed. Do dees Funktion noch em Early Access es, föije mir et IfElseOp explizit zum Backend-Target dozo, falls et noch nit verfögbaa es.

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

Layer Fidelity String för Ußwahl vun ener 1D-Kette bruche

Do mir de Performance vun dynamische un unitäre Schaltkreise op ener 1D-Kette verglieche welle, bruche mir dä Layer Fidelity String, öm ä lineare Topologie vun dä besten Kettche vun Qubits usem Jerät ußzewähle. Dat stellt secker, dat beide Arte vun Schaltkreise ongere däne selve Konnektivitäts-Beschränkunge transpileet wääde, wat ä faire Verglich vun hör Performance erlöv.

# 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

Output of the previous code cell

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

Output of the previous code cell

Qubits visualiseere, die för dä LRCX-Schaltkreis jebrucht wääde

En desem Abschnitt ongersöke mir, wie dä LRCX-Schaltkreis op Hardware ömjesetzt weed. Mir fange demit aan, de physische Qubits ze visualiseere, die em Schaltkreis jebrucht wääde, un dann studiere mir, wie de Control–Target-Distanz em Layout de Zahl vun Operatione beiflusst.

# 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

Schritt 3: Met Qiskit-Primitives usföhre

En desem Schritt föhre mir et Experiment op däm aanjejovvene Backend us. Mir bruche och Batching, öm et Experiment efficient övver miehre Trials ze lofe ze losse. Wiederholt Trials erlaube uns, Durchschnitte för ä jenaure Verglich zwesche dä unitäre un dynamische Methode ze berechne, un och hör Variabilität ze quantifizeere, indem m'r de Abweichunge övver Runs verglieche.

print(backend.name)
ibm_kingston

Wählt Zahl vun Trials us un föhrt Batch-Usföhrung durch.

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))

Schritt 4: Nohbearbeide un Erjebnisse em jewönschte klassische Format retourniere

Nohdem de Experimente erfolsreich usjeföhrt worde sin, verarbeide mir jetz de Messungs-Counts noh, öm sinnvolle Metrike ze extrahiere. En desem Schritt:

  • Defineere mir Qualitäts-Metrike för de Evaluierung vun dä Performance vum Langstrecke-CX.
  • Berechne mir Erwartungswääte vun Pauli-Operatore us rohe Messungs-Erjebnisse.
  • Bruche mir dees, öm de Fidelity vum jenereetde Bell-Zohstand ze berechne.

Dees Analyse jitt uns ä klares Beld dovun, wie joot de dynamische Schaltkreise relativ zur unitäre Baseline-Implementation affschnigge.

Qualitäts-Metrike

Öm dä Erfolch vum Langstrecke-CX-Protokoll ze evaluiere, messe mir, wie noh dä Output-Zohstand am ideale Bell-Zohstand es. Ä bequeme Art, dat ze quantifizeere, es de State-Fidelity met Erwartungswääte vun Pauli-Operatore ze berechne. Fidelity för ä Bell-Zohstand op däm Control- un Target-Zohstand kann berechnet wääde, nohdem m'r et XX\braket{XX}, YY\braket{YY} un ZZ\braket{ZZ} wesse. Konkret,

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

Öm dees Erwartungswääte us rohe Messungs-Date ze berechne, defineere mir ä Satz vun Helfer-Funktione:

  • compute_ZZ_expectation: Berechnet, jäve Messungs-Counts, dä Erwartungswäät vun enem Zwoi-Qubit-Pauli-Operator en dä ZZ-Basis.
  • compute_fidelity: Kombineet de Erwartungswääte vun XXXX, YYYY un ZZZZ en de Fidelity-Expression bovve.
  • get_counts_from_bitarray: Helfer, öm Counts us Backend-Erjebnisse-Objekte ze extrahiere.
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)

Mir berechne de Fidelity för de dynamische Langstrecke-CX-Schaltkreise. För jede Distanz extrahiere mir Messungs-Erjebnisse en däne XX\braket{XX}-, YY\braket{YY}- un ZZ\braket{ZZ}-Base. Dees Erjebnisse wääde met däne vörher definiete Helfer-Funktione kombineet, öm de Fidelity noch F=14(1+XXYY+ZZ)F = \tfrac{1}{4} \big( 1 + \langle XX \rangle - \langle YY \rangle + \langle ZZ \rangle \big) ze berechne. Dat jitt de beobachtete Fidelity vum dynamisch usjeföhrte Protokoll bei jederer Distanz.

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)

Jetz berechne mir de Fidelity för de unitäre Langstrecke-CX-Schaltkreise, un mir maache et jenauso wie för de dynamische Schaltkreise bovve.

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)

De Erjebnisse plotte

Öm de Erjebnisse visuell ze appreciere, plott de Zell unge de jeschätzte Gatter-Fidelitys, die bei verschiedene Distanze zwesche verschränkte Qubits för de Methode jemesse worde sin.

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()

Output of the previous code cell

Usem Fidelity-Plot bovve hät et LRCX nit konstant besser affeschnitte wie de direkt unitäre Implementation. En Faat, för koote Control–Target-Trennunge hät dä unitäre Schaltkreis ä hühre Fidelity erreisch. Ävver bei jrüüßere Trennunge fänk dä dynamische Schaltkreis aan, besser Fidelity wie de unitäre Implementation ze erreische. Dat Verhalte es nit onerwaat op aktuelle Hardware: Während dynamische Schaltkreise de Schaltkreisdeefe reduziere, indem se lang SWAP-Kette vermeide, föhre se zusätzliche Schaltkreiszit us Mid-Circuit-Messunge, klassische Feedforward un Control-Path-Verzögerunge en. De zusätzliche Latenz erhöht Dekohärenz un Readout-Fähler, wat de Deefe-Ersparnisse bei koote Distanze överschatte kann.

Trotzdem beobachte mir ä Crossover-Punkt, woh dä dynamische Ansatz dä unitäre övvertrefft. Dat es ä direktes Erjebniss vum verschiedene Skalierung: De Deefe vum unitäre Schaltkreis wääß linear met dä Distanz zwesche Qubits, während de Deefe vum dynamische Schaltkreis konstant bliev.

Zentrale Ponkte:

  • Direkte Vördeil vun dynamische Schaltkreise: De hauptsächliche hüüdije Motivation es reduzeet Zwoi-Qubit-Deefe, nit nootwenicherwies verbesserte Fidelity.
  • Woröm Fidelity hück schlechter sin kann: Erhöhti Schaltkreiszit us Messung un klassische Operatione domineet off, besonders wann de Control–Target-Trennung klein es.
  • Bleck en de Zokoonft: Met besser Hardware, speziell schneller Readout, kööter klassische Control-Latenz un reduzeet Mid-Circuit-Overhead, sollte mir erwate, dat dees Deefe- un Dauer-Reduzierunge sech en messbare Fidelity-Jewenne ömsätze.
# 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()

Output of the previous code cell

Dä Zwoi-Qubit-Deefe-Plot hebt dä hauptsächliche Vördeil vum LRCX, implementeet met dynamische Schaltkreise, eruus: De Performance bliev im Wesentliche konstant, wie de Trennung zwesche Control- un Target-Qubits jrüüßer weed. Em Jejensatz dozo wääß de unitäre Implementation linear met dä Distanz wejen dä jebruchte SWAP-Kette. Deefe fänk de logische Skalierung vun Zwoi-Qubit-Operatione, während de Messungs-Zahl dä zusätzliche Overhead för dynamische Schaltkreise widerspigelt. Dees Messunge sin efficient, do se parallel durchjeföhrt wääde, ävver se föhre trotzdem fixe Koste op hüüdije Hardware en.

Woröm Fidelity hück schlechter sin kann: Erhöhti Schaltkreiszit us Messung un klassische Operatione domineet off, besonders wann de Control-Target-Trennung klein es. Zom Beispill, de durchschnittliche Readout-Längt op enem Heron r2-Prozessor es 2.280 ns, während sing 2Q-Gatterlängt bloß 68 ns es.

Met besser Messungs- un klassische Latenze erwate mir, dat de konstante Deefe- un konstante Messungs-Skalierung vun dynamische Schaltkreise klare Fidelity- un Runtime-Vördeile op jrüüßere Schaltkreise levere.

Referenze

[1] Efficient Long-Range Entanglement using Dynamic Circuits, by 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