JESS Tutorial

Example code: cat.clp

--------------------------------------------------------------------------

1.  What is JESS?
2.  Invoking JESS
3.  Programming in JESS
4.  Identifier and Datatypes
     4.1   Atoms
     4.2   Numbers
     4.3   Strings
     4.4   Lists
5.  Variables
     5.1   Binding
     5.2   Multi-field variables
     5.3   Global Variables
6.  Type checking and Conversion
     6.1   Type Checking
     6.2   Type Casting
7.  Operators
     7.1   Arithmetic
     7.2   Comparison
     7.3   Logical
8.  Facts
     8.1   Add Facts
     8.2   Remove Facts
     8.3   Using templates
9.  Rules
10.Functions
11.I/O operations
     11.1 Standard I/O
     11.2 File I/O
12.Debugging

1. What is JESS?

Jess stands for Java Expert System Shell. It was developed by Ernest Friedmal-Hill at Sandia National Lab. It's a rule engine and scripting environment similar to CLIPS developed by NASA. Jess is totally written in Java platform of Sun Microsystems. Jess is a forward chaining production system that uses RETE algorithm. It's free for academic purpose and can be downloaded from http://herzberg.ca.sandia.gov/jess/. There is no need for your to install Jess in your machine as it is installed already in CS Linux machines. You could use it by simply connecting to CS machines through ssh. This tutorial covers most of the things that you need to know to implement the project. But, not all the functionalities/functions in Jess are covered in this tutorial. You are recommended to read the user's manual in the above webpage also.

Sample programs in jess can be found at the following directory:
/usr/local/Jess61p7/examples

2. Invoking JESS

To run JESS, just type
> jess

Typing the above command, will put you in "Jess>" prompt. This is the shell that you are going to interact with. To exit from this shell, you should type and for any command, remember those brackets!
Jess> (exit)

You can use your UNIX system commands even from the Jess prompt by the following way.

Jess>(system pwd)
/users/yzhang/jess
Jess>

3. Programming in JESS

Though you can write Java programs and use the rete engine of Jess, it is sufficient that you interact with Jess shell alone. You don't need to know Java to program in Jess. You basically need to know only the Jess scripting language. You can define facts and rules from the command prompt. Jess stores them in its internal knowledge base, but never gives you access after you clear the memory. So, type your script in a separate file and call that file from Jess prompt. You can use whatever editor you want to edit your file. If the file name is cat.clp, you can load it by
Jess> (batch cat.clp)
Jess>

The comments/documentation can be written by preceding it with a ";" character.
;; Agent moves forward

All the facts that you give gets an id and goes into the knowledge base. The rules go to the rule base. When you run a Jess program, it runs as long as it doesn't find any rule to fire or gets a termination signal which is (halt) equivalent to exit()/System.exit() in other languages. In a sense, execution of a Jess program is basically firing of rules that keeps modifying the KB until the goal is reached. After you load your program using batch command, you should use the command reset to clear the working memory and load the facts into it. To run your program, use the command run. You can use clear to clear the working memory, but you should consult (batch) your file again after using this command.

Jess> (batch cat.clp)
;; loads the facts and rules into knowlege base
Jess> (reset) ;; clear the working memory loads them into working memory
Jess> (run) ;; runs the script
Jess> (clear) ;; clears Jess (after this reset or run will not work)

4. Identifiers and Datatypes

4.1 Atoms

The atoms is the basic unit in Jess representation. It can contain letters or numbers or characters like $,*,=,+,/,<,>,_,?,#. The most important thing to note is that everything in Jess is CASE SENSITIVE. This might be the major source of errors when you start programming in Jess. The boolean values in Jess are TRUE and FALSE (again, they are case sensitive). The Jess equivalent of C's NULL or Java's null is nil.

4.2 Numbers

Jess allows floating point and integer numbers. They are like any other language that you would have programmed with.

4.3 Strings

The strings should be enclosed between double quotes. You can use the escape character if you want Jess to ignore double quotes. str-length can be used to find the length of a string.  str-cmp will let you compare two strings. It returns 0 if the two strings are identical, else a negative/positive value.

Jess> (str-length "howdy")
5
Jess> (str-length "how\"dy")
6
Jess> (str-compare "Aggie" "Aggie")
0

4.4 Lists

List is a collection of one or more atoms, strings, numbers or lists between parentheses.  As you can see, list is the base for the whole syntax of Jess.

Jess> (+ 1 2)
3

5. Variables

5.1 Binding

The variables should be denoted with a preceding "?". You can use bind to associate something to a variable.
Jess> (bind ?x "Howdy")

You don't have to declare variables before using them.

Jess> (bind ?x 1)
1
Jess> (= ?x 1)
TRUE
Jess> (> ?x 2)
FALSE

5.2 Multi-field Variables


