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

البدء مع صيغ المنتج المتعدد (MPF)

البدء مع صيغ المنتج المتعدد (MPFs)

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

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

qiskit[all]~=2.3.0
qiskit-addon-utils~=0.3.0
qiskit-addon-mpf~=0.3.0
scipy~=1.16.3

يوضح هذا الدليل كيفية استخدام حزمة qiskit-addon-mpf، مع التطور الزمني لنموذج إيزينغ كمثال. باستخدام هذه الحزمة، يمكنك بناء صيغة منتج متعدد (MPF) تحقق خطأ Trotter أقل في قياسات المؤثِّرات (observables). تتيح لك الأدوات المتوفرة تحديد أوزان MPF المختارة، والتي يمكن استخدامها لاحقًا لإعادة دمج قيم التوقع المُقدَّرة من عدة دوائر تطور زمني، كل منها بعدد مختلف من خطوات Trotter.

ابدأ بالنظر في هاميلتوني نموذج إيزينغ المؤلف من 10 مواقع:

HIsing=i=19Ji,(i+1)ZiZi+1+i=110hiXiH_{\text{Ising}} = \sum_{i=1}^9 J_{i,(i+1)}Z_iZ_{i+1} + \sum_{i=1}^{10} h_i X_i

حيث Ji,(i+1)J_{i,(i+1)} هي قوة الاقتران وhih_i هي شدة المجال المغناطيسي الخارجي. لإعداد المسألة، سيكون المؤثِّر المراد قياسه هو إجمالي المغنطة للنظام

M=i=110Zi.\langle M \rangle = \sum_{i=1}^{10} \langle Z_i \rangle.

يُعِدّ مقطع الكود أدناه هاميلتوني سلسلة إيزينغ باستخدام حزمة qiskit-addon-utils، ويُعرِّف المؤثِّر الذي سيُقاس.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-mpf qiskit-addon-utils scipy
from qiskit.transpiler import CouplingMap
from qiskit.synthesis import SuzukiTrotter
from qiskit.quantum_info import SparsePauliOp
from qiskit.primitives import StatevectorEstimator
from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import generate_preset_pass_manager
from qiskit_addon_utils.problem_generators import (
generate_xyz_hamiltonian,
generate_time_evolution_circuit,
)
from qiskit_addon_mpf.costs import (
setup_exact_problem,
setup_sum_of_squares_problem,
)
from qiskit_addon_mpf.static import setup_static_lse

from scipy.linalg import expm
import numpy as np

# Generate some coupling map to use for this example
coupling_map = CouplingMap.from_line(10, bidirectional=False)

# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)
print(f"Hamiltonian:\n {hamiltonian}\n")

L = coupling_map.size()
observable = SparsePauliOp.from_sparse_list(
[("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L
)
print(f"Observable:\n {observable}")
Hamiltonian:
SparsePauliOp(['IIIIIIIZZI', 'IIIIIZZIII', 'IIIZZIIIII', 'IZZIIIIIII', 'IIIIIIIIZZ', 'IIIIIIZZII', 'IIIIZZIIII', 'IIZZIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIXI', 'IIIIIIIXII', 'IIIIIIXIII', 'IIIIIXIIII', 'IIIIXIIIII', 'IIIXIIIIII', 'IIXIIIIIII', 'IXIIIIIIII', 'XIIIIIIIII'],
coeffs=[1. +0.j, 1. +0.j, 1. +0.j, 1. +0.j, 1. +0.j, 1. +0.j, 1. +0.j, 1. +0.j,
1. +0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j, 0.4+0.j,
0.4+0.j, 0.4+0.j, 0.4+0.j])

Observable:
SparsePauliOp(['IIIIIIIIIZ', 'IIIIIIIIZI', 'IIIIIIIZII', 'IIIIIIZIII', 'IIIIIZIIII', 'IIIIZIIIII', 'IIIZIIIIII', 'IIZIIIIIII', 'IZIIIIIIII', 'ZIIIIIIIII'],
coeffs=[0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j, 0.05+0.j,
0.05+0.j, 0.05+0.j, 0.05+0.j])

بعد ذلك، تُعِدّ MPF. أول اختيار يجب اتخاذه هو ما إذا كانت المعاملات ستكون ثابتة (مستقلة عن الزمن) أم ديناميكية؛ يستخدم هذا الدليل MPF ثابتة. الاختيار التالي هو مجموعة قيم kjk_j. يحدد ذلك عدد الحدود في MPF، وكذلك عدد خطوات Trotter التي يستخدمها كل حد لمحاكاة التطور الزمني. اختيار قيم kjk_j أمر استدلالي (heuristic)، لذا تحتاج إلى الحصول على مجموعة "جيدة" من قيم kjk_j الخاصة بك. اقرأ الإرشادات حول إيجاد مجموعة جيدة من القيم في نهاية صفحة البدء.

بعد ذلك، حين تُحدَّد قيم kjk_j، يمكنك إعداد نظام المعادلات Ax=bAx=b للحل. تتحدد المصفوفة AA أيضًا بصيغة الضرب المستخدمة. الاختيارات هنا هي الرتبة (order)، التي تُضبط على 22 في هذا المثال، وما إذا كانت صيغة الضرب يجب أن تكون متناظرة (symmetric)، والتي تُضبط على True في هذا المثال. يختار مقطع الكود أدناه إجمالي وقت تطور النظام، وقيم kjk_j المُستخدَمة، ومجموعة المعادلات للحل باستخدام طريقة qiskit_addon_mpf.static.setup_static_lse.

time = 8.0
trotter_steps = (8, 12, 19)

lse = setup_static_lse(trotter_steps, order=2, symmetric=True)
print(lse)
LSE(A=array([[1.00000000e+00, 1.00000000e+00, 1.00000000e+00],
[1.56250000e-02, 6.94444444e-03, 2.77008310e-03],
[2.44140625e-04, 4.82253086e-05, 7.67336039e-06]]), b=array([1., 0., 0.]))

بعد إنشاء نظام المعادلات الخطية، يمكن حله إما بشكل مضبوط أو من خلال نموذج تقريبي باستخدام مجموع المربعات (أو معيار فروبينيوس للمعاملات الديناميكية؛ انظر مرجع API لمزيد من المعلومات). ينشأ الاختيار لاستخدام نموذج تقريبي عادةً عندما يُعدّ معيار المعاملات لمجموعة قيم kjk_j المختارة مرتفعًا جدًا ولا يمكن اختيار مجموعة مختلفة من قيم kjk_j. يوضح هذا الدليل كلا الطريقتين لمقارنة النتائج.

model_exact, coeffs_exact = setup_exact_problem(lse)
model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=3.0
)
model_exact.solve()
model_approx.solve()
print(f"Exact solution: {coeffs_exact.value}")
print(f"Approximate solution: {coeffs_approx.value}")
Exact solution: [ 0.17239057 -1.19447005  2.02207947]
Approximate solution: [-0.40454257 0.57553173 0.8290123 ]
ملاحظة

يمتلك الكائن LSE أيضًا طريقة LSE.solve()، التي تحل نظام المعادلات بشكل مضبوط. سبب استخدام setup_exact_problem() في هذا الدليل هو إظهار الواجهة التي توفرها الطرق التقريبية الأخرى.

إعداد وتنفيذ دوائر Trotter

الآن بعد الحصول على المعاملات xjx_j، الخطوة الأخيرة هي توليد دوائر التطور الزمني للرتبة ومجموعة الخطوات kjk_j المختارة لـ MPF. يمكن لحزمة qiskit-addon-utils تسريع هذه العملية.

circuits = []
for k in trotter_steps:
circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(order=2, reps=k),
time=time,
)
circuits.append(circ)
circuits[0].draw("mpl", fold=-1)

