انتقل إلى المحتوى الرئيسي

مدخلات ومخرجات الـ Primitive

نموذج تنفيذ جديد، متاح الآن في إصدار تجريبي

يتوفر الآن الإصدار التجريبي من نموذج تنفيذ جديد. يوفر نموذج التنفيذ الموجَّه مرونةً أكبر عند تخصيص سير عمل تخفيف الأخطاء. اطلع على دليل نموذج التنفيذ الموجَّه لمزيد من المعلومات.

إصدارات الحزم

الكود الموجود في هذه الصفحة طُوِّر باستخدام المتطلبات التالية. نوصي باستخدام هذه الإصدارات أو ما هو أحدث منها.

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1

تقدّم هذه الصفحة نظرةً عامة على مدخلات ومخرجات Qiskit Runtime primitives التي تُنفِّذ أعباء العمل على موارد الحوسبة الكمية IBM Quantum®. توفر لك هذه الـ primitives القدرةَ على تعريف أعباء العمل المتجهة (vectorized workloads) بكفاءة عبر استخدام بنية بيانات تُعرف بـ Primitive Unified Bloc (PUB). هذه الـ PUBs هي الوحدة الأساسية للعمل التي يحتاج إليها QPU لتنفيذ أعباء العمل تلك. تُستخدم كمدخلات لأسلوب run() الخاص بـ Sampler وEstimator primitives، اللذين يُنفِّذان عبء العمل المحدَّد كمهمة (job). بعد اكتمال المهمة، تُعاد النتائج بتنسيق يعتمد على كل من الـ PUBs المستخدمة والخيارات المحددة من Sampler أو Estimator primitives.

نظرة عامة على الـ PUBs

عند استدعاء أسلوب run() الخاص بأحد الـ primitives، الوسيط الرئيسي المطلوب هو list يحتوي على صف واحد أو أكثر من الصفوف (tuples) — واحد لكل دائرة (circuit) تُنفَّذ بواسطة الـ primitive. كل صف من هذه الصفوف يُعدّ PUB، والعناصر المطلوبة في كل صف تعتمد على الـ primitive المستخدم. يمكن أيضًا ترتيب البيانات المقدَّمة لهذه الصفوف بأشكال متنوعة لتوفير مرونة في عبء العمل من خلال البث (broadcasting) — وقواعده موضَّحة في القسم التالي.

Estimator PUB

بالنسبة لـ Estimator primitive، يجب أن يحتوي تنسيق الـ PUB على أربع قيم كحد أقصى:

  • QuantumCircuit واحدة، قد تحتوي على كائن Parameter واحد أو أكثر
  • قائمة من observable واحد أو أكثر، تحدد قيم التوقع (expectation values) المراد تقديرها، مرتَّبة في مصفوفة (على سبيل المثال، observable واحد يُمثَّل كمصفوفة صفرية الأبعاد، وقائمة من observables كمصفوفة أحادية البعد، وما إلى ذلك). يمكن أن تكون البيانات بأي تنسيق من تنسيقات ObservablesArrayLike كـ Pauli أو SparsePauliOp أو PauliList أو str.
    ملاحظة

    إذا كان لديك observable-ان متبادلان في PUBs مختلفة ولكن بنفس الدائرة، فلن يُقدَّر قيمهما باستخدام نفس القياس. كل PUB تمثل أساسًا مختلفًا للقياس، وبالتالي، تستلزم قياسات منفصلة لكل PUB. لضمان تقدير الـ observables المتبادلة باستخدام نفس القياس، يجب تجميعها داخل نفس الـ PUB.

  • مجموعة من قيم المعاملات (parameter values) لربط الدائرة بها. يمكن تحديد هذا على شكل كائن يشبه المصفوفة (array-like) حيث يكون الفهرس الأخير على كائنات Parameter الخاصة بالدائرة، أو يمكن حذفه (أو تعيينه إلى None بشكل مكافئ) إذا كانت الدائرة لا تحتوي على كائنات Parameter.
  • (اختياريًا) دقة مستهدفة لتقدير قيم التوقع

Sampler PUB

بالنسبة لـ Sampler primitive، يحتوي صف الـ PUB على ثلاث قيم كحد أقصى:

  • QuantumCircuit واحدة، قد تحتوي على كائن Parameter واحد أو أكثر ملاحظة: يجب أن تتضمن هذه الدوائر أيضًا تعليمات قياس لكل qubit يُراد أخذ عينات منه.
  • مجموعة من قيم المعاملات لربط الدائرة بها θk\theta_k (مطلوبة فقط إذا كانت هناك كائنات Parameter يجب ربطها في وقت التشغيل)
  • (اختياريًا) عدد اللقطات (shots) لقياس الدائرة بها

