דלג לתוכן הראשי

ניסוי בסדר גודל utility I

הערה

Tamiya Onodera (5 ביולי 2024)

הורד את ה-pdf של ההרצאה המקורית. שים לב שחלק מקטעי הקוד עשויים להפוך למיושנים, שכן מדובר בתמונות סטטיות.

זמן QPU משוער להרצת ניסוי זה הוא 45 שניות.

1. מבוא למאמר ה-utility

בשיעור זה, אנו מריצים מעגל בסדר גודל של utility, המופיע במה שאנו מכנים בצורה לא רשמית "מאמר ה-utility", שפורסם ב-Nature כרך 618, 15 ביוני 2023. המאמר עוסק באבולוציית הזמן של מודל איזינג דו-ממדי בשדה רוחבי. בפרט, הם בוחנים את דינמיקת הזמן של ההמילטוניאן,

H=HZZ+HX=J(i,j)ZiZj+hiXiH = H_{ZZ} + H_X = - J \sum_{(i,j)} Z_i Z_j + h \sum_{i} X_i

שבו J>0J > 0 הוא הצימוד בין ספינים שכנים עם i<ji < j ו-hh הוא השדה הרוחבי הגלובלי. הם מדמים את דינמיקת הספין ממצב התחלתי באמצעות פירוק טרוטר מסדר ראשון של אופרטור האבולוציה בזמן,

exp(iHZZδt)=(i,j)exp(iJδtZiZj)=(i,j)RZiZj(2Jδt)exp(iHXδt)=iexp(ihδtXi)=iRXi(2hδt)\begin{aligned} \exp(-i H_{ZZ} \delta t) &= \prod_{(i,j)} \exp (i J \delta t Z_i Z_j) = \prod_{(i,j)} \mathrm{R}_{Z_i Z_j} ( - 2 J \delta t) \\ \exp(-i H_X \delta t) &= \prod_{i} \exp (-i h \delta t X_i ) = \prod_{i} \mathrm{R}_{X_i} ( 2 h \delta t) \end{aligned}

שבו זמן האבולוציה TT מדוּסקרת ל-T/δtT / \delta t צעדי טרוטר, ו-RZiZj(θJ)\mathrm{R}_{Z_i Z_j}(\theta_J) ו-RXi(θh)\mathrm{R}_{X_i}(\theta_h) הם שערי סיבוב ZZZZ ו-XX, בהתאמה.

הם הריצו ניסויים על מעבד Eagle של IBM Quantum®, שהוא מכשיר בעל 127 Qubit עם קישוריות heavy-hex, תוך הפעלת אינטראקציות XX על כל ה-Qubit ואינטראקציות ZZZZ על כל הצלעות של מפת הצימוד. שים לב שלא ניתן להפעיל את כל אינטראקציות ה-ZZZZ בו-זמנית בשל "תלות בנתונים". לפיכך, הם צובעים את מפת הצימוד כדי לקבץ אותן לשכבות. אלו שבאותה שכבה מקבלות את אותו צבע, וניתן להפעילן במקביל.

בנוסף, לפשטות ניסויית, הם התמקדו במקרה θJ=π/2\theta_J=-\pi /2.

התרומה החדשנית של המאמר היא שהם בנו מעגלים קוונטיים בסדר גודל שמעבר לסימולציית וקטור מצב, הריצו אותם על מחשבים קוונטיים רועשים, והצליחו לחלץ תוצאות אמינות. כלומר, הם הוכיחו את ה-utility של מחשבים קוונטיים רועשים. לשם כך, הם הפעילו אקסטרפולציית אפס-רעש (ZNE) עם הגברת שגיאות הסתברותית (PEA) כדי לצמצם שגיאות ממכשירים רועשים.

מאז, אנו מכנים ניסויים ומעגלים כאלה "בסדר גודל utility".

1.1 המטרה שלך

המטרה שלך בשיעור זה היא לבנות מעגל בסדר גודל utility ולהריץ אותו על מעבד Eagle. מעבר לתחום מחברת זו לחלץ תוצאות אמינות, בחלקו משום ש-PEA הוא תכונה ניסיונית של Qiskit בעת כתיבת שורות אלה, ובחלקו משום שהחלת ZNE עם PEA תיקח זמן לא מועט.

באופן קונקרטי, אתה מתבקש לבנות ולהריץ את המעגל המתאים לאיור 4b של המאמר, ולשרטט את הנקודות "הלא-מוקזזות" בעצמך. כפי שאתה רואה, מדובר במעגל של 127 Qubit ×\times 60 שכבות (20 צעדי טרוטר) עם Z62\langle Z_{62} \rangle כאובזרבבל. image.png נשמע מאתגר?   אל דאגה. שלושת השיעורים האחרונים של קורס זה מספקים אבני דרך. להתחלה, נדגים ניסוי בסדר גודל קטן יותר — בניה והרצה על מכשיר מדומה של מעגל 27 Qubit ×\times 6 שכבות (2 צעדי טרוטר) עם Z13\langle Z_{13} \rangle כאובזרבבל.

