ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
particle_coupling.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-2022 The ESPResSo project
3 *
4 * This file is part of ESPResSo.
5 *
6 * ESPResSo is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * ESPResSo is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
21#include "BoxGeometry.hpp"
22#include "LocalBox.hpp"
23#include "Particle.hpp"
25#include "communication.hpp"
26#include "config/config.hpp"
27#include "errorhandling.hpp"
28#include "random.hpp"
29#include "system/System.hpp"
30#include "thermostat.hpp"
31
32#include <utils/Counter.hpp>
33#include <utils/Vector.hpp>
34
35#include <boost/mpi.hpp>
36#include <boost/serialization/optional.hpp>
37
38#ifdef CALIPER
39#include <caliper/cali.h>
40#endif
41
42#include <cmath>
43#include <cstdint>
44#include <initializer_list>
45#include <limits>
46#include <stdexcept>
47#include <vector>
48
50 double lb_gamma) {
51#ifdef THERMOSTAT_PER_PARTICLE
52 auto const &partcl_gamma = p.gamma();
53#ifdef PARTICLE_ANISOTROPY
54 auto const default_gamma = Thermostat::GammaType::broadcast(lb_gamma);
55#else
56 auto const default_gamma = lb_gamma;
57#endif // PARTICLE_ANISOTROPY
58 return Thermostat::handle_particle_gamma(partcl_gamma, default_gamma);
59#else
60 return lb_gamma;
61#endif // THERMOSTAT_PER_PARTICLE
62}
63
64static Utils::Vector3d lb_drag_force(Particle const &p, double lb_gamma,
65 Utils::Vector3d const &v_fluid) {
66#ifdef LB_ELECTROHYDRODYNAMICS
67 auto const v_drift = v_fluid + p.mu_E();
68#else
69 auto const &v_drift = v_fluid;
70#endif
71 auto const gamma = lb_handle_particle_anisotropy(p, lb_gamma);
72
73 /* calculate viscous force (eq. (9) @cite ahlrichs99a) */
74 return Utils::hadamard_product(gamma, v_drift - p.v());
75}
76
77Utils::Vector3d lb_drag_force(LB::Solver const &lb, double lb_gamma,
78 Particle const &p,
79 Utils::Vector3d const &shifted_pos) {
80 auto const v_fluid = lb.get_coupling_interpolated_velocity(shifted_pos);
81 return lb_drag_force(p, lb_gamma, v_fluid);
82}
83
84/**
85 * @brief Check if a position is within the local box + halo.
86 *
87 * @param local_box Local geometry
88 * @param pos Position to check
89 * @param halo Halo
90 *
91 * @return True iff the point is inside of the box up to halo.
92 */
93static bool in_local_domain(LocalBox const &local_box,
94 Utils::Vector3d const &pos, double halo = 0.) {
95 auto const halo_vec = Utils::Vector3d::broadcast(halo);
96 auto const lower_corner = local_box.my_left() - halo_vec;
97 auto const upper_corner = local_box.my_right() + halo_vec;
98
99 return pos >= lower_corner and pos < upper_corner;
100}
101
102static bool in_box(Utils::Vector3d const &pos,
103 Utils::Vector3d const &lower_corner,
104 Utils::Vector3d const &upper_corner) {
105 return pos >= lower_corner and pos < upper_corner;
106}
107
108bool in_local_halo(LocalBox const &local_box, Utils::Vector3d const &pos,
109 double agrid) {
110 auto const halo = 0.5 * agrid;
111 return in_local_domain(local_box, pos, halo);
112}
113
114static void positions_in_halo_impl(Utils::Vector3d const &pos_folded,
115 Utils::Vector3d const &halo_lower_corner,
116 Utils::Vector3d const &halo_upper_corner,
117 BoxGeometry const &box_geo,
118 std::vector<Utils::Vector3d> &res) {
119 for (int i : {-1, 0, 1}) {
120 for (int j : {-1, 0, 1}) {
121 for (int k : {-1, 0, 1}) {
122 Utils::Vector3d shift{{double(i), double(j), double(k)}};
123 Utils::Vector3d pos_shifted =
124 pos_folded + Utils::hadamard_product(box_geo.length(), shift);
125
126 if (box_geo.type() == BoxType::LEES_EDWARDS) {
127 auto le = box_geo.lees_edwards_bc();
128 auto normal_shift = (pos_shifted - pos_folded)[le.shear_plane_normal];
129 if (normal_shift > std::numeric_limits<double>::epsilon())
130 pos_shifted[le.shear_direction] += le.pos_offset;
131 if (normal_shift < -std::numeric_limits<double>::epsilon())
132 pos_shifted[le.shear_direction] -= le.pos_offset;
133 }
134
135 if (in_box(pos_shifted, halo_lower_corner, halo_upper_corner)) {
136 res.emplace_back(pos_shifted);
137 }
138 }
139 }
140 }
141}
142
143/**
144 * @brief Return a vector of positions shifted by +,- box length in each
145 * coordinate
146 */
147std::vector<Utils::Vector3d> positions_in_halo(Utils::Vector3d const &pos,
148 BoxGeometry const &box_geo,
149 LocalBox const &local_box,
150 double agrid) {
151 auto const halo = 0.5 * agrid;
152 auto const halo_vec = Utils::Vector3d::broadcast(halo);
153 auto const fully_inside_lower = local_box.my_left() + 2. * halo_vec;
154 auto const fully_inside_upper = local_box.my_right() - 2. * halo_vec;
155 auto const pos_folded = box_geo.folded_position(pos);
156 if (in_box(pos_folded, fully_inside_lower, fully_inside_upper)) {
157 return {pos_folded};
158 }
159 auto const halo_lower_corner = local_box.my_left() - halo_vec;
160 auto const halo_upper_corner = local_box.my_right() + halo_vec;
161 std::vector<Utils::Vector3d> res;
162 positions_in_halo_impl(pos_folded, halo_lower_corner, halo_upper_corner,
163 box_geo, res);
164 return res;
165}
166
167namespace LB {
168
170 if (not m_thermalized) {
171 return Utils::Vector3d{};
172 }
173 using std::sqrt;
174 using Utils::sqrt;
175 auto const gamma = lb_handle_particle_anisotropy(p, m_thermostat.gamma);
176 auto const noise = Random::noise_uniform<RNGSalt::PARTICLES>(
177 m_thermostat.rng_counter(), m_thermostat.rng_seed(), p.id());
178 return m_noise_pref_wo_gamma * Utils::hadamard_product(sqrt(gamma), noise);
179}
180
181void ParticleCoupling::kernel(std::vector<Particle *> const &particles) {
182 if (particles.empty()) {
183 return;
184 }
185 enum coupling_modes { none, particle_force, swimmer_force_on_fluid };
186 auto const halo = 0.5 * m_lb.get_agrid();
187 auto const halo_vec = Utils::Vector3d::broadcast(halo);
188 auto const fully_inside_lower = m_local_box.my_left() + 2. * halo_vec;
189 auto const fully_inside_upper = m_local_box.my_right() - 2. * halo_vec;
190 auto const halo_lower_corner = m_local_box.my_left() - halo_vec;
191 auto const halo_upper_corner = m_local_box.my_right() + halo_vec;
192 std::vector<Utils::Vector3d> positions_velocity_coupling;
193 std::vector<Utils::Vector3d> positions_force_coupling;
194 std::vector<Utils::Vector3d> force_coupling_forces;
195 std::vector<uint8_t> positions_force_coupling_counter;
196 std::vector<Particle *> coupled_particles;
197 for (auto ptr : particles) {
198 auto &p = *ptr;
199 auto span_size = 1u;
200 auto const folded_pos = m_box_geo.folded_position(p.pos());
201 if (in_box(folded_pos, fully_inside_lower, fully_inside_upper)) {
202 positions_force_coupling.emplace_back(folded_pos);
203 } else {
204 auto const old_size = positions_force_coupling.size();
205 positions_in_halo_impl(folded_pos, halo_lower_corner, halo_upper_corner,
206 m_box_geo, positions_force_coupling);
207 auto const new_size = positions_force_coupling.size();
208 span_size = static_cast<uint8_t>(new_size - old_size);
209 }
210 auto coupling_mode = none;
211#ifdef ENGINE
212 if (p.swimming().is_engine_force_on_fluid) {
213 coupling_mode = swimmer_force_on_fluid;
214 }
215#endif
216 if (coupling_mode == none) {
217 for (auto end = positions_force_coupling.end(), it = end - span_size;
218 it != end; ++it) {
219 auto const &pos = *it;
220 if (pos >= halo_lower_corner and pos < halo_upper_corner) {
221 positions_velocity_coupling.emplace_back(pos);
222 coupling_mode = particle_force;
223 break;
224 }
225 }
226 }
227 if (coupling_mode == none) {
228 positions_force_coupling.erase(positions_force_coupling.end() - span_size,
229 positions_force_coupling.end());
230 } else {
231 coupled_particles.emplace_back(ptr);
232 positions_force_coupling_counter.emplace_back(span_size);
233 }
234 }
235
236 if (coupled_particles.empty()) {
237 return;
238 }
239 auto interpolated_velocities =
240 m_lb.get_coupling_interpolated_velocities(positions_velocity_coupling);
241
242 auto const &domain_lower_corner = m_local_box.my_left();
243 auto const &domain_upper_corner = m_local_box.my_right();
244 auto it_interpolated_velocities = interpolated_velocities.begin();
245 auto it_positions_force_coupling = positions_force_coupling.begin();
246 auto it_positions_force_coupling_counter =
247 positions_force_coupling_counter.begin();
248 for (auto ptr : coupled_particles) {
249 auto &p = *ptr;
250 auto coupling_mode = particle_force;
251#ifdef ENGINE
252 if (p.swimming().is_engine_force_on_fluid) {
253 coupling_mode = swimmer_force_on_fluid;
254 }
255#endif
256 Utils::Vector3d force_on_particle = {};
257 if (coupling_mode == particle_force) {
258 auto const &v_fluid = *it_interpolated_velocities;
259 auto const drag_force = lb_drag_force(p, m_thermostat.gamma, v_fluid);
260 auto const random_force = get_noise_term(p);
261 force_on_particle = drag_force + random_force;
262 ++it_interpolated_velocities;
263 }
264
265 auto force_on_fluid = -force_on_particle;
266#ifdef ENGINE
267 if (coupling_mode == swimmer_force_on_fluid) {
268 force_on_fluid = p.calc_director() * p.swimming().f_swim;
269 }
270#endif
271
272 auto const span_size = *it_positions_force_coupling_counter;
273 ++it_positions_force_coupling_counter;
274 for (uint8_t i{0u}; i < span_size; ++i) {
275 auto &pos = *it_positions_force_coupling;
276 if (pos >= domain_lower_corner and pos < domain_upper_corner) {
277 /* Particle is in our LB volume, so this node
278 * is responsible to adding its force */
279 p.force() += force_on_particle;
280 }
281 force_coupling_forces.emplace_back(force_on_fluid);
282 ++it_positions_force_coupling;
283 }
284 }
285 m_lb.add_forces_at_pos(positions_force_coupling, force_coupling_forces);
286}
287
288#if defined(THERMOSTAT_PER_PARTICLE) and defined(PARTICLE_ANISOTROPY)
289static void lb_coupling_sanity_checks(Particle const &p) {
290 /*
291 lb does (at the moment) not support rotational particle coupling.
292 Consequently, anisotropic particles are also not supported.
293 */
294 auto const &p_gamma = p.gamma();
295 if (p_gamma[0] != p_gamma[1] or p_gamma[1] != p_gamma[2]) {
296 runtimeErrorMsg() << "anisotropic particle (id " << p.id()
297 << ") coupled to LB.";
298 }
299}
300#endif
301
302} // namespace LB
303
305#ifdef CALIPER
306 CALI_CXX_MARK_FUNCTION;
307#endif
308 assert(thermostat->lb != nullptr);
309 if (thermostat->lb->couple_to_md) {
310 if (not lb.is_solver_set()) {
311 runtimeErrorMsg() << "The LB thermostat requires a LB fluid";
312 return;
313 }
314 auto const real_particles = cell_structure->local_particles();
315 auto const ghost_particles = cell_structure->ghost_particles();
316 LB::ParticleCoupling coupling{*thermostat->lb, lb, *box_geo, *local_geo};
317 LB::CouplingBookkeeping bookkeeping{*cell_structure};
318 std::vector<Particle *> particles{};
319 for (auto const *particle_range : {&real_particles, &ghost_particles}) {
320 for (auto &p : *particle_range) {
321 if (not LB::is_tracer(p) and bookkeeping.should_be_coupled(p)) {
322#if defined(THERMOSTAT_PER_PARTICLE) and defined(PARTICLE_ANISOTROPY)
324#endif
325 particles.emplace_back(&p);
326 }
327 }
328 }
329 coupling.kernel(particles);
330 }
331}
@ LEES_EDWARDS
Vector implementation and trait types for boost qvm interoperability.
__shared__ int pos[MAXDEPTH *THREADS5/WARPSIZE]
__shared__ float res[]
float u[3]
Utils::Vector3d const & length() const
Box length.
LeesEdwardsBC const & lees_edwards_bc() const
auto folded_position(Utils::Vector3d const &p) const
Calculate coordinates folded to primary simulation box.
BoxType type() const
Keep track of ghost particles that have already been coupled once.
void kernel(std::vector< Particle * > const &particles)
Utils::Vector3d get_noise_term(Particle const &p) const
auto const & my_right() const
Right (top, back) corner of this nodes local box.
Definition LocalBox.hpp:47
auto const & my_left() const
Left (bottom, front) corner of this nodes local box.
Definition LocalBox.hpp:45
void lb_couple_particles()
Calculate particle-lattice interactions.
static Vector< T, N > broadcast(T const &s)
Create a vector that has all entries set to one value.
Definition Vector.hpp:110
This file contains the defaults for ESPResSo.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
static void lb_coupling_sanity_checks(Particle const &p)
bool is_tracer(Particle const &p)
auto const & handle_particle_gamma(GammaType const &particle_gamma, GammaType const &default_gamma)
Vector< T, N > sqrt(Vector< T, N > const &a)
Definition Vector.hpp:342
auto hadamard_product(Vector< T, N > const &a, Vector< U, N > const &b)
Definition Vector.hpp:364
bool in_local_halo(LocalBox const &local_box, Utils::Vector3d const &pos, double agrid)
Check if a position is within the local LB domain plus halo.
static bool in_box(Utils::Vector3d const &pos, Utils::Vector3d const &lower_corner, Utils::Vector3d const &upper_corner)
static bool in_local_domain(LocalBox const &local_box, Utils::Vector3d const &pos, double halo=0.)
Check if a position is within the local box + halo.
static void positions_in_halo_impl(Utils::Vector3d const &pos_folded, Utils::Vector3d const &halo_lower_corner, Utils::Vector3d const &halo_upper_corner, BoxGeometry const &box_geo, std::vector< Utils::Vector3d > &res)
std::vector< Utils::Vector3d > positions_in_halo(Utils::Vector3d const &pos, BoxGeometry const &box_geo, LocalBox const &local_box, double agrid)
Return a vector of positions shifted by +,- box length in each coordinate.
static Utils::Vector3d lb_drag_force(Particle const &p, double lb_gamma, Utils::Vector3d const &v_fluid)
static Thermostat::GammaType lb_handle_particle_anisotropy(Particle const &p, double lb_gamma)
Random number generation using Philox.
uint64_t rng_counter() const
Get current value of the RNG.
uint32_t rng_seed() const
double gamma
Friction coefficient.
Utils::Vector3d get_coupling_interpolated_velocity(Utils::Vector3d const &pos) const
Calculate the interpolated fluid velocity in MD units.
void add_forces_at_pos(std::vector< Utils::Vector3d > const &pos, std::vector< Utils::Vector3d > const &forces)
double get_agrid() const
Get the LB grid spacing.
std::vector< Utils::Vector3d > get_coupling_interpolated_velocities(std::vector< Utils::Vector3d > const &pos) const
Struct holding all information for one particle.
Definition Particle.hpp:393
auto const & gamma() const
Definition Particle.hpp:529
auto const & v() const
Definition Particle.hpp:431
auto const & mu_E() const
Definition Particle.hpp:512
auto const & id() const
Definition Particle.hpp:412