Lisp grounder, work-in-progress
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