votca 2026-dev
Loading...
Searching...
No Matches
ewaldrealspacesum.h
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#pragma once
21#ifndef VOTCA_XTP_EWALDREALSPACESUM_H
22#define VOTCA_XTP_EWALDREALSPACESUM_H
23
24// Standard includes
25#include <map>
26#include <string>
27#include <tuple>
28#include <unordered_map>
29#include <utility>
30#include <vector>
31
32// Local VOTCA includes
33#include "eigen.h"
35#include "ewaldregistry.h"
36
66
67namespace votca {
68namespace xtp {
69
71 public:
72 // box: columns are the lattice vectors a, b, c (matching
73 // the Topology::getBox() convention).
74 // registry: source of every segment's multipole/induced-dipole state.
75 // Stored by reference; the registry must outlive this object, and
76 // AddFieldAt() reads its current state (e.g. updated induced dipoles).
77 // alpha, thole_a: passed through to the underlying
78 // EwaldRealSpaceInteractor.
79 // r_min: minimum shell radius (bohr) before convergence may be declared,
80 // regardless of how small any individual shell's contribution is.
81 // field_tol: convergence threshold (atomic units) on a shell's
82 // contribution to the field magnitude.
83 // shell_width: radial bin width (bohr) used to group periodic images
84 // into shells.
85 // n_max: safety cap on the lattice-translation search box (translations
86 // with |na|,|nb|,|nc| <= n_max are considered candidates). If
87 // convergence is not reached within this box, AddFieldAt() throws.
88 // screening_factor: sets the real-space distance cutoff to
89 // screening_factor / alpha. Beyond it a pair contributes at most
90 // ~erfc(screening_factor); the default 6.0 gives ~2e-17 and discards
91 // 78.7% of the pairs on a 1000-segment box (4.5x faster real-space
92 // sum, converged dipoles unchanged in all printed digits). The number
93 // of retained pairs grows as the cube of the cutoff. Values below ~4
94 // affect results at the 1e-11 level.
95 // Kept as the last parameter so that existing positional calls are
96 // not silently reinterpreted; add new parameters at the end.
97 //
98 // foreground: (segment id, centroid position) pairs treated explicitly
99 // by a polar region and therefore removed from this sum (the MM/MM or
100 // QM/MM carve-out). Each entry suppresses only the periodic copy at
101 // the given position; all other images remain in the background.
102 //
103 // Copies are matched by position, not by minimum image: two
104 // foreground segments can be up to twice the region cutoff apart, so
105 // the minimum image may differ from the copy held by the polar region
106 // (legacy likewise keys its ForegroundTable on (id, na, nb, nc)).
107 // Positions come from JobTopology and are already centred, so
108 // kForegroundMatchTol only absorbs round-off.
109 //
110 // Empty by default, which is the plain periodic background.
112 const Eigen::Matrix3d& box, const EwaldRegistry& registry, double alpha,
113 double thole_a, double r_min, double field_tol,
114 double shell_width = 0.945, Index n_max = 15,
115 double screening_factor = 6.0,
116 const std::vector<std::pair<Index, Eigen::Vector3d>>& foreground = {});
117
118 // Accumulates the total intermolecular real-space field into target's
119 // V()/V_noE() accumulators (EwaldRealSpaceInteractor convention) from
120 // every segment registered at source_state, excluding the
121 // zero-translation copy of target_segment_id (see class documentation).
122 // The field is evaluated at target.getPos(); target need not be
123 // registered. Throws std::runtime_error if convergence is not reached
124 // within n_max shells.
125 //
126 // include_static = false skips the permanent-multipole field and
127 // accumulates only the induced-dipole field. EwaldPeriodicDipoleOperator
128 // uses this, since the permanent field is a constant that cancels in
129 // RawMultiply(v) - baseline_; it is ~53% of the per-pair cost.
130 template <enum Estatic CE>
131 void AddFieldAt(Index target_segment_id, PolarSite& target,
132 EwaldChargeState source_state,
133 bool include_static = true) const;
134
135 // AddFieldAt for a whole batch, in parallel. Every missing neighbour
136 // list is built first by PrepareNeighborCache (parallel search, serial
137 // insertion), after which the field pass only reads this object.
138 // Targets are spread over threads by site: repeated entries of the same
139 // site are applied by one thread, in input order. The result is
140 // bit-identical to calling AddFieldAt on every entry in turn: each
141 // target sees the same (source, translation) list, applied in the same
142 // order.
143 //
144 // Use this (or PrepareNeighborCache first) rather than an OpenMP loop
145 // around AddFieldAt: AddFieldAt inserts into neighbor_cache_ on a miss,
146 // and concurrent insertion into an unordered_map is a data race.
147 template <enum Estatic CE>
148 void AddFieldAtMany(const std::vector<std::pair<Index, PolarSite*>>& targets,
149 EwaldChargeState source_state,
150 bool include_static = true) const;
151
152 // erfc-screened interaction energy between the PERMANENT multipoles of
153 // one target site and of every background source, over the same cached
154 // neighbour list (foreground suppression and distance cull included)
155 // that AddFieldAt uses, so energy and field describe the same system.
156 // Induced dipoles do not enter: the foreground's are handled by
157 // PolarRegion (E_polar_ext), the background's by
158 // CalcInducedSourceEnergyAt.
159 //
160 // Requires the neighbour list of this target (AddFieldAt or
161 // PrepareNeighborCache must have run); it is not built here, to keep
162 // the energy query cheap and independent of call order. The list
163 // already encodes the own-segment exclusion, so no segment id is
164 // needed.
165 double CalcStaticEnergyAt(const PolarSite& target,
166 EwaldChargeState source_state) const;
167
168 // POTENTIAL at arbitrary points, batched. Total of both channels, to
169 // match EwaldReciprocalSpaceSum::PotentialAtMany and the field
170 // AddFieldAt delivers: permanent moments through CalcStaticEnergy,
171 // background induced dipoles through CalcInducedSourceEnergy.
172 //
173 // Uses no neighbour cache: the cache is keyed on site addresses, and
174 // one entry per grid point (e.g. a DFT grid) would be prohibitively
175 // large. The distance cull, foreground suppression and zero-translation
176 // self-skip of AddFieldAt are therefore duplicated here; consistency is
177 // tested by unit_probe_potential_reproduces_the_static_energy and the
178 // EwaldRegion comparison with a direct lattice sum. Instead of the
179 // shell-convergence exit only the geometric cutoff is used, which
180 // visits a superset of the shell search.
181 //
182 // target_segment_id decides only whether the zero-translation
183 // self-pair is skipped, and that skip is disabled for any source with
184 // a foreground copy. Pass a foreground segment's id: a point that is
185 // not a site of its own wants no self-skip.
186 Eigen::VectorXd PotentialAtMany(Index target_segment_id,
187 const std::vector<Eigen::Vector3d>& points,
188 EwaldChargeState source_state) const;
189
190 // erfc-screened, Thole-damped energy between the BACKGROUND induced
191 // dipoles (sources) and the PERMANENT moments of one foreground target,
192 // over the same neighbour list as CalcStaticEnergyAt (same cache
193 // requirement). This is the [fg permanent] x [bg induced] term; see
194 // EwaldRealSpaceInteractor::CalcInducedSourceEnergy for why it is
195 // damped where the permanent energies are not.
196 double CalcInducedSourceEnergyAt(const PolarSite& target,
197 EwaldChargeState source_state) const;
198
199 // Builds the neighbour list of every target that does not have one
200 // yet, WITHOUT applying any field: the search runs on a scratch copy of
201 // each target, so the targets themselves are not touched. Targets that
202 // already have a list (and repeated entries) are skipped.
203 //
204 // The searches run in parallel; the lists and statistics are then
205 // inserted serially. Afterwards AddFieldAt does not modify this object
206 // and may be called concurrently for distinct targets. Without this
207 // call AddFieldAt builds lists on demand and must not run in parallel.
208 //
209 // converge_static_field selects the field whose shell contribution
210 // terminates the search, as include_static does for AddFieldAt: true
211 // for permanent plus induced, false for induced only. Pass the value
212 // the first AddFieldAt call would use, so that the list is identical
213 // to the on-demand one.
215 const std::vector<std::pair<Index, PolarSite*>>& targets,
216 EwaldChargeState source_state, bool converge_static_field = false) const;
217
218 // Neighbour-list statistics over every target whose list has been
219 // built so far. entries is the total number of (source segment,
220 // translation) pairs kept; culled is how many were rejected by the
221 // distance cutoff. Both are zero until the first AddFieldAt call.
227 double entries_per_target() const {
228 return targets > 0 ? double(entries) / double(targets) : 0.0;
229 }
230 double culled_fraction() const {
231 const Index seen = entries + culled;
232 return seen > 0 ? double(culled) / double(seen) : 0.0;
233 }
234 };
239 double RealSpaceCutoff() const { return real_space_cutoff_; }
240
241 private:
242 // One periodic image translation vector, tagged with its distance from
243 // the origin so shells can be built by sorting once.
244 struct Translation {
245 Eigen::Vector3d t;
246 double r;
247 };
248 std::vector<Translation> GenerateSortedTranslations() const;
249
251 std::vector<std::tuple<const PolarSegment*, Index, Eigen::Vector3d>>;
252
253 // Per-search statistics, accumulated locally so that a search can run
254 // without writing to this object, and added to the member counters by
255 // whoever inserts the list.
260
261 // The shell-by-shell convergence search for one target. Accumulates
262 // the field into scratch (a copy of the target: the convergence test
263 // needs a field to watch) and returns every (source, translation) pair
264 // it applied, in the order it applied them. Writes nothing in this
265 // object, so concurrent calls are safe.
266 template <enum Estatic CE>
267 NeighborList SearchNeighbors(Index target_segment_id, PolarSite& scratch,
268 EwaldChargeState source_state,
269 bool include_static, SearchCounts& counts) const;
270
271 // Applies a neighbour list to target: the fast path of AddFieldAt.
272 template <enum Estatic CE>
273 void ApplyNeighborList(const NeighborList& list, PolarSite& target,
274 bool include_static) const;
275
276 // PrepareNeighborCache for the search AddFieldAt<CE>(..., include_static)
277 // would run on a miss, so a list built here is the one the on-demand
278 // path would have built. AddFieldAtMany<CE> uses this directly.
279 template <enum Estatic CE>
281 const std::vector<std::pair<Index, PolarSite*>>& targets,
282 EwaldChargeState source_state, bool include_static) const;
283
284 // Inserts one list and its statistics. Not thread-safe.
286 const std::pair<const PolarSite*, EwaldChargeState>& key,
287 NeighborList list, const SearchCounts& counts) const;
288
289 // std::pair has no std::hash specialization; combine the hashes of
290 // the pointer and the enum (hashable since C++14).
291 struct PairHash {
292 std::size_t operator()(
293 const std::pair<const PolarSite*, EwaldChargeState>& key) const {
294 return std::hash<const PolarSite*>()(key.first) ^
295 (std::hash<EwaldChargeState>()(key.second) << 1);
296 }
297 };
298
299 // Real-space screening cutoff: the separation beyond which a pair's
300 // erfc(alpha*r)-screened contribution is negligible, set to
301 // screening_factor / alpha (see the constructor). This is a cost
302 // cutoff only: it never extends the sum, and r_min_/field_tol_ still
303 // govern how far the shell search goes.
305 // Tolerance for deciding that a shifted source segment coincides with a
306 // foreground segment. Round-off only -- see the constructor.
307 static constexpr double kForegroundMatchTol = 1e-4;
308 // Tolerance for recognising the ZERO lattice translation, used to skip
309 // a target's own segment at its own position (intramolecular, and
310 // containing the r = 0 self-pair) while keeping its other images. The
311 // quantity tested is built from an exactly-zero baseline shift plus an
312 // exactly-zero lattice vector, so this only has to absorb round-off;
313 // the next-smallest translation is a full lattice vector away.
314 static constexpr double kSelfTranslationTol = 1e-8;
315 // Foreground copies to suppress, grouped by segment id. Most segments
316 // have no entry at all; those that do usually have exactly one.
317 std::map<Index, std::vector<Eigen::Vector3d>> foreground_;
318 // Neighbour-list statistics, accumulated as the cache is built. The
319 // number of entries determines the cost per solver iteration; it is
320 // reported so that the effect of alpha, r_min and the cull is visible.
324 // How many (segment, image) pairs were suppressed as foreground.
326 // Largest site-to-centroid distance over every registered segment,
327 // used as the margin when the cutoff (a per-site-pair quantity) is
328 // applied at segment granularity.
330
331 Eigen::Matrix3d box_;
334 double r_min_;
338
339 // Generated once at construction (depends only on the box, not on the
340 // target position), reused by every AddFieldAt() call.
341 std::vector<Translation> translations_;
342
343 // Neighbour list (source segment, translation) of each target's
344 // converged sum, analogous to legacy RThread::PolarNbs(). Keyed by
345 // (target site address, source charge state), since the set of
346 // registered sources depends on the charge state.
347 // Sources are stored by pointer rather than id: an id lookup costs two
348 // std::map lookups (~59 ns vs ~3 ns per entry, ~3e7 entries per
349 // iteration for 1000 segments). This is safe because the registry
350 // outlives this object and stores segments in a std::map, whose
351 // elements have stable addresses.
352 mutable std::unordered_map<std::pair<const PolarSite*, EwaldChargeState>,
355};
356
357} // namespace xtp
358} // namespace votca
359
360#endif // VOTCA_XTP_EWALDREALSPACESUM_H
NeighborStats GetNeighborStats() const
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_
static constexpr double kSelfTranslationTol
void ApplyNeighborList(const NeighborList &list, PolarSite &target, bool include_static) const
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={})
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.
Definition polarsite.h:36
Provides a means for comparing floating point numbers.
Definition basebead.h:33
Eigen::Index Index
Definition types.h:26
std::size_t operator()(const std::pair< const PolarSite *, EwaldChargeState > &key) const