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
76
77namespace votca {
78namespace xtp {
79
81 public:
82 // box: columns are the lattice vectors a, b, c.
83 // registry: source of every segment's multipole/induced-dipole state.
84 // Stored by reference; must outlive this object.
85 // alpha: Ewald splitting parameter, shared with whatever
86 // EwaldRealSpaceInteractor/EwaldRealSpaceSum instance is used
87 // alongside this class for the same background.
88 // k_max: spherical cutoff (bohr^-1) on |k|.
89 EwaldReciprocalSpaceSum(const Eigen::Matrix3d& box,
90 const EwaldRegistry& registry, double alpha,
91 double k_max);
92
93 // Number of k-vectors (fixed at construction). This, not k_max, sets the
94 // cost of a call: it grows with the cube of k_max relative to the
95 // reciprocal lattice spacing, so it can be large for a large box.
96 std::size_t NumKVectors() const { return kvectors_.size(); }
97
98 // Position-independent 3x3 matrix M such that a site's dipole mu produces
99 // a spurious reciprocal-space self-field -M*mu at its own position, because
100 // S(k) includes the site itself. A charge has no such term: its own
101 // contribution at its position is real, and only the imaginary part enters
102 // the field.
103 //
104 // M_ab = (4/3) * alpha^3 / sqrt(pi) * delta_ab
105 //
106 // the standard analytic Ewald self-term (r -> 0 limit of the erf-screened
107 // dipole field). Legacy applies the same correction in real space
108 // (EwdInteractor::FU12_ERF_At_By, R1 < 1e-2 branch).
109 //
110 // This is not the discrete k-lattice sum at r = 0, which also contains the
111 // field of the site's periodic images (physical, not an artifact); see the
112 // definition for the size of the difference (1.63% in a realistic box).
113 //
114 // Neither this class nor EwaldRealSpaceSum subtracts the self-field; a
115 // caller wanting the self-interaction-free field subtracts
116 // SelfFieldMatrix()*mu itself.
117 Eigen::Matrix3d SelfFieldMatrix() const;
118
119 // Progress callback signature: called as (k_vectors_done,
120 // k_vectors_total) periodically (not necessarily every single
121 // k-vector) during the expensive part of AddFieldAtMany. Pass an empty
122 // std::function (the default) for no callback.
123 using ProgressCallback = std::function<void(std::size_t, std::size_t)>;
124
125 // Accumulates the total reciprocal-space field into target's own
126 // V()/V_noE() accumulators (matching EwaldRealSpaceSum's convention),
127 // generated by every registered segment at charge state source_state
128 // (see class documentation on scope -- no segment is excluded, not
129 // even target's own). Convenience wrapper around AddFieldAtMany for a
130 // single target; prefer AddFieldAtMany when computing the field at
131 // many sites at once (see the class documentation's performance note).
132 template <enum Estatic CE>
133 void AddFieldAt(PolarSite& target, EwaldChargeState source_state) const;
134
135 // Batched form of AddFieldAt: computes the field at every target site
136 // in targets, sharing a single pass over every k-vector's structure
137 // factor across the whole batch. target pointers must be non-null and
138 // must remain valid for the duration of the call; the same site may
139 // appear more than once (field is simply accumulated twice in that
140 // case, same as two separate AddFieldAt calls would do). progress, if
141 // non-empty, is called periodically during the k-vector loop (see
142 // ProgressCallback).
143 template <enum Estatic CE>
144 void AddFieldAtMany(
145 const std::vector<PolarSite*>& targets, EwaldChargeState source_state,
146 const ProgressCallback& progress = ProgressCallback()) const;
147
148 // POTENTIAL at arbitrary points, batched -- the exact counterpart of
149 // AddFieldAtMany, and like it carrying BOTH channels: the structure
150 // factor is built from getStaticDipole() + getInducedDipole(), so the
151 // permanent and induced backgrounds are already summed. It is what
152 // CalcStaticEnergyBetween and CalcInducedSourceEnergyBetween report
153 // ADDED TOGETHER, not either one alone.
154 //
155 // Setting q = 1, mu = 0 in CalcStaticEnergyBetween leaves
156 // conj(s_fg) = exp(+i k.r), so what a unit test charge at r reports as
157 // its own energy is
158 //
159 // phi(r) = (4*pi/V) * sum_{k!=0} weight(k) * Re[ S(k) exp(i k.r) ]
160 //
161 // Consistent with AddFieldAtMany by construction: -grad of that
162 // expression is its Im[S exp(i k.r)] * k.
163 //
164 // Batched because S(k) is rebuilt on every call and costs
165 // O(N_k * N_sites) -- for a DFT grid, computing phi a point at a time
166 // would repeat that tens of thousands of times over. Here it is paid
167 // once and replayed against every point.
168 Eigen::VectorXd PotentialAtMany(
169 const std::vector<Eigen::Vector3d>& points, EwaldChargeState source_state,
170 const ProgressCallback& progress = ProgressCallback()) const;
171
172 // Reciprocal-space PERMANENT-multipole interaction energy between a
173 // supplied set of sites (the foreground) and the rest of the periodic
174 // cell (the background):
175 //
176 // E = sum_k (4*pi/V) * exp(-k^2/4*alpha^2)/k^2 * Re[ S_fg*(k) . S_bg(k) ]
177 //
178 // `foreground` lists the sites whose moments enter S_fg, together with
179 // the position each one occupies. These are the foreground's OWN sites
180 // -- in the job's own charge state -- not the background copies they
181 // were carved from. That distinction is the whole point of the cross
182 // term: the interaction being computed is the one the job's charge
183 // state actually has with the medium, so taking the moments from the
184 // neutral background copies instead would make a charged job report
185 // the neutral job's reciprocal energy, and the difference between
186 // charge states -- the one quantity a site-energy calculation is
187 // after -- would silently lose this contribution altogether.
188 //
189 // `background_exclusions` is therefore supplied separately: which
190 // sites carry the foreground's moments and which registered sites must
191 // be held out of S_bg are two different questions, and conflating them
192 // is what produced the bug just described. Identity is by address.
193 //
194 // S_fg and S_bg are accumulated SEPARATELY rather than obtaining S_bg
195 // by subtracting S_fg from the total. The foreground is a tiny
196 // fraction of the cell, so that subtraction would difference two large
197 // nearly-equal numbers and lose exactly the precision the cross term
198 // needs.
199 //
200 // Permanent multipoles only, matching EwaldRealSpaceSum's own
201 // CalcStaticEnergyAt: the induced contribution reaches the polar
202 // region through the field, and adding it here too would double-count.
204 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>&
205 foreground,
206 const std::vector<const PolarSite*>& background_exclusions,
207 EwaldChargeState source_state) const;
208
209 // The same reciprocal cross sum, but with the background entering
210 // through its INDUCED dipoles instead of its permanent moments:
211 //
212 // S_bg(k) = sum_bg (-i k.mu_ind) exp(-i k.r)
213 //
214 // while the foreground still contributes its permanent moments. This
215 // is the reciprocal partner of
216 // EwaldRealSpaceSum::CalcInducedSourceEnergyAt; see
217 // EwaldRealSpaceInteractor::CalcInducedSourceEnergy for what the term
218 // is and why it exists.
219 //
220 // No Thole damping, deliberately: damping is a short-range correction
221 // and a reciprocal-space sum has no short range to correct. Its
222 // real-space partner IS damped, which is legacy's convention too and
223 // is why the two together are only approximately alpha-independent.
225 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>&
226 foreground,
227 const std::vector<const PolarSite*>& background_exclusions,
228 EwaldChargeState source_state) const;
229
230 private:
231 struct KVector {
232 Eigen::Vector3d k;
233 double k2;
234 };
235 std::vector<KVector> GenerateKVectors() const;
236
237 // Structure factor S(k) (see class documentation), one entry per
238 // kvectors_, summed over every site of every registered segment at
239 // source_state. This is the expensive O(N_k * N_sites) loop
240 // AddFieldAtMany's own progress callback reports on.
241 std::vector<std::complex<double>> TotalStructureFactors(
242 EwaldChargeState source_state, const ProgressCallback& progress) const;
243
244 Eigen::Matrix3d box_;
245 double volume_;
247 double alpha_;
248 double k_max_;
249
250 std::vector<KVector> kvectors_;
251};
252
253} // namespace xtp
254} // namespace votca
255
256#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