204 const std::vector<PolarSegment>& foreground)
const {
210 constexpr double kTol = 1
e-4;
212 const Eigen::Vector3d centroid = Centroid(seg);
215 if (entry.first == seg.getId() &&
216 (centroid - entry.second).norm() <= kTol) {
222 std::stringstream message;
223 message <<
"EwaldRegion: asked about segment " << seg.getId()
224 <<
", which is not part of the foreground these sums were "
225 "built for. The suppression list and the real-space "
226 "neighbour cache belong to that foreground, so answering "
227 "would drop the wrong background copies and subtract erf "
228 "corrections for copies that were never dropped -- a wrong "
229 "energy with no symptom. JobTopology declares the whole "
230 "foreground with RegisterForeground before any region is "
231 "evaluated; if this fires, that declaration is missing or "
233 throw std::runtime_error(message.str());
239 std::vector<std::pair<Index, Eigen::Vector3d>> source =
241 if (source.empty()) {
243 source.push_back({seg.getId(), Centroid(seg)});
249 for (
const auto& entry : source) {
250 const Index seg_id = entry.first;
252 throw std::runtime_error(
253 "EwaldRegion: the foreground contains segment " +
254 std::to_string(seg_id) +
255 ", which is absent from the periodic background. The foreground "
256 "must be carved out of the same system the background was "
283 const Eigen::Vector3d bg_centroid = Centroid(bg_copy);
284 const Eigen::Vector3d delta = entry.second - bg_centroid;
285 const Eigen::Vector3d fractional =
params_.box.inverse() * delta;
286 const Eigen::Vector3d image =
287 params_.box * fractional.array().round().matrix();
288 const Eigen::Vector3d residual = delta - image;
294 constexpr double kResidualWarn = 5.0;
295 if (residual.norm() > kResidualWarn) {
297 <<
TimeStamp() <<
" WARNING: foreground segment " << seg_id
298 <<
" sits " << residual.norm()
299 <<
" bohr from the nearest periodic image of its background "
300 "copy. That is too far to be geometry relaxation, so the "
301 "copy this code is about to suppress may not be the one the "
302 "foreground actually displaces."
309 real_sum_ = std::make_unique<EwaldRealSpaceSum>(
313 recip_sum_ = std::make_unique<EwaldReciprocalSpaceSum>(
315 shape_ = std::make_unique<EwaldShapeCorrection>(
params_.box.determinant(),
369 const std::vector<Eigen::Vector3d>& points)
const {
376 using Clock = std::chrono::steady_clock;
377 auto seconds_since = [](Clock::time_point t) {
378 return std::chrono::duration<double>(Clock::now() - t).count();
380 auto t_start = Clock::now();
381 Eigen::VectorXd phi =
real_sum_->PotentialAtMany(probe_segment_id, points,
383 const double t_real = seconds_since(t_start);
384 auto t_recip_start = Clock::now();
386 const double t_recip = seconds_since(t_recip_start);
387 auto t_corr_start = Clock::now();
403 std::vector<ProbeSource> erf_sources;
407 const Eigen::Vector3d shift = copy.second - Centroid(bg);
409 erf_sources.push_back(
410 {source.getPos() + shift, source.getCharge(),
411 source.getStaticDipole() + source.getInducedDipole()});
414#pragma omp parallel for schedule(static)
415 for (
Index p = 0; p < n_points; ++p) {
416 const Eigen::Vector3d& point = points[std::size_t(p)];
418 for (
const ProbeSource& source : erf_sources) {
420 interactor_->ErfPotential(source.q, source.mu, point - source.pos);
425 <<
TimeStamp() <<
" Ewald potential at " << n_points
426 <<
" points: real space " << std::setprecision(4) << t_real
427 <<
" s, reciprocal " << t_recip <<
" s, shape + erf correction "
429 <<
" foreground copies)" << std::flush;
435 NotInitialized(
"ApplyFieldTo");
447 std::vector<PolarSite*> targets;
448 std::vector<Index> target_segment_ids;
451 targets.push_back(&site);
452 target_segment_ids.push_back(seg.getId());
474 std::vector<Eigen::Vector3d> v_before;
475 v_before.reserve(targets.size());
476 for (
const PolarSite* target : targets) {
477 v_before.push_back(target->V());
487 const Index n_targets =
Index(targets.size());
489 std::vector<std::pair<Index, PolarSite*>> real_targets;
490 real_targets.reserve(targets.size());
491 for (
Index i = 0; i < n_targets; ++i) {
492 real_targets.push_back(
493 {target_segment_ids[std::size_t(i)], targets[std::size_t(i)]});
510#pragma omp parallel for schedule(dynamic, 16) reduction(+ : e_real)
511 for (
Index i = 0; i < n_targets; ++i) {
512 e_real +=
real_sum_->CalcStaticEnergyAt(*targets[std::size_t(i)],
515 double energy = e_real;
527 double e_real_pu = 0.0;
528#pragma omp parallel for schedule(dynamic, 16) reduction(+ : e_real_pu)
529 for (
Index i = 0; i < n_targets; ++i) {
530 e_real_pu +=
real_sum_->CalcInducedSourceEnergyAt(
540 PolarSite probe(-1,
"X", Eigen::Vector3d::Zero());
542 const Eigen::Vector3d shape_field = probe.
V();
544 target->V() += shape_field;
552#pragma omp parallel for schedule(dynamic, 16)
553 for (
Index i = 0; i < n_targets; ++i) {
554 PolarSite& target = *targets[std::size_t(i)];
558 const Eigen::Vector3d shift = copy.second - Centroid(bg);
561 source, target, shift);
593 double e_recip = 0.0;
594 double e_shape = 0.0;
597 std::vector<std::pair<const PolarSite*, Eigen::Vector3d>> fg_sites;
598 fg_sites.reserve(targets.size());
599 for (std::size_t n = 0; n < targets.size(); ++n) {
600 fg_sites.push_back({targets[n], targets[n]->getPos()});
602 const std::vector<const PolarSite*> no_exclusions;
604 e_recip =
recip_sum_->CalcStaticEnergyBetween(fg_sites, no_exclusions,
606 e_shape =
shape_->CalcStaticEnergyBetween(fg_sites, no_exclusions,
614 const Eigen::Vector3d shift = copy.second - Centroid(bg_copy);
615 for (
const PolarSite& source : bg_copy) {
616 for (
const auto& entry : fg_sites) {
618 source, *entry.first, shift);
622 energy += e_recip + e_shape - e_erf;
627 double e_recip_pu =
recip_sum_->CalcInducedSourceEnergyBetween(
629 double e_shape_pu =
shape_->CalcInducedSourceEnergyBetween(
631 double e_erf_pu = 0.0;
635 const Eigen::Vector3d shift = copy.second - Centroid(bg_copy);
636 for (
const PolarSite& source : bg_copy) {
637 for (
const auto& entry : fg_sites) {
638 e_erf_pu +=
interactor_->CalcErfInducedSourceEnergy(
639 source, *entry.first, shift);
643 energy += e_recip_pu + e_shape_pu - e_erf_pu;
657 <<
" Ewald energy [eV], permanent x permanent: real = " << e_real * h2ev
658 <<
" recip = " << e_recip * h2ev <<
" shape = " << e_shape * h2ev
659 <<
" erf = " << e_erf * h2ev << std::flush;
662 <<
" Ewald energy [eV], fg permanent x bg induced: real = "
663 << e_real_pu * h2ev <<
" recip = " << e_recip_pu * h2ev
664 <<
" shape = " << e_shape_pu * h2ev <<
" erf = " << e_erf_pu * h2ev
667 <<
TimeStamp() <<
" Ewald energy [eV], total = " << energy * h2ev
671 <<
" 1/bohr (" <<
params_.alpha * 18.8972612 <<
" 1/nm)"
672 <<
", k_max = " <<
params_.k_max <<
" 1/bohr ("
673 <<
params_.k_max * 18.8972612 <<
" 1/nm)"
674 <<
", r_min = " <<
params_.r_min
675 <<
" bohr, V = " <<
params_.box.determinant()
676 <<
" bohr^3, thole_a = " <<
params_.thole_a << std::flush;
679 <<
" segments, " << targets.size() <<
" sites, carved from "
680 <<
registry_.AllIds().size() <<
" registered" << std::flush;
688 const Index expected =
692 <<
" of " << expected
693 << ((stats.
foreground == expected) ?
" (ok)" :
" <-- MISMATCH")
699 for (std::size_t i = 0; i < targets.size(); ++i) {
700 const Eigen::Vector3d ewald_contribution = targets[i]->V() - v_before[i];
701 targets[i]->V() = v_before[i] - ewald_contribution;
708 <<
" NOTE: the background's own internal energy is deliberately "
709 "absent -- it is a constant that cancels in any charge-state "
710 "difference. Everything else is here; see ApplyFieldTo's "
711 "declaration for the full accounting."