Search code examples
expert-systemclipsinference-enginegeneralization

Generalization of set of facts in CLIPS (trying to find matching slot values in multislot slots)


I am trying to do something like 'facts generalization' in CLIPS (not sure which term describes it best of all) and I am not sure how to do this in a best way.

Consider such situation. I have a set of facts which are described by the below templates:

(deftemplate MAIN::simplecause
   (slot coraxidcause (type INTEGER) (default 0))
   (slot changeidcause (type SYMBOL) (default PF1))
   (multislot coraxinfo (type SYMBOL) (default undefined))
   (multislot changeinfo (type SYMBOL) (default undefined)))  

(deftemplate MAIN::finalcause  
   (multislot coraxinfo (type SYMBOL) (default undefined))
   (multislot changeinfo (type SYMBOL) (default undefined))
   (slot casecount (type INTEGER) (default 0)))

Coraxidcause and changeidcause combination is a kind of key - combination of these 2 fields is unique. changeinfo and coraxinfo have some symbolic values in slots (I always have not more than 10 values in each of these slots)

So I have some simplecause facts. What I want to do is to find which values are same in changeinfo and coraxinfo and assert them. For instance if I have these simplecause facts:

(simplecause (coraxidcause id1) (changeidcause id1) (coraxinfo 1 2 3) (changeinfo a b c))

(simplecause (coraxidcause id2) (changeidcause id2) (coraxinfo 2 3 6 7) (changeinfo e a b d f))

(simplecause (coraxidcause id3) (changeidcause id3) (coraxinfo 9 11 2 3 0) (changeinfo g a b))

(simplecause (coraxidcause id4) (changeidcause id4) (coraxinfo 77) (changeinfo z))

I want to assert such fact:

(finalcause (coraxinfo 2 3) (changeinfo a b))

For now I have written this rule:

(defrule MAIN::cause_generalization_initial
   (simplecause (coraxidcause ?coraxid1) (changeidcause ?factid1) (coraxinfo $? $?coraxdetails $?) (changeinfo $? $?changedetails $?))
   (simplecause (coraxidcause ?coraxid2) (changeidcause ?factid2) (coraxinfo $? $?coraxdetails $?) (changeinfo $? $?changedetails $?))
   (or (test (<> ?coraxid1 ?coraxid2))
                                (neq ?factid1 ?factid2))
                (not (finalcause (coraxinfo $?coraxdetails) (changeinfo $?changeddetails)))
   =>
   (assert (finalcause (coraxinfo ?coraxdetails) (changeinfo ?changedetails) (casecount 0))))

The issue is that if we get back to those 4 facts mentioned earlier it asserts this:

(finalcause (coraxinfo 2) (changeinfo a))
(finalcause (coraxinfo 3) (changeinfo a))
(finalcause (coraxinfo 2 3) (changeinfo b))

etc.

I don't need all these 'partial matches', I just need the fully matching part - (finalcause (coraxinfo 2 3) (changeinfo a b)), and I am not sure how to this. Moreover, a really terrible things happen when I have something like this:

(simplecause (coraxidcause id5) (changeidcause id5) (coraxinfo 0 1 2 3) (changeidcause a b c))

(simplecause (coraxidcause id6) (changeidcause id6) (coraxinfo 6 1 2 3) (changeidcause a b c))

At this moment CLIPS engine goes into smth like an infinite loop, LHS lists every possible match:

(finalcause (coraxinfo 1) (changeidcause a))
(finalcause (coraxinfo 1) (changeidcause a b))

etc.

