src/terms.lisp @ a406e590934f

Get the layer partitioning working
author Steve Losh <steve@stevelosh.com>
date Thu, 10 Nov 2016 12:26:02 +0000
parents 8070c79ec77c
children b4a7a2c6e449
(in-package :scully.terms)
(in-readtable :fare-quasiquote)


;;;; Overview -----------------------------------------------------------------
;;; We start with a set of grounded rules like: ((next bar) x y (true foo)).
;;;
;;; We need to turn map each term to a unique integer, making sure that they end
;;; up ordered how we want them.
;;;
;;; Our desired ordering has a few "layers":
;;;
;;; Base: (true ...)
;;; Does: (does ...)
;;; Possible: (legal ...)
;;;           terminal
;;;           (goal ...)
;;;           anything that depends on only these or lower
;;; Happens: (next ...)
;;;          (sees ...)
;;;           anything that depends on only these or lower
;;;
;;; Within each layer the ordering is arbitrary, EXCEPT that if a rule X that
;;; relies on the negation of a rule Y, then Y must come before X.


;;;; Utils --------------------------------------------------------------------
(defun-match bare-term (term)
  (`(not ,x) x)
  (x x))

(defun-match negationp (term)
  (`(not ,_) t)
  (_ nil))


(defun-match normalize-term (term)
  (`(not ,body) `(not ,(normalize-term body)))
  (_ (ensure-list term)))

(defun normalize-rule (rule)
  (mapcar #'normalize-term (ensure-list rule)))

(defun normalize-rules (rules)
  (mapcar #'normalize-rule rules))


;;;; Dependency Graph ---------------------------------------------------------
(defun build-dependency-graph (rules &key negations-only)
  (let ((graph (digraph:make-digraph :test #'equal)))
    (labels
        ((mark-dependency (head-pred body-pred)
           (digraph:insert-vertex graph head-pred)
           (digraph:insert-vertex graph body-pred)
           (digraph:insert-edge graph head-pred body-pred))
         (mark-dependencies (head body)
           (iterate (for b :in body)
                    (when (or (negationp b)
                              (not negations-only))
                      (mark-dependency head b)))))
      (iterate (for rule :in rules)
               (for (head . body) = (ensure-list rule))
               (mark-dependencies head body)))
    graph))


;;;; Layer Partitioning -------------------------------------------------------
(defun mark (layers layer term)
  (setf (gethash term layers) layer)
  (pushnew term (gethash layer layers) :test #'equal))


(defun extract-simple (predicates layer layers terms)
  (iterate (for term :in terms)
           (if (member (car term) predicates)
             (mark layers layer term)
             (collect term))))


(defun extract-base (layers terms)
  (extract-simple '(true) :base layers terms))

(defun extract-does (layers terms)
  (extract-simple '(does) :does layers terms))


(defun extract-possible% (layers dependencies terms)
  (labels ((find-dependencies (term)
             (mapcar (rcurry #'gethash layers)
                     (digraph:successors dependencies term)))
           (find-eligible (terms)
             (iterate (for term :in terms)
                      (for deps = (find-dependencies term))
                      (for unmet = (set-difference deps '(:base :possible)))
                      (when (null unmet)
                        (collect term)))))
    (iterate
      (with remaining = terms)
      (for next = (find-eligible remaining))
      (while next)
      (mapcar (curry #'mark layers :possible) next)
      (zapf remaining (set-difference % next :test #'equal))
      (finally (return remaining)))))

(defun extract-possible (layers dependencies terms)
  (-<> terms
    (extract-simple '(legal goal terminal) :possible layers <>)
    (extract-possible% layers dependencies <>)))


(defun extract-happens (layers terms)
  (mapcar (curry #'mark layers :happens) terms)
  nil)


(defun partition-predicates (rules)
  (let* ((rules (normalize-rules rules))
         (dependencies (build-dependency-graph rules))
         (terms (-<> rules
                  (mapcan #'identity <>)
                  (mapcar #'bare-term <>)))
         (layers (make-hash-table :test #'equal)))
    (-<> terms
      (extract-base layers <>)
      (extract-does layers <>)
      (extract-possible layers dependencies <>)
      (extract-happens layers <>))
    layers))


;;;; Intra-Layer Ordering -----------------------------------------------------
(defun build-layer-graph (rules)
  (let ((graph (digraph:make-digraph :test #'equal
                                     :initial-vertices (mapcar #'first rules))))
    (iterate
      (for (head . body) :in rules)
      (iterate (for term :in body)
               (when (negationp term)
                 (let ((dependency (bare-term term)))
                   (when (digraph:contains-vertex-p graph dependency)
                     (digraph:insert-edge graph head dependency))))))
    graph))


(defun order-rules (rules)
  (-> rules
    build-layer-graph
    digraph:topological-sort))


;;;; Scratch ------------------------------------------------------------------
; (defparameter *g*
;   (build-layer-graph (list
;                        '((t foo) a)
;                        '((t bar) a (not (t foo)))
;                        '((t baz) a (not (t bar)))
;                        '((t dogs) a x)
;                        '((t cats) a (not (t dogs)))
;                        )))

; (order-rules (list
;                '((t foo) a)
;                '((t bar) a (not (t foo)))
;                '((t baz) a (not (t bar)))))

(-<> '(
       ((foo x) (true 1))
       (cats (foo x))
       (dogs (not cats))
       ((bar x) (true 2) (does q))
       (mice (bar x))
       ((legal x) (true 3))
       )
  partition-predicates
  ; print-hash-table
  )

(print-hash-table *)