Friday, September 11, 2009

Calling Prolog Rules from Java

Last week, I tried my hand at writing some semi-real world Prolog code by modeling the Drools Petstore Example in Prolog. Looking back now, I think the code was pretty amateurish - more expressive code could probably be written using a procedural language such as Java.

This week, I provide a somewhat improved implementation that is less procedural and more logical, or at least more in line with how I think one would code against a typical rules engine. Typically, one would call a goal on a rules engine, and the goal would either succeed or fail. If it fails, there should be some mechanism to let the client know that it failed and (optionally) what extra information it needs to proceed. The client can then call it again (and again) with the extra information, until the goal succeeds, or give up and do something else, depending on the application.

I also wanted the set of Prolog rules to be callable from Java. My main goal in learning Prolog is to be able to write rules that can be treated as configuration from a Java application - the application will retrieve and pass in the necessary facts for the Prolog rules to work on, and then use the decision/output of the rules to do whatever its supposed to. Prior to this, I have built several home grown data-driven "rule-engines" out of database tables/properties files and application code to do something similar, but I figured that Prolog would be more expressive and flexible once I learnt how to use it correctly.

Prolog Rulebase

Here is the new and improved version of the rules. As you can see, its shorter and (at least to me) cleaner. The client will call the checkout/1 goal. If the goal can proceed to completion, ie, it has all the information it needs to complete, then it will assert a cart/1 predicate into the Prolog factbase. The client will see that the goal completed successfully, and will query the factbase with cart(X). On the other hand, if there are not enough facts for the goal to complete, it will fail and assert a question/1 predicate representing the question it needs answered before it can continue. The client will see that the goal failed, and query the factbase with question(X) to get the question that needs to be answered, answer it, and call checkout/1 again with the answer appended to the original list of parameters.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
%% Source: petstore2.pro

:- dynamic cart/1.
:- dynamic question/1.
:- dynamic answer/2.

% A shopping cart is minimally represented as a sequence of quantities
% of [fish, food, tank]. Elements subsequent to that are answers to
% questions that the engine has asked and which it requires answers to
% in order to proceed. If provided, they are asserted into the factbase.
itemize(Cart, Fish, Food, Tank) :-
  [Fish, Food, Tank | Q] = Cart,
  extract_info(Q).
extract_info([H|R]) :-
  assert(H),
  extract_info(R).
extract_info([]) :- !.

% Add 1 packet of food for every 5 fish purchased.
add_free_food(Fish, FreeFood) :-
  FreeFood is floor(Fish / 5).

% Depending on customer's feedback, add more fish food to shopping cart.
% Only do this if the customer has bought some fish. If he is here to
% buy only food or a tank, don't even ask him about it.
add_more_food(Fish, MoreFood) :-
  Fish =< 0,
  MoreFood is 0,
  !.
add_more_food(Fish, MoreFood) :-
  Fish > 0,
  not(answer(how_many_food, _)),
  assert(question(how_many_food)),
  MoreFood is 0,
  fail.
add_more_food(Fish, MoreFood) :-
  Fish > 0,
  answer(how_many_food, MoreFood).

% If customer has bought 10 or more fish and no tank to put them in,
% ask if he wants a fish tank. If answer is already provided, add
% fish tank (or not if he says no) to cart.
add_fish_tank(Fish, _, AddTank) :-
  Fish < 10,
  AddTank is 0,
  !.
add_fish_tank(_, Tank, AddTank) :-
  Tank > 0,
  AddTank is 0,
  !.
add_fish_tank(Fish, Tank, AddTank) :-
  Fish >= 10,
  Tank == 0,
  not(answer(add_a_tank, _)),
  assert(question(add_a_tank)),
  AddTank is 0,
  fail.
add_fish_tank(Fish, Tank, AddTank) :-
  Fish >= 10,
  Tank == 0,
  answer(add_a_tank, AddTank).

% Apply a 10% discount on orders over $50.
apply_discount(Total, Discount) :-
  Total < 50,
  Discount is 0.
apply_discount(Total, Discount) :-
  Total >= 50,
  Discount is 0.1 * Total.

