36#include "communication.hpp"
38#include "system/System.hpp"
42#include <boost/mpi/collectives/all_reduce.hpp>
43#include <boost/mpi/collectives/reduce.hpp>
44#include <boost/mpi/operations.hpp>
60void DipolarLayerCorrection::check_gap(
Particle const &p)
const {
62 auto const z = p.
pos()[2];
65 <<
"by " << z - ((z < 0.) ? 0. :
dlc.box_h);
72 auto const local_particles = system.
cell_structure->local_particles();
73 auto const mu_max_local = std::accumulate(
74 local_particles.begin(), local_particles.end(), 0.,
75 [](
double mu,
Particle const &p) { return std::max(mu, p.dipm()); });
77 return boost::mpi::all_reduce(
comm_cart, mu_max_local,
78 boost::mpi::maximum<double>());
83 auto const cc2 = c * c;
84 auto const x3 = x * x * x;
85 auto const a = g * g * g / x + 1.5 * cc2 + 1.5 * g / x3 + 0.75 / (x3 * x);
90 auto const x2 = x * x;
91 auto const a = g * g / x + 2.0 * g / x2 + 2.0 / (x2 * x);
99 for (
auto const &p : particles) {
100 if (p.
dipm() != 0.) {
105 return boost::mpi::all_reduce(
comm_cart, local_dip, std::plus<>());
113 std::vector<Utils::Vector3d> &fs,
114 std::vector<Utils::Vector3d> &ts,
117 auto const facux = 2. * std::numbers::pi * box_geo.
length_inv()[0];
118 auto const facuy = 2. * std::numbers::pi * box_geo.
length_inv()[1];
120 auto const n_local_particles = particles.
size();
121 std::vector<double> ReSjp(n_local_particles);
122 std::vector<double> ReSjm(n_local_particles);
123 std::vector<double> ImSjp(n_local_particles);
124 std::vector<double> ImSjm(n_local_particles);
125 std::vector<double> ReGrad_Mup(n_local_particles);
126 std::vector<double> ImGrad_Mup(n_local_particles);
127 std::vector<double> ReGrad_Mum(n_local_particles);
128 std::vector<double> ImGrad_Mum(n_local_particles);
130 for (
int ix = -kcut; ix <= +kcut; ix++) {
131 for (
int iy = -kcut; iy <= +kcut; iy++) {
132 if (ix == 0 and iy == 0) {
135 auto const gx =
static_cast<double>(ix) * facux;
136 auto const gy =
static_cast<double>(iy) * facuy;
137 auto const gr = sqrt(gx * gx + gy * gy);
140 auto const fa1 = 1. / (gr * (exp(gr * box_geo.
length()[2]) - 1.));
145 double S[4] = {0., 0., 0., 0.};
147 for (
auto const &p : particles) {
148 if (p.
dipm() != 0.) {
149 auto const &pos = p.
pos();
152 auto const a = gx * dip[0] + gy * dip[1];
153 auto const b = gr * dip[2];
154 auto const er = gx * pos[0] + gy * pos[1];
155 auto const ez = gr * pos[2];
156 auto const c = cos(er);
157 auto const d = sin(er);
158 auto const f = exp(ez);
160 ReSjp[ip] = (b * c - a * d) * f;
161 ImSjp[ip] = (c * a + b * d) * f;
162 ReSjm[ip] = (-b * c - a * d) / f;
163 ImSjm[ip] = (c * a - b * d) / f;
164 ReGrad_Mup[ip] = c * f;
165 ReGrad_Mum[ip] = c / f;
166 ImGrad_Mup[ip] = d * f;
167 ImGrad_Mum[ip] = d / f;
177 MPI_Allreduce(MPI_IN_PLACE, S, 4, MPI_DOUBLE, MPI_SUM,
comm_cart);
182 for (
auto &p : particles) {
183 if (p.
dipm() != 0.) {
186 auto const s1 = -(-ReSjp[ip] * S[3] + ImSjp[ip] * S[2]);
187 auto const s2 = +(ReSjm[ip] * S[1] - ImSjm[ip] * S[0]);
188 auto const s3 = -(-ReSjm[ip] * S[1] + ImSjm[ip] * S[0]);
189 auto const s4 = +(ReSjp[ip] * S[3] - ImSjp[ip] * S[2]);
191 auto const s1z = +(ReSjp[ip] * S[2] + ImSjp[ip] * S[3]);
192 auto const s2z = -(ReSjm[ip] * S[0] + ImSjm[ip] * S[1]);
193 auto const s3z = -(ReSjm[ip] * S[0] + ImSjm[ip] * S[1]);
194 auto const s4z = +(ReSjp[ip] * S[2] + ImSjp[ip] * S[3]);
196 auto const ss = s1 + s2 + s3 + s4;
197 fs[ip][0] += fa1 * gx * ss;
198 fs[ip][1] += fa1 * gy * ss;
199 fs[ip][2] += fa1 * gr * (s1z + s2z + s3z + s4z);
203 auto const s1 = -(-ReGrad_Mup[ip] * S[3] + ImGrad_Mup[ip] * S[2]);
204 auto const s2 = +(ReGrad_Mum[ip] * S[1] - ImGrad_Mum[ip] * S[0]);
205 auto const s3 = -(-ReGrad_Mum[ip] * S[1] + ImGrad_Mum[ip] * S[0]);
206 auto const s4 = +(ReGrad_Mup[ip] * S[3] - ImGrad_Mup[ip] * S[2]);
208 auto const s1z = +(ReGrad_Mup[ip] * S[2] + ImGrad_Mup[ip] * S[3]);
209 auto const s2z = -(ReGrad_Mum[ip] * S[0] + ImGrad_Mum[ip] * S[1]);
210 auto const s3z = -(ReGrad_Mum[ip] * S[0] + ImGrad_Mum[ip] * S[1]);
211 auto const s4z = +(ReGrad_Mup[ip] * S[2] + ImGrad_Mup[ip] * S[3]);
213 auto const ss = s1 + s2 + s3 + s4;
214 ts[ip][0] += fa1 * gx * ss;
215 ts[ip][1] += fa1 * gy * ss;
216 ts[ip][2] += fa1 * gr * (s1z + s2z + s3z + s4z);
227 for (
auto const &p : particles) {
228 if (p.
dipm() != 0.) {
238 for (std::size_t j = 0; j < n_local_particles; ++j) {
251 auto const facux = 2. * std::numbers::pi * box_geo.
length_inv()[0];
252 auto const facuy = 2. * std::numbers::pi * box_geo.
length_inv()[1];
255 double sum_S[4] = {0., 0., 0., 0.};
256 for (
int ix = -kcut; ix <= +kcut; ix++) {
257 for (
int iy = -kcut; iy <= +kcut; iy++) {
258 if ((ix == 0 && iy == 0)) {
261 auto const gx =
static_cast<double>(ix) * facux;
262 auto const gy =
static_cast<double>(iy) * facuy;
263 auto const gr = sqrt(gx * gx + gy * gy);
266 auto const fa1 = 1. / (gr * (exp(gr * box_geo.
length()[2]) - 1.));
270 double S[4] = {0., 0., 0., 0.};
272 for (
auto const &p : particles) {
273 if (p.
dipm() != 0.) {
274 auto const &pos = p.
pos();
277 auto const a = gx * dip[0] + gy * dip[1];
278 auto const b = gr * dip[2];
279 auto const er = gx * pos[0] + gy * pos[1];
280 auto const ez = gr * pos[2];
281 auto const c = cos(er);
282 auto const d = sin(er);
283 auto const f = exp(ez);
285 S[0] += (b * c - a * d) * f;
286 S[1] += (c * a + b * d) * f;
287 S[2] += (-b * c - a * d) / f;
288 S[3] += (c * a - b * d) / f;
291 boost::mpi::reduce(
comm_cart, S, 4, sum_S, std::plus<>(), 0);
295 energy += fa1 * 2. * (sum_S[0] * sum_S[2] + sum_S[1] * sum_S[3]);
309 auto const volume = box_geo.volume();
310 auto const correc = 4. * std::numbers::pi / volume;
314 std::vector<Utils::Vector3d> dip_DLC_f(particles.
size());
315 std::vector<Utils::Vector3d> dip_DLC_t(particles.
size());
320 auto const kcut =
static_cast<int>(std::round(
dlc.
far_cut));
336 for (
auto &p : particles) {
339 if (p.
dipm() != 0.) {
361 auto const volume = box_geo.volume();
362 auto const pref =
prefactor * 2. * std::numbers::pi / volume;
367 for (
auto const &p : particles) {
374 auto const k_cut =
static_cast<int>(std::round(
dlc.
far_cut));
375 auto dip_DLC_energy =
388 dip_DLC_energy += pref *
Utils::sqr(box_dip[2]);
394 return dip_DLC_energy;
403 if (p.
dipm() != 0.) {
408 return boost::mpi::all_reduce(
comm_cart, local_n, std::plus<>());
417double DipolarLayerCorrection::tune_far_cut()
const {
422 auto const &box_geo = *system.box_geo;
423 auto const lx = box_geo.length()[0];
424 auto const ly = box_geo.length()[1];
425 auto const lz = box_geo.length()[2];
427 if (std::abs(lx - ly) > 0.001) {
428 throw std::runtime_error(
"DLC tuning: box size in x direction is "
429 "different from y direction. The tuning "
430 "formula requires both to be equal.");
433 auto constexpr limitkc = 200;
434 auto constexpr exp_max = +709.8;
435 auto constexpr exp_min = -708.4;
436 auto const log_max = std::log(std::numeric_limits<double>::max());
437 auto const piarea = std::numbers::pi / (lx * ly);
441 for (
int kc = 1; kc < limitkc; kc++) {
442 auto const gc = kc * 2. * std::numbers::pi / lx;
446 auto const exp_term = 2. * gc * h;
447 auto const log_term1 = exp_term + std::log(9. * pref1);
448 if (exp_term >= exp_max or log_term1 >= log_max or -exp_term <= exp_min) {
452 auto const fa0 = std::sqrt(9. * std::exp(+exp_term) * pref1 +
453 9. * std::exp(-exp_term) * pref2 + 22. * pref0);
454 auto const fa1 =
sqrt(0.125 * piarea) * fa0;
456 auto const de = nmp * mu_max_sq / (4. * (exp(gc * lz) - 1.)) * (fa1 + fa2);
458 far_cut =
static_cast<double>(kc);
463 throw std::runtime_error(
"DLC tuning failed: maxPWerror too small");
472 void operator()(std::shared_ptr<DipolarDirectSum>
const &solver) {
480 m_actor->
epsilon = solver->dp3m_params.epsilon;
495void DipolarLayerCorrection::sanity_checks_node_grid()
const {
497 if (!box_geo.periodic(0) || !box_geo.periodic(1) || !box_geo.periodic(2)) {
498 throw std::runtime_error(
"DLC: requires periodicity (True, True, True)");
503 : maxPWerror{maxPWerror}, gap_size{gap_size}, box_h{-1.}, far_cut{far_cut},
504 far_calculated{far_cut == -1.} {
506 throw std::domain_error(
"Parameter 'far_cut' must be > 0");
509 throw std::domain_error(
"Parameter 'maxPWerror' must be > 0");
512 throw std::domain_error(
"Parameter 'gap_size' must be > 0");
517 auto const lz =
get_system().box_geo->length()[2];
519 if (new_box_h < 0.) {
520 throw std::runtime_error(
"DLC gap size (" + std::to_string(
dlc.
gap_size) +
521 ") larger than box length in z-direction (" +
522 std::to_string(lz) +
")");
529 : dlc{parameters}, base_solver{solver} {
Utils::Vector3d const & length() const
Box length.
Utils::Vector3d const & length_inv() const
Inverse box length.
base_type::size_type size() const
std::shared_ptr< CellStructure > cell_structure
boost::mpi::communicator comm_cart
The communicator.
int this_node
The number of this node.
This file contains the defaults for ESPResSo.
__device__ void vector_product(float const *a, float const *b, float *out)
static void dipolar_force_corrections(int kcut, std::vector< Utils::Vector3d > &fs, std::vector< Utils::Vector3d > &ts, ParticleRange const &particles, BoxGeometry const &box_geo)
Compute the dipolar force and torque corrections.
static double g1_DLC_dip(double g, double x)
static double dipolar_energy_correction(int kcut, ParticleRange const &particles, BoxGeometry const &box_geo)
Compute the dipolar DLC energy correction.
static double g2_DLC_dip(double g, double x)
static Utils::Vector3d calc_slab_dipole(ParticleRange const &particles)
Compute the box magnetic dipole.
static double calc_mu_max(System::System const &system)
Calculate the maximal dipole moment in the system.
static int count_magnetic_particles(System::System const &system)
P3M algorithm for long-range magnetic dipole-dipole interaction.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
DEVICE_QUALIFIER constexpr T sqr(T x)
Calculates the SQuaRe of x.
Vector< T, N > sqrt(Vector< T, N > const &a)
Common functions for dipolar and charge P3M.
auto constexpr P3M_EPSILON_METALLIC
This value indicates metallic boundary conditions.
Lock an actor and modify its parameters.
AdaptSolver(DipolarLayerCorrection *this_ptr)
void operator()(std::shared_ptr< DipolarDirectSum > const &solver)
void operator()(std::shared_ptr< DipolarP3M > const &solver)
Adapt a magnetostatics solver to remove contributions from the z-direction.
double epsilon_correction
double energy_correction(ParticleRange const &particles) const
Calculate the dipolar energy correction.
BaseSolver base_solver
Magnetostatics solver that is adapted.
void add_force_corrections(ParticleRange const &particles) const
Add the dipolar force and torque corrections.
DipolarLayerCorrection(dlc_data &¶meters, BaseSolver &&solver)
std::variant< std::shared_ptr< DipolarP3M >, std::shared_ptr< DipolarDirectSum > > BaseSolver
Struct holding all information for one particle.
auto const & torque() const
auto const & dipm() const
auto const & force() const
Parameters for the DLC method.
bool far_calculated
Flag whether far_cut was set by the user, or calculated by ESPResSo.
dlc_data(double maxPWerror, double gap_size, double far_cut)
double maxPWerror
maximal pairwise error of the potential and force
double far_cut
Cutoff of the exponential sum.
double gap_size
Size of the empty gap.
double box_h
Up to where particles can be found.