• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Tim Cooke
  • Liutauras Vilda
  • Jeanne Boyarsky
  • paul wheaton
Sheriffs:
  • Ron McLeod
  • Devaka Cooray
  • Henry Wong
Saloon Keepers:
  • Tim Holloway
  • Stephan van Hulst
  • Carey Brown
  • Tim Moores
  • Mikalai Zaikin
Bartenders:
  • Frits Walraven

deftemplate not working JESS 7.1

 
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am new to rule engines but not to java. I have been experimenting for about 2 weeks now with Jess 7.1. Specifically I am trying to implement something similar to the Pricing Engine example.

I have the two problems listed below, but really I am just trying to solve #1 but I think that #2 may be a hindrance.
(please see end of this post for relevant code snippets).
#1) Error occurred in Obligation Engine: No such slot availAmount in template MAIN::BalS at token 'availAmount'
Every example I try tells me that I don't have the proper slot available, however unless I completely misunderstood the documentation, the whole purpose of the "from-class" construct is that JESS can dynamically analyze java objects and build the required slots, etc.

#2) I thought that perhaps since I changed my original property from "_amount" to "availAmount" (I thought perhaps the underscore was causing issues) that I now need to remove my previously defined template. The API and manual both claim that "removeDeftemplate" is a valid function (it takes a string argument for the name) however the JESS.jar file I downloaded (from herzberg.ca.sandia.gov) definitely does not support that method.

-------------
My .clp file:
;; templates for the model classes
(import pkgBusinessObjects.*)
(deftemplate BalanceSheet (declare (from-class BalanceSheet)))

;; rules
(defrule Halve-Available-Balance
"Cut the Available Balance in half if its more than 1000"
(BalanceSheet {availAmount > 1000})
=>
(BalanceSheet setAmount((/ ?availAmount 2))))

My Balance Sheet Class:
public class BalanceSheet {
public String balAccountCode;
public float availAmount;

public void setAccountCode(String inACC)
{
balAccountCode = inACC;
}

public String getAccountCode()
{
return balAccountCode;
}

public void setAmount(float inAmnt)
{
availAmount = inAmnt;
}

public double getAmount()
{
return availAmount;
}
}

