diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e2c911..027ce2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,15 +8,18 @@ set(BINDINGS_DIR "src") find_package(OpenMP) pybind11_add_module(flagser_pybind "${BINDINGS_DIR}/flagser_bindings.cpp") -include_directories(.) + if(OpenMP_FOUND) target_link_libraries(flagser_pybind PRIVATE OpenMP::OpenMP_CXX) endif() -target_compile_options(flagser_pybind PUBLIC -Ofast -DNDEBUG) -target_compile_options(flagser_pybind PUBLIC $<$:-O2 -ggdb -D_GLIBCXX_DEBUG>) -# Cannot have two inlined namespace ! -if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug") - target_compile_options(flagser_pybind PUBLIC -march=native -D_GLIBCXX_PARALLEL) - target_link_libraries(flagser_pybind PRIVATE) -endif() + target_compile_definitions(flagser_pybind PRIVATE RETRIEVE_PERSISTENCE=1) +target_include_directories(flagser_pybind PRIVATE .) + +if(MSVC) + target_compile_options(flagser_pybind PUBLIC $<$: /Wall /O2>) + target_compile_options(flagser_pybind PUBLIC $<$:/O1 /DEBUG:FULL /Zi /Zo>) +else() + target_compile_options(flagser_pybind PUBLIC $<$: -Ofast>) + target_compile_options(flagser_pybind PUBLIC $<$: -O2 -ggdb -D_GLIBCXX_DEBUG>) +endif() diff --git a/RELEASE.rst b/RELEASE.rst index 6b7ed92..105d5cd 100644 --- a/RELEASE.rst +++ b/RELEASE.rst @@ -1,16 +1,79 @@ +Release 0.2.1 +============== + +Major Features and Improvements +------------------------------- + +``CMakeLists`` updated to enable compile flags on MSVC. This improves performance on Windows systems. + +Bug Fixes +--------- + +Hotfix addressing multiples issues where forwarding arguments to C++ ``flagser``: + + - ``filtration`` was not correctly forwarded and it always fallback to zero filtration. + - ``max-dim`` and ``min-dim`` were always equal to 0. + +``CMakeLists`` updated to disable AVX instructions. This addresses incompatibilities observed with specific hardware setups. + +Backwards-Incompatible Changes +------------------------------ + +None. + +Thanks to our Contributors +-------------------------- + +This release contains contributions from many people: + +Julian Burella Pérez, Umberto Lupo, and Guillaume Tauzin. + +We are also grateful to all who filed issues or helped resolve them, asked and +answered questions, and were part of inspiring discussions. + + +Release 0.2.0 +============== + +Major Features and Improvements +------------------------------- + +The ``flagser`` method now accepts ``filtration`` as an argument. All filtrations available for the C++ flagser software can be used. + +Bug Fixes +--------- + +Fixed bug related to the generation of a file by C++ ``flagser``. Whenever pyflagser's ``flagser`` method was interrupted, it would not remove the generated file, which would prevent the ``flagser`` method to be called again. + +Backwards-Incompatible Changes +------------------------------ + +None. + +Thanks to our Contributors +-------------------------- + +This release contains contributions from many people: + +Julian Burella Pérez, Umberto Lupo, and Guillaume Tauzin. + +We are also grateful to all who filed issues or helped resolve them, asked and +answered questions, and were part of inspiring discussions. + + Release 0.1.0 ============== -Initial release of pyflagser. +Initial release of ``pyflagser``. Major Features and Improvements ------------------------------- The following methods where added: -- `loadflag` enable the user to load a `.flag` file into a `scipy` or `numpy` matrix. -- `saveflag` enables the user to save a `scipy` or `numpy` matrix into a `.flag` file. -- `flagser` computes the persistent homology of directed/undirected flag complexes. +- ``loadflag`` enable the user to load a ``.flag`` file into a ``scipy`` or ``numpy`` matrix. +- ``saveflag`` enables the user to save a ``scipy`` or ``numpy`` matrix into a ``.flag`` file. +- ``flagser`` computes the persistent homology of directed/undirected flag complexes. Bug Fixes --------- diff --git a/pyflagser/_version.py b/pyflagser/_version.py index 72872a3..02440f5 100644 --- a/pyflagser/_version.py +++ b/pyflagser/_version.py @@ -17,4 +17,4 @@ # 'X.Y.dev0' is the canonical version of 'X.Y.dev' # -__version__ = '0.2.0' +__version__ = '0.2.1' diff --git a/pyflagser/tests/test_flagser.py b/pyflagser/tests/test_flagser.py index 75f2497..2563967 100644 --- a/pyflagser/tests/test_flagser.py +++ b/pyflagser/tests/test_flagser.py @@ -1,10 +1,12 @@ """Testing for the python bindings of the C++ flagser library.""" import os +import numpy as np from numpy.testing import assert_almost_equal from pyflagser import loadflag, flagser +from flagser_pybind import implemented_filtrations betti = { 'a.flag': [1, 2, 0], @@ -26,9 +28,176 @@ 'd10.flag': [1, 0, 0, 0, 0, 0, 0, 0, 0, 1334961], } +""" +Filtrations are only tested for d5.flag +""" +filtrations_results = { + 'dimension': + { + 'dgms': [ + np.array([[0., 1.], + [0., 1.], + [0., 1.], + [0., 1.], + [0., float('inf')]]), + np.array([[1., 2.], + [1., 2.], + [1., 2.], + [1., 2.], + [1., 2.], + [1., 2.]])] + }, + 'zero': + { + 'dgms': [ + np.array([[0., float('inf')]])] + }, + 'max': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.29499996, 1.34000003], + [1.20000005, 1.65499997]])] + }, + 'max3': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.29499996, 1.34000003], + [1.20000005, 1.65499997]])] + }, + 'max_plus_one': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.76999998, 2.76999998], + [1.755, 2.75500011], + [1.65499997, 2.65499997], + [1.34000003, 2.34000015], + [1.29499996, 2.34000015], + [1.20000005, 2.65499997]])] + }, + 'product': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.76999998, 2.04691648], + [1.755, 1.99411869], + [1.65499997, 1.97242892], + [1.34000003, 1.77884996], + [1.29499996, 2.08236003], + [1.20000005, 2.27397013]])] + }, + 'sum': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.76999998, 3.92499995], + [1.755, 3.8900001], + [1.65499997, 3.84500003], + [1.34000003, 3.64499998], + [1.29499996, 3.83500004], + [1.20000005, 4.]])] + }, + 'pmean': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.76999998, 2.54917502], + [1.755, 2.52713633], + [1.65499997, 2.41156578], + [1.34000003, 2.04345369], + [1.29499996, 2.07881474], + [1.20000005, 2.43602848]])] + }, + 'pmoment': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.76999998, 1.9275918], + [1.755, 1.85709834], + [1.65499997, 1.7869581], + [1.34000003, 1.37369251], + [1.29499996, 1.39265192], + [1.20000005, 1.81259179]])] + }, + 'remove_edges': + { + 'dgms': [ + np.array([[0.44, 1.00999999], + [0.36000001, 1.125], + [0.33000001, 1.14499998], + [0.88999999, 1.17999995], + [0.11, float('inf')]]), + np.array([[1.29499996, 1.34000003], + [1.20000005, 1.65499997]])] + }, +} + def test_flagser(flag_file): betti_exp = betti[os.path.split(flag_file)[1]] flag_matrix = loadflag(flag_file) betti_res = flagser(flag_matrix)['betti'] assert_almost_equal(betti_res, betti_exp) + + +def are_matrix_equal(m1, m2): + for i in range(min(len(m1), len(m2))): + m1f = np.array(m1[i]).flatten() + m2f = np.array(m2[i]).flatten() + if not np.isclose(m1f, m2f).all(): + return False + return True + + +def test_filtrations(flag_file): + """ + Testing all filtrations available for dataset d5.flag + vertex_degree filtrations was disable because it produces a segmentation + fault. + """ + if os.path.split(flag_file)[1] == 'd5.flag': + flag_matrix = loadflag(flag_file) + for filtration in implemented_filtrations: + if filtration not in ['vertex_degree']: + assert filtration in filtrations_results.keys(),\ + "Test for {} is not implemented, current implemented tests\ + are {}".format(filtration, filtrations_results.keys()) + res = flagser(flag_matrix, max_dimension=1, directed=False, + filtration=filtration) + for filt, tests in filtrations_results.items(): + if filtration == filt: + tmp = np.array(res['dgms']).tolist() + tmp2 = np.array(tests['dgms']).tolist() + assert are_matrix_equal(tmp, tmp2), \ + "diagrams {} \n and {} \n are not equal"\ + .format(tmp, tmp2) diff --git a/src/flagser_bindings.cpp b/src/flagser_bindings.cpp index 3c77b29..8c52123 100644 --- a/src/flagser_bindings.cpp +++ b/src/flagser_bindings.cpp @@ -43,6 +43,9 @@ PYBIND11_MODULE(flagser_pybind, m) { std::string filtration) { // Save std::cout status auto cout_buff = std::cout.rdbuf(); + named_arguments_t named_arguments; + std::string str_max; + std::string str_min; HAS_EDGE_FILTRATION has_edge_filtration = HAS_EDGE_FILTRATION::TOO_EARLY_TO_DECIDE; @@ -58,10 +61,12 @@ PYBIND11_MODULE(flagser_pybind, m) { if (max_dim < 0) effective_max_dim = std::numeric_limits::max(); - named_arguments_t named_arguments; + str_max = std::to_string(effective_max_dim); + str_min = std::to_string(min_dim); + named_arguments["out"] = "output_flagser_file"; - named_arguments["--max-dim"] = std::to_string(effective_max_dim).c_str(); - named_arguments["--min-dim"] = std::to_string(min_dim).c_str(); + named_arguments["max-dim"] = str_max.c_str(); + named_arguments["min-dim"] = str_min.c_str(); // Is filtration supported ? if (std::find(custom_filtration_computer.begin(), @@ -80,7 +85,7 @@ PYBIND11_MODULE(flagser_pybind, m) { } } - named_arguments["--filtration"] = filtration.c_str(); + named_arguments["filtration"] = filtration.c_str(); remove(named_arguments["out"]);