diff options
30 files changed, 256 insertions, 177 deletions
diff --git a/.github/next_release.md b/.github/next_release.md index 190f8408..7e7fe03b 100644 --- a/.github/next_release.md +++ b/.github/next_release.md @@ -12,6 +12,9 @@ Below is a list of changes made since GUDHI 3.3.0: - [Module](link) - ... +- [Subsampling](https://gudhi.inria.fr/doc/latest/group__subsampling.html) + - The C++ function `choose_n_farthest_points()` now takes a distance function instead of a kernel as first argument, users can replace `k` with `k.squared_distance_d_object()` in each call in their code. + - Miscellaneous - The [list of bugs that were solved since GUDHI-3.3.0](https://github.com/GUDHI/gudhi-devel/issues?q=label%3A3.4.0+is%3Aclosed) is available on GitHub. diff --git a/.github/workflows/pip-build-linux.yml b/.github/workflows/pip-build-linux.yml index 14fdc8d2..cf8ddadf 100644 --- a/.github/workflows/pip-build-linux.yml +++ b/.github/workflows/pip-build-linux.yml @@ -11,11 +11,11 @@ jobs: - uses: actions/checkout@v1 with: submodules: true - - name: Build wheels for Python 3.8 + - name: Build wheels for Python 3.9 run: | - mkdir build_38 - cd build_38 - cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$PYTHON38/bin/python .. + mkdir build_39 + cd build_39 + cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$PYTHON39/bin/python .. cd src/python - $PYTHON38/bin/python setup.py bdist_wheel + $PYTHON39/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl
\ No newline at end of file diff --git a/.github/workflows/pip-build-osx.yml b/.github/workflows/pip-build-osx.yml index 15b8880a..50b8b09c 100644 --- a/.github/workflows/pip-build-osx.yml +++ b/.github/workflows/pip-build-osx.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.8'] + python-version: ['3.9'] name: Build wheels for Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/pip-build-windows.yml b/.github/workflows/pip-build-windows.yml index 995d52dd..aacbbc52 100644 --- a/.github/workflows/pip-build-windows.yml +++ b/.github/workflows/pip-build-windows.yml @@ -8,7 +8,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.8'] + python-version: ['3.9'] name: Build wheels for Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/pip-packaging-linux.yml b/.github/workflows/pip-packaging-linux.yml index bd524af9..469c3b3b 100644 --- a/.github/workflows/pip-packaging-linux.yml +++ b/.github/workflows/pip-packaging-linux.yml @@ -45,12 +45,21 @@ jobs: cd src/python $PYTHON38/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl + - name: Build wheels for Python 3.9 + run: | + mkdir build_39 + cd build_39 + cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$PYTHON39/bin/python .. + cd src/python + $PYTHON39/bin/python setup.py bdist_wheel + auditwheel repair dist/*.whl - name: Publish on PyPi env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - $PYTHON38/bin/python -m twine upload build_35/src/python/wheelhouse/* - $PYTHON38/bin/python -m twine upload build_36/src/python/wheelhouse/* - $PYTHON38/bin/python -m twine upload build_37/src/python/wheelhouse/* - $PYTHON38/bin/python -m twine upload build_38/src/python/wheelhouse/*
\ No newline at end of file + $PYTHON39/bin/python -m twine upload build_35/src/python/wheelhouse/* + $PYTHON39/bin/python -m twine upload build_36/src/python/wheelhouse/* + $PYTHON39/bin/python -m twine upload build_37/src/python/wheelhouse/* + $PYTHON39/bin/python -m twine upload build_38/src/python/wheelhouse/* + $PYTHON39/bin/python -m twine upload build_39/src/python/wheelhouse/*
\ No newline at end of file diff --git a/.github/workflows/pip-packaging-osx.yml b/.github/workflows/pip-packaging-osx.yml index c94369ac..46441e65 100644 --- a/.github/workflows/pip-packaging-osx.yml +++ b/.github/workflows/pip-packaging-osx.yml @@ -10,7 +10,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.5', '3.6', '3.7', '3.8'] + python-version: ['3.5', '3.6', '3.7', '3.8', '3.9'] name: Build wheels for Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v1 diff --git a/.github/workflows/pip-packaging-windows.yml b/.github/workflows/pip-packaging-windows.yml index 8f4ab6e7..3a751486 100644 --- a/.github/workflows/pip-packaging-windows.yml +++ b/.github/workflows/pip-packaging-windows.yml @@ -10,7 +10,7 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.5', '3.6', '3.7', '3.8'] + python-version: ['3.5', '3.6', '3.7', '3.8', '3.9'] name: Build wheels for Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v1 diff --git a/Dockerfile_for_pip b/Dockerfile_for_pip index 98668a04..d5ae6417 100644 --- a/Dockerfile_for_pip +++ b/Dockerfile_for_pip @@ -39,12 +39,14 @@ RUN /opt/python/cp35-cp35m/bin/pip install -r build-requirements.txt \ && /opt/python/cp36-cp36m/bin/pip install -r build-requirements.txt\ && /opt/python/cp37-cp37m/bin/pip install -r build-requirements.txt\ && /opt/python/cp38-cp38/bin/pip install -r build-requirements.txt\ - && /opt/python/cp38-cp38/bin/pip install twine + && /opt/python/cp39-cp39/bin/pip install -r build-requirements.txt\ + && /opt/python/cp39-cp39/bin/pip install twine ENV PYTHON35="/opt/python/cp35-cp35m/" ENV PYTHON36="/opt/python/cp36-cp36m/" ENV PYTHON37="/opt/python/cp37-cp37m/" ENV PYTHON38="/opt/python/cp38-cp38/" +ENV PYTHON39="/opt/python/cp39-cp39/" ENV PATH="/opt/cmake/bin:${PATH}" ENV PATH="/opt/rh/devtoolset-8/root/usr/bin:${PATH}" diff --git a/src/Rips_complex/include/gudhi/Sparse_rips_complex.h b/src/Rips_complex/include/gudhi/Sparse_rips_complex.h index 1b250818..a5501004 100644 --- a/src/Rips_complex/include/gudhi/Sparse_rips_complex.h +++ b/src/Rips_complex/include/gudhi/Sparse_rips_complex.h @@ -67,8 +67,7 @@ class Sparse_rips_complex { : epsilon_(epsilon) { GUDHI_CHECK(epsilon > 0, "epsilon must be positive"); auto dist_fun = [&](Vertex_handle i, Vertex_handle j) { return distance(points[i], points[j]); }; - Ker<decltype(dist_fun)> kernel(dist_fun); - subsampling::choose_n_farthest_points(kernel, boost::irange<Vertex_handle>(0, boost::size(points)), -1, -1, + subsampling::choose_n_farthest_points(dist_fun, boost::irange<Vertex_handle>(0, boost::size(points)), -1, -1, std::back_inserter(sorted_points), std::back_inserter(params)); compute_sparse_graph(dist_fun, epsilon, mini, maxi); } @@ -128,17 +127,6 @@ class Sparse_rips_complex { } private: - // choose_n_farthest_points wants the distance function in this form... - template <class Distance> - struct Ker { - typedef std::size_t Point_d; // index into point range - Ker(Distance& d) : dist(d) {} - // Despite the name, this is not squared... - typedef Distance Squared_distance_d; - Squared_distance_d& squared_distance_d_object() const { return dist; } - Distance& dist; - }; - // PointRange must be random access. template <typename Distance> void compute_sparse_graph(Distance& dist, double epsilon, Filtration_value mini, Filtration_value maxi) { diff --git a/src/Spatial_searching/include/gudhi/Kd_tree_search.h b/src/Spatial_searching/include/gudhi/Kd_tree_search.h index 87969dd9..a50a8537 100644 --- a/src/Spatial_searching/include/gudhi/Kd_tree_search.h +++ b/src/Spatial_searching/include/gudhi/Kd_tree_search.h @@ -12,11 +12,12 @@ #ifndef KD_TREE_SEARCH_H_ #define KD_TREE_SEARCH_H_ +#include <gudhi/Debug_utils.h> + #include <CGAL/Orthogonal_k_neighbor_search.h> #include <CGAL/Orthogonal_incremental_neighbor_search.h> #include <CGAL/Search_traits.h> #include <CGAL/Search_traits_adapter.h> -#include <CGAL/Fuzzy_sphere.h> #include <CGAL/property_map.h> #include <CGAL/version.h> // for CGAL_VERSION_NR @@ -40,7 +41,6 @@ namespace Gudhi { namespace spatial_searching { - /** * \class Kd_tree_search Kd_tree_search.h gudhi/Kd_tree_search.h * \brief Spatial tree data structure to perform (approximate) nearest and furthest neighbor search. @@ -83,7 +83,8 @@ class Kd_tree_search { typedef CGAL::Search_traits< FT, Point, typename Traits::Cartesian_const_iterator_d, - typename Traits::Construct_cartesian_const_iterator_d> Traits_base; + typename Traits::Construct_cartesian_const_iterator_d, + typename Traits::Dimension> Traits_base; typedef CGAL::Search_traits_adapter< std::ptrdiff_t, @@ -110,7 +111,76 @@ class Kd_tree_search { /// of a point P and `second` is the squared distance between P and the query point. typedef Incremental_neighbor_search INS_range; - typedef CGAL::Fuzzy_sphere<STraits> Fuzzy_sphere; + // Because CGAL::Fuzzy_sphere takes the radius and not its square + struct Sphere_for_kdtree_search + { + typedef typename Traits::Point_d Point_d; + typedef typename Traits::FT FT; + typedef typename Traits::Dimension D; + typedef D Dimension; + + private: + STraits traits; + Point_d c; + FT sqradmin, sqradmax; + bool use_max; + + public: + // `prefer_max` means that we prefer outputting more points at squared distance between r2min and r2max, + // while `!prefer_max` means we prefer fewer. + Sphere_for_kdtree_search(Point_d const& c_, FT const& r2min, FT const& r2max, bool prefer_max=true, STraits const& traits_ = {}) + : traits(traits_), c(c_), sqradmin(r2min), sqradmax(r2max), use_max(prefer_max) + { GUDHI_CHECK(r2min >= 0 && r2max >= r2min, "0 <= r2min <= r2max"); } + + bool contains(std::ptrdiff_t i) const { + const Point_d& p = get(traits.point_property_map(), i); + auto ccci = traits.construct_cartesian_const_iterator_d_object(); + return contains_point_given_as_coordinates(ccci(p), ccci(p, 0)); + } + + template <typename Coord_iterator> + bool contains_point_given_as_coordinates(Coord_iterator pi, Coord_iterator CGAL_UNUSED) const { + FT distance = 0; + auto ccci = traits.construct_cartesian_const_iterator_d_object(); + auto ci = ccci(c); + auto ce = ccci(c, 0); + FT const& limit = use_max ? sqradmax : sqradmin; + while (ci != ce) { + distance += CGAL::square(*pi++ - *ci++); + // I think Clément advised to check the distance at every step instead of + // just at the end, especially when the dimension becomes large. Distance + // isn't part of the concept anyway. + if (distance > limit) return false; + } + return true; + } + + bool inner_range_intersects(CGAL::Kd_tree_rectangle<FT, D> const& rect) const { + auto ccci = traits.construct_cartesian_const_iterator_d_object(); + FT distance = 0; + auto ci = ccci(c); + auto ce = ccci(c, 0); + for (int i = 0; ci != ce; ++i, ++ci) { + distance += CGAL::square(CGAL::max<FT>(CGAL::max<FT>(*ci - rect.max_coord(i), rect.min_coord(i) - *ci), 0 )); + if (distance > sqradmin) return false; + } + return true; + } + + + bool outer_range_contains(CGAL::Kd_tree_rectangle<FT, D> const& rect) const { + auto ccci = traits.construct_cartesian_const_iterator_d_object(); + FT distance = 0; + auto ci = ccci(c); + auto ce = ccci(c, 0); + for (int i = 0; ci != ce; ++i, ++ci) { + distance += CGAL::square(CGAL::max<FT>(*ci - rect.min_coord(i), rect.max_coord(i) - *ci)); + if (distance > sqradmax) return false; + } + return true; + } + }; + /// \brief Constructor /// @param[in] points Const reference to the point range. This range /// is not copied, so it should not be destroyed or modified afterwards. @@ -266,10 +336,26 @@ class Kd_tree_search { /// @param[in] eps Approximation factor. template <typename OutputIterator> void all_near_neighbors(Point const& p, - FT radius, + FT const& radius, OutputIterator it, FT eps = FT(0)) const { - m_tree.search(it, Fuzzy_sphere(p, radius, eps, m_tree.traits())); + all_near_neighbors2(p, CGAL::square(radius - eps), CGAL::square(radius + eps), it); + } + + /// \brief Search for all the neighbors in a ball. This is similar to `all_near_neighbors` but takes directly + /// the square of the minimum distance below which points must be considered neighbors and square of the + /// maximum distance above which they cannot be. + /// @param[in] p The query point. + /// @param[in] sq_radius_min The square of the minimum search radius + /// @param[in] sq_radius_max The square of the maximum search radius + /// @param[out] it The points that lie inside the sphere of center `p` and squared radius `sq_radius`. + /// Note: `it` is used this way: `*it++ = each_point`. + template <typename OutputIterator> + void all_near_neighbors2(Point const& p, + FT const& sq_radius_min, + FT const& sq_radius_max, + OutputIterator it) const { + m_tree.search(it, Sphere_for_kdtree_search(p, sq_radius_min, sq_radius_max, true, m_tree.traits())); } int tree_depth() const { diff --git a/src/Subsampling/example/CMakeLists.txt b/src/Subsampling/example/CMakeLists.txt index dfac055c..f4a23d22 100644 --- a/src/Subsampling/example/CMakeLists.txt +++ b/src/Subsampling/example/CMakeLists.txt @@ -3,7 +3,6 @@ project(Subsampling_examples) if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) add_executable(Subsampling_example_pick_n_random_points example_pick_n_random_points.cpp) add_executable(Subsampling_example_choose_n_farthest_points example_choose_n_farthest_points.cpp) - add_executable(Subsampling_example_custom_kernel example_custom_kernel.cpp) add_executable(Subsampling_example_sparsify_point_set example_sparsify_point_set.cpp) target_link_libraries(Subsampling_example_sparsify_point_set ${CGAL_LIBRARY}) @@ -13,5 +12,6 @@ if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) COMMAND $<TARGET_FILE:Subsampling_example_choose_n_farthest_points>) add_test(NAME Subsampling_example_sparsify_point_set COMMAND $<TARGET_FILE:Subsampling_example_sparsify_point_set>) - endif(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) + +add_executable(Subsampling_example_custom_distance example_custom_distance.cpp) diff --git a/src/Subsampling/example/example_choose_n_farthest_points.cpp b/src/Subsampling/example/example_choose_n_farthest_points.cpp index 27cf5d4e..e8b3ce2d 100644 --- a/src/Subsampling/example/example_choose_n_farthest_points.cpp +++ b/src/Subsampling/example/example_choose_n_farthest_points.cpp @@ -20,7 +20,7 @@ int main(void) { K k; std::vector<Point_d> results; - Gudhi::subsampling::choose_n_farthest_points(k, points, 100, + Gudhi::subsampling::choose_n_farthest_points(k.squared_distance_d_object(), points, 100, Gudhi::subsampling::random_starting_point, std::back_inserter(results)); std::clog << "Before sparsification: " << points.size() << " points.\n"; diff --git a/src/Subsampling/example/example_custom_distance.cpp b/src/Subsampling/example/example_custom_distance.cpp new file mode 100644 index 00000000..3325b12d --- /dev/null +++ b/src/Subsampling/example/example_custom_distance.cpp @@ -0,0 +1,44 @@ +#include <gudhi/choose_n_farthest_points.h> + +#include <iostream> +#include <vector> +#include <iterator> + + +typedef unsigned Point; + +/* The class Distance contains a distance function defined on the set of points {0, 1, 2, 3} + * and computes a distance according to the matrix: + * 0 1 2 4 + * 1 0 4 2 + * 2 4 0 1 + * 4 2 1 0 + */ +class Distance { + private: + std::vector<std::vector<double>> matrix_; + + public: + Distance() { + matrix_.push_back({0, 1, 2, 4}); + matrix_.push_back({1, 0, 4, 2}); + matrix_.push_back({2, 4, 0, 1}); + matrix_.push_back({4, 2, 1, 0}); + } + + double operator()(Point p1, Point p2) const { + return matrix_[p1][p2]; + } +}; + +int main(void) { + std::vector<Point> points = {0, 1, 2, 3}; + std::vector<Point> results; + + Gudhi::subsampling::choose_n_farthest_points(Distance(), points, 2, + Gudhi::subsampling::random_starting_point, + std::back_inserter(results)); + std::clog << "Before sparsification: " << points.size() << " points.\n"; + std::clog << "After sparsification: " << results.size() << " points.\n"; + std::clog << "Result table: {" << results[0] << "," << results[1] << "}\n"; +} diff --git a/src/Subsampling/example/example_custom_kernel.cpp b/src/Subsampling/example/example_custom_kernel.cpp deleted file mode 100644 index 535bf42a..00000000 --- a/src/Subsampling/example/example_custom_kernel.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include <gudhi/choose_n_farthest_points.h> - -#include <iostream> -#include <vector> -#include <iterator> - - -/* The class Kernel contains a distance function defined on the set of points {0, 1, 2, 3} - * and computes a distance according to the matrix: - * 0 1 2 4 - * 1 0 4 2 - * 2 4 0 1 - * 4 2 1 0 - */ -class Kernel { - public: - typedef double FT; - typedef unsigned Point_d; - - // Class Squared_distance_d - class Squared_distance_d { - private: - std::vector<std::vector<FT>> matrix_; - - public: - Squared_distance_d() { - matrix_.push_back(std::vector<FT>({0, 1, 2, 4})); - matrix_.push_back(std::vector<FT>({1, 0, 4, 2})); - matrix_.push_back(std::vector<FT>({2, 4, 0, 1})); - matrix_.push_back(std::vector<FT>({4, 2, 1, 0})); - } - - FT operator()(Point_d p1, Point_d p2) { - return matrix_[p1][p2]; - } - }; - - // Constructor - Kernel() {} - - // Object of type Squared_distance_d - Squared_distance_d squared_distance_d_object() const { - return Squared_distance_d(); - } -}; - -int main(void) { - typedef Kernel K; - typedef typename K::Point_d Point_d; - - K k; - std::vector<Point_d> points = {0, 1, 2, 3}; - std::vector<Point_d> results; - - Gudhi::subsampling::choose_n_farthest_points(k, points, 2, - Gudhi::subsampling::random_starting_point, - std::back_inserter(results)); - std::clog << "Before sparsification: " << points.size() << " points.\n"; - std::clog << "After sparsification: " << results.size() << " points.\n"; - std::clog << "Result table: {" << results[0] << "," << results[1] << "}\n"; - - return 0; -} diff --git a/src/Subsampling/include/gudhi/choose_n_farthest_points.h b/src/Subsampling/include/gudhi/choose_n_farthest_points.h index b70af8a0..e6347d96 100644 --- a/src/Subsampling/include/gudhi/choose_n_farthest_points.h +++ b/src/Subsampling/include/gudhi/choose_n_farthest_points.h @@ -38,33 +38,35 @@ enum : std::size_t { * \ingroup subsampling * \brief Subsample by a greedy strategy of iteratively adding the farthest point from the * current chosen point set to the subsampling. - * The iteration starts with the landmark `starting point` or, if `starting point==random_starting_point`, with a random landmark. - * \tparam Kernel must provide a type Kernel::Squared_distance_d which is a model of the - * concept <a target="_blank" - * href="http://doc.cgal.org/latest/Kernel_d/classKernel__d_1_1Squared__distance__d.html">Kernel_d::Squared_distance_d</a> (despite the name, taken from CGAL, this can be any kind of metric or proximity measure). - * It must also contain a public member `squared_distance_d_object()` that returns an object of this type. - * \tparam Point_range Range whose value type is Kernel::Point_d. It must provide random-access - * via `operator[]` and the points should be stored contiguously in memory. - * \tparam PointOutputIterator Output iterator whose value type is Kernel::Point_d. - * \tparam DistanceOutputIterator Output iterator for distances. - * \details It chooses `final_size` points from a random access range + * \details + * The iteration starts with the landmark `starting point` or, if `starting point==random_starting_point`, + * with a random landmark. + * It chooses `final_size` points from a random access range * `input_pts` (or the number of distinct points if `final_size` is larger) * and outputs them in the output iterator `output_it`. It also * outputs the distance from each of those points to the set of previous * points in `dist_it`. - * @param[in] k A kernel object. - * @param[in] input_pts Const reference to the input points. + * \tparam Distance must provide an operator() that takes 2 points (value type of the range) + * and returns their distance (or some more general proximity measure) as a `double`. + * \tparam Point_range Random access range of points. + * \tparam PointOutputIterator Output iterator whose value type is the point type. + * \tparam DistanceOutputIterator Output iterator for distances. + * @param[in] dist A distance function. + * @param[in] input_pts The input points. * @param[in] final_size The size of the subsample to compute. * @param[in] starting_point The seed in the farthest point algorithm. * @param[out] output_it The output iterator for points. * @param[out] dist_it The optional output iterator for distances. + * + * \warning Older versions of this function took a CGAL kernel as argument. Users need to replace `k` with + * `k.squared_distance_d_object()` in the first argument of every call to `choose_n_farthest_points`. * */ -template < typename Kernel, +template < typename Distance, typename Point_range, typename PointOutputIterator, typename DistanceOutputIterator = Null_output_iterator> -void choose_n_farthest_points(Kernel const &k, +void choose_n_farthest_points(Distance dist, Point_range const &input_pts, std::size_t final_size, std::size_t starting_point, @@ -86,9 +88,9 @@ void choose_n_farthest_points(Kernel const &k, starting_point = dis(gen); } - typename Kernel::Squared_distance_d sqdist = k.squared_distance_d_object(); - std::size_t current_number_of_landmarks = 0; // counter for landmarks + static_assert(std::numeric_limits<double>::has_infinity, "the number type needs to support infinity()"); + // FIXME: don't hard-code the type as double. For Epeck_d, we also want to handle types that do not have an infinity. const double infty = std::numeric_limits<double>::infinity(); // infinity (see next entry) std::vector< double > dist_to_L(nb_points, infty); // vector of current distances to L from input_pts @@ -100,7 +102,7 @@ void choose_n_farthest_points(Kernel const &k, *dist_it++ = dist_to_L[curr_max_w]; std::size_t i = 0; for (auto&& p : input_pts) { - double curr_dist = sqdist(p, input_pts[curr_max_w]); + double curr_dist = dist(p, input_pts[curr_max_w]); if (curr_dist < dist_to_L[i]) dist_to_L[i] = curr_dist; ++i; diff --git a/src/Subsampling/include/gudhi/pick_n_random_points.h b/src/Subsampling/include/gudhi/pick_n_random_points.h index a67b2b84..e4246c29 100644 --- a/src/Subsampling/include/gudhi/pick_n_random_points.h +++ b/src/Subsampling/include/gudhi/pick_n_random_points.h @@ -11,7 +11,9 @@ #ifndef PICK_N_RANDOM_POINTS_H_ #define PICK_N_RANDOM_POINTS_H_ -#include <gudhi/Clock.h> +#ifdef GUDHI_SUBSAMPLING_PROFILING +# include <gudhi/Clock.h> +#endif #include <boost/range/size.hpp> @@ -44,6 +46,12 @@ void pick_n_random_points(Point_container const &points, Gudhi::Clock t; #endif + std::random_device rd; + std::mt19937 g(rd()); + +#if __cplusplus >= 201703L + std::sample(std::begin(points), std::end(points), output_it, final_size, g); +#else std::size_t nbP = boost::size(points); if (final_size > nbP) final_size = nbP; @@ -51,14 +59,12 @@ void pick_n_random_points(Point_container const &points, std::vector<int> landmarks(nbP); std::iota(landmarks.begin(), landmarks.end(), 0); - std::random_device rd; - std::mt19937 g(rd()); - std::shuffle(landmarks.begin(), landmarks.end(), g); landmarks.resize(final_size); for (int l : landmarks) *output_it++ = points[l]; +#endif #ifdef GUDHI_SUBSAMPLING_PROFILING t.end(); diff --git a/src/Subsampling/include/gudhi/sparsify_point_set.h b/src/Subsampling/include/gudhi/sparsify_point_set.h index b30cec80..4571b8f3 100644 --- a/src/Subsampling/include/gudhi/sparsify_point_set.h +++ b/src/Subsampling/include/gudhi/sparsify_point_set.h @@ -11,6 +11,13 @@ #ifndef SPARSIFY_POINT_SET_H_ #define SPARSIFY_POINT_SET_H_ +#include <boost/version.hpp> +#if BOOST_VERSION < 106600 +# include <boost/function_output_iterator.hpp> +#else +# include <boost/iterator/function_output_iterator.hpp> +#endif + #include <gudhi/Kd_tree_search.h> #ifdef GUDHI_SUBSAMPLING_PROFILING #include <gudhi/Clock.h> @@ -27,7 +34,7 @@ namespace subsampling { * \ingroup subsampling * \brief Outputs a subset of the input points so that the * squared distance between any two points - * is greater than or equal to `min_squared_dist`. + * is greater than `min_squared_dist`. * * \tparam Kernel must be a model of the <a target="_blank" * href="http://doc.cgal.org/latest/Spatial_searching/classSearchTraits.html">SearchTraits</a> @@ -63,29 +70,15 @@ sparsify_point_set( // Parse the input points, and add them if they are not too close to // the other points std::size_t pt_idx = 0; - for (typename Point_range::const_iterator it_pt = input_pts.begin(); - it_pt != input_pts.end(); - ++it_pt, ++pt_idx) { - if (dropped_points[pt_idx]) + for (auto const& pt : input_pts) { + if (dropped_points[pt_idx++]) continue; - *output_it++ = *it_pt; - - auto ins_range = points_ds.incremental_nearest_neighbors(*it_pt); + *output_it++ = pt; // If another point Q is closer that min_squared_dist, mark Q to be dropped - for (auto const& neighbor : ins_range) { - std::size_t neighbor_point_idx = neighbor.first; - // If the neighbor is too close, we drop the neighbor - if (neighbor.second < min_squared_dist) { - // N.B.: If neighbor_point_idx < pt_idx, - // dropped_points[neighbor_point_idx] is already true but adding a - // test doesn't make things faster, so why bother? - dropped_points[neighbor_point_idx] = true; - } else { - break; - } - } + auto drop = [&dropped_points] (std::ptrdiff_t neighbor_point_idx) { dropped_points[neighbor_point_idx] = true; }; + points_ds.all_near_neighbors2(pt, min_squared_dist, min_squared_dist, boost::make_function_output_iterator(std::ref(drop))); } #ifdef GUDHI_SUBSAMPLING_PROFILING diff --git a/src/Subsampling/test/test_choose_n_farthest_points.cpp b/src/Subsampling/test/test_choose_n_farthest_points.cpp index b318d58e..94793295 100644 --- a/src/Subsampling/test/test_choose_n_farthest_points.cpp +++ b/src/Subsampling/test/test_choose_n_farthest_points.cpp @@ -44,7 +44,8 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_choose_farthest_point, Kernel, list_of_tested landmarks.clear(); Kernel k; - Gudhi::subsampling::choose_n_farthest_points(k, points, 100, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); + auto d = k.squared_distance_d_object(); + Gudhi::subsampling::choose_n_farthest_points(d, points, 100, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); BOOST_CHECK(landmarks.size() == 100); for (auto landmark : landmarks) @@ -61,32 +62,33 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_choose_farthest_point_limits, Kernel, list_of std::vector< FT > distances; landmarks.clear(); Kernel k; + auto d = k.squared_distance_d_object(); // Choose -1 farthest points in an empty point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 0); landmarks.clear(); distances.clear(); // Choose 0 farthest points in an empty point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, 0, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, 0, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 0); landmarks.clear(); distances.clear(); // Choose 1 farthest points in an empty point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, 1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, 1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 0); landmarks.clear(); distances.clear(); std::vector<FT> point({0.0, 0.0, 0.0, 0.0}); points.emplace_back(point.begin(), point.end()); // Choose -1 farthest points in a one point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 1 && distances.size() == 1); BOOST_CHECK(distances[0] == std::numeric_limits<FT>::infinity()); landmarks.clear(); distances.clear(); // Choose 0 farthest points in a one point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, 0, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, 0, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 0 && distances.size() == 0); landmarks.clear(); distances.clear(); // Choose 1 farthest points in a one point cloud - Gudhi::subsampling::choose_n_farthest_points(k, points, 1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, 1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 1 && distances.size() == 1); BOOST_CHECK(distances[0] == std::numeric_limits<FT>::infinity()); landmarks.clear(); distances.clear(); @@ -94,7 +96,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_choose_farthest_point_limits, Kernel, list_of std::vector<FT> point2({1.0, 0.0, 0.0, 0.0}); points.emplace_back(point2.begin(), point2.end()); // Choose all farthest points among 2 points - Gudhi::subsampling::choose_n_farthest_points(k, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 2 && distances.size() == 2); BOOST_CHECK(distances[0] == std::numeric_limits<FT>::infinity()); BOOST_CHECK(distances[1] == 1); @@ -102,7 +104,7 @@ BOOST_AUTO_TEST_CASE_TEMPLATE(test_choose_farthest_point_limits, Kernel, list_of // Ignore duplicated points points.emplace_back(point.begin(), point.end()); - Gudhi::subsampling::choose_n_farthest_points(k, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); + Gudhi::subsampling::choose_n_farthest_points(d, points, -1, -1, std::back_inserter(landmarks), std::back_inserter(distances)); BOOST_CHECK(landmarks.size() == 2 && distances.size() == 2); BOOST_CHECK(distances[0] == std::numeric_limits<FT>::infinity()); BOOST_CHECK(distances[1] == 1); diff --git a/src/Witness_complex/doc/Witness_complex_doc.h b/src/Witness_complex/doc/Witness_complex_doc.h index 62203054..202f4539 100644 --- a/src/Witness_complex/doc/Witness_complex_doc.h +++ b/src/Witness_complex/doc/Witness_complex_doc.h @@ -92,11 +92,11 @@ int main(int argc, char * const argv[]) { // Choose landmarks (one can choose either of the two methods below) // Gudhi::subsampling::pick_n_random_points(point_vector, nbL, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), point_vector, nbL, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), point_vector, nbL, + Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); // Compute witness complex - Witness_complex witness_complex(landmarks, - point_vector); + Witness_complex witness_complex(landmarks, point_vector); witness_complex.create_complex(simplex_tree, alpha2, lim_dim); } diff --git a/src/Witness_complex/example/example_strong_witness_complex_off.cpp b/src/Witness_complex/example/example_strong_witness_complex_off.cpp index 583a04ab..2bb135bf 100644 --- a/src/Witness_complex/example/example_strong_witness_complex_off.cpp +++ b/src/Witness_complex/example/example_strong_witness_complex_off.cpp @@ -43,7 +43,8 @@ int main(int argc, char* const argv[]) { // Choose landmarks (decomment one of the following two lines) // Gudhi::subsampling::pick_n_random_points(point_vector, nbL, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), point_vector, nbL, Gudhi::subsampling::random_starting_point, + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), point_vector, + nbL, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); // Compute witness complex diff --git a/src/Witness_complex/example/example_witness_complex_off.cpp b/src/Witness_complex/example/example_witness_complex_off.cpp index 3635da78..e1384c73 100644 --- a/src/Witness_complex/example/example_witness_complex_off.cpp +++ b/src/Witness_complex/example/example_witness_complex_off.cpp @@ -47,7 +47,8 @@ int main(int argc, char * const argv[]) { // Choose landmarks (decomment one of the following two lines) // Gudhi::subsampling::pick_n_random_points(point_vector, nbL, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), point_vector, nbL, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), point_vector, nbL, + Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); // Compute witness complex start = clock(); diff --git a/src/Witness_complex/example/example_witness_complex_sphere.cpp b/src/Witness_complex/example/example_witness_complex_sphere.cpp index 78d5db4f..12a56de4 100644 --- a/src/Witness_complex/example/example_witness_complex_sphere.cpp +++ b/src/Witness_complex/example/example_witness_complex_sphere.cpp @@ -53,7 +53,7 @@ int main(int argc, char* const argv[]) { // Choose landmarks start = clock(); // Gudhi::subsampling::pick_n_random_points(point_vector, number_of_landmarks, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), point_vector, number_of_landmarks, + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), point_vector, number_of_landmarks, Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); diff --git a/src/Witness_complex/utilities/strong_witness_persistence.cpp b/src/Witness_complex/utilities/strong_witness_persistence.cpp index 1f61c77c..614de0d4 100644 --- a/src/Witness_complex/utilities/strong_witness_persistence.cpp +++ b/src/Witness_complex/utilities/strong_witness_persistence.cpp @@ -61,7 +61,8 @@ int main(int argc, char* argv[]) { // Choose landmarks (decomment one of the following two lines) // Gudhi::subsampling::pick_n_random_points(point_vector, nbL, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), witnesses, nbL, Gudhi::subsampling::random_starting_point, + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), witnesses, nbL, + Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); // Compute witness complex diff --git a/src/Witness_complex/utilities/weak_witness_persistence.cpp b/src/Witness_complex/utilities/weak_witness_persistence.cpp index 93050af5..5ea31d6b 100644 --- a/src/Witness_complex/utilities/weak_witness_persistence.cpp +++ b/src/Witness_complex/utilities/weak_witness_persistence.cpp @@ -61,7 +61,8 @@ int main(int argc, char* argv[]) { // Choose landmarks (decomment one of the following two lines) // Gudhi::subsampling::pick_n_random_points(point_vector, nbL, std::back_inserter(landmarks)); - Gudhi::subsampling::choose_n_farthest_points(K(), witnesses, nbL, Gudhi::subsampling::random_starting_point, + Gudhi::subsampling::choose_n_farthest_points(K().squared_distance_d_object(), witnesses, nbL, + Gudhi::subsampling::random_starting_point, std::back_inserter(landmarks)); // Compute witness complex diff --git a/src/common/doc/examples.h b/src/common/doc/examples.h index c19b3444..474f8699 100644 --- a/src/common/doc/examples.h +++ b/src/common/doc/examples.h @@ -42,7 +42,7 @@ * @example Persistence_representations/persistence_landscape.cpp * @example Tangential_complex/example_basic.cpp * @example Tangential_complex/example_with_perturb.cpp - * @example Subsampling/example_custom_kernel.cpp + * @example Subsampling/example_custom_distance.cpp * @example Subsampling/example_choose_n_farthest_points.cpp * @example Subsampling/example_sparsify_point_set.cpp * @example Subsampling/example_pick_n_random_points.cpp diff --git a/src/common/doc/installation.h b/src/common/doc/installation.h index 9df1c2f0..c2e63a24 100644 --- a/src/common/doc/installation.h +++ b/src/common/doc/installation.h @@ -113,8 +113,6 @@ make doxygen * Spatial_searching/example_spatial_searching.cpp</a> * \li <a href="_subsampling_2example_choose_n_farthest_points_8cpp-example.html"> * Subsampling/example_choose_n_farthest_points.cpp</a> - * \li <a href="_subsampling_2example_custom_kernel_8cpp-example.html"> - * Subsampling/example_custom_kernel.cpp</a> * \li <a href="_subsampling_2example_pick_n_random_points_8cpp-example.html"> * Subsampling/example_pick_n_random_points.cpp</a> * \li <a href="_subsampling_2example_sparsify_point_set_8cpp-example.html"> @@ -153,8 +151,6 @@ make doxygen * Spatial_searching/example_spatial_searching.cpp</a> * \li <a href="_subsampling_2example_choose_n_farthest_points_8cpp-example.html"> * Subsampling/example_choose_n_farthest_points.cpp</a> - * \li <a href="_subsampling_2example_custom_kernel_8cpp-example.html"> - * Subsampling/example_custom_kernel.cpp</a> * \li <a href="_subsampling_2example_pick_n_random_points_8cpp-example.html"> * Subsampling/example_pick_n_random_points.cpp</a> * \li <a href="_subsampling_2example_sparsify_point_set_8cpp-example.html"> diff --git a/src/python/gudhi/subsampling.pyx b/src/python/gudhi/subsampling.pyx index b11d07e5..46f32335 100644 --- a/src/python/gudhi/subsampling.pyx +++ b/src/python/gudhi/subsampling.pyx @@ -105,7 +105,7 @@ def pick_n_random_points(points=None, off_file='', nb_points=0): def sparsify_point_set(points=None, off_file='', min_squared_dist=0.0): """Outputs a subset of the input points so that the squared distance - between any two points is greater than or equal to min_squared_dist. + between any two points is greater than min_squared_dist. :param points: The input point set. :type points: Iterable[Iterable[float]] diff --git a/src/python/include/Alpha_complex_factory.h b/src/python/include/Alpha_complex_factory.h index d699ad9b..3405fdd6 100644 --- a/src/python/include/Alpha_complex_factory.h +++ b/src/python/include/Alpha_complex_factory.h @@ -48,11 +48,14 @@ static CgalPointType pt_cython_to_cgal(std::vector<double> const& vec) { class Abstract_alpha_complex { public: virtual std::vector<double> get_point(int vh) = 0; + virtual bool create_simplex_tree(Simplex_tree_interface<>* simplex_tree, double max_alpha_square, bool default_filtration_value) = 0; + + virtual ~Abstract_alpha_complex() = default; }; -class Exact_Alphacomplex_dD : public Abstract_alpha_complex { +class Exact_Alphacomplex_dD final : public Abstract_alpha_complex { private: using Kernel = CGAL::Epeck_d<CGAL::Dynamic_dimension_tag>; using Point = typename Kernel::Point_d; @@ -78,7 +81,7 @@ class Exact_Alphacomplex_dD : public Abstract_alpha_complex { Alpha_complex<Kernel> alpha_complex_; }; -class Inexact_Alphacomplex_dD : public Abstract_alpha_complex { +class Inexact_Alphacomplex_dD final : public Abstract_alpha_complex { private: using Kernel = CGAL::Epick_d<CGAL::Dynamic_dimension_tag>; using Point = typename Kernel::Point_d; @@ -104,7 +107,7 @@ class Inexact_Alphacomplex_dD : public Abstract_alpha_complex { }; template <complexity Complexity> -class Alphacomplex_3D : public Abstract_alpha_complex { +class Alphacomplex_3D final : public Abstract_alpha_complex { private: using Point = typename Alpha_complex_3d<Complexity, false, false>::Bare_point_3; diff --git a/src/python/include/Subsampling_interface.h b/src/python/include/Subsampling_interface.h index cdda851f..6aee7231 100644 --- a/src/python/include/Subsampling_interface.h +++ b/src/python/include/Subsampling_interface.h @@ -11,6 +11,7 @@ #ifndef INCLUDE_SUBSAMPLING_INTERFACE_H_ #define INCLUDE_SUBSAMPLING_INTERFACE_H_ +#include <gudhi/distance_functions.h> #include <gudhi/choose_n_farthest_points.h> #include <gudhi/pick_n_random_points.h> #include <gudhi/sparsify_point_set.h> @@ -27,14 +28,13 @@ namespace subsampling { using Subsampling_dynamic_kernel = CGAL::Epick_d< CGAL::Dynamic_dimension_tag >; using Subsampling_point_d = Subsampling_dynamic_kernel::Point_d; -using Subsampling_ft = Subsampling_dynamic_kernel::FT; // ------ choose_n_farthest_points ------ std::vector<std::vector<double>> subsampling_n_farthest_points(const std::vector<std::vector<double>>& points, unsigned nb_points) { std::vector<std::vector<double>> landmarks; - Subsampling_dynamic_kernel k; - choose_n_farthest_points(k, points, nb_points, random_starting_point, std::back_inserter(landmarks)); + choose_n_farthest_points(Euclidean_distance(), points, nb_points, + random_starting_point, std::back_inserter(landmarks)); return landmarks; } @@ -42,8 +42,8 @@ std::vector<std::vector<double>> subsampling_n_farthest_points(const std::vector std::vector<std::vector<double>> subsampling_n_farthest_points(const std::vector<std::vector<double>>& points, unsigned nb_points, unsigned starting_point) { std::vector<std::vector<double>> landmarks; - Subsampling_dynamic_kernel k; - choose_n_farthest_points(k, points, nb_points, starting_point, std::back_inserter(landmarks)); + choose_n_farthest_points(Euclidean_distance(), points, nb_points, + starting_point, std::back_inserter(landmarks)); return landmarks; } diff --git a/src/python/test/test_subsampling.py b/src/python/test/test_subsampling.py index 31f64e32..4019852e 100755 --- a/src/python/test/test_subsampling.py +++ b/src/python/test/test_subsampling.py @@ -141,12 +141,16 @@ def test_simple_sparsify_points(): # assert gudhi.sparsify_point_set(points = [], min_squared_dist = 0.0) == [] # assert gudhi.sparsify_point_set(points = [], min_squared_dist = 10.0) == [] assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=0.0) == point_set - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.0) == point_set - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.0) == [ + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=0.999) == point_set + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.001) == [ [0, 1], [1, 0], ] - assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.01) == [[0, 1]] + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=1.999) == [ + [0, 1], + [1, 0], + ] + assert gudhi.sparsify_point_set(points=point_set, min_squared_dist=2.001) == [[0, 1]] assert ( len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=0.0)) @@ -157,11 +161,11 @@ def test_simple_sparsify_points(): == 5 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=40.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=40.1)) == 4 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=90.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=89.9)) == 3 ) assert ( @@ -169,7 +173,7 @@ def test_simple_sparsify_points(): == 2 ) assert ( - len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=325.0)) + len(gudhi.sparsify_point_set(off_file="subsample.off", min_squared_dist=324.9)) == 2 ) assert ( |