Inicio rápido
Esta guía muestra un ejemplo mínimo y funcional del paquete qiskit-addon-sqd . Utilizamos la diagonalización cuántica basada en muestras (SQD) para refinar muestras con ruido obtenidas de un circuito cuántico que aproxima el estado fundamental de un hamiltoniano químico. Las energías del estado fundamental se calculan de forma aproximada diagonalizando el hamiltoniano en subespacios formados por las muestras refinadas.
Si quieres conocer otros flujos de trabajo completos y explicativos que se ejecutan en hardware cuántico, echa un vistazo a los tutoriales:
- Diagonalización cuántica basada en muestras : flujo de trabajo del hamiltoniano químico
- Diagonalización cuántica de Krylov basada en muestras : flujo de trabajo del modelo de red fermiónica
1. Preparar los datos de entrada para el SQD
En primer lugar, definimos el hamiltoniano químico y el circuito cuántico que aproxima su estado fundamental. El sistema estudiado en este ejemplo es N₂ en geometría de equilibrio, en una base de tipo « STO-3G », con dos orbitales centrales congelados. Este sistema tiene 8 orbitales espaciales y 10 electrones, lo que significa que el circuito cuántico se define en 16 qubits, donde los qubits 0-7 representan los orbitales de espín hacia arriba y los qubits 8-15 representan los orbitales de espín hacia abajo. 10Dado que se trata de un sistema de capa cerrada con 10 electrones, sabemos que cada muestra físicamente válida debe tener un peso de Hamming, donde el peso de Hamming tanto de la mitad izquierda como de la derecha de la cadena de bits es 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. Generar muestras con ruido
En un flujo de trabajo real, se diseñaría un circuito «ansatz», se optimizaría para el hardware y se simularía en la QPU. Aquí nos saltamos ese paso y, en su lugar, generamos 10,000 cadenas de bits aleatorias uniformemente distribuidas. Aunque nuestras muestras no contienen información sobre el estado fundamental real, veremos que el protocolo SQD puede recuperar el verdadero estado fundamental refinando estas muestras de forma iterativa, diagonalizando y utilizando las energías de cada iteración para orientar la siguiente ronda de refinamiento de la cadena de bits. De esta forma, podemos llegar a recuperar el estado fundamental exacto, ya que el subespacio que contiene el estado fundamental es lo suficientemente pequeño como para poder calcularlo con exactitud. 5Concretamente, dado que sabemos que cada mitad de cada cadena de bits (8 bits) debe tener un peso de Hamming, sabemos que el subespacio de Hilbert completo del sistema tiene un tamaño de .
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. Posprocesado con SQD
El objetivo del algoritmo SQD es refinar las muestras con ruido obtenidas de una QPU y, en última instancia, diagonalizar el hamiltoniano en un subespacio generado por dichas muestras refinadas. Esto se lleva a cabo de forma iterativa de la siguiente manera:
- Filtrar las muestras seleccionando posteriormente solo aquellas que tengan el número correcto de partículas tanto para el subsistema de aceleración como para el de desaceleración
- Diagonalizar en pequeños subespacios generados por las muestras refinadas y recopilar información sobre los subespacios que producen las mejores (más bajas) energías.
- En cada iteración:
- Refina las muestras con ruido invirtiendo de forma probabilística los bits de las muestras cuyo número de partículas sea incorrecto. Las probabilidades se calculan utilizando la información del subespacio obtenida de la diagonalización más reciente.
- Diagonalizar en pequeños subespacios generados por las muestras refinadas
- Recopilar información sobre los subespacios que dan lugar a las mejores (más bajas) energías
De los resultados se desprende que el algoritmo converge a la energía exacta del estado fundamental tras 6 iteraciones y que la diagonalización se lleva a cabo sobre el subespacio de Hilbert completo de dimensión . Por supuesto, la diagonalización sobre el subespacio completo no suele ser viable y, por lo tanto, los sistemas cuyos estados fundamentales tienen un soporte disperso son los que más probablemente se beneficien de técnicas de diagonalización de subespacios como el SQD.
Para obtener más información sobre la función diagonalize_fermionic_hamiltonian, consulta la documentación de la 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