54Eigen::Vector3d UnweightedCentroid(
const PolarSegment& seg) {
55 Eigen::Vector3d pos = Eigen::Vector3d::Zero();
69 const Eigen::Matrix3d& box,
const EwaldRegistry& registry,
double alpha,
70 double thole_a,
double r_min,
double field_tol,
double shell_width,
71 Index n_max,
double screening_factor,
72 const std::vector<std::pair<Index, Eigen::Vector3d>>& foreground)
84 for (
const auto& entry : foreground) {
101 const Eigen::Vector3d centroid = UnweightedCentroid(segment);
110std::vector<EwaldRealSpaceSum::Translation>
112 std::vector<Translation> translations;
113 const Eigen::Vector3d a =
box_.col(0);
114 const Eigen::Vector3d b =
box_.col(1);
115 const Eigen::Vector3d c =
box_.col(2);
120 Eigen::Vector3d t = double(na) * a + double(nb) * b + double(nc) * c;
121 translations.push_back({t, t.norm()});
127 translations.begin(), translations.end(),
132template <enum Estatic CE>
135 bool include_static)
const {
136 for (
const auto& entry : list) {
143 const PolarSegment& source_segment = *std::get<0>(entry);
144 const Index translation_idx = std::get<1>(entry);
145 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
146 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
147 for (
const PolarSite& source_site : source_segment) {
150 if (include_static) {
153 interactor_.ApplyInducedField<CE>(source_site, target, t);
159 const std::pair<const PolarSite*, EwaldChargeState>& key,
NeighborList list,
168template <enum Estatic CE>
171 bool include_static)
const {
172 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
185 target_segment_id, scratch, source_state, include_static, counts);
197template <enum Estatic CE>
210 Index shell_start = 0;
211 double shell_edge = 0.0;
212 bool converged =
false;
222 Index shell_end = shell_start;
232 const Eigen::Vector3d before_shell =
241 if (!
registry_.Has(source_id, source_state)) {
283 const Eigen::Vector3d source_centroid =
284 UnweightedCentroid(source_segment);
285 const Eigen::Vector3d raw_offset = target.
getPos() - source_centroid;
286 const Eigen::Vector3d frac =
box_.inverse() * raw_offset;
287 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
288 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
289 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
291 for (
Index idx = shell_start; idx < shell_end; ++idx) {
318 const double pair_distance =
320 if (pair_distance > cutoff_with_margin) {
324 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
335 const Eigen::Vector3d shifted_centroid = source_centroid + t;
336 bool suppressed =
false;
337 for (
const Eigen::Vector3d& fg_pos : fg->second) {
361 const bool source_has_foreground =
363 if (!source_has_foreground && source_id == target_segment_id &&
368 visited_pairs.emplace_back(&source_segment, idx, baseline_shift);
369 for (
const PolarSite& source_site : source_segment) {
370 if (include_static) {
373 interactor_.ApplyInducedField<CE>(source_site, target, t);
378 const Eigen::Vector3d after_shell =
380 const Eigen::Vector3d shell_field = after_shell - before_shell;
382 const double shell_radius =
383 translations_[shell_end > shell_start ? shell_end - 1 : shell_start].r;
389 shell_start = shell_end;
393 std::stringstream message;
394 message <<
"EwaldRealSpaceSum: real-space sum for segment "
396 <<
" did not converge within the n_max lattice-translation "
397 "search box; increase n_max or field_tol.";
398 throw std::runtime_error(message.str());
401 return visited_pairs;
407 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
411 throw std::runtime_error(
412 "EwaldRealSpaceSum::CalcStaticEnergyAt: no neighbour list for this "
413 "target. AddFieldAt or PrepareNeighborCache must run first -- this "
414 "method deliberately does not build one, so that an energy query "
415 "cannot silently become the expensive shell search.");
419 for (
const auto& entry : cached->second) {
420 const PolarSegment& source_segment = *std::get<0>(entry);
421 const Index translation_idx = std::get<1>(entry);
422 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
423 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
424 for (
const PolarSite& source_site : source_segment) {
433 Index target_segment_id,
const std::vector<Eigen::Vector3d>& points,
446 std::vector<std::vector<ProbeSource>> segments;
447 std::vector<Index> segment_ids;
448 std::vector<Eigen::Vector3d> centroids;
450 if (!
registry_.Has(source_id, source_state)) {
454 std::vector<ProbeSource> sites;
455 sites.reserve(segment.
size());
457 sites.push_back({site.getPos(), site.getCharge(),
458 site.getStaticDipole() + site.getInducedDipole()});
460 segments.push_back(std::move(sites));
461 segment_ids.push_back(source_id);
462 centroids.push_back(UnweightedCentroid(segment));
467 const Index n_sources =
Index(segments.size());
468 Eigen::VectorXd phi = Eigen::VectorXd::Zero(n_points);
473 const Eigen::Matrix3d box_inv =
box_.inverse();
475#pragma omp parallel for schedule(dynamic, 8)
476 for (
Index p = 0; p < n_points; ++p) {
477 const Eigen::Vector3d& point = points[std::size_t(p)];
480 for (
Index s_i = 0; s_i < n_sources; ++s_i) {
481 const std::vector<ProbeSource>& source_segment =
482 segments[std::size_t(s_i)];
483 const Index source_id = segment_ids[std::size_t(s_i)];
484 const Eigen::Vector3d& source_centroid = centroids[std::size_t(s_i)];
489 const Eigen::Vector3d raw_offset = point - source_centroid;
490 const Eigen::Vector3d frac = box_inv * raw_offset;
491 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
492 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
493 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
496 const bool source_has_foreground = fg !=
foreground_.end();
512 const double t_limit = cutoff_with_margin + min_image_offset.norm();
514 for (std::size_t idx = 0; idx <
translations_.size(); ++idx) {
518 const double pair_distance =
520 if (pair_distance > cutoff_with_margin) {
523 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
527 if (source_has_foreground) {
528 const Eigen::Vector3d shifted_centroid = source_centroid + t;
529 bool suppressed =
false;
530 for (
const Eigen::Vector3d& fg_pos : fg->second) {
541 if (!source_has_foreground && source_id == target_segment_id &&
552 const Eigen::Vector3d probe_offset = point - t;
553 for (
const ProbeSource& source_site : source_segment) {
554 acc +=
interactor_.ScreenedPotential(source_site.q, source_site.mu,
555 probe_offset - source_site.pos);
571 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
575 throw std::runtime_error(
576 "EwaldRealSpaceSum::CalcInducedSourceEnergyAt: no neighbour list for "
577 "this target. AddFieldAt or PrepareNeighborCache must run first -- "
578 "this method deliberately does not build one, so that an energy "
579 "query cannot silently become the expensive shell search.");
583 for (
const auto& entry : cached->second) {
584 const PolarSegment& source_segment = *std::get<0>(entry);
585 const Index translation_idx = std::get<1>(entry);
586 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
587 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
588 for (
const PolarSite& source_site : source_segment) {
589 energy +=
interactor_.CalcInducedSourceEnergy(source_site, target, t);
596 const std::vector<std::pair<Index, PolarSite*>>& targets,
600 converge_static_field);
603template <enum Estatic CE>
605 const std::vector<std::pair<Index, PolarSite*>>& targets,
613 std::vector<std::size_t> todo;
615 std::unordered_map<std::pair<const PolarSite*, EwaldChargeState>, bool,
618 for (std::size_t i = 0; i < targets.size(); ++i) {
619 const std::pair<const PolarSite*, EwaldChargeState> key(targets[i].second,
621 if (
neighbor_cache_.count(key) > 0 || !seen.emplace(key,
true).second) {
634 std::vector<NeighborList> lists(todo.size());
635 std::vector<SearchCounts> counts(todo.size());
636 std::exception_ptr failure =
nullptr;
638#pragma omp parallel for schedule(dynamic, 16)
639 for (
Index k = 0; k < n_todo; ++k) {
641 const auto& entry = targets[todo[std::size_t(k)]];
643 lists[std::size_t(k)] =
645 include_static, counts[std::size_t(k)]);
647#pragma omp critical(ewald_real_prepare_failure)
650 failure = std::current_exception();
656 std::rethrow_exception(failure);
660 for (std::size_t k = 0; k < todo.size(); ++k) {
661 const std::pair<const PolarSite*, EwaldChargeState> key(
662 targets[todo[k]].second, source_state);
667template <enum Estatic CE>
669 const std::vector<std::pair<Index, PolarSite*>>& targets,
676 const Index n_targets =
Index(targets.size());
677#pragma omp parallel for schedule(dynamic, 16)
678 for (
Index i = 0; i < n_targets; ++i) {
679 const auto& entry = targets[std::size_t(i)];
680 const auto cached =
neighbor_cache_.find({entry.second, source_state});
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.