انتقل إلى المحتوى الرئيسي
هذا المشروع في مرحلة تجريبية. وجدت خطأ؟ افتح مشكلة على GitHub. لديك سؤال أو فكرة؟ اسأل في Discussions — نحب سماع ملاحظاتك!
كيف جودة الترجمة في هذه الصفحة؟

محاكاة الدوائر كلاسيكيًا باستخدام OBP

في هذا الدليل ستتعلم كيف تحاكي كائنات QuantumCircuit كلاسيكيًا لتقدير القيم المتوقعة بالكامل عبر OBP.

بما أن OBP يأخذ مرصودًا وينشره عكسيًا عبر دائرة معينة، فإن "محاكاة" الدائرة تعني حساب القيمة المتوقعة للمرصود المستهدف بالنسبة لهذه الدائرة. وكما سترى لاحقًا، فإن حزمة qiskit-addon-obp قادرة حتى على التعامل مع نماذج ضوضاء بسيطة، مما يتيح لك حساب القيم المتوقعة المشوَّشة أيضًا!

بناء دائرة مثال​

لأغراض هذا الدليل، سنستخدم نفس الدائرة المثال المستخدمة في دليل اقتطاع حدود باولي:

# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util

_needed = {"numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_obp": "qiskit-addon-obp", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "rustworkx": "rustworkx"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
import rustworkx.generators
from qiskit.synthesis import LieTrotter
from qiskit_addon_utils.problem_generators import (
PauliOrderStrategy,
generate_time_evolution_circuit,
generate_xyz_hamiltonian,
)
from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types

# we generate a linear chain of 10 qubits
num_qubits = 10
linear_chain = rustworkx.generators.path_graph(num_qubits)

# we use an arbitrary XY model
hamiltonian = generate_xyz_hamiltonian(
linear_chain,
coupling_constants=(0.05, 0.02, 0.0),
ext_magnetic_field=(0.02, 0.08, 0.0),
pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,
)
# we evolve for some time
circuit = generate_time_evolution_circuit(
hamiltonian, synthesis=LieTrotter(reps=3), time=2.0
)
# slice the circuit by gate type
slices = slice_by_gate_types(circuit)

لكن ما سبق هو فقط الدائرة التي تصف التطور الزمني تحت هاملتوني مختار. نحتاج أيضًا إلى حالة ابتدائية للبدء منها، نحسب بالنسبة لها القيم المتوقعة للمرصود.

بالطبع، يمكننا اختيار حالة الأصفار (أو الفراغ) كحالة ابتدائية، لكن لإظهار كيفية إدراج حالة ابتدائية خاصة بك، نختار حالة مختلفة أدناه.

أحد الاحتمالات هو إضافة الحالة الابتدائية في بداية دائرة التطور الزمني أعلاه: circuit.compose(initial_state, front=True). لكن بما أننا قسّمنا circuit إلى شرائح بالفعل، فمن الأسهل إدراج الحالة الابتدائية ببساطة كأول شريحة، وهو ما نفعله أدناه.

بهذه الطريقة، يمكننا ببساطة استبدال الشريحة الأولى بحالة ابتدائية أخرى إن أردنا تغييرها مستقبلًا، دون الحاجة إلى إعادة حساب شرائحنا.

from qiskit.circuit import QuantumCircuit

initial_state = QuantumCircuit(num_qubits)
for i in range(0, num_qubits, 2):
initial_state.x(i)

slices.insert(0, initial_state)
# for visualization purposes only, we recombine the slices with barriers between them and draw the resulting circuit
combine_slices(slices, include_barriers=True).draw("mpl", fold=50, scale=0.6)

Output of the previous code cell

محاكاة قيمة متوقعة بلا ضوضاء​

كمرصود مستهدف، نختار المرصود ZZ على الكيوبتات المركزية:

from qiskit.quantum_info import SparsePauliOp

obs = SparsePauliOp("IIIIZZIIII")

عند هذه النقطة، نحن جاهزون بالفعل لمحاكاة القيمة المتوقعة كلاسيكيًا باستخدام OBP. ولفعل ذلك، نمرّر ببساطة جميع الشرائح إلى الدالة backpropagate، هكذا:

from qiskit_addon_obp import backpropagate

vacuum_state_obs, _, metadata = backpropagate(obs, slices)

لقد نشرنا الآن مرصودنا المستهدف obs عكسيًا عبر الدائرة بأكملها (بما في ذلك initial_state التي وضعناها في slices[0]) مما أعطى SparsePauliOp جديدًا نحصل على قيمته المتوقعة بإسقاطه على حالة الفراغ (|00...00>).

يمكن تحقيق ذلك بسهولة بجمع معاملات جميع حدود باولي المعرَّفة في الأساس الحسابي:

vacuum_state_obs.coeffs[~vacuum_state_obs.paulis.x.any(axis=1)].sum()
np.complex128(-0.8285688012239535+4.9487770271457865e-20j)

للتحقق من صحة النتيجة (ولإثبات أن هذا يعمل) يمكننا مقارنة نتيجتنا مع Statevector من Qiskit:

from qiskit.quantum_info import Statevector

Statevector(combine_slices(slices)).expectation_value(obs)
np.complex128(-0.8285687255430366+0j)

بعض الملاحظات حول الأداء​

ستعتمد الكفاءة الحسابية لاستدعاء backpropagate أعلاه بشكل كبير على عدة أمور، منها:

  • بنية circuit
  • طريقة تقسيم الدائرة إلى شرائح
  • المرصود المستهدف
  • معاملات الاقتطاع

بما أن الدالة backpropagate تبسّط المرصود بعد تطبيق كل شريحة، فإن عدد البوابات في الشريحة يمكن أن يؤثر بشكل كبير على العبء الحسابي. يمكن تحقيق أكثر استراتيجيات تبسيط المؤثر عدوانية بتقسيم دائرتك إلى شرائح من بوابات منفردة.

بالإضافة إلى ذلك، يمكنك الاستفادة من جميع آليات الاقتطاع المدمجة في الدالة backpropagate. لم نفعل ذلك أعلاه، مما أدى فعليًا إلى قيمة متوقعة دقيقة، لكن يمكنك تعلم كيفية ذلك في دليل اقتطاع حدود باولي.

محاكاة قيمة متوقعة مشوَّشة​

تدعم حزمة qiskit-addon-obp أيضًا التعامل مع نماذج الضوضاء على شكل PauliLindbladError. وهذا مفيد خصوصًا عندما تكون قد وصّفت نموذج الضوضاء لطبقات البوابات ثنائية الكيوبت في دائرتك، مثلًا باستخدام NoiseLearner.

في هذا القسم سترى كيف يمكنك استخدام كائنات LayerError التي يعيدها NoiseLearner لحساب القيم المتوقعة المشوَّشة باستخدام OBP.

الحصول على نموذج ضوضاء​

عادةً، ستنفّذ NoiseLearner للحصول على نموذج ضوضاء لدائرتك المحددة. ولتجنب التعقيد (والعشوائية) في هذا الدرس، سنمتنع عن ذلك، وبدلًا منه سنكتب نموذج ضوضاء ثابتًا لدائرتنا أدناه.

لكننا نتأكد من أن بنية بياناتنا تطابق بنية NoiseLearnerResult. في شكلها الحالي (غير المترجمة)، تحتوي دائرتنا على 4 طبقات فريدة من البوابات ثنائية الكيوبت:

  • slices[1]: وتحتوي على بوابات Rxx تعمل على جميع أزواج الكيوبتات الفردية
  • slices[2]: وتحتوي على بوابات Rxx تعمل على جميع أزواج الكيوبتات الزوجية
  • slices[3]: وتحتوي على بوابات Ryy تعمل على جميع أزواج الكيوبتات الفردية
  • slices[4]: وتحتوي على بوابات Ryy تعمل على جميع أزواج الكيوبتات الزوجية

في الخلية أدناه، نبني يدويًا 4 كائنات LayerError لكل طبقة من هذه الطبقات بمعدلات خطأ عشوائية.

import numpy as np
from qiskit.quantum_info import PauliList
from qiskit_ibm_runtime.utils.noise_learner_result import (
LayerError,
PauliLindbladError,
)

# fmt: off
pauli_errors_even = ['IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIXX', 'IIIIIIIIXY', 'IIIIIIIIXZ', 'IIIIIIIIYI', 'IIIIIIIIYX', 'IIIIIIIIYY', 'IIIIIIIIYZ', 'IIIIIIIIZI', 'IIIIIIIIZX', 'IIIIIIIIZY', 'IIIIIIIIZZ', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII', 'XIIIIIIIII', 'XXIIIIIIII', 'XYIIIIIIII', 'XZIIIIIIII', 'YIIIIIIIII', 'YXIIIIIIII', 'YYIIIIIIII', 'YZIIIIIIII', 'ZIIIIIIIII', 'ZXIIIIIIII', 'ZYIIIIIIII', 'ZZIIIIIIII']
pauli_errors_odd = ['IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII']
# fmt: on

np.random.seed(42)

layer_error_odd_xx = LayerError(
circuit=slices[1],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_odd),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),
),
)

