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

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

Package versions

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

qiskit[all]~=2.4.0

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

نظرة عامة على الـ 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.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit
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.primitives import StatevectorEstimator

import numpy as np

# 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 without providing a backend
pm = generate_preset_pass_manager(optimization_level=1)
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, 10),
np.linspace(-4 * np.pi, 4 * np.pi, 10),
]
).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 = StatevectorEstimator()
estimator_pub = (transpiled_circuit, observables, params)

# Run the transpiled circuit
# using the set of parameters and observables.

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.

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

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

تُمثَّل مجموعات قيم المعاملات بمصفوفات 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. يحتوي PrimitiveResult على قائمة قابلة للتكرار من كائنات PubResult التي تضم نتائج التنفيذ لكل PUB. على سبيل المثال، مهمة أُرسلت مع 20 PUB ستُعيد كائن PrimitiveResult يحتوي على قائمة من 20 PubResult، واحد لكل PUB.

كل كائن من كائنات PubResult هذه يمتلك خاصية data وخاصية metadata اختيارية. خاصية data عبارة عن DataBin مخصصة تحتوي على تقديرات قيم التوقع في حالة Estimator، أو عينات من مخرجات الدائرة في حالة Sampler.

قد تتضمن خاصية data أيضًا معلومات إضافية خاصة بالتطبيق مثل الانحرافات المعيارية. يمكن أن تحتوي خاصية metadata على معلومات إضافية خاصة بالتطبيق حول تنفيذ الـ PUB المرتبط.

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

└── PrimitiveResult
├── PubResult[0]
│ ├── metadata
│ └── data ## In the form of a DataBin object,
| | ## which includes data such as the following:
│ ├── 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,
| | ## which includes data such as the following:
| ├── 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
├── ...
├── ...
└── ...
ملاحظة

ما سبق مثال على البيانات التي قد تُعاد. تعتمد البيانات الفعلية المُعادة على التطبيق.

مخرجات Estimator

كما ذُكر سابقًا، تعتمد البيانات المُعادة في PubResult لبدائية Estimator على التطبيق. على سبيل المثال، قد تحتوي على مصفوفة من قيم التوقع (PubResult.data.evs) والانحرافات المعيارية المرتبطة بها (PubResult.data.stds).

يصف مقتطف الكود التالي تنسيق 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, 10), dtype=float64>), stds=np.ndarray(<shape=(3, 10), dtype=float64>), shape=(3, 10)), metadata={'target_precision': 0.0, 'circuit_metadata': {}})], metadata={'version': 2})

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

And this DataBin has attributes: dict_keys(['evs', 'stds'])
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:
[[ 3.06161700e-16 4.52395120e-01 4.36594428e-01 2.16506351e-01
6.33718361e-01 -6.33718361e-01 -2.16506351e-01 -4.36594428e-01
-4.52395120e-01 -3.06161700e-16]
[ 1.22464680e-16 6.42787610e-01 9.84807753e-01 8.66025404e-01
3.42020143e-01 -3.42020143e-01 -8.66025404e-01 -9.84807753e-01
-6.42787610e-01 -1.22464680e-16]
[ 4.89858720e-16 2.62002630e-01 -1.11618897e-01 -4.33012702e-01
9.25416578e-01 -9.25416578e-01 4.33012702e-01 1.11618897e-01
-2.62002630e-01 -4.89858720e-16]]

مخرجات Sampler

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

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

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

from qiskit.primitives import StatevectorSampler

# 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)

sampler = StatevectorSampler()

# run the Sampler job and retrieve the results

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=1024, num_bits=10>))

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

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

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

قد يكون من المفيد أحياناً التحويل من تنسيق البايتات في 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: {'0000000000': 492, '1111111111': 532}

عندما تحتوي دائرة على أكثر من سجل كلاسيكي واحد، تُخزَّن النتائج في كائنات 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

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=1024, num_bits=1>)
BitArray for register 'beta': BitArray(<shape=(), num_shots=1024, 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 (1024, 1).
The bytes in register `alpha`, shot by shot:
[[1]
[1]
[1]
...
[0]
[0]
[1]]

The shape of register `beta` is (1024, 2).
The bytes in register `beta`, shot by shot:
[[ 1 255]
[ 1 255]
[ 1 255]
...
[ 0 0]
[ 0 0]
[ 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 (1024, 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:
[[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]
[ 1 255]]
Exp. val. for observable `SparsePauliOp(['ZZZZZZZZZ'],
coeffs=[1.+0.j])` is: -0.017578125
Exp. val. for observable `SparsePauliOp(['IIIIIIIIZ'],
coeffs=[1.+0.j])` is: -0.017578125

The shape of the merged results is (1024, 2).
The bytes of the merged results:
[[ 3 255]
[ 3 255]
[ 3 255]
...
[ 0 0]
[ 0 0]
[ 3 255]]

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

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

# 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:
'version' : 2,

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

الخطوات التالية

توصيات