Fork me on GitHub
#clara
<
2017-01-19
>
dm314:01:58

do I understand correctly that a rule like the one in https://github.com/cerner/clara-examples/blob/master/src/main/clojure/clara/examples/sensors.clj#L48:

(defrule get-current-temperature
  "Get the current temperature at a location by simply looking at the newest reading."
  [?current-temp <- newest-temp :from [TemperatureReading (= ?location location)]]
  =>
  (insert! (->CurrentTemperature (:value ?current-temp) ?location)))
will accumulate CurrentTemperature facts, never retracting them?

dm316:01:48

seems the above is retracted on every new TemperatureReading. Is my intuition right in this case - the retraction happens because when the new TemperatureReading (t+1) arrives, the condition under which the previous CurrentTemperature became true (t+0) is now false?

wparker17:01:40

@dm3 that is correct

wparker17:01:21

When the conditions that caused a logical fact insertion to occur Clara will adjust the session to reflect the new status of those conditions

wparker17:01:31

Logical insertions are those done with insert!

wparker17:01:29

If you used insert-unconditional! you’d end up with multiple CurrentTemperature facts potentially depending on your rules firing pattern

wparker17:01:33

But as long as you stay away from uncondition insertions and manual fact retractions inside rules you can just think of the rules as pure logical relationships that “if as session has these things in it it will also have these other things in it"

wparker17:01:49

“a session” not “as session"

wparker17:01:08

After you call fire-rules at least, i.e. (-> your-session (insert your-facts-here) ;;no-contract-about-status-now fire-rules ;; now-there-is-a-contract-about-the-status)

wparker17:01:35

Also note that you’ll group on the location of the TemperatureReading in the above

wparker17:01:12

So if you have TemperatureReading facts with different locations you can have a CurrentTemperature fact for each distinct location

wparker17:01:16

Does that make sense?

dm317:01:07

running into some other issues now

dm317:01:10

what if I have facts that change with time and do an insert CurrentTimestamp -> fire-rules -> retract CurrentTimestamp periodically? Are there any obvious issues I'll run into?

wparker18:01:44

@dm3 As long as you use truth maintenance Clara will resolve all transitive impacts on rules

wparker18:01:18

i.e. if you insert Fact1 which causes RuleA to insert fact2, and fact2 causes ruleB to insert fact3

wparker18:01:58

a retraction of fact1 will cause fact3 to be removed from the session if appropriate based on the rules

wparker18:01:35

Regarding “facts that change with time”:

wparker18:01:47

Clara assumes that facts are immutable

wparker18:01:39

If you do something like (insert JavaBeanFact) fire-rules (.setSomeFieldUsedInRules JavaBeanFact newValue)

wparker18:01:49

the behavior will be undefined

wparker18:01:08

if you want to change fields you’ll need to retract the fact and then insert a new one with the modifications you want

wparker18:01:28

Actually I’d consider mutation of facts after insertion to result in undefined behavior in general, apart from whether the field is used

wparker18:01:44

Mutating fields after insertion used in rules would definitely cause problems with the current implementation, would need to think about mutations not impacting rule bindings but either way the behavior isn’t guaranteed and I wouldn’t advise doing it

dm318:01:25

I wasn't considering mutations

dm318:01:37

I'm thinking of batching the input stream of events and reducing the batch with (fn [sess batch] (-> (insert sess timestamp) (insert-all batch) (fire-rules) (retract timestamp)))

wparker18:01:52

yeah obviously the existence of problems depends on the specific use-case but I don’t see problems in general with inserting and then retracting a timestamp fact that would have downstream impact through various rules

wparker18:01:23

truth maintenance will sort out all the impacts to downstream rules

wparker18:01:08

also remember Clara sessions are immutable

wparker18:01:14

so you could do something like this as well

wparker18:01:32

(def session-with-facts-no-timestamp)

wparker18:01:28

(def time1-session (-> session-with-facts-no-timestamp (insert time1) fire-rules))

wparker18:01:44

(def time2-session (-> session-with-facts-no-timestamp (insert time2) fire-rules))

dm319:01:21

I'm now trying to do the following:

(ns rulebot.test
  (:require [clara.rules :as clara]
            [clara.rules.accumulators :as acc]
            [clara.tools.tracing :as tt]))

