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

סימולציה של מודל Ising מובה עם פונקציית TEM

שיטת Tensor-network Error Mitigation (TEM) של Algorithmiq היא אלגוריתם היברידי קוונטי-קלאסי שנועד לביצוע הפחתת רעש לחלוטין בשלב העיבוד הקלאסי שלאחר המדידה. עם TEM, המשתמש יכול לחשב ערכי תוחלת של גדלים נמדדים (observables), תוך הפחתת שגיאות הרעש הבלתי נמנעות המתרחשות בחומרה קוונטית בדיוק ויעילות עלות גבוהים יותר, מה שהופך אותו לאפשרות אטרקטיבית ביותר עבור חוקרי קוונטים ואנשי תעשייה כאחד.

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

הערכת שימוש: מחברת זו משתמשת בכ-10 דקות QPU על מכשירי Heron r3. זמן הריצה יכול להשתנות משמעותית בהתאם למכשיר הנבחר. הערכות שימוש לפי סעיפים ניתן למצוא להלן.

הרצת ניסויי פיזיקה של גופים רבים עם הפחתת שגיאות בעזרת פונקציית TEM

מדריך זה מבוסס על ההפניה הבאה: L. E. Fischer et al., Nat. Phys. (2026). הפניה זו דנה בסימולציה אמיתית על חומרה קוונטית של עד 91 קיוביטים. במדריך זה, אנו מחדשים סימולציה דומה על גודל מעגל קטן יותר.

מודל Ising המובה תואם למודל Ising הרגיל:

H^I=Jn=0N2Z^nZ^n+1+hn=0N1Z^n\hat{H}_{\text{I}} = J \sum_{n=0}^{N-2} \hat{Z}_n \hat{Z}_{n+1} + h \sum_{n=0}^{N-1} \hat{Z}_n

אליו מוחל מובה רוחבי:

H^K=bn=0N1X^n\hat{H}_{K} = b \sum_{n=0}^{N-1} \hat{X}_n

המטרה היא לסמלץ את הדינמיקה של מצב תחת ה-Hamiltonian של Ising המובה הרוחבי, שאת אבולוציית הזמן שלו ניתן לממש באמצעות הצומד ה-Floquet U^KI=eiH^KeiH^I\hat{U}_{\text{KI}} = e^{-i \hat{H}_K} e^{-i \hat{H}_I}. המצב ההתחלתי לאבולוציה הוא זה שבו הקיוביט הראשון נמצא במצב +|+\rangle, בעוד שהאחרים מזווגים ומוצבים במצב Bell (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}.

הכמות שאנחנו רוצים לצפות בה היא פונקציית הקורלציה. מאמר ההפניה דן כיצד ניתן לכתוב מחדש כמות זו כאופרטור Pauli X^\hat{X} על הקיוביט ה-nn. לאחר מספר צעדי זמן פיזיים tt, אנו מחשבים את ערך אופרטור Pauli X^n=t\hat{X}_{n=t}. בהתאם לפרמטרים של המערכת, ערך הגודל הנמדד הזה שווה לערך שניתן לחשב במדויק, או לערך שניתן לסמלץ רק באמצעות שיטות מקורבות. ספציפית, עבור J=b=π/4|J|=|b|=\pi/4 הוא שווה ל-[cos(2h)]t[\cos(2h)]^t, שהוא הערך שנשתמש בו כמדד להשוואה לתוצאות המדריך. יתרה מכך, בצעד זמן נתון tt, X^nt\langle\hat{X}_{n\neq t}\rangle הוא אפס. לפרטים כיצד לקבל ערכים אלה, ולהשוואה עם תוצאות סימולציה קלאסית מקורבת מחוץ לפרמטרים אלה, ראה L. E. Fischer et al., Nat. Phys. (2026).

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

הגדרה

כדרישה מוקדמת, ודא שהתלויות הנחוצות מותקנות.

%pip install numpy matplotlib qiskit qiskit-ibm-catalog qiskit-ibm-runtime pylatexenc qiskit_qasm3_import
import os
from matplotlib import pyplot as plt
import numpy as np

from qiskit.quantum_info import SparsePauliOp
from qiskit.qasm3 import load

from qiskit_ibm_catalog import QiskitFunctionsCatalog

הפחתת שגיאות עם TEM

