Search code examples
pythonclipsclipspy

Using Logical And In CLIPS


I altered some CLIPS/CLIPSpy code to look for when the Variable column in a CSV is the word Oil Temp and when the duration of that column is above 600 or above. The rule should fire twice according to the CSV I'm using:
Sample Data

I'm receiving the following error.
Error

Here is my code currently. I think it's failing on the variable check or the logical and check.

import sys
from tempfile import mkstemp
import os
import clips


CLIPS_CONSTRUCTS = """
(defglobal ?*oil-too-hot-times* = 0)

(deftemplate oil-is-too-hot-too-long
  (slot Variable (type STRING))  
  (slot Duration (type INTEGER)))

(defrule check-for-hot-oil-too-long-warning
  (oil-is-too-hot-too-long (Variable ?variable) (Duration ?duration))
  (test (?variable Oil Temp))
  (and (>= ?duration 600))
  =>
  (printout t "Warning! Check engine light on!" tab ?*oil-too-hot-times* crlf)) 

"""


def main():
    environment = clips.Environment()

    # use environment.load() to load constructs from a file
    constructs_file, constructs_file_name = mkstemp()
    file = open(constructs_file, 'wb')
    file.write(CLIPS_CONSTRUCTS.encode())
    file.close()

    environment.load(constructs_file_name)
    os.remove(constructs_file_name)

    # enable fact duplication as data has duplicates
    environment.eval("(set-fact-duplication TRUE)")


    # Template facts can be built from their deftemplate
    oil_too_hot_too_long_template = environment.find_template("oil-is-too-hot-too-long")

    for variable, duration in get_data_frames(sys.argv[1]):
        new_fact = oil_too_hot_too_long_template.new_fact()

        # Template facts are represented as dictionaries
        new_fact["Variable"] = variable
        new_fact["Duration"] = int(duration)

        # Add the fact into the environment Knowledge Base
        new_fact.assertit()

    # Fire all the rules which got activated
    environment.run()

def get_data_frames(file_path):
    """Parse a CSV file returning the dataframes."""
    with open(file_path) as data_file:
        return [l.strip().split(",") for i, l in enumerate(data_file) if i > 1]


if __name__ == "__main__":
    main()

Solution

  • CLIPS adopts Polish/Prefix notation. Therefore, your rule should be written as follows.

    (defrule check-for-hot-oil-too-long-warning
      (oil-is-too-hot-too-long (Variable ?variable) (Duration ?duration))
      (test (and (eq ?variable "Oil Temp") 
                 (>= ?duration 600)))
      =>
      (printout t "Warning! Check engine light on!" tab ?*oil-too-hot-times* crlf)) 
    

    Also notice how the type STRING requires double quotes ".

    Yet I'd suggest you to leverage the alpha network matching of the engine which is more concise and efficient.

    (defrule check-for-hot-oil-too-long-warning
      (oil-is-too-hot-too-long (Variable "Oil Temp") (Duration ?duration))
      (test (>= ?duration 600))
      =>
      (printout t "Warning! Check engine light on!" tab ?*oil-too-hot-times* crlf)) 
    

    The engine can immediately see that your Variable slot is a constant and can optimize the matching logic accordingly. I am not sure it can make the same assumption within the joint test.