Avvio rapido
Questa guida illustra un esempio minimale funzionante del qiskit-addon-sqd pacchetto. Utilizziamo la diagonalizzazione quantistica basata su campioni (SQD) per affinare campioni rumorosi prelevati da un circuito quantistico che approssima lo stato fondamentale di un hamiltoniano chimico. Le energie dello stato fondamentale vengono approssimate diagonalizzando l'Hamiltoniano nei sottospazi formati dai campioni raffinati.
Per ulteriori flussi di lavoro end-to-end illustrativi che vengono eseguiti su hardware quantistico, consulta i tutorial:
- Diagonalizzazione quantistica basata su campioni : flusso di lavoro relativo all'Hamiltoniano chimico
- Diagonalizzazione quantistica di Krylov basata su campioni : flusso di lavoro per un modello a reticolo fermionico
1. Preparare i dati di input per SQD
Per prima cosa, definiamo l'Hamiltoniano chimico e il circuito quantistico che ne approssima lo stato fondamentale. Il sistema oggetto di studio in questo esempio è l'N₂ in geometria di equilibrio, in una bas STO-3G a con due orbitali centrali congelati. Questo sistema presenta 8 orbitali spaziali ed 10 elettroni, il che significa che il circuito quantistico è definito su 16 qubit, dove i qubit 0-7 rappresentano gli orbitali con spin verso l'alto e i qubit 8-15 rappresentano gli orbitali con spin verso il basso. 10Trattandosi di un sistema a guscio chiuso con 10 elettroni, sappiamo che ogni campione fisicamente valido dovrebbe avere peso di Hamming, dove il peso di Hamming sia della metà sinistra che di quella destra della stringa di bit è 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. Generare campioni con rumore
In un flusso di lavoro reale, si progetterebbe un circuito di Ansatz, lo si ottimizzerebbe per l'hardware e lo si campionerebbe sulla QPU. In questo caso tralasciamo questa fase e generiamo 10,000 invece stringhe di bit casuali uniformemente distribuite. Sebbene i nostri campioni non contengano alcuna informazione sullo stato fondamentale effettivo, vedremo che il protocollo SQD è comunque in grado di ricostruire il vero stato fondamentale perfezionando iterativamente questi campioni, diagonalizzando e utilizzando le energie ottenute ad ogni iterazione per guidare la successiva fase di perfezionamento della stringa di bit. In questo modo possiamo alla fine ricavare con esattezza lo stato fondamentale, poiché il sottospazio che contiene lo stato fondamentale è sufficientemente piccolo da poter essere calcolato con precisione. 5In particolare, poiché sappiamo che ciascuna metà di ogni stringa di bit (8 bit) dovrebbe avere peso di Hamming, sappiamo che il sottospazio di Hilbert completo del sistema ha dimensione .
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-elaborazione con SQD
L'obiettivo dell'algoritmo SQD è quello di raffinare i campioni rumorosi prelevati da una QPU e, in ultima analisi, di diagonalizzare l'hamiltoniano in un sottospazio generato da tali campioni raffinati. Questo processo viene eseguito in modo iterativo come segue:
- Filtrare i campioni selezionando successivamente solo quelli che presentano un numero corretto di particelle sia per il sottosistema “spin-up” che per quello “spin-down”
- Diagonalizzare i piccoli sottospazi generati dai campioni raffinati e raccogliere informazioni sui sottospazi che producono le energie migliori (più basse).
- Per ogni iterazione:
- Rifinire i campioni affetti da rumore invertendo in modo probabilistico i bit nei campioni con un numero di particelle errato. Le probabilità vengono calcolate utilizzando le informazioni relative al sottospazio ricavate dalla diagonalizzazione più recente.
- Diagonalizzare nei piccoli sottospazi generati dai campioni raffinati
- Raccogliere informazioni sui sottospazi che producono le energie migliori (più basse)
Dall’output si può notare che l’algoritmo converge all’energia esatta dello stato fondamentale dopo 6 iterazioni e che la diagonalizzazione viene eseguita sull’intero sottospazio di Hilbert di dimensione . Naturalmente, la diagonalizzazione sull’intero sottospazio non è solitamente fattibile e, pertanto, i sistemi con stati fondamentali a supporto sparso sono quelli che più probabilmente trarranno vantaggio dalle tecniche di diagonalizzazione su sottospazio come l’SQD.
Per ulteriori informazioni sulla funzione diagonalize\_fermionic\_hamiltonian, consulta la documentazione dell'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