Saltar al contenido principal
Este proyecto está en fase beta. ¿Encontraste un error? Abre un issue en GitHub. ¿Tienes una pregunta o una idea? Pregunta en Discussions — ¡nos encantaría tu opinión!
¿Cómo es la calidad de la traducción en esta página?

Inicio rápido

Esta guía muestra un ejemplo mínimo funcional del paquete qiskit-addon-sqd. Usamos la diagonalización cuántica basada en muestras (SQD) para refinar muestras ruidosas tomadas de un circuito cuántico que aproxima el estado fundamental de un hamiltoniano químico. Las energías del estado fundamental se aproximan diagonalizando el hamiltoniano en subespacios formados por las muestras refinadas.

Para flujos de trabajo de extremo a extremo más explicativos que se ejecutan en hardware cuántico, consulta los tutoriales:

1. Prepara las entradas para SQD​

Primero, 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 con base STO-3G y dos orbitales internos congelados. Este sistema tiene 8 orbitales espaciales y 10 electrones, lo que significa que el circuito cuántico se define sobre 16 qubits, donde los qubits 0-7 representan orbitales de espín arriba y los qubits 8-15 representan orbitales de espín abajo. Como este es un sistema de capa cerrada con 10 electrones, sabemos que cada muestra físicamente válida debe tener peso de Hamming 10, donde el peso de Hamming de las mitades izquierda y derecha de la cadena de bits es 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. Genera muestras ruidosas​

En un flujo de trabajo real diseñarías un circuito ansatz, lo optimizarías para el hardware y lo muestrearías en la QPU. Aquí omitimos eso y en su lugar extraemos 10,000 cadenas de bits aleatorias uniformes. Aunque nuestras muestras no contienen información sobre el estado fundamental real, veremos que el protocolo SQD aún puede recuperar el verdadero estado fundamental refinando iterativamente estas muestras, diagonalizando y usando las energías de cada iteración para orientar la siguiente ronda de refinamiento de cadenas de bits. Con el tiempo podemos recuperar el estado fundamental exacto de esta manera porque el subespacio que contiene el estado fundamental es lo bastante pequeño como para calcularlo exactamente. En concreto, como sabemos que cada mitad de cada cadena de bits (8 bits) debe tener peso de Hamming 5, sabemos que el subespacio de Hilbert completo del sistema tiene tamaño (85)2=3136\binom{8}{5}^2=3136.

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. Posprocesa con SQD​

El objetivo del algoritmo SQD es refinar muestras ruidosas tomadas de una QPU y, finalmente, diagonalizar el hamiltoniano en un subespacio generado por esas muestras refinadas. Esto se hace de forma iterativa como sigue:

  • Refina las muestras posseleccionando solo aquellas que tienen el número de partículas correcto tanto para el subsistema de espín arriba como para el de espín abajo

  • Diagonaliza en pequeños subespacios generados por las muestras refinadas y recopila información sobre los subespacios que producen las mejores energías (las más bajas).

  • Para cada iteración:

    • Refina las muestras ruidosas invirtiendo bits de forma probabilística en las muestras con un número de partículas incorrecto. Las probabilidades se calculan usando la información del subespacio de la diagonalización más reciente.

    • Diagonaliza en pequeños subespacios generados por las muestras refinadas

    • Recopila información sobre los subespacios que producen las mejores energías (las más bajas)

Podemos ver en la salida que el algoritmo converge a la energía exacta del estado fundamental tras 6 iteraciones y que la diagonalización se realiza sobre el subespacio de Hilbert completo de dimensión (85)2=3136\binom{8}{5}^2=3136. Por supuesto, diagonalizar sobre el subespacio completo normalmente no es factible y, por tanto, los sistemas con estados fundamentales de soporte disperso son los que más probablemente se beneficiarán de técnicas de diagonalización en subespacios como SQD.

Para más información sobre la función diagonalize_fermionic_hamiltonian, visita 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),
)
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