packages feed

math-grads-0.1.6.2: test/Isomorphism.hs

{-# OPTIONS_GHC -fno-warn-orphans  #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE MultiParamTypeClasses #-}

module Main where

import           Control.Arrow                      ((***))
import qualified Data.Array                         as A
import           Data.List                          (sort)
import           Data.Map.Strict                    (Map)
import qualified Data.Map.Strict                    as M
import           Math.Grads.Graph                   (fromList, toList)
import           Test.Hspec

import           Math.Grads.Algo.Isomorphism        (GComparable (..), isIsoSub)
import qualified Math.Grads.Algo.Isomorphism.RI     as RI
import qualified Math.Grads.Algo.Isomorphism.Ullman as UI
import           Math.Grads.GenericGraph            (GenericGraph, gIndex,
                                                     getEdge)

instance GComparable GenericGraph Int Int GenericGraph Int Int where
  vComparator g1 g2 ind1 ind2 = gIndex g1 A.! ind1 == gIndex g2 A.! ind2
  eComparator _ _ (_, _, t) (_, _, t') = t == t'

pathToGraphs :: FilePath
pathToGraphs = "data/Graphs.txt"

testMap :: IO (Map String (GenericGraph Int Int))
testMap = do
    graphsInLines <- lines <$> readFile pathToGraphs
    let graphsInWords = fmap words graphsInLines

    let forMap = fmap (\(x : y : _) -> (x, fromList (read y))) graphsInWords
    return (M.fromList forMap)

bigSubGraph :: GenericGraph Int Int
bigSubGraph = fromList ( [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                       , [ (0, 1, 1), (0, 26, 1), (1, 2, 1), (2, 3, 1), (3, 4, 1), (3, 25, 1), (4, 5, 1), (4, 11, 1), (5, 6, 1)
                         , (6, 7, 1), (7, 8, 1), (7, 10, 1), (8, 9, 1), (9, 10, 1), (11, 12, 1), (11, 24, 1), (12, 13, 1), (13, 14, 1)
                         , (13, 18, 1), (14, 15, 1), (15, 16, 1), (16, 17, 1), (17, 18, 1), (18, 19, 1), (19, 20, 1), (19, 24, 1)
                         , (20, 21, 1), (21, 22, 1), (22, 23, 1), (23, 24, 1), (25, 26, 1)
                         ]
                       )

pathGraph :: GenericGraph Int Int
pathGraph = fromList ([0, 0, 0, 0, 0, 0, 0], [(0, 1, 1), (0, 2, 1), (0, 3, 1), (0, 4, 1), (4, 5, 1), (4, 6, 1)])

conjugatedCycles :: GenericGraph Int Int
conjugatedCycles = fromList ( [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                            , [ (0, 1, 1), (0, 2, 1), (1, 3, 1), (2, 4, 1), (4, 5, 1), (3, 5, 1), (3, 6, 1), (5, 7, 1)
                              , (6, 8, 1), (7, 9, 1), (8, 9, 1), (1, 10, 1), (6, 11, 1), (10, 12, 1), (11, 12, 1)
                              ]
                            )

connectedCycles :: GenericGraph Int Int
connectedCycles = fromList ( [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                           , [ (0, 1, 1), (0, 2, 1), (1, 3, 1), (2, 4, 1), (4, 5, 1), (3, 5, 1), (3, 6, 1), (6, 7, 1)
                             , (6, 8, 1), (7, 9, 1), (8, 10, 1), (9, 11, 1), (10, 11, 1), (8, 12, 1)
                             ]
                           )

cycleAndTriangle :: GenericGraph Int Int
cycleAndTriangle = fromList ( [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
                            , [ (0, 1, 1), (0, 2, 1), (1, 3, 1), (2, 4, 1), (3, 4, 1), (4, 5, 1), (5, 6, 1), (6, 7, 1)
                              , (7, 8, 1), (7, 9, 1), (8, 9, 1)
                              ]
                            )

triangleAndTriangle :: GenericGraph Int Int
triangleAndTriangle = fromList ( [0, 0, 0, 0, 0, 0, 0]
                               , [(0, 1, 1), (0, 2, 1), (1, 2, 1), (1, 3, 1), (3, 4, 1), (3, 5, 1), (4, 5, 1)]
                               )

testIsIsoSub :: SpecWith ()
testIsIsoSub = describe "Check whether subgraph isomorphism algorithm is working correctly" $ do
    it "Path" $ do
        graph <- fmap (M.! "only_path") testMap
        graph `shouldSatisfy` isIsoSub pathGraph
        uiIsoEqRiIso pathGraph graph
    it "Conjugated cycles" $ do
        graph <- fmap (M.! "only_cycles") testMap
        graph `shouldSatisfy` isIsoSub conjugatedCycles
        uiIsoEqRiIso conjugatedCycles graph
    it "Connected cycles" $ do
        graph <- fmap (M.! "simple_drawing") testMap
        graph `shouldSatisfy` isIsoSub connectedCycles
        uiIsoEqRiIso connectedCycles graph
    it "Conjugated cycles again" $ do
        graph <- fmap (M.! "hard_drawing") testMap
        graph `shouldSatisfy` isIsoSub conjugatedCycles
        uiIsoEqRiIso conjugatedCycles graph
    it "Cycle and triangle" $ do
        graph <- fmap (M.! "paths_through_conjugated_cycles") testMap
        graph `shouldSatisfy` isIsoSub cycleAndTriangle
        uiIsoEqRiIso cycleAndTriangle graph
    it "Big graph" $ do
        graph <- fmap (M.! "takes_long_if_done_wrong") testMap
        graph `shouldSatisfy` isIsoSub bigSubGraph
        uiIsoEqRiIso bigSubGraph graph
    it "Triangle and triangle. No match" $ do
        graph <- fmap (M.! "paths_through_conjugated_cycles") testMap
        graph `shouldNotSatisfy` isIsoSub triangleAndTriangle
        uiIsoEqRiIso triangleAndTriangle graph
    it "Cycle and triangle. No match" $ do
        graph <- fmap (M.! "simple_drawing") testMap
        graph `shouldNotSatisfy` isIsoSub cycleAndTriangle
        uiIsoEqRiIso cycleAndTriangle graph

uiIsoEqRiIso :: GenericGraph Int Int -> GenericGraph Int Int -> Expectation
uiIsoEqRiIso query target = do
    mapM_ (`shouldSatisfy` isValidIso query target) uiIsos
    mapM_ (`shouldSatisfy` isValidIso query target) riIsos
    length uiIsos `shouldBe` length riIsos
    toIsoList uiIsos `shouldBe` toIsoList riIsos
  where
    uiIsos = UI.getMultiIso query target
    riIsos = RI.getMultiIso query target

    toIsoList = sort . fmap (sort . M.toList)

isValidIso :: GenericGraph Int Int -> GenericGraph Int Int -> Map Int Int -> Bool
isValidIso query target iso = vsEq && esEq
  where
    (queryVs, queryEs) = toList query
    targetVs           = fst $ toList target

    vsEq = all (uncurry (==) . ((queryVs !!) *** (targetVs !!))) $ M.toList iso
    esEq = all (\(v1, v2, t) -> t == getEdge target (iso M.! v1) (iso M.! v2)) queryEs


main :: IO ()
main = hspec testIsIsoSub