clustering  0.12
Clustering suite for molecular dynamics trajectories.
 All Classes Namespaces Files Functions Variables Typedefs
density_clustering.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 "tools.hpp"
27 #include "logger.hpp"
28 #include "density_clustering.hpp"
30 #ifdef DC_USE_OPENCL
31  #include "density_clustering_opencl.hpp"
32 #endif
33 
34 #include <algorithm>
35 
36 namespace Clustering {
37  namespace Density {
38  BoxGrid
39  compute_box_grid(const float* coords,
40  const std::size_t n_rows,
41  const std::size_t n_cols,
42  const float radius) {
43  // use first and second coordinates, since these usually
44  // correspond to first and second PCs, having highest variance.
45  // if clustering is only in 1 dimension, boxes of higher dimensions
46  // will be kept empty.
47  const int BOX_DIM_1 = 0;
48  const int BOX_DIM_2 = 1;
49  BoxGrid grid;
50  ASSUME_ALIGNED(coords);
51  // find min/max values for first and second dimension
52  float min_x1=0.0f, max_x1=0.0f, min_x2=0.0f, max_x2=0.0f;
53  min_x1=coords[0*n_cols+BOX_DIM_1];
54  max_x1=coords[0*n_cols+BOX_DIM_1];
55  if (n_cols > 1) {
56  min_x2=coords[0*n_cols+BOX_DIM_2];
57  max_x2=coords[0*n_cols+BOX_DIM_2];
58  }
59  Clustering::logger(std::cout) << "setting up boxes for fast NN search" << std::endl;
60  for (std::size_t i=1; i < n_rows; ++i) {
61  min_x1 = std::min(min_x1, coords[i*n_cols+BOX_DIM_1]);
62  max_x1 = std::max(max_x1, coords[i*n_cols+BOX_DIM_1]);
63  if (n_cols > 1) {
64  min_x2 = std::min(min_x2, coords[i*n_cols+BOX_DIM_2]);
65  max_x2 = std::max(max_x2, coords[i*n_cols+BOX_DIM_2]);
66  }
67  }
68  // build 2D grid with boxes for efficient nearest neighbor search
69  grid.n_boxes.push_back((max_x1 - min_x1) / radius + 1);
70  if (n_cols > 1) {
71  grid.n_boxes.push_back((max_x2 - min_x2) / radius + 1);
72  } else {
73  grid.n_boxes.push_back(1);
74  }
75  grid.assigned_box.resize(n_rows);
76  for (std::size_t i=0; i < n_rows; ++i) {
77  int i_box_1=0, i_box_2=0;
78  i_box_1 = (coords[i*n_cols+BOX_DIM_1] - min_x1) / radius;
79  if (n_cols > 1) {
80  i_box_2 = (coords[i*n_cols+BOX_DIM_2] - min_x2) / radius;
81  }
82  grid.assigned_box[i] = {i_box_1, i_box_2};
83  grid.boxes[grid.assigned_box[i]].push_back(i);
84  }
85  return grid;
86  }
87 
88  constexpr Box
89  neighbor_box(const Box center, const int i_neighbor) {
90  return {center[0] + BOX_DIFF[i_neighbor][0]
91  , center[1] + BOX_DIFF[i_neighbor][1]};
92  }
93 
94  bool
95  is_valid_box(const Box box, const BoxGrid& grid) {
96  int i1 = box[0];
97  int i2 = box[1];
98  return ((i1 >= 0)
99  && (i1 < grid.n_boxes[0])
100  && (i2 >= 0)
101  && (i2 < grid.n_boxes[1]));
102  }
103 
104  std::vector<std::size_t>
105  calculate_populations(const float* coords,
106  const std::size_t n_rows,
107  const std::size_t n_cols,
108  const float radius) {
109  std::vector<float> radii = {radius};
110 #ifdef DC_USE_OPENCL
111  std::map<float, std::vector<std::size_t>> pop_map =
113  , n_rows
114  , n_cols
115  , radii);
116 #else
117  std::map<float, std::vector<std::size_t>> pop_map =
118  calculate_populations(coords, n_rows, n_cols, radii);
119 #endif
120  return pop_map[radius];
121  }
122 
123  std::map<float, std::vector<std::size_t>>
124  calculate_populations(const float* coords,
125  const std::size_t n_rows,
126  const std::size_t n_cols,
127  std::vector<float> radii) {
128  std::map<float, std::vector<std::size_t>> pops;
129  for (float rad: radii) {
130  pops[rad].resize(n_rows, 1);
131  }
132  std::sort(radii.begin(), radii.end(), std::greater<float>());
133  std::size_t n_radii = radii.size();
134  std::vector<float> rad2(n_radii);
135  for (std::size_t i=0; i < n_radii; ++i) {
136  rad2[i] = radii[i]*radii[i];
137  }
138  ASSUME_ALIGNED(coords);
139  std::size_t i, j, k, l, ib;
140  BoxGrid grid = compute_box_grid(coords, n_rows, n_cols, radii[0]);
141  Clustering::logger(std::cout) << " box grid: "
142  << grid.n_boxes[0]
143  << " x "
144  << grid.n_boxes[1]
145  << std::endl;
146  Clustering::logger(std::cout) << "computing pops" << std::endl;
147  float dist, c;
148  Box box;
149  Box center;
150  int i_neighbor;
151  std::vector<int> box_buffer;
152  #pragma omp parallel for default(none) private(i,box,box_buffer,center,i_neighbor,ib,dist,j,k,l,c) \
153  firstprivate(n_rows,n_cols,n_radii,radii,rad2,N_NEIGHBOR_BOXES) \
154  shared(coords,pops,grid) \
155  schedule(dynamic,1024)
156  for (i=0; i < n_rows; ++i) {
157  center = grid.assigned_box[i];
158  // loop over surrounding boxes to find neighbor candidates
159  for (i_neighbor=0; i_neighbor < N_NEIGHBOR_BOXES; ++i_neighbor) {
160  box = neighbor_box(center, i_neighbor);
161  if (is_valid_box(box, grid)) {
162  box_buffer = grid.boxes[box];
163  // loop over frames inside surrounding box
164  for (ib=0; ib < box_buffer.size(); ++ib) {
165  j = box_buffer[ib];
166  if (i < j) {
167  dist = 0.0f;
168  #pragma simd reduction(+:dist)
169  for (k=0; k < n_cols; ++k) {
170  c = coords[i*n_cols+k] - coords[j*n_cols+k];
171  dist += c*c;
172  }
173  for (l=0; l < n_radii; ++l) {
174  if (dist < rad2[l]) {
175  #pragma omp atomic
176  pops[radii[l]][i] += 1;
177  #pragma omp atomic
178  pops[radii[l]][j] += 1;
179  } else {
180  // if it's not in the bigger radius,
181  // it won't be in the smaller ones.
182  break;
183  }
184  }
185  }
186  }
187  }
188  }
189  }
190  return pops;
191  }
192 
193  std::vector<float>
194  calculate_free_energies(const std::vector<std::size_t>& pops) {
195  std::size_t i;
196  const std::size_t n_frames = pops.size();
197  const float max_pop = (float) ( * std::max_element(pops.begin(), pops.end()));
198  std::vector<float> fe(n_frames);
199  #pragma omp parallel for default(none) private(i) firstprivate(max_pop, n_frames) shared(fe, pops)
200  for (i=0; i < n_frames; ++i) {
201  fe[i] = (float) -1 * log(pops[i]/max_pop);
202  }
203  return fe;
204  }
205 
206  std::vector<FreeEnergy>
207  sorted_free_energies(const std::vector<float>& fe) {
208  std::vector<FreeEnergy> fe_sorted;
209  for (std::size_t i=0; i < fe.size(); ++i) {
210  fe_sorted.push_back(FreeEnergy(i, fe[i]));
211  }
212  // sort for free energy: lowest to highest (low free energy = high density)
213  std::sort(fe_sorted.begin(),
214  fe_sorted.end(),
215  [] (const FreeEnergy& d1, const FreeEnergy& d2) -> bool {return d1.second < d2.second;});
216  return fe_sorted;
217  }
218 
219  std::tuple<Neighborhood, Neighborhood>
220  nearest_neighbors(const float* coords,
221  const std::size_t n_rows,
222  const std::size_t n_cols,
223  const std::vector<float>& free_energy) {
224  Neighborhood nh;
225  Neighborhood nh_high_dens;
226  // initialize neighborhood
227  for (std::size_t i=0; i < n_rows; ++i) {
228  nh[i] = Neighbor(n_rows+1, std::numeric_limits<float>::max());
229  nh_high_dens[i] = Neighbor(n_rows+1, std::numeric_limits<float>::max());
230  }
231  // calculate nearest neighbors with distances
232  std::size_t i, j, c, min_j, min_j_high_dens;
233  float dist, d, mindist, mindist_high_dens;
234  ASSUME_ALIGNED(coords);
235  #pragma omp parallel for default(none) \
236  private(i,j,c,dist,d,mindist,mindist_high_dens,min_j,min_j_high_dens) \
237  firstprivate(n_rows,n_cols) \
238  shared(coords,nh,nh_high_dens,free_energy) \
239  schedule(dynamic, 2048)
240  for (i=0; i < n_rows; ++i) {
241  mindist = std::numeric_limits<float>::max();
242  mindist_high_dens = std::numeric_limits<float>::max();
243  min_j = n_rows+1;
244  min_j_high_dens = n_rows+1;
245  for (j=0; j < n_rows; ++j) {
246  if (i != j) {
247  dist = 0.0f;
248  #pragma simd reduction(+:dist)
249  for (c=0; c < n_cols; ++c) {
250  d = coords[i*n_cols+c] - coords[j*n_cols+c];
251  dist += d*d;
252  }
253  // direct neighbor
254  if (dist < mindist) {
255  mindist = dist;
256  min_j = j;
257  }
258  // next neighbor with higher density / lower free energy
259  if (free_energy[j] < free_energy[i] && dist < mindist_high_dens) {
260  mindist_high_dens = dist;
261  min_j_high_dens = j;
262  }
263  }
264  }
265  nh[i] = Neighbor(min_j, mindist);
266  nh_high_dens[i] = Neighbor(min_j_high_dens, mindist_high_dens);
267  }
268  return std::make_tuple(nh, nh_high_dens);
269  }
270 
271  // returns neighborhood set of single frame.
272  // all ids are sorted in free energy.
273  std::set<std::size_t>
274  high_density_neighborhood(const float* coords,
275  const std::size_t n_cols,
276  const std::vector<FreeEnergy>& sorted_fe,
277  const std::size_t i_frame,
278  const std::size_t limit,
279  const float max_dist) {
280  // buffer to hold information whether frame i is
281  // in neighborhood (-> assign 1) or not (-> keep 0)
282  std::vector<int> frame_in_nh(limit, 0);
283  std::set<std::size_t> nh;
284  std::size_t j,c;
285  const std::size_t i_frame_sorted = sorted_fe[i_frame].first * n_cols;
286  float d,dist2;
287  ASSUME_ALIGNED(coords);
288  #pragma omp parallel for default(none) private(j,c,d,dist2) \
289  firstprivate(i_frame,i_frame_sorted,limit,max_dist,n_cols) \
290  shared(coords,sorted_fe,frame_in_nh)
291  for (j=0; j < limit; ++j) {
292  if (i_frame != j) {
293  dist2 = 0.0f;
294  #pragma simd reduction(+:dist2)
295  for (c=0; c < n_cols; ++c) {
296  d = coords[i_frame_sorted+c] - coords[sorted_fe[j].first*n_cols+c];
297  dist2 += d*d;
298  }
299  if (dist2 < max_dist) {
300  frame_in_nh[j] = 1;
301  }
302  }
303  }
304  // reduce buffer data to real neighborhood structure
305  for (j=0; j < limit; ++j) {
306  if (frame_in_nh[j] > 0) {
307  nh.insert(j);
308  }
309  }
310  nh.insert(i_frame);
311  return nh;
312  }
313 
314  double
316  double sigma2 = 0.0;
317  for (auto match: nh) {
318  // 'first second': nearest neighbor info
319  // 'second second': squared dist to nearest neighbor
320  sigma2 += match.second.second;
321  }
322  return (sigma2 / nh.size());
323  }
324 
325  std::vector<std::size_t>
326  assign_low_density_frames(const std::vector<std::size_t>& initial_clustering,
327  const Neighborhood& nh_high_dens,
328  const std::vector<float>& free_energy) {
329  std::vector<FreeEnergy> fe_sorted = sorted_free_energies(free_energy);
330  std::vector<std::size_t> clustering(initial_clustering);
331  for (const auto& fe: fe_sorted) {
332  std::size_t id = fe.first;
333  if (clustering[id] == 0) {
334  std::size_t neighbor_id = nh_high_dens.find(id)->second.first;
335  // assign cluster of nearest neighbor with higher density
336  clustering[id] = clustering[neighbor_id];
337  }
338  }
339  return clustering;
340  }
341 
342 #ifndef DC_USE_MPI
343  void
344  main(boost::program_options::variables_map args) {
345  using namespace Clustering::Tools;
346  const std::string input_file = args["file"].as<std::string>();
347  // setup coords
348  // TODO: use a simple 'Coords'-struct for these three
349  float* coords;
350  std::size_t n_rows;
351  std::size_t n_cols;
352  Clustering::logger(std::cout) << "reading coords" << std::endl;
353  std::tie(coords, n_rows, n_cols) = read_coords<float>(input_file);
355  std::vector<float> free_energies;
356  if (args.count("free-energy-input")) {
357  Clustering::logger(std::cout) << "re-using free energy data." << std::endl;
358  free_energies = read_free_energies(args["free-energy-input"].as<std::string>());
359  } else if (args.count("free-energy") || args.count("population") || args.count("output")) {
360  if (args.count("radii")) {
361  // compute populations & free energies for different radii in one go
362  if (args.count("output")) {
363  std::cerr << "error: clustering cannot be done with several radii (-R is set)." << std::endl;
364  exit(EXIT_FAILURE);
365  }
366  if ( ! (args.count("population") || args.count("free-energy"))) {
367  std::cerr << "error: no output defined for populations or free energies. why did you define -R ?" << std::endl;
368  exit(EXIT_FAILURE);
369  }
370  std::vector<float> radii = args["radii"].as<std::vector<float>>();
371 #ifdef DC_USE_OPENCL
372  std::map<float, std::vector<std::size_t>> pops = Clustering::Density::OpenCL::calculate_populations(coords, n_rows, n_cols, radii);
373 #else
374  std::map<float, std::vector<std::size_t>> pops = calculate_populations(coords, n_rows, n_cols, radii);
375 #endif
376  for (auto radius_pops: pops) {
377  if (args.count("population")) {
378  std::string basename_pop = args["population"].as<std::string>() + "_%f";
379  write_pops(Clustering::Tools::stringprintf(basename_pop, radius_pops.first), radius_pops.second);
380  }
381  if (args.count("free-energy")) {
382  std::string basename_fe = args["free-energy"].as<std::string>() + "_%f";
383  write_fes(Clustering::Tools::stringprintf(basename_fe, radius_pops.first), calculate_free_energies(radius_pops.second));
384  }
385  }
386  } else {
387  if ( ! args.count("radius")) {
388  std::cerr << "error: radius (-r) is required!" << std::endl;
389  }
390  const float radius = args["radius"].as<float>();
391  // compute populations & free energies for clustering and/or saving
392  Clustering::logger(std::cout) << "calculating populations" << std::endl;
393  std::vector<std::size_t> pops = calculate_populations(coords, n_rows, n_cols, radius);
394  if (args.count("population")) {
395  write_pops(args["population"].as<std::string>(), pops);
396  }
397  Clustering::logger(std::cout) << "calculating free energies" << std::endl;
398  free_energies = calculate_free_energies(pops);
399  if (args.count("free-energy")) {
400  write_fes(args["free-energy"].as<std::string>(), free_energies);
401  }
402  }
403  }
405  Neighborhood nh;
406  Neighborhood nh_high_dens;
407  if (args.count("nearest-neighbors-input")) {
408  Clustering::logger(std::cout) << "re-using nearest neighbor data." << std::endl;
409  auto nh_pair = read_neighborhood(args["nearest-neighbors-input"].as<std::string>());
410  nh = nh_pair.first;
411  nh_high_dens = nh_pair.second;
412  } else if (args.count("nearest-neighbors") || args.count("output")) {
413  Clustering::logger(std::cout) << "calculating nearest neighbors" << std::endl;
414  if ( ! args.count("radius")) {
415  std::cerr << "error: radius (-r) is required!" << std::endl;
416  }
417  auto nh_tuple = nearest_neighbors(coords, n_rows, n_cols, free_energies);
418  nh = std::get<0>(nh_tuple);
419  nh_high_dens = std::get<1>(nh_tuple);
420  if (args.count("nearest-neighbors")) {
421  Clustering::Tools::write_neighborhood(args["nearest-neighbors"].as<std::string>(), nh, nh_high_dens);
422  }
423  }
425  if (args.count("output")) {
426  const std::string output_file = args["output"].as<std::string>();
427  std::vector<std::size_t> clustering;
428  if (args.count("input")) {
429  Clustering::logger(std::cout) << "reading initial clusters from file." << std::endl;
430  clustering = read_clustered_trajectory(args["input"].as<std::string>());
431  } else if (args.count("threshold-screening")) {
432  std::vector<float> threshold_params = args["threshold-screening"].as<std::vector<float>>();
433  if (threshold_params.size() > 3) {
434  std::cerr << "error: option -T expects at most three floating point arguments: FROM STEP TO." << std::endl;
435  exit(EXIT_FAILURE);
436  }
437  Clustering::logger(std::cout) << "running free energy landscape screening" << std::endl;
438  float t_from = 0.1;
439  float t_step = 0.1;
440  float t_to = *std::max_element(free_energies.begin(), free_energies.end());
441  if (threshold_params.size() >= 1 && threshold_params[0] >= 0.0f) {
442  t_from = threshold_params[0];
443  }
444  if (threshold_params.size() >= 2) {
445  t_step = threshold_params[1];
446  }
447  if (threshold_params.size() == 3) {
448  t_to = threshold_params[2];
449  }
450  std::vector<std::size_t> clustering(n_rows);
451 //TODO what about 'fuzzy_equal' function?
452  // upper limit extended to a 10th of the stepsize to
453  // circumvent rounding errors when comparing on equality
454  float t_to_low = t_to - t_step/10.0f + t_step;
455  float t_to_high = t_to + t_step/10.0f + t_step;
456  for (float t=t_from; (t < t_to_low) && !(t_to_high < t); t += t_step) {
457  // compute clusters, re-using old results from previous step
458  clustering = initial_density_clustering(free_energies, nh, t, coords, n_rows, n_cols, clustering);
459  //TODO why is there an empty 'clust' file?
460 // clustering = initial_density_clustering(free_energies, nh, t, coords, n_rows, n_cols, {});
461  write_single_column(Clustering::Tools::stringprintf(output_file + ".%0.2f", t)
462  , clustering);
463  }
464  } else {
465  Clustering::logger(std::cout) << "calculating initial clusters" << std::endl;
466  if (args.count("threshold") == 0) {
467  std::cerr << "error: need threshold value for initial clustering" << std::endl;
468  exit(EXIT_FAILURE);
469  }
470  float threshold = args["threshold"].as<float>();
471  clustering = initial_density_clustering(free_energies, nh, threshold, coords, n_rows, n_cols, {});
472  }
473  if ( ! args["only-initial"].as<bool>() && ( ! args.count("threshold-screening"))) {
474  Clustering::logger(std::cout) << "assigning low density states to initial clusters" << std::endl;
475  clustering = assign_low_density_frames(clustering, nh_high_dens, free_energies);
476  }
477  Clustering::logger(std::cout) << "writing clusters to file " << output_file << std::endl;
478  write_single_column<std::size_t>(output_file, clustering);
479  }
480  Clustering::logger(std::cout) << "freeing coords" << std::endl;
481  free_coords(coords);
482  }
483 #endif
484  } // end namespace Density
485 } // end namespace Clustering
486 
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)
double compute_sigma2(const Neighborhood &nh)
const int N_NEIGHBOR_BOXES
number of neigbor boxes in 2D grid (including center box).
bool is_valid_box(const Box box, const BoxGrid &grid)
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)
std::vector< float > read_free_energies(std::string filename)
read free energies from plain text file
Definition: tools.cpp:111
std::map< float, std::vector< std::size_t > > calculate_populations(const float *coords, const std::size_t n_rows, const std::size_t n_cols, std::vector< float > radii)
void main(boost::program_options::variables_map args)
Clustering::Tools::Neighborhood Neighborhood
map frame id to neighbors
void free_coords(NUM *coords)
free memory pointing to coordinates
Definition: tools.hxx:107
std::vector< int > n_boxes
total number of boxes
std::map< Box, std::vector< int > > boxes
the boxes with a list of assigned frame ids
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)
void write_pops(std::string fname, std::vector< std::size_t > pops)
write populations as column into given file
Definition: tools.cpp:48
constexpr int BOX_DIFF[9][2]
void write_single_column(std::string filename, std::vector< NUM > dat, bool with_scientific_format=false)
write single column of numbers to given file. number type (int, float, ...) given as template paramet...
Definition: tools.hxx:147
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)
BoxGrid compute_box_grid(const float *coords, const std::size_t n_rows, const std::size_t n_cols, const float radius)
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)
calculate population of n-dimensional hypersphere per frame for one fixed radius. ...
constexpr Box neighbor_box(const Box center, const int i_neighbor)
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)
std::vector< FreeEnergy > sorted_free_energies(const std::vector< float > &fe)
std::pair< std::size_t, float > FreeEnergy
matches frame id to free energy
std::array< int, 2 > Box
encodes 2D box for box-assisted search algorithm
Clustering::Tools::Neighbor Neighbor
matches neighbor's frame id to distance
std::vector< Box > assigned_box
matching frame id to the frame's assigned box
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
void write_neighborhood(const std::string fname, const Neighborhood &nh, const Neighborhood &nh_high_dens)
Definition: tools.cpp:145