14

Given two simple graphs:

library(igraph)

g <- graph.empty()
g <- g + vertices(1,2,3)
g <- g + path(1,2,3)


g1 <- g
V(g1)$color = c(1,2,2)
g2 <- g
V(g2)$color = c(2,1,1)

which look like:

par(mfrow=c(1,2))
palette(rainbow(3))
plot(g1)
plot(g2)

graphs

How come they are not isomorphic?

graph.isomorphic.vf2(g1,g2)$iso

FALSE

and most important, if this is not an isomorphism, how can I detect this kind of equivalence within igraph ?

alberto
  • 2,625
  • 4
  • 29
  • 48
  • Looks like `graph.subisomorphic(g1,g2)` returns TRUE? Any reason you specifically are trying to use the `vf2` algorithm? You might want to check the references listed on the `?graph.subisomorphic.vf2` help page to see of that algorithm has any known shortcomings. – MrFlick Apr 12 '15 at 20:09
  • The reason I use `vf2` is because, looking at the docs, it seemed to me the only algorithm dealing with colors. – alberto Apr 12 '15 at 21:39

4 Answers4

5

(I post a first hack as an answer to keep the question uncluttered. This hack does not always work and therefore is faulty, see the second example below.

For a hack that does work, please see either my second answer or the other people answers!)

I find the canonical permutation of labels, then a canonical coloring of this new canonical graph, and then I can use vf2.

Our function to re-color the graph is:

# Convert aaabbccdefaa -> 111223345611
canonical <- function(input){
  labels <- unique(input)
  match(input, labels)
}

And now back to business:

g <- graph.empty()
g <- g + vertices(1,2,3)
g <- g + path(1,2,3)


g1 <- g
V(g1)$color = c(1,2,2)
g2 <- g
V(g2)$color = c(2,1,1)

# Find canonical topological labeling and then canonical coloring
g1 <- permute.vertices(g1, canonical.permutation(g1)$labeling)
g2 <- permute.vertices(g2, canonical.permutation(g2)$labeling)
V(g1)$color <- canonical(V(g1)$color)
V(g2)$color <- canonical(V(g2)$color)                     

par(mfrow=c(1,2))
palette(rainbow(3))
plot(g1)
plot(g2)

iso

Which now will be detected as isomorfic:

#vf2 wants colors to be the same, not "up to a relabeling"
# this is why we use canonical colors
graph.isomorphic.vf2(g1, g2)$iso

TRUE

Failure example:

For this example, it does not work:

g1 <- graph.empty()
g1 <- g1 + vertices(1,2)
g1 <- g1 + edge(1,2)
V(g1)$color = c(1,2)

g2 <- graph.empty()
g2 <- g2 + vertices(1,2)
g2 <- g2 + edge(2,1)
V(g2)$color = c(2,1)

# Find canonical topological labeling and then canonical coloring
g1 <- permute.vertices(g1, canonical.permutation(g1)$labeling)
g2 <- permute.vertices(g2, canonical.permutation(g2)$labeling)
V(g1)$color <- canonical(V(g1)$color)
V(g2)$color <- canonical(V(g2)$color)                     

par(mfrow=c(1,2))
palette(rainbow(3))
plot(g1)
plot(g2)

graph.isomorphic.vf2(g1,g2)$iso
# FALSE 

fail

alberto
  • 2,625
  • 4
  • 29
  • 48
3

To avoid color permutations, Bertrand Jouve pointed to me to this trick suggested in the nauty user guide (pages 58-59). The idea is to recolour the vertices to be all the same and then all vertices that used to share the same color now have an edge to a common vertex. And then we can apply a classic vf2for colored graphs.

nauty

My implementation:

library(igraph)
isocolor.setup <- function(g){
   # Transform a graph so that it can be used in colored isomorphism algorithms
   # Args:
   #   g: graph
   # Returns:
   #   Transformed graph
  nvertices <- vcount(g)
  colors <- unique(V(g)$color)
  g <- add.vertices(g, length(colors), color=max(colors)+1)
  for(i in 1:length(colors)){
    group <- V(g)[V(g)$color==colors[i]]
    aux.id <- nvertices + i
    g[from = group, to = rep(aux.id,length(group))] <- TRUE
  }
  V(g)[1:nvertices]$color <- 1
  V(g)[V(g)$color != 1]$color <- 2
  return(g)
}

Examples:

setup_palette <- function(g){
  palette(rainbow(max(2,length(unique(V(g)$color)))))
}

par(mfrow=c(3,2))

# First graph
g1 <- graph.ring(6)
V(g1)$color <- c(1,1,2,2,3,3)
setup_palette(g1)
plot(g1)

g1.mapped <- isocolor.setup(g1)
setup_palette(g1.mapped)
setup_palette(g1.mapped)
plot(g1.mapped)

# Second graph
g2 <- graph.ring(6)
V(g2)$color <- c(2,3,2,3,1,1)
setup_palette(g2)
plot(g2)

