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
20#include <config/config.hpp>
21
22#include "BoxGeometry.hpp"
23#include "LocalBox.hpp"
24#include "Particle.hpp"
26#include "communication.hpp"
27#include "config/config.hpp"
28#include "errorhandling.hpp"
30#include "random.hpp"
31#include "system/System.hpp"
32#include "thermostat.hpp"
33
34#include <utils/Counter.hpp>
35#include <utils/Vector.hpp>
36
37#include <boost/mpi.hpp>
38
39#ifdef ESPRESSO_CALIPER
40#include <caliper/cali.h>
41#endif
42
43#include <cmath>
44#include <cstdint>
45#include <initializer_list>
46#include <limits>
47#include <ranges>
48#include <stdexcept>
49#include <vector>
50
52 double lb_gamma) {
53#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
54 auto const &partcl_gamma = p.gamma();
55#ifdef ESPRESSO_PARTICLE_ANISOTROPY
56 auto const default_gamma = Thermostat::GammaType::broadcast(lb_gamma);
57#else
58 auto const default_gamma = lb_gamma;
59#endif // ESPRESSO_PARTICLE_ANISOTROPY
60 return Thermostat::handle_particle_gamma(partcl_gamma, default_gamma);
61#else
62 return lb_gamma;
63#endif // ESPRESSO_THERMOSTAT_PER_PARTICLE
64}
65
66static Utils::Vector3d lb_drag_force(Particle const &p, double lb_gamma,
67 Utils::Vector3d const &v_fluid) {
68#ifdef ESPRESSO_LB_ELECTROHYDRODYNAMICS
69 auto const v_drift = v_fluid + p.mu_E();
70#else
71 auto const &v_drift = v_fluid;
72#endif
73 auto const gamma = lb_handle_particle_anisotropy(p, lb_gamma);
74
75 /* calculate viscous force (eq. (9) @cite ahlrichs99a) */
76 return Utils::hadamard_product(gamma, v_drift - p.v());
77}
78
79Utils::Vector3d lb_drag_force(LB::Solver const &lb, double lb_gamma,
80 Particle const &p,
81 Utils::Vector3d const &shifted_pos) {
82 auto const v_fluid = lb.get_coupling_interpolated_velocity(shifted_pos);
83 return lb_drag_force(p, lb_gamma, v_fluid);
84}
85
86/**
87 * @brief Check if a position is within the local box + halo.
88 *
89 * @param local_box Local geometry
90 * @param pos Position to check
91 * @param halo Halo
92 *
93 * @return True iff the point is inside of the box up to halo.
94 */
95static bool in_local_domain(LocalBox const &local_box,
96 Utils::Vector3d const &pos, double halo = 0.) {
97 auto const halo_vec = Utils::Vector3d::broadcast(halo);
98 auto const lower_corner = local_box.my_left() - halo_vec;
99 auto const upper_corner = local_box.my_right() + halo_vec;
100
101 return pos >= lower_corner and pos < upper_corner;
102}
103
104static bool in_box(Utils::Vector3d const &pos,
105 Utils::Vector3d const &lower_corner,
106 Utils::Vector3d const &upper_corner) {
107 return pos >= lower_corner and pos < upper_corner;
108}
109
110bool in_local_halo(LocalBox const &local_box, Utils::Vector3d const &pos,
111 double agrid) {
112 auto const halo = 0.5 * agrid;
113 return in_local_domain(local_box, pos, halo);
114}
115
116static void positions_in_halo_impl(Utils::Vector3d const &pos_folded,
117 Utils::Vector3d const &halo_lower_corner,
118 Utils::Vector3d const &halo_upper_corner,
119 BoxGeometry const &box_geo,
120 std::vector<Utils::Vector3d> &res) {
121
122 // Lees-Edwards: pre-calc positional offset folded into the simulation box
123 double folded_le_offset = 0.;
124 auto const &le = box_geo.lees_edwards_bc();
125 if (box_geo.type() == BoxType::LEES_EDWARDS) {
126 folded_le_offset = Algorithm::periodic_fold(
127 le.pos_offset, box_geo.length()[le.shear_direction]);
128 }
129
130 for (int i : {-1, 0, 1}) {
131 for (int j : {-1, 0, 1}) {
132 for (int k : {-1, 0, 1}) {
133 Utils::Vector3d shift{{double(i), double(j), double(k)}};
134
135 auto pos_shifted = pos_folded;
136 // Lees Edwards: folded position incl. LE pos offset
137 // This is needed to ensure that the position from which `pos_shifted`
138 // is calculated below, is always in the primary simulation box.
139 if (box_geo.type() == BoxType::LEES_EDWARDS) {
140 pos_shifted[le.shear_direction] = Algorithm::periodic_fold(
141 pos_folded[le.shear_direction] +
142 shift[le.shear_plane_normal] * folded_le_offset,
143 box_geo.length()[le.shear_direction]);
144 }
145 pos_shifted += Utils::hadamard_product(box_geo.length(), shift);
146
147 if (in_box(pos_shifted, halo_lower_corner, halo_upper_corner)) {
148 res.emplace_back(pos_shifted);
149 }
150 }
151 }
152 }
153}
154
155/**
156 * @brief Return a vector of positions shifted by +,- box length in each
157 * coordinate
158 */
159std::vector<Utils::Vector3d> positions_in_halo(Utils::Vector3d const &pos,
160 BoxGeometry const &box_geo,
161 LocalBox const &local_box,
162 double agrid) {
163 auto const halo = 0.5 * agrid;
164 auto const halo_vec = Utils::Vector3d::broadcast(halo);
165 auto const fully_inside_lower = local_box.my_left() + 2. * halo_vec;
166 auto const fully_inside_upper = local_box.my_right() - 2. * halo_vec;
167 auto const pos_folded = box_geo.folded_position(pos);
168 if (in_box(pos_folded, fully_inside_lower, fully_inside_upper)) {
169 return {pos_folded};
170 }
171 auto const halo_lower_corner = local_box.my_left() - halo_vec;
172 auto const halo_upper_corner = local_box.my_right() + halo_vec;
173 std::vector<Utils::Vector3d> res;
174 positions_in_halo_impl(pos_folded, halo_lower_corner, halo_upper_corner,
175 box_geo, res);
176 return res;
177}
178
179static auto lees_edwards_vel_shift(Utils::Vector3d const &pos_shifted_by_box_l,
180 Utils::Vector3d const &orig_pos,
181 BoxGeometry const &box_geo) {
182 Utils::Vector3d vel_shift{{0., 0., 0.}};
183 if (box_geo.type() == BoxType::LEES_EDWARDS) {
184 auto le = box_geo.lees_edwards_bc();
185 auto normal_shift =
186 (pos_shifted_by_box_l - orig_pos)[le.shear_plane_normal];
187 // normal_shift is +,- box_l or 0 up to floating point errors
188 if (normal_shift > std::numeric_limits<double>::epsilon()) {
189 vel_shift[le.shear_direction] -= le.shear_velocity;
190 }
191 if (normal_shift < -std::numeric_limits<double>::epsilon()) {
192 vel_shift[le.shear_direction] += le.shear_velocity;
193 }
194 }
195 return vel_shift;
196}
197
198namespace LB {
199
201 if (not m_thermalized) {
202 return Utils::Vector3d{};
203 }
204 using std::sqrt;
205 using Utils::sqrt;
206 auto const gamma = lb_handle_particle_anisotropy(p, m_thermostat.gamma);
207 auto const noise = Random::noise_uniform<RNGSalt::PARTICLES>(
208 m_thermostat.rng_counter(), m_thermostat.rng_seed(), p.id());
209 return m_noise_pref_wo_gamma * Utils::hadamard_product(sqrt(gamma), noise);
210}
211
212void ParticleCoupling::kernel(std::vector<Particle *> const &particles) {
213 if (particles.empty()) {
214 return;
215 }
216 enum coupling_modes { none, particle_force, swimmer_force_on_fluid };
217 auto const halo = 0.5 * m_lb.get_agrid();
218 auto const halo_vec = Utils::Vector3d::broadcast(halo);
219 auto const fully_inside_lower = m_local_box.my_left() + 2. * halo_vec;
220 auto const fully_inside_upper = m_local_box.my_right() - 2. * halo_vec;
221 auto const halo_lower_corner = m_local_box.my_left() - halo_vec;
222 auto const halo_upper_corner = m_local_box.my_right() + halo_vec;
223 std::vector<Utils::Vector3d> positions_velocity_coupling;
224 std::vector<Utils::Vector3d> positions_force_coupling;
225 std::vector<Utils::Vector3d> force_coupling_forces;
226 std::vector<uint8_t> positions_force_coupling_counter;
227 std::vector<Particle *> coupled_particles;
228 for (auto ptr : particles) {
229 auto &p = *ptr;
230 auto span_size = uint8_t{1u};
231 auto const folded_pos = m_box_geo.folded_position(p.pos());
232 if (in_box(folded_pos, fully_inside_lower, fully_inside_upper)) {
233 positions_force_coupling.emplace_back(folded_pos);
234 } else {
235 auto const old_size = positions_force_coupling.size();
236 positions_in_halo_impl(folded_pos, halo_lower_corner, halo_upper_corner,
237 m_box_geo, positions_force_coupling);
238 auto const new_size = positions_force_coupling.size();
239 span_size = static_cast<uint8_t>(new_size - old_size);
240 }
241 auto coupling_mode = none;
242#ifdef ESPRESSO_ENGINE
243 if (p.swimming().is_engine_force_on_fluid) {
244 coupling_mode = swimmer_force_on_fluid;
245 }
246#endif
247 if (coupling_mode == none) {
248 for (auto end = positions_force_coupling.end();
249 auto const &pos : std::views::counted(end - span_size, span_size)) {
250 if (pos >= halo_lower_corner and pos < halo_upper_corner) {
251 positions_velocity_coupling.emplace_back(pos);
252 coupling_mode = particle_force;
253 break;
254 }
255 }
256 }
257 if (coupling_mode == none) {
258 positions_force_coupling.erase(positions_force_coupling.end() - span_size,
259 positions_force_coupling.end());
260 } else {
261 coupled_particles.emplace_back(ptr);
262 positions_force_coupling_counter.emplace_back(span_size);
263 }
264 }
265
266 if (coupled_particles.empty()) {
267 return;
268 }
269 auto interpolated_velocities =
270 m_lb.get_coupling_interpolated_velocities(positions_velocity_coupling);
271
272 auto const &domain_lower_corner = m_local_box.my_left();
273 auto const &domain_upper_corner = m_local_box.my_right();
274 auto it_interpolated_velocities = interpolated_velocities.begin();
275 auto it_positions_force_coupling = positions_force_coupling.begin();
276 auto it_positions_velocity_coupling = positions_velocity_coupling.begin();
277 auto it_positions_force_coupling_counter =
278 positions_force_coupling_counter.begin();
279 for (auto ptr : coupled_particles) {
280 auto &p = *ptr;
281 auto coupling_mode = particle_force;
282#ifdef ESPRESSO_ENGINE
283 if (p.swimming().is_engine_force_on_fluid) {
284 coupling_mode = swimmer_force_on_fluid;
285 }
286#endif
287 Utils::Vector3d force_on_particle = {};
288 if (coupling_mode == particle_force) {
289#ifndef ESPRESSO_THERMOSTAT_PER_PARTICLE
290 if (m_thermostat.gamma > 0.)
291#endif
292 {
293 auto &v_fluid = *it_interpolated_velocities;
294 if (m_box_geo.type() == BoxType::LEES_EDWARDS) {
295 // Account for the case where the interpolated velocity has been read
296 // from a ghost of the particle across the LE boundary (or vice versa)
297 // Then the particle velocity is shifted by +,- the LE shear velocity
298 auto const vel_correction = lees_edwards_vel_shift(
299 *it_positions_velocity_coupling, p.pos(), m_box_geo);
300 v_fluid += vel_correction;
301 }
302 auto const drag_force = lb_drag_force(p, m_thermostat.gamma, v_fluid);
303 auto const random_force = get_noise_term(p);
304 force_on_particle = drag_force + random_force;
305 }
306 ++it_interpolated_velocities;
307 ++it_positions_velocity_coupling;
308 }
309
310 auto force_on_fluid = -force_on_particle;
311#ifdef ESPRESSO_ENGINE
312 if (coupling_mode == swimmer_force_on_fluid) {
313 force_on_fluid = p.calc_director() * p.swimming().f_swim;
314 }
315#endif
316
317 auto const span_size = *it_positions_force_coupling_counter;
318 ++it_positions_force_coupling_counter;
319 for (uint8_t i{0u}; i < span_size; ++i) {
320 auto &pos = *it_positions_force_coupling;
321 if (pos >= domain_lower_corner and pos < domain_upper_corner) {
322 /* Particle is in our LB volume, so this node
323 * is responsible to adding its force */
324 p.force() += force_on_particle;
325 }
326 force_coupling_forces.emplace_back(force_on_fluid);
327 ++it_positions_force_coupling;
328 }
329 }
330 m_lb.add_forces_at_pos(positions_force_coupling, force_coupling_forces);
331}
332
333#if defined(ESPRESSO_THERMOSTAT_PER_PARTICLE) and \
334 defined(ESPRESSO_PARTICLE_ANISOTROPY)
335static void lb_coupling_sanity_checks(Particle const &p) {
336 /*
337 lb does (at the moment) not support rotational particle coupling.
338 Consequently, anisotropic particles are also not supported.
339 */
340 auto const &p_gamma = p.gamma();
341 if (p_gamma[0] != p_gamma[1] or p_gamma[1] != p_gamma[2]) {
342 runtimeErrorMsg() << "anisotropic particle (id " << p.id()
343 << ") coupled to LB.";
344 }
345}
346#endif
347
348} // namespace LB
349
351#ifdef ESPRESSO_CALIPER
352 CALI_CXX_MARK_FUNCTION;
353#endif
354 assert(thermostat->lb != nullptr);
355 if (thermostat->lb->couple_to_md) {
356 if (not lb.is_solver_set()) {
357 runtimeErrorMsg() << "The LB thermostat requires a LB fluid";
358 return;
359 }
360 auto const real_particles = cell_structure->local_particles();
361 auto const ghost_particles = cell_structure->ghost_particles();
362 LB::ParticleCoupling coupling{*thermostat->lb, lb, *box_geo, *local_geo};
363 LB::CouplingBookkeeping bookkeeping{*cell_structure};
364 lb.ghost_communication_vel();
365 std::vector<Particle *> particles{};
366 for (auto const *particle_range : {&real_particles, &ghost_particles}) {
367 for (auto &p : *particle_range) {
368 if (not LB::is_tracer(p) and bookkeeping.should_be_coupled(p)) {
369#if defined(ESPRESSO_THERMOSTAT_PER_PARTICLE) and \
370 defined(ESPRESSO_PARTICLE_ANISOTROPY)
372#endif
373 particles.emplace_back(&p);
374 }
375 }
376 }
377 coupling.kernel(particles);
378 }
379}
@ LEES_EDWARDS
Vector implementation and trait types for boost qvm interoperability.
auto folded_position(Utils::Vector3d const &pos) const
Calculate coordinates folded to primary simulation box.
Utils::Vector3d const & length() const
Box length.
LeesEdwardsBC const & lees_edwards_bc() const
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 DEVICE_QUALIFIER constexpr Vector< T, N > broadcast(typename Base::value_type const &value) noexcept
Create a vector that has all entries set to the same value.
Definition Vector.hpp:132
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
auto periodic_fold(std::floating_point auto x, std::integral auto i, std::floating_point auto l)
Fold value into primary interval.
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)
auto hadamard_product(Vector< T, N > const &a, Vector< U, N > const &b)
Definition Vector.hpp:378
auto sqrt(Vector< T, N > const &a)
Definition Vector.hpp:358
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 auto lees_edwards_vel_shift(Utils::Vector3d const &pos_shifted_by_box_l, Utils::Vector3d const &orig_pos, BoxGeometry const &box_geo)
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
Calculate the interpolated fluid velocities in MD units.
Struct holding all information for one particle.
Definition Particle.hpp:435
auto const & gamma() const
Definition Particle.hpp:603
auto const & v() const
Definition Particle.hpp:473
auto const & mu_E() const
Definition Particle.hpp:584
auto const & id() const
Definition Particle.hpp:454