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

חיתוך מעגלים לתנאי גבול מחזוריים

הערכת שימוש: שתי דקות על מעבד Eagle (הערה: זוהי אומדן בלבד. זמן הריצה שלך עשוי להשתנות.)

רקע

במחברת זו, אנו בוחנים את הסימולציה של שרשרת מחזורית של Qubits שבה קיימת פעולת שני Qubits בין כל שני Qubits סמוכים, כולל הראשון והאחרון. שרשראות מחזוריות נמצאות לעתים קרובות בבעיות פיזיקה וכימיה כגון מודלים של Ising וסימולציה מולקולרית.

התקני IBM Quantum® הנוכחיים הם מישוריים. ניתן להטמיע חלק מהשרשראות המחזוריות על הטופולוגיה ישירות כאשר ה-Qubits הראשון והאחרון הם שכנים. עם זאת, עבור בעיות גדולות מספיק, ה-Qubits הראשון והאחרון יכולים להיות רחוקים זה מזה, ולכן נדרשים שערי SWAP רבים לפעולת 2-Qubit בין שני Qubits אלה. בעיית גבול מחזורי כזאת נחקרה במאמר זה.

במחברת זו אנו מציגים את השימוש בחיתוך מעגלים להתמודדות עם בעיית שרשרת מחזורית בסדר גודל שימושי כזו שבה ה-Qubits הראשון והאחרון אינם שכנים. חיתוך קישוריות ארוכת טווח זו נמנע משערי SWAP נוספים במחיר של ביצוע מופעים מרובים של המעגל, וקצת עיבוד קלאסי לאחר הביצוע. לסיכום, ניתן לשלב חיתוך כדי לחשב באופן לוגי את פעולות 2-Qubit למרחק ארוך. במילים אחרות, גישה זו מובילה לעלייה אפקטיבית בקישוריות של מפת הצימוד, ובכך מובילה למספר קטן יותר של שערי SWAP.

שימו לב שישנם שני סוגים של חיתוכים - חיתוך חוט של מעגל (הנקרא wire cutting), או החלפת שער 2-Qubit במספר פעולות יחיד Qubit (הנקראות gate cutting). במחברת זו, נתמקד בחיתוך שערים. לפרטים נוספים על חיתוך שערים, עיין בחומרי הסבר ב-qiskit-addon-cutting, ובהפניות המתאימות. לפרטים נוספים על חיתוך חוטים, עיין בחיתוך חוטים להערכת ערכי ציפייה, או במדריכים ב-qiskit-addon-cutting.

דרישות

לפני שמתחילים מדריך זה, וודא שיש לך את הדברים הבאים מותקנים:

  • Qiskit SDK v1.2 ואילך (pip install qiskit)
  • Qiskit Runtime v0.3 ואילך (pip install qiskit-ibm-runtime)
  • Circuit cutting Qiskit addon v.9.0 ואילך (pip install qiskit-addon-cutting)

הגדרה

# 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

שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית

כאן, נייצר מעגל TwoLocal ונגדיר כמה תצפיות.

  • קלט: פרמטרים ליצירת מעגל
  • פלט: מעגל מופשט ותצפיות

אנו שוקלים entangler map יעילה לחומרה עבור מעגל TwoLocal עם קישוריות מחזורית בין ה-Qubits האחרון והראשון של ה-entangler map. אינטראקציה ארוכת טווח זו יכולה להוביל לשערי SWAP נוספים במהלך הטרנספילציה, ובכך להגדיל את עומק המעגל.

בחירת Backend ו-Layout ראשוני

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

עבור מחברת זו נשקול שרשרת 1D מחזורית של 109 Qubits, שהיא השרשרת 1D הארוכה ביותר בטופולוגיה של התקן IBM Quantum בן 127 Qubits. לא ניתן לסדר שרשרת מחזורית של 109 Qubits על התקן של 127 Qubits כך שה-Qubits הראשון והאחרון יהיו שכנים מבלי לשלב שערי 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_qubits
109

בניית entangler map עבור מעגל 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

מעגל TwoLocal מאפשר את החזרה על ה-rotation_blocks וה-entangler map מספר פעמים. במקרה זה, מספר החזרות קובע את מספר השערים המחזוריים שצריכים להיות חתוכים. מכיוון שהעומס הסטטיסטי לדגימה גדל באופן אקספוננציאלי עם מספר החיתוכים (עיין במדריך חיתוך חוטים להערכת ערכי ציפייה לפרטים נוספים), נקבע את מספר החזרות ל-2 במחברת זו.

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

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

