Algoritma SqDRIFT untuk anggaran keadaan asas
Anggaran penggunaan: 180 saat pada pemproses Heron r3 (NOTA: Ini hanyalah anggaran. Masa larian anda mungkin berbeza.)
Hasil pembelajaran
-
Pelajari cara mencipta circuit berkedalaman lebih kecil berbanding Trotterization
-
Ikuti aliran kerja hujung ke hujung untuk anggaran keadaan asas menggunakan qDRIFT dan SQD
-
Pelajari cara menggunakan
qiskit-fermionsbersama addon Qiskit yang lain untuk melaksanakan aliran kerja sebegitu
Tutorial ini dibentangkan sebagai notebook Python untuk tujuan pengajaran.
Prasyarat
-
Baca gambaran keseluruhan Pepenjuruan kuantum berasaskan sampel (SQD)
-
Baca pelajaran Pepenjuruan Kuantum Krylov berasaskan sampel (SKQD)
Latar belakang
SqDRIFT ialah varian SKQD yang menggantikan keperluan memilih ansatz untuk mensampel bitstring dengan ensemble circuit evolusi masa yang dibina terus daripada Hamiltonian sasaran. Ini dicapai dengan mensubsampel operator evolusi masa yang lebih kecil daripada Hamiltonian berdasarkan pekalinya, yang dikenali sebagai kaedah Trotterization qDRIFT.
Tutorial ini menggunakan Qiskit Fermions untuk mencipta circuit fermion yang lebih semula jadi bagi algoritma qDRIFT, diikuti dengan penggunaan pass susun atur dan sintesis fermion sebelum memasukkan circuit ke dalam saluran Qiskit tradisional untuk pelaksanaan pada perkakasan.
Biarkan Hamiltonian berbentuk:
di mana, tanpa kehilangan keumuman, kita memerlukan dan nilai eigen terbesar bagi sama, dalam nilai mutlak, dengan . Sebarang faktor awalan bertanda atau kompleks diserap ke dalam , jadi pekali ialah berat yang positif sepenuhnya manakala membawa arah bagi setiap sebutan. Di sini ialah bilangan sebutan (atau, selepas pengumpulan, bilangan kumpulan) dalam Hamiltonian; ia ialah sifat Hamiltonian dan berbeza daripada bilangan operator yang disampel ke dalam satu circuit, ditulis di bawah.
Algoritma qDRIFT kemudian merealisasikan, untuk masa sasaran , sesuatu operator , di mana pergi dari dan menandakan circuit SqDRIFT ke-, ditakrifkan sebagai:
Di sini ialah bilangan operator yang disampel bagi setiap circuit dan ialah bilangan circuit dalam ensemble. Hasil darab merentasi cabutan, bukan semua sebutan Hamiltonian, dan oleh sebab sebutan dicabut dengan pengembalian, yang sama boleh muncul lebih daripada sekali dalam satu .
Kuantiti:
ialah norma bagi pekali, jadi setiap satu daripada langkah berevolusi untuk tempoh yang sama tanpa mengira sebutan mana yang dicabut. Keseragaman sudut langkah ialah ciri khas qDRIFT: pekali mempengaruhi hasil melalui kekerapan sebutannya dicabut, bukan melalui sejauh mana sebutan itu diputar. Indeks disampel daripada taburan:
jadi siri ialah jujukan rawak indeks sebutan yang dicabut daripada taburan ini. Oleh sebab positif dan berjumlah , ini ialah taburan kebarangkalian ternormal, dan jangkaan saluran yang terhasil ke atas cabutan rawak menghampiri evolusi di bawah , dengan ralat yang berkurang apabila membesar. Perhatikan bahawa ralat penghampiran bergantung pada dan bukan pada bilangan sebutan .
(Kertas SqDRIFT menulis bilangan sebutan sebagai dan panjang jujukan sebagai ; kami menggunakan dan di sini supaya keduanya jelas berbeza.)
Tutorial ini menunjukkan cara menjana ensemble circuit rawak sedemikian. Selepas kita mencipta circuit ini, serupa dengan cara kita mencipta subruang Krylov bagi operator berbeza, kita mensampel bitstring daripada beberapa operator sebegitu dengan parameter masa yang berbeza. Ini memastikan pertindihan yang lebih tinggi antara vektor keadaan asas dan bitstring yang disampel.
Keperluan
Sebelum memulakan tutorial ini, pastikan anda telah memasang
- Persekitaran maya Python (>=3.10)
- pip>=25.1
- qiskit ~= 2.5
- qiskit-fermions==0.1.0 (Perhatikan bahawa namanya jamak)
- numpy
- pyscf
- qiskit-aer
- qiskit-ibm-runtime
- qiskit-addon-sqd
Anda boleh memasang semua pakej yang diperlukan dengan:
pip install "qiskit~=2.5" "qiskit-fermions==0.1.0" qiskit-aer qiskit-ibm-runtime qiskit-addon-sqd pyscf numpy
Persediaan
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "pyscf": "pyscf", "qiskit": "qiskit", "qiskit_addon_sqd": "qiskit-addon-sqd", "qiskit_aer": "qiskit-aer", "qiskit_fermions": "qiskit-fermions", "qiskit_ibm_runtime": "qiskit-ibm-runtime"}
_missing = [pip for module, pip in _needed.items()
if importlib.util.find_spec(module) is None]
# One at a time, so a package that fails to install does not block the others
for _pip in _missing:
%pip install -q {_pip}
if not _missing:
print("\u2713 All required packages are installed")
# Third-party scientific computing
import numpy as np
# PySCF
from pyscf import tools, ao2mo, fci
# Qiskit core
from qiskit import transpile
from qiskit.primitives import BitArray
# Qiskit Aer
from qiskit_aer import AerSimulator
# IBM Quantum Compute Service
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2 as Sampler
# Qiskit Fermions
from qiskit_fermions.operators.library import FCIDump
from qiskit_fermions.operators import FermionOperator
from qiskit_fermions.operators.terms.filtering import filter_diagonal_terms
from qiskit_fermions.operators.terms.grouping import (
group_terms_by_electronic_structure,
)
from qiskit_fermions.operators.terms.ordering import canonical_order
from qiskit_fermions.circuit import FermionicCircuit
from qiskit_fermions.circuit.library import Evolution
from qiskit_fermions.transpiler import FermionicPassManager
from qiskit_fermions.transpiler.presets import generate_preset_jw_pass_manager
from qiskit_fermions.transpiler.passes import QDriftTrotterization
from qiskit_fermions.circuit.library import InitializeModes
# Qiskit addon SQD
from qiskit_addon_sqd.fermion import (
diagonalize_fermionic_hamiltonian,
SCIResult,
)
Contoh simulator
Langkah 1: Petakan input klasik kepada masalah kuantum
Membaca dan Menyediakan FCIDump
Untuk tutorial ini, kita akan memuatkan Hamiltonian struktur elektronik bagi nitrogen (N2). Terdapat cara lain untuk mencipta operator fermion juga. Rujuk dokumentasi di qiskit_fermions.operators.library.
Tentang FCIDump ini. Fail N2_sto_3g menerangkan molekul nitrogen () dalam asas STO-3G minimum pada pemisahan antara atom 1.09 , panjang ikatan keseimbangan eksperimen. Pengepalanya mengisytiharkan NORB=10, NELEC=14, dan MS2=0: 10 orbital ruang (justeru 20 orbital spin, dan 20 qubit di bawah Jordan-Wigner), 14 elektron dalam singlet spin, jadi tujuh elektron dan tujuh . Semua orbital diberi label simetri 1, iaitu tiada simetri kumpulan titik dieksploitasi. Sebagai longgokan STO-3G ruang penuh, tiada orbital dibekukan dan ruang korelasi cukup kecil sehingga tenaga rujukan FCI tepat boleh dikira secara klasik untuk perbandingan, seperti yang ditunjukkan dalam sel seterusnya.
Fail yang setara boleh dijana semula dengan PySCF:
from pyscf import gto, scf, tools
mol = gto.M(atom="N 0 0 0; N 0 0 1.09", basis="sto-3g", symmetry=False)
mf = scf.RHF(mol).run()
tools.fcidump.from_scf(mf, "N2_sto_3g")
Oleh sebab kamiran bergantung pada orbital SCF yang menumpu, fail yang dijana semula mungkin berbeza daripada yang disertakan dari segi fasa atau tertib orbital; jumlah tenaga tidak terjejas.
Mendapatkan fail. Cari FCIDump dalam repositori GitHub ini. Anda boleh menjalankan sel di bawah untuk mengambilnya ke lokasi yang dijangka oleh bahagian tutorial yang lain.
Mula-mula kita menggunakan cisolver yang disediakan oleh pyscf untuk mendapatkan tenaga rujukan. Ini ialah tenaga keadaan asas sebenar bagi molekul yang kita gunakan. Untuk ini kita akan mengisytiharkan norb dan nelec dahulu, iaitu bilangan orbital dan bilangan elektron, masing-masing. Kemudian kita isytiharkan h1e dan h2e, iaitu kamiran satu dan dua elektron masing-masing. Semua ini akan digunakan kemudian untuk SQD juga.
import os
from urllib.request import urlopen
# The FCIDump is stored with this tutorial in the Qiskit documentation repository.
FCIDUMP_URL = "https://raw.githubusercontent.com/Qiskit/documentation/main/docs/tutorials/assets/sqdrift/fcidump_files/N2_sto_3g"
FCIDUMP_PATH = "fcidump_files/N2_sto_3g"
if not os.path.exists(FCIDUMP_PATH):
os.makedirs(os.path.dirname(FCIDUMP_PATH), exist_ok=True)
with urlopen(FCIDUMP_URL) as response:
contents = response.read()
with open(FCIDUMP_PATH, "wb") as f:
f.write(contents)
print(f"Downloaded FCIDump to {FCIDUMP_PATH}")
else:
print(f"Using existing FCIDump at {FCIDUMP_PATH}")
Using existing FCIDump at assets/sqdrift/fcidump_files/N2_sto_3g
name = "fcidump_files/N2_sto_3g"
fcidump = tools.fcidump.read(name)
# Extract metadata from the FCIDump header
norb = fcidump["NORB"] # number of spatial orbitals
nelec = fcidump["NELEC"] # total number of electrons
e_nuc = fcidump["ECORE"] # nuclear repulsion / core energy
ms2 = fcidump["MS2"] # 2S (spin)
num_elec_a = (nelec + ms2) // 2 # alpha electrons
num_elec_b = (nelec - ms2) // 2 # beta electrons
# Reconstruct full 4-index ERIs from the FCIDump (stored in 8-fold symmetry)
h1e = fcidump["H1"] # shape (norb, norb)
h2e = ao2mo.restore( # shape (norb, norb, norb, norb)
1, fcidump["H2"], norb
)
cisolver = fci.direct_spin1.FCI()
cisolver.max_cycle = 200
cisolver.conv_tol = 1e-12
e_fci, _ = cisolver.kernel(
h1e,
h2e,
norb,
(num_elec_a, num_elec_b),
ecore=e_nuc, # adds nuclear repulsion to the final energy
)
reference_energy = e_fci
print(f"Reference FCI Energy = {reference_energy:.10f} Ha")
nuclear_repulsion_energy = fcidump["ECORE"]
print(f"Nuclear Repulsion Energy = {nuclear_repulsion_energy:.10f} Ha")
Parsing assets/sqdrift/fcidump_files/N2_sto_3g
Reference FCI Energy = -107.6481842917 Ha
Nuclear Repulsion Energy = 23.7887003074 Ha
Memuatkan Hamiltonian
Dengan data yang diperlukan sedia, kita membaca Hamiltonian daripada fail FCI dalam format yang serasi dengan qiskit-fermions
fcidump = FCIDump.from_file(name)
hamiltonian = FermionOperator.from_fcidump(fcidump)
num_modes = 2 * fcidump.norb
Aliran kerja fermion dengan qiskit-fermions
Kita akan memetakan Hamiltonian kepada model circuit fermion dahulu menggunakan qiskit-fermions, yang menyediakan pass transpiler dan gate khusus untuk circuit fermion. Ini akan digunakan kemudian sebelum pass transpiler tradisional Qiskit untuk aliran kerja ini.
Pengumpulan sebutan
Untuk memastikan hasil boleh diulang, kita mula-mula menggunakan canonical_order untuk menyusun sebutan berdasarkan strukturnya sahaja. Tertib operator dalam senarai canon oleh itu tetap. Ini memastikan operator yang dicipta boleh diulang kerana pass QDriftTrotterization yang akan kita gunakan nanti mensampel indeks rawak untuk mencipta operator qDRIFT.
Dalam langkah ini, kita memanfaatkan banyak simetri yang terdapat dalam Hamiltonian struktur elektronik dengan mengumpulkan sebutan berkaitan yang mempunyai pekali sama. Walaupun berbuat demikian mengubah taburan pekali operator yang disampel oleh protokol qDRIFT, ini tidak menjejaskan jaminan penumpuannya. Yang penting, mengumpulkan sebutan yang berkaitan melalui simetri menghasilkan pembatalan sebutan Pauli yang menguntungkan dan kedalaman circuit keseluruhan yang lebih pendek apabila menyebarkan keadaan mengikut masa di bawah tindakannya.
qiskit-fermions menyediakan fungsi group_terms_by_electronic_structure yang melakukan pengumpulan ini untuk kita.
Perhatikan bahawa group_terms_by_electronic_structure mengandaikan sebutan tertib normal.
Menapis sebutan pepenjuru
Kita membuang sebutan pepenjuru daripada Hamiltonian yang digunakan untuk menjana circuit, supaya slot persampelan qDRIFT dihabiskan pada sebutan yang memindahkan populasi antara konfigurasi. Sebutan sebegitu paling baik ditapis daripada Hamiltonian pada ketika ini, sebelum gate Evolution dibina dalam langkah seterusnya.
Sebutan yang dimaksudkan ialah yang berpepenjuru dalam asas nombor pendudukan, iaitu hasil darab operator nombor . Tiga jenis sebutan termasuk dalam huraian ini:
-
ofset tenaga malar, hasil darab sifar operator nombor, yang evolusi masanya hanya menyumbang fasa global;
-
operator nombor individu , yang evolusi masanya terturun kepada putaran qubit tunggal;
-
hasil darab tertib lebih tinggi seperti .
Dengan sendirinya, tiada satu pun daripada ini memindahkan populasi antara konfigurasi nombor pendudukan; ia hanya bertindak pada fasa konfigurasi yang sudah ada. Namun begitu, ia tidak lengai: fasa relatif itu menyumbang kepada interferens yang dijana oleh sebutan pengujaan kemudian dalam circuit, jadi menapisnya mengubah evolusi yang sebenarnya dijana dan boleh mengubah taburan persampelan. Ini ialah penghampiran yang disengajakan dalam langkah penjanaan circuit, dibuat untuk memfokuskan persampelan pada sebutan pengujaan, dan bukan langkah yang membiarkan taburan yang disampel tidak terjejas. Tidak seperti pengumpulan simetri di atas, yang membiarkan jaminan penumpuan qDRIFT utuh, penapis ini mengubah operator yang dievolusikan. Circuit oleh itu tidak lagi menghampiri evolusi di bawah Hamiltonian penuh, dan sempadan ralat qDRIFT terpakai pada operator yang ditapis dan bukan yang asal. Ini boleh diterima di sini kerana circuit hanyalah heuristik persampelan yang digunakan untuk mencadangkan konfigurasi: tiada sebutan hilang daripada anggaran tenaga itu sendiri, kerana penapis hanya terpakai pada Hamiltonian yang digunakan untuk membina circuit, manakala pepenjuruan klasik kemudian menggunakan Hamiltonian penuh, termasuk sebutan pepenjuru. Ketepatan SQD bergantung pada langkah klasik itu, yang kekal variasi dalam subruang yang disampel tanpa mengira cara konfigurasi dicadangkan.
Fungsi filter_diagonal_terms() membuang sebutan sebegitu daripada operator secara in-place. Ia mengenal pasti sebutan itu daripada struktur tertib normalnya — multiset mod penciptaan sepadan dengan multiset mod pemusnahan — jadi ia hanya sah pada operator yang sudah bertertib normal. Andaian ini tidak disemak semasa larian.
# Apply automatic grouping
canon = canonical_order(hamiltonian.normal_ordered().simplify(atol=1e-16))
exit_code = group_terms_by_electronic_structure(
canon, num_modes, two_body_physicist_order=False
)
filter_diagonal_terms(canon)
print(len(canon.groups))
5060
Sekarang setelah kita mengumpulkan sebutan dalam Hamiltonian, kita akan menentukan parameter berikut untuk menjana ensemble circuit:
- Bilangan circuit yang dijana:
num_circuits - Panjang setiap circuit dari segi kumpulan pengujaan:
num_exc - Faktor untuk masa evolusi yang berbeza:
times
Mencipta circuit fermion
Sekarang kita akan mencipta circuit fermion bagi setiap langkah masa. Setiap circuit akan terdiri daripada satu gate evolusi, dengan masa evolusi yang kita isytiharkan tadi. Operator evolusi ialah Hamiltonian. Kemudian kita menjalankan pass transpiler pada circuit ini untuk mencipta circuit qDRIFT.
Penyediaan ansatz
Kita menyediakan keadaan Hartree-Fock menggunakan kelas InitializeModes. Bagi nitrogen, prosesnya hanyalah mengenakan gate X pada num_elec_a qubit pertama dan kemudian pada num_elec_b qubit, kedua-duanya sama dengan tujuh bagi nitrogen. Keadaan ini mewakili tujuh elektron dan tujuh bagi nitrogen.
# SqDRIFT parameters
times = [1.0, 10.0] # Total evolution times used for the subspace creation
num_exc = 10 # Number of excitation groups per circuit
num_circuits = 200 # Number of circuits to generate
init_circuits = []
hf_gate = InitializeModes.from_hartree_fock(norb, (num_elec_a, num_elec_b))
for time in times:
evo_gate = Evolution(num_modes, canon, time)
circ = FermionicCircuit(num_modes)
circ.append(hf_gate, circ.modes)
circ.append(evo_gate, circ.modes)
init_circuits.append(circ)
Langkah 2: Optimumkan masalah untuk pelaksanaan pada perkakasan kuantum
Sekarang setelah kita ada circuit, kita akan menggunakan pass yang tersedia dalam qiskit-fermions dahulu untuk melakukan pengoptimuman peringkat fermion, diikuti dengan transpiling circuit kita untuk backend pilihan. Oleh sebab ini eksperimen simulator, kita akan melakukannya dahulu untuk AerSimulator.
Pengiraan berat bagi setiap kumpulan
Dalam langkah ini, kita melakukan pensampelan qDRIFT bagi sebutan secara stokastik dengan kebarangkalian berkadar dengan pekali sebutan tersebut dalam Hamiltonian. Laluan transpiler qDRIFT melakukannya untuk kita. Sekarang kita boleh mencipta litar yang lebih cetek yang boleh dilaksanakan pada perkakasan dengan lebih cekap walaupun kesambungan qubit terhad, malah apabila Hamiltonian mengandungi gandingan jarak jauh dan sebutan yang lebih tinggi daripada kuadratik. Selepas pengumpulan sebutan, ia mensampel operator berdasarkan pemberat mereka. Bagi setiap operator , pemberat ditakrifkan seperti berikut:
Oleh sebab sebutan telah dikumpulkan dalam Langkah 1, setiap di sini ialah satu kumpulan penuh: ialah purata pekali mutlak bagi sebutan dalam kumpulan , dan setiap sebutan dalam kumpulan itu dikembangkan dengan pekalinya dikurangkan kepada tandanya sahaja.
Pengoptimuman fermionik dan natif perkakasan
Fungsi generate_preset_jw_pass_manager() memulangkan MultiStagePassManager yang mengambil FermionicCircuit dan menghasilkan litar akhir yang dioptimumkan yang boleh kita transpile untuk dijalankan pada perkakasan kita. Kita menggantikan peringkat pengoptimuman lalainya dengan FermionicPassManager yang mengandungi laluan QDriftTrotterization kita:
-
Laluan
QDriftTrotterizationmenggunakan pengiraan pemberat dan pensampelan secara dalaman untuk menjana litar yang akan kita gunakan untuk pensampelan -
Laluan
RelabelModesialah satu lagi laluan pengoptimuman yang boleh digunakan untuk menukar susunan mod fermionik bagi mengoptimumkan kesambungan merentasi qubit dan mengurangkan kedalaman gate; baca lebih lanjut dalam rujukan API
Peringkat MultiStagePassManager yang selebihnya berjalan secara automatik dan mengendalikan pemetaan fermion-ke-qubit sepenuhnya:
-
F2QLayout: Pass manager pratetap menggunakan laluan
TrivialF2QLayout, yang memetakan bit fermionik kepada qubit secara remeh. -
F2QSynth: Laluan transpilasi untuk memetakan arahan litar berasaskan fermion kepada arahan berasaskan qubit.
qdrift = QDriftTrotterization(num_exc, rng=19)
pm = generate_preset_jw_pass_manager()
pm.optimization = FermionicPassManager([qdrift])
sqdrift_circuits = []
for circ in init_circuits:
sqdrift_circuits += (pm.run(circ) for _ in range(num_circuits))
for circ in sqdrift_circuits:
circ.measure_all()
print(len(sqdrift_circuits))
400
Sekarang setelah pengoptimuman peringkat fermionik selesai, kita boleh transpile litar untuk dilaksanakan pada simulator.
simulator = AerSimulator()
shots = 100
transpiled_circuits = transpile(sqdrift_circuits, simulator)
Langkah 3: Laksanakan menggunakan primitif Qiskit
Sekarang setelah kita ada litar, kita boleh menjalankannya menggunakan primitif Qiskit pada AerSimulator. Kita akan menggabungkan semua kiraan daripada litar yang berbeza. Kita menukarkannya kepada vektor boolean sebelum akhirnya melakukan pasca-pemprosesan dengan SQD.
print(
f"Executing {len(transpiled_circuits)} circuits with {shots} shots each..."
)
job = simulator.run(transpiled_circuits, shots=shots)
result = job.result()
all_counts = [result.get_counts(i) for i in range(len(transpiled_circuits))]
print(len(all_counts), "length before post processing")
Executing 400 circuits with 100 shots each...
400 length before post processing
Langkah 4: Pasca-proses dan pulangkan hasil dalam format klasik yang dikehendaki
Menggunakan bitstring untuk SQD
Sekarang kita boleh menjalankan skema pepenjuruan pada bitstring yang dipilih untuk mencari nilai eigen terendah yang sepadan dengan tenaga keadaan asas molekul. Kita mencipta fungsi callback, mengisytiharkan penghunian awal, dan menetapkan parameter sebelum akhirnya menjalankan skema pepenjuruan. Fungsi callback digunakan untuk mencetak lelaran semasa dan anggaran nilai eigen semasa pada setiap lelaran.
Akhir sekali, untuk mendapatkan anggaran keadaan asas, kita tambah nuclear_repulsion_energy kepada tenaga yang terhasil.
Nota: Dimensi subruang tidak tetap merentasi lelaran, malah pada simulator tanpa hingar — setiap subsampel melukis set konfigurasi yang berbeza, dan langkah pemulihan membentuk semula kolam antara lelaran, jadi dimensi yang dilaporkan berbeza dari satu subsampel ke subsampel seterusnya. Pensampelan tanpa hingar tidak dengan sendirinya mengunci dimensi subruang terpilih. Walau bagaimanapun, larian perkakasan cenderung memberikan subruang yang lebih besar secara sistematik, kerana tembakan berhingar memecahkan simetri nombor zarah dan pemulihan konfigurasi menukarkannya menjadi vektor asas tambahan. Oleh sebab itu, kita juga akan memperkenalkan satu lagi langkah untuk memangkas bitstring dalam bahagian perkakasan.
combined_counts = {}
for counts in all_counts:
for bitstring, count in counts.items():
combined_counts[bitstring] = combined_counts.get(bitstring, 0) + count
bit_array = BitArray.from_counts(combined_counts)
print(bit_array.num_shots)
print(f" Alpha electrons: {num_elec_a}")
print(f" Beta electrons: {num_elec_b}")
print(f" Number of orbitals: {norb}")
print(f" Number of spin orbitals (qubits): {2*norb}")
print(f"Integral shapes: h1e={h1e.shape}, h2e={h2e.shape}")
# SQD parameters
samples_per_batch = 300
num_batches = 3
max_iterations = 5
initial_occupancies = (
np.array([1] * num_elec_a + [0] * (norb - num_elec_a)), # alpha
np.array([1] * num_elec_b + [0] * (norb - num_elec_b)), # beta
)
result_history = []
def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy + nuclear_repulsion_energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
# Run SQD with configuration recovery
print("\nRunning SQD with configuration recovery...")
result = diagonalize_fermionic_hamiltonian(
h1e,
h2e,
bit_array,
samples_per_batch=samples_per_batch,
norb=norb,
nelec=(num_elec_a, num_elec_b),
num_batches=num_batches,
energy_tol=1e-3,
occupancies_tol=1e-3,
max_iterations=max_iterations,
initial_occupancies=initial_occupancies,
seed=42,
callback=callback,
)
computed_energy = result.energy + nuclear_repulsion_energy
print("FINAL SQD RESULTS")
print(f"Orbital occupancies (alpha): {result.orbital_occupancies[0]}")
print(f"Orbital occupancies (beta): {result.orbital_occupancies[1]}")
energy_error = abs(computed_energy - reference_energy)
print(f"Reference Energy: {reference_energy:.10f} Ha")
print(f"Computed Energy: {computed_energy:.10f} Ha")
print(f"Error: {energy_error:.10e} Ha")
40000
Alpha electrons: 7
Beta electrons: 7
Number of orbitals: 10
Number of spin orbitals (qubits): 20
Integral shapes: h1e=(10, 10), h2e=(10, 10, 10, 10)
Running SQD with configuration recovery...
Iteration 1
Subsample 0
Energy: -107.64767025226178
Subspace dimension: 5538
Subsample 1
Energy: -107.64772799119115
Subspace dimension: 5670
Subsample 2
Energy: -107.64765512281548
Subspace dimension: 5767
Iteration 2
Subsample 0
Energy: -107.64795948524682
Subspace dimension: 6080
Subsample 1
Energy: -107.64806617355072
Subspace dimension: 6300
Subsample 2
Energy: -107.64802260640258
Subspace dimension: 6308
FINAL SQD RESULTS
Orbital occupancies (alpha): [0.99999464 0.99999643 0.99584631 0.99332984 0.96684652 0.96686712
0.99301927 0.0373282 0.0373266 0.00944508]
Orbital occupancies (beta): [0.99999462 0.99999643 0.9958261 0.99332349 0.96684268 0.96686737
0.99302145 0.03733536 0.03733399 0.0094585 ]
Reference Energy: -107.6481842917 Ha
Computed Energy: -107.6480661736 Ha
Error: 1.1811817564e-04 Ha
Contoh perkakasan
Contoh ini menggunakan 20 qubit (10 orbital ruang). Pilihan itu hanyalah kemudahan untuk tutorial yang sepatutnya berjalan pantas, bukan had mutlak bagi kaedah ini.
Kos langkah klasik tidak ditentukan secara langsung oleh bilangan qubit. SQD memepenjurukan Hamiltonian yang diunjurkan ke atas subruang yang dijangkau oleh konfigurasi yang disampel, jadi apa yang menentukan kos klasik ialah dimensi subruang terpilih itu — dikawal di sini oleh samples_per_batch, num_batches, dan bilangan konfigurasi berbeza yang sebenarnya dihasilkan oleh litar — bersama algebra linear jarang yang diperlukan untuk mengenakan Hamiltonian yang diunjurkan. Ruang CI penuh membesar secara kombinatorik dengan orbital dan elektron, tetapi subruang terpilih ialah hirisan kecil yang boleh ditala daripadanya, dan kita mengawal saiznya secara langsung. Akibatnya, bilangan qubit dan kesukaran klasik boleh diubah agak bebas antara satu sama lain: ruang orbital yang lebih luas yang disampel ke dalam subruang sederhana boleh lebih murah daripada sistem yang lebih kecil yang dipepenjurukan pada subruang yang sangat besar.
Dalam praktiknya, saiz sistem yang boleh dilaksanakan bergantung pada dimensi subruang yang anda perlukan untuk ketepatan yang anda mahu, serta pada memori dan teras yang tersedia untuk penyelesai eigen. Ruang orbital yang lebih besar lazimnya memerlukan subruang yang lebih besar untuk mencapai ketepatan kimia, dan itulah yang akhirnya mendorong penggunaan sumber teragih — lihat qiskit-addon-sqd-hpc untuk menskalakan langkah ini. Daripada menganggap had tetap, pendekatan praktikal ialah memerhatikan dimensi subruang yang dilaporkan dan penumpuan tenaga merentasi lelaran, dan meningkatkan saiz subruang sehingga tenaga berhenti bertambah baik atau memori yang tersedia habis.
Nota: Disebabkan ralat pensampelan daripada hingar dalam perkakasan, subruang yang dicipta untuk pepenjuruan dalam larian perkakasan akan lebih besar daripada yang kita dapat apabila menggunakan simulator. Walaupun ia menambah dimensi subruang yang ingin kita pepenjurukan, aliran kerja ini masih memberikan jawapan yang tepat berkat kekukuhan SQD terhadap hingar.
Pemangkasan rentetan palsu
Di sini kita boleh memilih untuk melakukan satu langkah tambahan. Apabila kita mempunyai semua bitstring daripada pelaksanaan litar, kita boleh menapis keluar bitstring yang tidak sah sebelum menjalankan SQD, atau teruskan tanpa pemangkasan. Melangkau pemangkasan umumnya lebih baik untuk larian perkakasan, kerana ia membiarkan tembakan yang simetrinya pecah tersedia untuk pemulihan konfigurasi, yang boleh membaikinya menjadi konfigurasi sah dan dengan itu melebarkan subruang dan bukannya membuang tembakan tersebut terus.
Oleh sebab nitrogen hanya boleh mempunyai tujuh elektron dan tujuh elektron , sebarang bitstring yang mempunyai lebih atau kurang daripada tujuh angka 1 dalam separuh pertama dan separuh kedua output boleh dibuang. Kita mentakrifkan fungsi yang menyemak sama ada bitstring sah, dan jika tidak, membuangnya. Setelah kita menapis keluar bitstring palsu, yang selebihnya dihantar ke dalam skema pepenjuruan. Gunakan bendera PRUNE di bawah untuk bertukar antara kedua-dua tingkah laku.
Ingat bahawa pemangkasan hanyalah satu daripada beberapa pilihan yang membentuk subruang akhir, bersama bilangan litar, set masa evolusi, dan penapisan sebutan pepenjuru. Membandingkan larian yang dipangkas dengan larian yang tidak dipangkas hanya bermaklumat jika semua yang lain dikekalkan tetap; versi C++ bagi tutorial ini membincangkannya dengan lebih terperinci, kerana ia melakukan pascapemilihan dan bukannya pemulihan, dan juga berbeza dalam parameter lain tersebut.
name = "fcidump_files/N2_sto_3g"
fcidump = tools.fcidump.read(name)
# Extract metadata from the FCIDump header
norb = fcidump["NORB"] # number of spatial orbitals
nelec = fcidump["NELEC"] # total number of electrons
e_nuc = fcidump["ECORE"] # nuclear repulsion / core energy
ms2 = fcidump["MS2"] # 2S (spin)
num_elec_a = (nelec + ms2) // 2 # alpha electrons
num_elec_b = (nelec - ms2) // 2 # beta electrons
# Reconstruct full 4-index ERIs from the FCIDump (stored in 8-fold symmetry)
h1e = fcidump["H1"] # shape (norb, norb)
h2e = ao2mo.restore( # shape (norb, norb, norb, norb)
1, fcidump["H2"], norb
)
cisolver = fci.direct_spin1.FCI()
cisolver.max_cycle = 200
cisolver.conv_tol = 1e-12
e_fci, _ = cisolver.kernel(
h1e,
h2e,
norb,
(num_elec_a, num_elec_b),
ecore=e_nuc, # adds nuclear repulsion to the final energy
)
reference_energy = e_fci
print(f"Reference FCI Energy = {reference_energy:.10f} Ha")
nuclear_repulsion_energy = fcidump["ECORE"]
print(f"Nuclear Repulsion Energy = {nuclear_repulsion_energy:.10f} Ha")
fcidump = FCIDump.from_file(name)
hamiltonian = FermionOperator.from_fcidump(fcidump)
num_modes = 2 * fcidump.norb
# Apply automatic grouping
canon = canonical_order(hamiltonian.normal_ordered().simplify(atol=1e-16))
exit_code = group_terms_by_electronic_structure(
canon, num_modes, two_body_physicist_order=False
)
filter_diagonal_terms(canon)
print(len(canon.groups))
# SqDRIFT parameters
times = [1.0, 10.0] # Total evolution times used for the subspace creation
num_exc = 10 # Number of excitation groups per circuit
num_circuits = 200 # Number of circuits to generate
init_circuits = []
hf_gate = InitializeModes.from_hartree_fock(norb, (num_elec_a, num_elec_b))
for time in times:
evo_gate = Evolution(num_modes, canon, time)
circ = FermionicCircuit(num_modes)
circ.append(hf_gate, circ.modes)
circ.append(evo_gate, circ.modes)
init_circuits.append(circ)
# Calculate weights for sampling (one per group)
qdrift = QDriftTrotterization(num_exc, rng=19)
pm = generate_preset_jw_pass_manager()
pm.optimization = FermionicPassManager([qdrift])
sqdrift_circuits = []
for circ in init_circuits:
sqdrift_circuits += (pm.run(circ) for _ in range(num_circuits))
for circ in sqdrift_circuits:
circ.measure_all()
print(len(sqdrift_circuits))
# This example assumes you have saved your IBM Quantum Platform account locally.
service = QiskitRuntimeService(channel="ibm_quantum_platform")
# Select backend (choose based on qubit requirements)
backend = service.least_busy(
operational=True,
simulator=False,
min_num_qubits=2 * norb,
)
print(f"Selected backend: {backend.name} ({backend.num_qubits} qubits)")
# Transpile for hardware
transpiled_circuits = transpile(
sqdrift_circuits,
backend=backend,
optimization_level=3,
seed_transpiler=42,
)
shots = 100
sampler = Sampler(mode=backend)
sampler.options.environment.job_tags = ["TUT-SqDRIFT"]
job = sampler.run(transpiled_circuits, shots=shots)
result = job.result()
# Extract counts from SamplerV2 results
all_counts = [pub_result.data.meas.get_counts() for pub_result in result]
# Set to True to filter out bitstrings that violate electron-number conservation
PRUNE = False
def is_valid_bitstring(
bitstring: str, norb: int, nelec: tuple[int, int]
) -> bool:
n_alpha, n_beta = nelec
return (
len(bitstring) == 2 * norb
and bitstring[norb:].count("1") == n_alpha
and bitstring[:norb].count("1") == n_beta
)
if PRUNE:
all_counts_filtered = []
for counts in all_counts:
filtered_count = {}
for key in counts:
if not is_valid_bitstring(key, norb, (num_elec_a, num_elec_b)):
continue
elif key not in filtered_count.keys():
filtered_count[key] = counts[key]
else:
filtered_count[key] += counts[key]
all_counts_filtered.append(filtered_count)
all_counts = all_counts_filtered
combined_counts = {}
for counts in all_counts:
for bitstring, count in counts.items():
combined_counts[bitstring] = combined_counts.get(bitstring, 0) + count
bit_array = BitArray.from_counts(combined_counts)
print(bit_array.num_shots)
print("Electron configuration:")
print(f" Total electrons: {nelec}")
print(f" Alpha electrons: {num_elec_a}")
print(f" Beta electrons: {num_elec_b}")
print(f" Number of orbitals: {norb}")
print(f" Number of spin orbitals (qubits): {2*norb}")
print(f"Integral shapes: h1e={h1e.shape}, h2e={h2e.shape}")
# SQD parameters
samples_per_batch = 300
num_batches = 3
max_iterations = 5
initial_occupancies = (
np.array([1] * num_elec_a + [0] * (norb - num_elec_a)), # alpha
np.array([1] * num_elec_b + [0] * (norb - num_elec_b)), # beta
)
result_history = []
def callback(results: list[SCIResult]):
result_history.append(results)
iteration = len(result_history)
print(f"Iteration {iteration}")
for i, result in enumerate(results):
print(f"\tSubsample {i}")
print(f"\t\tEnergy: {result.energy + nuclear_repulsion_energy}")
print(
f"\t\tSubspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
# Run SQD with configuration recovery
print("\nRunning SQD with configuration recovery...")
result = diagonalize_fermionic_hamiltonian(
h1e,
h2e,
bit_array,
samples_per_batch=samples_per_batch,
norb=norb,
nelec=(num_elec_a, num_elec_b),
num_batches=num_batches,
energy_tol=1e-3,
occupancies_tol=1e-3,
max_iterations=max_iterations,
initial_occupancies=initial_occupancies,
seed=42,
callback=callback,
)
computed_energy = result.energy + nuclear_repulsion_energy
print("FINAL SQD RESULTS")
print(f"Orbital occupancies (alpha): {result.orbital_occupancies[0]}")
print(f"Orbital occupancies (beta): {result.orbital_occupancies[1]}")
energy_error = abs(computed_energy - reference_energy)
print(f"Reference Energy: {reference_energy:.10f} Ha")
print(f"Computed Energy: {computed_energy:.10f} Ha")
print(f"Error: {energy_error:.10e} Ha")
Parsing assets/sqdrift/fcidump_files/N2_sto_3g
Reference FCI Energy = -107.6481842917 Ha
Nuclear Repulsion Energy = 23.7887003074 Ha
5060
400
Selected backend: ibm_aachen (156 qubits)
40000
Electron configuration:
Total electrons: 14
Alpha electrons: 7
Beta electrons: 7
Number of orbitals: 10
Number of spin orbitals (qubits): 20
Integral shapes: h1e=(10, 10), h2e=(10, 10, 10, 10)
Running SQD with configuration recovery...
Iteration 1
Subsample 0
Energy: -107.64593072647523
Subspace dimension: 7221
Subsample 1
Energy: -107.6458270048177
Subspace dimension: 7209
Subsample 2
Energy: -107.64007673117075
Subspace dimension: 7138
Iteration 2
Subsample 0
Energy: -107.64757372124944
Subspace dimension: 9009
Subsample 1
Energy: -107.64674060104392
Subspace dimension: 8245
Subsample 2
Energy: -107.64731360491942
Subspace dimension: 8178
Iteration 3
Subsample 0
Energy: -107.64765518770588
Subspace dimension: 8835
Subsample 1
Energy: -107.64767975712016
Subspace dimension: 8649
Subsample 2
Energy: -107.64761634415606
Subspace dimension: 8648
FINAL SQD RESULTS
Orbital occupancies (alpha): [0.99999504 0.9999964 0.99590318 0.9932359 0.96697158 0.96696295
0.99298797 0.03728154 0.03728186 0.00938359]
Orbital occupancies (beta): [0.9999946 0.99999641 0.99590413 0.99323077 0.96697361 0.96696174
0.99298424 0.03728121 0.03728169 0.00939159]
Reference Energy: -107.6481842917 Ha
Computed Energy: -107.6476797571 Ha
Error: 5.0453460619e-04 Ha
Langkah seterusnya
Jika anda mendapati kerja ini menarik, anda mungkin berminat dengan bahan berikut:
- Pepenjuruan kuantum Krylov berasaskan sampel bagi model kekisi fermionik - tutorial berkaitan yang menggunakan litar evolusi masa dan bukannya ansatz variasi.
- Pepenjuruan kuantum berasaskan sampel bagi Hamiltonian kimia - tutorial tentang cara membina litar local unitary cluster Jastrow (LUCJ) untuk simulasi kimia kuantum.
- Kertas SqDRIFT - kajian yang menjadi asas tutorial ini. (Perhatikan bahawa sebahagian pengoptimuman yang dibincangkan dalam kertas ini masih dalam usaha, dan tutorial ini tertakluk kepada perubahan pada masa hadapan berdasarkan perkembangan pustaka yang digunakan.)