דלג לתוכן הראשי
פרויקט זה בשלב בטא. מצאת באג? פתח issue ב-GitHub. יש שאלה או רעיון? שאל ב-Discussions — נשמח לשמוע ממך!
מה איכות התרגום בדף זה?

ניפוי שגיאות ומעקב

הפונקציה build() הופכת מעגל מוער לזוג של מעגל תבנית ו-samplex. במעגל מורכב קשה לפעמים לדעת איזו קופסה במעגל המקורי מתאימה לאילו barriers בתבנית, או לאילו צמתים ב-DAG של ה-samplex.

Samplomatic מספקת שני כלי מעקב משלימים:

  • תוויות barrier: כל barrier במעגל התבנית נושא תווית שמקודדת את המקור שלו. התוויות האלה תמיד קיימות, בין אם debug=True מוגדר ובין אם לא.

  • מידע מעקב בצמתי samplex: כש-build() נקראת עם debug=True, כל צומת samplex נושא אובייקט TraceInfo שמקשר אותו בחזרה לקופסה או לקופסאות שיצרו אותו.

שני הכלים שימושיים ביותר כשהקופסאות נושאות הערת Tag, שמצמידה מחרוזת ref לקופסה. לחלופין, מוצמדות גם הפניות של InjectNoise.

קופסאות עם תגית​

הערת Tag מצמידה מחרוזת ref לקופסה. ה-ref מופיע אחר כך בתוויות ה-barrier ובמידע המעקב בצמתי ה-samplex.

במעגל הדוגמה הבא יש שלוש קופסאות: שתי קופסאות CX עם תגית על זוגות Qubit-ים זרים, וקופסת מדידה בלי תגית עם dressing ימני. בקופסת ה-CX השנייה יש גם הערת InjectNoise — גם ה-ref של tag שלה וגם ה-ref של הרעש שלה יופיעו בתוויות ה-barrier. שתי קופסאות ה-CX מכסות Qubit-ים שונים, ולכן ה-optimizer של ה-samplex יכול למזג את צמתי ההפצה שלהן לצמתים משותפים. את זה רואים בסעיף מידע המעקב.

from qiskit.circuit import QuantumCircuit

from samplomatic import InjectNoise, Tag, Twirl, build

circuit = QuantumCircuit(4, 4)

with circuit.box([Twirl(), Tag("cx_ab")]):
circuit.cx(0, 1)

with circuit.box([Twirl(), InjectNoise("cx_noise"), Tag("cx_cd")]):
circuit.cx(2, 3)

with circuit.box([Twirl(), Tag("meas_box")]):
circuit.measure(range(4), range(4))

circuit.draw("mpl")

../_images/f6b6fe7b60a39ba897814008443fa79b66213005462ab09f49ffc89aab1f3668.png

תוויות barrier במעגל התבנית​

קריאה ל-build() על המעגל שלמעלה מפיקה תבנית שה-barriers שלה נושאים תוויות מזהות. לכל תווית יש הצורה {side}{scope}@{key=value&...}:

  • צד (Side): L = גבול dressing שמאלי, M = גבול התוכן הפנימי של הקופסה, R = גבול dressing ימני.

  • היקף (Scope): אינדקס שלם (או רשימה מופרדת בקווים תחתונים לקופסאות מקוננות) שמבדיל בין כמה קופסאות באותה רמת קינון.

  • הערות (Annotations): רשימה שמתחילה ב-@, מופרדת ב-&, של זוגות key=value שנגזרים מההערות של הקופסה. המפתח tag בא מהערת Tag, ו-inject_noise בא מהערת InjectNoise.

ל-barriers מקופסאות בלי תגית יש רק צד והיקף (לדוגמה, L2), בלי סיומת @.

template, samplex = build(circuit)
template.draw("mpl", fold=100)

../_images/e533d70a29504005a636aa27306d3ce3b1d2488358ac6a53f0674f2934698ab8.png

