ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
dipolar_direct_sum.cpp
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_DIPOLES
25
28
29#include "BoxGeometry.hpp"
30#include "cells.hpp"
31#include "communication.hpp"
32#include "errorhandling.hpp"
34#include "system/System.hpp"
35
36#include <Kokkos_Core.hpp>
37#include <Kokkos_ScatterView.hpp>
38
39#include <utils/Vector.hpp>
42
43#include <boost/mpi/collectives.hpp>
44#include <boost/mpi/communicator.hpp>
45
46#include <mpi.h>
47
48#include <cassert>
49#include <cstddef>
50#include <numeric>
51#include <stdexcept>
52#include <tuple>
53#include <utility>
54#include <vector>
55
56/**
57 * @brief Position and dipole moment of one particle.
58 */
59struct PosMom {
62
63 template <class Archive> void serialize(Archive &ar, long int) {
64 ar & pos & m;
65 }
66};
67
68static auto gather_particle_data(BoxGeometry const &box_geo,
69 ParticleRange const &particles) {
70 auto const &comm = ::comm_cart;
71 std::vector<Particle *> local_particles;
72 std::vector<PosMom> local_posmom;
73 std::vector<PosMom> all_posmom;
74 std::vector<boost::mpi::request> reqs;
75
76 local_particles.reserve(particles.size());
77 local_posmom.reserve(particles.size());
78
79 for (auto &p : particles) {
80 if (p.dipm() != 0.0) {
81 local_particles.emplace_back(&p);
82 local_posmom.emplace_back(
83 PosMom{box_geo.folded_position(p.pos()), p.calc_dip()});
84 }
85 }
86
87 auto const local_size = static_cast<int>(local_posmom.size());
88 std::vector<int> all_sizes;
89 boost::mpi::all_gather(comm, local_size, all_sizes);
90
91 auto const offset =
92 std::accumulate(all_sizes.begin(), all_sizes.begin() + comm.rank(), 0);
93 auto const total_size =
94 std::accumulate(all_sizes.begin() + comm.rank(), all_sizes.end(), offset);
95
96 if (comm.size() > 1) {
97 all_posmom.resize(total_size);
99 all_posmom.data(), all_sizes.data());
100 } else {
101 std::swap(all_posmom, local_posmom);
102 }
103
104 return std::make_tuple(std::move(local_particles), std::move(all_posmom),
105 std::move(reqs), offset);
106}
107
108static auto get_n_cut(BoxGeometry const &box_geo, int n_replicas) {
109 return n_replicas * Utils::Vector3i{static_cast<int>(box_geo.periodic(0)),
110 static_cast<int>(box_geo.periodic(1)),
111 static_cast<int>(box_geo.periodic(2))};
112}
113
114/**
115 * Real-space image shifts n x box_l inside the |ncut| sphere. Index 0 is the
116 * primary (zero) shift so self-interaction loops start at index 1.
117 */
118static std::vector<Utils::Vector3d>
120 auto const ncut2 = ncut.norm2();
121 std::vector<Utils::Vector3d> shifts;
122 shifts.push_back({0., 0., 0.});
123 for (int nx = -ncut[0]; nx <= ncut[0]; ++nx)
124 for (int ny = -ncut[1]; ny <= ncut[1]; ++ny)
125 for (int nz = -ncut[2]; nz <= ncut[2]; ++nz) {
126 if (nx == 0 && ny == 0 && nz == 0)
127 continue;
128 if (nx * nx + ny * ny + nz * nz <= ncut2)
129 shifts.push_back({nx * box_l[0], ny * box_l[1], nz * box_l[2]});
130 }
131 return shifts;
132}
133
134/**
135 * @brief Calculate and add the interaction forces/torques to the particles.
136 *
137 * This employs a parallel N-square loop over all particle pairs.
138 * The computation the partitioned into several steps so that the
139 * communication latency can be hidden behind some local computation:
140 *
141 * 1. The local particle positions and momenta are packed into
142 * one array.
143 * 2. The asynchronous distribution of the local arrays to all
144 * ranks is started.
145 * 3. The interaction for the local pairs is started, here every
146 * pair is visited only once, and the force is added to both particles.
147 * 4. Wait for the data from the other nodes.
148 * 5. Calculate the interaction with the rest of the particles. Here
149 * every pair is visited twice (not necessarily on the same rank)
150 * so that no reduction of the forces is needed.
151 *
152 * Logically this is equivalent to the potential calculation
153 * in @ref DipolarDirectSum::long_range_energy, which calculates
154 * a naive N-square sum, but has better performance and scaling.
155 */
158 auto const &system = get_system();
159 auto const &box_geo = *system.box_geo;
160 auto const &box_l = box_geo.length();
161 auto const particles = system.cell_structure->local_particles();
162 auto [local_particles, all_posmom, reqs, offset_signed] =
163 gather_particle_data(box_geo, particles);
164
165 /* Number of image boxes considered */
166 auto const ncut = get_n_cut(box_geo, n_replicas);
167 auto const with_replicas = (ncut.norm2() > 0);
168 auto const shifts = make_image_shifts(ncut, box_l);
169
170 auto const offset = static_cast<std::size_t>(offset_signed);
171 auto const n_local = local_particles.size();
172 auto const n_total = all_posmom.size();
173
174 auto const prefactor_local = prefactor;
175
176 /* Raw pointer to the gathered AoS data; the local slice is populated before
177 * wait_all, so Phase A may read it, and it outlives all fences. Safe to
178 * capture by value in the Kokkos [=] lambdas. */
179 auto const *pm = all_posmom.data();
180
181 using execution_space = Kokkos::DefaultExecutionSpace;
182 using ForceView =
183 Kokkos::View<double *[3], Kokkos::LayoutRight, Kokkos::HostSpace>;
184 using ScatterForce =
185 Kokkos::Experimental::ScatterView<double *[3], Kokkos::LayoutRight>;
186 ForceView local_force("dds_force", n_local);
187 ForceView local_torque("dds_torque", n_local);
188 ScatterForce scatter_force(local_force);
189 ScatterForce scatter_torque(local_torque);
190
191 /* Raw pointers so the Kokkos lambdas do not capture std::vector by value. */
192 auto *local_particles_ptr = local_particles.data();
193 auto const *shifts_ptr = shifts.data();
194 auto const n_shifts = shifts.size();
195
196 /* Phase A: local pairs. Each i owns its own force/torque accumulation
197 * (written directly, unique owner, no race); the Newton's-third-law
198 * partner-j contributions go through the ScatterView with a per-lane
199 * scatter. */
200 Kokkos::RangePolicy<execution_space, Kokkos::Schedule<Kokkos::Dynamic>>
201 policy_local(std::size_t{0}, n_local);
202 policy_local.set_chunk_size(64);
203 Kokkos::parallel_for(
204 "dds_local_pairs", policy_local, [=](std::size_t const i) {
205 auto const gi = offset + i;
206 auto const &pos_i = pm[gi].pos;
207 auto const &m_i = pm[gi].m;
208 PairForce fi{};
209
210 /* (a) self-images (shifts[1..], primary excluded) */
211 for (std::size_t s = 1; s < n_shifts; ++s)
212 fi += pair_force(shifts_ptr[s], m_i, m_i);
213
214 auto force_access = scatter_force.access();
215 auto torque_access = scatter_torque.access();
216
217 /* (b) pairs with j in (gi, offset + n_local) */
218 for (auto j = gi + 1; j < offset + n_local; ++j) {
219 auto const &pos_j = pm[j].pos;
220 auto const &m_j = pm[j].m;
221 auto const d0 = with_replicas ? (pos_i - pos_j)
222 : box_geo.get_mi_vector(pos_i, pos_j);
223 auto const jl = j - offset;
224 for (std::size_t s = 0; s < n_shifts; ++s) {
225 auto const rn = d0 + shifts_ptr[s];
226 auto const pf = pair_force(rn, m_i, m_j);
227 fi.f += pf.f;
228 fi.torque += pf.torque;
229 /* Conservation of angular momentum mandates that
230 * 0 = t_i + r_ij x F_ij + t_j */
231 auto const torque_j = vector_product(pf.f, rn) - pf.torque;
232 for (int c = 0; c < 3; ++c) {
233 force_access(jl, c) -= pf.f[c];
234 torque_access(jl, c) += torque_j[c];
235 }
236 }
237 }
238 /* (d) write i's own total directly (unique owner, no race) */
239 local_particles_ptr[i]->force() += prefactor_local * fi.f;
240 local_particles_ptr[i]->torque() += prefactor_local * fi.torque;
241 });
242 Kokkos::fence();
243
244 /* Wait for remote data; the remote slices of all_posmom are now populated. */
245 boost::mpi::wait_all(reqs.begin(), reqs.end());
246
247 /* Phase B: remote pairs (red [0, offset) + black [offset + n_local,
248 * n_total)), visit-twice, no scatter; accumulate only i. */
249 Kokkos::RangePolicy<execution_space> policy_remote(std::size_t{0}, n_local);
250 Kokkos::parallel_for(
251 "dds_remote_pairs", policy_remote, [=](std::size_t const i) {
252 auto const gi = offset + i;
253 auto const &pos_i = pm[gi].pos;
254 auto const &m_i = pm[gi].m;
255 PairForce fi{};
256
257 /* Two remote ranges: red [0, offset) and black [offset + n_local,
258 * n_total). Visit-twice (each remote pair is visited once per owning
259 * rank), so only i accumulates; no scatter. */
260 std::size_t const ranges[2][2] = {{std::size_t{0}, offset},
261 {offset + n_local, n_total}};
262 for (auto const &range : ranges) {
263 auto const range_begin = range[0];
264 auto const range_end = range[1];
265 for (auto j = range_begin; j < range_end; ++j) {
266 auto const &pos_j = pm[j].pos;
267 auto const &m_j = pm[j].m;
268 auto const d0 = with_replicas ? (pos_i - pos_j)
269 : box_geo.get_mi_vector(pos_i, pos_j);
270 for (std::size_t s = 0; s < n_shifts; ++s) {
271 auto const rn = d0 + shifts_ptr[s];
272 auto const pf = pair_force(rn, m_i, m_j);
273 fi.f += pf.f;
274 fi.torque += pf.torque;
275 }
276 }
277 }
278 local_particles_ptr[i]->force() += prefactor_local * fi.f;
279 local_particles_ptr[i]->torque() += prefactor_local * fi.torque;
280 });
281 Kokkos::fence();
282
283 /* Reduce the Newton's-third-law contributions and add to particles. */
284 Kokkos::Experimental::contribute(local_force, scatter_force);
285 Kokkos::Experimental::contribute(local_torque, scatter_torque);
286 Kokkos::RangePolicy<execution_space> policy_reduce(std::size_t{0}, n_local);
287 Kokkos::parallel_for(
288 "dds_reduction", policy_reduce, [=](std::size_t const i) {
289 local_particles_ptr[i]->force() +=
290 prefactor_local * Utils::Vector3d{local_force(i, 0),
291 local_force(i, 1),
292 local_force(i, 2)};
293 local_particles_ptr[i]->torque() +=
294 prefactor_local * Utils::Vector3d{local_torque(i, 0),
295 local_torque(i, 1),
296 local_torque(i, 2)};
297 });
298 Kokkos::fence();
299
300#ifdef ESPRESSO_NPT
301 // As with DipolarP3M, the energy is not a valid
302 // substitute for the virial trace for dipole-dipole interactions, so the
303 // pressure tensor (see long_range_pressure()) is reused instead.
304 if (system.has_npt_enabled()) {
306 get_system().npt_add_virial_contribution(
308 }
309#endif
310#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
311 if (not m_is_gpu) {
313 }
314#endif
315}
316
317/**
318 * @brief Calculate the interaction potential.
319 *
320 * This employs a parallel N-square loop over all particle pairs.
321 */
324 auto const &system = get_system();
325 auto const &box_geo = *system.box_geo;
326 auto const &box_l = box_geo.length();
327 auto const particles = system.cell_structure->local_particles();
328 auto [local_particles, all_posmom, reqs, offset_signed] =
329 gather_particle_data(box_geo, particles);
330
331 /* Number of image boxes considered */
332 auto const ncut = get_n_cut(box_geo, n_replicas);
333 auto const with_replicas = (ncut.norm2() > 0);
334 auto const shifts = make_image_shifts(ncut, box_l);
335
336 auto const offset = static_cast<std::size_t>(offset_signed);
337 auto const n_local = local_particles.size();
338 auto const n_total = all_posmom.size();
339
340 /* Raw pointer to the gathered AoS data; the local slice is populated before
341 * wait_all, so Phase A may read it, and it outlives all fences. Safe to
342 * capture by value in the Kokkos [=] lambdas. */
343 auto const *pm = all_posmom.data();
344
345 using execution_space = Kokkos::DefaultExecutionSpace;
346
347 /* Raw pointers so the Kokkos lambdas do not capture std::vector by value. */
348 auto const *shifts_ptr = shifts.data();
349 auto const n_shifts = shifts.size();
350
351 /* Phase A: local-upper triangular sum over j in [gi, offset + n_local),
352 * i.e. the self-image energy (shifts[1..], primary excluded) plus the pairs
353 * with j in (gi, offset + n_local). Computed from the local slice of the
354 * gathered data while the remote data is still in flight. */
355 double uA = 0.;
356 Kokkos::RangePolicy<execution_space, Kokkos::Schedule<Kokkos::Dynamic>>
357 policy_local(std::size_t{0}, n_local);
358 policy_local.set_chunk_size(64);
359 Kokkos::parallel_reduce(
360 "dds_energy_local", policy_local,
361 [=](std::size_t const i, double &u_local) {
362 auto const gi = offset + i;
363 auto const &pos_i = pm[gi].pos;
364 auto const &m_i = pm[gi].m;
365
366 /* (a) self-images (shifts[1..], primary excluded) */
367 for (std::size_t s = 1; s < n_shifts; ++s)
369
370 /* (b) pairs with j in (gi, offset + n_local) */
371 for (auto j = gi + 1; j < offset + n_local; ++j) {
372 auto const &pos_j = pm[j].pos;
373 auto const &m_j = pm[j].m;
374 auto const d0 = with_replicas ? (pos_i - pos_j)
375 : box_geo.get_mi_vector(pos_i, pos_j);
376 for (std::size_t s = 0; s < n_shifts; ++s)
378 }
379 },
380 uA);
381
382 /* Wait for remote data, fill the black slice. The red range [0, offset) is
383 * never summed by the energy kernel (each pair is counted once on the rank
384 * owning its lower index). */
385 boost::mpi::wait_all(reqs.begin(), reqs.end());
386
387 /* Phase B: remote-black sum over j in [offset + n_local, n_total). No self
388 * term and no primary exclusion; the range is entirely remote. */
389 double uB = 0.;
390 Kokkos::RangePolicy<execution_space> policy_remote(std::size_t{0}, n_local);
391 Kokkos::parallel_reduce(
392 "dds_energy_remote", policy_remote,
393 [=](std::size_t const i, double &u_local) {
394 auto const gi = offset + i;
395 auto const &pos_i = pm[gi].pos;
396 auto const &m_i = pm[gi].m;
397 /* sum over j in [offset + n_local, n_total) */
398 for (auto j = offset + n_local; j < n_total; ++j) {
399 auto const &pos_j = pm[j].pos;
400 auto const &m_j = pm[j].m;
401 auto const d0 = with_replicas ? (pos_i - pos_j)
402 : box_geo.get_mi_vector(pos_i, pos_j);
403 for (std::size_t s = 0; s < n_shifts; ++s)
405 }
406 },
407 uB);
408
409 return prefactor * (uA + uB);
410}
411
412/**
413 * @brief Calculate the dipolar pressure tensor.
414 *
415 * This employs a parallel N-square loop over all particle pairs.
416 * The dipole-dipole force is not central (unlike Coulomb), so the pair
417 * contribution to the virial only accounts for the force, not the torque,
418 * matching the convention used by @ref DipolarP3M.
419 */
421 if (m_is_gpu) {
422 runtimeWarningMsg() << "Pressure calculation not implemented for "
423 "DipolarDirectSum on GPU.";
424 return Utils::Vector9d{};
425 }
426
427 auto const &system = get_system();
428 auto const &box_geo = *system.box_geo;
429 auto const &box_l = box_geo.length();
430 auto const particles = system.cell_structure->local_particles();
431 auto [local_particles, all_posmom, reqs, offset_signed] =
432 gather_particle_data(box_geo, particles);
433
434 /* Number of image boxes considered */
435 auto const ncut = get_n_cut(box_geo, n_replicas);
436 auto const with_replicas = (ncut.norm2() > 0);
437 auto const shifts = make_image_shifts(ncut, box_l);
438
439 auto const offset = static_cast<std::size_t>(offset_signed);
440 auto const n_local = local_particles.size();
441 auto const n_total = all_posmom.size();
442
443 /* Raw pointer to the gathered AoS data; the local slice is populated before
444 * wait_all, so Phase A may read it, and it outlives all fences. Safe to
445 * capture by value in the Kokkos [=] lambdas. */
446 auto const *pm = all_posmom.data();
447
448 using execution_space = Kokkos::DefaultExecutionSpace;
449
450 /* Raw pointers so the Kokkos lambdas do not capture std::vector by value. */
451 auto const *shifts_ptr = shifts.data();
452 auto const n_shifts = shifts.size();
453
454 /* The pair contribution to the virial is r_n (x) F(r_n), reduced as a flat
455 * 9-component array. The dipole-dipole force is not central, so only the
456 * force enters the virial (not the torque). */
457
458 /* Phase A: local upper-triangular sum over j in [gi, offset + n_local) --
459 * the self-image term (shifts[1..], primary excluded) plus the pairs with
460 * j in (gi, offset + n_local). Computed while remote data is in flight. */
462 Kokkos::RangePolicy<execution_space, Kokkos::Schedule<Kokkos::Dynamic>>
463 policy_local(std::size_t{0}, n_local);
464 policy_local.set_chunk_size(64);
465 auto const join_op = [](Utils::Vector9d &acc, Utils::Vector9d const &val) {
466 acc += val;
467 };
468 auto reducerA = Reduction::make_kokkos_reducer<Utils::Vector9d>(
469 // NOLINTNEXTLINE(bugprone-exception-escape)
470 [=](std::size_t const i, Utils::Vector9d &psum) noexcept {
471 auto const gi = offset + i;
472 auto const &pos_i = pm[gi].pos;
473 auto const &m_i = pm[gi].m;
474
475 /* (a) self-images (shifts[1..], primary excluded) */
476 for (std::size_t s = 1; s < n_shifts; ++s) {
477 auto const rn = shifts_ptr[s];
480 }
481
482 /* (b) pairs with j in (gi, offset + n_local) */
483 for (auto j = gi + 1; j < offset + n_local; ++j) {
484 auto const &pos_j = pm[j].pos;
485 auto const &m_j = pm[j].m;
486 auto const d0 = with_replicas ? (pos_i - pos_j)
487 : box_geo.get_mi_vector(pos_i, pos_j);
488 for (std::size_t s = 0; s < n_shifts; ++s) {
489 auto const rn = d0 + shifts_ptr[s];
492 }
493 }
494 },
495 join_op);
496 Kokkos::parallel_reduce("dds_pressure_local", policy_local, reducerA, pA);
497
498 /* Wait for remote data, fill the black slice. The red range [0, offset) is
499 * never summed (each pair is counted once on the rank owning its lower
500 * index). */
501 boost::mpi::wait_all(reqs.begin(), reqs.end());
502
503 /* Phase B: remote-black sum over j in [offset + n_local, n_total). No self
504 * term and no primary exclusion -- the range is entirely remote. */
506 Kokkos::RangePolicy<execution_space> policy_remote(std::size_t{0}, n_local);
507 auto reducerB = Reduction::make_kokkos_reducer<Utils::Vector9d>(
508 // NOLINTNEXTLINE(bugprone-exception-escape)
509 [=](std::size_t const i, Utils::Vector9d &psum) noexcept {
510 auto const gi = offset + i;
511 auto const &pos_i = pm[gi].pos;
512 auto const &m_i = pm[gi].m;
513 for (auto j = offset + n_local; j < n_total; ++j) {
514 auto const &pos_j = pm[j].pos;
515 auto const &m_j = pm[j].m;
516 auto const d0 = with_replicas ? (pos_i - pos_j)
517 : box_geo.get_mi_vector(pos_i, pos_j);
518 for (std::size_t s = 0; s < n_shifts; ++s) {
519 auto const rn = d0 + shifts_ptr[s];
522 }
523 }
524 },
525 join_op);
526 Kokkos::parallel_reduce("dds_pressure_remote", policy_local, reducerB, pB);
527
528 return prefactor * (pA + pB);
529}
530
531/**
532 * @brief Calculate total dipole field of each particle.
533 *
534 * This employs a parallel N-square loop over all particles.
535 * Logically this is equivalent to the potential calculation
536 * in @ref DipolarDirectSum::long_range_energy, which calculates
537 * a naive N-square sum. The difference is summation range,
538 * and the kernel calculates the dipole field rather than the energy.
539 */
540#ifdef ESPRESSO_DIPOLE_FIELD_TRACKING
543 auto const &system = get_system();
544 auto const &box_geo = *system.box_geo;
545 auto const &box_l = box_geo.length();
546 auto const particles = system.cell_structure->local_particles();
547 /* collect particle data */
548 auto [local_particles, all_posmom, reqs, offset_signed] =
549 gather_particle_data(box_geo, particles);
550
551 auto const ncut = get_n_cut(box_geo, n_replicas);
552 auto const with_replicas = (ncut.norm2() > 0);
553 auto const shifts = make_image_shifts(ncut, box_l);
554
555 auto const offset = static_cast<std::size_t>(offset_signed);
556 auto const n_local = local_particles.size();
557 auto const n_total = all_posmom.size();
558
559 auto const prefactor_local = prefactor;
560
561 /* The field sweeps over all j, so every view slice is needed. Unlike the
562 * force/energy kernels there is no local-only computation to overlap with,
563 * so wait for the remote data first, then fill all slices. */
564 boost::mpi::wait_all(reqs.begin(), reqs.end());
565
566 /* Raw pointer to the gathered AoS data; all slices are now populated, and it
567 * outlives the fence. Safe to capture by value in the Kokkos [=] lambda. */
568 auto const *pm = all_posmom.data();
569
570 using execution_space = Kokkos::DefaultExecutionSpace;
571
572 /* Raw pointers so the Kokkos lambdas do not capture std::vector by value. */
573 auto *local_particles_ptr = local_particles.data();
574 auto const *shifts_ptr = shifts.data();
575 auto const n_shifts = shifts.size();
576
577 Kokkos::RangePolicy<execution_space> policy(std::size_t{0}, n_local);
578 Kokkos::parallel_for("dds_dipole_field", policy, [=](std::size_t const i) {
579 auto const gi = offset + i;
580 auto const &pos_i = pm[gi].pos;
581 auto const &m_i = pm[gi].m;
583
584 /* (a) self-image term over shifts[1..] (primary excluded) */
585 for (std::size_t s = 1; s < n_shifts; ++s)
587
588 /* Sweep over all j in [0, n_total), self-primary excluded by splitting
589 * the range into [0, gi) and [gi + 1, n_total). */
590 std::size_t const ranges[2][2] = {{std::size_t{0}, gi}, {gi + 1, n_total}};
591 for (auto const &range : ranges) {
592 auto const range_begin = range[0];
593 auto const range_end = range[1];
594 for (auto j = range_begin; j < range_end; ++j) {
595 auto const &pos_j = pm[j].pos;
596 auto const &m_j = pm[j].m;
597 auto const d0 = with_replicas ? (pos_i - pos_j)
598 : box_geo.get_mi_vector(pos_i, pos_j);
599 for (std::size_t s = 0; s < n_shifts; ++s)
600 u += dipole_field(d0 + shifts_ptr[s], m_j);
601 }
602 }
603 local_particles_ptr[i]->dip_fld() = prefactor_local * u;
604 });
605 Kokkos::fence();
606}
607#endif // ESPRESSO_DIPOLE_FIELD_TRACKING
608
609DipolarDirectSum::DipolarDirectSum(double prefactor, int n_replicas, bool gpu) {
611 m_is_gpu = gpu;
612 this->n_replicas = n_replicas;
613 if (n_replicas < 0) {
614 throw std::domain_error("Parameter 'n_replicas' must be >= 0");
615 }
616}
617
618#endif // ESPRESSO_DIPOLES
Vector implementation and trait types for boost qvm interoperability.
This file contains everything related to the global cell structure / cell system.
auto folded_position(Utils::Vector3d const &pos) const
Calculate coordinates folded to primary simulation box.
constexpr bool periodic(unsigned coord) const
Check periodicity in direction.
void set_prefactor(double new_prefactor)
double prefactor
Magnetostatics prefactor.
A range of particles.
base_type::size_type size() const
constexpr T norm2() const
Definition Vector.hpp:159
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
boost::mpi::communicator comm_cart
The communicator.
static std::vector< Utils::Vector3d > make_image_shifts(Utils::Vector3i const &ncut, Utils::Vector3d const &box_l)
Real-space image shifts n x box_l inside the |ncut| sphere.
static auto gather_particle_data(BoxGeometry const &box_geo, ParticleRange const &particles)
static auto get_n_cut(BoxGeometry const &box_geo, int n_replicas)
__device__ void vector_product(float const *a, float const *b, float *out)
Utils::Vector3d dipole_field(Utils::Vector3d const &d, Utils::Vector3d const &m1)
Dipole field contribution from a dipole m1 at distance d.
PairForce pair_force(Utils::Vector3d const &d, Utils::Vector3d const &m1, Utils::Vector3d const &m2)
Pair force of two interacting dipoles (see dipolar_direct_sum.cpp).
double pair_potential(Utils::Vector3d const &d, Utils::Vector3d const &m1, Utils::Vector3d const &m2)
Pair potential for two interacting dipoles.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeWarningMsg()
auto iall_gatherv(boost::mpi::communicator const &comm, T const *in_values, int in_size, T *out_values, int const *sizes)
void flatten(Range const &v, OutputIterator out)
Flatten a range of ranges.
Definition flatten.hpp:56
Matrix< T, N, M > tensor_product(const Vector< T, N > &x, const Vector< T, M > &y)
double long_range_energy_cpu() const
Calculate the interaction potential.
void dipole_field_at_part_cpu() const
Calculate total dipole field of each particle.
Utils::Vector9d long_range_pressure() const
Calculate the dipolar pressure tensor.
DipolarDirectSum(double prefactor, int n_replicas, bool gpu)
void add_long_range_forces_cpu() const
Calculate and add the interaction forces/torques to the particles.
Force and torque of one pair interaction.
Position and dipole moment of one particle.
void serialize(Archive &ar, long int)
Utils::Vector3d m
Utils::Vector3d pos