זה הכל למבוא. בואו נצא להרפתקת utility-scale!

# 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. הכנה

2.1 בניית RZZ(-π\pi / 2)

ראשית, שים לב שה-Gate RZZ בכלל דורש שני Gates מסוג CXCX.

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

Output of the previous code cell

כאמור לעיל, אנו מתמקדים ב-Gate RZZ עם זווית ספציפית, -π\pi / 2, עבור ניסוי זה. כפי שמוצג במאמר, ניתן לממש זאת עם Gate CXCX אחד בלבד.

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

Output of the previous code cell

אנו מגדירים Gate במונחים של Circuit זה לעיון עתידי.

rzz = qc2.to_gate(label="RZZ")

בואו נשתמש בשימוש אקראי ב-rzz החדש שהגדרנו.

qc3 = QuantumCircuit(3)
qc3.append(rzz, [0, 1])
qc3.append(rzz, [0, 2])
display(qc3.draw("mpl"))
# display(qc.decompose(reps=1).draw("mpl"))

Output of the previous code cell

לפני שנמשיך, בואו נאמת את השקילות הלוגית של qc1 (ה-Gate RZZ) עבור -pi/2 ו-Gate rzz או qc2 החדש שהגדרנו:

from qiskit.quantum_info import Operator

op1 = Operator(qc1.assign_parameters([-np.pi / 2]))
op2 = Operator(qc2)

op1.equiv(op2)
True

2.2 צביעת מפת הצימוד

בואו נבחן כיצד אנו צובעים את מפת הצימוד של Backend. זה נדרש כדי לקבץ אינטראקציות ZZZZ לשכבות.

להתחלה, בואו נמחיש את מפת הצימוד של Backend. שים לב שמפות הצימוד הן בצורת heavy-hexagonal לכל מכשירי IBM Quantum הנוכחיים.

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

backend.coupling_map.draw()

Output of the previous code cell

לצביעת מפת הצימוד, אנו משתמשים ב-rustworkx, שהוא חבילת Python לעבודה עם גרפים ורשתות מורכבות. היא מספקת מספר אלגוריתמי צביעה, שכולם היוריסטיים ולכן אינם מובטחים למצוא צביעה מינימלית.

עם זאת, מאחר שגרפי heavy-hex הם דו-צדדיים, אנו בוחרים ב-graph_bipartite_edge_color, שאמור למצוא צביעה מינימלית עבור גרפים אלה.

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

גרפי heavy-hexagonal אמורים להיצבע בשלושה צבעים. בואו נבדוק זאת עבור מפת הצימוד לעיל.

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.

אכן!

לכיף, בואו נצבע את מפת הצימוד לפי הצביעה שהתקבלה, תוך שימוש בתכונת הוויזואליזציה של 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"]}
)

Output of the previous code cell

3. פתרון אבולוציית הזמן של מודל איזינג 2D באמצעות טרוטר.

בואו נגדיר שגרה לבניית Circuit של מאמר ה-utility לאבולוציית הזמן של מודל איזינג דו-ממדי. השגרה מקבלת שלושה פרמטרים: Backend, מספר שלם המציין את מספר צעדי הטרוטר, וערך בוליאני השולט בהוספת מחסומים.

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

שים לב שכבר ביצענו ידנית את מיפוי ה-Qubit והניתוב עבור ה-Circuit שנבנה. לכן, כשנבצע Transpile של ה-Circuit מאוחר יותר, אנו לא נבקש (ו__לא כדאי__ לבקש) מה-Transpiler לבצע מיפוי ניתוב של Qubit. כפי שתראה בקרוב, נפעיל אותו עם רמת אופטימיזציה 1 ושיטת layout "trivial".

