Sari la conținutul principal

Tăierea circuitelor pentru condiții periodice la margine

Estimare de utilizare: Două minute pe un procesor Eagle (NOTĂ: Aceasta este doar o estimare. Timpul tău de rulare poate varia.)

Fundal

În acest notebook, analizăm simularea unui lanț periodic de qubiți în care există o operație pe doi qubiți între fiecare doi qubiți adiacenți, inclusiv primul și ultimul. Lanțurile periodice apar frecvent în probleme de fizică și chimie, cum ar fi modelele Ising și simulările moleculare.

Dispozitivele IBM Quantum® actuale sunt planare. Este posibil să integrezi unele lanțuri periodice direct în topologie, astfel încât primul și ultimul qubit să fie vecini. Totuși, pentru probleme suficient de mari, primul și ultimul qubit pot fi foarte departe unul de celălalt, necesitând astfel multe porți SWAP pentru operația cu 2 qubiți dintre acești doi qubiți. Această problemă cu condiții periodice la margine a fost studiată în această lucrare.

În acest notebook prezentăm utilizarea tăierii circuitelor pentru a rezolva o astfel de problemă de lanț periodic la scară de utilitate, unde primul și ultimul qubit nu sunt vecini. Tăierea acestei conectivități de lungă distanță evită porțile SWAP suplimentare, cu prețul executării mai multor instanțe ale circuitului și al unor post-procesări clasice. Pe scurt, tăierea poate fi incorporată pentru a calcula logic operațiile cu 2 qubiți pe distanțe mari. Cu alte cuvinte, această abordare conduce la o creștere efectivă a conectivității hărții de cuplare, reducând astfel numărul de porți SWAP necesare.

Notează că există două tipuri de tăieri: tăierea firului unui circuit (numită wire cutting) sau înlocuirea unei porți cu 2 qubiți cu mai multe operații pe un singur qubit (numită gate cutting). În acest notebook ne vom concentra pe gate cutting. Pentru mai multe detalii despre gate cutting, consultă materialele explicative din qiskit-addon-cutting și referințele corespunzătoare. Pentru mai multe detalii despre wire cutting, consultă tutorialul Wire cutting pentru estimarea valorilor de așteptare sau tutorialele din qiskit-addon-cutting.

Cerințe

Înainte de a începe acest tutorial, asigură-te că ai instalat următoarele:

  • Qiskit SDK v1.2 sau o versiune mai recentă (pip install qiskit)
  • Qiskit Runtime v0.3 sau o versiune mai recentă (pip install qiskit-ibm-runtime)
  • Circuit cutting Qiskit addon v.9.0 sau o versiune mai recentă (pip install qiskit-addon-cutting)

Configurare

# 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

Pasul 1: Maparea intrărilor clasice la o problemă cuantică

Aici vom genera un Circuit TwoLocal și vom defini câteva observabile.

  • Intrare: Parametri pentru crearea unui circuit
  • Ieșire: Circuit abstract și observabile

Considerăm o hartă de conectare eficientă hardware pentru Circuit-ul TwoLocal cu conectivitate periodică între ultimul și primul qubit din harta de conectare. Această interacțiune de lungă distanță poate duce la porți SWAP suplimentare în timpul transpilării, crescând astfel adâncimea circuitului.

Selectarea Backend-ului și a layoutului inițial

service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=127
)

Pentru acest notebook vom considera un lanț 1D periodic de 109 qubiți, care este cel mai lung lanț 1D din topologia unui dispozitiv IBM Quantum de 127 de qubiți. Nu este posibil să aranjezi un lanț periodic de 109 qubiți pe un dispozitiv de 127 de qubiți astfel încât primul și ultimul qubit să fie vecini fără a introduce porți SWAP suplimentare.

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

Construirea hărții de conectare pentru Circuit-ul 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

Circuit-ul TwoLocal permite repetarea blocurilor de rotație și a hărții de conectare de mai multe ori. În acest caz, numărul de repetiții determină numărul de porți periodice care trebuie tăiate. Deoarece supraîncărcarea de eșantionare crește exponențial cu numărul de tăieri (consultă tutorialul Wire cutting pentru estimarea valorilor de așteptare pentru mai multe detalii), vom fixa numărul de repetiții la 2 în acest 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)

Output of the previous code cell

