0

I'm implementing DPLL algorithm that counts the number of visited nodes. I managed to implement DPLL that doesn't count visited nodes but I can't think of any solutions to the problem of counting. The main problem is that as the algorithm finds satisfying valuation and returns True, the recursion rolls up and returns counter from the moment the recursion started. In any imperative language I would just use global variable and increment it as soon as function is invoked, but it is not the case in Haskell.

The code I pasted here does not represent my attempts to solve the counting problem, it is just my solution without it. I tried to use tuples such as (True,Int) but it will always return integer value from the moment the recursion started.

This is my implementation where (Node -> Variable) is a heuristic function, Sentence is list of clauses in CNF to be satisfied, [Variable] is a list of Literals not assigned and Model is just a truth valuation.

dpll' :: (Node -> Variable) -> Sentence -> [Variable] -> Model -> Bool
dpll' heurFun sentence vars model
  | satisfiesSentence model sentence  = True
  | falsifiesSentence model sentence  = False
  | otherwise         = applyRecursion
    where
      applyRecursion
        | pureSymbol /= Nothing = recurOnPureSymbol
        | unitSymbol /= Nothing = recurOnUnitSymbol
        | otherwise             = recurUsingHeuristicFunction
          where
            pureSymbol  = findPureSymbol vars sentence model
            unitSymbol  = findUnitClause sentence model
            heurVar = heurFun (sentence,(vars,model))
            recurOnPureSymbol =
              dpll' heurFun sentence (vars \\ [getVar pureSymbol]) ((formAssignment pureSymbol):model)
            recurOnUnitSymbol =
              dpll' heurFun sentence (vars \\ [getVar unitSymbol]) ((formAssignment unitSymbol):model)
            recurUsingHeuristicFunction = case vars of
              (v:vs) ->     (dpll' heurFun sentence (vars \\ [heurVar]) ((AS (heurVar,True)):model)
                        ||   dpll' heurFun sentence (vars \\ [heurVar]) ((AS (heurVar,False)):model))
              []     ->     False

I would really appreciate any advice on how to count the visited nodes. Thank you.

EDIT:

The only libraries I'm allowed to use are System.Random, Data.Maybe and Data.List.

EDIT:

One possible solution I tried to implement is to use a tuple (Bool,Int) as a return value from DPPL function. The code looks like:

dpll'' :: (Node -> Variable) -> Sentence -> [Variable] -> Model -> Int -> (Bool,Int)
dpll'' heurFun sentence vars model counter
  | satisfiesSentence model sentence  = (True,counter)
  | falsifiesSentence model sentence  = (False,counter)
  | otherwise         = applyRecursion
  where
    applyRecursion
      | pureSymbol /= Nothing = recurOnPureSymbol
      | unitSymbol /= Nothing = recurOnUnitSymbol
      | otherwise             = recurUsingHeuristicFunction
      where
        pureSymbol  = findPureSymbol vars sentence model
        unitSymbol  = findUnitClause sentence model
        heurVar = heurFun (sentence,(vars,model))
        recurOnPureSymbol =
          dpll'' heurFun sentence (vars \\ [getVar pureSymbol]) ((formAssignment pureSymbol):model) (counter + 1)
        recurOnUnitSymbol =
          dpll'' heurFun sentence (vars \\ [getVar unitSymbol]) ((formAssignment unitSymbol):model) (counter + 1)
        recurUsingHeuristicFunction = case vars of
          (v:vs)    ->    ((fst $ dpll'' heurFun sentence (vars \\ [heurVar]) ((AS (heurVar,True)):model) (counter + 1))
                      ||  (fst $ dpll'' heurFun sentence (vars \\ [heurVar]) ((AS (heurVar,False)):model) (counter + 1)),counter)
          []        -> (False,counter)

The basic idea of this approach is to increment the counter at each recursive call. However, the problem with this approach is that I have no idea how to retrieve counter from recursive calls in OR statement. I'm not even sure if this is possible in Haskell.

Guy Coder
  • 24,501
  • 8
  • 71
  • 136
jdet
  • 15
  • 3
  • I'm not yet sure I understand the problem. What does "it will always return integer value from the moment the recursion started" mean? – Daniel Wagner Mar 13 '17 at 20:52
  • In one possible solution I used (True,Int) tuple to return node counter. As DPLL function reached OR statement and after applying recursion n times until Model satisfied Sentence, function evaluates the OR statement at the same level as the initial ORed recursion started ultimately returning counter from that particular level of recursion. I hope you can understand the problem now. Thank you. – jdet Mar 13 '17 at 21:19
  • So, are you asking how to merge the two `Int`s returned from the two calls to `dpll'` in the `v:vs` case of `recurUsingHeuristicFunction`? If so, why is `(+)` not the right way to merge them? – Daniel Wagner Mar 13 '17 at 21:28
  • If you think that using tuple (Bool,Int) would solve the problem then yes. In my attempt to solve it I was basically unable to retrieve counter values from lower levels of recursion in OR statement. – jdet Mar 13 '17 at 21:30
  • I tried increasing the counter by one on each recursive call but it always returns the counter value from the level at which OR statement was first evaluated (just after all pure symbols and unit clauses were used). – jdet Mar 13 '17 at 21:35
  • 1
    You should include an MWE: the smallest complete snippet of code that exhibits the behavior you don't like, together with a description of how to run it, what you expect to happen, and what happens instead. – Daniel Wagner Mar 13 '17 at 21:35

2 Answers2

1

You can retrieve the counter from the recursive call using case or similar.

recurUsingHeuristicFunction = case vars of
    v:vs -> case dpll'' heurFun sentence (vars \\ [heurVar]) (AS (heurVar,True):model) (counter + 1) of
        (result, counter') -> case dpll'' heurFun sentence (vars \\ [heurVar]) (AS (heurVar,False):model) counter' of
            (result', counter'') -> (result || result', counter'')
    []   -> (False,counter)

This is a manual implementation of the State monad. However, it's not clear to me why you are passing in a counter at all. Just return it. Then it is the simpler Writer monad instead. The code for this helper would look something like this:

recurUsingHeuristicFunction = case vars of
    v:vs -> case dpll'' heurFun sentence (vars \\ [heurVar]) (AS (heurVar,True):model) of
        (result, counter) -> case dpll'' heurFun sentence (vars \\ [heurVar]) (AS (heurVar,False):model) of
            (result', counter') -> (result || result', counter + counter' + 1)
    []   -> (False,0)

Other results would be similar -- returning 0 instead of counter and 1 instead of counter+1 -- and the call to the function would be simpler, with one fewer argument to worry about setting up correctly.

Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
  • It works! Great. Thank you. However, It adds a huge overhead. Version without the counter solves one of my test problems in 4s while version with the counter solves exactly the same problem in 22s. What might be the problem? – jdet Mar 13 '17 at 22:16
  • @jdet My blind stab would be to add `~` to the patterns (or use `let` instead of `case`), as in `case dpll'' ... of ~(result, counter) -> ...`. A second try would be to force `counter` more carefully when it's an argument. But a real (experimentally-tested, rather than off-the-cuff) answer requires you taking the time to turn this into a real question. – Daniel Wagner Mar 13 '17 at 22:22
  • I think it might be case that now DPLL must evaluate both sides of OR statement while It could terminate early in the previous implementation as soon as lhs evaluated to True. I'll try to solve it on my own. However, thank you for your help Daniel. I really appreciate it. – jdet Mar 13 '17 at 22:30
  • Simple if statement solved the problem. Thanks again Daniel! – jdet Mar 13 '17 at 22:35
0

Basically what you described as your solution in imperative language can be modeled by passing around a counting variable, adding the variable to the result at the time you return it (the bottom of recursion that reaches satisfiable assignment), i.e. for a function a -> b you would create a new function a -> Int -> (b, Int). The Int argument is the current state of the counter, the result is enriched with the updated state of the counter.

This can further be re-expressed elegantly using the state monad. A very nice tutorial on haskell in general and state monad is here. Basically the transformation of a -> b to a -> Int -> (b, Int) can be seen as transformation of a -> b into a -> State Int b by simply given a nicer name to the function Int -> (b, Int). There is a very nice blog that explains where these nice abstractions come from in a very accessible way.

import Control.Monad.Trans.StateT

type Count = Int

dpllM :: (Node -> Variable) -> Sentence -> [Variable] -> Model -> State Count Bool
dpllM heurFun sentence vars model | ... = do
    -- do your thing
    modify (+1)
    -- do your thing

dpll' :: (Node -> Variable) -> Sentence -> [Variable] -> Model -> Bool
dpll' heurFun sentence vars model = runState (dpllM heurFun sentence vars model) 0

Maybe you want something like

f :: A -> Int -> (Bool, Int)
f a c =
    let a' = ...
        a'' = ...
        (b', c') = f a' c in f a'' c'
jakubdaniel
  • 2,233
  • 1
  • 13
  • 20
  • The second solution is really interesting but unfortunately I'm allowed to use only basic libraries (see updated question, sorry for confusion). Could you give me an example on how the first solution works? Thanks – jdet Mar 13 '17 at 20:54
  • Just turn your pure function from `a -> ... -> b` into a function `a -> ... -> Int -> (b, Int)` like `f a = expression` becomes `f a c = (expression, if ... then c + 1 else c)` then you will need to extract the new state when ever you need to pass it to some callee. At some point you will want your function to return `(Bool, Int)` where the `Int` is the current state. :) if however your state changes only along one recursion path then you can simply extend your function with an argument expressing the current count and add that number to output on the base case of your recursion. – jakubdaniel Mar 13 '17 at 21:37
  • I think this is in some sense similar to what I was trying to do (see second code snippet). I would really appreciate if you can take a look at my 'solution' and somehow show me what's wrong with it. Thanks! – jdet Mar 13 '17 at 21:57