High-precision calculations for one- and two-valence atomic systems
CSF.hpp
1#pragma once
2#include "IO/FRW_fileReadWrite.hpp"
3#include "LinAlg/include.hpp"
4#include "Wavefunction/DiracSpinor.hpp"
5#include <array>
6#include <iostream>
7#include <optional>
8#include <string_view>
9#include <utility>
10#include <vector>
11
12namespace CI {
13
14//==============================================================================
15/*!
16 @brief Two-electron configuration state function (CSF).
17 @details
18 A CSF is an antisymmetrised two-electron basis state with definite total
19 angular momentum (\f$ J^2 \f$, \f$ J_z \f$) and parity,
20 built from a pair of single-particle
21 relativistic orbitals. Only two-electron CSFs are implemented.
22
23 Each CSF2 stores the indices of its two constituent orbitals (always sorted
24 to avoid double-counting) and the total parity, which is the product of the
25 parities of the two single-particle states.
26
27 @note The orbital pair is stored as a sorted array of DiracSpinor::Index
28 (uint16_t) rather than DiracSpinor references, so CSF2 objects are
29 cheap to copy and store. There is a limit to maximum n<=256 - see @ref Angular::nk_to_index
30*/
31class CSF2 {
32 int m_parity;
33
34public:
35 // nb: array of states is always sorted
36 std::array<DiracSpinor::Index, 2> states;
37
38 CSF2(const DiracSpinor &a, const DiracSpinor &b);
39
40 //! Index (nk_index) of the ith constituent orbital (i = 0 or 1)
41 DiracSpinor::Index state(std::size_t i) const;
42
43 friend bool operator==(const CSF2 &A, const CSF2 &B);
44 friend bool operator!=(const CSF2 &A, const CSF2 &B);
45
46 /*!
47 @brief Returns the number of orbitals that differ between two CSFs (0, 1,
48 or 2).
49 @details
50 Used to select the appropriate Slater-Condon rule when evaluating CI matrix
51 elements: 0 -- diagonal; 1 -- single substitution; 2 -- double
52 substitution; >2 -- zero by orthogonality.
53 */
54 static int num_different(const CSF2 &A, const CSF2 &B);
55
56 /*!
57 @brief For two CSFs differing by exactly one orbital, returns {n, a} where
58 @p V contains orbital n and @p X contains orbital a.
59 @details
60 Identifies the "particle" index n (in @p V but not @p X) and the "hole"
61 index a (in @p X but not @p V), as needed to apply the single-substitution
62 Slater-Condon rule: \f$ \langle V | \hat{O} | X \rangle \f$ where
63 \f$ |V\rangle = \hat{a}^\dag_n \hat{a}_a |X\rangle \f$.
64
65 @warning Result is undefined if @p V and @p X do not differ by exactly one
66 orbital; check with num_different() first.
67 */
68 static std::array<DiracSpinor::Index, 2> diff_1_na(const CSF2 &V,
69 const CSF2 &X);
70
71 /*!
72 @brief Returns the orbital index shared by two CSFs that differ by exactly
73 one orbital.
74 @details
75 Extracts the common (spectator) orbital needed for single-substitution
76 matrix elements.
77
78 @warning Assumes @p A and @p B differ by exactly one orbital.
79 */
80 static DiracSpinor::Index same_1_j(const CSF2 &A, const CSF2 &B);
81
82 //! Parity of the CSF, +/-1
83 int parity() const;
84
85 //! Single-particle configuration as a string, in relativistic or non-rel form
86 std::string config(bool relativistic = false) const;
87};
88
89//==============================================================================
90/*!
91 @brief Forms all two-electron CSFs with given total J and parity.
92 @details
93 Iterates over all pairs of single-particle states in @p cisp_basis and
94 retains those whose angular momenta can be coupled to total \f$ J = \f$
95 @p twoJ /2 and whose combined parity equals @p parity. Duplicate pairs are
96 excluded by construction.
97
98 @param twoJ Twice the total angular momentum 2J.
99 @param parity Total parity: +1 (even) or -1 (odd).
100 @param cisp_basis Single-particle basis from which CSFs are constructed.
101 @return Sorted list of all valid two-electron CSFs for the given J and parity.
102*/
103std::vector<CSF2> form_CSFs(int twoJ, int parity,
104 const std::vector<DiracSpinor> &cisp_basis);
105
106//==============================================================================
107/*!
108 @brief jj -> LS recoupling amplitude for an antisymmetrised two-electron CSF.
109 @details
110 Returns the amplitude of the antisymmetrised jj-coupled CSF
111 \f$ |\{(n_1 l_1 j_1)(n_2 l_2 j_2)\}; J\rangle \f$ (orbitals in stored,
112 i.e., sorted, order) onto the antisymmetrised LS-coupled state
113 \f$ |\{(n_1 l_1)(n_2 l_2)\} L S; J\rangle \f$ of the same non-relativistic
114 configuration:
115
116 \f[
117 A(L,S) = \eta \sqrt{[j_1][j_2][L][S]}
118 \begin{Bmatrix} l_1 & l_2 & L \\ 1/2 & 1/2 & S \\ j_1 & j_2 & J \end{Bmatrix}
119 \f]
120
121 Taken in the non-relativistic limit: the radial orbitals of
122 \f$ j = l \pm 1/2 \f$ are treated as identical (overlap = 1).
123
124 For a common non-relativistic shell (\f$ n_1 = n_2 \f$, \f$ l_1 = l_2 \f$)
125 only L+S even terms exist (Pauli). When additionally \f$ j_1 \neq j_2 \f$
126 the L+S odd components cancel in the antisymmetrisation and the even ones
127 carry \f$ \eta = \sqrt{2} \f$; otherwise \f$ \eta = 1 \f$.
128 In all cases \f$ \sum_{LS} A^2 = 1 \f$.
129
130 @param n1,l1,twoj1 Quantum numbers of the first stored orbital.
131 @param n2,l2,twoj2 Quantum numbers of the second stored orbital.
132 @param L,S Total orbital and spin angular momenta of the LS term.
133 @param twoJ Twice the total angular momentum 2J.
134 @return Recoupling amplitude A(L,S); zero if forbidden.
135
136 @note The sign convention follows the stored (sorted) orbital order; since
137 nk_index sorting keeps the (n, l) order identical for all CSFs of one
138 non-relativistic configuration, relative signs between such CSFs are
139 consistent.
140*/
141double LS_amplitude(int n1, int l1, int twoj1, int n2, int l2, int twoj2, int L,
142 int S, int twoJ);
143
144/*!
145 @brief Expectation values of L^2 and S^2 for a two-electron CI state.
146 @details
147 Recouples each CSF to LS coupling (see @ref LS_amplitude) and accumulates,
148 per non-relativistic configuration g,
149 \f$ B_g(L,S) = \sum_{I \in g} c_I A_I(L,S) \f$, giving
150
151 \f[
152 \langle L^2 \rangle = \sum_{g,L,S} B_g(L,S)^2 \, L(L+1), \qquad
153 \langle S^2 \rangle = \sum_{g,L,S} B_g(L,S)^2 \, S(S+1).
154 \f]
155
156 These are expectation values of the CI state, not eigenvalues: deviation
157 from L(L+1), S(S+1) measures the LS-purity of the state.
158
159 @param coefs CI expansion coefficients (one per CSF).
160 @param csfs The CSF basis (matching @p coefs).
161 @param twoJ Twice the total angular momentum 2J.
162 @return Pair {<L^2>, <S^2>}.
163
164 @note Non-relativistic limit: radial overlaps between j = l +- 1/2 orbitals
165 are set to 1, so for a normalised state the total LS weight is exactly
166 1 and no renormalisation is required.
167*/
168std::pair<double, double>
170 const std::vector<CSF2> &csfs, int twoJ);
171
172//==============================================================================
173/*!
174 @brief Identifies one CI level: its (J, parity), and which solution.
175 @details
176 The standard text form is `J{+,-}:index`, e.g., `2+:3` is the fourth solution
177 (index counts from zero) of the J=2, even parity, CI.
178 The index may be omitted, in which case it is zero:
179 `0+` is the lowest even-parity J=0 solution. See @ref parse_level and
180 @ref to_string.
181*/
182struct Level {
183 //! Twice the total angular momentum, 2J
184 int twoJ{0};
185 //! Parity: +1 or -1
186 int parity{1};
187 //! Which solution, counting from zero, in order of energy
188 std::size_t index{0};
189};
190
191/*!
192 @brief Parses the text form of a CI level reference; see @ref Level.
193 @details
194 Accepts `2+:3` (standard) and `e2:3`; the index is optional. Surrounding
195 whitespace is ignored. Only integer J is accepted, since only two-electron CI
196 is implemented.
197
198 @param str Text form, e.g., `2+:3`, `e2:3`, `0+`.
199 @return The level; empty if @p str is not a valid level reference.
200*/
201[[nodiscard]] std::optional<Level> parse_level(std::string_view str);
202
203//! Text form of a CI level reference, e.g., "2+:3"; see @ref Level
204[[nodiscard]] std::string to_string(const Level &level);
205
206//==============================================================================
207/*!
208 @brief Configuration metadata for a single CI level.
209 @details
210 Stores identifying information derived after solving the CI eigenvalue
211 problem: the dominant non-relativistic configuration label, the squared CI
212 coefficient of that configuration, and approximate good quantum numbers
213 (g_J factor, L, S) where they can be assigned.
214
215 Fields are left at their default (empty/negative) values if not yet computed;
216 call @ref PsiJPi::update_config_info() to populate them.
217*/
219 //! Dominant configuration label (typically non-relativistic notation)
220 std::string config{};
221 //! Squared CI coefficient of the dominant configuration (or sum over non-rel degenerates)
222 double ci2{0.0};
223 double gJ{0.0};
224 //! Approximate orbital angular momentum L (-1 if not assigned)
225 double L{-1.0};
226 //! Twice the approximate spin S (-1 if not assigned)
227 double twoS{-1.0};
228 //! Expectation value of L^2 for the CI state (-1 if not computed)
229 double L2{-1.0};
230 //! Expectation value of S^2 for the CI state (-1 if not computed)
231 double S2{-1.0};
232};
233
234//==============================================================================
235/*!
236 @brief Container for CI solutions in a single (J, parity) block.
237 @details
238 Holds the complete set of configuration state functions and the results of
239 the CI diagonalisation for a fixed total angular momentum J and parity.
240
241 Construction builds the CSF basis via form_CSFs() but does not solve the
242 eigenvalue problem; call solve() separately after constructing the CI
243 Hamiltonian matrix. Configuration labels are not set automatically -- call
244 update_config_info() for each solution after solving.
245
246 @note Only two-electron (two-particle) systems are supported.
247*/
248class PsiJPi {
249
250 int m_twoj{-1};
251 int m_pi{0};
252
253 // Number of solutions stored:
254 std::size_t m_num_solutions{0};
255 // List of CSFs
256 std::vector<CSF2> m_CSFs{};
257 // Energy, and CI expansion coeficients
258 std::pair<LinAlg::Vector<double>, LinAlg::Matrix<double>> m_Solution{};
259 std::vector<ConfigInfo> m_Info{};
260
261public:
262 /*!
263 @brief Constructs the CSF basis for the given J and parity; does not solve.
264 @details
265 Calls form_CSFs() to build the list of two-electron CSFs. The eigenvalue
266 problem is not solved until solve() is called with the CI Hamiltonian.
267
268 @param twoJ Twice the total angular momentum 2J.
269 @param pi Total parity: +1 or -1.
270 @param cisp_basis Single-particle basis used to construct the CSFs.
271 */
272 PsiJPi(int twoJ, int pi, const std::vector<DiracSpinor> &cisp_basis)
273 : m_twoj(twoJ), m_pi(pi), m_CSFs(form_CSFs(twoJ, pi, cisp_basis)) {}
274
275 PsiJPi() {}
276
277 /*!
278 @brief Solves the CI eigenvalue problem for the given Hamiltonian matrix.
279 @details
280 Diagonalises @p Hci and stores the resulting eigenvalues and eigenvectors.
281 Does not populate ConfigInfo; call update_config_info() separately.
282
283 - If @p num_solutions > 0, finds only the lowest @p num_solutions eigenpairs.
284 - If @p all_below is set, finds all eigenpairs with energy below that value
285 (in cm^-1); @p num_solutions is then ignored.
286 - If both are unset (or @p num_solutions <= 0), all eigenpairs are computed.
287
288 @param Hci CI Hamiltonian matrix in the CSF basis.
289 @param num_solutions Number of lowest solutions to find [0 = all].
290 @param all_below If set, find all solutions below this energy (cm^-1).
291 */
292 void solve(const LinAlg::Matrix<double> &Hci, int num_solutions = 0,
293 std::optional<double> all_below = {});
294
295 /*!
296 @brief Stores a single solution directly, without diagonalising.
297 @details
298 Replaces any existing solutions with the single one given. For states that
299 are not eigenstates of the CI Hamiltonian: e.g., the mixed states of
300 @ref solve_mixed_state, for which @p energy is that of the reference state.
301
302 @param energy Energy to be associated with the solution (atomic units).
303 @param coefs CI expansion coefficients; one per CSF.
304 */
305 void set_solution(double energy, const LinAlg::Vector<double> &coefs);
306
307 //! Set configuration info for the ith solution (must be called manually after solve())
308 void update_config_info(std::size_t i, const ConfigInfo &info);
309
310 //! Full list of CSFs spanning this (J, parity) block
311 const std::vector<CSF2> &CSFs() const;
312
313 //! Returns reference to the ith CSF
314 const CSF2 &CSF(std::size_t i) const;
315
316 //! Energy of the ith CI solution (atomic units)
317 double energy(std::size_t i) const;
318
319 //! CI expansion coefficients for the ith solution (one per CSF)
320 LinAlg::View<const double> coefs(std::size_t i) const;
321
322 //! CI coefficient for the ith solution corresponding to the jth CSF
323 double coef(std::size_t i, std::size_t j) const;
324
325 //! Parity of the block (+/-1)
326 int parity() const;
327
328 //! Twice the total angular momentum 2J for this block
329 int twoJ() const;
330
331 //! Number of CI solutions currently stored
332 std::size_t num_solutions() const;
333
334 //! Configuration info for the ith solution (must have been set via update_config_info())
335 const ConfigInfo &info(std::size_t i) const;
336
337 /*!
338 @brief Reads or writes CI solutions (energies, eigenvectors) to/from a multi-block binary file.
339 @details
340 A single file holds multiple blocks, one per (twoJ, parity) pair.
341 Each block is self-describing: (twoJ, pi, num_csfs, num_solutions, E[num_csfs], M[num_csfs x num_csfs]).
342 Energies and eigenvectors are always stored at full num_csfs size (zero-padded
343 if only a partial solve was done), so block size is determined from num_csfs
344 alone, enabling O(N) scan and in-place overwrite.
345
346 The CSF basis (m_CSFs) is not touched; it must be constructed via the normal
347 constructor before calling this function. The basis (and hence num_csfs) must
348 be consistent with the file on write; a mismatch causes failure.
349
350 ConfigInfo is not stored -- call update_config_info() after reading if needed.
351
352 On read: scans blocks until matching (twoJ, pi) is found, verifies num_csfs,
353 reads num_solutions, E, and M; returns false if block not found or num_csfs
354 mismatches.
355
356 On write: if the block already exists and num_csfs matches, overwrites it
357 in-place using @ref IO::FRW::update. If it is a new block, appends it to
358 the end of the file -- no rewrite of existing data.
359
360 @note No settings are stored in the file: the filename identifies the
361 calculation. The default filenames encode the settings that change the
362 solutions -- see @ref CI::configuration_interaction.
363
364 @param fname Path to the binary file.
365 @param rw @ref IO::FRW::read to read; @ref IO::FRW::write to write.
366 @param outstream Output stream for progress/notes.
367
368 @return True on success; false if the file does not exist (read), the block
369 is not found (read), or num_csfs mismatches.
370 */
371 bool read_write(const std::string &fname, IO::FRW::RoW rw,
372 std::ostream &outstream = std::cout);
373};
374
375} // namespace CI
Two-electron configuration state function (CSF).
Definition CSF.hpp:31
static DiracSpinor::Index same_1_j(const CSF2 &A, const CSF2 &B)
Returns the orbital index shared by two CSFs that differ by exactly one orbital.
Definition CSF.cpp:60
DiracSpinor::Index state(std::size_t i) const
Index (nk_index) of the ith constituent orbital (i = 0 or 1)
Definition CSF.cpp:22
std::string config(bool relativistic=false) const
Single-particle configuration as a string, in relativistic or non-rel form.
Definition CSF.cpp:75
int parity() const
Parity of the CSF, +/-1.
Definition CSF.cpp:73
static int num_different(const CSF2 &A, const CSF2 &B)
Returns the number of orbitals that differ between two CSFs (0, 1, or 2).
Definition CSF.cpp:35
static std::array< DiracSpinor::Index, 2 > diff_1_na(const CSF2 &V, const CSF2 &X)
For two CSFs differing by exactly one orbital, returns {n, a} where V contains orbital n and X contai...
Definition CSF.cpp:46
Container for CI solutions in a single (J, parity) block.
Definition CSF.hpp:248
void solve(const LinAlg::Matrix< double > &Hci, int num_solutions=0, std::optional< double > all_below={})
Solves the CI eigenvalue problem for the given Hamiltonian matrix.
Definition CSF.cpp:276
double energy(std::size_t i) const
Energy of the ith CI solution (atomic units)
Definition CSF.cpp:328
PsiJPi(int twoJ, int pi, const std::vector< DiracSpinor > &cisp_basis)
Constructs the CSF basis for the given J and parity; does not solve.
Definition CSF.hpp:272
LinAlg::View< const double > coefs(std::size_t i) const
CI expansion coefficients for the ith solution (one per CSF)
Definition CSF.cpp:334
bool read_write(const std::string &fname, IO::FRW::RoW rw, std::ostream &outstream=std::cout)
Reads or writes CI solutions (energies, eigenvectors) to/from a multi-block binary file.
Definition CSF.cpp:361
const ConfigInfo & info(std::size_t i) const
Configuration info for the ith solution (must have been set via update_config_info())
Definition CSF.cpp:355
std::size_t num_solutions() const
Number of CI solutions currently stored.
Definition CSF.cpp:352
const CSF2 & CSF(std::size_t i) const
Returns reference to the ith CSF.
Definition CSF.cpp:325
double coef(std::size_t i, std::size_t j) const
CI coefficient for the ith solution corresponding to the jth CSF.
Definition CSF.cpp:340
void set_solution(double energy, const LinAlg::Vector< double > &coefs)
Stores a single solution directly, without diagonalising.
Definition CSF.cpp:302
const std::vector< CSF2 > & CSFs() const
Full list of CSFs spanning this (J, parity) block.
Definition CSF.cpp:322
int twoJ() const
Twice the total angular momentum 2J for this block.
Definition CSF.cpp:349
void update_config_info(std::size_t i, const ConfigInfo &info)
Set configuration info for the ith solution (must be called manually after solve())
Definition CSF.cpp:316
int parity() const
Parity of the block (+/-1)
Definition CSF.cpp:346
Stores radial Dirac spinor: F_nk = (f, g)
Definition DiracSpinor.hpp:44
uint16_t Index
Integer type for the compressed (n,kappa) index.
Definition DiracSpinor.hpp:51
Row-major dense matrix with arithmetic and linear algebra support.
Definition Matrix.hpp:208
Owning 1D array; inherits from Matrix<T> with a single column.
Definition Vector.hpp:25
Non-owning strided view onto a 1D segment of an array.
Definition Matrix.hpp:69
Functions and classes for Configuration Interaction calculations.
Definition CI_Integrals.cpp:22
std::optional< Level > parse_level(std::string_view str)
Parses the text form of a CI level reference; see Level.
Definition CSF.cpp:91
int parity
Parity: +1 or -1.
Definition CSF.hpp:186
std::size_t index
Which solution, counting from zero, in order of energy.
Definition CSF.hpp:188
double LS_amplitude(int n1, int l1, int twoj1, int n2, int l2, int twoj2, int L, int S, int twoJ)
jj -> LS recoupling amplitude for an antisymmetrised two-electron CSF.
Definition CSF.cpp:202
double L
Approximate orbital angular momentum L (-1 if not assigned)
Definition CSF.hpp:225
double ci2
Squared CI coefficient of the dominant configuration (or sum over non-rel degenerates)
Definition CSF.hpp:222
std::pair< double, double > expectation_L2S2(const LinAlg::View< const double > &coefs, const std::vector< CSF2 > &csfs, int twoJ)
Expectation values of L^2 and S^2 for a two-electron CI state.
Definition CSF.cpp:225
double twoS
Twice the approximate spin S (-1 if not assigned)
Definition CSF.hpp:227
std::string config
Dominant configuration label (typically non-relativistic notation)
Definition CSF.hpp:220
int twoJ
Twice the total angular momentum, 2J.
Definition CSF.hpp:184
double S2
Expectation value of S^2 for the CI state (-1 if not computed)
Definition CSF.hpp:231
double L2
Expectation value of L^2 for the CI state (-1 if not computed)
Definition CSF.hpp:229
std::vector< CSF2 > form_CSFs(int twoJ, int parity, const std::vector< DiracSpinor > &cisp_basis)
Forms all two-electron CSFs with given total J and parity.
Definition CSF.cpp:164
std::string to_string(const Level &level)
Text form of a CI level reference, e.g., "2+:3"; see Level.
Definition CSF.cpp:157
Configuration metadata for a single CI level.
Definition CSF.hpp:218
Identifies one CI level: its (J, parity), and which solution.
Definition CSF.hpp:182