% The main goal which is called from the Java client. Applies the sub-goals
% in sequence, failing if there is not enough information to go ahead. If
% all information is provided, then populates a new shopping cart and asserts
% it into the factbase, from which it should be retrieved by the client.
checkout(Cart) :-
  retractall(answer(_, _)),
  retractall(question(_)),
  retractall(cart(_)),
  itemize(Cart, Fish, Food, Tank),
  add_free_food(Fish, FreeFood),
  add_more_food(Fish, MoreFood),
  TotalFood is Food + MoreFood,
  add_fish_tank(Fish, Tank, AddTank),
  Total is (5 * Fish) + (2 * TotalFood) + (40 * AddTank),
  apply_discount(Total, Discount),
  append([], [Fish, TotalFood, AddTank, FreeFood, Discount], NewCart),
  assert(cart(NewCart)),
  !.

A typical session within the SWI-Prolog listener application looks like this. This is the same flow that the Java client will use as well.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
?- consult('petstore2.pro').
% petstore2.pro compiled 0.00 sec, 0 bytes
true.
?- checkout([10, 1, 0]).
false.
?- question(X).
X = how_many_food.
?- checkout([10, 1, 0, answer(how_many_food,2)]).
false.
?- question(X).
X = add_a_tank.
?- checkout([10, 1, 0, answer(how_many_food,2), answer(add_a_tank,1)]).
true.
?- cart(X).
X = [10, 3, 1, 2, 5.6].

Java Connectivity: Setting up JPL

For calling the rulebase from within Java, I used JPL, a native (JNI) Java library which provides bi-directional connectivity between SWI-Prolog and Java, and which comes bundled with the version of my SWI-Prolog (5.6.64). I copied the JAR file from the distribution into my Maven repository and added it as a dependency to my POM. Here is the snippet.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<project...>
  <dependencies>
    ...
    <dependency>
      <groupId>swiprolog</groupId>
      <artifactId>jpl</artifactId>
      <version>5.6.64</version>
      <scope>compile</scope>
    </dependency>
  </dependencies>
  ...
</project>

At this point, I could write and compile the client but not run it, since it kept failing with the error "UnsatisfiedLinkError: no jpl in java.library.path". Googling for solutions, I came across many pages like this one, but none of them worked for me - perhaps they are all Windows centric?. Ultimately, I remembered using LD_LIBRARY_PATH back in the days when I used JNI libraries, and setting that in my environment (I use Linux, Fedora Core 9, in case that helps) worked instantly with no other change. Here is the snippet from my .bash_profile. Your SWI_Prolog installation directory may be different if you are installing from a package, I had to compile mine from source because there were no distributions on 64-bit platforms.

1
2
# Prolog/JPL native access
export LD_LIBRARY_PATH=/usr/local/lib/pl-5.6.64/lib/x86_64-linux

Java Client

The JPL library has a very clean, minimalistic feel. The code here is based on the examples in the JPL Getting Started page. One caveat - the documentation is either outdated or incorrect, at least in the "Querying with Variables" section - I wasted quite a bit of time trying to figure out why I could not get back the value of the variable from the Prolog rulebase. Ultimately it turned out that I was not naming the variable, and looking up with the Variable reference as shown in the page. The code here pointed me in the right direction. So anyway, here is the Java code for the client.

A "real" client would instantiate the client, and call its init() method, then call the checkout(PetstoreCart) method with various values of PetstoreCart, and finally terminate it with the destroy() method. The checkout(PetstoreCart) method calls the _checkout(PetstoreCart) method in a loop until the cart is fully processed, then returns it.

It is worth pointing out that the _checkout(PetstoreCart) is synchronized. This is because there is only a single Prolog engine per JVM, so access from multiple threads must be serialized. When client code calls _checkout, it passes in the input cart data (see PetstoreCart.clone()), along with an answer map of questions already answered. The first 3 lines of the Prolog checkout/1 goal cleans up any data left over from a previous call. Within the synchronized _checkout(PetstoreCart) code, the code first makes a call to checkout/1, then based on the goal status, makes another call to either cart/1 or question/1.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Source: src/main/java/net/sf/jtmt/inferencing/prolog/PrologPetstoreClient.java
package net.sf.jtmt.inferencing.prolog;

import java.io.Console;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jpl.Atom;
import jpl.JPL;
import jpl.Query;
import jpl.Term;
import jpl.Util;
import jpl.Variable;
import jpl.fli.Prolog;

