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

تقليل خطأ تروتر في ديناميكيات هاملتوني باستخدام صيغ المنتج المتعدد

في هذا الدفتر، ستتعلم كيفية استخدام صيغة المنتج المتعدد (MPF) لتحقيق خطأ تروتر أقل في العنصر القابل للرصد مقارنةً بالخطأ الناجم عن أعمق دائرة تروتر التي سننفذها فعلياً. ستقوم بذلك من خلال العمل على خطوات نمط Qiskit:

  • الخطوة 1: التعيين إلى المسألة الكمومية
    • تهيئة هاملتوني المسألة
    • استخدام MPF لتوليد دوائر التطور الزمني المقطّرة (Trotterized)
  • الخطوة 2: تحسين المسألة
  • الخطوة 3: تنفيذ التجارب
  • الخطوة 4: إعادة بناء النتائج
    • حساب قيمة التوقع للـ MPF

الخطوة 1: التعيين إلى المسألة الكمومية

1أ: إعداد الهاملتوني

نستخدم نموذج إيزنغ على خط من 10 مواقع:

H^Ising=i=19Ji,(i+1)ZiZ(i+1)+i=110hiXi,\hat{\mathcal{H}}_{\text{Ising}} = \sum_{i=1}^{9} J_{i,(i+1)} Z_i Z_{(i+1)} + \sum_{i=1}^{10} h_i X_i \, ,

حيث JJ هو قوة الاقتران بين موقعين وhh هو المجال المغناطيسي الخارجي. تُوفّر حزمة qiskit_addon_utils بعض الوظائف القابلة لإعادة الاستخدام لأغراض متنوعة.

يُتيح وحدتها qiskit_addon_utils.problem_generators دوال لتوليد هاملتونيات شبيهة بـ Heisenberg على رسم بياني للتوصيل محدد. يمكن أن يكون هذا الرسم البياني إما rustworkx.PyGraph أو CouplingMap مما يسهّل استخدامه في سير العمل المحوري حول Qiskit.

فيما يلي، ننشئ خطاً بسيطاً من 10 Qubit باستخدام الأسلوب CouplingMap.from_line.

# Added by doQumentation — required packages for this notebook
!pip install -q numpy qiskit qiskit-addon-mpf qiskit-addon-utils rustworkx scipy
from qiskit.transpiler import CouplingMap

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

graphviz_draw(coupling_map.graph, method="circo")

Code output

بعد ذلك، نولّد SparsePauliOp على التوصيل المقدَّم مع الثوابت المطلوبة.

from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian

# 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(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])

العنصر القابل للرصد الذي سنقيسه هو إجمالي المغنطة، ويمكننا بناؤه ببساطة كما هو موضح أدناه:

from qiskit.quantum_info import SparsePauliOp

