Saturday, March 28, 2009

Using Enums in Jess Patterns

As Wolfgang Laun and Scott Krasnigor have mentioned on the Jess list, you can indeed use enums in your Jess rule patterns. The trick is that if you look at the way enums work in Java 1.5 and beyond, accessing the enumeration type is a function call. To match a slot on the return value of a function call, you have to use Jess's return value constraint (=).

Figure 1 shows a pseudo-code example.



(slot-name =(EnumType.enumConstant))


Figure 1. Using enums in Jess patterns.

In SINFERS, our rules get a bit more sophisticated. Figure 2 shows the LHS of the rule that proposes candidate computations of the amount of exchangeable calcium (Ca++) ions as a function of the the amount of exchangeable bases, soil pH, and the percentage of clay in a soil sample.



(defrule PROPOSE::ptf__15F1_CA_11
"Proposes _15F1_CA_11 as best candidate to compute _15F1_CA"
(not (MAIN::SoilProperty (labCode "_15F1_CA" ) (state =(SoilPropertyStateType.COMPUTED))))
(MAIN::SoilProperty
(labCode "_15F1_CEC")
(state =(SoilPropertyStateType.INITIAL)|=(SoilPropertyStateType.COMPUTED))
(uncertainty ?e1)
(value ?v1)
(OBJECT ?objSp1))

(MAIN::SoilProperty
(labCode "_4A1")
(state =(SoilPropertyStateType.INITIAL)|=(SoilPropertyStateType.COMPUTED))
(uncertainty ?e2)
(value ?v2)
(OBJECT ?objSp2))

(MAIN::SoilProperty
(labCode "_P10_NR_C")
(state =(SoilPropertyStateType.INITIAL)|=(SoilPropertyStateType.COMPUTED))
(uncertainty ?e3)
(value ?v3)
(OBJECT ?objSp3))


Figure 2. A real world example from SINFERS.

The pattern matches if we see sinfers.core.SoilProperty objects (as shadow facts) that are either INITIAL (meaning they were part of the input set) or COMPUTED (meaning they have survived candidacy, and are now authoritative sources of data for this computed property).

Add this powerful pattern-matching technique to your Jess toolbox! -JM

Friday, March 27, 2009

Application Scripting with the Jess Language

Early on in my running example, the SINFERS soil property expert system that I've been working on for 2+ years now, we encountered the need for a console user interface to drive our prototype during testing and to give proof-of-concept demos.

Arguably, there are many ways to skin this cat (sorry PETA folks!), but what is the quickest and easiest? What is simple enough to meet the immediate need, but also scalable should we decide to keep it? Given our time constraints, we don't have the luxury of expendable code.

Rejected Approaches

The first thought was to write a command line parser in ANTLR, but this was quickly determined not to be worth the time and effort involved since it would require rather special knowledge of ANTLR to maintain and expand.

Second, we looked at using the Jython language, since the Jython API includes the org.python.util.PythonInterpreter class for embedding Jython scripting in a Java application and since we were already using Jython for doing automated rule generation from our pedotransfer function database. This worked well for driving the SINFERS API for a while, but that meant that we'd have to depend on later developers knowing Java, Jython, and Jess. What we really needed was transparent access to SINFERS and Jess via a command line, keeping dependencies on other libraries to a minimum.

Then a crazy idea occurred to me: Just use Jess!

Jess as a General Purpose Scripting Language

Like Dorothy, I had had the power to go back to Kansas all along, I just didn't see it.

The reason is simple: 99% of the time, I use the Jess language for the mundane tasks of scripting rules, defining modules, deftemplates, and all the other constructs and initialization stuff -- all as you're supposed to do. However, the Jess language is much more that just a driver of the Jess API, it can script the Java language itself.

As Ernest is want to say, "Anything that you can do with Java you can do with Jess."

Now, one of the neat things about Jess is that is extremely easy to add new functionality. All you have to do to add a function to Jess is create a new command class that implements the jess.Userfunction interface. For greater convenience, these functions can be loaded via a jess.Userpackage class during application initialization.

This is great if you want to call new, custom functions from Jess script during debugging and general development, but what if Jess is acting as an embedded component in a larger system?

What we decided to do was to create a number of commands classes implementing the jess.Userfunction interface, add them to a userpackage, and make the userpackage an internal implementation detail of the SINFERS API. This gives us a simple and robust means of adding commands to SINFERS. The sinfers.core.commandsCmdImportModel class is shown in Figure 1 as an example.



public class CmdImportModel implements Userfunction {

public Value call(ValueVector vv, Context context) throws JessException {
String successMessage = null;
log.debug("CALLING COMMAND: import-model...");
Object obj = vv.get(1).javaObjectValue(context);
Rete engine = context.getEngine();
Object model = null;
Sinfers sinfers = (Sinfers) (engine.fetch("SINFERS")
.javaObjectValue(context));
if (obj instanceof java.io.File) {
File f = (File) obj;
model = sinfers.importModel(f);
try {
successMessage = "Model imported from " + f.getCanonicalPath() + " OK!";
} catch (IOException e) {
log.error("Model imported failed! Could not find canonical file path.");
e.printStackTrace(System.err);
}
} else if (obj instanceof java.net.URL) {
URL url = (URL) obj;
model = sinfers.importModel(url);
successMessage = "Model imported from " + url.toString() + " OK!";
} else if (obj instanceof java.lang.String) {
String s = (String) obj;
model = sinfers.importModel(s);
successMessage = "Model imported from " + s + " OK!";
} else {
log.debug("Unrecognized argument to import-model command.");
throw new JessException(
"Unrecognized argument to import-model command", null, 0);
}
log.debug("Model imported OK.");
System.out.println(successMessage);
return new Value(model);
}

public String getName() {
return "import-model";
}
}


Figure 1. The SINFERS CmdImportModel class.

Just as Jess has a Main class, so to does SINFERS, which drives an instance of the sinfers.main.Sinfers class. Sinfers is the true application class in the SINFERS API - a facade much like the Rete class in the Jess API.

The Rete method eval() is able to parse and execute functional expressions. So, since each userfunction fits that description, we can pass our SINFERS commands to Jess from the console and have them executed by Rete.eval(). The net effect is that Jess is scripting our API, giving us the ability to run either one SINFERS commands or Jess's commands at the SINFERS command line.

Figure 2 shows the result of having imported one of our soil property data files for analysis. Note the seamless extension with Jess proper.



Figure 2. Using Jess to provide a simple, embedded, command line interface.


Of course, I could have hacked Jess's source to change the prompt and all that, but the problem is more than cosmetic. Among other things, we wanted to reserve the right to pass arguments, expressions, and switches on the command line that Jess eval() cannot accept. [I suppose I could just add more userfunctions for these special cases too, eh? -JM]

The really cool thing is that the Rete instance that is powering the SINFERS command line in this session is the same instance that this session uses for its inferencing -- Jess is pulling double duty! But it gets cooler! Can SINFERS run a script of its commands? You bet! Jess doesn't care. Just like writing any CLP file of Jess code, I can mix and match SINFERS commands with native Jess commands for true shell scripting. See Figure 3 for an example script.


;; test.clp
;; ========
;; Imports and analyzes a SINFERS model file.
(watch all)
(bind ?model (import-model "simpleModel.xml"))
(add-model ?model)
(run-sinfers)
(facts *)


Figure 3. A SINFERS script file.

As far as I know, this is a novel way of exploiting the power of the Jess language to control the API of a host application. - JM

Saturday, February 14, 2009

The Zen of Jess 2009: Part II

by Jason Morris

In Part I, we said that for many new Jess users, declarative programming will be a completely alien paradigm that warrants a careful and thoughtful study before attempting to write serious code. The following steps are a practical checklist that will greatly facilitate your Jess study.

1. Get the latest Jess code.
Go to http://www.jessrules.com/jess/ and install the latest binary version of Jess (jess.jar) or the whole JessDE if you're using Eclipse. Updating old versions will help you avoid fixed bugs incompatibility issues. Jess runs very well on the latest Ganymede (3.4.x) releases of Eclipse.

If you pose a question on the Jess list server and you’re running an old version, the very first thing you will be asked to do is update your code. Don’t expect that code that you wrote on an ancient version of Jess will run with the latest version, although we will certainly help you try to migrate if we can.

NOTE: I wouldn’t use anything else but the JessDE for my Jess development. Editors like JessWin were developed by a third-party and are not supported by the Jess team.



2. Study the online documentation.
You'll want to keep a link to http://www.jessrules.com/jess/docs/71/table_of_contents.html as your current background reference on Jess. The Jess manual still provides the single best overview of how Jess functionality is structured. Dr. Friedman-Hill is dedicated to keeping this document up-to-date and full of relevant examples. This is a manual, not a textbook, so there is no fluff or extraneous filler.

The current table of contents has 22 chapters:
  • Chapters 1 through 11 are essential reading.
  • Chapters 12 through 15 are advanced topics that you can visit once you have mastered the basics.
  • Chapters 16 through 18 are essential reference links that you will need at all times. Chapters 19 through 22 are various appendices.

One unfortunate thing about the online HTML docs is that they are not searchable. So, I would also keep a link to the PDF version, too http://www.jessrules.com/jess/docs/Jess71p2.pdf

The HTML docs really need a better index. Perhaps I can whip up a Microsoft Compiled Help File (*.chm) for the Jess docs? How about a Java help version, too?



3. Create reference shortcuts on your desktop.
I recommend making desktop shortcuts to the following pages:

http://www.jessrules.com/jess/docs/70/function_index.html
http://www.jessrules.com/jess/docs/71/constructs.html#
http://www.jessrules.com/jess/docs/71/api.html#

4. Purchase a copy of Jess In Action (JIA).
JIA http://www.manning.com/friedman-hill by Jess's author, Dr. E. Friedman-Hill, will be your main tutorial and primer on how to program in Jess. Its ISBIN code is 1930110898. Mine is "dog-eared" and annotated already from use – I honestly don’t know what is holding it together anymore. Eat, sleep, and breathe chapters 1 through 7 – know them like a Marine knows his rifle. Try everything at the Jess command line using the Jess language first.

I recommend not trying to program the Jess API directly until you do this step.

Why? Because you will learn the value of what should be done using the Jess language versus using the Jess API this way.

You can see my full Amazon review at
http://www.amazon.com/exec/obidos/tg/detail/-/1930110898/

5. Work the JIA tutorials.
Now, once you have a grasp of the Jess language, try the tutorials in JIA (chapters 8 and beyond), and type the code samples into Jess so that you can see how they work. Experimental learning is the key with Jess – take advantage of the fact the Jess language is interpreted to get that immediate feedback. It's very gratifying to write a little code snippet that gives you insight into a larger example.

Some variations on the what if? theme that worked for me:
  • Writing many, small deffunctions that print something or calculate something.
    This is a great way to practice using Jess's variables and list-based syntax. Practice taking apart and creating lists and multifields. Remember that lists don't just hold primitives, they can hold object references as well.
  • Changing the behavior of a JIA snippet.
    If an example writes output to the console, change it to write to a file. If a defquery looks for all (foo (color ?c)) facts, make it look for all (foo (color blue)) facts, or any non-blue foo facts (foo (color ~blue)).
  • Experiment with LHS patterns.
    Get a little fact-base built with some simple rules and modify the rule LHS patterns a bit after each run. Try all the different pattern constraints (this takes time, but pays huge dividends later). Avoid the performance traps of bad pattern ordering on your LHS of rules.
6. Participate on the Jess Mailing List and Jess Wiki
Bar none, the atmosphere at the Jess list server is one of the friendliest and most supportive on the internet. New Jess users do not have to fear the condescending replies and otherwise inappropriate remarks that mar so many other IT forums and email lists.


Jess List Server Etiquette

1. Read the documentation.
Make sure that you have read the Jess documentation most relevant to your question before you post your question. By doing this, you help eliminate the most likely problems and increase the likelihood of finding a solution on your own which is always very satisfying.

2. Be specific.
When you ask a question about the behavior of some code, give the actual code snippet that is causing the problem, not just a word description of it.

3. Explain your purpose.
Tell us what you are trying to do or what result you want to have. Very often, we can suggest a better or more correct way than what you have already coded.

You can join the mailing list at:
http://www.jessrules.com/jess/mailing_list.shtml
or participate on the Jess Wiki at:
http://www.jessrules.com/jesswiki/view


As for posting on the list server, don't be afraid to ask questions, but do try to exhaust the obvious causes of errors before posting -- you learn more that way. I strongly encourage you to try to replace all questions of the form How do I...?, How can I…? with a simple experimental code snippet.

Here's an example: Instead of asking "How do I make Jess switch from one module to the next?", I'd first read a bit about using (focus), then I'd write a little test like

;; test_focus.clp
(clear)
;; Using a fact as a trigger is perfectly acceptable
(deftemplate MAIN::switch-to (slot module-name))
;; Make a rule that looks for this pattern
(defrule MAIN::switch-module
(MAIN::switch-to (module-name ?name))
=>
;; Show me what's happening
(watch all)
;; Hypothesize here... what do you think will happen?
(focus ?name))
;; Need a place to switch to!
(defmodule FOO)
;; A friendly confirmation that we made it OK
(defrule FOO::executing-in-foo
=>
(printout t "Switched to module FOO!" crlf))
(reset)
(assert (switch-to (module-name FOO)))
(run)

... and Jess will print out

<== Focus MAIN
==> Focus FOO
FIRE 2 FOO::executing-in-foo f-0
Switched to module FOO!
<== Focus FOO ==> Focus MAIN
<== Focus MAIN 2
Jess>

Experimenting like this is very serendipitous. One thing that you might notice is that focus returned to the MAIN module automatically when no further rules in the FOO module were activated. You can imagine that doing many, many little such exercises will really boost your Jess understanding. Eventually, when you move beyond trivial examples to real applications, you'll have a whole toolbox of techniques with which to construct your Jess code.

So, when I'm faced with a tough bug, I ask myself, "Ok...what's Ernest going tell me?", and I dig deeper and usually find the answer on my own. Infinitely more satisfying and more educational! If you do discover a useful approach or novel technique, by all means, share it on the Jess Wiki!



7. Keep a programmer's journal.
As you experiment and construct your own Jess knowledge, you will accumulate your own tips, tricks, and best-practices. Veteran programmers do this religiously. I continue to have many little epiphanies as I create course content, program my own applications, and study new materials, and you'll want to record your thoughts and discoveries, too. Again, if you can share your learning experiences on the Jess Wiki, that would be great!

8. Review some LISP and some CLIPS.
Learning LISP is to learning CLIPS and Jess what learning Latin is to learning English -- it will give you a better sense of where certain concepts originated and help you with the list-based syntax.

CLIPS is the inspiration for Jess, and Jess owes a good deal of its syntax to CLIPS. Therefore, much (but not all) of what you see in CLIPS is similar to Jess.

Expert Systems: Principles and Programming 4th Ed, by Giarratano & Riley, is a good companion volume to JIA for reviewing CLIPS syntax, and it will reinforce your study of Jess. For Common LISP, there is the very good and FREE online reference http://www.gigamonkeys.com/book/

All of these techniques are helpful, but in order to really put them into practice, you have to first study what tools like Jess are meant to do.

We’ll tackle this in Part III. - JM

Sunday, February 08, 2009

The Zen of Jess 2009: Part I
by Jason Morris

Jess and the Art of (Safe) Rule-Based Computing

I’ve noted that new Jess users are highly susceptible to the viral coding malady, scribo precoxium (SP), roughly translated from Latin as “to write prematurely”. Most poor souls who contract scribo precoxium come from relatively weak Java backgrounds. Their bookshelves are devoid of the names Giarratano, Riley, Russell, Norvig, and Jackson, though the sharing of books with the phrases “head-first” or “in a nutshell” seems to be higher among the population of scribo precoxium sufferers. The typical progression finds casual coders hanging out in unmoderated forums leading to libri indifferens, or an indifference to reading reference material. Longer exposure gives way to outright Documentational Disaffective Disorder. In no time, victims are attempting complicated Jess applications without first doing any homework, and once they start writing a little code, the urge to continue cannot be controlled. Soon, they are left exasperated, due dates looming, with piles of uncompilable source. First responders on the scene often report victims face down in puddle of Mountain Dew, comatose from trying to ease their pain by mainlining a snowball of Jess and Drools.

Professionals are less susceptible than academics to SP, though even the most seasoned veteran of many product cycles has been known to contract it. As trained help providers, we on the Jess list server are keen to spot the warning signs. Typical symptoms of SP include severe external pleading such as, “Help me please!” and “I can’t get this to run!”. Often, there is an additional appeal for assistance by a certain date such as, “I need to make this work by tomorrow!!” or something similar.



What can be done?

Fortunately, there is a time-tested treatment for combating scribo precoxium: RTFD. When given intra-occularly, RTFD has been proven in the vast majority of cases to eliminate the symptoms of SP overnight. Small doses of RTFD, taken daily, can prevent the reoccurrence of scribo precoxium. Left untreated, SP can metastize into full-blown molestus neophytus which can impair your hearing, judgment, and ability to take sound programming advice of any kind. In the most serious cases, patients are have been known to develop an owen-oma, an ulcerated rip midway between their eyes and halfway up their forehead.

So, don’t hesitate to write Dr. Friedman-Hill if you need your RTFD prescription refilled.

A New Beginning

All kidding aside, in the five years since I first wrote The Zen of Jess, Jess has acquired a significant body of questions and answers on the Jess email list. Most of these answers come from Dr. Friedman-Hill himself, and though I originally opined that he must be frustrated in having to answer questions that he has taken great pains to answer elsewhere, one would never know it for the effort that he makes ensuring that every question, however “noobious”, is answered. It has occurred to me that despite all the warnings, admonitions, and evidence to the contrary, there are those for whom the cart must come before the horse. So, in keeping with our great Madame Speaker’s penchant for prevention of communicable diseases such as scribo precoxium, I shall advocate a policy of Prophylactic Programming.

Lest you contract SP, keep in mind that using Jess is not so simple as including another JAR in your application. Jess is in many ways a meta-tool, that is a tool for building tools – in my case rule-based expert systems. For many new Jess users, declarative programming will be a completely alien paradigm that warrants a careful and thoughtful study before attempting to write serious code.

We'll delve into what that entails in Part II. - JM

Wednesday, January 28, 2009

Introducing the SINFERS Project


Hi All,
After a nearly three year hiatus and at the bequest of James Owen, I have decided to reactivate my blog. To motivate myself, I'll start a thread about my on-going work with the University of Sydney, Australia. Since May of 2006, I've been working with a super chap by the name of Grant Tranter, a PhD candidate in the Department of Soil Science, Faculty of Agriculture, Food and Natural Resources at UniS. Together, we've been creating an expert system called SINFERS that uses Jess to infer unknown soil properties from a set of known input data.

The underlying theory of SINFERS rests on the concept of a pedotransfer function (PTF) - as predictive functions of certain soil properties from other more available, easily, routinely, or cheaply measured properties [1]. SINFERS ultimately will have 300-500 such PTFs at its disposal for computing soil properties. Listing 1 is a typical example.

34.0023360256908 - 0.171438746911598 * P10_NR_Z - 0.333101650254256 * P10_NR_S

Listing 1. A PTF for computing 15 BAR Moisture g/g by gravimetric pressure plate

The above PTF has to do with soil water content, which can be measured to determined other characteristics under certain conditions (i.e., wilting point and field capacity). Such an examination yields the moisture released, and such tests are conducted under specific suction pressures (15 BAR and 0.3 BAR).

How SINFERS Works
A user inputs a set of soil property data that we, as a convention, refer to as a batch. SINFERS has a knowledgebase that relates the arguments of each serialized PTF in its PTF database to the entered soil properties. If SINFERS recognizes a subset of those soil properties as matching the arguments to a particular PTF, that PTF is de-serialized, instantiated as a Java object, and is made available to SINFERS. The instantiated PTF object has methods that allow it to compute a value for its dependent variable as well as an estimate of the error.

When a PTF calculates a new soil property, that property is placed in working memory along with the initial set. The addition of such new properties generates new subsets that match the argument lists of other PTFs and the process repeats until the knowledgebase is exhausted and all possible soil properties have been inferred from the initial set. A typical PTF propose rule is given in Listing 2.
(defrule propose::ptf_P3A1_1
"Computes the soil bulk density - (g/cm^3)"
(soil-property (batchId ?id)(labCode depth)(value ?depth)(error ?edepth)
(status normal|initial)(ptf ?ptf1))
(soil-property (batchId ?id)(labCode _6A1)(value ?_6A1)(error ?e_6A1)
(status normal|initial)(ptf ?ptf2))
(soil-property (batchId ?id)(labCode P10_NR_S)(value ?P10_NR_S)(error ?eP10_NR_S)
(status normal|initial)(ptf ?ptf3))
=>
; Propose a candidate value and error for this PTF.
(ptf-propose-candidate P3A1_PTF_1 P3A1 "1.36892296397839 - 0.138035067345618 * depth
- 0.321710483557874 * log10(_6A1) + 0.00198950541325674 * P10_NR_S +
( P10_NR_S - 48.8224031007752) * (P10_NR_S - 48.8224031007752) * -0.0000884352051558744"
(list depth _6A1 P10_NR_S)(list ?depth ?_6A1 ?P10_NR_S)
(list ?edepth ?e_6A1 ?eP10_NR_S)))

Listing 2. A PTF rule for computing soil bulk density using the given regression expression.


Lest it appear that the process of selecting PTFs is straight forward, let me point out that not only do the rules have to select which PTFs to apply, but they also have to allow the computed value and uncertainty of any soil property to be updated with a less uncertain one provided that:
  • The update does not cause a circular reference.
  • The update does not overwrite an initial input property.
SINFERS uses a modified propose and revise problem-solving method, implemented by three Jess modules. The propose module contains rules that allow each activated PTF rule to go ahead and compute its value and uncertainty, asserting these as candidates for consideration by the select module. A third module, the revise module handles special cases. For example, if a PTF rule is activated, and the very property it is set to replace was used to compute one of its arguments, a circular reference forms and we abort the computation.

An example of a select rule is given in Listing 3.

(defrule select::choose-best-soil-property-P3A1
"Selects best candidate providing most certain P3A1 and removes next best loser from WM"
(declare (auto-focus TRUE))
?sp1 <-(soil-property (batchId ?id)(id ?id1)(error ?e1)(status candidate)
(labCode P3A1) (value ?v1)(ptf ?ptf1))
?sp2 <-(soil-property (batchId ?id)(id ?id2&~?id1) (error ?e2&:(< ?e2 ?e1))
(status candidate) (labCode P3A1) (value ?v2)(ptf ?ptf2))

=>
; Remove loser
(retract ?sp1)
(printout t "ptf-" ?ptf2 " has replaced ptf-" ?ptf1 " for
providing the most certain P3A1 value = " ?v2 ", error = " ?e2 crlf))
Listing 3. A selection rule for choosing the best bulk density computation.

I will save a detailed explanation of the inferencing logic for a later blog.

Getting Automated
I'll start this thread by talking about how we automated some of the build processes concerning the SINFERS knowledgebase. One of the early decisions was to integrate using Jython as an auxiliary scripting language for SINFERS. Jython provides a handy command line interpreter class that can be plugged into any Java application. With Jython, there is no need to use a tool like ANTLR to write your own scripting language. In fact, we could have used Jess as the internal scripting language for SINFERS (and may still for some parts of it) since Jess (the language) provides many of the same features. The cool thing is that we can drive Jess from Jython as well, so we get the best of both worlds.

When complex rules can be developed by applying a set of parameters to a template, it makes sense to automate the creation of that rule set. Rather than add potentially brittle classes to the SINEFRS API, we decided to write simple Jython scripts to generate our rules.

The Jython function that generates the proposal rules is given in Listing 4 below.

def makeRules(databasePath):
sys = System
rules = StringBuffer()
rules.append('(defmodule propose)\n\n')

xmlFileObj = getFileFromPath(databasePath)
dom = loadXMLDocument(xmlFileObj)
dependentVars = ArrayList()

root = dom.getRootElement();

ptfsCol = root.getChild('ptfs')
ptfs = ptfsCol.getChildren('ptf')

i = 1
for ptf in ptfs:
# Set var count
n = 1

# Extract all the body elements
ptfIdElement = ptf.getChild('id')
ptfLabCodeElement = ptf.getChild('labCode')
ptfNameElement = ptf.getChild('ptfName')
ptfFunctionTypeElement = ptf.getChild('functionType')
ptfLinsCorrelationCoefficientElement = ptf.getChild('linsCorrelationCoefficient')
ptfRmsErrorElement = ptf.getChild('rmsError')
ptfDependentTransformElement = ptf.getChild('dependentTransformation')
ptfCovarianceMatrixElement = ptf.getChild('covarianceMatrix')
ptfClusterCountElement = ptf.getChild('clusterCount')
ptfTrainingSampleCountElement = ptf.getChild('trainingSampleCount')
ptfAlphaElement = ptf.getChild('alpha')
ptfFuzzyExponentElement = ptf.getChild('fuzzyExponent')
ptfExpressionElement = ptf.getChild('expression')
ptfCreationDateElement = ptf.getChild('creationDate')
ptfCountryCodeElement = ptf.getChild('countryCode')
ptfIndependentVariablesCol = ptf.getChild('independentVariables')
ptfIndependentVariables = ptfIndependentVariablesCol
.getChildren('independentVariable')
ptfTrainingClustersCol = ptf.getChild('trainingClusters')
ptfTrainingClusters = ptfTrainingClustersCol.getChildren('trainingCluster')

# Extract the field values
ptfId = ptfIdElement.getText()
ptfLabCode = ptfLabCodeElement.getText()
ptfName = ptfNameElement.getText()
ptfFunctionType = ptfFunctionTypeElement.getText()
ptfLinsCorrelationCoefficient = ptfLinsCorrelationCoefficientElement.getText()
ptfRmsError = ptfRmsErrorElement.getText()
ptfDependentTransform = ptfDependentTransformElement.getText()
ptfCovarianceMatrix = ptfCovarianceMatrixElement.getText()
ptfClusterCount = ptfClusterCountElement.getText()
ptfTrainingSampleCount = ptfTrainingSampleCountElement.getText()
ptfAlpha = ptfAlphaElement.getText()
ptfFuzzyExponent = ptfFuzzyExponentElement.getText()
ptfExpression = ptfExpressionElement.getText()
ptfCreationDate = ptfCreationDateElement.getText()
ptfCountryCode = ptfCountryCodeElement.getText()

# Store the dependent variable lab codes for later
dependentVars.add(ptfLabCode)

rules.append('(defrule propose::ptf_' + ptfLabCode + '_' + ptfId + '\n')
rules.append('\"Insert ptf purpose here\"\n')

# Store the PTF arguments for later
args = ArrayList()
for variable in ptfIndependentVariables:
varLabCodeElement = variable.getChild('labCode')
varLabCode = varLabCodeElement.getText()
args.add(varLabCode)
# Create a conditional element for each argument in the PTF
for variable in ptfIndependentVariables:
varLabCodeElement = variable.getChild('labCode')
varLabCode = varLabCodeElement.getText()
rules.append('(soil-property (batchId ?id)(labCode ' +
varLabCode + ')(value ?' +
varLabCode + ')(error ?e' +
varLabCode + ') (status normal|initial)(ptf ?ptf' +
str(n) + '))\n')
n = n + 1
rules.append('=>\n')
rules.append('; ?ptfName ?labCode ?expression ?arg-syms ?arg-vals ?arg-errs\n')
rules.append('(ptf-propose-candidate ' + ptfName + ' ' +
ptfLabCode + ' \"' + ptfExpression + '\" ')
rules.append('(list')
for arg in args:
rules.append(' ' + arg)
rules.append(') ')
rules.append('(list')
for arg in args:
rules.append(' ?' + arg)
rules.append(') ')
rules.append('(list')
for arg in args:
rules.append(' ?e' + arg)
rules.append(')))\n\n')

# Build the selection rules

rules.append('(defmodule select)\n\n')

for var in dependentVars:
rules.append('(defrule select::choose-best-soil-property-' + var + '\n')
rules.append('\"Selects the best candidate for soil property ' +
var + ' and removes the next best loser from WM\"\n')
rules.append('(declare (auto-focus TRUE))\n')
rules.append('?sp1 <-(soil-property (batchId ?id)(id ?id1)(error ?e1)' +
'(status candidate) (labCode ' + var + ') (value ?v1) (ptf ?ptf1))\n')
rules.append('?sp2 <-(soil-property (batchId ?id)(id ?id2&~?id1)' +
'(error ?e2&:(< ?e2 ?e1)) (status candidate) (labCode ' + var + ')' +
'(value ?v2) (ptf ?ptf2))\n')
rules.append('=>')
rules.append('; Remove loser\n')
rules.append('(retract ?sp1)\n')
rules.append('(printout t "ptf-" ?ptf2 " has defeated ptf-" ?ptf1 " ' +
' for soil property ' + var + ' value = " ?v2 ", error = " ?e2 crlf))\n\n')

sys.out.println('Rule file written OK. \nOUTPUT: ' + ruleOutputFile)
return rules.toString()

Listing 4. A Jython function for generating SINFERS PTF rules.

In the next installment, I'll talk more about the inferencing considerations and how we arrived at the logic.

-JM




[1] See http://en.wikipedia.org/wiki/Pedotransfer_function