diff options
author | ROUVREAU Vincent <vincent.rouvreau@inria.fr> | 2021-03-18 16:16:48 +0100 |
---|---|---|
committer | ROUVREAU Vincent <vincent.rouvreau@inria.fr> | 2021-03-18 16:16:48 +0100 |
commit | af98f16e12ec9d1af7d925ecdc53b4daefea6ebe (patch) | |
tree | 08fa846346bda913e65b75bf5005f21112bf5c44 | |
parent | d2ae3d4e9f17649813f64bbc3b00d540b23f21dd (diff) |
Add weight support
-rw-r--r-- | src/python/doc/alpha_complex_user.rst | 104 | ||||
-rw-r--r-- | src/python/gudhi/alpha_complex.pyx | 48 | ||||
-rw-r--r-- | src/python/include/Alpha_complex_factory.h | 65 | ||||
-rw-r--r-- | src/python/include/Alpha_complex_interface.h | 28 |
4 files changed, 194 insertions, 51 deletions
diff --git a/src/python/doc/alpha_complex_user.rst b/src/python/doc/alpha_complex_user.rst index b116944c..6f35cc15 100644 --- a/src/python/doc/alpha_complex_user.rst +++ b/src/python/doc/alpha_complex_user.rst @@ -44,16 +44,15 @@ This example builds the alpha-complex from the given points: .. testcode:: - import gudhi - alpha_complex = gudhi.AlphaComplex(points=[[1, 1], [7, 0], [4, 6], [9, 6], [0, 14], [2, 19], [9, 17]]) - - simplex_tree = alpha_complex.create_simplex_tree() - result_str = 'Alpha complex is of dimension ' + repr(simplex_tree.dimension()) + ' - ' + \ - repr(simplex_tree.num_simplices()) + ' simplices - ' + \ - repr(simplex_tree.num_vertices()) + ' vertices.' - print(result_str) + from gudhi import AlphaComplex + ac = AlphaComplex(points=[[1, 1], [7, 0], [4, 6], [9, 6], [0, 14], [2, 19], [9, 17]]) + + stree = ac.create_simplex_tree() + print('Alpha complex is of dimension ', stree.dimension(), ' - ', + stree.num_simplices(), ' simplices - ', stree.num_vertices(), ' vertices.') + fmt = '%s -> %.2f' - for filtered_value in simplex_tree.get_filtration(): + for filtered_value in stree.get_filtration(): print(fmt % tuple(filtered_value)) The output is: @@ -174,6 +173,78 @@ of speed-up, since we always first build the full filtered complex, so it is rec :paramref:`~gudhi.AlphaComplex.create_simplex_tree.max_alpha_square`. In the following example, a threshold of :math:`\alpha^2 = 32.0` is used. +Weighted specific version +^^^^^^^^^^^^^^^^^^^^^^^^^ + +:Requires: `Eigen <installation.html#eigen>`_ :math:`\geq` 3.1.0 and `CGAL <installation.html#cgal>`_ :math:`\geq` 5.1.0. + +A weighted version for Alpha complex is available. It is like a usual Alpha complex, but based on a +`CGAL regular triangulation <https://doc.cgal.org/latest/Triangulation/index.html#title20>`_ +of Delaunay. + +This example builds the CGAL weighted alpha shapes from a small molecule, and initializes the alpha complex with +it. This example is taken from +`CGAL 3d weighted alpha shapes <https://doc.cgal.org/latest/Alpha_shapes_3/index.html#title13>`_. + +Then, it is asked to display information about the alpha complex. + +.. testcode:: + + from gudhi import AlphaComplex + wgt_ac = AlphaComplex(weighted_points=[[[ 1., -1., -1.], 4.], + [[-1., 1., -1.], 4.], + [[-1., -1., 1.], 4.], + [[ 1., 1., 1.], 4.], + [[ 2., 2., 2.], 1.]]) + # equivalent to: + # wgt_ac = AlphaComplex(points=[[ 1., -1., -1.], + # [-1., 1., -1.], + # [-1., -1., 1.], + # [ 1., 1., 1.], + # [ 2., 2., 2.]], + # weights = [4., 4., 4., 4., 1.]) + + stree = wgt_ac.create_simplex_tree() + print('Weighted alpha complex is of dimension ', stree.dimension(), ' - ', + stree.num_simplices(), ' simplices - ', stree.num_vertices(), ' vertices.') + fmt = '%s -> %.2f' + for filtered_value in stree.get_filtration(): + print(fmt % tuple(filtered_value)) + +The output is: + +.. testoutput:: + + Weighted alpha complex is of dimension 3 - 29 simplices - 5 vertices. + [ 0 ] -> [-4] + [ 1 ] -> [-4] + [ 2 ] -> [-4] + [ 3 ] -> [-4] + [ 1, 0 ] -> [-2] + [ 2, 0 ] -> [-2] + [ 2, 1 ] -> [-2] + [ 3, 0 ] -> [-2] + [ 3, 1 ] -> [-2] + [ 3, 2 ] -> [-2] + [ 2, 1, 0 ] -> [-1.33333] + [ 3, 1, 0 ] -> [-1.33333] + [ 3, 2, 0 ] -> [-1.33333] + [ 3, 2, 1 ] -> [-1.33333] + [ 3, 2, 1, 0 ] -> [-1] + [ 4 ] -> [-1] + [ 4, 2 ] -> [-1] + [ 4, 0 ] -> [23] + [ 4, 1 ] -> [23] + [ 4, 2, 0 ] -> [23] + [ 4, 2, 1 ] -> [23] + [ 4, 3 ] -> [23] + [ 4, 3, 2 ] -> [23] + [ 4, 1, 0 ] -> [95] + [ 4, 2, 1, 0 ] -> [95] + [ 4, 3, 0 ] -> [95] + [ 4, 3, 1 ] -> [95] + [ 4, 3, 2, 0 ] -> [95] + [ 4, 3, 2, 1 ] -> [95] Example from OFF file ^^^^^^^^^^^^^^^^^^^^^ @@ -186,14 +257,9 @@ Then, it computes the persistence diagram and displays it: :include-source: import matplotlib.pyplot as plt - import gudhi - alpha_complex = gudhi.AlphaComplex(off_file=gudhi.__root_source_dir__ + \ - '/data/points/tore3D_300.off') - simplex_tree = alpha_complex.create_simplex_tree() - result_str = 'Alpha complex is of dimension ' + repr(simplex_tree.dimension()) + ' - ' + \ - repr(simplex_tree.num_simplices()) + ' simplices - ' + \ - repr(simplex_tree.num_vertices()) + ' vertices.' - print(result_str) - diag = simplex_tree.persistence() - gudhi.plot_persistence_diagram(diag) + import gudhi as gd + ac = gd.AlphaComplex(off_file=gd.__root_source_dir__ + '/data/points/tore3D_300.off') + stree = ac.create_simplex_tree() + dgm = stree.persistence() + gd.plot_persistence_diagram(dgm, legend = True) plt.show() diff --git a/src/python/gudhi/alpha_complex.pyx b/src/python/gudhi/alpha_complex.pyx index b7c20f74..681faebe 100644 --- a/src/python/gudhi/alpha_complex.pyx +++ b/src/python/gudhi/alpha_complex.pyx @@ -29,7 +29,7 @@ __license__ = "GPL v3" cdef extern from "Alpha_complex_interface.h" namespace "Gudhi": cdef cppclass Alpha_complex_interface "Gudhi::alpha_complex::Alpha_complex_interface": - Alpha_complex_interface(vector[vector[double]] points, bool fast_version, bool exact_version) nogil except + + Alpha_complex_interface(vector[vector[double]] points, vector[double] weights, bool fast_version, bool exact_version) nogil except + vector[double] get_point(int vertex) nogil except + void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, double max_alpha_square, bool default_filtration_value) nogil except + @@ -56,26 +56,35 @@ cdef class AlphaComplex: cdef Alpha_complex_interface * this_ptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, points=[], off_file='', weights=[], weight_file='', precision='safe'): + def __init__(self, points=[], off_file='', weights=[], weight_file='', weighted_points=[], + precision='safe'): """AlphaComplex constructor. :param points: A list of points in d-Dimension. - :type points: list of list of double + :type points: Iterable[Iterable[float]] - :param off_file: An `OFF file style <fileformats.html#off-file-format>`_ name. `points` are - read and overwritten by the points in the `off_file`. + :param off_file: An `OFF file style <fileformats.html#off-file-format>`_ name. + If an `off_file` is given with `points` or `weighted_points`, only points from the + file are taken into account. :type off_file: string :param weights: A list of weights. If set, the number of weights must correspond to the number of points. - :type weights: list of double + :type weights: Iterable[float] :param weight_file: A file containing a list of weights (one per line). - `weights` are read and overwritten by the weights in the `weight_file`. - If set, the number of weights must correspond to the number of points. + If a `weight_file` is given with `weights` or `weighted_points`, only weights from the + file are taken into account. + :type weight_file: string - :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is 'safe'. + :param weighted_points: A list of points in d-Dimension and its weight. + If `weighted_points` are given with `weights` or `points`, these last ones will + not be taken into account. + :type weighted_points: Iterable[Iterable[float], float] + + :param precision: Alpha complex precision can be 'fast', 'safe' or 'exact'. Default is + 'safe'. :type precision: string :raises FileNotFoundError: If `off_file` and/or `weight_file` is set but not found. @@ -83,11 +92,16 @@ cdef class AlphaComplex: """ # The real cython constructor - def __cinit__(self, points = [], off_file = '', weights=[], weight_file='', precision = 'safe'): + def __cinit__(self, points = [], off_file = '', weights=[], weight_file='', weighted_points=[], + precision = 'safe'): assert precision in ['fast', 'safe', 'exact'], "Alpha complex precision can only be 'fast', 'safe' or 'exact'" cdef bool fast = precision == 'fast' cdef bool exact = precision == 'exact' + if len(weighted_points) > 0: + points = [wpt[0] for wpt in weighted_points] + weights = [wpt[1] for wpt in weighted_points] + if off_file: if os.path.isfile(off_file): points = read_points_from_off_file(off_file = off_file) @@ -100,20 +114,18 @@ cdef class AlphaComplex: else: raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), weight_file) + # weights are set but is inconsistent with the number of points + if len(weights) != 0 and len(weights) != len(points): + raise ValueError("Inconsistency between the number of points and weights") + # need to copy the points to use them without the gil cdef vector[vector[double]] pts cdef vector[double] wgts pts = points + wgts = weights if len(weights) == 0: with nogil: - self.this_ptr = new Alpha_complex_interface(pts, fast, exact) - else: - if len(weights) == len(points): - wgts = weights - with nogil: - self.this_ptr = new Alpha_complex_interface(pts, fast, exact) - else: - raise ValueError("Inconsistency between the number of points and weights") + self.this_ptr = new Alpha_complex_interface(pts, wgts, fast, exact) def __dealloc__(self): if self.this_ptr != NULL: diff --git a/src/python/include/Alpha_complex_factory.h b/src/python/include/Alpha_complex_factory.h index 3405fdd6..36e98615 100644 --- a/src/python/include/Alpha_complex_factory.h +++ b/src/python/include/Alpha_complex_factory.h @@ -55,13 +55,13 @@ class Abstract_alpha_complex { virtual ~Abstract_alpha_complex() = default; }; -class Exact_Alphacomplex_dD final : public Abstract_alpha_complex { +class Exact_alpha_complex_dD final : public Abstract_alpha_complex { private: using Kernel = CGAL::Epeck_d<CGAL::Dynamic_dimension_tag>; using Point = typename Kernel::Point_d; public: - Exact_Alphacomplex_dD(const std::vector<std::vector<double>>& points, bool exact_version) + Exact_alpha_complex_dD(const std::vector<std::vector<double>>& points, bool exact_version) : exact_version_(exact_version), alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal<Point>)) { } @@ -81,13 +81,13 @@ class Exact_Alphacomplex_dD final : public Abstract_alpha_complex { Alpha_complex<Kernel> alpha_complex_; }; -class Inexact_Alphacomplex_dD final : public Abstract_alpha_complex { +class Inexact_alpha_complex_dD final : public Abstract_alpha_complex { private: using Kernel = CGAL::Epick_d<CGAL::Dynamic_dimension_tag>; using Point = typename Kernel::Point_d; public: - Inexact_Alphacomplex_dD(const std::vector<std::vector<double>>& points, bool exact_version) + Inexact_alpha_complex_dD(const std::vector<std::vector<double>>& points, bool exact_version) : exact_version_(exact_version), alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal<Point>)) { } @@ -106,8 +106,61 @@ class Inexact_Alphacomplex_dD final : public Abstract_alpha_complex { Alpha_complex<Kernel> alpha_complex_; }; +class Exact_weighted_alpha_complex_dD final : public Abstract_alpha_complex { + private: + using Kernel = CGAL::Epeck_d<CGAL::Dynamic_dimension_tag>; + using Point = typename Kernel::Point_d; + + public: + Exact_weighted_alpha_complex_dD(const std::vector<std::vector<double>>& points, + const std::vector<double>& weights, bool exact_version) + : exact_version_(exact_version), + alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal<Point>), weights) { + } + + virtual std::vector<double> get_point(int vh) override { + Point const& point = alpha_complex_.get_point(vh).point(); + return pt_cgal_to_cython(point); + } + + virtual bool create_simplex_tree(Simplex_tree_interface<>* simplex_tree, double max_alpha_square, + bool default_filtration_value) override { + return alpha_complex_.create_complex(*simplex_tree, max_alpha_square, exact_version_, default_filtration_value); + } + + private: + bool exact_version_; + Alpha_complex<Kernel, true> alpha_complex_; +}; + +class Inexact_weighted_alpha_complex_dD final : public Abstract_alpha_complex { + private: + using Kernel = CGAL::Epick_d<CGAL::Dynamic_dimension_tag>; + using Point = typename Kernel::Point_d; + + public: + Inexact_weighted_alpha_complex_dD(const std::vector<std::vector<double>>& points, + const std::vector<double>& weights, bool exact_version) + : exact_version_(exact_version), + alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal<Point>), weights) { + } + + virtual std::vector<double> get_point(int vh) override { + Point const& point = alpha_complex_.get_point(vh).point(); + return pt_cgal_to_cython(point); + } + virtual bool create_simplex_tree(Simplex_tree_interface<>* simplex_tree, double max_alpha_square, + bool default_filtration_value) override { + return alpha_complex_.create_complex(*simplex_tree, max_alpha_square, exact_version_, default_filtration_value); + } + + private: + bool exact_version_; + Alpha_complex<Kernel, true> alpha_complex_; +}; + template <complexity Complexity> -class Alphacomplex_3D final : public Abstract_alpha_complex { +class Alpha_complex_3D final : public Abstract_alpha_complex { private: using Point = typename Alpha_complex_3d<Complexity, false, false>::Bare_point_3; @@ -116,7 +169,7 @@ class Alphacomplex_3D final : public Abstract_alpha_complex { } public: - Alphacomplex_3D(const std::vector<std::vector<double>>& points) + Alpha_complex_3D(const std::vector<std::vector<double>>& points) : alpha_complex_(boost::adaptors::transform(points, pt_cython_to_cgal_3)) { } diff --git a/src/python/include/Alpha_complex_interface.h b/src/python/include/Alpha_complex_interface.h index 23be194d..43c96b2f 100644 --- a/src/python/include/Alpha_complex_interface.h +++ b/src/python/include/Alpha_complex_interface.h @@ -27,8 +27,11 @@ namespace alpha_complex { class Alpha_complex_interface { public: - Alpha_complex_interface(const std::vector<std::vector<double>>& points, bool fast_version, bool exact_version) + Alpha_complex_interface(const std::vector<std::vector<double>>& points, + const std::vector<double>& weights, + bool fast_version, bool exact_version) : points_(points), + weights_(weights), fast_version_(fast_version), exact_version_(exact_version) { } @@ -41,13 +44,13 @@ class Alpha_complex_interface { bool default_filtration_value) { if (points_.size() > 0) { std::size_t dimension = points_[0].size(); - if (dimension == 3 && !default_filtration_value) { + if (dimension == 3 && weights_.size() == 0 && !default_filtration_value) { if (fast_version_) - alpha_ptr_ = std::make_unique<Alphacomplex_3D<Gudhi::alpha_complex::complexity::FAST>>(points_); + alpha_ptr_ = std::make_unique<Alpha_complex_3D<Gudhi::alpha_complex::complexity::FAST>>(points_); else if (exact_version_) - alpha_ptr_ = std::make_unique<Alphacomplex_3D<Gudhi::alpha_complex::complexity::EXACT>>(points_); + alpha_ptr_ = std::make_unique<Alpha_complex_3D<Gudhi::alpha_complex::complexity::EXACT>>(points_); else - alpha_ptr_ = std::make_unique<Alphacomplex_3D<Gudhi::alpha_complex::complexity::SAFE>>(points_); + alpha_ptr_ = std::make_unique<Alpha_complex_3D<Gudhi::alpha_complex::complexity::SAFE>>(points_); if (!alpha_ptr_->create_simplex_tree(simplex_tree, max_alpha_square, default_filtration_value)) { // create_simplex_tree will fail if all points are on a plane - Retry with dD by setting dimension to 2 dimension--; @@ -55,11 +58,19 @@ class Alpha_complex_interface { } } // Not ** else ** because we have to take into account if 3d fails - if (dimension != 3 || default_filtration_value) { + if (dimension != 3 || weights_.size() != 0 || default_filtration_value) { if (fast_version_) { - alpha_ptr_ = std::make_unique<Inexact_Alphacomplex_dD>(points_, exact_version_); + if (weights_.size() == 0) { + alpha_ptr_ = std::make_unique<Inexact_alpha_complex_dD>(points_, exact_version_); + } else { + alpha_ptr_ = std::make_unique<Inexact_weighted_alpha_complex_dD>(points_, weights_, exact_version_); + } } else { - alpha_ptr_ = std::make_unique<Exact_Alphacomplex_dD>(points_, exact_version_); + if (weights_.size() == 0) { + alpha_ptr_ = std::make_unique<Exact_alpha_complex_dD>(points_, exact_version_); + } else { + alpha_ptr_ = std::make_unique<Exact_weighted_alpha_complex_dD>(points_, weights_, exact_version_); + } } alpha_ptr_->create_simplex_tree(simplex_tree, max_alpha_square, default_filtration_value); } @@ -69,6 +80,7 @@ class Alpha_complex_interface { private: std::unique_ptr<Abstract_alpha_complex> alpha_ptr_; std::vector<std::vector<double>> points_; + std::vector<double> weights_; bool fast_version_; bool exact_version_; }; |