חיתוך חוטים לאומדן ערכי תוחלת
אומדן שימוש: 22 שניות על מעבד Heron (הערה: זהו אומדן בלבד. זמן הריצה שלך עשוי להשתנות.)
תוצאות למידה
לאחר סיום מדריך זה, המשתמשים צריכים להבין:
- כיצד להשתמש ב-
qiskit-addon-cuttingכדי לחלק מעגל גדול למעגלי משנה קטנים יותר, ובכך להפחית את השפעת הרעש
דרישות מוקדמות
אנחנו מציעים שהמשתמשים יהיו מכירים את הנושא הבא לפני עבור מדריך זה:
- שימוש בפרימיטיב Sampler, המשמש בתהליך עבודה זה
רקע
Circuit-knitting הוא מונח גג המקיף שיטות שונות לחלוקת מעגל למספר מעגלי משנה קטנים יותר הכוללים פחות שערים או קיוביטים. כל אחד ממעגלי המשנה יכול להתבצע באופן עצמאי, והתוצאה הסופית מתקבלת על ידי עיבוד קלאסי מסוים על תוצאת כל מעגל משנה. טכניקה זו נגישה ב-Circuit cutting Qiskit addon; ראו את התיעוד יחד עם חומר מבוא נוסף להסבר מפורט של הטכניקה.
מדריך זה מתמקד בשיטה הנקראת חיתוך חוטים, שבה המעגל מחולק לאורך החוט [1], [2]. שימו לב שחלוקה פשוטה במעגלים קלאסיים מכיוון שהתוצאה בנקודת החלוקה ניתנת לקביעה דטרמיניסטית, והיא 0 או 1. עם זאת, מצב הקיוביט בנקודת החיתוך הוא, באופן כללי, מצב מעורב. לכן, יש למדוד כל מעגל משנה מספר פעמים בבסיסים שונים (בדרך כלל בסיס שלם טומוגרפית, כגון בסיס Pauli [3], [4]) ובהתאם להכין אותו במצב העצמי שלו. האיור למטה (באדיבות: [7]) מציג דוגמה של חיתוך חוטים למצב GHZ של ארבעה קיוביטים לשלושה מעגלי משנה. כאן מציין קבוצת בסיסים (בדרך כלל Pauli X, Y ו-Z), ו- מציין קבוצת מצבים עצמיים (בדרך כלל , , ו-).
מכיוון שלכל מעגל משנה יש פחות קיוביטים ושערים, הם צפויים להיות פחות רגישים לרעש. מדריך זה מציג דוגמה שבה ניתן להשתמש בשיטה זו לדיכוי יעיל של הרעש במערכת.
דרישות
לפני שמתחילים את המדריך הזה, ודאו שהדברים הבאים מותקנים:
- Qiskit SDK v2.0 ומעלה, עם תמיכה ב-visualization
- Qiskit Runtime v0.22 ומעלה (
pip install qiskit-ibm-runtime) - Circuit cutting Qiskit addon v0.10.0 ומעלה (
pip install qiskit-addon-cutting) - Qiskit addon utils 0.3 ומעלה (
pip install qiskit-addon-utils) - Qiskit Aer (
pip install qiskit-aer)
הגדרות
# Added by doQumentation — required packages for this notebook
!pip install -q matplotlib numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
import matplotlib.pyplot as plt
from qiskit.circuit import Parameter, ParameterVector, QuantumCircuit
from qiskit.quantum_info import PauliList, SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_aer import AerSimulator
from qiskit.result import sampled_expectation_value
from qiskit_addon_cutting.instructions import CutWire
from qiskit_addon_cutting import (
cut_wires,
expand_observables,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2, Batch
דוגמה בסימולטור בקנה מידה קטן
מדריך זה מממש תבנית Qiskit לסימולציה של מעגל Many-Body Localization (MBL) חד-ממדי (1D). מעגל MBL הוא מעגל יעיל לחומרה והוא מפורמט על ידי שני פרמטרים ו-. כאשר מוגדר ל- והמצב ההתחלתי מוכן ב- עבור כל הקיוביטים, ערך התוחלת האידיאלי של הוא עבור כל אתר קיוביט ללא תלות בערכים של . פרטים נוספים על מעגל זה זמינים ב-מאמר זה.
שימו לב שבסימולטור ללא רעש, ערך התוחלת שהתקבל עם חיתוך מעגל ובלעדיו יהיה זהה.
שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית
בניית מעגל MBL החד-ממדי
ראשית, אנחנו מציגים פונקציה לבניית מעגל MBL החד-ממדי.
class MBLChainCircuit(QuantumCircuit):
def __init__(
self, num_qubits: int, depth: int, use_cut: bool = False
) -> None:
super().__init__(
num_qubits, name=f"MBLChainCircuit<{num_qubits}, {depth}>"
)
evolution = MBLChainEvolution(num_qubits, depth, use_cut)
self.compose(evolution, inplace=True)
class MBLChainEvolution(QuantumCircuit):
def __init__(self, num_qubits: int, depth: int, use_cut) -> None:
super().__init__(
num_qubits, name=f"MBLChainEvolution<{num_qubits}, {depth}>"
)
theta = Parameter("θ")
phis = ParameterVector("φ", num_qubits)
for layer in range(depth):
layer_parity = layer % 2
# print("layer parity", layer_parity)
for qubit in range(layer_parity, num_qubits - 1, 2):
# print(qubit)
self.cz(qubit, qubit + 1)
self.u(theta, 0, np.pi, qubit)
self.u(theta, 0, np.pi, qubit + 1)
if (
use_cut
and layer_parity == 0
and (
qubit == num_qubits // 2 - 1
or qubit == num_qubits // 2
)
):
self.append(CutWire(), [num_qubits // 2])
if use_cut and layer < depth - 1 and layer_parity == 1:
if qubit == num_qubits // 2:
self.append(CutWire(), [qubit])
for qubit in range(num_qubits):
self.p(phis[qubit], qubit)
num_qubits = 10
depth = 2
mbl = MBLChainCircuit(num_qubits, depth)
mbl.draw("mpl", fold=-1)
אנחנו מחשבים את ערך התוחלת הממוצע על פני כל הקיוביטים עבור . מכיוון שערך התוחלת האידיאלי של , ערך התוחלת האידיאלי של הוא גם . הפרמטרים נבחרים באקראי.
np.random.seed(42)
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
המעגל צריך להיות מסומן על ידי הוספת CutWire במיקומים הרצויים כדי לחלק אותו. עבור מדריך זה, אנחנו בוחרים בחלוקה שווה. מעגל MBL מתוכנן כך שהגדרת use_cut=True בפונקציה מוסיפה את הסימון כראוי אחרי קיוביטים, כאשר הוא מספר הקיוביטים במעגל המקורי. כמו כן, הקצנו את הפרמטרים שנוצרו באקראי למעגל.
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_cut.draw("mpl", fold=-1)
שלב 2: אופטימיזציה של הבעיה לביצוע על חומרה קוונטית
חיתוך המעגל למעגלי משנה קטנים יותר
כעת אנחנו מחלקים את המעגל לשני מעגלי משנה קטנים יותר באמצעות qiskit-addon-cutting. qiskit-addon-cutting מוסיף שער Move וירטואלי כדי לפצל את מיקום חיתוך החוט על ידי התאמת מספר הקיוביטים בצורה מתאימה. כעת אנחנו יוצרים את המעגל עם שער וירטואלי זה. מכיוון שיש חיתוך חוט אחד, מספר הקיוביטים המשויכים יגדל באחד.
mbl_move = cut_wires(mbl_cut)
mbl_move.draw("mpl", fold=-1)
בניית והרחבת הנצפה
הנצפה, כפי שהוגדר קודם, יהיה הממוצע של על כל קיוביט. עם זאת, לאחר הוספת שער Move הוירטואלי, מספר הקיוביטים האפקטיבי במעגל גדל. הנצפה חייב להיות מורחב בהתאם כדי להתחשב בשינוי זה במספר הקיוביטים. שימו לב שהנצפה פועל תמיד באופן טריוויאלי (כ-) על הקיוביט הנוסף שנוסף עבור שער Move הוירטואלי.
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
observable
PauliList(['ZIIIIIIIII', 'IZIIIIIIII', 'IIZIIIIIII', 'IIIZIIIIII',
'IIIIZIIIII', 'IIIIIZIIII', 'IIIIIIZIII', 'IIIIIIIZII',
'IIIIIIIIZI', 'IIIIIIIIIZ'])
new_obs = expand_observables(observable, mbl, mbl_move)
new_obs
PauliList(['ZIIIIIIIIII', 'IZIIIIIIIII', 'IIZIIIIIIII', 'IIIZIIIIIII',
'IIIIZIIIIII', 'IIIIIIZIIII', 'IIIIIIIZIII', 'IIIIIIIIZII',
'IIIIIIIIIZI', 'IIIIIIIIIIZ'])
כעת ניתן לחלק את המעגל לאורך שער Move ואנחנו מקבלים את מעגלי המשנה, כמו גם את הנצפה-המשנה, שהוא החלק מהנצפה המקורי המשויך לכל מעגל משנה.
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
כאן אנחנו מציגים חזותית את שני מעגלי המשנה:
subcircuits[0].draw("mpl", fold=-1)
subcircuits[1].draw("mpl", fold=-1)
הרחבת הנצפה באמצעות פעולת Move דורשת מבנה נתונים PauliList. כדי לשחזר את ערך התוחלת של המעגל המקורי, אנחנו זקוקים לנצפה בפורמט SparsePauliOp.
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
כפי שנדון קודם לכן, עבור כל חיתוך המעגל הזרם-עלי חייב להימדד בבסיס Pauli, והמעגל הזרם-תחתי חייב להיות מוכן במצב העצמי של הבסיס. הפונקציה generate_cutting_experiments יוצרת את כל המעגלים הנדרשים הללו והמקדמים המשויכים לכל מעגל הדרושים לשחזור. מצאו פרטים נוספים ב-מאמר זה.
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
העברת המעגלים להתאמה לחומרה
עבור הדוגמה הראשונה הכוללת רק סימולציה, אנחנו מעבירים את המעגל לקבוצת שערי הבסיס של הלקצה:
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, simulator=False, min_num_qubits=133
)
print(backend)
<IBMBackend('ibm_fez')>
שלב 3: ביצוע באמצעות Qiskit primitives
כעת, בצעו כל subexperiment:
pm_basis = generate_preset_pass_manager(
optimization_level=2, basis_gates=backend.configuration().basis_gates
)
basis_subexperiments = {
label: pm_basis.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
sampler = SamplerV2(mode=AerSimulator())
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in basis_subexperiments.items()
}
שלב 4: עיבוד לאחר והחזרת תוצאה בפורמט קלאסי רצוי
כעת אנחנו מאחזרים את תוצאת כל הרצת subexperiment ומשחזרים את ערך התוחלת של המעגל הלא חתוך:
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
reconstructed_expval
np.float64(0.9953821063041687)
methods = [
"Uncut",
"Wire cut",
]
values = [
1,
reconstructed_expval,
] # since the ideal expectation value in noiseless simulation is +1
ax = plt.gca()
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
ax.set_ylabel(r"$M_Z$", fontsize=12)
Text(0, 0.5, '$M_Z$')
דוגמת חומרה בקנה מידה גדול
כעת אנחנו מדגימים חיתוך חוטים עבור מעגל MBL של 60 קיוביטים. המעגל הלא חתוך, וכן המעגלים החתוכים, יבוצעו על חומרת IBM Quantum®:
num_qubits = 60
depth = 2
# construct the circuit
mbl = MBLChainCircuit(num_qubits, depth)
# create parameters
phis = list(np.random.rand(mbl.num_parameters - 1))
theta = [0]
params = theta + phis
# construct the cut circuit
mbl_cut = MBLChainCircuit(num_qubits, depth, use_cut=True)
mbl_cut.assign_parameters(params, inplace=True)
mbl_move = cut_wires(mbl_cut)
# Define observable and expand to account for the wire cut
observable = PauliList(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)]
)
new_obs = expand_observables(observable, mbl, mbl_move)
# Construct a SparsePauliOp version of the observable for later use in reconstruction
M_z = SparsePauliOp(
["I" * i + "Z" + "I" * (num_qubits - i - 1) for i in range(num_qubits)],
coeffs=[1 / num_qubits] * num_qubits,
)
# Partition the circuit and get subcircuits and subobservables
partitioned_problem = partition_problem(circuit=mbl_move, observables=new_obs)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
# Obtain subexperiments and coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits,
observables=subobservables,
num_samples=np.inf,
)
# Transpile the subexperiments to the backend
pm = generate_preset_pass_manager(optimization_level=2, backend=backend)
isa_subexperiments = {
label: pm.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}
# Execute the subexperiments and retrieve results
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
sampler.options.environment.job_tags = ["TUT_WC"]
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
results = {label: job.result() for label, job in jobs.items()}
# Reconstruct the expectation value of the original observable
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, M_z.coeffs).real
# Compute the uncut circuit to obtain the noisy expectation value for comparison
sampler = SamplerV2(mode=backend)
sampler.options.environment.job_tags = ["TUT_WC"]
if mbl.num_clbits == 0:
mbl.measure_all()
isa_mbl = pm.run(mbl)
pub = (isa_mbl, params)
uncut_job = sampler.run([pub])
uncut_counts = uncut_job.result()[0].data.meas.get_counts()
uncut_expval = sampled_expectation_value(uncut_counts, M_z)
# visualize the results
ax = plt.gca()
methods = ["uncut", "cut"]
values = [uncut_expval, reconstructed_expval]
plt.bar(methods, values, color="#a56eff", width=0.4, edgecolor="#8a3ffc")
plt.axhline(y=1, color="k", linestyle="--")
plt.text(0.3, 0.95, "Exact result")
plt.show()
uncut_expval
0.9202473958333336
הצעדים הבאים
אם מצאת עבודה זו מעניינת, ייתכן שתתעניין בחומר הבא:
אסמכתאות
[1] Peng, T., Harrow, A. W., Ozols, M., & Wu, X. (2020). Simulating large quantum circuits on a small quantum computer. Physical review letters, 125(15), 150504.
[2] Tang, W., Tomesh, T., Suchara, M., Larson, J., & Martonosi, M. (2021, April). Cutqc: using small quantum computers for large quantum circuit evaluations. In Proceedings of the 26th ACM International conference on architectural support for programming languages and operating systems (pp. 473-486).
[3] Perlin, M. A., Saleem, Z. H., Suchara, M., & Osborn, J. C. (2021). Quantum circuit cutting with maximum-likelihood tomography. npj Quantum Information, 7(1), 64.
[4] Majumdar, R., & Wood, C. J. (2022). Error mitigated quantum circuit cutting. arXiv preprint arXiv:2211.13431.
[5] Khare, T., Majumdar, R., Sangle, R., Ray, A., Seshadri, P. V., & Simmhan, Y. (2023). Parallelizing Quantum-Classical Workloads: Profiling the Impact of Splitting Techniques. In 2023 IEEE International Conference on Quantum Computing and Engineering (QCE) (Vol. 1, pp. 990-1000). IEEE.
[6] Bhoumik, D., Majumdar, R., Saha, A., & Sur-Kolay, S. (2023). Distributed Scheduling of Quantum Circuits with Noise and Time Optimization. arXiv preprint arXiv:2309.06005.
[7] Majumdar, R. (2024). Efficient Reduction of Resources and Noise in Discrete Quantum Computing Circuits (Doctoral dissertation, Indian Statistical Institute - Kolkata). https://www.proquest.com/openview/b481def90b1cc80e6b58a77c99e8385c/1?pq-origsite=gscholar&cbl=2026366&diss=y