diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a1eb46..c320297 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,9 @@ add_executable(graphe main.c structs.c structs.h render.h - render.c) + render.c + algorithms.h + communities.c + louvain.c) target_link_libraries(graphe SDL2::SDL2 m) diff --git a/algorithms.h b/algorithms.h new file mode 100644 index 0000000..ff57951 --- /dev/null +++ b/algorithms.h @@ -0,0 +1,48 @@ +// +// Created by Tiago Batista Cardoso on 2/26/2026. +// + +#ifndef GRAPHE_ALGORITHMS_H +#define GRAPHE_ALGORITHMS_H + +#include "structs.h" + +// -- k-clique communities +struct community_t { + int *members; // node ids in this community + int size; +}; +typedef struct community_t community_t; + +struct community_result_t { + community_t *communities; + int count; + int *node_community; // node_community[i] = community index of node i (-1 if none) +}; +typedef struct community_result_t community_result_t; + +community_result_t *find_k_clique_communities(const graph_t *graph, int k); +void free_community_result(community_result_t *result); + +// -- louvain +struct louvain_result_t { + int *node_community; // node_community[i] = community id of node i + int count; // total number of communities + double modularity; // final modularity score +}; +typedef struct louvain_result_t louvain_result_t; + +louvain_result_t *compute_louvain(const graph_t *graph); +void free_louvain_result(louvain_result_t *result); + +// -- hybrid algorithm +struct hybrid_result_t { + int *node_community; // node_community[i] = community id of node i + int count; // total number of communities +}; +typedef struct hybrid_result_t hybrid_result_t; + +hybrid_result_t *hybrid_community_detection(const graph_t *graph, int k); +void free_hybrid_result(hybrid_result_t *result); + +#endif diff --git a/communities.c b/communities.c new file mode 100644 index 0000000..e4d48fa --- /dev/null +++ b/communities.c @@ -0,0 +1,166 @@ +// +// Created by Tiago Batista Cardoso on 2/26/2026. +// + +#include +#include +#include +#include "algorithms.h" + +static int has_edge(const graph_t *graph, int u, int v) +{ + node_t *n = graph->adj_lists[u]; + while (n) { + if (n->id == v) + return 1; + n = n->next; + } + return 0; +} + +typedef struct { + int **list; + int count; + int capacity; + int k; +} clique_store_t; + +static void store_clique(clique_store_t *store, int *current) +{ + if (store->count >= store->capacity) { + store->capacity *= 2; + store->list = + realloc(store->list, store->capacity * sizeof(int *)); + } + int *copy = malloc(store->k * sizeof(int)); + memcpy(copy, current, store->k * sizeof(int)); + store->list[store->count++] = copy; +} + +static void enumerate_cliques(const graph_t *graph, clique_store_t *store, + int *current, int depth, int start) +{ + if (depth == store->k) { + store_clique(store, current); + return; + } + for (int v = start; v < graph->n; v++) { + // v must be connected to all nodes already in current + int ok = 1; + for (int i = 0; i < depth; i++) { + if (!has_edge(graph, current[i], v)) { + ok = 0; + break; + } + } + if (!ok) + continue; + current[depth] = v; + enumerate_cliques(graph, store, current, depth + 1, v + 1); + } +} + +static int uf_find(int *parent, int x) +{ + while (parent[x] != x) { + parent[x] = parent[parent[x]]; + x = parent[x]; + } + return x; +} + +static void uf_union(int *parent, int *rank, int a, int b) +{ + a = uf_find(parent, a); + b = uf_find(parent, b); + if (a == b) + return; + if (rank[a] < rank[b]) { + int t = a; + a = b; + b = t; + } + parent[b] = a; + if (rank[a] == rank[b]) + rank[a]++; +} + +community_result_t *find_k_clique_communities(const graph_t *graph, int k) +{ + int n = graph->n; + + // find all k-cliques + clique_store_t store = { .list = malloc(64 * sizeof(int *)), + .count = 0, + .capacity = 64, + .k = k }; + int *current = malloc(k * sizeof(int)); + enumerate_cliques(graph, &store, current, 0, 0); + free(current); + + printf("[communities] found %d %d-cliques\n", store.count, k); + + int *parent = malloc(store.count * sizeof(int)); + int *rank = calloc(store.count, sizeof(int)); + for (int i = 0; i < store.count; i++) + parent[i] = i; + + for (int i = 0; i < store.count; i++) { + for (int j = i + 1; j < store.count; j++) { + // Count shared nodes + int shared = 0; + for (int a = 0; a < k; a++) + for (int b = 0; b < k; b++) + if (store.list[i][a] == + store.list[j][b]) + shared++; + if (shared >= k - 1) + uf_union(parent, rank, i, j); + } + } + + community_result_t *result = malloc(sizeof(community_result_t)); + result->node_community = malloc(n * sizeof(int)); + for (int i = 0; i < n; i++) + result->node_community[i] = -1; + + for (int i = 0; i < store.count; i++) { + int community_id = uf_find(parent, i); + for (int j = 0; j < k; j++) { + int node = store.list[i][j]; + result->node_community[node] = community_id; + } + } + + int *id_map = malloc(store.count * sizeof(int)); + memset(id_map, -1, store.count * sizeof(int)); + int next_id = 0; + for (int i = 0; i < n; i++) { + int c = result->node_community[i]; + if (c == -1) + continue; + if (id_map[c] == -1) + id_map[c] = next_id++; + result->node_community[i] = id_map[c]; + } + result->count = next_id; + printf("[communities] found %d communities\n", result->count); + + // cleanup + for (int i = 0; i < store.count; i++) + free(store.list[i]); + free(store.list); + free(parent); + free(rank); + free(id_map); + + return result; +} + +void free_community_result(community_result_t *result) +{ + if (!result) + return; + free(result->node_community); + free(result); +} diff --git a/hybrid.c b/hybrid.c new file mode 100644 index 0000000..e69de29 diff --git a/louvain.c b/louvain.c new file mode 100644 index 0000000..a8a9e61 --- /dev/null +++ b/louvain.c @@ -0,0 +1,215 @@ +// +// Created by Tiago Batista Cardoso on 2/26/2026. +// + +#include +#include +#include +#include +#include "algorithms.h" + +// -- helper methods + +// total number of edges +static int count_edges(const graph_t *graph) +{ + int count = 0; + for (int i = 0; i < graph->n; i++) { + node_t *n = graph->adj_lists[i]; + while (n) { + count++; + n = n->next; + } + } + return count / 2; // undirected +} + +// degree of node i +static int degree(const graph_t *graph, int i) +{ + int d = 0; + node_t *n = graph->adj_lists[i]; + while (n) { + d++; + n = n->next; + } + return d; +} + +// sum of weights of edges from node i to community c +static double k_i_in(const graph_t *graph, int i, int c, int *community) +{ + double sum = 0.0; + node_t *n = graph->adj_lists[i]; + while (n) { + if (community[n->id] == c) + sum += 1.0; + n = n->next; + } + return sum; +} + +// Sum of degrees of all nodes in community c +static double sigma_tot(const graph_t *graph, int c, int *community) +{ + double sum = 0.0; + for (int i = 0; i < graph->n; i++) + if (community[i] == c) + sum += degree(graph, i); + return sum; +} + +// ΔQ = [ (k_i_in - sigma_tot * k_i / 2m) / m ] +static double delta_modularity(const graph_t *graph, int i, int c, + int *community, double m) +{ + double ki = degree(graph, i); + double ki_in = k_i_in(graph, i, c, community); + double sig = sigma_tot(graph, c, community); + + return (ki_in - (sig * ki) / (2.0 * m)) / m; +} + +static int phase1(const graph_t *graph, int *community, double m) +{ + int n = graph->n; + int improved = 1; + int total_moves = 0; + + while (improved) { + improved = 0; + for (int i = 0; i < n; i++) { + int best_community = community[i]; + double best_gain = 0.0; + + // Collect neighboring communities + int *neighbor_communities = calloc(n, sizeof(int)); + int nc_count = 0; + node_t *nb = graph->adj_lists[i]; + while (nb) { + int c = community[nb->id]; + // Check if already in list + int found = 0; + for (int x = 0; x < nc_count; x++) + if (neighbor_communities[x] == c) { + found = 1; + break; + } + if (!found) + neighbor_communities[nc_count++] = c; + nb = nb->next; + } + + // temporarily remove i from its community + int old_community = community[i]; + community[i] = -1; + + // try moving i to each neighboring community + for (int x = 0; x < nc_count; x++) { + int c = neighbor_communities[x]; + if (c == old_community) + continue; + double gain = delta_modularity(graph, i, c, + community, m) - + delta_modularity(graph, i, + old_community, + community, m); + if (gain > best_gain) { + best_gain = gain; + best_community = c; + } + } + + community[i] = best_community; + + if (best_community != old_community) { + improved = 1; + total_moves++; + } + + free(neighbor_communities); + } + } + + return total_moves; +} + +static double compute_modularity(const graph_t *graph, int *community, double m) +{ + int n = graph->n; + double Q = 0.0; + + for (int i = 0; i < n; i++) { + node_t *nb = graph->adj_lists[i]; + while (nb) { + int j = nb->id; + if (community[i] == community[j]) + Q += 1.0 - ((double)degree(graph, i) * + degree(graph, j)) / + (2.0 * m); + nb = nb->next; + } + } + return Q / (2.0 * m); +} + +static int compact(int *community, int n) +{ + int *map = malloc(n * sizeof(int)); + memset(map, -1, n * sizeof(int)); + int next = 0; + for (int i = 0; i < n; i++) { + if (community[i] == -1) + continue; + if (map[community[i]] == -1) + map[community[i]] = next++; + community[i] = map[community[i]]; + } + free(map); + return next; +} + +louvain_result_t *compute_louvain(const graph_t *graph) +{ + int n = graph->n; + double m = (double)count_edges(graph); + + if (m == 0) { + printf("[louvain] no edges found\n"); + louvain_result_t *r = malloc(sizeof(louvain_result_t)); + r->node_community = calloc(n, sizeof(int)); + r->count = n; + r->modularity = 0.0; + return r; + } + + // each node starts in its own community + int *community = malloc(n * sizeof(int)); + for (int i = 0; i < n; i++) + community[i] = i; + + printf("[louvain] m = %.0f, n = %d\n", m, n); + + int moves = phase1(graph, community, m); + printf("[louvain] phase 1 done — %d moves\n", moves); + + double Q = compute_modularity(graph, community, m); + printf("[louvain] modularity Q = %.4f\n", Q); + + int count = compact(community, n); + printf("[louvain] %d communities detected\n", count); + + louvain_result_t *result = malloc(sizeof(louvain_result_t)); + result->node_community = community; + result->count = count; + result->modularity = Q; + return result; +} + +void free_louvain_result(louvain_result_t *result) +{ + if (!result) + return; + free(result->node_community); + free(result); +} diff --git a/main.c b/main.c index eac91f2..e3b60a4 100644 --- a/main.c +++ b/main.c @@ -1,17 +1,14 @@ #include "structs.h" #include "render.h" -#include -#include #include #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 -int main(void) +int main(int argc, char *argv[]) { - srand(time(0)); - - graph_t *g = generate_graph(20, 1, 0.04); + // Figure 1 + graph_t *g = generate_graph(20, 1, 0.04, 122); SDL_Init(SDL_INIT_VIDEO); @@ -21,7 +18,8 @@ int main(void) SDL_Renderer *renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); - render_graph(renderer, g); + // Figure 2 + render_graph(renderer, g, LOUVAIN); SDL_Event e; int running = 1; diff --git a/render.c b/render.c index 1a87bbd..ae6391b 100644 --- a/render.c +++ b/render.c @@ -1,3 +1,5 @@ +#include "render.h" +#include "algorithms.h" #include "structs.h" #include #include @@ -19,7 +21,7 @@ typedef struct layout_node_t layout_node_t; // display a simple node of radius r static void draw_node(SDL_Renderer *renderer, int cx, int cy, int r) { - SDL_SetRenderDrawColor(renderer, 215, 153, 33, 255); + //SDL_SetRenderDrawColor(renderer, 215, 153, 33, 255); for (int dy = -r; dy <= r; dy++) { int dx = (int)sqrt((double)(r * r - dy * dy)); SDL_RenderDrawLine(renderer, cx - dx, cy + dy, cx + dx, @@ -118,7 +120,19 @@ static layout_node_t *compute_layout(const graph_t *graph) return nodes; } -void render_graph(SDL_Renderer *renderer, const graph_t *graph) +static SDL_Color community_colors[] = { + { 251, 73, 52, 255 }, // gruvbox red + { 250, 189, 47, 255 }, // gruvbox yellow + { 142, 192, 124, 255 }, // gruvbox green + { 131, 165, 152, 255 }, // gruvbox aqua + { 69, 133, 136, 255 }, // gruvbox blue + { 211, 134, 155, 255 }, // gruvbox pink + { 254, 128, 25, 255 }, // gruvbox orange +}; +#define N_COLORS (sizeof(community_colors) / sizeof(community_colors[0])) + +void render_graph(SDL_Renderer *renderer, const graph_t *graph, + VISUALIZATION_TYPE type) { if (!renderer || !graph || !graph->adj_lists) return; @@ -129,11 +143,21 @@ void render_graph(SDL_Renderer *renderer, const graph_t *graph) if (!layout) return; - // Clear background + community_result_t *communities = NULL; + louvain_result_t *louvain = NULL; + + switch (type) { + case CLIQUE: + communities = find_k_clique_communities(graph, 3); + break; + case LOUVAIN: + louvain = compute_louvain(graph); + break; + } + SDL_SetRenderDrawColor(renderer, 48, 48, 48, 255); SDL_RenderClear(renderer); - // Draw edges SDL_SetRenderDrawColor(renderer, 189, 189, 189, 255); for (int i = 0; i < n; i++) { node_t *neighbor = graph->adj_lists[i]; @@ -155,10 +179,34 @@ void render_graph(SDL_Renderer *renderer, const graph_t *graph) int x = (int)layout[i].x; int y = (int)layout[i].y; + int c; + SDL_Color col; + + switch (type) { + case CLIQUE: + c = communities->node_community[i]; + col = (c == -1) ? (SDL_Color){ 150, 150, 150, 255 } : + community_colors[c % N_COLORS]; + break; + case LOUVAIN: + c = louvain->node_community[i]; + col = (c == -1) ? (SDL_Color){ 168, 153, 132, 255 } : + community_colors[c % N_COLORS]; + break; + } + SDL_SetRenderDrawColor(renderer, col.r, col.g, col.b, col.a); + // Node fill draw_node(renderer, x, y, NODE_RADIUS); } + if (communities != NULL) { + free_community_result(communities); + } + if (louvain != NULL) { + free_louvain_result(louvain); + } + free(layout); SDL_RenderPresent(renderer); } diff --git a/render.h b/render.h index b5c38c4..a645d59 100644 --- a/render.h +++ b/render.h @@ -1,4 +1,17 @@ +// +// Created by Tiago Batista Cardoso on 2/23/2026. +// + +#ifndef GRAPHE_RENDER_H +#define GRAPHE_RENDER_H + #include "structs.h" #include -void render_graph(SDL_Renderer *renderer, const graph_t *graph); +enum VISUALIZATION_TYPE { CLIQUE, LOUVAIN }; +typedef enum VISUALIZATION_TYPE VISUALIZATION_TYPE; + +void render_graph(SDL_Renderer *renderer, const graph_t *graph, + VISUALIZATION_TYPE); + +#endif diff --git a/structs.c b/structs.c index df2e2e1..4981a3e 100644 --- a/structs.c +++ b/structs.c @@ -28,19 +28,6 @@ graph_t *create_graph(int n, double p, double q) return new_graph; } -//void add_edge(graph_t *graph, int src, int dest) -//{ -// // src -> dest -// node_t *new_node = create_node(dest); -// new_node->next = graph->adj_lists[src]; -// graph->adj_lists[src] = new_node; -// -// // dest -> src -// new_node = create_node(src); -// new_node->next = graph->adj_lists[dest]; -// graph->adj_lists[dest] = new_node; -//} - void add_edge(graph_t *graph, int src, int dest) { // Guard against self-loops and out-of-bounds @@ -89,7 +76,7 @@ graph_t *basic_graph() return basic; } -graph_t *generate_graph(int n, double p, double q) +graph_t *generate_graph(int n, double p, double q, int seed) { clock_t start, end; double cpu_time_used; @@ -100,6 +87,8 @@ graph_t *generate_graph(int n, double p, double q) graph_t *result = create_graph(n, p, q); int remaining = n; + srand(seed); + // Calcul des repartitions aleatoires int n1 = rand() % (remaining + 1); remaining -= n1; diff --git a/structs.h b/structs.h index 255fe22..f0637b0 100644 --- a/structs.h +++ b/structs.h @@ -24,7 +24,7 @@ node_t *create_node(int id); graph_t *create_graph(int n, double p, double q); void add_edge(graph_t *graph, int src, int dest); graph_t *basic_graph(); -graph_t *generate_graph(int n, double p, double q); +graph_t *generate_graph(int n, double p, double q, int seed); void displayGraph(graph_t *graph); void free_graph(graph_t *graph);