Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Publish v2.5.1 #86

Merged
merged 4 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [PVP versioning](https://pvp.haskell.org/).

## v2.5.1 _(2024-08-26)_

### Added
- Added `SharingMode = None | StableNames` in `Language.Hasmtlib.Internal.Sharing`. Defaults to `StableNames`.
- Added function `setSharingMode` which allows you to change the `SharingMode`.

### Changed
- `runSharing` in `Language.Hasmtlib.Internal.Sharing` now differentiates sharing behavior based on newly given argument of type `SharingMode`

## v2.5.0 _(2024-08-25)_

### Added
Expand Down
2 changes: 1 addition & 1 deletion hasmtlib.cabal
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cabal-version: 3.0

name: hasmtlib
version: 2.5.0
version: 2.5.1
synopsis: A monad for interfacing with external SMT solvers
description: Hasmtlib is a library for generating SMTLib2-problems using a monad.
It takes care of encoding your problem, marshaling the data to an external solver and parsing and interpreting the result into Haskell types.
Expand Down
2 changes: 2 additions & 0 deletions src/Language/Hasmtlib.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Language.Hasmtlib
, module Language.Hasmtlib.Solver.Yices
, module Language.Hasmtlib.Solver.OpenSMT
, module Language.Hasmtlib.Solver.MathSAT
, SharingMode(..), setSharingMode
)
where

Expand All @@ -47,3 +48,4 @@ import Language.Hasmtlib.Solver.Z3
import Language.Hasmtlib.Solver.Yices
import Language.Hasmtlib.Solver.OpenSMT
import Language.Hasmtlib.Solver.MathSAT
import Language.Hasmtlib.Internal.Sharing
27 changes: 27 additions & 0 deletions src/Language/Hasmtlib/Example/Sharing.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Language.Hasmtlib.Example.Arith where

import Prelude hiding ((&&))
import Language.Hasmtlib
import Data.Default

main :: IO ()
main = do
res <- solveWith @SMT (debug cvc5 def) $ do
setLogic "QF_LIA"

x <- var @IntSort

let a1 = x + x
a2 = a1 + a1

assert $ a2 === a2

setSharingMode None
assert $ a2 === a2

setSharingMode StableNames
assert $ a2 === a2

return x

print res
19 changes: 17 additions & 2 deletions src/Language/Hasmtlib/Internal/Sharing.hs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

module Language.Hasmtlib.Internal.Sharing
( Sharing(..)
, SharingMode(..)
, runSharing, share
)
where
Expand All @@ -12,6 +13,7 @@ import Language.Hasmtlib.Type.SMTSort
import Language.Hasmtlib.Type.Expr
import Data.GADT.Compare
import Data.HashMap.Lazy
import Data.Default
import Control.Monad.State
import Control.Lens
import System.Mem.StableName
Expand All @@ -20,6 +22,15 @@ import Unsafe.Coerce

import Data.Kind

-- | Mode used for sharing.
data SharingMode =
None -- ^ Common expressions are not shared at all
| StableNames -- ^ Expressions that resolve to the same 'StableName' are shared
deriving Show

instance Default SharingMode where
def = StableNames

-- | States that can share expressions by comparing their 'StableName's.
class Sharing s where
-- | A constraint on the monad used when asserting the shared node in 'assertSharedNode'.
Expand All @@ -32,12 +43,16 @@ class Sharing s where
-- Also gives access to the 'StableName' of the original expression.
assertSharedNode :: (MonadState s m, SharingMonad s m) => StableName () -> Expr BoolSort -> m ()

-- | Sets the mode used for sharing common expressions. Defaults to 'StableNames'.
setSharingMode :: MonadState s m => SharingMode -> m ()

