חיתוך מעגל להפחתת עומק
אומדן שימוש: שמונה דקות על מעבד Eagle (הערה: זהו אומדן בלבד. זמן הריצה שלך עשוי להשתנות.)
רקע
מדריך זה מדגים כיצד לבנות Qiskit pattern לחיתוך שערים במעגל קוונטי כדי להפחית את עומק המעגל. לדיון מעמיק יותר בחיתוך מעגלים, בקרו בתיעוד ה-Qiskit addon לחיתוך מעגלים.
דרישות
לפני תחילת מדריך זה, ודאו שהמוצרים הבאים מותקנים אצלכם:
- Qiskit SDK גרסה 2.0 ומעלה, עם תמיכת ויזואליזציה
- Qiskit Runtime גרסה 0.22 ומעלה (
pip install qiskit-ibm-runtime) - התוסף Qiskit לחיתוך מעגלים גרסה 0.9.0 ומעלה (
pip install qiskit-addon-cutting)
הכנה
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_cutting": "qiskit-addon-cutting", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import numpy as np
from qiskit.circuit.library import EfficientSU2
from qiskit.quantum_info import PauliList, Statevector, SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_addon_cutting import (
cut_gates,
generate_cutting_experiments,
reconstruct_expectation_values,
)
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2
שלב 1: מיפוי קלטים קלאסיים לבעיה קוונטית
ניישם את תבנית ה-Qiskit שלנו באמצעות ארבעת השלבים המפורטים בתיעוד. במקרה זה, נדמה ערכי תוחלת על מעגל בעומק מסוים על ידי חיתוך שערים שמביאים לשערי החלפה והרצת ניסויי-משנה על מעגלים רדודים יותר. חיתוך שערים רלוונטי לשלבים 2 (אופטימיזציה של המעגל להרצה קוונטית על ידי פירוק שערים מרוחקים) ו-4 (עיבוד לאחר הריצה לשחזור ערכי התוחלת על המעגל המקורי). בשלב הראשון, נייצר מעגל מספריית המעגלים של Qiskit ונגדיר כמה משתני מדידה.
-
קלט: פרמטרים קלאסיים להגדרת מעגל
-
פלט: מעגל מופשט ומשתני מדידה
circuit = EfficientSU2(num_qubits=4, entanglement="circular").decompose()
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)
observables = PauliList(["ZZII", "IZZI", "IIZZ", "XIXI", "ZIZZ", "IXIX"])
circuit.draw("mpl", scale=0.8, style="iqp")
שלב 2: אופטימיזציה של הבעיה להרצה על חומרה קוונטית
-
קלט: מעגל מופשט ומשתני מדידה
-
פלט: מעגל יעד ומשתני מדידה שהופקו על ידי חיתוך שערים מרוחקים להפחתת עומק המעגל המתורגם
אנחנו בוחרים פריסה התחלתית הדורשת שתי החלפות כדי לבצע את השערים בין qubit 3 ו-0 ושתי החלפות נוספות כדי להחזיר את ה-qubits למיקומם ההתחלתי. אנחנו בוחרים optimization_level=3, שהיא רמת האופטימיזציה הגבוהה ביותר הזמינה עם מנהל מעברים קבוע מראש.
service = QiskitRuntimeService()
backend = service.least_busy(
operational=True, min_num_qubits=circuit.num_qubits, simulator=False
)
pm = generate_preset_pass_manager(
optimization_level=3, initial_layout=[0, 1, 2, 3], backend=backend
)
transpiled_qc = pm.run(circuit)

