├── asd-generator-data.asd ├── .gitignore ├── circle.yml ├── testscr.ros ├── cl-sat.test.asd ├── src ├── 0-package.lisp ├── 2-class.lisp ├── 1-util.lisp ├── 4-competition.lisp ├── 3-dimacs.lisp └── 1-parse.lisp ├── .travis.yml ├── cl-sat.asd ├── competition.org ├── t ├── package.lisp ├── plaidctf-2015.lisp └── competitions.lisp └── README.org /asd-generator-data.asd: -------------------------------------------------------------------------------- 1 | 2 | ((#:src (:rest))) 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.fasl 3 | *.dx32fsl 4 | *.dx64fsl 5 | *.lx32fsl 6 | *.lx64fsl 7 | *.x86f 8 | *~ 9 | .#* 10 | *.backup 11 | solvers/2016/ 12 | solvers/2017/ 13 | solvers/2018/ 14 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | environment: 3 | PATH: ~/.roswell/bin:$PATH 4 | 5 | dependencies: 6 | pre: 7 | - curl -L https://raw.githubusercontent.com/snmsts/roswell/master/scripts/install-for-ci.sh | sh 8 | - ros install ccl-bin 9 | cache_directories: 10 | - ~/.roswell/ 11 | 12 | test: 13 | override: 14 | - ros -L sbcl-bin testscr.ros 15 | - ros -L ccl-bin testscr.ros 16 | -------------------------------------------------------------------------------- /testscr.ros: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #|-*- mode:lisp -*-|# 3 | #| 4 | exec ros -Q -- $0 "$@" 5 | |# 6 | 7 | (ql:quickload :fiveam) 8 | 9 | (defun main (&rest argv) 10 | (declare (ignorable argv)) 11 | (uiop:quit (if (handler-case 12 | (progn 13 | (ql:quickload :cl-sat.test) 14 | (eval (read-from-string "(5am:run! :cl-sat)"))) 15 | (serious-condition (c) 16 | (describe c) 17 | (uiop:quit 2))) 18 | 0 1))) 19 | -------------------------------------------------------------------------------- /cl-sat.test.asd: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of cl-sat project. 3 | Copyright (c) 2016 Masataro Asai (guicho2.71828@gmail.com) 4 | |# 5 | 6 | 7 | (defsystem cl-sat.test 8 | :author "Masataro Asai" 9 | :mailto "guicho2.71828@gmail.com" 10 | :description "Test system of cl-sat" 11 | :license "LLGPL" 12 | :depends-on (:cl-sat 13 | :fiveam) 14 | :components ((:module "t" 15 | :components 16 | ((:file "package")))) 17 | :perform (test-op :after (op c) (eval (read-from-string "(5am:run! :cl-sat)")) 18 | )) 19 | -------------------------------------------------------------------------------- /src/0-package.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of cl-sat project. 3 | Copyright (c) 2016 Masataro Asai (guicho2.71828@gmail.com) 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage cl-sat 8 | (:nicknames :sat) 9 | (:use :cl :trivia :alexandria :iterate) 10 | (:export 11 | #:solve 12 | #:sat-instance 13 | #:print-cnf 14 | #:with-temp 15 | #:*instance* 16 | #:variables 17 | #:to-nnf 18 | #:to-cnf 19 | #:to-cnf-naive 20 | #:to-cnf-tseytin 21 | #:symbolicate-form 22 | #:*verbosity* 23 | #:parse-dimacs-output 24 | #:imply 25 | #:=> 26 | #:iff 27 | #:<=> 28 | #:xor 29 | #:var 30 | #:aux 31 | #:simplify-nnf 32 | #:to-anf 33 | #:expand-extensions 34 | #:parse-assignments)) 35 | (in-package :cl-sat) 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: common-lisp 2 | sudo: false 3 | 4 | addons: 5 | apt: 6 | packages: 7 | - libc6-i386 8 | - clisp 9 | 10 | env: 11 | global: 12 | - PATH=~/.roswell/bin:$PATH 13 | - ROSWELL_INSTALL_DIR=$HOME/.roswell 14 | matrix: 15 | - LISP=sbcl-bin 16 | - LISP=ccl-bin 17 | - LISP=abcl 18 | - LISP=clisp 19 | - LISP=ecl 20 | - LISP=cmucl 21 | - LISP=alisp 22 | 23 | matrix: 24 | allow_failures: 25 | - env: LISP=clisp 26 | - env: LISP=abcl 27 | - env: LISP=ecl 28 | - env: LISP=cmucl 29 | - env: LISP=alisp 30 | 31 | install: 32 | - curl -L https://raw.githubusercontent.com/snmsts/roswell/release/scripts/install-for-ci.sh | sh 33 | 34 | cache: 35 | directories: 36 | - $HOME/.roswell 37 | - $HOME/.config/common-lisp 38 | 39 | script: 40 | - ./testscr.ros 41 | -------------------------------------------------------------------------------- /cl-sat.asd: -------------------------------------------------------------------------------- 1 | ;;;; Autogenerated ASD file for system "CL-SAT" 2 | ;;;; In order to regenerate it, run update-asdf from shell (see https://github.com/phoe-krk/asd-generator) 3 | ;;;; For those who do not have update-asdf, run `ros install asd-generator` (if you have roswell installed) 4 | ;;;; There are also an interface available from lisp: (asd-generator:regen &key im-sure) 5 | 6 | (defsystem cl-sat 7 | :version "0.1" 8 | :author "Masataro Asai" 9 | :mailto "guicho2.71828@gmail.com" 10 | :license "LLGPL" 11 | :depends-on (:trivia :alexandria :iterate 12 | :trivial-features) 13 | :serial t 14 | :components ((:file "src/0-package") 15 | (:file "src/1-parse") 16 | (:file "src/1-util") 17 | (:file "src/2-class") 18 | (:file "src/3-dimacs") 19 | (:file "src/4-competition")) 20 | :description "Common Lisp API to Boolean SAT Solvers" 21 | :in-order-to ((test-op (test-op :cl-sat.test)))) 22 | -------------------------------------------------------------------------------- /src/2-class.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-sat) 2 | 3 | (defclass sat-instance () 4 | ((cnf :reader cnf :initarg :cnf) 5 | (%variables))) 6 | 7 | (defgeneric solve (input solver-designator &rest args)) 8 | 9 | (defmethod initialize-instance ((i sat-instance) &rest args &key form cnf (converter #'to-cnf-tseytin) &allow-other-keys) 10 | (assert (not (and form cnf)) nil ) 11 | (cond 12 | ((and form cnf) 13 | (error "incompatible keywords: :form and :cnf specified at the same time")) 14 | (form 15 | (remf args :form) 16 | (apply #'call-next-method i :cnf (to-cnf form converter) args)) 17 | (t 18 | (call-next-method)))) 19 | 20 | (defvar *instance*) 21 | 22 | (defmethod solve ((*instance* sat-instance) solver &rest args &key debug &allow-other-keys) 23 | ;; Remove sat-instance initargs so they aren't passed to command line 24 | (remf args :form) 25 | (remf args :cnf) 26 | (remf args :converter) 27 | 28 | (with-temp (tmp :template "cnf.XXXXXXX" :debug debug) 29 | (with-output-to-file (s tmp :if-exists :supersede) 30 | (print-cnf *instance* s)) 31 | (apply #'solve (pathname tmp) solver args))) 32 | 33 | (defmethod solve ((i list) solver &rest args &key (converter #'to-cnf-tseytin) &allow-other-keys) 34 | (apply #'solve 35 | (make-instance 'sat-instance :form i :converter converter) 36 | solver 37 | args)) 38 | 39 | 40 | (defgeneric variables (instance)) 41 | (defmethod variables :around ((instance sat-instance)) 42 | (with-slots (%variables) instance 43 | (if (slot-boundp instance '%variables) 44 | %variables 45 | (setf %variables 46 | (coerce 47 | (call-next-method) 48 | 'simple-vector))))) 49 | 50 | (defmethod variables ((instance sat-instance)) 51 | (remove-duplicates 52 | (set-difference 53 | (flatten (cnf instance)) 54 | '(and or not)))) 55 | 56 | -------------------------------------------------------------------------------- /src/1-util.lisp: -------------------------------------------------------------------------------- 1 | (in-package :cl-sat) 2 | 3 | ;; These constants are borrowed from the tempname utility 4 | ;; of coreutils. 5 | (defparameter +random-string-size-min+ 3) 6 | (defparameter +temp-file-attempts+ (* 62 62 62)) 7 | 8 | (defmacro with-temp ((var &key directory (template "tmp.XXXXXXX") (tmpdir `(uiop:temporary-directory)) debug) &body body) 9 | "Create a temporary file, then remove the file by unwind-protect. 10 | Most arguments are analogous to mktemp. 11 | TEMPLATE should be a string that ends with one or more X's, these X's will be replaced by random characters. 12 | When DIRECTORY is non-nil, creates a directory instead. 13 | When DEBUG is non-nil, it does not remove the directory so that you can investigate what happened inside the directory. 14 | An error of type file-error is signalled if a unique file name can't be generated after a number of attempts." 15 | (declare (ignorable template tmpdir)) 16 | (once-only (template tmpdir directory debug) 17 | `(let ((,var 18 | (let* ((template-without-xs (string-right-trim "X" ,template))) 19 | (attempt-create-temp ,directory 20 | ,tmpdir 21 | template-without-xs 22 | (- (length ,template) (length template-without-xs)) 23 | +temp-file-attempts+)))) 24 | (unwind-protect 25 | (progn ,@body) 26 | (if ,debug 27 | (format t "~¬ removing ~a for debugging" ,var) 28 | (if ,directory 29 | (uiop:delete-directory-tree (make-pathname :directory (list :absolute ,var)) 30 | :if-does-not-exist :ignore 31 | :validate t) 32 | (delete-file ,var))))))) 33 | 34 | (defun attempt-create-temp (directory base-dir name-prefix random-string-size attempts) 35 | "Creates a file/directory in BASE-DIR with NAME-PREFIX as a prefix of the name and RANDOM-STRING-SIZE 36 | random base62 characters at the end. 37 | If DIRECTORY is non-nil, creates a directory. 38 | Returns the name of the created file. 39 | Signals an error if it can't generate a unique name after ATTEMPTS attempts." 40 | (when (> +random-string-size-min+ random-string-size) 41 | (error "Random string part of temporary file name isn't long enough.")) 42 | (if (<= attempts 0) 43 | (error "Couldn't create a unique temp file/folder.") 44 | (let ((path (merge-pathnames (let ((name (generate-temp-name name-prefix random-string-size))) 45 | (if directory 46 | (make-pathname :directory `(:relative ,name)) 47 | (uiop:parse-unix-namestring name))) 48 | (uiop:parse-unix-namestring base-dir)))) 49 | (if (create-nonexisting directory path) 50 | (namestring path) 51 | (attempt-create-temp directory base-dir name-prefix random-string-size (1- attempts)))))) 52 | 53 | (defun generate-temp-name (name-prefix random-string-size) 54 | "Generates a random name for a temp file/directory. 55 | NAME-PREFIX is the prefix of the name, after which RANDOM-STRING-SIZE random characters are added. " 56 | (concatenate 'string name-prefix (random-base62 random-string-size))) 57 | 58 | (defun random-base62 (n) 59 | "Returns a random base62 string with n characters." 60 | (let ((table "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789")) 61 | (coerce (loop repeat n collect (aref table (random (length table)))) 62 | 'string))) 63 | 64 | (defun create-nonexisting (directory path) 65 | "Attempts to create a file/directory, returns NIL if it exists already and T otherwise." 66 | (handler-case 67 | (if directory 68 | (multiple-value-bind (_ was-nonexisting) (ensure-directories-exist path) 69 | (declare (ignore _)) 70 | was-nonexisting) 71 | (progn 72 | (open path 73 | :direction :io 74 | :if-exists :error) 75 | t)) 76 | (file-error (_) 77 | (declare (ignore _)) 78 | nil))) 79 | 80 | (defun format1 (stream format-control first-arg &rest more-args) 81 | (apply #'format stream format-control first-arg more-args) 82 | first-arg) 83 | -------------------------------------------------------------------------------- /src/4-competition.lisp: -------------------------------------------------------------------------------- 1 | 2 | (in-package :cl-sat) 3 | 4 | (defvar *base-url* 5 | `((2016 . "https://baldur.iti.kit.edu/sat-competition-2016/solvers/") 6 | (2017 . "https://baldur.iti.kit.edu/sat-competition-2017/solvers/") 7 | (2018 . "http://sat2018.forsyte.tuwien.ac.at/solvers/") 8 | (2019 . "http://sat-race-2019.ciirc.cvut.cz/solvers/"))) 9 | 10 | (define-condition competition-setup-error (error) 11 | ((year :initarg :year) 12 | (track :initarg :track) 13 | (name :initarg :name)) 14 | (:report 15 | (lambda (c s) 16 | (print c s)))) 17 | 18 | (defmethod print-object ((c competition-setup-error) s) 19 | (print-unreadable-object (c s :type t) 20 | (with-slots (year track name) c 21 | (format s "~a ~a ~a" year track name)))) 22 | 23 | (define-condition download-error (competition-setup-error) ()) 24 | (define-condition unzip-error (competition-setup-error) ()) 25 | (define-condition build-error (competition-setup-error) ()) 26 | (define-condition chmod-error (competition-setup-error) ()) 27 | 28 | (defun download-solver (year track name) 29 | (check-type year fixnum) 30 | (let* ((dir (namestring (asdf:system-relative-pathname :cl-sat (format nil "solvers/~a/~a/" year track)))) 31 | (zip (namestring (merge-pathnames (format nil "~a.zip" name) dir))) 32 | (runner (namestring (merge-pathnames (format nil "~a/bin/starexec_run_default" name) dir))) 33 | (bin (namestring (merge-pathnames (format nil "~a/bin/" name) dir))) 34 | (home (namestring (merge-pathnames (format nil "~a/" name) dir)))) 35 | (ensure-directories-exist zip) 36 | (unless (probe-file runner) 37 | (alexandria:unwind-protect-case () 38 | (progn 39 | (handler-case 40 | (uiop:run-program `("wget" ,(format nil "~a/~a/~a.zip" (cdr (assoc year *base-url*)) track name) "-O" ,zip) 41 | :output t :error t) 42 | (uiop:subprocess-error () 43 | (error 'download-error :year year :track track :name name))) 44 | (handler-case 45 | (uiop:run-program `("sh" "-c" ,(format nil "cd ~a; unzip ~a.zip" dir name)) 46 | :output t :error t) 47 | (uiop:subprocess-error () 48 | (error 'unzip-error :year year :track track :name name))) 49 | (handler-case 50 | (uiop:run-program `("sh" "-c" ,(format nil "cd ~a; chmod +x starexec_build build/*; MAKEFLAGS=\"-j 4\" ./starexec_build" home)) 51 | :output t :error t) 52 | (uiop:subprocess-error () 53 | (error 'build-error :year year :track track :name name)))) 54 | (:abort 55 | (format *error-output* "~&Aborting, cleaning up~%") 56 | (uiop:run-program `("rm" "-rv" ,zip ,home) 57 | :output t :error t :ignore-error-status t)))) 58 | (unless (probe-file runner) 59 | (error "Runner script ~a is missing in ~a !" runner bin)) 60 | (handler-case 61 | (uiop:run-program `("sh" "-c" ,(format nil "chmod +x ~a/*" bin)) 62 | :output t :error-output t) 63 | (uiop:subprocess-error () 64 | (error 'chmod-error :year year :track track :name name))) 65 | (values runner bin))) 66 | 67 | (defmethod solve ((input pathname) (competition (eql :competition)) &rest options &key debug year track name &allow-other-keys) 68 | (remf options :debug) 69 | (remf options :solver) 70 | 71 | (with-temp (dir :directory t :template "glucose.XXXXXXXX" :debug debug) 72 | (multiple-value-bind (runner bin) (download-solver year track name) 73 | (let* ((command (format nil "cd ~a ; bash ~a ~a ~a" 74 | bin 75 | runner 76 | (namestring input) 77 | (namestring dir))) 78 | (result (format nil "~a/result" dir))) 79 | (format t "~&; ~a" command) 80 | (multiple-value-match (uiop:run-program command 81 | :output result 82 | :error-output t 83 | :ignore-error-status t) 84 | ((_ _ 0) 85 | ;; indeterminite 86 | (values nil nil nil)) 87 | ((_ _ 10) 88 | ;; sat 89 | (parse-dimacs-output result *instance*)) 90 | ((_ _ 20) 91 | ;; unsat 92 | (values nil nil t))))))) 93 | -------------------------------------------------------------------------------- /competition.org: -------------------------------------------------------------------------------- 1 | 2 | + List of competition solvers that compiled: 100 3 | + List of competition solvers that worked (produced a conforming output): 52 4 | 5 | #+begin_src 6 | solvers/2016/agile/CHBR_glucose_agile.zip 7 | solvers/2016/agile/CHBR_glucose_tuned_agile.zip 8 | solvers/2016/agile/MapleGlucose.zip 9 | solvers/2016/agile/abcdSAT_drup.zip 10 | solvers/2016/agile/glucosePLE.zip 11 | solvers/2016/agile/glucose_hack_kiel_newScript.zip 12 | solvers/2016/agile/glue_alt.zip 13 | solvers/2016/agile/gulch-agile.zip 14 | solvers/2016/agile/gulch-once.zip 15 | solvers/2016/agile/tb_glucose_agile.zip 16 | solvers/2016/agile/tc_glucose_agile.zip 17 | solvers/2016/main/CHBR_glucose.zip 18 | solvers/2016/main/CHBR_glucose_tuned.zip 19 | solvers/2016/main/Glucose_nbSat.zip 20 | solvers/2016/main/MapleGlucose.zip 21 | solvers/2016/main/Scavel_SAT.zip 22 | solvers/2016/main/abcdSAT_drup.zip 23 | solvers/2016/main/drat-trim.zip 24 | solvers/2016/main/glucose.zip 25 | solvers/2016/main/glucosePLE.zip 26 | solvers/2016/main/glucose_hack_kiel_newScript.zip 27 | solvers/2016/main/glue_alt.zip 28 | solvers/2016/main/gulch.zip 29 | solvers/2016/main/tb_glucose.zip 30 | solvers/2016/main/tc_glucose.zip 31 | solvers/2016/random/CSCCSat.zip 32 | solvers/2016/random/DCCAlm.zip 33 | solvers/2016/random/multi-sat.zip 34 | solvers/2016/random/polypower1.0.zip 35 | solvers/2016/random/polypower2.0.zip 36 | solvers/2016/random/stocBCD.zip 37 | solvers/2017/agile/abcdsat_a17.zip 38 | solvers/2017/agile/abcdsat_r17.zip 39 | solvers/2017/agile/bs_glucose.zip 40 | solvers/2017/agile/cadical-sc17-agile.zip 41 | solvers/2017/agile/cadical-sc17-noproof.zip 42 | solvers/2017/agile/glu_vc.zip 43 | solvers/2017/agile/glucose-4.1.zip 44 | solvers/2017/agile/lingeling-bbe.zip 45 | solvers/2017/agile/tch_glucose1.zip 46 | solvers/2017/agile/tch_glucose2.zip 47 | solvers/2017/agile/tch_glucose3.zip 48 | solvers/2017/agile/yalsat-03s.zip 49 | solvers/2017/main/COMiniSatPS_Pulsar_drup.zip 50 | solvers/2017/main/GHackCOMSPS_drup.zip 51 | solvers/2017/main/MapleCOMSPS_CHB_VSIDS_drup.zip 52 | solvers/2017/main/MapleCOMSPS_LRB_VSIDS_2_drup.zip 53 | solvers/2017/main/MapleCOMSPS_LRB_VSIDS_drup.zip 54 | solvers/2017/main/MapleLRB_LCM.zip 55 | solvers/2017/main/MapleLRB_LCMoccRestart.zip 56 | solvers/2017/main/Maple_LCM.zip 57 | solvers/2017/main/Maple_LCM_Dist.zip 58 | solvers/2017/main/abcdsat_r17.zip 59 | solvers/2017/main/bs_glucose.zip 60 | solvers/2017/main/cadical-sc17-agile-proof.zip 61 | solvers/2017/main/cadical-sc17-proof.zip 62 | solvers/2017/main/glu_vc.zip 63 | solvers/2017/main/glucose-3.0+width.zip 64 | solvers/2017/main/glucose-4.1.zip 65 | solvers/2017/main/lingeling-bbe.zip 66 | solvers/2017/main/tch_glucose1.zip 67 | solvers/2017/main/tch_glucose2.zip 68 | solvers/2017/main/tch_glucose3.zip 69 | solvers/2017/random/Score2SAT.zip 70 | solvers/2017/random/tch_glucose3.zip 71 | solvers/2017/random/yalsat-03s.zip 72 | solvers/2018/main_and_glucose_hack/COMiniSatPS_Pulsar_drup.zip 73 | solvers/2018/main_and_glucose_hack/GHackCOMSPS_drup.zip 74 | solvers/2018/main_and_glucose_hack/Glucose_Hack_Kiel_fastBVE.zip 75 | solvers/2018/main_and_glucose_hack/Lingeling.zip 76 | solvers/2018/main_and_glucose_hack/MapleCOMSPS_CHB_VSIDS_drup.zip 77 | solvers/2018/main_and_glucose_hack/MapleCOMSPS_LRB_VSIDS_drup.zip 78 | solvers/2018/main_and_glucose_hack/MapleLCMDistChronoBT.zip 79 | solvers/2018/main_and_glucose_hack/Maple_CM.zip 80 | solvers/2018/main_and_glucose_hack/Maple_CM_Dist.zip 81 | solvers/2018/main_and_glucose_hack/Maple_CM_ordUIP+.zip 82 | solvers/2018/main_and_glucose_hack/Maple_CM_ordUIP.zip 83 | solvers/2018/main_and_glucose_hack/Maple_LCM+BCrestart.zip 84 | solvers/2018/main_and_glucose_hack/Maple_LCM+BCrestart_M1.zip 85 | solvers/2018/main_and_glucose_hack/Maple_LCM_M1.zip 86 | solvers/2018/main_and_glucose_hack/Minisat-v2.2.0-106-ge2dd095.zip 87 | solvers/2018/main_and_glucose_hack/YalSAT.zip 88 | solvers/2018/main_and_glucose_hack/abcdsat_r18.zip 89 | solvers/2018/main_and_glucose_hack/expGlucose.zip 90 | solvers/2018/main_and_glucose_hack/expMC_LRB_VSIDS_Switch.zip 91 | solvers/2018/main_and_glucose_hack/expMC_LRB_VSIDS_Switch_2500.zip 92 | solvers/2018/main_and_glucose_hack/expMC_VSIDS_LRB_Switch_2500.zip 93 | solvers/2018/main_and_glucose_hack/gluHack.zip 94 | solvers/2018/main_and_glucose_hack/glu_mix.zip 95 | solvers/2018/main_and_glucose_hack/glucose-3.0D-patched.zip 96 | solvers/2018/main_and_glucose_hack/glucose-3.0_PADC_10.zip 97 | solvers/2018/main_and_glucose_hack/glucose-3.0_PADC_3.zip 98 | solvers/2018/main_and_glucose_hack/glucose3.0.zip 99 | solvers/2018/main_and_glucose_hack/inIDGlucose.zip 100 | solvers/2018/main_and_glucose_hack/smallsat.zip 101 | solvers/2018/random/CPSparrow.zip 102 | solvers/2018/random/ReasonLS.zip 103 | solvers/2018/random/YalSAT.zip 104 | solvers/2018/random/gluHack.zip 105 | solvers/2018/random/glucose-3.0_PADC_10_NoDRUP.zip 106 | solvers/2018/random/glucose-3.0_PADC_3_NoDRUP.zip 107 | #+end_src 108 | -------------------------------------------------------------------------------- /t/package.lisp: -------------------------------------------------------------------------------- 1 | #| 2 | This file is a part of cl-sat project. 3 | Copyright (c) 2016 Masataro Asai (guicho2.71828@gmail.com) 4 | |# 5 | 6 | (in-package :cl-user) 7 | (defpackage :cl-sat.test 8 | (:use :cl 9 | :cl-sat 10 | :fiveam 11 | :trivia :alexandria :iterate)) 12 | (in-package :cl-sat.test) 13 | 14 | (defun s= (a b) 15 | "equivalence between two logical formula" 16 | (match* (a b) 17 | (((list* op1 rest1) 18 | (list* op2 rest2)) 19 | (and (eq op1 op2) 20 | (set-equal rest1 rest2 :test 's=))) 21 | (((symbol) 22 | (symbol)) 23 | (eq a b)))) 24 | 25 | (def-suite :cl-sat) 26 | (in-suite :cl-sat) 27 | 28 | ;; run test with (run! test-name) 29 | 30 | (test symbolicate-form 31 | 32 | (is (s= '(and (or a (not b) c) d) 33 | (symbolicate-form 34 | '(and (or a !b c) d)))) 35 | 36 | (is (s= '(and (or a cl-sat.variables::V1 c) d) 37 | (symbolicate-form 38 | '(and (or a 1 c) d)))) 39 | 40 | (is (s= '(and (or a (not cl-sat.variables::V1) c) d) 41 | (symbolicate-form 42 | '(and (or a -1 c) d)))) 43 | 44 | (signals error (symbolicate-form '!!!!!)) 45 | 46 | (is (s= 'a (symbolicate-form 'a))) 47 | (is (s= '(not a) (symbolicate-form '!a))) 48 | (is (s= 'a (symbolicate-form '!!a))) 49 | (is (s= '(not a) (symbolicate-form '!!!a))) 50 | 51 | ;; https://www.satcompetition.org/2009/format-benchmarks2009.html 52 | ;; 0 is not allowed as a literal 53 | (signals error (symbolicate-form 0))) 54 | 55 | 56 | 57 | (test to-nnf 58 | 59 | (is (s= (to-nnf 60 | '(not (or a b c))) 61 | '(and (not a) (not b) (not c)))) 62 | 63 | (is (s= (to-nnf 64 | '(not (and a b c))) 65 | '(or (not a) (not b) (not c))))) 66 | 67 | 68 | 69 | (test to-cnf-naive 70 | 71 | (is (s= '(and a b c) 72 | (to-cnf-naive '(and a b c)))) 73 | 74 | (is (s= '(or a b c) 75 | (to-cnf-naive '(or a b c)))) 76 | 77 | (is (s= '(and (or a (not b) c) d) 78 | (to-cnf-naive 79 | (to-nnf 80 | (symbolicate-form 81 | '(and (or a !b c) d)))))) 82 | 83 | (is (s= 'a (to-cnf-naive '(and (and (and a)))))) 84 | 85 | 86 | (is (s= '(or (not a) (not b)) 87 | (to-cnf-naive 88 | (to-nnf '(not (and a b)))))) 89 | 90 | (is (s= '(and (not a) (not b)) 91 | (to-cnf-naive 92 | (to-nnf '(not (or a b)))))) 93 | 94 | (is (s= '(and (not a) (not b)) 95 | (to-cnf-naive 96 | (to-nnf '(not (or a b)))))) 97 | 98 | (is (s= '(and (not a) (not b)) 99 | (to-cnf-naive 100 | (to-nnf '(not (or a b)))))) 101 | 102 | (is (s= '(and p (or q a r) (or q b r) s) 103 | (to-cnf-naive 104 | '(and p (or q (and a b) r) s)))) 105 | 106 | (is (s= '(and (or a c) (or b c)) 107 | (to-cnf-naive 108 | '(or (and a b) c)))) 109 | 110 | (is (s= '(and (or a c) (or b c) (or a d) (or b d)) 111 | (to-cnf-naive 112 | '(or (and a b) (and c d))))) 113 | 114 | (is (s= '(and 115 | (or a c e) (or b c e) (or a d e) (or b d e) 116 | (or a c f) (or b c f) (or a d f) (or b d f)) 117 | (to-cnf-naive 118 | '(or (and a b) (and c d) (and e f))))) 119 | 120 | (is (s= '(and 121 | (or a c) (or a d) (or b c) (or b d) 122 | (or e g) (or e h) (or f g) (or f h)) 123 | (to-cnf-naive 124 | '(and 125 | (or (and a b) (and c d)) 126 | (or (and e f) (and g h)))))) 127 | 128 | ;; checking (and) and (or) 129 | 130 | (is (s= '(and a c) 131 | (to-cnf-naive '(and a (or c (and a (or) b)))))) 132 | ;; ^^^^^^^^^^^^^^ == ⊥ 133 | 134 | (is (s= (to-cnf-naive '(and a (or c (and a b)))) 135 | (to-cnf-naive '(and a (or c (and a (and) b)))))) 136 | 137 | (is (s= '(and a (or c a) (or c b)) 138 | (to-cnf-naive '(and a (or c (and a (and) b)))))) 139 | 140 | (is (s= '(and a (or c a) (or c b)) 141 | (to-cnf-naive '(and a (or c (and a (and) b)))))) 142 | 143 | (is (s= '(or) 144 | (to-cnf-naive '(or)))) 145 | (is (s= '(or) 146 | (to-cnf-naive '(and (or))))) 147 | 148 | (is (s= '(and) 149 | (to-cnf-naive '(or (and))))) 150 | (is (s= '(and) 151 | (to-cnf-naive '(and (and)))))) 152 | 153 | #+nil 154 | (test to-cnf-tseytin 155 | ;; trying to test to-cnf-tseytin programatically, but it turns out not trivial 156 | 157 | (let* ((form1 ;; Naive methods convert this form into an exponential CNF 158 | '(or (and a b) (and c d) (and e f) (and g h) (and i j))) 159 | (vars1 '(a b c d e f g h i j)) 160 | (form2 (to-cnf form1)) 161 | (vars2 (remove-duplicates 162 | (set-difference 163 | (flatten form2) 164 | '(and or not)))) 165 | (f (compile nil `(lambda ,vars2 ,form1))) 166 | (g (compile nil `(lambda ,vars2 ,form2)))) 167 | 168 | (iter (repeat 100) 169 | (for args = 170 | (iter (for v in vars2) 171 | (collect (random-elt '(nil t))))) 172 | (is (eq (apply f args) 173 | (apply g args)) 174 | "~@{~a:~a~%~}" 175 | 'form1 form1 176 | 'form2 form2 177 | 'vars1 vars1 178 | 'args args 179 | '(apply f args) (apply f args) 180 | '(apply g args) (apply g args) 181 | )))) 182 | 183 | (test instantiate 184 | (finishes (make-instance 'sat-instance :form '(and a b c))) 185 | (finishes (make-instance 'sat-instance :form '(or a b c))) 186 | (finishes (make-instance 'sat-instance :form '(and (or a !b c) d))) 187 | (finishes (make-instance 'sat-instance :form '(and (and (and a))))) 188 | (finishes (make-instance 'sat-instance :form '(not (and a b)))) 189 | (finishes (make-instance 'sat-instance :form '(not (or a b))))) 190 | 191 | (test print-cnf 192 | (fresh-line) 193 | (finishes (print-cnf (make-instance 'sat-instance :form '(and a b c)))) 194 | (finishes (print-cnf (make-instance 'sat-instance :form '(or a b c)))) 195 | (finishes (print-cnf (make-instance 'sat-instance :form '(and (or a !b c) d)))) 196 | (finishes (print-cnf (make-instance 'sat-instance :form '(and (and (and a)))))) 197 | (finishes (print-cnf (make-instance 'sat-instance :form '(not (and a b))))) 198 | (finishes (print-cnf (make-instance 'sat-instance :form '(not (or a b))))) 199 | (finishes (print-cnf (make-instance 'sat-instance :form '(and a !a))))) 200 | 201 | (test converter 202 | (is (solve '(and a (not a)) :minisat :converter (constantly '(and a))))) 203 | 204 | -------------------------------------------------------------------------------- /t/plaidctf-2015.lisp: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | #|-*- mode:lisp -*-|# 3 | #| 4 | exec ros -Q -- $0 "$@" 5 | |# 6 | 7 | (ql:register-local-projects) 8 | (ql:quickload :cl-ppcre) 9 | (ql:quickload :iterate) 10 | (ql:quickload :alexandria) 11 | 12 | (ql:quickload :cl-sat) 13 | (ql:quickload :cl-sat.glucose) 14 | 15 | 16 | (defpackage :plaid 17 | (:use :cl :iterate :alexandria)) 18 | 19 | (in-package :plaid) 20 | 21 | ;; Get this file from 22 | ;; https://raw.githubusercontent.com/ctfs/write-ups-2015/master/plaidctf-2015/reversing/re-gex/regex_57f2cf49f6a354b4e8896c57a4e3c973.txt 23 | 24 | (defvar *path* (asdf:system-relative-pathname :cl-sat "t/regex_57f2cf49f6a354b4e8896c57a4e3c973.txt")) 25 | 26 | (unless (probe-file *path*) 27 | (uiop:run-program 28 | `("curl" 29 | "https://raw.githubusercontent.com/ctfs/write-ups-2015/master/plaidctf-2015/reversing/re-gex/regex_57f2cf49f6a354b4e8896c57a4e3c973.txt") 30 | :output (asdf:system-relative-pathname :cl-sat "t/regex_57f2cf49f6a354b4e8896c57a4e3c973.txt"))) 31 | 32 | (defparameter *regex* (alexandria:read-file-into-string *path*)) 33 | 34 | (defparameter *syntax-tree* (cl-ppcre:parse-string *regex*)) 35 | 36 | 37 | (defconstant +len+ 171) 38 | (defparameter +chars+ "plaidctf") 39 | 40 | 41 | ;; Prepare data structure 42 | (defun sym-for-char (ch i) 43 | (intern (format nil "~a~d" (char-upcase ch) i) 44 | :plaid)) 45 | 46 | (defun syms-for-char (i) 47 | (iter:iter (for ch in-vector +chars+) 48 | (collect (sym-for-char ch i)))) 49 | 50 | (defun syms-for-chars (i chars) 51 | (iter:iter (for ch in chars) 52 | (collect (sym-for-char ch i)))) 53 | 54 | #+(or) 55 | (syms-for-char 10) 56 | 57 | (defun exactly-one-of (syms) 58 | (cons 59 | 'or 60 | (iter:iter (for not-negated in syms) 61 | (for with-1-not-negated = 62 | (mapcar (lambda (i) 63 | (if (eql i not-negated) 64 | i 65 | `(not ,i))) 66 | syms)) 67 | (collect (cons 'and with-1-not-negated))))) 68 | 69 | #+(or) 70 | (exactly-one-of (syms-for-char 10)) 71 | 72 | 73 | 74 | (defun re-clauses-to-sat-vars (pos clauses) 75 | (let ((cls (first clauses))) 76 | (trivia:match cls 77 | ((list* :char-class _) 78 | (cons (syms-for-chars pos (cdr cls)) 79 | (re-clauses-to-sat-vars (1+ pos) 80 | (cdr clauses)))) 81 | (:everything 82 | (re-clauses-to-sat-vars (1+ pos) 83 | (cdr clauses))) 84 | ((list :greedy-repetition from to :everything) 85 | (if (eql from to) 86 | (re-clauses-to-sat-vars (+ pos from) 87 | (cdr clauses))))))) 88 | 89 | 90 | (defun syntax-tree-to-sat-expr-original () 91 | (concatenate 92 | 'list 93 | ;'(and) 94 | (iter:iter (for i below +len+) 95 | (for syms = (syms-for-char i)) 96 | (collect (exactly-one-of syms))) 97 | (iter:iter outer 98 | #+(or) 99 | (repeat 10) 100 | (for desc in (second (third *syntax-tree*))) 101 | (if (and (consp desc) 102 | (eq :sequence (first desc))) 103 | (let ((char-sets (re-clauses-to-sat-vars 0 (rest desc)))) 104 | (collect 105 | (iter:iterate 106 | (with now-positive = ()) 107 | (for negative in char-sets) 108 | (for clause = `(and (not (or ,@ negative)) 109 | ,@ (mapcar 110 | (lambda (chars) 111 | (cons 'or chars)) 112 | now-positive))) 113 | (collect clause into clauses) 114 | (push negative now-positive) 115 | (finally 116 | (return 117 | (cons 'or clauses)))))))))) 118 | 119 | 120 | (defun syntax-tree-to-sat-expr () 121 | (concatenate 122 | 'list 123 | '(and) 124 | (iter:iter (for i below +len+) 125 | (for syms = (syms-for-char i)) 126 | (collect (exactly-one-of syms))) 127 | (iter:iter outer 128 | #+(or) 129 | (repeat 10) 130 | (for desc in (second (third *syntax-tree*))) 131 | (if (and (consp desc) 132 | (eq :sequence (first desc))) 133 | (let* ((char-sets (re-clauses-to-sat-vars 0 (rest desc))) 134 | (this-re-clause 135 | (iter:iterate 136 | (with now-positive = ()) 137 | (for negative in char-sets) 138 | (for clause = `(and ,@ (mapcar 139 | (lambda (clause) 140 | (list 'not clause)) 141 | negative) 142 | ,@ (mapcar 143 | (lambda (chars) 144 | (cons 'or chars)) 145 | now-positive))) 146 | (collect clause into clauses) 147 | (push negative now-positive) 148 | (finally 149 | (return 150 | (if clauses 151 | (cons 'or clauses))))))) 152 | (if this-re-clause 153 | (collect this-re-clause))))))) 154 | 155 | 156 | (defparameter *sat-expression* 157 | (syntax-tree-to-sat-expr)) 158 | 159 | (defparameter *sat-expression-cnf* (time (cl-sat:to-cnf plaid::*sat-expression*))) 160 | 161 | (print :original-number-of-variables) 162 | (print 163 | (length 164 | (set-difference 165 | (remove-duplicates (flatten plaid::*sat-expression*)) 166 | '(or and not)))) 167 | 168 | (print :original+aux-variables) 169 | (print 170 | (length 171 | (set-difference 172 | (remove-duplicates (flatten *sat-expression-cnf*)) 173 | '(or and not)))) 174 | 175 | (print :cnf-clauses) 176 | (print 177 | (length 178 | *sat-expression-cnf*)) 179 | (finish-output) 180 | 181 | #+(or) 182 | (defparameter *cnf* (sat:to-cnf *sat-expression*)) 183 | 184 | #+(or) 185 | (defparameter *result* (sat:solve *sat-expression* 186 | :minisat)) 187 | 188 | #+(or) 189 | (defparameter *result* (sat:solve *sat-expression* 190 | :glucose)) 191 | 192 | (defun main (&rest argv) 193 | (declare (ignorable argv)) 194 | (time (print (sat:solve *sat-expression* :glucose)))) 195 | 196 | 197 | -------------------------------------------------------------------------------- /README.org: -------------------------------------------------------------------------------- 1 | 2 | * CL-SAT - Common Lisp API to Boolean SAT Solvers 3 | 4 | [[https://travis-ci.org/cl-model-languages/cl-sat][https://travis-ci.org/cl-model-languages/cl-sat.svg?branch=master]] 5 | 6 | This library provides a simple S-exp -> CNF translator and an API to Boolean SAT solvers. 7 | 8 | It does not provide actual implementation to each solver instance by itself. 9 | Currently there are two implementations. Consult the following: 10 | 11 | + https://github.com/guicho271828/cl-sat.minisat 12 | + https://github.com/guicho271828/cl-sat.glucose 13 | 14 | *NEWS* 15 | 16 | + 2018/12/24 :: Implemented Tseytin's transformation for the input logic formula. 17 | The input S-exp can be an arbitrary logical formula that is not necessarily a CNF. 18 | + 2019/1/8 :: Implemented a =:COMPETITION= keyword for the generic function 19 | =SOLVE=, which accepts =:year=, =:track=, =:name= argument 20 | specifying the solver that particupated in SAT Competition 2016,2017,2018. 21 | For example, you can run 22 | =(solve :competition