Taglio di circuiti per condizioni al contorno periodiche
Stima di utilizzo: Due minuti su un processore Eagle (NOTA: Questa è solo una stima. Il vostro tempo di esecuzione potrebbe variare.)
Contesto​
In questo notebook, consideriamo la simulazione di una catena periodica di qubit dove è presente un'operazione a due qubit tra ogni coppia di qubit adiacenti, inclusi 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 alcune catene periodiche direttamente sulla topologia dove il primo e l'ultimo qubit sono vicini. Tuttavia, per problemi sufficientemente grandi, il primo e l'ultimo qubit possono essere molto distanti, richiedendo quindi molte porte SWAP per l'operazione a 2 qubit tra questi due qubit. Un tale problema di contorno periodico è stato studiato in questo articolo.
In questo notebook mostriamo l'utilizzo del taglio di circuiti per gestire un tale problema di catena periodica su scala utility dove il primo e l'ultimo qubit non sono vicini. Tagliare questa connettività a lungo raggio evita le porte SWAP extra al costo di eseguire multiple istanze del circuito e un certo post-processing classico. In sintesi, il taglio può essere incorporato per calcolare logicamente le operazioni a 2 qubit a lunga distanza. In altre parole, questo approccio porta ad un aumento effettivo nella connettività della mappa di accoppiamento, portando quindi ad un minor numero di porte SWAP.
Notate che esistono due tipi di tagli - tagliare il filo di un circuito (chiamato wire cutting), o sostituire una porta a 2 qubit con multiple operazioni a singolo qubit (chiamato gate cutting). In questo notebook, ci concentreremo sul gate cutting. Per maggiori dettagli sul gate cutting, fate riferimento ai materiali esplicativi in qiskit-addon-cutting, e ai riferimenti corrispondenti. Per maggiori dettagli sul wire cutting, fate riferimento al tutorial Wire cutting for expectation values estimation, o ai tutorial in qiskit-addon-cutting.
Requisiti​
Prima di iniziare questo tutorial, assicuratevi di avere installato quanto segue:
- Qiskit SDK v1.2 o successiva (
pip install qiskit) - Qiskit Runtime v0.3 o successiva (
pip install qiskit-ibm-runtime) - circuito cutting Qiskit addon v.9.0 o successiva (
pip install qiskit-addon-cutting)
Configurazione​
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
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, Batch
Passo 1: Mappare gli input classici a un problema quantistico​
Qui, genereremo un circuito TwoLocal e definiremo alcune osservabili.
- Input: Parametri per creare un circuito
- Output: Circuito astratto e osservabili
Consideriamo una entangler map hardware-efficient per il circuito TwoLocal con connettività periodica tra l'ultimo e il primo qubit della entangler map. Questa interazione a lungo raggio può portare a porte SWAP extra durante la transpilazione, aumentando quindi la profondità del circuito.
Selezionare il backend e il layout iniziale​
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)
Per questo notebook considereremo una catena 1D periodica di 109 qubit, che è la catena 1D più lunga nella topologia di un dispositivo IBM Quantum a 127 qubit. Non è possibile disporre una catena periodica di 109 qubit su un dispositivo a 127 qubit in modo tale che il primo e l'ultimo qubit siano vicini senza incorporare porte SWAP extra.
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_qubits
109
Costruire l'entangler map per il circuito 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 connectivity
Il circuito TwoLocal consente la ripetizione dei rotation_blocks e della entangler map multiple volte. Per questo caso, il numero di ripetizioni determina il numero di porte periodiche che devono essere tagliate. Poiché l'overhead di campionamento aumenta esponenzialmente con il numero di tagli (fate riferimento al tutorial Wire cutting for expectation values estimation per maggiori dettagli), fisseremo il numero di ripetizioni a 2 in questo notebook.
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)