/**
 * A JPL client to the Prolog rules for the Drools Petstore Example.
 * The basic idea is that the client calls the checkout goal repeatedly
 * until it succeeds. If it fails, the checkout goal will assert a 
 * question which needs to be answered before the goal can proceed
 * further, which the Java client reads and answers.
 */
public class PrologPetstoreClient {

  private final String RULES_FILE = 
    "src/main/prolog/net/sf/jtmt/inferencing/prolog/petstore2.pro";
  
  /**
   * Connects to the engine and injects the rules file into it. This is
   * done once during the lifetime of the engine.
   * @throws Exception if thrown.
   */
  public void init() throws Exception {
    JPL.init();
    Query consultQuery = new Query("consult", new Term[] {
      new Atom(RULES_FILE)});
    if (! consultQuery.hasSolution()) {
      throw new Exception("File not found: " + RULES_FILE);
    }
    consultQuery.close();
  }
  
  /**
   * Stops the prolog engine.
   * @throws Exception if thrown.
   */
  public void destroy() throws Exception {
    Query haltQuery = new Query("halt");
    haltQuery.hasSolution();
    haltQuery.close();
  }
  
  /**
   * Called by client after the PetstoreCart reaches the checkout phase.
   * This method is unsynchronized because it will go back and forth for
   * user input. This is the version that is expected to be called from
   * the client.
   * @param cart the Petstore cart object.
   * @return the processed cart object.
   */
  public PetstoreCart checkout(PetstoreCart cart) {
    return checkout(cart, null);
  }
  
  /**
   * Overloaded version for testing. This contains a hook to supply the
   * answers to the questions, so we don't have to enter them at the 
   * command prompt.  
   * @param cart the PetstoreCart object to checkout.
   * @param answers a Map of question and answer.
   * @return the processed PetstoreCart object.
   */
  protected PetstoreCart checkout(PetstoreCart cart,
      Map<String,String> answers) {
    PetstoreCart newCart = null;
    for (;;) {
      newCart = _checkout(cart);
      if (newCart.isInProgress()) {
        String question = newCart.getQuestion();
        String answer = prompt(question, answers);
        newCart.getAnswers().put(question, answer);
      } else {
        break;
      }
    }
    return newCart;
  }
  
  /**
   * Prompt the user for the question and waits for an answer, or
   * gets the answer from the answers map, if provided.
   * @param question the question to answer.
   * @param answers a Map of question and answer.
   * @return the answer to the question.
   */
  private String prompt(String question, Map<String,String> answers) {
    if (answers == null) {
      Console console = System.console();
      if (console != null) {
        return console.readLine(">> " + question + "?");  
      } else {
        throw new RuntimeException("No console, start client on OS prompt");
      }
    } else {
      // run using a map of predefined answers (for testing).
      if (answers.containsKey(question)) {
        String answer = answers.get(question);
        System.out.println(">> " + question + "? " + answer + ".");
        return answer;
      } else {
        throw new RuntimeException("No answer defined for question:[" + 
          question + "]");
      }
    }
  }

  /**
   * Called from checkout. This is the method that actually issues the
   * checkout query to the Prolog engine, and in case of failure, retrieves
   * the question to be answered, and in case of success, retrieves back
   * the Cart object from the Prolog engine. 
   * @param cart the current form of the PetstoreCart object.
   * @return the processed PetstoreCart object.
   */
  private synchronized PetstoreCart _checkout(PetstoreCart cart) {
    Query checkoutQuery = new Query("checkout", 
      new Term[] {buildPrologCart(cart)});
    boolean checkoutSuccessful = checkoutQuery.hasSolution();
    checkoutQuery.close();
    if (checkoutSuccessful) {
      // succeeded, get cart from factbase
      Variable X = new Variable("X");
      Query cartQuery = new Query("cart", new Term[] {X});
      Term prologCart = (Term) cartQuery.oneSolution().get("X");
      PetstoreCart newCart = parsePrologCart(prologCart);
      newCart.setInProgress(false);
      cartQuery.close();
      return newCart;
    } else {
      // failed, get question, and stick it into question
      PetstoreCart newCart = cart.clone();
      Variable X = new Variable("X");
      Query questionQuery = new Query("question", new Term[] {X});
      newCart.setQuestion(
        String.valueOf(questionQuery.oneSolution().get("X")));
      newCart.setInProgress(true);
      questionQuery.close();
      return newCart;
    }
  }
  
