ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
CellSystem.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2022-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
20#include "CellSystem.hpp"
21
25
26#include "core/BoxGeometry.hpp"
31#include "core/cells.hpp"
36#include "core/tuning.hpp"
37
38#include <utils/Vector.hpp>
40
41#include <boost/mpi/collectives/gather.hpp>
42
43#include <omp.h>
44
45#include <algorithm>
46#include <cassert>
47#include <iterator>
48#include <optional>
49#include <set>
50#include <sstream>
51#include <stdexcept>
52#include <string>
53#include <unordered_map>
54#include <utility>
55#include <variant>
56#include <vector>
57
58static int coord(std::string const &s) {
59 if (s == "x")
60 return 0;
61 if (s == "y")
62 return 1;
63 if (s == "z")
64 return 2;
65 throw std::invalid_argument("Invalid Cartesian coordinate: '" + s + "'");
66}
67
68static std::string coord_letter(int c) {
69 if (c == 0)
70 return "x";
71 if (c == 1)
72 return "y";
73 assert(c == 2);
74 return "z";
75}
76
77namespace ScriptInterface {
78namespace CellSystem {
79
82 {"use_verlet_lists",
83 [this](Variant const &v) {
84 get_cell_structure().use_verlet_list = get_value<bool>(v);
85 },
86 [this]() { return get_cell_structure().use_verlet_list; }},
87 {"node_grid",
88 [this](Variant const &v) {
89 context()->parallel_try_catch([this, &v]() {
90 auto const error_msg = std::string("Parameter 'node_grid'");
93 if (::communicator.locked_for_checkpointing) {
95 return;
96 }
99 if (n_nodes_new != n_nodes_old) {
100 std::stringstream reason;
101 reason << ": MPI world size " << n_nodes_old << " incompatible "
102 << "with new node grid [" << new_node_grid << "]";
103 throw std::invalid_argument(error_msg + reason.str());
104 }
105 try {
107 get_system().on_node_grid_change();
108 } catch (...) {
110 get_system().on_node_grid_change();
111 throw;
112 }
113 });
114 },
115 []() { return ::communicator.node_grid; }},
116 {"skin",
117 [this](Variant const &v) {
118 auto const new_skin = get_value<double>(v);
119 if (new_skin < 0.) {
120 if (context()->is_head_node()) {
121 throw std::domain_error("Parameter 'skin' must be >= 0");
122 }
123 throw Exception("");
124 }
125 get_cell_structure().set_verlet_skin(new_skin);
126 },
127 [this]() { return get_cell_structure().get_verlet_skin(); }},
128 {"decomposition_type", AutoParameter::read_only,
129 [this]() {
130 return cs_type_to_name.at(get_cell_structure().decomposition_type());
131 }},
132 {"n_square_types", AutoParameter::read_only,
133 [this]() {
134 if (get_cell_structure().decomposition_type() !=
136 return Variant{none};
137 }
138 auto const hd = get_hybrid_decomposition();
139 auto const ns_types = hd.get_n_square_types();
140 return Variant{std::vector<int>(ns_types.begin(), ns_types.end())};
141 }},
142 {"fully_connected_boundary", AutoParameter::read_only,
143 [this]() {
144 if (get_cell_structure().decomposition_type() !=
146 return Variant{none};
147 }
148 auto const rd = get_regular_decomposition();
149 auto const fcb = rd.fully_connected_boundary();
150 if (not fcb)
151 return Variant{none};
152 return Variant{std::unordered_map<std::string, Variant>{
153 {{"boundary", Variant{coord_letter((*fcb).first)}},
154 {"direction", Variant{coord_letter((*fcb).second)}}}}};
155 }},
156 {"cutoff_regular", AutoParameter::read_only,
157 [this]() {
158 if (get_cell_structure().decomposition_type() !=
160 return Variant{none};
161 }
162 auto const hd = get_hybrid_decomposition();
163 return Variant{hd.get_cutoff_regular()};
164 }},
165 {"max_cut_nonbonded", AutoParameter::read_only,
166 [this]() { return get_system().nonbonded_ias->maximal_cutoff(); }},
167 {"max_cut_bonded", AutoParameter::read_only,
168 [this]() { return get_system().bonded_ias->maximal_cutoff(); }},
169 {"interaction_range", AutoParameter::read_only,
170 [this]() { return get_system().get_interaction_range(); }},
171 });
172}
173
174Variant CellSystem::do_call_method(std::string const &name,
175 VariantMap const &params) {
176 if (name == "initialize") {
177 auto const cs_name = get_value<std::string>(params, "name");
178 auto const cs_type = cs_name_to_type.at(cs_name);
179 initialize(cs_type, params);
180 return {};
181 }
182 if (name == "resort") {
183 auto const global_flag = get_value_or<bool>(params, "global_flag", true);
184 return mpi_resort_particles(global_flag);
185 }
186 if (name == "get_state") {
187 auto state = get_parameters();
188 auto const cs_type = get_cell_structure().decomposition_type();
190 auto const rd = get_regular_decomposition();
191 state["cell_grid"] = Variant{rd.cell_grid};
192 state["cell_size"] = Variant{rd.cell_size};
193 } else if (cs_type == CellStructureType::HYBRID) {
194 auto const hd = get_hybrid_decomposition();
195 state["cell_grid"] = Variant{hd.get_cell_grid()};
196 state["cell_size"] = Variant{hd.get_cell_size()};
197 mpi_resort_particles(true); // needed to get correct particle counts
198 state["parts_per_decomposition"] =
199 Variant{std::unordered_map<std::string, Variant>{
200 {"regular", hd.count_particles_in_regular()},
201 {"n_square", hd.count_particles_in_n_square()}}};
202 }
203 state["verlet_reuse"] = get_cell_structure().get_verlet_reuse();
204 state["n_nodes"] = context()->get_comm().size();
205 state["omp_num_threads"] = omp_get_max_threads();
206 return state;
207 }
208 if (name == "get_pairs") {
209 std::vector<Variant> out;
210 context()->parallel_try_catch([this, &params, &out]() {
211 auto &system = get_system();
212 system.on_observable_calc();
213 std::vector<std::pair<int, int>> pair_list;
214 auto const distance = get_value<double>(params, "distance");
215 if (std::get_if<std::string>(&params.at("types"))) {
216 auto const key = get_value<std::string>(params, "types");
217 if (key != "all") {
218 throw std::invalid_argument("Unknown argument types='" + key + "'");
219 }
220 pair_list = get_pairs(system, distance);
221 } else {
222 auto const types = get_value<std::vector<int>>(params, "types");
223 pair_list = get_pairs_of_types(system, distance, types);
224 }
225 Utils::Mpi::gather_buffer(pair_list, context()->get_comm());
226 std::ranges::transform(pair_list, std::back_inserter(out),
227 [](auto const &pair) {
228 return std::vector<int>{pair.first, pair.second};
229 });
230 });
231 return out;
232 }
233 if (name == "get_neighbors") {
234 std::vector<std::vector<int>> neighbors_global;
235 context()->parallel_try_catch([this, &neighbors_global, &params]() {
236 auto &system = get_system();
237 system.on_observable_calc();
238 auto const dist = get_value<double>(params, "distance");
239 auto const pid = get_value<int>(params, "pid");
240 auto const ret = get_short_range_neighbors(system, pid, dist);
241 std::vector<int> neighbors_local;
242 if (ret) {
244 }
245 boost::mpi::gather(context()->get_comm(), neighbors_local,
247 });
248 std::vector<int> neighbors;
249 for (auto const &neighbors_local : neighbors_global) {
250 if (not neighbors_local.empty()) {
251 neighbors = neighbors_local;
252 break;
253 }
254 }
255 return neighbors;
256 }
257 if (name == "non_bonded_loop_trace") {
258 auto &system = get_system();
259 system.on_observable_calc();
260 std::vector<Variant> out;
261 auto pair_list =
262 non_bonded_loop_trace(system, context()->get_comm().rank());
263 Utils::Mpi::gather_buffer(pair_list, context()->get_comm());
264 std::ranges::transform(
265 pair_list, std::back_inserter(out), [](auto const &pair) {
266 return std::vector<Variant>{pair.id1, pair.id2, pair.pos1,
267 pair.pos2, pair.vec21, pair.node};
268 });
269 return out;
270 }
271 if (name == "tune_skin") {
272 auto &system = get_system();
273 system.tune_verlet_skin(
274 get_value<double>(params, "min_skin"),
275 get_value<double>(params, "max_skin"), get_value<double>(params, "tol"),
276 get_value<int>(params, "int_steps"),
277 get_value_or<bool>(params, "adjust_max_skin", false));
278 return get_cell_structure().get_verlet_skin();
279 }
280 if (name == "get_max_range") {
281 return get_cell_structure().max_range();
282 }
283 return {};
284}
285
286std::vector<int> CellSystem::mpi_resort_particles(bool global_flag) const {
287 auto &cell_structure = get_cell_structure();
288 cell_structure.resort_particles(global_flag);
289 cell_structure.set_resort_particles(Cells::RESORT_GLOBAL);
291 auto const size = static_cast<int>(cell_structure.local_particles().size());
292 std::vector<int> n_part_per_node;
293 boost::mpi::gather(context()->get_comm(), size, n_part_per_node, 0);
294 return n_part_per_node;
295}
296
297void CellSystem::initialize(CellStructureType const &cs_type,
298 VariantMap const &params) {
299 auto const verlet = get_value_or<bool>(params, "use_verlet_lists", true);
300 auto &system = get_system();
301 m_cell_structure->use_verlet_list = verlet;
303 auto const cutoff_regular = get_value<double>(params, "cutoff_regular");
304 auto const ns_types =
305 get_value_or<std::vector<int>>(params, "n_square_types", {});
306 auto n_square_types = std::set<int>{ns_types.begin(), ns_types.end()};
307 m_cell_structure->set_hybrid_decomposition(cutoff_regular, n_square_types);
308 } else if (cs_type == CellStructureType::REGULAR) {
309 std::optional<std::pair<int, int>> fcb_pair = std::nullopt;
310 if (params.contains("fully_connected_boundary") and
311 not is_none(params.at("fully_connected_boundary"))) {
312 auto const variant =
313 get_value<VariantMap>(params, "fully_connected_boundary");
315 fcb_pair = {{coord(std::get<std::string>(variant.at("boundary"))),
316 coord(std::get<std::string>(variant.at("direction")))}};
317 });
318 }
319 context()->parallel_try_catch([this, &fcb_pair]() {
320 m_cell_structure->set_regular_decomposition(
321 get_system().get_interaction_range(), fcb_pair);
322 });
323 } else {
324 system.set_cell_structure_topology(cs_type);
325 }
326}
327
331
335
337 m_node_grid = get_value_or(params, "node_grid", ::communicator.node_grid);
338}
339
341 VariantMap const &params) {
342 if (name == "get_node_grid") {
343 return ::communicator.node_grid;
344 }
345 if (name == "acquire_lock") {
346 ::communicator.set_node_grid(m_node_grid);
348 } else if (name == "release_lock") {
350 }
351 return {};
352}
353
354} // namespace CellSystem
355} // namespace ScriptInterface
CellStructureType
Cell structure topology.
@ HYBRID
Hybrid decomposition.
@ REGULAR
Regular decomposition.
static int coord(std::string const &s)
static std::string coord_letter(int c)
Vector implementation and trait types for boost qvm interoperability.
Data structures for bonded interactions.
std::optional< std::vector< int > > get_short_range_neighbors(System::System const &system, int const pid, double const distance)
Get ids of particles that are within a certain distance of another particle.
Definition cells.cpp:118
std::vector< PairInfo > non_bonded_loop_trace(System::System const &system, int const rank)
Returns pairs of particle ids, positions and distance as seen by the non-bonded loop.
Definition cells.cpp:175
std::vector< std::pair< int, int > > get_pairs_of_types(System::System const &system, double const distance, std::vector< int > const &types)
Get pairs closer than distance if both their types are in types.
Definition cells.cpp:166
std::vector< std::pair< int, int > > get_pairs(System::System const &system, double const distance)
Get pairs closer than distance from the cells.
Definition cells.cpp:158
This file contains everything related to the global cell structure / cell system.
void add_parameters(std::vector< AutoParameter > &&params)
Variant do_call_method(std::string const &name, VariantMap const &params) override
void configure(Particles::ParticleHandle &)
void do_construct(VariantMap const &params) override
Variant do_call_method(std::string const &name, VariantMap const &params) override
virtual void parallel_try_catch(std::function< void()> const &cb) const =0
virtual boost::mpi::communicator const & get_comm() const =0
VariantMap get_parameters() const
Get current parameters.
Context * context() const
Responsible context.
std::string_view name() const
std::weak_ptr<::System::System > m_system
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
Communicator communicator
This file contains the asynchronous MPI communication.
constexpr bool is_none(Variant const &v)
Definition Variant.hpp:163
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.
constexpr const None none
None-"literal".
Definition Variant.hpp:126
void gather_buffer(std::vector< T, Allocator > &buffer, boost::mpi::communicator const &comm, int root=0)
Gather buffer with different size on each node.
T product(Vector< T, N > const &v)
Definition Vector.hpp:383
Various procedures concerning interactions between particles.
void clear_particle_node()
Invalidate particle_node.
Particles creation and deletion.
Utils::Vector3i node_grid
void set_node_grid(Utils::Vector3i const &value)
Set new Cartesian topology.
static constexpr const ReadOnly read_only
Recursive variant implementation.
Definition Variant.hpp:84