يوضح الكود التالي مثالًا على مجموعة من المدخلات المتجهة لـ Estimator primitive وتنفيذها على backend من IBM® كـ RuntimeJobV2 object واحد.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-ibm-runtime
from qiskit.circuit import (
Parameter,
QuantumCircuit,
ClassicalRegister,
QuantumRegister,
)
from qiskit.transpiler import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives.containers import BitArray

from qiskit_ibm_runtime import (
QiskitRuntimeService,
EstimatorV2 as Estimator,
SamplerV2 as Sampler,
)

import numpy as np

# Instantiate runtime service and get
# the least busy backend
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Define a circuit with two parameters.
circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.ry(Parameter("a"), 0)
circuit.rz(Parameter("b"), 0)
circuit.cx(0, 1)
circuit.h(0)

# Transpile the circuit
pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
transpiled_circuit = pm.run(circuit)
layout = transpiled_circuit.layout

# Now define a sweep over parameter values, the last axis of dimension 2 is
# for the two parameters "a" and "b"
params = np.vstack(
[
np.linspace(-np.pi, np.pi, 100),
np.linspace(-4 * np.pi, 4 * np.pi, 100),
]
).T

# Define three observables. The inner length-1 lists cause this array of
# observables to have shape (3, 1), rather than shape (3,) if they were
# omitted.
observables = [
[SparsePauliOp(["XX", "IY"], [0.5, 0.5])],
[SparsePauliOp("XX")],
[SparsePauliOp("IY")],
]
# Apply the same layout as the transpiled circuit.
observables = [
[observable.apply_layout(layout) for observable in observable_set]
for observable_set in observables
]

# Estimate the expectation value for all 300 combinations of observables
# and parameter values, where the pub result will have shape (3, 100).
#
# This shape is due to our array of parameter bindings having shape
# (100, 2), combined with our array of observables having shape (3, 1).
estimator_pub = (transpiled_circuit, observables, params)

# Instantiate the new estimator object, then run the transpiled circuit
# using the set of parameters and observables.
estimator = Estimator(mode=backend)
job = estimator.run([estimator_pub])
result = job.result()

قواعد البث (Broadcasting)

تجمع الـ PUBs عناصر من مصفوفات متعددة (observables وقيم المعاملات) باتباع نفس قواعد البث الخاصة بـ NumPy. يلخّص هذا القسم تلك القواعد باختصار. للاطلاع على شرح مفصل، راجع توثيق قواعد بث NumPy.

القواعد:

  • لا يلزم أن تحتوي المصفوفات المدخلة على نفس عدد الأبعاد.
    • ستحتوي المصفوفة الناتجة على نفس عدد أبعاد المصفوفة المدخلة ذات الأبعاد الأكبر.
    • حجم كل بُعد هو أكبر حجم للبُعد المقابل.
    • يُفترض أن الأبعاد المفقودة حجمها واحد.
  • تبدأ مقارنات الشكل من البُعد الأيمن وتتجه نحو اليسار.
  • يكون بُعدان متوافقَين إذا كان حجماهما متساويَين أو كان أحدهما 1.

أمثلة على أزواج المصفوفات التي تُطبَّق عليها قواعد البث:

A1     (1d array):      1
A2 (2d array): 3 x 5
Result (2d array): 3 x 5

A1 (3d array): 11 x 2 x 7
A2 (3d array): 11 x 1 x 7
Result (3d array): 11 x 2 x 7

أمثلة على أزواج المصفوفات التي لا تُطبَّق عليها قواعد البث:

A1     (1d array):  5
A2 (1d array): 3

A1 (2d array): 2 x 1
A2 (3d array): 6 x 5 x 4 # This would work if the middle dimension were 2, but it is 5.

تُعيد EstimatorV2 تقديرًا واحدًا لقيمة التوقع لكل عنصر من عناصر الشكل الناتج عن البث.

فيما يلي بعض الأمثلة على الأنماط الشائعة معبَّرًا عنها بلغة بث المصفوفات. يظهر تمثيلها المرئي في الشكل الذي يليها:

