potassco / clasp

⚙️ A conflict-driven nogood learning answer set solver

Home Page:https://potassco.org/clasp/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Backtracking without notification in Propagator::check when lookahead is activated.

rkaminsk opened this issue · comments

I did not manage to produce a really short example but the attached example reliably reproduces the bug:

@rkaminsk Could you please describe a little more what the bug is? I could not figure it out from running your example alone. The example prints something like:

The truth value of literal 11 was True at the beginning of check. Even though
propagate and add_clause failed 0 times, its truth value is Free now.

Not sure what this means and it also seems that literal 11 is actually not watched. If I add watches for 11 and -11, I instead either get:

The truth value of literal 11 was Free at the beginning of check. Even though
propagate and add_clause failed 0 times, its truth value is Free now.

or

The truth value of literal 11 was True at the beginning of check. Even though
propagate and add_clause failed 0 times, its truth value is True now.

depending on when the watches are added.

If you look at the code, then you see that the truth value of literal 11 is true at the beginning of the check call (line 44). But at the end of the check call (line 69), the truth value suddenly is free. This means that the solver must have backtracked somewhere in between but neither propagate nor add_clause returned false (n_fail is zero). I always made the assumption that this cannot happen and I think this an essential assumption.

Check calls should be completely independent of watches, right? They are called no matter which watches were set. Every literal should be subject to the assumption that it cannot switch from assigned to unassigned if neither propagate nor add_clause returned false.

Hope this makes sense...

Yes, it seems that propagate() is not conservative enough in the presence of lookahead. What happens is this:

  • check() is called on decision level X and sees that the unwatched literal x is true on X
  • check() adds a new unit-resulting clause
  • check() calls propagate()
  • propagate() eventually invokes lookahead, which finds a conflict when testing some literal p at decision level X+1
  • conflict resolution backtracks to level Y = X - 1. Now, if no watched literal was (yet) propagated on level >= X, clingo's undo() callback is not called but unfortunately, propagate() currently relies on the call to undo() to decide whether some backtracking invalidated the current state.

I ran it again on my example (not just the extracted trace) and with your fix it seems to be working.