{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Math.Grads.Algo.Isomorphism.Ullman
( getMultiIso
) where
import Control.Arrow (second, (&&&), (***))
import qualified Data.Array as A
import Data.List (delete, sortOn)
import Data.Map (Map)
import qualified Data.Map.Strict as M
import Data.Matrix (Matrix (..), getElem,
getRow, mapRow, matrix,
multStd, ncols, nrows,
setElem, transpose)
import Data.Tuple (swap)
import qualified Data.Vector as V
import Math.Grads.Algo.Isomorphism.Types (EComparator,
GComparable (..),
VComparator)
import Math.Grads.GenericGraph (GenericGraph (..))
import Math.Grads.Graph (GraphEdge, changeIndsEdge,
fromList, incidentIdx,
toList, vCount, (!.))
import Math.Grads.Utils (nub)
type GenericGraphIso v e = GenericGraph Int e
getMultiIso :: (Ord v1, Ord v2, GComparable GenericGraph v1 e1 GenericGraph v2 e2, Eq e1, Eq e2)
=> GenericGraph v1 e1
-> GenericGraph v2 e2
-> [Map Int Int]
getMultiIso queryGraph' targetGraph' = matches
where
((queryGraph, queryGraphWI), fromIsoToOldQ) = second inverseMap $ graphToGraphIso queryGraph'
((targetGraph, targetGraphWI), fromIsoToOldT) = second inverseMap $ graphToGraphIso targetGraph'
vComp = vComparator queryGraphWI targetGraphWI
eComp = eComparator queryGraphWI targetGraphWI
isos = isoGraph vComp eComp queryGraph targetGraph
matches = fmap (\x -> getMatchMap x fromIsoToOldQ fromIsoToOldT) isos
inverseMap :: Map Int Int -> Map Int Int
inverseMap = M.fromList . (swap <$>) . M.toList
getMatchMap :: Matrix Int -> Map Int Int -> Map Int Int -> Map Int Int
getMatchMap isoMatrix fromIsoToOldQ fromIsoToOldT = res
where
forMap = fmap (getMatchRow isoMatrix) [0 .. nrows isoMatrix - 1]
res = M.fromList (fmap ((fromIsoToOldQ M.!) *** (fromIsoToOldT M.!)) forMap)
getMatchRow :: Matrix Int -> Int -> (Int, Int)
getMatchRow isoMatrix ind = (ind, helper 0)
where
row = getRow (ind + 1) isoMatrix
helper :: Int -> Int
helper counter = if row V.! counter == 1 then counter
else helper (counter + 1)
isoGraph :: (Eq e1, Eq e2) => VComparator v1 v2
-> EComparator e1 e2
-> GenericGraphIso v1 e1
-> GenericGraphIso v2 e2
-> [Matrix Int]
isoGraph vComp eComp queryGraph targetGraph = res
where
queryGraphEdges = (fst <$>) <$> gAdjacency queryGraph
sizeOfQueryGraph = vCount queryGraph
pMatrix = matrix sizeOfQueryGraph sizeOfQueryGraph (\(i, j) -> if i - 1 `elem` queryGraphEdges A.! (j - 1) then 1 else 0)
targetGraphEdges = (fst <$>) <$> gAdjacency targetGraph
sizeOfTargetGraph = vCount targetGraph
gMatrix = matrix sizeOfTargetGraph sizeOfTargetGraph (\(i, j) -> if i - 1 `elem` targetGraphEdges A.! (j - 1) then 1 else 0)
mMatrix = matrix sizeOfQueryGraph sizeOfTargetGraph (\(i, j) -> if fits vComp eComp queryGraph targetGraph i j then 1 else 0)
currentRow = 0
unusedColumns = [1 .. ncols mMatrix]
res = recurse eComp queryGraph targetGraph unusedColumns currentRow gMatrix pMatrix mMatrix
fits :: (Eq e1, Eq e2) => VComparator v1 v2
-> EComparator e1 e2
-> GenericGraphIso v1 e1
-> GenericGraphIso v2 e2
-> Int
-> Int
-> Bool
fits vComp eComp queryGraph targetGraph i j = res
where
(vertex, edges) = (gIndex queryGraph A.! (i - 1), incidentIdx queryGraph $ i - 1)
(vertex', edges') = (gIndex targetGraph A.! (j - 1), incidentIdx targetGraph $ j - 1)
res = length edges <= length edges' && canBeSubset eComp edges edges' && vertex `vComp` vertex'
canBeSubset :: forall e1 e2. EComparator e1 e2 -> [GraphEdge e1] -> [GraphEdge e2] -> Bool
canBeSubset eComp query target = uniqueSeq maps
where
bondsInd = zip [0 ..] target
maps = findMatches <$> query
findMatches :: GraphEdge e1 -> [Int]
findMatches thisEdge = fst <$> filter (\(_, otherEdge) -> eComp thisEdge otherEdge) bondsInd
uniqueSeq :: [[Int]] -> Bool
uniqueSeq maps = res
where
seqs = sequence maps
res = any (\x -> length x == length (nub x)) seqs
graphToGraphIso :: (Ord v) => GenericGraph v e
-> ((GenericGraphIso v e, GenericGraph v e), M.Map Int Int)
graphToGraphIso graph = res
where
(vertices, edges) = toList graph
vArr = gIndex graph
indsWithNCount = fmap (id &&& (length . (graph !.))) [0 .. length vertices - 1]
sortedInds = fst <$> sortOn (\x -> - (snd x)) indsWithNCount
changesMap = M.fromList (zip sortedInds [0 ..])
sortedV = fmap (vArr A.!) sortedInds
changedEdges = fmap (changeIndsEdge (changesMap M.!)) edges
forGraphWI = (sortedV, changedEdges)
forGraph = ([0 .. length sortedV - 1], changedEdges)
res = ((fromList forGraph, fromList forGraphWI), changesMap)
recurse :: (Eq e1, Eq e2) => EComparator e1 e2
-> GenericGraphIso v1 e1
-> GenericGraphIso v2 e2
-> [Int]
-> Int
-> Matrix Int
-> Matrix Int
-> Matrix Int
-> [Matrix Int]
recurse eComp queryGraph targetGraph unusedColumns currentRow gMatrix pMatrix mMatrix = res
where
prunedM = prune eComp queryGraph targetGraph mMatrix currentRow
recs = concatMap pruneNext unusedColumns
res | hasEmptyRow mMatrix = []
| currentRow == nrows mMatrix && isIsomorphism gMatrix pMatrix mMatrix = [mMatrix]
| not (hasEmptyRow prunedM) = recs
| otherwise = []
pruneNext :: Int -> [Matrix Int]
pruneNext x = recurse eComp queryGraph targetGraph newColumns newRow gMatrix pMatrix changedMatrix
where
newColumns = delete x unusedColumns
newRow = currentRow + 1
changedMatrix = changeRow prunedM newRow x
prune :: (Eq e1, Eq e2) => EComparator e1 e2
-> GenericGraphIso v1 e1
-> GenericGraphIso v2 e2
-> Matrix Int
-> Int
-> Matrix Int
prune eComp queryGraph targetGraph mMatrix currentRow | null indicesToChange = mMatrix
| hasEmptyRow mMatrix = mMatrix
| otherwise = res
where
numberOfMRows = nrows mMatrix
numberOfMColumns = ncols mMatrix
pairsOfindices = [(i, j) | i <- [1.. numberOfMRows], j <- [1.. numberOfMColumns], getElem i j mMatrix == 1]
suitPair :: Int -> Int -> Bool
suitPair = hasSuitableNeighbors eComp queryGraph targetGraph mMatrix
indicesToChange = filter (not . uncurry suitPair) pairsOfindices
changedMMatrix = foldl (flip (setElem 0)) mMatrix indicesToChange
res = prune eComp queryGraph targetGraph changedMMatrix currentRow
hasSuitableNeighbors :: forall v1 v2 e1 e2. (Eq e1, Eq e2) => EComparator e1 e2
-> GenericGraphIso v1 e1
-> GenericGraphIso v2 e2
-> Matrix Int
-> Int
-> Int
-> Bool
hasSuitableNeighbors eComp queryGraph targetGraph mMatrix query target = doesSatisfy
where
iQ = query - 1
iT = target - 1
neighborsOfQ = (\(i, e) -> (iQ, i, e)) <$> queryGraph !. iQ
neighborsOfT = (\(i, e) -> (iT, i, e)) <$> targetGraph !. iT
hasProperNeighbor :: GraphEdge e1 -> Bool
hasProperNeighbor edge = any (\edge' -> getProperElem edge edge' == 1 && eComp edge edge') neighborsOfT
getProperElem :: GraphEdge e1 -> GraphEdge e2 -> Int
getProperElem (_, b, _) (_, b', _) = getElem (b + 1) (b' + 1) mMatrix
doesSatisfy = all hasProperNeighbor neighborsOfQ
isIsomorphism :: Matrix Int
-> Matrix Int
-> Matrix Int
-> Bool
isIsomorphism gMatrix pMatrix mMatrix = leqMatrices pMatrix check
where
check = multStd mMatrix (transpose (multStd mMatrix gMatrix))
leqMatrices :: Matrix Int -> Matrix Int -> Bool
leqMatrices matrixA matrixB = nrows matrixA * ncols matrixA <= nrows matrixB * ncols matrixB && helper elems
where
numOfRows = nrows matrixA
numOfColumns = ncols matrixB
elems = [(i, j) | i <- [1..numOfRows], j <- [1..numOfColumns]]
helper = foldr (\x -> (&&) (uncurry getElem x matrixA <= uncurry getElem x matrixB)) True
changeRow :: Matrix Int -> Int -> Int -> Matrix Int
changeRow mMatrix row column = mapRow helper row mMatrix
where helper column' a = if column' /= column then 0 else a
hasEmptyRow :: Matrix Int -> Bool
hasEmptyRow prunedMatrix = cond
where
numberOfRows = nrows prunedMatrix
cond = any (\x -> all (== 0) (getRow x prunedMatrix)) [1 .. numberOfRows]