Feedforward classico e flusso di controllo
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.3.0
qiskit-ibm-runtime~=0.43.1
La nuova versione dei circuiti dinamici è ora disponibile per tutti gli utenti su tutti i backend. Puoi eseguire circuiti dinamici su scala applicativa. Consulta l'annuncio per ulteriori dettagli.
I circuiti dinamici sono strumenti potenti che ti permettono di misurare i qubit durante l'esecuzione di un circuito quantistico e di eseguire operazioni di logica classica all'interno del circuito, sulla base del risultato 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
Questi miglioramenti apportati dai circuiti dinamici comportano, tuttavia, dei compromessi. Le misure a metà circuito e le operazioni classiche hanno tipicamente tempi di esecuzione più lunghi rispetto ai gate a due qubit, e questo aumento di tempo potrebbe vanificare i benefici della riduzione della profondità del circuito. Pertanto, la riduzione della durata delle misure a metà circuito è un'area di miglioramento prioritaria mentre IBM Quantum® rilascia la nuova versione dei circuiti dinamici.
La specifica OpenQASM 3 definisce diverse strutture di flusso di controllo, ma Qiskit Runtime attualmente supporta solo l'istruzione condizionale if. In Qiskit SDK, questo corrisponde al metodo if_test su QuantumCircuit. Questo metodo restituisce un context manager e viene tipicamente usato in un'istruzione with. Questa guida descrive come usare questa istruzione condizionale.
Gli esempi di codice in questa guida usano l'istruzione di misura standard per le misure a metà circuito. Tuttavia, si consiglia di usare l'istruzione MidCircuitMeasure al suo posto, se il backend la supporta. Consulta la documentazione sulle misure a metà circuito per i dettagli.
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 qiskit-ibm-runtime
from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister
qubits = QuantumRegister(1)
clbits = ClassicalRegister(1)
circuit = QuantumCircuit(qubits, clbits)
(q0,) = qubits
(c0,) = clbits
circuit.h(q0)
# Use MidCircuitMeasure() if it's supported by the backend.
# circuit.append(MidCircuitMeasure(), [q0], [c0])
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 nero indica la condizione su 1, mentre un cerchio bianco 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}
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. A causa delle limitazioni hardware, attualmente sono supportate solo le condizioni di QuantumCircuit.if_test().
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)
Trovare i backend che supportano i circuiti dinamici​
Per trovare tutti i backend a cui il tuo account può accedere e che supportano i circuiti dinamici, esegui un codice simile al seguente. Questo esempio presuppone che tu abbia salvato le tue credenziali di accesso. Puoi anche specificare le credenziali esplicitamente quando inizializzi il tuo account del servizio Qiskit Runtime. Questo ti permetterebbe di visualizzare i backend disponibili per una specifica istanza o tipo di piano, ad esempio.
- I backend disponibili per l'account dipendono dall'istanza specificata nelle credenziali.
- La nuova versione dei circuiti dinamici è ora disponibile per tutti gli utenti su tutti i backend. Consulta l'annuncio per ulteriori dettagli.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
dc_backends = service.backends(dynamic_circuits=True)
print(dc_backends)
[<IBMBackend('ibm_pittsburgh')>, <IBMBackend('ibm_boston')>, <IBMBackend('ibm_fez')>, <IBMBackend('ibm_miami')>, <IBMBackend('ibm_marrakesh')>, <IBMBackend('ibm_torino')>, <IBMBackend('ibm_kingston')>]
Limitazioni di Qiskit Runtime​
Tieni presente i seguenti vincoli quando esegui circuiti dinamici in Qiskit Runtime.
-
A causa della memoria fisica limitata sull'elettronica di controllo, esiste anche un limite sul numero di istruzioni
ife sulla dimensione dei loro operandi. Questo limite è una funzione del numero di broadcast e del numero di bit trasmessi in un job (non in un circuito).Quando si elabora una condizione
if, i dati di misura devono essere trasferiti alla logica di controllo per effettuare quella valutazione. Un broadcast è un trasferimento di dati classici univoci, e i bit trasmessi sono il numero di bit classici trasferiti. Considera il seguente esempio:c0 = ClassicalRegister(3)
c1 = ClassicalRegister(5)
...
with circuit.if_test((c0, 1)) ...
with circuit.if_test((c0, 3)) ...
with circuit.if_test((c1[2], 1)) ...Nell'esempio di codice precedente, i primi due oggetti
if_testsuc0sono considerati un unico broadcast perch é il contenuto dic0non è cambiato, e quindi non deve essere ritrasmesso. L'if_testsuc1è un secondo broadcast. Il primo trasmette tutti e tre i bit dic0e il secondo trasmette un solo bit, per un totale di quattro bit trasmessi.Attualmente, se trasmetti 60 bit per volta, il job può avere circa 300 broadcast. Se trasmetti un solo bit per volta, invece, il job può avere 2400 broadcast.
-
L'operando usato in un'istruzione
if_testdeve avere 32 bit o meno. Pertanto, se stai confrontando un interoClassicalRegister, la dimensione di quelClassicalRegisterdeve essere di 32 bit o meno. Se invece confronti un singolo bit di unClassicalRegister, quelClassicalRegisterpuò avere qualsiasi dimensione (poiché l'operando è di un solo bit).Ad esempio, il blocco di codice "Non valido" non funziona perché
crha più di 32 bit. Puoi tuttavia usare un registro classico più largo di 32 bit se stai testando un solo bit, come mostrato nel blocco di codice "Valido".- Non valido
- Valido
cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr, 15)):
...cr = ClassicalRegister(50)
qr = QuantumRegister(50)
circuit = QuantumCircuit(qr, cr)
...
circ.measure(qr, cr)
with circ.if_test((cr[5], 1)):
... -
I condizionali annidati non sono consentiti. Ad esempio, il seguente blocco di codice non funzionerà perché contiene un
if_testall'interno di un altroif_test:- Non valido
- Valido
c1 = ClassicalRegister(1, "c1")
c2 = ClassicalRegister(2, "c2")
...
with circ.if_test((c1, 1)):
with circ.if_test(c2, 1)):
...cr = ClassicalRegister(2)
...
with circuit.if_test((cr, 0b11)):
... -
La presenza di
reseto misure all'interno di condizionali non è supportata. -
Le operazioni aritmetiche non sono supportate.
-
Consulta la tabella delle funzionalità di OpenQASM 3 per determinare quali funzionalità di OpenQASM 3 sono supportate su Qiskit e Qiskit Runtime.
-
Quando OpenQASM 3 (invece di
QuantumCircuit) viene usato come formato di input per passare circuiti alle primitive di Qiskit Runtime, sono supportate solo le istruzioni che possono essere caricate in Qiskit. Le operazioni classiche, ad esempio, non sono supportate perché non possono essere caricate in Qiskit. Consulta Importare un programma OpenQASM 3 in Qiskit per ulteriori informazioni. -
Le istruzioni
for,whileeswitchnon sono supportate.
Usare i circuiti dinamici con Estimator​
Poiché Estimator non supporta i circuiti dinamici, puoi usare Sampler e costruire i tuoi circuiti di misura. In alternativa, puoi usare la primitiva Executor, che supporta i circuiti dinamici.
Per replicare il comportamento di Estimator, segui questo processo:
- Raggruppa i termini di tutti gli osservabili in una partizione. Questo può essere fatto usando la
PauliListAPI, ad esempio.notaPuoi usare l'attributo primitivo
BitArrayper calcolare i valori di aspettazione degli osservabili forniti. - Esegui un circuito di cambio di base per partizione (qualunque cambio di base sia necessario per ciascuna partizione). Consulta il modulo
measurement_basesdell'addon utility Measurement bases per ulteriori informazioni. Inizia con le utility. - Somma di nuovo i risultati per ciascuna partizione.
Passi successivi​
- Impara come implementare il dynamic decoupling accurato usando stretch.
- Scopri le misure a metà circuito più brevi che riducono il tempo del circuito.
- Usa la visualizzazione della schedulazione del circuito per il debug e l'ottimizzazione dei tuoi circuiti dinamici.