285 auto t_start = std::chrono::steady_clock::now();
286 auto elapsed_s = [&](std::chrono::steady_clock::time_point since) {
287 return std::chrono::duration<double>(std::chrono::steady_clock::now() -
293 <<
" Starting Ewald background calculation"
300 std::vector<Index> ids;
305 std::vector<Index> offsets;
307 offsets.reserve(top.
Segments().size() + 1);
308 offsets.push_back(0);
312 ids.push_back(seg.getId());
313 offsets.push_back(offsets.back() + 3 * mol.
size());
315 const Index total_size = offsets.back();
318 <<
" segments, " << (total_size / 3)
319 <<
" polarizable sites total (" << elapsed_s(t_start)
320 <<
"s)" << std::flush;
322 const Eigen::Matrix3d& box = top.
getBox();
323 const double volume = box.col(0).dot(box.col(1).cross(box.col(2)));
384 const double eps = std::max(std::erfc(s_r), 1
e-300);
385 const double s_k = 2.0 * std::sqrt(-std::log(eps));
388 const double n_sites = double(total_size / 3);
390 std::cbrt(volume) * std::sqrt(s_r / s_k);
416 <<
" Real/reciprocal-space sums constructed ("
417 << elapsed_s(t_start) <<
"s)" << std::flush;
420 <<
" nm^-1), k_max=" <<
k_max_ <<
" bohr^-1 ("
423 <<
" k-vectors), real-space cutoff "
426 <<
")" << std::flush;
434 const double n_sites = double(total_size / 3);
435 const double n_real_est = (n_sites / volume) *
440 <<
" Ewald balance (terms per target, estimated): " <<
"real "
441 << n_real_est <<
" vs reciprocal " << recip_sum.
NumKVectors()
452 <<
" bohr) lies beyond the real-space cutoff (" << cutoff
453 <<
" bohr). The shell search will walk shells that contribute "
454 "nothing before it is allowed to stop. Accuracy is unaffected "
455 "-- it is erfc(screening_factor) either way -- but lowering "
456 "r_min to at most the cutoff would save that work."
463 <<
", r_min=" <<
r_min_ <<
" bohr ("
466 <<
", thole_a=" <<
thole_a_ << std::flush;
468 << (
induce_ ?
"true" :
"false")
471 <<
", solver=" << (
use_jor_ ?
"JOR" :
"PCG")
475 :
" (unpreconditioned)"))
483 <<
TimeStamp() <<
" debug_match_legacy_first_step is ON" << std::flush;
491 auto t_field = std::chrono::steady_clock::now();
492 std::vector<std::pair<Index, PolarSite*>> targets;
493 targets.reserve(std::size_t(total_size / 3));
494 for (std::size_t n = 0; n < ids.size(); ++n) {
496 for (
Index s = 0; s < segment.
size(); ++s) {
500 targets.push_back({ids[n], &site});
508 << elapsed_s(t_field) <<
"s)" << std::flush;
510 auto t_recip = std::chrono::steady_clock::now();
514 std::vector<PolarSite*> recip_targets;
515 recip_targets.reserve(targets.size());
516 for (
const auto& entry : targets) {
517 recip_targets.push_back(entry.second);
521 [&](std::size_t done, std::size_t total) {
525 <<
TimeStamp() <<
" k-space progress: " << done <<
"/" << total
526 <<
" k-vectors (" << elapsed_s(t_recip) <<
"s)" << std::flush;
529 <<
" Reciprocal-space permanent field done ("
530 << elapsed_s(t_recip) <<
"s)" << std::flush;
537 auto t_shape = std::chrono::steady_clock::now();
539 PolarSite shape_probe(-1,
"X", Eigen::Vector3d::Zero());
541 const Eigen::Vector3d shape_field = shape_probe.
V();
542 for (
const auto& entry : targets) {
543 entry.second->V() += shape_field;
547 << elapsed_s(t_shape) <<
"s)" << std::flush;
551 << elapsed_s(t_field) <<
"s)" << std::flush;
552 auto t_intra = std::chrono::steady_clock::now();
586 for (
Index n = 0; n <
Index(ids.size()); ++n) {
589 for (
Index i = 0; i < n_sites; ++i) {
590 for (
Index j = 0; j < n_sites; ++j) {
592 segment[j], segment[i]);
597 <<
" Intramolecular static compensation applied ("
598 << elapsed_s(t_intra) <<
"s)" << std::flush;
600 Eigen::VectorXd b(total_size);
603 for (std::size_t n = 0; n < ids.size(); ++n) {
606 Index base = offsets[n];
607 for (
Index s = 0; s < segment.
size(); ++s) {
608 b.segment<3>(base + 3 * s) = targets[t].second->V();
609 targets[t].second->Reset();
618 <<
" solve (max " <<
max_iter_ <<
" iterations, tolerance "
620 auto t_pcg = std::chrono::steady_clock::now();
628 Index indefinite_at_iteration = -1;
629 double indefinite_curvature = 0.0;
630 double lanczos_min_eigenvalue = std::numeric_limits<double>::quiet_NaN();
645 iterations = result.iterations;
646 residual = result.residual;
647 converged = result.converged;
650 <<
TimeStamp() <<
" JOR finished after " << iterations
651 <<
" iterations, max_dU=" << result.max_dU
652 <<
" avg_dU=" << result.avg_dU <<
" (residual=" << residual
653 <<
", informational) (" << elapsed_s(t_pcg) <<
"s)" << std::flush;
655 throw std::runtime_error(
656 "EwaldBackground: JOR did not converge within max_iter");
679 iterations = result.iterations;
680 residual = result.residual;
681 converged = result.converged;
682 indefinite_at_iteration = result.indefinite_at_iteration;
683 indefinite_curvature = result.indefinite_curvature;
684 lanczos_min_eigenvalue = result.lanczos_min_eigenvalue;
686 Eigen::DiagonalPreconditioner<double> precond;
691 iterations = result.iterations;
692 residual = result.residual;
693 converged = result.converged;
694 indefinite_at_iteration = result.indefinite_at_iteration;
695 indefinite_curvature = result.indefinite_curvature;
696 lanczos_min_eigenvalue = result.lanczos_min_eigenvalue;
700 <<
TimeStamp() <<
" PCG finished after " << iterations
701 <<
" iterations, residual " << residual <<
" (" << elapsed_s(t_pcg)
702 <<
"s)" << std::flush;
713 const double n = double(std::max<Index>(tm.n_calls, 1));
715 <<
TimeStamp() <<
" RawMultiply phase breakdown over " << tm.n_calls
716 <<
" calls (total " << tm.total() <<
"s):" << std::flush;
717 auto line = [&](
const char* nm,
double t) {
720 << (boost::format(
" %1$-12s %2$8.3fs total "
721 "%3$7.3fs/call %4$5.1f%%") %
723 (tm.total() > 0 ? 100.0 * t / tm.total() : 0.0))
727 line(
"setup", tm.setup);
728 line(
"real_space", tm.real_space);
729 line(
"reciprocal", tm.reciprocal);
730 line(
"shape", tm.shape);
731 line(
"assemble", tm.assemble);
732 line(
"intra", tm.intra);
740 " neighbours: %1$.1f entries/target over %2$d targets "
741 "(%3$d kept, %4$d culled beyond %5$.1f bohr = %6$.1f%%)") %
742 ns.entries_per_target() % ns.targets % ns.entries % ns.culled %
747 if (!std::isnan(lanczos_min_eigenvalue)) {
749 << lanczos_min_eigenvalue << std::flush;
750 if (lanczos_min_eigenvalue < 0.0) {
753 <<
" NEGATIVE -- strong evidence the operator is not "
754 "positive-definite (see "
755 "PcgIndefinitenessResult::lanczos_min_eigenvalue for what "
756 "this does and does not guarantee)."
761 if (indefinite_at_iteration >= 0) {
762 throw std::runtime_error(
763 "EwaldBackground: PCG's own operator was found to be NOT "
764 "positive-definite at iteration " +
765 std::to_string(indefinite_at_iteration) +
766 " (p.A.p = " + std::to_string(indefinite_curvature) +
767 " <= 0) -- this is a direct algebraic certificate, not an "
768 "inference from residual behavior.");
772 throw std::runtime_error(
773 "EwaldBackground: PCG did not converge (max_iter reached, "
774 "operator was never found indefinite along the way)");
778 for (std::size_t n = 0; n < ids.size(); ++n) {
780 Index base = offsets[n];
781 for (
Index s = 0; s < segment.
size(); ++s) {
782 segment[s].setInduced_Dipole(x.segment<3>(base + 3 * s));
793 <<
" polarmethod.induce is false: skipping the PCG solve, "
794 "induced dipoles left at zero"
798 auto t_cpt = std::chrono::steady_clock::now();
813 for (std::size_t n = 0; n < ids.size(); ++n) {
815 Index base = offsets[n];
816 for (
Index s = 0; s < segment.
size(); ++s) {
817 segment[s].V() = b.segment<3>(base + 3 * s);
848 <<
"s)" << std::flush;
850 <<
" Ewald background calculation done, total "
851 << elapsed_s(t_start) <<
"s" << std::flush;