تُمثَّل مجموعات قيم المعاملات بمصفوفات n × m، وتُمثَّل مصفوفات الـ observable بعمود واحد أو أكثر من الأعمدة. في كل مثال، تُدمج مجموعات قيم المعاملات مع مصفوفة الـ observable الخاصة بها لإنشاء تقديرات قيم التوقع الناتجة.

  • المثال الأول: (بث observable واحد) يحتوي على مجموعة قيم معاملات هي مصفوفة 5×1 ومصفوفة observables هي 1×1. يُدمج العنصر الوحيد في مصفوفة الـ observables مع كل عنصر في مجموعة قيم المعاملات لإنشاء مصفوفة 5×1 واحدة حيث كل عنصر فيها هو مزيج من العنصر الأصلي في مجموعة قيم المعاملات مع العنصر في مصفوفة الـ observables.

  • المثال الثاني: (zip) يحتوي على مجموعة قيم معاملات 5×1 ومصفوفة observables 5×1. الناتج مصفوفة 5×1 حيث كل عنصر فيها هو مزيج من العنصر رقم n في مجموعة قيم المعاملات مع العنصر رقم n في مصفوفة الـ observables.

  • المثال الثالث: (الضرب الخارجي/الحاصل) يحتوي على مجموعة قيم معاملات 1×6 ومصفوفة observables 4×1. ينتج عن دمجهما مصفوفة 4×6 تُنشأ بدمج كل عنصر في مجموعة قيم المعاملات مع كل عنصر في مصفوفة الـ observables، وبالتالي يصبح كل قيمة معاملة عمودًا كاملًا في الناتج.

  • المثال الرابع: (التعميم متعدد الأبعاد القياسي) يحتوي على مصفوفة قيم معاملات 3×6 ومصفوفتَي observables كلتاهما 3×1. تتحد لإنشاء مصفوفتَي ناتج 3×6 بطريقة مشابهة للمثال السابق.

هذه الصورة توضح عدة تمثيلات مرئية لبث المصفوفات

# Broadcast single observable
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = SparsePauliOp("ZZZ") # shape ()
# >> pub result has shape (5,)

# Zip
parameter_values = np.random.uniform(size=(5,)) # shape (5,)
observables = [
SparsePauliOp(pauli) for pauli in ["III", "XXX", "YYY", "ZZZ", "XYZ"]
] # shape (5,)
# >> pub result has shape (5,)

# Outer/Product
parameter_values = np.random.uniform(size=(1, 6)) # shape (1, 6)
observables = [
[SparsePauliOp(pauli)] for pauli in ["III", "XXX", "YYY", "ZZZ"]
] # shape (4, 1)
# >> pub result has shape (4, 6)

# Standard nd generalization
parameter_values = np.random.uniform(size=(3, 6)) # shape (3, 6)
observables = [
[
[SparsePauliOp(["XII"])],
[SparsePauliOp(["IXI"])],
[SparsePauliOp(["IIX"])],
],
[
[SparsePauliOp(["ZII"])],
[SparsePauliOp(["IZI"])],
[SparsePauliOp(["IIZ"])],
],
] # shape (2, 3, 1)
# >> pub result has shape (2, 3, 6)
SparsePauliOp

كل SparsePauliOp تُحسب كعنصر واحد في هذا السياق، بصرف النظر عن عدد الـ Paulis الموجودة داخل SparsePauliOp. وبالتالي، ولأغراض قواعد البث هذه، جميع العناصر التالية لها نفس الشكل:

a = SparsePauliOp("Z") # shape ()
b = SparsePauliOp("IIIIZXYIZ") # shape ()
c = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()

القوائم التالية من العوامل، رغم تكافؤها من حيث المعلومات الواردة فيها، لها أشكال مختلفة:

list1 = SparsePauliOp.from_list(["XX", "XY", "IZ"]) # shape ()
list2 = [SparsePauliOp("XX"), SparsePauliOp("XY"), SparsePauliOp("IZ")] # shape (3, )

نظرة عامة على مخرجات البدائيات

بمجرد إرسال PUB واحد أو أكثر إلى وحدة QPU للتنفيذ وإتمام المهمة بنجاح، تُعاد البيانات في صورة كائن حاوية من نوع PrimitiveResult، ويمكن الوصول إليه عبر استدعاء التابع RuntimeJobV2.result(). يحتوي PrimitiveResult على قائمة قابلة للتكرار من كائنات PubResult التي تضم نتائج التنفيذ لكل PUB. وبحسب البدائية المستخدمة، ستكون هذه البيانات إما قيم توقع مع أشرطة الخطأ الخاصة بها في حالة Estimator، أو عينات من مخرجات الدائرة في حالة Sampler.

كل عنصر في هذه القائمة يقابل كل PUB أُرسل إلى تابع run() الخاص بالبدائية (مثلاً، مهمة أُرسلت مع 20 PUB ستُعيد كائن PrimitiveResult يحتوي على قائمة من 20 PubResult، واحد لكل PUB).

كل كائن من كائنات PubResult هذه يمتلك الخاصيتين data وmetadata. خاصية data عبارة عن DataBin مخصصة تحتوي على قيم القياسات الفعلية والانحرافات المعيارية وما شابه ذلك. يمتلك DataBin هذا خصائص متنوعة تعتمد على شكل وبنية PUB المرتبط به، وكذلك على خيارات تخفيف الأخطاء التي حددتها البدائية المستخدمة لإرسال المهمة (مثل ZNE أو PEC). أما خاصية metadata فتحتوي على معلومات حول خيارات وقت التشغيل وتخفيف الأخطاء المستخدمة (موضحة لاحقاً في قسم بيانات وصفية للنتيجة في هذه الصفحة).

