ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
elc.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_P3M
25
27
30
31#include "BoxGeometry.hpp"
32#include "Particle.hpp"
34#include "ParticleRange.hpp"
36#include "communication.hpp"
37#include "errorhandling.hpp"
38#include "system/System.hpp"
39
40#include <utils/math/sqr.hpp>
41
42#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
43#include <Kokkos_Core.hpp>
44#endif
45
46#include <boost/mpi/collectives/all_reduce.hpp>
47#include <boost/range/combine.hpp>
48
49#include <algorithm>
50#include <cassert>
51#include <cmath>
52#include <cstddef>
53#include <functional>
54#include <numbers>
55#include <stdexcept>
56#include <utility>
57#include <variant>
58#include <vector>
59
60/** \name Product decomposition data organization
61 * For the cell blocks it is assumed that the lower blocks part is in the
62 * lower half. This has to have positive sign, so that has to be first.
63 */
64/**@{*/
65#define POQESP 0
66#define POQECP 1
67#define POQESM 2
68#define POQECM 3
69
70#define PQESSP 0
71#define PQESCP 1
72#define PQECSP 2
73#define PQECCP 3
74#define PQESSM 4
75#define PQESCM 5
76#define PQECSM 6
77#define PQECCM 7
78/**@}*/
79
80/** ELC axes (x and y directions)*/
81enum class PoQ : int { P, Q };
82/** ELC charge sum/assign protocol: real charges, image charges, or both. */
83enum class ChargeProtocol : int { REAL, IMAGE, BOTH };
84
85/** temporary buffers for product decomposition */
86static std::vector<double> partblk;
87/** collected data from the other cells */
88static double gblcblk[8];
89
90/** structure for caching sin and cos values */
91struct SCCache {
92 double s, c;
93};
94
95/** Cached sin/cos values along the x-axis and y-axis */
96/**@{*/
97static std::vector<SCCache> scxcache;
98static std::vector<SCCache> scycache;
99/**@}*/
100
101/**
102 * @brief Calculate cached sin/cos values for one direction.
103 *
104 * @tparam dir Index of the dimension to consider (e.g. 0 for x ...).
105 *
106 * @param particles Particle to calculate values for
107 * @param n_freq Number of frequencies to calculate per particle
108 * @param u Inverse box length
109 * @return Calculated values.
110 */
111template <std::size_t dir>
112static std::vector<SCCache> calc_sc_cache(ParticleRange const &particles,
113 std::size_t n_freq, double u) {
114 auto constexpr c_2pi = 2. * std::numbers::pi;
115 auto const n_part = particles.size();
116 std::vector<SCCache> ret(n_freq * n_part);
117
118 for (std::size_t freq = 1; freq <= n_freq; freq++) {
119 auto const pref = c_2pi * u * static_cast<double>(freq);
120
121 std::size_t o = (freq - 1) * n_part;
122 for (auto const &p : particles) {
123 auto const arg = pref * p.pos()[dir];
124 ret[o++] = {sin(arg), cos(arg)};
125 }
126 }
127
128 return ret;
129}
130
131static std::pair<std::size_t, std::size_t>
132prepare_sc_cache(ParticleRange const &particles, BoxGeometry const &box_geo,
133 double far_cut) {
134 assert(far_cut >= 0.);
135 auto const n_freq_x =
136 static_cast<std::size_t>(std::ceil(far_cut * box_geo.length()[0]) + 1.);
137 auto const n_freq_y =
138 static_cast<std::size_t>(std::ceil(far_cut * box_geo.length()[1]) + 1.);
139 auto const u_x = box_geo.length_inv()[0];
140 auto const u_y = box_geo.length_inv()[1];
141 scxcache = calc_sc_cache<0>(particles, n_freq_x, u_x);
142 scycache = calc_sc_cache<1>(particles, n_freq_y, u_y);
143 return {n_freq_x, n_freq_y};
144}
145
146/*****************************************************************/
147/* data distribution */
148/*****************************************************************/
149
150static void clear_vec(double *pdc, std::size_t size) {
151 for (std::size_t i = 0; i < size; i++)
152 pdc[i] = 0.;
153}
154
155static void copy_vec(double *pdc_d, double const *pdc_s, std::size_t size) {
156 for (std::size_t i = 0; i < size; i++)
157 pdc_d[i] = pdc_s[i];
158}
159
160static void add_vec(double *pdc_d, double const *pdc_s1, double const *pdc_s2,
161 std::size_t size) {
162 for (std::size_t i = 0; i < size; i++)
163 pdc_d[i] = pdc_s1[i] + pdc_s2[i];
164}
165
166static void addscale_vec(double *pdc_d, double scale, double const *pdc_s1,
167 double const *pdc_s2, std::size_t size) {
168 for (std::size_t i = 0; i < size; i++)
169 pdc_d[i] = scale * pdc_s1[i] + pdc_s2[i];
170}
171
172static void scale_vec(double scale, double *pdc, std::size_t size) {
173 for (std::size_t i = 0; i < size; i++)
174 pdc[i] *= scale;
175}
176
177static double *block(double *p, std::size_t index, std::size_t size) {
178 return &p[index * size];
179}
180
181static void distribute(std::size_t size) {
182 assert(size <= 8);
183 double send_buf[8];
184 copy_vec(send_buf, gblcblk, size);
185 boost::mpi::all_reduce(comm_cart, send_buf, static_cast<int>(size), gblcblk,
186 std::plus<>());
187}
188
189void ElectrostaticLayerCorrection::check_gap(Particle const &p) const {
190 if (p.q() != 0.) {
191 auto const z = p.pos()[2];
192 if (z < 0. or z > elc.box_h) {
193 runtimeErrorMsg() << "Particle " << p.id() << " entered ELC gap "
194 << "region by " << ((z < 0.) ? z : z - elc.box_h);
195 }
196 }
197}
198
199/*****************************************************************/
200/* dipole terms */
201/*****************************************************************/
202
203/** Calculate the dipole force.
204 * See @cite yeh99a.
205 */
206void ElectrostaticLayerCorrection::add_dipole_force() const {
207 constexpr std::size_t size = 3;
208 auto const &system = get_system();
209 auto const &box_geo = *system.box_geo;
210 auto const particles = system.cell_structure->local_particles();
211 auto const pref = prefactor * 4. * std::numbers::pi / box_geo.volume();
212
213 /* for non-neutral systems, this shift gives the background contribution
214 * (rsp. for this shift, the DM of the background is zero) */
215 auto const shift = box_geo.length_half()[2];
216
217 // collect moments
218
219 gblcblk[0] = 0.; // sum q_i (z_i - L/2)
220 gblcblk[1] = 0.; // sum q_i z_i
221 gblcblk[2] = 0.; // sum q_i
222
223 for (auto const &p : particles) {
224 check_gap(p);
225 auto const q = p.q();
226 auto const z = p.pos()[2];
227
228 gblcblk[0] += q * (z - shift);
229 gblcblk[1] += q * z;
230 gblcblk[2] += q;
231
233 if (z < elc.space_layer) {
234 gblcblk[0] += elc.delta_mid_bot * q * (-z - shift);
235 gblcblk[2] += elc.delta_mid_bot * q;
236 }
237 if (z > (elc.box_h - elc.space_layer)) {
238 gblcblk[0] += elc.delta_mid_top * q * (2. * elc.box_h - z - shift);
239 gblcblk[2] += elc.delta_mid_top * q;
240 }
241 }
242 }
243
244 gblcblk[0] *= pref;
245 gblcblk[1] *= pref / elc.box_h * box_geo.length()[2];
246 gblcblk[2] *= pref;
247
248 distribute(size);
249
250 // Yeh + Berkowitz dipole term @cite yeh99a
251 auto field_tot = gblcblk[0];
252
253 // Constant potential contribution
254 if (elc.const_pot) {
255 auto const field_induced = gblcblk[1];
256 auto const field_applied = elc.pot_diff / elc.box_h;
258 }
259
260 for (auto &p : particles) {
261 p.force()[2] -= field_tot * p.q();
262
263 if (!elc.neutralize) {
264 // SUBTRACT the forces of the P3M homogeneous neutralizing background
265 p.force()[2] += gblcblk[2] * p.q() * (p.pos()[2] - shift);
266 }
267 }
268}
269
270/** Calculate the dipole energy.
271 * See @cite yeh99a.
272 */
273double ElectrostaticLayerCorrection::dipole_energy() const {
274 constexpr std::size_t size = 7;
275 auto const &system = get_system();
276 auto const &box_geo = *system.box_geo;
277 auto const particles = system.cell_structure->local_particles();
278 auto const pref = prefactor * 2. * std::numbers::pi / box_geo.volume();
279 auto const lz = box_geo.length()[2];
280 /* for nonneutral systems, this shift gives the background contribution
281 (rsp. for this shift, the DM of the background is zero) */
282 auto const shift = box_geo.length_half()[2];
283
284 // collect moments
285
286 gblcblk[0] = 0.; // sum q_i primary box
287 gblcblk[1] = 0.; // sum q_i boundary layers
288 gblcblk[2] = 0.; // sum q_i (z_i - L/2) primary box
289 gblcblk[3] = 0.; // sum q_i (z_i - L/2) boundary layers
290 gblcblk[4] = 0.; // sum q_i (z_i - L/2)^2 primary box
291 gblcblk[5] = 0.; // sum q_i (z_i - L/2)^2 boundary layers
292 gblcblk[6] = 0.; // sum q_i z_i primary box
293
294 for (auto const &p : particles) {
295 check_gap(p);
296 auto const q = p.q();
297 auto const z = p.pos()[2];
298
299 gblcblk[0] += q;
300 gblcblk[2] += q * (z - shift);
301 gblcblk[4] += q * (Utils::sqr(z - shift));
302 gblcblk[6] += q * z;
303
305 if (z < elc.space_layer) {
306 gblcblk[1] += elc.delta_mid_bot * q;
307 gblcblk[3] += elc.delta_mid_bot * q * (-z - shift);
308 gblcblk[5] += elc.delta_mid_bot * q * (Utils::sqr(-z - shift));
309 }
310 if (z > (elc.box_h - elc.space_layer)) {
311 gblcblk[1] += elc.delta_mid_top * q;
312 gblcblk[3] += elc.delta_mid_top * q * (2. * elc.box_h - z - shift);
313 gblcblk[5] +=
314 elc.delta_mid_top * q * (Utils::sqr(2. * elc.box_h - z - shift));
315 }
316 }
317 }
318
319 distribute(size);
320
321 // Yeh + Berkowitz term @cite yeh99a
322 auto energy = 2. * pref * (Utils::sqr(gblcblk[2]) + gblcblk[2] * gblcblk[3]);
323
324 if (!elc.neutralize) {
325 // SUBTRACT the energy of the P3M homogeneous neutralizing background
326 energy += 2. * pref *
327 (-gblcblk[0] * gblcblk[4] -
328 (.25 - .5 / 3.) * Utils::sqr(gblcblk[0] * lz));
329 }
330
332 if (elc.const_pot) {
333 // zero potential difference contribution
334 energy += pref / elc.box_h * lz * Utils::sqr(gblcblk[6]);
335 // external potential shift contribution
336 energy -= 2. * elc.pot_diff / elc.box_h * gblcblk[6];
337 }
338
339 /* counter the P3M homogeneous background contribution to the
340 boundaries. We never need that, since a homogeneous background
341 spanning the artificial boundary layers is aphysical. */
342 energy +=
343 pref * (-(gblcblk[1] * gblcblk[4] + gblcblk[0] * gblcblk[5]) -
344 (1. - 2. / 3.) * gblcblk[0] * gblcblk[1] * Utils::sqr(lz));
345 }
346
347 return this_node == 0 ? energy : 0.;
348}
349
350/*****************************************************************/
351
352struct ImageSum {
353 double delta;
354 double shift;
355 double lz;
356 double dci; // delta complement inverse
357
358 ImageSum(double delta, double shift, double lz)
359 : delta{delta}, shift{shift}, lz{lz}, dci{1. / (1. - delta)} {}
360
361 /** @brief Image sum from the bottom layer. */
362 double b(double q, double z) const {
363 return q * dci * (z - 2. * delta * lz * dci) - q * dci * shift;
364 }
365
366 /** @brief Image sum from the top layer. */
367 double t(double q, double z) const {
368 return q * dci * (z + 2. * delta * lz * dci) - q * dci * shift;
369 }
370};
371
372double ElectrostaticLayerCorrection::z_energy() const {
373 constexpr std::size_t size = 4;
374 auto const &system = get_system();
375 auto const &box_geo = *system.box_geo;
376 auto const particles = system.cell_structure->local_particles();
377 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
378 auto const pref = prefactor * 2. * std::numbers::pi * xy_area_inv;
379
380 /* for non-neutral systems, this shift gives the background contribution
381 * (rsp. for this shift, the DM of the background is zero) */
382 auto const shift = box_geo.length_half()[2];
383 auto const lz = box_geo.length()[2];
384
386 if (elc.const_pot) {
387 // metallic boundaries
388 clear_vec(gblcblk, size);
389 for (auto const &p : particles) {
390 auto const z = p.pos()[2];
391 auto const q = p.q();
392 gblcblk[0] += q;
393 gblcblk[1] += q * (z - shift);
394 if (z < elc.space_layer) {
395 gblcblk[2] -= elc.delta_mid_bot * q;
396 gblcblk[3] -= elc.delta_mid_bot * q * (-z - shift);
397 }
398 if (z > (elc.box_h - elc.space_layer)) {
399 gblcblk[2] += elc.delta_mid_top * q;
400 gblcblk[3] += elc.delta_mid_top * q * (2. * elc.box_h - z - shift);
401 }
402 }
403 } else {
404 // dielectric boundaries
405 auto const delta = elc.delta_mid_top * elc.delta_mid_bot;
406 auto const fac_delta_mid_bot = elc.delta_mid_bot / (1. - delta);
407 auto const fac_delta_mid_top = elc.delta_mid_top / (1. - delta);
408 auto const fac_delta = delta / (1. - delta);
409 clear_vec(gblcblk, size);
410 auto const h = elc.box_h;
411 ImageSum const image_sum{delta, shift, lz};
412 for (auto const &p : particles) {
413 auto const z = p.pos()[2];
414 auto const q = p.q();
415 gblcblk[0] += q;
416 gblcblk[1] += q * (z - shift);
418 if (z < elc.space_layer) {
419 gblcblk[2] += fac_delta * (elc.delta_mid_bot + 1.) * q;
420 gblcblk[3] +=
421 q * (image_sum.b(elc.delta_mid_bot * delta, -(2. * h + z)) +
422 image_sum.b(delta, -(2. * h - z)));
423 } else {
424 gblcblk[2] += fac_delta_mid_bot * (1. + elc.delta_mid_top) * q;
425 gblcblk[3] += q * (image_sum.b(elc.delta_mid_bot, -z) +
426 image_sum.b(delta, -(2. * h - z)));
427 }
428 if (z > (h - elc.space_layer)) {
429 // note the minus sign here which is required due to |z_i-z_j|
430 gblcblk[2] -= fac_delta * (elc.delta_mid_top + 1.) * q;
431 gblcblk[3] -=
432 q * (image_sum.t(elc.delta_mid_top * delta, 4. * h - z) +
433 image_sum.t(delta, 2. * h + z));
434 } else {
435 // note the minus sign here which is required due to |z_i-z_j|
436 gblcblk[2] -= fac_delta_mid_top * (1. + elc.delta_mid_bot) * q;
437 gblcblk[3] -= q * (image_sum.t(elc.delta_mid_top, 2. * h - z) +
438 image_sum.t(delta, 2. * h + z));
439 }
440 }
441 }
442 }
443 }
444 distribute(size);
445
446 auto const energy = gblcblk[1] * gblcblk[2] - gblcblk[0] * gblcblk[3];
447 return (this_node == 0) ? -pref * energy : 0.;
448}
449
450void ElectrostaticLayerCorrection::add_z_force() const {
451 constexpr std::size_t size = 1;
452 auto const &system = get_system();
453 auto const &box_geo = *system.box_geo;
454 auto const particles = system.cell_structure->local_particles();
455 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
456 auto const pref = prefactor * 2. * std::numbers::pi * xy_area_inv;
457
459 if (elc.const_pot) {
460 // metallic boundaries
461 clear_vec(gblcblk, size);
462 /* just counter the 2 pi |z| contribution stemming from P3M */
463 for (auto const &p : particles) {
464 auto const z = p.pos()[2];
465 auto const q = p.q();
466 if (z < elc.space_layer)
467 gblcblk[0] -= elc.delta_mid_bot * q;
468 if (z > (elc.box_h - elc.space_layer))
469 gblcblk[0] += elc.delta_mid_top * q;
470 }
471 } else {
472 // dielectric boundaries
473 auto const delta = elc.delta_mid_top * elc.delta_mid_bot;
474 auto const fac_delta_mid_bot = elc.delta_mid_bot / (1. - delta);
475 auto const fac_delta_mid_top = elc.delta_mid_top / (1. - delta);
476 auto const fac_delta = delta / (1. - delta);
477 clear_vec(gblcblk, size);
478 for (auto const &p : particles) {
479 auto const z = p.pos()[2];
480 auto const q = p.q();
481 if (z < elc.space_layer) {
482 gblcblk[0] += fac_delta * (elc.delta_mid_bot + 1.) * q;
483 } else {
484 gblcblk[0] += fac_delta_mid_bot * (elc.delta_mid_top + 1.) * q;
485 }
486 if (z > (elc.box_h - elc.space_layer)) {
487 // note the minus sign here which is required due to |z_i-z_j|
488 gblcblk[0] -= fac_delta * (elc.delta_mid_top + 1.) * q;
489 } else {
490 // note the minus sign here which is required due to |z_i-z_j|
491 gblcblk[0] -= fac_delta_mid_top * (elc.delta_mid_bot + 1.) * q;
492 }
493 }
494 }
495
496 gblcblk[0] *= pref;
497
498 distribute(size);
499
500 for (auto &p : particles) {
501 p.force()[2] += gblcblk[0] * p.q();
502 }
503 }
504}
505
506/*****************************************************************/
507/* PoQ exp sum */
508/*****************************************************************/
509
510/** \name q=0 or p=0 per frequency code */
511/**@{*/
512template <PoQ axis>
513void setup_PoQ(elc_data const &elc, double prefactor, std::size_t index,
514 double omega, ParticleRange const &particles,
515 BoxGeometry const &box_geo) {
516 assert(index >= 1);
517 constexpr std::size_t size = 4;
518 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
519 auto const pref_di = prefactor * 4. * std::numbers::pi * xy_area_inv;
520 auto const pref = -pref_di / expm1(omega * box_geo.length()[2]);
521 double lclimgebot[4], lclimgetop[4], lclimge[4];
522 double fac_delta_mid_bot = 1., fac_delta_mid_top = 1., fac_delta = 1.;
523
524 if (elc.dielectric_contrast_on) {
525 auto const delta = elc.delta_mid_top * elc.delta_mid_bot;
526 auto const fac_elc = 1. / (1. - delta * exp(-omega * 2. * elc.box_h));
530 }
531
532 clear_vec(lclimge, size);
533 clear_vec(gblcblk, size);
534 auto const &sc_cache = (axis == PoQ::P) ? scxcache : scycache;
535
536 std::size_t ic = 0;
537 auto const o = (index - 1) * particles.size();
538 for (auto const &p : particles) {
539 auto const z = p.pos()[2];
540 auto const q = p.q();
541 auto e = exp(omega * z);
542
543 partblk[size * ic + POQESM] = q * sc_cache[o + ic].s / e;
544 partblk[size * ic + POQESP] = q * sc_cache[o + ic].s * e;
545 partblk[size * ic + POQECM] = q * sc_cache[o + ic].c / e;
546 partblk[size * ic + POQECP] = q * sc_cache[o + ic].c * e;
547
548 add_vec(gblcblk, gblcblk, block(partblk.data(), ic, size), size);
549
550 if (elc.dielectric_contrast_on) {
551 if (z < elc.space_layer) { // handle the lower case first
552 // negative sign is okay here as the image is located at -z
553
554 e = exp(-omega * z);
555
556 auto const scale = q * elc.delta_mid_bot;
557
558 lclimgebot[POQESM] = sc_cache[o + ic].s / e;
559 lclimgebot[POQESP] = sc_cache[o + ic].s * e;
560 lclimgebot[POQECM] = sc_cache[o + ic].c / e;
561 lclimgebot[POQECP] = sc_cache[o + ic].c * e;
562
563 addscale_vec(gblcblk, scale, lclimgebot, gblcblk, size);
564
565 e = (exp(omega * (-z - 2. * elc.box_h)) * elc.delta_mid_bot +
566 exp(omega * (+z - 2. * elc.box_h))) *
567 fac_delta;
568 } else {
569 e = (exp(-omega * z) +
570 exp(omega * (z - 2. * elc.box_h)) * elc.delta_mid_top) *
572 }
573
574 lclimge[POQESP] += q * sc_cache[o + ic].s * e;
575 lclimge[POQECP] += q * sc_cache[o + ic].c * e;
576
577 if (z > (elc.box_h - elc.space_layer)) { // handle the upper case now
578 e = exp(omega * (2. * elc.box_h - z));
579
580 auto const scale = q * elc.delta_mid_top;
581
582 lclimgetop[POQESM] = sc_cache[o + ic].s / e;
583 lclimgetop[POQESP] = sc_cache[o + ic].s * e;
584 lclimgetop[POQECM] = sc_cache[o + ic].c / e;
585 lclimgetop[POQECP] = sc_cache[o + ic].c * e;
586
587 addscale_vec(gblcblk, scale, lclimgetop, gblcblk, size);
588
589 e = (exp(omega * (+z - 4. * elc.box_h)) * elc.delta_mid_top +
590 exp(omega * (-z - 2. * elc.box_h))) *
591 fac_delta;
592 } else {
593 e = (exp(omega * (+z - 2. * elc.box_h)) +
594 exp(omega * (-z - 2. * elc.box_h)) * elc.delta_mid_bot) *
596 }
597
598 lclimge[POQESM] += q * sc_cache[o + ic].s * e;
599 lclimge[POQECM] += q * sc_cache[o + ic].c * e;
600 }
601
602 ++ic;
603 }
604
605 scale_vec(pref, gblcblk, size);
606
607 if (elc.dielectric_contrast_on) {
608 scale_vec(pref_di, lclimge, size);
610 }
611}
612
613template <PoQ axis> void add_PoQ_force(ParticleRange const &particles) {
614 constexpr auto i = static_cast<int>(axis);
615 constexpr std::size_t size = 4;
616
617 std::size_t ic = 0;
618 for (auto &p : particles) {
619 auto &force = p.force();
620 force[i] += partblk[size * ic + POQESM] * gblcblk[POQECP] -
621 partblk[size * ic + POQECM] * gblcblk[POQESP] +
622 partblk[size * ic + POQESP] * gblcblk[POQECM] -
623 partblk[size * ic + POQECP] * gblcblk[POQESM];
624 force[2] += partblk[size * ic + POQECM] * gblcblk[POQECP] +
625 partblk[size * ic + POQESM] * gblcblk[POQESP] -
626 partblk[size * ic + POQECP] * gblcblk[POQECM] -
627 partblk[size * ic + POQESP] * gblcblk[POQESM];
628 ++ic;
629 }
630}
631
632static double PoQ_energy(double omega, std::size_t n_part) {
633 constexpr std::size_t size = 4;
634
635 auto energy = 0.;
636 for (std::size_t ic = 0; ic < n_part; ic++) {
637 energy += partblk[size * ic + POQECM] * gblcblk[POQECP] +
638 partblk[size * ic + POQESM] * gblcblk[POQESP] +
639 partblk[size * ic + POQECP] * gblcblk[POQECM] +
640 partblk[size * ic + POQESP] * gblcblk[POQESM];
641 }
642
643 return energy / omega;
644}
645/**@}*/
646
647/*****************************************************************/
648/* PQ particle blocks */
649/*****************************************************************/
650
651/** \name p,q <> 0 per frequency code */
652/**@{*/
653static void setup_PQ(elc_data const &elc, double prefactor, std::size_t index_p,
654 std::size_t index_q, double omega,
655 ParticleRange const &particles,
656 BoxGeometry const &box_geo) {
657 assert(index_p >= 1);
658 assert(index_q >= 1);
659 constexpr std::size_t size = 8;
660 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
661 auto const pref_di = prefactor * 8. * std::numbers::pi * xy_area_inv;
662 auto const pref = -pref_di / expm1(omega * box_geo.length()[2]);
663 double lclimgebot[8], lclimgetop[8], lclimge[8];
664 double fac_delta_mid_bot = 1., fac_delta_mid_top = 1., fac_delta = 1.;
665 if (elc.dielectric_contrast_on) {
666 auto const delta = elc.delta_mid_top * elc.delta_mid_bot;
667 auto const fac_elc = 1. / (1. - delta * exp(-omega * 2. * elc.box_h));
671 }
672
673 clear_vec(lclimge, size);
674 clear_vec(gblcblk, size);
675
676 std::size_t ic = 0;
677 auto const ox = (index_p - 1) * particles.size();
678 auto const oy = (index_q - 1) * particles.size();
679 for (auto const &p : particles) {
680 auto const z = p.pos()[2];
681 auto const q = p.q();
682 auto e = exp(omega * z);
683
684 partblk[size * ic + PQESSM] =
685 scxcache[ox + ic].s * scycache[oy + ic].s * q / e;
686 partblk[size * ic + PQESCM] =
687 scxcache[ox + ic].s * scycache[oy + ic].c * q / e;
688 partblk[size * ic + PQECSM] =
689 scxcache[ox + ic].c * scycache[oy + ic].s * q / e;
690 partblk[size * ic + PQECCM] =
691 scxcache[ox + ic].c * scycache[oy + ic].c * q / e;
692
693 partblk[size * ic + PQESSP] =
694 scxcache[ox + ic].s * scycache[oy + ic].s * q * e;
695 partblk[size * ic + PQESCP] =
696 scxcache[ox + ic].s * scycache[oy + ic].c * q * e;
697 partblk[size * ic + PQECSP] =
698 scxcache[ox + ic].c * scycache[oy + ic].s * q * e;
699 partblk[size * ic + PQECCP] =
700 scxcache[ox + ic].c * scycache[oy + ic].c * q * e;
701
702 add_vec(gblcblk, gblcblk, block(partblk.data(), ic, size), size);
703
704 if (elc.dielectric_contrast_on) {
705 if (z < elc.space_layer) { // handle the lower case first
706 // change e to take into account the z position of the images
707
708 e = exp(-omega * z);
709 auto const scale = q * elc.delta_mid_bot;
710
711 lclimgebot[PQESSM] = scxcache[ox + ic].s * scycache[oy + ic].s / e;
712 lclimgebot[PQESCM] = scxcache[ox + ic].s * scycache[oy + ic].c / e;
713 lclimgebot[PQECSM] = scxcache[ox + ic].c * scycache[oy + ic].s / e;
714 lclimgebot[PQECCM] = scxcache[ox + ic].c * scycache[oy + ic].c / e;
715
716 lclimgebot[PQESSP] = scxcache[ox + ic].s * scycache[oy + ic].s * e;
717 lclimgebot[PQESCP] = scxcache[ox + ic].s * scycache[oy + ic].c * e;
718 lclimgebot[PQECSP] = scxcache[ox + ic].c * scycache[oy + ic].s * e;
719 lclimgebot[PQECCP] = scxcache[ox + ic].c * scycache[oy + ic].c * e;
720
721 addscale_vec(gblcblk, scale, lclimgebot, gblcblk, size);
722
723 e = (exp(omega * (-z - 2. * elc.box_h)) * elc.delta_mid_bot +
724 exp(omega * (+z - 2. * elc.box_h))) *
725 fac_delta * q;
726
727 } else {
728
729 e = (exp(-omega * z) +
730 exp(omega * (z - 2. * elc.box_h)) * elc.delta_mid_top) *
732 }
733
734 lclimge[PQESSP] += scxcache[ox + ic].s * scycache[oy + ic].s * e;
735 lclimge[PQESCP] += scxcache[ox + ic].s * scycache[oy + ic].c * e;
736 lclimge[PQECSP] += scxcache[ox + ic].c * scycache[oy + ic].s * e;
737 lclimge[PQECCP] += scxcache[ox + ic].c * scycache[oy + ic].c * e;
738
739 if (z > (elc.box_h - elc.space_layer)) { // handle the upper case now
740
741 e = exp(omega * (2. * elc.box_h - z));
742 auto const scale = q * elc.delta_mid_top;
743
744 lclimgetop[PQESSM] = scxcache[ox + ic].s * scycache[oy + ic].s / e;
745 lclimgetop[PQESCM] = scxcache[ox + ic].s * scycache[oy + ic].c / e;
746 lclimgetop[PQECSM] = scxcache[ox + ic].c * scycache[oy + ic].s / e;
747 lclimgetop[PQECCM] = scxcache[ox + ic].c * scycache[oy + ic].c / e;
748
749 lclimgetop[PQESSP] = scxcache[ox + ic].s * scycache[oy + ic].s * e;
750 lclimgetop[PQESCP] = scxcache[ox + ic].s * scycache[oy + ic].c * e;
751 lclimgetop[PQECSP] = scxcache[ox + ic].c * scycache[oy + ic].s * e;
752 lclimgetop[PQECCP] = scxcache[ox + ic].c * scycache[oy + ic].c * e;
753
754 addscale_vec(gblcblk, scale, lclimgetop, gblcblk, size);
755
756 e = (exp(omega * (+z - 4. * elc.box_h)) * elc.delta_mid_top +
757 exp(omega * (-z - 2. * elc.box_h))) *
758 fac_delta * q;
759
760 } else {
761
762 e = (exp(omega * (+z - 2. * elc.box_h)) +
763 exp(omega * (-z - 2. * elc.box_h)) * elc.delta_mid_bot) *
765 }
766
767 lclimge[PQESSM] += scxcache[ox + ic].s * scycache[oy + ic].s * e;
768 lclimge[PQESCM] += scxcache[ox + ic].s * scycache[oy + ic].c * e;
769 lclimge[PQECSM] += scxcache[ox + ic].c * scycache[oy + ic].s * e;
770 lclimge[PQECCM] += scxcache[ox + ic].c * scycache[oy + ic].c * e;
771 }
772
773 ic++;
774 }
775
776 scale_vec(pref, gblcblk, size);
777 if (elc.dielectric_contrast_on) {
778 scale_vec(pref_di, lclimge, size);
780 }
781}
782
783static void add_PQ_force(std::size_t index_p, std::size_t index_q, double omega,
784 ParticleRange const &particles,
785 BoxGeometry const &box_geo) {
786 auto constexpr c_2pi = 2. * std::numbers::pi;
787 auto const pref_x =
788 c_2pi * box_geo.length_inv()[0] * static_cast<double>(index_p) / omega;
789 auto const pref_y =
790 c_2pi * box_geo.length_inv()[1] * static_cast<double>(index_q) / omega;
791 constexpr std::size_t size = 8;
792
793 std::size_t ic = 0;
794 for (auto &p : particles) {
795 auto &force = p.force();
796 force[0] += pref_x * (partblk[size * ic + PQESCM] * gblcblk[PQECCP] +
797 partblk[size * ic + PQESSM] * gblcblk[PQECSP] -
798 partblk[size * ic + PQECCM] * gblcblk[PQESCP] -
799 partblk[size * ic + PQECSM] * gblcblk[PQESSP] +
800 partblk[size * ic + PQESCP] * gblcblk[PQECCM] +
801 partblk[size * ic + PQESSP] * gblcblk[PQECSM] -
802 partblk[size * ic + PQECCP] * gblcblk[PQESCM] -
803 partblk[size * ic + PQECSP] * gblcblk[PQESSM]);
804 force[1] += pref_y * (partblk[size * ic + PQECSM] * gblcblk[PQECCP] +
805 partblk[size * ic + PQESSM] * gblcblk[PQESCP] -
806 partblk[size * ic + PQECCM] * gblcblk[PQECSP] -
807 partblk[size * ic + PQESCM] * gblcblk[PQESSP] +
808 partblk[size * ic + PQECSP] * gblcblk[PQECCM] +
809 partblk[size * ic + PQESSP] * gblcblk[PQESCM] -
810 partblk[size * ic + PQECCP] * gblcblk[PQECSM] -
811 partblk[size * ic + PQESCP] * gblcblk[PQESSM]);
812 force[2] += (partblk[size * ic + PQECCM] * gblcblk[PQECCP] +
813 partblk[size * ic + PQECSM] * gblcblk[PQECSP] +
814 partblk[size * ic + PQESCM] * gblcblk[PQESCP] +
815 partblk[size * ic + PQESSM] * gblcblk[PQESSP] -
816 partblk[size * ic + PQECCP] * gblcblk[PQECCM] -
817 partblk[size * ic + PQECSP] * gblcblk[PQECSM] -
818 partblk[size * ic + PQESCP] * gblcblk[PQESCM] -
819 partblk[size * ic + PQESSP] * gblcblk[PQESSM]);
820 ic++;
821 }
822}
823
824static double PQ_energy(double omega, std::size_t n_part) {
825 constexpr std::size_t size = 8;
826
827 auto energy = 0.;
828 for (std::size_t ic = 0; ic < n_part; ic++) {
829 energy += partblk[size * ic + PQECCM] * gblcblk[PQECCP] +
830 partblk[size * ic + PQECSM] * gblcblk[PQECSP] +
831 partblk[size * ic + PQESCM] * gblcblk[PQESCP] +
832 partblk[size * ic + PQESSM] * gblcblk[PQESSP] +
833 partblk[size * ic + PQECCP] * gblcblk[PQECCM] +
834 partblk[size * ic + PQECSP] * gblcblk[PQECSM] +
835 partblk[size * ic + PQESCP] * gblcblk[PQESCM] +
836 partblk[size * ic + PQESSP] * gblcblk[PQESSM];
837 }
838 return energy / omega;
839}
840/**@}*/
841
842void ElectrostaticLayerCorrection::add_force() const {
843 auto constexpr c_2pi = 2. * std::numbers::pi;
844 auto const &system = get_system();
845 auto const &box_geo = *system.box_geo;
846 auto const particles = system.cell_structure->local_particles();
847 auto const n_freqs = prepare_sc_cache(particles, box_geo, elc.far_cut);
848 auto const n_scxcache = std::get<0>(n_freqs);
849 auto const n_scycache = std::get<1>(n_freqs);
850 partblk.resize(particles.size() * 8);
851
852 add_dipole_force();
853 add_z_force();
854
855 /* the second condition is just for the case of numerical accident */
856 for (std::size_t p = 1;
857 box_geo.length_inv()[0] * static_cast<double>(p - 1) < elc.far_cut &&
858 p <= n_scxcache;
859 p++) {
860 auto const omega = c_2pi * box_geo.length_inv()[0] * static_cast<double>(p);
861 setup_PoQ<PoQ::P>(elc, prefactor, p, omega, particles, box_geo);
862 distribute(4);
863 add_PoQ_force<PoQ::P>(particles);
864 }
865
866 for (std::size_t q = 1;
867 box_geo.length_inv()[1] * static_cast<double>(q - 1) < elc.far_cut &&
868 q <= n_scycache;
869 q++) {
870 auto const omega = c_2pi * box_geo.length_inv()[1] * static_cast<double>(q);
871 setup_PoQ<PoQ::Q>(elc, prefactor, q, omega, particles, box_geo);
872 distribute(4);
873 add_PoQ_force<PoQ::Q>(particles);
874 }
875
876 for (std::size_t p = 1;
877 box_geo.length_inv()[0] * static_cast<double>(p - 1) < elc.far_cut &&
878 p <= n_scxcache;
879 p++) {
880 for (std::size_t q = 1;
881 Utils::sqr(box_geo.length_inv()[0] * static_cast<double>(p - 1)) +
882 Utils::sqr(box_geo.length_inv()[1] *
883 static_cast<double>(q - 1)) <
884 elc.far_cut2 &&
885 q <= n_scycache;
886 q++) {
887 auto const omega =
888 c_2pi *
889 sqrt(Utils::sqr(box_geo.length_inv()[0] * static_cast<double>(p)) +
890 Utils::sqr(box_geo.length_inv()[1] * static_cast<double>(q)));
891 setup_PQ(elc, prefactor, p, q, omega, particles, box_geo);
892 distribute(8);
893 add_PQ_force(p, q, omega, particles, box_geo);
894 }
895 }
896}
897
898double ElectrostaticLayerCorrection::calc_energy() const {
899 auto constexpr c_2pi = 2. * std::numbers::pi;
900 auto const &system = get_system();
901 auto const &box_geo = *system.box_geo;
902 auto const particles = system.cell_structure->local_particles();
903 auto energy = dipole_energy() + z_energy();
904 auto const n_freqs = prepare_sc_cache(particles, box_geo, elc.far_cut);
905 auto const n_scxcache = std::get<0>(n_freqs);
906 auto const n_scycache = std::get<1>(n_freqs);
907
908 auto const n_localpart = particles.size();
909 partblk.resize(n_localpart * 8);
910
911 /* the second condition is just for the case of numerical accident */
912 for (std::size_t p = 1;
913 box_geo.length_inv()[0] * static_cast<double>(p - 1) < elc.far_cut &&
914 p <= n_scxcache;
915 p++) {
916 auto const omega = c_2pi * box_geo.length_inv()[0] * static_cast<double>(p);
917 setup_PoQ<PoQ::P>(elc, prefactor, p, omega, particles, box_geo);
918 distribute(4);
919 energy += PoQ_energy(omega, n_localpart);
920 }
921
922 for (std::size_t q = 1;
923 box_geo.length_inv()[1] * static_cast<double>(q - 1) < elc.far_cut &&
924 q <= n_scycache;
925 q++) {
926 auto const omega = c_2pi * box_geo.length_inv()[1] * static_cast<double>(q);
927 setup_PoQ<PoQ::Q>(elc, prefactor, q, omega, particles, box_geo);
928 distribute(4);
929 energy += PoQ_energy(omega, n_localpart);
930 }
931
932 for (std::size_t p = 1;
933 box_geo.length_inv()[0] * static_cast<double>(p - 1) < elc.far_cut &&
934 p <= n_scxcache;
935 p++) {
936 for (std::size_t q = 1;
937 Utils::sqr(box_geo.length_inv()[0] * static_cast<double>(p - 1)) +
938 Utils::sqr(box_geo.length_inv()[1] *
939 static_cast<double>(q - 1)) <
940 elc.far_cut2 &&
941 q <= n_scycache;
942 q++) {
943 auto const omega =
944 c_2pi *
945 sqrt(Utils::sqr(box_geo.length_inv()[0] * static_cast<double>(p)) +
946 Utils::sqr(box_geo.length_inv()[1] * static_cast<double>(q)));
947 setup_PQ(elc, prefactor, p, q, omega, particles, box_geo);
948 distribute(8);
949 energy += PQ_energy(omega, n_localpart);
950 }
951 }
952 /* we count both i<->j and j<->i, so return just half of it */
953 return 0.5 * energy;
954}
955
956double ElectrostaticLayerCorrection::tune_far_cut() const {
957 // Largest reasonable cutoff for far formula
958 auto constexpr maximal_far_cut = 50.;
959 auto const &box_geo = *get_system().box_geo;
960 auto const box_l_x_inv = box_geo.length_inv()[0];
961 auto const box_l_y_inv = box_geo.length_inv()[1];
962 auto const min_inv_boxl = std::min(box_l_x_inv, box_l_y_inv);
963 auto const box_l_z = box_geo.length()[2];
964 // adjust lz according to dielectric layer method
965 auto const lz =
967
969 double err;
970 do {
971 auto const pref = 2. * std::numbers::pi * tuned_far_cut;
972 auto const sum = pref + 2. * (box_l_x_inv + box_l_y_inv);
973 auto const den = -expm1(-pref * lz);
974 auto const num1 = exp(pref * (elc.box_h - lz));
975 auto const num2 = exp(-pref * (elc.box_h + lz));
976
977 err = 0.5 / den *
978 (num1 * (sum + 1. / (lz - elc.box_h)) / (lz - elc.box_h) +
979 num2 * (sum + 1. / (lz + elc.box_h)) / (lz + elc.box_h));
980
984 throw std::runtime_error("ELC tuning failed: maxPWerror too small");
985 }
987}
988
989static auto calc_total_charge(CellStructure const &cell_structure) {
990 auto local_q = 0.;
991 for (auto const &p : cell_structure.local_particles()) {
992 local_q += p.q();
993 }
994 return boost::mpi::all_reduce(comm_cart, local_q, std::plus<>());
995}
996
997void ElectrostaticLayerCorrection::sanity_checks_periodicity() const {
998 auto const &box_geo = *get_system().box_geo;
999 if (!box_geo.periodic(0) || !box_geo.periodic(1) || !box_geo.periodic(2)) {
1000 throw std::runtime_error("ELC: requires periodicity (True, True, True)");
1001 }
1002}
1003
1004void ElectrostaticLayerCorrection::sanity_checks_dielectric_contrasts() const {
1006 auto const &cell_structure = *get_system().cell_structure;
1007 auto const precision_threshold = std::sqrt(round_error_prec);
1008 auto const total_charge = std::abs(calc_total_charge(cell_structure));
1010 if (elc.const_pot) {
1011 // Disable this line to make ELC work again with non-neutral systems
1012 // and metallic boundaries
1013 throw std::runtime_error("ELC does not currently support non-neutral "
1014 "systems with a dielectric contrast.");
1015 }
1016 // ELC with non-neutral systems and no fully metallic boundaries
1017 // does not work
1018 throw std::runtime_error("ELC does not work for non-neutral systems and "
1019 "non-metallic dielectric contrast.");
1020 }
1021 }
1022}
1023
1024void ElectrostaticLayerCorrection::adapt_solver() {
1025 std::visit(
1026 [this](auto &solver) {
1027 set_prefactor(solver->prefactor);
1028 solver->adapt_epsilon_elc();
1029 assert(solver->p3m_params.epsilon == P3M_EPSILON_METALLIC);
1030 },
1031 base_solver);
1032}
1033
1034void ElectrostaticLayerCorrection::recalc_box_h() {
1035 m_box_geo = get_system().box_geo.get();
1036 auto const box_z = m_box_geo->length()[2];
1037 auto const new_box_h = box_z - elc.gap_size;
1038 if (new_box_h < 0.) {
1039 throw std::runtime_error("ELC gap size (" + std::to_string(elc.gap_size) +
1040 ") larger than box length in z-direction (" +
1041 std::to_string(box_z) + ")");
1042 }
1044}
1045
1046void ElectrostaticLayerCorrection::recalc_space_layer() {
1048 auto const p3m_r_cut = std::visit(
1049 [](auto &solver) { return solver->p3m_params.r_cut; }, base_solver);
1050 // recalculate the space layer size:
1051 // 1. set the space_layer to be 1/3 of the gap size, so that box = layer
1052 elc.space_layer = (1. / 3.) * elc.gap_size;
1053 // 2. but make sure we don't overlap with the near-field formula
1054 auto const free_space = elc.gap_size - p3m_r_cut;
1055 // 3. and make sure the space layer is not bigger than half the actual
1056 // simulation box, to avoid overlaps
1057 auto const half_box_h = elc.box_h / 2.;
1058 auto const max_space_layer = std::min(free_space, half_box_h);
1060 if (max_space_layer <= 0.) {
1061 throw std::runtime_error("P3M real-space cutoff too large for ELC w/ "
1062 "dielectric contrast");
1063 }
1065 }
1067 }
1068}
1069
1070elc_data::elc_data(double maxPWerror, double gap_size, double far_cut,
1071 bool neutralize, double delta_top, double delta_bot,
1072 bool with_const_pot, double potential_diff)
1073 : maxPWerror{maxPWerror}, gap_size{gap_size}, box_h{-1.}, far_cut{far_cut},
1074 far_cut2{-1.}, far_calculated{far_cut == -1.},
1075 dielectric_contrast_on{delta_top != 0. or delta_bot != 0.},
1076 const_pot{with_const_pot and dielectric_contrast_on},
1077 neutralize{neutralize and !dielectric_contrast_on},
1078 delta_mid_top{std::clamp(delta_top, -1., +1.)},
1079 delta_mid_bot{std::clamp(delta_bot, -1., +1.)},
1080 pot_diff{(with_const_pot) ? potential_diff : 0.},
1081 // initial setup of parameters, may change later when P3M is finally tuned
1082 // set the space_layer to be 1/3 of the gap size, so that box = layer
1083 space_layer{(dielectric_contrast_on) ? gap_size / 3. : 0.},
1084 space_box{gap_size - ((dielectric_contrast_on) ? 2. * space_layer : 0.)} {
1085
1086 auto const delta_range = 1. + std::sqrt(round_error_prec);
1087 if (far_cut <= 0. and not far_calculated) {
1088 throw std::domain_error("Parameter 'far_cut' must be > 0");
1089 }
1090 if (maxPWerror <= 0.) {
1091 throw std::domain_error("Parameter 'maxPWerror' must be > 0");
1092 }
1093 if (gap_size <= 0.) {
1094 throw std::domain_error("Parameter 'gap_size' must be > 0");
1095 }
1097 throw std::invalid_argument(
1098 "Parameter 'const_pot' must be True when 'pot_diff' is non-zero");
1099 }
1101 throw std::domain_error(
1102 "Parameter 'delta_mid_top' must be >= -1 and <= +1");
1103 }
1105 throw std::domain_error(
1106 "Parameter 'delta_mid_bot' must be >= -1 and <= +1");
1107 }
1108 /* Dielectric contrasts: the deltas should be either both -1 or both +1 when
1109 * no constant potential difference is applied. The case of two non-metallic
1110 * parallel boundaries can only be treated with a constant potential. */
1112 (std::fabs(1. - delta_mid_top * delta_mid_bot) < round_error_prec)) {
1113 throw std::domain_error("ELC with two parallel metallic boundaries "
1114 "requires the const_pot option");
1115 }
1116}
1117
1119 elc_data &&parameters, BaseSolver &&solver)
1120 : elc{parameters}, base_solver{solver} {
1121 adapt_solver();
1122}
1123
1124template <ChargeProtocol protocol, typename combined_ranges>
1125void charge_assign(elc_data const &elc, CoulombP3M &solver,
1127
1128 solver.prepare_fft_mesh(protocol == ChargeProtocol::BOTH or
1129 protocol == ChargeProtocol::IMAGE);
1130
1131#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
1132 // multi-threading -> cache sizes must be equal to the number of particles
1133 auto constexpr include_neutral_particles = true;
1134#else
1135 auto constexpr include_neutral_particles = false;
1136#endif
1137
1138 for (auto zipped : p_q_pos_range) {
1139 auto const p_q = boost::get<0>(zipped);
1140 auto const &p_pos = boost::get<1>(zipped);
1141 if (include_neutral_particles or p_q != 0.) {
1142 // assign real charges
1143 if (protocol == ChargeProtocol::BOTH or
1144 protocol == ChargeProtocol::REAL) {
1145 solver.assign_charge(p_q, p_pos, false);
1146 }
1147 // assign image charges
1148 if (protocol == ChargeProtocol::BOTH or
1149 protocol == ChargeProtocol::IMAGE) {
1150 if (p_pos[2] < elc.space_layer) {
1151 auto const q_eff = elc.delta_mid_bot * p_q;
1152 solver.assign_charge(q_eff, {p_pos[0], p_pos[1], -p_pos[2]}, true);
1153 }
1154 if (p_pos[2] > (elc.box_h - elc.space_layer)) {
1155 auto const q_eff = elc.delta_mid_top * p_q;
1156 solver.assign_charge(
1157 q_eff, {p_pos[0], p_pos[1], 2. * elc.box_h - p_pos[2]}, true);
1158 }
1159 }
1160 }
1161 }
1162}
1163
1164template <ChargeProtocol protocol, typename combined_range>
1165void modify_p3m_sums(elc_data const &elc, CoulombP3M &solver,
1167
1168 auto local_n = std::size_t{0u};
1169 auto local_q2 = 0.0;
1170 auto local_q = 0.0;
1171 for (auto zipped : p_q_pos_range) {
1172 auto const p_q = boost::get<0>(zipped);
1173 auto const &p_pos = boost::get<1>(zipped);
1174 if (p_q != 0.) {
1175 auto const p_z = p_pos[2];
1176
1177 if (protocol == ChargeProtocol::BOTH or
1178 protocol == ChargeProtocol::REAL) {
1179 local_n++;
1181 local_q += p_q;
1182 }
1183
1184 if (protocol == ChargeProtocol::BOTH or
1185 protocol == ChargeProtocol::IMAGE) {
1186 if (p_z < elc.space_layer) {
1187 local_n++;
1189 local_q += elc.delta_mid_bot * p_q;
1190 }
1191
1192 if (p_z > (elc.box_h - elc.space_layer)) {
1193 local_n++;
1195 local_q += elc.delta_mid_top * p_q;
1196 }
1197 }
1198 }
1199 }
1200
1201 auto global_n = std::size_t{0u};
1202 auto global_q2 = 0.;
1203 auto global_q = 0.;
1204 boost::mpi::all_reduce(comm_cart, local_n, global_n, std::plus<>());
1205 boost::mpi::all_reduce(comm_cart, local_q2, global_q2, std::plus<>());
1206 boost::mpi::all_reduce(comm_cart, local_q, global_q, std::plus<>());
1208}
1209
1211 auto const &system = get_system();
1212 auto const energy = std::visit(
1213 [this, &system](auto const &solver_ptr) {
1214 auto &solver = *solver_ptr;
1215 auto const particles = system.cell_structure->local_particles();
1216 auto const &box_geo = *system.box_geo;
1217
1220 auto p_q_pos_range = boost::combine(p_q_range, p_pos_range);
1221
1222 // assign the original charges (they may not have been assigned yet)
1223 solver.charge_assign();
1224
1226 return solver.long_range_energy();
1227 }
1228
1229 auto energy = 0.;
1230 energy += 0.5 * solver.long_range_energy();
1231 energy +=
1232 0.5 * elc.dielectric_layers_self_energy(solver, box_geo, particles);
1233
1234 // assign both original and image charges
1237 energy += 0.5 * solver.long_range_energy();
1238
1239 // assign only the image charges now
1242 energy -= 0.5 * solver.long_range_energy();
1243
1244 // restore modified sums
1246
1247 return energy;
1248 },
1249 base_solver);
1250 return energy + calc_energy();
1251}
1252
1254 auto const &system = get_system();
1255 std::visit(
1256 [this, &system](auto const &solver_ptr) {
1257 auto const particles = system.cell_structure->local_particles();
1258 auto &solver = *solver_ptr;
1261 auto p_q_pos_range = boost::combine(p_q_range, p_pos_range);
1263 auto const &box_geo = *system.box_geo;
1266 elc.dielectric_layers_self_forces(solver, box_geo, particles);
1267 } else {
1268 solver.charge_assign();
1269 }
1270 solver.add_long_range_forces();
1273 }
1274 },
1275 base_solver);
1276 add_force();
1277}
1278
1279#endif // ESPRESSO_P3M
Utils::Vector3d const & length() const
Box length.
Utils::Vector3d const & length_inv() const
Inverse box length.
Describes a cell structure / cell system.
ParticleRange local_particles() const
A range of particles.
base_type::size_type size() const
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
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
T image_sum(InputIterator begin, InputIterator end, InputIterator it, bool with_replicas, Utils::Vector3i const &ncut, BoxGeometry const &box_geo, T init, F f)
Sum over all pairs with periodic images.
static void addscale_vec(double *pdc_d, double scale, double const *pdc_s1, double const *pdc_s2, std::size_t size)
Definition elc.cpp:166
#define PQESSM
Definition elc.cpp:74
#define POQESP
Definition elc.cpp:65
static std::pair< std::size_t, std::size_t > prepare_sc_cache(ParticleRange const &particles, BoxGeometry const &box_geo, double far_cut)
Definition elc.cpp:132
#define PQESCP
Definition elc.cpp:71
static void add_PQ_force(std::size_t index_p, std::size_t index_q, double omega, ParticleRange const &particles, BoxGeometry const &box_geo)
Definition elc.cpp:783
static auto calc_total_charge(CellStructure const &cell_structure)
Definition elc.cpp:989
static std::vector< double > partblk
temporary buffers for product decomposition
Definition elc.cpp:86
#define PQESCM
Definition elc.cpp:75
static void clear_vec(double *pdc, std::size_t size)
Definition elc.cpp:150
static double * block(double *p, std::size_t index, std::size_t size)
Definition elc.cpp:177
void setup_PoQ(elc_data const &elc, double prefactor, std::size_t index, double omega, ParticleRange const &particles, BoxGeometry const &box_geo)
Definition elc.cpp:513
static void distribute(std::size_t size)
Definition elc.cpp:181
void modify_p3m_sums(elc_data const &elc, CoulombP3M &solver, combined_range const &p_q_pos_range)
Definition elc.cpp:1165
static double PoQ_energy(double omega, std::size_t n_part)
Definition elc.cpp:632
static std::vector< SCCache > scxcache
Cached sin/cos values along the x-axis and y-axis.
Definition elc.cpp:97
#define PQECCP
Definition elc.cpp:73
static double PQ_energy(double omega, std::size_t n_part)
Definition elc.cpp:824
void charge_assign(elc_data const &elc, CoulombP3M &solver, combined_ranges const &p_q_pos_range)
Definition elc.cpp:1125
#define POQECP
Definition elc.cpp:66
static std::vector< SCCache > scycache
Definition elc.cpp:98
static void setup_PQ(elc_data const &elc, double prefactor, std::size_t index_p, std::size_t index_q, double omega, ParticleRange const &particles, BoxGeometry const &box_geo)
Definition elc.cpp:653
static void copy_vec(double *pdc_d, double const *pdc_s, std::size_t size)
Definition elc.cpp:155
#define PQECCM
Definition elc.cpp:77
#define POQECM
Definition elc.cpp:68
static void add_vec(double *pdc_d, double const *pdc_s1, double const *pdc_s2, std::size_t size)
Definition elc.cpp:160
#define POQESM
Definition elc.cpp:67
static void scale_vec(double scale, double *pdc, std::size_t size)
Definition elc.cpp:172
void add_PoQ_force(ParticleRange const &particles)
Definition elc.cpp:613
ChargeProtocol
ELC charge sum/assign protocol: real charges, image charges, or both.
Definition elc.cpp:83
static std::vector< SCCache > calc_sc_cache(ParticleRange const &particles, std::size_t n_freq, double u)
Calculate cached sin/cos values for one direction.
Definition elc.cpp:112
#define PQESSP
Definition elc.cpp:70
#define PQECSP
Definition elc.cpp:72
static double gblcblk[8]
collected data from the other cells
Definition elc.cpp:88
PoQ
ELC axes (x and y directions)
Definition elc.cpp:81
#define PQECSM
Definition elc.cpp:76
ELC algorithm for long-range Coulomb interactions.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
ParticleRange particles(std::span< Cell *const > cells)
auto charge_range(ParticleRange const &particles)
auto pos_range(ParticleRange const &particles)
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Definition sqr.hpp:28
auto sqrt(Vector< T, N > const &a)
Definition Vector.hpp:357
STL namespace.
auto constexpr P3M_EPSILON_METALLIC
This value indicates metallic boundary conditions.
P3M algorithm for long-range Coulomb interaction.
P3M solver.
Definition p3m.hpp:55
virtual void prepare_fft_mesh(bool reset_weights)=0
virtual void count_charged_particles_elc(std::size_t, double, double)=0
virtual void assign_charge(double q, Utils::Vector3d const &real_pos, bool skip_cache)=0
Assign a single charge into the current charge grid.
void add_long_range_forces() const
Accumulate long-range electrostatic forces with corrections.
Definition elc.cpp:1253
std::variant< std::shared_ptr< CoulombP3M > > BaseSolver
Definition elc.hpp:194
BaseSolver base_solver
Electrostatics solver that is adapted.
Definition elc.hpp:200
ElectrostaticLayerCorrection(elc_data &&parameters, BaseSolver &&solver)
Definition elc.cpp:1118
double long_range_energy() const
Calculate long-range electrostatic energy with corrections.
Definition elc.cpp:1210
BoxGeometry * m_box_geo
Definition elc.hpp:197
double b(double q, double z) const
Image sum from the bottom layer.
Definition elc.cpp:362
double dci
Definition elc.cpp:356
double shift
Definition elc.cpp:354
ImageSum(double delta, double shift, double lz)
Definition elc.cpp:358
double t(double q, double z) const
Image sum from the top layer.
Definition elc.cpp:367
double lz
Definition elc.cpp:355
double delta
Definition elc.cpp:353
Struct holding all information for one particle.
Definition Particle.hpp:435
auto const & q() const
Definition Particle.hpp:578
auto const & pos() const
Definition Particle.hpp:471
auto const & force() const
Definition Particle.hpp:475
auto const & id() const
Definition Particle.hpp:454
structure for caching sin and cos values
Definition elc.cpp:91
double c
Definition elc.cpp:92
double s
Definition elc.cpp:92
Parameters for the ELC method.
Definition elc.hpp:64
double dielectric_layers_self_energy(CoulombP3M const &p3m, BoxGeometry const &box_geo, ParticleRange const &particles) const
self energies of top and bottom layers with their virtual images
Definition elc.hpp:164
double maxPWerror
Maximal allowed pairwise error for the potential and force.
Definition elc.hpp:73
double pot_diff
Constant potential difference.
Definition elc.hpp:112
double box_h
Up to where particles can be found.
Definition elc.hpp:79
bool dielectric_contrast_on
Flag whether there is any dielectric contrast in the system.
Definition elc.hpp:95
elc_data(double maxPWerror, double gap_size, double far_cut, bool neutralize, double delta_top, double delta_bot, bool const_pot, double pot_diff)
Definition elc.cpp:1070
double space_box
The space that is finally left.
Definition elc.hpp:117
bool neutralize
Flag whether the box is neutralized by a homogeneous background.
Definition elc.hpp:105
double far_cut
Cutoff of the exponential sum.
Definition elc.hpp:85
double space_layer
Layer around the dielectric contrast in which we trick around.
Definition elc.hpp:115
bool far_calculated
Flag whether far_cut was set by the user, or calculated by ESPResSo.
Definition elc.hpp:92
double gap_size
Size of the empty gap.
Definition elc.hpp:77
double delta_mid_bot
dielectric contrast in the lower part of the simulation cell.
Definition elc.hpp:110
void dielectric_layers_self_forces(CoulombP3M const &p3m, BoxGeometry const &box_geo, ParticleRange const &particles) const
forces of particles in border layers with themselves
Definition elc.hpp:179
bool const_pot
Flag whether a constant potential difference is applied.
Definition elc.hpp:97
double far_cut2
Squared value of far_cut.
Definition elc.hpp:87
double delta_mid_top
dielectric contrast in the upper part of the simulation cell.
Definition elc.hpp:108