بناء قالب دالة لمحاكاة هاميلتونيان
يُغلّف هذا القالب سير عمل لمحاكاة التطور الزمني لحالة ابتدائية مقارنةً بهاميلتونيان قائم على السبين يُعرّفه المستخدم، ويُعيد مجموعة من قيم التوقع المحددة باستخدام إضافة AQC.
هذا القالب مُهيكل كنمط Qiskit يتضمن الخطوات التالية:
1. جمع المدخلات وتعيين المسألة
يأخذ هذا القسم كمدخلات: هاميلتونيان المراد محاكاته، وحالة ابتدائية على شكل QuantumCircuit، ومجموعة من المؤثرات لتقدير قيم التوقع، ومواصفات خيارات إضافة AQC. تتحقق هذه الخطوة من وجود جميع بيانات الإدخال المطلوبة وأنها بالتنسيق الصحيح.
تُستخدم بعد ذلك وسيطات الإدخال لبناء الدوائر الكمومية والمؤثرات ذات الصلة لسير العمل. يُنشأ دائرة هدف، ويجري البحث عن تمثيل حالة جداء المصفوفة لهذه الدائرة باستخدام إضافة AQC. بعد ذلك، تُولَّد دائرة ansatz وتُحسَّن باستخدام طرق الشبكات الترابطية (tensor network)، مما ينتج دائرةً نهائية تُنفّذ بقية التطور الزمني.
2. إعداد الدوائر المولَّدة للتنفيذ
تُحوَّل الدوائر المولَّدة من إضافة AQC بعد ذلك عبر الـ transpilation لتُنفَّذ على بيكاند مختار. يُنشأ نسخة من EstimatorV2 مع مجموعة افتراضية من خيارات تخفيف الأخطاء لإدارة تنفيذ الدائرة.
3. التنفيذ
أخيراً، تُحوَّل دائرة ansatz عبر الـ transpilation وتُنفَّذ على QPU، وتجمع التقديرات لجميع قيم التوقع المحددة، التي تُعاد بتنسيق قابل للتسلسل (serializable) ليتمكن المستخدم من الوصول إليها.
كتابة قالب الدالة
أولاً، اكتب قالب دالة لمحاكاة هاميلتونيان يستخدم إضافة AQC-Tensor لـ Qiskit لتعيين وصف المسألة إلى دائرة ذات عمق مخفَّض للتنفيذ على العتاد.
طوال العملية، يُحفظ الكود في ./source_files/template_hamiltonian_simulation.py. هذا الملف هو قالب الدالة الذي يمكنك رفعه وتشغيله عن بُعد مع Qiskit Serverless.
# Added by doQumentation — required packages for this notebook
!pip install -q mergedeep numpy qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-ibm-catalog qiskit-ibm-runtime qiskit-serverless quimb scipy
# This cell is hidden from users, it just creates a new folder
from pathlib import Path
Path("./source_files").mkdir(exist_ok=True)
جمع المدخلات والتحقق منها
ابدأ بالحصول على مدخلات القالب. يحتوي هذا المثال على مدخلات خاصة بالمجال وذات صلة بمحاكاة هاميلتونيان (مثل هاميلتونيان والمؤثر)، وخيارات خاصة بالإمكانيات (مثل مقدار ضغط الطبقات الأولية من دائرة Trotter باستخدام AQC-Tensor، أو خيارات متقدمة لضبط دقيق لقمع الأخطاء وتخفيفها ما وراء الإعدادات الافتراضية المضمّنة في هذا المثال).
%%writefile ./source_files/template_hamiltonian_simulation.py
from qiskit import QuantumCircuit
from qiskit_serverless import get_arguments, save_result
# Extract parameters from arguments
#
# Do this at the top of the program so it fails early if any required arguments are missing or invalid.
arguments = get_arguments()
dry_run = arguments.get("dry_run", False)
backend_name = arguments["backend_name"]
aqc_evolution_time = arguments["aqc_evolution_time"]
aqc_ansatz_num_trotter_steps = arguments["aqc_ansatz_num_trotter_steps"]
aqc_target_num_trotter_steps = arguments["aqc_target_num_trotter_steps"]
remainder_evolution_time = arguments["remainder_evolution_time"]
remainder_num_trotter_steps = arguments["remainder_num_trotter_steps"]
# Stop if this fidelity is achieved
aqc_stopping_fidelity = arguments.get("aqc_stopping_fidelity", 1.0)
# Stop after this number of iterations, even if stopping fidelity is not achieved
aqc_max_iterations = arguments.get("aqc_max_iterations", 500)
hamiltonian = arguments["hamiltonian"]
observable = arguments["observable"]
initial_state = arguments.get("initial_state", QuantumCircuit(hamiltonian.num_qubits))
Writing ./source_files/template_hamiltonian_simulation.py
%%writefile --append ./source_files/template_hamiltonian_simulation.py
import numpy as np
import json
from mergedeep import merge
# Configure `EstimatorOptions`, to control the parameters of the hardware experiment
#
# Set default options
estimator_default_options = {
"resilience": {
"measure_mitigation": True,
"zne_mitigation": True,
"zne": {
"amplifier": "gate_folding",
"noise_factors": [1, 2, 3],
"extrapolated_noise_factors": list(np.linspace(0, 3, 31)),
"extrapolator": ["exponential", "linear", "fallback"],
},
"measure_noise_learning": {
"num_randomizations": 512,
"shots_per_randomization": 512,
},
},
"twirling": {
"enable_gates": True,
"enable_measure": True,
"num_randomizations": 300,
"shots_per_randomization": 100,
"strategy": "active",
},
}
# Merge with user-provided options
estimator_options = merge(
arguments.get("estimator_options", {}), estimator_default_options
)
Appending to ./source_files/template_hamiltonian_simulation.py
حين يعمل قالب الدالة، يُفيد إرجاع المعلومات في السجلات باستخدام عبارات print، حتى تتمكن من تقييم تقدم حمل العمل بشكل أفضل. فيما يلي مثال بسيط على طباعة estimator_options لتبقى سجلاً بخيارات Estimator الفعلية المستخدمة. ثمة أمثلة مشابهة كثيرة أخرى في أنحاء البرنامج للإبلاغ عن التقدم أثناء التنفيذ، بما في ذلك قيمة الدالة الهدف خلال المكوّن التكراري لـ AQC-Tensor، وعمق البوابتين الكيوبتيتين لدائرة بنية مجموعة التعليمات (ISA) النهائية المُعدَّة للتنفيذ على العتاد.
%%writefile --append ./source_files/template_hamiltonian_simulation.py
print("estimator_options =", json.dumps(estimator_options, indent=4))
Appending to ./source_files/template_hamiltonian_simulation.py
التحقق من المدخلات
جانب مهم من ضمان إمكانية إعادة استخدام القالب عبر نطاق من المدخلات هو التحقق من المدخلات. الكود التالي مثال على التحقق من أن دقة الإيقاف (stopping fidelity) أثناء AQC-Tensor قد حُدِّدت بشكل مناسب، وإن لم تكن كذلك، يُعيد رسالة خطأ مفيدة توضح كيفية إصلاح الخطأ.
%%writefile --append ./source_files/template_hamiltonian_simulation.py
# Perform parameter validation
if not 0.0 < aqc_stopping_fidelity <= 1.0:
raise ValueError(
f"Invalid stopping fidelity: {aqc_stopping_fidelity}. It must be a positive float no greater than 1."
)
Appending to ./source_files/template_hamiltonian_simulation.py
إعداد مخرجات الدالة
أولاً، أعدَّ قاموساً لحفظ جميع مخرجات قالب الدالة. ستُضاف مفاتيح إلى هذا القاموس طوال سير العمل، ويُعاد في نهاية البرنامج.
%%writefile --append ./source_files/template_hamiltonian_simulation.py
output = {}
Appending to ./source_files/template_hamiltonian_simulation.py
تعيين المسألة ومعالجة الدائرة مسبقاً باستخدام AQC
يحدث تحسين AQC-Tensor في الخطوة 1 من نمط Qiskit. أولاً، تُنشأ حالة هدف. في هذا المثال، تُبنى من دائرة هدف تُطوِّر نفس الهاميلتونيان للفترة الزمنية نفسها مثل الجزء الخاص بـ AQC. بعد ذلك، يُولَّد ansatz من دائرة مكافئة لكن بخطوات Trotter أقل. في الجزء الرئيسي من خوارزمية AQC، يُقرَّب ذلك الـ ansatz تكرارياً من الحالة الهدف. أخيراً، يُدمج الناتج مع بقية خطوات Trotter اللازمة للوصول إلى وقت التطور المطلوب.
لاحظ الأمثلة الإضافية للتسجيل المدمجة في الكود التالي.
%%writefile --append ./source_files/template_hamiltonian_simulation.py
import os
os.environ["NUMBA_CACHE_DIR"] = "/data"
import datetime
import quimb.tensor
from scipy.optimize import OptimizeResult, minimize
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit
from qiskit_addon_aqc_tensor.ansatz_generation import (
generate_ansatz_from_circuit,
AnsatzBlock,
)
from qiskit_addon_aqc_tensor.simulation import (
tensornetwork_from_circuit,
compute_overlap,
)
from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator
from qiskit_addon_aqc_tensor.objective import OneMinusFidelity
print("Hamiltonian:", hamiltonian)
print("Observable:", observable)
simulator_settings = QuimbSimulator(quimb.tensor.CircuitMPS, autodiff_backend="jax")
# Construct the AQC target circuit
aqc_target_circuit = initial_state.copy()
if aqc_evolution_time:
aqc_target_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)
# Construct matrix-product state representation of the AQC target state
aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)
print("Target MPS maximum bond dimension:", aqc_target_mps.psi.max_bond())
output["target_bond_dimension"] = aqc_target_mps.psi.max_bond()
# Generate an ansatz and initial parameters from a Trotter circuit with fewer steps
aqc_good_circuit = initial_state.copy()
if aqc_evolution_time:
aqc_good_circuit.compose(
generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
time=aqc_evolution_time,
),
inplace=True,
)
aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(aqc_good_circuit)
print("Number of AQC parameters:", len(aqc_initial_parameters))
output["num_aqc_parameters"] = len(aqc_initial_parameters)
# Calculate the fidelity of ansatz circuit vs. the target state, before optimization
good_mps = tensornetwork_from_circuit(aqc_good_circuit, simulator_settings)
starting_fidelity = abs(compute_overlap(good_mps, aqc_target_mps)) ** 2
print("Starting fidelity of AQC portion:", starting_fidelity)
output["aqc_starting_fidelity"] = starting_fidelity
# Optimize the ansatz parameters by using MPS calculations
def callback(intermediate_result: OptimizeResult):
fidelity = 1 - intermediate_result.fun
print(f"{datetime.datetime.now()} Intermediate result: Fidelity {fidelity:.8f}")
if intermediate_result.fun < stopping_point:
raise StopIteration
objective = OneMinusFidelity(aqc_target_mps, aqc_ansatz, simulator_settings)
stopping_point = 1.0 - aqc_stopping_fidelity
result = minimize(
objective,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": aqc_max_iterations},
callback=callback,
)
if result.status not in (
0,
1,
99,
): # 0 => success; 1 => max iterations reached; 99 => early termination via StopIteration
raise RuntimeError(
f"Optimization failed: {result.message} (status={result.status})"
)
print(f"Done after {result.nit} iterations.")
output["num_iterations"] = result.nit
aqc_final_parameters = result.x
output["aqc_final_parameters"] = list(aqc_final_parameters)
# Construct an optimized circuit for initial portion of time evolution
aqc_final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
# Calculate fidelity after optimization
aqc_final_mps = tensornetwork_from_circuit(aqc_final_circuit, simulator_settings)
aqc_fidelity = abs(compute_overlap(aqc_final_mps, aqc_target_mps)) ** 2
print("Fidelity of AQC portion:", aqc_fidelity)
output["aqc_fidelity"] = aqc_fidelity
# Construct final circuit, with remainder of time evolution
final_circuit = aqc_final_circuit.copy()
if remainder_evolution_time:
remainder_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=remainder_num_trotter_steps),
time=remainder_evolution_time,
)
final_circuit.compose(remainder_circuit, inplace=True)
Appending to ./source_files/template_hamiltonian_simulation.py