Algoritma SqDRIFT untuk anggaran keadaan asas
- Python
- C++
Muat turun buku nota Python untuk menjalankan tutorial ini sendiri. Buku nota ini memuat turun input FCIDump jika ia belum wujud.
Anggaran penggunaan: 180 saat pada pemproses Heron r3 (NOTA: Ini hanyalah anggaran. Masa larian anda mungkin berbeza.)
Hasil pembelajaran
-
Pelajari cara mencipta litar dengan kedalaman lebih kecil berbanding dengan Trotterisasi
-
Ikuti aliran kerja hujung-ke-hujung untuk anggaran keadaan asas menggunakan qDRIFT dan SQD
-
Pelajari cara menggunakan
qiskit-fermionsbersama-sama dengan addon Qiskit lain untuk melaksanakan aliran kerja sedemikian
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 untuk memilih ansatz yang darinya bitstring disampel dengan ensembel litar evolusi-masa yang dibina terus daripada Hamiltonian sasaran. Ini dicapai dengan subpensampelan operator evolusi-masa yang lebih kecil daripada Hamiltonian berdasarkan pekalinya, yang dikenali sebagai kaedah Trotterisasi qDRIFT.
Kedua-dua pelaksanaan menggunakan Qiskit Fermions untuk mewakili Hamiltonian molekul dan membina litar bagi algoritma qDRIFT. Pelaksanaan Python menggunakan laluan Transpiler fermionik, manakala pelaksanaan C++ mensampel kumpulan secara eksplisit dan memetakannya kepada operator Qubit.
Andaikan Hamiltonian mempunyai bentuk:
di mana, tanpa kehilangan keumuman, kita memerlukan dan nilai eigen terbesar bagi adalah sama, dalam nilai mutlak, dengan . Sebarang faktor-awal bertanda atau kompleks diserap ke dalam , jadi pekali ialah berat positif secara ketat manakala membawa arah setiap sebutan. Di sini ialah bilangan sebutan (atau, selepas pengumpulan, bilangan kumpulan) dalam Hamiltonian; ia ialah ciri Hamiltonian dan berbeza daripada bilangan operator yang disampel ke dalam satu litar, ditulis di bawah.
Algoritma qDRIFT kemudian merealisasikan, bagi masa sasaran , sesuatu operator , di mana berjulat dari dan menandakan litar SqDRIFT ke-, ditakrifkan sebagai:
Di sini ialah bilangan operator yang disampel bagi setiap litar dan ialah bilangan litar dalam ensembel. Hasil darab berjalan merentas cabutan, bukan merentas kesemua terma Hamiltonian, dan kerana terma dicabut dengan penggantian, 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 terma mana yang dicabut. Keseragaman sudut langkah ialah ciri khusus qDRIFT: sesuatu pekali mempengaruhi hasil melalui berapa kerap termanya dicabut, bukan melalui berapa jauh terma itu diputar. Indeks disampel daripada taburan:
jadi siri ialah jujukan rawak indeks terma yang dicabut daripada taburan ini. Kerana adalah positif dan berjumlah , ini ialah taburan kebarangkalian ternormal, dan jangkaan saluran yang terhasil merentas cabutan rawak menghampiri evolusi di bawah , dengan ralat yang berkurangan apabila bertambah. Perhatikan bahawa ralat penghampiran bergantung pada dan bukan pada bilangan terma .
(Kertas SqDRIFT menulis bilangan terma sebagai dan panjang jujukan sebagai ; kami menggunakan dan di sini untuk mengekalkan kedua-duanya jelas berbeza.)
Tutorial ini menunjukkan cara menjana ensembel litar rawak sedemikian. Selepas kita mencipta litar ini, sama seperti kita mencipta subruang Krylov bagi operator yang berbeza, kita menyampel bitstring daripada beberapa operator sedemikian 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.5qiskit-fermions==0.1.0 (Perhatikan bahawa namanya jamak)- numpy
- pyscf
qiskit-aerqiskit-ibm-runtimeqiskit-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
# 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,
)
Pelaksanaan
Langkah 1: Petakan input klasik kepada masalah kuantum
Langkah Python di bawah menggunakan simulator Aer. Contoh perkakasan menyusul selepas itu.
Membaca dan Menyediakan FCIDump
Untuk tutorial ini, kita akan memuatkan Hamiltonian struktur elektronik bagi nitrogen (N2). Terdapat cara lain untuk mencipta operator fermionik juga. Rujuk dokumentasi di qiskit_fermions.operators.library.
Tentang FCIDump ini. Fail N2_sto_3g menerangkan molekul nitrogen () dalam asas minimum STO-3G pada jarak antara atom 1.09 , iaitu panjang ikatan keseimbangan eksperimen. Pengepalanya mengisytiharkan NORB=10, NELEC=14, dan MS2=0: 10 orbital ruang (maka 20 orbital spin, dan 20 qubit di bawah Jordan-Wigner), 14 elektron dalam singlet spin, jadi tujuh elektron dan tujuh elektron . Semua orbital diberikan label simetri 1, iaitu tiada simetri kumpulan titik yang dieksploitasi. Kerana ini adalah dump STO-3G ruang penuh, tiada orbital yang dibekukan dan ruang korelasi cukup kecil supaya tenaga rujukan FCI yang tepat boleh dikira secara klasik untuk perbandingan, seperti yang ditunjukkan dalam blok kod 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 kerana kamiran bergantung pada orbital SCF yang telah menumpu, fail yang dijana semula mungkin berbeza daripada fail yang disertakan dari segi fasa atau susunan orbital; jumlah tenaga tidak terjejas.
Mendapatkan fail. Cari FCIDump dalam repositori GitHub ini. Anda boleh jalankan kod di bawah untuk mengambilnya ke lokasi yang dijangkakan oleh selebihnya tutorial ini.
Mula-mula kita guna cisolver yang disediakan oleh pyscf untuk mendapatkan tenaga rujukan. Ini ialah tenaga keadaan asas sebenar bagi molekul yang kita sedang kerjakan. Untuk ini, kita akan mengisytiharkan norb dan nelec dahulu, iaitu bilangan orbital dan bilangan elektron masing-masing. Kemudian kita mengisytiharkan 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}")
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")
Reference FCI Energy = -107.6481842917 Ha
Nuclear Repulsion Energy = 23.7887003074 Ha
Memuatkan Hamiltonian
Dengan data yang diperlukan sudah 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 fermionik dengan qiskit-fermions
Kita akan memetakan Hamiltonian terlebih dahulu ke dalam model litar fermionik menggunakan qiskit-fermions, yang menyediakan laluan transpiler dan Gate khusus untuk litar fermionik. Ini akan digunakan kemudian sebelum laluan transpiler tradisional Qiskit untuk aliran kerja ini.
Pengumpulan sebutan
Untuk memastikan kebolehulangan hasil, kita mula-mula menggunakan canonical_order untuk menyusun sebutan berdasarkan strukturnya sahaja. Susunan operator dalam senarai canon dengan itu adalah tetap. Ini memastikan kebolehulangan operator yang dicipta kerana laluan QDriftTrotterization yang akan kita gunakan kelak mensampel indeks rawak untuk mencipta operator qDRIFT.
Dalam langkah ini, kita mengeksploitasi pelbagai simetri yang wujud dalam Hamiltonian struktur elektronik dengan mengumpulkan sebutan berkaitan yang mempunyai pekali yang sama. Walaupun langkah ini mengubah taburan pekali operator yang disampel oleh protokol qDRIFT, ini tidak menjejaskan jaminan penumpuannya. Yang penting, pengumpulan sebutan yang berkaitan melalui simetri menghasilkan pembatalan sebutan Pauli yang baik dan kedalaman litar yang lebih pendek secara keseluruhan apabila mengevolusikan-masa suatu keadaan di bawah tindakannya.
qiskit-fermions menyediakan fungsi group_terms_by_electronic_structure yang melakukan pengumpulan ini untuk kita.
Ambil perhatian bahawa group_terms_by_electronic_structure mengandaikan sebutan susunan normal.
Menapis sebutan pepenjuru
Kita mengeluarkan sebutan pepenjuru daripada Hamiltonian yang digunakan untuk menjana litar, supaya slot pensampelan qDRIFT sebanyak digunakan untuk sebutan yang menggerakkan populasi antara konfigurasi. Sebutan sedemikian sebaiknya ditapis keluar daripada Hamiltonian pada peringkat ini, sebelum Gate Evolution dibina dalam langkah seterusnya.
Sebutan yang dimaksudkan ialah sebutan yang bersifat pepenjuru dalam asas nombor-penghunian, iaitu hasil darab operator nombor . Tiga jenis sebutan tergolong dalam huraian ini:
-
ofset tenaga malar, hasil darab sifar operator nombor, yang evolusi masanya hanya menyumbang fasa global;
-
operator nombor individu , yang evolusi masanya berkurang kepada putaran satu-qubit;
-
hasil darab peringkat lebih tinggi seperti .
Dengan sendirinya, tiada satu pun daripada sebutan ini menggerakkan populasi antara konfigurasi nombor-penghunian; ia hanya bertindak ke atas fasa konfigurasi yang sudah wujud. Namun, ia bukan lengai: fasa relatif tersebut menyumbang kepada interferens yang dihasilkan oleh sebutan pengujaan kemudian dalam litar, jadi menapisnya mengubah evolusi yang sebenarnya dihasilkan dan boleh mengubah taburan pensampelan. Ini adalah penghampiran yang disengajakan dalam langkah penjanaan litar, dibuat untuk memfokuskan pensampelan pada sebutan pengujaan, bukannya langkah yang membiarkan taburan yang disampel tidak terjejas. Berbeza daripada pengumpulan simetri di atas, yang mengekalkan jaminan penumpuan qDRIFT, penapis ini mengubah operator yang dievolusikan. Oleh itu litar tidak lagi menghampiri evolusi di bawah Hamiltonian penuh, dan sempadan ralat qDRIFT terpakai kepada operator yang ditapis dan bukannya operator asal. Ini boleh diterima di sini kerana litar hanyalah heuristik pensampelan yang digunakan untuk mencadangkan konfigurasi: tiada sebutan yang hilang daripada anggaran tenaga itu sendiri, kerana penapis hanya terpakai kepada Hamiltonian yang digunakan untuk membina litar, manakala pepenjuruan klasik kemudiannya menggunakan Hamiltonian penuh, termasuk sebutan pepenjuru. Ketepatan SQD bergantung pada langkah klasik tersebut, yang kekal variasional dalam subruang yang disampel tanpa mengira bagaimana konfigurasi itu dicadangkan.
Fungsi filter_diagonal_terms() mengeluarkan sebutan sedemikian daripada operator secara in-place. Ia mengenal pasti sebutan tersebut daripada struktur susunan normalnya — multiset mod penciptaan yang sepadan dengan multiset mod pemusnahan — jadi ia hanya sah pada operator yang sudah tersusun secara normal. Andaian ini tidak diperiksa semasa masa jalan.
# 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 ensembel litar:
- Bilangan litar yang akan dijana:
num_circuits - Panjang setiap litar dari segi kumpulan pengujaan:
num_exc - Faktor untuk masa evolusi yang berbeza-beza:
times
Mencipta litar fermionik
Kita akan mencipta litar fermionik untuk setiap langkah masa. Setiap litar akan terdiri daripada satu Gate evolusi sahaja, dengan masa evolusi yang telah kita isytiharkan sebelum ini. Operator evolusi ialah Hamiltonian. Kemudian kita menjalankan laluan transpiler pada litar-litar ini untuk mencipta litar 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 bersamaan dengan tujuh bagi nitrogen. Keadaan ini mewakili tujuh elektron dan tujuh elektron 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 perkakasan kuantum
Sekarang setelah kita mempunyai litar kita, kita akan mula-mula menggunakan laluan yang tersedia dalam qiskit-fermions untuk melakukan pengoptimuman pada peringkat fermionik, diikuti dengan mentranspil litar kita untuk Backend pilihan. Oleh kerana ini adalah eksperimen simulator, kita akan melakukannya terlebih dahulu untuk AerSimulator.
Pengiraan berat untuk setiap kumpulan
Dalam langkah ini, kita melakukan pensampelan qDRIFT bagi sebutan secara stokastik dengan kebarangkalian berkadar dengan pekalinya dalam Hamiltonian. Laluan transpiler qDRIFT melakukan ini untuk kita. Kita kini boleh mencipta litar yang lebih cetek yang boleh dilaksanakan pada perkakasan dengan lebih cekap walaupun konektiviti qubit terhad, malah apabila Hamiltonian mengandungi gandingan jarak jauh dan sebutan lebih tinggi daripada kuadratik. Selepas pengumpulan sebutan, ia mensampel operator berdasarkan beratnya. Bagi setiap operator , berat ditakrifkan seperti berikut:
Oleh sebab sebutan telah dikumpulkan dalam Langkah 1, setiap di sini ialah satu kumpulan penuh: ialah pekali mutlak min bagi sebutan dalam kumpulan , dan setiap sebutan dalam kumpulan itu dievolusikan dengan pekalinya dikurangkan kepada tandanya.
Pengoptimuman fermionik dan asli-perkakasan
Fungsi generate_preset_jw_pass_manager() mengembalikan MultiStagePassManager yang menerima FermionicCircuit dan menghasilkan litar akhir yang dioptimumkan yang boleh kita transpil untuk dijalankan pada perkakasan kita. Kita menggantikan peringkat pengoptimuman lalainya dengan FermionicPassManager yang mengandungi laluan QDriftTrotterization kita:
-
Laluan
QDriftTrotterizationmenggunakan pengiraan berat dan pensampelan secara dalaman untuk menjana litar yang akan kita gunakan untuk pensampelan -
Laluan
RelabelModesialah laluan pengoptimuman lain yang boleh digunakan untuk memermutasikan mod fermionik bagi mengoptimumkan konektiviti merentas qubit dan mengurangkan kedalaman Gate; baca lebih lanjut dalam rujukan API
Peringkat-peringkat lain bagi MultiStagePassManager berjalan secara automatik dan mengendalikan pemetaan fermion-ke-qubit yang lengkap:
-
F2QLayout: Pengurus laluan pratetap mengenakan laluan
TrivialF2QLayout, yang memetakan secara remeh bit fermionik kepada qubit. -
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 kita selesai dengan pengoptimuman pada peringkat fermionik, kita boleh mentranspil litar untuk pelaksanaan pada simulator.
simulator = AerSimulator()
shots = 100
transpiled_circuits = transpile(sqdrift_circuits, simulator)
Langkah 3: Laksanakan menggunakan primitif Qiskit
Sekarang setelah kita mempunyai litar kita, 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 kembalikan hasil dalam format klasik yang dikehendaki
Menggunakan string bit untuk SQD
Kita kini boleh menjalankan skema pepenjuruan pada string bit yang dipilih untuk mencari nilai eigen terendah yang akan sepadan dengan tenaga keadaan asas molekul. Kita mencipta fungsi panggil balik, mengisytiharkan penghunian awal, dan menetapkan parameter sebelum akhirnya menjalankan skema pepenjuruan. Fungsi panggil balik digunakan untuk mencetak lelaran semasa dan anggaran nilai eigen semasa pada setiap lelaran.
Akhirnya, untuk mendapatkan anggaran keadaan asas, kita menambah nuclear_repulsion_energy kepada tenaga yang terhasil.
Nota: Dimensi subruang tidak tetap merentas lelaran, walaupun pada simulator tanpa hingar — setiap subsampel mengambil set konfigurasi yang berbeza, dan langkah pemulihan membentuk semula kolam antara lelaran, jadi dimensi yang dilaporkan berbeza-beza daripada satu subsampel ke subsampel seterusnya. Pensampelan tanpa hingar tidak dengan sendirinya menetapkan dimensi subruang yang dipilih. Namun, larian perkakasan cenderung memberikan subruang yang secara sistematik lebih besar, kerana tembakan berhingar memecahkan simetri bilangan zarah dan pemulihan konfigurasi menukarkannya kepada vektor asas tambahan. Oleh sebab itu, kita juga akan memperkenalkan satu lagi langkah untuk memangkas string bit 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 adalah untuk kemudahan tutorial yang sepatutnya berjalan pantas, bukan had mutlak pada kaedah ini.
Kos langkah klasik tidak ditentukan secara langsung oleh bilangan qubit. SQD mempepenjurukan Hamiltonian yang diunjurkan ke atas subruang yang direntangi 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 berapa banyak konfigurasi berbeza yang sebenarnya dihasilkan oleh litar — bersama-sama dengan aljabar linear jarang yang diperlukan untuk mengenakan Hamiltonian yang diunjurkan itu. Ruang CI penuh berkembang secara kombinatorik mengikut orbital dan elektron, tetapi subruang terpilih ialah sekeping kecil yang boleh dilaraskan daripadanya, dan kita mengawal saiznya secara langsung. Akibatnya, bilangan qubit dan kesukaran klasik boleh diubah secara 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 tetapi dipepenjurukan merentas subruang yang sangat besar.
Dalam praktiknya, saiz sistem yang boleh dilaksanakan bergantung pada dimensi subruang yang anda perlukan untuk ketepatan yang anda inginkan dan pada memori serta teras yang tersedia untuk penyelesai eigen. Ruang orbital yang lebih besar biasanya memang 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 mengandaikan had tetap, pendekatan praktikal ialah memerhatikan dimensi subruang yang dilaporkan dan penumpuan tenaga merentas lelaran, dan meningkatkan saiz subruang sehingga tenaga berhenti bertambah baik atau anda kehabisan memori yang tersedia.
Nota: Disebabkan ralat pensampelan daripada hingar pada perkakasan, subruang yang dicipta untuk pepenjuruan dalam larian perkakasan akan lebih besar daripada yang kita perolehi apabila menggunakan simulator. Walaupun ini meningkatkan dimensi subruang yang kita ingin pepenjurukan, aliran kerja masih memberikan kita jawapan yang tepat kerana keteguhan SQD terhadap hingar.
Pemangkasan string palsu
Di sini kita boleh memilih untuk melakukan satu langkah tambahan. Apabila kita mempunyai semua string bit daripada pelaksanaan litar, kita boleh sama ada menapis keluar string bit yang tidak sah sebelum menjalankan SQD, atau meneruskan tanpa pemangkasan. Melangkau pemangkasan secara amnya lebih baik untuk larian perkakasan, kerana ia membiarkan tembakan yang simetrinya terpecah tersedia untuk pemulihan konfigurasi, yang boleh membaikinya menjadi konfigurasi yang sah dan dengan itu meluaskan subruang berbanding membuang tembakan tersebut terus.
Oleh kerana nitrogen hanya boleh mempunyai tujuh elektron dan tujuh elektron , mana-mana string bit yang mempunyai lebih atau kurang daripada tujuh 1 dalam separuh pertama dan separuh kedua output boleh dibuang. Kita mentakrifkan fungsi yang memeriksa sama ada string bit itu sah, dan jika tidak, membuangnya. Setelah kita menapis keluar string bit palsu, selebihnya dihantar ke dalam skema pepenjuruan. Gunakan bendera PRUNE di bawah untuk bertukar antara kedua-dua tingkah laku.
Ingatlah bahawa pemangkasan hanyalah salah satu daripada beberapa pilihan yang membentuk subruang akhir, di samping bilangan litar, set masa evolusi, dan penapisan sebutan pepenjuru. Membandingkan larian yang dipangkas dengan larian yang tidak dipangkas hanya bermaklumat jika segala-galanya yang lain dikekalkan tetap; tab C++ membincangkan ini dengan lebih terperinci, memandangkan ia melakukan pasca-pemilihan dan bukannya pemulihan dan juga berbeza dari segi 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")
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
Sumber C++, projek CMake, dan arahan pembinaan terdapat dalam direktori docs/tutorials/assets/sqdrift/cpp bagi repositori dokumentasi Qiskit. Input FCIDump terdapat dalam direktori fcidump_files yang bersebelahan. Klon repositori, atau muat turun kedua-dua direktori, untuk membina dan menjalankan tutorial ini sendiri.
Anggaran penggunaan: 180 saat pada pemproses Heron r3 (NOTA: Ini hanyalah anggaran. Masa larian anda mungkin berbeza.)
Hasil pembelajaran (C++)
-
Pelajari cara mencipta litar dengan kedalaman lebih kecil berbanding dengan Trotterisasi
-
Ikuti aliran kerja hujung-ke-hujung untuk anggaran keadaan asas menggunakan qDRIFT dan SQD
-
Pelajari cara menggunakan
qiskit-fermionsbersama-sama dengan addon Qiskit lain untuk melaksanakan aliran kerja sedemikian
Prasyarat (C++)
-
Baca gambaran keseluruhan Pepenjuruan kuantum berasaskan sampel (SQD)
-
Baca pelajaran Pepenjuruan Kuantum Krylov Berasaskan Sampel (SKQD)
Latar Belakang (C++)
SqDRIFT ialah varian SKQD yang menggantikan keperluan untuk memilih ansatz yang darinya bitstring disampel dengan ensembel litar evolusi-masa yang dibina terus daripada Hamiltonian sasaran. Ini dicapai dengan subpensampelan operator evolusi-masa yang lebih kecil daripada Hamiltonian berdasarkan pekalinya, yang dikenali sebagai kaedah Trotterisasi qDRIFT.
Kedua-dua pelaksanaan menggunakan Qiskit Fermions untuk mewakili Hamiltonian molekul dan membina litar bagi algoritma qDRIFT. Pelaksanaan Python menggunakan laluan Transpiler fermionik, manakala pelaksanaan C++ mensampel kumpulan secara eksplisit dan memetakannya kepada operator Qubit.
Andaikan Hamiltonian mempunyai bentuk:
di mana, tanpa kehilangan keumuman, kita memerlukan dan nilai eigen terbesar bagi adalah sama, dalam nilai mutlak, dengan . Sebarang faktor-awal bertanda atau kompleks diserap ke dalam , jadi pekali ialah berat positif secara ketat manakala membawa arah setiap sebutan. Di sini ialah bilangan sebutan (atau, selepas pengumpulan, bilangan kumpulan) dalam Hamiltonian; ia ialah ciri Hamiltonian dan berbeza daripada bilangan operator yang disampel ke dalam satu litar, ditulis di bawah.
Algoritma qDRIFT kemudian merealisasikan, bagi masa sasaran , sesuatu operator , di mana berjulat dari dan menandakan litar SqDRIFT ke-, ditakrifkan sebagai:
Di sini ialah bilangan operator yang disampel bagi setiap litar dan ialah bilangan litar dalam ensembel. Hasil darab berjalan merentas cabutan, bukan merentas kesemua terma Hamiltonian, dan kerana terma dicabut dengan penggantian, 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 terma mana yang dicabut. Keseragaman sudut langkah ialah ciri khusus qDRIFT: sesuatu pekali mempengaruhi hasil melalui berapa kerap termanya dicabut, bukan melalui berapa jauh terma itu diputar. Indeks disampel daripada taburan:
jadi siri ialah jujukan rawak indeks terma yang dicabut daripada taburan ini. Kerana adalah positif dan berjumlah , ini ialah taburan kebarangkalian ternormal, dan jangkaan saluran yang terhasil merentas cabutan rawak menghampiri evolusi di bawah , dengan ralat yang berkurangan apabila bertambah. Perhatikan bahawa ralat penghampiran bergantung pada dan bukan pada bilangan terma .
(Kertas SqDRIFT menulis bilangan terma sebagai dan panjang jujukan sebagai ; kami menggunakan dan di sini untuk mengekalkan kedua-duanya jelas berbeza.)
Tutorial ini menunjukkan cara menjana ensembel litar rawak sedemikian. Selepas kita mencipta litar ini, sama seperti kita mencipta subruang Krylov bagi operator yang berbeza, kita menyampel bitstring daripada beberapa operator sedemikian dengan parameter masa yang berbeza. Ini memastikan pertindihan yang lebih tinggi antara vektor keadaan asas dan bitstring yang disampel.
Keperluan (C++)
Pelaksanaan ini berjalan pada perkakasan IBM Quantum® dan menggunakan boleh laku SBD luaran untuk pepenjurukan klasik. Ia tidak termasuk contoh simulator Python.
Ikuti arahan pembinaan C++ untuk memasang pengkompil, CMake, Rust, Boost, dan kebergantungan Qiskit. Pasang Rust dengan rustup: kebergantungan Qiskit menyematkan versi Rust mereka sendiri, yang dimuat turun oleh rustup secara automatik, tetapi Rust yang dipasang daripada Homebrew atau pengurus pakej Linux mungkin terlalu lama. Langkah SBD terakhir juga memerlukan MPI, OpenMP, dan BLAS/LAPACK. Windows buat masa ini tidak disokong oleh pelaksanaan ini.
Gunakan sumber C++ dan projek CMake yang lengkap daripada repositori. Kekalkan direktori fcidump_files yang bersebelahan semasa memuat turun contoh. Daripada checkout repositori dokumentasi, bina dengan arahan berikut. Pembinaan pertama memuat turun dan mengkompil semua kebergantungan dan mengambil masa beberapa minit.
cd docs/tutorials/assets/sqdrift/cpp
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
Pembinaan menulis boleh laku SqDRIFT ke docs/tutorials/assets/sqdrift/cpp (akar projek), bukan ke build/. Jalankan ./SqDRIFT dari akar projek supaya ia dapat menjumpai ../fcidump_files/N2_sto_3g. Semua arahan lain dalam tab ini juga dijalankan dari akar projek melainkan dinyatakan sebaliknya. Jika ./SqDRIFT gagal dengan ralat pustaka kongsi tiada, ikuti arahan laluan pustaka khusus platform dalam panduan pembinaan; pada macOS, boleh laku biasanya berjalan tanpanya.
Persediaan (C++)
Petikan di bawah ialah ekstrak daripada program SqDRIFT.cpp yang lengkap. Bina dan jalankan fail itu sebagai satu program.
Sebelum menjalankan SqDRIFT, pastikan token API IBM Quantum anda dikonfigurasikan untuk IBM Cloud Platform.
Jika anda mempunyai Python dengan qiskit-ibm-runtime dipasang, cara paling mudah untuk mencipta fail konfigurasi yang berformat betul ialah:
from qiskit_ibm_runtime import QiskitRuntimeService
QiskitRuntimeService.save_account(channel="ibm_quantum_platform", token="YOUR_IBM_QUANTUM_TOKEN_HERE")
Ini menulis ~/.qiskit/qiskit-ibm.json (Linux/macOS) atau %USERPROFILE%\.qiskit\qiskit-ibm.json (Windows) dengan bentuk yang betul. Jika anda lebih suka mencipta fail secara manual:
{
"default-ibm-quantum-platform": {
"channel": "ibm_quantum_platform",
"token": "YOUR_IBM_QUANTUM_TOKEN_HERE"
}
}
Dapatkan kunci API anda daripada IBM Quantum Platform.
Nota: Fail konfigurasi yang tiada atau tidak betul bentuknya menyebabkan panik Rust unwrap() yang tidak membantu semasa permulaan dan bukannya ralat yang jelas. Semak semula nama kunci (default-ibm-quantum-platform) dan nilai saluran (ibm_quantum_platform) jika anda menghadapi masalah.
Pelaksanaan (C++)
Langkah 1: Petakan input klasik kepada masalah kuantum (C++)
Muatkan Hamiltonian molekul
char filename[] = "../fcidump_files/N2_sto_3g";
QfFCIDump* fcidump = qf_fcidump_from_file(filename);
if (fcidump == nullptr) {
std::cerr << "Failed to load FCIDump!" << std::endl;
return 1;
}
uint32_t norb = qf_fcidump_norb(fcidump);
uint32_t nelec = qf_fcidump_nelec(fcidump);
uint32_t n_alpha = nelec / 2;
uint32_t n_beta = nelec - n_alpha;
uint32_t num_modes = 2 * norb;
uint32_t num_qubits = num_modes;
std::cout << "✓ Loaded N2 molecule (" << norb << " orbitals, "
<< num_qubits << " qubits, " << nelec << " electrons)" << std::endl;
Output:
✓ Loaded N2 molecule (10 orbitals, 20 qubits, 14 electrons)
Memuatkan Hamiltonian molekul daripada fail FCIDump. Bilangan qubit bersamaan dua kali bilangan orbital (spin-atas dan spin-bawah).
Cipta dan susun normal Hamiltonian
// Create and normal order Hamiltonian
QfFermionOperator* hamiltonian = qf_ferm_op_from_fcidump(fcidump);
QfFermionOperator* normal_ordered = qf_ferm_op_normal_ordered(hamiltonian, nullptr);
Menukar data FCIDump kepada operator fermionik dan menggunakan susunan normal untuk memudahkan struktur operator.
Kumpulkan sebutan mengikut struktur elektronik
// Group terms by electronic structure
QfExitCode exit_code = qf_ferm_op_group_terms_by_electronic_structure(
normal_ordered, num_modes, false
);
if (exit_code != QfExitCode_Success) {
std::cerr << "Failed to group terms!" << std::endl;
return 1;
}
uint32_t num_groups = qf_ferm_op_num_groups(normal_ordered);
std::cout << "Grouped into " << num_groups << " groups" << std::endl;
// Split into group operators
QfFermionOperator** group_ops = new QfFermionOperator*[num_groups];
qf_ferm_op_split_out_groups(normal_ordered, nullptr, 0, group_ops);
Output:
Grouped into 1902 groups
Mengumpulkan sebutan Hamiltonian yang berkaitan mengikut struktur elektronik sebelum membina litar evolusi mereka.
Kira pemberat persampelan, kemudian normalkan dan petakan kepada operator qubit
Susunan penting di sini. Normalisasi menskala semula setiap pekali kepada magnitud unit di tempat, jadi pemberat persampelan mesti dikira dahulu. Jika normalisasi dijalankan dahulu, setiap akan terbaca semula sebagai tepat 1.0 dan pemberat setiap kumpulan runtuh menjadi sekadar kiraan sebutannya — taburan persampelan kemudiannya akan didorong oleh saiz kumpulan dan bukannya oleh pekali fizikal.
Pemberat setiap kumpulan ialah pekali mutlak min bagi sebutannya. Laluan QDriftTrotterization pada tab Python menggunakan pemberat yang sama untuk Hamiltonian berkumpulan. Menggunakan hasil tambah sebaliknya akan menyebabkan berskala dengan saiz kumpulan, yang seterusnya mencondongkan masa evolusi .
// Calculate sampling weights BEFORE normalization.
std::vector<double> weights(num_groups);
double total_weight = 0.0;
for (uint32_t i = 0; i < num_groups; i++) {
QkComplex64* coeffs;
uint64_t num_terms;
qf_ferm_op_get_coeffs(group_ops[i], &coeffs, &num_terms);
double abs_sum = 0.0;
for (uint64_t j = 0; j < num_terms; j++) {
abs_sum += std::sqrt(coeffs[j].re * coeffs[j].re +
coeffs[j].im * coeffs[j].im);
}
weights[i] = (num_terms > 0) ? (abs_sum / static_cast<double>(num_terms)) : 0.0;
total_weight += weights[i];
}
std::cout << "Total weight (λ): " << total_weight << std::endl;
// Normalize the evolution operators term-by-term, now that the sampling
// weights above have already captured the original magnitudes.
for (uint32_t i = 0; i < num_groups; i++) {
QkComplex64* coeffs;
uint64_t num_terms;
qf_ferm_op_get_coeffs(group_ops[i], &coeffs, &num_terms);
for (uint64_t j = 0; j < num_terms; j++) {
const double magnitude = std::sqrt(coeffs[j].re * coeffs[j].re +
coeffs[j].im * coeffs[j].im);
if (magnitude > 0.0) {
coeffs[j].re /= magnitude;
coeffs[j].im /= magnitude;
}
}
}
QkObs** qubit_ops = new QkObs*[num_groups];
for (uint32_t i = 0; i < num_groups; i++) {
QfExitCode jw_exit = qf_ferm_op_jordan_wigner(group_ops[i], num_qubits, &qubit_ops[i]);
if (jw_exit != QfExitCode_Success) {
std::cerr << "Failed to map group " << i << std::endl;
return 1;
}
}
std::cout << "Mapped all " << num_groups << " normalized groups to qubit operators" << std::endl;
Output:
Total weight (λ): 337.261
Mapped all 1902 normalized groups to qubit operators
Pemberat ialah pekali mutlak min bagi sebutan kumpulan , dibaca daripada pekali asal. Normalisasi kemudian menskala semula setiap sebutan fermionik kepada magnitud unit sebelum transformasi Jordan-Wigner, dengan hanya mengekalkan fasa/tandanya dalam operator yang dievolusikan. Ini sepadan dengan evolusi sebutan ternormal qDRIFT: magnitud pekali menentukan kebarangkalian persampelan melalui , manakala evolusi litar menggunakan sebutan ternormal supaya pekali besar tidak dikira dua kali.
Sampel set operator
// SqDRIFT Sampling: Create operator sets
const int num_circuits = 100; // Number of circuits to create
const int ops_per_circuit = 10; // Operators per circuit
const double time_step = 1; // Time step for evolution
std::cout << "\n Generating " << num_circuits << " circuits with "
<< ops_per_circuit << " operators each..." << std::endl;
std::mt19937 gen(42);
std::discrete_distribution<> dist(weights.begin(), weights.end());
// Store sampled operator indices
std::vector<std::vector<int>> operator_sets(num_circuits);
for (int i = 0; i < num_circuits; i++) {
operator_sets[i].resize(ops_per_circuit);
for (int j = 0; j < ops_per_circuit; j++) {
operator_sets[i][j] = dist(gen);
}
}
Output:
Generating 100 circuits with 10 operators each...
Mencabut set operator untuk 100 litar rawak, setiap satu mengandungi 10 kumpulan yang disampel daripada taburan berpemberat. Langkah seterusnya menukar set ini menjadi ensembel litar qDRIFT.
Langkah 2: Bina litar qDRIFT (C++)
Dalam versi C++, langkah ini membina litar daripada set operator yang disampel. Transpilasi untuk backend sasaran berlaku dalam Langkah 3, sejurus sebelum setiap litar dihantar.
// Create Suzuki-Trotter circuits by composing all operators
std::vector<Qiskit::circuit::QuantumCircuit> circuits;
circuits.reserve(num_circuits);
const uint32_t trotter_order = 1;
const uint32_t trotter_reps = 1;
const bool preserve_order = false;
const bool insert_barriers = false;
// Gate name mapping is static — compute once before all loops
auto name_map = Qiskit::circuit::get_standard_gate_name_mapping();
for (int circuit_idx = 0; circuit_idx < num_circuits; circuit_idx++) {
// Create initial circuit with state preparation using C++ API
Qiskit::circuit::QuantumCircuit qc(num_qubits, num_qubits);
// State preparation: apply X gates to spin-up qubits 0..n_alpha-1
// and spin-down qubits norb..norb+n_beta-1 (derived from FCIDump NELEC)
for (uint32_t q = 0; q < n_alpha && q < num_qubits; q++) {
qc.x(q);
}
for (uint32_t q = norb; q < norb + n_beta && q < num_qubits; q++) {
qc.x(q);
}
// For each operator in this circuit's set
for (int op_idx = 0; op_idx < ops_per_circuit; op_idx++) {
int group_idx = operator_sets[circuit_idx][op_idx];
double evolution_time = (total_weight * time_step) / ops_per_circuit;
// Create Suzuki-Trotter evolution circuit
QkCircuit* evolution_circuit = qk_circuit_library_suzuki_trotter(
qubit_ops[group_idx], trotter_order, trotter_reps,
evolution_time, preserve_order, insert_barriers
);
if (evolution_circuit != nullptr) {
// Get number of instructions
uint32_t num_ops = qk_circuit_num_instructions(evolution_circuit);
// Manually append each instruction
for (uint32_t i = 0; i < num_ops; i++) {
QkCircuitInstruction inst;
qk_circuit_get_instruction(evolution_circuit, i, &inst);
// Prepare qubit vector
std::vector<uint32_t> qubits(inst.num_qubits);
for (uint32_t j = 0; j < inst.num_qubits; j++) {
qubits[j] = inst.qubits[j];
}
// Get operation kind
QkOperationKind kind = qk_circuit_instruction_kind(evolution_circuit, i);
// Get mutable pointer to circuit
QkCircuit* mutable_circuit = qc.get_rust_circuit().get();
// Append based on operation type
if (kind == QkOperationKind_Gate) {
// Convert gate name string to QkGate enum
std::string gate_name(inst.name);
auto gate_it = name_map.find(gate_name);
if (gate_it != name_map.end()) {
QkGate gate_enum = gate_it->second.gate_map();
qk_circuit_parameterized_gate(
mutable_circuit,
gate_enum, // Use QkGate enum, not string
qubits.data(),
inst.params
);
} else {
std::cerr << "Unknown gate in evolution circuit: " << gate_name << std::endl;
qk_circuit_instruction_clear(&inst);
qk_circuit_free(evolution_circuit);
return 1;
}
} else if (kind == QkOperationKind_Barrier) {
qk_circuit_barrier(
mutable_circuit,
qubits.data(),
inst.num_qubits
);
} else if (kind == QkOperationKind_Reset) {
qk_circuit_reset(
mutable_circuit,
qubits[0]
);
}
qk_circuit_instruction_clear(&inst);
}
qk_circuit_free(evolution_circuit);
}
}
circuits.push_back(qc);
// Display progress
if ((circuit_idx + 1) % 10 == 0) {
std::cout << " Created " << (circuit_idx + 1) << "/"
<< num_circuits << " circuits" << std::endl;
}
}
std::cout << "✓ Created all " << num_circuits << " Suzuki-Trotter circuits" << std::endl;
Output:
Created 10/100 circuits
Created 20/100 circuits
Created 30/100 circuits
Created 40/100 circuits
Created 50/100 circuits
Created 60/100 circuits
Created 70/100 circuits
Created 80/100 circuits
Created 90/100 circuits
Created 100/100 circuits
✓ Created all 100 Suzuki-Trotter circuits
Program lengkap kemudian mencetak statistik litar dan indeks operator bagi litar 0, yang ditinggalkan di sini.
Bagi setiap operator yang disampel, mencipta litar evolusi Suzuki-Trotter dengan masa , dengan ialah bilangan operator yang disampel bagi setiap litar dan ialah langkah masa. Oleh sebab setiap kumpulan yang disampel dinormalkan sebutan demi sebutan sebelum pemetaan, evolusi ini hanya menggunakan fasa/tanda setiap sebutan semasa putaran; magnitud hanya menyumbang melalui taburan persampelan. Keadaan awal Hartree-Fock disediakan dengan menggunakan get X pada qubit 0-6 dan 10-16 (tujuh elektron dalam setiap sektor spin). Arahan setiap litar evolusi ditambah secara manual pada litar utama menggunakan API C++.
Langkah 3: Laksanakan menggunakan primitif Qiskit (C++)
std::cout << "\n Connecting to IBM Quantum Cloud..." << std::endl;
// Initialize IBM Quantum Compute Service using qiskit-cpp
Qiskit::service::QiskitRuntimeService service;
std::cout << "✓ Connected to IBM Quantum" << std::endl;
// Get backend
const std::string backend_name = "ibm_fez";
auto backend = service.backend(backend_name);
if (backend.name().empty()) {
std::cerr << "Backend " << backend_name
<< " is not available to this account." << std::endl;
return 1;
}
std::cout << "✓ Selected backend: " << backend.name() << std::endl;
// Create sampler primitive
const int32_t shots = 100; // Number of shots per circuit
Qiskit::primitives::BackendSamplerV2 sampler(backend, shots);
std::cout << "\n Submitting " << num_circuits << " circuits ("
<< shots << " shots each)..." << std::endl;
// Submit one job per circuit.
std::vector<std::shared_ptr<Qiskit::primitives::BasePrimitiveJob>> jobs;
for (int i = 0; i < num_circuits; i++) {
for (uint32_t q = 0; q < num_qubits; q++) {
circuits[i].measure(q, q);
}
Qiskit::circuit::QuantumCircuit transpiled_qc =
Qiskit::compiler::transpile(circuits[i], backend, 2, 1.0, 42);
std::vector<Qiskit::primitives::SamplerPub> pubs;
pubs.push_back(Qiskit::primitives::SamplerPub(transpiled_qc, shots));
jobs.push_back(sampler.run(pubs));
}
std::cout << "\n⏳ Waiting for " << num_circuits << " jobs to complete..." << std::endl;
// Collect results — poll each job and extract bitstrings
std::vector<boost::dynamic_bitset<>> all_bitstrings;
for (int i = 0; i < num_circuits; i++) {
while (!jobs[i]->in_final_state()) {
sleep(5);
}
if (jobs[i]->done()) {
auto result = jobs[i]->result();
if (result.size() > 0) {
auto& pub_result = result[0];
auto& bit_array = pub_result.data();
for (size_t s = 0; s < bit_array.num_shots(); s++) {
auto sample = bit_array[s].to_string();
boost::dynamic_bitset<> bs(num_qubits);
for (uint32_t bit = 0; bit < num_qubits; bit++) {
bs[bit] = (sample[num_qubits - 1 - bit] == '1');
}
all_bitstrings.push_back(bs);
}
}
} else {
std::cout << " Circuit " << (i+1) << " did not complete successfully" << std::endl;
}
}
std::cout << "\n✓ Collected " << all_bitstrings.size()
<< " total bitstrings from all circuits" << std::endl;
Menyambung ke IBM Quantum, memilih backend mengikut nama, mentranspil setiap litar untuk backend itu, dan menghantar satu kerja bagi setiap litar: 100 kerja Sampler berasingan dengan 100 tembakan setiap satu pada daftar 20 qubit. Kemudian ia menyemak status setiap kerja secara berkala sehingga selesai dan mengumpul rentetan bit daripada result[0]. Setiap kerja menggunakan masa QPU dan menunggu dalam baris gilir secara berasingan, jadi jangkakan langkah ini mengambil masa yang jauh lebih lama daripada satu kerja kelompok tunggal.
Keputusan perkakasan yang dipetik pada halaman ini datang daripada larian pada ibm_pittsburgh (100 litar × 100 tembakan), dengan backend_name diubah daripada lalai ibm_fez. Sampel yang disimpan telah dipasca-proses dengan susunan bit penentu yang dibetulkan seperti yang ditunjukkan dalam Langkah 4. Hingar perkakasan berbeza antara larian, jadi kiraan dan tenaga akhir anda akan berbeza.
Output:
Connecting to IBM Quantum Cloud...
✓ Connected to IBM Quantum
✓ Selected backend: ibm_pittsburgh
Submitting 100 circuits (100 shots each)...
⏳ Waiting for 100 jobs to complete...
✓ Collected 10000 total bitstrings from all circuits
Menukar backend: Tukar backend_name dalam SqDRIFT.cpp untuk menyasarkan peranti lain; service.backends() menyenaraikan apa yang boleh dicapai oleh akaun anda.
Langkah 4: Pasca-proses dan kembalikan hasil dalam format klasik yang dikehendaki (C++)
Pasca-pilih rentetan bit dan tulis rentetan CI
// Postselect bitstrings by Hamming weight (derived from FCIDump NELEC)
std::vector<double> bitstring_weights(all_bitstrings.size(), 1.0);
auto [filtered_bitstrings, filtered_weights] = Qiskit::addon::sqd::postselect_bitstrings(
all_bitstrings,
bitstring_weights,
Qiskit::addon::sqd::MatchesRightLeftHamming<uint32_t>(n_alpha, n_beta)
);
std::ignore = filtered_weights;
std::cout << "✓ Postselected " << filtered_bitstrings.size()
<< " bitstrings with Hamming weight ("
<< n_alpha << "," << n_beta << ")" << std::endl;
// Convert bitstrings to CI strings using SQD addon
auto ci_strings = Qiskit::addon::sqd::bitstrings_to_ci_strings_symmetrize_spin(
filtered_bitstrings,
std::nullopt // No dimension limit
);
std::cout << "✓ Generated " << ci_strings.size() << " CI strings" << std::endl;
// Write CI strings to file for SBD
std::ofstream alpha_file("alphadets_from_sqd.txt");
for (const auto& ci_string : ci_strings) {
std::string bitstr;
// SBD expects the highest orbital index first (bit zero on the right).
boost::to_string(ci_string, bitstr);
alpha_file << bitstr << "\n";
}
alpha_file.close();
std::cout << "✓ Wrote " << ci_strings.size()
<< " CI strings to alphadets_from_sqd.txt" << std::endl;
std::cout << " Ready for SBD diagonalization!" << std::endl;
Output:
✓ Postselected 1137 bitstrings with Hamming weight (7,7)
✓ Generated 49 CI strings
✓ Wrote 49 CI strings to alphadets_from_sqd.txt
Ready for SBD diagonalization!
Memasca-pilih rentetan bit dengan berat Hamming yang betul (n_alpha elektron spin-atas, n_beta elektron spin-bawah, diperoleh daripada medan NELEC FCIDump), menukarnya kepada rentetan Interaksi Konfigurasi (CI) menggunakan simetrisasi spin, dan menulisnya ke alphadets_from_sqd.txt untuk Pepenjurukan Asas Terpilih seterusnya.
Bersihkan sumber
// Cleanup
// circuits vector will be automatically cleaned up (RAII)
// Free qubit operators
for (uint32_t i = 0; i < num_groups; i++) {
qk_obs_free(qubit_ops[i]);
}
delete[] qubit_ops;
// Free fermionic operators
for (uint32_t i = 0; i < num_groups; i++) {
qf_ferm_op_free(group_ops[i]);
}
delete[] group_ops;
qf_ferm_op_free(normal_ordered);
qf_ferm_op_free(hamiltonian);
qf_fcidump_free(fcidump);
return 0;
Membebaskan dengan betul semua memori dan sumber yang diperuntukkan. std::vector<QuantumCircuit> C++ dibersihkan secara automatik melalui RAII. Operator qubit, operator fermionik, dan data FCIDump dibebaskan secara manual.
Sekarang kita telah mencipta asas untuk memproyeksikan Hamiltonian kita, kita boleh meneruskan proses pepenjurukan untuk mendapatkan anggaran keadaan asas. Jalankan arahan berikut dari akar projek, docs/tutorials/assets/sqdrift/cpp.
Bina binari SBD diag
CMake mengambil SBD sebagai sumber sahaja ke dalam deps/sbd/ (pembinaan utama sengaja melangkau pengkompilannya). Aplikasi SBD menggunakan Makefile biasa, dan SBD sendiri memerlukan MPI, OpenMP, dan BLAS/LAPACK — jadi make biasa tidak mencukupi sehingga semua itu dipasang dan fail Configuration aplikasi sepadan dengan rantaian alat anda.
Makefile membaca bendera pengkompil dan pautannya daripada fail Configuration dalam direktori yang sama. Versi yang dihantar oleh huluan ditala untuk satu susun atur macOS/Homebrew tertentu dan tidak akan dibina begitu sahaja pada mesin biasa — terutamanya -I/opt/homebrew/opt/llvm/include tidak mengandungi omp.h (ia terletak dalam direktori sumber clang khusus versi), dan clang++ Apple menolak -fopenmp terus:
clang++: error: unsupported option '-fopenmp'
Resipi berikut disahkan berfungsi pada macOS (Apple Silicon, Darwin 25.6, Open MPI 5.x + Homebrew LLVM).
macOS (Apple Silicon) — telah diuji
# 1. Install MPI and an OpenMP-capable compiler.
# Apple clang cannot do OpenMP, so Homebrew's LLVM provides both
# clang++ and libomp. BLAS/LAPACK come from Apple's Accelerate
# framework, which is already part of macOS — no openblas needed.
brew install open-mpi llvm
Kemudian gantikan deps/sbd/apps/chemistry_tpb_selected_basis_diagonalization/Configuration dengan:
# Path to the SBD library
SBD_PATH=../..
# MPI C++ compiler. OMPI_CXX points Open MPI's wrapper at Homebrew's clang++,
# which (unlike Apple clang) supports -fopenmp.
CCCOM=OMPI_CXX=/opt/homebrew/opt/llvm/bin/clang++ mpicxx
# Build flags. omp.h lives in a version-specific clang resource dir, so let
# Homebrew's clang++ find it itself rather than hardcoding an -I path.
CCFLAGS= -std=c++17 -fopenmp -O3
# Link flags: OpenMP runtime from Homebrew LLVM, BLAS/LAPACK from the
# Accelerate framework that ships with macOS (no openblas install needed).
SYSLIB= -L/opt/homebrew/opt/llvm/lib -lomp -framework Accelerate
# 2. Build
cd deps/sbd/apps/chemistry_tpb_selected_basis_diagonalization
make
cd ../../../..
Pembinaan yang berjaya mencetak dua baris kompil/pautan dan meninggalkan binari diag di sebelah Makefile. Satu ld: warning: ignoring duplicate libraries: '-lomp' tidak berbahaya.
macOS Intel: Awalan Homebrew ialah /usr/local dan bukannya /opt/homebrew, jadi gantikannya dalam kedua-dua laluan sebelum ini.
Linux (Ubuntu/Debian) — belum diuji
Prasyarat berikut meliputi MPI, OpenMP, dan BLAS/LAPACK. GCC menyokong -fopenmp secara asli, jadi tiada penggantian pengkompil diperlukan. Ini belum disahkan pada Linux:
sudo apt install -y libopenmpi-dev libomp-dev libblas-dev liblapack-dev
SBD_PATH=../..
CCCOM=mpicxx
CCFLAGS= -std=c++17 -fopenmp -O3
SYSLIB= -llapack -lblas
Jalankan SqDRIFT untuk menjana rentetan CI
Jika anda sudah menjalankan ./SqDRIFT melalui Langkah 1–4, alphadets_from_sqd.txt sudah wujud dalam akar projek dan anda boleh melangkau langkah ini. Jika tidak, jalankannya dari akar projek:
./SqDRIFT
# Writes alphadets_from_sqd.txt in the project root
Jalankan pepenjurukan
cd deps/sbd/apps/chemistry_tpb_selected_basis_diagonalization
# The FCIDump lives in the shared sqdrift/fcidump_files/ directory, one level
# above this tutorial's project root, hence five levels up from here.
ln -sf ../../../../../fcidump_files/N2_sto_3g fcidump.txt
# alphadets_from_sqd.txt is written by ./SqDRIFT into the project root itself,
# which is four levels up.
ln -sf ../../../../alphadets_from_sqd.txt alphadets.txt
./diag \
--fcidump fcidump.txt \
--adetfile alphadets.txt \
--method 0 \
--iteration 100 \
--block 10 \
--tolerance 1e-8 \
| tee ../../../../sbd_output.txt
cd ../../../..
Parameter:
--fcidump: Laluan ke fail FCIDump yang mengandungi Hamiltonian--adetfile: Laluan ke fail penentu alfa (rentetan CI daripada SqDRIFT)--method 0: Kaedah pepenjurukan Davidson--iteration 100: Bilangan maksimum lelaran Davidson--block 10: Saiz blok untuk algoritma Davidson--tolerance 1e-8: Toleransi penumpuan untuk tenaga
Elapsed time for helper construction 0.001512 (sec)
Elapsed time for init 1e-06 (sec)
Davidson iteration 0.0 (tol=0.1857493937033264): -107.493531425221
Davidson iteration 0.1 (tol=0.0455699976495391): -107.5098150493379 -105.93368397386
Davidson iteration 0.2 (tol=0.009291923190816917): -107.5108356028703 -106.1245977311231 -105.6823366883346
Davidson iteration 0.3 (tol=0.001488674426419356): -107.5108711084406 -106.3219246016818 -105.7376912083761 -105.4392974334491
Davidson iteration 0.4 (tol=0.0003188697654737439): -107.5108722237947 -106.5935152393201 -106.1108017566682 -105.4932621297472
Davidson iteration 0.5 (tol=3.080273116687244e-05): -107.5108722587463 -106.7245930484005 -106.2313070085243 -105.6423237790271
Davidson iteration 0.6 (tol=2.981303155550973e-06): -107.5108722590151 -106.8008116251279 -106.330411722324 -105.69489630447
Davidson iteration 0.7 (tol=4.565075388900609e-07): -107.5108722590186 -106.8090569201597 -106.3345525819528 -106.0578325555555
Davidson iteration 0.8 (tol=4.870689101320012e-08): -107.5108722590186 -106.8098939189322 -106.3973756614146 -106.1617813840531
Davidson iteration 0.9 (tol=6.224330513865847e-09): -107.5108722590187 -106.8159869231416 -106.4315891597535 -106.2222021964013
Elapsed time for davidson 0.031276 (sec)
Elapsed time for diagonalization 0.031286 (sec)
Elapsed time for mult 0.002511 (sec)
Energy = -107.5108722590187
Elapsed time for measurement 4.6e-05 (sec)
Sample-based diagonalization: Energy = -107.5108722590187
Sample-based diagonalization: density = [1.999989340109303,1.99999662423008,1.999199121034164,1.981735389885314,1.999825046204946,1.999847567448985,1.995347419300126,0.01136512984757364,0.009844780833945199,0.002849581105572152
Sample-based diagonalization: carryover bitstrings = [], size = 0
Baris Energy = -107.5108722590187 ialah anggaran keadaan asas ke atas subruang yang disampel.
Bagaimana ini berbeza daripada contoh Python
Kedua-dua contoh berbeza dalam beberapa aspek serentak, bukan sahaja dalam cara ia menangani rentetan bit simetri buruk, jadi ia tidak dijangka menghasilkan tenaga yang sama:
| Tab C++ | Tab Python | |
|---|---|---|
| Rentetan bit simetri buruk | Dibuang oleh postselect_bitstrings pada berat Hamming | Dibaiki melalui pemulihan konfigurasi lelaran |
| Struktur gelung | Satu laluan: sampel → pasca-pilih → pepenjurukan sekali | Gelung luar: pepenjurukan, baca penghunian orbital, pulihkan konfigurasi, pepenjurukan semula |
| Bilangan litar | 100 litar (num_circuits = 100) | 400 litar (200 bagi setiap masa evolusi) |
| Masa evolusi | Satu (time_step = 1) | Dua masa, times = [1.0, 10.0] |
| Sebutan pepenjuru | Dikekalkan dalam operator yang disampel | Dibuang oleh filter_diagonal_terms, mengubah taburan persampelan dan litar |
| Susunan sebutan | Dikumpulkan sahaja | canonical_order dikenakan sebelum pengumpulan untuk kebolehulangan |
| Penyelesai pepenjurukan | SBD (diag, binari MPI/OpenMP luaran) | Penyelesai dalam proses qiskit-addon-sqd |
| Backend | Ditetapkan mengikut nama (backend_name = "ibm_fez"; larian pada halaman ini menggunakan ibm_pittsburgh) | Backend operasi least_busy dengan bilangan qubit yang mencukupi |
| Transpilasi | Setiap litar secara berasingan, aras pengoptimuman 2 | Semua litar bersama-sama, aras pengoptimuman 3 |
| Penghantaran kerja | 100 kerja, satu litar setiap satu | Satu kerja Sampler yang mengandungi kesemua 400 litar |
| Larian simulator | Tiada; perkakasan sahaja | Contoh simulator tanpa hingar sebelum larian perkakasan |
Kedua-dua versi menggunakan Hamiltonian yang sama, mengumpulkan sebutannya mengikut struktur elektronik, memberi pemberat kepada setiap kumpulan mengikut pekali mutlak min sebutannya, bermula daripada keadaan Hartree-Fock, dan mensampel 10 kumpulan serta 100 tembakan bagi setiap litar.
Setiap perbezaan menjejaskan subruang yang disampel, dan oleh itu tenaga, secara bebas. Ensembel yang lebih besar dan masa evolusi kedua yang lebih panjang kedua-duanya meluaskan konfigurasi yang diterokai oleh contoh Python; persampelan pada lebih daripada satu masa memberikan SqDRIFT liputan seakan-akan Krylov bagi ruang tenaga rendah. Menapis sebutan pepenjuru mengubah operator yang boleh dicabut oleh pensampel dan pemberatnya, jadi kedua-dua larian mensampel taburan yang berbeza.
Pasca-pemilihan ialah penapis yang ketat, dan tembakan dengan bilangan zarah yang salah dibuang terus. Dalam larian di atas, itu membuang kebanyakan data, hanya 1137 daripada 10000 tembakan lulus semakan berat Hamming (7,7) (kira-kira 11%), runtuh menjadi hanya 49 penentu- yang berbeza. Sebaliknya, pemulihan konfigurasi menggunakan penghunian orbital purata daripada pepenjurukan terdahulu untuk membalikkan bit dan membaiki tembakan sedemikian, mengitar semula apa yang akan dibuang oleh pasca-pemilihan. Suapan balik itu menjadikannya secara semula jadi berlelaran: recover_configurations dalam qiskit-addon-sqd-hpc memerlukan argumen avg_occupancies yang hanya wujud selepas pepenjurukan dijalankan.
Larian C++ ini menghasilkan subruang 49 penentu- dan tenaga -107.5109 Ha. Ruang penentu- penuh untuk tujuh elektron dalam 10 orbital hanyalah rentetan, jadi memberikan kesemua 120 kepada binari diag yang sama menghasilkan tenaga FCI tepat -107.6482 Ha. Oleh itu larian ini terletak kira-kira 0.1373 Ha di atas nilai tepat sambil merangkumi 49 daripada 120 penentu-. Oleh sebab SQD bersifat variasi dalam subruang yang dipilih, subruang yang lebih kecil hanya boleh menaikkan tenaga, jadi subruang yang tidak lengkap ialah tafsiran paling langsung bagi jurang itu.
Semakan FCI tepat ini hanya mungkin kerana contoh ini kecil; ia bukan sebahagian daripada aliran kerja SqDRIFT, dan untuk saiz sistem yang benar-benar memerlukan SQD, rujukan sedemikian tidak wujud. Ia hanya berfungsi di sini untuk mengukur berapa banyak ruang yang dipulihkan oleh larian ini.
Perbandingan dengan contoh Python tidak terkawal, jadi perbezaan tidak sepatutnya dikaitkan dengan ketiadaan pemulihan konfigurasi sahaja; perhatikan bahawa bilangan litar, masa evolusi tunggal, penapisan sebutan pepenjuru, dan hingar perkakasan semuanya berbeza serentak dan semuanya membentuk subruang.
Untuk merapatkan jurang, berikut ialah beberapa langkah seterusnya yang boleh dicuba selepas melengkapkan tutorial ini: naikkan shots, num_circuits, atau ops_per_circuit; sampel pada lebih daripada satu time_step; dan tambah gelung pemulihan di atas Qiskit::addon::sqd::recover_configurations.
Langkah seterusnya
-
Pepenjurukan kuantum Krylov berasaskan sampel bagi model kekisi fermionik — tutorial berkaitan yang menggunakan litar evolusi masa dan bukannya ansatz variasi.
-
Pepenjurukan kuantum berasaskan sampel bagi Hamiltonian kimia — tutorial tentang cara membina litar kluster unitari setempat 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 proses, dan tutorial ini tertakluk kepada perubahan pada masa depan berdasarkan perkembangan pustaka yang digunakan.)