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

الصيغ متعددة الجداء للحد من خطأ تروتر

الاستخدام التقديري: أربع دقائق على معالج Heron r2 (ملاحظة: هذا تقدير فحسب. قد يختلف وقت التشغيل الفعلي.)

مخرجات التعلّم​

  • كيف تقلل الصيغ متعددة الجداء (MPFs) خطأ Trotter في محاكاة الهاملتوني من خلال دمج قيم التوقع من عدة دارات ضحلة

  • متى تكون MPFs مفيدة مقارنة بصيغ الجداء القياسية ومتى لا تكون الأداة المناسبة

  • كيفية حساب معاملات MPF الساكنة والديناميكية باستخدام حزمة qiskit_addon_mpf

  • كيفية تنفيذ سير عمل MPF من البداية إلى النهاية على جهاز IBM Quantum®، بما في ذلك التصريف وتخفيف الأخطاء والمعالجة اللاحقة

المتطلبات الأساسية​

الخلفية​

ما هي الصيغ متعددة الجداء؟​

عند محاكاة الأنظمة الكمومية على حاسوب كمومي، تكون المهمة الأساسية هي تقريب مؤثر التطور الزمني e−iHte^{-iHt} لهاملتوني HH. يستخدم النهج القياسي صيغ الجداء (PFs)، المعروفة أيضًا باسم تحليلات Trotter-Suzuki. تحلل هذه الصيغ H=∑a=1dFaH = \sum_{a=1}^d F_a إلى حدود يكون تنفيذ مؤثراتها الأحادية الفردية e−iFate^{-iF_a t} فيها فعالًا، ثم تقرّب التطور الكامل على شكل جداء مرتب لهذه المؤثرات الأحادية الأبسط.

صيغة الجداء من الرتبة الأولى (Lie-Trotter) هي:

S1(t):=∏a=1de−iFat,S_1(t) := \prod_{a=1}^d e^{-i F_a t},

التي تنطوي على خطأ تربيعي: S1(t)=e−iHt+O(t2)S_1(t) = e^{-iHt} + \mathcal{O}(t^2). تتقارب الصيغ المتماثلة من رتبة أعلى S2χ(t)S_{2\chi}(t)، حيث يشير χ\chi إلى رتبة صيغة الجداء المتماثلة (انظر المرجع [1])، بشكل أسرع على النحو e−iHt+O(t2χ+1)e^{-iHt} + \mathcal{O}(t^{2\chi+1})، لكن على حساب دارات أعمق لكل خطوة.

لتقليل الخطأ عند رتبة ثابتة χ\chi، يُقسَّم عادةً زمن التطور الكلي tt إلى kk من خطوات Trotter الأصغر. تقرّب كل خطوة e−iHt/ke^{-iHt/k} بصيغة جداء، وتُسلسَل الخطوات معًا:

e−iHt≈[S2χ(t/k)]k.e^{-iHt} \approx \left[S_{2\chi}(t/k)\right]^k.

بالنسبة لصيغة متماثلة من الرتبة 2χ2\chi، يتناسب خطأ Trotter المتبقي مع O ⁣(t2χ+1/k2χ)\mathcal{O}\!\left(t^{2\chi+1} / k^{2\chi}\right). لذا فإن زيادة kk تكبح خطأ Trotter بسرعة — لكنها تعمّق الدارة أيضًا بشكل خطي، وعلى الأجهزة الصاخبة يعني ذلك تراكمًا أكبر لضوضاء البوابات. هذا التوتر بين خطأ Trotter (الذي يفضّل kk أكبر) وضوضاء الجهاز (التي تفضّل kk أصغر) هو بالضبط ما صُمّمت الصيغ متعددة الجداء لحله. لاحظ أن الصيغ متعددة الجداء تتعلق بدمج نتائج من اختيارات مختلفة لـ kk عند رتبة ثابتة χ\chi — فهي لا تغيّر رتبة صيغة الجداء الأساسية.

تبني الصيغ متعددة الجداء (MPFs) [1] مجموعة خطية مرجّحة من قيم التوقع المحصَّلة من عدة دارات Trotter أقل عمقًا، تستخدم كل منها عددًا مختلفًا من خطوات Trotter k1,k2,…,krk_1, k_2, \ldots, k_r (مجموعة من rr عدد من الخطوات):

⟨A⟩MPF(t)=∑j=1rxj ⟨A⟩kj(t),\langle A \rangle_{\text{MPF}}(t) = \sum_{j=1}^r x_j \, \langle A \rangle_{k_j}(t),

حيث ⟨A⟩kj(t)\langle A \rangle_{k_j}(t) هي قيمة التوقع لمرصود AA عند الزمن tt المقدَّرة من دارة Trotter بعدد kjk_j من الخطوات، وتُختار المعاملات {xj}j=1r\{x_j\}_{j=1}^r بحيث تُلغى حدود خطأ Trotter الرئيسية في المجموعة. سنعود إلى هذا التعبير في الخطوة 4، حيث نقيّمه صراحةً لدمج نتائج Trotter الخاصة بنا. النقطة العملية الأساسية هي أن أعمق دارة في MPF تحتاج فقط إلى kmax⁡k_{\max} من الخطوات، وهو أصغر بكثير من قيمة kk الواحدة التي ستكون مطلوبة للوصول مباشرة إلى نفس خطأ Trotter الفعّال. تجعل الدارات الأقل عمقًا نهج MPF أكثر ملاءمة للأجهزة الصاخبة.

كيف تُحدَّد المعاملات؟​

توجد فئتان من معاملات MPF:

المعاملات الساكنة مستقلة عن الهاملتوني، والحالة الابتدائية، وزمن التطور. تُوجد بحل نظام خطي Ax=bAx = b يفرض إلغاء حدود خطأ Trotter الرئيسية. بالنسبة لمجموعة من خطوات Trotter {kj}j=1r\{k_j\}_{j=1}^r المستخدمة مع صيغة جداء متماثلة من الرتبة 2χ2\chi، يؤدي توسيع خطأ Trotter بقوى معكوسة من kjk_j إلى معادلات قيد من الشكل:

∑j=1rxj=1,∑j=1rxjkjηn=0(n=0,…,r−2),\sum_{j=1}^r x_j = 1, \quad \sum_{j=1}^r \frac{x_j}{k_j^{\eta_n}} = 0 \quad (n = 0, \ldots, r-2),

حيث الأسس الصحيحة {ηn}\{\eta_n\} هي رتب حدود خطأ Trotter المتتالية لصيغة الجداء المختارة. بالنسبة لصيغة جداء متماثلة من الرتبة 2χ2\chi، يتناسب الخطأ الرئيسي في [S2χ(t/k)]k\left[S_{2\chi}(t/k)\right]^k مع 1/k2χ1/k^{2\chi}، مع تصحيحات لاحقة عند 1/k2χ+2,1/k2χ+4,…1/k^{2\chi+2}, 1/k^{2\chi+4}, \ldots — لذا فإن الأسس هي ηn=2χ+2n\eta_n = 2\chi + 2n. أما بالنسبة لصيغ الجداء غير المتماثلة، فتساهم القوى الفردية والزوجية معًا وتكون ηn=2χ+n\eta_n = 2\chi + n. انظر المرجع [1] للاطلاع على الاشتقاق الكامل. تضمن المعادلة الأولى في النظام أعلاه عدم التحيز (تعيد MPF إنتاج قيمة التوقع الدقيقة في نهاية kj→∞k_j \to \infty)، وتُلغي المعادلات المتبقية البالغ عددها r−1r-1 حدود خطأ Trotter الأولى r−1r-1 على التوالي. عندما تكون معيار L1L_1 الناتج ∥x∥1\|x\|_1 كبيرة جدًا (مما يضخّم ضوضاء أخذ العينات)، يمكنك بدلًا من ذلك حل مسألة تحسين تقريبية تحدّ من ∥x∥1\|x\|_1 مع تصغير ∥Ax−b∥\|Ax - b\|.

