Source code for vibeqc.solvers._determinant

"""Determinant and configuration-state-function data structures.

Representation of Slater determinants as tuples of occupied orbital
indices, with helper functions for building the determinant space,
computing excitation ranks, and generating connected determinants.
"""

from __future__ import annotations

from typing import Iterator, Optional

import numpy as np

# Type alias: a determinant is a sorted tuple of integer orbital indices.
Det = tuple[int, ...]

# (alpha_occ, beta_occ) for unrestricted determinants.
SpinDet = tuple[Det, Det]


def determinant_string(occ: Det) -> str:
    """Render a determinant as a binary occupation string (0 = empty, 1 = occ)."""
    norb = max(occ) + 1 if occ else 0
    s = ["0"] * norb
    for i in occ:
        s[i] = "1"
    return "".join(s)


def excitation_rank(occ_a: Det, occ_b: Det) -> int:
    """Number of orbital replacements to go from ``occ_a`` to ``occ_b``.

    Both must have the same length.
    """
    if len(occ_a) != len(occ_b):
        raise ValueError("Determinants must have the same electron count")
    return len(set(occ_a) - set(occ_b))


def is_connected(occ_a: Det, occ_b: Det, max_rank: int = 2) -> bool:
    """True iff ``occ_a`` and ``occ_b`` differ by ≤ ``max_rank`` excitations.

    In standard ab initio Hamiltonians, only single and double excitations
    have non-zero matrix elements.
    """
    return excitation_rank(occ_a, occ_b) <= max_rank


[docs] def generate_determinants( norb: int, nalpha: int, nbeta: int, ) -> list[SpinDet]: """Generate all determinants for ``nalpha`` α + ``nbeta`` β electrons in ``norb`` orbitals. Returns a list of ``(alpha_occ, beta_occ)`` tuples in lexicographic order. This is the complete FCI determinant space — use with caution (``C(norb, nalpha) × C(norb, nbeta)``). """ from itertools import combinations alpha_dets = [tuple(c) for c in combinations(range(norb), nalpha)] beta_dets = [tuple(c) for c in combinations(range(norb), nbeta)] return [(a, b) for a in alpha_dets for b in beta_dets]
def generate_closed_shell_determinants( norb: int, nocc: int, ) -> list[Det]: """Generate all closed-shell (spin-restricted) determinants. Each determinant is represented as a tuple of spatial-orbital indices that are doubly occupied. """ from itertools import combinations return [tuple(c) for c in combinations(range(norb), nocc)] def generate_singles( ref: Det, norb: int, ) -> list[Det]: """All single excitations from ``ref`` (one electron moved to a virtual).""" dets: list[Det] = [] occ_set = set(ref) vir_set = set(range(norb)) - occ_set for i in occ_set: new_occ = list(ref) new_occ.remove(i) for a in sorted(vir_set): dets.append(tuple(sorted(new_occ + [a]))) return dets def generate_doubles( ref: Det, norb: int, ) -> list[Det]: """All double excitations from ``ref`` (two electrons moved).""" dets: list[Det] = [] occ_set = set(ref) occ_list = sorted(occ_set) vir_set = set(range(norb)) - occ_set vir_list = sorted(vir_set) for idx_i, i in enumerate(occ_list): for idx_j in range(idx_i + 1, len(occ_list)): j = occ_list[idx_j] for idx_a, a in enumerate(vir_list): for idx_b in range(idx_a + 1, len(vir_list)): b = vir_list[idx_b] new_occ = sorted(set(ref) - {i, j} | {a, b}) dets.append(tuple(new_occ)) return dets def reference_determinant( nalpha: int, nbeta: int, *, spin_restricted: bool = True, ) -> SpinDet: """The aufbau reference determinant: lowest-energy orbitals doubly occupied. For spin-restricted: ``(0,1,...,nalpha-1), (0,1,...,nbeta-1)``. """ if spin_restricted and nalpha == nbeta: occ = tuple(range(nalpha)) return (occ, occ) else: return (tuple(range(nalpha)), tuple(range(nbeta)))