حسّن تحضير محدّد سلاتر
المفاهيم في هذا الدليل متاحة حاليًا في واجهة Python فقط. ستتوفر وظائف مكافئة في واجهة C في إصدار مستقبلي.
تعلن بوابة InitializeModes عن إشغال أنماط مرجعي؛ وتُدير بوابة OrbitalRotation أساس الجسيم المفرد. عند وضعهما متتاليتين، تُحضّر البوابتان محدّد سلاتر؛ تبدأ الأنماط في المحدّد المرجعي ثم تُدار إلى المدارات الهدف. تلتقط بوابة PrepareSlaterDeterminant هذا التركيب، ولأنها تحمل الإشغال المرجعي، يمكن عند ترجمتها استخدام givens_decomposition_slater() المستطيل، الذي يحقّق المدارات المشغولة فقط، بدل التفكيك المربع الكامل الذي تتطلبه OrbitalRotation وحدها. وهذا تركيب أرخص بصرامة (انظر الفائدة في نهاية هذا الدليل).
يكتشف مُمرِّر المُترجِم MergeSlaterDeterminantPreparation نمط InitializeModes متبوعًا بـ OrbitalRotation في FermionicCircuit ويعيد كتابته إلى بوابات PrepareSlaterDeterminant، فتلتقط مرحلة تركيب لاحقة التفكيك المخفَّض تلقائيًا. وتحت المحاكاة، لا تغيّر إعادة الكتابة الحالة: فـ PrepareSlaterDeterminant هي تحقّق ثم دوّر (تتحقق من الإشغال المرجعي ثم تطبّق الدوران، تمامًا كما تفعل البوابتان المنفصلتان)، فيتيح الدمج التركيب الأرخص دون تغيير الحالة المحضَّرة.
في هذا الدليل، تُرسم كل دائرة قبل المُمرِّر وبعده جنبًا إلى جنب: المُدخَل على اليسار ونتيجة تشغيل المُمرِّر عليه على اليمين. تقوم دالتان مساعدتان بذلك. تشغّل merge المُمرِّر على نسخة من الدائرة (عبر FermionicDAGCircuit) وتحوّل النتيجة مجددًا إلى FermionicCircuit قابلة للرسم، وترسم draw_merge النصفين في شكل واحد قبل/بعد:
>>> import matplotlib.pyplot as plt
>>> import numpy as np
>>>
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import InitializeModes, OrbitalRotation
>>> from qiskit_fermions.transpiler import FermionicCircuitToDAG
>>> from qiskit_fermions.transpiler.converters import FermionicDAGToCircuit
>>> from qiskit_fermions.transpiler.passes import MergeSlaterDeterminantPreparation
>>>
>>> def merge(circuit):
... dag = FermionicCircuitToDAG().run(circuit)
... merged = MergeSlaterDeterminantPreparation().run(dag)
... return FermionicDAGToCircuit().run(merged)
>>>
>>> def natural_size(circuit):
... # the figure size Qiskit picks for a circuit on its own (discard the probe figure)
... probe = circuit.draw("mpl")
... size = probe.get_size_inches()
... plt.close(probe)
... return size
>>>
>>> def draw_merge(circuit):
... merged = merge(circuit)
... # let Qiskit size each half naturally, then lay them out side by side
... (bw, bh), (aw, ah) = natural_size(circuit), natural_size(merged)
... fig, (before, after) = plt.subplots(1, 2, figsize=(bw + aw, max(bh, ah)))
... circuit.draw("mpl", ax=before)
... merged.draw("mpl", ax=after)
... before.set_title("before")
... after.set_title("after")
... return fig
ما الذي يُدمَج
يتعرّف المُمرِّر على ثلاثة أنماط، كلها مبنية على اصطلاح أنماط السبين الكتلي الذي تستخدمه InitializeModes وOrbitalRotation. لنظام بـ norb مدارًا مكانيًا، الأنماط 0..norb هي قطاع سبين ألفا، وnorb..2*norb هي قطاع سبين بيتا. يتم تحضير محدّد سلاتر لكل قطاع سبين لأن لكل قطاع شكله الخاص (إلكترونات، مدارات).
السجل الكامل (أو بلا سبين)
أبسط نمط هو استخدام InitializeModes متبوعًا مباشرة بـ OrbitalRotation على الأنماط نفسها. هذه هي حالة بلا سبين (محدّد واحد على كل الأنماط) وكذلك دائرة ذات سبين يمتد دورانها على القطاعين معًا. تندمج البوابتان في PrepareSlaterDeterminant واحدة:
>>> circuit = FermionicCircuit(4)
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(4)), circuit.modes)
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
لكل قطاع سبين
هذا الشكل نفسه لكنه مقيّد بنصف سبين واحد. هنا يندمج زوجان مستقلان من تهيئة-ثم-دوران، واحد لكل قطاع، كلٌّ على حدة في PrepareSlaterDeterminant:
>>> circuit = FermionicCircuit(6) # norb = 3: alpha modes 0..3, beta modes 3..6
>>> circuit.append(InitializeModes([1, 1, 0]), circuit.modes[:3])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> circuit.append(InitializeModes([1, 0, 0]), circuit.modes[3:])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[3:])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
تهيئة شاملة، ودورانات لكل سبين
يتضمن هذا InitializeModes واحدة للسجل الكامل (2 * norb) متبوعة بفعلَي OrbitalRotation اثنين، واحد على كل نصف سبين متجاور. يقسم المُمرِّر الإشغال الشامل عند حدّ القطاع ويُصدر بوابتَي PrepareSlaterDeterminant اثنتين. قد يظهر الدورانان بأي ترتيب.
هذا هو الشكل الناتج عن سير عمل العنقود الأحادي المحلي جاسترو (LUCJ) (انظر دليل LUCJ)؛ يضع المستخدم InitializeModes (عادةً المرجع هارتري-فوك باستخدام from_hartree_fock()) في مقدمة الدائرة ويُلحق نموذج UCJ الذي يتبع أول دورانَين مداريَّين لكل سبين فيه التهيئة مباشرة بعد تفكيك النموذج.
>>> circuit = FermionicCircuit(6) # norb = 3, nelec = (2, 1)
>>> circuit.append(InitializeModes.from_hartree_fock(3, (2, 1)), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[3:])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
يعمل هذا النمط أيضًا عندما يُدار نصف سبين واحد فقط ببوابة تلي التهيئة مباشرة، وهو الشكل الناتج حين يقع دوران مداري لقطاع سبين واحد في مقدمة الدائرة. يصبح النصف المُدار PrepareSlaterDeterminant مع دورانه، ويُحضَّر النصف الآخر بدوران محايد. يُركَّب التحضير المحايد إلى بوابات X المرجعية التي كانت InitializeModes ستُصدرها لذلك النصف على أي حال، فلا تكلّف التعبئة بوابات إضافية مع إتاحة تركيب سلاتر المخفَّض على النصف المُدار:
>>> circuit = FermionicCircuit(6)
>>> circuit.append(InitializeModes.from_hartree_fock(3, (2, 1)), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
ما يبقى دون مساس
المُمرِّر محافظ عمدًا. لا يدمج إلا عندما تتركب البوابتان فعلًا في تحضير محدّد سلاتر، ويمرّر كل شيء آخر دون تغيير. يُفهم «المتبوع مباشرة» عبر مخطط تدفق البيانات للدائرة. لا تندمج OrbitalRotation إلا عندما تكون InitializeModes سابقتها الوحيدة عبر كل أنماطها. في الأشكال أدناه، قبل وبعد متطابقان: لم يندمج شيء.
عملية بينهما
إذا لمست أي بوابة الأنماط بين التهيئة والدوران، فلن تتركبا بعد ذلك في تحضير خالص، فلا يُدمج شيء:
>>> from qiskit_fermions.circuit.library import Evolution
>>> from qiskit_fermions.operators import FermionOperator, ann, cre
>>>
>>> circuit = FermionicCircuit(4)
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> number_op = FermionOperator.from_dict({(cre(0), ann(0)): 1.0})
>>> circuit.append(Evolution(4, number_op, time=0.5), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(4)), circuit.modes)
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
دوران جزئي ليس نصف سبين
الدوران الذي يغطي بالضبط نصف سبين متجاور واحدًا من تهيئة السجل الكامل يندمج (انظر القسم السابق). لكن الدوران على أي مجموعة أنماط جزئية أخرى (أنماط أقل من التهيئة وليست أحد نصفَي السبين، مثل نطاق فرعي يعبر حدّ القطاع) ليس تحضيرًا لكل قطاع. سيُسقط دمجه بصمت دور التهيئة على الأنماط التي يتركها، لذا يُبقي المُمرِّر البوابتين كلتيهما في مكانهما:
>>> circuit = FermionicCircuit(4) # norb = 2: the only spin halves are modes [0, 1] and [2, 3]
>>> circuit.append(InitializeModes([1, 1, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(np.eye(3)), circuit.modes[:3])
>>> draw_merge(circuit)
<Figure size ... with 2 Axes>
لماذا هذا مهم: فائدة التركيب
الدمج مجدٍ لأن PrepareSlaterDeterminant تُركَّب إلى بوابات أقل بكثير من OrbitalRotation التي تمتصها. يحتاج محدّد من إلكترون على مدارًا إلى دوران ثنائي الكيوبت على الأكثر ولا بوابات طور قطرية، مقابل دوران زائد طور للدوران المداري المربع الكامل.
يشغّل مدير التمريرات المسبق الضبط لجوردان-فيغنر MergeSlaterDeterminantPreparation في مرحلة التحسين، فيحدث هذا الاختزال تلقائيًا. ترجِم تحضير إلكترونين على ستة مدارات وصولًا إلى QuantumCircuit وعُدّ بواباته:
يشغّل الإعداد المسبق MergeOrbitalRotations مباشرة قبل MergeSlaterDeterminantPreparation. وهذا مهم حين تتبع التهيئةَ سلسلة من بوابات OrbitalRotation المتتالية. يطوي المُمرِّر الأسبق السلسلة أولًا في دوران واحد، ثم تدمجه MergeSlaterDeterminantPreparation مع التهيئة في PrepareSlaterDeterminant واحدة. بدون ذلك الترتيب، لن يُمتص إلا أول دوران في السلسلة وسيُركَّب الباقي بالتفكيك المربع الكامل.
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> # a reproducible random 6 x 6 orbital rotation
>>> rng = np.random.default_rng(1)
>>> z = rng.standard_normal((6, 6)) + 1j * rng.standard_normal((6, 6))
>>> q, r = np.linalg.qr(z)
>>> rotation = q * (np.diagonal(r) / np.abs(np.diagonal(r)))
>>>
>>> circuit = FermionicCircuit(6)
>>> circuit.append(InitializeModes([1, 1, 0, 0, 0, 0]), circuit.modes)
>>> circuit.append(OrbitalRotation(rotation), circuit.modes)
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> prepared = pm.run(circuit)
>>> print(dict(sorted(prepared.count_ops().items())))
{'x': 2, 'xx_plus_yy': 8}
>>> prepared.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
قارن ذلك بتركيب الدوران المداري نفسه وحده. بدون تهيئة سابقة لا يوجد ما يُدمَج، فيعود الإعداد المسبق إلى التفكيك المربع الكامل، وهو أعمق ويحمل بوابات طور قطرية:
>>> reference = FermionicCircuit(6)
>>> reference.append(OrbitalRotation(rotation), reference.modes)
>>> reference_prepared = pm.run(reference)
>>> print(dict(sorted(reference_prepared.count_ops().items())))
{'rz': 6, 'xx_plus_yy': 15}
>>> reference_prepared.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

يُسقط التفكيك المخفَّض كل بوابات الطور الست ويكاد يخفض عدد الدورانات ثنائية الكيوبت إلى النصف. المحدّد المحضَّر صحيح حتى طور شامل (غير مهم فيزيائيًا لتحضير الحالة)، وهو ما كانت بوابات الطور ستثبّته.
طريقتان لتفكيك البوابة
يمكن تفكيك PrepareSlaterDeterminant بطريقتين مختلفتين، وهما غير قابلتين للتبادل:
decompose()
توسّع البوابة إلى تعريفها: InitializeModes للمرجع متبوعة بـ OrbitalRotation الكاملة. وهذا مكافئ للبوابة كـمُشغّل، لأي حالة مُدخَلة.
PrepareSlaterDeterminant.synthesis، المُطبَّق بواسطة F2FSynthesis
تستبدل البوابة بالتفكيك المخفَّض لهذا الدليل. وهذا مكافئ للبوابة كـتحضير حالة فقط: فهو ينتج المحدّد نفسه، حتى طور شامل، عندما تبدأ الأنماط من الفراغ. وهو أرخص تحديدًا لأنه يفترض ذلك.
استخدم التركيب عندما تحضّر البوابة حالة على الفراغ، وهذا ما صُمّمت له البوابة وما يفعله المُترجِم نيابةً عنك. واستخدم decompose() عندما يجب أن تبقى البوابة تعمل كمُشغّل، مثلًا لأن بوابات أخرى تسبقها على الأنماط نفسها، أو عندما تريد فحص البوابات البسيطة أو محاكاتها. لا يمكن لمُمرِّر F2FSynthesis معرفة أي حالة ينظر إليها، فيقع على عاتق الدائرة التي تعطيه إياها أن تحضّر المحدّد من الفراغ.
لا تحتاج محاكاة دائرة باستخدام ffsim إلى أي منهما: فكل بوابة تطبّق وحدويتها الكاملة، فتُحاكى الدائرة التي تحوي PrepareSlaterDeterminant كما هي، وتبقى على المسار السريع لبوابات OrbitalRotation المحيطة بها. كما أن تفكيك بوابة سلاتر وحدها بـ circuit.decompose("PrepareSlaterDeterminant") يترك تلك الدورانات كاملة.
PrepareSlaterDeterminant، InitializeModes، OrbitalRotation، MergeOrbitalRotations، F2FSynthesis، FermionicSlaterGivensDecomposition، ودليل LUCJ لسير العمل الذي ينتج نمط التهيئة الشاملة.
