SparseMatrix.hs 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. {-# LANGUAGE TemplateHaskell #-}
  2. module Internal.Termination.SparseMatrix
  3. ( matrix
  4. , tests
  5. ) where
  6. import Agda.Termination.Semiring (HasZero(..), Semiring)
  7. import qualified Agda.Termination.Semiring as Semiring
  8. import Agda.Termination.SparseMatrix
  9. import Agda.Utils.Functor
  10. import Agda.Utils.Tuple
  11. import Data.Array
  12. import Data.Function
  13. import qualified Data.List as List
  14. import Internal.Helpers
  15. ------------------------------------------------------------------------
  16. -- * Generators, properties and tests
  17. ------------------------------------------------------------------------
  18. -- ** Size
  19. ------------------------------------------------------------------------
  20. instance Integral i => Arbitrary (Size i) where
  21. arbitrary = do
  22. r <- natural
  23. c <- natural
  24. return $ Size { rows = fromInteger r, cols = fromInteger c }
  25. instance CoArbitrary i => CoArbitrary (Size i) where
  26. coarbitrary (Size rs cs) = coarbitrary rs . coarbitrary cs
  27. -- | Size invariant: dimensions are non-negative.
  28. sizeInvariant :: (Ord i, Num i) => Size i -> Bool
  29. sizeInvariant sz = rows sz >= 0 && cols sz >= 0
  30. prop_Arbitrary_Size :: Size Integer -> Bool
  31. prop_Arbitrary_Size = sizeInvariant
  32. prop_size :: TM -> Bool
  33. prop_size m = sizeInvariant (size m)
  34. -- ** Matrix indices
  35. ------------------------------------------------------------------------
  36. instance Integral i => Arbitrary (MIx i) where
  37. arbitrary = MIx <$> positive <*> positive
  38. instance CoArbitrary i => CoArbitrary (MIx i) where
  39. coarbitrary (MIx r c) = coarbitrary r . coarbitrary c
  40. -- | Indices must be positive, @>= 1@.
  41. mIxInvariant :: (Ord i, Num i) => MIx i -> Bool
  42. mIxInvariant i = row i >= 1 && col i >= 1
  43. prop_Arbitrary_MIx :: MIx Integer -> Bool
  44. prop_Arbitrary_MIx = mIxInvariant
  45. -- ** Matrices
  46. ------------------------------------------------------------------------
  47. -- | Matrix indices are lexicographically sorted with no duplicates.
  48. -- All indices must be within bounds.
  49. matrixInvariant :: (Num i, Ix i, HasZero b) => Matrix i b -> Bool
  50. matrixInvariant (Matrix size@Size{ rows, cols} m) =
  51. sizeInvariant size
  52. && all inBounds m
  53. && all nonZero m
  54. && strictlySorted (MIx 0 0) m
  55. where
  56. inBounds (MIx i j, _) = 1 <= i && i <= rows
  57. && 1 <= j && j <= cols
  58. nonZero (_, b) = b /= zeroElement
  59. -- | Check whether an association list is ordered and
  60. -- deterministic, a partial function from @i@ to @b@.
  61. strictlySorted :: (Ord i) => i -> [(i, b)] -> Bool
  62. strictlySorted i [] = True
  63. strictlySorted i ((i', _) : l) = i < i' && strictlySorted i' l
  64. -- Ord MIx should be the lexicographic order already (Haskell report).
  65. -- | Generates a matrix of the given size, using the given generator
  66. -- to generate the rows.
  67. matrixUsingRowGen :: (Integral i, HasZero b)
  68. => Size i
  69. -> (i -> Gen [b])
  70. -- ^ The generator is parameterised on the size of the row.
  71. -> Gen (Matrix i b)
  72. matrixUsingRowGen sz rowGen = do
  73. rows <- vectorOf (fromIntegral $ rows sz) (rowGen $ cols sz)
  74. return $ fromLists sz rows
  75. -- | Generates a matrix of the given size.
  76. matrix :: (Integral i, Arbitrary b, HasZero b)
  77. => Size i -> Gen (Matrix i b)
  78. matrix sz = matrixUsingRowGen sz (\n -> vectorOf (fromIntegral n) arbitrary)
  79. prop_matrix :: Size Int -> Property
  80. prop_matrix sz = forAll (matrix sz :: Gen TM) $ \ m -> size m == sz
  81. -- | Generate a matrix of arbitrary size.
  82. instance (Integral i, Arbitrary b, HasZero b)
  83. => Arbitrary (Matrix i b) where
  84. arbitrary = matrix =<< arbitrary
  85. instance (Integral i, CoArbitrary b, HasZero b) => CoArbitrary (Matrix i b) where
  86. coarbitrary m = coarbitrary (toLists m)
  87. -- | This matrix type is used for tests.
  88. type TM = Matrix Int Int
  89. prop_Arbitrary_Matrix :: TM -> Bool
  90. prop_Arbitrary_Matrix = matrixInvariant
  91. -- ** Matrix operations
  92. -- | 'fromIndexList' is identity on well-formed sparse matrices.
  93. prop_fromIndexList :: TM -> Bool
  94. prop_fromIndexList m@(Matrix size vs) = fromIndexList size vs == m
  95. -- | Converting a matrix to a list of lists and back is the identity.
  96. prop_fromLists_toLists :: TM -> Bool
  97. prop_fromLists_toLists m = fromLists (size m) (toLists m) == m
  98. -- | Any 1x1 matrix is a singleton.
  99. prop_isSingleton :: Int -> Bool
  100. prop_isSingleton b = Just b == (isSingleton (fromLists (Size 1 1) [[b]] :: TM))
  101. -- | The length of the diagonal is the minimum of the number of
  102. -- rows and columns of the matrix.
  103. prop_diagonal :: TM -> Bool
  104. prop_diagonal m@(Matrix (Size r c) _) =
  105. length (diagonal m) == min r c
  106. -- prop_diagonal' n =
  107. -- forAll natural $ \n ->
  108. -- forAll (matrix (Size n n) :: Gen TM) $ \m ->
  109. -- length (diagonal m) == n
  110. -- | Transposing twice is the identity.
  111. prop_transpose :: TM -> Bool
  112. prop_transpose m = matrixInvariant m' && m == transpose m'
  113. where m' = transpose m
  114. -- | Verify 'toSparseRows' against an alternative implementation which
  115. -- serves as specification.
  116. toSparseRows' :: (Eq i) => Matrix i b -> [(i,[(i,b)])]
  117. toSparseRows' (Matrix _ m) =
  118. -- group list by row index
  119. for (List.groupBy ((==) `on` (row . fst)) m) $ \ ((MIx i j, b) : vs) ->
  120. -- turn each group into a sparse row
  121. (i, (j,b) : map (mapFst col) vs)
  122. prop_toSparseRows :: TM -> Bool
  123. prop_toSparseRows m = toSparseRows m == toSparseRows' m
  124. prop_addColumn :: TM -> Bool
  125. prop_addColumn m =
  126. matrixInvariant m'
  127. &&
  128. map init (toLists m') == toLists m
  129. where
  130. m' = addColumn zeroElement m
  131. prop_addRow :: TM -> Bool
  132. prop_addRow m =
  133. matrixInvariant m'
  134. &&
  135. init (toLists m') == toLists m
  136. where
  137. m' = addRow zeroElement m
  138. -- ** Matrix addition
  139. -- | Old implementation of 'zipMatrices'.
  140. zipMatrices' :: forall a b c i . (Ord i)
  141. => (a -> c) -- ^ Element only present in left matrix.
  142. -> (b -> c) -- ^ Element only present in right matrix.
  143. -> (a -> b -> c) -- ^ Element present in both matrices.
  144. -> (c -> Bool) -- ^ Result counts as zero?
  145. -> Matrix i a -> Matrix i b -> Matrix i c
  146. zipMatrices' f g h zero m1 m2 = Matrix (supSize m1 m2) (merge (unM m1) (unM m2))
  147. where
  148. merge :: [(MIx i,a)] -> [(MIx i,b)] -> [(MIx i,c)]
  149. merge [] m2 = filter (not . zero . snd) $ map (mapSnd g) m2
  150. merge m1 [] = filter (not . zero . snd) $ map (mapSnd f) m1
  151. merge m1@((i,a):m1') m2@((j,b):m2') =
  152. case compare i j of
  153. LT -> if zero c then r else (i,c) : r where c = f a ; r = merge m1' m2
  154. GT -> if zero c then r else (j,c) : r where c = g b ; r = merge m1 m2'
  155. EQ -> if zero c then r else (i,c) : r where c = h a b ; r = merge m1' m2'
  156. -- | Verify 'zipMatrices' against older implementation
  157. prop_zipMatrices_correct :: TM -> TM -> Bool
  158. prop_zipMatrices_correct m1 m2 =
  159. zipMatrices id id (+) (== 0) m1 m2 == zipMatrices' id id (+) (== 0) m1 m2
  160. -- | Matrix addition is well-defined, associative and commutative.
  161. prop_add :: Size Int -> Property
  162. prop_add sz =
  163. forAll (three (matrix sz :: Gen TM)) $ \(m1, m2, m3) ->
  164. let m' = add (+) m1 m2 in
  165. isAssociative (add (+)) m1 m2 m3 &&
  166. isCommutative (add (+)) m1 m2 &&
  167. matrixInvariant m' &&
  168. size m' == size m1
  169. -- | Verify addition against an older implementation.
  170. --
  171. -- The older implementation did not fully preserve sparsity,
  172. -- i.e., introduced zeros. Thus, we need to convert to lists to
  173. -- obtain equal results.
  174. prop_add_correct :: TM -> TM -> Bool
  175. prop_add_correct m1 m2 = toLists (add (+) m1 m2) == toLists (add' (+) m1 m2)
  176. where
  177. add' :: (Ord i) => (a -> a -> a) -> Matrix i a -> Matrix i a -> Matrix i a
  178. add' plus m1 m2 = Matrix (supSize m1 m2) $ mergeAssocWith plus (unM m1) (unM m2)
  179. where
  180. mergeAssocWith :: (Ord i) => (a -> a -> a) -> [(i,a)] -> [(i,a)] -> [(i,a)]
  181. mergeAssocWith f [] m = m
  182. mergeAssocWith f l [] = l
  183. mergeAssocWith f l@((i,a):l') m@((j,b):m')
  184. | i < j = (i,a) : mergeAssocWith f l' m
  185. | i > j = (j,b) : mergeAssocWith f l m'
  186. | otherwise = (i, f a b) : mergeAssocWith f l' m'
  187. -- ** Matrix multiplication
  188. -- | Specification of 'interAssocWith'.
  189. interAssocWith' :: (Eq i) => (a -> a -> a) -> [(i,a)] -> [(i,a)] -> [(i,a)]
  190. interAssocWith' f l l' = [ (i, f a b) | (i,a) <- l, (j,b) <- l', i == j ]
  191. -- | Efficient implementation of 'interAssocWith' matches its specification.
  192. no_tested_prop_interAssocWith_correct :: [(Int,Int)] -> [(Int,Int)] -> Bool
  193. no_tested_prop_interAssocWith_correct xs ys =
  194. interAssocWith (*) l l' == interAssocWith' (*) l l'
  195. where
  196. l = List.sortBy (compare `on` fst) xs
  197. l' = List.sortBy (compare `on` fst) ys
  198. interAssocWith2 :: Ord i => (a -> a -> a) -> [(i,a)] -> [(i,a)] -> [(i,a)]
  199. interAssocWith2 f = zipAssocWith (const []) (const []) (const Nothing) (const Nothing) (\ a -> Just . f a)
  200. no_tested_prop_interAssocWith_correct2 :: [(Int,Int)] -> [(Int,Int)] -> Bool
  201. no_tested_prop_interAssocWith_correct2 xs ys =
  202. interAssocWith (*) xs ys == interAssocWith2 (*) xs ys
  203. where
  204. l = List.sortBy (compare `on` fst) xs
  205. l' = List.sortBy (compare `on` fst) ys
  206. -- | Matrix multiplication is well-defined and associative.
  207. prop_mul :: Size Int -> Property
  208. prop_mul sz =
  209. mapSize (`div` 2) $
  210. forAll (two natural) $ \(c2, c3) ->
  211. forAll (matrix sz :: Gen TM) $ \m1 ->
  212. forAll (matrix (Size { rows = cols sz, cols = c2 })) $ \m2 ->
  213. forAll (matrix (Size { rows = c2, cols = c3 })) $ \m3 ->
  214. let m' = mult m1 m2 in
  215. isAssociative mult m1 m2 m3 &&
  216. matrixInvariant m' &&
  217. size m' == Size { rows = rows sz, cols = c2 }
  218. where mult = mul Semiring.intSemiring
  219. ------------------------------------------------------------------------
  220. -- * All tests
  221. ------------------------------------------------------------------------
  222. -- Template Haskell hack to make the following $allProperties work
  223. -- under ghc-7.8.
  224. return [] -- KEEP!
  225. -- | All tests as collected by 'allProperties'.
  226. --
  227. -- Using 'allProperties' is convenient and superior to the manual
  228. -- enumeration of tests, since the name of the property is added
  229. -- automatically.
  230. tests :: TestTree
  231. tests = testProperties "Internal.Termination.SparseMatrix" $allProperties