אנחנו מספקים כאן מעגל המממש את מודל Ising המובה שתואר לעיל. המעגל מוכן כך. ראשית, ישנה שלב הכנת מצב, שבו הקיוביט הראשון נמצא במצב +|+\rangle, בעוד שהאחרים בזוגות Bell (00+11)/2(|00\rangle + |11\rangle)/\sqrt{2}. לאחר מכן מגיעה מבנה הלבנים שמממש את אבולוציית הזמן האונית U^KI\hat{U}_{\text{KI}}. מספר צעדי הזמן הפיזיים תואם ל-t/2t/2 שכבות מעגל. הקוד הבא מוריד את שני קבצי ה-QASM הדרושים למדריך זה.

# Download required QASM files
import urllib

urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/swy5jtq309b0xpzluzlmsmj908yphes8.qasm",
"ki_30q.qasm",
)
urllib.request.urlretrieve(
"https://ibm.box.com/shared/static/et3gkodonw6gsp2trs43lzaozrdtiu7s.qasm",
"ki_12q.qasm",
)

אפשר לדמיין גרסה קטנה של המעגל, עם 12 קיוביטים ושישה צעדי זמן:

# Parameters of the kicked Ising model
h = 0.0
num_qubits = 12
t_steps = 6

# Load the circuit for the kicked Ising model
small_circuit = load("ki_12q.qasm")

# Draw the circuit
small_circuit.draw("mpl", scale=0.25, fold=-1)

Output of the previous code cell

לאחר מכן, בנה את הגודל הנמדד (observable), X^n=t\hat{X}_{n=t}. הוא בנוי כמחרוזת Pauli פשוטה עם הסדר התואם את זה שנעשה בו שימוש על ידי Qiskit:

def xt_observable(n_qubits, t_steps):
pauli_str = "".join(["I" * t_steps, "X", "I" * (n_qubits - t_steps - 1)])
pauli_str = pauli_str[::-1] # Reverse the string to match qiskit order
return SparsePauliOp(data=pauli_str, coeffs=1.0)

בדוגמת 12 הקיוביטים הקטנה שלנו, הגודל הנמדד נראה כך:

# Build the observable for the kicked Ising model
small_observable = xt_observable(n_qubits=12, t_steps=6)
print(small_observable)
SparsePauliOp(['IIIIIXIIIIII'],
coeffs=[1.+0.j])

פונקציות Qiskit משתמשות ב-PUBs כדרך לאסוף את הקלטים. במקרה שלנו, בואו נשקול מעגל וגודל נמדד בודדים כ-PUB שלנו:

# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(small_circuit, [small_observable])]

לאחר מכן, אנחנו מקבלים גישה לפונקציית TEM. ראשית, נגדיר את האימות הנדרש ל-IBM Cloud ונבחר Backend מהמכשירים הזמינים. ניתן לקבל את הטוקן, ה-Backends הזמינים ושמות משאבי הענן המתאימים (CRN) על ידי כניסה לחשבונך ב-לוח הבקרה של IBM Quantum Platform.

# Set IBM Quantum credentials and backend configuration
personal_token = os.environ.get(
"QISKIT_IBM_TOKEN", "<API-KEY>"
) # Replace with your personal token or set the environment variable
channel = "ibm_quantum_platform"
crn = "your_crn" # Replace with the Cloud Resource Name (CRN)

# Select the QPU backend
backend_name = "ibm_qpu_name" # Replace with your desired backend's name

טען את פונקציית TEM מ-קטלוג פונקציות Qiskit:

# Load the TEM function from the Qiskit Functions Catalog
catalog = QiskitFunctionsCatalog(
channel=channel,
token=personal_token,
instance=crn,
)
tem = catalog.load("algorithmiq/tem")

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

tem_job = tem.run(pubs=pubs, backend_name=backend_name)

עם אפשרויות ברירת מחדל, פונקציית TEM מריצה שלוש משימות על המחשב הקוונטי: למידת רעש, הפחתת שגיאת קריאה ודגימת מעגל. מספר ה-shots שנעשה בהם שימוש על ידי כל אחת מהן יכול להשתנות באפשרויות שמועברות לפונקציה. כברירת מחדל, הפרמטרים הללו מוגדרים להשגת דיוק של 0.05 בערכי הציפייה המוקלים. ניתן לבדוק את מצב המשימה שלך ב-לוח הבקרה של IBM Quantum Platform או עם:

