interval-algebra-0.1.2: test/IntervalAlgebraSpec.hs
{-# LANGUAGE FlexibleInstances #-}
import Test.Hspec
import Test.Hspec.QuickCheck
import Test.QuickCheck
import IntervalAlgebra as IA
import Data.Maybe
import Control.Monad
xor :: Bool -> Bool -> Bool
xor a b = a /= b
instance Arbitrary IntervalInt where
arbitrary = liftM2 safeInterval' arbitrary arbitrary
type IntervalInt = Interval Int
makePos :: Int -> Int
makePos x
| x == 0 = x + 1
| x < 0 = negate x
| otherwise = x
-- | A function for creating intervals when you think you know what you're doing.
safeInterval :: Int -> Int -> IntervalInt
safeInterval x y = unsafeInterval (min x y) (max x y)
-- | Safely create a valid 'IntervalInt' from two Ints by adding 'makepos' @dur@
-- to @start@ to set the duration of the interval.
safeInterval' :: Int -> Int -> IntervalInt
safeInterval' start dur = safeInterval start (start + makePos dur)
-- | Create a 'Maybe IntervalInt' from two Ints.
safeInterval'' :: Int -> Int -> Maybe IntervalInt
safeInterval'' a b
| b <= a = Nothing
| otherwise = Just $ safeInterval a b
-- | A set used for testing M1 defined so that the M1 condition is true.
data M1set = M1set {
m11 :: IntervalInt
, m12 :: IntervalInt
, m13 :: IntervalInt
, m14 :: IntervalInt }
deriving (Show)
instance Arbitrary M1set where
arbitrary = do
x <- arbitrary
a <- arbitrary
b <- arbitrary
c <- arbitrary
return $ m1set x a b c
-- | Smart constructor of 'M1set'.
m1set :: IntervalInt -> Int -> Int -> Int -> M1set
m1set x a b c = M1set p1 p2 p3 p4
where p1 = x -- interval i in prop_IAaxiomM1
p2 = safeInterval' (end x) a -- interval j in prop_IAaxiomM1
p3 = safeInterval' (end x) b -- interval k in prop_IAaxiomM1
p4 = safeInterval (begin p2 - (makePos c)) (begin p2)
{-
** Axiom M1
The first axiom of Allen and Hayes (1987) states that if "two periods both
meet a third, thn any period met by one must also be met by the other."
That is:
\[
\forall i,j,k,l s.t. (i:j & i:k & l:j) \implies l:k
\]
-}
prop_IAaxiomM1 :: M1set -> Property
prop_IAaxiomM1 x =
(i `meets` j && i `meets` k && l `meets` j) ==> (l `meets` k)
where i = m11 x
j = m12 x
k = m13 x
l = m14 x
-- | A set used for testing M2 defined so that the M2 condition is true.
data M2set = M2set {
m21 :: IntervalInt
, m22 :: IntervalInt
, m23 :: IntervalInt
, m24 :: IntervalInt }
deriving (Show)
instance Arbitrary M2set where
arbitrary = do
x <- arbitrary
a <- arbitrary
b <- arbitrary
c <- arbitrary
return $ m2set x a b c
-- | Smart constructor of 'M2set'.
m2set :: IntervalInt -> IntervalInt -> Int -> Int -> M2set
m2set x y a b = M2set p1 p2 p3 p4
where p1 = x -- interval i in prop_IAaxiomM2
p2 = safeInterval' (end x) a -- interval j in prop_IAaxiomM2
p3 = y -- interval k in prop_IAaxiomM2
p4 = safeInterval' (end y) b -- interval l in prop_IAaxiomM2
{-
** Axiom M2
If period i meets period j and period k meets l,
then exactly one of the following holds:
1) i meets l;
2) there is an m such that i meets m and m meets l;
3) there is an n such that k meets n and n meets j.
That is,
\[
\forall i,j,k,l s.t. (i:j & k:l) \implies
i:l \oplus
(\exists m s.t. i:m:l) \oplus
(\exists m s.t. k:m:j)
\]
-}
prop_IAaxiomM2 :: M2set -> Property
prop_IAaxiomM2 x =
(i `meets` j && k `meets` l) ==>
(i `meets` l) `xor`
(not $ isNothing m) `xor`
(not $ isNothing n)
where i = m21 x
j = m22 x
k = m23 x
l = m24 x
m = safeInterval'' (end $ i) (begin $ l)
n = safeInterval'' (end $ k) (begin $ j)
{-
** Axiom ML1
An interval cannot meet itself.
\[
\forall i \lnot i:i
\]
-}
prop_IAaxiomML1 :: IntervalInt -> Property
prop_IAaxiomML1 x = not (x `meets` x) === True
{-
** Axiom ML2
If i meets j then j does not meet i.
\[
\forall i,j i:j \implies \lnot j:i
\]
-}
prop_IAaxiomML2 :: M2set -> Property
prop_IAaxiomML2 x =
(i `meets` j) ==> not (j `meets` i)
where i = m21 x
j = m22 x
{-
** Axiom M3
Time does not start or stop:
\[
\forall i \exists j,k s.t. j:i:k
\]
-}
prop_IAaxiomM3 :: IntervalInt -> Property
prop_IAaxiomM3 i =
(j `meets` i && i `meets` k) === True
where j = safeInterval (begin i - 1) (begin i)
k = safeInterval (end i) (end i + 1)
{-
** Axiom M4
If two meets are separated by intervals, then this sequence is a longer interval.
\[
\forall i,j i:j \implies (\exists k,m,n s.t m:i:j:n & m:k:n)
\]
-}
prop_IAaxiomM4 :: M2set -> Property
prop_IAaxiomM4 x =
((m `meets` i && i `meets` j && j `meets` n) &&
(m `meets` k && k `meets` n)) === True
where i = m21 x
j = m22 x
m = safeInterval (begin i - 1) (begin i)
n = safeInterval (end j) (end j + 1)
k = safeInterval (end m) (begin n)
{-
** Axiom M5
If two meets are separated by intervals, then this sequence is a longer interval.
\[
\forall i,j,k,l (i:j:l & i:k:l) \seteq j = k
\]
-}
-- | A set used for testing M5.
data M5set = M5set {
m51 :: IntervalInt
, m52 :: IntervalInt }
deriving (Show)
instance Arbitrary M5set where
arbitrary = do
x <- arbitrary
a <- arbitrary
b <- arbitrary
return $ m5set x a b
-- | Smart constructor of 'M5set'.
m5set :: IntervalInt -> Int -> Int -> M5set
m5set x a b = M5set p1 p2
where p1 = x -- interval i in prop_IAaxiomM5
p2 = safeInterval' ps a -- interval l in prop_IAaxiomM5
ps = (makePos b) + (end x) -- creating l by shifting and expanding i
prop_IAaxiomM5 :: M5set -> Property
prop_IAaxiomM5 x =
((i `meets` j && j `meets` l) &&
(i `meets` k && k `meets` l)) === (j == k)
where i = m51 x
j = safeInterval (end i) (begin l)
k = safeInterval (end i) (begin l)
l = m52 x
{-
** Axiom M4.1
Ordered unions:
\[
\forall i,j i:j \implies (\exists m,n s.t. m:i:j:n & m:(i+j):n)
\]
-}
prop_IAaxiomM4_1 :: M2set -> Property
prop_IAaxiomM4_1 x =
((m `meets` i && i `meets` j && j `meets` n) &&
(m `meets` ij && ij `meets` n)) === True
where i = m21 x
j = m22 x
m = safeInterval (begin i - 1) (begin i)
n = safeInterval (end j) (end j + 1)
ij = fromJust $ i .+. j
{-
* Interval Relation property testing
-}
prop_IAbefore :: IntervalInt -> IntervalInt -> Property
prop_IAbefore i j =
IA.before i j ==> (i `meets` k) && (k `meets` j)
where k = safeInterval (end i) (begin j)
prop_IAstarts:: IntervalInt -> IntervalInt -> Property
prop_IAstarts i j
| ((IA.starts i j) == True) =
let k = safeInterval (end i) (end j)
in
(j == (fromJust $ i .+. k)) === True
| otherwise = IA.starts i j === False
prop_IAfinishes:: IntervalInt -> IntervalInt -> Property
prop_IAfinishes i j
| ((IA.finishes i j) == True) =
let k = safeInterval (begin j) (begin i)
in
(j == (fromJust $ k .+. i)) === True
| otherwise = IA.finishes i j === False
prop_IAoverlaps:: IntervalInt -> IntervalInt -> Property
prop_IAoverlaps i j
| ((IA.overlaps i j) == True) =
let k = safeInterval (begin i) (begin j)
l = safeInterval (begin j) (end i)
m = safeInterval (end i) (end j)
in
((i == (fromJust $ k .+. l )) &&
(j == (fromJust $ l .+. m ))) === True
| otherwise = IA.overlaps i j === False
prop_IAduring:: IntervalInt -> IntervalInt -> Property
prop_IAduring i j
| ((IA.during i j) == True) =
let k = safeInterval (begin j) (begin i)
l = safeInterval (end i) (end j)
in
(j == (fromJust $ (fromJust $ k .+. i) .+. l)) === True
| otherwise = IA.during i j === False
{-
For any two pair of intervals exactly one 'IntervalRelation' should hold.
-}
allIArelations:: [(ComparativePredicateOf (IntervalInt))]
allIArelations = [ IA.equals
, IA.meets
, IA.metBy
, IA.before
, IA.after
, IA.starts
, IA.startedBy
, IA.finishes
, IA.finishedBy
, IA.overlaps
, IA.overlappedBy
, IA.during
, IA.contains ]
prop_exclusiveRelations:: IntervalInt -> IntervalInt -> Property
prop_exclusiveRelations x y =
( 1 == length (filter id $ map (\r -> r x y) allIArelations)) === True
main :: IO ()
main = hspec $ do
describe "Interval Algebra Axioms for meets property" $
--modifyMaxDiscardRatio (* 10) $
do
it "M1" $ property prop_IAaxiomM1
it "M2" $ property prop_IAaxiomM2
it "ML1" $ property prop_IAaxiomML1
it "ML2" $ property prop_IAaxiomML2
{-
ML3 says that For all i, there does not exist m such that i meets m and
m meet i. Not testing that this axiom holds, as I'm not sure how I would
test the lack of existence.
-}
--it "ML3" $ property prop_IAaxiomML3
it "M3" $ property prop_IAaxiomM3
it "M4" $ property prop_IAaxiomM4
it "M5" $ property prop_IAaxiomM5
it "M4.1" $ property prop_IAaxiomM4_1
describe "Interval Algebra relation properties" $
modifyMaxSuccess (*10) $
do
it "before" $ property prop_IAbefore
it "starts" $ property prop_IAstarts
it "finishes" $ property prop_IAfinishes
it "overlaps" $ property prop_IAoverlaps
it "during" $ property prop_IAduring
describe "Interval Algebra relation uniqueness" $
modifyMaxSuccess (*100) $
do
it "exactly one relation must be true" $ property prop_exclusiveRelations