לאחר מכן, אנו מגדירים שגרה פשוטה לקבלת מידע על ה-Circuit שנבנה לבדיקה מהירה.

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()])}"""
)

בואו נתרגל שגרות אלה. אמור להופיע Circuit של 27 Qubit ×\times 15 שכבות (5 צעדי טרוטר). מאחר שלמכשיר המדומה יש 28 צלעות, צריכים להיות 28*5 שערי שזירה.

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

Output of the previous code cell

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. פתרון גרסת ה-27-Qubit של הבעיה.

כעת נדגים גרסה קטנה יותר של ניסוי ה-Utility. נבנה Circuit בגודל 27 Qubit ×\times 6 שכבות (2 צעדי Trotter) עם Z13\langle Z_{13} \rangle כ-Observable, ונריץ אותו גם על AerSimulator וגם על מכשיר מדומה.

כמובן, אנחנו עוקבים אחר תהליך העבודה בן ארבעת השלבים שלנו, "Qiskit pattern", שמורכב מ-Map, Optimize, Execute ו-Post-Process. ביתר פירוט:

  • ממפים קלטים קלאסיים לחישוב קוונטי.
  • מייעלים Circuit לחישוב קוונטי.
  • מריצים Circuit בעזרת primitives.
  • מעבדים לאחר הרצה ומחזירים תוצאות בפורמט קלאסי.

בהמשך, יש לנו את שלב ה-Map ליצירת Circuit לניסוי בקנה מידה קטן יותר. לאחר מכן יש סט אחד של Optimize ו-Execute עבור AerSimulator ועוד אחד עבור מכשיר מדומה. לבסוף, יש לנו את שלב ה-Post-Process לציור התוצאות.

4.1 שלב 1: Map

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 שלבים 2 ו-3: Optimize ו-Execute (סימולטור)

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

משתמש אחד הריץ את התא הבא על MacBook Pro עם מעבד Intel Core i7 Quad-Core ב-2.3 GHz ו-32GB זיכרון RAM מסוג 3LPDDR4X, הפועל תחת macOS 14.5. זה לקח 161ms בזמן קיר. כל מחשב נייד יהיה מעט שונה.

%%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 שלבים 2 ו-3: Optimize ו-Execute (מכשיר מדומה)

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

כשאותו משתמש הריץ את התא הבא באותה סביבה, זה לקח 2 דקות ו-19 שניות בזמן קיר. הרצת Circuit על מכשיר מדומה מפעילה סימולציה רועשת שלוקחת הרבה יותר זמן מסימולציה מדויקת. אנחנו ממליצים לא להריץ Circuit גדול יותר (כמו 27 Qubit ×\times 9 שכבות עם 3 צעדי Trotter) על מכשיר מדומה.

%%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 שלב 4: Post-process

אנחנו מציגים את התוצאות מהסימולציות המדויקות והרועשות. תראו את ההשפעות החמורות של הרעש על 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()

Output of the previous code cell

5. פתרו את גרסת ה-127 Qubit של הבעיה

המטרה שלכם היא להריץ את ניסוי ה-Utility בקנה מידה כפי שהוזכר בהתחלה. תיצרו ותריצו Circuit בגודל 127 Qubit ו-60 שכבות (20 צעדי Trotter) עם Z62\langle Z_{62} \rangle כ-Observable. אנחנו ממליצים לנסות לעשות את זה בעצמכם, תוך שימוש בקוד לגרסת ה-27 Qubit כשמתאים. אבל הפתרון מסופק כאן.

פתרון:

5.1 שלב 1: Map

# 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 שלבים 2 ו-3: Optimize ו-Execute

נציין שמפת הצימוד של מעבד Eagle כוללת 144 קצוות.

# 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-process

אנחנו מספקים את הערכים עבור הנקודות "המתוקנות" מאיור 4b של מאמר ה-Utility. הציגו אותן יחד עם התוצאות שלכם.

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

Output of the previous code cell

האם התוצאות שלכם דומות ל"לא-מתוקנות" באיור 4b?   הן עשויות להיות שונות מאוד, בהתאם למכשיר ולמצבו בזמן הניסוי. אל תדאגו מהתוצאות עצמן. מה שנבדוק הוא האם ביצעתם את הקידוד נכון. אם עשיתם זאת, מזל טוב — הגעתם לקו הזינוק של עידן ה-Utility.

כמו במאמר ה-Utility, מדענים ברחבי העולם גילו כושר המצאה עצום בחילוץ תוצאות משמעותיות גם בנוכחות רעש. המטרה הסופית של מאמץ קולקטיבי זה היא יתרון קוונטי: מצב שבו מחשבים קוונטיים יוכלו לפתור בעיות מסוימות בעלות ערך תעשייתי מהר יותר, ברמת דיוק גבוהה יותר, או בעלות נמוכה יותר מאשר מחשבים קלאסיים. סביר להניח שזה לא יהיה אירוע בודד, אלא עידן שבו שחזור קלאסי של תוצאות קוונטיות לוקח זמן רב יותר באופן הדרגתי, עד שיגיע שלב שבו הפער הזה יהפוך לקריטי. דבר אחד ברור לגבי יתרון קוונטי: אנחנו מגיעים אליו רק דרך ניסויים בקנה מידה של Utility. אם קורס זה יביא אתכם להצטרף למסע הזה, שמלא אתגרים וכיף, נשמח מאוד.

Reference