Zum Hauptinhalt springen

Circuit Cutting für periodische Randbedingungen

Geschätzte Laufzeit: Zwei Minuten auf einem Eagle-Prozessor (HINWEIS: Dies ist nur eine Schätzung. Deine tatsächliche Laufzeit kann abweichen.)

Hintergrund

In diesem Notebook betrachten wir die Simulation einer periodischen Qubitkette, bei der zwischen je zwei benachbarten Qubits eine Zwei-Qubit-Operation durchgeführt wird – einschließlich des ersten und des letzten Qubits. Periodische Ketten treten häufig in physikalischen und chemischen Problemen auf, beispielsweise bei Ising-Modellen und molekularen Simulationen.

Aktuelle IBM Quantum®-Geräte sind planar. Bei manchen periodischen Ketten lässt sich die Topologie so nutzen, dass der erste und der letzte Qubit direkt benachbart sind. Bei hinreichend großen Problemen jedoch können der erste und der letzte Qubit weit voneinander entfernt sein, sodass viele SWAP-Gates für die Zwei-Qubit-Operation zwischen diesen beiden Qubits benötigt werden. Dieses Problem der periodischen Randbedingungen wurde in diesem Paper untersucht.

In diesem Notebook zeigen wir den Einsatz von Circuit Cutting für ein solches periodisches Kettenproblem im Utility-Maßstab, bei dem der erste und der letzte Qubit keine Nachbarn sind. Das Schneiden dieser weitreichenden Konnektivität vermeidet die zusätzlichen SWAP-Gates – auf Kosten der Ausführung mehrerer Circuit-Instanzen und einiger klassischer Nachverarbeitung. Zusammenfassend lässt sich sagen, dass Circuit Cutting genutzt werden kann, um die weitreichenden Zwei-Qubit-Operationen logisch zu berechnen. Dies führt effektiv zu einer erhöhten Konnektivität der Coupling Map und damit zu einer geringeren Anzahl von SWAP-Gates.

Es gibt zwei Arten von Schnitten: das Schneiden der Leitung eines Circuits (sogenanntes Wire Cutting) oder das Ersetzen eines Zwei-Qubit-Gates durch mehrere Einzel-Qubit-Operationen (sogenanntes Gate Cutting). In diesem Notebook konzentrieren wir uns auf Gate Cutting. Weitere Details zu Gate Cutting findest du in den Erklärungsmaterialien in qiskit-addon-cutting und den entsprechenden Referenzen. Für mehr Details zu Wire Cutting, schau dir das Tutorial Wire Cutting zur Schätzung von Erwartungswerten oder die Tutorials in qiskit-addon-cutting an.

Voraussetzungen

Bevor du mit diesem Tutorial beginnst, stelle sicher, dass du Folgendes installiert hast:

  • Qiskit SDK v1.2 oder neuer (pip install qiskit)
  • Qiskit Runtime v0.3 oder neuer (pip install qiskit-ibm-runtime)
  • Circuit Cutting Qiskit Addon v.9.0 oder neuer (pip install qiskit-addon-cutting)

Setup

# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal

from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)

from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, Batch

Schritt 1: Klassische Eingaben auf ein Quantenproblem abbilden

Hier erstellen wir einen TwoLocal-Circuit und definieren einige Observablen.

  • Eingabe: Parameter zur Erstellung eines Circuits
  • Ausgabe: Abstrakter Circuit und Observablen

Wir betrachten eine hardware-effiziente entangler map für den TwoLocal-Circuit mit periodischer Konnektivität zwischen dem letzten und dem ersten Qubit der entangler map. Diese weitreichende Wechselwirkung kann bei der Transpilierung zu zusätzlichen SWAP-Gates führen und damit die Tiefe des Circuits erhöhen.

Backend und initiales Layout auswählen

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

