Skip to main content
IBM Quantum Platform

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:


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 (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."
)

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 (85)2=3136\binom{8}{5}^2=3136. 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
¿Le ha resultado útil esta página?
Informe de un error, de una errata o solicite contenido en GitHub.