votca 2026-dev
Loading...
Searching...
No Matches
ewaldrealspacesum.cc
Go to the documentation of this file.
1/*
2 * Copyright 2009-2026 The VOTCA Development Team
3 * (http://www.votca.org)
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License")
6 *
7 * You may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20// Standard includes
21#include <algorithm>
22#include <fstream>
23#include <iostream>
24#include <sstream>
25#include <stdexcept>
26
27// Local VOTCA includes
29
30namespace votca {
31namespace xtp {
32
33namespace {
34// BUG FIX (this session): PolarSegment::getPos() (via AtomContainer<T>::
35// calcPos()) is a MASS-weighted center of mass. Legacy's own equivalent
36// (PolarSeg::CalcPos()) is a plain, UNWEIGHTED arithmetic mean of site
37// positions instead -- confirmed by direct comparison of the two
38// implementations. Since this position feeds directly into the
39// minimum-image PBC wrap below (raw_offset = target - source's own
40// representative position), even a small difference between the two
41// definitions can flip which periodic image is chosen as the true
42// minimum for a given pair -- a discrete, not continuous, effect,
43// consistent with the scattered (not uniformly scaled) per-site
44// mismatch pattern that motivated this fix, rather than a smooth
45// site-by-site drift.
46//
47// Deliberately NOT fixed by changing calcPos() itself: that's a shared
48// AtomContainer<T> method used well beyond this file (md2qmengine,
49// segmentmapper), where a genuine physical mass-weighted center of mass
50// may be exactly what's wanted. This local helper instead gives THIS
51// file its own, legacy-matching definition, without touching shared
52// code any other caller depends on.
53Eigen::Vector3d UnweightedCentroid(const PolarSegment& seg) {
54 Eigen::Vector3d pos = Eigen::Vector3d::Zero();
55 Index n = 0;
56 for (const PolarSite& site : seg) {
57 pos += site.getPos();
58 ++n;
59 }
60 if (n > 0) {
61 pos /= double(n);
62 }
63 return pos;
64}
65} // namespace
66
68 const Eigen::Matrix3d& box, const EwaldRegistry& registry, double alpha,
69 double thole_a, double r_min, double field_tol, double shell_width,
70 Index n_max, double screening_factor,
71 const std::vector<std::pair<Index, Eigen::Vector3d>>& foreground)
72 : real_space_cutoff_(screening_factor / alpha),
73 segment_radius_(0.0),
74 box_(box),
75 registry_(registry),
76 interactor_(alpha, thole_a),
77 r_min_(r_min),
78 field_tol_(field_tol),
79 shell_width_(shell_width),
80 n_max_(n_max) {
82
83 for (const auto& entry : foreground) {
84 foreground_[entry.first].push_back(entry.second);
85 }
86
87 // Largest site-to-centroid distance anywhere in the registry -- the
88 // margin the segment-granular distance cull needs (see AddFieldAt).
89 // Computed once here rather than per call: the registry's geometry is
90 // fixed for this object's lifetime, the same assumption the neighbor
91 // cache already relies on.
92 for (Index id : registry_.AllIds()) {
93 for (EwaldChargeState state :
96 if (!registry_.Has(id, state)) {
97 continue;
98 }
99 const PolarSegment& segment = registry_.Get(id, state);
100 const Eigen::Vector3d centroid = UnweightedCentroid(segment);
101 for (const PolarSite& site : segment) {
103 std::max(segment_radius_, (site.getPos() - centroid).norm());
104 }
105 }
106 }
107}
108
109std::vector<EwaldRealSpaceSum::Translation>
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);
115
116 for (Index na = -n_max_; na <= n_max_; ++na) {
117 for (Index nb = -n_max_; nb <= n_max_; ++nb) {
118 for (Index nc = -n_max_; nc <= n_max_; ++nc) {
119 Eigen::Vector3d t = double(na) * a + double(nb) * b + double(nc) * c;
120 translations.push_back({t, t.norm()});
121 }
122 }
123 }
124
125 std::sort(
126 translations.begin(), translations.end(),
127 [](const Translation& x, const Translation& y) { return x.r < y.r; });
128 return translations;
129}
130
131template <enum Estatic CE>
132void EwaldRealSpaceSum::AddFieldAt(Index target_segment_id, PolarSite& target,
133 EwaldChargeState source_state,
134 bool include_static) const {
135 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
136 source_state);
137 auto cached = neighbor_cache_.find(cache_key);
138 if (cached != neighbor_cache_.end()) {
139 // Fast path: the real, geometry-determined neighbor set for this
140 // target was already found by an earlier call (see this class's own
141 // documentation for why reusing it across calls for the same target
142 // is safe). Just apply every cached (source, translation) pair
143 // directly -- no shell-by-shell search, no scan of
144 // registry_.AllIds() members that turned out not to matter.
145 for (const auto& entry : cached->second) {
146 // Stored by pointer rather than by id -- see neighbor_cache_'s own
147 // declaration for why. The Has()/Get() guard the id-based version
148 // needed is gone with it: the cache is keyed on source_state, so
149 // every entry in THIS list was registered at THIS state when the
150 // list was built, and segment addresses are stable for the
151 // registry's lifetime.
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 =
156 baseline_shift + translations_[translation_idx].t;
157 for (const PolarSite& source_site : source_segment) {
158 // Shift passed through rather than applied to a copy of the
159 // source site -- see ApplyStaticField's own source_shift note.
160 if (include_static) {
161 interactor_.ApplyStaticField<PolarSite, CE>(source_site, target, t);
162 }
163 interactor_.ApplyInducedField<CE>(source_site, target, t);
164 }
165 }
166 return;
167 }
168
169 // Slow path: first call for this target. Runs the full convergence
170 // search, exactly as before, but additionally records every (source,
171 // translation) pair actually visited, so every subsequent call for
172 // this same target can skip straight to the fast path above.
173 std::vector<std::tuple<const PolarSegment*, Index, Eigen::Vector3d>>
174 visited_pairs;
175
176 // See the cull inside the shell loop below for what this is and why.
177 const double cutoff_with_margin = real_space_cutoff_ + 2.0 * segment_radius_;
178
179 Index shell_start = 0;
180 double shell_edge = 0.0;
181 bool converged = false;
182
183 while (shell_start < Index(translations_.size())) {
184 // Advance shell_edge by shell_width_ until it covers at least the next
185 // not-yet-processed translation, then collect every translation up to
186 // that edge into this shell. Since translations_ is sorted by
187 // distance, this always yields a contiguous, radially-ordered shell.
188 shell_edge =
189 translations_[shell_start].r +
190 (shell_edge > translations_[shell_start].r ? 0.0 : shell_width_);
191 Index shell_end = shell_start;
192 while (shell_end < Index(translations_.size()) &&
193 translations_[shell_end].r <= shell_edge) {
194 ++shell_end;
195 }
196
197 // Field is written into target.V()/V_noE() by ApplyStaticField/
198 // ApplyInducedField as a side effect; recover this shell's own
199 // contribution by differencing target's accumulator before and after
200 // processing the whole shell.
201 const Eigen::Vector3d before_shell =
202 (CE == Estatic::noE_V) ? target.V_noE() : target.V();
203
204 for (Index source_id : registry_.AllIds()) {
205 // No per-segment skip: a target's own segment is treated like any
206 // other. Its COINCIDENT copy is dropped by the foreground
207 // suppression below; its other lattice images are ordinary
208 // background molecules and stay. Legacy agrees -- SetupMidground
209 // excludes by (segment id, na, nb, nc), not by segment id.
210 if (!registry_.Has(source_id, source_state)) {
211 continue;
212 }
213 const PolarSegment& source_segment =
214 registry_.Get(source_id, source_state);
215
216 // Per-source baseline wrap (computed once per source_id, not per
217 // shell-translation index, since it depends only on the target
218 // and source_segment's own representative positions, not on
219 // which small shell-translation t is currently being applied).
220 //
221 // BUG FIX (this session, found via a real production comparison
222 // against legacy): translations_ is sorted and shell-converged
223 // by |t| alone (the raw lattice-translation magnitude), but the
224 // quantity that actually determines a pair's true separation is
225 // |raw_offset + t|, not |t| by itself. A translation with a
226 // LARGE |t| can still be the one that gives the SMALLEST actual
227 // distance, if it happens to cancel a large raw target-source
228 // offset -- and such a translation is exactly what the
229 // shell-convergence criterion (stop once shell radius >= r_min_)
230 // can miss entirely, since it never even considers translations
231 // whose OWN magnitude exceeds r_min_, regardless of what they'd
232 // produce once combined with the raw offset. Confirmed on a real
233 // pair: legacy's own minimum-image search (round-based,
234 // independent of any shell-magnitude cutoff) found r=13.6 bohr
235 // via t=(0,-1,-1)*box (|t|~97 bohr, comfortably past r_min_ in
236 // this run's own ~75.6 bohr setting) for a pair this class's own
237 // unfixed search reported no closer than r=55.4 bohr on, because
238 // it never got there.
239 //
240 // Fixed by mirroring legacy's own two-step structure exactly:
241 // wrap the raw (large, unbounded) target-source separation into
242 // its true minimum image FIRST (fractional-coordinate rounding,
243 // correct for any box_ shape, not just orthorhombic), THEN run
244 // the existing small, bounded shell search of translations_ on
245 // top of that already-small baseline -- exactly as legacy's own
246 // dr12_pbc + L structure does. The shell search only ever needs
247 // to explore the neighborhood right around the true minimum
248 // image, not the raw, unbounded separation, so its own
249 // shell_radius >= r_min_ stopping criterion is now sound (it was
250 // never wrong in isolation -- it was being applied to the wrong
251 // starting point).
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;
259
260 for (Index idx = shell_start; idx < shell_end; ++idx) {
261 // Distance cull. The erfc(alpha*r) screening means a pair's
262 // contribution falls off as fast as erfc does: at alpha*r = 6 a
263 // single pair contributes ~1e-20 of the total field, and the
264 // whole remaining tail sums to far below field_tol_. Without
265 // this test every registered segment is evaluated at every
266 // accepted translation, however far away it is -- measured on a
267 // real 1000-segment system as 14985 (segment, translation)
268 // entries per target, of which only ~22% lie inside the cutoff.
269 // That factor of ~4.6, not any per-pair cost, was the whole
270 // remaining real-space gap against legacy (whose own
271 // PolarNbs list is distance-built, so it never had the excess).
272 //
273 // r_min_ does NOT already do this: it bounds the shell search by
274 // TRANSLATION magnitude |t|, which is unrelated to how far a
275 // given source segment is once that translation is applied.
276 //
277 // The true separation for this (segment, translation) pair is
278 // |min_image_offset - translations_[idx].t| -- the same quantity
279 // the interactor will form, since it evaluates
280 // target - (source + t) with t = baseline_shift +
281 // translations_[idx].t and baseline_shift = raw_offset -
282 // min_image_offset. Compared at segment granularity, so
283 // segment_radius_ (the largest site-to-centroid distance in the
284 // registry) is added twice as a margin: no individual site pair
285 // can then be closer than the cutoff while its segment pair is
286 // culled.
287 const double pair_distance =
288 (min_image_offset - translations_[idx].t).norm();
289 if (pair_distance > cutoff_with_margin) {
291 continue;
292 }
293 const Eigen::Vector3d t = baseline_shift + translations_[idx].t;
294
295 // Foreground suppression. This one periodic copy of this segment
296 // is handled explicitly elsewhere (a polar region), so it must
297 // not also appear in the periodic background -- see the
298 // constructor's own foreground documentation. Only the copy
299 // sitting at the recorded position is dropped; the segment's
300 // other lattice images stay in the sum.
301 if (!foreground_.empty()) {
302 auto fg = foreground_.find(source_id);
303 if (fg != foreground_.end()) {
304 const Eigen::Vector3d shifted_centroid = source_centroid + t;
305 bool suppressed = false;
306 for (const Eigen::Vector3d& fg_pos : fg->second) {
307 if ((shifted_centroid - fg_pos).norm() < kForegroundMatchTol) {
308 suppressed = true;
309 break;
310 }
311 }
312 if (suppressed) {
314 continue;
315 }
316 }
317 }
318
319 // The target's own segment at ZERO translation is the segment
320 // itself: intramolecular pairs plus the r = 0 self-pair, never
321 // part of this sum (AddIntraSegmentCoupling owns the induced
322 // side, the permanent side has its own compensation pass).
323 //
324 // Only where the segment has no recorded foreground copy. Where
325 // it does, the suppression above already decides which copy is
326 // carved out, and that need not be the t = 0 one: a foreground
327 // segment sitting at a nonzero image has its t = 0 copy as a
328 // genuine neighbour. Omitting this guard made the field infinite
329 // in every background solve.
330 const bool source_has_foreground =
331 !foreground_.empty() && foreground_.count(source_id) > 0;
332 if (!source_has_foreground && source_id == target_segment_id &&
333 t.squaredNorm() < kSelfTranslationTol * kSelfTranslationTol) {
334 continue;
335 }
336
337 visited_pairs.emplace_back(&source_segment, idx, baseline_shift);
338 for (const PolarSite& source_site : source_segment) {
339 if (include_static) {
340 interactor_.ApplyStaticField<PolarSite, CE>(source_site, target, t);
341 }
342 interactor_.ApplyInducedField<CE>(source_site, target, t);
343 }
344 }
345 }
346
347 const Eigen::Vector3d after_shell =
348 (CE == Estatic::noE_V) ? target.V_noE() : target.V();
349 const Eigen::Vector3d shell_field = after_shell - before_shell;
350
351 const double shell_radius =
352 translations_[shell_end > shell_start ? shell_end - 1 : shell_start].r;
353 if (shell_radius >= r_min_ && shell_field.norm() < field_tol_) {
354 converged = true;
355 break;
356 }
357
358 shell_start = shell_end;
359 }
360
361 if (!converged) {
362 std::stringstream message;
363 message << "EwaldRealSpaceSum: real-space sum for segment "
364 << target_segment_id
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());
368 }
369
371 cached_entries_ += Index(visited_pairs.size());
372 neighbor_cache_.emplace(cache_key, std::move(visited_pairs));
373}
374
376 const PolarSite& target, EwaldChargeState source_state) const {
377 // See this method's own declaration for what is and is not included.
378 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
379 source_state);
380 auto cached = neighbor_cache_.find(cache_key);
381 if (cached == neighbor_cache_.end()) {
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.");
387 }
388
389 double energy = 0.0;
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) {
396 energy += interactor_.CalcStaticEnergy<PolarSite, PolarSite>(source_site,
397 target, t);
398 }
399 }
400 return energy;
401}
402
404 Index target_segment_id, const std::vector<Eigen::Vector3d>& points,
405 EwaldChargeState source_state) const {
406 // Sources flattened once: AllIds() returns by value and the centroids
407 // are fixed, so recomputing either per point would cost more than the
408 // sum itself on a grid.
409 std::vector<const PolarSegment*> segments;
410 std::vector<Index> segment_ids;
411 std::vector<Eigen::Vector3d> centroids;
412 for (Index source_id : registry_.AllIds()) {
413 if (!registry_.Has(source_id, source_state)) {
414 continue;
415 }
416 const PolarSegment& segment = registry_.Get(source_id, source_state);
417 segments.push_back(&segment);
418 segment_ids.push_back(source_id);
419 centroids.push_back(UnweightedCentroid(segment));
420 }
421
422 const double cutoff_with_margin = real_space_cutoff_ + 2.0 * segment_radius_;
423 const Index n_points = Index(points.size());
424 const Index n_sources = Index(segments.size());
425 Eigen::VectorXd phi = Eigen::VectorXd::Zero(n_points);
426
427 // Hoisted out of both loops below: the box is fixed for this object's
428 // lifetime, and inverting it per (point, source) is n_points * n_sources
429 // 3x3 inversions for one matrix.
430 const Eigen::Matrix3d box_inv = box_.inverse();
431
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)];
435
436 // Unit test charge: every energy routine here reduces to
437 // q*phi - mu.E, so with q = 1 and mu = 0 what comes back is phi.
438 // Built per point on the stack, which is safe only because nothing
439 // below keys anything on its address.
440 PolarSite probe(0, "H", point);
441 probe.Reset();
442 probe.setCharge(1.0);
443 probe.setStaticDipole(Eigen::Vector3d::Zero());
444 probe.setInduced_Dipole(Eigen::Vector3d::Zero());
445
446 double acc = 0.0;
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)];
451
452 // Minimum image first, shell search on top of it -- see AddFieldAt's
453 // own account of why the raw separation cannot be handed to a
454 // search bounded by |t|.
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;
460
461 const auto fg = foreground_.find(source_id);
462 const bool source_has_foreground = fg != foreground_.end();
463
464 // STOP, don't skip. translations_ is sorted by |t| ascending, and
465 // |min_image_offset - t| >= |t| - |min_image_offset|, so once |t|
466 // passes this limit every remaining translation is culled as well.
467 // The bound is exact, not a heuristic: nothing inside the cutoff can
468 // sit beyond it.
469 //
470 // This matters far more here than in the site-based paths above,
471 // which never walk the whole list -- their shell-convergence search
472 // hands them a [shell_start, shell_end) window. Without the break
473 // this loop runs (2*n_max+1)^3 translations, 29791 at the default
474 // n_max of 15, for EVERY (point, source) pair. On a DFT integration
475 // grid against a 1000-segment registry that is of order 1e12
476 // distance evaluations and hours of wall time; with it the surviving
477 // range is a couple of lattice shells.
478 const double t_limit = cutoff_with_margin + min_image_offset.norm();
479
480 for (std::size_t idx = 0; idx < translations_.size(); ++idx) {
481 if (translations_[idx].r > t_limit) {
482 break;
483 }
484 const double pair_distance =
485 (min_image_offset - translations_[idx].t).norm();
486 if (pair_distance > cutoff_with_margin) {
487 continue;
488 }
489 const Eigen::Vector3d t = baseline_shift + translations_[idx].t;
490
491 // Foreground suppression: this one periodic copy is handled
492 // explicitly elsewhere, so it must not also appear here.
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) {
497 if ((shifted_centroid - fg_pos).norm() < kForegroundMatchTol) {
498 suppressed = true;
499 break;
500 }
501 }
502 if (suppressed) {
503 continue;
504 }
505 }
506
507 if (!source_has_foreground && source_id == target_segment_id &&
508 t.squaredNorm() < kSelfTranslationTol * kSelfTranslationTol) {
509 continue;
510 }
511
512 for (const PolarSite& source_site : source_segment) {
513 acc += interactor_.CalcStaticEnergy<PolarSite, PolarSite>(source_site,
514 probe, t);
515 // UNDAMPED: the target is a point in space, not a
516 // point-polarizable site, so there is no overlap for Thole to
517 // correct -- and the rest of the package already treats
518 // [induced dipole] x [QM density] undamped (AOMultipole and
519 // DFTEngine::ExternalRepulsion apply no Thole at all). See
520 // CalcInducedSourceEnergy's own declaration, including why
521 // this cannot be said by zeroing the probe's polarizability.
522 acc += interactor_.CalcInducedSourceEnergy(source_site, probe, t,
523 /*damp=*/false);
524 }
525 }
526 }
527 phi[p] = acc;
528 }
529 return phi;
530}
531
533 const PolarSite& target, EwaldChargeState source_state) const {
534 // Deliberately a near-copy of CalcStaticEnergyAt above rather than a
535 // shared template over the interactor call: the two differ only in
536 // which moments they contract, but they are validated separately and
537 // against different legacy channels (_pp and half of _pu), and a
538 // shared body would let a change to one silently move the other.
539 const std::pair<const PolarSite*, EwaldChargeState> cache_key(&target,
540 source_state);
541 auto cached = neighbor_cache_.find(cache_key);
542 if (cached == neighbor_cache_.end()) {
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.");
548 }
549
550 double energy = 0.0;
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);
558 }
559 }
560 return energy;
561}
562
564 const std::vector<std::pair<Index, PolarSite*>>& targets,
565 EwaldChargeState source_state) const {
566 // See this method's own declaration for what this is for. The search
567 // and the field accumulation share one code path in AddFieldAt, so
568 // the list is built by running it and then undoing its effect on the
569 // target, rather than by duplicating the shell-search logic here
570 // where the two copies could drift apart.
571 for (const auto& entry : targets) {
572 PolarSite& target = *entry.second;
573 const Eigen::Vector3d saved_V = target.V();
574 const Eigen::Vector3d saved_V_noE = target.V_noE();
575 AddFieldAt<Estatic::V>(entry.first, target, source_state, false);
576 target.V() = saved_V;
577 target.V_noE() = saved_V_noE;
578 }
579}
580
583 bool) const;
586 bool) const;
587
588} // namespace xtp
589} // namespace votca
static constexpr double kForegroundMatchTol
Eigen::VectorXd PotentialAtMany(Index target_segment_id, const std::vector< Eigen::Vector3d > &points, EwaldChargeState source_state) const
double CalcInducedSourceEnergyAt(const PolarSite &target, EwaldChargeState source_state) 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 AddFieldAt(Index target_segment_id, PolarSite &target, EwaldChargeState source_state, bool include_static=true) const
std::unordered_map< std::pair< const PolarSite *, EwaldChargeState >, std::vector< std::tuple< const PolarSegment *, Index, Eigen::Vector3d > >, PairHash > neighbor_cache_
std::vector< Translation > translations_
void PrepareNeighborCache(const std::vector< std::pair< Index, PolarSite * > > &targets, EwaldChargeState source_state) const
EwaldRealSpaceInteractor interactor_
static constexpr double kSelfTranslationTol
EwaldRealSpaceSum(const Eigen::Matrix3d &box, const EwaldRegistry &registry, 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={})
Class to represent Atom/Site in electrostatic+polarization.
Definition polarsite.h:36
void setInduced_Dipole(const Eigen::Vector3d &induced_dipole)
Definition polarsite.h:93
const Eigen::Vector3d & V_noE() const
Definition polarsite.h:72
const Eigen::Vector3d & V() const
Definition polarsite.h:68
const Eigen::Vector3d & getPos() const
Definition staticsite.h:80
void setStaticDipole(const Eigen::Vector3d &dipole)
Definition staticsite.h:110
void setCharge(double q)
Definition staticsite.h:104
Charge transport classes.
Definition ERIs.h:28
ClassicalSegment< PolarSite > PolarSegment
template void EwaldRealSpaceSum::AddFieldAt< Estatic::V >(Index, PolarSite &, EwaldChargeState, bool) const
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26