votca 2026-dev
Loading...
Searching...
No Matches
podcoupling.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
21#include "votca/xtp/aomatrix.h"
22#include "votca/xtp/basisset.h"
23#include <Eigen/Eigenvalues>
24#include <algorithm>
25#include <sstream>
26
27namespace votca {
28namespace xtp {
29
30std::vector<Index> MapAtomsToAOIndices(
31 const std::vector<Index>& atom_indices,
32 const std::vector<Index>& func_per_atom) {
33 // Prefix sum: ao_start[a] is the first AO index belonging to atom a
34 // (and ao_start[a+1] is one past its own last AO index) -- valid
35 // specifically because AO basis functions are laid out contiguously
36 // per atom, in atom order, a direct, standard consequence of how AO
37 // bases are constructed (each atom's own shells are added in turn),
38 // not something specific to this function's own use case.
39 std::vector<Index> ao_start(func_per_atom.size() + 1, 0);
40 for (size_t a = 0; a < func_per_atom.size(); ++a) {
41 ao_start[a + 1] = ao_start[a] + func_per_atom[a];
42 }
43
44 std::vector<Index> ao_indices;
45 for (Index atom_index : atom_indices) {
46 if (atom_index < 0 ||
47 atom_index >= static_cast<Index>(func_per_atom.size())) {
48 throw std::runtime_error("MapAtomsToAOIndices: atom index " +
49 std::to_string(atom_index) +
50 " is out of range (0.." +
51 std::to_string(func_per_atom.size() - 1) + ").");
52 }
53 // Each INDIVIDUAL atom's own AOs are appended as one, contiguous
54 // run -- but successive atoms in atom_indices need not be
55 // adjacent to each other at all (the whole reason this function
56 // exists in the first place -- see this function's own header
57 // comment), so the OVERALL ao_indices returned here is not, in
58 // general, a single contiguous range even though each atom's own
59 // contribution to it is.
60 for (Index ao = ao_start[atom_index]; ao < ao_start[atom_index + 1]; ++ao) {
61 ao_indices.push_back(ao);
62 }
63 }
64 return ao_indices;
65}
66
67namespace {
68// Gathers the (indices.size(), indices.size()) sub-matrix of full_matrix
69// at the given (possibly scattered, non-contiguous) row/column indices
70// -- a direct gather, not an explicit permutation of full_matrix itself:
71// mathematically identical to "reorder full_matrix so these indices
72// become contiguous, then slice a contiguous block", but avoids ever
73// constructing or applying a full-size permutation on the (potentially
74// much larger) full_matrix at all.
75Eigen::MatrixXd GatherSubMatrix(const Eigen::MatrixXd& full_matrix,
76 const std::vector<Index>& indices) {
77 Index n = static_cast<Index>(indices.size());
78 Eigen::MatrixXd result(n, n);
79 for (Index i = 0; i < n; ++i) {
80 for (Index j = 0; j < n; ++j) {
81 result(i, j) = full_matrix(indices[size_t(i)], indices[size_t(j)]);
82 }
83 }
84 return result;
85}
86
87// Off-diagonal (indices_row.size(), indices_col.size()) block of
88// full_matrix -- same gather approach as GatherSubMatrix above, but for
89// a genuinely rectangular block (row indices from one fragment, column
90// indices from the other), needed for the final donor-acceptor
91// coupling element itself.
92Eigen::MatrixXd GatherOffDiagonalBlock(const Eigen::MatrixXd& full_matrix,
93 const std::vector<Index>& indices_row,
94 const std::vector<Index>& indices_col) {
95 Index n_row = static_cast<Index>(indices_row.size());
96 Index n_col = static_cast<Index>(indices_col.size());
97 Eigen::MatrixXd result(n_row, n_col);
98 for (Index i = 0; i < n_row; ++i) {
99 for (Index j = 0; j < n_col; ++j) {
100 result(i, j) =
101 full_matrix(indices_row[size_t(i)], indices_col[size_t(j)]);
102 }
103 }
104 return result;
105}
106} // namespace
107
108namespace {
109// Each fragment's own number of occupied orbitals is NOT directly,
110// unambiguously available from the full, intact supermolecule's own,
111// delocalized wavefunction at all -- estimated instead as half the
112// fragment's own total nuclear charge (i.e. assuming a neutral,
113// closed-shell fragment), rounded to the nearest integer, matching
114// the standard convention already used elsewhere in this codebase for
115// a fragment's own "neutral reference" electron count (see
116// DFTEngine::BuildCDFTConstraint's own neutral_reference_population).
117// Genuinely approximate for a COVALENTLY-bonded fragment specifically
118// (there is no truly well-defined "neutral fragment" electron count
119// once a bond has been cut across the fragment boundary) -- flagged
120// directly to the user as a real modeling choice, not silently
121// assumed to be exact.
122Index CountFragmentElectrons(const QMMolecule& mol,
123 const std::vector<Index>& atoms) {
124 double nuccharge = 0.0;
125 for (Index atom_index : atoms) {
126 nuccharge += static_cast<double>(mol[atom_index].getNuccharge());
127 }
128 return static_cast<Index>(std::lround(nuccharge / 2.0));
129}
130
131// Fragment-local analogue of DFTcoupling::DetermineRangeOfStates --
132// same definition (minimal = homo_index - numberofstates + 1, maximal
133// = lumo_index + numberofstates - 1, covering both occupied and
134// virtual orbitals in one, single, combined range), but without that
135// function's own degeneracy_ handling: not requested by the user for
136// this class, and left out deliberately rather than added
137// speculatively. Bounds-checked directly against the fragment's own
138// total number of orbitals (n_basis, i.e. the fragment's own AO count
139// -- the fragment-block Fock sub-block is square, n_basis x n_basis,
140// so this is also the fragment's own total number of orbitals
141// available from its own generalized eigenvalue solve).
142std::pair<Index, Index> DetermineFragmentRangeOfStates(Index homo_index,
143 Index lumo_index,
144 Index numberofstates,
145 Index n_basis) {
146 Index minimal = homo_index - numberofstates + 1;
147 Index maximal = lumo_index + numberofstates - 1;
148 if (minimal < 0 || maximal >= n_basis) {
149 throw std::runtime_error(
150 "PODCoupling: requested numberofstates=" +
151 std::to_string(numberofstates) +
152 " exceeds the fragment's own available orbital range (0.." +
153 std::to_string(n_basis - 1) + ").");
154 }
155 return {minimal, maximal - minimal + 1};
156}
157} // namespace
158
160 std::vector<Index> fragment_A_atoms,
161 std::vector<Index> fragment_B_atoms)
162 : orbitals_(orbitals),
163 pLog_(log),
164 fragment_A_atoms_(std::move(fragment_A_atoms)),
165 fragment_B_atoms_(std::move(fragment_B_atoms)) {
166 const QMMolecule& mol = orbitals_.QMAtoms();
167 nocc_A_ = CountFragmentElectrons(mol, fragment_A_atoms_);
168 nocc_B_ = CountFragmentElectrons(mol, fragment_B_atoms_);
169}
170
172 Index numberofstatesB) {
173 const QMMolecule& mol = orbitals_.QMAtoms();
174
175 AOBasis full_dftbasis;
176 {
177 BasisSet basisset;
178 basisset.Load(orbitals_.getDFTbasisName());
179 full_dftbasis.Fill(basisset, mol);
180 }
181 AOOverlap overlap;
182 overlap.Fill(full_dftbasis);
183 const Eigen::MatrixXd& S = overlap.Matrix();
184
185 // Reconstructs the full, AO-basis Fock matrix from the already-
186 // converged MOs/orbital energies -- F_AO = S*C*eps*C^T*S, valid
187 // because C is S-orthonormal (C^T*S*C = I, so C^{-1} = C^T*S) and
188 // spans the full AO space (confirmed directly: no near-linearly-
189 // dependent basis functions were removed for the reference
190 // calculation this class is designed to consume -- see this class's
191 // own header comment on requiring an already-converged, ordinary,
192 // neutral ground-state calculation as input). Orbitals itself does
193 // not persist the raw AO-basis Fock matrix directly, only the
194 // diagonalized MO representation, so this reconstruction is the
195 // standard, direct way to recover it.
196 const Eigen::MatrixXd& C = orbitals_.MOs().eigenvectors();
197 const Eigen::VectorXd& eps = orbitals_.MOs().eigenvalues();
198 Eigen::MatrixXd F = S * C * eps.asDiagonal() * C.transpose() * S;
199
200 std::vector<Index> ao_indices_A =
202 std::vector<Index> ao_indices_B =
204
205 Eigen::MatrixXd F_AA = GatherSubMatrix(F, ao_indices_A);
206 Eigen::MatrixXd S_AA = GatherSubMatrix(S, ao_indices_A);
207 Eigen::MatrixXd F_BB = GatherSubMatrix(F, ao_indices_B);
208 Eigen::MatrixXd S_BB = GatherSubMatrix(S, ao_indices_B);
209
210 // Separately diagonalizes each fragment's own donor/acceptor block
211 // IN THE ORIGINAL AO BASIS -- this is the specific "2" in POD2 (Ghan
212 // et al.), deliberately NOT the original POD's own global Lowdin-
213 // orthogonalization of the whole AO basis first (confirmed directly,
214 // via arXiv:1512.00200's own critical comparison, to make results
215 // basis-set-unstable: larger basis sets increase inter-fragment AO
216 // mixing under global orthogonalization, degrading the resulting
217 // coupling).
218 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es_A(F_AA, S_AA);
219 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es_B(F_BB, S_BB);
220 if (es_A.info() != Eigen::Success || es_B.info() != Eigen::Success) {
221 throw std::runtime_error(
222 "PODCoupling: generalized eigenvalue solve failed for one or both "
223 "fragment Fock sub-blocks -- this can happen if a fragment's own "
224 "S_AA is (numerically) singular, e.g. from a badly chosen or "
225 "overlapping fragment definition.");
226 }
227
228 // Stored for later use by GetFragmentOrbital -- see this member's
229 // own header comment in podcoupling.h for why (embedding a fragment
230 // orbital back into the full, whole-molecule AO basis, e.g. for
231 // visualization, needs exactly these ingredients).
232 fragment_A_eigenvectors_ = es_A.eigenvectors();
233 fragment_B_eigenvectors_ = es_B.eigenvectors();
234 ao_indices_A_ = ao_indices_A;
235 ao_indices_B_ = ao_indices_B;
236 nao_full_ = F.rows();
237 // Stored for DescribeFragmentOrbitalComposition's own use -- see
238 // its own header comment in podcoupling.h for why a Mulliken-style
239 // weighting (needing each fragment's own S explicitly) is used
240 // there instead of raw |coefficient|.
241 S_AA_ = S_AA;
242 S_BB_ = S_BB;
243
244 Range_orbA_ = DetermineFragmentRangeOfStates(getFragmentAHomoIndex(),
246 numberofstatesA, F_AA.rows());
247 Range_orbB_ = DetermineFragmentRangeOfStates(getFragmentBHomoIndex(),
249 numberofstatesB, F_BB.rows());
250 Index levelsA = Range_orbA_.second;
251 Index levelsB = Range_orbB_.second;
252
254 << TimeStamp()
255 << " PODCoupling: fragment A HOMO=" << getFragmentAHomoIndex()
256 << ", LUMO=" << getFragmentALumoIndex()
257 << " (both estimated), range covers orbitals [" << Range_orbA_.first
258 << ", " << (Range_orbA_.first + levelsA - 1) << "]" << std::flush;
260 << TimeStamp()
261 << " PODCoupling: fragment B HOMO=" << getFragmentBHomoIndex()
262 << ", LUMO=" << getFragmentBLumoIndex()
263 << " (both estimated), range covers orbitals [" << Range_orbB_.first
264 << ", " << (Range_orbB_.first + levelsB - 1) << "]" << std::flush;
265
266 // The off-diagonal AO blocks themselves do not depend on which
267 // specific orbital pair is being coupled -- gathered once, outside
268 // the loop below, and reused for every (i, j) pair in the requested
269 // range, rather than redundantly re-gathering the same block once
270 // per orbital pair.
271 Eigen::MatrixXd F_AB = GatherOffDiagonalBlock(F, ao_indices_A, ao_indices_B);
272 Eigen::MatrixXd S_AB_block =
273 GatherOffDiagonalBlock(S, ao_indices_A, ao_indices_B);
274
275 JAB_ = Eigen::MatrixXd(levelsA, levelsB);
276 for (Index i = 0; i < levelsA; ++i) {
277 Eigen::VectorXd orbital_A = es_A.eigenvectors().col(Range_orbA_.first + i);
278 double e_A_hartree = es_A.eigenvalues()(Range_orbA_.first + i);
279 for (Index j = 0; j < levelsB; ++j) {
280 Eigen::VectorXd orbital_B =
281 es_B.eigenvectors().col(Range_orbB_.first + j);
282 double e_B_hartree = es_B.eigenvalues()(Range_orbB_.first + j);
283
284 double J_AB = orbital_A.dot(F_AB * orbital_B);
285 // S_AB: the overlap between this specific PAIR of fragment
286 // orbitals themselves (not either fragment's own internal
287 // S_AA/S_BB, already used above for each one's own
288 // normalization).
289 double S_AB = orbital_A.dot(S_AB_block * orbital_B);
290
291 // The actual, final coupling for this pair: NOT J_AB itself --
292 // confirmed directly, from a real, independent cross-check
293 // against a symmetric test dimer's own half-HOMO-HOMO-1 gap,
294 // that the raw J_AB is wrong by close to a factor of 2 (the two
295 // fragment orbitals are not mutually orthogonal in general --
296 // S_AB above is the direct evidence of that -- so J_AB alone
297 // conflates the true electronic coupling with an overlap-
298 // induced contribution). The paper this whole POD2
299 // implementation grew out of (Baumeier, Kirkpatrick, Andrienko,
300 // PCCP 2010, 12, 11103) derives exactly this same correction for
301 // DIPRO's own, analogous non-orthogonal monomer HOMOs, its own
302 // eqn (10): t_AB = (J_AB - 0.5*(e_A+e_B)*S_AB) / (1-S_AB^2).
303 double denominator = 1.0 - S_AB * S_AB;
304 JAB_(i, j) =
305 (J_AB - 0.5 * (e_A_hartree + e_B_hartree) * S_AB) / denominator;
306
307 // Per-pair diagnostic: S_AB and the Lowdin denominator (1-S_AB^2)
308 // are worth watching on an ongoing basis, since a large |S_AB|
309 // approaching +-1 would make this correction numerically
310 // unstable (the denominator collapsing toward zero) -- this is
311 // a genuine, real risk for a COVALENTLY-bonded fragment pair
312 // specifically, where the two fragment orbitals sit directly
313 // across a real chemical bond, unlike the small, well-behaved
314 // S_AB already confirmed for a non-bonded test case (the
315 // ethylene dimer, ~0.03). Confirmed directly, on a real
316 // covalently-bonded case (2,2'-bithiophene), that a large,
317 // physically genuine coupling and a small, stable S_AB can both
318 // be true at once -- e.g. S_AB=0.063 there, comfortably away
319 // from +-1, with the Lowdin correction only a ~13% adjustment
320 // to J_AB, even though the resulting coupling itself (~3.2 eV)
321 // is large: strong THROUGH-BOND coupling is a different regime
322 // from the weak, through-space coupling this correction was
323 // first validated against, and a large result is not itself a
324 // red flag -- only S_AB itself getting close to +-1 would be.
326 << TimeStamp()
327 << " PODCoupling diagnostic: pair (A=" << (Range_orbA_.first + i)
328 << ", B=" << (Range_orbB_.first + j) << "): S_AB=" << S_AB
329 << ", (1-S_AB^2)=" << denominator
330 << ", raw J_AB=" << (J_AB * 27.211386245988)
331 << " eV, corrected=" << (JAB_(i, j) * 27.211386245988) << " eV"
332 << std::flush;
333 }
334 }
335}
336
337double PODCoupling::getCouplingElement(Index levelA, Index levelB) const {
338 Index indexA = levelA - Range_orbA_.first;
339 Index indexB = levelB - Range_orbB_.first;
340 if (indexA < 0 || indexA >= JAB_.rows() || indexB < 0 ||
341 indexB >= JAB_.cols()) {
342 throw std::runtime_error(
343 "PODCoupling::getCouplingElement: requested levelA=" +
344 std::to_string(levelA) + "/levelB=" + std::to_string(levelB) +
345 " is outside the range covered by the most recent "
346 "CalculateCouplings call.");
347 }
348 return JAB_(indexA, indexB);
349}
350
351Eigen::VectorXd PODCoupling::GetFragmentOrbital(bool fragment_A,
352 Index level) const {
353 const Eigen::MatrixXd& eigenvectors =
355 const std::vector<Index>& ao_indices =
356 fragment_A ? ao_indices_A_ : ao_indices_B_;
357 const std::pair<Index, Index>& range = fragment_A ? Range_orbA_ : Range_orbB_;
358
359 Index index = level - range.first;
360 if (index < 0 || index >= range.second) {
361 throw std::runtime_error(
362 "PODCoupling::GetFragmentOrbital: requested level=" +
363 std::to_string(level) +
364 " is outside the range covered by the most recent "
365 "CalculateCouplings call.");
366 }
367
368 // Scatters the fragment-local coefficient vector (length equal to
369 // this fragment's own AO count) into the full, whole-molecule AO
370 // basis (length nao_full_) -- the direct inverse of
371 // MapAtomsToAOIndices/GatherSubMatrix's own gather: every AO NOT
372 // belonging to this fragment gets a zero coefficient, since the
373 // fragment orbital, by construction, has no amplitude there at all.
374 //
375 // Uses level DIRECTLY here, NOT the range-relative index computed
376 // above -- unlike JAB_ (only ever range-sized, levelsA x levelsB),
377 // eigenvectors_ stores ALL of this fragment's own columns (its full
378 // n_A x n_A eigenvector set), so level itself is already the
379 // correct, absolute column index into it. Confirmed directly by
380 // comparing against getCouplingElement's own code before writing
381 // this: using the range-relative index here instead would have
382 // silently returned the wrong orbital, offset by range.first
383 // columns, whenever range.first != 0 (i.e. whenever the fragment's
384 // own HOMO is not literally its lowest-energy orbital -- the normal
385 // case for any real molecule).
386 Eigen::VectorXd fragment_local = eigenvectors.col(level);
387 Eigen::VectorXd full_basis = Eigen::VectorXd::Zero(nao_full_);
388 for (Index i = 0; i < Index(ao_indices.size()); ++i) {
389 full_basis(ao_indices[size_t(i)]) = fragment_local(i);
390 }
391 return full_basis;
392}
393
395 Index level,
396 Index top_n) const {
397 const Eigen::MatrixXd& eigenvectors =
399 const std::vector<Index>& ao_indices =
400 fragment_A ? ao_indices_A_ : ao_indices_B_;
401 const std::vector<Index>& fragment_atoms =
403 const std::pair<Index, Index>& range = fragment_A ? Range_orbA_ : Range_orbB_;
404
405 Index index = level - range.first;
406 if (index < 0 || index >= range.second) {
407 throw std::runtime_error(
408 "PODCoupling::DescribeFragmentOrbitalComposition: requested level=" +
409 std::to_string(level) +
410 " is outside the range covered by the most recent "
411 "CalculateCouplings call.");
412 }
413 Eigen::VectorXd fragment_local = eigenvectors.col(level);
414
415 // Full-molecule-AO-index -> (atom index, shell angular momentum L)
416 // lookup, built by re-constructing the same AOBasis
417 // CalculateCouplings itself already used (Orbitals itself does not
418 // persist a full-AO-index -> (atom, shell) map directly, so this is
419 // rebuilt here the same way CalculateCouplings' own F/S
420 // reconstruction already does, via getDFTbasisName()/QMAtoms()).
421 const QMMolecule& mol = orbitals_.QMAtoms();
422 AOBasis full_dftbasis;
423 {
424 BasisSet basisset;
425 basisset.Load(orbitals_.getDFTbasisName());
426 full_dftbasis.Fill(basisset, mol);
427 }
428 std::vector<Index> ao_to_atom(full_dftbasis.AOBasisSize(), -1);
429 std::vector<L> ao_to_L(full_dftbasis.AOBasisSize());
430 for (const AOShell& shell : full_dftbasis) {
431 Index offset = shell.getStartIndex();
432 for (Index k = 0; k < shell.getNumFunc(); ++k) {
433 ao_to_atom[size_t(offset + k)] = shell.getAtomIndex();
434 ao_to_L[size_t(offset + k)] = shell.getL();
435 }
436 }
437
438 // Mulliken population of each AO in this orbital: P_i = c_i*(S*c)_i
439 // (S being this fragment's own AO overlap sub-block, S_AA_/S_BB_) --
440 // sums to c^T*S*c = 1 over all AOs, since the generalized eigenvalue
441 // solve normalizes with respect to S, not the plain Euclidean norm.
442 // Ranking by |P_i| here, NOT |coefficient| -- see this method's own
443 // header comment in podcoupling.h for why the latter is actively
444 // misleading in a non-orthogonal basis (confirmed directly, from a
445 // real, misleading run, not just in principle).
446 const Eigen::MatrixXd& S_local = fragment_A ? S_AA_ : S_BB_;
447 Eigen::VectorXd mulliken_population =
448 fragment_local.cwiseProduct(S_local * fragment_local);
449
450 // Sort this fragment orbital's own AOs by |Mulliken population|,
451 // largest first -- ao_indices[i] (the full-molecule AO index for
452 // this fragment's own local AO i) is what actually indexes into
453 // ao_to_atom/ao_to_L above; fragment_local(i)/mulliken_population(i)
454 // are that same AO's own coefficient/population in THIS orbital.
455 std::vector<Index> order(fragment_local.size());
456 for (Index i = 0; i < fragment_local.size(); ++i) {
457 order[size_t(i)] = i;
458 }
459 std::sort(order.begin(), order.end(), [&](Index a, Index b) {
460 return std::abs(mulliken_population(a)) > std::abs(mulliken_population(b));
461 });
462
463 std::ostringstream out;
464 out << "Top " << std::min(top_n, Index(order.size()))
465 << " AO Mulliken populations for fragment " << (fragment_A ? "A" : "B")
466 << " orbital " << level << ":";
467 for (Index rank = 0; rank < std::min(top_n, Index(order.size())); ++rank) {
468 Index local_ao = order[size_t(rank)];
469 Index full_ao = ao_indices[size_t(local_ao)];
470 Index atom_index = ao_to_atom[size_t(full_ao)];
471 std::string element = atom_index >= 0 ? mol[atom_index].getElement() : "?";
472 out << "\n " << (rank + 1)
473 << ". population=" << mulliken_population(local_ao)
474 << " (coeff=" << fragment_local(local_ao) << "), atom " << atom_index
475 << " (" << element << ", fragment atom "
476 << (std::find(fragment_atoms.begin(), fragment_atoms.end(),
477 atom_index) -
478 fragment_atoms.begin())
479 << "), shell " << EnumToString(ao_to_L[size_t(full_ao)]);
480 }
481 return out.str();
482}
483
484} // namespace xtp
485} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
void Fill(const BasisSet &bs, const QMMolecule &atoms)
Definition aobasis.cc:85
const std::vector< Index > & getFuncPerAtom() const
Definition aobasis.h:72
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
Definition aomatrix.h:52
void Load(const std::string &name)
Definition basisset.cc:149
Logger is used for thread-safe output of messages.
Definition logger.h:164
Container for molecular orbitals and derived one-particle data.
Definition orbitals.h:47
Eigen::MatrixXd JAB_
Index getFragmentAHomoIndex() const
std::pair< Index, Index > Range_orbA_
std::vector< Index > ao_indices_A_
std::pair< Index, Index > Range_orbB_
void CalculateCouplings(Index numberofstatesA, Index numberofstatesB)
double getCouplingElement(Index levelA, Index levelB) const
Eigen::MatrixXd fragment_B_eigenvectors_
Eigen::VectorXd GetFragmentOrbital(bool fragment_A, Index level) const
std::vector< Index > ao_indices_B_
std::vector< Index > fragment_B_atoms_
Eigen::MatrixXd fragment_A_eigenvectors_
std::vector< Index > fragment_A_atoms_
Index getFragmentBLumoIndex() const
Index getFragmentALumoIndex() const
Index getFragmentBHomoIndex() const
Eigen::MatrixXd S_BB_
std::string DescribeFragmentOrbitalComposition(bool fragment_A, Index level, Index top_n=5) const
Eigen::MatrixXd S_AA_
PODCoupling(Orbitals &orbitals, Logger *log, std::vector< Index > fragment_A_atoms, std::vector< Index > fragment_B_atoms)
Timestamp returns the current time as a string Example: cout << TimeStamp().
Definition logger.h:224
#define XTP_LOG(level, log)
Definition logger.h:40
STL namespace.
Charge transport classes.
Definition ERIs.h:28
std::string EnumToString(L l)
Definition basisset.cc:60
std::vector< Index > MapAtomsToAOIndices(const std::vector< Index > &atom_indices, const std::vector< Index > &func_per_atom)
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26