Output of the previous code cell

circuits[1].draw("mpl", fold=-1)

Output of the previous code cell

circuits[2].draw("mpl", fold=-1)

Output of the previous code cell

بعد بناء هذه الدوائر، يمكنك ترجمتها (transpile) وتنفيذها باستخدام QPU. في هذا المثال، سنستخدم أحد المحاكيات الخالية من الضجيج لتوضيح كيفية تقليل خطأ Trotter.

backend = GenericBackendV2(num_qubits=10)
transpiler = generate_preset_pass_manager(
optimization_level=2, backend=backend
)

transpiled_circuits = [transpiler.run(circ) for circ in circuits]

estimator = StatevectorEstimator()
job = estimator.run([(circ, observable) for circ in transpiled_circuits])
result = job.result()

mpf_evs = [res.data.evs for res in result]
print(mpf_evs)
[array(0.23799162), array(0.35754312), array(0.38649906)]

إعادة بناء النتائج

الآن بعد تنفيذ الدوائر، إعادة بناء النتائج أمر بسيط نسبيًا. كما ذُكر في صفحة نظرة عامة على MPF، يُعاد بناء مؤثِّرنا من خلال المجموع الموزون

M=jxjMj.\langle M \rangle = \sum_j x_j \langle M_j \rangle.

حيث xjx_j هي المعاملات التي وجدناها وMj\langle M_j \rangle هي تقدير المؤثِّر iZi\sum_i \langle Z_i \rangle لكل دائرة. يمكننا بعد ذلك مقارنة النتائج التي حصلنا عليها مع القيمة المضبوطة باستخدام حزمة scipy.linalg.

exp_H = expm(-1j * time * hamiltonian.to_matrix())
initial_state = np.zeros(exp_H.shape[0])
initial_state[0] = 1.0

time_evolved_state = exp_H @ initial_state
exact_obs = (
time_evolved_state.conj() @ observable.to_matrix() @ time_evolved_state
)

# Print out the different observable measurements
print(f"Exact value: {exact_obs.real}")
print(f"PF with 19 steps: {mpf_evs[-1]}")
print(f"MPF using exact solution: {mpf_evs @ coeffs_exact.value}")
print(f"MPF using approximate solution: {mpf_evs @ coeffs_approx.value}")
Exact value: 0.4006024248789992
PF with 19 steps: 0.3864990619977402
MPF using exact solution: 0.3954847855979902
MPF using approximate solution: 0.4299121425348959

يمكنك هنا أن ترى أن MPF قلّلت خطأ Trotter مقارنةً بما تم الحصول عليه باستخدام PF واحدة فقط مع kj=19k_j=19. غير أن النموذج التقريبي أعطى قيمة توقع أسوأ من النموذج المضبوط. يوضح هذا أهمية استخدام معايير تقارب دقيقة على النموذج التقريبي وإيجاد مجموعة "جيدة" من قيم kjk_j.