clips

CLIPS: using nested multislot fields in deftemplates to represent data abstractions of real world artefacts


I am modelling real world artefacts, and I am using CLIPS (6.4) to reason about a system whose process information is necessarily nested - and therein lies the rub; I'm finding it difficult to map between my data structures (which are heavily nested), and the datatypes used by CLIPS.

Here is a trivial example of my C++ structures:

struct Vehicle {
    enum Category category;
    std:list<Engine> engines;
    std:list<Wheel> wheels;
    std:list<Instrument> instruments;
};


struct ORing {
    enum metal_type;
    bool has_seal;
    float diameter;
    float length;
    float weight;
};


struct Piston {
    int foo;
    float foobar; 
    std::string foofoo;
    std:list<ORing> orings;
};


struct Engine {
    std:list<Piston> piston;
    float weight;
};


struct Wheel {
  float diameter;
  bool has_alloy;    
};


struct Widget {
    std:string name;
};


struct Instrument {
    std:string name;
    bool is_electronic;
    std:list<Widget> widgets;
};

Here is my CLIPS data types to represent the same information

(deftemplate vehicle
   (slot category (type SYMBOL) (allowed-symbols CAR BUS TRUCK TRAIN SHIP AIRCRAFT))
   (multislot engines (type SYMBOL))
   (multislot wheels (type SYMBOL))
   (multislot instruments (type SYMBOL))
)


(deftemplate engine
    (multislot pistons (type SYMBOL))
    (slot weight (type FLOAT) (range 0.01 ?VARIABLE) )
)


(deftemplate wheel
    (slot diameter (type FLOAT) (range 0.01 ?VARIABLE) )
    (slot has_alloy (type SYMBOL) (allowed-symbols YES NO) )
)

(deftemplate widget
    (slot name (type STRING))
)

(deftemplate instrument
    (slot name (type STRING))
    (slot is_electronic  (type SYMBOL) (allowed-symbols YES NO) )
    (multislot widget (type SYMBOL))
)

Using this trivial example, my questions (unfortunately, more than one - to make the example meaningful) would be:

1. How to assert a vehicle fact with all slots filled?
2. How to fetch multislot field and check the length of items (for example how to check for number of engines in an asserted vehicle fact?)
3. How to fetch the ith element from a multislot field (2nd engine in a vehicle fact?)
4. How to get a specified field from the ith element of a multislot field (get the metal_type of the 2nd ORing in the 2nd Engine?)
5. How to add/remove an item to a multislot field? (how to add/remove a 3rd engine to an existing vehicle fact?)

Solution

  • These types of nested data structures are easier to represent using classes:

             CLIPS (6.4 2/9/21)
    CLIPS> 
    (defclass VEHICLE
       (is-a USER)
       (slot category (type SYMBOL) (allowed-symbols CAR BUS TRUCK TRAIN SHIP AIRCRAFT))
       (multislot engines (type SYMBOL))
       (multislot wheels (type SYMBOL))
       (multislot instruments (type SYMBOL)))
    CLIPS> 
    (defclass ENGINE
       (is-a USER)
       (multislot pistons (type SYMBOL))
       (slot weight (type FLOAT) (range 0.01 ?VARIABLE)))
    CLIPS> 
    (defclass WHEEL
       (is-a USER)
       (slot id)
       (slot diameter (type FLOAT) (range 0.01 ?VARIABLE))
       (slot has_alloy (type SYMBOL) (allowed-symbols YES NO)))
    CLIPS>
    

    Instances of classes automatically have names generated for them (if not provided) which provides a convenient mechanism for symbolically linking one instance to another:

    CLIPS> 
    (make-instance B737 of VEHICLE
       (category AIRCRAFT)
       (engines (make-instance of ENGINE (weight 4515.0))
                (make-instance of ENGINE (weight 4515.0))))
    [B737]
    CLIPS>                         
    (send [B737] print)
    [B737] of VEHICLE
    (category AIRCRAFT)
    (engines [gen1] [gen2])
    (wheels)
    (instruments)
    CLIPS>
    

    If your instances don't change, you can just define them once and make multiple references to them:

    CLIPS> (make-instance ng of WHEEL (diameter 27.0))
    [ng]
    CLIPS> (make-instance mg of WHEEL (diameter 44.0))
    [mg]
    CLIPS> (send [B737] put-wheels [ng] [ng] [mg] [mg] [mg] [mg])
    ([ng] [ng] [mg] [mg] [mg] [mg])
    CLIPS> (send [B737] print)
    [B737] of VEHICLE
    (category AIRCRAFT)
    (engines [gen1] [gen2])
    (wheels [ng] [ng] [mg] [mg] [mg] [mg])
    (instruments)
    CLIPS>
    

    Message-handlers are automatically created for the slots of a class, so you can use these for determining the number of items in a slot:

    CLIPS> (length$ (send [B737] get-engines))
    2
    CLIPS> 
    

    Retrieve a specific value:

    CLIPS> (nth$ 2 (send [B737] get-engines))
    [gen2]
    CLIPS>
    

    Make nested calls to traverse the class hierarchy:

    CLIPS> (send (nth$ 2 (send [B737] get-engines)) get-weight)
    4515.0
    CLIPS>
    

    Or modify the slots:

    CLIPS> 
    (send [B737] put-engines (send [B737] get-engines)
                             (make-instance [JT8D] of ENGINE (weight 4515.0)))
    ([gen1] [gen2] [JT8D])
    CLIPS> (send [B737] put-wheels (delete$ (send [B737] get-wheels) 5 6))
    ([ng] [ng] [mg] [mg])
    CLIPS> (send [B737] print)
    [B737] of VEHICLE
    (category AIRCRAFT)
    (engines [gen1] [gen2] [JT8D])
    (wheels [ng] [ng] [mg] [mg])
    (instruments)
    CLIPS> 
    

    To do the same with facts, you'd need to add a symbolic link to each template that would serve the same purpose as the name slot of instances. And you'd need to utility functions to look up facts by their symbolic names if you want to do the same type of traversal you can accomplish with message-passing.

    For both fact and instances, the mechanism would be similar if you're using pattern matching to traverse the hierarchy. For facts, this is how you'd retrieve the metal type of the 2nd oring of the 2nd engine:

    (defrule example
       (vehicle (engines ? ?e $?))
       (engine (id ?e)
               (oring ? ?o $?))
       (oring (id ?o)
              (metal_type ?mt))
       =>
       (println "Metal type is " ?mt))