Für dieses Notebook betrachten wir eine periodische 1D-Kette mit 109 Qubits – die längste 1D-Kette in der Topologie eines 127-Qubit-IBM-Quantum-Geräts. Es ist nicht möglich, eine periodische 109-Qubit-Kette auf einem 127-Qubit-Gerät so anzuordnen, dass der erste und der letzte Qubit Nachbarn sind, ohne zusätzliche SWAP-Gates zu verwenden.

init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]

# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubits
109

Entangler Map für den TwoLocal-Circuit erstellen

coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivity

Der TwoLocal-Circuit ermöglicht die mehrfache Wiederholung der rotation_blocks und der entangler map. In diesem Fall bestimmt die Anzahl der Wiederholungen, wie viele periodische Gates geschnitten werden müssen. Da der Sampling-Overhead exponentiell mit der Anzahl der Schnitte wächst (weitere Details findest du im Tutorial Wire Cutting zur Schätzung von Erwartungswerten), fixieren wir die Anzahl der Wiederholungen in diesem Notebook auf 2.

num_reps = 2
entangler_map = []

for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)

for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)
ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)

Ausgabe der vorherigen Code-Zelle

Um die Qualität des Ergebnisses mit Circuit Cutting zu überprüfen, müssen wir das ideale Ergebnis kennen. Der hier gewählte Circuit liegt jenseits der klassischen Brute-Force-Simulation. Daher wählen wir die Parameter des Circuits sorgfältig so, dass er Clifford-artig wird.

Wir weisen den ersten beiden Schichten der Rx-Gates den Parameterwert 00 und der letzten Schicht den Wert π\pi zu. Dies stellt sicher, dass das ideale Ergebnis dieses Circuits 1n|1\rangle^{\otimes n} ist, wobei nn die Anzahl der Qubits ist. Die Erwartungswerte von Zi\langle Z_i \rangle und ZiZi+1\langle Z_i Z_{i+1} \rangle, wobei ii der Index des Qubits ist, betragen daher 1-1 bzw. +1+1.

params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)

ansatz.assign_parameters(params, inplace=True)

Observablen auswählen

Um den Vorteil von Gate Cutting zu quantifizieren, messen wir die Erwartungswerte der Observablen 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle und 1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. Wie bereits erwähnt, betragen die idealen Erwartungswerte 1-1 bzw. +1+1.

observables = []

for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)

for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)

observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffs

Schritt 2: Problem für die Ausführung auf Quantenhardware optimieren

  • Eingabe: Abstrakter Circuit und Observablen
  • Ausgabe: Ziel-Circuit und Observablen, erzeugt durch das Schneiden weitreichender Gates

Den Circuit transpilieren

Beachte, dass der Circuit an dieser Stelle oder nach dem Schneiden transpiliert werden kann. Wenn wir nach dem Schneiden transpilieren, müssen wir jeden der durch den Sampling-Overhead generierten Subexperimente einzeln transpilieren. Daher ist es sinnvoller, bereits jetzt zu transpilieren, um den Transpilierungs-Overhead zu reduzieren.

Wenn die Transpilierung jedoch an dieser Stelle mit der nativen Hardware-Konnektivität durchgeführt wird, fügt der Transpiler mehrere SWAP-Gates hinzu, um die periodische Zwei-Qubit-Operation zu platzieren – was den Vorteil des Circuit Cuttings verdeckt. Um dieses Problem zu umgehen, können wir ausnutzen, dass wir die genauen Gates kennen, die geschnitten werden sollen. Konkret können wir eine virtuelle Coupling Map erstellen, indem wir virtuelle Verbindungen zwischen weit entfernten Qubits hinzufügen, um diese periodischen Zwei-Qubit-Gates zu ermöglichen. So kann der Circuit an dieser Stelle ohne zusätzliche SWAP-Gates transpiliert werden.

coupling_map = backend.configuration().coupling_map

# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])
pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)

virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)

Ausgabe der vorherigen Code-Zelle

