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:
- Preparazione efficiente di stati quantistici, come lo stato GHZ, lo stato W (per ulteriori informazioni sullo stato W, consulta anche "State preparation by shallow circuits using feed forward") e un'ampia classe di stati prodotto a matrice
- Entanglement efficiente a lungo raggio tra qubit sullo stesso chip tramite circuiti poco profondi
- Campionamento efficiente di circuiti di tipo IQP
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:
- Istruzione if -
QuantumCircuit.if_test - Istruzione switch -
QuantumCircuit.switch - Ciclo for -
QuantumCircuit.for_loop - Ciclo while -
QuantumCircuit.while_loop
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.
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}
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}
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}
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}
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}
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}
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}
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 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 . Si applica 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)
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 , dove , rispetto a . 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")
Passi successivi
- Scopri come implementare il dynamic decoupling accurato usando stretch.
- Usa la visualizzazione della schedulazione del circuito per il debug e l'ottimizzazione dei tuoi circuiti dinamici.
- Esegui circuiti dinamici.