packages feed

toysolver-0.0.2: src/Data/Interval.hs

{-# LANGUAGE ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  Data.Interval
-- Copyright   :  (c) Masahiro Sakai 2011
-- License     :  BSD-style
-- 
-- Maintainer  :  masahiro.sakai@gmail.com
-- Stability   :  provisional
-- Portability :  non-portable (ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, DeriveDataTypeable)
--
-- Interval datatype.
-- 
-----------------------------------------------------------------------------
module Data.Interval
  (
  -- * Interval type
    Interval
  , EndPoint

  -- * Construction
  , interval
  , closedInterval
  , openInterval
  , univ
  , empty
  , singleton

  -- * Query
  , null
  , member
  , notMember
  , isSubsetOf
  , isProperSubsetOf
  , lowerBound
  , upperBound
  , size

  -- * Comparison
  , (<!), (<=!), (==!), (>=!), (>!)
  , (<?), (<=?), (==?), (>=?), (>?)

  -- * Combine
  , intersection
  , join

  -- * Operations
  , pickup
  , tightenToInteger
  ) where

import Control.Monad hiding (join)
import Data.List hiding (null)
import Data.Maybe
import Data.Monoid
import Data.Linear
import Data.Lattice
import Data.Typeable
import Util (combineMaybe, isInteger)
import Prelude hiding (null)

-- | Interval
data Interval r
  = Empty
  | Interval (EndPoint r) (EndPoint r)
  deriving (Eq, Typeable)  

-- | Lower bound of the interval
lowerBound :: Num r => Interval r -> EndPoint r
lowerBound Empty = Just (False,0)
lowerBound (Interval lb _) = lb

-- | Upper bound of the interval
upperBound :: Num r => Interval r -> EndPoint r
upperBound Empty = Just (False,0)
upperBound (Interval _ ub) = ub

-- | Endpoint
-- 
-- > (isInclusive, value)
type EndPoint r = Maybe (Bool, r)

instance (Ord r, Num r) => Lattice (Interval r) where
  top    = univ
  bottom = empty
  join   = join'
  meet   = intersection

instance (Num r, Show r) => Show (Interval r) where
  showsPrec p x  = showParen (p > appPrec) $
    showString "interval " .
    showsPrec appPrec1 (lowerBound x) .
    showChar ' ' . 
    showsPrec appPrec1 (upperBound x)

-- | smart constructor for 'Interval'
interval
  :: (Ord r, Num r)
  => EndPoint r -- ^ lower bound
  -> EndPoint r -- ^ upper bound
  -> Interval r
interval lb@(Just (in1,x1)) ub@(Just (in2,x2)) =
  case x1 `compare` x2 of
    GT -> empty
    LT -> Interval lb ub
    EQ -> if in1 && in2 then Interval lb ub else empty
interval lb ub = Interval lb ub

-- | closed set [@l, @u]
closedInterval
  :: (Ord r, Num r)
  => r -- ^ lower bound @l@
  -> r -- ^ upper bound @u@
  -> Interval r
closedInterval lb ub = interval (Just (True, lb)) (Just (True, ub))

-- | open set (@l, @u)
openInterval
  :: (Ord r, Num r)
  => r -- ^ lower bound @l@
  -> r -- ^ upper bound @u@
  -> Interval r
openInterval lb ub = interval (Just (False, lb)) (Just (False, ub))

-- | universal set (-∞, ∞)
univ :: (Num r, Ord r) => Interval r
univ = interval Nothing Nothing

-- | empty (contradicting) interval
empty :: Num r => Interval r
empty = Empty

-- | singleton set \[x,x\]
singleton :: (Num r, Ord r) => r -> Interval r
singleton x = interval (Just (True, x)) (Just (True, x))

-- | intersection (greatest lower bounds) of two intervals
intersection :: forall r. (Ord r, Num r) => Interval r -> Interval r -> Interval r
intersection (Interval l1 u1) (Interval l2 u2) = interval (maxLB l1 l2) (minUB u1 u2)
  where
    maxLB :: EndPoint r -> EndPoint r -> EndPoint r
    maxLB = combineMaybe $ \(in1,x1) (in2,x2) ->
      ( case x1 `compare` x2 of
          EQ -> in1 && in2
          LT -> in2
          GT -> in1
      , max x1 x2
      )
    minUB :: EndPoint r -> EndPoint r -> EndPoint r
    minUB = combineMaybe $ \(in1,x1) (in2,x2) ->
      ( case x1 `compare` x2 of
          EQ -> in1 && in2
          LT -> in1
          GT -> in2
      , min x1 x2
      )
intersection _ _ = empty

-- | join (least upperbound) of two intervals.
join' :: forall r. (Ord r, Num r) => Interval r -> Interval r -> Interval r
join' Empty x2 = x2
join' x1 Empty = x1
join' (Interval l1 u1) (Interval l2 u2) = interval (minLB l1 l2) (maxUB u1 u2)
  where
    maxUB :: EndPoint r -> EndPoint r -> EndPoint r
    maxUB u1 u2 = do
      (in1,x1) <- u1
      (in2,x2) <- u2
      return $
        ( case x1 `compare` x2 of
          EQ -> in1 || in2
          LT -> in2
          GT -> in1
        , max x1 x2
        )
    minLB :: EndPoint r -> EndPoint r -> EndPoint r
    minLB l1 l2 = do
      (in1,x1) <- l1
      (in2,x2) <- l2
      return $
        ( case x1 `compare` x2 of
          EQ -> in1 || in2
          LT -> in1
          GT -> in2
        , min x1 x2
        )

-- | Is the interval empty?
null :: Ord r => Interval r -> Bool
null Empty = True
null _ = False

-- | Is the element in the interval?
member :: Ord r => r -> Interval r -> Bool
member _ Empty = False
member x (Interval lb ub) = testLB x lb && testUB x ub
  where
    testLB x Nothing = True
    testLB x (Just (in1,x1)) = if in1 then x1 <= x else x1 < x
    testUB x Nothing = True
    testUB x (Just (in2,x2)) = if in2 then x <= x2 else x < x2

-- | Is the element not in the interval?
notMember :: Ord r => r -> Interval r -> Bool
notMember a i = not $ member a i

-- | Is this a subset?
-- @(i1 `isSubsetOf` i2)@ tells whether @i1@ is a subset of @i2@.
isSubsetOf :: Ord r => Interval r -> Interval r -> Bool
isSubsetOf Empty _ = True
isSubsetOf _ Empty = False
isSubsetOf (Interval lb1 ub1) (Interval lb2 ub2) = testLB lb1 lb2 && testUB ub1 ub2
  where
    testLB _ Nothing = True
    testLB (Just (in1,x1)) (Just (in2,x2)) =
      case x1 `compare` x2 of
        GT -> True
        LT -> False
        EQ -> not in1 || in2 -- in1 => in2
    testLB Nothing _ = False

    testUB _ Nothing = True
    testUB (Just (in1,x1)) (Just (in2,x2)) =
      case x1 `compare` x2 of
        LT -> True
        GT -> False
        EQ -> not in1 || in2 -- in1 => in2
    testUB Nothing _ = False

-- | Is this a proper subset? (ie. a subset but not equal).
isProperSubsetOf :: Ord r => Interval r -> Interval r -> Bool
isProperSubsetOf i1 i2 = i1 /= i2 && i1 `isSubsetOf` i2

-- | Size of a interval. Size of an unbounded interval is @undefined@.
size :: (Num r, Ord r) => Interval r -> r
size Empty = 0
size (Interval (Just (_,l)) (Just (_,u))) = u - l
size _ = error "Data.Interval.size: unbounded interval"

-- | pick up an element from the interval if the interval is not empty.
pickup :: (Real r, Fractional r) => Interval r -> Maybe r
pickup Empty = Nothing
pickup (Interval Nothing Nothing) = Just 0
pickup (Interval (Just (in1,x1)) Nothing) = Just $ if in1 then x1 else x1+1
pickup (Interval Nothing (Just (in2,x2))) = Just $ if in2 then x2 else x2-1
pickup (Interval (Just (in1,x1)) (Just (in2,x2))) =
  case x1 `compare` x2 of
    GT -> Nothing
    LT -> Just $ (x1+x2) / 2
    EQ -> if in1 && in2 then Just x1 else Nothing

-- | tightening intervals by ceiling lower bounds and flooring upper bounds.
tightenToInteger :: forall r. (RealFrac r) => Interval r -> Interval r
tightenToInteger Empty = Empty
tightenToInteger (Interval lb ub) = interval (fmap tightenLB lb) (fmap tightenUB ub)
  where
    tightenLB (incl,lb) =
      ( True
      , if isInteger lb && not incl
        then lb + 1
        else fromIntegral (ceiling lb :: Integer)
      )
    tightenUB (incl,ub) =
      ( True
      , if isInteger ub && not incl
        then ub - 1
        else fromIntegral (floor ub :: Integer)
      )

-- | For all @x@ in @X@, @y@ in @Y@. @x '<' y@
(<!) :: Real r => Interval r -> Interval r -> Bool
a <! b
  | null a = True
  | null b = True
  | otherwise =
    case upperBound a of
      Nothing -> False
      Just (in1,ub1) ->
        case lowerBound b of
          Nothing -> False
          Just (in2,lb2) ->
            ub1 < lb2 || (ub1==lb2 && not (in1 && in2))

-- | For all @x@ in @X@, @y@ in @Y@. @x '<=' y@
(<=!) :: Real r => Interval r -> Interval r -> Bool
a <=! b
  | null a = True
  | null b = True
  | otherwise =
    case upperBound a of
      Nothing -> False
      Just (in1,ub1) ->
        case lowerBound b of
          Nothing -> False
          Just (in2,lb2) ->
            ub1 <= lb2

-- | For all @x@ in @X@, @y@ in @Y@. @x '==' y@
(==!) :: Real r => Interval r -> Interval r -> Bool
a ==! b = a <=! b && a >=! b

-- | For all @x@ in @X@, @y@ in @Y@. @x '>=' y@
(>=!) :: Real r => Interval r -> Interval r -> Bool
(>=!) = flip (<=!)

-- | For all @x@ in @X@, @y@ in @Y@. @x '>' y@
(>!) :: Real r => Interval r -> Interval r -> Bool
(>!) = flip (<!)

-- | Does there exist an @x@ in @X@, @y@ in @Y@ such that @x '<' y@?
(<?) :: Real r => Interval r -> Interval r -> Bool
a <? b
  | null a = False
  | null b = False
  | otherwise =
    case lowerBound a of
      Nothing -> True
      Just (in1,lb) ->
        case upperBound b of
          Nothing -> True
          Just (in2,ub) -> lb < ub

-- | Does there exist an @x@ in @X@, @y@ in @Y@ such that @x '<=' y@?
(<=?) :: Real r => Interval r -> Interval r -> Bool
a <=? b
  | null a = False
  | null b = False
  | otherwise =
    case lowerBound a of
      Nothing -> True
      Just (in1,lb) ->
        case upperBound b of
          Nothing -> True
          Just (in2,ub) ->
            lb < ub || (lb==ub && in1 && in2)

-- | Does there exist an @x@ in @X@, @y@ in @Y@ such that @x '==' y@?
(==?) :: Real r => Interval r -> Interval r -> Bool
a ==? b = not $ null $ intersection a b

-- | Does there exist an @x@ in @X@, @y@ in @Y@ such that @x '>=' y@?
(>=?) :: Real r => Interval r -> Interval r -> Bool
(>=?) = flip (<=?)

-- | Does there exist an @x@ in @X@, @y@ in @Y@ such that @x '>' y@?
(>?) :: Real r => Interval r -> Interval r -> Bool
(>?) = flip (<?)

-- | Interval airthmetics.
-- Note that this instance does not satisfy algebraic laws of linear spaces.
instance Real r => Module r (Interval r) where
  lzero = singleton 0

  Interval lb1 ub1 .+. Interval lb2 ub2 = interval (f lb1 lb2) (f ub1 ub2)
    where
      f = liftM2 $ \(in1,x1) (in2,x2) -> (in1 && in2, x1 + x2)
  _ .+. _ = Empty

  _ .*. Empty = Empty
  c .*. Interval lb ub =
    case compare c 0 of
      EQ -> singleton 0
      LT -> interval (f ub) (f lb)
      GT -> interval (f lb) (f ub)
    where
      f Nothing = Nothing
      f (Just (incl,val)) = Just (incl, c * val)

instance (Real r, Fractional r) => Linear r (Interval r)

appPrec, appPrec1 :: Int
appPrec = 10
appPrec1 = appPrec + 1


instance forall r. (Real r, Fractional r) => Num (Interval r) where
  a + b = a .+. b
  a - b = a .-. b
  negate a = (-1) .*. a
  fromInteger i = singleton (fromInteger i)

  abs x = ((x `intersection` nonneg) `join` (negate x `intersection` nonneg))
    where
      nonneg = interval (Just (True,0)) Nothing

  signum x = zero `join` pos `join` neg
    where
      zero = if member 0 x then singleton 0 else empty
      pos = if null $ intersection (interval (Just (False,0)) Nothing) x
            then empty
            else singleton 1
      neg = if null $ intersection (interval Nothing (Just (False,0))) x
            then empty
            else singleton (-1)

  Interval lb1 ub1 * Interval lb2 ub2 = interval lb3 ub3
    where
      xs = [ mulInf' x1 x2
           | x1 <- [lbToInf lb1, ubToInf ub1]
           , x2 <- [lbToInf lb2, ubToInf ub2]
           ]
      ub3 = infToUB $ maximumBy cmpUB xs
      lb3 = infToLB $ minimumBy cmpLB xs
  _ * _ = Empty

instance forall r. (Real r, Fractional r) => Fractional (Interval r) where
  fromRational r = singleton (fromRational r)
  recip Empty = Empty
  recip i | 0 `member` i = univ -- should be error?
  recip (Interval lb ub) = interval lb3 ub3
    where
      ub3 = infToUB $ maximumBy cmpUB xs
      lb3 = infToLB $ minimumBy cmpLB xs
      xs = [recipLB (lbToInf lb), recipUB (ubToInf ub)]

data Inf r = NegInf | Finite !r | PosInf
  deriving (Ord, Eq)

cmpUB, cmpLB :: Real r => (Bool, Inf r) -> (Bool, Inf r) -> Ordering
cmpUB (in1,x1) (in2,x2) = compare x1 x2 `mappend` compare in1 in2
cmpLB (in1,x1) (in2,x2) = compare x1 x2 `mappend` flip compare in1 in2

negateInf :: Num r => Inf r -> Inf r
negateInf NegInf = PosInf
negateInf PosInf = NegInf
negateInf (Finite x) = Finite (negate x)

mulInf' :: (Num r, Ord r) => (Bool, Inf r) -> (Bool, Inf r) -> (Bool, Inf r)
mulInf' (True, Finite 0) _ = (True, Finite 0)
mulInf' _ (True, Finite 0) = (True, Finite 0)
mulInf' (in1,x1) (in2,x2) = (in1 && in2, mulInf x1 x2)

mulInf :: (Num r, Ord r) => Inf r -> Inf r -> Inf r
mulInf (Finite x1) (Finite x2) = Finite (x1 * x2)
mulInf inf (Finite x2) =
  case compare x2 0 of
    EQ -> Finite 0
    GT -> inf
    LT -> negateInf inf
mulInf (Finite x1) inf =
  case compare x1 0 of
    EQ -> Finite 0
    GT -> inf
    LT -> negateInf inf
mulInf PosInf PosInf = PosInf
mulInf PosInf NegInf = NegInf
mulInf NegInf PosInf = NegInf
mulInf NegInf NegInf = PosInf

recipLB :: (Fractional r, Ord r) => (Bool, Inf r) -> (Bool, Inf r)
recipLB (_, Finite 0) = (False, PosInf)
recipLB (in1, x1) = (in1, recipInf x1)

recipUB :: (Fractional r, Ord r) => (Bool, Inf r) -> (Bool, Inf r)
recipUB (_, Finite 0) = (False, NegInf)
recipUB (in1, x1) = (in1, recipInf x1)

recipInf :: (Fractional r, Ord r) => Inf r -> Inf r
recipInf PosInf = Finite 0
recipInf NegInf = Finite 0
recipInf (Finite x) = Finite (1/x)

lbToInf :: Num r => EndPoint r -> (Bool, Inf r)
lbToInf Nothing = (False, NegInf)
lbToInf (Just (in1,x1)) = (in1, Finite x1)

ubToInf :: Num r => EndPoint r -> (Bool, Inf r)
ubToInf Nothing = (False, PosInf)
ubToInf (Just (in1,x1)) = (in1, Finite x1)

infToLB :: Num r => (Bool, Inf r) -> EndPoint r
infToLB (in1, Finite x)  = Just (in1, x)
infToLB (False, NegInf)  = Nothing
infToLB (_, PosInf)      = error "infToLB: should not happen"
infToLB (True, NegInf)   = error "infToLB: should not happen"

infToUB :: Num r => (Bool, Inf r) -> EndPoint r
infToUB (in1, Finite x)  = Just (in1, x)
infToUB (False, PosInf)  = Nothing
infToUB (_, NegInf)      = error "infToUB: should not happen"
infToUB (True, PosInf)   = error "infToUB: should not happen"