Symmetric rule causing infinite loop. How to get around?

67 views
Skip to first unread message

Pond Premtoon

unread,
Nov 10, 2018, 12:05:55 PM11/10/18
to Clara
Hello,

I want have a record to assert that two things are equivalent:


(defrecord Eq [obj1 obj2])


I also want to have a rule that says "If (Eq X Y), then (Eq Y X)". My first pass at the rule looks like this:

(defrule eq-symmetry
 
""
 
[Eq (= ?x x) (= ?y y)]
 
=>
 
(insert! (->Eq ?y ?x)))

As I came to understand, this causes an infinite loop because the RHS generate a new fact that matches the LHS condition, which generate another fact. Since there can be multiple facts that are equal (but not identical), this goes on forever. 

I have also tried using accumulator and/or negation to make sure that I don't add a fact that already exists. The problem with that, as I understand, is when the LHS matches, I insert a new Eq fact, which invalidates the LHS, causing that insertion to be retracted, causing LHS to match again. I can't seem to come up with a way to keep facts "unique"

This is actually a boiled down version of a more messy problem I'm dealing with, and I think I'm missing some fundamental understanding. Here are some questions, all related to each other:

1.) How would you get around this problem I have?
2.) Am I using the rule engine the way it should be used? Am I modeling my problem correctly? 
3.) In general, how do you avoid the looping situation caused by (a.) LHS and RHS keep matching each other and (b.) RHS invalidates LHS? Related to that, is there a way to say "Add fact X if it hasn't been stated before"?

Thank you very much

Jason Felice

unread,
Nov 11, 2018, 9:38:17 AM11/11/18
to Pond Premtoon, Clara
Hi!

If you think about how truth maintenance works, in order to be able to “de-dupe” facts, you’d have to keep track of how many times evidence supported a fact.  This is one of the things accumulators do. They also have a bit of code to avoid retracting and reinserting the fact if the result does not change when a new fact arrives.

That said, there are a few ways to restructure your logic:

* You could introduce a UniDirectionalEq which creates both Eq a b and Eq b a and is produced by earlier rules.
* You could add a flag field to Eq to indicate whether it was produced by symmetry.

-Jason


--
You received this message because you are subscribed to the Google Groups "Clara" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clara-rules...@googlegroups.com.
To post to this group, send email to clara...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/clara-rules/68fab0c7-294f-4bed-bf8c-ea75ddbbdf0b%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Pond Premtoon

unread,
Nov 11, 2018, 9:50:27 AM11/11/18
to Jason Felice, Clara
Thank you very much!

How would you use the accumulator to keep track of how many times evidence supports a fact? I have tried adding something like this to the LHS:

[?eq <- (acc/all) :from [Eq (= ?obj2 obj1) (= ?obj1 obj2)]]
[:test (empty? ?eq)]

This ran it into the situation where the RHS then makes the LHS false and gets retracted.

Jason Felice

unread,
Nov 11, 2018, 1:45:33 PM11/11/18
to Pond Premtoon, Clara
If you use acc/distinct, then the accumulator will not update and retrigger for the second, etc., instances of the fact.  So I was thinking something like:

(defrule symmetry
  [?eq <- (acc/distinct) [Eq (= obj1 ?obj1) (= obj2 ?obj2)]]
  =>
  (insert! (->Eq ?obj2 ?obj1)))

The test isn't necessary, (and you would want it to be [:not [:test (empty? ?eq)]] anyway, since we are accumulating over Eq instances (and binding ?obj1 and ?obj2 to them).  It can't come up with values for ?obj1 and ?obj2 for which accumulation would have an empty result.

You could use acc/all instead of acc/distinct, except that I'll bet that would retrigger rules.
This should terminate, though it *will* produce some duplicate facts (namely a symmetry of a symmetry).  If that's a problem, then you could use a different fact type for the first Eq.  (Or, if there's an ordering of ?obj1 and and ?obj2, you could test (< ?obj1 ?obj2).

-Jason

Pond Premtoon

unread,
Nov 25, 2018, 8:28:03 PM11/25/18
to Clara
After a few days of trying, using (acc/distinct) seems to solve my problem. (Thanks again, Jason) On top of that, I also found this little hack to be quite useful and just wanted to share for others who might run into this problem:

(defrecord Unique [obj])

;; I suppose you can make it work with multiple object arguments, too
(defn insert-u! [obj] (insert! (->Unique obj))) 
(defn insert-u  [session obj] (insert session (->Unique obj)))

(defrule unique
  ""
  [?x <- (acc/distinct) :from [Unique (= ?o obj)]]
  =>
  (insert! ?o))

For fact types that I want to be distinct, I use `insert-u` and `insert-u!` instead of the normal insert:

(defrecord SomeUniqueFact [x y z])
... 
(-> mk-session  
    ... 
    (insert-u! (->SomeUniqueFact [x y z]))
    ...

The Unique type acts as a gate that will only perform an insert once for a distinct fact. This helps me avoid peppering (acc/distinct) is multiple places for facts that I always want unique. I didn't care to make `insert-u` take multiple facts; I suppose you could do that with a bit more work.
Reply all
Reply to author
Forward
0 new messages