clips

CLIPS : Tree / hierachy of physical objects to represent an assembly (with child / parent relations)


I am concidering using CLIPS for engineering contexts (e.g. mechanical/electrical/mechatronics engineering). We always work with trees/hierachies. E.g. for assemblies (with child / parent relations). Let me give you an example :

Object_01
    |
    -- Object_10
    |   |
    |   -- Object_20
    -- Object_11

Object_01 has children Object_10 and Object_11.

Object_10 has child Object_20.

Object_11 and Object_20 are leaves : they have no child.

And we can add / remove child to a parent. This child can of course be composed of several children etc.

The nice part is that the coupling between child/parent has physical restrictions (feasible or not). For instance we cannot link Object_20 if its mass is higher than the max payload of Object_10. Or if the mass of Object_20 plus the mass of Object_10 is higher than the max payload of Object_01. Let's now imagine that this is feasible, and we link Object_10 (which has child Object_20) to Object_01. Linking child/parent has a consequence : the cumulated mass of each sub-children must be propagated to parent and parents. In our case, the mass of the assembly starting at Object_10 is the mass of Object_10 plus the mass of Object_20. The mass of the assembly starting at Object_01 is the mass of all elements.

I am frankly sure that CLIPS has all needed to do this (and much more). For instance the example circuit.clp had some interesting concepts. But I am new, and I really spend too much time trying...

I have started to model this like that


(defclass PhysicalObject (is-a USER)
(role abstract)
(slot weightKg (type FLOAT) (default 0.0) (range 0.0 ?VARIABLE))
(slot cumulatedWeightKg (type FLOAT) (default 0.0) (range 0.0 ?VARIABLE))
)

(defclass ParentLinkable (is-a USER)
(role abstract)
(slot parentLink (type INSTANCE))
)

(defclass ChildrenLinkable (is-a USER) ;TODO : how to manage connection to multiple children ? BB 20230402
(role abstract)
(slot childLink (type INSTANCE))
)

(defrule initCumulatedWeightKg
   (declare (salience 80))
   ?o <- (object (is-a PhysicalObject) (weightKg ?w) (cumulatedWeightKg ?cw))
   (test (> ?w 0.0))
   (test (= ?cw 0.0))
   =>
   (send ?o put-cumulatedWeightKg ?w)
)

(defclass ExampleOfPartThatCanHaveParentAndChildrens (is-a PhysicalObject | ParentLinkable | ChildrenLinkable )
(role concrete)
)

(defclass ExampleOfPartThatIsForcedToBeALeaf (is-a PhysicalObject | ParentLinkable)
(role concrete)
)

(deffunction linkChild (?parent ?child)
???
)

(deffunction unlinkChild (?parent ?child)
???
)

(deffunction linkToParent (?child ?parent)
???
)

(deffunction unlinkToParent (?child ?parent)
???
)

(defrule updateCumulatedWeightKg
???
??rule or within the functions ??
)

I guess this will use recursivity a lot.

Can someone help me with a sample code based on my objects (Object_01 etc.) ? Also, all advises to reference material / training material is more than welcome. Currently, I use the official "basic programing guide" (from clipsrule website), and the "Adventures in Rule‑Based Programming" e-book (nice, but far from engineering contexts).

Thanks in advance. Boris

(defclass PhysicalObject (is-a USER)
(role abstract)
(slot weightKg (type FLOAT) (default 0.0) (range 0.0 ?VARIABLE))
(slot cumulatedWeightKg (type FLOAT) (default 0.0) (range 0.0 ?VARIABLE))
)

(defclass ParentLinkable (is-a USER)
(role abstract)
(slot parentLink (type INSTANCE))
)

(defclass ChildrenLinkable (is-a USER) ;TODO : how to manage connection to multiple children ? BB 20230402
(role abstract)
(slot childLink (type INSTANCE))
)

(defrule initCumulatedWeightKg
   (declare (salience 80))
   ?o <- (object (is-a PhysicalObject) (weightKg ?w) (cumulatedWeightKg ?cw))
   (test (> ?w 0.0))
   (test (= ?cw 0.0))
   =>
   (send ?o put-cumulatedWeightKg ?w)
)