فيما يلي مخطط مرئي لبنية بيانات PrimitiveResult:

└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object
│ ├── evs
│ │ └── List of estimated expectation values in the shape
| | specified by the first pub
│ └── stds
│ └── List of calculated standard deviations in the
| same shape as above
├── PubResult[1]
| ├── metadata
| └── data ## In the form of a DataBin object
| ├── evs
| │ └── List of estimated expectation values in the shape
| | specified by the second pub
| └── stds
| └── List of calculated standard deviations in the
| same shape as above
├── ...
├── ...
└── ...

ببساطة، تُعيد المهمة الواحدة كائن PrimitiveResult يحتوي على قائمة من كائن PubResult واحد أو أكثر. تخزّن كائنات PubResult هذه بيانات القياس لكل PUB أُرسل إلى المهمة.

يمتلك كل PubResult تنسيقات وخصائص مختلفة بناءً على نوع البدائية المستخدمة في المهمة. التفاصيل موضحة أدناه.

مخرجات Estimator

يحتوي كل PubResult لبدائية Estimator على الأقل على مصفوفة من قيم التوقع (PubResult.data.evs) والانحرافات المعيارية المرتبطة بها (إما PubResult.data.stds أو PubResult.data.ensemble_standard_error حسب resilience_level المستخدم)، وقد يحتوي على بيانات إضافية تبعاً لخيارات تخفيف الأخطاء التي تم تحديدها.

يصف مقتطف الكود التالي تنسيق PrimitiveResultPubResult المرتبط به) للمهمة التي أُنشئت أعلاه.

print(
f"The result of the submitted job had {len(result)} PUB and has a value:\n {result}\n"
)
print(
f"The associated PubResult of this job has the following data bins:\n {result[0].data}\n"
)
print(f"And this DataBin has attributes: {result[0].data.keys()}")
print(
"Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the\n\
number of parameters in the circuit -- combined with our array of observables having shape (3, 1). \n"
)
print(
f"The expectation values measured from this PUB are: \n{result[0].data.evs}"
)
The result of the submitted job had 1 PUB and has a value:
PrimitiveResult([PubResult(data=DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100)), metadata={'shots': 4096, 'target_precision': 0.015625, 'circuit_metadata': {}, 'resilience': {}, 'num_randomizations': 32})], metadata={'dynamical_decoupling': {'enable': False, 'sequence_type': 'XX', 'extra_slack_distribution': 'middle', 'scheduling_method': 'alap'}, 'twirling': {'enable_gates': False, 'enable_measure': True, 'num_randomizations': 'auto', 'shots_per_randomization': 'auto', 'interleave_randomizations': True, 'strategy': 'active-accum'}, 'resilience': {'measure_mitigation': True, 'zne_mitigation': False, 'pec_mitigation': False}, 'version': 2})

The associated PubResult of this job has the following data bins:
DataBin(evs=np.ndarray(<shape=(3, 100), dtype=float64>), stds=np.ndarray(<shape=(3, 100), dtype=float64>), ensemble_standard_error=np.ndarray(<shape=(3, 100), dtype=float64>), shape=(3, 100))

And this DataBin has attributes: dict_keys(['evs', 'stds', 'ensemble_standard_error'])
Recall that this shape is due to our array of parameter binding sets having shape (100, 2) -- where 2 is the
number of parameters in the circuit -- combined with our array of observables having shape (3, 1).

