// OO jDREW - An Object Oriented extension of the Java Deductive Reasoning Engine for the Web // Copyright (C) 2005 Marcel Ball // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA package org.ruleml.oojdrew.BottomUp; import java.util.ArrayList; import java.util.Collection; import java.util.Enumeration; import java.util.Hashtable; import java.util.Iterator; import java.util.Vector; import org.ruleml.oojdrew.SyntaxFormat; import org.ruleml.oojdrew.Reasoner; import org.ruleml.oojdrew.BottomUp.Builtins.AssertBuiltin; import org.ruleml.oojdrew.BottomUp.Builtins.BUBuiltin; import org.ruleml.oojdrew.Builtins.AbsBuiltin; import org.ruleml.oojdrew.Builtins.AddBuiltin; import org.ruleml.oojdrew.Builtins.Builtin; import org.ruleml.oojdrew.Builtins.CeilingBuiltin; import org.ruleml.oojdrew.Builtins.ContainsBuiltin; import org.ruleml.oojdrew.Builtins.ContainsIgnoreCaseBuiltin; import org.ruleml.oojdrew.Builtins.CosBuiltin; import org.ruleml.oojdrew.Builtins.DateBuiltin; import org.ruleml.oojdrew.Builtins.DateTimeBuiltin; import org.ruleml.oojdrew.Builtins.DivideBuiltin; import org.ruleml.oojdrew.Builtins.EndsWithBuiltin; import org.ruleml.oojdrew.Builtins.EqualBuiltin; import org.ruleml.oojdrew.Builtins.FloorBuiltin; import org.ruleml.oojdrew.Builtins.GreaterThanBuiltin; import org.ruleml.oojdrew.Builtins.GreaterThanOrEqualBuiltin; import org.ruleml.oojdrew.Builtins.IntegerDivideBuiltin; import org.ruleml.oojdrew.Builtins.LessThanBuiltin; import org.ruleml.oojdrew.Builtins.LessThanOrEqualBuiltin; import org.ruleml.oojdrew.Builtins.ModBuiltin; import org.ruleml.oojdrew.Builtins.MultiplyBuiltin; import org.ruleml.oojdrew.Builtins.NotEqualBuiltin; import org.ruleml.oojdrew.Builtins.PowBuiltin; import org.ruleml.oojdrew.Builtins.ReplaceBuiltin; import org.ruleml.oojdrew.Builtins.RoundBuiltin; import org.ruleml.oojdrew.Builtins.SinBuiltin; import org.ruleml.oojdrew.Builtins.StartsWithBuiltin; import org.ruleml.oojdrew.Builtins.StringConcatBuiltin; import org.ruleml.oojdrew.Builtins.StringEqualIgnoreCaseBuiltin; import org.ruleml.oojdrew.Builtins.StringLengthBuiltin; import org.ruleml.oojdrew.Builtins.StringLowerCaseBuiltin; import org.ruleml.oojdrew.Builtins.StringUpperCaseBuiltin; import org.ruleml.oojdrew.Builtins.SubstringAfterBuiltin; import org.ruleml.oojdrew.Builtins.SubstringBeforeBuiltin; import org.ruleml.oojdrew.Builtins.SubstringBuiltin; import org.ruleml.oojdrew.Builtins.SubtractBuiltin; import org.ruleml.oojdrew.Builtins.TanBuiltin; import org.ruleml.oojdrew.Builtins.TimeBuiltin; import org.ruleml.oojdrew.Builtins.UnaryMinusBuiltin; import org.ruleml.oojdrew.Builtins.UnaryPlusBuiltin; import org.ruleml.oojdrew.parsing.RuleMLFormat; import org.ruleml.oojdrew.util.DefiniteClause; import org.ruleml.oojdrew.util.Term; import org.ruleml.oojdrew.util.Util; import ptolemy.graph.DirectedAcyclicGraph; import ptolemy.graph.Edge; import ptolemy.graph.Node; /** * This class implements the forward reasoner (bottom-up) modules of OO jDREW; * The unifier for this module is implemented by the jdrew.oo.bu.Unifier class * and a subsumption checking system is implemented by the * jdrew.oo.bu.Subsumption Class. * * A forward reasoner works by processing "new" facts; As each new fact is * processed unification with all previously existing rules is attempted; if * the unification is successful one of two things happens: if the resolvent is * a fact then it is added to the end of the new facts list; if the resolvent is * a rule then it is processed (attempting unification with all processed facts) * and is then added to the list of rules. * * <p> * Title: OO jDREW * </p> * * <p> * Description: Reasoning Engine for the Semantic Web - Supporting OO RuleML * 0.88 * </p> * * <p> * Copyright: Copyright (c) 2005 * </p> * * @author Marcel A. Ball * @version 0.89 */ public class ForwardReasoner implements Reasoner { /** * This Hashtable stores all known facts that have already been processed. * The facts are indexed by the predicate name of the head (only) atom of * the facts. * * For each predicate that has been used there is a key->value pair in the * hash table; where the key is the integer code for the predicate symbol * (in the form of an Integer object) and the value is a Vector containing * the DefiniteClause objects for all processed facts with that predicate * symbol. */ private Hashtable oldFacts; /** * This Hashtable stores all known rules in the knowledge base (both those * provided and those that have been derived). The rules are indexed by the * predicate name of the first atom in the body of the rule. * * For each predicate that has been used there is a key->value pair in the * hash table; where the key is the integer code for the predicate symbol * (in the form of an Integer object) and the value is a Vector containing * the DefiniteClause objects for all processed facts with that predicate * symbol. */ private Hashtable rules; /** * This Vector contains all new facts that have not yet been processed. */ private Vector newFacts; /** * This Hashtable contains references to all registered built-ins (the * default built-ins are registered with the engine at the time of object * creation). * * The built-ins are stored as a key->value pair in the hash table; where * the integer code for the built-in predicate name (as an Integer object) * is the key and the object that implements the built-in (this will be * either a sub-class of BUBuiltin or a Class that implements the * jdrew.oo.builtins.Builtin interface and is wrapped in a BUBuiltin by the * system) is the value. * * User created built-ins can be registered with calls to * registerBuiltin(jdrew.oo.builtins.Builtin) [in the case of a generic * built-in] or registerBuiltin(jdrew.oo.bu.builtins.BUBuiltin) [in the case * of a BU specific built-in that requires access to the engine data * structures]. */ private Hashtable builtins; /** * This number is set by the user(Set to zero to ignore it all together). To * determine how many times they want to iterate over all the clauses, this * will prevent infinite loops and will display to the user what has already * been derived so far. */ private int loopCounter; // Logger logger = Logger.getLogger(ForwardReasoner.class); /** * This vector will contain all nodes in the precedence graph. Its needed to * see when we add a new node if the node already exists or not. */ private Vector nodes = new Vector(); /** * This vector will contain all the edges in the precedence graph. We need * the edges so we can test which edges are in cycles later on. */ private Vector edges = new Vector(); /** * This vector will contain a message for each edge that is negative in a * cycle. This is used to report to the user why stratification failed. */ private Vector message = new Vector(); /** * This boolean refers to negative as being true */ private static final int negative = -1; /** * This boolean refers to positive as being false */ private static final int positive = 1; /** * This Directed Graph contains the rules based on a directed graph */ private DirectedAcyclicGraph dg = new DirectedAcyclicGraph(); private Vector stringsPOSL = new Vector(); private Vector stringsRULEML = new Vector(); private boolean flip = false; /** * This method constructs a new ForwardReasoner object (implementation of a * bottom-up reasoning engine); creating the required buffers for knowledge * base storage (oldFacts and rules Hashtable's and newFacts Vector) and * registers the system provided built-in relations with the engine by * calling the registerBuiltins(); */ public ForwardReasoner() { rules = new Hashtable(); builtins = new Hashtable(); oldFacts = new Hashtable(); newFacts = new Vector(); registerBuiltins(); } /** * Allows user code to access the newFacts vector. Users may want to use * this in their code to allow the display of information in the knowledge * base. * * @return A reference to the newFacts Vector for this bottom-up reasoning * engine. */ public Vector getNewFacts() { return newFacts; } /** * Allows user code to access the oldFacts hash table. Users may want to use * this in their code to allow the display of information in the knowledge * base. * * @return A reference to the oldFacts Hashtable for this bottom-up * reasoning engine. */ public Hashtable getOldFacts() { return oldFacts; } /** * Allows user code to access the rules hash table. Users may want to use * this in their code to allow the display of information in the knowledge * base. * * @return A reference to the oldFacts Hashtable for this bottom-up * reasoning engine. */ public Hashtable getRules() { return rules; } /** * This method registers all of the standard built-in relations that are * included with the OO jDREW system with the created reasoning engine. */ private void registerBuiltins() { this.registerBuiltin(new AssertBuiltin(this)); this.registerBuiltin(new AbsBuiltin()); this.registerBuiltin(new AddBuiltin()); this.registerBuiltin(new CeilingBuiltin()); this.registerBuiltin(new ContainsBuiltin()); this.registerBuiltin(new ContainsIgnoreCaseBuiltin()); this.registerBuiltin(new CosBuiltin()); this.registerBuiltin(new DivideBuiltin()); this.registerBuiltin(new EndsWithBuiltin()); this.registerBuiltin(new EqualBuiltin()); this.registerBuiltin(new FloorBuiltin()); this.registerBuiltin(new GreaterThanBuiltin()); this.registerBuiltin(new GreaterThanOrEqualBuiltin()); this.registerBuiltin(new IntegerDivideBuiltin()); this.registerBuiltin(new LessThanBuiltin()); this.registerBuiltin(new LessThanOrEqualBuiltin()); this.registerBuiltin(new ModBuiltin()); this.registerBuiltin(new MultiplyBuiltin()); this.registerBuiltin(new NotEqualBuiltin()); this.registerBuiltin(new PowBuiltin()); this.registerBuiltin(new RoundBuiltin()); this.registerBuiltin(new SinBuiltin()); this.registerBuiltin(new StartsWithBuiltin()); this.registerBuiltin(new StringConcatBuiltin()); this.registerBuiltin(new StringEqualIgnoreCaseBuiltin()); this.registerBuiltin(new StringLengthBuiltin()); this.registerBuiltin(new StringLowerCaseBuiltin()); this.registerBuiltin(new StringUpperCaseBuiltin()); this.registerBuiltin(new SubstringBuiltin()); this.registerBuiltin(new SubtractBuiltin()); this.registerBuiltin(new TanBuiltin()); this.registerBuiltin(new DateBuiltin()); this.registerBuiltin(new TimeBuiltin()); this.registerBuiltin(new DateTimeBuiltin()); this.registerBuiltin(new SubstringAfterBuiltin()); this.registerBuiltin(new SubstringBeforeBuiltin()); this.registerBuiltin(new ReplaceBuiltin()); this.registerBuiltin(new UnaryPlusBuiltin()); this.registerBuiltin(new UnaryMinusBuiltin()); } /** * This method is used to register a new user created built-in with the * reasoning engine. This version of the method is for those built-ins that * implement the jdrew.oo.builtins.Builtin interface (these can be used with * both the bottom-up and the top-down engine). * * If the built-in that is being created requires access to the data * structures of the reasoning engine it should instead extend the * jdrew.oo.bu.builtins.BUBuiltin class and be registered with the * registerBuiltin(BUBuiltin) method. * * @param b * An instance of the class that implements the built-in * relation; this should implement the jdrew.oo.builtins.Builtin * interface. */ public void registerBuiltin(Builtin b) { registerBuiltin(new BUBuiltin(b)); } /** * This method is used to register a new user created built-in with the * reasoning engine. This version of the method is for those built-ins that * extend the jdrew.oo.bu.builtins.BUBuiltin class (those specific to the * bottom-up engine). * * @param b * An instance of the class that implements the built-in * relation; this should be a sub-class of the * jdrew.oo.bu.builtins.BUBuiltin class. */ public void registerBuiltin(BUBuiltin b) { Integer sym = b.getSymbol(); builtins.put(sym, b); } /** * This method will return a string that will contain the new facts and old * facts in RuleML or POSL form */ public String printClauses(SyntaxFormat syntaxFormat, RuleMLFormat rmlFormat) { StringBuilder output = new StringBuilder(); Iterator newFactsIterator = newFacts.iterator(); // needs to execute multiple times if (!flip) { flip = true; } while (newFactsIterator.hasNext()) { DefiniteClause dc = (DefiniteClause) newFactsIterator.next(); stringsPOSL.addElement(dc.toPOSLString()); stringsRULEML.addElement(dc.toRuleMLString(rmlFormat)); } if (flip) { output.append(Util.NEWLINE); output.append("%Old Facts:"); output.append(Util.NEWLINE); Iterator oldFactsIterator; if (syntaxFormat == SyntaxFormat.POSL) { oldFactsIterator = stringsPOSL.iterator(); } else { // use SyntaxFormat.RULEML oldFactsIterator = stringsRULEML.iterator(); } while (oldFactsIterator.hasNext()) { String currentFact = (String) oldFactsIterator.next(); output.append(currentFact); output.append(Util.NEWLINE); } } output.append(Util.NEWLINE); output.append("%New Facts::"); output.append(Util.NEWLINE); Enumeration oldFactsEnum = oldFacts.keys(); Iterator clauseIterator; if (syntaxFormat == SyntaxFormat.POSL) { clauseIterator = stringsRULEML.iterator(); } else { clauseIterator = stringsPOSL.iterator(); } while (oldFactsEnum.hasMoreElements()) { Integer key = (Integer) oldFactsEnum.nextElement(); Vector oldFact = (Vector) oldFacts.get(key); newFactsIterator = oldFact.iterator(); while (newFactsIterator.hasNext()) { DefiniteClause dc = (DefiniteClause) newFactsIterator.next(); String currentClause; if (syntaxFormat == SyntaxFormat.POSL) { currentClause = dc.toPOSLString(); } else { currentClause = dc.toRuleMLString(rmlFormat); } boolean print = true; while (clauseIterator.hasNext()) { String test = (String) clauseIterator.next(); if (test.equals(currentClause)) { print = false; break; } } if (print) { output.append(currentClause); output.append(Util.NEWLINE); } } } return output.toString(); } /** * This method is used to load clauses into the reasoning engine. This will * process each clause as it is loaded - placing new facts into the new * facts list and processing new rules against all exisiting processed * facts. * * @param it * An iterator containing the new clauses to be loaded; this * should only iterate over DefiniteClause objects. These * iterators can be created by calling the iterator() method of * parsers - such as RuleMLParser or POSLParser. */ public void loadClauses(Iterator it) { while (it.hasNext()) { DefiniteClause dc = (DefiniteClause) it.next(); process(dc); } } /** * This method runs the main forward reasoner; causing the engine to find * all possible conclusions from the knowledge base that was loaded into the * engine. * * Bellow is the main process of the system. * * As long as there is a new fact in the newFacts list remove the first fact * from the list. * * Check to see if this fact is subsumed by another already processed fact * (oldFacts); if it is subsumed, discard the fact and move to the next fact * in the list. * * Add the fact to the old fact table. * * Find all rules that may be possible unification matches with the newly * selected fact. Attempt unification with each possible rule; if the * unification succeeds then build the resolvent and call the * process(DefiniteClause) method passing the newly created resolvent. */ public void runForwardReasoner() { // If the user defined counter reaches this number then we // stop running the reasoner. int counter = 0; // If the user supplies 0 as the counter then it will just continue to // process normally with out a counter // But if they define a counter it will stop after that many iterations while (newFacts.size() > 0) { DefiniteClause dc = (DefiniteClause) newFacts.remove(0); // logger.debug("Processing " + dc.toPOSLString()); if (this.isSubsumed(dc)) { // If this new fact is subsumed by an old fact ignore and go to // next new fact continue; } Integer sym = dc.atoms[0].getSymbol(); // Add new fact to oldFact "list" if (oldFacts.containsKey(sym)) { Vector v = (Vector) oldFacts.get(sym); v.add(dc); } else { Vector v = new Vector(); v.add(dc); oldFacts.put(sym, v); } // end adding new fact to oldFact "List" ArrayList al = new ArrayList(); if (rules.containsKey(sym)) { // Rules with the same relation symbol - possible unifications Vector v = (Vector) rules.get(sym); Iterator it = v.iterator(); while (it.hasNext()) { // go though all possible unifications, and try to unify DefiniteClause rule = (DefiniteClause) it.next(); // logger.debug("Unifying with rule " + rule.toPOSLString()); Unifier unifier = new Unifier(dc, rule); if (unifier.unifies()) { // new fact unifies with a rule - process the resolvent DefiniteClause r = unifier.resolvent(); // logger.debug("Unified - resolvent: " + r.toPOSLString()); al.add(r); } } it = al.iterator(); while (it.hasNext()) { process((DefiniteClause) it.next()); } } // incrementing the iteration counter counter++; if (counter == loopCounter) { break; } } } /** * This method is used to determine if a newly selected fact is subsumed by * another fact that has already been processed by the system. * * @param fact * This is the newly selected fact (DefiniteClause) to perform * the subsumption check on. * * @return Returns true if the fact is subsumed by another processed fact; * false otherwise. If this returns true (is subsumed) then the * input fact should be discarded as there is another already * processed fact which is more general than the newly selected * fact. */ private boolean isSubsumed(DefiniteClause fact) { Subsumption s = new Subsumption(fact); Integer sym = fact.atoms[0].getSymbol(); if (oldFacts.containsKey(sym)) { Vector v = (Vector) oldFacts.get(sym); Iterator it = v.iterator(); while (it.hasNext()) { DefiniteClause dc = (DefiniteClause) it.next(); if (s.subsumedBy(dc)) { // logger.info(fact.toPOSLString() + " is subsumed by " + // dc.toPOSLString()); return true; } } } return false; } /** * This method is used to an iterator over all clauses that may unify with * the specified atom in the given clause. * * The first step in the process is to retrieve the atom that is to be * considered for unification and access the predicate symbol. * * Once the predicate symbol is retrieved the system checks to see if there * is a built-in relation registered for that predicate symbol; if there is * the the clause and atom index are passed to the built-in implementation * for it to generate the appropriate response. * * If there is no built-in registered for that predicate the system will * retrieve any processed facts with that predicate symbol; these will be * returned (as an iterator) to check if they unify with the clause. * * @param dc * The clause to find possible unifying facts for. * * @param term * The index to the atom to search on (0 is the head, 1 to n-1 * are the atoms of the body); typically this is always a 1. * * @return An iterator over all clauses that may unify with the specified * atom of the passed clause. */ private Iterator getUnifiableIterator(DefiniteClause dc, int term) { Term t = dc.atoms[term]; Integer sym = t.getSymbol(); if (builtins.containsKey(sym)) { BUBuiltin b = (BUBuiltin) builtins.get(sym); Vector v = b.buildResult(dc, term); return v.iterator(); } else if (oldFacts.containsKey(sym)) { Vector ofs = (Vector) oldFacts.get(sym); return ofs.iterator(); } else { // Not a built-in and no facts to consider for unification Vector v = new Vector(); return v.iterator(); } } /** * This method is used to process new clauses - either when loading them or * when a new rule is generated in a resolution step. * * If the new clause is a fact it is simply added to the new facts list. * * If the new clause is a rule then it is processed against all previously * used facts (oldFacts). This processing is done in two stages: first all * oldFacts that can possibly unify with the new rule are selected from the * oldFacts table; then unification is attempted with each of those facts * and if successful the resolvent is added to the newResults list. Once all * of the old facts have been tried then the system will iterate through the * newResults list and process each of the new resolvents by calling the * process(DefiniteClause) method recursively. * * @param dc * This should be the new clause to process; this can either be a * clause as it is loaded; or a newly generated resolvent in the * process(DefiniteClause) method or the runForwardReasoner() * method. */ private void process(DefiniteClause dc) { if (dc.atoms.length == 1) { // check to see at this point if dc still contains data newFacts.add(dc); } else { // logger.debug("Processing " + dc.toPOSLString()); Vector newResults = new Vector(); Integer sym = dc.atoms[1].getSymbol(); Iterator ofsit = getUnifiableIterator(dc, 1); while (ofsit.hasNext()) { DefiniteClause oldfact = (DefiniteClause) ofsit.next(); Unifier u = new Unifier(oldfact, dc); // logger.debug("Unifying new rule " + dc.toPOSLString() + // " with old fact " + oldfact.toPOSLString()); if (u.unifies()) { DefiniteClause nr = u.resolvent(); newResults.add(nr); // logger.debug("Unified - resolvent: " + // nr.toPOSLString()); } } Iterator nrit = newResults.iterator(); while (nrit.hasNext()) { DefiniteClause dc2 = (DefiniteClause) nrit.next(); process(dc2); } if (rules.containsKey(sym)) { Vector v = (Vector) rules.get(sym); v.add(dc); } else { Vector v = new Vector(); v.add(dc); rules.put(sym, v); } } } /** * This method builds a precedence graph based on the given rules. * * For each rule we must find the head of the rule and then check if it * exists or not. If it exists we use that node as the sink for each body * term. If it doesn't exists we make a new node object with the term object * and it is used for the sink of each body term. We test if a term exists * or not by unifying the head of the rule with all the other terms that we * seen already. If we create a node we have to store it in the node vector, * that is used to compare if a node exists or not. * * Then for each body clause of a rule we check if the term already exists * or not. If it doesn't we create a new node with the term object for the * source. If it does exist we just use that node as the source. If we * create a node we have to store it in the node vector, that is used to * compare if a node exists or not. * * After we get a source and a sink node we can then create a edge. The * edges are what define the graph. For every head of a rule and each body * clause there will be an edge created. * * See the source for a more detailed comment. */ public void buildPrecedenceGraph() { Enumeration e = rules.elements(); // loop through each rule while (e.hasMoreElements()) { // we are getting the rule from the iterator Vector rule = (Vector) e.nextElement(); Iterator it = rule.iterator(); // we are now going through each term in the rule while (it.hasNext()) { DefiniteClause dc = (DefiniteClause) it.next(); Term head = dc.atoms[0]; Node sink = null; // this will be the sink for each edge in this // rule boolean headExists = false; // check to see if the head already // exists // check to see if the head exist Iterator nodeIterator = nodes.iterator(); // looping through all previous terms while (nodeIterator.hasNext()) { Node n1 = (Node) nodeIterator.next(); Term compare = (Term) n1.getWeight(); // if the head already exists want to use that node as // the sink // need to make a unifier to test to see if 2 terms are // equal Unifier u = new Unifier(); if (u.unify(head, compare)) { sink = n1; headExists = true; break; } } // making a new node since it didnt exist if (!headExists) { sink = new Node(head); dg.addNode(sink);// adding the new node to the graph nodes.addElement(sink);// adding the new node to the vector } // going through each body clause for (int j = 1; j < dc.atoms.length; j++) { Term body = dc.atoms[j]; boolean hasNegEdge = false; boolean bodyExists = false; // if it does the edges will be made differntly boolean containsANestedNaf = false; Node source = null; // if the atom is a naf atom // System.out.println("body symbol: " + // body.getSymbolString()); if (body.getSymbolString().equals("naf")) { // System.out.println("negative edge found"); hasNegEdge = true; // removing the naf part of the atom Term[] nafAtoms = dc.atoms[j].getSubTerms(); body = nafAtoms[0]; // checking to see if there was a nested naf if (nafAtoms.length != 1) { containsANestedNaf = true; break;// the break prevents crashes // May not need to deal with this at all // we can process the for loop here // for multiple nested nafs } } // only execute this code if there is no nested naf if (!containsANestedNaf) { Iterator nodeIterator2 = nodes.iterator(); while (nodeIterator2.hasNext()) { Node n2 = (Node) nodeIterator2.next(); Term compare2 = (Term) n2.getWeight(); Unifier u2 = new Unifier(); if (u2.unify(body, compare2)) { source = n2; bodyExists = true; break; } } if (!bodyExists) { source = new Node(body); dg.addNode(source); nodes.addElement(source); } } // creating the edge with the source, sink and if it // is negative clause or not Edge e1 = null; Object b = -1; Object c = 1; if (hasNegEdge) { e1 = new Edge(source, sink, b); } if (!hasNegEdge) { e1 = new Edge(source, sink, c); } dg.addEdge(e1); edges.addElement(e1); }// each body clause }// each term }// each rule // ****JUST USED for testing purposes to print out the graph***** // System.out.println("Graph built The edges are: \n"); Iterator test = edges.iterator(); while (test.hasNext()) { Edge edgeTest = (Edge) test.next(); // System.out.println("Edge"); Node n1 = (Node) edgeTest.source(); Node n2 = (Node) edgeTest.sink(); Term t1 = (Term) n1.getWeight(); Term t2 = (Term) n2.getWeight(); // System.out.println("Source: " + t1.toPOSLString(true)); // System.out.println("Sink: " + t2.toPOSLString(true)); // System.out.println("Is the edge negative: " + // (Integer)edgeTest.getWeight() + "\n"); } // ****Just used for testing purposes to print out the graph***** } /** * This method is used to see if there is a negative edge in a cycle in the * precedence graph. What it does is check every edge in the precedence graph * and test if it is within a cycle and if it is then we test if the edge is * negative. If we find a negative edge in a cycle its all we need to know * that a knowledge base is not stratifiable. * * We loop through each edge and check if its source and sink are nodes in * cycles, which ptolemy can detect. So we loop through all possible * combinations of cycle nodes and see if the edge exists or not * * It also populates a string vector containing the reasons why * stratification fails. * * @return True if a negative edge exists, false otherwise */ public boolean detectNegativeCycle() { boolean neg = false; // getting the nodes in cycles Collection col = dg.cycleNodeCollection(); Vector cycleNodes = new Vector(); cycleNodes.addAll(col); // System.out.println("Number of nodes in cycles: " + // cycleNodes.size()); // System.out.println("The Nodes are \n"); // if there is are no cycles then we know the knowledge base is // stratifiable // if(cycleNodes.size() == 0){ // return false; // } // ****TEST**** for (int p = 0; p < cycleNodes.size(); p++) { Node tNode = (Node) cycleNodes.elementAt(p); Term tTerm = (Term) tNode.getWeight(); // System.out.println(tTerm.toPOSLString(true)); } // System.out.println(); // ****TEST**** // must iterator through the edges to see if a cycle // contains a negative edge Iterator edgeIterator = edges.iterator(); // going through each edge in the graph to see if its in a cycle // if it is then we check if its negative while (edgeIterator.hasNext()) { Edge edge = (Edge) edgeIterator.next(); // if the source and the sink are in the cycleNodes // then we know the edge is in the cylce Term source = (Term) ((Node) edge.source()).getWeight(); Term sink = (Term) ((Node) edge.sink()).getWeight(); int numberOfNodes = cycleNodes.size(); for (int i = 0; i < numberOfNodes; i++) { Node n1 = (Node) cycleNodes.elementAt(i); Term t1 = (Term) n1.getWeight(); for (int j = 0; j < numberOfNodes; j++) { Node n2 = (Node) cycleNodes.elementAt(j); Term t2 = (Term) n2.getWeight(); Unifier u = new Unifier(); if (u.unify(source, t1) && (u.unify(sink, t2))) { String ugly = "" + edge.getWeight(); int b = Integer.parseInt(ugly); if (b == negative) { // System.out.println("Negative edge found in cycle"); Node n11 = (Node) edge.source(); Node n22 = (Node) edge.sink(); Term t11 = (Term) n11.getWeight(); Term t22 = (Term) n22.getWeight(); // System.out.println("Source: " + // t11.toPOSLString(true)); // System.out.println("Sink: " + // t22.toPOSLString(true)); // System.out.println("Is the edge negative: " + // (Integer)edge.getWeight() + "\n"); // adding the reasons why stratification fail to a // vector String msg = " The rule with a head atom '" + t22.toPOSLString(true) + "'\n cannot have a Naf in a body with an atom containing relation '" + t11.getSymbolString() + "'"; message.addElement(msg); // return true; neg = true; } } } } } return neg; } /** * This method is used to see if a Knowledge base is stratifiable or not. It * first checks if the rules contain a naf or not. If no rule contains a naf * then its stratifiable. If there is a naf we have to create a precedence * graph and test if there is a negative cycle or not. * * @return True if the knowledge base is stratifiable, otherwise false */ public boolean isStratifiable() { // checks to see if the rules contain a naf if (!this.rulesContainsNaf()) { return true; } // build the precedent graph this.buildPrecedenceGraph(); // if we detect a negative cycle that means that its not stratifiable // if we do not detect a negative cycle that means it is stratifiable return !this.detectNegativeCycle(); } /** * This method is used to see if the body of any rule contains a naf to see * whether or not we need to check for stratification. It goes through each * rule to see if a term contains a naf or not. If no rules contain a naf * then we know its stratifiable and there is no point in building a * precedence graph and checking for negative cycles. * * @return boolean - true if a rule contains naf, false otherwise */ public boolean rulesContainsNaf() { Enumeration ruleElements = rules.elements(); while (ruleElements.hasMoreElements()) { Vector rule = (Vector) ruleElements.nextElement(); Iterator ruleIterator = rule.iterator(); while (ruleIterator.hasNext()) { DefiniteClause dc = (DefiniteClause) ruleIterator.next(); for (int j = 0; j < dc.atoms.length; j++) { Term t1 = dc.atoms[j]; if (t1.getSymbolString().equals("naf")) { return true; } } } } return false; } /** * This method is used to set the number of times when the forward reasoner * should stop running. * * @param String * The number of times a rule should loop * */ public void setLoopCounter(String number) { int num = Integer.parseInt(number); setLoopCounter(num); } /** * This method is used to set the number of times when the forward reasoner * should stop running. * * @param number * The number of times a rule should loop * */ public void setLoopCounter(int number) { loopCounter = number; } /** * This method is used to get the number of times when the forward reasoner * should stop running. * * @return The number of times a rule should loop * */ public int getLoopCounter() { return loopCounter; } /** * This method returns a vector of strings containing information about * stratification. * * @return A vector of strings containing why stratification fails * */ public Vector getMessage() { return message; } /** * Clear previously added reasoning results */ public void clearClauses() { oldFacts.clear(); newFacts.clear(); rules.clear(); } }