Source code for qarp.operators.onv

"""Occupation-number vectors as plain lists.

An ONV is a ``list[int]`` of spin-orbital occupations in abab (alpha/beta
interleaved) ordering: spin orbital ``2p`` is spatial orbital ``p`` alpha,
``2p + 1`` its beta partner.  Index ``i`` = spin orbital ``i`` = qubit ``i``
(LSB, ``qarp_conventions.md`` §1) — mappings' ``encode_state`` and state-
preparation blocks consume these lists positionally.
"""

from collections.abc import Sequence
from typing import Union

Onv = list[int]


[docs] def is_alpha(spin_orbital: int) -> bool: """True if the spin orbital sits on the alpha (even-index) sublattice of the abab layout.""" return spin_orbital % 2 == 0
[docs] def is_beta(spin_orbital: int) -> bool: """True if the spin orbital sits on the beta (odd-index) sublattice of the abab layout.""" return spin_orbital % 2 == 1
[docs] def same_spin(spin_orbital: int, other: int) -> bool: """True if two spin orbitals carry the same spin (same abab sublattice).""" return (spin_orbital - other) % 2 == 0
[docs] def freeze(onv: Onv, indices: Sequence[int]) -> Onv: """Return a new ONV with the given spin-orbital indices removed. Args: onv: The occupation-number vector. indices: Spin-orbital indices to freeze out. Returns: A new ONV without the frozen entries. """ if not all(0 <= idx < len(onv) for idx in indices): raise ValueError("Invalid indices, must be >= 0 and < total number of spin orbitals.") dropped = set(indices) return [occ for i, occ in enumerate(onv) if i not in dropped]
[docs] def freeze_spatial(onv: Onv, spatial_indices: Sequence[int]) -> Onv: """Return a new ONV with the given spatial orbitals (abab pairs) removed. Freezing spatial orbital ``p`` removes spin orbitals ``2p`` and ``2p + 1``. Args: onv: The occupation-number vector. spatial_indices: Spatial-orbital indices to freeze out. Returns: A new ONV without the frozen spin-orbital pairs. """ if not all(0 <= idx * 2 < len(onv) for idx in spatial_indices): raise ValueError("Spatial orbital index is out of range - did you mean freeze()?") spin_indices: list[int] = [] for idx in spatial_indices: spin_indices += [idx * 2, idx * 2 + 1] return freeze(onv, spin_indices)
[docs] def active_space( onv: Onv, active_electrons: Union[int, tuple[int, int]], active_orbitals: int ) -> Onv: """Return an Aufbau-obeying ONV for an active space carved from ``onv``. Args: onv: The full-space occupation-number vector. active_electrons: Number of active electrons, or an (alpha, beta) tuple. active_orbitals: Number of active spatial orbitals. Returns: The active-space ONV, obeying the Aufbau principle. """ total_num_electrons = sum(onv) if isinstance(active_electrons, int): beta_active_electrons = active_electrons // 2 alpha_active_electrons = active_electrons - beta_active_electrons else: alpha_active_electrons, beta_active_electrons = active_electrons if alpha_active_electrons + beta_active_electrons > total_num_electrons: raise ValueError("Cannot construct active space, too many active electrons requested") if alpha_active_electrons < beta_active_electrons: raise ValueError( "Cannot construct active space, number of beta electrons cannot be larger than that of alpha electrons" ) if active_orbitals > len(onv) // 2: raise ValueError("Cannot construct active space, too many active orbitals requested") if active_orbitals < alpha_active_electrons: raise ValueError("Cannot construct active space, not enough active orbitals") alphas = [1] * alpha_active_electrons + [0] * (active_orbitals - alpha_active_electrons) betas = [1] * beta_active_electrons + [0] * (active_orbitals - beta_active_electrons) return [val for pair in zip(alphas, betas, strict=True) for val in pair]
[docs] def onv_from_spatial_occupations(occupations: Sequence[int]) -> Onv: """Build an abab spin-orbital ONV from spatial-orbital occupations. Occupation 2 fills both spin orbitals, 1 fills the alpha spin orbital (matching the open-shell convention of restricted references), 0 neither: ``[2, 1, 0] -> [1, 1, 1, 0, 0, 0]``. Args: occupations: Per-spatial-orbital occupations, each 0, 1 or 2. Returns: The spin-orbital ONV. """ onv: Onv = [] for occ in occupations: if occ not in (0, 1, 2): raise ValueError(f"Spatial occupations must be 0, 1 or 2, got {occ}.") onv += [1, 1] if occ == 2 else ([1, 0] if occ == 1 else [0, 0]) return onv