From b5d1f4e2f375e697ad99897ee452e2539208950e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Mon, 24 Aug 2026 22:40:00 +0200 Subject: [PATCH] feat(cdcl): add cdcl search with 1-uip analysis and restarts --- src/Sift/Cdcl/Search.hs | 393 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 393 insertions(+) create mode 100644 src/Sift/Cdcl/Search.hs diff --git a/src/Sift/Cdcl/Search.hs b/src/Sift/Cdcl/Search.hs new file mode 100644 index 0000000..aa302a3 --- /dev/null +++ b/src/Sift/Cdcl/Search.hs @@ -0,0 +1,393 @@ +{-# LANGUAGE FlexibleContexts #-} +module Sift.Cdcl.Search + ( solveInternal + , luby + , propagate + , analyze + , analyzeFinal + , pickBranchVar + , maybeReduce + , installLearnt + , extractModel + , collectStats + ) where + +import Control.Monad (when, forM_, foldM, filterM) +import Control.Monad.ST (ST) +import Data.STRef +import Data.Array.ST +import qualified Data.IntSet as IntSet +import Sift.Cdcl.State +import Sift.Cdcl.Heap +import Sift.Stats + +luby :: Int -> Int +luby x0 = descend size0 seq0 x0 + where + (size0, seq0) = ascend 1 0 + ascend :: Int -> Int -> (Int, Int) + ascend size sq + | size < x0 + 1 = ascend (2 * size + 1) (sq + 1) + | otherwise = (size, sq) + descend :: Int -> Int -> Int -> Int + descend size sq x + | size - 1 == x = 2 ^ sq + | otherwise = + let size' = (size - 1) `div` 2 + in descend size' (sq - 1) (x `mod` size') + +propagate :: Solver s -> ST s Cid +propagate s = loop + where + loop = do + qh <- readSTRef (qhead s) + n <- readSTRef (trailN s) + if qh >= n + then pure (-1) + else do + p <- readArray (trail s) qh + writeSTRef (qhead s) (qh + 1) + modifySTRef' (statProp s) (+ 1) + confl <- propagateLit s p + if confl >= 0 then pure confl else loop + +propagateLit :: Solver s -> Int -> ST s Cid +propagateLit s p = do + ws <- readArray (watches s) p + writeArray (watches s) p [] + go ws [] + where + falseLit = litNeg p + go [] kept = do + writeArray (watches s) p kept + pure (-1) + go (w@(Watcher cid blocker) : rest) kept = do + bv <- litValueInt s blocker + if bv == 1 + then go rest (w : kept) + else do + cl <- getClause s cid + dead <- readSTRef (clsDead cl) + if dead + then go rest kept + else do + let lits = clsLits cl + sz = clsSize cl + l0 <- readArray lits 0 + when (l0 == falseLit) $ do + l1 <- readArray lits 1 + writeArray lits 0 l1 + writeArray lits 1 l0 + first <- readArray lits 0 + fv <- litValueInt s first + if fv == 1 + then go rest (Watcher cid first : kept) + else do + mNew <- findNewWatch s lits sz 2 + case mNew of + Just k -> do + lk <- readArray lits k + writeArray lits 1 lk + writeArray lits k falseLit + modifyWatch s (litNeg lk) (Watcher cid first) + go rest kept + Nothing -> + if fv == -1 + then do + writeArray (watches s) p (reverse kept ++ (w : rest)) + pure cid + else do + _ <- enqueue s first cid + go rest (Watcher cid first : kept) + +findNewWatch :: Solver s -> STUArray s Int Int -> Int -> Int -> ST s (Maybe Int) +findNewWatch s lits sz i + | i >= sz = pure Nothing + | otherwise = do + l <- readArray lits i + lv <- litValueInt s l + if lv /= -1 then pure (Just i) else findNewWatch s lits sz (i + 1) + +analyze :: Solver s -> Cid -> ST s ([Int], Int, Int) +analyze s conflict0 = do + dl <- currentLevel s + othersRef <- newSTRef [] + counterRef <- newSTRef (0 :: Int) + pathRef <- newSTRef conflict0 + pivotRef <- newSTRef (-1 :: Int) + idxRef <- newSTRef =<< (subtract 1 <$> readSTRef (trailN s)) + let step = do + cid <- readSTRef pathRef + cl <- getClause s cid + when (clsLearnt cl) $ claBumpActivity s cl + let lits = clsLits cl + sz = clsSize cl + pivot <- readSTRef pivotRef + forM_ [0 .. sz - 1] $ \j -> do + q <- readArray lits j + when (q /= pivot) $ do + let v = litVar q + sv <- readArray (seen s) v + lev <- readArray (varLevel s) v + when (not sv && lev > 0) $ do + varBumpActivity s v + writeArray (seen s) v True + if lev >= dl + then modifySTRef' counterRef (+ 1) + else modifySTRef' othersRef (q :) + let selectNext = do + i <- readSTRef idxRef + l <- readArray (trail s) i + sv <- readArray (seen s) (litVar l) + if sv then pure (i, l) else do writeSTRef idxRef (i - 1); selectNext + (i, l) <- selectNext + writeSTRef idxRef (i - 1) + let v = litVar l + writeArray (seen s) v False + writeSTRef pivotRef l + writeSTRef pathRef =<< readArray (varReason s) v + modifySTRef' counterRef (subtract 1) + c <- readSTRef counterRef + when (c > 0) step + step + pivot <- readSTRef pivotRef + let asserting = litNeg pivot + othersRaw <- readSTRef othersRef + minimized <- minimize s othersRaw + let learnt = orderLearnt asserting minimized + btLevel <- computeBtLevel s minimized + lbd <- computeLbd s learnt + forM_ (litVar asserting : map litVar othersRaw) $ \v -> writeArray (seen s) v False + pure (learnt, btLevel, lbd) + +installLearnt :: Solver s -> [Int] -> Int -> ST s () +installLearnt s learnt lbd = do + case learnt of + [u] -> do + _ <- enqueue s u (-1) + pure () + _ -> do + cid <- newClause s learnt True + cl <- getClause s cid + writeSTRef (clsLbd cl) lbd + claBumpActivity s cl + attachClause s cid + l0 <- readArray (clsLits cl) 0 + _ <- enqueue s l0 cid + pure () + modifySTRef' (statLearn s) (+ 1) + modifySTRef' (varInc s) (/ varDecayConst) + modifySTRef' (claInc s) (/ claDecayConst) + +analyzeFinal :: Solver s -> Int -> ST s [Int] +analyzeFinal s p = do + dl <- currentLevel s + if dl == 0 + then pure [p] + else do + writeArray (seen s) (litVar p) True + lim <- readSTRef (trailLim s) + n <- readSTRef (trailN s) + let bottom = last lim + outRef <- newSTRef [p] + forM_ [n - 1, n - 2 .. bottom] $ \i -> do + l <- readArray (trail s) i + let x = litVar l + sv <- readArray (seen s) x + when sv $ do + r <- readArray (varReason s) x + if r < 0 + then modifySTRef' outRef (litNeg l :) + else do + cl <- getClause s r + let lits = clsLits cl + sz = clsSize cl + forM_ [1 .. sz - 1] $ \j -> do + q <- readArray lits j + lev <- readArray (varLevel s) (litVar q) + when (lev > 0) $ writeArray (seen s) (litVar q) True + writeArray (seen s) x False + writeArray (seen s) (litVar p) False + readSTRef outRef + +minimize :: Solver s -> [Int] -> ST s [Int] +minimize s others = filterM (fmap not . redundant s) others + +redundant :: Solver s -> Int -> ST s Bool +redundant s l = do + r <- readArray (varReason s) (litVar l) + if r < 0 + then pure False + else do + cl <- getClause s r + let lits = clsLits cl + sz = clsSize cl + let chk j + | j >= sz = pure True + | otherwise = do + q <- readArray lits j + let v = litVar q + sv <- readArray (seen s) v + lev <- readArray (varLevel s) v + if sv || lev == 0 then chk (j + 1) else pure False + chk 1 + +orderLearnt :: Int -> [Int] -> [Int] +orderLearnt asserting [] = [asserting] +orderLearnt asserting others = asserting : others + +computeBtLevel :: Solver s -> [Int] -> ST s Int +computeBtLevel s others = + foldM (\acc l -> do + lev <- readArray (varLevel s) (litVar l) + pure (max acc lev)) 0 others + +computeLbd :: Solver s -> [Int] -> ST s Int +computeLbd s learnt = do + levels <- foldM (\acc l -> do + lev <- readArray (varLevel s) (litVar l) + pure (IntSet.insert lev acc)) IntSet.empty learnt + pure (IntSet.size levels) + +pickBranchVar :: Solver s -> ST s Int +pickBranchVar s = go + where + go = do + empty <- heapIsEmpty (heapH s) + if empty + then pure (-1) + else do + v <- heapRemoveMax (heapH s) + val <- varValue s v + if val == 0 then pure v else go + +search :: Solver s -> Int -> ST s (Maybe Bool) +search s confBudget = loop 0 + where + loop confSoFar = do + confl <- propagate s + if confl >= 0 + then do + modifySTRef' (statConf s) (+ 1) + lvl <- currentLevel s + if lvl == 0 + then do + writeSTRef (okFlag s) False + pure (Just False) + else do + (learnt, btLevel, lbd) <- analyze s confl + cancelUntil s btLevel + installLearnt s learnt lbd + loop (confSoFar + 1) + else do + if confSoFar >= confBudget + then do + cancelUntil s 0 + pure Nothing + else do + maybeReduce s + v <- pickBranchVar s + if v < 0 + then pure (Just True) + else do + modifySTRef' (statDec s) (+ 1) + pol <- readArray (polarity s) v + let l = if pol then posLit v else negLit v + newDecisionLevel s + _ <- enqueue s l (-1) + loop confSoFar + +maybeReduce :: Solver s -> ST s () +maybeReduce s = do + nl <- readSTRef (nLearnts s) + when (nl >= 2000) $ reduceDB s + +reduceDB :: Solver s -> ST s () +reduceDB s = do + cids <- readSTRef (learnts s) + alive <- filterM (\cid -> do cl <- getClause s cid; not <$> readSTRef (clsDead cl)) cids + scored <- mapM (\cid -> do + cl <- getClause s cid + a <- readSTRef (clsAct cl) + lbd <- readSTRef (clsLbd cl) + locked <- isLocked s cid cl + pure (cid, cl, a, lbd, locked)) alive + let removable = [ t | t@(_, _, _, lbd, locked) <- scored, not locked, lbd > 2 ] + keepFixed = [ (cid, cl) | (cid, cl, _, lbd, locked) <- scored, locked || lbd <= 2 ] + sortedByAct = sortByActAsc removable + half = length sortedByAct `div` 2 + (toRemove, toKeepRest) = splitAt half sortedByAct + forM_ toRemove $ \(_, cl, _, _, _) -> writeSTRef (clsDead cl) True + let survivors = map (\(cid, cl) -> (cid, cl)) keepFixed + ++ map (\(cid, cl, _, _, _) -> (cid, cl)) toKeepRest + writeSTRef (learnts s) (map fst survivors) + writeSTRef (nLearnts s) (length survivors) + modifySTRef' (statRemove s) (+ length toRemove) + +sortByActAsc :: [(Cid, Clause s, Double, Int, Bool)] -> [(Cid, Clause s, Double, Int, Bool)] +sortByActAsc = sortByKey (\(_, _, a, _, _) -> a) + +sortByKey :: Ord k => (a -> k) -> [a] -> [a] +sortByKey f = foldr insertBy [] + where + insertBy x [] = [x] + insertBy x (y : ys) + | f x <= f y = x : y : ys + | otherwise = y : insertBy x ys + +isLocked :: Solver s -> Cid -> Clause s -> ST s Bool +isLocked s cid cl = do + l0 <- readArray (clsLits cl) 0 + let v = litVar l0 + vv <- varValue s v + if vv == 0 + then pure False + else do + r <- readArray (varReason s) v + pure (r == cid) + +solveInternal :: Int -> [[Int]] -> ST s (Maybe [Bool], Stats) +solveInternal nv clausesIn = do + s <- newSolver nv + forM_ clausesIn (addClause s) + ok0 <- readSTRef (okFlag s) + result <- + if not ok0 + then pure False + else do + confl0 <- propagate s + if confl0 >= 0 + then do writeSTRef (okFlag s) False; pure False + else runRestarts s 0 + model <- + if result + then Just <$> extractModel s + else pure Nothing + st <- collectStats s + pure (model, st) + +runRestarts :: Solver s -> Int -> ST s Bool +runRestarts s restartNo = do + let budget = luby restartNo * 100 + outcome <- search s budget + case outcome of + Just verdict -> pure verdict + Nothing -> do + modifySTRef' (statRest s) (+ 1) + runRestarts s (restartNo + 1) + +extractModel :: Solver s -> ST s [Bool] +extractModel s = + mapM (\v -> do + vv <- varValue s v + pure (vv /= -1)) [0 .. numVars s - 1] + +collectStats :: Solver s -> ST s Stats +collectStats s = do + d <- readSTRef (statDec s) + c <- readSTRef (statConf s) + p <- readSTRef (statProp s) + r <- readSTRef (statRest s) + l <- readSTRef (statLearn s) + rm <- readSTRef (statRemove s) + pure (Stats d c p r l rm)