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 "npt.hpp"
53#include "system/System.hpp"
54#include "tuning.hpp"
55
56#include <utils/Vector.hpp>
59#include <utils/math/sqr.hpp>
60
61#include <boost/mpi/collectives/all_reduce.hpp>
62#include <boost/mpi/collectives/reduce.hpp>
63
64#include <Kokkos_Core.hpp>
65#include <Kokkos_ScatterView.hpp>
66
67#include <algorithm>
68#include <array>
69#include <cmath>
70#include <cstddef>
71#include <cstdio>
72#include <functional>
73#include <iterator>
74#include <memory>
75#include <numbers>
76#include <optional>
77#include <span>
78#include <sstream>
79#include <stdexcept>
80#include <tuple>
81#include <utility>
82#include <vector>
83
84#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
85#ifndef NDEBUG
86template <typename T>
87bool heffte_almost_equal(T const &value, T const &reference) {
88 auto const diff = std::abs(value - reference);
89 using FT = std::remove_cvref_t<decltype(diff)>;
90 auto constexpr atol = std::is_same_v<FT, float> ? FT{2e-4} : FT{1e-6};
91 auto constexpr rtol = std::is_same_v<FT, float> ? FT{5e-5} : FT{1e-5};
92 auto const non_zero = std::abs(reference) != FT{0};
93 return (diff < atol) or (non_zero and (diff / std::abs(reference) < rtol));
94}
95#endif // not NDEBUG
96#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
97
98template <typename FloatType, Arch Architecture, class FFTConfig>
99void DipolarP3MHeffte<FloatType, Architecture,
101 auto local_n = std::size_t{0u};
102 double local_mu2 = 0.;
103
104 for (auto const &p : get_system().cell_structure->local_particles()) {
105 if (p.dipm() != 0.) {
106 local_mu2 += p.calc_dip().norm2();
107 local_n++;
108 }
109 }
110
111 boost::mpi::all_reduce(comm_cart, local_mu2, dp3m.sum_mu2, std::plus<>());
112 boost::mpi::all_reduce(comm_cart, local_n, dp3m.sum_dip_part, std::plus<>());
113}
114
115inline double dp3m_k_space_error(double box_size, int mesh, int cao,
116 std::size_t n_c_part, double sum_q2,
117 double alpha_L);
118
119inline double dp3m_real_space_error(double box_size, double r_cut_iL,
120 std::size_t n_c_part, double sum_q2,
121 double alpha_L);
122
123/** Compute the value of alpha through a bisection method.
124 * Based on eq. (33) @cite wang01a.
125 */
126double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part,
127 double sum_q2, double x1, double x2, double xacc,
128 double tuned_accuracy);
129
130template <typename FloatType, Arch Architecture, class FFTConfig>
131double DipolarP3MHeffte<FloatType, Architecture,
132 FFTConfig>::calc_average_self_energy_k_space() const {
133 auto const &box_geo = *get_system().box_geo;
134 auto const node_phi =
136 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop, dp3m.g_energy);
137
138 double phi = 0.;
139 boost::mpi::reduce(comm_cart, node_phi, phi, std::plus<>(), 0);
140 phi /= 3. * box_geo.length()[0] * Utils::int_pow<3>(dp3m.params.mesh[0]);
141 return phi * std::numbers::pi;
142}
143
144template <typename FloatType, Arch Architecture, class FFTConfig>
146 assert(dp3m.params.mesh >= Utils::Vector3i::broadcast(1));
147 assert(dp3m.params.cao >= p3m_min_cao and dp3m.params.cao <= p3m_max_cao);
148 assert(dp3m.params.alpha > 0.);
149
150 auto const &system = get_system();
151 auto const &box_geo = *system.box_geo;
152 auto const &local_geo = *system.local_geo;
153 auto const verlet_skin = system.cell_structure->get_verlet_skin();
154
155 dp3m.params.cao3 = Utils::int_pow<3>(dp3m.params.cao);
156 dp3m.params.recalc_a_ai_cao_cut(box_geo.length());
157
158 assert(dp3m.fft);
159 dp3m.local_mesh.calc_local_ca_mesh(dp3m.params, local_geo, verlet_skin, 0.);
160 dp3m.fft_buffers->init_halo();
161 dp3m.fft->init(dp3m.params);
162 dp3m.mesh.ks_pnum = dp3m.fft->get_ks_pnum();
163 dp3m.fft_buffers->init_meshes(dp3m.fft->get_ca_mesh_size());
164 dp3m.update_mesh_views();
165#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
166 dp3m.heffte.world_size = comm_cart.size();
167 dp3m.heffte.fft =
168 std::make_shared<P3MFFT<FloatType, Architecture, FFTConfig>>(
169 nullptr, ::comm_cart, dp3m.params.mesh, dp3m.local_mesh.ld_no_halo,
170 dp3m.local_mesh.ur_no_halo, ::communicator.node_grid);
171 dp3m.resize_heffte_buffers();
172#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
173 dp3m.calc_differential_operator();
174
175 /* fix box length dependent constants */
176 scaleby_box_l();
177
179}
180
181namespace {
182template <int cao> struct AssignDipole {
183 void operator()(auto &dp3m, auto &cell_structure) {
184 using DipolarP3MState = std::remove_reference_t<decltype(dp3m)>;
185 using value_type = DipolarP3MState::value_type;
186 auto const &aosoa = cell_structure.get_aosoa();
187 auto const &unique_particles = cell_structure.get_unique_particles();
188 auto const n_part = cell_structure.count_local_particles();
189 dp3m.inter_weights.zfill(n_part); // allocate buffer for parallel write
191 "InterpolateDipoles", std::size_t{0u}, n_part, [&](auto p_index) {
193 auto const tid = omp_get_thread_num();
194 auto const p_pos = aosoa.get_span_at(aosoa.position, p_index);
195 auto const dip = unique_particles.at(p_index)->calc_dip();
196 auto const weights =
198 p_pos, dp3m.params.ai, dp3m.local_mesh);
199 dp3m.inter_weights.store_at(p_index, weights);
201 dp3m.local_mesh, weights, [&dip, tid, &dp3m](int ind, double w) {
202 dp3m.rs_fields_kokkos(tid, 0u, ind) += value_type(w * dip[0u]);
203 dp3m.rs_fields_kokkos(tid, 1u, ind) += value_type(w * dip[1u]);
204 dp3m.rs_fields_kokkos(tid, 2u, ind) += value_type(w * dip[2u]);
205 });
206 });
207 Kokkos::fence();
208 using execution_space = Kokkos::DefaultHostExecutionSpace;
209 int num_threads = execution_space().concurrency();
210 Kokkos::RangePolicy<execution_space> policy(std::size_t{0},
211 dp3m.local_mesh.size);
212 Kokkos::parallel_for("ReduceInterpolatedDipoles", policy,
213 [&dp3m, num_threads](std::size_t const i) {
214 for (int dir = 0; dir < 3; ++dir) {
215 value_type acc{};
216 for (int tid = 0; tid < num_threads; ++tid) {
217 acc += dp3m.rs_fields_kokkos(tid, dir, i);
218 }
219 dp3m.mesh.rs_fields[dir][i] += acc;
220#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
221 dp3m.heffte.rs_dipole_density[dir][i] += acc;
222#endif
223 }
224 });
225 Kokkos::fence();
226 }
227};
228} // namespace
229
230template <typename FloatType, Arch Architecture, class FFTConfig>
232 prepare_fft_mesh();
233
234 Utils::integral_parameter<int, AssignDipole, p3m_min_cao, p3m_max_cao>(
235 dp3m.params.cao, dp3m, *get_system().cell_structure);
236}
237
238namespace {
239template <int cao> struct AssignTorques {
240 void operator()(auto &dp3m, double prefac, int d_rs,
241 CellStructure &cell_structure) const {
242
243 assert(cao == dp3m.inter_weights.cao());
244
245 auto const kernel = [d_rs, &dp3m](auto const &pref, auto &p_torque,
246 std::size_t p_index) {
247 auto const weights = dp3m.inter_weights.template load<cao>(p_index);
249 p3m_interpolate(dp3m.local_mesh, weights,
250 [&E, &dp3m, d_rs](int ind, double w) {
251 // heFFTe data: dp3m.heffte.ks_scalar.real()
252 E[d_rs] += w * double(dp3m.mesh.rs_scalar[ind]);
253 });
254
255 auto const torque = vector_product(pref, E);
256 auto access = p_torque.access();
257 access(p_index, 0) -= torque[0];
258 access(p_index, 1) -= torque[1];
259 access(p_index, 2) -= torque[2];
260 };
261
262 auto const n_part = dp3m.inter_weights.size();
263 auto const &unique_particles = cell_structure.get_unique_particles();
264 auto scatter_torque = cell_structure.get_scatter_torque();
266 "AssignTorques", std::size_t{0u}, n_part, [&](std::size_t p_index) {
267 auto const &p = *unique_particles.at(p_index);
268 if (p.dipm() != 0.) {
269 kernel(p.calc_dip() * prefac, scatter_torque, p_index);
270 }
271 });
272 }
273};
274
275template <int cao> struct AssignForcesDip {
276 void operator()(auto &dp3m, double prefac, int d_rs,
277 CellStructure &cell_structure) const {
278
279 assert(cao == dp3m.inter_weights.cao());
280
281 auto const kernel = [d_rs, &dp3m](auto const &pref, auto &p_force,
282 std::size_t p_index) {
283 auto const weights = dp3m.inter_weights.template load<cao>(p_index);
284
286 p3m_interpolate(dp3m.local_mesh, weights, [&E, &dp3m](int ind, double w) {
287 // heFFTe data: dp3m.heffte.rs_B_fields
288 E[0u] += w * double(dp3m.mesh.rs_fields[0u][ind]);
289 E[1u] += w * double(dp3m.mesh.rs_fields[1u][ind]);
290 E[2u] += w * double(dp3m.mesh.rs_fields[2u][ind]);
291 });
292
293 auto access = p_force.access();
294 access(p_index, d_rs) += pref * E;
295 };
296
297 auto const n_part = dp3m.inter_weights.size();
298 auto const &unique_particles = cell_structure.get_unique_particles();
299 auto scatter_force = cell_structure.get_scatter_force();
301 "AssignForcesDip", std::size_t{0u}, n_part, [&](std::size_t p_index) {
302 auto const &p = *unique_particles.at(p_index);
303 if (p.dipm() != 0.) {
304 kernel(p.calc_dip() * prefac, scatter_force, p_index);
305 }
306 });
307 }
308};
309} // namespace
310
311#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
312template <typename FloatType, class FFTConfig>
314 auto const rs_array_size =
315 static_cast<std::size_t>(Utils::product(this->local_mesh.dim));
316 auto const rs_array_size_no_halo =
317 static_cast<std::size_t>(Utils::product(this->local_mesh.dim_no_halo));
318 auto const fft_mesh_size =
319 static_cast<std::size_t>(Utils::product(heffte.fft->ks_local_size()));
320 for (auto d : {0u, 1u, 2u}) {
321 heffte.rs_dipole_density[d].resize(rs_array_size);
322 heffte.ks_dipole_density[d].resize(fft_mesh_size);
323 heffte.rs_B_fields[d].resize(rs_array_size);
324 heffte.rs_B_fields_no_halo[d].resize(rs_array_size_no_halo);
325 }
326 heffte.ks_B_field_storage.resize(fft_mesh_size);
327 heffte.ks_scalar.resize(fft_mesh_size);
328}
329#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
330
331template <typename FloatType, Arch Architecture, class FFTConfig>
333 bool force_flag, bool energy_flag) {
334 /* k-space energy */
335 double energy = 0.;
336 auto const &system = get_system();
337 auto const &box_geo = *system.box_geo;
338 auto const dipole_prefac = prefactor / Utils::product(dp3m.params.mesh);
339#ifdef ESPRESSO_NPT
340 auto const npt_flag = force_flag and system.has_npt_enabled();
341#else
342 auto constexpr npt_flag = false;
343#endif
344
345 auto constexpr mesh_start = Utils::Vector3i::broadcast(0);
347#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
348 auto constexpr r2c_dir = FFTConfig::r2c_dir;
349 auto const rs_local_size = dp3m.heffte.fft->rs_local_size();
350 auto const local_size = dp3m.heffte.fft->ks_local_size();
352 if constexpr (FFTConfig::use_r2c) {
353 local_size_full[r2c_dir] -= 1;
354 local_size_full[r2c_dir] *= 2;
355 }
356 auto const local_origin = dp3m.heffte.fft->ks_local_ld_index();
357#ifndef NDEBUG
358 auto const line_stride = local_size_full[0];
359 auto const plane_stride = local_size_full[0] * local_size_full[0];
360#endif
361 auto const &global_size = dp3m.params.mesh;
362 auto const cutoff_left = 1 - local_origin[r2c_dir];
363 auto const cutoff_right = global_size[r2c_dir] / 2 - local_origin[r2c_dir];
364 auto &short_dim = local_index[r2c_dir];
365 dp3m.resize_heffte_buffers();
366#endif
367
368 if (dp3m.sum_mu2 > 0.) {
369 dipole_assign();
370 dp3m.fft_buffers->perform_vector_halo_gather();
371 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
372 dp3m.fft->forward_fft(rs_mesh);
373 }
374 dp3m.update_mesh_views();
375
376#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
377 if (dp3m.heffte.world_size == 1) {
378 // halo communication of real space dipoles density
379 std::array<FloatType *, 3u> rs_fields = {
380 {dp3m.heffte.rs_dipole_density[0u].data(),
381 dp3m.heffte.rs_dipole_density[1u].data(),
382 dp3m.heffte.rs_dipole_density[2u].data()}};
383 dp3m.heffte.halo_comm.gather_grid(::comm_cart, rs_fields,
384 dp3m.local_mesh.dim);
385
386 for (auto dir : {0u, 1u, 2u}) {
387 // get real-space dipoles density without ghost layers
389 FFTConfig::r_space_order>(
390 dp3m.heffte.rs_dipole_density[dir], dp3m.local_mesh.dim,
391 dp3m.local_mesh.n_halo_ld,
392 dp3m.local_mesh.dim - dp3m.local_mesh.n_halo_ur);
393 // re-order data in row-major
394 std::vector<FloatType> rs_field_no_halo_reorder;
396 std::size_t index_row_major = 0u;
398 mesh_start, rs_local_size, local_index, [&]() {
399 auto constexpr KX = 1, KY = 2, KZ = 0;
400 auto const index = local_index[KZ] +
401 rs_local_size[0] * local_index[KY] +
402 Utils::sqr(rs_local_size[0]) * local_index[KX];
404 rs_field_no_halo[index];
406 });
407 dp3m.heffte.fft->forward(rs_field_no_halo_reorder.data(),
408 dp3m.heffte.ks_dipole_density[dir].data());
409#ifndef NDEBUG
410 if (not dp3m.params.tuning) {
411 std::size_t index_row_major_r2c = 0u;
414 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
415 auto constexpr KX = 2, KY = 0, KZ = 1;
416 auto const index_fft_legacy = local_index[KZ] +
419 auto const old_value = std::complex<FloatType>{
420 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy],
421 dp3m.mesh.rs_fields[dir][2 * index_fft_legacy + 1]};
422 auto const &new_value =
423 dp3m.heffte.ks_dipole_density[dir][index_row_major_r2c];
426 }
427 });
428 }
429#endif // not NDEBUG
430 }
431 }
432#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
433 }
434
435 /* === k-space energy calculation === */
436 if (energy_flag or npt_flag) {
437 /*********************
438 Dipolar energy
439 **********************/
440 if (dp3m.sum_mu2 > 0.) {
441 /* i*k differentiation for dipolar gradients:
442 * |(\Fourier{\vect{mu}}(k)\cdot \vect{k})|^2 */
443
444 auto index = std::size_t(0u);
445 auto it_energy = dp3m.g_energy.begin();
446 auto node_energy = 0.;
447 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
448 auto constexpr KX = 2, KY = 0, KZ = 1;
449 auto const shift = local_index + dp3m.mesh.start;
450 auto const &d_op = dp3m.d_op[0u];
451 auto const &mesh_dip = dp3m.mesh.rs_fields;
452 // Re(mu)*k
453 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
454 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
455 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
456 ++index;
457 // Im(mu)*k
458 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
459 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
460 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
461 ++index;
462 node_energy += *it_energy * (Utils::sqr(re) + Utils::sqr(im));
463 std::advance(it_energy, 1);
464 });
465#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
466 if (dp3m.heffte.world_size == 1) {
467 [[maybe_unused]] auto node_energy_heffte = 0.;
468 std::size_t index_row_major_r2c = 0u;
471 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
473 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
474 auto const cell_field =
476 FloatType(dp3m.d_op[0u][global_index[1u]]) +
478 FloatType(dp3m.d_op[1u][global_index[2u]]) +
480 FloatType(dp3m.d_op[2u][global_index[0u]]);
481 auto cell_energy = static_cast<double>(
482 dp3m.heffte.g_energy[index_row_major_r2c] *
483 std::norm(cell_field));
484 if (FFTConfig::use_r2c and (short_dim >= cutoff_left and
485 short_dim <= cutoff_right - 1)) {
486 // k-space symmetry: double counting except in the first and
487 // last planes of the short dimension; although the wavevector
488 // points in the opposite direction in the redundant region of
489 // k-space, the product of two components of the wavevector
490 // cancels out the negative sign
491 cell_energy *= 2.;
492 }
494 }
496 });
497 assert(heffte_almost_equal(static_cast<FloatType>(node_energy_heffte),
498 static_cast<FloatType>(node_energy)));
499 }
500#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
501 node_energy *= dipole_prefac * std::numbers::pi * box_geo.length_inv()[0];
502 boost::mpi::reduce(comm_cart, node_energy, energy, std::plus<>(), 0);
503
504 if (dp3m.energy_correction == 0.)
505 calc_energy_correction();
506
507 if (this_node == 0) {
508 /* self energy correction */
509 energy -= prefactor * dp3m.sum_mu2 * std::numbers::inv_sqrtpi *
510 (2. / 3.) * Utils::int_pow<3>(dp3m.params.alpha);
511
512 /* dipolar energy correction due to systematic Madelung-self effects */
513 energy += prefactor * dp3m.energy_correction / box_geo.volume();
514 }
515 }
516 } // if (energy_flag)
517
518 /* === k-space force calculation === */
519 if (force_flag) {
520 /****************************
521 * DIPOLAR TORQUES (k-space)
522 ****************************/
523 if (dp3m.sum_mu2 > 0.) {
524 auto const wavenumber = 2. * std::numbers::pi * box_geo.length_inv()[0u];
525 dp3m.ks_scalar.resize(dp3m.local_mesh.size);
526 /* fill in ks_scalar array for torque calculation */
527 {
528 auto index{std::size_t(0u)};
529 auto it_energy = dp3m.g_energy.begin();
530 auto it_ks_scalar = dp3m.ks_scalar.begin();
531 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() mutable {
532 auto constexpr KX = 2, KY = 0, KZ = 1;
533 auto const shift = local_index + dp3m.mesh.start;
534 auto const &d_op = dp3m.d_op[0u];
535 auto const &mesh_dip = dp3m.mesh.rs_fields;
536 // Re(mu)*k
537 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
538 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
539 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
540 ++index;
541 // Im(mu)*k
542 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
543 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
544 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
545 ++index;
546 *it_ks_scalar = *it_energy * std::complex<FloatType>{re, im};
547 std::advance(it_energy, 1);
548 std::advance(it_ks_scalar, 1);
549 });
550 }
551#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
552 if (dp3m.heffte.world_size == 1) {
553 std::size_t index_row_major_r2c = 0u;
556 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
558 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
559 dp3m.heffte.ks_scalar[index_row_major_r2c] =
560 dp3m.heffte.g_energy[index_row_major_r2c] *
562 FloatType(dp3m.d_op[0u][global_index[1u]]) +
564 FloatType(dp3m.d_op[1u][global_index[2u]]) +
566 FloatType(dp3m.d_op[2u][global_index[0u]]));
567#ifndef NDEBUG
568 if (not dp3m.params.tuning) {
569 auto constexpr KX = 2, KY = 0, KZ = 1;
570 auto const index_fft_legacy = local_index[KZ] +
574 dp3m.heffte.ks_scalar[index_row_major_r2c],
575 dp3m.ks_scalar[index_fft_legacy]));
576 }
577#endif // not NDEBUG
579 }
580 });
581 }
582#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
583
584 /* Torque component loop */
585 for (int d = 0; d < 3; d++) {
586 auto it_ks_scalar = dp3m.ks_scalar.begin();
587 auto index = 0u;
588 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
589 auto const &offset = dp3m.mesh.start;
590 auto const &d_op = dp3m.d_op[0u];
591 auto const d_op_val = FloatType(d_op[local_index[d] + offset[d]]);
592 auto const &value = *it_ks_scalar;
593 dp3m.mesh.rs_scalar[index] = d_op_val * value.real();
594 ++index;
595 dp3m.mesh.rs_scalar[index] = d_op_val * value.imag();
596 ++index;
597 std::advance(it_ks_scalar, 1);
598 });
599#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
600 if (dp3m.heffte.world_size == 1) {
601 unsigned int constexpr d_ks[3] = {2u, 0u, 1u};
602 std::size_t index_row_major_r2c = 0u;
605 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
607 auto const d_op_val =
608 FloatType(dp3m.d_op[d][global_index[d_ks[d]]]);
609 dp3m.heffte.ks_B_field_storage[index_row_major_r2c] =
610 d_op_val * dp3m.heffte.ks_scalar[index_row_major_r2c];
611#ifndef NDEBUG
612 if (not dp3m.params.tuning) {
613 auto constexpr KX = 2, KY = 0, KZ = 1;
614 auto const index_fft_legacy =
617 auto const old_value = std::complex<FloatType>{
618 dp3m.mesh.rs_scalar[2 * index_fft_legacy],
619 dp3m.mesh.rs_scalar[2 * index_fft_legacy + 1]};
620 auto const &new_value =
621 dp3m.heffte.ks_B_field_storage[index_row_major_r2c];
623 }
624#endif // not NDEBUG
626 }
627 });
628 dp3m.heffte.fft->backward(dp3m.heffte.ks_B_field_storage.data(),
629 dp3m.heffte.rs_B_fields_no_halo[d].data());
630 // pad zeros around the B-field in real space for ghost layers
631 dp3m.heffte.rs_B_fields[d] =
632 pad_with_zeros_discard_imag<FFTConfig::r_space_order,
634 std::span(dp3m.heffte.rs_B_fields_no_halo[d]),
635 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
636 dp3m.local_mesh.n_halo_ur);
637 // communicate ghost layers of the B-field in real space
638 dp3m.heffte.halo_comm.spread_grid(::comm_cart,
639 dp3m.heffte.rs_B_fields[d].data(),
640 dp3m.local_mesh.dim);
641 }
642#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
643 dp3m.fft->backward_fft(dp3m.fft_buffers->get_scalar_mesh());
644 // communicate ghost layers of the B-field in real space
645 dp3m.fft_buffers->perform_scalar_halo_spread();
646 // assign torque component from mesh to particle
647 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
648 Utils::integral_parameter<int, AssignTorques, p3m_min_cao, p3m_max_cao>(
649 dp3m.params.cao, dp3m, dipole_prefac * wavenumber, d_rs,
650 *system.cell_structure);
651 }
652
653 /***************************
654 DIPOLAR FORCES (k-space)
655 ****************************/
656 // Compute forces after torques because the algorithm below overwrites the
657 // grids dp3m.mesh.rs_fields !
658 // Note: I'll do here 9 inverse FFTs. By symmetry, we can reduce this
659 // number to 6 !
660 /* fill in ks_scalar array for force calculation */
661 {
662 auto it_force = dp3m.g_force.begin();
663 auto it_ks_scalar = dp3m.ks_scalar.begin();
664 std::size_t index = 0u;
665 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
666 auto constexpr KX = 2, KY = 0, KZ = 1;
667 auto const shift = local_index + dp3m.mesh.start;
668 auto const &d_op = dp3m.d_op[0u];
669 auto const &mesh_dip = dp3m.mesh.rs_fields;
670 // Re(mu)*k
671 auto const re = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
672 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
673 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
674 ++index;
675 // Im(mu)*k
676 auto const im = mesh_dip[0u][index] * FloatType(d_op[shift[KX]]) +
677 mesh_dip[1u][index] * FloatType(d_op[shift[KY]]) +
678 mesh_dip[2u][index] * FloatType(d_op[shift[KZ]]);
679 ++index;
680 *it_ks_scalar = {*it_force * im, *it_force * (-re)};
681 std::advance(it_force, 1);
682 std::advance(it_ks_scalar, 1);
683 });
684 }
685
686#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
687 if (dp3m.heffte.world_size == 1) {
688 std::size_t index_row_major_r2c = 0u;
691 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
693 auto const &mesh_dip = dp3m.heffte.ks_dipole_density;
694 auto const value =
695 dp3m.heffte.g_force[index_row_major_r2c] *
697 FloatType(dp3m.d_op[0u][global_index[1u]]) +
699 FloatType(dp3m.d_op[1u][global_index[2u]]) +
701 FloatType(dp3m.d_op[2u][global_index[0u]]));
702 dp3m.heffte.ks_scalar[index_row_major_r2c] = {value.imag(),
703 -value.real()};
704#ifndef NDEBUG
705 if (not dp3m.params.tuning) {
706 auto constexpr KX = 2, KY = 0, KZ = 1;
707 auto const index_fft_legacy = local_index[KZ] +
711 dp3m.heffte.ks_scalar[index_row_major_r2c],
712 dp3m.ks_scalar[index_fft_legacy]));
713 }
714#endif // not NDEBUG
716 }
717 });
718 }
719#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
720
721 /* Force component loop */
722 for (int d = 0; d < 3; d++) {
723 std::size_t index = 0u;
724 auto it_ks_scalar = dp3m.ks_scalar.begin();
725 for_each_3d(mesh_start, dp3m.mesh.size, local_index, [&]() {
726 auto constexpr KX = 2, KY = 0, KZ = 1;
727 auto const shift = local_index + dp3m.mesh.start;
728 auto const &d_op = dp3m.d_op[0u];
729 auto const &mesh_dip = dp3m.mesh.rs_fields;
730 auto const d_op_val = FloatType(d_op[shift[d]]);
731 auto const f = *it_ks_scalar * d_op_val;
732 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.real();
733 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.real();
734 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.real();
735 ++index;
736 mesh_dip[0u][index] = FloatType(d_op[shift[KX]]) * f.imag();
737 mesh_dip[1u][index] = FloatType(d_op[shift[KY]]) * f.imag();
738 mesh_dip[2u][index] = FloatType(d_op[shift[KZ]]) * f.imag();
739 ++index;
740 std::advance(it_ks_scalar, 1);
741 });
742
743#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
744 if (dp3m.heffte.world_size == 1) {
745 std::size_t index_row_major_r2c = 0u;
748 if (not FFTConfig::use_r2c or (short_dim <= cutoff_right)) {
749 auto constexpr KX = 1, KY = 2, KZ = 0;
751 auto const remapped_index =
754 auto const d_op_val =
755 FloatType(dp3m.d_op[d][global_index[d]]);
756 auto &mesh_dip = dp3m.heffte.ks_dipole_density;
758 FloatType(dp3m.d_op[d][global_index[2u]]) * d_op_val *
759 dp3m.heffte.ks_scalar[remapped_index];
761 FloatType(dp3m.d_op[d][global_index[0u]]) * d_op_val *
762 dp3m.heffte.ks_scalar[remapped_index];
764 FloatType(dp3m.d_op[d][global_index[1u]]) * d_op_val *
765 dp3m.heffte.ks_scalar[remapped_index];
766#ifndef NDEBUG
767 if (not FFTConfig::use_r2c and not dp3m.params.tuning) {
768 auto const index_fft_legacy = local_index[2] +
771 for (int j = 0; j < 3; ++j) {
772 auto const old_value = std::complex<FloatType>{
773 dp3m.mesh.rs_fields[j][2 * index_fft_legacy],
774 dp3m.mesh.rs_fields[j][2 * index_fft_legacy + 1]};
775 auto const &new_value = mesh_dip[j][index_row_major_r2c];
777 }
778 }
779#endif // not NDEBUG
781 }
782 });
783 for (int dir = 0u; dir < 3u; ++dir) {
784 dp3m.heffte.fft->backward(
785 dp3m.heffte.ks_dipole_density[dir].data(),
786 dp3m.heffte.rs_B_fields_no_halo[dir].data());
787 // pad zeros around the B-field in real space for ghost layers
788 dp3m.heffte.rs_B_fields[d] =
789 pad_with_zeros_discard_imag<FFTConfig::r_space_order,
791 std::span(dp3m.heffte.rs_B_fields_no_halo[dir]),
792 dp3m.local_mesh.dim_no_halo, dp3m.local_mesh.n_halo_ld,
793 dp3m.local_mesh.n_halo_ur);
794 }
795 // communicate ghost layers of the B-field in real space
796 auto rs_fields =
797 std::array<FloatType *, 3u>{{dp3m.heffte.rs_B_fields[0u].data(),
798 dp3m.heffte.rs_B_fields[1u].data(),
799 dp3m.heffte.rs_B_fields[2u].data()}};
800 dp3m.heffte.halo_comm.spread_grid(::comm_cart, rs_fields,
801 dp3m.local_mesh.dim);
802 }
803#endif // ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
804 for (auto &rs_mesh : dp3m.fft_buffers->get_vector_mesh()) {
805 dp3m.fft->backward_fft(rs_mesh);
806 }
807 // communicate ghost layers of the B-field in real space
808 dp3m.fft_buffers->perform_vector_halo_spread();
809 // assign force component from mesh to particle
810 auto const d_rs = (d + dp3m.mesh.ks_pnum) % 3;
811 Utils::integral_parameter<int, AssignForcesDip, p3m_min_cao,
813 dp3m.params.cao, dp3m, dipole_prefac * Utils::sqr(wavenumber), d_rs,
814 *system.cell_structure);
815 }
816 } /* if (dp3m.sum_mu2 > 0) */
817 } /* if (force_flag) */
818
819 if (dp3m.params.epsilon != P3M_EPSILON_METALLIC) {
820 auto const surface_term =
821 calc_surface_term(force_flag, energy_flag or npt_flag);
822 if (this_node == 0) {
823 energy += surface_term;
824 }
825 }
826#ifdef ESPRESSO_NPT
827 if (npt_flag) {
829 }
830#endif
831 if (not energy_flag) {
832 energy = 0.;
833 }
834
835 return energy;
836}
837
838template <typename FloatType, Arch Architecture, class FFTConfig>
840 bool force_flag, bool energy_flag) {
841 auto const &system = get_system();
842 auto const &box_geo = *system.box_geo;
843 auto const particles = system.cell_structure->local_particles();
844 auto const pref = prefactor * 4. * std::numbers::pi / box_geo.volume() /
845 (2. * dp3m.params.epsilon + 1.);
846 auto const n_local_part = particles.size();
847
848 // We put all the dipolar momenta in a the arrays mx,my,mz according to the
849 // id-number of the particles
850 std::vector<double> mx(n_local_part);
851 std::vector<double> my(n_local_part);
852 std::vector<double> mz(n_local_part);
853
854 std::size_t ip = 0u;
855 for (auto const &p : particles) {
856 auto const dip = p.calc_dip();
857 mx[ip] = dip[0u];
858 my[ip] = dip[1u];
859 mz[ip] = dip[2u];
860 ip++;
861 }
862
863 // we will need the sum of all dipolar momenta vectors
864 auto local_dip = Utils::Vector3d{};
865 for (std::size_t i = 0u; i < n_local_part; i++) {
866 local_dip[0u] += mx[i];
867 local_dip[1u] += my[i];
868 local_dip[2u] += mz[i];
869 }
870 auto const box_dip =
871 boost::mpi::all_reduce(comm_cart, local_dip, std::plus<>());
872
873 double energy = 0.;
874 if (energy_flag) {
875 double sum_e = 0.;
876 for (std::size_t i = 0u; i < n_local_part; i++) {
877 sum_e += mx[i] * box_dip[0] + my[i] * box_dip[1] + mz[i] * box_dip[2];
878 }
879 energy =
880 0.5 * pref * boost::mpi::all_reduce(comm_cart, sum_e, std::plus<>());
881 }
882
883 if (force_flag) {
884
885 std::vector<double> sumix(n_local_part);
886 std::vector<double> sumiy(n_local_part);
887 std::vector<double> sumiz(n_local_part);
888
889 for (std::size_t i = 0u; i < n_local_part; i++) {
890 sumix[i] = my[i] * box_dip[2u] - mz[i] * box_dip[1u];
891 sumiy[i] = mz[i] * box_dip[0u] - mx[i] * box_dip[2u];
892 sumiz[i] = mx[i] * box_dip[1u] - my[i] * box_dip[0u];
893 }
894
895 ip = 0u;
896 for (auto &p : particles) {
897 auto &torque = p.torque();
898 torque[0u] -= pref * sumix[ip];
899 torque[1u] -= pref * sumiy[ip];
900 torque[2u] -= pref * sumiz[ip];
901 ip++;
902 }
903 }
904
905 return energy;
906}
907
908template <typename FloatType, Arch Architecture, class FFTConfig>
909void DipolarP3MHeffte<FloatType, Architecture,
910 FFTConfig>::calc_influence_function_force() {
911 dp3m.g_force = grid_influence_function_dipolar<FloatType, 3, P3M_BRILLOUIN,
912 FFTConfig::k_space_order>(
913 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
914 get_system().box_geo->length_inv());
915#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
916 if (dp3m.heffte.world_size == 1) {
917 dp3m.heffte.g_force =
919 FFTConfig::k_space_order>(
920 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
921 dp3m.heffte.fft->ks_local_ur_index(),
922 get_system().box_geo->length_inv());
923 if constexpr (FFTConfig::use_r2c) {
925 dp3m.heffte.g_force, dp3m.params.mesh,
926 dp3m.heffte.fft->ks_local_size(),
927 dp3m.heffte.fft->ks_local_ld_index());
928 }
929 }
930#endif
931}
932
933template <typename FloatType, Arch Architecture, class FFTConfig>
934void DipolarP3MHeffte<FloatType, Architecture,
935 FFTConfig>::calc_influence_function_energy() {
936 dp3m.g_energy = grid_influence_function_dipolar<FloatType, 2, P3M_BRILLOUIN,
937 FFTConfig::k_space_order>(
938 dp3m.params, dp3m.mesh.start, dp3m.mesh.stop,
939 get_system().box_geo->length_inv());
940#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
941 if (dp3m.heffte.world_size == 1) {
942 dp3m.heffte.g_energy =
944 FFTConfig::k_space_order>(
945 dp3m.params, dp3m.heffte.fft->ks_local_ld_index(),
946 dp3m.heffte.fft->ks_local_ur_index(),
947 get_system().box_geo->length_inv());
948 if constexpr (FFTConfig::use_r2c) {
950 dp3m.heffte.g_energy, dp3m.params.mesh,
951 dp3m.heffte.fft->ks_local_size(),
952 dp3m.heffte.fft->ks_local_ld_index());
953 }
954 }
955#endif
956}
957
958template <typename FloatType, Arch Architecture, class FFTConfig>
961 int m_mesh_max = -1, m_mesh_min = -1;
962 std::pair<std::optional<int>, std::optional<int>> m_tune_limits;
963
964public:
966 double prefactor, int timings,
967 decltype(m_tune_limits) tune_limits)
968 : TuningAlgorithm(system, prefactor, timings), dp3m{input_dp3m},
969 m_tune_limits{std::move(tune_limits)} {}
970
971 P3MParameters &get_params() override { return dp3m.params; }
972
973 void on_solver_change() const override { m_system.on_dipoles_change(); }
974
975 std::optional<std::string>
976 layer_correction_veto_r_cut(double) const override {
977 return {};
978 }
979
980 void setup_logger(bool verbose) override {
981 auto const &box_geo = *m_system.box_geo;
982 m_logger = std::make_unique<TuningLogger>(
983 verbose and this_node == 0, "DipolarP3M", TuningLogger::Mode::Dipolar);
984 m_logger->tuning_goals(dp3m.params.accuracy, m_prefactor,
985 box_geo.length()[0], dp3m.sum_dip_part,
986 dp3m.sum_mu2);
987 m_logger->log_tuning_start();
988 }
989
990 std::tuple<double, double, double, double>
992 double r_cut_iL) const override {
993
994 double alpha_L, rs_err, ks_err;
995 auto const &box_geo = *m_system.box_geo;
996
997 /* calc maximal real space error for setting */
998 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
999 dp3m.sum_dip_part, dp3m.sum_mu2, 0.001);
1000 // alpha cannot be zero for dipoles because real-space formula breaks down
1001
1002 if (std::numbers::sqrt2 * rs_err > dp3m.params.accuracy) {
1003 /* assume rs_err = ks_err -> rs_err = accuracy/sqrt(2.0) -> alpha_L */
1004 alpha_L = dp3m_rtbisection(
1005 box_geo.length()[0], r_cut_iL, dp3m.sum_dip_part, dp3m.sum_mu2,
1006 0.0001 * box_geo.length()[0], 5. * box_geo.length()[0], 0.0001,
1007 dp3m.params.accuracy);
1008 } else {
1009 /* even alpha=0 is ok, however, we cannot choose it since it kills the
1010 k-space error formula.
1011 Anyways, this very likely NOT the optimal solution */
1012 alpha_L = 0.1;
1013 }
1014
1015 /* calculate real-space and k-space error for this alpha_L */
1016 rs_err = dp3m_real_space_error(box_geo.length()[0], r_cut_iL,
1017 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1018 ks_err = dp3m_k_space_error(box_geo.length()[0], mesh[0], cao,
1019 dp3m.sum_dip_part, dp3m.sum_mu2, alpha_L);
1020
1021 return {Utils::Vector2d{rs_err, ks_err}.norm(), rs_err, ks_err, alpha_L};
1022 }
1023
1024 void determine_mesh_limits() override {
1025 if (dp3m.params.mesh[0] == -1) {
1026 /* simple heuristic to limit the tried meshes if the accuracy cannot
1027 be obtained with smaller meshes, but normally not all these
1028 meshes have to be tested */
1029 auto const expo = std::log(std::cbrt(dp3m.sum_dip_part)) / std::log(2.);
1030 /* Medium-educated guess for the minimal mesh */
1031 m_mesh_min = static_cast<int>(std::round(std::pow(2., std::floor(expo))));
1032 /* avoid using more than 1 GB of FFT arrays */
1033 m_mesh_max = 128;
1034 if (m_tune_limits.first) {
1035 m_mesh_min = *m_tune_limits.first;
1036 }
1037 if (m_tune_limits.second) {
1038 m_mesh_max = *m_tune_limits.second;
1039 }
1040 } else {
1041 m_mesh_min = m_mesh_max = dp3m.params.mesh[0];
1042 m_logger->report_fixed_mesh(dp3m.params.mesh);
1043 }
1044 }
1045
1048 auto time_best = time_sentinel;
1049 for (auto tmp_mesh = m_mesh_min; tmp_mesh <= m_mesh_max; tmp_mesh += 2) {
1052 trial_params.cao = cao_best;
1053
1054 auto const trial_time =
1056 trial_params.alpha_L, trial_params.accuracy);
1057
1058 /* this mesh does not work at all */
1059 if (trial_time < 0.)
1060 continue;
1061
1062 /* the optimum r_cut for this mesh is the upper limit for higher meshes,
1063 everything else is slower */
1064 m_r_cut_iL_max = trial_params.r_cut_iL;
1065
1066 if (trial_time < time_best) {
1067 /* new optimum */
1073 /* no hope of further optimisation */
1074 break;
1075 }
1076 }
1077 return tuned_params;
1078 }
1079};
1080
1081template <typename FloatType, Arch Architecture, class FFTConfig>
1083 auto &system = get_system();
1084 auto const &box_geo = *system.box_geo;
1085 if (dp3m.params.alpha_L == 0. and dp3m.params.alpha != 0.) {
1086 dp3m.params.alpha_L = dp3m.params.alpha * box_geo.length()[0];
1087 }
1088 if (dp3m.params.r_cut_iL == 0. and dp3m.params.r_cut != 0.) {
1089 dp3m.params.r_cut_iL = dp3m.params.r_cut * box_geo.length_inv()[0];
1090 }
1091 if (not is_tuned()) {
1093 if (dp3m.sum_dip_part == 0) {
1094 throw std::runtime_error(
1095 "DipolarP3M: no dipolar particles in the system");
1096 }
1097 try {
1099 system, dp3m, prefactor, tuning.timings, tuning.limits);
1100 parameters.setup_logger(tuning.verbose);
1101 // parameter ranges
1102 parameters.determine_mesh_limits();
1103 parameters.determine_r_cut_limits();
1104 parameters.determine_cao_limits(3);
1105 // run tuning algorithm
1106 parameters.tune();
1107 m_is_tuned = true;
1108 system.on_dipoles_change();
1109 } catch (...) {
1110 dp3m.params.tuning = false;
1111 throw;
1112 }
1113 }
1114 init();
1115}
1116
1117/** Tuning dipolar-P3M */
1118inline auto dp3m_tune_aliasing_sums(Utils::Vector3i const &shift, int mesh,
1119 double mesh_i, int cao, double alpha_L_i) {
1120
1123 auto const factor1 = Utils::sqr(std::numbers::pi * alpha_L_i);
1124 auto alias1 = 0.;
1125 auto alias2 = 0.;
1126
1132 [&]() {
1133 auto const norm_sq = nm.norm2();
1134 auto const ex = std::exp(-factor1 * norm_sq);
1135 auto const U2 = std::pow(Utils::product(fnm), 2 * cao);
1137 alias2 += U2 * ex * std::pow(shift * nm, 3) / norm_sq;
1138 },
1139 [&](unsigned dim, int n) {
1140 nm[dim] = shift[dim] + n * mesh;
1141 fnm[dim] = math::sinc(nm[dim] * mesh_i);
1142 });
1143
1144 return std::make_pair(alias1, alias2);
1145}
1146
1147/** Calculate the k-space error of dipolar-P3M */
1148inline double dp3m_k_space_error(double box_size, int mesh, int cao,
1149 std::size_t n_c_part, double sum_q2,
1150 double alpha_L) {
1151
1153 auto const mesh_i = 1. / static_cast<double>(mesh);
1154 auto const alpha_L_i = 1. / alpha_L;
1155 auto const mesh_stop = Utils::Vector3i::broadcast(mesh / 2);
1156 auto const mesh_start = -mesh_stop;
1157 auto indices = Utils::Vector3i{};
1158 auto values = Utils::Vector3d{};
1159 auto he_q = 0.;
1160
1163 [&]() {
1164 if ((indices[0] != 0) or (indices[1] != 0) or (indices[2] != 0)) {
1165 auto const n2 = indices.norm2();
1166 auto const cs = Utils::product(values);
1167 auto const [alias1, alias2] =
1169 auto const d =
1170 alias1 - Utils::sqr(alias2 / cs) /
1171 Utils::int_pow<3>(static_cast<double>(n2));
1172 /* at high precision, d can become negative due to extinction;
1173 also, don't take values that have no significant digits left*/
1174 if (d > 0. and std::fabs(d / alias1) > round_error_prec)
1175 he_q += d;
1176 }
1177 },
1178 [&values, &mesh_i, cotangent_sum](unsigned dim, int n) {
1179 values[dim] = cotangent_sum(n, mesh_i);
1180 });
1181
1182 return 8. * Utils::sqr(std::numbers::pi) / 3. * sum_q2 *
1183 sqrt(he_q / static_cast<double>(n_c_part)) /
1184 Utils::int_pow<4>(box_size);
1185}
1186
1187/** Calculate the value of the errors for the REAL part of the force in terms
1188 * of the splitting parameter alpha of Ewald. Based on eq. (33) @cite wang01a.
1189 *
1190 * Please note that in this more refined approach we don't use
1191 * eq. (37), but eq. (33) which maintains all the powers in alpha.
1192 */
1193inline double dp3m_real_space_error(double box_size, double r_cut_iL,
1194 std::size_t n_c_part, double sum_q2,
1195 double alpha_L) {
1196 auto constexpr exp_min = -708.4; // for IEEE-compatible double
1197 double d_error_f, d_cc, d_dc, d_con;
1198
1199 auto const d_rcut = r_cut_iL * box_size;
1200 auto const d_rcut2 = Utils::sqr(d_rcut);
1201 auto const d_rcut4 = Utils::sqr(d_rcut2);
1202
1203 auto const d_a2 = Utils::sqr(alpha_L) / Utils::sqr(box_size);
1204 auto const exponent = -d_a2 * d_rcut2;
1205 auto const exp_term = (exponent < exp_min) ? 0. : std::exp(exponent);
1206 auto const d_c = sum_q2 * exp_term;
1207
1208 d_cc = 4. * Utils::sqr(d_a2) * Utils::sqr(d_rcut2) + 6. * d_a2 * d_rcut2 + 3.;
1209
1210 d_dc = 8. * Utils::int_pow<3>(d_a2) * Utils::int_pow<3>(d_rcut2) +
1211 20. * Utils::sqr(d_a2) * d_rcut4 + 30. * d_a2 * d_rcut2 + 15.;
1212
1213 d_con = 1. / sqrt(Utils::int_pow<3>(box_size) * Utils::sqr(d_a2) * d_rcut *
1214 Utils::sqr(d_rcut4) * static_cast<double>(n_c_part));
1215
1216 d_error_f = d_c * d_con *
1217 sqrt((13. / 6.) * Utils::sqr(d_cc) +
1218 (2. / 15.) * Utils::sqr(d_dc) - (13. / 15.) * d_cc * d_dc);
1219
1220 return d_error_f;
1221}
1222
1223/** Using bisection, find the root of a function "func-tuned_accuracy/sqrt(2.)"
1224 * known to lie between x1 and x2. The root, returned as rtbis, will be
1225 * refined until its accuracy is \f$\pm\f$ @p xacc.
1226 */
1227double dp3m_rtbisection(double box_size, double r_cut_iL, std::size_t n_c_part,
1228 double sum_q2, double x1, double x2, double xacc,
1229 double tuned_accuracy) {
1230 constexpr int JJ_RTBIS_MAX = 40;
1231
1232 auto const constant = tuned_accuracy / std::numbers::sqrt2;
1233
1234 auto const f1 =
1235 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x1) -
1236 constant;
1237 auto const f2 =
1238 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, x2) -
1239 constant;
1240 if (f1 * f2 >= 0.0) {
1241 throw std::runtime_error(
1242 "Root must be bracketed for bisection in dp3m_rtbisection");
1243 }
1244 // Orient the search dx, and set rtb to x1 or x2 ...
1245 double dx;
1246 double rtb = f1 < 0.0 ? (dx = x2 - x1, x1) : (dx = x1 - x2, x2);
1247 for (int j = 1; j <= JJ_RTBIS_MAX; j++) {
1248 auto const xmid = rtb + (dx *= 0.5);
1249 auto const fmid =
1250 dp3m_real_space_error(box_size, r_cut_iL, n_c_part, sum_q2, xmid) -
1251 constant;
1252 if (fmid <= 0.0)
1253 rtb = xmid;
1254 if (fabs(dx) < xacc || fmid == 0.0)
1255 return rtb;
1256 }
1257 throw std::runtime_error("Too many bisections in dp3m_rtbisection");
1258}
1259
1261 auto const &system = get_system();
1262 auto const &box_geo = *system.box_geo;
1263 auto const &local_geo = *system.local_geo;
1264 for (auto i = 0u; i < 3u; i++) {
1265 /* check k-space cutoff */
1266 if (dp3m_params.cao_cut[i] >= box_geo.length_half()[i]) {
1267 std::stringstream msg;
1268 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1269 << " is larger than half of box dimension " << box_geo.length()[i];
1270 throw std::runtime_error(msg.str());
1271 }
1272 if (dp3m_params.cao_cut[i] >= local_geo.length()[i]) {
1273 std::stringstream msg;
1274 msg << "dipolar P3M_init: k-space cutoff " << dp3m_params.cao_cut[i]
1275 << " is larger than local box dimension " << local_geo.length()[i];
1276 throw std::runtime_error(msg.str());
1277 }
1278 }
1279
1280 if ((box_geo.length()[0] != box_geo.length()[1]) or
1281 (box_geo.length()[1] != box_geo.length()[2])) {
1282 throw std::runtime_error("DipolarP3M: requires a cubic box");
1283 }
1284}
1285
1287 auto const &box_geo = *get_system().box_geo;
1288 if (!box_geo.periodic(0) or !box_geo.periodic(1) or !box_geo.periodic(2)) {
1289 throw std::runtime_error(
1290 "DipolarP3M: requires periodicity (True, True, True)");
1291 }
1292}
1293
1295 auto const &local_geo = *get_system().local_geo;
1296 if (local_geo.cell_structure_type() != CellStructureType::REGULAR and
1297 local_geo.cell_structure_type() != CellStructureType::HYBRID) {
1298 throw std::runtime_error(
1299 "DipolarP3M: requires the regular or hybrid decomposition cell system");
1300 }
1301 if (::communicator.size > 1 and
1302 local_geo.cell_structure_type() == CellStructureType::HYBRID) {
1303 throw std::runtime_error(
1304 "DipolarP3M: does not work with the hybrid decomposition cell system, "
1305 "if using more than one MPI node");
1306 }
1307}
1308
1310 auto const &node_grid = ::communicator.node_grid;
1311 if (node_grid[0] < node_grid[1] or node_grid[1] < node_grid[2]) {
1312 throw std::runtime_error(
1313 "DipolarP3M: node grid must be sorted, largest first");
1314 }
1315}
1316
1317template <typename FloatType, Arch Architecture, class FFTConfig>
1319 auto const &box_geo = *get_system().box_geo;
1320 dp3m.params.r_cut = dp3m.params.r_cut_iL * box_geo.length()[0];
1321 dp3m.params.alpha = dp3m.params.alpha_L * box_geo.length_inv()[0];
1322 dp3m.params.recalc_a_ai_cao_cut(box_geo.length());
1323 dp3m.local_mesh.recalc_ld_pos(dp3m.params);
1324 sanity_checks_boxl();
1325 calc_influence_function_force();
1326 calc_influence_function_energy();
1327 dp3m.energy_correction = 0.;
1328#ifdef ESPRESSO_DP3M_HEFFTE_CROSS_CHECKS
1329 if (dp3m.heffte.world_size == 1) {
1330 dp3m.heffte.halo_comm.resize(::comm_cart, dp3m.local_mesh);
1331 }
1332#endif
1333}
1334
1335template <typename FloatType, Arch Architecture, class FFTConfig>
1336void DipolarP3MHeffte<FloatType, Architecture,
1337 FFTConfig>::calc_energy_correction() {
1338 auto const &box_geo = *get_system().box_geo;
1339 auto const Ukp3m = calc_average_self_energy_k_space() * box_geo.volume();
1340 auto const Ewald_volume = Utils::int_pow<3>(dp3m.params.alpha_L);
1341 auto const Eself = -2. * Ewald_volume * std::numbers::inv_sqrtpi / 3.;
1342 dp3m.energy_correction =
1343 -dp3m.sum_mu2 * (Ukp3m + Eself + 2. * std::numbers::pi / 3.);
1344}
1345
1346#ifdef ESPRESSO_NPT
1347template <typename FloatType, Arch Architecture, class FFTConfig>
1348void DipolarP3MHeffte<FloatType, Architecture,
1349 FFTConfig>::npt_add_virial_contribution(double energy)
1350 const {
1351 get_system().npt_add_virial_contribution(energy);
1352}
1353#endif // ESPRESSO_NPT
1354
1355#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_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:159
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:131
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.
auto pad_with_zeros_discard_imag(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.
auto extract_block(Container const &in_array, Utils::Vector3i const &dimensions, Utils::Vector3i const &start, Utils::Vector3i const &stop)
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:380
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.
ESPRESSO_ATTR_ALWAYS_INLINE void kokkos_parallel_range_for(auto const &name, auto start, auto end, auto const &kernel)
Utils::Vector3i node_grid
double calc_surface_term(bool force_flag, bool energy_flag) override
void dipole_assign() override
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 prefac, int d_rs, CellStructure &cell_structure) const
void operator()(auto &dp3m, double prefac, int d_rs, CellStructure &cell_structure) const