Multi-field variables are special variables with $ sign preceding them and they refer to a special kind of list. The following functions can be used with these variables.

Jess> (bind $?courses (create$ 625 629 613 606))         ;; creates a multi-field list and assign to multi-field variable
(625 629 613 606)                                                                 
Jess> $?courses                       
(625 629 613 606)
Jess> (first$ $?courses)                                                    ;; returns the first element
(625)  
Jess> (rest$ $?courses)                                                    ;; returns rest of the elements
(629 613 606)
Jess> (complement$ $?courses (create$ 601 602 603 604 605 629 613))   ;; returns all elements of second multi-field not in first one
(601 602 603 604 605)
Jess> (delete$ (create$ 100 101 102 103 104) 2 4)        ;; deletes between the range 2 and 4 (between 2nd and 4th element in list)
(100 104)

5.3 Global Variables

All local variables that you might create will be destroyed when you execute the command reset. You can also have global variables. The global variables will persist even after using (reset) (i.e. even if you assign the variable to something else, it will still remain the initial one after reset). If you want the global variable to reflect the change even after reset, use (set-reset-globals nil)

Jess> (defglobal ?*a* = 100)
TRUE
Jess> ?*a*
100
Jess> (bind ?*a* 50)
50
Jess> (reset)
TRUE
Jess> ?*a*
100

6. Type checking and Conversion

