Skip to main content
IBM Quantum Platform

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:

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 gestore di contesto e viene solitamente utilizzato in un’istruzione with . Il resto di questa guida illustra ciascuno di questi costrutti e come utilizzarli.

Caution

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:

Output of the previous code cell

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:

Output of the previous code cell

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:

Output of the previous code cell

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:

Output of the previous code cell

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:

Output of the previous code cell

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:

Output of the previous code cell

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:

Output of the previous code cell

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 n/2n/2 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 0\vert 0 \rangle. Si applica XX 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:

Output of the previous code cell

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 B[0]B[1]B[2]B[0] \oplus B[1] \oplus B[2] \ldots, dove B=¬AB = \neg A, piuttosto che (¬A[0])(¬A[1])(¬A[2])(\neg A[0]) \oplus (\neg A[1]) \oplus (\neg A[2]) \ldots. 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:

Output of the previous code cell

Passi successivi

Suggerimenti
Questa pagina è stata utile?
Segnala un bug, un errore di battitura o richiedi contenuti su GitHub.