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
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:
- Efektywne przygotowywanie stanów kwantowych, np. stanu GHZ, stanu W, (więcej informacji na temat stanu W znajdziesz w artykule "State preparation by shallow circuits using feed forward") oraz szerokiej klasy stanów iloczynu macierzowego
- Efektywne splątanie dalekiego zasięgu pomiędzy Qubitami na tym samym chipie przy użyciu płytkich Circuit'ów
- Efektywne próbkowanie układów podobnych do IQP
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.
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}
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}
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}
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 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 . Zastosuj 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)
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.
- 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
iforaz 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
ifdane 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_testnac0są traktowane jako jedna transmisja, ponieważ zawartośćc0nie uległa zmianie i nie musi być ponownie transmitowana.if_testnac1to druga transmisja. Pierwsza transmituje wszystkie trzy bity zc0, 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_testmusi mieć 32 bity lub mniej. Dlatego jeśli porównujesz całyClassicalRegister, jego rozmiar musi wynosić 32 bity lub mniej. Jeśli jednak porównujesz tylko jeden bit zClassicalRegister, tenClassicalRegistermoże mieć dowolny rozmiar (ponieważ operand ma tylko jeden bit).Na przykład blok kodu „Niepoprawne" nie działa, ponieważ
crma 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".- Niepoprawne
- Poprawne
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)):
... -
Zagnieżdżone warunkowe nie są dozwolone. Na przykład poniższy blok kodu nie zadziała, ponieważ zawiera
if_testwewnątrz innegoif_test:- Niepoprawne
- Poprawne
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)):
... -
Instrukcja
resetani 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,whileiswitchnie 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:
- Pogrupuj składniki wszystkich obserwabli w partycję. Można to zrobić na przykład za pomocą API
PauliList,.uwagaMożesz użyć atrybutu prymitywu
BitArray, aby obliczyć wartości oczekiwane podanych obserwabli. - 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_basesmodule. Zacznij korzystać z narzędzi. - Zsumuj z powrotem wyniki dla każdej partycji.
Następne kroki
- Dowiedz się, jak wdrożyć dokładne dynamiczne odsprzęganie (dynamic decoupling) za pomocą stretch.
- Dowiedz się więcej o krótszych pomiarach śródukładowych (mid-circuit measurements), które skracają czas Circuit-u.
- Użyj wizualizacji harmonogramu Circuit-u, aby debugować i optymalizować swoje dynamiczne Circuit-y.