The following functions help in type checking (to see whether the variable is an integer etc and to cast a variable to another type. The examples below would be self explanatory.

6.1 Type checking

-  (eq <expression> <expression>+)    ;; returns true if 1st exp is of the same type as others
-  (neq <expression> <expression>+)  ;; returns true if 1st exp is of the same type and value as others
-  (integerp <expression>)                   ;; returns true if the exp is an integer
-  (numberp <expression>)                  ;; returns true if the exp is a number

Jess> (bind ?x 1.4)
1.4
Jess> (eq ?x 1)
FALSE
Jess> (integerp ?x)
FALSE

6.2 Type casting

It's similar to Java's typecasting. Generally, the use of casting in Jess won't be that common in your code.

- (float <numeric-expression>)       ;; casting to float
- (integer <numeric-expression>)   ;; casting to  integer

Jess> (bind ?x 1.4)
1.4
Jess> (= ?x 1)
FALSE
Jess> (= (integer ?x) 1)
TRUE


7. Operators

7.1 Arithmetic

The arithmetic operators are the same in Jess as any other standard programming language. But, you have to represent them in prefix form. If you want to say x++ somewhere in your program, you should say
(bind ?x (+ ?x 1))

7.2 Comparison

For testing equality/inequality, you can use the operators in the same way. Remember, = is a comparison operator. For assignment, you should use bind.

Jess> (bind ?x 1)
1
Jess> (= ?x 1)
TRUE
Jess> (> ?x 2)
FALSE

7.3 Logical

There are three logical operators available in Jess. They are and, or and not. The following example will help you understand their usages and functionalities.

Jess> (bind ?x 1)
1
Jess> (and (= ?x 1) (< ?x 0))
FALSE
Jess> (or (= ?x 1) (< ?x 0))
TRUE
Jess> (not (= ?x 5))
TRUE

As any other programming language, Jess allows the use of if-then-else statements and while loops. 

- (if <expression> then <actio>+  else <action>+)
- (while <expression> do <action>+)

 

8 Facts

The facts are stored in the knowledge base. You can do following things with facts.

8.1 Add Facts

You can use assert to add a fact to the KB. Assert is especially helpful when you want to add a fact to a KB when a rule is fired or during the execution of the program. The examples are from the example code cat.clp.

(assert (Room (x 5) (has-milk TRUE)))  ;; asserts that 5th room has milk

However, to definite the initial condition of the world, it would be easy to use deffacts to assert all facts in one shot.

(deffacts init-room
  (Room (x 1))
  (Room (x 2))
  (Room (x 3))
  (Room (x 4))
  (Room (x 5) (has-milk TRUE))
)

8.2 Remove Facts

You can remove facts using retract. However, the usage of retract isn't that simple as you have to first get the fact-id of the fact the concerned fact that you want to remove from KB and then remove it.
....
?room <- (Room (x 5) (has-milk TRUE))  ;; gets the fact id
=>
(retract ?room)                                          ;; removes the fact

8.3 Using templates

You can roughly compare templates to a struct in C or Class in C++/Java. This is a very handy tool in representing different worlds. The fields inside a template are called slots. You should use deftemplate to define a template. The following example would be self explanatory on it's usage.

(deftemplate Cat
  (slot x (type integer) (default 1))
  (slot status (default hungry) )
  (slot smells-milk (default FALSE))
)

You can use modify to change the values of a slot. Look at the following example.
...
?cat <- (Cat(x 5))
=>
(modify ?cat (smells-milk TRUE))


9 Rules

Rules are responsible for taking actions based on the facts in the KB. The rules have a left-hand-side part and a right-hand-side part with the "implies" operator in between. In a sense, all rules can be compared to a if-else statement in procedural languages. In procedural languages, the statements are executed line by line and only once. But, Jess fires all the rules continuously as long as it's left-hand-side is satisfied. You should use defrule to define a rule. The following example defines the rule for moving the cat from current room to the next room.

(defrule move
  ?cat <- (Cat(x ?x) (smells-milk FALSE))
=>
  (modify ?cat (x (+ ?x 1) ))
  (printout t "Cat moves to ["?x"]." crlf)
)


The above rule would be fired whenever the cat doesn't smell milk. Assume, we defined the initial states using deffunction and I want to ask the user whether he is ok with the default values of the location of milk. Now, we might have a conflict of which of these two rules should be executed? In procedural languages, it is easy as the code is executed line by line in a timely fashion. Here, if we have two or more rules with all preconditions satisfied, you should assign some priority (refered as salience) to each rule. The salience should be assigned while you define the rule and it is just a simple integer value. So, in a conflicting scenario, a rule with higher salience would be always executed. We can add the salience value as follows.

(defrule move
(declare (salience 50))
...
=>
....
)

10. Functions

In the above discussions, we went through several inbuilt functions in Jess. In addition, you can also define your own function so that you don't have to dump everything inside rules. You should use deffunction to define a function. You can call the following function by calling  (change-default)

(deffunction change-default ()
   ......
  (assert (Room (x ?x) (has-milk TRUE)))
)

11. I/O operations

11.1 Standard I/O

There are two functions available for taking input from Standard input (your terminal). You can use read to read a single atom or string or number. You can also use readline to read an entire line as a string.

- (read <router-identifier>)  ;; router-identifier has to be t for standard input - specifying t is optional
- (readline <router-identifier>) ;; --same--

Jess> (read t)
or inside the code
(if (eq (read) y) then
  (retract ?room)
  (change-default)
)

To write to the terminal, you can use printout. As we saw for read, you would specify the router-identifier which is again t as you are printing to the standard output (your terminal).

- (printout <router-identifier> <expression>)

Jess> (printout t "Howdy!" crlf)
Howdy!


11.2 File I/O

File I/O is easy in Jess. Use the open function with a <router-identifier> to open a file with read/write/append mode. Then, to read the contents of the file, you can use the read/readline function using the same router-identifier that you used for opening the file. Similarly, to write to a file, you can use printout function using the same router-identifier that you used for opening the file.

- (open <file-name> <router-identifier> [r or w or a])

The following example would clearly illustrate the usages of the above functions.

Jess> (open temp.txt id w)               ;;   open a file temp.txt in write mode
id                                                       ;;   returns the router-identifier
Jess> (printout id "Howdy!!" crlf)    ;;  writes the string "Howdy!" into the file corresponding to the router-identifier id
Jess> (open temp.txt id r)                 ;;  opens the file temp.txt in read mode
id
Jess> (read id)                                  ;;  reads the stuff from the file opened using the router-identifier
Howdy!!
Jess> (system cat temp.txt)              ;;  just double-checking - reading the contents using unix command cat
Howdy!!
<External-Address:java.lang.UNIXProcess>  

To take the output of your program, you can use the unix command "script <output-file>". After executing this command, do all your stuff with Jess (run your program) and then come back to % prompt and say "exit". This will write all your transactions into the output file. Please follow the following sequence and avoid using screenshots

> script output.txt
%java jess.Main
Jess> (run)
..
...
Jess> (exit)
%exit
>more output.txt

12 Debugging

Debugging is a challenging task in any language. In procedural languages, printing the variables would help a lot. In Jess, you can use printout function to print stuffs to the terminal. But, it won't be sufficient. You will be interested in seeing which rule fires, what are the current facts in the KB? etc.

The function (watch all) would tell you the different states as the program executes. (equivalent to running in debug mode). Also you can use (facts) command that would list the functions in the knowledge base. (you should use reset before using facts). Also you can use (rules) command that would list the rules from the rule base. You can try the following sequence.

Jess> (watch all)
Jess> (batch cat.clp)
Jess> (reset)
Jess> (facts)
Jess> (rules)


Pretty print is another nice utility that would print the concerned rule/function/template. The respective functions are ppdefrule, ppdeffunction and ppdeftemplate. The following example shows the usage of ppdeftemplate.

Jess> (ppdeftemplate Cat)
(deftemplate Cat extends __fact ""
(slot x (default 1) (type 4))
(slot status (default hungry))
(slot smells-milk (default FALSE)))


Since, Jess has been developed in Java, it would throw an exception when something goes wrong. Read the exception message carefully as it will give an idea of the problem and the line number corresponding to that.