التنقيح والتتبّع
تحوّل الدالة build() دائرة مشروحة إلى زوج: دائرة قالب وsamplex. في الدوائر المعقدة قد يصعب معرفة أي صندوق في الدائرة الأصلية يقابل أي حواجز (barriers) في القالب، أو أي عقد في DAG الخاص بـ samplex.
يوفّر Samplomatic أداتَي تتبّع متكاملتين:
-
تسميات الحواجز: كل حاجز في دائرة القالب يحمل تسمية تشفّر أصله. وهي موجودة دائماً سواء عُيّن
debug=Trueأم لا. -
معلومات التتبّع على عقد samplex: عند استدعاء
build()معdebug=True، تحمل كل عقدة في samplex كائنTraceInfoيربطها بالصندوق أو الصناديق التي أنتجتها.
تكون الأداتان أنفع ما تكون عندما تحمل الصناديق شرح Tag، الذي يُلحق سلسلة ref بالصندوق. وبدلاً من ذلك، تُلحق أيضاً مراجع InjectNoise.
وسم الصناديق
يُلحق شرح Tag سلسلة ref بصندوق. ثم تظهر ref في تسميات الحواجز وفي معلومات التتبّع على عقد samplex.
تحتوي دائرة المثال التالية على ثلاثة صناديق: صندوقا CX موسومان على زوجين منفصلين من الكيوبتات، وصندوق قياس غير موسوم بتلبيس يميني. ويحمل صندوق CX الثاني أيضاً شرح InjectNoise — فيظهر في تسميات الحواجز كل من مرجع tag ومرجع الضوضاء ref. يغطي صندوقا CX كيوبتات مختلفة، مما يتيح لمحسّن 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")
تسميات الحواجز في دائرة القالب
استدعاء build() على الدائرة أعلاه ينتج قالباً تحمل حواجزه تسميات تعريفية. لكل تسمية الصيغة {side}{scope}@{key=value&...}:
-
الجانب (Side):
L= حدّ التلبيس الأيسر،M= حدّ محتوى الصندوق الداخلي،R= حدّ التلبيس الأيمن. -
النطاق (Scope): فهرس صحيح (أو قائمة مفصولة بشرطة سفلية للصناديق المتداخلة) يميّز الصناديق المتعددة في مستوى التداخل نفسه.
-
الشروح (Annotations): قائمة تبدأ بـ
@ومفصولة بـ&من أزواجkey=valueمشتقة من شروح الصندوق. يأتي المفتاحtagمن شرحTagويأتيinject_noiseمن شرحInjectNoise.
الحواجز من الصناديق غير الموسومة تحمل الجانب والنطاق فقط (مثلاً L2)، بدون لاحقة @.
template, samplex = build(circuit)
template.draw("mpl", fold=100)
يمكن أيضاً استخراج تسميات الحواجز برمجياً:
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'. دمج محسّن samplex عقد الانتشار المتوازية الخاصة بهما لأن الصندوقين يغطيان كيوبتات منفصلة ويشتركان في سابق مشترك من إصدار صندوق القياس ذي التلبيس اليميني.
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() |
يتم التنفيذ على ثلاث مراحل:
-
تعمل عقد أخذ العينات (sampling) أولاً (بالتوازي).
-
ثم تعمل عقد التقييم (evaluation)، بترتيب طوبولوجي (بالتوازي داخل كل جيل).
-
تعمل عقد التجميع (collection) أخيراً (بالتوازي).
يعكس تخطيط graphviz ذلك. تظهر عقد أخذ العينات في الأعلى وعقد التجميع في الأسفل.
تلميحات التمرير
تُظهر كل عقدة في التصوير التفاعلي تلميحاً عند تمرير المؤشر فوقها. يحتوي التلميح على المعلومات التالية:
-
اسم فئة العقدة وفهرسها الصحيح في الرسم البياني.
-
بيانات السجلات (Register manifests)، أي السجلات التي تنشئها العقدة وتقرأ منها وتكتب فيها وتزيلها. تخبرك هذه كيف تتدفق البيانات بين العقد.
-
تفاصيل خاصة بالعقدة، تشمل نوع التوزيع لعقد أخذ العينات، والمعامل (operand) لعقد الضرب، ومؤشرات معاملات القالب لعقد التجميع، وهكذا.
-
مراجع التتبّع (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].
عقد التجميع 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وهكذا) للصناديق بترتيب الدائرة. كل صندوق يحصل علىrefمميز بغض النظر عن بنيته. -
"unique_box": يحسب تجزئة بنيوية لمحتوى كل صندوق ويعيّن نفس قيمةrefلكل الصناديق المتكافئة بنيوياً. هذا مفيد لتجميع الصناديق حسب النوع بدل الموضع. -
"noise_ref": ينسخ قيمةrefمن تعليقInjectNoiseالخاص بكل صندوق، ويوسم فقط الصناديق التي تحتوي على تعليق كهذا. هذا مفيد عندما تكون قيم noise ref ذات المعنى موجودة أصلاً.
المثال التالي يطبّق "unique_instance" و"unique_box" على دائرة فيها صندوقا CX متكافئان بنيوياً. مع "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