The expectation values measured from this PUB are:
[[ 0.00948597 0.12163221 0.29100944 0.40535344 0.46625814 0.54716103
0.57690846 0.59809047 0.5784682 0.50924868 0.4579837 0.40035644
0.37174056 0.32887613 0.25850853 0.26396412 0.25852429 0.26074166
0.29282485 0.34388535 0.37368314 0.43562138 0.46912323 0.51955146
0.54430185 0.55467261 0.5162183 0.52744696 0.47261781 0.42613541
0.35400013 0.33217125 0.29600426 0.27561903 0.25307754 0.25672088
0.28783701 0.36612701 0.40433263 0.44428286 0.51028376 0.55034507
0.55979913 0.57160124 0.54127534 0.49753533 0.42942659 0.32552331
0.20215918 0.04303087 -0.08115732 -0.18473659 -0.34015892 -0.44489319
-0.49112115 -0.54588034 -0.60601287 -0.55869218 -0.53353861 -0.51628053
-0.44978534 -0.38090252 -0.32481576 -0.28832245 -0.27057547 -0.26542929
-0.27054473 -0.29367389 -0.31531828 -0.38462352 -0.40276794 -0.47168997
-0.48548191 -0.5382924 -0.52716406 -0.53277032 -0.50776933 -0.48512907
-0.44335198 -0.38756463 -0.34438156 -0.29199194 -0.2729216 -0.24602918
-0.23527174 -0.3019153 -0.35159518 -0.38303379 -0.42434541 -0.47743033
-0.54652609 -0.5877912 -0.59175701 -0.57386895 -0.56416812 -0.48022381
-0.3853372 -0.2639702 -0.12030502 0.02081148]
[ 0.00581765 0.0552677 0.15998546 0.20725389 0.25452232 0.34178711
0.39196437 0.47050268 0.50031815 0.527952 0.57231161 0.64066903
0.72429779 0.77011181 0.78174711 0.86610308 0.88646487 0.91337151
0.94245978 0.98100173 0.97372966 1.00936279 1.01881647 1.0544496
1.01954368 1.03699664 0.99845469 1.03845105 1.00936279 1.00354513
0.95409508 0.95264067 0.91264431 0.91846196 0.8355604 0.80283611
0.77956549 0.74102354 0.69520953 0.64575948 0.58976457 0.53231524
0.43996 0.3956004 0.32069812 0.27706572 0.22470684 0.16653032
0.07272066 -0.00218162 -0.05817653 -0.06253977 -0.15853104 -0.25015908
-0.28506499 -0.34251432 -0.44359604 -0.44432324 -0.53158804 -0.60285429
-0.637033 -0.67630215 -0.71266249 -0.76793019 -0.81519862 -0.86464867
-0.90173621 -0.93155168 -0.9337333 -0.98245614 -0.99627307 -1.01518044
-1.01590764 -1.04863194 -1.00499955 -1.02827016 -1.01663485 -1.0108172
-1.02317971 -0.97518407 -0.96500318 -0.94682302 -0.901009 -0.87846559
-0.79556404 -0.84937733 -0.78101991 -0.73811472 -0.65521316 -0.57667485
-0.59921825 -0.49813653 -0.44577766 -0.36505772 -0.33524225 -0.25888556
-0.21161713 -0.12289792 -0.03781474 0.00654486]
[ 0.01315429 0.18799671 0.42203343 0.603453 0.67799397 0.75253494
0.76185256 0.72567827 0.65661825 0.49054535 0.3436558 0.16004385
0.01918334 -0.11235955 -0.26473006 -0.33817484 -0.36941628 -0.39188819
-0.35681008 -0.29323102 -0.22636339 -0.13812003 -0.08057002 -0.01534667
0.06906002 0.07234859 0.03398191 0.01644286 -0.06412716 -0.15127432
-0.24609482 -0.28829816 -0.32063579 -0.3672239 -0.32940532 -0.28939435
-0.20389148 -0.00876953 0.11345574 0.24280625 0.43080296 0.5683749
0.67963826 0.74760208 0.76185256 0.71800493 0.63414634 0.48451631
0.3315977 0.08824335 -0.10413812 -0.30693341 -0.52178679 -0.6396273
-0.69717731 -0.74924637 -0.76842971 -0.67306111 -0.53548918 -0.42970677
-0.26253768 -0.08550288 0.06303097 0.19128528 0.27404768 0.33379008
0.36064675 0.34420389 0.30309674 0.2132091 0.19073719 0.07180049
0.04494382 -0.02795286 -0.04932858 -0.03727049 0.00109619 0.04055906
0.13647575 0.20005481 0.27624007 0.36283913 0.3551658 0.38640723
0.32502055 0.24554673 0.07782954 -0.02795286 -0.19347767 -0.3781858
-0.49383393 -0.67744588 -0.73773637 -0.78268019 -0.793094 -0.70156207
-0.55905728 -0.40504248 -0.20279529 0.0350781 ]]

كيف يحسب Estimator الخطأ

بالإضافة إلى تقدير متوسط المؤثرات الممررة في PUBs المدخلة (حقل evs في DataBin)، يحاول Estimator أيضاً تقديم تقدير للخطأ المرتبط بقيم التوقع تلك. ستملأ جميع استعلامات Estimator حقل stds بكمية تشبه الخطأ المعياري للمتوسط لكل قيمة توقع، لكن بعض خيارات تخفيف الأخطاء تنتج معلومات إضافية مثل ensemble_standard_error.

لنأخذ مؤثراً واحداً O\mathcal{O}. في غياب ZNE، يمكنك اعتبار كل لقطة من تنفيذ Estimator بمثابة تقدير نقطي لقيمة التوقع O\langle \mathcal{O} \rangle. إذا كانت التقديرات النقطية في متجه Os، فإن القيمة المعادة في ensemble_standard_error تعادل التالي (حيث σO\sigma_{\mathcal{O}} هو الانحراف المعياري لتقدير قيمة التوقع وNshotsN_{shots} هو عدد اللقطات):

