Skip to content

Commit

Permalink
107-smart-ite: partly #111
Browse files Browse the repository at this point in the history
pure product-encoding
exactly 1 derived from pure product-encoding
  • Loading branch information
bruderj15 committed Sep 14, 2024
1 parent 78d8355 commit 651fe6d
Showing 1 changed file with 77 additions and 16 deletions.
93 changes: 77 additions & 16 deletions src/Language/Hasmtlib/Counting.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Therefore additional information for the temporal summation may need to be provi
E.g. if your logic is \"QF_LIA\" you would want @'count'' \@'IntSort' $ ...@
It is worth noting that some cardinality constraints use optimized encodings, such as @'atLeast' 1 ≡ 'or'@.
It is worth noting that some cardinality constraints use optimized encodings for special cases.
-}
module Language.Hasmtlib.Counting
(
Expand All @@ -27,48 +27,109 @@ module Language.Hasmtlib.Counting

-- * At-Most
, atMost
, amoSqrt
, amoQuad
)
where

import Prelude hiding (not, (&&), (||), or)
import Prelude hiding (not, (&&), (||), and, or, all, any)
import Language.Hasmtlib.Type.SMTSort
import Language.Hasmtlib.Type.Expr
import Language.Hasmtlib.Boolean
import Data.Foldable (toList)
import Data.List (transpose)
import Data.Proxy
import Control.Lens

-- | Wrapper for 'count' which takes a 'Proxy'.
count' :: forall t f. (Functor f, Foldable f, Num (Expr t)) => Proxy t -> f (Expr BoolSort) -> Expr t
count' _ = sum . fmap (\b -> ite b 1 0)
{-# INLINEABLE count' #-}
{-# INLINE count' #-}

-- | Out of many bool-expressions build a formula which encodes how many of them are 'true'.
count :: forall t f. (Functor f, Foldable f, Num (Expr t)) => f (Expr BoolSort) -> Expr t
count = count' (Proxy @t)
{-# INLINE count #-}

-- | Out of many bool-expressions build a formula which encodes that __at most__ @k@ of them are 'true'.
--
-- 'atMost' is defined as follows:
--
-- @
-- 'atMost' 0 = 'nand'
-- 'atMost' 1 = 'amoSqrt'
-- 'atMost' k = ('<=?' k) . 'count'
-- @
atMost :: forall t f. (Functor f, Foldable f, KnownSMTSort t, Num (HaskellType t), Ord (HaskellType t)) => Expr t -> f (Expr BoolSort) -> Expr BoolSort
atMost (Constant 0) = nand
atMost (Constant 1) = atMostOneLinear
atMost 0 = nand
atMost 1 = amoSqrt
atMost k = (<=? k) . count
{-# INLINE atMost #-}

atMostOneLinear :: (Foldable f, Boolean b) => f b -> b
atMostOneLinear xs =
let (_, sz) = foldr (plus . (, false)) (false, false) xs
in not sz
where
plus (xe, xz) (ye, yz) = (xe || ye, xz || yz || (xe && ye))

-- | Out of many bool-expressions build a formula which encodes that __at least__ @k@ of them are 'true'.
--
-- 'atLeast' is defined as follows:
--
-- @
-- 'atLeast' 0 = 'const' 'true'
-- 'atLeast' 1 = 'or'
-- 'atLeast' k = ('>=?' k) . 'count'
-- @
atLeast :: forall t f. (Functor f, Foldable f, KnownSMTSort t, Num (HaskellType t), Ord (HaskellType t)) => Expr t -> f (Expr BoolSort) -> Expr BoolSort
atLeast (Constant 0) = const true
atLeast (Constant 1) = or
atLeast 0 = const true
atLeast 1 = or
atLeast k = (>=? k) . count
{-# INLINE atLeast #-}

-- | Out of many bool-expressions build a formula which encodes that __exactly__ @k@ of them are 'true'.
--
-- 'exactly' is defined as follows:
--
-- @
-- 'exactly' 0 xs = 'nand' xs
-- 'exactly' 1 xs = 'atLeast' \@t 1 xs '&&' 'atMost' \@t 1 xs
-- 'exactly' k xs = 'count' xs '===' k
-- @
exactly :: forall t f. (Functor f, Foldable f, KnownSMTSort t, Num (HaskellType t), Ord (HaskellType t)) => Expr t -> f (Expr BoolSort) -> Expr BoolSort
exactly (Constant 0) = nand
exactly k = (=== k) . count
exactly 0 xs = nand xs
exactly 1 xs = atLeast @t 1 xs && atMost @t 1 xs
exactly k xs = count xs === k
{-# INLINE exactly #-}

-- | The squareroot-encoding, also called product-encoding, is a special encoding for @atMost 1@.
--
-- The original product-encoding provided by /Jingchao Chen/ in /A New SAT Encoding of the At-Most-One Constraint (2010)/
-- used auxiliary variables and would therefore be monadic.
-- It requires \( 2 \sqrt{n} + \mathcal{O}(\sqrt[4]{n}) \) auxiliary variables and
-- \( 2n + 4\sqrt{n} + \mathcal{O}(\sqrt[4]{n}) \) clauses.
--
-- To make this encoding pure, all auxiliary variables are replaced with a disjunction of size \( \mathcal{O}(\sqrt{n}) \).
-- Therefore zero auxiliary variables are required and technically clause-count remains the same, although the clauses get bigger.
amoSqrt :: (Foldable f, Boolean b) => f b -> b
amoSqrt xs
| length xs < 10 = amoQuad $ toList xs
| otherwise =
let n = toInteger $ length xs
p = ceiling $ sqrt $ fromInteger n
rows = splitEvery (fromInteger p) $ toList xs
columns = transpose rows
vs = or <$> rows
us = or <$> columns
in amoSqrt vs && amoSqrt us &&
and (imap (\j r -> and $ imap (\i x -> (x ==> us !! i) && (x ==> vs !! j)) r) rows)
where
splitEvery n = takeWhile (not . null) . map (take n) . iterate (drop n)

-- | The quadratic-encoding, also called pairwise-encoding, is a special encoding for @atMost 1@.
--
-- It's the naive encoding for the at-most-one-constraint and produces \( \binom{n}{2} \) clauses and no auxiliary variables..
amoQuad :: Boolean b => [b] -> b
amoQuad as = and $ do
ys <- subs 2 as
return $ any not ys
where
subs :: Int -> [a] -> [[a]]
subs 0 _ = [[]]
subs _ [] = []
subs k (x : xs) = map (x :) (subs (k -1) xs) <> subs k xs
{-# INLINE amoQuad #-}

0 comments on commit 651fe6d

Please sign in to comment.