ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
icc.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2022 The ESPResSo project
3 * Copyright (C) 2002,2003,2004,2005,2006,2007,2008,2009,2010
4 * Max-Planck-Institute for Polymer Research, Theory Group
5 *
6 * This file is part of ESPResSo.
7 *
8 * ESPResSo is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * ESPResSo is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22/** \file
23 * Functions to compute the electric field acting on the induced charges,
24 * excluding forces other than the electrostatic ones. Detailed information
25 * about the ICC* method is included in the corresponding header file
26 * \ref icc.hpp.
27 */
28
29#include "config/config.hpp"
30
31#ifdef ESPRESSO_ELECTROSTATICS
32
33#include "icc.hpp"
34
35#include "Particle.hpp"
36#include "ParticleRange.hpp"
37#include "PropagationMode.hpp"
38#include "actor/visitors.hpp"
40#include "communication.hpp"
44#include "errorhandling.hpp"
47#include "system/System.hpp"
48
49#include <boost/mpi/collectives/all_reduce.hpp>
50#include <boost/mpi/operations.hpp>
51
52#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
53#include <Kokkos_Core.hpp>
54#include <omp.h>
55#endif
56
57#include <algorithm>
58#include <cmath>
59#include <cstddef>
60#include <limits>
61#include <numbers>
62#include <stdexcept>
63#include <variant>
64#include <vector>
65
66/** Calculate the electrostatic forces between source charges (= real charges)
67 * and wall charges. For each electrostatic method, the proper functions
68 * for short- and long-range parts are called. Long-range parts are calculated
69 * directly, short-range parts need helper functions according to the particle
70 * data organisation. This is a modified version of
71 * @ref System::System::calculate_forces.
72 */
73static void force_calc_icc(
74 CellStructure &cell_structure,
77 // reset forces
78 auto const reset_kernel = [](Particle &p) { p.force_and_torque() = {}; };
79 cell_structure.for_each_local_particle(reset_kernel);
80 cell_structure.for_each_ghost_particle(reset_kernel);
81#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
82 cell_structure.reset_local_force();
83#endif
84
85 // calc ICC forces
86 cell_structure.non_bonded_loop(
87 [coulomb_kernel_ptr = get_ptr(coulomb_kernel),
88 elc_kernel_ptr = get_ptr(elc_kernel)](Particle &p1, Particle &p2,
89 Distance const &d) {
90 auto const q1q2 = p1.q() * p2.q();
91 if (q1q2 != 0.) {
92 auto force = (*coulomb_kernel_ptr)(q1q2, d.vec21, std::sqrt(d.dist2));
93 p1.force() += force;
94 p2.force() -= force;
95#ifdef ESPRESSO_P3M
96 if (elc_kernel_ptr) {
97 (*elc_kernel_ptr)(p1.pos(), p2.pos(), p1.force_and_torque().f,
98 p2.force_and_torque().f, q1q2);
99 }
100#endif // ESPRESSO_P3M
101 }
102 });
103}
104
106 try {
107 sanity_check();
108 } catch (std::runtime_error const &err) {
109 runtimeErrorMsg() << err.what();
110 return;
111 }
112
113 auto &system = get_system();
114 auto &cell_structure = *system.cell_structure;
115 auto const &coulomb = system.coulomb;
116 auto const particles = cell_structure.local_particles();
117 auto const prefactor = std::visit(
118 [](auto const &ptr) { return ptr->prefactor; }, *coulomb.impl->solver);
119 auto const pref = 1. / (prefactor * 2. * std::numbers::pi);
120 auto const kernel = coulomb.pair_force_kernel();
121 auto const elc_kernel = coulomb.pair_force_elc_kernel();
123
124#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
125 using execution_space = Kokkos::DefaultExecutionSpace;
126 auto const &unique_particles = cell_structure.get_unique_particles();
127 auto const &local_force = cell_structure.get_local_force();
128#endif // ESPRESSO_SHARED_MEMORY_PARALLELISM
129
130 auto global_max_rel_diff = 0.;
131
132 for (int j = 0; j < icc_cfg.max_iterations; j++) {
133 auto charge_density_max = 0.;
134
135 // calculate electrostatic forces (SR+LR) excluding self-interactions
136 force_calc_icc(cell_structure, kernel, elc_kernel);
137 system.coulomb.calc_long_range_force(particles);
138 cell_structure.ghosts_reduce_forces();
139#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
140 // force reduction
141 int num_threads = execution_space().concurrency();
142 kokkos_parallel_range_for<Kokkos::RangePolicy<execution_space>>(
143 "reduction", std::size_t{0}, unique_particles.size(),
144 [&local_force, &unique_particles, num_threads](std::size_t const i) {
145 auto &force = unique_particles.at(i)->force();
146 for (int tid = 0; tid < num_threads; ++tid) {
147 force[0] += local_force(i, tid, 0);
148 force[1] += local_force(i, tid, 1);
149 force[2] += local_force(i, tid, 2);
150 }
151 });
152 Kokkos::fence();
153#endif // ESPRESSO_SHARED_MEMORY_PARALLELISM
154
155 auto max_rel_diff = 0.;
156
157 for (auto &p : particles) {
158 auto const pid = p.id();
159 if (pid >= icc_cfg.first_id and pid < icc_cfg.n_icc + icc_cfg.first_id) {
160 if (p.q() == 0.) {
162 << "ICC found zero electric charge on a particle. This must "
163 "never happen";
164 break;
165 }
166 auto const id = p.id() - icc_cfg.first_id;
167 /* the dielectric-related prefactor: */
168 auto const eps_in = icc_cfg.epsilons[id];
169 auto const eps_out = icc_cfg.eps_out;
170 auto const del_eps = (eps_in - eps_out) / (eps_in + eps_out);
171 /* calculate the electric field at the certain position */
172 auto const local_e_field = p.force() / p.q() + icc_cfg.ext_field;
173
174 if (local_e_field.norm2() == 0.) {
176 << "ICC found zero electric field on a charge. This must "
177 "never happen";
178 }
179
180 auto const charge_density_old = p.q() / icc_cfg.areas[id];
181
182 charge_density_max =
183 std::max(charge_density_max, std::abs(charge_density_old));
184
185 auto const charge_density_update =
186 del_eps * pref * (local_e_field * icc_cfg.normals[id]) +
188 icc_cfg.sigmas[id];
189 /* relative variation: never use an estimator which can be negative
190 * here */
191 auto const charge_density_new =
192 (1. - icc_cfg.relaxation) * charge_density_old +
193 (icc_cfg.relaxation) * charge_density_update;
194
195 /* Take the largest error to check for convergence */
196 auto const relative_difference =
197 std::abs((charge_density_new - charge_density_old) /
198 (charge_density_max +
199 std::abs(charge_density_new + charge_density_old)));
200
201 max_rel_diff = std::max(max_rel_diff, relative_difference);
202
203 p.q() = charge_density_new * icc_cfg.areas[id];
204
205 /* check if the charge now is more than 1e6, to determine if ICC still
206 * leads to reasonable results. This is kind of an arbitrary measure
207 * but does a good job of spotting divergence! */
208 if (std::abs(p.q()) > 1e6) {
210 << "Particle with id " << p.id() << " has a charge (q=" << p.q()
211 << ") that is too large for the ICC algorithm";
212
213 max_rel_diff = std::numeric_limits<double>::max();
214 break;
215 }
216 }
217 }
218
219 /* Update charges on ghosts. */
220 cell_structure.ghosts_update(Cells::DATA_PART_PROPERTIES);
221#ifdef ESPRESSO_SHARED_MEMORY_PARALLELISM
222 // refresh local properties
223 update_aosoa_charges(cell_structure);
224#endif
225
227
228 boost::mpi::all_reduce(comm_cart, max_rel_diff, global_max_rel_diff,
229 boost::mpi::maximum<double>());
230
231 if (global_max_rel_diff < icc_cfg.convergence)
232 break;
233 }
234
235 if (global_max_rel_diff > icc_cfg.convergence) {
237 << "ICC failed to converge in the given number of maximal steps.";
238 }
239
240 system.on_particle_charge_change();
241}
242
244 if (n_icc <= 0)
245 throw std::domain_error("Parameter 'n_icc' must be >= 1");
246 if (convergence <= 0.)
247 throw std::domain_error("Parameter 'convergence' must be > 0");
248 if (relaxation < 0. or relaxation > 2.)
249 throw std::domain_error("Parameter 'relaxation' must be >= 0 and <= 2");
250 if (max_iterations <= 0)
251 throw std::domain_error("Parameter 'max_iterations' must be > 0");
252 if (first_id < 0)
253 throw std::domain_error("Parameter 'first_id' must be >= 0");
254 if (eps_out <= 0.)
255 throw std::domain_error("Parameter 'eps_out' must be > 0");
256 if (areas.size() != static_cast<std::size_t>(n_icc))
257 throw std::invalid_argument("Parameter 'areas' has incorrect shape");
258 if (epsilons.size() != static_cast<std::size_t>(n_icc))
259 throw std::invalid_argument("Parameter 'epsilons' has incorrect shape");
260 if (sigmas.size() != static_cast<std::size_t>(n_icc))
261 throw std::invalid_argument("Parameter 'sigmas' has incorrect shape");
262 if (normals.size() != static_cast<std::size_t>(n_icc))
263 throw std::invalid_argument("Parameter 'normals' has incorrect shape");
264}
265
267 data.sanity_checks();
268 icc_cfg = std::move(data);
269}
270
272 sanity_check();
273 auto &system = get_system();
274 system.on_particle_charge_change();
275}
276
278 template <typename T> void operator()(std::shared_ptr<T> const &) const {}
279#ifdef ESPRESSO_P3M
280#ifdef ESPRESSO_CUDA
281 void operator()(std::shared_ptr<CoulombP3M> const &p) const {
282 if (p->is_gpu()) {
283 throw std::runtime_error("ICC does not work with P3MGPU");
284 }
285 }
286#endif // ESPRESSO_CUDA
287 void
288 operator()(std::shared_ptr<ElectrostaticLayerCorrection> const &actor) const {
289 if (actor->elc.dielectric_contrast_on) {
290 throw std::runtime_error("ICC conflicts with ELC dielectric contrast");
291 }
292 std::visit(*this, actor->base_solver);
293 }
294#endif // ESPRESSO_P3M
295 [[noreturn]] void operator()(std::shared_ptr<DebyeHueckel> const &) const {
296 throw std::runtime_error("ICC does not work with DebyeHueckel.");
297 }
298 [[noreturn]] void operator()(std::shared_ptr<ReactionField> const &) const {
299 throw std::runtime_error("ICC does not work with ReactionField.");
300 }
301};
302
305#ifdef ESPRESSO_NPT
306 if (get_system().has_npt_enabled()) {
307 throw std::runtime_error("ICC does not work in the NpT ensemble");
308 }
309#endif
310}
311
313 auto &system = get_system();
314 if (system.coulomb.impl->solver) {
315 std::visit(SanityChecksICC(), *system.coulomb.impl->solver);
316 } else {
317 throw std::runtime_error("An electrostatics solver is needed by ICC");
318 }
319}
320
322 return coulomb.impl->extension and
323 std::holds_alternative<std::shared_ptr<ICCStar>>(
324 *coulomb.impl->extension);
325}
326
328 if (coulomb.impl->extension) {
329 if (auto icc = std::get_if<std::shared_ptr<ICCStar>>(
330 get_ptr(coulomb.impl->extension))) {
331 (**icc).iteration();
332 }
333 }
334}
335
336#endif // ESPRESSO_ELECTROSTATICS
Describes a cell structure / cell system.
void for_each_local_particle(ParticleUnaryOp &&f, bool parallel=true) const
Run a kernel on all local particles.
void non_bonded_loop(PairKernel pair_kernel)
Non-bonded pair loop.
void for_each_ghost_particle(ParticleUnaryOp &&f) const
Run a kernel on all ghost particles.
void update_icc_particles()
Definition icc.cpp:327
bool has_icc_enabled() const
Definition icc.cpp:321
boost::mpi::communicator comm_cart
The communicator.
const T * get_ptr(std::optional< T > const &opt)
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
static void force_calc_icc(CellStructure &cell_structure, Coulomb::ShortRangeForceKernel::result_type const &coulomb_kernel, Coulomb::ShortRangeForceCorrectionsKernel::result_type const &elc_kernel)
Calculate the electrostatic forces between source charges (= real charges) and wall charges.
Definition icc.cpp:73
ICC is a method that allows to take into account the influence of arbitrarily shaped dielectric inter...
@ DATA_PART_PROPERTIES
Particle::p.
P3M algorithm for long-range Coulomb interaction.
ESPRESSO_ATTR_ALWAYS_INLINE void update_aosoa_charges(CellStructure &cell_structure)
std::optional< kernel_type > result_type
std::optional< kernel_type > result_type
Distance vector and length handed to pair kernels.
void sanity_checks_active_solver() const
Definition icc.cpp:312
void iteration()
The main iterative scheme, where the surface element charges are calculated self-consistently.
Definition icc.cpp:105
icc_data icc_cfg
ICC parameters.
Definition icc.hpp:92
void on_activation() const
Definition icc.cpp:271
void sanity_check() const
Definition icc.cpp:303
ICCStar(icc_data data)
Definition icc.cpp:266
Struct holding all information for one particle.
Definition Particle.hpp:450
void operator()(std::shared_ptr< T > const &) const
Definition icc.cpp:278
void operator()(std::shared_ptr< ReactionField > const &) const
Definition icc.cpp:298
void operator()(std::shared_ptr< CoulombP3M > const &p) const
Definition icc.cpp:281
void operator()(std::shared_ptr< DebyeHueckel > const &) const
Definition icc.cpp:295
void operator()(std::shared_ptr< ElectrostaticLayerCorrection > const &actor) const
Definition icc.cpp:288
ICC data structure.
Definition icc.hpp:61
double convergence
convergence criteria
Definition icc.hpp:75
int first_id
first ICC particle id
Definition icc.hpp:85
double relaxation
relaxation parameter
Definition icc.hpp:81
std::vector< Utils::Vector3d > normals
surface normal vectors
Definition icc.hpp:77
int max_iterations
maximum number of iterations
Definition icc.hpp:65
int n_icc
First id of ICC particle.
Definition icc.hpp:63
double eps_out
bulk dielectric constant
Definition icc.hpp:67
void sanity_checks() const
Definition icc.cpp:243
std::vector< double > sigmas
surface charge density of the particles
Definition icc.hpp:73
std::vector< double > epsilons
dielectric constants of the particles
Definition icc.hpp:71
Utils::Vector3d ext_field
external electric field
Definition icc.hpp:79
int citeration
last number of iterations
Definition icc.hpp:83
std::vector< double > areas
areas of the particles
Definition icc.hpp:69