σONshots,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{shots}} },

وهذا يعامل جميع اللقطات باعتبارها جزءاً من مجموعة واحدة. إذا طلبت تلوية البوابات (twirling.enable_gates = True)، يمكنك تصنيف التقديرات النقطية لـ O\langle \mathcal{O} \rangle في مجموعات تشترك في نفس التلوية. سمِّ هذه المجموعات من التقديرات O_twirls، وعددها num_randomizations (عدد التلويات). عندها يكون stds هو الخطأ المعياري لمتوسط O_twirls، كما في:

σONtwirls,\frac{ \sigma_{\mathcal{O}} }{ \sqrt{N_{twirls}} },

حيث σO\sigma_{\mathcal{O}} هو الانحراف المعياري لـ O_twirls وNtwirlsN_{twirls} هو عدد التلويات. عندما لا تُفعّل التلوية، يكون stds وensemble_standard_error متساويين.

إذا فعّلت ZNE، تصبح stds الموصوفة أعلاه أوزاناً في انحدار غير خطي لنموذج استقراء. ما يُعاد أخيراً في stds في هذه الحالة هو عدم اليقين في نموذج الملاءمة مقيَّماً عند عامل ضوضاء يساوي صفراً. عندما تكون الملاءمة سيئة أو يكون عدم اليقين فيها كبيراً، يمكن أن تصبح stds المُبلَّغ عنها كبيرة جداً. عند تفعيل ZNE، يتم أيضاً ملء pub_result.data.evs_noise_factors وpub_result.data.stds_noise_factors، بحيث يمكنك إجراء الاستقراء الخاص بك.

مخرجات Sampler

عند اكتمال مهمة Sampler بنجاح، يحتوي كائن PrimitiveResult المعاد على قائمة من كائنات SamplerPubResult، واحد لكل PUB. حاويات البيانات لكائنات SamplerPubResult هذه عبارة عن كائنات تشبه القواميس تحتوي على BitArray واحد لكل ClassicalRegister في الدائرة.

كلاس BitArray هو حاوية لبيانات اللقطات المرتبة. بمزيد من التفصيل، يخزّن سلاسل البت المأخوذة كبايتات داخل مصفوفة ثنائية الأبعاد. يمتد المحور الأيسر لهذه المصفوفة على اللقطات المرتبة، بينما يمتد المحور الأيمن على البايتات.

كمثال أولي، لنلقِ نظرة على دائرة العشرة كيوبتات التالية:

# generate a ten-qubit GHZ circuit
circuit = QuantumCircuit(10)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure_all()

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains one BitArray
data = result[0].data
print(f"Databin: {data}\n")

# to access the BitArray, use the key "meas", which is the default name of
# the classical register when this is added by the `measure_all` method
array = data.meas
print(f"BitArray: {array}\n")
print(f"The shape of register `meas` is {data.meas.array.shape}.\n")
print(f"The bytes in register `alpha`, shot by shot:\n{data.meas.array}\n")
Databin: DataBin(meas=BitArray(<shape=(), num_shots=4096, num_bits=10>))

BitArray: BitArray(<shape=(), num_shots=4096, num_bits=10>)

The shape of register `meas` is (4096, 2).

The bytes in register `alpha`, shot by shot:
[[ 3 254]
[ 0 0]
[ 3 255]
...
[ 0 0]
[ 3 255]
[ 0 0]]

قد يكون من المفيد أحياناً التحويل من تنسيق البايتات في BitArray إلى سلاسل البت. تابع get_count يُعيد قاموساً يربط سلاسل البت بعدد مرات ظهورها.

