Pornire rapidă
Acest ghid prezintă un exemplu minimal funcțional al pachetului qiskit-addon-sqd. Folosim diagonalizarea cuantică bazată pe eșantioane (SQD) pentru a rafina eșantioanele zgomotoase obținute dintr-un circuit cuantic care aproximează starea fundamentală a unui hamiltonian chimic. Energiile stării fundamentale sunt aproximate prin diagonalizarea hamiltonianului în subspații formate din eșantioanele rafinate.
Pentru fluxuri de lucru complete mai explicative care rulează pe hardware cuantic, consultă tutorialele:
-
Diagonalizare cuantică bazată pe eșantioane: flux de lucru pentru hamiltonian chimic
-
Diagonalizare cuantică Krylov bazată pe eșantioane: flux de lucru pentru model de rețea fermionic
1. Pregătește intrările pentru SQD
Mai întâi, definim hamiltonianul chimic și circuitul cuantic care aproximează starea sa fundamentală. Sistemul studiat în acest exemplu este N₂ la geometria de echilibru în baza STO-3G, cu doi orbitali de miez înghețați. Acest sistem are 8 orbitali spațiali și 10 electroni, ceea ce înseamnă că circuitul cuantic este definit pe 16 qubiți, unde qubiții 0-7 reprezintă orbitalii cu spin în sus, iar qubiții 8-15 reprezintă orbitalii cu spin în jos. Deoarece acesta este un sistem cu strat închis cu 10 electroni, știm că fiecare eșantion valid fizic ar trebui să aibă ponderea Hamming 10, unde ponderea Hamming a ambelor jumătăți, stângă și dreaptă, ale șirului de biți este 5.
# Added by doQumentation — installs the packages this notebook needs if they are missing
import importlib.util
_needed = {"numpy": "numpy", "pyscf": "pyscf", "qiskit_addon_sqd": "qiskit-addon-sqd"}
_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")
from pyscf import ao2mo, gto, mcscf, scf
# Construct the Hamiltonian, pull out integrals and other information about the system
mol = gto.M(
atom="N 0 0 0; N 0 0 1.09768", basis="sto-3g", spin=0, charge=0, verbose=0
)
mf = scf.RHF(mol).run()
cas = mcscf.CASCI(mf, ncas=8, nelecas=10)
hcore, nuclear_repulsion_energy = cas.get_h1eff()
eri = cas.get_h2eff()
num_orbitals = cas.ncas
eri = ao2mo.restore(1, eri, num_orbitals)
n_alpha, n_beta = cas.nelecas
nelec = (n_alpha, n_beta)
print(
f"Spatial orbitals: {num_orbitals}\nQubits: {num_orbitals * 2}\nElectrons (alpha, beta): {nelec}"
)
Spatial orbitals: 8
Qubits: 16
Electrons (alpha, beta): (5, 5)
2. Generează eșantioane zgomotoase
Într-un flux de lucru real ai proiecta un circuit ansatz, l-ai optimiza pentru hardware și l-ai eșantiona pe QPU. Aici sărim peste acești pași și extragem în schimb 10,000 de șiruri de biți uniform aleatorii. Deși eșantioanele noastre nu conțin nicio informație despre starea fundamentală reală, vom vedea că protocolul SQD poate totuși recupera starea fundamentală adevărată rafinând iterativ aceste eșantioane, diagonalizând și folosind energiile de la fiecare iterație pentru a orienta runda următoare de rafinare a șirurilor de biți. Putem recupera în cele din urmă starea fundamentală exactă în acest mod deoarece subspațiul care conține starea fundamentală este suficient de mic pentru a fi calculat exact. Mai precis, deoarece știm că fiecare jumătate a fiecărui șir de biți (8 biți) ar trebui să aibă ponderea Hamming 5, știm că subspațiul Hilbert complet al sistemului are dimensiunea .
import numpy as np
from qiskit_addon_sqd.counts import generate_bit_array_uniform
# Generate some uniformly-random bitstrings to simulate QPU samples
rng = np.random.default_rng(24)
bit_array = generate_bit_array_uniform(
10_000, num_orbitals * 2, rand_seed=rng
)
print(
f"Generated {bit_array.num_shots} uniformly-random, {bit_array.num_bits}-qubit samples."
)
Generated 10000 uniformly-random, 16-qubit samples.
3. Post-procesează cu SQD
Obiectivul algoritmului SQD este să rafineze eșantioanele zgomotoase obținute de pe un QPU și, în final, să diagonalizeze hamiltonianul într-un subspațiu generat de acele eșantioane rafinate. Acest lucru se face iterativ, după cum urmează:
-
Rafinează eșantioanele postselectând doar pe cele care au numărul corect de particule atât pentru subsistemul cu spin în sus, cât și pentru cel cu spin în jos
-
Diagonalizează în subspații mici generate de eșantioanele rafinate și colectează informații despre subspațiile care produc cele mai bune (cele mai joase) energii.
-
Pentru fiecare iterație:
-
Rafinează eșantioanele zgomotoase inversând probabilistic biți în eșantioanele cu număr incorect de particule. Probabilitățile se calculează folosind informațiile despre subspațiu din cea mai recentă diagonalizare.
-
Diagonalizează în subspații mici generate de eșantioanele rafinate
-
Colectează informații despre subspațiile care produc cele mai bune (cele mai joase) energii
-
Din rezultat vedem că algoritmul converge la energia exactă a stării fundamentale după 6 iterații și că diagonalizarea se efectuează pe întregul subspațiu Hilbert de dimensiune . Desigur, diagonalizarea pe întregul subspațiu nu este de obicei fezabilă și, prin urmare, sistemele cu stări fundamentale având suport rar sunt cele mai susceptibile să beneficieze de tehnici de diagonalizare pe subspații precum SQD.
Pentru mai multe informații despre funcția diagonalize_fermionic_hamiltonian, vizitează documentația API.
from functools import partial
from pyscf import fci
from qiskit_addon_sqd.fermion import (
SCIResult,
diagonalize_fermionic_hamiltonian,
solve_sci_batch,
)
exact_energy, _ = fci.direct_spin1.kernel(
hcore, eri, int(num_orbitals), nelec
)
exact_energy += nuclear_repulsion_energy
print(f"Exact (FCI) reference energy: {exact_energy:.6f} Ha\n")
sci_solver = partial(solve_sci_batch, spin_sq=0.0, max_cycle=200)
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):
error = result.energy + nuclear_repulsion_energy - exact_energy
print(f" Subsample {i}")
print(f" Energy: {result.energy + nuclear_repulsion_energy:.6f}")
print(
f" Subspace dimension: {np.prod(result.sci_state.amplitudes.shape)}"
)
print(f" Error vs exact: {error:.6f} Ha")
samples_per_batch = 20
result = diagonalize_fermionic_hamiltonian(
hcore,
eri,
bit_array,
samples_per_batch=samples_per_batch,
norb=num_orbitals,
nelec=nelec,
occupancies_tol=1e-7,
max_iterations=30,
sci_solver=sci_solver,
symmetrize_spin=True,
callback=callback,
seed=np.random.default_rng(32),
)
Exact (FCI) reference energy: -107.652521 Ha
Iteration 1
Subsample 0
Energy: -106.908481
Subspace dimension: 784
Error vs exact: 0.744040 Ha
Iteration 2
Subsample 0
Energy: -106.934921
Subspace dimension: 1764
Error vs exact: 0.717600 Ha
Iteration 3
Subsample 0
Energy: -106.953430
Subspace dimension: 2209
Error vs exact: 0.699091 Ha
Iteration 4
Subsample 0
Energy: -107.651946
Subspace dimension: 2916
Error vs exact: 0.000575 Ha
Iteration 5
Subsample 0
Energy: -107.652003
Subspace dimension: 3025
Error vs exact: 0.000518 Ha
Iteration 6
Subsample 0
Energy: -107.652521
Subspace dimension: 3136
Error vs exact: 0.000000 Ha
Iteration 7
Subsample 0
Energy: -107.652521
Subspace dimension: 3136
Error vs exact: 0.000000 Ha