Download (slot color (type STRING))

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
Introduction to Jamocha
author
Peter Lin
Comment
First complete draft
Date
2008/05/15
Table of Contents
Foreword....................................................................................................................................................... 2
What is an Expert System Shell ..................................................................................................................... 2
What is a Rule Engine ................................................................................................................................... 2
Where to get Jamocha .................................................................................................................................. 3
Starting Jamocha........................................................................................................................................... 3
Windows ................................................................................................................................................... 3
GUI ........................................................................................................................................................ 3
Shell ....................................................................................................................................................... 3
Unix ........................................................................................................................................................... 3
GUI ........................................................................................................................................................ 3
Shell ....................................................................................................................................................... 4
Using Jamocha .............................................................................................................................................. 7
Deftemplates ............................................................................................................................................ 7
Functions ................................................................................................................................................... 9
Rules ........................................................................................................................................................ 11
Scenarios ..................................................................................................................................................... 13
Message Routing ..................................................................................................................................... 14
Chess Game............................................................................................................................................. 21
Limitations .................................................................................................................................................. 27
CLIPS ........................................................................................................................................................ 27
JESS.......................................................................................................................................................... 27
Innovative Features .................................................................................................................................... 28
Foreword
Welcome to Jamocha and thanks for giving it a try. Jamocha is an expert system shell, which implements
Dr. Charles Forgy’s RETE algorithm. Jamocha began as a port of CLIPS from C to Java back in late 2003.
Since then, Jamocha has slowly developed and become a platform for pattern matching and rule engine
research. This tutorial will provide some scenarios and demonstrate several ways of implementing rules.
At the end, the tutorial will cover some of the innovative features in Jamocha.
What is an Expert System Shell
Expert systems were originally designed for artificial intelligence research. As the name suggests, it
consists of a rule engine, programming language and a shell. The rule engine is responsible for executing
the rules. The programming language provides a way to declare the rules, and the shell provides a
programming environment. There are several different types of pattern matching algorithms. Over the
last 25 years, RETE has proven to be the most versatile general purpose pattern matching algorithm.
There are lazy algorithms like LEAPS, which build on RETE. At the most basic level, a rule engine takes a
set of rules and compiles them to an execution format. For RETE, that means a rooted acyclic-directed
graph.
The rule language used by Jamocha was originally designed by the developers of ART at Inference
Corporation. The syntax is inspired by LISP and combines declarative and functional programming
concepts. In most rule engines, there are predefined functions commonly called “built-in”. An example
of a built-in function would be “+” for addition.
What is a Rule Engine
Before we dive into Jamocha, it’s beneficial to get a basic understanding of rule engines and what they
do. A rule at the most basic level is a “if/then” statement. It defines one or more conditions which are
interested in. If those conditions are satisfied, the rule executes one or more actions. The action could
be send an email, calculate a value or send the data somewhere else. Not all rule engines are the same,
but they generally have three parts: domain model, rules and working memory.
The domain model defines the concepts and objects we want to reason over. We can use a database for
an analogy. An object in the domain model is equivalent to a database table, which defines one or more
columns. The rules are equivalent to SQL “select” queries. The working memory is the data in each table.
One row is equivalent to a fact, which is also called object instance. To add, remove or update the data
in the rule engine, we have assert, retract and modify. These commands are equivalent to the Data
Manipulation Language (DML) in SQL.
Where to get Jamocha
The easiest way to get Jamocha is from sourceforge http://www.sourceforge.net/projects/jamocha.
Currently, there’s two branches in Jamocha. The main branch is written by a group at Aachen University
in Germany. The Morendo branch is the original implementation written for high performance and
scalability. The design and implementation of these two branches are completely different. The main
branch is focused on clean design, extensibility, agents and multiple languages. The download page
currently has 4 items.
http://sourceforge.net/project/showfiles.php?group_id=84875
142 – is a version of morendo which runs in java 1.4.2
Jamocha – is the most recent build of the main branch
Jamocha eclipse plugin – is a simple eclipse plugin which provides a simple editor with syntax
highlighting
Morendo – is the high performance build
Starting Jamocha
Jamocha provides both shell and gui mode. The GUI mode takes more memory, but it makes it easier to
view the facts, templates, rules and RETE network. Here are step-by-step instructions for starting
Jamocha.
Windows
GUI
1. Open a new dos shell
2. Go to the directory where Jamocha is located
3. Type “java” to see if Java is installed on the system. If java isn’t installed, go to
http://java.sun.com/ and download the latest JDK. If Java is installed, but the command
doesn’t run, set Java_home=<java path>
4. Type “jamocha.bat –gui”
Shell
1. Open a new dos shell
2. Go to the directory where Jamocha is located
3. Type “java” to see if Java is installed on the system. If java isn’t installed, go to
http://java.sun.com/ and download the latest JDK. If Java is installed, but the command
doesn’t run, set Java_home=<java path>
4. Type “jamocha.bat –shell”
Unix
GUI
1. Start your favorite XServer from Unix command
2. Open a shell
3. Go to the directory where Jamocha is located
4. Type “java” to see if Java is installed on the system. If java isn’t installed, go to
http://java.sun.com/ and download the latest JDK. If Java is installed, but the command
doesn’t run, open jamocha.sh and add JAVA_HOME=<path>
5. Type “jamocha.sh –gui”
Shell
1. Go to the directory where Jamocha is located
2. Type “java” to see if Java is installed on the system. If java isn’t installed, go to
http://java.sun.com/ and download the latest JDK. If Java is installed, but the command
doesn’t run, open jamocha.sh and add JAVA_HOME=<path>
3. Type “jamocha.sh –shell”
Here are some screen captures of what the GUI looks like for the morendo branch. The GUI in the main
branch has a more advanced RETE viewer and more functionality.
Shell Tab
Facts Tab
Functions Tab
Rete Tab
Using Jamocha
Before we dive into writing rules and useful techniques, it’s a good idea to go over the basics. Regardless
of the kind of rules one writes or how complex they are, there are three fundamental concepts one
must understand: deftemplates, functions and rules.
Deftemplates
A deftemplate defines an object structure. In the same way a class defines the fields of an object, a
deftemplate defines the slots. There’s an importance difference between deftemplates and objects.
Deftemplates are data objects, which means they have no behavior. A Java class in contrast can be a
data object or a rich object with predefined behavior. Here is an example.
(deftemplate transaction
(slot accountId (type STRING))
(slot buyPrice (type DOUBLE))
(slot countryCode (type STRING))
(slot currentPrice (type DOUBLE))
(slot cusip (type INTEGER))
(slot exchange (type STRING))
(slot industryGroupID (type INTEGER))
(slot
(slot
(slot
(slot
(slot
(slot
(slot
(slot
industryID (type INTEGER))
issuer (type STRING))
lastPrice (type DOUBLE))
purchaseDate (type STRING))
sectorID (type INTEGER))
shares (type DOUBLE))
subIndustryID (type INTEGER))
total (type DOUBLE))
)
A Deftemplates consists of a name and one or more slots. A slot has a name and optionally a type. If the
type is not defined, the rule engine will automatically convert the objects during evaluation. In situations
where the type is important, declare the type. Slots can also have default values, which Jamocha
currently does not support. Here is the grammar for deftemplates in BNF notation
<deftemplate-construct> ::= (deftemplate <deftemplate-name>
[<comment>]
<slot-definition>*)
<slot-definition> ::= <single-slot-definition> | <multislot-definition>
<single-slot-definition> ::= (slot <slot-name> <template-attribute>*)
<multislot-definition> ::= (multislot <slot-name> <template-attribute>*)
Another way to declare a deftemplate is with an object. The command is “defclass”. When a java Class is
declared using “defclass”, the rule engine uses BeanInfo to convert it to a deftemplate.
Jamocha> (defclass woolfel.examples.model.Account account1)
account1
true
Jamocha> (templates)
account1
_initialFact
for a total of 2
Jamocha> (ppdeftemplate account1)
(account1
(accountId (type STRING) )
(accountType (type STRING) )
(areaCode (type STRING) )
(exchange (type STRING) )
(ext (type STRING) )
(first (type STRING) )
(last (type STRING) )
(middle (type STRING) )
(number (type STRING) )
(officeCode (type STRING) )
(regionCode (type STRING) )
(status (type STRING) )
(title (type STRING) )
(username (type STRING) )
[woolfel.examples.model.Account] )
Notice the last line of the Deftemplate definition has “[woolfell.examples.model.Account]”. This makes
it easier to see which Deftemplates were created from Java classes.
Functions
In Jamocha, functions can be written in Java or in the shell directly. The main difference between them
is performance. Functions written in Java are more efficient than functions defined in the shell or gui.
Functions written in Jamocha use deffunction command.
(deffunction positive-slope
(?x1 ?y1 ?x2 ?y2)
(<
0
(/
(- ?y2 ?y1)
(- ?x2 ?x1)
)
)
)
Once the function is defined, we can use it like this.
(positive-slope 3 3 5 7)
Writing a function in Java is straight forward. The easiest way to learn how to write a function in Java is
to look at the built-in functions in Jamocha. For example, here is the code for “mem-used” function.
package org.jamocha.rete.functions;
import java.io.Serializable;
import
import
import
import
import
import
org.jamocha.rete.Constants;
org.jamocha.rete.DefaultReturnVector;
org.jamocha.rete.Function;
org.jamocha.rete.Parameter;
org.jamocha.rete.Rete;
org.jamocha.rete.ReturnVector;
public class MemoryUsedFunction implements Function, Serializable {
public static final String MEMORY_FREE = "mem-used";
public MemoryUsedFunction() {
super();
}
/**
* the return type of the function. The Contants class contains the
* list of types a function can return
*/
public int getReturnType() {
return Constants.RETURN_VOID_TYPE;
}
/**
* executeFunction performs the logic of the function
*/
public ReturnVector executeFunction(Rete engine, Parameter[] params) {
Runtime rt = Runtime.getRuntime();
long free = rt.freeMemory();
long total = rt.totalMemory();
long used = total - free;
used = used/1024/1024;
total = total/1024;
long mbtotal = total/1024;
engine.writeMessage(String.valueOf(used) + "Mb used of " +
String.valueOf(mbtotal) + "Mb " +
Constants.LINEBREAK,"t");
DefaultReturnVector ret = new DefaultReturnVector();
return ret;
}
/**
* each function must have an unique name
*/
public String getName() {
return MEMORY_FREE;
}
/**
* The types of parameters the function takes for the input. There’s 3
types
* of parameters: ValueParam, BoundParam, FunctionParam
*/
public Class[] getParameter() {
return new Class[0];
}
/**
* each function should provide a description of how to use the
function
*/
public String toPPString(Parameter[] params, int indents) {
return "(mem-free)";
}
}
Once the rule is written, compile it and put it in a jar file. You’ll need to edit jamocha.bat or jamocha.sh
to add the jar file to the class path. Once that is done, the function needs to be loaded with “loadfunction”.
(load-function com.mydomain.function.MyFunction)
Use “functions” to show the current functions loaded. If your function isn’t listed, double check the jar
file and function name to make sure it is not already used. For an example of a complex function,
Jamocha’s website contains an example
http://www.jamocha.org/wiki/pmwiki.php?n=Documentation.Examples.
Rules
Writing rules in Jamocha is just like JESS and CLIPS. A rule consists of conditions and actions. The
condition is commonly referred to as the Left-hand side (LHS) of the rule and the action is the Righthand side (RHS). The LHS consists of conditional elements, which has one or more conditions for a single
deftemplate. The RHS may assert new facts, modify existing facts or perform any arbitrary process. The
basic structure of a rule looks like this.
(defrule ruleName “comment”
;; conditional elements
=>
;; actions
)
From CLIPS user manual, the syntax for rules follows this format. The format uses BNF notation, which is
one of the standard notations for languages.
<defrule-construct> ::=
(defrule <rule-name> [<comment>]
[<declaration>]
<conditional-element>*
=>
<action>*
)
<conditional-element> ::= <pattern-CE> |
<assigned-pattern-CE> |
<not-CE> | <and-CE> | <or-CE> |
<logical-CE> | <test-CE> |
<exists-CE> | <forall-CE>
Conditional elements are patterns for a deftemplate. It may have literal constraints, variable bindings or
joins. A literal constraint is a condition with a constant value. For example:
(Address
(city “santa monica”) ;; the city is santa monica
(zip ?zipcode) ;; bind the zipcode to a variable named “zipcode”
)
For convenience, a literal constraint can have multiple constants. The most common is comparing a
string to multiple constant values. For example:
(defrule livesInLA “rule
(Address
(city “santa
“Englewood”) ;; the city
=>
(printout t “lives
)
to send spam to people in LA”
monica” | “los angeles” | “culver city” |
is equal any of the four values
in LA area” crlf)
We can also write a condition so it checks for the negative case. For example:
(defrule noInLA “rule to spam those not in LA”
(Address
(city ~”los angeles” | ~“santa monica”) ;; not either city
)
=>
(printout t “doesn’t live in LA” crlf)
)
The tilde “~” character is used to indicate “not equal to”. One important note is to be careful mixing
“equal to” and “not equal to” in a constraint. It easy to list dozens or hundreds of literals, and end up
with a rule that is non-sense.
(defrule nonsense “rule that is contradictory”
(Address
(city ~”los angeles” | “los angeles”)
)
=>
(printout t “non sense rule that always fires” crlf)
)
To join 2 facts, we have to first declare a variable binding. In CLIPS rule language, “?” question mark
indicates a variable binding. Here is a simple example.
(defrule losAngeles “simple rule that prints out city”
(Address
(city ?city) ;; bind city to a variable named city
(zip 90013 | 90014)
)
=>
(printout t “the city “ ?city crlf)
)
Once a variable is declared, subsequent use of the same variable indicates a join. Here is an example of a
rule that joins two facts.
(defrule livesInLA “look at the
(Customer
(id ?id) ;; bind id
(name ?name)
)
(Phone
(customerId ?id) ;;
(area 310)
)
=>
(printout t ?name “ lives
)
area code for 310”
to variable named “id”
join on the customer id
in the LA area” crlf)
We can also join several facts and different types. Say for example, some objects have the id as a string,
while other objects use numeric.
(defrule joins
(Customer
(id ?id) ;; bind id to variable named “id”
)
(Profile
(customerId ?id) ;; join on customer id
(category “frequent shopper”)
)
(Order
(customerId ?id) ;; join on customer id
(subtotal ?subt&:( > ?subt 200.00) ) ;; if the subtotal is
greater than 200
)
=>
(give-coupon ?id)
)
The rule above checks if the order subtotal is over 200.00 dollars. Using “&:” connective, we can use any
number of function that returns a Boolean true/false. For example, we can check if a string contains a
specific sequence of characters or any custom function. For example: (name ?name&:(synonym
?name “bob”) ) would check to see if the name is a synonym of bob.
Scenarios
This section provides a few scenarios to demonstrate how to use Jamocha and some useful techniques
for writing rules. Many of these techniques have been around for over 2 decades, but many developers
are not familiar with them. Hopefully, at the end of this section, the reader will have a basic
understanding of how to use rules and a few tricks in their bag.
Message Routing
The first example illustrates how one could use Jamocha to route messages. Let’s say we have an Instant
Messaging system, which needs to route messages efficiently. Each user has an instant messaging client
similar to AOL, Yahoo or MSN Messenger. The client application gives the user the ability to filter the
messages based on their preferences. For example, I may have a list of buddies, and a block list. I want
to insure the messaging server does not send useless messages to reduce bandwidth cost and load. This
means the messaging server has a list of each person’s buddies and block list. When the message
reaches the server, the system routes the message appropriately.
To start out, we need an object model.
public class Message {
String senderId;
String recieverId;
String text;
List images;
List files;
Calender sendTime;
int messageStatus;
}
public class BuddyList {
String userId;
List buddies;
}
public class BlockList {
String userId;
List blocked;
}
public class User {
String firstName;
String LastName;
String userId;
String street1;
String street2;
String city;
String postalCode;
String country;
String password;
boolean publicProfile;
Calendar signUpDate;
Inet4Address ipAddress;
}
The first thing we need to do is to declare those classes in Jamocha. We do that using the “defclass”
command.
(defclass org.jamocha.sample.im.Message Message)
(defclass org.jamocha.sample.im.BuddyList BuddyList)
(defclass org.jamocha.sample.im.BlockList BlockList)
(defclass org.jamocha.sample.im.User User)
After we declare the classes, we can check to make sure they were created successfully.
Jamocha> (templates)
User
BlockList
Message
_initialFact
BuddyList
for a total of 5
Jamocha> (ppdeftemplate Message)
(Message
(files (type java.lang.Object) )
(images (type java.lang.Object) )
(messageStatus (type INTEGER) )
(receiverId (type STRING) )
(sendTime (type java.lang.Object) )
(senderId (type STRING) )
(text (type STRING) )
[org.jamocha.sample.im.Message] )
Jamocha> Jamocha> (ppdeftemplate User)
(User
(city (type STRING) )
(country (type STRING) )
(firstName (type STRING) )
(ipAddress (type java.lang.Object) )
(lastName (type STRING) )
(password (type STRING) )
(postalCode (type STRING) )
(publicProfile (type BOOLEAN) )
(signUpDate (type java.lang.Object) )
(street1 (type STRING) )
(street2 (type STRING) )
(userId (type STRING) )
[org.jamocha.sample.im.User] )
Next we need the business rules the messaging server will use to filter the instant messages. We’ll start
out with some hard coded rules and progress to data driven rules.
Rule: peter.lin blocked messages
If
The sender is in peter.lin block list
Then
Return message “user does not exist”
Rule: peter.lin allowed messages
If
The sender is in peter.lin buddy list
Then
Route message to recipient
The simplest way for a beginner to write a rule is to put the values in the rule directly. The downside is it
results in more rules. For example, AOL has millions of users. If we have 2 million users online at peak
times, it means the systems have to handle 4 million rules. Clearly, that isn’t desirable and could have a
serious impact on performance and scalability. Using a RETE rule engine like Jamocha, we can write the
rule so it is generic.
Rule: blocked messages
If
The sender is in the recipient’s block list
Then
Return message “user does not exist”
Rule: allowed messages
If
The sender is in the recipient’s buddy list
Then
Route message to recipient
The rules in CLIPS format would join on the objects.
(defrule BlockedMessages
?msg <- (Message ;; bind Message object to variable msg
(senderId ?sender) ;; bind senderId to variable sender
(receiverId ?receiver)
)
(BlockList
(userId ?receiver) ;; join on senderId
(blocked $?blocked)
)
(test (member$ ?sender $?blocked) ) ;; check if sender is in the list
=>
(return-msg ?msg) ;; custom function route the message back to sender
(retract ?msg) ;; remove the message, we’re done with it
)
(defrule AllowedMessages
?msg <- (Message
(senderId ?sender) ;; bind senderId to variable sender
(receiverId ?receiver) ;; bind receiverId to variable receiver
)
(BuddyList
(userId ?receiver) ;; join on the recieverId
(buddies $?buddies) ;; bind list to variable buddies
)
(test (member$ ?sender $?buddies) ) ;; test sender and list
=>
(route-msg ?msg) ;; custom function for routing message
(retract ?msg) ;; remove the message, we’re done with it
)
Using data driven approach, we only need 2 rules to handle the buddy list and blocked list. When each
user logs in, it sends data to the server. Using data driven approach, we don’t need to deploy new rules
on the server. It also means that when users log out, we remove the data. This approach makes it easier
for the system to distribute the load dynamically. Using hard coded rules, it would be harder to unload
the rules from one messaging server and load them on a different server. The downside of using data
driven rules is it uses more memory than hard coded rules. Below is an unit test that demonstrates how
to use Jamocha from java.
package org.jamocha.sample.im;
import
import
import
import
java.net.Inet4Address;
java.util.ArrayList;
java.util.Calendar;
java.util.List;
import org.jamocha.rete.Rete;
import junit.framework.TestCase;
public class MessagingTest extends TestCase {
private Rete engine = null;
public MessagingTest() {
}
public void testMessageFilter() {
this.engine = setupEngine();
if (this.engine == null) {
fail();
} else {
try {
Message msg = createSpamMessage();
engine.assertObject(msg,"Message",false,true);
engine.fire();
System.out.println(msg.getMessageStatus());
List facts = engine.getDeffacts();
System.out.println("number of facts - " + facts.size());
Message msg2 = createSimpleMessage();
engine.assertObject(msg2,"Message",false,true);
engine.fire();
System.out.println(msg2.getMessageStatus());
} catch (Exception e) {
e.printStackTrace();
fail();
}
}
}
public Rete setupEngine() {
if (this.engine == null) {
this.engine = new Rete();
}
String ruleset = "./samples/InstantMessaging/instant_messaging.clp";
// we load the rules
engine.loadRuleset(ruleset);
User u = createUser();
BuddyList buddy = createBuddies();
BlockList block = createBlock();
// now assert the facts
try {
engine.assertObject(u, "User", false, true);
engine.assertObject(buddy,"BuddyList",false,true);
engine.assertObject(block,"BlockList",false,true);
return engine;
} catch (Exception e) {
e.printStackTrace();
fail();
}
return null;
}
public User createUser() {
User usr = new User();
usr.setCity("Boston");
usr.setCountry("US");
usr.setFirstName("John");
Inet4Address ip = null;
usr.setIpAddress(ip);
usr.setLastName("Doe");
usr.setPostalCode("010101");
usr.setPublicProfile(false);
usr.setStreet1("100 Massachusetts Ave");
usr.setUserId("john.doe");
return usr;
}
public BuddyList createBuddies() {
BuddyList buddies = new BuddyList();
buddies.setUserId("john.doe");
ArrayList buds = new ArrayList();
buds.add("jane.doe");
buds.add("mike.doe");
buds.add("howard.doe");
buds.add("bestbuds");
buddies.setBuddies(buds);
return buddies;
}
public BlockList createBlock() {
BlockList block = new BlockList();
block.setUserId("john.doe");
ArrayList bl = new ArrayList();
bl.add("hot.tomalie");
bl.add("george.bush");
bl.add("dick.cheney");
block.setBlocked(bl);
return block;
}
public Message createSimpleMessage() {
Message msg = new Message();
msg.setReceiverId("john.doe");
msg.setMessageStatus(Message.QUEUED);
msg.setSenderId("jane.doe");
msg.setSendTime(Calendar.getInstance());
msg.setText("hello john");
return msg;
}
public Message createSpamMessage() {
Message msg = new Message();
msg.setReceiverId("john.doe");
msg.setMessageStatus(Message.QUEUED);
msg.setSenderId("dick.cheney");
msg.setSendTime(Calendar.getInstance());
msg.setText("hello john");
return msg;
}
}
The source for the rules and unit test are available on Jamocha’s subversion repository at
http://jamocha.svn.sourceforge.net/viewvc/jamocha/morendo/samples/InstantMessaging/ and
http://jamocha.svn.sourceforge.net/viewvc/jamocha/morendo/src/samples/org/jamocha/sample/im/.
When we run the unit test in Eclipse, we get the following output in the console.
Message returned, user does not exist
500
number of facts - 4
message recieved!
400
RETE Network for instant_messaging.clp
To make things more interesting, let’s change the functional requirements. Instead of allowing message
in the buddy list to go through, we can allow any message that is not blocked. To do that, we can change
the “allowedMessages” rule.
(defrule AllowedMessages
?msg <- (Message
(senderId ?sender) ;; bind senderId to variable sender
(receiverId ?receiver) ;; bind receiverId to variable receiver
)
(BlockList
(userId ?receiver) ;; join on senderId
(blocked $?blocked)
)
(test (not (member$ ?sender $?blocked) ) )
;; check if sender not in the list
=>
(route-msg ?msg) ;; custom function for routing message
(retract ?msg) ;; remove the message, we're done with it
)
The revised “allowedMessages” rule now uses the BlockList object and makes sure the sender is not in
the blocked list. For those new to rule engines, the “(test)” condition is called Test Conditional Element
(TestCE). Any function that returns a boolean true/false can be used in a test pattern. The second
example is also available on subversion.
http://jamocha.svn.sourceforge.net/viewvc/jamocha/morendo/samples/chess/
http://jamocha.svn.sourceforge.net/viewvc/jamocha/morendo/src/samples/org/jamocha/sample/im/M
essagingTest2.java?view=log
When we run the unit test, we get the following output in eclipse console.
Message returned, user does not exist
500
number of facts - 3
message recieved!
400
Chess Game
One of the earliest uses of expert systems and AI was in chess games. For many people, video games
was the first introduction to AI. Today, many strategy and online worlds employ rule engines for bots
and non-player characters. In this scenario, we’ll look at how to chain rules. Since writing a full chess
program is complex, this example won’t be complete. For those interested in writing a chess game,
there’s a wealth of books on the topic. This section will illustrate some useful techniques. The files for
this demo are located in Jamocha’s subversion repository and can be downloaded from
http://jamocha.svn.sourceforge.net/viewvc/jamocha/morendo/samples/chess/ sourceforge.net. First
we start off with the deftemplate definitions.
(deftemplate piece
(slot name)
(slot id (type STRING))
(slot inPlay (type BOOLEAN))
(slot pieceType)
(slot color)
(slot blocked (type BOOLEAN))
(slot currentPosition)
(slot lastPosition)
(slot weight)
(slot moveCount)
)
(deftemplate capture
(slot attacker (type STRING))
(slot defender (type STRING))
(slot startPosition (type STRING))
(slot endPosition (type STRING))
(slot weight (type INTEGER))
(slot color (type STRING))
)
(deftemplate move
(slot name (type STRING))
(slot id (type STRING))
(slot color (type STRING))
(slot startPosition (type STRING))
(slot endPosition (type STRING))
)
(deftemplate availableMove
(slot
(slot
(slot
(slot
(slot
name (type STRING))
id (type STRING))
color (type STRING))
startPosition (type STRING))
endPosition (type STRING))
)
(deftemplate executedMove
(slot piece (type STRING))
(slot color (type STRING))
(slot startPosition (type STRING))
(slot endPosition (type STRING))
(slot capturedPiece(type STRING))
)
(deftemplate position
(slot piece (type STRING))
(slot lastpiece (type STRING))
(slot cellId (type STRING))
)
(deftemplate turn
(slot color (type STRING))
)
(deftemplate game
(slot status)
(slot nextMove)
(slot result)
)
(deftemplate player
(slot level)
(slot name)
(slot color)
)
(deftemplate computer
(slot calculate)
(slot searchDepth)
)
The example assumes we are using standard chess notation for the pieces and the position. For those
unfamiliar with chess notation, here is a link http://en.wikipedia.org/wiki/Descriptive_chess_notation.
Let’s start out with a simple rule to start the game.
(defrule newGame
?game <- (game
(status "new")
)
=>
(assert (turn (color "white") ) ) ;; assert a turn fact to start the game
(modify ?game (status “playing”) ) ;; change game status to playing
)
The “newGame” rule is responsible for kicking off the game by asserting a turn fact. So why do it this
way and can we write it differently? For example, we make the rule specific to the computer.
(defrule newGame
?game <- (game
(status “new”)
)
(player
(name “computer”)
(color “white”)
)
=>
(bind ?move
(generateFirstMove “white”) ) ;; generate move with custom function
(modify ?game (status “playing”) ) ;; update game status playing
(assert ?move)
)
If we write the “newGame” rule the second way, we need a second rule for cases when the human
player moves first. Going with the first approach allows us to chain the rules. What does it mean to
“chain the rules?” The easiest way to illustrate this is with the second rule.
(defrule newGame
?game <- (game
(status "new")
)
=>
(assert (turn (color "white") ) ) ;; assert a turn fact to start the game
(modify ?game (status “playing”) ) ;; change game status to playing
)
(defrule computerMove
?turn <- (turn
(color ?color) ;; bind color to variable named color
)
(player
(name "computer") ;; the player is the computer
(color ?color) ;; join on the color
)
=>
(assert (computer (calculate true)(depth 1) ) );; assert fact to calculate
)
(defrule movePiece
(move
(color ?color)
(piece ?piece)
(endPosition ?endpos)
)
?piece <- (piece
(color ?color)
(name ?id)
(currentPosition ?lastpos)
)
=>
;; update the location of the piece
(modify ?piece (currentPosition ?endpos) (lastPosition ?lastpos) )
)
When “newGame” rule asserts the new turn fact, it causes “computerMove” rule to match and execute.
The real power of using rule chaining is it provides a way to divide pattern matching into small units of
work and improve performance. It doesn’t matter if the computer is playing white or black, both rules
will still work. We can test the three rules using tutorial.clp in the URL above.
(watch all)
(batch ./samples/chess/simple_chess.clp)
(batch ./samples/chess/chess_start_facts.clp)
(fire)
(assert (move (name "queen biship pawn")(id "qbp")(startPosition
"qbp2")(endPosition "qbp3")(color "white") ) )
(fire)
Tutorial_step_1.clp
If we run “(batch ./samples/chess/tutorial_step_1.clp)”, here is what we get.
Watch Notation:
==>
Fact added
=>
rule added to agenda
<==
Fact removed
==> : fire
Rule executed
Jamocha> (batch ./samples/chess/tutorial_step_1.clp)
==> f-2 (player (level 100) (name "computer") (color "white") )
==> f-3 (player (level 5) (name "peter") (color "black") )
==> f-4 (game (status "new") (nextMove "nil") (result "") )
=> Activation: newGame, id-4 AggrTime--1
==> f-5 (piece (name "queen pawn") (id "qp") (inPlay TRUE) (pieceType "pawn") (color
"white") (blocked FALSE) (currentPosition "q2") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-6 (piece (name "queen biship pawn") (id "qbp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "qb2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-7 (piece (name "queen knight pawn") (id "qnp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "qn2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-8 (piece (name "queen Rook pawn") (id "qrp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "qr2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-9 (piece (name "king pawn") (id "kp") (inPlay TRUE) (pieceType "pawn") (color
"white") (blocked FALSE) (currentPosition "k2") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-10 (piece (name "king biship pawn") (id "kbp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "kb2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-11 (piece (name "king knight pawn") (id "knp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "kn2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-12 (piece (name "king Rook pawn") (id "krp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "kr2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-13 (piece (name "queen biship") (id "qb") (inPlay TRUE) (pieceType "biship")
(color "white") (blocked TRUE) (currentPosition "qb1") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-14 (piece (name "queen knight") (id "qn") (inPlay TRUE) (pieceType "knight")
(color "white") (blocked FALSE) (currentPosition "qn1") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-15 (piece (name "queen Rook") (id "qr") (inPlay TRUE) (pieceType "rook") (color
"white") (blocked TRUE) (currentPosition "qr1") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-16 (piece (name "king biship") (id "kb") (inPlay TRUE) (pieceType "biship")
(color "white") (blocked TRUE) (currentPosition "kb1") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-17 (piece (name "king knight") (id "kn") (inPlay TRUE) (pieceType "knight")
(color "white") (blocked FALSE) (currentPosition "kn1") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-18 (piece (name "king Rook") (id "kr") (inPlay TRUE) (pieceType "rook") (color
"white") (blocked TRUE) (currentPosition "kr1") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-19 (piece (name "queen") (id "q") (inPlay TRUE) (pieceType "queen") (color
"white") (blocked TRUE) (currentPosition "q1") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-20 (piece (name "king") (id "k") (inPlay TRUE) (pieceType "king") (color
"white") (blocked TRUE) (currentPosition "k1") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-21 (piece (name "queen pawn") (id "qp") (inPlay TRUE) (pieceType "pawn") (color
"black") (blocked FALSE) (currentPosition "q7") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-22 (piece (name "queen biship pawn") (id "qbp") (inPlay TRUE) (pieceType "pawn")
(color "black") (blocked FALSE) (currentPosition "qb7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-23 (piece (name "queen knight pawn") (id "qnp") (inPlay TRUE) (pieceType "pawn")
(color "black") (blocked FALSE) (currentPosition "qn7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-24 (piece (name "queen Rook pawn") (id "qrp") (inPlay TRUE) (pieceType "pawn")
(color "black") (blocked FALSE) (currentPosition "qr7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-25 (piece (name "king pawn") (id "kp") (inPlay TRUE) (pieceType "pawn") (color
"black") (blocked FALSE) (currentPosition "k7") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-26 (piece (name "king biship pawn") (id "kbp") (inPlay TRUE) (pieceType "nil")
(color "black") (blocked FALSE) (currentPosition "kb7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-27 (piece (name "king knight pawn") (id "knp") (inPlay TRUE) (pieceType "pawn")
(color "black") (blocked FALSE) (currentPosition "kn7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-28 (piece (name "king Rook pawn") (id "krp") (inPlay TRUE) (pieceType "pawn")
(color "black") (blocked FALSE) (currentPosition "kr7") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-29 (piece (name "queen biship") (id "qb") (inPlay TRUE) (pieceType "biship")
(color "black") (blocked TRUE) (currentPosition "qb8") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-30 (piece (name "queen knight") (id "qn") (inPlay TRUE) (pieceType "knight")
(color "black") (blocked FALSE) (currentPosition "qn8") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-31 (piece (name "queen Rook") (id "qr") (inPlay TRUE) (pieceType "rook") (color
"black") (blocked TRUE) (currentPosition "qr8") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-32 (piece (name "king biship") (id "kb") (inPlay TRUE) (pieceType "biship")
(color "black") (blocked TRUE) (currentPosition "kb8") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-33 (piece (name "king knight") (id "kn") (inPlay TRUE) (pieceType "knight")
(color "black") (blocked FALSE) (currentPosition "kn8") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-34 (piece (name "king Rook") (id "kr") (inPlay TRUE) (pieceType "rook") (color
"black") (blocked TRUE) (currentPosition "kr8") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-35 (piece (name "queen") (id "q") (inPlay TRUE) (pieceType "queen") (color
"black") (blocked TRUE) (currentPosition "q8") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> f-36 (piece (name "king") (id "k") (inPlay TRUE) (pieceType "king") (color
"black") (blocked TRUE) (currentPosition "k8") (lastPosition "nil") (weight "nil")
(moveCount "nil") )
==> fire: Activation: newGame, id-4 AggrTime--1
==> f-37 (turn (color "white") )
=> Activation: computerMove, id-37, id-2 AggrTime--1
<== f-4 (game (status "new") (nextMove "nil") (result "") )
==> f-4 (game (status "playing") (nextMove "nil") (result "") )
==> fire: Activation: computerMove, id-37, id-2 AggrTime--1
==> f-38 (computer (calculate TRUE) (searchDepth 1) )
==> f-39 (move (name "queen biship pawn") (id "qbp") (color "white") (startPosition
"qb2") (endPosition "qb3") )
=> Activation: movePiece, id-39, id-6 AggrTime--1
==> fire: Activation: movePiece, id-39, id-6 AggrTime--1
<== f-6 (piece (name "queen biship pawn") (id "qbp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "qb2") (lastPosition "nil") (weight
"nil") (moveCount "nil") )
==> f-6 (piece (name "queen biship pawn") (id "qbp") (inPlay TRUE) (pieceType "pawn")
(color "white") (blocked FALSE) (currentPosition "qb3") (lastPosition "qb2") (weight
"nil") (moveCount "nil") )
true
Jamocha>
A few closing words on building a chess game. Using a brute force approach to calculate chess moves
and evaluating them is not practical due to space and time. If we write rules to calculate all legal moves
and then evaluate them, the search space would be gigantic. The key is to filter the pieces and only
calculate the moves for the most promising pieces. To do that, the program needs to dynamically weight
the pieces and calculate the moves accordingly. Once the legal moves have been calculated, the
program will need to evaluate the moves and choose the best one. Writing efficient and elegant rules
for a chess game is very hard.
Limitations
Jamocha is a work in progress and currently does not support everything in CLIPS or JESS. Here is a
partial list of features that are currently missing in the morendo branch, which are available in CLIPS and
JESS.
CLIPS
COOL – since Jamocha is written in Java, there’s no plans to support COOL
Simplicity strategy – not implemented
Complexity Strategy – not implemented
LEX Strategy – not implemented
MEA Strategy – not implemented
Random Strategy – not implemented
OR Conditional Element – The work around is to write 2 rules
FORALL Conditional Element – not implemented
LOGICAL Conditional Element – not implemented
Find, replace multifield values – not implemented
Procedural functions – not implemented
JESS
OR Conditional Element – The work around is to write 2 rules
FORALL Conditional Element – not implemented
LOGICAL Conditional Element – not implemented
Create, Delete, Find, replace multifield values – not implemented
Procedural functions – not implemented
Run-until-halt – no plans to implement. Use “no-agenda” instead
Bag – not implemented
Bit-and, Bit-or – not implemented
Bload, bsave – not implemented
Call – not implemented. Eventually it will be implemented
Call-on-engine – not implemented
Defadvice – not implemented
Do-backward-chaining – Eventually Jamocha will support backward chaining, but it will be different than
JESS. It will be based on Paul Haley’s paper on backward chaining.
External-address – not implemented
Foreach - not implemented
Get-member, set-member – not implemented. Eventually it will be implemented
Halt – not implemented. Not plans to support run-until-halt, so halt support is not planned
Import – not implemented. Eventually it will be implemented
Save-facts – not implemented. Eventually it will be supported
Innovative Features
The morendo branch provides some innovative features, which many commercial products do not
provide.
No-agenda – this features allows the engine to run a mix of reactive and non-reactive rules
Temporal-activation – to use temporal activation, set the rule property to true
Assert-temporal – function for asserting temporal facts
Temporal pattern – a new type of conditional element, which provides relative time and interval time
functionality for temporal logic
Temporal fact – facts with extensions for temporal and fuzzy logic support
Lazy agenda – for situations where most of the activations added to the agenda are removed, lazyagenda improves performance.
Alpha node hashing – this improves performance for the situation where there are thousands of simple
rules. In many cases, the performance is constant with respect to rule count
Generate facts – the function will generate the facts needed to fire a given rule
Test rule function – the function will use generate-facts function to generate the facts and assert them.
It provides an easy way to test a rule from the shell
Rule effective date – give a rule an effective date
Rule expiration date – give a rule an expiration date
Rule version – give a rule a version
Remember match – if the property is set to false, the alpha nodes will not remember the partial
matches. The beta nodes will still remember partial matches.