27#include <unordered_map>
56Eigen::Vector3d UnweightedCentroid(
const PolarSegment& seg) {
57 Eigen::Vector3d pos = Eigen::Vector3d::Zero();
71 const Eigen::Matrix3d& box,
const EwaldRegistry& registry,
double alpha,
72 double thole_a,
double r_min,
double field_tol,
double shell_width,
73 Index n_max,
double screening_factor,
74 const std::vector<std::pair<Index, Eigen::Vector3d>>& foreground)
86 for (
const auto& entry : foreground) {
103 const Eigen::Vector3d centroid = UnweightedCentroid(segment);
112std::vector<EwaldRealSpaceSum::Translation>
114 std::vector<Translation> translations;
115 const Eigen::Vector3d a =
box_.col(0);
116 const Eigen::Vector3d b =
box_.col(1);
117 const Eigen::Vector3d c =
box_.col(2);
122 Eigen::Vector3d t = double(na) * a + double(nb) * b + double(nc) * c;
123 translations.push_back({t, t.norm()});
129 translations.begin(), translations.end(),
134template <enum Estatic CE>
137 bool include_static)
const {
138 for (
const auto& entry : list) {
145 const PolarSegment& source_segment = *std::get<0>(entry);
146 const Index translation_idx = std::get<1>(entry);
147 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
148 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
149 for (
const PolarSite& source_site : source_segment) {
152 if (include_static) {
155 interactor_.ApplyInducedField<CE>(source_site, target, t);
161 const std::pair<const PolarSite*, EwaldChargeState>& key,
NeighborList list,
170template <enum Estatic CE>
173 bool include_static)
const {
174 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
187 target_segment_id, scratch, source_state, include_static, counts);
199template <enum Estatic CE>
212 Index shell_start = 0;
213 double shell_edge = 0.0;
214 bool converged =
false;
224 Index shell_end = shell_start;
234 const Eigen::Vector3d before_shell =
243 if (!
registry_.Has(source_id, source_state)) {
285 const Eigen::Vector3d source_centroid =
286 UnweightedCentroid(source_segment);
287 const Eigen::Vector3d raw_offset = target.
getPos() - source_centroid;
288 const Eigen::Vector3d frac =
box_.inverse() * raw_offset;
289 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
290 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
291 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
293 for (
Index idx = shell_start; idx < shell_end; ++idx) {
320 const double pair_distance =
322 if (pair_distance > cutoff_with_margin) {
326 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
337 const Eigen::Vector3d shifted_centroid = source_centroid + t;
338 bool suppressed =
false;
339 for (
const Eigen::Vector3d& fg_pos : fg->second) {
363 const bool source_has_foreground =
365 if (!source_has_foreground && source_id == target_segment_id &&
370 visited_pairs.emplace_back(&source_segment, idx, baseline_shift);
371 for (
const PolarSite& source_site : source_segment) {
372 if (include_static) {
375 interactor_.ApplyInducedField<CE>(source_site, target, t);
380 const Eigen::Vector3d after_shell =
382 const Eigen::Vector3d shell_field = after_shell - before_shell;
384 const double shell_radius =
385 translations_[shell_end > shell_start ? shell_end - 1 : shell_start].r;
391 shell_start = shell_end;
395 std::stringstream message;
396 message <<
"EwaldRealSpaceSum: real-space sum for segment "
398 <<
" did not converge within the n_max lattice-translation "
399 "search box; increase n_max or field_tol.";
400 throw std::runtime_error(message.str());
403 return visited_pairs;
409 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
413 throw std::runtime_error(
414 "EwaldRealSpaceSum::CalcStaticEnergyAt: no neighbour list for this "
415 "target. AddFieldAt or PrepareNeighborCache must run first -- this "
416 "method deliberately does not build one, so that an energy query "
417 "cannot silently become the expensive shell search.");
421 for (
const auto& entry : cached->second) {
422 const PolarSegment& source_segment = *std::get<0>(entry);
423 const Index translation_idx = std::get<1>(entry);
424 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
425 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
426 for (
const PolarSite& source_site : source_segment) {
435 Index target_segment_id,
const std::vector<Eigen::Vector3d>& points,
448 std::vector<std::vector<ProbeSource>> segments;
449 std::vector<Index> segment_ids;
450 std::vector<Eigen::Vector3d> centroids;
452 if (!
registry_.Has(source_id, source_state)) {
456 std::vector<ProbeSource> sites;
457 sites.reserve(segment.
size());
459 sites.push_back({site.getPos(), site.getCharge(),
460 site.getStaticDipole() + site.getInducedDipole()});
462 segments.push_back(std::move(sites));
463 segment_ids.push_back(source_id);
464 centroids.push_back(UnweightedCentroid(segment));
469 const Index n_sources =
Index(segments.size());
470 Eigen::VectorXd phi = Eigen::VectorXd::Zero(n_points);
475 const Eigen::Matrix3d box_inv =
box_.inverse();
477#pragma omp parallel for schedule(dynamic, 8)
478 for (
Index p = 0; p < n_points; ++p) {
479 const Eigen::Vector3d& point = points[std::size_t(p)];
482 for (
Index s_i = 0; s_i < n_sources; ++s_i) {
483 const std::vector<ProbeSource>& source_segment =
484 segments[std::size_t(s_i)];
485 const Index source_id = segment_ids[std::size_t(s_i)];
486 const Eigen::Vector3d& source_centroid = centroids[std::size_t(s_i)];
491 const Eigen::Vector3d raw_offset = point - source_centroid;
492 const Eigen::Vector3d frac = box_inv * raw_offset;
493 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
494 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
495 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
498 const bool source_has_foreground = fg !=
foreground_.end();
514 const double t_limit = cutoff_with_margin + min_image_offset.norm();
516 for (std::size_t idx = 0; idx <
translations_.size(); ++idx) {
520 const double pair_distance =
522 if (pair_distance > cutoff_with_margin) {
525 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
529 if (source_has_foreground) {
530 const Eigen::Vector3d shifted_centroid = source_centroid + t;
531 bool suppressed =
false;
532 for (
const Eigen::Vector3d& fg_pos : fg->second) {
543 if (!source_has_foreground && source_id == target_segment_id &&
554 const Eigen::Vector3d probe_offset = point - t;
555 for (
const ProbeSource& source_site : source_segment) {
556 acc +=
interactor_.ScreenedPotential(source_site.q, source_site.mu,
557 probe_offset - source_site.pos);
573 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
577 throw std::runtime_error(
578 "EwaldRealSpaceSum::CalcInducedSourceEnergyAt: no neighbour list for "
579 "this target. AddFieldAt or PrepareNeighborCache must run first -- "
580 "this method deliberately does not build one, so that an energy "
581 "query cannot silently become the expensive shell search.");
585 for (
const auto& entry : cached->second) {
586 const PolarSegment& source_segment = *std::get<0>(entry);
587 const Index translation_idx = std::get<1>(entry);
588 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
589 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
590 for (
const PolarSite& source_site : source_segment) {
591 energy +=
interactor_.CalcInducedSourceEnergy(source_site, target, t);
598 const std::vector<std::pair<Index, PolarSite*>>& targets,
602 converge_static_field);
605template <enum Estatic CE>
607 const std::vector<std::pair<Index, PolarSite*>>& targets,
615 std::vector<std::size_t> todo;
617 std::unordered_map<std::pair<const PolarSite*, EwaldChargeState>, bool,
620 for (std::size_t i = 0; i < targets.size(); ++i) {
621 const std::pair<const PolarSite*, EwaldChargeState> key(targets[i].second,
623 if (
neighbor_cache_.count(key) > 0 || !seen.emplace(key,
true).second) {
636 std::vector<NeighborList> lists(todo.size());
637 std::vector<SearchCounts> counts(todo.size());
638 std::exception_ptr failure =
nullptr;
640#pragma omp parallel for schedule(dynamic, 16)
641 for (
Index k = 0; k < n_todo; ++k) {
643 const auto& entry = targets[todo[std::size_t(k)]];
645 lists[std::size_t(k)] =
647 include_static, counts[std::size_t(k)]);
649#pragma omp critical(ewald_real_prepare_failure)
652 failure = std::current_exception();
658 std::rethrow_exception(failure);
662 for (std::size_t k = 0; k < todo.size(); ++k) {
663 const std::pair<const PolarSite*, EwaldChargeState> key(
664 targets[todo[k]].second, source_state);
669template <enum Estatic CE>
671 const std::vector<std::pair<Index, PolarSite*>>& targets,
681 std::unordered_map<const PolarSite*, std::size_t> group_of;
682 std::vector<std::vector<std::size_t>> groups;
683 group_of.reserve(targets.size());
684 groups.reserve(targets.size());
685 for (std::size_t i = 0; i < targets.size(); ++i) {
686 const auto inserted = group_of.emplace(targets[i].second, groups.size());
687 if (inserted.second) {
688 groups.emplace_back();
690 groups[inserted.first->second].push_back(i);
693#pragma omp parallel for schedule(dynamic, 16)
694 for (
Index g = 0; g < n_groups; ++g) {
695 for (
const std::size_t i : groups[std::size_t(g)]) {
static constexpr double kForegroundMatchTol
Eigen::VectorXd PotentialAtMany(Index target_segment_id, const std::vector< Eigen::Vector3d > &points, EwaldChargeState source_state) const
std::unordered_map< std::pair< const PolarSite *, EwaldChargeState >, NeighborList, PairHash > neighbor_cache_
std::vector< std::tuple< const PolarSegment *, Index, Eigen::Vector3d > > NeighborList
double CalcInducedSourceEnergyAt(const PolarSite &target, EwaldChargeState source_state) const
void AddFieldAtMany(const std::vector< std::pair< Index, PolarSite * > > &targets, EwaldChargeState source_state, bool include_static=true) const
std::map< Index, std::vector< Eigen::Vector3d > > foreground_
double CalcStaticEnergyAt(const PolarSite &target, EwaldChargeState source_state) const
std::vector< Translation > GenerateSortedTranslations() const
const EwaldRegistry & registry_
void PrepareNeighborCache(const std::vector< std::pair< Index, PolarSite * > > &targets, EwaldChargeState source_state, bool converge_static_field=false) const
void AddFieldAt(Index target_segment_id, PolarSite &target, EwaldChargeState source_state, bool include_static=true) const
void PrepareNeighborCacheImpl(const std::vector< std::pair< Index, PolarSite * > > &targets, EwaldChargeState source_state, bool include_static) const
std::vector< Translation > translations_
void InsertNeighborList(const std::pair< const PolarSite *, EwaldChargeState > &key, NeighborList list, const SearchCounts &counts) const
EwaldRealSpaceInteractor interactor_
double real_space_cutoff_
Index foreground_entries_
static constexpr double kSelfTranslationTol
void ApplyNeighborList(const NeighborList &list, PolarSite &target, bool include_static) const
EwaldRealSpaceSum(const Eigen::Matrix3d &box, const EwaldRegistry ®istry, double alpha, double thole_a, double r_min, double field_tol, double shell_width=0.945, Index n_max=15, double screening_factor=6.0, const std::vector< std::pair< Index, Eigen::Vector3d > > &foreground={})
NeighborList SearchNeighbors(Index target_segment_id, PolarSite &scratch, EwaldChargeState source_state, bool include_static, SearchCounts &counts) const
Class to represent Atom/Site in electrostatic+polarization.
const Eigen::Vector3d & V_noE() const
const Eigen::Vector3d & V() const
const Eigen::Vector3d & getPos() const
Charge transport classes.
ClassicalSegment< PolarSite > PolarSegment
Provides a means for comparing floating point numbers.