Come ripetere il circuito in modo efficiente e affidabile in IBM Qiskit?

Oct 30 2020

Ho un problema in IBM Qiskit con le mie identità qubit che cambiano posizione durante il transpilation. Gli sviluppatori sul server Qiskit slack mi hanno detto che attualmente non esiste un modo integrato per risolvere questo problema. Mi chiedo se qualcuno ne abbia trovato uno da solo.

Il problema è che mi piacerebbe eseguire un algoritmo in questo modo:

  1. Preparati in uno stato iniziale
  2. Applicare un circuito Uripetutamente n_iter volte
  3. Misurare

Voglio anche che questo algoritmo:

  • Sii ragionevolmente efficiente con i cancelli (in particolare i CNOT rumorosi)
  • Avere lo stesso errore Uper ogni iterazione di esso

Questo risulta essere un mal di testa. Quando Uè complicato, diventa difficile per il trasportatore scomporlo in modo efficiente in gate nativi. Questa difficoltà aumenta per U*Ue U*U*Upoiché sono più lunghi. Quindi, se traspilassi l'intero algoritmo in una volta (con molti U), certamente non darebbe una decomposizione efficiente del gate e non avrebbe lo stesso errore per ogni iterazione di U.

La soluzione che ho pensato fosse ragionevole è trasporla Uda sola, quindi comporla con se stessa. Ma questo ha un problema. Al transpiler piace cambiare identità qubit (e aggiungere fase globale, e forse anche di più?) Mentre cerca un circuito più efficiente. Quindi, se seguo il mio algoritmo come indicato sopra, Unon si allineerà correttamente con la preparazione o la misurazione e i miei risultati saranno sbagliati.

Gli sviluppatori mi hanno detto che attualmente non esiste un modo integrato per risolvere questo problema. Il transpiler non segnala uno final_layoutdei qubit.

Ho cercato di determinare final_layouta mano confrontando l'uscita del transpiler con il mio circuito previsto più i gate di scambio (fino a una fase globale), ma non riesco nemmeno a questo.

Qualcuno ha un codice funzionante per determinare final_layouto un approccio migliore all'implementazione di questo algoritmo?

(EDIT: codice e ulteriori informazioni sono inclusi nella mia risposta di seguito)

Risposte

tsgeorgios Nov 02 2020 at 06:46

Se capisco correttamente la tua domanda, immagino che potresti ottenere, dopo il transpiling $U$, il layout finale dalla _layoutproprietà di un circuito e rimappare i qubit nel circuito di preparazione dello stato iniziale$ V $per applicare questo layout. Se vuoi che i risultati della tua misura rispettino l'ordine iniziale dei qubit, puoi riordinare il registro classico, cioè se qubit 0 è qubit 1 nel circuito finale, memorizza la misura del qubit 1 nel classico bit 0.

Infine, puoi eseguire il circuito. L'unico problema è che potresti aver bisogno di un altro passaggio di transpilation nel caso in cui il layout non sia compatibile con$ V $ ma se $U$ è molto più "complicato" di $V$, dovrebbe andare bene.

Ecco del codice funzionante.

from qiskit import transpile, execute, Aer, IBMQ

from qiskit.circuit import QuantumCircuit, ClassicalRegister
from qiskit.circuit.random import random_circuit

from qiskit.transpiler import PassManager
from qiskit.transpiler.passes.layout import ApplyLayout, SetLayout

from qiskit.quantum_info import Statevector
from qiskit.quantum_info.random import random_unitary

qasm  = Aer.get_backend('qasm_simulator')

IBMQ.load_account()
provider = IBMQ.get_provider(hub='ibm-q')
device = provider.get_backend('ibmq_santiago')

gates = device.configuration().basis_gates
coupling_map = device.configuration().coupling_map

n = 3
n_iter = 2

# init state
V = QuantumCircuit(n)
V.h(0)
for i in range(n - 1):
    V.cx(i, i + 1)
    
# random U
U = random_circuit(n, depth=5)

# full algorithm - no optimization - just for reference
qc = U.repeat(n_iter)
qc.compose(V, front=True, inplace=True)
qc.measure_all()

# transpile U
Utr = transpile(U, 
                basis_gates=gates, 
                coupling_map=coupling_map, 
                optimization_level=3)

layout = Utr._layout
mapping = layout.get_virtual_bits() # a map from virtual to physical qubits

# enforce layout in V
passes_ = [SetLayout(layout), ApplyLayout()]
pm = PassManager(passes_)
Vm = pm.run(V)

# full algorithm
circ = Utr.repeat(n_iter)
circ.compose(Vm, front=True, inplace=True)

# add measurements
cr = ClassicalRegister(n)
circ.add_register(cr)

# re-order classical registers 
for q in U.qubits:
    circ.measure(mapping[q], 
                 cr[q.index])


def simulate(qc):
    return execute(qc, qasm).result().get_counts()

print(simulate(qc))
---
{'000': 484, '001': 17, '010': 6, '011': 47, '100': 10, '110': 19, '111': 441}

print(simulate(circ))
---
{'000': 496, '001': 23, '010': 7, '011': 37, '100': 12, '101': 1, '110': 14, '111': 434}
Max Oct 31 2020 at 01:21

Ho trovato una risposta parziale che condividerò per chiarire qual è il problema e fornire una soluzione hacker.

Alla fine ho capito che il transpiling di un pezzo di circuito (senza una misura) non sembra eseguire il circuito su una versione permutata dei qubit come pensavo, ma piuttosto finisce per eseguire il circuito e poi una permutazione alla fine. Confrontando ciò che accade quando le misurazioni sono presenti, sembra che transpile presuma che le misurazioni siano presenti anche se non lo sono. Quindi consente alla fase di misurazione di "assorbire" alcuni nodi sotto forma di porte di scambio, scambiando semplicemente le mappe di misurazione con cosa. Questo è intelligente quando sono presenti misurazioni, ma quando non lo sono fa sì che il circuito butti via gli scambi alla fine del circuito senza motivo.

Ho anche trovato una strana correzione parziale per questo: sembra che se transpilo una seconda volta dopo aver aggiunto a mano quegli swap mancanti, il secondo transpile non cancellerà erroneamente gli swap e li assorbirà parzialmente nel circuito come volevo. Non sono sicuro del motivo per cui funziona.

EDIT: ecco il codice che dimostra il problema:

qc_foo = QuantumCircuit(n_q, n_q)
qubit_list = [0,1,2]
qc_foo.swap(0,1)
qc_foo.swap(0,2)
qc_foo.barrier()
qc_foo.measure(qubit_list, qubit_list)
qc_foo_trans = transpile(qc_foo, backend=sant, optimization_level=3)
qc_foo_trans.draw()

Si noti che il circuito transpilato ha trasformato lo scambio 0-2 in uno scambio 1-2, assorbendo la differenza nella misurazione. Ora commenta il passaggio di misurazione e riesegui. Lo 0-2 diventa comunque un 1-2, nonostante la mancanza di misura, rendendo errato il circuito transpilato.

Uno sviluppatore mi ha anche confermato che si tratta probabilmente di un bug.