נקצה את ערך הפרמטר 00 עבור שתי השכבות הראשונות של שערי Rx, ואת הערך π\pi עבור השכבה האחרונה. זה מבטיח שהתוצאה האידיאלית של מעגל זה היא 1n|1\rangle^{\otimes n}, כאשר nn הוא מספר ה-Qubits. לכן, ערכי הציפייה של Zi\langle Z_i \rangle ו-ZiZi+1\langle Z_i Z_{i+1} \rangle, כאשר ii הוא האינדקס של ה-Qubit, הם 1-1 ו-+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)

בחירת תצפיות

כדי לכמת את היתרונות של חיתוך שערים אנו מודדים את ערכי הציפייה של התצפיות 1ni=1nZi\frac{1}{n}\sum_{i=1}^n \langle Z_i \rangle ו-1n1i=1n1ZiZi+1\frac{1}{n-1}\sum_{i=1}^{n-1} \langle Z_i Z_{i+1} \rangle. כפי שנדון קודם, ערכי הציפייה האידיאליים הם 1-1 ו-+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

שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית

  • קלט: מעגל מופשט ותצפיות
  • פלט: מעגל יעד ותצפיות המיוצרות על ידי חיתוך שערים ארוכי טווח

טרנספילציה של המעגל

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

עם זאת, אם הטרנספילציה נעשית בשלב זה עם קישוריות חומרה מקורית, ה-Transpiler יוסיף שערי SWAP מרובים כדי למקם את פעולת 2-Qubit המחזורית - מטשטש את היתרונות של חיתוך המעגל. כדי להימנע מבעיה זו אנו יכולים למנף את העובדה שאנו יודעים את השערים המדויקים שצריכים להיות חתוכים. במיוחד, אנו יכולים ליצור מפת צימוד וירטואלית על ידי הוספת קשרים וירטואליים בין Qubits רחוקים כדי להכיל את שערי 2-Qubit המחזוריים הללו. זה יבטיח שניתן לבצע טרנספילציה של המעגל בשלב זה מבלי לשלב את שערי ה-SWAP הנוספים.

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

חיתוך קישוריות מחזוריות ארוכות טווח

כעת אנו חותכים את השערים במעגל שעבר טרנספילציה. שימו לב ששערי 2-Qubit שצריכים להיות חתוכים הם אלה המחברים בין ה-Qubits האחרון והראשון של ה-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]}
]

נחיל את ה-Layout של המעגל שעבר טרנספילציה על התצפית.

trans_observables = observables.apply_layout(virtual_mapped_circuit.layout)

לבסוף, תתי-הניסויים נוצרים על ידי דגימה על בסיסי מדידה והכנה שונים.

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

שימו לב שחיתוך האינטראקציות ארוכות הטווח מוביל לביצוע של דגימות מרובות של המעגל השונות בבסיסי המדידה וההכנה. מידע נוסף על כך ניתן למצוא ב-Constructing a virtual two-qubit gate by sampling single-qubit operations וב-Cutting circuits with multiple two-qubit unitaries.

מספר השערים המחזוריים שיש לחתוך שווה למספר החזרות של השכבה TwoLocal, שהוגדרה כ-num_reps לעיל. עומס הדגימה של חיתוך שערים הוא 6. לכן, המספר הכולל של תתי-הניסויים יהיה 6num_reps6^{num\_reps}.

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

טרנספילציה של תתי-הניסויים

בשלב זה, תתי-הניסויים מכילים מעגלים עם כמה שערי 1-Qubit שאינם במערך שערי הבסיס. זאת משום שה-Qubits החתוכים נמדדים בבסיס שונה, ושערי הסיבוב המשמשים לכך אינם בהכרח שייכים למערך שערי הבסיס. לדוגמה, מדידה בבסיס X מרמזת על החלת שער Hadamard לפני המדידה הרגילה בבסיס Z. אך Hadamard אינו חלק ממערך שערי הבסיס.

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

נחיל את המעברים BasisTranslator ולאחר מכן Optimize1qGatesDecomposition כדי להבטיח שכל השערים במעגלים אלה שייכים למערך שערי הבסיס. שימוש בשני מעברים אלה מהיר יותר מתהליך הטרנספילציה המלא, מכיוון שצעדים אחרים כגון ניתוב ובחירת Layout ראשוני אינם מבוצעים שוב.

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

