-- SATSolver Clausal Normal Form Satisfiability Solver
-- Copyright (C) 2007 Andrii Loki Zvorygin andrii.z@gmail.com
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see .
module Main
where
-- need to read .cnf file
-- find amount of variables
-- get random binary string of length equal to variables
-- then go by get lines substituting appropriate variables, if result is 1 then continue, if result is 0 then output 0 as result, once reached end with all 1's output 1
import Numeric
import System.Environment
import System.Random
main = do
args <- getArgs
let file = args !! 0
fileStr <- readFile file
let lineTokens = lines fileStr
title = cLine lineTokens
pLined = pLine lineTokens
variables = (words $ pLined) !! 2 -- read $ (words $ lineTokens !! 1) !! 2
numVariables = read variables
clauses = (words $ pLined) !! 3 -- (words $ lineTokens !! 1) !! 3
numClauses = read clauses
strTokens = words $ unlines $ cnfLine lineTokens -- convert file String to [String] by words
intTokens = map read strTokens -- convert token [String] to [Int]
-- positIntTokens = map posit intTokens
-- uniqueIntTokens = unique positIntTokens -- amount of variables, calculated by finding unique
-- maxInt = getMax intTokens -- amount of variables, calculated by finding max
-- putStrLn $ "unique variables: " ++ (show $ length uniqueIntTokens)
if variables == "--help"
then putStrLn "$ SATSolve "
else do
putStrLn $ "Loki's Shunso CNF Categorizer"
putStrLn $ "header of CNF file: "
putStr $ title
putStrLn $ pLined
putStrLn $ "number of variables: " ++ variables ++ " hex: " ++ showHex numVariables "" -- amount of variables
putStrLn $ "number of clauses: " ++ clauses ++ " hex: " ++ showHex (read clauses) ""
putStrLn $ "max variable combinations:" ++ (show ((2^(fromIntegral (read variables)))::Integer)) ++ " hex: " ++ showHex ((2^(fromIntegral (read variables)))::Integer) ""
-- gen random binary string of length maxInt
loopSolve intTokens numVariables numClauses
cnfLine :: [String] -- ^ takes lines of CNF file
-> [String] -- ^ returns only CNF containing lines
cnfLine [] = "":[]
cnfLine (x:xs) =
case head x of
'c' -> cnfLine xs
'p' -> cnfLine xs
_ -> (x:xs)
cLine :: [String] -- ^ takes lines of CNF file
-> String -- ^ returns header
cLine [] = ""
cLine (x:xs) =
if head x == 'c'
then x ++ "\n" ++ cLine xs
else ""
pLine :: [String] -- ^ takes lines of CNF file
-> String -- ^ returns p line that has number of variables and number of clauses
pLine [] = ""
pLine (x:xs) =
if head x == 'p'
then x
else pLine xs
loopSolve :: [Int] -- ^ CNF expression
-> Int -- ^ number of variables
-> Int -- ^ number of clauses
-> IO () -- ^ outputs solution to screen
loopSolve a b c = loopSolve2 a b c (1 :: Integer) 0
loopSolve2 :: [Int] -- ^ CNF expression
-> Int -- ^ number of variables
-> Int -- ^ number of clauses
-> Integer -- ^ counter for number of atempts
-> Int -- ^ counter for maximum clauses solved -- MAX-SAT
-> IO () -- ^ outputs solution to screen
loopSolve2 intTokens numVariables numClauses atempts maxResult = do
randGen <- newStdGen
let (varValues, result) = runSolver intTokens numVariables randGen
if result == numClauses
then do
putStrLn $ "assignment of variables: " ++ (show varValues)
putStrLn $ "number of clauses solved: " ++ (show $ result) ++ " hex: " ++ showHex result ""
putStrLn $ "number of atempts: " ++ (show atempts) ++ " hex: " ++ showHex atempts ""
putStrLn $ "SATisfiable"
else if result > maxResult
then do
putStrLn $ "assignment of variables: " ++ (show varValues)
putStrLn $ "number of clauses solved: " ++ (show $ result) ++ " hex: " ++ showHex result ""
putStrLn $ "number of atempts: " ++ (show atempts) ++ " hex: " ++ showHex atempts ""
loopSolve2 intTokens numVariables numClauses (atempts+1) result
else if atempts >= ((2^(fromIntegral (numVariables)))::Integer)
then do
putStrLn $ "max number of clauses solved: " ++ (show $ result) ++ " hex: " ++ showHex result ""
putStrLn $ "number of atempts: " ++ (show atempts) ++ " hex: " ++ showHex atempts ""
putStrLn $ "2^" ++ (show numVariables) ++ " = " ++ (show $ 2^numVariables) ++ " max variable assignment combinations reached"
putStrLn $ "Not SATisfiable"
else loopSolve2 intTokens numVariables numClauses (atempts+1) maxResult
for :: Int -- ^ number of times to recurse
-> IO t -- ^ process to run
-> IO () -- ^ output
for 0 process = return ()
for times process = do { process ; for (times -1) process; }
runSolver :: [Int] -- ^ CNF expression
-> Int -- ^ number of variables
-> StdGen -- ^ random number generator
-> ([Int],Int) -- ^ returns tuple with [Int] variable values and binary Int to indicate if it was solved
runSolver intTokens maxInt randGen =
let varValues = getValues maxInt randGen
in
(varValues, maxCNF intTokens varValues)
-- | evaluates to see if completely satisfied
evaluateCNF :: [Int] -- ^ CNF expression
-> [Int] -- ^ variable values
-> Int -- ^ returns binary Int result
-- ^ if assigns 1 to variable result of clause is 1
-- ^ if assigns 0 to clause return 0
-- ^ if all clauses exhausted, return 1
evaluateCNF [] values = 1
evaluateCNF (x:xs) values =
if x == 0 -- end of clause
then 0 -- since end of clause reached the result of formula is 0
else if x > 0 -- check if negation occurs
then if values !! (x-1) == 1 -- check that value is equal to 1
then evaluateCNF (tillAfterZero xs) values
else evaluateCNF xs values
else if values !! ((x*(-1))-1) == 0 -- check that negated value is equal to 1
then evaluateCNF (tillAfterZero xs) values
else evaluateCNF xs values
-- | finds amount of clauses satisfied
maxCNF :: [Int] -- ^ CNF expression
-> [Int] -- ^ variable values
-> Int -- ^ returns Int of number of clauses solved
maxCNF [] values = 0
maxCNF (x:xs) values =
if x == 0 -- end of clause
then 0 + maxCNF xs values -- since end of clause reached the result of formula is 0
else if x > 0 -- check if negation occurs
then if values !! (x-1) == 1 -- check that value is equal to 1
then 1 + maxCNF (tillAfterZero xs) values
else 0 + maxCNF xs values
else if values !! ((x*(-1))-1) == 0 -- check that negated value is equal to 1
then 1 + maxCNF (tillAfterZero xs) values
else 0 + maxCNF xs values
tillAfterZero :: [Int] -- ^ takes [Int] CNF expression
-> [Int] -- ^ drops until after intercepting the first 0 returning result
tillAfterZero [] = []
tillAfterZero (x:xs) =
if x == 0
then xs
else tillAfterZero xs
getValues :: Int -- ^ takes number of variables
-> StdGen -- ^ random number generator
-> [Int] -- ^ returns binary [Int] of variable values
getValues 0 rand = []
getValues max rand =
let (value,rand2) = randomR (0,1) rand
in value : getValues (max-1) rand2
-- | unused, outputs unique list of Int's
unique :: [Int] -> [Int]
unique [] = []
unique (x:xs) =
if elem x xs
then unique xs
else x : unique xs
-- | unused, outputs max Int from [Int]
getMax :: [Int] -> Int
getMax [] = 0
getMax (x:xs) =
if length (x:xs) > 1
then if x >= head xs
then getMax (x:(tail xs))
else getMax xs
else x
-- | unused, like absolute, only returns positive integer
posit :: Int -> Int
posit x =
if x < 0
then x*(-1)
else x