90 const Eigen::Matrix3d recip = 2.0 * kPi *
box_.inverse().transpose();
91 const Eigen::Vector3d b1 = recip.col(0);
92 const Eigen::Vector3d b2 = recip.col(1);
93 const Eigen::Vector3d b3 = recip.col(2);
101 std::vector<KVector> kvecs;
102 for (
Index i1 = -n1; i1 <= n1; ++i1) {
103 for (
Index i2 = -n2; i2 <= n2; ++i2) {
104 for (
Index i3 = -n3; i3 <= n3; ++i3) {
105 if (i1 == 0 && i2 == 0 && i3 == 0) {
109 Eigen::Vector3d k = double(i1) * b1 + double(i2) * b2 + double(i3) * b3;
110 double k2 = k.squaredNorm();
112 kvecs.push_back({k, k2});
123 std::vector<std::complex<double>>
S(
kvectors_.size(),
124 std::complex<double>(0.0, 0.0));
132 std::vector<double> q_flat;
133 std::vector<Eigen::Vector3d> mu_flat;
134 std::vector<Eigen::Vector3d> pos_flat;
136 if (!
registry_.Has(source_id, source_state)) {
141 q_flat.push_back(site.getCharge());
142 mu_flat.push_back(site.getStaticDipole() + site.getInducedDipole());
143 pos_flat.push_back(site.getPos());
162 const Index chunk = std::max<Index>(1, n_k / 20);
163 for (
Index k_begin = 0; k_begin < n_k; k_begin += chunk) {
164 const Index k_end = std::min(n_k, k_begin + chunk);
165#pragma omp parallel for schedule(static)
166 for (
Index idx = k_begin; idx < k_end; ++idx) {
167 const Eigen::Vector3d& k =
kvectors_[std::size_t(idx)].k;
168 std::complex<double> acc(0.0, 0.0);
169 for (
Index n = 0; n < n_sites; ++n) {
170 const double kr = k.dot(pos_flat[std::size_t(n)]);
173 const std::complex<double> phase = std::polar(1.0, -kr);
174 const double k_dot_mu = k.dot(mu_flat[std::size_t(n)]);
175 acc += std::complex<double>(q_flat[std::size_t(n)], -k_dot_mu) * phase;
177 S[std::size_t(idx)] = acc;
180 progress(std::size_t(k_end), std::size_t(n_k));
187 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>& foreground,
188 const std::vector<const PolarSite*>& background_exclusions,
200 std::vector<double> q_fg;
201 std::vector<Eigen::Vector3d> mu_fg;
202 std::vector<Eigen::Vector3d> pos_fg;
203 q_fg.reserve(foreground.size());
204 mu_fg.reserve(foreground.size());
205 pos_fg.reserve(foreground.size());
206 for (
const auto& entry : foreground) {
210 pos_fg.push_back(entry.second);
216 const std::vector<const PolarSite*>& fg_sites = background_exclusions;
217 std::vector<double> q_bg;
218 std::vector<Eigen::Vector3d> mu_bg;
219 std::vector<Eigen::Vector3d> pos_bg;
221 if (!
registry_.Has(source_id, source_state)) {
226 bool is_foreground =
false;
229 is_foreground =
true;
236 q_bg.push_back(site.getCharge());
237 mu_bg.push_back(site.getStaticDipole());
238 pos_bg.push_back(site.getPos());
245 const double prefactor = 4.0 * kPi /
volume_;
252#pragma omp parallel for schedule(static) reduction(+ : energy)
253 for (
Index idx = 0; idx < n_k; ++idx) {
254 const Eigen::Vector3d& k =
kvectors_[std::size_t(idx)].k;
255 const double k2 =
kvectors_[std::size_t(idx)].k2;
257 std::complex<double> s_fg(0.0, 0.0);
258 for (
Index n = 0; n < n_fg; ++n) {
259 const double kr = k.dot(pos_fg[std::size_t(n)]);
260 const std::complex<double> phase = std::polar(1.0, -kr);
261 const double k_dot_mu = k.dot(mu_fg[std::size_t(n)]);
262 s_fg += std::complex<double>(q_fg[std::size_t(n)], -k_dot_mu) * phase;
265 std::complex<double> s_bg(0.0, 0.0);
266 for (
Index n = 0; n < n_bg; ++n) {
267 const double kr = k.dot(pos_bg[std::size_t(n)]);
268 const std::complex<double> phase = std::polar(1.0, -kr);
269 const double k_dot_mu = k.dot(mu_bg[std::size_t(n)]);
270 s_bg += std::complex<double>(q_bg[std::size_t(n)], -k_dot_mu) * phase;
273 const double weight = std::exp(-k2 / (4.0 *
alpha_ *
alpha_)) / k2;
274 energy += prefactor * weight * (std::conj(s_fg) * s_bg).real();
280 const std::vector<std::pair<const PolarSite*, Eigen::Vector3d>>& foreground,
281 const std::vector<const PolarSite*>& background_exclusions,
289 std::vector<double> q_fg;
290 std::vector<Eigen::Vector3d> mu_fg;
291 std::vector<Eigen::Vector3d> pos_fg;
292 q_fg.reserve(foreground.size());
293 mu_fg.reserve(foreground.size());
294 pos_fg.reserve(foreground.size());
295 for (
const auto& entry : foreground) {
299 pos_fg.push_back(entry.second);
305 std::vector<Eigen::Vector3d> mu_bg;
306 std::vector<Eigen::Vector3d> pos_bg;
308 if (!
registry_.Has(source_id, source_state)) {
313 bool is_excluded =
false;
314 for (
const PolarSite* skip : background_exclusions) {
323 mu_bg.push_back(site.getInducedDipole());
324 pos_bg.push_back(site.getPos());
331 const double prefactor = 4.0 * kPi /
volume_;
334#pragma omp parallel for schedule(static) reduction(+ : energy)
335 for (
Index idx = 0; idx < n_k; ++idx) {
336 const Eigen::Vector3d& k =
kvectors_[std::size_t(idx)].k;
337 const double k2 =
kvectors_[std::size_t(idx)].k2;
339 std::complex<double> s_fg(0.0, 0.0);
340 for (
Index n = 0; n < n_fg; ++n) {
341 const double kr = k.dot(pos_fg[std::size_t(n)]);
342 const std::complex<double> phase = std::polar(1.0, -kr);
343 const double k_dot_mu = k.dot(mu_fg[std::size_t(n)]);
344 s_fg += std::complex<double>(q_fg[std::size_t(n)], -k_dot_mu) * phase;
347 std::complex<double> s_bg(0.0, 0.0);
348 for (
Index n = 0; n < n_bg; ++n) {
349 const double kr = k.dot(pos_bg[std::size_t(n)]);
350 const std::complex<double> phase = std::polar(1.0, -kr);
351 const double k_dot_mu = k.dot(mu_bg[std::size_t(n)]);
352 s_bg += std::complex<double>(0.0, -k_dot_mu) * phase;
355 const double weight = std::exp(-k2 / (4.0 *
alpha_ *
alpha_)) / k2;
356 energy += prefactor * weight * (std::conj(s_fg) * s_bg).real();
372 const std::vector<std::complex<double>>
S =
374 const double prefactor = 4.0 * kPi /
volume_;
383 const Index n_targets =
Index(targets.size());
384 std::vector<Eigen::Vector3d> fields(targets.size());
385#pragma omp parallel for schedule(static)
386 for (
Index t_i = 0; t_i < n_targets; ++t_i) {
387 const PolarSite& target = *targets[std::size_t(t_i)];
389 const Eigen::Vector3d r = target.
getPos();
390 Eigen::Vector3d field = Eigen::Vector3d::Zero();
392 for (std::size_t idx = 0; idx <
kvectors_.size(); ++idx) {
399 const std::complex<double> phase = std::polar(1.0, kv.
k.dot(r));
400 double im_part = (
S[idx] * phase).imag();
401 field += prefactor * weight * im_part * kv.
k;
403 fields[std::size_t(t_i)] = field;
406 for (std::size_t t_i = 0; t_i < targets.size(); ++t_i) {
408 targets[t_i]->V_noE() += fields[t_i];
410 targets[t_i]->V() += fields[t_i];
418 const std::vector<std::complex<double>>
S =
420 const double prefactor = 4.0 * kPi /
volume_;
422 const std::size_t n_k =
kvectors_.size();
423 Eigen::VectorXd phi = Eigen::VectorXd::Zero(n_points);
434 std::vector<double> c_re(n_k);
435 std::vector<double> c_im(n_k);
436 const double inv_four_alpha2 = 1.0 / (4.0 *
alpha_ *
alpha_);
437 for (std::size_t idx = 0; idx < n_k; ++idx) {
438 const double weight = prefactor *
439 std::exp(-
kvectors_[idx].k2 * inv_four_alpha2) /
441 c_re[idx] = weight *
S[idx].real();
442 c_im[idx] = weight *
S[idx].imag();
449#pragma omp parallel for schedule(static)
450 for (
Index p = 0; p < n_points; ++p) {
451 const Eigen::Vector3d& r = points[std::size_t(p)];
453 for (std::size_t idx = 0; idx < n_k; ++idx) {
457 const double theta =
kvectors_[idx].k.dot(r);
458 acc += c_re[idx] * std::cos(theta) - c_im[idx] * std::sin(theta);