L = coupling_map.size()
observable = SparsePauliOp.from_sparse_list([("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L)
print(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])

1ب: صيغ المنتج المتعدد

تُقلّل صيغ MPF من خطأ تروتر في ديناميكيات هاملتوني من خلال تركيبة موزونة لعدة عمليات تنفيذ للدوائر.

لتوضيح ذلك بشكل أكثر تحديداً، نعرّف MPF على النحو التالي:

μ(t)=jxjρjkj(tkj)+some remaining Trotter error,\mu(t) = \sum_{j} x_j \rho^{k_j}_{j}\left(\frac{t}{k_j}\right) + \text{some remaining Trotter error} \, ,

حيث xjx_j هي معاملات الترجيح، وρjkj\rho^{k_j}_j هي مصفوفة الكثافة المقابلة للحالة النقية التي يتم الحصول عليها بتطور الحالة الأولية بصيغة المنتج SkjS^{k_j} التي تتضمن kjk_j خطوة تروتر، وjj يُفهرس عدد صيغ المنتج التي تُشكّل MPF.

المفتاح هنا هو أن خطأ تروتر المتبقي أصغر من خطأ تروتر الذي يمكن الحصول عليه باستخدام أكبر قيمة kjk_j ببساطة!

يمكنك النظر إلى فائدة MPF من منظورين:

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

مقدمة إلى صيغ MPF الساكنة

صيغ MPF الساكنة هي تلك التي لا تعتمد فيها قيم xjx_j على زمن التطور tt.

يُعادل تحديد معاملات MPF الساكنة لمجموعة معطاة من قيم kjk_j حلّ نظام معادلات خطي: Ax=bAx=b، حيث xx هي معاملاتنا المطلوبة، وAA مصفوفة تعتمد على kjk_j ونوع صيغة المنتج (PF) التي نستخدمها (SS)، وbb متجه قيود. للإيجاز، لن نتعمق في مزيد من التفاصيل هنا وسنُحيلك بدلاً من ذلك إلى توثيق LSE.

يمكننا إيجاد حل لـ xx تحليلياً بوصفه x=A1bx = A^{-1}b، انظر مثلاً Carrera Vazquez et al., 2023 أو Zhuk et al., 2023. غير أن هذا الحل الدقيق قد يكون "سيء التهيئة" مما يُفضي إلى معايير L1 كبيرة جداً لمعاملاتنا xx، وهو ما قد يُؤدي إلى أداء ضعيف لـ MPF. بدلاً من ذلك، يمكن الحصول على حل تقريبي يُصغّر معيار L1 للـ xx في محاولة لتحسين سلوك MPF.

فيما يلي، ستتعلم كيفية القيام بكل ذلك.

اختيار kjk_j

اختيار kjk_j متروك للمستخدم النهائي. يمكن من حيث المبدأ اختيار أي قيم، لكن بعض قيم kjk_j ستُؤدي إلى تضخيم أكبر للضوضاء في الأجهزة الفعلية مقارنةً بخيارات أخرى. لذا، من المهم السعي للعثور على قيم "جيدة" لـ kjk_j.

هنا، سنختار ببساطة بعض القيم الثابتة لـ kjk_j. القيمة الأصغر مستوحاة من زمن التطور المستهدف t=8.0t=8.0 الذي يُخبرنا عادةً بضرورة تحقيق t/kmin<1t/k_{\text{min}} \lt 1، لكننا نعلم تجريبياً أن جعلها مساويةً لـ 1 يعمل في الغالب أيضاً. إذا أردت معرفة المزيد عن هذا وكيفية اختيار قيم kjk_j الأخرى، ارجع إلى الدليل المقابل: كيفية اختيار خطوات تروتر لـ MPF.

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

إعداد LSE

الآن بعد أن اخترنا قيم kjk_j، يجب أولاً بناء LSE أي Ax=bAx=b كما هو موضح أعلاه. تعتمد المصفوفة AA ليس فقط على kjk_j بل أيضاً على اختيارنا لصيغة المنتج (PF) -- ولا سيما رتبتها. علاوةً على ذلك، يمكن أن يُؤخذ بعين الاعتبار ما إذا كانت صيغة المنتج متماثلة أم لا (انظر Carrera Vazquez et al., 2023)، وذلك بضبط symmetric=True. غير أن هذا ليس شرطاً كما أثبت Zhuk et al., 2023.

هنا، سنستخدم صيغة Suzuki-Trotter من الرتبة الثانية مما يُعطينا order=2 وسنضبط symmetric=True.

from qiskit_addon_mpf.static import setup_static_lse

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.]))

الحل التحليلي لـ xx

كما ذُكر سابقاً، يمكننا إيجاد xx تحليلياً:

import numpy as np

coeffs_analytical = lse.solve()
print(coeffs_analytical)
[ 0.17239057 -1.19447005  2.02207947]

تحسين xx باستخدام نموذج دقيق

بديلاً عن حساب x=A1bx=A^{-1}b، يمكنك أيضاً استخدام setup_exact_problem لبناء مثيل cvxpy.Problem الذي يستخدم LSE كقيود وسيُعطي حله الأمثل قيمة xx.

في القسم التالي، سيتضح السبب في وجود هذه الواجهة.

from qiskit_addon_mpf.costs import setup_exact_problem