تعتمد المعاملات الديناميكية [2]، [3] أيضًا على الهاملتوني، والحالة الابتدائية، وزمن التطور tt. وهي تصغّر المسافة بمعيار فروبينيوس بين الحالة المتطورة زمنيًا الحقيقية وتقريب MPF:

∥ρ(t)−μD(t)∥F2=1+∑i,jMij(t) xi(t) xj(t)−2∑iLi(t) xi(t),\|\rho(t) - \mu^D(t)\|_F^2 = 1 + \sum_{i,j} M_{ij}(t)\, x_i(t)\, x_j(t) - 2\sum_i L_i(t)\, x_i(t),

حيث Mij(t)=Tr[ρki(t) ρkj(t)]M_{ij}(t) = \mathrm{Tr}[\rho_{k_i}(t)\,\rho_{k_j}(t)] هي مصفوفة غرام للتداخلات بين الحالات المتطورة بطريقة Trotter لعدد مختلف من الخطوات ki,kjk_i, k_j، وLi(t)=Tr[ρ(t) ρki(t)]L_i(t) = \mathrm{Tr}[\rho(t)\,\rho_{k_i}(t)] تقيس التداخل مع الحالة الدقيقة (التقريبية). في هذا الدرس التعليمي، تُحسب هذه الكميات بكفاءة باستخدام طرق الشبكات الموترية، وتحديدًا الخلفيات القائمة على TeNPy في qiskit_addon_mpf.

متى تستخدم MPFs​

تكون MPFs مفيدة أكثر عندما:

  • عمق الدارة هو العائق. إذا كانت ضوضاء العتاد تحدّ من العمق الذي يمكنك تشغيله، استخدم MPFs لتحقيق دقة Trotter فعّالة أعلى من دارات أقل عمقاً.

  • تحتاج قيم توقع دقيقة، وليس تحضير الحالة الكاملة. تعمل MPFs على مستوى قيم التوقع — فهي تجمع أرقاماً كلاسيكية، وليس حالات كمومية. لذا فهي مثالية لتقدير المرصودة عند استخدام أداة Estimator الأساسية.

  • تجمع عدداً معتدلاً من عدد خطوات تروتر. عادةً ما يكفي الجمع بين r=3r = 3–55 عدد خطوات مختلفة kjk_j لإلغاء عدة حدود من أخطاء تروتر الرئيسية مع إبقاء ∥x∥1\|x\|_1 قابلاً للتحكم.

متى قد لا تساعد MPFs​

  • أوقات تطور قصيرة جداً. عندما يكون tt صغيراً بما يكفي بحيث تكون صيغة تروتر منخفضة الرتبة دقيقة بالفعل، تصبح تكلفة تشغيل دارات متعددة غير ضرورية.

  • مهام تحضير الحالة. تنتج MPFs قيمة توقع مُصححة، وليس حالة كمومية مُصححة. إذا كنت بحاجة إلى الحالة الفعلية المتطورة زمنياً (على سبيل المثال، كمدخل لروتين فرعي كمومي آخر)، فلا تنطبق MPFs.

  • عدد خطوات تروتر التي تنتهك نظام التقارب. يوسّع الاشتقاق ذو المعاملات الثابتة كل [S2χ(t/kj)]kj\left[S_{2\chi}(t/k_j)\right]^{k_j} فردي كسلسلة في t/kjt/k_j؛ هذا التوسيع يتقارب جيداً فقط عندما يكون t/kmin⁡≲1t/k_{\min} \lesssim 1. إذا اختير kmin⁡k_{\min} صغيراً جداً بالنسبة لـ tt المعطى، تكون الدارة الأقل عمقاً بعيدة جداً عن النظام الاضطرابي، وتصبح حدود الخطأ ذات الرتبة الأعلى التي لا تُلغيها MPF كبيرة، وقد يتطلب الإلغاء معاملات كبيرة. معيار L1L_1 أي ∥x∥1\|x\|_1 هو المؤشر التشخيصي العملي: عندما يكون ∥x∥1≫1\|x\|_1 \gg 1، قد تفوق تكلفة العينات ∝∥x∥12\propto \|x\|_1^2 تخفيض خطأ تروتر. راجع دليل اختيار خطوات تروتر للتفاصيل.

ما يغطيه هذا البرنامج التعليمي​

يستعرض هذا البرنامج التعليمي سير عمل MPF كامل على مرحلتين. أولاً، يوضح مثال محاكاة صغير النطاق (سلسلة هايزنبرغ من 10 كيوبتات) كيفية إعداد المسألة، وحساب معاملات MPF الثابتة والديناميكية، ومقارنة قيم التوقع الناتجة مقابل القطرنة الدقيقة (exact diagonalization). ثم يُظهر مثال عتاد كبير النطاق (سلسلة XXZ من 50 كيوبت) كيفية التصريف، والتنفيذ على عتاد IBM Quantum مع تخفيف الأخطاء، ومعالجة النتائج لاحقاً باستخدام معاملات MPF. طوال الوقت، نستخدم حزمة qiskit_addon_mpf جنباً إلى جنب مع أدوات Qiskit القياسية.

المتطلبات​

قبل البدء في هذا البرنامج التعليمي، تأكد من تثبيت ما يلي:

  • Qiskit SDK الإصدار v2.0 أو أحدث مع دعم التصور

  • Qiskit Runtime الإصدار v0.22 أو أحدث (pip install qiskit-ibm-runtime)

  • محاكي Qiskit Aer (pip install qiskit-aer)

  • إضافة MPF لـ Qiskit مع خلفية TeNPy (pip install "qiskit-addon-mpf[tenpy]")

  • أدوات إضافات Qiskit (pip install qiskit-addon-utils)

  • SciPy (pip install scipy)

الإعداد​

نجمع أدناه جميع استيرادات الحزم المستخدمة طوال هذا البرنامج التعليمي في خلية واحدة. كما نُعرّف ممر تصريف (transpiler pass) باسم CollectAndCollapse يدمج تدويرات rxx وryy المتجاورة في بوابة واحدة XXPlusYYGate. يُطبَّق هذا الممر أثناء بناء الدارة في الخطوة 1 (للحفاظ على عدد البوابات منخفضاً) وبشكل غير مباشر عندما نستخرج البنية الطبقية لـ MPF الديناميكية في الخطوة 4 (تتوقع TeNPy بوابات ثنائية الكيوبت، وليس أزواجاً من التدويرات غير المدمجة).

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

_needed = {"matplotlib": "matplotlib", "numpy": "numpy", "qiskit": "qiskit", "qiskit_addon_mpf": "qiskit-addon-mpf", "qiskit_addon_utils": "qiskit-addon-utils", "qiskit_aer": "qiskit-aer", "qiskit_ibm_runtime": "qiskit-ibm-runtime", "scipy": "scipy", "tenpy": "physics-tenpy"}
_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 warnings

import numpy as np
import matplotlib.pyplot as plt
from functools import partial
from copy import deepcopy

