votca 2026-dev
Loading...
Searching...
No Matches
ewaldreciprocalspacesum.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_EWALDRECIPROCALSPACESUM_H
22#define VOTCA_XTP_EWALDRECIPROCALSPACESUM_H
23
24// Standard includes
25#include <complex>
26#include <functional>
27#include <vector>
28
29// Local VOTCA includes
30#include "eeinteractor.h"
31#include "eigen.h"
32#include "ewaldregistry.h"
33
109
110namespace votca {
111namespace xtp {
112
114 public:
115 // box: columns are the lattice vectors a, b, c.
116 // registry: source of every segment's multipole/induced-dipole state.
117 // Stored by reference; must outlive this object.
118 // alpha: Ewald splitting parameter, shared with whatever
119 // EwaldRealSpaceInteractor/EwaldRealSpaceSum instance is used
120 // alongside this class for the same background.
121 // k_max: spherical cutoff (bohr^-1) on |k|.
122 EwaldReciprocalSpaceSum(const Eigen::Matrix3d& box,
123 const EwaldRegistry& registry, double alpha,
124 double k_max);
125
126 // Number of k-vectors this instance will sum over (fixed at
127 // construction) -- useful for logging/progress reporting before or
128 // during a call to AddFieldAt/AddFieldAtMany, since this count (not
129 // k_max itself) is what actually determines the cost of a call: it
130 // scales with the cube of k_max relative to the box's own reciprocal
131 // lattice spacing, so a k_max that looks modest can still generate an
132 // unexpectedly large count for a large box.
133 std::size_t NumKVectors() const { return kvectors_.size(); }
134
135 // The position-independent 3x3 matrix M such that a site's own dipole
136 // moment mu produces a spurious reciprocal-space "self-field" -M*mu at
137 // its own position -- a known Ewald-summation artifact: since this
138 // class's own structure factor S(k) never excludes any site, not even
139 // the one a field is being evaluated at (see class documentation), a
140 // site with a nonzero dipole moment feels a nonzero contribution from
141 // itself. This does NOT happen for a pure charge (monopole) term --
142 // that self-contribution is exactly zero by construction (a charge's
143 // own term in S(k), evaluated back at its own position, is purely real
144 // -- q*exp(-i*k.r)*exp(i*k.r) = q -- and this class's own field formula
145 // only ever keeps the imaginary part) -- so this matrix, and the
146 // correction it enables, only ever matters for dipoles (static or
147 // induced), never for bare charges.
148 //
149 // M_ab = (4/3) * alpha^3 / sqrt(pi) * delta_ab
150 //
151 // -- the standard analytic Ewald self-term, i.e. the r -> 0 limit of
152 // the erf-screened dipole field, and hence isotropic. Legacy applies
153 // exactly this too (EwdInteractor::FU12_ERF_At_By's own R1 < 1e-2
154 // branch), though it does so in REAL space, as a separate "atomic ERF
155 // self-interaction correction" pass rather than anywhere in its
156 // reciprocal-space code -- worth knowing, since an earlier version of
157 // this comment concluded from a reciprocal-space-only trace that
158 // legacy applied no self-correction at all, which is false.
159 //
160 // This deliberately is NOT the discrete k-lattice sum over this
161 // class's own k-vector set, even though that sum is what AddFieldAtMany
162 // numerically produces at r = 0. That sum is the site's erf-screened
163 // field from itself AND all its own periodic images; only the n = 0
164 // part is the artifact to remove, and the image terms are real physics.
165 // See SelfFieldMatrix's own definition for the measured size of the
166 // difference (1.63% in a realistic box) and why tightening k_max does
167 // not reduce it.
168 //
169 // Neither this class nor EwaldRealSpaceSum subtracts this contribution
170 // automatically anywhere. A caller wanting the physically-corrected
171 // (self-interaction-free) field must subtract SelfFieldMatrix()*mu
172 // itself, for whichever site's own dipole moment mu is being evaluated.
173 Eigen::Matrix3d SelfFieldMatrix() const;
174
175 // Progress callback signature: called as (k_vectors_done,
176 // k_vectors_total) periodically (not necessarily every single
177 // k-vector) during the expensive part of AddFieldAtMany. Pass an empty
178 // std::function (the default) for no callback at all.
179 using ProgressCallback = std::function<void(std::size_t, std::size_t)>;
180
181 // Accumulates the total reciprocal-space field into target's own
182 // V()/V_noE() accumulators (matching EwaldRealSpaceSum's convention),
183 // generated by every registered segment at charge state source_state
184 // (see class documentation on scope -- no segment is excluded, not
185 // even target's own). Convenience wrapper around AddFieldAtMany for a
186 // single target; prefer AddFieldAtMany when computing the field at
187 // many sites at once (see the class documentation's performance note).
188 template <enum Estatic CE>
189 void AddFieldAt(PolarSite& target, EwaldChargeState source_state) const;
190
191 // Batched form of AddFieldAt: computes the field at every target site
192 // in targets, sharing a single pass over every k-vector's structure
193 // factor across the whole batch. target pointers must be non-null and
194 // must remain valid for the duration of the call; the same site may
195 // appear more than once (field is simply accumulated twice in that
196 // case, same as two separate AddFieldAt calls would do). progress, if
197 // non-empty, is called periodically during the k-vector loop (see
198 // ProgressCallback).
199 template <enum Estatic CE>
200 void AddFieldAtMany(
201 const std::vector<PolarSite*>& targets, EwaldChargeState source_state,
202 const ProgressCallback& progress = ProgressCallback()) const;
203
204 // POTENTIAL at arbitrary points, batched -- the exact counterpart of
205 // AddFieldAtMany, and like it carrying BOTH channels: the structure
206 // factor is built from getStaticDipole() + getInducedDipole(), so the
207 // permanent and induced backgrounds are already summed. It is what
208 // CalcStaticEnergyBetween and CalcInducedSourceEnergyBetween report
209 // ADDED TOGETHER, not either one alone.
210 //
211 // Setting q = 1, mu = 0 in CalcStaticEnergyBetween leaves
212 // conj(s_fg) = exp(+i k.r), so what a unit test charge at r reports as
213 // its own energy is
214 //
215 // phi(r) = (4*pi/V) * sum_{k!=0} weight(k) * Re[ S(k) exp(i k.r) ]
216 //
217 // Consistent with AddFieldAtMany by construction: -grad of that
218 // expression is its Im[S exp(i k.r)] * k.
219 //
220 // Batched because S(k) is rebuilt on every call and costs
221 // O(N_k * N_sites) -- for a DFT grid, computing phi a point at a time
222 // would repeat that tens of thousands of times over. Here it is paid
223 // once and replayed against every point.
224 Eigen::VectorXd PotentialAtMany(
225 const std::vector<Eigen::Vector3d>& points, EwaldChargeState source_state,
226 const ProgressCallback& progress = ProgressCallback()) const;
227
228 // Reciprocal-space PERMANENT-multipole interaction energy between a
229 // supplied set of sites (the foreground) and the rest of the periodic
230 // cell (the background):
231 //
232 // E = sum_k (4*pi/V) * exp(-k^2/4*alpha^2)/k^2 * Re[ S_fg*(k) . S_bg(k) ]
233 //
234 // `foreground` lists the sites whose moments enter S_fg, together with
235 // the position each one occupies. These are the foreground's OWN sites
236 // -- in the job's own charge state -- not the background copies they
237 // were carved from. That distinction is the whole point of the cross
238 // term: the interaction being computed is the one the job's charge
239 // state actually has with the medium, so taking the moments from the
240 // neutral background copies instead would make a charged job report
241 // the neutral job's reciprocal energy, and the difference between
242 // charge states -- the one quantity a site-energy calculation is
243 // after -- would silently lose this contribution altogether.
244 //
245 // `background_exclusions` is therefore supplied separately: which
246 // sites carry the foreground's moments and which registered sites must
247 // be held out of S_bg are two different questions, and conflating them
248 // is what produced the bug just described. Identity is by address.
249 //
250 // S_fg and S_bg are accumulated SEPARATELY rather than obtaining S_bg
251 // by subtracting S_fg from the total. The foreground is a tiny
252 // fraction of the cell, so that subtraction would difference two large
253 // nearly-equal numbers and lose exactly the precision the cross term
254 // needs.
255 //
256 // Permanent multipoles only, matching EwaldRealSpaceSum's own
257 // CalcStaticEnergyAt: the induced contribution reaches the polar
258 // region through the field, and adding it here too would double-count.
260 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>&
261 foreground,
262 const std::vector<const PolarSite*>& background_exclusions,
263 EwaldChargeState source_state) const;
264
265 // The same reciprocal cross sum, but with the background entering
266 // through its INDUCED dipoles instead of its permanent moments:
267 //
268 // S_bg(k) = sum_bg (-i k.mu_ind) exp(-i k.r)
269 //
270 // while the foreground still contributes its permanent moments. This
271 // is the reciprocal partner of
272 // EwaldRealSpaceSum::CalcInducedSourceEnergyAt; see
273 // EwaldRealSpaceInteractor::CalcInducedSourceEnergy for what the term
274 // is and why it exists.
275 //
276 // No Thole damping, deliberately: damping is a short-range correction
277 // and a reciprocal-space sum has no short range to correct. Its
278 // real-space partner IS damped, which is legacy's convention too and
279 // is why the two together are only approximately alpha-independent.
281 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>&
282 foreground,
283 const std::vector<const PolarSite*>& background_exclusions,
284 EwaldChargeState source_state) const;
285
286 private:
287 struct KVector {
288 Eigen::Vector3d k;
289 double k2;
290 };
291 std::vector<KVector> GenerateKVectors() const;
292
293 // Structure factor S(k) (see class documentation), one entry per
294 // kvectors_, summed over every site of every registered segment at
295 // source_state. This is the expensive O(N_k * N_sites) loop
296 // AddFieldAtMany's own progress callback reports on.
297 std::vector<std::complex<double>> TotalStructureFactors(
298 EwaldChargeState source_state, const ProgressCallback& progress) const;
299
300 Eigen::Matrix3d box_;
301 double volume_;
303 double alpha_;
304 double k_max_;
305
306 std::vector<KVector> kvectors_;
307};
308
309} // namespace xtp
310} // namespace votca
311
312#endif // VOTCA_XTP_EWALDRECIPROCALSPACESUM_H
EwaldReciprocalSpaceSum(const Eigen::Matrix3d &box, const EwaldRegistry &registry, double alpha, double k_max)
double CalcInducedSourceEnergyBetween(const std::vector< std::pair< const PolarSite *, Eigen::Vector3d > > &foreground, const std::vector< const PolarSite * > &background_exclusions, EwaldChargeState source_state) const
double CalcStaticEnergyBetween(const std::vector< std::pair< const PolarSite *, Eigen::Vector3d > > &foreground, const std::vector< const PolarSite * > &background_exclusions, EwaldChargeState source_state) const
std::function< void(std::size_t, std::size_t)> ProgressCallback
std::vector< std::complex< double > > TotalStructureFactors(EwaldChargeState source_state, const ProgressCallback &progress) const
Eigen::VectorXd PotentialAtMany(const std::vector< Eigen::Vector3d > &points, EwaldChargeState source_state, const ProgressCallback &progress=ProgressCallback()) const
void AddFieldAtMany(const std::vector< PolarSite * > &targets, EwaldChargeState source_state, const ProgressCallback &progress=ProgressCallback()) const
void AddFieldAt(PolarSite &target, EwaldChargeState source_state) const
std::vector< KVector > GenerateKVectors() 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