  /**
   * Builds a Term representing a Prolog List object from the contents 
   * of a PetstoreCart. The List is passed to the checkout goal. 
   * @param cart the PetstoreCart object.
   * @return a Term to pass to the checkout goal.
   */
  private Term buildPrologCart(PetstoreCart cart) {
    StringBuilder prologCart = new StringBuilder();
    prologCart.append("[").
      append(String.valueOf(cart.getNumFish())).
      append(",").
      append(String.valueOf(cart.getNumFood())).
      append(",").
      append(String.valueOf(cart.getNumTank()));
    Map<String,String> answers = cart.getAnswers();
    for (String question : answers.keySet()) {
      prologCart.append(",").
        append("answer(").
        append(question).append(",").
        append(answers.get(question)).
        append(")");
    }
    prologCart.append("]");
    return Util.textToTerm(prologCart.toString());
  }

  /**
   * Parses the returned Term object representing a Prolog List that
   * represents the processed contents of the cart. The Term is 
   * returned as a compound term of nested "." (concat) functions.
   * @param prologCart the Term representing the Cart, returned from
   *        querying Prolog with cart(X).
   * @return a PetstoreCart object.
   */
  private PetstoreCart parsePrologCart(Term prologCart) {
    List<String> elements = new ArrayList<String>();
    Term term = prologCart;
    for (;;) {
      if (term.type() == Prolog.COMPOUND) {
        elements.add(term.arg(1).toString());
        term = term.arg(2);
      } else if (term.type() == Prolog.ATOM) {
        break;
      }
    }
    PetstoreCart cart = new PetstoreCart(
      Integer.valueOf(elements.get(0)),
      Integer.valueOf(elements.get(1)), 
      Integer.valueOf(elements.get(2)));
    cart.setNumFreeFood(Integer.valueOf(elements.get(3)));
    cart.setDiscount(Float.valueOf(elements.get(4)));
    return cart;
  }
}

The PetstoreCart class is a simple bean with a custom clone() and prettyPrint() methods. The bean is shown (without the getter/setter methods for brevity) below:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// Source: src/main/java/net/sf/jtmt/inferencing/prolog/PetstoreCart.java
package net.sf.jtmt.inferencing.prolog;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;

/**
 * Simple bean to hold shopping cart information for PrologPetstoreClient.
 */
public class PetstoreCart {

  private int numFish;
  private int numFood;
  private int numTank;

  private String question;
  private Map<String,String> answers = 
    new HashMap<String,String>();
  private int numFreeFood;
  private float discount;
  private boolean inProgress;
  
  public PetstoreCart(int numFish, int numFood, int numTank) {
    this.numFish = numFish;
    this.numFood = numFood;
    this.numTank = numTank;
  }

  @Override
  public PetstoreCart clone() {
    PetstoreCart clone = new PetstoreCart(numFish, numFood, numTank);
    clone.setAnswers(getAnswers());
    return clone;
  }

  public String prettyPrint() {
    StringBuilder buf = new StringBuilder();
    buf.append("[").append(String.valueOf(getNumFish())).
      append(", ").append(String.valueOf(getNumFood())).
      append(", ").append(String.valueOf(getNumTank())).
      append(", ").append(String.valueOf(getNumFreeFood())).
      append(", ").append(String.valueOf(getDiscount())).
      append("]");
    return buf.toString();
  }
  ...
}

The corresponding unit test for the client is shown below. I test several common boundary cases to make sure that the code works.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// Source: src/test/java/net/sf/jtmt/inferencing/prolog/PrologPetstoreClientTest.java
package net.sf.jtmt.inferencing.prolog;

import java.util.Map;

import org.apache.commons.lang.ArrayUtils;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

/**
 * Test cases to exercise the Prolog Petstore JPL client.
 */
public class PrologPetstoreClientTest {
  
  private static PrologPetstoreClient CLIENT;
  
  @BeforeClass
  public static void setupBeforeClass() throws Exception {
    CLIENT = new PrologPetstoreClient();
    CLIENT.init();
  }

  @AfterClass
  public static void teardownAfterClass() throws Exception {
    CLIENT.destroy();
  }
  