from qiskit import QuantumCircuit
from qiskit.quantum_info import Pauli, SparsePauliOp, Statevector
from qiskit.synthesis import SuzukiTrotter
from qiskit.transpiler import CouplingMap, PassManager
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit.circuit.library import XXPlusYYGate
from qiskit.transpiler.passes.optimization.collect_and_collapse import (
CollectAndCollapse,
collect_using_filter_function,
collapse_to_operation,
)

from qiskit_aer import AerSimulator
from qiskit_ibm_runtime import EstimatorV2 as Estimator, QiskitRuntimeService

from qiskit_addon_utils.problem_generators import (
generate_xyz_hamiltonian,
generate_time_evolution_circuit,
)
from qiskit_addon_utils.slicing import slice_by_depth
from qiskit_addon_mpf.static import setup_static_lse
from qiskit_addon_mpf.dynamic import setup_dynamic_lse
from qiskit_addon_mpf.costs import (
setup_exact_problem,
setup_sum_of_squares_problem,
setup_frobenius_problem,
)
from qiskit_addon_mpf.backends.tenpy_layers import (
LayerModel,
LayerwiseEvolver,
)
from qiskit_addon_mpf.backends.tenpy_tebd import MPOState, MPS_neel_state

from scipy.linalg import expm

# Suppress TeNPy's `unit_cell_width` future-API warning. The default
# (`unit_cell_width=len(sites)`) is correct for Chain lattices, which is what
# `CouplingMap.from_line(...)` produces here, so the warning is informational.
warnings.filterwarnings(
"ignore",
message=r".*unit_cell_width.*",
category=UserWarning,
)

# --- Helper: collect XX + YY rotations into a single gate ---
def filter_function(node):
return node.op.name in {"rxx", "ryy"}

collect_function = partial(
collect_using_filter_function,
filter_function=filter_function,
split_blocks=True,
min_block_size=1,
)

def collapse_to_xx_plus_yy(block):
param = 0.0
for node in block.data:
param += node.operation.params[0]
return XXPlusYYGate(param)

collapse_function = partial(
collapse_to_operation,
collapse_function=collapse_to_xx_plus_yy,
)

pm = PassManager()
pm.append(CollectAndCollapse(collect_function, collapse_function))

مثال محاكاة صغير النطاق​

الخطوة 1: تحويل المدخلات الكلاسيكية إلى مسألة كمية​

نبدأ بنموذج هايزنبرغ من 10 كيوبتات على خط، باستخدام حالة نيل ∣0101…01⟩\vert 0101\ldots01 \rangle كحالة ابتدائية. الهاملتوني هو:

H^Heis=J∑i=1L−1(XiXi+1+YiYi+1+ZiZi+1),\hat{\mathcal{H}}_{\text{Heis}} = J \sum_{i=1}^{L-1} \left(X_i X_{i+1} + Y_i Y_{i+1} + Z_i Z_{i+1}\right),

حيث JJ هي قوة اقتران الجوار الأقرب. نقيس مترابط ZZ ZL/2−1ZL/2Z_{L/2-1} Z_{L/2} على زوج من الكيوبتات في منتصف السلسلة، ونستخدم خطوات تروتر kj=[1,2,4]k_j = [1, 2, 4] مع صيغة جداء من الرتبة الثانية.

L = 10

# Generate coupling map and Hamiltonian
coupling_map = CouplingMap.from_line(L, bidirectional=False)

hamiltonian = generate_xyz_hamiltonian(
coupling_map,
coupling_constants=(1.0, 1.0, 1.0),
ext_magnetic_field=(0.0, 0.0, 0.0),
)
print(hamiltonian)
SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII'],
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,
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,
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])
# Observable: ZZ on the middle pair of qubits
observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)
print(observable)
SparsePauliOp(['IIIIZZIIII'],
coeffs=[1.+0.j])
# MPF parameters
mpf_trotter_steps = [1, 2, 4]
order = 2
symmetric = False

trotter_times = np.arange(0.5, 1.55, 0.1)
exact_evolution_times = np.arange(trotter_times[0], 1.55, 0.05)

بناء دارات تروتر​

ننشئ الدارات التي تنفّذ تطورات تروتر الزمنية التقريبية لكل نقطة زمنية وكل عدد خطوات تروتر. يجمع ممر CollectAndCollapse المُعرَّف في قسم الإعداد تدويرات XX وYY في بوابات XX+YY واحدة، تحضيراً لمحاكاة شبكة موترية أكثر كفاءة لاحقاً.

# Initial Neel state preparation
initial_state_circ = QuantumCircuit(L)
initial_state_circ.x([i for i in range(L) if i % 2 != 0])

all_circs = []
for total_time in trotter_times:
mpf_trotter_circs = [
generate_time_evolution_circuit(
hamiltonian,
time=total_time,
synthesis=SuzukiTrotter(reps=num_steps, order=order),
)
for num_steps in mpf_trotter_steps
]

mpf_trotter_circs = pm.run(
mpf_trotter_circs
) # Collect XX and YY into XX + YY

mpf_circuits = [
initial_state_circ.compose(circuit) for circuit in mpf_trotter_circs
]
all_circs.append(mpf_circuits)
mpf_circuits[-1].draw("mpl", fold=-1)

Output of the previous code cell

الخطوة 2: تحسين المسألة لتنفيذها على العتاد الكمي​

بالنسبة للمثال صغير النطاق نستهدف محاكي Aer. يحدث تحولان قبل أن تكون الدوائر جاهزة للتنفيذ:

  1. تجميع البوابات على مستوى محاكاة الهاملتوني. في خلية الإعداد بنينا ممر CollectAndCollapse الذي يدمج تدويرات rxx وryy المتجاورة في بوابة واحدة XXPlusYYGate. طبّقنا هذا الممر بالفعل عند بناء دارات تروتر في الخطوة 1 (استدعاء pm.run(...)). هذا يقلل عدد البوابات ثنائية الكيوبت وينتج بنية أكثر ملاءمة لمحاكاة الشبكة الموترية لحساب المعاملات الديناميكية لاحقاً.

  2. الخفض إلى بنية مجموعة تعليمات المحاكي (ISA). أدناه نشغّل مدير الممرات المُعد مسبقاً في Qiskit عند optimization_level=3 لخفض كل دارة تروتر إلى بنية مجموعة تعليمات المحاكي (ISA).

aer_sim = AerSimulator()
pm_sim = generate_preset_pass_manager(backend=aer_sim, optimization_level=3)

isa_circs_all_times = [
pm_sim.run([deepcopy(c) for c in mpf_circuits])
for mpf_circuits in all_circs
]

الخطوة 3: التنفيذ باستخدام Qiskit primitives​

بالنسبة للمثال صغير النطاق، نشغّل دارات تروتر المخفوضة إلى ISA عبر أداة EstimatorV2 الأساسية المدعومة بـ Aer. يعطينا ذلك قيمة مرجعية خالية من الضوضاء لكل زوج (kj,t)(k_j, t) — وهذه هي قيم ⟨A⟩kj(t)\langle A \rangle_{k_j}(t) التي ستجمعها MPF في الخطوة 4. نمسح عبر أوقات التطور بحيث يمكننا لاحقاً رسم منحنى السلسلة الزمنية الكامل لكل صيغة جداء منفردة ولـ MPF.

estimator = Estimator(mode=aer_sim)

mpf_expvals_all_times, mpf_stds_all_times = [], []
for isa_circuits in isa_circs_all_times:
result = estimator.run(
[(circuit, observable) for circuit in isa_circuits], precision=0.005
).result()
mpf_expvals_all_times.append([res.data.evs for res in result])
mpf_stds_all_times.append([res.data.stds for res in result])

الخطوة 4: المعالجة اللاحقة وإعادة النتيجة بالصيغة الكلاسيكية المطلوبة​

