Início rápido
Este guia demonstra um exemplo mínimo funcional do pacote qiskit-addon-sqd. Usamos a diagonalização quântica baseada em amostras (SQD) para refinar amostras ruidosas obtidas de um circuito quântico que aproxima o estado fundamental de um Hamiltoniano químico. As energias do estado fundamental são aproximadas diagonalizando o Hamiltoniano em subespaços formados pelas amostras refinadas.
Para fluxos de trabalho completos mais explicativos que rodam em hardware quântico, confira os tutoriais:
-
Diagonalização quântica baseada em amostras: fluxo de trabalho com Hamiltoniano químico
-
Diagonalização quântica de Krylov baseada em amostras: fluxo de trabalho com modelo de rede fermiônica
1. Prepare as entradas para o SQD
Primeiro, definimos o Hamiltoniano químico e o circuito quântico que aproxima seu estado fundamental. O sistema estudado neste exemplo é o N₂ na geometria de equilíbrio, na base STO-3G, com dois orbitais de caroço congelados. Esse sistema tem 8 orbitais espaciais e 10 elétrons, o que significa que o circuito quântico é definido em 16 qubits, onde os qubits 0-7 representam orbitais de spin para cima e os qubits 8-15 representam orbitais de spin para baixo. Como este é um sistema de camada fechada com 10 elétrons, sabemos que cada amostra fisicamente válida deve ter peso de Hamming 10, em que o peso de Hamming de cada metade, esquerda e direita, da bitstring é 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. Gere amostras ruidosas
Em um fluxo de trabalho real, você projetaria um circuito ansatz, o otimizaria para o hardware e o amostraria na QPU. Aqui pulamos isso e sorteamos 10,000 bitstrings uniformemente aleatórias. Embora nossas amostras não contenham nenhuma informação sobre o estado fundamental real, veremos que o protocolo SQD ainda consegue recuperar o verdadeiro estado fundamental refinando essas amostras iterativamente, diagonalizando e usando as energias de cada iteração para orientar a próxima rodada de refinamento das bitstrings. Conseguimos recuperar o estado fundamental exato dessa forma porque o subespaço que contém o estado fundamental é pequeno o bastante para ser calculado exatamente. Especificamente, como sabemos que cada metade de cada bitstring (8 bits) deve ter peso de Hamming 5, sabemos que o subespaço de Hilbert completo do sistema tem tamanho .
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. Pós-processe com SQD
O objetivo do algoritmo SQD é refinar amostras ruidosas obtidas de uma QPU e, por fim, diagonalizar o Hamiltoniano em um subespaço gerado por essas amostras refinadas. Isso é feito iterativamente da seguinte forma:
-
Refine as amostras selecionando apenas aquelas que têm o número de partículas correto tanto para o subsistema de spin para cima quanto para o de spin para baixo
-
Diagonalize em pequenos subespaços gerados pelas amostras refinadas e colete informações sobre os subespaços que produzem as melhores (menores) energias.
-
Para cada iteração:
-
Refine as amostras ruidosas invertendo bits de forma probabilística nas amostras com número de partículas incorreto. As probabilidades são calculadas usando as informações de subespaço da diagonalização mais recente.
-
Diagonalize em pequenos subespaços gerados pelas amostras refinadas
-
Colete informações sobre os subespaços que produzem as melhores (menores) energias
-
Pela saída, vemos que o algoritmo converge para a energia exata do estado fundamental após 6 iterações e que a diagonalização é feita sobre o subespaço de Hilbert completo de dimensão . É claro que diagonalizar sobre o subespaço completo geralmente não é viável e, por isso, sistemas com estados fundamentais de suporte esparso são os que mais provavelmente se beneficiam de técnicas de diagonalização em subespaços, como o SQD.
Para mais informações sobre a função diagonalize_fermionic_hamiltonian, visite a documentação da 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