# optionally, convert away from the native BitArray format to a dictionary format
counts = data.meas.get_counts()
print(f"Counts: {counts}")
Counts: {'1111111110': 199, '0000000000': 1337, '1111111111': 1052, '1111111000': 33, '1110000000': 65, '1100100000': 2, '1100000000': 25, '0010001110': 1, '0000000011': 30, '1111111011': 58, '1111111010': 25, '0000000110': 7, '0010000001': 11, '0000000001': 179, '1110111110': 6, '1111110000': 33, '1111101111': 49, '1110111111': 40, '0000111010': 2, '0100000000': 35, '0000000010': 51, '0000100000': 31, '0110000000': 7, '0000001111': 22, '1111111100': 24, '1011111110': 5, '0001111111': 58, '0000111111': 24, '1111101110': 10, '0000010001': 5, '0000001001': 2, '0011111111': 38, '0000001000': 11, '1111100000': 34, '0111111111': 45, '0000000100': 18, '0000000101': 2, '1011111111': 11, '1110000001': 13, '1101111000': 1, '0010000000': 52, '0000010000': 17, '0000011111': 15, '1110100001': 1, '0111111110': 9, '0000000111': 19, '1101111111': 15, '1111110111': 17, '0011111110': 5, '0001101110': 1, '0111111011': 6, '0100001000': 2, '0010001111': 1, '1111011000': 1, '0000111110': 4, '0011110010': 1, '1110111100': 2, '1111000000': 8, '1111111101': 27, '0000011110': 6, '0001000000': 5, '1111010000': 3, '0000011011': 4, '0001111110': 9, '1111011110': 6, '1110001111': 2, '0100000001': 7, '1110111011': 3, '1111101101': 2, '1101111110': 5, '1110000010': 7, '0111111000': 1, '1110111000': 1, '0000100001': 2, '1110100000': 6, '1000000001': 2, '0001011111': 1, '0000010111': 1, '1011111100': 1, '0111110000': 5, '0110111111': 2, '0010000010': 1, '0001111100': 4, '0011111001': 2, '1111110011': 1, '1110000011': 5, '0000001011': 8, '0100000010': 3, '1111011111': 13, '0010111000': 2, '0100111110': 1, '1111101000': 2, '1110110000': 2, '1100000001': 1, '0001110000': 3, '1011101111': 2, '1111000001': 2, '1111110001': 8, '1111110110': 4, '1100000010': 3, '0011000000': 2, '1110011111': 3, '0011101111': 3, '0010010000': 2, '0000100010': 1, '1100001110': 1, '0001111011': 4, '1010000000': 3, '0000001110': 5, '0000001010': 2, '0011111011': 4, '0100100000': 2, '1111110100': 1, '1111100011': 3, '0000110110': 1, '0001111101': 2, '1111100001': 2, '1000000000': 5, '0010000011': 3, '0010011111': 3, '0100001111': 1, '0100000111': 1, '1011101110': 1, '0011110111': 1, '1100000111': 1, '1100111111': 3, '0001111010': 1, '1101111011': 1, '0111111100': 2, '0100000110': 2, '0100000011': 2, '0001101111': 3, '0001000001': 1, '1111110010': 1, '0010100000': 1, '0011100000': 4, '1010001111': 1, '0101111111': 2, '1111101001': 1, '1110111101': 1, '0000011101': 1, '1110001000': 2, '0001111001': 1, '0101000000': 1, '1111111001': 5, '0001110111': 2, '0000111001': 1, '0100001011': 1, '0000010011': 1, '1011110111': 1, '0011110001': 1, '0000001100': 2, '0111010111': 1, '0001101011': 1, '1110010000': 2, '1110000100': 1, '0010111111': 3, '0111011100': 1, '1010001000': 1, '0000101110': 1, '0011111100': 2, '0000111100': 2, '1110011110': 1, '0011111000': 2, '0110100000': 1, '1001101111': 1, '1011000000': 1, '1101000000': 1, '1110001011': 1, '1110110111': 1, '0110111110': 1, '0011011111': 1, '0111100000': 1, '0000110111': 1, '0000010010': 2, '1111101100': 2, '1111011101': 1, '1101100000': 1, '0010111110': 1, '1101101110': 1, '1111001111': 1, '1101111100': 1, '1011111010': 1, '0001100000': 1, '1101110111': 1, '1100001011': 1}

عندما تحتوي دائرة على أكثر من سجل كلاسيكي واحد، تُخزَّن النتائج في كائنات BitArray مختلفة. المثال التالي يعدّل المقتطف السابق بتقسيم السجل الكلاسيكي إلى سجلين منفصلين:

# generate a ten-qubit GHZ circuit with two classical registers
circuit = QuantumCircuit(
qreg := QuantumRegister(10),
alpha := ClassicalRegister(1, "alpha"),
beta := ClassicalRegister(9, "beta"),
)
circuit.h(0)
circuit.cx(range(0, 9), range(1, 10))

# append measurements with the `measure_all` method
circuit.measure([0], alpha)
circuit.measure(range(1, 10), beta)

# transpile the circuit
transpiled_circuit = pm.run(circuit)

# run the Sampler job and retrieve the results
sampler = Sampler(mode=backend)
job = sampler.run([transpiled_circuit])
result = job.result()

# the data bin contains two BitArrays, one per register, and can be accessed
# as attributes using the registers' names
data = result[0].data
print(f"BitArray for register 'alpha': {data.alpha}")
print(f"BitArray for register 'beta': {data.beta}")
BitArray for register 'alpha': BitArray(<shape=(), num_shots=4096, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=4096, num_bits=9>)

الاستفادة من كائنات BitArray للمعالجة اللاحقة عالية الأداء

