Make the Louvain code comply with our conventions
Reviewers: teon.banek Reviewed By: teon.banek Subscribers: buda, pullbot Differential Revision: https://phabricator.memgraph.io/D2566
This commit is contained in:
parent
9e04183be7
commit
03ecc58715
@ -12,7 +12,7 @@ namespace algorithms {
|
|||||||
/// algorithm. The algorithm attempts to maximze the modularity of a weighted
|
/// algorithm. The algorithm attempts to maximze the modularity of a weighted
|
||||||
/// graph.
|
/// graph.
|
||||||
///
|
///
|
||||||
/// @param G pointer to an undirected, weighted graph which may contain
|
/// @param graph pointer to an undirected, weighted graph which may contain
|
||||||
/// self-loops.
|
/// self-loops.
|
||||||
void Louvain(comdata::Graph *G);
|
void Louvain(comdata::Graph *graph);
|
||||||
} // namespace algorithms
|
} // namespace algorithms
|
||||||
|
@ -9,27 +9,27 @@
|
|||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
void OptimizeLocally(comdata::Graph *G) {
|
void OptimizeLocally(comdata::Graph *graph) {
|
||||||
// We will consider local optimizations uniformly at random.
|
// We will consider local optimizations uniformly at random.
|
||||||
std::random_device rd;
|
std::random_device rd;
|
||||||
std::mt19937 g(rd());
|
std::mt19937 g(rd());
|
||||||
std::vector<uint32_t> p(G->Size());
|
std::vector<uint32_t> p(graph->Size());
|
||||||
std::iota(p.begin(), p.end(), 0);
|
std::iota(p.begin(), p.end(), 0);
|
||||||
std::shuffle(p.begin(), p.end(), g);
|
std::shuffle(p.begin(), p.end(), g);
|
||||||
|
|
||||||
double total_w = G->TotalWeight();
|
double total_w = graph->TotalWeight();
|
||||||
bool stable = false;
|
bool stable = false;
|
||||||
while (!stable) {
|
while (!stable) {
|
||||||
stable = true;
|
stable = true;
|
||||||
for (uint32_t node_id : p) {
|
for (uint32_t node_id : p) {
|
||||||
std::unordered_map<uint32_t, double> c_contrib;
|
std::unordered_map<uint32_t, double> c_contrib;
|
||||||
c_contrib[G->Community(node_id)] = 0;
|
c_contrib[graph->Community(node_id)] = 0;
|
||||||
for (const auto &neigh : G->Neighbours(node_id)) {
|
for (const auto &neigh : graph->Neighbours(node_id)) {
|
||||||
uint32_t nxt_id = neigh.dest;
|
uint32_t nxt_id = neigh.dest;
|
||||||
double weight = neigh.weight;
|
double weight = neigh.weight;
|
||||||
double contrib = weight - G->IncidentWeight(node_id) *
|
double contrib = weight - graph->IncidentWeight(node_id) *
|
||||||
G->IncidentWeight(nxt_id) / (2 * total_w);
|
graph->IncidentWeight(nxt_id) / (2 * total_w);
|
||||||
c_contrib[G->Community(nxt_id)] += contrib;
|
c_contrib[graph->Community(nxt_id)] += contrib;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto best_c = std::max_element(c_contrib.begin(), c_contrib.end(),
|
auto best_c = std::max_element(c_contrib.begin(), c_contrib.end(),
|
||||||
@ -38,8 +38,8 @@ void OptimizeLocally(comdata::Graph *G) {
|
|||||||
return p1.second < p2.second;
|
return p1.second < p2.second;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (best_c->second - c_contrib[G->Community(node_id)] > 0) {
|
if (best_c->second - c_contrib[graph->Community(node_id)] > 0) {
|
||||||
G->SetCommunity(node_id, best_c->first);
|
graph->SetCommunity(node_id, best_c->first);
|
||||||
stable = false;
|
stable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -50,40 +50,43 @@ void OptimizeLocally(comdata::Graph *G) {
|
|||||||
|
|
||||||
namespace algorithms {
|
namespace algorithms {
|
||||||
|
|
||||||
void Louvain(comdata::Graph *G) {
|
void Louvain(comdata::Graph *graph) {
|
||||||
OptimizeLocally(G);
|
OptimizeLocally(graph);
|
||||||
|
|
||||||
// Collapse the locally optimized graph.
|
// Collapse the locally optimized graph.
|
||||||
uint32_t collapsed_nodes = G->NormalizeCommunities();
|
uint32_t collapsed_nodes = graph->NormalizeCommunities();
|
||||||
if (collapsed_nodes == G->Size()) return;
|
if (collapsed_nodes == graph->Size()) return;
|
||||||
comdata::Graph collapsed_G(collapsed_nodes);
|
comdata::Graph collapsed_graph(collapsed_nodes);
|
||||||
std::map<std::pair<uint32_t, uint32_t>, double> collapsed_edges;
|
std::map<std::pair<uint32_t, uint32_t>, double> collapsed_edges;
|
||||||
|
|
||||||
for (uint32_t node_id = 0; node_id < G->Size(); ++node_id) {
|
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id) {
|
||||||
std::unordered_map<uint32_t, double> edges;
|
std::unordered_map<uint32_t, double> edges;
|
||||||
for (const auto &neigh : G->Neighbours(node_id)) {
|
for (const auto &neigh : graph->Neighbours(node_id)) {
|
||||||
uint32_t nxt_id = neigh.dest;
|
uint32_t nxt_id = neigh.dest;
|
||||||
double weight = neigh.weight;
|
double weight = neigh.weight;
|
||||||
if (G->Community(nxt_id) < G->Community(node_id)) continue;
|
if (graph->Community(nxt_id) < graph->Community(node_id)) continue;
|
||||||
edges[G->Community(nxt_id)] += weight;
|
edges[graph->Community(nxt_id)] += weight;
|
||||||
}
|
}
|
||||||
for (const auto &neigh : edges) {
|
for (const auto &neigh : edges) {
|
||||||
uint32_t a = std::min(G->Community(node_id), neigh.first);
|
uint32_t a = std::min(graph->Community(node_id), neigh.first);
|
||||||
uint32_t b = std::max(G->Community(node_id), neigh.first);
|
uint32_t b = std::max(graph->Community(node_id), neigh.first);
|
||||||
collapsed_edges[{a, b}] += neigh.second;
|
collapsed_edges[{a, b}] += neigh.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &p : collapsed_edges)
|
for (const auto &p : collapsed_edges)
|
||||||
collapsed_G.AddEdge(p.first.first, p.first.second, p.second);
|
collapsed_graph.AddEdge(p.first.first, p.first.second, p.second);
|
||||||
|
|
||||||
// Repeat until no local optimizations can be found.
|
// Repeat until no local optimizations can be found.
|
||||||
Louvain(&collapsed_G);
|
Louvain(&collapsed_graph);
|
||||||
|
|
||||||
// Propagate results from collapsed graph.
|
// Propagate results from collapsed graph.
|
||||||
for (uint32_t node_id = 0; node_id < G->Size(); ++node_id)
|
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id) {
|
||||||
G->SetCommunity(node_id, collapsed_G.Community(G->Community(node_id)));
|
graph->SetCommunity(node_id,
|
||||||
G->NormalizeCommunities();
|
collapsed_graph.Community(graph->Community(node_id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
graph->NormalizeCommunities();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace algorithms
|
} // namespace algorithms
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
/// @file graph.hpp
|
/// @file
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
@ -21,9 +21,8 @@ public:
|
|||||||
/// Constructs a new graph with a given number of nodes and no edges between
|
/// Constructs a new graph with a given number of nodes and no edges between
|
||||||
/// them.
|
/// them.
|
||||||
///
|
///
|
||||||
/// At the moment, the implementation assumes (and enforces) that all nodes
|
/// The implementation assumes (and enforces) that all nodes
|
||||||
/// are indexed from 0 to n_nodes - 1. This will be changed in the final
|
/// are indexed from 0 to n_nodes.
|
||||||
/// implementation. - TODO(ipaljak)
|
|
||||||
///
|
///
|
||||||
/// @param n_nodes Number of nodes in the graph.
|
/// @param n_nodes Number of nodes in the graph.
|
||||||
explicit Graph(uint32_t n_nodes);
|
explicit Graph(uint32_t n_nodes);
|
||||||
|
@ -10,19 +10,19 @@ int main() {
|
|||||||
int n;
|
int n;
|
||||||
int m;
|
int m;
|
||||||
std::cin >> n >> m;
|
std::cin >> n >> m;
|
||||||
comdata::Graph G(n);
|
comdata::Graph graph(n);
|
||||||
for (int i = 0; i < m; ++i) {
|
for (int i = 0; i < m; ++i) {
|
||||||
int a;
|
int a;
|
||||||
int b;
|
int b;
|
||||||
double c;
|
double c;
|
||||||
std::cin >> a >> b >> c;
|
std::cin >> a >> b >> c;
|
||||||
G.AddEdge(a, b, c);
|
graph.AddEdge(a, b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
algorithms::Louvain(&G);
|
algorithms::Louvain(&graph);
|
||||||
|
|
||||||
for (int i = 0; i < n; ++i)
|
for (int i = 0; i < n; ++i)
|
||||||
std::cout << i << G.Community(i) << "\n";
|
std::cout << i << graph.Community(i) << "\n";
|
||||||
std::cout << G.Modularity() << "\n";
|
std::cout << graph.Modularity() << "\n";
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -4,130 +4,135 @@
|
|||||||
#include "data_structures/graph.hpp"
|
#include "data_structures/graph.hpp"
|
||||||
#include "utils.hpp"
|
#include "utils.hpp"
|
||||||
|
|
||||||
// Checks if commmunities of nodes in G correspond to a given community vector.
|
// Checks if commmunities of nodes in graph correspond to a given community
|
||||||
bool community_check(const comdata::Graph &G, const std::vector<uint32_t> &c) {
|
// vector.
|
||||||
if (G.Size() != c.size()) return false;
|
bool CommunityCheck(const comdata::Graph &graph,
|
||||||
for (uint32_t node_id = 0; node_id < G.Size(); ++node_id)
|
const std::vector<uint32_t> &c) {
|
||||||
if (G.Community(node_id) != c[node_id])
|
if (graph.Size() != c.size()) return false;
|
||||||
|
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||||
|
if (graph.Community(node_id) != c[node_id])
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if degrees of nodes in G correspond to a given degree vector.
|
// Checks if degrees of nodes in graph correspond to a given degree vector.
|
||||||
bool degree_check(const comdata::Graph &G, const std::vector<uint32_t> °) {
|
bool DegreeCheck(const comdata::Graph &graph,
|
||||||
if (G.Size() != deg.size()) return false;
|
const std::vector<uint32_t> °) {
|
||||||
for (uint32_t node_id = 0; node_id < G.Size(); ++node_id)
|
if (graph.Size() != deg.size()) return false;
|
||||||
if (G.Degree(node_id) != deg[node_id])
|
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||||
|
if (graph.Degree(node_id) != deg[node_id])
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if incident weights of nodes in G correspond to a given weight vector.
|
// Checks if incident weights of nodes in graph correspond to a given weight
|
||||||
bool inc_w_check(const comdata::Graph &G, const std::vector<double> &inc_w) {
|
// vector.
|
||||||
if (G.Size() != inc_w.size()) return false;
|
bool IncidentWeightCheck(const comdata::Graph &graph,
|
||||||
for (uint32_t node_id = 0; node_id < G.Size(); ++node_id)
|
const std::vector<double> &inc_w) {
|
||||||
if (std::abs(G.IncidentWeight(node_id) - inc_w[node_id]) > 1e-6)
|
if (graph.Size() != inc_w.size()) return false;
|
||||||
|
for (uint32_t node_id = 0; node_id < graph.Size(); ++node_id)
|
||||||
|
if (std::abs(graph.IncidentWeight(node_id) - inc_w[node_id]) > 1e-6)
|
||||||
return false;
|
return false;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets communities of nodes in G. Returns true on success.
|
// Sets communities of nodes in graph. Returns true on success.
|
||||||
bool set_communities(comdata::Graph *G, const std::vector<uint32_t> &c) {
|
bool SetCommunities(comdata::Graph *graph, const std::vector<uint32_t> &c) {
|
||||||
if (G->Size() != c.size()) return false;
|
if (graph->Size() != c.size()) return false;
|
||||||
for (uint32_t node_id = 0; node_id < G->Size(); ++node_id)
|
for (uint32_t node_id = 0; node_id < graph->Size(); ++node_id)
|
||||||
G->SetCommunity(node_id, c[node_id]);
|
graph->SetCommunity(node_id, c[node_id]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Constructor) {
|
TEST(Graph, Constructor) {
|
||||||
uint32_t nodes = 100;
|
uint32_t nodes = 100;
|
||||||
comdata::Graph G(nodes);
|
comdata::Graph graph(nodes);
|
||||||
ASSERT_EQ(G.Size(), nodes);
|
ASSERT_EQ(graph.Size(), nodes);
|
||||||
for (uint32_t node_id = 0; node_id < nodes; ++node_id) {
|
for (uint32_t node_id = 0; node_id < nodes; ++node_id) {
|
||||||
ASSERT_EQ(G.IncidentWeight(node_id), 0);
|
ASSERT_EQ(graph.IncidentWeight(node_id), 0);
|
||||||
ASSERT_EQ(G.Community(node_id), node_id);
|
ASSERT_EQ(graph.Community(node_id), node_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Size) {
|
TEST(Graph, Size) {
|
||||||
comdata::Graph G1 = GenRandomUnweightedGraph(0, 0);
|
comdata::Graph graph1 = GenRandomUnweightedGraph(0, 0);
|
||||||
comdata::Graph G2 = GenRandomUnweightedGraph(42, 41);
|
comdata::Graph graph2 = GenRandomUnweightedGraph(42, 41);
|
||||||
comdata::Graph G3 = GenRandomUnweightedGraph(100, 250);
|
comdata::Graph graph3 = GenRandomUnweightedGraph(100, 250);
|
||||||
ASSERT_EQ(G1.Size(), 0);
|
ASSERT_EQ(graph1.Size(), 0);
|
||||||
ASSERT_EQ(G2.Size(), 42);
|
ASSERT_EQ(graph2.Size(), 42);
|
||||||
ASSERT_EQ(G3.Size(), 100);
|
ASSERT_EQ(graph3.Size(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Communities) {
|
TEST(Graph, Communities) {
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(100, 250);
|
comdata::Graph graph = GenRandomUnweightedGraph(100, 250);
|
||||||
|
|
||||||
for (int i = 0; i < 100; ++i) G.SetCommunity(i, i % 5);
|
for (int i = 0; i < 100; ++i) graph.SetCommunity(i, i % 5);
|
||||||
for (int i = 0; i < 100; ++i) ASSERT_EQ(G.Community(i), i % 5);
|
for (int i = 0; i < 100; ++i) ASSERT_EQ(graph.Community(i), i % 5);
|
||||||
|
|
||||||
// Try to set communities on non-existing nodes
|
// Try to set communities on non-existing nodes
|
||||||
ASSERT_DEATH({ G.SetCommunity(100, 2); }, "");
|
ASSERT_DEATH({ graph.SetCommunity(100, 2); }, "");
|
||||||
ASSERT_DEATH({ G.SetCommunity(150, 0); }, "");
|
ASSERT_DEATH({ graph.SetCommunity(150, 0); }, "");
|
||||||
|
|
||||||
// Try to get a the community of a non-existing node
|
// Try to get a the community of a non-existing node
|
||||||
ASSERT_DEATH({ G.Community(100); }, "");
|
ASSERT_DEATH({ graph.Community(100); }, "");
|
||||||
ASSERT_DEATH({ G.Community(150); }, "");
|
ASSERT_DEATH({ graph.Community(150); }, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, CommunityNormalization) {
|
TEST(Graph, CommunityNormalization) {
|
||||||
// Communities are already normalized.
|
// Communities are already normalized.
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(5, 10);
|
comdata::Graph graph = GenRandomUnweightedGraph(5, 10);
|
||||||
std::vector<uint32_t> init_c = {0, 2, 1, 3, 4};
|
std::vector<uint32_t> init_c = {0, 2, 1, 3, 4};
|
||||||
std::vector<uint32_t> final_c = {0, 2, 1, 3, 4};
|
std::vector<uint32_t> final_c = {0, 2, 1, 3, 4};
|
||||||
ASSERT_TRUE(set_communities(&G, init_c));
|
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||||
G.NormalizeCommunities();
|
graph.NormalizeCommunities();
|
||||||
ASSERT_TRUE(community_check(G, final_c));
|
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||||
|
|
||||||
// Each node in its own community.
|
// Each node in its own community.
|
||||||
G = GenRandomUnweightedGraph(5, 10);
|
graph = GenRandomUnweightedGraph(5, 10);
|
||||||
init_c = {20, 30, 10, 40, 50};
|
init_c = {20, 30, 10, 40, 50};
|
||||||
final_c = {1, 2, 0, 3, 4};
|
final_c = {1, 2, 0, 3, 4};
|
||||||
ASSERT_TRUE(set_communities(&G, init_c));
|
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||||
G.NormalizeCommunities();
|
graph.NormalizeCommunities();
|
||||||
ASSERT_TRUE(community_check(G, final_c));
|
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||||
|
|
||||||
// Multiple nodes in the same community
|
// Multiple nodes in the same community
|
||||||
G = GenRandomUnweightedGraph(7, 10);
|
graph = GenRandomUnweightedGraph(7, 10);
|
||||||
init_c = {13, 99, 13, 13, 1, 99, 1};
|
init_c = {13, 99, 13, 13, 1, 99, 1};
|
||||||
final_c = {1, 2, 1, 1, 0, 2, 0};
|
final_c = {1, 2, 1, 1, 0, 2, 0};
|
||||||
ASSERT_TRUE(set_communities(&G, init_c));
|
ASSERT_TRUE(SetCommunities(&graph, init_c));
|
||||||
G.NormalizeCommunities();
|
graph.NormalizeCommunities();
|
||||||
ASSERT_TRUE(community_check(G, final_c));
|
ASSERT_TRUE(CommunityCheck(graph, final_c));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, AddEdge) {
|
TEST(Graph, AddEdge) {
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(5, 0);
|
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||||
|
|
||||||
// Node out of bounds.
|
// Node out of bounds.
|
||||||
ASSERT_DEATH({ G.AddEdge(1, 5, 7); }, "");
|
ASSERT_DEATH({ graph.AddEdge(1, 5, 7); }, "");
|
||||||
|
|
||||||
// Repeated edge
|
// Repeated edge
|
||||||
G.AddEdge(1, 2, 1);
|
graph.AddEdge(1, 2, 1);
|
||||||
ASSERT_DEATH({ G.AddEdge(1, 2, 7); }, "");
|
ASSERT_DEATH({ graph.AddEdge(1, 2, 7); }, "");
|
||||||
|
|
||||||
// Non-positive edge weight
|
// Non-positive edge weight
|
||||||
ASSERT_DEATH({ G.AddEdge(2, 3, -7); }, "");
|
ASSERT_DEATH({ graph.AddEdge(2, 3, -7); }, "");
|
||||||
ASSERT_DEATH({ G.AddEdge(3, 4, 0); }, "");
|
ASSERT_DEATH({ graph.AddEdge(3, 4, 0); }, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Degrees) {
|
TEST(Graph, Degrees) {
|
||||||
// Graph without edges
|
// Graph without edges
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(5, 0);
|
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||||
std::vector<uint32_t> deg = {0, 0, 0, 0, 0};
|
std::vector<uint32_t> deg = {0, 0, 0, 0, 0};
|
||||||
ASSERT_TRUE(degree_check(G, deg));
|
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||||
|
|
||||||
// Chain
|
// Chain
|
||||||
// (0)--(1)--(2)--(3)--(4)
|
// (0)--(1)--(2)--(3)--(4)
|
||||||
G = BuildGraph(5, {{0, 1, 1},
|
graph = BuildGraph(5, {{0, 1, 1},
|
||||||
{1, 2, 1},
|
{1, 2, 1},
|
||||||
{2, 3, 1},
|
{2, 3, 1},
|
||||||
{3, 4, 1}});
|
{3, 4, 1}});
|
||||||
deg = {1, 2, 2, 2, 1};
|
deg = {1, 2, 2, 2, 1};
|
||||||
ASSERT_TRUE(degree_check(G, deg));
|
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
// (0)--(3)
|
// (0)--(3)
|
||||||
@ -135,21 +140,21 @@ TEST(Graph, Degrees) {
|
|||||||
// (1) (2)
|
// (1) (2)
|
||||||
// | / \
|
// | / \
|
||||||
// (4) (5) (6)
|
// (4) (5) (6)
|
||||||
G = BuildGraph(7, {{0, 1, 1},
|
graph = BuildGraph(7, {{0, 1, 1},
|
||||||
{0, 2, 1},
|
{0, 2, 1},
|
||||||
{0, 3, 1},
|
{0, 3, 1},
|
||||||
{1, 4, 1},
|
{1, 4, 1},
|
||||||
{2, 5, 1},
|
{2, 5, 1},
|
||||||
{2, 6, 1}});
|
{2, 6, 1}});
|
||||||
deg = {3, 2, 3, 1, 1, 1, 1};
|
deg = {3, 2, 3, 1, 1, 1, 1};
|
||||||
ASSERT_TRUE(degree_check(G, deg));
|
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||||
|
|
||||||
// Graph without self-loops
|
// Graph without self-loops
|
||||||
// (0)--(1)
|
// (0)--(1)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2)--(3)-(4)
|
// (2)--(3)-(4)
|
||||||
G = BuildGraph(5, {{0, 1, 1},
|
graph = BuildGraph(5, {{0, 1, 1},
|
||||||
{0, 2, 1},
|
{0, 2, 1},
|
||||||
{0, 3, 1},
|
{0, 3, 1},
|
||||||
{1, 3, 1},
|
{1, 3, 1},
|
||||||
@ -157,14 +162,14 @@ TEST(Graph, Degrees) {
|
|||||||
{2, 3, 1},
|
{2, 3, 1},
|
||||||
{3, 4, 1}});
|
{3, 4, 1}});
|
||||||
deg = {3, 3, 2, 4, 2};
|
deg = {3, 3, 2, 4, 2};
|
||||||
ASSERT_TRUE(degree_check(G, deg));
|
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||||
|
|
||||||
// Graph with self loop [*nodes have self loops]
|
// Graph with self loop [*nodes have self loops]
|
||||||
// (0)--(1*)
|
// (0)--(1*)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2*)--(3)-(4*)
|
// (2*)--(3)-(4*)
|
||||||
G = BuildGraph(5, {{0, 1, 1},
|
graph = BuildGraph(5, {{0, 1, 1},
|
||||||
{0, 2, 1},
|
{0, 2, 1},
|
||||||
{0, 3, 1},
|
{0, 3, 1},
|
||||||
{1, 3, 1},
|
{1, 3, 1},
|
||||||
@ -175,29 +180,29 @@ TEST(Graph, Degrees) {
|
|||||||
{2, 2, 2},
|
{2, 2, 2},
|
||||||
{4, 4, 4}});
|
{4, 4, 4}});
|
||||||
deg = {3, 4, 3, 4, 3};
|
deg = {3, 4, 3, 4, 3};
|
||||||
ASSERT_TRUE(degree_check(G, deg));
|
ASSERT_TRUE(DegreeCheck(graph, deg));
|
||||||
|
|
||||||
// Try to get degree of non-existing nodes
|
// Try to get degree of non-existing nodes
|
||||||
ASSERT_DEATH({ G.Degree(5); }, "");
|
ASSERT_DEATH({ graph.Degree(5); }, "");
|
||||||
ASSERT_DEATH({ G.Degree(100); }, "");
|
ASSERT_DEATH({ graph.Degree(100); }, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Weights) {
|
TEST(Graph, Weights) {
|
||||||
// Graph without edges
|
// Graph without edges
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(5, 0);
|
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||||
std::vector<double> inc_w = {0, 0, 0, 0, 0};
|
std::vector<double> inc_w = {0, 0, 0, 0, 0};
|
||||||
ASSERT_TRUE(inc_w_check(G, inc_w));
|
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||||
ASSERT_EQ(G.TotalWeight(), 0);
|
ASSERT_EQ(graph.TotalWeight(), 0);
|
||||||
|
|
||||||
// Chain
|
// Chain
|
||||||
// (0)--(1)--(2)--(3)--(4)
|
// (0)--(1)--(2)--(3)--(4)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{1, 2, 0.5},
|
{1, 2, 0.5},
|
||||||
{2, 3, 2.3},
|
{2, 3, 2.3},
|
||||||
{3, 4, 4.2}});
|
{3, 4, 4.2}});
|
||||||
inc_w = {0.1, 0.6, 2.8, 6.5, 4.2};
|
inc_w = {0.1, 0.6, 2.8, 6.5, 4.2};
|
||||||
ASSERT_TRUE(inc_w_check(G, inc_w));
|
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||||
ASSERT_NEAR(G.TotalWeight(), 7.1, 1e-6);
|
ASSERT_NEAR(graph.TotalWeight(), 7.1, 1e-6);
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
// (0)--(3)
|
// (0)--(3)
|
||||||
@ -205,22 +210,22 @@ TEST(Graph, Weights) {
|
|||||||
// (1) (2)
|
// (1) (2)
|
||||||
// | / \
|
// | / \
|
||||||
// (4) (5) (6)
|
// (4) (5) (6)
|
||||||
G = BuildGraph(7, {{0, 1, 1.3},
|
graph = BuildGraph(7, {{0, 1, 1.3},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 1},
|
{0, 3, 1},
|
||||||
{1, 4, 3.2},
|
{1, 4, 3.2},
|
||||||
{2, 5, 4.2},
|
{2, 5, 4.2},
|
||||||
{2, 6, 0.7}});
|
{2, 6, 0.7}});
|
||||||
inc_w = {2.5, 4.5, 5.1, 1, 3.2, 4.2, 0.7};
|
inc_w = {2.5, 4.5, 5.1, 1, 3.2, 4.2, 0.7};
|
||||||
ASSERT_TRUE(inc_w_check(G, inc_w));
|
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||||
EXPECT_NEAR(G.TotalWeight(), 10.6, 1e-6);
|
EXPECT_NEAR(graph.TotalWeight(), 10.6, 1e-6);
|
||||||
|
|
||||||
// Graph without self-loops
|
// Graph without self-loops
|
||||||
// (0)--(1)
|
// (0)--(1)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2)--(3)-(4)
|
// (2)--(3)-(4)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 0.3},
|
{0, 3, 0.3},
|
||||||
{1, 3, 0.4},
|
{1, 3, 0.4},
|
||||||
@ -228,15 +233,15 @@ TEST(Graph, Weights) {
|
|||||||
{2, 3, 0.6},
|
{2, 3, 0.6},
|
||||||
{3, 4, 0.7}});
|
{3, 4, 0.7}});
|
||||||
inc_w = {0.6, 1, 0.8, 2, 1.2};
|
inc_w = {0.6, 1, 0.8, 2, 1.2};
|
||||||
ASSERT_TRUE(inc_w_check(G, inc_w));
|
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||||
EXPECT_NEAR(G.TotalWeight(), 2.8, 1e-6);
|
EXPECT_NEAR(graph.TotalWeight(), 2.8, 1e-6);
|
||||||
|
|
||||||
// Graph with self loop [*nodes have self loops]
|
// Graph with self loop [*nodes have self loops]
|
||||||
// (0)--(1*)
|
// (0)--(1*)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2*)--(3)-(4*)
|
// (2*)--(3)-(4*)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 0.3},
|
{0, 3, 0.3},
|
||||||
{1, 3, 0.4},
|
{1, 3, 0.4},
|
||||||
@ -247,28 +252,28 @@ TEST(Graph, Weights) {
|
|||||||
{2, 2, 0.9},
|
{2, 2, 0.9},
|
||||||
{4, 4, 1}});
|
{4, 4, 1}});
|
||||||
inc_w = {0.6, 1.8, 1.7, 2, 2.2};
|
inc_w = {0.6, 1.8, 1.7, 2, 2.2};
|
||||||
ASSERT_TRUE(inc_w_check(G, inc_w));
|
ASSERT_TRUE(IncidentWeightCheck(graph, inc_w));
|
||||||
EXPECT_NEAR(G.TotalWeight(), 5.5, 1e-6);
|
EXPECT_NEAR(graph.TotalWeight(), 5.5, 1e-6);
|
||||||
|
|
||||||
// Try to get incident weight of non-existing node
|
// Try to get incident weight of non-existing node
|
||||||
ASSERT_DEATH({ G.IncidentWeight(5); }, "");
|
ASSERT_DEATH({ graph.IncidentWeight(5); }, "");
|
||||||
ASSERT_DEATH({ G.IncidentWeight(100); }, "");
|
ASSERT_DEATH({ graph.IncidentWeight(100); }, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(Graph, Modularity) {
|
TEST(Graph, Modularity) {
|
||||||
// Graph without edges
|
// Graph without edges
|
||||||
comdata::Graph G = GenRandomUnweightedGraph(5, 0);
|
comdata::Graph graph = GenRandomUnweightedGraph(5, 0);
|
||||||
ASSERT_EQ(G.Modularity(), 0);
|
ASSERT_EQ(graph.Modularity(), 0);
|
||||||
|
|
||||||
// Chain
|
// Chain
|
||||||
// (0)--(1)--(2)--(3)--(4)
|
// (0)--(1)--(2)--(3)--(4)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{1, 2, 0.5},
|
{1, 2, 0.5},
|
||||||
{2, 3, 2.3},
|
{2, 3, 2.3},
|
||||||
{3, 4, 4.2}});
|
{3, 4, 4.2}});
|
||||||
std::vector<uint32_t> c = {0, 1, 1, 2, 2};
|
std::vector<uint32_t> c = {0, 1, 1, 2, 2};
|
||||||
set_communities(&G, c);
|
SetCommunities(&graph, c);
|
||||||
EXPECT_NEAR(G.Modularity(), 0.37452886332076973, 1e-6);
|
EXPECT_NEAR(graph.Modularity(), 0.37452886332076973, 1e-6);
|
||||||
|
|
||||||
// Tree
|
// Tree
|
||||||
// (0)--(3)
|
// (0)--(3)
|
||||||
@ -276,22 +281,22 @@ TEST(Graph, Modularity) {
|
|||||||
// (1) (2)
|
// (1) (2)
|
||||||
// | / \
|
// | / \
|
||||||
// (4) (5) (6)
|
// (4) (5) (6)
|
||||||
G = BuildGraph(7, {{0, 1, 1.3},
|
graph = BuildGraph(7, {{0, 1, 1.3},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 1},
|
{0, 3, 1},
|
||||||
{1, 4, 3.2},
|
{1, 4, 3.2},
|
||||||
{2, 5, 4.2},
|
{2, 5, 4.2},
|
||||||
{2, 6, 0.7}});
|
{2, 6, 0.7}});
|
||||||
c = {0, 0, 1, 0, 0, 1, 2};
|
c = {0, 0, 1, 0, 0, 1, 2};
|
||||||
set_communities(&G, c);
|
SetCommunities(&graph, c);
|
||||||
EXPECT_NEAR(G.Modularity(), 0.6945087219651122, 1e-6);
|
EXPECT_NEAR(graph.Modularity(), 0.6945087219651122, 1e-6);
|
||||||
|
|
||||||
// Graph without self-loops
|
// Graph without self-loops
|
||||||
// (0)--(1)
|
// (0)--(1)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2)--(3)-(4)
|
// (2)--(3)-(4)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 0.3},
|
{0, 3, 0.3},
|
||||||
{1, 3, 0.4},
|
{1, 3, 0.4},
|
||||||
@ -299,15 +304,15 @@ TEST(Graph, Modularity) {
|
|||||||
{2, 3, 0.6},
|
{2, 3, 0.6},
|
||||||
{3, 4, 0.7}});
|
{3, 4, 0.7}});
|
||||||
c = {0, 1, 1, 1, 1};
|
c = {0, 1, 1, 1, 1};
|
||||||
set_communities(&G, c);
|
SetCommunities(&graph, c);
|
||||||
EXPECT_NEAR(G.Modularity(), 0.32653061224489793, 1e-6);
|
EXPECT_NEAR(graph.Modularity(), 0.32653061224489793, 1e-6);
|
||||||
|
|
||||||
// Graph with self loop [*nodes have self loops]
|
// Graph with self loop [*nodes have self loops]
|
||||||
// (0)--(1*)
|
// (0)--(1*)
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// | \ | \
|
// | \ | \
|
||||||
// (2*)--(3)-(4*)
|
// (2*)--(3)-(4*)
|
||||||
G = BuildGraph(5, {{0, 1, 0.1},
|
graph = BuildGraph(5, {{0, 1, 0.1},
|
||||||
{0, 2, 0.2},
|
{0, 2, 0.2},
|
||||||
{0, 3, 0.3},
|
{0, 3, 0.3},
|
||||||
{1, 3, 0.4},
|
{1, 3, 0.4},
|
||||||
@ -318,6 +323,6 @@ TEST(Graph, Modularity) {
|
|||||||
{2, 2, 0.9},
|
{2, 2, 0.9},
|
||||||
{4, 4, 1}});
|
{4, 4, 1}});
|
||||||
c = {0, 0, 0, 0, 1};
|
c = {0, 0, 0, 0, 1};
|
||||||
set_communities(&G, c);
|
SetCommunities(&graph, c);
|
||||||
EXPECT_NEAR(G.Modularity(), 0.2754545454545455, 1e-6);
|
EXPECT_NEAR(graph.Modularity(), 0.2754545454545455, 1e-6);
|
||||||
}
|
}
|
||||||
|
@ -7,21 +7,6 @@
|
|||||||
|
|
||||||
#include "data_structures/graph.hpp"
|
#include "data_structures/graph.hpp"
|
||||||
|
|
||||||
/// This class is threadsafe
|
|
||||||
class Timer {
|
|
||||||
public:
|
|
||||||
Timer() : start_time_(std::chrono::steady_clock::now()) {}
|
|
||||||
|
|
||||||
template <typename TDuration = std::chrono::duration<double>>
|
|
||||||
TDuration Elapsed() const {
|
|
||||||
return std::chrono::duration_cast<TDuration>(
|
|
||||||
std::chrono::steady_clock::now() - start_time_);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::chrono::steady_clock::time_point start_time_;
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Builds the graph from a given number of nodes and a list of edges.
|
/// Builds the graph from a given number of nodes and a list of edges.
|
||||||
/// Nodes should be 0-indexed and each edge should be provided only once.
|
/// Nodes should be 0-indexed and each edge should be provided only once.
|
||||||
comdata::Graph BuildGraph(
|
comdata::Graph BuildGraph(
|
||||||
|
Loading…
Reference in New Issue
Block a user