GQCP
Loading...
Searching...
No Matches
LinearExpansion.hpp
Go to the documentation of this file.
1// This file is part of GQCG-GQCP.
2//
3// Copyright (C) 2017-2020 the GQCG developers
4//
5// GQCG-GQCP is free software: you can redistribute it and/or modify
6// it under the terms of the GNU Lesser General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// GQCG-GQCP is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU Lesser General Public License for more details.
14//
15// You should have received a copy of the GNU Lesser General Public License
16// along with GQCG-GQCP. If not, see <http://www.gnu.org/licenses/>.
17
18#pragma once
19
20
34#include "ONVBasis/SpinResolvedONV.hpp"
35#include "ONVBasis/SpinResolvedONVBasis.hpp"
36#include "ONVBasis/SpinResolvedSelectedONVBasis.hpp"
37#include "ONVBasis/SpinUnresolvedSelectedONVBasis.hpp"
38#include "Partition/DiscreteDomainPartition.hpp"
39#include "Partition/ONVPartition.hpp"
40#include "Partition/SpinResolvedElectronPartition.hpp"
41#include "Partition/SpinUnresolvedElectronPartition.hpp"
42#include "Utilities/aliases.hpp"
44
45#include <boost/algorithm/string.hpp>
46#include <boost/dynamic_bitset.hpp>
47
48
49namespace GQCP {
50
51
58template <typename _Scalar, typename _ONVBasis>
60public:
61 // The scalar type of the expansion coefficients: real or complex.
62 using Scalar = _Scalar;
63
64 // The type of the ONV basis.
65 using ONVBasis = _ONVBasis;
66
67
68private:
69 // The ONV basis with respect to which the coefficients are defined.
70 ONVBasis onv_basis;
71
72 // The expansion coefficients.
73 VectorX<Scalar> m_coefficients;
74
75
76public:
77 /*
78 * MARK: Constructors
79 */
80
88 onv_basis {onv_basis},
89 m_coefficients {coefficients} {}
90
91
92 /*
93 * The default constructor.
94 */
95 LinearExpansion() = default;
96
97
98 /*
99 * MARK: Named constructors
100 */
101
110
111 VectorX<Scalar> coefficients = VectorX<Scalar>::Ones(onv_basis.dimension());
112 coefficients.normalize();
113
115 }
116
117
126
127 VectorX<Scalar> coefficients = VectorX<Scalar>::Unit(onv_basis.dimension(), 0); // The first ONV in the ONV basis is the HF determinant.
128
130 }
131
132
142
143 // Normalize the coefficients if they aren't.
144 return LinearExpansion<Scalar, ONVBasis>(onv_basis,
145 std::abs(coefficients.norm() - 1.0) > 1.0e-12 ? coefficients.normalized() : coefficients);
146 }
147
148
157
159 coefficients.normalize();
160
162 }
163
164
172 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
173 static enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedSelectedONVBasis>::value, LinearExpansion<double, SpinResolvedSelectedONVBasis>> FromGAMESSUS(const std::string& GAMESSUS_filename) {
174
175 // If the filename isn't properly converted into an input file stream, we assume the user supplied a wrong file.
176 std::ifstream input_file_stream {GAMESSUS_filename};
177 if (!input_file_stream.good()) {
178 throw std::runtime_error("LinearExpansionReader(std::string): The provided GAMESS file is illegible. Maybe you specified a wrong path?");
179 }
180
181 std::ifstream input_file_stream_count {GAMESSUS_filename}; // made to count the expansion size
182
183
184 // Do the actual parsing.
185
186 // Read in dummy lines up until we actually get to the ONVs and coefficients.
187 std::string line;
188 std::string buffer; // dummy for the counting stream
189 while (std::getline(input_file_stream, line)) {
190 std::getline(input_file_stream_count, buffer);
191
192
193 if (line.find("ALPHA") != std::string::npos && line.find("BETA") != std::string::npos && line.find("COEFFICIENT") != std::string::npos) { // if find() returns an index that's different from the 'not-found' index
194
195 // This line should have dashes and we skip it
196 std::getline(input_file_stream, line);
197 std::getline(input_file_stream_count, buffer);
198 break;
199 }
200 }
201
202 size_t space_size = 0;
203 // Count the number of configurations.
204 while (std::getline(input_file_stream_count, buffer)) {
205 if (buffer.length() != 0 | buffer[0] != '\n') {
206 space_size++;
207 }
208 }
209
211
212
213 std::getline(input_file_stream, line);
214
215 // Read the first line containing the configurations.
216 std::vector<std::string> splitted_line;
217 boost::split(splitted_line, line, boost::is_any_of("|")); // split on '|'
218
219
220 // Create an alpha ONV for the first field
221 std::string trimmed_alpha = boost::algorithm::trim_copy(splitted_line[0]);
222
223 // Create a beta ONV for the second field
224 std::string trimmed_beta = boost::algorithm::trim_copy(splitted_line[1]);
225
226
227 size_t index_count = 0; // counts the number of configurations added
228 coefficients(index_count) = std::stod(splitted_line[2]);
229
230
231 // Parse the trimmed ONV strings into boost::dynamic_bitset to use its functionality.
232 std::string reversed_alpha {trimmed_alpha.rbegin(), trimmed_alpha.rend()};
233 std::string reversed_beta {trimmed_beta.rbegin(), trimmed_beta.rend()};
234
235 boost::dynamic_bitset<> alpha_transfer {reversed_alpha};
236 boost::dynamic_bitset<> beta_transfer {reversed_beta};
237
238 size_t K = alpha_transfer.size();
239 size_t N_alpha = alpha_transfer.count();
240 size_t N_beta = beta_transfer.count();
241
242 SpinResolvedSelectedONVBasis onv_basis {K, N_alpha, N_beta};
243 onv_basis.expandWith(SpinResolvedONV::FromString(reversed_alpha, reversed_beta));
244
245
246 // Read in the ONVs and the coefficients by splitting the line on '|', and then trimming whitespace.
247 // In the GAMESS-US format, the bit strings are ordered in reverse.
248 while (std::getline(input_file_stream, line)) {
249
250 index_count++;
251 boost::split(splitted_line, line, boost::is_any_of("|")); // split on '|'
252
253
254 // Create an alpha SpinUnresolvedONV for the first field
255 trimmed_alpha = boost::algorithm::trim_copy(splitted_line[0]);
256 if (trimmed_alpha.length() != K) {
257 throw std::invalid_argument("LinearExpansionReader(std::string): One of the provided alpha ONVs does not have the correct number of orbitals.");
258 }
259 reversed_alpha = std::string(trimmed_alpha.rbegin(), trimmed_alpha.rend());
260
261 // Create a beta SpinUnresolvedONV for the second field
262 trimmed_beta = boost::algorithm::trim_copy(splitted_line[1]);
263 if (trimmed_beta.length() != K) {
264 throw std::invalid_argument("LinearExpansionReader(std::string): One of the provided beta ONVs does not have the correct number of orbitals.");
265 }
266 reversed_beta = std::string(trimmed_beta.rbegin(), trimmed_beta.rend());
267
268
269 // Create a double for the third field
270 coefficients(index_count) = std::stod(splitted_line[2]);
271 onv_basis.expandWith(SpinResolvedONV::FromString(reversed_alpha, reversed_beta));
272
273 } // The `while getline` loop.
274
276 }
277
278
288 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
289 static enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, LinearExpansion<double, SpinResolvedONVBasis>> FromONVProjection(const SpinResolvedONV& onv, const RSpinOrbitalBasis<double, GTOShell>& r_spinor_basis, const USpinOrbitalBasis<double, GTOShell>& u_spinor_basis) {
290
291 // Determine the overlap matrices of the underlying scalar orbital bases, which is needed later on.
292 auto S_r = r_spinor_basis.overlap(); // The overlap matrix of the restricted MOs/spin-orbitals.
293 S_r.transform(r_spinor_basis.expansion().inverse()); // now in AO basis
294
295 auto S_u = u_spinor_basis.overlap(); // The overlap matrix of the unrestricted spin-orbitals.
296 S_u.transform(u_spinor_basis.expansion().inverse()); // Now in AO basis.
297
298 if (!(S_r.parameters().isApprox(S_u.alpha().parameters(), 1.0e-08)) || !(S_r.parameters().isApprox(S_u.beta().parameters(), 1.0e-08))) {
299 throw std::invalid_argument("LinearExpansion::FromONVProjection(const SpinResolvedONV&, const RSpinOrbitalBasis<double, GTOShell>&, const USpinOrbitalBasis<double, GTOShell>&): The given spinor bases are not expressed using the same scalar orbital basis.");
300 }
301
302
303 // Prepare some parameters.
304 const auto& C_restricted = r_spinor_basis.expansion();
305
306 const auto C_unrestricted = u_spinor_basis.expansion();
307
308
309 // Set up the required spin-resolved ONV basis.
310 const auto K = onv.numberOfSpatialOrbitals(Spin::alpha); // assume equal numbers of spin-orbitals for alpha- and beta-electrons
311 const auto N_alpha = onv.numberOfElectrons(Spin::alpha);
312 const auto N_beta = onv.numberOfElectrons(Spin::beta);
313 const SpinResolvedONVBasis onv_basis {K, N_alpha, N_beta};
314
315
316 // Determine the coefficients through calculating the overlap between two ONVs.
317 VectorX<double> coefficients = VectorX<double>::Zero(onv_basis.dimension());
318
319 onv_basis.forEach([&onv, &C_unrestricted, &C_restricted, &S_r, &coefficients, &onv_basis](const SpinUnresolvedONV& alpha_onv, const size_t I_alpha, const SpinUnresolvedONV& beta_onv, const size_t I_beta) {
320 const SpinResolvedONV onv_on {alpha_onv, beta_onv}; // the spin-resolved ONV that should be projected 'on'
321
322 const auto coefficient = onv.calculateProjection(onv_on, C_unrestricted, C_restricted, S_r.parameters());
323 const auto address = onv_basis.compoundAddress(I_alpha, I_beta);
324
325 coefficients(address) = coefficient;
326 });
327
329 }
330
331
341 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
342 static enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinUnresolvedONVBasis>::value, LinearExpansion<double, SpinUnresolvedONVBasis>> FromONVProjection(const SpinUnresolvedONV& onv_of, const GSpinorBasis<double, GTOShell>& spinor_basis_on, const GSpinorBasis<double, GTOShell>& spinor_basis_of) {
343
344 // Determine the overlap matrices of the underlying scalar orbital bases, which is needed later on.
345 auto S_on = spinor_basis_on.overlap();
346 S_on.transform(spinor_basis_on.expansion().inverse()); // now in AO basis
347
348 auto S_of = spinor_basis_of.overlap();
349 S_of.transform(spinor_basis_of.expansion().inverse()); // now in AO basis
350
351 if (!(S_on.parameters().isApprox(S_of.parameters(), 1.0e-08))) {
352 throw std::invalid_argument("LinearExpansion::FromONVProjection(const SpinUnresolvedONV&, const RSpinOrbitalBasis<double, GTOShell>&, const GSpinorBasis<double, GTOShell>&): The given spinor bases are not expressed using the same scalar orbital basis.");
353 }
354
355
356 // Prepare some parameters.
357 const auto& C_on = spinor_basis_on.expansion();
358 const auto& C_of = spinor_basis_of.expansion();
359
360
361 // Set up the required spin-resolved ONV basis.
362 const auto M = onv_of.numberOfSpinors();
363 const auto N = onv_of.numberOfElectrons();
364 const SpinUnresolvedONVBasis onv_basis {M, N};
365
366
367 // Determine the coefficients through calculating the overlap between two ONVs.
368 VectorX<double> coefficients = VectorX<double>::Zero(onv_basis.dimension());
369 onv_basis.forEach([&onv_of, &C_on, &C_of, &S_on, &coefficients](const SpinUnresolvedONV& onv_on, const size_t I) {
370 coefficients(I) = onv_of.calculateProjection(onv_on, C_of, C_on, S_on.parameters());
371 });
372
374 }
375
376
377 /*
378 * MARK: Access
379 */
380
388 Scalar coefficient(const size_t i) const { return this->m_coefficients(i); }
389
393 const VectorX<Scalar>& coefficients() const { return this->m_coefficients; }
394
398 const ONVBasis& onvBasis() const { return onv_basis; }
399
400
401 /*
402 * MARK: Basis transformations
403 */
404
413 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
414 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value> basisTransform(const RTransformation<double>& T) {
415
416 const auto K = onv_basis.numberOfOrbitals(); // number of spatial orbitals
417 if (K != T.numberOfOrbitals()) {
418 throw std::invalid_argument("LinearExpansion::basisTransform(const RTransformation<double>&): The number of spatial orbitals does not match the dimension of the transformation matrix.");
419 }
420
421
422 // LU-decompose the transformation matrix LU decomposition for T
423 const auto& lu_decomposition = T.matrix().noPivotLUDecompose();
424
426 L.triangularView<Eigen::StrictlyLower>() = lu_decomposition[0];
427
428 SquareMatrix<double> U = SquareMatrix<double>(lu_decomposition[1].triangularView<Eigen::Upper>());
429 SquareMatrix<double> U_inv = U.inverse();
430
431
432 // Calculate t (the operator which allows per-orbital transformation of the wave function)
434
435
436 // Set up spin-unresolved ONV basis variables for the loops over the ONVs
437 const SpinUnresolvedONVBasis& alpha_onv_basis = onv_basis.alpha();
438 const SpinUnresolvedONVBasis& beta_onv_basis = onv_basis.beta();
439
440 auto dim_alpha = alpha_onv_basis.dimension();
441 auto dim_beta = beta_onv_basis.dimension();
442 auto N_alpha = alpha_onv_basis.numberOfElectrons();
443 auto N_beta = beta_onv_basis.numberOfElectrons();
444
445
451 VectorX<double> current_coefficients = this->m_coefficients; // coefficients will be updated after each orbital transform (C^(n-1)) in Helgaker
452 VectorX<double> correction_coefficients = VectorX<double>::Zero(onv_basis.dimension());
453
454
455 for (size_t m = 0; m < K; m++) { // iterate over all orbitals
456
457 // Perform alpha and beta CI iterations.
458
459 // 1) Alpha-branch
460 SpinUnresolvedONV alpha = alpha_onv_basis.constructONVFromAddress(0);
461 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
462 if (!alpha.isOccupied(m)) {
463 for (size_t e1 = 0; e1 < N_alpha; e1++) { // e1 (electron 1) loops over the (number of) electrons
464 size_t p = alpha.occupationIndexOf(e1); // retrieve the index of a given electron
465
466 if (p < m) {
467 size_t address = I_alpha - alpha_onv_basis.vertexWeight(p, e1 + 1);
468 size_t e2 = e1 + 1;
469 size_t q = p + 1;
470 int sign = 1;
471
472 alpha_onv_basis.shiftUntilNextUnoccupiedOrbital<1>(alpha, address, q, e2, sign);
473 while (q != m) {
474 q++;
475 alpha_onv_basis.shiftUntilNextUnoccupiedOrbital<1>(alpha, address, q, e2, sign);
476 }
477
478 address += alpha_onv_basis.vertexWeight(q, e2);
479
480 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
481 correction_coefficients(I_alpha * dim_beta + I_beta) += sign * t(p, m) * current_coefficients(address * dim_beta + I_beta);
482 }
483 }
484
485 if (p > m) {
486 size_t address = I_alpha - alpha_onv_basis.vertexWeight(p, e1 + 1);
487 size_t e2 = e1 - 1;
488 size_t q = p - 1;
489 int sign = 1;
490
491 alpha_onv_basis.shiftUntilPreviousUnoccupiedOrbital<1>(alpha, address, q, e2, sign);
492 while (q != m) {
493 q--;
494 alpha_onv_basis.shiftUntilPreviousUnoccupiedOrbital<1>(alpha, address, q, e2, sign);
495 }
496
497 address += alpha_onv_basis.vertexWeight(q, e2 + 2);
498 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
499 correction_coefficients(I_alpha * dim_beta + I_beta) += sign * t(p, m) * current_coefficients(address * dim_beta + I_beta);
500 }
501 }
502 }
503
504 } else { // if orbital m is occupied we can perform an in-place operation
505 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
506 correction_coefficients(I_alpha * dim_beta + I_beta) += (t(m, m) - 1) * current_coefficients(I_alpha * dim_beta + I_beta);
507 }
508 }
509
510
511 if (I_alpha < dim_alpha - 1) { // prevent the last permutation from occurring
512 alpha_onv_basis.transformONVToNextPermutation(alpha);
513 }
514 }
515
516 current_coefficients += correction_coefficients;
517 correction_coefficients.setZero();
518
519 // 2) Beta-branch
520 SpinUnresolvedONV beta = beta_onv_basis.constructONVFromAddress(0);
521
522 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
523 if (!beta.isOccupied(m)) {
524 for (size_t e1 = 0; e1 < N_beta; e1++) { // e1 (electron 1) loops over the (number of) electrons
525 size_t p = beta.occupationIndexOf(e1); // retrieve the index of a given electron
526
527 if (p < m) {
528 size_t address = I_beta - beta_onv_basis.vertexWeight(p, e1 + 1);
529 size_t e2 = e1 + 1;
530 size_t q = p + 1;
531 int sign = 1;
532
533 beta_onv_basis.shiftUntilNextUnoccupiedOrbital<1>(beta, address, q, e2, sign);
534 while (q != m) {
535 q++;
536 beta_onv_basis.shiftUntilNextUnoccupiedOrbital<1>(beta, address, q, e2, sign);
537 }
538
539 address += beta_onv_basis.vertexWeight(q, e2);
540
541 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
542 correction_coefficients(I_alpha * dim_beta + I_beta) += sign * t(p, m) * current_coefficients(I_alpha * dim_beta + address);
543 }
544 }
545
546 if (p > m) {
547 size_t address = I_beta - beta_onv_basis.vertexWeight(p, e1 + 1);
548 size_t e2 = e1 - 1;
549 size_t q = p - 1;
550
551 int sign = 1;
552
553 beta_onv_basis.shiftUntilPreviousUnoccupiedOrbital<1>(beta, address, q, e2, sign);
554 while (q != m) {
555 q--;
556 beta_onv_basis.shiftUntilPreviousUnoccupiedOrbital<1>(beta, address, q, e2, sign);
557 }
558
559 address += beta_onv_basis.vertexWeight(q, e2 + 2);
560
561 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
562 correction_coefficients(I_alpha * dim_beta + I_beta) += sign * t(p, m) * current_coefficients(I_alpha * dim_beta + address);
563 }
564 }
565 }
566
567 } else { // if orbital m is occupied we can perform an in-place operation
568 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
569 correction_coefficients(I_alpha * dim_beta + I_beta) += (t(m, m) - 1) * current_coefficients(I_alpha * dim_beta + I_beta);
570 }
571 }
572
573 if (I_beta < dim_beta - 1) { // prevent the last permutation from occurring
574 beta_onv_basis.transformONVToNextPermutation(beta);
575 }
576 }
577
578 current_coefficients += correction_coefficients;
579 correction_coefficients.setZero();
580 }
581 this->m_coefficients = current_coefficients;
582 }
583
584
585 /*
586 * MARK: Density matrices for spin-unresolved ONV bases
587 */
588
589 /*
590 * Calculate general one-electron density matrix for a spin-unresolved wave function expansion.
591 *
592 * @return The generalized one-electron density matrix.
593 */
594 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
595 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinUnresolvedONVBasis>::value, G1DM<double>> calculate1DM() const {
596
597 // Prepare some variables.
598 const auto M = this->onv_basis.numberOfOrbitals();
599 const auto N = this->onv_basis.numberOfElectrons();
600 const auto dim = onv_basis.dimension(); // Dimension of the SpinUnresolvedONVBasis = number of SpinUnresolvedONVs.
601
603
604 SpinUnresolvedONV onv = onv_basis.constructONVFromAddress(0); // Start with ONV with address 0.
605 for (size_t J = 0; J < dim; J++) { // Loops over all possible ONV indices.
606
607 // Loop over electrons that can be annihilated in an ONV.
608 for (size_t e1 = 0; e1 < N; e1++) {
609
610 // Create an ONVPath for each new ONV.
611 ONVPath<SpinUnresolvedONVBasis> onv_path {onv_basis, onv};
612 const auto c_J = this->coefficient(J);
613
614 // Figure out the orbital index of the electron that will be annihilated.
615 const auto q = onv.occupationIndexOf(e1);
616
617
618 // The diagonal values are a result of annihilation-creation on the same orbital index and are thus the same as the initial ONV.
619 D(q, q) += c_J * c_J;
620
621 // For the non-diagonal values, we will create all possible matrix elements of the density matrix in the routine below.
622 onv_path.annihilate(q, e1);
623
624 // Stop the loop if 1) the path is finished, meaning that orbital index p is at M (the total number of orbitals) and 2) if the orbital index is out of bounds after left translation of a vertical arc.
625 while (!onv_path.isFinished() && onv_path.isOrbitalIndexValid()) {
626
627 // Find the next unoccupied orbital, i.e. the next vertical arc in the path.
628 onv_path.leftTranslateDiagonalArcUntilVerticalArc();
629
630 // Calculate the address of the path if we would close it right now.
631 const auto I = onv_path.addressAfterCreation();
632 const auto c_I = this->coefficient(I);
633
634
635 // Add the density matrix elements.
636 const auto p = onv_path.orbitalIndex();
637
638 const double value = c_I * c_J;
639
640 D(p, q) += onv_path.sign() * value;
641 D(q, p) += onv_path.sign() * value;
642
643 // Move orbital index such that other unoccupied orbitals can be found within the loop.
644 onv_path.leftTranslateVerticalArc();
645 }
646 }
647
648 // Prevent last ONV since there is no possibility for an electron to be annihilated anymore.
649 if (J < dim - 1) {
650 onv_basis.transformONVToNextPermutation(onv);
651 }
652 }
653
654 return G1DM<double> {D};
655 }
656
657
668 template <typename Z = ONVBasis>
669 enable_if_t<std::is_same<Z, SpinUnresolvedONVBasis>::value, Scalar> calculateNDMElement(const std::vector<size_t>& bra_indices, const std::vector<size_t>& ket_indices) const {
670
671 // The ket indices should be reversed because the annihilators on the ket should be applied from right to left.
672 std::vector<size_t> ket_indices_reversed = ket_indices;
673 std::reverse(ket_indices_reversed.begin(), ket_indices_reversed.end());
674
675
676 Scalar value = 0.0;
677 int sign_bra = 1;
678 int sign_ket = 1;
679 const auto dim = this->onv_basis.dimension();
680
681
682 auto bra = this->onv_basis.constructONVFromAddress(0);
683 size_t I = 0;
684 while (I < dim) { // Loop over all bra addresses.
685
686 // Annihilate the bra on the bra indices.
687 if (!bra.annihilateAll(bra_indices, sign_bra)) { // If we can't annihilate, the bra doesn't change.
688
689 // Go to the beginning of the outer while loop with the next bra.
690 if (I < dim - 1) { // Prevent the last permutation from occurring.
691 this->onv_basis.transformONVToNextPermutation(bra);
692 I++;
693 sign_bra = 1;
694 continue;
695 } else {
696 break; // We have to jump out if we have looped over the whole bra dimension.
697 }
698 }
699
700
701 auto ket = this->onv_basis.constructONVFromAddress(0);
702 size_t J = 0;
703 while (J < dim) { // Loop over all ket indices.
704
705 // Annihilate the ket on the ket indices.
706 if (!ket.annihilateAll(ket_indices_reversed, sign_ket)) { // If we can't annihilate, the ket doesn't change.
707 // Go to the beginning of this (the inner) while loop with the next bra.
708 if (J < dim - 1) { // Prevent the last permutation from occurring.
709 this->onv_basis.transformONVToNextPermutation(ket);
710 J++;
711 sign_ket = 1;
712 continue;
713 } else {
714 break; // We have to jump out if we have looped over the whole ket dimension.
715 }
716 }
717
718 if (bra == ket) {
719 value += static_cast<double>(sign_bra * sign_ket) * GQCP::conj(this->coefficient(I)) * this->coefficient(J);
720 }
721
722 // Reset the previous ket annihilations and move to the next ket.
723 if (J == dim - 1) { // Prevent the last permutation from occurring.
724 break; // Out of the J-loop.
725 }
726 ket.createAll(ket_indices_reversed);
727 this->onv_basis.transformONVToNextPermutation(ket);
728 sign_ket = 1;
729 J++;
730 } // Loop over all ket indices J.
731
732 // Reset the previous bra annihilations and move to the next bra.
733 if (I == dim - 1) { // Prevent the last permutation from occurring.
734 break; // Out of the I-loop.
735 }
736 bra.createAll(bra_indices);
737 this->onv_basis.transformONVToNextPermutation(bra);
738 sign_bra = 1;
739 I++;
740 } // Loop over all bra indices I.
741
742 return value;
743 }
744
745
756 template <typename Z = ONVBasis>
757 enable_if_t<std::is_same<Z, SpinUnresolvedSelectedONVBasis>::value, Scalar> calculateNDMElement(const std::vector<size_t>& bra_indices, const std::vector<size_t>& ket_indices) const {
758
759 // The ket indices should be reversed because the annihilators on the ket should be applied from right to left.
760 std::vector<size_t> ket_indices_reversed = ket_indices;
761 std::reverse(ket_indices_reversed.begin(), ket_indices_reversed.end());
762
763
764 Scalar value = 0.0;
765 int sign_bra = 1;
766 int sign_ket = 1;
767 const auto dim = this->onv_basis.dimension();
768
769
770 size_t I = 0;
771 auto bra = this->onv_basis.onvWithIndex(I);
772 while (I < dim) { // Loop over all bra addresses.
773
774 // Annihilate the bra on the bra indices.
775 if (!bra.annihilateAll(bra_indices, sign_bra)) { // If we can't annihilate, the bra doesn't change.
776
777 // Go to the beginning of the outer while loop with the next bra.
778 if (I < dim - 1) { // Prevent the last permutation from occurring.
779 I++;
780 bra = this->onv_basis.onvWithIndex(I);
781 sign_bra = 1;
782 continue;
783 } else {
784 break; // We have to jump out if we have looped over the whole bra dimension.
785 }
786 }
787
788
789 size_t J = 0;
790 auto ket = this->onv_basis.onvWithIndex(J);
791 while (J < dim) { // Loop over all ket indices.
792
793 // Annihilate the ket on the ket indices.
794 if (!ket.annihilateAll(ket_indices_reversed, sign_ket)) { // If we can't annihilate, the ket doesn't change.
795 // Go to the beginning of this (the inner) while loop with the next bra.
796 if (J < dim - 1) { // Prevent the last permutation from occurring.
797 J++;
798 ket = this->onv_basis.onvWithIndex(J);
799 sign_ket = 1;
800 continue;
801 } else {
802 break; // We have to jump out if we have looped over the whole ket dimension.
803 }
804 }
805
806 if (bra == ket) {
807 value += static_cast<double>(sign_bra * sign_ket) * GQCP::conj(this->coefficient(I)) * this->coefficient(J);
808 }
809
810 // Reset the previous ket annihilations and move to the next ket.
811 if (J == dim - 1) { // Prevent the last permutation from occurring.
812 break; // Out of the J-loop.
813 }
814 ket.createAll(ket_indices_reversed);
815 J++;
816 ket = this->onv_basis.onvWithIndex(J);
817 sign_ket = 1;
818 } // Loop over all ket indices J.
819
820 // Reset the previous bra annihilations and move to the next bra.
821 if (I == dim - 1) { // Prevent the last permutation from occurring.
822 break; // Out of the I-loop.
823 }
824 I++;
825 bra = this->onv_basis.onvWithIndex(I);
826 sign_bra = 1;
827 } // Loop over all bra indices I.
828
829 return value;
830 }
831
832
833 /*
834 * MARK: Density matrices for spin-resolved ONV bases
835 */
836
842 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
843 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, SpinResolved1DM<double>> calculateSpinResolved1DM() const {
844
845 // Initialize as zero matrices.
846 size_t K = this->onv_basis.alpha().numberOfOrbitals();
847
850
851 SpinUnresolvedONVBasis onv_basis_alpha = onv_basis.alpha();
852 SpinUnresolvedONVBasis onv_basis_beta = onv_basis.beta();
853
854 auto dim_alpha = onv_basis_alpha.dimension();
855 auto dim_beta = onv_basis_beta.dimension();
856
857 // ALPHA
858 SpinUnresolvedONV spin_string_alpha = onv_basis_alpha.constructONVFromAddress(0); // alpha spin string with address 0
859 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) { // I_alpha loops over all the addresses of the alpha spin strings
860 for (size_t p = 0; p < K; p++) { // p loops over SOs
861 int sign_p = 1;
862 if (spin_string_alpha.annihilate(p, sign_p)) { // if p is in I_alpha
863 double diagonal_contribution = 0;
864
865 // Diagonal contributions for the 1-DM, i.e. D_pp
866 // We are storing the alpha addresses as 'major', i.e. the total address I_alpha I_beta = I_alpha * dim_beta + I_beta
867 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
868 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta);
869 diagonal_contribution += std::pow(c_I_alpha_I_beta, 2);
870 }
871 D_aa(p, p) += diagonal_contribution;
872
873 // Off-diagonal contributions for the 1-DM, i.e. D_pq (p!=q)
874 for (size_t q = 0; q < p; q++) { // q < p loops over SOs
875 int sign_pq = sign_p;
876 if (spin_string_alpha.create(q, sign_pq)) { // if q is not occupied in I_alpha
877 size_t J_alpha = onv_basis_alpha.addressOf(spin_string_alpha); // find all strings J_alpha that couple to I_alpha
878
879 double off_diagonal_contribution = 0;
880 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
881 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta); // alpha addresses are 'major'
882 double c_J_alpha_I_beta = this->coefficient(J_alpha * dim_beta + I_beta);
883 off_diagonal_contribution += c_I_alpha_I_beta * c_J_alpha_I_beta;
884 }
885
886 D_aa(p, q) += sign_pq * off_diagonal_contribution;
887 D_aa(q, p) += sign_pq * off_diagonal_contribution; // add the symmetric contribution because we are looping over q < p
888
889 spin_string_alpha.annihilate(q); // undo the previous creation
890
891 } // create on q
892 } // q loop
893
894 spin_string_alpha.create(p); // undo the previous annihilation
895
896 } // annihilate on p
897 } // p loop
898
899 if (I_alpha < dim_alpha - 1) { // prevent the last permutation from occurring
900 onv_basis_alpha.transformONVToNextPermutation(spin_string_alpha);
901 }
902
903 } // I_alpha loop
904
905
906 // BETA
907 SpinUnresolvedONV spin_string_beta = onv_basis_beta.constructONVFromAddress(0); // spin string with address 0
908 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) { // I_beta loops over all the addresses of the spin strings
909 for (size_t p = 0; p < K; p++) { // p loops over SOs
910 int sign_p = 1;
911 if (spin_string_beta.annihilate(p, sign_p)) { // if p is in I_beta
912 double diagonal_contribution = 0;
913
914 // Diagonal contributions for the 1-DM, i.e. D_pp
915 // We are storing the alpha addresses as 'major', i.e. the total address I_alpha I_beta = I_alpha * dim_beta + I_beta
916 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
917 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta);
918 diagonal_contribution += std::pow(c_I_alpha_I_beta, 2);
919 }
920
921 D_bb(p, p) += diagonal_contribution;
922
923 // Off-diagonal contributions for the 1-DM
924 for (size_t q = 0; q < p; q++) { // q < p loops over SOs
925 int sign_pq = sign_p;
926 if (spin_string_beta.create(q, sign_pq)) { // if q is not in I_beta
927 size_t J_beta = onv_basis_beta.addressOf(spin_string_beta); // find all strings J_beta that couple to I_beta
928
929 double off_diagonal_contribution = 0;
930 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
931 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta); // alpha addresses are 'major'
932 double c_I_alpha_J_beta = this->coefficient(I_alpha * dim_beta + J_beta);
933 off_diagonal_contribution += c_I_alpha_I_beta * c_I_alpha_J_beta;
934 }
935 D_bb(p, q) += sign_pq * off_diagonal_contribution;
936 D_bb(q, p) += sign_pq * off_diagonal_contribution; // add the symmetric contribution because we are looping over q < p
937
938 spin_string_beta.annihilate(q); // undo the previous creation
939
940 } // create on q
941 } // loop over q
942
943 spin_string_beta.create(p); // undo the previous annihilation
944
945 } // annihilate on p
946 } // loop over p
947
948 if (I_beta < dim_beta - 1) { // prevent the last permutation from occurring
949 onv_basis_beta.transformONVToNextPermutation(spin_string_beta);
950 }
951
952 } // I_beta loop.
954 }
955
956
962 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
963 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, SpinResolved2DM<double>> calculateSpinResolved2DM() const {
964
965 // KISS implementation of the 2-DMs (no symmetry relations are used yet)
966
967 SpinUnresolvedONVBasis onv_basis_alpha = onv_basis.alpha();
968 SpinUnresolvedONVBasis onv_basis_beta = onv_basis.beta();
969
970 auto dim_alpha = onv_basis_alpha.dimension();
971 auto dim_beta = onv_basis_beta.dimension();
972
973 // Initialize as zero matrices.
974 size_t K = this->onv_basis.alpha().numberOfOrbitals();
975
979
980 // ALPHA-ALPHA-ALPHA-ALPHA.
981 SpinUnresolvedONV spin_string_alpha_aaaa = onv_basis_alpha.constructONVFromAddress(0); // spin string with address 0
982 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) { // I_alpha loops over all the addresses of the alpha spin strings
983
984 for (size_t p = 0; p < K; p++) { // p loops over SOs
985 int sign_p = 1; // sign of the operator a_p
986
987 if (spin_string_alpha_aaaa.annihilate(p, sign_p)) { // if p is not in I_alpha
988
989 for (size_t r = 0; r < K; r++) { // r loops over SOs
990 int sign_pr = sign_p; // sign of the operator a_r a_p
991
992 if (spin_string_alpha_aaaa.annihilate(r, sign_pr)) { // if r is not in I_alpha
993
994 for (size_t s = 0; s < K; s++) { // s loops over SOs
995 int sign_prs = sign_pr; // sign of the operator a^dagger_s a_r a_p
996
997 if (spin_string_alpha_aaaa.create(s, sign_prs)) { // if s is in I_alpha
998
999 for (size_t q = 0; q < K; q++) { // q loops over SOs
1000 int sign_prsq = sign_prs; // sign of the operator a^dagger_q a^dagger_s a_r a_p
1001
1002 if (spin_string_alpha_aaaa.create(q, sign_prsq)) { // if q is not in I_alpha
1003 size_t J_alpha = onv_basis_alpha.addressOf(spin_string_alpha_aaaa); // address of the coupling string
1004
1005 double contribution = 0.0;
1006 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) {
1007 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta); // alpha addresses are 'major'
1008 double c_J_alpha_I_beta = this->coefficient(J_alpha * dim_beta + I_beta);
1009 contribution += c_I_alpha_I_beta * c_J_alpha_I_beta;
1010 }
1011
1012
1013 d_aaaa(p, q, r, s) += sign_prsq * contribution;
1014
1015 spin_string_alpha_aaaa.annihilate(q); // undo the previous creation
1016 }
1017 } // loop over q
1018
1019 spin_string_alpha_aaaa.annihilate(s); // undo the previous creation
1020 }
1021 } // loop over s
1022
1023 spin_string_alpha_aaaa.create(r); // undo the previous annihilation
1024 }
1025 } // loop over r
1026
1027 spin_string_alpha_aaaa.create(p); // undo the previous annihilation
1028 }
1029 } // loop over p
1030
1031 if (I_alpha < dim_alpha - 1) { // prevent the last permutation from occurring
1032 onv_basis_alpha.transformONVToNextPermutation(spin_string_alpha_aaaa);
1033 }
1034
1035 } // loop over I_alpha
1036
1037
1038 // ALPHA-ALPHA-BETA-BETA
1039 SpinUnresolvedONV spin_string_alpha_aabb = onv_basis_alpha.constructONVFromAddress(0); // spin string with address 0
1040 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) { // I_alpha loops over all the addresses of the alpha spin strings
1041
1042 for (size_t p = 0; p < K; p++) { // p loops over SOs
1043 int sign_p = 1; // sign of the operator a_p_alpha
1044
1045 if (spin_string_alpha_aabb.annihilate(p, sign_p)) { // if p is in I_alpha
1046
1047 for (size_t q = 0; q < K; q++) { // q loops over SOs
1048 int sign_pq = sign_p; // sign of the operator a^dagger_p_alpha a_p_alpha
1049
1050 if (spin_string_alpha_aabb.create(q, sign_pq)) { // if q is not in I_alpha
1051 size_t J_alpha = onv_basis_alpha.addressOf(spin_string_alpha_aabb); // the string that couples to I_alpha
1052
1053
1054 SpinUnresolvedONV spin_string_beta_aabb = onv_basis_beta.constructONVFromAddress(0); // spin string with address 0
1055 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) { // I_beta loops over all addresses of beta spin strings
1056
1057 for (size_t r = 0; r < K; r++) { // r loops over all SOs
1058 int sign_r = 1; // sign of the operator a_r_beta
1059
1060 if (spin_string_beta_aabb.annihilate(r, sign_r)) {
1061
1062 for (size_t s = 0; s < K; s++) { // s loops over all SOs
1063 int sign_rs = sign_r; // sign of the operator a^dagger_s_beta a_r_beta
1064
1065 if (spin_string_beta_aabb.create(s, sign_rs)) {
1066 size_t J_beta = onv_basis_beta.addressOf(spin_string_beta_aabb); // the string that couples to I_beta
1067
1068 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta); // alpha addresses are 'major'
1069 double c_J_alpha_J_beta = this->coefficient(J_alpha * dim_beta + J_beta);
1070 d_aabb(p, q, r, s) += sign_pq * sign_rs * c_I_alpha_I_beta * c_J_alpha_J_beta;
1071
1072 spin_string_beta_aabb.annihilate(s); // undo the previous creation
1073 }
1074 } // loop over s
1075
1076
1077 spin_string_beta_aabb.create(r); // undo the previous annihilation
1078 }
1079
1080 } // loop over r
1081
1082 if (I_beta < dim_beta - 1) { // prevent the last permutation from occurring
1083 onv_basis_beta.transformONVToNextPermutation(spin_string_beta_aabb);
1084 }
1085
1086 } // loop over beta addresses
1087
1088 spin_string_alpha_aabb.annihilate(q); // undo the previous creation
1089 }
1090 } // loop over q
1091
1092 spin_string_alpha_aabb.create(p); // undo the previous annihilation
1093 }
1094 } // loop over p
1095
1096 if (I_alpha < dim_alpha - 1) { // prevent the last permutation from occurring
1097 onv_basis_alpha.transformONVToNextPermutation(spin_string_alpha_aabb);
1098 }
1099
1100 } // loop over alpha addresses
1101
1102
1103 // BETA-BETA-ALPHA-ALPHA.
1104 // We know that d^aabb_pqrs = d^bbaa_rspq.
1105 Eigen::array<int, 4> axes {2, 3, 0, 1}; // array specifying the axes that should be swapped
1107
1108
1109 // BETA-BETA-BETA-BETA.
1110 SpinUnresolvedONV spin_string_beta_bbbb = onv_basis_beta.constructONVFromAddress(0); // spin string with address 0
1111 for (size_t I_beta = 0; I_beta < dim_beta; I_beta++) { // I_beta loops over all the addresses of the beta spin strings
1112
1113 for (size_t p = 0; p < K; p++) { // p loops over SOs
1114 int sign_p = 1; // sign of the operator a_p
1115
1116 if (spin_string_beta_bbbb.annihilate(p, sign_p)) { // if p is not in I_beta
1117
1118 for (size_t r = 0; r < K; r++) { // r loops over SOs
1119 int sign_pr = sign_p; // sign of the operator a_r a_p
1120
1121 if (spin_string_beta_bbbb.annihilate(r, sign_pr)) { // if r is not in I_beta
1122
1123 for (size_t s = 0; s < K; s++) { // s loops over SOs
1124 int sign_prs = sign_pr; // sign of the operator a^dagger_s a_r a_p
1125
1126 if (spin_string_beta_bbbb.create(s, sign_prs)) { // if s is in I_beta
1127
1128 for (size_t q = 0; q < K; q++) { // q loops over SOs
1129 int sign_prsq = sign_prs; // sign of the operator a^dagger_q a^dagger_s a_r a_p
1130
1131 if (spin_string_beta_bbbb.create(q, sign_prsq)) { // if q is not in I_beta
1132 size_t J_beta = onv_basis_beta.addressOf(spin_string_beta_bbbb); // address of the coupling string
1133
1134 double contribution = 0.0;
1135 for (size_t I_alpha = 0; I_alpha < dim_alpha; I_alpha++) {
1136 double c_I_alpha_I_beta = this->coefficient(I_alpha * dim_beta + I_beta); // alpha addresses are 'major'
1137 double c_I_alpha_J_beta = this->coefficient(I_alpha * dim_beta + J_beta);
1138 contribution += c_I_alpha_I_beta * c_I_alpha_J_beta;
1139 }
1140
1141
1142 d_bbbb(p, q, r, s) += sign_prsq * contribution;
1143
1144 spin_string_beta_bbbb.annihilate(q); // undo the previous creation
1145 }
1146 } // loop over q
1147
1148 spin_string_beta_bbbb.annihilate(s); // undo the previous creation
1149 }
1150 } // loop over s
1151
1152 spin_string_beta_bbbb.create(r); // undo the previous annihilation
1153 }
1154 } // loop over r
1155
1156 spin_string_beta_bbbb.create(p); // undo the previous annihilation
1157 }
1158 } // loop over p
1159
1160 if (I_beta < dim_beta - 1) { // prevent the last permutation from occurring
1161 onv_basis_beta.transformONVToNextPermutation(spin_string_beta_bbbb);
1162 }
1163
1164 } // loop over I_beta.
1165
1167 }
1168
1169
1175 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1176 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, Orbital1DM<double>> calculate1DM() const { return this->calculateSpinResolved1DM().orbitalDensity(); }
1177
1178
1184 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1185 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, Orbital2DM<double>> calculate2DM() const { return this->calculateSpinResolved2DM().orbitalDensity(); }
1186
1187
1188 /*
1189 * MARK: Density matrices for seniority-zero ONV bases
1190 */
1191
1197 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1198 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SeniorityZeroONVBasis>::value, Orbital1DM<double>> calculate1DM() const {
1199
1200 // Prepare some variables.
1201 const auto K = this->onv_basis.numberOfSpatialOrbitals();
1202 const auto dimension = this->onv_basis.dimension();
1204
1205 // Create the first ONV (with address 0). In DOCI, the ONV basis for alpha and beta is equal, so we can use the proxy ONV basis.
1206 const auto onv_basis_proxy = this->onv_basis.proxy();
1207 SpinUnresolvedONV onv = onv_basis_proxy.constructONVFromAddress(0);
1208 for (size_t I = 0; I < dimension; I++) { // I loops over all the addresses of the doubly-occupied ONVs
1209
1210 for (size_t e1 = 0; e1 < onv_basis_proxy.numberOfElectrons(); e1++) { // e1 (electron 1) loops over the number of electrons
1211 const size_t p = onv.occupationIndexOf(e1); // retrieve the index of the orbital the electron occupies
1212 const double c_I = this->coefficient(I); // coefficient of the I-th basis vector
1213
1214 D(p, p) += 2 * std::pow(c_I, 2);
1215 }
1216
1217 if (I < dimension - 1) { // prevent the last permutation from occurring
1218 onv_basis_proxy.transformONVToNextPermutation(onv);
1219 }
1220 }
1221
1222 return Orbital1DM<double> {D};
1223 }
1224
1225
1231 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1232 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SeniorityZeroONVBasis>::value, Orbital2DM<double>> calculate2DM() const { return this->calculateSpinResolved2DM().orbitalDensity(); }
1233
1239 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1241
1242
1248 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1249 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SeniorityZeroONVBasis>::value, SpinResolved2DM<double>> calculateSpinResolved2DM() const {
1250
1251 // Prepare some variables.
1252 const auto K = this->onv_basis.numberOfSpatialOrbitals();
1253 const auto dimension = this->onv_basis.dimension();
1254
1255
1256 // For seniority-zero linear expansions, we only have to calculate d_aaaa and d_aabb.
1259
1260
1261 // Create the first ONV (with address 0). In DOCI, the ONV basis for alpha and beta is equal, so we can use the proxy ONV basis.
1262 const auto onv_basis_proxy = this->onv_basis.proxy();
1263 SpinUnresolvedONV onv = onv_basis_proxy.constructONVFromAddress(0);
1264 for (size_t I = 0; I < dimension; I++) { // I loops over all the addresses of the spin strings
1265 for (size_t p = 0; p < K; p++) { // p loops over SOs
1266 if (onv.annihilate(p)) { // if p is occupied in I
1267
1268 const double c_I = this->coefficient(I); // coefficient of the I-th basis vector
1269 const double c_I_2 = std::pow(c_I, 2); // square of c_I
1270
1271 d_aabb(p, p, p, p) += c_I_2;
1272
1273 for (size_t q = 0; q < p; q++) { // q loops over SOs with an index smaller than p
1274 if (onv.create(q)) { // if q is not occupied in I
1275 const size_t J = onv_basis_proxy.addressOf(onv); // the address of the coupling string
1276 const double c_J = this->coefficient(J); // coefficient of the J-th basis vector
1277
1278 d_aabb(p, q, p, q) += c_I * c_J;
1279 d_aabb(q, p, q, p) += c_I * c_J; // since we're looping for q < p
1280
1281 onv.annihilate(q); // reset the spin string after previous creation on q
1282 }
1283
1284 else { // if q is occupied in I
1285 d_aaaa(p, p, q, q) += c_I_2;
1286 d_aaaa(q, q, p, p) += c_I_2; // since we're looping for q < p
1287
1288 d_aaaa(p, q, q, p) -= c_I_2;
1289 d_aaaa(q, p, p, q) -= c_I_2; // since we're looping for q < p
1290
1291 d_aabb(p, p, q, q) += c_I_2;
1292 d_aabb(q, q, p, p) += c_I_2; // since we're looping for q < p
1293 }
1294 }
1295 onv.create(p); // reset the spin string after previous annihilation on p
1296 }
1297 }
1298
1299 if (I < dimension - 1) { // prevent the last permutation from occurring
1300 onv_basis_proxy.transformONVToNextPermutation(onv);
1301 }
1302 }
1303
1304 // For seniority-zero linear expansions, we have additional symmetries (two_rdm_aaaa = two_rdm_bbbb, two_rdm_aabb = two_rdm_bbaa).
1306 }
1307
1308
1309 /*
1310 * MARK: Density matrices for spin-resolved selected ONV bases
1311 */
1312
1318 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1319 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedSelectedONVBasis>::value, SpinResolved1DM<double>> calculateSpinResolved1DM() const {
1320
1321 size_t K = this->onv_basis.numberOfOrbitals();
1322 size_t dim = onv_basis.dimension();
1323
1326
1327
1328 for (size_t I = 0; I < dim; I++) { // Loop over all addresses (1).
1329 SpinResolvedONV configuration_I = this->onv_basis.onvWithIndex(I);
1330 SpinUnresolvedONV alpha_I = configuration_I.onv(Spin::alpha);
1331 SpinUnresolvedONV beta_I = configuration_I.onv(Spin::beta);
1332
1333 double c_I = this->coefficient(I);
1334
1335
1336 // Calculate the diagonal of the 1-DMs
1337 for (size_t p = 0; p < K; p++) {
1338
1339 if (alpha_I.isOccupied(p)) {
1340 D_aa(p, p) += std::pow(c_I, 2);
1341 }
1342
1343 if (beta_I.isOccupied(p)) {
1344 D_bb(p, p) += std::pow(c_I, 2);
1345 }
1346 }
1347
1348
1349 // Calculate the off-diagonal elements, by going over all other ONVs
1350 for (size_t J = I + 1; J < dim; J++) {
1351
1352 SpinResolvedONV configuration_J = this->onv_basis.onvWithIndex(J);
1353 SpinUnresolvedONV alpha_J = configuration_J.onv(Spin::alpha);
1354 SpinUnresolvedONV beta_J = configuration_J.onv(Spin::beta);
1355
1356 double c_J = this->coefficient(J);
1357
1358
1359 // 1 electron excitation in alpha (i.e. 2 differences), 0 in beta
1360 if ((alpha_I.countNumberOfExcitations(alpha_J) == 1) && (beta_I.countNumberOfExcitations(beta_J) == 0)) {
1361
1362 // Find the orbitals that are occupied in one string, and aren't in the other
1363 size_t p = alpha_I.findDifferentOccupations(alpha_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1364 size_t q = alpha_J.findDifferentOccupations(alpha_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1365
1366 // Calculate the total sign, and include it in the DM contribution
1367 int sign = alpha_I.operatorPhaseFactor(p) * alpha_J.operatorPhaseFactor(q);
1368 D_aa(p, q) += sign * c_I * c_J;
1369 D_aa(q, p) += sign * c_I * c_J;
1370 }
1371
1372
1373 // 1 electron excitation in beta, 0 in alpha
1374 if ((alpha_I.countNumberOfExcitations(alpha_J) == 0) && (beta_I.countNumberOfExcitations(beta_J) == 1)) {
1375
1376 // Find the orbitals that are occupied in one string, and aren't in the other
1377 size_t p = beta_I.findDifferentOccupations(beta_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1378 size_t q = beta_J.findDifferentOccupations(beta_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1379
1380 // Calculate the total sign, and include it in the DM contribution
1381 int sign = beta_I.operatorPhaseFactor(p) * beta_J.operatorPhaseFactor(q);
1382 D_bb(p, q) += sign * c_I * c_J;
1383 D_bb(q, p) += sign * c_I * c_J;
1384 }
1385
1386 } // Loop over addresses J > I.
1387 } // Loop over addresses I.
1388
1390 }
1391
1392
1398 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1399 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedSelectedONVBasis>::value, SpinResolved2DM<double>> calculateSpinResolved2DM() const {
1400
1401 size_t K = this->onv_basis.numberOfOrbitals();
1402 size_t dim = onv_basis.dimension();
1403
1408
1409 for (size_t I = 0; I < dim; I++) { // Loop over all addresses I.
1410
1411 SpinResolvedONV configuration_I = this->onv_basis.onvWithIndex(I);
1412 SpinUnresolvedONV alpha_I = configuration_I.onv(Spin::alpha);
1413 SpinUnresolvedONV beta_I = configuration_I.onv(Spin::beta);
1414
1415 double c_I = this->coefficient(I);
1416
1417 for (size_t p = 0; p < K; p++) {
1418
1419 // 'Diagonal' elements of the 2-DM: aaaa and aabb
1420 if (alpha_I.isOccupied(p)) {
1421 for (size_t q = 0; q < K; q++) {
1422 if (beta_I.isOccupied(q)) {
1423 d_aabb(p, p, q, q) += std::pow(c_I, 2);
1424 }
1425
1426 if (p != q) { // can't create/annihilate the same orbital twice
1427 if (alpha_I.isOccupied(q)) {
1428 d_aaaa(p, p, q, q) += std::pow(c_I, 2);
1429 d_aaaa(p, q, q, p) -= std::pow(c_I, 2);
1430 }
1431 }
1432
1433 } // loop over q
1434 }
1435
1436 // 'Diagonal' elements of the 2-DM: bbbb and bbaa
1437 if (beta_I.isOccupied(p)) {
1438 for (size_t q = 0; q < K; q++) {
1439 if (alpha_I.isOccupied(q)) {
1440 d_bbaa(p, p, q, q) += std::pow(c_I, 2);
1441 }
1442
1443 if (p != q) { // can't create/annihilate the same orbital twice
1444 if (beta_I.isOccupied(q)) {
1445 d_bbbb(p, p, q, q) += std::pow(c_I, 2);
1446 d_bbbb(p, q, q, p) -= std::pow(c_I, 2);
1447 }
1448 }
1449 } // loop over q
1450 }
1451 } // loop over q
1452
1453
1454 for (size_t J = I + 1; J < dim; J++) {
1455
1456 SpinResolvedONV configuration_J = this->onv_basis.onvWithIndex(J);
1457 SpinUnresolvedONV alpha_J = configuration_J.onv(Spin::alpha);
1458 SpinUnresolvedONV beta_J = configuration_J.onv(Spin::beta);
1459
1460 double c_J = this->coefficient(J);
1461
1462 // 1 electron excitation in alpha, 0 in beta
1463 if ((alpha_I.countNumberOfExcitations(alpha_J) == 1) && (beta_I.countNumberOfExcitations(beta_J) == 0)) {
1464
1465 // Find the orbitals that are occupied in one string, and aren't in the other
1466 size_t p = alpha_I.findDifferentOccupations(alpha_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1467 size_t q = alpha_J.findDifferentOccupations(alpha_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1468
1469 // Calculate the total sign
1470 int sign = alpha_I.operatorPhaseFactor(p) * alpha_J.operatorPhaseFactor(q);
1471
1472
1473 for (size_t r = 0; r < K; r++) { // r loops over spatial orbitals
1474
1475 if (alpha_I.isOccupied(r) && alpha_J.isOccupied(r)) { // r must be occupied on the left and on the right
1476 if ((p != r) && (q != r)) { // can't create or annihilate the same orbital
1477 // Fill in the 2-DM contributions
1478 d_aaaa(p, q, r, r) += sign * c_I * c_J;
1479 d_aaaa(r, q, p, r) -= sign * c_I * c_J;
1480 d_aaaa(p, r, r, q) -= sign * c_I * c_J;
1481 d_aaaa(r, r, p, q) += sign * c_I * c_J;
1482
1483 d_aaaa(q, p, r, r) += sign * c_I * c_J;
1484 d_aaaa(q, r, r, p) -= sign * c_I * c_J;
1485 d_aaaa(r, p, q, r) -= sign * c_I * c_J;
1486 d_aaaa(r, r, q, p) += sign * c_I * c_J;
1487 }
1488 }
1489
1490 if (beta_I.isOccupied(r)) { // beta_I == beta_J from the previous if-branch
1491
1492 // Fill in the 2-DM contributions
1493 d_aabb(p, q, r, r) += sign * c_I * c_J;
1494 d_aabb(q, p, r, r) += sign * c_I * c_J;
1495
1496 d_bbaa(r, r, p, q) += sign * c_I * c_J;
1497 d_bbaa(r, r, q, p) += sign * c_I * c_J;
1498 }
1499 }
1500 }
1501
1502
1503 // 0 electron excitations in alpha, 1 in beta
1504 if ((alpha_I.countNumberOfExcitations(alpha_J) == 0) && (beta_I.countNumberOfExcitations(beta_J) == 1)) {
1505
1506 // Find the orbitals that are occupied in one string, and aren't in the other
1507 size_t p = beta_I.findDifferentOccupations(beta_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1508 size_t q = beta_J.findDifferentOccupations(beta_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1509
1510 // Calculate the total sign
1511 int sign = beta_I.operatorPhaseFactor(p) * beta_J.operatorPhaseFactor(q);
1512
1513
1514 for (size_t r = 0; r < K; r++) { // r loops over spatial orbitals
1515
1516 if (beta_I.isOccupied(r) && beta_J.isOccupied(r)) { // r must be occupied on the left and on the right
1517 if ((p != r) && (q != r)) { // can't create or annihilate the same orbital
1518 // Fill in the 2-DM contributions
1519 d_bbbb(p, q, r, r) += sign * c_I * c_J;
1520 d_bbbb(r, q, p, r) -= sign * c_I * c_J;
1521 d_bbbb(p, r, r, q) -= sign * c_I * c_J;
1522 d_bbbb(r, r, p, q) += sign * c_I * c_J;
1523
1524 d_bbbb(q, p, r, r) += sign * c_I * c_J;
1525 d_bbbb(q, r, r, p) -= sign * c_I * c_J;
1526 d_bbbb(r, p, q, r) -= sign * c_I * c_J;
1527 d_bbbb(r, r, q, p) += sign * c_I * c_J;
1528 }
1529 }
1530
1531 if (alpha_I.isOccupied(r)) { // alpha_I == alpha_J from the previous if-branch
1532
1533 // Fill in the 2-DM contributions
1534 d_bbaa(p, q, r, r) += sign * c_I * c_J;
1535 d_bbaa(q, p, r, r) += sign * c_I * c_J;
1536
1537 d_aabb(r, r, p, q) += sign * c_I * c_J;
1538 d_aabb(r, r, q, p) += sign * c_I * c_J;
1539 }
1540 }
1541 }
1542
1543
1544 // 1 electron excitation in alpha, 1 in beta
1545 if ((alpha_I.countNumberOfExcitations(alpha_J) == 1) && (beta_I.countNumberOfExcitations(beta_J) == 1)) {
1546
1547 // Find the orbitals that are occupied in one string, and aren't in the other
1548 size_t p = alpha_I.findDifferentOccupations(alpha_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1549 size_t q = alpha_J.findDifferentOccupations(alpha_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1550
1551 size_t r = beta_I.findDifferentOccupations(beta_J)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1552 size_t s = beta_J.findDifferentOccupations(beta_I)[0]; // we're sure that there is only 1 element in the std::vector<size_t>
1553
1554 // Calculate the total sign, and include it in the 2-DM contribution
1555 int sign = alpha_I.operatorPhaseFactor(p) * alpha_J.operatorPhaseFactor(q) * beta_I.operatorPhaseFactor(r) * beta_J.operatorPhaseFactor(s);
1556 d_aabb(p, q, r, s) += sign * c_I * c_J;
1557 d_aabb(q, p, s, r) += sign * c_I * c_J;
1558
1559 d_bbaa(r, s, p, q) += sign * c_I * c_J;
1560 d_bbaa(s, r, q, p) += sign * c_I * c_J;
1561 }
1562
1563
1564 // 2 electron excitations in alpha, 0 in beta
1565 if ((alpha_I.countNumberOfExcitations(alpha_J) == 2) && (beta_I.countNumberOfExcitations(beta_J) == 0)) {
1566
1567 // Find the orbitals that are occupied in one string, and aren't in the other
1568 std::vector<size_t> occupied_indices_I = alpha_I.findDifferentOccupations(alpha_J); // we're sure this has two elements
1569 size_t p = occupied_indices_I[0];
1570 size_t r = occupied_indices_I[1];
1571
1572 std::vector<size_t> occupied_indices_J = alpha_J.findDifferentOccupations(alpha_I); // we're sure this has two elements
1573 size_t q = occupied_indices_J[0];
1574 size_t s = occupied_indices_J[1];
1575
1576
1577 // Calculate the total sign, and include it in the 2-DM contribution
1578 int sign = alpha_I.operatorPhaseFactor(p) * alpha_I.operatorPhaseFactor(r) * alpha_J.operatorPhaseFactor(q) * alpha_J.operatorPhaseFactor(s);
1579 d_aaaa(p, q, r, s) += sign * c_I * c_J;
1580 d_aaaa(p, s, r, q) -= sign * c_I * c_J;
1581 d_aaaa(r, q, p, s) -= sign * c_I * c_J;
1582 d_aaaa(r, s, p, q) += sign * c_I * c_J;
1583
1584 d_aaaa(q, p, s, r) += sign * c_I * c_J;
1585 d_aaaa(s, p, q, r) -= sign * c_I * c_J;
1586 d_aaaa(q, r, s, p) -= sign * c_I * c_J;
1587 d_aaaa(s, r, q, p) += sign * c_I * c_J;
1588 }
1589
1590
1591 // 0 electron excitations in alpha, 2 in beta
1592 if ((alpha_I.countNumberOfExcitations(alpha_J) == 0) && (beta_I.countNumberOfExcitations(beta_J) == 2)) {
1593
1594 // Find the orbitals that are occupied in one string, and aren't in the other
1595 std::vector<size_t> occupied_indices_I = beta_I.findDifferentOccupations(beta_J); // we're sure this has two elements
1596 size_t p = occupied_indices_I[0];
1597 size_t r = occupied_indices_I[1];
1598
1599 std::vector<size_t> occupied_indices_J = beta_J.findDifferentOccupations(beta_I); // we're sure this has two elements
1600 size_t q = occupied_indices_J[0];
1601 size_t s = occupied_indices_J[1];
1602
1603
1604 // Calculate the total sign, and include it in the 2-DM contribution
1605 int sign = beta_I.operatorPhaseFactor(p) * beta_I.operatorPhaseFactor(r) * beta_J.operatorPhaseFactor(q) * beta_J.operatorPhaseFactor(s);
1606 d_bbbb(p, q, r, s) += sign * c_I * c_J;
1607 d_bbbb(p, s, r, q) -= sign * c_I * c_J;
1608 d_bbbb(r, q, p, s) -= sign * c_I * c_J;
1609 d_bbbb(r, s, p, q) += sign * c_I * c_J;
1610
1611 d_bbbb(q, p, s, r) += sign * c_I * c_J;
1612 d_bbbb(s, p, q, r) -= sign * c_I * c_J;
1613 d_bbbb(q, r, s, p) -= sign * c_I * c_J;
1614 d_bbbb(s, r, q, p) += sign * c_I * c_J;
1615 }
1616
1617 } // loop over all addresses J > I
1618
1619 } // Loop over all addresses I.
1620
1622 }
1623
1624
1630 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1631 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedSelectedONVBasis>::value, Orbital1DM<double>> calculate1DM() const { return this->calculateSpinResolved1DM().orbitalDensity(); }
1632
1633
1639 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1640 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedSelectedONVBasis>::value, Orbital2DM<double>> calculate2DM() const { return this->calculateSpinResolved2DM().orbitalDensity(); }
1641
1642
1648 template <typename Z = ONVBasis>
1650
1651 // Prepare some variables.
1652 const auto M = this->onv_basis.numberOfOrbitals();
1653 const auto dim = this->onv_basis.dimension();
1654
1656
1657 for (size_t I = 0; I < dim; I++) { // Loop over all bra addresses I.
1658 const auto& onv_I = this->onv_basis.onvWithIndex(I);
1659 const auto& c_I = this->coefficient(I);
1660
1661 // Calculate the diagonal elements of the 1-DM.
1662 const auto& occupied_indices_I = onv_I.occupiedIndices();
1663 for (const auto& p : occupied_indices_I) {
1664 D(p, p) += GQCP::conj(c_I) * c_I;
1665 }
1666
1667 for (size_t J = I + 1; J < dim; J++) { // Loop over all other (J != I) ket addresses.
1668
1669 const auto& onv_J = this->onv_basis.onvWithIndex(J);
1670 const auto& c_J = this->coefficient(J);
1671
1672 // We only have a contribution if I and J are exactly 1 excitation away.
1673 if (onv_I.countNumberOfExcitations(onv_J) == 1) {
1674 const auto p = onv_I.findDifferentOccupations(onv_J)[0]; // The orbital that is occupied in I, but unoccupied in J.
1675 const auto q = onv_J.findDifferentOccupations(onv_I)[0]; // The orbital that is occupied in J, but unoccupied in I.
1676
1677 const auto sign = static_cast<double>(onv_I.operatorPhaseFactor(p) * onv_J.operatorPhaseFactor(q));
1678 const auto value = sign * GQCP::conj(c_I) * c_J;
1679
1680 D(p, q) += value;
1681 D(q, p) += GQCP::conj(value);
1682 }
1683
1684 } // Loop over ket addresses J > I.
1685 } // Loop over bra addresses I.
1686
1687 return G1DM<Scalar> {D};
1688 }
1689
1690
1696 template <typename Z = ONVBasis>
1698
1699 // Prepare some variables.
1700 const auto M = this->onv_basis.numberOfOrbitals();
1701 const auto dim = this->onv_basis.dimension();
1702
1704
1705
1706 for (size_t I = 0; I < dim; I++) { // Loop over all bra addresses I.
1707 const auto& onv_I = this->onv_basis.onvWithIndex(I);
1708 const auto& c_I = this->coefficient(I);
1709
1710 const auto& occupied_indices_I = onv_I.occupiedIndices();
1711 for (const auto& p : occupied_indices_I) {
1712 for (const auto& q : occupied_indices_I) {
1713 if (p != q) { // We can't annihilate on the same orbital twice.
1714 const auto value = GQCP::conj(c_I) * c_I;
1715 d(p, p, q, q) += value;
1716 d(p, q, q, p) -= value;
1717 }
1718 }
1719 }
1720
1721
1722 for (size_t J = I + 1; J < dim; J++) { // Loop over all different (J != I) ket indices J.
1723 const auto& onv_J = this->onv_basis.onvWithIndex(J);
1724 const auto& c_J = this->coefficient(J);
1725
1726 // Calculate the contribution if I and J are 1 excitation away.
1727 if (onv_I.countNumberOfExcitations(onv_J) == 1) {
1728
1729 // Determine the orbital indices that match the excitation.
1730 const auto p = onv_I.findDifferentOccupations(onv_J)[0]; // The orbital that is occupied in I, but unoccupied in J.
1731 const auto q = onv_J.findDifferentOccupations(onv_I)[0]; // The orbital that is occupied in J, but unoccupied in I.
1732
1733 // Add the contribution from this excitation to all appropriate density matrix elements.
1734 const auto sign = static_cast<double>(onv_I.operatorPhaseFactor(p) * onv_J.operatorPhaseFactor(q));
1735
1736 for (size_t r = 0; r < M; r++) {
1737 if (onv_I.isOccupied(r) && onv_J.isOccupied(r)) { // `r` must be occupied in the bra and in the ket.
1738 if ((p != r) && (q != r)) { // We can't annihilate on the same orbital twice.
1739 const auto value = sign * GQCP::conj(c_I) * c_J;
1740
1741 d(p, q, r, r) += value;
1742 d(p, r, r, q) -= value;
1743 d(r, r, p, q) += value;
1744 d(r, q, p, r) -= value;
1745
1746 d(q, p, r, r) += GQCP::conj(value);
1747 d(r, p, q, r) -= GQCP::conj(value);
1748 d(r, r, q, p) += GQCP::conj(value);
1749 d(q, r, r, p) -= GQCP::conj(value);
1750 }
1751 }
1752 }
1753 }
1754
1755 // Calculate the contribution if I and J are 2 excitations away.
1756 else if (onv_I.countNumberOfExcitations(onv_J) == 2) {
1757
1758 // Determine the orbital indices that match the excitation.
1759 auto occupied_in_I = onv_I.findDifferentOccupations(onv_J); // The orbitals that are occupied in J, but not in J.
1760 const auto p = occupied_in_I[0];
1761 const auto r = occupied_in_I[1];
1762
1763 auto occupied_in_J = onv_J.findDifferentOccupations(onv_I); // The orbitals that are occupied in I, but not in J.
1764 const auto q = occupied_in_J[0];
1765 const auto s = occupied_in_J[1];
1766
1767
1768 // Add the contribution from this excitation to all appropriate density matrix elements.
1769 const auto sign = static_cast<double>(onv_I.operatorPhaseFactor(p) * onv_I.operatorPhaseFactor(r) * onv_J.operatorPhaseFactor(q) * onv_J.operatorPhaseFactor(s));
1770 const auto value = sign * GQCP::conj(c_I) * c_J;
1771
1772 d(p, q, r, s) += value;
1773 d(p, s, r, q) -= value;
1774 d(r, s, p, q) += value;
1775 d(r, q, p, s) -= value;
1776
1777 d(q, p, s, r) += GQCP::conj(value);
1778 d(s, p, q, r) -= GQCP::conj(value);
1779 d(s, r, q, p) += GQCP::conj(value);
1780 d(q, r, s, p) -= GQCP::conj(value);
1781 }
1782 } // Loop over all ket addresses J > I
1783 } // Loop over all bra addresses I.
1784
1785 return G2DM<Scalar> {d};
1786 }
1787
1788
1801 template <typename Z = ONVBasis>
1802 enable_if_t<std::is_same<Z, SpinUnresolvedONVBasis>::value | std::is_same<Z, SpinResolvedONVBasis>::value, GQCP::Tensor<Scalar, 2>> tensorizeCoefficients(const std::vector<typename ONVBasis::ONV>& system_onvs, const std::vector<typename ONVBasis::ONV>& environment_onvs) const {
1803
1804 if (system_onvs.size() != environment_onvs.size()) {
1805 throw std::invalid_argument("LinearExpansion::tensorizeCoefficients(std::vector<ONV>& system_onvs, std::vector<ONV>& environment_onvs) const: The amount of system ONVs should be exactly the same as the amount of environment ONVs.");
1806 }
1807
1808 // Create a collection of unique ONVs to determine the dimension of the system orbital RDM.
1809 const auto unique_onvs = [](const std::vector<typename ONVBasis::ONV>& onvs) {
1810 // The ONV basis containing all unique ONVs.
1811 std::vector<typename ONVBasis::ONV> onv_collection;
1812 for (const auto& onv : onvs) {
1813 bool unique = true;
1814 // If the ONV already is inside this collection, do not add it again.
1815 for (const auto& unique_onv : onv_collection) {
1816 if (onv.asString() == unique_onv.asString()) {
1817 unique = false;
1818 }
1819 }
1820 // If it is an unique ONV, add it to the ONV collection.
1821 if (unique) {
1822 onv_collection.push_back(onv);
1823 }
1824 }
1825 return onv_collection;
1826 };
1827
1828 const auto system_onv_collection = unique_onvs(system_onvs);
1829 const auto environment_onv_collection = unique_onvs(environment_onvs);
1830
1831 // Retrieve the index of a given ONV in the collection of unique ONVs.
1832 const auto onv_index = [](const typename ONVBasis::ONV onv, std::vector<typename ONVBasis::ONV> onv_collection) {
1833 for (int i = 0; i < onv_collection.size(); ++i) {
1834 if (onv == onv_collection[i]) {
1835 return i;
1836 }
1837 }
1838 return -1;
1839 };
1840
1841 GQCP::Tensor<Scalar, 2> C(system_onv_collection.size(), environment_onv_collection.size());
1842 C.setZero();
1843
1844 for (size_t ij = 0; ij < system_onvs.size(); ++ij) {
1845 int i = onv_index(system_onvs[ij], system_onv_collection);
1846 int j = onv_index(environment_onvs[ij], environment_onv_collection);
1847 C(i, j) = this->coefficient(ij);
1848 }
1849 return C;
1850 }
1851
1852
1860 template <typename Z = ONVBasis>
1862
1863 // The expansion coefficients of this linear expansion must be adjusted according to fermionic anti-commutation rules when we partition the ONVs according to the domain partition.
1864 VectorX<Scalar> adjusted_coefficients = VectorX<Scalar>::Zero(this->onvBasis().dimension()); // The adjusted expansion coefficients.
1865 std::vector<std::vector<SpinResolvedONV>> onvs(domain_partition.dimension());
1866
1867 const auto partition_onv = [&](const SpinUnresolvedONV& onv_alpha, size_t I_alpha, const SpinUnresolvedONV& onv_beta, size_t I_beta) {
1868 const auto onv = GQCP::SpinResolvedONV(onv_alpha, onv_beta);
1869 const auto I_ab = this->onvBasis().compoundAddress(I_alpha, I_beta);
1870
1871 ONVPartition<SpinResolvedONV> onv_partition {domain_partition, onv};
1872 for (size_t i = 0; i < onv_partition.dimension(); ++i) {
1873 onvs[i].push_back(onv_partition(i));
1874 }
1875 adjusted_coefficients(I_ab) = this->coefficient(I_ab) * onv_partition.phaseFactor();
1876 };
1877
1878 this->onvBasis().forEach(partition_onv);
1879 LinearExpansion<Scalar, SpinResolvedONVBasis> adjusted_wfn(this->onvBasis(), adjusted_coefficients);
1880
1881 // The coefficients must be in the tensorized form to apply equation (2).
1882 const auto C = adjusted_wfn.tensorizeCoefficients(onvs[0], onvs[1]);
1883
1884 // Partial trace over the index of the environment ("j").
1885 return C.template einsum<1>("ij,kj->ik", C);
1886 }
1887
1888
1896 template <typename Z = ONVBasis>
1898
1899 // The expansion coefficients of this linear expansion must be adjusted according to fermionic anti-commutation rules when we partition the ONVs according to the domain partition.
1900 VectorX<Scalar> adjusted_coefficients = VectorX<Scalar>::Zero(this->onvBasis().dimension()); // The adjusted expansion coefficients.
1901 std::vector<std::vector<SpinUnresolvedONV>> onvs(domain_partition.dimension());
1902
1903 const auto partition_onv = [&](const SpinUnresolvedONV& onv, size_t I) {
1904 ONVPartition<SpinUnresolvedONV> onv_partition {domain_partition, onv};
1905 for (size_t i = 0; i < onv_partition.dimension(); ++i) {
1906 onvs[i].push_back(onv_partition(i));
1907 }
1908 adjusted_coefficients(I) = this->coefficient(I) * onv_partition.phaseFactor();
1909 };
1910
1911 this->onvBasis().forEach(partition_onv);
1912 LinearExpansion<Scalar, SpinUnresolvedONVBasis> adjusted_wfn(this->onvBasis(), adjusted_coefficients);
1913
1914 // The coefficients must be in the tensorized form to apply equation (2).
1915 const auto C = adjusted_wfn.tensorizeCoefficients(onvs[0], onvs[1]);
1916
1917 std::cout << "system onvs: " << std::endl;
1918 for (size_t i = 0; i < this->onvBasis().dimension(); i++) {
1919 std::cout << onvs[0][i] << "\t";
1920 }
1921 std::cout << std::endl;
1922 std::cout << "environment onvs: " << std::endl;
1923 for (size_t i = 0; i < this->onvBasis().dimension(); i++) {
1924 std::cout << onvs[1][i] << "\t";
1925 }
1926 std::cout << std::endl;
1927
1928 // Partial trace over the index of the environment ("j").
1929 return C.template einsum<1>("ij,kj->ik", C);
1930 }
1931
1932
1936 template <typename Z = Scalar>
1938
1939 // Sum over the ONV basis dimension, and only include the term if |c_k|^2 != 0. This avoids any NaN errors.
1940 // We might as well replace all coefficients that are 0 by 1, since log(1) = 0, which would have no influence on the final entropy value.
1941 Eigen::ArrayXd c2_not_replaced = this->coefficients().array().square();
1942 Eigen::ArrayXd c2 = c2_not_replaced.unaryExpr(
1943 [](const double c) {
1944 return c < 1.0e-18 ? 1 : c; // Replace zeroes by ones.
1945 });
1946
1947 return -1 / std::log(2) * (c2 * c2.log()).sum(); // Apply the log prefactor to change to log2.
1948 }
1949
1950
1960 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
1961 enable_if_t<std::is_same<Z1, double>::value && std::is_same<Z2, SpinResolvedONVBasis>::value, double> calculateSingleOrbitalEntropy(const size_t orbital_index) const {
1962
1963 // Check whether the given orbital index is smaller than or equal to the number of orbitals present in the system.
1964 if (orbital_index > this->onv_basis.numberOfOrbitals()) {
1965 throw std::invalid_argument("LinearExpansion::calculateSingleOrbitalEntropy(const size_t): The given orbital index is larger than the amount of orbitals in the system.");
1966 }
1967
1968 // In order to calculate the single orbital entropy, we need the spin resolved one- and two-particle density matrices.
1969 const auto D = this->calculateSpinResolved1DM();
1970 const auto d = this->calculateSpinResolved2DM();
1971
1972 // Extract the necessary elements from these density matrices.
1973 const auto& gamma_i_alpha = D.alpha().matrix()(orbital_index, orbital_index);
1974 const auto& gamma_i_beta = D.beta().matrix()(orbital_index, orbital_index);
1975 const auto& Gamma_ii_alpha_beta = d.alphaBeta().tensor()(orbital_index, orbital_index, orbital_index, orbital_index);
1976
1977 // Zero-initiate the one-orbital density matrix.
1979
1980 // Fill in the diagonal elements of the one-orbital density matrix.
1981 rho(0, 0) += 1 - gamma_i_alpha - gamma_i_beta + Gamma_ii_alpha_beta;
1982 rho(1, 1) += gamma_i_alpha - Gamma_ii_alpha_beta;
1983 rho(2, 2) += gamma_i_beta - Gamma_ii_alpha_beta;
1984 rho(3, 3) += Gamma_ii_alpha_beta;
1985
1986 // To calculate the one-orbital entropy, we need the eigenvalues of the one-orbital RDM. But, since the one-orbital RDM is already diagonal, we can calculate the one-orbital entropy from those diagonal elements.
1987 double S = 0;
1988
1989 for (int i = 0; i < 4; i++) {
1990 if (abs(rho(i, i)) > 1e-12) { // The diagonal element must be non-zero in order to contribute to the entropy.
1991 S -= rho(i, i) * log(rho(i, i));
1992 }
1993 }
1994
1995 // Return the single orbital entropy.
1996 return S;
1997 }
1998
1999
2009 template <typename Z1 = Scalar, typename Z2 = ONVBasis>
2010 enable_if_t<std::is_same<Z1, Scalar>::value && std::is_same<Z2, SeniorityZeroONVBasis>::value, double> calculateSingleOrbitalEntropy(const size_t orbital_index) const {
2011
2012 // Check whether the given orbital index is smaller than or equal to the number of orbitals present in the system.
2013 if (orbital_index > this->onv_basis.numberOfSpatialOrbitals()) {
2014 throw std::invalid_argument("LinearExpansion::calculateSingleOrbitalEntropy(const size_t): The given orbital index is larger than the amount of orbitals in the system.");
2015 }
2016
2017 // In order to calculate the single orbital entropy, we need the spin resolved one-particle density matrix.
2018 const auto D = this->calculateSpinResolved1DM();
2019
2020 // Extract the necessary elements from these density matrices.
2021 const auto& gamma_i_alpha = D.alpha().matrix()(orbital_index, orbital_index);
2022
2023 // Zero-initiate the one-orbital density matrix. Due to the absence of singly occupied orbitals, it reduces to a two-by-two matrix.
2025
2026 // Fill in the diagonal elements of the one-orbital density matrix.
2027 rho(0, 0) += 1 - gamma_i_alpha;
2028 rho(1, 1) += gamma_i_alpha;
2029
2030
2031 // To calculate the one-orbital entropy, we need the eigenvalues of the one-orbital RDM. But, since the one-orbital RDM is already diagonal, we can calculate the one-orbital entropy from those diagonal elements.
2032 double S = 0;
2033
2034 for (int i = 0; i < 2; i++) {
2035 if (abs(rho(i, i)) > 1e-12) { // The diagonal element must be non-zero in order to contribute to the entropy.
2036 S -= rho(i, i) * log(rho(i, i));
2037 }
2038 }
2039
2040 // Return the single orbital entropy.
2041 return S;
2042 }
2043
2044
2057 template <typename ElectronPartition, typename Z = ONVBasis, std::enable_if_t<std::is_same<Z, SpinResolvedONVBasis>::value, bool> = true>
2058 double calculateProbabilityOfFindingElectronPartition(const DiscreteDomainPartition& domain_partition, const ElectronPartition& electron_partition) const {
2059
2060 double domain_probability = 0.0;
2061 const auto calculate_domain_probability = [&](const SpinUnresolvedONV& onv_alpha, size_t I_alpha, const SpinUnresolvedONV& onv_beta, size_t I_beta) mutable {
2062 const SpinResolvedONV onv(onv_alpha, onv_beta);
2063 if (domain_partition.overlapWithONV(onv) == electron_partition) {
2064 const size_t I_ab = this->onvBasis().compoundAddress(I_alpha, I_beta);
2065 domain_probability += std::pow(this->coefficient(I_ab), 2);
2066 }
2067 };
2068 onv_basis.forEach(calculate_domain_probability);
2069 return domain_probability;
2070 }
2071
2080 template <typename ElectronPartition, typename Z = ONVBasis, std::enable_if_t<std::is_same<Z, SpinUnresolvedONVBasis>::value, bool> = true>
2081 double calculateProbabilityOfFindingElectronPartition(const DiscreteDomainPartition& domain_partition, const ElectronPartition& electron_partition) const {
2082
2083 double domain_probability = 0.0;
2084 const auto calculate_domain_probability = [&](const SpinUnresolvedONV& onv, size_t I) mutable {
2085 if (domain_partition.overlapWithONV(onv) == electron_partition) {
2086 domain_probability += std::pow(this->coefficient(I), 2);
2087 }
2088 };
2089 onv_basis.forEach(calculate_domain_probability);
2090 return domain_probability;
2091 }
2092
2093
2094 /*
2095 * MARK: Iterating
2096 */
2097
2103 template <typename Z = ONVBasis>
2104 enable_if_t<std::is_same<Z, SpinResolvedONVBasis>::value, void> forEach(const std::function<void(const Scalar, const SpinResolvedONV)>& callback) const {
2105
2106 // Iterate over all ONVs in this ONV basis, and look up the corresponding coefficient.
2107 const auto& onv_basis = this->onv_basis;
2108 const auto& coefficients = this->m_coefficients;
2109 onv_basis.forEach([&onv_basis, &callback, &coefficients](const SpinUnresolvedONV& onv_alpha, const size_t I_alpha, const SpinUnresolvedONV& onv_beta, const size_t I_beta) {
2110 const SpinResolvedONV onv {onv_alpha, onv_beta};
2111 const auto address = onv_basis.compoundAddress(I_alpha, I_beta);
2112 const auto coefficient = coefficients(address);
2113
2114 callback(coefficient, onv);
2115 });
2116 }
2117
2118
2124 template <typename Z = ONVBasis>
2125 enable_if_t<std::is_same<Z, SpinUnresolvedONVBasis>::value, void> forEach(const std::function<void(const Scalar, const SpinUnresolvedONV)>& callback) const {
2126
2127 // Iterate over all ONVs in this ONV basis, and look up the corresponding coefficient.
2128 const auto& onv_basis = this->onv_basis;
2129 const auto& coefficients = this->m_coefficients;
2130 onv_basis.forEach([&onv_basis, &callback, &coefficients](const SpinUnresolvedONV& onv, const size_t I) {
2131 const auto coefficient = coefficients(I);
2132
2133 callback(coefficient, onv);
2134 });
2135 }
2136
2137
2138 /*
2139 * MARK: Comparing
2140 */
2141
2148 bool isApprox(const LinearExpansion<Scalar, ONVBasis>& other, double tolerance = 1.0e-12) const {
2149
2150 if (this->onv_basis.dimension() != other.onv_basis.dimension()) {
2151 return false;
2152 }
2153
2154 return (this->coefficients()).isEqualEigenvectorAs(other.coefficients(), tolerance);
2155 }
2156};
2157
2158
2159} // namespace GQCP
Definition: G1DM.hpp:41
Definition: G2DM.hpp:42
Definition: GSpinorBasis.hpp:62
Definition: LinearExpansion.hpp:59
static LinearExpansion< Scalar, ONVBasis > Normalized(const ONVBasis &onv_basis, const VectorX< Scalar > &coefficients)
Definition: LinearExpansion.hpp:141
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedSelectedONVBasis >::value, Orbital1DM< double > > calculate1DM() const
Definition: LinearExpansion.hpp:1631
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value > basisTransform(const RTransformation< double > &T)
Definition: LinearExpansion.hpp:414
enable_if_t< std::is_same< Z, SpinUnresolvedONVBasis >::value, void > forEach(const std::function< void(const Scalar, const SpinUnresolvedONV)> &callback) const
Definition: LinearExpansion.hpp:2125
enable_if_t< std::is_same< Z, SpinUnresolvedONVBasis >::value, Tensor< Scalar, 2 > > calculateSystemOrbitalRDM(const DiscreteDomainPartition &domain_partition) const
Definition: LinearExpansion.hpp:1897
double calculateProbabilityOfFindingElectronPartition(const DiscreteDomainPartition &domain_partition, const ElectronPartition &electron_partition) const
Definition: LinearExpansion.hpp:2058
_ONVBasis ONVBasis
Definition: LinearExpansion.hpp:65
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SeniorityZeroONVBasis >::value, SpinResolved2DM< double > > calculateSpinResolved2DM() const
Definition: LinearExpansion.hpp:1249
enable_if_t< std::is_same< Z, SpinUnresolvedONVBasis >::value, Scalar > calculateNDMElement(const std::vector< size_t > &bra_indices, const std::vector< size_t > &ket_indices) const
Definition: LinearExpansion.hpp:669
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SeniorityZeroONVBasis >::value, Orbital2DM< double > > calculate2DM() const
Definition: LinearExpansion.hpp:1232
enable_if_t< std::is_same< Z, SpinUnresolvedONVBasis >::value|std::is_same< Z, SpinResolvedONVBasis >::value, GQCP::Tensor< Scalar, 2 > > tensorizeCoefficients(const std::vector< typename ONVBasis::ONV > &system_onvs, const std::vector< typename ONVBasis::ONV > &environment_onvs) const
Definition: LinearExpansion.hpp:1802
enable_if_t< std::is_same< Z, SpinUnresolvedSelectedONVBasis >::value, G2DM< Scalar > > calculate2DM() const
Definition: LinearExpansion.hpp:1697
enable_if_t< std::is_same< Z, SpinResolvedONVBasis >::value, Tensor< Scalar, 2 > > calculateSystemOrbitalRDM(const DiscreteDomainPartition &domain_partition) const
Definition: LinearExpansion.hpp:1861
static enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedSelectedONVBasis >::value, LinearExpansion< double, SpinResolvedSelectedONVBasis > > FromGAMESSUS(const std::string &GAMESSUS_filename)
Definition: LinearExpansion.hpp:173
Scalar coefficient(const size_t i) const
Definition: LinearExpansion.hpp:388
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedSelectedONVBasis >::value, Orbital2DM< double > > calculate2DM() const
Definition: LinearExpansion.hpp:1640
enable_if_t< std::is_same< Z, double >::value, double > calculateShannonEntropy() const
Definition: LinearExpansion.hpp:1937
const VectorX< Scalar > & coefficients() const
Definition: LinearExpansion.hpp:393
static LinearExpansion< Scalar, ONVBasis > Random(const ONVBasis &onv_basis)
Definition: LinearExpansion.hpp:156
enable_if_t< std::is_same< Z, SpinUnresolvedSelectedONVBasis >::value, Scalar > calculateNDMElement(const std::vector< size_t > &bra_indices, const std::vector< size_t > &ket_indices) const
Definition: LinearExpansion.hpp:757
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SeniorityZeroONVBasis >::value, SpinResolved1DM< double > > calculateSpinResolved1DM() const
Definition: LinearExpansion.hpp:1240
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinUnresolvedONVBasis >::value, G1DM< double > > calculate1DM() const
Definition: LinearExpansion.hpp:595
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, Orbital2DM< double > > calculate2DM() const
Definition: LinearExpansion.hpp:1185
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, SpinResolved1DM< double > > calculateSpinResolved1DM() const
Definition: LinearExpansion.hpp:843
enable_if_t< std::is_same< Z, SpinResolvedONVBasis >::value, void > forEach(const std::function< void(const Scalar, const SpinResolvedONV)> &callback) const
Definition: LinearExpansion.hpp:2104
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SeniorityZeroONVBasis >::value, Orbital1DM< double > > calculate1DM() const
Definition: LinearExpansion.hpp:1198
enable_if_t< std::is_same< Z1, Scalar >::value &&std::is_same< Z2, SeniorityZeroONVBasis >::value, double > calculateSingleOrbitalEntropy(const size_t orbital_index) const
Definition: LinearExpansion.hpp:2010
static LinearExpansion< Scalar, ONVBasis > HartreeFock(const ONVBasis &onv_basis)
Definition: LinearExpansion.hpp:125
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, double > calculateSingleOrbitalEntropy(const size_t orbital_index) const
Definition: LinearExpansion.hpp:1961
_Scalar Scalar
Definition: LinearExpansion.hpp:62
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, SpinResolved2DM< double > > calculateSpinResolved2DM() const
Definition: LinearExpansion.hpp:963
static LinearExpansion< Scalar, ONVBasis > Constant(const ONVBasis &onv_basis)
Definition: LinearExpansion.hpp:109
static enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinUnresolvedONVBasis >::value, LinearExpansion< double, SpinUnresolvedONVBasis > > FromONVProjection(const SpinUnresolvedONV &onv_of, const GSpinorBasis< double, GTOShell > &spinor_basis_on, const GSpinorBasis< double, GTOShell > &spinor_basis_of)
Definition: LinearExpansion.hpp:342
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, Orbital1DM< double > > calculate1DM() const
Definition: LinearExpansion.hpp:1176
const ONVBasis & onvBasis() const
Definition: LinearExpansion.hpp:398
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedSelectedONVBasis >::value, SpinResolved2DM< double > > calculateSpinResolved2DM() const
Definition: LinearExpansion.hpp:1399
static enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedONVBasis >::value, LinearExpansion< double, SpinResolvedONVBasis > > FromONVProjection(const SpinResolvedONV &onv, const RSpinOrbitalBasis< double, GTOShell > &r_spinor_basis, const USpinOrbitalBasis< double, GTOShell > &u_spinor_basis)
Definition: LinearExpansion.hpp:289
bool isApprox(const LinearExpansion< Scalar, ONVBasis > &other, double tolerance=1.0e-12) const
Definition: LinearExpansion.hpp:2148
enable_if_t< std::is_same< Z1, double >::value &&std::is_same< Z2, SpinResolvedSelectedONVBasis >::value, SpinResolved1DM< double > > calculateSpinResolved1DM() const
Definition: LinearExpansion.hpp:1319
LinearExpansion(const ONVBasis &onv_basis, const VectorX< Scalar > &coefficients)
Definition: LinearExpansion.hpp:87
enable_if_t< std::is_same< Z, SpinUnresolvedSelectedONVBasis >::value, G1DM< Scalar > > calculate1DM() const
Definition: LinearExpansion.hpp:1649
Definition: Matrix.hpp:47
Definition: MixedSpinResolved2DMComponent.hpp:37
Definition: Orbital1DM.hpp:42
Definition: Orbital2DM.hpp:41
Definition: PureSpinResolved2DMComponent.hpp:36
Definition: RSpinOrbitalBasis.hpp:63
Definition: RTransformation.hpp:41
SQOverlapOperator overlap() const
Definition: SimpleSpinorBasis.hpp:118
const Transformation & expansion() const
Definition: SimpleSpinorBasis.hpp:98
size_t numberOfOrbitals() const
Definition: SimpleTransformation.hpp:153
const SquareMatrix< Scalar > & matrix() const
Definition: SimpleTransformation.hpp:168
Definition: SpinResolved1DMComponent.hpp:41
Definition: SpinResolved1DM.hpp:44
static SpinResolved1DM< Scalar > FromOrbital1DM(const Orbital1DM< Scalar > &D)
Definition: SpinResolved1DM.hpp:79
Definition: SpinResolved2DM.hpp:44
Definition: SquareMatrix.hpp:39
size_t dimension() const
Definition: SquareMatrix.hpp:299
static Self Identity(const size_t dim)
Definition: SquareMatrix.hpp:143
std::array< Self, 2 > noPivotLUDecompose() const
Definition: SquareMatrix.hpp:335
static Self Zero(const size_t dim)
Definition: SquareMatrix.hpp:289
Definition: SquareRankFourTensor.hpp:36
static Self Zero(const size_t dim)
Definition: SquareRankFourTensor.hpp:147
Definition: Tensor.hpp:46
const Base & Eigen() const
Definition: Tensor.hpp:116
Definition: USpinOrbitalBasis.hpp:60
ScalarUSQOneElectronOperator< ExpansionScalar > overlap() const
Definition: USpinOrbitalBasis.hpp:406
UTransformation< ExpansionScalar > expansion() const
Definition: USpinOrbitalBasis.hpp:238
Definition: BaseOneElectronIntegralBuffer.hpp:25
typename std::enable_if< B, T >::type enable_if_t
Definition: type_traits.hpp:37
complex conj(const complex &c)
Definition: complex.cpp:31
@ beta
Definition: Spin.hpp:29
@ alpha
Definition: Spin.hpp:28