نظراً لأن المصفوفات توفر عموماً أداءً أفضل مقارنةً بالقواميس، يُنصح بإجراء أي معالجة لاحقة مباشرةً على كائنات BitArray بدلاً من قواميس الأعداد. يوفر كلاس BitArray مجموعة من التوابع لإجراء بعض عمليات المعالجة اللاحقة الشائعة:

print(f"The shape of register `alpha` is {data.alpha.array.shape}.")
print(f"The bytes in register `alpha`, shot by shot:\n{data.alpha.array}\n")

print(f"The shape of register `beta` is {data.beta.array.shape}.")
print(f"The bytes in register `beta`, shot by shot:\n{data.beta.array}\n")

# post-select the bitstrings of `beta` based on having sampled "1" in `alpha`
mask = data.alpha.array == "0b1"
ps_beta = data.beta[mask[:, 0]]
print(f"The shape of `beta` after post-selection is {ps_beta.array.shape}.")
print(f"The bytes in `beta` after post-selection:\n{ps_beta.array}")

# get a slice of `beta` to retrieve the first three bits
beta_sl_bits = data.beta.slice_bits([0, 1, 2])
print(
f"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}."
)
print(f"The bytes in `beta` after bit-wise slicing:\n{beta_sl_bits.array}\n")

# get a slice of `beta` to retrieve the bytes of the first five shots
beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])
print(
f"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}."
)
print(
f"The bytes in `beta` after shot-wise slicing:\n{beta_sl_shots.array}\n"
)

# calculate the expectation value of diagonal operators on `beta`
ops = [SparsePauliOp("ZZZZZZZZZ"), SparsePauliOp("IIIIIIIIZ")]
exp_vals = data.beta.expectation_values(ops)
for o, e in zip(ops, exp_vals):
print(f"Exp. val. for observable `{o}` is: {e}")

# concatenate the bitstrings in `alpha` and `beta` to "merge" the results of the two
# registers
merged_results = BitArray.concatenate_bits([data.alpha, data.beta])
print(f"\nThe shape of the merged results is {merged_results.array.shape}.")
print(f"The bytes of the merged results:\n{merged_results.array}\n")
The shape of register `alpha` is (4096, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]

The shape of register `beta` is (4096, 2).
The bytes in register `beta`, shot by shot:
[[ 0 135]
[ 0 247]
[ 1 247]
...
[ 0 0]
[ 1 224]
[ 1 255]]

The shape of `beta` after post-selection is (0, 2).
The bytes in `beta` after post-selection:
[]
The shape of `beta` after bit-wise slicing is (4096, 1).
The bytes in `beta` after bit-wise slicing:
[[7]
[7]
[7]
...
[0]
[0]
[7]]

The shape of `beta` after shot-wise slicing is (5, 2).
The bytes in `beta` after shot-wise slicing:
[[ 0 135]
[ 0 247]
[ 1 247]
[ 1 128]
[ 1 255]]

Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: 0.068359375
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: 0.06396484375

The shape of the merged results is (4096, 2).
The bytes of the merged results:
[[ 1 15]
[ 1 239]
[ 3 239]
...
[ 0 0]
[ 3 192]
[ 3 255]]

البيانات الوصفية للنتيجة

بالإضافة إلى نتائج التنفيذ، يحتوي كلٌّ من كائني PrimitiveResult وPubResult على خاصية بيانات وصفية حول المهمة التي أُرسلت. يمكن العثور على البيانات الوصفية التي تحتوي على معلومات لجميع PUBs المُرسَلة (مثل خيارات وقت التشغيل المتاحة المختلفة) في PrimitiveResult.metatada، بينما توجد البيانات الوصفية الخاصة بكل PUB في PubResult.metadata.

ملاحظة

في حقل البيانات الوصفية، يمكن لتطبيقات البدائيات إعادة أي معلومات حول التنفيذ ذات صلة بها، ولا توجد أزواج مفتاح-قيمة مضمونة من قِبل البدائية الأساسية. لذلك، قد تختلف البيانات الوصفية المُعادة في تطبيقات البدائيات المختلفة.

# Print out the results metadata
print("The metadata of the PrimitiveResult is:")
for key, val in result.metadata.items():
print(f"'{key}' : {val},")

print("\nThe metadata of the PubResult result is:")
for key, val in result[0].metadata.items():
print(f"'{key}' : {val},")
The metadata of the PrimitiveResult is:
'execution' : {'execution_spans': ExecutionSpans([DoubleSliceSpan(<start='2026-01-15 08:07:33', stop='2026-01-15 08:07:36', size=4096>)])},
'version' : 2,

The metadata of the PubResult result is:
'circuit_metadata' : {},

بالنسبة لمهام Sampler، يمكنك أيضاً مراجعة بيانات وصفية النتيجة لفهم متى نُفّذت بيانات معينة؛ وهذا ما يُعرف بـ نطاق التنفيذ.