diff options
Diffstat (limited to 'src/Rips_complex')
-rw-r--r-- | src/Rips_complex/doc/Intro_rips_complex.h | 17 | ||||
-rw-r--r-- | src/Rips_complex/example/CMakeLists.txt | 21 | ||||
-rw-r--r-- | src/Rips_complex/example/example_one_skeleton_rips_from_points.cpp (renamed from src/Rips_complex/example/example_rips_complex_from_points.cpp) | 19 | ||||
-rw-r--r-- | src/Rips_complex/example/full_skeleton_rips_points_for_doc.txt (renamed from src/Rips_complex/example/rips_points_for_doc_12_3.txt) | 0 | ||||
-rw-r--r-- | src/Rips_complex/example/one_skeleton_rips_points_for_doc.txt (renamed from src/Rips_complex/example/rips_points_for_doc_12_1.txt) | 0 | ||||
-rw-r--r-- | src/Rips_complex/test/CMakeLists.txt | 24 | ||||
-rw-r--r-- | src/Rips_complex/test/README | 12 | ||||
-rw-r--r-- | src/Rips_complex/test/test_rips_complex.cpp | 231 |
8 files changed, 300 insertions, 24 deletions
diff --git a/src/Rips_complex/doc/Intro_rips_complex.h b/src/Rips_complex/doc/Intro_rips_complex.h index 85108168..bd9f5fad 100644 --- a/src/Rips_complex/doc/Intro_rips_complex.h +++ b/src/Rips_complex/doc/Intro_rips_complex.h @@ -29,11 +29,11 @@ namespace rips_complex { /** \defgroup rips_complex Rips complex * - * \author Clément Maria & Vincent Rouvreau + * \author Clément Maria and Vincent Rouvreau * * @{ * - * \section definition Definition + * \section ripsdefinition Definition * * Rips_complex * <a target="_blank" href="https://en.wikipedia.org/wiki/Vietoris%E2%80%93Rips_complex">(Wikipedia)</a> is a @@ -61,18 +61,19 @@ namespace rips_complex { * * Then, it is asked to display information about the simplicial complex. * - * \include Rips_complex/example_rips_complex_from_points.cpp + * \include Rips_complex/example_one_skeleton_rips_from_points.cpp * - * When launching: + * When launching (rips maximal distance between 2 points is 12.0, is expanded until dimension 1 - one skeleton graph + * with other words): * - * \code $> ./ripspoints + * \code $> ./oneskeletonripspoints 12.0 * \endcode * * the program output is: * - * \include Rips_complex/rips_points_for_doc_12_2.txt + * \include Rips_complex/one_skeleton_rips_points_for_doc.txt * - * \section offexample Example from OFF file + * \section ripsoffexample Example from OFF file * * This example builds the one skeleton graph from the given points in an OFF file, threshold value, and distance * function. @@ -90,7 +91,7 @@ namespace rips_complex { * * the program output is: * - * \include Rips_complex/rips_points_for_doc_12_3.txt + * \include Rips_complex/full_skeleton_rips_points_for_doc.txt * * \copyright GNU General Public License v3. * \verbatim Contact: gudhi-users@lists.gforge.inria.fr \endverbatim diff --git a/src/Rips_complex/example/CMakeLists.txt b/src/Rips_complex/example/CMakeLists.txt index f8dcf94d..3962b15e 100644 --- a/src/Rips_complex/example/CMakeLists.txt +++ b/src/Rips_complex/example/CMakeLists.txt @@ -3,12 +3,25 @@ project(Rips_complex_examples) add_executable ( ripsoffreader example_rips_complex_from_off_file.cpp ) target_link_libraries(ripsoffreader ${Boost_SYSTEM_LIBRARY}) + +add_executable ( oneskeletonripspoints example_one_skeleton_rips_from_points.cpp ) +target_link_libraries(oneskeletonripspoints ${Boost_SYSTEM_LIBRARY}) + if (TBB_FOUND) target_link_libraries(ripsoffreader ${TBB_LIBRARIES}) + target_link_libraries(oneskeletonripspoints ${TBB_LIBRARIES}) endif() -add_executable ( ripspoints example_rips_complex_from_points.cpp ) -target_link_libraries(ripspoints ${Boost_SYSTEM_LIBRARY}) -if (TBB_FOUND) - target_link_libraries(ripspoints ${TBB_LIBRARIES}) +add_test(oneskeletonripspoints ${CMAKE_CURRENT_BINARY_DIR}/oneskeletonripspoints 12.0) +# Do not forget to copy test files in current binary dir +file(COPY "${CMAKE_SOURCE_DIR}/data/points/alphacomplexdoc.off" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) +add_test(ripsoffreader_doc_12_1 ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader alphacomplexdoc.off 12.0 1 ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_1.txt) +add_test(ripsoffreader_doc_12_3 ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader alphacomplexdoc.off 12.0 3 ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_3.txt) +if (DIFF_PATH) + # Do not forget to copy test results files in current binary dir + file(COPY "one_skeleton_rips_points_for_doc.txt" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) + file(COPY "full_skeleton_rips_points_for_doc.txt" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) + + add_test(ripsoffreader_doc_12_1_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_1.txt ${CMAKE_CURRENT_BINARY_DIR}/one_skeleton_rips_points_for_doc.txt) + add_test(ripsoffreader_doc_12_3_diff_files ${DIFF_PATH} ${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_3.txt ${CMAKE_CURRENT_BINARY_DIR}/full_skeleton_rips_points_for_doc.txt) endif() diff --git a/src/Rips_complex/example/example_rips_complex_from_points.cpp b/src/Rips_complex/example/example_one_skeleton_rips_from_points.cpp index c241d137..db41b425 100644 --- a/src/Rips_complex/example/example_rips_complex_from_points.cpp +++ b/src/Rips_complex/example/example_one_skeleton_rips_from_points.cpp @@ -11,20 +11,15 @@ void usage(int nbArgs, char * const progName) { std::cerr << "Error: Number of arguments (" << nbArgs << ") is not correct\n"; - std::cerr << "Usage: " << progName << " dim_max [threshold]\n"; - std::cerr << " i.e.: " << progName << " 3 12.0\n"; + std::cerr << "Usage: " << progName << " threshold\n"; + std::cerr << " i.e.: " << progName << " 12.0\n"; exit(-1); // ----- >> } int main(int argc, char **argv) { - if ((argc != 2) && (argc != 3)) usage(argc, argv[0]); + if (argc != 2) usage(argc, argv[0]); - double threshold = std::numeric_limits<double>::infinity(); - int dim_max = atoi(argv[1]); - - if (argc == 3) { - threshold = atof(argv[2]); - } + double threshold = atof(argv[1]); // Type definitions using Point = std::vector<double>; @@ -41,14 +36,14 @@ int main(int argc, char **argv) { points.push_back({9.0, 17.0}); // ---------------------------------------------------------------------------- - // Init of a rips complex from an OFF file + // Init of a rips complex from points // ---------------------------------------------------------------------------- Rips_complex rips_complex_from_file(points, threshold, euclidean_distance<Point>); Simplex_tree simplex; - if (rips_complex_from_file.create_complex(simplex, dim_max)) { + if (rips_complex_from_file.create_complex(simplex, 1)) { // ---------------------------------------------------------------------------- - // Display information about the rips complex + // Display information about the one skeleton rips complex // ---------------------------------------------------------------------------- std::cout << "Rips complex is of dimension " << simplex.dimension() << " - " << simplex.num_simplices() << " simplices - " << diff --git a/src/Rips_complex/example/rips_points_for_doc_12_3.txt b/src/Rips_complex/example/full_skeleton_rips_points_for_doc.txt index 319931e0..319931e0 100644 --- a/src/Rips_complex/example/rips_points_for_doc_12_3.txt +++ b/src/Rips_complex/example/full_skeleton_rips_points_for_doc.txt diff --git a/src/Rips_complex/example/rips_points_for_doc_12_1.txt b/src/Rips_complex/example/one_skeleton_rips_points_for_doc.txt index b0e25cc5..b0e25cc5 100644 --- a/src/Rips_complex/example/rips_points_for_doc_12_1.txt +++ b/src/Rips_complex/example/one_skeleton_rips_points_for_doc.txt diff --git a/src/Rips_complex/test/CMakeLists.txt b/src/Rips_complex/test/CMakeLists.txt new file mode 100644 index 00000000..3972e9fc --- /dev/null +++ b/src/Rips_complex/test/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 2.6) +project(Rips_complex_tests) + +if (GCOVR_PATH) + # for gcovr to make coverage reports - Corbera Jenkins plugin + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") +endif() +if (GPROF_PATH) + # for gprof to make coverage reports - Jenkins + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") +endif() + +add_executable ( rips_complex_UT test_rips_complex.cpp ) +target_link_libraries(rips_complex_UT ${Boost_SYSTEM_LIBRARY} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY}) +if (TBB_FOUND) + target_link_libraries(rips_complex_UT ${TBB_LIBRARIES}) +endif() + +# Do not forget to copy test files in current binary dir +file(COPY "${CMAKE_SOURCE_DIR}/data/points/alphacomplexdoc.off" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) + +add_test(rips_complex_UT ${CMAKE_CURRENT_BINARY_DIR}/rips_complex_UT + # XML format for Jenkins xUnit plugin + --log_format=XML --log_sink=${CMAKE_SOURCE_DIR}/rips_complex_UT.xml --log_level=test_suite --report_level=no) diff --git a/src/Rips_complex/test/README b/src/Rips_complex/test/README new file mode 100644 index 00000000..28236b52 --- /dev/null +++ b/src/Rips_complex/test/README @@ -0,0 +1,12 @@ +To compile: +*********** + +cmake . +make + +To launch with details: +*********************** + +./rips_complex_UT --report_level=detailed --log_level=all + + ==> echo $? returns 0 in case of success (non-zero otherwise) diff --git a/src/Rips_complex/test/test_rips_complex.cpp b/src/Rips_complex/test/test_rips_complex.cpp new file mode 100644 index 00000000..5f129160 --- /dev/null +++ b/src/Rips_complex/test/test_rips_complex.cpp @@ -0,0 +1,231 @@ +/* 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(s): Vincent Rouvreau + * + * Copyright (C) 2016 INRIA Saclay (France) + * + * 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 <http://www.gnu.org/licenses/>. + */ + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE "rips_complex" +#include <boost/test/unit_test.hpp> + +#include <cmath> // float comparison +#include <limits> +#include <string> +#include <vector> +#include <algorithm> // std::max + +#include <gudhi/Rips_complex.h> +// to construct Rips_complex from a OFF file of points +#include <gudhi/Points_off_io.h> +// to construct a simplex_tree from rips complex +#include <gudhi/Simplex_tree.h> +#include <gudhi/distance_functions.h> + +// Type definitions +using Point = std::vector<double>; +using Simplex_tree = Gudhi::Simplex_tree<>; +using Rips_complex = Gudhi::rips_complex::Rips_complex<Simplex_tree::Filtration_value>; + +bool are_almost_the_same(float a, float b) { + return std::fabs(a - b) < std::numeric_limits<float>::epsilon(); +} + +BOOST_AUTO_TEST_CASE(RIPS_DOC_OFF_file) { + // ---------------------------------------------------------------------------- + // + // Init of a rips complex from a OFF file + // + // ---------------------------------------------------------------------------- + std::string off_file_name("alphacomplexdoc.off"); + double rips_threshold = 12.0; + std::cout << "========== OFF FILE NAME = " << off_file_name << " - rips threshold=" << + rips_threshold << "==========" << std::endl; + + Gudhi::Points_off_reader<Point> off_reader(off_file_name); + Rips_complex rips_complex_from_file(off_reader.get_point_cloud(), rips_threshold, euclidean_distance<Point>); + + const int DIMENSION_1 = 1; + Simplex_tree st; + BOOST_CHECK(rips_complex_from_file.create_complex(st, DIMENSION_1)); + std::cout << "st.dimension()=" << st.dimension() << std::endl; + BOOST_CHECK(st.dimension() == DIMENSION_1); + + const int NUMBER_OF_VERTICES = 7; + std::cout << "st.num_vertices()=" << st.num_vertices() << std::endl; + BOOST_CHECK(st.num_vertices() == NUMBER_OF_VERTICES); + + std::cout << "st.num_simplices()=" << st.num_simplices() << std::endl; + BOOST_CHECK(st.num_simplices() == 18); + + // Check filtration values of vertices is 0.0 + for (auto f_simplex : st.skeleton_simplex_range(0)) { + BOOST_CHECK(st.filtration(f_simplex) == 0.0); + } + + // Check filtration values of edges + for (auto f_simplex : st.skeleton_simplex_range(DIMENSION_1)) { + if (DIMENSION_1 == st.dimension(f_simplex)) { + std::vector<Point> vp; + std::cout << "vertex = ("; + for (auto vertex : st.simplex_vertex_range(f_simplex)) { + std::cout << vertex << ","; + vp.push_back(off_reader.get_point_cloud().at(vertex)); + } + std::cout << ") - distance =" << euclidean_distance(vp.at(0), vp.at(1)) << + " - filtration =" << st.filtration(f_simplex) << std::endl; + BOOST_CHECK(vp.size() == 2); + BOOST_CHECK(are_almost_the_same(st.filtration(f_simplex), euclidean_distance(vp.at(0), vp.at(1)))); + } + } + + const int DIMENSION_2 = 2; + Simplex_tree st2; + BOOST_CHECK(rips_complex_from_file.create_complex(st2, DIMENSION_2)); + std::cout << "st2.dimension()=" << st2.dimension() << std::endl; + BOOST_CHECK(st2.dimension() == DIMENSION_2); + + std::cout << "st2.num_vertices()=" << st2.num_vertices() << std::endl; + BOOST_CHECK(st2.num_vertices() == NUMBER_OF_VERTICES); + + std::cout << "st2.num_simplices()=" << st2.num_simplices() << std::endl; + BOOST_CHECK(st2.num_simplices() == 23); + + Simplex_tree::Filtration_value f01 = st2.filtration(st2.find({0, 1})); + Simplex_tree::Filtration_value f02 = st2.filtration(st2.find({0, 2})); + Simplex_tree::Filtration_value f12 = st2.filtration(st2.find({1, 2})); + Simplex_tree::Filtration_value f012 = st2.filtration(st2.find({0, 1, 2})); + std::cout << "f012= " << f012 << " | f01= " << f01 << " - f02= " << f02 << " - f12= " << f12 << std::endl; + BOOST_CHECK(are_almost_the_same(f012, std::max(f01, std::max(f02,f12)))); + + Simplex_tree::Filtration_value f45 = st2.filtration(st2.find({4, 5})); + Simplex_tree::Filtration_value f56 = st2.filtration(st2.find({5, 6})); + Simplex_tree::Filtration_value f46 = st2.filtration(st2.find({4, 6})); + Simplex_tree::Filtration_value f456 = st2.filtration(st2.find({4, 5, 6})); + std::cout << "f456= " << f456 << " | f45= " << f45 << " - f56= " << f56 << " - f46= " << f46 << std::endl; + BOOST_CHECK(are_almost_the_same(f456, std::max(f45, std::max(f56,f46)))); + + const int DIMENSION_3 = 3; + Simplex_tree st3; + BOOST_CHECK(rips_complex_from_file.create_complex(st3, DIMENSION_3)); + std::cout << "st3.dimension()=" << st3.dimension() << std::endl; + BOOST_CHECK(st3.dimension() == DIMENSION_3); + + std::cout << "st3.num_vertices()=" << st3.num_vertices() << std::endl; + BOOST_CHECK(st3.num_vertices() == NUMBER_OF_VERTICES); + + std::cout << "st3.num_simplices()=" << st3.num_simplices() << std::endl; + BOOST_CHECK(st3.num_simplices() == 24); + + Simplex_tree::Filtration_value f123 = st3.filtration(st3.find({1, 2, 3})); + Simplex_tree::Filtration_value f013 = st3.filtration(st3.find({0, 1, 3})); + Simplex_tree::Filtration_value f023 = st3.filtration(st3.find({0, 2, 3})); + Simplex_tree::Filtration_value f0123 = st3.filtration(st3.find({0, 1, 2, 3})); + std::cout << "f0123= " << f0123 << " | f012= " << f012 << " - f123= " << f123 << " - f013= " << f013 << + " - f023= " << f023 << std::endl; + BOOST_CHECK(are_almost_the_same(f0123, std::max(f012, std::max(f123, std::max(f013, f023))))); + +} + +using Vector_of_points = std::vector<Point>; + +bool is_point_in_list(Vector_of_points points_list, Point point) { + for (auto& point_in_list : points_list) { + if (point_in_list == point) { + return true; // point found + } + } + return false; // point not found +} + +/* Compute the square value of Euclidean distance between two Points given by a range of coordinates. + * The points are assumed to have the same dimension. */ +template< typename Point > +double custom_square_euclidean_distance(const Point &p1,const Point &p2) { + double dist = 0.; + auto it1 = p1.begin(); + auto it2 = p2.begin(); + for (; it1 != p1.end(); ++it1, ++it2) { + double tmp = *it1 - *it2; + dist += tmp*tmp; + } + return dist; +} + +BOOST_AUTO_TEST_CASE(Rips_complex_from_points) { + // ---------------------------------------------------------------------------- + // Init of a list of points + // ---------------------------------------------------------------------------- + Vector_of_points points; + std::vector<double> coords = { 0.0, 0.0, 0.0, 1.0 }; + points.push_back(Point(coords.begin(), coords.end())); + coords = { 0.0, 0.0, 1.0, 0.0 }; + points.push_back(Point(coords.begin(), coords.end())); + coords = { 0.0, 1.0, 0.0, 0.0 }; + points.push_back(Point(coords.begin(), coords.end())); + coords = { 1.0, 0.0, 0.0, 0.0 }; + points.push_back(Point(coords.begin(), coords.end())); + + // ---------------------------------------------------------------------------- + // Init of a rips complex from the list of points + // ---------------------------------------------------------------------------- + Rips_complex rips_complex_from_points(points, 2.0, custom_square_euclidean_distance<Point>); + + std::cout << "========== Rips_complex_from_points ==========" << std::endl; + Simplex_tree st; + const int DIMENSION = 3; + BOOST_CHECK(rips_complex_from_points.create_complex(st, DIMENSION)); + + // Another way to check num_simplices + std::cout << "Iterator on rips complex simplices in the filtration order, with [filtration value]:" << std::endl; + int num_simplices = 0; + for (auto f_simplex : st.filtration_simplex_range()) { + num_simplices++; + std::cout << " ( "; + for (auto vertex : st.simplex_vertex_range(f_simplex)) { + std::cout << vertex << " "; + } + std::cout << ") -> " << "[" << st.filtration(f_simplex) << "] "; + std::cout << std::endl; + } + BOOST_CHECK(num_simplices == 15); + std::cout << "st.num_simplices()=" << st.num_simplices() << std::endl; + BOOST_CHECK(st.num_simplices() == 15); + + std::cout << "st.dimension()=" << st.dimension() << std::endl; + BOOST_CHECK(st.dimension() == DIMENSION); + std::cout << "st.num_vertices()=" << st.num_vertices() << std::endl; + BOOST_CHECK(st.num_vertices() == 4); + + for (auto f_simplex : st.filtration_simplex_range()) { + std::cout << "dimension(" << st.dimension(f_simplex) << ") - f = " << st.filtration(f_simplex) << std::endl; + switch (st.dimension(f_simplex)) { + case 0: + BOOST_CHECK(are_almost_the_same(st.filtration(f_simplex), 0.0)); + break; + case 1: + case 2: + case 3: + BOOST_CHECK(are_almost_the_same(st.filtration(f_simplex), 2.0)); + break; + default: + BOOST_CHECK(false); // Shall not happen + break; + } + } +} |