SQD لتقدير طاقة هاميلتوني كيميائي
في هذا الدرس، سنطبّق SQD لتقدير طاقة الحالة الأساسية لجزيء.
تحديدًا، سنناقش المواضيع التالية باستخدام نهج نمط Qiskit المكوّن من خطوات:
- الخطوة 1: تعيين المسألة إلى دوائر كمية وعوامل
- إعداد الهاميلتوني الجزيئي لـ .
- شرح نموذج Jastrow للعنقود الأحادي الموحّد المحلي (LUCJ) المستوحى من الكيمياء والمتوافق مع العتاد [1]
- الخطوة 2: تحسين الأداء للعتاد المستهدف
- تحسين عدد البوابات وتخطيط النموذج الأولي للتنفيذ على العتاد
- الخطوة 3: التنفيذ على العتاد المستهدف
- تشغيل الدائرة المحسَّنة على وحدة معالجة كمية حقيقية (QPU) لتوليد عينات الفضاء الجزئي.
- الخطوة 4: معالجة النتائج
- تقديم حلقة الاسترداد الذاتي للتكوينات [2]
- معالجة المجموعة الكاملة من عينات السلاسل الثنائية، مع الاستفادة من المعرفة المسبقة بعدد الجسيمات ومتوسط إشغال المدارات المحسوب في آخر تكرار.
- إنشاء دُفعات احتمالية من العينات الفرعية المستعادة من السلاسل الثنائية.
- إسقاط الهاميلتوني الجزيئي وقطرنةه على كل فضاء جزئي من العينات.
- حفظ أدنى طاقة للحالة الأساسية عبر جميع الدُفعات وتحديث متوسط إشغال المدارات.
- تقديم حلقة الاسترداد الذاتي للتكوينات [2]
سنستخدم عدة حزم برمجية خلال هذا الدرس.
PySCFلتعريف الجزيء وإعداد الهاميلتوني.- حزمة
ffsimلبناء النموذج الأولي LUCJ. Qiskitلتهيئة النموذج الأولي للتنفيذ على العتاد.Qiskit IBM Runtimeلتنفيذ الدائرة على وحدة QPU وجمع العينات.- إضافة
Qiskit addon SQDلاسترداد التكوينات وتقدير طاقة الحالة الأساسية باستخدام إسقاط الفضاء الجزئي وقطرنة المصفوفات.
1. تعيين المسألة إلى دوائر كمية وعوامل
الهاميلتوني الجزيئي
يأخذ الهاميلتوني الجزيئي الصورة العامة التالية:
/ هي عوامل إنشاء/إفناء الفيرميونات المرتبطة بعنصر مجموعة الأساس من الرتبة والدوران . أما و فهما تكاملات الإلكترون أحادية وثنائية الجسم. باستخدام pySCF، سنعرّف الجزيء ونحسب التكاملات أحادية وثنائية الجسم للهاميلتوني بمجموعة الأساس 6-31g.
# Added by doQumentation — required packages for this notebook
!pip install -q ffsim matplotlib numpy pyscf qiskit qiskit-addon-sqd qiskit-ibm-runtime
import warnings
import pyscf
import pyscf.cc
import pyscf.mcscf
warnings.filterwarnings("ignore")
# Specify molecule properties
open_shell = False
spin_sq = 0
# Build N2 molecule
mol = pyscf.gto.Mole()
mol.build(
atom=[["N", (0, 0, 0)], ["N", (1.0, 0, 0)]], # Two N atoms 1 angstrom apart
basis="6-31g",
symmetry="Dooh",
)
# Define active space
n_frozen = 2
active_space = range(n_frozen, mol.nao_nr())
# Get molecular integrals
scf = pyscf.scf.RHF(mol).run()
num_orbitals = len(active_space)
n_electrons = int(sum(scf.mo_occ[active_space]))
num_elec_a = (n_electrons + mol.spin) // 2
num_elec_b = (n_electrons - mol.spin) // 2
cas = pyscf.mcscf.CASCI(scf, num_orbitals, (num_elec_a, num_elec_b))
mo = cas.sort_mo(active_space, base=0)
hcore, nuclear_repulsion_energy = cas.get_h1cas(mo) # hcore: one-body integrals
eri = pyscf.ao2mo.restore(1, cas.get_h2cas(mo), num_orbitals) # eri: two-body integrals
# Compute exact energy for comparison
exact_energy = cas.run().e_tot
converged SCF energy = -108.835236570774
CASCI E = -109.046671778080 E(CI) = -32.8155692383188 S^2 = 0.0000000
في هذا الدرس، سنستخدم تحويل جوردان-ويغنر (JW) لتعيين دالة موجة الفيرميونات إلى دالة موجة كيوبتية حتى يمكن إعدادها باستخدام دائرة كمية. يُعيّن تحويل JW فضاء فوك للفيرميونات في M مدار مكاني إلى فضاء هيلبرت لـ 2M كيوبت، أي أن المدار المكاني يُقسَّم إلى مدارَي دوران، أحدهما مرتبط بإلكترون دوران للأعلى () والآخر بدوران للأسفل (). يمكن أن يكون مدار الدوران محتلًا أو شاغرًا. عادةً، حين نشير إلى عدد المدارات، نعني عدد المدارات المكانية. أما عدد مدارات الدوران فيكون ضعف ذلك. في الدوائر الكمية، سنمثّل كل مدار دوران بكيوبت واحد. وبذلك، ستمثّل مجموعة من الكيوبتات المدارات ذات الدوران للأعلى أو ، وتمثّل مجموعة أخرى المدارات ذات الدوران للأسفل أو . على سبيل المثال، جزيء بمجموعة الأساس 6-31g يمتلك مدارًا مكانيًا (أي مدار + مدار = مدار دوران). وبالتالي، نحتاج إلى دائرة كمية من كيوبت (قد نحتاج إلى كيوبتات مساعدة إضافية كما سيُناقش لاحقًا). تُقاس الكيوبتات في الأساس الحسابي لتوليد سلاسل ثنائية تمثّل التكوينات الإلكترونية أو المحددات (Slater). سنستخدم طوال هذا الدرس مصطلحات السلاسل الثنائية والتكوينات والمحددات بشكل متبادل. تُخبرنا السلاسل الثنائية عن إشغال الإلكترونات في مدارات الدوران: الرقم في موضع بت ما يعني أن مدار الدوران المقابل مشغول، بينما يعني أن المدار شاغر. ولأن مسائل البنية الإلكترونية تحافظ على عدد الجسيمات، يجب أن يكون عدد محدد من مدارات الدوران مشغولًا. يمتلك جزيء إلكترونات ذات دوران للأعلى () و إلكترونات ذات دوران للأسفل (). وبالتالي، يجب أن تحتوي أي سلسلة ثنائية تمثّل مدارات و على خمسة أرقام لكل منها في جزيء .
1.1 الدائرة الكمية لتوليد العينات: النموذج الأولي LUCJ
في هذا الدرس، سنستخدم النموذج الأولي Jastrow للعنقود الأحادي الموحّد المحلي (LUCJ) \[1\] لإعداد الحالة الكمية وأخذ العينات منها لاحقًا. أولًا، سنشرح مختلف المكوّنات الأساسية لنموذج UCJ الكامل والتقريبات المُجراة في نسخته المحلية. بعد ذلك، باستخدام حزمة ffsim، سنبني النموذج الأولي LUCJ ونحسّنه باستخدام محوّل Qiskit للتنفيذ على العتاد.
يأخذ النموذج الأولي UCJ الشكل التالي (لحاصل ضرب طبقة أو تكرار من عامل UCJ):
حيث هي الحالة المرجعية، وعادةً تُؤخذ كحالة هارتري-فوك (HF). بما أن حالة هارتري-فوك تُعرَّف بأن تكون المدارات الأقل رقمًا هي المشغولة، فإن إعداد حالة HF سيتضمّن تطبيق بوابات X لضبط الكيوبتات المقابلة للمدارات المشغولة على القيمة واحد. على سبيل المثال، قد يبدو كتلة إعداد حالة HF لـ 4 مدارات مكانية و2 إلكترون ذو دوران للأعلى و2 ذو دوران للأسفل كالتالي:
يتكوّن تكرار واحد من عامل UCJ من تطور كولوم قطري () محاط بدورانات مدارية ( و).
تعمل كتل الدوران المداري على نوع واحد من الدوران ( (للأعلى)/ (للأسفل)). لكل نوع إلكتروني، يتكوّن الدوران المداري من طبقة من بوابات أحادية الكيوبت تليها سلسلة من بوابات دوران Givens ثنائية الكيوبت (بوابات ).
تعمل بوابات الكيوبتَين على مدارات الدوران المتجاورة (الكيوبتات الأقرب جوارًا)، وبالتالي يمكن تنفيذها على وحدات IBM® QPU دون الحاجة إلى بوابات SWAP.
يتكوّن ، المعروف أيضًا بعامل كولوم القطري، من ثلاثة كتل. اثنتان منها تعملان على قطاعات الدوران نفسها ( و)، وواحدة تعمل بين قطاعَي الدوران ().
تتكوّن جميع الكتل في من بوابات عدد-عدد [1]. يمكن تحليل بوابة بشكل أعمق إلى بوابة تليها بوابتا أحاديتا الكيوبت تعملان على كيوبتَين منفصلَين.
تحتوي المكوّنات ذات الدوران المتماثل ( و) على بوابات بين جميع أزواج الكيوبتات المحتملة. ومع ذلك، نظرًا لأن وحدات QPU فائقة التوصيل ذات اتصال محدود، يجب تبديل الكيوبتات لتحقيق البوابات بين الكيوبتات غير المتجاورة.
على سبيل المثال، تأمّل الكتلة (أو ) التالية لـ مدارات مكانية. بالنسبة لاتصال خطي بين الكيوبتات، لا يمكن تنفيذ البوابات الثلاثة الأخيرة مباشرةً لأنها تعمل بين كيوبتات غير متجاورة (مثلًا، Q0 وQ2 غير متصلَين مباشرةً). لذلك نحتاج إلى بوابات SWAP لجعلها متجاورة (يُظهر الشكل التالي مثالًا بـ بوابات SWAP).
بعد ذلك، تُنفّذ بوابات بين المدارات ذات نفس الفهرس من قطاعات الدوران المختلفة (مثلًا، بين و). وبالمثل، إذا لم تكن الكيوبتات متجاورة فيزيائيًا على وحدة QPU، فستحتاج هذه البوابات أيضًا إلى SWAPs.
من النقاش السابق، يواجه النموذج الأولي UCJ بعض العقبات للتنفيذ على العتاد لأنه يحتاج إلى بوابات SWAP بسبب التفاعلات بين الكيوبتات غير المتجاورة. يعالج النموذج المحلي للنموذج الأولي UCJ، وهو LUCJ، هذا التحدي عن طريق إزالة بعض من عامل كولوم القطري.
في كتل نفس النوع الإلكتروني ( و)، نحتفظ فقط ببوابات المتوافقة مع الاتصال بين أقرب الجيران ونحذف البوابات بين الكيوبتات غير المتجاورة في نسخة LUCJ. يُظهر الشكل التالي كتلة LUCJ بعد حذف البوابات غير المتجاورة.
بعد ذلك، يمكن أن تأخذ نسخة LUCJ من كتلة التي تعمل بين أنواع إلكترونية مختلفة أشكالًا متعددة بناءً على طوبولوجيا الجهاز.
هنا أيضًا، تتخلص نسخة LUCJ من البوابات غير المتوافقة. يُظهر الشكل أدناه متغيرات كتلة لطوبولوجيا كيوبت مختلفة تشمل الشبكة المربعة والسداسية وهيفي-هكس والخطية.
- مربعة: يمكن وضع بوابات بين جميع مدارات و دون أي SWAPs، وبالتالي لا نحتاج إلى حذف أي بوابات .
- هيفي-هكس: تُحفظ تفاعلات - بين المدارات ذات الفهرس المضاعف للـ -الأول (مثل الفهرس 0 و4 و8) وتكون مُوسَّطة بكيوبتات مساعدة، أي نحتاج إلى كيوبتات مساعدة بين السلاسل الخطية التي تمثّل مدارات و. يحتاج هذا الترتيب إلى عدد محدود من SWAPs.
- سداسية: كل مدار آخر، مثل المدارات ذات الفهرس 0 و2 و4، يصبح أقرب جار عند ترتيب و في سلسلتَين خطيتَين متجاورتَين.
- خطية: يكون مدار واحد فقط ومدار واحد متصلَين، مما يعني أن كتلة ستحتوي على بوابة واحدة فقط.
بينما يجعل حذف البوابات من نموذج UCJ لبناء نسخة LUCJ التنفيذ على العتاد أكثر ملاءمة، إلا أن النموذج الأولي يفقد بعض القدرة التعبيرية. وبالتالي، قد تكون هناك حاجة إلى تكرارات () أكثر من عامل UCJ المعدَّل عند استخدام نموذج LUCJ.
1.2 تهيئة النموذج الأولي LUCJ
LUCJ نموذج أولي مُعامَل، ونحتاج إلى تهيئة المعاملات قبل التنفيذ على العتاد. إحدى طرق تهيئة النموذج الأولي هي استخدام سَعات t1 وt2 من طريقة العنقود الأحادي المزدوج الكلاسيكية (CCSD)، حيث سَعات t1 هي معاملات عوامل الإثارة المفردة وسَعات t2 هي لعوامل الإثارة المزدوجة.
لاحظ أنه رغم أن تهيئة نموذج LUCJ بسَعات t1 وt2 تُعطي نتائج جيدة، إلا أن معاملات النموذج الأولي قد تحتاج إلى مزيد من التحسين.
# Get CCSD t2 amplitudes for initializing the ansatz
ccsd = pyscf.cc.CCSD(
scf, frozen=[i for i in range(mol.nao_nr()) if i not in active_space]
)
ccsd.run()
t1 = ccsd.t1
t2 = ccsd.t2
E(CCSD) = -109.0398256929733 E_corr = -0.20458912219883
1.3 بناء النموذج الأولي LUCJ باستخدام ffsim
سنستخدم حزمة ffsim لإنشاء النموذج الأولي وتهيئته بسَعات t1 وt2 المحسوبة أعلاه. بما أن جزيئنا يمتلك حالة هارتري-فوك ذات غلاف مغلق، سنستخدم النسخة متوازنة الدوران من نموذج UCJ، وهي UCJOpSpinBalanced.
نظرًا لأن عتاد IBM يمتلك طوبولوجيا هيفي-هكس، سنعتمد نمط zig-zag المستخدم في [1] والموضَّح أعلاه لتفاعلات الكيوبتات. في هذا النمط، المدارات (الكيوبتات) ذات الدوران نفسه متصلة بطوبولوجيا خطية (الدوائر الحمراء والزرقاء). بسبب طوبولوجيا هيفي-هكس، تمتلك المدارات ذات دوران مختلف اتصالات بين كل مدار من الرتبة الرابعة، أي المدارات ذات الفهرس 0 و4 و8 وما إلى ذلك (الدوائر البنفسجية).
import ffsim
from qiskit import QuantumCircuit, QuantumRegister
n_reps = 2
alpha_alpha_indices = [(p, p + 1) for p in range(num_orbitals - 1)]
alpha_beta_indices = [(p, p) for p in range(0, num_orbitals, 4)]
ucj_op = ffsim.UCJOpSpinBalanced.from_t_amplitudes(
t2=t2,
t1=t1,
n_reps=n_reps,
interaction_pairs=(alpha_alpha_indices, alpha_beta_indices),
)
nelec = (num_elec_a, num_elec_b)
# create an empty quantum circuit
qubits = QuantumRegister(2 * num_orbitals, name="q")
circuit = QuantumCircuit(qubits)
# prepare Hartree-Fock state as the reference state and append it to the quantum circuit
circuit.append(ffsim.qiskit.PrepareHartreeFockJW(num_orbitals, nelec), qubits)
# apply the UCJ operator to the reference state
circuit.append(ffsim.qiskit.UCJOpSpinBalancedJW(ucj_op), qubits)
circuit.measure_all()
# circuit.decompose().draw("mpl", scale=0.5, fold=-1)
يمكن تحسين نموذج LUCJ ذي الطبقات المتكررة عن طريق دمج بعض الكتل المتجاورة. تأمّل حالة n_reps=2. يمكن دمج كتلتَي الدوران المداري في الوسط في كتلة دوران مداري واحدة. تمتلك حزمة ffsim مدير تمريرات بالاسم ffsim.qiskit.PRE_INIT لتحسين الدائرة عن طريق دمج هذه الكتل المتجاورة.
2. تحسين الأداء للعتاد المستهدف
أولًا، نُحضر الواجهة الخلفية التي اخترناها. سنحسّن دائرتنا للواجهة الخلفية، ثم ننفّذ الدائرة المحسَّنة على نفس الواجهة الخلفية لتوليد عينات الفضاء الجزئي.
from qiskit_ibm_runtime import QiskitRuntimeService
service = QiskitRuntimeService()
# Use the least-busy backend or specify a quantum computer using the syntax commented out below.
backend = service.least_busy(operational=True, simulator=False)
# backend = service.backend("ibm_brisbane")
بعد ذلك، نوصي باتباع الخطوات التالية لتحسين النموذج الأولي وجعله متوافقًا مع العتاد.
- اختر كيوبتات فيزيائية (
initial_layout) من العتاد المستهدف تتوافق مع نمط zig-zag (سلسلتَين خطيتَين مع كيوبت مساعد بينهما) الموضَّح أعلاه. يؤدي ترتيب الكيوبتات وفق هذا النمط إلى دائرة متوافقة مع العتاد بكفاءة وعدد بوابات أقل. - أنشئ مدير تمريرات مُرحَّل باستخدام دالة
generate_preset_pass_managerمن Qiskit معbackendوinitial_layoutاللذَين اخترتهما. - اضبط مرحلة
pre_initفي مدير التمريرات المُرحَّل علىffsim.qiskit.PRE_INIT. يتضمّنffsim.qiskit.PRE_INITتمريرات محوّل Qiskit التي تحلّل البوابات إلى دورانات مدارية ثم تدمج هذه الدورانات، مما يؤدي إلى عدد بوابات أقل في الدائرة النهائية. - شغّل مدير التمريرات على دائرتك.
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
spin_a_layout = [0, 14, 18, 19, 20, 33, 39, 40, 41, 53, 60, 61, 62, 72, 81, 82]
spin_b_layout = [2, 3, 4, 15, 22, 23, 24, 34, 43, 44, 45, 54, 64, 65, 66, 73]
initial_layout = spin_a_layout + spin_b_layout
pass_manager = generate_preset_pass_manager(
optimization_level=3, backend=backend, initial_layout=initial_layout
)
# without PRE_INIT passes
isa_circuit = pass_manager.run(circuit)
print(f"Gate counts (w/o pre-init passes): {isa_circuit.count_ops()}")
# with PRE_INIT passes
# We will use the circuit generated by this pass manager for hardware execution
pass_manager.pre_init = ffsim.qiskit.PRE_INIT
isa_circuit = pass_manager.run(circuit)
print(f"Gate counts (w/ pre-init passes): {isa_circuit.count_ops()}")
Gate counts (w/o pre-init passes): OrderedDict({'rz': 7579, 'sx': 6106, 'ecr': 2316, 'x': 336, 'measure': 32, 'barrier': 1})
Gate counts (w/ pre-init passes): OrderedDict({'rz': 4088, 'sx': 3125, 'ecr': 1262, 'x': 201, 'measure': 32, 'barrier': 1})
3. التنفيذ على العتاد المستهدف
بعد تحسين الدائرة لتنفيذها على العتاد، أصبحنا جاهزين لتشغيلها على العتاد المستهدف وجمع العينات لتقدير طاقة الحالة الأساسية. بما أن لدينا دائرة واحدة فقط، سنستخدم وضع تنفيذ المهام في Qiskit Runtime لتنفيذ الدائرة.
from qiskit_ibm_runtime import SamplerV2 as Sampler
sampler = Sampler(mode=backend)
sampler.options.dynamical_decoupling.enable = True
job = sampler.run([isa_circuit], shots=10_000) # Takes approximately 5sec of QPU time
# Run cell after IQX job completion
primitive_result = job.result()
pub_result = primitive_result[0]
counts = pub_result.data.meas.get_counts()
4. المعالجة اللاحقة للنتائج
يمكن تلخيص جزء المعالجة اللاحقة في سير عمل SQD باستخدام المخطط التالي.
أخذ عينات من دالة LUCJ الأنساتز في الأساس الحسابي يولّد مجموعة من التهيئات الضوضائية ، والتي تُستخدم في روتين المعالجة اللاحقة. يتضمن هذا الروتين طريقةً تُسمى استرداد التهيئة (سيأتي شرحها لاحقاً) لتصحيح التهيئات ذات الأعداد الخاطئة من الإلكترونات باحتمالية معينة. ثم تُأخذ عينات فرعية من التهيئات التي تمتلك أعداداً صحيحة من الإلكترونات فقط، وتُوزَّع على دُفعات متعددة بناءً على تكرار ظهور كل تهيئة فريدة. كل دفعة من العينات تُعرّف فضاءً جزئياً (). بعد ذلك، يُسقَط هاميلتوني الجزيء على الفضاءات الجزئية:
ثم يُغذَّى كل هاميلتوني مُسقَط إلى حلّال القيم الذاتية، حيث يُقطَّر لحساب القيم الذاتية والمتجهات الذاتية لإعادة بناء الحالة الذاتية. في هذا الدرس، نُسقط الهاميلتوني ونُقطّره باستخدام حزمة qiskit-addon-sqd، التي تستخدم طريقة دافيدسون من PySCF للقطرنة.
نجمع بعد ذلك أدنى قيمة ذاتية (طاقة) من الدُفعات، ونحسب أيضاً متوسط إشغال المدارات . تُستخدم معلومات متوسط الإشغال في خطوة استرداد التهيئة لتصحيح التهيئات الضوضائية باحتمالية معينة.
نشرح فيما يلي حلقة استرداد التهيئة ذاتية الاتساق بالتفصيل، ونعرض أمثلة برمجية ملموسة لتطبيق الخطوات المذكورة أعلاه لتقدير طاقة الحالة الأساسية لهاميلتوني .
4.1 استرداد التهيئة: نظرة عامة
كل بت في السلسلة الثنائية (محدد سلاتر) يمثل مداراً دورانياً. النصف الأيمن من السلسلة يمثل المدارات ذات الدوران الصاعد، والنصف الأيسر يمثل المدارات ذات الدوران النازل. القيمة 1 تعني أن المدار مشغول بإلكترون، والقيمة 0 تعني أن المدار فارغ. نعرف مسبقاً العدد الصحيح للجسيمات (إلكترونات الدوران الصاعد والنازل على حدٍّ سواء). لنفترض أن لدينا محدداً يحتوي على إلكتروناً (أي أن هناك من القيم 1 في السلسلة). العدد الصحيح للجسيمات هو . إذا كان ، فنعلم أن السلسلة قد أُفسدت بالضوضاء. يحاول روتين التهيئة ذاتية الاتساق تصحيح السلسلة بقلب بت باحتمالية معينة، مستعيناً بمعلومات متوسط إشغال المدارات. يخبرنا متوسط الإشغال () بمدى احتمال أن يكون المدار مشغولاً بإلكترون. إذا كان ، فهذا يعني أن لدينا إلكترونات أقل مما ينبغي، ونحتاج إلى قلب بعض الأصفار إلى آحاد، والعكس بالعكس.
احتمال القلب يمكن أن يكون للمدار الدوراني i. في [2]، استخدم المؤلفون احتمالاً موزوناً للقلب باستخدام دالة ReLU المعدَّلة.
هنا يحدد موضع "الزاوية" في دالة ReLU، والمعامل يحدد قيمة دالة ReLU عند الزاوية. عند ، تصبح دالة ReLU الحقيقية، وعند ، تصبح ReLU المعدَّلة. في الورقة البحثية، استخدم المؤلفون و عدد جسيمات ألفا (أو بيتا) / عدد مدارات ألفا (أو بيتا) الدورانية (عامل الملء).
متوسط إشغال المدارات () غير معروف مسبقاً. تبدأ الدورة الأولى لتقدير الحالة الأساسية بالتهيئات التي تمتلك فقط أعداداً صحيحة من الجسيمات في كلا النوعين الدورانيين. بعد الدورة الأولى، يكون لدينا تقدير أولي للحالة الأساسية، ونستطيع باستخدام هذا التقدير بناء التخمين الأول لـ. يُستخدم هذا التخمين لـ لاسترداد التهيئات، وتشغيل دورة التقدير التالية للحالة الأساسية، وتحسين التخمين لـ بشكل ذاتي الاتساق. تتكرر العملية حتى يُستوفى معيار التوقف.
خذ المثال التالي لـ و (). نحتاج إلى قلب أحد الأصفار إلى واحد لتصحيح أعداد الجسيمات، والخيارات هي 1100 و1010 و1001. بناءً على احتمال القلب، سيُختار أحد الخيارات بوصفه التهيئة المُستردة (أي السلسلة ذات العدد الصحيح من الجسيمات).
لنفترض أننا نُشغّل دُفعتين في الدورة الأولى، والحالتان الأساسيتان المقدَّرتان منهما هما:
باستخدام حالات الأساس الحسابي وسعاتها، يمكننا حساب احتمال إشغال الإلكترونات (باختصار الإشغالات) لكل مدار دوراني (كيوبت) (علماً بأن الاحتمال = |السعة|). في الجداول أدناه نُدوّن الإشغالات لكل كيوبت لكل سلسلة تظهر في الحالة الأساسية المقدَّرة، ونحسب إجمالي إشغال المدارات لكل دُفعة. لاحظ أنه وفق اتفاقية ترتيب Qiskit، يمثل البت الأيمن الكيوبت 0 (Q0)، ويمثل البت الأيسر Q3.
الإشغال (الدُفعة 0):
| Q3 | Q2 | Q1 | Q0 | |
|---|---|---|---|---|
| 1001 | 0.64 | 0.0 | 0.0 | 0.64 |
| 0110 | 0.0 | 0.36 | 0.36 | 0.0 |
| n (Batch0) | 0.64 | 0.36 | 0.36 | 0.64 |
الإشغال (الدُفعة 1)
| Q3 | Q2 | Q1 | Q0 | |
|---|---|---|---|---|
| 1001 | 0.33 | 0.00 | 0.00 | 0.33 |
| 0101 | 0.0 | 0.33 | 0.00 | 0.33 |
| 0110 | 0.0 | 0.33 | 0.33 | 0.00 |
| n (Batch1) | 0.33 | 0.66 | 0.33 | 0.66 |
الإشغال (المتوسط عبر الدُفعات)
| Q3 | Q2 | Q1 | Q0 | |
|---|---|---|---|---|
| n (Batch0) | 0.64 | 0.36 | 0.36 | 0.64 |
| n (Batch1) | 0.33 | 0.66 | 0.33 | 0.66 |
| n (average) | 0.49 | 0.51 | 0.35 | 0.65 |
باستخدام متوسط إشغال المدارات المحسوب أعلاه، يمكننا إيجاد احتمالات القلب للمدارات المختلفة في التهيئة . بما أن المدار الذي يمثله Q3 مشغول بالفعل ولا يحتاج إلى قلب، نضع p(flip) له يساوي . أما بقية المدارات غير المشغولة، فاحتمال قلب كل منها هو . إلى جانب p(flip)، نحسب أيضاً الوزن الاحتمالي المرتبط بالقلب باستخدام دالة ReLU المعدَّلة الموضحة أعلاه.
احتمال القلب (، ، )
| Q3 | Q2 | Q1 | Q0 | |
|---|---|---|---|---|
| p(flip) () | 0 | 0.51 | 0.35 | 0.65 |
| w(p(flip)) | 0 | 0.03 | 0.007 | 0.31 |
أخيراً، باستخدام الاحتمالات الموزونة أعلاه، يمكننا قلب أحد المدارات غير المشغولة Q2 أو Q1 أو Q0. بناءً على القيم أعلاه، Q0 هو الأرجح للقلب، والتهيئة المحتملة المُستردة هي .
يمكن تلخيص عملية استرداد التهيئة ذاتية الاتساق الكاملة على النحو التالي:
الدورة الأولى: لنفترض أن السلاسل الثنائية (التهيئات أو محددات سلاتر) التي يولّدها الحاسوب الكمومي تشكّل مجموعة ، والتي تضم التهيئات ذات الأعداد الصحيحة () والأعداد الخاطئة () من الجسيمات في كل قطاع دوراني.
- تُأخذ عينات عشوائية من التهيئات في () لإنشاء دُفعات من المتجهات للإسقاط على الفضاء الجزئي. عدد الدُفعات وعدد العينات في كل دُفعة معاملان يحددهما المستخدم. كلما زاد عدد العينات في كل دُفعة، زادت أبعاد الفضاء الجزئي وأصبح القطرنة أكثر تطلباً من الناحية الحسابية. في المقابل، قد يُفضي عدد العينات الصغير جداً إلى إغفال متجهات دعم الحالة الأساسية والتوصل إلى تقديرات خاطئة.
- تشغيل حلّال الحالة الذاتية (أي الإسقاط على الفضاء الجزئي والقطرنة) على الدُفعات والحصول على الحالات الذاتية التقريبية .
- بناء التخمين الأول لـ من الحالات الذاتية التقريبية.
الدورات اللاحقة:
- استخدام لتصحيح التهيئات ذات الأعداد الخاطئة من الجسيمات في . لنسمِّها . عندئذٍ، تشكّل المجموعة الجديدة من التهيئات ذات الأعداد الصحيحة من الجسيمات.
- أخذ عينات من لإنشاء دُفعات .
- تشغيل حلّال الحالة الذاتية مع الدُفعات الجديدة وتوليد تقديرات جديدة للحالات الأساسية .
- بناء التخمين المحسَّن لـ من الحالات الذاتية التقريبية.
- إذا لم يُستوفَ معيار التوقف، العودة إلى الخطوة
2.1.
4.2 تقدير الحالة الأساسية
أولاً، سنحوّل العدّات إلى مصفوفة سلاسل ثنائية ومصفوفة احتمالات للمعالجة اللاحقة.
كل صف في المصفوفة يمثل سلسلة ثنائية فريدة واحدة. بما أن الكيوبتات مُرقَّمة من اليمين في السلسلة الثنائية في Qiskit، يمثل العمود 0 الكيوبت N-1، ويمثل العمود N-1 الكيوبت 0، حيث N هو عدد الكيوبتات.
المدارات ألفا ممثَّلة في نطاق فهرس الأعمدة (N, N/2] (النصف الأيمن)، والمدارات بيتا ممثَّلة في النطاق (N/2, 0] (النصف الأيسر).
from qiskit_addon_sqd.counts import counts_to_arrays
# Convert counts into bitstring and probability arrays
bitstring_matrix_full, probs_arr_full = counts_to_arrays(counts)
ثمة خيارات عديدة يتحكم فيها المستخدم وهي مهمة لهذه التقنية:
iterations: عدد دورات استرداد التهيئة ذاتية الاتساقn_batches: عدد دُفعات التهيئات المستخدمة في الاستدعاءات المختلفة لحلّال الحالة الذاتيةsamples_per_batch: عدد التهيئات الفريدة المُدرجة في كل دُفعةmax_davidson_cycles: الحد الأقصى لعدد دورات دافيدسون التي يُشغّلها كل حلّال قيم ذاتية
import numpy as np
from qiskit_addon_sqd.configuration_recovery import recover_configurations
from qiskit_addon_sqd.fermion import (
bitstring_matrix_to_ci_strs,
solve_fermion,
)
from qiskit_addon_sqd.subsampling import postselect_and_subsample
rng = np.random.default_rng(24)
# SQD options
iterations = 5
# Eigenstate solver options
n_batches = 5
samples_per_batch = 500
max_davidson_cycles = 300
# Self-consistent configuration recovery loop
e_hist = np.zeros((iterations, n_batches)) # energy history
s_hist = np.zeros((iterations, n_batches)) # spin history
occupancy_hist = []
avg_occupancy = None
for i in range(iterations):
print(f"Starting configuration recovery iteration {i}")
# On the first iteration, we have no orbital occupancy information from the
# solver, so we begin with the full set of noisy configurations.
if avg_occupancy is None:
bs_mat_tmp = bitstring_matrix_full
probs_arr_tmp = probs_arr_full
# If we have average orbital occupancy information, we use it to refine
# the full set of noisy configurations.
else:
bs_mat_tmp, probs_arr_tmp = recover_configurations(
bitstring_matrix_full,
probs_arr_full,
avg_occupancy,
num_elec_a,
num_elec_b,
rand_seed=rng,
)
# Create batches of subsamples. We postselect here to remove configurations
# with incorrect hamming weight during iteration 0, since no config recovery was performed.
batches = postselect_and_subsample(
bs_mat_tmp,
probs_arr_tmp,
hamming_right=num_elec_a,
hamming_left=num_elec_b,
samples_per_batch=samples_per_batch,
num_batches=n_batches,
rand_seed=rng,
)
# Run eigenstate solvers in a loop. This loop should be parallelized for larger problems.
e_tmp = np.zeros(n_batches)
s_tmp = np.zeros(n_batches)
occs_tmp = []
coeffs = []
for j in range(n_batches):
strs_a, strs_b = bitstring_matrix_to_ci_strs(batches[j])
print(f" Batch {j} subspace dimension: {len(strs_a) * len(strs_b)}")
energy_sci, coeffs_sci, avg_occs, spin = solve_fermion(
batches[j],
hcore,
eri,
open_shell=open_shell,
spin_sq=spin_sq,
max_davidson=max_davidson_cycles,
)
energy_sci += nuclear_repulsion_energy
e_tmp[j] = energy_sci
s_tmp[j] = spin
occs_tmp.append(avg_occs)
coeffs.append(coeffs_sci)
# Combine batch results
avg_occupancy = tuple(np.mean(occs_tmp, axis=0))
# Track optimization history
e_hist[i, :] = e_tmp
s_hist[i, :] = s_tmp
occupancy_hist.append(avg_occupancy)
Starting configuration recovery iteration 0
Batch 0 subspace dimension: 21609
Batch 1 subspace dimension: 21609
Batch 2 subspace dimension: 21609
Batch 3 subspace dimension: 21609
Batch 4 subspace dimension: 21609
Starting configuration recovery iteration 1
Batch 0 subspace dimension: 609961
Batch 1 subspace dimension: 616225
Batch 2 subspace dimension: 627264
Batch 3 subspace dimension: 633616
Batch 4 subspace dimension: 624100
Starting configuration recovery iteration 2
Batch 0 subspace dimension: 564001
Batch 1 subspace dimension: 605284
Batch 2 subspace dimension: 582169
Batch 3 subspace dimension: 559504
Batch 4 subspace dimension: 591361
Starting configuration recovery iteration 3
Batch 0 subspace dimension: 550564
Batch 1 subspace dimension: 549081
Batch 2 subspace dimension: 531441
Batch 3 subspace dimension: 527076
Batch 4 subspace dimension: 531441
Starting configuration recovery iteration 4
Batch 0 subspace dimension: 544644
Batch 1 subspace dimension: 580644
Batch 2 subspace dimension: 527076
Batch 3 subspace dimension: 531441
Batch 4 subspace dimension: 537289
4.3 مناقشة النتائج
يُظهر الرسم الأول أننا بعد بضع دورات نقدّر طاقة الحالة الأساسية بفارق ~24 mH (الدقة الكيميائية مقبولة عموماً عند 1 kcal/mol 1.6 mH). يُظهر الرسم الثاني متوسط إشغال كل مدار فراغي بعد الدورة الأخيرة. يتضح أن إلكترونات الدوران الصاعد والنازل على حدٍّ سواء تشغل المدارات الخمسة الأولى باحتمالية عالية في حلولنا.
على الرغم من أن طاقة الحالة الأساسية المقدَّرة معقولة، إلا أنها لا تقع ضمن حد الدقة الكيميائية ( mH). يمكن إرجاع هذا الفارق إلى صغر أبعاد الفضاء الجزئي المستخدم أعلاه للإسقاط والقطرنة. بما أننا استخدمنا samples_per_batch=500، فإن الفضاء الجزئي يمتد على أقصى تقدير على متجه، وهو ما يُغفل متجهات دعم الحالة الأساسية. زيادة قيمة المعامل samples_per_batch ينبغي أن تُحسّن الدقة على حساب موارد الحوسبة الكلاسيكية ووقت التشغيل.
# Data for energies plot
x1 = range(iterations)
min_e = [np.min(e) for e in e_hist]
e_diff = [abs(e - exact_energy) for e in min_e]
yt1 = [1.0, 1e-1, 1e-2, 1e-3, 1e-4, 1e-5]
# Chemical accuracy (+/- 1 milli-Hartree)
chem_accuracy = 0.001
# Data for avg spatial orbital occupancy
y2 = occupancy_hist[-1][0] + occupancy_hist[-1][1]
x2 = range(len(y2))
import matplotlib.pyplot as plt
fig, axs = plt.subplots(1, 2, figsize=(12, 6))
# Plot energies
axs[0].plot(x1, e_diff, label="energy error", marker="o")
axs[0].set_xticks(x1)
axs[0].set_xticklabels(x1)
axs[0].set_yticks(yt1)
axs[0].set_yticklabels(yt1)
axs[0].set_yscale("log")
axs[0].set_ylim(1e-6)
axs[0].axhline(
y=chem_accuracy, color="#BF5700", linestyle="--", label="chemical accuracy"
)
axs[0].set_title("Approximated Ground State Energy Error vs SQD Iterations")
axs[0].set_xlabel("Iteration Index", fontdict={"fontsize": 12})
axs[0].set_ylabel("Energy Error (Ha)", fontdict={"fontsize": 12})
axs[0].legend()
# Plot orbital occupancy
axs[1].bar(x2, y2, width=0.8)
axs[1].set_xticks(x2)
axs[1].set_xticklabels(x2)
axs[1].set_title("Avg Occupancy per Spatial Orbital")
axs[1].set_xlabel("Orbital Index", fontdict={"fontsize": 12})
axs[1].set_ylabel("Avg Occupancy", fontdict={"fontsize": 12})
print(f"Exact energy: {exact_energy:.5f} Ha")
print(f"SQD energy: {min_e[-1]:.5f} Ha")
print(f"Absolute error: {e_diff[-1]:.5f} Ha")
plt.tight_layout()
plt.show()
Exact energy: -109.04667 Ha
SQD energy: -109.02234 Ha
Absolute error: 0.02434 Ha
تمرين للقارئ
زِد قيمة المعامل samples_per_batch تدريجياً (مثلاً، من إلى بخطوة ؛ بحسب ما تسمح به ذاكرة حاسوبك) وقارن طاقات الحالة الأساسية المقدَّرة.
المراجع
[1] M. Motta et al., "Bridging physical intuition and hardware efficiency for correlated electronic states: the local unitary cluster Jastrow ansatz for electronic structure" (2023). Chem. Sci., 2023, 14, 11213.
[2] J. Robledo-Moreno et al., "Chemistry Beyond Exact Solutions on a Quantum-Centric Supercomputer" (2024). arXiv:quant-ph/2405.05068.