Giter VIP home page Giter VIP logo

clara-rules's Introduction

Build Status

About

Clara is a forward-chaining rules engine written in Clojure(Script) with Java interoperability. It aims to simplify code with a developer-centric approach to expert systems. See clara-rules.org for more.

Usage

Here's a simple example. Complete documentation is at clara-rules.org.

(ns clara.support-example
  (:require [clara.rules :refer :all]))

(defrecord SupportRequest [client level])

(defrecord ClientRepresentative [name client])

(defrule is-important
  "Find important support requests."
  [SupportRequest (= :high level)]
  =>
  (println "High support requested!"))

(defrule notify-client-rep
  "Find the client representative and send a notification of a support request."
  [SupportRequest (= ?client client)]
  [ClientRepresentative (= ?client client) (= ?name name)] ; Join via the ?client binding.
  =>
  (println "Notify" ?name "that"  ?client "has a new support request!"))

;; Run the rules! We can just use Clojure's threading macro to wire things up.
(-> (mk-session)
    (insert (->ClientRepresentative "Alice" "Acme")
            (->SupportRequest "Acme" :high))
    (fire-rules))

;;;; Prints this:

;; High support requested!
;; Notify Alice that Acme has a new support request!

Building

Clara is built, tested, and deployed using Leiningen. ClojureScript tests are executed via puppeteer.

npm install -g puppeteer
npx puppeteer browsers install chrome

Availability

Clara releases are on Clojars. Simply add the following to your project:

Clojars Project

Communication

Questions can be posted to the Clara Rules Google Group or the Slack channel.

Contributing

See CONTRIBUTING.md

LICENSE

Copyright 2018 Cerner Innovation, Inc.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

clara-rules's People

Contributors

rbrush avatar williamparker avatar ethanechristian avatar k13gomez avatar ylando2 avatar alex-dixon avatar sparkofreason avatar hugoduncan avatar levand avatar llasram avatar kulkarnipushkar avatar zylox avatar sunilgunisetty avatar bfontaine avatar carlosphillips avatar devn avatar frankshearar avatar pbors avatar imrekoszo avatar jessesherlock avatar noprompt avatar jonsmock avatar prio avatar matthiasn avatar mischov avatar

Stargazers

 avatar Hoon Wee avatar Constantin Rack avatar David Roberts avatar Nikolas Pafitis avatar Jacob Nguyen avatar Anh Nguyet Vu avatar  avatar  avatar  avatar  avatar Lee Won Jun avatar Saket Patel avatar Nick Jacob avatar KevinLi avatar Dainius Jocas avatar Douglas Carneiro avatar Dan avatar Juan José Vázquez avatar Jubeen Lee avatar Nikola Vojičić avatar  avatar Patrick Delaney avatar Roman Dronov avatar Tyler DiBartolo avatar  avatar Andrew Huggins avatar  avatar Peter Denno avatar Brett Anthoine avatar  avatar Thiago Miranda avatar Axel Baudot avatar Kristoff avatar  avatar Kris Rajendren avatar Boran Car avatar  avatar Leif Eric Fredheim avatar Tyler DiBartolo avatar Artem Medeu avatar Albert Snow avatar Kyle Summers avatar David Bruce avatar  avatar Ismaël Mejía avatar Byron Gibson avatar Vytautas Aukstikalnis avatar  avatar  avatar Graeme McCutcheon avatar Marc Christophe avatar Timon Böhler avatar  avatar  avatar Helder S Ribeiro avatar Rafa Leblic avatar adt avatar Jason Manuel avatar  avatar Samuel Ludwig avatar Sergey Toropenko avatar Spencer Wilson avatar Nikola Rusakov avatar Mostafa Hassanein avatar  avatar Sergii Lapin avatar Eugene Huang avatar Antônio Sousa avatar Erich Ocean avatar Peter Fisk avatar David Russell avatar seeumara avatar  avatar  avatar Jorge Fernando Gómez avatar Yao avatar Michiel Trimpe avatar Josue Santos avatar Giorgos Makris avatar Ken Nakayama avatar Filipe Bezerra de Souza avatar Cesar Marinho avatar Kevin Henderson avatar  avatar chan avatar crazywoola avatar vinurs avatar Xiaoguang Wang avatar Yerai avatar  avatar Dima Pasko avatar  avatar Jack Park avatar Jorge Zaccaro avatar RJ Sheperd avatar Thomas Minor avatar Alexander Oloo avatar  avatar Vasya Poteryaiko avatar