  @Test
  public void test5_0_0() throws Exception {
    Map<String,String> answers = ArrayUtils.toMap(new String[][] {
      new String[] {"how_many_food", "0"},
      new String[] {"add_a_tank", "0"}
    });
    System.out.println("?- checkout([5, 0, 0]).");
    PetstoreCart cart = CLIENT.checkout(new PetstoreCart(5, 0, 0), answers);
    System.out.println("?- cart(" + cart.prettyPrint() + ").");
    System.out.println();
  }

  @Test
  public void test10_0_0() throws Exception {
    Map<String,String> answers = ArrayUtils.toMap(new String[][] {
      new String[] {"how_many_food", "5"},
      new String[] {"add_a_tank", "0"}
    });
    System.out.println("?- checkout([10, 0, 0]).");
    PetstoreCart cart = CLIENT.checkout(new PetstoreCart(10, 0, 0), answers);
    System.out.println("?- cart(" + cart.prettyPrint() + ").");
    System.out.println();
  }

  @Test
  public void test10_0_0_1() throws Exception {
    Map<String,String> answers = ArrayUtils.toMap(new String[][] {
      new String[] {"how_many_food", "5"},
      new String[] {"add_a_tank", "1"}
    });
    System.out.println("?- checkout([10, 0, 0]).");
    PetstoreCart cart = CLIENT.checkout(new PetstoreCart(10, 0, 0), answers);
    System.out.println("?- cart(" + cart.prettyPrint() + ").");
    System.out.println();
  }

  @Test
  public void test0_10_0() throws Exception {
    Map<String,String> answers = ArrayUtils.toMap(new String[][] {
      new String[] {"how_many_food", "0"},
      new String[] {"add_a_tank", "0"}
    });
    System.out.println("?- checkout([0, 10, 0]).");
    PetstoreCart cart = CLIENT.checkout(new PetstoreCart(0, 10, 0), answers);
    System.out.println("?- cart(" + cart.prettyPrint() + ").");
    System.out.println();
  }
}

Here is the output from the JUnit test run. For completeness, one should put in Assert calls, but as you can see, the cart(X) values look correct. Another thing to notice is that based on the inputs, not all the questions are required everytime, so the system asks them only as needed.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
?- checkout([5, 0, 0]).
>> how_many_food? 0.
?- cart([5, 0, 0, 1, 0.0]).

?- checkout([10, 0, 0]).
>> how_many_food? 5.
>> add_a_tank? 0.
?- cart([10, 5, 0, 2, 6.0]).

?- checkout([10, 0, 0]).
>> how_many_food? 5.
>> add_a_tank? 1.
?- cart([10, 5, 1, 2, 10.0]).

?- checkout([0, 10, 0]).
?- cart([0, 10, 0, 0, 0.0]).

What about Prova?

I did take another look at Prova, this time in anger, because I struggled quite a bit to get the JNI stuff to work. However, it turns out that Prova's Prolog dialect is different from SWI-Prolog, because it failed to parse my rulebase. I did not want to learn Prova and rewrite the Prolog code in Prova at that point, so I (fortunately, it turns out) decided to take another crack at JPL.

Another thing about Prova is that it comes with a lot of dependencies. That should probably not be a concern in our brave new Mavenized world, as long as Prova exposes the dependencies in its meta information. I don't know for sure either way, although they probably do since their JAR file appears to be built by Maven based on its naming convention. But in any case, I decided to stick with SWI-Prolog and JPL for now - just too lazy to learn another language right now, I guess.

One other thing that attracted me to Prova in the first place is its ability to connect to a database from within the rulebase and potentially generate facts directly from it. However, this is also possible to do with JPL (indirectly, by asserting facts into the rulebase from the Java client) as this post describes.

14 comments (moderated to prevent spam):

António Vaz said...

Hi, i'm studding the possibilities of implementing a multi-user expert system using IBM portal and JPL.

I read somewhere that we can only have one instance of prolog for JVM, but i need to guarantee that i can run the program for multi-user.

Have you tested the multi-threaded or some other way of doing this?

Sujit Pal said...

Hi Antonio, yes, there can be only one instance of the prolog engine per JVM, and if you are using it for multiple threads, you (the application programmer) must ensure that the calls are serialized and they don't leak state. My example is a simple one, but I have done that by making the _checkout method synchronized, by maintaining state on the client end, and by retracting all facts asserted previously if any in the checkout goal.

