Search code examples
pythonpython-3.xz3z3pysat

Better way of reading and parsing DIMACS for Z3


I am very new to SAT and Z3 (Have not even started with SMT yet). I have been playing around with gophersat (A nice Go implementation which works for a good set of SAT problems) and I discovered DIMACS format there. Although I agree that it is not the best way to work with variables, but for some simple testing I found it pretty handy. I tried to check if there is a direct way to read, parse, and construct a SAT formula from a DIMACS file in Z3. I didn't find any (as mentioned, I am very new, so I may not know the existence of one). So I ended up coding the following. I would like to know what people think about it and whether there is a better way to achieve the same.

(N.B.: I have not put any checks on the number of clauses and/or number of variables for the formula)

from z3 import *


def read_dimacs_and_make_SAT_formula(file_name: str):
    vars = dict()
    base_name = "x"
    with open(file_name, "r") as f:
        for idx, line in enumerate(f):
            if line.strip() != "\n" and line.strip() != "" and line.strip() != "\r" and line.strip() != None and idx > 0:
                splitted_vals = line.split()
                for element in splitted_vals:
                    if int(element) != 0:
                        if int(element) > 0 and vars.get(element) is None:
                            # pure variable which never seen
                            vars[element] = Bool(base_name+element)
                        elif int(element) > 0 and vars.get("-"+element) is not None:
                            # pure variable we have seen the negetion before.
                            vars[element] = Not(vars["-"+element])
                        elif int(element) < 0 and vars.get("-"+element) is None:
                            # negetion of a variable and we have not seen it before.
                            vars[element] = Not(Bool(base_name+element.replace("-", "")))
                        elif int(element) < 0 and vars.get(element.replace("-", "")) is not None:
                            # Negetion, we have seen the pure variable before.
                            vars[element] = Not(vars[element.replace("-", "")])
        f.seek(0)
        disjunctions = []
        for idx, line in enumerate(f):
            clauses = []
            if line.strip() != "\n" and line.strip() != "" and line.strip() != "\r" and line.strip() != None and idx > 0:
                splitted_vals = line.split()
                for element in splitted_vals:
                    if int(element) != 0:
                        clauses.append(vars[element])
                disjunctions.append(Or([*clauses]))
        # print(disjunctions)
        if disjunctions:
            return And([*disjunctions])
        return None

The way you use it is simple. Like so -

if __name__== "__main__":
    s = Solver()

    disjunctions = read_dimacs_and_make_SAT_formula("dimacs_files/empty.cnf")
    if disjunctions is not None:
        s.add(disjunctions)
        print(s.check())
        if s.check().r != -1:
            print(s.model())

If called that way, the result looks like the following

 python test_1.py                                                                                                                                                                              ✔ │ SAT Py 
sat
[x3 = False, x2 = True, x1 = True, x4 = False]

So the question is, again, what do you think? And can I do something else? Is there a better way?

Thanks in advance


Solution

  • Note that code reviews are usually off-topic for stack-overflow. There's a dedicated stack-exchange website (https://codereview.stackexchange.com) for such purposes. You might get better feedback on your program there.

    Having said that, z3 supports DIMACS format out-of-the-box, so you don't really need to program it. Here's an example. Say we have the file a.dimacs:

    c  simple_v3_c2.cnf
    c
    p cnf 3 2
    1 -3 0
    2 3 -1 0
    

    You can directly feed this to z3:

    $ z3 a.dimacs
    s SATISFIABLE
    v -1 -2 -3
    

    Or, if you want to use the Python API, you can write:

    from z3 import *
    
    s = Solver()
    s.from_file("a.dimacs")
    print(s)
    print(s.check())
    print(s.model())
    

    which prints:

    [Or(k!1, Not(k!3)), Or(Not(k!1), k!2, k!3)]
    sat
    [k!1 = False, k!3 = False, k!2 = False]
    

    Note that the file extension has to be dimacs, so z3 will use the correct parser internally when you call s.from_file. From the command line, you can either use the .dimacs extension, or pass the command line option -dimacs to z3.