packages feed

satplus-0.1.0.0: SAT/Binary.hs

{-|
Module      : SAT.Binary
Description : Functions for working with natural numbers represented as
              binary numbers.
              
              WARNING: completely untested so far.
-}
module SAT.Binary(
  -- * The Binary type
    Binary
  , newBinary
  , zero
  , number
  , digit
  , maxValue

  -- * Counting
  , count
  , add
  , addList
  , addBits
  , mul1
  , mul

  -- * Operations
  , invert

  -- * Conversion
  , fromList
  , toList

  -- * Models
  , modelValue
  )
 where

------------------------------------------------------------------------------

import SAT hiding ( modelValue )
import qualified SAT
import SAT.Bool
import SAT.Equal
import SAT.Order
import Data.List( insert, sort )

------------------------------------------------------------------------------

-- | The type Binary, for natural numbers represented in binary
newtype Binary = Binary [Lit] -- least significant bit first
 deriving ( Eq, Ord )

instance Show Binary where
  show (Binary xs) = show xs

-- | Creates a binary number from a list of digits (least significant bit first).
fromList :: [Lit] -> Binary
fromList xs = Binary xs

-- | Returns the list of digits (least significant bit first).
toList :: Binary -> [Lit]
toList (Binary xs) = xs

-- | Creates a fresh binary number, with the specified number of bits.
newBinary :: Solver -> Int -> IO Binary
newBinary s k =
  do xs <- sequence [ newLit s | i <- [1..k] ]
     return (Binary xs)

-- | Creates 0 as a binary number.
zero :: Binary
zero = Binary []

-- | Creates n>=0 as a binary number.
number :: Int -> Binary
number n = Binary (bin n)
 where
  bin 0 = []
  bin n = (if odd n then true else false) : bin (n `div` 2)

-- | Creates a 1-digit binary number, specified by the given literal.
digit :: Lit -> Binary
digit x = fromList [x]

-- | Inverts a binary number; computes /maxValue n - n/. Can be used to maximize
-- instead of minimize.
invert :: Binary -> Binary
invert (Binary xs) = Binary (map neg xs)

-- | Returns a binary number that represents the number of true literals in
-- the given list.
count :: Solver -> [Lit] -> IO Binary
count s xs = addList s (map digit xs)

-- | Adds up two binary numbers.
add :: Solver -> Binary -> Binary -> IO Binary
add s a b = addList s [a,b]

-- | Adds up a list of binary numbers. When adding more than 2 numbers, this
-- function is preferred over linearly folding the function 'add' over a list,
-- because a balanced tree (based on the sizes of the numbers involved) is
-- constructed by this function, which creates a lot less clauses than doing
-- it the naive way.
addList :: Solver -> [Binary] -> IO Binary
addList s bs = addBits s [ (k,x) | Binary xs <- bs, (k,x) <- [0..] `zip` xs ]

-- | Adds up a list of digits, annotated with their weight, which is the
-- placement of the binary digit. This function is used in the functions @addList@
-- and @mul@, but may be useful to users in its own right.
addBits :: Solver -> [(Int,Lit)] -> IO Binary
addBits s ixs = Binary `fmap` go 0 (sort ixs)
 where
  go _ [] =
    do return []

  go i xs@((i0,x):_) | i < i0 =
    do ys <- go (i+1) xs
       return (false : ys)

  go _ ((i0,x):(i1,y):(i2,z):xs) | i0 == i1 && i0 == i2 =
    do (v,c) <- full x y z
       go i0 ((i0,v):insert (i0+1,c) xs)

  go _ ((i0,x):(i1,y):xs) | i0 == i1 =
    do (v,c) <- full x y false
       ys <- go (i0+1) ((i0+1,c):xs)
       return (v:ys)

  go _ ((i0,x):xs) =
    do ys <- go (i0+1) xs
       return (x:ys)

  full x y z =
    do v <- xorl s [x,y,z]
       c <- atLeast2 x y z
       return (v,c)
  
  -- desparately tries to avoid creating extra literals
  atLeast2 x y z
    | x == true = orl s [y,z] 
    | y == true = orl s [x,z] 
    | z == true = orl s [x,y] 

    | x == false = andl s [y,z] 
    | y == false = andl s [x,z] 
    | z == false = andl s [x,y] 

    | x == y = return x 
    | y == z = return y 
    | x == z = return z
    
    | x == neg y = return z 
    | y == neg z = return x 
    | x == neg z = return y
    
    | otherwise =
      do v <- newLit s
         addClause s [neg x, neg y, v]
         addClause s [neg x, neg z, v]
         addClause s [neg y, neg z, v]
         addClause s [x, y, neg v]
         addClause s [x, z, neg v]
         addClause s [y, z, neg v]
         return v

-- | Returns the maximum value a given binary number can have.
maxValue :: Num a => Binary -> a
maxValue (Binary xs) = (2^length xs) - 1

-- | Multiplies a digit and a binary number.
mul1 :: Solver -> Lit -> Binary -> IO Binary
mul1 s x (Binary ys) =
  do ys' <- sequence [ andl s [x,y] | y <- ys ]
     return (Binary ys')

-- | Multiplies two binary numbers.
mul :: Solver -> Binary -> Binary -> IO Binary
mul s (Binary xs) (Binary ys) =
  do izs <- sequence
            [ do z <- andl s [x,y]
                 return (i+j,z)
            | (i,x) <- [0..] `zip` xs
            , (j,y) <- [0..] `zip` ys
            ]
     addBits s izs

-- | Return the numeric value of a binary number in the current model.
-- (/Use only when 'solve' has returned True!/)
modelValue :: Num a => Solver -> Binary -> IO a
modelValue s (Binary xs) = go xs
 where
  go []     = do return 0
  go (x:xs) = do b <- SAT.modelValue s x
                 n <- go xs
                 return (2*n + if b then 1 else 0)

------------------------------------------------------------------------------

instance Equal Binary where
  equalOr s pre (Binary xs) (Binary ys) =
    equalOr s pre (pad xs ys) (pad ys xs)

  notEqualOr s pre (Binary xs) (Binary ys) =
    notEqualOr s pre (pad xs ys) (pad ys xs)

instance Order Binary where
  lessOr s pre b (Binary xsLSBF) (Binary ysLSBF) =
    do lessOr s pre b xs ys
    where xs = reverse (pad xsLSBF ysLSBF)
          ys = reverse (pad ysLSBF xsLSBF)


pad xs ys = xs ++ replicate (length ys - length xs) false

------------------------------------------------------------------------------