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

ابدأ مع قطع الدوائر باستخدام قطع البوابات

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

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

qiskit[all]~=2.3.0
qiskit-ibm-runtime~=0.43.1
qiskit-aer~=0.17
qiskit-addon-cutting~=0.10.0

يوضح هذا الدليل مثالين عمليين لقطع البوابات باستخدام حزمة qiskit-addon-cutting. يوضح المثال الأول كيفية تقليل عمق الدائرة (عدد تعليمات الدائرة) عن طريق قطع البوابات المتشابكة على الكيوبتات غير المتجاورة التي ستُكبّد خلاف ذلك نفقات SWAP عند نقلها إلى الأجهزة. أما المثال الثاني فيتناول كيفية استخدام قطع البوابات لتقليل عرض الدائرة (عدد الكيوبتات) عن طريق تقسيم الدائرة إلى عدة دوائر بعدد أقل من الكيوبتات.

سيستخدم كلا المثالين نموذج efficient_su2 ويُعيد بناء نفس المعيار القابل للقياس.

قطع البوابات لتقليل عمق الدائرة

يقلل سير العمل التالي عمق الدائرة عن طريق قطع البوابات البعيدة، متجنبًا سلسلة كبيرة من بوابات SWAP التي سيتم إدخالها خلاف ذلك.

ابدأ بنموذج efficient_su2 مع تشابك "دائري" لإدخال بوابات بعيدة.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-cutting qiskit-aer qiskit-ibm-runtime
import numpy as np
from qiskit.circuit.library import efficient_su2
from qiskit.quantum_info import SparsePauliOp
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_ibm_runtime.fake_provider import FakeManilaV2
from qiskit_ibm_runtime import SamplerV2, Batch
from qiskit_aer.primitives import EstimatorV2
from qiskit_addon_cutting import (
cut_gates,
partition_problem,
generate_cutting_experiments,
reconstruct_expectation_values,
)

circuit = efficient_su2(num_qubits=4, entanglement="circular")
circuit.assign_parameters([0.4] * len(circuit.parameters), inplace=True)

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")
circuit.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

تُدخل كل بوابة من بوابات CNOT بين الكيوبتين q0q_0 وq3q_3 بوابتَي SWAP بعد النقل (بافتراض أن الكيوبتات متصلة في خط مستقيم). لتجنب هذه الزيادة في العمق، يمكنك استبدال هذه البوابات البعيدة بكائنات TwoQubitQPDGate باستخدام طريقة cut_gates(). تُعيد هذه الدالة أيضًا قائمة من نسخ QPDBasis — واحدة لكل تحليل.

# Find the indices of the distant gates
cut_indices = [
i
for i, instruction in enumerate(circuit.data)
if {circuit.find_bit(q)[0] for q in instruction.qubits} == {0, 3}
]

# Decompose distant CNOTs into TwoQubitQPDGate instances
qpd_circuit, bases = cut_gates(circuit, cut_indices)

qpd_circuit.draw("mpl", scale=0.8)

Output of the previous code cell

بعد إضافة تعليمات بوابة القطع، ستمتلك التجارب الفرعية عمقًا أصغر بعد النقل مقارنةً بالدائرة الأصلية. يُولّد مقطع الكود أدناه التجارب الفرعية باستخدام generate_cutting_experiments، الذي يستقبل الدائرة والمعيار القابل للقياس لإعادة بنائه.

ملاحظة حول عدد العينات

تحدد وسيطة num_samples عدد العينات التي يتم سحبها من توزيع الاحتمالية شبه الكمومية، وتحدد دقة المعاملات المستخدمة لإعادة البناء. سيضمن تمرير اللانهاية (np.inf) حساب جميع المعاملات بدقة تامة. اقرأ وثائق API حول توليد الأوزان وتوليد تجارب القطع للمزيد من المعلومات.

بعد توليد التجارب الفرعية، يمكنك نقلها واستخدام primitive المُعاين Sampler لأخذ عينات من التوزيع وإعادة بناء قيم التوقع المُقدَّرة. يُولّد كتلة الكود التالية التجارب الفرعية وينقلها وينفذها، ثم يُعيد بناء النتائج ويقارنها بقيمة التوقع الدقيقة.

# Generate the subexperiments and sampling coefficients
subexperiments, coefficients = generate_cutting_experiments(
circuits=qpd_circuit, observables=observable.paulis, num_samples=np.inf
)

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = pass_manager.run(subexperiments)

# Set up the Qiskit Runtime Sampler primitive, submit the subexperiments, and retrieve the results
sampler = SamplerV2(backend)
job = sampler.run(isa_subexperiments, shots=4096 * 3)
results = job.result()

# Reconstruct the results
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
observable.paulis,
)

# Apply the coefficients of the original observable
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(circuit, observable, [0.4] * len(circuit.parameters))])
.result()[0]
.data.evs
)
print(
f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}"
)
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(
f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}"
)
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.49812826
Exact expectation value: 0.50497603
Error in estimation: -0.00684778
Relative error in estimation: -0.0135606
ملاحظة حول معاملات المعيار القابل للقياس

لإعادة بناء قيمة التوقع بدقة، يجب تطبيق معاملات المعيار القابل للقياس الأصلي (التي تختلف عن المعاملات في ناتج generate_cutting_experiments()) على ناتج إعادة البناء، إذ تُفقد هذه المعلومات عند توليد تجارب القطع أو عند توسيع المعيار القابل للقياس.

عادةً يمكن تطبيق هذه المعاملات من خلال numpy.dot() كما هو موضح أعلاه.

قطع البوابات لتقليل عرض الدائرة

