Skip to main content
IBM Quantum Platform

Schnellstart

Diese Anleitung zeigt ein minimales, funktionsfähiges Beispiel für das qiskit-addon-sqd Paket. Wir verwenden die sample-basierte Quantendiagonalisierung (SQD), um verrauschte Stichproben zu verfeinern, die aus einem Quantenschaltkreis stammen, der den Grundzustand eines chemischen Hamilton-Operators approximiert. Die Grundzustandsenergien werden durch Diagonalisierung des Hamilton-Operators in Unterräumen approximiert, die durch die verfeinerten Stichproben gebildet werden.

Weitere anschauliche End-to-End-Workflows, die auf Quantenhardware ausgeführt werden, finden Sie in den Tutorials:


1. Bereiten Sie die Eingaben für SQD vor

Zunächst definieren wir den chemischen Hamilton-Operator und den Quantenschaltkreis, der dessen Grundzustand approximiert. Das in diesem Beispiel untersuchte System ist N₂ in Gleichgewichtsgeometrie auf der „ STO-3G “-Basis mit zwei eingefrorenen Kernorbitalen. Dieses System verfügt über 8 räumliche Orbitalen und 10 Elektronen, was bedeutet, dass der Quantenschaltkreis auf 16 Qubits definiert ist, wobei die Qubits 0-7 die Orbitalen mit Spin-up und die Qubits 8-15 die Orbitalen mit Spin-down darstellen. 5``10Da es sich um ein System mit geschlossener Schale und 10 Elektronen handelt, wissen wir, dass jede physikalisch gültige Probe ein Hamming-Gewicht von haben sollte, wobei das Hamming-Gewicht sowohl der linken als auch der rechten Hälfte der Bitfolge beträgt.

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. Erzeugen Sie mit Rauschen versehene Stichproben

In einem realen Arbeitsablauf würde man eine Ansatzschaltung entwerfen, diese für die Hardware optimieren und sie auf der QPU abtasten. Hier lassen wir das weg und generieren 10,000 stattdessen gleichverteilte Zufallsbitstrings. Obwohl unsere Stichproben keine Informationen über den tatsächlichen Grundzustand enthalten, werden wir sehen, dass das SQD-Protokoll den wahren Grundzustand dennoch rekonstruieren kann, indem es diese Stichproben iterativ verfeinert, diagonalisiert und die Energien jeder Iteration nutzt, um die nächste Runde der Bitstring-Verfeinerung zu steuern. Auf diese Weise können wir letztendlich den exakten Grundzustand wiederherstellen, da der Unterraum, der den Grundzustand enthält, klein genug ist, um exakt berechnet zu werden. 5Da wir insbesondere wissen, dass jede Hälfte jeder Bitfolge (8 bits) das Hamming-Gewicht haben sollte, wissen wir, dass der gesamte Hilbert-Unterraum des Systems die Größe (85)2=3136\binom{8}{5}^2=3136 hat.

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. Nachbearbeitung mit SQD

Das Ziel des SQD-Algorithmus besteht darin, verrauschte Stichproben aus einer QPU zu verfeinern und schließlich den Hamilton-Operator in einem Unterraum zu diagonalisieren, der durch diese verfeinerten Stichproben aufgespannt wird. Dies erfolgt iterativ wie folgt:

  • Verfeinern Sie die Stichproben, indem Sie im Anschluss nur diejenigen auswählen, die sowohl für das Spin-up- als auch für das Spin-down-Teilsystem die korrekte Partikelanzahl aufweisen
  • Führen Sie eine Diagonalisierung in kleinen Unterräumen durch, die durch die verfeinerten Stichproben aufgespannt werden, und sammeln Sie Informationen über die Unterräume, die die besten (niedrigsten) Energien liefern.
  • Bei jeder Iteration:
    • Verfeinern Sie die verrauschten Messwerte, indem Sie Bits in Messwerten mit falscher Partikelanzahl probabilistisch umkehren. Die Wahrscheinlichkeiten werden anhand der Unterrauminformationen aus der letzten Diagonalisierung berechnet.
    • Diagonalisierung in kleinen Unterräumen, die durch die verfeinerten Stichproben aufgespannt werden
    • Sammeln Sie Informationen über die Unterräume, die die besten (niedrigsten) Energien liefern

Aus der Ausgabe geht hervor, dass der Algorithmus nach 6 Iterationen zur exakten Grundzustandsenergie konvergiert und dass die Diagonalisierung über den gesamten Hilbert-Unterraum der Dimension (85)2=3136\binom{8}{5}^2=3136 durchgeführt wird. Natürlich ist eine Diagonalisierung über den gesamten Unterraum in der Regel nicht durchführbar, weshalb Systeme mit Grundzuständen, die einen spärlichen Trägerbereich aufweisen, am ehesten von Unterraum-Diagonalisierungstechniken wie SQD profitieren.

Weitere Informationen zur Funktion „diagonalize_fermionic_hamiltonian“ finden Sie in der API-Dokumentation.

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
War diese Seite hilfreich?
Melden Sie auf GitHub einen Fehler, einen Tippfehler oder fordern Sie Inhalte an.