Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Random graph methods #1039

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions src/common/include/gudhi/Random.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/* 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): Vincent Rouvreau
*
* Copyright (C) 2024 Inria
*
* Modification(s):
* - YYYY/MM Author: Description of the modification
*/

#ifndef RANDOM__H_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we put GUDHI in the name of the macro, to reduce the risk of conflicts?

#define RANDOM__H_

#include <random> // for std::random_device, std::mt19937, std::uniform_real_distribution
#include <vector>
#include <algorithm> // for std::generate
#include <cstddef> // for std::size_t

namespace Gudhi {
std::random_device rd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a global object, it may cause trouble if you try to link 2 .o that both use gudhi.
You could mark it inline (and then also hide it as a static member of the class or in some subnamespace), or create a temporary one just where it is used.


class Random {
public:
Random() : gen_(rd()) {}
Random(std::uint_fast32_t seed) : gen_(seed) {}

template <typename Type>
Type get(const Type& min = 0, const Type& max = 1) {
if constexpr (std::is_floating_point_v<Type>) {
std::uniform_real_distribution<Type> dis(min, max);
return dis(gen_);
} else if constexpr (std::is_integral_v<Type>) {
std::uniform_int_distribution<Type> dis(min, max);
return dis(gen_);
}
}

template <typename Type>
std::vector<Type> get_range(std::size_t nbr, const Type& min = 0, const Type& max = 1) {
std::vector<Type> result(nbr);
if constexpr (std::is_floating_point_v<Type>) {
std::uniform_real_distribution<Type> dis(min, max);
std::generate(result.begin(), result.end(), [&]() { return dis(gen_); });
} else if constexpr (std::is_integral_v<Type>) {
std::uniform_int_distribution<Type> dis(min, max);
std::generate(result.begin(), result.end(), [&]() { return dis(gen_); });
}
return result;
}

private:
std::mt19937 gen_;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we pick one, mt19937_64 may be better, the platforms we target are all 64 bits (and then the seed is 64 bits as well).

};
} // namespace Gudhi

#endif // RANDOM__H_
98 changes: 98 additions & 0 deletions src/common/include/gudhi/random_graph_generators.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/* 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): Vincent Rouvreau
*
* Copyright (C) 2024 Inria
*
* Modification(s):
* - YYYY/MM Author: Description of the modification
*/

#ifndef RANDOM_GRAPH_GENERATORS_H_
#define RANDOM_GRAPH_GENERATORS_H_

#include <gudhi/Random.h>


#include <algorithm> // for std::prev_permutation
#include <vector>
#include <array>
#include <random>
#include <numeric> // for std::iota
#include <cstddef> // for std::size_t
#include <type_traits> // for std::is_floating_point_v
#ifdef DEBUG_TRACES
#include <iostream>
#endif // DEBUG_TRACES

std::random_device rd;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh!


namespace Gudhi {

template <typename Vertex_handle>
std::vector<std::array<Vertex_handle, 2>> random_edges(Vertex_handle nb_vertices, double density = 0.15) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The algorithm in there seems very complicated. What do you want exactly?

  1. For each pair of vertices, add the edge with proba 15% (independently)
  2. Get exactly 15% * n(n-1)/2 edges, chosen randomly
  3. Other

std::vector<std::array<Vertex_handle, 2>> permutations;
if (nb_vertices < 2)
return permutations;

std::vector<bool> to_permute(nb_vertices);
std::fill(to_permute.begin(), to_permute.begin() + 2, true);

std::size_t nb_permutations = (nb_vertices * (nb_vertices - 1)) / 2;
auto random_values = Gudhi::Random().get_range<double>(nb_permutations);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are going to create Random objects left and right, I don't see how this will help determinism. There is no way for the user to specify a seed...

std::size_t idx = 0;
do {
// Keep only X% of the possible edges
if (random_values[idx] > density) {
idx++;
continue;
}
idx++;

std::array<Vertex_handle, 2> permutation = {-1, -1};
for (Vertex_handle idx = 0; idx < nb_vertices; ++idx) {
if (to_permute[idx]) {
if (permutation[0] == -1) {
permutation[0] = idx;
} else {
permutation[1] = idx;
// No need to go further, only 2 'true' values
break;
}
}
}
#ifdef DEBUG_TRACES
std::cout << permutation.first << ", " << permutation.second << std::endl;
#endif // DEBUG_TRACES
permutations.push_back(permutation);
//std::cout << permutation.first << ", " << permutation.second << "\n";
} while (std::prev_permutation(to_permute.begin(), to_permute.end()));

return permutations;
}

