packages feed

LinearSplit-0.1: tests/Properties.hs

-- | Tests for the Utils.LinearSplit module.

module Main where

import Data.LinearSplit
import Test.QuickCheck
import Test.Framework (Test, defaultMain, testGroup)
import Test.Framework.Providers.QuickCheck2 (testProperty)

-- | A datatype to model the generation the arbitrary splitting processers. 
-- There are the restrictions to items of type Item Int Double 
data Split = Split {
   chunks :: Int,
   items :: [Item Int Double],
   threshold :: Double
} deriving (Show)

-- | A datatype to generate a positive item weight. The weight
-- is restricted to the arbitrary values from 0.0 to 1000000.0
data Weight = W {
  unW :: Double 
}

instance Arbitrary Weight where
   arbitrary = do 
      w <- choose (0.0, 1000000.0)
      return $ W w

instance Arbitrary Split where
   arbitrary = do 
      n <- choose (1,25) :: Gen Int
      ws <- arbitrary :: Gen [Weight]
      thr <- choose (0.0, 10000.0) :: Gen Double
      let mkItem (id, W w) = Item id w
      let is = map mkItem $ zip [1..length ws] ws
      return $ Split n is thr

-- |
splitters (Split n xs t) = 
  [lPartition n xs, ltPartition n xs t, byLength n xs, byAvgCost n xs]
  where
    byLength n xs = 
       let fit = length xs `div` n 
           fun ys = fit > length ys
       in gPartition fun n xs
    byAvgCost n xs = 
       let fit = (sum . map weight) xs / fromIntegral n 
           fun ys = fit > (sum . map weight) ys
       in gPartition fun n xs

-- | Ensure that the sum of the items weights equals to 
-- the total ranges costs 
prop_totalCost s = 
   let totalCosts = map (floor . sum . map price) (splitters s)
       itemsCost = floor $ sum $ map weight (items s)
   in all (== itemsCost) totalCosts

-- | The optimal algorithm has to produce the lowest partition cost
prop_bestCost s = 
   let (n,xs) = (chunks s, items s)
       maxCost ys = foldr max 0.0 (map price ys)
       partitionCost = floor . maxCost 
       bestCost = partitionCost (lPartition (chunks s) (items s))
   in all (>= bestCost) (map partitionCost (splitters s))

-- | Ensure that the real number of ranges no more than required
prop_numRanges = forAll (arbitrary :: Gen Split) $ \s ->
   all (<= (chunks s)) (map length (splitters s))

-- | Ensure that the splitting dividers are ordered as working items
prop_ordered s = 
  let divs = map (foldr dividers []) (splitters s)
      dividers r xs = if low r == high r then low r : xs
                       else low r : (high r : xs)
   in all (ordered (map item (items s))) divs

-- | Reverse working items preserves the optimal cost
prop_reverse s =
  let (n,xs) = (chunks s, items s)
      maxCost ys = foldr max 0.0 (map price ys)
      partitionCost = floor . maxCost
  in partitionCost (lPartition n xs) == partitionCost (lPartition n (reverse xs))

-- | Ensure that the ranges prices equal to the sum of weight corresponding
-- work items
prop_rangeCost s =
  and [eqCost rs (items s) | rs <- splitters s] 

-- | Testing helpers
ordered :: [Int] -> [Int] -> Bool
ordered [] [] = True
ordered (x:xs) (y:ys) 
   | x == y = ordered xs ys
   | otherwise = ordered xs (y:ys)
ordered _ _ = False

eqCost :: [Range Int Double] -> [Item Int Double] -> Bool
eqCost [] [] = True
eqCost (Range p l h:xs) ys =
        let (ks,zs) = span (\(Item i _) -> i /= h) ys
            (ks',zs') = (ks ++ [head zs], tail zs)
        in and [(item . head) ks' == l
               ,(item . last) ks' == h
               ,floor (sum (map weight ks')) == floor p 
               ,eqCost xs zs'
               ] 

main :: IO ()
main = defaultMain tests

tests :: [Test]
tests =
    [ testProperty "numRanges" prop_numRanges
    , testProperty "ordered" prop_ordered
    , testProperty "reverse" prop_reverse
    , testProperty "totalCost" prop_totalCost
    , testProperty "bestCost" prop_bestCost
    , testProperty "rangeCost" prop_rangeCost
    ]