Przejdź do głównej treści

Klasyczne sprzężenie zwrotne i przepływ sterowania (układy dynamiczne)

Package versions

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

qiskit[all]~=2.4.0

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 obwód, 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:

Qiskit obsługuje cztery konstrukcje sterowania przepływem dla klasycznego sprzężenia zwrotnego, z których każda jest zaimplementowana jako metoda na QuantumCircuit. Konstrukcje i odpowiadające im metody to:

Każda z tych metod zwraca menedżera kontekstu i jest zazwyczaj używana w instrukcji with. Dalsza część tego przewodnika wyjaśnia każdą z tych konstrukcji i sposób ich użycia.

ostrożnie

Istnieją pewne ograniczenia operacji klasycznego sprzężenia zwrotnego i sterowania przepływem na sprzęcie kwantowym, które mogą wpłynąć na twój program. Aby uzyskać więcej informacji, zapoznaj się z Wykonaj dynamiczne Circuit-y.

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
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 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. Pełne kółko oznacza warunkowanie na 1, natomiast puste (zarysowane) 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

Instrukcja switch

Instrukcja switch służy do wybierania akcji na podstawie wartości klasycznego bitu lub rejestru. Jest podobna do instrukcji if, ale można określić więcej przypadków dla logiki rozgałęzień. Poniższy przykład stosuje bramkę Hadamarda do Qubitu i mierzy go. Jeśli wynik wynosi 0, zastosuj bramkę X na Qubicie, a jeśli wynik wynosi 1, zastosuj bramkę Z. Wynikowy wynik pomiaru powinien wynosić 1 z prawdopodobieństwem 100%.

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 of the previous code cell

Ponieważ powyższy przykład używał pojedynczego klasycznego bitu, były tylko dwa możliwe przypadki, więc można było osiągnąć ten sam wynik za pomocą instrukcji if-else. Instrukcja switch jest głównie przydatna przy rozgałęzieniu na wartości klasycznego rejestru złożonego z wielu bitów. Poniższy przykład pokazuje, jak skonstruować domyślny przypadek, który jest wykonywany, gdy żaden z poprzedzających przypadków nie pasuje. Warto zauważyć, że w instrukcji switch tylko jeden blok jest kiedykolwiek wykonywany. Nie ma przechodzenia do następnego przypadku (fallthrough).

Poniższy przykład stosuje bramki Hadamarda do dwóch Qubitów i mierzy je. Jeśli wynik wynosi 00 lub 11, zastosuj bramkę Z do trzeciego Qubitu. Jeśli wynik wynosi 01, zastosuj bramkę Y. Jeśli żaden z poprzedzających przypadków nie pasuje, zastosuj bramkę X. Na koniec zmierz trzeci 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 of the previous code cell

Pętla for

Pętla for służy do iterowania po sekwencji klasycznych wartości i wykonywania pewnych operacji podczas każdej iteracji.

Poniższy przykład używa pętli for do zastosowania 5 bramek X do Qubitu, a następnie mierzy go. Ponieważ wykonuje nieparzystą liczbę bramek X, ogólny efekt polega na odwróceniu Qubitu ze stanu 0 do stanu 1.

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 of the previous code cell

Pętla while

Pętla while służy do powtarzania instrukcji, gdy spełniony jest pewien warunek.

Poniższy przykład stosuje bramki Hadamarda do dwóch Qubitów i mierzy je. Następnie tworzy pętlę while, która powtarza tę procedurę, gdy wynik pomiaru wynosi 11. W rezultacie końcowy wynik pomiaru nigdy nie powinien wynosić 11, a pozostałe możliwości pojawiają się z mniej więcej jednakową częstotliwością.

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

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

Store

Możesz użyć instrukcji store, aby zapisać wynik wyrażenia klasycznego, jeśli to wyrażenie będzie wielokrotnie używane. Operacje są automatycznie zrównoleglane, co znacznie zwiększa efektywność kodu w czasie wykonywania.

Na przykład, bardziej naturalne i efektywniejsze w czasie wykonywania jest pisanie B[0]B[1]B[2]B[0] \oplus B[1] \oplus B[2] \ldots, gdzie B=¬AB = \neg A, niż (¬A[0])(¬A[1])(¬A[2])(\neg A[0]) \oplus (\neg A[1]) \oplus (\neg A[2]) \ldots. Pierwsze podejście oblicza negację w jednym równoległym kroku przed łańcuchem XOR, zamiast obliczać każdą negację sekwencyjnie wewnątrz wyrażenia.

Pełny przykład:

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 of the previous code cell

Następne kroki

Rekomendacje