packages feed

toysolver-0.7.0: src/ToySolver/SAT/Encoder/Cardinality/Internal/Totalizer.hs

{-# OPTIONS_GHC -Wall #-}
{-# OPTIONS_HADDOCK show-extensions #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ScopedTypeVariables #-}
-----------------------------------------------------------------------------
-- |
-- Module      :  ToySolver.SAT.Encoder.Cardinality.Internal.Totalizer
-- Copyright   :  (c) Masahiro Sakai 2020
-- License     :  BSD-style
--
-- Maintainer  :  masahiro.sakai@gmail.com
-- Stability   :  provisional
-- Portability :  non-portable
--
-----------------------------------------------------------------------------
module ToySolver.SAT.Encoder.Cardinality.Internal.Totalizer
  ( Encoder (..)
  , newEncoder

  , Definitions
  , getDefinitions
  , evalDefinitions

  , addAtLeast
  , encodeAtLeast

  , addCardinality
  , encodeCardinality

  , encodeSum
  ) where

import Control.Monad.Primitive
import Control.Monad.State.Strict
import qualified Data.IntSet as IntSet
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Primitive.MutVar
import Data.Vector.Unboxed (Vector)
import qualified Data.Vector.Unboxed as V
import qualified ToySolver.SAT.Types as SAT
import qualified ToySolver.SAT.Encoder.Tseitin as Tseitin


data Encoder m = Encoder (Tseitin.Encoder m) (MutVar (PrimState m) (Map SAT.LitSet (Vector SAT.Var)))

instance Monad m => SAT.NewVar m (Encoder m) where
  newVar   (Encoder a _) = SAT.newVar a
  newVars  (Encoder a _) = SAT.newVars a
  newVars_ (Encoder a _) = SAT.newVars_ a

instance Monad m => SAT.AddClause m (Encoder m) where
  addClause (Encoder a _) = SAT.addClause a

newEncoder :: PrimMonad m => Tseitin.Encoder m -> m (Encoder m)
newEncoder tseitin = do
  tableRef <- newMutVar Map.empty
  return $ Encoder tseitin tableRef


type Definitions = [(Vector SAT.Var, SAT.LitSet)]

getDefinitions :: PrimMonad m => Encoder m -> m Definitions
getDefinitions (Encoder _ tableRef) = do
  m <- readMutVar tableRef
  return [(vars', lits) | (lits, vars') <- Map.toList m]


evalDefinitions :: SAT.IModel m => m -> Definitions -> [(SAT.Var, Bool)]
evalDefinitions m defs = do
  (vars', lits) <- defs
  let n = length [() | l <- IntSet.toList lits, SAT.evalLit m l]
  (i, v) <- zip [1..] (V.toList vars')
  return (v, i <= n)


addAtLeast :: PrimMonad m => Encoder m -> SAT.AtLeast -> m ()
addAtLeast enc (lhs, rhs) = do
  addCardinality enc lhs (rhs, length lhs)


addCardinality :: PrimMonad m => Encoder m -> [SAT.Lit] -> (Int, Int) -> m ()
addCardinality enc lits (lb, ub) = do
  let n = length lits
  if lb <= 0 && n <= ub then
    return ()
  else if n < lb || ub < 0 then
    SAT.addClause enc []
  else do
    lits' <- encodeSum enc lits
    forM_ (take lb lits') $ \l -> SAT.addClause enc [l]
    forM_ (drop ub lits') $ \l -> SAT.addClause enc [- l]


-- TODO: consider polarity
encodeAtLeast :: PrimMonad m => Encoder m -> SAT.AtLeast -> m SAT.Lit
encodeAtLeast enc (lhs,rhs) = do
  encodeCardinality enc lhs (rhs, length lhs)


-- TODO: consider polarity
encodeCardinality :: PrimMonad m => Encoder m -> [SAT.Lit] -> (Int, Int) -> m SAT.Lit
encodeCardinality enc@(Encoder tseitin _) lits (lb, ub) = do
  let n = length lits
  if lb <= 0 && n <= ub then
    Tseitin.encodeConj tseitin []
  else if n < lb || ub < 0 then
    Tseitin.encodeDisj tseitin []
  else do
    lits' <- encodeSum enc lits
    forM_ (zip lits' (tail lits')) $ \(l1, l2) -> do
      SAT.addClause enc [-l2, l1] -- l2→l1 or equivalently ¬l1→¬l2
    Tseitin.encodeConj tseitin $
      [lits' !! (lb - 1) | lb > 0] ++ [- (lits' !! (ub + 1 - 1)) | ub < n]


encodeSum :: PrimMonad m => Encoder m -> [SAT.Lit] -> m [SAT.Lit]
encodeSum enc = liftM V.toList . encodeSumV enc . V.fromList


encodeSumV :: PrimMonad m => Encoder m -> Vector SAT.Lit -> m (Vector SAT.Lit)
encodeSumV (Encoder enc tableRef) = f
  where
    f lits
      | n <= 1 = return lits
      | otherwise = do
          m <- readMutVar tableRef
          let key = IntSet.fromList (V.toList lits)
          case Map.lookup key m of
            Just vars -> return vars
            Nothing -> do
              rs <- liftM V.fromList $ SAT.newVars enc n
              writeMutVar tableRef (Map.insert key rs m)
              case V.splitAt n1 lits of
                (lits1, lits2) -> do
                  lits1' <- f lits1
                  lits2' <- f lits2
                  forM_ [0 .. n] $ \sigma ->
                    -- a + b = sigma, 0 <= a <= n1, 0 <= b <= n2
                    forM_ [max 0 (sigma - n2) .. min n1 sigma] $ \a -> do
                      let b = sigma - a
                      -- card(lits1) >= a ∧ card(lits2) >= b → card(lits) >= sigma
                      -- ¬(card(lits1) >= a) ∨ ¬(card(lits2) >= b) ∨ card(lits) >= sigma
                      unless (sigma == 0) $ do
                        SAT.addClause enc $
                          [- (lits1' V.! (a - 1)) | a > 0] ++
                          [- (lits2' V.! (b - 1)) | b > 0] ++
                          [rs V.! (sigma - 1)]
                      -- card(lits) > sigma → (card(lits1) > a ∨ card(lits2) > b)
                      -- card(lits) >= sigma+1 → (card(lits1) >= a+1 ∨ card(lits2) >= b+1)
                      -- card(lits1) >= a+1 ∨ card(lits2) >= b+1 ∨ ¬(card(lits) >= sigma+1)
                      unless (sigma + 1 == n + 1) $ do
                        SAT.addClause enc $
                          [lits1' V.! (a + 1 - 1) | a + 1 < n1 + 1] ++
                          [lits2' V.! (b + 1 - 1) | b + 1 < n2 + 1] ++
                          [- (rs V.! (sigma + 1 - 1))]
                  return rs
     where
       n = V.length lits
       n1 = n `div` 2
       n2 = n - n1