module Numeric.Domain
(
Domain
, intervals
, empty
, singleton
, interval
, greaterThan
, greaterOrEqual
, lessThan
, lessOrEqual
, maxDomain
, (<..<)
, (<=..<)
, (<..<=)
, (<=..<=)
, difference
, intersect
, union
, greaterThanDomain
, greaterOrEqualDomain
, lessThanDomain
, lessOrEqualDomain
, notEqual
, null
, isSingleton
, isInfinite
, isSubsetOf
, sameElements
, elems
, member
, maxValue
, minValue
, div
, inverseAbs
, inverseSignum
, pretty
, putPrettyLn
) where
import Data.List (find, intercalate, sortBy)
import Data.Maybe (maybeToList)
import Data.Ord (comparing)
import Prelude hiding (div, isInfinite, null)
import qualified Numeric.Bound as B
import qualified Numeric.Interval as I
import Numeric.Distance (Dist)
newtype Domain a = Domain
{ intervals :: [I.Interval a]
}
deriving (Eq, Show)
instance (Dist a, Fractional a) => Fractional (Domain a) where
fromRational = singleton . fromRational
recip = Numeric.Domain.recip
instance (Dist a, Num a) => Num (Domain a) where
(+) = join I.plus
(-) = join I.minus
(*) = join I.times
negate = Domain . fmap I.negate . intervals
abs = Numeric.Domain.abs
signum = Numeric.Domain.signum
fromInteger = singleton . fromInteger
empty :: Domain a
empty = Domain []
singleton :: a -> Domain a
singleton value =
Domain [I.singleton value]
interval :: Ord a => B.LowerBound a -> B.UpperBound a -> Domain a
interval low high =
case I.interval low high of
Nothing -> empty
Just di -> Domain [di]
lessThan :: Dist a => a -> Domain a
lessThan value =
Domain [I.upperBounded (B.openUpper value)]
lessOrEqual :: a -> Domain a
lessOrEqual value =
Domain [I.upperBounded (B.closedUpper value)]
greaterThan :: Dist a => a -> Domain a
greaterThan value =
Domain [I.lowerBounded (B.openLower value)]
greaterOrEqual :: a -> Domain a
greaterOrEqual value =
Domain [I.lowerBounded (B.closedLower value)]
maxDomain :: Domain a
maxDomain = Domain [I.maxInterval]
(<..<) :: Dist a => a -> a -> Domain a
(<..<) low high = interval (B.openLower low) (B.openUpper high)
(<=..<) :: Dist a => a -> a -> Domain a
(<=..<) low high = interval (B.closedLower low) (B.openUpper high)
(<..<=) :: Dist a => a -> a -> Domain a
(<..<=) low high = interval (B.openLower low) (B.closedUpper high)
(<=..<=) :: Ord a => a -> a -> Domain a
(<=..<=) low high = interval (B.closedLower low) (B.closedUpper high)
isInfinite :: Domain a -> Bool
isInfinite (Domain is) = any I.isInfinite is
isSingleton :: Eq a => Domain a -> Bool
isSingleton (Domain [i]) = I.isSingleton i
isSingleton _ = False
isSubsetOf :: Ord a => Domain a -> Domain a -> Bool
isSubsetOf (Domain sub) (Domain whole) = go False 0 sub whole
where go smaller len [] domain = smaller || len < length domain
go smaller len (i:is) domain =
case find (`I.contains` i) domain of
Nothing -> False
Just wi -> go (smaller || wi /= i) (len + 1) is domain
member :: Ord a => a -> Domain a -> Bool
member value (Domain is) =
any (I.member value) is
null :: Domain a -> Bool
null (Domain []) = True
null _ = False
sameElements :: Ord a => Domain a -> Domain a -> Bool
sameElements (Domain xs) (Domain ys) =
sortBy (comparing I.lowerBound) xs ==
sortBy (comparing I.lowerBound) ys
elems :: Enum a => Domain a -> Maybe [a]
elems (Domain is) = go is
where go [] = Just []
go (i:rs) = do
xs <- I.elems i
ts <- go rs
pure (xs ++ ts)
difference :: Dist a => Domain a -> Domain a -> Domain a
difference (Domain whole) (Domain diff) = Domain (foldl go whole diff)
where go [] _ = []
go (r:rs) i =
I.difference r i ++ go rs i
intersect :: Ord a => Domain a -> Domain a -> Domain a
intersect dx dy = Domain $ do
x <- intervals dx
y <- intervals dy
maybeToList (I.intersect x y)
union :: Dist a => Domain a -> Domain a -> Domain a
union (Domain ls) (Domain rs) = Domain (listUnion ls rs)
where
listUnion domain [] = domain
listUnion domain (i:is) = listUnion (go domain i [] []) is
go [] r remains merges =
case merges of
[] -> r : remains
ms -> listUnion remains ms
go (i:is) r remains merges =
case I.merge i r of
Just mi -> go is r remains (mi : merges)
Nothing -> go is r (i : remains) merges
lessThanDomain :: Dist a => Domain a -> Domain a
lessThanDomain domain =
case maxValue domain of
Just mv -> lessThan mv
Nothing | null domain -> domain
| otherwise -> maxDomain
lessOrEqualDomain :: Ord a => Domain a -> Domain a
lessOrEqualDomain domain =
case maxValue domain of
Just mv -> lessOrEqual mv
Nothing | null domain -> domain
| otherwise -> maxDomain
greaterThanDomain :: Dist a => Domain a -> Domain a
greaterThanDomain domain =
case minValue domain of
Just mv -> greaterThan mv
Nothing | null domain -> domain
| otherwise -> maxDomain
greaterOrEqualDomain :: Ord a => Domain a -> Domain a
greaterOrEqualDomain domain =
case minValue domain of
Just mv -> greaterOrEqual mv
Nothing | null domain -> domain
| otherwise -> maxDomain
notEqual :: Dist a => Domain a -> Domain a
notEqual domain
| isSingleton domain = maxDomain `difference` domain
| otherwise = maxDomain
maxValue :: Ord a => Domain a -> Maybe a
maxValue = aggregate I.maxValue max
minValue :: Ord a => Domain a -> Maybe a
minValue = aggregate I.minValue min
aggregate :: (I.Interval a -> Maybe a) -> (a -> a -> a) -> Domain a -> Maybe a
aggregate mapping combine (Domain is) =
case is of
[] -> Nothing
r:rs ->
case mapping r of
Nothing -> Nothing
Just m -> go m rs
where
go current [] = Just current
go current (x:xs) =
case mapping x of
Nothing -> Nothing
Just n -> go (combine n current) xs
join
:: Dist a
=> (I.Interval a -> I.Interval a -> I.Interval a)
-> Domain a
-> Domain a
-> Domain a
join f (Domain xs) (Domain ys) =
union empty (Domain is)
where
is = do x <- xs
y <- ys
pure (f x y)
abs :: (Dist a, Num a) => Domain a -> Domain a
abs (Domain is) =
union empty (Domain ns)
where
ns = fmap I.abs is
signum :: (Dist a, Num a) => Domain a -> Domain a
signum (Domain is) =
foldl union empty (fmap intervalSignum is)
where
intervalSignum r =
let neg | I.hasNegatives r = singleton (-1)
| otherwise = empty
zero | I.member 0 r = singleton 0
| otherwise = empty
pos | I.hasPositives r = singleton 1
| otherwise = empty
in
union neg (union zero pos)
recip :: (Dist a, Fractional a) => Domain a -> Domain a
recip (Domain is) =
foldl union empty (fmap recipInterval is)
where
recipInterval r =
if not hasZero
then interval recipHigh recipLow
else
case (I.hasNegatives r, I.hasPositives r) of
(True, True) ->
let Domain li = interval B.infiniteLower recipLow
Domain hi = interval recipHigh B.infiniteUpper
in Domain (li ++ hi)
(True , False) -> interval B.infiniteLower recipLow
(False, True ) -> interval recipHigh B.infiniteUpper
(False, False) -> empty
where
low = I.lowerBound r
high = I.upperBound r
recipLow = B.recipLower low
recipHigh = B.recipUpper high
hasZero =
I.member 0 r ||
B.isZeroLower low ||
B.isZeroUpper high
inverseAbs :: (Dist a, Num a) => Domain a -> Domain a
inverseAbs domain = union positives negatives
where positives = intersect domain (greaterOrEqual 0)
negatives = negate positives
inverseSignum :: (Dist a, Num a) => Domain a -> Domain a
inverseSignum (Domain is) =
let neg | any (I.member (-1)) is = lessThan 0
| otherwise = empty
zero | any (I.member 0) is = singleton 0
| otherwise = empty
pos | any (I.member 1) is = greaterThan 0
| otherwise = empty
in
union neg (union zero pos)
div :: (Dist a, Integral a) => Domain a -> Domain a -> Domain a
div = join I.div
pretty :: (Ord a, Show a) => Domain a -> String
pretty (Domain is) =
case sortBy (comparing I.lowerBound) is of
[] -> "\8709"
xs -> intercalate " \8746 " (fmap I.pretty xs)
putPrettyLn :: (Ord a, Show a) => Domain a -> IO ()
putPrettyLn = putStrLn . pretty