Feedforward classico e flusso di controllo (circuiti dinamici)
Il codice di questa pagina è stato sviluppato in base ai seguenti requisiti. Si consiglia di utilizzare queste versioni o versioni più recenti.
qiskit[all]~=2.5.1
I circuiti dinamici sono strumenti potenti con i quali è possibile misurare i qubit nel mezzo dell'esecuzione di un circuito quantistico e quindi eseguire operazioni logiche classiche all'interno del circuito, sulla base del risultato di tali misurazioni a metà circuito. Questo processo è noto anche come feedforward classico. Sebbene sia ancora presto per capire come sfruttare al meglio i circuiti dinamici, la comunità di ricerca quantistica ha già identificato una serie di casi d'uso, come i seguenti:
- Preparazione efficiente dello stato quantistico, come lo stato GHZ, lo stato W (per ulteriori informazioni sullo stato W, consultare anche "Preparazione dello stato mediante circuiti superficiali che utilizzano feed forward" ) e un'ampia classe di stati di prodotto matriciale
- Efficiente entanglement a lungo raggio tra qubit sullo stesso chip utilizzando circuiti poco profondi
- Campionamento efficiente di circuiti simili a 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 gestore di contesto e viene solitamente utilizzato in un’istruzione with . Il resto di questa guida illustra ciascuno di questi costrutti e come utilizzarli.
Esistono alcune limitazioni relative alle operazioni classiche di feedforward e di controllo del flusso sull'hardware quantistico che potrebbero influire sul vostro programma. Per ulteriori informazioni, consultare la sezione "Esecuzione di circuiti dinamici".
if istruzione
L'istruzione if viene utilizzata per eseguire operazioni condizionali basate sul valore di un bit o di un registro classico.
Nell'esempio che segue, applichiamo un gate di Hadamard a un qubit e lo misuriamo. Se il risultato è 1, allora applichiamo un gate X sul qubit, che ha l'effetto di riportarlo allo stato 0. Poi misuriamo di nuovo il qubit. Il risultato della misurazione deve essere 0 con una probabilità del 100%.
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:
All'istruzione with può essere assegnato un target di assegnazione che è a sua volta un gestore di contesto che può essere memorizzato e successivamente utilizzato per creare un blocco else, che viene eseguito ogni volta che il contenuto del blocco if non viene eseguito.
Nell'esempio che segue, inizializziamo i registri con due qubit e due bit classici. Applichiamo un gate di Hadamard al primo qubit e lo misuriamo. Se il risultato è 1, si applica un gate Hadamard al secondo qubit; altrimenti, si applica 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:
Oltre al condizionamento su un singolo bit classico, è anche possibile condizionare sul valore di un registro classico composto da più bit.
Nell'esempio seguente, applichiamo i gate di Hadamard a due qubit e li misuriamo. Se il risultato è 01, ovvero il primo qubit è 1 e il secondo qubit è 0, allora applichiamo un gate X a un terzo qubit. Infine, misuriamo il terzo qubit. Si noti che, per chiarezza, abbiamo scelto di specificare lo stato del terzo bit classico, che è 0, nella if condizione. Nel disegno del circuito, la condizione è indicata dai cerchi sui bit classici che vengono condizionati. Un cerchio pieno indica il condizionamento su 1, mentre un cerchio tratteggiato indica il condizionamento 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:
Istruzione switch
L'istruzione switch viene utilizzata per selezionare delle azioni in base al valore di un bit o di un registro classico. È simile a un'istruzione "if", ma permette di specificare più casi per la logica di ramificazione. L'esempio riportato di seguito applica una porta di Hadamard a un qubit e ne rileva il valore. Se il risultato è 0, applicare un gate X al qubit; se il risultato è 1, applicare un gate Z. Il risultato della misurazione dovrebbe essere 1 con una 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:
Poiché nell'esempio precedente è stato utilizzato un singolo bit classico, i casi possibili erano solo due; pertanto, si sarebbe potuto ottenere lo stesso risultato utilizzando un'istruzione if-else. Il caso dello switch è utile soprattutto quando si effettua una ramificazione in base al valore di un registro classico composto da più bit. L'esempio seguente mostra come definire un caso predefinito, che viene eseguito se nessuno dei casi precedenti viene eseguito. Si noti che in un'istruzione switch viene eseguito sempre e solo uno dei blocchi. Non c'è alcun "fallthrough".
L'esempio riportato di seguito applica le porte di Hadamard a due qubit e ne misura i valori. Se il risultato è 00 o 11, applicare un gate Z al terzo qubit. Se il risultato è 01, applicare una porta Y. Se nessuno dei casi precedenti corrisponde, applicare un gate X. Infine, misurare 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:
Per il ciclo
Un ciclo "for" viene utilizzato per scorrere una sequenza di valori classici ed eseguire alcune operazioni durante ogni iterazione.
L'esempio seguente utilizza un ciclo for per applicare 5 porte X a un qubit e poi ne misura il valore. Poiché esegue un numero dispari di porte X, l'effetto complessivo è quello di far passare 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:
Loop While
Un ciclo while viene utilizzato per ripetere una serie di istruzioni finché una determinata condizione rimane vera.
L'esempio riportato di seguito applica le porte di Hadamard a due qubit e ne misura i valori. Quindi crea un ciclo while che ripete questa procedura finché il risultato della misurazione è 11. Di conseguenza, il risultato finale non dovrebbe mai essere 11, mentre le altre possibilità dovrebbero presentarsi 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:
Espressioni classiche
Il modulo qiskit.circuit.classical "Espressione classica" di Qiskit contiene una rappresentazione esplorativa delle operazioni eseguite in fase di esecuzione su valori classici durante l'esecuzione del circuito.
L'esempio seguente mostra che è possibile utilizzare il calcolo della parità per creare uno stato GHZ a n-qubit utilizzando circuiti dinamici. In primo luogo, si generano coppie di Bell su qubit adiacenti. Quindi, incollare queste coppie utilizzando uno strato di porte CNOT tra le coppie. Si misura quindi il qubit target di tutte le porte CNOT precedenti e si resetta ogni qubit misurato allo stato . Si applica a ogni sito non misurato per il quale la parità di tutti i bit precedenti è dispari. Infine, le porte CNOT vengono applicate ai qubit misurati per ristabilire l'entanglement perso durante la misurazione.
Nel calcolo della parità, il primo elemento dell'espressione costruita comporta il sollevamento dell'oggetto Pythonmr[0] a un Value nodo (lift viene utilizzato per trasformare oggetti arbitrari in espressioni classiche). Ciò non è necessario per mr[1] e il possibile registro classico seguente, poiché sono input per expr.bit_xore qualsiasi sollevamento necessario viene eseguito automaticamente in questi casi. Tali espressioni possono essere costruite in loop e altri costrutti.
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:
Store
È possibile utilizzare l'istruzione store per salvare il risultato di un'espressione classica, qualora tale espressione debba essere utilizzata più volte. Le operazioni vengono parallelizzate automaticamente, rendendo il codice notevolmente più efficiente in fase di esecuzione.
Ad esempio, in fase di esecuzione è più naturale ed efficiente scrivere , dove , piuttosto che . Nel primo caso, la negazione viene calcolata in un unico passaggio parallelo prima della catena XOR, invece di valutare sequenzialmente ogni negazione 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:
Passi successivi
- Scopri come implementare un disaccoppiamento dinamico accurato utilizzando lo stretch.
- Utilizza la visualizzazione dello schema dei circuiti per eseguire il debug e ottimizzare i tuoi circuiti dinamici.
- Eseguire circuiti dinamici.