Crediting Alex Matthews as a code contributor

PiperOrigin-RevId: 360859332
This commit is contained in:
Jonathan Schwarz
2021-03-04 09:28:30 +00:00
committed by Louise Deason
parent 7e6fd889e4
commit ca532c106c
32 changed files with 5580 additions and 1 deletions
@@ -60,7 +60,8 @@
"code authors:\n",
"\n",
"* Jonathan Schwarz (schwarzjn@google.com)\n",
"* Michalis Titsias (mtitsias@google.com)"
"* Michalis Titsias (mtitsias@google.com)\n",
"* Alex Matthews (alexmatthews@google.com)"
]
},
{
+246
View File
@@ -0,0 +1,246 @@
#lang racket/base
;**************************************************************************************;
;**** Clause: Clauses With Additional Properties In A Struct ****;
;**************************************************************************************;
(require define2
define2/define-wrapper
global
racket/format
racket/list
racket/string
satore/clause
satore/clause-format
satore/misc
satore/unification
text-table)
(provide (all-defined-out))
;==============;
;=== Clause ===;
;==============;
;; TODO: A lot of space is wasted in Clause (boolean flags?)
;; What's the best way to gain space without losing time or readability?
;; parents : (listof Clause?) ; The first parent is the 'mother'.
;; binary-rewrite-rule? : Initiually #false, set to #true if the clause has been added (at some point)
;; to the binary rewrite rules (but may not be in the set anymore if subsumed).
;; size: tree-size of the clause.
;; depth: Maternal-path depth.
;; cost: Maternal-path cost.
(struct Clause (idx
parents
clause
type
[binary-rewrite-rule? #:mutable]
[candidate? #:mutable]
[discarded? #:mutable]
n-literals
size
depth
[cost #:mutable]
[g-cost #:mutable])
#:prefab)
(define-counter clause-index 0)
(define (make-Clause cl
[parents '()]
#:type [type '?]
#:candidate? [candidate? #false]
#:n-literals [n-literals (length cl)]
#:size [size (clause-size cl)]
#:depth [depth (if (empty? parents) 1 (+ 1 (Clause-depth (first parents))))])
(++clause-index)
(when-debug>= steps
(define cl2 (clause-normalize cl)) ; costly, hence done only in debug mode
(unless (= (tree-size cl) (tree-size cl2))
(displayln "Assertion failed: clause is in normal form")
(printf "Clause (type: ~a):\n~a\n" type (clause->string cl))
(displayln "Parents:")
(print-Clauses parents)
(error (format "Assertion failed: (= (tree-size cl) (tree-size cl2)): ~a ~a"
(tree-size cl) (tree-size cl2)))))
; Notice: Variables are ASSUMED freshed. Freshing is not performed here.
(Clause clause-index
parents
cl
type
#false ; binary-rewrite-rule
candidate?
#false ; discarded?
n-literals
size
depth ; depth (C0 is of depth 0, axioms are of depth 1)
0. ; cost
0. ; g-cost
))
(define (discard-Clause! C) (set-Clause-discarded?! C #true))
(define true-Clause (make-Clause (list ltrue)))
;; For temporary converse Clauses for binary clauses.
(define (make-converse-Clause C #:candidate? [candidate? #false])
(if (unit-Clause? C)
true-Clause ; If C has 1 literal A, then C = A | false, and converse is ~A | true = true
(make-Clause (fresh (clause-converse (Clause-clause C)))
(list C)
#:type 'converse
#:candidate? candidate?
)))
(define Clause->string-all-fields '(idx parents clause type binary-rw? depth size cost))
;; If what is a list, each element is printed (possibly multiple times).
;; If what is 'all, all fields are printed.
(define (Clause->list C [what '(idx parents clause)])
(when (eq? what 'all)
(set! what Clause->string-all-fields))
(for/list ([w (in-list what)])
(case w
[(idx) (~a (Clause-idx C))]
[(parents) (~a (map Clause-idx (Clause-parents C)))]
[(clause) (clause->string (Clause-clause C))]
[(clause-pretty) (clause->string/pretty (Clause-clause C))]
[(type) (~a (Clause-type C))]
[(binary-rw?) (~a (Clause-binary-rewrite-rule? C))]
[(depth) (~r (Clause-depth C))]
[(size) (~r (Clause-size C))]
[(cost) (~r2 (Clause-cost C))])))
(define (Clause->string C [what '(idx parents clause)])
(string-join (Clause->list C what) " "))
(define (Clause->string/alone C [what '(idx parents clause)])
(when (eq? what 'all)
(set! what Clause->string-all-fields))
(string-join (map (λ (f w) (format "~a: ~a " w f))
(Clause->list C what)
what)
" "))
(define (print-Clauses Cs [what '(idx parents clause)])
(when (eq? what 'all)
(set! what Clause->string-all-fields))
(print-simple-table
(cons what
(map (λ (C) (Clause->list C what)) Cs))))
;; <=> to avoid hard-to-debug mistakes where Clause-subsumes is used instead of Clause<-subsumes
;; for example.
;; Notice: This is an approximation of the correct subsumption based on multisets, and may not
;; be confluent.
(define (Clause<=>-subsumes C1 C2)
(clause-subsumes (Clause-clause C1) (Clause-clause C2)))
;; Use atom<=> ?
(define Clause-cmp-key Clause-size)
(define (Clause<= C1 C2) (<= (Clause-cmp-key C1) (Clause-cmp-key C2)))
(define (Clause< C1 C2) (< (Clause-cmp-key C1) (Clause-cmp-key C2)))
(define (Clause<=-subsumes C1 C2)
(and (Clause<= C1 C2)
(Clause<=>-subsumes C1 C2)))
(define (Clause<-subsumes C1 C2)
(and (Clause< C1 C2)
(Clause<=>-subsumes C1 C2)))
;; Useful for rewrite rules
(define (Clause<=>-converse-subsumes C1 C2)
(clause-subsumes (clause-converse (Clause-clause C1))
(Clause-clause C2)))
(define (unit-Clause? C)
(= 1 (Clause-n-literals C)))
(define (binary-Clause? C)
(= 2 (Clause-n-literals C)))
(define (Clause-tautology? C)
(clause-tautology? (Clause-clause C)))
;; Returns the tree of ancestor Clauses of C up to init Clauses,
;; but each Clause appears only once in the tree.
;; (The full tree can be further retrieved from the Clause-parents.)
;; Used for proofs.
(define (Clause-ancestor-graph C #:depth [dmax +inf.0])
(define h (make-hasheq))
(let loop ([C C] [depth 0])
(cond
[(or (> depth dmax)
(hash-has-key? h C))
#false]
[else
(hash-set! h C #true)
(cons C (filter-map (λ (C2) (loop C2 (+ depth 1)))
(Clause-parents C)))])))
(define (Clause-ancestor-graph-string C
#:? [depth +inf.0]
#:? [prefix ""]
#:? [tab " "]
#:? [what '(idx parents type clause)])
(define h (make-hasheq))
(define str-out "")
(let loop ([C C] [d 0])
(unless (or (> d depth)
(hash-has-key? h C))
(set! str-out (string-append str-out
prefix
(string-append* (make-list d tab))
(Clause->string C what)
"\n"))
(hash-set! h C #true)
(for ([P (in-list (Clause-parents C))])
(loop P (+ d 1)))))
str-out)
(define-wrapper (display-Clause-ancestor-graph
(Clause-ancestor-graph-string C #:? depth #:? prefix #:? tab #:? what))
#:call-wrapped call
(display (call)))
(define (Clause-age<= C1 C2)
(<= (Clause-idx C1) (Clause-idx C2)))
(define (save-Clauses! Cs f #:? exists)
(save-clauses! (map Clause-clause Cs) f #:exists exists))
(define (load-Clauses f #:? [sort? #true] #:? [type 'load])
(define Cs (map (λ (c) (make-Clause c #:type type))
(load-clauses f)))
(if sort?
(sort Cs Clause<=)
Cs))
(define (Clause-equivalence? C1 C2)
(and (Clause<=>-subsumes C1 C2)
(Clause<=>-subsumes C2 C1)))
;; Provides testing utilities. Use with `(require (submod "Clause.rkt" test))`.
(module+ test
(require rackunit)
(provide Clausify
check-Clause-set-equivalent?)
(define Clausify (compose make-Clause clausify))
(define-check (check-Clause-set-equivalent? Cs1 Cs2)
(unless (= (length Cs1) (length Cs2))
(fail-check "not ="))
(for/fold ([Cs2 Cs2])
([C1 (in-list Cs1)])
(define C1b
(for/first ([C2 (in-list Cs2)] #:when (Clause-equivalence? C1 C2))
C2))
(unless C1b
(printf "Cannot find equivalence Clause for ~a\n" (Clause->string C1))
(print-Clauses Cs1)
(print-Clauses Cs2)
(fail-check))
(remq C1b Cs2))))
+50
View File
@@ -0,0 +1,50 @@
# Satore: First-order logic saturation with atomic rewriting
This is a first-order logic resolution based theorem prover in CNF without
equality, but with atom rewrite rules. New rewrite rules can be
discovered during the proof search, potentially reducing exponentially the
search space.
Satore stands for Saturation with Atom Rewriting.
## Installation
### Install racket (Apache2/MIT):
* Windows, MacOS X: https://download.racket-lang.org
* Ubuntu/Debian: `[sudo] apt install racket`
* Linux (other): [Download](https://download.racket-lang.org) the `.sh` and
install it with `[sudo] sh racket-<something>.sh`
You may need to configure the PATH environment variable to include the
directory containing the `racket` and `raco` executables.
For Windows this directory should be something like
`C:>Program Files\Racket`.
### Install satore and its dependencies (all are Apache2/MIT licensed):
In a directory of your choice, type:
```shell
git clone https://github.com/deepmind/deepmind-research/tree/master/satore
raco pkg install --auto --link satore
```
## Running Satore
Run a trivial example:
```shell
racket -l- satore -p satore/examples/socrates.p --proof
```
To see the various flags:
```shell
racket -l- satore --help
```
The .p file is assumed to be a standalone file with only comments and
`cnf(…).` lines without equality, where the logic clause must be surrounded by
parentheses. All axioms must be included. (This will likely be improved soon.)
Note that `racket -l- satore` can be invoked from anywhere.
+41
View File
@@ -0,0 +1,41 @@
#lang racket/base
;***************************************************************************************;
;**** Clause <-> String Conversions ****;
;***************************************************************************************;
;;; In a separate file because of cyclic dependencies with "tptp.rkt" if in "clause.rkt"
(require racket/format
racket/list
racket/pretty
satore/clause
satore/tptp
satore/unification
text-table)
(provide (all-defined-out))
(define (clause->string cl)
((if (*tptp-out?*)
clause->tptp-string
~a)
(Vars->symbols cl)))
(define (clause->string/pretty cl)
(pretty-format (Vars->symbols cl)))
(define (print-clause cl)
(displayln (clause->string cl)))
(define (print-clauses cls #:sort? [sort? #false])
(unless (empty? cls)
(print-table
(for/list ([cl (in-list (if sort?
(sort cls < #:key tree-size #:cache-keys? #true)
cls))]
[i (in-naturals)])
(cons i (Vars->symbols cl)))
#:border-style 'space
#:row-sep? #false
#:framed? #false)))
+197
View File
@@ -0,0 +1,197 @@
#lang racket/base
;***************************************************************************************;
;**** Operations on clauses ****;
;***************************************************************************************;
(require bazaar/cond-else
bazaar/debug
bazaar/list
bazaar/loop
bazaar/mutation
(except-in bazaar/order atom<=>)
define2
global
racket/file
racket/format
racket/list
racket/pretty
satore/misc
satore/trie
satore/unification
syntax/parse/define
text-table)
(provide (all-defined-out))
(define-global *subsumes-iter-limit* 0
'("Number of iterations in the θ-subsumption loop before failing."
"May help in cases where subsumption take far too long."
"0 = no limit.")
exact-nonnegative-integer?
string->number)
(define-counter n-tautologies 0)
(define (sort-clause cl)
(sort cl literal<?))
;; cl is assumed to be already Varified, but possibly not freshed.
;; Notice: Does not do rewriting.
(define (clause-normalize cl)
; fresh the variables just to make sure
(fresh (safe-factoring (sort-clause cl))))
;; Used to turn human-readable clauses into computer-friendly clauses.
(define (clausify cl)
(clause-normalize (Varify cl)))
(define (empty-clause? cl)
(empty? cl))
;; Definition of tautology:
;; cl is a tautology if all ground instances of cl contain an atom and its negation.
;; Assumes that the clause cl is sorted according to `sort-clause`.
(define (clause-tautology? cl)
(define-values (neg pos) (partition lnot? cl))
(define pneg (map lnot neg))
(and
(or
(memq ltrue pos)
(memq lfalse pneg)
(let loop ([pos pos] [pneg pneg])
(cond/else
[(or (empty? pos) (empty? pneg)) #false]
#:else
(define p (first pos))
(define n (first pneg))
(define c (literal<=> p n))
#:cond
[(order<? c) (loop (rest pos) pneg)]
[(order>? c) (loop pos (rest pneg))]
[(literal==? p n)]
#:else (error "uh?"))))
(begin (++n-tautologies) #true)))
;; NOTICE: This does *not* rename the variables.
(define (clause-converse cl)
(sort-clause (map lnot cl)))
;; Assumes that cl is sorted according to `sort-clause`.
(define (remove-duplicate-literals cl)
(zip-loop ([(x r) cl] [res '()] #:result (reverse res))
(cond/else
[(empty? r) (cons x res)]
#:else
(define y (first r))
#:cond
[(literal==? x y) res]
#:else (cons x res))))
(define (predicate.arity lit)
(cond [(list? lit) (cons (first lit) (length lit))]
[else (cons lit 0)]))
(define-counter n-subsumes-checks 0)
(define-counter n-subsumes-steps 0)
(define-counter n-subsumes-breaks 0)
(define (reset-subsumes-stats!)
(reset-n-subsumes-checks!)
(reset-n-subsumes-steps!)
(reset-n-subsumes-breaks!))
;; θ-subsumption.
;; ca θ-subsumes cb if there exists a substitution α such that ca[α] ⊆ cb
;; (requires removing duplicate literals as in FOL clauses are assumed to be sets of literals).
;; Assumes vars(ca) ∩ vars(cb) = ∅.
(define (clause-subsumes ca cb)
(++n-subsumes-checks)
; For every each la of ca with current substitution β, we need to find a literal lb of cb
; such that we can extend β to β' so that la[β'] = lb.
; TODO: order the groups by smallest size for cb.
; TODO: need to split by polarity first, or sort by (polarity predicate arity)
; For each literal of ca, obtain the list of literals of cb that unify with it.
; place cb in a trie
; then retrieve
(define cbtrie (make-trie #:variable? Var?))
(for ([litb (in-list cb)])
; the key must be a list, but a literal may be just a constant, so we need to `list` it.
(trie-insert! cbtrie (list litb) litb))
;; Each literal lita of ca is paired with a list of potential literals in cb that lita matches,
;; for subsequent left-unification.
;; We sort the groups by smallest size first, to fail fast.
(define groups
(sort
(for/list ([lita (in-list ca)])
; lita must match litb, hence inverse-ref
(cons lita (append* (trie-inverse-ref cbtrie (list lita)))))
< #:key length #:cache-keys? #true))
;; Depth-first search while trying to find a substitution that works for all literals of ca.
;; TODO: if number of iterations is larger than threshold, abort (use let/ec)
(define n-iter-max (*subsumes-iter-limit*))
(define n-iter 0)
(let/ec return
(let loop ([groups groups] [subst '()])
(++ n-iter)
(when (= n-iter n-iter-max) ; if n-iter-max = 0 then no limit
(++n-subsumes-breaks)
(return #false))
(++n-subsumes-steps)
(cond
[(empty? groups) subst]
[else
(define-values (lita litbs) (car+cdr (first groups)))
(for/or ([litb (in-list litbs)])
; We use a immutable substitution to let racket handle copies when needed.
(define new-subst (left-unify/assoc lita litb subst))
(and new-subst (loop (rest groups) new-subst)))]))))
;; Assumes that the clause cl is sorted according to `sort-clause`.
;; A safe factor f of cl is such that f[α] ⊆ cl for some subst α, that is,
;; f θ-subsumes cl. But since cl necessarily θ-subsumes all of its factors XXX
;; - The return value is eq? to the argument cl if no safe-factoring is possible.
;; - Applies safe-factoring as much as possible.
(define (safe-factoring cl)
(let/ec return
(zip-loop ([(l x r) cl])
(define pax (predicate.arity x))
(zip-loop ([(l2 y r2) r] #:break (not (equal? pax (predicate.arity y))))
; To avoid code duplication:
(define-simple-macro (attempt a b)
(begin
(define s (left-unify a b))
(when s
(define new-cl
(sort-clause
(fresh ; required for clause-subsumes below
(left-substitute (rev-append l (rev-append l2 (cons a r2))) ; remove b
s))))
(when (clause-subsumes new-cl cl)
; Try one more time with new-cl.
(return (safe-factoring new-cl))))))
(attempt x y)
(attempt y x)))
cl))
(define (clause-equivalence? A B)
(and (clause-subsumes A B)
(clause-subsumes B A)))
;==============;
;=== Saving ===;
;==============;
(define (save-clauses! cls f #:? [exists 'replace])
(with-output-to-file f #:exists exists
(λ () (for-each writeln cls))))
(define (load-clauses f)
(map clausify (file->list f)))
+62
View File
@@ -0,0 +1,62 @@
cnf(i_0, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a))).
cnf(i_1, plain, (~num(b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28) | num(a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28))).
cnf(i_2, plain, (num(b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28) | ~num(a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27, X28))).
cnf(i_3, plain, (~num(a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27) | num(b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27))).
cnf(i_4, plain, (num(a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27) | ~num(b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26, X27))).
cnf(i_5, plain, (~num(a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26) | num(b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26))).
cnf(i_6, plain, (num(a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26) | ~num(b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25, X26))).
cnf(i_7, plain, (~num(a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25) | num(b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25))).
cnf(i_8, plain, (num(a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25) | ~num(b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24, X25))).
cnf(i_9, plain, (~num(a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24) | num(b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24))).
cnf(i_10, plain, (num(a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24) | ~num(b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23, X24))).
cnf(i_11, plain, (~num(a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23) | num(b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23))).
cnf(i_12, plain, (num(a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23) | ~num(b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22, X23))).
cnf(i_13, plain, (~num(a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22) | num(b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22))).
cnf(i_14, plain, (num(a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22) | ~num(b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21, X22))).
cnf(i_15, plain, (~num(a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21) | num(b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21))).
cnf(i_16, plain, (num(a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21) | ~num(b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20, X21))).
cnf(i_17, plain, (~num(a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20) | num(b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20))).
cnf(i_18, plain, (num(a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20) | ~num(b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19, X20))).
cnf(i_19, plain, (~num(a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19) | num(b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19))).
cnf(i_20, plain, (num(a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19) | ~num(b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18, X19))).
cnf(i_21, plain, (~num(a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18) | num(b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18))).
cnf(i_22, plain, (num(a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18) | ~num(b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17, X18))).
cnf(i_23, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17) | num(b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17))).
cnf(i_24, plain, (num(a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17) | ~num(b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16, X17))).
cnf(i_25, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16) | num(b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16))).
cnf(i_26, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15, X16))).
cnf(i_27, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15))).
cnf(i_28, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14, X15))).
cnf(i_29, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14))).
cnf(i_30, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13, X14))).
cnf(i_31, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13))).
cnf(i_32, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12, X13))).
cnf(i_33, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12))).
cnf(i_34, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11, X12))).
cnf(i_35, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11))).
cnf(i_36, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10, X11))).
cnf(i_37, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10))).
cnf(i_38, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9, X10))).
cnf(i_39, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9))).
cnf(i_40, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8, X9))).
cnf(i_41, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8))).
cnf(i_42, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7, X8) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7, X8))).
cnf(i_43, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7))).
cnf(i_44, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6, X7) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6, X7))).
cnf(i_45, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6))).
cnf(i_46, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5, X6) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5, X6))).
cnf(i_47, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5))).
cnf(i_48, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4, X5) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4, X5))).
cnf(i_49, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4))).
cnf(i_50, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3, X4) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3, X4))).
cnf(i_51, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3))).
cnf(i_52, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2, X3) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2, X3))).
cnf(i_53, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2))).
cnf(i_54, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1, X2) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1, X2))).
cnf(i_55, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1))).
cnf(i_56, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0, X1) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0, X1))).
cnf(i_57, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0))).
cnf(i_58, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b, X0) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a, X0))).
cnf(i_59, plain, (~num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b) | num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a))).
cnf(i_60, plain, (num(a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, a, b) | ~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, a))).
cnf(i_0, negated_conjecture, (~num(b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b))).
+3
View File
@@ -0,0 +1,3 @@
cnf(humans_are_mortal, axiom, (~human(X) | mortal(X))).
cnf(socrates_is_human, hypothesis, (human(c))).
cnf(socrates_is_mortal, negated_conjecture, (~mortal(c))).
+30
View File
@@ -0,0 +1,30 @@
#lang info
(define collection "satore")
(define deps '("bazaar"
"data-lib"
"define2"
"global"
"math-lib"
"text-table"
"base"))
(define build-deps '("rackunit-lib"
"scribble-lib"
))
(define scribblings '(("scribblings/satore.scrbl" ())))
(define pkg-desc "First-order logic prover in CNF without equality, but with atom rewrite rules")
(define version "0.1")
(define pkg-authors '(orseau))
(define racket-launcher-names '("satore"))
(define racket-launcher-libraries '("satore.rkt"))
(define test-omit-paths '("info.rkt"
"last-results.rkt"
"parse-log.rkt"
"in-progress/"
"find-rules.rkt"
"print-rules.rkt"
"run-eprover.rkt"
"rules/"
"logs/"
"scribblings/"))
+96
View File
@@ -0,0 +1,96 @@
#lang racket/base
;***************************************************************************************;
;**** User Interaction Commands ****;
;***************************************************************************************;
(require (for-syntax racket/base syntax/parse)
racket/format
racket/list
racket/match
racket/port)
(provide (all-defined-out))
;; Notice: variables set via eval or only set locally, in the local namespace,
;; and not in the main namespace.
;; variables set via the (list 'var val) pattern are set in the main namespace.
;; Even though the namespace is at the module level, the variables
;; are set in the namespace with their value so they can be used with eval.
;; TODO: When a ns-anchor is given, commands are eval'ed by default, and to directly modify
;; variables one must use ! (where the second argument is evaled)
(define-syntax (interact stx)
(syntax-parse stx
#:literals (list)
[(_ (~alt (~optional (~seq #:prompt prompt:expr)) ; must evaluate to a string, default "> "
(~optional (~seq #:command command:expr))
(~optional (~seq #:namespace-anchor ns-anchor:expr)) ; default #false
(~optional (~seq #:variables (var:id ...))) ; must be bound identifiers
(~optional (~seq #:readline? readline?:expr))) ; start with readline enabled? (#false)
...
[(list pat ...) help-string body ...+] ...) ; match patterns
(with-syntax ([(var ...) #'(~? (var ...) ())])
#'(begin
(define names (list 'var ...))
(define nsa (~? ns-anchor #false))
(define ns (and nsa (namespace-anchor->namespace nsa)))
(when (~? readline? #false) (eval '(require readline) ns))
(when ns
(namespace-set-variable-value! 'var var #false ns) ...
(void)) ; to avoid bad 'when' form if no variable
(define the-prompt (~? prompt "> "))
(let loop ()
(with-handlers ([exn:fail? (λ (e)
(displayln (exn-message e))
(loop))])
(define cmd (~? command #false))
(when (and cmd (not (string? cmd)))
(error "command must be a string"))
(unless cmd (display the-prompt))
(define cmd-str (or cmd (read-line)))
(unless (eof-object? cmd-str)
(define cmd (with-input-from-string (string-append "(" cmd-str ")") read))
(match cmd
['() (void)]
['(help)
(unless (empty? names)
(printf "Available variables: ~a\n" names))
(displayln "Other commands:")
(parameterize ([print-reader-abbreviations #true]
[print-as-expression #false])
(void)
(begin
(displayln (string-append " " (apply ~v '(pat ...) #:separator " ")))
(displayln (string-append " " help-string)))
...)
(when ns
(displayln " eval expr")
(displayln
" Evaluate expr in a namespace that is local to this interaction loop."))
(loop)]
[(list 'eval cmd)
(if ns
(call-with-values (λ () (eval cmd ns))
(λ l (if (= 1 (length l))
(unless (void? (first l))
(displayln (first l)))
(for-each displayln l))))
(displayln "Cannot use eval without a namespace-anchor"))
(loop)]
['(var) (println var) (loop)] ...
[(list 'var val) (set! var val) (loop)] ...
[(list pat ...) body ... (loop)] ...
[else (printf "Unknown command: ~a\n" cmd)
(loop)]))))))]))
(module+ drracket
(define-namespace-anchor ns-anchor) ; optional, to use the eval command
(let ([x 3] [y 'a])
(interact
#:prompt ">> "
#:namespace-anchor ns-anchor
#:variables (x y)
;; All patterns must be of the form (list ....)
[(list 'yo) "prints yo" (displayln "yo")]
[(list 'yo (? number? n)) "prints multiple yos" (displayln (make-list n 'yo))])))
+65
View File
@@ -0,0 +1,65 @@
#lang racket/base
;***************************************************************************************;
;**** Json Output ****;
;***************************************************************************************;
(require bazaar/debug
racket/dict
racket/string)
(provide (all-defined-out))
(define status-dict
'((running . UNSPECIFIED_PROOF_STATUS)
(refuted . REFUTATION_FOUND)
(time . TIME_LIMIT_REACHED)
(memory . MEMORY_LIMIT_REACHED)
(steps . STEP_LIMIT_REACHED)
(saturated . COUNTER_SATISFIABLE)))
(define (saturation-result->json res)
(define d
(let* ([res (dict-remove res 'name)]
[res (dict-remove res 'file)]
[res (dict-update res 'status (λ (v) (dict-ref status-dict v)))])
res))
(string-join
#:before-first "{\n "
(for/list ([(k v) (in-dict d)])
(define kstr (regexp-replace* #px"-|:" (symbol->string k) "_"))
(format "~s: ~s" kstr (if (symbol? v) (symbol->string v) v)))
",\n "
#:after-last "\n}"))
(module+ drracket
(define res
'((name . "GEO170+1.p")
(file . "data/tptp_geo/GEO170+1.p")
(status . refuted)
(steps . 205)
(generated . 3186)
(actives . 106)
(candidates . 2651)
(priority-remaining . 0)
(tautologies . 156)
(rules . 30)
(unit-rules . 24)
(binary-rules . 6)
(binary-rules-static . 0)
(binary-rules-dynamic . 6)
(binary-rewrites . 164)
(forward-subsumed . 96)
(backward-subsumed . 0)
(subsumes-checks . 7654)
(subsumes-steps . 13268)
(subsumes-breaks . 0)
(L-resolvent-pruning . 0)
(memory . 181509744)
(time . 196)
(proof-length . 12)
(proof-inferences . 5)
(proof-type:in . 7)
(proof-type:res . 4)
(proof-type:rw . 1)))
(displayln (saturation-result->json res)))
+57
View File
@@ -0,0 +1,57 @@
#lang racket/base
;***************************************************************************************;
;**** Logging To File With Consistent Debugging Information ****;
;***************************************************************************************;
(require bazaar/date
bazaar/debug
define2
global
racket/file
racket/port
racket/pretty
racket/string
racket/system)
(provide call-with-log
*log*)
(define-global:boolean *log* #false
"Output to a log file?")
(define-global:boolean *git?* #false
"Commit to git if needed and include the last git commit hash in the globals.")
(define (call-with-log thunk
#:? [dir "logs"]
#:? [filename (string-append "log-" (date-iso-file) ".txt")]
; if given, dir and filename have no effect:
#:? [filepath (build-path dir filename)]
#:? [log? (*log*)]
#:? [quiet? #false])
(when (*git?*)
(define cmd "git commit -am \".\" ")
(displayln cmd)
(system cmd))
;; Non-quiet mode.
(define (thunk2)
; Also write the last commit hash for easy retrieval.
(pretty-write
(list* `(cmd-line . ,(current-command-line-arguments))
`(git-commit . ,(and (*git?*)
(string-normalize-spaces
(with-output-to-string (λ () (system "git rev-parse HEAD"))))))
(globals->assoc)))
(thunk))
(cond [log?
(make-parent-directory* filepath)
(assert (not (file-exists? filepath)) filepath)
(printf "Logging to: ~a\n" filepath)
(pretty-write (globals->assoc))
(with-output-to-file filepath thunk2)]
[quiet? (thunk)]
[else (thunk2)]))
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env racket
#lang racket/base
;**************************************************************************************;
;**** Satore ****;
;**************************************************************************************;
(module+ main
(require global
racket/file
racket/port
satore/misc
satore/rewrite-tree
satore/saturation
satore/unification)
(define-global *prog* #false
'("Data file containing a single TPTP program."
"If not provided, reads from the input port.")
file-exists?
values
'("-p"))
;; If -p is not specified, reads from current-input-port
(void (globals->command-line #:program "satore"))
;; No validation here yet.
(define program
(if (*prog*)
(file->string (*prog*))
(port->string)))
(iterative-saturation
(λ (#:clauses input-clauses #:cpu-limit cpu-limit #:rwtree-in rwtree-in #:rwtree-out rwtree-out)
(saturation input-clauses
#:cpu-limit cpu-limit
#:rwtree rwtree-in
#:rwtree-out rwtree-out))
#:tptp-program program
#:rwtree-in (make-rewrite-tree #:atom<=> (get-atom<=>)
#:dynamic-ok? (*dynamic-rules?*)
#:rules-file (*input-rules*))))
+90
View File
@@ -0,0 +1,90 @@
#lang racket/base
;***************************************************************************************;
;**** Various Utilities ****;
;***************************************************************************************;
(require (for-syntax racket/base racket/port racket/syntax syntax/parse)
(except-in bazaar/order atom<=>)
global
racket/contract
racket/format
racket/list
racket/match
racket/port
racket/struct
racket/stxparam)
(provide (all-defined-out))
(print-boolean-long-form #true)
(define-syntax-rule (begin-for-both e)
(begin
e
(begin-for-syntax e)))
(begin-for-both
(define (debug-level->number lev)
(cond
[(number? lev) lev]
[(string? lev) (debug-level->number (with-input-from-string lev read))]
[else
(case lev
[(none) 0]
[(init) 1]
[(step steps) 2]
[(interact) 3]
[else (error "unknown debug level" lev)])])))
(define-global *debug-level* 0
"Number or one of (none=0 init=1 steps interact)."
exact-nonnegative-integer?
debug-level->number
'("--debug"))
(define (debug>= lev)
(>= (*debug-level*) (debug-level->number lev)))
;; TODO: Make faster
(define-syntax (when-debug>= stx)
(syntax-case stx ()
[(_ lev body ...)
(with-syntax ([levv (debug-level->number (syntax-e #'lev))])
#'(when (>= (*debug-level*) levv)
body ...))]))
(define (thunk? p)
(and (procedure? p)
(procedure-arity-includes? p 0)))
;; Defines a counter with a reset function and an increment function
;; Ex:
;; (define-counter num 0)
;; (++num)
;; (++num 3)
;; (reset-num!)
(define-syntax (define-counter stx)
(syntax-case stx ()
[(_ name init)
(with-syntax ([reset (format-id stx #:source stx "reset-~a!" (syntax-e #'name))]
[++ (format-id stx #:source stx "++~a" (syntax-e #'name))])
#'(begin
(define name init)
(define (reset)
(set! name init))
(define (++ [n 1])
(set! name (+ name n)))))]))
(define (current-memory-use-MB)
(arithmetic-shift (current-memory-use) -20))
(define (car+cdr p)
(values (car p) (cdr p)))
(define (~r2 x #:precision [precision 2])
(if (rational? x)
(~r x #:precision precision)
(~a x)))
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
#lang scribble/manual
@title{First-order logic saturation with atomic rewriting}
See the @hyperlink["https://link-to-readme"]{readme}.
+12
View File
@@ -0,0 +1,12 @@
#lang racket/base
(require rackunit
"../Clause.rkt"
(submod "../Clause.rkt" test))
;; Polarity should not count for the 'weight' cost function because otherwise it will be harder
;; to prove ~A | ~B than A | B.
(check-equal? (Clause-size (make-Clause '[p q]))
(Clause-size (make-Clause '[(not p) (not q)])))
;; TODO: test (check-Clause-set-equivalent? Cs1 Cs2)
+197
View File
@@ -0,0 +1,197 @@
#lang racket/base
(require global
racket/dict
rackunit
racket/list
"../clause.rkt"
"../misc.rkt"
"../unification.rkt")
(*subsumes-iter-limit* 0)
(begin
(define-simple-check (check-tautology cl res)
(check-equal? (clause-tautology? (sort-clause (Varify cl))) res))
(check-tautology '[] #false)
(check-tautology `[,ltrue] #true)
(check-tautology `[,(lnot lfalse)] #true)
(check-tautology '[a] #false)
(check-tautology '[a a] #false)
(check-tautology '[a (not a)] #true)
(check-tautology '[a b (not c)] #false)
(check-tautology '[a b (not a)] #true)
(check-tautology '[a (not (a a)) (a b) (not (a (not a)))] #false)
(check-tautology '[a (a a) b c (not (a a))] #true)
(check-tautology `[(a b) b (not (b a)) (not (b b)) (not (a c)) (not (a ,(Var 'b)))] #false)
)
(begin
(define-simple-check (check-remove-duplicate-literals cl res)
(check-equal? (remove-duplicate-literals (sort-clause (Varify cl)))
(sort-clause (Varify res))))
(check-remove-duplicate-literals '(a (p X) (q X) (p X) (p Y) a (p a) a b)
'(a b (p a) (p X) (p Y) (q X))))
(begin
;; NOTICE: symbol-tree->clause first performs some simplications (which themselves call
;; clause-implies/1pass)
;; Equivalences
(for ([(A B) (in-dict '(([] . [] ) ; if empty clause #true, everything is #true
([p] . [p] )
([(p X)] . [(p X)] )
([(p X)] . [(p Y)] )
([(not (p X))] . [(not (p X))] )
([(p X) (q X)] . [(p X) (q X) (q Y)] )
))])
(define cl1 (sort-clause (Varify A)))
(define cl2 (sort-clause (fresh (Varify B))))
(check-not-false (clause-subsumes cl1 cl2)
(format "cl1: ~a\ncl2: ~a" cl1 cl2))
(check-not-false (clause-subsumes cl2 cl1)
(format "cl1: ~a\ncl2: ~a" cl1 cl2))
)
;; One-way implication (not equivalence)
(for ([(A B) (in-dict '(([] . [p] ) ; if empty clause #true, everything is #true
([p] . [p q] )
([(p X)] . [(p c)] )
([(p X) (p X) (p Y)] . [(p c)] )
([(p X)] . [(p X) (q X)] )
([(p X)] . [(p X) (q Y)] )
([(p X Y)] . [(p X X)] )
([(p X) (q Y)] . [(p X) (p Y) (q Y)] )
([(p X) (p Y) (q Y)] . [(p Y) (q Y) c] )
([(p X Y) (p Y X)] . [(p X X)] )
([(q X X) (q X Y) (q Y Z)] . [(q a a) (q b b)])
([(f (q X)) (p X)] . [(p c) (f (q c))])
; A θ-subsumes B, but does not θ-subsume it 'strictly'
([(p X Y) (p Y X)] . [(p X X) (r)])
))])
(define cl1 (sort-clause (Varify A)))
(define cl2 (sort-clause (fresh (Varify B))))
(check-not-false (clause-subsumes cl1 cl2))
(check-false (clause-subsumes cl2 cl1)))
; Not implications, both ways. Actually, this is independence
(for ([(A B) (in-dict '(([p] . [q])
([(p X)] . [(q X)])
([p] . [(not p)])
([(p X c)] . [(p d Y)])
([(p X) (q X)] . [(p c)])
([(p X) (f (q X))] . [(p c)])
([(eq X X)] . [(eq (mul X0 X1) (mul X2 X3))
(not (eq X0 X2)) (not (eq X1 X3))])
; A implies B, but there is no θ-subsumption
; https://www.doc.ic.ac.uk/~kb/MACTHINGS/SLIDES/2013Notes/6LSub4up13.pdf
([(p (f X)) (not (p X))] . [(p (f (f Y))) (not (p Y))])
))])
(define cl1 (sort-clause (Varify A)))
(define cl2 (sort-clause (fresh (Varify B))))
(check-false (clause-subsumes cl1 cl2)
(list (list 'A= A) (list 'B= B)))
(check-false (clause-subsumes cl2 cl1)
(list A B)))
(let* ()
(define-Vars X Y Z)
(define cl
`((not (incident ,X ,Y))
(not (incident ab ,Y))
(not (incident ab ,Z))
(not (incident ab ,Z))
(not (incident ac ,Y))
(not (incident ac ,Z))
(not (incident ac ,Z))
(not (incident bc a1b1))
(not (line_equal ,Z ,Z))
(not (point_equal bc ,X))))
(define cl2
(sort-clause (fresh (left-substitute cl (hasheq (Var-name X) 'bc
(Var-name Y) 'a1b1)))))
(check-not-false (clause-subsumes cl cl2)))
)
#;
(begin
; This case SHOULD pass, according to the standard definition of clause subsumption based on
; multisets, but our current definition of subsumption is more general (not necessarily in a
; good way.)
; Our definition is based on sets, with a constraint on the number of literals (in
; Clause<=-subsumes).
; This makes it more general, but also not well-founded (though I'm not sure yet whether this is
; really bad).
(check-false (clause-subsumes (clausify '[(p A A) (q X Y) (q Y Z)])
(clausify '[(p a a) (p b b) (q C C)])))
)
(begin
(*debug-level* (debug-level->number 'steps))
(define-simple-check (check-safe-factoring cl res)
(define got (safe-factoring (sort-clause (Varify cl))))
(set! res (sort-clause (Varify res)))
; Check equivalence
(check-not-false (clause-subsumes res got))
(check-not-false (clause-subsumes got res)))
(check-safe-factoring '[(p a b) (p A B)]
'[(p a b)]) ; Note that [(p a b) (p A B)] ≠> (p A B)
(check-safe-factoring '[(p X) (p Y)]
'[(p Y)])
(check-safe-factoring '[(p Y) (p Y)]
'[(p Y)])
(check-safe-factoring '[(p X) (q X) (p Y) (q Y)]
'[(p Y) (q Y)])
(check-safe-factoring '[(p X Y) (p A X)]
'[(p X Y) (p A X)])
(check-safe-factoring '[(p X Y) (p X X)]
'[(p X X)]) ; is a subset of above, so necessarily no less general
(check-safe-factoring '[(p X Y) (p A X) (p Y A)]
'[(p X Y) (p A X) (p Y A)]) ; cannot be safely factored?
(check-safe-factoring '[(p X) (p Y) (q X Y)]
'[(p X) (p Y) (q X Y)]) ; Cannot be safely factored (proven)
(check-safe-factoring '[(leq B A) (leq A B) (not (def B)) (not (def A))]
'[(leq B A) (leq A B) (not (def B)) (not (def A))]) ; no safe factor
(check-safe-factoring '[(p X) (p (f X))]
'[(p X) (p (f X))])
(check-safe-factoring
(fresh '((not (incident #s(Var 5343) #s(Var 5344)))
(not (incident ab #s(Var 5344)))
(not (incident ab #s(Var 5345)))
(not (incident ab #s(Var 5345)))
(not (incident ac #s(Var 5344)))
(not (incident ac #s(Var 5345)))
(not (incident ac #s(Var 5345)))
(not (incident bc a1b1))
(not (line_equal #s(Var 5345) #s(Var 5345)))
(not (point_equal bc #s(Var 5343)))))
(fresh
'((not (incident #s(Var 148) #s(Var 149)))
(not (incident ab #s(Var 149)))
(not (incident ab #s(Var 150)))
(not (incident ac #s(Var 149)))
(not (incident ac #s(Var 150)))
(not (incident bc a1b1))
(not (line_equal #s(Var 150) #s(Var 150)))
(not (point_equal bc #s(Var 148))))))
(check-not-exn (λ () (safe-factoring
(fresh '((not (incident #s(Var 5343) #s(Var 5344)))
(not (incident ab #s(Var 5344)))
(not (incident ab #s(Var 5345)))
(not (incident ab #s(Var 5345)))
(not (incident ac #s(Var 5344)))
(not (incident ac #s(Var 5345)))
(not (incident ac #s(Var 5345)))
(not (incident bc a1b1))
(not (line_equal #s(Var 5345) #s(Var 5345)))
(not (point_equal bc #s(Var 5343))))))))
)
+118
View File
@@ -0,0 +1,118 @@
#lang racket/base
(require (for-syntax racket/base
syntax/parse)
bazaar/debug
define2
define2/define-wrapper
global
racket/list
racket/pretty
rackunit
"../Clause.rkt"
"../clause.rkt"
"../clause-format.rkt"
"../rewrite-tree.rkt"
"../unification.rkt"
(submod "../Clause.rkt" test))
(define-global:boolean *dynamic-ok?* #true
"Use dynamic rules?")
(define (take-at-most l n)
(take l (min (length l) n)))
(define (display-rwtree rwtree #:? [n-max 100])
(define rules (rewrite-tree-rules rwtree))
(define-values (statics dyns)
(partition rule-static?
(filter-not (λ (rl) (lnot? (rule-from-literal rl)))
rules)))
(display-rules (take-at-most (reverse (sort-rules statics)) n-max))
(display-rules (take-at-most (reverse (sort-rules dyns)) n-max))
(when (or (> (length statics) n-max) (> (length dyns) n-max))
(displayln "(output truncated because there are too many rules)"))
(pretty-write (rewrite-tree-stats rwtree)))
;; Adds an equivalence as rules.
;; For testing purposes.
(define (add-equiv! rwtree equiv)
(define C (make-Clause (clausify (list (lnot (first equiv)) (second equiv)))))
(force-add-binary-Clause! rwtree C))
(define (rewrite-literal rwt lit)
(define-values (new-lit rls) (binary-rewrite-literal rwt lit #false))
new-lit)
;; Given a set of implications, generate equivalence
(define (equivs->rwtree equivs
#:? [dynamic-ok? (*dynamic-ok?*)]
#:? [atom<=> (get-atom<=>)])
(define rwt (make-rewrite-tree #:atom<=> atom<=> #:dynamic-ok? dynamic-ok?))
(for ([equiv (in-list equivs)])
(add-equiv! rwt equiv)
(add-equiv! rwt (map lnot equiv)))
rwt)
(define-syntax (test-confluence stx)
(syntax-parse stx
[(_ equivs expected-stats #:with rwt body ...)
#'(let ()
(define rwt (equivs->rwtree equivs))
(rewrite-tree-confluence! rwt)
(define stats (rewrite-tree-stats rwt))
(unless (equal? stats expected-stats)
(display-rwtree rwt))
(check-equal? stats expected-stats)
body ...)]
[(_ equivs expected-stats)
#'(test-confluence equivs expected-stats #:with _rwt)]))
(with-globals ([*bounded-confluence?* #true]
[*dynamic-ok?* #false])
;; This induction does work and is not subsumed.
;; This is possibly the minimal induction scheme (that doesn't lead to subsumed rules).
(test-confluence
'([(p A (f B)) (p A B)]
[(p C C) d]) ; not left linear
; Should not produce longer rules than the parents!
'((rules . 6)
(unit-rules . 0)
(binary-rules . 6)
(binary-rules-static . 6)
(binary-rules-dynamic . 0)))
(test-confluence
'([(p (f (f (f z))) (f (f (f z)))) (g (g (g b)))] ; should -> b
[(p (f (f (f z))) (f (f (f X)))) b]
[(p (f (f z)) (f (f X))) c]
[(p (f z) (f X)) d]
[(p X X) (q X)])
'((rules . 18) ; 16 also ok
(unit-rules . 0)
(binary-rules . 18)
(binary-rules-static . 18)
(binary-rules-dynamic . 0))
#:with rwt
(check-equal? (rewrite-literal rwt '(p z z)) '(q z))
(check-equal? (rewrite-literal rwt '(p (f (f (f z))) (f (f (f a))))) 'b)
(check-equal? (rewrite-literal rwt '(p (f (f (f z))) (f (f (f z))))) 'b))
(test-confluence
'([(p a X) q]
[(p X a) f]
[(p a a) (g b)])
'((rules . 8)
(unit-rules . 0)
(binary-rules . 8)
(binary-rules-static . 8)
(binary-rules-dynamic . 0))
#:with rwt
(check-equal? (rewrite-literal rwt '(p a a)) 'f)
(check-equal? (rewrite-literal rwt '(p a b)) 'f)
(check-equal? (rewrite-literal rwt '(g b)) 'f)
(check-equal? (rewrite-literal rwt 'q) 'f))
)
+34
View File
@@ -0,0 +1,34 @@
#lang racket/base
(require racket/list
racket/port
rackunit
"../interact.rkt")
(define-syntax-rule (check-interact in out args ...)
(check-equal?
(with-output-to-string
(λ ()
(with-input-from-string (string-append in "\n\n") ; ensure no read loop
(λ () (interact args ...)))))
out))
(define-namespace-anchor ns-anchor) ; optional, to use the eval command
(let ([x 2] [y 'a])
(check-interact
"x\ny\nx 3\nx"
"2\n'a\n3\n"
#:prompt ""
#:variables (x y)))
(let ([x 3] [y 'a])
(check-interact
"yo\nyo 4\nx\nx 2\nx"
"yo\n(yo yo yo yo)\n3\n2\n"
#:prompt ""
#:namespace-anchor ns-anchor
#:variables (x y)
;; All patterns must be of the form (list ....)
[(list 'yo) "prints yo" (displayln "yo")]
[(list 'yo (? number? n)) "prints multiple yos" (displayln (make-list n 'yo))]))
+5
View File
@@ -0,0 +1,5 @@
#lang racket/base
(require "../misc.rkt"
rackunit)
+250
View File
@@ -0,0 +1,250 @@
#lang racket/base
(require bazaar/debug
(except-in bazaar/order atom<=>)
racket/list
racket/file
racket/pretty
racket/random
rackunit
"../Clause.rkt"
"../clause.rkt"
"../misc.rkt"
"../rewrite-tree.rkt"
"../unification.rkt" ; for atom1<=>
(submod "../Clause.rkt" test))
(*debug-level* 0)
(define-check (check-rewrite rwtree c crw)
(define C (Clausify c))
(define Crw (binary-rewrite-Clause rwtree C))
(define crw-sorted (sort-clause crw))
(unless (equal? (Clause-clause Crw) crw-sorted)
(eprintf "c-sorted : ~a\ncrw-sorted: ~a\n" (Clause-clause C) crw-sorted)
(eprintf (Clause-ancestor-graph-string Crw))
(fail-check)))
(define-simple-check (check-not-rewrite rwtree c)
(check-rewrite rwtree c c))
;;; Self-equivalence
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define C1 (make-Clause (clausify '[(not (eq A B)) (eq B A)])))
(define rls1 (Clause->rules C1 C1 #:atom<=> atom1<=>))
(rewrite-tree-add-binary-Clause! rwtree C1 C1)
(check-equal? (rewrite-tree-count rwtree) 2)
; Rewrite clause in lexicographical order
(check-rewrite rwtree '[(eq b a)] '[(eq a b)]))
;;; Adding two converse implications
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define C1 (make-Clause (clausify '[(not (p A A)) (q A)])))
(define C2 (make-Clause (clausify '[(not (q A)) (p A A)])))
(define rls1 (Clause->rules C1 C2 #:atom<=> atom1<=>))
(rewrite-tree-add-binary-Clause! rwtree C1 C2)
; This is not needed because both polarities are considered by Clause->rules:
(rewrite-tree-add-binary-Clauses! rwtree (list C2) C1 #:rewrite? #true)
(check-equal? (rewrite-tree-count rwtree) 2)
(check-rewrite rwtree '[(p a a) (z c)] '[(q a) (z c)])
(check-rewrite rwtree '[(not (p a a)) (z c)] '[(not (q a)) (z c)]))
;;; Adding rules where the converse implication is more general
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define Crules
(map (compose make-Clause clausify)
'([(not (p A A)) (q A)]
[(not (q a)) (p a a)]
[(not (q b)) (p b b)])))
(for ([C (in-list (rest Crules))])
(rewrite-tree-add-binary-Clause! rwtree C (first Crules)))
(check-equal? (rewrite-tree-count rwtree) 4)
(check-rewrite rwtree '[(p a a) (z c)] '[(q a) (z c)])
(check-rewrite rwtree '[(not (p a a)) (z c)] '[(not (q a)) (z c)])
(check-rewrite rwtree '[(p b b) (z c)] '[(q b) (z c)])
(check-rewrite rwtree '[(not (p b b)) (z c)] '[(not (q b)) (z c)])
(check-not-rewrite rwtree '[(p x x) (z c)]))
;;; The same with add-binary-Clauses
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define Crules
(map (compose make-Clause clausify)
'([(not (p A A)) (q A)]
[(not (q a)) (p a a)]
[(not (q b)) (p b b)])))
(rewrite-tree-add-binary-Clauses! rwtree (rest Crules) (first Crules))
(check-equal? (rewrite-tree-count rwtree) 4)
(check-rewrite rwtree '[(p a a) (z c)] '[(q a) (z c)])
(check-rewrite rwtree '[(not (p a a)) (z c)] '[(not (q a)) (z c)])
(check-not-rewrite rwtree '[(p x x) (z c)]))
;;; Dynamic, non-self-converse Clauses, leading to 4 rules
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define C1 (make-Clause (clausify '[(not (p A B C)) (p C A B)])))
(define C2 (make-converse-Clause C1))
(define rls1 (Clause->rules C1 C2 #:atom<=> atom1<=>))
(rewrite-tree-add-binary-Clause! rwtree C1 C2)
(check-equal? (rewrite-tree-count rwtree) 4)
(check-rewrite rwtree '[(p a b c)] '[(p a b c)])
(check-rewrite rwtree '[(p c a b)] '[(p a b c)])
(check-rewrite rwtree '[(p b c a)] '[(p a b c)])
(check-rewrite rwtree '[(p b a c)] '[(p a c b)]))
;;; Some random testing to make sure atom<=> has the Groundedness property.
(define (random-atom)
(define syms '(aaa a p q r z zzz))
(define choices (append syms syms '(NV OV L L))) ; reduce proba of NV and OV
(define vars '())
(let loop ()
(define r (random-ref choices))
(case r
[(NV) ; new var
(define v (new-Var))
(set! vars (cons v vars))
v]
[(OV) (if (empty? vars) (loop) (random-ref vars))] ; old vars
[(L) (cons (random-ref syms) ; first element must be a symbol
(build-list (random 4) (λ (i) (loop))))]
[else r])))
(define random-atom-bank
(remove-duplicates (build-list 1000 (λ _ (random-atom)))))
(debug-vars (length random-atom-bank))
(define (check-groundedness atom<=> lita litb)
(define from<=>to (atom<=> lita litb))
; no point in testing groundedness if we don't have from literal< to
(assert (order<? from<=>to) lita litb from<=>to)
(define vs (vars (list lita litb)))
(define s (make-subst))
(for ([v (in-list vs)])
(subst-set!/name s v (random-ref random-atom-bank)))
(define lita2 (substitute lita s))
(define litb2 (substitute litb s))
(check-equal? (atom<=> lita2 litb2) '<))
(for ([i 10000])
(apply check-groundedness atom1<=> (Varify '[(eq A B) (eq A (mul B a))]))
(apply check-groundedness atom1<=> (Varify '[(eq A B a) (eq A (mul B a))])))
; IMPORTANT CASE: Check circularity of the rules
; Imagine we have two clauses:
; c1 = p | q
; c2 = ~p | ~q
; They are converse implications.
; From c1 we can generate the rules:
; r1 = ~p → q
; r2 = ~q → p
; and from c2 we can generate:
; r3 = p → ~q
; r4 = q → ~p
; If we choose {r1, r4} or {r2, r3} we run in circles!
; Hence the valid choices are {r2, r4} and {r1, r2}
; {r2, r4} is justified by removing negations and considering 'p < 'q.
; {r1, r2} is justified by considering that the negated atoms 'weigh' more.
;
; Now if the two clauses are:
; c3 = ~p | q with rules p → q and ~q → ~p
; c4 = p | ~q with rules ~p → ~q and q → p
; Now we should choose q → p and ~q → ~p to avoid running in circles.
(for* ([lits (in-list '( (p q) ; + ((not p) (not q))
(p (not q))
((distinct_points A B) (equal_points A B))
((distinct_points A B) (not (equal_points A B)))
))]
[r1 (in-list
(Clause->rules (Clausify lits) #false #:atom<=> atom1<=>))]
[r2 (in-list
(Clause->rules (Clausify (map lnot lits)) #false #:atom<=> atom1<=>))])
; The rules should NOT be circular!
(check-not-equal?
(Vars->symbols (list (rule-from-literal r1) (rule-to-literal r1)))
(Vars->symbols (list (rule-to-literal r2) (rule-from-literal r2)))))
;; Saving and loading rules, especially with asymmetric rules.
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define rwtree2 (make-rewrite-tree #:atom<=> atom1<=>))
;; Asymmetric rules
(let ([Conv (Clausify '[(not (p A A)) (q A)])])
(rewrite-tree-add-binary-Clauses! rwtree
(map Clausify
'([(not (q a)) (p a a)]
[(not (q b)) (p b b)]
[(not (q c)) (p c c)]))
Conv))
; Self-converse
(let ([C (Clausify '[(not (eq A B)) (eq B A)])])
(rewrite-tree-add-binary-Clause! rwtree C C))
; Symmetric
(rewrite-tree-add-binary-Clause! rwtree
(Clausify '[(not (pp A A)) (qq A)])
(Clausify '[(pp A A) (not (qq A))]))
(define Crules (rewrite-tree-original-Clauses rwtree))
(define f (make-temporary-file))
(save-rules! rwtree #:rules-file f)
(load-rules! rwtree2 #:rules-file f)
(define Crules2 (rewrite-tree-original-Clauses rwtree2))
(check-equal? (length Crules2) (length Crules))
;; not efficient
(for ([C (in-list Crules)])
(define cl (Clause-clause C))
(check-not-false (for/or ([C2 (in-list Crules2)])
(Clause-equivalence? C C2))
cl)))
;; Tautology reduction
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(rewrite-tree-add-binary-Clause! rwtree
(make-Clause (clausify '[(not (p (p X))) (p X)]))
(make-Clause (clausify '[(p (p X)) (not (p X))])))
(check-equal? (rewrite-tree-stats rwtree)
'((rules . 2)
(unit-rules . 0)
(binary-rules . 2)
(binary-rules-static . 2)
(binary-rules-dynamic . 0)))
; These should be reduced to tautologies and thus not added
(rewrite-tree-add-binary-Clause! rwtree
(make-Clause (clausify '[(not (p (p (p X)))) (p X)]))
(make-Clause (clausify '[(p (p (p X))) (not (p X))])))
(check-equal? (rewrite-tree-stats rwtree)
'((rules . 2)
(unit-rules . 0)
(binary-rules . 2)
(binary-rules-static . 2)
(binary-rules-dynamic . 0))))
;; Tautology reduction by dynamic rule
;; Currently fails
#;
(let ()
(define rwtree (make-rewrite-tree #:atom<=> atom1<=>))
(define Cp (Clausify '[(not (p A B)) (p B A)]))
(rewrite-tree-add-binary-Clause! rwtree Cp Cp)
; What should we do?
; The dynamic rule *can* reduce this to a tautology, but doesn't because
; it can't be ground-oriented.
(check Clause-equivalence?
(binary-rewrite-Clause rwtree (Clausify '[(p A B) (p B A) q]))
(Clausify '[(p A B) q]))
; Same, but after a rewrite
(rewrite-tree-add-binary-Clause! rwtree
(Clausify '[(not (p (f A) B)) (p A B)])
(Clausify '[(p (f A) B) (not (p A B))]))
(check Clause-equivalence?
(binary-rewrite-Clause rwtree (Clausify '[(p (f A) B) (p B A) q]))
(Clausify '[(p A B) q])))
+348
View File
@@ -0,0 +1,348 @@
#lang racket/base
(require (for-syntax syntax/parse
racket/base)
define2
define2/define-wrapper
global
rackunit
racket/dict
racket/pretty
syntax/parse/define
"../clause.rkt"
"../misc.rkt" ; for easy access to *debug-level*
"../rewrite-tree.rkt"
(prefix-in sat: "../saturation.rkt")
"../unification.rkt")
(define-global *cpu-limit* 10
"Time limit in seconds for tests"
number?
string->number)
(define (Vars+clausify-list l)
(map clausify
(symbol-variables->Vars l)))
(define current-saturation-args #false)
(define-syntax (for-in-list* stx)
(syntax-parse stx
[(_ ([var x ...] ... clauses ...) body ...)
#:with (name ...) (generate-temporaries #'(var ...))
#'(for ((~@ [var (in-list (list x ...))]
[name (in-list '(x ...))]
#:when #true)
...)
(set! current-saturation-args (list (cons 'var name) ...))
body ...)]))
;; Print additional information
(define old-check-handler (current-check-handler))
(current-check-handler
(λ (e)
(eprintf (pretty-format current-saturation-args))
(eprintf "\n")
(old-check-handler e)))
;; USE THIS FOR DEBUGGING
(define-simple-macro (replay-on-failure body ...)
(let ([old-check-handler (current-check-handler)])
(parameterize ([current-check-handler
(λ (e)
(old-check-handler e)
(eprintf
"Some checks have failed. Replaying in interactive mode for debugging.\n")
(*debug-level* 3)
(*cpu-limit* +inf.0)
(let () body ...))])
; encapsulated to avoid collisions
(let () body ...))))
(for-in-list* ([l-res-pruning? #true #false]
[neg-lit-select? #true #false]
[atom<=> KBO1lex<=> atom1<=>]
[dynamic-ok? #true #false]
[rwtree-in=out? #true #false] ; false means no search for new rules
;#:unless (and l-res-pruning? neg-lit-select?) ; can't have both at the same time
)
(define-wrapper (saturation
(sat:saturation input-clauses
#:? [step-limit 200]
#:? [memory-limit 4096] ; in MB
#:? [cpu-limit (*cpu-limit*)] ; in seconds
#:? [rwtree (make-rewrite-tree #:atom<=> atom<=>
#:dynamic-ok? dynamic-ok?)]
#:? [rwtree-out (and rwtree-in=out? rwtree)]
#:? backward-rewrite?
#:? age:cost
#:? cost-type
#:? [disp-proof? #false]
#:? [L-resolvent-pruning? l-res-pruning?]
#:? [negative-literal-selection? neg-lit-select?]))
#:call-wrapped call
(define res (call))
(unless l-res-pruning?
(check-equal? (dict-ref res 'L-resolvent-pruning) 0))
(unless dynamic-ok?
(check-equal? (dict-ref res 'binary-rules-dynamic) 0))
(unless rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 0)
(check-true (= (dict-ref res 'binary-rewrites) 0)))
res)
;; Some refutation tests
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [] )))
'status)
'refuted)
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [p] )))
'status)
'saturated)
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [p]
[(not p)])))
'status)
'refuted)
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [p]
[(not q)])))
'status)
'saturated)
;; WARNING (TODO): If L-resolvents-pruning applied to input clauses too,
;; it would discard the 2nd clause immediately and would saturate!
(replay-on-failure
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [(p z)]
[(not (p X)) (p (s X))]
[(not q)])))
'status)
'steps))
;; Russell's 'paradox', requires factoring:
(check-equal?
(dict-ref (saturation (Vars+clausify-list
'( [(s X X) (s b X)]
[(not (s X X)) (not (s b X))])))
'status)
'refuted)
;; Second version
(check-equal?
(dict-ref (saturation (Vars+clausify-list
'( [(s X b) (s b X)]
[(not (s X b)) (not (s b X))])))
'status)
'refuted)
; TPTP 100k idx = 348
(check-equal?
(dict-ref (saturation (Vars+clausify-list
'( [(big_f T0_0 T0_1) (big_g T0_0 T0_2)]
[(big_f T1_0 T1_1) (not (big_g T1_0 T1_0))]
[(big_g T2_0 T2_1) (not (big_f T2_0 T2_2))]
[(not (big_f T3_0 T3_1)) (not (big_g T3_0 (esk1_1 T3_0)))])))
'status)
'refuted)
; TPTP 100k idx = 784
(check-equal?
(dict-ref (saturation (Vars+clausify-list '( [p1 p2]
[p1 (not p2)]
[p2 (not p1)]
[(not p1) (not p2)])))
'status)
'refuted)
; TPTP 100k idx = 117
(check-equal?
(dict-ref (saturation
(Vars+clausify-list
'( [(big_f T0_0 T0_1 (esk3_2 T0_0 T0_1))]
[(big_f esk1_0 esk2_0 esk2_0) (not (big_f esk1_0 esk1_0 esk2_0))]
[(big_f esk1_0 esk1_0 esk2_0)
(big_f esk1_0 esk2_0 esk2_0)
(not (big_f esk2_0 esk2_0 T2_0))]
[(big_f esk1_0 esk1_0 esk2_0)
(big_f esk2_0 T3_0 T3_1)
(not (big_f esk1_0 esk2_0 esk2_0))]
[(not (big_f esk1_0 esk1_0 esk2_0))
(not (big_f esk1_0 esk2_0 esk2_0))
(not (big_f esk2_0 esk2_0 T4_0))]
[(big_f esk1_0 esk2_0 esk2_0)
(not (big_f T5_0 T5_1 (esk3_2 T5_1 T5_0)))
(not (big_f esk1_0 esk1_0 esk2_0))]
[(big_f esk1_0 esk1_0 esk2_0)
(not (big_f T6_0 (esk3_2 T6_0 T6_1) (esk3_2 T6_0 T6_1)))
(not (big_f esk1_0 esk2_0 esk2_0))] )))
'status)
'refuted)
(check-equal?
(dict-ref
(saturation
(Vars+clausify-list
'([(p X) (not (p (p X)))]
[(not (p a))]
[(not (q a))]
[(q X) (not (q (q (q X))))]
[(q (q (q (q (q (q (q (q (q (q (q (q (q (q (q a)))))))))))))))])))
'status)
'refuted)
;; This problem shows there may be some loops with implication-removal and factoring!
(check-equal?
(dict-ref
(saturation
(Vars+clausify-list
'([(not (p X Y)) (p X Z) (p Z Y)]
[(p x x)]
[(not (q a a a a b b b b c c c c))]
[(q A A A A B B B B C C C C) (not (q (q A A A A) (q B B B B) (q C C C C)))]
[(q (q a a a a) (q b b b b) (q c c c c))])))
'status)
'refuted)
;; Binary rewrite
;; This example shows that *not* backward rewriting rules can be a problem:
;; Around step 19, there should be immediate resolution to '() with an active clause.
;; But because [(not (p a A))] has not been rewritten to [(notp a A)],
;; it cannot unify to '() immediately, and must wait for a *resolution* between
;; the rule and the clause to pop up from the queue.
(replay-on-failure
(define res
(saturation
(map clausify
'(((notp A B) (p A B)) ; axiom, binary clause
((not (notp A B)) (not (p A B))) ; axiom, converse binary clause
((p a A) (q b B) (r c C) (s d D)) ; these two clauses should resolve to '() immediately
((not (p A a))) ; Note that 'a A' is to prevent unit-clause rewrites
((not (q B b)))
((not (r C c)))
((not (s D d)))
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check > (dict-ref res 'unit-rules) 0)
(check-equal? (dict-ref res 'binary-rules) 2)
(check > (dict-ref res 'binary-rewrites) 0)))
;; 'Asymmetric' rules
(replay-on-failure
(define res
(saturation
(map clausify
'([(not (p A A)) (q A)] ; Not a rule in itself (too general), but enables the next ones
[(p a a) (not (q a))] ; rule (p a a) <-> (q a)
[(p b b) (not (q b))] ; rule (p b b) <-> (q b)
[(p a a) (p b b) (p c c)]
[(not (q a)) (remove-me x Y)]
[(not (q b)) (remove-me x Y)]
[(not (p c c)) (remove-me x Y)]
[(not (remove-me X y))] ; defeats urw
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 4)
(check-true (> (dict-ref res 'binary-rewrites) 0))))
;; Same but with rules loaded from a file
;; TODO
#;
(replay-on-failure
(define res
(saturation
(map clausify
'([(not (p A A)) (q A)] ; Not a rule in itself (too general), but enables the next ones
[(p a a) (not (q a))] ; rule (p a a) <-> (q a)
[(p b b) (not (q b))] ; rule (p b b) <-> (q b)
[(p a a) (p b b) (p c c)]
[(not (q a)) (remove-me x Y)]
[(not (q b)) (remove-me x Y)]
[(not (p c c)) (remove-me x Y)]
[(not (remove-me X y))] ; defeats urw
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 4)
(check-true (> (dict-ref res 'binary-rewrites) 0))))
;; Greedy selection of binary rewrites can lead to failure
(replay-on-failure
(define res
(saturation
(map clausify
'(; equivalences
[(not (q A B C D)) (p A B C)] ; (q A B C D) <=> (p A B C)
[(q A B C D) (not (p A B C))]
[(not (p A b C)) (t a)] ; (p A b C) <=> (t a)
[(p A b C) (not (t a))]
[(not (q A B c D)) (s b c)] ; (q A b c D) <=> (s b c)
[(q A B c D) (not (s b c))]
; inputs
; may be rewritten to (s b c)
[(q a b c d) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
[(not (t a)) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
;
[(not (remove-me X y))] ; defeats urw
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 6)
(check-true (> (dict-ref res 'binary-rewrites) 0))))
;; Overlapping rewrites can lead to failures (but not without rewrites)
(replay-on-failure
(define res
(saturation
(map clausify
'(; equivalences
[(not (q A B C D)) (p A B C)] ; (q A B C D) <=> (p A B C)
[(q A B C D) (not(p A B C))]
[(not (p A b C)) (t a)] ; (p A b C) <=> (t a)
[(p A b C) (not (t a))]
[(not (q A b c D)) (s b c)] ; (q A b c D) <=> (s b c)
[(q A b c D) (not (s b c))]
; inputs
[(s b c) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
[(not (t a)) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
;
[(not (remove-me X y))] ; defeats urw
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 6)
(check-true (> (dict-ref res 'binary-rewrites) 0))))
;; Greedy selection of overlapping rewrites can lead to failures
(replay-on-failure
(define res
(saturation
(map clausify
'(; equivalences
[(not (q A B C D)) (p A B C)] ; (q A B C D) <=> (p A B C)
[(q A B C D) (not(p A B C))]
[(not (p A b C)) (r A C)] ; (p A b C) <=> (r A C)
[(p A b C) (not (r A C))]
[(not (r A c)) (t a)] ; (r A c) <=> (t a)
[(r A c) (not (t a))]
[(not (q A b c D)) (s b c)] ; (q A b c D) <=> (s b c)
[(q A b c D) (not (s b c))]
; inputs
[(s b c) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
[(not (t a)) (remove-me x Y) (remove-me y Y) (remove-me z Y)]
;
[(not (remove-me X y))] ; defeats urw
))))
(check-equal? (dict-ref res 'status) 'refuted)
(when rwtree-in=out?
(check-equal? (dict-ref res 'binary-rules) 8)
(check-true (> (dict-ref res 'binary-rewrites) 0)))))
+34
View File
@@ -0,0 +1,34 @@
#lang racket/base
(require "../clause.rkt"
"../unification.rkt")
(define cms current-milliseconds)
;;; Stress test.
;;; There's only one predicate of two arguments.
;;; This takes basically exponential time with n.
;;; All the time is taken by clausify (safe-factoring, which includes subsumption check)
(define (stress n)
(define pre (cms))
(define cl1
(time
(clausify
(fresh ; ensures the names are adequate variable names
(for/list ([i n])
`(eq #s(Var ,i) #s(Var ,(+ i 1))))))))
(define cl2
(time
(clausify
(fresh
(for/list ([i (+ n 1)])
`(eq #s(Var ,i) #s(Var ,(+ i 1))))))))
(void (time (clause-subsumes cl1 cl2)))
(void (time (clause-subsumes cl2 cl1)))
(- (cms) pre))
;; Takes about 10s on my desktop machine for n=40 (subsumes-iter-limit=0).
(for/list ([n (in-list '(10 20 30 40))])
(printf "n = ~a\n" n)
(stress n))
+13
View File
@@ -0,0 +1,13 @@
#lang racket/base
(require "../timeplus.rkt"
rackunit)
(check-equal? (string-drop-common-prefix '("auiebépo" "auiensrt" "au"))
'("iebépo" "iensrt" ""))
(check-equal? (string-drop-common-prefix '("auiebépo" "auiensrt" ))
'("bépo" "nsrt"))
(check-equal? (string-drop-common-prefix '("auiebépo" ))
'(""))
(check-equal? (string-drop-common-prefix '("" ))
'(""))
+81
View File
@@ -0,0 +1,81 @@
#lang racket/base
(require "../trie.rkt"
rackunit
(only-in "../unification.rkt" symbol-variable?)
racket/pretty)
(let ([atrie (make-trie #:variable? symbol-variable?)])
(trie-set! atrie
'(a X (f Y) c)
'A)
(trie-set! atrie
'(a b (f Y) c)
'B)
(trie-set! atrie
'(a b (f Y) E)
'C)
(check-equal?
(sort (trie-ref atrie '(a b (f (g e)) c)) symbol<?)
'(A B C))
(check-equal? (trie-ref atrie '(a Y (f (g e)) c))
'(A))
(check-equal? (trie-ref atrie '(a Y (f (g Y)) c))
'(A))
(check-equal? (trie-ref atrie '(a Y (f (g Y)) Z))
'())
(check-equal? (trie-ref atrie '(a b (f Y) (g e)))
'(C))
(check-equal? (trie-ref atrie '(a (f (g Y)) (f Y) c))
'(A))
#;(pretty-print (trie-root atrie))
#;(displayln (trie-values atrie))
(check-equal? (sort (trie-values atrie) symbol<?)
'(A B C))
(check-equal? (trie-ref atrie '(X X X X)) '())
(check-equal? (sort (trie-inverse-ref atrie '(X X X X)) symbol<?)
'(A B C))
(check-equal? (trie-inverse-ref atrie '(a b (f e) c)) '())
(check-equal? (sort (trie-both-ref atrie '(a Y (f c) c)) symbol<?)
'(A B C))
(check-equal? (sort (trie-both-ref atrie '(a e (f (g X)) c)) symbol<?)
'(A))
(check-equal? (sort (trie-both-ref atrie '(a b (f c) d)) symbol<?)
'(C)))
(let ([atrie (make-trie #:variable? symbol-variable?)])
(trie-set! atrie
'(eq X0 X1)
'A)
(trie-set! atrie
'(eq X0 (mul X1 one))
'B)
(trie-set! atrie
'(eq X0 (mul X1 X0))
'C)
(check-equal? (trie-ref atrie '(eq X Y))
'(A))
(check-equal?
(sort (trie-ref atrie '(eq Y (mul Y one))) symbol<?)
'(A B C)))
;; Trie-traversal
(let ([atrie (make-trie #:variable? symbol-variable?)])
(trie-set! atrie
'(a X (f Y) c)
'A)
(trie-set! atrie
'(a b (f Y) c)
'B)
(trie-set! atrie
'(a b (f Y) E)
'C)
(trie-set! atrie 'X 'D)
(trie-set! atrie 'abc 'E)
(trie-set! atrie '(a B c) 'F)
(trie-set! atrie '(a B c d) 'G)
(trie-set! atrie '(a B) 'H)
(trie-set! atrie '() 'I)
(check-equal? (sort (trie-inverse-ref atrie 'A) symbol<?)
'(A B C D E F G H I)))
+69
View File
@@ -0,0 +1,69 @@
#lang racket/base
(require racket/list
rackunit
"../Clause.rkt"
(submod "../Clause.rkt" test)
"../clause.rkt"
"../unification-tree.rkt")
(let ()
(define utree (make-unification-tree))
(add-Clause! utree (Clausify '[(p A B) (not (q A x B))]))
(check-Clause-set-equivalent?
(utree-resolve+unsafe-factors utree (Clausify '[(not (p a b)) (q e x f) (q g x h) (p c d)])
#:L-resolvent-pruning? #false)
(map Clausify
'([(p c d) (q e x f) (q g x h) (not (q a x b))]
[(p c d) (p g h) (q e x f) (not (p a b))]
[(p c d) (p e f) (q g x h) (not (p a b))])))
(check-Clause-set-equivalent?
(utree-resolve+unsafe-factors utree (Clausify '[(not (p X Y)) (r X Y Y)])
#:L-resolvent-pruning? #false)
(map Clausify '([(r A B B) (not (q A x B))]))))
(let ()
(define utree (make-unification-tree))
(add-Clause! utree (Clausify '[(p A b) (not (q A x c))]))
(define C2 (Clausify '[(not (p a B)) (q d x B)]))
(check-Clause-set-equivalent?
(utree-resolve+unsafe-factors utree C2 #:L-resolvent-pruning? #false)
(map Clausify '([(not (q a x c)) (q d x b)]
[(p d b) (not (p a c))]))))
(define (utree-remove-subsumed! utree cl)
(define C (make-Clause cl))
(utree-inverse-find/remove! utree C Clause<=>-subsumes))
(define (make-utree1)
(define utree (make-unification-tree))
(for-each
(λ (cl) (add-Clause! utree (make-Clause (clausify cl))))
'([(p A) (not (q B))]
[(q A) (r B)]
[(p c) (r b)]))
utree)
(let ()
(define utree (make-utree1))
(define removed (utree-remove-subsumed! utree (clausify '[(q X)])))
(check-equal? (length removed) 1)
(check-equal? (length (utree-remove-subsumed! utree (clausify '[(not (q X))]))) 1)
(check-equal? (length (utree-remove-subsumed! utree (clausify '[(r X)]))) 1)
(check-equal? (append* (trie-values utree)) '()))
(let ()
(define utree (make-utree1))
(define removed (utree-remove-subsumed! utree (clausify '[(p d)])))
(check-equal? (length removed) 0)
(define removed2 (utree-remove-subsumed! utree (clausify '[(p c)])))
(check-equal? (length removed2) 1))
(let ()
(define utree (make-utree1))
(define removed (utree-remove-subsumed! utree (clausify '[(p X)])))
(check-equal? (length removed) 2))
+381
View File
@@ -0,0 +1,381 @@
#lang racket/base
(require (submod bazaar/order test)
racket/match
rackunit
"../unification.rkt")
(check-eq? (Var-name->symbol (symbol->Var-name 'C)) 'C)
(check-eq? (Var-name->symbol (symbol->Var-name 'X0)) 'X0)
(check-eq? (Var-name->symbol (symbol->Var-name 'X1353)) 'X1353)
(check-equal?
(find-var-names (Varify '(p D C A B)))
(map Var-name (Varify '(D C A B))))
(check-equal?
(find-var-names (Varify '(p (q D E) C A B E D D A)))
(map Var-name (Varify '(D E C A B))))
(let ()
(define-check (check/fail-var-occs<=> p q c)
(let ([res (var-occs<=> p q)])
(unless (eq? res c)
(fail-check (format "Params: ~a ~a \nExpected: ~a\nGot: ~a\n" p q c res)))))
(define (check-var-occs<=> p q c)
(let ([p (Varify p)] [q (Varify q)])
(check/fail-var-occs<=> p q c)
(case c
[(<) (check/fail-var-occs<=> q p '>)]
[(>) (check/fail-var-occs<=> q p '<)]
[(= #false) (check/fail-var-occs<=> q p c)])))
(check-var-occs<=> '(p) '(q) '=)
(check-var-occs<=> '(p X) '(q) '>)
(check-var-occs<=> '(p X) '(q X) '=)
(check-var-occs<=> '(p X X) '(q X) '>)
(check-var-occs<=> '(p X Y) '(q X) '>)
(check-var-occs<=> '(p X Y) '(q X Z) #false)
(check-var-occs<=> '(p X X Y) '(q X Z) #false)
(check-var-occs<=> '(p X X Y) '(q X Y) '>)
(check-var-occs<=> '(p X X Y) '(q X Y Y) #false))
(let ()
(check equal? (lnot 'auie) `(not auie))
(check equal? (lnot (lnot 'auie)) 'auie)
(check equal? (lnot lfalse) ltrue)
(check equal? (lnot `(not ,lfalse)) lfalse) ; to fix non-reduced values
(check equal? (lnot `(not ,ltrue)) ltrue) ; to fix non-reduced values
(check equal? (lnot ltrue) lfalse)
(check equal? (lnot (lnot ltrue)) ltrue)
(check equal? (lnot (lnot lfalse)) lfalse)
(check<=> polarity<=> 'a '(not a) '<))
(let ()
(define-simple-check (check-atom1<=> a b res)
(check<=> atom1<=> (Varify a) (Varify b) res))
(check-atom1<=> lfalse 'a '<)
(check-atom1<=> lfalse lfalse '=)
(check-atom1<=> '() '() '=)
(check-atom1<=> '(eq b a) '(eq a b) '>) ; lexicographical order
(check-atom1<=> '(p X Y) '(p Y X) '=) ; no lex order between variables
(check-atom1<=> '(p a X) '(p X a) '=) ; no lex order between variable and symbol
(check-atom1<=> '(p A (q B))
'(p (q A) B)
'=) ; no lex order when variable is involved
(check-atom1<=> '(p A (q b))
'(p (q A) b)
'=) ; ????
(check-atom1<=> '(not (eq X0 X1 X1)) ; var-occs=
'(not (eq X0 X1 X1))
'=)
(check-atom1<=> '(eq X0 X1 X1)
'(not (eq X0 X1 X1))
'=) ; negation should NOT count
;;; This is very important, otherwise the following problem can end with 'saturated:
;;; ((notp A B) (p A B)) ; axiom, binary clause
;;; ((not (notp A B)) (not (p A B))) ; axiom, converse binary clause
;;; ((p a A)) ; these two clauses should resolve to '() immediately
;;; ((not (p A a))) ; Note that 'a A' is to prevent unit-clause rewrites
(check-atom1<=> 'p 'q '<)
(check-atom1<=> '(not p) '(not q) '<)
(check-atom1<=> '(not (eq X0 X1 X1))
'(not (eq X1 X0))
'>)
(check-atom1<=> '(p X Y Z)
'(p X Y one)
'>)
(check-atom1<=> '(p X A one)
'(p X Y one)
'#false)
(check-atom1<=> '(p X one one)
'(p X one)
'>)
(check-atom1<=> '(p X one (q one))
'(p X one one)
'>)
)
;; Tests for KBO
(let ()
(define-simple-check (check-KBO1lex<=> a b res)
(check<=> KBO1lex<=> (Varify a) (Varify b) res))
(check-KBO1lex<=> lfalse 'a '<)
(check-KBO1lex<=> lfalse lfalse '=)
;(check-KBO1lex<=> '() '() '=) ; not a term
(check-KBO1lex<=> '(eq b a) '(eq a b) '>) ; lexicographical order
(check-KBO1lex<=> '(p X Y) '(p Y X) #false) ; commutativity cannot be oriented
(check-KBO1lex<=> '(p a X) '(p X a) #false) ; left->right order: a <=> X -> #false
(check-KBO1lex<=> '(p A (q B))
'(p (q A) B)
'<) ; left->right order: A < (q A)
(check-KBO1lex<=> '(p A (q b))
'(p (q A) b)
'<) ; left->right order: A < (q A)
(check-KBO1lex<=> '(not (eq X0 X1 X1)) ; var-occs=
'(not (eq X0 X1 X1))
'=)
(check-KBO1lex<=> '(eq X0 X1 X1)
'(not (eq X0 X1 X1))
'=) ; negation should NOT count
;;; This is very important, otherwise the following problem can end with 'saturated:
;;; ((notp A B) (p A B)) ; axiom, binary clause
;;; ((not (notp A B)) (not (p A B))) ; axiom, converse binary clause
;;; ((p a A)) ; these two clauses should resolve to '() immediately
;;; ((not (p A a))) ; Note that 'a A' is to prevent unit-clause rewrites
(check-KBO1lex<=> 'p 'q '<) ; lex
(check-KBO1lex<=> '(not p) '(not q) '<) ; lex
(check-KBO1lex<=> '(not (eq X0 X1 X1))
'(not (eq X1 X0))
'>) ; by var-occs and weight
(check-KBO1lex<=> '(p X Y Z)
'(p X Y one)
#false) ; var-occs incomparable
(check-KBO1lex<=> '(p X Y (f Z))
'(p X (f Y) one)
#false) ; var-occs incomparable
(check-KBO1lex<=> '(p X Y (f Z))
'(p X (f Y) Z)
'<) ; same weight, Y < (f Y)
(check-KBO1lex<=> '(p X A one)
'(p X Y one)
#false)
(check-KBO1lex<=> '(p X one one)
'(p X one)
'>)
(check-KBO1lex<=> '(p X one (q one))
'(p X one one)
'>)
(check-KBO1lex<=> '(p A (p B C))
'(p (p A B) C)
'<) ; associativity ok: A < (p A B)
)
(let ()
(check-equal? (term-lex2<=> '(p a) '(p b)) '<))
(let ()
(define-simple-check (check-term-lex<=> a b res)
(let-values ([(a b) (apply values (fresh (Varify (list a b))))])
(check<=> term-lex<=> (Varify a) (Varify b) res)))
(check-term-lex<=> 'a (Var 'X) '>)
(check-term-lex<=> (Var 'X) (Var 'X) '=)
(check-term-lex<=> 'a 'a '=)
(check-term-lex<=> 'a 'b '<)
(define-simple-check (check-literal<=> a b res)
(let-values ([(a b) (apply values (fresh (Varify (list a b))))])
(check<=> literal<=> (Varify a) (Varify b) res)))
(check-literal<=> 'a '(not a) '<)
(check-literal<=> 'a 'b '<)
(check-literal<=> 'z '(not a) '<)
(check-literal<=> '(z b) '(not a) '<)
(check-literal<=> 'a 'a '=)
(check-literal<=> 'z '(a a) '>)
(check-literal<=> '(z z) '(z (a a)) '<))
(let ()
(check-true (literal==? 'a 'a))
(check-true (literal==? (Var 'X) (Var 'X)))
(check-true (literal==? (Var 'X) #s(Var X))) ; prefab
(check-false (literal==? (Var 'X) (Var 'Y)))
(check-false (literal==? (fresh (Var 'X)) (Var 'X)))
(check-false (literal==? 'X (Var 'X))) ; not considered the same??
(check-true (literal==? `(p (f ,(Var 'X) ,(Var 'X)) y) `(p (f ,(Var 'X) ,(Var 'X)) y))))
(let ()
(define-check (test-unify t1 t2 subst)
(let ([t1 (Varify t1)] [t2 (Varify t2)])
(set! subst (subst/#false->imsubst subst))
(define sh (unify t1 t2))
(define sl (subst/#false->imsubst
(and sh
(for/list ([(k v) (in-subst sh)])
(cons (Var k)
(if (already-substed? v)
(already-substed-term v)
v))))))
(unless (equal? sl subst)
(fail-check (format "Expected ~a. Got: ~a\nt1 = ~a\nt2 = ~a\n"
subst sl
t1 t2)))
(when sh
(define r1 (substitute t1 sh))
(define r2 (substitute t2 sh))
(unless (equal? r1 r2)
(fail-check "r1≠r2" sh r1 r2)))))
(test-unify '(p X)
'(p X)
'())
(test-unify '(p (f X) X)
'(p (f a) a)
'((X . a)))
(test-unify '(p (f c) (g X))
'(p Y Y)
#false)
(test-unify '(p X (f X))
'(p a Y)
'((X . a) (Y . (f a))))
(test-unify '(p (f X Y) (f Y Z))
'(p (f (f a) (f b)) (f (f b) c))
'((X . (f a)) (Y . (f b)) (Z . c)))
(test-unify '(p X (p X) a)
'(p Y (p (p Z)) Z)
(if reduce-mgu?
'((Z . a) (X . (p a)) (Y . (p a)))
'((X . Y) (Y . (p Z)) (Z . a))))
(test-unify '(p X (p X) (p (p X)))
'(p a Y Z)
'((X . a) (Y . (p a)) (Z . (p (p a)))))
(test-unify '(p X (p X) (p (p X)))
'(p a (p Y) (p (p Z)))
'((X . a) (Y . a) (Z . a)))
(test-unify '(p (p X) (p X) a)
'(p Y (p (p Z)) Z)
(if reduce-mgu?
'((Z . a) (X . (p a)) (Y . (p (p a))))
'((Y . (p X)) (X . (p Z)) (Z . a))))
(test-unify '(p X X)
'(p a Y)
'((X . a) (Y . a)))
(test-unify '(p X X)
'(p (f Y) Z)
'((X . (f Y)) (Z . (f Y))))
(test-unify '(p X X) '(p (f Y) Y) #false)
(test-unify '(p (f X Y) (g Z Z))
'(p (f (f W U) V) W)
(if reduce-mgu?
'((W . (g Z Z)) (Y . V) (X . (f (g Z Z) U)))
'((X . (f W U)) (Y . V) (W . (g Z Z)))))
(test-unify '(eq X30 (mul X31 (mul X32 (inv (mul X31 X32)))))
'(eq (mul X25 one) X26)
`((X26 . (mul X31 (mul X32 (inv (mul X31 X32)))))
(X30 . (mul X25 one))))
(test-unify '(p A B)
'(p B A)
'((A . B)))
)
(let ()
(define (test-suite-left-unify left-unify)
(define-simple-check (test-left-unify t1 t2 subst)
(let ([t1 (Varify t1)] [t2 (Varify t2)])
(set! subst (subst/#false->imsubst subst))
(define sh (left-unify t1 t2))
(define sl (subst/#false->imsubst sh))
(check-equal? sl subst
(format "Expected ~a. Got: ~at1 = ~a\nt2 = ~a\n"
subst sl
t1 t2))
(when sh
(define r1 (left-substitute t1 sh))
(check-equal? r1 t2 (format "r1≠t2\nsh=~a\nr1=~a\nt2=~a\n" sh r1 t2)))))
(test-left-unify '(p (f X) X)
'(p (f a) a)
'((X . a)))
(test-left-unify '(p (f c) (g X))
'(p Y Y)
#false)
(test-left-unify '(p X (f X)) '(p a Y) #false)
(test-left-unify '(p (f X Y) (f Y Z))
'(p (f (f a) (f b)) (f (f b) c))
'((Z . c) (Y . (f b)) (X . (f a))))
(test-left-unify '(p X X) '(p a Y) #false)
(test-left-unify '(p X X) '(p (f Y) Z) #false)
(test-left-unify '(p X X) '(p (f Y) Y) #false)
(test-left-unify '(p (f X Y) (g Z Z))
'(p (f (f W U) V) W)
#false)
(test-left-unify '(p X X)
'(p A B)
#false)
; This MUST return false because of the circularity.
; The found substitution must be specializing, that is C2[σ] = C2 (and C1[σ] = C2),
; otherwise safe factoring can fail, in particular.
; Hence we must ensure that vars(C2) ∩ dom(σ) = ø.
(test-left-unify '(p A B)
'(p B A)
#false)
(test-left-unify '(p B A)
'(p A B)
#false)
(test-left-unify '(p A A)
'(p B B)
'((A . B)))
(test-left-unify '(p A)
'(p A)
'())
(test-left-unify '(p a)
'(p a)
'())
(test-left-unify '(p A X) ;; WARNING
'(p X Y)
#false))
(test-suite-left-unify left-unify)
(test-suite-left-unify (λ (t1 t2) (define subst-assoc (left-unify/assoc t1 t2))
(and subst-assoc (make-subst subst-assoc)))))
(let ([pat '(_not_ (_not_ #s(Var A)))]
[t (fresh (Varify '(q (p (_not_ (_not_ (f A B)))))))])
(define s
(left-unify-anywhere pat t))
(check-equal? (left-substitute pat s)
(cadadr t)))
(let ([t '(q (p (_not_ (_not_ (f A B)))))])
(check-equal?
(match-anywhere (match-lambda [`(_not_ (_not_ ,x)) `([x . ,x])] [else #false])
t)
'([x . (f A B)])))
;; Stress test for unification
;; This should take 0ms
;; See https://en.wikipedia.org/wiki/Unification_(computer_science)
;; #Examples_of_syntactic_unification_of_first-order_terms
(let ()
(define last-var? #true)
(define (stress-unify n)
(define A
(let left ([d 1])
(if (>= d n)
(list '* (Var d) (if last-var? (Var (+ d 1)) 'a))
(list '* (left (+ d 1)) (Var d)))))
(define B
(let right ([d 1])
(if (>= d n)
(list '* (if last-var? (Var (+ d 1)) 'a) (Var d))
(list '* (Var d) (right (+ d 1))))))
(define subst (time (unify A B)))
; Verify that there's only 1 variable in the each rhs
(when (and reduce-mgu? last-var?)
(check-equal? (length (Vars (map cdr (subst->list subst))))
1
subst))
(time (substitute A subst)))
(for ([n (in-range 30 50)])
(printf "~a: " n)
(stress-unify n)))
+218
View File
@@ -0,0 +1,218 @@
#lang racket/base
;**************************************************************************************;
;**** Tptp Input/Output Format ****;
;**************************************************************************************;
(require bazaar/debug
bazaar/string
global
racket/dict
racket/file
racket/format
racket/list
racket/match
racket/port
racket/string
satore/clause
satore/unification)
(provide (all-defined-out))
(define-global:boolean *tptp-out?* #false
"Output is in TPTP format?")
#|
File formats:
.rktd: Racket data, one Racket clause per line.
.p: Prolog format, with Prolog clauses that contain tptp (FOL) clauses.
.tptp: only the tptp clauses, one per line.
|#
(define (tptp-program-file->clauses program-file)
; Not efficient: Loads the whole program as a string then parses it.
; It would be more efficient to read it as a stream with an actual parser.
; Another possibility is to read it line by line and parse each line as a cnf(…)
; but that will file if the cnf(…) is multiline.
(tptp-prog->clauses (file->string program-file)))
(define (tptp-pre-clauses->clauses pre-clauses)
(define clauses
(for/list ([cl (in-list pre-clauses)])
(let loop ([t cl])
(match t
[(? symbol? x) x]
[(? string? x)
(string->symbol (string-append "_str_" x))] ; to avoid being interpreted as a variable
['() '()]
[(list '~ (? symbol? pred) (list a ...) r ...)
(cons (list 'not (cons (loop pred) (loop a)))
(loop r))]
[(list (? symbol? pred) (list a ...) r ...)
(cons (cons (loop pred) (loop a))
(loop r))]
[(list '~ x r ...)
(cons (list 'not (loop x))
(loop r))]
[(list x a ...)
(cons (loop x) (loop a))]
[else (error "Unrecognized token: " t)]))))
(map (compose clausify symbol-variables->Vars) clauses))
;; Prolog .p program to rkt format
(define (tptp-prog->clauses str)
; hardly tested and not strict enough
; It should be mostly robust to line breaking though.
; Doesn't parse strings properly (will remove lines that look like comments in multiline strings)
(define l
(filter
(λ (x)
(if (list? x)
x
(begin
(assert (memq x '(cnf end_cnf))
x)
#false)))
; Ensure operators are surrounded with spaces
; turn racket special symbols (| and ,) into normal symbols.
; then use racket's reader to parse it like an s-expression
(string->data
(regexp-replaces
str
(list*
'[#px"(?:^|\n)\\s*[%#][^\n]*" "\n"] ; prolog and shell/python/eprover full-line comments
'[#px"\\bnot\\b" "_not_"] ;; WARNING!!! replace lnot with $not instead (as in TPTP)
(map (λ (p) (list (regexp-quote (first p))
(string-append " " (regexp-replace-quote (second p)) " ")))
'(["|" "" ]
["&" "" ]
["," "" ]
["$false" ""] ; empty literal
["~" "~"]
["." "end_cnf"]
["'" "\""])))))))
; first is name, second is type, third is clause, rest is comments(?)
(define pre-clauses (map third l))
(tptp-pre-clauses->clauses pre-clauses))
;; Simple parser for the proposer output into s-exp clauses.
;; The format is expected to be in cnf.
(define (tptp-string->clauses str)
; TODO: Optimize. This can be veeeery slow for large conjectures.
(define pre-clauses
(append*
; split first to avoid regenerating the whole string after each substitution?
(for/list ([str (in-list (string-split str #px"&|\n"))]) ; & and \n play the same role
(with-handlers ([exn? (λ (e) (displayln str) (raise e))])
(string->data
; Ensure operators are surrounded with spaces
; turn racket special symbols (| and ,) into normal symbols
(regexp-replaces
str
(list*
'[#px"\\bnot\\b" "_not_"] ;; WARNING!!! Instead: replace lnot with $not (as TPTP)
(map (λ (p) (list (regexp-quote (first p))
(string-append " " (regexp-replace-quote (second p)) " ")))
'(["|" ""]
["," ""]
["~" "~"]
["'" "\""])))))))))
(tptp-pre-clauses->clauses pre-clauses))
(define (literal->tptp-string lit)
(cond
[(lnot? lit)
(string-append "~ " (literal->tptp-string (second lit)))]
[(empty? lit)
"$false"]
[(list? lit)
(string-append (literal->tptp-string (first lit))
"("
(string-join (map literal->tptp-string (rest lit)) ", ")
")")]
[(Var? lit) (symbol->string (Var-name->symbol lit))]
[else (format "~a" lit)]))
(define (clause->tptp-string cl)
(string-join
(map literal->tptp-string (Vars->symbols cl))
" | "))
(define (clauses->tptp-string cls)
(string-join (map clause->tptp-string cls) "\n"))
;; String replacement of tptp names with shorter ones to improve readability
(define (tptp-shortener str)
(define substs
(sort
(map (λ (p) (cons (~a (car p)) (~a (cdr p))))
; fld_1
(append
'((multiplicative_identity . _1)
(additive_identity . _0)
(less_or_equal . )
(additive_inverse . )
(multiplicative_inverse . /)
(equalish . )
(multiply . ×)
(product . ×=)
(inverse . /)
(add . +)
)
;grp_5
'((equalish . )
(multiply . ×)
(product . ×=)
(inverse . /)
(identity . _1)
)
; geo
'((convergent_lines . /\\)
(unorthogonal_lines . ¬⊥)
(orthogonal_through_point . ⊥_thru_pt)
(parallel_through_point . //_thru_pt)
(distinct_lines . ≠_ln)
(apart_point_and_line . ≠_pt_ln)
(orthogonal_lines . )
(distinct_points . ≠_pt)
(parallel_lines . //)
(equal_lines . =_ln)
(equal_points . =_pt)))
)
; forces prefixes to appear later to match longer strings first:
> #:key (compose string-length car)))
(string-join
(for/list ([line (in-lines (open-input-string str))])
(for/fold ([line line])
([(from to) (in-dict substs)])
(string-replace line from to #:all? #true)))
"\n"))
(define-syntax-rule (with-tptp-shortener body ...)
(let ([str (with-output-to-string (λ () body ...))])
(displayln (tptp-shortener str))))
;============;
;=== Main ===;
;============;
(module+ main
(require global
racket/file)
(define-global *rktd-file* #false
"file in rktd format to output in tptp format"
file-exists?
values)
(void (globals->command-line))
(when (*rktd-file*)
(displayln (clauses->tptp-string (file->list (*rktd-file*))))))
+220
View File
@@ -0,0 +1,220 @@
#lang racket/base
;***************************************************************************************;
;**** Trie: Imperfect Discrimination Tree ****;
;***************************************************************************************;
;;; A key is a tree (a list of lists of ...), which is flattened to a list
;;; where parenthesis are replaced with symbols.
;;; Variables are considered to be unnamed and there is no unification/matching.
;;; The only dependency on first-order logic specifics is `variable?`.
(require bazaar/cond-else
racket/list
racket/match
satore/misc)
(provide (except-out (all-defined-out) no-value)
(rename-out [no-value trie-no-value]))
;; Default value at the leaves. Should not be visible to the user.
(define no-value (string->uninterned-symbol "no-value"))
; Tokens used in the keys of the tree
(define anyvar (string->uninterned-symbol "¿"))
(define sublist-begin (string->uninterned-symbol "<<"))
(define sublist-end (string->uninterned-symbol ">>"))
;; edges: hasheq(key . node?)
(struct trie-node (edges value)
#:transparent
#:mutable)
(define (make-node)
(trie-node (make-hasheq) no-value))
;; Trie structure with variables.
(struct trie (root variable?))
(define (make-trie #:constructor [constructor trie]
#:variable? [variable? (λ (x) #false)]
. other-args)
(apply constructor (make-node) variable? other-args))
;; Updates the value of the node for the given key (or add one if none exists).
;; atrie: trie?
;; key: list?
;; val: any/c
(define (trie-update! atrie key update default-val/proc)
(match-define (trie root variable?) atrie)
; The key is `list`ed because we need a list, and this allows the given key to not be a list.
(let node-insert! ([nd root] [key (list key)])
(cond/else
[(empty? key)
; Stop here.
(define old-value (trie-node-value nd))
(set-trie-node-value! nd (update (if (eq? old-value no-value)
(if (thunk? default-val/proc)
(default-val/proc)
default-val/proc)
old-value)))]
#:else ; key is a list
(define k (car key))
(define edges (trie-node-edges nd))
#:cond
[(pair? k)
; Linearize the tree structure of the key.
(define key2 (cons sublist-begin (append k (cons sublist-end (cdr key)))))
(node-insert! nd key2)]
#:else ; nil, atom, variable
(let ([k (if (variable? k) anyvar k)])
(define nd2 (hash-ref! edges k make-node))
(node-insert! nd2 (cdr key))))))
;; Keep a list of values at the leaves.
;; If `trie-insert!` is used, any use of `trie-update!` should be consistent with values being lists.
(define (trie-insert! atrie key val)
(trie-update! atrie key (λ (old) (cons val old)) '()))
;; Replacing the current value (if any) for key with val.
(define (trie-set! atrie key val)
(trie-update! atrie key (λ _ val) #false))
;; Applies on-leaf at each node that match with key.
;; The matching keys of the trie are necessarily no less general than the given key.
(define (trie-find atrie key on-leaf)
(define variable? (trie-variable? atrie))
(let node-ref ([nd (trie-root atrie)] [key (list key)])
(cond/else
[(empty? key)
; Leaf found.
(unless (eq? no-value (trie-node-value nd))
(on-leaf nd))]
#:else
(define k (car key))
(define var-nd (hash-ref (trie-node-edges nd) anyvar #false))
#:cond
[(variable? k)
(when var-nd
; both the key and the node are variables
(node-ref var-nd (cdr key)))]
#:else
(when var-nd
; If a variable matches, consider the two paths.
(node-ref var-nd (cdr key)))
#:cond
[(pair? k)
; Linearize the tree structure of the key.
(define key2 (cons sublist-begin (append k (cons sublist-end (cdr key)))))
(node-ref nd key2)]
#:else
(define nd2 (hash-ref (trie-node-edges nd) k #false))
(when nd2
(node-ref nd2 (cdr key))))))
;; Applies the procedure `on-leaf` to any node for which the key is matched by the given key.
;; The matching keys of the trie are necessarily no more general than the given key.
;; TODO: We could easily maintain a substitution over the branches since there's only one match.
;; on-leaf: (-> node? any/c)
(define (trie-inverse-find atrie key on-leaf)
(define variable? (trie-variable? atrie))
(let node-find ([nd (trie-root atrie)] [key (list key)] [depth 0])
(define edges (trie-node-edges nd))
(cond/else
[(> depth 0)
; If the depth is positive, that means we are currently matching a variable.
; We need to continue through every branch and decrease the depth only if we encounter
; a sublist-end, and increase the counter if we encounter a sublist-begin.
; Note that key can be empty while depth > 0.
(for([(k2 nd2) (in-hash edges)])
(node-find nd2 key
(cond [(eq? k2 sublist-begin) (+ depth 1)]
[(eq? k2 sublist-end) (- depth 1)]
[else depth])))]
[(empty? key)
; Leaf found.
(unless (eq? no-value (trie-node-value nd))
(on-leaf nd))]
#:else
(define k (car key))
#:cond
[(variable? k)
;; Anything matches. For sublist we need to keep track of the depth.
;; Note that variables in the tree can only be matched if k is a variable.
(for ([(k2 nd2) (in-hash edges)])
(node-find nd2 (cdr key) (if (eq? k2 sublist-begin) 1 0)))]
[(pair? k)
; Linearize the tree structure of the key.
(define key2 (cons sublist-begin (append k (cons sublist-end (cdr key)))))
(node-find nd key2 0)]
#:else
(define nd2 (hash-ref edges k #false))
(when nd2
(node-find nd2 (cdr key) 0)))))
;; Both find and inverse-find at the same time.
;; Useful when (full) unification must be performed afterwards.
;; WARNING: A LOT OF CODE DUPLICATION WITH THE ABOVE 2 FUNCTIONS.
(define (trie-both-find atrie key on-leaf)
(define variable? (trie-variable? atrie))
(let node-find ([nd (trie-root atrie)] [key (list key)] [depth 0])
(define edges (trie-node-edges nd))
(cond/else
[(> depth 0)
; If the depth is positive, that means we are currently matching a variable.
; Consume everything until we find a sublist-end at depth 1.
; We need to continue through every branch and decrease the depth only if we encounter
; a sublist-end, and increase the counter if we encounter a sublist-begin.
; Note that key can be empty while depth > 0.
(for([(k2 nd2) (in-hash edges)])
(node-find nd2 key
(cond [(eq? k2 sublist-begin) (+ depth 1)]
[(eq? k2 sublist-end) (- depth 1)]
[else depth])))]
[(empty? key)
; Leaf found.
(unless (eq? no-value (trie-node-value nd))
(on-leaf nd))]
#:else
(define k (car key))
(define var-nd (hash-ref (trie-node-edges nd) anyvar #false))
#:cond
[(variable? k)
;; Anything matches. For sublist we need to keep track of the depth.
;; Note that variables in the tree can only be matched if k is a variable.
(for ([(k2 nd2) (in-hash edges)])
(node-find nd2 (cdr key) (if (eq? k2 sublist-begin) 1 0)))]
#:else
(when var-nd
; The node contains a variable, which thus matches the key.
(node-find var-nd (cdr key) 0))
#:cond
[(pair? k)
; Linearize the tree structure of the key.
(define key2 (cons sublist-begin (append k (cons sublist-end (cdr key)))))
(node-find nd key2 0)]
#:else
(define nd2 (hash-ref edges k #false))
(when nd2
(node-find nd2 (cdr key) 0)))))
(define ((make-proc-tree-ref proc) atrie key)
(define res '())
(proc atrie
key
(λ (nd) (set! res (cons (trie-node-value nd) res))))
res)
;; Returns a list of values which keys are matched by the given key.
;; The matching keys of the trie are necessarily no more general than the given key.
;; TODO: We could easily maintain a substitution over the branches since there's only one mach
(define trie-inverse-ref (make-proc-tree-ref trie-inverse-find))
(define trie-ref (make-proc-tree-ref trie-find))
(define trie-both-ref (make-proc-tree-ref trie-both-find))
(define (trie-values atrie)
(let loop ([nd (trie-root atrie)] [res '()])
(define edges (trie-node-edges nd))
(define val (trie-node-value nd))
(for/fold ([res (if (eq? val no-value) res (cons val res))])
([(key nd2) (in-hash edges)])
(loop nd2 res))))
+346
View File
@@ -0,0 +1,346 @@
#lang racket/base
;**************************************************************************************;
;**** Unification Tree ****;
;**************************************************************************************;
;;; A trie specialized for unifying literals.
;;; This is *different* from "substitution trees"
;;; (https://link.springer.com/chapter/10.1007%2F3-540-59200-8_52)
;;; TODO: This should be probably named a Clause-trie instead, since the
;;; major difference with the trie is that we are dealing with Clauses, which
;;; are lists of literals, and the same Clause can appear in different leaves
;;; of the trie. Unification is only one of the operations performed on Clauses.
;;; * A literal A unifies with a literal B iff there exists a substitution σ s.t. Aσ = Bσ.
;;; * A literal A left-unifies with a literal B iff there exists a substitution σ s.t.
;;; Aσ = B and Bσ = B
;;; The last requirement ensures that left-unifies => unifies.
;;; * We call 'sub-varing' a set of literals As the process of replacing each variable occurrence in
;;; the As with a fresh variable. Hence, if B=subvar(A) then the variables in B occur only once
;;; each in B.
(require bazaar/cond-else
bazaar/debug
bazaar/list
bazaar/loop
bazaar/mutation
(except-in bazaar/order atom<=>)
define2
global
racket/list
satore/Clause
satore/clause
satore/misc
satore/trie
satore/unification)
(provide (all-defined-out)
(all-from-out satore/trie))
(module+ test
(require rackunit))
;; WARNING: This cannot be applied to input clauses.
;; WARNING: To pass Russell's problem, we must
;; do 1-to-N resolution (non-binary resolution), OR, maybe,
;; binary resolution + unsafe factoring, but the 'resolutions'
;; for factoring must be taken into account too.
(define-global:boolean *L-resolvent-pruning?* #false
'("Discard clauses for which a literal leads to 0 resolvents."
"Currently doesn't apply to input clauses."))
(define-counter n-L-resolvent-pruning 0)
;========================;
;=== Unification Tree ===;
;========================;
;; TODO: Fix naming convention on operations on unification-tree. (utree-?)
;; Clause-clause: Clause? -> clause ; extract the clause from the Clause object.
;; This module does not need to know what a Clause? is, it only needs to be given this extraction
;; function. It is however assumed that the Clause is an immutable struct object (or the mutation
;; does not concern Clause-clause).
(struct unification-tree trie () #:transparent)
;; Several leaves may have the same clause-idx but different clauses——well, the same clauses
;; but ordered differently. It's named `uclause` to make it clear it's not a well-formed clause
;; (stands for unordered-clause).
(struct utree-leaf (Clause uclause) #:transparent)
(define (make-unification-tree #:constructor [constructor unification-tree]
. other-args)
(apply make-trie
#:constructor constructor
#:variable? Var?
other-args))
;; Each literal of the clause cl is added to the tree, and the leaf value at each literal lite is the
;; clause, but where the first literal is lit.
;; /!\ Thus the clause is *not* sorted according to `sort-clause`.
;; Note: We could also keep the clause unchanged and cons the index of the literal,
;; that would avoid using up new cons cells, while keeping the clause intact.
(define (add-Clause! utree C)
(define cl (Clause-clause C))
(zip-loop ([(left lit right) cl])
;; *****WARNING*****
;; The key must be a list! what if the literal is a mere symbol??
(define reordered-clause (cons lit (rev-append left right)))
(trie-insert! utree lit (utree-leaf C reordered-clause))))
(define (unification-tree-Clauses utree)
(remove-duplicates (map utree-leaf-Clause (append* (trie-values utree))) eq?))
;; Calls on-unified for each literal of each clause of utree that unifies with lit.
;; If a clause cl has n literals that unify with lit, then `on-unified` is called n times.
;; on-unified : utree-leaf? subst lit1 lit2 other-lit2s -> void?
(define (find-unifiers utree lit on-unified)
(trie-both-find utree lit
(λ (nd)
(define val (trie-node-value nd))
(when (list? val)
(for ([lf (in-list val)])
(define cl (utree-leaf-uclause lf))
; Unify only with the first literal, assuming clauses in node-values
; are so that the first literal corresponds to the key
; (the path from the root)
(define lit2 (first cl))
(define subst (unify lit2 lit))
(when subst
(on-unified lf subst lit lit2 (rest cl))))))))
;; Returns the set of Clauses that *may* left-unify with lit.
;; The returned clauses are sorted according to `sort-clause` and duplicate clauses are removed.
(define (unification-tree-ref utree lit)
; Node values are lists of rules, and trie-ref returns a list of node-values,
; hence the append*.
(remove-duplicates (append* (map utree-leaf-Clause (trie-ref utree lit))) eq?))
;; Helper for the resolve/factors functions below.
;; Defines a new set of Clauses, and a helper function that creates new Clauses,
;; rewrites them, checks for tautologies and add them to the new-Clauses.
(define-syntax-rule (define-add-Clause! C new-Clauses add-Clause! rewriter)
(begin
(define new-Clauses '())
(define (add-Clause! lits subst type parents)
(define cl (clause-normalize (substitute lits subst)))
(define new-C (make-Clause cl (cons C parents) #:type type))
; Rewrite
(let ([new-C (rewriter new-C)])
(unless (Clause-tautology? new-C)
(cons! new-C new-Clauses))))))
(define (utree-resolve/select-literal utree C
#:? [rewriter (λ (C) C)]
#:? [literal-cost literal-size])
(define cl (Clause-clause C))
;; Choose the costliest negative literal if any (for elimination)
(define selected-idx
(for/fold ([best-idx #false]
[best-cost -inf.0]
#:result best-idx)
([lit (in-list cl)]
[idx (in-naturals)]
#:when (lnot? lit)) ; negative literals only
(define c (literal-cost lit))
(if (> c best-cost)
(values idx c)
(values best-idx best-cost))))
(zip-loop ([(left lit right) cl]
[resolvents '()]
[lit-idx 0]
#:result (or resolvents '()))
(cond
[(or (not selected-idx)
(= lit-idx selected-idx))
(define-add-Clause! C new-Clauses add-Clause! rewriter)
; Find resolvents
(find-unifiers utree
(lnot lit)
(λ (lf subst nlit lit2 rcl2)
(add-Clause! (rev-append left (rev-append right rcl2))
subst
'res
(list (utree-leaf-Clause lf)))))
(values (rev-append new-Clauses resolvents)
(+ 1 lit-idx))]
[else
(values resolvents
(+ 1 lit-idx))])))
(define (unsafe-factors C #:? [rewriter (λ (C) C)])
(define-add-Clause! C factors add-Clause! rewriter)
(define cl (Clause-clause C))
(zip-loop ([(left lit1 right) cl])
(define pax (predicate.arity lit1))
(zip-loop ([(left2 lit2 right2) right]
; Literals are sorted, so no need to go further.
#:break (not (equal? pax (predicate.arity lit2))))
(define subst (unify lit1 lit2))
; We could do left-unify instead, but then we need to do both sides,
; at the risk of generating twice as many clauses, so may not be worth it.
(when subst
(add-Clause! (rev-append left right) ; remove lit1
subst
'fac
'()))))
factors)
(define (utree-resolve+unsafe-factors/select utree C #:? rewriter #:? literal-cost)
(rev-append
(unsafe-factors C #:rewriter rewriter)
(utree-resolve/select-literal utree C
#:rewriter rewriter
#:literal-cost literal-cost)))
;; TODO: Deactivate rewriting inside add-candidates!
;; Returns the set of Clauses from resolutions between cl and the clauses in utree,
;; as well as the factors
(define (utree-resolve+unsafe-factors utree C
#:? [rewriter (λ (C) C)]
#:! L-resolvent-pruning?)
;; Used to prevent pruning by L-resolvent-discard.
;; This is used to mark the second literals in unsafe factors.
(define lit-marks (make-vector (Clause-n-literals C) #false))
(define (mark-literal! idx) (vector-set! lit-marks idx #true))
(define (literal-marked? idx) (vector-ref lit-marks idx))
(zip-loop ([(left lit right) (Clause-clause C)]
[resolvents+factors '()]
[lit-idx 0]
#:break (not resolvents+factors) ; shortcut
#:result (or resolvents+factors '()))
(define-add-Clause! C new-Clauses add-Clause! rewriter)
;; Resolutions
(find-unifiers utree
(lnot lit)
(λ (lf subst nlit lit2 rcl2)
(add-Clause! (rev-append left (rev-append right rcl2))
subst
'res
(list (utree-leaf-Clause lf)))))
;; Unsafe binary factors
;; Somewhat efficient implementation since the literals are sorted by predicate.arity.
(define pax (predicate.arity lit))
(zip-loop ([(left2 lit2 right2) right]
[lit2-idx (+ 1 lit-idx)]
#:break (not (equal? pax (predicate.arity lit2))))
(define subst (unify lit lit2))
(when subst
(mark-literal! lit2-idx) ; prevents pruning
(add-Clause! (rev-append left right) ; remove lit
subst
'fac
'()))
(+ 1 lit2-idx))
;; L-resolvent 'pruning'
;; See the principle of implication modulo resolution:
;; "A unifying principle for clause elimination in first-order logic", CADE 26.
;; which contains other techniques and short proofs of their soundness.
;; We return the empty set of resolution, meaning that the selected clause
;; can (must) be discarded, i.e., not added to the active set.
(cond [(and L-resolvent-pruning?
(empty? new-Clauses)
(not (literal-marked? lit-idx)))
(++n-L-resolvent-pruning)
(values #false (+ 1 lit-idx))]
[else
(values (rev-append new-Clauses resolvents+factors)
(+ 1 lit-idx))])))
;; Returns the first (in any order) Clause C2 such that
;; there is a literal of C2 that left-subunifies on a literal of C,
;; and (pred C C2).
(define (utree-find/any utree C2 pred)
(define tested (make-hasheq)) ; don't test the same C2 twice
(define cl2 (Clause-clause C2))
(let/ec return
(for ([lit (in-list cl2)])
(trie-find utree lit
(λ (nd)
(define val (trie-node-value nd))
(when (list? val)
(for ([lf (in-list val)])
(define C (utree-leaf-Clause lf))
(hash-ref! tested
C
(λ ()
(when (pred C C2)
(return C))
#true)))))))
#false))
;; Return all Clauses C that left-subunify on at least one literal and for which (pred C C2).
(define (utree-find/all utree C2 pred)
(define tested (make-hasheq)) ; don't test the same C2 twice
(define cl2 (Clause-clause C2))
(define res '())
(for ([lit (in-list cl2)])
(trie-find utree lit
(λ (nd)
(define val (trie-node-value nd))
(when (list? val)
(for ([lf (in-list val)])
(define C (utree-leaf-Clause lf))
(hash-ref! tested
C
(λ ()
(when (pred C C2)
(set! res (cons C res)))
#true)))))))
res)
;; Removes the Clause C from the utree.
(define (utree-remove-Clause! utree C)
(define cl (Clause-clause C))
(for ([lit (in-list cl)])
(trie-find utree lit
(λ (nd)
(define val (trie-node-value nd))
(when (list? val)
(set-trie-node-value! nd
(filter-not (λ (lf2) (eq? C (utree-leaf-Clause lf2)))
val)))))))
;; Finds the leaves for which C loosely left-unifies on some literal and remove those which clause C2
;; where (pred C C2).
;; Returns the set of Clauses that have been removed.
;; pred: Clause? Clause? -> boolean
(define (utree-inverse-find/remove! utree C pred)
; Since the same Clause may match multiple times,
; We use a hash to remember which clauses have already been tested (and if the result
; was #true or #false).
; Then remove all the leaves of each clause to remove.
(define tested (make-hasheq))
(define Clauses-to-remove '())
(define cl (Clause-clause C))
(for ([lit (in-list cl)])
(trie-inverse-find utree lit
(λ (nd)
(define val (trie-node-value nd))
(when (list? val)
(for ([lf (in-list (trie-node-value nd))])
(define C2 (utree-leaf-Clause lf))
(hash-ref! tested
C2
(λ ()
(cond [(pred C C2)
(cons! C2 Clauses-to-remove)
#true]
[else #false]))))))))
(for ([C2 (in-list Clauses-to-remove)])
(utree-remove-Clause! utree C2))
Clauses-to-remove)
File diff suppressed because it is too large Load Diff