24#if defined(ESPRESSO_ELECTROSTATICS) and defined(ESPRESSO_GSL)
40#include <gsl/gsl_sf_bessel.h>
50 auto const wavenumber = 2. * std::numbers::pi * box_l_inv[2];
52 auto const rhores = wavenumber * minrad;
53 auto const pref = 4. * box_l_inv[2] * std::max(1., wavenumber);
55 return pref * gsl_sf_bessel_K1(rhores *
P) * exp(rhores) / rhores *
56 (
P - 1. + 1. / rhores);
63 auto constexpr min_rad = 0.01;
64 auto const rgranularity = min_rad * box_l[2];
65 auto rmin = rgranularity;
66 auto rmax = std::min(box_l[0], box_l[1]);
67 auto const errmin =
far_error(
P, rmin, box_l_inv);
68 auto const errmax =
far_error(
P, rmax, box_l_inv);
69 if (errmin < maxPWerror) {
73 if (errmax > maxPWerror) {
75 return 2. * std::max(box_l[0], box_l[1]);
78 while (rmax - rmin > rgranularity) {
79 auto const c = 0.5 * (rmin + rmax);
81 if (errc > maxPWerror) {
87 return 0.5 * (rmin + rmax);
92 assert(not series.empty());
93 auto const value = std::accumulate(
94 series.rbegin(), series.rend(), 0.,
95 [x](
auto const &acc,
auto const &coeff) { return acc * x + coeff; });
109void CoulombMMM1D::determine_bessel_radii() {
111 auto const &box_l = box_geo.length();
112 auto const &box_l_inv = box_geo.length_inv();
113 for (
int i = 0; i < MAXIMAL_B_CUT; ++i) {
118void CoulombMMM1D::prepare_polygamma_series() {
121 auto const rhomax2 = uz2 * far_switch_radius_sq;
124 auto rhomax2nm2 = 1.0;
126 create_mod_psi_up_to(n + 1);
129 err = 2. *
static_cast<double>(n) * fabs(
mod_psi_even(modPsi, n, 0.5)) *
131 rhomax2nm2 *= rhomax2;
137 double switch_rad,
int tune_timings,
139 : maxPWerror{maxPWerror}, far_switch_radius{switch_rad},
140 tune_timings{tune_timings}, tune_verbose{tune_verbose}, m_is_tuned{false},
141 far_switch_radius_sq{-1.}, uz2{0.}, prefuz2{0.}, prefL3_i{0.} {
144 throw std::domain_error(
"Parameter 'maxPWerror' must be > 0");
147 throw std::domain_error(
"Parameter 'far_switch_radius' must be > 0");
153 throw std::domain_error(
"Parameter 'timings' must be > 0");
157void CoulombMMM1D::sanity_checks_periodicity()
const {
159 if (box_geo.periodic(0) || box_geo.periodic(1) || !box_geo.periodic(2)) {
160 throw std::runtime_error(
"MMM1D requires periodicity (False, False, True)");
164void CoulombMMM1D::sanity_checks_cell_structure()
const {
165 auto const &local_geo = *
get_system().local_geo;
167 throw std::runtime_error(
"MMM1D requires the N-square cellsystem");
171void CoulombMMM1D::recalc_boxl_parameters() {
174 if (far_switch_radius_sq >=
Utils::sqr(box_geo.length()[2])) {
175 far_switch_radius_sq = 0.8 *
Utils::sqr(box_geo.length()[2]);
181 prefL3_i = prefuz2 * box_geo.length_inv()[2];
183 determine_bessel_radii();
184 prepare_polygamma_series();
189 auto constexpr c_2pi = 2. * std::numbers::pi;
191 auto const n_modPsi =
static_cast<int>(modPsi.size()) >> 1;
192 auto const rxy2 = d[0] * d[0] + d[1] * d[1];
193 auto const rxy2_d = rxy2 * uz2;
194 auto const z_d = d[2] * box_geo.length_inv()[2];
197 if (rxy2 <= far_switch_radius_sq) {
202 for (
int n = 1; n < n_modPsi; n++) {
203 auto const deriv =
static_cast<double>(2 * n);
206 auto const r2n = r2nm1 * rxy2_d;
209 sr += deriv * r2nm1 * mpe;
217 double Fx = prefL3_i * sr * d[0];
218 double Fy = prefL3_i * sr * d[1];
219 double Fz = prefuz2 * sz;
223 double pref, rt, rt2, shift_z;
225 pref = 1. / Utils::int_pow<3>(dist);
230 shift_z = d[2] + box_geo.length()[2];
231 rt2 = rxy2 + shift_z * shift_z;
233 pref = 1. / (rt2 * rt);
236 Fz += pref * shift_z;
238 shift_z = d[2] - box_geo.length()[2];
239 rt2 = rxy2 + shift_z * shift_z;
241 pref = 1. / (rt2 * rt);
244 Fz += pref * shift_z;
246 force = {Fx, Fy, Fz};
249 auto const rxy = sqrt(rxy2);
250 auto const rxy_d = rxy * box_geo.length_inv()[2];
251 auto sr = 0., sz = 0.;
253 for (
int bp = 1; bp < MAXIMAL_B_CUT; bp++) {
254 if (bessel_radii[bp - 1] < rxy)
257 auto const fq = c_2pi * bp;
258 auto const k0 = gsl_sf_bessel_K0(fq * rxy_d);
259 auto const k1 = gsl_sf_bessel_K1(fq * rxy_d);
260 sr += bp * k1 * cos(fq * z_d);
261 sz += bp * k0 * sin(fq * z_d);
263 sr *= uz2 * 4. * c_2pi;
264 sz *= uz2 * 4. * c_2pi;
266 auto const pref = sr / rxy + 2. * box_geo.length_inv()[2] / rxy2;
268 force = {pref * d[0], pref * d[1], sz};
275 double const dist)
const {
279 auto constexpr c_2pi = 2. * std::numbers::pi;
281 auto const n_modPsi =
static_cast<int>(modPsi.size()) >> 1;
282 auto const rxy2 = d[0] * d[0] + d[1] * d[1];
283 auto const rxy2_d = rxy2 * uz2;
284 auto const z_d = d[2] * box_geo.length_inv()[2];
287 if (rxy2 <= far_switch_radius_sq) {
289 energy = -2. * std::numbers::egamma;
293 for (
int n = 0; n < n_modPsi; n++) {
302 energy *= box_geo.length_inv()[2];
310 shift_z = d[2] + box_geo.length()[2];
311 rt = sqrt(rxy2 + shift_z * shift_z);
314 shift_z = d[2] - box_geo.length()[2];
315 rt = sqrt(rxy2 + shift_z * shift_z);
319 auto const rxy = sqrt(rxy2);
320 auto const rxy_d = rxy * box_geo.length_inv()[2];
324 -0.25 * log(rxy2_d) + 0.5 * (std::numbers::ln2 - std::numbers::egamma);
325 for (
int bp = 1; bp < MAXIMAL_B_CUT; bp++) {
326 if (bessel_radii[bp - 1] < rxy)
329 auto const fq = c_2pi * bp;
330 energy += gsl_sf_bessel_K0(fq * rxy_d) * cos(fq * z_d);
332 energy *= 4. * box_geo.length_inv()[2];
342 recalc_boxl_parameters();
345 if (far_switch_radius_sq < 0.) {
346 auto const &box_geo = *system.box_geo;
347 auto const maxrad = box_geo.length()[2];
348 auto min_time = std::numeric_limits<double>::infinity();
350 auto switch_radius = 0.2 * maxrad;
352 while (switch_radius < 0.4 * maxrad) {
353 if (switch_radius > bessel_radii.back()) {
355 far_switch_radius_sq =
Utils::sqr(switch_radius);
356 system.on_coulomb_change();
362 std::printf(
"r= %f t= %f ms\n", switch_radius, int_time);
365 if (int_time < min_time) {
367 min_rad = switch_radius;
368 }
else if (int_time > 2. * min_time) {
373 switch_radius += 0.025 * maxrad;
375 switch_radius = min_rad;
377 far_switch_radius_sq =
Utils::sqr(switch_radius);
378 }
else if (far_switch_radius_sq <=
Utils::sqr(bessel_radii.back())) {
380 throw std::runtime_error(
"MMM1D could not find a reasonable Bessel cutoff");
384 system.on_coulomb_change();
@ NSQUARE
Atom decomposition (N-square).
Vector implementation and trait types for boost qvm interoperability.
void set_prefactor(double new_prefactor)
double prefactor
Electrostatics prefactor.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
static double evaluateAsTaylorSeriesAt(std::span< const double > series, double x)
static double mod_psi_odd(auto const &modPsi, int n, double x)
Modified polygamma for odd order 2*n+1, n>= 0
static auto determine_minrad(double maxPWerror, int P, Utils::Vector3d const &box_l, Utils::Vector3d const &box_l_inv)
static double mod_psi_even(auto const &modPsi, int n, double x)
Modified polygamma for even order 2*n, n >= 0
static auto far_error(int P, double minrad, Utils::Vector3d const &box_l_inv)
MMM1D algorithm for long-range Coulomb interactions on the CPU.
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
CoulombMMM1D(double prefactor, double maxPWerror, double switch_rad, int tune_timings, bool tune_verbose)
double far_switch_radius
Far switch radius.
Utils::Vector3d pair_force(double q1q2, Utils::Vector3d const &d, double dist) const
Compute the pair force.
double pair_energy(double q1q2, Utils::Vector3d const &d, double dist) const
Compute the pair energy.
double maxPWerror
Maximal allowed pairwise error for the potential and force.
double benchmark_integration_step(System::System &system, int int_steps)
Benchmark the integration loop.