/* This file is part of the Gudhi Library. The Gudhi library * (Geometric Understanding in Higher Dimensions) is a generic C++ * library for computational topology. * * Author: Mathieu Carriere * * Copyright (C) 2017 Inria * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef GIC_H_ #define GIC_H_ #ifdef GUDHI_USE_TBB #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for numeric_limits #include // for std::pair<> #include // for std::max #include #include #include namespace Gudhi { namespace cover_complex { using Simplex_tree = Gudhi::Simplex_tree<>; using Filtration_value = Simplex_tree::Filtration_value; using Rips_complex = Gudhi::rips_complex::Rips_complex; using Persistence_diagram = std::vector >; using Graph = boost::subgraph< boost::adjacency_list > > >; using Vertex_t = boost::graph_traits::vertex_descriptor; using Index_map = boost::property_map::type; using Weight_map = boost::property_map::type; /** * \class Cover_complex * \brief Cover complex data structure. * * \ingroup cover_complex * * \details * The data structure is a simplicial complex, representing a * Graph Induced simplicial Complex (GIC) or a Nerve, * and whose simplices are computed with a cover C of a point * cloud P, which often comes from the preimages of intervals * covering the image of a function f defined on P. * These intervals are parameterized by their resolution * (either their length or their number) * and their gain (percentage of overlap). * To compute a GIC, one also needs a graph G built on top of P, * whose cliques with vertices belonging to different elements of C * correspond to the simplices of the GIC. * */ template class Cover_complex { private: bool verbose = false; // whether to display information. std::string type; // Nerve or GIC std::vector point_cloud; // input point cloud. std::vector > distances; // all pairwise distances. int maximal_dim; // maximal dimension of output simplicial complex. int data_dimension; // dimension of input data. int n; // number of points. std::vector func; // function used to compute the output simplicial complex. std::vector func_color; // function used to compute the colors of the nodes of the output simplicial complex. bool functional_cover = false; // whether we use a cover with preimages of a function or not. Graph one_skeleton_OFF; // one-skeleton given by the input OFF file (if it exists). Graph one_skeleton; // one-skeleton used to compute the connected components. std::vector vertices; // vertices of one_skeleton. std::vector > simplices; // simplices of output simplicial complex. std::vector voronoi_subsamples; // Voronoi germs (in case of Voronoi cover). Persistence_diagram PD; std::vector distribution; std::vector > cover; // function associating to each data point the vector of cover elements to which it belongs. std::map > cover_back; // inverse of cover, in order to get the data points associated to a specific cover element. std::map cover_std; // standard function (induced by func) used to compute the extended persistence // diagram of the output simplicial complex. std::map cover_fct; // integer-valued function that allows to state if two elements of the cover are consecutive or not. std::map > cover_color; // size and coloring (induced by func_color) of the vertices of the output simplicial complex. int resolution_int = -1; double resolution_double = -1; double gain = -1; double rate_constant = 10; // Constant in the subsampling. double rate_power = 0.001; // Power in the subsampling. int mask = 0; // Ignore nodes containing less than mask points. std::map name2id, name2idinv; std::string cover_name; std::string point_cloud_name; std::string color_name; // Remove all edges of a graph. void remove_edges(Graph& G) { boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(G); ei != ei_end; ++ei) boost::remove_edge(*ei, G); } // Thread local is not available on XCode version < V.8 // If not available, random engine is a class member. #ifndef GUDHI_CAN_USE_CXX11_THREAD_LOCAL std::default_random_engine re; #endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL // Find random number in [0,1]. double GetUniform() { // Thread local is not available on XCode version < V.8 // If available, random engine is defined for each thread. #ifdef GUDHI_CAN_USE_CXX11_THREAD_LOCAL thread_local std::default_random_engine re; #endif // GUDHI_CAN_USE_CXX11_THREAD_LOCAL std::uniform_real_distribution Dist(0, 1); return Dist(re); } // Subsample points. void SampleWithoutReplacement(int populationSize, int sampleSize, std::vector& samples) { int t = 0; int m = 0; double u; while (m < sampleSize) { u = GetUniform(); if ((populationSize - t) * u >= sampleSize - m) { t++; } else { samples[m] = t; t++; m++; } } } // ******************************************************************************************************************* // Utils. // ******************************************************************************************************************* public: /** \brief Specifies whether the type of the output simplicial complex. * * @param[in] t std::string (either "GIC" or "Nerve"). * */ void set_type(const std::string& t) { type = t; } public: /** \brief Specifies whether the program should display information or not. * * @param[in] verb boolean (true = display info, false = do not display info). * */ void set_verbose(bool verb = false) { verbose = verb; } public: /** \brief Sets the constants used to subsample the data set. These constants are * explained in \cite Carriere17c. * * @param[in] constant double. * @param[in] power double. * */ void set_subsampling(double constant, double power) { rate_constant = constant; rate_power = power; } public: /** \brief Sets the mask, which is a threshold integer such that nodes in the complex that contain a number of data * points which is less than or equal to * this threshold are not displayed. * * @param[in] nodemask integer. * */ void set_mask(int nodemask) { mask = nodemask; } public: /** \brief Reads and stores the input point cloud. * * @param[in] off_file_name name of the input .OFF or .nOFF file. * */ bool read_point_cloud(const std::string& off_file_name) { point_cloud_name = off_file_name; std::ifstream input(off_file_name); std::string line; char comment = '#'; while (comment == '#') { std::getline(input, line); if (!line.empty() && !all_of(line.begin(), line.end(), (int (*)(int))isspace)) comment = line[line.find_first_not_of(' ')]; } if (strcmp((char*)line.c_str(), "nOFF") == 0) { comment = '#'; while (comment == '#') { std::getline(input, line); if (!line.empty() && !all_of(line.begin(), line.end(), (int (*)(int))isspace)) comment = line[line.find_first_not_of(' ')]; } std::stringstream stream(line); stream >> data_dimension; } else { data_dimension = 3; } comment = '#'; int numedges, numfaces, i, dim; while (comment == '#') { std::getline(input, line); if (!line.empty() && !all_of(line.begin(), line.end(), (int (*)(int))isspace)) comment = line[line.find_first_not_of(' ')]; } std::stringstream stream(line); stream >> n; stream >> numfaces; stream >> numedges; i = 0; while (i < n) { std::getline(input, line); if (!line.empty() && line[line.find_first_not_of(' ')] != '#' && !all_of(line.begin(), line.end(), (int (*)(int))isspace)) { std::stringstream iss(line); std::vector point; point.assign(std::istream_iterator(iss), std::istream_iterator()); point_cloud.emplace_back(point.begin(), point.begin() + data_dimension); boost::add_vertex(one_skeleton_OFF); vertices.push_back(boost::add_vertex(one_skeleton)); cover.emplace_back(); i++; } } i = 0; while (i < numfaces) { std::getline(input, line); if (!line.empty() && line[line.find_first_not_of(' ')] != '#' && !all_of(line.begin(), line.end(), (int (*)(int))isspace)) { std::vector simplex; std::stringstream iss(line); simplex.assign(std::istream_iterator(iss), std::istream_iterator()); dim = simplex[0]; for (int j = 1; j <= dim; j++) for (int k = j + 1; k <= dim; k++) boost::add_edge(vertices[simplex[j]], vertices[simplex[k]], one_skeleton_OFF); i++; } } return input.is_open(); } // ******************************************************************************************************************* // Graphs. // ******************************************************************************************************************* public: // Set graph from file. /** \brief Creates a graph G from a file containing the edges. * * @param[in] graph_file_name name of the input graph file. * The graph file contains one edge per line, * each edge being represented by the IDs of its two nodes. * */ void set_graph_from_file(const std::string& graph_file_name) { remove_edges(one_skeleton); int neighb; std::ifstream input(graph_file_name); std::string line; int source; while (std::getline(input, line)) { std::stringstream stream(line); stream >> source; while (stream >> neighb) boost::add_edge(vertices[source], vertices[neighb], one_skeleton); } } public: // Set graph from OFF file. /** \brief Creates a graph G from the triangulation given by the input .OFF file. * */ void set_graph_from_OFF() { remove_edges(one_skeleton); if (num_edges(one_skeleton_OFF)) one_skeleton = one_skeleton_OFF; else std::cout << "No triangulation read in OFF file!" << std::endl; } public: // Set graph from Rips complex. /** \brief Creates a graph G from a Rips complex. * * @param[in] threshold threshold value for the Rips complex. * @param[in] distance distance used to compute the Rips complex. * */ template void set_graph_from_rips(double threshold, Distance distance) { remove_edges(one_skeleton); if (distances.size() == 0) compute_pairwise_distances(distance); for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (distances[i][j] <= threshold) { boost::add_edge(vertices[i], vertices[j], one_skeleton); boost::put(boost::edge_weight, one_skeleton, boost::edge(vertices[i], vertices[j], one_skeleton).first, distances[i][j]); } } } } public: void set_graph_weights() { Index_map index = boost::get(boost::vertex_index, one_skeleton); Weight_map weight = boost::get(boost::edge_weight, one_skeleton); boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(one_skeleton); ei != ei_end; ++ei) boost::put(weight, *ei, distances[index[boost::source(*ei, one_skeleton)]][index[boost::target(*ei, one_skeleton)]]); } public: // Pairwise distances. /** \private \brief Computes all pairwise distances. */ template void compute_pairwise_distances(Distance ref_distance) { double d; std::vector zeros(n); for (int i = 0; i < n; i++) distances.push_back(zeros); std::string distance = point_cloud_name + "_dist"; std::ifstream input(distance, std::ios::out | std::ios::binary); if (input.good()) { if (verbose) std::cout << "Reading distances..." << std::endl; for (int i = 0; i < n; i++) { for (int j = i; j < n; j++) { input.read((char*)&d, 8); distances[i][j] = d; distances[j][i] = d; } } input.close(); } else { if (verbose) std::cout << "Computing distances..." << std::endl; input.close(); std::ofstream output(distance, std::ios::out | std::ios::binary); for (int i = 0; i < n; i++) { int state = (int)floor(100 * (i * 1.0 + 1) / n) % 10; if (state == 0 && verbose) std::cout << "\r" << state << "%" << std::flush; for (int j = i; j < n; j++) { double dis = ref_distance(point_cloud[i], point_cloud[j]); distances[i][j] = dis; distances[j][i] = dis; output.write((char*)&dis, 8); } } output.close(); if (verbose) std::cout << std::endl; } } public: // Automatic tuning of Rips complex. /** \brief Creates a graph G from a Rips complex whose threshold value is automatically tuned with subsampling---see * \cite Carriere17c. * * @param[in] distance distance between data points. * @param[in] N number of subsampling iteration (the default reasonable value is 100, but there is no guarantee on * how to choose it). * @result delta threshold used for computing the Rips complex. * */ template double set_graph_from_automatic_rips(Distance distance, int N = 100) { int m = floor(n / std::exp((1 + rate_power) * std::log(std::log(n) / std::log(rate_constant)))); m = std::min(m, n - 1); double delta = 0; if (verbose) std::cout << n << " points in R^" << data_dimension << std::endl; if (verbose) std::cout << "Subsampling " << m << " points" << std::endl; if (distances.size() == 0) compute_pairwise_distances(distance); // This cannot be parallelized if thread_local is not defined // thread_local is not defined for XCode < v.8 #if defined(GUDHI_USE_TBB) && defined(GUDHI_CAN_USE_CXX11_THREAD_LOCAL) tbb::mutex deltamutex; tbb::parallel_for(0, N, [&](int i){ std::vector samples(m); SampleWithoutReplacement(n, m, samples); double hausdorff_dist = 0; for (int j = 0; j < n; j++) { double mj = distances[j][samples[0]]; for (int k = 1; k < m; k++) mj = std::min(mj, distances[j][samples[k]]); hausdorff_dist = std::max(hausdorff_dist, mj); } deltamutex.lock(); delta += hausdorff_dist / N; deltamutex.unlock(); }); #else for (int i = 0; i < N; i++) { std::vector samples(m); SampleWithoutReplacement(n, m, samples); double hausdorff_dist = 0; for (int j = 0; j < n; j++) { double mj = distances[j][samples[0]]; for (int k = 1; k < m; k++) mj = std::min(mj, distances[j][samples[k]]); hausdorff_dist = std::max(hausdorff_dist, mj); } delta += hausdorff_dist / N; } #endif if (verbose) std::cout << "delta = " << delta << std::endl; set_graph_from_rips(delta, distance); return delta; } // ******************************************************************************************************************* // Functions. // ******************************************************************************************************************* public: // Set function from file. /** \brief Creates the function f from a file containing the function values. * * @param[in] func_file_name name of the input function file. * */ void set_function_from_file(const std::string& func_file_name) { int i = 0; std::ifstream input(func_file_name); std::string line; double f; while (std::getline(input, line)) { std::stringstream stream(line); stream >> f; func.push_back(f); i++; } functional_cover = true; cover_name = func_file_name; } public: // Set function from kth coordinate /** \brief Creates the function f from the k-th coordinate of the point cloud P. * * @param[in] k coordinate to use (start at 0). * */ void set_function_from_coordinate(int k) { for (int i = 0; i < n; i++) func.push_back(point_cloud[i][k]); functional_cover = true; cover_name = "coordinate " + std::to_string(k); } public: // Set function from vector. /** \brief Creates the function f from a vector stored in memory. * * @param[in] function input vector of values. * */ template void set_function_from_range(InputRange const& function) { for (int i = 0; i < n; i++) func.push_back(function[i]); functional_cover = true; } // ******************************************************************************************************************* // Covers. // ******************************************************************************************************************* public: // Automatic tuning of resolution. /** \brief Computes the optimal length of intervals * (i.e. the smallest interval length avoiding discretization artifacts---see \cite Carriere17c) for a functional * cover. * * @result reso interval length used to compute the cover. * */ double set_automatic_resolution() { if (!functional_cover) { std::cout << "Cover needs to come from the preimages of a function." << std::endl; return 0; } if (type != "Nerve" && type != "GIC") { std::cout << "Type of complex needs to be specified." << std::endl; return 0; } double reso = 0; Index_map index = boost::get(boost::vertex_index, one_skeleton); if (type == "GIC") { boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(one_skeleton); ei != ei_end; ++ei) reso = std::max(reso, std::abs(func[index[boost::source(*ei, one_skeleton)]] - func[index[boost::target(*ei, one_skeleton)]])); if (verbose) std::cout << "resolution = " << reso << std::endl; resolution_double = reso; } if (type == "Nerve") { boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(one_skeleton); ei != ei_end; ++ei) reso = std::max(reso, std::abs(func[index[boost::source(*ei, one_skeleton)]] - func[index[boost::target(*ei, one_skeleton)]]) / gain); if (verbose) std::cout << "resolution = " << reso << std::endl; resolution_double = reso; } return reso; } public: /** \brief Sets a length of intervals from a value stored in memory. * * @param[in] reso length of intervals. * */ void set_resolution_with_interval_length(double reso) { resolution_double = reso; } /** \brief Sets a number of intervals from a value stored in memory. * * @param[in] reso number of intervals. * */ void set_resolution_with_interval_number(int reso) { resolution_int = reso; } /** \brief Sets a gain from a value stored in memory (default value 0.3). * * @param[in] g gain. * */ void set_gain(double g = 0.3) { gain = g; } public: // Set cover with preimages of function. /** \brief Creates a cover C from the preimages of the function f. * */ void set_cover_from_function() { if (resolution_double == -1 && resolution_int == -1) { std::cout << "Number and/or length of intervals not specified" << std::endl; return; } if (gain == -1) { std::cout << "Gain not specified" << std::endl; return; } // Read function values and compute min and max double minf = std::numeric_limits::max(); double maxf = std::numeric_limits::lowest(); for (int i = 0; i < n; i++) { minf = std::min(minf, func[i]); maxf = std::max(maxf, func[i]); } if (verbose) std::cout << "Min function value = " << minf << " and Max function value = " << maxf << std::endl; // Compute cover of im(f) std::vector > intervals; int res; if (resolution_double == -1) { // Case we use an integer for the number of intervals. double incr = (maxf - minf) / resolution_int; double x = minf; double alpha = (incr * gain) / (2 - 2 * gain); double y = minf + incr + alpha; std::pair interm(x, y); intervals.push_back(interm); for (int i = 1; i < resolution_int - 1; i++) { x = minf + i * incr - alpha; y = minf + (i + 1) * incr + alpha; std::pair inter(x, y); intervals.push_back(inter); } x = minf + (resolution_int - 1) * incr - alpha; y = maxf; std::pair interM(x, y); intervals.push_back(interM); res = intervals.size(); if (verbose) { for (int i = 0; i < res; i++) std::cout << "Interval " << i << " = [" << intervals[i].first << ", " << intervals[i].second << "]" << std::endl; } } else { if (resolution_int == -1) { // Case we use a double for the length of the intervals. double x = minf; double y = x + resolution_double; while (y <= maxf && maxf - (y - gain * resolution_double) >= resolution_double) { std::pair inter(x, y); intervals.push_back(inter); x = y - gain * resolution_double; y = x + resolution_double; } std::pair interM(x, maxf); intervals.push_back(interM); res = intervals.size(); if (verbose) { for (int i = 0; i < res; i++) std::cout << "Interval " << i << " = [" << intervals[i].first << ", " << intervals[i].second << "]" << std::endl; } } else { // Case we use an integer and a double for the length of the intervals. double x = minf; double y = x + resolution_double; int count = 0; while (count < resolution_int && y <= maxf && maxf - (y - gain * resolution_double) >= resolution_double) { std::pair inter(x, y); intervals.push_back(inter); count++; x = y - gain * resolution_double; y = x + resolution_double; } res = intervals.size(); if (verbose) { for (int i = 0; i < res; i++) std::cout << "Interval " << i << " = [" << intervals[i].first << ", " << intervals[i].second << "]" << std::endl; } } } // Sort points according to function values std::vector points(n); for (int i = 0; i < n; i++) points[i] = i; std::sort(points.begin(), points.end(), [=](const int & p1, const int & p2){return (this->func[p1] < this->func[p2]);}); int id = 0; int pos = 0; Index_map index = boost::get(boost::vertex_index, one_skeleton); // int maxc = -1; std::map > preimages; std::map funcstd; if (verbose) std::cout << "Computing preimages..." << std::endl; for (int i = 0; i < res; i++) { // Find points in the preimage std::pair inter1 = intervals[i]; int tmp = pos; double u, v; if (i != res - 1) { if (i != 0) { std::pair inter3 = intervals[i - 1]; while (func[points[tmp]] < inter3.second && tmp != n) { preimages[i].push_back(points[tmp]); tmp++; } u = inter3.second; } else { u = inter1.first; } std::pair inter2 = intervals[i + 1]; while (func[points[tmp]] < inter2.first && tmp != n) { preimages[i].push_back(points[tmp]); tmp++; } v = inter2.first; pos = tmp; while (func[points[tmp]] < inter1.second && tmp != n) { preimages[i].push_back(points[tmp]); tmp++; } } else { std::pair inter3 = intervals[i - 1]; while (func[points[tmp]] < inter3.second && tmp != n) { preimages[i].push_back(points[tmp]); tmp++; } while (tmp != n) { preimages[i].push_back(points[tmp]); tmp++; } u = inter3.second; v = inter1.second; } funcstd[i] = 0.5 * (u + v); } #ifdef GUDHI_USE_TBB if (verbose) std::cout << "Computing connected components (parallelized)..." << std::endl; tbb::mutex covermutex, idmutex; tbb::parallel_for(0, res, [&](int i){ // Compute connected components Graph G = one_skeleton.create_subgraph(); int num = preimages[i].size(); std::vector component(num); for (int j = 0; j < num; j++) boost::add_vertex(index[vertices[preimages[i][j]]], G); boost::connected_components(G, &component[0]); int max = 0; // For each point in preimage for (int j = 0; j < num; j++) { // Update number of components in preimage if (component[j] > max) max = component[j]; // Identify component with Cantor polynomial N^2 -> N int identifier = ((i + component[j])*(i + component[j]) + 3 * i + component[j]) / 2; // Update covers covermutex.lock(); cover[preimages[i][j]].push_back(identifier); cover_back[identifier].push_back(preimages[i][j]); cover_fct[identifier] = i; cover_std[identifier] = funcstd[i]; cover_color[identifier].second += func_color[preimages[i][j]]; cover_color[identifier].first += 1; covermutex.unlock(); } // Maximal dimension is total number of connected components idmutex.lock(); id += max + 1; idmutex.unlock(); }); #else if (verbose) std::cout << "Computing connected components..." << std::endl; for (int i = 0; i < res; i++) { // Compute connected components Graph G = one_skeleton.create_subgraph(); int num = preimages[i].size(); std::vector component(num); for (int j = 0; j < num; j++) boost::add_vertex(index[vertices[preimages[i][j]]], G); boost::connected_components(G, &component[0]); int max = 0; // For each point in preimage for (int j = 0; j < num; j++) { // Update number of components in preimage if (component[j] > max) max = component[j]; // Identify component with Cantor polynomial N^2 -> N int identifier = (std::pow(i + component[j], 2) + 3 * i + component[j]) / 2; // Update covers cover[preimages[i][j]].push_back(identifier); cover_back[identifier].push_back(preimages[i][j]); cover_fct[identifier] = i; cover_std[identifier] = funcstd[i]; cover_color[identifier].second += func_color[preimages[i][j]]; cover_color[identifier].first += 1; } // Maximal dimension is total number of connected components id += max + 1; } #endif maximal_dim = id - 1; for (std::map >::iterator iit = cover_color.begin(); iit != cover_color.end(); iit++) iit->second.second /= iit->second.first; } public: // Set cover from file. /** \brief Creates the cover C from a file containing the cover elements of each point (the order has to be the same * as in the input file!). * * @param[in] cover_file_name name of the input cover file. * */ void set_cover_from_file(const std::string& cover_file_name) { int i = 0; int cov; std::vector cov_elts, cov_number; std::ifstream input(cover_file_name); std::string line; while (std::getline(input, line)) { cov_elts.clear(); std::stringstream stream(line); while (stream >> cov) { cov_elts.push_back(cov); cov_number.push_back(cov); cover_fct[cov] = cov; cover_color[cov].second += func_color[i]; cover_color[cov].first++; cover_back[cov].push_back(i); } cover[i] = cov_elts; i++; } std::sort(cov_number.begin(), cov_number.end()); std::vector::iterator it = std::unique(cov_number.begin(), cov_number.end()); cov_number.resize(std::distance(cov_number.begin(), it)); maximal_dim = cov_number.size() - 1; for (int i = 0; i <= maximal_dim; i++) cover_color[i].second /= cover_color[i].first; cover_name = cover_file_name; } public: // Set cover from Voronoi /** \brief Creates the cover C from the Voronoï cells of a subsampling of the point cloud. * * @param[in] distance distance between the points. * @param[in] m number of points in the subsample. * */ template void set_cover_from_Voronoi(Distance distance, int m = 100) { voronoi_subsamples.resize(m); SampleWithoutReplacement(n, m, voronoi_subsamples); if (distances.size() == 0) compute_pairwise_distances(distance); set_graph_weights(); Weight_map weight = boost::get(boost::edge_weight, one_skeleton); Index_map index = boost::get(boost::vertex_index, one_skeleton); std::vector mindist(n); for (int j = 0; j < n; j++) mindist[j] = std::numeric_limits::max(); // Compute the geodesic distances to subsamples with Dijkstra #ifdef GUDHI_USE_TBB if (verbose) std::cout << "Computing geodesic distances (parallelized)..." << std::endl; tbb::mutex coverMutex; tbb::mutex mindistMutex; tbb::parallel_for(0, m, [&](int i){ int seed = voronoi_subsamples[i]; std::vector dmap(n); boost::dijkstra_shortest_paths( one_skeleton, vertices[seed], boost::weight_map(weight).distance_map(boost::make_iterator_property_map(dmap.begin(), index))); coverMutex.lock(); mindistMutex.lock(); for (int j = 0; j < n; j++) if (mindist[j] > dmap[j]) { mindist[j] = dmap[j]; if (cover[j].size() == 0) cover[j].push_back(i); else cover[j][0] = i; } coverMutex.unlock(); mindistMutex.unlock(); }); #else for (int i = 0; i < m; i++) { if (verbose) std::cout << "Computing geodesic distances to seed " << i << "..." << std::endl; int seed = voronoi_subsamples[i]; std::vector dmap(n); boost::dijkstra_shortest_paths( one_skeleton, vertices[seed], boost::weight_map(weight).distance_map(boost::make_iterator_property_map(dmap.begin(), index))); for (int j = 0; j < n; j++) if (mindist[j] > dmap[j]) { mindist[j] = dmap[j]; if (cover[j].size() == 0) cover[j].push_back(i); else cover[j][0] = i; } } #endif for (int i = 0; i < n; i++) { cover_back[cover[i][0]].push_back(i); cover_color[cover[i][0]].second += func_color[i]; cover_color[cover[i][0]].first++; } for (int i = 0; i < m; i++) cover_color[i].second /= cover_color[i].first; maximal_dim = m - 1; cover_name = "Voronoi"; } public: // return subset of data corresponding to a node /** \brief Returns the data subset corresponding to a specific node of the created complex. * * @param[in] c ID of the node. * @result cover_back(c) vector of IDs of data points. * */ const std::vector& subpopulation(int c) { return cover_back[name2idinv[c]]; } // ******************************************************************************************************************* // Visualization. // ******************************************************************************************************************* public: // Set color from file. /** \brief Computes the function used to color the nodes of the simplicial complex from a file containing the function * values. * * @param[in] color_file_name name of the input color file. * */ void set_color_from_file(const std::string& color_file_name) { int i = 0; std::ifstream input(color_file_name); std::string line; double f; while (std::getline(input, line)) { std::stringstream stream(line); stream >> f; func_color.push_back(f); i++; } color_name = color_file_name; } public: // Set color from kth coordinate /** \brief Computes the function used to color the nodes of the simplicial complex from the k-th coordinate. * * @param[in] k coordinate to use (start at 0). * */ void set_color_from_coordinate(int k = 0) { for (int i = 0; i < n; i++) func_color.push_back(point_cloud[i][k]); color_name = "coordinate "; color_name.append(std::to_string(k)); } public: // Set color from vector. /** \brief Computes the function used to color the nodes of the simplicial complex from a vector stored in memory. * * @param[in] color input vector of values. * */ void set_color_from_vector(std::vector color) { for (unsigned int i = 0; i < color.size(); i++) func_color.push_back(color[i]); } public: // Create a .dot file that can be compiled with neato to produce a .pdf file. /** \brief Creates a .dot file called SC.dot for neato (part of the graphviz package) once the simplicial complex is * computed to get a visualization * of its 1-skeleton in a .pdf file. */ void plot_DOT() { std::string mapp = point_cloud_name + "_sc.dot"; std::ofstream graphic(mapp); double maxv = std::numeric_limits::lowest(); double minv = std::numeric_limits::max(); for (std::map >::iterator iit = cover_color.begin(); iit != cover_color.end(); iit++) { maxv = std::max(maxv, iit->second.second); minv = std::min(minv, iit->second.second); } int k = 0; std::vector nodes; nodes.clear(); graphic << "graph GIC {" << std::endl; int id = 0; for (std::map >::iterator iit = cover_color.begin(); iit != cover_color.end(); iit++) { if (iit->second.first > mask) { nodes.push_back(iit->first); name2id[iit->first] = id; name2idinv[id] = iit->first; id++; graphic << name2id[iit->first] << "[shape=circle fontcolor=black color=black label=\"" << name2id[iit->first] << ":" << iit->second.first << "\" style=filled fillcolor=\"" << (1 - (maxv - iit->second.second) / (maxv - minv)) * 0.6 << ", 1, 1\"]" << std::endl; k++; } } int ke = 0; int num_simplices = simplices.size(); for (int i = 0; i < num_simplices; i++) if (simplices[i].size() == 2) { if (cover_color[simplices[i][0]].first > mask && cover_color[simplices[i][1]].first > mask) { graphic << " " << name2id[simplices[i][0]] << " -- " << name2id[simplices[i][1]] << " [weight=15];" << std::endl; ke++; } } graphic << "}"; graphic.close(); std::cout << mapp << " file generated. It can be visualized with e.g. neato." << std::endl; } public: // Create a .txt file that can be compiled with KeplerMapper. /** \brief Creates a .txt file called SC.txt describing the 1-skeleton, which can then be plotted with e.g. * KeplerMapper. */ void write_info() { int num_simplices = simplices.size(); int num_edges = 0; std::string mapp = point_cloud_name + "_sc.txt"; std::ofstream graphic(mapp); for (int i = 0; i < num_simplices; i++) if (simplices[i].size() == 2) if (cover_color[simplices[i][0]].first > mask && cover_color[simplices[i][1]].first > mask) num_edges++; graphic << point_cloud_name << std::endl; graphic << cover_name << std::endl; graphic << color_name << std::endl; graphic << resolution_double << " " << gain << std::endl; graphic << cover_color.size() << " " << num_edges << std::endl; int id = 0; for (std::map >::iterator iit = cover_color.begin(); iit != cover_color.end(); iit++) { graphic << id << " " << iit->second.second << " " << iit->second.first << std::endl; name2id[iit->first] = id; name2idinv[id] = iit->first; id++; } for (int i = 0; i < num_simplices; i++) if (simplices[i].size() == 2) if (cover_color[simplices[i][0]].first > mask && cover_color[simplices[i][1]].first > mask) graphic << name2id[simplices[i][0]] << " " << name2id[simplices[i][1]] << std::endl; graphic.close(); std::cout << mapp << " generated. It can be visualized with e.g. python KeplerMapperVisuFromTxtFile.py and firefox." << std::endl; } public: // Create a .off file that can be visualized (e.g. with Geomview). /** \brief Creates a .off file called SC.off for 3D visualization, which contains the 2-skeleton of the GIC. * This function assumes that the cover has been computed with Voronoi. If data points are in 1D or 2D, * the remaining coordinates of the points embedded in 3D are set to 0. */ void plot_OFF() { assert(cover_name == "Voronoi"); int m = voronoi_subsamples.size(); int numedges = 0; int numfaces = 0; std::vector > edges, faces; int numsimplices = simplices.size(); std::string mapp = point_cloud_name + "_sc.off"; std::ofstream graphic(mapp); graphic << "OFF" << std::endl; for (int i = 0; i < numsimplices; i++) { if (simplices[i].size() == 2) { numedges++; edges.push_back(simplices[i]); } if (simplices[i].size() == 3) { numfaces++; faces.push_back(simplices[i]); } } graphic << m << " " << numedges + numfaces << std::endl; for (int i = 0; i < m; i++) { if (data_dimension <= 3) { for (int j = 0; j < data_dimension; j++) graphic << point_cloud[voronoi_subsamples[i]][j] << " "; for (int j = data_dimension; j < 3; j++) graphic << 0 << " "; graphic << std::endl; } else { for (int j = 0; j < 3; j++) graphic << point_cloud[voronoi_subsamples[i]][j] << " "; } } for (int i = 0; i < numedges; i++) graphic << 2 << " " << edges[i][0] << " " << edges[i][1] << std::endl; for (int i = 0; i < numfaces; i++) graphic << 3 << " " << faces[i][0] << " " << faces[i][1] << " " << faces[i][2] << std::endl; graphic.close(); std::cout << mapp << " generated. It can be visualized with e.g. geomview." << std::endl; } // ******************************************************************************************************************* // Extended Persistence Diagrams. // ******************************************************************************************************************* public: /** \brief Computes the extended persistence diagram of the complex. * */ void compute_PD() { Simplex_tree st; // Compute max and min double maxf = std::numeric_limits::lowest(); double minf = std::numeric_limits::max(); for (std::map::iterator it = cover_std.begin(); it != cover_std.end(); it++) { maxf = std::max(maxf, it->second); minf = std::min(minf, it->second); } // Build filtration for (auto const& simplex : simplices) { std::vector splx = simplex; splx.push_back(-2); st.insert_simplex_and_subfaces(splx, -3); } for (std::map::iterator it = cover_std.begin(); it != cover_std.end(); it++) { int vertex = it->first; float val = it->second; int vert[] = {vertex}; int edge[] = {vertex, -2}; st.assign_filtration(st.find(vert), -2 + (val - minf)/(maxf - minf)); st.assign_filtration(st.find(edge), 2 - (val - minf)/(maxf - minf)); } st.make_filtration_non_decreasing(); // Compute PD Gudhi::persistent_cohomology::Persistent_cohomology pcoh(st); pcoh.init_coefficients(2); pcoh.compute_persistent_cohomology(); // Output PD int max_dim = st.dimension(); for (int i = 0; i < max_dim; i++) { std::vector > bars = pcoh.intervals_in_dimension(i); int num_bars = bars.size(); if(i == 0) num_bars -= 1; if(verbose) std::cout << num_bars << " interval(s) in dimension " << i << ":" << std::endl; for (int j = 0; j < num_bars; j++) { double birth = bars[j].first; double death = bars[j].second; if (i == 0 && std::isinf(death)) continue; if (birth < 0) birth = minf + (birth + 2) * (maxf - minf); else birth = minf + (2 - birth) * (maxf - minf); if (death < 0) death = minf + (death + 2) * (maxf - minf); else death = minf + (2 - death) * (maxf - minf); PD.push_back(std::pair(birth, death)); if (verbose) std::cout << " [" << birth << ", " << death << "]" << std::endl; } } } public: /** \brief Computes bootstrapped distances distribution. * * @param[in] N number of bootstrap iterations. * */ void compute_distribution(unsigned int N = 100) { unsigned int sz = distribution.size(); if (sz >= N) { std::cout << "Already done!" << std::endl; } else { for (unsigned int i = 0; i < N - sz; i++) { if (verbose) std::cout << "Computing " << i << "th bootstrap, bottleneck distance = "; Cover_complex Cboot; Cboot.n = this->n; Cboot.data_dimension = this->data_dimension; Cboot.type = this->type; Cboot.functional_cover = true; std::vector boot(this->n); for (int j = 0; j < this->n; j++) { double u = GetUniform(); int id = std::floor(u * (this->n)); boot[j] = id; Cboot.point_cloud.push_back(this->point_cloud[id]); Cboot.cover.emplace_back(); Cboot.func.push_back(this->func[id]); boost::add_vertex(Cboot.one_skeleton_OFF); Cboot.vertices.push_back(boost::add_vertex(Cboot.one_skeleton)); } Cboot.set_color_from_vector(Cboot.func); for (int j = 0; j < n; j++) { std::vector dist(n); for (int k = 0; k < n; k++) dist[k] = distances[boot[j]][boot[k]]; Cboot.distances.push_back(dist); } Cboot.set_graph_from_automatic_rips(Gudhi::Euclidean_distance()); Cboot.set_gain(); Cboot.set_automatic_resolution(); Cboot.set_cover_from_function(); Cboot.find_simplices(); Cboot.compute_PD(); double db = Gudhi::persistence_diagram::bottleneck_distance(this->PD, Cboot.PD); if (verbose) std::cout << db << std::endl; distribution.push_back(db); } std::sort(distribution.begin(), distribution.end()); } } public: /** \brief Computes the bottleneck distance threshold corresponding to a specific confidence level. * * @param[in] alpha Confidence level. * */ double compute_distance_from_confidence_level(double alpha) { unsigned int N = distribution.size(); double d = distribution[std::floor(alpha * N)]; if (verbose) std::cout << "Distance corresponding to confidence " << alpha << " is " << d << std::endl; return d; } public: /** \brief Computes the confidence level of a specific bottleneck distance threshold. * * @param[in] d Bottleneck distance. * */ double compute_confidence_level_from_distance(double d) { unsigned int N = distribution.size(); double level = 1; for (unsigned int i = 0; i < N; i++) if (distribution[i] > d){ level = i * 1.0 / N; break; } if (verbose) std::cout << "Confidence level of distance " << d << " is " << level << std::endl; return level; } public: /** \brief Computes the p-value, i.e. the opposite of the confidence level of the largest bottleneck * distance preserving the points in the persistence diagram of the output simplicial complex. * */ double compute_p_value() { double distancemin = std::numeric_limits::max(); int N = PD.size(); for (int i = 0; i < N; i++) distancemin = std::min(distancemin, 0.5 * std::abs(PD[i].second - PD[i].first)); double p_value = 1 - compute_confidence_level_from_distance(distancemin); if (verbose) std::cout << "p value = " << p_value << std::endl; return p_value; } // ******************************************************************************************************************* // Computation of simplices. // ******************************************************************************************************************* public: /** \brief Creates the simplicial complex. * * @param[in] complex SimplicialComplex to be created. * */ template void create_complex(SimplicialComplex& complex) { unsigned int dimension = 0; for (auto const& simplex : simplices) { int numvert = simplex.size(); double filt = std::numeric_limits::lowest(); for (int i = 0; i < numvert; i++) filt = std::max(cover_color[simplex[i]].second, filt); complex.insert_simplex_and_subfaces(simplex, filt); if (dimension < simplex.size() - 1) dimension = simplex.size() - 1; } } public: /** \brief Computes the simplices of the simplicial complex. */ void find_simplices() { if (type != "Nerve" && type != "GIC") { std::cout << "Type of complex needs to be specified." << std::endl; return; } if (type == "Nerve") { for(int i = 0; i < n; i++) simplices.push_back(cover[i]); std::sort(simplices.begin(), simplices.end()); std::vector >::iterator it = std::unique(simplices.begin(), simplices.end()); simplices.resize(std::distance(simplices.begin(), it)); } if (type == "GIC") { Index_map index = boost::get(boost::vertex_index, one_skeleton); if (functional_cover) { // Computes the simplices in the GIC by looking at all the edges of the graph and adding the // corresponding edges in the GIC if the images of the endpoints belong to consecutive intervals. if (gain >= 0.5) throw std::invalid_argument( "the output of this function is correct ONLY if the cover is minimal, i.e. the gain is less than 0.5."); // Loop on all edges. boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(one_skeleton); ei != ei_end; ++ei) { int nums = cover[index[boost::source(*ei, one_skeleton)]].size(); for (int i = 0; i < nums; i++) { int vs = cover[index[boost::source(*ei, one_skeleton)]][i]; int numt = cover[index[boost::target(*ei, one_skeleton)]].size(); for (int j = 0; j < numt; j++) { int vt = cover[index[boost::target(*ei, one_skeleton)]][j]; if (cover_fct[vs] == cover_fct[vt] + 1 || cover_fct[vt] == cover_fct[vs] + 1) { std::vector edge(2); edge[0] = std::min(vs, vt); edge[1] = std::max(vs, vt); simplices.push_back(edge); goto afterLoop; } } } afterLoop:; } std::sort(simplices.begin(), simplices.end()); std::vector >::iterator it = std::unique(simplices.begin(), simplices.end()); simplices.resize(std::distance(simplices.begin(), it)); } else { // Find edges to keep Simplex_tree st; boost::graph_traits::edge_iterator ei, ei_end; for (boost::tie(ei, ei_end) = boost::edges(one_skeleton); ei != ei_end; ++ei) if (!(cover[index[boost::target(*ei, one_skeleton)]].size() == 1 && cover[index[boost::target(*ei, one_skeleton)]] == cover[index[boost::source(*ei, one_skeleton)]])) { std::vector edge(2); edge[0] = index[boost::source(*ei, one_skeleton)]; edge[1] = index[boost::target(*ei, one_skeleton)]; st.insert_simplex_and_subfaces(edge); } // st.insert_graph(one_skeleton); // Build the Simplex Tree corresponding to the graph st.expansion(maximal_dim); // Find simplices of GIC simplices.clear(); for (auto simplex : st.complex_simplex_range()) { if (!st.has_children(simplex)) { std::vector simplx; for (auto vertex : st.simplex_vertex_range(simplex)) { unsigned int sz = cover[vertex].size(); for (unsigned int i = 0; i < sz; i++) { simplx.push_back(cover[vertex][i]); } } std::sort(simplx.begin(), simplx.end()); std::vector::iterator it = std::unique(simplx.begin(), simplx.end()); simplx.resize(std::distance(simplx.begin(), it)); simplices.push_back(simplx); } } std::sort(simplices.begin(), simplices.end()); std::vector >::iterator it = std::unique(simplices.begin(), simplices.end()); simplices.resize(std::distance(simplices.begin(), it)); } } } }; } // namespace cover_complex } // namespace Gudhi #endif // GIC_H_