layer_error_even_xx = LayerError(
circuit=slices[2],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_even),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),
),
)

layer_error_odd_yy = LayerError(
circuit=slices[3],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_odd),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),
),
)

layer_error_even_yy = LayerError(
circuit=slices[4],
qubits=list(range(num_qubits)),
error=PauliLindbladError(
PauliList(pauli_errors_even),
0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),
),
)

لو استخدمت NoiseLearner لتحديد طبقات البوابات الفريدة ثنائية الكيوبت في دائرتك ووصف ضوضائها، لحصلت على كائن NoiseLearnerResult. سيحتوي هذا الناتج على قائمة من كائنات LayerError، تمامًا مثل التي بنيناها يدويًا أعلاه.

لكل طبقة فريدة ثنائية الكيوبت، يحتوي LayerError على:

  • QuantumCircuit الذي يمثل طبقة البوابات ثنائية الكيوبت تلك
  • فهارس الكيوبتات التي تعمل عليها هذه الدائرة
  • PauliLindbladError الذي يمثل نموذج الضوضاء الموصَّف لهذه الطبقة

سيحتوي PauliLindbladError على كائنين:

  • قائمة أخطاء باولي التي جرى توصيفها
  • معدلات الخطأ المقابلة لكل واحد من أخطاء باولي تلك