Watchers

Orlin M Bozhinov avatar  avatar  avatar Tom Brooke avatar Matteo Redaelli avatar Abhik Khanra avatar Jonas Enlund avatar Sunil S Nandihalli avatar Sreehari avatar Michael Gai avatar  avatar François Roucoux avatar Craig Read avatar Liu Hao avatar Richard K. Miller avatar  avatar zhangzhihong avatar hamlet avatar James Cloos avatar Gary Paige avatar Greg Katkov avatar  avatar Binita avatar Nilton Lessa avatar  avatar Ross Bonner avatar Rafał Krzyważnia avatar  avatar Boris Kourtoukov avatar  avatar Göran Kirchner avatar fdserr avatar Mike Rodriguez avatar  avatar 胡雨軒 Петр avatar  avatar  avatar Vladimir Mladenovic avatar  avatar gavin avatar Hung Nguyen avatar Huabin Zhang avatar Sergei Vasilev avatar Novak Boškov avatar Gregory Adebesin avatar Victor Inacio avatar  avatar caocao485 avatar Prabhat Ranjan avatar Richard Kallay avatar Mike Garcia avatar  avatar Jody Alford avatar Kris Rajendren avatar Paul Hartwell avatar  avatar  avatar Nick Fn Blum avatar  avatar

clara-rules's Issues

Put node-id in the memory part.

And give node-id function a third argument for the type of node.
It will help in creating a more efficient memory model for the nodes.

QUESTION: Dynamic definition of rules?

This is a question not an "issue" per se.

Do you have examples of defining rules dynamically (instead of using defrule) ?

Use case:

  • allow users to define some rules via a custom-made UI
  • persist such rules

Thanks! Clara looks great.

Optimize Root Nodes

Root nodes unnecessary group facts by their bindings and store them in working memory, even though there is no possible left-hand activation that can take advantage of this. When building the Rete network we should create a specialized RootNode instead of the JoinNode currently used and skip the unneeded processing.

The impact of this is most pronounced for simple rules with few joins.

Serialize and/or persist working memory

From the Google Group discussion at [1], add the ability to serialize and/or persist the working memory of a session.

Internally, the working memory is simply a set of Clojure data structures (maps, lists, and vectors) and facts are clojure Records, so a simple version of this could be to expose this data structure and allow the caller to use EDN (or a binary optimized equivalent) to persist and transport it.

[1]
https://groups.google.com/forum/#!topic/clojure/pfeFyZkoRdU

Type hint tags in rules are unqualified

Type hints used in rules aren't qualified, and therefore may not be properly resolved when the rules are compiled. They should be qualified when the rule is parsed to remove ambiguity for compilation.

Support for arbitrary (non-record) Clojure maps

See the comments from zcaudate on this Google Group on this topic:

https://groups.google.com/forum/#!topic/clojure/pfeFyZkoRdU

I had been considering using the ":type" metadata of a Clojure map to define the type, e.g. a map created with metadata, such as:

(->  {:first "Alice" :last "Jones" :age 34}
       (with-meta {:type :example/person}))

would match a rule like this:

(defrule is-alice
   [:example/person (= (:first this) "Alice")]
   =>
   (println "Hello, Alice!"))

Note that such metadata can efficiently be attached to existing objects that exist in code, so the code inserting maps into the working memory could simply attach type metadata at insertion time. The rest of the program works with the data, unchanged.

It seems like the suggestions in the Google groups thread suggested that the "type" of the map would be identified by some external context, such as the fact that it was actually represented as a map under a certain key of some containing object. It's not immediately clear to me how we'd detect this in the constraints of a rule system. I'm going to do some digging here.

In any case, there are clearly some open questions in this space, so suggestions are welcome.

Optimize Accumulators

The current accumulator implementation is inefficient, both in terms of computation and memory consumption. The following optimizations will help:

  1. All updates to accumulators should simply merge new data into the previously accumulated result, rather than attempting to sum across all previous data.
  2. We currently hold onto the facts that were involved in an accumulator's computation, but this could cause memory consumption issues if processing large streams of data. The accumulators should be able to let unneeded intermediate facts be garbage collected.

Defining rules with lexical vars?

defrules does not close over lexicals, so this fails:

(ns nag.test-locals
  (:require [clara.rules :refer :all]))

(defrecord Message [message])

