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.");
78 Eigen::MatrixXd::Zero(auxbasis.
AOBasisSize(), 3 * n_points);
79 if (n_points == 0 || shells.empty()) {
84 std::vector<libint2::Engine> engines(nthreads);
86 libint2::Engine(libint2::Operator::nuclear,
int(auxbasis.
getMaxNprim()),
88 for (
Index i = 1; i < nthreads; ++i) {
89 engines[i] = engines[0];
93 std::vector<libint2::Engine> coulomb(nthreads);
95 libint2::Engine(libint2::Operator::coulomb,
int(auxbasis.
getMaxNprim()),
97 coulomb[0].set(libint2::BraKet::xs_xs);
98 for (
Index i = 1; i < nthreads; ++i) {
99 coulomb[i] = coulomb[0];
104 const double h = 1
e-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)};
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)];
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)];
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) {
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;
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)];
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) {
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];
409 const Eigen::MatrixXd& T,
413 std::ostringstream rep;
418 std::vector<SiteInfo> info;
419 auto collect = [&](
const std::vector<PolarSegment>& segs,
bool shell) {
425 3.0 / site.getPInv().trace(),
426 std::numeric_limits<double>::max(),
428 for (
Index a = 0; a < atoms.
size(); ++a) {
429 const double d = (atoms[a].
getPos() - site.getPos()).norm();
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();
455 std::vector<std::size_t> order(info.size());
456 for (std::size_t i = 0; i < order.size(); ++i) {
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;
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;
468 rep << std::setprecision(1) <<
" " << r <<
" A: " << n <<
",";
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";
479 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
480 Eigen::MatrixXd F_exp, F_sh;
481 Eigen::LLT<Eigen::MatrixXd> llt;
483 std::vector<Eigen::Vector3d> pos;
484 for (
const SiteInfo& si : info) {
486 pos.push_back(si.site->getPos());
493 if (llt.info() != Eigen::Success) {
494 out.
lowest = -std::numeric_limits<double>::infinity();
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";
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>();
508 std::vector<Eigen::Vector3d> pos;
509 for (
const SiteInfo& si : info) {
511 pos.push_back(si.site->getPos());
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();
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);
531 rep <<
"\n " << out.
n_unstable <<
" at or below -1, "
532 << (lam.array() < -0.5).count() <<
" below -0.5, of " << lam.size()
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";
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) {
553 c.segment(shell.getStartIndex(), shell.getNumFunc()).squaredNorm() /
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()});
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());
566 rep << std::setprecision(2) <<
" " << by_shell[k].second <<
" ("
567 << 100 * by_shell[k].first <<
"%);";
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));
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)) /
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) {
599 std::vector<std::size_t> order(info.size());
600 for (std::size_t j = 0; j < order.size(); ++j) {
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]);
606 rep << std::setprecision(1) <<
" " << 100 * e_near / e_total
607 <<
"% of its reaction from sites within 3 A of the QM atoms; "
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";