2

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

SRC
  • 2,123
  • 3
  • 31
  • 44

1 Answers1

5

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.

alias
  • 28,120
  • 2
  • 23
  • 40
  • Thank you. I was aware of the `from_file` API but was not sure if it supports DIMACS out of the box. Also, will keep the suggestion for Code Review thing for the future. Was not aware of that one. Thanks for that as well. – SRC Aug 23 '21 at 10:20