الخطوة 4 هي حيث تُبنى MPF فعلياً. رغم أن المعاملات xjx_j تُحسب هنا (وبالنسبة للنسخة الديناميكية، يمكن أن يكون هذا الحساب مكثفاً)، فهي من الناحية المفاهيمية وصفة كلاسيكية لدمج القياسات الكمومية من الخطوة 3 في قيمة توقع مُصححة واحدة — لذا نعامل سير عمل المعاملات والدمج بالكامل كمعالجة لاحقة.

لتقييم مدى تتبع MPF للديناميكية الحقيقية، نحسب أولاً قيم التوقع الزمنية الدقيقة عن طريق حساب أُسّ الهاملتوني مباشرةً (e−iHte^{-iHt}). هذا ممكن فقط لأن L=10L = 10؛ في مثال العتاد كبير النطاق أدناه سيتعين علينا الاعتماد على تقديرات الشبكة الموترية بدلاً من ذلك.

exact_expvals = []
for t in exact_evolution_times:
exp_H = expm(-1j * t * hamiltonian.to_matrix())
initial_state = Statevector(initial_state_circ).data
time_evolved_state = exp_H @ initial_state

exact_obs = (
time_evolved_state.conj()
@ observable.to_matrix()
@ time_evolved_state
).real
exact_expvals.append(exact_obs)

معاملات MPF الثابتة​

تستخدم MPFs الثابتة معاملات xjx_j مستقلة عن وقت التطور والهاملتوني والحالة الابتدائية. نُعدّ النظام الخطي Ax=bAx = b الموصوف في الخلفية ونحل من أجل المعاملات. تُحدَّد المصفوفة AA بواسطة عدد خطوات تروتر kjk_j، ورتبة χ\chi لصيغة الجداء، وما إذا كانت الصيغة متماثلة (وهو ما يتحكم في الأسس ηn\eta_n).

بالنسبة لمثالنا صغير النطاق نستخدم kj=[1,2,4]k_j = [1, 2, 4] مع صيغة سوزوكي-تروتر غير متماثلة من الرتبة 2χ=22\chi=2 (بحيث χ=1\chi=1 و ηn=2+n\eta_n = 2 + n، مما يعطي η0=2, η1=3\eta_0 = 2,\, \eta_1 = 3). يصبح النظام:

A=[11111221421123143],b=[100].A = \begin{bmatrix} 1 & 1 & 1\\ 1 & \frac{1}{2^2} & \frac{1}{4^2} \\ 1 & \frac{1}{2^3} & \frac{1}{4^3} \\ \end{bmatrix}, \quad b = \begin{bmatrix} 1 \\ 0 \\ 0 \end{bmatrix}.

يفرض الصف الأول عدم التحيز (∑jxj=1\sum_j x_j = 1)؛ ويلغي الصفان الثاني والثالث حدود خطأ تروتر الرئيسية 1/k21/k^2 والرتبة التالية 1/k31/k^3، على التوالي.

إعداد نظام المعادلات الخطية (LSE)​

نستخدم setup_static_lse من qiskit_addon_mpf.static لتجميع المصفوفة AA والمتجه bb في الطرف الأيمن الموصوفين أعلاه. تعتمد المصفوفة AA ليس فقط على kjk_j بل أيضاً على اختيارنا لصيغة الجداء — خصوصاً رتبتها χ\chi وما إذا كانت متماثلة. تتحكم العلامة symmetric في نمط الأسس ηn\eta_n (تنتج الصيغ المتماثلة فقط حدود خطأ تروتر ذات القوى الزوجية؛ انظر المرجع [1]). لاحظ أنه، كما هو موضح في المرجع [2]، ضبط symmetric=True ليس ضرورياً بشكل صارم حتى عندما تكون صيغة الجداء الأساسية متماثلة — يبقى النظام الخطي غير المتماثل صالحاً (فهو يفرض قيوداً إضافية غير ضرورية).

بالنسبة لمثالنا، ضبطنا بالفعل order = 2 وsymmetric = False في الخطوة 1.

lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)

افحص المصفوفة AA والمتجه bb المُنشأين للتأكد من مطابقتهما للنظام المكتوب أعلاه.

lse.A
array([[1. , 1. , 1. ],
[1. , 0.25 , 0.0625 ],
[1. , 0.125 , 0.015625]])
lse.b
array([1., 0., 0.])

بعد الحصول على النظام الخطي، نحل من أجل المعاملات الثابتة xjx_j عبر lse.solve() (وهذا هو الحل المباشر x=A−1bx = A^{-1}b).

mpf_coeffs = lse.solve()
print(
f"The static coefficients associated with the ansatze are: {mpf_coeffs}"
)
The static coefficients associated with the ansatze are: [ 0.04761905 -0.57142857 1.52380952]
تحسين xx باستخدام نموذج دقيق​

كبديل لحساب x=A−1bx = A^{-1}b، يمكنك استخدام setup_exact_model لبناء نسخة من cvxpy.Problem تستخدم نظام المعادلات الخطية كقيود وتكون نتيجتها المثلى هي xx.

model_exact, coeffs_exact = setup_exact_problem(lse)
model_exact.solve()
print(coeffs_exact.value)
[ 0.04761905 -0.57142857 1.52380952]
print(
"L1 norm of the exact coefficients:",
np.linalg.norm(coeffs_exact.value, ord=1),
)
L1 norm of the exact coefficients: 2.1428571428556378
تحسين xx باستخدام نموذج تقريبي​

قد يحدث أن يُعدّ معيار L1L_1 لمجموعة قيم kjk_j المختارة مرتفعاً جداً. إذا كان الأمر كذلك ولم تتمكن من اختيار مجموعة مختلفة من قيم kjk_j، يمكنك استخدام حل تقريبي يقيّد معيار L1L_1 إلى عتبة مختارة مع تقليل ∥Ax−b∥\|Ax - b\|. راجع الدليل حول كيفية استخدام النموذج التقريبي.

model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=1.5
)
model_approx.solve()
print(coeffs_approx.value)
print(
"L1 norm of the approximate coefficients:",
np.linalg.norm(coeffs_approx.value, ord=1),
)
[-1.10294118e-03 -2.48897059e-01 1.25000000e+00]
L1 norm of the approximate coefficients: 1.5

معاملات MPF الديناميكية​

تُلغي MPF الثابتة حدود خطأ تروتر بطريقة مستقلة عن الهاملتوني والحالة، لذا فهي لا تنتج بالضرورة أصغر خطأ تقريب ممكن لهاملتوني وحالة ابتدائية معينين. تجد MPF الديناميكية (المرجعان [2]، [3]) بدلاً من ذلك معاملات تعتمد على الزمن xi(t)x_i(t) تُقلّل مسافة معيار فروبينيوس ∥ρ(t)−μD(t)∥F2\|\rho(t) - \mu^D(t)\|_F^2 عند كل زمن tt. كما هو موضح في الخلفية، يتطلب هذا مصفوفة التداخل Mij(t)M_{ij}(t) بين الحالات المتطورة بتروتر والتداخل Li(t)L_i(t) مع الحالة الدقيقة — وكلاهما نقدّرهما باستخدام خلفيات الشبكة الموترية (TeNPy) في qiskit_addon_mpf.

