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
37#ifdef ESPRESSO_CALIPER
38#include <caliper/cali.h>
39#endif
40
41#include <cmath>
42#include <cstdint>
43#include <initializer_list>
44#include <limits>
45#include <ranges>
46#include <stdexcept>
47#include <vector>
48
50 double lb_gamma) {
51#ifdef ESPRESSO_THERMOSTAT_PER_PARTICLE
52 auto const &partcl_gamma = p.gamma();
53#ifdef ESPRESSO_PARTICLE_ANISOTROPY
54 auto const default_gamma = Thermostat::GammaType::broadcast(lb_gamma);
55#else
56 auto const default_gamma = lb_gamma;
57#endif // ESPRESSO_PARTICLE_ANISOTROPY
58 return Thermostat::handle_particle_gamma(partcl_gamma, default_gamma);
59#else
60 return lb_gamma;
61#endif // ESPRESSO_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 ESPRESSO_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
120 // Lees-Edwards: pre-calc positional offset folded into the simulation box
121 double folded_le_offset = 0.;
122 auto const &le = box_geo.lees_edwards_bc();
123 if (box_geo.type() == BoxType::LEES_EDWARDS) {
124 folded_le_offset = Algorithm::periodic_fold(
125 le.pos_offset, box_geo.length()[le.shear_direction]);
126 }
127
128 for (int i : {-1, 0, 1}) {
129 for (int j : {-1, 0, 1}) {
130 for (int k : {-1, 0, 1}) {
131 Utils::Vector3d shift{{double(i), double(j), double(k)}};
132
133 auto pos_shifted = pos_folded;
134 // Lees Edwards: folded position incl. LE pos offset
135 // This is needed to ensure that the position from which `pos_shifted`
136 // is calculated below, is always in the primary simulation box.
137 if (box_geo.type() == BoxType::LEES_EDWARDS) {
138 pos_shifted[le.shear_direction] = Algorithm::periodic_fold(
139 pos_folded[le.shear_direction] +
140 shift[le.shear_plane_normal] * folded_le_offset,
141 box_geo.length()[le.shear_direction]);
142 }
143 pos_shifted += Utils::hadamard_product(box_geo.length(), shift);
144
145 if (in_box(pos_shifted, halo_lower_corner, halo_upper_corner)) {
146 res.emplace_back(pos_shifted);
147 }
148 }
149 }
150 }
151}
152
153/**
154 * @brief Return a vector of positions shifted by +,- box length in each
155 * coordinate
156 */
157std::vector<Utils::Vector3d> positions_in_halo(Utils::Vector3d const &pos,
158 BoxGeometry const &box_geo,
159 LocalBox const &local_box,
160 double agrid) {
161 auto const halo = 0.5 * agrid;
162 auto const halo_vec = Utils::Vector3d::broadcast(halo);
163 auto const fully_inside_lower = local_box.my_left() + 2. * halo_vec;
164 auto const fully_inside_upper = local_box.my_right() - 2. * halo_vec;
165 auto const pos_folded = box_geo.folded_position(pos);
166 if (in_box(pos_folded, fully_inside_lower, fully_inside_upper)) {
167 return {pos_folded};
168 }
169 auto const halo_lower_corner = local_box.my_left() - halo_vec;
170 auto const halo_upper_corner = local_box.my_right() + halo_vec;
171 std::vector<Utils::Vector3d> res;
172 positions_in_halo_impl(pos_folded, halo_lower_corner, halo_upper_corner,
173 box_geo, res);
174 return res;
175}
176
177static auto lees_edwards_vel_shift(Utils::Vector3d const &pos_shifted_by_box_l,
178 Utils::Vector3d const &orig_pos,
179 BoxGeometry const &box_geo) {
180 Utils::Vector3d vel_shift{{0., 0., 0.}};
181 if (box_geo.type() == BoxType::LEES_EDWARDS) {
182 auto le = box_geo.lees_edwards_bc();
183 auto normal_shift =
184 (pos_shifted_by_box_l - orig_pos)[le.shear_plane_normal];
185 // normal_shift is +,- box_l or 0 up to floating point errors
186 if (normal_shift > std::numeric_limits<double>::epsilon()) {
187 vel_shift[le.shear_direction] -= le.shear_velocity;
188 }
189 if (normal_shift < -std::numeric_limits<double>::epsilon()) {
190 vel_shift[le.shear_direction] += le.shear_velocity;
191 }
192 }
193 return vel_shift;
194}
195
196namespace LB {
197
199 if (not m_thermalized) {
200 return Utils::Vector3d{};
201 }
202 using std::sqrt;
203 using Utils::sqrt;
204 auto const gamma = lb_handle_particle_anisotropy(p, m_thermostat.gamma);
205 auto const noise = Random::noise_uniform<RNGSalt::PARTICLES>(
206 m_thermostat.rng_counter(), m_thermostat.rng_seed(), p.id());
207 return m_noise_pref_wo_gamma * Utils::hadamard_product(sqrt(gamma), noise);
208}
209
210void ParticleCoupling::kernel(std::vector<Particle *> const &particles) {
211 if (particles.empty()) {
212 return;
213 }
214 enum coupling_modes { none, particle_force, swimmer_force_on_fluid };
215 auto const halo = 0.5 * m_lb.get_agrid();
216 auto const halo_vec = Utils::Vector3d::broadcast(halo);
217 auto const fully_inside_lower = m_local_box.my_left() + 2. * halo_vec;
218 auto const fully_inside_upper = m_local_box.my_right() - 2. * halo_vec;
219 auto const halo_lower_corner = m_local_box.my_left() - halo_vec;
220 auto const halo_upper_corner = m_local_box.my_right() + halo_vec;
221 std::vector<Utils::Vector3d> positions_velocity_coupling;
222 std::vector<Utils::Vector3d> positions_force_coupling;
223 std::vector<Utils::Vector3d> force_coupling_forces;
224 std::vector<uint8_t> positions_force_coupling_counter;
225 std::vector<Particle *> coupled_particles;
226 for (auto ptr : particles) {
227 auto &p = *ptr;
228 auto span_size = uint8_t{1u};
229 auto const folded_pos = m_box_geo.folded_position(p.pos());
230 if (in_box(folded_pos, fully_inside_lower, fully_inside_upper)) {
231 positions_force_coupling.emplace_back(folded_pos);
232 } else {
233 auto const old_size = positions_force_coupling.size();
234 positions_in_halo_impl(folded_pos, halo_lower_corner, halo_upper_corner,
235 m_box_geo, positions_force_coupling);
236 auto const new_size = positions_force_coupling.size();
237 span_size = static_cast<uint8_t>(new_size - old_size);
238 }
239 auto coupling_mode = none;
240#ifdef ESPRESSO_ENGINE
241 if (p.swimming().is_engine_force_on_fluid) {
242 coupling_mode = swimmer_force_on_fluid;
243 }
244#endif
245 if (coupling_mode == none) {
246 for (auto end = positions_force_coupling.end();
247 auto const &pos : std::views::counted(end - span_size, span_size)) {
248 if (pos >= halo_lower_corner and pos < halo_upper_corner) {
249 positions_velocity_coupling.emplace_back(pos);
250 coupling_mode = particle_force;
251 break;
252 }
253 }
254 }
255 if (coupling_mode == none) {
256 positions_force_coupling.erase(positions_force_coupling.end() - span_size,
257 positions_force_coupling.end());
258 } else {
259 coupled_particles.emplace_back(ptr);
260 positions_force_coupling_counter.emplace_back(span_size);
261 }
262 }
263
264 if (coupled_particles.empty()) {
265 return;
266 }
267 auto interpolated_velocities =
268 m_lb.get_coupling_interpolated_velocities(positions_velocity_coupling);
269
270 auto const &domain_lower_corner = m_local_box.my_left();
271 auto const &domain_upper_corner = m_local_box.my_right();
272 auto it_interpolated_velocities = interpolated_velocities.begin();
273 auto it_positions_force_coupling = positions_force_coupling.begin();
274 auto it_positions_velocity_coupling = positions_velocity_coupling.begin();
275 auto it_positions_force_coupling_counter =
276 positions_force_coupling_counter.begin();
277 for (auto ptr : coupled_particles) {
278 auto &p = *ptr;
279 auto coupling_mode = particle_force;
280#ifdef ESPRESSO_ENGINE
281 if (p.swimming().is_engine_force_on_fluid) {
282 coupling_mode = swimmer_force_on_fluid;
283 }
284#endif
285 Utils::Vector3d force_on_particle = {};
286 if (coupling_mode == particle_force) {
287#ifndef ESPRESSO_THERMOSTAT_PER_PARTICLE
288 if (m_thermostat.gamma > 0.)
289#endif
290 {
291 auto &v_fluid = *it_interpolated_velocities;
292 if (m_box_geo.type() == BoxType::LEES_EDWARDS) {
293 // Account for the case where the interpolated velocity has been read
294 // from a ghost of the particle across the LE boundary (or vice versa)
295 // Then the particle velocity is shifted by +,- the LE shear velocity
296 auto const vel_correction = lees_edwards_vel_shift(
297 *it_positions_velocity_coupling, p.pos(), m_box_geo);
298 v_fluid += vel_correction;
299 }
300 auto const drag_force = lb_drag_force(p, m_thermostat.gamma, v_fluid);
301 auto const random_force = get_noise_term(p);
302 force_on_particle = drag_force + random_force;
303 }
304 ++it_interpolated_velocities;
305 ++it_positions_velocity_coupling;
306 }
307
308 auto force_on_fluid = -force_on_particle;
309#ifdef ESPRESSO_ENGINE
310 if (coupling_mode == swimmer_force_on_fluid) {
311 force_on_fluid = p.calc_director() * p.swimming().f_swim;
312 }
313#endif
314
315 auto const span_size = *it_positions_force_coupling_counter;
316 ++it_positions_force_coupling_counter;
317 for (uint8_t i{0u}; i < span_size; ++i) {
318 auto &pos = *it_positions_force_coupling;
319 if (pos >= domain_lower_corner and pos < domain_upper_corner) {
320 /* Particle is in our LB volume, so this node
321 * is responsible to adding its force */
322 p.force() += force_on_particle;
323 }
324 force_coupling_forces.emplace_back(force_on_fluid);
325 ++it_positions_force_coupling;
326 }
327 }
328 m_lb.add_forces_at_pos(positions_force_coupling, force_coupling_forces);
329}
330
331#if defined(ESPRESSO_THERMOSTAT_PER_PARTICLE) and \
332 defined(ESPRESSO_PARTICLE_ANISOTROPY)
333static void lb_coupling_sanity_checks(Particle const &p) {
334 /*
335 lb does (at the moment) not support rotational particle coupling.
336 Consequently, anisotropic particles are also not supported.
337 */
338 auto const &p_gamma = p.gamma();
339 if (p_gamma[0] != p_gamma[1] or p_gamma[1] != p_gamma[2]) {
340 runtimeErrorMsg() << "anisotropic particle (id " << p.id()
341 << ") coupled to LB.";
342 }
343}
344#endif
345
346} // namespace LB
347
349#ifdef ESPRESSO_CALIPER
350 CALI_CXX_MARK_FUNCTION;
351#endif
352 assert(thermostat->lb != nullptr);
353 if (thermostat->lb->couple_to_md) {
354 if (not lb.is_solver_set()) {
355 runtimeErrorMsg() << "The LB thermostat requires a LB fluid";
356 return;
357 }
358 auto const real_particles = cell_structure->local_particles();
359 auto const ghost_particles = cell_structure->ghost_particles();
360 LB::ParticleCoupling coupling{*thermostat->lb, lb, *box_geo, *local_geo};
361 LB::CouplingBookkeeping bookkeeping{*cell_structure};
362 lb.ghost_communication_vel();
363 std::vector<Particle *> particles{};
364 for (auto const *particle_range : {&real_particles, &ghost_particles}) {
365 for (auto &p : *particle_range) {
366 if (not LB::is_tracer(p) and bookkeeping.should_be_coupled(p)) {
367#if defined(ESPRESSO_THERMOSTAT_PER_PARTICLE) and \
368 defined(ESPRESSO_PARTICLE_ANISOTROPY)
370#endif
371 particles.emplace_back(&p);
372 }
373 }
374 }
375 coupling.kernel(particles);
376 }
377}
@ 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:450
auto const & gamma() const
Definition Particle.hpp:622
auto const & v() const
Definition Particle.hpp:488
auto const & mu_E() const
Definition Particle.hpp:599
auto const & id() const
Definition Particle.hpp:469