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
48 // Iterate over pairs
49 auto const box_geo_handle = get_box_geo();
50 auto const &box_geo = *box_geo_handle;
51 PartCfg partCfg{box_geo};
52 Utils::for_each_pair(partCfg, [this](Particle const &p1, Particle const &p2) {
53 this->add_pair(p1, p2);
54 });
55 merge_clusters();
56}
57
59 clear();
60 auto const box_geo_handle = get_box_geo();
61 auto const &box_geo = *box_geo_handle;
62 PartCfg partCfg{box_geo};
63 for (const auto &p : partCfg) {
64 for (auto const bond : p.bonds()) {
65 if (bond.partner_ids().size() == 1) {
66 add_pair(p, get_particle_data(bond.partner_ids()[0]));
67 }
68 }
69 }
70
71 merge_clusters();
72}
73
74void ClusterStructure::add_pair(const Particle &p1, const Particle &p2) {
75 // * check, if there's a neighbor
76 // * No: Then go on to the next particle
77 // * Yes: Then if
78 // * One of them belongs to a cluster, give the other one the same cluster
79 // id.
80 // * None of them belongs to a cluster: Give them both a new cluster id
81 // * Both belong to different clusters: Mark the clusters as identical
82 // * so that they can be put together later
83 if (!m_pair_criterion) {
84 runtimeErrorMsg() << "No cluster criterion defined";
85 return;
86 }
87 // If the two particles are neighbors...
88 if (m_pair_criterion->decide(p1, p2)) {
89
90 if // None belongs to a cluster
91 ((!part_of_cluster(p1)) && (!part_of_cluster(p2))) {
92 // Both particles belong to the same, new cluster
93 const int cid = get_next_free_cluster_id();
94
95 // assign the cluster_ids
96 cluster_id[p1.id()] = cid;
97 cluster_id[p2.id()] = cid;
98 } else if // p2 belongs to a cluster but p1 doesn't
99 (part_of_cluster(p2) && !part_of_cluster(p1)) {
100 // Give p1 the same cluster id as p2
101 cluster_id[p1.id()] = find_id_for(cluster_id.at(p2.id()));
102 } else if // i belongs to a cluster but j doesn't
103 (part_of_cluster(p1) && !part_of_cluster(p2)) {
104 // give p2 the cluster id from p1
105 cluster_id[p2.id()] = find_id_for(cluster_id.at(p1.id()));
106 } else if // Both belong to different clusters
107 (part_of_cluster(p1) && part_of_cluster(p2) &&
108 cluster_id.at(p1.id()) != cluster_id.at(p2.id())) {
109 // Clusters of p1 and p2 are one and the same. Add an identity to the list
110 // The higher number must be inserted as first value of the pair
111 // because the substitutions later have to be done in descending order
112 const int cid1 = find_id_for(cluster_id.at(p1.id()));
113 const int cid2 = find_id_for(cluster_id.at(p2.id()));
114 if (cid1 > cid2) {
115 m_cluster_identities[cid1] = cid2;
116 } else if (cid1 < cid2) {
117 m_cluster_identities[cid2] = cid1;
118 }
119 // else do nothing. The clusters are already noted for merging.
120 // Connected clusters will be merged later
121 }
122 // The case for both particles being in the same cluster does not need to be
123 // treated.
124 }
125}
126
127void ClusterStructure::merge_clusters() {
128 // Relabel particles according to the cluster identities map.
129 // Also create empty cluster objects for the final cluster id.
130
131 // Collect needed changes in a separate map, as doing the changes on the fly
132 // would invalidate iterators
133 std::vector<std::pair<int, int>> pending_changes;
134
135 for (auto const &[pid, cid] : cluster_id) {
136 // We change the cluster id according to the cluster identities map
137 auto const merged_cid = find_id_for(cid);
138 // We note the list of changes here, so we don't modify the map
139 // while iterating
140 pending_changes.emplace_back(pid, merged_cid);
141 // Initialize new cluster objects
142 if (not clusters.contains(merged_cid)) {
143 clusters[merged_cid] = std::make_shared<Cluster>(m_box_geo);
144 }
145 }
146
147 // Now act on the changes marked in above iteration
148 for (auto &[pid, merged_cid] : pending_changes) {
149 cluster_id[pid] = merged_cid;
150 }
151
152 // Now fill the cluster objects with particle ids
153 // Iterate over particles, fill in the cluster map
154 // to each cluster particle the corresponding cluster id
155 for (auto const &[pid, cid] : cluster_id) {
156 clusters[cid]->particles.emplace_back(pid);
157 }
158
159 // Sort particle ids in the clusters
160 for (auto const &cluster : clusters | std::views::values) {
161 std::ranges::sort(cluster->particles);
162 }
163}
164
165int ClusterStructure::find_id_for(int x) const {
166 auto cid = x;
167 while (m_cluster_identities.contains(cid)) {
168 cid = m_cluster_identities.at(cid);
169 }
170 return cid;
171}
172
173int ClusterStructure::get_next_free_cluster_id() {
174 int max_id = 0;
175 if (not cluster_id.empty()) {
176 max_id = *std::ranges::max_element(cluster_id | std::views::values);
177 }
178 return max_id + 1;
179}
180
181} // 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
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
auto const & id() const
Definition Particle.hpp:454