الترقية إلى أوليات 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 من الأولية المحددة.
- Estimator V2
- Estimator (V1)
from qiskit_ibm_runtime import EstimatorV2 as Estimator
from qiskit_ibm_runtime import Estimator
- Sampler V2
- Sampler (V1)
from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime import 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 (مدخلات ومخرجات)
- دائرة واحدة، 4 مراصد
- دائرة واحدة، 4 مراصد، مجموعتا معاملات
- دائرتان، مرصدان
# 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
# Estimator V1: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v1.run([circuit] * 8, [obs1, obs2, obs3, obs4] * 2, [vals1, vals2] * 4)
evs = job.result().values
# Estimator V2: Execute 1 circuit with 4 observables and 2 parameter sets
job = estimator_v2.run([(circuit, [[obs1], [obs2], [obs3], [obs4]], [[vals1], [vals2]])])
evs = job.result()[0].data.evs
# Estimator V1: Cannot execute 2 circuits with different observables
# Estimator V2: Execute 2 circuits with 2 different observables. There are
# two PUBs because each PUB can have only one circuit.
job = estimator_v2.run([(circuit1, obs1), (circuit2, obs2)])
evs1 = job.result()[0].data.evs # result for pub 1 (circuit 1)
evs2 = job.result()[1].data.evs # result for pub 2 (circuit 2)
أمثلة على Sampler (مدخلات ومخرجات)
- دائرة واحدة، 3 مجموعات معاملات
- دائرتان، مجموعة معاملات واحدة
- تحويل مخرجات V2 إلى صيغة V1
# 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()
# Sampler V1: Execute 2 circuits with 1 parameter set
job = sampler_v1.run([circuit1, circuit2], [vals1] * 2)
dists = job.result().quasi_dists
# Sampler V2: Execute 2 circuits with 1 parameter set
job = sampler_v2.run([(circuit1, vals1), (circuit2, vals1)])
counts1 = job.result()[0].data.meas.get_counts() # result for pub 1 (circuit 1)
counts2 = job.result()[1].data.meas.get_counts() # result for pub 2 (circuit 2)
كانت صيغة مخرجات V1 عبارة عن قاموس مفاتيحه سلاسل البت (كأعداد صحيحة) وقيمه الاحتمالات شبه الدقيقة (quasi-probabilities) لكل دائرة. أما صيغة V2 فلها المفتاح ذاته (لكن كسلسلة نصية) والأعداد (counts) كقيم. لتحويل صيغة V2 إلى V1، قسّم الأعداد على عدد shots، علماً بأن عدد الـ shots المختار موصوف في دليل خيارات Sampler.
v2_result = sampler_v2_job.result()
v1_format = []
for pub_result in v2_result:
counts = pub_result.data.meas.get_counts()
v1_format.append( {int(key, 2): val/shots for key, val in counts.items()} )
مثال يستخدم سجلات مخرجات مختلفة
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لتحويلها إلى قاموس.
راجع مرجع واجهة برمجة التطبيقات للاطلاع على قائمة الخيارات المتاحة.
- Estimator V2
- Estimator (V1)
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 qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
estimator = Estimator(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
estimator.set_options(shots=4000)
- Sampler V2
- Sampler (V1)
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))
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False)
# Setting options during primitive initialization
options = Options()
# This uses auto complete.
options.resilience_level = 2
sampler = Sampler(backend=backend, options=options)
# Setting options after primitive initialization.
# This does bulk update.
sampler.set_options(shots=2000)
تخفيف الأخطاء وقمعها
-
بما أن 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 twirling Measurement twirling تخفيف أخطاء القراءة تخفيف أخطاء القراءة ZNE
- Estimator V2
- Estimator (V1)
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 Estimator, Options
estimator = Estimator(backend, options=options)
options = Options()
options.resilience_level = 2
- Sampler V2
- Sampler (V1)
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}")
from qiskit_ibm_runtime import Sampler, Options
sampler = Sampler(backend, options=options)
options = Options()
options.resilience_level = 2
التحويل (Transpilation)
لا تدعم أوليات V2 سوى الدوائر التي تلتزم بمعمارية مجموعة التعليمات (ISA) الخاصة بالـ Backend المعيّن. ونظراً لأن الأوليات لا تُجري عمليات التخطيط (layout) والتوجيه (routing) والترجمة (translation)، فإن خيارات التحويل المقابلة في V1 غير مدعومة.
حالة المهمة
تمتلك أوليات V2 فئة جديدة هي RuntimeJobV2، ترث من BasePrimitiveJob. تُعيد دالة status() في هذه الفئة الجديدة سلسلة نصية بدلاً من قيمة JobStatus enum من Qiskit. راجع مرجع واجهة برمجة RuntimeJobV2 للاطلاع على التفاصيل.
- أوليات V2
- أوليات V1
job = estimator.run(...)
# check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() == "RUNNING"}")
from qiskit.providers.jobstatus import JobStatus
job = estimator.run(...)
#check if a job is still running
print(f"Job {job.job_id()} is still running: {job.status() is JobStatus.RUNNING}")
خطوات الترحيل إلى Estimator V2
-
استبدل
from qiskit_ibm_runtime import Estimatorبـfrom qiskit_ibm_runtime import EstimatorV2 as Estimator. -
احذف أي عبارات
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 -
راجع جميع الخيارات المدعومة وأجرِ التحديثات اللازمة.
-
جمِّع كل دائرة تريد تشغيلها مع المراقبات وقيم المعاملات المراد تطبيقها عليها في صف (PUB). مثلًا، استخدم
(circuit1, observable1, parameter_set1)إذا أردت تشغيلcircuit1معobservable1وparameter_set1. -
قد تحتاج إلى إعادة تشكيل مصفوفات المراقبات أو مجموعات المعاملات إذا أردت حساب جداءها الخارجي. على سبيل المثال، مصفوفة مراقبات بشكل (4, 1) ومصفوفة مجموعات معاملات بشكل (1, 6) ستعطيك نتيجة بشكل (4, 6) قيمة توقع. راجع قواعد البث في NumPy لمزيد من التفاصيل.
-
يمكنك اختياريًا تحديد الدقة التي تريدها لهذا الـ PUB تحديدًا.
-
حدِّث طريقة
run()في Estimator لتمرير قائمة الـ PUBs. مثلًا:run([(circuit1, observable1, parameter_set1)]). يمكنك اختياريًا تحديدprecisionهنا، وستُطبَّق على جميع الـ PUBs. -
نتائج وظيفة 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 والمراقِبة.
- Estimator V2
- Estimator (V1)
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}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
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)
observable = SparsePauliOp("Z" * n_qubits)
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
isa_circuit = pm.run(circuit)
isa_observable = observable.apply_layout(isa_circuit.layout)
estimator = Estimator(backend)
job = estimator.run(isa_circuit, isa_observable)
result = job.result()
print(f" > Observable: {observable.paulis}")
print(f" > Expectation value: {result.values}")
print(f" > Metadata: {result.metadata}")
تشغيل تجارب متعددة في وظيفة واحدة
استخدم Estimator لتحديد قيم التوقع لأزواج متعددة من الـ Circuit والمراقِبات.
- Estimator V2
- Estimator (V1)
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}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import SparsePauliOp, random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Estimator
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]
observables = [
SparsePauliOp("X" * n_qubits),
SparsePauliOp("Y" * n_qubits),
SparsePauliOp("Z" * n_qubits),
]
# Get ISA circuits
pm = generate_preset_pass_manager(backend=backend, optimization_level=1)
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, isa_observables)
result = job.result()
print(f" > Expectation values: {result.values}")
تشغيل دوائر ذات معاملات
استخدم Estimator لتشغيل تجارب متعددة في وظيفة واحدة، مستفيدًا من قيم المعاملات لزيادة إمكانية إعادة استخدام الـ Circuit. في المثال التالي، لاحظ أن الخطوتين 1 و2 متطابقتان في V1 وV2.
- Estimator V2
- Estimator (V1)
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}")
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 Estimator
# Step 3: Execute using Qiskit Primitives.
num_ops = len(isa_observables)
batch_circuits = [chsh_isa_circuit] * number_of_phases * num_ops
batch_ops = [op for op in isa_observables for _ in individual_phases]
batch_phases = individual_phases * num_ops
estimator = Estimator(backend, options={"shots": int(1e4)})
job = estimator.run(batch_circuits, batch_ops, batch_phases)
expvals = job.result().values
استخدام الجلسات والخيارات المتقدمة
استكشف الجلسات والخيارات المتقدمة لتحسين أداء الـ Circuit على وحدات المعالجة الكمية (QPUs).
كتلة الكود التالية ستُعيد خطأً للمستخدمين على الخطة المفتوحة (Open Plan) لأنها تستخدم الجلسات. لا يمكن تشغيل أعباء العمل على الخطة المفتوحة إلا في وضع الوظيفة أو وضع الدُّفعة.
- Estimator V2
- Estimator (V1)
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}")
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, Estimator, Options
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(backend=backend, optimization_level=1)
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)
options = Options()
options.optimization_level = 2
options.resilience_level = 2
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
estimator = Estimator(options=options)
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 values job 1: {result.values}")
# second job
print(f" > Expectation values job 2: {another_result.values}")
خطوات الانتقال إلى Sampler V2
- استبدل
from qiskit_ibm_runtime import Samplerبـfrom qiskit_ibm_runtime import SamplerV2 as Sampler. - احذف أي عبارات
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 - راجع جميع الخيارات المدعومة وأجرِ التعديلات اللازمة.
- اجمع كل دائرة تريد تشغيلها مع المؤثِّرات (observables) وقيم المعاملات في tuple واحدة (وهي ما يُعرف بـ PUB). مثلًا، استخدم
(circuit1, parameter_set1)إذا أردت تشغيلcircuit1معparameter_set1. يمكنك اختياريًا تحديد عدد القياسات (shots) لكل PUB بشكل منفصل. - حدِّث طريقة
run()في Sampler لتمرير قائمة PUBs. مثلًا،run([(circuit1, parameter_set1)]). يمكنك اختياريًا تحديدshotsهنا، وسيُطبَّق على جميع PUBs. - نتائج مهام 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) لدائرة واحدة.
- Sampler V2
- Sampler (V1)
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()
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler
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()
sampler = Sampler(backend)
job = sampler.run(circuit)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
تشغيل تجارب متعددة في مهمة واحدة
استخدم Sampler لتحديد العدد (counts) أو توزيعات الاحتمالات شبه الكلاسيكية لدوائر متعددة في مهمة واحدة.
- Sampler V2
- Sampler (V1)
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()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, 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()
sampler = Sampler(backend)
job = sampler.run(circuits)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
تشغيل دوائر مُعامَلة (parameterized circuits)
شغِّل عدة تجارب في مهمة واحدة مستفيدًا من قيم المعاملات لزيادة إمكانية إعادة استخدام الدوائر.
- Sampler V2
- Sampler (V1)
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()}")
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 = 5
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 Sampler
sampler = Sampler(backend)
job = sampler.run([isa_circuit] * 3, parameter_values)
result = job.result()
print(f" > Quasi-probability distribution: {result.quasi_dists}")
print(f" > Metadata: {result.metadata}")
استخدام الجلسات والخيارات المتقدمة
استكشف الجلسات (sessions) والخيارات المتقدمة لتحسين أداء الدوائر على وحدات معالجة ال كم (QPUs).
سيُرجع مقطع الكود التالي خطأً للمستخدمين الذين يستخدمون الخطة المجانية (Open Plan)، لأنه يستخدم الجلسات. لا يمكن تشغيل أعباء العمل على الخطة المجانية إلا في وضع المهمة (job mode) أو وضع الدُفعات (batch mode).
- Sampler V2
- Sampler (V1)
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()}")
import numpy as np
from qiskit.circuit.library import IQP
from qiskit.quantum_info import random_hermitian
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Session, Options
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()
options = Options()
options.optimization_level = 2
options.resilience_level = 0
service = QiskitRuntimeService()
backend = service.least_busy(operational=True, simulator=False, min_num_qubits=127)
with Session(backend=backend) as session:
sampler = Sampler(options=options)
job = sampler.run(circuit)
another_job = sampler.run(another_circuit)
result = job.result()
another_result = another_job.result()
# first job
print(f" > Quasi-probability distribution job 1: {result.quasi_dists}")
# second job
print(f" > Quasi-probability distribution job 2: {another_result.quasi_dists}")
الخطوات التالية
- تعلَّم المزيد عن ضبط الخيارات في أدلة خيارات Sampler وخيارات Estimator وخيارات Executor.
- اطَّلع على مزيد من التفاصيل حول مدخلات ومخرجات Primitives.
- جرِّب البرنامج التعليمي متراجحة CHSH.