يوضح هذا القسم استخدام قطع البوابات لتقليل عرض الدائرة. ابدأ بنفس efficient_su2 لكن استخدم تشابكًا "خطيًا".

qc = efficient_su2(4, entanglement="linear", reps=2)
qc.assign_parameters([0.4] * len(qc.parameters), inplace=True)

observable = SparsePauliOp(["ZZII", "IZZI", "-IIZZ", "XIXI", "ZIZZ", "IXIX"])
print(f"Observable: {observable}")

qc.draw("mpl", scale=0.8)
Observable: SparsePauliOp(['ZZII', 'IZZI', 'IIZZ', 'XIXI', 'ZIZZ', 'IXIX'],
coeffs=[ 1.+0.j, 1.+0.j, -1.+0.j, 1.+0.j, 1.+0.j, 1.+0.j])

Output of the previous code cell

بعد ذلك، ولّد الدوائر الفرعية والمعايير الفرعية القابلة للقياس التي ستنفذها باستخدام دالة partition_problem(). تأخذ هذه الدالة الدائرة والمعيار القابل للقياس ومخطط التقسيم الاختياري، وتُعيد الدوائر المقطوعة والمعايير في صورة قاموس.

يُعرَّف التقسيم بسلسلة تسمية من النموذج "AABB" حيث تتوافق كل تسمية في هذه السلسلة مع الكيوبت في نفس الفهرس في وسيطة circuit. تُجمَّع الكيوبتات التي تشترك في نفس تسمية القسم معًا، وستُقطع أي بوابات غير محلية تمتد عبر أكثر من قسم واحد.

ملاحظة

وسيطة observables في partition_problem من نوع PauliList. يتم تجاهل معاملات حدود المعيار القابل للقياس وأطواره أثناء تحليل المسألة وتنفيذ التجارب الفرعية. يمكن إعادة تطبيقها أثناء إعادة بناء قيمة التوقع.

partitioned_problem = partition_problem(
circuit=qc, partition_labels="AABB", observables=observable.paulis
)
subcircuits = partitioned_problem.subcircuits
subobservables = partitioned_problem.subobservables
bases = partitioned_problem.bases

print(f"Sampling overhead: {np.prod([basis.overhead for basis in bases])}")
print(f"Subobservables: {subobservables}")
subcircuits["A"].draw("mpl", scale=0.8)
Sampling overhead: 81.0
Subobservables: {'A': PauliList(['II', 'ZI', 'ZZ', 'XI', 'ZZ', 'IX']), 'B': PauliList(['ZZ', 'IZ', 'II', 'XI', 'ZI', 'IX'])}

Output of the previous code cell

subcircuits["B"].draw("mpl", scale=0.8)

Output of the previous code cell

الخطوة التالية هي استخدام الدوائر الفرعية والمعايير الفرعية القابلة للقياس لتوليد التجارب الفرعية التي ستُنفَّذ على QPU باستخدام طريقة generate_cutting_experiments.

لتقدير قيمة التوقع للدائرة الكاملة، يتم توليد تجارب فرعية كثيرة من التوزيع المشترك شبه الكمومي للبوابات المحللة ثم تنفيذها على QPU واحد أو أكثر. يتحكم في عدد العينات التي يتم سحبها من هذا التوزيع وسيطةُ num_samples.

تُولّد كتلة الكود التالية التجارب الفرعية وتنفذها باستخدام primitive المُعاين Sampler على محاكٍ محلي. (لتشغيلها على QPU، غيّر backend إلى مورد QPU الذي اخترته.)

subexperiments, coefficients = generate_cutting_experiments(
circuits=subcircuits, observables=subobservables, num_samples=np.inf
)

# Set a backend to use and transpile the subexperiments
backend = FakeManilaV2()
pass_manager = generate_preset_pass_manager(
optimization_level=1, backend=backend
)
isa_subexperiments = {
label: pass_manager.run(partition_subexpts)
for label, partition_subexpts in subexperiments.items()
}

# Submit each partition's subexperiments to the Qiskit Runtime Sampler
# primitive, in a single batch so that the jobs will run back-to-back.
with Batch(backend=backend) as batch:
sampler = SamplerV2(mode=batch)
jobs = {
label: sampler.run(subsystem_subexpts, shots=4096 * 3)
for label, subsystem_subexpts in isa_subexperiments.items()
}

# Retrieve results
results = {label: job.result() for label, job in jobs.items()}

أخيرًا، يتم إعادة بناء قيمة التوقع للدائرة الكاملة باستخدام طريقة reconstruct_expectation_values.

تُعيد كتلة الكود أدناه بناء النتائج وتقارنها بقيمة التوقع الدقيقة.

# Get expectation values for each observable term
reconstructed_expval_terms = reconstruct_expectation_values(
results,
coefficients,
subobservables,
)

# Reconstruct final expectation value
reconstructed_expval = np.dot(reconstructed_expval_terms, observable.coeffs)

estimator = EstimatorV2()
exact_expval = (
estimator.run([(qc, observable, [0.4] * len(qc.parameters))])
.result()[0]
.data.evs
)
print(
f"Reconstructed expectation value: {np.real(np.round(reconstructed_expval, 8))}"
)
print(f"Exact expectation value: {np.round(exact_expval, 8)}")
print(
f"Error in estimation: {np.real(np.round(reconstructed_expval-exact_expval, 8))}"
)
print(
f"Relative error in estimation: {np.real(np.round((reconstructed_expval-exact_expval) / exact_expval, 8))}"
)
Reconstructed expectation value: 0.53571896
Exact expectation value: 0.56254612
Error in estimation: -0.02682716
Relative error in estimation: -0.04768882

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