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

الترقية إلى أوليات Qiskit Runtime الإصدار الثاني (V2 primitives)

تحذير

الأوليات الأصلية (المعروفة بأوليات V1)، وهي V1 Sampler وV1 Estimator، تم إهمالها في qiskit-ibm-runtime الإصدار 0.23. وقد أُوقف دعمها نهائياً في 15 أغسطس 2024.

مع إهمال أوليات V1، ينبغي ترقية جميع الأكواد لاستخدام واجهات V2. يصف هذا الدليل ما الذي تغيّر في أوليات Qiskit Runtime V2 (المتاحة مع qiskit-ibm-runtime 0.21.0) وسبب ذلك التغيير، ويشرح كل أولية جديدة بالتفصيل، ويقدّم أمثلة تساعدك على ترقية كودك من الأوليات القديمة إلى أوليات V2. تستخدم الأمثلة في هذا الدليل أوليات Qiskit Runtime، لكن بشكل عام تنطبق نفس التغييرات على بقية تطبيقات الأوليات الأخرى. أما الوظائف الخاصة بـ Qiskit Runtime كتخفيف الأخطاء (error mitigation)، فتظل حكراً على Qiskit Runtime.

للاطلاع على التغييرات التي طرأت على الأوليات المرجعية في Qiskit (المعروفة الآن بأوليات statevector)، راجع قسم qiskit.primitives في صفحة تغييرات ميزات Qiskit 1.0. كذلك راجع StatevectorSampler وStatevectorEstimator للاطلاع على التطبيقات المرجعية لأوليات V2.

نظرة عامة

يُقدَّم الإصدار الثاني من الأوليات مع فئة أساسية جديدة لكلٍّ من Sampler وEstimator (BaseSamplerV2 وBaseEstimatorV2)، إلى جانب أنواع جديدة للمدخلات والمخرجات.

تتيح لك الواجهة الجديدة تحديد دائرة واحدة مع مراصد (observables) متعددة (عند استخدام Estimator) ومجموعات قيم المعاملات لتلك الدائرة، مما يتيح تحديد عمليات المسح على مجموعات قيم المعاملات والمراصد بكفاءة عالية. في السابق، كنت مضطراً لتحديد نفس الدائرة عدة مرات لتتطابق مع حجم البيانات المراد دمجها. كذلك، وإن كان بإمكانك الاستمرار في استخدام resilience_level (عند استخدام Estimator) كمؤشر بسيط، فإن أوليات V2 تمنحك مرونة في تشغيل أو إيقاف أساليب تخفيف/قمع الأخطاء الفردية لتخصيصها وفق احتياجاتك.

للحدّ من إجمالي وقت تنفيذ المهمة، لا تقبل أوليات V2 سوى الدوائر والمراصد التي تستخدم تعليمات يدعمها الـ QPU (وحدة المعالجة الكمية) المستهدفة. يُشار إلى هذه الدوائر والمراصد باسم دوائر ومراصد معمارية مجموعة التعليمات (ISA). لا تُجري أوليات V2 عمليات التخطيط (layout) والتوجيه (routing) والترجمة (translation). راجع توثيق التحويل (transpilation) للاطلاع على تعليمات تحويل الدوائر.

يتمحور Sampler V2 حول مهمته الأساسية وهي أخذ العيّنات من سجل الإخراج الناتج عن تنفيذ الدوائر الكمية. يُعيد العيّنات بنوع محدد من البرنامج دون أوزان. كما يتم فصل بيانات الإخراج حسب أسماء سجلات الإخراج المحددة في البرنامج. يتيح هذا التغيير دعماً مستقبلياً للدوائر ذات التدفق الكلاسيكي المشروط (classical control flow).

راجع مرجع واجهة برمجة EstimatorV2 ومرجع واجهة برمجة SamplerV2 للاطلاع على التفاصيل الكاملة.

التغييرات الرئيسية

الاستيراد