-- | Shares all possible sub-expressions in given expression.
-- Replaces each node in the expression-tree with an auxiliary variable.
-- All nodes @x@ @y@ where @makeStableName x == makeStableName y@ are replaced with the same auxiliary variable.
-- Therefore this creates a DAG.
runSharing :: (KnownSMTSort t, MonadSMT s m, Sharing s, SharingMonad s m) => Expr t -> m (Expr t)
runSharing = lazyParaM1 (
runSharing :: (KnownSMTSort t, MonadSMT s m, Sharing s, SharingMonad s m) => SharingMode -> Expr t -> m (Expr t)
runSharing None = return
runSharing StableNames = lazyParaM1 (
\origExpr expr ->
if isLeaf origExpr
then return origExpr
Expand Down
6 changes: 3 additions & 3 deletions src/Language/Hasmtlib/Type/Expr.hs
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,7 @@ instance Real (Expr IntSort) where
toRational x = error $ "Real#toRational[Expr IntSort] only supported for constants. But given: " <> show x
{-# INLINE toRational #-}

-- | This instance is __partial__ for 'toEnum', it's only intended for use with constants ('Constant').
-- | This instance is __partial__ for 'fromEnum', it's only intended for use with constants ('Constant').
instance Enum (Expr IntSort) where
fromEnum (Constant (IntValue x)) = fromIntegral x
fromEnum x = error $ "Enum#fromEnum[Expr IntSort] only supported for constants. But given: " <> show x
Expand Down Expand Up @@ -747,7 +747,7 @@ instance Real (Expr RealSort) where
toRational x = error $ "Real#toRational[Expr RealSort] only supported for constants. But given: " <> show x
{-# INLINE toRational #-}

-- | This instance is __partial__ for 'toEnum', it's only intended for use with constants ('Constant').
-- | This instance is __partial__ for 'fromEnum', it's only intended for use with constants ('Constant').
instance Enum (Expr RealSort) where
fromEnum (Constant (RealValue x)) = fromEnum x
fromEnum x = error $ "Enum#fromEnum[Expr RealSort] only supported for constants. But given: " <> show x
Expand All @@ -761,7 +761,7 @@ instance KnownNat n => Real (Expr (BvSort n)) where
toRational x = error $ "Real#toRational[Expr BvSort] only supported for constants. But given: " <> show x
{-# INLINE toRational #-}

-- | This instance is __partial__ for 'toEnum', it's only intended for use with constants ('Constant').
-- | This instance is __partial__ for 'fromEnum', it's only intended for use with constants ('Constant').
instance KnownNat n => Enum (Expr (BvSort n)) where
fromEnum (Constant (BvValue x)) = fromIntegral x
fromEnum x = error $ "Enum#fromEnum[Expr BvSort] only supported for constants. But given: " <> show x
Expand Down
12 changes: 8 additions & 4 deletions src/Language/Hasmtlib/Type/OMT.hs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ instance Sharing OMT where
type SharingMonad OMT = Monad
stableMap = smt.Language.Hasmtlib.Type.SMT.stableMap
assertSharedNode _ expr = modifying (smt.formulas) (|> expr)
setSharingMode sm = smt.sharingMode .= sm

instance MonadState OMT m => MonadSMT OMT m where
smtvar' _ = fmap coerce $ (smt.lastVarId) <+= 1
Expand All @@ -62,7 +63,7 @@ instance MonadState OMT m => MonadSMT OMT m where

assert expr = do
omt <- get
sExpr <- runSharing expr
sExpr <- runSharing (omt^.smt.sharingMode) expr
qExpr <- case omt^.smt.mlogic of
Nothing -> return sExpr
Just logic -> if "QF" `isPrefixOf` logic then return sExpr else quantify sExpr
Expand All @@ -78,13 +79,16 @@ instance MonadState OMT m => MonadSMT OMT m where

instance MonadSMT OMT m => MonadOMT OMT m where
minimize expr = do
sExpr <- runSharing expr
sm <- use (smt.sharingMode)
sExpr <- runSharing sm expr
modifying targetMinimize (|> SomeSMTSort (Minimize sExpr))
maximize expr = do
sExpr <- runSharing expr
sm <- use (smt.sharingMode)
sExpr <- runSharing sm expr
modifying targetMaximize (|> SomeSMTSort (Maximize sExpr))
assertSoft expr w gid = do
sExpr <- runSharing expr
sm <- use (smt.sharingMode)
sExpr <- runSharing sm expr
modifying softFormulas (|> SoftFormula sExpr w gid)

instance Render SoftFormula where
Expand Down
100 changes: 51 additions & 49 deletions src/Language/Hasmtlib/Type/Pipe.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ import System.Mem.StableName
data Pipe = Pipe
{ _lastPipeVarId :: {-# UNPACK #-} !Int -- ^ Last Id assigned to a new var
, _mPipeLogic :: Maybe String -- ^ Logic for the SMT-Solver
, _pipeSharingMode :: !SharingMode -- ^ How to share common expressions
, _pipeStableMap :: !(HashMap (StableName ()) (SomeKnownSMTSort Expr)) -- ^ Mapping between a 'StableName' and it's 'Expr' we may share
, _incrSharedAuxs :: !(Seq (Seq (StableName ()))) -- ^ Index of each 'Seq' ('StableName' ()) is incremental stack height where 'StableName' representing auxiliary var that has been shared
, _pipe :: !B.Solver -- ^ Active pipe to the backend
, _pipeSolver :: !B.Solver -- ^ Active pipe to the backend
, _isDebugging :: !Bool -- ^ Flag if pipe shall debug
}
$(makeLenses ''Pipe)
Expand All @@ -48,83 +49,84 @@ instance Sharing Pipe where
type SharingMonad Pipe = MonadIO
stableMap = pipeStableMap
assertSharedNode sn expr = do
smt <- get
pipe <- get
modifying (incrSharedAuxs._last) (|> sn)
let cmd = renderAssert expr
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
setSharingMode sm = pipeSharingMode .= sm

instance (MonadState Pipe m, MonadIO m) => MonadSMT Pipe m where
smtvar' _ = fmap coerce $ lastPipeVarId <+= 1
{-# INLINE smtvar' #-}

var' p = do
smt <- get
pipe <- get
newVar <- smtvar' p
let cmd = renderDeclareVar newVar
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
return $ Var newVar
{-# INLINEABLE var' #-}

assert expr = do
smt <- get
sExpr <- runSharing expr
qExpr <- case smt^.mPipeLogic of
pipe <- get
sExpr <- runSharing (pipe^.pipeSharingMode) expr
qExpr <- case pipe^.mPipeLogic of
Nothing -> return sExpr
Just logic -> if "QF" `isPrefixOf` logic then return sExpr else quantify sExpr
let cmd = renderAssert qExpr
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
{-# INLINEABLE assert #-}

setOption opt = do
smt <- get
pipe <- get
let cmd = render opt
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd

setLogic l = do
mPipeLogic ?= l
smt <- get
pipe <- get
let cmd = renderSetLogic (stringUtf8 l)
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd

instance (MonadState Pipe m, MonadIO m) => MonadIncrSMT Pipe m where
push = do
smt <- get
pipe <- get
let cmd = "(push 1)"
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
incrSharedAuxs <>= mempty

pop = do
smt <- get
pipe <- get
let cmd = "(pop 1)"
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
forMOf_ (incrSharedAuxs._last.folded) smt (\sn -> pipeStableMap.at sn .= Nothing)
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
forMOf_ (incrSharedAuxs._last.folded) pipe (\sn -> pipeStableMap.at sn .= Nothing)
modifying incrSharedAuxs $ \case (auxs:>_) -> auxs ; auxs -> auxs

checkSat = do
smt <- get
pipe <- get
let cmd = "(check-sat)"
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
result <- liftIO $ B.command (smt^.pipe) cmd
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn result
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
result <- liftIO $ B.command (pipe^.pipeSolver) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn result
case parseOnly resultParser (toStrict result) of
Left e -> liftIO $ do
print result
error e
Right res -> return res

getModel = do
smt <- get
pipe <- get
let cmd = "(get-model)"
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
model <- liftIO $ B.command (smt^.pipe) cmd
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn model
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
model <- liftIO $ B.command (pipe^.pipeSolver) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn model
case parseOnly anyModelParser (toStrict model) of
Left e -> liftIO $ do
print model
Expand All @@ -133,11 +135,11 @@ instance (MonadState Pipe m, MonadIO m) => MonadIncrSMT Pipe m where

getValue :: forall t. KnownSMTSort t => Expr t -> m (Maybe (Decoded (Expr t)))
getValue v@(Var x) = do
smt <- get
pipe <- get
let cmd = renderUnary "get-value" $ "(" <> render x <> ")"
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
model <- liftIO $ B.command (smt^.pipe) cmd
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn model
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
model <- liftIO $ B.command (pipe^.pipeSolver) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn model
case parseOnly (getValueParser @t x) (toStrict model) of
Left e -> liftIO $ do
print model
Expand All @@ -155,25 +157,25 @@ instance (MonadState Pipe m, MonadIO m) => MonadIncrSMT Pipe m where

instance (MonadSMT Pipe m, MonadIO m) => MonadOMT Pipe m where
minimize expr = do
smt <- get
sExpr <- runSharing expr
pipe <- get
sExpr <- runSharing (pipe^.pipeSharingMode) expr
let cmd = render $ Minimize sExpr
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
{-# INLINEABLE minimize #-}

maximize expr = do
smt <- get
sExpr <- runSharing expr
pipe <- get
sExpr <- runSharing (pipe^.pipeSharingMode) expr
let cmd = render $ Maximize sExpr
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
{-# INLINEABLE maximize #-}

assertSoft expr w gid = do
smt <- get
sExpr <- runSharing expr
pipe <- get
sExpr <- runSharing (pipe^.pipeSharingMode) expr
let cmd = render $ SoftFormula sExpr w gid
when (smt^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (smt^.pipe) cmd
when (pipe^.isDebugging) $ liftIO $ ByteString.Char8.putStrLn $ toLazyByteString cmd
liftIO $ B.command_ (pipe^.pipeSolver) cmd
{-# INLINEABLE assertSoft #-}
6 changes: 4 additions & 2 deletions src/Language/Hasmtlib/Type/SMT.hs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,19 @@ data SMT = SMT
, _formulas :: !(Seq (Expr BoolSort)) -- ^ All asserted formulas
, _mlogic :: Maybe String -- ^ Logic for the SMT-Solver
, _options :: [SMTOption] -- ^ All manually configured SMT-Solver-Options
, _sharingMode :: !SharingMode -- ^ How to share common expressions
, _stableMap :: !(HashMap (StableName ()) (SomeKnownSMTSort Expr)) -- ^ Mapping between a 'StableName' and it's 'Expr' we may share
}
$(makeLenses ''SMT)

instance Default SMT where
def = SMT 0 mempty mempty mempty [ProduceModels True] mempty
def = SMT 0 mempty mempty mempty [ProduceModels True] def mempty

instance Sharing SMT where
type SharingMonad SMT = Monad
stableMap = Language.Hasmtlib.Type.SMT.stableMap
assertSharedNode _ expr = modifying formulas (|> expr)
setSharingMode sm = sharingMode .= sm

instance MonadState SMT m => MonadSMT SMT m where
smtvar' _ = fmap coerce $ lastVarId <+= 1
Expand All @@ -51,7 +53,7 @@ instance MonadState SMT m => MonadSMT SMT m where

assert expr = do
smt <- get
sExpr <- runSharing expr
sExpr <- runSharing (smt^.sharingMode) expr
qExpr <- case smt^.mlogic of
Nothing -> return sExpr
Just logic -> if "QF" `isPrefixOf` logic then return sExpr else quantify sExpr
Expand Down
Loading