23#include <Eigen/Eigenvalues>
31 const std::vector<Index>& atom_indices,
32 const std::vector<Index>& func_per_atom) {
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];
44 std::vector<Index> ao_indices;
45 for (
Index atom_index : atom_indices) {
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) +
").");
60 for (
Index ao = ao_start[atom_index]; ao < ao_start[atom_index + 1]; ++ao) {
61 ao_indices.push_back(ao);
75Eigen::MatrixXd GatherSubMatrix(
const Eigen::MatrixXd& full_matrix,
76 const std::vector<Index>& indices) {
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)]);
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) {
101 full_matrix(indices_row[
size_t(i)], indices_col[
size_t(j)]);
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());
128 return static_cast<Index>(std::lround(nuccharge / 2.0));
142std::pair<Index, Index> DetermineFragmentRangeOfStates(
Index homo_index,
144 Index numberofstates,
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) +
").");
155 return {minimal, maximal - minimal + 1};
160 std::vector<Index> fragment_A_atoms,
161 std::vector<Index> fragment_B_atoms)
172 Index numberofstatesB) {
179 full_dftbasis.
Fill(basisset, mol);
182 overlap.
Fill(full_dftbasis);
183 const Eigen::MatrixXd&
S = overlap.
Matrix();
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;
200 std::vector<Index> ao_indices_A =
202 std::vector<Index> ao_indices_B =
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);
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.");
246 numberofstatesA, F_AA.rows());
249 numberofstatesB, F_BB.rows());
257 <<
" (both estimated), range covers orbitals [" <<
Range_orbA_.first
258 <<
", " << (
Range_orbA_.first + levelsA - 1) <<
"]" << std::flush;
263 <<
" (both estimated), range covers orbitals [" <<
Range_orbB_.first
264 <<
", " << (
Range_orbB_.first + levelsB - 1) <<
"]" << std::flush;
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);
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 =
282 double e_B_hartree = es_B.eigenvalues()(
Range_orbB_.first + j);
284 double J_AB = orbital_A.dot(F_AB * orbital_B);
289 double S_AB = orbital_A.dot(S_AB_block * orbital_B);
303 double denominator = 1.0 - S_AB * S_AB;
305 (J_AB - 0.5 * (e_A_hartree + e_B_hartree) * S_AB) / denominator;
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"
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.");
348 return JAB_(indexA, indexB);
353 const Eigen::MatrixXd& eigenvectors =
355 const std::vector<Index>& ao_indices =
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.");
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);
397 const Eigen::MatrixXd& eigenvectors =
399 const std::vector<Index>& ao_indices =
401 const std::vector<Index>& fragment_atoms =
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.");
413 Eigen::VectorXd fragment_local = eigenvectors.col(level);
426 full_dftbasis.
Fill(basisset, mol);
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();
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);
455 std::vector<Index> order(fragment_local.size());
456 for (
Index i = 0; i < fragment_local.size(); ++i) {
457 order[size_t(i)] = i;
459 std::sort(order.begin(), order.end(), [&](
Index a,
Index b) {
460 return std::abs(mulliken_population(a)) > std::abs(mulliken_population(b));
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(),
478 fragment_atoms.begin())
479 <<
"), shell " <<
EnumToString(ao_to_L[
size_t(full_ao)]);
Container to hold Basisfunctions for all atoms.
Index AOBasisSize() const
void Fill(const BasisSet &bs, const QMMolecule &atoms)
const std::vector< Index > & getFuncPerAtom() const
void Fill(const AOBasis &aobasis) final
const Eigen::MatrixXd & Matrix() const
void Load(const std::string &name)
Logger is used for thread-safe output of messages.
Container for molecular orbitals and derived one-particle data.
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
std::string DescribeFragmentOrbitalComposition(bool fragment_A, Index level, Index top_n=5) const
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().
#define XTP_LOG(level, log)
Charge transport classes.
std::string EnumToString(L l)
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.