jean said...

Hi. I'm studying Artificial Intelligence and I'm implementing a software by jpl provided by swi-prolog for calling prolog from java. I'm using Eclipse as IDE. I don't know how to start this example very useful for my purposes that I found on line.

and these are the prolog files about this program



kb_intro(['',
'MDC: A Demonstration Medical Diagnostic System',
' Using Confidence Rules',
'']).

kb_threshold(65).

kb_hypothesis('The patient has allergic rhinitis.').
kb_hypothesis('The patient has strep throat.').
kb_hypothesis('The patient has pneumonia.').
....

c_rule('The patient has nasal congestion.',
95,
[],
['The patient is breathing through the mouth.',yes]).

c_rule('The patient has a sore throat.',
95,
[],
[and,['The patient is coughing.',yes],['The inside of the patient''s throat is red.',yes]]).



then the other file prolog

:- ensure_loaded('getyesno.pl').

:- dynamic known/3.

conman :- kb_intro(Statement),
writeln(Statement),nl,
kb_threshold(T),
kb_hypothesis(Hypothesis),
confidence_in([Hypothesis,yes],CF),
CF >= T,
write('Conclusion: '),
writeln(Hypothesis),
write('Confidence in hypothesis: '),
write(CF),
writeln('%.'),
explain_conclusion(Hypothesis), fail.

conman :- kb_hypothesis(Hypothesis),
confirm([Hypothesis]),!,
writeln('No further conclusions.'),
nl, finish_conman.

conman :- writeln('Can draw no conclusions.'),
nl, finish_conman.

finish_conman :-
retractall(known(_,_,_)),
write('Do you want to conduct another consultation?'),
yes, nl, nl, !, conman.

finish_conman.


ask_confidence(Hypothesis,CF) :-
kb_can_ask(Hypothesis),
writeln('Is the following conjecture true? --'),
write(' '), writeln(Hypothesis),
writeln(['Possible responses: ',
' (y) yes (n) no',
' (l) very likely (v) very unlikely',
' (p) probably (u) unlikely',
' (m) maybe (d) don''t know.',
' (?) why?']),
write(' Your response --> '),
get_only([y,l,p,m,n,v,u,d,?],Reply), nl, nl,
convert_reply_to_confidence(Reply,CF),
!, Reply \== d,
ask_confidence_aux(Reply,Hypothesis,CF).


........................

and the last is:

get_yes_or_no(Result) :- get(Char), % read a character
get0(_), % consume the Return after it
interpret(Char,Result),
!.

get_yes_or_no(Result) :- nl,
write('Type Y or N:'),
get_yes_or_no(Result).

interpret(89,yes). % ASCII 89 = 'Y'
interpret(121,yes). % ASCII 121 = 'y'
interpret(78,no). % ASCII 78 = 'N'
interpret(110,no). % ASCII 110 = 'n'

This file if I run it
from Prolog they work very well. The same when I call them from the program java.
I can see the output in the console of eclipse.
But I would like to build
some java gui for the interaction between the user and the system
but I don't know how
to take the code from prolog in Java and put it in the gui. For example how can I reply to the first question from a gui java and communicate the reply to the prolog program?

Thanks
jean

Sujit Pal said...

Hi Jean, sorry for the delay in replying, have been playing catch-up at work. To answer your question, I suppose you could either build a local or web based GUI. With the approach I describe, you will make multiple calls to prolog - you communicate via a custom bean (in this case PetStoreCart). For the first call, you provide a populated bean with the information from your application to Prolog. Prolog executes its rule and updates the bean with a question it needs answered. The returned bean contains the question which you then pass back to the user, who answers it, and the Java application calls Prolog again with the answer, which allows it to move a little further into the rule, where it needs another question answered, so it populates the bean with the new question and passes it back, and so on.

jean said...

Hi Sujit,
thanks for your reply, I really appreciate it very much during this period of my efforts for understanding the use of jpl from java. I used your example for understanding better the relation between jpl and java and I found it very useful for my purposes. I noticed that for viewing the result of the consult of the file prolog and view this message on the Eclipse console
% C:\Users\my_pc\workspace\petstore2.pl compiled 0.00 sec, 5,996 bytes
I had to change the part of the main in this way.