لإعداد النظام الخطي الديناميكي نحتاج إلى ثلاثة مكونات:

  1. مصنع مُطوِّر تقريبي يشغّله الملحق لكل kjk_j لإنتاج ρkj(t)\rho_{k_j}(t) كـ MPS/MPO. نبنيه من البنية الطبقية لدارة تروتر من الرتبة الثانية (طبقة واحدة لكل slice_by_depth)، مُغلّفة كـ LayerwiseEvolver مع معاملات اقتطاع TeNPy.

  2. مصنع مُطوِّر دقيق ينتج مرجعاً عالي الدقة ρ(t)\rho(t). نستخدم دارة سوزوكي-تروتر من الرتبة الرابعة بخطوة زمنية صغيرة (dt=0.1، order=4) كبديل للتطور الدقيق.

  3. مصنع هوية وحالة ابتدائية MPS تُهيّئ محاكاة TeNPy.

تبني الخلية أدناه مصنع المُطوِّر التقريبي.

# Create approximate time-evolution circuits
single_2nd_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)
)
single_2nd_order_circ = pm.run(single_2nd_order_circ) # collect XX and YY

# Find layers in the circuit
layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)

# Create tensor network models
models = [
LayerModel.from_quantum_circuit(layer, conserve="Sz") for layer in layers
]

# Create the time-evolution object
approx_factory = partial(
LayerwiseEvolver,
layers=models,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 2,
},
)
تحذير

يجب اختيار خيارات LayerwiseEvolver التي تحدد تفاصيل محاكاة الشبكة الموترية (tensor network) بعناية لتجنب إعداد مسألة تحسين غير محددة جيدًا.

نقرّب الحالة الدقيقة المتطورة زمنيًا بصيغة سوزوكي-تروتر من الرتبة الرابعة باستخدام خطوة زمنية صغيرة dt=0.1. يمكن أن تؤثر معاملات القطع (truncation) الخاصة بـ TeNPy على الدقة، لذا من المهم استكشاف نطاق من القيم.

single_4th_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)
)
single_4th_order_circ = pm.run(single_4th_order_circ)
exact_model_layers = [
LayerModel.from_quantum_circuit(layer, conserve="Sz")
for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)
]

exact_factory = partial(
LayerwiseEvolver,
layers=exact_model_layers,
dt=0.1,
options={
"preserve_norm": False,
"trunc_params": {
"chi_max": 64,
"svd_min": 1e-8,
"trunc_cut": None,
},
"max_delta_t": 2,
},
)

أخيراً، نُعرّف identity_factory الذي ينتج حالة MPO الابتدائية ونحضّر حالة نيل الابتدائية كـ MPS يطابق الشبكة المستخدمة في نموذج تروتر الطبقي.

def identity_factory():
return MPOState.initialize_from_lattice(models[0].lat, conserve=True)

mps_initial_state = MPS_neel_state(models[0].lat)

مع وجود المصانع في مكانها، نحسب الآن المعاملات الديناميكية عند كل وقت تطور. لكل tt، تبني setup_dynamic_lse مصفوفات التداخل ذات الصلة عبر TeNPy، وتُعيد setup_frobenius_problem كائن cvxpy.Problem يقلّل تكلفة معيار فروبينيوس. يُعيد الحلّال معاملات xj(t)x_j(t) مُصممة لذلك الزمن؛ نجمعها في mpf_dynamic_coeffs_list. إذا فشل الحلّال لزمن معين tt، نعود إلى معاملات صفرية بحيث تستمر الحلقة.

mpf_dynamic_coeffs_list = []
for t in trotter_times:
print(f"Computing dynamic coefficients for time={t}")
lse = setup_dynamic_lse(
mpf_trotter_steps,
t,
identity_factory,
exact_factory,
approx_factory,
mps_initial_state,
)
problem, coeffs = setup_frobenius_problem(lse)
try:
problem.solve()
mpf_dynamic_coeffs_list.append(coeffs.value)
except Exception as error:
mpf_dynamic_coeffs_list.append(np.zeros(len(mpf_trotter_steps)))
print(error, "Calculation Failed for time", t)
print("")
Computing dynamic coefficients for time=0.5

Computing dynamic coefficients for time=0.6

Computing dynamic coefficients for time=0.7

Computing dynamic coefficients for time=0.7999999999999999

Computing dynamic coefficients for time=0.8999999999999999

Computing dynamic coefficients for time=0.9999999999999999

Computing dynamic coefficients for time=1.0999999999999999

Computing dynamic coefficients for time=1.1999999999999997

Computing dynamic coefficients for time=1.2999999999999998

Computing dynamic coefficients for time=1.4

Computing dynamic coefficients for time=1.4999999999999998

دمج قيم توقع تروتر مع معاملات MPF​

الآن نُقيّم ⟨A⟩MPF(t)=∑jxj ⟨A⟩kj(t)\langle A \rangle_{\text{MPF}}(t) = \sum_j x_j \, \langle A \rangle_{k_j}(t) لكل مجموعة من المعاملات (الثابتة الدقيقة، والثابتة التقريبية، والديناميكية)، وننشر الأخطاء المعيارية لكل دائرة، ونرسم السلسلة الزمنية الناتجة مقابل منحنى القطرنة الدقيقة.

sym = {1: "^", 2: "s", 4: "p"}
# Get expectation values at all times for each Trotter step
for k, step in enumerate(mpf_trotter_steps):
trotter_curve, trotter_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
trotter_curve.append(trotter_expvals[k])
trotter_curve_error.append(trotter_stds[k])

plt.errorbar(
trotter_times,
trotter_curve,
yerr=trotter_curve_error,
alpha=0.5,
markersize=4,
marker=sym[step],
color="grey",
label=f"{mpf_trotter_steps[k]} Trotter steps",
)

# Get expectation values at all times for the static MPF with exact coeffs
exact_mpf_curve, exact_mpf_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_exact.value, trotter_stds)
]
)
)
exact_mpf_curve_error.append(mpf_std)
exact_mpf_curve.append(trotter_expvals @ coeffs_exact.value)

plt.errorbar(
trotter_times,
exact_mpf_curve,
yerr=exact_mpf_curve_error,
markersize=4,
marker="o",
label="Static MPF - Exact",
color="purple",
)

# Get expectation values at all times for the static MPF with approximate coeffs
approx_mpf_curve, approx_mpf_curve_error = [], []
for trotter_expvals, trotter_stds in zip(
mpf_expvals_all_times, mpf_stds_all_times
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_approx.value, trotter_stds)
]
)
)
approx_mpf_curve_error.append(mpf_std)
approx_mpf_curve.append(trotter_expvals @ coeffs_approx.value)

plt.errorbar(
trotter_times,
approx_mpf_curve,
yerr=approx_mpf_curve_error,
markersize=4,
marker="o",
label="Static MPF - Approx",
color="orange",
)

# Get expectation values at all times for the dynamic MPF
dynamic_mpf_curve, dynamic_mpf_curve_error = [], []
for trotter_expvals, trotter_stds, dynamic_coeffs in zip(
mpf_expvals_all_times, mpf_stds_all_times, mpf_dynamic_coeffs_list
):
mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(dynamic_coeffs, trotter_stds)
]
)
)
dynamic_mpf_curve_error.append(mpf_std)
dynamic_mpf_curve.append(trotter_expvals @ dynamic_coeffs)

plt.errorbar(
trotter_times,
dynamic_mpf_curve,
yerr=dynamic_mpf_curve_error,
markersize=4,
marker="o",
label="Dynamic MPF",
color="pink",
)

# Exact expectation values
plt.plot(
exact_evolution_times,
exact_expvals,
color="red",
linestyle="--",
label="Exact time-evolution",
)

plt.title(f"$\\langle Z_{{{L//2-1}}} Z_{{{L//2}}} \\rangle$ vs time")
plt.xlabel("Time")
plt.ylabel("Expectation Value")
plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=2)
plt.grid(alpha=0.1)
plt.tight_layout()
plt.show()