template<typename Simplex_tree>
void simplex_tree_random_flag_complex(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't produce a flag complex, it produces a graph.

Simplex_tree& st,
typename Simplex_tree::Vertex_handle nb_vertices,
double density = 0.15) {
using Vertex_handle = typename Simplex_tree::Vertex_handle;
std::vector<Vertex_handle> vertices(nb_vertices);
std::iota(vertices.begin(), vertices.end(), 0); // vertices is { 0, 1, 2, ..., 99 } when nb_vertices is 100
st.insert_batch_vertices(vertices);
Comment on lines +80 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
std::vector<Vertex_handle> vertices(nb_vertices);
std::iota(vertices.begin(), vertices.end(), 0); // vertices is { 0, 1, 2, ..., 99 } when nb_vertices is 100
st.insert_batch_vertices(vertices);
st.insert_batch_vertices(boost::irange(nb_vertices));

https://www.boost.org/doc/libs/1_85_0/libs/range/doc/html/range/reference/ranges/irange.html (or counting_range)


auto edges = random_edges(nb_vertices, density);

auto random_filtrations = Gudhi::Random().get_range<typename Simplex_tree::Filtration_value>(edges.size());

std::size_t idx = 0;
for (auto edge : edges) {
st.insert_simplex(edge, random_filtrations[idx]);
idx++;
}

}

} // namespace Gudhi

#endif // RANDOM_GRAPH_GENERATORS_H_
7 changes: 7 additions & 0 deletions src/common/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ include(GUDHI_boost_test)
add_executable ( Common_test_points_off_reader test_points_off_reader.cpp )
add_executable ( Common_test_distance_matrix_reader test_distance_matrix_reader.cpp )
add_executable ( Common_test_persistence_intervals_reader test_persistence_intervals_reader.cpp )
add_executable ( Common_test_random_graph_generators test_random_graph_generators.cpp )
if(TARGET TBB::tbb)
target_link_libraries(Common_test_random_graph_generators TBB::tbb)
endif()
add_executable ( Common_test_random test_random.cpp )

# Do not forget to copy test files in current binary dir
file(COPY "${CMAKE_SOURCE_DIR}/data/points/alphacomplexdoc.off" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/)
Expand All @@ -17,3 +22,5 @@ file(COPY "${CMAKE_SOURCE_DIR}/src/common/test/persistence_intervals_without_dim
gudhi_add_boost_test(Common_test_points_off_reader)
gudhi_add_boost_test(Common_test_distance_matrix_reader)
gudhi_add_boost_test(Common_test_persistence_intervals_reader)
gudhi_add_boost_test(Common_test_random_graph_generators)
gudhi_add_boost_test(Common_test_random)
65 changes: 65 additions & 0 deletions src/common/test/test_random.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* 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): Vincent Rouvreau
*
* Copyright (C) 2024 Inria
*
* Modification(s):
* - YYYY/MM Author: Description of the modification
*/

#include <gudhi/Random.h>

#include <iostream>
#include <vector>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "random"
#include <boost/test/unit_test.hpp>
#include <boost/mpl/list.hpp>

using list_of_rnd_types = boost::mpl::list<double, float, int, unsigned int, long>;

