packages feed

ersatz-0.6: examples/qbf/Synthesis.hs

-- | An example program that solves a circuit synthesis problem with the Boolean Chain approach,
-- as cited in "Circuit Minimization with QBF-Based Exact Synthesis" by Reichl et al. 2023
-- <https://doi.org/10.1609/aaai.v37i4.25524/>.

module Main where

import Prelude hiding (not, (&&), and, or, product)

import Ersatz
import Ersatz.Relation
import Ersatz.Counting

import qualified Data.Array as A
import Data.Array (Array, Ix)

import Control.Monad.State.Lazy (StateT)
import Control.Monad (guard)

import Text.Printf (printf)
import Data.List (sortOn)

import qualified Data.ByteString.Lazy as B
import qualified Data.ByteString.Lazy.Char8 as C
import Data.Functor.Identity


-- | AIG where @atmost 2 [1,2,3,4]@ is equivalent to @10@
--
-- Another problem that can be quickly solved is, for example:
-- n = 3, l = 5, m = 1, f xs = [exactly 2 xs]
-- (AIG where @exactly 2 [1,2,3]@ is equivalent to @8@)
main :: IO ()
main = do
  let n = 4 -- number of inputs
      l = 6 -- number of steps (gates)
      m = 1 -- number of outputs
      f :: [Bit] -> [Bit]
      f xs = [atmost 2 xs]
  formulaSize $ problem f n l m
  solve $ problem f n l m


solve :: StateT QSAT IO (Relation Int Int, Relation Int Int) -> IO ()
solve p = do
  result <- solveWith depqbf p
  case result of
    (Satisfied, Just (s,o)) -> printf "output: %s\nAIG:\n%s%s\n" (show $ map fst $ sortOn snd $ edgesA o) (table s) (show $ edgesA s)
    _                       -> putStrLn "unsat"

-- | @problem f n l m@ generates a QBF problem that encodes an AIG graph with @n@ inputs,
-- @l@ gates and @m@ outputs which is equivalent to the boolean function @f@.
problem :: Monad a => ([Bit] -> [Bit]) -> Int -> Int -> Int -> StateT QSAT a (Relation Int Int, Relation Int Int)
problem f n l m = do
  s <- relation ((n+1,1),(n+l,n+l)) -- selection variables: (x,y) is in s iff gate x takes gate/input y as an input
  assert $ and $ do -- ensure that s is acyclic (DAG)
    (i,j) <- indices s
    guard (i <= j)
    return $ s!(i,j) === false
  o <- relation ((1,1),(n+l,m)) -- output variables: (x,y) is in o iff output y is gate/input x
  assert $ regular_in_degree 1 o
  v <- universally_quantified_relation ((1,1),(1,n)) -- input variables: (1,y) is in v iff input y is true
  g <- relation ((1,1),(1,n+l)) -- gate value variables: (1,y) is in g iff gate/input y is true
  assert $ and $ do
    i <- [1..n]
    return $ g!(1,i) === v!(1,i)
  assert $ and $ do -- g!(1,y) = nand xs where xs is the list of inputs of y
    i <- [n+1..n+l]
    let val = not $ and $ do
          p <- [1..i-1]
          return $ s!(i,p) ==> g!(1,p)
    return $ g!(1,i) === val
  assert $ f (elems v) === elems (product g o)
  return (s,o)


universally_quantified_relation :: (Ix a, Ix b, MonadQSAT s m)
  => ((a,b),(a,b)) -> m (Relation a b)
universally_quantified_relation bnd = do
  pairs <- sequence $ do
    p <- A.range bnd
    return $ do
      x <- forall_
      return (p,x)
  return $ build bnd pairs


edgesA :: (Ix a, Ix b) => Array (a,b) Bool -> [(a,b)]
edgesA a = [ i | (i, True) <- A.assocs a]

formulaSize :: StateT QSAT Identity a -> IO ()
formulaSize p = mapM_ C.putStrLn $ take 2 $ B.split 10 $ qdimacsQSAT p