Output of the previous code cell

يوضح الرسم أعلاه التفاعل بين خطأ تروتر وخطأ العينات.

  • خطأ تروتر. تنحرف صيغ الجداء المنفردة (العلامات الرمادية) عن المنحنى الدقيق أكثر فأكثر مع تقدم الزمن. دارة k=1k=1 لديها أكبر انحراف وهي الأقل عمقاً، لكنها أيضاً بالفعل في نظام حيث t/k≳1t/k \gtrsim 1، لذا يكون حد الخطأ الرئيسي 1/k21/k^{2} كبيراً. تُلغي توليفات MPF (العلامات الملونة) عدة من حدود خطأ تروتر الرئيسية هذه، لذا تتبع المنحنى الدقيق بشكل أوثق بكثير من أي دارة kjk_j منفردة. تعكس الفجوة المتبقية حدود تروتر ذات الرتبة الأعلى التي لا تُلغيها MPF: تقتل MPF ثابتة من الرتبة 22، r=3r=3 فقط أول رتبتَي خطأ، وعند t/kmin⁡t/k_{\min} كبير يهيمن في النهاية الذيل غير المُلغى — لذا لا تضمن MPF أن تبقى الدارات الضحلة جداً دقيقة عند أزمنة تعسفية.

  • خطأ العينات. أشرطة الخطأ الأوسع على منحنيات MPF هي نتيجة مباشرة للتوليفة الخطية: نشر الأخطاء المعيارية المستقلة لكل دارة σkj\sigma_{k_j} يعطي تبايناً كلياً σMPF2=∑jxj2 σkj2\sigma_{\text{MPF}}^2 = \sum_j x_j^2 \, \sigma_{k_j}^2. لذا، كلما كان ∥x∥2\|x\|_2 أكبر (وعملياً ∥x∥1\|x\|_1، وهو ما نتحكم به)، كلما زادت اللقطات المطلوبة للوصول إلى عدم يقين مستهدف معين. هذا هو التوازن وراء خيار الحلّال التقريبي في الخلفية: نحدّ ∥x∥1\|x\|_1 للحفاظ على هذه التكلفة الإضافية قابلة للإدارة. بشكل حاسم، وعلى عكس خطأ تروتر، يتقلص خطأ العينات مع 1/Nshots1/\sqrt{N_{\text{shots}}}، لذا يمكن دائماً تقليله بإنفاق المزيد من اللقطات.

في مثال العتاد كبير النطاق أدناه، تدخل ضوضاء العتاد كمصدر خطأ إضافي على كل ⟨A⟩kj\langle A \rangle_{k_j}، والذي يُضخَّم بالمثل بواسطة معاملات MPF. سنرى كيف يتفاعل تخفيف الأخطاء مع MPFs في ذلك القسم.

مثال عتاد كبير النطاق​

في هذا القسم نُوسّع المسألة إلى ما هو أبعد مما يمكن محاكاته بدقة. نُعيد إنتاج بعض النتائج المُوضحة في المرجع [3]، باستخدام سلسلة XXZ من 50 كيوبت عند زمن t=3t = 3. نتبع نفس سير العمل من أربع خطوات كالمثال صغير النطاق، مستهدفين الآن عتاد كمومي حقيقي مع تخفيف الأخطاء. كما في القالب، تُوضَّح كل خطوة ضمن الشيفرة، ويمكن أن تمتد خطوة واحدة عبر عدة خلايا عندما تستحق مخرجاتها الوسيطة الفحص. تحاكي خطوة التحويل (الخطوة 1) المثال صغير النطاق: تعريف هاملتوني، اختيار معاملات تروتر، حساب معاملات MPF (ثابتة وديناميكية)، وبناء الدارات. الاختلافات الرئيسية هي:

  • هاملتوني XXZ على 50 موقعاً مع اقترانات عشوائية مسحوبة من U(0.5,1.5)\mathcal{U}(0.5, 1.5) (المرجع [3]).

  • صيغة تروتر متماثلة من الرتبة الثانية مع kj=[3,4,6]k_j = [3, 4, 6] (بحيث χ=1\chi=1، symmetric=True).

  • وقت تطور ثابت واحد t=3t = 3. مع kmin⁡=3k_{\min}=3 يعطي هذا t/kmin⁡=1t/k_{\min}=1، مما يُبقي المكونات الضحلة داخل نظام تقارب تروتر حيث يكون نموذج الخطأ الرئيسي الذي تعتمد عليه MPF صالحاً.

  • تشغيلة مقارنة إضافية بدارة واحدة بعدد خطوات تروتر k=10k = 10، تُستخدم كخط أساس. اخترنا k=10k = 10 لأن عمقها ثنائي الكيوبت على العتاد أعمق من أعمق مكون في MPF (kmax⁡=6k_{\max}=6) بالإضافة إلى تكلفة تشغيل عدة دارات MPF — عميقة بما يكفي لتكون محدودة بالضوضاء، وهو النظام الذي يُتوقع فيه أن يتفوق توليف MPF على خط الأساس ذي الدارة الواحدة. إنها مقارنة "دارة واحدة عميقة" مقابل توليف MPF، وليست دارة تستهدف خطأ تروتر الفعّال لـ MPF (وهو ما سيتطلب خطوات أكثر بكثير).

لاحظ أنه رغم أننا ما زلنا في الخطوة 1 هنا (التحويل وبناء الدارة)، نحسب أيضاً مسبقاً المعاملات الديناميكية جنباً إلى جنب مع الثابتة في هذه الخلية. تعتمد المعاملات الديناميكية على HH وtt ولكن ليس على القياسات الكمومية، لذا يمكن حسابها في أي وقت قبل الخطوة 4. نقوم بذلك الآن للحفاظ على كل الإعداد الخاص بـ MPF في مكان واحد.

# -------------------------Step 1-------------------------
L = 50
coupling_map = CouplingMap.from_line(L, bidirectional=False)

# XXZ Hamiltonian with random couplings (Ref. [3])
np.random.seed(0)
even_edges = list(coupling_map.get_edges())[::2]
odd_edges = list(coupling_map.get_edges())[1::2]

Js = np.random.uniform(0.5, 1.5, size=L)
hamiltonian = SparsePauliOp(Pauli("I" * L))
for i, edge in enumerate(even_edges + odd_edges):
hamiltonian += SparsePauliOp.from_sparse_list(
[
("XX", (edge), 2 * Js[i]),
("YY", (edge), 2 * Js[i]),
("ZZ", (edge), 4 * Js[i]),
],
num_qubits=L,
)

