ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
ExclusionRadius.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 "ExclusionRadius.hpp"
21
22#include "cells.hpp"
23#include "particle_node.hpp"
24#include "system/System.hpp"
25
26#include <boost/mpi/collectives/all_reduce.hpp>
27#include <boost/mpi/collectives/broadcast.hpp>
28#include <boost/mpi/communicator.hpp>
29
30#include <algorithm>
31#include <cassert>
32#include <functional>
33#include <stdexcept>
34#include <string>
35#include <unordered_map>
36#include <vector>
37
38static auto get_real_particle(boost::mpi::communicator const &comm, int p_id) {
39 assert(p_id >= 0);
40 auto &system = System::get_system();
41 auto ptr = system.cell_structure->get_local_particle(p_id);
42 if (ptr != nullptr and ptr->is_ghost()) {
43 ptr = nullptr;
44 }
45 assert(boost::mpi::all_reduce(comm, static_cast<int>(ptr != nullptr),
46 std::plus<>()) == 1);
47 return ptr;
48}
49
51 if (range < 0.) {
52 throw std::domain_error("Invalid value for exclusion range");
53 }
54 exclusion_range = range;
55 recalc_derived_parameters();
56}
57
59 for (auto const &[type, exclusion_radius] : map) {
60 if (exclusion_radius < 0.) {
61 throw std::domain_error("Invalid exclusion radius for type " +
62 std::to_string(type) + ": radius " +
63 std::to_string(exclusion_radius));
64 }
65 }
67 recalc_derived_parameters();
68}
69
70void ExclusionRadius::recalc_derived_parameters() {
71 m_max_exclusion_range = exclusion_range;
72 for (auto const &item : exclusion_radius_per_type) {
73 auto const radius = item.second;
74 m_max_exclusion_range = std::max(m_max_exclusion_range, 2. * radius);
75 }
76}
77
78/**
79 * Check if an inserted particle is too close to neighboring particles.
80 */
81bool ExclusionRadius::check_exclusion_range(int p_id, int p_type) {
82 /* Check the exclusion radius of the inserted particle */
83 if (exclusion_radius_per_type.contains(p_type)) {
84 if (exclusion_radius_per_type[p_type] == 0.) {
85 return false;
86 }
87 }
88
89 auto p1_ptr = get_real_particle(m_comm, p_id);
90
91 auto const &system = System::get_system();
92 auto const &box_geo = *system.box_geo;
93 auto &cell_structure = *system.cell_structure;
94
95 /* Test whether particle @p p2 lies inside the exclusion range of the
96 * inserted particle located at @p p1_pos. */
97 auto const is_inside_exclusion_range = [&](Utils::Vector3d const &p1_pos,
98 Particle const &p2) {
99 double excluded_distance;
100 if (not exclusion_radius_per_type.contains(p_type) or
101 not exclusion_radius_per_type.contains(p2.type())) {
102 excluded_distance = exclusion_range;
103 } else if (exclusion_radius_per_type[p2.type()] == 0.) {
104 return false;
105 } else {
106 excluded_distance = exclusion_radius_per_type[p_type] +
107 exclusion_radius_per_type[p2.type()];
108 }
109 auto const d_min = box_geo.get_mi_vector(p2.pos(), p1_pos).norm();
110 return d_min < excluded_distance;
111 };
112
114 /* Exhaustive O(N) search. The candidate id list is global (every rank
115 * holds the full list), but each real particle is owned by exactly one
116 * rank. We broadcast the position of the inserted particle to every rank,
117 * let each rank distance-test the candidates it owns locally (real,
118 * non-ghost copies, to avoid double-counting ghosts), and OR-reduce the
119 * partial results. This keeps the check correct even when a candidate
120 * sits within exclusion_range of the inserted particle but beyond the
121 * ghost layer of the inserted particle's domain, where get_local_particle
122 * would return nullptr. */
123 auto all_ids = get_particle_ids_parallel();
124 /* remove the inserted particle id */
125 std::erase(all_ids, p_id);
126
127 /* broadcast the position of the inserted particle from its owning rank */
128 auto p1_pos = (p1_ptr != nullptr) ? p1_ptr->pos() : Utils::Vector3d{};
129 auto const owner_rank =
130 boost::mpi::all_reduce(m_comm, (p1_ptr != nullptr) ? m_comm.rank() : -1,
131 boost::mpi::maximum<int>());
132 boost::mpi::broadcast(m_comm, p1_pos, owner_rank);
133
134 bool local_touched = false;
135 for (auto const p2_id : all_ids) {
136 auto const p2_ptr = cell_structure.get_local_particle(p2_id);
137 /* only the owning rank (real, non-ghost copy) tests each candidate */
138 if (p2_ptr != nullptr and not p2_ptr->is_ghost() and
139 is_inside_exclusion_range(p1_pos, *p2_ptr)) {
140 local_touched = true;
141 break;
142 }
143 }
144 return boost::mpi::all_reduce(m_comm, local_touched, std::logical_or<>());
145 }
146
147 /* Neighbor search via the cell structure: the neighbor ids returned by
148 * get_short_range_neighbors are local/ghost on the inserted particle's rank
149 * and within the interaction range, so resolving them with
150 * get_local_particle is correct here. */
151 std::vector<int> particle_ids;
152 {
153 auto &mutable_system = System::get_system();
154 mutable_system.on_observable_calc();
155 auto const local_ids =
156 get_short_range_neighbors(mutable_system, p_id, m_max_exclusion_range);
157 assert(p1_ptr == nullptr or !!local_ids);
158 if (local_ids) {
159 particle_ids = std::move(*local_ids);
160 }
161 }
162
163 bool local_touched = false;
164 if (p1_ptr != nullptr) {
165 auto const &p1 = *p1_ptr;
166 /* Check if the inserted particle within the exclusion radius of any other
167 * particle */
168 for (auto const p2_id : particle_ids) {
169 if (auto const p2_ptr = cell_structure.get_local_particle(p2_id)) {
170 if (is_inside_exclusion_range(p1.pos(), *p2_ptr)) {
171 local_touched = true;
172 break;
173 }
174 }
175 }
176 if (m_comm.rank() != 0) {
177 m_comm.send(0, 1, local_touched);
178 }
179 } else if (m_comm.rank() == 0) {
180 m_comm.recv(boost::mpi::any_source, 1, local_touched);
181 }
182 boost::mpi::broadcast(m_comm, local_touched, 0);
183 return local_touched;
184}
185
187 auto const *p = get_real_particle(m_comm, pid);
188 assert(boost::mpi::all_reduce(m_comm, static_cast<int>(p != nullptr),
189 std::plus<>()) == 1);
190 int type_local = -1;
191 if (m_comm.rank() == 0) {
192 if (p) {
193 type_local = p->type();
194 } else {
195 m_comm.recv(boost::mpi::any_source, 42, type_local);
196 }
197 } else if (p) {
198 m_comm.send(0, 42, p->type());
199 }
200 boost::mpi::broadcast(m_comm, type_local, 0);
201 return check_exclusion_range(pid, type_local);
202}
static auto get_real_particle(boost::mpi::communicator const &comm, int p_id)
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:117
This file contains everything related to the global cell structure / cell system.
void set_exclusion_range(double range)
std::unordered_map< int, double > map_type
bool check_exclusion_range(int p_id, int p_type)
Check if an inserted particle is too close to neighboring particles.
void set_exclusion_radius_per_type(map_type const &map)
System & get_system()
std::vector< int > get_particle_ids_parallel()
Particles creation and deletion.
Struct holding all information for one particle.
Definition Particle.hpp:436