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);
118 for (std::size_t freq = 1; freq <= n_freq; freq++) {
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];
124 ret[o++] = {sin(arg), cos(arg)};
131static std::pair<std::size_t, std::size_t>
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.);
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};
151 for (std::size_t i = 0; i < size; i++)
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++)
160static void add_vec(
double *pdc_d,
double const *pdc_s1,
double const *pdc_s2,
162 for (std::size_t i = 0; i < size; i++)
163 pdc_d[i] = pdc_s1[i] + pdc_s2[i];
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];
172static void scale_vec(
double scale,
double *pdc, std::size_t size) {
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;
210 auto const particles = system.cell_structure->local_particles();
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];
255 auto const field_induced =
gblcblk[1];
257 field_tot -= field_applied + field_induced;
261 p.
force()[2] -= field_tot * p.
q();
273double ElectrostaticLayerCorrection::dipole_energy()
const {
274 constexpr std::size_t size = 7;
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];
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;
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;
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;
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;
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;
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.;
526 auto const fac_elc = 1. / (1. - delta * exp(-omega * 2. * elc.
box_h));
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);
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;
566 exp(omega * (+z - 2. * elc.
box_h))) *
569 e = (exp(-omega * z) +
574 lclimge[
POQESP] += q * sc_cache[o + ic].s * e;
575 lclimge[
POQECP] += q * sc_cache[o + ic].c * e;
578 e = exp(omega * (2. * elc.
box_h - z));
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;
590 exp(omega * (-z - 2. * elc.
box_h))) *
593 e = (exp(omega * (+z - 2. * elc.
box_h)) +
598 lclimge[
POQESM] += q * sc_cache[o + ic].s * e;
599 lclimge[
POQECM] += q * sc_cache[o + ic].c * e;
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,
657 assert(index_p >= 1);
658 assert(index_q >= 1);
659 constexpr std::size_t size = 8;
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.;
667 auto const fac_elc = 1. / (1. - delta * exp(-omega * 2. * elc.
box_h));
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);
724 exp(omega * (+z - 2. * elc.
box_h))) *
729 e = (exp(-omega * z) +
731 fac_delta_mid_bot * q;
741 e = exp(omega * (2. * elc.
box_h - z));
757 exp(omega * (-z - 2. * elc.
box_h))) *
762 e = (exp(omega * (+z - 2. * elc.
box_h)) +
764 fac_delta_mid_top * q;
783static void add_PQ_force(std::size_t index_p, std::size_t index_q,
double omega,
786 auto constexpr c_2pi = 2. * std::numbers::pi;
788 c_2pi * box_geo.
length_inv()[0] *
static_cast<double>(index_p) / omega;
790 c_2pi * box_geo.
length_inv()[1] *
static_cast<double>(index_q) / omega;
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;
846 auto const particles = system.cell_structure->local_particles();
848 auto const n_scxcache = std::get<0>(n_freqs);
849 auto const n_scycache = std::get<1>(n_freqs);
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);
861 setup_PoQ<PoQ::P>(
elc,
prefactor, p, omega, particles, box_geo);
863 add_PoQ_force<PoQ::P>(particles);
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);
871 setup_PoQ<PoQ::Q>(
elc,
prefactor, q, omega, particles, box_geo);
873 add_PoQ_force<PoQ::Q>(particles);
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;
902 auto const particles = system.cell_structure->local_particles();
903 auto energy = dipole_energy() + z_energy();
905 auto const n_scxcache = std::get<0>(n_freqs);
906 auto const n_scycache = std::get<1>(n_freqs);
909 partblk.resize(n_localpart * 8);
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);
917 setup_PoQ<PoQ::P>(
elc,
prefactor, p, omega, particles, box_geo);
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);
927 setup_PoQ<PoQ::Q>(
elc,
prefactor, q, omega, particles, box_geo);
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 {
958 auto constexpr maximal_far_cut = 50.;
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];
968 auto tuned_far_cut = min_inv_boxl;
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));
981 tuned_far_cut += min_inv_boxl;
982 }
while (err >
elc.
maxPWerror and tuned_far_cut < maximal_far_cut);
983 if (tuned_far_cut >= maximal_far_cut) {
984 throw std::runtime_error(
"ELC tuning failed: maxPWerror too small");
986 return tuned_far_cut - min_inv_boxl;
994 return boost::mpi::all_reduce(
comm_cart, local_q, std::plus<>());
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;
1009 if (total_charge >= precision_threshold) {
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() {
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) +
")");
1046void ElectrostaticLayerCorrection::recalc_space_layer() {
1048 auto const p3m_r_cut = std::visit(
1049 [](
auto &solver) {
return solver->p3m_params.r_cut; },
base_solver);
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");
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.},
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");
1096 if (potential_diff != 0. and not with_const_pot) {
1097 throw std::invalid_argument(
1098 "Parameter 'const_pot' must be True when 'pot_diff' is non-zero");
1100 if (delta_top < -delta_range or delta_top > delta_range) {
1101 throw std::domain_error(
1102 "Parameter 'delta_mid_top' must be >= -1 and <= +1");
1104 if (delta_bot < -delta_range or delta_bot > delta_range) {
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");
1120 : elc{parameters}, base_solver{solver} {
1124template <ChargeProtocol protocol,
typename combined_ranges>
1126 combined_ranges
const &p_q_pos_range) {
1131#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
1133 auto constexpr include_neutral_particles =
true;
1135 auto constexpr include_neutral_particles =
false;
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.) {
1152 solver.
assign_charge(q_eff, {p_pos[0], p_pos[1], -p_pos[2]},
true);
1157 q_eff, {p_pos[0], p_pos[1], 2. * elc.
box_h - p_pos[2]},
true);
1164template <ChargeProtocol protocol,
typename combined_range>
1166 combined_range
const &p_q_pos_range) {
1168 auto local_n = std::size_t{0u};
1169 auto local_q2 = 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);
1175 auto const p_z = p_pos[2];
1201 auto global_n = std::size_t{0u};
1202 auto global_q2 = 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<>());
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;
1220 auto p_q_pos_range = boost::combine(p_q_range, p_pos_range);
1223 solver.charge_assign();
1226 return solver.long_range_energy();
1230 energy += 0.5 * solver.long_range_energy();
1235 charge_assign<ChargeProtocol::BOTH>(
elc, solver, p_q_pos_range);
1236 modify_p3m_sums<ChargeProtocol::BOTH>(
elc, solver, p_q_pos_range);
1237 energy += 0.5 * solver.long_range_energy();
1240 charge_assign<ChargeProtocol::IMAGE>(
elc, solver, p_q_pos_range);
1241 modify_p3m_sums<ChargeProtocol::IMAGE>(
elc, solver, p_q_pos_range);
1242 energy -= 0.5 * solver.long_range_energy();
1245 modify_p3m_sums<ChargeProtocol::REAL>(
elc, solver, p_q_pos_range);
1250 return energy + calc_energy();
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;
1264 modify_p3m_sums<ChargeProtocol::BOTH>(
elc, solver, p_q_pos_range);
1265 charge_assign<ChargeProtocol::BOTH>(
elc, solver, p_q_pos_range);
1268 solver.charge_assign();
1270 solver.add_long_range_forces();
1272 modify_p3m_sums<ChargeProtocol::REAL>(
elc, solver, p_q_pos_range);
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
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.