-- | High level API for writing propagators. {-# LANGUAGE KindSignatures #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternSynonyms #-} module Clingo.Propagation ( Propagation, PropagationPhase (..), Assignment, Literal, Signed (..), negateLiteral, -- | A propagator is defined by four functions. The first is executed during -- the initialization phase. The remaining three are called during solving, -- depending on how the propagator is initialized. -- -- -- Initialization: This function is called once before each solving step. -- It is used to map relevant program literals to solver literals, add -- watches for solver literals, and initialize the data structures used -- during propagation. This is the last point to access symbolic and theory -- atoms. Once the search has started, they are no longer accessible. -- -- Propagation: Can be used to add conflicts in order to propagate solver -- literals. -- -- Undo: Is called on backjumping and can be used to synchronize internal -- data structures of the propagator. -- -- Check: Can be used to check whether an assignment is valid. Propagator (..), emptyPropagator, propagatorToIO, addWatch, -- * Initialization countThreads, solverLiteral, propSymbolicAtoms, propTheoryAtoms, -- * Actions During Solving P.Clause (..), P.ClauseType (..), addClause, propagate, hasWatch, removeWatch, getThreadId, newLiteral, assignment, -- * Assignment decisionLevel, hasConflict, hasLiteral, levelOf, decision, isFixed, truthValue, -- * Truth Values TruthValue, pattern TruthFree, pattern TruthFalse, pattern TruthTrue, negateTruth ) where import Control.Applicative import Control.Monad.IO.Class import Control.Monad.Catch import Control.Monad.Reader import Control.Monad.Trans.Maybe import Numeric.Natural import Clingo.Internal.Types import Clingo.Internal.Symbol import qualified Clingo.Internal.Propagation as P import Clingo.Internal.Propagation (Assignment, Clause) -- | Propagators can be in one of two phases, initialization and solving. data PropagationPhase = Init | Solving type family PhaseHandle (k :: PropagationPhase) s where PhaseHandle 'Init s = PropagateInit s PhaseHandle 'Solving s = PropagateCtrl s -- | A concrete monad for propagators, which observes the invariants -- stipulated by clingo. newtype Propagation (phase :: PropagationPhase) s a = Propagation { runPropagator :: MaybeT (ReaderT (PhaseHandle phase s) IO) a } deriving ( Functor, Monad, Applicative , MonadIO, MonadThrow ) getIOAction :: Propagation phase s () -> PhaseHandle phase s -> IO () getIOAction = (void .) . runReaderT . runMaybeT . runPropagator instance MonadReader (PropagateInit s) (Propagation 'Init s) where ask = Propagation ask local f (Propagation x) = Propagation (local f x) reader = Propagation . reader instance MonadReader (PropagateCtrl s) (Propagation 'Solving s) where ask = Propagation ask local f (Propagation x) = Propagation (local f x) reader = Propagation . reader instance MonadSymbol (Propagation phase) where createSignature = createSignature' createNumber = createNumber' createSupremum = createSupremum' createInfimum = createInfimum' createString = createString' createFunction = createFunction' handleStop :: P.PropagationStop -> Propagation phase s () handleStop P.Continue = return () handleStop P.Stop = Propagation empty -- Propagator and wrapping -- ----------------------- -- | A propagator is defined by four functions. No function is mandatory. data Propagator s = Propagator { propInit :: Maybe (Propagation 'Init s ()) , propPropagate :: Maybe ([Literal s] -> Propagation 'Solving s ()) , propUndo :: Maybe ([Literal s] -> Propagation 'Solving s ()) , propCheck :: Maybe (Propagation 'Solving s ()) } -- | The empty propagator for convenience. emptyPropagator :: Propagator s emptyPropagator = Propagator Nothing Nothing Nothing Nothing propagatorToIO :: Propagator s -> IOPropagator s propagatorToIO prop = IOPropagator { propagatorInit = getIOAction <$> propInit prop , propagatorPropagate = runLitSolv <$> propPropagate prop , propagatorUndo = runLitSolv <$> propUndo prop , propagatorCheck = getIOAction <$> propCheck prop } where runLitSolv = flip . (getIOAction .) -- Operations that are always available -- ------------------------------------ class CanAddWatch (phase :: PropagationPhase) where mAddWatch :: Literal s -> Propagation phase s () instance CanAddWatch 'Init where mAddWatch l = ask >>= flip P.initAddWatch l instance CanAddWatch 'Solving where mAddWatch l = ask >>= flip P.addWatch l -- | Watches can be added in any phase of the propagation. The propagate and -- undo functions will only be called on changes to watched literals! addWatch :: CanAddWatch phase => Literal s -> Propagation phase s () addWatch = mAddWatch -- Actions during initialization -- ----------------------------- -- | Obtain the number of solver threads. countThreads :: Propagation 'Init s Integer countThreads = ask >>= P.countThreads -- | Convert an 'AspifLiteral' to a solver 'Literal'. solverLiteral :: AspifLiteral s -> Propagation 'Init s (Literal s) solverLiteral l = ask >>= flip P.solverLiteral l -- | Obtain a handle to the symbolic atoms, see 'Clingo.Inspection.Symbolic' propSymbolicAtoms :: Propagation 'Init s (SymbolicAtoms s) propSymbolicAtoms = ask >>= P.symbolicAtoms -- | Obtain a handle to the theory atoms, see 'Clingo.Inspection.Theory' propTheoryAtoms :: Propagation 'Init s (TheoryAtoms s) propTheoryAtoms = ask >>= P.theoryAtoms -- Actions during Solving -- ---------------------- -- | Check whether a 'Literal' is watched. hasWatch :: Literal s -> Propagation 'Solving s Bool hasWatch l = ask >>= flip P.hasWatch l -- | Stop watching a literal. removeWatch :: Literal s -> Propagation 'Solving s () removeWatch l = ask >>= flip P.removeWatch l -- | Get the thread id of the calling solver thread. getThreadId :: Propagation 'Solving s Integer getThreadId = ask >>= P.getThreadId -- | Introduce a new literal to the solver. newLiteral :: Propagation 'Solving s (Literal s) newLiteral = ask >>= P.addLiteral -- | Add a clause. This call might result in termination of the propagation -- function, when backjumping is necessary. addClause :: Clause s -> Propagation 'Solving s () addClause c = ask >>= flip P.addClause c >>= handleStop -- | Propagate implied literals from added clauses. Might result in backjumping -- and hence termination of the propagation. propagate :: Propagation 'Solving s () propagate = ask >>= P.propagate >>= handleStop -- | Obtain the current (partial) assignment. assignment :: Propagation 'Solving s (Assignment s) assignment = ask >>= P.assignment -- | Get the current decision level. decisionLevel :: Assignment s -> Propagation 'Solving s Natural decisionLevel = P.decisionLevel -- | Determine whether assignment has a conflict. hasConflict :: Assignment s -> Propagation 'Solving s Bool hasConflict = P.hasConflict -- | Determine whether a literal is part of an assignment. hasLiteral :: Assignment s -> Literal s -> Propagation 'Solving s Bool hasLiteral = P.hasLiteral -- | Find the decision level of a given literal in an assignment. levelOf :: Assignment s -> Literal s -> Propagation 'Solving s Natural levelOf = P.levelOf -- | Determine the decision literal given a decision level. decision :: Assignment s -> Natural -> Propagation 'Solving s (Literal s) decision = P.decision -- | Check if a literal has a fixed truth value. isFixed :: Assignment s -> Literal s -> Propagation 'Solving s Bool isFixed = P.isFixed -- | Obtain the truth value of a literal truthValue :: Assignment s -> Literal s -> Propagation 'Solving s TruthValue truthValue = P.truthValue