Classical feedforward and control flow (dynamische Schaltungen)
Paketversionen
Der Code auf dieser Seite wurde mit den folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.
qiskit[all]~=2.4.0
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
Qiskit unterstützt vier Kontrollfluss-Konstrukte für Classical Feedforward, die jeweils als Methode auf QuantumCircuit implementiert sind. Die Konstrukte und ihre entsprechenden Methoden sind:
- If-Anweisung –
QuantumCircuit.if_test - Switch-Anweisung –
QuantumCircuit.switch - For-Schleife –
QuantumCircuit.for_loop - While-Schleife –
QuantumCircuit.while_loop
Jede dieser Methoden gibt einen Context Manager zurück und wird typischerweise in einer with-Anweisung verwendet. Der Rest dieses Leitfadens erklärt jedes dieser Konstrukte und wie du sie verwendest.
Es gibt einige Einschränkungen bei Classical-Feedforward- und Kontrollflussoperationen auf Quantenhardware, die dein Programm beeinflussen könnten. Weitere Informationen findest du unter Dynamische Schaltungen ausführen.
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
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
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 ausgefüllter Kreis zeigt eine Bedingung auf 1 an, während ein nicht ausgefüllter 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}
Switch-Anweisung
Die Switch-Anweisung wird verwendet, um Aktionen basierend auf dem Wert eines klassischen Bits oder Registers auszuwählen. Sie ähnelt einer If-Anweisung, aber du kannst mehr Fälle für die Verzweigungslogik angeben. Das folgende Beispiel wendet ein Hadamard-Gate auf ein Qubit an und misst es. Wenn das Ergebnis 0 ist, wird ein X-Gate auf das Qubit angewendet, und wenn das Ergebnis 1 ist, wird ein Z-Gate angewendet. Das resultierende Messergebnis sollte mit 100%iger Wahrscheinlichkeit 1 sein.
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
circuit.measure(q0, c0)
with circuit.switch(c0) as case:
with case(0):
circuit.x(q0)
with case(1):
circuit.z(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'1': 1024}
Da das obige Beispiel ein einzelnes klassisches Bit verwendet hat, gab es nur zwei mögliche Fälle, sodass du dasselbe Ergebnis mit einer If-Else-Anweisung hättest erzielen können. Der Switch-Case ist hauptsächlich nützlich, wenn auf den Wert eines klassischen Registers verzweigt wird, das aus mehreren Bits besteht. Das folgende Beispiel zeigt, wie ein Standardfall erstellt wird, der ausgeführt wird, wenn keiner der vorhergehenden Fälle zutrifft. Beachte, dass in einer Switch-Anweisung immer nur einer der Blöcke ausgeführt wird. Es gibt kein Durchfallen.
Das folgende Beispiel wendet Hadamard-Gates auf zwei Qubits an und misst sie. Wenn das Ergebnis entweder 00 oder 11 ist, wird ein Z-Gate auf das dritte Qubit angewendet. Wenn das Ergebnis 01 ist, wird ein Y-Gate angewendet. Wenn keiner der vorhergehenden Fälle zutrifft, wird ein X-Gate angewendet. Schließlich wird das dritte Qubit gemessen.
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.switch(clbits) as case:
with case(0b000, 0b011):
circuit.z(q2)
with case(0b001):
circuit.y(q2)
with case(case.DEFAULT):
circuit.x(q2)
circuit.measure(q2, c2)
circuit.draw("mpl")
# example output counts: {'101': 267, '110': 249, '011': 265, '000': 243}
For-Schleife
Eine For-Schleife wird verwendet, um über eine Folge von klassischen Werten zu iterieren und dabei in jeder Iteration einige Operationen durchzuführen.
Das folgende Beispiel verwendet eine For-Schleife, um 5 X-Gates auf ein Qubit anzuwenden und es dann zu messen. Da eine ungerade Anzahl von X-Gates angewendet wird, besteht der Gesamteffekt darin, das Qubit vom 0-Zustand in den 1-Zustand umzuschalten.
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
with circuit.for_loop(range(5)) as _:
circuit.x(q0)
circuit.measure(q0, c0)
circuit.draw("mpl")
# example output counts: {'1': 1024}
While-Schleife
Eine While-Schleife wird verwendet, um Anweisungen zu wiederholen, solange eine Bedingung erfüllt ist.
Das folgende Beispiel wendet Hadamard-Gates auf zwei Qubits an und misst sie. Dann wird eine While-Schleife erstellt, die dieses Verfahren wiederholt, solange das Messergebnis 11 ist. Als Ergebnis sollte das abschließende Messergebnis niemals 11 sein, wobei die übrigen Möglichkeiten mit ungefähr gleicher Häufigkeit auftreten.
qubits = QuantumRegister(2)
clbits = ClassicalRegister(2)
circuit = QuantumCircuit(qubits, clbits)
q0, q1 = qubits
c0, c1 = clbits
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
with circuit.while_loop((clbits, 0b11)):
circuit.h([q0, q1])
circuit.measure(q0, c0)
circuit.measure(q1, c1)
circuit.draw("mpl")
# example output counts: {'01': 334, '10': 368, '00': 322}
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.
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)
Store
Du kannst die store-Anweisung verwenden, um das Ergebnis eines klassischen Ausdrucks zu speichern, wenn dieser Ausdruck wiederholt verwendet wird. Operationen werden automatisch parallelisiert, wodurch dein Code zur Laufzeit erheblich effizienter wird.
Zum Beispiel ist es natürlicher und zur Laufzeit effizienter, zu schreiben, wobei , als . Ersteres berechnet die Negation in einem einzigen parallelen Schritt vor der XOR-Kette, anstatt jede Negation sequenziell innerhalb des Ausdrucks auszuwerten.
Vollständiges Beispiel:
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.circuit.classical import expr
qregs = QuantumRegister(4, "q")
creg = ClassicalRegister(3, "c")
# temp is a plain ClassicalRegister used as the store target
temp = ClassicalRegister(3, "temp")
qc = QuantumCircuit(qregs, creg, temp)
qc.h([0, 1, 2])
qc.measure([0, 1, 2], creg)
# Store bit-NOT of the full 3-bit register into temp
qc.store(temp, expr.bit_not(creg))
# Compute parity of temp using bit-indexed XOR
parity = expr.bit_xor(
expr.bit_xor(expr.index(temp, 0), expr.index(temp, 1)),
expr.index(temp, 2),
)
# Flip q3 if parity of ~creg is 1
with qc.if_test(parity):
qc.x(3)
qc.measure([0, 1, 2], creg)
qc.draw("mpl")
Nächste Schritte
- Erfahre, wie du genaues dynamisches Entkoppeln durch Verwendung von stretch implementierst.
- Verwende Circuit-Schedule-Visualisierung zur Fehlersuche und Optimierung deiner dynamischen Schaltungen.
- Führe dynamische Schaltungen aus.