packages feed

lattest-lib-0.1.0.0: src/Lattest/SMT.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE TypeApplications #-}
module Lattest.SMT (
  SMT,
  SolvableProblem(..),

  addAssertions,
  addDeclarations,
  getSolution,
  getSolvable,
  pop,
  push,
  runSMT
) where

import Data.SBV( constrain, HasKind(isBoolean), SBV, SymVal (..), freshVar)
import Data.SBV.Control( CheckSatResult, checkSat, getModel, query, Query)
import Data.SBV.Internals( CV(cvVal), CVal(..), SMTModel(modelAssocs),cvToBool )
import qualified Data.SBV as SBV
import qualified Data.SBV.Control as SBV
import qualified Data.SBV.List as SBV
import qualified Data.SBV.Internals as SBVI -- 'unsafe' internals

import Lattest.Model.Symbolic.Expr(ExprView(..), Variable (..), Valuation, Expr, fromConstantsMap, Type (..), Constant (..), toConstantsMap, view)
import Lattest.Model.Symbolic.Internal.FreeMonoidX
import Lattest.Model.Symbolic.Internal.Sum(SumTerm(..))

import Control.Monad((<=<))
import Control.Monad.State (StateT, evalStateT, lift, modify, gets)
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as Set
import Lattest.Model.Symbolic.Internal.Product (ProductTerm(..))

--------------------
-- exported types and functions
-- these define the interface to
-- the SMT backend
--------------------
type  Solution v       =  Map.Map v Constant
data  SolvableProblem  = Sat
                       | Unsat
                       | Unknown
     deriving (Eq,Ord,Read,Show)
data  SolveProblem v  = Solved (Solution v)
                      | Unsolvable
                      | UnableToSolve
     deriving (Eq,Ord,Read,Show)

type SMT = StateT (Map String SBVI.SVal) Query

runSMT :: SMT a -> IO a
runSMT = SBV.runSMT . query . flip evalStateT Map.empty

getSolution :: [Variable] -> SMT Valuation
getSolution vs =
  fromConstantsMap
  . (`Map.intersection` Map.fromList (map (,()) vs))
  . toConstantsMap
  . sbvModelToValuation <$> lift getModel

addAssertions :: [Expr Bool] -> SMT ()
addAssertions = mapM_ (lift . constrain <=< exprToSymbolic . view)

-- This is the reason we have the StateT wrapper in SMT:
-- SBV wants us to keep track of the symbolic variables
-- we get on each declaration, and use them to reference
-- the variable.
addDeclarations :: [Variable] -> SMT ()
addDeclarations = mapM_ $ \(Variable nm tp) -> case tp of
  IntType -> do
    SBVI.SBV v <- freshVar @Integer nm
    modify $ Map.insert nm v
  BoolType -> do
    SBVI.SBV v <- freshVar @Bool nm
    modify $ Map.insert nm v
  StringType -> do
    SBVI.SBV v <- freshVar @String nm
    modify $ Map.insert nm v
  FloatType -> do
    SBVI.SBV v <- freshVar @Double nm
    modify $ Map.insert nm v

getSolvable :: SMT SolvableProblem
getSolvable = checkSatToSolveProblem <$> lift checkSat

pop, push :: SMT ()
pop  = lift $ SBV.pop  1
push = lift $ SBV.push 1

---------------
-- Non-exported functions
---------------

-- The main translation between our Exprs and SBV's Symbolic
exprToSymbolic :: (Show a, SBV.SymVal a) => ExprView a -> SMT (SBV a)
exprToSymbolic v = case v of
  Var (Variable nm _tp) -> gets (SBVI.SBV . (Map.! nm))
  Const t -> pure $ literal t
  Ite i t e -> SBV.ite <$> go i <*> go t <*> go e
  EqualInt    l r -> (SBV..==) <$> go l <*> go r
  EqualFloat  l r -> (SBV..==) <$> go l <*> go r
  EqualString l r -> (SBV..==) <$> go l <*> go r
  EqualBool   l r -> (SBV..==) <$> go l <*> go r
  Divide      x y -> SBV.sDiv  <$> go x <*> go y
  DivideFloat x y -> (/)       <$> go x <*> go y
  Modulo x y      -> SBV.sMod  <$> go x <*> go y
  Sum      s -> foldOccur (\(SumTerm x) i symY -> (\sX sY -> sX * literal i               + sY) <$> go x <*> symY) (pure $ literal 0) s
  SumFloat s -> foldOccur (\(SumTerm x) i symY -> (\sX sY -> sX * literal (fromInteger i) + sY) <$> go x <*> symY) (pure $ literal 0) s
  Product      p -> foldOccur (\(ProductTerm x) i symY -> (\x' y -> x' ^ i * y) <$> go x <*> symY) (pure $ literal 1) p
  ProductFloat p -> foldOccur (\(ProductTerm x) i symY -> (\x' y -> x' ^ i * y) <$> go x <*> symY) (pure $ literal 1) p
  Length s -> SBV.length <$> go s
  GezInt   i -> (SBV..>= literal 0) <$> go i
  GezFloat f -> (SBV..>= literal 0) <$> go f
  Not b -> SBV.sNot <$> go b
  And xs -> foldr (\b bs -> (SBV..&&) <$> go b <*> bs) (pure $ literal True) (Set.toList xs)
   -- The below version errors because SBV doesn't properly declare some variable
   -- My best guess is that it's a bug if you use 'and' inside a Query, but I haven't
   -- looked deep enough nor done enough testing to report as a bug.
   -- SBV.and <$> foldr (\b bs -> (SBV..:) <$> go b <*> bs) (pure SBV.nil) (Set.toList xs)
  Concat strs -> SBV.concat <$> foldr (\s ss -> (SBV..:) <$> go s <*> ss) (pure SBV.nil) strs
  where
    go :: (SBV.SymVal a, Show a) => ExprView a -> SMT (SBV a)
    go = exprToSymbolic

checkSatToSolveProblem :: CheckSatResult -> SolvableProblem
checkSatToSolveProblem = \case
  SBV.Sat -> Sat
  SBV.Unsat -> Unsat
  SBV.Unk -> Unknown
  SBV.DSat _ -> Unknown

sbvModelToValuation :: SMTModel -> Valuation
sbvModelToValuation = fromConstantsMap . foldr f Map.empty . modelAssocs
  where
    f (varname, cv) = (\(typ, c) -> Map.insert (Variable varname typ) c) $ case cvVal cv of
      -- booleans for some reason are represented as CInteger with a different 'Kind'
      _ | isBoolean cv -> (BoolType, Cbool (cvToBool cv))
      CInteger i -> (IntType, Cint i)
      CString s -> (StringType, Cstring s)
      CDouble d -> (FloatType, Cfloat d)
      _ -> error "todo: the other SBV types, including lists, sets, arbitrary ADTs, floating point values, etc"