packages feed

chatter-0.8.0.0: tests/src/NLP/Similarity/VectorSimTests.hs

{-# LANGUAGE OverloadedStrings #-}
module NLP.Similarity.VectorSimTests where

import Prelude hiding (negate, sum)
import Test.QuickCheck ( Property, (==>) )
import Test.QuickCheck.Property ()
import Test.Tasty (TestTree, testGroup)
import Test.Tasty.QuickCheck (testProperty)

import qualified Data.Text as T
import qualified NLP.Similarity.VectorSim as TV

import NLP.Similarity.VectorSim
import NLP.Types (mkCorpus)

import TestUtils

tests :: TestTree
tests = testGroup "Vector Sim"
        [ -- testGroup "Dot Products" $ map (genTestF2 dotProd)
        --   [ ("3-4-5", [1,3,-5], [4, -2, -1], 3)
        --   , ("identical", [1], [1], 1.0)
        --   , ("orthogonal", [1,0], [0,1], 0)
        --   ]
        -- , testGroup "cosines" $ map (genTestF2 cosVec)
        --   -- ("identical", [0], [0], NaN) -- can't determine the angle.
        --   [ ("identical", [1], [1], 1.0)
        --   , ("identical", [1,2], [1,2], 1.0)
        --   , ("orthogonal", [1,0], [0,1], 0)
        --   ]
        -- , testGroup "Magnitude tests" $ map (genTest magnitude)
        --   [ ("3-4-5", [3,4], 5)
        --   , ("empty", [], 0)
        --   , ("single", [1], 1)
        --   ]
         testGroup "tf tests" $ map (genTest2 tf)
          [ ("", "test", mkDocument ["test"], 1)
          , ("", "a",    mkDocument ["a", "test"], 1)
          , ("", "a",    mkDocument ["test"], 0)
          ]
        , testGroup "idf tests" $ map (genTestF2 idf)
          [ ("", "test", mkCorpus [["test"]], log(2/3))
          , ("", "a", mkCorpus [["test"]], log(1))
          , ("", "a", mkCorpus [["a", "test"],["test"]], log(2/2))
          ]
        , testGroup "tf_idf tests" $ map (genTestF3 tf_idf)
          [ ("", "test", mkDocument ["test"], mkCorpus [["test"]], log(2/3))
          , ("", "a",    mkDocument ["a", "test"], mkCorpus [["some"], ["test"]], 0.40546)
          , ("", "foo",  mkDocument ["foo"], mkCorpus [["test"]], 0)
          , ("", "foo",  mkDocument ["foo"], mkCorpus [["foo"],["test"]], 0)
          , ("", "bar",  mkDocument ["foo"], mkCorpus [["test"]], 0)
          , ("", "bar",  mkDocument ["foo"], mkCorpus [["foo"],["test"]], 0)
          ]
        , testGroup "Similarity tests, trivial corpus" $
            map (genTest2 $ sim $ mkCorpus [["test"]])
                    [ ("same doc", "test", "test", 1)
                      -- This next test is invalid becausse the
                      -- initial smoothing causes funny results (this
                      -- should not be 1.0, and it /is not/ when the
                      -- corpus is bigger.)
                    , ("one off", "a test", "the test", 1.0)
                    , ("No match", "foo", "bar", 0.0)
                    ]
        , testGroup "Similarity tests, minor corpus" $
            map (genTestF2 $ sim $ mkCorpus [ ["a", "sample"]
                                            , ["the", "test"]
                                            , ["big", "example"]
                                            , ["more", "terms"]])
                    [ ("same doc", "test", "test", 1)
                    , ("one off", "a test", "the test", 0.5)
                    , ("No match", "foo", "bar", 0.0)
                    ]
        , testProperty "idf /= NaN" prop_idfIsANum
        , testProperty "tf_idf /= NaN" prop_tf_idfIsANum
        , testProperty "similarity /= NaN" prop_similarity_isANum
        , testProperty "v + 0 = v" prop_addVectorZero
        , testProperty "v - v = 0" prop_negateVector
        ]

prop_addVectorZero :: TermVector -> Bool
prop_addVectorZero v = addVectors v zeroVector == v &&
                       addVectors zeroVector v == v

prop_negateVector :: TermVector -> Bool
prop_negateVector v =
    let theSum = addVectors v (negate v)
    in and [ TV.lookup k theSum == 0
           | k <- TV.keys v
           ]

prop_sum :: [TermVector] -> Bool
prop_sum vs =
    foldr addVectors zeroVector vs == sum vs

prop_idfIsANum :: String -> [[String]] -> Bool
prop_idfIsANum term docs = not (isNaN (idf termTxt $ mkCorpus docsTxt))
  where
    termTxt = T.pack term
    docsTxt = map (map T.pack) docs

prop_tf_idfIsANum :: String -> [String] -> [[String]] -> Bool
prop_tf_idfIsANum term doc docs = not $ isNaN $ tf_idf termTxt (mkDocument docTxt) $ mkCorpus docsTxt
  where
    termTxt = T.pack term
    docTxt = map T.pack doc
    docsTxt = map (map T.pack) docs

prop_similarity_isANum :: [[String]] -> [String] -> [String] -> Property
prop_similarity_isANum strCorp d1 d2 = strCorp /= [] &&
                                       (concat d1 /= []) &&
                                       (concat d2 /= [])==> let
  corpus = mkCorpus $ map (map T.pack) strCorp
  doc1 = map T.pack d1
  doc2 = map T.pack d2
  in not $ isNaN $ similarity corpus doc1 doc2