From 2d3865079d53e5c7dff83df93b0a8f0dc2a44a3b Mon Sep 17 00:00:00 2001 From: Tiago Batista Cardoso Date: Sat, 28 Feb 2026 22:41:19 +0100 Subject: [PATCH] lol --- .idea/.gitignore | 10 -- .idea/editor.xml | 345 --------------------------------------------- .idea/graphe.iml | 2 - .idea/misc.xml | 7 - .idea/modules.xml | 8 -- .idea/vcs.xml | 6 - CMakeLists.txt | 8 +- README | 10 ++ algorithms.h | 22 ++- benchmark.c | 185 ++++++++++++++++++++++++ benchmark.h | 10 ++ cbla.c | 348 ++++++++++++++++++++++++++++++++++++++++++++++ cliques.c | 278 ++++++++++++++++++++++++++++++++++++ communities.c | 166 ---------------------- hybrid.c | 0 louvain.c | 14 +- main.c | 191 +++++++++++++++++++++++-- render.c | 16 ++- render.h | 4 +- structs.c | 11 ++ structs.h | 1 + 21 files changed, 1072 insertions(+), 570 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/editor.xml delete mode 100644 .idea/graphe.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml create mode 100644 README create mode 100644 benchmark.c create mode 100644 benchmark.h create mode 100644 cbla.c create mode 100644 cliques.c delete mode 100644 communities.c delete mode 100644 hybrid.c diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index ab1f416..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Ignored default folder with query files -/queries/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/editor.xml b/.idea/editor.xml deleted file mode 100644 index 8d0e15e..0000000 --- a/.idea/editor.xml +++ /dev/null @@ -1,345 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/graphe.iml b/.idea/graphe.iml deleted file mode 100644 index 4c94235..0000000 --- a/.idea/graphe.iml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 0b76fe5..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index d2060fb..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c320297..26975a3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,7 +11,11 @@ add_executable(graphe main.c render.h render.c algorithms.h - communities.c - louvain.c) + cliques.c + louvain.c + cbla.c + benchmark.h + benchmark.c +) target_link_libraries(graphe SDL2::SDL2 m) diff --git a/README b/README new file mode 100644 index 0000000..1e2196b --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +Dependances de compilation : +CMAKE, SDL2 + +Pour compiler : +mkdir build +cd build +cmake .. +make + +L'executable graphe est alors genere diff --git a/algorithms.h b/algorithms.h index ff57951..34f872f 100644 --- a/algorithms.h +++ b/algorithms.h @@ -21,6 +21,18 @@ struct community_result_t { }; typedef struct community_result_t community_result_t; +typedef struct { + int **list; + int count; + int capacity; + int k; +} clique_store_t; + +int find_max_clique_size(const graph_t *graph); +void compute_kclique_distribution(const graph_t *graph); +void store_clique(clique_store_t *store, int *current); +void enumerate_cliques(const graph_t *graph, clique_store_t *store, + int *current, int depth, int start); community_result_t *find_k_clique_communities(const graph_t *graph, int k); void free_community_result(community_result_t *result); @@ -35,14 +47,14 @@ 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 { +// -- cbla algorithm +struct cbla_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; +typedef struct cbla_result_t cbla_result_t; -hybrid_result_t *hybrid_community_detection(const graph_t *graph, int k); -void free_hybrid_result(hybrid_result_t *result); +cbla_result_t *cbla_community_detection(const graph_t *graph, int k); +void free_cbla_result(cbla_result_t *result); #endif diff --git a/benchmark.c b/benchmark.c new file mode 100644 index 0000000..9de9a23 --- /dev/null +++ b/benchmark.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include "benchmark.h" +#include "structs.h" +#include "algorithms.h" + +#define NUM_SIZES 5 +#define NUM_CONFIGS 5 +#define TOTAL_GRAPHS 5 + +static int graph_sizes[NUM_SIZES] = { 5, 10, 20, 100, 500 }; + +typedef struct { + int n; + double p; + double q; + int k; + int graph_index; + int config_index; + // results + double time_kclique; + double time_louvain; + double time_cbla; + int kclique_communities; + int louvain_communities; + int cbla_communities; + int done; + char error[128]; +} bench_task_t; + +static double elapsed_ms(struct timespec start, struct timespec end) +{ + return ((end.tv_sec - start.tv_sec) * 1000.0) + + ((end.tv_nsec - start.tv_nsec) / 1e6); +} + +static void *benchmark_thread(void *arg) +{ + bench_task_t *task = (bench_task_t *)arg; + struct timespec t0, t1; + + printf("[thread] config=%d graph=%d n=%-7d p=%.2f q=%.2f — generating...\n", + task->config_index + 1, task->graph_index + 1, task->n, task->p, + task->q); + + graph_t *graph = generate_graph(task->n, task->p, task->q, 122); + if (!graph) { + snprintf(task->error, sizeof(task->error), + "generate_graph failed"); + task->done = 1; + return NULL; + } + + clock_gettime(CLOCK_MONOTONIC, &t0); + community_result_t *kc = find_k_clique_communities(graph, task->k); + clock_gettime(CLOCK_MONOTONIC, &t1); + task->time_kclique = elapsed_ms(t0, t1); + task->kclique_communities = kc ? kc->count : 0; + free_community_result(kc); + + clock_gettime(CLOCK_MONOTONIC, &t0); + louvain_result_t *lv = compute_louvain(graph); + clock_gettime(CLOCK_MONOTONIC, &t1); + task->time_louvain = elapsed_ms(t0, t1); + task->louvain_communities = lv ? lv->count : 0; + free_louvain_result(lv); + + clock_gettime(CLOCK_MONOTONIC, &t0); + cbla_result_t *hy = cbla_community_detection(graph, task->k); + clock_gettime(CLOCK_MONOTONIC, &t1); + task->time_cbla = elapsed_ms(t0, t1); + task->cbla_communities = hy ? hy->count : 0; + free_cbla_result(hy); + + free_graph(graph); + + printf("[thread] config=%d graph=%d n=%-7d — done " + "(kclique=%.1fms louvain=%.1fms cbla=%.1fms)\n", + task->config_index + 1, task->graph_index + 1, task->n, + task->time_kclique, task->time_louvain, task->time_cbla); + + task->done = 1; + return NULL; +} + +void run_benchmark(void) +{ + // p/q configurations + double configs[NUM_CONFIGS][3] = { + { 1.0, 0.15, 3 }, { 1.0, 0.15, 3 }, { 0.8, 0.05, 3 }, + { 0.4, 0.02, 5 }, { 0.10, 0.005, 6 }, + }; + + bench_task_t tasks[TOTAL_GRAPHS]; + pthread_t threads[TOTAL_GRAPHS]; + int task_idx = 0; + + // Initialize all tasks + for (int c = 0; c < TOTAL_GRAPHS; c++) { + bench_task_t *t = &tasks[task_idx++]; + t->n = graph_sizes[c]; + t->p = configs[c][0]; + t->q = configs[c][1]; + t->k = configs[c][2]; + t->config_index = c; + t->graph_index = c; + t->done = 0; + t->error[0] = '\0'; + t->time_kclique = 0; + t->time_louvain = 0; + t->time_cbla = 0; + } + + printf("[benchmark] launching %d threads...\n", TOTAL_GRAPHS); + + // Launch one thread per task + for (int i = 0; i < TOTAL_GRAPHS; i++) + pthread_create(&threads[i], NULL, benchmark_thread, &tasks[i]); + + // Wait for all threads + for (int i = 0; i < TOTAL_GRAPHS; i++) + pthread_join(threads[i], NULL); + + printf("[benchmark] all threads done — writing CSV...\n"); + + // ── Build timestamped filename ──────────────────────────────────────────── + time_t now = time(NULL); + struct tm *tm = localtime(&now); + char filename[64]; + strftime(filename, sizeof(filename), "benchmark_%Y%m%d_%H%M%S.csv", tm); + + // ── Write CSV ───────────────────────────────────────────────────────────── + FILE *f = fopen(filename, "w"); + if (!f) { + perror("fopen"); + return; + } + + // Header + fprintf(f, "config,p,q,graph_size," + "kclique_time_ms,kclique_communities," + "louvain_time_ms,louvain_communities," + "cbla_time_ms,cbla_communities\n"); + + char config_names[NUM_CONFIGS][32] = { + "very_dense", "dense", "medium", "sparse", "very_sparse", + }; + + task_idx = 0; + for (int i = 0; i < TOTAL_GRAPHS; i++) { + bench_task_t *t = &tasks[i]; + + char kclique_time[16], kclique_comm[16]; + char cbla_time[16], cbla_comm[16]; + + if (t->time_kclique < 0) { + snprintf(kclique_time, sizeof(kclique_time), "N/A"); + snprintf(kclique_comm, sizeof(kclique_comm), "N/A"); + } else { + snprintf(kclique_time, sizeof(kclique_time), "%.4f", + t->time_kclique); + snprintf(kclique_comm, sizeof(kclique_comm), "%d", + t->kclique_communities); + } + + if (t->time_cbla < 0) { + snprintf(cbla_time, sizeof(cbla_time), "N/A"); + snprintf(cbla_comm, sizeof(cbla_comm), "N/A"); + } else { + snprintf(cbla_time, sizeof(cbla_time), "%.4f", + t->time_cbla); + snprintf(cbla_comm, sizeof(cbla_comm), "%d", + t->cbla_communities); + } + + fprintf(f, "%s,%.4f,%.6f,%d,%s,%s,%.4f,%d,%s,%s\n", + config_names[i], t->p, t->q, t->n, kclique_time, + kclique_comm, t->time_louvain, t->louvain_communities, + cbla_time, cbla_comm); + } + + fclose(f); + printf("[benchmark] results written to %s\n", filename); +} diff --git a/benchmark.h b/benchmark.h new file mode 100644 index 0000000..f23ca71 --- /dev/null +++ b/benchmark.h @@ -0,0 +1,10 @@ +// +// Created by Tiago Batista Cardoso on 2/28/2026. +// + +#ifndef GRAPHE_BENCHMARK_H +#define GRAPHE_BENCHMARK_H + +void run_benchmark(void); + +#endif diff --git a/cbla.c b/cbla.c new file mode 100644 index 0000000..77039b8 --- /dev/null +++ b/cbla.c @@ -0,0 +1,348 @@ +// +// Created by Tiago Batista Cardoso on 2/27/2026. +// + +#include +#include +#include +#include "algorithms.h" + +// Step 1 : find all k-cliques + +// Step 2 : build clique graph (simple graph) +// Nodes = cliques, edges between cliques sharing >= k-1 nodes + +//static graph_t *build_clique_graph(clique_store_t *store, int k) +//{ +// int nc = store->count; +// if (nc == 0) +// return NULL; +// +// graph_t *cg = malloc(sizeof(graph_t)); +// cg->n = nc; +// cg->p = 0; +// cg->q = 0; +// cg->adj_lists = calloc(nc, sizeof(node_t *)); +// +// for (int i = 0; i < nc; i++) { +// for (int j = i + 1; j < nc; j++) { +// // Count shared nodes between clique i and clique j +// 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) { +// // Add edge in clique graph +// node_t *n1 = malloc(sizeof(node_t)); +// n1->id = j; +// n1->next = cg->adj_lists[i]; +// cg->adj_lists[i] = n1; +// +// node_t *n2 = malloc(sizeof(node_t)); +// n2->id = i; +// n2->next = cg->adj_lists[j]; +// cg->adj_lists[j] = n2; +// } +// } +// } +// return cg; +//} +// +//static void free_clique_graph(graph_t *cg) +//{ +// for (int i = 0; i < cg->n; i++) { +// node_t *n = cg->adj_lists[i]; +// while (n) { +// node_t *tmp = n->next; +// free(n); +// n = tmp; +// } +// } +// free(cg->adj_lists); +// free(cg); +//} +// +//// ── Step 3 : map clique communities back to original nodes ──────────────────── +// +//static int *map_cliques_to_nodes(const graph_t *graph, clique_store_t *store, +// louvain_result_t *louvain_result, int k, +// int *classified) +//{ +// int n = graph->n; +// int *node_community = malloc(n * sizeof(int)); +// memset(node_community, -1, +// n * sizeof(int)); // -1 = not classified (ncn) +// +// for (int i = 0; i < store->count; i++) { +// int community = louvain_result->node_community[i]; +// for (int j = 0; j < k; j++) { +// int node = store->list[i][j]; +// node_community[node] = community; +// } +// } +// +// // Mark which nodes are classified +// for (int i = 0; i < n; i++) +// classified[i] = (node_community[i] != -1) ? 1 : 0; +// +// return node_community; +//} +// +//// ── Step 4 : assign ncn to nearest community ───────────────────────────────── +//// Each unclassified node joins the community it has the most edges into +// +//static void assign_ncn(const graph_t *graph, int *node_community, +// int *classified, int community_count) +//{ +// int n = graph->n; +// int changed = 1; +// +// // Iterate until no more ncn can be assigned (some may be fully isolated) +// while (changed) { +// changed = 0; +// for (int i = 0; i < n; i++) { +// if (classified[i]) +// continue; +// +// // Count edges to each community +// int *votes = calloc(community_count, sizeof(int)); +// int total = 0; +// +// node_t *nb = graph->adj_lists[i]; +// while (nb) { +// if (classified[nb->id]) { +// votes[node_community[nb->id]]++; +// total++; +// } +// nb = nb->next; +// } +// +// if (total == 0) { +// free(votes); +// continue; +// } // still isolated +// +// // Find community with most connections +// int best_c = -1, best_v = 0; +// for (int c = 0; c < community_count; c++) { +// if (votes[c] > best_v) { +// best_v = votes[c]; +// best_c = c; +// } +// } +// +// if (best_c != -1) { +// node_community[i] = best_c; +// classified[i] = 1; +// changed = 1; +// } +// free(votes); +// } +// } +// +// // Any remaining isolated ncn get their own community +// for (int i = 0; i < n; i++) { +// if (!classified[i]) { +// node_community[i] = community_count++; +// classified[i] = 1; +// } +// } +//} +// +//// ── Compact ids ─────────────────────────────────────────────────────────────── +// +//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] < 0) +// continue; +// if (map[community[i]] == -1) +// map[community[i]] = next++; +// community[i] = map[community[i]]; +// } +// free(map); +// return next; +//} +// +//// ── Main ────────────────────────────────────────────────────────────────────── +// +//cbla_result_t *cbla_community_detection(const graph_t *graph, int k) +//{ +// int n = graph->n; +// printf("[cbla] starting cbla k-clique + louvain (k=%d, n=%d)\n", k, n); +// +// // Step 1 — 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("[cbla] found %d %d-cliques\n", store.count, k); +// +// cbla_result_t *result = malloc(sizeof(cbla_result_t)); +// result->node_community = malloc(n * sizeof(int)); +// +// if (store.count == 0) { +// // No cliques found — assign all nodes to community 0 +// printf("[cbla] no cliques found, all nodes are ncn\n"); +// for (int i = 0; i < n; i++) +// result->node_community[i] = 0; +// result->count = 1; +// free(store.list); +// return result; +// } +// +// // Step 2 — build clique graph and apply Louvain on it +// graph_t *clique_graph = build_clique_graph(&store, k); +// printf("[cbla] clique graph built (%d nodes)\n", clique_graph->n); +// +// louvain_result_t *lv = compute_louvain(clique_graph); +// printf("[cbla] louvain found %d communities on clique graph\n", +// lv->count); +// +// // Step 3 — map clique communities back to original nodes +// int *classified = calloc(n, sizeof(int)); +// int *node_community = +// map_cliques_to_nodes(graph, &store, lv, k, classified); +// +// int ncn_count = 0; +// for (int i = 0; i < n; i++) +// if (!classified[i]) +// ncn_count++; +// printf("[cbla] %d non-classified nodes (ncn)\n", ncn_count); +// +// // Step 4 — assign ncn to nearest community +// assign_ncn(graph, node_community, classified, lv->count); +// +// // Compact and finalize +// result->count = compact(node_community, n); +// memcpy(result->node_community, node_community, n * sizeof(int)); +// printf("[cbla] final community count: %d\n", result->count); +// +// // Cleanup +// for (int i = 0; i < store.count; i++) +// free(store.list[i]); +// free(store.list); +// free_louvain_result(lv); +// free_clique_graph(clique_graph); +// free(node_community); +// free(classified); +// +// return result; +//} +// +void free_cbla_result(cbla_result_t *result) +{ + if (!result) + return; + free(result->node_community); + free(result); +} + +graph_t *build_meta_graph(const graph_t *graph, clique_store_t *store) +{ + graph_t *meta = malloc(sizeof(graph_t)); + meta->n = store->count; + meta->adj_lists = calloc(meta->n, sizeof(node_t *)); + + for (int i = 0; i < store->count; i++) { + for (int j = i + 1; j < store->count; j++) { + int connected = 0; + // Vérifier s'il existe une arête entre un nœud de clique i et clique j + for (int ni = 0; ni < store->k && !connected; ni++) { + for (int nj = 0; nj < store->k; nj++) { + if (has_edge(graph, store->list[i][ni], + store->list[j][nj])) { + connected = 1; + break; + } + } + } + if (connected) { + add_edge(meta, i, j); + add_edge(meta, j, i); + } + } + } + return meta; +} + +cbla_result_t *cbla_community_detection(const graph_t *graph, int k) +{ + cbla_result_t *final_result = malloc(sizeof(cbla_result_t)); + final_result->node_community = malloc(graph->n * sizeof(int)); + // Initialisation à -1 (non classifié) + for (int i = 0; i < graph->n; i++) + final_result->node_community[i] = -1; + + // 1. Énumérer toutes les k-cliques + clique_store_t store = { NULL, 0, 10, k }; + store.list = malloc(store.capacity * sizeof(int *)); + int *current_clique = malloc(k * sizeof(int)); + + enumerate_cliques(graph, &store, current_clique, 0, 0); + free(current_clique); + + if (store.count == 0) { + // Cas dégradé : pas de cliques, on pourrait renvoyer Louvain simple + final_result->count = 0; + free(store.list); + return final_result; + } + + // 2. Reconstruire le méta-graphe (Graphe de cliques) + // Chaque clique devient un "super-nœud" + //graph_t *meta_graph = malloc(sizeof(graph_t)); + //meta_graph->n = store.count; + // Initialisation simplifiée de l'adjacence du méta-graphe + // (Supposons une matrice ou liste d'adjacence pour meta_graph) + // On connecte Clique A et Clique B s'il existe une arête entre un noeud de A et un noeud de B + + // NOTE: Pour Louvain, nous devons construire meta_graph proprement (adjacences) + // Ici, nous simulons la création pour l'algorithme + graph_t *meta_graph = build_meta_graph(graph, &store); + + // 3. Appliquer Louvain sur le méta-graphe + louvain_result_t *l_res = compute_louvain(meta_graph); + + // 4. Mapper les résultats : Nœuds originaux appartenant aux cliques + for (int c_idx = 0; c_idx < store.count; c_idx++) { + int community_id = l_res->node_community[c_idx]; + for (int i = 0; i < k; i++) { + int original_node = store.list[c_idx][i]; + final_result->node_community[original_node] = + community_id; + } + } + + // 5. Gérer les NCN (Non-Classified Nodes) + // On les affecte à la première communauté d'un voisin classifié + // ou à une nouvelle communauté isolée. + for (int i = 0; i < graph->n; i++) { + if (final_result->node_community[i] == -1) { + // Logique simplifiée : on cherche un voisin déjà classé + // Sinon, on le laisse à -1 ou on crée une ID unique + final_result->node_community[i] = + 0; // Exemple : par défaut communauté 0 + } + } + + final_result->count = l_res->count; + + // Nettoyage + for (int i = 0; i < store.count; i++) + free(store.list[i]); + free(store.list); + free_louvain_result(l_res); + // free_graph(meta_graph); + + return final_result; +} diff --git a/cliques.c b/cliques.c new file mode 100644 index 0000000..21eed35 --- /dev/null +++ b/cliques.c @@ -0,0 +1,278 @@ +// +// Created by Tiago Batista Cardoso on 2/26/2026. +// + +#include +#include +#include +#include "algorithms.h" +#include + +static int degree(const graph_t *graph, int v) +{ + int d = 0; + node_t *n = graph->adj_lists[v]; + while (n) { + d++; + n = n->next; + } + return d; +} + +// Sort vertices by degree descending +static int *sort_by_degree_desc(const graph_t *graph) +{ + int n = graph->n; + int *order = malloc(n * sizeof(int)); + for (int i = 0; i < n; i++) + order[i] = i; + + // Simple insertion sort — fine for this use case + for (int i = 1; i < n; i++) { + int key = order[i]; + int key_deg = degree(graph, key); + int j = i - 1; + while (j >= 0 && degree(graph, order[j]) < key_deg) { + order[j + 1] = order[j]; + j--; + } + order[j + 1] = key; + } + return order; +} + +// Check if vertex v is adjacent to all vertices in clique +static int adjacent_to_all(const graph_t *graph, int *clique, int size, int v) +{ + for (int i = 0; i < size; i++) + if (!has_edge(graph, v, clique[i])) + return 0; + return 1; +} + +int find_max_clique_size(const graph_t *graph) +{ + int n = graph->n; + + int *order = sort_by_degree_desc(graph); + int *max_clique = malloc(n * sizeof(int)); + int *curr_clique = malloc(n * sizeof(int)); + int max_size = 0; + + for (int i = 0; i < n; i++) { + int v = order[i]; + + curr_clique[0] = v; + int curr_size = 1; + + node_t *nb = graph->adj_lists[v]; + while (nb) { + if (adjacent_to_all(graph, curr_clique, curr_size, + nb->id)) + curr_clique[curr_size++] = nb->id; + nb = nb->next; + } + + if (curr_size > max_size) { + max_size = curr_size; + memcpy(max_clique, curr_clique, + curr_size * sizeof(int)); + + printf("[max_clique] new max clique of size %d: {", + max_size); + for (int j = 0; j < max_size; j++) + printf("%d%s", max_clique[j], + j < max_size - 1 ? ", " : ""); + printf("}\n"); + } + } + + printf("[max_clique] final max clique size: %d\n", max_size); + + free(order); + free(max_clique); + free(curr_clique); + return max_size; +} + +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; +} + +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++) { + if (depth > 0 && v <= current[depth - 1]) + continue; + + // 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]++; +} + +void compute_kclique_distribution(const graph_t *graph) +{ + printf("\n=== Distribution des k-cliques ===\n"); + printf("%-5s %-15s %-20s\n", "k", "communautes", "noeuds_classes"); + + int max_clique = find_max_clique_size(graph); + + for (int k = 2; k <= max_clique; k++) { + community_result_t *result = + find_k_clique_communities(graph, k); + + if (!result || result->count == 0) { + printf("%-5d %-15d (aucune clique de taille %d)\n", k, + 0, k); + free_community_result(result); + break; // inutile de continuer, k plus grand donnera aussi 0 + } + + // Compter les noeuds effectivement classés (non isolés) + int classified = 0; + for (int i = 0; i < graph->n; i++) + if (result->node_community[i] != -1) + classified++; + + printf("%-5d %-15d %-20d\n", k, result->count, classified); + free_community_result(result); + } + printf("==================================\n"); +} + +community_result_t *find_k_clique_communities(const graph_t *graph, int k) +{ + clock_t start, end; + double cpu_time_used; + printf("[find_k_clique_communities()] starting...\n"); + start = clock(); + + 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); + + // union-find : merge cliques sharing k-1 nodes + 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); + + end = clock(); + cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + printf("[find_k_clique_communities()] done (%f s)\n", cpu_time_used); + + // 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/communities.c b/communities.c deleted file mode 100644 index e4d48fa..0000000 --- a/communities.c +++ /dev/null @@ -1,166 +0,0 @@ -// -// 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 deleted file mode 100644 index e69de29..0000000 diff --git a/louvain.c b/louvain.c index a8a9e61..4bf1d31 100644 --- a/louvain.c +++ b/louvain.c @@ -5,8 +5,8 @@ #include #include #include -#include #include "algorithms.h" +#include // -- helper methods @@ -49,7 +49,7 @@ static double k_i_in(const graph_t *graph, int i, int c, int *community) return sum; } -// Sum of degrees of all nodes in community c +// 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; @@ -171,6 +171,11 @@ static int compact(int *community, int n) louvain_result_t *compute_louvain(const graph_t *graph) { + clock_t start, end; + double cpu_time_used; + printf("[compute_louvain()] starting...\n"); + start = clock(); + int n = graph->n; double m = (double)count_edges(graph); @@ -203,6 +208,11 @@ louvain_result_t *compute_louvain(const graph_t *graph) result->node_community = community; result->count = count; result->modularity = Q; + + end = clock(); + cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + printf("[compute_louvain()] done (%f s)\n", cpu_time_used); + return result; } diff --git a/main.c b/main.c index e3b60a4..6f50259 100644 --- a/main.c +++ b/main.c @@ -1,40 +1,205 @@ +#include "algorithms.h" +#include "benchmark.h" #include "structs.h" #include "render.h" #include +#include #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 -int main(int argc, char *argv[]) +static void print_usage(const char *prog) { - // Figure 1 - graph_t *g = generate_graph(20, 1, 0.04, 122); + fprintf(stderr, + "Usage:\n" + " %s -fig1|-fig2|-fig3|-fig4|-fig5\n" + " %s -b\n" + " %s

\n\n" + "Options:\n" + " -fig1 preset: n=20, p=0.9, q=0.25\n" + " -fig2 preset: n=50, p=0.7, q=0.15\n" + " -fig3 preset: n=100, p=0.5, q=0.05\n" + " -fig4 preset: n=200, p=0.2, q=0.02\n" + " -fig5 preset: n=500, p=0.01, q=0.00025\n" + " -b run benchmark\n" + " n number of nodes\n" + " p intra-group edge probability\n" + " q inter-group edge probability\n" + " algo clique | louvain | cbla\n", + prog, prog, prog); +} - SDL_Init(SDL_INIT_VIDEO); +typedef struct { + int n; + double p; + double q; + int seed; + int k; + VISUALIZATION_TYPE algo; +} run_config_t; - SDL_Window *window = SDL_CreateWindow( - "Graph Render", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, - WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_SHOWN); - SDL_Renderer *renderer = - SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); +static int parse_algo(const char *s, VISUALIZATION_TYPE *out) +{ + if (strcmp(s, "clique") == 0) { + *out = CLIQUE; + return 1; + } else if (strcmp(s, "louvain") == 0) { + *out = LOUVAIN; + return 1; + } else if (strcmp(s, "cbla") == 0) { + *out = CBLA; + return 1; + } + return 0; +} - // Figure 2 - render_graph(renderer, g, LOUVAIN); +static int parse_fig(const char *arg, run_config_t *cfg) +{ + if (strcmp(arg, "-fig1") == 0) { + cfg->n = 20; + cfg->p = 1.0; + cfg->q = 0.04; + cfg->k = 3; + cfg->seed = 122; + cfg->algo = CLIQUE; + return 1; + } else if (strcmp(arg, "-fig2") == 0) { + cfg->n = 20; + cfg->q = 1.0; + cfg->p = 0.04; + cfg->k = 3; + cfg->seed = 122; + cfg->algo = CLIQUE; + return 1; + } else if (strcmp(arg, "-fig3") == 0) { + cfg->n = 20; + cfg->p = 0.04; + cfg->q = 0.04; + cfg->k = 3; + cfg->seed = 122; + cfg->algo = CLIQUE; + return 1; + } else if (strcmp(arg, "-fig4") == 0) { + cfg->n = 20; + cfg->p = 0.8; + cfg->q = 0.10; + cfg->k = 0; + cfg->seed = 122; + cfg->algo = LOUVAIN; + return 1; + } else if (strcmp(arg, "-fig5") == 0) { + cfg->n = 20; + cfg->p = 0.8; + cfg->q = 0.10; + cfg->k = 3; + cfg->seed = 122; + cfg->algo = CBLA; + return 1; + } + return 0; +} +static void run_sdl(run_config_t *cfg) +{ + printf("[main] generating graph n=%d p=%.4f q=%.6f\n", cfg->n, cfg->p, + cfg->q); + + graph_t *graph = generate_graph(cfg->n, cfg->p, cfg->q, cfg->seed); + if (!graph) { + fprintf(stderr, "[main] generate_graph failed\n"); + return; + } + + if (SDL_Init(SDL_INIT_VIDEO) < 0) { + fprintf(stderr, "SDL_Init: %s\n", SDL_GetError()); + free_graph(graph); + return; + } + SDL_Window *window = + SDL_CreateWindow("Graph Visualizer", SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, WINDOW_WIDTH, + WINDOW_HEIGHT, SDL_WINDOW_SHOWN); + + SDL_Renderer *renderer = SDL_CreateRenderer( + window, -1, + SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); + + render_graph(renderer, graph, cfg->algo, cfg->k); + + // Event loop SDL_Event e; int running = 1; while (running) { - while (SDL_PollEvent(&e)) + while (SDL_PollEvent(&e)) { if (e.type == SDL_QUIT) running = 0; + if (e.type == SDL_KEYDOWN && + e.key.keysym.sym == SDLK_ESCAPE) + running = 0; + // Re-render on window resize + if (e.type == SDL_WINDOWEVENT && + e.window.event == SDL_WINDOWEVENT_RESIZED) + render_graph(renderer, graph, cfg->algo, + cfg->k); + } SDL_Delay(16); } SDL_DestroyRenderer(renderer); SDL_DestroyWindow(window); SDL_Quit(); + free_graph(graph); +} - free_graph(g); +int main(int argc, char *argv[]) +{ + if (argc < 2) { + print_usage(argv[0]); + return 1; + } + + if (strcmp(argv[1], "-b") == 0) { + run_benchmark(); + return 0; + } + + run_config_t cfg = { + .n = 0, .p = 0, .q = 0, .seed = 42, .algo = CLIQUE + }; + + if (parse_fig(argv[1], &cfg)) { + // optionally override algo: ./graphe -fig3 louvain + if (argc >= 3 && !parse_algo(argv[2], &cfg.algo)) { + fprintf(stderr, "[main] unknown algo '%s'\n", argv[2]); + print_usage(argv[0]); + return 1; + } + run_sdl(&cfg); + return 0; + } + + if (argc < 5) { + print_usage(argv[0]); + return 1; + } + + cfg.n = atoi(argv[1]); + cfg.p = atof(argv[2]); + cfg.q = atof(argv[3]); + + if (cfg.n <= 0 || cfg.p < 0 || cfg.p > 1 || cfg.q < 0 || cfg.q > 1) { + fprintf(stderr, + "[main] invalid arguments: n must be > 0, p and q in [0,1]\n"); + return 1; + } + if (!parse_algo(argv[4], &cfg.algo)) { + fprintf(stderr, "[main] unknown algo '%s'\n", argv[4]); + print_usage(argv[0]); + return 1; + } + + run_sdl(&cfg); + return 0; return 0; } diff --git a/render.c b/render.c index ae6391b..13f7652 100644 --- a/render.c +++ b/render.c @@ -132,7 +132,7 @@ static SDL_Color community_colors[] = { #define N_COLORS (sizeof(community_colors) / sizeof(community_colors[0])) void render_graph(SDL_Renderer *renderer, const graph_t *graph, - VISUALIZATION_TYPE type) + VISUALIZATION_TYPE type, int k) { if (!renderer || !graph || !graph->adj_lists) return; @@ -145,14 +145,18 @@ void render_graph(SDL_Renderer *renderer, const graph_t *graph, community_result_t *communities = NULL; louvain_result_t *louvain = NULL; + cbla_result_t *cbla = NULL; switch (type) { case CLIQUE: - communities = find_k_clique_communities(graph, 3); + communities = find_k_clique_communities(graph, k); break; case LOUVAIN: louvain = compute_louvain(graph); break; + case CBLA: + cbla = cbla_community_detection(graph, k); + break; } SDL_SetRenderDrawColor(renderer, 48, 48, 48, 255); @@ -193,6 +197,11 @@ void render_graph(SDL_Renderer *renderer, const graph_t *graph, col = (c == -1) ? (SDL_Color){ 168, 153, 132, 255 } : community_colors[c % N_COLORS]; break; + case CBLA: + c = cbla->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); @@ -206,6 +215,9 @@ void render_graph(SDL_Renderer *renderer, const graph_t *graph, if (louvain != NULL) { free_louvain_result(louvain); } + if (cbla != NULL) { + free_cbla_result(cbla); + } free(layout); SDL_RenderPresent(renderer); diff --git a/render.h b/render.h index a645d59..1ace07b 100644 --- a/render.h +++ b/render.h @@ -8,10 +8,10 @@ #include "structs.h" #include -enum VISUALIZATION_TYPE { CLIQUE, LOUVAIN }; +enum VISUALIZATION_TYPE { CLIQUE, LOUVAIN, CBLA }; typedef enum VISUALIZATION_TYPE VISUALIZATION_TYPE; void render_graph(SDL_Renderer *renderer, const graph_t *graph, - VISUALIZATION_TYPE); + VISUALIZATION_TYPE, int k); #endif diff --git a/structs.c b/structs.c index 4981a3e..f00bb76 100644 --- a/structs.c +++ b/structs.c @@ -52,6 +52,17 @@ void add_edge(graph_t *graph, int src, int dest) graph->adj_lists[dest] = new_node; } +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; +} + graph_t *basic_graph() { clock_t start, end; diff --git a/structs.h b/structs.h index f0637b0..1d4bd4d 100644 --- a/structs.h +++ b/structs.h @@ -20,6 +20,7 @@ struct graph_t { typedef struct graph_t graph_t; // structure-related functions +int has_edge(const graph_t *graph, int u, int v); 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);