(let [f #(println "It worked!" %)]
  (defrule foo
    [?f <- Message]
    =>
    (f ?f)))

(-> (mk-session)
    (insert (->Message "hi!"))
    fire-rules)

;;; CompilerException java.lang.RuntimeException: Unable to resolve symbol:
;;; f in this context, compiling:(null:12:1)

It seems to me that I may be trying to do something unidiomatic. I'm writing a toy notification system much like the sensors one in the clara-examples project. I want to package it as a library that will be used by a larger project, and I want it to be possible to customize the actions that are carried out on each notification.

I could ask the users to write Clara rules on their namespaces and then call mk-session with all the relevant IRuleSources. But I would prefer to keep Clara as an implementation detail, and have a more standard API in which callers register callbacks.

Is this the wrong way to about things? Would you say the use case is legitimate?

Add Java Support

Create a first-class Java API and support for running rule logic against Java Beans. The rules should be externalized in a Clojure file and easily loaded and run from Java code. This includes exposing any Clara query via a simple Java API.

Feature Request: View Function

I'm working on a (view) tool, like Jess', to graphically display clara's RETE network/session for debugging purposes.

Any suggestions/tips on traversing the graph? How should accumulators be visualized?

Internally, I will probably break this down into two parts, the graph traversal part that allows the caller to provide a protocol (e.g. GraphRenderer) implementation that will be called during traversal. This implementation is responsible for rendering the nodes and links.

The renderings I'm considering are:

Any thoughts on where this thing should live in the clara-rules namespace? Or should it live in a separate namespace all together?

Suggestions and/or help appreciated!

Feature Request: Clara LightTable Plugin

I'm looking at creating a clara plugin for LT to help a user understand their rules, working memory state, etc. similar in function to the various CLIPS UI's.

A second purpose for the plugin would be to allow running clara rules in the background within LT itself for automating various programming tasks.

Remove dependency on crossovers (deprecated)

I read recently that Chas has deprecated the crossovers support in the lein plugin.

The suggested technique is to use cljx in order to support clojurescript.

I can make the mods but would want to do so against a fairly stable version/branch. Are there any fairly large refactorings in the works? Do you have any suggested approaches to converting to cljx?

Support arbitrary tests between bound variables in constraints

As of 0.6.x, a given constraint can only work with data for the fact at hand or create bindings. It cannot run arbitrary tests against variables bound by other constraints. For example, suppose we want to find a temperature less than that of some other temperature. In 0.6 we'd need to write it like this:

(defrule has-lower-temp
   [Temperature (= ?t1 temperature)]
   [Temperature (= ?t2 temperature)]
   [:test (< ?t1 ?t2)]
   => 
   . . .)

This change would allow the test to be in-lined with the second constraint like this:

(defrule has-lower-temp
   [Temperature (= ?t1 temperature)]
   [Temperature (< ?t1 temperature)]
   => 
   . . .)

The resulting Rete network would be the same between the two options, but this makes rule authoring cleaner and can avoid unneeded bindings.

Using hash to compare values is not safe.

Here is a code in https://github.com/rbrush/clara-rules/blob/master/src/main/clojure/clara/rules/compiler.clj#L461

        ;; Sort nodes so the same id is assigned consistently,
        ;; then map the to corresponding ids.
        nodes-to-id (zipmap
                     (sort #(< (hash %1) (hash %2)) nodes)
                     (range))

This code has two problems:

  1. It compute the hash for every node log(n) times.
  2. It depend on hash function, What if some adversary find out how to create two nodes with the same hash. He could attack your code. I think that in the end result it will be better to compare the nodes. We need to walk on the nodes and compare the values.

How to write queries on fact created with :fact-type-fn ?

Hello,
i have created facts using :fact-type-fn, insertion and rules works fine but i don't understand how to make queries passing arguments.
For testing i write a fact Test

(defrecord Test [n])

and two kind of query

(defquery get-all-test
  "return all Test facts"
  []
  [?a <- :Test])

(defquery get-one-test
  "return Test fact with arg n same as value"
  [:?value]
  [?a <- [:Test [{n :n}] (= ?value n)]])

, the first return all facts of kind Test, with the second my purpose is to filter for a value passed as argument.

If i run this test (where my project name is of course "clararulestest".. a lot of imagination..)

  (let [session 
        (->
          (mk-session 'clararulestest.core :fact-type-fn :fact-type)
          (insert {:fact-type :Test :n 1}))]
    (prn "ALL" (query session get-all-test)) 
    (prn "ONE" (query session get-one-test :?value 1))))

the results is

"ALL" ({:?a {:fact-type :Test, :n 1}})
"ONE" ()

I'm not sure if my query is wrong or is not correct the way as i'm passing arguments to query, but i haven't found any example of query on fact created using :fact-type-fn.

Any suggestion is really appreciate.
Tell me if i was not clear or if you need example code,thanks.

Paolo

Add shorthand binding notation

As originally suggested in issue #20 , it may be convenient to support a syntax like (=== ?test-case) which is equivalent to (== ?test-case test-case)

I had toyed with the idea of a (bind …) function with these semantics…it's only one character longer than === and would be more clear to the reader. We could do multi-arity support where (bind ?test-case) is equivalent to (bind ?test-case test-case), where the caller can provide a second parameter if desired to bind to a different variable.

Bugs in clara.

This is a great project, a great way to learn what expert system does.
I look over the code and found some bugs.

  1. One major bug is in engine.clj line 627 in ast-to-dnf
    It turn logic expression (and (or a b) (or c d) e f) into
    (or (and a e f) (and (b e f)) (and c e f) (and d e f))
    now suppose that a is true but c and d are false, it will give us the wrong answer.
    It should turn to:
    (or (and a c e f) (and a d e f) (and b c e f) (and b d e f))
    I suggest that we create a function:
  (defn- cartesian-join [lists lst]
    (if (seq lists)
      (let [[h & t] lists]
        (mapcat 
         (fn [l]
           (map #(conj % l) (cartesian-join t lst)))
         h))
      [lst]))

and put in line 627:

  (let [disjunctions (map :content (filter #(#{:or} (:type %)) children)]
        {:type :or 
           :content (for [c (cartesian-join disjunctions conjunctions)] 
           {:type :and
               :content c})})

Note that I put map in there instead of mapcat.
2) In java.clj line 37 it should be clara/retract
3) In rules.clj line 49: == is used to unified but if the field value is false, it will cause the condition to fail.
4) In rules.clj line 144 parse-query-body It will replace ?x <- (condition ...) by ?x

Rule fire only with not inserted fact but not with retracted

Hi (again..) ,
my problem is that I have two facts and two complementary (it's true??) rules.
One fact is needed only to give the input (Tick fact).
One rule fire when there is a fact of the other kind (TodayEnd), the other rule fire when there isn't any fact TodayEnd, here the code :

(defrecord Tick [timestamp])
(defrecord TodayEnd [d timestamp])

(defrule today-end-rule
  [:Tick [{tick_timestamp :timestamp}] (= ?tick_timestamp tick_timestamp)]
  [:TodayEnd [{d :d}] (= ?d d)]
  =>
  (prn "TODAY END" ?tick_timestamp ?d))

(defrule not-today-end-rule
  [:Tick [{tick_timestamp :timestamp}] (= ?tick_timestamp tick_timestamp)]
  [:not [:TodayEnd [{d :d}] (= ?d d)]]
  =>
  (prn "NOT TODAY END" ?tick_timestamp ?d))

My test steps :

  • create empty clara session
  • insert Tick (1) -> print "NOT TODAY END"
  • insert Tick (2) -> print "NOT TODAY END"
  • insert TodayEnd -> print "TODAY END" (twice, one for each Tick)
  • retract TodayEnd -> fire rules -> print "NOT TODAY END" (twice, one for each Tick)
  • insert Tick (3) -> print NOTHING! It doesn't fire any of the two (i think complementary) rules.

So the "not-today-end-rule" works if I haven't inserted any TodayEnd fact yet, but doesn't work if I retract a TodayEnd fact added before.
It seems that clara doesn't handle in the same way facts retracted vs facts not inserted. But maybe is only a crazy idea and I should take some rest..

Currently I'm using clara-rules 0.7.0-snapshot but i tested also with the 0.6.1 and nothing change.

Here the full code :

(defrecord Tick [timestamp])
(defrecord TodayEnd [d timestamp])

(defquery get-all-today-end
  []
  [?a <- :TodayEnd])

(defn get-all-today-end-facts [session]
  "Return a list of fact Today End"
  (let [facts-list (query session get-all-today-end)]
    (map (fn [el] (get-in el [:?a])) facts-list)))

(defrule today-end-rule
  [:Tick [{tick_timestamp :timestamp}] (= ?tick_timestamp tick_timestamp)]
  [:TodayEnd [{d :d}] (= ?d d)]
  =>
  (prn "TODAY END" ?tick_timestamp ?d))

(defrule not-today-end-rule
  [:Tick [{tick_timestamp :timestamp}] (= ?tick_timestamp tick_timestamp)]
  [:not [:TodayEnd [{d :d}] (= ?d d)]]
  =>
  (prn "NOT TODAY END" ?tick_timestamp ?d))

(defrule inserted-tick-rule
  [:Tick [{tick_timestamp :timestamp}] (= ?tick_timestamp tick_timestamp)]
  =>
  (prn "Inserted Tick" ?tick_timestamp))

The test code :

  (let [session (mk-session 'clararulestest.core :fact-type-fn :fact-type :cache false)
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :Tick :timestamp (java.util.Date.)})
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :Tick :timestamp (java.util.Date.)})
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :Tick :timestamp (java.util.Date.)})
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :TodayEnd :d (java.util.Date.) :timestamp (java.util.Date.)})
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (retract (first (get-all-today-end-facts session)))
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :Tick :timestamp (java.util.Date.)})
                    (fire-rules))
        _ (prn "_____________________________________________________________________________________________________")
        session (-> session
                    (insert {:fact-type :Tick :timestamp (java.util.Date.)})
                    (fire-rules))]))

