ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
dp3m_heffte.impl.hpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2026 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22#include <config/config.hpp>
23
24#ifdef ESPRESSO_DP3M
25
27
30
31#include "magnetostatics/dp3m_heffte.hpp" // must be included after dipoles.hpp
32
33#include "fft/fft.hpp"
34#include "p3m/P3MFFT.hpp"
36#include "p3m/TuningLogger.hpp"
37#include "p3m/common.hpp"
40#include "p3m/interpolation.hpp"
41#include "p3m/math.hpp"
42
43#include "BoxGeometry.hpp"
44#include "LocalBox.hpp"
45#include "Particle.hpp"
46#include "PropagationMode.hpp"
49#include "communication.hpp"
50#include "errorhandling.hpp"
52#include "kokkos_helpers.hpp"
53#include "npt.hpp"
54#include "system/System.hpp"
55#include "tuning.hpp"
56
57#include <utils/Vector.hpp>
60#include <utils/math/sqr.hpp>
61
62#include <boost/mpi/collectives/all_reduce.hpp>
63#include <boost/mpi/collectives/reduce.hpp>
64
65#include <Kokkos_Core.hpp>
66#include <Kokkos_ScatterView.hpp>
67
68#include <algorithm>
69#include <array>
70#include <cmath>
71#include <cstddef>
72#include <cstdio>
73#include <functional>
74#include <iterator>
75#include <memory>
76#include <numbers>
77#include <optional>
78#include <span>
79#include <sstream>
80#include <stdexcept>
81#include <tuple>
82#include <utility>
83#include <vector>
84
85#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
86#ifndef NDEBUG
87template <typename T>
88bool heffte_almost_equal(T const &value, T const &reference) {
89 auto const diff = std::abs(value - reference);
90 using FT = std::remove_cvref_t<decltype(diff)>;
91 auto constexpr atol = std::is_same_v<FT, float> ? FT{2e-4} : FT{1e-6};
92 auto constexpr rtol = std::is_same_v<FT, float> ? FT{5e-5} : FT{1e-5};
93 auto const non_zero = std::abs(reference) != FT{0};
94 return (diff < atol) or (non_zero and (diff / std::abs(reference) < rtol));
95}
96#endif // not NDEBUG
97#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
98
99template <typename FloatType, Arch Architecture, class FFTConfig>
100void DipolarP3MHeffte<FloatType, Architecture,
102 auto local_n = std::size_t{0u};
103 double local_mu2 = 0.;
104
105 for (auto const &p : get_system().cell_structure->local_particles()) {
106 if (p.dipm() != 0.) {
107 local_mu2 += p.calc_dip().norm2();
108 local_n++;
109 }
110 }
111
112 boost::mpi::all_reduce(comm_cart, local_mu2, dp3m.sum_mu2, std::plus<>());
113 boost::mpi::all_reduce(comm_cart, local_n, dp3m.sum_dip_part, std::plus<>());
114}
115
116inline double dp3m_k_space_error(double box_size, int mesh, int cao,
117 std::size_t n_c_part, double sum_q2,
118 double alpha_L);
119
120inline double dp3m_real_space_error(double box_size, double r_cut_iL,
121 std::size_t n_c_part, double sum_q2,
122 double alpha_L);
123
124/** Compute the value of alpha through a bisection method.
125 * Based on eq. (33) @cite wang01a.
126 */
127double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part,
128 double sum_q2, double x1, double x2, double xacc,
129 double tuned_accuracy);
130
131template <typename FloatType, Arch Architecture, class FFTConfig>
132double DipolarP3MHeffte<FloatType, Architecture,
133 FFTConfig>::calc_average_self_energy_k_space() const {
134 auto const &box_geo = *get_system().box_geo;
135 auto const node_phi =
137 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop, dp3m.g_energy);
138
139 double phi = 0.;
140 boost::mpi::reduce(comm_cart, node_phi, phi, std::plus<>(), 0);
141 phi /= 3. * box_geo.length()[0] *
142 Utils::int_pow<3>(static_cast<double>(dp3m.params.mesh[0]));
143 return phi * std::numbers::pi;
144}
145
146template <typename FloatType, Arch Architecture, class FFTConfig>
148 assert(dp3m.params.mesh >= Utils::Vector3i::broadcast(1));
149 assert(dp3m.params.cao >= p3m_min_cao and dp3m.params.cao <= p3m_max_cao);
150 assert(dp3m.params.alpha > 0.);
151
152 auto const &system = get_system();
153 auto const &box_geo = *system.box_geo;
154 auto const &local_geo = *system.local_geo;
155 auto const verlet_skin = system.cell_structure->get_verlet_skin();
156
157 dp3m.params.cao3 = Utils::int_pow<3>(dp3m.params.cao);
158 dp3m.params.recalc_a_ai_cao_cut(box_geo.length());
159
160 assert(dp3m.fft);
161 dp3m.local_mesh.calc_local_ca_mesh(dp3m.params, local_geo, verlet_skin, 0.);
162 dp3m.fft_buffers->init_halo();
163 dp3m.fft->init(dp3m.params);
164 dp3m.mesh.ks_pnum = dp3m.fft->get_ks_pnum();
165 dp3m.fft_buffers->init_meshes(dp3m.fft->get_ca_mesh_size());
166 dp3m.update_mesh_views();
167#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
168 dp3m.heffte.world_size = comm_cart.size();
169 dp3m.heffte.fft =
170 std::make_shared<P3MFFT<FloatType, Architecture, FFTConfig>>(
171 nullptr, ::comm_cart, dp3m.params.mesh, dp3m.local_mesh.ld_no_halo,
172 dp3m.local_mesh.ur_no_halo, ::communicator.node_grid);
173 dp3m.resize_heffte_buffers();
174#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
175 dp3m.calc_differential_operator();
176
177 /* fix box length dependent constants */
178 scaleby_box_l();
179
181}
182
183namespace {
184template <int cao> struct AssignDipole {
185 void operator()(auto &dp3m, auto &cell_structure) {
186 using DipolarP3MState = std::remove_reference_t<decltype(dp3m)>;
187 using value_type = DipolarP3MState::value_type;
188 using execution_space = Kokkos::DefaultHostExecutionSpace;
189 auto const &aosoa = cell_structure.get_aosoa();
190 auto const &unique_particles = cell_structure.get_unique_particles();
191 auto const n_part = cell_structure.count_local_particles();
192 dp3m.inter_weights.zfill(n_part); // allocate buffer for parallel write
194 "InterpolateDipoles", std::size_t{0u}, n_part, [&](auto p_index) {
196 auto const tid = omp_get_thread_num();
197 auto const p_pos = aosoa.get_span_at(aosoa.position, p_index);
198 auto const dip = unique_particles.at(p_index)->calc_dip();
199 auto const weights =
201 p_pos, dp3m.params.ai, dp3m.local_mesh);
202 dp3m.inter_weights.store_at(p_index, weights);
204 dp3m.local_mesh, weights, [&dip, tid, &dp3m](int ind, double w) {
205 dp3m.rs_fields_kokkos(tid, 0u, ind) += value_type(w * dip[0u]);
206 dp3m.rs_fields_kokkos(tid, 1u, ind) += value_type(w * dip[1u]);
207 dp3m.rs_fields_kokkos(tid, 2u, ind) += value_type(w * dip[2u]);
208 });
209 });
210 Kokkos::fence();
211 int num_threads = execution_space().concurrency();
213 "ReduceInterpolatedDipoles", std::size_t{0}, dp3m.local_mesh.size,
214 [&dp3m, num_threads](std::size_t const i) {
215 for (int dir = 0; dir < 3; ++dir) {
216 value_type acc{};
217 for (int tid = 0; tid < num_threads; ++tid) {
218 acc += dp3m.rs_fields_kokkos(tid, dir, i);
219 }
220 dp3m.mesh.rs_fields[dir][i] += acc;
221#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
222 dp3m.heffte.rs_dipole_density[dir][i] += acc;
223#endif
224 }
225 });
226 Kokkos::fence();
227 }
228};
229} // namespace
230
231template <typename FloatType, Arch Architecture, class FFTConfig>
233 prepare_fft_mesh();
234
235 Utils::integral_parameter<int, AssignDipole, p3m_min_cao, p3m_max_cao>(
236 dp3m.params.cao, dp3m, *get_system().cell_structure);
237}
238
239namespace {
240template <int cao> struct AssignTorques {
241 void operator()(auto &dp3m, double pref, int d_rs,
242 CellStructure &cell_structure) const {
243
244 assert(cao == dp3m.inter_weights.cao());
245 using execution_space = Kokkos::DefaultHostExecutionSpace;
246
247 auto const kernel = [d_rs, pref, &dp3m](auto const &dip,
248#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
249 auto &p_dip_fld,
250#endif
251 auto &p_torque,
252 std::size_t p_index) {
253 auto const weights = dp3m.inter_weights.template load<cao>(p_index);
255 p3m_interpolate(dp3m.local_mesh, weights,
256 [&E, &dp3m, d_rs](int ind, double w) {
257 // heFFTe data: dp3m.heffte.ks_scalar.real()
258 E[d_rs] += w * double(dp3m.mesh.rs_scalar[ind]);
259 });
260
261 auto const torque = pref * vector_product(dip, E);
262 auto access = p_torque.access();
263 access(p_index, 0) -= torque[0];
264 access(p_index, 1) -= torque[1];
265 access(p_index, 2) -= torque[2];
266#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
267 auto const dip_fld = pref * E;
268 auto access_dip_fld = p_dip_fld.access();
269 access_dip_fld(p_index, 0) -= dip_fld[0];
270 access_dip_fld(p_index, 1) -= dip_fld[1];
271 access_dip_fld(p_index, 2) -= dip_fld[2];
272#endif
273 };
274
275 auto const n_part = dp3m.inter_weights.size();
276 auto const &unique_particles = cell_structure.get_unique_particles();
277 auto scatter_torque = cell_structure.get_scatter_torque();
278#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
279 auto scatter_dip_fld = cell_structure.get_scatter_dip_fld();
280#endif
282 "AssignTorques", std::size_t{0u}, n_part, [&](std::size_t p_index) {
283 auto const &p = *unique_particles.at(p_index);
284 if (p.dipm() != 0.) {
285 kernel(p.calc_dip(),
288#endif
290 }
291 });
292 }
293};
294
295template <int cao> struct AssignForcesDip {
296 void operator()(auto &dp3m, double pref, int d_rs,
297 CellStructure &cell_structure) const {
298
299 assert(cao == dp3m.inter_weights.cao());
300 using execution_space = Kokkos::DefaultHostExecutionSpace;
301
302 auto const kernel = [d_rs, pref, &dp3m](auto const &dip, auto &p_force,
303 std::size_t p_index) {
304 auto const weights = dp3m.inter_weights.template load<cao>(p_index);
305
307 p3m_interpolate(dp3m.local_mesh, weights, [&E, &dp3m](int ind, double w) {
308 // heFFTe data: dp3m.heffte.rs_B_fields
309 E[0u] += w * double(dp3m.mesh.rs_fields[0u][ind]);
310 E[1u] += w * double(dp3m.mesh.rs_fields[1u][ind]);
311 E[2u] += w * double(dp3m.mesh.rs_fields[2u][ind]);
312 });
313
314 auto access = p_force.access();
315 access(p_index, d_rs) += pref * (dip * E);
316 };
317
318 auto const n_part = dp3m.inter_weights.size();
319 auto const &unique_particles = cell_structure.get_unique_particles();
320 auto scatter_force = cell_structure.get_scatter_force();
322 "AssignForcesDip", std::size_t{0u}, n_part, [&](std::size_t p_index) {
323 auto const &p = *unique_particles.at(p_index);
324 if (p.dipm() != 0.) {
325 kernel(p.calc_dip(), scatter_force, p_index);
326 }
327 });
328 }
329};
330} // namespace
331
332#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
333template <typename FloatType, class FFTConfig>
335 auto const rs_array_size =
336 static_cast<std::size_t>(Utils::product(this->local_mesh.dim));
337 auto const rs_array_size_no_halo =
338 static_cast<std::size_t>(Utils::product(this->local_mesh.dim_no_halo));
339 auto const fft_mesh_size =
340 static_cast<std::size_t>(Utils::product(heffte.fft->ks_local_size()));
341 for (auto d : {0u, 1u, 2u}) {
342 heffte.rs_dipole_density[d].resize(rs_array_size);
343 heffte.ks_dipole_density[d].resize(fft_mesh_size);
344 heffte.rs_B_fields[d].resize(rs_array_size);
345 heffte.rs_B_fields_no_halo[d].resize(rs_array_size_no_halo);
346 }
347 heffte.ks_B_field_storage.resize(fft_mesh_size);
348 heffte.ks_scalar.resize(fft_mesh_size);
349}
350#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
351
352/**
353 * @brief Reciprocal-space virial for the dipolar Ewald/P3M sum. Obtained
354 * via the same Nose-Klein strain-derivative method used for the Coulomb
355 * case (@cite essmann95a eq. (2.7), \f$\Pi_{\textrm{rec}, \alpha, \beta}\f$),
356 * applied to the dipolar structure
357 * factor \f$Q(\vec k) = \vec M(\vec k)\cdot\vec k\f$ with
358 * \f$\vec M(\vec k) = \sum_j \vec \mu_j \exp(i\vec k\cdot\vec r_j)\f$.
359 * Unlike the charge structure factor, \f$Q(\vec k)\f$ depends on
360 * \f$\vec k\f$ explicitly (not only through the phase factor), which
361 * produces an extra cross term beyond the charge-case \f$k_a k_b\f$
362 * envelope. This cross term is generally asymmetric in \f$(a,b)\f$: its
363 * symmetric half, \f$k_a\Re[M_b Q^*] + k_b\Re[M_a Q^*]\f$, is the
364 * dipole-dipole reciprocal-space pressure tensor eq. (46) in
365 * @cite aguado03a (their \f$\vec h\f$, \f$\kappa\f$ correspond to
366 * \f$\vec k\f$, \f$\alpha\f$ here), which only ever reports that
367 * symmetrized form. The remaining antisymmetric half is not in that
368 * reference -- it is the reciprocal-space image of the same
369 * dipole-dipole torque that already makes the real-space virial
370 * asymmetric (see @ref DipolarDirectSum::long_range_pressure and
371 * @ref DipolarP3M::pair_force), derived here by differentiating
372 * the reciprocal energy directly (via the strain parametrization
373 * \f$H(\varepsilon)=LI+\varepsilon E_{ab}\f$) instead of presupposing a
374 * symmetric result.
375 *
376 * Care is needed with the index convention: probing the strain component
377 * \f$\varepsilon_{ab}\f$ (i.e. \f$H(\varepsilon)=LI+\varepsilon E_{ab}\f$)
378 * yields \f$-\partial U/\partial\varepsilon_{ab} = r_b F_a\f$ for a pair
379 * separation \f$\vec r\f$ and force \f$\vec F\f$ -- the *transpose* of the
380 * \f$r_a F_b\f$ (@ref Utils::tensor_product "d (x) f") convention used by
381 * the real-space term and by @ref DipolarDirectSum::long_range_pressure.
382 * Concretely, differentiating \f$Q(\vec k)=\vec k\cdot\vec M\f$ gives a
383 * cross-term contribution to \f$-\partial U/\partial\varepsilon_{ab}\f$
384 * proportional to \f$k_a\Re[M_bQ^*]\f$; to match the \f$r_aF_b\f$
385 * convention, this must be stored as the \f$(b,a)\f$ tensor component,
386 * i.e. \f$\Pi_{ab}\f$ gets cross term \f$2k_b\Re[M_aQ^*]\f$ (indices
387 * swapped relative to the strain probe that produced it).
388 */
389template <typename FloatType, Arch Architecture, class FFTConfig>
392 auto const &system = get_system();
393 auto const &box_geo = *system.box_geo;
394 auto const dipole_prefac = prefactor / Utils::product(dp3m.params.mesh);
396
397 if (dp3m.sum_mu2 > 0.) {
398 dipole_assign();
399 dp3m.fft_buffers->perform_vector_halo_gather();
400 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
401 dp3m.fft->forward_fft(rs_mesh);
402 }
403 dp3m.update_mesh_views();
404
405 auto constexpr mesh_start = Utils::Vector3i::broadcast(0);
407 auto const wavevector = 2. * std::numbers::pi * box_geo.length_inv()[0];
408 auto const half_alpha_inv_sq = Utils::sqr(1. / (2. * dp3m.params.alpha));
409
410 auto index = std::size_t(0u);
411 auto it_energy = dp3m.g_energy.begin();
412 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
413 auto constexpr KX = 2, KY = 0, KZ = 1;
414 auto const shift = local_index + dp3m.mesh.start;
415 auto const &d_op = dp3m.d_op[0u];
416 auto const &mesh_dip = dp3m.mesh.rs_fields;
417 auto const d_op_x = static_cast<FloatType>(d_op[shift[KX]]);
418 auto const d_op_y = static_cast<FloatType>(d_op[shift[KY]]);
419 auto const d_op_z = static_cast<FloatType>(d_op[shift[KZ]]);
420
421 // Re(M(k)) and Re(Q(k)) = Re(M(k)).n, same as the energy kernel's `re`
422 auto const Mx_re = mesh_dip[0u][index];
423 auto const My_re = mesh_dip[1u][index];
424 auto const Mz_re = mesh_dip[2u][index];
425 auto const Q_re = Mx_re * d_op_x + My_re * d_op_y + Mz_re * d_op_z;
426 ++index;
427 // Im(M(k)) and Im(Q(k))
428 auto const Mx_im = mesh_dip[0u][index];
429 auto const My_im = mesh_dip[1u][index];
430 auto const Mz_im = mesh_dip[2u][index];
431 auto const Q_im = Mx_im * d_op_x + My_im * d_op_y + Mz_im * d_op_z;
432 ++index;
433
434 auto const nx = static_cast<double>(d_op[shift[KX]]);
435 auto const ny = static_cast<double>(d_op[shift[KY]]);
436 auto const nz = static_cast<double>(d_op[shift[KZ]]);
437 auto const kx = nx * wavevector;
438 auto const ky = ny * wavevector;
439 auto const kz = nz * wavevector;
440 auto const norm_sq = Utils::sqr(kx) + Utils::sqr(ky) + Utils::sqr(kz);
441 if (norm_sq != 0.) {
442 auto const g = static_cast<double>(*it_energy);
443 auto const cell_energy =
444 g * static_cast<double>(Utils::sqr(Q_re) + Utils::sqr(Q_im));
445 auto const vterm = -2. * (1. / norm_sq + half_alpha_inv_sq);
446
447 // g * Re(M_a(k) * Q(k)^*), a in {x, y, z}
448 auto const Rx = g * static_cast<double>(Mx_re * Q_re + Mx_im * Q_im);
449 auto const Ry = g * static_cast<double>(My_re * Q_re + My_im * Q_im);
450 auto const Rz = g * static_cast<double>(Mz_re * Q_re + Mz_im * Q_im);
451
452 // Full (generally asymmetric) tensor: Pi_ab = cell_energy * (delta_ab
453 // + vterm * k_a * k_b) + 2 * k_b * R_a (note: indices of the cross
454 // term are swapped relative to the strain probe that produces it --
455 // see the class-level comment above for the derivation). The
456 // symmetric combination (R_a k_b + R_b k_a)/2 recovers the
457 // literature (Aguado & Madden, eq. 46) result; the leftover
458 // antisymmetric part is the k-space image of the same
459 // dipolar-torque signature that already makes the real-space term
460 // asymmetric (see dipolar_direct_sum.cpp / dp3m.hpp).
461 node_k_space_pressure_tensor[0u] +=
462 cell_energy * (1. + vterm * kx * kx) + 2. * nx * Rx; /* xx */
463 node_k_space_pressure_tensor[1u] +=
464 cell_energy * vterm * kx * ky + 2. * ny * Rx; /* xy */
465 node_k_space_pressure_tensor[2u] +=
466 cell_energy * vterm * kx * kz + 2. * nz * Rx; /* xz */
467 node_k_space_pressure_tensor[3u] +=
468 cell_energy * vterm * ky * kx + 2. * nx * Ry; /* yx */
469 node_k_space_pressure_tensor[4u] +=
470 cell_energy * (1. + vterm * ky * ky) + 2. * ny * Ry; /* yy */
471 node_k_space_pressure_tensor[5u] +=
472 cell_energy * vterm * ky * kz + 2. * nz * Ry; /* yz */
473 node_k_space_pressure_tensor[6u] +=
474 cell_energy * vterm * kz * kx + 2. * nx * Rz; /* zx */
475 node_k_space_pressure_tensor[7u] +=
476 cell_energy * vterm * kz * ky + 2. * ny * Rz; /* zy */
477 node_k_space_pressure_tensor[8u] +=
478 cell_energy * (1. + vterm * kz * kz) + 2. * nz * Rz; /* zz */
479 }
480 std::advance(it_energy, 1);
481 });
482 }
483
484 return node_k_space_pressure_tensor * dipole_prefac * std::numbers::pi *
485 box_geo.length_inv()[0];
486}
487
488template <typename FloatType, Arch Architecture, class FFTConfig>
490 bool force_flag, bool energy_flag) {
491 /* k-space energy */
492 double energy = 0.;
493 auto const &system = get_system();
494 auto const &box_geo = *system.box_geo;
495 auto const dipole_prefac = prefactor / Utils::product(dp3m.params.mesh);
496#ifdef ESPRESSO_NPT
497 auto const npt_flag = force_flag and system.has_npt_enabled();
498#else
499 auto constexpr npt_flag = false;
500#endif
501
502 auto constexpr mesh_start = Utils::Vector3i::broadcast(0);
504#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
505 auto constexpr r2c_dir = FFTConfig::r2c_dir;
506 auto const rs_local_size = dp3m.heffte.fft->rs_local_size();
507 auto const local_size = dp3m.heffte.fft->ks_local_size();
509 if constexpr (FFTConfig::use_r2c) {
510 local_size_full[r2c_dir] -= 1;
511 local_size_full[r2c_dir] *= 2;
512 }
513 auto const local_origin = dp3m.heffte.fft->ks_local_ld_index();
514#ifndef NDEBUG
515 auto const line_stride = local_size_full[0];
516 auto const plane_stride = local_size_full[0] * local_size_full[0];
517#endif
518 auto const &global_size = dp3m.params.mesh;
519 auto const cutoff_left = 1 - local_origin[r2c_dir];
520 auto const cutoff_right = global_size[r2c_dir] / 2 - local_origin[r2c_dir];
521 auto &short_dim = local_index[r2c_dir];
522 dp3m.resize_heffte_buffers();
523#endif
524
525 if (dp3m.sum_mu2 > 0.) {
526 dipole_assign();
527 dp3m.fft_buffers->perform_vector_halo_gather();
528 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
529 dp3m.fft->forward_fft(rs_mesh);
530 }
531 dp3m.update_mesh_views();
532
533#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
534 if (dp3m.heffte.world_size == 1) {
535 // halo communication of real space dipoles density
536 std::array<FloatType *, 3u> rs_fields = {
537 {dp3m.heffte.rs_dipole_density[0u].data(),
538 dp3m.heffte.rs_dipole_density[1u].data(),
539 dp3m.heffte.rs_dipole_density[2u].data()}};
540 dp3m.heffte.halo_comm.gather_grid(::comm_cart, rs_fields,
541 dp3m.local_mesh.dim);
542
543 for (auto dir : {0u, 1u, 2u}) {
544 // get real-space dipoles density without ghost layers
546 FFTConfig::r_space_order>(
547 dp3m.rs_field_no_halo_kokkos.data(),
548 dp3m.heffte.rs_dipole_density[dir], dp3m.local_mesh.dim,
549 dp3m.local_mesh.n_halo_ld,
550 dp3m.local_mesh.dim - dp3m.local_mesh.n_halo_ur);
551 // re-order data in row-major
552 std::size_t index_row_major = 0u;
554 mesh_start, rs_local_size, local_index, [&]() {
555 auto constexpr KX = 1, KY = 2, KZ = 0;
556 auto const index = local_index[KZ] +
557 rs_local_size[0] * local_index[KY] +
558 Utils::sqr(rs_local_size[0]) * local_index[KX];
559 dp3m.rs_field_no_halo_reorder_kokkos(index_row_major) =
560 dp3m.rs_field_no_halo_kokkos(index);
562 });
563 dp3m.heffte.fft->forward(dp3m.rs_field_no_halo_reorder_kokkos.data(),
564 dp3m.heffte.ks_dipole_density[dir].data());
565#ifndef NDEBUG
566 if (not dp3m.params.tuning) {
567 std::size_t index_row_major_r2c = 0u;
570 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
571 auto constexpr KX = 2, KY = 0, KZ = 1;
572 auto const index_fft_legacy = local_index[KZ] +
575 auto const old_value = std::complex<FloatType>{
576 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy],
577 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy + 1]};
578 auto const &new_value =
579 dp3m.heffte.ks_dipole_density[dir][index_row_major_r2c];
582 }
583 });
584 }
585#endif // not NDEBUG
586 }
587 }
588#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
589 }
590
591 /* === k-space energy calculation === */
592 if (energy_flag) {
593 /*********************
594 Dipolar energy
595 **********************/
596 if (dp3m.sum_mu2 > 0.) {
597 /* i*k differentiation for dipolar gradients:
598 * |(\Fourier{\vect{mu}}(k)\cdot \vect{k})|^2 */
599
600 auto index = std::size_t(0u);
601 auto it_energy = dp3m.g_energy.begin();
602 auto node_energy = 0.;
603 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
604 auto constexpr KX = 2, KY = 0, KZ = 1;
605 auto const shift = local_index + dp3m.mesh.start;
606 auto const &d_op = dp3m.d_op[0u];
607 auto const &mesh_dip = dp3m.mesh.rs_fields;
608 // Re(mu)*k
609 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
610 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
611 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
612 ++index;
613 // Im(mu)*k
614 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
615 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
616 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
617 ++index;
618 node_energy += *it_energy * (Utils::sqr(re) + Utils::sqr(im));
619 std::advance(it_energy, 1);
620 });
621#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
622 if (dp3m.heffte.world_size == 1) {
623 [[maybe_unused]] auto node_energy_heffte = 0.;
624 std::size_t index_row_major_r2c = 0u;
627 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
629 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
630 auto const cell_field =
632 FloatType(dp3m.d_op[0u][global_index[1u]]) +
634 FloatType(dp3m.d_op[1u][global_index[2u]]) +
636 FloatType(dp3m.d_op[2u][global_index[0u]]);
637 auto cell_energy = static_cast<double>(
638 dp3m.heffte.g_energy[index_row_major_r2c] *
639 std::norm(cell_field));
640 if (FFTConfig::use_r2c and (short_dim >= cutoff_left and
641 short_dim <= cutoff_right - 1)) {
642 // k-space symmetry: double counting except in the first and
643 // last planes of the short dimension; although the wavevector
644 // points in the opposite direction in the redundant region of
645 // k-space, the product of two components of the wavevector
646 // cancels out the negative sign
647 cell_energy *= 2.;
648 }
650 }
652 });
653 assert(heffte_almost_equal(static_cast<FloatType>(node_energy_heffte),
654 static_cast<FloatType>(node_energy)));
655 }
656#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
657 node_energy *= dipole_prefac * std::numbers::pi * box_geo.length_inv()[0];
658 boost::mpi::reduce(comm_cart, node_energy, energy, std::plus<>(), 0);
659
660 if (dp3m.energy_correction == 0.)
661 calc_energy_correction();
662
663 if (this_node == 0) {
664 /* self energy correction */
665 energy -= prefactor * dp3m.sum_mu2 * std::numbers::inv_sqrtpi *
666 (2. / 3.) * Utils::int_pow<3>(dp3m.params.alpha);
667
668 /* dipolar energy correction due to systematic Madelung-self effects */
669 energy += prefactor * dp3m.energy_correction / box_geo.volume();
670 }
671 }
672 } // if (energy_flag)
673
674 /* === k-space force calculation === */
675 if (force_flag) {
676 /****************************
677 * DIPOLAR TORQUES (k-space)
678 ****************************/
679 if (dp3m.sum_mu2 > 0.) {
680 auto const wavenumber = 2. * std::numbers::pi * box_geo.length_inv()[0u];
681 dp3m.ks_scalar.resize(dp3m.local_mesh.size);
682 /* fill in ks_scalar array for torque calculation */
683 {
684 auto index{std::size_t(0u)};
685 auto it_energy = dp3m.g_energy.begin();
686 auto it_ks_scalar = dp3m.ks_scalar.begin();
687 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() mutable {
688 auto constexpr KX = 2, KY = 0, KZ = 1;
689 auto const shift = local_index + dp3m.mesh.start;
690 auto const &d_op = dp3m.d_op[0u];
691 auto const &mesh_dip = dp3m.mesh.rs_fields;
692 // Re(mu)*k
693 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
694 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
695 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
696 ++index;
697 // Im(mu)*k
698 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
699 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
700 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
701 ++index;
702 *it_ks_scalar = *it_energy * std::complex<FloatType>{re, im};
703 std::advance(it_energy, 1);
704 std::advance(it_ks_scalar, 1);
705 });
706 }
707#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
708 if (dp3m.heffte.world_size == 1) {
709 std::size_t index_row_major_r2c = 0u;
712 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
714 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
715 dp3m.heffte.ks_scalar[index_row_major_r2c] =
716 dp3m.heffte.g_energy[index_row_major_r2c] *
718 FloatType(dp3m.d_op[0u][global_index[1u]]) +
720 FloatType(dp3m.d_op[1u][global_index[2u]]) +
722 FloatType(dp3m.d_op[2u][global_index[0u]]));
723#ifndef NDEBUG
724 if (not dp3m.params.tuning) {
725 auto constexpr KX = 2, KY = 0, KZ = 1;
726 auto const index_fft_legacy = local_index[KZ] +
730 dp3m.heffte.ks_scalar[index_row_major_r2c],
731 dp3m.ks_scalar[index_fft_legacy]));
732 }
733#endif // not NDEBUG
735 }
736 });
737 }
738#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
739
740 /* Torque component loop */
741 for (int d = 0; d < 3; d++) {
742 auto it_ks_scalar = dp3m.ks_scalar.begin();
743 auto index = 0u;
744 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
745 auto const &offset = dp3m.mesh.start;
746 auto const &d_op = dp3m.d_op[0u];
747 auto const d_op_val = FloatType(d_op[local_index[d] + offset[d]]);
748 auto const &value = *it_ks_scalar;
749 dp3m.mesh.rs_scalar[index] = d_op_val * value.real();
750 ++index;
751 dp3m.mesh.rs_scalar[index] = d_op_val * value.imag();
752 ++index;
753 std::advance(it_ks_scalar, 1);
754 });
755#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
756 if (dp3m.heffte.world_size == 1) {
757 unsigned int constexpr d_ks[3] = {2u, 0u, 1u};
758 std::size_t index_row_major_r2c = 0u;
761 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
763 auto const d_op_val =
764 FloatType(dp3m.d_op[d][global_index[d_ks[d]]]);
765 dp3m.heffte.ks_B_field_storage[index_row_major_r2c] =
766 d_op_val * dp3m.heffte.ks_scalar[index_row_major_r2c];
767#ifndef NDEBUG
768 if (not dp3m.params.tuning) {
769 auto constexpr KX = 2, KY = 0, KZ = 1;
770 auto const index_fft_legacy =
773 auto const old_value = std::complex<FloatType>{
774 dp3m.mesh.rs_scalar[2 * index_fft_legacy],
775 dp3m.mesh.rs_scalar[2 * index_fft_legacy + 1]};
776 auto const &new_value =
777 dp3m.heffte.ks_B_field_storage[index_row_major_r2c];
779 }
780#endif // not NDEBUG
782 }
783 });
784 dp3m.heffte.fft->backward(dp3m.heffte.ks_B_field_storage.data(),
785 dp3m.heffte.rs_B_fields_no_halo[d].data());
786 // pad zeros around the B-field in real space for ghost layers,
787 // writing straight into the persistent halo-sized buffer
788 pad_with_zeros_discard_imag_into<FFTConfig::r_space_order,
790 dp3m.heffte.rs_B_fields[d].data(),
791 std::span(dp3m.heffte.rs_B_fields_no_halo[d]),
792 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
793 dp3m.local_mesh.n_halo_ur);
794 // communicate ghost layers of the B-field in real space
795 dp3m.heffte.halo_comm.spread_grid(::comm_cart,
796 dp3m.heffte.rs_B_fields[d].data(),
797 dp3m.local_mesh.dim);
798 }
799#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
800 dp3m.fft->backward_fft(dp3m.fft_buffers->get_scalar_mesh());
801 // communicate ghost layers of the B-field in real space
802 dp3m.fft_buffers->perform_scalar_halo_spread();
803 // assign torque component from mesh to particle
804 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
805 Utils::integral_parameter<int, AssignTorques, p3m_min_cao, p3m_max_cao>(
806 dp3m.params.cao, dp3m, dipole_prefac * wavenumber, d_rs,
807 *system.cell_structure);
808 }
809
810 /***************************
811 DIPOLAR FORCES (k-space)
812 ****************************/
813 // Compute forces after torques because the algorithm below overwrites the
814 // grids dp3m.mesh.rs_fields !
815 // Note: I'll do here 9 inverse FFTs. By symmetry, we can reduce this
816 // number to 6 !
817 /* fill in ks_scalar array for force calculation */
818 {
819 auto it_force = dp3m.g_force.begin();
820 auto it_ks_scalar = dp3m.ks_scalar.begin();
821 std::size_t index = 0u;
822 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
823 auto constexpr KX = 2, KY = 0, KZ = 1;
824 auto const shift = local_index + dp3m.mesh.start;
825 auto const &d_op = dp3m.d_op[0u];
826 auto const &mesh_dip = dp3m.mesh.rs_fields;
827 // Re(mu)*k
828 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
829 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
830 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
831 ++index;
832 // Im(mu)*k
833 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
834 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
835 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
836 ++index;
837 *it_ks_scalar = {*it_force * im, *it_force * (-re)};
838 std::advance(it_force, 1);
839 std::advance(it_ks_scalar, 1);
840 });
841 }
842
843#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
844 if (dp3m.heffte.world_size == 1) {
845 std::size_t index_row_major_r2c = 0u;
848 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
850 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
851 auto const value =
852 dp3m.heffte.g_force[index_row_major_r2c] *
854 FloatType(dp3m.d_op[0u][global_index[1u]]) +
856 FloatType(dp3m.d_op[1u][global_index[2u]]) +
858 FloatType(dp3m.d_op[2u][global_index[0u]]));
859 dp3m.heffte.ks_scalar[index_row_major_r2c] = {value.imag(),
860 -value.real()};
861#ifndef NDEBUG
862 if (not dp3m.params.tuning) {
863 auto constexpr KX = 2, KY = 0, KZ = 1;
864 auto const index_fft_legacy = local_index[KZ] +
868 dp3m.heffte.ks_scalar[index_row_major_r2c],
869 dp3m.ks_scalar[index_fft_legacy]));
870 }
871#endif // not NDEBUG
873 }
874 });
875 }
876#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
877
878 /* Force component loop */
879 for (int d = 0; d < 3; d++) {
880 std::size_t index = 0u;
881 auto it_ks_scalar = dp3m.ks_scalar.begin();
882 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
883 auto constexpr KX = 2, KY = 0, KZ = 1;
884 auto const shift = local_index + dp3m.mesh.start;
885 auto const &d_op = dp3m.d_op[0u];
886 auto const &mesh_dip = dp3m.mesh.rs_fields;
887 auto const d_op_val = FloatType(d_op[shift[d]]);
888 auto const f = *it_ks_scalar * d_op_val;
889 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.real();
890 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.real();
891 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.real();
892 ++index;
893 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.imag();
894 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.imag();
895 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.imag();
896 ++index;
897 std::advance(it_ks_scalar, 1);
898 });
899
900#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
901 if (dp3m.heffte.world_size == 1) {
902 std::size_t index_row_major_r2c = 0u;
905 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
906 auto constexpr KX = 1, KY = 2, KZ = 0;
908 auto const remapped_index =
911 auto const d_op_val =
912 FloatType(dp3m.d_op[d][global_index[d]]);
913 auto &mesh_dip = dp3m.heffte.ks_dipole_density;
915 FloatType(dp3m.d_op[d][global_index[2u]]) * d_op_val *
916 dp3m.heffte.ks_scalar[remapped_index];
918 FloatType(dp3m.d_op[d][global_index[0u]]) * d_op_val *
919 dp3m.heffte.ks_scalar[remapped_index];
921 FloatType(dp3m.d_op[d][global_index[1u]]) * d_op_val *
922 dp3m.heffte.ks_scalar[remapped_index];
923#ifndef NDEBUG
924 if (not FFTConfig::use_r2c and not dp3m.params.tuning) {
925 auto const index_fft_legacy = local_index[2] +
928 for (int j = 0; j < 3; ++j) {
929 auto const old_value = std::complex<FloatType>{
930 dp3m.mesh.rs_fields[j][2 * index_fft_legacy],
931 dp3m.mesh.rs_fields[j][2 * index_fft_legacy + 1]};
932 auto const &new_value = mesh_dip[j][index_row_major_r2c];
934 }
935 }
936#endif // not NDEBUG
938 }
939 });
940 for (int dir = 0u; dir < 3u; ++dir) {
941 dp3m.heffte.fft->backward(
942 dp3m.heffte.ks_dipole_density[dir].data(),
943 dp3m.heffte.rs_B_fields_no_halo[dir].data());
944 // pad zeros around the B-field in real space for ghost layers,
945 // writing straight into the persistent halo-sized buffer
946 pad_with_zeros_discard_imag_into<FFTConfig::r_space_order,
948 dp3m.heffte.rs_B_fields[d].data(),
949 std::span(dp3m.heffte.rs_B_fields_no_halo[dir]),
950 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
951 dp3m.local_mesh.n_halo_ur);
952 }
953 // communicate ghost layers of the B-field in real space
954 auto rs_fields =
955 std::array<FloatType *, 3u>{{dp3m.heffte.rs_B_fields[0u].data(),
956 dp3m.heffte.rs_B_fields[1u].data(),
957 dp3m.heffte.rs_B_fields[2u].data()}};
958 dp3m.heffte.halo_comm.spread_grid(::comm_cart, rs_fields,
959 dp3m.local_mesh.dim);
960 }
961#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
962 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
963 dp3m.fft->backward_fft(rs_mesh);
964 }
965 // communicate ghost layers of the B-field in real space
966 dp3m.fft_buffers->perform_vector_halo_spread();
967 // assign force component from mesh to particle
968 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
969 Utils::integral_parameter<int, AssignForcesDip, p3m_min_cao,
971 dp3m.params.cao, dp3m, dipole_prefac * Utils::sqr(wavenumber), d_rs,
972 *system.cell_structure);
973 }
974 } /* if (dp3m.sum_mu2 > 0) */
975 } /* if (force_flag) */
976
977 if (dp3m.params.epsilon != P3M_EPSILON_METALLIC) {
978 auto const surface_term = calc_surface_term(force_flag, energy_flag);
979 if (this_node == 0) {
980 energy += surface_term;
981 }
982 }
983#ifdef ESPRESSO_NPT
984 if (npt_flag) {
985 // reuse the validated reciprocal-space pressure tensor (same one used by
986 // the pressure observable) instead of an energy-proxy: unlike Coulomb,
987 // the dipolar structure factor is not simply homogeneous in k, so energy
988 // is not a valid substitute for the virial trace here (see
989 // long_range_pressure())
990 auto const pressure_tensor = long_range_pressure();
993 }
994#endif
995 if (not energy_flag) {
996 energy = 0.;
997 }
998
999 return energy;
1000}
1001
1002template <typename FloatType, Arch Architecture, class FFTConfig>
1004 bool force_flag, bool energy_flag) {
1005 auto const &system = get_system();
1006 auto const &box_geo = *system.box_geo;
1007 auto const particles = system.cell_structure->local_particles();
1008 auto const pref = prefactor * 4. * std::numbers::pi / box_geo.volume() /
1009 (2. * dp3m.params.epsilon + 1.);
1010 auto const n_local_part = particles.size();
1011
1012 // We put all the dipolar momenta in a the arrays mx,my,mz according to the
1013 // id-number of the particles
1014 std::vector<double> mx(n_local_part);
1015 std::vector<double> my(n_local_part);
1016 std::vector<double> mz(n_local_part);
1017
1018 std::size_t ip = 0u;
1019 for (auto const &p : particles) {
1020 auto const dip = p.calc_dip();
1021 mx[ip] = dip[0u];
1022 my[ip] = dip[1u];
1023 mz[ip] = dip[2u];
1024 ip++;
1025 }
1026
1027 // we will need the sum of all dipolar momenta vectors
1028 auto local_dip = Utils::Vector3d{};
1029 for (std::size_t i = 0u; i < n_local_part; i++) {
1030 local_dip[0u] += mx[i];
1031 local_dip[1u] += my[i];
1032 local_dip[2u] += mz[i];
1033 }
1034 auto const box_dip =
1035 boost::mpi::all_reduce(comm_cart, local_dip, std::plus<>());
1036
1037 double energy = 0.;
1038 if (energy_flag) {
1039 double sum_e = 0.;
1040 for (std::size_t i = 0u; i < n_local_part; i++) {
1041 sum_e += mx[i] * box_dip[0] + my[i] * box_dip[1] + mz[i] * box_dip[2];
1042 }
1043 energy =
1044 0.5 * pref * boost::mpi::all_reduce(comm_cart, sum_e, std::plus<>());
1045 }
1046
1047 if (force_flag) {
1048
1049 std::vector<double> sumix(n_local_part);
1050 std::vector<double> sumiy(n_local_part);
1051 std::vector<double> sumiz(n_local_part);
1052
1053 for (std::size_t i = 0u; i < n_local_part; i++) {
1054 sumix[i] = my[i] * box_dip[2u] - mz[i] * box_dip[1u];
1055 sumiy[i] = mz[i] * box_dip[0u] - mx[i] * box_dip[2u];
1056 sumiz[i] = mx[i] * box_dip[1u] - my[i] * box_dip[0u];
1057 }
1058
1059 ip = 0u;
1060 for (auto &p : particles) {
1061 auto &torque = p.torque();
1062 torque[0u] -= pref * sumix[ip];
1063 torque[1u] -= pref * sumiy[ip];
1064 torque[2u] -= pref * sumiz[ip];
1065#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
1066 p.dip_fld() -= pref * box_dip;
1067#endif
1068 ip++;
1069 }
1070 }
1071
1072 return energy;
1073}
1074
1075template <typename FloatType, Arch Architecture, class FFTConfig>
1076void DipolarP3MHeffte<FloatType, Architecture,
1077 FFTConfig>::calc_influence_function_force() {
1078 dp3m.g_force = grid_influence_function_dipolar<FloatType, 3, P3M_BRILLOUIN,
1079 FFTConfig::k_space_order>(
1080 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
1081 get_system().box_geo->length_inv());
1082#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1083 if (dp3m.heffte.world_size == 1) {
1084 dp3m.heffte.g_force =
1086 FFTConfig::k_space_order>(
1087 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
1088 dp3m.heffte.fft->ks_local_ur_index(),
1089 get_system().box_geo->length_inv());
1090 if constexpr (FFTConfig::use_r2c) {
1092 dp3m.heffte.g_force, dp3m.params.mesh,
1093 dp3m.heffte.fft->ks_local_size(),
1094 dp3m.heffte.fft->ks_local_ld_index());
1095 }
1096 }
1097#endif
1098}
1099
1100template <typename FloatType, Arch Architecture, class FFTConfig>
1101void DipolarP3MHeffte<FloatType, Architecture,
1102 FFTConfig>::calc_influence_function_energy() {
1103 dp3m.g_energy = grid_influence_function_dipolar<FloatType, 2, P3M_BRILLOUIN,
1104 FFTConfig::k_space_order>(
1105 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
1106 get_system().box_geo->length_inv());
1107#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1108 if (dp3m.heffte.world_size == 1) {
1109 dp3m.heffte.g_energy =
1111 FFTConfig::k_space_order>(
1112 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
1113 dp3m.heffte.fft->ks_local_ur_index(),
1114 get_system().box_geo->length_inv());
1115 if constexpr (FFTConfig::use_r2c) {
1117 dp3m.heffte.g_energy, dp3m.params.mesh,
1118 dp3m.heffte.fft->ks_local_size(),
1119 dp3m.heffte.fft->ks_local_ld_index());
1120 }
1121 }
1122#endif
1123}
1124
1125template <typename FloatType, Arch Architecture, class FFTConfig>
1128 int m_mesh_max = -1, m_mesh_min = -1;
1129 std::pair<std::optional<int>, std::optional<int>> m_tune_limits;
1130
1131public:
1133 double prefactor, int timings,
1134 decltype(m_tune_limits) tune_limits)
1135 : TuningAlgorithm(system, prefactor, timings), dp3m{input_dp3m},
1136 m_tune_limits{std::move(tune_limits)} {}
1137
1138 P3MParameters &get_params() override { return dp3m.params; }
1139
1140 void on_solver_change() const override { m_system.on_dipoles_change(); }
1141
1142 std::optional<std::string>
1143 layer_correction_veto_r_cut(double) const override {
1144 return {};
1145 }
1146
1147 void setup_logger(bool verbose) override {
1148 auto const &box_geo = *m_system.box_geo;
1149 m_logger = std::make_unique<TuningLogger>(
1150 verbose and this_node == 0, "DipolarP3M", TuningLogger::Mode::Dipolar);
1151 m_logger->tuning_goals(dp3m.params.accuracy, m_prefactor,
1152 box_geo.length()[0], dp3m.sum_dip_part,
1153 dp3m.sum_mu2);
1154 m_logger->log_tuning_start();
1155 }
1156
1157 std::tuple<double, double, double, double>
1159 double r_cut_iL) const override {
1160
1161 double alpha_L, rs_err, ks_err;
1162 auto const &box_geo = *m_system.box_geo;
1163
1164 /* calc maximal real space error for setting */
1165 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
1166 dp3m.sum_dip_part, dp3m.sum_mu2, 0.001);
1167 // alpha cannot be zero for dipoles because real-space formula breaks down
1168
1169 if (std::numbers::sqrt2 * rs_err > dp3m.params.accuracy) {
1170 /* assume rs_err = ks_err -> rs_err = accuracy/sqrt(2.0) -> alpha_L */
1171 alpha_L = dp3m_rtbisection(
1172 box_geo.length()[0], r_cut_iL, dp3m.sum_dip_part, dp3m.sum_mu2,
1173 0.0001 * box_geo.length()[0], 5. * box_geo.length()[0], 0.0001,
1174 dp3m.params.accuracy);
1175 } else {
1176 /* even alpha=0 is ok, however, we cannot choose it since it kills the
1177 k-space error formula.
1178 Anyways, this very likely NOT the optimal solution */
1179 alpha_L = 0.1;
1180 }
1181
1182 /* calculate real-space and k-space error for this alpha_L */
1183 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
1184 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1185 ks_err = dp3m_k_space_error(box_geo.length()[0], mesh[0], cao,
1186 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1187
1188 return {Utils::Vector2d{rs_err, ks_err}.norm(), rs_err, ks_err, alpha_L};
1189 }
1190
1191 void determine_mesh_limits() override {
1192 if (dp3m.params.mesh[0] == -1) {
1193 /* simple heuristic to limit the tried meshes if the accuracy cannot
1194 be obtained with smaller meshes, but normally not all these
1195 meshes have to be tested */
1196 auto const expo = std::log(std::cbrt(dp3m.sum_dip_part)) / std::log(2.);
1197 /* Medium-educated guess for the minimal mesh */
1198 m_mesh_min = static_cast<int>(std::round(std::pow(2., std::floor(expo))));
1199 /* avoid using more than 1 GB of FFT arrays */
1200 m_mesh_max = 128;
1201 if (m_tune_limits.first) {
1202 m_mesh_min = *m_tune_limits.first;
1203 }
1204 if (m_tune_limits.second) {
1205 m_mesh_max = *m_tune_limits.second;
1206 }
1207 } else {
1208 m_mesh_min = m_mesh_max = dp3m.params.mesh[0];
1209 m_logger->report_fixed_mesh(dp3m.params.mesh);
1210 }
1211 }
1212
1215 auto time_best = time_sentinel;
1216 for (auto tmp_mesh = m_mesh_min; tmp_mesh <= m_mesh_max; tmp_mesh += 2) {
1219 trial_params.cao = cao_best;
1220
1221 auto const trial_time =
1223 trial_params.alpha_L, trial_params.accuracy);
1224
1225 /* this mesh does not work at all */
1226 if (trial_time < 0.)
1227 continue;
1228
1229 /* the optimum r_cut for this mesh is the upper limit for higher meshes,
1230 everything else is slower */
1231 m_r_cut_iL_max = trial_params.r_cut_iL;
1232
1233 if (trial_time < time_best) {
1234 /* new optimum */
1240 /* no hope of further optimisation */
1241 break;
1242 }
1243 }
1244 return tuned_params;
1245 }
1246};
1247
1248template <typename FloatType, Arch Architecture, class FFTConfig>
1250 auto &system = get_system();
1251 auto const &box_geo = *system.box_geo;
1252 if (dp3m.params.alpha_L == 0. and dp3m.params.alpha != 0.) {
1253 dp3m.params.alpha_L = dp3m.params.alpha * box_geo.length()[0];
1254 }
1255 if (dp3m.params.r_cut_iL == 0. and dp3m.params.r_cut != 0.) {
1256 dp3m.params.r_cut_iL = dp3m.params.r_cut * box_geo.length_inv()[0];
1257 }
1258 if (not is_tuned()) {
1260 if (dp3m.sum_dip_part == 0) {
1261 throw std::runtime_error(
1262 "DipolarP3M: no dipolar particles in the system");
1263 }
1264 try {
1266 system, dp3m, prefactor, tuning.timings, tuning.limits);
1267 parameters.setup_logger(tuning.verbose);
1268 // parameter ranges
1269 parameters.determine_mesh_limits();
1270 parameters.determine_r_cut_limits();
1271 parameters.determine_cao_limits(3);
1272 // run tuning algorithm
1273 parameters.tune();
1274 m_is_tuned = true;
1275 system.on_dipoles_change();
1276 } catch (...) {
1277 dp3m.params.tuning = false;
1278 throw;
1279 }
1280 }
1281 init();
1282}
1283
1284/** Tuning dipolar-P3M */
1285inline auto dp3m_tune_aliasing_sums(Utils::Vector3i const &shift, int mesh,
1286 double mesh_i, int cao, double alpha_L_i) {
1287
1290 auto const factor1 = Utils::sqr(std::numbers::pi * alpha_L_i);
1291 auto alias1 = 0.;
1292 auto alias2 = 0.;
1293
1299 [&]() {
1300 auto const norm_sq = nm.norm2();
1301 auto const ex = std::exp(-factor1 * norm_sq);
1302 auto const U2 = std::pow(Utils::product(fnm), 2 * cao);
1304 alias2 += U2 * ex * std::pow(shift * nm, 3) / norm_sq;
1305 },
1306 [&](unsigned dim, int n) {
1307 nm[dim] = shift[dim] + n * mesh;
1308 fnm[dim] = math::sinc(nm[dim] * mesh_i);
1309 });
1310
1311 return std::make_pair(alias1, alias2);
1312}
1313
1314/** Calculate the k-space error of dipolar-P3M */
1315inline double dp3m_k_space_error(double box_size, int mesh, int cao,
1316 std::size_t n_c_part, double sum_q2,
1317 double alpha_L) {
1318
1320 auto const mesh_i = 1. / static_cast<double>(mesh);
1321 auto const alpha_L_i = 1. / alpha_L;
1322 auto const mesh_stop = Utils::Vector3i::broadcast(mesh / 2);
1323 auto const mesh_start = -mesh_stop;
1324 auto indices = Utils::Vector3i{};
1325 auto values = Utils::Vector3d{};
1326 auto he_q = 0.;
1327
1330 [&]() {
1331 if ((indices[0] != 0) or (indices[1] != 0) or (indices[2] != 0)) {
1332 auto const n2 = indices.norm2();
1333 auto const cs = Utils::product(values);
1334 auto const [alias1, alias2] =
1336 auto const d =
1337 alias1 - Utils::sqr(alias2 / cs) /
1338 Utils::int_pow<3>(static_cast<double>(n2));
1339 /* at high precision, d can become negative due to extinction;
1340 also, don't take values that have no significant digits left*/
1341 if (d > 0. and std::fabs(d / alias1) > round_error_prec)
1342 he_q += d;
1343 }
1344 },
1345 [&values, &mesh_i, cotangent_sum](unsigned dim, int n) {
1346 values[dim] = cotangent_sum(n, mesh_i);
1347 });
1348
1349 return 8. * Utils::sqr(std::numbers::pi) / 3. * sum_q2 *
1350 sqrt(he_q / static_cast<double>(n_c_part)) /
1351 Utils::int_pow<4>(box_size);
1352}
1353
1354/** Calculate the value of the errors for the REAL part of the force in terms
1355 * of the splitting parameter alpha of Ewald. Based on eq. (33) @cite wang01a.
1356 *
1357 * Please note that in this more refined approach we don't use
1358 * eq. (37), but eq. (33) which maintains all the powers in alpha.
1359 */
1360inline double dp3m_real_space_error(double box_size, double r_cut_iL,
1361 std::size_t n_c_part, double sum_q2,
1362 double alpha_L) {
1363 auto constexpr exp_min = -708.4; // for IEEE-compatible double
1364 double d_error_f, d_cc, d_dc, d_con;
1365
1366 auto const d_rcut = r_cut_iL * box_size;
1367 auto const d_rcut2 = Utils::sqr(d_rcut);
1368 auto const d_rcut4 = Utils::sqr(d_rcut2);
1369
1370 auto const d_a2 = Utils::sqr(alpha_L) / Utils::sqr(box_size);
1371 auto const exponent = -d_a2 * d_rcut2;
1372 auto const exp_term = (exponent < exp_min) ? 0. : std::exp(exponent);
1373 auto const d_c = sum_q2 * exp_term;
1374
1375 d_cc = 4. * Utils::sqr(d_a2) * Utils::sqr(d_rcut2) + 6. * d_a2 * d_rcut2 + 3.;
1376
1377 d_dc = 8. * Utils::int_pow<3>(d_a2) * Utils::int_pow<3>(d_rcut2) +
1378 20. * Utils::sqr(d_a2) * d_rcut4 + 30. * d_a2 * d_rcut2 + 15.;
1379
1380 d_con = 1. / sqrt(Utils::int_pow<3>(box_size) * Utils::sqr(d_a2) * d_rcut *
1381 Utils::sqr(d_rcut4) * static_cast<double>(n_c_part));
1382
1383 d_error_f = d_c * d_con *
1384 sqrt((13. / 6.) * Utils::sqr(d_cc) +
1385 (2. / 15.) * Utils::sqr(d_dc) - (13. / 15.) * d_cc * d_dc);
1386
1387 return d_error_f;
1388}
1389
1390/** Using bisection, find the root of a function "func-tuned_accuracy/sqrt(2.)"
1391 * known to lie between x1 and x2. The root, returned as rtbis, will be
1392 * refined until its accuracy is \f$\pm\f$ @p xacc.
1393 */
1394double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part,
1395 double sum_q2, double x1, double x2, double xacc,
1396 double tuned_accuracy) {
1397 constexpr int JJ_RTBIS_MAX = 40;
1398
1399 auto const constant = tuned_accuracy / std::numbers::sqrt2;
1400
1401 auto const f1 =
1402 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x1) -
1403 constant;
1404 auto const f2 =
1405 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x2) -
1406 constant;
1407 if (f1 * f2 >= 0.0) {
1408 throw std::runtime_error(
1409 "Root must be bracketed for bisection in dp3m_rtbisection");
1410 }
1411 // Orient the search dx, and set rtb to x1 or x2 ...
1412 double dx;
1413 double rtb = f1 < 0.0 ? (dx = x2 - x1, x1) : (dx = x1 - x2, x2);
1414 for (int j = 1; j <= JJ_RTBIS_MAX; j++) {
1415 auto const xmid = rtb + (dx *= 0.5);
1416 auto const fmid =
1417 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, xmid) -
1418 constant;
1419 if (fmid <= 0.0)
1420 rtb = xmid;
1421 if (fabs(dx) < xacc || fmid == 0.0)
1422 return rtb;
1423 }
1424 throw std::runtime_error("Too many bisections in dp3m_rtbisection");
1425}
1426
1428 auto const &system = get_system();
1429 auto const &box_geo = *system.box_geo;
1430 auto const &local_geo = *system.local_geo;
1431 for (auto i = 0u; i < 3u; i++) {
1432 /* check k-space cutoff */
1433 if (dp3m_params.cao_cut[i] >= box_geo.length_half()[i]) {
1434 std::stringstream msg;
1435 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1436 << " is larger than half of box dimension " << box_geo.length()[i];
1437 throw std::runtime_error(msg.str());
1438 }
1439 if (dp3m_params.cao_cut[i] >= local_geo.length()[i]) {
1440 std::stringstream msg;
1441 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1442 << " is larger than local box dimension " << local_geo.length()[i];
1443 throw std::runtime_error(msg.str());
1444 }
1445 }
1446
1447 if ((box_geo.length()[0] != box_geo.length()[1]) or
1448 (box_geo.length()[1] != box_geo.length()[2])) {
1449 throw std::runtime_error("DipolarP3M: requires a cubic box");
1450 }
1451}
1452
1454 auto const &box_geo = *get_system().box_geo;
1455 if (!box_geo.periodic(0) or !box_geo.periodic(1) or !box_geo.periodic(2)) {
1456 throw std::runtime_error(
1457 "DipolarP3M: requires periodicity (True, True, True)");
1458 }
1459}
1460
1462 auto const &local_geo = *get_system().local_geo;
1463 if (local_geo.cell_structure_type() != CellStructureType::REGULAR and
1464 local_geo.cell_structure_type() != CellStructureType::HYBRID) {
1465 throw std::runtime_error(
1466 "DipolarP3M: requires the regular or hybrid decomposition cell system");
1467 }
1468 if (::communicator.size > 1 and
1469 local_geo.cell_structure_type() == CellStructureType::HYBRID) {
1470 throw std::runtime_error(
1471 "DipolarP3M: does not work with the hybrid decomposition cell system, "
1472 "if using more than one MPI node");
1473 }
1474}
1475
1477 auto const &node_grid = ::communicator.node_grid;
1478 if (node_grid[0] < node_grid[1] or node_grid[1] < node_grid[2]) {
1479 throw std::runtime_error(
1480 "DipolarP3M: node grid must be sorted, largest first");
1481 }
1482}
1483
1484template <typename FloatType, Arch Architecture, class FFTConfig>
1486 auto const &box_geo = *get_system().box_geo;
1487 dp3m.params.r_cut = dp3m.params.r_cut_iL * box_geo.length()[0];
1488 dp3m.params.alpha = dp3m.params.alpha_L * box_geo.length_inv()[0];
1489 dp3m.params.recalc_a_ai_cao_cut(box_geo.length());
1490 dp3m.local_mesh.recalc_ld_pos(dp3m.params);
1491 sanity_checks_boxl();
1492 calc_influence_function_force();
1493 calc_influence_function_energy();
1494 dp3m.energy_correction = 0.;
1495#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1496 if (dp3m.heffte.world_size == 1) {
1497 dp3m.heffte.halo_comm.resize(::comm_cart, dp3m.local_mesh);
1498 }
1499#endif
1500}
1501
1502template <typename FloatType, Arch Architecture, class FFTConfig>
1503void DipolarP3MHeffte<FloatType, Architecture,
1504 FFTConfig>::calc_energy_correction() {
1505 auto const &box_geo = *get_system().box_geo;
1506 auto const Ukp3m = calc_average_self_energy_k_space() * box_geo.volume();
1507 auto const Ewald_volume = Utils::int_pow<3>(dp3m.params.alpha_L);
1508 auto const Eself = -2. * Ewald_volume * std::numbers::inv_sqrtpi / 3.;
1509 dp3m.energy_correction =
1510 -dp3m.sum_mu2 * (Ukp3m + Eself + 2. * std::numbers::pi / 3.);
1511}
1512
1513#ifdef ESPRESSO_NPT
1514template <typename FloatType, Arch Architecture, class FFTConfig>
1515void DipolarP3MHeffte<FloatType, Architecture,
1516 FFTConfig>::npt_add_virial_contribution(double virial)
1517 const {
1518 get_system().npt_add_virial_contribution(virial);
1519}
1520#endif // ESPRESSO_NPT
1521
1522#endif // ESPRESSO_DP3M
@ HYBRID
Hybrid decomposition.
@ REGULAR
Regular decomposition.
Vector implementation and trait types for boost qvm interoperability.
Describes a cell structure / cell system.
auto const & get_unique_particles() const
auto get_scatter_dip_fld()
auto get_scatter_torque()
std::tuple< double, double, double, double > calculate_accuracy(Utils::Vector3i const &mesh, int cao, double r_cut_iL) const override
TuningAlgorithm::Parameters get_time() override
DipolarTuningAlgorithm(System::System &system, decltype(dp3m) &input_dp3m, double prefactor, int timings, decltype(m_tune_limits) tune_limits)
void on_solver_change() const override
void determine_mesh_limits() override
P3MParameters & get_params() override
std::optional< std::string > layer_correction_veto_r_cut(double) const override
void setup_logger(bool verbose) override
base_type::size_type size() const
Main system class.
void npt_add_virial_contribution(double energy)
Definition npt.cpp:137
std::shared_ptr< CellStructure > cell_structure
std::shared_ptr< BoxGeometry > box_geo
Tuning algorithm for P3M.
double get_m_time(Utils::Vector3i const &mesh, int &tuned_cao, double &tuned_r_cut_iL, double &tuned_alpha_L, double &tuned_accuracy)
Get the optimal alpha and the corresponding computation time for a fixed mesh.
static auto constexpr time_sentinel
Value for invalid time measurements.
static auto constexpr max_n_consecutive_trials
Maximal number of consecutive trials that don't improve runtime.
System::System & m_system
std::unique_ptr< TuningLogger > m_logger
static auto constexpr time_granularity
Granularity of the time measurement (milliseconds).
T norm() const
Definition Vector.hpp:160
static DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:132
void zfill(std::size_t size)
Fill cache with zero-initialized data.
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
Communicator communicator
boost::mpi::communicator comm_cart
The communicator.
int this_node
The number of this node.
constexpr auto round_error_prec
Precision below which a double-precision float is assumed to be zero.
Definition config.hpp:47
__device__ void vector_product(float const *a, float const *b, float *out)
static std::size_t count_magnetic_particles(ParticleRange const &particles)
Definition dlc.cpp:400
P3M algorithm for long-range magnetic dipole-dipole interaction.
double dp3m_real_space_error(double box_size, double r_cut_iL, std::size_t n_c_part, double sum_q2, double alpha_L)
Calculate the value of the errors for the REAL part of the force in terms of the splitting parameter ...
double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part, double sum_q2, double x1, double x2, double xacc, double tuned_accuracy)
Compute the value of alpha through a bisection method.
auto dp3m_tune_aliasing_sums(Utils::Vector3i const &shift, int mesh, double mesh_i, int cao, double alpha_L_i)
Tuning dipolar-P3M.
double dp3m_k_space_error(double box_size, int mesh, int cao, std::size_t n_c_part, double sum_q2, double alpha_L)
Calculate the k-space error of dipolar-P3M.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
Routines, row decomposition, data structures and communication for the 3D-FFT.
void extract_block_into(OutValue *out, Container const &in_array, Utils::Vector3i const &dimensions, Utils::Vector3i const &start, Utils::Vector3i const &stop)
Extract a 3D block from the halo field into a caller-provided buffer.
void pad_with_zeros_discard_imag_into(OutValue *out, std::span< T > cropped_array, Utils::Vector3i const &cropped_dim, Utils::Vector3i const &pad_left, Utils::Vector3i const &pad_right)
Pad a 3D matrix with zeros to restore halo regions, writing into a caller-provided buffer of product(...
and std::invocable< Projector, unsigned, int > void for_each_3d(detail::IndexVectorConcept auto &&start, detail::IndexVectorConcept auto &&stop, detail::IndexVectorConcept auto &&counters, Kernel &&kernel, Projector &&projector=detail::noop_projector)
Repeat an operation on every element of a 3D grid.
std::vector< FloatType > grid_influence_function_dipolar(P3MParameters const &params, Utils::Vector3i const &n_start, Utils::Vector3i const &n_stop, Utils::Vector3d const &inv_box_l)
Map influence function over a grid.
void p3m_interpolate(P3MLocalMesh const &local_mesh, WeightsStorage< cao > const &weights, Kernel kernel)
P3M grid interpolation.
constexpr int p3m_min_cao
Minimal charge assignment order.
Definition math.hpp:48
constexpr int p3m_max_cao
Maximal charge assignment order.
Definition math.hpp:50
#define P3M_BRILLOUIN
P3M: Number of Brillouin zones taken into account in the calculation of the optimal influence functio...
Definition math.hpp:38
System & get_system()
T product(Vector< T, N > const &v)
Definition Vector.hpp:383
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
decltype(auto) integral_parameter(T i, Args &&...args)
Generate a call table for an integral non-type template parameter.
DEVICE_QUALIFIER auto sinc(T x)
Calculate the function .
Definition math.hpp:71
auto get_analytic_cotangent_sum_kernel(int cao)
Definition math.hpp:146
STL namespace.
Exports for the NpT code.
Common functions for dipolar and charge P3M.
auto constexpr P3M_EPSILON_METALLIC
This value indicates metallic boundary conditions.
Utils::Vector3i node_grid
double calc_surface_term(bool force_flag, bool energy_flag) override
void dipole_assign() override
Utils::Vector9d long_range_pressure() override
Reciprocal-space virial for the dipolar Ewald/P3M sum.
void scaleby_box_l() override
double long_range_kernel(bool force_flag, bool energy_flag)
Compute the k-space part of forces and energies.
Base class for the magnetostatics P3M algorithm.
double sum_mu2
Sum of square of magnetic dipoles.
p3m_interpolation_cache inter_weights
FloatType value_type
std::size_t sum_dip_part
number of dipolar particles.
p3m_send_mesh< FloatType > halo_comm
double energy_correction
cached k-space self-energy correction
void resize_heffte_buffers()
struct DipolarP3MState::@1 heffte
void sanity_checks_boxl() const
Checks for correctness of the k-space cutoff.
void sanity_checks_cell_structure() const
P3MParameters const & dp3m_params
Definition dp3m.hpp:55
void sanity_checks_periodicity() const
void sanity_checks_node_grid() const
void recalc_ld_pos(P3MParameters const &params)
Recalculate quantities derived from the mesh and box length: ld_pos (position of the left down mesh).
Structure to hold P3M parameters and some dependent variables.
Utils::Vector3d cao_cut
cutoff for charge assignment.
double alpha
unscaled alpha_L for use with fast inline functions only
double r_cut_iL
cutoff radius for real space electrostatics (>0), rescaled to r_cut_iL = r_cut * box_l_i.
double accuracy
accuracy of the actual parameter set.
double alpha_L
Ewald splitting parameter (0.
double r_cut
unscaled r_cut_iL for use with fast inline functions only
void recalc_a_ai_cao_cut(Utils::Vector3d const &box_l)
Recalculate quantities derived from the mesh and box length: a, ai and cao_cut.
bool tuning
tuning or production?
Utils::Vector3i mesh
number of mesh points per coordinate direction (>0), in real space.
P3MLocalMesh local_mesh
Local mesh geometry information for this MPI rank.
P3MParameters params
P3M base parameters.
void operator()(auto &dp3m, double pref, int d_rs, CellStructure &cell_structure) const
void operator()(auto &dp3m, double pref, int d_rs, CellStructure &cell_structure) const