ESPResSo
Extensible Simulation Package for Research on Soft Matter Systems
Loading...
Searching...
No Matches
ClusterStructure.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2010-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 "ClusterStructure.hpp"
20
21#include "BoxGeometry.hpp"
22#include "Cluster.hpp"
23#include "PartCfg.hpp"
24#include "errorhandling.hpp"
25#include "particle_node.hpp"
26
28
29#include <algorithm>
30#include <memory>
31#include <stdexcept>
32#include <utility>
33#include <vector>
34
35namespace ClusterAnalysis {
36
38 clusters.clear();
39 cluster_id.clear();
40 m_cluster_identities.clear();
41}
42
43// Analyze the cluster structure of the given particles
45 // clear data structs
46 clear();
47 sanity_checks();
48
49 // Iterate over pairs
50 auto const box_geo_handle = get_box_geo();
51 auto const &box_geo = *box_geo_handle;
52 PartCfg partCfg{box_geo};
53 Utils::for_each_pair(partCfg, [this](Particle const &p1, Particle const &p2) {
54 this->add_pair(p1, p2);
55 });
56 merge_clusters();
57}
58
60 clear();
61 sanity_checks();
62 auto const box_geo_handle = get_box_geo();
63 auto const &box_geo = *box_geo_handle;
64 PartCfg partCfg{box_geo};
65 for (const auto &p : partCfg) {
66 for (auto const bond : p.bonds()) {
67 if (bond.partner_ids().size() == 1) {
68 add_pair(p, get_particle_data(bond.partner_ids()[0]));
69 }
70 }
71 }
72
73 merge_clusters();
74}
75
76void ClusterStructure::add_pair(const Particle &p1, const Particle &p2) {
77 // * check, if there's a neighbor
78 // * No: Then go on to the next particle
79 // * Yes: Then if
80 // * One of them belongs to a cluster, give the other one the same cluster
81 // id.
82 // * None of them belongs to a cluster: Give them both a new cluster id
83 // * Both belong to different clusters: Mark the clusters as identical
84 // * so that they can be put together later
85 if (!m_pair_criterion) {
86 runtimeErrorMsg() << "No cluster criterion defined";
87 return;
88 }
89 // If the two particles are neighbors...
90 if (m_pair_criterion->decide(p1, p2)) {
91
92 if // None belongs to a cluster
94 // Both particles belong to the same, new cluster
95 const int cid = get_next_free_cluster_id();
96
97 // assign the cluster_ids
98 cluster_id[p1.id()] = cid;
99 cluster_id[p2.id()] = cid;
100 } else if // p2 belongs to a cluster but p1 doesn't
102 // Give p1 the same cluster id as p2
103 cluster_id[p1.id()] = find_id_for(cluster_id.at(p2.id()));
104 } else if // i belongs to a cluster but j doesn't
106 // give p2 the cluster id from p1
107 cluster_id[p2.id()] = find_id_for(cluster_id.at(p1.id()));
108 } else if // Both belong to different clusters
110 cluster_id.at(p1.id()) != cluster_id.at(p2.id())) {
111 // Clusters of p1 and p2 are one and the same. Add an identity to the list
112 // The higher number must be inserted as first value of the pair
113 // because the substitutions later have to be done in descending order
114 const int cid1 = find_id_for(cluster_id.at(p1.id()));
115 const int cid2 = find_id_for(cluster_id.at(p2.id()));
116 if (cid1 > cid2) {
117 m_cluster_identities[cid1] = cid2;
118 } else if (cid1 < cid2) {
119 m_cluster_identities[cid2] = cid1;
120 }
121 // else do nothing. The clusters are already noted for merging.
122 // Connected clusters will be merged later
123 }
124 // The case for both particles being in the same cluster does not need to be
125 // treated.
126 }
127}
128
129void ClusterStructure::merge_clusters() {
130 // Relabel particles according to the cluster identities map.
131 // Also create empty cluster objects for the final cluster id.
132
133 // Collect needed changes in a separate map, as doing the changes on the fly
134 // would invalidate iterators
135 std::vector<std::pair<int, int>> pending_changes;
136
137 for (auto const &[pid, cid] : cluster_id) {
138 // We change the cluster id according to the cluster identities map
139 auto const merged_cid = find_id_for(cid);
140 // We note the list of changes here, so we don't modify the map
141 // while iterating
142 pending_changes.emplace_back(pid, merged_cid);
143 // Initialize new cluster objects
144 if (not clusters.contains(merged_cid)) {
145 clusters[merged_cid] = std::make_shared<Cluster>(m_box_geo);
146 }
147 }
148
149 // Now act on the changes marked in above iteration
150 for (auto &[pid, merged_cid] : pending_changes) {
151 cluster_id[pid] = merged_cid;
152 }
153
154 // Now fill the cluster objects with particle ids
155 // Iterate over particles, fill in the cluster map
156 // to each cluster particle the corresponding cluster id
157 for (auto const &[pid, cid] : cluster_id) {
158 clusters[cid]->particles.emplace_back(pid);
159 }
160
161 // Sort particle ids in the clusters
162 for (auto const &cluster : clusters | std::views::values) {
163 std::ranges::sort(cluster->particles);
164 }
165}
166
167int ClusterStructure::find_id_for(int x) const {
168 auto cid = x;
169 while (m_cluster_identities.contains(cid)) {
170 cid = m_cluster_identities.at(cid);
171 }
172 return cid;
173}
174
175int ClusterStructure::get_next_free_cluster_id() {
176 int max_id = 0;
177 if (not cluster_id.empty()) {
178 max_id = *std::ranges::max_element(cluster_id | std::views::values);
179 }
180 return max_id + 1;
181}
182
183void ClusterStructure::sanity_checks() const {
184 if (get_box_geo()->type() != BoxType::CUBOID) {
185 throw std::runtime_error(
186 "Cluster analysis is not compatible with non-cuboid box types");
187 }
188}
189
190} // namespace ClusterAnalysis
std::map< int, int > cluster_id
Map between particle ids and corresponding cluster ids.
void clear()
Clear data structures.
void run_for_bonded_particles()
Run cluster analysis, consider pairs of particles connected by a bonded interaction.
void run_for_all_pairs()
Run cluster analysis, consider all particle pairs.
bool part_of_cluster(Particle const &p) const
Is particle p part of a cluster.
std::map< int, std::shared_ptr< Cluster > > clusters
Map holding the individual clusters.
Particle cache on the head node.
Definition PartCfg.hpp:34
cudaStream_t stream[1]
CUDA streams for parallel computing on CPU and GPU.
This file contains the errorhandling code for severe errors, like a broken bond or illegal parameter ...
#define runtimeErrorMsg()
void for_each_pair(ForwardIterator first, ForwardIterator last, BinaryOp op)
Execute op for each pair of elements in [first, last) once.
STL namespace.
const Particle & get_particle_data(int p_id)
Get particle data.
Particles creation and deletion.
Struct holding all information for one particle.
Definition Particle.hpp:435