packages feed

rest-rewrite-0.1: src/Language/REST/OrderingConstraints/ADT.hs

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE CPP #-}

#define OPTIMIZE_WQO

module Language.REST.OrderingConstraints.ADT where

import GHC.Generics (Generic)

import Debug.Trace
import Data.Hashable
import Control.Monad.State.Lazy
import qualified Data.Set as S
import qualified Data.Maybe as Mb
import qualified Data.Map.Strict as M
import qualified Language.REST.WQO as WQO
import qualified Language.REST.OrderingConstraints as OC
import Language.REST.SMT
import Language.REST.Op
import System.IO.Unsafe
import Text.Printf

type WQO = WQO.WQO

data ConstraintsADT a =
    Sat (WQO a)
  | Unsat
  | Union (ConstraintsADT a) (ConstraintsADT a)
  | Intersect (ConstraintsADT a) (ConstraintsADT a)
  deriving (Eq, Ord, Generic, Hashable)

instance {-# OVERLAPPING #-} (ToSMTVar a Int) => ToSMT (ConstraintsADT a) Bool where
  toSMT (Sat w)           = toSMT w
  toSMT Unsat             = smtFalse
  toSMT (Union w1 w2)     = Or  [toSMT w1, toSMT w2]
  toSMT (Intersect w1 w2) = And [toSMT w1, toSMT w2]

{-# SPECIALIZE cost :: ConstraintsADT Op -> Int #-}
cost :: (Ord a, Eq a, Hashable a) => ConstraintsADT a -> Int
cost (Union lhs rhs)     = min (cost lhs) (cost rhs)
cost (Intersect lhs rhs) = cost lhs + cost rhs
cost (Sat wqo)           = S.size $ WQO.elems wqo
cost Unsat               = 100

minDepth :: ConstraintsADT a -> Int
minDepth (Union lhs rhs)     = 1 + min (minDepth lhs) (minDepth rhs)
minDepth (Intersect lhs rhs) = 1 + min (minDepth lhs) (minDepth rhs)
minDepth _                   = 1

maxDepth :: ConstraintsADT a -> Int
maxDepth (Union lhs rhs)     = 1 + max (maxDepth lhs) (maxDepth rhs)
maxDepth (Intersect lhs rhs) = 1 + max (maxDepth lhs) (maxDepth rhs)
maxDepth _                   = 1

intersect :: (Eq a, Ord a, Hashable a) => ConstraintsADT a -> ConstraintsADT a -> ConstraintsADT a

#ifdef OPTIMIZE_WQO
-- Optimization
intersect (Sat t) (Sat u) =
  case WQO.merge t u of
    Just t' -> Sat t'
    Nothing -> Unsat
#endif

intersect (Sat w) v            | w == WQO.empty = v
intersect v            (Sat w) | w == WQO.empty = v
intersect _ Unsat     = Unsat
intersect Unsat _     = Unsat
intersect t1 t2 | t1 == t2 = t1
intersect t1 (Union t2 t3) | t1 == t2 || t1 == t3 = t1
#ifdef OPTIMIZE_WQO
intersect (Sat w1) (Intersect (Sat w2) t2) =
  case WQO.merge w1 w2 of
    Just w' -> intersect (Sat w') t2
    Nothing -> Unsat
intersect (Sat w1) (Intersect t2 (Sat w2)) =
  case WQO.merge w1 w2 of
    Just w' -> intersect (Sat w') t2
    Nothing -> Unsat
intersect (Intersect t1 (Sat w1)) (Sat w2) =
  case WQO.merge w1 w2 of
    Just w' -> intersect t1 (Sat w')
    Nothing -> Unsat
intersect (Intersect (Sat w1) t1) (Sat w2) =
  case WQO.merge w1 w2 of
    Just w' -> intersect t1 (Sat w')
    Nothing -> Unsat
#endif
intersect t1 t2            = Intersect t1 t2

union (Sat w) _            | w == WQO.empty = Sat w
union _            (Sat w) | w == WQO.empty = Sat w
union (Intersect a b)  c | a == c || b == c = c
union a (Intersect b c)  | a == b || a == c = a
union a (Union b c)      | a == b           = union a c
union Unsat s     = s
union s Unsat     = s
union c1 c2 | c1 == c2 = c1
union c1 c2            = Union c1 c2

addConstraint o c = intersect (Sat o) c

relevantConstraints c _ _ = c

notStrongerThan t1 t2 | t1 == t2            = smtTrue
notStrongerThan t1 _  | t1 == noConstraints = smtTrue
notStrongerThan t1 t2 | otherwise           = Implies (toSMT t2) (toSMT t1)

noConstraints = Sat (WQO.empty)
unsatisfiable = Unsat

trace' = trace

