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

Core conflicts.

rkaminsk opened this issue · comments

When a problem is unsatisfiable when solving under assumptions, it would be nice if there were an interface to get a conflict clause that is a subset of the assumptions literals. A trivial example would be something like the following:

Program:

:- a, b.
{c}.

Solve call:

ret = solve(assumptions=[a, b, c])
assert(ret.conflict == [a, b])

The above is not meant to be the final interface.

@rkaminsk
While it would be quite straightforward to add this for single-threaded solving and for solving in competition mode (as long as we only talk about problems that are truly unsat under some given assumptions), I don't currently see an easy way to properly support this in splitting mode.
Atm, clasp simply does not maintain enough information to extract such a final conflict clasue in that case. So, without extending parallel solve with the capability to store/resolve unsat paths, I see only two possibilities:
a) always return the initial set of assumptions
b) do a second solve() call in the end hoping that solvers learnt/kept enough crucial clauses to make it faster than the first call.
Neither approach seems particular desirable.

@rkaminsk
While it would be quite straightforward to add this for single-threaded solving and for solving in competition mode (as long as we only talk about problems that are truly unsat under some given assumptions), I don't currently see an easy way to properly support this in splitting mode.
Atm, clasp simply does not maintain enough information to extract such a final conflict clasue in that case. So, without extending parallel solve with the capability to store/resolve unsat paths, I see only two possibilities:
a) always return the initial set of assumptions
b) do a second solve() call in the end hoping that solvers learnt/kept enough crucial clauses to make it faster than the first call.
Neither approach seems particular desirable.

I would think a) is fine because it is still "correct". We can document this as a caveat. I hope I am not wrong but I think splitting is not used very often anyway.

I implemented a first version in branch issue-59. However, I'm not convinced that this is really useful because without doing any subset minimization, the returned literals are probably just the initial assumptions.

This is cool. I will put it into Clingo's SolveHandle class like you did. Having the possibility to minimize cores further would of course be nice, too. Having the ones that are already used in the uncore minimization would be nice. But one can also role one's own minimization strategy using multiple solve calls.

I did a first throw at an implementation. Using the Python API one can write now:

>>> import clingo
>>>
>>> ctl = clingo.Control()
>>> ctl.add("base", [], "{a;b;c}. :- a, b.")
>>> ctl.ground([("base", [])])
>>> 
>>> assumptions = []
>>> for name in ("a", "b", "c"):
...     assumptions.append(ctl.symbolic_atoms[clingo.Function(name)].literal)
... 
>>> print(ctl.solve(assumptions=assumptions, on_core=lambda core: print(core)))
[1, 2]
UNSAT

There are still some issues though. It looks like for empty cores unsatCore returns a null pointer. I think it should really be an empty vector instead.

Another thing is that the core is returned in terms of solver literals. For the interface, I need program literals. I had to write quite some ugly code (that is not even fully working yet) to do the mapping. Would it be possible to get a little help from Clasp::Asp::LogicProgram to do the mapping?

Can you have a look at the feature/core branch in the clingo repository, please?

PS: I additionally added a callback because this is easier to use in Python. There is a SolveHandle.core method too.

There are still some issues though. It looks like for empty cores unsatCore returns a null pointer. I think it should really be an empty vector instead.

Ok. My idea was to return nullptr whenever the problem was not "unsat under assumptions" but I guess it makes more sense to only return a nullptr if the problem was not unsat at all.

Another thing is that the core is returned in terms of solver literals. For the interface, I need program literals. I had to write quite some ugly code (that is not even fully working yet) to do the mapping. Would it be possible to get a little help from Clasp::Asp::LogicProgram to do the mapping?

Probably, I'll look into that. At least for the assumptions, we'll probably always have or can find a corresponding program literal. This is not the case for unsat-core based minimization, though. There, unsat cores nearly completely consist of solver-local aux variables.

Probably, I'll look into that. At least for the assumptions, we'll probably always have or can find a corresponding program literal. This is not the case for unsat-core based minimization, though. There, unsat cores nearly completely consist of solver-local aux variables.

Thanks. Yes, here it should really work because the clingo API only allows for specifying assumptions as program literals. Maybe with a lower level interface it would also make sense to get solver literals. For example, if we had something like the enumerator in the API.

@rkaminsk I pushed an update to the issue-59 branch. Let me know if you need anything more or if I can close the issue and merge the branch into dev.

There is the corner case that someone might assume a fact to be false (or assume a false literal to be true). In this case I would expect to get a core with one literal but clasp reports an empty core. This happens, for example, in the snippet below. Can we fix this?

#script (python)

import sys
from clingo import Function

def main(prg):
    cores = []
    prg.ground([("base", [])])
    a = prg.symbolic_atoms[Function("a")].literal
    prg.solve(assumptions=[-a], on_core=lambda core: cores.append(core))
    assert cores[-1] == [-a]

#end.

a.

That seems to do the trick. I did not thorough testing but my simple tests all go through now.

Merged to dev.