عادةً، ستكون قائمة أخطاء باولي متناثرة. وبشكل أدق، ستحتوي على أخطاء باولي أحادية الكيوبت لجميع الكيوبتات التي تعمل عليها بوابات، بالإضافة إلى أخطاء باولي ثنائية الكيوبت لجميع الكيوبتات المتصلة ببعضها.

إدراج الطبقات المشوَّشة في دائرتنا​

في القسم السابق، بنينا تحديدًا LayerError واحدًا لكل طبقة من طبقات البوابات الفريدة ثنائية الكيوبت المعروفة لدينا. هذا يعني أننا نعرف أي LayerError يطابق بدقة إحدى شرائحنا.

عادةً، عند استخدام LayerError، ستحتاج إلى معرفة ما هي طبقة البوابات الفريدة ثنائية الكيوبت، وأين تظهر داخل دائرتك. ثم ستحتاج إلى تعديل circuit و/أو slices لإدراج LayerError وفقًا لذلك. كيفية فعل ذلك في الحالة العامة خارج نطاق دليل الطريقة هذا. TODO: link to external documentation, once it exists! هنا، الأمر أبسط لأننا نعرف أي شريحة يقابلها LayerError. لذلك فالأمر الآن مجرد إدراج شرائح جديدة لتمثيل الضوضاء.

لاحظ أنه يجب علينا تغليف كل PauliLindbladError من LayerError.error داخل PauliLindbladErrorInstruction حتى يكون تعليمة صالحة في QuantumCircuit.

from qiskit_addon_obp.utils.noise import PauliLindbladErrorInstruction

noisy_slices = []
for slice_ in slices:
if slice_ == layer_error_even_xx.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_even_xx.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_odd_xx.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_odd_xx.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_even_yy.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_even_yy.error
),
slice_.qubits,
)
]
)
)
elif slice_ == layer_error_odd_yy.circuit:
noisy_slices.append(
QuantumCircuit.from_instructions(
[
(
PauliLindbladErrorInstruction(
layer_error_odd_yy.error
),
slice_.qubits,
)
]
)
)
noisy_slices.append(slice_)

يمكننا التحقق من عملنا ورسم الدائرة أدناه:

combine_slices(noisy_slices, include_barriers=True).draw(
"mpl", fold=100, scale=0.8
)

Output of the previous code cell

محاكاة قيمة متوقعة مشوَّشة​

عند هذه النقطة، تعمل محاكاة القيمة المتوقعة كلاسيكيًا بنفس الطريقة تمامًا كما سبق، فقط

vacuum_state_noisy_obs, _, metadata = backpropagate(obs, noisy_slices)
vacuum_state_noisy_obs.coeffs[
~vacuum_state_noisy_obs.paulis.x.any(axis=1)
].sum()
np.complex128(-0.7230801696448901+7.082755280463563e-19j)

نشير مرة أخرى إلى أنه ينبغي مراعاة عدة اعتبارات تخص الأداء. يرجى العودة إلى القسم المقابل أعلاه.