diff options
author | Vincent Rouvreau <vincent.rouvreau@inria.fr> | 2021-11-02 10:53:05 +0100 |
---|---|---|
committer | Vincent Rouvreau <vincent.rouvreau@inria.fr> | 2021-11-02 10:53:05 +0100 |
commit | 003abb66b7c657356328bc24306e2c5aef02b1d4 (patch) | |
tree | 0530c8987c824da79ecddd22485f143831a4c71f | |
parent | b1f604312adb6a9240f8e8b7effcd4bd7251e82b (diff) | |
parent | 8adb46d8a54f1a0dd71ea686473cc4ca9f5d2f67 (diff) |
Merge master and fix conflicts
41 files changed, 847 insertions, 262 deletions
diff --git a/.github/for_maintainers/new_gudhi_version_creation.md b/.github/for_maintainers/new_gudhi_version_creation.md index d6c4cdd3..3e5295c5 100644 --- a/.github/for_maintainers/new_gudhi_version_creation.md +++ b/.github/for_maintainers/new_gudhi_version_creation.md @@ -128,5 +128,5 @@ docker image on docker hub. ## Mail sending Send version mail to the following lists : -* gudhi-devel@lists.gforge.inria.fr -* gudhi-users@lists.gforge.inria.fr (not for release candidate) +* gudhi-devel@inria.fr +* gudhi-users@inria.fr (not for release candidate) diff --git a/.github/workflows/pip-build-linux.yml b/.github/workflows/pip-build-linux.yml index 726ed0d5..12979a86 100644 --- a/.github/workflows/pip-build-linux.yml +++ b/.github/workflows/pip-build-linux.yml @@ -4,7 +4,7 @@ on: [push, pull_request] jobs: build: - name: build pip wheels + name: build pip wheel runs-on: ubuntu-latest # cf. https://github.com/GUDHI/gudhi-deploy/blob/main/Dockerfile_for_pip container: gudhi/pip_for_gudhi @@ -12,11 +12,15 @@ jobs: - uses: actions/checkout@v1 with: submodules: true - - name: Build wheels for Python 3.9 + - name: Build wheel 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
\ No newline at end of file + auditwheel repair dist/*.whl + - name: Install and test wheel for Python 3.9 + run: | + $PYTHON39/bin/python -m pip install --user pytest build_39/src/python/dist/*.whl + $PYTHON39/bin/python -m pytest src/python/test/test_alpha_complex.py diff --git a/.github/workflows/pip-build-osx.yml b/.github/workflows/pip-build-osx.yml index 732e26af..1626bb77 100644 --- a/.github/workflows/pip-build-osx.yml +++ b/.github/workflows/pip-build-osx.yml @@ -32,3 +32,7 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release -DPython_ADDITIONAL_VERSIONS=3 .. cd src/python python setup.py bdist_wheel + - name: Install and test python wheel + run: | + python -m pip install --user pytest build/src/python/dist/*.whl + python -m pytest src/python/test/test_alpha_complex.py diff --git a/.github/workflows/pip-build-windows.yml b/.github/workflows/pip-build-windows.yml index d07e8c46..0080f4d5 100644 --- a/.github/workflows/pip-build-windows.yml +++ b/.github/workflows/pip-build-windows.yml @@ -20,18 +20,36 @@ jobs: architecture: x64 - name: Install dependencies run: | - vcpkg update - vcpkg upgrade --no-dry-run - type c:/vcpkg/ports/cgal/portfile.cmake - vcpkg install eigen3 cgal --triplet x64-windows + cd c:/vcpkg + git fetch --all --tags + git checkout 2020.11-1 + bootstrap-vcpkg.bat + set VCPKG_BUILD_TYPE=release + vcpkg install eigen3 mpfr boost-accumulators boost-algorithm boost-bimap boost-callable-traits boost-concept-check boost-container boost-core boost-detail boost-filesystem boost-functional boost-fusion boost-geometry boost-graph boost-heap boost-intrusive boost-iostreams boost-iterator boost-lambda boost-logic boost-math boost-mpl boost-multi-index boost-multiprecision boost-numeric-conversion boost-optional boost-parameter boost-pool boost-preprocessor boost-property-map boost-property-tree boost-ptr-container boost-random boost-range boost-serialization boost-spirit boost-thread boost-tuple boost-type-traits boost-units boost-utility boost-variant --triplet x64-windows + vcpkg version + ls C:/vcpkg/installed/x64-windows/bin + Invoke-WebRequest https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1.zip -OutFile CGAL-5.2.1.zip + Expand-Archive -Path CGAL-5.2.1.zip -DestinationPath . + cd CGAL-5.2.1 + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DGMP_INCLUDE_DIR=c:/vcpkg/installed/x64-windows/include -DGMP_LIBRARIES=c:/vcpkg/installed/x64-windows/bin/mpir.dll . + cd ${{ github.workspace }} python -m pip install --user -r ext/gudhi-deploy/build-requirements.txt python -m pip list - name: Build python wheel run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DGMP_INCLUDE_DIR="c:/vcpkg/installed/x64-windows/include" -DGMP_LIBRARIES="c:/vcpkg/installed/x64-windows/lib/mpir.lib" -DGMP_LIBRARIES_DIR="c:/vcpkg/installed/x64-windows/lib" -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DPython_ADDITIONAL_VERSIONS=3 .. + cmake -DCGAL_DIR=c:/vcpkg/CGAL-5.2.1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DGMP_INCLUDE_DIR=c:/vcpkg/installed/x64-windows/include -DGMP_LIBRARIES=c:/vcpkg/installed/x64-windows/bin/mpir.dll .. cd src/python cp c:/vcpkg/installed/x64-windows/bin/mpfr.dll gudhi/ cp c:/vcpkg/installed/x64-windows/bin/mpir.dll gudhi/ - python setup.py bdist_wheel
\ No newline at end of file + python setup.py bdist_wheel + ls dist + - name: Install and test python wheel + run: | + cd ${{ github.workspace }} + cd build/src/python/dist/ + Get-ChildItem *.whl | ForEach-Object{python -m pip install --user $_.Name} + cd ${{ github.workspace }} + python -m pip install --user pytest + python -m pytest src/python/test/test_alpha_complex.py diff --git a/.github/workflows/pip-packaging-linux.yml b/.github/workflows/pip-packaging-linux.yml index 95e8f034..6ce0ba89 100644 --- a/.github/workflows/pip-packaging-linux.yml +++ b/.github/workflows/pip-packaging-linux.yml @@ -6,7 +6,7 @@ on: jobs: build: - name: build pip wheels + name: build pip wheel runs-on: ubuntu-latest # cf. https://github.com/GUDHI/gudhi-deploy/blob/main/Dockerfile_for_pip container: gudhi/pip_for_gudhi @@ -14,15 +14,7 @@ jobs: - uses: actions/checkout@v1 with: submodules: true - - name: Build wheels for Python 3.5 - run: | - mkdir build_35 - cd build_35 - cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$PYTHON35/bin/python .. - cd src/python - $PYTHON35/bin/python setup.py bdist_wheel - auditwheel repair dist/*.whl - - name: Build wheels for Python 3.6 + - name: Build wheel for Python 3.6 run: | mkdir build_36 cd build_36 @@ -30,7 +22,11 @@ jobs: cd src/python $PYTHON36/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl - - name: Build wheels for Python 3.7 + - name: Install and test wheel for Python 3.6 + run: | + $PYTHON36/bin/python -m pip install --user pytest build_36/src/python/dist/*.whl + $PYTHON36/bin/python -m pytest src/python/test/test_alpha_complex.py + - name: Build wheel for Python 3.7 run: | mkdir build_37 cd build_37 @@ -38,7 +34,11 @@ jobs: cd src/python $PYTHON37/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl - - name: Build wheels for Python 3.8 + - name: Install and test wheel for Python 3.7 + run: | + $PYTHON37/bin/python -m pip install --user pytest build_37/src/python/dist/*.whl + $PYTHON37/bin/python -m pytest src/python/test/test_alpha_complex.py + - name: Build wheel for Python 3.8 run: | mkdir build_38 cd build_38 @@ -46,7 +46,11 @@ jobs: cd src/python $PYTHON38/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl - - name: Build wheels for Python 3.9 + - name: Install and test wheel for Python 3.8 + run: | + $PYTHON38/bin/python -m pip install --user pytest build_38/src/python/dist/*.whl + $PYTHON38/bin/python -m pytest src/python/test/test_alpha_complex.py + - name: Build wheel for Python 3.9 run: | mkdir build_39 cd build_39 @@ -54,13 +58,29 @@ jobs: cd src/python $PYTHON39/bin/python setup.py bdist_wheel auditwheel repair dist/*.whl + - name: Install and test wheel for Python 3.9 + run: | + $PYTHON39/bin/python -m pip install --user pytest build_39/src/python/dist/*.whl + $PYTHON39/bin/python -m pytest src/python/test/test_alpha_complex.py + - name: Build wheel for Python 3.10 + run: | + mkdir build_310 + cd build_310 + cmake -DCMAKE_BUILD_TYPE=Release -DPYTHON_EXECUTABLE=$PYTHON310/bin/python .. + cd src/python + $PYTHON310/bin/python setup.py bdist_wheel + auditwheel repair dist/*.whl + - name: Install and test wheel for Python 3.10 + run: | + $PYTHON310/bin/python -m pip install --user pytest build_310/src/python/dist/*.whl + $PYTHON310/bin/python -m pytest src/python/test/test_alpha_complex.py - name: Publish on PyPi env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - $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 + $PYTHON36/bin/python -m twine upload build_36/src/python/wheelhouse/* + $PYTHON36/bin/python -m twine upload build_37/src/python/wheelhouse/* + $PYTHON36/bin/python -m twine upload build_38/src/python/wheelhouse/* + $PYTHON36/bin/python -m twine upload build_39/src/python/wheelhouse/* + $PYTHON36/bin/python -m twine upload build_310/src/python/wheelhouse/* diff --git a/.github/workflows/pip-packaging-osx.yml b/.github/workflows/pip-packaging-osx.yml index 56309b88..120c15b6 100644 --- a/.github/workflows/pip-packaging-osx.yml +++ b/.github/workflows/pip-packaging-osx.yml @@ -34,6 +34,10 @@ jobs: cmake -DCMAKE_BUILD_TYPE=Release -DPython_ADDITIONAL_VERSIONS=3 .. cd src/python python setup.py bdist_wheel + - name: Install and test python wheel + run: | + python -m pip install --user pytest build/src/python/dist/*.whl + python -m pytest src/python/test/test_alpha_complex.py - name: Publish on PyPi env: TWINE_USERNAME: __token__ diff --git a/.github/workflows/pip-packaging-windows.yml b/.github/workflows/pip-packaging-windows.yml index a428eaba..f387c5ff 100644 --- a/.github/workflows/pip-packaging-windows.yml +++ b/.github/workflows/pip-packaging-windows.yml @@ -22,10 +22,19 @@ jobs: architecture: x64 - name: Install dependencies run: | - vcpkg update - vcpkg upgrade --no-dry-run - type c:/vcpkg/ports/cgal/portfile.cmake - vcpkg install eigen3 cgal --triplet x64-windows + cd c:/vcpkg + git fetch --all --tags + git checkout 2020.11-1 + bootstrap-vcpkg.bat + set VCPKG_BUILD_TYPE=release + vcpkg install eigen3 mpfr boost-accumulators boost-algorithm boost-bimap boost-callable-traits boost-concept-check boost-container boost-core boost-detail boost-filesystem boost-functional boost-fusion boost-geometry boost-graph boost-heap boost-intrusive boost-iostreams boost-iterator boost-lambda boost-logic boost-math boost-mpl boost-multi-index boost-multiprecision boost-numeric-conversion boost-optional boost-parameter boost-pool boost-preprocessor boost-property-map boost-property-tree boost-ptr-container boost-random boost-range boost-serialization boost-spirit boost-thread boost-tuple boost-type-traits boost-units boost-utility boost-variant --triplet x64-windows + vcpkg version + ls C:/vcpkg/installed/x64-windows/bin + Invoke-WebRequest https://github.com/CGAL/cgal/releases/download/v5.2.1/CGAL-5.2.1.zip -OutFile CGAL-5.2.1.zip + Expand-Archive -Path CGAL-5.2.1.zip -DestinationPath . + cd CGAL-5.2.1 + cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DGMP_INCLUDE_DIR=c:/vcpkg/installed/x64-windows/include -DGMP_LIBRARIES=c:/vcpkg/installed/x64-windows/bin/mpir.dll . + cd ${{ github.workspace }} python -m pip install --user -r ext/gudhi-deploy/build-requirements.txt python -m pip install --user twine python -m pip list @@ -33,11 +42,20 @@ jobs: run: | mkdir build cd build - cmake -DCMAKE_BUILD_TYPE=Release -DGMP_INCLUDE_DIR="c:/vcpkg/installed/x64-windows/include" -DGMP_LIBRARIES="c:/vcpkg/installed/x64-windows/lib/mpir.lib" -DGMP_LIBRARIES_DIR="c:/vcpkg/installed/x64-windows/lib" -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DPython_ADDITIONAL_VERSIONS=3 .. + cmake -DCGAL_DIR=c:/vcpkg/CGAL-5.2.1 -DCMAKE_BUILD_TYPE=Release -DCMAKE_TOOLCHAIN_FILE=c:/vcpkg/scripts/buildsystems/vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows -DGMP_INCLUDE_DIR=c:/vcpkg/installed/x64-windows/include -DGMP_LIBRARIES=c:/vcpkg/installed/x64-windows/bin/mpir.dll .. cd src/python cp c:/vcpkg/installed/x64-windows/bin/mpfr.dll gudhi/ cp c:/vcpkg/installed/x64-windows/bin/mpir.dll gudhi/ python setup.py bdist_wheel + ls dist + - name: Install and test python wheel + run: | + cd ${{ github.workspace }} + cd build/src/python/dist/ + Get-ChildItem *.whl | ForEach-Object{python -m pip install --user $_.Name} + cd ${{ github.workspace }} + python -m pip install --user pytest + python -m pytest src/python/test/test_alpha_complex.py - name: Publish on PyPi env: TWINE_USERNAME: __token__ diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6c194f2a..a96323fd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -23,6 +23,7 @@ jobs: sudo conda install --yes --quiet --name gudhi_build_env python=$(pythonVersion) python -m pip install --user -r ext/gudhi-deploy/build-requirements.txt python -m pip install --user -r ext/gudhi-deploy/test-requirements.txt + python -m pip uninstall -y pykeops brew update || true brew install graphviz doxygen boost eigen gmp mpfr tbb cgal || true displayName: 'Install build dependencies' diff --git a/biblio/bibliography.bib b/biblio/bibliography.bib index 579fa85e..b5afff52 100644 --- a/biblio/bibliography.bib +++ b/biblio/bibliography.bib @@ -15,7 +15,6 @@ title = {{Statistical analysis and parameter selection for Mapper}}, volume = {19}, year = {2018}, url = {http://jmlr.org/papers/v19/17-291.html}, -doi = {10.5555/3291125.3291137} } @inproceedings{Dey13, diff --git a/scripts/cpp_examples_for_doxygen.py b/scripts/cpp_examples_for_doxygen.py new file mode 100644 index 00000000..5c091c4f --- /dev/null +++ b/scripts/cpp_examples_for_doxygen.py @@ -0,0 +1,16 @@ +import os +import glob + +for gd_mod in glob.glob("src/*/"): + mod_files = [] + for paths in [gd_mod + 'utilities', gd_mod + 'example']: + if os.path.isdir(paths): + for root, dirs, files in os.walk(paths): + for file in files: + if file.endswith(".cpp"): + mod_files.append(str(os.path.join(root, file)).split(paths)[1][1:]) + if len(mod_files) > 0: + mod = str(gd_mod).split('/')[1] + print(' * \section ' + mod + '_example_section ' + mod) + for file in mod_files: + print(' * @example ' + file) diff --git a/src/Bottleneck_distance/doc/perturb_pd.png b/src/Bottleneck_distance/doc/perturb_pd.png Binary files differindex be638de0..eabf3c8c 100644 --- a/src/Bottleneck_distance/doc/perturb_pd.png +++ b/src/Bottleneck_distance/doc/perturb_pd.png diff --git a/src/Bottleneck_distance/utilities/bottleneckdistance.md b/src/Bottleneck_distance/utilities/bottleneckdistance.md index a81426cf..2f5dedc9 100644 --- a/src/Bottleneck_distance/utilities/bottleneckdistance.md +++ b/src/Bottleneck_distance/utilities/bottleneckdistance.md @@ -10,14 +10,14 @@ Leave the lines above as it is required by the web site generator 'Jekyll' {:/comment}
-## bottleneck_read_file_example ##
+## bottleneck_distance ##
This program computes the Bottleneck distance between two persistence diagram files.
**Usage**
```
- bottleneck_read_file_example <file_1.pers> <file_2.pers> [<tolerance>]
+ bottleneck_distance <file_1.pers> <file_2.pers> [<tolerance>]
```
where
diff --git a/src/Persistent_cohomology/example/CMakeLists.txt b/src/Persistent_cohomology/example/CMakeLists.txt index c68c6524..3e7e9369 100644 --- a/src/Persistent_cohomology/example/CMakeLists.txt +++ b/src/Persistent_cohomology/example/CMakeLists.txt @@ -11,7 +11,7 @@ if (TBB_FOUND) target_link_libraries(persistence_from_simple_simplex_tree ${TBB_LIBRARIES}) endif() add_test(NAME Persistent_cohomology_example_from_simple_simplex_tree COMMAND $<TARGET_FILE:persistence_from_simple_simplex_tree> - "1" "0") + "2" "0") if(TARGET Boost::program_options) add_executable(rips_persistence_step_by_step rips_persistence_step_by_step.cpp) diff --git a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h index d34ee07d..d428e497 100644 --- a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h +++ b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology.h @@ -100,7 +100,7 @@ class Persistent_cohomology { ds_rank_(num_simplices_), // union-find ds_parent_(num_simplices_), // union-find ds_repr_(num_simplices_, NULL), // union-find -> annotation vectors - dsets_(&ds_rank_[0], &ds_parent_[0]), // union-find + dsets_(ds_rank_.data(), ds_parent_.data()), // union-find cam_(), // collection of annotation vectors zero_cocycles_(), // union-find -> Simplex_key of creator for 0-homology transverse_idx_(), // key -> row diff --git a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology/Field_Zp.h b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology/Field_Zp.h index 0673625c..8ec89e41 100644 --- a/src/Persistent_cohomology/include/gudhi/Persistent_cohomology/Field_Zp.h +++ b/src/Persistent_cohomology/include/gudhi/Persistent_cohomology/Field_Zp.h @@ -13,6 +13,7 @@ #include <utility> #include <vector> +#include <stdexcept> namespace Gudhi { @@ -34,14 +35,29 @@ class Field_Zp { void init(int charac) { assert(charac > 0); // division by zero + non negative values + Prime = charac; + + // Check that the provided prime is less than the maximum allowed as int, calculation below, and 'plus_times_equal' function : 46337 ; i.e (max_prime-1)*max_prime <= INT_MAX + if(Prime > 46337) + throw std::invalid_argument("Maximum homology_coeff_field allowed value is 46337"); + + // Check for primality + if (Prime <= 1) + throw std::invalid_argument("homology_coeff_field must be a prime number"); + inverse_.clear(); inverse_.reserve(charac); inverse_.push_back(0); for (int i = 1; i < Prime; ++i) { int inv = 1; - while (((inv * i) % Prime) != 1) + int mult = inv * i; + while ( (mult % Prime) != 1) { ++inv; + if(mult == Prime) + throw std::invalid_argument("homology_coeff_field must be a prime number"); + mult = inv * i; + } inverse_.push_back(inv); } } diff --git a/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp b/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp index fe3f8517..041cb0fd 100644 --- a/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp +++ b/src/Persistent_cohomology/test/persistent_cohomology_unit_test.cpp @@ -146,9 +146,14 @@ void test_rips_persistence_in_dimension(int dimension) { std::clog << "str_rips_persistence=" << str_rips_persistence << std::endl; } +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_0 ) +{ + BOOST_CHECK_THROW(test_rips_persistence_in_dimension(0), std::invalid_argument); +} + BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_1 ) { - test_rips_persistence_in_dimension(1); + BOOST_CHECK_THROW(test_rips_persistence_in_dimension(1), std::invalid_argument); } BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_2 ) @@ -161,15 +166,35 @@ BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_3 ) test_rips_persistence_in_dimension(3); } +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_4 ) +{ + BOOST_CHECK_THROW(test_rips_persistence_in_dimension(4), std::invalid_argument); +} + BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_5 ) { test_rips_persistence_in_dimension(5); } -// TODO(VR): not working from 6 -// std::string str_rips_persistence = test_rips_persistence(6, 0); -// TODO(VR): division by zero -// std::string str_rips_persistence = test_rips_persistence(0, 0); +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_6 ) +{ + BOOST_CHECK_THROW(test_rips_persistence_in_dimension(6), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_11 ) +{ + test_rips_persistence_in_dimension(11); +} + +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_13 ) +{ + test_rips_persistence_in_dimension(13); +} + +BOOST_AUTO_TEST_CASE( rips_persistent_cohomology_single_field_dim_46349 ) +{ + BOOST_CHECK_THROW(test_rips_persistence_in_dimension(46349), std::invalid_argument); +} /** SimplexTree minimal options to test the limits. * diff --git a/src/Spatial_searching/include/gudhi/Kd_tree_search.h b/src/Spatial_searching/include/gudhi/Kd_tree_search.h index a50a8537..6fb611f2 100644 --- a/src/Spatial_searching/include/gudhi/Kd_tree_search.h +++ b/src/Spatial_searching/include/gudhi/Kd_tree_search.h @@ -139,7 +139,7 @@ class Kd_tree_search { } template <typename Coord_iterator> - bool contains_point_given_as_coordinates(Coord_iterator pi, Coord_iterator CGAL_UNUSED) const { + bool contains_point_given_as_coordinates(Coord_iterator pi, Coord_iterator) const { FT distance = 0; auto ccci = traits.construct_cartesian_const_iterator_d_object(); auto ci = ccci(c); diff --git a/src/cmake/modules/GUDHI_third_party_libraries.cmake b/src/cmake/modules/GUDHI_third_party_libraries.cmake index e1566877..023061f1 100644 --- a/src/cmake/modules/GUDHI_third_party_libraries.cmake +++ b/src/cmake/modules/GUDHI_third_party_libraries.cmake @@ -156,6 +156,8 @@ if( PYTHONINTERP_FOUND ) find_python_module("eagerpy") find_python_module_no_version("hnswlib") find_python_module("tensorflow") + find_python_module("sphinx_paramlinks") + find_python_module_no_version("python_docs_theme") endif() if(NOT GUDHI_PYTHON_PATH) diff --git a/src/common/doc/examples.h b/src/common/doc/examples.h index b2183990..879fb96a 100644 --- a/src/common/doc/examples.h +++ b/src/common/doc/examples.h @@ -1,99 +1,134 @@ -// List of GUDHI examples - Doxygen needs at least a file tag to analyse comments -// In user_version, `find . -name "*.cpp"` in example and utilities folders +// List of GUDHI examples and utils - Doxygen needs at least a file tag to analyse comments +// Generated from scripts/cpp_examples_for_doxygen.py /*! @file Examples - * @example Alpha_complex_from_off.cpp - * @example Alpha_complex_from_points.cpp - * @example bottleneck_basic_example.cpp - * @example alpha_rips_persistence_bottleneck_distance.cpp - * @example example_nearest_landmark_table.cpp + * \section Witness_complex_example_section Witness_complex + * @example strong_witness_persistence.cpp + * @example weak_witness_persistence.cpp * @example example_witness_complex_off.cpp - * @example example_witness_complex_sphere.cpp * @example example_strong_witness_complex_off.cpp + * @example example_nearest_landmark_table.cpp + * @example example_witness_complex_sphere.cpp + * \section Contraction_example_section Contraction + * @example Rips_contraction.cpp + * @example Garland_heckbert.cpp + * \section Simplex_tree_example_section Simplex_tree * @example mini_simplex_tree.cpp + * @example cech_complex_cgal_mini_sphere_3d.cpp * @example graph_expansion_with_blocker.cpp * @example simple_simplex_tree.cpp * @example simplex_tree_from_cliques_of_graph.cpp * @example example_alpha_shapes_3_simplex_tree_from_off_file.cpp - * @example cech_complex_cgal_mini_sphere_3d.cpp - * @example plain_homology.cpp - * @example persistence_from_file.cpp + * \section Persistent_cohomology_example_section Persistent_cohomology + * @example custom_persistence_sort.cpp * @example rips_persistence_step_by_step.cpp + * @example persistence_from_file.cpp * @example rips_persistence_via_boundary_matrix.cpp - * @example custom_persistence_sort.cpp - * @example persistence_from_simple_simplex_tree.cpp + * @example plain_homology.cpp * @example rips_multifield_persistence.cpp - * @example Skeleton_blocker_from_simplices.cpp - * @example Skeleton_blocker_iteration.cpp - * @example Skeleton_blocker_link.cpp - * @example Garland_heckbert.cpp - * @example Rips_contraction.cpp - * @example Random_bitmap_cubical_complex.cpp - * @example example_CGAL_3D_points_off_reader.cpp - * @example example_vector_double_points_off_reader.cpp - * @example example_CGAL_points_off_reader.cpp - * @example example_one_skeleton_rips_from_distance_matrix.cpp - * @example example_one_skeleton_rips_from_points.cpp - * @example example_rips_complex_from_csv_distance_matrix_file.cpp - * @example example_rips_complex_from_off_file.cpp - * @example persistence_intervals.cpp - * @example persistence_vectors.cpp - * @example persistence_heat_maps.cpp - * @example persistence_landscape_on_grid.cpp - * @example persistence_landscape.cpp - * @example example_basic.cpp - * @example example_with_perturb.cpp - * @example example_custom_distance.cpp - * @example example_choose_n_farthest_points.cpp + * @example persistence_from_simple_simplex_tree.cpp + * \section Subsampling_example_section Subsampling * @example example_sparsify_point_set.cpp + * @example example_choose_n_farthest_points.cpp + * @example example_custom_distance.cpp * @example example_pick_n_random_points.cpp - * @example CoordGIC.cpp + * \section Toplex_map_example_section Toplex_map + * @example simple_toplex_map.cpp + * \section Collapse_example_section Collapse + * @example distance_matrix_edge_collapse_rips_persistence.cpp + * @example point_cloud_edge_collapse_rips_persistence.cpp + * @example edge_collapse_conserve_persistence.cpp + * @example edge_collapse_basic_example.cpp + * \section Cech_complex_example_section Cech_complex + * @example cech_persistence.cpp + * @example cech_complex_step_by_step.cpp + * @example cech_complex_example_from_points.cpp + * \section Bitmap_cubical_complex_example_section Bitmap_cubical_complex + * @example periodic_cubical_complex_persistence.cpp + * @example cubical_complex_persistence.cpp + * @example Random_bitmap_cubical_complex.cpp + * \section Coxeter_triangulation_example_section Coxeter_triangulation + * @example cell_complex_from_basic_circle_manifold.cpp + * @example manifold_tracing_flat_torus_with_boundary.cpp + * @example manifold_tracing_custom_function.cpp + * \section Nerve_GIC_example_section Nerve_GIC + * @example VoronoiGIC.cpp * @example Nerve.cpp + * @example CoordGIC.cpp * @example FuncGIC.cpp - * @example VoronoiGIC.cpp - * @example example_spatial_searching.cpp - * @example alpha_complex_3d_persistence.cpp - * @example alpha_complex_persistence.cpp - * @example Weighted_alpha_complex_3d_from_points.cpp - * @example bottleneck_distance.cpp - * @example weak_witness_persistence.cpp - * @example strong_witness_persistence.cpp - * @example cubical_complex_persistence.cpp - * @example periodic_cubical_complex_persistence.cpp - * @example off_file_from_shape_generator.cpp - * @example rips_distance_matrix_persistence.cpp - * @example rips_persistence.cpp + * \section Tangential_complex_example_section Tangential_complex + * @example example_basic.cpp + * @example example_with_perturb.cpp + * \section Persistence_representations_example_section Persistence_representations + * @example persistence_vectors/create_persistence_vectors.cpp + * @example persistence_vectors/compute_scalar_product_of_persistence_vectors.cpp + * @example persistence_vectors/plot_persistence_vectors.cpp + * @example persistence_vectors/average_persistence_vectors.cpp + * @example persistence_vectors/compute_distance_of_persistence_vectors.cpp + * @example persistence_landscapes_on_grid/average_landscapes_on_grid.cpp * @example persistence_landscapes_on_grid/create_landscapes_on_grid.cpp - * @example persistence_landscapes_on_grid/plot_landscapes_on_grid.cpp - * @example persistence_landscapes_on_grid/compute_scalar_product_of_landscapes_on_grid.cpp * @example persistence_landscapes_on_grid/compute_distance_of_landscapes_on_grid.cpp - * @example persistence_landscapes_on_grid/average_landscapes_on_grid.cpp + * @example persistence_landscapes_on_grid/compute_scalar_product_of_landscapes_on_grid.cpp + * @example persistence_landscapes_on_grid/plot_landscapes_on_grid.cpp * @example persistence_intervals/compute_birth_death_range_in_persistence_diagram.cpp - * @example persistence_intervals/compute_number_of_dominant_intervals.cpp * @example persistence_intervals/plot_persistence_Betti_numbers.cpp - * @example persistence_intervals/plot_persistence_intervals.cpp - * @example persistence_intervals/plot_histogram_of_intervals_lengths.cpp * @example persistence_intervals/compute_bottleneck_distance.cpp + * @example persistence_intervals/compute_number_of_dominant_intervals.cpp + * @example persistence_intervals/plot_histogram_of_intervals_lengths.cpp + * @example persistence_intervals/plot_persistence_intervals.cpp + * @example persistence_heat_maps/compute_distance_of_persistence_heat_maps.cpp * @example persistence_heat_maps/create_pssk.cpp * @example persistence_heat_maps/create_p_h_m_weighted_by_arctan_of_their_persistence.cpp + * @example persistence_heat_maps/create_p_h_m_weighted_by_distance_from_diagonal.cpp * @example persistence_heat_maps/create_p_h_m_weighted_by_squared_diag_distance.cpp - * @example persistence_heat_maps/compute_distance_of_persistence_heat_maps.cpp * @example persistence_heat_maps/compute_scalar_product_of_persistence_heat_maps.cpp - * @example persistence_heat_maps/create_p_h_m_weighted_by_distance_from_diagonal.cpp - * @example persistence_heat_maps/average_persistence_heat_maps.cpp * @example persistence_heat_maps/plot_persistence_heat_map.cpp * @example persistence_heat_maps/create_persistence_heat_maps.cpp - * @example persistence_vectors/plot_persistence_vectors.cpp - * @example persistence_vectors/compute_distance_of_persistence_vectors.cpp - * @example persistence_vectors/average_persistence_vectors.cpp - * @example persistence_vectors/create_persistence_vectors.cpp - * @example persistence_vectors/compute_scalar_product_of_persistence_vectors.cpp - * @example persistence_landscapes/average_landscapes.cpp - * @example persistence_landscapes/compute_scalar_product_of_landscapes.cpp - * @example persistence_landscapes/create_landscapes.cpp + * @example persistence_heat_maps/average_persistence_heat_maps.cpp * @example persistence_landscapes/compute_distance_of_landscapes.cpp + * @example persistence_landscapes/compute_scalar_product_of_landscapes.cpp + * @example persistence_landscapes/average_landscapes.cpp * @example persistence_landscapes/plot_landscapes.cpp - * @example cell_complex_from_basic_circle_manifold.cpp - * @example manifold_tracing_custom_function.cpp - * @example manifold_tracing_flat_torus_with_boundary.cpp + * @example persistence_landscapes/create_landscapes.cpp + * @example persistence_landscape_on_grid.cpp + * @example persistence_intervals.cpp + * @example persistence_landscape.cpp + * @example persistence_vectors.cpp + * @example sliced_wasserstein.cpp + * @example persistence_heat_maps.cpp + * \section Spatial_searching_example_section Spatial_searching + * @example example_spatial_searching.cpp + * \section Bottleneck_distance_example_section Bottleneck_distance + * @example bottleneck_distance.cpp + * @example bottleneck_basic_example.cpp + * @example alpha_rips_persistence_bottleneck_distance.cpp + * \section common_example_section common + * @example off_file_from_shape_generator.cpp + * @example example_vector_double_points_off_reader.cpp + * @example example_CGAL_points_off_reader.cpp + * @example example_CGAL_3D_points_off_reader.cpp + * \section Alpha_complex_example_section Alpha_complex + * @example alpha_complex_3d_persistence.cpp + * @example alpha_complex_persistence.cpp + * @example Fast_alpha_complex_from_off.cpp + * @example Alpha_complex_3d_from_points.cpp + * @example Alpha_complex_from_off.cpp + * @example Weighted_alpha_complex_3d_from_points.cpp + * @example Weighted_alpha_complex_from_points.cpp + * @example Alpha_complex_from_points.cpp + * \section Skeleton_blocker_example_section Skeleton_blocker + * @example Skeleton_blocker_from_simplices.cpp + * @example Skeleton_blocker_link.cpp + * @example Skeleton_blocker_iteration.cpp + * \section Rips_complex_example_section Rips_complex + * @example rips_persistence.cpp + * @example rips_correlation_matrix_persistence.cpp + * @example sparse_rips_persistence.cpp + * @example rips_distance_matrix_persistence.cpp + * @example example_sparse_rips.cpp + * @example example_rips_complex_from_csv_distance_matrix_file.cpp + * @example example_one_skeleton_rips_from_correlation_matrix.cpp + * @example example_one_skeleton_rips_from_distance_matrix.cpp + * @example example_one_skeleton_rips_from_points.cpp + * @example example_rips_complex_from_off_file.cpp */ diff --git a/src/common/doc/header.html b/src/common/doc/header.html index 9da20bbc..7c20478b 100644 --- a/src/common/doc/header.html +++ b/src/common/doc/header.html @@ -49,6 +49,7 @@ $extrastylesheet <li><a href="/relatedprojects/">Related projects</a></li> <li><a href="/theyaretalkingaboutus/">They are talking about us</a></li> <li><a href="/inaction/">GUDHI in action</a></li> + <li><a href="/etymology/">Etymology</a></li> </ul> </li> <li class="divider"></li> diff --git a/src/common/doc/installation.h b/src/common/doc/installation.h index 91d30081..ef668dfb 100644 --- a/src/common/doc/installation.h +++ b/src/common/doc/installation.h @@ -88,9 +88,9 @@ make \endverbatim * Witness_complex/example_witness_complex_off.cpp</a> * \li <a href="example_witness_complex_sphere_8cpp-example.html"> * Witness_complex/example_witness_complex_sphere.cpp</a> - * \li <a href="alpha_complex_from_off_8cpp-example.html"> + * \li <a href="_alpha_complex_from_off_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_off.cpp</a> - * \li <a href="alpha_complex_from_points_8cpp-example.html"> + * \li <a href="_alpha_complex_from_points_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_points.cpp</a> * \li <a href="alpha_complex_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_persistence.cpp</a> @@ -100,15 +100,15 @@ make \endverbatim * Bottleneck_distance/alpha_rips_persistence_bottleneck_distance.cpp.cpp</a> * \li <a href="bottleneck_basic_example_8cpp-example.html"> * Bottleneck_distance/bottleneck_basic_example.cpp</a> - * \li <a href="bottleneck_read_file_8cpp-example.html"> + * \li <a href="bottleneck_distance_8cpp-example.html"> * Bottleneck_distance/bottleneck_distance.cpp</a> - * \li <a href="coord_g_i_c_8cpp-example.html"> + * \li <a href="_coord_g_i_c_8cpp-example.html"> * Nerve_GIC/CoordGIC.cpp</a> - * \li <a href="func_g_i_c_8cpp-example.html"> + * \li <a href="_func_g_i_c_8cpp-example.html"> * Nerve_GIC/FuncGIC.cpp</a> - * \li <a href="nerve_8cpp-example.html"> + * \li <a href="_nerve_8cpp-example.html"> * Nerve_GIC/Nerve.cpp</a> - * \li <a href="voronoi_g_i_c_8cpp-example.html"> + * \li <a href="_voronoi_g_i_c_8cpp-example.html"> * Nerve_GIC/VoronoiGIC.cpp</a> * \li <a href="example_spatial_searching_8cpp-example.html"> * Spatial_searching/example_spatial_searching.cpp</a> @@ -122,7 +122,7 @@ make \endverbatim * Tangential_complex/example_basic.cpp</a> * \li <a href="example_with_perturb_8cpp-example.html"> * Tangential_complex/example_with_perturb.cpp</a> - * \li <a href="weighted_alpha_complex_3d_from_points_8cpp-example.html"> + * \li <a href="_weighted_alpha_complex_3d_from_points_8cpp-example.html"> * Alpha_complex/Weighted_alpha_complex_3d_from_points.cpp</a> * \li <a href="alpha_complex_3d_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_3d_persistence.cpp</a> @@ -136,15 +136,15 @@ make \endverbatim * * The following examples/utilities require the <a target="_blank" href="http://eigen.tuxfamily.org/">Eigen</a> and will not be * built if Eigen is not installed: - * \li <a href="alpha_complex_from_off_8cpp-example.html"> + * \li <a href="_alpha_complex_from_off_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_off.cpp</a> - * \li <a href="alpha_complex_from_points_8cpp-example.html"> + * \li <a href="_alpha_complex_from_points_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_points.cpp</a> * \li <a href="alpha_complex_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_persistence.cpp</a> * \li <a href="alpha_complex_3d_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_3d_persistence.cpp</a> - * \li <a href="weighted_alpha_complex_3d_from_points_8cpp-example.html"> + * \li <a href="_weighted_alpha_complex_3d_from_points_8cpp-example.html"> * Alpha_complex/Weighted_alpha_complex_3d_from_points.cpp</a> * \li <a href="alpha_rips_persistence_bottleneck_distance_8cpp-example.html"> * Bottleneck_distance/alpha_rips_persistence_bottleneck_distance.cpp.cpp</a> @@ -187,27 +187,27 @@ make \endverbatim * Having Intel® TBB installed is recommended to parallelize and accelerate some GUDHI computations. * * The following examples/utilities are using Intel® TBB if installed: - * \li <a href="alpha_complex_from_off_8cpp-example.html"> + * \li <a href="_alpha_complex_from_off_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_off.cpp</a> - * \li <a href="alpha_complex_from_points_8cpp-example.html"> + * \li <a href="_alpha_complex_from_points_8cpp-example.html"> * Alpha_complex/Alpha_complex_from_points.cpp</a> * \li <a href="alpha_complex_3d_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_3d_persistence.cpp</a> * \li <a href="alpha_complex_persistence_8cpp-example.html"> * Alpha_complex/alpha_complex_persistence.cpp</a> - * \li <a href="bitmap_cubical_complex_8cpp-example.html"> + * \li <a href="cubical_complex_persistence_8cpp-example.html"> * Bitmap_cubical_complex/cubical_complex_persistence.cpp</a> - * \li <a href="bitmap_cubical_complex_periodic_boundary_conditions_8cpp-example.html"> + * \li <a href="periodic_cubical_complex_persistence_8cpp-example.html"> * Bitmap_cubical_complex/periodic_cubical_complex_persistence.cpp</a> - * \li <a href="random_bitmap_cubical_complex_8cpp-example.html"> + * \li <a href="_random_bitmap_cubical_complex_8cpp-example.html"> * Bitmap_cubical_complex/Random_bitmap_cubical_complex.cpp</a> - * \li <a href="coord_g_i_c_8cpp-example.html"> + * \li <a href="_coord_g_i_c_8cpp-example.html"> * Nerve_GIC/CoordGIC.cpp</a> - * \li <a href="func_g_i_c_8cpp-example.html"> + * \li <a href="_func_g_i_c_8cpp-example.html"> * Nerve_GIC/FuncGIC.cpp</a> - * \li <a href="nerve_8cpp-example.html"> + * \li <a href="_nerve_8cpp-example.html"> * Nerve_GIC/Nerve.cpp</a> - * \li <a href="voronoi_g_i_c_8cpp-example.html"> + * \li <a href="_voronoi_g_i_c_8cpp-example.html"> * Nerve_GIC/VoronoiGIC.cpp</a> * \li <a href="simple_simplex_tree_8cpp-example.html"> * Simplex_tree/simple_simplex_tree.cpp</a> @@ -251,10 +251,12 @@ make \endverbatim * Witness_complex/example_nearest_landmark_table.cpp</a> * * \section Contributions Bug reports and contributions - * Please help us improving the quality of the GUDHI library. You may report bugs or suggestions to: - * \verbatim Contact: gudhi-users@lists.gforge.inria.fr \endverbatim + * Please help us improving the quality of the GUDHI library. + * You may <a href="https://github.com/GUDHI/gudhi-devel/issues">report bugs</a> or + * <a href="https://gudhi.inria.fr/contact/">contact us</a> for any suggestions. * - * GUDHI is open to external contributions. If you want to join our development team, please contact us. + * GUDHI is open to external contributions. If you want to join our development team, please take some time to read our + * <a href="https://github.com/GUDHI/gudhi-devel/blob/master/.github/CONTRIBUTING.md">contributing guide</a>. * */ diff --git a/src/common/test/test_distance_matrix_reader.cpp b/src/common/test/test_distance_matrix_reader.cpp index 73be8104..92e899b8 100644 --- a/src/common/test/test_distance_matrix_reader.cpp +++ b/src/common/test/test_distance_matrix_reader.cpp @@ -57,7 +57,7 @@ BOOST_AUTO_TEST_CASE( full_square_distance_matrix ) { Distance_matrix from_full_square; // Read full_square_distance_matrix.csv file where the separator is the default one ';' - from_full_square = Gudhi::read_lower_triangular_matrix_from_csv_file<double>("full_square_distance_matrix.csv"); + from_full_square = Gudhi::read_lower_triangular_matrix_from_csv_file<double>("full_square_distance_matrix.csv", ';'); for (auto& i : from_full_square) { for (auto j : i) { std::clog << j << " "; diff --git a/src/python/CMakeLists.txt b/src/python/CMakeLists.txt index a1440cbc..96107cfe 100644 --- a/src/python/CMakeLists.txt +++ b/src/python/CMakeLists.txt @@ -41,13 +41,15 @@ function( add_gudhi_debug_info DEBUG_INFO ) endfunction( add_gudhi_debug_info ) if(PYTHONINTERP_FOUND) - if(PYBIND11_FOUND) + if(PYBIND11_FOUND AND CYTHON_FOUND) add_gudhi_debug_info("Pybind11 version ${PYBIND11_VERSION}") + # PyBind11 modules set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'bottleneck', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'hera', ") set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'clustering', ") - endif() - if(CYTHON_FOUND) + set(GUDHI_PYTHON_MODULES_EXTRA "${GUDHI_PYTHON_MODULES_EXTRA}'datasets', ") + + # Cython modules set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'off_reader', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'simplex_tree', ") set(GUDHI_PYTHON_MODULES "${GUDHI_PYTHON_MODULES}'rips_complex', ") @@ -106,6 +108,16 @@ if(PYTHONINTERP_FOUND) if(TENSORFLOW_FOUND) add_gudhi_debug_info("TensorFlow version ${TENSORFLOW_VERSION}") endif() + if(SPHINX_FOUND) + add_gudhi_debug_info("Sphinx version ${SPHINX_VERSION}") + endif() + if(SPHINX_PARAMLINKS_FOUND) + add_gudhi_debug_info("Sphinx-paramlinks version ${SPHINX_PARAMLINKS_VERSION}") + endif() + if(PYTHON_DOCS_THEME_FOUND) + # Does not have a version number... + add_gudhi_debug_info("python_docs_theme found") + endif() set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DBOOST_RESULT_OF_USE_DECLTYPE', ") set(GUDHI_PYTHON_EXTRA_COMPILE_ARGS "${GUDHI_PYTHON_EXTRA_COMPILE_ARGS}'-DBOOST_ALL_NO_LIB', ") @@ -151,6 +163,7 @@ if(PYTHONINTERP_FOUND) set(GUDHI_PYBIND11_MODULES "${GUDHI_PYBIND11_MODULES}'hera/wasserstein', ") set(GUDHI_PYBIND11_MODULES "${GUDHI_PYBIND11_MODULES}'hera/bottleneck', ") if (NOT CGAL_VERSION VERSION_LESS 4.11.0) + set(GUDHI_PYBIND11_MODULES "${GUDHI_PYBIND11_MODULES}'datasets/generators/points', ") set(GUDHI_PYBIND11_MODULES "${GUDHI_PYBIND11_MODULES}'bottleneck', ") set(GUDHI_CYTHON_MODULES "${GUDHI_CYTHON_MODULES}'nerve_gic', ") endif () @@ -262,9 +275,11 @@ if(PYTHONINTERP_FOUND) file(COPY "gudhi/weighted_rips_complex.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") file(COPY "gudhi/dtm_rips_complex.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi") file(COPY "gudhi/hera/__init__.py" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi/hera") + file(COPY "gudhi/datasets" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/gudhi" FILES_MATCHING PATTERN "*.py") # Some files for pip package file(COPY "introduction.rst" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/") + file(COPY "pyproject.toml" DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/") add_custom_command( OUTPUT gudhi.so @@ -278,62 +293,65 @@ if(PYTHONINTERP_FOUND) # Documentation generation is available through sphinx - requires all modules # Make it first as sphinx test is by far the longest test which is nice when testing in parallel if(SPHINX_PATH) - if(MATPLOTLIB_FOUND) - if(NUMPY_FOUND) - if(SCIPY_FOUND) - if(SKLEARN_FOUND) - if(OT_FOUND) - if(PYBIND11_FOUND) - if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) - set (GUDHI_SPHINX_MESSAGE "Generating API documentation with Sphinx in ${CMAKE_CURRENT_BINARY_DIR}/sphinx/") - # User warning - Sphinx is a static pages generator, and configured to work fine with user_version - # Images and biblio warnings because not found on developper version - if (GUDHI_PYTHON_PATH STREQUAL "src/python") - set (GUDHI_SPHINX_MESSAGE "${GUDHI_SPHINX_MESSAGE} \n WARNING : Sphinx is configured for user version, you run it on developper version. Images and biblio will miss") - endif() - # sphinx target requires gudhi.so, because conf.py reads gudhi version from it - add_custom_target(sphinx - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doc - COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" - ${SPHINX_PATH} -b html ${CMAKE_CURRENT_SOURCE_DIR}/doc ${CMAKE_CURRENT_BINARY_DIR}/sphinx - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/gudhi.so" - COMMENT "${GUDHI_SPHINX_MESSAGE}" VERBATIM) - - add_test(NAME sphinx_py_test - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" - ${SPHINX_PATH} -b doctest ${CMAKE_CURRENT_SOURCE_DIR}/doc ${CMAKE_CURRENT_BINARY_DIR}/doctest) - - # Set missing or not modules - set(GUDHI_MODULES ${GUDHI_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MODULES") - else(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) - message("++ Python documentation module will not be compiled because it requires a Eigen3 and CGAL version >= 4.11.0") + if(SPHINX_PARAMLINKS_FOUND) + if(PYTHON_DOCS_THEME_FOUND) + if(MATPLOTLIB_FOUND) + if(NUMPY_FOUND) + if(SCIPY_FOUND) + if(SKLEARN_FOUND) + if(OT_FOUND) + if(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) + set (GUDHI_SPHINX_MESSAGE "Generating API documentation with Sphinx in ${CMAKE_CURRENT_BINARY_DIR}/sphinx/") + # User warning - Sphinx is a static pages generator, and configured to work fine with user_version + # Images and biblio warnings because not found on developper version + if (GUDHI_PYTHON_PATH STREQUAL "src/python") + set (GUDHI_SPHINX_MESSAGE "${GUDHI_SPHINX_MESSAGE} \n WARNING : Sphinx is configured for user version, you run it on developper version. Images and biblio will miss") + endif() + # sphinx target requires gudhi.so, because conf.py reads gudhi version from it + add_custom_target(sphinx + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/doc + COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" + ${SPHINX_PATH} -b html ${CMAKE_CURRENT_SOURCE_DIR}/doc ${CMAKE_CURRENT_BINARY_DIR}/sphinx + DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/gudhi.so" + COMMENT "${GUDHI_SPHINX_MESSAGE}" VERBATIM) + add_test(NAME sphinx_py_test + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" + ${SPHINX_PATH} -b doctest ${CMAKE_CURRENT_SOURCE_DIR}/doc ${CMAKE_CURRENT_BINARY_DIR}/doctest) + # Set missing or not modules + set(GUDHI_MODULES ${GUDHI_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MODULES") + else(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) + message("++ Python documentation module will not be compiled because it requires a Eigen3 and CGAL version >= 4.11.0") + set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") + endif(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) + else(OT_FOUND) + message("++ Python documentation module will not be compiled because POT was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(NOT CGAL_WITH_EIGEN3_VERSION VERSION_LESS 4.11.0) - else(PYBIND11_FOUND) - message("++ Python documentation module will not be compiled because pybind11 was not found") + endif(OT_FOUND) + else(SKLEARN_FOUND) + message("++ Python documentation module will not be compiled because scikit-learn was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(PYBIND11_FOUND) - else(OT_FOUND) - message("++ Python documentation module will not be compiled because POT was not found") + endif(SKLEARN_FOUND) + else(SCIPY_FOUND) + message("++ Python documentation module will not be compiled because scipy was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(OT_FOUND) - else(SKLEARN_FOUND) - message("++ Python documentation module will not be compiled because scikit-learn was not found") + endif(SCIPY_FOUND) + else(NUMPY_FOUND) + message("++ Python documentation module will not be compiled because numpy was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(SKLEARN_FOUND) - else(SCIPY_FOUND) - message("++ Python documentation module will not be compiled because scipy was not found") + endif(NUMPY_FOUND) + else(MATPLOTLIB_FOUND) + message("++ Python documentation module will not be compiled because matplotlib was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(SCIPY_FOUND) - else(NUMPY_FOUND) - message("++ Python documentation module will not be compiled because numpy was not found") + endif(MATPLOTLIB_FOUND) + else(PYTHON_DOCS_THEME_FOUND) + message("++ Python documentation module will not be compiled because python-docs-theme was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(NUMPY_FOUND) - else(MATPLOTLIB_FOUND) - message("++ Python documentation module will not be compiled because matplotlib was not found") + endif(PYTHON_DOCS_THEME_FOUND) + else(SPHINX_PARAMLINKS_FOUND) + message("++ Python documentation module will not be compiled because sphinxcontrib-paramlinks was not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(MATPLOTLIB_FOUND) + endif(SPHINX_PARAMLINKS_FOUND) else(SPHINX_PATH) message("++ Python documentation module will not be compiled because sphinx and sphinxcontrib-bibtex were not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python-documentation" CACHE INTERNAL "GUDHI_MISSING_MODULES") @@ -381,9 +399,7 @@ if(PYTHONINTERP_FOUND) COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/example/bottleneck_basic_example.py") - if (PYBIND11_FOUND) - add_gudhi_py_test(test_bottleneck_distance) - endif() + add_gudhi_py_test(test_bottleneck_distance) # Cover complex file(COPY ${CMAKE_SOURCE_DIR}/data/points/human.off DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/) @@ -423,6 +439,10 @@ if(PYTHONINTERP_FOUND) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/example/alpha_complex_from_points_example.py") + add_test(NAME alpha_complex_from_generated_points_on_sphere_example_py_test + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" + ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/example/alpha_complex_from_generated_points_on_sphere_example.py") add_test(NAME alpha_complex_diagram_persistence_from_off_file_example_py_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" @@ -457,7 +477,7 @@ if(PYTHONINTERP_FOUND) WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ${CMAKE_COMMAND} -E env "${GUDHI_PYTHON_PATH_ENV}" ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py" - --no-diagram -f ${CMAKE_SOURCE_DIR}/data/distance_matrix/lower_triangular_distance_matrix.csv -e 12.0 -d 3) + --no-diagram -f ${CMAKE_SOURCE_DIR}/data/distance_matrix/lower_triangular_distance_matrix.csv -s , -e 12.0 -d 3) add_test(NAME rips_complex_diagram_persistence_from_off_file_example_py_test WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} @@ -493,14 +513,14 @@ if(PYTHONINTERP_FOUND) add_gudhi_py_test(test_reader_utils) # Wasserstein - if(OT_FOUND AND PYBIND11_FOUND) + if(OT_FOUND) # EagerPy dependency because of enable_autodiff=True if(EAGERPY_FOUND) add_gudhi_py_test(test_wasserstein_distance) endif() + add_gudhi_py_test(test_wasserstein_barycenter) - endif() - if(OT_FOUND) + if(TORCH_FOUND AND TENSORFLOW_FOUND AND EAGERPY_FOUND) add_gudhi_py_test(test_wasserstein_with_tensors) endif() @@ -521,7 +541,7 @@ if(PYTHONINTERP_FOUND) endif() # Tomato - if(SCIPY_FOUND AND SKLEARN_FOUND AND PYBIND11_FOUND) + if(SCIPY_FOUND AND SKLEARN_FOUND) add_gudhi_py_test(test_tomato) endif() @@ -538,10 +558,10 @@ if(PYTHONINTERP_FOUND) # Set missing or not modules set(GUDHI_MODULES ${GUDHI_MODULES} "python" CACHE INTERNAL "GUDHI_MODULES") - else(CYTHON_FOUND) - message("++ Python module will not be compiled because cython was not found") + else(PYBIND11_FOUND AND CYTHON_FOUND) + message("++ Python module will not be compiled because cython and/or pybind11 was/were not found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python" CACHE INTERNAL "GUDHI_MISSING_MODULES") - endif(CYTHON_FOUND) + endif(PYBIND11_FOUND AND CYTHON_FOUND) else(PYTHONINTERP_FOUND) message("++ Python module will not be compiled because no Python interpreter was found") set(GUDHI_MISSING_MODULES ${GUDHI_MISSING_MODULES} "python" CACHE INTERNAL "GUDHI_MISSING_MODULES") diff --git a/src/python/doc/_templates/layout.html b/src/python/doc/_templates/layout.html index cd40a51b..e074b6c7 100644 --- a/src/python/doc/_templates/layout.html +++ b/src/python/doc/_templates/layout.html @@ -194,6 +194,7 @@ <li><a href="/relatedprojects/">Related projects</a></li> <li><a href="/theyaretalkingaboutus/">They are talking about us</a></li> <li><a href="/inaction/">GUDHI in action</a></li> + <li><a href="/etymology/">Etymology</a></li> </ul> </li> <li class="divider"></li> diff --git a/src/python/doc/conf.py b/src/python/doc/conf.py index b06baf9c..e69e2751 100755 --- a/src/python/doc/conf.py +++ b/src/python/doc/conf.py @@ -120,15 +120,12 @@ pygments_style = 'sphinx' # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'classic' +html_theme = 'python_docs_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { - "sidebarbgcolor": "#A1ADCD", - "sidebartextcolor": "black", - "sidebarlinkcolor": "#334D5C", "body_max_width": "100%", } diff --git a/src/python/doc/installation.rst b/src/python/doc/installation.rst index 9c16b04e..35c344e3 100644 --- a/src/python/doc/installation.rst +++ b/src/python/doc/installation.rst @@ -194,8 +194,10 @@ A complete configuration would be : Documentation ============= -To build the documentation, `sphinx-doc <http://www.sphinx-doc.org>`_ and -`sphinxcontrib-bibtex <https://sphinxcontrib-bibtex.readthedocs.io>`_ are +To build the documentation, `sphinx-doc <http://www.sphinx-doc.org>`_, +`sphinxcontrib-bibtex <https://sphinxcontrib-bibtex.readthedocs.io>`_, +`sphinxcontrib-paramlinks <https://github.com/sqlalchemyorg/sphinx-paramlinks>`_ and +`python-docs-theme <https://github.com/python/python-docs-theme>`_ are required. As the documentation is auto-tested, `CGAL`_, `Eigen`_, `Matplotlib`_, `NumPy`_, `POT`_, `Scikit-learn`_ and `SciPy`_ are also mandatory to build the documentation. @@ -357,7 +359,7 @@ Python Optimal Transport ------------------------ The :doc:`Wasserstein distance </wasserstein_distance_user>` -module requires `POT <https://pot.readthedocs.io/>`_, a library that provides +module requires `POT <https://pythonot.github.io/>`_, a library that provides several solvers for optimization problems related to Optimal Transport. PyTorch @@ -396,8 +398,9 @@ TensorFlow Bug reports and contributions ***************************** -Please help us improving the quality of the GUDHI library. You may report bugs or suggestions to: +Please help us improving the quality of the GUDHI library. +You may `report bugs <https://github.com/GUDHI/gudhi-devel/issues>`_ or +`contact us <https://gudhi.inria.fr/contact/>`_ for any suggestions. - Contact: gudhi-users@lists.gforge.inria.fr - -GUDHI is open to external contributions. If you want to join our development team, please contact us. +GUDHI is open to external contributions. If you want to join our development team, please take some time to read our +`contributing guide <https://github.com/GUDHI/gudhi-devel/blob/master/.github/CONTRIBUTING.md>`_. diff --git a/src/python/doc/wasserstein_distance_user.rst b/src/python/doc/wasserstein_distance_user.rst index 9ffc2759..76eb1469 100644 --- a/src/python/doc/wasserstein_distance_user.rst +++ b/src/python/doc/wasserstein_distance_user.rst @@ -44,7 +44,7 @@ Basic example ************* This example computes the 1-Wasserstein distance from 2 persistence diagrams with Euclidean ground metric. -Note that persistence diagrams must be submitted as (n x 2) numpy arrays and must not contain inf values. +Note that persistence diagrams must be submitted as (n x 2) numpy arrays. .. testcode:: @@ -67,14 +67,16 @@ We can also have access to the optimal matching by letting `matching=True`. It is encoded as a list of indices (i,j), meaning that the i-th point in X is mapped to the j-th point in Y. An index of -1 represents the diagonal. +It handles essential parts (points with infinite coordinates). However if the cardinalities of the essential parts differ, +any matching has a cost +inf and thus can be considered to be optimal. In such a case, the function returns `(np.inf, None)`. .. testcode:: import gudhi.wasserstein import numpy as np - dgm1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974]]) - dgm2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1]]) + dgm1 = np.array([[2.7, 3.7],[9.6, 14.],[34.2, 34.974], [3, np.inf]]) + dgm2 = np.array([[2.8, 4.45], [5, 6], [9.5, 14.1], [4, np.inf]]) cost, matchings = gudhi.wasserstein.wasserstein_distance(dgm1, dgm2, matching=True, order=1, internal_p=2) message_cost = "Wasserstein distance value = %.2f" %cost @@ -90,16 +92,31 @@ An index of -1 represents the diagonal. for j in dgm2_to_diagonal: print("point %s in dgm2 is matched to the diagonal" %j) -The output is: + # An example where essential part cardinalities differ + dgm3 = np.array([[1, 2], [0, np.inf]]) + dgm4 = np.array([[1, 2], [0, np.inf], [1, np.inf]]) + cost, matchings = gudhi.wasserstein.wasserstein_distance(dgm3, dgm4, matching=True, order=1, internal_p=2) + print("\nSecond example:") + print("cost:", cost) + print("matchings:", matchings) + + +The output is: .. testoutput:: - Wasserstein distance value = 2.15 + Wasserstein distance value = 3.15 point 0 in dgm1 is matched to point 0 in dgm2 point 1 in dgm1 is matched to point 2 in dgm2 + point 3 in dgm1 is matched to point 3 in dgm2 point 2 in dgm1 is matched to the diagonal point 1 in dgm2 is matched to the diagonal + Second example: + cost: inf + matchings: None + + Barycenters ----------- @@ -181,4 +198,4 @@ Tutorial This `notebook <https://github.com/GUDHI/TDA-tutorial/blob/master/Tuto-GUDHI-Barycenters-of-persistence-diagrams.ipynb>`_ -presents the concept of barycenter, or Fréchet mean, of a family of persistence diagrams.
\ No newline at end of file +presents the concept of barycenter, or Fréchet mean, of a family of persistence diagrams. diff --git a/src/python/example/alpha_complex_from_generated_points_on_sphere_example.py b/src/python/example/alpha_complex_from_generated_points_on_sphere_example.py new file mode 100644 index 00000000..267e6436 --- /dev/null +++ b/src/python/example/alpha_complex_from_generated_points_on_sphere_example.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +from gudhi.datasets.generators import points +from gudhi import AlphaComplex + + +""" 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): Hind Montassif + + Copyright (C) 2021 Inria + + Modification(s): + - YYYY/MM Author: Description of the modification +""" + +__author__ = "Hind Montassif" +__copyright__ = "Copyright (C) 2021 Inria" +__license__ = "MIT" + +print("#####################################################################") +print("AlphaComplex creation from generated points on sphere") + + +gen_points = points.sphere(n_samples = 50, ambient_dim = 2, radius = 1, sample = "random") + +# Create an alpha complex +alpha_complex = AlphaComplex(points = gen_points) +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) + diff --git a/src/python/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py b/src/python/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py index 236d085d..8a9cc857 100755 --- a/src/python/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py +++ b/src/python/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py @@ -21,11 +21,12 @@ parser = argparse.ArgumentParser( description="RipsComplex creation from " "a distance matrix read in a csv file.", epilog="Example: " "example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py " - "-f ../data/distance_matrix/lower_triangular_distance_matrix.csv -e 12.0 -d 3" + "-f ../data/distance_matrix/lower_triangular_distance_matrix.csv -s , -e 12.0 -d 3" "- Constructs a Rips complex with the " "distance matrix from the given csv file.", ) parser.add_argument("-f", "--file", type=str, required=True) +parser.add_argument("-s", "--separator", type=str, required=True) parser.add_argument("-e", "--max_edge_length", type=float, default=0.5) parser.add_argument("-d", "--max_dimension", type=int, default=1) parser.add_argument("-b", "--band", type=float, default=0.0) @@ -44,7 +45,7 @@ print("RipsComplex creation from distance matrix read in a csv file") message = "RipsComplex with max_edge_length=" + repr(args.max_edge_length) print(message) -distance_matrix = gudhi.read_lower_triangular_matrix_from_csv_file(csv_file=args.file) +distance_matrix = gudhi.read_lower_triangular_matrix_from_csv_file(csv_file=args.file, separator=args.separator) rips_complex = gudhi.RipsComplex( distance_matrix=distance_matrix, max_edge_length=args.max_edge_length ) diff --git a/src/python/gudhi/cubical_complex.pyx b/src/python/gudhi/cubical_complex.pyx index 28fbe3af..97c69a2d 100644 --- a/src/python/gudhi/cubical_complex.pyx +++ b/src/python/gudhi/cubical_complex.pyx @@ -35,7 +35,7 @@ cdef extern from "Cubical_complex_interface.h" namespace "Gudhi": cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": cdef cppclass Cubical_complex_persistence_interface "Gudhi::Persistent_cohomology_interface<Gudhi::Cubical_complex::Cubical_complex_interface<>>": Cubical_complex_persistence_interface(Bitmap_cubical_complex_base_interface * st, bool persistence_dim_max) nogil - void compute_persistence(int homology_coeff_field, double min_persistence) nogil + void compute_persistence(int homology_coeff_field, double min_persistence) nogil except+ vector[pair[int, pair[double, double]]] get_persistence() nogil vector[vector[int]] cofaces_of_cubical_persistence_pairs() nogil vector[int] betti_numbers() nogil @@ -147,7 +147,7 @@ cdef class CubicalComplex: :func:`persistence` returns. :param homology_coeff_field: The homology coefficient field. Must be a - prime number + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int. :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is @@ -169,7 +169,7 @@ cdef class CubicalComplex: """This function computes and returns the persistence of the complex. :param homology_coeff_field: The homology coefficient field. Must be a - prime number + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int. :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is diff --git a/src/python/gudhi/datasets/__init__.py b/src/python/gudhi/datasets/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/python/gudhi/datasets/__init__.py diff --git a/src/python/gudhi/datasets/generators/__init__.py b/src/python/gudhi/datasets/generators/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/python/gudhi/datasets/generators/__init__.py diff --git a/src/python/gudhi/datasets/generators/points.cc b/src/python/gudhi/datasets/generators/points.cc new file mode 100644 index 00000000..d658946b --- /dev/null +++ b/src/python/gudhi/datasets/generators/points.cc @@ -0,0 +1,68 @@ +/* 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): Hind Montassif + * + * Copyright (C) 2021 Inria + * + * Modification(s): + * - YYYY/MM Author: Description of the modification + */ + +#include <pybind11/pybind11.h> +#include <pybind11/numpy.h> + +#include <gudhi/random_point_generators.h> +#include <gudhi/Debug_utils.h> + +#include <CGAL/Epick_d.h> + +namespace py = pybind11; + + +typedef CGAL::Epick_d< CGAL::Dynamic_dimension_tag > Kern; + +py::array_t<double> generate_points_on_sphere(size_t n_samples, int ambient_dim, double radius, std::string sample) { + + if (sample != "random") { + throw pybind11::value_error("This sample type is not supported"); + } + + py::array_t<double> points({n_samples, (size_t)ambient_dim}); + + py::buffer_info buf = points.request(); + double *ptr = static_cast<double *>(buf.ptr); + + GUDHI_CHECK(n_samples == buf.shape[0], "Py array first dimension not matching n_samples on sphere"); + GUDHI_CHECK(ambient_dim == buf.shape[1], "Py array second dimension not matching the ambient space dimension"); + + + py::gil_scoped_release release; + auto points_generated = Gudhi::generate_points_on_sphere_d<Kern>(n_samples, ambient_dim, radius); + + for (size_t i = 0; i < n_samples; i++) + for (int j = 0; j < ambient_dim; j++) + ptr[i*ambient_dim+j] = points_generated[i][j]; + + return points; +} + +PYBIND11_MODULE(points, m) { + m.attr("__license__") = "LGPL v3"; + m.def("sphere", &generate_points_on_sphere, + py::arg("n_samples"), py::arg("ambient_dim"), + py::arg("radius") = 1., py::arg("sample") = "random", + R"pbdoc( + Generate random i.i.d. points uniformly on a (d-1)-sphere in R^d + + :param n_samples: The number of points to be generated. + :type n_samples: integer + :param ambient_dim: The ambient dimension d. + :type ambient_dim: integer + :param radius: The radius. Default value is `1.`. + :type radius: float + :param sample: The sample type. Default and only available value is `"random"`. + :type sample: string + :rtype: numpy array of float + :returns: the generated points on a sphere. + )pbdoc"); +} diff --git a/src/python/gudhi/periodic_cubical_complex.pyx b/src/python/gudhi/periodic_cubical_complex.pyx index d353d2af..ef1d3080 100644 --- a/src/python/gudhi/periodic_cubical_complex.pyx +++ b/src/python/gudhi/periodic_cubical_complex.pyx @@ -32,7 +32,7 @@ cdef extern from "Cubical_complex_interface.h" namespace "Gudhi": cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": cdef cppclass Periodic_cubical_complex_persistence_interface "Gudhi::Persistent_cohomology_interface<Gudhi::Cubical_complex::Cubical_complex_interface<Gudhi::cubical_complex::Bitmap_cubical_complex_periodic_boundary_conditions_base<double>>>": Periodic_cubical_complex_persistence_interface(Periodic_cubical_complex_base_interface * st, bool persistence_dim_max) nogil - void compute_persistence(int homology_coeff_field, double min_persistence) nogil + void compute_persistence(int homology_coeff_field, double min_persistence) nogil except + vector[pair[int, pair[double, double]]] get_persistence() nogil vector[vector[int]] cofaces_of_cubical_persistence_pairs() nogil vector[int] betti_numbers() nogil @@ -148,7 +148,7 @@ cdef class PeriodicCubicalComplex: :func:`persistence` returns. :param homology_coeff_field: The homology coefficient field. Must be a - prime number + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int. :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is @@ -170,7 +170,7 @@ cdef class PeriodicCubicalComplex: """This function computes and returns the persistence of the complex. :param homology_coeff_field: The homology coefficient field. Must be a - prime number + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int. :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is diff --git a/src/python/gudhi/simplex_tree.pxd b/src/python/gudhi/simplex_tree.pxd index 3b8ea4f9..006a24ed 100644 --- a/src/python/gudhi/simplex_tree.pxd +++ b/src/python/gudhi/simplex_tree.pxd @@ -78,7 +78,7 @@ cdef extern from "Simplex_tree_interface.h" namespace "Gudhi": cdef extern from "Persistent_cohomology_interface.h" namespace "Gudhi": cdef cppclass Simplex_tree_persistence_interface "Gudhi::Persistent_cohomology_interface<Gudhi::Simplex_tree<Gudhi::Simplex_tree_options_full_featured>>": Simplex_tree_persistence_interface(Simplex_tree_interface_full_featured * st, bool persistence_dim_max) nogil - void compute_persistence(int homology_coeff_field, double min_persistence) nogil + void compute_persistence(int homology_coeff_field, double min_persistence) nogil except + vector[pair[int, pair[double, double]]] get_persistence() nogil vector[int] betti_numbers() nogil vector[int] persistent_betti_numbers(double from_value, double to_value) nogil diff --git a/src/python/gudhi/simplex_tree.pyx b/src/python/gudhi/simplex_tree.pyx index be08a3a1..9c51cb46 100644 --- a/src/python/gudhi/simplex_tree.pyx +++ b/src/python/gudhi/simplex_tree.pyx @@ -412,7 +412,7 @@ cdef class SimplexTree: """This function retrieves good values for extended persistence, and separate the diagrams into the Ordinary, Relative, Extended+ and Extended- subdiagrams. - :param homology_coeff_field: The homology coefficient field. Must be a prime number. Default value is 11. + :param homology_coeff_field: The homology coefficient field. Must be a prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int :param min_persistence: The minimum persistence value (i.e., the absolute value of the difference between the persistence diagram point coordinates) to take into account (strictly greater than min_persistence). @@ -449,7 +449,7 @@ cdef class SimplexTree: """This function computes and returns the persistence of the simplicial complex. :param homology_coeff_field: The homology coefficient field. Must be a - prime number. Default value is 11. + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is @@ -472,7 +472,7 @@ cdef class SimplexTree: when you do not want the list :func:`persistence` returns. :param homology_coeff_field: The homology coefficient field. Must be a - prime number. Default value is 11. + prime number. Default value is 11. Max is 46337. :type homology_coeff_field: int :param min_persistence: The minimum persistence value to take into account (strictly greater than min_persistence). Default value is diff --git a/src/python/gudhi/wasserstein/wasserstein.py b/src/python/gudhi/wasserstein/wasserstein.py index a9d1cdff..dc18806e 100644 --- a/src/python/gudhi/wasserstein/wasserstein.py +++ b/src/python/gudhi/wasserstein/wasserstein.py @@ -9,6 +9,7 @@ import numpy as np import scipy.spatial.distance as sc +import warnings try: import ot @@ -70,6 +71,7 @@ def _perstot_autodiff(X, order, internal_p): ''' return _dist_to_diag(X, internal_p).norms.lp(order) + def _perstot(X, order, internal_p, enable_autodiff): ''' :param X: (n x 2) numpy.array (points of a given diagram). @@ -79,6 +81,9 @@ def _perstot(X, order, internal_p, enable_autodiff): transparent to automatic differentiation. :type enable_autodiff: bool :returns: float, the total persistence of the diagram (that is, its distance to the empty diagram). + + .. note:: + Can be +inf if the diagram has an essential part (points with infinite coordinates). ''' if enable_autodiff: import eagerpy as ep @@ -88,32 +93,163 @@ def _perstot(X, order, internal_p, enable_autodiff): return np.linalg.norm(_dist_to_diag(X, internal_p), ord=order) -def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enable_autodiff=False): +def _get_essential_parts(a): ''' - :param X: (n x 2) numpy.array encoding the (finite points of the) first diagram. Must not contain essential points - (i.e. with infinite coordinate). - :param Y: (m x 2) numpy.array encoding the second diagram. - :param matching: if True, computes and returns the optimal matching between X and Y, encoded as - a (n x 2) np.array [...[i,j]...], meaning the i-th point in X is matched to - the j-th point in Y, with the convention (-1) represents the diagonal. - :param order: exponent for Wasserstein; Default value is 1. - :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2); - Default value is `np.inf`. - :param enable_autodiff: If X and Y are torch.tensor or tensorflow.Tensor, make the computation + :param a: (n x 2) numpy.array (point of a diagram) + :returns: five lists of indices (between 0 and len(a)) accounting for the five types of points with infinite + coordinates that can occur in a diagram, namely: + type0 : (-inf, finite) + type1 : (finite, +inf) + type2 : (-inf, +inf) + type3 : (-inf, -inf) + type4 : (+inf, +inf) + .. note:: + For instance, a[_get_essential_parts(a)[0]] returns the points in a of coordinates (-inf, x) for some finite x. + Note also that points with (+inf, -inf) are not handled (points (x,y) in dgm satisfy by assumption (y >= x)). + + Finally, we consider that points with coordinates (-inf,-inf) and (+inf, +inf) belong to the diagonal. + ''' + if len(a): + first_coord_finite = np.isfinite(a[:,0]) + second_coord_finite = np.isfinite(a[:,1]) + first_coord_infinite_positive = (a[:,0] == np.inf) + second_coord_infinite_positive = (a[:,1] == np.inf) + first_coord_infinite_negative = (a[:,0] == -np.inf) + second_coord_infinite_negative = (a[:,1] == -np.inf) + + ess_first_type = np.where(second_coord_finite & first_coord_infinite_negative)[0] # coord (-inf, x) + ess_second_type = np.where(first_coord_finite & second_coord_infinite_positive)[0] # coord (x, +inf) + ess_third_type = np.where(first_coord_infinite_negative & second_coord_infinite_positive)[0] # coord (-inf, +inf) + + ess_fourth_type = np.where(first_coord_infinite_negative & second_coord_infinite_negative)[0] # coord (-inf, -inf) + ess_fifth_type = np.where(first_coord_infinite_positive & second_coord_infinite_positive)[0] # coord (+inf, +inf) + return ess_first_type, ess_second_type, ess_third_type, ess_fourth_type, ess_fifth_type + else: + return [], [], [], [], [] + + +def _cost_and_match_essential_parts(X, Y, idX, idY, order, axis): + ''' + :param X: (n x 2) numpy.array (dgm points) + :param Y: (n x 2) numpy.array (dgm points) + :param idX: indices to consider for this one dimensional OT problem (in X) + :param idY: indices to consider for this one dimensional OT problem (in Y) + :param order: exponent for Wasserstein distance computation + :param axis: must be 0 or 1, correspond to the coordinate which is finite. + :returns: cost (float) and match for points with *one* infinite coordinate. + + .. note:: + Assume idX, idY come when calling _handle_essential_parts, thus have same length. + ''' + u = X[idX, axis] + v = Y[idY, axis] + + cost = np.sum(np.abs(np.sort(u) - np.sort(v))**(order)) # OT cost in 1D + + sortidX = idX[np.argsort(u)] + sortidY = idY[np.argsort(v)] + # We return [i,j] sorted per value + match = list(zip(sortidX, sortidY)) + + return cost, match + + +def _handle_essential_parts(X, Y, order): + ''' + :param X: (n x 2) numpy array, first diagram. + :param Y: (n x 2) numpy array, second diagram. + :order: Wasserstein order for cost computation. + :returns: cost and matching due to essential parts. If cost is +inf, matching will be set to None. + ''' + ess_parts_X = _get_essential_parts(X) + ess_parts_Y = _get_essential_parts(Y) + + # Treats the case of infinite cost (cardinalities of essential parts differ). + for u, v in list(zip(ess_parts_X, ess_parts_Y))[:3]: # ignore types 4 and 5 as they belong to the diagonal + if len(u) != len(v): + return np.inf, None + + # Now we know each essential part has the same number of points in both diagrams. + # Handle type 0 and type 1 essential parts (those with one finite coordinates) + c1, m1 = _cost_and_match_essential_parts(X, Y, ess_parts_X[0], ess_parts_Y[0], axis=1, order=order) + c2, m2 = _cost_and_match_essential_parts(X, Y, ess_parts_X[1], ess_parts_Y[1], axis=0, order=order) + + c = c1 + c2 + m = m1 + m2 + + # Handle type3 (coordinates (-inf,+inf), so we just align points) + m += list(zip(ess_parts_X[2], ess_parts_Y[2])) + + # Handle type 4 and 5, considered as belonging to the diagonal so matched to (-1) with cost 0. + for z in ess_parts_X[3:]: + m += [(u, -1) for u in z] # points in X are matched to -1 + for z in ess_parts_Y[3:]: + m += [(-1, v) for v in z] # -1 is match to points in Y + + return c, np.array(m) + + +def _finite_part(X): + ''' + :param X: (n x 2) numpy array encoding a persistence diagram. + :returns: The finite part of a diagram `X` (points with finite coordinates). + ''' + return X[np.where(np.isfinite(X[:,0]) & np.isfinite(X[:,1]))] + + +def _warn_infty(matching): + ''' + Handle essential parts with different cardinalities. Warn the user about cost being infinite and (if + `matching=True`) about the returned matching being `None`. + ''' + if matching: + warnings.warn('Cardinality of essential parts differs. Distance (cost) is +inf, and the returned matching is None.') + return np.inf, None + else: + warnings.warn('Cardinality of essential parts differs. Distance (cost) is +inf.') + return np.inf + + +def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enable_autodiff=False, + keep_essential_parts=True): + ''' + Compute the Wasserstein distance between persistence diagram using Python Optimal Transport backend. + Diagrams can contain points with infinity coordinates (essential parts). + Points with (-inf,-inf) and (+inf,+inf) coordinates are considered as belonging to the diagonal. + If the distance between two diagrams is +inf (which happens if the cardinalities of essential + parts differ) and optimal matching is required, it will be set to ``None``. + + :param X: The first diagram. + :type X: n x 2 numpy.array + :param Y: The second diagram. + :type Y: m x 2 numpy.array + :param matching: if ``True``, computes and returns the optimal matching between X and Y, encoded as + a (n x 2) np.array [...[i,j]...], meaning the i-th point in X is matched to + the j-th point in Y, with the convention that (-1) represents the diagonal. + :param order: Wasserstein exponent q (1 <= q < infinity). + :type order: float + :param internal_p: Ground metric on the (upper-half) plane (i.e. norm L^p in R^2). + :type internal_p: float + :param enable_autodiff: If X and Y are ``torch.tensor`` or ``tensorflow.Tensor``, make the computation transparent to automatic differentiation. This requires the package EagerPy and is currently incompatible - with `matching=True`. + with ``matching=True`` and with ``keep_essential_parts=True``. - .. note:: This considers the function defined on the coordinates of the off-diagonal points of X and Y + .. note:: This considers the function defined on the coordinates of the off-diagonal finite points of X and Y and lets the various frameworks compute its gradient. It never pulls new points from the diagonal. :type enable_autodiff: bool - :returns: the Wasserstein distance of order q (1 <= q < infinity) between persistence diagrams with + :param keep_essential_parts: If ``False``, only considers the finite points in the diagrams. + Otherwise, include essential parts in cost and matching computation. + :type keep_essential_parts: bool + :returns: The Wasserstein distance of order q (1 <= q < infinity) between persistence diagrams with respect to the internal_p-norm as ground metric. If matching is set to True, also returns the optimal matching between X and Y. + If cost is +inf, any matching is optimal and thus it returns `None` instead. ''' + + # First step: handle empty diagrams n = len(X) m = len(Y) - # handle empty diagrams if n == 0: if m == 0: if not matching: @@ -122,16 +258,45 @@ def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enab else: return 0., np.array([]) else: - if not matching: - return _perstot(Y, order, internal_p, enable_autodiff) + cost = _perstot(Y, order, internal_p, enable_autodiff) + if cost == np.inf: + return _warn_infty(matching) else: - return _perstot(Y, order, internal_p, enable_autodiff), np.array([[-1, j] for j in range(m)]) + if not matching: + return cost + else: + return cost, np.array([[-1, j] for j in range(m)]) elif m == 0: - if not matching: - return _perstot(X, order, internal_p, enable_autodiff) + cost = _perstot(X, order, internal_p, enable_autodiff) + if cost == np.inf: + return _warn_infty(matching) else: - return _perstot(X, order, internal_p, enable_autodiff), np.array([[i, -1] for i in range(n)]) + if not matching: + return cost + else: + return cost, np.array([[i, -1] for i in range(n)]) + + # Check essential part and enable autodiff together + if enable_autodiff and keep_essential_parts: + warnings.warn('''enable_autodiff=True and keep_essential_parts=True are incompatible together. + keep_essential_parts is set to False: only points with finite coordinates are considered + in the following. + ''') + keep_essential_parts = False + + # Second step: handle essential parts if needed. + if keep_essential_parts: + essential_cost, essential_matching = _handle_essential_parts(X, Y, order=order) + if (essential_cost == np.inf): + return _warn_infty(matching) # Tells the user that cost is infty and matching (if True) is None. + # avoid computing transport cost between the finite parts if essential parts + # cardinalities do not match (saves time) + else: + essential_cost = 0 + essential_matching = None + + # Now the standard pipeline for finite parts if enable_autodiff: import eagerpy as ep @@ -139,6 +304,12 @@ def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enab Y_orig = ep.astensor(Y) X = X_orig.numpy() Y = Y_orig.numpy() + + # Extract finite points of the diagrams. + X, Y = _finite_part(X), _finite_part(Y) + n = len(X) + m = len(Y) + M = _build_dist_matrix(X, Y, order=order, internal_p=internal_p) a = np.ones(n+1) # weight vector of the input diagram. Uniform here. a[-1] = m @@ -154,7 +325,10 @@ def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enab # Now we turn to -1 points encoding the diagonal match[:,0][match[:,0] >= n] = -1 match[:,1][match[:,1] >= m] = -1 - return ot_cost ** (1./order) , match + # Finally incorporate the essential part matching + if essential_matching is not None: + match = np.concatenate([match, essential_matching]) if essential_matching.size else match + return (ot_cost + essential_cost) ** (1./order) , match if enable_autodiff: P = ot.emd(a=a, b=b, M=M, numItermax=2000000) @@ -173,9 +347,9 @@ def wasserstein_distance(X, Y, matching=False, order=1., internal_p=np.inf, enab return ep.concatenate(dists).norms.lp(order).raw # We can also concatenate the 3 vectors to compute just one norm. - # Comptuation of the otcost using the ot.emd2 library. + # Comptuation of the ot cost using the ot.emd2 library. # Note: it is the Wasserstein distance to the power q. # The default numItermax=100000 is not sufficient for some examples with 5000 points, what is a good value? ot_cost = ot.emd2(a, b, M, numItermax=2000000) - return ot_cost ** (1./order) + return (ot_cost + essential_cost) ** (1./order) diff --git a/src/python/pyproject.toml b/src/python/pyproject.toml new file mode 100644 index 00000000..a9fb4985 --- /dev/null +++ b/src/python/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools", "wheel", "numpy>=1.15.0", "cython", "pybind11"] +build-backend = "setuptools.build_meta" diff --git a/src/python/setup.py.in b/src/python/setup.py.in index 759ec8d8..23746998 100644 --- a/src/python/setup.py.in +++ b/src/python/setup.py.in @@ -71,7 +71,7 @@ setup( name = 'gudhi', packages=find_packages(), # find_namespace_packages(include=["gudhi*"]) author='GUDHI Editorial Board', - author_email='gudhi-contact@lists.gforge.inria.fr', + author_email='gudhi-contact@inria.fr', version='@GUDHI_VERSION@', url='https://gudhi.inria.fr/', project_urls={ @@ -82,10 +82,10 @@ setup( }, description='The Gudhi library is an open source library for ' \ 'Computational Topology and Topological Data Analysis (TDA).', + data_files=[('.', ['./introduction.rst'])], long_description_content_type='text/x-rst', long_description=long_description, ext_modules = ext_modules, install_requires = ['numpy >= 1.15.0',], - setup_requires = ['cython','numpy >= 1.15.0','pybind11',], package_data={"": ["*.dll"], }, ) diff --git a/src/python/test/test_reader_utils.py b/src/python/test/test_reader_utils.py index 90da6651..e96e0569 100755 --- a/src/python/test/test_reader_utils.py +++ b/src/python/test/test_reader_utils.py @@ -30,7 +30,7 @@ def test_full_square_distance_matrix_csv_file(): test_file.write("0;1;2;3;\n1;0;4;5;\n2;4;0;6;\n3;5;6;0;") test_file.close() matrix = gudhi.read_lower_triangular_matrix_from_csv_file( - csv_file="full_square_distance_matrix.csv" + csv_file="full_square_distance_matrix.csv", separator=";" ) assert matrix == [[], [1.0], [2.0, 4.0], [3.0, 5.0, 6.0]] diff --git a/src/python/test/test_wasserstein_distance.py b/src/python/test/test_wasserstein_distance.py index e3b521d6..3a004d77 100755 --- a/src/python/test/test_wasserstein_distance.py +++ b/src/python/test/test_wasserstein_distance.py @@ -5,25 +5,97 @@ Copyright (C) 2019 Inria Modification(s): + - 2020/07 Théo Lacombe: Added tests about handling essential parts in diagrams. - YYYY/MM Author: Description of the modification """ -from gudhi.wasserstein.wasserstein import _proj_on_diag +from gudhi.wasserstein.wasserstein import _proj_on_diag, _finite_part, _handle_essential_parts, _get_essential_parts +from gudhi.wasserstein.wasserstein import _warn_infty from gudhi.wasserstein import wasserstein_distance as pot from gudhi.hera import wasserstein_distance as hera import numpy as np import pytest + __author__ = "Theo Lacombe" __copyright__ = "Copyright (C) 2019 Inria" __license__ = "MIT" + def test_proj_on_diag(): dgm = np.array([[1., 1.], [1., 2.], [3., 5.]]) assert np.array_equal(_proj_on_diag(dgm), [[1., 1.], [1.5, 1.5], [4., 4.]]) empty = np.empty((0, 2)) assert np.array_equal(_proj_on_diag(empty), empty) + +def test_finite_part(): + diag = np.array([[0, 1], [3, 5], [2, np.inf], [3, np.inf], [-np.inf, 8], [-np.inf, 12], [-np.inf, -np.inf], + [np.inf, np.inf], [-np.inf, np.inf], [-np.inf, np.inf]]) + assert np.array_equal(_finite_part(diag), [[0, 1], [3, 5]]) + + +def test_handle_essential_parts(): + diag1 = np.array([[0, 1], [3, 5], + [2, np.inf], [3, np.inf], + [-np.inf, 8], [-np.inf, 12], + [-np.inf, -np.inf], + [np.inf, np.inf], + [-np.inf, np.inf], [-np.inf, np.inf]]) + + diag2 = np.array([[0, 2], [3, 5], + [2, np.inf], [4, np.inf], + [-np.inf, 8], [-np.inf, 11], + [-np.inf, -np.inf], + [np.inf, np.inf], + [-np.inf, np.inf], [-np.inf, np.inf]]) + + diag3 = np.array([[0, 2], [3, 5], + [2, np.inf], [4, np.inf], [6, np.inf], + [-np.inf, 8], [-np.inf, 11], + [-np.inf, -np.inf], + [np.inf, np.inf], + [-np.inf, np.inf], [-np.inf, np.inf]]) + + c, m = _handle_essential_parts(diag1, diag2, order=1) + assert c == pytest.approx(2, 0.0001) # Note: here c is only the cost due to essential part (thus 2, not 3) + # Similarly, the matching only corresponds to essential parts. + # Note that (-inf,-inf) and (+inf,+inf) coordinates are matched to the diagonal. + assert np.array_equal(m, [[4, 4], [5, 5], [2, 2], [3, 3], [8, 8], [9, 9], [6, -1], [7, -1], [-1, 6], [-1, 7]]) + + c, m = _handle_essential_parts(diag1, diag3, order=1) + assert c == np.inf + assert (m is None) + + +def test_get_essential_parts(): + diag1 = np.array([[0, 1], [3, 5], [2, np.inf], [3, np.inf], [-np.inf, 8], [-np.inf, 12], [-np.inf, -np.inf], + [np.inf, np.inf], [-np.inf, np.inf], [-np.inf, np.inf]]) + + diag2 = np.array([[0, 1], [3, 5], [2, np.inf], [3, np.inf]]) + + res = _get_essential_parts(diag1) + res2 = _get_essential_parts(diag2) + assert np.array_equal(res[0], [4, 5]) + assert np.array_equal(res[1], [2, 3]) + assert np.array_equal(res[2], [8, 9]) + assert np.array_equal(res[3], [6] ) + assert np.array_equal(res[4], [7] ) + + assert np.array_equal(res2[0], [] ) + assert np.array_equal(res2[1], [2, 3]) + assert np.array_equal(res2[2], [] ) + assert np.array_equal(res2[3], [] ) + assert np.array_equal(res2[4], [] ) + + +def test_warn_infty(): + assert _warn_infty(matching=False)==np.inf + c, m = _warn_infty(matching=True) + assert (c == np.inf) + assert (m is None) + + def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_matching=True): diag1 = np.array([[2.7, 3.7], [9.6, 14.0], [34.2, 34.974]]) diag2 = np.array([[2.8, 4.45], [9.5, 14.1]]) @@ -64,7 +136,7 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_mat assert wasserstein_distance(diag4, diag5) == np.inf assert wasserstein_distance(diag5, diag6, order=1, internal_p=np.inf) == approx(4.) - + assert wasserstein_distance(diag5, emptydiag) == np.inf if test_matching: match = wasserstein_distance(emptydiag, emptydiag, matching=True, internal_p=1., order=2)[1] @@ -78,6 +150,31 @@ def _basic_wasserstein(wasserstein_distance, delta, test_infinity=True, test_mat match = wasserstein_distance(diag1, diag2, matching=True, internal_p=2., order=2.)[1] assert np.array_equal(match, [[0, 0], [1, 1], [2, -1]]) + if test_matching and test_infinity: + diag7 = np.array([[0, 3], [4, np.inf], [5, np.inf]]) + diag8 = np.array([[0,1], [0, np.inf], [-np.inf, -np.inf], [np.inf, np.inf]]) + diag9 = np.array([[-np.inf, -np.inf], [np.inf, np.inf]]) + diag10 = np.array([[0,1], [-np.inf, -np.inf], [np.inf, np.inf]]) + + match = wasserstein_distance(diag5, diag6, matching=True, internal_p=2., order=2.)[1] + assert np.array_equal(match, [[0, -1], [-1,0], [-1, 1], [1, 2]]) + match = wasserstein_distance(diag5, diag7, matching=True, internal_p=2., order=2.)[1] + assert (match is None) + cost, match = wasserstein_distance(diag7, emptydiag, matching=True, internal_p=2., order=2.3) + assert (cost == np.inf) + assert (match is None) + cost, match = wasserstein_distance(emptydiag, diag7, matching=True, internal_p=2.42, order=2.) + assert (cost == np.inf) + assert (match is None) + cost, match = wasserstein_distance(diag8, diag9, matching=True, internal_p=2., order=2.) + assert (cost == np.inf) + assert (match is None) + cost, match = wasserstein_distance(diag9, diag10, matching=True, internal_p=1., order=1.) + assert (cost == 1) + assert (match == [[0, -1],[1, -1],[-1, 0], [-1, 1], [-1, 2]]) # type 4 and 5 are match to the diag anyway. + cost, match = wasserstein_distance(diag9, emptydiag, matching=True, internal_p=2., order=2.) + assert (cost == 0.) + assert (match == [[0, -1], [1, -1]]) def hera_wrap(**extra): @@ -85,15 +182,19 @@ def hera_wrap(**extra): return hera(*kargs,**kwargs,**extra) return fun + def pot_wrap(**extra): def fun(*kargs,**kwargs): return pot(*kargs,**kwargs,**extra) return fun + def test_wasserstein_distance_pot(): - _basic_wasserstein(pot, 1e-15, test_infinity=False, test_matching=True) - _basic_wasserstein(pot_wrap(enable_autodiff=True), 1e-15, test_infinity=False, test_matching=False) + _basic_wasserstein(pot, 1e-15, test_infinity=False, test_matching=True) # pot with its standard args + _basic_wasserstein(pot_wrap(enable_autodiff=True, keep_essential_parts=False), 1e-15, test_infinity=False, test_matching=False) + def test_wasserstein_distance_hera(): _basic_wasserstein(hera_wrap(delta=1e-12), 1e-12, test_matching=False) _basic_wasserstein(hera_wrap(delta=.1), .1, test_matching=False) + |