(defrecord Value [value instant])
(defrecord Step [step instant])
(defrecord CurrentValue [value])
(defrecord CurrentStep [step])
(defrecord CurrentTimestamp [instant])

(clara/defrule current-value
  [?e <- (acc/max :instant :returns-fact true) :from [Value]]
  =>
  (clara/insert! (->CurrentValue (:value ?e))))

(clara/defrule current-step
  [?e <- (acc/max :instant :returns-fact true) :from [Step]]
  =>
  (clara/insert! (->CurrentStep (:step ?e))))

(clara/defrule step
  [CurrentValue (= ?value value)]
  [CurrentStep (= ?prev-step step)
               (= ?curr-step (int (/ ?value 10)))]
  [CurrentTimestamp (= ?instant instant)]
  [:test (> ?curr-step ?prev-step)]
  =>
  (clara/insert! (->Step ?curr-step ?instant)))

(defn go []
  (let [session (-> (clara/mk-session 'rulebot.test)
                    (tt/with-tracing))]
    (-> session
        (clara/insert (->CurrentTimestamp 0))
        (clara/insert (->Value 1 0))
        (clara/insert (->Step 0 0))
        (clara/fire-rules)
        (clara/retract (->CurrentTimestamp 0))

        (clara/insert (->CurrentTimestamp 1))
        (clara/insert (->Value 11 1))
        (clara/fire-rules)
        (tt/get-trace))))
which fails on the step rule because ?prev-step gets bound to an {:ns-name .., :lhs ..., :rhs ...} map for some reason...

dm319:01:36

@wparker can you see anything obviously wrong with that?

wparker20:01:19

{:ns-name .., :lhs ..., :rhs …} is what the internal representation of a rule looks like; its the map structure that defrule stores in a var

wparker20:01:03

I don’t have time to look into it in detail at the moment but I’d guess that the “step” field is being resolved to the “step” var instead

wparker20:01:15

If you type “step” in a REPL in that namespace you should see the same value

wparker20:01:16

I’d say this edge case merits a GitHub issue to determine what to do with it but for the moment I think you can resolve this by naming your rule something that isn’t the name of one of your fact fields

dm321:01:44

you're right. Once that's fixed, the example goes into infinite loop

dm321:01:59

so your help will still be appreciated! 🙂

wparker22:01:06

I see you’re inserting Step in one of your rules that looks for facts that are inserted due to Step

wparker22:01:18

At first glance I’d say you’re likely doing something where you’re inserting a Step that then causes itself to be retracted and entering an infinite loop that way

wparker22:01:46

I’d need to take a closer look at it to work out exactly what

dm322:01:10

the step rule does insert a new Step, which should then retract the old CurrentStep and insert a new one, unless I'm misunderstanding something

dm322:01:08

however, after the step rule runs, the current-step rule is still executed with the previous Step fact

dm323:01:21

it's probably the truth maintenance screwing with me - Step (t0) is inserted outside the rules, Step (t1) is inserted by the next-step rule with CurrentStep (t0). After Step (t1) triggers current-step to retract the CurrentStep (t0), the Step (t1) also gets retracted which again triggers next-step with CurrentStep (t0)

dm323:01:38

what should I do in order to accumulate the Step facts (more like events in this case) instead of having the above happening?

wparker23:01:55

to be honest I’m not sure what sort of logic you’re trying to represent with the recursion there so an alternative isn’t obvious to me

dm323:01:53

I'm trying to capture increases in Value as Steps. There's a stream of Values coming in, which I batch and fire with a new CurrentTimestamp every N ms. Some of the batches might trigger a new Step, which I've imagined would appear as a new fact in the session. I guess the culprit of my troubles is the recursive relation between the CurrentStep and Step.

dm323:01:56

tried reformulating the next-step rule as follows (becuase the CurrentStep is just the MAX of all steps):

(clara/defrule next-step
  [CurrentValue (= ?value value)]
  [?s <- (acc/max :step :returns-fact true) :from [Step (= ?prev-step step)]]
  [CurrentTimestamp (= ?instant instant)]
  [:test (> (int (/ ?value 10)) ?prev-step)]
  =>
  (clara/insert! (->Step (int (/ ?value 10)) ?instant)))

dm323:01:32

the above works, except I couldn't figure out how to bind the ?curr-step = (int (/ ?value 10)) inside the condition