生成所有独特的有向图,每个节点有2个输入

时间:2017-11-01 16:32:37

标签: algorithm haskell graph graph-theory digraphs

我正在尝试生成符合规范的所有独特的有向图:

  • 每个节点必须有2个输入
  • 并允许任意多次输出到图中的其他节点

我目前的解决方案很慢。例如,对于6个节点,算法花费了1.5天的时间来到达认为它完成的地方,但它可能还要再检查几天。

我的n节点图的算法:

  1. 生成所有n - 长度为0的字符串,其中一个符号为1,例如,对于n = 3,[[0,0,1], [0,1,0], [1,0,0]]。这些可以被认为是来自单位矩阵的行。

  2. 生成所有可能的n * n矩阵,其中每一行都是step 1. + step 1.的所有可能组合

  3. 这是连接矩阵,其中每个单元格代表从column-indexrow-index的连接

    因此,对于n = 3,这些是可能的:

    • [0,1,0] + [1,0,0] = [1,1,0]
    • [1,0,0] + [1,0,0] = [2,0,0]

    这些代表节点的输入,通过向自身添加步骤1,结果将始终代表2个输入。

    例如:

         A B C     
    A' [[0,1,1],
    B'  [0,2,0],
    C'  [1,1,0]]
    

    所以BC分别与A相关联:B -> A', C -> A'

    B连接自己两次:B => B'

    1. 我只想要独一无二的,所以对于生成的每个连接矩阵,我只能保留它,如果它与已经看过的图形不同构。
    2. 这一步很昂贵。我需要通过遍历同构图的每个排列,对它们进行排序,并将第一个作为“规范形式”来将图形转换为“规范形式”。

      如果有人潜水测试其中任何一项,这里是n个节点的唯一图表计数:

      2 - 6
      3 - 44
      4 - 475
      5 - 6874
      6 - 109,934 (I think, it's not done running yet but I haven't found a new graph in >24 hrs.)
      7 - I really wanna know! 
      

      可能的优化:

      1. 因为我要生成要测试的图形,有没有一种方法可以将它们排除在外,而不进行测试,因为它们与已经看过的图形同构?

      2. 有更快的图形同构算法吗? 我认为这个与“Nauty”有关,而还有其他我在论文中读过的内容,但我还没有实现它们的专业知识(或带宽)。

      3. 这是一个可证明的连接矩阵,可以在graphonline.ru上绘制以获得乐趣,显示自我连接,以及与同一节点的2个连接:

        1, 0, 0, 0, 0, 1, 
        1, 0, 0, 0, 1, 0, 
        0, 1, 0, 1, 0, 0, 
        0, 1, 2, 0, 0, 0, 
        0, 0, 0, 1, 0, 1, 
        0, 0, 0, 0, 1, 0,
        

        这里是haskell中的代码,如果你想玩它,但我更关心的是让算法正确(比如修剪搜索空间),而不是实现:

        -- | generate all permutations of length n given symbols from xs
        npermutations :: [a] -> Int -> [[a]]
        npermutations xs size = mapM (const xs) [1..size]
        
        identity :: Int -> [[Int]]
        identity size = scanl
                        (\xs _ -> take size $ 0 : xs)      -- keep shifting right
                        (1 : (take (size - 1) (repeat 0))) -- initial, [1,0,0,...]
                        [1 .. size-1]                      -- correct size
        
        -- | return all possible pairings of [Column]
        columnPairs :: [[a]] -> [([a], [a])]
        columnPairs xs = (map (\x y -> (x,y)) xs)
                         <*> xs
        
        -- | remove duplicates
        rmdups :: Ord a => [a] -> [a]
        rmdups = rmdups' Set.empty where
          rmdups' _ [] = []
          rmdups' a (b : c) = if Set.member b a
            then rmdups' a c
            else b : rmdups' (Set.insert b a) c
        
        
        -- | all possible patterns for inputting 2 things into one node.
        -- eg [0,1,1] means cells B, and C project into some node
        --    [0,2,0] means cell B projects twice into one node
        binaryInputs :: Int -> [[Int]]
        binaryInputs size = rmdups $ map -- rmdups because [1,0]+[0,1] is same as flipped
                 (\(x,y) -> zipWith (+) x y)
                 (columnPairs $ identity size)
        
        transposeAdjMat :: [[Int]] -> [[Int]]
        transposeAdjMat ([]:_) = []
        transposeAdjMat m = (map head m) : transposeAdjMat (map tail m)
        
        -- | AdjMap [(name, inbounds)]
        data AdjMap a = AdjMap [(a, [a])] deriving (Show, Eq)
        
        addAdjColToMap :: Int -- index
                       -> [Int] -- inbound
                       -> AdjMap Int
                       -> AdjMap Int
        addAdjColToMap ix col (AdjMap xs) = 
          let conns = foldl (\c (cnt, i) -> case cnt of
                                1 -> i:c
                                2 -> i:i:c
                                _ -> c
                            ) 
                      [] 
                      (zip col [0..])  in
            AdjMap ((ix, conns) : xs)
        
        adjMatToMap :: [[Int]] -> AdjMap Int
        adjMatToMap cols = foldl 
          (\adjMap@(AdjMap nodes) col -> addAdjColToMap (length nodes) col adjMap)
          (AdjMap [])
          cols
        
        -- | a graph's canonical form : http://mfukar.github.io/2015/09/30/haskellxiii.html
        -- very expensive algo, of course
        canon :: (Ord a, Enum a, Show a) => AdjMap a -> String
        canon (AdjMap g) = minimum $ map f $ Data.List.permutations [1..(length g)]
           where
              -- Graph vertices:
              vs = map fst g
              -- Find, via brute force on all possible orderings (permutations) of vs,
              -- a mapping of vs to [1..(length g)] which is minimal.
              -- For example, map [1, 5, 6, 7] to [1, 2, 3, 4].
              -- Minimal is defined lexicographically, since `f` returns strings:
              f p = let n = zip vs p
                    in (show [(snd x, sort id $ map (\x -> snd $ head $ snd $ break ((==) x . fst) n)
                                              $ snd $ take_edge g x)
                             | x <- sort snd n])
              -- Sort elements of N in ascending order of (map f N):
              sort f n = foldr (\x xs -> let (lt, gt) = break ((<) (f x) . f) xs
                                          in lt ++ [x] ++ gt) [] n
              -- Get the first entry from the adjacency list G that starts from the given node X
              -- (actually, the vertex is the first entry of the pair, hence `(fst x)`):
              take_edge g x = head $ dropWhile ((/=) (fst x) . fst) g
        
        -- | all possible matrixes where each node has 2 inputs and arbitrary outs
        binaryMatrixes :: Int -> [[[Int]]]
        binaryMatrixes size = let columns = binaryInputs size 
                                  unfiltered = mapM (const columns) [1..size] in
          fst $ foldl'
          (\(keep, seen) x -> let can = canon . adjMatToMap $ x in
                                (if Set.member can seen 
                                 then keep
                                 else id $! x : keep
                                , Set.insert can seen))
          ([], Set.fromList [])
          unfiltered
        

