ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
LBFluid.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2021-2026 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#include <config/config.hpp>
20
21#ifdef ESPRESSO_WALBERLA
22
23#include "LBFluid.hpp"
26
27#include "core/BoxGeometry.hpp"
32
35
41
42#include <utils/Vector.hpp>
43#include <utils/matrix.hpp>
45
46#include <boost/mpi.hpp>
47#include <boost/mpi/collectives/all_reduce.hpp>
48#include <boost/mpi/collectives/broadcast.hpp>
49
50#include <algorithm>
51#include <cassert>
52#include <cstddef>
53#include <filesystem>
54#include <functional>
55#include <memory>
56#include <optional>
57#include <sstream>
58#include <stdexcept>
59#include <string>
60#include <unordered_map>
61#include <vector>
62
64
65std::unordered_map<std::string, int> const LBVTKHandle::obs_map = {
66 {"density", static_cast<int>(OutputVTK::density)},
67 {"velocity_vector", static_cast<int>(OutputVTK::velocity_vector)},
68 {"pressure_tensor", static_cast<int>(OutputVTK::pressure_tensor)},
69 {"boundary", static_cast<int>(OutputVTK::boundary)},
70};
71
72Variant LBFluid::do_call_method(std::string const &name,
73 VariantMap const &params) {
74 if (name == "activate") {
75 auto &system = get_system();
80 m_is_active = true;
81 return {};
82 }
83 if (name == "deactivate") {
84 get_system().lb.reset();
85 m_is_active = false;
86 return {};
87 }
88 if (not name.starts_with("get_")) {
91 }
92 if (name == "add_force_at_pos") {
93 auto const &box_geo = *get_system().box_geo;
94 auto const pos = get_value<Utils::Vector3d>(params, "pos");
95 auto const f = get_value<Utils::Vector3d>(params, "force");
96 auto const folded_pos = box_geo.folded_position(pos);
97 m_instance->add_force_at_pos(folded_pos * m_conv_dist, f * m_conv_force);
98 return {};
99 }
100 if (name == "get_interpolated_velocity") {
101 auto const pos = get_value<Utils::Vector3d>(params, "pos");
102 return get_interpolated_velocity(pos);
103 }
104 if (name == "get_boundary_force_from_shape") {
105 return get_boundary_force_from_shape(
106 get_value<std::vector<int>>(params, "raster"));
107 }
108 if (name == "get_boundary_force") {
109 return get_boundary_force();
110 }
111 if (name == "get_pressure_tensor") {
112 return get_average_pressure_tensor();
113 }
114 if (name == "load_checkpoint") {
115 auto const path = get_value<std::filesystem::path>(params, "path");
116 auto const mode = get_value<int>(params, "mode");
117 load_checkpoint(path, mode);
118 return {};
119 }
120 if (name == "save_checkpoint") {
121 auto const path = get_value<std::filesystem::path>(params, "path");
122 auto const mode = get_value<int>(params, "mode");
123 save_checkpoint(path, mode);
124 return {};
125 }
126 if (name == "clear_boundaries") {
127 m_instance->clear_boundaries();
129 return {};
130 }
131 if (name == "add_boundary_from_shape") {
132 m_instance->update_boundary_from_shape(
133 get_value<std::vector<int>>(params, "raster"),
134 get_value<std::vector<double>>(params, "values"));
135 return {};
136 }
137 if (name == "get_lattice_speed") {
138 return 1. / m_conv_speed;
139 }
140
141 return Base::do_call_method(name, params);
142}
143
145 auto const visc = get_value<double>(params, "kinematic_viscosity");
146 auto const dens = get_value<double>(params, "density");
147 auto const gpu = get_value_or(params, "gpu", false);
148 auto const precision = get_value_or(params, "single_precision", gpu);
149 auto const lb_lattice = m_lattice->lattice();
150 auto const lb_visc = m_conv_visc * visc;
151 auto const lb_dens = m_conv_dens * dens;
152 auto *make_new_instance = &new_lb_walberla_cpu;
153 if (gpu) {
154 std::vector<std::string> required_features;
155 required_features.emplace_back("CUDA");
157#ifdef ESPRESSO_CUDA
159 m_lattice->get_parameter("blocks_per_mpi_rank"));
160 if (blocks_per_mpi_rank != Utils::Vector3i{{1, 1, 1}}) {
161 throw std::runtime_error(
162 "Using more than one block per MPI rank is not supported for GPU LB");
163 }
164 make_new_instance = &new_lb_walberla_gpu;
165#endif
166 }
167 m_instance = make_new_instance(lb_lattice, lb_visc, lb_dens, precision);
168}
169
173 get_value_or<decltype(m_vtk_writers)>(params, "vtk_writers", {});
174 auto const tau = get_value<double>(params, "tau");
175 auto const agrid = get_value<double>(m_lattice->get_parameter("agrid"));
176 auto const visc = get_value<double>(params, "kinematic_viscosity");
177 auto const dens = get_value<double>(params, "density");
178 auto const kT = get_value<double>(params, "kT");
179 auto const ext_f = get_value<Utils::Vector3d>(params, "ext_force_density");
180 m_lb_params = std::make_shared<::LB::LBWalberlaParams>(agrid, tau);
181 m_is_active = false;
182 auto const seed = get_value<int>(params, "seed");
183 context()->parallel_try_catch([&]() {
184 if (tau <= 0.) {
185 throw std::domain_error("Parameter 'tau' must be > 0");
186 }
187 m_conv_dist = 1. / agrid;
188 m_conv_visc = Utils::int_pow<1>(tau) / Utils::int_pow<2>(agrid);
189 m_conv_energy = Utils::int_pow<2>(tau) / Utils::int_pow<2>(agrid);
190 m_conv_dens = Utils::int_pow<3>(agrid);
191 m_conv_speed = Utils::int_pow<1>(tau) / Utils::int_pow<1>(agrid);
192 m_conv_press = Utils::int_pow<2>(tau) * Utils::int_pow<1>(agrid);
193 m_conv_force = Utils::int_pow<2>(tau) / Utils::int_pow<1>(agrid);
194 m_conv_force_dens = Utils::int_pow<2>(tau) * Utils::int_pow<2>(agrid);
195 auto const lb_visc = m_conv_visc * visc;
196 auto const lb_dens = m_conv_dens * dens;
197 auto const lb_kT = m_conv_energy * kT;
198 auto const lb_ext_f = m_conv_force_dens * ext_f;
199 if (seed < 0) {
200 throw std::domain_error("Parameter 'seed' must be >= 0");
201 }
202 if (lb_kT < 0.) {
203 throw std::domain_error("Parameter 'kT' must be >= 0");
204 }
205 if (lb_dens <= 0.) {
206 throw std::domain_error("Parameter 'density' must be > 0");
207 }
208 if (lb_visc < 0.) {
209 throw std::domain_error("Parameter 'kinematic_viscosity' must be >= 0");
210 }
211 make_instance(params);
213 m_instance->set_collision_model(lb_kT, seed);
214 m_instance->set_external_force(lb_ext_f);
215 m_instance->ghost_communication();
216 for (auto &vtk : m_vtk_writers) {
217 vtk->attach_to_lattice(m_instance, get_lattice_to_md_units_conversion());
218 }
219 });
220}
221
223LBFluid::get_boundary_force_from_shape(std::vector<int> const &raster) const {
224 auto const local =
225 m_instance->get_boundary_force_from_shape(raster) / m_conv_force;
226 return mpi_reduce_sum(context()->get_comm(), local);
227}
228
229Variant LBFluid::get_boundary_force() const {
230 auto const local = m_instance->get_boundary_force() / m_conv_force;
231 return mpi_reduce_sum(context()->get_comm(), local);
232}
233
234std::vector<Variant> LBFluid::get_average_pressure_tensor() const {
235 auto const local = m_instance->get_pressure_tensor() / m_conv_press;
236 auto const tensor_flat = mpi_reduce_sum(context()->get_comm(), local);
238 std::ranges::copy(tensor_flat, tensor.m_data.begin());
239 return std::vector<Variant>{tensor.row<0>().as_vector(),
240 tensor.row<1>().as_vector(),
241 tensor.row<2>().as_vector()};
242}
243
244Variant LBFluid::get_interpolated_velocity(Utils::Vector3d const &pos) const {
245 auto const &box_geo = *get_system().box_geo;
246 auto const lb_pos = box_geo.folded_position(pos) * m_conv_dist;
247 auto const result = m_instance->get_velocity_at_pos(lb_pos);
248 return Utils::Mpi::reduce_optional(context()->get_comm(), result) /
250}
251
252void LBFluid::load_checkpoint(std::filesystem::path const &path, int mode) {
253 auto &lb_obj = *m_instance;
254
255 auto const read_metadata = [&lb_obj](CheckpointFile &cpfile) {
256 auto const expected_grid_size = lb_obj.get_lattice().get_grid_dimensions();
257 auto const expected_pop_size = lb_obj.stencil_size();
259 std::size_t read_pop_size;
261 cpfile.read(read_pop_size);
263 std::stringstream message;
264 message << "grid dimensions mismatch, read [" << read_grid_size << "], "
265 << "expected [" << expected_grid_size << "].";
266 throw std::runtime_error(message.str());
267 }
269 throw std::runtime_error("population size mismatch, read " +
270 std::to_string(read_pop_size) + ", expected " +
271 std::to_string(expected_pop_size) + ".");
272 }
273 };
274
275 auto const read_data = [&lb_obj](CheckpointFile &cpfile) {
276 auto const grid_size = lb_obj.get_lattice().get_grid_dimensions();
277 auto const i_max = grid_size[0];
278 auto const j_max = grid_size[1];
279 auto const k_max = grid_size[2];
281 cpnode.populations.resize(lb_obj.stencil_size());
282 for (int i = 0; i < i_max; i++) {
283 for (int j = 0; j < j_max; j++) {
284 for (int k = 0; k < k_max; k++) {
285 auto const ind = Utils::Vector3i{{i, j, k}};
286 cpfile.read(cpnode.populations);
287 cpfile.read(cpnode.last_applied_force);
288 cpfile.read(cpnode.is_boundary);
289 if (cpnode.is_boundary) {
290 cpfile.read(cpnode.slip_velocity);
291 }
292 lb_obj.set_node_population(ind, cpnode.populations);
293 lb_obj.set_node_last_applied_force(ind, cpnode.last_applied_force);
294 if (cpnode.is_boundary) {
295 lb_obj.set_node_velocity_at_boundary(ind, cpnode.slip_velocity);
296 }
297 }
298 }
299 }
300 };
301
302 auto const on_success = [&lb_obj]() {
303 lb_obj.ghost_communication();
304 lb_obj.reallocate_ubb_field();
305 };
306
308 on_success);
309}
310
311void LBFluid::save_checkpoint(std::filesystem::path const &path, int mode) {
312 auto &lb_obj = *m_instance;
313
314 auto const write_metadata = [&lb_obj,
315 mode](std::shared_ptr<CheckpointFile> cpfile_ptr,
316 Context const &context) {
317 auto const grid_size = lb_obj.get_lattice().get_grid_dimensions();
318 auto const pop_size = lb_obj.stencil_size();
319 if (context.is_head_node()) {
320 cpfile_ptr->write(grid_size);
321 cpfile_ptr->write(pop_size);
323 }
324 };
325
326 auto const on_failure = [](std::shared_ptr<CheckpointFile> const &,
327 Context const &context) {
328 if (context.is_head_node()) {
329 auto failure = true;
330 boost::mpi::broadcast(context.get_comm(), failure, 0);
331 }
332 };
333
334 auto const write_data = [&lb_obj,
335 mode](std::shared_ptr<CheckpointFile> cpfile_ptr,
336 Context const &context) {
337 auto const get_node_checkpoint =
338 [&](Utils::Vector3i const &ind) -> std::optional<LBWalberlaNodeState> {
339 auto const pop = lb_obj.get_node_population(ind);
340 auto const laf = lb_obj.get_node_last_applied_force(ind);
341 auto const lbb = lb_obj.get_node_is_boundary(ind);
342 auto const vbb = lb_obj.get_node_velocity_at_boundary(ind);
343 if (pop and laf and lbb and ((*lbb) ? vbb.has_value() : true)) {
346 cpnode.last_applied_force = *laf;
347 cpnode.is_boundary = *lbb;
348 if (*lbb) {
349 cpnode.slip_velocity = *vbb;
350 }
351 return {cpnode};
352 }
353 return std::nullopt;
354 };
355
356 auto failure = false;
357 auto const &comm = context.get_comm();
358 auto const is_head_node = context.is_head_node();
359 auto const unit_test_mode = (mode != static_cast<int>(CptMode::ascii)) and
360 (mode != static_cast<int>(CptMode::binary));
361 auto const grid_size = lb_obj.get_lattice().get_grid_dimensions();
362 auto const i_max = grid_size[0];
363 auto const j_max = grid_size[1];
364 auto const k_max = grid_size[2];
366 for (int i = 0; i < i_max; i++) {
367 for (int j = 0; j < j_max; j++) {
368 for (int k = 0; k < k_max; k++) {
369 auto const ind = Utils::Vector3i{{i, j, k}};
370 auto const result = get_node_checkpoint(ind);
371 if (!unit_test_mode) {
372 assert(1 == boost::mpi::all_reduce(comm, static_cast<int>(!!result),
373 std::plus<>()) &&
374 "Incorrect number of return values");
375 }
376 if (is_head_node) {
377 if (result) {
378 cpnode = *result;
379 } else {
380 comm.recv(boost::mpi::any_source, 42, cpnode);
381 }
382 auto &cpfile = *cpfile_ptr;
383 cpfile.write(cpnode.populations);
384 cpfile.write(cpnode.last_applied_force);
385 cpfile.write(cpnode.is_boundary);
386 if (cpnode.is_boundary) {
387 cpfile.write(cpnode.slip_velocity);
388 }
389 boost::mpi::broadcast(comm, failure, 0);
390 } else {
391 if (result) {
392 comm.send(0, 42, *result);
393 }
394 boost::mpi::broadcast(comm, failure, 0);
395 if (failure) {
396 return;
397 }
398 }
399 }
400 }
401 }
402 };
403
406}
407
408} // namespace ScriptInterface::walberla
409
410#endif // ESPRESSO_WALBERLA
Vector implementation and trait types for boost qvm interoperability.
virtual void parallel_try_catch(std::function< void()> const &cb) const =0
virtual bool is_head_node() const =0
virtual boost::mpi::communicator const & get_comm() const =0
Context * context() const
Responsible context.
std::string_view name() const
std::optional< ResourceObserver > m_mpi_cart_comm_observer
Definition LBFluid.hpp:74
void do_construct(VariantMap const &params) override
Definition LBFluid.cpp:170
void make_instance(VariantMap const &params) override
Definition LBFluid.cpp:144
::LatticeModel::units_map get_lattice_to_md_units_conversion() const override
Definition LBFluid.hpp:152
Variant do_call_method(std::string const &name, VariantMap const &params) override
Definition LBFluid.cpp:72
std::shared_ptr<::LB::LBWalberlaParams > m_lb_params
Definition LBFluid.hpp:73
Variant do_call_method(std::string const &method_name, VariantMap const &params) override
void on_lb_boundary_conditions_change()
Called when the LB boundary conditions change (geometry, slip velocity, or both).
std::shared_ptr< BoxGeometry > box_geo
std::shared_ptr< LBWalberlaBase > new_lb_walberla_cpu(std::shared_ptr< LatticeWalberla > const &lattice, double viscosity, double density, bool single_precision)
std::shared_ptr< LBWalberlaBase > new_lb_walberla_gpu(std::shared_ptr< LatticeWalberla > const &lattice, double viscosity, double density, bool single_precision)
Matrix implementation and trait types for boost qvm interoperability.
void check_features(std::vector< std::string > const &features)
Definition CodeInfo.cpp:74
void save_checkpoint_common(Context const &context, std::string const classname, std::filesystem::path const &path, int mode, F1 const write_metadata, F2 const write_data, F3 const on_failure)
void lb_throw_if_expired(std::optional< ResourceObserver > const &mpi_obs)
Definition LBFluid.hpp:63
void unit_test_handle(int mode)
Inject code for unit tests.
void load_checkpoint_common(Context const &context, std::string const classname, std::filesystem::path const &path, int mode, F1 const read_metadata, F2 const read_data, F3 const on_success)
T get_value(Variant const &v)
Extract value of specific type T from a Variant.
std::unordered_map< std::string, Variant > VariantMap
Definition Variant.hpp:133
T get_value_or(VariantMap const &vals, std::string const &name, T const &default_)
Get a value from a VariantMap by name, or return a default value if it does not exist.
T mpi_reduce_sum(boost::mpi::communicator const &comm, T const &result)
Reduce object by sum on the head node.
make_recursive_variant< ObjectRef > Variant
Possible types for parameters.
Definition Variant.hpp:131
System & get_system()
T reduce_optional(boost::mpi::communicator const &comm, std::optional< T > const &result)
Reduce an optional on the head node.
ResourceObserver get_mpi_cart_comm_observer()
Get an observer on waLBerla's MPI Cartesian communicator status.
Checkpoint data for a LB node.
std::vector< double > populations
void update_collision_model()
void reset()
Remove the LB solver.
Definition lb/Solver.cpp:67
Recursive variant implementation.
Definition Variant.hpp:84
Matrix representation with static size.
Definition matrix.hpp:65