Taglio dei circuiti per condizioni al contorno periodiche
Stima di utilizzo: Due minuti con un processore Eagle (NOTA: si tratta solo di una stima). Il tempo di esecuzione potrebbe variare)
Sfondo
In questo quaderno consideriamo la simulazione di una catena periodica di qubit in cui vi sono due operazioni di qubit tra ogni due qubit adiacenti, compresi il primo e l'ultimo. Le catene periodiche si trovano spesso in problemi di fisica e chimica, come i modelli di Ising e la simulazione molecolare.
Gli attuali dispositivi IBM Quantum® sono planari. È possibile incorporare direttamente nella topologia alcune catene periodiche in cui il primo e l'ultimo qubit sono vicini. Tuttavia, per problemi abbastanza grandi, il primo e l'ultimo qubit possono essere molto distanti tra loro, richiedendo quindi molte porte SWAP per l'operazione a 2 qubit tra questi due qubit. Un tale problema di confine periodico è stato studiato in questo documento.
In questo quaderno mostriamo l'uso del taglio dei circuiti per affrontare un problema di catena periodica su scala di utilità in cui il primo e l'ultimo qubit non sono vicini. L'eliminazione di questa connettività a lungo raggio evita le porte SWAP aggiuntive, al costo di eseguire più istanze del circuito e di effettuare alcune classiche post-elaborazioni. In sintesi, il taglio può essere incorporato per calcolare logicamente le operazioni a 2 qubit a lunga distanza. In altre parole, questo approccio porta a un effettivo aumento della connettività della mappa di accoppiamento, con conseguente riduzione del numero di porte SWAP.
Si noti che esistono due tipi di tagli: tagliare il filo di un circuito (chiamato wire cutting), o sostituire un gate a 2 qubit con operazioni multiple a singolo qubit (chiamato gate cutting). In questo quaderno ci concentreremo sul taglio dei cancelli. Per maggiori dettagli sul taglio delle porte, consultare il sito materiale esplicativo in qiskit-addon-cutting e i relativi riferimenti. Per ulteriori dettagli sul taglio dei fili, consultare l'esercitazione Taglio dei fili per la stima dei valori di aspettativa o le esercitazioni in qiskit-addon-taglio.
Requisiti
Prima di iniziare questa esercitazione, assicuratevi di aver installato quanto segue:
- Qiskit SDK v1.2 o successivamente (
pip install qiskit) - Qiskit Runtime v0.3 o successivamente (
pip install qiskit-ibm-runtime) - Taglio dei circuiti Add-on Qiskit v.9.0 e o successive (
pip install qiskit-addon-cutting)
Configura
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
from qiskit.transpiler import PassManager
from qiskit.transpiler.passes import (
BasisTranslator,
Optimize1qGatesDecomposition,
)
from qiskit.circuit.equivalence_library import (
SessionEquivalenceLibrary as sel,
)
from qiskit.converters import circuit_to_dag, dag_to_circuit
from qiskit.result import sampled_expectation_value
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import TwoLocal
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, SamplerOptions, BatchFase 1: mappare gli input classici su un problema quantistico
Qui genereremo un circuito TwoLocal e definiremo alcuni osservabili.
- Ingresso: Parametri per creare un circuito
- Uscita: Circuito astratto e osservabili
Consideriamo un entangler map efficiente dal punto di vista hardware per il circuito TwoLocal con connettività periodica tra l'ultimo e il primo qubit di entangler map. Questa interazione a lungo raggio può portare a porte SWAP aggiuntive durante la transpilazione, aumentando così la profondità del circuito.
Seleziona il backend e il layout iniziale
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)Per questo quaderno considereremo una catena 1D periodica di 109 qubit, che è la catena 1D più lunga nella topologia di un dispositivo quantistico a 127 qubit IBM. Non è possibile disporre una catena periodica di 109 qubit su un dispositivo da 127 qubit in modo che il primo e l'ultimo qubit siano vicini senza incorporare ulteriori porte SWAP.
init_layout = [
13,
12,
11,
10,
9,
8,
7,
6,
5,
4,
3,
2,
1,
0,
14,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
31,
32,
36,
51,
50,
49,
48,
47,
46,
45,
44,
43,
42,
41,
40,
39,
38,
37,
52,
56,
57,
58,
59,
60,
61,
62,
63,
64,
65,
66,
67,
68,
69,
70,
74,
89,
88,
87,
86,
85,
84,
83,
82,
81,
80,
79,
78,
77,
76,
75,
90,
94,
95,
96,
97,
98,
99,
100,
101,
102,
103,
104,
105,
106,
107,
108,
112,
126,
125,
124,
123,
122,
121,
120,
119,
118,
117,
116,
115,
114,
113,
]
# the number of qubits in the circuit is governed by the length of the initial layout
num_qubits = len(init_layout)
num_qubitsOutput:
109
Costruisci la mappa dell'entangler per il circuito dell' TwoLocal
coupling_map = [(i, i + 1) for i in range(0, len(init_layout) - 1)]
coupling_map.append(
(len(init_layout) - 1, 0)
) # adding in the periodic connectivityTwoLocal il circuito permette di ripetere più volte rotation_blocks e entangler map . In questo caso, il numero di ripetizioni determina il numero di porte periodiche da tagliare. Poiché l'overhead del campionamento aumenta esponenzialmente con il numero di tagli (per maggiori dettagli si rimanda al tutorial Wire cutting for expectation values estimation ), in questo quaderno fisseremo a 2 il numero di ripetizioni.
num_reps = 2
entangler_map = []
for even_edge in coupling_map[0 : len(coupling_map) : 2]:
entangler_map.append(even_edge)
for odd_edge in coupling_map[1 : len(coupling_map) : 2]:
entangler_map.append(odd_edge)ansatz = TwoLocal(
num_qubits=num_qubits,
rotation_blocks="rx",
entanglement_blocks="cx",
entanglement=entangler_map,
reps=num_reps,
).decompose()
ansatz.draw("mpl", fold=-1)Output:
Per verificare la qualità del risultato utilizzando il taglio dei circuiti, è necessario conoscere il risultato ideale. L'attuale circuito di scelta va oltre la simulazione classica a forza bruta. Pertanto, fissiamo con cura i parametri del circuito per renderlo clifford.
Assegneremo il valore del parametro per i primi due strati di porte Rx e il valore per l'ultimo strato. Ciò garantisce che il risultato ideale di questo circuito sia , essendo il numero di qubit. Pertanto, i valori di aspettativa di e , dove è l'indice del qubit, sono rispettivamente e .
params_last_layer = [np.pi] * ansatz.num_qubits
params = [0] * (ansatz.num_parameters - ansatz.num_qubits)
params.extend(params_last_layer)
ansatz.assign_parameters(params, inplace=True)Seleziona osservabili
Per quantificare i benefici del taglio dei cancelli, misuriamo i valori di aspettativa delle osservabili e . Come discusso in precedenza, i valori di aspettativa ideali sono rispettivamente e .
observables = []
for i in range(num_qubits):
obs = "I" * (i) + "Z" + "I" * (num_qubits - i - 1)
observables.append(obs)
for i in range(num_qubits):
if i == num_qubits - 1:
obs = "Z" + "I" * (num_qubits - 2) + "Z"
else:
obs = "I" * i + "ZZ" + "I" * (num_qubits - i - 2)
observables.append(obs)
observables = SparsePauliOp(observables)
paulis = observables.paulis
coeffs = observables.coeffsFase 2: Ottimizzazione del problema per l'esecuzione su hardware quantistico
- Ingresso: Circuito astratto e osservabili
- Output: Circuito target e osservabili prodotti dal taglio delle porte a lunga distanza
Traspila il circuito
Si noti che il circuito può essere transpilato in questa fase o dopo il taglio. Se effettuiamo la trasposizione dopo il taglio, dovremo trasporre tutti i sottoesperimenti generati a causa del sovraccarico di campionamento. Pertanto, è più prudente transpilare in questa fase per ridurre l'overhead della transpilatura.
Tuttavia, se la transpilazione viene eseguita in questa fase con la connettività hardware nativa, il transpiler aggiungerà più porte SWAP per posizionare l'operazione periodica a 2 qubit, offuscando i vantaggi del taglio dei circuiti. Per evitare questo problema possiamo fare leva sul fatto che conosciamo esattamente le porte che devono essere tagliate. In particolare, possiamo creare una mappa di accoppiamento virtuale aggiungendo connessioni virtuali tra qubit lontani per ospitare queste porte periodiche a 2 qubit. In questo modo, il circuito può essere transpilato in questa fase senza incorporare le porte SWAP aggiuntive.
coupling_map = backend.configuration().coupling_map
# create a virtual coupling map with long range connectivity
virtual_coupling_map = coupling_map.copy()
virtual_coupling_map.append([init_layout[-1], init_layout[0]])
virtual_coupling_map.append([init_layout[0], init_layout[-1]])pm_virtual = generate_preset_pass_manager(
optimization_level=1,
coupling_map=virtual_coupling_map,
initial_layout=init_layout,
basis_gates=backend.configuration().basis_gates,
)
virtual_mapped_circuit = pm_virtual.run(ansatz)
virtual_mapped_circuit.draw("mpl", fold=-1, idle_wires=False)Output:
Taglia le connettività periodiche a lungo raggio
Ora tagliamo le porte del circuito transpilato. Si noti che le porte a 2 qubit che devono essere tagliate sono quelle che collegano l'ultimo e il primo qubit del layout.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(virtual_mapped_circuit.data)
if {virtual_mapped_circuit.find_bit(q)[0] for q in instruction.qubits}
== {init_layout[-1], init_layout[0]}
]Applicheremo la disposizione del circuito transpilato all'osservabile.
trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)Infine, i sottoesperimenti vengono generati campionando su diverse basi di misura e di preparazione.
qpd_circuit, bases = cut_gates(virtual_mapped_circuit, cut_indices)
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit,
observables=trans_observables.paulis,
num_samples=np.inf,
)Si noti che il taglio delle interazioni a lungo raggio porta all'esecuzione di più campioni del circuito che differiscono nelle basi di misura e di preparazione. Ulteriori informazioni al riguardo sono disponibili in Costruire un gate virtuale a due-qubit campionando operazioni a singolo-qubit e Circuiti di taglio con unitari multipli a due quit.
Il numero di porte periodiche da tagliare è pari al numero di ripetizioni dello strato TwoLocal , definito come num_reps sopra. L'overhead di campionamento del taglio del cancello è pari a 6. Pertanto, il numero totale di sottoesperimenti sarà .
print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")Output:
Number of subexperiments is 36 = 6**2
Traspila i sottoesperimenti
A questo punto, i sottoesperimenti contengono circuiti con alcune porte a 1 qubit che non sono presenti nell'insieme delle porte di base. Questo perché i qubit tagliati sono misurati in basi diverse e le porte di rotazione utilizzate per questo non appartengono necessariamente all'insieme delle porte di base. Ad esempio, la misura in base X implica l'applicazione di un gate di Hadamard prima della consueta misura in base Z. Ma Hadamard non fa parte dell'insieme delle porte di base.
Invece di applicare l'intero processo di transpilazione a ciascuno dei circuiti dei sottoesperimenti, possiamo utilizzare passaggi di transpilazione specifici. Per una descrizione dettagliata di tutti i passaggi di transpilazione disponibili, consultare il sito questa documentazione.
Applicheremo i passaggi BasisTranslator e poi Optimize1qGatesDecomposition per garantire che tutte le porte di questi circuiti appartengano all'insieme di porte di base. L'uso di questi due passaggi è più veloce dell'intero processo di transpilazione, poiché non vengono eseguite nuovamente altre fasi come l'instradamento e la selezione del layout iniziale.
pass_ = PassManager(
[Optimize1qGatesDecomposition(basis=backend.configuration().basis_gates)]
)
subexperiments = pass_.run(
[
dag_to_circuit(
BasisTranslator(sel, target_basis=backend.basis_gates).run(
circuit_to_dag(circ)
)
)
for circ in subexperiments
]
)Passaggio 3: eseguire utilizzando Qiskit primitives
- Ingresso: Circuiti target
- Output: Distribuzioni di quasi-probabilità
Per l'esecuzione dei circuiti di taglio utilizziamo la primitiva SamplerV2 . Disabilitiamo dynamical decoupling e twirling in modo che qualsiasi miglioramento ottenuto nel risultato sia dovuto esclusivamente all'applicazione efficace del taglio delle porte per questo tipo di circuito.
options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = FalseOra invieremo i lavori utilizzando la modalità batch.
with Batch(backend=backend) as batch:
sampler = SamplerV2(options=options)
cut_job = sampler.run(subexperiments)
print(f"Job ID {cut_job.job_id()}")Output:
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()Fase 4: Post-elaborazione e restituzione del risultato nel formato classico desiderato
- Input: Distribuzioni di quasi-probabilità
- Output: Valori di aspettativa ricostruiti
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)Calcoliamo ora la media delle osservabili di tipo Z di weight-1 e weight-2.
cut_weight_1 = np.mean(reconstructed_expvals[:num_qubits])
cut_weight_2 = np.mean(reconstructed_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {cut_weight_1}")
print(f"Average of weight-2 expectation values is {cut_weight_2}")Output:
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495
Verifica incrociata: ottenere il valore atteso non ridotto
È utile verificare in modo incrociato il vantaggio della tecnica di taglio del circuito rispetto a quella non tagliata. Qui calcoleremo i valori di aspettativa senza tagliare il circuito. Si noti che un tale circuito non tagliato soffrirà di un gran numero di porte SWAP necessarie per implementare l'operazione a 2 qubit tra il primo e l'ultimo qubit. Utilizzeremo la funzione sampled_expectation_value per ottenere i valori di aspettativa del circuito non tagliato dopo aver ottenuto la distribuzione di probabilità tramite SamplerV2. Ciò consente un uso omogeneo della primitiva su tutte le istanze. Tuttavia, si noti che avremmo potuto utilizzare anche EstimatorV2 per calcolare direttamente i valori di aspettativa.
if ansatz.num_clbits == 0:
ansatz.measure_all()
pm_uncut = generate_preset_pass_manager(
optimization_level=1, backend=backend, initial_layout=init_layout
)
transpiled_circuit = pm_uncut.run(ansatz)sampler = SamplerV2(mode=backend, options=options)
uncut_job = sampler.run([transpiled_circuit])uncut_job_id = uncut_job.job_id()
print(f"The job id for the uncut clifford circuit is {uncut_job_id}")Output:
The job id for the uncut clifford circuit is cwxfads2ac5g008jhe7g
uncut_result = uncut_job.result()[0]
uncut_counts = uncut_result.data.meas.get_counts()Ora calcoleremo i valori medi di aspettativa di tutte le osservabili di tipo weight-1 e weight-2 Z senza tagli.
uncut_expvals = [
sampled_expectation_value(uncut_counts, obs) for obs in paulis
]
uncut_weight_1 = np.mean(uncut_expvals[:num_qubits])
uncut_weight_2 = np.mean(uncut_expvals[num_qubits:])
print(f"Average of weight-1 expectation values is {uncut_weight_1}")
print(f"Average of weight-2 expectation values is {uncut_weight_2}")Output:
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656
Visualizza
Visualizziamo ora i miglioramenti ottenuti per le osservabili weight-1 e weight-2 quando si utilizza il taglio dei gate per il circuito a catena periodica
mpl.rcParams.update(mpl.rcParamsDefault)
fig = plt.subplots(figsize=(12, 8), dpi=200)
width = 0.25
labels = ["Weight-1", "Weight-2"]
x = np.arange(len(labels))
ideal = [-1, 1]
cut = [cut_weight_1, cut_weight_2]
uncut = [uncut_weight_1, uncut_weight_2]
br1 = np.arange(len(ideal))
br2 = [x + width for x in br1]
br3 = [x + width for x in br2]
plt.bar(
br1, ideal, width=width, edgecolor="k", label="Ideal", color="#4589ff"
)
plt.bar(br2, cut, width=width, edgecolor="k", label="Cut", color="#a56eff")
plt.bar(
br3, uncut, width=width, edgecolor="k", label="Uncut", color="#009d9a"
)
plt.axhline(y=0, color="k", linestyle="-")
plt.xticks([r + width for r in range(len(ideal))], labels, fontsize=14)
plt.yticks(fontsize=14)
plt.legend(fontsize=14)
plt.show()Output:
Riepilogo
In sintesi, abbiamo calcolato i valori medi di aspettativa delle osservabili dei tipi Z di weight-1 e weight-2 per una catena periodica 1D di 109 qubit. A tal fine, abbiamo
- ha creato una mappa di accoppiamento virtuale aggiungendo una connettività a lungo raggio tra il primo e l'ultimo qubit della catena 1D e ha trasposto il circuito.
- la trasposizione in questa fase ci ha permesso di evitare l'onere di trasporre separatamente ogni sottoesperimento dopo il taglio,
- l'utilizzo della mappa di accoppiamento virtuale ci ha permesso di evitare porte SWAP aggiuntive per l'operazione a 2 qubit tra il primo e l'ultimo qubit.
- ha rimosso la connettività a lungo raggio dal circuito transpilato tramite il taglio del gate.
- convertire i circuiti tagliati in set di porte di base applicando gli opportuni passaggi di transpilazione.
- ha eseguito i circuiti di taglio sul dispositivo IBM Quantum utilizzando una primitiva
SamplerV2. - ottenendo il valore di aspettativa attraverso la ricostruzione degli esiti dei circuiti tagliati.
Inferenza
Dai risultati si nota che la media delle osservabili di tipo weight-1 e weight-2 migliora significativamente tagliando le porte periodiche. Si noti che questo studio non include alcuna tecnica di soppressione o attenuazione degli errori. Il miglioramento osservato è dovuto esclusivamente all'uso corretto del taglio del cancello per questo problema. I risultati avrebbero potuto essere ulteriormente migliorati utilizzando le tecniche di mitigazione e soppressione.
Questo studio mostra un esempio di utilizzo efficace del taglio delle porte per migliorare le prestazioni del calcolo.
Sondaggio tutorial
Vi invitiamo a partecipare a questo breve sondaggio per fornire un feedback su questa esercitazione. Le vostre indicazioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza degli utenti.