print(f"Transpiled circuit depth: {transpiled_qc.depth()}")
transpiled_qc.draw("mpl", scale=0.4, idle_wires=False, style="iqp", fold=-1)
Transpiled circuit depth: 103
מציאה וחיתוך של השערים המרוחקים: נחליף את השערים המרוחקים (שערים המחברים qubits לא מקומיים, 0 ו-3) באובייקטים מסוג TwoQubitQPDGate על ידי ציון האינדקסים שלהם. cut_gates יחליף את השערים באינדקסים שצוינו באובייקטים מסוג TwoQubitQPDGate ויחזיר גם רשימה של מופעי QPDBasis - אחד לכל פירוק שער. האובייקט QPDBasis מכיל מידע על איך לפרק את השערים החתוכים לפעולות של qubit בודד.
# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]
# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)
qpd_circuit.draw("mpl", scale=0.8)
יצירת ניסויי המשנה להרצה על ה-backend: generate_cutting_experiments מקבל מעגל המכיל מופעי TwoQubitQPDGate ומשתני מדידה כ-PauliList.
כדי לדמות את ערך התוחלת של המעגל בגודל המלא, ניסויי משנה רבים מיוצרים מההתפלגות הקוואזי-הסתברותית המשולבת של השערים המפורקים ולאחר מכן מבוצעים על backend אחד או יותר. מספר הדגימות שנלקחות מההתפלגות נשלט על ידי num_samples, ומקדם משולב אחד ניתן לכל דגימה ייחודית. למידע נוסף על אופן חישוב המקדמים, ראו בחומר ההסבר.
# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observables, num_samples=np.inf
)
לצורך השוואה, אנחנו רואים שניסויי המשנה QPD יהיו רדודים יותר לאחר חיתוך השערים המרוחקים: זו דוגמה לניסוי משנה שנבחר באופן שרירותי שנוצר מהמעגל QPD. העומק שלו הופחת ביותר מחצי. יש ליצור ולהעריך ניסויי משנה הסתברותיים רבים כאלה על מנת לשחזר ערך תוחלת של המעגל העמוק יותר.
# Transpile the decomposed circuit to the same layout
transpiled_qpd_circuit = pm.run(subexperiments[100])
print(f"Original circuit depth after transpile: {transpiled_qc.depth()}")
print(
f"QPD subexperiment depth after transpile: {transpiled_qpd_circuit.depth()}"
)
transpiled_qpd_circuit.draw(
"mpl", scale=0.6, style="iqp", idle_wires=False, fold=-1
)
Original circuit depth after transpile: 103
QPD subexperiment depth after transpile: 46
מצד שני, החיתוך גורם לצורך בדגימה נוספת. כאן חתכנו שלושה שערי CNOT, מה שמביא לעומס דגימה של . למידע נוסף על עומס הדגימה שנגרם על ידי חיתוך מעגלים, ראו בתיעוד ערכת הכלים Circuit Knitting.
print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 729.0
שלב 3: הרצה באמצעות פרימיטיבים של Qiskit
הרצת מעגלי היעד ("ניסויי המשנה") עם הפרימיטיב Sampler.
-
קלט: מעגלי יעד
-
פלט: התפלגויות קוואזי-הסתברות
# Transpile the subexperiments to the backend's instruction set architecture (ISA)
isa_subexperiments = pm.run(subexperiments)
# Set up the IBM Quantum Sampler primitive. For a fake backend, this will use a local simulator.
sampler = SamplerV2(backend)
# Submit the subexperiments
job = sampler.run(isa_subexperiments)
# Retrieve the results
results = job.result()
print(job.job_id())
czypg1r6rr3g008mgp6g
שלב 4: עיבוד לאחר ההרצה והחזרת התוצאה בפורמט קלאסי הרצוי
שימוש בתוצאות ניסויי המשנה, משתני מדידת המשנה ומקדמי הדגימה לשחזור ערך התוחלת של המעגל המקורי.
קלט: התפלגויות קוואזי-הסתברות פלט: ערכי תוחלת משוחזרים
reconstructed_expvals = reconstruct_expectation_values(
results,
coefficients,
observables,
)
# Reconstruct final expectation value
final_expval = np.dot(reconstructed_expvals, [1] * len(observables))
print("Final reconstructed expectation value")
print(final_expval)
Final reconstructed expectation value
1.0751342773437473
ideal_expvals = [
Statevector(circuit).expectation_value(SparsePauliOp(observable))
for observable in observables
]
print("Ideal expectation value")
print(np.dot(ideal_expvals, [1] * len(observables)).real)
Ideal expectation value
1.2283177520039992
סקר מדריך
מלאו את הסקר הקצר הזה כדי לספק משוב על המדריך. התובנות שלכם יעזרו לנו לשפר את הצעות התוכן וחוויית המשתמש שלנו.
הערה: סקר זה נערך על ידי IBM Quantum ומכסה את תוכן המדריך (שנכתב על ידי IBM). doQumentation מספקת את האתר, התרגומים, והרצת הקוד — למשוב על אלה, אנא פתחו issue ב-GitHub.