clustering  0.12
Clustering suite for molecular dynamics trajectories.
 All Classes Namespaces Files Functions Variables Typedefs
density_clustering_mpi.cpp
1 /*
2 Copyright (c) 2015, Florian Sittel (www.lettis.net)
3 All rights reserved.
4 
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
7 
8 1. Redistributions of source code must retain the above copyright notice,
9  this list of conditions and the following disclaimer.
10 
11 2. Redistributions in binary form must reproduce the above copyright notice,
12  this list of conditions and the following disclaimer in the documentation
13  and/or other materials provided with the distribution.
14 
15 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
16 EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
18 SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
20 OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
22 TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
23 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25 
26 #include "density_clustering_mpi.hpp"
28 
29 #include "tools.hpp"
30 #include "logger.hpp"
31 
32 #include <mpi.h>
33 
34 namespace Clustering {
35 namespace Density {
36 namespace MPI {
37 
38  namespace {
59  std::vector<std::size_t>
60  triangular_load_balance(std::size_t n_rows,
61  std::size_t n_nodes) {
62  auto young_gauss = [](std::size_t n) -> std::size_t {
63  return n*(n+1) / 2;
64  };
65  std::size_t workload = young_gauss(n_rows) / n_nodes;
66  std::size_t last_index = 0;
67  std::vector<std::size_t> load_balanced_indices(n_nodes);
68  for (int i=n_nodes-1; i >= 0; --i) {
69  if (i == 0) {
70  load_balanced_indices[i] = 0;
71  } else {
72  last_index = (std::size_t) sqrt(2*(young_gauss(last_index) + workload));
73  load_balanced_indices[i] = n_rows - last_index;
74  }
75  }
76  return load_balanced_indices;
77  }
78  } // end local namespace
79 
80 
81  std::vector<std::size_t>
82  calculate_populations(const float* coords,
83  const std::size_t n_rows,
84  const std::size_t n_cols,
85  const float radius,
86  const int mpi_n_nodes,
87  const int mpi_node_id) {
88  std::vector<float> radii = {radius};
89  std::map<float, std::vector<std::size_t>> pop_map = calculate_populations(coords, n_rows, n_cols, radii, mpi_n_nodes, mpi_node_id);
90  return pop_map[radius];
91  }
92 
93 
94  std::map<float, std::vector<std::size_t>>
95  calculate_populations(const float* coords,
96  const std::size_t n_rows,
97  const std::size_t n_cols,
98  std::vector<float> radii,
99  const int mpi_n_nodes,
100  const int mpi_node_id) {
101  std::vector<std::size_t> load_balanced_indices = triangular_load_balance(n_rows, mpi_n_nodes);
102  unsigned int i_row_from = load_balanced_indices[mpi_node_id];
103  unsigned int i_row_to;
104  if (mpi_node_id == mpi_n_nodes-1) {
105  // last node: run to end
106  i_row_to = n_rows;
107  } else {
108  i_row_to = load_balanced_indices[mpi_node_id+1];
109  }
110  std::map<float, std::vector<unsigned int>> pops;
111  for (float rad: radii) {
112  pops[rad].resize(n_rows, 1);
113  }
114  std::sort(radii.begin(), radii.end(), std::greater<float>());
115  std::size_t n_radii = radii.size();
116  std::vector<float> rad2(n_radii);
117  for (std::size_t i=0; i < n_radii; ++i) {
118  rad2[i] = radii[i]*radii[i];
119  }
120 
121 //TODO: check: error with double counting?
122 
123  // per-node parallel computation of pops using shared memory
124  {
125  std::size_t i, j, k, l;
126  float dist, c;
127  ASSUME_ALIGNED(coords);
128  #pragma omp parallel for default(none) private(i,j,k,l,c,dist) \
129  firstprivate(i_row_from,i_row_to,n_rows,n_cols,rad2,radii,n_radii) \
130  shared(coords,pops) \
131  schedule(dynamic,1024)
132  for (i=i_row_from; i < i_row_to; ++i) {
133  for (j=i+1; j < n_rows; ++j) {
134  dist = 0.0f;
135  #pragma simd reduction(+:dist)
136  for (k=0; k < n_cols; ++k) {
137  c = coords[i*n_cols+k] - coords[j*n_cols+k];
138  dist += c*c;
139  }
140  for (l=0; l < n_radii; ++l) {
141  if (dist < rad2[l]) {
142  #pragma omp atomic
143  pops[radii[l]][i] += 1;
144  #pragma omp atomic
145  pops[radii[l]][j] += 1;
146  } else {
147  // if it's not in the bigger radius,
148  // it won't be in the smaller ones.
149  break;
150  }
151  }
152  }
153  }
154  }
155  std::map<float, std::vector<std::size_t>> pops_results;
156  for (auto& rad_pops: pops) {
157  // accumulate pops in main process and send result to slaves
158  MPI_Barrier(MPI_COMM_WORLD);
159  if (mpi_node_id == MAIN_PROCESS) {
160  // collect slave results
161  for (int slave_id=1; slave_id < mpi_n_nodes; ++slave_id) {
162  std::vector<unsigned int> pops_buf(n_rows);
163  MPI_Recv(pops_buf.data(), n_rows, MPI_UNSIGNED, slave_id, 0, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
164  for (std::size_t i=0; i < n_rows; ++i) {
165  rad_pops.second[i] += pops_buf[i];
166  }
167  }
168  } else {
169  // send pops from slaves
170  MPI_Send(rad_pops.second.data(), n_rows, MPI_UNSIGNED, MAIN_PROCESS, 0, MPI_COMM_WORLD);
171  }
172  MPI_Barrier(MPI_COMM_WORLD);
173  // broadcast accumulated pops to slaves
174  MPI_Bcast(rad_pops.second.data(), n_rows, MPI_UNSIGNED, MAIN_PROCESS, MPI_COMM_WORLD);
175  MPI_Barrier(MPI_COMM_WORLD);
176  // cast unsigned int to size_t and add 1 for own structure
177  pops_results[rad_pops.first].resize(n_rows);
178  for (std::size_t i=0; i < n_rows; ++i) {
179  pops_results[rad_pops.first][i] = (std::size_t) rad_pops.second[i] + 1;
180  }
181  }
182  return pops_results;
183  }
184 
185 
186  std::tuple<Neighborhood, Neighborhood>
187  nearest_neighbors(const float* coords,
188  const std::size_t n_rows,
189  const std::size_t n_cols,
190  const std::vector<float>& free_energy,
191  const int mpi_n_nodes,
192  const int mpi_node_id) {
193  unsigned int rows_per_chunk = n_rows / mpi_n_nodes;
194  unsigned int i_row_from = mpi_node_id * rows_per_chunk;
195  unsigned int i_row_to = i_row_from + rows_per_chunk;
196  // last process has to do slightly more work
197  // in case of uneven separation of workload
198  if (mpi_node_id == mpi_n_nodes-1 ) {
199  i_row_to = n_rows;
200  }
201  Neighborhood nh;
202  Neighborhood nh_high_dens;
203  // initialize neighborhood
204  for (std::size_t i=i_row_from; i < i_row_to; ++i) {
205  nh[i] = Neighbor(n_rows+1, std::numeric_limits<float>::max());
206  nh_high_dens[i] = Neighbor(n_rows+1, std::numeric_limits<float>::max());
207  }
208  // calculate nearest neighbors with distances
209  {
210  std::size_t i, j, c, min_j, min_j_high_dens;
211  float dist, d, mindist, mindist_high_dens;
212  ASSUME_ALIGNED(coords);
213  #pragma omp parallel for default(none) \
214  private(i,j,c,dist,d,mindist,mindist_high_dens,min_j,min_j_high_dens) \
215  firstprivate(i_row_from,i_row_to,n_rows,n_cols) \
216  shared(coords,nh,nh_high_dens,free_energy) \
217  schedule(dynamic, 2048)
218  for (i=i_row_from; i < i_row_to; ++i) {
219  mindist = std::numeric_limits<float>::max();
220  mindist_high_dens = std::numeric_limits<float>::max();
221  min_j = n_rows+1;
222  min_j_high_dens = n_rows+1;
223  for (j=0; j < n_rows; ++j) {
224  if (i != j) {
225  dist = 0.0f;
226  #pragma simd reduction(+:dist)
227  for (c=0; c < n_cols; ++c) {
228  d = coords[i*n_cols+c] - coords[j*n_cols+c];
229  dist += d*d;
230  }
231  // direct neighbor
232  if (dist < mindist) {
233  mindist = dist;
234  min_j = j;
235  }
236  // next neighbor with higher density / lower free energy
237  if (free_energy[j] < free_energy[i] && dist < mindist_high_dens) {
238  mindist_high_dens = dist;
239  min_j_high_dens = j;
240  }
241  }
242  }
243  nh[i] = Neighbor(min_j, mindist);
244  nh_high_dens[i] = Neighbor(min_j_high_dens, mindist_high_dens);
245  }
246  }
247  // collect results in MAIN_PROCESS
248  MPI_Barrier(MPI_COMM_WORLD);
249  {
250  // buf: I_FROM, I_TO_NH, DIST, I_TO_NH_HD, DIST_HD
251  int BUF_SIZE = 5;
252  float buf[BUF_SIZE];
253  if (mpi_node_id == MAIN_PROCESS) {
254  while (nh.size() != n_rows) {
255  MPI_Recv(buf, BUF_SIZE, MPI_FLOAT, MPI_ANY_SOURCE, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
256  nh[(int) buf[0]] = Neighbor((int) buf[1], buf[2]);
257  nh_high_dens[(int) buf[0]] = Neighbor((int) buf[3], buf[4]);
258  }
259  } else {
260  for (std::size_t i=i_row_from; i < i_row_to; ++i) {
261  buf[0] = (float) i;
262  buf[1] = (float) nh[i].first;
263  buf[2] = nh[i].second;
264  buf[3] = nh_high_dens[i].first;
265  buf[4] = nh_high_dens[i].second;
266  MPI_Send(buf, BUF_SIZE, MPI_FLOAT, MAIN_PROCESS, 0, MPI_COMM_WORLD);
267  }
268  }
269  }
270  // broadcast result to slaves
271  MPI_Barrier(MPI_COMM_WORLD);
272  {
273  // buf: n_rows X {I_FROM, I_TO_NH, DIST, I_TO_NH_HD, DIST_HD}
274  int BUF_SIZE = 4*n_rows;
275  std::vector<float> buf(BUF_SIZE);
276  if (mpi_node_id == MAIN_PROCESS) {
277  for (std::size_t i=0; i < n_rows; ++i) {
278  buf[4*i] = (float) nh[i].first;
279  buf[4*i+1] = nh[i].second;
280  buf[4*i+2] = (float) nh_high_dens[i].first;
281  buf[4*i+3] = nh_high_dens[i].second;
282  }
283  }
284  MPI_Bcast(buf.data(), BUF_SIZE, MPI_FLOAT, MAIN_PROCESS, MPI_COMM_WORLD);
285  if (mpi_node_id != MAIN_PROCESS) {
286  // unpack broadcasted neighborhood data
287  for (std::size_t i=0; i < n_rows; ++i) {
288  nh[i] = Neighbor((int) buf[4*i], buf[4*i+1]);
289  nh_high_dens[i] = Neighbor((int) buf[4*i+2], buf[4*i+3]);
290  }
291  }
292  }
293  return std::make_tuple(nh, nh_high_dens);
294  }
295 
296 
297  std::set<std::size_t>
298  high_density_neighborhood(const float* coords,
299  const std::size_t n_cols,
300  const std::vector<FreeEnergy>& sorted_fe,
301  const std::size_t i_frame,
302  const std::size_t limit,
303  const float max_dist,
304  const int mpi_n_nodes,
305  const int mpi_node_id) {
306  unsigned int rows_per_chunk = limit / mpi_n_nodes;
307  unsigned int i_row_from = mpi_node_id * rows_per_chunk;
308  unsigned int i_row_to = i_row_from + rows_per_chunk;
309  if (mpi_node_id == mpi_n_nodes-1) {
310  i_row_to = limit;
311  }
312  // compute local neighborhood parallel on several MPI nodes,
313  // with per-node shared memory parallelization via OpenMP threads.
314  std::set<std::size_t> nh;
315  {
316  // buffer to hold information whether frame i is
317  // in neighborhood (-> assign 1) or not (-> keep 0)
318  std::vector<int> frame_in_nh(limit, 0);
319  std::size_t j,c;
320  const std::size_t i_frame_sorted = sorted_fe[i_frame].first * n_cols;
321  float d,dist2;
322  ASSUME_ALIGNED(coords);
323  #pragma omp parallel for default(none) private(j,c,d,dist2) \
324  firstprivate(i_frame,i_frame_sorted,i_row_from,i_row_to,max_dist,n_cols) \
325  shared(coords,sorted_fe,frame_in_nh)
326  for (j=i_row_from; j < i_row_to; ++j) {
327  if (i_frame != j) {
328  dist2 = 0.0f;
329  #pragma simd reduction(+:dist2)
330  for (c=0; c < n_cols; ++c) {
331  d = coords[i_frame_sorted+c] - coords[sorted_fe[j].first*n_cols+c];
332  dist2 += d*d;
333  }
334  if (dist2 < max_dist) {
335  frame_in_nh[j] = 1;
336  }
337  }
338  }
339  // reduce buffer data to real neighborhood structure
340  for (j=i_row_from; j < i_row_to; ++j) {
341  if (frame_in_nh[j] > 0) {
342  nh.insert(j);
343  }
344  }
345  }
346  // collect data from all slaves in MAIN_PROCESS
347  MPI_Barrier(MPI_COMM_WORLD);
348  if (mpi_node_id == MAIN_PROCESS) {
349  for (int i=1; i < mpi_n_nodes; ++i) {
350  // get number of elements in set for correct buffer size
351  MPI_Status stat;
352  MPI_Probe(i, MPI_ANY_TAG, MPI_COMM_WORLD, &stat);
353  int n_neighbors;
354  MPI_Get_count(&stat, MPI_INT, &n_neighbors);
355  std::vector<unsigned int> buf(n_neighbors);
356  MPI_Recv(buf.data(), n_neighbors, MPI_UNSIGNED, i, MPI_ANY_TAG, MPI_COMM_WORLD, MPI_STATUS_IGNORE);
357  for (auto i_neighbor: buf) {
358  nh.insert(i_neighbor);
359  }
360  }
361  nh.insert(i_frame);
362  } else {
363  // send data
364  std::vector<unsigned int> buf(nh.size());
365  std::copy(nh.begin(), nh.end(), buf.begin());
366  MPI_Send(buf.data(), buf.size(), MPI_UNSIGNED, MAIN_PROCESS, MPI_ANY_TAG, MPI_COMM_WORLD);
367  }
368  // broadcast data to slaves
369  unsigned int n_neighbors_total;
370  std::vector<unsigned int> buf;
371  if (mpi_node_id == MAIN_PROCESS) {
372  n_neighbors_total = nh.size();
373  }
374  MPI_Barrier(MPI_COMM_WORLD);
375  MPI_Bcast(&n_neighbors_total, 1, MPI_UNSIGNED, MAIN_PROCESS, MPI_COMM_WORLD);
376  buf.resize(n_neighbors_total);
377  if (mpi_node_id == MAIN_PROCESS) {
378  // fill buffer for broadcast
379  std::copy(nh.begin(), nh.end(), buf.begin());
380  }
381  MPI_Bcast(buf.data(), n_neighbors_total, MPI_UNSIGNED, MAIN_PROCESS, MPI_COMM_WORLD);
382  if (mpi_node_id != MAIN_PROCESS) {
383  // buffer -> set for slaves after broadcast
384  nh.clear();
385  for (auto i_neighbor: buf) {
386  nh.insert(i_neighbor);
387  }
388  }
389  return nh;
390  }
391 
392  void
393  main(boost::program_options::variables_map args) {
394  // initialize MPI
395  MPI_Init(NULL, NULL);
396  int n_nodes;
397  MPI_Comm_size(MPI_COMM_WORLD, &n_nodes);
398  int node_id;
399  MPI_Comm_rank(MPI_COMM_WORLD, &node_id);
400  // read basic inputs
401  const std::string input_file = args["file"].as<std::string>();
402  // setup coords
403  float* coords;
404  std::size_t n_rows;
405  std::size_t n_cols;
406  if (node_id == MAIN_PROCESS) {
407  Clustering::logger(std::cout) << "reading coords" << std::endl;
408  }
409  std::tie(coords, n_rows, n_cols) = Clustering::Tools::read_coords<float>(input_file);
411  std::vector<float> free_energies;
412  if (args.count("free-energy-input")) {
413  if (node_id == MAIN_PROCESS) {
414  Clustering::logger(std::cout) << "re-using free energy data." << std::endl;
415  }
416  free_energies = Clustering::Tools::read_free_energies(args["free-energy-input"].as<std::string>());
417  } else if (args.count("free-energy") || args.count("population") || args.count("output")) {
418  if (args.count("radii")) {
419  // compute populations & free energies for different radii in one go
420  if (args.count("output")) {
421  if (node_id == MAIN_PROCESS) {
422  std::cerr << "error: clustering cannot be done with several radii (-R is set)." << std::endl;
423  }
424  exit(EXIT_FAILURE);
425  }
426  std::vector<float> radii = args["radii"].as<std::vector<float>>();
427  std::map<float, std::vector<std::size_t>> pops = calculate_populations(coords, n_rows, n_cols, radii, n_nodes, node_id);
428  if (node_id == MAIN_PROCESS) {
429  for (auto radius_pops: pops) {
430  std::string basename_pop = args["population"].as<std::string>() + "_%f";
431  std::string basename_fe = args["free-energy"].as<std::string>() + "_%f";
432  Clustering::Tools::write_pops(Clustering::Tools::stringprintf(basename_pop, radius_pops.first), radius_pops.second);
433  Clustering::Tools::write_fes(Clustering::Tools::stringprintf(basename_fe, radius_pops.first), calculate_free_energies(radius_pops.second));
434  }
435  }
436  } else {
437  if ( ! args.count("radius")) {
438  std::cerr << "error: radius (-r) is required!" << std::endl;
439  }
440  const float radius = args["radius"].as<float>();
441  if (node_id == MAIN_PROCESS) {
442  Clustering::logger(std::cout) << "calculating populations" << std::endl;
443  }
444  std::vector<std::size_t> pops = calculate_populations(coords, n_rows, n_cols, radius, n_nodes, node_id);
445  if (node_id == MAIN_PROCESS && args.count("population")) {
446  Clustering::Tools::write_single_column<std::size_t>(args["population"].as<std::string>(), pops);
447  }
448  if (node_id == MAIN_PROCESS) {
449  Clustering::logger(std::cout) << "calculating free energies" << std::endl;
450  }
451  free_energies = Clustering::Density::calculate_free_energies(pops);
452  if (node_id == MAIN_PROCESS && args.count("free-energy")) {
453  Clustering::Tools::write_single_column<float>(args["free-energy"].as<std::string>(), free_energies, true);
454  }
455  }
456  }
458  Neighborhood nh;
459  Neighborhood nh_high_dens;
460  if (args.count("nearest-neighbors-input")) {
461  Clustering::logger(std::cout) << "re-using nearest neighbor data." << std::endl;
462  auto nh_pair = Clustering::Tools::read_neighborhood(args["nearest-neighbors-input"].as<std::string>());
463  nh = nh_pair.first;
464  nh_high_dens = nh_pair.second;
465  } else if (args.count("nearest-neighbors") || args.count("output")) {
466  Clustering::logger(std::cout) << "calculating nearest neighbors" << std::endl;
467  auto nh_tuple = nearest_neighbors(coords, n_rows, n_cols, free_energies, n_nodes, node_id);
468  nh = std::get<0>(nh_tuple);
469  nh_high_dens = std::get<1>(nh_tuple);
470  if (node_id == MAIN_PROCESS && args.count("nearest-neighbors")) {
471  Clustering::Tools::write_neighborhood(args["nearest-neighbors"].as<std::string>(), nh, nh_high_dens);
472  }
473  }
475  if (args.count("output")) {
476  const std::string output_file = args["output"].as<std::string>();
477  std::vector<std::size_t> clustering;
478  if (args.count("input")) {
479  Clustering::logger(std::cout) << "reading initial clusters from file." << std::endl;
480  clustering = Clustering::Tools::read_clustered_trajectory(args["input"].as<std::string>());
481  } else {
482  Clustering::logger(std::cout) << "calculating initial clusters" << std::endl;
483  if (args.count("threshold") == 0) {
484  std::cerr << "error: need threshold value for initial clustering" << std::endl;
485  exit(EXIT_FAILURE);
486  }
487  float threshold = args["threshold"].as<float>();
488  clustering = Clustering::Density::initial_density_clustering(free_energies, nh, threshold, coords, n_rows, n_cols, {}, n_nodes, node_id);
489  }
490  if (node_id == MAIN_PROCESS) {
491  if ( ! args["only-initial"].as<bool>()) {
492  Clustering::logger(std::cout) << "assigning low density states to initial clusters" << std::endl;
493  clustering = Clustering::Density::assign_low_density_frames(clustering, nh_high_dens, free_energies);
494  }
495  Clustering::logger(std::cout) << "writing clusters to file " << output_file << std::endl;
496  Clustering::Tools::write_single_column<std::size_t>(output_file, clustering);
497  }
498  }
499  // clean up
500  if (node_id == MAIN_PROCESS) {
501  Clustering::logger(std::cout) << "freeing coords" << std::endl;
502  }
504  MPI_Finalize();
505  }
506 
507 } // end namespace MPI
508 } // end namespace Density
509 } // end namespace Clustering
510 
const int MAIN_PROCESS
identify MPI process 0 as main process
std::vector< std::size_t > assign_low_density_frames(const std::vector< std::size_t > &initial_clustering, const Neighborhood &nh_high_dens, const std::vector< float > &free_energy)
std::vector< std::size_t > triangular_load_balance(std::size_t n_rows, std::size_t n_nodes)
std::tuple< Neighborhood, Neighborhood > nearest_neighbors(const float *coords, const std::size_t n_rows, const std::size_t n_cols, const std::vector< float > &free_energy, const int mpi_n_nodes, const int mpi_node_id)
std::vector< float > read_free_energies(std::string filename)
read free energies from plain text file
Definition: tools.cpp:111
Clustering::Tools::Neighborhood Neighborhood
map frame id to neighbors
void free_coords(NUM *coords)
free memory pointing to coordinates
Definition: tools.hxx:107
void write_pops(std::string fname, std::vector< std::size_t > pops)
write populations as column into given file
Definition: tools.cpp:48
std::ostream & logger(std::ostream &s)
Definition: logger.cpp:32
std::vector< std::size_t > initial_density_clustering(const std::vector< float > &free_energy, const Neighborhood &nh, const float free_energy_threshold, const float *coords, const std::size_t n_rows, const std::size_t n_cols, const std::vector< std::size_t > initial_clusters)
void write_fes(std::string fname, std::vector< float > fes)
write free energies as column into given file
Definition: tools.cpp:34
std::pair< Neighborhood, Neighborhood > read_neighborhood(const std::string fname)
Definition: tools.cpp:116
std::vector< float > calculate_free_energies(const std::vector< std::size_t > &pops)
void main(boost::program_options::variables_map args)
Clustering::Tools::Neighbor Neighbor
matches neighbor's frame id to distance
std::vector< std::size_t > calculate_populations(const float *coords, const std::size_t n_rows, const std::size_t n_cols, const float radius, const int mpi_n_nodes, const int mpi_node_id)
std::vector< std::size_t > read_clustered_trajectory(std::string filename)
read states from trajectory (given as plain text file)
Definition: tools.cpp:54
std::string stringprintf(const std::string &str,...)
printf-version for std::string
Definition: tools.cpp:95
std::set< std::size_t > high_density_neighborhood(const float *coords, const std::size_t n_cols, const std::vector< FreeEnergy > &sorted_fe, const std::size_t i_frame, const std::size_t limit, const float max_dist, const int mpi_n_nodes, const int mpi_node_id)
void write_neighborhood(const std::string fname, const Neighborhood &nh, const Neighborhood &nh_high_dens)
Definition: tools.cpp:145