Classical Feedforward und Kontrollfluss
Paketversionen
Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.
qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Die neue Version dynamischer Schaltungen ist jetzt für alle Benutzer auf allen Backends verfügbar. Du kannst dynamische Schaltungen nun im Utility-Maßstab ausführen. Weitere Einzelheiten findest du in der Ankündigung.
Dynamische Schaltungen sind leistungsstarke Werkzeuge, mit denen du Qubits während der Ausführung einer Quantenschaltung messen und anschließend klassische Logikoperationen innerhalb der Schaltung basierend auf dem Ergebnis dieser Mid-Circuit-Messungen durchführen kannst. Dieser Prozess wird auch als Classical Feedforward bezeichnet. Obwohl es noch frühe Tage sind, um zu verstehen, wie man dynamische Schaltungen am besten nutzt, hat die Quantenforschungsgemeinschaft bereits eine Reihe von Anwendungsfällen identifiziert, wie zum Beispiel:
- Effiziente Vorbereitung von Quantenzuständen, wie GHZ-Zustand, W-Zustand, (weitere Informationen zum W-Zustand findest du auch unter "State preparation by shallow circuits using feed forward") und eine breite Klasse von Matrix-Produktzuständen
- Effiziente Langstrecken-Verschränkung zwischen Qubits auf demselben Chip durch Verwendung flacher Schaltungen
- Effizientes Sampling von IQP-ähnlichen Schaltungen
Diese Verbesserungen durch dynamische Schaltungen bringen jedoch Kompromisse mit sich. Mid-Circuit-Messungen und klassische Operationen haben typischerweise längere Ausführungszeiten als Zwei-Qubit-Gates, und diese Zeitverlängerung könnte die Vorteile reduzierter Schaltungstiefe zunichte machen. Daher ist die Verkürzung der Mid-Circuit-Messungszeit ein Schwerpunktbereich für Verbesserungen, während IBM Quantum® die neue Version dynamischer Schaltungen veröffentlicht.
Die OpenQASM 3-Spezifikation definiert eine Reihe von Kontrollflussstrukturen, aber Qiskit Runtime unterstützt derzeit nur die bedingte if-Anweisung. In Qiskit SDK entspricht dies der if_test-Methode auf QuantumCircuit. Diese Methode gibt einen Context Manager zurück und wird typischerweise in einer with-Anweisung verwendet. Dieser Leitfaden beschreibt, wie du diese bedingte Anweisung verwendest.
Die Code-Beispiele in diesem Leitfaden verwenden die Standard-Messanweisung für Mid-Circuit-Messungen. Es wird jedoch empfohlen, stattdessen die MidCircuitMeasure-Anweisung zu verwenden, falls das Backend dies unterstützt. Siehe die Mid-Circuit-Messungen-Dokumentation für Details.
if-Anweisung
Die if-Anweisung wird verwendet, um Operationen basierend auf dem Wert eines klassischen Bits oder Registers bedingt auszuführen.
Im folgenden Beispiel wenden wir ein Hadamard-Gate auf ein Qubit an und messen es. Wenn das Ergebnis 1 ist, wenden wir ein X-Gate auf das Qubit an, was den Effekt hat, es zurück in den 0-Zustand zu kippen. Wir messen dann das Qubit erneut. Das resultierende Messergebnis sollte mit 100%iger Wahrscheinlichkeit 0 sein.
# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-ibm-runtime
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
# Use MidCircuitMeasure() if it's supported by the backend.
# circuit.append(MidCircuitMeasure(), [q0], [c0])
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)):
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'0': 1024}
Der with-Anweisung kann ein Zuweisungsziel gegeben werden, das selbst ein Context Manager ist, der gespeichert und anschließend zur Erstellung eines else-Blocks verwendet werden kann, der ausgeführt wird, wenn die Inhalte des if-Blocks nicht ausgeführt werden.
Im folgenden Beispiel initialisieren wir Register mit zwei Qubits und zwei klassischen Bits. Wir wenden ein Hadamard-Gate auf das erste Qubit an und messen es. Wenn das Ergebnis 1 ist, wenden wir ein Hadamard-Gate auf das zweite Qubit an; andernfalls wenden wir ein X-Gate auf das zweite Qubit an. Schließlich messen wir auch das zweite Qubit.
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1) = qubits
(c0, c1) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.if_test((c0, 1)) as else_:
circuit.h(q1)
with else_:
circuit.x(q1)
circuit.measure(q1, c1)
circuit.draw("mpl")
# example output counts: {'01': 260, '11': 272, '10': 492}
Neben der Bedingung auf einem einzelnen klassischen Bit ist es auch möglich, auf den Wert eines klassischen Registers zu bedingen, das aus mehreren Bits besteht.
Im folgenden Beispiel wenden wir Hadamard-Gates auf zwei Qubits an und messen sie. Wenn das Ergebnis 01 ist, das heißt, das erste Qubit ist 1 und das zweite Qubit ist 0, wenden wir ein X-Gate auf ein drittes Qubit an. Schließlich messen wir das dritte Qubit. Beachte, dass wir aus Gründen der Klarheit den Zustand des dritten klassischen Bits, der 0 ist, in der if-Bedingung angegeben haben. In der Schaltungszeichnung wird die Bedingung durch die Kreise auf den klassischen Bits angezeigt, auf die bedingt wird. Ein schwarzer Kreis zeigt eine Bedingung auf 1 an, während ein weißer Kreis eine Bedingung auf 0 anzeigt.
qubits = QuantumRegister(3)
clbits = ClassicalRegister(3)
circuit = QuantumCircuit(qubits, clbits)
(q0, q1, q2) = qubits
(c0, c1, c2) = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.if_test((clbits, 0b001)):
circuit.x(q2)
circuit.measure(q2, c2)
circuit.draw("mpl")
# example output counts: {'101': 269, '011': 260, '000': 252, '010': 243}
Klassische Ausdrücke
Das Qiskit-Modul für klassische Ausdrücke qiskit.circuit.classical enthält eine experimentelle Darstellung von Laufzeitoperationen auf klassischen Werten während der Schaltungsausführung. Aufgrund von Hardwareeinschränkungen werden derzeit nur QuantumCircuit.if_test()-Bedingungen unterstützt.
Das folgende Beispiel zeigt, dass du die Berechnung der Parität verwenden kannst, um einen n-Qubit-GHZ-Zustand mit dynamischen Schaltungen zu erstellen. Erzeuge zunächst Bell-Paare auf benachbarten Qubits. Klebe dann diese Paare zusammen, indem du eine Schicht von CNOT-Gates zwischen den Paaren verwendest. Miss dann das Ziel-Qubit aller vorherigen CNOT-Gates und setze jedes gemessene Qubit auf den Zustand zurück. Wende auf jede ungemessene Stelle an, für die die Parität aller vorhergehenden Bits ungerade ist. Schlie ßlich werden CNOT-Gates auf die gemessenen Qubits angewendet, um die bei der Messung verlorene Verschränkung wiederherzustellen.
In der Paritätsberechnung beinhaltet das erste Element des konstruierten Ausdrucks das Anheben des Python-Objekts mr[0] zu einem Value-Knoten (lift wird verwendet, um beliebige Objekte in klassische Ausdrücke umzuwandeln). Dies ist für mr[1] und das mögliche folgende klassische Register nicht notwendig, da sie Eingaben für expr.bit_xor sind und das notwendige Anheben in diesen Fällen automatisch erfolgt. Solche Ausdrücke können in Schleifen und anderen Konstrukten aufgebaut werden.
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
num_qubits = 8
if num_qubits % 2 or num_qubits < 4:
raise ValueError("num_qubits must be an even integer ≥ 4")
meas_qubits = list(range(2, num_qubits, 2)) # qubits to measure and reset
qr = QuantumRegister(num_qubits, "qr")
mr = ClassicalRegister(len(meas_qubits), "m")
qc = QuantumCircuit(qr, mr)
# Create local Bell pairs
qc.reset(qr)
qc.h(qr[::2])
for ctrl in range(0, num_qubits, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Glue neighboring pairs
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
# Measure boundary qubits between pairs,reset to 0
for k, q in enumerate(meas_qubits):
qc.measure(qr[q], mr[k])
qc.reset(qr[q])
# Parity-conditioned X corrections
# Each non-measured qubit gets flipped iff the parity (XOR) of all
# preceding measurement bits is 1
for tgt in range(num_qubits):
if tgt in meas_qubits: # skip measured qubits
continue
# all measurement registers whose physical qubit index < tgt
left_bits = [k for k, q in enumerate(meas_qubits) if q < tgt]
if not left_bits: # skip if list empty
continue
# build XOR-parity expression
parity = expr.lift(
mr[left_bits[0]]
) # lift the first bit to Value so it will be treated like a boolean.
for k in left_bits[1:]:
parity = expr.bit_xor(
mr[k], parity
) # calculate parity with all other bits
with qc.if_test(parity): # Add X if parity is 1
qc.x(qr[tgt])
# Re-entangle measured qubits
for ctrl in range(1, num_qubits - 1, 2):
qc.cx(qr[ctrl], qr[ctrl + 1])
qc.draw(output="mpl", style="iqp", idle_wires=False, fold=-1)
Backends finden, die dynamische Schaltungen unterstützen
Um alle Backends zu finden, auf die dein Konto zugreifen kann und die dynamische Schaltungen unterstützen, führe Code wie den folgenden aus. Dieses Beispiel geht davon aus, dass du deine Anmeldedaten gespeichert hast. Du könntest auch explizit Anmeldedaten angeben beim Initialisieren deines Qiskit Runtime-Dienstkontos. Dies würde es dir ermöglichen, beispielsweise Backends anzuzeigen, die für eine bestimmte Instanz oder einen Plantyp verfügbar sind.
- Die Backends, die für das Konto verfügbar sind, hängen von der in den Anmeldedaten angegebenen Instanz ab.
- Die neue Version dynamischer Schaltungen ist jetzt für alle Benutzer auf allen Backends verfügbar. Weitere Einzelheiten findest du in der Ankündigung.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]
Qiskit Runtime-Einschränkungen
Beachte die folgenden Einschränkungen beim Ausführen dynamischer Schaltungen in Qiskit Runtime.
-
Aufgrund des begrenzten physischen Speichers in der Steuerungselektronik gibt es auch eine Begrenzung für die Anzahl der
if-Anweisungen und die Größe ihrer Operanden. Diese Grenze ist eine Funktion der Anzahl der Broadcasts und der Anzahl der übertragenen Bits in einem Job (nicht in einer Schaltung).Bei der Verarbeitung einer
if-Bedingung müssen Messdaten zur Steuerungslogik übertragen werden, um diese Auswertung durchzuführen. Ein Broadcast ist eine Übertragung eindeutiger klassischer Daten, und übertragene Bits ist die Anzahl der übertragenen klassischen Bits. Betrachte das Folgende:c0 = ClassicalRegister(3)
c1 = ClassicalRegister(5)
...
with circuit.if_test((c0, 1)) ...
with circuit.if_test((c0, 3)) ...
with circuit.if_test((c1[2], 1)) ...Im vorherigen Code-Beispiel werden die ersten beiden
if_test-Objekte aufc0als ein Broadcast betrachtet, da sich der Inhalt vonc0nicht geändert hat und daher nicht erneut übertragen werden muss. Derif_testaufc1ist ein zweiter Broadcast. Der erste überträgt alle drei Bits inc0und der zweite überträgt nur ein Bit, was insgesamt vier übertragene Bits ergibt.Derzeit kannst du, wenn du jedes Mal 60 Bits überträgst, etwa 300 Broadcasts im Job haben. Wenn du jedoch nur ein Bit pro Übertragung sendest, kann der Job 2400 Broadcasts haben.
-
Der in einer
if_test-Anweisung verwendete Operand darf maximal 32 Bits groß sein. Wenn du also ein gesamtesClassicalRegistervergleichst, muss die Größe diesesClassicalRegister32 oder weniger Bits betragen. Wenn du jedoch nur ein einzelnes Bit aus einemClassicalRegistervergleichst, kann diesesClassicalRegisterbeliebiger Größe sein (da der Operand nur ein Bit ist).Zum Beispiel funktioniert der Code-Block "Nicht gültig" nicht, weil
crmehr als 32 Bits hat. Du kannst jedoch ein klassisches Register verwenden, das breiter als 32 Bits ist, wenn du nur ein Bit testest, wie im Code-Block "Gültig" gezeigt.- Nicht gültig
- Gültig
cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr, 15)):
...cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr[5], 1)):
... -
Verschachtelte Bedingungen sind nicht erlaubt. Zum Beispiel funktioniert der folgende Code-Block nicht, weil er ein
if_testinnerhalb eines anderenif_testhat:- Nicht gültig
- Gültig
c1 = ClassicalRegister(1, "c1")
c2 = ClassicalRegister(2, "c2")
...
with circ.if_test((c1, 1)):
with circ.if_test(c2, 1)):
...cr = ClassicalRegister(2)
...
with circuit.if_test((cr, 0b11)):
... -
resetoder Messungen innerhalb von Bedingungen werden nicht unterstützt. -
Arithmetische Operationen werden nicht unterstützt.
-
Siehe die OpenQASM 3-Funktionstabelle, um zu ermitteln, welche OpenQASM 3-Funktionen auf Qiskit und Qiskit Runtime unterstützt werden.
-
Wenn OpenQASM 3 (anstelle von
QuantumCircuit) als Eingabeformat zum Übergeben von Schaltungen an Qiskit Runtime-Primitive verwendet wird, werden nur Anweisungen unterstützt, die in Qiskit geladen werden können. Klassische Operationen werden beispielsweise nicht unterstützt, weil sie nicht in Qiskit geladen werden können. Siehe Import eines OpenQASM 3-Programms in Qiskit für weitere Informationen. -
Die Anweisungen
for,whileundswitchwerden nicht unterstützt.
Dynamische Schaltungen mit Estimator verwenden
Da Estimator keine dynamischen Schaltungen unterstützt, kannst du stattdessen Sampler verwenden und deine eigenen Messschaltungen erstellen. Alternativ kannst du die Executor-Primitive verwenden, die dynamische Schaltungen unterstützt.
Um das Verhalten von Estimator nachzubilden, folge diesem Prozess:
- Gruppiere die Terme aller Observablen in eine Partition. Dies kann beispielsweise mit der
PauliList-API erfolgen.hinweisDu kannst das
BitArray-Primitive-Attribut verwenden, um die Erwartungswerte der bereitgestellten Observablen zu berechnen. - Führe eine Basiswechsel-Schaltung pro Partition aus (welcher Basiswechsel auch immer für jede Partition durchgeführt werden muss). Siehe das Measurement Bases Addon-Utility
measurement_bases-Modul für weitere Informationen. Erste Schritte mit Utilities. - Addiere die Ergebnisse für jede Partition zusammen.
Nächste Schritte
- Erfahre, wie du genaues dynamisches Entkoppeln durch Verwendung von stretch implementierst.
- Erfahre mehr über die kürzeren Mid-Circuit-Messungen, die die Schaltungszeit reduzieren.
- Verwende Circuit-Schedule-Visualisierung zur Fehlersuche und Optimierung deiner dynamischen Schaltungen.