That takes ages (and still does the thing I don't need, as I mentioned before). I am a newbie in CLIPS so I assume that I miss something obvious, there should be some way to do what I need. I will appreciate any help or suggestions on how to do this. Any ideas will be really useful.

Looks like I haven't clarified what exactly I want. I need to find all possible 'matches' accross all of the facts, for instance, if I have these facts:

    (deffacts start
       (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
       (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
       (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z))
       (simplecause (coraxinfo 77 88 99 66) (changeinfo k m l s))
       (simplecause (coraxinfo 88 99 11 22) (changeinfo v k m w))
       (simplecause (coraxinfo 13 88 99) (changeinfo k m))
       (simplecause (coraxinfo 666 777) (changeinfo abc def)))

I would need to get this as output:

(finalcause (coraxinfo 2 3) (changeinfo a b))
(finalcause 88 99) (changeinfo k m)) 

Solution

  • You can do this with a single rule, but it's a bit gnarly:

    CLIPS> 
    (deftemplate simplecause
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS>    
    (deftemplate finalcause   
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS> 
    (deffacts start
       (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
       (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
       (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z)))
    CLIPS> 
    (defrule cause_generalization_initial
       ;; There's a simplecause with two subsequences
       (simplecause (coraxinfo $? $?coraxdetails $?) (changeinfo $? $?changedetails $?))
       ;; And every simplecause contains that same subsequence
       (forall (simplecause (coraxinfo $?all1) (changeinfo $?all2))
               (test (and (subsetp $?coraxdetails $?all1) (subsetp $?changedetails $?all2))))
       ;; And there's not a longer subsequence where every simplecause contains that subsequence
       (not (and (simplecause (coraxinfo $? $?coraxdetails2 $?) (changeinfo $? $?changedetails2 $?)) 
                 (test (or (and (>= (length $?coraxdetails2) (length $?coraxdetails))
                                (> (length $?changedetails2) (length $?changedetails)))
                           (and (> (length $?coraxdetails2) (length $?coraxdetails))
                                (>= (length $?changedetails2) (length $?changedetails)))))
                 (forall (simplecause (coraxinfo $?all1) (changeinfo $?all2))
                         (test (and (subsetp $?coraxdetails2 $?all1) (subsetp $?changedetails2 $?all2))))))
       ;; And a fact for the subsequences has not been generated (since 
       ;; the rule will have an activation for each simple cause)
       (not (finalcause (coraxinfo $?coraxdetails) (changeinfo $?changedetails)))
       =>
       (assert (finalcause (coraxinfo $?coraxdetails) (changeinfo $?changedetails))))
    CLIPS> (reset)
    CLIPS> (agenda)
    0      cause_generalization_initial: f-3,*,*,*
    0      cause_generalization_initial: f-2,*,*,*
    0      cause_generalization_initial: f-1,*,*,*
    For a total of 3 activations.
    CLIPS> (watch rules)
    CLIPS> (run)
    FIRE    1 cause_generalization_initial: f-3,*,*,*
    CLIPS> (facts)
    f-0     (initial-fact)
    f-1     (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
    f-2     (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
    f-3     (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z))
    f-4     (finalcause (coraxinfo 2 3) (changeinfo a b))
    For a total of 5 facts.
    CLIPS>
    

    It's a little bit easier to understand if the work is spread between multiple rules:

    CLIPS> (unwatch all)
    CLIPS> 
    (deftemplate simplecause
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS>    
    (deftemplate finalcause   
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS> 
    (deffacts start
       (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
       (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
       (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z)))
    CLIPS> 
    (defrule cause_generalization_initial
       (simplecause (coraxinfo $? $?coraxdetails $?) (changeinfo $? $?changedetails $?))
       (forall (simplecause (coraxinfo $?all1) (changeinfo $?all2))
               (test (and (subsetp $?coraxdetails $?all1) (subsetp $?changedetails $?all2))))
       =>
       (assert (finalcause (coraxinfo $?coraxdetails) (changeinfo $?changedetails))))
    CLIPS> 
    (defrule cause_generalization_better
       ?f <- (finalcause (coraxinfo $?coraxdetails1) (changeinfo $?changedetails1))
       (finalcause (coraxinfo $?coraxdetails2) (changeinfo $?changedetails2))
       (test (or (< (length $?coraxdetails1) (length $?coraxdetails2))
                 (< (length $?changedetails1) (length $?changedetails2))))
       =>
       (retract ?f))
    CLIPS> (reset)
    CLIPS> (run)
    CLIPS> (facts)
    f-0     (initial-fact)
    f-1     (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
    f-2     (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
    f-3     (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z))
    f-24    (finalcause (coraxinfo 2 3) (changeinfo a b))
    For a total of 5 facts.
    CLIPS> 
    

    The key part of both methods is the forall conditional element which checks that each simplecause contains the subsequence being considered.

    A modified approach based on your comment:

    CLIPS> (clear)
    CLIPS>     
    (deftemplate simplecause
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS>     
    (deftemplate finalcause   
       (multislot coraxinfo)
       (multislot changeinfo))
    CLIPS>  
    (deffacts start
       (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
       (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
       (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z))
       (simplecause (coraxinfo 77 88 99 66) (changeinfo k m l s))
       (simplecause (coraxinfo 88 99 11 22) (changeinfo v k m w))
       (simplecause (coraxinfo 13 88 99) (changeinfo k m))
       (simplecause (coraxinfo 666 777) (changeinfo abc def)))
    CLIPS>    
    (defrule cause_generalization_initial
       ?f1 <- (simplecause (coraxinfo $? ?v11 ?v12 $?) (changeinfo $? ?v21 ?v22 $?))
       ?f2 <- (simplecause (coraxinfo $? ?v11 ?v12 $?) (changeinfo $? ?v21 ?v22 $?))
       (test (neq ?f1 ?f2))
       (not (finalcause (coraxinfo ?v11 ?v12) (changeinfo ?v21 ?v22)))
       =>
       (assert (finalcause (coraxinfo ?v11 ?v12) (changeinfo ?v21 ?v22))))
    CLIPS> (reset)
    CLIPS> (watch rules)
    CLIPS> (run)
    FIRE    1 cause_generalization_initial: f-6,f-5,*
    FIRE    2 cause_generalization_initial: f-3,f-2,*
    CLIPS> (facts)
    f-0     (initial-fact)
    f-1     (simplecause (coraxinfo 1 2 3) (changeinfo a b c))
    f-2     (simplecause (coraxinfo 7 8 2 3 9) (changeinfo d a b e))
    f-3     (simplecause (coraxinfo 2 3 10 13) (changeinfo f g a b z))
    f-4     (simplecause (coraxinfo 77 88 99 66) (changeinfo k m l s))
    f-5     (simplecause (coraxinfo 88 99 11 22) (changeinfo v k m w))
    f-6     (simplecause (coraxinfo 13 88 99) (changeinfo k m))
    f-7     (simplecause (coraxinfo 666 777) (changeinfo abc def))
    f-8     (finalcause (coraxinfo 88 99) (changeinfo k m))
    f-9     (finalcause (coraxinfo 2 3) (changeinfo a b))
    For a total of 10 facts.
    CLIPS>