{-# SPECIALIZE getConstraints :: ConstraintsADT Op -> [WQO Op] #-}
getConstraints :: forall a. (Show a, Ord a, Hashable a) => ConstraintsADT a -> [WQO a]
getConstraints adt = -- trace' ("Get constraints, size : " ++ (show $ dnfSize adt)) $
  evalState (getConstraints' adt) (GCState M.empty M.empty)

data GCState a = GCState {
    cs :: M.Map (ConstraintsADT a) (GCResult a)
  , ms :: M.Map (WQO a, WQO a) (Maybe (WQO a))
}

type GCResult a = [WQO a]

type GCMonad a = State (GCState a) (GCResult a)

cached :: (Ord a) => ConstraintsADT a -> GCMonad a -> GCMonad a
cached key thunk = do
  cache <- gets cs
  case M.lookup key cache of
    Just result -> trace' ("ADT Cache hit") $ return result
    Nothing     -> trace' ("ADT Cache miss") $ do
      result <- trace' "Do thunk" thunk
      trace' "Done" $ modify (\st -> st{cs = M.insert key result (cs st)})
      return result
 where
   trace' _  x = x
   -- trace' = trace

cached' :: (Hashable a, Show a, Ord a) => (WQO a, WQO a) -> Maybe (WQO a) -> State (GCState a) (Maybe (WQO a))
cached' (lhs, rhs) thunk = do
  cache <- gets ms
  case M.lookup (lhs, rhs) cache of
    Just result -> trace' ("WQO Cache hit") $ return result
    Nothing     -> trace' ("WQO Cache miss" ++ show (lhs, rhs)) $ do
      trace' "Done" $ modify (\st -> st{ms = M.insert (rhs, lhs) thunk $ M.insert (lhs, rhs) thunk (ms st)})
      return thunk
 where
   trace' _  x = x
   -- trace' = trace

getConstraints' :: forall a. (Show a, Ord a, Hashable a) => ConstraintsADT a -> State (GCState a) [WQO a]
getConstraints' (Sat w)         = return [w]
getConstraints' Unsat           = return []
getConstraints' c@(Union lhs rhs) =
  cached c $ do
    c1' <- cached c1 $ getConstraints' c1
    c2' <- cached c2 $ getConstraints' c2
    return $ c1' ++ c2'
  where
      (c1, c2) =
        if cost lhs < cost rhs
        then (lhs, rhs)
        else (rhs, lhs)
getConstraints' c@(Intersect lhs rhs) = cached c $ do
  c1' <- cached c1 $ getConstraints' c1
  if null c1'
    then return []
    else (cached c2 $ getConstraints' c2) >>= go c1'
  where
      go :: [WQO a] -> [WQO a] -> State (GCState a) [WQO a]
      go c1' c2' = flatten <$>
        (sequence $ do
          wqo1 <- c1'
          wqo2 <- c2'
          return (cached' (wqo1, wqo2) $ WQO.merge wqo1 wqo2))
      flatten = concatMap Mb.maybeToList
      (c1, c2) =
        if cost lhs > cost rhs
        then (lhs, rhs)
        else (rhs, lhs)

dnfSize :: ConstraintsADT a -> Int
dnfSize (Sat w)       = 1
dnfSize Unsat         = 0
dnfSize (Union w1 w2) = dnfSize w1 + dnfSize w2
dnfSize (Intersect w1 w2) = dnfSize w1 * dnfSize w2

-- toDNF (Union lhs rhs) = S.union (toDNF lhs) (toDNF rhs)
-- toDNF (Intersect lhs rhs) =
--   let
--     ldnf = toDNF lhs
--     rdnf = toDNF rhs
--   in
--     S.unions

simplify adt = undefined
-- simplify adt = case getConstraints adt of
--   []     -> Unsat
--   (x:xs) -> foldl go (Sat x) xs
--   where
--     go a x = Union (Sat x) a

permits adt wqo = any (`WQO.notStrongerThan` wqo) (getConstraints adt)

isSatisfiable :: (ToSMTVar a Int, Show a, Eq a, Ord a, Hashable a) => ConstraintsADT a -> SMTExpr Bool
isSatisfiable s = toSMT s
  -- trace (show (minDepth s) ++ " " ++ show (maxDepth s)) $ not $ null $ getConstraints s

instance (Eq a, Hashable a,  Show a) => Show (ConstraintsADT a) where
  -- show s = go 0 s where
  --   go n (Sat w)         = indent n $ show w
  --   go n Unsat           = indent n $ "⊥"
  --   go n (Union w t )    = indent n $ printf "∪\n%s\n%s" (go (n+1) w) (go (n+1) t)
  --   go n (Intersect w t) = indent n $ printf "∩\n%s\n%s" (go (n+1) w) (go (n+1) t)

  --   indent 0 s = s
  --   indent n s = take (n - 1) (repeat '|') ++ '+':s

  show (Sat w)         = show w
  show Unsat           = "⊥"
  show (Union w t )    = printf "(%s ∨\n %s)" (show w) (show t)
  show (Intersect w t) = printf "(%s ∧ %s)" (show w) (show t)

adtOC z3 = OC.liftC (checkSat' z3) adtOC'

adtOC' = OC.OC
  addConstraint
  intersect
  isSatisfiable
  notStrongerThan
  noConstraints
  permits
  relevantConstraints
  union
  unsatisfiable
  undefined
  undefined
  simplify