/** * Simple rule for trading. */ rule trade { /* In this rule, one agent will buy a product from another agent. */ declarations Salesman s; Customer c; Product p; conditions c.needs(p); // he won't buy junk... s.owns(p); s.priceAskedFor(p) <= c.getMoney(); actions s.sell(p); c.buy(p); }
true
,
the owns method call with parameter p for the object
s returns true
, and if the result of the invocation
of the method priceAskedFor with parameter p in object
s is less than or equals to the result of the method
getMoney in the object c, then execute the methods
sell and buy in objects s and c respectively
with p as parameter". These methods must have been defined in
the appropriate classes.
true
when the variables are instantiated with those objects,
then the body of the actions
field will be executed.
true
. Accordingly, if a declaration is
of some interface type instead of a regular class, any object whose
class implements that interface can be used.
country.getUnemploymentRate() > 12.5; person.getMiddleInitial() == 'E';
System.out.println("Hello world!"); f.charAt(f.length() - 1) == File.separatorChar;
import
and package
statements. The former is more a syntatic sugar, but as it is a
part of Java language, we thought it would be useful. Package
statements did not make sense in the first version, because the
rules could be stored in any file. As rules now become classes,
we felt the necessity to support this constructor.
Rule ::= "rule" "{" <Rule Body> "}" Rule Body ::= <Declarations> <Local Declarations>? <Conditions> <Actions> Declarations ::= "declarations" (<class name> <ident> ("," <ident>)* )* Local Declarations ::= "localdecl" (<class name> <ident> "=" <expression>)* Conditions ::= "conditions" (<expression>)* Actions ::= (Action)+ Action ::= "tell" "(" <expression> ")" | "retract" "(" <expression> ")" | "modified" "(" <expression> ")" | <block>
rule walkInWumpusWorld { /* If the agent is in a room with a neighboring room that he knows is safe, then he should go to that room, if he hasn't visited it yet. */ declarations agents.Agent archer; localdecl places.Room currPos = archer.getCurrentPosition(); places.Room upperRoom = currPos.getUpperNeighbor(); conditions upperRoom.isSafe(); agent.hasNotVisited(upperRoom); actions agent.moveTo(upperRoom); modified(agent); }
archer.getCurrentPosition().getUpperNeighbor()
could
have been used instead of upperRoom
in the condition
and action sections.
true
for a given set of
objects, then the rule is said to be fireable, and is added to
the conflict set, where one of the rules is chosen to be fired
(which means to execute the statements of its action section).
value
, there
are two accessor methods, int getValue()
and void setValue(int)
.
import jeops.examples.fibonacci.Fibonacci; public ruleBase FibonacciBase { rule BaseCase { // if n == 1 or n == 0, then the value is n. declarations Fibonacci f; conditions f.getN() <= 1; f.getValue() == -1; actions f.setValue(f.getN()); modified(f); // Yes, I modified f } rule GoDown { // if n >= 2, create two sons for the object declarations Fibonacci f; conditions f.getValue() == -1; f.getN() >= 2; f.getSon1() == null; actions Fibonacci f1 = new Fibonacci(f.getN() - 1); Fibonacci f2 = new Fibonacci(f.getN() - 2); f.setSon1(f1); f.setSon2(f2); tell(f1); // Let's tell our knowledge base tell(f2); // that these two sons exist. modified(f); } rule GoUp { // if both subproblems are solved, so let's solve this one declarations Fibonacci f, f1, f2; conditions f1 == f.getSon1(); f2 == f.getSon2(); f.getValue() == -1; f.getN() >= 2; f1.getValue() != -1; f2.getValue() != -1; actions f.setValue(f1.getValue() + f2.getValue()); retract(f1); // I don't need retract(f2); // them anymore... modified(f); } }
jeops.compiler.Main
class, and it is shown in the
following picture:
FibonacciBase.rules
, we can
then compile the rules file into a java file.
java jeops.compiler.Main FibonacciBase.rules
FibonacciBase.java
. This
is the actual implementation of the knowledge base that is driven by the
rules defined in the FibonacciBase.rules
file. As the rule
base uses the class Fibonacci
, JEOPS compiler will try to
invoke the Java compiler to compile this class. If the "javac" program
cannot be found, the user will have to compile the Fibonacci
,
himself. After the generation of the FibonacciBase.java
file, one could define a simple program that used that knowledge base to
reason about the fibonacci series, with a code that would be similar
to that:
import jeops.examples.fibonacci.Fibonacci; public class TestFibonacci { public static void main(java.lang.String[] args) { int n = Integer.parseInt(args[0]);} }
- Fibonacci f = new Fibonacci(n);
- FibonacciBase kb = new FibonacciBase();
- kb.tell(f);
- kb.run();
- System.out.println(f.getN() + "th number of the fibonacci series = " + f.getValue());
Fibonacci
object (as described before) is created.
This is the object that is going to be inserted into the knowledge base
for the reasoning. Its value can be defined in the command-line by the
user.
kb.run()
method returns. In this way, retrieving the new information that
the "agent" has learned becomes as simple as performing a method
invocation.
jeops.AbstractKnowledgeBase
that allows the user to
insert objects into, remove objects from the knowledge base, and to
notify it of changes that happened in the object. These are the
methods:
tell(Object)
flush()
modifiied(Object)
modified
method is seldom used from
outside the knowledge base: it is often used within the rules (as
described below).
retract(Object)
TestFibonacci
shown above. The Fibonacci
object is telled into the knowledge base via a simple
method invocation.
BaseCase
, GoUp
and
GoDown
. As we have been doing in the whole system, we
tried to make the rule body to resemble as much as possible to the
Java world, so that the final result is a system that is easy to
use.
Vector
, but we decided to store
the objects in a structure from where we could retrieve the objects of
a given class in a more efficient way. Hence, the object base is
implemented with a hashtable that maps fully-qualified names of the
classes to the set of objects belonging to that class. With that,
we can efficiently retrieve all objects that belong to a given class,
which is necessary in the matching stage of the inference engine. The
object base is also responsible for storing the inheritance
relationships between the class of the objects stored in it, so that
when the inference engine asks for all objects of class Person, it
will return both the direct instances of this class and the instances
of its subclasses (i.e., its indirect instances).
".rules"
file, is
actually a subclass of jeops.AbstractRuleBase
. This class
defines methods so that the knowledge base can deal with all rule
bases in the same way. These include methods for retrieving control
information such as the number and names of the rules, for testing
rule conditions and firing rules, given their indexes within the rule
base.
.rules
file, have two constructors. One of them
has no parameters, and the other receives an instance of a
ConflictSet
. If the user does not specify which
conflict resolution policy the system must use, JEOPS has a
default policy (defined in the
jeops.conflict.DefaultConflictSet
class) to be used
in this case. This policy does not provide any warranties at all
about the conflict resolution method. It is the most efficient
strategy, though, and it can be very useful in cases where
conflict resolution is not a major concern.
run()
. This is the method that, when invoked,
puts the inference engine to work, reasoning on the objects of its
working memory until the conflict set gets empty. This method was
shown in the example of the fibonacci series.
jeops.conflict
, are the
following:
DefaultConflictSet
LRUConflictSet
MRUConflictSet
NaturalConflictSet
OneShotConflictSet
PriorityConflictSet
jeops.conflict.ConflictSet
. The required methods are
the following:
void insertElement(ConflictSetElement)
ConflictSetElement nextElement()
boolean isEmpty()
void flush()
void removeElementsWith(Object)
void addInternalConflictSetListener(InternalConflictSetListener)
void removeInternalConflictSetListener(InternalConflictSetListener)
jeops.conflict.AbstractConflictSet
, is a superclass
of every conflict set implementation provided by JEOPS.
import jeops.conflict.PriorityConflictSet; import jeops.examples.Fibonacci; public class TestFibonacci { public static void main(java.lang.String[] args) { int n = Integer.parseInt(args[0]);} }
- Fibonacci f = new Fibonacci(n);
- FibonacciBase kb = new FibonacciBase(new PriorityConflictSet());
- kb.tell(f);
- kb.run();
- System.out.println(f.getN() + "th number of the fibonacci series = " + f.getValue());
setConflictSet(ConflictSet)
method of the knowledge base class can be invoked. It should not
be used, though, when the knowledge base current conflict set is
not empty, because it will loose its contents, causing
unpredictable results. A knowledge base is guaranteed to have an
empty conflict set after its creation, after a call to
flush()
, or after a natural exit from the
run()
method.
modified
constructor of JEOPS.
GoUp
in some
previous cycle, but as the value of the right object (its
son1
) was still undefined (i.e., -1
),
the object in the left could not be matched with the declaration
of the rule GoUp
. Eventually, the object in the right
will have its value computed (when the rule GoUp
fires for this object), and the inference engine will be notified
of this change. However, the object of the left was also
modified, and the system needs to be notified of this
modification. This is what we call the "Transitive Modified
Problem"
GoUp
, by saying that
the objects f1
and f2
are regular
declarations of that rule, even though they could be defined as
functions of the object f
(as local declarations),
the system can filter the rule again when the object bound to
f1
and f2
are modified.
jeops.examples.*
.