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

חיתוך חוטים כהוראת `Move` דו-Qubit

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

אלה הצעדים שנבצע בתבנית Qiskit הזו:

  • שלב 1: מיפוי הבעיה ל-Circuit-ים ואופרטורים:
    • מיפוי ה-Hamiltonian על Circuit קוונטי.
  • שלב 2: אופטימיזציה לחומרה היעד [משתמש ב-cutting addon]:
    • חיתוך ה-Circuit והאובייקט הנצפה.
    • Transpile של תת-הניסויים לחומרה.
  • שלב 3: הרצה על החומרה היעד:
    • הרצת תת-הניסויים שהתקבלו בשלב 2 באמצעות ה-Primitive של Sampler.
  • שלב 4: עיבוד תוצאות [משתמש ב-cutting addon]:
    • שילוב תוצאות שלב 3 לשחזור ערך הציפייה של האובייקט הנצפה.

שלב 1: מיפוי

יצירת Circuit לחיתוך

ראשית, אנחנו מתחילים עם Circuit המבוסס על איור 1(a) ב-arXiv:2302.03366v1.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit import QuantumCircuit

qc_0 = QuantumCircuit(7)
for i in range(7):
qc_0.rx(np.pi / 4, i)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
qc_0.cx(3, 4)
qc_0.cx(3, 5)
qc_0.cx(3, 6)
qc_0.cx(0, 3)
qc_0.cx(1, 3)
qc_0.cx(2, 3)
<qiskit.circuit.instructionset.InstructionSet at 0x7f16ab191a80>
qc_0.draw("mpl")

Quantum circuit diagram

הגדרת אובייקט נצפה

from qiskit.quantum_info import SparsePauliOp

observable = SparsePauliOp(["ZIIIIII", "IIIZIII", "IIIIIIZ"])

שלב 2: אופטימיזציה

יצירת Circuit חדש עם הוראות Move במקומות החיתוך הרצויים

בהינתן ה-Circuit לעיל, ברצוננו להציב שני חיתוכי חוטים על קו ה-Qubit האמצעי, כך שה-Circuit יוכל להתפצל לשני Circuit-ים בני ארבעה Qubit כל אחד. דרך אחת לעשות זאת היא להציב ידנית הוראות Move דו-Qubit שמעבירות את המצב מחוט Qubit אחד לאחר. הוראת Move שקולה מבחינה רעיונית לפעולת Reset על ה-Qubit השני, ואחריה שער SWAP. השפעת הוראה זו היא העברת המצב של ה-Qubit הראשון (המקור) ל-Qubit השני (היעד), תוך השלכת המצב הנכנס של ה-Qubit השני. כדי שזה יעבוד כמצופה, חשוב ש-Qubit היעד (השני) לא ישתף שזירה עם שאר המערכת; אחרת, פעולת ה-Reset תגרום לקריסה חלקית של מצב שאר המערכת.

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

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

from qiskit_addon_cutting.instructions import Move

qc_1 = QuantumCircuit(8)
for i in [*range(4), *range(5, 8)]:
qc_1.rx(np.pi / 4, i)
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)
qc_1.append(Move(), [3, 4])
qc_1.cx(4, 5)
qc_1.cx(4, 6)
qc_1.cx(4, 7)
qc_1.append(Move(), [4, 3])
qc_1.cx(0, 3)
qc_1.cx(1, 3)
qc_1.cx(2, 3)

qc_1.draw("mpl")

Quantum circuit diagram

יצירת אובייקט נצפה לה-Circuit החדש

האובייקט הנצפה הזה מתאים ל-observable, אבל עלינו להתחשב נכון בחוט ה-Qubit הנוסף שנוסף (כלומר, אנחנו מוסיפים "I" באינדקס 4). שים לב שב-Qiskit, ייצוג המחרוזת של qubit-0 מתאים לתו ה-Pauli הימני ביותר.

observable_expanded = SparsePauliOp(["ZIIIIIII", "IIIIZIII", "IIIIIIIZ"])

הפרדת ה-Circuit והאובייקטים הנצפים

כמו במדריכים הקודמים, Qubit-ים שחולקים תווית מחיצה משותפת יקובצו יחד, ושערים לא-מקומיים המתפרסים על יותר ממחיצה אחת ייחתכו.

from qiskit_addon_cutting import partition_problem

partitioned_problem = partition_problem(
circuit=qc_1, partition_labels="AAAABBBB", observables=observable_expanded.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

הצגה חזותית של הבעיה המפורקת

subobservables
{'A': PauliList(['IIII', 'ZIII', 'IIIZ']),
'B': PauliList(['ZIII', 'IIII', 'IIII'])}
subcircuits["A"].draw("mpl")

Quantum circuit diagram

subcircuits["B"].draw("mpl")

Quantum circuit diagram

חישוב עלות הדגימה עבור החיתוכים שנבחרו

כאן אנחנו חותכים שני חוטים, מה שמביא לעלות דגימה של 444^4.

למידע נוסף על עלות הדגימה הנגרמת מחיתוך Circuit, ראה בחומר ההסבר.

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
Sampling overhead: 256.0

יצירת תת-הניסויים להרצה על ה-Backend

generate_cutting_experiments מקבל ארגומנטים circuits/observables כמילונים המחברים בין תוויות מחיצות Qubit לבין ה-subcircuit/subobservables המתאימים.

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

from qiskit_addon_cutting import generate_cutting_experiments

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)

בחירת Backend

כאן אנחנו משתמשים ב-Backend מדומה, מה שיגרום ל-Qiskit Runtime לרוץ במצב מקומי (כלומר, על סימולטור מקומי).

from qiskit_ibm_runtime.fake_provider import FakeManilaV2

backend = FakeManilaV2()

הכנת תת-הניסויים ל-Backend

עלינו לבצע Transpile של ה-Circuit-ים עם ה-Backend שלנו כיעד לפני שליחתם ל-Qiskit Runtime.

from qiskit.transpiler import generate_preset_pass_manager

# Transpile the subexperiments to ISA circuits
pass_manager = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

שלב 3: הרצה

הרצת תת-הניסויים באמצעות ה-Primitive Sampler של Qiskit Runtime

from qiskit_ibm_runtime import SamplerV2, Batch

# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=2**12)
for label, subsystem_subexpts in isa_subexperiments.items()
}
/home/garrison/Qiskit/qiskit-ibm-runtime/qiskit_ibm_runtime/session.py:157: UserWarning: Session is not supported in local testing mode or when using a simulator.
warnings.warn(
# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

שלב 4: עיבוד תוצאות

שחזור ערך הציפייה

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

from qiskit_addon_cutting import reconstruct_expectation_values

reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

השוואת ערך הציפייה המשוחזר עם ערך הציפייה המדויק מה-Circuit והאובייקט הנצפה המקוריים

from qiskit_aer.primitives import EstimatorV2

estimator = EstimatorV2()
exact_expval = estimator.run([(qc_0, observable)]).result()[0].data.evs
print(f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}")
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}")
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 1.51319069
Exact expectation value: 1.59099026
Error in estimation: -0.07779957
Relative error in estimation: -0.04890009