public static void init() throws Exception {
Term consult_arg0[] = {
new Atom(RULES_FILE )
};

Query consult_query1 =
new Query(
"consult",
consult_arg0 );

boolean consulted = consult_query1.query();

if ( !consulted ){
System.err.println( "Consult failed" );
System.exit( 1 );
}
else {

checkout(PetstoreCart cart, Map answers);
}
}
I followed the example on a forum about this preferred consult phase in jpl.
Apart this, my question is about the part of your explanation when you talk about the steps of the run of the program and in detail the call to the checkout(PetstoreCart) method with various values of PetstoreCart, and finally terminate it ……
I found that I can’t call the checkout(PetstoreCart) method. I tried in various way, but I can’t (I tried as shown above with checkout(PetstoreCart cart, Map answers); and other ways ..). I continued to write the code about the the getter/setter methods in this way
private char[] getDiscount() {
return null;
}

private char[] getNumFreeFood() {

return null;
}

char[] getNumTank() {

return null;
}

char[] getNumFood() {

return null;
}

char[] getNumFish() {

return null;
}

public void setDiscount(Float valueOfFl) {
this.valueOfFl=valueOfFl;
}

public void setNumFreeFood(Integer valueOfint) {
this.valueOfint=valueOfint;

}

public void setInProgress(boolean b) {


this.b=b;

}
public boolean isInProgress() {
return false;
}
etc..

I hope that section is not wrong and doesn’t affect to the call to the method. However my problem is the call to the checkout(PetstoreCart) method. Can you tell me how call this method, please?

Best regards

jean

Sujit Pal said...

Hi Jean, take a look at the JUnit test in my post. There are multiple examples of this. Make sure to fold in the commands in @BeforeClass and @AfterClass annotated methods in your calls.

Justin said...

Hi, I don't know whether you can help me with the following problem:

I added to the PATH "D:\swipl\bin" and CLASSPATH "D:\swipl\lib\jpl.jar" in environment variables correctly, and then ran it, the following error came out:

Exception in thread "main" java.lang.UnsatisfiedLinkError: jpl.fli.Prolog.thread_self()I
at jpl.fli.Prolog.thread_self(Native Method)
at jpl.Query.open(Query.java:286)
at jpl.Util.textToTerm(Util.java:162)
at jpl.Query.Query1(Query.java:183)
at jpl.Query.(Query.java:176)
at Versions.main(Versions.java:11)


I'm using the version 6.2.0 of SWI-Prolog on Windows 7, and I'm developing Java in Eclipse.

Sujit Pal said...

Hi Justin, its been a while and I don't have the setup on my machine anymore, but did you set the LD_LIBRARY_PATH (or equivalent for your OS)? Since JPL is basically a JNI wrapper over the swipl interpreter, it needs to know where the .so file (or equivalent for your OS) is.

Dharmith said...

Hi, I'm having trouble trying to query , because I have a predicate ,solve/2, which inside has a predicate and asks for the Confidence Factor, eg: solve(next_st(X, St, Desc), CF).


I'm trying to query this using this:

Query q1 =
new Query(
new Compound("solve", new Term[]
{new Compound("next_st", new Term[]
{new Variable("X"), new Variable("St"), new Variable("Desc")})}));

while (q1.hasMoreSolutions() ){
solution = q1.nextSolution();
System.out.println( "X = " + solution.get("X"));
}


But it's not working, hope you can help me with this.

Sujit Pal said...

Hi Dharmith, do you have the values for X, St and Desc defined? Perhaps try the expression in the Prolog shell? That may give you more insight into the problem.

Dharmith said...

Hi Sujit, first of all thank you for replying because this is been a great help for my thesis.

Yes I have those values defined.
Essentialy I have 2 files in my system and they are working.
One is a KB defining how "IF.... THEN" expressions can be written and readable, and how to process the Confidence factor as well.

......

:- unknown(_, fail).



:- op(10, xfy, then).
:- op(100, fx, if).

:- op(1000, xfy, and).
:- op(1100, xfy, or).

:- op(1199, xfy, '?').


fact(P, CF):- (P ? CF), P \= _ then _ .

rule(A, B, CF):- if A then B ? CF.
rule(A, B, 1):- if A then B.

.....

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

And I have another file with all the "if and then" rules.

