Skip to content

Commit c1ae838

Browse files
authored
Merge pull request #263 from ktwalsh/devel
Added single excitation specific functions to citools
2 parents 21051b7 + 05884ff commit c1ae838

2 files changed

Lines changed: 202 additions & 0 deletions

File tree

src/libra_py/citools/interfaces.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,76 @@ def build_minimal_csf_basis(
163163

164164
return min_basis, transposed_basis
165165

166+
def build_minimal_csf_basis_singlet(
167+
configs: List[Tuple[int, ...]],
168+
active_space: List[int],
169+
nelec: int,
170+
max_unpaired: int
171+
) -> Tuple[List[Tuple[Any, ...]], List[Tuple[Any, ...]]]:
172+
"""
173+
Construct a minimal CSF (Configuration State Function) basis
174+
from raw MOPAC/Libra configurations.
175+
176+
Parameters
177+
----------
178+
configs : list[tuple[int]]
179+
Configurations extracted from Libra or MOPAC output (raw orbital numbers).
180+
active_space : list[int]
181+
List of active orbital numbers defining the active space.
182+
nelec : int
183+
Total number of electrons.
184+
max_unpaired : int
185+
Twice the target spin projection (2*Ms).
186+
For example, 0 corresponds to singlet-only configurations.
187+
188+
Returns
189+
-------
190+
min_basis : list[tuple]
191+
List of matching determinants (CSFs) as (configuration, phase) tuples.
192+
transposed_basis : list[tuple]
193+
Transposed representation, i.e. `list(zip(*min_basis))`.
194+
Each element groups together all configurations or all phases across the basis.
195+
196+
Notes
197+
-----
198+
- Each entry in `min_basis` corresponds to a determinant that matches
199+
one of the given MOPAC configurations, within the spin constraint `max_unpaired`.
200+
- `transposed_basis` is convenient for splitting the basis into
201+
separate lists of determinants and phases.
202+
- If no matches are found, `transposed_basis` is returned as an empty list.
203+
204+
Example
205+
-------
206+
>>> import numpy as np
207+
>>> # Example input from MOPAC/Libra
208+
>>> configs0_raw = [(6, -6, 7, -7, 9, -8)]
209+
>>> active_space = [6, 7, 8, 9, 10, 11]
210+
>>> nelec = 6
211+
>>> max_unpaired = 0 # singlet configurations only
212+
>>>
213+
>>> min_basis, (all_confs, all_phases) = build_minimal_csf_basis(
214+
... configs0_raw, active_space, nelec, max_unpaired
215+
... )
216+
>>>
217+
>>> print(len(min_basis))
218+
19
219+
>>> print(all_confs[0])
220+
(6, -6, 7, -7, 8, -8)
221+
>>> print(all_phases[0])
222+
1
223+
"""
224+
225+
# Generate all possible determinants (with parity) for the given active space
226+
# dets: List[Tuple[Any, ...]] = list(sd.generate_determinants_with_parity(active_space, nelec))
227+
228+
min_basis: List[Tuple[Any, ...]] = []
229+
for det, phase in sd.generate_single_excitations(active_space, nelec):
230+
min_basis.append((det, phase))
231+
232+
# Avoid error when basis is empty
233+
transposed_basis: List[Tuple[Any, ...]] = list(zip(*min_basis)) if min_basis else []
234+
235+
return min_basis, transposed_basis
166236

167237

168238

@@ -392,3 +462,72 @@ def configs_and_T_matrix(
392462
return mapped_basis, T
393463

394464

465+
def configs_and_T_matrix_singlet(
466+
configs0_raw: List[Tuple[int, ...]],
467+
active_space: List[int],
468+
orbital_space: List[int],
469+
nelec: int,
470+
S: int,
471+
Ms: int
472+
) -> Tuple[List[Tuple[int, ...]], "CMATRIX"]:
473+
"""
474+
Generate the minimal active-space configurations mapped to a given orbital space
475+
and the configuration-to-CSF transformation matrix for a CAS with given spin.
476+
477+
Parameters
478+
----------
479+
configs0_raw : list[tuple[int]]
480+
List of raw configurations from Libra/MOPAC (signed orbital indices).
481+
active_space : list[int]
482+
Orbitals defining the active space used to generate the minimal determinant basis.
483+
orbital_space : list[int]
484+
Orbital indices used for mapping configurations (output will be relative to this space).
485+
nelec : int
486+
Number of active electrons.
487+
S : int
488+
Total spin quantum number.
489+
Ms : int
490+
Spin projection quantum number.
491+
492+
Returns
493+
-------
494+
mapped_basis : list[tuple[int, ...]]
495+
List of minimal configurations mapped to the specified `orbital_space`,
496+
with signs preserved (positive = α-spin, negative = β-spin).
497+
T : CMATRIX
498+
Complex-valued configuration-to-CSF transformation matrix.
499+
500+
Example
501+
-------
502+
>>> # Build configurations and T matrix for singlet CAS
503+
>>> mapped_basis, T = configs_and_T_matrix(
504+
... configs0_raw,
505+
... active_space=[6, 7, 8, 9, 10, 11],
506+
... orbital_space=[2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
507+
... nelec=6,
508+
... S=0,
509+
... Ms=0
510+
... )
511+
>>> # mapped_basis shows minimal determinants mapped to orbital_space indices
512+
>>> print(mapped_basis[:5])
513+
[(5, -5, 6, -6, 7, -7),
514+
(5, -5, 6, -6, 7, -8),
515+
(5, -5, 6, -6, -7, 8),
516+
(5, -5, 6, -6, 7, -9),
517+
(5, -5, 6, -6, -7, 9)]
518+
>>> # T is the configuration-to-CSF transformation matrix
519+
>>> print("Shape of T:", T.num_of_rows, "x", T.num_of_cols)
520+
"""
521+
522+
# 1. Build minimal SD basis with spin constraint 2*Ms
523+
min_basis, (all_confs, all_phases) = build_minimal_csf_basis_singlet(
524+
configs0_raw, active_space, nelec, 2*Ms
525+
)
526+
527+
# 2. Map configurations to the specified orbital space
528+
mapped_basis = map_to_active_indices(all_confs, orbital_space)
529+
530+
# 3. Compute configuration-to-CSF transformation matrix
531+
T = conf2csf_matrix(min_basis, all_confs, all_phases, S, Ms)
532+
533+
return mapped_basis, T

src/libra_py/citools/slatdet.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from __future__ import annotations
12
# *********************************************************************************
23
# * Copyright (C) 2025 Alexey V. Akimov
34
# *
@@ -214,7 +215,69 @@ def generate_determinants_with_parity(
214215
parity: int = permutation_parity(original, sorted_combo)
215216
yield sorted_combo, parity
216217

218+
def generate_single_excitations(active_orbitals: List[int], nelec: int):
219+
"""
220+
Generate the ground-state determinant first, followed by all single-excitation
221+
determinants (sorted + parity), for a closed-shell reference inside an active space.
222+
223+
Parameters
224+
----------
225+
active_orbitals : list[int]
226+
Ordered list of spatial orbital numbers in the active space.
227+
Example: [1,2,3,4,5,6,7,8,9,10]
217228
229+
nelec : int
230+
Number of electrons in the active space. Must be even for closed-shell.
231+
Example: 10 -> 5 occupied spatial orbitals (paired).
232+
233+
Yields
234+
------
235+
(det_sorted, parity)
236+
det_sorted: tuple of signed spin-orbitals (canonical order)
237+
parity: +1 or -1
238+
"""
239+
if nelec % 2 != 0:
240+
raise ValueError("Closed-shell reference requires an even number of electrons.")
241+
242+
n_occ = nelec // 2
243+
occ = active_orbitals[:n_occ]
244+
virt = active_orbitals[n_occ:]
245+
246+
# -----------------------------
247+
# 1. Ground state determinant
248+
# -----------------------------
249+
ref_raw = []
250+
for o in occ:
251+
ref_raw.append(+o) # alpha
252+
ref_raw.append(-o) # beta
253+
254+
ref_sorted = tuple(sorted(ref_raw, key=canonical_sort_key))
255+
ref_parity = permutation_parity(tuple(ref_raw), ref_sorted)
256+
257+
# yield ground-state first
258+
yield ref_sorted, ref_parity
259+
260+
# -----------------------------
261+
# 2. Single excitations
262+
# -----------------------------
263+
for o in occ:
264+
for v in virt:
265+
266+
# α-spin excitation: +o → +v
267+
det_raw = list(ref_raw)
268+
det_raw.remove(+o)
269+
det_raw.append(+v)
270+
det_sorted = tuple(sorted(det_raw, key=canonical_sort_key))
271+
parity = permutation_parity(tuple(det_raw), det_sorted)
272+
yield det_sorted, parity
273+
274+
# β-spin excitation: -o → -v
275+
det_raw = list(ref_raw)
276+
det_raw.remove(-o)
277+
det_raw.append(-v)
278+
det_sorted = tuple(sorted(det_raw, key=canonical_sort_key))
279+
parity = permutation_parity(tuple(det_raw), det_sorted)
280+
yield det_sorted, parity
218281

219282
def slater_overlap_matrix(dets_A, dets_B, S_orb, complex_valued=False):
220283
"""

0 commit comments

Comments
 (0)