observable = SparsePauliOp.from_sparse_list(
[("ZZ", (L // 2 - 1, L // 2), 1.0)], num_qubits=L
)

total_time = 3
mpf_trotter_steps = [3, 4, 6]
order = 2
symmetric = True

# Static coefficients
lse = setup_static_lse(mpf_trotter_steps, order=order, symmetric=symmetric)
mpf_coeffs = lse.solve()
print(f"Static coefficients: {mpf_coeffs}")
print(f"L1 norm: {np.linalg.norm(mpf_coeffs, ord=1)}")

model_approx, coeffs_approx = setup_sum_of_squares_problem(
lse, max_l1_norm=2.0
)
model_approx.solve()
print(f"Approximate coefficients: {coeffs_approx.value}")
print(f"L1 norm (approx): {np.linalg.norm(coeffs_approx.value, ord=1)}")

# -------------------------Dynamic coefficients-------------------------
single_2nd_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=order)
)
single_2nd_order_circ = pm.run(single_2nd_order_circ)

layers = slice_by_depth(single_2nd_order_circ, max_slice_depth=1)
models = [
LayerModel.from_quantum_circuit(layer, conserve="Sz") for layer in layers
]

approx_factory = partial(
LayerwiseEvolver,
layers=models,
options={
"preserve_norm": False,
"trunc_params": {"chi_max": 64, "svd_min": 1e-8, "trunc_cut": None},
"max_delta_t": 4,
},
)

single_4th_order_circ = generate_time_evolution_circuit(
hamiltonian, time=1.0, synthesis=SuzukiTrotter(reps=1, order=4)
)
single_4th_order_circ = pm.run(single_4th_order_circ)
exact_model_layers = [
LayerModel.from_quantum_circuit(layer, conserve="Sz")
for layer in slice_by_depth(single_4th_order_circ, max_slice_depth=1)
]

exact_factory = partial(
LayerwiseEvolver,
layers=exact_model_layers,
dt=0.1,
options={
"preserve_norm": False,
"trunc_params": {"chi_max": 64, "svd_min": 1e-8, "trunc_cut": None},
"max_delta_t": 3,
},
)

def identity_factory():
return MPOState.initialize_from_lattice(models[0].lat, conserve=True)

mps_initial_state = MPS_neel_state(models[0].lat)

print(f"Computing dynamic coefficients for time={total_time}")
lse_dyn = setup_dynamic_lse(
mpf_trotter_steps,
total_time,
identity_factory,
exact_factory,
approx_factory,
mps_initial_state,
)
problem, coeffs_dyn = setup_frobenius_problem(lse_dyn)
try:
problem.solve()
mpf_dynamic_coeffs = coeffs_dyn.value
except Exception as error:
mpf_dynamic_coeffs = np.zeros(len(mpf_trotter_steps))
print(error, "Calculation Failed")

# -------------------------Step 1 (cont): Build circuits-------------------------
mpf_circuits = []
for k in mpf_trotter_steps:
circuit = QuantumCircuit(L)
circuit.x([i for i in range(L) if i % 2])
trotter_circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=k, order=order),
time=total_time,
)
circuit.compose(trotter_circ, qubits=range(L), inplace=True)
mpf_circuits.append(circuit)

# Baseline "single deep circuit" comparison run with k=10 Trotter steps.
# Its two-qubit depth is deeper than the deepest MPF constituent (k_max=6) plus
# the overhead of running multiple circuits, pushing it into the noise-limited
# regime where MPF is expected to outperform. It does NOT target the MPF's effective
# Trotter error (which would require many more steps).
comp_circuit = QuantumCircuit(L)
comp_circuit.x([i for i in range(L) if i % 2])
trotter_circ = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=10, order=order),
time=total_time,
)
comp_circuit.compose(trotter_circ, qubits=range(L), inplace=True)
mpf_circuits.append(comp_circuit)
Static coefficients: [ 0.42857143 -1.82857143 2.4 ]
L1 norm: 4.65714285714286
Approximate coefficients: [-0.4942491 0.40206845 1.09218065]
L1 norm (approx): 1.9884981979026675
Computing dynamic coefficients for time=3

نُحسّن الآن الدوائر للعتاد المُختار. نستخدم مدير الممرات المُعد مسبقاً في Qiskit عند optimization_level=3، والذي يختار تلقائياً مجموعة جيدة من الكيوبتات الفيزيائية ويوجّه كل دائرة إلى طوبولوجيا الجهاز.

# -------------------------Step 2-------------------------
service = QiskitRuntimeService()
# backend = service.least_busy(operational=True, simulator=False, min_num_qubits=L)
backend = service.backend("ibm_fez")
print(backend)

transpiler = generate_preset_pass_manager(
optimization_level=3, backend=backend
)
transpiled_circuits = [transpiler.run(circ) for circ in mpf_circuits]

isa_observables = [
observable.apply_layout(circ.layout) for circ in transpiled_circuits
]
<IBMBackend('ibm_fez')>

يتطلب تشغيل دوائر أعمق على عتاد حقيقي تخفيف أخطاء قوياً. نُفعّل فك الاقتران الديناميكي، وتفتيل البوابات والقياس (twirling)، وتخفيف خطأ القياس، والاستقراء إلى الضوضاء الصفرية (ZNE). لاحظ أن عوامل ضوضاء ZNE التي نستخدمها هنا (1, 1.2, 1.4) أصغر مما هي عليه في سيناريو الدائرة الضحلة، لأن مكونات MPF الأعمق قريبة بالفعل من عتبة الضوضاء وقد تدفع التضخيمات الكبيرة للضوضاء بها إلى ما بعد نقطة موثوقية استقراء ZNE.

نُرسل الدوائر الأربع جميعها (ثلاثة مكونات MPF عند kj=[3,4,6]k_j = [3, 4, 6] بالإضافة إلى خط الأساس k=10k = 10) في وظيفة Estimator واحدة.

# -------------------------Step 3-------------------------
estimator = Estimator(mode=backend)
estimator.options.default_shots = 30000

# Error suppression/mitigation
estimator.options.dynamical_decoupling.enable = True
estimator.options.twirling.enable_gates = True
estimator.options.twirling.enable_measure = True
estimator.options.twirling.num_randomizations = "auto"
estimator.options.twirling.strategy = "active-accum"
estimator.options.resilience.measure_mitigation = True
estimator.options.experimental.execution_path = "gen3-turbo"

estimator.options.resilience.zne_mitigation = True
estimator.options.resilience.zne.noise_factors = (1, 1.2, 1.4)
estimator.options.resilience.zne.extrapolator = "linear"

estimator.options.environment.job_tags = ["TUT_MPF"]

job_50 = estimator.run(
[
(circ, observable)
for circ, observable in zip(transpiled_circuits, isa_observables)
]
)

نسحب قيم التوقع والانحرافات المعيارية لكل دائرة من نتيجة الوظيفة، ثم نجمعها مع كل مجموعة من معاملات MPF تماماً كما في المثال صغير النطاق: ⟨A⟩MPF=∑jxj ⟨A⟩kj\langle A \rangle_{\text{MPF}} = \sum_j x_j \, \langle A \rangle_{k_j}، مع تباين منشور σ2=∑jxj2σkj2\sigma^2 = \sum_j x_j^2 \sigma_{k_j}^2.

# -------------------------Step 4-------------------------
result = job_50.result()
evs = [res.data.evs for res in result]
std = [res.data.stds for res in result]

print(evs)
print(std)
[array(-0.07916195), array(-0.04479681), array(-0.2560756), array(-0.06045848)]
[array(0.04605538), array(0.10056336), array(0.14426151), array(0.04059092)]
exact_mpf_std = np.sqrt(
sum([(coeff**2) * (std**2) for coeff, std in zip(mpf_coeffs, std[:3])])
)
print(
"Exact static MPF expectation value: ",
evs[:3] @ mpf_coeffs,
"+-",
exact_mpf_std,
)
approx_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(coeffs_approx.value, std[:3])
]
)
)
print(
"Approximate static MPF expectation value: ",
evs[:3] @ coeffs_approx.value,
"+-",
approx_mpf_std,
)
dynamic_mpf_std = np.sqrt(
sum(
[
(coeff**2) * (std**2)
for coeff, std in zip(mpf_dynamic_coeffs, std[:3])
]
)
)
print(
"Dynamic MPF expectation value: ",
evs[:3] @ mpf_dynamic_coeffs,
"+-",
dynamic_mpf_std,
)
Exact static MPF expectation value: -0.5665938395816946 +- 0.3925273058119915
Approximate static MPF expectation value: -0.25856647611537903 +- 0.164249927266166
Dynamic MPF expectation value: -0.12667812062949296 +- 0.06059471006973169
sym = {3: "^", 4: "s", 6: "p"}
for k, step in enumerate(mpf_trotter_steps):
plt.errorbar(
k,
evs[k],
yerr=std[k],
alpha=0.5,
markersize=4,
marker=sym[step],
color="grey",
label=f"{mpf_trotter_steps[k]} Trotter steps",
)

