تحويل ansatz من نوع LUCJ (transpile)
المفاهيم في هذا الدليل متاحة فقط في واجهة Python البرمجية.
ansatz عنقود جاستراو الأحادي المحلي (LUCJ) هو معاملة مضغوطة وفعّالة للعتاد للدالة الموجية الإلكترونية المترابطة. وهو عضو في عائلة عنقود جاستراو الأحادي (UCJ) الأعم، ويأخذ الصيغة
حيث حالة مرجعية (عادةً هارتري-فوك)، وكل دوران مداري، وكل مؤثر كولوم قطري
مع مؤثر العدد على المدار المكاني ذي السبين .
تبني ffsim مؤثر ansatz؛ وتحوّله هذه الحزمة إلى دارة ثم تنزل بها إلى مستوى الكيوبتات. يتناول هذا الدليل النصف الثاني من هذه الجملة. سبب القيام بهذه الرحلة هو التنزيل نفسه: بوابات Qiskit الخاصة بـ ffsim مرتبطة بتحويل جوردان-فيغنر تحديدًا، بينما لا تفترض FermionicCircuit أي ترميز من فرميون إلى كيوبت، لذا يمكن تركيب ansatz نفسه عبر أي ترميز يناسب الجهاز المستهدف.
وهكذا يصل مؤثر ansatz إلى هنا جاهزًا، وكل ما يلي هو ما يحدث له بعد ذلك: إلى ماذا تتفكك البوابة، وكيف تُنزَّل عبر جوردان-فيغنر، وكيف تُطابَق مع خريطة اقتران الجهاز، وما الذي سيظل ناقصًا في تنزيل غير جوردان-فيغنر. لبناء ansatz من نوع UCJ أو ضبط معاملاته أو محاكاته أصلاً، راجع أدلة ffsim الخاصة.
دليل علاقة ffsim لمعرفة كيف تتقاسم الحزمتان العمل، ودليل التحويل (transpilation) لمراحل المسار المستخدمة أدناه.
1. الحصول على مؤثر LUCJ من ffsim
تُهيَّأ ansatz من سعات حساب العنقود المقترن للأحاديات والثنائيات (CCSD). شغّل هارتري-فوك المقيَّد ثم CCSD لجزيء هيدروجين في أساس 6-31g، باستخدام PySCF للكيمياء الكمومية، ثم مرّر السعات إلى from_t_amplitudes() في ffsim. تجري هذه الدالة تحليلاً مضاعفًا لسعات للحصول على مصفوفات كولوم القطرية والدورانات المدارية لكل طبقة، وتشتق دورانًا مداريًا نهائيًا اختياريًا من سعات . عدد التكرارات هو ما ينتجه ذلك التحليل؛ وn_reps يقتطعه، مقايضًا الدقة بدارة أقل عمقًا.
>>> import ffsim
>>> import pyscf
>>> import pyscf.cc
>>>
>>> # build the molecule and run Hartree-Fock
>>> mol = pyscf.gto.Mole()
>>> mol.build(
... atom=[["H", (0, 0, 0)], ["H", (0, 0, 0.74)]],
... basis="6-31g",
... symmetry="Dooh",
... verbose=0,
... )
<pyscf.gto.mole.Mole object at ...>
>>> scf = pyscf.scf.RHF(mol).run()
>>>
>>> norb = scf.mo_coeff.shape[1]
>>> nelec = (mol.nelec[0], mol.nelec[1])
>>>
>>> # run CCSD for the t-amplitudes, then factorize them into an ansatz operator
>>> ccsd = pyscf.cc.CCSD(scf).run()
>>> t1, t2 = ccsd.t1, ccsd.t2
>>>
>>> ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(t2, t1=t1, n_reps=2)
هنا تتوقف ansatz عن كونها شأنًا يخص ffsim. تقدّم ffsim خيارات هنا لا ترصدها هذه الحزمة ولا تحتاج إلى معرفتها، مثل التحليل المضغوط المحسَّن تغايُريًّا خلف optimize=True، أو تعبئة متجه المعاملات التي يقودها مُحسِّن تغايُري. جميعها تنتج النوع نفسه من المؤثرات، وكل ما يلي يعمل دون تغيير على أي منها.
2. تحويل المؤثر إلى دارة فرميونية
تغلّف بوابة UCJ المؤثر وتعبّر عنه كدارة فوق الأنماط الفرميونية. البوابة أحادية بحتة ولا تحمل مرجعًا خاصًا بها، لذا أضف في البداية بوابة InitializeModes (مبنية بـ from_hartree_fock()) لتوفير مرجع هارتري-فوك الذي تُطبَّق عليه ansatz.
>>> from qiskit_fermions.circuit import FermionicCircuit
>>> from qiskit_fermions.circuit.library import InitializeModes, UCJ
>>>
>>> ansatz = UCJ(ucj_op)
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(InitializeModes.from_hartree_fock(norb, nelec), circuit.modes)
>>> circuit.append(ansatz, circuit.modes)
يكشف تفكيك الدارة عن تشريحها، وهذا هو التمثيل الذي يعمل عليه بقية الدليل. تحضّر بوابة InitializeModes المحدد المرجعي، وتسهم كل طبقة من ansatz بـ OrbitalRotation ، ثم (وهو Evolution لمؤثر كولوم القطري )، ثم ، مع OrbitalRotation نهائي في النهاية. تعمل الدورانات المدارية لكل قطاع سبين، لذا يوضع كل منها على أنماط ألفا 0..norb وأنماط بيتا norb..2*norb بشكل مستقل.
>>> circuit.decompose().draw("mpl", fold=-1)
<Figure size ... with 1 Axes>

