feat(xor): add xor reasoning with gaussian elimination

This commit is contained in:
dualfroz committed 2026-08-25 19:15:00 +02:00
1 parent 4c9f61e79c
commit 64876af98f
1 file changed
+126
+126
View File
@@ -0,0 +1,126 @@
module Sift.Xor
( XorClause(..)
, xorClause
, xorVarsOf
, xorSatisfies
, systemSatisfies
, xorToCnf
, systemToCnf
, GaussResult(..)
, gauss
, gaussSolve
) where
import Sift.Types
import Data.List (foldl', sortOn)
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
import qualified Data.Map.Strict as Map
data XorClause = XorClause
{ xorVars :: IntSet
, xorParity :: Bool
} deriving (Eq, Ord, Show)
xorClause :: [Var] -> Bool -> XorClause
xorClause vs parity =
let raw = map varId vs
folded = foldl' (\acc v -> if IntSet.member v acc
then IntSet.delete v acc
else IntSet.insert v acc)
IntSet.empty raw
in XorClause folded parity
xorVarsOf :: XorClause -> [Var]
xorVarsOf = map Var . IntSet.toList . xorVars
xorSatisfies :: Assignment -> XorClause -> Bool
xorSatisfies asn (XorClause vs parity) =
IntSet.foldr (\v acc -> acc /= Map.findWithDefault False (Var v) asn) False vs == parity
systemSatisfies :: Assignment -> [XorClause] -> Bool
systemSatisfies asn = all (xorSatisfies asn)
xorToCnf :: XorClause -> [Clause]
xorToCnf (XorClause vs parity) =
[ clauseFor signs
| signs <- assignmentsOver n
, violates signs ]
where
varList = map Var (IntSet.toList vs)
n = length varList
violates signs = odd (length (filter id signs)) /= parity
clauseFor signs =
[ if s then negLit v else posLit v
| (v, s) <- zip varList signs ]
assignmentsOver :: Int -> [[Bool]]
assignmentsOver 0 = [[]]
assignmentsOver k = [ b : rest | b <- [False, True], rest <- assignmentsOver (k - 1) ]
systemToCnf :: [XorClause] -> CNF
systemToCnf = CNF . concatMap xorToCnf
data Row = Row
{ rowVars :: IntSet
, rowRhs :: Bool
} deriving (Eq, Show)
xorRows :: Row -> Row -> Row
xorRows a b = Row (symDiff (rowVars a) (rowVars b)) (rowRhs a /= rowRhs b)
symDiff :: IntSet -> IntSet -> IntSet
symDiff a b = IntSet.union (IntSet.difference a b) (IntSet.difference b a)
data GaussResult
= GaussUnsat
| GaussSat [(Int, Row)]
deriving (Eq, Show)
gauss :: [XorClause] -> GaussResult
gauss system = go [] (map toRow system)
where
toRow (XorClause vs parity) = Row vs parity
go pivots [] = GaussSat pivots
go pivots (r : rest) =
let reduced = reduce pivots r
in if IntSet.null (rowVars reduced)
then if rowRhs reduced then GaussUnsat else go pivots rest
else
let pivotVar = IntSet.findMin (rowVars reduced)
pivots' = insertPivot pivotVar reduced pivots
in go pivots' rest
reduce :: [(Int, Row)] -> Row -> Row
reduce pivots row =
foldl' (\acc (pv, prow) ->
if IntSet.member pv (rowVars acc) then xorRows acc prow else acc)
row
(sortOn fst pivots)
insertPivot :: Int -> Row -> [(Int, Row)] -> [(Int, Row)]
insertPivot pivotVar row pivots =
(pivotVar, row) : map back pivots
where
back (pv, prow) =
if IntSet.member pivotVar (rowVars prow)
then (pv, xorRows prow row)
else (pv, prow)
gaussSolve :: [XorClause] -> Maybe Assignment
gaussSolve system =
case gauss system of
GaussUnsat -> Nothing
GaussSat pivots ->
let allVarsSet = IntSet.unions (map xorVars system)
pivotVars = IntSet.fromList (map fst pivots)
freeVars = IntSet.difference allVarsSet pivotVars
base = Map.fromList [ (Var v, False) | v <- IntSet.toList freeVars ]
solved = foldl' assignPivot base pivots
in Just solved
where
assignPivot acc (pv, row) =
let others = IntSet.delete pv (rowVars row)
othersXor = IntSet.foldr (\v a -> a /= Map.findWithDefault False (Var v) acc) False others
val = othersXor /= rowRhs row
in Map.insert (Var pv) val acc