167ffd6a7a6a

Lisp grounder, work-in-progress
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Mon, 26 Sep 2016 12:27:26 +0000
parents 39b13193bce2
children b261c086fa4e
branches/tags (none)
files gdl/buttons.gdl package.lisp scully.asd src/gdl.lisp src/grounders/prolog.lisp src/reasoners/prolog.lisp

Changes

--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/gdl/buttons.gdl	Mon Sep 26 12:27:26 2016 +0000
@@ -0,0 +1,85 @@
+(role robot)
+(init (off p))
+(init (off q))
+(init (off r))
+(init (step 1))
+(<= (next (on p))
+    (does robot a)
+    (true (off p)))
+(<= (next (on q))
+    (does robot a)
+    (true (on q)))
+(<= (next (on r))
+    (does robot a)
+    (true (on r)))
+(<= (next (off p))
+    (does robot a)
+    (true (on p)))
+(<= (next (off q))
+    (does robot a)
+    (true (off q)))
+(<= (next (off r))
+    (does robot a)
+    (true (off r)))
+(<= (next (on p))
+    (does robot b)
+    (true (on q)))
+(<= (next (on q))
+    (does robot b)
+    (true (on p)))
+(<= (next (on r))
+    (does robot b)
+    (true (on r)))
+(<= (next (off p))
+    (does robot b)
+    (true (off q)))
+(<= (next (off q))
+    (does robot b)
+    (true (off p)))
+(<= (next (off r))
+    (does robot b)
+    (true (off r)))
+(<= (next (on p))
+    (does robot c)
+    (true (on p)))
+(<= (next (on q))
+    (does robot c)
+    (true (on r)))
+(<= (next (on r))
+    (does robot c)
+    (true (on q)))
+(<= (next (off p))
+    (does robot c)
+    (true (off p)))
+(<= (next (off q))
+    (does robot c)
+    (true (off r)))
+(<= (next (off r))
+    (does robot c)
+    (true (off q)))
+(<= (next (step ?y))
+    (true (step ?x))
+    (succ ?x ?y))
+(succ 1 2)
+(succ 2 3)
+(succ 3 4)
+(succ 4 5)
+(succ 5 6)
+(succ 6 7)
+(legal robot a)
+(legal robot b)
+(legal robot c)
+(<= (goal robot 100)
+    (true (on p))
+    (true (on q))
+    (true (on r)))
+(<= (goal robot 0) (or
+    (not (true (on p)))
+    (not (true (on q)))
+    (not (true (on r)))))
+(<= terminal
+    (true (step 7)))
+(<= terminal
+    (true (on p))
+    (true (on q))
+    (true (on r)))
--- a/package.lisp	Sat Sep 24 15:56:44 2016 +0000
+++ b/package.lisp	Mon Sep 26 12:27:26 2016 +0000
@@ -1,3 +1,16 @@
+(defpackage #:scully.gdl
+  (:use
+    #:cl
+    #:losh
+    #:iterate
+    #:cl-arrows
+    #:temperance
+    #:scully.quickutils)
+  (:export
+    #:read-gdl
+    #:load-rules
+    ))
+
 (defpackage #:scully.reasoners.prolog
   (:use
     #:cl
@@ -18,6 +31,18 @@
     #:roles
     ))
 
+(defpackage #:scully.grounders.prolog
+  (:use
+    #:cl
+    #:losh
+    #:iterate
+    #:trivia
+    #:cl-arrows
+    #:temperance
+    #:scully.quickutils)
+  (:export
+    ))
+
 (defpackage #:scully.players.random
   (:use
     #:cl
--- a/scully.asd	Sat Sep 24 15:56:44 2016 +0000
+++ b/scully.asd	Mon Sep 26 12:27:26 2016 +0000
@@ -11,6 +11,7 @@
                #:losh
                #:temperance
                #:hunchentoot
+               #:trivia
                #:cl-arrows
                #:cl-ggp)
 
@@ -20,7 +21,10 @@
                              (:file "quickutils")))
                (:file "package")
                (:module "src" :serial t
-                :components ((:module "reasoners" :serial t
+                :components ((:file "gdl")
+                             (:module "reasoners" :serial t
+                              :components ((:file "prolog")))
+                             (:module "grounders" :serial t
                               :components ((:file "prolog")))
                              (:module "players" :serial t
                               :components ((:file "random")
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/gdl.lisp	Mon Sep 26 12:27:26 2016 +0000
@@ -0,0 +1,19 @@
+(in-package #:scully.gdl)
+
+(defun read-gdl (filename)
+  (let ((*package* (find-package :ggp-rules)))
+    (with-open-file (stream filename)
+      (loop
+        :with done = (gensym)
+        :for form = (read stream nil done)
+        :while (not (eq form done))
+        :collect form))))
+
+(defun load-rules (database rules)
+  (push-logic-frame-with database
+    (mapc (lambda (rule)
+            (if (and (consp rule)
+                     (eq (car rule) 'ggp-rules::<=))
+              (apply #'invoke-rule database (cdr rule))
+              (invoke-fact database rule)))
+          rules)))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/grounders/prolog.lisp	Mon Sep 26 12:27:26 2016 +0000
@@ -0,0 +1,181 @@
+(in-package #:scully.grounders.prolog)
+
+
+;;;; Utils
+(defun fixed-point (function data &key (test 'eql))
+  "Find the fixed point of `function`, starting with `data`."
+  (let ((next (funcall function data)))
+    (if (funcall test data next)
+      data
+      (fixed-point function next :test test))))
+
+(defun gensyms (n prefix)
+  (iterate (repeat n) (collect (gensym prefix))))
+
+
+;;;; Sanitization
+(defun clause-is-p (clause functor-name)
+  (and (consp clause)
+       (eql (first clause) functor-name)))
+
+(defun clause-is-not-p (clause)
+  (clause-is-p clause 'ggp-rules::not))
+
+(defun clause-is-distinct-p (clause)
+  (clause-is-p clause 'ggp-rules::distinct))
+
+(defun clause-is-and-p (clause)
+  (clause-is-p clause 'ggp-rules::and))
+
+(defun clause-is-or-p (clause)
+  (clause-is-p clause 'ggp-rules::or))
+
+
+(defun split-ors (rule)
+  (labels ((split (body)
+             ;; take the body of a clause and return a list of the bodies that
+             ;; result after splitting up any `(or ...)`s inside it.
+             (match body
+               (nil (list nil))
+
+               ((list* (list* 'ggp-rules::or args) remaining)
+                (mapcan (lambda (arg)
+                          (mapcar (curry #'cons arg)
+                                  (split remaining)))
+                        args))
+
+               ((list* other remaining)
+                (mapcar (curry #'cons other) (split remaining))))))
+    (destructuring-bind (head . body) rule
+      (mapcar (curry #'cons head) (split body)))))
+
+(defun strip-ands (rule)
+  (labels ((flatten-ands (body)
+             (match body
+               (nil nil)
+               ((list* first-clause remaining)
+                (append (if (clause-is-and-p first-clause)
+                          (flatten-ands (rest first-clause))
+                          (list first-clause))
+                        (flatten-ands remaining))))))
+    (destructuring-bind (head . body) rule
+      (cons head (flatten-ands body)))))
+
+(defun strip-nots (rule)
+  (destructuring-bind (head . body) rule
+    (cons head (remove-if #'clause-is-not-p body))))
+
+(defun strip-distincts (rule)
+  (destructuring-bind (head . body) rule
+    (cons head (remove-if #'clause-is-distinct-p body))))
+
+
+(defun sanitize-rule (rule)
+  (match rule
+    ((list* 'ggp-rules::<= contents)
+     (->> contents
+       split-ors
+       (mapcar #'strip-ands)
+       (mapcar #'strip-nots)
+       (mapcar #'strip-distincts)
+       (mapcar (curry #'cons 'ggp-rules::<=))))
+    (fact (list fact))))
+
+(defun sanitize-rules (rules)
+  (mapcan #'sanitize-rule rules))
+
+
+;;;; Fluents
+(defun find-initial-state (database)
+  (query-map database
+             (lambda (result)
+               `(ggp-rules::true ,(getf result '?what)))
+             (ggp-rules::init ?what)))
+
+(defun find-trues (database)
+  (query-map database
+             (lambda (result)
+               `(ggp-rules::true ,(getf result '?what)))
+             (ggp-rules::next ?what)))
+
+(defun find-moves (database)
+  (query-map database
+             (lambda (result)
+               `(ggp-rules::does
+                 ,(getf result '?role)
+                 ,(getf result '?move)))
+             (ggp-rules::legal ?role ?move)))
+
+(defun push-fluents (database fluents)
+  (push-logic-frame-with database
+    (map nil (curry #'invoke-fact database) fluents)))
+
+(defun pop-fluents (database)
+  (pop-logic-frame database))
+
+(defun find-more-fluents (database fluents)
+  (push-fluents database fluents)
+  (prog1
+      (-> fluents
+        (union (find-moves database) :test #'equal)
+        (union (find-trues database) :test #'equal)) 
+    (pop-fluents database)))
+
+(defun ground-fluents (rules)
+  (let ((database (make-database)))
+    (scully.gdl:load-rules database rules)
+    (fixed-point (curry #'find-more-fluents database)
+                 (find-initial-state database)
+                 :test (rcurry #'set-equal :test #'equal))))
+
+
+;;;; Axioms
+(defun find-functor (rule)
+  (ematch rule
+    ((list* 'ggp-rules::<= (list* functor arguments) _)
+     (cons functor (length arguments)))
+
+    ((list* 'ggp-rules::<= bare-functor _)
+     (cons bare-functor 0))
+
+    ((list* functor arguments)
+     (cons functor (length arguments)))))
+
+(defun find-axioms (rules)
+  (-<> rules
+    (mapcar #'find-functor <>)
+    (remove-duplicates <> :test #'equal)))
+
+(defun ground-single-axiom (database functor arity)
+  (let ((vars (gensyms arity "?")))
+    (remove-duplicates
+      (invoke-query-map database
+                        (lambda (result)
+                          (if (zerop arity)
+                            functor
+                            `(,functor ,@(mapcar (curry #'getf result) vars))))
+                        `(,functor ,@vars))
+      :test #'equal)))
+
+(defun find-all-axioms (database functors)
+  (iterate (for (functor . arity) :in functors)
+           (unioning (ground-single-axiom database functor arity)
+                     :test #'equal)))
+
+(defun ground-axioms (rules grounded-fluents)
+  (let ((database (make-database)))
+    (scully.gdl:load-rules database rules)
+    (push-fluents database grounded-fluents)
+    (find-all-axioms database (find-axioms rules))))
+
+
+;;;; API
+(defun ground-rules (rules)
+  (let* ((rules (sanitize-rules rules))
+         (fluents (ground-fluents rules))
+         (axioms (ground-axioms rules fluents)))
+    fluents
+    axioms))
+
+
+; (map nil #'print (ground-rules (scully.gdl:read-gdl "gdl/buttons.gdl")))
--- a/src/reasoners/prolog.lisp	Sat Sep 24 15:56:44 2016 +0000
+++ b/src/reasoners/prolog.lisp	Mon Sep 26 12:27:26 2016 +0000
@@ -150,14 +150,8 @@
 
 ;;;; API ----------------------------------------------------------------------
 (defun load-rules (reasoner rules)
-  (let ((db (pr-database reasoner)))
-    (push-logic-frame-with db
-      (mapc (lambda (rule)
-              (if (and (consp rule)
-                       (eq (car rule) 'ggp-rules::<=))
-                (apply #'invoke-rule db (cdr rule))
-                (invoke-fact db rule)))
-            (clean-gdl rules)))))
+  (scully.gdl:load-rules (pr-database reasoner)
+                         (clean-gdl rules)))
 
 (defun initial-state (reasoner)
   (normalize-state