packages feed

algraph-0.7.0.0: test/Test/Graph/AdjacencyList/PushRelabel/FGLComparison.hs

module Test.Graph.AdjacencyList.PushRelabel.FGLComparison where

import Data.Maybe
import Data.List
import qualified Data.Map.Strict as M

import qualified Data.Graph.Inductive as I
import qualified Data.Graph.Inductive.Graph as G
import qualified Data.Graph.Inductive.Query.MaxFlow as MF

import Test.QuickCheck

import TestHS

import Data.Graph.AdjacencyList
import Data.Graph.AdjacencyList.Network
import Data.Graph.AdjacencyList.PushRelabel.Internal
import Data.Graph.AdjacencyList.PushRelabel.Pure (pushRelabel)

-- ================================================================
-- Random network generator
-- ================================================================

data TestNetwork = TestNetwork
  { tnNetwork  :: Network
  , tnNumVerts :: Int
  , tnNumEdges :: Int
  } deriving (Show)

instance Arbitrary TestNetwork where
  arbitrary = do
    n <- choose (3, 20)
    let s = 1
        t = n
    -- Guarantee a path from source to sink
    let pathEdges = [(i, i+1) | i <- [1..n-1]]
    numExtra <- choose (0, n * (n-1) `div` 2)
    extraEdges <- genExtraEdges n numExtra pathEdges
    let allEdgePairs = nub $ pathEdges ++ extraEdges
        es = map (\(u,v) -> Edge u v) allEdgePairs
    caps <- mapM (\_ -> choose (1, 100 :: Int)) allEdgePairs
    let capMap = M.fromList $ zip es (map toRational caps)
        g = graphFromEdges es
        net = Network { graph = g
                       , source = s
                       , sink = t
                       , capacities = capMap
                       , flow = M.empty
                       }
    return $ TestNetwork net n (length allEdgePairs)

  shrink _ = []

genExtraEdges :: Int -> Int -> [(Int,Int)] -> Gen [(Int,Int)]
genExtraEdges _ 0 _ = return []
genExtraEdges n numExtra existing = do
  pairs <- vectorOf (numExtra * 2) $ do
    u <- choose (1, n)
    v <- choose (1, n)
    return (u, v)
  let valid  = filter (\(u,v) -> u /= v) pairs
      unique = nub valid
      new    = filter (`notElem` existing) unique
  return $ take numExtra new

-- ================================================================
-- Convert to FGL
-- ================================================================

networkToFGL :: Network -> (I.Gr () Double, Int, Int)
networkToFGL net =
  let g = graph net
      s = source net
      t = sink net
      vs = map (\v -> (v, ())) $ vertices g
      es = map (\e -> (from e, to e,
                       fromRational $ fromJust $ M.lookup e (capacities net)))
               $ edges g
  in (G.mkGraph vs es, s, t)

-- ================================================================
-- Properties
-- ================================================================

-- | Tide max flow equals FGL max flow
prop_maxFlowMatchesFGL :: TestNetwork -> Property
prop_maxFlowMatchesFGL (TestNetwork net _ _) =
  case pushRelabel net of
    Left err -> counterexample ("pushRelabel failed: " ++ err) False
    Right res ->
      let tideFlow = netFlow res
          (fglGraph, s, t) = networkToFGL net
          fglFlow = toRational (MF.maxFlow fglGraph s t :: Double)
      in counterexample
           ("Tide: " ++ show (fromRational tideFlow :: Double)
            ++ " FGL: " ++ show (fromRational fglFlow :: Double)
            ++ " (" ++ show (length $ vertices $ graph net) ++ " vertices, "
            ++ show (length $ edges $ graph net) ++ " edges)")
           (tideFlow == fglFlow)

-- ================================================================
-- Test runner
-- ================================================================

qcCount :: Int
qcCount = 10000

ioTests :: [IO Test]
ioTests =
  [ qcTest "Tide max flow == FGL max flow"  prop_maxFlowMatchesFGL
  ]

qcTest :: Testable prop => String -> prop -> IO Test
qcTest name prop = do
  result <- quickCheckWithResult stdArgs { maxSuccess = qcCount, chatty = False } prop
  case result of
    Success {} -> return $ testPassed name
                    ("passed (" ++ show qcCount ++ " random graphs)")
    failure    -> return $ testFailed name ("QuickCheck failure", show failure)