111 std::vector<Translation> translations;
112 const Eigen::Vector3d a =
box_.col(0);
113 const Eigen::Vector3d b =
box_.col(1);
114 const Eigen::Vector3d c =
box_.col(2);
119 Eigen::Vector3d t = double(na) * a + double(nb) * b + double(nc) * c;
120 translations.push_back({t, t.norm()});
126 translations.begin(), translations.end(),
134 bool include_static)
const {
135 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
145 for (
const auto& entry : cached->second) {
152 const PolarSegment& source_segment = *std::get<0>(entry);
153 const Index translation_idx = std::get<1>(entry);
154 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
155 const Eigen::Vector3d t =
157 for (
const PolarSite& source_site : source_segment) {
160 if (include_static) {
163 interactor_.ApplyInducedField<CE>(source_site, target, t);
173 std::vector<std::tuple<const PolarSegment*, Index, Eigen::Vector3d>>
179 Index shell_start = 0;
180 double shell_edge = 0.0;
181 bool converged =
false;
191 Index shell_end = shell_start;
201 const Eigen::Vector3d before_shell =
210 if (!
registry_.Has(source_id, source_state)) {
252 const Eigen::Vector3d source_centroid =
253 UnweightedCentroid(source_segment);
254 const Eigen::Vector3d raw_offset = target.
getPos() - source_centroid;
255 const Eigen::Vector3d frac =
box_.inverse() * raw_offset;
256 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
257 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
258 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
260 for (
Index idx = shell_start; idx < shell_end; ++idx) {
287 const double pair_distance =
289 if (pair_distance > cutoff_with_margin) {
293 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
304 const Eigen::Vector3d shifted_centroid = source_centroid + t;
305 bool suppressed =
false;
306 for (
const Eigen::Vector3d& fg_pos : fg->second) {
330 const bool source_has_foreground =
332 if (!source_has_foreground && source_id == target_segment_id &&
337 visited_pairs.emplace_back(&source_segment, idx, baseline_shift);
338 for (
const PolarSite& source_site : source_segment) {
339 if (include_static) {
342 interactor_.ApplyInducedField<CE>(source_site, target, t);
347 const Eigen::Vector3d after_shell =
349 const Eigen::Vector3d shell_field = after_shell - before_shell;
351 const double shell_radius =
352 translations_[shell_end > shell_start ? shell_end - 1 : shell_start].r;
358 shell_start = shell_end;
362 std::stringstream message;
363 message <<
"EwaldRealSpaceSum: real-space sum for segment "
365 <<
" did not converge within the n_max lattice-translation "
366 "search box; increase n_max or field_tol.";
367 throw std::runtime_error(message.str());
378 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
382 throw std::runtime_error(
383 "EwaldRealSpaceSum::CalcStaticEnergyAt: no neighbour list for this "
384 "target. AddFieldAt or PrepareNeighborCache must run first -- this "
385 "method deliberately does not build one, so that an energy query "
386 "cannot silently become the expensive shell search.");
390 for (
const auto& entry : cached->second) {
391 const PolarSegment& source_segment = *std::get<0>(entry);
392 const Index translation_idx = std::get<1>(entry);
393 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
394 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
395 for (
const PolarSite& source_site : source_segment) {
404 Index target_segment_id,
const std::vector<Eigen::Vector3d>& points,
409 std::vector<const PolarSegment*> segments;
410 std::vector<Index> segment_ids;
411 std::vector<Eigen::Vector3d> centroids;
413 if (!
registry_.Has(source_id, source_state)) {
417 segments.push_back(&segment);
418 segment_ids.push_back(source_id);
419 centroids.push_back(UnweightedCentroid(segment));
424 const Index n_sources =
Index(segments.size());
425 Eigen::VectorXd phi = Eigen::VectorXd::Zero(n_points);
430 const Eigen::Matrix3d box_inv =
box_.inverse();
432#pragma omp parallel for schedule(dynamic, 8)
433 for (
Index p = 0; p < n_points; ++p) {
434 const Eigen::Vector3d& point = points[std::size_t(p)];
447 for (
Index s_i = 0; s_i < n_sources; ++s_i) {
448 const PolarSegment& source_segment = *segments[std::size_t(s_i)];
449 const Index source_id = segment_ids[std::size_t(s_i)];
450 const Eigen::Vector3d& source_centroid = centroids[std::size_t(s_i)];
455 const Eigen::Vector3d raw_offset = point - source_centroid;
456 const Eigen::Vector3d frac = box_inv * raw_offset;
457 const Eigen::Vector3d wrapped_frac = frac - frac.array().round().matrix();
458 const Eigen::Vector3d min_image_offset =
box_ * wrapped_frac;
459 const Eigen::Vector3d baseline_shift = raw_offset - min_image_offset;
462 const bool source_has_foreground = fg !=
foreground_.end();
478 const double t_limit = cutoff_with_margin + min_image_offset.norm();
480 for (std::size_t idx = 0; idx <
translations_.size(); ++idx) {
484 const double pair_distance =
486 if (pair_distance > cutoff_with_margin) {
489 const Eigen::Vector3d t = baseline_shift +
translations_[idx].t;
493 if (source_has_foreground) {
494 const Eigen::Vector3d shifted_centroid = source_centroid + t;
495 bool suppressed =
false;
496 for (
const Eigen::Vector3d& fg_pos : fg->second) {
507 if (!source_has_foreground && source_id == target_segment_id &&
512 for (
const PolarSite& source_site : source_segment) {
522 acc +=
interactor_.CalcInducedSourceEnergy(source_site, probe, t,
539 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
543 throw std::runtime_error(
544 "EwaldRealSpaceSum::CalcInducedSourceEnergyAt: no neighbour list for "
545 "this target. AddFieldAt or PrepareNeighborCache must run first -- "
546 "this method deliberately does not build one, so that an energy "
547 "query cannot silently become the expensive shell search.");
551 for (
const auto& entry : cached->second) {
552 const PolarSegment& source_segment = *std::get<0>(entry);
553 const Index translation_idx = std::get<1>(entry);
554 const Eigen::Vector3d& baseline_shift = std::get<2>(entry);
555 const Eigen::Vector3d t = baseline_shift +
translations_[translation_idx].t;
556 for (
const PolarSite& source_site : source_segment) {
557 energy +=
interactor_.CalcInducedSourceEnergy(source_site, target, t);
564 const std::vector<std::pair<Index, PolarSite*>>& targets,
571 for (
const auto& entry : targets) {
573 const Eigen::Vector3d saved_V = target.
V();
574 const Eigen::Vector3d saved_V_noE = target.
V_noE();
576 target.
V() = saved_V;
577 target.
V_noE() = saved_V_noE;
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={})