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}).
[1/2]
The solver itself is composed of two components, the driver and expression handlers. The driver is used to dispatch based on the type of expression for expressions that have yet to be handled, and expression handlers actually deal with the expressions
(in-package #:symbolic-sat) <<expression-handlers>> <<driver>>
CLOSED: [2018-03-18 Sun 10:14]
The driver itself is a simple shim on top of the handler dispatch facility. As such, it takes an expression, adds the expression to a new list (the unhandled expressions list), and creates another list (the current path).
(defun solve (expression) (dispatch-solution (non-atomic-add expression (list)) (list expression)))
[0/7]
This is organized as follows: handler storage, a recipe for handler definition, handler dispatch and the the definition of the handlers themselves.
(defvar *handlers* '()) <<defining-handlers>> <<dispatching-handlers>> <<and-handlers>> <<or-handlers>> <<implication-handlers>> <<biconditional-handlers>> <<doubled-not-handler>>
(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-solution (unchecked current-path) (if (null unchecked) (collect-atoms current-path) (let ((expression (first unchecked)) (unchecked (rest unchecked))) (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)))))))
(define-expression-handler sentential-and (when (not (any-conflict-p (rest expression) current-path)) (dispatch-solution (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 (dispatch-solution (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 (dispatch-solution (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)) (dispatch-solution (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)) (dispatch-solution (non-atomic-add branch-a unchecked) (cons branch-a current-path))) ((not (has-conflict-p branch-b current-path)) (dispatch-solution (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)) (dispatch-solution (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)) (dispatch-solution (concatenate 'list (collect-non-atoms branch-a) unchecked) (concatenate 'list branch-a current-path))) ((not (any-conflict-p branch-b current-path)) (dispatch-solution (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)) (dispatch-solution (concatenate 'list (collect-non-atoms branch-a) unchecked) (concatenate 'list branch-a current-path))) ((not (any-conflict-p branch-b current-path)) (dispatch-solution (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)))) (dispatch-solution (non-atomic-add new unchecked) (cons new current-path))))
[0/8]
(in-package #:symbolic-sat-classifier) (defvar *types* '()) <<expression-type-definition>> <<expression-type-checking>> <<not-classification>> <<and-classification>> <<or-classification>> <<implication-classification>> <<bicond-classification>> <<atom-classification>>
(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)))
(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}