print(tem_job.status())
QUEUED

כאשר הסטטוס הוא DONE, ניתן לבדוק את התוצאות הגולמיות והמוקלות. ה-tem_evs המוגדרים להלן הם ערכי התוחלת של הגדלים הנמדדים (observables) המבוקשים, במקרה זה רק גודל נמדד אחד, X^n=t\langle \hat X_{n=t}\rangle, ו-tem_std הם סטיות התקן המתאימות.

# Get the results of the TEM job
tem_results = tem_job.result()[
0
] # Get the first and only result from the job
tem_evs = tem_results.data.evs[0]
tem_std = tem_results.data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")
TEM Result: 1.031 ± 0.046

אפשר גם לבדוק כמה זמן ריצה קוונטי שימש לכל קריאה ב-IBM Quantum Platform, או על ידי בדיקת המטא-דאטה של התוצאות מקוד Python.

# Get the TEM job runtime
tem_runtime = tem_job.result().metadata["resource_usage"][
"RUNNING: EXECUTING_QPU"
]["QPU_TIME"]

print(f"TEM Runtime: {tem_runtime} seconds")
TEM Runtime: 155.0 seconds

התאמת פרמטרי TEM ואפשרויות מתקדמות

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

אפשרויות מתקדמות נפוצות הן:

  • precision: ציון הדיוק המטרה עבור ערכי הציפייה המוקלים.
  • default_shots: במקום precision, ניתן לציין את מספר ה-shots המשמשים במשימת המדידה.
  • tem_max_bond_dimension: הממד הקשר המקסימלי המשמש ברשת הטנסורים.
  • tem_compression_cutoff: ערך הקיצוץ המשמש לרשת הטנסורים.
  • אפשרויות למידת רעש: הגדרת שיטת ייחוס הרעש, כגון מספר האיטרציות או מעגלי כיול ספציפיים.
  • private: הבטחת פרטיות המעגלים ותוצאות הניסוי עבורך וביטול הורדות מרובות של תוצאות המשימה.

עיין ב-תיעוד TEM או ב-קטלוג Qiskit Functions לקבלת רשימה מלאה של האפשרויות הנתמכות ותיאוריהן. ניתן לכוונן פרמטרים אלה לאיזון בין זמן ריצה, שימוש במשאבים ודיוק התוצאות. ניתן להעביר אפשרויות אלה כמילון לארגומנט options בעת הרצת פונקציית TEM:

options = {
"default_shots": 10_000,
"tem_max_bond_dimension": 512,
"tem_compression_cutoff": 1e-16,
# This option helps optimizing the measurement
# stage since the observable is strongly biased
# toward the X operator for all the qubits.
"compute_shadows_bias_from_observable": True,
# set to True to keep experiment results private,
# recommended for confidential circuits
"private": False,
}

ניתן גם להעביר אפשרויות מותאמות אישית ללומד הרעש. ההגדרות עוקבות אחר אלה שנמצאות ב-Qiskit Runtime NoiseLearnerOptions:

nl_options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32],
}

# add noise learning options to the overall options
options |= nl_options

הרץ שוב את הניסוי עם אפשרויות מותאמות אישית אלה שמוגדרות עבור המעגל שלנו. זמן הריצה הצפוי הוא כ-4 דקות QPU.

tem_job_custom = tem.run(
pubs=pubs, backend_name=backend_name, options=options
)

אם המשימה לא הוגדרה כפרטית, ניתן לשחזר את התוצאה מאוחר יותר. לשם כך, שמור את מזהה המשימה המודפס כאן והשתמש ב-tem_job_custom = catalog.get_job_by_id("your-job-id").

job_id = tem_job_custom.job_id
print(f"Job ID: {job_id}")
Job ID: 1ba10094-a541-457a-9287-dbd49306d12d
results_custom = tem_job_custom.result()
tem_evs = results_custom[0].data.evs[0]
tem_std = results_custom[0].data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")
TEM Result: 0.956 ± 0.018

עכשיו אפשר לבחון את התוצאות והמטא-דאטה לקבלת תובנות על הניסוי:

metadata_custom = results_custom[0].metadata