Die weitreichenden periodischen Verbindungen schneiden

Jetzt schneiden wir die Gates im transpilierten Circuit. Beachte, dass die zu schneidenden Zwei-Qubit-Gates diejenigen sind, die den letzten und den ersten Qubit des Layouts verbinden.

# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]

Wir wenden das Layout des transpilierten Circuits auf die Observable an.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

Schließlich werden die Subexperimente durch Sampling über verschiedene Mess- und Präparationsbases generiert.

qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)

Beachte, dass das Schneiden der weitreichenden Wechselwirkungen zur Ausführung mehrerer Circuit-Samples führt, die sich in den Mess- und Präparationsbases unterscheiden. Mehr dazu findest du in Constructing a virtual two-qubit gate by sampling single-qubit operations und Cutting circuits with multiple two-qubit unitaries.

Die Anzahl der zu schneidenden periodischen Gates ist gleich der Anzahl der Wiederholungen der TwoLocal-Schicht, die oben als num_reps definiert wurde. Der Sampling-Overhead von Gate Cutting beträgt 6. Die Gesamtanzahl der Subexperimente beträgt daher 6num_reps6^{num\_reps}.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

Die Subexperimente transpilieren

Zu diesem Zeitpunkt enthalten die Subexperimente Circuits mit einigen 1-Qubit-Gates, die nicht zum Basis-Gate-Set gehören. Dies liegt daran, dass die geschnittenen Qubits in verschiedenen Basen gemessen werden und die dafür verwendeten Rotationsgates nicht unbedingt zum Basis-Gate-Set gehören. Die Messung in der X-Basis erfordert beispielsweise das Anwenden eines Hadamard-Gates vor der üblichen Messung in der Z-Basis. Hadamard ist jedoch kein Teil des Basis-Gate-Sets.

Anstatt den gesamten Transpilierungsprozess auf jeden der Circuits in den Subexperimenten anzuwenden, können wir spezifische Transpilierungspässe nutzen. Eine detaillierte Beschreibung aller verfügbaren Transpilierungspässe findest du in dieser Dokumentation.

Wir wenden zunächst BasisTranslator und dann Optimize1qGatesDecomposition an, um sicherzustellen, dass alle Gates in diesen Circuits zum Basis-Gate-Set gehören. Die Verwendung dieser beiden Pässe ist schneller als der vollständige Transpilierungsprozess, da andere Schritte wie Routing und initiale Layout-Auswahl nicht erneut durchgeführt werden.

pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)

subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)

Schritt 3: Mit Qiskit Primitives ausführen

  • Eingabe: Ziel-Circuits
  • Ausgabe: Quasi-Wahrscheinlichkeitsverteilungen

Wir verwenden ein SamplerV2-Primitiv zur Ausführung der geschnittenen Circuits. Wir deaktivieren dynamical decoupling und twirling, sodass jede Verbesserung der Ergebnisse ausschließlich auf den wirksamen Einsatz von Gate Cutting für diesen Circuit-Typ zurückzuführen ist.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

Jetzt übermitteln wir die Jobs im Batch-Modus.

with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)

print(f"Job ID {cut_job.job_id()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()

Schritt 4: Nachverarbeitung und Ausgabe im gewünschten klassischen Format

  • Eingabe: Quasi-Wahrscheinlichkeitsverteilungen
  • Ausgabe: Rekonstruierte Erwartungswerte
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

Wir berechnen nun den Durchschnitt der Z-Typ-Observablen vom Gewicht 1 und Gewicht 2.

cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495

Gegenprüfung: Erwartungswert ohne Schnitt ermitteln

Es ist nützlich, den Vorteil der Circuit-Cutting-Technik gegenüber der ungeschnittenen Variante zu überprüfen. Hier berechnen wir die Erwartungswerte, ohne den Circuit zu schneiden. Beachte, dass ein solcher ungeschnittener Circuit unter einer großen Anzahl von SWAP-Gates leidet, die für die Zwei-Qubit-Operation zwischen dem ersten und dem letzten Qubit erforderlich sind. Wir nutzen die Funktion sampled_expectation_value, um die Erwartungswerte des ungeschnittenen Circuits nach Erhalt der Wahrscheinlichkeitsverteilung über SamplerV2 zu erhalten. Dies ermöglicht eine einheitliche Verwendung des Primitivs in allen Instanzen. Beachte jedoch, dass wir alternativ auch EstimatorV2 hätten verwenden können, um die Erwartungswerte direkt zu berechnen.

if ansatz.num_clbits == 0:
ansatz.measure_all()

pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)

