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"
37
38// Local VOTCA includes (charge response)
40
41namespace votca {
42namespace xtp {
43
44namespace {
45// The polarizable sites in the order ReactionFieldKernel and ShellKernel
46// index them, and the order AuxFieldAtPoints must be called with.
47std::vector<const PolarSite*> SitesOf(
48 const std::vector<PolarSegment>& segments) {
49 std::vector<const PolarSite*> sites;
50 for (const PolarSegment& seg : segments) {
51 for (const PolarSite& site : seg) {
52 sites.push_back(&site);
53 }
54 }
55 return sites;
56}
57
58void CheckShape(const Eigen::MatrixXd& F, Index n_sites,
59 const std::string& who) {
60 if (F.cols() != 3 * n_sites) {
61 throw std::runtime_error(
62 "EnvironmentScreening::" + who + ": F has " + std::to_string(F.cols()) +
63 " columns for " + std::to_string(n_sites) +
64 " polarizable sites; expected " + std::to_string(3 * n_sites) +
65 ". F must be AuxFieldAtPoints at exactly these sites, in order.");
66 }
67}
68// A = alpha^-1 + T_Thole, block for block as
69// DipoleDipoleInteraction::multiply applies it: PInv on the diagonal,
70// FillTholeInteraction(i, j) above and its transpose below. Every pair,
71// intramolecular ones included, exactly as PolarRegion's CG sees it.
72Eigen::MatrixXd AssembleThole(const std::vector<const PolarSite*>& sites,
73 double exp_damp) {
74 const Index n = Index(sites.size());
75 const eeInteractor interactor(exp_damp);
76 Eigen::MatrixXd A = Eigen::MatrixXd::Zero(3 * n, 3 * n);
77#pragma omp parallel for schedule(dynamic)
78 for (Index i = 0; i < n; ++i) {
79 A.block<3, 3>(3 * i, 3 * i) = sites[std::size_t(i)]->getPInv();
80 for (Index j = i + 1; j < n; ++j) {
81 const Eigen::Matrix3d block = interactor.FillTholeInteraction(
82 *sites[std::size_t(i)], *sites[std::size_t(j)]);
83 A.block<3, 3>(3 * i, 3 * j) = block;
84 A.block<3, 3>(3 * j, 3 * i) = block.transpose();
85 }
86 }
87 return A;
88}
89bool HasChargeResponse(const std::vector<PolarSegment>& segments) {
90 return std::any_of(
91 segments.begin(), segments.end(),
92 [](const PolarSegment& seg) { return seg.hasChargeResponse(); });
93}
94
95// Positions and widths of the charge-response sites, in order
96void ChargeResponsePoints(const std::vector<PolarSegment>& segments,
97 std::vector<Eigen::Vector3d>& points,
98 std::vector<double>& widths) {
99 for (const PolarSite* site : SitesOf(segments)) {
100 if (site->isChargeResponse()) {
101 points.push_back(site->getPos());
102 widths.push_back(site->CRWidth());
103 }
104 }
105}
106
107// The pieces of ChargeResponseKernel: M^1/2 C and the Cholesky factor of
108// A = 1 + M^1/2 T M^1/2 (ok() false if A is not positive definite)
109struct ChargeResponseSystem {
110 Eigen::MatrixXd MhC;
111 CholeskyFactor llt;
112};
113
114ChargeResponseSystem BuildChargeResponseSystem(
115 const Eigen::MatrixXd& F, const Eigen::MatrixXd& Phi,
116 const std::vector<PolarSegment>& segments, double exp_damp) {
117 std::vector<PolarSegment> segs = segments; // the solver needs its own
118 const eeInteractor interactor(exp_damp);
119 ChargeResponseSolver solver(segs, interactor);
120 const Index nq = solver.NumCharges();
121 const Index ns = Index(SitesOf(segments).size());
122 if (Phi.cols() != nq || Phi.rows() != F.rows()) {
123 throw std::runtime_error(
124 "EnvironmentScreening::ChargeResponseKernel: Phi is " +
125 std::to_string(Phi.rows()) + "x" + std::to_string(Phi.cols()) +
126 ", expected " + std::to_string(F.rows()) + "x" + std::to_string(nq) +
127 " (AuxPotentialAtPoints at the charge-response sites, in order).");
128 }
129 // sources c = (-phi, E) per auxiliary function, unknowns in the solver's
130 // order: charges first (site order), then 3 dipole components per site
131 Eigen::MatrixXd C(solver.size(), F.rows());
132 C.topRows(nq) = -Phi.transpose();
133 C.bottomRows(3 * ns) = F.transpose();
134 ChargeResponseSystem sys;
135 sys.MhC = solver.ApplyMhalf(C);
136 sys.llt.compute(solver.DenseOperator());
137 return sys;
138}
139} // namespace
140
142 const Eigen::MatrixXd& F, const Eigen::MatrixXd& Phi,
143 const std::vector<PolarSegment>& segments, double exp_damp) {
144 const Index n = Index(SitesOf(segments).size());
145 CheckShape(F, n, "ChargeResponseKernel");
146 if (n == 0) {
147 return Eigen::MatrixXd::Zero(F.rows(), F.rows());
148 }
149 const ChargeResponseSystem sys =
150 BuildChargeResponseSystem(F, Phi, segments, exp_damp);
151 if (!sys.llt.ok()) {
152 throw std::runtime_error(
153 "EnvironmentScreening::ChargeResponseKernel: 1 + M^1/2 T M^1/2 of "
154 "the polar region (" +
155 std::to_string(n) +
156 " sites, charge response) is not positive definite: a polarization "
157 "catastrophe, no stable response to screen with.");
158 }
159 // B = -(L^-1 M^1/2 C)^T (L^-1 M^1/2 C)
160 const Eigen::MatrixXd Y = sys.llt.SolveL(sys.MhC);
161 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(F.rows(), F.rows());
162 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
163 return B.selfadjointView<Eigen::Lower>();
164}
165
167 const Eigen::MatrixXd& F, const std::vector<PolarSegment>& segments,
168 double exp_damp) {
169 const std::vector<const PolarSite*> sites = SitesOf(segments);
170 const Index n = Index(sites.size());
171 CheckShape(F, n, "ReactionFieldKernel");
172 if (n == 0) {
173 return Eigen::MatrixXd::Zero(F.rows(), F.rows());
174 }
175
176 const Eigen::MatrixXd A = AssembleThole(sites, exp_damp);
177 const CholeskyFactor llt(A);
178 if (!llt.ok()) {
179 throw std::runtime_error(
180 "EnvironmentScreening::ReactionFieldKernel: the Thole interaction "
181 "matrix of the polar region (" +
182 std::to_string(n) +
183 " sites) is not positive definite. That is a polarization "
184 "catastrophe -- sites too close together for their damping -- and "
185 "there is no stable induced-dipole response for it to screen with.");
186 }
187
188 // B = -F A^-1 F^T = -(L^-1 F^T)^T (L^-1 F^T): symmetric and negative
189 // semidefinite by construction.
190 const Eigen::MatrixXd Y = llt.SolveL(F.transpose());
191 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(F.rows(), F.rows());
192 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
193 return B.selfadjointView<Eigen::Lower>();
194}
195
197 const Eigen::MatrixXd& F, const std::vector<PolarSegment>& segments,
198 double epsilon) {
199 const std::vector<const PolarSite*> sites = SitesOf(segments);
200 const Index n = Index(sites.size());
201 CheckShape(F, n, "ShellKernel");
202 if (!(epsilon >= 1.0)) {
203 throw std::runtime_error(
204 "EnvironmentScreening::ShellKernel: epsilon must be at least 1 "
205 "(it screens the QM field; below 1 it would amplify it). Got " +
206 std::to_string(epsilon) + ".");
207 }
208
209 // B_shell = - sum_j F_j (alpha_j / epsilon) F_j^T. Written as -Z Z^T with
210 // Z_j = F_j (alpha_j / epsilon)^(1/2), so it is symmetric and negative
211 // semidefinite by construction, like the explicit-region kernel.
212 Eigen::MatrixXd Z(F.rows(), 3 * n);
213 for (Index j = 0; j < n; ++j) {
214 const Eigen::Matrix3d alpha = sites[std::size_t(j)]->getPInv().inverse();
215 const Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(alpha / epsilon);
216 const Eigen::Matrix3d sqrt_alpha = es.operatorSqrt();
217 Z.middleCols<3>(3 * j) = F.middleCols<3>(3 * j) * sqrt_alpha;
218 }
219 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(F.rows(), F.rows());
220 B.selfadjointView<Eigen::Lower>().rankUpdate(Z, -1.0);
221 return B.selfadjointView<Eigen::Lower>();
222}
223
225 const Eigen::MatrixXd& B, const Eigen::MatrixXd& T) {
226 if (B.rows() != T.rows() || B.cols() != T.rows()) {
227 throw std::runtime_error(
228 "EnvironmentScreening::SymmetrizedReactionField: B is " +
229 std::to_string(B.rows()) + "x" + std::to_string(B.cols()) +
230 " but the metric T has " + std::to_string(T.rows()) +
231 " rows. Both must be over the same auxiliary basis.");
232 }
233 Eigen::MatrixXd R = T.transpose() * B * T;
234 // Exactly symmetric, so the eigensolvers downstream see what they expect.
235 R = 0.5 * (R + R.transpose()).eval();
236
237 const SymmetricEigenSystem es = SymmetricEigen(R, false);
238 const double lowest = es.values.minCoeff();
239 if (!(lowest > -1.0)) {
240 throw std::runtime_error(
241 "EnvironmentScreening::SymmetrizedReactionField: the lowest "
242 "eigenvalue of R is " +
243 std::to_string(lowest) +
244 ", so 1 + R, the effective interaction v + v_reac in this metric, "
245 "is not positive definite. The environment would screen a charge "
246 "fluctuation by more than the fluctuation itself.");
247 }
248 return R;
249}
250
252 const std::vector<PolarSegment>& segments, double site_width) {
253 if (!(site_width >= 0.0)) {
254 throw std::runtime_error(
255 "EnvironmentScreening::SiteWidths: site_width must be >= 0, got " +
256 std::to_string(site_width) + ".");
257 }
258 std::vector<double> widths;
259 for (const PolarSite* site : SitesOf(segments)) {
260 if (site->isChargeResponse()) {
261 widths.push_back(site->CRWidth());
262 continue;
263 }
264 const double alpha_iso = site->getPInv().inverse().trace() / 3.0;
265 widths.push_back(site_width * std::cbrt(alpha_iso));
266 }
267 return widths;
268}
269
270Eigen::MatrixXd EnvironmentScreening::Kernel(const AOBasis& auxbasis,
271 const ScreeningEnvironment& env) {
272 auto positions = [](const std::vector<PolarSegment>& segs) {
273 std::vector<Eigen::Vector3d> pos;
274 for (const PolarSite* site : SitesOf(segs)) {
275 pos.push_back(site->getPos());
276 }
277 return pos;
278 };
279 const Index naux = auxbasis.AOBasisSize();
280 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
281 if (!env.explicit_segments.empty()) {
282 const Eigen::MatrixXd F =
283 AuxFieldAtPoints(auxbasis, positions(env.explicit_segments),
285 if (HasChargeResponse(env.explicit_segments)) {
286 std::vector<Eigen::Vector3d> cr_points;
287 std::vector<double> cr_widths;
288 ChargeResponsePoints(env.explicit_segments, cr_points, cr_widths);
289 const Eigen::MatrixXd Phi =
290 AuxPotentialAtPoints(auxbasis, cr_points, cr_widths);
292 } else {
294 }
295 }
296 if (!env.shell_segments.empty()) {
297 const Eigen::MatrixXd F =
298 AuxFieldAtPoints(auxbasis, positions(env.shell_segments),
301 }
302 return B;
303}
304
305Eigen::MatrixXd EnvironmentScreening::DressingMatrix(const Eigen::MatrixXd& R) {
307 const double lowest = es.values.minCoeff();
308 if (!(lowest > -1.0)) {
309 throw std::runtime_error(
310 "EnvironmentScreening::DressingMatrix: 1 + R is not positive "
311 "definite (lowest eigenvalue of R " +
312 std::to_string(lowest) + ").");
313 }
314 return es.vectors * (1.0 + es.values.array()).sqrt().matrix().asDiagonal() *
315 es.vectors.transpose();
316}
317
318Eigen::MatrixXd EnvironmentScreening::Metric(const AOBasis& auxbasis) {
319 AOCoulomb coulomb;
320 coulomb.Fill(auxbasis);
321 AOOverlap overlap;
322 overlap.Fill(auxbasis);
323 return coulomb.Pseudo_InvSqrt_GWBSE(overlap,
325}
326
327namespace {
328struct SiteInfo {
329 const PolarSite* site;
330 Index segment;
331 bool shell;
332 double alpha; // isotropic, bohr^3
333 double d_qm; // to the nearest QM atom, bohr
334 Index qm_atom; // that atom's position in the QM molecule
335};
336} // namespace
337
339 const QMMolecule& atoms,
340 const ScreeningEnvironment& env,
341 const Eigen::MatrixXd& T,
342 Index n_modes) {
343 const double b2a = tools::conv::bohr2ang;
344 ScreeningCheck out;
345 std::ostringstream rep;
346 rep << std::fixed;
347
348 // Every site, explicit first then shell, with its distance to the QM
349 // molecule -- the quantity the failure mode depends on.
350 std::vector<SiteInfo> info;
351 auto collect = [&](const std::vector<PolarSegment>& segs, bool shell) {
352 for (const PolarSegment& seg : segs) {
353 for (const PolarSite& site : seg) {
354 SiteInfo si{&site,
355 seg.getId(),
356 shell,
357 3.0 / site.getPInv().trace(),
358 std::numeric_limits<double>::max(),
359 -1};
360 for (Index a = 0; a < atoms.size(); ++a) {
361 const double d = (atoms[a].getPos() - site.getPos()).norm();
362 if (d < si.d_qm) {
363 si.d_qm = d;
364 si.qm_atom = a; // position, as AOShell::getAtomIndex counts
365 }
366 }
367 info.push_back(si);
368 }
369 }
370 };
371 collect(env.explicit_segments, false);
372 collect(env.shell_segments, true);
373 const Index n_explicit = Index(SitesOf(env.explicit_segments).size());
374
375 auto describe_site = [&](const SiteInfo& si) {
376 std::ostringstream o;
377 o << std::fixed << std::setprecision(2) << "segment " << si.segment << " "
378 << si.site->getElement() << " (alpha " << si.alpha << " bohr^3"
379 << (si.shell ? ", shell" : "") << ") at " << si.d_qm * b2a
380 << " A from QM atom " << si.qm_atom << " "
381 << atoms[si.qm_atom].getElement();
382 return o.str();
383 };
384
385 // Geometry: how close does the environment come?
386 {
387 std::vector<std::size_t> order(info.size());
388 for (std::size_t i = 0; i < order.size(); ++i) {
389 order[i] = i;
390 }
391 std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
392 return info[a].d_qm < info[b].d_qm;
393 });
394 rep << " " << info.size() << " polar sites around " << atoms.size()
395 << " QM atoms; sites within";
396 for (double r : {1.5, 2.0, 2.5, 3.0}) {
397 const Index n = std::count_if(info.begin(), info.end(), [&](auto& si) {
398 return si.d_qm * b2a < r;
399 });
400 rep << std::setprecision(1) << " " << r << " A: " << n << ",";
401 }
402 rep << "\n closest sites:\n";
403 for (std::size_t k = 0; k < std::min<std::size_t>(5, order.size()); ++k) {
404 rep << " " << describe_site(info[order[k]]) << "\n";
405 }
406 }
407
408 // B and R exactly as Kernel and SymmetrizedReactionField build them,
409 // keeping the pieces needed to take the lowest modes apart.
410 const Index naux = auxbasis.AOBasisSize();
411 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
412 Eigen::MatrixXd F_exp, F_sh;
413 CholeskyFactor llt;
414 ChargeResponseSystem cr_sys;
415 bool have_cr = false;
416 {
417 std::vector<Eigen::Vector3d> pos;
418 for (const SiteInfo& si : info) {
419 if (!si.shell) {
420 pos.push_back(si.site->getPos());
421 }
422 }
423 if (!pos.empty() && HasChargeResponse(env.explicit_segments)) {
424 F_exp = AuxFieldAtPoints(
425 auxbasis, pos, SiteWidths(env.explicit_segments, env.site_width));
426 std::vector<Eigen::Vector3d> cr_points;
427 std::vector<double> cr_widths;
428 ChargeResponsePoints(env.explicit_segments, cr_points, cr_widths);
429 cr_sys = BuildChargeResponseSystem(
430 F_exp, AuxPotentialAtPoints(auxbasis, cr_points, cr_widths),
431 env.explicit_segments, env.exp_damp);
432 if (!cr_sys.llt.ok()) {
433 out.lowest = -std::numeric_limits<double>::infinity();
434 out.report = rep.str() +
435 " 1 + M^1/2 T M^1/2 of the explicit polar sites (charge "
436 "response) is not positive definite: a polarization "
437 "catastrophe inside the environment itself.\n";
438 return out;
439 }
440 const Eigen::MatrixXd Y = cr_sys.llt.SolveL(cr_sys.MhC);
441 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
442 B = B.selfadjointView<Eigen::Lower>();
443 have_cr = true;
444 } else if (!pos.empty()) {
445 F_exp = AuxFieldAtPoints(
446 auxbasis, pos, SiteWidths(env.explicit_segments, env.site_width));
447 llt.compute(AssembleThole(SitesOf(env.explicit_segments), env.exp_damp));
448 if (!llt.ok()) {
449 out.lowest = -std::numeric_limits<double>::infinity();
450 out.report = rep.str() +
451 " The Thole matrix of the explicit polar sites is not "
452 "positive definite: a polarization catastrophe inside "
453 "the environment itself, independent of the QM region.\n";
454 return out;
455 }
456 const Eigen::MatrixXd Y = llt.SolveL(F_exp.transpose());
457 B.selfadjointView<Eigen::Lower>().rankUpdate(Y.transpose(), -1.0);
458 B = B.selfadjointView<Eigen::Lower>();
459 }
460 }
461 if (!env.shell_segments.empty()) {
462 std::vector<Eigen::Vector3d> pos;
463 for (const SiteInfo& si : info) {
464 if (si.shell) {
465 pos.push_back(si.site->getPos());
466 }
467 }
468 F_sh = AuxFieldAtPoints(auxbasis, pos,
470 B += ShellKernel(F_sh, env.shell_segments, env.shell_dielectric);
471 }
472
473 Eigen::MatrixXd R = T.transpose() * B * T;
474 R = 0.5 * (R + R.transpose()).eval();
476 const Eigen::VectorXd& lam = es.values;
477 out.lowest = lam(0);
478 out.n_unstable = (lam.array() <= -1.0).count();
479 rep << std::setprecision(2) << " site_width " << env.site_width
480 << (env.site_width > 0.0 ? " alpha^(1/3)" : " (point sites)") << "\n";
481 rep << std::setprecision(4) << " lowest eigenvalues of R (must be > -1):";
482 for (Index m = 0; m < std::min<Index>(6, lam.size()); ++m) {
483 rep << " " << lam(m);
484 }
485 rep << "\n " << out.n_unstable << " at or below -1, "
486 << (lam.array() < -0.5).count() << " below -0.5, of " << lam.size()
487 << "\n";
488
489 // The lowest modes. Column w of the eigenvectors is in the metric of
490 // the three-centre integrals; c = T w are its coefficients over the
491 // auxiliary functions as charge densities, rho = sum_Q c_Q chi_Q, with
492 // c^T V c = 1.
493 const Eigen::VectorXd q_aux = AuxCharges(auxbasis);
494 for (Index m = 0; m < std::min<Index>(n_modes, lam.size()); ++m) {
495 const Eigen::VectorXd c = T * es.vectors.col(m);
496 rep << std::setprecision(4) << " mode " << m << ": lambda " << lam(m)
497 << ", net charge " << c.dot(q_aux)
498 << " (in units where its Coulomb self energy is 1/2)\n";
499
500 // Where the charge sits: squared coefficients by QM atom, and the
501 // three auxiliary shells carrying most of them.
502 const double norm = c.squaredNorm();
503 std::vector<double> by_atom(atoms.size(), 0.0);
504 std::vector<std::pair<double, std::string>> by_shell;
505 for (const AOShell& shell : auxbasis) {
506 const double w =
507 c.segment(shell.getStartIndex(), shell.getNumFunc()).squaredNorm() /
508 norm;
509 by_atom[std::size_t(shell.getAtomIndex())] += w;
510 std::ostringstream o;
511 o << std::setprecision(3) << "atom " << shell.getAtomIndex() << " "
512 << atoms[shell.getAtomIndex()].getElement() << " "
513 << EnumToString(shell.getL()) << " exp " << shell.getMinDecay();
514 by_shell.push_back({w, o.str()});
515 }
516 std::sort(by_shell.rbegin(), by_shell.rend());
517 rep << " aux shells:";
518 for (std::size_t k = 0; k < std::min<std::size_t>(3, by_shell.size());
519 ++k) {
520 rep << std::setprecision(2) << " " << by_shell[k].second << " ("
521 << 100 * by_shell[k].first << "%);";
522 }
523 rep << "\n";
524
525 // Which sites carry its reaction: g = F^T c is the mode's field at the
526 // sites, mu its induced dipoles, and -c^T B c = sum_j g_j . mu_j.
527 std::vector<double> e_site(info.size(), 0.0);
528 double e_total = 0.0;
529 if (have_cr) {
530 // -c^T B c = |L^-1 M^1/2 C c|^2 = sum_i (M^1/2 C c)_i (A^-1 M^1/2 C c)_i
531 // over the unknowns; summed per site (its charge and dipole)
532 const Eigen::VectorXd g = cr_sys.MhC * c;
533 const Eigen::VectorXd y = cr_sys.llt.Solve(g);
534 const Eigen::VectorXd gy = g.cwiseProduct(y);
535 const Index nq = gy.size() - 3 * n_explicit;
536 Index q = 0;
537 for (Index j = 0; j < n_explicit; ++j) {
538 e_site[std::size_t(j)] = gy.segment<3>(nq + 3 * j).sum();
539 if (info[std::size_t(j)].site->isChargeResponse()) {
540 e_site[std::size_t(j)] += gy(q++);
541 }
542 }
543 } else if (F_exp.size() > 0) {
544 const Eigen::VectorXd g = F_exp.transpose() * c;
545 const Eigen::VectorXd mu = llt.Solve(g);
546 for (Index j = 0; j < n_explicit; ++j) {
547 e_site[std::size_t(j)] = g.segment<3>(3 * j).dot(mu.segment<3>(3 * j));
548 }
549 }
550 if (F_sh.size() > 0) {
551 const Eigen::VectorXd g = F_sh.transpose() * c;
552 for (Index j = 0; j < Index(info.size()) - n_explicit; ++j) {
553 const SiteInfo& si = info[std::size_t(n_explicit + j)];
554 const Eigen::Matrix3d alpha = si.site->getPInv().inverse();
555 e_site[std::size_t(n_explicit + j)] =
556 g.segment<3>(3 * j).dot(alpha * g.segment<3>(3 * j)) /
558 }
559 }
560 double e_near = 0.0;
561 for (std::size_t j = 0; j < info.size(); ++j) {
562 e_total += e_site[j];
563 if (info[j].d_qm * b2a < 3.0) {
564 e_near += e_site[j];
565 }
566 }
567 std::vector<std::size_t> order(info.size());
568 for (std::size_t j = 0; j < order.size(); ++j) {
569 order[j] = j;
570 }
571 std::sort(order.begin(), order.end(), [&](std::size_t a, std::size_t b) {
572 return std::abs(e_site[a]) > std::abs(e_site[b]);
573 });
574 rep << std::setprecision(1) << " " << 100 * e_near / e_total
575 << "% of its reaction from sites within 3 A of the QM atoms; "
576 "largest:\n";
577 for (std::size_t k = 0; k < std::min<std::size_t>(5, order.size()); ++k) {
578 rep << " " << std::setprecision(1)
579 << 100 * e_site[order[k]] / e_total << "% "
580 << describe_site(info[order[k]]) << "\n";
581 }
582 }
583 out.report = rep.str();
584 return out;
585}
586
587} // namespace xtp
588} // namespace votca
Container to hold Basisfunctions for all atoms.
Definition aobasis.h:42
Index AOBasisSize() const
Definition aobasis.h:46
void Fill(const AOBasis &aobasis) final
Eigen::MatrixXd Pseudo_InvSqrt_GWBSE(const AOOverlap &auxoverlap, double etol)
Definition aomatrix.cc:54
void Fill(const AOBasis &aobasis) final
const Eigen::Vector3d & getPos() const
Induced charges and dipoles of a polar region with charge-response segments (Thole segments may be mi...
void compute(const Eigen::MatrixXd &A)
Eigen::MatrixXd Solve(const Eigen::MatrixXd &B) const
A^-1 B.
Eigen::MatrixXd SolveL(const Eigen::MatrixXd &B) const
L^-1 B.
static Eigen::VectorXd AuxCharges(const AOBasis &auxbasis)
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 AuxPotentialAtPoints(const AOBasis &auxbasis, const std::vector< Eigen::Vector3d > &points, const std::vector< double > &widths)
The electrostatic potential of each auxiliary function (as a charge density) at each point,...
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 Eigen::MatrixXd ChargeResponseKernel(const Eigen::MatrixXd &F, const Eigen::MatrixXd &Phi, const std::vector< PolarSegment > &segments, double exp_damp)
B for an explicit region with charge-response segments (Thole segments may be mixed in): the induced ...
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; for a charge-respons...
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
const Eigen::Matrix3d & getPInv() const
Definition polarsite.h:54
static constexpr double metric_tolerance
Mediates interaction between polar and static sites.
const double bohr2ang
Definition constants.h:49
Charge transport classes.
Definition ERIs.h:28
std::string EnumToString(L l)
Definition basisset.cc:60
SymmetricEigenSystem SymmetricEigen(const Eigen::MatrixXd &A, bool vectors=true)
With vectors = false only the eigenvalues are computed.
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