g2.mapped<- isocolor.setup(g2)
setup_palette(g2.mapped)
plot(g2.mapped)
title(paste("\ng1 iso g2?", graph.isomorphic.vf2(g1.mapped, g2.mapped)$iso))

# Third graph
g3 <- graph.ring(6)
V(g3)$color <- c(1,1,3,3,2,2)
setup_palette(g3)
plot(g3)

g3.mapped<- isocolor.setup(g3)
setup_palette(g3.mapped)
plot(g3.mapped)
title(paste("\ng1 iso g3?", graph.isomorphic.vf2(g1.mapped, g3.mapped)$iso))

figure

Of course we should check, as a first filter, whether they both have the same color frequency as explained by @josilber.

alberto
  • 2,625
  • 4
  • 29
  • 48
2

Indeed Isomorphic wants the colour labels to match. The solution is to permute all colour labels and test whether one of them is isomorphic. If it is, then your graphs are isomorphic.

library(combinat)

colour_isomorphic<-function(g1,g2){

g2_copy<-g2
colour2<-unique(V(g2)$color)
colour2_permutations<-permn(colour2)

for(p in colour2_permutations){
names[p]<-as.character(colour2)
V(g2_copy)$color<-sapply(V(g2)$color, function(x) p[as.character(x)])
test_result<-graph.isomorphic.vf2(g1,g2_copy)$iso
if (test_result) {return(T)}


}

return(F)
}

colour_isomorphic(g1,g2) should now return TRUE and it should also work in the other test case of the other answer given. The only place where it can fail is if the colour labels are nor systematically chosen as the first n natural numbers (1,2,3,4,...) in which case you need to convert them to that first.

patapouf_ai
  • 17,605
  • 13
  • 92
  • 132
2

@bisounours_tronconneuse correctly points out that you could just consider every mapping from the colors of one graph to the colors of the other, using graph.isomorphic.vf2 to check if the relabeled graphs are isomorphic. While this is mathematically true, it is computationally challenging because it requires n! (n factorial) isomorphism checks for a pair of graphs with n colors. This is 3.6 million checks for graphs with 10 colors and 9e157 checks for graphs with 20 colors, so clearly it could only be used in a setting with a very small number of colors.

We could potentially be much more efficient by considering one additional fact: a pair of graphs can only be isomorphic if their color frequency distributions exactly match. This means we only need to consider mappings between colors with the same frequency in the pair of graphs. In your question, there is only one possible mapping because in each input graph there is one color appearing once and one color appearing twice. Except in pathological cases where many colors have identical frequencies in your graph, this should lead to a much more efficient procedure for checking for isomorphism.

library(igraph)
iso.josilber <- function(g1, g2) {
  freq1 <- table(V(g1)$color)
  freq2 <- table(V(g2)$color)
  col2 <- as.character(V(g2)$color)
  if (length(freq1) != length(freq2)) {
    return(FALSE)  # Different numbers of colors
  }
  relabels <- as.matrix(do.call(expand.grid, lapply(freq2, function(x) as.numeric(names(freq1[freq1 == x])))))
  relabels <- relabels[apply(relabels, 1, function(x) length(unique(x)) == length(x)),]
  print(paste("Number of reorderings to check:", nrow(relabels)))
  if (nrow(relabels) == 0) {
    return(FALSE)  # No valid relabels based on frequency distribution
  }
  for (i in seq(nrow(relabels))) {
    V(g2)$color <- relabels[i,][col2]
    if(graph.isomorphic.vf2(g1,g2)$iso) {
      return(TRUE)  # Found an isomorphic relabeling
    }
  }
  return(FALSE)  # Checked all valid relabelings; none were isomorphic
}

iso.josilber(g1, g2) returns TRUE for both of the tiny graph pairs you posed in your question and your answer. To stress test the procedure, consider g1, a random directed graph with 100 nodes, 0.5 density, and 15 randomly selected colors, and g2, an identical graph with a randomly relabeled version of these colors (aka it is isomorphic).

set.seed(144)
g1 <- erdos.renyi.game(100, 0.5)
V(g1)$color <- sample(1:15, 100, replace=T)
g2 <- g1
V(g2)$color <- sample(1:15)[V(g1)$color]
system.time(print(iso.josilber(g1, g2)))
# [1] "Number of reorderings to check: 144"
# [1] TRUE
#    user  system elapsed 
#   0.172   0.004   0.189 

Note that the approach that exhaustively checks all color mappings would have needed to check 15! color mappings, or more than one trillion.

One word of warning --- though this procedure may be more efficient on many graph pairs than a more naive approach, it still has exponential worst-case runtime, meaning there are classes of graphs where it will still perform quite slowly.

josliber
  • 43,891
  • 12
  • 98
  • 133
  • josilber @bisounours_tronconneuse thanks a lot ! I added a new answer with a new solution that avoids permutations. I think you will like it :) (unless I'm missing some cases where it does not work) – alberto Apr 21 '15 at 10:41