votca 2026-dev
Loading...
Searching...
No Matches
environmentscreening.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
20// Standard includes
21#include <algorithm>
22#include <array>
23#include <cmath>
24#include <iomanip>
25#include <limits>
26#include <sstream>
27#include <stdexcept>
28#include <string>
29
30// Local VOTCA includes
31#include "votca/xtp/aomatrix.h"
36
37// libint2 last, as in libint2_calls.cc, otherwise it overrides Eigen. And,
38// as in libint2_derivative_calls.cc, WITHOUT <libint2/statics_definition.h>:
39// that header defines storage for libint2's static tables and may appear in
40// exactly one translation unit of the library, which is libint2_calls.cc.
42#define LIBINT2_CONSTEXPR_STATICS 0
43#if defined(__clang__)
44#pragma clang diagnostic push
45#pragma clang diagnostic ignored "-W#warnings"
46#elif defined(__GNUC__)
47#pragma GCC diagnostic push
48#pragma GCC diagnostic ignored "-Warray-bounds"
49#pragma GCC diagnostic ignored "-Wcpp"
50#endif
51#include <libint2.hpp>
52#if defined(__clang__)
53#pragma clang diagnostic pop
54#elif defined(__GNUC__)
55#pragma GCC diagnostic pop
56#endif
57
58namespace votca {
59namespace xtp {
60
62 const AOBasis& auxbasis, const std::vector<Eigen::Vector3d>& points,
63 const std::vector<double>& widths) {
64 if (!widths.empty() && widths.size() != points.size()) {
65 throw std::runtime_error("EnvironmentScreening::AuxFieldAtPoints: " +
66 std::to_string(widths.size()) + " widths for " +
67 std::to_string(points.size()) + " points.");
68 }
69
70 // Same shells, same function offsets, same normalization as
71 // TCMatrix_gwbse::Fill3cMO -- which is what makes the rows of F line up
72 // with the auxiliary index of the stored three-centre integrals.
73 const std::vector<libint2::Shell> shells = auxbasis.GenerateLibintBasis();
74 const std::vector<Index> shell2bf = auxbasis.getMapToBasisFunctions();
75 const Index n_points = Index(points.size());
76
77 Eigen::MatrixXd F =
78 Eigen::MatrixXd::Zero(auxbasis.AOBasisSize(), 3 * n_points);
79 if (n_points == 0 || shells.empty()) {
80 return F;
81 }
82
83 const Index nthreads = OPENMP::getMaxThreads();
84 std::vector<libint2::Engine> engines(nthreads);
85 engines[0] =
86 libint2::Engine(libint2::Operator::nuclear, int(auxbasis.getMaxNprim()),
87 int(auxbasis.getMaxL()), 0);
88 for (Index i = 1; i < nthreads; ++i) {
89 engines[i] = engines[0];
90 }
91 // Smeared sites: (chi_Q | g) with g a single s primitive, as the
92 // auxiliary metric is computed (xs_xs, unit shells on the other side).
93 std::vector<libint2::Engine> coulomb(nthreads);
94 coulomb[0] =
95 libint2::Engine(libint2::Operator::coulomb, int(auxbasis.getMaxNprim()),
96 int(auxbasis.getMaxL()), 0);
97 coulomb[0].set(libint2::BraKet::xs_xs);
98 for (Index i = 1; i < nthreads; ++i) {
99 coulomb[i] = coulomb[0];
100 }
101
102 // Four-point central difference for d/dC, error O(h^4):
103 // f'(C) = [-f(C+2h) + 8 f(C+h) - 8 f(C-h) + f(C-2h)] / (12 h)
104 const double h = 1e-3;
105 const std::array<double, 4> offset = {2.0 * h, h, -h, -2.0 * h};
106 const std::array<double, 4> weight = {-1.0 / (12.0 * h), 8.0 / (12.0 * h),
107 -8.0 / (12.0 * h), 1.0 / (12.0 * h)};
108
109 // Sign. libint2's nuclear operator with a unit charge at C is
110 // -1/|r - C|, so the integral against Shell::unit() is -phi_P(C), minus
111 // the potential of the density chi_P at C (verified against the closed
112 // form for an s-Gaussian). The field is E = -grad_C phi = +grad_C of
113 // that integral, so the stencil is applied to the raw integral as is.
114#pragma omp parallel for schedule(dynamic)
115 for (Index p = 0; p < n_points; ++p) {
116 const double width = widths.empty() ? 0.0 : widths[std::size_t(p)];
117 if (width > 0.0) {
118 // Field of chi_Q averaged over g: E = -grad_C (chi_Q | g_C), with
119 // (chi_Q | g_C) the potential of chi_Q averaged over g (positive
120 // for a positive chi_Q), so the stencil is applied with a minus.
121 // libint2 normalizes the primitive to unit L2 norm, N = (2b/pi)^3/4;
122 // dividing by its charge N (pi/b)^3/2 makes it a unit charge.
123 libint2::Engine& engine = coulomb[OPENMP::getThreadId()];
124 const libint2::Engine::target_ptr_vec& buf = engine.results();
125 const double beta = 1.0 / (width * width);
126 const double charge =
127 std::pow(2.0 * beta / M_PI, 0.75) * std::pow(M_PI / beta, 1.5);
128 for (Index k = 0; k < 3; ++k) {
129 for (std::size_t s = 0; s < offset.size(); ++s) {
130 Eigen::Vector3d C = points[std::size_t(p)];
131 C(k) += offset[s];
132 const libint2::Shell g{
133 {beta}, {{0, false, {1.0}}}, {{C.x(), C.y(), C.z()}}};
134 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
135 engine.compute2<libint2::Operator::coulomb, libint2::BraKet::xs_xs,
136 0>(shells[sh], libint2::Shell::unit(), g,
137 libint2::Shell::unit());
138 if (buf[0] == nullptr) {
139 continue;
140 }
141 const Index start = shell2bf[sh];
142 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
143 F(start + Index(f), 3 * p + k) -= weight[s] * buf[0][f] / charge;
144 }
145 }
146 }
147 }
148 continue;
149 }
150 libint2::Engine& engine = engines[OPENMP::getThreadId()];
151 const libint2::Engine::target_ptr_vec& buf = engine.results();
152 for (Index k = 0; k < 3; ++k) {
153 for (std::size_t s = 0; s < offset.size(); ++s) {
154 Eigen::Vector3d C = points[std::size_t(p)];
155 C(k) += offset[s];
156 std::vector<std::pair<double, std::array<double, 3>>> charge{
157 {1.0, {C.x(), C.y(), C.z()}}};
158 engine.set_params(charge);
159 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
160 engine.compute(shells[sh], libint2::Shell::unit());
161 if (buf[0] == nullptr) {
162 continue; // screened out by libint2: exactly zero
163 }
164 const Index start = shell2bf[sh];
165 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
166 F(start + Index(f), 3 * p + k) += weight[s] * buf[0][f];
167 }
168 }
169 }
170 }
171 }
172 return F;
173}
174
175namespace {
176// The polarizable sites in the order ReactionFieldKernel and ShellKernel
177// index them, and the order AuxFieldAtPoints must be called with.
178std::vector<const PolarSite*> SitesOf(
179 const std::vector<PolarSegment>& segments) {
180 std::vector<const PolarSite*> sites;
181 for (const PolarSegment& seg : segments) {
182 for (const PolarSite& site : seg) {
183 sites.push_back(&site);
184 }
185 }
186 return sites;
187}
188
189void CheckShape(const Eigen::MatrixXd& F, Index n_sites,
190 const std::string& who) {
191 if (F.cols() != 3 * n_sites) {
192 throw std::runtime_error(
193 "EnvironmentScreening::" + who + ": F has " + std::to_string(F.cols()) +
194 " columns for " + std::to_string(n_sites) +
195 " polarizable sites; expected " + std::to_string(3 * n_sites) +
196 ". F must be AuxFieldAtPoints at exactly these sites, in order.");
197 }
198}
199// A = alpha^-1 + T_Thole, block for block as
200// DipoleDipoleInteraction::multiply applies it: PInv on the diagonal,
201// FillTholeInteraction(i, j) above and its transpose below. Every pair,
202// intramolecular ones included, exactly as PolarRegion's CG sees it.
203Eigen::MatrixXd AssembleThole(const std::vector<const PolarSite*>& sites,
204 double exp_damp) {
205 const Index n = Index(sites.size());
206 const eeInteractor interactor(exp_damp);
207 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(3 * n, 3 * n);
208#pragma omp parallel for schedule(dynamic)
209 for (Index i = 0; i < n; ++i) {
210 A.block<3, 3>(3 * i, 3 * i) = sites[std::size_t(i)]->getPInv();
211 for (Index j = i + 1; j < n; ++j) {
212 const Eigen::Matrix3d block = interactor.FillTholeInteraction(
213 *sites[std::size_t(i)], *sites[std::size_t(j)]);
214 A.block<3, 3>(3 * i, 3 * j) = block;
215 A.block<3, 3>(3 * j, 3 * i) = block.transpose();
216 }
217 }
218 return A;
219}
220} // namespace
221
223 const Eigen::MatrixXd& F, const std::vector<PolarSegment>& segments,
224 double exp_damp) {
225 const std::vector<const PolarSite*> sites = SitesOf(segments);
226 const Index n = Index(sites.size());
227 CheckShape(F, n, "ReactionFieldKernel");
228 if (n == 0) {
229 return Eigen::MatrixXd::Zero(F.rows(), F.rows());
230 }
231
232 const Eigen::MatrixXd A = AssembleThole(sites, exp_damp);
233 const Eigen::LLT<Eigen::MatrixXd> llt(A);
234 if (llt.info() != Eigen::Success) {
235 throw std::runtime_error(
236 "EnvironmentScreening::ReactionFieldKernel: the Thole interaction "
237 "matrix of the polar region (" +
238 std::to_string(n) +
239 " sites) is not positive definite. That is a polarization "
240 "catastrophe -- sites too close together for their damping -- and "
241 "there is no stable induced-dipole response for it to screen with.");
242 }
243
244 // B = -F A^-1 F^T = -(L^-1 F^T)^T (L^-1 F^T): symmetric and negative
245 // semidefinite by construction.
246 const Eigen::MatrixXd Y = llt.matrixL().solve(Eigen::MatrixXd(F.transpose()));
247 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(F.rows(), F.rows());
248 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
249 return B.selfadjointView<Eigen::Lower>();
250}
251
253 const Eigen::MatrixXd& F, const std::vector<PolarSegment>& segments,
254 double epsilon) {
255 const std::vector<const PolarSite*> sites = SitesOf(segments);
256 const Index n = Index(sites.size());
257 CheckShape(F, n, "ShellKernel");
258 if (!(epsilon >= 1.0)) {
259 throw std::runtime_error(
260 "EnvironmentScreening::ShellKernel: epsilon must be at least 1 "
261 "(it screens the QM field; below 1 it would amplify it). Got " +
262 std::to_string(epsilon) + ".");
263 }
264
265 // B_shell = - sum_j F_j (alpha_j / epsilon) F_j^T. Written as -Z Z^T with
266 // Z_j = F_j (alpha_j / epsilon)^(1/2), so it is symmetric and negative
267 // semidefinite by construction, like the explicit-region kernel.
268 Eigen::MatrixXd Z(F.rows(), 3 * n);
269 for (Index j = 0; j < n; ++j) {
270 const Eigen::Matrix3d alpha = sites[std::size_t(j)]->getPInv().inverse();
271 const Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(alpha / epsilon);
272 const Eigen::Matrix3d sqrt_alpha = es.operatorSqrt();
273 Z.middleCols<3>(3 * j) = F.middleCols<3>(3 * j) * sqrt_alpha;
274 }
275 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(F.rows(), F.rows());
276 B.selfadjointView<Eigen::Lower>().rankUpdate(Z, -1.0);
277 return B.selfadjointView<Eigen::Lower>();
278}
279
281 const Eigen::MatrixXd& B, const Eigen::MatrixXd& T) {
282 if (B.rows() != T.rows() || B.cols() != T.rows()) {
283 throw std::runtime_error(
284 "EnvironmentScreening::SymmetrizedReactionField: B is " +
285 std::to_string(B.rows()) + "x" + std::to_string(B.cols()) +
286 " but the metric T has " + std::to_string(T.rows()) +
287 " rows. Both must be over the same auxiliary basis.");
288 }
289 Eigen::MatrixXd R = T.transpose() * B * T;
290 // Exactly symmetric, so the eigensolvers downstream see what they expect.
291 R = 0.5 * (R + R.transpose()).eval();
292
293 const Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(
294 R, Eigen::EigenvaluesOnly);
295 const double lowest = es.eigenvalues().minCoeff();
296 if (!(lowest > -1.0)) {
297 throw std::runtime_error(
298 "EnvironmentScreening::SymmetrizedReactionField: the lowest "
299 "eigenvalue of R is " +
300 std::to_string(lowest) +
301 ", so 1 + R, the effective interaction v + v_reac in this metric, "
302 "is not positive definite. The environment would screen a charge "
303 "fluctuation by more than the fluctuation itself.");
304 }
305 return R;
306}
307
309 const std::vector<PolarSegment>& segments, double site_width) {
310 if (!(site_width >= 0.0)) {
311 throw std::runtime_error(
312 "EnvironmentScreening::SiteWidths: site_width must be >= 0, got " +
313 std::to_string(site_width) + ".");
314 }
315 std::vector<double> widths;
316 for (const PolarSite* site : SitesOf(segments)) {
317 const double alpha_iso = site->getPInv().inverse().trace() / 3.0;
318 widths.push_back(site_width * std::cbrt(alpha_iso));
319 }
320 return widths;
321}
322
323Eigen::MatrixXd EnvironmentScreening::Kernel(const AOBasis& auxbasis,
324 const ScreeningEnvironment& env) {
325 auto positions = [](const std::vector<PolarSegment>& segs) {
326 std::vector<Eigen::Vector3d> pos;
327 for (const PolarSite* site : SitesOf(segs)) {
328 pos.push_back(site->getPos());
329 }
330 return pos;
331 };
332 const Index naux = auxbasis.AOBasisSize();
333 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
334 if (!env.explicit_segments.empty()) {
335 const Eigen::MatrixXd F =
336 AuxFieldAtPoints(auxbasis, positions(env.explicit_segments),
339 }
340 if (!env.shell_segments.empty()) {
341 const Eigen::MatrixXd F =
342 AuxFieldAtPoints(auxbasis, positions(env.shell_segments),
345 }
346 return B;
347}
348
349Eigen::MatrixXd EnvironmentScreening::DressingMatrix(const Eigen::MatrixXd& R) {
350 const Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(R);
351 const double lowest = es.eigenvalues().minCoeff();
352 if (!(lowest > -1.0)) {
353 throw std::runtime_error(
354 "EnvironmentScreening::DressingMatrix: 1 + R is not positive "
355 "definite (lowest eigenvalue of R " +
356 std::to_string(lowest) + ").");
357 }
358 return es.eigenvectors() *
359 (1.0 + es.eigenvalues().array()).sqrt().matrix().asDiagonal() *
360 es.eigenvectors().transpose();
361}
362
363Eigen::MatrixXd EnvironmentScreening::Metric(const AOBasis& auxbasis) {
364 AOCoulomb coulomb;
365 coulomb.Fill(auxbasis);
366 AOOverlap overlap;
367 overlap.Fill(auxbasis);
368 return coulomb.Pseudo_InvSqrt_GWBSE(overlap,
370}
371
372namespace {
373// Net charge of every auxiliary function, integral chi_Q d^3r: an overlap
374// integral against the unit shell, in the order and normalization of the
375// three-centre integrals (same shells, same offsets).
376Eigen::VectorXd AuxCharges(const AOBasis& auxbasis) {
377 const std::vector<libint2::Shell> shells = auxbasis.GenerateLibintBasis();
378 const std::vector<Index> shell2bf = auxbasis.getMapToBasisFunctions();
379 Eigen::VectorXd q = Eigen::VectorXd::Zero(auxbasis.AOBasisSize());
380 libint2::Engine engine(libint2::Operator::overlap,
381 int(auxbasis.getMaxNprim()), int(auxbasis.getMaxL()),
382 0);
383 const libint2::Engine::target_ptr_vec& buf = engine.results();
384 for (std::size_t sh = 0; sh < shells.size(); ++sh) {
385 engine.compute(shells[sh], libint2::Shell::unit());
386 if (buf[0] == nullptr) {
387 continue;
388 }
389 for (std::size_t f = 0; f < shells[sh].size(); ++f) {
390 q(shell2bf[sh] + Index(f)) = buf[0][f];
391 }
392 }
393 return q;
394}
395
396struct SiteInfo {
397 const PolarSite* site;
398 Index segment;
399 bool shell;
400 double alpha; // isotropic, bohr^3
401 double d_qm; // to the nearest QM atom, bohr
402 Index qm_atom; // that atom's position in the QM molecule
403};
404} // namespace
405
407 const QMMolecule& atoms,
408 const ScreeningEnvironment& env,
409 const Eigen::MatrixXd& T,
410 Index n_modes) {
411 const double b2a = tools::conv::bohr2ang;
412 ScreeningCheck out;
413 std::ostringstream rep;
414 rep << std::fixed;
415
416 // Every site, explicit first then shell, with its distance to the QM
417 // molecule -- the quantity the failure mode depends on.
418 std::vector<SiteInfo> info;
419 auto collect = [&](const std::vector<PolarSegment>& segs, bool shell) {
420 for (const PolarSegment& seg : segs) {
421 for (const PolarSite& site : seg) {
422 SiteInfo si{&site,
423 seg.getId(),
424 shell,
425 3.0 / site.getPInv().trace(),
426 std::numeric_limits<double>::max(),
427 -1};
428 for (Index a = 0; a < atoms.size(); ++a) {
429 const double d = (atoms[a].getPos() - site.getPos()).norm();
430 if (d < si.d_qm) {
431 si.d_qm = d;
432 si.qm_atom = a; // position, as AOShell::getAtomIndex counts
433 }
434 }
435 info.push_back(si);
436 }
437 }
438 };
439 collect(env.explicit_segments, false);
440 collect(env.shell_segments, true);
441 const Index n_explicit = Index(SitesOf(env.explicit_segments).size());
442
443 auto describe_site = [&](const SiteInfo& si) {
444 std::ostringstream o;
445 o << std::fixed << std::setprecision(2) << "segment " << si.segment << " "
446 << si.site->getElement() << " (alpha " << si.alpha << " bohr^3"
447 << (si.shell ? ", shell" : "") << ") at " << si.d_qm * b2a
448 << " A from QM atom " << si.qm_atom << " "
449 << atoms[si.qm_atom].getElement();
450 return o.str();
451 };
452
453 // Geometry: how close does the environment come?
454 {
455 std::vector<std::size_t> order(info.size());
456 for (std::size_t i = 0; i < order.size(); ++i) {
457 order[i] = i;
458 }
459 std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
460 return info[a].d_qm < info[b].d_qm;
461 });
462 rep << " " << info.size() << " polar sites around " << atoms.size()
463 << " QM atoms; sites within";
464 for (double r : {1.5, 2.0, 2.5, 3.0}) {
465 const Index n = std::count_if(info.begin(), info.end(), [&](auto& si) {
466 return si.d_qm * b2a < r;
467 });
468 rep << std::setprecision(1) << " " << r << " A: " << n << ",";
469 }
470 rep << "\n closest sites:\n";
471 for (std::size_t k = 0; k < std::min<std::size_t>(5, order.size()); ++k) {
472 rep << " " << describe_site(info[order[k]]) << "\n";
473 }
474 }
475
476 // B and R exactly as Kernel and SymmetrizedReactionField build them,
477 // keeping the pieces needed to take the lowest modes apart.
478 const Index naux = auxbasis.AOBasisSize();
479 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
480 Eigen::MatrixXd F_exp, F_sh;
481 Eigen::LLT<Eigen::MatrixXd> llt;
482 {
483 std::vector<Eigen::Vector3d> pos;
484 for (const SiteInfo& si : info) {
485 if (!si.shell) {
486 pos.push_back(si.site->getPos());
487 }
488 }
489 if (!pos.empty()) {
490 F_exp = AuxFieldAtPoints(
491 auxbasis, pos, SiteWidths(env.explicit_segments, env.site_width));
492 llt.compute(AssembleThole(SitesOf(env.explicit_segments), env.exp_damp));
493 if (llt.info() != Eigen::Success) {
494 out.lowest = -std::numeric_limits<double>::infinity();
495 out.report = rep.str() +
496 " The Thole matrix of the explicit polar sites is not "
497 "positive definite: a polarization catastrophe inside "
498 "the environment itself, independent of the QM region.\n";
499 return out;
500 }
501 const Eigen::MatrixXd Y =
502 llt.matrixL().solve(Eigen::MatrixXd(F_exp.transpose()));
503 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
504 B = B.selfadjointView<Eigen::Lower>();
505 }
506 }
507 if (!env.shell_segments.empty()) {
508 std::vector<Eigen::Vector3d> pos;
509 for (const SiteInfo& si : info) {
510 if (si.shell) {
511 pos.push_back(si.site->getPos());
512 }
513 }
514 F_sh = AuxFieldAtPoints(auxbasis, pos,
516 B += ShellKernel(F_sh, env.shell_segments, env.shell_dielectric);
517 }
518
519 Eigen::MatrixXd R = T.transpose() * B * T;
520 R = 0.5 * (R + R.transpose()).eval();
521 const Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(R);
522 const Eigen::VectorXd& lam = es.eigenvalues();
523 out.lowest = lam(0);
524 out.n_unstable = (lam.array() <= -1.0).count();
525 rep << std::setprecision(2) << " site_width " << env.site_width
526 << (env.site_width > 0.0 ? " alpha^(1/3)" : " (point sites)") << "\n";
527 rep << std::setprecision(4) << " lowest eigenvalues of R (must be > -1):";
528 for (Index m = 0; m < std::min<Index>(6, lam.size()); ++m) {
529 rep << " " << lam(m);
530 }
531 rep << "\n " << out.n_unstable << " at or below -1, "
532 << (lam.array() < -0.5).count() << " below -0.5, of " << lam.size()
533 << "\n";
534
535 // The lowest modes. Column w of the eigenvectors is in the metric of
536 // the three-centre integrals; c = T w are its coefficients over the
537 // auxiliary functions as charge densities, rho = sum_Q c_Q chi_Q, with
538 // c^T V c = 1.
539 const Eigen::VectorXd q_aux = AuxCharges(auxbasis);
540 for (Index m = 0; m < std::min<Index>(n_modes, lam.size()); ++m) {
541 const Eigen::VectorXd c = T * es.eigenvectors().col(m);
542 rep << std::setprecision(4) << " mode " << m << ": lambda " << lam(m)
543 << ", net charge " << c.dot(q_aux)
544 << " (in units where its Coulomb self energy is 1/2)\n";
545
546 // Where the charge sits: squared coefficients by QM atom, and the
547 // three auxiliary shells carrying most of them.
548 const double norm = c.squaredNorm();
549 std::vector<double> by_atom(atoms.size(), 0.0);
550 std::vector<std::pair<double, std::string>> by_shell;
551 for (const AOShell& shell : auxbasis) {
552 const double w =
553 c.segment(shell.getStartIndex(), shell.getNumFunc()).squaredNorm() /
554 norm;
555 by_atom[std::size_t(shell.getAtomIndex())] += w;
556 std::ostringstream o;
557 o << std::setprecision(3) << "atom " << shell.getAtomIndex() << " "
558 << atoms[shell.getAtomIndex()].getElement() << " "
559 << EnumToString(shell.getL()) << " exp " << shell.getMinDecay();
560 by_shell.push_back({w, o.str()});
561 }
562 std::sort(by_shell.rbegin(), by_shell.rend());
563 rep << " aux shells:";
564 for (std::size_t k = 0; k < std::min<std::size_t>(3, by_shell.size());
565 ++k) {
566 rep << std::setprecision(2) << " " << by_shell[k].second << " ("
567 << 100 * by_shell[k].first << "%);";
568 }
569 rep << "\n";
570
571 // Which sites carry its reaction: g = F^T c is the mode's field at the
572 // sites, mu its induced dipoles, and -c^T B c = sum_j g_j . mu_j.
573 std::vector<double> e_site(info.size(), 0.0);
574 double e_total = 0.0;
575 if (F_exp.size() > 0) {
576 const Eigen::VectorXd g = F_exp.transpose() * c;
577 const Eigen::VectorXd mu = llt.solve(g);
578 for (Index j = 0; j < n_explicit; ++j) {
579 e_site[std::size_t(j)] = g.segment<3>(3 * j).dot(mu.segment<3>(3 * j));
580 }
581 }
582 if (F_sh.size() > 0) {
583 const Eigen::VectorXd g = F_sh.transpose() * c;
584 for (Index j = 0; j < Index(info.size()) - n_explicit; ++j) {
585 const SiteInfo& si = info[std::size_t(n_explicit + j)];
586 const Eigen::Matrix3d alpha = si.site->getPInv().inverse();
587 e_site[std::size_t(n_explicit + j)] =
588 g.segment<3>(3 * j).dot(alpha * g.segment<3>(3 * j)) /
590 }
591 }
592 double e_near = 0.0;
593 for (std::size_t j = 0; j < info.size(); ++j) {
594 e_total += e_site[j];
595 if (info[j].d_qm * b2a < 3.0) {
596 e_near += e_site[j];
597 }
598 }
599 std::vector<std::size_t> order(info.size());
600 for (std::size_t j = 0; j < order.size(); ++j) {
601 order[j] = j;
602 }
603 std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
604 return std::abs(e_site[a]) > std::abs(e_site[b]);
605 });
606 rep << std::setprecision(1) << " " << 100 * e_near / e_total
607 << "% of its reaction from sites within 3 A of the QM atoms; "
608 "largest:\n";
609 for (std::size_t k = 0; k < std::min<std::size_t>(5, order.size()); ++k) {
610 rep << " " << std::setprecision(1)
611 << 100 * e_site[order[k]] / e_total << "% "
612 << describe_site(info[order[k]]) << "\n";
613 }
614 }
615 out.report = rep.str();
616 return out;
617}
618
619} // namespace xtp
620} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index getMaxL() const
Definition aobasis.cc:29
Index AOBasisSize() const
Definition aobasis.h:46
std::vector< Index > getMapToBasisFunctions() const
Definition aobasis.cc:45
Index getMaxNprim() const
Definition aobasis.cc:37
std::vector< libint2::Shell > GenerateLibintBasis() const
Definition aobasis.cc:119
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd Pseudo_InvSqrt_GWBSE(const AOOverlap &auxoverlap, double etol)
Definition aomatrix.cc:53
void Fill(const AOBasis &aobasis) final
const Eigen::Vector3d & getPos() const
static Eigen::MatrixXd ShellKernel(const Eigen::MatrixXd &F, const std::vector< PolarSegment > &segments, double epsilon)
The tail beyond the explicit region: legacy's radial_dielectric.
static Eigen::MatrixXd Metric(const AOBasis &auxbasis)
T for an auxiliary basis on its own: the metric TCMatrix_gwbse::Fill folds into the three-centre inte...
static Eigen::MatrixXd Kernel(const AOBasis &auxbasis, const ScreeningEnvironment &env)
B for a whole environment: ReactionFieldKernel of the explicit segments plus ShellKernel of the shell...
static Eigen::MatrixXd DressingMatrix(const Eigen::MatrixXd &R)
S = (1 + R)^(1/2), the dressing of the auxiliary index that turns the bare interaction into u = v + v...
static ScreeningCheck Check(const AOBasis &auxbasis, const QMMolecule &atoms, const ScreeningEnvironment &env, const Eigen::MatrixXd &T, Index n_modes=3)
Builds R for env and reports whether 1 + R is positive definite, and why not.
static Eigen::MatrixXd ReactionFieldKernel(const Eigen::MatrixXd &F, const std::vector< PolarSegment > &segments, double exp_damp)
B = -F A^-1 F^T for an explicit Thole region.
static std::vector< double > SiteWidths(const std::vector< PolarSegment > &segments, double site_width)
R per site, in bohr: site_width * alpha_iso^(1/3), with alpha_iso = tr(alpha)/3.
static Eigen::MatrixXd AuxFieldAtPoints(const AOBasis &auxbasis, const std::vector< Eigen::Vector3d > &points, const std::vector< double > &widths={})
F: the electric field at each point produced by each auxiliary basis function, taken as a charge dens...
static Eigen::MatrixXd SymmetrizedReactionField(const Eigen::MatrixXd &B, const Eigen::MatrixXd &T)
R = T^T B T, the reaction field in the metric of the stored three-centre integrals,...
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
static constexpr double metric_tolerance
Mediates interaction between polar and static sites.
const double bohr2ang
Definition constants.h:49
Index getMaxThreads()
Definition eigen.h:128
Index getThreadId()
Definition eigen.h:143
std::string EnumToString(L l)
Definition basisset.cc:60
ClassicalSegment< PolarSite > PolarSegment
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
Outcome of EnvironmentScreening::Check: whether 1 + R is positive definite, and a human-readable acco...
The polarizable environment a GW-BSE calculation is screened by: what a QM/MM job hands to GWBSE (GWB...
std::vector< PolarSegment > explicit_segments
std::vector< PolarSegment > shell_segments