Iniciação rápida
qiskit-addon-sqd Este guia apresenta um exemplo mínimo funcional do pacote. Utilizamos 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 estimadas por meio da diagonalização do hamiltoniano em subespaços formados pelas amostras refinadas.
Para conhecer fluxos de trabalho mais detalhados, de ponta a ponta, executados em hardware quântico, confira os tutoriais:
- Diagonalização quântica baseada em amostras : fluxo de trabalho do hamiltoniano químico
- Diagonalização quântica de Krylov baseada em amostras : fluxo de trabalho do modelo de rede fermiónica
1. Preparar os dados de entrada 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 de coordenaçã STO-3G, com dois orbitais centrais congelados. Esse sistema possui 8 orbitais espaciais e 10 elétrons, o que significa que o circuito quântico é definido por 16 qubits, em que os qubits 0-7 representam orbitais de spin para cima e os qubits 8-15 representam orbitais de spin para baixo. 10Como se trata de um sistema de camada fechada com 10 elétrons, sabemos que cada amostra fisicamente válida deve ter peso de Hamming, sendo que o peso de Hamming tanto da metade esquerda quanto da metade direita da sequência de bits é 5.
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}"
)Output:
Spatial orbitals: 8
Qubits: 16
Electrons (alpha, beta): (5, 5)
2. Gerar amostras com ruído
Em um fluxo de trabalho real, você projetaria um circuito ansatz, o otimizaria para o hardware e o simularia na QPU. Aqui, ignoramos isso e, em vez disso, geramos 10,000 sequências de bits aleatórias uniformemente distribuídas. Embora nossas amostras não contenham informações sobre o estado fundamental real, veremos que o protocolo SQD ainda é capaz de recuperar o verdadeiro estado fundamental por meio do refinamento iterativo dessas amostras, da diagonalização e do uso das energias em cada iteração para orientar a próxima rodada de refinamento da sequência de bits. Podemos, eventualmente, recuperar o estado fundamental exato dessa maneira, pois o subespaço que contém o estado fundamental é pequeno o suficiente para ser calculado com exatidão. 5Especificamente, como sabemos que cada metade de cada sequência de bits (8 bits) deve ter peso de Hamming, 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."
)Output:
Generated 10000 uniformly-random, 16-qubit samples.
3. Pós-processamento 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 de forma iterativa, da seguinte maneira:
- Refinar as amostras selecionando posteriormente apenas aquelas que apresentam o número correto de partículas tanto para o subsistema de aceleração quanto para o de desaceleração
- Diagonalize em pequenos subespaços gerados pelas amostras refinadas e colete informações sobre os subespaços que apresentam as melhores (mais baixas) energias.
- Para cada iteração:
- Refine as amostras com ruído invertendo, de forma probabilística, os bits nas amostras com número incorreto de partículas. As probabilidades são calculadas utilizando as informações do subespaço obtidas na diagonalização mais recente.
- Diagonalizar em pequenos subespaços gerados pelas amostras refinadas
- Coletar informações sobre os subespaços que apresentam as melhores (mais baixas) energias
Podemos observar na saída que o algoritmo converge para a energia exata do estado fundamental após 6 iterações e que a diagonalização é realizada sobre todo o subespaço de Hilbert de dimensão . É claro que a diagonalização sobre todo o subespaço geralmente não é viável e, portanto, os sistemas com estados fundamentais de suporte esparso são os que mais provavelmente se beneficiarão de técnicas de diagonalização de subespaços, como o SQD.
Para obter mais informações sobre a função diagonalize_fermionic_hamiltonian, consulte 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),
)Output:
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