Vai al contenuto principale

Feedforward classico e flusso di controllo (circuiti dinamici)

Versioni dei pacchetti

Il codice in questa pagina è stato sviluppato con i seguenti requisiti. Si consiglia di usare queste versioni o versioni più recenti.

qiskit[all]~=2.4.0

I circuiti dinamici sono strumenti potenti con cui è possibile misurare i qubit durante l'esecuzione di un circuito quantistico e quindi eseguire operazioni di logica classica all'interno del circuito, sulla base dei risultati di quelle misure a metà circuito. Questo processo è noto anche come feedforward classico. Benché siamo ancora agli inizi della comprensione di come sfruttare al meglio i circuiti dinamici, la comunità di ricerca quantistica ha già identificato diversi casi d'uso, tra cui:

Qiskit supporta quattro costrutti di flusso di controllo per il feedforward classico, ciascuno implementato come metodo su QuantumCircuit. I costrutti e i relativi metodi sono:

Ciascuno di questi metodi restituisce un context manager e viene tipicamente usato in un'istruzione with. Il resto di questa guida spiega ciascuno di questi costrutti e come usarli.

attenzione

Esistono alcune limitazioni delle operazioni di feedforward classico e flusso di controllo sull'hardware quantistico che potrebbero influire sul tuo programma. Per ulteriori informazioni, consulta Esegui circuiti dinamici.

Istruzione if

L'istruzione if viene usata per eseguire operazioni in modo condizionale in base al valore di un bit o registro classico.

Nell'esempio seguente, applichiamo un gate di Hadamard a un qubit e lo misuriamo. Se il risultato è 1, applichiamo un gate X al qubit, che ha l'effetto di riportarlo allo stato 0. Poi misuriamo di nuovo il qubit. Il risultato della misura dovrebbe essere 0 con probabilità del 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 della cella di codice precedente

All'istruzione with può essere assegnato un target che è a sua volta un context manager, il quale può essere memorizzato e usato successivamente per creare un blocco else, eseguito ogni volta che il contenuto del blocco if non viene eseguito.

Nell'esempio seguente, inizializziamo dei registri con due qubit e due bit classici. Applichiamo un gate di Hadamard al primo qubit e lo misuriamo. Se il risultato è 1, applichiamo un gate di Hadamard al secondo qubit; altrimenti, applichiamo un gate X al secondo qubit. Infine, misuriamo anche il secondo 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 della cella di codice precedente

Oltre a condizionare su un singolo bit classico, è anche possibile condizionare sul valore di un registro classico composto da più bit.

Nell'esempio seguente, applichiamo gate di Hadamard a due qubit e li misuriamo. Se il risultato è 01, ossia il primo qubit è 1 e il secondo qubit è 0, applichiamo un gate X a un terzo qubit. Infine, misuriamo il terzo qubit. Nota che per chiarezza abbiamo scelto di specificare lo stato del terzo bit classico, che è 0, nella condizione if. Nel disegno del circuito, la condizione è indicata dai cerchi sui bit classici su cui si condiziona. Un cerchio pieno indica la condizione su 1, mentre un cerchio vuoto indica la condizione su 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 della cella di codice precedente

Istruzione switch

L'istruzione switch viene usata per selezionare azioni in base al valore di un bit o registro classico. È simile a un'istruzione if, ma è possibile specificare più casi per la logica di ramificazione. L'esempio seguente applica un gate di Hadamard a un qubit e lo misura. Se il risultato è 0, applica un gate X al qubit, e se il risultato è 1, applica un gate Z. Il risultato della misura dovrebbe essere 1 con probabilità del 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 della cella di codice precedente

Poiché l'esempio precedente usava un singolo bit classico, c'erano solo due casi possibili, quindi si sarebbe potuto ottenere lo stesso risultato con un'istruzione if-else. Il costrutto switch è particolarmente utile quando si ramifica sul valore di un registro classico composto da più bit. L'esempio seguente mostra come costruire un caso predefinito (default), che viene eseguito se nessuno dei casi precedenti corrisponde. Nota che in un'istruzione switch viene eseguito solo uno dei blocchi. Non esiste il fallthrough.

L'esempio seguente applica gate di Hadamard a due qubit e li misura. Se il risultato è 00 oppure 11, applica un gate Z al terzo qubit. Se il risultato è 01, applica un gate Y. Se nessuno dei casi precedenti corrisponde, applica un gate X. Infine, misura il terzo 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 della cella di codice precedente

Ciclo for

Un ciclo for viene usato per iterare su una sequenza di valori classici ed eseguire alcune operazioni durante ogni iterazione.

L'esempio seguente usa un ciclo for per applicare 5 gate X a un qubit e poi lo misura. Poiché esegue un numero dispari di gate X, l'effetto complessivo è portare il qubit dallo stato 0 allo stato 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 della cella di codice precedente

Ciclo while

Un ciclo while viene usato per ripetere istruzioni finché una certa condizione è soddisfatta.

L'esempio seguente applica gate di Hadamard a due qubit e li misura. Poi crea un ciclo while che ripete questa procedura finché il risultato della misura è 11. Di conseguenza, la misura finale non dovrebbe mai essere 11, con le restanti possibilità che appaiono con frequenza approssimativamente uguale.

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 della cella di codice precedente

Espressioni classiche

Il modulo di espressioni classiche di Qiskit qiskit.circuit.classical contiene una rappresentazione esplorativa delle operazioni runtime su valori classici durante l'esecuzione del circuito.

L'esempio seguente mostra come usare il calcolo della parità per creare uno stato GHZ a n qubit usando i circuiti dinamici. Prima, si generano n/2n/2 coppie di Bell su qubit adiacenti. Poi, si collegano queste coppie tramite un layer di gate CNOT tra le coppie. Si misurano quindi il qubit target di tutti i gate CNOT precedenti e si reimposta ogni qubit misurato allo stato 0\vert 0 \rangle. Si applica XX a ogni sito non misurato per il quale la parità di tutti i bit precedenti è dispari. Infine, i gate CNOT vengono applicati ai qubit misurati per ristabilire l'entanglement perso nella misura.

Nel calcolo della parità, il primo elemento dell'espressione costruita comporta il lifting dell'oggetto Python mr[0] a un nodo Value (lift viene usato per convertire oggetti arbitrari in espressioni classiche). Questo non è necessario per mr[1] e il possibile registro classico successivo, poiché sono input di expr.bit_xor, e il lifting necessario viene eseguito automaticamente in questi casi. Tali espressioni possono essere costruite in cicli e altre strutture.

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 della cella di codice precedente

Store

È possibile usare l'istruzione store per salvare il risultato di un'espressione classica, se quell'espressione verrà usata ripetutamente. Le operazioni vengono automaticamente parallelizzate, rendendo il codice significativamente più efficiente in fase di esecuzione.

Ad esempio, è più naturale ed efficiente in fase di esecuzione scrivere B[0]B[1]B[2]B[0] \oplus B[1] \oplus B[2] \ldots, dove B=¬AB = \neg A, rispetto a (¬A[0])(¬A[1])(¬A[2])(\neg A[0]) \oplus (\neg A[1]) \oplus (\neg A[2]) \ldots. Il primo approccio calcola la negazione in un unico passo parallelo prima della catena XOR, anziché valutare ogni negazione sequenzialmente all'interno dell'espressione.

Esempio completo:

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

Passi successivi

Raccomandazioni