للحفاظ على التوافق مع الإصدارات السابقة، يجب استيراد أوليات V2 بصراحة. تحديد import <primitive>V2 as <primitive> ليس إلزامياً، لكنه يُسهّل عملية الانتقال في الكود إلى V2.

تحذير

بعد انتهاء دعم أوليات V1، سيقوم import <primitive> باستيراد إصدار V2 من الأولية المحددة.

from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import SamplerV2 as Sampler

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

المدخلات

يقبل كلٌّ من SamplerV2 وEstimatorV2 واحداً أو أكثر من وحدات الأوليات الموحّدة (PUBs) كمدخلات. كل PUB عبارة عن مجموعة (tuple) تحتوي على دائرة واحدة والبيانات المُوزَّعة على تلك الدائرة، والتي يمكن أن تشمل مراصد ومعاملات متعددة. تُعيد كل PUB نتيجة خاصة بها.

  • صيغة PUB الخاصة بـ Sampler V2: (<circuit>, <parameter values>, <shots>)، حيث <parameter values> و<shots> اختياريان.
  • صيغة PUB الخاصة بـ Estimator V2: (<circuit>, <observables>, <parameter values>, <precision>)، حيث <parameter values> و<precision> اختياريان. تُستخدم قواعد البث (broadcasting rules) في NumPy عند دمج المراصد وقيم المعاملات.

إضافةً لذلك، تمت إضافة التغييرات التالية:

  • اكتسب Estimator V2 معاملاً جديداً هو precision في دالة run() يحدد الدقة المستهدفة لتقديرات قيمة التوقع.
  • يحتوي Sampler V2 على معامل shots في دالة run() الخاصة به.
أمثلة

مثال على Estimator V2 يستخدم الدقة precision في run():

# Estimate expectation values for two PUBs, both with 0.05 precision.
estimator.run([(circuit1, obs_array1), (circuit2, obs_array_2)], precision=0.05)

مثال على Sampler V2 يستخدم shots في run():

# Sample two circuits at 128 shots each.
sampler.run([circuit1, circuit2], shots=128)

# Sample two circuits at different amounts of shots.
# The "None"s are necessary as placeholders
# for the lack of parameter values in this example.
sampler.run([
(circuit1, None, 123),
(circuit2, None, 456),
])

المخرجات

أصبحت المخرجات الآن بصيغة PubResult. الـ PubResult هو البيانات والبيانات الوصفية الناتجة عن تنفيذ PUB واحدة.

  • يستمر Estimator V2 في إعادة قيم التوقع.

  • يحتوي الجزء data في PubResult الخاص بـ Estimator V2 على قيم التوقع والأخطاء المعيارية (stds). كانت V1 تُعيد التباين (variance) في البيانات الوصفية.

  • يُعيد Sampler V2 القياسات لكل shot على شكل سلاسل بت (bitstrings)، بدلاً من التوزيعات شبه الاحتمالية (quasi-probability distributions) التي كانت تُعيدها واجهة V1. تعرض سلاسل البت نتائج القياس مع الحفاظ على ترتيب الـ shots كما تم قياسها.

  • يمتلك Sampler V2 أساليب مساعدة مثل get_counts() لتسهيل عملية الترقية.

  • تُنظّم كائنات نتائج Sampler V2 البيانات حسب أسماء السجلات الكلاسيكية للدوائر المدخلة، لضمان التوافق مع الدوائر الديناميكية. الاسم الافتراضي للسجل الكلاسيكي هو meas كما يظهر في المثال التالي. عند تعريف دائرتك، إذا أنشأت سجلاً كلاسيكياً أو أكثر بأسماء غير افتراضية، استخدم تلك الأسماء للحصول على النتائج. يمكنك معرفة اسم السجل الكلاسيكي بتشغيل <circuit_name>.cregs، مثلاً qc.cregs.

    # Define a quantum circuit with 2 qubits
    circuit = QuantumCircuit(2)
    circuit.h(0)
    circuit.cx(0, 1)
    circuit.measure_all()
    circuit.draw()
            ┌───┐      ░ ┌─┐
    q_0: ┤ H ├──■───░─┤M├───
    └───┘┌─┴─┐ ░ └╥┘┌─┐
    q_1: ─────┤ X ├─░──╫─┤M├
    └───┘ ░ ║ └╥┘
    meas: 2/══════════════╩══╩═
    0 1