1 个答案:

答案 0 :(得分:1)

您可以尝试多种方法。我注意到的一件事是,具有多边的循环(彩色循环?)有点不寻常,但可能只需要对现有技术进行改进。

过滤另一个程序的输出

这里明显的候选人当然是nAUTy / trace(http://pallini.di.uniroma1.it/)或类似的(俏皮,幸福等)。根据你想要的方式,它可以像运行nauty(例如)和输出到文件一样简单,然后在列表过滤中读取。

对于较大的 n 值,如果要生成大文件,这可能会成为一个问题。我不确定在你没时间用完之前你是否开始耗尽空间,但仍然如此。可能更好的是在你去的时候生成并测试它们,扔掉候选人。出于您的目的,可能存在用于生成的现有库 - 我发现this one但我不知道它有多好。

使用图形不变量

更有效地列出图表的第一步非常简单,就是使用graph invariants进行过滤。一个显而易见的是degree sequence(图的度数的有序列表)。其他包括周期数,周长等。出于您的目的,您可以使用一些indegree / outdegree序列。

基本思想是使用不变量作为过滤器来避免对同构进行昂贵的检查。您可以存储已生成图形的不变量(列表),并首先根据列表检查新变量。结构的规范形式是一种不变的。

实施算法

遗失了GI算法,包括nauty和朋友使用的算法。但是,它们往往很难! this answer中给出的描述是一个很好的概述,但当然还有细节。

另请注意,该描述适用于一般图形,而您可能更容易生成图形的特定子类。可能有关于有向图列表(生成)的文件,但我没有检查过。