Extending Solvers
This guide explains how to add a new solver to qvartools.
Architecture
All solvers extend the Solver ABC defined in
qvartools/solvers/solver.py. The solver ecosystem follows a flat
hierarchy:
Solver (ABC)
├── FCISolver (reference/)
├── CCSDSolver (reference/)
├── SQDSolver (subspace/)
├── BatchedSQDSolver (subspace/)
├── CIPSISolver (subspace/)
├── SKQDSolver (krylov/)
├── NFSKQDSolver (krylov/)
├── DCISKQDSolver (krylov/)
├── IterativeNFSQDSolver (iterative/)
└── IterativeNFSKQDSolver (iterative/)
Step 1: Choose a Location
Place your solver in the appropriate subdirectory:
solvers/reference/– exact or near-exact methods (FCI, CCSD, DMRG)solvers/subspace/– methods that diagonalize in a sampled subspacesolvers/krylov/– methods that use Krylov subspace techniquessolvers/iterative/– methods with iterative refinement loops
Step 2: Implement the Solver
# solvers/subspace/my_solver.py
from __future__ import annotations
import time
from typing import Any, Dict
from qvartools.hamiltonians.hamiltonian import Hamiltonian
from qvartools.solvers.solver import Solver, SolverResult
class MySolver(Solver):
"""My custom subspace solver.
Parameters
----------
n_samples : int
Number of configurations to sample.
tolerance : float
Convergence tolerance in Hartree.
Examples
--------
>>> from qvartools.molecules import get_molecule
>>> hamiltonian, mol_info = get_molecule("H2")
>>> solver = MySolver(n_samples=1000)
>>> result = solver.solve(hamiltonian, mol_info)
>>> print(f"Energy: {result.energy:.10f}")
"""
def __init__(
self,
n_samples: int = 1000,
tolerance: float = 1e-6,
) -> None:
self._n_samples = n_samples
self._tolerance = tolerance
def solve(
self, hamiltonian: Hamiltonian, mol_info: Dict[str, Any]
) -> SolverResult:
t0 = time.perf_counter()
# Your algorithm here
energy = self._run_algorithm(hamiltonian)
return SolverResult(
energy=energy,
diag_dim=self._n_samples,
wall_time=time.perf_counter() - t0,
method="MySolver",
converged=True,
)
def _run_algorithm(self, hamiltonian: Hamiltonian) -> float:
...
Step 3: Register the Solver
Export the solver from its package __init__.py:
# solvers/subspace/__init__.py
from qvartools.solvers.subspace.my_solver import MySolver
And from the top-level solvers/__init__.py:
# solvers/__init__.py
from qvartools.solvers.subspace import MySolver
Step 4: Add Tests
Create a test file in tests/test_solvers/:
# tests/test_solvers/test_my_solver.py
import pytest
from qvartools.solvers.subspace.my_solver import MySolver
@pytest.mark.pyscf
def test_my_solver_h2(h2_hamiltonian, h2_mol_info, h2_exact_energy):
solver = MySolver(n_samples=500)
result = solver.solve(h2_hamiltonian, h2_mol_info)
assert result.energy is not None
assert abs(result.energy - h2_exact_energy) < 0.01 # 10 mHa
Best Practices
Use
dataclasses.replace()instead of mutating config objectsStore iteration history in
SolverResult.metadataRaise
ValueErrorfor invalid parameters in__init__Use
torch.no_grad()for inference-only computationSupport both CPU and CUDA devices via a
deviceparameter