Experiment la scară de utilitate I
Tamiya Onodera (5 iulie 2024)
Descarcă PDF-ul al lecției originale. Rețineți că unele fragmente de cod ar putea deveni depreciate, deoarece acestea sunt imagini statice.
Timpul estimat de QPU pentru a rula acest experiment este de 45 de secunde.
1. Introducere în lucrarea de utilitate
În această lecție, rulăm un circuit la scară de utilitate care apare în ceea ce numim informal „lucrarea de utilitate", publicată în Nature Vol 618, 15 iunie 2023. Lucrarea se ocupă de evoluția în timp a modelului Ising 2D în câmp transversal. În particular, se ia în considerare dinamica temporală a Hamiltonian-ului,
în care este cuplajul spinilor vecini cu , iar este câmpul transversal global. Se simulează dinamica spinilor dintr-o stare inițială prin intermediul descompunerii Trotter de ordinul întâi a operatorului de evoluție temporală,
în care timpul de evoluție este discretizat în pași Trotter, iar și sunt porți de rotație și, respectiv, .
S-au rulat experimente pe un procesor IBM Quantum® Eagle, care este un dispozitiv cu 127 de qubiți cu conectivitate heavy-hex, aplicând interacțiuni tuturor qubiților și interacțiuni pentru toate muchiile hărții de cuplare. Rețineți că nu toate interacțiunile pot fi aplicate simultan din cauza „dependenței de date". Prin urmare, se colorează harta de cuplare pentru a le grupa în straturi. Cele dintr-un strat primesc aceeași culoare și pot fi aplicate în paralel.
În plus, pentru simplitate experimentală, s-a focalizat pe cazul .
Contribuția originală a lucrării este că au construit circuite cuantice la o scară dincolo de simularea vectorului de stare, le-au rulat pe calculatoare cuantice cu zgomot și au reușit să extragă rezultate fiabile. Adică, au demonstrat utilitatea calculatoarelor cuantice cu zgomot. În acest scop, au aplicat extrapolarea la zgomot zero (ZNE) cu amplificarea probabilistică a erorilor (PEA) pentru a atenua erorile provenite de la dispozitivele cu zgomot.
De atunci, numim astfel de experimente și circuite „la scară de utilitate".
1.1 Obiectivul tău
Obiectivul tău în această lecție este să construiești un circuit la scară de utilitate și să-l rulezi pe un procesor Eagle. Extragerea de rezultate fiabile depășește scopul acestui notebook, parțial deoarece PEA este o funcție experimentală a Qiskit la momentul scrierii și parțial deoarece aplicarea ZNE cu PEA va necesita o perioadă considerabilă de timp.
Concret, ți se cere să construiești și să rulezi circuitul corespunzător Figurii 4b din lucrare și să reprezinți grafic punctele „nemitigate" ale tale. După cum vei vedea, este un circuit de 127 de qubiți 60 de straturi (20 de pași Trotter) cu ca observabilă.
Sună complicat? Nu-ți face griji. Ultimele trei lecții ale acestui curs oferă trepte intermediare. Pentru început, vom demonstra un experiment la scară mai mică, și anume construirea și rularea pe un dispozitiv fals a unui circuit de 27 de qubiți 6 straturi (2 pași Trotter) cu ca observabilă.
Asta e tot pentru introducere. Hai să pornim la aventura la scară de utilitate!
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-aer qiskit-ibm-runtime rustworkx
import qiskit
qiskit.__version__
'2.0.2'
#!pip install qiskit_ibm_runtime
#!pip install qiskit_aer
import matplotlib.pyplot as plt
import numpy as np
import rustworkx as rx
from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.circuit.library import YGate
from qiskit.quantum_info import SparsePauliOp
from qiskit_ibm_runtime import (
QiskitRuntimeService,
fake_provider,
EstimatorV2 as Estimator,
)
from qiskit_aer import AerSimulator
service = QiskitRuntimeService()
2. Pregătire
2.1 Construiește RZZ(- / 2)
Mai întâi, observă că Gate-ul RZZ în general necesită două Gate-uri .
from qiskit.circuit.library import RZZGate
θ_h = Parameter("$\\theta_h$")
qc1 = QuantumCircuit(2)
qc1.append(RZZGate(θ_h), [0, 1])
qc1.decompose(reps=1).draw("mpl")
După cum s-a menționat mai sus, ne concentrăm pe Gate-ul RZZ cu un unghi specific, - / 2, pentru acest experiment. Așa cum se arată în lucrare, acesta poate fi realizat cu un singur Gate .
qc2 = QuantumCircuit(2)
qc2.sdg([0, 1])
qc2.append(YGate().power(1 / 2), [1])
qc2.cx(0, 1)
qc2.append(YGate().power(1 / 2).adjoint(), [1])
qc2.draw("mpl")
Definim un Gate pe baza acestui circuit pentru referințe viitoare.
rzz = qc2.to_gate(label="RZZ")
Hai să folosim rzz-ul nou definit într-un exemplu aleatoriu.
qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))
Înainte de a-l folosi mai departe, să verificăm echivalența logică dintre qc1 (Gate-ul RZZ) pentru -pi/2 și rzz-ul sau qc2-ul nou definit:
from qiskit.quantum_info import Operator
op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)
op1.equiv(op2)
True
2.2 Colorează harta de cuplare
Să studiem cum colorăm harta de cuplare a unui Backend. Acest lucru este necesar pentru a grupa interacțiunile în straturi.
Pentru început, să vizualizăm harta de cuplare a unui Backend. Rețineți că hărțile de cuplare sunt heavy-hexagonale pentru toate dispozitivele IBM Quantum actuale.
backend = service.least_busy(operational=True, simulator=False)
backend.coupling_map.draw()