كل بوابة في ذلك التفكيك ما تزال فرميونية: OrbitalRotation وEvolution معرَّفتان على الأنماط، ولم يظهر بعدُ أي كيوبت أو مؤثر باولي. وهذا ما يُبقي الترميز مفتوحًا، وهي الحالة التي تبقى عليها ansatz حتى تختار مرحلة التركيب ترميزًا.
تنتهي كل طبقة بـ وتبدأ التالية بـ ، لذا يمكن دمج بوابات OrbitalRotation المتجاورة في دوران واحد. تمرير تحويل (transpilation) ينفّذ هذا الدمج تطوير مستقبلي مخطط له.
3. اختيار ترميز من فرميون إلى كيوبت
الدارة ما تزال فرميونية، وهذه هي النقطة التي تثبت فيها هذه الحزمة قيمتها في سير العمل. لا تفترض FermionicCircuit شيئًا عن كيفية تحوّل الأنماط إلى كيوبتات، لذا فالترميز خيار في مرحلة التركيب وليس شيئًا مدمجًا في ansatz. وهذا ما تضيفه هذه الحزمة على بوابات Qiskit الخاصة بـ ffsim، المرتبطة بتحويل جوردان-فيغنر.
لجوردان-فيغنر وحده لن تحتاج إلى هذا المسار الجانبي: توفّر ffsim UCJOpSpinBalancedJW وأخواتها، التي تنقل مؤثر UCJ إلى دارة كيوبتات بمفردها. والسبب في تمرير ansatz عبر دارة فرميونية هو كل ما يمكن لترميز آخر أن يمنحك إياه. فالترميزات المحلية مثلاً تنفق كيوبتات إضافية لتحديد وزن باولي لكل حد، وهو ما تستخدمه أدلة مجموعات التدفق أحادية البعد وثنائية البعد لجعل عمق البوابات ثنائية الكيوبت في خطوة تروتر مستقلاً عن حجم النظام.
بالنسبة إلى ansatz من نوع UCJ تحديدًا، يجب أن تغطي مرحلة التركيب نوعين من البوابات:
-
تطور كولوم القطري.
Evolutionمحايدة أصلاً تجاه الترميز: تأخذMapperFnEvolutionSynthesisدالة محوِّل، لذا تُنزَّل طبقات عبر أي ترميز يمكنك التعبير عنه بدالة. تبيّن أدلة مجموعات التدفق كيفية كتابة واحدة. -
الدورانات المدارية وتحضير محدد سلاتر. لا
OrbitalRotationولاPrepareSlaterDeterminantالتي تندمج فيهاInitializeModesالأولى والدوران (انظر أدناه) تُحوَّل إلى كيوبتات كما هي. يفككها تمريرF2FSynthesisأولاً، في الفضاء الفرميوني، إلى بواباتEvolutionعلى نمط واحد أو نمطين لكل منها (وInitializeModesللمرجع في حالة التحضير)، لذا فأي ترميز يستطيع تحويلEvolutionيتعامل معها. في جوردان-فيغنر، تصبح العوامل بين الأنماط المتجاورة كائناتXXPlusYYGateعند مرحلة الكيوبت. -
التهيئة. الإضافة الخاصة بـ
InitializeModesليست معاملَة بترميز: فهي توثّق افتراضها لترميز في أساس الإشغال مع تحويل واحد لواحد من نمط إلى كيوبت، وتُصدر البوابات مباشرةً على ذلك الأساس. يحتاج ترميز مختلف إلى إضافة خاصة به، ولا توجد واحدة حتى الآن.
كيفية تحويل (transpile) دارات UCJ تحت ترميز غير جوردان-فيغنر مسألة بحثية مفتوحة، وتوفّر هذه الحزمة الإطار الذي يمكن ضمنه تطوير مسار كهذا. يبني بقية هذا الدليل مسار المترجم (transpiler) لترميز جوردان-فيغنر، المكافئ للمسار الذي تنتجه generate_preset_jw_pass_manager()، كمثال.
4. التنزيل عبر جوردان-فيغنر
يحدث التنزيل في F2QSynthesis، التي تعمل مثل HighLevelSynthesis في Qiskit: تمرّ على التعليمات الفرميونية وتسلّم كلاً منها إلى إضافة مسجَّلة لاسم تلك البوابة. تحتفظ خاصيتها F2QSynthesis.methods بـ F2QSynthesisPlugin واحدة لكل اسم بوابة، لذا فبناء المرحلة يعني اختيار إضافة لكل بوابة فرميونية تحتويها الدارة:
>>> from qiskit_fermions.mappers.library import jordan_wigner
>>> from qiskit_fermions.transpiler.passes import F2QSynthesis
>>> from qiskit_fermions.transpiler.passes.synthesis import (
... MapperFnEvolutionSynthesis,
... TrivialOccupationInitializeModesSynthesis,
... )
>>>
>>> synthesis = F2QSynthesis()
>>> synthesis.methods["Evolution"] = MapperFnEvolutionSynthesis(jordan_wigner)
>>> synthesis.methods["InitializeModes"] = TrivialOccupationInitializeModesSynthesis()
إسناد النسخ بهذه الطريقة هو أيضًا كيف تدخل إضافة كتبتَها بنفسك إلى المسار، دون الحاجة إلى تسجيلها في أي مكان أولاً.
هنا يُحسم الترميز، بالطريقتين اللتين وصفهما القسم 3: صراحةً، بتمرير jordan_wigner() إلى MapperFnEvolutionSynthesis، وضمنيًا، بافتراض الإضافة الأخرى له أصلاً.
ثم تُدرَج تلك المرحلة في MultiStagePassManager تعمل مراحلها بترتيب ثابت:
>>> from qiskit.passmanager import MultiStagePassManager
>>> from qiskit.transpiler import generate_preset_pass_manager
>>> from qiskit.transpiler.passes.synthesis.high_level_synthesis import HLSConfig
>>>
>>> from qiskit_fermions.transpiler import (
... FermionicCircuitToDAG,
... FermionicPassManager,
... QuantumDAGToCircuit,
... )
>>> from qiskit_fermions.transpiler.passes import (
... F2FSynthesis,
... HopEvolutionSynthesis,
... MergeOrbitalRotations,
... MergeSlaterDeterminantPreparation,
... TrivialF2QLayout,
... )
>>>
>>> pm = MultiStagePassManager(
... input=FermionicCircuitToDAG(),
... optimization=FermionicPassManager(
... [
... MergeOrbitalRotations(),
... MergeSlaterDeterminantPreparation(),
... F2FSynthesis(
... {"OrbitalRotation": "Givens", "PrepareSlaterDeterminant": "Givens"}
... ),
... ]
... ),
... layout=FermionicPassManager(TrivialF2QLayout()),
... synthesis=synthesis,
... qubit=generate_preset_pass_manager(
... hls_config=HLSConfig(PauliEvolution=[HopEvolutionSynthesis(), "default"])
... ),
... output=QuantumDAGToCircuit(),
... )
تعمل مرحلة optimization قبل التركيب، على الدارة الفرميونية، وهذا ما يجعلها مفيدة: فهي تعيد كتابة البوابات وهي ما تزال فرميونية ومحايدة تجاه الترميز، لذا ينتقل التوفير إلى أي ترميز يليها. تدمج MergeOrbitalRotations كل سلسلة من بوابات OrbitalRotation المتتالية في واحدة، وهذا مهم لـ ansatz من نوع UCJ لأن كل طبقة تنتهي بـ وتبدأ التالية بـ . ثم تدمج MergeSlaterDeterminantPreparation بوابة InitializeModes الأولى والدوران الذي يليها في PrepareSlaterDeterminant واحدة، يُنزِّلها التركيب عبر تحليل سلاتر المختزل الأرخص بدلاً من دوران مربع كامل. الترتيب مهم: دمج الدورانات أولاً يكشف الدوران الواحد الذي يلي التهيئة مباشرةً، ثم يقلّصه دمج سلاتر فيها. تأتي F2FSynthesis أخيرًا، لأنها تفكك أي دوران متبقٍ، وإجراء ذلك قبلها سيترك تمريرتَي الدمج دون ما تدمجه.
المراحل المتبقية تمهيدية. تحوّل input وoutput بين الدارات وDAG، وتسند layout الأنماط الفرميونية إلى الكيوبتات (TrivialF2QLayout تربط النمط i بالكيوبت i)، وqubit هي مدير تمريرات Qiskit عادي يتولى الأمر عندما لا يتبقى شيء فرميوني.
يتطلب تشغيله خطوة تحضيرية واحدة: يجب تفكيك بوابة UCJ المركّبة إلى بواباتها الأولية أولاً، حتى ترى مرحلة optimization الدورانات والتطورات الفردية.
>>> transpiled = pm.run(circuit.decompose())
>>> print(dict(sorted(transpiled.count_ops().items())))
{'rz': 14, 'rzz': 12, 'x': 2, 'xx_plus_yy': 28}
بدون جهاز مستهدف، يُحوَّل هذا إلى 2 * norb كيوبت مع افتراض اتصال كل بكل؛ تُركَّب الدورانات المدارية إلى كائنات XXPlusYYGateوتطورات كولوم القطرية إلى كائنات RZZGateوذلك على النحو التالي:
>>> transpiled.draw("mpl", fold=-1)
<Figure size ... with 1 Axes>
هذا المسار هو بالضبط ما تجمعه generate_preset_jw_pass_manager()، لذا استخدم الإعداد المسبق عمليًا وابنِ المراحل يدويًا عندما تحتاج إلى تغيير إحداها:
>>> from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
>>>
>>> preset = generate_preset_jw_pass_manager()
>>> preset.run(circuit.decompose()).count_ops() == transpiled.count_ops()
True
5. مطابقة طوبولوجيا الجهاز
للجهاز الحقيقي خريطة اقتران كيوبتات مقيَّدة، وقد صُمِّمت ansatz من نوع LUCJ لتطابقها. تشكّل تفاعلات السبين نفسه (pairs_aa) سلسلتين خطيتين، وتشكّل تفاعلات ألفا-بيتا (pairs_ab) جسورًا بينهما. تبني generate_lucj_pass_manager() في ffsim مسار كيوبتات واعيًا بالجهاز لهذه البنية، وتعيد المجموعة الجزئية من pairs_ab التي يستطيع العتاد استيعابها فعلاً. أدرج هذا المسار في مرحلة qubit في الإعداد المسبق مع الإبقاء على تركيب الحزمة الخاص من فرميون إلى كيوبت:
>>> from ffsim.qiskit import generate_lucj_pass_manager
>>> from qiskit.providers.fake_provider import GenericBackendV2
>>> from qiskit.transpiler import CouplingMap
>>>
>>> # a heavy-hex device coupling map (any BackendV2 works, e.g. a real fake_provider backend)
>>> coupling_map = CouplingMap.from_heavy_hex(5)
>>> backend = GenericBackendV2(
... num_qubits=coupling_map.size(),
... basis_gates=["cp", "xx_plus_yy", "p", "x", "swap"],
... coupling_map=coupling_map,
... )
>>>
>>> # nearest-neighbor same-spin chain; let the pass manager choose the alpha-beta pairs
>>> pairs_aa = [(p, p + 1) for p in range(norb - 1)]
>>>
>>> pm = generate_preset_jw_pass_manager()
>>> hls_config = HLSConfig(PauliEvolution=[HopEvolutionSynthesis(), "default"])
>>> pm.qubit, allowed_pairs_ab = generate_lucj_pass_manager(
... backend,
... norb,
... "heavy-hex",
... (pairs_aa, None),
... optimization_level=3,
... seed_transpiler=0,
... hls_config=hls_config,
... )
>>>
>>> # the alpha-beta interactions the heavy-hex connectivity can implement
>>> print(allowed_pairs_ab)
[(0, 0)]
هذا التركيب هو بيت القصيد: مرحلة qubit واعية بالجهاز، بينما تبقى مرحلة التركيب من فرميون إلى كيوبت التي تسبقها خاصة بهذه الحزمة، وبالتالي قابلة للاستبدال. استبدال مرحلة qubit يُسقط ما يسجّله الإعداد المسبق هناك، وهو HopEvolutionSynthesis التي تحوّل عوامل جيفنز للدوران المداري إلى كائنات XXPlusYYGateمرِّرها عبر hls_config كما سبق، وإلا فيجب الوصول إلى أساس بوابات الجهاز بدونها.
بعد ضبط pm.qubit على المسار الواعي بالجهاز، يؤدي تشغيل مدير التمريرات إلى توزيع الدارة على كيوبتات الـ backend وتوجيهها إلى خريطة الاقتران. بالنسبة إلى ansatz غير المقيَّدة من الخطوة 1، التي ما يزال مؤثر كولوم القطري فيها يحتوي على حدود ألفا-بيتا لا يستطيع العتاد الوصول إليها مباشرةً، يجب على الموجِّه إدراج الكثير من بوابات SWAP لتجسيرها:
>>> naive = pm.run(circuit.decompose())
>>> naive.num_qubits # laid out on the full heavy-hex device register
57
>>> naive_swaps = naive.count_ops()["swap"]
>>> naive_swaps # many SWAPs to bridge the unreachable alpha-beta interactions
33
قيّد ansatz بالتفاعلات القابلة للتنفيذ على العتاد
الحل هو تغذية allowed_pairs_ab مرة أخرى في بناء ansatz، عبر الوسيط interaction_pairs في from_t_amplitudes()، بحيث يحتوي مؤثر كولوم القطري فقط على حدود ألفا-بيتا التي تستطيع خريطة الاقتران تنفيذها مباشرةً. عندها تطابق ansatz طوبولوجيا الجهاز ويكاد الموجِّه لا يحتاج إلى لمسها:
>>> restricted = UCJ(
... ffsim.UCJOpSpinBalanced.from_t_amplitudes(
... t2, t1=t1, n_reps=2, interaction_pairs=(pairs_aa, allowed_pairs_ab)
... )
... )
>>>
>>> circuit = FermionicCircuit(2 * norb)
>>> circuit.append(InitializeModes.from_hartree_fock(norb, nelec), circuit.modes)
>>> circuit.append(restricted, circuit.modes)
>>>
>>> transpiled = pm.run(circuit.decompose())
>>> restricted_swaps = transpiled.count_ops()["swap"]
>>> restricted_swaps # far fewer routing SWAPs than the unrestricted ansatz
4
يُظهر رسم الكيوبتات النشطة فقط (idle_wires=False) الدارة مقيَّدة بسلسلتَي السبين وجسر ألفا-بيتا، معبَّرًا عنها ببوابات أساس الجهاز. يبعثر التوزيع والتوجيه الأنماط المنطقية عبر الكيوبتات الفيزيائية للجهاز، لذا مرّر wire_order مأخوذًا من التوزيع النهائي للدارة (final_index_layout() تسرد الكيوبت الفيزيائي الذي انتهى عنده كل كيوبت إدخال، بترتيب كيوبتات الإدخال) لرسم الأسلاك بترتيب الأنماط الأصلي:
>>> wire_order = transpiled.layout.final_index_layout(filter_ancillas=False)
>>> transpiled.draw("mpl", idle_wires=False, fold=-1, wire_order=wire_order)
<Figure size ... with 1 Axes>

