36#include "communication.hpp"
38#include "system/System.hpp"
42#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
43#include <Kokkos_Core.hpp>
46#include <boost/mpi/collectives/all_reduce.hpp>
47#include <boost/range/combine.hpp>
111template <std::
size_t dir>
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);
119 auto const pref =
c_2pi *
u *
static_cast<double>(
freq);
121 std::size_t
o = (
freq - 1) * n_part;
122 for (
auto const &p : particles) {
123 auto const arg = pref * p.pos()[dir];
131static std::pair<std::size_t, std::size_t>
136 static_cast<std::size_t
>(std::ceil(far_cut * box_geo.
length()[0]) + 1.);
138 static_cast<std::size_t
>(std::ceil(far_cut * box_geo.
length()[1]) + 1.);
151 for (std::size_t i = 0; i < size; i++)
156 for (std::size_t i = 0; i < size; i++)
162 for (std::size_t i = 0; i < size; i++)
167 double const *
pdc_s2, std::size_t size) {
168 for (std::size_t i = 0; i < size; i++)
173 for (std::size_t i = 0; i < size; i++)
177static double *
block(
double *p, std::size_t index, std::size_t size) {
178 return &p[index * size];
185 boost::mpi::all_reduce(
comm_cart, send_buf,
static_cast<int>(size),
gblcblk,
189void ElectrostaticLayerCorrection::check_gap(
Particle const &p)
const {
191 auto const z = p.
pos()[2];
194 <<
"region by " << ((z < 0.) ? z : z -
elc.box_h);
206void ElectrostaticLayerCorrection::add_dipole_force()
const {
207 constexpr std::size_t size = 3;
209 auto const &box_geo = *
system.box_geo;
211 auto const pref =
prefactor * 4. * std::numbers::pi / box_geo.volume();
215 auto const shift = box_geo.length_half()[2];
225 auto const q = p.
q();
226 auto const z = p.
pos()[2];
273double ElectrostaticLayerCorrection::dipole_energy()
const {
274 constexpr std::size_t size = 7;
276 auto const &box_geo = *
system.box_geo;
278 auto const pref =
prefactor * 2. * std::numbers::pi / box_geo.volume();
279 auto const lz = box_geo.length()[2];
282 auto const shift = box_geo.length_half()[2];
296 auto const q = p.
q();
297 auto const z = p.
pos()[2];
326 energy += 2. * pref *
362 double b(
double q,
double z)
const {
367 double t(
double q,
double z)
const {
372double ElectrostaticLayerCorrection::z_energy()
const {
373 constexpr std::size_t size = 4;
375 auto const &box_geo = *
system.box_geo;
377 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
382 auto const shift = box_geo.length_half()[2];
383 auto const lz = box_geo.length()[2];
390 auto const z = p.
pos()[2];
391 auto const q = p.
q();
408 auto const fac_delta = delta / (1. - delta);
413 auto const z = p.
pos()[2];
414 auto const q = p.
q();
447 return (
this_node == 0) ? -pref * energy : 0.;
450void ElectrostaticLayerCorrection::add_z_force()
const {
451 constexpr std::size_t size = 1;
453 auto const &box_geo = *
system.box_geo;
455 auto const xy_area_inv = box_geo.length_inv()[0] * box_geo.length_inv()[1];
464 auto const z = p.
pos()[2];
465 auto const q = p.
q();
476 auto const fac_delta = delta / (1. - delta);
479 auto const z = p.
pos()[2];
480 auto const q = p.
q();
517 constexpr std::size_t size = 4;
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);
566 exp(omega * (+z - 2. * elc.
box_h))) *
569 e = (
exp(-omega * z) +
590 exp(omega * (-z - 2. * elc.
box_h))) *
593 e = (
exp(omega * (+z - 2. * elc.
box_h)) +
614 constexpr auto i =
static_cast<int>(axis);
615 constexpr std::size_t size = 4;
618 for (
auto &p : particles) {
619 auto &force = p.
force();
633 constexpr std::size_t size = 4;
636 for (std::size_t
ic = 0;
ic < n_part;
ic++) {
643 return energy / omega;
654 std::size_t
index_q,
double omega,
659 constexpr std::size_t size = 8;
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);
724 exp(omega * (+z - 2. * elc.
box_h))) *
729 e = (
exp(-omega * z) +
757 exp(omega * (-z - 2. * elc.
box_h))) *
762 e = (
exp(omega * (+z - 2. * elc.
box_h)) +
786 auto constexpr c_2pi = 2. * std::numbers::pi;
791 constexpr std::size_t size = 8;
794 for (
auto &p : particles) {
795 auto &force = p.
force();
824static double PQ_energy(
double omega, std::size_t n_part) {
825 constexpr std::size_t size = 8;
828 for (std::size_t
ic = 0;
ic < n_part;
ic++) {
838 return energy / omega;
842void ElectrostaticLayerCorrection::add_force()
const {
843 auto constexpr c_2pi = 2. * std::numbers::pi;
845 auto const &box_geo = *
system.box_geo;
856 for (std::size_t p = 1;
857 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
860 auto const omega =
c_2pi * box_geo.length_inv()[0] *
static_cast<double>(p);
866 for (std::size_t q = 1;
867 box_geo.length_inv()[1] *
static_cast<double>(q - 1) <
elc.
far_cut &&
870 auto const omega =
c_2pi * box_geo.length_inv()[1] *
static_cast<double>(q);
876 for (std::size_t p = 1;
877 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
880 for (std::size_t q = 1;
881 Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p - 1)) +
883 static_cast<double>(q - 1)) <
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)));
898double ElectrostaticLayerCorrection::calc_energy()
const {
899 auto constexpr c_2pi = 2. * std::numbers::pi;
901 auto const &box_geo = *
system.box_geo;
903 auto energy = dipole_energy() + z_energy();
912 for (std::size_t p = 1;
913 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
916 auto const omega =
c_2pi * box_geo.length_inv()[0] *
static_cast<double>(p);
922 for (std::size_t q = 1;
923 box_geo.length_inv()[1] *
static_cast<double>(q - 1) <
elc.
far_cut &&
926 auto const omega =
c_2pi * box_geo.length_inv()[1] *
static_cast<double>(q);
932 for (std::size_t p = 1;
933 box_geo.length_inv()[0] *
static_cast<double>(p - 1) <
elc.
far_cut &&
936 for (std::size_t q = 1;
937 Utils::sqr(box_geo.length_inv()[0] *
static_cast<double>(p - 1)) +
939 static_cast<double>(q - 1)) <
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)));
956double ElectrostaticLayerCorrection::tune_far_cut()
const {
963 auto const box_l_z = box_geo.length()[2];
973 auto const den = -
expm1(-pref * lz);
984 throw std::runtime_error(
"ELC tuning failed: maxPWerror too small");
997void ElectrostaticLayerCorrection::sanity_checks_periodicity()
const {
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)");
1004void ElectrostaticLayerCorrection::sanity_checks_dielectric_contrasts()
const {
1006 auto const &cell_structure = *
get_system().cell_structure;
1013 throw std::runtime_error(
"ELC does not currently support non-neutral "
1014 "systems with a dielectric contrast.");
1018 throw std::runtime_error(
"ELC does not work for non-neutral systems and "
1019 "non-metallic dielectric contrast.");
1024void ElectrostaticLayerCorrection::adapt_solver() {
1026 [
this](
auto &solver) {
1028 solver->adapt_epsilon_elc();
1034void ElectrostaticLayerCorrection::recalc_box_h() {
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) +
")");
1046void ElectrostaticLayerCorrection::recalc_space_layer() {
1049 [](
auto &solver) {
return solver->p3m_params.r_cut; },
base_solver);
1061 throw std::runtime_error(
"P3M real-space cutoff too large for ELC w/ "
1062 "dielectric contrast");
1073 : maxPWerror{maxPWerror}, gap_size{gap_size}, box_h{-1.}, far_cut{far_cut},
1074 far_cut2{-1.}, far_calculated{far_cut == -1.},
1077 neutralize{neutralize
and !dielectric_contrast_on},
1083 space_layer{(dielectric_contrast_on) ? gap_size / 3. : 0.},
1084 space_box{gap_size - ((dielectric_contrast_on) ? 2. * space_layer : 0.)} {
1088 throw std::domain_error(
"Parameter 'far_cut' must be > 0");
1091 throw std::domain_error(
"Parameter 'maxPWerror' must be > 0");
1094 throw std::domain_error(
"Parameter 'gap_size' must be > 0");
1097 throw std::invalid_argument(
1098 "Parameter 'const_pot' must be True when 'pot_diff' is non-zero");
1101 throw std::domain_error(
1102 "Parameter 'delta_mid_top' must be >= -1 and <= +1");
1105 throw std::domain_error(
1106 "Parameter 'delta_mid_bot' must be >= -1 and <= +1");
1113 throw std::domain_error(
"ELC with two parallel metallic boundaries "
1114 "requires the const_pot option");
1124template <ChargeProtocol protocol,
typename combined_ranges>
1131#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
1164template <ChargeProtocol protocol,
typename combined_range>
1212 auto const energy = std::visit(
1215 auto const particles =
system.cell_structure->local_particles();
1216 auto const &box_geo = *
system.box_geo;
1223 solver.charge_assign();
1226 return solver.long_range_energy();
1230 energy += 0.5 * solver.long_range_energy();
1237 energy += 0.5 * solver.long_range_energy();
1242 energy -= 0.5 * solver.long_range_energy();
1250 return energy + calc_energy();
1257 auto const particles =
system.cell_structure->local_particles();
1263 auto const &box_geo = *
system.box_geo;
1268 solver.charge_assign();
1270 solver.add_long_range_forces();
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
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.
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)
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 auto calc_total_charge(CellStructure const &cell_structure)
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)
void modify_p3m_sums(elc_data const &elc, CoulombP3M &solver, combined_range const &p_q_pos_range)
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)
void charge_assign(elc_data const &elc, CoulombP3M &solver, combined_ranges const &p_q_pos_range)
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 copy_vec(double *pdc_d, double const *pdc_s, std::size_t size)
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.
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
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)
auto charge_range(ParticleRange const &particles)
auto pos_range(ParticleRange const &particles)
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 lz)
double t(double q, double z) const
Image sum from the top layer.
Struct holding all information for one particle.
auto const & force() 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.
void dielectric_layers_self_forces(CoulombP3M const &p3m, BoxGeometry const &box_geo, ParticleRange const &particles) const
forces of particles in border layers with themselves
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.