Klassische Vorwärtskopplung und Kontrollfluss (dynamische Schaltungen)
Der Code auf dieser Seite wurde unter Berücksichtigung der folgenden Anforderungen entwickelt. Wir empfehlen die Verwendung dieser oder neuerer Versionen.
qiskit[all]~=2.5.2
Dynamische Schaltkreise sind leistungsstarke Werkzeuge, mit denen man Qubits mitten in der Ausführung eines Quantenschaltkreises messen und dann klassische logische Operationen innerhalb des Schaltkreises durchführen kann, die auf dem Ergebnis dieser Messungen in der Mitte des Schaltkreises basieren. Dieses Verfahren wird auch als klassische Vorsteuerung bezeichnet. Auch wenn wir noch am Anfang stehen, um zu verstehen, wie man die Vorteile dynamischer Schaltkreise am besten nutzen kann, hat die Quantenforschungsgemeinschaft bereits eine Reihe von Anwendungsfällen identifiziert, wie z. B. die folgenden:
- Effiziente Quantenzustandsvorbereitung, wie GHZ-Zustand, W-Zustand (weitere Informationen zum W-Zustand finden Sie auch unter „Zustandsvorbereitung durch flache Schaltungen unter Verwendung von Feedforward“ ) und eine breite Klasse von Matrixproduktzuständen
- Effiziente Verschränkung über große Entfernungen zwischen Qubits auf demselben Chip durch Verwendung flacher Schaltungen
- Effiziente Abtastung von IQP-ähnlichen Schaltungen
QuantumCircuitQiskit unterstützt vier Kontrollflusskonstrukte für klassische Feedforward-Netzwerke, die jeweils als Methode auf implementiert sind. Die Konstrukte und die ihnen entsprechenden Methoden lauten wie folgt:
- if-Anweisung –
QuantumCircuit.if_test - Switch-Anweisung –
QuantumCircuit.switch - for-Schleife –
QuantumCircuit.for_loop - While-Schleife –
QuantumCircuit.while_loop
Jede dieser Methoden gibt einen Kontextmanager zurück und wird in der Regel in einer with -Anweisung verwendet. Im weiteren Verlauf dieses Leitfadens werden diese Konstrukte einzeln erläutert und ihre Verwendung erklärt.
Es gibt einige Einschränkungen bei klassischen Feedforward- und Kontrollflussoperationen auf Quantenhardware, die sich möglicherweise auf Ihr Programm auswirken könnten. Weitere Informationen finden Sie unter „Dynamische Schaltkreise ausführen “.
if Anweisung
Die Anweisung if wird verwendet, um bedingte Operationen basierend auf dem Wert eines klassischen Bits oder Registers durchzuführen.
Im folgenden Beispiel wenden wir ein Hadamard-Gatter auf ein Qubit an und messen es. Wenn das Ergebnis 1 ist, wenden wir ein X-Gatter auf das Qubit an, das es wieder in den Zustand 0 zurückversetzt. Dann messen wir das Qubit erneut. Das Ergebnis der Messung sollte mit 100 %iger Wahrscheinlichkeit 0 sein.
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}Output:
Die Anweisung with kann mit einem Zuweisungsziel versehen werden, das selbst ein Kontextmanager ist, der gespeichert und anschließend zur Erstellung eines else-Blocks verwendet werden kann, der immer dann ausgeführt wird, wenn der Inhalt des if -Blocks nicht ausgeführt wird.
Im folgenden Beispiel werden Register mit zwei Qubits und zwei klassischen Bits initialisiert. Wir wenden ein Hadamard-Tor auf das erste Qubit an und messen es. Wenn das Ergebnis 1 ist, wenden wir ein Hadamard-Gatter auf das zweite Qubit an; andernfalls wenden wir ein X-Gatter auf das zweite Qubit an. Schließlich messen wir auch noch 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}Output:
Neben der Konditionierung auf ein einzelnes klassisches Bit ist auch die Konditionierung auf den Wert eines aus mehreren Bits bestehenden klassischen Registers möglich.
Im folgenden Beispiel wenden wir Hadamard-Gatter auf zwei Qubits an und messen sie. Wenn das Ergebnis ist 01, d. h. das erste Qubit ist 1 und das zweite Qubit ist 0, dann wenden wir ein X-Gatter auf ein drittes Qubit an. Schließlich messen wir das dritte Qubit. Beachten Sie, dass wir aus Gründen der Klarheit beschlossen haben, den Zustand des dritten klassischen Bits, der 0 ist, in der if Bedingung anzugeben. In der Schaltplanzeichnung wird die Bedingung durch die Kreise auf den klassischen Bits angezeigt, die konditioniert werden. Ein ausgefüllter Kreis steht für die Konditionierung auf 1, während ein umrandeter Kreis die Konditionierung 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}Output:
Switch-Anweisung
Die switch-Anweisung dient dazu, Aktionen anhand des Werts eines klassischen Bits oder Registers auszuwählen. Es ähnelt einer if-Anweisung, allerdings können Sie mehr Fälle für die Verzweigungslogik festlegen. Im folgenden Beispiel wird ein Hadamard-Gatter auf ein Qubit angewendet und dessen Zustand gemessen. Ist das Ergebnis 0, wende ein X-Gatter auf das Qubit an; ist das Ergebnis 1, wende ein Z-Gatter an. Das resultierende Messergebnis sollte mit einer Wahrscheinlichkeit von 100 % den Wert 1 ergeben.
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}Output:
Da im obigen Beispiel ein einzelnes klassisches Bit verwendet wurde, gab es nur zwei mögliche Fälle, sodass man das gleiche Ergebnis auch mit einer if-else-Anweisung hätte erzielen können. Der „switch“-Fall ist vor allem dann nützlich, wenn eine Verzweigung anhand des Werts eines klassischen Registers erfolgt, das aus mehreren Bits besteht. Das folgende Beispiel zeigt, wie man einen Standardfall erstellt, der ausgeführt wird, wenn keiner der vorhergehenden Fälle zutrifft. Beachten Sie, dass in einer switch-Anweisung immer nur einer der Blöcke ausgeführt wird. Es gibt keinen Fallthrough.
Im folgenden Beispiel werden Hadamard-Gatter auf zwei Qubits angewendet und diese anschließend gemessen. Wenn das Ergebnis entweder 00 oder 11 ist, wende ein Z-Gatter auf das dritte Qubit an. Wenn das Ergebnis 01 ist, wende ein Y-Gatter an. Wenn keiner der vorangegangenen Fälle zutrifft, wende ein X-Gatter an. Messen Sie schließlich das dritte Qubit.
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}Output:
For-Schleife
Eine for-Schleife wird verwendet, um eine Folge klassischer Werte durchzugehen und bei jeder Iteration bestimmte Operationen auszuführen.
Im folgenden Beispiel werden mithilfe einer for-Schleife 5 X-Gatter auf ein Qubit angewendet und anschließend dessen Zustand gemessen. Da dabei eine ungerade Anzahl von X-Gattern ausgeführt wird, führt dies insgesamt dazu, dass das Qubit vom 0-Zustand in den 1-Zustand wechselt.
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}Output:
While-Schleife
Eine „while“-Schleife wird verwendet, um Anweisungen so lange zu wiederholen, wie eine bestimmte Bedingung erfüllt ist.
Im folgenden Beispiel werden Hadamard-Gatter auf zwei Qubits angewendet und diese anschließend gemessen. Anschließend wird eine while-Schleife erstellt, die diesen Vorgang so lange wiederholt, wie das Messergebnis 11 beträgt. Daher sollte das Endergebnis niemals 11 betragen, wobei die übrigen Möglichkeiten mit annähernd 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}Output:
Klassische Ausdrücke
Das Qiskit-Modul qiskit.circuit.classical für klassische Ausdrücke enthält eine explorative Darstellung der Laufzeitoperationen mit klassischen Werten während der Schaltungsausführung.
Das folgende Beispiel zeigt, dass man die Berechnung der Parität nutzen kann, um einen n-Qubit-GHZ-Zustand mit Hilfe dynamischer Schaltungen zu erzeugen. Erzeugen Sie zunächst Bell-Paare auf benachbarten Qubits. Dann kleben Sie diese Paare zusammen, indem Sie eine Schicht von CNOT-Gattern zwischen die Paare legen. Dann misst man das Ziel-Qubit aller vorherigen CNOT-Tore und setzt jedes gemessene Qubit auf den Zustand zurück. Sie wenden auf jede nicht gemessene Stelle an, bei der die Parität aller vorhergehenden Bits ungerade ist. Schließlich werden CNOT-Gatter auf die gemessenen Qubits angewendet, um die bei der Messung verlorene Verschränkung wiederherzustellen.
Bei der Paritätsberechnung beinhaltet das erste Element des konstruierten Ausdrucks die Erhebung des Objekts Pythonmr[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 nachfolgende klassische Register nicht erforderlich, da sie Eingaben für sind expr.bit_xor und in diesen Fällen alle erforderlichen Hebevorgänge automatisch durchgeführt werden. 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)Output:
Store
Sie können die store Anweisung verwenden, um das Ergebnis eines klassischen Ausdrucks zu speichern, falls dieser Ausdruck wiederholt verwendet wird. Operationen werden automatisch parallelisiert, wodurch Ihr Code zur Laufzeit deutlich effizienter wird.
Beispielsweise ist es natürlicher und zur Laufzeit effizienter, „ “ zu schreiben, wobei „ “ gilt, als „ “. Im ersten Fall wird die Negation in einem einzigen parallelen Schritt vor der XOR-Kette berechnet, anstatt jede Negation nacheinander 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")Output:
Nächste Schritte
- Erfahren Sie, wie Sie mithilfe von Stretch eine präzise dynamische Entkopplung implementieren können.
- Nutzen Sie die Visualisierung von Schaltkreisen, um Ihre dynamischen Schaltkreise zu debuggen und zu optimieren.
- Dynamische Schaltkreise ausführen.