:- ensure_loaded('C:/workspace/Thesis/src/cf_solve.pl').




et(e1).

person(dharmith).
current_st(daniel, norm, unknown_reason).
attributes(daniel, e1, negative).
attends(daniel, e1).
cog_interprets(daniel, e1, risk(e1)).



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% NORMAL -> HYPED_EVENT %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


if (person(X) and et(E) and
current_st(X, norm, _) and
attends(X, E) and
attributes(X, E, negative) and
not(cog_interprets(X, E, risk(E))))

then next_st(X, hyped_event, occurred(E)) ? 0.8.

....

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

When querying on the file with the "if and then" rules , on the Prolog console it gives me this, eg:

solve(next_stage(X,Stage,Cause), CF).
X = dharmith,
Stage = hyped_event,
Cause = occurred(e1),
CF = 0.8

But when I do the query on java it doesnt have any solutions or show any...


Right now the code is like this:

public void testQuery(){

System.out.println("1");
Compound next_st = new Compound (
"next_st", new Term[] {
new Variable("X"),
new Variable("St"),
new Variable("Cause")
});
System.out.println("2");

Query q1 =
new Query(
new Compound("solve", new Term[]
{next_st, new Variable("CF")}));

System.out.println("3");

if(!q1.hasMoreSolutions()){
System.out.println("4");
}
else{

while (q1.hasMoreSolutions() ){

System.out.println("5");

solution = q1.nextSolution();

System.out.println("6");

System.out.println( "X = " + solution.get("X"));

System.out.println("7");
}
}

System.out.println("8");
}

In the console only the numbers outside of if else appears (1,2,3,8) ...
Am I missing something in the query construction??

Thanks for the help!

Best Regards.

Sujit Pal said...

You are welcome, although its been quite a while since I did this, and the Prolog suggestion did not fly with the other project members, so I haven't pursued it further since then. From the code, it appears that perhaps q1.hasMoreSolutions() has side effects (of traversing over the solution value). Consider this snippet:

..System.out.println("3");
..if (! q1.hasMoreSolutions()){
....System.out.println("4");
..}.else {
....while (q1.hasMoreSolutions() ){
......System.out.println("5");

we know it skips the if and gets into the else, so q1.hasMoreSolutions() must be true, yet it never enters the while loop, based on the trace being (1,2,3,8). So the first call actually moves the pointer to the next solution, and when it gets to the while its empty. Not sure if the Java interface has active maintainers if it does, may make sense to point this out on their mailing list.

Dharmith said...

I did a new program to test things out.


person(ivo) ? 0.1.
person(hugo) ? 0.2.
person(tiago) ? 0.3.



bro(hugo,ivo) ? 0.6.
bro(tiago, ivo) ? 0.8.

if ( person(A) and person(B) and person(C) and
bro(A,B) and bro(C,B) )
then irmaos(A,C) ? 0.9.


......

And as result I tried a new query:

public void testQuery(){

Variable P = new Variable("P");
Variable CF = new Variable("CF");
Query q1 =
new Query( new Compound(
"fact", new Term[] { P, CF }));

while (q1.hasMoreSolutions()) {




if(!((q1.nextSolution().get("P")) != null) ){

System.out.println("P = " + q1.nextSolution().get("P"));
}else { System.out.println("P is Null!"); }
System.out.println("CF = " + q1.nextSolution().get("CF"));


}


And with that I managed to get the CF values, but it doesn't print the predicates eg - person(ivo).
I have been debugging and with that I managed to see that it contains the predicate on the value of the hashtable, but when it tries to convert to a string or pass to the variable to print it out, it occurs an exception when invoking the method to do so and as a result it fails to pass it to the variable. So the variable P returns null.
Do I need a specific or do I need to do some kind of method so it passes the predicates to the variable P ? or am I doing something wrong?

Thanks again for the previous feedback as well, really apreciated.

Sujit Pal said...

Sorry about the delay in responding, meant to reply but got busy and completely forgot about it... I am guessing that perhaps toSting() method is throwing the exception? Although this may be best answered by the maintainers of the JPL project, I think perhaps the stack trace for the exception would be your clue to see what is going wrong (for example if its a null pointer exception you can guess at the variable it is choking on from the trace, and maybe ensure that the variable in question is not null in your client code).