Skip to main content
IBM Quantum Platform

Démarrage rapide

qiskit-addon-sqd Ce guide présente un exemple minimal fonctionnel du paquet. Nous utilisons la diagonalisation quantique par échantillonnage (SQD) pour affiner des échantillons bruités prélevés à partir d'un circuit quantique qui approxime l'état fondamental d'un hamiltonien chimique. Les énergies de l'état fondamental sont estimées en diagonalisant l'hamiltonien dans les sous-espaces formés par les échantillons affinés.

Pour découvrir d'autres workflows de bout en bout explicatifs s'exécutant sur du matériel quantique, consultez les tutoriels suivants :


1. Préparer les données d'entrée pour le SQD

Tout d'abord, nous définissons l'hamiltonien chimique et le circuit quantique qui en approximent l'état fondamental. Le système étudié dans cet exemple est le N₂ en géométrie d'équilibre, dans une base d STO-3G, avec deux orbitales centrales figées. Ce système comporte 8 des orbitales spatiales et 10 des électrons, ce qui signifie que le circuit quantique est défini sur 16 des qubits, où les qubits 0-7 représentent les orbitales à spin vers le haut et les qubits 8-15 représentent les orbitales à spin vers le bas. 10Comme il s'agit d'un système à couche fermée comportant 10 des électrons, nous savons que chaque échantillon physiquement valable doit avoir un poids de Hamming, où le poids de Hamming des deux moitiés (gauche et droite) de la chaîne binaire est 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. Générer des échantillons bruités

Dans un flux de travail réel, vous concevriez un circuit « ansatz », vous l'optimiseriez pour le matériel, puis vous le simuleriez sur le QPU. Ici, nous ne nous attardons pas là-dessus et nous générons 10,000 plutôt des chaînes de bits aléatoires uniformément réparties. Bien que nos échantillons ne contiennent aucune information sur l'état fondamental réel, nous verrons que le protocole SQD peut tout de même déterminer cet état fondamental en affinant ces échantillons de manière itérative, en les diagonalisant et en utilisant les énergies obtenues à chaque itération pour orienter la prochaine itération d'affinement de la chaîne de bits. Nous pouvons finalement déterminer avec exactitude l'état fondamental de cette manière, car le sous-espace contenant l'état fondamental est suffisamment petit pour permettre un calcul exact. 5Plus précisément, puisque nous savons que chaque moitié de chaque chaîne binaire (8 bits) doit avoir un poids de Hamming, nous savons que la taille totale du sous-espace de Hilbert du système est 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. Post-traitement avec SQD

L'objectif de l'algorithme SQD est d'affiner les échantillons bruités provenant d'un QPU et, à terme, de diagonaliser l'hamiltonien dans un sous-espace engendré par ces échantillons affinés. Cette opération s'effectue de manière itérative, comme suit :

  • Affiner les échantillons en ne retenant que ceux dont le nombre de particules est correct pour les sous-systèmes « spin-up » et « spin-down »
  • Diagonaliser dans de petits sous-espaces engendrés par les échantillons affinés et recueillir des informations sur les sous-espaces qui produisent les meilleures énergies (les plus faibles).
  • À chaque itération :
    • Affiner les échantillons bruités en inversant de manière probabiliste les bits des échantillons dont le nombre de particules est incorrect. Les probabilités sont calculées à partir des informations relatives au sous-espace issues de la dernière diagonalisation.
    • Diagonaliser dans de petits sous-espaces engendrés par les échantillons affinés
    • Recueillir des informations sur les sous-espaces donnant les meilleures énergies (les plus faibles)

On constate d'après les résultats que l'algorithme converge vers l'énergie exacte de l'état fondamental après 6 itérations et que la diagonalisation est effectuée sur l'ensemble du sous-espace de Hilbert de dimension (85)2=3136\binom{8}{5}^2=3136 Bien entendu, la diagonalisation sur l'ensemble du sous-espace n'est généralement pas réalisable; par conséquent, ce sont les systèmes dont les états fondamentaux ont un support clairsemé qui sont les plus susceptibles de tirer profit des techniques de diagonalisation par sous-espace telles que la SQD.

Pour plus d'informations sur la fonction diagonalize\_fermionic\_hamiltonian, consultez la documentation de l'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
Cette page a-t-elle été utile ?
Signaler un bogue, une coquille ou proposer du contenu sur GitHub.