Per verificare la qualità del risultato utilizzando il taglio di circuiti, dobbiamo conoscere il risultato ideale. Il circuito attuale scelto è oltre la simulazione classica brute force. Pertanto, fissiamo i parametri del circuito con attenzione per renderlo clifford.
Assegneremo il valore del parametro per i primi due strati di porte Rx, e il valore per l'ultimo strato. Questo garantisce che il risultato ideale di questo circuito sia , essendo il numero di qubit. Pertanto, i valori di aspettazione 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)
Selezionare le osservabili​
Per quantificare i benefici del gate cutting misuriamo i valori di aspettazione delle osservabili e . Come discusso in precedenza, i valori di aspettazione 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.coeffs
Step 2: Ottimizzare il problema per l'esecuzione su hardware quantistico​
- Input: Circuito astratto e osservabili
- Output: Circuito target e osservabili prodotti dal taglio dei gate a lungo raggio
Trasporre il circuito​
Notate che il circuito può essere trasposto in questa fase, oppure dopo il taglio. Se trasponiamo dopo il taglio, ciò richiederà di trasporre ciascuno dei sottoesperimenti generati a causa dell'overhead di campionamento. Pertanto, è più prudente trasporre in questa fase per ridurre l'overhead della trasposizione.
Tuttavia, se la trasposizione viene effettuata in questa fase con la connettività nativa dell'hardware, il transpiler aggiungerà molteplici gate SWAP per posizionare l'operazione periodica a 2 qubit – oscurando i vantaggi del taglio del circuito. Per evitare questo problema possiamo sfruttare il fatto che conosciamo esattamente i gate che devono essere tagliati. Nello specifico, possiamo creare una mappa di accoppiamento virtuale aggiungendo connessioni virtuali tra qubit distanti per accogliere questi gate periodici a 2 qubit. Ciò garantirà che il circuito possa essere trasposto in questa fase senza incorporare i gate SWAP aggiuntivi.
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)