And my results :

"_____________________________________________________________________________________________________"
"NOT TODAY END" #inst "2014-09-04T15:55:35.975-00:00" nil
"Inserted Tick" #inst "2014-09-04T15:55:35.975-00:00"
"_____________________________________________________________________________________________________"
"Inserted Tick" #inst "2014-09-04T15:55:35.990-00:00"
"NOT TODAY END" #inst "2014-09-04T15:55:35.990-00:00" nil
"_____________________________________________________________________________________________________"
"Inserted Tick" #inst "2014-09-04T15:55:35.993-00:00"
"NOT TODAY END" #inst "2014-09-04T15:55:35.993-00:00" nil
"_____________________________________________________________________________________________________"
"TODAY END" #inst "2014-09-04T15:55:35.993-00:00" #inst "2014-09-04T15:55:35.995-00:00"
"TODAY END" #inst "2014-09-04T15:55:35.990-00:00" #inst "2014-09-04T15:55:35.995-00:00"
"TODAY END" #inst "2014-09-04T15:55:35.975-00:00" #inst "2014-09-04T15:55:35.995-00:00"
"_____________________________________________________________________________________________________"
"NOT TODAY END" #inst "2014-09-04T15:55:35.993-00:00" nil
"NOT TODAY END" #inst "2014-09-04T15:55:35.990-00:00" nil
"NOT TODAY END" #inst "2014-09-04T15:55:35.975-00:00" nil
"_____________________________________________________________________________________________________"
"Inserted Tick" #inst "2014-09-04T15:55:36.007-00:00"
"_____________________________________________________________________________________________________"
"Inserted Tick" #inst "2014-09-04T15:55:36.009-00:00"

Thanks in advance and if you need more details or something else let me know.

Paolo

Support lazy insertions and retractions

Rules that have many activations, inserting many derived facts into the working memory, could be made more efficient. Today we process each activation independently, but lazily firing them as batches after a set of activations would allow them to take advantage of the batch network optimizations more efficiently.

Variables inside rule (when using :fact-type-fn) fail to match

I am using regular clojure maps and am using the :fact-type-fn feature. If I have a rule where the match is a variable, the rule doesn't get matched.

For example:

(def a "a")

; doesn't get matched
(defrule some-rule
  [a]
  =>
  (println "matched!"))

; gets matched
(defrule some-rule
  ["a"]
  =>
  (println "matched!"))

I suppose this is expected behavior given how records are matched but it would be extremely helpful otherwise.

A working illustrative example:

https://github.com/andrew-nguyen/clara-test/blob/master/src/clara_example/core.clj

Generate Rete network on the server for ClojureScript

The current code base does unnecessary work on the client side, identifying and reconciling shared structure and assembling the Rete network. A better approach would be to shift this to compilation time, and emitting optimized ClojureScript directly representing the Rete network itself that can be efficiently used in the browser or other JavaScript environment.

Reloading namespace with defrule results in IllegalArgumentException

Just starting to play around with clara-rules and tried the simple example contained in the readme and a bit of an expanded one (instead of printing as a consequence of a rule, I inject a response record.

Seems that if I reload just the namespace that contains a bunch of defrule's, I get an exception "The given query is invalid or not included in the rule base." If I do a reload-all, then it works as I would expect.

If I use the example as is, it runs the first time and if I reload just the namespace, the println's don't seem to be executed.

Port to ClojureScript

It should be possible to port clara-rules to ClojureScript. The only part that is highly JVM-centric is the mechanism for detecting fields from Java objects, but this can be conditionally ignored during a ClojureScript build.

Accumulator key does not contain it's type

Accumulator function condition-key does not contain it's type so
If you create two different accumulators for the same condition they will have the same key. For example:

?max <- (acc/max temperature) from (Temperature)
?min <- (acc/min temperature) from (Temperature)

Will have have the same key.
I am not fixing it because the it look like you are still working on the accumulator.

Is there a way to list all the facts?

During development, it is important to have some introspection into what the rete network is doing as a result of facts and rules. The only way I have found to introspect the network has been via queries, which require adding new query rules and is not very interactive.

Is there a way to list all the facts, rules that fired and why? if not, consider this a request for adding such features. I am reading the paper on which this project is based on to see if I can give it a try.

Splitting facts and rules...

Hi,
after experimenting Clara with real data, I tried to split facts and rules.
The main reason being that a data structure is built from facts elsewhere and rules are run later.
These facts are to be used by more than one rule engine (diff. set of rules).
I have been playing with this since Friday and cannot get my mind to it.
When I refer to records defined in a different name space, I end up with no facts being identified by rules.
As soon as I reinstate defrecord(s) in the same name space as rule, it works.
Testing type equality between name spaces is fine, the facts are of the same type has know by the name space were rules are defined.

I guess I need to understand more deeply how the compilation of the rules work.
I have the same problem with 0.6.2 and 0.7, it's not relevant to a version.
Any suggestions of where I should investigate ?

I'll try to replicate this in a minimal project but so far my attempts have a mixed success. It looks like it's working...

Thank you,
Luc

Transaction queue support

Is there a way to get the set of all the novel tuples after the match resolve act cycles have resolved?

Store the memory in a more efficient way.

If this rule engine is going to be used on local computer, the bottle neck is going to be the memory handling.
Maybe we need to create one file that is optimize for the jvm and another for JavaScript.

Thought on shorthand

Might be nice to introduce a thread-all bind

(defrule
  (bind-> (?loc loc)
    [Location]
    [Person (= ?rocks-the-party true)])
  [Year (= year 1999)]
  =>
  (println "swell"))

which would be equivalent to:

(defrule
  [Location (?loc loc)]
  [Person (?loc loc) (= ?rocks-the-party true)])
  [Year (= year 1999)]
  =>
  (println "swell"))