تعتمد أعداد البوابات الدقيقة والعمق بعد التوزيع على تمريرات التوجيه/التحسين والجهاز المختار، لذا لا نعرضها هنا. النقطة الأساسية هي التصميم المشترك؛ فالتعبير عن ansatz بسلسلة pairs_aa من أقرب الجيران وجسور pairs_ab المرشَّحة بحسب العتاد يُبقي الدارة المركَّبة قريبة من طوبولوجيا الجهاز، مما يقلل كلفة التوجيه (بوابات SWAP المُدرَجة). راجع FermionicSlaterGivensDecomposition لخيار تركيب ذي صلة (minimize_2q_gate_count)، يقايض عدد البوابات ثنائية الكيوبت بالعمق بعد التوجيه.
الخطوات التالية
-
تعرّف على كيفية عمل البوابات الفردية في توثيق
qiskit_fermions.circuit.libraryودليل الدارة الفرميونية. -
استكشف دليل شرح المؤثرات لفهم كيفية بناء المؤثرات الفرميونية والهاملتونيانات مباشرةً.
-
اطّلع على كيفية تحويل الدارة الفرميونية إلى كيوبتات في دليل التحويل (transpilation).
-
اقرأ توثيق
F2QSynthesisلمعرفة الطريقة الأخرى لضبط مرحلة التركيب: قاموسconfigيختار الإضافات بالأسماء القصيرة التي تسجَّل بها ضمن نقاط الدخولqiskit_fermions.transpiler.synthesis، بدلاً من اختيارها بالنسخة. -
اقرأ دليل علاقة ffsim لفهم كيف تتقاسم الحزمتان العمل، وأي البروتوكولات تتيح لـ ffsim محاكاة دارات هذه الحزمة.