Pentru a colora o hartă de cuplare, folosim rustworkx, care este un pachet Python pentru lucrul cu grafuri și rețele complexe. Acesta oferă mai mulți algoritmi de colorare, care sunt toți euristici și, prin urmare, nu garantează găsirea unei colorări minimale.
Spunând acestea, deoarece grafurile heavy-hex sunt bipartite, alegem graph_bipartite_edge_color, care ar trebui să găsească o colorare minimală pentru aceste grafuri.
def color_coupling_map(backend):
graph = backend.coupling_map.graph
undirected_graph = graph.to_undirected(multigraph=False)
edge_color_map = rx.graph_bipartite_edge_color(undirected_graph)
if edge_color_map is None:
edge_color_map = rx.graph_greedy_edge_color(undirected_graph)
# build a map from color to a list of edges
edge_index_map = undirected_graph.edge_index_map()
color_edges_map = {color: [] for color in edge_color_map.values()}
for edge_index, color in edge_color_map.items():
color_edges_map[color].append(
(edge_index_map[edge_index][0], edge_index_map[edge_index][1])
)
return edge_color_map, color_edges_map
Grafurile heavy-hexagonale ar trebui pictate în trei culori. Să verificăm acest lucru pentru harta de cuplare de mai sus.
edge_color_map, color_edges_map = color_coupling_map(backend)
print(
f"{backend.name}, {backend.num_qubits}-qubit device, {len(color_edges_map.keys())} colors assigned."
)
ibm_strasbourg, 127-qubit device, 3 colors assigned.
Da, este!
Din distracție, să pictăm harta de cuplare cu colorarea obținută, folosind funcția de vizualizare a rustworkx.
color_str_map = {0: "green", 1: "red", 2: "blue"}
undirected_graph = backend.coupling_map.graph.to_undirected(multigraph=False)
for i in undirected_graph.edge_indices():
undirected_graph.get_edge_data_by_index(i)["color"] = color_str_map[
edge_color_map[i]
]
rx.visualization.graphviz_draw(
undirected_graph, method="neato", edge_attr_fn=lambda edge: {"color": edge["color"]}
)

3. Rezolvă evoluția temporală Trotterizată a unui model Ising 2D.
Să definim o rutină pentru a construi un Circuit din lucrarea de utilitate pentru evoluția temporală a unui model Ising 2D. Rutina primește trei parametri: un Backend, un număr întreg indicând numărul de pași Trotter și un Boolean care controlează inserarea de bariere.
def get_utility_circuit(backend, num_steps: int, barrier: bool = False):
num_qubits = backend.num_qubits
_, color_edges_map = color_coupling_map(backend)
θ_h = Parameter("$\\theta_h$")
qc = QuantumCircuit(num_qubits)
for i in range(num_steps):
qc.rx(θ_h, range(num_qubits))
for _, edge_list in color_edges_map.items():
for edge in edge_list:
qc.append(rzz, edge)
if barrier:
qc.barrier()
return qc
Rețineți că am efectuat deja manual maparea și rutarea qubiților pentru Circuit-ul construit. Prin urmare, atunci când transpilăm Circuit-ul mai târziu, nu cerem (și nu ar trebui să cerem) transpilatorului să efectueze maparea și rutarea qubiților. Cum vei vedea în curând, îl invocăm cu nivelul de optimizare 1 și metoda de layout „trivial".
În continuare definim o rutină simplă pentru a obține informații despre Circuit-ul construit, pentru o verificare rapidă.
def get_circuit_info(qc: QuantumCircuit, reps: int = 0):
qc0 = qc.decompose(reps=reps)
return (
f"{qc0.num_qubits} qubits × {qc0.depth(lambda x: x.operation.num_qubits == 2)} layers ({qc0.depth()}-depth)"
+ ", "
+ f"""Gate breakdown: {", ".join([f"{k.upper()} {v}" for k, v in qc0.count_ops().items()])}"""
)
Să exersăm aceste rutine. Ar trebui să obții un Circuit de 27 de qubiți 15 straturi (5 pași Trotter). Deoarece dispozitivul fals are 28 de muchii, ar trebui să existe 28*5 Gate-uri de entanglare.
backend = fake_provider.FakeTorontoV2()
num_steps = 5
qc = get_utility_circuit(backend, num_steps, True)
display(qc.draw(output="mpl", fold=-1))
print(get_circuit_info(qc, reps=0))
print(get_circuit_info(qc, reps=1))

