Skip to content

Conversation

@wsttiger
Copy link
Collaborator

@wsttiger wsttiger commented Jan 4, 2026

Add Quantum Exact Lanczos (QEL) Algorithm to CUDA-Q Solvers

This PR introduces a complete implementation of the Quantum Exact Lanczos (QEL) algorithm for computing ground state energies of molecular Hamiltonians using hybrid quantum-classical methods.

Overview

QEL uses block encoding and amplitude amplification to build a Krylov subspace from quantum moment measurements, then solves a generalized eigenvalue problem classically to extract ground state energies. This implementation provides both C++ and Python interfaces with comprehensive testing on five molecular systems.

Key Components

Core Algorithm (quantum_exact_lanczos.cpp, 340 lines)

  • PREPARE-SELECT-REFLECT circuits for quantum moment collection
  • Observable construction for ancilla projectors (R̂ and Û operators)
  • Krylov matrix construction (Hamiltonian H and overlap S matrices)
  • Clean free-function API returning matrices for user-level eigenvalue solving

Block Encoding Support (block_encoding.cpp)

  • Expanded PauliLCU preparation kernel from 10 to 20 ancilla qubits
  • Now supports Hamiltonians with up to 2²⁰ (~1M) Pauli terms
  • Essential for large molecular systems (e.g., LiH with 630 terms)

Python Bindings (py_block_encoding.cpp, 379 lines)

  • BlockEncoding abstract base class
  • PauliLCU with NumPy array accessors for debug/inspection
  • QELResult container with get_hamiltonian_matrix(), get_overlap_matrix()
  • quantum_exact_lanczos() function with krylov_dim, shots, verbose kwargs

Comprehensive Testing

  • C++ unit tests: 3 test cases (operators, H2 molecule, metadata)
  • Python unit tests: 7 test cases covering API, inheritance, and simple Hamiltonians
  • Molecular tests: 5 molecular systems with CASCI reference energies

Molecular Test Coverage

Molecule Qubits Active Space Pauli Terms Error (mHa) Runtime
H₂ 4 (2e, 2o) 3 0.11 ~30s
LiH 12 (4e, 6o) 630 0.66 ~206s
N₂ 8 (4e, 4o) 80 0.33 ~45s
H₂O 8 (4e, 4o) 104 0.22 ~50s
C₆H₆ (Benzene) 8 (4e, 4o) 160 0.76 ~8.5s

All tests achieve sub-mHa accuracy (chemical accuracy is 2.5 mHa).

Design Highlights

1. Proper Constant Term Handling

  • Critical fix: Constant terms kept separate from Hamiltonian to preserve 1-norm
  • Adding constant to SpinOperator inflates normalization incorrectly
  • Energy formula: E = eigenvalue * α + constant (constant added manually)

2. S-Matrix Filtering for Numerical Stability

  • Krylov overlap matrix can become ill-conditioned
  • Python tests filter S eigenvalues (threshold ~1e-4) before solving
  • Ensures robust eigenvalue extraction

3. Pre-Extracted Hamiltonians

  • All molecular data extracted via cudaq_solvers.create_molecule()
  • Ensures reproducibility without PySCF/OpenFermion runtime dependency
  • Data files include CASCI/CCSD reference energies for validation

4. Scalable Architecture

  • Stateless quantum kernels compatible with CUDA-Q compiler
  • No C++ linear algebra dependencies (user handles eigensolving in Python)
  • Row-major matrix flattening for seamless NumPy integration

Example Usage

import numpy as np
from scipy import linalg
from cudaq import spin
import cudaq_solvers as solvers

# Define a simple Hamiltonian
h = 0.5 * spin.z(0) + 0.3 * spin.z(1) + 0.2 * spin.x(0) * spin.x(1)

# Run QEL
result = solvers.quantum_exact_lanczos(
    h, 
    num_qubits=2, 
    n_electrons=2,
    krylov_dim=4,
    shots=-1
)

# Extract Krylov matrices
H = result.get_hamiltonian_matrix()
S = result.get_overlap_matrix()

# Apply S-matrix filtering for numerical stability
threshold = 1e-4
evals_S, evecs_S = np.linalg.eigh(S)
keep = [i for i, e in enumerate(evals_S) if e > threshold]

# Solve in filtered subspace
V = evecs_S[:, keep]
H_proj = V.T @ H @ V
S_proj = np.diag(evals_S[keep])
S_inv_sqrt = np.diag(1.0 / np.sqrt(np.diag(S_proj)))
H_final = S_inv_sqrt @ H_proj @ S_inv_sqrt

eigenvalues = np.linalg.eigvalsh(H_final)

# Ground state energy (scaled by normalization + constant)
E_ground = eigenvalues[0] * result.normalization + result.constant_term
print(f"Ground state energy: {E_ground:.6f} Ha")For complete molecular examples, see `libs/solvers/python/cudaq_solvers/examples/qel_h2_example.py`.

Documentation

  • README_QEL.md: Complete API reference, algorithm description, usage examples
  • qel_h2_example.py: 5 working examples demonstrating QEL on H2 molecule
  • qel_test_molecules.md: Catalog of current and proposed molecular test systems

