#| Copyright © 2021 by Pete Manolios CS 4820 Fall 2021 Homework 7. Due: 11/22 (Midnight) For this assignment, work in groups of 2-3. Send me exactly one solution per team and make sure to follow the submission instructions on the course Web page. In particular, make sure that the subject of your email submission is "CS 4820 HWK 7". The group members are: ... (put the names of the group members here) To make sure that we are all on the same page, build the latest version of ACL2s, as per HWK1. You are going to be using SBCL, which you already have, due to the build process in Next, install quicklisp. See https://www.quicklisp.org/beta/. To make sure everything is OK with quicklisp and to initialize it, start sbcl and issue the following commands (load "~/quicklisp/setup.lisp") (ql:quickload :trivia) (ql:quickload :cl-ppcre) (ql:quickload :let-plus) (setf ppcre:*allow-named-registers* t) (exit) Next, clone the ACL2s interface repository: (https) https://gitlab.com/acl2s/external-tool-support/interface.git (ssh) git@gitlab.com:acl2s/external-tool-support/interface.git This repository includes scripts for interfacing with ACL2s from lisp. Put this directory in the ...books/acl2s/ of your ACL2 repository, or use a symbolic link. Now, certify the books, by going to ...books/acl2s/interface and typing "cert.pl -j 4 top" Look at the documentation for cert.pl. If cert.pl isn't in your path, then use "...books/build/cert.pl -j 4 top" The "-j 4" option indicates that you want to run up to 4 instances of ACL2 in parallel. Set this number to the number of cores you have. As mentioned at the beginning of the semester, some of the code we will write is in Lisp. You can find the common lisp manual online in various formats by searching for "common lisp manual." Other references that you might find useful and are available online include - Common Lisp: A Gentle Introduction to Symbolic Computation by David Touretzky - ANSI Common Lisp by Paul Graham |# (in-package "ACL2S") (include-book "acl2s/interface/top" :dir :system) #| The interface books provide us with the ability to call the theorem prover within lisp, which will be useful in checking your code. Here are some examples you can try. acl2s-compute is the form you use when you are using ACL2s to compute something, e.g., running a function on some input. (acl2s-compute '(+ 1 2)) acl2s-query is the form you use when you are querying ACL2s, e.g., a property without a name. If the property has a name, then that is not a query, but an event and you have to use acl2s-event. (acl2s-query 'acl2s::(property (p :all q :all) (iff (=> p q) (v (! p) q)))) acl2s-arity is a function that determines if f is a function defined in ACL2s and if so, its arity (number of arguments). If f is not a function, then the arity is nil. Otherwise, the arity is a natural number. Note that f can't be a macro. (acl2s-arity 'acl2s::app) ; is nil since app is a macro (acl2s-arity 'acl2s::bin-app) ; is 2 |# (set-ignore-ok t) ;; This gets us to raw lisp. :q #| Next, we need to load some software libraries using quicklisp. For example, the trivia library provides pattern matching capabilities. Search for "match" below. Links to online documentation for the software libraries are provided below. |# (load "~/quicklisp/setup.lisp") ; See https://lispcookbook.github.io/cl-cookbook/pattern_matching.html (ql:quickload :trivia) ; See https://www.quicklisp.org/beta/UNOFFICIAL/docs/cl-ppcre/doc/index.html (ql:quickload :cl-ppcre) ; See https://github.com/sharplispers/let-plus (ql:quickload :let-plus) (setf ppcre:*allow-named-registers* t) #| We now define our own package. |# (defpackage :tp (:use :cl :trivia :ppcre :let-plus :acl2 :acl2s)) (in-package :tp) ;; We import acl2s-compute and acl2s-query into our package. (import 'acl2s::(acl2s-compute acl2s-query acl2s-arity)) #| We have a list of the propositional operators and information about them. :arity can be a positive integer or - (meaning arbitrary arity) If :arity is -, there must be an identity and the function must be associative and commutative. If :identity is non-nil, then the operator has the indicated identity. An operator is idempotent iff :idem is t. If :sink is not -, then it must be the case that (op ... sink ...) = sink, e.g., (and ... nil ...) = nil. FYI: it is common for global variables to be enclosed in *'s. |# (defparameter *p-ops* '((and :arity - :identity t :idem t :sink nil) (or :arity - :identity nil :idem t :sink t ) (not :arity 1 :identity - :idem nil :sink - ) (implies :arity 2 :identity - :idem nil :sink - ) (iff :arity - :identity t :idem nil :sink - ) (if :arity 3 :identity - :idem nil :sink - ))) #| mapcar is like map. See the common lisp manual. In general if you have questions about lisp, ask on Piazza. |# (defparameter *p-funs* (mapcar #'car *p-ops*)) (defparameter *fo-quantifiers* '(forall exists)) (defparameter *booleans* '(t nil)) (defparameter *fo-keywords* (append *p-funs* *booleans* *fo-quantifiers*)) #| See the definition of member in the common lisp manual. Notice that there are different types of equality, including =, eql, eq, equal and equals. We need to be careful, so we'll just use equal and we'll define functions that are similar to the ACL2s functions we're familiar with. |# (defun in (a x) (member a x :test #'equal)) (defmacro len (l) `(length ,l)) (defun p-funp (x) (in x *p-funs*)) (defun get-alist (k l) (cdr (assoc k l :test #'equal))) (defun get-key (k l) (cadr (member k l :test #'equal))) (defun remove-dups (l) (remove-duplicates l :test #'equal)) (defmacro == (x y) `(equal ,x ,y)) (defmacro != (x y) `(not (equal ,x ,y))) (defun booleanp (x) (in x *booleans*)) (defun no-dupsp (l) (or (endp l) (and (not (in (car l) (cdr l))) (no-dupsp (cdr l))))) (defun pfun-argsp (pop args) (and (p-funp pop) (let ((arity (get-key :arity (get-alist pop *p-ops*)))) (and (or (== arity '-) (== (len args) arity)) (every #'p-formulap args))))) #| Next we have some utilities for converting propositional formulas to ACL2s formulas. |# (defun to-acl2s (f) (match f ((type symbol) (intern (symbol-name f) "ACL2S")) ((list 'iff) t) ((list 'iff p) (to-acl2s p)) ((list* 'iff args) (acl2s::xxxjoin 'acl2s::iff (mapcar #'to-acl2s args))) ((cons x xs) (mapcar #'to-acl2s f)) (_ f))) #| A FO term is either a constant symbol: a symbol whose name starts with "C" and is optionally followed by a natural number with no leading 0's, e.g., c0, c1, c101, c, etc are constant symbols, but c00, c0101, c01, etc are not. Notice that (gentemp "C") will create a new constant. Notice that symbol names are case insensitive, so C1 is the same as c1. quoted constant: anything of the form (quote object) for any object constant object: a rational, boolean, string, character or keyword variable: a symbol whose name starts with "X", "Y", "Z", "W", "U" or "V" and is optionally followed by a natural number with no leading 0's (see constant symbol). Notice that (gentemp "X") etc will create a new variable. function application: (f t1 ... tn), where f is a non-constant/non-variable/non-boolean/non-keyword symbol. The arity of f is n and every occurrence of (f ...) in a term or formula has to have arity n. Also, if f is a defined function in ACL2s, its arity has to match what ACL2s expects. We allow functions of 0-arity. |# (defun char-nat-symbolp (s chars) (and (symbolp s) (let ((name (symbol-name s))) (and (<= 1 (len name)) (in (char name 0) chars) (or (== 1 (len name)) (let ((i (parse-integer name :start 1 :junk-allowed t))) (and (integerp i) (<= 0 i) (string= (format nil "~a~a" (char name 0) i) name)))))))) (defun constant-symbolp (s) (char-nat-symbolp s '(#\C))) (defun variable-symbolp (s) (char-nat-symbolp s '(#\X #\Y #\Z #\W #\U #\V))) (defun quotep (c) (and (consp c) (== (car c) 'quote))) (defun constant-objectp (c) (typep c '(or boolean rational simple-string standard-char keyword))) #| Examples (constant-objectp #\a) (constant-objectp 0) (constant-objectp 1/221) (constant-objectp "hi there") (constant-objectp t) (constant-objectp nil) (constant-objectp :hi) (constant-objectp 'a) (quotep '1) ; recall that '1 is evaluated first (quotep ''1) ; but this works (quotep '(1 2 3)) ; similar to above (quotep ''(1 2 3)) ; similar to above (quotep (list 'quote (list 1 2 3))) ; verbose version of previous line |# (defun function-symbolp (f) (and (symbolp f) (not (in f *fo-keywords*)) (not (keywordp f)) (not (constant-symbolp f)) (not (variable-symbolp f)))) #| (function-symbolp 'c) (function-symbolp 'c0) (function-symbolp 'c00) (function-symbolp 'append) (function-symbolp '+) |# (defmacro mv-and (a b &optional (fsig 'fsig) (rsig 'rsig)) `(if ,a ,b (values nil ,fsig ,rsig))) (defmacro mv-or (a b &optional (fsig 'fsig) (rsig 'rsig)) `(if ,a (values t ,fsig ,rsig) ,b)) (defun fo-termp (term &optional (fsig nil) (rsig nil)) (match term ((satisfies constant-symbolp) (values t fsig rsig)) ((satisfies variable-symbolp) (values t fsig rsig)) ((satisfies quotep) (values t fsig rsig)) ((satisfies constant-objectp) (values t fsig rsig)) ((list* f args) (mv-and (and (function-symbolp f) (not (get-alist f rsig))) (let* ((fsig-arity (get-alist f fsig)) (acl2s-arity (or fsig-arity (acl2s-arity (to-acl2s f)))) (arity (or acl2s-arity (len args))) (fsig (if fsig-arity fsig (acons f arity fsig)))) (mv-and (== arity (len args)) (fo-termsp args fsig rsig))))) (_ (values nil fsig rsig)))) (defun fo-termsp (terms fsig rsig) (mv-or (endp terms) (let+ (((&values res fsig rsig) (fo-termp (car terms) fsig rsig))) (mv-and res (fo-termsp (cdr terms) fsig rsig))))) #| Examples (fo-termp '(f d 2)) (fo-termp '(f c 2)) (fo-termp '(f c0 2)) (fo-termp '(f c00 2)) (fo-termp '(f '1 '2)) (fo-termp '(f (f '1 '2) (f v1 c1 '2))) (fo-termp '(binary-append '1 '2)) (fo-termp '(binary-append '1 '2 '3)) (fo-termp '(binary-+ '1 '2)) (fo-termp '(+ '1 '2)) (fo-termp '(- '1 '2)) |# #| A FO atomic formula is either an atomic equality: (= t1 t2), where t1, t2 are FO terms. atomic relation: (P t1 ... tn), where P is a non-constant/non-variable symbol. The arity of P is n and every occurrence of (P ...) has to have arity n. Also, if P is a defined function in ACL2s, its arity has to match what ACL2s expects. We do not check that if P is a defined function then it has to return a Boolean. Make sure do not use such examples. |# (defun relation-symbolp (f) (function-symbolp f)) #| Examples (relation-symbolp '<) (relation-symbolp '<=) (relation-symbolp 'binary-+) |# (defun fo-atomic-formulap (f &optional (fsig nil) (rsig nil)) (match f ((list '= t1 t2) (fo-termsp (list t1 t2) fsig rsig)) ((list* r args) (mv-and (and (relation-symbolp r) (not (get-alist r fsig))) (let* ((rsig-arity (get-alist r rsig)) (acl2s-arity (or rsig-arity (acl2s::acl2s-arity (to-acl2s r)))) (arity (or acl2s-arity (len args))) (rsig (if rsig-arity rsig (acons r arity rsig)))) (mv-and (== arity (len args)) (fo-termsp args fsig rsig))))) (_ (values nil fsig rsig)))) #| Here is the definition of a propositional formula. We allow Booleans. |# (defun pfun-fo-argsp (pop args fsig rsig) (mv-and (p-funp pop) (let ((arity (get-key :arity (get-alist pop *p-ops*)))) (mv-and (or (== arity '-) (== (len args) arity)) (fo-formulasp args fsig rsig))))) (defun p-fo-formulap (f fsig rsig) (match f ((type boolean) (values t fsig rsig)) ((list* pop args) (if (p-funp pop) (pfun-fo-argsp pop args fsig rsig) (values nil fsig rsig))) (_ (values nil fsig rsig)))) #| Here is the definition of a quantified formula. The quantified variables can be a variable or a non-empty list of variables with no duplicates. Examples include (exists w (P w y z x)) (exists (w) (P w y z x)) (forall (x y z) (exists w (P w y z x))) But this does not work (exists c (P w y z x)) (forall () (exists w (P w y z x))) (forall (x y z x) (exists w (P w y z x))) |# (defun quant-fo-formulap (f fsig rsig) (match f ((list q vars body) (mv-and (and (in q *fo-quantifiers*) (or (variable-symbolp vars) (and (consp vars) (no-dupsp vars) (every #'variable-symbolp vars)))) (fo-formulap body fsig rsig))) (_ (values nil fsig rsig)))) (defun mv-seq-first-fun (l) (if (endp (cdr l)) (car l) (let ((res (gensym)) (f (gensym)) (r (gensym))) `(multiple-value-bind (,res ,f ,r) ,(car l) (if ,res (values t ,f ,r) ,(mv-seq-first-fun (cdr l))))))) (defmacro mv-seq-first (&rest rst) (mv-seq-first-fun rst)) (defun fo-formulap (f &optional (fsig nil) (rsig nil)) (mv-seq-first (fo-atomic-formulap f fsig rsig) (p-fo-formulap f fsig rsig) (quant-fo-formulap f fsig rsig) (values nil fsig rsig))) (defun fo-formulasp (fs fsig rsig) (mv-or (endp fs) (let+ (((&values res fsig rsig) (fo-formulap (car fs) fsig rsig))) (mv-and res (fo-formulasp (cdr fs) fsig rsig))))) #| We can use fo-formulasp to find the function and relation symbols in a formula as follows. |# (defun fo-f-symbols (f) (let+ (((&values res fsig rsig) (fo-formulap f))) (mapcar #'car fsig))) (defun fo-r-symbols (f) (let+ (((&values res fsig rsig) (fo-formulap f))) (mapcar #'car rsig))) #| Examples (fo-formulap '(forall (x y z) (exists w (P w y z x)))) (fo-formulap '(forall (x y z x) (exists w (P w y z x)))) (quant-fo-formulap '(forall (x y z) (exists y (P w y z x))) nil nil) (fo-formulap '(exists w (P w y z x))) (fo-atomic-formulap '(exists w (P w y z x)) nil nil) (quant-fo-formulap '(exists w (P w y z x)) nil nil) (fo-formulap '(P w y z x)) (fo-formulap '(and (forall (x y z) (or (not (= (q z) (r z))) nil (p '1 x y))) (exists w (implies (forall x1 (iff (= (p1 x1 w) c2) (q c1) (r c2))) (p '2 y w))))) (fo-formulap '(forall (x y z) (or (not (= (q z) (r z))) nil (p '1 x y)))) (fo-formulap '(exists w (implies (forall x1 (iff (= (p1 x1 w) c2) (q c1) (r c2))) (p '2 y w)))) (fo-formulap '(exists w (implies (forall x1 (iff (p1 x1 w) (q c1) (r c2))) (p '2 y w)))) (fo-formulap '(and (forall (x y z) (or (not (= (q2 z) (r2 z))) nil (p '1 x y))) (exists w (implies (forall x1 (iff (= (p1 x1 w) c2) (q c1) (r c2))) (p '2 y w))))) (fo-formulap '(forall x1 (iff (p1 x1 w) (q c1) (r c2)))) (fo-formulap '(iff (p1 x1 w) (q c1) (r c2))) (fo-atomic-formulap '(p1 x1 w)) (variable-symbolp 'c1) (fo-termp 'x1) (fo-termp 'w1) (fo-termp '(x1 w) nil nil) (fo-termsp '(x1 w) nil nil) |# #| Where appropriate, for the problems below, modify your solutions from homework 4. For example, you already implemented most of the simplifications in Question 1 in homework 4. |# #| Question 1. (25 pts) Define function fo-simplify that given a first-order (FO) formula returns an equivalent FO formula with the following properties. A. The returned formula is either a constant or does not include any constants. For example: (and (p x) t (q t y) (q y z)) should be simplified to (and (p x) (q t y) (q y z)) (and (p x) t (q t b) nil) should be simplified to nil B. Expressions are flattened, e.g.: (and (p c) (= c '1) (and (r) (s) (or (r1) (r2)))) is not flat, but this is (and (p c) (= c '1) (r) (s) (or (r1) (r2))) A formula of the form (op ...) where op is a Boolean operator of arbitrary arity (ie, and, or, iff) applied to 0 or 1 arguments is not flat. For example, replace (and) with t. A formula of the form (op ... (op ...)) where op is a Boolean operator of arbitrary arity is not flat. For example, replace (and p q (and r s)) with (and p q r s). C. If there is Boolean constant s s.t. If (op ... s ...) = s, then we say that s is a sink of op. For example t is a sink of or. A formula is sink-free if no such subformulas remain. The returned formula should be sink-free. D. Simplify your formulas so that no subexpressions of the following form remain (where f is a formula) (not (not f)) E. Simplify formulas so that no subexpressions of the following form remain (op ... p ... q ...) where p, q are equal literals or p = (not q) or q = (not p). For example (or (f) (f1) (p a b) (not (p a b)) (= w z)) should be simplified to t F. Simplify formulas so there are no vacuous quantified formulas. For example, (forall (x w) (P y z)) should be simplified to (P y z) and (forall (x w) (P x y z)) should be simplified to (forall (x) (P x y z)) G. Simplify formulas by using ACL2s to evaluate, when possible, terms of the form (f ...) where f is an ACL2s function all of whose arguments are either constant-objects or quoted objects. For example, (P (binary-+ 4 2) 3) should be simplified to (P 6 3) Hint: use acl2s-compute and to-acl2s. For example, consider (acl2s-compute (to-acl2s '(binary-+ 4 2))) On the other hand, (P (binary-+ 'a 2) 3) does not get simplified because (acl2s-compute (to-acl2s '(binary-+ 'a 2))) indicates an error (contract/guard violation). See the definition of acl2s-compute to see how to determine if an error occurred. H. Test your definitions using at least 10 interesting formulas. Use the acl2s code, if you find it useful. Include deeply nested formulas, all of the Boolean operators, quantified formulas, etc. Make sure that your algorithm is as efficient as you can make it. The idea is to use this simplification as a preprocessing step, so it needs to be fast. You are not required to perform any other simplifications beyond those specified above. If you do, your simplifier must be guaranteed to always return something that is simpler that what would be returned if you just implemented the simplifications explicitly requested. Also, if you implement any other simplifications, your algorithm must run in comparable time (eg, no validity checking). Notice some simple consequences. You cannot transform the formula to an equivalent formula that uses a small subset of the connectives (such as not/and). If you do that, the formula you get can be exponentially larger than the input formula, as we have discussed in class. Notice that even negation normal form (NNF) can increase the size of a formula. |# (defun fo-simplify (f) ...) #| Question 2. (10 pts) Define nnf, a function that given a FO formula, something that satisfies fo-formulap, puts it into negation normal form (NNF). The resulting formula cannot contain any of the following propositional connectives: implies, iff, if. Test nnf using at least 10 interesting formulas. Make sure you support quantification. |# (defun nnf (f) ...) #| Question 3. (25 pts) Define simp-skolem-pnf-cnf, a function that given a FO formula, simplifies it using fo-simplify, then puts it into negation normal form, applies skolemization, then puts the formula in prenex normal form and finally transforms the matrix into an equivalent CNF formula. To be clear: The formula returned should be equi-satisfiable with the input formula, should contain no existential quantifiers, and if it has quantifiers it should be of the form (forall (...) matrix) where matrix is quantifier-free and in CNF. The fewer quantified variables, the better. The fewer Skolem functions, the better. The smaller the arity of Skolem functions, the better. Having said that, correctness should be your primary consideration. Test your functions using at least 10 interesting formulas. |# (defun simp-skolem-pnf-cnf (f) ...) #| Question 4. (15 pts) Define unify, a function that given an a non-empty list of pairs, where every element of the pair is FO-term, returns an mgu (most general unifier) if one exists or the symbol 'fail otherwise. An assignment is a list of conses, where car is a variable, the cdr is a term and the variables (in the cars) are unique. Test your functions using at least 10 interesting inputs. |# (defun unify (l) ...) #| Question 5. (25 pts) Define fo-no=-val, a function that given a FO formula, without equality, checks if it is valid using U-Resolution. If it is valid, return 'valid. Your code should use positive resolution and must implement subsumption and replacement. Test your functions using at least 10 interesting inputs including the formulas from the following pages of the book: 178 (p38, p34), 179 (ewd1062), 180 (barb), and 198 (the Los formula). |# (defun fo-no=-val (f) ...) #| Question 6. Extra Credit (20 pts) Define fo-val, a function that given a FO formula, checks if it is valid using U-Resolution. If it is valid, return 'valid. Your code should use positive resolution and must implement subsumption and replacement. This is an extension of question 5, where you replace equality with a new relation symbol and add the appropriate equivalence and congruence hypotheses. |# (defun fo-val (f) ...) #| Question 7. Extra Credit (25 pts) Define horn-sat. It is a function that takes as input (1) hs, a list of positive universal Horn sentences and (2) e, a sentence of the form (exists vars (and ...)) where ... is a list of atomic formulas You can assume none of the inputs formulas contain =, if you wish. If you solved q5, you can also deal with =. If hs |= e, then horn-sat returns an assignment, sigma, s.t. hs |= (and ...)sigma Recall that an assignment is defined in Q4. |# (defun horn-sat (hs e) ...)