27 qubits × 15 layers (20-depth), Gate breakdown: CIRCUIT-165 140, RX 135, BARRIER 5
27 qubits × 15 layers (60-depth), Gate breakdown: SDG 280, UNITARY 280, CX 140, R 135, BARRIER 5
4. Rezolvă versiunea cu 27 de qubiți a problemei.
Acum demonstrăm o versiune la scară mai mică a experimentului de utilitate. Construim un Circuit de 27 de qubiți 6 straturi (2 pași Trotter) cu ca observabilă și îl rulăm atât pe AerSimulator, cât și pe un dispozitiv fals.
Desigur, urmăm fluxul nostru de lucru în patru pași, „Qiskit pattern", care constă din Mapare, Optimizare, Execuție și Post-Procesare. Mai concret:
- Maparea intrărilor clasice la o calcul cuantic.
- Optimizarea Circuit-urilor pentru calculul cuantic.
- Executarea Circuit-urilor folosind primitive.
- Post-procesarea și returnarea rezultatelor în format clasic.
În cele ce urmează, avem pasul Mapare pentru crearea unui Circuit pentru un experiment la scară mai mică. Avem apoi un set de Optimizare și Execuție pentru AerSimulator și un altul pentru un dispozitiv fals. În final, avem pasul de Post-Procesare pentru reprezentarea grafică a rezultatelor.
4.1 Pasul 1: Mapare
backend = fake_provider.FakeTorontoV2() # a 27 qubit fake device.
num_steps = 2
qc = get_utility_circuit(backend, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [13], 1)], num_qubits=backend.num_qubits
) # Falcon
angles = [
0,
0.1,
0.2,
0.3,
0.4,
0.5,
0.6,
0.7,
0.8,
1.0,
np.pi / 2,
] # We try 11 angles for theta_h.
4.2 Pașii 2 și 3: Optimizare și Execuție (Simulator)
backend_sim = AerSimulator()
transpiled_qc_sim = transpile(
qc, backend_sim, optimization_level=1, layout_method="trivial"
)
transpiled_obs_sim = obs.apply_layout(layout=transpiled_qc_sim.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_sim, reps=1))
27 qubits × 6 layers (23-depth), Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (16-depth), Gate breakdown: U3 80, CX 56, R 54, U1 32, U 28
Un utilizator a rulat celula următoare folosind un MacBook Pro cu procesor Intel Core i7 quad-core de 2,3 GHz, echipat cu 32 GB RAM 3LPDDR4X, care rula macOS 14.5. A durat 161 ms în timp real. Fiecare laptop va fi ușor diferit.
%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_sim)
pub = (transpiled_qc_sim, transpiled_obs_sim, params)
result_sim = estimator.run([pub]).result()
CPU times: user 231 ms, sys: 186 ms, total: 417 ms
Wall time: 111 ms
4.3 Pașii 2 și 3: Optimizare și execuție (dispozitiv fals)
backend_fake = fake_provider.FakeTorontoV2()
transpiled_qc_fake = transpile(
qc, backend_fake, optimization_level=1, layout_method="trivial"
)
transpiled_obs_fake = obs.apply_layout(layout=transpiled_qc_fake.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc_fake, reps=1))
27 qubits × 6 layers (23-depth), Gate breakdown: SDG 112, UNITARY 112, CX 56, R 54
27 qubits × 6 layers (49-depth), Gate breakdown: SDG 324, U1 274, H 162, CX 56, U3 14
Când același utilizator a rulat celula următoare cu același mediu ca mai sus, a durat 2 min 19 s în timp real. Executarea unui Circuit pe un dispozitiv fals invocă simulare cu zgomot, ceea ce durează mult mai mult decât simularea exactă. Îți recomandăm să nu execuți un Circuit mai mare (cum ar fi unul de 27 de qubiți 9 straturi cu 3 pași Trotter) pe un dispozitiv fals.
%%time
params = [[p] for p in angles]
estimator = Estimator(mode=backend_fake)
pub = (transpiled_qc_fake, transpiled_obs_fake, params)
result_fake = estimator.run([pub]).result()
CPU times: user 4min 42s, sys: 9.35 s, total: 4min 51s
Wall time: 38.3 s
4.4 Pasul 4: Post-procesare
Reprezentăm grafic rezultatele din simulările exacte și cu zgomot. Poți observa efectele severe ale zgomotului pe FakeToronto.
plt.plot(angles, result_fake[0].data.evs, "o", label="Fake Device")
plt.plot(angles, result_sim[0].data.evs, "o", label="AerSimulator")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{13} \\rangle$")
plt.legend()
plt.show()
5. Rezolvă versiunea cu 127 de qubiți a problemei
Obiectivul tău este să rulezi experimentul la scară de utilitate menționat la început. Vei crea și executa un Circuit de 127 de qubiți și 60 de straturi (20 de pași Trotter) cu ca observabilă. Îți recomandăm să încerci să faci asta singur, folosind codul pentru versiunea cu 27 de qubiți acolo unde este potrivit. Dar soluția este furnizată aici.
Soluție:
5.1 Pasul 1: Mapare
# backend_map = service.backend("ibm_brisbane")
backend_map = service.least_busy(operational=True, simulator=False)
num_steps = 20
qc = get_utility_circuit(backend_map, num_steps)
obs = SparsePauliOp.from_sparse_list(
[("Z", [62], 1)], num_qubits=backend_map.num_qubits
) # Eagle
angles = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 1.0, np.pi / 2]
5.2 Pașii 2 și 3: Optimizare și execuție
Rețineți că harta de cuplare a procesorului Eagle are 144 de muchii.
# backend = service.backend("ibm_brisbane")
backend = backend_map
transpiled_qc = transpile(qc, backend, optimization_level=1, layout_method="trivial")
transpiled_obs = obs.apply_layout(layout=transpiled_qc.layout)
print(get_circuit_info(qc, reps=1))
print(get_circuit_info(transpiled_qc))
156 qubits × 60 layers (221-depth), Gate breakdown: SDG 7040, UNITARY 7040, CX 3520, R 3120
156 qubits × 60 layers (201-depth), Gate breakdown: RZ 11933, SX 6240, CZ 3520
params = [[p] for p in angles]
estimator = Estimator(mode=backend)
pub = (transpiled_qc, transpiled_obs, params)
job = estimator.run([pub])
job_id = job.job_id()
print(f"job id={job_id}")
job id=d1479n6qf56g0081sxa0
5.3 Post-procesare
Furnizăm valorile pentru punctele „mitigate" din Figura 4b a lucrării de utilitate. Reprezintă-le grafic împreună cu rezultatele tale.
result_paper = [
1.0171,
1.0044,
0.9563,
0.9602,
0.8394,
0.8120,
0.5466,
0.4556,
0.1953,
0.0141,
0.0117,
]
# REPLACE WITH YOUR OWN JOB ID
job = service.job(job_id)
plt.plot(angles, job.result()[0].data.evs, "o", label=f"{job.backend().name}")
plt.plot(angles, result_paper, "o", label="Utility Paper")
plt.xlabel("$\\mathrm{R_x}$ angle $\\theta_h$")
plt.title("$\\langle Z_{62} \\rangle$")
plt.legend()
plt.show()
Sunt rezultatele tale similare cu cele „nemitigate" din Figura 4b? Ar putea fi foarte diferite, în funcție de dispozitiv și de starea sa la momentul experimentului. Nu-ți face griji în privința rezultatelor în sine. Ceea ce vom verifica este dacă ai scris codul corect. Dacă ai făcut-o, felicitări, ai atins linia de start a erei utilității.
Ca în lucrarea de utilitate, oamenii de știință din întreaga lume au demonstrat o ingeniozitate extraordinară în extragerea de rezultate semnificative chiar și în prezența zgomotului. Obiectivul final al acestui efort colectiv este avantajul cuantic: o stare în care calculatoarele cuantice pot rezolva unele probleme utile în industrie mai rapid, cu fidelitate mai mare sau mai ieftin decât calculatoarele clasice. Aceasta probabil nu va fi un eveniment singular, ci mai degrabă o eră în care reproducerea clasică a rezultatelor cuantice devine progresiv mai dificilă, până când la un moment dat acel avans al calculului cuantic devine critic de important. Un lucru este clar în privința avantajului cuantic: ajungem acolo doar prin experimente la scară de utilitate. Dacă acest curs are ca rezultat alăturarea ta la această căutare, plină de provocări și satisfacții, am fi mai mult decât fericiți.