341 const Eigen::MatrixXd& T,
345 std::ostringstream rep;
350 std::vector<SiteInfo> info;
351 auto collect = [&](
const std::vector<PolarSegment>& segs,
bool shell) {
357 3.0 / site.getPInv().trace(),
358 std::numeric_limits<double>::max(),
360 for (
Index a = 0; a < atoms.
size(); ++a) {
361 const double d = (atoms[a].
getPos() - site.getPos()).norm();
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();
387 std::vector<std::size_t> order(info.size());
388 for (std::size_t i = 0; i < order.size(); ++i) {
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;
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;
400 rep << std::setprecision(1) <<
" " << r <<
" A: " << n <<
",";
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";
411 Eigen::MatrixXd B = Eigen::MatrixXd::Zero(naux, naux);
412 Eigen::MatrixXd F_exp, F_sh;
414 ChargeResponseSystem cr_sys;
415 bool have_cr =
false;
417 std::vector<Eigen::Vector3d> pos;
418 for (
const SiteInfo& si : info) {
420 pos.push_back(si.site->getPos());
426 std::vector<Eigen::Vector3d> cr_points;
427 std::vector<double> cr_widths;
429 cr_sys = BuildChargeResponseSystem(
432 if (!cr_sys.llt.ok()) {
433 out.
lowest = -std::numeric_limits<double>::infinity();
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";
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>();
444 }
else if (!pos.empty()) {
449 out.
lowest = -std::numeric_limits<double>::infinity();
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";
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>();
462 std::vector<Eigen::Vector3d> pos;
463 for (
const SiteInfo& si : info) {
465 pos.push_back(si.site->getPos());
473 Eigen::MatrixXd R = T.transpose() * B * T;
474 R = 0.5 * (R + R.transpose()).eval();
476 const Eigen::VectorXd& lam = es.
values;
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);
485 rep <<
"\n " << out.
n_unstable <<
" at or below -1, "
486 << (lam.array() < -0.5).count() <<
" below -0.5, of " << lam.size()
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";
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) {
507 c.segment(shell.getStartIndex(), shell.getNumFunc()).squaredNorm() /
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()});
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());
520 rep << std::setprecision(2) <<
" " << by_shell[k].second <<
" ("
521 << 100 * by_shell[k].first <<
"%);";
527 std::vector<double> e_site(info.size(), 0.0);
528 double e_total = 0.0;
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;
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++);
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));
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)) /
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) {
567 std::vector<std::size_t> order(info.size());
568 for (std::size_t j = 0; j < order.size(); ++j) {
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]);
574 rep << std::setprecision(1) <<
" " << 100 * e_near / e_total
575 <<
"% of its reaction from sites within 3 A of the QM atoms; "
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";