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:
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.
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.
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.
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}).
(in-package #:symbolic-sat) <<expression-handlers>> <<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)))
(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>>
(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)))))))
(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)))))
(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)))))
(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))))
(define-expression-handler double-not
(let ((new (second (second expression))))
(solve-it (non-atomic-add new unchecked) (cons new current-path))))
(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>>
(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))))
(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))))
(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))))
(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))))
(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))))
(define-expression-type atom
(or (symbolp expression)
(and (expression-type-p 'sentential-not expression)
(symbolp (second expression)))))
(in-package #:symbolic-sat-utils) <<clause-collection>> <<conflict-checking>> <<clause-adding>>
(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))
(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))))
(defun non-atomic-add (expr list)
(if (not (expression-type-p 'atom expr))
(cons expr list)
list))
(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))
(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")))
\bibliographystyle{plain} \bibliography{bibliography}