אפשר גם לחלץ את תוויות ה-barrier בתוכנית:

barrier_labels = [instr.operation.label for instr in template if instr.operation.name == "barrier"]
barrier_labels
['L0@tag=cx_ab',
'M0@tag=cx_ab',
'R0@tag=cx_ab',
'L1@inject_noise=cx_noise&tag=cx_cd',
'M1@inject_noise=cx_noise&tag=cx_cd',
'R1@inject_noise=cx_noise&tag=cx_cd',
'L2@tag=meas_box',
'M2@tag=meas_box',
'R2@tag=meas_box']

מידע מעקב בצמתי samplex​

העברת debug=True ל-build() מצמידה מידע מעקב לכל צומת samplex. התכונה trace_info של כל צומת היא אובייקט TraceInfo שמילון ה-trace_refs שלו ממפה מפתחות הערה (לדוגמה, "tag" ו-"inject_noise") לקבוצות של מחרוזות ref. לצמתים בלי הערת קופסה מתאימה יש trace_info=None.

כש-draw() נקראת על samplex שנבנה במצב ניפוי שגיאות, ריחוף מעל כל צומת בגרף האינטראקטיבי חושף את ה-trace_refs שלו בתוך חלונית המידע הצפה.

template, samplex = build(circuit, debug=True)
samplex.draw()

אפשר גם לבדוק את מידע המעקב בתוכנית. שימו לב שחלק מהצמתים נושאים הפניות גם מ-'cx_ab' וגם מ-'cx_cd'. ה-optimizer של ה-samplex מיזג את צמתי ההפצה המקבילים שלהם, כי שתי הקופסאות מכסות Qubit-ים זרים ומשתפות קודם משותף מהפליטה של קופסת המדידה עם ה-dressing הימני.

for node in samplex.graph.nodes():
if node.trace_info is not None:
tags = node.trace_info.trace_refs.get("tag", set())
merged = " ← merged" if len(tags) > 1 else ""
print(f"{type(node).__name__:40s} tags={tags}{merged}")
TwirlSamplingNode tags={'meas_box'}
InjectNoiseNode tags={'cx_cd'}
CollectZ2ToOutputNode tags={'cx_cd'}
TwirlSamplingNode tags={'cx_cd'}
CombineRegistersNode tags={'cx_cd'}
CollectTemplateValues tags={'cx_cd'}
TwirlSamplingNode tags={'cx_ab'}
CombineRegistersNode tags={'cx_ab', 'cx_cd'} ← merged
CollectTemplateValues tags={'cx_ab', 'cx_cd'} ← merged

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

tag_ref = "cx_ab"
matching_nodes = [
node
for node in samplex.graph.nodes()
if node.trace_info is not None and tag_ref in node.trace_info.trace_refs.get("tag", set())
]

print(f"Nodes originating from box '{tag_ref}':")
for node in matching_nodes:
print(f" {type(node).__name__}")
Nodes originating from box 'cx_ab':
TwirlSamplingNode
CombineRegistersNode
CollectTemplateValues

ה-DAG של ה-samplex​

ה-samplex ש-build() מחזירה הוא גרף מכוון חסר מעגלים (DAG) שבו קשתות מציינות תלות ברגיסטרים. כלומר, קשת מצומת A לצומת B אומרת ש-B חייב לחכות ש-A יפעל על הרגיסטרים הווירטואליים המשותפים לפני ש-B רשאי לפעול. זה לא סדר זמנים כמו ה-DAG של מעגל קוואנטי. במקום זאת, הגרף זורם מצמתים שאחראים ליצירת רנדומיזציות אל צמתים שאחראים לסנתז אותן כפלט.

יש שלושה סוגי צמתים, לכל אחד סגנון חזותי ייחודי בתרשים האינטראקטיבי:

צורהצבעסוגתפקיד
כוכבאדוםSamplingNodeיוצר רגיסטרים וירטואליים חדשים מהתפלגות או מקלט
עיגולירוקEvaluationNodeהופך, משלב או מפיץ רגיסטרים וירטואליים
פפיוןכחול / סגולCollectionNodeקורא רגיסטרים וכותב לפלטי sample()

ההרצה מתקדמת בשלושה שלבים:

  1. כל צמתי ה-sampling רצים ראשונים (במקביל).

  2. צמתי ה-evaluation רצים אחריהם, בסדר טופולוגי (במקביל בתוך כל דור).

  3. צמתי ה-collection רצים אחרונים (במקביל).

פריסת graphviz משקפת את זה. צמתי sampling מופיעים למעלה, וצמתי collection למטה.

חלוניות מידע בריחוף​

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

  • שם מחלקת הצומת ואינדקס הגרף השלם שלו.

  • מניפסטי רגיסטרים, כלומר אילו רגיסטרים הצומת יוצר, קורא מהם, כותב אליהם ומסיר. הם מראים איך הנתונים זורמים בין צמתים.

  • פרטים ספציפיים לצומת, כולל סוג ההתפלגות בצמתי sampling, האופרנד בצמתי כפל, אינדקסי פרמטרי התבנית בצמתי collection, וכן הלאה.

  • Trace refs (רק כש-debug=True), שהם מפתחות ההערה ומחרוזות ה-ref שמקשרים את הצומת בחזרה לקופסאות שיצרו אותו.

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

סיכום samplex​

לפני בחינת ההדמיה, print(samplex) נותן סיכום טקסט קצר של מספר הצמתים, הקלטים הנדרשים והפלטים המובטחים.

template, samplex = build(circuit)
print(samplex)
Samplex(<17 nodes>)
Inputs:
- 'pauli_lindblad_maps.cx_noise' <PauliLindbladMap>: A PauliLindblad map acting on 2
qubits, with 'num_terms_cx_noise' terms.

Outputs:
* 'measurement_flips.c' <bool['num_randomizations', 1, 4]>: Bit-flip corrections for
measurement twirling.
* 'parameter_values' <float32['num_randomizations', 24]>: Parameter values valid for an
associated template circuit.
* 'pauli_signs' <bool['num_randomizations', 1]>: Signs from sampled Pauli Lindblad
maps, where boolean values represent the parity of the number of non-trivial factors in the
sampled error that arise from negative rates. In other words, in order to implement basic
PEC, the sign used to correct expectation values should be ``(-1)**bool_value``. The order
matches the iteration order of boxes in the original circuit with noise injection
annotations.

בדיקת רגיסטרים​

העברת keep_registers=True ל-sample() משמרת את אובייקטי VirtualRegister הביניים שחיים בסוף הדגימה, ושומרת אותם ב-outputs.metadata["registers"]. כל רגיסטר הוא מערך דו-ממדי (צורה (num_subsystems, num_randomizations), עם ממדי צורת שער נוספים אפשריים בסוף) של איברי חבורה וירטואליים.

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

from qiskit.quantum_info import PauliLindbladMap

outputs = samplex.sample(
{"pauli_lindblad_maps.cx_noise": PauliLindbladMap.identity(2)},
num_randomizations=3,
keep_registers=True,
)
for name, reg in outputs.metadata["registers"].items():
print(f"{name}: type={reg.TYPE.value}, shape={reg.virtual_gates.shape}")
lhs_0: type=pauli, shape=(4, 3)
rhs_0: type=pauli, shape=(4, 3)
lhs_3: type=pauli, shape=(2, 3)
rhs_3: type=pauli, shape=(2, 3)
inject_noise_1: type=pauli, shape=(2, 3)
sign_1: type=z2, shape=(1, 3)
lhs_7: type=pauli, shape=(2, 3)
rhs_7: type=pauli, shape=(2, 3)
meas_prop_6: type=pauli, shape=(4, 3)
collect_10: type=u2, shape=(2, 3, 2, 2)
meas_prop_z2a_6: type=z2, shape=(4, 3)
collect_9: type=u2, shape=(4, 3, 2, 2)
collect_5: type=u2, shape=(2, 3, 2, 2)

