Source code for nnf.kissat

"""Interoperability with `kissat <http://fmv.jku.at/kissat/>`_.

``solve`` invokes the SAT solver directly on the given theory.
"""

import os
import shutil
import subprocess
import typing as t

from nnf import And, Or, Var, dimacs
from nnf.util import Model

__all__ = ('solve',)


[docs]def solve( sentence: And[Or[Var]], extra_args: t.Sequence[str] = () ) -> t.Optional[Model]: """Run kissat to compute a satisfying assignment. :param sentence: The CNF sentence to solve. :param extra_args: Extra arguments to pass to kissat. """ if not sentence.is_CNF(): raise ValueError("Sentence must be in CNF") if shutil.which('kissat') is not None: SOLVER = 'kissat' else: SOLVER = os.path.join( os.path.dirname(os.path.abspath(__file__)), 'bin', 'kissat' ) assert os.path.isfile(SOLVER), "Cannot seem to find kissat solver." args = [SOLVER] args.extend(extra_args) var_labels = dict(enumerate(sentence.vars(), start=1)) var_labels_inverse = {v: k for k, v in var_labels.items()} cnf = dimacs.dumps(sentence, mode='cnf', var_labels=var_labels_inverse) try: proc = subprocess.Popen( args, stdout=subprocess.PIPE, stdin=subprocess.PIPE, universal_newlines=True ) log, _ = proc.communicate(cnf) except OSError as err: if err.errno == 8: print("Error: Attempting to run the kissat binary on an") print(" incompatible system. Consider compiling kissat") print(" natively so it is accessible via the command line.") raise # Two known exit codes for the solver if proc.returncode not in [10, 20]: raise RuntimeError( "kissat failed with code {}. Log:\n\n{}".format( proc.returncode, log ) ) # Unsatisfiable if proc.returncode == 20: return None assert proc.returncode == 10, "Bad error code. Log:\n\n{}".format(log) variable_lines = [ line[2:] for line in log.split("\n") if line.startswith("v ") ] literals = [int(num) for line in variable_lines for num in line.split()] assert literals[-1] == 0, "Last entry should be 0. Log:\n\n{}".format(log) literals.pop() model = {var_labels[abs(lit)]: lit > 0 for lit in literals} return model