24#include <Eigen/Eigenvalues>
33 const std::vector<Index>& atom_indices,
34 const std::vector<Index>& func_per_atom) {
37 std::vector<Index> ao_start(func_per_atom.size() + 1, 0);
38 for (
size_t a = 0; a < func_per_atom.size(); ++a) {
39 ao_start[a + 1] = ao_start[a] + func_per_atom[a];
42 std::vector<Index> ao_indices;
43 for (
Index atom_index : atom_indices) {
45 atom_index >=
static_cast<Index>(func_per_atom.size())) {
46 throw std::runtime_error(
"MapAtomsToAOIndices: atom index " +
47 std::to_string(atom_index) +
48 " is out of range (0.." +
49 std::to_string(func_per_atom.size() - 1) +
").");
52 for (
Index ao = ao_start[atom_index]; ao < ao_start[atom_index + 1]; ++ao) {
53 ao_indices.push_back(ao);
62Eigen::MatrixXd GatherSubMatrix(
const Eigen::MatrixXd& full_matrix,
63 const std::vector<Index>& indices) {
65 Eigen::MatrixXd result(n, n);
66 for (
Index i = 0; i < n; ++i) {
67 for (
Index j = 0; j < n; ++j) {
68 result(i, j) = full_matrix(indices[
size_t(i)], indices[
size_t(j)]);
76Eigen::MatrixXd GatherOffDiagonalBlock(
const Eigen::MatrixXd& full_matrix,
77 const std::vector<Index>& indices_row,
78 const std::vector<Index>& indices_col) {
79 Index n_row =
static_cast<Index>(indices_row.size());
80 Index n_col =
static_cast<Index>(indices_col.size());
81 Eigen::MatrixXd result(n_row, n_col);
82 for (
Index i = 0; i < n_row; ++i) {
83 for (
Index j = 0; j < n_col; ++j) {
85 full_matrix(indices_row[
size_t(i)], indices_col[
size_t(j)]);
99 const std::vector<Index>& atoms) {
100 double nuccharge = 0.0;
101 for (
Index atom_index : atoms) {
102 nuccharge +=
static_cast<double>(mol[atom_index].getNuccharge());
104 return static_cast<Index>(std::lround(nuccharge / 2.0));
111std::pair<Index, Index> DetermineFragmentRangeOfStates(
Index homo_index,
113 Index numberofstates,
115 Index minimal = homo_index - numberofstates + 1;
116 Index maximal = lumo_index + numberofstates - 1;
117 if (minimal < 0 || maximal >= n_basis) {
118 throw std::runtime_error(
"PODCoupling: requested numberofstates=" +
119 std::to_string(numberofstates) +
120 " exceeds the fragment's orbital range (0.." +
121 std::to_string(n_basis - 1) +
").");
123 return {minimal, maximal - minimal + 1};
128 std::vector<Index> fragment_A_atoms,
129 std::vector<Index> fragment_B_atoms)
140 Index numberofstatesB) {
148 full_dftbasis.
Fill(basisset, mol);
151 overlap.
Fill(full_dftbasis);
152 const Eigen::MatrixXd&
S = overlap.
Matrix();
157 const Eigen::MatrixXd& C =
orbitals_.MOs().eigenvectors();
158 const Eigen::VectorXd& eps =
orbitals_.MOs().eigenvalues();
159 Eigen::MatrixXd
F =
S * C * eps.asDiagonal() * C.transpose() *
S;
161 std::vector<Index> ao_indices_A =
163 std::vector<Index> ao_indices_B =
166 Eigen::MatrixXd F_AA = GatherSubMatrix(
F, ao_indices_A);
167 Eigen::MatrixXd S_AA = GatherSubMatrix(
S, ao_indices_A);
168 Eigen::MatrixXd F_BB = GatherSubMatrix(
F, ao_indices_B);
169 Eigen::MatrixXd S_BB = GatherSubMatrix(
S, ao_indices_B);
175 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es_A(F_AA, S_AA);
176 Eigen::GeneralizedSelfAdjointEigenSolver<Eigen::MatrixXd> es_B(F_BB, S_BB);
177 if (es_A.info() != Eigen::Success || es_B.info() != Eigen::Success) {
178 throw std::runtime_error(
179 "PODCoupling: generalized eigenvalue solve failed for one or both "
180 "fragment Fock sub-blocks; the fragment overlap matrix may be "
181 "numerically singular (check the fragment definition).");
195 numberofstatesA, F_AA.rows());
198 numberofstatesB, F_BB.rows());
206 <<
" (both estimated), range covers orbitals [" <<
Range_orbA_.first
207 <<
", " << (
Range_orbA_.first + levelsA - 1) <<
"]" << std::flush;
212 <<
" (both estimated), range covers orbitals [" <<
Range_orbB_.first
213 <<
", " << (
Range_orbB_.first + levelsB - 1) <<
"]" << std::flush;
216 Eigen::MatrixXd F_AB = GatherOffDiagonalBlock(
F, ao_indices_A, ao_indices_B);
217 Eigen::MatrixXd S_AB_block =
218 GatherOffDiagonalBlock(
S, ao_indices_A, ao_indices_B);
220 JAB_ = Eigen::MatrixXd(levelsA, levelsB);
221 for (
Index i = 0; i < levelsA; ++i) {
222 Eigen::VectorXd orbital_A = es_A.eigenvectors().col(
Range_orbA_.first + i);
223 double e_A_hartree = es_A.eigenvalues()(
Range_orbA_.first + i);
224 for (
Index j = 0; j < levelsB; ++j) {
225 Eigen::VectorXd orbital_B =
227 double e_B_hartree = es_B.eigenvalues()(
Range_orbB_.first + j);
229 double J_AB = orbital_A.dot(F_AB * orbital_B);
231 double S_AB = orbital_A.dot(S_AB_block * orbital_B);
238 double denominator = 1.0 - S_AB * S_AB;
240 (J_AB - 0.5 * (e_A_hartree + e_B_hartree) * S_AB) / denominator;
248 <<
" PODCoupling diagnostic: pair (A=" << (
Range_orbA_.first + i)
249 <<
", B=" << (
Range_orbB_.first + j) <<
"): S_AB=" << S_AB
250 <<
", (1-S_AB^2)=" << denominator
261 if (indexA < 0 || indexA >=
JAB_.rows() || indexB < 0 ||
262 indexB >=
JAB_.cols()) {
263 throw std::runtime_error(
264 "PODCoupling::getCouplingElement: requested levelA=" +
265 std::to_string(levelA) +
"/levelB=" + std::to_string(levelB) +
266 " is outside the range covered by the most recent "
267 "CalculateCouplings call.");
269 return JAB_(indexA, indexB);
274 const Eigen::MatrixXd& eigenvectors =
276 const std::vector<Index>& ao_indices =
280 Index index = level - range.first;
281 if (index < 0 || index >= range.second) {
282 throw std::runtime_error(
283 "PODCoupling::GetFragmentOrbital: requested level=" +
284 std::to_string(level) +
285 " is outside the range covered by the most recent "
286 "CalculateCouplings call.");
293 Eigen::VectorXd fragment_local = eigenvectors.col(level);
294 Eigen::VectorXd full_basis = Eigen::VectorXd::Zero(
nao_full_);
295 for (
Index i = 0; i <
Index(ao_indices.size()); ++i) {
296 full_basis(ao_indices[
size_t(i)]) = fragment_local(i);
304 const Eigen::MatrixXd& eigenvectors =
306 const std::vector<Index>& ao_indices =
308 const std::vector<Index>& fragment_atoms =
312 Index index = level - range.first;
313 if (index < 0 || index >= range.second) {
314 throw std::runtime_error(
315 "PODCoupling::DescribeFragmentOrbitalComposition: requested level=" +
316 std::to_string(level) +
317 " is outside the range covered by the most recent "
318 "CalculateCouplings call.");
320 Eigen::VectorXd fragment_local = eigenvectors.col(level);
329 full_dftbasis.
Fill(basisset, mol);
331 std::vector<Index> ao_to_atom(full_dftbasis.
AOBasisSize(), -1);
332 std::vector<L> ao_to_L(full_dftbasis.
AOBasisSize());
333 for (
const AOShell& shell : full_dftbasis) {
334 Index offset = shell.getStartIndex();
335 for (
Index k = 0; k < shell.getNumFunc(); ++k) {
336 ao_to_atom[size_t(offset + k)] = shell.getAtomIndex();
337 ao_to_L[size_t(offset + k)] = shell.getL();
344 const Eigen::MatrixXd& S_local = fragment_A ?
S_AA_ :
S_BB_;
345 Eigen::VectorXd mulliken_population =
346 fragment_local.cwiseProduct(S_local * fragment_local);
350 std::vector<Index> order(fragment_local.size());
351 for (
Index i = 0; i < fragment_local.size(); ++i) {
352 order[size_t(i)] = i;
354 std::sort(order.begin(), order.end(), [&](
Index a,
Index b) {
355 return std::abs(mulliken_population(a)) > std::abs(mulliken_population(b));
358 std::ostringstream out;
359 out <<
"Top " << std::min(top_n,
Index(order.size()))
360 <<
" AO Mulliken populations for fragment " << (fragment_A ?
"A" :
"B")
361 <<
" orbital " << level <<
":";
362 for (
Index rank = 0; rank < std::min(top_n,
Index(order.size())); ++rank) {
363 Index local_ao = order[size_t(rank)];
364 Index full_ao = ao_indices[size_t(local_ao)];
365 Index atom_index = ao_to_atom[size_t(full_ao)];
366 std::string element = atom_index >= 0 ? mol[atom_index].getElement() :
"?";
367 out <<
"\n " << (rank + 1)
368 <<
". population=" << mulliken_population(local_ao)
369 <<
" (coeff=" << fragment_local(local_ao) <<
"), atom " << atom_index
370 <<
" (" << element <<
", fragment atom "
371 << (std::find(fragment_atoms.begin(), fragment_atoms.end(),
373 fragment_atoms.begin())
374 <<
"), 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)
void Cite(const std::string &key)
marks a reference as used (thread safe; unknown keys are printed as such)
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.