Przejdź do głównej treści

Klasyczne sprzężenie zwrotne i przepływ sterowania

Wersje pakietów

Kod na tej stronie został opracowany przy użyciu poniższych wymagań. Zalecamy korzystanie z tych lub nowszych wersji.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
Układy dynamiczne dostępne teraz na wszystkich Backend'ach

Nowa wersja układów dynamicznych jest teraz dostępna dla wszystkich użytkowników na wszystkich Backend'ach. Możesz teraz uruchamiać układy dynamiczne w skali narzędziowej. Więcej informacji znajdziesz w ogłoszeniu.

Układy dynamiczne to potężne narzędzia, dzięki którym możesz mierzyć Qubity w trakcie wykonywania kwantowego Circuit, a następnie wykonywać klasyczne operacje logiczne w ramach tego Circuit, bazując na wynikach tych pomiarów śródukładowych. Proces ten jest też znany jako klasyczne sprzężenie zwrotne. Choć wciąż jesteśmy na wczesnym etapie rozumienia, jak najlepiej wykorzystać układy dynamiczne, społeczność badaczy kwantowych już zidentyfikowała szereg przypadków użycia, takich jak:

Ulepszenia wprowadzane przez układy dynamiczne wiążą się jednak z pewnymi kompromisami. Pomiary śródukładowe i operacje klasyczne zazwyczaj mają dłuższy czas wykonania niż bramki dwuqubitowe, a ten wzrost czasu może niwelować korzyści wynikające ze zmniejszonej głębokości Circuit'u. Dlatego skracanie czasu pomiarów śródukładowych jest priorytetowym obszarem usprawnień, który IBM Quantum® realizuje w ramach nowej wersji układów dynamicznych.

Specyfikacja OpenQASM 3 definiuje wiele struktur przepływu sterowania, ale Qiskit Runtime obsługuje obecnie tylko warunkową instrukcję if. W Qiskit SDK odpowiada jej metoda if_test klasy QuantumCircuit. Metoda ta zwraca menedżer kontekstu i jest zwykle używana w instrukcji with. Ten przewodnik opisuje, jak korzystać z tej instrukcji warunkowej.

uwaga

Przykłady kodu w tym przewodniku używają standardowej instrukcji pomiaru do pomiarów śródukładowych. Zaleca się jednak użycie instrukcji MidCircuitMeasure zamiast niej, jeśli Backend to obsługuje. Szczegółowe informacje znajdziesz w dokumentacji pomiarów śródukładowych.

Instrukcja if

Instrukcja if służy do warunkowego wykonywania operacji na podstawie wartości klasycznego bitu lub rejestru.

W poniższym przykładzie stosujemy bramkę Hadamarda do Qubitu i mierzymy go. Jeśli wynik wynosi 1, stosujemy bramkę X na tym Qubicie, co powoduje odwrócenie go z powrotem do stanu 0. Następnie mierzymy Qubit ponownie. Wynikowy wynik pomiaru powinien wynosić 0 z prawdopodobieństwem 100%.

# 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

Instrukcji with można nadać cel przypisania, który sam w sobie jest menedżerem kontekstu — można go zachować i następnie użyć do stworzenia bloku else, który jest wykonywany zawsze wtedy, gdy zawartość bloku if nie jest wykonywana.

W poniższym przykładzie inicjalizujemy rejestry z dwoma Qubitami i dwoma klasycznymi bitami. Stosujemy bramkę Hadamarda do pierwszego Qubitu i mierzymy go. Jeśli wynik wynosi 1, stosujemy bramkę Hadamarda na drugim Qubicie; w przeciwnym razie stosujemy bramkę X na drugim Qubicie. Na końcu mierzymy też drugi 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

Oprócz warunkowania na pojedynczym klasycznym bicie, możliwe jest również warunkowanie na wartości klasycznego rejestru złożonego z wielu bitów.

