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

تقليل عمق الدائرة باستخدام إضافة Qiskit للـ AQC-Tensor

في هذا الدفتر، سنعمل عبر خطوات نمط Qiskit مع استخدام التجميع الكمي التقريبي بشبكات الموترات (AQC-Tensor) لتحقيق عمق دائرة أقل مما يُحتاج إليه عادةً لأداء تطور Trotter.

هذه هي الخطوات التي سنتبعها:

  • الخطوة 1: التعيين إلى مسألة كمية
    • تهيئة هاميلتونيان مسألتنا والعنصر/العناصر القابلة للرصد
    • توليد حالة شبكة موترات هدف للجزء الأولي من الدائرة
    • توليد دائرة ذات عمق منخفض تقارب الجزء المضغوط
    • توليد نمط أولي عام من تلك الدائرة
    • تحسين المعاملات لتقريب النمط الأولي قدر الإمكان من الهدف
    • إضافة خطوات Trotter اللاحقة إلى النمط الأولي المُحسَّن
  • الخطوة 2: التحسين للعتاد المستهدف
    • نقل الدائرة (Transpile) للعتاد
  • الخطوة 3: تنفيذ التجارب
    • استخدام Backend وهمي للتبسيط
  • الخطوة 4: إعادة بناء النتائج
    • غير مطلوب؛ بدلاً من ذلك، نخرج العنصر القابل للرصد المقيس

الخطوة 1: التعيين إلى دائرة كمية ومؤثر

إعداد هاميلتوني نموذجي وعنصر قابل للرصد

في هذا الدفتر، نستخدم نموذج Ising على دائرة من 10 مواقع:

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

حيث تعني شروط الحدود الدورية أنه عند i=10i=10 نحصل على i+1=111i+1=11\rightarrow1، وJJ هي قوة الاقتران بين موقعين وhh هو المجال المغناطيسي الخارجي.

# Added by doQumentation — required packages for this notebook
!pip install -q qiskit qiskit-addon-aqc-tensor qiskit-addon-utils qiskit-ibm-runtime quimb scipy
from qiskit.transpiler import CouplingMap
from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian

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

# Choose a 10-qubit circle on this coupling map
reduced_coupling_map = coupling_map.reduce([0, 13, 1, 14, 10, 16, 4, 15, 3, 9])

# Get a qubit operator describing the Ising field model
hamiltonian = generate_xyz_hamiltonian(
reduced_coupling_map,
coupling_constants=(0.0, 0.0, 1.0),
ext_magnetic_field=(0.4, 0.0, 0.0),
)

العنصر القابل للرصد الذي سنقيسه هو المغنطة الكلية.

from qiskit.quantum_info import SparsePauliOp

L = reduced_coupling_map.size()
observable = SparsePauliOp.from_sparse_list([("Z", [i], 1 / L / 2) for i in range(L)], num_qubits=L)

تحديد مقدار تطور الزمن الذي يمكن محاكاته كلاسيكيًا

هدفنا الإجمالي هو محاكاة تطور الزمن للهاميلتوني النموذجي أعلاه. نفعل ذلك عبر تطور Trotter، الذي نقسمه إلى جزأين:

  1. جزء أولي قابل للمحاكاة بحالات حاصل الضرب المصفوفي (MPS). سنقوم بـ"تجميع" هذا الجزء باستخدام AQC كما هو مقدَّم في https://arxiv.org/abs/2301.08609.
  2. جزء لاحق من الدائرة سيُنفَّذ على العتاد. نخطط لاستخدام AQC-Tensor لضغط دائرة تطور الزمن حتى الزمن t=4t=4، ثم التطور باستخدام خطوات Trotter العادية حتى t=5t=5.

توليد الدوائر قبل الانقسام وبعده

بعد أن اخترنا الانقسام عند t=4t=4، سنولّد دائرتين:

  1. دائرة "هدف" لجزء AQC من التطور، من ti=0t_i=0 إلى tf=4t_f=4. نظرًا لأن هذه تُحاكى بمحاكي شبكة موترات، فإن عدد الطبقات يؤثر على وقت التنفيذ بعامل ثابت فحسب، لذا قد نستخدم عددًا سخيًا من الطبقات لتقليل خطأ Trotter.
