packages feed

allen-0.1.0.0: src/Data/Allen/Interval.hs

-- |
-- Module      : Data.Allen.Interval
-- Description : Functions for working with intervals.
-- Maintainer  : Archaversine 
--
-- This module provides functions for working with intervals. Note that almost 
-- all exposed functions only work with interval IDs. This is because the 
-- internal representation of intervals is subject to change, but the IDs will 
-- remain the same no matter what.
-- 
-- = Creating intervals
-- Intervals are created with the 'interval' function, which creates an interval 
-- adds it to the internal network representation, then returns its corresponding 
-- ID. Note that upon creating a new interval, it will have all relations to all 
-- other intervals. This is because the creation of an interval does not provide 
-- any meaningful information about its relations to other intervals.
--
-- Creating two intervals sleeps and snores:
--
-- @ 
-- sleeps <- 'interval' 
-- snores <- 'interval'
-- @
--
-- = Defining Relations Between Intervals
-- There are three main ways to define relations betweek intervals:
--
-- (1) Define a single relation using the 'Relation' type.
-- (2) Define a set of relations using a list of 'Relation' types.
-- (3) Define a set of relations using a 'RelationBits' type.
--
-- == Defining a single relation
-- This is the easiest to do, and is done with the 'assume' function. This 
-- function takes three arguments: the ID of the first interval, the relation 
-- between the two intervals, and the ID of the second interval. 
--
-- Example:
--
-- @ 
-- sleeps <- 'interval' 
-- snores <- 'interval' 
--
-- 'assume' snores 'During' sleeps
-- @
--
-- == Defining a Set of Relations 
-- This is done with the 'assumeSet' function. This function takes three 
-- arguments: the ID of the first interval, a list of relations between the 
-- two intervals, and the ID of the second interval. 
--
-- Example: 
--
-- @ 
-- sleeps <- 'interval' 
-- snores <- 'interval' 
--
-- 'assumeSet' snores ['StartedBy', 'During', 'FinishedBy'] sleeps
-- @
--
-- == Defining a Set of Relations Using Bit Representation
-- This is done with the 'assumeBits' function. This function takes three 
-- arguments: the ID of the first interval, a 'RelationBits' type representing 
-- the relations between the two intervals, and the ID of the second interval. 
-- Generally, this function should not be used directly, but it can be used 
-- to speed up calculations if you already know the bit representation.
--
-- Example: 
--
-- @ 
-- sleeps <- 'interval' 
-- snores <- 'interval' 
--
-- let relations = 'relationUnion' $ map 'toBits' ['StartedBy', 'During', 'FinishedBy']
--
-- 'assumeBits' snores relations sleeps
-- @
--
-- = Getting Constraints
-- The 'getConstraints' function returns a 'RelationBits' type representing the 
-- set of all possible relations between two intervals. This is useful for 
-- determining specific information between two intervals.
--
-- Example: 
--
-- @
-- sleeps <- 'interval' 
-- snores <- 'interval' 
--
-- 'assume' snores 'During' sleeps
--
-- 'fromBits' \<$\> 'getConstraints' snores sleeps
-- @

module Data.Allen.Interval ( interval
                           , intervalCount
                           , fromID
                           , assume
                           , assumeSet
                           , assumeBits
                           , setRelation
                           , getConstraints
                           ) where

import Control.Monad
import Control.Monad.State

import Data.Allen.Types
import Data.Allen.Relation

import Data.Bits

import qualified Data.Map.Strict as Map

-- | Create a new interval. 
-- Returns the interval ID.
interval :: Allen IntervalID 
interval = do
    intervals <- get

    let iD         = Map.size intervals
        iRelations = Map.fromList [(x, allRelationBits) | x <- [0 .. iD - 1]]
        intervals' = Map.map (\x -> setRelation x allRelationBits iD) intervals
        i          = Interval iD iRelations

    put $ Map.insert iD i intervals'
    return iD 

-- | Return the number of intervals that are currently in the graph.
intervalCount :: Allen Int 
intervalCount = gets Map.size

-- | Given two intervals, return a copy of the first interval such that it now 
-- has the specified set of relations to the second interval.
--
-- This has no effect on the second interval or the network representation.
setRelation :: Interval -> RelationBits -> IntervalID -> Interval 
setRelation i1 r i2 = i1 { intervalRelations = relations }
    where relations = Map.insert i2 r $ intervalRelations i1

-- | Define a relation between two intervals. 
assume :: IntervalID -> Relation -> IntervalID -> Allen ()
assume id1 r = assumeBits id1 (toBits r)

-- | Define a set of relations between two intervals.
assumeSet :: IntervalID -> [Relation] -> IntervalID -> Allen ()
assumeSet id1 = assumeBits id1 . relationUnion . map toBits

-- | Define a relation between two intervals using RelationBits.
assumeBits :: IntervalID -> RelationBits -> IntervalID -> Allen ()
assumeBits id1 r id2 = do 
    i1 <- fromID id1 
    i2 <- fromID id2 

    let i1' = setRelation i1 r id2 
        i2' = setRelation i2 (converse r) id1

    modify $ Map.insert id1 i1' . Map.insert id2 i2'
    propogate (id1, id2)

-- | Propogate the relations between two intervals to all other intervals 
-- that are related to either of the two intervals.
propogate :: (IntervalID, IntervalID) -> Allen ()
propogate r = evalStateT propogate' [r]

propogate' :: StateT [(IntervalID, IntervalID)] Allen ()
propogate' = do 
    toDo <- get
    case toDo of 
        [] -> return ()
        ((i, j):_) -> do 
            modify tail -- Remove the first element from the queue
            propogate'' (i, j)
            propogate'' (j, i)
            propogate'

propogate'' :: (IntervalID, IntervalID) -> StateT [(IntervalID, IntervalID)] Allen () 
propogate'' (i, j) = do 
    count <- lift intervalCount

    let range = [k | k <- [0 .. count - 1], k /= i, k /= j]

    forM_ range $ \k -> do 
        constraints <- lift $ compose <$> getConstraints k i <*> getConstraints i j
        nkj         <- lift $ getConstraints k j

        let rkj = nkj .&. constraints 

        -- If rkj is a proper subset of nkj, then add (k, j) to the queue
        when (rkj .|. nkj == nkj && rkj < nkj) $ do 
            modify ((k, j):)

        intervalK <- lift $ fromID k
        lift $ modify $ Map.insert k (setRelation intervalK rkj j)

    forM_ range $ \k -> do 
        constraints <- lift $ compose <$> getConstraints i j <*> getConstraints j k
        nik         <- lift $ getConstraints i k 

        let rik = nik .&. constraints

        -- If rik is a proper subset of nik, then add (i, k) to the queue
        when (rik .|. nik == nik && rik < nik) $ do 
            modify ((i, k):)

        intervalI <- lift $ fromID i
        lift $ modify $ Map.insert i (setRelation intervalI rik k)
   
-- | Return the set of possible constraints/relations between two intervals.
getConstraints :: IntervalID -> IntervalID -> Allen RelationBits
getConstraints id1 id2 = Map.findWithDefault 0 id2 . intervalRelations <$> fromID id1