أمثلة على Estimator (مدخلات ومخرجات)

# Estimator V1: Execute 1 circuit with 4 observables
job = estimator_v1.run([circuit] * 4, [obs1, obs2, obs3, obs4])
evs = job.result().values

# Estimator V2: Execute 1 circuit with 4 observables
job = estimator_v2.run([(circuit, [obs1, obs2, obs3, obs4])])
evs = job.result()[0].data.evs

أمثلة على Sampler (مدخلات ومخرجات)

  # Sampler V1: Execute 1 circuit with 3 parameter sets
job = sampler_v1.run([circuit] * 3, [vals1, vals2, vals3])
dists = job.result().quasi_dists

# Sampler V2: Executing 1 circuit with 3 parameter sets
job = sampler_v2.run([(circuit, [vals1, vals2, vals3])])
counts = job.result()[0].data.meas.get_counts()

مثال يستخدم سجلات مخرجات مختلفة

from qiskit import ClassicalRegister, QuantumRegister, QuantumCircuit
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

alpha = ClassicalRegister(5, "alpha")
beta = ClassicalRegister(7, "beta")
qreg = QuantumRegister(12)

circuit = QuantumCircuit(qreg, alpha, beta)
circuit.h(0)
circuit.measure(qreg[:5], alpha)
circuit.measure(qreg[5:], beta)

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=12)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
print(f" >> Counts for the alpha output register: {pub_result.data.alpha.get_counts()}")
print(f" >> Counts for the beta output register: {pub_result.data.beta.get_counts()}")

الخيارات

يختلف أسلوب تحديد الخيارات في أوليات V2 على النحو التالي:

  • أصبح لكلٍّ من SamplerV2 وEstimatorV2 فئات خيارات منفصلة. يمكنك الاطلاع على الخيارات المتاحة وتحديث قيمها أثناء تهيئة الأولية أو بعدها.
  • بدلاً من دالة set_options()، أصبحت خيارات أوليات V2 تمتلك دالة update() تُطبّق التغييرات على سمة options.
  • إذا لم تحدد قيمة لأحد الخيارات، يأخذ القيمة الخاصة Unset ويُستخدم الإعداد الافتراضي للخادم.
  • في أوليات V2، سمة options من نوع dataclass في Python. يمكنك استخدام الدالة المدمجة asdict لتحويلها إلى قاموس.

راجع مرجع واجهة برمجة التطبيقات للاطلاع على قائمة الخيارات المتاحة.

from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
estimator = Estimator(backend, options={"resilience_level": 2})

# Setting options after primitive initialization
# This uses auto complete.
estimator.options.default_shots = 4000
# This does bulk update.
estimator.options.update(default_shots=4000, resilience_level=2)

# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(estimator.options))
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import SamplerV2 as Sampler

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
sampler = Sampler(backend, options={"default_shots": 4096})

# Setting options after primitive initialization
# This uses auto complete.
sampler.options.dynamical_decoupling.enable = True
# Turn on gate twirling. Requires qiskit_ibm_runtime 0.23.0 or later.
sampler.options.twirling.enable_gates = True

# This does bulk update. The value for default_shots is overridden if you specify shots with run() or in the PUB.
sampler.options.update(default_shots=1024, dynamical_decoupling={"sequence_type": "XpXm"})

# Print the dictionary format.
# Server defaults are used for unset options.
print(asdict(sampler.options))