Implements block encoding for quantum Hamiltonians using the Pauli
Linear Combination of Unitaries (LCU) approach. This is a fundamental
building block for quantum algorithms such as Quantum Eigenvalue
Learning (QEL), Quantum Singular Value Transformation (QSVT), and
Hamiltonian simulation.

Key additions:
- Abstract block_encoding base class defining the PREPARE, SELECT,
  and UNPREPARE operations
- pauli_lcu concrete implementation optimized for Pauli Hamiltonians
  from quantum chemistry (e.g., molecular Hamiltonians)
- State preparation tree using controlled rotations with binary
  control patterns
- Controlled Pauli operations indexed by ancilla register state
- Support for Hamiltonians with up to 1024 terms (10 ancilla qubits)
- Comprehensive unit tests covering various Hamiltonian types

The implementation uses log₂(# terms) ancilla qubits and achieves
α = ||H||₁ normalization. Quantum kernels are implemented as stateless
functors compatible with the CUDA-Q compiler.

Files modified:
- libs/solvers/include/cudaq/solvers/operators/block_encoding.h (new)
- libs/solvers/lib/operators/block_encoding.cpp (new)
- libs/solvers/unittests/test_block_encoding.cpp (new)
- libs/solvers/include/cudaq/solvers/operators.h
- libs/solvers/lib/CMakeLists.txt
- libs/solvers/unittests/CMakeLists.txt

Signed-off-by: Scott Thornton <[email protected]>
Implements the Quantum Exact Lanczos method for computing ground state
energies of quantum Hamiltonians. QEL uses block encoding and amplitude
amplification to build a Krylov subspace, then returns the Krylov matrices
for eigenvalue extraction via classical linear algebra.

Key features:
- Implements PREPARE-SELECT-REFLECT quantum circuits for moment collection
- Builds Krylov Hamiltonian (H) and overlap (S) matrices from 2k moments
- Returns matrices to user for solving H|v⟩ = E·S|v⟩ in Python
- Clean free-function API following VQE pattern
- Observable construction utilities for ancilla projectors
- Support for Hamiltonians with up to 1024 terms (10 ancilla qubits)

Algorithm components:
- qel_reflection_kernel: Amplitude amplification via PREPARE†-X-CZ-X-PREPARE
- qel_state_prep_kernel: Odd moment measurement circuits
- qel_measure_even_kernel: Even moment measurement circuits
- build_R_observable: R = 2P₀ - I for even moments
- build_U_observable: Σᵢ sign(cᵢ)Pᵢ⊗Pᵢ for odd moments
- build_krylov_matrices: Constructs H and S from moment vector

Design decisions:
- No C++ eigenvalue solver dependency (user handles in Python/NumPy)
- Built on pauli_lcu block encoding infrastructure
- Stateless quantum kernels compatible with CUDA-Q compiler
- Row-major matrix flattening for easy NumPy integration

Files added:
- libs/solvers/include/cudaq/solvers/quantum_exact_lanczos.h
- libs/solvers/lib/quantum_exact_lanczos.cpp
- libs/solvers/unittests/test_quantum_exact_lanczos.cpp

All unit tests passing (H2 molecule, simple operators, metadata checks).

Signed-off-by: Scott Thornton <[email protected]>
Add comprehensive Python bindings for QEL algorithm enabling hybrid
quantum-classical ground state energy calculations.

Exposed Classes:
- BlockEncoding: Abstract base class (prepare, select, apply methods)
- PauliLCU: Pauli LCU implementation with NumPy array accessors
- QELResult: Result container returning Krylov matrices as NumPy arrays
- quantum_exact_lanczos(): Main function with krylov_dim/shots/verbose kwargs

Includes:
- py_block_encoding.cpp: Full pybind11 bindings (379 lines)
- test_quantum_exact_lanczos.py: 7 test cases (all passing)
- qel_h2_example.py: 5 working examples with H2 molecule
- README_QEL.md: Complete API reference and usage guide

Usage:
  result = solvers.quantum_exact_lanczos(h2, num_qubits=4, n_electrons=2)
  H = result.get_hamiltonian_matrix()
  S = result.get_overlap_matrix()
  eigenvalues = scipy.linalg.eigh(H, S)

Follows existing VQE/QAOA binding patterns. Integrates with cudaq.SpinOperator
via type casters. Enables flexible eigenvalue extraction at Python level.

7 files changed, 1287 insertions(+)

Signed-off-by: Scott Thornton <[email protected]>
Expand block encoding support and add comprehensive LiH testing with
proper constant term handling for accurate molecular energy calculations.

Block Encoding Expansion:
- Increase ancilla qubit support from 10 to 20 in PauliLCU preparation
- Now handles Hamiltonians with up to 2^20 (~1M) Pauli terms
- Added explicit controlled-RY cases for layers 10-19
- Added error handling for >20 ancilla with informative message
- Enables large molecular systems like LiH (630 terms, 10 ancilla)

LiH Molecule Support:
- Add lih_hamiltonian_data.py with 630 Pauli terms from STO-3G basis
- LiH geometry: 1.595 Å bond length, 12 qubits, 4 electrons
- FCI target energy: -7.882402 Ha, HF energy: -7.862024 Ha
- Standalone data file, no runtime PySCF/OpenFermion dependency

QEL Testing Improvements:
- Add test_quantum_exact_lanczos_lih_molecule() with krylov_dim=8
- Fix critical constant term handling bug in H2 and LiH tests
- Constant terms now kept separate from Hamiltonian to preserve 1-norm
- Energy calculation: eigenvalue * normalization + constant (manual)

Bug Fix - Constant Term Handling:
- Previously: Adding constant to Hamiltonian inflated 1-norm incorrectly
  * H2: 1.887 → 1.984 (includes |const|)
  * LiH: 12.342 → 16.477 (includes |const|)
- Fixed: Keep constant separate, add after eigenvalue extraction
- Results: H2 error 0.00 mHa (exact), LiH error 1.44 mHa (excellent)

Performance:
- LiH test runtime: ~206 seconds (22 qubits: 12 system + 10 ancilla)
- Both H2 and LiH achieve chemical accuracy (<2.5 mHa error)
- C++ implementation 8% faster than pure Python equivalent

Testing:
  pytest libs/solvers/python/tests/test_quantum_exact_lanczos.py::test_quantum_exact_lanczos_h2_molecule
  pytest libs/solvers/python/tests/test_quantum_exact_lanczos.py::test_quantum_exact_lanczos_lih_molecule

3 files changed, 965 insertions(+), 5 deletions(-)

Signed-off-by: Scott Thornton <[email protected]>
Extends the QEL test suite with two additional molecular systems using
pre-extracted Hamiltonians for reproducibility. This brings the total
molecular test coverage to four systems (H2, LiH, N2, H2O) spanning
different chemical bonding types and system sizes.

New Test Cases:
- N2 molecule: 8 qubits (4e, 4o), 80 Pauli terms, 0.33 mHa accuracy
- H2O molecule: 8 qubits (4e, 4o), 104 Pauli terms, 0.22 mHa accuracy

Implementation Details:
- Pre-extracted Hamiltonian data files ensure reproducibility and avoid
  PySCF/chemistry library variability
- Enhanced parse_pauli_term() to handle both space-separated ("Z0 Z1")
  and concatenated ("Z0Z1") Pauli string formats using regex
- Both tests use CASCI reference energies from STO-3G calculations
- Tests include S-matrix filtering for numerical stability

Test Results:
- All 11 QEL tests passing (6 unit tests + 4 molecular tests + 1 API test)
- Total test runtime: ~3.5 minutes
- Molecular test accuracies: H2 (0.11 mHa), LiH (0.66 mHa),
  N2 (0.33 mHa), H2O (0.22 mHa)

Files:
- libs/solvers/python/tests/n2_hamiltonian_data.py: N2 Hamiltonian
  extracted via cudaq_solvers.create_molecule()
- libs/solvers/python/tests/h2o_hamiltonian_data.py: H2O Hamiltonian
  with (4e, 4o) active space
- libs/solvers/python/tests/test_quantum_exact_lanczos.py: Added
  test_quantum_exact_lanczos_n2_molecule() and
  test_quantum_exact_lanczos_h2o_molecule()

Signed-off-by: Scott Thornton <[email protected]>
Extends QEL molecular test suite with benzene, demonstrating the algorithm's
capability on aromatic π-systems. Uses a reduced (4e, 4o) active space to
maintain computational tractability while capturing essential π-orbital
chemistry.

New Test Case:
- Benzene (C6H6): 8 qubits, 160 Pauli terms, 0.76 mHa accuracy
- Active space: (4e, 4o) captures HOMO-1, HOMO, LUMO, LUMO+1 π-orbitals
- CASCI reference energy: -227.945110 Ha (STO-3G basis)
- Test runtime: ~8.5 seconds

Implementation:
- libs/solvers/python/tests/benzene_hamiltonian_data.py: Pre-extracted
  Hamiltonian using cudaq_solvers.create_molecule() for reproducibility
- libs/solvers/python/tests/test_quantum_exact_lanczos.py: Added
  test_quantum_exact_lanczos_benzene_molecule() with S-matrix filtering
- docs/qel_test_molecules.md: Added benzene to implemented tests table
  and documented active space selection rationale

Design Note:
Full (6e, 6o) π-system would yield ~1000-3000 Pauli terms, making tests
prohibitively slow. The (4e, 4o) truncation provides a good balance
between chemical relevance and computational efficiency.

Test Results:
- All 12 QEL tests passing (7 unit + 5 molecular)
- Total test suite runtime: ~3.6 minutes
- Molecular test accuracies: H2 (0.11 mHa), LiH (0.66 mHa), N2 (0.33 mHa),
  H2O (0.22 mHa), Benzene (0.76 mHa)

Signed-off-by: Scott Thornton <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant