aboutsummaryrefslogtreecommitdiff
path: root/src/clojure/contrib/datalog/literals.clj
diff options
context:
space:
mode:
Diffstat (limited to 'src/clojure/contrib/datalog/literals.clj')
-rw-r--r--src/clojure/contrib/datalog/literals.clj400
1 files changed, 400 insertions, 0 deletions
diff --git a/src/clojure/contrib/datalog/literals.clj b/src/clojure/contrib/datalog/literals.clj
new file mode 100644
index 00000000..337106aa
--- /dev/null
+++ b/src/clojure/contrib/datalog/literals.clj
@@ -0,0 +1,400 @@
+;; Copyright (c) Jeffrey Straszheim. All rights reserved. The use and
+;; distribution terms for this software are covered by the Eclipse Public
+;; License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
+;; be found in the file epl-v10.html at the root of this distribution. By
+;; using this software in any fashion, you are agreeing to be bound by the
+;; terms of this license. You must not remove this notice, or any other,
+;; from this software.
+;;
+;; literals.clj
+;;
+;; A Clojure implementation of Datalog -- Literals
+;;
+;; straszheimjeffrey (gmail)
+;; Created 25 Feburary 2009
+
+
+(ns clojure.contrib.datalog.literals
+ (:use clojure.contrib.datalog.util)
+ (:use clojure.contrib.datalog.database)
+ (:use [clojure.set :only (intersection)])
+ (:use [clojure.contrib.set :only (subset?)])
+ (:use [clojure.contrib.seq-utils :only (flatten)]))
+
+
+;;; Type Definitions
+
+(defstruct atomic-literal
+ :predicate ; The predicate name
+ :term-bindings ; A map of column names to bindings
+ :literal-type) ; ::literal or ::negated
+
+(derive ::negated ::literal)
+
+(defstruct conditional-literal
+ :fun ; The fun to call
+ :symbol ; The fun symbol (for display)
+ :terms ; The formal arguments
+ :literal-type) ; ::conditional
+
+
+;;; Basics
+
+
+(defmulti literal-predicate
+ "Return the predicate/relation this conditional operates over"
+ :literal-type)
+
+(defmulti literal-columns
+ "Return the column names this applies to"
+ :literal-type)
+
+(defmulti literal-vars
+ "Returns the logic vars used by this literal"
+ :literal-type)
+
+(defmulti positive-vars
+ "Returns the logic vars used in a positive position"
+ :literal-type)
+
+(defmulti negative-vars
+ "Returns the logic vars used in a negative position"
+ :literal-type)
+
+(defmethod literal-predicate ::literal
+ [l]
+ (:predicate l))
+
+(defmethod literal-predicate ::conditional
+ [l]
+ nil)
+
+(defmethod literal-columns ::literal
+ [l]
+ (-> l :term-bindings keys set))
+
+(defmethod literal-columns ::conditional
+ [l]
+ nil)
+
+(defmethod literal-vars ::literal
+ [l]
+ (set (filter is-var? (-> l :term-bindings vals))))
+
+(defmethod literal-vars ::conditional
+ [l]
+ (set (filter is-var? (:terms l))))
+
+(defmethod positive-vars ::literal
+ [l]
+ (literal-vars l))
+
+(defmethod positive-vars ::negated
+ [l]
+ nil)
+
+(defmethod positive-vars ::conditional
+ [l]
+ nil)
+
+(defmethod negative-vars ::literal
+ [l]
+ nil)
+
+(defmethod negative-vars ::negated
+ [l]
+ (literal-vars l))
+
+(defmethod negative-vars ::conditional
+ [l]
+ (literal-vars l))
+
+(defn negated?
+ "Is this literal a negated literal?"
+ [l]
+ (= (:literal-type l) ::negated))
+
+(defn positive?
+ "Is this a positive literal?"
+ [l]
+ (= (:literal-type l) ::literal))
+
+
+;;; Building Literals
+
+(def negation-symbol 'not!)
+(def conditional-symbol 'if)
+
+(defmulti build-literal
+ "(Returns an unevaluated expression (to be used in macros) of a
+ literal."
+ first)
+
+(defn build-atom
+ "Returns an unevaluated expression (to be used in a macro) of an
+ atom."
+ [f type]
+ (let [p (first f)
+ ts (map #(if (is-var? %) `(quote ~%) %) (next f))
+ b (if (seq ts) (apply assoc {} ts) nil)]
+ `(struct atomic-literal ~p ~b ~type)))
+
+(defmethod build-literal :default
+ [f]
+ (build-atom f ::literal))
+
+(defmethod build-literal negation-symbol
+ [f]
+ (build-atom (rest f) ::negated))
+
+(defmethod build-literal conditional-symbol
+ [f]
+ (let [symbol (fnext f)
+ terms (nnext f)
+ fun `(fn [binds#] (apply ~symbol binds#))]
+ `(struct conditional-literal
+ ~fun
+ '~symbol
+ '~terms
+ ::conditional)))
+
+
+;;; Display
+
+(defmulti display-literal
+ "Converts a struct representing a literal to a normal list"
+ :literal-type)
+
+(defn- display
+ [l]
+ (conj (-> l :term-bindings list* flatten) (literal-predicate l)))
+
+(defmethod display-literal ::literal
+ [l]
+ (display l))
+
+(defmethod display-literal ::negated
+ [l]
+ (conj (display l) negation-symbol))
+
+(defmethod display-literal ::conditional
+ [l]
+ (list* conditional-symbol (:symbol l) (:terms l)))
+
+
+;;; Sip computation
+
+(defmulti get-vs-from-cs
+ "From a set of columns, return the vars"
+ :literal-type)
+
+(defmethod get-vs-from-cs ::literal
+ [l bound]
+ (set (filter is-var?
+ (vals (select-keys (:term-bindings l)
+ bound)))))
+
+(defmethod get-vs-from-cs ::conditional
+ [l bound]
+ nil)
+
+
+(defmulti get-cs-from-vs
+ "From a set of vars, get the columns"
+ :literal-type)
+
+(defmethod get-cs-from-vs ::literal
+ [l bound]
+ (reduce conj
+ #{}
+ (remove nil?
+ (map (fn [[k v]] (if (bound v) k nil))
+ (:term-bindings l)))))
+
+(defmethod get-cs-from-vs ::conditional
+ [l bound]
+ nil)
+
+
+(defmulti get-self-bound-cs
+ "Get the columns that are bound withing the literal."
+ :literal-type)
+
+(defmethod get-self-bound-cs ::literal
+ [l]
+ (reduce conj
+ #{}
+ (remove nil?
+ (map (fn [[k v]] (if (not (is-var? v)) k nil))
+ (:term-bindings l)))))
+
+(defmethod get-self-bound-cs ::conditional
+ [l]
+ nil)
+
+
+(defmulti literal-appropriate?
+ "When passed a set of bound vars, determines if this literal can be
+ used during this point of a SIP computation."
+ (fn [b l] (:literal-type l)))
+
+(defmethod literal-appropriate? ::literal
+ [bound l]
+ (not (empty? (intersection (literal-vars l) bound))))
+
+(defmethod literal-appropriate? ::negated
+ [bound l]
+ (subset? (literal-vars l) bound))
+
+(defmethod literal-appropriate? ::conditional
+ [bound l]
+ (subset? (literal-vars l) bound))
+
+
+(defmulti adorned-literal
+ "When passed a set of bound columns, returns the adorned literal"
+ (fn [l b] (:literal-type l)))
+
+(defmethod adorned-literal ::literal
+ [l bound]
+ (let [pred (literal-predicate l)
+ bnds (intersection (literal-columns l) bound)]
+ (if (empty? bound)
+ l
+ (assoc l :predicate [pred bnds]))))
+
+(defmethod adorned-literal ::conditional
+ [l bound]
+ l)
+
+
+(defn get-adorned-bindings
+ "Get the bindings from this adorned literal."
+ [pred]
+ (if (vector? pred)
+ (last pred)
+ nil))
+
+(defn get-base-predicate
+ "Get the base predicate from this predicate."
+ [pred]
+ (if (vector? pred)
+ (first pred)
+ pred))
+
+
+;;; Magic Stuff
+
+(defn magic-literal
+ "Create a magic version of this adorned predicate."
+ [l]
+ (assert (-> l :literal-type (isa? ::literal)))
+ (let [pred (literal-predicate l)
+ base-pred (get-base-predicate pred)
+ bound (get-adorned-bindings pred)
+ ntb (select-keys (:term-bindings l) bound)]
+ (assoc l :predicate [base-pred :magic bound] :term-bindings ntb :literal-type ::literal)))
+
+(defn literal-magic?
+ "Is this literal magic?"
+ [lit]
+ (let [pred (literal-predicate lit)]
+ (when (and (vector? pred)
+ (> (count pred) 1))
+ (= (pred 1) :magic))))
+
+(defn build-seed-bindings
+ "Given a seed literal, already adorned and in magic form, convert
+ its bound constants to new variables."
+ [s]
+ (assert (-> s :literal-type (isa? ::literal)))
+ (let [ntbs (map-values (fn [_] (gensym '?_gen_)) (:term-bindings s))]
+ (assoc s :term-bindings ntbs)))
+
+;;; Database operations
+
+(defn- build-partial-tuple
+ [lit binds]
+ (let [tbs (:term-bindings lit)
+ each (fn [[key val :as pair]]
+ (if (is-var? val)
+ (if-let [n (binds val)]
+ [key n]
+ nil)
+ pair))]
+ (into {} (remove nil? (map each tbs)))))
+
+(defn- project-onto-literal
+ "Given a literal, and a materialized tuple, return a set of variable
+ bindings."
+ [lit tuple]
+ (let [step (fn [binds [key val]]
+ (if (and (is-var? val)
+ (contains? tuple key))
+ (assoc binds val (tuple key))
+ binds))]
+ (reduce step {} (:term-bindings lit))))
+
+
+(defn- join-literal*
+ [db lit bs fun]
+ (let [each (fn [binds]
+ (let [pt (build-partial-tuple lit binds)]
+ (fun binds pt)))]
+ (when (contains? db (literal-predicate lit))
+ (apply concat (map each bs)))))
+
+(defmulti join-literal
+ "Given a database (db), a literal (lit) and a seq of bindings (bs),
+ return a new seq of bindings by joining this literal."
+ (fn [db lit bs] (:literal-type lit)))
+
+(defmethod join-literal ::literal
+ [db lit bs]
+ (join-literal* db lit bs (fn [binds pt]
+ (map #(merge binds %)
+ (map (partial project-onto-literal lit)
+ (select db (literal-predicate lit) pt))))))
+
+(defmethod join-literal ::negated
+ [db lit bs]
+ (join-literal* db lit bs (fn [binds pt]
+ (if (any-match? db (literal-predicate lit) pt)
+ nil
+ [binds]))))
+
+(defmethod join-literal ::conditional
+ [db lit bs]
+ (let [each (fn [binds]
+ (let [resolve (fn [term]
+ (if (is-var? term)
+ (binds term)
+ term))
+ args (map resolve (:terms lit))]
+ (if ((:fun lit) args)
+ binds
+ nil)))]
+ (remove nil? (map each bs))))
+
+(defn project-literal
+ "Project a stream of bindings onto a literal/relation. Returns a new
+ db."
+ ([db lit bs] (project-literal db lit bs is-var?))
+ ([db lit bs var?]
+ (assert (= (:literal-type lit) ::literal))
+ (let [rel-name (literal-predicate lit)
+ columns (-> lit :term-bindings keys)
+ idxs (vec (get-adorned-bindings (literal-predicate lit)))
+ db1 (ensure-relation db rel-name columns idxs)
+ rel (get-relation db1 rel-name)
+ step (fn [rel bindings]
+ (let [step (fn [t [k v]]
+ (if (var? v)
+ (assoc t k (bindings v))
+ (assoc t k v)))
+ tuple (reduce step {} (:term-bindings lit))]
+ (add-tuple rel tuple)))]
+ (replace-relation db rel-name (reduce step rel bs)))))
+
+
+;; End of file