Mabilisang simula
Ipinapakita ng gabay na ito ang isang minimal na gumaganang halimbawa ng package na qiskit-addon-sqd. Gumagamit tayo ng sample-based quantum diagonalization (SQD) para pinuhin ang mga maingay na sample na kinuha mula sa isang quantum circuit na tumatantiya sa ground state ng isang chemistry Hamiltonian. Tinatantiya ang mga ground state energy sa pamamagitan ng pag-diagonalize sa Hamiltonian sa mga subspace na binuo ng mga pinong sample.
Para sa mas detalyadong end-to-end na mga workflow na tumatakbo sa quantum hardware, tingnan ang mga tutorial:
-
Sample-based quantum diagonalization: workflow ng chemistry Hamiltonian
-
Sample-based Krylov quantum diagonalization: workflow ng fermionic lattice model
1. Ihanda ang mga input para sa SQD
Una, tinutukoy natin ang chemistry Hamiltonian at ang quantum circuit na tumatantiya sa ground state nito. Ang sistemang pinag-aaralan sa halimbawang ito ay N₂ sa equilibrium geometry sa STO-3G basis na may dalawang frozen na core orbital. May 8 spatial orbital at 10 electron ang sistemang ito, kaya ang quantum circuit ay nakadefine sa 16 qubit, kung saan ang mga qubit na 0-7 ay kumakatawan sa mga spin-up orbital at ang mga qubit na 8-15 ay kumakatawan sa mga spin-down orbital. Dahil closed-shell ang sistemang ito na may 10 electron, alam nating ang bawat physically-valid na sample ay dapat may hamming weight na 10, kung saan ang hamming weight ng kaliwa at kanang kalahati ng bitstring ay 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. Gumawa ng mga maingay na sample
Sa totoong workflow, magdidisenyo ka ng ansatz circuit, i-o-optimize ito para sa hardware, at kukuha ng sample mula rito sa QPU. Dito, lalaktawan natin iyon at gagawa na lang tayo ng 10,000 na uniformly random na mga bitstring. Kahit walang impormasyon ang mga sample natin tungkol sa totoong ground state, makikita nating mababawi pa rin ng SQD protocol ang tunay na ground state sa pamamagitan ng paulit-ulit na pagpino sa mga sample na ito, pag-diagonalize, at paggamit sa mga energy sa bawat iteration para gabayan ang susunod na round ng pagpino ng bitstring. Mababawi natin sa wakas ang eksaktong ground state sa ganitong paraan dahil sapat na maliit ang subspace na naglalaman ng ground state para makalkula nang eksakto. Partikular, dahil alam nating ang bawat kalahati ng bawat bitstring (8 bit) ay dapat may hamming weight na 5, alam nating ang buong Hilbert subspace ng sistema ay may sukat na .
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. I-post-process gamit ang SQD
Ang layunin ng SQD algorithm ay pinuhin ang mga maingay na sample na kinuha mula sa isang QPU at sa huli ay i-diagonalize ang Hamiltonian sa isang subspace na binuo ng mga pinong sample na iyon. Paulit-ulit itong ginagawa ayon sa sumusunod:
-
Pinuhin ang mga sample sa pamamagitan ng pag-postselect lamang ng mga may tamang particle number para sa parehong spin-up at spin-down na subsystem
-
I-diagonalize sa maliliit na subspace na binuo ng mga pinong sample at mangolekta ng impormasyon tungkol sa mga subspace na nagbibigay ng pinakamahusay (pinakamababang) energy.
-
Sa bawat iteration:
-
Pinuhin ang mga maingay na sample sa pamamagitan ng probabilistikong pag-flip ng mga bit sa mga sample na may maling particle number. Kinakalkula ang mga probability gamit ang impormasyon ng subspace mula sa pinakahuling diagonalization.
-
I-diagonalize sa maliliit na subspace na binuo ng mga pinong sample
-
Mangolekta ng impormasyon tungkol sa mga subspace na nagbibigay ng pinakamahusay (pinakamababang) energy
-
Makikita natin sa output na nagko-converge ang algorithm sa eksaktong ground state energy pagkatapos ng 6 iteration at ang diagonalization ay ginagawa sa buong Hilbert subspace na may dimensyon na . Siyempre, kadalasan ay hindi praktikal ang pag-diagonalize sa buong subspace, kaya ang mga sistemang may ground state na may sparse na suporta ang pinakamalamang na makinabang sa mga subspace diagonalization technique tulad ng SQD.
Para sa higit pang impormasyon tungkol sa function na diagonalize_fermionic_hamiltonian, bisitahin ang API docs.
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