Tagliare le connettività periodiche a lungo raggio​
Ora tagliamo i gate nel circuito trasposto. Notate che i gate a 2 qubit che devono essere tagliati sono quelli 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 il layout del circuito trasposto all'osservabile.
trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)
Infine i sottoesperimenti vengono generati campionando diverse basi di misurazione e 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,
)
Notate che il taglio delle interazioni a lungo raggio porta all'esecuzione di campioni multipli del circuito che differiscono nelle basi di misurazione e preparazione. Maggiori informazioni a riguardo possono essere trovate in Constructing a virtual two-qubit gate by sampling single-qubit operations e Cutting circuits with multiple two-qubit unitaries.
Il numero di gate periodici da tagliare è uguale al numero di ripetizioni del layer TwoLocal, definito come num_reps sopra. L'overhead di campionamento del taglio del gate è 6. Pertanto, il numero totale di sottoesperimenti sarà .
print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2
Trasporre i sottoesperimenti​
A questo punto, i sottoesperimenti contengono circuiti con alcuni gate a 1 qubit che non sono nel set di gate di base. Questo perché i qubit tagliati vengono misurati in basi diverse, e i gate di rotazione utilizzati per questo non appartengono necessariamente al set di gate di base. Ad esempio, la misurazione in base X implica l'applicazione di un gate Hadamard prima della solita misurazione in base Z. Ma Hadamard non fa parte del set di gate di base.
Invece di applicare l'intero processo di trasposizione su ciascuno dei circuiti nei sottoesperimenti, possiamo utilizzare passaggi di trasposizione specifici. Fate riferimento a questa documentazione per una descrizione dettagliata di tutti i passaggi di trasposizione disponibili.
Applicheremo i passaggi BasisTranslator e poi Optimize1qGatesDecomposition per garantire che tutti i gate in questi circuiti appartengano al set di gate di base. L'utilizzo di questi due passaggi è più veloce dell'intero processo di trasposizione, poiché altri passaggi come il routing e la selezione del layout iniziale non vengono eseguiti nuovamente.
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
]
)
Step 3: Eseguire utilizzando le primitive Qiskit​
- Input: Circuiti target
- Output: Distribuzioni di quasi-probabilitÃ
Utilizziamo un primitivo SamplerV2 per l'esecuzione dei circuiti tagliati. Disabilitiamo dynamical decoupling e twirling in modo che qualsiasi miglioramento che otteniamo nel risultato sarà dovuto esclusivamente all'applicazione efficace del taglio del gate 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 = False
Ora sottometteremo i job 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()}")
Job ID cwxf7wq60bqg008pvt8g
result = cut_job.result()
Step 4: Post-elaborare e restituire il risultato nel formato classico desiderato​
- Input: Distribuzioni di quasi-probabilitÃ
- Output: Valori di aspettazione ricostruiti
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)
Ora calcoliamo la media degli osservabili di tipo Z di peso-1 e peso-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}")
Average of weight-1 expectation values is -0.741733944954063
Average of weight-2 expectation values is 0.6968862385320495
Verifica incrociata: Ottenere il valore di aspettazione non tagliato​
È utile verificare in modo incrociato il vantaggio della tecnica di taglio del circuito rispetto al non tagliato. Qui calcoleremo i valori di aspettazione senza tagliare il circuito. Notate che un tale circuito non tagliato soffrirà di un gran numero di gate SWAP necessari 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 aspettazione del circuito non tagliato dopo aver ottenuto la distribuzione di probabilità tramite SamplerV2. Ciò consente un utilizzo omogeneo del primitivo su tutte le istanze. Tuttavia, notate che avremmo potuto utilizzare anche EstimatorV2 per calcolare direttamente i valori di aspettazione.
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}")
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 di aspettazione medi di tutti gli osservabili di tipo Z di peso-1 e peso-2 senza taglio.
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}")
Average of weight-1 expectation values is -0.32494128440366965
Average of weight-2 expectation values is 0.32340917431192656
Visualizzare​
Visualizziamo ora il miglioramento ottenuto per gli osservabili di peso-1 e peso-2 quando si utilizza il taglio del 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()

Riepilogo​
In sintesi, abbiamo calcolato i valori di aspettazione medi degli osservabili di tipo Z di peso-1 e peso-2 per una catena 1D periodica di 109 qubit. Per fare ciò, abbiamo
- creato una mappa di accoppiamento virtuale aggiungendo una connettività a lungo raggio tra il primo e l'ultimo qubit della catena 1D, e trasposto il circuito.
- la trasposizione in questa fase ci ha permesso di evitare l'overhead di trasporre ciascun sottoesperimento separatamente dopo il taglio,
- l'utilizzo della mappa di accoppiamento virtuale ci ha permesso di evitare gate SWAP extra per l'operazione a 2 qubit tra il primo e l'ultimo qubit.
- rimosso la connettività a lungo raggio dal circuito trasposto tramite taglio del gate.
- convertito i circuiti tagliati nel set di gate di base applicando i passaggi di trasposizione appropriati.
- eseguito i circuiti tagliati su dispositivo IBM Quantum utilizzando un primitivo
SamplerV2. - ottenuto il valore di aspettazione ricostruendo i risultati dei circuiti tagliati.
Inferenza​
Notiamo dai risultati che la media degli osservabili di tipo di peso-1 e di peso-2 vengono significativamente migliorati tagliando i gate periodici. Notate che questo studio non include alcuna tecnica di soppressione o mitigazione degli errori. Il miglioramento osservato è dovuto esclusivamente all'uso appropriato del taglio del gate 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 del gate per migliorare le prestazioni del calcolo.
Sondaggio del tutorial​
Per favore, partecipate a questo breve sondaggio per fornire feedback su questo tutorial. Le vostre opinioni ci aiuteranno a migliorare la nostra offerta di contenuti e l'esperienza utente.