unmitigated_evs = metadata_custom["evs_non_mitigated"][0]
unmitigated_stds = metadata_custom["stds_non_mitigated"][0]
print(f"Unmitigated Result: {unmitigated_evs:.3f} ± {unmitigated_stds:.3f}")

# Exact result for the kicked Ising model from the reference paper
exact_evs = np.cos(2 * h) ** t_steps
print("Exact Result:", exact_evs)
Unmitigated Result: 0.894 ± 0.015
Exact Result: 1.0
# Plot comparing the different expectation values
plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()

Output of the previous code cell

לבסוף, אפשר לבדוק את ההשפעה של האפשרויות המותאמות אישית על זמן ריצת QPU והקלאסי:

# Get the metadata of the TEM job
job_metadata = results_custom.metadata

# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)

print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")
QPU Runtime: 342.0 seconds
Classical Runtime: 107.632604 seconds

הרחבת TEM למעגלים גדולים

ניתן להריץ מעגלים גדולים עם פונקציית TEM באופן עקרוני. עם זאת, חשוב לדעת את מגבלות המשאבים הקלאסיים, שכן TEM מתבצע על מנהלי IBM Cloud עם זמני ריצה ארוכים אפשריים. עבור מעגלים גדולים מאוד, צור קשר עם צוות התמיכה של TEM בכתובת qiskit_ibm@algorithmiq.fi.

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

# Kicked Ising model parameters
n_qubits = 30
t_steps = 15
h = 0.0

# Load the circuit for the kicked Ising model
circuit = load("ki_30q.qasm")

# Build the observable for the kicked Ising model
observable = xt_observable(n_qubits=n_qubits, t_steps=t_steps)

# Collect the input PUBs, in this case composed of a
# single circuit and observable
pubs = [(circuit, [observable])]

בואו נגדיר כמה אפשרויות מכוונות לביצועים:

options = {
"num_randomizations": 32,
"max_layers_to_learn": 2,
"shots_per_randomization": 128,
"layer_pair_depths": [0, 1, 2, 4, 16, 32, 64],
"default_shots": 5_000,
"tem_max_bond_dimension": 128,
"tem_compression_cutoff": 1e-10,
"compute_shadows_bias_from_observable": True,
"private": False,
}

לבסוף, הרץ את הניסוי, קבל את התוצאה, ודמיין אותה. זה ייקח כ-3.5 דקות QPU.

tem_job_large = tem.run(pubs=pubs, backend_name=backend_name, options=options)
job_id = tem_job_large.job_id
print(f"Job ID: {job_id}")
Job ID: 9f3f190f-f4b0-4dcb-bb83-5f71f37d0d77
results_large = tem_job_large.result()
tem_evs = results_large[0].data.evs[0]
tem_std = results_large[0].data.stds[0]

print(f"TEM Result: {tem_evs:.3f} ± {tem_std:.3f}")

# Get the metadata of the TEM job
job_metadata = tem_job_large.result().metadata

# Get the runtime of the TEM job
qpu_runtime = job_metadata["resource_usage"]["RUNNING: EXECUTING_QPU"][
"QPU_TIME"
]
classical_runtime = (
job_metadata["resource_usage"]["RUNNING: OPTIMIZING_FOR_HARDWARE"][
"CPU_TIME"
]
+ job_metadata["resource_usage"]["RUNNING: POST_PROCESSING"]["CPU_TIME"]
)

print(f"QPU Runtime: {qpu_runtime} seconds")
print(f"Classical Runtime: {classical_runtime} seconds")
TEM Result: 0.794 ± 0.026
QPU Runtime: 203.0 seconds
Classical Runtime: 251.71805499999996 seconds
# Plot comparing the different expectation values
metadata_large = results_large[0].metadata
unmitigated_evs = metadata_large["evs_non_mitigated"][0]
unmitigated_stds = metadata_large["stds_non_mitigated"][0]

exact_evs = np.cos(2 * h) ** t_steps

plt.bar(
["Unmitigated", "TEM"],
[unmitigated_evs, tem_evs],
yerr=[unmitigated_stds, tem_std],
color=["grey", "c"],
)
plt.hlines(y=exact_evs, xmin=-0.5, xmax=1.5, colors="r", linestyles="dashed")
plt.ylabel("Expectation Value")
plt.ylim(0, 1.1)
plt.show()

Output of the previous code cell