Source code for qvartools.solvers.reference.fci

"""
fci --- Full Configuration Interaction solver
==============================================

Implements :class:`FCISolver`, which computes the exact ground-state
energy via full configuration interaction.  Uses PySCF's FCI module
when available; falls back to dense diagonalisation of the Hamiltonian
matrix for small systems.
"""

from __future__ import annotations

import logging
import time
from typing import Any, Dict

from qvartools.hamiltonians.hamiltonian import Hamiltonian
from qvartools.solvers.solver import Solver, SolverResult

__all__ = [
    "FCISolver",
]

logger = logging.getLogger(__name__)


[docs] class FCISolver(Solver): """Full configuration interaction solver. Attempts to use PySCF's FCI module for molecular Hamiltonians. When PySCF is unavailable or the Hamiltonian is not molecular, falls back to dense exact diagonalisation via :meth:`~qvartools.hamiltonians.hamiltonian.Hamiltonian.exact_ground_state`. Parameters ---------- max_configs : int, optional Maximum number of configurations (Hilbert-space dimension) for which dense diagonalisation is attempted (default ``500_000``). Systems exceeding this limit raise a ``RuntimeError`` when PySCF is unavailable. Attributes ---------- max_configs : int Configuration limit for dense fallback. Examples -------- >>> solver = FCISolver() >>> result = solver.solve(hamiltonian, mol_info) >>> result.energy -1.1373060356 """ def __init__(self, max_configs: int = 500_000) -> None: if max_configs < 1: raise ValueError(f"max_configs must be >= 1, got {max_configs}") self.max_configs: int = max_configs
[docs] def solve( self, hamiltonian: Hamiltonian, mol_info: Dict[str, Any] ) -> SolverResult: """Compute the FCI ground-state energy. Parameters ---------- hamiltonian : Hamiltonian The molecular Hamiltonian. mol_info : dict Molecular metadata (must contain ``"name"``). Returns ------- SolverResult FCI energy result. Raises ------ RuntimeError If the Hilbert-space dimension exceeds ``max_configs`` and PySCF is not available. """ t_start = time.perf_counter() energy, diag_dim, converged, metadata = self._try_pyscf_fci( hamiltonian, mol_info ) if energy is None: energy, diag_dim, converged, metadata = self._dense_fallback( hamiltonian ) wall_time = time.perf_counter() - t_start logger.info( "FCISolver [%s]: energy=%.10f, dim=%d, time=%.2fs", mol_info.get("name", "unknown"), energy, diag_dim, wall_time, ) return SolverResult( energy=energy, diag_dim=diag_dim, wall_time=wall_time, method="FCI", converged=converged, metadata=metadata, )
def _try_pyscf_fci( self, hamiltonian: Hamiltonian, mol_info: Dict[str, Any] ) -> tuple: """Attempt FCI via PySCF. Parameters ---------- hamiltonian : Hamiltonian Must have an ``integrals`` attribute for PySCF FCI. mol_info : dict Molecular metadata. Returns ------- tuple ``(energy, diag_dim, converged, metadata)`` or ``(None, 0, False, {})`` if PySCF is unavailable or the Hamiltonian is not molecular. """ if not hasattr(hamiltonian, "integrals"): return None, 0, False, {} try: from pyscf import fci, gto, scf except ImportError: logger.info("PySCF not available; falling back to dense FCI.") return None, 0, False, {} integrals = hamiltonian.integrals geometry = mol_info.get("geometry", []) basis = mol_info.get("basis", "sto-3g") charge = mol_info.get("charge", 0) spin = mol_info.get("spin", 0) mol = gto.Mole() mol.atom = [(atom, coord) for atom, coord in geometry] mol.basis = basis mol.charge = charge mol.spin = spin mol.unit = "Angstrom" mol.build() mf = scf.RHF(mol) mf.kernel() cisolver = fci.FCI(mf) e_fci, ci_vec = cisolver.kernel() n_orb = integrals.n_orbitals n_alpha = integrals.n_alpha n_beta = integrals.n_beta from math import comb diag_dim = comb(n_orb, n_alpha) * comb(n_orb, n_beta) metadata: Dict[str, Any] = { "pyscf_converged": mf.converged, "n_orbitals": n_orb, "n_alpha": n_alpha, "n_beta": n_beta, } return float(e_fci), diag_dim, True, metadata def _dense_fallback( self, hamiltonian: Hamiltonian ) -> tuple: """Fall back to dense exact diagonalisation. Parameters ---------- hamiltonian : Hamiltonian The Hamiltonian to diagonalise. Returns ------- tuple ``(energy, diag_dim, converged, metadata)``. Raises ------ RuntimeError If the Hilbert-space dimension exceeds ``max_configs``. """ diag_dim = hamiltonian.hilbert_dim if diag_dim > self.max_configs: raise RuntimeError( f"Dense FCI requires {diag_dim} configurations, which exceeds " f"max_configs={self.max_configs}. Install PySCF for large systems." ) logger.info("Using dense diagonalisation (dim=%d).", diag_dim) energy, _ = hamiltonian.exact_ground_state() metadata: Dict[str, Any] = {"fallback": "dense_diag"} return energy, diag_dim, True, metadata