8e281422161d

Port old rule tree implementation to ZDDs & implement basic rule tree reasoning
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Tue, 01 Nov 2016 15:20:35 +0000
parents 089d9e0ffbc7
children f766019a72af
branches/tags (none)
files scully.asd src/rule-trees.lisp src/zdd.lisp

Changes

--- a/scully.asd	Tue Nov 01 14:20:24 2016 +0000
+++ b/scully.asd	Tue Nov 01 15:20:35 2016 +0000
@@ -26,7 +26,6 @@
                (:file "package")
                (:module "src" :serial t
                 :components ((:file "gdl")
-                             (:file "rule-trees")
                              (:file "zdd")
                              (:module "reasoners" :serial t
                               :components ((:file "prolog")))
--- a/src/rule-trees.lisp	Tue Nov 01 14:20:24 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,156 +0,0 @@
-(in-package :scully.rule-trees)
-
-
-(adt:defdata rule-tree
-  (node t rule-tree rule-tree)
-  (leaf list))
-
-
-(defun rule-head (rule)
-  (first rule))
-
-(defun rule-body (rule)
-  (rest rule))
-
-(defun rule-first-body (rule)
-  (first (rule-body rule)))
-
-(defun rule-empty-p (rule)
-  (null (rule-body rule)))
-
-
-(defun negationp (term)
-  (and (consp term) (eql 'not (first term))))
-
-(defun bare-term (term)
-  (if (negationp term)
-    (second term)
-    term))
-
-
-(defun %term< (t1 t2)
-  ;; symbol < number < cons
-  (ensure-boolean
-    (etypecase t1
-      (symbol (etypecase t2
-                (symbol (string< (symbol-name t1) (symbol-name t2)))
-                (number t)
-                (cons t)))
-      (number (etypecase t2
-                (symbol nil)
-                (number (< t1 t2))
-                (cons t)))
-      (cons (etypecase t2
-              (symbol nil)
-              (number nil)
-              (cons (cond
-                      ((term< (car t1) (car t2)) t)
-                      ((term< (car t2) (car t1)) nil)
-                      (t (term< (cdr t1) (cdr t2))))))))))
-(defun term< (t1 t2)
-  ;; symbol < number < cons
-  (%term< (bare-term t1) (bare-term t2)))
-
-
-(defun sort-body (rule)
-  (destructuring-bind (head . body) rule
-    (list* head (sort body #'term<))))
-
-(defun drop-first (rule)
-  (destructuring-bind (head . body) rule
-    (list* head (rest body))))
-
-(defun find-smallest-body-term (rules)
-  (-<> rules
-    (mapcar #'rule-first-body <>)
-    (sort <> #'term<)
-    (first <>)))
-
-(defun partition-rules (rules)
-  (let ((element (bare-term (find-smallest-body-term rules))))
-    (labels
-        ((rule-requires (rule)
-           (equal (rule-first-body rule) element))
-         (rule-disallows (rule)
-           (equal (rule-first-body rule) `(not ,element)))
-         (rule-ignores (rule)
-           (not (or (rule-requires rule)
-                    (rule-disallows rule)))))
-      (values element
-              (remove-if-not #'rule-disallows rules)
-              (remove-if-not #'rule-requires rules)
-              (remove-if-not #'rule-ignores rules)))))
-
-
-(defun make-rule-tree (rules)
-  (recursively ((rules (mapcar #'sort-body rules))
-                (accumulated-heads nil))
-    (let* ((heads (-<> rules
-                    (remove-if-not #'rule-empty-p <>)
-                    (mapcar #'rule-head <>)
-                    (remove-duplicates <> :test #'equal)
-                    (union accumulated-heads <> :test #'equal))) ; slow
-           (next-rules (remove-if
-                         (lambda (rule)
-                           (member (rule-head rule) heads :test #'equal))
-                         rules)))
-      (if (null next-rules)
-        (leaf heads)
-        (multiple-value-bind (term low high both)
-            (partition-rules next-rules)
-          (node term
-                (recur (append (mapcar #'drop-first low) both) heads)
-                (recur (append (mapcar #'drop-first high) both) heads)))))))
-
-
-;;;; GraphViz
-(setf cl-dot:*dot-path* "/usr/local/bin/dot")
-
-(defmethod attrs (object &rest attributes)
-  (make-instance 'cl-dot:attributed
-                 :object object
-                 :attributes attributes))
-
-(defmethod cl-dot:graph-object-node ((graph (eql 'rule-tree))
-                                     (object rule-tree))
-  (make-instance 'cl-dot:node
-                 :attributes (adt:match rule-tree object
-                               ((leaf heads)
-                                `(:label ,(structural-string heads)
-                                  :shape :ellipse))
-                               ((node term _ _)
-                                `(:label ,(structural-string term)
-                                  :shape :box)))))
-
-(defmethod cl-dot:graph-object-points-to ((graph (eql 'rule-tree))
-                                          (object rule-tree))
-  (adt:match rule-tree object
-    ((leaf _) nil)
-    ((node _ low high)
-     (list (attrs high :style :solid)
-           (attrs low :style :dashed)))))
-
-(defun draw-rule-tree (tree &optional (filename "tree.png"))
-  (cl-dot:dot-graph
-    (cl-dot:generate-graph-from-roots 'rule-tree (list tree))
-    filename
-    :format :png))
-
-
-;;;; Scratch
-(defparameter *rules* '(
-                        (x (not b) a)
-                        (x a c)
-                        (y c)
-                        (z b d)
-                        (z d (not c))
-                        ))
-(defparameter *rules* '(
-                        (x (not b) a)
-                        (x a c)
-                        (y c)
-                        (z d b)
-                        (z (not c) d)
-                        ))
-
-(draw-rule-tree (make-rule-tree *rules*))
--- a/src/zdd.lisp	Tue Nov 01 14:20:24 2016 +0000
+++ b/src/zdd.lisp	Tue Nov 01 15:20:35 2016 +0000
@@ -12,6 +12,18 @@
 (defpattern leaf (&optional content)
   `(structure leaf :content ,content))
 
+(defun never (val)
+  (declare (ignore val))
+  (values))
+
+(defun print-through (function val)
+  (pr (funcall function val))
+  val)
+
+(defun mapprint-through (function val)
+  (mapc #'pr (funcall function val))
+  val)
+
 
 ;;;; GraphViz -----------------------------------------------------------------
 (setf cl-dot:*dot-path* "/usr/local/bin/dot")
@@ -90,10 +102,6 @@
      (append (mapcar (curry #'cons variable) (enumerate hi))
              (enumerate lo)))))
 
-(defun print-enumerated (zdd)
-  (pr (enumerate zdd))
-  zdd)
-
 
 (defun zdd-count (zdd)
   "Return the number of members of `zdd`."
@@ -283,6 +291,117 @@
   (reduce #'zdd-union (mapcar #'zdd-set sets)))
 
 
+;;;; Rule Trees ---------------------------------------------------------------
+(defun rule-head (rule)
+  (first rule))
+
+(defun rule-body (rule)
+  (rest rule))
+
+(defun rule-first-body (rule)
+  (first (rule-body rule)))
+
+(defun rule-empty-p (rule)
+  (null (rule-body rule)))
+
+
+(defun negationp (term)
+  (and (consp term) (eql 'not (first term))))
+
+(defun bare-term (term)
+  (if (negationp term)
+    (second term)
+    term))
+
+(defun term< (t1 t2)
+  (< (bare-term t1) (bare-term t2)))
+
+
+(defun sort-body (rule)
+  (destructuring-bind (head . body) rule
+    (list* head (sort body #'term<))))
+
+(defun drop-first (rule)
+  (destructuring-bind (head . body) rule
+    (list* head (rest body))))
+
+(defun find-smallest-body-term (rules)
+  (-<> rules
+    (mapcar #'rule-first-body <>)
+    (sort <> #'term<)
+    (first <>)))
+
+(defun partition-rules (rules)
+  (let ((element (bare-term (find-smallest-body-term rules))))
+    (labels
+        ((rule-requires (rule)
+           (equal (rule-first-body rule) element))
+         (rule-disallows (rule)
+           (equal (rule-first-body rule) `(not ,element)))
+         (rule-ignores (rule)
+           (not (or (rule-requires rule)
+                    (rule-disallows rule)))))
+      (values element
+              (remove-if-not #'rule-disallows rules)
+              (remove-if-not #'rule-requires rules)
+              (remove-if-not #'rule-ignores rules)))))
+
+
+(defun make-rule-tree (rules)
+  (recursively ((rules (mapcar #'sort-body rules))
+                (accumulated-heads nil))
+    (let* ((heads (-<> rules
+                    (remove-if-not #'rule-empty-p <>)
+                    (mapcar #'rule-head <>)
+                    (remove-duplicates <> :test #'equal)
+                    (union accumulated-heads <> :test #'equal))) ; slow
+           (next-rules (remove-if
+                         (lambda (rule)
+                           (member (rule-head rule) heads :test #'equal))
+                         rules)))
+      (if (null next-rules)
+        (zdd-set heads)
+        (multiple-value-bind (term low high both)
+            (partition-rules next-rules)
+            (zdd-node term
+                (recur (append (mapcar #'drop-first high) both) heads)
+                (recur (append (mapcar #'drop-first low) both) heads)))))))
+
+
+(defun apply-rule-tree (zdd rule-tree head-bound)
+  (recursively ((zdd zdd)
+                (rule-tree rule-tree))
+    (ematch* (zdd rule-tree)
+      ;; If Z = ∅ there are no sets to cons heads onto, bail.
+      (((leaf nil) _) zdd)
+
+      ;; If R = ∅ or {∅} we've bottomed out of the rule tree and there are no
+      ;; heads to cons, we're done.
+      ((_ (leaf)) zdd)
+
+      ;; If we've passed the head boundary on the rule tree side then we're done
+      ;; filtering and just need to cons in all the heads.
+      ((_ (guard (node var _ _)
+                 (>= var head-bound)))
+       (zdd-join zdd rule-tree))
+
+      ;; If Z = {∅} we might have some heads we need to cons later in the rule
+      ;; tree, so recur down the lo side of it.
+      (((leaf t) (node _ _ lo))
+       (recur zdd lo))
+
+      ;; Otherwise we need to filter.
+      (((node var-z hi-z lo-z) (node var-r hi-r lo-r))
+       (cond
+         ((= var-z var-r) (zdd-node var-z
+                                    (recur hi-z hi-r)
+                                    (recur lo-z lo-r)))
+         ((< var-z var-r) (zdd-node var-z
+                                    (recur hi-z rule-tree)
+                                    (recur lo-z rule-tree)))
+         ((> var-z var-r) (recur zdd lo-r)))))))
+
+
 ;;;; Scratch ------------------------------------------------------------------
 (let ((*draw-unique-sinks* nil))
   (with-zdd
@@ -295,3 +414,58 @@
       (draw <>)
       (zdd-size <>)
       )))
+
+(defparameter *rules* '(
+                        (1001 (not 2) 1)
+                        (1001 1 3)
+                        (1002 3)
+                        (1003 4 2)
+                        (1003 (not 3) 4)
+                        (1004 1 2 3 (not 4))
+                        (1005 (not 2) (not 3))
+                        (1006 4 5)
+                        (1006 2)
+                        ))
+
+(defparameter *state* '(
+                        (1 3)
+                        (1 2)
+                        (2 4 5)
+                        ()
+                        (1 2 4 7)
+                        )
+  )
+
+(let ((*draw-unique-sinks* t))
+  (with-zdd
+    (-<> (make-rule-tree *rules*)
+      ; (print-enumerated <>)
+      ; (zdd-keep-avoiders-of <> '(2 7))
+      (mapprint-through #'enumerate <>)
+      (print-through #'zdd-count <>)
+      (print-through #'zdd-size <>)
+      (draw <>)
+      ; (zdd-size <>)
+      (never)
+      )
+    (pr '--------------)
+    (-<> (apply #'zdd-family *state*)
+      (mapprint-through #'enumerate <>)
+      (print-through #'zdd-count <>)
+      (print-through #'zdd-size <>)
+      ; (draw <>)
+      ; (zdd-size <>)
+      (never)
+      )
+    (pr '--------------)
+    (-<> (apply-rule-tree (apply #'zdd-family *state*)
+                          (make-rule-tree *rules*)
+                          100)
+      (mapprint-through #'enumerate <>)
+      (print-through #'zdd-count <>)
+      (print-through #'zdd-size <>)
+      ; (draw <>)
+      ; (zdd-size <>)
+      (never)
+      )
+    ))