// 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.Vector; import org.ruleml.oojdrew.util.DefiniteClause; import org.ruleml.oojdrew.util.EngineException; import org.ruleml.oojdrew.util.SymbolTable; import org.ruleml.oojdrew.util.Term; import org.ruleml.oojdrew.util.Types; /** * This class is used for checking if one (newly selected) fact is subsumed by * another fact that has already been processed. * * Subsumption checking is a two step process; first the newly selected fact is * ground (all variables are bound to newly created constants), once the fact * has been made ground then subsumption is checked by attempting to unify the * ground atom with other facts; if unification succeedes then the fact it was * unified wtih subsumes the fact that was made ground. * * <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 Subsumption { /** * This variable stores references to the two clauses (facts) that are * being considered; the fact stored at index 0 is the one to be made * ground and checked for subsumption; the fact that will be stored at * index 1 is the one to check against. */ private DefiniteClause[] clauses = new DefiniteClause[2]; /** * This variable stores a copy of the atoms of the two clauses; the same * indexing convention is used as for DefiniteClause[] clauses. */ private Term[] atoms = new Term[2]; /** * This variable is used to temporarly store variable bindings; the same * indexing convention is used as for DefiniteClause[] clauses. */ private Term[][] vars = new Term[2][]; private static final int LEFT = 0; private static final int RIGHT = 1; /** * This is used to generate new constants for grounding the facts; it is * initially set to the first unused symbol code. */ private int sid; /** * This creates a subsumption checker; First it initializes all data * structures based upon the fact that is passed (this is the one to be * checked if it is subsumed by another fact); then it will call the * ground(Term) method to ground the fact. * * @param base DefiniteClause The fact to check for subsumption; this is * generally a newly selected fact in the runForwardReasoner method of a * ForwardReasoner object. */ public Subsumption(DefiniteClause base) { super(); clauses[LEFT] = base; atoms[LEFT] = base.atoms[0].deepCopy(LEFT); vars[LEFT] = new Term[base.variableNames.length]; sid = SymbolTable.symbols.size(); ground(atoms[LEFT]); } /** * This method is used to ground a term; If the term is an unbound variable * it will generate a new symbol to bind the variable to; if it is a * non-simple term (atom, plex, complex term) it will recursively call the * ground(Term) method on each of the parameters; to ensure that the term * is completely ground. * * @param t Term The term to ground; this is called on the head atom of the * fact to be ground for subsumption checking. */ private void ground(Term t) { if (t.symbol < 0) { if (t.getRole() == SymbolTable.IREST || t.getRole() == SymbolTable.IPREST) { vars[t.getSide()][ -(t.symbol + 1)] = new Term(SymbolTable.IPLEX, SymbolTable.INOROLE, t.type, new Vector()); } if (vars[t.getSide()][ -(t.symbol + 1)] == null && t.getRole() != SymbolTable.IREST && t.getRole() != SymbolTable.IPREST) { vars[t.getSide()][ -(t.symbol + 1)] = new Term(sid++, SymbolTable.INOROLE, t.type); } } if (t.isExpr()) { for (int i = 0; i < t.subTerms.length; i++) { ground(t.subTerms[i]); } } } /** * This method is used to dereference a variable; i.e. to find any variable * bindings that have already been made. * * @param term Term The term to dereference. * * @return Term The dereferenced term; if the initial term is not a variable * then this is that initial term; if the inital term is a variable, then * this will either be the original variable; or the term that that * variable was bound to if it has been bound. */ private Term deref(Term term) { if (term.getSymbol() > 0) { return term; } else { int side = term.getSide(); int sym = -(term.getSymbol() + 1); Term termd = vars[side][sym]; if (termd == null) { return term; } else { return deref(termd); } } } /** * This method is called to check to see if the passed fact subsumes the * fact that was used in the consturctor of the Subsumption object. * * This method can be called multiple times to check the newly selcted fact * for subsumption by more than one old processed fact. * * @param oldfact DefiniteClause The old fact to test for subsumption with. * * @return boolean returns true if the passed fact subsums the fact given to * the constructor; false otherwise. */ public boolean subsumedBy(DefiniteClause oldfact) { clauses[RIGHT] = oldfact; atoms[RIGHT] = oldfact.atoms[0].deepCopy(RIGHT); vars[RIGHT] = new Term[oldfact.variableNames.length]; boolean subsumes = unify(atoms[LEFT], atoms[RIGHT]); clauses[RIGHT] = null; atoms[RIGHT] = null; vars[RIGHT] = null; return subsumes; } /** * This method is used to check if two terms unify with each other; and to * perform any variable bindings that are necessary to make the terms unfiy. * * For more details see the inline comments in the source of this method * and/or look at the description of the unify(Term, Term) method of the * jdrew.oo.bu.Unifier class. * * @param term1 Term One of the terms to attempt to unify. * * @param term2 Term The second term to attempt to unify. * * @return boolean Returns true if the two terms unify; false otherwise. */ private boolean unify(Term term1, Term term2) { Term t1 = deref(term1); Term t2 = deref(term2); if (t1.isExpr() && t2.isExpr()) { if (t1.getSymbol() == t2.getSymbol() && Types.isSuperClass(t2.getType(), t1.getType())) { Vector t1restterms = new Vector(); Vector t2restterms = new Vector(); Vector t1prestterms = new Vector(); Vector t2prestterms = new Vector(); boolean t1rest = (t1.rest > 0); boolean t2rest = (t2.rest > 0); boolean t1prest = (t1.prest > 0); boolean t2prest = (t2.prest > 0); if (t1rest && !t2rest) { return false; // t1 has slotted rest term, t2 must have slotted rest term to be more general } if (t1prest && !t2prest) { return false; // t2 has positional rest term, t2 must have slotted rest term to be more general } int i = 0; int j = 0; while (i < t1.subTerms.length && j < t2.subTerms.length) { if (t1.subTerms[i].role == SymbolTable.IREST || t1.subTerms[i].role == SymbolTable.IPREST) { // This is a rest term in t1 - skip for now - this is handeled at the end of unification i++; continue; } if (t2.subTerms[j].role == SymbolTable.IREST || t2.subTerms[j].role == SymbolTable.IPREST) { // This is a rest term in t2 - skip for now - this is handeled at the end of unification j++; continue; } if (t1.subTerms[i].role < t2.subTerms[j].role) { // role(t1[i]) is before role(t2[j]) - go to next i or fail if (t1.subTerms[i].role == SymbolTable.INOROLE && t2prest) { // add to positional rest term list for t2 t2prestterms.add(t1.subTerms[i]); i++; } else if (t1.subTerms[i].role > SymbolTable.INOROLE && t2rest) { // add to slotted rest term list for t2 t2restterms.add(t1.subTerms[i]); i++; } else { return false; // no appropriate rest term in t2 - unification fails } } else if (t1.subTerms[i].role == t2.subTerms[j].role) { // role(t1[i]) is same as role(t2[j]) - unify t1[i] and t2[j] if (!unify(t1.subTerms[i], t2.subTerms[j])) { return false; } i++; j++; } else if (t1.subTerms[i].role > t2.subTerms[j].role) { // role(t1[i]) is after role(t2[j]) - go to next j if (t2.subTerms[j].role == SymbolTable.INOROLE && t1prest) { // add to positional rest term list for t1 t1prestterms.add(t2.subTerms[j]); j++; } else if (t2.subTerms[j].role > SymbolTable.INOROLE && t1rest) { // add to slotted rest term list for t1 t1restterms.add(t2.subTerms[j]); j++; } else { return false; // no appropriate rest term in t1 - unification fails } } } while (i < t1.subTerms.length) { if (t1.subTerms[i].role == SymbolTable.IREST || t1.subTerms[i].role == SymbolTable.IPREST) { // This is a rest term in t1 - skip for now - this is handeled at the end of unification i++; } else if (t1.subTerms[i].role == SymbolTable.INOROLE && t2prest) { t2prestterms.add(t1.subTerms[i]); i++; } else if (t1.subTerms[i].role > SymbolTable.INOROLE && t2rest) { t2restterms.add(t1.subTerms[i]); i++; } else { return false; // no appropriate rest term in t2 - unification fails } } while (j < t2.subTerms.length) { if (t2.subTerms[j].role == SymbolTable.IREST || t2.subTerms[j].role == SymbolTable.IPREST) { // This is a rest term in t1 - skip for now - this is handeled at the end of unification j++; } else if (t2.subTerms[j].role == SymbolTable.INOROLE && t1prest) { t1prestterms.add(t2.subTerms[j]); j++; } else if (t2.subTerms[j].role > SymbolTable.INOROLE && t1rest) { t1restterms.add(t2.subTerms[j]); j++; } else { return false; // no appropriate rest term in t2 - unification fails } } // Now do unification of rest term with rest term list that was created Term t1prestterm = new Term(SymbolTable.IPLEX, SymbolTable.INOROLE, Types.IOBJECT, t1prestterms); Term t1restterm = new Term(SymbolTable.IPLEX, SymbolTable.INOROLE, Types.IOBJECT, t1restterms); Term t2prestterm = new Term(SymbolTable.IPLEX, SymbolTable.INOROLE, Types.IOBJECT, t2prestterms); Term t2restterm = new Term(SymbolTable.IPLEX, SymbolTable.INOROLE, Types.IOBJECT, t2restterms); if (t1prest) { if (!unify(t1.subTerms[t1.prest], t1prestterm)) { return false; } } else { if (t1prestterms.size() > 0) { return false; // t1 has no positional rest term, but one is required for successful unification } } if (t1rest) { if (!unify(t1.subTerms[t1.rest], t1restterm)) { return false; } } else { if (t1restterms.size() > 0) { return false; // t1 has no slotted rest term, but one is required for successful unification } } if (t2prest) { if (!unify(t2.subTerms[t2.prest], t2prestterm)) { return false; } } else { if (t2prestterms.size() > 0) { return false; // t2 has no positional rest term, but one is required for successful unification } } if (t2rest) { if (!unify(t2.subTerms[t2.rest], t2restterm)) { return false; } } else { if (t2restterms.size() > 0) { return false; // t2 has no slotted rest term, but one is required for successful unification } } return true; // All subterms unified correctly, symbols and types are compatible, therefore t1 and t2 unify } else { return false; // Symbols were different or types were not compatible (! (type(t2) >= type(t1))) } } else if (t1.isExpr() && !t2.isExpr()) { if (t2.getSymbol() < 0 && Types.isSuperClass(t2.getType(), t1.getType())) { // t2 is a variable, t1 is a complex term (Cterm, Plex, Atom) int side = t2.getSide(); int sym = -(t2.getSymbol() + 1); this.vars[side][sym] = t1; return true; } else { // t2 is an individual constant (Ind) and t2 is a complex term (Cterm, Plex, Atom) return false; } } else if (!t1.isExpr() && t2.isExpr()) { if (t1.getSymbol() < 0 && Types.isSuperClass(t1.getType(), t2.getType())) { // t1 is a variable, t2 is a complex term (Cterm, Plex, Atom) int side = t1.getSide(); int sym = -(t1.getSymbol() + 1); this.vars[side][sym] = t2; return true; } else { // t1 is an individual constant (Ind) and t2 is a complex term (Cterm, Plex, Atom) return false; } } else if (!t1.isExpr() && !t2.isExpr()) { if (t1.getSymbol() >= 0 && t2.getSymbol() >= 0) { // Both t1 and t2 are individual constants (Ind) if (t1.getSymbol() == t2.getSymbol() && Types.isSuperClass(t2.getType(), t1.getType())) { //Both symbols are the same, and the types are compatible (type(t2) >= type(t1)) return true; } else { return false; } } else if (t1.getSymbol() < 0 && t2.getSymbol() >= 0) { // t1 is a variable (Var) and t2 is an individual constant (Ind) if (Types.isSuperClass(t1.getType(), t2.getType())) { int sym = -(t1.getSymbol() + 1); int side = t1.getSide(); this.vars[side][sym] = t2; return true; } else { return false; // Types are not compatible (! (type(t1) >= type(t2)) ) } } else if (t1.getSymbol() >= 0 && t2.getSymbol() < 0) { // t1 is an individual constant (Ind) and t2 is a variable (Var) if (Types.isSuperClass(t2.getType(), t1.getType())) { int sym = -(t2.getSymbol() + 1); int side = t2.getSide(); this.vars[side][sym] = t1; return true; } else { return false; // Types are not compatible (! (type(t2) >= type(t2)) ) } } else if (t1.getSymbol() < 0 && t2.getSymbol() < 0) { // Both t1 and t2 are variables (Var) int type = Types.greatestLowerBound(t1.getType(), t2.getType()); int side = t2.getSide(); int sym = -(t2.getSymbol() + 1); Term t1dc = t1.deepCopy(); t1dc.setType(type); this.vars[side][sym] = t1dc; return true; } else { throw new EngineException("Terms are not valid."); } // This should never happen - one of the previous cases will always occur } else { throw new EngineException("Terms are not valid."); } // This should never happen - one of the previous cases will always occur } }