אפשר לראות את התוכן של מצב הסיום של רגיסטר מסוים. בדוגמה הבאה הרגיסטר הוא מסוג PauliRegister. לפרטים על פורמט האחסון ראו את API documentation(/apidocs/index)__.

print("lhs_0:", outputs.metadata["registers"]["lhs_0"])
outputs.metadata["registers"]["lhs_0"].virtual_gates
lhs_0: PauliRegister(<4, 3>)
array([[0, 0, 1],
[3, 1, 1],
[3, 0, 0],
[1, 0, 0]], dtype=uint8)

איך צמתי samplex ממופים לפרמטרי התבנית​

למערך outputs["parameter_values"] ש-sample() מחזירה יש צורה (num_randomizations, N), כאשר N שווה ל-len(template.parameters). כל עמודה מתאימה לפרמטר אחד במעגל התבנית — העמודה ה-i ממלאת את template.parameters[i].

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

במדריך {Samplex inputs and outputs}samplex-io תלמדו איך לקשור את ערכי הפרמטרים שנדגמו למעגל התבנית ולהריץ ניסויים.

from samplomatic.samplex.nodes import CollectTemplateValues

template, samplex = build(circuit, debug=True)
for node in samplex.graph.nodes():
if isinstance(node, CollectTemplateValues):
tags = node.trace_info.trace_refs.get("tag", set()) if node.trace_info else set()
print(f"tags={tags} → template param indices: {node.template_idxs.tolist()}")
tags={'cx_cd'} → template param indices: [[6, 7, 8], [9, 10, 11]]
tags={'cx_ab', 'cx_cd'} → template param indices: [[12, 13, 14], [15, 16, 17], [18, 19, 20], [21, 22, 23]]
tags=set() → template param indices: [[0, 1, 2], [3, 4, 5]]

תיוג אוטומטי​

כשמשתמשים ב-generate_boxing_pass_manager(), הפרמטר add_tags מוסיף אוטומטית הערות Tag לכל הקופסאות. שלושה מצבים זמינים:

  • "unique_instance": מקצה ref רציפים (t0, t1 וכן הלאה) ל-boxes לפי סדר המעגל. כל box מקבל ref ייחודי, בלי קשר למבנה שלו.

  • "unique_box": מחשב hash מבני של תוכן כל box ומקצה את אותו ref לכל ה-boxes השקולים מבחינה מבנית. זה שימושי לקיבוץ boxes לפי סוג ולא לפי מיקום.

  • "noise_ref": מעתיק את ה-ref מההערה InjectNoise של כל box ומתייג רק boxes שיש להם כזו. זה שימושי כשכבר קיימים noise refs משמעותיים.

הדוגמה הבאה מפעילה את "unique_instance" ואת "unique_box" על מעגל ששני ה-CX boxes שלו שקולים מבחינה מבנית. עם "unique_instance" כל אחד מקבל ref נפרד, ואילו עם "unique_box" הם חולקים אחד.

from samplomatic.transpiler import generate_boxing_pass_manager

base_circuit = QuantumCircuit(3)
base_circuit.cx(0, 1)
base_circuit.cx(1, 2)
base_circuit.measure_all()

# unique_instance: every box gets a distinct tag ref
pm = generate_boxing_pass_manager(add_tags="unique_instance")
boxed = pm.run(base_circuit)
template, _ = build(boxed)

print("unique_instance barrier labels:")
for instr in template:
if instr.operation.name == "barrier" and instr.operation.label:
print(f" {instr.operation.label}")
unique_instance barrier labels:
L0@tag=t0
M0@tag=t0
R0@tag=t0
L1@tag=t1
M1@tag=t1
R1@tag=t1
L2@tag=t2
M2@tag=t2
R2@tag=t2