/* This file is part of the Gudhi Library - https://gudhi.inria.fr/ - which is released under MIT. * See file LICENSE or go to https://gudhi.inria.fr/licensing/ for full license details. * Author(s): Clement Jamin * * Copyright (C) 2016 Inria * * Modification(s): * - YYYY/MM Author: Description of the modification */ #ifndef TANGENTIAL_COMPLEX_SIMPLICIAL_COMPLEX_H_ #define TANGENTIAL_COMPLEX_SIMPLICIAL_COMPLEX_H_ #include #include #include #include #include // For is_pure_pseudomanifold #include #include #include #include #include #include #include #include // for map<> #include // for vector<> #include // for set<> namespace Gudhi { namespace tangential_complex { namespace internal { class Simplicial_complex { public: typedef boost::container::flat_set Simplex; typedef std::set Simplex_set; // If perform_checks = true, the function: // - won't insert the simplex if it is already in a higher dim simplex // - will erase any lower-dim simplices that are faces of the new simplex // Returns true if the simplex was added bool add_simplex( const Simplex &s, bool perform_checks = true) { if (perform_checks) { unsigned int num_pts = static_cast (s.size()); std::vector to_erase; bool check_higher_dim_simpl = true; for (Complex::iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { // Check if the simplex is not already in a higher dim simplex if (check_higher_dim_simpl && it_simplex->size() > num_pts && std::includes(it_simplex->begin(), it_simplex->end(), s.begin(), s.end())) { // No need to insert it, then return false; } // Check if the simplex includes some lower-dim simplices if (it_simplex->size() < num_pts && std::includes(s.begin(), s.end(), it_simplex->begin(), it_simplex->end())) { to_erase.push_back(it_simplex); // We don't need to check higher-sim simplices any more check_higher_dim_simpl = false; } } for (std::vector::const_iterator it = to_erase.begin(); it != to_erase.end(); ++it) { m_complex.erase(*it); } } return m_complex.insert(s).second; } const Simplex_set &simplex_range() const { return m_complex; } bool empty() { return m_complex.empty(); } void clear() { m_complex.clear(); } template void get_simplices_matching_test(Test test, Output_it out) { for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { if (test(*it_simplex)) *out++ = *it_simplex; } } // When a simplex S has only one co-face C, we can remove S and C // without changing the topology void collapse(int max_simplex_dim, bool quiet = false) { #ifdef DEBUG_TRACES if (!quiet) std::cerr << "Collapsing... "; #endif // We note k = max_simplex_dim - 1 int k = max_simplex_dim - 1; typedef Complex::iterator Simplex_iterator; typedef std::vector Simplex_iterator_list; typedef std::map Cofaces_map; std::size_t num_collapsed_maximal_simplices = 0; do { num_collapsed_maximal_simplices = 0; // Create a map associating each non-maximal k-faces to the list of its // maximal cofaces Cofaces_map cofaces_map; for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { if (static_cast (it_simplex->size()) > k + 1) { std::vector k_faces; // Get the k-faces composing the simplex combinations(*it_simplex, k + 1, std::back_inserter(k_faces)); for (const auto &comb : k_faces) cofaces_map[comb].push_back(it_simplex); } } // For each non-maximal k-face F, if F has only one maximal coface Cf: // - Look for the other k-faces F2, F3... of Cf in the map and: // * if the list contains only Cf, clear the list (we don't remove the // list since it creates troubles with the iterators) and add the F2, // F3... to the complex // * otherwise, remove Cf from the associated list // - Remove Cf from the complex for (Cofaces_map::const_iterator it_map_elt = cofaces_map.begin(), it_map_end = cofaces_map.end(); it_map_elt != it_map_end; ++it_map_elt) { if (it_map_elt->second.size() == 1) { std::vector k_faces; const Simplex_iterator_list::value_type &it_Cf = *it_map_elt->second.begin(); GUDHI_CHECK(it_Cf->size() == max_simplex_dim + 1, std::logic_error("Wrong dimension")); // Get the k-faces composing the simplex combinations(*it_Cf, k + 1, std::back_inserter(k_faces)); for (const auto &f2 : k_faces) { // Skip F if (f2 != it_map_elt->first) { Cofaces_map::iterator it_comb_in_map = cofaces_map.find(f2); if (it_comb_in_map->second.size() == 1) { it_comb_in_map->second.clear(); m_complex.insert(f2); } else { // it_comb_in_map->second.size() > 1 Simplex_iterator_list::iterator it = std::find(it_comb_in_map->second.begin(), it_comb_in_map->second.end(), it_Cf); GUDHI_CHECK(it != it_comb_in_map->second.end(), std::logic_error("Error: it == it_comb_in_map->second.end()")); it_comb_in_map->second.erase(it); } } } m_complex.erase(it_Cf); ++num_collapsed_maximal_simplices; } } // Repeat until no maximal simplex got removed } while (num_collapsed_maximal_simplices > 0); // Collapse the lower dimension simplices if (k > 0) collapse(max_simplex_dim - 1, true); #ifdef DEBUG_TRACES if (!quiet) std::cerr << "done.\n"; #endif } void display_stats() const { std::cerr << yellow << "Complex stats:\n" << white; if (m_complex.empty()) { std::cerr << " * No simplices.\n"; } else { // Number of simplex for each dimension std::map simplex_stats; for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { ++simplex_stats[static_cast (it_simplex->size()) - 1]; } for (std::map::const_iterator it_map = simplex_stats.begin(); it_map != simplex_stats.end(); ++it_map) { std::cerr << " * " << it_map->first << "-simplices: " << it_map->second << "\n"; } } } // verbose_level = 0, 1 or 2 bool is_pure_pseudomanifold__do_not_check_if_stars_are_connected(int simplex_dim, bool allow_borders = false, bool exit_at_the_first_problem = false, int verbose_level = 0, std::size_t *p_num_wrong_dim_simplices = NULL, std::size_t *p_num_wrong_number_of_cofaces = NULL) const { typedef Simplex K_1_face; typedef std::map Cofaces_map; std::size_t num_wrong_dim_simplices = 0; std::size_t num_wrong_number_of_cofaces = 0; // Counts the number of cofaces of each K_1_face // Create a map associating each non-maximal k-faces to the list of its // maximal cofaces Cofaces_map cofaces_map; for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { if (static_cast (it_simplex->size()) != simplex_dim + 1) { if (verbose_level >= 2) std::cerr << "Found a simplex with dim = " << it_simplex->size() - 1 << "\n"; ++num_wrong_dim_simplices; } else { std::vector k_1_faces; // Get the facets composing the simplex combinations( *it_simplex, simplex_dim, std::back_inserter(k_1_faces)); for (const auto &k_1_face : k_1_faces) { ++cofaces_map[k_1_face]; } } } for (Cofaces_map::const_iterator it_map_elt = cofaces_map.begin(), it_map_end = cofaces_map.end(); it_map_elt != it_map_end; ++it_map_elt) { if (it_map_elt->second != 2 && (!allow_borders || it_map_elt->second != 1)) { if (verbose_level >= 2) std::cerr << "Found a k-1-face with " << it_map_elt->second << " cofaces\n"; if (exit_at_the_first_problem) return false; else ++num_wrong_number_of_cofaces; } } bool ret = num_wrong_dim_simplices == 0 && num_wrong_number_of_cofaces == 0; if (verbose_level >= 1) { std::cerr << "Pure pseudo-manifold: "; if (ret) { std::cerr << green << "YES" << white << "\n"; } else { std::cerr << red << "NO" << white << "\n" << " * Number of wrong dimension simplices: " << num_wrong_dim_simplices << "\n" << " * Number of wrong number of cofaces: " << num_wrong_number_of_cofaces << "\n"; } } if (p_num_wrong_dim_simplices) *p_num_wrong_dim_simplices = num_wrong_dim_simplices; if (p_num_wrong_number_of_cofaces) *p_num_wrong_number_of_cofaces = num_wrong_number_of_cofaces; return ret; } template std::size_t num_K_simplices() const { Simplex_set k_simplices; for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { if (it_simplex->size() == K + 1) { k_simplices.insert(*it_simplex); } else if (it_simplex->size() > K + 1) { // Get the k-faces composing the simplex combinations( *it_simplex, K + 1, std::inserter(k_simplices, k_simplices.begin())); } } return k_simplices.size(); } std::ptrdiff_t euler_characteristic(bool verbose = false) const { if (verbose) std::cerr << "\nComputing Euler characteristic of the complex...\n"; std::size_t num_vertices = num_K_simplices<0>(); std::size_t num_edges = num_K_simplices<1>(); std::size_t num_triangles = num_K_simplices<2>(); std::ptrdiff_t ec = (std::ptrdiff_t) num_vertices - (std::ptrdiff_t) num_edges + (std::ptrdiff_t) num_triangles; if (verbose) std::cerr << "Euler characteristic: V - E + F = " << num_vertices << " - " << num_edges << " + " << num_triangles << " = " << blue << ec << white << "\n"; return ec; } // TODO(CJ): ADD COMMENTS bool is_pure_pseudomanifold( int simplex_dim, std::size_t num_vertices, bool allow_borders = false, bool exit_at_the_first_problem = false, int verbose_level = 0, std::size_t *p_num_wrong_dim_simplices = NULL, std::size_t *p_num_wrong_number_of_cofaces = NULL, std::size_t *p_num_unconnected_stars = NULL, Simplex_set *p_wrong_dim_simplices = NULL, Simplex_set *p_wrong_number_of_cofaces_simplices = NULL, Simplex_set *p_unconnected_stars_simplices = NULL) const { // If simplex_dim == 1, we do not need to check if stars are connected if (simplex_dim == 1) { if (p_num_unconnected_stars) *p_num_unconnected_stars = 0; return is_pure_pseudomanifold__do_not_check_if_stars_are_connected(simplex_dim, allow_borders, exit_at_the_first_problem, verbose_level, p_num_wrong_dim_simplices, p_num_wrong_number_of_cofaces); } // Associates each vertex (= the index in the vector) // to its star (list of simplices) typedef std::vector > Stars; std::size_t num_wrong_dim_simplices = 0; std::size_t num_wrong_number_of_cofaces = 0; std::size_t num_unconnected_stars = 0; // Fills a Stars data structure Stars stars; stars.resize(num_vertices); for (Complex::const_iterator it_simplex = m_complex.begin(), it_simplex_end = m_complex.end(); it_simplex != it_simplex_end; ++it_simplex) { if (static_cast (it_simplex->size()) != simplex_dim + 1) { if (verbose_level >= 2) std::cerr << "Found a simplex with dim = " << it_simplex->size() - 1 << "\n"; ++num_wrong_dim_simplices; if (p_wrong_dim_simplices) p_wrong_dim_simplices->insert(*it_simplex); } else { for (Simplex::const_iterator it_point_idx = it_simplex->begin(); it_point_idx != it_simplex->end(); ++it_point_idx) { stars[*it_point_idx].push_back(it_simplex); } } } // Now, for each star, we have a vector of its d-simplices // i.e. one index for each d-simplex // Boost Graph only deals with indexes, so we also need indexes for the // (d-1)-simplices std::size_t center_vertex_index = 0; for (Stars::const_iterator it_star = stars.begin(); it_star != stars.end(); ++it_star, ++center_vertex_index) { typedef std::map > Dm1_faces_to_adj_D_faces; Dm1_faces_to_adj_D_faces dm1_faces_to_adj_d_faces; for (std::size_t i_dsimpl = 0; i_dsimpl < it_star->size(); ++i_dsimpl) { Simplex dm1_simpl_of_link = *((*it_star)[i_dsimpl]); dm1_simpl_of_link.erase(center_vertex_index); // Copy it to a vector so that we can use operator[] on it std::vector dm1_simpl_of_link_vec( dm1_simpl_of_link.begin(), dm1_simpl_of_link.end()); CGAL::Combination_enumerator dm2_simplices( simplex_dim - 1, 0, simplex_dim); for (; !dm2_simplices.finished(); ++dm2_simplices) { Simplex dm2_simpl; for (int j = 0; j < simplex_dim - 1; ++j) dm2_simpl.insert(dm1_simpl_of_link_vec[dm2_simplices[j]]); dm1_faces_to_adj_d_faces[dm2_simpl].push_back(i_dsimpl); } } Adj_graph adj_graph; std::vector d_faces_descriptors; d_faces_descriptors.resize(it_star->size()); for (std::size_t j = 0; j < it_star->size(); ++j) d_faces_descriptors[j] = boost::add_vertex(adj_graph); Dm1_faces_to_adj_D_faces::const_iterator dm1_to_d_it = dm1_faces_to_adj_d_faces.begin(); Dm1_faces_to_adj_D_faces::const_iterator dm1_to_d_it_end = dm1_faces_to_adj_d_faces.end(); for (std::size_t i_km1_face = 0; dm1_to_d_it != dm1_to_d_it_end; ++dm1_to_d_it, ++i_km1_face) { Graph_vertex km1_gv = boost::add_vertex(adj_graph); for (std::vector::const_iterator kface_it = dm1_to_d_it->second.begin(); kface_it != dm1_to_d_it->second.end(); ++kface_it) { boost::add_edge(km1_gv, *kface_it, adj_graph); } if (dm1_to_d_it->second.size() != 2 && (!allow_borders || dm1_to_d_it->second.size() != 1)) { ++num_wrong_number_of_cofaces; if (p_wrong_number_of_cofaces_simplices) { for (auto idx : dm1_to_d_it->second) p_wrong_number_of_cofaces_simplices->insert(*((*it_star)[idx])); } } } // What is left is to check the connexity bool is_connected = true; if (boost::num_vertices(adj_graph) > 0) { std::vector components(boost::num_vertices(adj_graph)); is_connected = (boost::connected_components(adj_graph, &components[0]) == 1); } if (!is_connected) { if (verbose_level >= 2) std::cerr << "Error: star #" << center_vertex_index << " is not connected\n"; ++num_unconnected_stars; if (p_unconnected_stars_simplices) { for (std::vector::const_iterator it_simpl = it_star->begin(), it_simpl_end = it_star->end(); it_simpl != it_simpl_end; ++it_simpl) { p_unconnected_stars_simplices->insert(**it_simpl); } } } } // Each one has been counted several times ("simplex_dim" times) num_wrong_number_of_cofaces /= simplex_dim; bool ret = num_wrong_dim_simplices == 0 && num_wrong_number_of_cofaces == 0 && num_unconnected_stars == 0; if (verbose_level >= 1) { std::cerr << "Pure pseudo-manifold: "; if (ret) { std::cerr << green << "YES" << white << "\n"; } else { std::cerr << red << "NO" << white << "\n" << " * Number of wrong dimension simplices: " << num_wrong_dim_simplices << "\n" << " * Number of wrong number of cofaces: " << num_wrong_number_of_cofaces << "\n" << " * Number of not-connected stars: " << num_unconnected_stars << "\n"; } } if (p_num_wrong_dim_simplices) *p_num_wrong_dim_simplices = num_wrong_dim_simplices; if (p_num_wrong_number_of_cofaces) *p_num_wrong_number_of_cofaces = num_wrong_number_of_cofaces; if (p_num_unconnected_stars) *p_num_unconnected_stars = num_unconnected_stars; return ret; } private: typedef Simplex_set Complex; // graph is an adjacency list typedef boost::adjacency_list Adj_graph; // map that gives to a certain simplex its node in graph and its dimension typedef boost::graph_traits::vertex_descriptor Graph_vertex; typedef boost::graph_traits::edge_descriptor Graph_edge; Complex m_complex; }; // class Simplicial_complex } // namespace internal } // namespace tangential_complex } // namespace Gudhi #endif // TANGENTIAL_COMPLEX_SIMPLICIAL_COMPLEX_H_