שלב 3: ביצוע באמצעות Primitives של Qiskit

  • קלט: מעגלי יעד
  • פלט: התפלגויות קוואזי-הסתברות

אנו משתמשים ב-primitive SamplerV2 לביצוע המעגלים החתוכים. אנו משביתים dynamical decoupling ו-twirling כך שכל שיפור שנקבל בתוצאה יהיה אך ורק בגלל יישום אפקטיבי של חיתוך שערים עבור סוג זה של מעגל.

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

כעת נשלח את העבודות באמצעות מצב אצווה.

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

שלב 4: עיבוד לאחר ביצוע והחזרת תוצאה בפורמט קלאסי רצוי

  • קלט: התפלגויות קוואזי-הסתברות
  • פלט: ערכי ציפייה משוחזרים
reconstructed_expvals = reconstruct_expectation_values(
result,
coefficients,
paulis,
)

כעת נחשב את הממוצע של תצפיות מסוג Z במשקל-1 ומשקל-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

אימות צולב: קבלת ערך ציפייה ללא חיתוך

כדאי לבצע אימות צולב של היתרון של טכניקת חיתוך המעגלים מול ללא חיתוך. כאן נחשב את ערכי הציפייה מבלי לחתוך את המעגל. שימו לב שמעגל שלא נחתך כזה יסבול ממספר גדול של שערי SWAP הנדרשים ליישום פעולת 2-Qubit בין ה-Qubits הראשון והאחרון. נשתמש בפונקציה sampled_expectation_value כדי להשיג את ערכי הציפייה של המעגל שלא נחתך לאחר קבלת התפלגות ההסתברות דרך SamplerV2. זה מאפשר שימוש הומוגני ב-primitive על פני כל המופעים. עם זאת, שימו לב שיכולנו להשתמש גם ב-EstimatorV2 כדי לחשב את ערכי הציפייה ישירות.

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

כעת נחשב את ערכי הציפייה הממוצעים של כל התצפיות מסוג Z במשקל-1 ומשקל-2 ללא חיתוך.

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

הדמיה

כעת נדמה את השיפור שהושג עבור תצפיות במשקל-1 ומשקל-2 בעת שימוש בחיתוך שערים עבור מעגל שרשרת מחזורית

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

סיכום

לסיכום, חישבנו את ערכי הציפייה הממוצעים של תצפיות מסוג Z במשקל-1 ומשקל-2 עבור שרשרת 1D מחזורית של 109 Qubits. כדי לעשות זאת, אנו

  • יצרנו מפת צימוד וירטואלית על ידי הוספת קישוריות ארוכת טווח בין ה-Qubits הראשון והאחרון של שרשרת 1D, וביצענו טרנספילציה של המעגל.
    • טרנספילציה בשלב זה אפשרה לנו להימנע מהעומס של טרנספילציה של כל תת-ניסוי בנפרד לאחר החיתוך,
    • שימוש במפת צימוד וירטואלית אפשר לנו להימנע משערי SWAP נוספים לפעולת 2-Qubit בין ה-Qubits הראשון והאחרון.
  • הסרנו את הקישוריות ארוכת הטווח מהמעגל שעבר טרנספילציה באמצעות חיתוך שערים.
  • המרנו את המעגלים החתוכים למערך שערי הבסיס על ידי החלת מעברי טרנספילציה מתאימים.
  • ביצענו את המעגלים החתוכים על התקן IBM Quantum באמצעות primitive SamplerV2.
  • קבלנו את ערך הציפייה על ידי שיחזור התוצאות של המעגלים החתוכים.

מסקנה

אנו שמים לב מהתוצאות שהממוצע של תצפיות סוג Z\langle Z \rangle במשקל-1 ו-ZZ\langle ZZ \rangle במשקל-2 משתפר באופן משמעותי על ידי חיתוך השערים המחזוריים. שימו לב שמחקר זה אינו כולל טכניקות דיכוי או הפחתת שגיאות כלשהן. השיפור שנצפה נובע אך ורק מהשימוש הנכון בחיתוך שערים עבור בעיה זו. התוצאות יכלו להשתפר עוד יותר על ידי שימוש בטכניקות ההפחתה והדיכוי.

מחקר זה מציג דוגמה לשימוש אפקטיבי בחיתוך שערים כדי לשפר את ביצועי החישוב.

סקר המדריך

אנא השתתף בסקר קצר זה כדי לספק משוב על מדריך זה. התובנות שלך יעזרו לנו לשפר את הצעות התוכן ואת חוויית המשתמש שלנו.

קישור לסקר