Took a bit of time to get used to C and the igraph library. Here I have implemented the 2 algorithms described in An Introduction to Clique Minimal Separator Decomposition. I mostly followed the pseudocode in the paper.
This draft of code seems to be working. I have not, for example, handled the errors, destroyed variables.
#include <igraph.h>
void MCS_M_plus(const igraph_t *g,
igraph_vector_int_t *alpha,
igraph_vector_int_t *min_sep_gen,
igraph_t *minimal_chordal);
void Atoms(const igraph_t *g,
const igraph_vector_int_t *alpha,
const igraph_vector_int_t *min_sep_gen,
const igraph_t *minimal_chordal,
igraph_vector_int_list_t *A,
igraph_vector_int_list_t *S_H,
igraph_vector_int_list_t *S_C);
int main() {
int n = 20;
int failed_cases = 0;
float radius = 0.5;
int num_cases = 10;
for (int i = 0; i < num_cases; i++) {
printf("======================================\n");
igraph_t g;
srand(i);
igraph_grg_game(&g, n, radius, 0, NULL, NULL);
igraph_t minimal_chordal;
igraph_vector_int_t alpha, min_sep_gen;
igraph_vector_int_init(&alpha, 0);
igraph_vector_int_init(&min_sep_gen, 0);
MCS_M_plus(&g, &alpha, &min_sep_gen, &minimal_chordal);
printf("alpha ");
igraph_vector_int_print(&alpha);
// igraph_is_chordal() on g
igraph_bool_t chordal;
igraph_vector_int_t fill_in;
igraph_vector_int_init(&fill_in, 0);
igraph_t newgraph;
igraph_is_chordal(&g, NULL, NULL, &chordal, &fill_in, &newgraph);
// compare number of edges
printf("g: %lld, minimal_chordal: %lld, newgraph: %lld, difference: %lld \n", igraph_ecount(&g), igraph_ecount(&minimal_chordal), igraph_ecount(&newgraph), igraph_ecount(&minimal_chordal) - igraph_ecount(&newgraph));
// check minimal_chordal is chordal
igraph_bool_t chordal_m;
igraph_vector_int_t fill_in_m;
igraph_vector_int_init(&fill_in_m, 0);
igraph_t newgraph_m;
igraph_is_chordal(&minimal_chordal, NULL, NULL, &chordal_m, &fill_in_m, &newgraph_m);
if (!chordal_m) {
printf("minimal_chordal is not chordal!");
failed_cases ++;
}
// check minimal_chordal is minimal
igraph_t g_small;
igraph_t newgraph_small;
igraph_es_t es;
igraph_vector_int_t fill_in_small;
igraph_bool_t chordal_small;
for (int j = 0; j < igraph_ecount(&minimal_chordal) - igraph_ecount(&g); j++) {
igraph_copy(&g_small, &minimal_chordal);
igraph_es_1(&es, igraph_ecount(&g)+j);
igraph_delete_edges(&g_small, es);
igraph_vector_int_init(&fill_in_small, n);
igraph_is_chordal(&g_small, NULL, NULL, &chordal_small, &fill_in_small, &newgraph_small);
if (chordal_small == 1) {
printf("minimal_chordal is not minimal! \n");
printf("i is %d \n", i);
printf("j is %d \n", j);
failed_cases ++;
}
}
igraph_vector_int_list_t A;
igraph_vector_int_list_t S_H;
igraph_vector_int_list_t S_C;
igraph_vector_int_list_init(&A, 0);
igraph_vector_int_list_init(&S_H, 0);
igraph_vector_int_list_init(&S_C, 0);
Atoms(&g, &alpha, &min_sep_gen, &minimal_chordal, &A, &S_H, &S_C);
printf("The set of atoms: \n");
igraph_integer_t size_A = igraph_vector_int_list_size(&A);
for (int j = 0; j < size_A; j++) {
igraph_vector_int_print(igraph_vector_int_list_get_ptr(&A, j));
}
printf("The separators: \n");
igraph_integer_t size_S_C = igraph_vector_int_list_size(&S_C);
for (int j = 0; j < size_S_C; j++) {
igraph_vector_int_print(igraph_vector_int_list_get_ptr(&S_C, j));
}
}
printf("======================================\n");
printf("Number of failed cases is %d. \n", failed_cases);
return 0;
}
void MCS_M_plus(const igraph_t *g,
igraph_vector_int_t *alpha,
igraph_vector_int_t *min_sep_gen,
igraph_t *minimal_chordal) {
// the number of vertices in the graph
const igraph_integer_t n = igraph_vcount(g);
// init
igraph_copy(minimal_chordal, g);
igraph_vector_int_init(alpha, n);
igraph_vector_int_init(min_sep_gen, 0);
igraph_vector_int_t F; // to store the fill_in edges
igraph_vector_int_init(&F, 0);
igraph_integer_t s = -1;
// initialise the labels of all vertices as 0
// label = -1 if the vertex has been chosen
igraph_vector_int_t label;
igraph_vector_int_init(&label, n);
// for loop
igraph_integer_t x, y, z, min_label, size_Y, size_Z, index;
igraph_vector_int_t Y, Z, reached;
igraph_vector_int_list_t reach;
igraph_bool_t x_y_adj;
for (int i = n-1; i >= 0; i--) {
// choose a vertex x of g of maximal label
igraph_vector_int_which_minmax(&label, &min_label, &x);
// Y <- N_{g}(x)
igraph_vector_int_init(&Y, 0);
igraph_neighbors(g, &Y, x, IGRAPH_ALL);
// remove from Y the vertices with label -1
size_Y = igraph_vector_int_size(&Y);
index = 0;
for (int j = 0; j < size_Y; j++) {
if (VECTOR(label)[VECTOR(Y)[j]] > -1) {
VECTOR(Y)[index] = VECTOR(Y)[j];
index ++;
}
}
igraph_vector_int_resize(&Y, index);
// if loop
if (VECTOR(label)[x] <= s) {
igraph_vector_int_push_back(min_sep_gen, x);
}
// s <- label(x)
s = VECTOR(label)[x];
// mark x reached and mark all other vertices of g unreached
igraph_vector_int_init(&reached, n);
VECTOR(reached)[x] = 1;
// for loop, initialise reach as a list of vectors
igraph_vector_int_list_init(&reach, n);
for (int j = 0; j < n; j++) {
igraph_vector_int_init(igraph_vector_int_list_get_ptr(&reach, j), 0);
}
// for loop, neighbours of x
size_Y = igraph_vector_int_size(&Y);
for (int j = 0; j < size_Y; j++) {
y = VECTOR(Y)[j];
// make y reached
VECTOR(reached)[y] = 1;
// add y to reach(label(y))
igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&reach, VECTOR(label)[y]), y);
}
// for loop, other vertices
for (int j = 0; j < n; j++) {
// while reach(j) \neq empty
while (igraph_vector_int_size(igraph_vector_int_list_get_ptr(&reach, j)) > 0) {
// remove a vertex y from reach(j)
y = igraph_vector_int_pop_back(igraph_vector_int_list_get_ptr(&reach, j));
// for loop, neighbours of y, Z is the neighbours of y in g
igraph_vector_int_init(&Z, 0);
igraph_neighbors(g, &Z, y, IGRAPH_ALL);
size_Z = igraph_vector_int_size(&Z);
for (int k = 0; k < size_Z; k++) {
z = VECTOR(Z)[k];
// if z is unreached
if (VECTOR(label)[z] > -1 && VECTOR(reached)[z] == 0) {
// mark z reached
VECTOR(reached)[z] = 1;
// if label(z) > j
if (VECTOR(label)[z] > j) {
// Y <- Y + {z}
igraph_vector_int_push_back(&Y, z);
// add z to reach(label(z))
igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&reach, VECTOR(label)[z]), z);
} else {
// add z to reach(j)
igraph_vector_int_push_back(igraph_vector_int_list_get_ptr(&reach, j), z);
}
}
}
}
}
// for loop
size_Y = igraph_vector_int_size(&Y);
for (int j = 0; j < size_Y; j++) {
y = VECTOR(Y)[j];
// F <- F + {xy} if {x,y} is not an edge in g, here one can also add everything then use igraph_simplify() to remove multiple edges
igraph_are_adjacent(g, x, y, &x_y_adj);
if (!x_y_adj) {
igraph_vector_int_push_back(&F, x);
igraph_vector_int_push_back(&F, y);
}
// label(y) <- label(y) + 1
VECTOR(label)[y] ++;
}
// alpha(i) <- x
VECTOR(*alpha)[i] = x;
// remove x from g, i.e. set the label -1
VECTOR(label)[x] = -1;
}
// add the edges of F to minimal_chordal
igraph_add_edges(minimal_chordal, &F, NULL);
}
void Atoms(const igraph_t *g,
const igraph_vector_int_t *alpha,
const igraph_vector_int_t *min_sep_gen,
const igraph_t *minimal_chordal,
igraph_vector_int_list_t *A,
igraph_vector_int_list_t *S_H,
igraph_vector_int_list_t *S_C) {
igraph_vector_int_list_init(A, 0);
igraph_vector_int_list_init(S_H, 0);
igraph_vector_int_list_init(S_C, 0);
// the number of vertices in the graph
const igraph_integer_t n = igraph_vcount(g);
// label = 1 if the vertex has been chosen, 0 otherwise (for minimal_chordal)
igraph_vector_int_t label;
igraph_vector_int_init(&label, n);
// g1 is G'
igraph_t g1;
igraph_copy(&g1, g);
// g1_v is the vertices of g1
igraph_vector_int_t g1_v;
igraph_vector_int_init_range(&g1_v, 0, n);
igraph_vector_int_t map, invmap;
igraph_vector_int_init(&map, 0);
igraph_vector_int_init(&invmap, 0);
// for loop
igraph_integer_t x, size_S, size_C, index;
igraph_vector_int_t S,g1_minus_S_v, C, g1_minus_C_v;
igraph_bool_t S_is_clique;
igraph_vs_t S_vids, g1_minus_S_vids, g1_minus_C_vids;
igraph_t g1_minus_S, g2;
for (int i = 0; i < n; i++) {
// x <- alpha(i)
x = VECTOR(*alpha)[i];
// if x in X
if (igraph_vector_int_contains(min_sep_gen, x)) {
// S <- N_{H'}(x)
//// get the neighbours of x in H
igraph_vector_int_init(&S, 0);
igraph_neighbors(minimal_chordal, &S, x, IGRAPH_ALL);
//// remove those neighbours not in H', i.e., with label != 0
size_S = igraph_vector_int_size(&S);
index = 0;
for (int j = 0; j < size_S; j++) {
if (VECTOR(label)[VECTOR(S)[j]] == 0) {
VECTOR(S)[index] = VECTOR(S)[j];
index ++;
}
}
igraph_vector_int_resize(&S, index);
//// make a copy of S
igraph_vector_int_t S_copy;
igraph_vector_int_init_copy(&S_copy, &S);
// S_H append S
if (igraph_vector_int_size(&S_copy) > 0) {
igraph_vector_int_list_push_back(S_H, &S_copy);
}
// if S is a clique in G
igraph_vs_vector(&S_vids, &S_copy);
igraph_is_clique(g, S_vids, 0, &S_is_clique);
if (S_is_clique == 1) {
// S_C append S
if (igraph_vector_int_size(&S_copy) > 0) {
igraph_vector_int_list_push_back(S_C, &S_copy);
}
// C <- the connected component of G' - S containing x
//// g1_minus_S is the graph G'-S
igraph_vector_int_init(&g1_minus_S_v, 0);
igraph_vector_int_difference_sorted(&g1_v, &S, &g1_minus_S_v);
igraph_vs_vector(&g1_minus_S_vids, &g1_minus_S_v);
igraph_induced_subgraph_map(g, &g1_minus_S, g1_minus_S_vids, IGRAPH_SUBGRAPH_AUTO, &map, &invmap);
//// C is the component containing x
igraph_vector_int_init(&C, 0);
igraph_subcomponent(&g1_minus_S, &C, VECTOR(map)[x] - 1, IGRAPH_ALL);
size_C = igraph_vector_int_size(&C);
for (int k = 0; k < size_C; k++) {
VECTOR(C)[k] = VECTOR(invmap)[VECTOR(C)[k]];
}
igraph_vector_int_sort(&C);
// A append S union C
igraph_vector_int_append(&S, &C);
igraph_vector_int_list_push_back(A, &S);
// G' <- G' - C
igraph_vector_int_init (&g1_minus_C_v, 0);
igraph_vector_int_difference_sorted(&g1_v, &C, &g1_minus_C_v);
igraph_vs_vector(&g1_minus_C_vids, &g1_minus_C_v);
igraph_induced_subgraph_map(g, &g2, g1_minus_C_vids, IGRAPH_SUBGRAPH_AUTO, &map, &invmap);
g1_v = g1_minus_C_v;
g1 = g2;
}
}
//remove x from H', i.e., set the label to 1
VECTOR(label)[x] = 1;
}
// A append g1_v
igraph_vector_int_list_push_back(A, &g1_v);
}
The output looks good to me. But I am not sure how to compare the results with the code I had in R. Do you use Rcpp or .C()?
======================================
alpha 18 17 3 19 15 16 14 13 12 11 10 9 7 6 8 5 4 2 1 0
g: 83, minimal_chordal: 95, newgraph: 90, difference: 5
The set of atoms:
10 13 17 18
0 2 4 5 3
9 11 12 13 14 15 16 19
1 2 4 5 8 6 7 9 10 11 12 13 14 15 16
0 1 2 4 5 8
The separators:
10 13
0 2 4 5
9 11 12 13 14 15 16
1 2 4 5 8
======================================
alpha 14 19 16 15 18 17 13 12 11 2 10 9 6 8 7 5 4 3 1 0
g: 86, minimal_chordal: 91, newgraph: 93, difference: -2
The set of atoms:
10 19 14
10 12 13 17 18 19
8 9 11 12 13 17 18 15 16
1 4 5 6 2
0 1 3 4 5 6 7 8 9 10 11 12 13 17 18
The separators:
10 19
10 12 13 17 18
8 9 11 12 13 17 18
1 4 5 6
======================================
alpha 15 18 13 19 17 16 14 10 8 7 6 9 12 11 5 4 3 2 1 0
g: 86, minimal_chordal: 116, newgraph: 108, difference: 8
The set of atoms:
11 12 14 16 17 19 15
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19
The separators:
11 12 14 16 17 19
======================================
alpha 19 17 3 16 18 11 10 8 15 14 13 12 9 6 7 5 4 2 1 0
g: 105, minimal_chordal: 111, newgraph: 108, difference: 3
The set of atoms:
8 9 10 11 12 16 18 17 19
5 6 7 8 9 10 11 12 3
5 6 7 9 12 13 8 10 11 14 15 16 18
4 5 6 7 9 12 13
0 4 5 7 6
0 1 4 5 7
0 1 2 4 5
The separators:
8 9 10 11 12 16 18
5 6 7 8 9 10 11 12
5 6 7 9 12 13
4 5 6 7
0 4 5 7
0 1 4 5
======================================
alpha 15 17 19 18 14 16 13 12 11 9 10 8 7 2 6 5 4 3 1 0
g: 80, minimal_chordal: 86, newgraph: 84, difference: 2
The set of atoms:
8 11 13 15
9 12 14 16 18 19 17
9 10 12 16 14 18 19
8 9 10 12 16
6 8 10 11 13
7 8 9 10 12
1 3 4 5 6 2 7 8 9 10 11
0 1 3 4 5 6
The separators:
8 11 13
9 12 14 16 18 19
9 10 12 16
8 9 10 12
6 8 10 11
7 8 9 10
1 3 4 5 6
======================================
alpha 19 18 17 16 13 12 8 7 6 14 15 11 5 2 10 9 4 3 1 0
g: 107, minimal_chordal: 114, newgraph: 116, difference: -2
The set of atoms:
8 12 13 14 15 16 17 18
2 5 6 11 14 15 7 8 12 13 16 17
2 4 5 11 14 15 6
0 3 4 9 10 2 5 11 14 15 19
0 1 3 4 9 10
The separators:
8 12 13 14 15 16 17
2 5 6 11 14 15
2 4 5 11 14 15
0 3 4 9 10
======================================
alpha 19 14 18 17 16 15 12 11 13 6 5 4 2 10 9 8 7 3 1 0
g: 86, minimal_chordal: 96, newgraph: 93, difference: 3
The set of atoms:
12 14 19
11 12 14
13 15 16 17 18
10 13 15 16 17
7 9 10 13 15
0 3 7 8 9 10 2 4 5 6 11 12 13
0 1 3 7 8 9 10
The separators:
12 14
11 12
13 15 16 17
10 13 15
7 9 10 13
0 3 7 8 9 10
======================================
alpha 17 14 19 18 1 16 12 7 5 15 13 11 10 6 9 3 8 4 2 0
g: 98, minimal_chordal: 108, newgraph: 109, difference: -1
The set of atoms:
8 10 13 14 15 17
8 10 11 13 15 14
3 4 5 6 7 9 1
0 4 8 3 5 6 7 9 10 11 12 13 15 16 18 19
0 2 4 8
The separators:
8 10 13 14 15
8 10 11 13 15
3 4 5 6 7 9
0 4 8
======================================
alpha 16 15 10 7 19 18 17 14 1 13 12 11 9 6 8 5 4 3 2 0
g: 87, minimal_chordal: 96, newgraph: 97, difference: -1
The set of atoms:
7 10 14 15 16
3 4 5 6 8 9 11 1 7 10 12 13 14 17 18 19
2 3 4 5 8 6 9 11
0 2 3 4 5 8
The separators:
7 10 14
3 4 5 6 8 9 11
2 3 4 5 8
======================================
alpha 1 3 18 17 15 19 16 14 11 9 13 12 10 6 5 2 8 7 4 0
g: 89, minimal_chordal: 103, newgraph: 106, difference: -3
The set of atoms:
2 3 5 6 9 1
2 5 6 7 9 3
11 14 15 16 17 19 18
4 7 8 2 5 6 9 10 11 12 13 14 15 16 17 19
0 4 7 8
The separators:
2 3 5 6 9
2 5 6 7 9
11 14 15 16 17 19
4 7 8
======================================
Number of failed cases is 0.
Notice that in some cases igraph_is_chordal() gives a chordal completion with fewer edges. So I agree that we should keep is_chordal() as is. The function MCS_M_plus() always gives a minimal chordal completion, which could also be useful.