Pentru a verifica calitatea rezultatului folosind tăierea circuitelor, trebuie să cunoaștem rezultatul ideal. Circuit-ul ales în prezent depășește capacitatea simulării clasice prin forță brută. Prin urmare, fixăm parametrii circuitului cu atenție pentru a-l face clifford.

Vom atribui valoarea de parametru 00 pentru primele două straturi de porți Rx și valoarea π\pi pentru ultimul strat. Aceasta asigură că rezultatul ideal al acestui circuit este 1n|1\rangle^{\otimes n}, unde nn este numărul de qubiți. Prin urmare, valorile de așteptare ale Zi\langle Z_i \rangle și ZiZi+1\langle Z_i Z_{i+1} \rangle, unde ii este indicele qubitului, sunt 1-1 și respectiv +1+1.

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)

Selectarea observabilelor

Pentru a cuantifica beneficiile gate cutting, măsurăm valorile de așteptare ale observabilelor 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle și 1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. Așa cum s-a discutat anterior, valorile ideale de așteptare sunt 1-1 și respectiv +1+1.

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

Pasul 2: Optimizarea problemei pentru execuția pe hardware cuantic

  • Intrare: Circuit abstract și observabile
  • Ieșire: Circuit țintă și observabile produse prin tăierea porților cu rază lungă

Transpilarea circuitului

Reține că circuitul poate fi transpilat în această etapă sau după tăiere. Dacă transpilăm după tăiere, va fi necesară transpilarea fiecăruia dintre sub-experimentele generate din cauza supracostului de eșantionare. Prin urmare, este mai prudent să transpilăm în această etapă pentru a reduce supracostul transpilării.

Totuși, dacă transpilarea se face în această etapă cu conectivitatea nativă a hardware-ului, transpilerul va adăuga mai multe porți SWAP pentru a plasa operația periodică pe 2 qubiți — ascunzând beneficiile tăierii circuitului. Pentru a evita această problemă, putem folosi faptul că știm exact care porți trebuie tăiate. Mai concret, putem crea o hartă de cuplare virtuală adăugând conexiuni virtuale între qubiții îndepărtați pentru a acomoda aceste porți periodice pe 2 qubiți. Astfel, circuitul poate fi transpilat în această etapă fără a incorpora porțile SWAP suplimentare.

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 of the previous code cell

Tăierea conectivităților periodice cu rază lungă

Acum tăiem porțile din circuitul transpilat. Reține că porțile pe 2 qubiți care trebuie tăiate sunt cele care conectează ultimii și primii qubiți din 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]}
]

Vom aplica layout-ul circuitului transpilat asupra observabilului.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

În final, sub-experimentele sunt generate prin eșantionarea diferitelor baze de măsurare și pregătire.

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,
)

Reține că tăierea interacțiunilor cu rază lungă duce la execuția mai multor eșantioane de circuit care diferă în bazele de măsurare și pregătire. Mai multe informații despre acest lucru pot fi găsite în Constructing a virtual two-qubit gate by sampling single-qubit operations și Cutting circuits with multiple two-qubit unitaries.

Numărul de porți periodice de tăiat este egal cu numărul de repetări ale stratului TwoLocal, definit ca num_reps mai sus. Supracostul de eșantionare al tăierii porților este 6. Prin urmare, numărul total de sub-experimente va fi 6num_reps6^{num\_reps}.

print(f"Number of subexperiments is {len(subexperiments)} = 6**{num_reps}")
Number of subexperiments is 36 = 6**2

Transpilarea sub-experimentelor

În acest moment, sub-experimentele conțin circuite cu unele porți pe 1 qubit care nu fac parte din setul de porți de bază. Aceasta se datorează faptului că qubiții tăiați sunt măsurați în baze diferite, iar porțile de rotație folosite în acest scop nu aparțin neapărat setului de porți de bază. De exemplu, măsurarea în baza X implică aplicarea unei porți Hadamard înainte de măsurarea obișnuită în baza Z. Însă Hadamard nu face parte din setul de porți de bază.

În loc să aplicăm întregul proces de transpilare pe fiecare circuit din sub-experimente, putem folosi pași de transpilare specifici. Consultă această documentație pentru o descriere detaliată a tuturor pașilor de transpilare disponibili.

Vom aplica pasele BasisTranslator și apoi Optimize1qGatesDecomposition pentru a ne asigura că toate porțile din aceste circuite aparțin setului de porți de bază. Utilizarea acestor două pase este mai rapidă decât întregul proces de transpilare, deoarece alți pași, cum ar fi rutarea și selectarea layout-ului inițial, nu sunt efectuați din nou.

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
]
)