تخفيف الأخطاء وقمعها

  • بما أن Sampler V2 يُعيد العيّنات دون معالجة لاحقة، فهو لا يدعم مستويات المرونة (resilience levels).

  • لا يدعم Sampler V2 خيار optimization_level.

  • سيتوقف Estimator V2 عن دعم optimization_level في أو حول 30 سبتمبر 2024.

  • لا يدعم Estimator V2 مستوى المرونة 3. يعود ذلك إلى أن مستوى المرونة 3 في Estimator V1 يستخدم تقنية إلغاء الأخطاء الاحتمالي (PEC)، وهي تقنية مثبتة لإعطاء نتائج غير متحيزة لكن بتكلفة وقت معالجة أسية. تمت إزالة المستوى 3 لتسليط الضوء على هذه المقايضة. بإمكانك مع ذلك استخدام تقنية PEC كأسلوب لتخفيف الأخطاء عبر تحديد خيار pec_mitigation.

  • يدعم Estimator V2 مستويات المرونة resilience_level من 0 إلى 2، كما هو موضّح في الجدول التالي. هذه الخيارات أكثر تقدماً من نظيراتها في V1. كما يمكنك تشغيل أو إيقاف أساليب تخفيف/قمع الأخطاء الفردية بشكل صريح.

    المستوى 1المستوى 2
    Measurement twirlingMeasurement twirling
    تخفيف أخطاء القراءةتخفيف أخطاء القراءة
    ZNE
from dataclasses import asdict
from qiskit_ibm_runtime import QiskitRuntimeService
from qiskit_ibm_runtime import EstimatorV2 as Estimator

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

# Setting options during primitive initialization
estimator = Estimator(backend)

# Set resilience_level to 0
estimator.options.resilience_level = 0

# Turn on measurement error mitigation
estimator.options.resilience.measure_mitigation = True
from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(backend)
# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"

print(f">> dynamical decoupling sequence to use: {sampler.options.dynamical_decoupling.sequence_type}")

التحويل (Transpilation)

لا تدعم أوليات V2 سوى الدوائر التي تلتزم بمعمارية مجموعة التعليمات (ISA) الخاصة بالـ Backend المعيّن. ونظراً لأن الأوليات لا تُجري عمليات التخطيط (layout) والتوجيه (routing) والترجمة (translation)، فإن خيارات التحويل المقابلة في V1 غير مدعومة.

حالة المهمة

تمتلك أوليات V2 فئة جديدة هي RuntimeJobV2، ترث من BasePrimitiveJob. تُعيد دالة status() في هذه الفئة الجديدة سلسلة نصية بدلاً من قيمة JobStatus enum من Qiskit. راجع مرجع واجهة برمجة RuntimeJobV2 للاطلاع على التفاصيل.

job = estimator.run(...)

# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")

خطوات الترحيل إلى Estimator V2

  1. استبدل from qiskit_ibm_runtime import Estimator بـ from qiskit_ibm_runtime import EstimatorV2 as Estimator.

  2. احذف أي عبارات from qiskit_ibm_runtime import Options، إذ لا تستخدم البدائيات V2 فئة Options. يمكنك عوضًا عن ذلك تمرير الخيارات كقاموس عند تهيئة فئة EstimatorV2 (مثلًا estimator = Estimator(backend, options={"dynamical_decoupling": {"enable": True}})), أو ضبطها بعد التهيئة:

    estimator = Estimator(backend)
    estimator.options.dynamical_decoupling.enable = True
  3. راجع جميع الخيارات المدعومة وأجرِ التحديثات اللازمة.

  4. جمِّع كل دائرة تريد تشغيلها مع المراقبات وقيم المعاملات المراد تطبيقها عليها في صف (PUB). مثلًا، استخدم (circuit1, observable1, parameter_set1) إذا أردت تشغيل circuit1 مع observable1 وparameter_set1.

  5. قد تحتاج إلى إعادة تشكيل مصفوفات المراقبات أو مجموعات المعاملات إذا أردت حساب جداءها الخارجي. على سبيل المثال، مصفوفة مراقبات بشكل (4, 1) ومصفوفة مجموعات معاملات بشكل (1, 6) ستعطيك نتيجة بشكل (4, 6) قيمة توقع. راجع قواعد البث في NumPy لمزيد من التفاصيل.

  6. يمكنك اختياريًا تحديد الدقة التي تريدها لهذا الـ PUB تحديدًا.

  7. حدِّث طريقة run() في المُقدِّر لتمرير قائمة الـ PUBs. مثلًا: run([(circuit1, observable1, parameter_set1)]). يمكنك اختياريًا تحديد precision هنا، وستُطبَّق على جميع الـ PUBs.

  8. نتائج وظيفة Estimator V2 مجمَّعة حسب الـ PUBs. يمكنك الاطلاع على قيمة التوقع والخطأ المعياري لكل PUB عبر الفهرسة إليه. مثلًا:

pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")

أمثلة كاملة على Estimator

تشغيل تجربة واحدة

استخدم Estimator لتحديد قيمة التوقع لزوج واحد من Circuit والمراقِبة.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
estimator = Estimator(backend)

n_qubits = 127

mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
observable = SparsePauliOp("Z" * n_qubits)

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)

job = estimator.run([(isa_circuit, isa_observable)])
result = job.result()

print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")

تشغيل تجارب متعددة في وظيفة واحدة

استخدم Estimator لتحديد قيم التوقع لأزواج متعددة من الـ Circuit والمراقِبات.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)

n_qubits = 3
rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]

isa_circuits = pm.run(circuits)
isa_observables = [ob.apply_layout(isa_circuits[0].layout) for ob in observables]

estimator = Estimator(backend)
job = estimator.run([(isa_circuits[0], isa_observables[0]),(isa_circuits[1], isa_observables[1]),(isa_circuits[2], isa_observables[2])])
job_result = job.result()
for idx in range(len(job_result)):
pub_result = job_result[idx]
print(f">>> Expectation values for PUB {idx}: {pub_result.data.evs}")
print(f">>> Standard errors for PUB {idx}: {pub_result.data.stds}")

تشغيل دوائر ذات معاملات

استخدم Estimator لتشغيل تجارب متعددة في وظيفة واحدة، مستفيدًا من قيم المعاملات لزيادة إمكانية إعادة استخدام الـ Circuit. في المثال التالي، لاحظ أن الخطوتين 1 و2 متطابقتان في V1 وV2.

import numpy as np

from qiskit.circuit import QuantumCircuit, Parameter
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

# Step 1: Map classical inputs to a quantum problem

theta = Parameter("θ")

chsh_circuit = QuantumCircuit(2)
chsh_circuit.h(0)
chsh_circuit.cx(0, 1)
chsh_circuit.ry(theta, 0)

number_of_phases = 21
phases = np.linspace(0, 2 * np.pi, number_of_phases)
individual_phases = [[ph] for ph in phases]

ZZ = SparsePauliOp.from_list([("ZZ", 1)])
ZX = SparsePauliOp.from_list([("ZX", 1)])
XZ = SparsePauliOp.from_list([("XZ", 1)])
XX = SparsePauliOp.from_list([("XX", 1)])
ops = [ZZ, ZX, XZ, XX]

# Step 2: Optimize problem for quantum execution.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
chsh_isa_circuit = pm.run(chsh_circuit)
isa_observables = [operator.apply_layout(chsh_isa_circuit.layout) for operator in ops]

from qiskit_ibm_runtime import EstimatorV2 as Estimator

# Step 3: Execute using Qiskit primitives.

# Reshape observable array for broadcasting
reshaped_ops = np.fromiter(isa_observables, dtype=object)
reshaped_ops = reshaped_ops.reshape((4, 1))

estimator = Estimator(backend, options={"default_shots": int(1e4)})
job = estimator.run([(chsh_isa_circuit, reshaped_ops, individual_phases)])
# Get results for the first (and only) PUB
pub_result = job.result()[0]
print(f">>> Expectation values: {pub_result.data.evs}")
print(f">>> Standard errors: {pub_result.data.stds}")
print(f">>> Metadata: {pub_result.metadata}")