And finally my Engine:
public ObligationEngine(BalSht BS, TravelObligation TA)
{
try
{
// Create a Jess rule engine
engine = new Rete();
engine.reset();

// Load the pricing rules
engine.batch("Obligation.clp");
}
catch (Exception e)
{
System.out.println("Error occured in Obligation Engine: " + e.getMessage());
System.out.println("Stack Trace: ");
e.printStackTrace(System.out);
}
 
author and iconoclast
Posts: 24207
46
Mac OS X Eclipse IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Hi Larry,

Welcome to JavaRanch!

By default, "from-class" will create a template from a class using the "JavaBeans properties" of that class, which are actually based on the class's methods, not its instance variables (see, for example here.) So for a class with a method named getAmount(), there will be a slots named "amount"; there's no property named "availAmount" or anything else based on the variable name. There's also a property named "accountCode".

It is possible to tell Jess to add additional slots to hold the public instance variables of the class, but that's useful in other circumstances. For a well-formed JavaBean like this, you should be using the accessor functions.

There's no reason why you should have to use removeDeftemplate() here -- Jess isn't remembering anything from previous invocations. But it's actually a new method, added late in the Jess 7.1 development cycle, so if you've got a Jess version older than 7.1RC1, that method won't be there. The documentation on the web site always corresponds to the latest version of the software, of course.
 
Larry Stark
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
First I would like to thank you for the quick and detailed reply. I can't believe I missed the part about the "JavaBeans properties". I distinctly remember reading that statement, but at the time it just didn't click in my head. Now I need to experiment with that new found knowledge and see how far I can get.

Second, I wanted to confirm for you that indeed, I am using an older version than RC1. I downloaded a copy of JESS on July 1st that is 7.0p2 (and that is the JAR file currently attached to my sample program). Since the dates were so close I just assumed it corresponded to the documentation I was reading on the website. I suppose that was why I hadn't noticed that method previously when I was reading the copy of the documentation on my local computer :-) ...careless beginner mistake.

I am however relieved to learn that JESS does not store data between invocations. There was one line in the manual in the pricing example section [chapter 11 I believe] (about loading a ruleset which is indexed for later uses to increase performance [and for other reasons I presume]) that made me unsure as to whether or not JESS was persisting data. It is safe to assume (from the view-point of my Java program) that a set of rules will no longer exist once that particular instance of the Rete engine is destroyed (program termination/dereferenced/etc.)

Thanks again for your assistance. I hope to post back tomorrow with a success story of my fully functioning debit/credit balance sheet proof-of-concept.
 
Ernest Friedman-Hill
author and iconoclast
Posts: 24207
46
Mac OS X Eclipse IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Larry Stark:
It is safe to assume (from the view-point of my Java program) that a set of rules will no longer exist once that particular instance of the Rete engine is destroyed (program termination/dereferenced/etc.)



Yes. There's no magic persistence between runs. The statement about indexing for later use applies to a single instance of Rete (or peer group of Retes) and only within that single process.
 
Larry Stark
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well I am happy to report that based on your first reply I was finally able to implement some sample rules! Thanks again.

However, I have come across another issue (by design) that has me completely puzzled...

Again, unless I misunderstood the documentation, the objects in working memory are compared against ALL of the rules to determine matches. By that logic, if I have an object that is satisfied by two rules, shouldn't both rules act on that object?

Here is my example that I am working from:
initial amount: 5025
rule1: IF amount < 10,000 THEN amount = amount * 2
rule2: IF amount < 15,000 THEN amount = amount * 4

If everything was working the way I perceive that it should, then I would expect the following calculation:
5025 * 2 = 10050 * 4 = 40200
where 40200 is the end result after all (both) rules have executed.

In reality I am getting 20100 (which I can see is that rule2 is executing with the initial amount of 5025). Or what I suspect is truly happening is that rule1 is executing with an initial value of 5025 then working memory is NOT updated then rule2 is executing with an initial value of 5025. Since there are no more rules to execute, control is returned back to my java code and the final state of the object is its state after rule2 executed.

Obviously then I am not properly updating the working memory. I tried a couple of Jess-based solutions (i.e. using (rest)). Those failed to solve this problem (and don't seem like correct or elegant solutions anyway). Using "reset" in a rule to update the working memory seems like using a sledgehammer to drive a finishing nail. So I moved onto implementing a proper bean with propertyChangeListeners. The interesting part in this (for me) is that depending on whether I fire a propertyChange or create a new propertyChangeEvent I get different results.

propertySupport.firePropertyChange(PROP_ledgerAmount, oldValue, ledgerAmount);
yields: 20100
while
new PropertyChangeEvent(this, PROP_ledgerAmount, oldValue, ledgerAmount);
yields: 10050

Clearly in both cases only one rule is updating my bean. I am completely stumped at this point (again) for two reasons:
#1) Even if I am using propertySupport vs. propertyChangeEvent incorrectly, shouldn't I still get the same result? Either working memory is updated or it isn't. Why would one rule execute under one scenario but not the other?

#2) I tried this same type of update on the BalanceSheet class (from my first post) and that generates an error that the "amount" property cannot be set because it is read-only. Fair enough. That is trivial at this point since the ultimate purpose of this would be to incorporate beans anyway.

Thank you in advance for your continued assistance. It is greatly appreciated.

Code Snippets
---------------------------
Here is my .clp file:
;; templates for the model classes
(import pkgBusinessObjects.*)
(deftemplate Ledger (declare (from-class Ledger)))

;; rules
(defrule Double-Ledger-Balance
"Double the Available Balance if its more than 1000"
?ledgerFact <- (Ledger {ledgerAmount < 10000} (ledgerAmount ?x))
=>
(modify ?ledgerFact (ledgerAmount (* ?ledgerAmount 2) )))


(defrule Quadruple-Ledger-Balance
"Quadruple the Available Balance if its less than 20000"
?ledgerQuadFact <- (Ledger {ledgerAmount < 15000} (ledgerAmount ?x))
=>
(modify ?ledgerQuadFact (ledgerAmount (* ?ledgerAmount 4) ) ))

And here is my entire bean (just in case there is something relevant outside of the "set" method).

 
Ernest Friedman-Hill
author and iconoclast
Posts: 24207
46
Mac OS X Eclipse IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well, there are a number of things you have to understand to interpret this:

1) First of all, if a class supports property change listeners, then changing the value of a property MUST send a notification. Creating the property change event (PCE) object does absolutely nothing -- you need that "firePropertyChange()" call to notify listeners like Jess. Jess assumes that if it's been added as a property change listener (PCL) to an object, then calling setX() will result in an event being sent. Therefore, when you "modify" a PCL-supporting object, Jess just calls setX() on the object, and assumes the PCE will be delivered. Jess only updates its working memory when it sees that PCE. So if the event never comes, then working memory never changes, and Jess and the object will be out of sync.

So in this program, if you DON'T send the PCEs, then first one rule (apparently the quad rule) fires first, updating the object once, and then the other rule fires, updating it a second time. Because working memory was never updated, the object is modified using the OLD value of the slot contents, so the first update is just overwritten.

2) The list of applicable rules (called the "agenda") is updated every time working memory changes. So if you DO send the PCEs, then first the quad rule fires, and now the slot value is not less than 10000 anymore -- it's 20100. Therefore the double rule no longer applies, and it never fires.

