packages feed

interval-algebra-2.2.0: src/IntervalAlgebra/Arbitrary.hs

{-|
Module      : Generate arbitrary Intervals
Description : Functions for generating arbitrary intervals
Copyright   : (c) NoviSci, Inc 2020-2022
                  TargetRWE, 2023
License     : BSD3
Maintainer  : bsaul@novisci.com 2020-2022, bbrown@targetrwe.com 2023
Stability   : experimental
-}
{-# LANGUAGE FlexibleContexts     #-}
{-# LANGUAGE FlexibleInstances    #-}
{-# LANGUAGE ScopedTypeVariables  #-}
{-# LANGUAGE TypeApplications     #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE UndecidableInstances #-}


module IntervalAlgebra.Arbitrary where

import           Control.Applicative (liftA2, (<$>))
import           Control.Monad       (liftM2)
import           Data.Bool
import           Data.Fixed
import           Data.Function       (flip, ($), (.))
import           Data.Maybe          (Maybe (Just, Nothing))
import           Data.Ord
import qualified Data.Set            (Set, difference, null, singleton)
import           Data.Time           as DT (Day (ModifiedJulianDay), DiffTime,
                                            NominalDiffTime, UTCTime (..),
                                            picosecondsToDiffTime,
                                            secondsToDiffTime,
                                            secondsToNominalDiffTime,
                                            toModifiedJulianDay)
import           GHC.Float
import           GHC.Int             (Int)
import           GHC.Num
import           GHC.Real
import           IntervalAlgebra     (Interval, IntervalRelation (..),
                                      Intervallic, PairedInterval, Point,
                                      SizedIv (..), beginerval, converse,
                                      duration, makePairedInterval, moment,
                                      predicate, strictWithinRelations)
import           Test.QuickCheck     (Arbitrary (arbitrary, shrink), Gen,
                                      NonNegative, arbitrarySizedNatural,
                                      elements, resize, sized, suchThat)

-- NOTE: the default size for arbitrary :: Gen Int appears to be 30
arbitrarySizedPositive :: Integral a => Gen a
arbitrarySizedPositive = (+ 1) <$> arbitrarySizedNatural

-- resize in utctDayTime is to avoid rare leap-seconds-related failure, in
-- which e.g.  1858-12-31 00:00:00 UTC /= 1858-12-30 23:59:60 UTC
maxDiffTime :: Int
maxDiffTime = 86399

--instance Arbitrary DT.DiffTime where
--  arbitrary = sized
--
--
--instance Arbitrary DT.UTCTime  where
--  arbitrary = liftA2 UTCTime arbitrary arbitrary

-- Helper
-- NOTE: You likely want to restrict the size of `dur` in a more appropriate
-- way, to be uniform over the range >= moment.
sizedIntervalGen :: (SizedIv (Interval a), Ord (Moment (Interval a))) => Int -> Gen a -> Gen (Moment (Interval a)) -> Gen (Interval a)
sizedIntervalGen s gpt gmom = do
  b <- s `resize` gpt
  dur <- s `resize` gmom
  pure $ beginerval dur b

-- Generators for types that do not implement Arbitrary. This avoids creating
-- orphan instances for these types.

genDay :: Gen DT.Day
genDay = sized (\s -> DT.ModifiedJulianDay <$> s `resize` arbitrary)

genNominalDiffTime :: Gen DT.NominalDiffTime
genNominalDiffTime = sized (\s -> fromInteger <$> (min s maxDiffTime `resize` arbitrarySizedNatural))

genDiffTime :: Gen DT.DiffTime
genDiffTime = sized (\s -> fromInteger <$> (min s maxDiffTime `resize` arbitrarySizedNatural))

genUTCTime :: Gen DT.UTCTime
genUTCTime = sized (\s -> liftA2 UTCTime genDay genDiffTime)

-- Arbitrary instances
-- for SizedIv instances defined in Core

instance Arbitrary (Interval Int) where
  arbitrary = sized (\s -> sizedIntervalGen s arbitrary arbitrary)

instance Arbitrary (Interval Integer) where
  arbitrary = sized (\s -> sizedIntervalGen s arbitrary arbitrary)

instance Arbitrary (Interval Double) where
  arbitrary = sized (\s -> sizedIntervalGen s arbitrary arbitrary)

instance Arbitrary (Interval DT.Day) where
  arbitrary = sized (\s -> sizedIntervalGen s genDay arbitrary)

instance Arbitrary (Interval DT.UTCTime) where
  arbitrary = sized (\s -> sizedIntervalGen s genUTCTime genNominalDiffTime)


-- | Conditional generation of intervals relative to a reference.  If the
-- reference @iv@ is of 'moment' duration, it is not possible to generate
-- intervals from the strict enclose relations StartedBy, Contains, FinishedBy.
-- If @iv@ and @rs@ are such that no possible relations can be generated, this
-- function returns `Nothing`. Otherwise, it returns `Just` an interval that
-- satisfies at least one of the possible relations in @rs@ relative to
-- @iv@.
--
-- @
-- > import Test.QuickCheck (generate)
-- > import Data.Set (fromList)
-- > isJust $ generate $ arbitraryWithRelation (beginerval 10 (0::Int)) (fromList [Before])
-- Just (20, 22)
-- > generate $ arbitraryWithRelation (beginerval 1 (0::Int)) (fromList [StartedBy])
-- Nothing
-- > generate $ arbitraryWithRelation (beginerval 1 (0::Int)) (fromList [StartedBy, Before])
-- Just (4, 13)
-- @
--
arbitraryWithRelation
  :: forall i a b
   . (SizedIv (Interval a), Ord a, Eq (Moment (Interval a)), Arbitrary (Interval a))
  => Interval a -- ^ reference interval
  -> Data.Set.Set IntervalRelation -- ^ set of `IntervalRelation`s, of which at least one will hold for the generated interval relative to the reference
  -> Gen (Maybe (Interval a))
arbitraryWithRelation iv rs
  | rs == Data.Set.singleton Equals = elements [Just iv]
  | isEnclose && isMom = elements [Nothing]
  | isMom = Just <$> arbitrary `suchThat` predicate notStrictEnclose iv
  | otherwise = Just <$> arbitrary `suchThat` predicate rs iv
 where
  notStrictEnclose = Data.Set.difference rs (converse strictWithinRelations)
  isEnclose        = Data.Set.null notStrictEnclose
  isMom            = duration iv == moment @(Interval a)