Only issue with this is that boolean expressions would be unclear as to what depth it maps to.

Anyways, happy halloween brother

Correct logical insert behavior?

I am trying to use Clara where facts are defined as in Datomic (4-element tuples, represented as vectors). Not sure if there's a problem with my understanding of Clara's behavior, my implementation, or if there's a real issue. Here is the sample code:

  (def test-rules 
    ['{:name "add",
       :lhs
       [{:args [[e a v t]],
         :type clojure.lang.PersistentVector,
         :constraints [(= ?e e) (= :x a) (= :first v)]}],
       :rhs [(insert! [1 :y :from-x (now)])]}
     '{:name "add2",
       :lhs
       [{:args [[e a v t]],
         :type clojure.lang.PersistentVector,
         :constraints [(= ?e e) (= :y a) (= :from-x v)]}],
       :rhs [(insert! [1 :z :from-y (now)])]}
     '{:name "remove",
       :lhs
       [{:args [[e a v t]],
         :type clojure.lang.PersistentVector,
         :constraints
         [(= ?e1 e) (= :y a) (= :from-x v) (= ?old (vector e a v t))]}],
       :rhs [(retract! ?old)]}])

  (defquery facts
  []
  [?f <- clojure.lang.PersistentVector])

  (def test-results
    (->
     (mk-session (conj test-rules facts))
     (insert [1 :x :first (now)])
     (fire-rules)
     (query facts))
    )

The output for test-results is