3) You may be wondering why the quad rule fires before the double rule, since the double rule comes first in the file. Lexical order isn't considered at all when ordering the rules. The firing of multiple rules that are activated by the same change to working memory -- here, asserting the Ledger fact -- is unspecified. Both rules are activated by the same event, so their relative ordering is undefined.
 
Larry Stark
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thank you again for the detailed reply. I need to brush up on my propertyChangeListeners. I didn't realize that I had to register (that might be wrong term) Jess with the propertyChangeEvent. I guess I assumed it worked more like the notifyAll() function where Jess was actively listening for events from my bean.

Also, if its not probing too deeply into either the Rete algorithm or the parsing engine for the .clp files, I can't help but ask how the lexical order does NOT play a factor in the order the agenda is executed. Surely I'm not the first person to make this assumption, but I still feel compelled to justify my reasoning.

Its really pretty trivial:
#1) I would assume the rules are parsed in the order in which they are read. Since everything is essentially a list I would assume that each rule is parsed as an element of a parent list (where the parent is a list of rules). Naturally then the first rule in the list would correspond to the first rule in the .clp file and so-on and so-forth. Then, once the agenda is built at run-time, I would further assume that the pattern matching is done in order from front to back of the list. (That is also based on the information I read about JESS not spawning extra threads).

#2) If I were to use the findFactByID() method to update one of my facts (say when modifying a bean) they are numbered 1-n where 1 is the first rule in my .clp file and the nth rule is the last one. Before I dig myself any deeper into this hole, I will admit that this observation is strictly based on my limited experience with JESS thus far. I did attempt some "findFactByID()" look-ups which fully support my statements, however I won't be totally surprised if you tell me thats a coincidence or side-effect of the simplistic nature of my agenda.
 
Ernest Friedman-Hill
author and iconoclast
Posts: 24207
46
Mac OS X Eclipse IDE Chrome
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
You don't have to do anything to register Jess as a PropertyChangeListener; adding the object to the engine (with definstance, or just Rete.add()) will automatically do it for you. My point above is that Jess is registered with your object as a listener, and is therefore expecting that if it calls setledgerAmount(), a change event will come immediately back.

About the ordering: assumption number one is wrong. Nothing is just a list! The simplest analogy I can give you is this one:



On my machine, this prints



HashSet uses hash codes to organize its contents, which makes for fast lookups. Jess does the same thing, but on steroids.
 
Larry Stark
Greenhorn
Posts: 5
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Originally posted by Ernest Friedman-Hill:

HashSet uses hash codes to organize its contents, which makes for fast lookups. Jess does the same thing, but on steroids.



Brilliant! Of course that would explain the behavior. I was so stuck on the lists that I couldn't even allow myself to consider the possibility of other data structures. I'm actually really glad that I asked those two questions. The explanations and insight that you've provided should really help me when I try to create a set of rules that goes beyond these trivial examples I've been using to learn.

Once again, thank you very much!
 
What's wrong? Where are you going? Stop! Read this tiny ad:
Gift giving made easy with the permaculture playing cards
https://coderanch.com/t/777758/Gift-giving-easy-permaculture-playing
reply
    Bookmark Topic Watch Topic
  • New Topic