استخدام الجلسات والخيارات المتقدمة

استكشف الجلسات والخيارات المتقدمة لتحسين أداء الـ Circuit على وحدات المعالجة الكمية (QPUs).

تنبيه

كتلة الكود التالية ستُعيد خطأً للمستخدمين على الخطة المفتوحة (Open Plan) لأنها تستخدم الجلسات. لا يمكن تشغيل أعباء العمل على الخطة المفتوحة إلا في وضع الوظيفة أو وضع الدُّفعة.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Session, EstimatorV2 as Estimator

n_qubits = 127

rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
observable = SparsePauliOp("X" * n_qubits)
another_observable = SparsePauliOp("Y" * n_qubits)

pm = generate_preset_pass_manager(optimization_level=1, backend=backend)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
another_isa_observable = another_observable.apply_layout(another_isa_circuit.layout)

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

with Session(backend=backend) as session:
estimator = Estimator()

estimator.options.resilience_level = 1

job = estimator.run([(isa_circuit, isa_observable)])
another_job = estimator.run([(another_isa_circuit, another_isa_observable)])
result = job.result()
another_result = another_job.result()

# first job
print(f" > Expectation value: {result[0].data.evs}")
print(f" > Metadata: {result[0].metadata}")

# second job
print(f" > Another Expectation value: {another_result[0].data.evs}")
print(f" > More Metadata: {another_result[0].metadata}")

خطوات الانتقال إلى Sampler V2

  1. استبدل from qiskit_ibm_runtime import Sampler بـ from qiskit_ibm_runtime import SamplerV2 as Sampler.
  2. احذف أي عبارات from qiskit_ibm_runtime import Options، لأن كلاس Options لم يعد مستخدمًا في primitives الإصدار الثاني. بدلًا من ذلك، يمكنك تمرير الخيارات كـ dictionary عند تهيئة كلاس SamplerV2 (مثلًا sampler = Sampler(backend, options={"default_shots": 1024})), أو ضبطها بعد التهيئة:
    sampler = Sampler(backend)
    sampler.options.default_shots = 1024
  3. راجع جميع الخيارات المدعومة وأجرِ التعديلات اللازمة.
  4. اجمع كل دائرة تريد تشغيلها مع المؤثِّرات (observables) وقيم المعاملات في tuple واحدة (وهي ما يُعرف بـ PUB). مثلًا، استخدم (circuit1, parameter_set1) إذا أردت تشغيل circuit1 مع parameter_set1. يمكنك اختياريًا تحديد عدد القياسات (shots) لكل PUB بشكل منفصل.
  5. حدِّث دالة run() الخاصة بـ sampler لتمرير قائمة PUBs. مثلًا، run([(circuit1, parameter_set1)]). يمكنك اختياريًا تحديد shots هنا، وسيُطبَّق على جميع PUBs.
  6. نتائج مهام Sampler V2 مُجمَّعة حسب PUBs. يمكنك الاطلاع على بيانات الإخراج لكل PUB عبر الفهرسة. وبينما يُرجع Sampler V2 عينات غير مرجَّحة، يوفر كلاس النتائج دالة مساعدة للحصول على العدد (counts) عوضًا عن ذلك. مثلًا:
pub_result = job.result()[0]
print(f">>> Counts: {pub_result.data.meas.get_counts()}")
print(f">>> Per-shot measurement: {pub_result.data.meas.get_counts()}")
ملاحظة

تحتاج إلى اسم السجل الكلاسيكي للحصول على النتائج. بشكل افتراضي، يُسمَّى meas عند استخدام measure_all(). إذا عرَّفت عند بناء الدائرة سجلًا كلاسيكيًا واحدًا أو أكثر باسم غير افتراضي، فاستخدم ذلك الاسم للحصول على النتائج. يمكنك معرفة اسم السجل الكلاسيكي عبر تشغيل <circuit_name>.cregs، مثلًا qc.cregs.

أمثلة كاملة على Sampler

