Benchmark dynamic circuits with cut Bell pairs
Bruuch-Schätzig: 22 Sekunde uf emne Heron r2 Prozässor (ACHTUNG: Das isch nume e Schätzig. Eui Laufziit chan angers sii.)
Background
Quantehardware isch normalerwiis uf lokali Interakzione bschränkt, aber vieli Algorithme bruuched s, ass Qubits verschränkt wärde, wo wiit ussenand ligged oder Qubits uf verschiedene Prozässore. Dynamischi Schaltkreis - das sind Schaltkreis mit Messige und Feedforward imene Lauf - sörged defür, ass mer die Iischränkige chönd umgah, indem mer Echtzit-Kommunikation bruuched, zum effektiv nöd-lokali Quanteoperazione z'implementiere. Demit chönd Messresultat us eme Teil vom Schaltkreis (oder eme QPU) bedingt Gates uf eme andere triggere, so dass mer Verschränkig über grossi Distanze teleportiere chönd. Das isch d Basis vo lokale Operazione und klassischi Kommunikation (LOCC) Schemas, wo mer verschränkti Ressurce-Zueständ (Bell-Paare) verbruuched und Messresultat klassisch kommuniziered, zum wiit ussenand liegendi Qubits z'verbinde.
E vielversprächendi Nutzig vo LOCC isch s, virtuelli Lang-Strecke-CNOT-Gates dur Teleportation z'realisiere, wie im long-range entanglement tutorial zeigt. Aastatt emne direkte Lang-Strecke-CNOT (wo d Hardware-Konnektivität vilech nöd erlaubt), mached mer Bell-Paare und füehred e teleportationsbasierti Gate-Implementierig dur. Allerdings hängt d Fidelität vo söttige Operazione vo de Hardwareeigenschafte ab. Qubit-Dekohärenz während de nötige Verzögerig (während mer uf Messresultat warted) und klassischi Kommunikationslatänz chönd de verschränkti Zuestand verschlächtere. Usserdäm sind Fähler bi Messige imene Lauf schwieriger z'korrigiere als Fähler bi de Schluss-Messige, will si sich dur die bedingete Gates uf de Rest vom Schaltkreis uusbreited.
Im Referänz-Experiment stelleds Autore e Bell-Paar-Fidelitäts-Benchmark vor, zum eruusfinde, weli Teili vonere Geräts am beschte für LOCC-basierti Verschränkig geignet sind. D Idee ischs, e chliine dynamische Schaltkreis uf jeder Gruppe vo vier verbundene Qubits im Prozässor z'laufe laa. De Vier-Qubit-Schaltkreis erstellt zerst e Bell-Paar uf zwei mittlere Qubits, und bruucht dänn das als Ressurce, zum die zwei Rand-Qubits dur LOCC z'verschränke. Konkret wärde Qubits 1 und 2 lokal in e ungeschnittne Bell-Paar präpariert (mitem Hadamard und CNOT), und dänn verbruucht e Teleportationsroutine das Bell-Paar, zum Qubits 0 und 3 z'verschränke. Qubits 1 und 2 wärde während de Uusfüehrig vom Schaltkreis gmässe, und basierend uf däne Ergebnis wärde Pauli-Korrekture (es X uf Qubit 3 und Z uf Qubit 0) aagwändet. Qubits 0 und 3 bliibe dänn am Änd vom Schaltkreis in emne Bell-Zuestand.
Zum d Qualität vo däm Änd-Bell-Paar z'quantifiziere, mässe mer sini Stabilisatore: spezifisch d Parität i de -Basis () und i de -Basis (). Für es perfekts Bell-Paar sind beidi vo däne Erwartungswärt +1. I de Praxis wird Hardware-Ruusche die Wärt reduziere. Drum wiederhöled mer de Schaltkreis zweimol für jedes Qubit-Paar: eine Schaltkreis misst Qubits 0 und 3 i de -Basis, und en andere missts i de -Basis. Us de Resultat überchömed mer e Schätzig vo und für das Paar vo Qubits. Mer bruuched de mittler quadratisch Abwiichig (MSE) vo däne Stabilisatore bezoge uf de ideal Wärt (1) als eifachi Metrik vo de Verschränkigsfidelität. Es nidrigers MSE bedütet, ass die zwei Qubits e Bell-Zuestand nööcher am Ideale erreicht händ (höcheri Fidelität), während es höchers MSE meh Fähler azzeigt. Indem mer das Experiment über s Gerät scanned, chönd mer d Mess-und-Feedforward-Fähigkeit vo verschiedene Qubit-Gruppe benchmarke und die beschte Paare vo Qubits für LOCC-Operazione identifiziere.
Das Tutorial demonstriert das Experiment uf emne IBM Quantum® Gerät, zum zeige, wie dynamischi Schaltkreis bruucht wärde chönd, zum Verschränkig zwüsche wiit ussenand liegede Qubits z'erzüge und z'evaluiere. Mer wärde alli Vier-Qubit-lineari Chette uf däm Gerät kartiere, de Teleportationsschaltkreis uf jeder laufe laa, und dänn d Verteilig vo de MSE-Wärt visualisiere. Die End-zu-End-Prozedur zeigt, wie mer Qiskit Runtime und dynamischi Schaltkreisfunktione nutze chönd, zum hardware-bewussti Entschäidige für s Schneide vo Schaltkreis oder s Verteile vo Quantealgorithme über es modulars System z'träffe.
Requirements
Bevor dir mit däm Tutorial aafanged, stellet sicher, dass dir Folgendes installiert händ:
- Qiskit SDK v2.0 oder neuer, mit visualization Ungerstützig
- Qiskit Runtime v0.40 oder neuer (
pip install qiskit-ibm-runtime)
Setup
from qiskit import QuantumCircuit
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler import generate_preset_pass_manager
import numpy as np
import matplotlib.pyplot as plt
def create_bell_stab(initial_layouts):
"""
Create a circuit for a 1D chain of qubits (number of qubits must be a multiple of 4),
where a middle Bell pair is consumed to create a Bell at the edge.
Takes as input a list of lists, where each element of the list is a
1D chain of physical qubits that is used as the initial_layout for the transpiled circuit.
Returns a list of length-2 tuples, each tuple contains a circuit to measure the ZZ stabilizer and
a circuit to measure the XX stabilizer of the edge Bell state.
"""
bell_circuits = []
for (
initial_layout
) in initial_layouts: # Iterate over chains of physical qubits
assert (
len(initial_layout) % 4 == 0
), f"The length of the chain must be a multiple of 4, len(inital_layout)={len(initial_layout)}"
num_pairs = len(initial_layout) // 4
bell_parallel = QuantumCircuit(4 * num_pairs, 4 * num_pairs)
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
bell_parallel.h(q0)
bell_parallel.h(q1)
bell_parallel.cx(q1, q2)
bell_parallel.cx(q0, q1)
bell_parallel.cx(q2, q3)
bell_parallel.h(q2)
# add barrier BEFORE measurements and add id in conditional
bell_parallel.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
bell_parallel.measure(q1, ca0)
bell_parallel.measure(q2, ca1)
# bell_parallel.barrier() #remove barrier after measurement
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(ca0, ca1) = pair_idx * 4 + 1, pair_idx * 4 + 2 # middle qubits
with bell_parallel.if_test((ca0, 1)):
bell_parallel.x(q3)
with bell_parallel.if_test((ca1, 1)):
bell_parallel.z(q0)
bell_parallel.id(q0) # add id here for correct alignment
bell_zz = bell_parallel.copy()
bell_zz.barrier()
bell_xx = bell_parallel.copy()
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
bell_xx.h(q0)
bell_xx.h(q3)
bell_xx.barrier()
for pair_idx in range(num_pairs):
(q0, q1, q2, q3) = (
pair_idx * 4,
pair_idx * 4 + 1,
pair_idx * 4 + 2,
pair_idx * 4 + 3,
)
(c0, c1) = pair_idx * 4, pair_idx * 4 + 3 # edge qubits
bell_zz.measure(q0, c0)
bell_zz.measure(q3, c1)
bell_xx.measure(q0, c0)
bell_xx.measure(q3, c1)
bell_circuits.append(bell_zz)
bell_circuits.append(bell_xx)
return bell_circuits
def get_mse(result, initial_layouts):
"""
given a result object and the initial layouts, returns a dict of layouts and their mse
"""
layout_mse = {}
for layout_idx, initial_layout in enumerate(initial_layouts):
layout_mse[tuple(initial_layout)] = {}
num_pairs = len(initial_layout) // 4
counts_zz = result[2 * layout_idx].data.c.get_counts()
total_shots = sum(counts_zz.values())
# Get ZZ expectation value
exp_zz_list = []
for pair_idx in range(num_pairs):
exp_zz = 0
for bitstr, shots in counts_zz.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
z_val0 = 1 if b0 == "0" else -1
z_val1 = 1 if b1 == "0" else -1
exp_zz += z_val0 * z_val1 * shots
exp_zz /= total_shots
exp_zz_list.append(exp_zz)
counts_xx = result[2 * layout_idx + 1].data.c.get_counts()
total_shots = sum(counts_xx.values())
# Get XX expectation value
exp_xx_list = []
for pair_idx in range(num_pairs):
exp_xx = 0
for bitstr, shots in counts_xx.items():
bitstr = bitstr[::-1] # reverse order to big endian
b1, b0 = (
bitstr[pair_idx * 4],
bitstr[pair_idx * 4 + 3],
) # parse bitstring to get edge measurements for each 4-q chain
x_val0 = 1 if b0 == "0" else -1
x_val1 = 1 if b1 == "0" else -1
exp_xx += x_val0 * x_val1 * shots
exp_xx /= total_shots
exp_xx_list.append(exp_xx)
mse_list = [
((exp_zz - 1) ** 2 + (exp_xx - 1) ** 2) / 2
for exp_zz, exp_xx in zip(exp_zz_list, exp_xx_list)
]
print(f"layout {initial_layout}")
for idx in range(num_pairs):
layout_mse[tuple(initial_layout)][
tuple(initial_layout[4 * idx : 4 * idx + 4])
] = mse_list[idx]
print(
f"qubits: {initial_layout[4*idx:4*idx+4]}, mse:, {round(mse_list[idx],4)}"
)
# print(f'exp_zz: {round(exp_zz_list[idx],4)}, exp_xx: {round(exp_xx_list[idx],4)}')
print(" ")
return layout_mse
def plot_mse_ecdfs(layouts_mse, combine_layouts=False):
"""
Plot CDF of MSE data for multiple layouts. Optionally combine all data in a single CDF
"""
if not combine_layouts:
for initial_layout, layouts in layouts_mse.items():
sorted_layouts = dict(
sorted(layouts.items(), key=lambda item: item[1])
) # sort layouts by mse
# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))
# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)
# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)
# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {initial_layout}",
)
# add qubits labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)
elif combine_layouts:
all_layouts = {}
all_initial_layout = []
for (
initial_layout,
layouts,
) in layouts_mse.items(): # puts together all layout information
all_layouts.update(layouts)
all_initial_layout += initial_layout
sorted_layouts = dict(
sorted(all_layouts.items(), key=lambda item: item[1])
) # sort layouts by mse
# get layouts and mses
layout_list = list(sorted_layouts.keys())
mse_list = np.asarray(list(sorted_layouts.values()))
# convert to numpy
x = np.array(mse_list)
y = np.arange(1, len(x) + 1) / len(x)
# Prepend (x[0], 0) to start CDF at zero
x = np.insert(x, 0, x[0])
y = np.insert(y, 0, 0)
# Create the plot
plt.plot(
x,
y,
marker="x",
linestyle="-",
label=f"qubits: {sorted(list(set(all_initial_layout)))}",
)
# add qubit labels for the edge pairs
for xi, yi, q in zip(x[1:], y[1:], layout_list):
plt.annotate(
[q[0], q[3]],
(xi, yi),
textcoords="offset points",
xytext=(5, -10),
ha="left",
fontsize=8,
)
plt.xscale("log")
plt.xlabel("Mean squared error of ⟨ZZ⟩ and ⟨XX⟩")
plt.ylabel("Cumulative distribution function")
plt.title("CDF for different initial layouts")
plt.grid(alpha=0.3)
plt.show()
Step 1: Map classical inputs to a quantum problem
De erscht Schritt ischs, e Satz vo Quanteschaltkreis z'erstelle, zum alli kandidat Bell-Paar-Links z'benchmarke, wo uf d Topologie vom Gerät aagpasst sind. Mer durchsueched programmatisch d Gerät-Koppligschart für alli linear verbundeni Chette vo vier Qubits. Jedi sötigi Chette (dur Qubit-Indizes bezeichnet) dient als Testfall für de Verschränkigs-Swapping-Schaltkreis. Indem mer alli möglichi Längi-4-Pfäd identifiziered, stellemed mer maximali Deckkig für möglichi Gruppierige vo Qubits sicher, wo s Protokoll realisiere chönted.
service = QiskitRuntimeService()
backend = service.least_busy(operational=True)
Mer erzüged die Chette, indem mer e Hilfsfunktion bruuched, wo e gierige Suechi uf däm Gerät-Graf durefüehrt. Si git "Streife" vo vier Vier-Qubit-Chette zrugg, wo in 16-Qubit-Gruppe bündlet sind (dynamischi Schaltkreis schränked momentan d Grössi vom Messregister uf 16 Qubits ii). S Bündle erlaubt ois, mehreri Vier-Qubit-Experiment parallel uf verschiedene Teili vom Chip z'laufe laa und s ganz Gerät effizient z'bruuche. Jede 16-Qubit-Streife enthält vier disjunkti Chette, das heisst, dass kei Qubit innerhalb vo dere Gruppe wider bruucht wird. Zum Bischpil chönnt eine Streife us Chette , , und bestah, alli zäme packt. Jedes Qubit, wo nöd in emne Streife enthalte gsi isch, wird i de leftover Variable zruggäh.
from itertools import chain
from collections import defaultdict
def stripes16_from_backend(backend):
"""
Creates stripes of 16 qubits, four non-overlapping four-qubit chains, that cover as much of
the coupling map as possible. Returns any unused qubits as leftovers.
"""
# get the undirected adjacency list
edges = backend.coupling_map.get_edges()
graph = defaultdict(set)
for u, v in edges:
graph[u].add(v)
graph[v].add(u)
qubits = sorted(graph) # all qubit indices that appear
# greedy search for 4-long linear chains (blocks) ────────────
used = set() # qubits already placed in a block
blocks = [] # each block is a four-qubit list
for q in qubits: # deterministic order for reproducibility
if q in used:
continue # already consumed by earlier block
# depth-first "straight" walk of length 3 without revisiting nodes
def extend(path):
if len(path) == 4:
return path
tip = path[-1]
for nbr in sorted(graph[tip]): # deterministic
if nbr not in path and nbr not in used:
maybe = extend(path + [nbr])
if maybe:
return maybe
return None
block = extend([q])
if block: # found a 4-node path
blocks.append(block)
used.update(block)
# bundle four four-qubit blocks into one 16-qubit stripe (max number of measurement compatible with if-else)
stripes = [
list(chain.from_iterable(blocks[i : i + 4]))
for i in range(0, len(blocks) // 4 * 4, 4) # full groups of four
]
leftovers = set(qubits) - set(chain.from_iterable(stripes))
return stripes, leftovers
initial_layouts, leftover = stripes16_from_backend(backend)
Als nächschts konstruiered mer de Schaltkreis für jede 16-Qubit-Streife. D Routine tuet s Folgend für jedi Chette:
- Es mittlers Bell-Paar präpariere: Es Hadamard uf Qubit 1 und es CNOT vo Qubit 1 zu Qubit 2 aawände. Das verschränkt Qubits 1 und 2 (und erstellt en Bell-Zuestand).
- D Rand-Qubits verschränke: Es CNOT vo Qubit 0 zu Qubit 1 und es CNOT vo Qubit 2 zu Qubit 3 aawände. Das verbindet die aafänglich tränte Paare, so dass Qubits 0 und 3 nach de nächschte Schritt verschränkt wärded. Es Hadamard uf Qubit 2 wird au aagwändet (das, kombiniert mit de früehere CNOTs, bildet en Teil vo nere Bell-Messig uf Qubits 1 und 2). Zu däm Zitpunkt sind Qubits 0 und 3 na nöd verschränkt, aber Qubits 1 und 2 sind mit dene in emne grössere Vier-Qubit-Zuestand verschränkt.
- Messige imene Lauf und Feedforward: Qubits 1 und 2 (die mittlere Qubits) wärde i de Berechnigsbasis gmässe, was zwei klassischi Bits ergibt. Basierend uf däne Messresultat wänded mer bedingti Operazione aa: Wenn d Qubit-1-Messig (das nänned mer Bit ) 1 isch, wänded mer es -Gate uf Qubit 3 aa; wenn d Qubit-2-Messig () 1 isch, wänded mer es -Gate uf Qubit 0 aa. Die bedingete Gates (realisiert dur s Qiskit
if_test/if_elseKonstrukt) implementiered di standard Teleportationskorrekture. Si "mached rückgängig" die zuefellige Pauli-Flips, wo dur s Projiziere vo Qubits 1 und 2 passiere, und stelleds sicher, ass Qubits 0 und 3 in emne bekannte Bell-Zuestand ände, unabhängig vo de Messresultat. Nach däm Schritt sötted Qubits 0 und 3 idealerwiis im Bell-Zuestand