Thank you szhorvat.
I implemented an algorithm presented in a recent paper https://epubs.siam.org/doi/10.1137/16M1059837.
The algorithm goes like this:
(1) Given a graph G, get a minimal chordal completion H.
(2) Get a clique tree of H (so we need the cliques and separators of H, see chordal_factor() below).
[Fact: the minimal separators of H which forms a clique in G are exactly the minimal separators of G.]
(3) Check the minimal separators of H one by one. If the separator does not form a clique in G, then contract that edge in the clique tree.
(4) The resulting clique tree becomes the atom tree, which gives the prime components.
In (1), I rely on the assumption that the output newgraph of the igraph function is_chordal() gives a minimal chordal completion. Could you please verify this?
The algorithm theoretically takes O(n^alpha log n) time, where alpha < 2.3729, using fast matrix multiplication to compute the matrix C (middle of prime_decom3()). Here I simply use %*% in R.
###### input graph must be chordal
chordal_factor <- function(graph) {
n <- vcount(graph)
if (n == 0) {
return(list("cliques" = list(), "separators" = list()))
}
PEO <- as.vector(max_cardinality(graph)$alpham1)
cliques <- list()
kdeg <- vector()
representatives <- vector()
perfectCliques <- list()
separators <- list()
# all possible cliques
for (i in 1:n) {
v <- PEO[i]
neighbours <- intersect(neighbors(graph, v), PEO[i:n])
cliques[[i]] <- c(v, neighbours)
kdeg[i] <- length(cliques[[i]]) - 1
}
# perfect sequence of cliques
perfectCliques <- list(cliques[[1]])
representatives[1] <- PEO[1]
if (n > 1) {
for (i in 2:n) {
if (kdeg[[i]] >= kdeg[[i-1]]) {
representatives <- c(representatives, PEO[i])
perfectCliques <- append(perfectCliques, list(cliques[[i]]))
}
}
perfectCliques <- rev(perfectCliques)
}
# separators
tempUnion <- perfectCliques[[1]]
num_maximal_cliques <- length(perfectCliques)
if (num_maximal_cliques > 1) {
for (i in 2:num_maximal_cliques) {
separators <- append(separators, list(intersect(tempUnion, perfectCliques[[i]])))
tempUnion <- union(tempUnion, perfectCliques[[i]])
}
}
return(list("cliques" = perfectCliques, "separators" = separators))
}
###### input graph is an undirected graph
# assume is_chordal() gives minimal chordal completion
prime_decom3 <- function(graph) {
n <- vcount(graph)
# get a minimal chordal completion of graph
chordal <- is_chordal(graph, newgraph = TRUE)
if (chordal$chordal) {
return (chordal_factor(graph))
} else {
chordal <- chordal$newgraph
factor <- chordal_factor(chordal)
cliques <- factor$cliques
separators <- factor$separators
}
min_sep <- list()
# define clique_matrix and C
clique_matrix <- matrix(0, nrow = n, ncol = length(separators))
for (i in seq_along(separators)) {
clique_matrix[separators[[i]], i] <- 1
}
C <- as_adjacency_matrix(graph, "both") %*% clique_matrix # use fast matrix multiplication
# check
remove <- vector()
for (i in seq_along(separators)) {
sep <- separators[[i]]
if (all(C[as.vector(sep), i] == length(sep) - 1)) {
min_sep <- append(min_sep, list(sep))
} else {
remove <- c(remove, i)
for (j in 1:i) {
if (all(sep %in% cliques[[j]])) {
cliques[[j]] <- union(cliques[[i]], cliques[[j]])
cliques[[i]] <- vector()
break
}
}
}
}
return (list(components = cliques[-remove], separators = min_sep))
}
If is_chordal() does not give minimal chordal completion, I can also implement the algorithm described in Section 4 of the review paper you posted. It’s self-contained.