symbolic-sat.org 17 KB

Symbolic SAT Solver

Introduction   nonum

CLOSED: [2018-03-17 Sat 23:00]

The following describes and implements a SAT solver for the five most common logical connectives ($\lnot, ∧, ∨, ⇒, ⇔\}$) It does so using the Tree Rules for Sentential Logic as described in \cite{klenk08:_under_symbol_logic}. While it is written first and foremost for programmers and computer scientists, it's also an attempt at a well-documented program that would likely be of interest to logicians who seek to understand more about the actual construction of computer programs. The organization is as follows:

  1. Description and implementation of the Solver. Although this builds on things defined later, this section is the bulk of the document and also the most interesting.

  2. A definition of an Expression Classifier. This is used by the solver to help decide what steps to make as it attempts to solve a given instance of the SAT problem.

  3. A description of several utilities used in the Solver. These utilities are not particularly interesting, but do help to abstract away certain common tasks used in the main solver.

  4. A description of how the software is packaged as a library. This in and of itself is rather mundane, however it should help to further demonstrate the organization of software.

This SAT solver is written in a language called Common Lisp (\cite{pitmann05:_common_lisp_hyper}), known well in the AI community. For those unfamiliar, aside from the previously cited source several good resources are available (\cite{common_lisp_wikib,seibel05:_pract_common_lisp}).

TOC   ignore