W poniższym przykładzie stosujemy bramki Hadamarda do dwóch Qubitów i mierzymy je. Jeśli wynik wynosi 01, tzn. pierwszy Qubit ma wartość 1, a drugi Qubit ma wartość 0, stosujemy bramkę X do trzeciego Qubitu. Na końcu mierzymy trzeci Qubit. Warto zauważyć, że dla przejrzystości zdecydowaliśmy się podać stan trzeciego klasycznego bitu, który wynosi 0, w warunku if. Na rysunku Circuit'u warunek jest wskazywany przez kółka na klasycznych bitach, na których bazuje warunek. Czarne kółko oznacza warunkowanie na 1, natomiast białe kółko oznacza warunkowanie na 0.

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

Wyrażenia klasyczne

Moduł klasycznych wyrażeń Qiskit qiskit.circuit.classical zawiera eksperymentalną reprezentację operacji wykonywanych w czasie rzeczywistym na wartościach klasycznych podczas wykonywania Circuit'u. Ze względu na ograniczenia sprzętowe, obsługiwane są obecnie tylko warunki QuantumCircuit.if_test().

Poniższy przykład pokazuje, że możesz użyć obliczania parzystości do tworzenia n-qubitowego stanu GHZ przy użyciu układów dynamicznych. Najpierw wygeneruj n/2n/2 par Bella na sąsiednich Qubitach. Następnie połącz te pary warstwą bramek CNOT pomiędzy nimi. Zmierz docelowy Qubit wszystkich poprzednich bramek CNOT i zresetuj każdy zmierzony Qubit do stanu 0\vert 0 \rangle. Zastosuj XX do każdego niezmierzonego miejsca, dla którego parzystość wszystkich poprzedzających bitów jest nieparzysta. Na koniec bramki CNOT są stosowane do zmierzonych Qubitów w celu przywrócenia splątania utraconego podczas pomiaru.

W obliczaniu parzystości pierwszy element zbudowanego wyrażenia polega na podniesieniu obiektu Python mr[0] do węzła Value (lift służy do przekształcania dowolnych obiektów w wyrażenia klasyczne). Nie jest to konieczne dla mr[1] i ewentualnych kolejnych rejestrów klasycznych, ponieważ są one danymi wejściowymi do expr.bit_xor, a wszelkie niezbędne podniesienie jest wykonywane automatycznie w tych przypadkach. Takie wyrażenia można budować w pętlach i innych konstrukcjach.

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

Znajdź Backend-i obsługujące dynamiczne Circuit-y

Aby znaleźć wszystkie Backend-i dostępne na Twoim koncie, które obsługują dynamiczne Circuit-y, uruchom kod podobny do poniższego. Ten przykład zakłada, że masz zapisane dane logowania. Możesz też jawnie podać dane logowania podczas inicjalizacji konta usługi Qiskit Runtime. Pozwoli Ci to na przykład wyświetlić Backend-i dostępne dla konkretnej instancji lub typu planu.

Uwagi
  • Backend-i dostępne dla konta zależą od instancji podanej w danych logowania.
  • Nowa wersja dynamicznych Circuit-ów jest teraz dostępna dla wszystkich użytkowników na wszystkich Backend-ach. Więcej szczegółów znajdziesz w ogłoszeniu.
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')>]

Ograniczenia Qiskit Runtime

