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 =
136 grid_influence_function_self_energy<FloatType, P3M_BRILLOUIN>(
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
193 kokkos_parallel_range_for<execution_space>(
194 "InterpolateDipoles", std::size_t{0u}, n_part, [&](auto p_index) {
195 auto constexpr memory_order = Utils::MemoryOrder::ROW_MAJOR;
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 =
200 p3m_calculate_interpolation_weights<cao, memory_order>(
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();
212 kokkos_parallel_range_for<execution_space>(
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);
254 Utils::Vector3d E{};
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 cell_structure.mark_torque_replicas_dirty();
278 auto scatter_torque = cell_structure.get_scatter_torque();
279#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
280 auto scatter_dip_fld = cell_structure.get_scatter_dip_fld();
281#endif
282 kokkos_parallel_range_for<execution_space>(
283 "AssignTorques", std::size_t{0u}, n_part, [&](std::size_t p_index) {
284 auto const &p = *unique_particles.at(p_index);
285 if (p.dipm() != 0.) {
286 kernel(p.calc_dip(),
287#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
288 scatter_dip_fld,
289#endif
290 scatter_torque, p_index);
291 }
292 });
293 }
294};
295
296template <int cao> struct AssignForcesDip {
297 void operator()(auto &dp3m, double pref, int d_rs,
298 CellStructure &cell_structure) const {
299
300 assert(cao == dp3m.inter_weights.cao());
301 using execution_space = Kokkos::DefaultHostExecutionSpace;
302
303 auto const kernel = [d_rs, pref, &dp3m](auto const &dip, auto &p_force,
304 std::size_t p_index) {
305 auto const weights = dp3m.inter_weights.template load<cao>(p_index);
306
307 Utils::Vector3d E{};
308 p3m_interpolate(dp3m.local_mesh, weights, [&E, &dp3m](int ind, double w) {
309 // heFFTe data: dp3m.heffte.rs_B_fields
310 E[0u] += w * double(dp3m.mesh.rs_fields[0u][ind]);
311 E[1u] += w * double(dp3m.mesh.rs_fields[1u][ind]);
312 E[2u] += w * double(dp3m.mesh.rs_fields[2u][ind]);
313 });
314
315 auto access = p_force.access();
316 access(p_index, d_rs) += pref * (dip * E);
317 };
318
319 auto const n_part = dp3m.inter_weights.size();
320 auto const &unique_particles = cell_structure.get_unique_particles();
321 auto scatter_force = cell_structure.get_scatter_force();
322 kokkos_parallel_range_for<execution_space>(
323 "AssignForcesDip", std::size_t{0u}, n_part, [&](std::size_t p_index) {
324 auto const &p = *unique_particles.at(p_index);
325 if (p.dipm() != 0.) {
326 kernel(p.calc_dip(), scatter_force, p_index);
327 }
328 });
329 }
330};
331} // namespace
332
333#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
334template <typename FloatType, class FFTConfig>
336 auto const rs_array_size =
337 static_cast<std::size_t>(Utils::product(this->local_mesh.dim));
338 auto const rs_array_size_no_halo =
339 static_cast<std::size_t>(Utils::product(this->local_mesh.dim_no_halo));
340 auto const fft_mesh_size =
341 static_cast<std::size_t>(Utils::product(heffte.fft->ks_local_size()));
342 for (auto d : {0u, 1u, 2u}) {
343 heffte.rs_dipole_density[d].resize(rs_array_size);
344 heffte.ks_dipole_density[d].resize(fft_mesh_size);
345 heffte.rs_B_fields[d].resize(rs_array_size);
346 heffte.rs_B_fields_no_halo[d].resize(rs_array_size_no_halo);
347 }
348 heffte.ks_B_field_storage.resize(fft_mesh_size);
349 heffte.ks_scalar.resize(fft_mesh_size);
350}
351#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
352
353/**
354 * @brief Reciprocal-space virial for the dipolar Ewald/P3M sum. Obtained
355 * via the same Nose-Klein strain-derivative method used for the Coulomb
356 * case (@cite essmann95a eq. (2.7), \f$\Pi_{\textrm{rec}, \alpha, \beta}\f$),
357 * applied to the dipolar structure
358 * factor \f$Q(\vec k) = \vec M(\vec k)\cdot\vec k\f$ with
359 * \f$\vec M(\vec k) = \sum_j \vec \mu_j \exp(i\vec k\cdot\vec r_j)\f$.
360 * Unlike the charge structure factor, \f$Q(\vec k)\f$ depends on
361 * \f$\vec k\f$ explicitly (not only through the phase factor), which
362 * produces an extra cross term beyond the charge-case \f$k_a k_b\f$
363 * envelope. This cross term is generally asymmetric in \f$(a,b)\f$: its
364 * symmetric half, \f$k_a\Re[M_b Q^*] + k_b\Re[M_a Q^*]\f$, is the
365 * dipole-dipole reciprocal-space pressure tensor eq. (46) in
366 * @cite aguado03a (their \f$\vec h\f$, \f$\kappa\f$ correspond to
367 * \f$\vec k\f$, \f$\alpha\f$ here), which only ever reports that
368 * symmetrized form. The remaining antisymmetric half is not in that
369 * reference -- it is the reciprocal-space image of the same
370 * dipole-dipole torque that already makes the real-space virial
371 * asymmetric (see @ref DipolarDirectSum::long_range_pressure and
372 * @ref DipolarP3M::pair_force), derived here by differentiating
373 * the reciprocal energy directly (via the strain parametrization
374 * \f$H(\varepsilon)=LI+\varepsilon E_{ab}\f$) instead of presupposing a
375 * symmetric result.
376 *
377 * Care is needed with the index convention: probing the strain component
378 * \f$\varepsilon_{ab}\f$ (i.e. \f$H(\varepsilon)=LI+\varepsilon E_{ab}\f$)
379 * yields \f$-\partial U/\partial\varepsilon_{ab} = r_b F_a\f$ for a pair
380 * separation \f$\vec r\f$ and force \f$\vec F\f$ -- the *transpose* of the
381 * \f$r_a F_b\f$ (@ref Utils::tensor_product "d (x) f") convention used by
382 * the real-space term and by @ref DipolarDirectSum::long_range_pressure.
383 * Concretely, differentiating \f$Q(\vec k)=\vec k\cdot\vec M\f$ gives a
384 * cross-term contribution to \f$-\partial U/\partial\varepsilon_{ab}\f$
385 * proportional to \f$k_a\Re[M_bQ^*]\f$; to match the \f$r_aF_b\f$
386 * convention, this must be stored as the \f$(b,a)\f$ tensor component,
387 * i.e. \f$\Pi_{ab}\f$ gets cross term \f$2k_b\Re[M_aQ^*]\f$ (indices
388 * swapped relative to the strain probe that produced it).
389 */
390template <typename FloatType, Arch Architecture, class FFTConfig>
393 auto const &system = get_system();
394 auto const &box_geo = *system.box_geo;
395 auto const dipole_prefac = prefactor / Utils::product(dp3m.params.mesh);
396 Utils::Vector9d node_k_space_pressure_tensor{};
397
398 if (dp3m.sum_mu2 > 0.) {
399 dipole_assign();
400 dp3m.fft_buffers->perform_vector_halo_gather();
401 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
402 dp3m.fft->forward_fft(rs_mesh);
403 }
404 dp3m.update_mesh_views();
405
406 auto constexpr mesh_start = Utils::Vector3i::broadcast(0);
407 auto local_index = Utils::Vector3i::broadcast(0);
408 auto const wavevector = 2. * std::numbers::pi * box_geo.length_inv()[0];
409 auto const half_alpha_inv_sq = Utils::sqr(1. / (2. * dp3m.params.alpha));
410
411 auto index = std::size_t(0u);
412 auto it_energy = dp3m.g_energy.begin();
413 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
414 auto constexpr KX = 2, KY = 0, KZ = 1;
415 auto const shift = local_index + dp3m.mesh.start;
416 auto const &d_op = dp3m.d_op[0u];
417 auto const &mesh_dip = dp3m.mesh.rs_fields;
418 auto const d_op_x = static_cast<FloatType>(d_op[shift[KX]]);
419 auto const d_op_y = static_cast<FloatType>(d_op[shift[KY]]);
420 auto const d_op_z = static_cast<FloatType>(d_op[shift[KZ]]);
421
422 // Re(M(k)) and Re(Q(k)) = Re(M(k)).n, same as the energy kernel's `re`
423 auto const Mx_re = mesh_dip[0u][index];
424 auto const My_re = mesh_dip[1u][index];
425 auto const Mz_re = mesh_dip[2u][index];
426 auto const Q_re = Mx_re * d_op_x + My_re * d_op_y + Mz_re * d_op_z;
427 ++index;
428 // Im(M(k)) and Im(Q(k))
429 auto const Mx_im = mesh_dip[0u][index];
430 auto const My_im = mesh_dip[1u][index];
431 auto const Mz_im = mesh_dip[2u][index];
432 auto const Q_im = Mx_im * d_op_x + My_im * d_op_y + Mz_im * d_op_z;
433 ++index;
434
435 auto const nx = static_cast<double>(d_op[shift[KX]]);
436 auto const ny = static_cast<double>(d_op[shift[KY]]);
437 auto const nz = static_cast<double>(d_op[shift[KZ]]);
438 auto const kx = nx * wavevector;
439 auto const ky = ny * wavevector;
440 auto const kz = nz * wavevector;
441 auto const norm_sq = Utils::sqr(kx) + Utils::sqr(ky) + Utils::sqr(kz);
442 if (norm_sq != 0.) {
443 auto const g = static_cast<double>(*it_energy);
444 auto const cell_energy =
445 g * static_cast<double>(Utils::sqr(Q_re) + Utils::sqr(Q_im));
446 auto const vterm = -2. * (1. / norm_sq + half_alpha_inv_sq);
447
448 // g * Re(M_a(k) * Q(k)^*), a in {x, y, z}
449 auto const Rx = g * static_cast<double>(Mx_re * Q_re + Mx_im * Q_im);
450 auto const Ry = g * static_cast<double>(My_re * Q_re + My_im * Q_im);
451 auto const Rz = g * static_cast<double>(Mz_re * Q_re + Mz_im * Q_im);
452
453 // Full (generally asymmetric) tensor: Pi_ab = cell_energy * (delta_ab
454 // + vterm * k_a * k_b) + 2 * k_b * R_a (note: indices of the cross
455 // term are swapped relative to the strain probe that produces it --
456 // see the class-level comment above for the derivation). The
457 // symmetric combination (R_a k_b + R_b k_a)/2 recovers the
458 // literature (Aguado & Madden, eq. 46) result; the leftover
459 // antisymmetric part is the k-space image of the same
460 // dipolar-torque signature that already makes the real-space term
461 // asymmetric (see dipolar_direct_sum.cpp / dp3m.hpp).
462 node_k_space_pressure_tensor[0u] +=
463 cell_energy * (1. + vterm * kx * kx) + 2. * nx * Rx; /* xx */
464 node_k_space_pressure_tensor[1u] +=
465 cell_energy * vterm * kx * ky + 2. * ny * Rx; /* xy */
466 node_k_space_pressure_tensor[2u] +=
467 cell_energy * vterm * kx * kz + 2. * nz * Rx; /* xz */
468 node_k_space_pressure_tensor[3u] +=
469 cell_energy * vterm * ky * kx + 2. * nx * Ry; /* yx */
470 node_k_space_pressure_tensor[4u] +=
471 cell_energy * (1. + vterm * ky * ky) + 2. * ny * Ry; /* yy */
472 node_k_space_pressure_tensor[5u] +=
473 cell_energy * vterm * ky * kz + 2. * nz * Ry; /* yz */
474 node_k_space_pressure_tensor[6u] +=
475 cell_energy * vterm * kz * kx + 2. * nx * Rz; /* zx */
476 node_k_space_pressure_tensor[7u] +=
477 cell_energy * vterm * kz * ky + 2. * ny * Rz; /* zy */
478 node_k_space_pressure_tensor[8u] +=
479 cell_energy * (1. + vterm * kz * kz) + 2. * nz * Rz; /* zz */
480 }
481 std::advance(it_energy, 1);
482 });
483 }
484
485 return node_k_space_pressure_tensor * dipole_prefac * std::numbers::pi *
486 box_geo.length_inv()[0];
487}
488
489template <typename FloatType, Arch Architecture, class FFTConfig>
491 bool force_flag, bool energy_flag) {
492 /* k-space energy */
493 double energy = 0.;
494 auto const &system = get_system();
495 auto const &box_geo = *system.box_geo;
496 auto const dipole_prefac = prefactor / Utils::product(dp3m.params.mesh);
497
498 auto constexpr mesh_start = Utils::Vector3i::broadcast(0);
499 auto local_index = Utils::Vector3i::broadcast(0);
500#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
501 auto constexpr r2c_dir = FFTConfig::r2c_dir;
502 auto const rs_local_size = dp3m.heffte.fft->rs_local_size();
503 auto const local_size = dp3m.heffte.fft->ks_local_size();
504 auto local_size_full = local_size;
505 if constexpr (FFTConfig::use_r2c) {
506 local_size_full[r2c_dir] -= 1;
507 local_size_full[r2c_dir] *= 2;
508 }
509 auto const local_origin = dp3m.heffte.fft->ks_local_ld_index();
510#ifndef NDEBUG
511 auto const line_stride = local_size_full[0];
512 auto const plane_stride = local_size_full[0] * local_size_full[0];
513#endif
514 auto const &global_size = dp3m.params.mesh;
515 auto const cutoff_left = 1 - local_origin[r2c_dir];
516 auto const cutoff_right = global_size[r2c_dir] / 2 - local_origin[r2c_dir];
517 auto &short_dim = local_index[r2c_dir];
518 dp3m.resize_heffte_buffers();
519#endif
520
521 if (dp3m.sum_mu2 > 0.) {
522 dipole_assign();
523 dp3m.fft_buffers->perform_vector_halo_gather();
524 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
525 dp3m.fft->forward_fft(rs_mesh);
526 }
527 dp3m.update_mesh_views();
528
529#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
530 if (dp3m.heffte.world_size == 1) {
531 // halo communication of real space dipoles density
532 std::array<FloatType *, 3u> rs_fields = {
533 {dp3m.heffte.rs_dipole_density[0u].data(),
534 dp3m.heffte.rs_dipole_density[1u].data(),
535 dp3m.heffte.rs_dipole_density[2u].data()}};
536 dp3m.heffte.halo_comm.gather_grid(::comm_cart, rs_fields,
537 dp3m.local_mesh.dim);
538
539 for (auto dir : {0u, 1u, 2u}) {
540 // get real-space dipoles density without ghost layers
542 FFTConfig::r_space_order>(
543 dp3m.rs_field_no_halo_kokkos.data(),
544 dp3m.heffte.rs_dipole_density[dir], dp3m.local_mesh.dim,
545 dp3m.local_mesh.n_halo_ld,
546 dp3m.local_mesh.dim - dp3m.local_mesh.n_halo_ur);
547 // re-order data in row-major
548 std::size_t index_row_major = 0u;
549 for_each_3d_order<FFTConfig::k_space_order>(
550 mesh_start, rs_local_size, local_index, [&]() {
551 auto constexpr KX = 1, KY = 2, KZ = 0;
552 auto const index = local_index[KZ] +
553 rs_local_size[0] * local_index[KY] +
554 Utils::sqr(rs_local_size[0]) * local_index[KX];
555 dp3m.rs_field_no_halo_reorder_kokkos(index_row_major) =
556 dp3m.rs_field_no_halo_kokkos(index);
557 ++index_row_major;
558 });
559 dp3m.heffte.fft->forward(dp3m.rs_field_no_halo_reorder_kokkos.data(),
560 dp3m.heffte.ks_dipole_density[dir].data());
561#ifndef NDEBUG
562 if (not dp3m.params.tuning) {
563 std::size_t index_row_major_r2c = 0u;
564 for_each_3d_order<FFTConfig::k_space_order>(
565 mesh_start, local_size, local_index, [&]() {
566 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
567 auto constexpr KX = 2, KY = 0, KZ = 1;
568 auto const index_fft_legacy = local_index[KZ] +
569 line_stride * local_index[KY] +
570 plane_stride * local_index[KX];
571 auto const old_value = std::complex<FloatType>{
572 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy],
573 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy + 1]};
574 auto const &new_value =
575 dp3m.heffte.ks_dipole_density[dir][index_row_major_r2c];
576 assert(heffte_almost_equal(new_value, old_value));
577 ++index_row_major_r2c;
578 }
579 });
580 }
581#endif // not NDEBUG
582 }
583 }
584#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
585 }
586
587 /* === k-space energy calculation === */
588 if (energy_flag) {
589 /*********************
590 Dipolar energy
591 **********************/
592 if (dp3m.sum_mu2 > 0.) {
593 /* i*k differentiation for dipolar gradients:
594 * |(\Fourier{\vect{mu}}(k)\cdot \vect{k})|^2 */
595
596 auto index = std::size_t(0u);
597 auto it_energy = dp3m.g_energy.begin();
598 auto node_energy = 0.;
599 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
600 auto constexpr KX = 2, KY = 0, KZ = 1;
601 auto const shift = local_index + dp3m.mesh.start;
602 auto const &d_op = dp3m.d_op[0u];
603 auto const &mesh_dip = dp3m.mesh.rs_fields;
604 // Re(mu)*k
605 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
606 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
607 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
608 ++index;
609 // Im(mu)*k
610 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
611 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
612 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
613 ++index;
614 node_energy += *it_energy * (Utils::sqr(re) + Utils::sqr(im));
615 std::advance(it_energy, 1);
616 });
617#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
618 if (dp3m.heffte.world_size == 1) {
619 [[maybe_unused]] auto node_energy_heffte = 0.;
620 std::size_t index_row_major_r2c = 0u;
621 for_each_3d_order<FFTConfig::k_space_order>(
622 mesh_start, local_size, local_index, [&]() {
623 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
624 auto const global_index = local_origin + local_index;
625 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
626 auto const cell_field =
627 mesh_dip[0u][index_row_major_r2c] *
628 FloatType(dp3m.d_op[0u][global_index[1u]]) +
629 mesh_dip[1u][index_row_major_r2c] *
630 FloatType(dp3m.d_op[1u][global_index[2u]]) +
631 mesh_dip[2u][index_row_major_r2c] *
632 FloatType(dp3m.d_op[2u][global_index[0u]]);
633 auto cell_energy = static_cast<double>(
634 dp3m.heffte.g_energy[index_row_major_r2c] *
635 std::norm(cell_field));
636 if (FFTConfig::use_r2c and (short_dim >= cutoff_left and
637 short_dim <= cutoff_right - 1)) {
638 // k-space symmetry: double counting except in the first and
639 // last planes of the short dimension; although the wavevector
640 // points in the opposite direction in the redundant region of
641 // k-space, the product of two components of the wavevector
642 // cancels out the negative sign
643 cell_energy *= 2.;
644 }
645 node_energy_heffte += cell_energy;
646 }
647 ++index_row_major_r2c;
648 });
649 assert(heffte_almost_equal(static_cast<FloatType>(node_energy_heffte),
650 static_cast<FloatType>(node_energy)));
651 }
652#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
653 node_energy *= dipole_prefac * std::numbers::pi * box_geo.length_inv()[0];
654 boost::mpi::reduce(comm_cart, node_energy, energy, std::plus<>(), 0);
655
656 if (dp3m.energy_correction == 0.)
657 calc_energy_correction();
658
659 if (this_node == 0) {
660 /* self energy correction */
661 energy -= prefactor * dp3m.sum_mu2 * std::numbers::inv_sqrtpi *
662 (2. / 3.) * Utils::int_pow<3>(dp3m.params.alpha);
663
664 /* dipolar energy correction due to systematic Madelung-self effects */
665 energy += prefactor * dp3m.energy_correction / box_geo.volume();
666 }
667 }
668 } // if (energy_flag)
669
670 /* === k-space force calculation === */
671 if (force_flag) {
672 /****************************
673 * DIPOLAR TORQUES (k-space)
674 ****************************/
675 if (dp3m.sum_mu2 > 0.) {
676 auto const wavenumber = 2. * std::numbers::pi * box_geo.length_inv()[0u];
677 dp3m.ks_scalar.resize(dp3m.local_mesh.size);
678 /* fill in ks_scalar array for torque calculation */
679 {
680 auto index{std::size_t(0u)};
681 auto it_energy = dp3m.g_energy.begin();
682 auto it_ks_scalar = dp3m.ks_scalar.begin();
683 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() mutable {
684 auto constexpr KX = 2, KY = 0, KZ = 1;
685 auto const shift = local_index + dp3m.mesh.start;
686 auto const &d_op = dp3m.d_op[0u];
687 auto const &mesh_dip = dp3m.mesh.rs_fields;
688 // Re(mu)*k
689 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
690 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
691 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
692 ++index;
693 // Im(mu)*k
694 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
695 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
696 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
697 ++index;
698 *it_ks_scalar = *it_energy * std::complex<FloatType>{re, im};
699 std::advance(it_energy, 1);
700 std::advance(it_ks_scalar, 1);
701 });
702 }
703#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
704 if (dp3m.heffte.world_size == 1) {
705 std::size_t index_row_major_r2c = 0u;
706 for_each_3d_order<FFTConfig::k_space_order>(
707 mesh_start, local_size, local_index, [&]() {
708 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
709 auto const global_index = local_origin + local_index;
710 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
711 dp3m.heffte.ks_scalar[index_row_major_r2c] =
712 dp3m.heffte.g_energy[index_row_major_r2c] *
713 (mesh_dip[0u][index_row_major_r2c] *
714 FloatType(dp3m.d_op[0u][global_index[1u]]) +
715 mesh_dip[1u][index_row_major_r2c] *
716 FloatType(dp3m.d_op[1u][global_index[2u]]) +
717 mesh_dip[2u][index_row_major_r2c] *
718 FloatType(dp3m.d_op[2u][global_index[0u]]));
719#ifndef NDEBUG
720 if (not dp3m.params.tuning) {
721 auto constexpr KX = 2, KY = 0, KZ = 1;
722 auto const index_fft_legacy = local_index[KZ] +
723 line_stride * local_index[KY] +
724 plane_stride * local_index[KX];
725 assert(heffte_almost_equal(
726 dp3m.heffte.ks_scalar[index_row_major_r2c],
727 dp3m.ks_scalar[index_fft_legacy]));
728 }
729#endif // not NDEBUG
730 ++index_row_major_r2c;
731 }
732 });
733 }
734#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
735
736 /* Torque component loop */
737 for (int d = 0; d < 3; d++) {
738 auto it_ks_scalar = dp3m.ks_scalar.begin();
739 auto index = 0u;
740 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
741 auto const &offset = dp3m.mesh.start;
742 auto const &d_op = dp3m.d_op[0u];
743 auto const d_op_val = FloatType(d_op[local_index[d] + offset[d]]);
744 auto const &value = *it_ks_scalar;
745 dp3m.mesh.rs_scalar[index] = d_op_val * value.real();
746 ++index;
747 dp3m.mesh.rs_scalar[index] = d_op_val * value.imag();
748 ++index;
749 std::advance(it_ks_scalar, 1);
750 });
751#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
752 if (dp3m.heffte.world_size == 1) {
753 unsigned int constexpr d_ks[3] = {2u, 0u, 1u};
754 std::size_t index_row_major_r2c = 0u;
755 for_each_3d_order<FFTConfig::k_space_order>(
756 mesh_start, local_size, local_index, [&]() {
757 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
758 auto const global_index = local_origin + local_index;
759 auto const d_op_val =
760 FloatType(dp3m.d_op[d][global_index[d_ks[d]]]);
761 dp3m.heffte.ks_B_field_storage[index_row_major_r2c] =
762 d_op_val * dp3m.heffte.ks_scalar[index_row_major_r2c];
763#ifndef NDEBUG
764 if (not dp3m.params.tuning) {
765 auto constexpr KX = 2, KY = 0, KZ = 1;
766 auto const index_fft_legacy =
767 local_index[KZ] + line_stride * local_index[KY] +
768 plane_stride * local_index[KX];
769 auto const old_value = std::complex<FloatType>{
770 dp3m.mesh.rs_scalar[2 * index_fft_legacy],
771 dp3m.mesh.rs_scalar[2 * index_fft_legacy + 1]};
772 auto const &new_value =
773 dp3m.heffte.ks_B_field_storage[index_row_major_r2c];
774 assert(heffte_almost_equal(new_value, old_value));
775 }
776#endif // not NDEBUG
777 ++index_row_major_r2c;
778 }
779 });
780 dp3m.heffte.fft->backward(dp3m.heffte.ks_B_field_storage.data(),
781 dp3m.heffte.rs_B_fields_no_halo[d].data());
782 // pad zeros around the B-field in real space for ghost layers,
783 // writing straight into the persistent halo-sized buffer
784 pad_with_zeros_discard_imag_into<FFTConfig::r_space_order,
786 dp3m.heffte.rs_B_fields[d].data(),
787 std::span(dp3m.heffte.rs_B_fields_no_halo[d]),
788 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
789 dp3m.local_mesh.n_halo_ur);
790 // communicate ghost layers of the B-field in real space
791 dp3m.heffte.halo_comm.spread_grid(::comm_cart,
792 dp3m.heffte.rs_B_fields[d].data(),
793 dp3m.local_mesh.dim);
794 }
795#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
796 dp3m.fft->backward_fft(dp3m.fft_buffers->get_scalar_mesh());
797 // communicate ghost layers of the B-field in real space
798 dp3m.fft_buffers->perform_scalar_halo_spread();
799 // assign torque component from mesh to particle
800 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
801 Utils::integral_parameter<int, AssignTorques, p3m_min_cao, p3m_max_cao>(
802 dp3m.params.cao, dp3m, dipole_prefac * wavenumber, d_rs,
803 *system.cell_structure);
804 }
805
806 /***************************
807 DIPOLAR FORCES (k-space)
808 ****************************/
809 // Compute forces after torques because the algorithm below overwrites the
810 // grids dp3m.mesh.rs_fields !
811 // Note: I'll do here 9 inverse FFTs. By symmetry, we can reduce this
812 // number to 6 !
813 /* fill in ks_scalar array for force calculation */
814 {
815 auto it_force = dp3m.g_force.begin();
816 auto it_ks_scalar = dp3m.ks_scalar.begin();
817 std::size_t index = 0u;
818 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
819 auto constexpr KX = 2, KY = 0, KZ = 1;
820 auto const shift = local_index + dp3m.mesh.start;
821 auto const &d_op = dp3m.d_op[0u];
822 auto const &mesh_dip = dp3m.mesh.rs_fields;
823 // Re(mu)*k
824 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
825 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
826 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
827 ++index;
828 // Im(mu)*k
829 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
830 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
831 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
832 ++index;
833 *it_ks_scalar = {*it_force * im, *it_force * (-re)};
834 std::advance(it_force, 1);
835 std::advance(it_ks_scalar, 1);
836 });
837 }
838
839#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
840 if (dp3m.heffte.world_size == 1) {
841 std::size_t index_row_major_r2c = 0u;
842 for_each_3d_order<FFTConfig::k_space_order>(
843 mesh_start, local_size, local_index, [&]() {
844 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
845 auto const global_index = local_origin + local_index;
846 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
847 auto const value =
848 dp3m.heffte.g_force[index_row_major_r2c] *
849 (mesh_dip[0u][index_row_major_r2c] *
850 FloatType(dp3m.d_op[0u][global_index[1u]]) +
851 mesh_dip[1u][index_row_major_r2c] *
852 FloatType(dp3m.d_op[1u][global_index[2u]]) +
853 mesh_dip[2u][index_row_major_r2c] *
854 FloatType(dp3m.d_op[2u][global_index[0u]]));
855 dp3m.heffte.ks_scalar[index_row_major_r2c] = {value.imag(),
856 -value.real()};
857#ifndef NDEBUG
858 if (not dp3m.params.tuning) {
859 auto constexpr KX = 2, KY = 0, KZ = 1;
860 auto const index_fft_legacy = local_index[KZ] +
861 line_stride * local_index[KY] +
862 plane_stride * local_index[KX];
863 assert(heffte_almost_equal(
864 dp3m.heffte.ks_scalar[index_row_major_r2c],
865 dp3m.ks_scalar[index_fft_legacy]));
866 }
867#endif // not NDEBUG
868 ++index_row_major_r2c;
869 }
870 });
871 }
872#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
873
874 /* Force component loop */
875 for (int d = 0; d < 3; d++) {
876 std::size_t index = 0u;
877 auto it_ks_scalar = dp3m.ks_scalar.begin();
878 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
879 auto constexpr KX = 2, KY = 0, KZ = 1;
880 auto const shift = local_index + dp3m.mesh.start;
881 auto const &d_op = dp3m.d_op[0u];
882 auto const &mesh_dip = dp3m.mesh.rs_fields;
883 auto const d_op_val = FloatType(d_op[shift[d]]);
884 auto const f = *it_ks_scalar * d_op_val;
885 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.real();
886 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.real();
887 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.real();
888 ++index;
889 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.imag();
890 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.imag();
891 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.imag();
892 ++index;
893 std::advance(it_ks_scalar, 1);
894 });
895
896#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
897 if (dp3m.heffte.world_size == 1) {
898 std::size_t index_row_major_r2c = 0u;
899 for_each_3d_order<FFTConfig::k_space_order>(
900 mesh_start, local_size, local_index, [&]() {
901 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
902 auto constexpr KX = 1, KY = 2, KZ = 0;
903 auto const global_index = local_origin + local_index;
904 auto const remapped_index =
905 local_index[KZ] + local_index[KY] * local_size[KZ] +
906 local_index[KX] * local_size[KZ] * local_size[KY];
907 auto const d_op_val =
908 FloatType(dp3m.d_op[d][global_index[d]]);
909 auto &mesh_dip = dp3m.heffte.ks_dipole_density;
910 mesh_dip[0u][index_row_major_r2c] =
911 FloatType(dp3m.d_op[d][global_index[2u]]) * d_op_val *
912 dp3m.heffte.ks_scalar[remapped_index];
913 mesh_dip[1u][index_row_major_r2c] =
914 FloatType(dp3m.d_op[d][global_index[0u]]) * d_op_val *
915 dp3m.heffte.ks_scalar[remapped_index];
916 mesh_dip[2u][index_row_major_r2c] =
917 FloatType(dp3m.d_op[d][global_index[1u]]) * d_op_val *
918 dp3m.heffte.ks_scalar[remapped_index];
919#ifndef NDEBUG
920 if (not FFTConfig::use_r2c and not dp3m.params.tuning) {
921 auto const index_fft_legacy = local_index[2] +
922 line_stride * local_index[1] +
923 plane_stride * local_index[0];
924 for (int j = 0; j < 3; ++j) {
925 auto const old_value = std::complex<FloatType>{
926 dp3m.mesh.rs_fields[j][2 * index_fft_legacy],
927 dp3m.mesh.rs_fields[j][2 * index_fft_legacy + 1]};
928 auto const &new_value = mesh_dip[j][index_row_major_r2c];
929 assert(heffte_almost_equal(new_value, old_value));
930 }
931 }
932#endif // not NDEBUG
933 ++index_row_major_r2c;
934 }
935 });
936 for (int dir = 0u; dir < 3u; ++dir) {
937 dp3m.heffte.fft->backward(
938 dp3m.heffte.ks_dipole_density[dir].data(),
939 dp3m.heffte.rs_B_fields_no_halo[dir].data());
940 // pad zeros around the B-field in real space for ghost layers,
941 // writing straight into the persistent halo-sized buffer
942 pad_with_zeros_discard_imag_into<FFTConfig::r_space_order,
944 dp3m.heffte.rs_B_fields[d].data(),
945 std::span(dp3m.heffte.rs_B_fields_no_halo[dir]),
946 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
947 dp3m.local_mesh.n_halo_ur);
948 }
949 // communicate ghost layers of the B-field in real space
950 auto rs_fields =
951 std::array<FloatType *, 3u>{{dp3m.heffte.rs_B_fields[0u].data(),
952 dp3m.heffte.rs_B_fields[1u].data(),
953 dp3m.heffte.rs_B_fields[2u].data()}};
954 dp3m.heffte.halo_comm.spread_grid(::comm_cart, rs_fields,
955 dp3m.local_mesh.dim);
956 }
957#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
958 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
959 dp3m.fft->backward_fft(rs_mesh);
960 }
961 // communicate ghost layers of the B-field in real space
962 dp3m.fft_buffers->perform_vector_halo_spread();
963 // assign force component from mesh to particle
964 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
965 Utils::integral_parameter<int, AssignForcesDip, p3m_min_cao,
967 dp3m.params.cao, dp3m, dipole_prefac * Utils::sqr(wavenumber), d_rs,
968 *system.cell_structure);
969 }
970 } /* if (dp3m.sum_mu2 > 0) */
971 } /* if (force_flag) */
972
973 if (dp3m.params.epsilon != P3M_EPSILON_METALLIC) {
974 auto const surface_term = calc_surface_term(force_flag, energy_flag);
975 if (this_node == 0) {
976 energy += surface_term;
977 }
978 }
979#ifdef ESPRESSO_NPT
980 if (force_flag and system.has_npt_enabled()) {
981 // reuse the validated reciprocal-space pressure tensor (same one used by
982 // the pressure observable) instead of an energy-proxy: unlike Coulomb,
983 // the dipolar structure factor is not simply homogeneous in k, so energy
984 // is not a valid substitute for the virial trace here (see
985 // long_range_pressure())
986 auto const pressure_tensor = long_range_pressure();
989 }
990#endif
991 if (not energy_flag) {
992 energy = 0.;
993 }
994
995 return energy;
996}
997
998template <typename FloatType, Arch Architecture, class FFTConfig>
1000 bool force_flag, bool energy_flag) {
1001 auto const &system = get_system();
1002 auto const &box_geo = *system.box_geo;
1003 auto const particles = system.cell_structure->local_particles();
1004 auto const pref = prefactor * 4. * std::numbers::pi / box_geo.volume() /
1005 (2. * dp3m.params.epsilon + 1.);
1006 auto const n_local_part = particles.size();
1007
1008 // We put all the dipolar momenta in a the arrays mx,my,mz according to the
1009 // id-number of the particles
1010 std::vector<double> mx(n_local_part);
1011 std::vector<double> my(n_local_part);
1012 std::vector<double> mz(n_local_part);
1013
1014 std::size_t ip = 0u;
1015 for (auto const &p : particles) {
1016 auto const dip = p.calc_dip();
1017 mx[ip] = dip[0u];
1018 my[ip] = dip[1u];
1019 mz[ip] = dip[2u];
1020 ip++;
1021 }
1022
1023 // we will need the sum of all dipolar momenta vectors
1024 auto local_dip = Utils::Vector3d{};
1025 for (std::size_t i = 0u; i < n_local_part; i++) {
1026 local_dip[0u] += mx[i];
1027 local_dip[1u] += my[i];
1028 local_dip[2u] += mz[i];
1029 }
1030 auto const box_dip =
1031 boost::mpi::all_reduce(comm_cart, local_dip, std::plus<>());
1032
1033 double energy = 0.;
1034 if (energy_flag) {
1035 double sum_e = 0.;
1036 for (std::size_t i = 0u; i < n_local_part; i++) {
1037 sum_e += mx[i] * box_dip[0] + my[i] * box_dip[1] + mz[i] * box_dip[2];
1038 }
1039 energy =
1040 0.5 * pref * boost::mpi::all_reduce(comm_cart, sum_e, std::plus<>());
1041 }
1042
1043 if (force_flag) {
1044
1045 std::vector<double> sumix(n_local_part);
1046 std::vector<double> sumiy(n_local_part);
1047 std::vector<double> sumiz(n_local_part);
1048
1049 for (std::size_t i = 0u; i < n_local_part; i++) {
1050 sumix[i] = my[i] * box_dip[2u] - mz[i] * box_dip[1u];
1051 sumiy[i] = mz[i] * box_dip[0u] - mx[i] * box_dip[2u];
1052 sumiz[i] = mx[i] * box_dip[1u] - my[i] * box_dip[0u];
1053 }
1054
1055 ip = 0u;
1056 for (auto &p : particles) {
1057 auto &torque = p.torque();
1058 torque[0u] -= pref * sumix[ip];
1059 torque[1u] -= pref * sumiy[ip];
1060 torque[2u] -= pref * sumiz[ip];
1061#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
1062 p.dip_fld() -= pref * box_dip;
1063#endif
1064 ip++;
1065 }
1066 }
1067
1068 return energy;
1069}
1070
1071template <typename FloatType, Arch Architecture, class FFTConfig>
1072void DipolarP3MHeffte<FloatType, Architecture,
1073 FFTConfig>::calc_influence_function_force() {
1074 dp3m.g_force = grid_influence_function_dipolar<FloatType, 3, P3M_BRILLOUIN,
1075 FFTConfig::k_space_order>(
1076 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
1077 get_system().box_geo->length_inv());
1078#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1079 if (dp3m.heffte.world_size == 1) {
1080 dp3m.heffte.g_force =
1082 FFTConfig::k_space_order>(
1083 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
1084 dp3m.heffte.fft->ks_local_ur_index(),
1085 get_system().box_geo->length_inv());
1086 if constexpr (FFTConfig::use_r2c) {
1087 influence_function_r2c<FFTConfig::r2c_dir>(
1088 dp3m.heffte.g_force, dp3m.params.mesh,
1089 dp3m.heffte.fft->ks_local_size(),
1090 dp3m.heffte.fft->ks_local_ld_index());
1091 }
1092 }
1093#endif
1094}
1095
1096template <typename FloatType, Arch Architecture, class FFTConfig>
1097void DipolarP3MHeffte<FloatType, Architecture,
1098 FFTConfig>::calc_influence_function_energy() {
1099 dp3m.g_energy = grid_influence_function_dipolar<FloatType, 2, P3M_BRILLOUIN,
1100 FFTConfig::k_space_order>(
1101 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
1102 get_system().box_geo->length_inv());
1103#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1104 if (dp3m.heffte.world_size == 1) {
1105 dp3m.heffte.g_energy =
1107 FFTConfig::k_space_order>(
1108 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
1109 dp3m.heffte.fft->ks_local_ur_index(),
1110 get_system().box_geo->length_inv());
1111 if constexpr (FFTConfig::use_r2c) {
1112 influence_function_r2c<FFTConfig::r2c_dir>(
1113 dp3m.heffte.g_energy, dp3m.params.mesh,
1114 dp3m.heffte.fft->ks_local_size(),
1115 dp3m.heffte.fft->ks_local_ld_index());
1116 }
1117 }
1118#endif
1119}
1120
1121template <typename FloatType, Arch Architecture, class FFTConfig>
1124 int m_mesh_max = -1, m_mesh_min = -1;
1125 std::pair<std::optional<int>, std::optional<int>> m_tune_limits;
1126
1127public:
1128 DipolarTuningAlgorithm(System::System &system, decltype(dp3m) &input_dp3m,
1129 double prefactor, int timings,
1130 decltype(m_tune_limits) tune_limits)
1131 : TuningAlgorithm(system, prefactor, timings), dp3m{input_dp3m},
1132 m_tune_limits{std::move(tune_limits)} {}
1133
1134 P3MParameters &get_params() override { return dp3m.params; }
1135
1136 void on_solver_change() const override { m_system.on_dipoles_change(); }
1137
1138 std::optional<std::string>
1139 layer_correction_veto_r_cut(double) const override {
1140 return {};
1141 }
1142
1143 void setup_logger(bool verbose) override {
1144 auto const &box_geo = *m_system.box_geo;
1145 m_logger = std::make_unique<TuningLogger>(
1146 verbose and this_node == 0, "DipolarP3M", TuningLogger::Mode::Dipolar);
1147 m_logger->tuning_goals(dp3m.params.accuracy, m_prefactor,
1148 box_geo.length()[0], dp3m.sum_dip_part,
1149 dp3m.sum_mu2);
1150 m_logger->log_tuning_start();
1151 }
1152
1153 std::tuple<double, double, double, double>
1155 double r_cut_iL) const override {
1156
1157 double alpha_L, rs_err, ks_err;
1158 auto const &box_geo = *m_system.box_geo;
1159
1160 /* calc maximal real space error for setting */
1161 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
1162 dp3m.sum_dip_part, dp3m.sum_mu2, 0.001);
1163 // alpha cannot be zero for dipoles because real-space formula breaks down
1164
1165 if (std::numbers::sqrt2 * rs_err > dp3m.params.accuracy) {
1166 /* assume rs_err = ks_err -> rs_err = accuracy/sqrt(2.0) -> alpha_L */
1167 alpha_L = dp3m_rtbisection(
1168 box_geo.length()[0], r_cut_iL, dp3m.sum_dip_part, dp3m.sum_mu2,
1169 0.0001 * box_geo.length()[0], 5. * box_geo.length()[0], 0.0001,
1170 dp3m.params.accuracy);
1171 } else {
1172 /* even alpha=0 is ok, however, we cannot choose it since it kills the
1173 k-space error formula.
1174 Anyways, this very likely NOT the optimal solution */
1175 alpha_L = 0.1;
1176 }
1177
1178 /* calculate real-space and k-space error for this alpha_L */
1179 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
1180 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1181 ks_err = dp3m_k_space_error(box_geo.length()[0], mesh[0], cao,
1182 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1183
1184 return {Utils::Vector2d{rs_err, ks_err}.norm(), rs_err, ks_err, alpha_L};
1185 }
1186
1187 void determine_mesh_limits() override {
1188 if (dp3m.params.mesh[0] == -1) {
1189 /* simple heuristic to limit the tried meshes if the accuracy cannot
1190 be obtained with smaller meshes, but normally not all these
1191 meshes have to be tested */
1192 auto const expo = std::log(std::cbrt(dp3m.sum_dip_part)) / std::log(2.);
1193 /* Medium-educated guess for the minimal mesh */
1194 m_mesh_min = static_cast<int>(std::round(std::pow(2., std::floor(expo))));
1195 /* avoid using more than 1 GB of FFT arrays */
1196 m_mesh_max = 128;
1197 if (m_tune_limits.first) {
1198 m_mesh_min = *m_tune_limits.first;
1199 }
1200 if (m_tune_limits.second) {
1201 m_mesh_max = *m_tune_limits.second;
1202 }
1203 } else {
1204 m_mesh_min = m_mesh_max = dp3m.params.mesh[0];
1205 m_logger->report_fixed_mesh(dp3m.params.mesh);
1206 }
1207 }
1208
1210 auto tuned_params = TuningAlgorithm::Parameters{};
1211 auto time_best = time_sentinel;
1212 for (auto tmp_mesh = m_mesh_min; tmp_mesh <= m_mesh_max; tmp_mesh += 2) {
1213 auto trial_params = TuningAlgorithm::Parameters{};
1214 trial_params.mesh = Utils::Vector3i::broadcast(tmp_mesh);
1215 trial_params.cao = cao_best;
1216
1217 auto const trial_time =
1218 get_m_time(trial_params.mesh, trial_params.cao, trial_params.r_cut_iL,
1219 trial_params.alpha_L, trial_params.accuracy);
1220
1221 /* this mesh does not work at all */
1222 if (trial_time < 0.)
1223 continue;
1224
1225 /* the optimum r_cut for this mesh is the upper limit for higher meshes,
1226 everything else is slower */
1227 m_r_cut_iL_max = trial_params.r_cut_iL;
1228
1229 if (trial_time < time_best) {
1230 /* new optimum */
1232 tuned_params = trial_params;
1233 time_best = tuned_params.time = trial_time;
1234 } else if (trial_time > time_best + time_granularity or
1236 /* no hope of further optimisation */
1237 break;
1238 }
1239 }
1240 return tuned_params;
1241 }
1242};
1243
1244template <typename FloatType, Arch Architecture, class FFTConfig>
1246 auto &system = get_system();
1247 auto const &box_geo = *system.box_geo;
1248 if (dp3m.params.alpha_L == 0. and dp3m.params.alpha != 0.) {
1249 dp3m.params.alpha_L = dp3m.params.alpha * box_geo.length()[0];
1250 }
1251 if (dp3m.params.r_cut_iL == 0. and dp3m.params.r_cut != 0.) {
1252 dp3m.params.r_cut_iL = dp3m.params.r_cut * box_geo.length_inv()[0];
1253 }
1254 if (not is_tuned()) {
1256 if (dp3m.sum_dip_part == 0) {
1257 throw std::runtime_error(
1258 "DipolarP3M: no dipolar particles in the system");
1259 }
1260 try {
1262 system, dp3m, prefactor, tuning.timings, tuning.limits);
1263 parameters.setup_logger(tuning.verbose);
1264 // parameter ranges
1265 parameters.determine_mesh_limits();
1266 parameters.determine_r_cut_limits();
1267 parameters.determine_cao_limits(3);
1268 // run tuning algorithm
1269 parameters.tune();
1270 m_is_tuned = true;
1271 system.on_dipoles_change();
1272 } catch (...) {
1273 dp3m.params.tuning = false;
1274 throw;
1275 }
1276 }
1277 init();
1278}
1279
1280/** Tuning dipolar-P3M */
1281inline auto dp3m_tune_aliasing_sums(Utils::Vector3i const &shift, int mesh,
1282 double mesh_i, int cao, double alpha_L_i) {
1283
1284 auto constexpr mesh_start = Utils::Vector3i::broadcast(-P3M_BRILLOUIN);
1285 auto constexpr mesh_stop = Utils::Vector3i::broadcast(P3M_BRILLOUIN + 1);
1286 auto const factor1 = Utils::sqr(std::numbers::pi * alpha_L_i);
1287 auto alias1 = 0.;
1288 auto alias2 = 0.;
1289
1290 Utils::Vector3i indices{};
1291 Utils::Vector3i nm{};
1292 Utils::Vector3d fnm{};
1294 mesh_start, mesh_stop, indices,
1295 [&]() {
1296 auto const norm_sq = nm.norm2();
1297 auto const ex = std::exp(-factor1 * norm_sq);
1298 auto const U2 = std::pow(Utils::product(fnm), 2 * cao);
1299 alias1 += Utils::sqr(ex) * norm_sq;
1300 alias2 += U2 * ex * std::pow(shift * nm, 3) / norm_sq;
1301 },
1302 [&](unsigned dim, int n) {
1303 nm[dim] = shift[dim] + n * mesh;
1304 fnm[dim] = math::sinc(nm[dim] * mesh_i);
1305 });
1306
1307 return std::make_pair(alias1, alias2);
1308}
1309
1310/** Calculate the k-space error of dipolar-P3M */
1311inline double dp3m_k_space_error(double box_size, int mesh, int cao,
1312 std::size_t n_c_part, double sum_q2,
1313 double alpha_L) {
1314
1315 auto const cotangent_sum = math::get_analytic_cotangent_sum_kernel(cao);
1316 auto const mesh_i = 1. / static_cast<double>(mesh);
1317 auto const alpha_L_i = 1. / alpha_L;
1318 auto const mesh_stop = Utils::Vector3i::broadcast(mesh / 2);
1319 auto const mesh_start = -mesh_stop;
1320 auto indices = Utils::Vector3i{};
1321 auto values = Utils::Vector3d{};
1322 auto he_q = 0.;
1323
1325 mesh_start, mesh_stop, indices,
1326 [&]() {
1327 if ((indices[0] != 0) or (indices[1] != 0) or (indices[2] != 0)) {
1328 auto const n2 = indices.norm2();
1329 auto const cs = Utils::product(values);
1330 auto const [alias1, alias2] =
1331 dp3m_tune_aliasing_sums(indices, mesh, mesh_i, cao, alpha_L_i);
1332 auto const d =
1333 alias1 - Utils::sqr(alias2 / cs) /
1334 Utils::int_pow<3>(static_cast<double>(n2));
1335 /* at high precision, d can become negative due to extinction;
1336 also, don't take values that have no significant digits left*/
1337 if (d > 0. and std::fabs(d / alias1) > round_error_prec)
1338 he_q += d;
1339 }
1340 },
1341 [&values, &mesh_i, cotangent_sum](unsigned dim, int n) {
1342 values[dim] = cotangent_sum(n, mesh_i);
1343 });
1344
1345 return 8. * Utils::sqr(std::numbers::pi) / 3. * sum_q2 *
1346 sqrt(he_q / static_cast<double>(n_c_part)) /
1347 Utils::int_pow<4>(box_size);
1348}
1349
1350/** Calculate the value of the errors for the REAL part of the force in terms
1351 * of the splitting parameter alpha of Ewald. Based on eq. (33) @cite wang01a.
1352 *
1353 * Please note that in this more refined approach we don't use
1354 * eq. (37), but eq. (33) which maintains all the powers in alpha.
1355 */
1356inline double dp3m_real_space_error(double box_size, double r_cut_iL,
1357 std::size_t n_c_part, double sum_q2,
1358 double alpha_L) {
1359 auto constexpr exp_min = -708.4; // for IEEE-compatible double
1360 double d_error_f, d_cc, d_dc, d_con;
1361
1362 auto const d_rcut = r_cut_iL * box_size;
1363 auto const d_rcut2 = Utils::sqr(d_rcut);
1364 auto const d_rcut4 = Utils::sqr(d_rcut2);
1365
1366 auto const d_a2 = Utils::sqr(alpha_L) / Utils::sqr(box_size);
1367 auto const exponent = -d_a2 * d_rcut2;
1368 auto const exp_term = (exponent < exp_min) ? 0. : std::exp(exponent);
1369 auto const d_c = sum_q2 * exp_term;
1370
1371 d_cc = 4. * Utils::sqr(d_a2) * Utils::sqr(d_rcut2) + 6. * d_a2 * d_rcut2 + 3.;
1372
1373 d_dc = 8. * Utils::int_pow<3>(d_a2) * Utils::int_pow<3>(d_rcut2) +
1374 20. * Utils::sqr(d_a2) * d_rcut4 + 30. * d_a2 * d_rcut2 + 15.;
1375
1376 d_con = 1. / sqrt(Utils::int_pow<3>(box_size) * Utils::sqr(d_a2) * d_rcut *
1377 Utils::sqr(d_rcut4) * static_cast<double>(n_c_part));
1378
1379 d_error_f = d_c * d_con *
1380 sqrt((13. / 6.) * Utils::sqr(d_cc) +
1381 (2. / 15.) * Utils::sqr(d_dc) - (13. / 15.) * d_cc * d_dc);
1382
1383 return d_error_f;
1384}
1385
1386/** Using bisection, find the root of a function "func-tuned_accuracy/sqrt(2.)"
1387 * known to lie between x1 and x2. The root, returned as rtbis, will be
1388 * refined until its accuracy is \f$\pm\f$ @p xacc.
1389 */
1390double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part,
1391 double sum_q2, double x1, double x2, double xacc,
1392 double tuned_accuracy) {
1393 constexpr int JJ_RTBIS_MAX = 40;
1394
1395 auto const constant = tuned_accuracy / std::numbers::sqrt2;
1396
1397 auto const f1 =
1398 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x1) -
1399 constant;
1400 auto const f2 =
1401 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x2) -
1402 constant;
1403 if (f1 * f2 >= 0.0) {
1404 throw std::runtime_error(
1405 "Root must be bracketed for bisection in dp3m_rtbisection");
1406 }
1407 // Orient the search dx, and set rtb to x1 or x2 ...
1408 double dx;
1409 double rtb = f1 < 0.0 ? (dx = x2 - x1, x1) : (dx = x1 - x2, x2);
1410 for (int j = 1; j <= JJ_RTBIS_MAX; j++) {
1411 auto const xmid = rtb + (dx *= 0.5);
1412 auto const fmid =
1413 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, xmid) -
1414 constant;
1415 if (fmid <= 0.0)
1416 rtb = xmid;
1417 if (fabs(dx) < xacc || fmid == 0.0)
1418 return rtb;
1419 }
1420 throw std::runtime_error("Too many bisections in dp3m_rtbisection");
1421}
1422
1424 auto const &system = get_system();
1425 auto const &box_geo = *system.box_geo;
1426 auto const &local_geo = *system.local_geo;
1427 for (auto i = 0u; i < 3u; i++) {
1428 /* check k-space cutoff */
1429 if (dp3m_params.cao_cut[i] >= box_geo.length_half()[i]) {
1430 std::stringstream msg;
1431 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1432 << " is larger than half of box dimension " << box_geo.length()[i];
1433 throw std::runtime_error(msg.str());
1434 }
1435 if (dp3m_params.cao_cut[i] >= local_geo.length()[i]) {
1436 std::stringstream msg;
1437 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1438 << " is larger than local box dimension " << local_geo.length()[i];
1439 throw std::runtime_error(msg.str());
1440 }
1441 }
1442
1443 if ((box_geo.length()[0] != box_geo.length()[1]) or
1444 (box_geo.length()[1] != box_geo.length()[2])) {
1445 throw std::runtime_error("DipolarP3M: requires a cubic box");
1446 }
1447}
1448
1450 auto const &box_geo = *get_system().box_geo;
1451 if (!box_geo.periodic(0) or !box_geo.periodic(1) or !box_geo.periodic(2)) {
1452 throw std::runtime_error(
1453 "DipolarP3M: requires periodicity (True, True, True)");
1454 }
1455}
1456
1458 auto const &local_geo = *get_system().local_geo;
1459 if (local_geo.cell_structure_type() != CellStructureType::REGULAR and
1460 local_geo.cell_structure_type() != CellStructureType::HYBRID) {
1461 throw std::runtime_error(
1462 "DipolarP3M: requires the regular or hybrid decomposition cell system");
1463 }
1464 if (::communicator.size > 1 and
1465 local_geo.cell_structure_type() == CellStructureType::HYBRID) {
1466 throw std::runtime_error(
1467 "DipolarP3M: does not work with the hybrid decomposition cell system, "
1468 "if using more than one MPI node");
1469 }
1470}
1471
1473 auto const &node_grid = ::communicator.node_grid;
1474 if (node_grid[0] < node_grid[1] or node_grid[1] < node_grid[2]) {
1475 throw std::runtime_error(
1476 "DipolarP3M: node grid must be sorted, largest first");
1477 }
1478}
1479
1480template <typename FloatType, Arch Architecture, class FFTConfig>
1482 auto const &box_geo = *get_system().box_geo;
1483 dp3m.params.r_cut = dp3m.params.r_cut_iL * box_geo.length()[0];
1484 dp3m.params.alpha = dp3m.params.alpha_L * box_geo.length_inv()[0];
1485 dp3m.params.recalc_a_ai_cao_cut(box_geo.length());
1486 dp3m.local_mesh.recalc_ld_pos(dp3m.params);
1487 sanity_checks_boxl();
1488 calc_influence_function_force();
1489 calc_influence_function_energy();
1490 dp3m.energy_correction = 0.;
1491#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1492 if (dp3m.heffte.world_size == 1) {
1493 dp3m.heffte.halo_comm.resize(::comm_cart, dp3m.local_mesh);
1494 }
1495#endif
1496}
1497
1498template <typename FloatType, Arch Architecture, class FFTConfig>
1499void DipolarP3MHeffte<FloatType, Architecture,
1500 FFTConfig>::calc_energy_correction() {
1501 auto const &box_geo = *get_system().box_geo;
1502 auto const Ukp3m = calc_average_self_energy_k_space() * box_geo.volume();
1503 auto const Ewald_volume = Utils::int_pow<3>(dp3m.params.alpha_L);
1504 auto const Eself = -2. * Ewald_volume * std::numbers::inv_sqrtpi / 3.;
1505 dp3m.energy_correction =
1506 -dp3m.sum_mu2 * (Ukp3m + Eself + 2. * std::numbers::pi / 3.);
1507}
1508
1509#ifdef ESPRESSO_NPT
1510template <typename FloatType, Arch Architecture, class FFTConfig>
1511void DipolarP3MHeffte<FloatType, Architecture,
1512 FFTConfig>::npt_add_virial_contribution(double virial)
1513 const {
1514 get_system().npt_add_virial_contribution(virial);
1515}
1516#endif // ESPRESSO_NPT
1517
1518#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()
void mark_torque_replicas_dirty()
Declare that a kernel scattering into the torque view is about to run.
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.
std::shared_ptr< LocalBox > local_geo
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
void determine_cao_limits(int initial_cao)
Determine a sensible range for the charge assignment order.
void determine_r_cut_limits()
Determine a sensible range for the real-space cutoff.
std::unique_ptr< TuningLogger > m_logger
static auto constexpr time_granularity
Granularity of the time measurement (milliseconds).
T norm() const
Definition Vector.hpp:164
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:134
void zfill(std::size_t size)
Fill cache with zero-initialized data.
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:369
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