(defclass ExampleOfPartThatCanHaveParentAndChildrens (is-a PhysicalObject | ParentLinkable | ChildrenLinkable )
(role concrete)
)

(defclass ExampleOfPartThatIsForcedToBeALeaf (is-a PhysicalObject | ParentLinkable)
(role concrete)
)

(deffunction linkChild (?parent ?child)
???
)

(deffunction unlinkChild (?parent ?child)
???
)

(deffunction linkToParent (?child ?parent)
???
)

(deffunction unlinkToParent (?child ?parent)
???
)

(defrule updateCumulatedWeightKg
???
??rule or within the functions ??
)


Solution

  • You can define a PhysicalObject class and add a message-handler to it to initialize the cumulated weight based on the weight:

             CLIPS (6.4 2/9/21)
    CLIPS> 
    (defclass PhysicalObject 
       (is-a USER)
       (slot parent (default FALSE))
       (slot weightKg)
       (slot cumulatedWeightKg))
    CLIPS>    
    (defmessage-handler PhysicalObject init after ()
       (bind ?self:cumulatedWeightKg ?self:weightKg))
    CLIPS>
    

    You can then define some instances and see that the objects are being initialized properly:

    CLIPS> (make-instance Object_01 of PhysicalObject (weightKg 20))
    [Object_01]
    CLIPS> (make-instance Object_10 of PhysicalObject (weightKg 15))
    [Object_10]
    CLIPS> (make-instance Object_20 of PhysicalObject (weightKg 10))
    [Object_20]
    CLIPS> (make-instance Object_11 of PhysicalObject (weightKg 5))
    [Object_11]
    CLIPS> 
    (do-for-all-instances ((?p PhysicalObject)) TRUE
       (println ?p (format nil " %3d %3d" ?p:weightKg ?p:cumulatedWeightKg)))
    [Object_01]  20  20
    [Object_10]  15  15
    [Object_20]  10  10
    [Object_11]   5   5
    CLIPS>
    

    Next define a message-handler to link a child to its parent:

    CLIPS> 
    (defmessage-handler PhysicalObject link (?child)
       (if (send ?child get-parent)
          then
          (return))
       (bind ?parent (instance-name ?self))
       (send ?child put-parent ?parent)
       (bind ?weight (send ?child get-weightKg))
       (while ?parent
          (bind ?cumulated (send ?parent get-cumulatedWeightKg))
          (send ?parent put-cumulatedWeightKg (+ ?cumulated ?weight))
          (bind ?parent (send ?parent get-parent))))
    CLIPS>
    

    The link message will propagate the added weight by following the parent links:

    CLIPS> (send [Object_01] link [Object_10])
    FALSE
    CLIPS> 
    (do-for-all-instances ((?p PhysicalObject)) TRUE
       (println ?p (format nil " %3d %3d" ?p:weightKg ?p:cumulatedWeightKg)))
    [Object_01]  20  35
    [Object_10]  15  15
    [Object_20]  10  10
    [Object_11]   5   5
    CLIPS> (send [Object_10] link [Object_20])
    FALSE
    CLIPS> 
    (do-for-all-instances ((?p PhysicalObject)) TRUE
       (println ?p (format nil " %3d %3d" ?p:weightKg ?p:cumulatedWeightKg)))
    [Object_01]  20  45
    [Object_10]  15  25
    [Object_20]  10  10
    [Object_11]   5   5
    CLIPS> (send [Object_01] link [Object_11])
    FALSE
    CLIPS> 
    (do-for-all-instances ((?p PhysicalObject)) TRUE
       (println ?p (format nil " %3d %3d" ?p:weightKg ?p:cumulatedWeightKg)))
    [Object_01]  20  50
    [Object_10]  15  25
    [Object_20]  10  10
    [Object_11]   5   5
    CLIPS>  
    

    Rather than using message-handlers, you can handle some of this logic using rules, such as what you did in your initCumulatedWeightKg rule, but when you have direct linkages between your objects and the conditions in the rule are simple, some tasks will be easier using message-passing instead. Rules make more sense when you're searching your collection of objects for some condition. For example, if you want to do some kind of balancing where you move children around so that no parent is at its maximum payload.

    Expert Systems: Principles and Programming has more coverage of CLIPS (including the CLIPS Object-Oriented Language), but it's expensive and you may only be able to find a used version.