votca 2026-dev
Loading...
Searching...
No Matches
ewaldrealspaceinteractor.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_EWALDREALSPACEINTERACTOR_H
22#define VOTCA_XTP_EWALDREALSPACEINTERACTOR_H
23
24// Local VOTCA includes
25#include "classicalsegment.h"
26#include "eeinteractor.h"
27#include "eigen.h"
28
71
72namespace votca {
73namespace xtp {
74
76 public:
77 explicit EwaldRealSpaceInteractor(double alpha, double thole_a = 0.39)
78 : alpha_(alpha), thole_(thole_a) {};
79
80 // Applies the erfc-screened field generated by a permanent multipole
81 // (charge + dipole, taken from site1's static moments) at site2, and
82 // returns the corresponding screened interaction energy. Undamped by
83 // Thole -- site1 is treated as a permanent source regardless of whether
84 // it happens to be a PolarSite.
85 //
86 // CE selects which of site2's field accumulators (V() or V_noE()) is
87 // updated, mirroring eeInteractor::ApplyStaticField.
88 //
89 // source_shift is a periodic-image translation added to site1's own
90 // position, WITHOUT modifying site1. It exists purely to avoid
91 // materializing a shifted copy of the source site in the caller's hot
92 // loop: EwaldRealSpaceSum evaluates every (target site, source site,
93 // translation) triple, and constructing a PolarSite copy per triple
94 // dominated the real-space cost (a PolarSite carries a Vector9d of
95 // multipoles, two 3x3 matrices, several vectors, and a std::string
96 // element name -- so the copy is far from free, and there are ~1e8 of
97 // them per solver iteration on a 1000-segment system). Legacy's own
98 // equivalent takes the shift as an argument for the same reason (see
99 // EwdInteractor::FU12_ERFC_At_By's own vec &s overload). Defaults to
100 // zero, so existing two-argument calls are unaffected.
101 template <class T, enum Estatic CE>
102 double ApplyStaticField(
103 const T& site1, PolarSite& site2,
104 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
105
106 // SUBTRACTS the erf(alpha*r)-screened (not erfc) field of a permanent
107 // multipole at site2 for two sites of the same molecule, as legacy
108 // EwdInteractor::FP12_ERF_At_By ("compensation term"). The reciprocal-space
109 // sum excludes no pair, so it contains the erf(alpha*r)/r part of every
110 // intramolecular static-static pair; subtracting it makes the net
111 // intramolecular static-static interaction zero, as in legacy and in
112 // molecular mechanics (bonded terms already account for it; see
113 // EwaldPeriodicDipoleOperator). Same CE/accumulator convention as
114 // ApplyStaticField.
115 //
116 // source_shift (see ApplyStaticField's own note) is last in the
117 // parameter list, not next to the sites it belongs with: a defaulted
118 // parameter inserted mid-signature silently reinterprets existing
119 // positional arguments at call sites that still compile. Keep new
120 // parameters at the end.
121 template <class T, enum Estatic CE>
123 const T& site1, PolarSite& site2,
124 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
125
126 // The induced-dipole counterpart of ApplyErfStaticFieldCorrection:
127 // removes the erf-screened (reciprocal-space) field that site1's
128 // INDUCED dipole contributes at site2. Subtracted, like its static
129 // sibling.
130 //
131 // Deliberately NOT Thole-damped. Thole damping is a short-range
132 // real-space construct; the reciprocal-space sum this term removes
133 // has no damping of its own, so damping the removal would not cancel
134 // what was actually added. Legacy agrees -- EwdInteractor::
135 // FU12_ERF_At_By uses the bare C1/C2 functions with no l3/l5 factor
136 // anywhere, unlike its erfc sibling FU12_ERFC_At_By.
137 //
138 // Built on the same EvaluateSource path as the static correction
139 // rather than re-deriving the field expression, so it inherits that
140 // path's already-validated sign convention instead of depending on a
141 // fresh derivation of it.
142 template <enum Estatic CE>
144 const PolarSite& site1, PolarSite& site2,
145 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
146
147 // Applies the erfc-screened, Thole-damped field generated by site1's
148 // induced dipole at site2, and returns the corresponding energy.
149 // source_shift: see ApplyStaticField's own note above.
150 template <enum Estatic CE>
151 double ApplyInducedField(
152 const PolarSite& site1, PolarSite& site2,
153 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
154
155 // erfc-screened interaction energy between two permanent multipole
156 // sites (charge + dipole only), undamped by Thole.
157 //
158 // source_shift, as elsewhere, is last in the parameter list: a
159 // defaulted parameter inserted mid-signature silently reinterprets
160 // existing positional arguments at call sites that still compile.
161 template <class S1, class S2>
162 double CalcStaticEnergy(
163 const S1& site1, const S2& site2,
164 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
165
166 // The erf-screened counterpart of CalcStaticEnergy: the permanent
167 // multipole interaction energy that the RECIPROCAL sum contributes for
168 // this pair. Subtracting it removes a foreground copy's energy from
169 // the periodic total, exactly as ApplyErfStaticFieldCorrection does
170 // for the field.
171 //
172 // Undamped, for the same reason its field sibling is: the
173 // reciprocal-space contribution it removes carries no Thole damping.
174 // Takes the analytic r -> 0 limit for coincident sites, which is the
175 // common case here rather than an edge case -- every foreground target
176 // sits inside a suppressed copy.
177 template <class S1, class S2>
178 double CalcErfStaticEnergy(
179 const S1& site1, const S2& site2,
180 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
181
182 // erfc-screened, Thole-damped interaction energy between two induced
183 // dipoles.
184 double CalcInducedEnergy(const PolarSite& site1,
185 const PolarSite& site2) const;
186
187 // erfc-screened, Thole-damped interaction energy between site1's
188 // INDUCED dipole (as source) and site2's PERMANENT moments (as
189 // target) -- the [background induced] x [foreground permanent] term.
190 //
191 // This is the half of legacy's _pu channel that nothing else in this
192 // code path covers. PolarRegion's own E_polar_ext contracts the
193 // FOREGROUND's induced dipoles against the delivered field, which
194 // gives [fg induced] x [bg permanent + bg induced]; the permanent
195 // energies here give [fg permanent] x [bg permanent]. The remaining
196 // corner, [fg permanent] x [bg induced], has no other home, and its
197 // absence was measurable: it accounts for the factor ~1.9 by which
198 // the neutral job's induced energy fell short of legacy's.
199 //
200 // Damped, unlike the permanent-multipole energies, because the source
201 // is an induced dipole and Thole damping is exactly the short-range
202 // correction to induced-dipole interactions. l3 multiplies the r^-3
203 // terms (the potential, and the field's mu term), l5 the r^-5 term,
204 // matching ApplyInducedField's own combination so the two cannot
205 // drift apart.
206 //
207 // NOTE that this makes the total mildly alpha-dependent, since the
208 // reciprocal-space partner carries no damping (a long-range sum has
209 // no short-range correction to apply). Legacy has the identical
210 // structure -- FU12_ERFC_At_By damps, FU12_ERF_At_By does not -- so
211 // this reproduces its convention rather than improving on it. The
212 // decomposition IS exactly alpha-independent once damping is switched
213 // off, which is how the unit test pins it down.
214 //
215 // damp = false is for a target that is a POINT rather than a site: a
216 // field-evaluation point, as EwaldRealSpaceSum::PotentialAtMany uses.
217 // Thole damping corrects the overlap of two POINT-POLARIZABLE sites,
218 // and a coordinate in space is not one of those -- so there is nothing
219 // to correct, and the undamped screened interaction is the whole
220 // answer.
221 //
222 // This is not a free choice, it is what the rest of the package
223 // already does for the same physical interaction. A QM region gets the
224 // classical regions' induced dipoles through AOMultipole (which reads
225 // getDipole(), permanent + induced) and through DFTEngine::
226 // ExternalRepulsion -> eeInteractor::CalcStaticEnergy_site: neither
227 // applies Thole. In eeInteractor, Thole lives only in
228 // FillTholeInteraction, which the induction solve and the polar-polar
229 // energies use and the QM path never touches. Damping here would give
230 // a three-region job two different conventions for [induced dipole] x
231 // [QM density] in one Hamiltonian, decided by which code path the
232 // dipole arrived through.
233 //
234 // It cannot be expressed by giving the probe no polarizability.
235 // ComputeThole would then form au3 = 0, read that as COMPLETE overlap,
236 // and damp maximally at every distance (l3 = l5 = 0, c3 = -B1_erf
237 // where undamped wants +B1). A PolarSite cannot be built without a
238 // polarizability in any case -- its constructor assigns one from the
239 // element name -- so the probe unavoidably carries a number that means
240 // nothing, and this flag is how the caller says so.
242 const PolarSite& site1, const PolarSite& site2,
243 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero(),
244 bool damp = true) const;
245
246 // The erf-screened counterpart of CalcInducedSourceEnergy, used to
247 // remove a coincident foreground copy from the reciprocal side, just
248 // as CalcErfStaticEnergy does for the permanent channel.
249 //
250 // UNDAMPED, matching ApplyErfInducedFieldCorrection: the
251 // reciprocal-space contribution this removes carries no Thole damping,
252 // so damping the removal would not cancel what was actually added.
254 const PolarSite& site1, const PolarSite& site2,
255 const Eigen::Vector3d& source_shift = Eigen::Vector3d::Zero()) const;
256
257 // The screened-Coulomb derivative functions B0, B1, B2 for separation r:
258 // B0 = erfc(alpha*r) / r
259 // B_l = [ (2l-1)*B_{l-1} + (2*alpha)^(2l-1) / sqrt(pi) * exp(-alpha^2 r^2)
260 // ] / r^2
261 // B0 supports charge-charge, B1 additionally supports charge-dipole,
262 // B2 additionally supports dipole-dipole. Cross-checked against
263 // EwdInteractor::UpdateAllBls (xtp/ewald/ewaldactor.h) for correctness
264 // -- that file was deleted with the legacy code; the check is in the
265 // git history, not in the tree.
266 // Public (rather than private) so the free helper functions in
267 // ewaldrealspaceinteractor.cc's anonymous namespace can take it by
268 // value/reference as a plain parameter type.
269 // Potential at a point, r_vec = point - source position, of a source with
270 // charge q and dipole mu (static plus induced). For a unit-charge probe
271 // ScreenedPotential equals CalcStaticEnergy + CalcInducedSourceEnergy with
272 // damp=false, and ErfPotential equals CalcErfStaticEnergy +
273 // CalcErfInducedSourceEnergy, coincidence limit included -- with one erfc
274 // and one exp instead of three of each. Used on DFT integration grids.
275 double ScreenedPotential(double q, const Eigen::Vector3d& mu,
276 const Eigen::Vector3d& r_vec) const;
277 double ErfPotential(double q, const Eigen::Vector3d& mu,
278 const Eigen::Vector3d& r_vec) const;
279
280 struct BFunctions {
281 double B0;
282 double B1;
283 double B2;
284 };
285
286 // Thole damping factors l3, l5 (multiplying the r^-3 and r^-5 field
287 // terms respectively) for a pair of induced-dipole sites, using the
288 // exponential Thole model already implemented in
289 // eeInteractor::FillTholeInteraction. Returns {1.0, 1.0} (no damping)
290 // once the pair is far enough apart that the exponential term is
291 // numerically negligible, matching FillTholeInteraction's own cutoff.
293 double l3;
294 double l5;
295 };
296
297 BFunctions ComputeB(double r) const;
298
299 // The erf(alpha*r)-screened complement to ComputeB's own erfc-screened
300 // B-functions -- same B0/B1/B2 roles, but for erf(alpha*r)/r rather
301 // than erfc(alpha*r)/r. Computed as the bare (undamped, alpha-
302 // independent) Coulomb B-functions minus ComputeB's own result: since
303 // erf(x)+erfc(x)=1, and the B-function recursion is linear in B0, this
304 // decomposition is exact, not an approximation (confirmed both
305 // algebraically and against a finite-difference derivative of
306 // erf(alpha*r)/r directly, matching to the precision the finite
307 // difference itself allows). B0_bare=1/r, B1_bare=1/r^3, B2_bare=3/r^5
308 // are the standard undamped point-multipole tensor coefficients.
309 BFunctions ComputeErfB(double r) const;
310 TholeFactors ComputeThole(double r, const PolarSite& site1,
311 const PolarSite& site2) const;
312
313 private:
314 // Below this separation two sites are treated as coincident by the
315 // erf-screened corrections, which take their analytic r -> 0 limit
316 // there. Matches legacy's own 1e-2 threshold in EwdInteractor.
317 static constexpr double kCoincidenceTol = 1e-2;
318
319 double alpha_;
320 double thole_;
321};
322
323} // namespace xtp
324} // namespace votca
325
326#endif // VOTCA_XTP_EWALDREALSPACEINTERACTOR_H
EwaldRealSpaceInteractor(double alpha, double thole_a=0.39)
double CalcInducedSourceEnergy(const PolarSite &site1, const PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero(), bool damp=true) const
double CalcStaticEnergy(const S1 &site1, const S2 &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
double ErfPotential(double q, const Eigen::Vector3d &mu, const Eigen::Vector3d &r_vec) const
TholeFactors ComputeThole(double r, const PolarSite &site1, const PolarSite &site2) const
void ApplyErfInducedFieldCorrection(const PolarSite &site1, PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
double ScreenedPotential(double q, const Eigen::Vector3d &mu, const Eigen::Vector3d &r_vec) const
double CalcInducedEnergy(const PolarSite &site1, const PolarSite &site2) const
double ApplyInducedField(const PolarSite &site1, PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
double CalcErfInducedSourceEnergy(const PolarSite &site1, const PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
double CalcErfStaticEnergy(const S1 &site1, const S2 &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
double ApplyStaticField(const T &site1, PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) const
void ApplyErfStaticFieldCorrection(const T &site1, PolarSite &site2, const Eigen::Vector3d &source_shift=Eigen::Vector3d::Zero()) 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