diff options
21 files changed, 656 insertions, 68 deletions
diff --git a/data/correlation_matrix/lower_triangular_correlation_matrix.csv b/data/correlation_matrix/lower_triangular_correlation_matrix.csv new file mode 100644 index 00000000..99ad0b5d --- /dev/null +++ b/data/correlation_matrix/lower_triangular_correlation_matrix.csv @@ -0,0 +1,6 @@ + +0.4090538938 +0.2182708406;0.5664245836 +0.9109757412;0.5234453492;0.4239008464 +0.2426856242;0.7178816327;0.4748826202;0.8254894051 +0.0908790566;0.9369574252;0.9760741671;0.5256838992;0.0653515265 diff --git a/src/Bitmap_cubical_complex/include/gudhi/Bitmap_cubical_complex.h b/src/Bitmap_cubical_complex/include/gudhi/Bitmap_cubical_complex.h index 969daba6..770eb55f 100644 --- a/src/Bitmap_cubical_complex/include/gudhi/Bitmap_cubical_complex.h +++ b/src/Bitmap_cubical_complex/include/gudhi/Bitmap_cubical_complex.h @@ -383,7 +383,7 @@ class Bitmap_cubical_complex : public T { std::vector<std::size_t> bdry = this->get_boundary_of_a_cell(sh); if (globalDbg) { std::cerr << "std::pair<Simplex_handle, Simplex_handle> endpoints( Simplex_handle sh )\n"; - std::cerr << "bdry.size() : " << bdry.size() << std::endl; + std::cerr << "bdry.size() : " << bdry.size() << "\n"; } // this method returns two first elements from the boundary of sh. if (bdry.size() < 2) diff --git a/src/Persistent_cohomology/doc/Intro_persistent_cohomology.h b/src/Persistent_cohomology/doc/Intro_persistent_cohomology.h index 4dbe82c7..3113a22c 100644 --- a/src/Persistent_cohomology/doc/Intro_persistent_cohomology.h +++ b/src/Persistent_cohomology/doc/Intro_persistent_cohomology.h @@ -162,6 +162,19 @@ persistence diagram with a family of field coefficients. Rips_complex/rips_distance_matrix_persistence.cpp</a> computes the Rips complex of a distance matrix and outputs its persistence diagram. +The file should contain square or lower triangular distance matrix with semicolons as separators. +The code do not check if it is dealing with a distance matrix. It is the user responsibility to provide a valid input. +Please refer to data/distance_matrix/lower_triangular_distance_matrix.csv for an example of a file. + +\li <a href="_rips_complex_2rips_correlation_matrix_persistence_8cpp-example.html"> +Rips_complex/rips_correlation_matrix_persistence.cpp</a> +computes the Rips complex of a correlation matrix and outputs its persistence diagram. + +Note that no check is performed if the matrix given as the input is a correlation matrix. +It is the user responsibility to ensure that this is the case. The input is to be given either as a square or a lower +triangular matrix. +Please refer to data/correlation_matrix/lower_triangular_correlation_matrix.csv for an example of a file. + \li <a href="_alpha_complex_2alpha_complex_3d_persistence_8cpp-example.html"> Alpha_complex/alpha_complex_3d_persistence.cpp</a> computes the persistent homology with \f$\mathbb{Z}/2\mathbb{Z}\f$ coefficients of the alpha complex on points sampling from an OFF file. diff --git a/src/Rips_complex/doc/Intro_rips_complex.h b/src/Rips_complex/doc/Intro_rips_complex.h index 8c517516..496c4218 100644 --- a/src/Rips_complex/doc/Intro_rips_complex.h +++ b/src/Rips_complex/doc/Intro_rips_complex.h @@ -146,6 +146,33 @@ namespace rips_complex { * * \include Rips_complex/full_skeleton_rips_for_doc.txt * + * + * \section ripscorrelationematrix Correlation matrix + * + * Analogously to the case of distance matrix, Rips complexes can be also constructed based on correlation matrix. + * Given a correlation matrix M, comportment-wise 1-M is a distance matrix. + * This example builds the one skeleton graph from the given corelation matrix and threshold value. + * Then it creates a `Simplex_tree` with it. + * + * Then, it is asked to display information about the simplicial complex. + * + * \include Rips_complex/example_one_skeleton_rips_from_correlation_matrix.cpp + * + * When launching: + * + * \code $> ./example_one_skeleton_from_correlation_matrix + * \endcode + * + * the program output is: + * + * \include Rips_complex/one_skeleton_rips_from_correlation_matrix_for_doc.txt + * + * All the other constructions discussed for Rips complex for distance matrix can be also performed for Rips complexes + * construction from correlation matrices. + * + * @warning As persistence diagrams points will be under the diagonal, bottleneck distance and persistence graphical + * tool will not work properly, this is a known issue. + * */ /** @} */ // end defgroup rips_complex diff --git a/src/Rips_complex/example/CMakeLists.txt b/src/Rips_complex/example/CMakeLists.txt index 2940f164..fcb1eaee 100644 --- a/src/Rips_complex/example/CMakeLists.txt +++ b/src/Rips_complex/example/CMakeLists.txt @@ -11,17 +11,23 @@ add_executable ( Rips_complex_example_one_skeleton_from_distance_matrix example_ add_executable ( Rips_complex_example_from_csv_distance_matrix example_rips_complex_from_csv_distance_matrix_file.cpp ) +# Correlation matrix +add_executable ( Rips_complex_example_one_skeleton_rips_from_correlation_matrix example_one_skeleton_rips_from_correlation_matrix.cpp ) + if (TBB_FOUND) target_link_libraries(Rips_complex_example_from_off ${TBB_LIBRARIES}) target_link_libraries(Rips_complex_example_one_skeleton_from_points ${TBB_LIBRARIES}) target_link_libraries(Rips_complex_example_one_skeleton_from_distance_matrix ${TBB_LIBRARIES}) target_link_libraries(Rips_complex_example_from_csv_distance_matrix ${TBB_LIBRARIES}) + target_link_libraries(Rips_complex_example_one_skeleton_rips_from_correlation_matrix ${TBB_LIBRARIES}) endif() add_test(NAME Rips_complex_example_one_skeleton_from_points COMMAND $<TARGET_FILE:Rips_complex_example_one_skeleton_from_points>) add_test(NAME Rips_complex_example_one_skeleton_from_distance_matrix COMMAND $<TARGET_FILE:Rips_complex_example_one_skeleton_from_distance_matrix>) +add_test(NAME Rips_complex_example_one_skeleton_rips_from_correlation_matrix + COMMAND $<TARGET_FILE:Rips_complex_example_one_skeleton_rips_from_correlation_matrix>) add_test(NAME Rips_complex_example_from_off_doc_12_1 COMMAND $<TARGET_FILE:Rips_complex_example_from_off> "${CMAKE_SOURCE_DIR}/data/points/alphacomplexdoc.off" "12.0" "1" "${CMAKE_CURRENT_BINARY_DIR}/ripsoffreader_result_12_1.txt") @@ -57,3 +63,4 @@ install(TARGETS Rips_complex_example_from_off DESTINATION bin) install(TARGETS Rips_complex_example_one_skeleton_from_points DESTINATION bin) install(TARGETS Rips_complex_example_one_skeleton_from_distance_matrix DESTINATION bin) install(TARGETS Rips_complex_example_from_csv_distance_matrix DESTINATION bin) +install(TARGETS Rips_complex_example_one_skeleton_rips_from_correlation_matrix DESTINATION bin) diff --git a/src/Rips_complex/example/example_one_skeleton_rips_from_correlation_matrix.cpp b/src/Rips_complex/example/example_one_skeleton_rips_from_correlation_matrix.cpp new file mode 100644 index 00000000..a34ce15c --- /dev/null +++ b/src/Rips_complex/example/example_one_skeleton_rips_from_correlation_matrix.cpp @@ -0,0 +1,83 @@ +#include <gudhi/Rips_complex.h> +#include <gudhi/Simplex_tree.h> +#include <gudhi/distance_functions.h> + +#include <iostream> +#include <string> +#include <vector> +#include <limits> // for std::numeric_limits + +int main() { + // Type definitions + using Simplex_tree = Gudhi::Simplex_tree<>; + using Filtration_value = Simplex_tree::Filtration_value; + using Rips_complex = Gudhi::rips_complex::Rips_complex<Filtration_value>; + using Distance_matrix = std::vector<std::vector<Filtration_value>>; + + // User defined correlation matrix is: + // |1 0.06 0.23 0.01 0.89| + // |0.06 1 0.74 0.01 0.61| + // |0.23 0.74 1 0.72 0.03| + // |0.01 0.01 0.72 1 0.7 | + // |0.89 0.61 0.03 0.7 1 | + + Distance_matrix correlations; + correlations.push_back({}); + correlations.push_back({0.06}); + correlations.push_back({0.23, 0.74}); + correlations.push_back({0.01, 0.01, 0.72}); + correlations.push_back({0.89, 0.61, 0.03, 0.7}); + + // ---------------------------------------------------------------------------- + // Convert correlation matrix to a distance matrix: + // ---------------------------------------------------------------------------- + double threshold = 0; + for (size_t i = 0; i != correlations.size(); ++i) { + for (size_t j = 0; j != correlations[i].size(); ++j) { + //Here we check if our data comes from corelation matrix. + if ( (correlations[i][j]<-1) || (correlations[i][j]>1) ) + { + std::cerr << "The input matrix is not a correlation matrix. The program will now terminate.\n"; + throw "The input matrix is not a correlation matrix. The program will now terminate.\n"; + } + correlations[i][j] = 1 - correlations[i][j]; + //Here we make sure that we will get the treshold value equal to maximal + //distance in the matrix. + if ( correlations[i][j] > threshold )threshold = correlations[i][j]; + } + } + + //----------------------------------------------------------------------------- + // Now the correlation matrix is a distance matrix and can be processed further. + //----------------------------------------------------------------------------- + Distance_matrix distances = correlations; + + + Rips_complex rips_complex_from_points(distances, threshold); + + Simplex_tree stree; + rips_complex_from_points.create_complex(stree, 1); + // ---------------------------------------------------------------------------- + // Display information about the one skeleton Rips complex. Note that + // the filtration displayed here comes from the distance matrix computed + // above, which is 1 - initial correlation matrix. Only this way, we obtain + // a complex with filtration. If a correlation matrix is used instead, we would + // have a reverse filtration (i.e. filtration of boundary of each simplex S + // is greater or equal to the filtration of S). + // ---------------------------------------------------------------------------- + std::cout << "Rips complex is of dimension " << stree.dimension() << " - " << stree.num_simplices() << " simplices - " + << stree.num_vertices() << " vertices." << std::endl; + + std::cout << "Iterator on Rips complex simplices in the filtration order, with [filtration value]:" << std::endl; + for (auto f_simplex : stree.filtration_simplex_range()) { + std::cout << " ( "; + for (auto vertex : stree.simplex_vertex_range(f_simplex)) { + std::cout << vertex << " "; + } + std::cout << ") -> " + << "[" << stree.filtration(f_simplex) << "] "; + std::cout << std::endl; + } + + return 0; +} diff --git a/src/Rips_complex/example/one_skeleton_rips_from_correlation_matrix_for_doc.txt b/src/Rips_complex/example/one_skeleton_rips_from_correlation_matrix_for_doc.txt new file mode 100644 index 00000000..640d7083 --- /dev/null +++ b/src/Rips_complex/example/one_skeleton_rips_from_correlation_matrix_for_doc.txt @@ -0,0 +1,17 @@ +Rips complex is of dimension 1 - 15 simplices - 5 vertices. +Iterator on Rips complex simplices in the filtration order, with [filtration value]: + ( 0 ) -> [0] + ( 1 ) -> [0] + ( 2 ) -> [0] + ( 3 ) -> [0] + ( 4 ) -> [0] + ( 4 0 ) -> [0.11] + ( 2 1 ) -> [0.26] + ( 3 2 ) -> [0.28] + ( 4 3 ) -> [0.3] + ( 4 1 ) -> [0.39] + ( 2 0 ) -> [0.77] + ( 1 0 ) -> [0.94] + ( 4 2 ) -> [0.97] + ( 3 0 ) -> [0.99] + ( 3 1 ) -> [0.99] diff --git a/src/Rips_complex/utilities/CMakeLists.txt b/src/Rips_complex/utilities/CMakeLists.txt index baa571fa..b99fc808 100644 --- a/src/Rips_complex/utilities/CMakeLists.txt +++ b/src/Rips_complex/utilities/CMakeLists.txt @@ -7,9 +7,13 @@ target_link_libraries(rips_distance_matrix_persistence ${Boost_PROGRAM_OPTIONS_L add_executable(rips_persistence rips_persistence.cpp) target_link_libraries(rips_persistence ${Boost_PROGRAM_OPTIONS_LIBRARY}) +add_executable(rips_correlation_matrix_persistence rips_correlation_matrix_persistence.cpp) +target_link_libraries(rips_correlation_matrix_persistence ${Boost_SYSTEM_LIBRARY} ${Boost_PROGRAM_OPTIONS_LIBRARY}) + if (TBB_FOUND) target_link_libraries(rips_distance_matrix_persistence ${TBB_LIBRARIES}) target_link_libraries(rips_persistence ${TBB_LIBRARIES}) + target_link_libraries(rips_correlation_matrix_persistence ${TBB_LIBRARIES}) endif() add_test(NAME Rips_complex_utility_from_rips_distance_matrix COMMAND $<TARGET_FILE:rips_distance_matrix_persistence> @@ -17,5 +21,9 @@ add_test(NAME Rips_complex_utility_from_rips_distance_matrix COMMAND $<TARGET_FI add_test(NAME Rips_complex_utility_from_rips_on_tore_3D COMMAND $<TARGET_FILE:rips_persistence> "${CMAKE_SOURCE_DIR}/data/points/tore3D_1307.off" "-r" "0.25" "-m" "0.5" "-d" "3" "-p" "3") +add_test(NAME Rips_complex_utility_from_rips_correlation_matrix COMMAND $<TARGET_FILE:rips_correlation_matrix_persistence> + "${CMAKE_SOURCE_DIR}/data/correlation_matrix/lower_triangular_correlation_matrix.csv" "-c" "0.3" "-d" "3" "-p" "3" "-m" "0") + install(TARGETS rips_distance_matrix_persistence DESTINATION bin) install(TARGETS rips_persistence DESTINATION bin) +install(TARGETS rips_correlation_matrix_persistence DESTINATION bin) diff --git a/src/Rips_complex/utilities/rips_correlation_matrix_persistence.cpp b/src/Rips_complex/utilities/rips_correlation_matrix_persistence.cpp new file mode 100644 index 00000000..95bce491 --- /dev/null +++ b/src/Rips_complex/utilities/rips_correlation_matrix_persistence.cpp @@ -0,0 +1,172 @@ +/* This file is part of the Gudhi Library. The Gudhi library + * (Geometric Understanding in Higher Dimensions) is a generic C++ + * library for computational topology. + * + * Author(s): Pawel Dlotko, Vincent Rouvreau + * + * Copyright (C) 2016 INRIA + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <gudhi/Rips_complex.h> +#include <gudhi/Simplex_tree.h> +#include <gudhi/Persistent_cohomology.h> +#include <gudhi/reader_utils.h> +#include <gudhi/writing_persistence_to_file.h> + +#include <boost/program_options.hpp> + +#include <string> +#include <vector> +#include <limits> // infinity + +// Types definition +using Simplex_tree = Gudhi::Simplex_tree<Gudhi::Simplex_tree_options_fast_persistence>; +using Filtration_value = Simplex_tree::Filtration_value; +using Rips_complex = Gudhi::rips_complex::Rips_complex<Filtration_value>; +using Field_Zp = Gudhi::persistent_cohomology::Field_Zp; +using Persistent_cohomology = Gudhi::persistent_cohomology::Persistent_cohomology<Simplex_tree, Field_Zp>; +using Correlation_matrix = std::vector<std::vector<Filtration_value>>; +using intervals_common = Gudhi::Persistence_interval_common<double, int>; + +void program_options(int argc, char* argv[], std::string& csv_matrix_file, std::string& filediag, + Filtration_value& correlation_min, int& dim_max, int& p, Filtration_value& min_persistence); + +int main(int argc, char* argv[]) { + std::string csv_matrix_file; + std::string filediag; + Filtration_value correlation_min; + int dim_max; + int p; + Filtration_value min_persistence; + + program_options(argc, argv, csv_matrix_file, filediag, correlation_min, dim_max, p, min_persistence); + + Correlation_matrix correlations = + Gudhi::read_lower_triangular_matrix_from_csv_file<Filtration_value>(csv_matrix_file); + + Filtration_value threshold = 0; + + // Given a correlation matrix M, we compute component-wise M'[i,j] = 1-M[i,j] to get a distance matrix: + for (size_t i = 0; i != correlations.size(); ++i) { + for (size_t j = 0; j != correlations[i].size(); ++j) { + correlations[i][j] = 1 - correlations[i][j]; + //Here we make sure that the values of corelations lie between -1 and 1. + //If not, we throw an exception. + if ((correlations[i][j] < -1) || (correlations[i][j] > 1)) + { + std::cerr << "The input matrix is not a correlation matrix. The program will now terminate. \n"; + throw "The input matrix is not a correlation matrix. The program will now terminate. \n"; + } + if ( correlations[i][j] > threshold ) threshold = correlations[i][j]; + } + } + + Rips_complex rips_complex_from_file(correlations, threshold); + + // Construct the Rips complex in a Simplex Tree + Simplex_tree simplex_tree; + + rips_complex_from_file.create_complex(simplex_tree, dim_max); + std::cout << "The complex contains " << simplex_tree.num_simplices() << " simplices \n"; + std::cout << " and has dimension " << simplex_tree.dimension() << " \n"; + + // Sort the simplices in the order of the filtration + simplex_tree.initialize_filtration(); + + // Compute the persistence diagram of the complex + Persistent_cohomology pcoh(simplex_tree); + // initializes the coefficient field for homology + pcoh.init_coefficients(p); + //compute persistence + pcoh.compute_persistent_cohomology(min_persistence); + + + //invert the persistence diagram. The reason for this procedure is the following: + //The input to the program is a corelation matrix M. When processing it, it is + //turned into 1-M and the obtained persistence intervals are in '1-M' units. + //Below we reverse every (birth,death) pair into (1-birth, 1-death) pair + //so that the input and the output to the program is expressed in the same + //units. + auto pairs = pcoh.get_persistent_pairs(); + std::vector<intervals_common> processed_persistence_intervals; + processed_persistence_intervals.reserve(pairs.size()); + for (auto pair : pairs) { + double birth = 1 - simplex_tree.filtration(get<0>(pair)); + double death = 1 - simplex_tree.filtration(get<1>(pair)); + unsigned dimension = (unsigned)simplex_tree.dimension(get<0>(pair)); + int field = get<2>(pair); + processed_persistence_intervals.push_back(intervals_common(birth, death, dimension, field)); + } + + // sort the processed intervals: + std::sort(processed_persistence_intervals.begin(), processed_persistence_intervals.end()); + + // and write them to a file + if (filediag.empty()) { + write_persistence_intervals_to_stream(processed_persistence_intervals); + } else { + std::ofstream out(filediag); + write_persistence_intervals_to_stream(processed_persistence_intervals, out); + } + return 0; +} + +void program_options(int argc, char* argv[], std::string& csv_matrix_file, std::string& filediag, + Filtration_value& correlation_min, int& dim_max, int& p, Filtration_value& min_persistence) { + namespace po = boost::program_options; + po::options_description hidden("Hidden options"); + hidden.add_options()( + "input-file", po::value<std::string>(&csv_matrix_file), + "Name of file containing a corelation matrix. Can be square or lower triangular matrix. Separator is ';'."); + po::options_description visible("Allowed options", 100); + visible.add_options()("help,h", "produce help message")( + "output-file,o", po::value<std::string>(&filediag)->default_value(std::string()), + "Name of file in which the persistence diagram is written. Default print in std::cout")( + "min-edge-corelation,c", po::value<Filtration_value>(&correlation_min)->default_value(0), + "Minimal corelation of an edge for the Rips complex construction.")( + "cpx-dimension,d", po::value<int>(&dim_max)->default_value(1), + "Maximal dimension of the Rips complex we want to compute.")( + "field-charac,p", po::value<int>(&p)->default_value(11), + "Characteristic p of the coefficient field Z/pZ for computing homology.")( + "min-persistence,m", po::value<Filtration_value>(&min_persistence), + "Minimal lifetime of homology feature to be recorded. Default is 0. Enter a negative value to see zero length " + "intervals"); + + po::positional_options_description pos; + pos.add("input-file", 1); + + po::options_description all; + all.add(visible).add(hidden); + + po::variables_map vm; + po::store(po::command_line_parser(argc, argv).options(all).positional(pos).run(), vm); + po::notify(vm); + + if (vm.count("help") || !vm.count("input-file")) { + std::cout << std::endl; + std::cout << "Compute the persistent homology with coefficient field Z/pZ \n"; + std::cout << "of a Rips complex defined on a corelation matrix.\n \n"; + std::cout << "The output diagram contains one bar per line, written with the convention: \n"; + std::cout << " p dim b d \n"; + std::cout << "where dim is the dimension of the homological feature,\n"; + std::cout << "b and d are respectively the birth and death of the feature and \n"; + std::cout << "p is the characteristic of the field Z/pZ used for homology coefficients." << std::endl << std::endl; + + std::cout << "Usage: " << argv[0] << " [options] input-file" << std::endl << std::endl; + std::cout << visible << std::endl; + std::abort(); + } +} diff --git a/src/Rips_complex/utilities/ripscomplex.md b/src/Rips_complex/utilities/ripscomplex.md index c1f2210e..d4dbc36e 100644 --- a/src/Rips_complex/utilities/ripscomplex.md +++ b/src/Rips_complex/utilities/ripscomplex.md @@ -49,11 +49,37 @@ Same as `rips_persistence` but taking a distance matrix as input. **Usage** -`rips_persistence [options] <CSV input file>` +`rips_distance_matrix_persistence [options] <CSV input file>` where `<CSV input file>` is the path to the file containing a distance matrix. Can be square or lower triangular matrix. Separator is ';'. +The code do not check if it is dealing with a distance matrix. It is the user responsibility to provide a valid input. +Please refer to data/distance_matrix/lower_triangular_distance_matrix.csv for an example of a file. **Example** `rips_distance_matrix_persistence data/distance_matrix/full_square_distance_matrix.csv -r 15 -d 3 -p 3 -m 0` + + +## rips_correlation_matrix_persistence ## + +Same as `rips_distance_matrix_persistence` but taking a correlation matrix as input. + +**Usage** + +`rips_correlation_matrix_persistence [options] <CSV input file>` + +where +`<CSV input file>` is the path to the file containing a correlation matrix. Can be square or lower triangular matrix. Separator is ';'. +Note that no check is performed if the matrix given as the input is a correlation matrix. +It is the user responsibility to ensure that this is the case. +Please refer to data/correlation_matrix/lower_triangular_correlation_matrix.csv for an example of a file. + +**Example** + +`rips_correlation_matrix_persistence data/distance_matrix/full_square_distance_matrix.csv -r 15 -d 3 -p 3 -m 0` + +**Warning** + +As persistence diagrams points will be under the diagonal, bottleneck distance and persistence graphical tool will not work +properly, this is a known issue. diff --git a/src/common/include/gudhi/writing_persistence_to_file.h b/src/common/include/gudhi/writing_persistence_to_file.h new file mode 100644 index 00000000..5020b5fb --- /dev/null +++ b/src/common/include/gudhi/writing_persistence_to_file.h @@ -0,0 +1,116 @@ +/* This file is part of the Gudhi Library. The Gudhi library + * (Geometric Understanding in Higher Dimensions) is a generic C++ + * library for computational topology. + * + * Author(s): Pawel Dlotko + * + * Copyright (C) 2017 Swansea University, UK + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef WRITING_PERSISTENCE_TO_FILE_H +#define WRITING_PERSISTENCE_TO_FILE_H + +#include <iostream> +#include <string> +#include <limits> + +namespace Gudhi { + +/** +* This is a class to store persistence intervals. Its main purpose is to +* exchange data in between different packages and provide unified way +* of writing a collection of persistence intervals to file. +**/ +template <typename Filtration_type, typename Coefficient_field> +class Persistence_interval_common { + public: + /** + * Constructor taking as an input birth and death of the pair. + **/ + Persistence_interval_common(Filtration_type birth, Filtration_type death) + : birth_(birth), + death_(death), + dimension_(std::numeric_limits<unsigned>::max), + arith_element_(std::numeric_limits<Coefficient_field>::max()) {} + + /** + * Constructor taking as an input birth, death and dimension of the pair. + **/ + Persistence_interval_common(Filtration_type birth, Filtration_type death, unsigned dim) + : birth_(birth), death_(death), dimension_(dim), arith_element_(std::numeric_limits<Coefficient_field>::max()) {} + + /** +* Constructor taking as an input birth, death, dimension of the pair as well +* as the number p such that this interval is present over Z_p field. +**/ + Persistence_interval_common(Filtration_type birth, Filtration_type death, unsigned dim, Coefficient_field field) + : birth_(birth), death_(death), dimension_(dim), arith_element_(field) {} + + /** + * Operator to compare two persistence pairs. During the comparision all the + * fields: birth, death, dimensiona and arith_element_ are taken into account + * and they all have to be equal for two pairs to be equal. + **/ + inline bool operator==(const Persistence_interval_common& i2) { + return ((this->birth_ == i2.birth_) && (this->death_ == i2.death_) && (this->dimension_ == i2.dimension_) && + (this->arith_element_ == i2.arith_element_)); + } + + /** + * Check if two persistence paris are not equal. + **/ + inline bool operator!=(const Persistence_interval_common& i2) { return (!((*this) == i2)); } + + /** + * Operator to compare objects of a type Persistence_interval_common. + * One intervals is smaller than the other if it has lower persistence. + * Note that this operator do not take Arith_element into account when doing comparisions. + **/ + inline bool operator<(const Persistence_interval_common& i2) { + return fabs(this->death_ - this->birth_) < fabs(i2.death_ - i2.birth_); + } + + friend std::ostream& operator<<(std::ostream& out, const Persistence_interval_common& it) { + if (it.arith_element_ != std::numeric_limits<Coefficient_field>::max()) { + out << it.arith_element_ << " "; + } + if (it.dimension_ != std::numeric_limits<unsigned>::max()) { + out << it.dimension_ << " "; + } + out << it.birth_ << " " << it.death_ << " "; + return out; + } + + private: + Filtration_type birth_; + Filtration_type death_; + unsigned dimension_; + Coefficient_field arith_element_; +}; + +/** + * This function write a vector<Persistence_interval_common> to a stream +**/ +template <typename Persistence_interval_range> +void write_persistence_intervals_to_stream(const Persistence_interval_range& intervals, + std::ostream& out = std::cout) { + for (auto interval : intervals) { + out << interval << "\n"; + } +} +} + +#endif // WRITING_PERSISTENCE_TO_FILE_H diff --git a/src/cython/cython/off_reader.pyx b/src/cython/cython/off_reader.pyx index b6e107ef..266dae2c 100644 --- a/src/cython/cython/off_reader.pyx +++ b/src/cython/cython/off_reader.pyx @@ -46,4 +46,5 @@ def read_off(off_file=''): return read_points_from_OFF_file(str.encode(off_file)) else: print("file " + off_file + " not found.") + return [] diff --git a/src/cython/cython/rips_complex.pyx b/src/cython/cython/rips_complex.pyx index ad9b0a4d..73b154b8 100644 --- a/src/cython/cython/rips_complex.pyx +++ b/src/cython/cython/rips_complex.pyx @@ -34,8 +34,6 @@ __license__ = "GPL v3" cdef extern from "Rips_complex_interface.h" namespace "Gudhi": cdef cppclass Rips_complex_interface "Gudhi::rips_complex::Rips_complex_interface": Rips_complex_interface(vector[vector[double]] values, double threshold, bool euclidean) - # bool from_file is a workaround for cython to find the correct signature - Rips_complex_interface(string file_name, double threshold, bool euclidean, bool from_file) void create_simplex_tree(Simplex_tree_interface_full_featured* simplex_tree, int dim_max) # RipsComplex python interface @@ -49,7 +47,7 @@ cdef class RipsComplex: cdef Rips_complex_interface * thisptr # Fake constructor that does nothing but documenting the constructor - def __init__(self, points=None, off_file='', distance_matrix=None, csv_file='', max_edge_length=float('inf')): + def __init__(self, points=None, distance_matrix=None, max_edge_length=float('inf')): """RipsComplex constructor. :param max_edge_length: Rips value. @@ -60,41 +58,14 @@ cdef class RipsComplex: Or - :param off_file: An OFF file style name. - :type off_file: string - - Or - :param distance_matrix: A distance matrix (full square or lower triangular). :type points: list of list of double - - Or - - :param csv_file: A csv file style name containing a full square or a - lower triangular distance matrix. - :type csv_file: string """ # The real cython constructor - def __cinit__(self, points=None, off_file='', distance_matrix=None, csv_file='', max_edge_length=float('inf')): - if off_file is not '': - if os.path.isfile(off_file): - self.thisptr = new Rips_complex_interface(str.encode(off_file), - max_edge_length, - True, - True) - else: - print("file " + off_file + " not found.") - elif csv_file is not '': - if os.path.isfile(csv_file): - self.thisptr = new Rips_complex_interface(str.encode(csv_file), - max_edge_length, - False, - True) - else: - print("file " + csv_file + " not found.") - elif distance_matrix is not None: + def __cinit__(self, points=None, distance_matrix=None, max_edge_length=float('inf')): + if distance_matrix is not None: self.thisptr = new Rips_complex_interface(distance_matrix, max_edge_length, False) else: if points is None: diff --git a/src/cython/doc/persistence_graphical_tools_user.rst b/src/cython/doc/persistence_graphical_tools_user.rst index 9033331f..a5523d23 100644 --- a/src/cython/doc/persistence_graphical_tools_user.rst +++ b/src/cython/doc/persistence_graphical_tools_user.rst @@ -58,8 +58,8 @@ This function can display the persistence result as a diagram: import gudhi - rips_complex = gudhi.RipsComplex(off_file=gudhi.__root_source_dir__ + \ - '/data/points/tore3D_1307.off', max_edge_length=0.2) + point_cloud = gudhi.read_off(off_file=gudhi.__root_source_dir__ + '/data/points/tore3D_1307.off') + rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=0.2) simplex_tree = rips_complex.create_simplex_tree(max_dimension=3) diag = simplex_tree.persistence() plt = gudhi.plot_persistence_diagram(diag, band_boot=0.13) @@ -69,8 +69,8 @@ This function can display the persistence result as a diagram: import gudhi - rips_complex = gudhi.RipsComplex(off_file=gudhi.__root_source_dir__ + \ - '/data/points/tore3D_1307.off', max_edge_length=0.2) + point_cloud = gudhi.read_off(off_file=gudhi.__root_source_dir__ + '/data/points/tore3D_1307.off') + rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=0.2) simplex_tree = rips_complex.create_simplex_tree(max_dimension=3) diag = simplex_tree.persistence() plt = gudhi.plot_persistence_diagram(diag, band_boot=0.13) diff --git a/src/cython/doc/pyplots/diagram_persistence.py b/src/cython/doc/pyplots/diagram_persistence.py index c2fbf801..ac20bf47 100755 --- a/src/cython/doc/pyplots/diagram_persistence.py +++ b/src/cython/doc/pyplots/diagram_persistence.py @@ -1,7 +1,8 @@ import gudhi -rips_complex = gudhi.RipsComplex(off_file=gudhi.__root_source_dir__ + \ - '/data/points/tore3D_1307.off', max_edge_length=0.2) +point_cloud = gudhi.read_off(off_file=gudhi.__root_source_dir__ + \ + '/data/points/tore3D_1307.off') +rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=0.2) simplex_tree = rips_complex.create_simplex_tree(max_dimension=3) diag = simplex_tree.persistence() plt = gudhi.plot_persistence_diagram(diag, band_boot=0.13) diff --git a/src/cython/doc/rips_complex_user.rst b/src/cython/doc/rips_complex_user.rst index 96ba9944..7738aef0 100644 --- a/src/cython/doc/rips_complex_user.rst +++ b/src/cython/doc/rips_complex_user.rst @@ -101,8 +101,8 @@ Finally, it is asked to display information about the Rips complex. .. testcode:: import gudhi - rips_complex = gudhi.RipsComplex(off_file=gudhi.__root_source_dir__ + \ - '/data/points/alphacomplexdoc.off', max_edge_length=12.0) + point_cloud = gudhi.read_off(off_file=gudhi.__root_source_dir__ + '/data/points/alphacomplexdoc.off') + rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=12.0) simplex_tree = rips_complex.create_simplex_tree(max_dimension=1) result_str = 'Rips complex is of dimension ' + repr(simplex_tree.dimension()) + ' - ' + \ repr(simplex_tree.num_simplices()) + ' simplices - ' + \ @@ -197,7 +197,7 @@ Example from csv file ^^^^^^^^^^^^^^^^^^^^^ This example builds the :doc:`Rips_complex <rips_complex_ref>` from the given -points in an OFF file, and max_edge_length value. +distance matrix in a csv file, and max_edge_length value. Then it creates a :doc:`Simplex_tree <simplex_tree_ref>` with it. Finally, it is asked to display information about the Rips complex. @@ -206,8 +206,9 @@ Finally, it is asked to display information about the Rips complex. .. testcode:: import gudhi - rips_complex = gudhi.RipsComplex(csv_file=gudhi.__root_source_dir__ + \ - '/data/distance_matrix/full_square_distance_matrix.csv', max_edge_length=12.0) + distance_matrix = gudhi.read_lower_triangular_matrix_from_csv_file(csv_file=gudhi.__root_source_dir__ + \ + '/data/distance_matrix/full_square_distance_matrix.csv') + rips_complex = gudhi.RipsComplex(distance_matrix=distance_matrix, max_edge_length=12.0) simplex_tree = rips_complex.create_simplex_tree(max_dimension=1) result_str = 'Rips complex is of dimension ' + repr(simplex_tree.dimension()) + ' - ' + \ repr(simplex_tree.num_simplices()) + ' simplices - ' + \ @@ -240,3 +241,72 @@ the program output is: [0, 3] -> 9.43 [4, 6] -> 9.49 [3, 6] -> 11.00 + +Correlation matrix +--------------- + +Example from a correlation matrix +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Analogously to the case of distance matrix, Rips complexes can be also constructed based on correlation matrix. +Given a correlation matrix M, comportment-wise 1-M is a distance matrix. +This example builds the one skeleton graph from the given corelation matrix and threshold value. +Then it creates a :doc:`Simplex_tree <simplex_tree_ref>` with it. + +Finally, it is asked to display information about the simplicial complex. + +.. testcode:: + + import gudhi + import numpy as np + + # User defined correlation matrix is: + # |1 0.06 0.23 0.01 0.89| + # |0.06 1 0.74 0.01 0.61| + # |0.23 0.74 1 0.72 0.03| + # |0.01 0.01 0.72 1 0.7 | + # |0.89 0.61 0.03 0.7 1 | + correlation_matrix=np.array([[1., 0.06, 0.23, 0.01, 0.89], + [0.06, 1., 0.74, 0.01, 0.61], + [0.23, 0.74, 1., 0.72, 0.03], + [0.01, 0.01, 0.72, 1., 0.7], + [0.89, 0.61, 0.03, 0.7, 1.]], float) + + distance_matrix = np.ones((correlation_matrix.shape),float) - correlation_matrix + rips_complex = gudhi.RipsComplex(distance_matrix=distance_matrix, max_edge_length=1.0) + + simplex_tree = rips_complex.create_simplex_tree(max_dimension=1) + result_str = 'Rips complex is of dimension ' + repr(simplex_tree.dimension()) + ' - ' + \ + repr(simplex_tree.num_simplices()) + ' simplices - ' + \ + repr(simplex_tree.num_vertices()) + ' vertices.' + print(result_str) + fmt = '%s -> %.2f' + for filtered_value in simplex_tree.get_filtration(): + print(fmt % tuple(filtered_value)) + +When launching (Rips maximal distance between 2 points is 12.0, is expanded +until dimension 1 - one skeleton graph in other words), the output is: + +.. testoutput:: + + Rips complex is of dimension 1 - 15 simplices - 5 vertices. + [0] -> 0.00 + [1] -> 0.00 + [2] -> 0.00 + [3] -> 0.00 + [4] -> 0.00 + [0, 4] -> 0.11 + [1, 2] -> 0.26 + [2, 3] -> 0.28 + [3, 4] -> 0.30 + [1, 4] -> 0.39 + [0, 2] -> 0.77 + [0, 1] -> 0.94 + [2, 4] -> 0.97 + [0, 3] -> 0.99 + [1, 3] -> 0.99 + +.. note:: + As persistence diagrams points will be under the diagonal, + bottleneck distance and persistence graphical tool will not work properly, + this is a known issue. diff --git a/src/cython/example/alpha_rips_persistence_bottleneck_distance.py b/src/cython/example/alpha_rips_persistence_bottleneck_distance.py index ab5fc1e9..386f8457 100755 --- a/src/cython/example/alpha_rips_persistence_bottleneck_distance.py +++ b/src/cython/example/alpha_rips_persistence_bottleneck_distance.py @@ -45,13 +45,14 @@ args = parser.parse_args() with open(args.file, 'r') as f: first_line = f.readline() if (first_line == 'OFF\n') or (first_line == 'nOFF\n'): + point_cloud = gudhi.read_off(off_file=args.file) print("#####################################################################") print("RipsComplex creation from points read in a OFF file") message = "RipsComplex with max_edge_length=" + repr(args.threshold) print(message) - rips_complex = gudhi.RipsComplex(off_file=args.file, + rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=args.threshold) rips_stree = rips_complex.create_simplex_tree(max_dimension=args.max_dimension) @@ -67,7 +68,7 @@ with open(args.file, 'r') as f: message = "AlphaComplex with max_edge_length=" + repr(args.threshold) print(message) - alpha_complex = gudhi.AlphaComplex(off_file=args.file) + alpha_complex = gudhi.AlphaComplex(points=point_cloud) alpha_stree = alpha_complex.create_simplex_tree(max_alpha_square=(args.threshold * args.threshold)) message = "Number of simplices=" + repr(alpha_stree.num_simplices()) diff --git a/src/cython/example/rips_complex_diagram_persistence_from_correlation_matrix_file_example.py b/src/cython/example/rips_complex_diagram_persistence_from_correlation_matrix_file_example.py new file mode 100755 index 00000000..aa82ef71 --- /dev/null +++ b/src/cython/example/rips_complex_diagram_persistence_from_correlation_matrix_file_example.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python + +import gudhi +import sys +import argparse + +"""This file is part of the Gudhi Library. The Gudhi library + (Geometric Understanding in Higher Dimensions) is a generic C++ + library for computational topology. + + Author(s): Vincent Rouvreau + + Copyright (C) 2017 INRIA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +""" + +__author__ = "Vincent Rouvreau" +__copyright__ = "Copyright (C) 2017 INRIA" +__license__ = "GPL v3" + +parser = argparse.ArgumentParser(description='RipsComplex creation from ' + 'a correlation matrix read in a csv file.', + epilog='Example: ' + 'example/rips_complex_diagram_persistence_from_correlation_matrix_file_example.py ' + '-f ../data/correlation_matrix/lower_triangular_correlation_matrix.csv -e 12.0 -d 3' + '- Constructs a Rips complex with the ' + 'correlation matrix from the given csv file.') +parser.add_argument("-f", "--file", type=str, required=True) +parser.add_argument("-c", "--min_edge_correlation", type=float, default=0.5) +parser.add_argument("-d", "--max_dimension", type=int, default=1) +parser.add_argument("-b", "--band_boot", type=float, default=0.) +parser.add_argument('--no-diagram', default=False, action='store_true' , help='Flag for not to display the diagrams') + +args = parser.parse_args() + +if not (-1. < args.min_edge_correlation < 1.): + print("Wrong value of the treshold corelation (should be between -1 and 1).") + sys.exit(1) + +print("#####################################################################") +print("Caution: as persistence diagrams points will be under the diagonal,") +print("bottleneck distance and persistence graphical tool will not work") +print("properly, this is a known issue.") + +print("#####################################################################") +print("RipsComplex creation from correlation matrix read in a csv file") + +message = "RipsComplex with min_edge_correlation=" + repr(args.min_edge_correlation) +print(message) + +correlation_matrix = gudhi.read_lower_triangular_matrix_from_csv_file(csv_file=args.file) +# Given a correlation matrix M, we compute component-wise M'[i,j] = 1-M[i,j] to get a distance matrix: +distance_matrix = [[1.-correlation_matrix[i][j] for j in range(len(correlation_matrix[i]))] for i in range(len(correlation_matrix))] + +rips_complex = gudhi.RipsComplex(distance_matrix=distance_matrix, + max_edge_length=1.-args.min_edge_correlation) +simplex_tree = rips_complex.create_simplex_tree(max_dimension=args.max_dimension) + +message = "Number of simplices=" + repr(simplex_tree.num_simplices()) +print(message) + +diag = simplex_tree.persistence() + +print("betti_numbers()=") +print(simplex_tree.betti_numbers()) + +# invert the persistence diagram +invert_diag = [(diag[pers][0],(1.-diag[pers][1][0], 1.-diag[pers][1][1])) for pers in range(len(diag))] + +if args.no_diagram == False: + pplot = gudhi.plot_persistence_diagram(invert_diag, band_boot=args.band_boot) + pplot.show() diff --git a/src/cython/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py b/src/cython/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py index 3baebd17..c8aac240 100755 --- a/src/cython/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py +++ b/src/cython/example/rips_complex_diagram_persistence_from_distance_matrix_file_example.py @@ -30,12 +30,12 @@ __copyright__ = "Copyright (C) 2016 INRIA" __license__ = "GPL v3" parser = argparse.ArgumentParser(description='RipsComplex creation from ' - 'a distance matrix read in a OFF file.', + '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' '- Constructs a Rips complex with the ' - 'points from the given OFF file.') + 'distance matrix from the given csv file.') parser.add_argument("-f", "--file", 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) @@ -50,7 +50,8 @@ print("RipsComplex creation from distance matrix read in a csv file") message = "RipsComplex with max_edge_length=" + repr(args.max_edge_length) print(message) -rips_complex = gudhi.RipsComplex(csv_file=args.file, max_edge_length=args.max_edge_length) +distance_matrix = gudhi.read_lower_triangular_matrix_from_csv_file(csv_file=args.file) +rips_complex = gudhi.RipsComplex(distance_matrix=distance_matrix, max_edge_length=args.max_edge_length) simplex_tree = rips_complex.create_simplex_tree(max_dimension=args.max_dimension) message = "Number of simplices=" + repr(simplex_tree.num_simplices()) diff --git a/src/cython/example/rips_complex_diagram_persistence_from_off_file_example.py b/src/cython/example/rips_complex_diagram_persistence_from_off_file_example.py index 5951eedf..544b68c9 100755 --- a/src/cython/example/rips_complex_diagram_persistence_from_off_file_example.py +++ b/src/cython/example/rips_complex_diagram_persistence_from_off_file_example.py @@ -53,7 +53,8 @@ with open(args.file, 'r') as f: message = "RipsComplex with max_edge_length=" + repr(args.max_edge_length) print(message) - rips_complex = gudhi.RipsComplex(off_file=args.file, max_edge_length=args.max_edge_length) + point_cloud = gudhi.read_off(off_file=args.file) + rips_complex = gudhi.RipsComplex(points=point_cloud, max_edge_length=args.max_edge_length) simplex_tree = rips_complex.create_simplex_tree(max_dimension=args.max_dimension) message = "Number of simplices=" + repr(simplex_tree.num_simplices()) diff --git a/src/cython/include/Rips_complex_interface.h b/src/cython/include/Rips_complex_interface.h index 02985727..f26befbc 100644 --- a/src/cython/include/Rips_complex_interface.h +++ b/src/cython/include/Rips_complex_interface.h @@ -25,9 +25,7 @@ #include <gudhi/Simplex_tree.h> #include <gudhi/Rips_complex.h> -#include <gudhi/Points_off_io.h> #include <gudhi/distance_functions.h> -#include <gudhi/reader_utils.h> #include "Simplex_tree_interface.h" @@ -56,21 +54,6 @@ class Rips_complex_interface { } } - Rips_complex_interface(const std::string& file_name, double threshold, bool euclidean, bool from_file = true) { - if (euclidean) { - // Rips construction where file_name is an OFF file - Gudhi::Points_off_reader<Point_d> off_reader(file_name); - rips_complex_ = new Rips_complex<Simplex_tree_interface<>::Filtration_value>(off_reader.get_point_cloud(), - threshold, - Gudhi::Euclidean_distance()); - } else { - // Rips construction where values is a distance matrix - Distance_matrix distances = - Gudhi::read_lower_triangular_matrix_from_csv_file<Simplex_tree_interface<>::Filtration_value>(file_name); - rips_complex_ = new Rips_complex<Simplex_tree_interface<>::Filtration_value>(distances, threshold); - } - } - ~Rips_complex_interface() { delete rips_complex_; } |