Podczas uruchamiania dynamicznych Circuit-ów w Qiskit Runtime weź pod uwagę następujące ograniczenia.

  • Ze względu na ograniczoną pamięć fizyczną w elektronice sterującej istnieje również limit liczby instrukcji if oraz rozmiaru ich operandów. Limit ten jest funkcją liczby transmisji (broadcasts) oraz liczby przesyłanych bitów w zadaniu (nie w Circuit-cie).

    Podczas przetwarzania warunku if dane pomiarowe muszą zostać przesłane do logiki sterującej w celu dokonania ewaluacji. Transmisja (broadcast) to transfer unikalnych danych klasycznych, a transmitowane bity to liczba przesyłanych bitów klasycznych. Rozważ następujący przykład:

    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)) ...

    W powyższym przykładzie kodu pierwsze dwa obiekty if_test na c0 są traktowane jako jedna transmisja, ponieważ zawartość c0 nie uległa zmianie i nie musi być ponownie transmitowana. if_test na c1 to druga transmisja. Pierwsza transmituje wszystkie trzy bity z c0, a druga tylko jeden bit, co daje łącznie cztery transmitowane bity.

    Obecnie, jeśli transmitujesz 60 bitów za każdym razem, zadanie może mieć około 300 transmisji. Jeśli jednak transmitujesz tylko jeden bit za każdym razem, zadanie może mieć 2400 transmisji.

  • Operand użyty w instrukcji if_test musi mieć 32 bity lub mniej. Dlatego jeśli porównujesz cały ClassicalRegister, jego rozmiar musi wynosić 32 bity lub mniej. Jeśli jednak porównujesz tylko jeden bit z ClassicalRegister, ten ClassicalRegister może mieć dowolny rozmiar (ponieważ operand ma tylko jeden bit).

    Na przykład blok kodu „Niepoprawne" nie działa, ponieważ cr ma więcej niż 32 bity. Możesz jednak używać rejestru klasycznego szerszego niż 32 bity, jeśli testujesz tylko jeden bit, jak pokazano w bloku kodu „Poprawne".

       cr = ClassicalRegister(50)
    qr = QuantumRegister(50)
    circuit = QuantumCircuit(qr, cr)
    ...
    circ.measure(qr, cr)
    with circ.if_test((cr, 15)):
    ...
  • Zagnieżdżone warunkowe nie są dozwolone. Na przykład poniższy blok kodu nie zadziała, ponieważ zawiera if_test wewnątrz innego if_test:

       c1 = ClassicalRegister(1, "c1")
    c2 = ClassicalRegister(2, "c2")
    ...
    with circ.if_test((c1, 1)):
    with circ.if_test(c2, 1)):
    ...
  • Instrukcja reset ani pomiary wewnątrz warunków nie są obsługiwane.

  • Operacje arytmetyczne nie są obsługiwane.

  • Zapoznaj się z tabelą funkcji OpenQASM 3, aby sprawdzić, które funkcje OpenQASM 3 są obsługiwane w Qiskit i Qiskit Runtime.

  • Gdy OpenQASM 3 (zamiast QuantumCircuit) jest używany jako format wejściowy do przekazywania Circuit-ów do prymitywów Qiskit Runtime, obsługiwane są tylko instrukcje, które można załadować do Qiskit. Operacje klasyczne na przykład nie są obsługiwane, ponieważ nie można ich załadować do Qiskit. Więcej informacji znajdziesz w artykule Importowanie programu OpenQASM 3 do Qiskit.

  • Instrukcje for, while i switch nie są obsługiwane.

Używanie dynamicznych Circuit-ów z Estimator-em

Ponieważ Estimator nie obsługuje dynamicznych Circuit-ów, możesz użyć Sampler-a i samodzielnie zbudować własne Circuit-y pomiarowe. Alternatywnie możesz skorzystać z prymitywu Executor, który obsługuje dynamiczne Circuit-y.

Aby odtworzyć zachowanie Estimator-a, postępuj zgodnie z następującym procesem:

  1. Pogrupuj składniki wszystkich obserwabli w partycję. Można to zrobić na przykład za pomocą API PauliList,.
    uwaga

    Możesz użyć atrybutu prymitywu BitArray, aby obliczyć wartości oczekiwane podanych obserwabli.

  2. Wykonaj jeden Circuit zmiany bazy na partycję (niezależnie od tego, jaka zmiana bazy jest potrzebna dla każdej partycji). Więcej informacji znajdziesz w dodatku do narzędzi pomiarowych measurement_bases module. Zacznij korzystać z narzędzi.
  3. Zsumuj z powrotem wyniki dla każdej partycji.

Następne kroki

Rekomendacje