Firing rules ends up in a loop
sdaro opened this issue · 4 comments
Hello,
I have a problem with clara rules and I hope you can help me.
To resolve components that are productive I create facts of dependencies and mark components that are used by productive components as productive as well.
Right when I have a loop in the dependency chain, clara rules ends up in a endless loop while firing rules.
Here is the code:
(ns clara.error
(:require [clara.rules :refer :all]))
(defrecord DependsOn [element dep])
(defrecord Productive [element])
(defrule productive
""
[DependsOn (= ?element1 element) (= ?element2 dep)]
[Productive (= ?element1 element)]
=>
(insert! (->Productive ?element2)))
; Works
(as-> (mk-session 'clara.error) $
(apply insert $ [(->Productive "A")])
(apply insert $ [(->DependsOn "A" "B")
(->DependsOn "B" "C")
(->DependsOn "C" "D")])
(fire-rules $))
; ends in a loop
(as-> (mk-session 'clara.error) $
(apply insert $ [(->Productive "A")])
(apply insert $ [(->DependsOn "A" "B")
(->DependsOn "B" "C")
(->DependsOn "C" "A")])
(fire-rules $))
The approach of clara rules is awesome. That's why I hope we can find the problem so I can continue using clara rules.
Thanks in advance.
Hi @sdaro,
As you said, when there is a cycle in the data being added clara will run into an infinite loop. This is due to clara trying to find a steady state, ie. no new facts inserted into the session. Meaning that in the scenario above there will always be a new fact insert((->Productive "A")
).
I'm not fully aware of your use-cases but perhaps a few changes like:
(ns user
(:require [clara.rules :as r]
[clara.rules.accumulators]))
(defrecord DependsOn [element dep])
(defrecord Productive [element])
(defrecord ProductiveInternal [element path])
(defn vec-contains?
[v item]
(some #(= % item) v))
(r/defrule map-internal-productive
[Productive (= ?element element)]
=>
(r/insert! (->ProductiveInternal ?element [])))
(r/defrule join-rule
[DependsOn (= ?element element) (= ?dep dep)]
[ProductiveInternal (= ?element element)
(= ?path path)]
[:test (not (vec-contains? ?path ?element))]
=>
(r/insert! (->ProductiveInternal ?dep (conj ?path ?element))))
(r/defquery cycle?
[]
[:exists [ProductiveInternal (vec-contains? path element)]])
(defn run []
(-> (r/mk-session)
(r/insert-all [(->Productive "A")
(->DependsOn "A" "B")
(->DependsOn "B" "C")
(->DependsOn "C" "A")])
(r/fire-rules)
(r/query cycle?)))
In the rules above i'm preventing the cycle from producing an infinite loop with the new guard and field:
[:test (not (vec-contains? ?path ?element))]
and in the event of a cycle the query should return a single result:
(run)
=>
({})
otherwise it should be an empty seq.
Agreed with @EthanEChristian 's assessment above - Clara will continue firing rules, including using newly inserted facts, until nothing is left to be fired and a logical final state is reached. http://www.clara-rules.org/docs/truthmaint/ might be good to read. This is analogous to infinite recursion causing a stack overflow error. Does this make sense?
@EthanEChristian Thank you! That was the missing piece of the puzzle. This solves my problem, finally.
@WilliamParker Thanks for the reference and the explanation. I will look into it.
@sdaro,
I'm going to close this issue.