BOOST_AUTO_TEST_CASE_TEMPLATE(random_same_seed, RndType, list_of_rnd_types) {
std::cout << " ## BOOST_AUTO_TEST_CASE( random_same_seed )" << std::endl;

RndType min{0};
RndType max{100};
RndType first = Gudhi::Random(1).get<RndType>(min, max);
RndType second = Gudhi::Random(1).get<RndType>(min, max);

std::clog << "First random number: " << first << " - Second random number: " << second << std::endl;
BOOST_CHECK(first >= min);
BOOST_CHECK(first <= max);
BOOST_CHECK(first == second);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(random_different_seed, RndType, list_of_rnd_types) {
std::cout << " ## BOOST_AUTO_TEST_CASE( random_different_seed )" << std::endl;

RndType min{0};
RndType max{100};
RndType first = Gudhi::Random().get<RndType>(min, max);
RndType second = Gudhi::Random().get<RndType>(min, max);

std::clog << "First random number: " << first << " - Second random number: " << second << std::endl;
BOOST_CHECK(first >= min);
BOOST_CHECK(first <= max);
}

BOOST_AUTO_TEST_CASE_TEMPLATE(random_range, RndType, list_of_rnd_types) {
std::cout << " ## BOOST_AUTO_TEST_CASE( random_range )" << std::endl;

RndType min{0};
RndType max{100};
const std::size_t N{20};
std::vector<RndType> range = Gudhi::Random().get_range<RndType>(N, min, max);

BOOST_CHECK(range.size() == N);

for (const auto& value : range) {
std::clog << "Random number: " << value << std::endl;
BOOST_CHECK(value >= min);
BOOST_CHECK(value <= max);
}
}
92 changes: 92 additions & 0 deletions src/common/test/test_random_graph_generators.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/* 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): Vincent Rouvreau
*
* Copyright (C) 2024 Inria
*
* Modification(s):
* - YYYY/MM Author: Description of the modification
*/

#include <gudhi/random_graph_generators.h>
#include <gudhi/Simplex_tree.h>

#include <iostream>
#include <array>
#include <vector>

#define BOOST_TEST_DYN_LINK
#define BOOST_TEST_MODULE "random_graph_generators"
#include <boost/test/unit_test.hpp>

using Edge = std::array<int, 2>;
using Edge_range = std::vector<Edge>;

BOOST_AUTO_TEST_CASE( random_edges_limits ) {
std::cout << " ## BOOST_AUTO_TEST_CASE( random_edges_limits )" << std::endl;
Edge_range edges = Gudhi::random_edges(0);
std::cout << "Gudhi::random_edges(0).size() = " << edges.size() << std::endl;
BOOST_CHECK(edges.size() == 0);

edges = Gudhi::random_edges(1);
std::cout << "Gudhi::random_edges(1).size() = " << edges.size() << std::endl;
BOOST_CHECK(edges.size() == 0);

edges = Gudhi::random_edges(2, 1.);
std::cout << "Gudhi::random_edges(2).size() = " << edges.size() << std::endl;
BOOST_CHECK(edges.size() == 1);

edges = Gudhi::random_edges(15, 0.);
std::cout << "Gudhi::random_edges(15, 0.).size() = " << edges.size() << std::endl;
BOOST_CHECK(edges.size() == 0);

edges = Gudhi::random_edges(15, 1.);
std::cout << "Gudhi::random_edges(15, 1.).size() = " << edges.size() << std::endl;
BOOST_CHECK(edges.size() == 105);
}

BOOST_AUTO_TEST_CASE( random_edges_mean ) {
std::cout << " ## BOOST_AUTO_TEST_CASE( random_edges_mean )" << std::endl;
int nb_edges {0};
for (int idx = 0; idx < 100; ++idx) {
Edge_range edges = Gudhi::random_edges(15);
nb_edges += edges.size();
#ifdef DEBUG_TRACES
std::cout << edges.size() << ", ";
#endif // DEBUG_TRACES
}
#ifdef DEBUG_TRACES
std::cout << "\n";
#endif // DEBUG_TRACES
std::cout << "Total number of edges for 100 x Gudhi::random_edges(15) [aka. 15% of 105] = " << nb_edges << std::endl;
// 1575 +/- 10%
BOOST_CHECK(nb_edges < 1733);
BOOST_CHECK(nb_edges > 1417);
}

BOOST_AUTO_TEST_CASE( simplex_tree_random_flag_complex_test ) {
std::cout << " ## BOOST_AUTO_TEST_CASE( simplex_tree_random_flag_complex_test )" << std::endl;
Gudhi::Simplex_tree<> stree;
// Insert 100% of the possible edges, with 10 vertices
simplex_tree_random_flag_complex(stree, 10, 1.);

std::cout << "Random flag complex with " << stree.num_vertices() << " vertices and " << stree.num_simplices() <<
" simplices" << std::endl;
BOOST_CHECK(stree.num_vertices() == 10);
BOOST_CHECK(stree.num_simplices() == 55);
}

BOOST_AUTO_TEST_CASE( simplex_tree_random_flag_complex_test_1000 ) {
std::cout << " ## BOOST_AUTO_TEST_CASE( simplex_tree_random_flag_complex_test_1000 )" << std::endl;
Gudhi::Simplex_tree<> stree;
// Insert 15% of the possible edges, with 1000 vertices
simplex_tree_random_flag_complex(stree, 1000);

std::cout << "Random flag complex with " << stree.num_vertices() << " vertices and " << stree.num_simplices() <<
" simplices" << std::endl;

// 15 % of 499500 = 74925
// 74925 +/- 5% = [71178.75, 78671.25]
BOOST_CHECK(stree.num_simplices() < 78671);
BOOST_CHECK(stree.num_simplices() > 71178);
}
Loading