from qiskit.synthesis import SuzukiTrotter
from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit

aqc_evolution_time = 4.0
aqc_target_num_trotter_steps = 45

aqc_target_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_target_num_trotter_steps),
time=aqc_evolution_time,
)
  1. دائرة تطور لاحقة، تتطور من ti=4t_i=4 إلى tf=5t_f=5. نظرًا لتشغيلها على عتاد كمي، من المرغوب استخدام أقل عدد ممكن من طبقات Trotter.
subsequent_evolution_time = 1.0
subsequent_num_trotter_steps = 5

subsequent_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=subsequent_num_trotter_steps),
time=subsequent_evolution_time,
)

لأغراض المقارنة لاحقًا، لنولّد أيضًا دائرة ثالثة: دائرة تتطور لـaqc_evolution_time ولكن لها نفس زمن التطور لكل خطوة Trotter كالدائرة اللاحقة. هذه هي الدائرة التي كنا سنعمل بها لو لم نستخدم عددًا سخيًا من خطوات Trotter للدائرة الهدف. سنشير إليها بـ_دائرة المقارنة_.

aqc_comparison_num_trotter_steps = int(
subsequent_num_trotter_steps / subsequent_evolution_time * aqc_evolution_time
)
aqc_comparison_num_trotter_steps
20
comparison_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_comparison_num_trotter_steps),
time=aqc_evolution_time,
)

توليد نمط أولي ومعاملات ابتدائية من دائرة Trotter بخطوات أقل

أولاً، نبني دائرة "جيدة" لها نفس زمن التطور كالدائرة الهدف، ولكن بخطوات Trotter أقل (وبالتالي طبقات أقل).

ثم نمرر هذه الدائرة "الجيدة" إلى دالة generate_ansatz_from_circuit في AQC-Tensor. تحلل هذه الدالة ترابطية Qubitين في الدائرة وتُرجع شيئين:

  1. دائرة نمط أولي عامة مُعلْمَة بنفس ترابطية Qubitين كدائرة الدخل؛ و
  2. معاملات تُنتج دائرة الدخل (الجيدة) عند تعويضها في النمط الأولي.

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

from qiskit_addon_aqc_tensor import generate_ansatz_from_circuit

aqc_ansatz_num_trotter_steps = 5

aqc_good_circuit = generate_time_evolution_circuit(
hamiltonian,
synthesis=SuzukiTrotter(reps=aqc_ansatz_num_trotter_steps),
time=aqc_evolution_time,
)

aqc_ansatz, aqc_initial_parameters = generate_ansatz_from_circuit(
aqc_good_circuit, qubits_initially_zero=True
)
aqc_ansatz.draw("mpl", fold=-1)

Quantum circuit diagram

print(f"Comparison circuit: depth {comparison_circuit.depth()}")
print(f"Target circuit: depth {aqc_target_circuit.depth()}")
print(f"Ansatz circuit: depth {aqc_ansatz.depth()}, with {len(aqc_initial_parameters)} parameters")
Comparison circuit: depth 120
Target circuit: depth 270
Ansatz circuit: depth 23, with 515 parameters

اختيار إعدادات محاكاة شبكة الموترات

هنا، نستخدم محاكي شبكة الموترات المستند إلى quimb. في هذا المثال، نستخدم محاكي حالة حاصل الضرب المصفوفي (MPS) من quimb، ونستخدم JAX للتفاضل التلقائي. راجع توثيق API لمزيد من المعلومات حول استخدام محاكي quimb.

from functools import partial

import quimb.tensor

from qiskit_addon_aqc_tensor.simulation.quimb import QuimbSimulator

simulator_settings = QuimbSimulator(
partial(quimb.tensor.CircuitMPS, max_bond=100, cutoff=1e-8),
autodiff_backend="jax",
)

بناء تمثيل حالة حاصل الضرب المصفوفي لحالة هدف AQC

بعد ذلك، نبني تمثيل حاصل الضرب المصفوفي للحالة المراد تقريبها بواسطة AQC.

from qiskit_addon_aqc_tensor.simulation import tensornetwork_from_circuit

aqc_target_mps = tensornetwork_from_circuit(aqc_target_circuit, simulator_settings)

