Search code examples
clips

Rule for union of two different deftemplates


I hope that this has not been asked before as I tried to scour the questions already asked. I am trying to write a rule that will check if both of two different slots defined in one deftemplate exist in a multislot of a different deftemplate. For example:

(deftemplate manifest
    (slot origin (type SYMBOL))
    (slot destination (type SYMBOL))
    (slot pkgsize (type INTEGER))
    (slot priority (type INTEGER))
    (slot zone (type SYMBOL) (default UNKNOWN))
)   
(deftemplate shipzone
    (slot zonename (type SYMBOL))
    (multislot cities (type SYMBOL))
)

(deffacts load-data
    (manifest (origin CHI) (destination DET) (pkgsize 10) (priority 1))
    (manifest (origin ATL) (destination WAS) (pkgsize 8) (priority 2))
    (manifest (origin CHI) (destination WAS) (pkgsize 15) (priority 1))
    (shipzone (zonename ZONEA) (cities CHI DET NYC BOS))
    (shipzone (zonename ZONEB) (cities ATL WAS NYC))
    (shipzone (zonename ZONEC) (cities CHI WAS BOS))
)

(defrule city-group
    ?f <- (manifest (origin ?x1) (destination ?x2) (zone UNKNOWN))
    (shipzone (zonename ?zname) (cities $?x1 $?x2))
    =>
    (modify ?f (zone ?zname))
)

Gives the following error:

CLIPS> (clear)
CLIPS> (load testgroup.clp)

[ANALYSIS3] testgroup.clp, Line 29: Variable ?x1 is used as both a single and multifield variable in the LHS.

ERROR:
(defrule MAIN::city-group
   ?f <- (manifest (origin ?x1) (destination ?x2))
   (shipzone (zonename ?zname) (cities $?x1 $?x2))
   =>
   (modify ?f (zone ?zname)))
%%$*
FALSE
CLIPS>

What am I doing wrong, or is there a better way to approach this?


Solution

  • When you bind variables in patterns, you must consistently bind to single-field or multifield variables. When you initially bind the single-variables ?x1 and ?x2 in the manifest pattern, these are bound to a single value. When you bind the multifield variables $?x1 and $?x2 in the shipzone pattern, these will be bound to zero or more values. Because variables bound in multiple places must have the same values, it's not possible for $?x1 and $?x2 have more than one value if the rule is matched, so this is treated as an error since it's unlikely that the pattern is doing what you think it's doing.

    There are several ways you could rewrite this rule. If you want the cities slot to begin with the origin and end with the destination with any number of intervening cities, you'd rewrite your rule this way using a multifield wildcard to skip over the intervening cities:

    (defrule city-group
        ?f <- (manifest (origin ?x1) (destination ?x2) (zone UNKNOWN))
        (shipzone (zonename ?zname) (cities ?x1 $? ?x2))
        =>
        (modify ?f (zone ?zname))
    )
    

    If cities can precede the origin or follow the destination, you can rewrite your rule this way:

    (defrule city-group
        ?f <- (manifest (origin ?x1) (destination ?x2) (zone UNKNOWN))
        (shipzone (zonename ?zname) (cities $? ?x1 $? ?x2 $?))
        =>
        (modify ?f (zone ?zname))
    )
    

    Finally, if you want to check that the origin and destination are contained in the cities slot (including the case where the destination occurs before the origin), you can rewrite your rule this way:

    (defrule city-group
        ?f <- (manifest (origin ?x1) (destination ?x2) (zone UNKNOWN))
        (shipzone (zonename ?zname) (cities $?cities))
        (test (and (member$ ?x1 ?cities)
                   (member$ ?x2 ?cities)))
        =>
        (modify ?f (zone ?zname))
    )
    

    Within the test conditional element, the member$ function is passed the variable ?cities rather than $?cities (although either will work). This is allowed because the variable is not bound by this use. The value of the variable is simply being passed to the function.