Solver

  (in-package #:symbolic-sat)

  <<expression-handlers>>
  <<driver>>

Driver

  (defun solve-it (unchecked current-path)
    (if (null unchecked)
        (collect-atoms current-path)
        (dispatch-on (first unchecked)
                     (rest unchecked)
                     current-path)))

  (defun solve (expression)
    (solve-it (list expression)
              (list expression)))

Expression Handlers

  (defvar *handlers* '())

  (defmacro define-expression-handler (expression-type &body handler)
    (let ((name (symbolicate 'handle- expression-type)))
      `(progn
         (defun ,name (expression unchecked current-path)
           ,@handler)
         (pushnew '(,expression-type . ,name) *handlers* :key #'first)
         ',expression-type)))

  (defun dispatch-on (expression unchecked current-path)
    (do* ((pair (first *handlers*) (first remaining))
          (remaining (rest *handlers*) (rest remaining)))
         ((or (null pair)
             (expression-type-p (car pair) expression))
          (when (not (null pair))
            (funcall (cdr pair) expression unchecked current-path)))))

  <<and-handlers>>
  <<or-handlers>>
  <<implication-handlers>>
  <<biconditional-handlers>>
  <<doubled-not-handler>>

And

  (define-expression-handler sentential-and
    (when (not (any-conflict-p (rest expression) current-path))
      (solve-it (concatenate 'list (collect-non-atoms (rest expression)) unchecked)
                (concatenate 'list (rest expression) current-path))))

  (define-expression-handler not-and
    (let ((expr-list (map 'list (lambda (expr) `(not ,expr)) (rest (second expression))))
          (satp nil))
      (do* ((expr-prime (first expr-list) (first expr-list-prime))
            (expr-list-prime (rest expr-list) (rest expr-list-prime)))
           ((or satp (null expr-prime)) satp)
        (when (not (has-conflict-p expr-prime current-path))
          (setf satp (solve-it (non-atomic-add expr-prime unchecked)
                               (cons expr-prime current-path)))))))

Or

  (define-expression-handler sentential-or
    (let ((satp nil))
      (do ((expr (first (rest expression)) (first expr-list))
           (expr-list (rest (rest expression)) (rest expr-list)))
          ((or satp (null expr)) satp)
        (when (not (has-conflict-p expr current-path))
          (setf satp (solve-it (non-atomic-add expr unchecked)
                               (cons expr current-path)))))))

  (define-expression-handler not-or
    (let ((expr-list (map 'list (lambda (expr) `(not ,expr))
                          (rest (second expression)))))
      (when (not (any-conflict-p expr-list current-path))
        (solve-it (concatenate 'list (collect-non-atoms expr-list) unchecked)
                  (concatenate 'list expr-list current-path)))))

Implication

  (define-expression-handler sentential-implication
    (let ((branch-a `(not ,(second expression)))
          (branch-b (third expression)))
      (cond
        ((not (has-conflict-p branch-a current-path))
         (solve-it (non-atomic-add branch-a unchecked) (cons branch-a current-path)))
        ((not (has-conflict-p branch-b current-path))
         (solve-it (non-atomic-add branch-b unchecked) (cons branch-b current-path)))
        (t nil))))

  (define-expression-handler not-implication
    (let ((the-expr-list (list (second (second expression)) `(not ,(third (second expression))))))
      (when (not (any-conflict-p the-expr-list current-path))
        (solve-it (concatenate 'list (collect-non-atoms the-expr-list) unchecked)
                  (concatenate 'list the-expr-list current-path)))))

Biconditional

  (define-expression-handler sentential-biconditional
    (let* ((branch-a (rest expression))
           (branch-b (map 'list (lambda (expr) `(not ,expr)) branch-a)))
      (cond
        ((not (any-conflict-p branch-a current-path))
         (solve-it (concatenate 'list (collect-non-atoms branch-a) unchecked)
                   (concatenate 'list branch-a current-path)))
        ((not (any-conflict-p branch-b current-path))
         (solve-it (concatenate 'list (collect-non-atoms branch-b) unchecked)
                   (concatenate 'list branch-b current-path)))
        (t nil))))

  (define-expression-handler not-biconditional
    (let ((branch-a (list (second (second expression)) `(not ,(third (second expression)))))
          (branch-b (list `(not ,(second (second expression))) (third (second expression)))))
      (cond
        ((not (any-conflict-p branch-a current-path))
         (solve-it (concatenate 'list (collect-non-atoms branch-a) unchecked)
                   (concatenate 'list branch-a current-path)))
        ((not (any-conflict-p branch-b current-path))
         (solve-it (concatenate 'list (collect-non-atoms branch-b) unchecked)
                   (concatenate 'list branch-b current-path)))
        (t nil))))

Doubled Not

  (define-expression-handler double-not
    (let ((new (second (second expression))))
      (solve-it (non-atomic-add new unchecked) (cons new current-path))))

Expression Classification

  (in-package #:symbolic-sat-classifier)

  (defvar *types* '())

  (defmacro define-expression-type (type-name &rest predicate)
    (check-type type-name symbol)
    (let ((predicate-name (symbolicate type-name '-p)))
      `(progn
         (defun ,predicate-name (expression)
           ,@predicate)
         (pushnew '(,type-name . ,predicate-name) *types* :key #'first :test #'equal)
         (export ',type-name)
         ',type-name)))

  (defun expression-type-p (expression-type expression)
    (if (eq '* expression-type)
        t
        (funcall (cdr (assoc expression-type *types*))
                 expression)))

  <<not-classification>>
  <<and-classification>>
  <<or-classification>>
  <<implication-classification>>
  <<bicond-classification>>
  <<atom-classification>>

Not

  (define-expression-type sentential-not
      (and (listp expression)
         (equal (first expression) 'not)
         (= (length expression) 2)))

  (define-expression-type double-not
      (and (expression-type-p 'sentential-not expression)
         (listp (second expression))
         (expression-type-p 'sentential-not (second expression))))

And

  (define-expression-type sentential-and
      (and (listp expression)
         (equal (first expression) 'and)
         (>= (length (rest expression)) 2)))

  (define-expression-type not-and
      (and (expression-type-p 'sentential-not expression)
         (listp (second expression))
         (expression-type-p 'sentential-and (second expression))))

Or

  (define-expression-type sentential-or
      (and (listp expression)
         (equal (first expression) 'or)
         (>= (length (rest expression)) 2)))

  (define-expression-type not-or
      (and (expression-type-p 'sentential-not expression)
         (listp (second expression))
         (expression-type-p 'sentential-or (second expression))))

Implication

  (define-expression-type sentential-implication
      (and (listp expression)
         (equal 'implies (first expression))
         (= (length (rest expression)) 2)))

  (define-expression-type not-implication
      (and (expression-type-p 'sentential-not expression)
         (listp (second expression))
         (expression-type-p 'sentential-implication (second expression))))

Biconditional

  (define-expression-type sentential-biconditional
      (and (listp expression)
         (equal (first expression) 'iff)
         (= (length (rest expression)) 2)))

  (define-expression-type not-biconditional
      (and (expression-type-p 'sentential-not expression)
         (listp (second expression))
         (expression-type-p 'sentential-biconditional (second expression))))

Other

  (define-expression-type atom
      (or (symbolp expression)
         (and (expression-type-p 'sentential-not expression)
            (symbolp (second expression)))))

Utilities

  (in-package #:symbolic-sat-utils)

  <<clause-collection>>
  <<conflict-checking>>
  <<clause-adding>>

Clause Collection

  (defun collect-atoms (expr-list)
    (remove-duplicates (remove-if (lambda (expr)
                                    (not (expression-type-p 'atom expr)))
                                  expr-list)
                       :test #'equal))

  (defun collect-non-atoms (expr-list)
    (remove-duplicates (remove-if (lambda (expr)
                                    (expression-type-p 'atom expr))
                                  expr-list)
                       :test #'equal))

Conflict Checking

  (defun has-conflict-p (current path)
    (or (member `(not ,current) path :test #'equal)
       (and (expression-type-p 'sentential-not current)
          (member (second current) path :test #'equal))))

  (defun any-conflict-p (expr-list path)
    (do* ((expr (first expr-list) (first expr-list-prime))
          (expr-list-prime (rest expr-list) (rest expr-list-prime)))
         ((or (has-conflict-p expr path)
             (null expr))
          (when (not (null expr))
            t))))

Clause Adding

  (defun non-atomic-add (expr list)
    (if (not (expression-type-p 'atom expr))
        (cons expr list)
        list))

Packaging

Package Definition

  (defpackage #:symbolic-sat-common-symbols
    (:use #:cl)
    (:export #:implies
             #:iff))

  (defpackage #:symbolic-sat-classifier
    (:use #:cl
          #:symbolic-sat-common-symbols)
    (:import-from #:alexandria
                  #:symbolicate)
    (:export #:expression-type-p))

  (defpackage #:symbolic-sat-utils
    (:use #:cl
          #:symbolic-sat-common-symbols
          #:symbolic-sat-classifier)
    (:export #:collect-atoms
             #:collect-non-atoms
             #:has-conflict-p
             #:any-conflict-p
             #:non-atomic-add))

  (defpackage #:symbolic-sat
    (:use #:cl
          #:symbolic-sat-classifier
          #:symbolic-sat-common-symbols
          #:symbolic-sat-utils)
    (:import-from #:alexandria
                  #:symbolicate)
    (:export #:solve))

System Definition

  (asdf:defsystem #:symbolic-sat
    :description "A basic Symbolic SAT solver using the Tableaux (tree) method for Sentential Logic."
    :author "Samuel Flint <swflint@flintfam.org>"
    :license "GNU GPLv3 or Later"
    :depends-on (#:alexandria)
    :serial t
    :components ((:file "packages")
                 (:file "classifier")
                 (:file "utils")
                 (:file "solver")))

Bibliography   ignore

\bibliographystyle{plain} \bibliography{bibliography}