packages feed

LinearSplit-0.2: tests/Properties.hs

-- | Tests for the Utils.LinearSplit module.

module Main where

import Data.LinearSplit
import Test.QuickCheck hiding (ranges)
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

-- | Different partition strategies to test
splitters (Split n xs t) = --map ranges 
  [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 rangeCost) (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)
       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 items after splitting the same as the items 
-- before splitting
prop_equal s = 
  let inpIds = map item (items s)
      outIds = concatMap (map item)
  in all (inpIds ==) (map outIds (splitters s))

-- | Reverse working items preserves the optimal cost
prop_reverse s =
  let (n,xs) = (chunks s, items s)
  in partitionCost (lPartition n xs) == 
     partitionCost (lPartition n (reverse xs))

-- | Testing helpers
--rangeCost :: [Item Int Double] -> Double
rangeCost = sum . map weight

partitionCost = floor . maxCost where
   maxCost = foldr (max . rangeCost) 0.0

main :: IO ()
main = defaultMain tests

tests :: [Test]
tests =
    [ testProperty "numRanges" prop_numRanges
    , testProperty "equal" prop_equal
    , testProperty "reverse" prop_reverse
    , testProperty "totalCost" prop_totalCost
    , testProperty "bestCost" prop_bestCost
    ]