Zum Hauptinhalt springen

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
Dynamische Schaltungen jetzt auf allen Backends verfügbar

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:

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.

hinweis

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}

Output of the previous code cell

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}

Output of the previous code cell

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}

Output of the previous code cell

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 n/2n/2 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 0\vert 0 \rangle zurück. Wende XX 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)

Output of the previous code cell

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.

Hinweise
  • 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 auf c0 als ein Broadcast betrachtet, da sich der Inhalt von c0 nicht geändert hat und daher nicht erneut übertragen werden muss. Der if_test auf c1 ist ein zweiter Broadcast. Der erste überträgt alle drei Bits in c0 und 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 gesamtes ClassicalRegister vergleichst, muss die Größe dieses ClassicalRegister 32 oder weniger Bits betragen. Wenn du jedoch nur ein einzelnes Bit aus einem ClassicalRegister vergleichst, kann dieses ClassicalRegister beliebiger Größe sein (da der Operand nur ein Bit ist).

    Zum Beispiel funktioniert der Code-Block "Nicht gültig" nicht, weil cr mehr 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.

       cr = ClassicalRegister(50)
    qr = QuantumRegister(50)
    circuit = QuantumCircuit(qr, cr)
    ...
    circ.measure(qr, cr)
    with circ.if_test((cr, 15)):
    ...
  • Verschachtelte Bedingungen sind nicht erlaubt. Zum Beispiel funktioniert der folgende Code-Block nicht, weil er ein if_test innerhalb eines anderen if_test hat:

       c1 = ClassicalRegister(1, "c1")
    c2 = ClassicalRegister(2, "c2")
    ...
    with circ.if_test((c1, 1)):
    with circ.if_test(c2, 1)):
    ...
  • reset oder 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, while und switch werden 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:

  1. Gruppiere die Terme aller Observablen in eine Partition. Dies kann beispielsweise mit der PauliList-API erfolgen.
    hinweis

    Du kannst das BitArray-Primitive-Attribut verwenden, um die Erwartungswerte der bereitgestellten Observablen zu berechnen.

  2. 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.
  3. Addiere die Ergebnisse für jede Partition zusammen.

Nächste Schritte

Empfehlungen