transpiled_circuit = pm_uncut.run(ansatz)
sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])
uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()

Jetzt berechnen wir die durchschnittlichen Erwartungswerte aller Z-Typ-Observablen vom Gewicht 1 und Gewicht 2 ohne Schnitt.

uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]

uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])

print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656

Visualisierung

Lass uns nun die Verbesserung visualisieren, die beim Einsatz von Gate Cutting für den periodischen Ketten-Circuit bei Gewicht-1- und Gewicht-2-Observablen erzielt wird.

mpl.rcParams.update(mpl.rcParamsDefault)

fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))

ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]

br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]

plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)

plt.axhline(y=0, color="k", linestyle="-")

plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)

plt.legend(fontsize=14)
plt.show()

Ausgabe der vorherigen Code-Zelle

Zusammenfassung

Zusammenfassend haben wir die durchschnittlichen Erwartungswerte von Z-Typ-Observablen vom Gewicht 1 und Gewicht 2 für eine periodische 1D-Kette mit 109 Qubits berechnet. Dazu haben wir

  • eine virtuelle Coupling Map erstellt, indem wir eine weitreichende Verbindung zwischen dem ersten und dem letzten Qubit der 1D-Kette hinzugefügt und den Circuit transpiliert haben.
    • die Transpilierung an dieser Stelle ermöglichte es uns, den Overhead der separaten Transpilierung jedes Subexperiments nach dem Schneiden zu vermeiden,
    • die virtuelle Coupling Map ermöglichte es uns, zusätzliche SWAP-Gates für die Zwei-Qubit-Operation zwischen dem ersten und dem letzten Qubit zu vermeiden.
  • die weitreichende Verbindung aus dem transpilierten Circuit durch Gate Cutting entfernt.
  • die geschnittenen Circuits durch Anwenden geeigneter Transpilierungspässe in das Basis-Gate-Set überführt.
  • die geschnittenen Circuits auf einem IBM Quantum-Gerät mit einem SamplerV2-Primitiv ausgeführt.
  • den Erwartungswert durch Rekonstruktion der Ergebnisse der geschnittenen Circuits erhalten.

Schlussfolgerung

Aus den Ergebnissen geht hervor, dass der Durchschnitt der Gewicht-1-Observablen Z\langle Z \rangle und der Gewicht-2-Observablen ZZ\langle ZZ \rangle durch das Schneiden der periodischen Gates deutlich verbessert wird. Beachte, dass diese Studie keine Techniken zur Fehlerunterdrückung oder -mitigation umfasst. Die beobachtete Verbesserung ist ausschließlich auf den gezielten Einsatz von Gate Cutting für dieses Problem zurückzuführen. Die Ergebnisse könnten durch den Einsatz von Mitigation- und Suppressionstechniken weiter verbessert werden.

Diese Studie zeigt ein Beispiel für den effektiven Einsatz von Gate Cutting zur Verbesserung der Rechenleistung.

Tutorial-Umfrage

Bitte nimm an dieser kurzen Umfrage teil, um Feedback zu diesem Tutorial zu geben. Deine Rückmeldungen helfen uns, unsere Inhalte und die Benutzererfahrung zu verbessern.

Link zur Umfrage