({:?f [1 :x :first #<DateTime 2014-02-16T16:14:57.511Z>]}
 {:?f [1 :z :from-y #<DateTime 2014-02-16T16:14:57.513Z>]})

I would have expected the :z fact to be retracted as well, since it's a logical consequence of :y.

Well hidden compilation error...

Hi,

while experimenting with Clara, I hit a sneaky problem. I would end up being unable to load reducers,
still I had only Clojure 1.6 and OpenJdk 7 on my class path.

Since I use Eclipse and CCW, I reported this on the CCW issue list:

https://code.google.com/p/counterclockwise/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Status%20Priority%20Milestone%20Owner%20Summary&groupby=&sort=&id=649

But after mulling over this, I AOTed my small project that we used to recreate the problem and we
ended up with a compilation error which yielded this nice stack trace:

Exception in thread "main" java.io.IOException: File name too long, compiling:(clara/rules/compiler.clj:498:52)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6651)
at clojure.lang.Compiler.analyze(Compiler.java:6445)
at clojure.lang.Compiler.analyze(Compiler.java:6406)
at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3665)
at clojure.lang.Compiler.analyzeSeq(Compiler.java:6646)
at clojure.lang.Compiler.analyze(Compiler.java:6445)
at clojure.lang.Compiler.analyze(Compiler.java:6406)
... remaining lines omitted to preserve your sanity :)

This screws up the Clojure runtime significantly. Have a look at the CCW ticket.

I looked at the code of the Clara rules compiler but it's not obvious to me why it fails.
You certainly have a better understanding than me.

I did not have time yet to pull a copy of Clara to AOT it to see if the problem is inherent
to the code or if something else here is causing problems. Will try to do so today.

Feedback welcomed. Will try to add more to this as I find useful clues.
Thank you

Optimize Rete Network

At this point Clara does not perform even simple optimizations of the Rete network, which can lead to duplicate execution when multiple productions have common logic. We should at least:

  1. Detect matching constraints and ensure they use the same alpha node.
  2. If two productions share a set of matching conditions in order, those conditions should share the same beta nodes.

Rules dynamic/hot deployment

Hi.

I went through the features of clara-rules. Could not find hot deployment listed among it. I am looking for something similar to : http://docs.jboss.org/drools/release/5.2.0.Final/drools-guvnor-docs/html/ch09.html. This feature periodically scans a particular directory for any changes (add/update/delete) to Jboss drool files, and loads the changes into memory.

Reason I am asking this is that in production , it may not be always possible to restart the rules JVM to accommodate changes.

Thanks

Can't use symbols with "-" in them

(defrecord Clara [test-case])

(defrule test
[Clara (== ?test-case test-case)]
=> (..))

will fail, but if named 'testcase' it will work -- i believe this is due to the java bean mapping. would be nice to have idiomatic clojure naming without all the ceremony of destructuring and duplication names as in:

(defrule test
[Clara [{:keys [test-case]}](== ?test-case test-case)]
=> (..))

On a side note, it may be convenient to support a syntax like (=== ?test-case) which is equivalent to (== ?test-case test-case)

Alternative implementation without using (tuned-)group-by

I was looking through the implementation a little bit and curious about the underlying implementation details. I noticed that you essentially group everything by type (or the output of fact-fn-type). Long term, I'd like to be able to have rules trigger by the elements within a set and it's not entirely obvious to me how to do that.

For example, I have maps that contains a set of values:

{:attributes #{:attr1 :attr2} :name "some name"}

And I'd really like to create a rule similar to:

(defrule some-rule
  [?obj <- (contains? :attr1)] => (println (:name ?obj))

where the fact-type-fn is just :attributes

Currently, I'm wrapping the map with a record that provides similar functionality but it definitely won't scale as the set grows and also pushes the matching into the rule itself. I'd like to bring this up so that I can avoid the initial rule match.

The other approach I thought about taking was to duplicate the facts, one per value of the :attributes set but this started to cause other problems for me unrelated to clara-rules.

I understand this may be an implementation that isn't of interest to others so I'm happy to take a stab at it. But, I wanted to check in and see if you had any comments/suggestions so I don't end up learning the same lessons the hard way.

Define two different binding variables to same query in different rules.

Hello, it's a question more than an issue.
For better understanding clara-rules i'have define two rules with same query but different rule name and binding name.

(defrecord SupportRequest [client level priority-value])

(defrule get-var1
  "SupportRequest count var1"
  [?var1 <- (acc/count) :from [ SupportRequest]])

(defrule get-var2
  "SupportRequest count var2"
  [?var2 <- (acc/count) :from [ SupportRequest]])

(defn print-all-test [session]
  (prn "ALL TEST11 : " (query session get-var1))
  (prn "ALL TEST22 : " (query session get-var2)))

When i make session with assertion and call :

(print-high-counter-and-sum session)

the result is :

"ALL TEST11 -> " ({:?var1 3})
"ALL TEST22 -> " ({:?var1 3})

with both "var1", instead of;

"ALL TEST11 -> " ({:?var1 3})
"ALL TEST22 -> " ({:?var2 3})

I'm agree that if the rule is the same i shouldn't create two rules with two binding, but i'd like to understand why i have this result and if it's my fault or not.

The full testing code here https://gist.github.com/paologf/9665581.
Thanks.

Add no-loop option for rules

I get some strange behavior when I have circular types of rules:

(defrule optimist
  [?g <- Glass (= ?full full) (= ?capacity capacity)]
  [Faucet (= ?flow flow)]
  [:test (not= ?full ?capacity)]
  =>
  (retract! (->Glass ?full ?capacity))
  (insert! (->Glass (+ ?full ?flow) capacity))
  (println ?g))

I was hoping this would work something like:

{:capacity 100 :full 0}
{:capacity 100 :full 25}
{:capacity 100 :full 50}
{:capacity 100 :full 100}

I was wonder what your thoughts are on handling a rule like this

Unexpected binding behavior

I tried to create a simple rule that would match against a bunch of working memory elements (Entry records in this case.) The goal is find any Entry's that have the same value and modify one of them to make it unique (e.g. append "_1" or "_2", etc.)

This was a problem/question posted to the clojure mailing list - I wanted to solve it using clara.

See my gist here:

https://gist.github.com/kahunamoore/8431800

Suggestion to improve efficiency.

You use hash to create id, I think that you can improve the efficiency of the rolebase system by creating an id serially 0..N and use a map node to id for the optimizations. Using a serial id can help improve the memory efficiency because you can put all the memory in vectors instead of maps.

Provide mechanism for annotating session context, and retrieving said context

From a brief conversation & email thread with @rbrush at @clojure_conj

I'd like to be able to provide some arbitrary context to a Clara session I create.
Perhaps by adding a keyword/map to mk-session, and anything I put into the map is stored in the session.

Later, when a rule fires, I would like to be able to get this context, as well as other particulars from Clara as to why that rule fired, etc.

My intention is to use this for logging, and perhaps to persist "derived" facts to a (persistent) event store.

core.async vs ITransport and LHS Usage

Suggestion: What do you think of an implementation (or redesign) of clara's internal ITransport protocol and convert it to use core.async instead. It seems like that might open up some interesting possibilities.

(defprotocol ITransport
(send-elements [transport memory nodes elements])
(send-tokens [transport memory nodes tokens])
(retract-elements [transport memory nodes elements])
(retract-tokens [transport memory nodes tokens]))

Could this be modelled as a channel that produces (for the chan consumer) data such as:

{:type :send-elements [memory nodes elements]}
{:type :retract-elements [memory nodes elements]}
... etc.

The RETE nodes would be connected with channels and this would allow the operations on the graph (insert, retract) to run asynchronously (and pushing tokens through the graph parallel?)

Just a thought. I'm still trying to read through the code to fully understand how clara works... maybe this is a super dumb suggestion. Maybe useful only in non-local use cases? TBD.

Of course I could just implement ITransport using core.async primitives... and maybe that is the right thing to do anyway... Thoughts?

Question: Can we use core.async operations on the LHS anywhere? Obviously the RHS is a no brainer - I'm just unsure of how clara decomposes the LHS and whether or not something it does while building the RETE graph would prevent their use.

The use case I'm thinking of is being able to have rules setup, wait for and subsequently match when a channel read succeeds (e.g. mouse click channel consumer reads mouse events.)

Obviously I could just create an application specific function that wraps the channel logic and call it in a (test) but that would probably be inefficient. It would be nice to have a lot of the channel management concerns taken care of by clara so that the use of core.async in rules is near trivial.

Thoughts?

Accumulating across an empty collection does not return initial value

(defrule nice-day-leg-count
[?legs <- (acc/sum :legs) [Bug (= ?loc loc)]]
[Weather (> temp 90) (= ?loc loc)]
=>
(report-legs ?legs))

This will only fire if there is at least one Bug fact. I would've expected accumulators with initial values like avg/sum/count/distinct would bind their initial value or the :convert-return-fn of the initial-value.

Add event listeners for rule engine state changes

Making incremental changes durable as described in #16 requires plugging into state transitions of the working memory, such as adding new facts, calculating accumulated results, and tracking pending rule activations. This can be generalized to an event listener mechanism to track specific changes to be persisted, and the same infrastructure can also be used to support tracing or other tooling.

This issue is to track the development of event listeners in Clara with simple tracing support, and to support the needs of #16

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.