تشغيل تجربة واحدة

استخدم Sampler لتحديد العدد (counts) أو توزيع الاحتمالات شبه الكلاسيكية (quasi-probability distribution) لدائرة واحدة.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

n_qubits = 127

mat = np.real(random_hermitian(n_qubits, seed=1234))
circuit = IQP(mat)
circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

sampler = Sampler(backend)
job = sampler.run([isa_circuit])
result = job.result()

تشغيل تجارب متعددة في مهمة واحدة

استخدم Sampler لتحديد العدد (counts) أو توزيعات الاحتمالات شبه الكلاسيكية لدوائر متعددة في مهمة واحدة.

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler

service = QiskitRuntimeService()

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

n_qubits = 127

rng = np.random.default_rng()
mats = [np.real(random_hermitian(n_qubits, seed=rng)) for _ in range(3)]
circuits = [IQP(mat) for mat in mats]
for circuit in circuits:
circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuits = pm.run(circuits)

sampler = Sampler(backend)
job = sampler.run(isa_circuits)
result = job.result()

for idx, pub_result in enumerate(result):
print(f" > Counts for pub {idx}: {pub_result.data.meas.get_counts()}")

تشغيل دوائر مُعامَلة (parameterized circuits)

شغِّل عدة تجارب في مهمة واحدة مستفيدًا من قيم المعاملات لزيادة إمكانية إعادة استخدام الدوائر.

import numpy as np
from qiskit.circuit.library import RealAmplitudes
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QiskitRuntimeService

# Step 1: Map classical inputs to a quantum problem
num_qubits = 127
circuit = RealAmplitudes(num_qubits=num_qubits, reps=2)
circuit.measure_all()

# Define three sets of parameters for the circuit
rng = np.random.default_rng(1234)
parameter_values = [
rng.uniform(-np.pi, np.pi, size=circuit.num_parameters) for _ in range(3)
]

# Step 2: Optimize problem for quantum execution.

service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=num_qubits)

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)

# Step 3: Execute using Qiskit primitives.

from qiskit_ibm_runtime import SamplerV2 as Sampler

sampler = Sampler(backend)
job = sampler.run([(isa_circuit, parameter_values)])
result = job.result()
# Get results for the first (and only) PUB
pub_result = result[0]
# Get counts from the classical register "meas".
print(f" >> Counts for the meas output register: {pub_result.data.meas.get_counts()}")

استخدام الجلسات والخيارات المتقدمة

استكشف الجلسات (sessions) والخيارات المتقدمة لتحسين أداء الدوائر على وحدات معالجة الكم (QPUs).

تنبيه

سيُرجع مقطع الكود التالي خطأً للمستخدمين الذين يستخدمون الخطة المجانية (Open Plan)، لأنه يستخدم الجلسات. لا يمكن تشغيل أعباء العمل على الخطة المجانية إلا في وضع المهمة (job mode) أو وضع الدُفعات (batch mode).

import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler, Session

n_qubits = 127

rng = np.random.default_rng(1234)
mat = np.real(random_hermitian(n_qubits, seed=rng))
circuit = IQP(mat)
circuit.measure_all()
mat = np.real(random_hermitian(n_qubits, seed=rng))
another_circuit = IQP(mat)
another_circuit.measure_all()

pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
another_isa_circuit = pm.run(another_circuit)

service = QiskitRuntimeService()

# Turn on dynamical decoupling with sequence XpXm.
sampler.options.dynamical_decoupling.enable = True
sampler.options.dynamical_decoupling.sequence_type = "XpXm"

backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)

with Session(backend=backend) as session:
sampler = Sampler()
job = sampler.run([isa_circuit])
another_job = sampler.run([another_isa_circuit])
result = job.result()
another_result = another_job.result()

# first job
print(f" > Counts for job 1: {result[0].data.meas.get_counts()}")

# second job
print(f" > Counts for job 2: {another_result[0].data.meas.get_counts()}")

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

توصيات