لاحظ أنه نظرًا لاختيارنا عددًا سخيًا من خطوات Trotter للحالة الهدف، فهي في الواقع تحتوي على خطأ Trotter أقل من دائرة المقارنة. يمكننا حساب الأمانة (ψ1ψ22| \langle \psi_1 | \psi_2 \rangle |^2) للحالة المُعدَّة بدائرة المقارنة مقابل الحالة الهدف:

from qiskit_addon_aqc_tensor.simulation import compute_overlap

comparison_mps = tensornetwork_from_circuit(comparison_circuit, simulator_settings)
comparison_fidelity = abs(compute_overlap(comparison_mps, aqc_target_mps)) ** 2
comparison_fidelity
0.9996761790297157

تحسين معاملات النمط الأولي باستخدام حسابات MPS

هنا، نقلل دالة التكلفة الأبسط الممكنة، MaximizeStateFidelity، باستخدام محسِّن L-BFGS من scipy.

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

from scipy.optimize import OptimizeResult, minimize

from qiskit_addon_aqc_tensor.objective import MaximizeStateFidelity

objective = MaximizeStateFidelity(aqc_target_mps, aqc_ansatz, simulator_settings)

stopping_point = 1 - comparison_fidelity

def callback(intermediate_result: OptimizeResult):
print(f"Intermediate result: Fidelity {1 - intermediate_result.fun:.8}")
if intermediate_result.fun < stopping_point:
# Good enough for now
raise StopIteration

result = minimize(
objective.loss_function,
aqc_initial_parameters,
method="L-BFGS-B",
jac=True,
options={"maxiter": 100},
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.")
aqc_final_parameters = result.x
Intermediate result: Fidelity 0.95080335
Intermediate result: Fidelity 0.98408927
Intermediate result: Fidelity 0.99140876
Intermediate result: Fidelity 0.9951876
Intermediate result: Fidelity 0.99563147
Intermediate result: Fidelity 0.99646297
Intermediate result: Fidelity 0.99679298
Intermediate result: Fidelity 0.99715793
Intermediate result: Fidelity 0.99756604
Intermediate result: Fidelity 0.99804283
Intermediate result: Fidelity 0.99832283
Intermediate result: Fidelity 0.99856583
Intermediate result: Fidelity 0.99868698
Intermediate result: Fidelity 0.998867
Intermediate result: Fidelity 0.99902237
Intermediate result: Fidelity 0.99912174
Intermediate result: Fidelity 0.99919705
Intermediate result: Fidelity 0.99926724
Intermediate result: Fidelity 0.99938605
Intermediate result: Fidelity 0.99951297
Intermediate result: Fidelity 0.99956172
Intermediate result: Fidelity 0.99962274
Intermediate result: Fidelity 0.99963919
Intermediate result: Fidelity 0.99967423
Intermediate result: Fidelity 0.9997101
Done after 25 iterations.

بناء الدائرة النهائية لتمريرها إلى Transpiler

final_circuit = aqc_ansatz.assign_parameters(aqc_final_parameters)
final_circuit.compose(subsequent_circuit, inplace=True)
final_circuit.draw("mpl", fold=-1)

Quantum circuit diagram

الخطوة 2: نقل الدائرة للتنفيذ على العتاد المستهدف

في الخطوة 2 من نمط Qiskit، ننقل هذه الدائرة وأي عناصر قابلة للرصد مرغوبة للتنفيذ على جهاز مستهدف. هنا نستخدم Backend وهميًا يوفره qiskit-ibm-runtime.

from qiskit import transpile
from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2

backend = FakeMelbourneV2()

isa_circuit = transpile(final_circuit, backend)
isa_observable = observable.apply_layout(isa_circuit.layout)

يمكن بعد ذلك إرسال دائرة ISA الناتجة للتنفيذ على Backend (الخطوة 3 من نمط Qiskit).

الخطوة 3: التنفيذ على العتاد الكمي

from qiskit_ibm_runtime import EstimatorV2 as Estimator

estimator = Estimator(backend)
job = estimator.run([(isa_circuit, isa_observable)])
pub_result = job.result()[0]

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

إعادة البناء ليست ضرورية في حالتنا. يمكننا الاطلاع مباشرةً على النتيجة.

pub_result.data.evs[()]
np.float64(0.047998046875000006)