Pasul 3: Executarea cu primitive Qiskit

  • Intrare: Circuite țintă
  • Ieșire: Distribuții de cvasiprobabiitate

Folosim o primitivă SamplerV2 pentru execuția circuitelor tăiate. Dezactivăm dynamical decoupling și twirling astfel încât orice îmbunătățire obținută în rezultate să fie exclusiv datorată aplicării eficiente a tăierii porților pentru acest tip de circuit.

options = SamplerOptions()
options.default_shots = 10000
options.dynamical_decoupling.enable = False
options.twirling.enable_gates = False
options.twirling.enable_measure = False

Acum vom trimite job-urile folosind modul 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()

Pasul 4: Post-procesare și returnarea rezultatului în formatul clasic dorit

  • Intrare: Distribuții de cvasi-probabilitate
  • Ieșire: Valori de așteptare reconstruite
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

Acum calculăm media observabilelor de tip Z cu pondere 1 și pondere 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

Verificare încrucișată: Obținerea valorii de așteptare netăiate

Este util să verificăm încrucișat avantajul tehnicii de tăiere a circuitului față de varianta netăiată. Aici vom calcula valorile de așteptare fără a tăia circuitul. Reține că un astfel de circuit netăiat va suferi din cauza unui număr mare de porți SWAP necesare pentru a implementa operația pe 2 qubiți între primul și ultimul qubit. Vom folosi funcția sampled_expectation_value pentru a obține valorile de așteptare ale circuitului netăiat după obținerea distribuției de probabilitate prin SamplerV2. Aceasta permite o utilizare omogenă a primitivei pentru toate instanțele. Totuși, reține că am fi putut folosi și EstimatorV2 pentru a calcula direct valorile de așteptare.

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()

Acum vom calcula media valorilor de așteptare a tuturor observabilelor de tip Z cu pondere 1 și pondere 2, fără tăiere.

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

Vizualizare

Să vizualizăm acum îmbunătățirea obținută pentru observabilele cu pondere 1 și pondere 2 când se folosește tăierea porților pentru circuitul de lanț periodic.

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 of the previous code cell

Rezumat

În rezumat, am calculat media valorilor de așteptare a observabilelor de tip Z cu pondere 1 și pondere 2 pentru un lanț 1D periodic de 109 qubiți. Pentru a face acest lucru, am

  • creat o hartă de cuplare virtuală adăugând o conectivitate cu rază lungă între primul și ultimul qubit ai lanțului 1D, și am transpilat circuitul.
    • transpilarea în această etapă ne-a permis să evităm supracostul transpilării fiecărui sub-experiment separat după tăiere,
    • utilizarea hărții de cuplare virtuale ne-a permis să evităm porțile SWAP suplimentare pentru operația pe 2 qubiți între primul și ultimul qubit.
  • eliminat conectivitatea cu rază lungă din circuitul transpilat prin tăierea porților.
  • convertit circuitele tăiate în setul de porți de bază aplicând pași de transpilare adecvați.
  • executat circuitele tăiate pe un dispozitiv IBM Quantum folosind o primitivă SamplerV2.
  • obținut valoarea de așteptare prin reconstrucția rezultatelor circuitelor tăiate.

Concluzie

Observăm din rezultate că media observabilelor de tip Z\langle Z \rangle cu pondere 1 și ZZ\langle ZZ \rangle cu pondere 2 este îmbunătățită semnificativ prin tăierea porților periodice. Reține că acest studiu nu include nicio tehnică de suprimare sau atenuare a erorilor. Îmbunătățirea observată se datorează exclusiv utilizării corecte a tăierii porților pentru această problemă. Rezultatele ar fi putut fi îmbunătățite și mai mult prin utilizarea tehnicilor de atenuare și suprimare.

Acest studiu prezintă un exemplu de utilizare eficientă a tăierii porților pentru a îmbunătăți performanța calculului.

Sondaj tutorial

Te rog să completezi acest scurt sondaj pentru a oferi feedback despre acest tutorial. Opiniile tale ne vor ajuta să îmbunătățim oferta de conținut și experiența utilizatorilor.

Link la sondaj

Note: This survey is provided by IBM Quantum and relates to the original English content. To give feedback on doQumentation's website, translations, or code execution, please open a GitHub issue.