37#include "communication.hpp"
39#include "system/System.hpp"
43#include <Kokkos_Core.hpp>
45#include <boost/mpi/collectives/all_reduce.hpp>
109template <std::
size_t dir>
111 std::size_t
n_freq,
double u) {
112 auto constexpr c_2pi = 2. * std::numbers::pi;
113 auto const n_part = particles.
size();
114 std::vector<SCCache>
ret(
n_freq * n_part);
117 auto const pref =
c_2pi *
u *
static_cast<double>(
freq);
119 std::size_t
o = (
freq - 1) * n_part;
120 for (
auto const &p : particles) {
121 auto const arg = pref * p.pos()[dir];
129static std::pair<std::size_t, std::size_t>
134 static_cast<std::size_t
>(std::ceil(far_cut * box_geo.
length()[0]) + 1.);
136 static_cast<std::size_t
>(std::ceil(far_cut * box_geo.
length()[1]) + 1.);
149 std::ranges::fill_n(
pdc,
static_cast<std::ptrdiff_t
>(size), 0.);
154 for (std::size_t i = 0; i < size; i++)
159 double const *
pdc_s2, std::size_t size) {
160 for (std::size_t i = 0; i < size; i++)
165 for (std::size_t i = 0; i < size; i++)
169static double *
block(
double *p, std::size_t index, std::size_t size) {
170 return &p[index * size];
176 std::ranges::copy_n(
gblcblk,
static_cast<std::ptrdiff_t
>(size), send_buf);
177 boost::mpi::all_reduce(
comm_cart, send_buf,
static_cast<int>(size),
gblcblk,
181void ElectrostaticLayerCorrection::check_gap(
Particle const &p)
const {
183 auto const z = p.
pos()[2];
186 <<
"region by " << ((z < 0.) ? z : z -
elc.box_h);
198void ElectrostaticLayerCorrection::add_dipole_force()
const {
199 constexpr std::size_t size = 3;
201 auto const &box_geo = *
system.box_geo;
203 auto const pref =
prefactor * 4. * std::numbers::pi / box_geo.volume();
207 auto const shift = box_geo.length_half()[2];
219 auto const q = p.
q();
220 auto const z = p.
pos()[2];
267double ElectrostaticLayerCorrection::dipole_energy()
const {
268 constexpr std::size_t size = 7;
270 auto const &box_geo = *
system.box_geo;
272 auto const pref =
prefactor * 2. * std::numbers::pi / box_geo.volume();
273 auto const lz = box_geo.length()[2];
276 auto const shift = box_geo.length_half()[2];
290 auto const q = p.
q();
291 auto const z = p.
pos()[2];
320 energy += 2. * pref *
357 double b(
double q,
double z)
const {
362 double t(
double q,
double z)
const {
367double ElectrostaticLayerCorrection::z_energy()
const {
368 constexpr std::size_t size = 4;
370 auto const &box_geo = *
system.box_geo;
372 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
377 auto const shift = box_geo.length_half()[2];
384 auto const z = p.
pos()[2];
385 auto const q = p.
q();
402 auto const fac_delta = delta / (1. - delta);
407 auto const z = p.
pos()[2];
408 auto const q = p.
q();
441 return (
this_node == 0) ? -pref * energy : 0.;
444void ElectrostaticLayerCorrection::add_z_force()
const {
445 constexpr std::size_t size = 1;
447 auto const &box_geo = *
system.box_geo;
449 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
458 auto const z = p.
pos()[2];
459 auto const q = p.
q();
470 auto const fac_delta = delta / (1. - delta);
473 auto const z = p.
pos()[2];
474 auto const q = p.
q();
511 constexpr std::size_t size = 4;
531 auto const o = (index - 1) * particles.
size();
532 for (
auto const &p : particles) {
533 auto const z = p.
pos()[2];
534 auto const q = p.
q();
535 auto e =
exp(omega * z);
560 exp(omega * (+z - 2. * elc.
box_h))) *
563 e = (
exp(-omega * z) +
584 exp(omega * (-z - 2. * elc.
box_h))) *
587 e = (
exp(omega * (+z - 2. * elc.
box_h)) +
608 constexpr auto i =
static_cast<int>(axis);
609 constexpr std::size_t size = 4;
612 for (
auto &p : particles) {
613 auto &force = p.
force();
627 constexpr std::size_t size = 4;
630 for (std::size_t
ic = 0;
ic < n_part;
ic++) {
637 return energy / omega;
648 std::size_t
index_q,
double omega,
653 constexpr std::size_t size = 8;
673 for (
auto const &p : particles) {
674 auto const z = p.
pos()[2];
675 auto const q = p.
q();
676 auto e =
exp(omega * z);
718 exp(omega * (+z - 2. * elc.
box_h))) *
723 e = (
exp(-omega * z) +
751 exp(omega * (-z - 2. * elc.
box_h))) *
756 e = (
exp(omega * (+z - 2. * elc.
box_h)) +
780 auto constexpr c_2pi = 2. * std::numbers::pi;
785 constexpr std::size_t size = 8;
788 for (
auto &p : particles) {
789 auto &force = p.
force();
818static double PQ_energy(
double omega, std::size_t n_part) {
819 constexpr std::size_t size = 8;
822 for (std::size_t
ic = 0;
ic < n_part;
ic++) {
832 return energy / omega;
836void ElectrostaticLayerCorrection::add_force()
const {
837 auto constexpr c_2pi = 2. * std::numbers::pi;
839 auto const &box_geo = *
system.box_geo;
850 for (std::size_t p = 1;
851 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
854 auto const omega =
c_2pi * box_geo.length_inv()[0] *
static_cast<double>(p);
860 for (std::size_t q = 1;
861 box_geo.length_inv()[1] *
static_cast<double>(q - 1) <
elc.
far_cut &&
864 auto const omega =
c_2pi * box_geo.length_inv()[1] *
static_cast<double>(q);
870 for (std::size_t p = 1;
871 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
874 for (std::size_t q = 1;
875 Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p - 1)) +
877 static_cast<double>(q - 1)) <
883 sqrt(
Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p)) +
884 Utils::sqr(box_geo.length_inv()[1] *
static_cast<double>(q)));
892double ElectrostaticLayerCorrection::calc_energy()
const {
893 auto constexpr c_2pi = 2. * std::numbers::pi;
895 auto const &box_geo = *
system.box_geo;
897 auto energy = dipole_energy() + z_energy();
906 for (std::size_t p = 1;
907 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
910 auto const omega =
c_2pi * box_geo.length_inv()[0] *
static_cast<double>(p);
916 for (std::size_t q = 1;
917 box_geo.length_inv()[1] *
static_cast<double>(q - 1) <
elc.
far_cut &&
920 auto const omega =
c_2pi * box_geo.length_inv()[1] *
static_cast<double>(q);
926 for (std::size_t p = 1;
927 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
930 for (std::size_t q = 1;
931 Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p - 1)) +
933 static_cast<double>(q - 1)) <
939 sqrt(
Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p)) +
940 Utils::sqr(box_geo.length_inv()[1] *
static_cast<double>(q)));
950double ElectrostaticLayerCorrection::tune_far_cut()
const {
957 auto const box_l_z = box_geo.length()[2];
969 auto const num1 =
exp(pref * h);
979 throw std::runtime_error(
"ELC tuning failed: maxPWerror too small");
984void ElectrostaticLayerCorrection::sanity_checks_periodicity()
const {
986 if (!box_geo.periodic(0) || !box_geo.periodic(1) || !box_geo.periodic(2)) {
987 throw std::runtime_error(
"ELC: requires periodicity (True, True, True)");
991void ElectrostaticLayerCorrection::adapt_solver() {
993 [
this](
auto &solver) {
995 solver->adapt_epsilon_elc();
1001void ElectrostaticLayerCorrection::recalc_box_h() {
1006 throw std::runtime_error(
"ELC gap size (" + std::to_string(
elc.
gap_size) +
1007 ") larger than box length in z-direction (" +
1008 std::to_string(
box_z) +
")");
1013void ElectrostaticLayerCorrection::recalc_space_layer() {
1016 [](
auto &solver) {
return solver->p3m_params.r_cut; },
base_solver);
1028 throw std::runtime_error(
"P3M real-space cutoff too large for ELC w/ "
1029 "dielectric contrast");
1040 : maxPWerror{maxPWerror}, gap_size{gap_size}, box_h{-1.}, far_cut{far_cut},
1041 far_cut2{-1.}, far_calculated{far_cut == -1.},
1044 neutralize{neutralize
and !dielectric_contrast_on},
1050 space_layer{(dielectric_contrast_on) ? gap_size / 3. : 0.},
1051 space_box{gap_size - ((dielectric_contrast_on) ? 2. * space_layer : 0.)} {
1055 throw std::domain_error(
"Parameter 'far_cut' must be > 0");
1058 throw std::domain_error(
"Parameter 'maxPWerror' must be > 0");
1061 throw std::domain_error(
"Parameter 'gap_size' must be > 0");
1064 throw std::invalid_argument(
1065 "Parameter 'const_pot' must be True when 'pot_diff' is non-zero");
1068 throw std::invalid_argument(
1069 "Parameter 'const_pot' requires a dielectric contrast; set "
1070 "'delta_mid_top' and 'delta_mid_bot' (use -1 for metallic walls)");
1073 throw std::domain_error(
1074 "Parameter 'delta_mid_top' must be >= -1 and <= +1");
1077 throw std::domain_error(
1078 "Parameter 'delta_mid_bot' must be >= -1 and <= +1");
1085 throw std::domain_error(
"ELC with two parallel metallic boundaries "
1086 "requires the const_pot option");
1099 throw std::runtime_error(
1100 "ELC with a dielectric contrast is not supported by the GPU "
1107template <ChargeProtocol protocol>
1115 auto const &aosoa = cs.get_aosoa();
1116 auto const n_part = cs.count_local_particles();
1120 auto const p_pos = aosoa.get_span_at(aosoa.position,
p_index);
1144template <ChargeProtocol protocol>
1147 auto const &aosoa = cs.get_aosoa();
1148 auto const n_part = cs.count_local_particles();
1155 auto const p_z = aosoa.position(
p_index, 2ul);
1192 auto const energy = std::visit(
1195 auto const &cs = *
system.cell_structure;
1196 auto const &box_geo = *
system.box_geo;
1197 auto const particles = cs.local_particles();
1200 solver.charge_assign();
1203 return solver.long_range_energy();
1207 energy += 0.5 * solver.long_range_energy();
1214 energy += 0.5 * solver.long_range_energy();
1219 energy -= 0.5 * solver.long_range_energy();
1227 return energy + calc_energy();
1234 auto const &cs = *
system.cell_structure;
1237 auto const &box_geo = *system.box_geo;
1238 auto const particles = cs.local_particles();
1239 modify_p3m_sums<ChargeProtocol::BOTH>(elc, solver, cs);
1240 charge_assign<ChargeProtocol::BOTH>(elc, solver, cs);
1241 elc.dielectric_layers_self_forces(solver, box_geo, particles);
1243 solver.charge_assign();
1245 solver.add_long_range_forces();
Utils::Vector3d const & length() const
Box length.
Utils::Vector3d const & length_inv() const
Inverse box length.
void set_prefactor(double new_prefactor)
double prefactor
Electrostatics prefactor.
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.
static void addscale_vec(double *pdc_d, double scale, double const *pdc_s1, double const *pdc_s2, std::size_t size)
static std::pair< std::size_t, std::size_t > prepare_sc_cache(ParticleRange const &particles, BoxGeometry const &box_geo, double far_cut)
static void add_PQ_force(std::size_t index_p, std::size_t index_q, double omega, ParticleRange const &particles, BoxGeometry const &box_geo)
static std::vector< double > partblk
temporary buffers for product decomposition
static void clear_vec(double *pdc, std::size_t size)
static double * block(double *p, std::size_t index, std::size_t size)
void setup_PoQ(elc_data const &elc, double prefactor, std::size_t index, double omega, ParticleRange const &particles, BoxGeometry const &box_geo)
static void distribute(std::size_t size)
static double PoQ_energy(double omega, std::size_t n_part)
static std::vector< SCCache > scxcache
Cached sin/cos values along the x-axis and y-axis.
static double PQ_energy(double omega, std::size_t n_part)
static std::vector< SCCache > scycache
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)
static void add_vec(double *pdc_d, double const *pdc_s1, double const *pdc_s2, std::size_t size)
static void scale_vec(double scale, double *pdc, std::size_t size)
void add_PoQ_force(ParticleRange const &particles)
ChargeProtocol
ELC charge sum/assign protocol: real charges, image charges, or both.
void modify_p3m_sums(elc_data const &elc, CoulombP3M &solver, auto const &cs)
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.
static double gblcblk[8]
collected data from the other cells
void charge_assign(elc_data const &elc, CoulombP3M &solver, auto const &cs)
PoQ
ELC axes (x and y directions)
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)
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
auto sqrt(Vector< T, N > const &a)
auto constexpr P3M_EPSILON_METALLIC
This value indicates metallic boundary conditions.
P3M algorithm for long-range Coulomb interaction.
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.
std::variant< std::shared_ptr< CoulombP3M > > BaseSolver
BaseSolver base_solver
Electrostatics solver that is adapted.
ElectrostaticLayerCorrection(elc_data &¶meters, BaseSolver &&solver)
double long_range_energy() const
Calculate long-range electrostatic energy with corrections.
double b(double q, double z) const
Image sum from the bottom layer.
ImageSum(double delta, double shift, double h)
double t(double q, double z) const
Image sum from the top layer.
Struct holding all information for one particle.
constexpr auto const & pos() const
constexpr auto const & force() const
constexpr auto const & id() const
constexpr auto const & q() const
structure for caching sin and cos values
Parameters for the ELC method.
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
double maxPWerror
Maximal allowed pairwise error for the potential and force.
double pot_diff
Constant potential difference.
double box_h
Up to where particles can be found.
bool dielectric_contrast_on
Flag whether there is any dielectric contrast in the system.
elc_data(double maxPWerror, double gap_size, double far_cut, bool neutralize, double delta_top, double delta_bot, bool const_pot, double pot_diff)
double space_box
The space that is finally left.
bool neutralize
Flag whether the box is neutralized by a homogeneous background.
double far_cut
Cutoff of the exponential sum.
double space_layer
Layer around the dielectric contrast in which we trick around.
bool far_calculated
Flag whether far_cut was set by the user, or calculated by ESPResSo.
double gap_size
Size of the empty gap.
double delta_mid_bot
dielectric contrast in the lower part of the simulation cell.
bool const_pot
Flag whether a constant potential difference is applied.
double far_cut2
Squared value of far_cut.
double delta_mid_top
dielectric contrast in the upper part of the simulation cell.