plt.errorbar(
3,
evs[-1],
yerr=std[-1],
alpha=0.5,
markersize=8,
marker="x",
color="blue",
label="10 Trotter steps",
)

plt.errorbar(
4,
evs[:3] @ mpf_coeffs,
yerr=exact_mpf_std,
markersize=4,
marker="o",
color="purple",
label="Static MPF",
)

plt.errorbar(
5,
evs[:3] @ coeffs_approx.value,
yerr=approx_mpf_std,
markersize=4,
marker="o",
color="orange",
label="Approximate static MPF",
)

plt.errorbar(
6,
evs[:3] @ mpf_dynamic_coeffs,
yerr=dynamic_mpf_std,
markersize=4,
marker="o",
color="pink",
label="Dynamic MPF",
)

exact_obs = -0.24384471447172074 # Calculated via Tensor Network calculation
plt.axhline(
y=exact_obs, linestyle="--", color="red", label="Exact time-evolution"
)

plt.title(
f"$\\langle Z_{{{L//2-1}}} Z_{{{L//2}}} \\rangle$ at time {total_time} for the different methods"
)
plt.xlabel("Method")
plt.ylabel("Expectation Value")
plt.legend(loc="upper center", bbox_to_anchor=(0.5, -0.2), ncol=2)
plt.grid(alpha=0.1)
plt.tight_layout()
plt.show()

Output of the previous code cell

بعض الملاحظات حول نتائج العتاد أعلاه:

  • الذهاب أعمق ليس مجانياً على العتاد. تروي خطوط الأساس ذات الدائرة الواحدة القصة مباشرةً: دائرة k=6k = 6 دقيقة تقريباً (−0.256-0.256 مقابل المرجع −0.244-0.244)، ومع ذلك خط الأساس الأعمق k=10k = 10 أسوأ (−0.061-0.061، بانحراف ∼0.18\sim 0.18)، وليس أفضل. بمجرد أن يصبح خطأ تروتر صغيراً بالفعل، تعمل إضافة الخطوات في الغالب على تعميق الدائرة وتراكم المزيد من ضوضاء البوابات والتفكك الترابطي. هذا بالضبط النظام الذي بُنيت من أجله MPFs: تحقيق دقة دائرة عميقة باستخدام مكونات ضحلة فقط.

  • MPF ذات معيار صغير تتفوق على الدائرة العميقة المنفردة. تصل MPF الثابتة التقريبية (المحدودة عند ∥x∥1≈2\|x\|_1 \approx 2) إلى −0.259-0.259، ضمن ∼0.015\sim 0.015 من المرجع وأقرب بكثير من خط الأساس k=10k = 10. تتفوق MPF الديناميكية أيضاً (−0.127-0.127) بشكل مريح على ذلك الخط الأساسي. كلاهما يجمع فقط الدوائر الضحلة kj=[3,4,6]k_j = [3, 4, 6]، ومع ذلك يستعيدان إجابة لم تستطع الدائرة العميقة المنفردة تحقيقها.

  • معيار المعاملات أهم من الأمثلية الرياضية. لدى MPF الثابتة الدقيقة ∥x∥1=4.66\|x\|_1 = 4.66 وهي أسوأ مُقدِّر على الإطلاق (−0.567-0.567، بانحراف أكثر من 0.30.3): يُضخّم معيار المعاملات الكبير ضوضاء البوابات المتبقية، والتفكك الترابطي، وخطأ ZNE على كل ⟨A⟩kj\langle A \rangle_{k_j} بنفس العامل تقريباً، مما يطغى على إلغاء خطأ تروتر الذي يشتريه. يزيل تحديد المعيار (الحلّال الثابت التقريبي، ∥x∥1≈2\|x\|_1 \approx 2) هذا الطغيان ويعطي أفضل تقدير — حتى لو لم تعد معاملاته تُلغي خطأ تروتر الرئيسي بدقة.

  • الدوائر الضحلة المنفردة يمكن أن تظل تنافسية. المكون المنفرد k=6k = 6 (−0.256-0.256) دقيق أساساً بذاته هنا — في هذا التشغيل هو حتى أقرب هامشياً من MPF الثابتة التقريبية. المشكلة هي أنك لا تعرف مسبقاً أي kk منفرد يقع في النقطة المثالية بين "متقارب لكن ليس بعد محدوداً بالضوضاء"، والخيار الذي يبدو آمناً وهو ببساطة الذهاب أعمق (k=10k = 10) لضمان تقارب تروتر هو بالضبط الخيار الذي يفشل. تعطي MPF توليفة مبدئية من الدوائر الضحلة لا تتطلب تخمين العمق الصحيح.

الخلاصة العملية هي أنه على العتاد، يجب إقران MPFs بتخفيف أخطاء قوي على كل ⟨A⟩kj\langle A \rangle_{k_j} منفرد، ويجب إبقاء معيار المعاملات L1L_1 معتدلاً (استخدم الحلّال التقريبي، أو MPF الديناميكية)، ويجب اختيار خطوات تروتر kjk_j بحيث يكون t/kmin⁡≲1t/k_{\min} \lesssim 1 — هنا kmin⁡=3k_{\min} = 3 عند t=3t = 3 يعطي t/kmin⁡=1t/k_{\min} = 1، مما يُبقي المكونات داخل النظام المتقارب حيث يكون نموذج الخطأ الرئيسي الذي تعتمد عليه MPF الثابتة صالحاً. مع هذه الاختيارات، تُطابق MPFs ذات المعيار الصغير هنا دائرة منفردة متقاربة بينما لا يفعل خط الأساس الساذج "فقط اذهب أعمق" ذلك، مُستعيدةً ميزة العمق مقابل الدقة الموضحة في المرجع [3]. لاحظ أيضاً أن التشغيلات الفردية ضوضائية — في إرسال مختلف لنفس الوظيفة (أو Backend مختلف)، يمكن أن يتغير الترتيب الدقيق؛ الاتجاهات القوية هي أن MPFs ذات ∥x∥1\|x\|_1 الصغير تعمل بشكل جيد، وأن MPF الثابتة الدقيقة ذات ∥x∥1\|x\|_1 الكبير مُضخَّمة بضوضاء العتاد، وأن الدائرة المنفردة العميقة أكثر من اللازم محدودة بالضوضاء.

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

توصيات

إذا وجدت هذا العمل مثيراً للاهتمام، فقد تكون مهتماً بالمواد التالية:

المراجع​

[1] Vazquez, A. C., Egger, D. J., Ochsner, D., & Woerner, S. Well-conditioned multi-product formulas for hardware-friendly Hamiltonian simulation. Quantum, 7, 1067 (2023)

[2] Zhuk, S., Robertson, N. F., & Bravyi, S. Trotter error bounds and dynamic multi-product formulas for Hamiltonian simulation. Physical Review Research, 6(3), 033309 (2024)

[3] Robertson, N. F., et al. Tensor network enhanced dynamic multiproduct formulas. arXiv:2407.17405 (2024)