IMP logo
IMP Reference Guide  develop.d36d3deffb,2026/11/09
The Integrative Modeling Platform
hierarchical_clustering.h
Go to the documentation of this file.
1 /**
2  * \file IMP/em2d/hierarchical_clustering.h
3  * \brief Agglomerative clustering algorithm
4  *
5  * Copyright 2007-2026 IMP Inventors. All rights reserved.
6  *
7  */
8 
9 #ifndef IMPEM2D_HIERARCHICAL_CLUSTERING_H
10 #define IMPEM2D_HIERARCHICAL_CLUSTERING_H
11 
12 #include <IMP/em2d/em2d_config.h>
13 #include "IMP/em2d/scores2D.h"
14 #include <IMP/em2d/internal/clustering_helper.h>
15 #include "IMP/base_types.h"
16 #include <IMP/log.h>
17 #include <vector>
18 #include <list>
19 #include <algorithm>
20 #include <limits>
21 #include <functional>
22 #include <cereal/access.hpp>
23 
24 IMPEM2D_BEGIN_NAMESPACE
25 
26 //! \deprecated_at{2.26} This function is not used in IMP, and will be removed.
27 template <class T>
28 IMPEM2D_DEPRECATED_FUNCTION_DECL(2.26)
29 void print_vector(const std::vector<T> &v) {
30  IMPEM2D_DEPRECATED_FUNCTION_DEF(
31  2.26, "This function is not used in IMP, and will be removed.");
32  for (unsigned int i = 0; i < v.size(); ++i) {
33  std::cout << v[i] << " , ";
34  }
35  std::cout << std::endl;
36 }
37 
38 //! A class to store the clusters generated during hierarchical clustering
39 class IMPEM2DEXPORT ClusterSet {
40  public:
41  ClusterSet() : steps_(0), n_elements_(0) {}
42 
43  /**
44  \param[in] N Number of elements to be clustered
45  */
46  ClusterSet(unsigned int N);
47 
48  //! join operation
49  /** \param[in] cluster_id1 id of the 1st cluster joined
50  \param[in] cluster_id2 id of the 2nd cluster merged
51  \param[in] distance_between_clusters distance between the merged clusters
52  */
53  void do_join_clusters(unsigned int cluster_id1, unsigned int cluster_id2,
54  double distance_between_clusters);
55 
56  //! Returns a vector with the ids of the elements that are in a cluster
57  /** Does not contain any hierarchical information, just the members
58  \param[in] id of the cluster
59  */
60  Ints get_cluster_elements(unsigned int id) const;
61 
62  //! Get the biggest clusters that have distances below a given cutoff
63  /** \param[in] cutoff distance
64  \return A vector of Ints: Each Ints has the ids of all elements of the
65  cluster
66  */
67  Ints get_clusters_below_cutoff(double cutoff) const;
68 
69  //! Return the elements of the cluster formed at a given step
70  Ints get_cluster_formed_at_step(unsigned int step) const;
71 
72  //! Distance in the linkage matrix at a given step
73  double get_distance_at_step(unsigned int step) const;
74 
75  unsigned int get_id_for_cluster_at_step(unsigned int step) const {
76  return step + n_elements_;
77  }
78 
79  //! Returns the linkage matrix
80  /**
81  \note Linkage matrix is a matrix A[N-1][3].
82  A[i][0] - id of the first cluster merged at step i
83  A[i][1] - id of the second cluster merged at step i
84  A[i][2] - distance between the clusters
85  */
86  FloatsList get_linkage_matrix() const;
87 
88  //! Returns the linkage matrix compatible with Matlab format
89  /**
90  \note This function merely adds 1 to the cluster ids, for compatibility
91  with Matlab.
92  Matlab format: https://www.mathworks.com/help/toolbox/stats/linkage.html
93  */
94  FloatsList get_linkage_matrix_in_matlab_format() const;
95 
96  //! Returns the number of steps of clustering recorded
97  unsigned int get_number_of_steps() const { return steps_; }
98 
99  void show(std::ostream &out) const;
100 
101  private:
102  void check_step_value(unsigned int s) const;
103  unsigned int get_step_from_id(unsigned int id) const {
104  return (id - n_elements_);
105  }
106 
107  unsigned int steps_;
108  unsigned int n_elements_; // number of elements to cluster
109  Ints joined_ids1_, joined_ids2_;
110  Floats cluster_distances_;
111  // each element of the outermost vector is a vector with all the elements
112  // in a cluster
113  IntsList clusters_elements_;
114 
115 private:
116  friend class cereal::access;
117 
118  template<class Archive> void serialize(Archive &ar) {
119  ar(steps_, n_elements_, joined_ids1_, joined_ids2_,
120  cluster_distances_, clusters_elements_);
121  }
122 };
124 
125 //! Functor for hierarchical clustering based on single linkage
126 class IMPEM2DEXPORT SingleLinkage {
127  public:
128  SingleLinkage() {}
129  /**
130  \param[in] id1 identity of cluster 1 to merge
131  \param[in] id2 identity of cluster 2 to merge
132  \param[in] cluster_set linkage matrix describing the contents of
133  clusters so far.
134  \param[in] distances A NxN matrix of distances(i,j) between the individual
135  members to cluster
136  \return Minimal distance between members of the clusters
137  \note the id of an isolated member n<N is n. The id of the cluster formed
138  at step i is i+N.
139  */
140  double operator()(unsigned int id1, unsigned int id2,
141  const ClusterSet &cluster_set,
142  const FloatsList &distances) const;
143 
144  void show(std::ostream &out) const {
145  out << "SingleLinkage";
146  };
147 private:
148  friend class cereal::access;
149 
150  template<class Archive> void serialize(Archive &) {}
151 };
153 
154 //! Functor for hierarchical clustering based on complete linkage
155 class IMPEM2DEXPORT CompleteLinkage {
156  public:
157  CompleteLinkage() {}
158  //! Distance between the clusters
159  /**
160  \note See SingleLinkage class for the meaning of the arguments
161  \return Maximal distance between 2 members in the merged cluster
162 
163  */
164  double operator()(unsigned int id1, unsigned int id2,
165  const ClusterSet &cluster_set, const FloatsList &distances);
166 
167  void show(std::ostream &out) const {
168  out << "CompleteLinkage";
169  };
170 private:
171  friend class cereal::access;
172 
173  template<class Archive> void serialize(Archive &) {}
174 };
176 
177 //! Functor for hierarchical clustering based on average-linkage
178 class IMPEM2DEXPORT AverageDistanceLinkage {
179  public:
181  //! Distance between the clusters
182  /**
183  \note See SingleLinkage class for the meaning of the arguments
184  \return Average between all members of the merged cluster
185  */
186  double operator()(unsigned int id1, unsigned int id2,
187  const ClusterSet &cluster_set, const FloatsList &distances);
188 
189  void show(std::ostream &out) const {
190  out << "AverageDistanceLinkage";
191  };
192 private:
193  friend class cereal::access;
194 
195  template<class Archive> void serialize(Archive &) {}
196 };
198 
199 //! Function to perform agglomerative clustering
200 /**
201  \param[in] distances Vector of Floats containing all the
202  possible distances(i,j) between elements to cluster. Given N elements to
203  cluster, there are N vectors of size N
204  \return a ClusterSet class containing all the clustering steps.
205 */
206 template <class LinkageFunction>
208  const FloatsList &distances) {
209  // Based on:
210  // http://nlp.stanford.edu/IR-book/html/htmledition/
211  // time-complexity-of-hac-1.html)
212 
213  IMP_LOG_TERSE("starting hierarchical_agglomerative_clustering " << std::endl);
214 
215  unsigned int N = distances.size(); // number of elements
216  // Lists of distances between elements
217  // List n has members (i,distance_n_i).
218  std::vector<internal::list_cluster_id_distance> lists(N);
219  // id of the cluster associated with each list
220  Ints cluster_id(N);
221  // All list active at the beginning
222  std::vector<bool> active_list(N);
223  std::fill(active_list.begin(), active_list.end(), true);
224  // Fill lists
225  for (unsigned int n = 0; n < N; ++n) {
226  for (unsigned int i = 0; i < N; ++i) {
227  if (i != n) {
228  lists[n].push_back(std::make_pair(i, distances[n][i]));
229  }
230  }
232  // At the beginning each list is associated with a cluster of one element
233  cluster_id[n] = n;
234  }
235 
236  ClusterSet cluster_set(N);
237  LinkageFunction linkage_function;
238  unsigned int steps = N - 1; // Steps of clustering
239  // cluster algorithm
240  for (unsigned int k = 0; k < steps; ++k) {
241  IMP_LOG_TERSE(std::endl);
242  // Find the list that contains lower distance
243  double minimum_distance = std::numeric_limits<double>::max();
244  unsigned int l1 = 0;
245 
246  for (unsigned int j = 0; j < N; ++j) {
247  if (active_list[j] == true) {
248  // closest distance for list j
249  if (lists[j].front().second < minimum_distance) {
250  minimum_distance = lists[j].front().second;
251  l1 = j;
252  }
253  }
254  } // list l1 contains lowest distance
255  // lowest distance is between list l1 and list l2
256  unsigned int l2 = lists[l1].front().first;
257  minimum_distance = lists[l2].front().second;
258  IMP_LOG_TERSE("step " << k << ": joining clusters " << cluster_id[l1]
259  << " and " << cluster_id[l2] << " to form cluster "
260  << cluster_set.get_id_for_cluster_at_step(k)
261  << " distance = " << minimum_distance << std::endl);
262  cluster_set.do_join_clusters(cluster_id[l1], cluster_id[l2],
263  minimum_distance);
264  active_list[l2] = false;
265  cluster_id[l1] = cluster_set.get_id_for_cluster_at_step(k);
266  // clear list 1. It will be filled with distance values for the new cluster
267  lists[l1].clear();
268  // Update lists of distances
269  for (unsigned int i = 0; i < N; ++i) {
270  IMP_LOG_TERSE("Updating list of distances " << i << std::endl);
271  if (active_list[i] == true && i != l1) {
272  IMP_LOG_TERSE("List " << i << " is active " << std::endl);
273  // Delete list elements that store distances to the merged clusters
274  lists[i].erase(std::remove_if(lists[i].begin(), lists[i].end(),
275  internal::ListHasDistance(l1, l2)),
276  lists[i].end());
277  // Update distances to the merged cluster
278  double dist = linkage_function(cluster_id[l1], cluster_id[i],
279  cluster_set, distances);
280  IMP_LOG_TERSE("Distance by linkage function " << dist << std::endl);
281  lists[i].push_back(std::make_pair(l1, dist));
282  lists[l1].push_back(std::make_pair(i, dist));
283  }
284  }
285  // Sort lists
286  for (unsigned int i = 0; i < N; ++i) {
287  if (active_list[i] == true) {
289  }
290  }
291  }
292  return cluster_set;
293 }
294 
295 IMPEM2D_END_NAMESPACE
296 
297 #endif /* IMPEM2D_HIERARCHICAL_CLUSTERING_H */
void print_vector(const std::vector< T > &v)
Basic types used by IMP.
ClusterSet do_hierarchical_agglomerative_clustering(const FloatsList &distances)
Function to perform agglomerative clustering.
Functor for hierarchical clustering based on single linkage.
#define IMP_LOG_TERSE(expr)
Definition: log_macros.h:72
Scoring functions for 2D Copyright 2007-2026 IMP Inventors. All rights reserved.
void do_join_clusters(unsigned int cluster_id1, unsigned int cluster_id2, double distance_between_clusters)
join operation
#define IMP_VALUES(Name, PluralName)
Define the type for storing sets of values.
Definition: value_macros.h:23
A class to store the clusters generated during hierarchical clustering.
std::ostream & show(Hierarchy h, std::ostream &out=std::cout)
Print the hierarchy using a given decorator to display each node.
Functor for hierarchical clustering based on complete linkage.
Functor for hierarchical clustering based on average-linkage.
Logging and error reporting support.
unsigned int get_number_of_steps() const
Returns the number of steps of clustering recorded.
Comparison of pairs by checking the second element.
Definition: scores2D.h:129