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

חיתוך מעגל להפחתת עומק

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

רקע

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

דרישות

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

  • 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 — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-ibm-runtime
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")

Output of the previous code cell

שלב 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)

Coupling map showing the qubits that will need to be swapped

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

Output of the previous code cell

מציאה וחיתוך של השערים המרוחקים: נחליף את השערים המרוחקים (שערים המחברים 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)

Output of the previous code cell

יצירת ניסויי המשנה להרצה על ה-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

Output of the previous code cell

מצד שני, החיתוך גורם לצורך בדגימה נוספת. כאן חתכנו שלושה שערי CNOT, מה שמביא לעומס דגימה של 939^3. למידע נוסף על עומס הדגימה שנגרם על ידי חיתוך מעגלים, עיינו בתיעוד ערכת הכלים 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 Qiskit Runtime 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

סקר מדריך

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

קישור לסקר