Spec.hs 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. {-# LANGUAGE FlexibleInstances #-}
  2. import Test.Hspec
  3. import Test.QuickCheck
  4. import Test.Hspec.QuickCheck
  5. import qualified Data.Matrix as M
  6. import Data.Matrix.SmithNormalForm
  7. import Data.Matrix.SmithNormalForm.Internal
  8. main :: IO ()
  9. main = do
  10. hspec diagonalizeSpec
  11. hspec snfSpec
  12. hspec invariantFactorsSpec
  13. diagonalizeSpec :: Spec
  14. diagonalizeSpec = do
  15. describe "diagonalize" $ do
  16. prop "result is a diagonal matrix" $
  17. \m -> (diagonalize m :: M.Matrix Integer) `shouldSatisfy` isDiagonalMatrix
  18. instance Arbitrary (M.Matrix Integer) where
  19. arbitrary = randomMatrix
  20. -- random (not necessarily square) matrix from 2x2 to 10x10
  21. randomMatrix :: (Num a, Arbitrary a) => Gen (M.Matrix a)
  22. randomMatrix = do
  23. n <- choose (2,10)
  24. m <- choose (2,10)
  25. entries <- vector (n*m)
  26. return (M.fromList n m entries)
  27. snfSpec :: Spec
  28. snfSpec = do
  29. describe "smithNormalForm" $ do
  30. it "is correct on sample square matrix" $ do -- from Wikipedia
  31. let sample = M.fromList 3 3 [2, 4, 4, -6, 6, 12, 10, 4, 16]
  32. smithNormalForm sample `shouldBe` M.diagonalList 3 0 [2, 2, 156]
  33. it "is correct on square matrix with larger entries" $ do -- src: https://www.mathworks.com/help/symbolic/smithform.html
  34. let largerEntryMatrix = M.fromList 5 5 [ 25, -300, 1050, -1400, 630, -300, 4800, -18900, 26880, -12600, 1050, -18900, 79380, -117600, 56700, -1400, 26880, -117600, 179200, -88200, 630, -12600, 56700, -88200, 44100]
  35. smithNormalForm largerEntryMatrix `shouldBe` M.diagonalList 5 0 [5, 60, 420, 840, 2520]
  36. it "is correct on a diagonal matrix that requires rectifying with a 0 column" $ do
  37. let diagonalMatrix = M.fromList 4 4 [4, 0, 0, 0, 0, 6, 0, 0, 0, 0, 8, 0, 0, 0, 0, 5]
  38. smithNormalForm diagonalMatrix `shouldBe` M.diagonalList 4 0 [1, 2, 4, 120]
  39. it "is correct on a determinant 0 matrix" $ do
  40. let det0example = M.fromList 4 4 [-2, 1, 1, 0, 1, -2, 1, 0, 1, 1, -3, 1, 0, 0, 1, -1]
  41. smithNormalForm det0example `shouldBe` M.diagonalList 4 0 [1, 1, 3, 0]
  42. it "is correct on a nonsquare matrix" $ do
  43. let nonsquare = M.fromList 2 3 [8, 4, 8, 4, 8, 4]
  44. smithNormalForm nonsquare `shouldBe` M.fromList 2 3 [4, 0, 0, 0, 12, 0]
  45. it "is correct on a nonsquare matrix with a gap of zeros" $ do
  46. let zerogap = M.fromList 2 5 [1, 0, -1, 0, -1, 0, 0, 1, 1, 1]
  47. smithNormalForm zerogap `shouldBe` M.fromList 2 5 [1, 0, 0, 0, 0, 0, 1, 0, 0, 0]
  48. prop "is idempotent" $
  49. \m -> (smithNormalForm (m :: M.Matrix Integer)) `shouldBe` (smithNormalForm . smithNormalForm) m
  50. invariantFactorsSpec :: Spec
  51. invariantFactorsSpec = do
  52. describe "invariantFactors" $ do
  53. prop "divisibility conditions hold" $
  54. \m -> (invariantFactors (m :: M.Matrix Integer)) `shouldSatisfy` divisibilityCondition
  55. divisibilityCondition :: [Integer] -> Bool
  56. divisibilityCondition xs = and $ map (\(d, d') -> d `divides` d') divPairs
  57. where divIndices = zip [0..(length xs) - 2] [1..(length xs) - 1]
  58. divPairs = map (\(k, k') -> (xs !! k, xs !! k')) $ divIndices