model_exact, coeffs_exact = setup_exact_problem(lse)
model_exact.solve()
print(coeffs_exact.value)
[ 0.17239057 -1.19447005  2.02207947]

كمؤشر على ما إذا كانت MPF المبنية بهذه المعاملات ستُعطي نتائج جيدة، يمكننا استخدام معيار L1 (انظر أيضاً Carrera Vazquez et al., 2023).

print(np.linalg.norm(coeffs_exact.value, ord=1))
3.3889400921655914

تحسين xx باستخدام نموذج تقريبي

قد يحدث أن يُعتبر معيار L1 لمجموعة قيم kjk_j المختارة مرتفعاً جداً. في هذه الحالة، إذا تعذّر عليك اختيار مجموعة مختلفة من قيم kjk_j، يمكنك استخدام حل تقريبي لـ LSE بدلاً من الحل الدقيق.

للقيام بذلك، استخدم ببساطة setup_sum_of_squares_problem لبناء مثيل مختلف من cvxpy.Problem الذي يُقيّد معيار L1 بحد أقصى محدد مع تصغير الفرق بين AxAx وbb.

from qiskit_addon_mpf.costs import setup_sum_of_squares_problem

model_approx, coeffs_approx = setup_sum_of_squares_problem(lse, max_l1_norm=3.0)
model_approx.solve()
print(coeffs_approx.value)
print(np.linalg.norm(coeffs_approx.value, ord=1))
[-0.40454257  0.57553173  0.8290123 ]
1.8090865903790838

لاحظ أنك تتمتع بحرية كاملة في كيفية حل مسألة التحسين هذه، بمعنى أنك تستطيع تغيير محلّ التحسين وعتبات التقارب الخاصة به وما إلى ذلك. اطلع على الدليل المقابل حول كيفية استخدام النموذج التقريبي.

1ج: إعداد دوائر تروتر

عند هذه النقطة، وجدنا معاملات التوسعة xx، وكل ما تبقّى هو توليد الدوائر الكمومية المقطّرة (Trotterized). مرةً أخرى، تتدخل وحدة qiskit_addon_utils.problem_generators للقيام بذلك تحديداً:

from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit

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)

Quantum circuit diagram

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

Quantum circuit diagram

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

Quantum circuit diagram

الخطوة 2: تحسين المسألة

عادةً، هذه هي الخطوة في النمط التي تُحسّن فيها دوائرك للتنفيذ على الأجهزة الفعلية. هنا، بما أننا نستخدم محاكياً خالياً من الضوضاء فقط، نكتفي بتحويل دائرتنا عبر Transpiler لـ GenericBackendV2.

from qiskit.providers.fake_provider import GenericBackendV2
from qiskit.transpiler import generate_preset_pass_manager

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

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

الخطوة 3: تنفيذ التجارب الكمومية

كما هو موضح في البداية، سنتخطى خطوة التحسين 2 لأننا سنحسب ببساطة قيم توقع العنصر القابل للرصد المستهدف باستخدام محاكٍ خالٍ من الضوضاء، وهو StatevectorEstimator.

from qiskit.primitives import StatevectorEstimator

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

الخطوة 4: إعادة بناء النتائج

أولاً، نستخرج قيم التوقع الفردية المحصّلة لكل دائرة من دوائر تروتر:

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

بعد ذلك، نُعيد تركيبها ببساطة مع معاملات MPF للحصول على قيم التوقع الإجمالية للـ MPF. فيما يلي، نقوم بذلك لكل طريقة من الطرق المختلفة التي حسبنا بها xx.

print("Analytical    solution:", evs @ coeffs_analytical)
print("Exact model solution:", evs @ coeffs_exact.value)
print("Approx. model solution:", evs @ coeffs_approx.value)
Analytical    solution: 0.3954847855980006
Exact model solution: 0.39548478559800204
Approx. model solution: 0.42991214253489807

أخيراً، يمكننا لهذه المسألة الصغيرة حساب القيمة المرجعية الدقيقة باستخدام scipy.linalg.expm على النحو التالي:

from scipy.linalg import expm

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(exact_obs.real)
0.40060242487899755

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