From 20b04e3e8e006c50ace4d3f4574f1a9ecfb7f973 Mon Sep 17 00:00:00 2001 From: stellamit Date: Mon, 4 Jun 2012 21:47:23 +0000 Subject: [PATCH] NIHVIVO-3293 add inverse property reasoning to SimpleReasoner --- .../vitro/webapp/reasoner/SimpleReasoner.java | 459 +++++++++++++++-- .../reasoner/SimpleReasonerPropertyTest.java | 487 ++++++++++++++++++ 2 files changed, 897 insertions(+), 49 deletions(-) create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerPropertyTest.java diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java index 6cb8e322c..d41ccb509 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasoner.java @@ -14,6 +14,7 @@ import com.hp.hpl.jena.ontology.AnnotationProperty; import com.hp.hpl.jena.ontology.OntClass; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModelSpec; +import com.hp.hpl.jena.ontology.OntProperty; import com.hp.hpl.jena.query.Query; import com.hp.hpl.jena.query.QueryExecution; import com.hp.hpl.jena.query.QueryExecutionFactory; @@ -25,6 +26,7 @@ import com.hp.hpl.jena.rdf.listeners.StatementListener; import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.ResourceFactory; @@ -32,6 +34,7 @@ import com.hp.hpl.jena.rdf.model.Statement; import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.JenaException; import com.hp.hpl.jena.shared.Lock; +import com.hp.hpl.jena.util.iterator.ExtendedIterator; import com.hp.hpl.jena.vocabulary.OWL; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; @@ -126,12 +129,13 @@ public class SimpleReasoner extends StatementListener { */ @Override public void addedStatement(Statement stmt) { - try { if (stmt.getPredicate().equals(RDF.type)) { addedABoxTypeAssertion(stmt, inferenceModel, new HashSet()); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet()); - } + } else { + addedABoxAssertion(stmt, inferenceModel); + } doPlugins(ModelUpdate.Operation.ADD,stmt); @@ -150,8 +154,9 @@ public class SimpleReasoner extends StatementListener { public void removedStatement(Statement stmt) { try { - if (!isInterestedInRemovedStatement(stmt)) { return; } - + // if (!isInterestedInRemovedStatement(stmt)) { return; } + // interested in all of them now that we are doing inverse + // property reasoning handleRemovedStatement(stmt); } catch (Exception e) { @@ -160,7 +165,6 @@ public class SimpleReasoner extends StatementListener { } } - /* * Synchronized part of removedStatement. Interacts * with DeltaComputer. @@ -175,7 +179,9 @@ public class SimpleReasoner extends StatementListener { if (stmt.getPredicate().equals(RDF.type)) { removedABoxTypeAssertion(stmt, inferenceModel); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet()); - } + } else { + removedABoxAssertion(stmt, inferenceModel); + } doPlugins(ModelUpdate.Operation.RETRACT,stmt); } } @@ -184,29 +190,32 @@ public class SimpleReasoner extends StatementListener { * Performs incremental ABox reasoning based * on changes to the class hierarchy. * - * Handles rdfs:subclassOf, owl:equivalentClass, + * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf */ public void addedTBoxStatement(Statement stmt) { + try { + if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) { + return; + } - try { - log.debug("added TBox assertion = " + stmt.toString()); + log.debug("added TBox assertion = " + stmt.toString()); - if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) { + if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { + log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); + return; + } + + if ( stmt.getSubject().getURI() == null ) { + log.warn("The subject of this assertion has a null URI: " + stmtString(stmt)); + return; + } + + if (stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass)) { // ignore anonymous classes if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) { return; } - if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { - log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); - return; - } - - if ( stmt.getSubject().getURI() == null ) { - log.warn("The subject of this assertion has a null URI: " + stmtString(stmt)); - return; - } - OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); if (subject == null) { log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); @@ -220,13 +229,27 @@ public class SimpleReasoner extends StatementListener { } if (stmt.getPredicate().equals(RDFS.subClassOf)) { - addedSubClass(subject,object,inferenceModel); + addedSubClass(subject,object,inferenceModel); } else { - // equivalent class is the same as subclass in both directions - addedSubClass(subject,object,inferenceModel); - addedSubClass(object,subject,inferenceModel); + // equivalent class is the same as subclass in both directions + addedSubClass(subject,object,inferenceModel); + addedSubClass(object,subject,inferenceModel); + } + } else { + OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI()); + if (prop1 == null) { + log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI()); + return; + } + + OntProperty prop2 = tboxModel.getOntProperty(((Resource)stmt.getObject()).getURI()); + if (prop2 == null) { + log.debug("didn't find object property in the tbox: " + ((Resource)stmt.getObject()).getURI()); + return; } - } + + addedInverseProperty(prop1, prop2, inferenceModel); + } } catch (Exception e) { // don't stop the edit if there's an exception log.error("Exception while adding inference(s): " + e.getMessage()); @@ -237,29 +260,33 @@ public class SimpleReasoner extends StatementListener { * Performs incremental ABox reasoning based * on changes to the class hierarchy. * - * Handles rdfs:subclassOf, owl:equivalentClass, + * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf */ - public void removedTBoxStatement(Statement stmt) { - + public void removedTBoxStatement(Statement stmt) { try { - log.debug("removed TBox assertion = " + stmt.toString()); + if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) { + return; + } + log.debug("removed TBox assertion = " + stmt.toString()); + + if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { + log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); + return; + } + + if ( stmt.getSubject().getURI() == null ) { + log.warn("The subject of this assertion has a null URI: " + stmtString(stmt)); + return; + } + if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) { + // ignore anonymous classes if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) { return; } - if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { - log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); - return; - } - - if ( stmt.getSubject().getURI() == null ) { - log.warn("The subject of this assertion has a null URI: " + stmtString(stmt)); - return; - } - OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); if (subject == null) { log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); @@ -279,7 +306,21 @@ public class SimpleReasoner extends StatementListener { removedSubClass(subject,object,inferenceModel); removedSubClass(object,subject,inferenceModel); } - } + } else { + OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI()); + if (prop1 == null) { + log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI()); + return; + } + + OntProperty prop2 = tboxModel.getOntProperty(((Resource)stmt.getObject()).getURI()); + if (prop2 == null) { + log.debug("didn't find object property in the tbox: " + ((Resource)stmt.getObject()).getURI()); + return; + } + + removedInverseProperty(prop1, prop2, inferenceModel); + } } catch (Exception e) { // don't stop the edit if there's an exception log.error("Exception while removing inference(s): " + e.getMessage()); @@ -376,6 +417,45 @@ public class SimpleReasoner extends StatementListener { } } + /* + * Performs incremental property-based reasoning. + * + * Materializes inferences based on the owl:inverseOf relationship. + * + * If it is added that x prop1 y, and prop2 is an inverseOf prop1 + * then add y prop2 x to the inference graph, if it is not already in + * the assertions graph. + */ + public void addedABoxAssertion(Statement stmt, Model inferenceModel) { + + List inverseProperties = getInverseProperties(stmt); + Iterator inverseIter = inverseProperties.iterator(); + + while (inverseIter.hasNext()) { + Property inverseProp = inverseIter.next(); + + Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp, stmt.getSubject()); + + aboxModel.enterCriticalSection(Lock.READ); + try { + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (inferenceModel.contains(stmt)) { + inferenceModel.remove(stmt); + } + + if (!inferenceModel.contains(infStmt) && !aboxModel.contains(infStmt) ) { + inferenceModel.add(infStmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } finally { + aboxModel.leaveCriticalSection(); + } + } + } + /* * If it is removed that B is of type A, then for each superclass of A remove * the inferred statement that B is of that type UNLESS it is otherwise entailed @@ -386,8 +466,7 @@ public class SimpleReasoner extends StatementListener { tboxModel.enterCriticalSection(Lock.READ); - try { - + try { OntClass cls = null; if ( (stmt.getObject().asResource()).getURI() != null ) { @@ -453,13 +532,50 @@ public class SimpleReasoner extends StatementListener { } } + /* + * Performs incremental property-based reasoning. + * + * Retracts inferences based on the owl:inverseOf relationship. + * + * If it is removed that x prop1 y, and prop2 is an inverseOf prop1 + * then remove y prop2 x from the inference graph, unless it is + * otherwise entailed by the assertions graph independently of + * this removed statement. + */ + public void removedABoxAssertion(Statement stmt, Model inferenceModel) { + List inverseProperties = getInverseProperties(stmt); + Iterator inverseIter = inverseProperties.iterator(); + + while (inverseIter.hasNext()) { + OntProperty inverseProp = inverseIter.next(); + + Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp, stmt.getSubject()); + + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (!entailedInverseStmt(infStmt) && inferenceModel.contains(infStmt)) { + inferenceModel.remove(infStmt); + } + + // if a statement has been removed that is otherwise entailed, + // add it to the inference graph. + if (entailedInverseStmt(stmt) && !inferenceModel.contains(stmt)) { + inferenceModel.add(stmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } + } + // Returns true if it is entailed by class subsumption that // subject is of type cls; otherwise returns false. protected boolean entailedType(Resource subject, OntClass cls) { - aboxModel.enterCriticalSection(Lock.READ); + tboxModel.enterCriticalSection(Lock.READ); + aboxModel.enterCriticalSection(Lock.READ); - try { + try { List subclasses = null; subclasses = (cls.listSubClasses(false)).toList(); subclasses.addAll((cls.listEquivalentClasses()).toList()); @@ -477,12 +593,101 @@ public class SimpleReasoner extends StatementListener { } catch (Exception e) { log.debug("exception in method entailedType: " + e.getMessage()); return false; - } finally { + } finally { aboxModel.leaveCriticalSection(); tboxModel.leaveCriticalSection(); } } + + // Returns true if the triple is entailed by inverse property + // reasoning; otherwise returns false. + protected boolean entailedInverseStmt(Statement stmt) { + ExtendedIterator iter = null; + + tboxModel.enterCriticalSection(Lock.READ); + try { + OntProperty prop = tboxModel.getOntProperty(stmt.getPredicate().asResource().getURI()); + iter = prop.listInverse(); + } finally { + tboxModel.leaveCriticalSection(); + } + + aboxModel.enterCriticalSection(Lock.READ); + try { + while (iter.hasNext()) { + Property invProp = iter.next(); + + // not reasoning on properties in the OWL, RDF or RDFS namespace + if ((invProp.getNameSpace()).equals(OWL.NS) || + (invProp.getNameSpace()).equals(RDFS.getURI()) || + (invProp.getNameSpace()).equals(RDF.getURI())) { + continue; + } + + Statement invStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), invProp, stmt.getSubject()); + if (aboxModel.contains(invStmt)) { + return true; + } + } + return false; + } finally { + aboxModel.leaveCriticalSection(); + } + } + /* + * Returns a list of properties that are inverses of the property + * in the given statement. + */ + protected List getInverseProperties(Statement stmt) { + + List inverses = new ArrayList(); + + if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) { + return inverses; + } + + tboxModel.enterCriticalSection(Lock.READ); + try { + + OntProperty prop = tboxModel.getOntProperty(stmt.getPredicate().getURI()); + + if (prop != null) { + if (!prop.isObjectProperty()) { + return inverses; + } + + if (!stmt.getObject().isResource()) { + log.warn("The predicate of this statement is an object property, but the object is not a resource."); + return inverses; + } + + // not reasoning on properties in the OWL, RDF or RDFS namespace + if ((prop.getNameSpace()).equals(OWL.NS) || + (prop.getNameSpace()).equals(RDFS.getURI()) || + (prop.getNameSpace()).equals(RDF.getURI())) { + return inverses; + } + + ExtendedIterator iter = prop.listInverse(); + + while (iter.hasNext()) { + OntProperty invProp = iter.next(); + + if ((invProp.getNameSpace()).equals(OWL.NS) || + (invProp.getNameSpace()).equals(RDFS.getURI()) || + (invProp.getNameSpace()).equals(RDF.getURI())) { + continue; + } + inverses.add(invProp); + } + } + } finally { + tboxModel.leaveCriticalSection(); + } + + return inverses; + } /* * If it is added that B is a subClass of A, then for each * individual that is typed as B, either in the ABox or in the @@ -566,6 +771,112 @@ public class SimpleReasoner extends StatementListener { } } + /* + * If it is added that P is an inverse of Q, then: + * 1. For each statement involving predicate P in + * the assertions model add the inverse statement + * to the inference model if that inverse is + * in the assertions model. + * + * 2. Repeat the same for predicate Q. + * + */ + public void addedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { + + if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { + log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); + return; + } + + Model inferences = ModelFactory.createDefaultModel(); + inferences.add(generateInverseInferences(prop1, prop2)); + inferences.add(generateInverseInferences(prop2, prop1)); + + if (inferences.isEmpty()) return; + + aboxModel.enterCriticalSection(Lock.READ); + try { + StmtIterator iter = inferences.listStatements(); + + while (iter.hasNext()) { + Statement infStmt = iter.next(); + + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (!inferenceModel.contains(infStmt) && !aboxModel.contains(infStmt)) { + inferenceModel.add(infStmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } + } finally { + aboxModel.leaveCriticalSection(); + } + } + + public Model generateInverseInferences(OntProperty prop, OntProperty inverseProp) { + Model inferences = ModelFactory.createDefaultModel(); + + aboxModel.enterCriticalSection(Lock.READ); + try { + StmtIterator iter = aboxModel.listStatements((Resource) null, prop, (RDFNode) null); + + while (iter.hasNext()) { + Statement stmt = iter.next(); + if (!stmt.getObject().isResource()) continue; + Statement infStmt = ResourceFactory.createStatement(stmt.getObject().asResource(), inverseProp, stmt.getSubject()); + inferences.add(infStmt); + } + } finally { + aboxModel.leaveCriticalSection(); + } + + return inferences; + } + + /* + * If it is removed that P is an inverse of Q, then: + * 1. For each statement involving predicate P in + * the abox assertions model remove the inverse + * statement from the inference model unless + * that statement is otherwise entailed. + * + * 2. Repeat the same for predicate Q. + */ + public void removedInverseProperty(OntProperty prop1, OntProperty prop2, Model inferenceModel) { + + if ( !prop1.isObjectProperty() || !prop2.isObjectProperty() ) { + log.warn("The subject and object of the inverseOf statement are not both object properties. No inferencing will be performed. property 1: " + prop1.getURI() + " property 2:" + prop2.getURI()); + return; + } + + Model inferences = ModelFactory.createDefaultModel(); + inferences.add(generateInverseInferences(prop1, prop2)); + inferences.add(generateInverseInferences(prop2, prop1)); + + if (inferences.isEmpty()) return; + + StmtIterator iter = inferences.listStatements(); + + while (iter.hasNext()) { + Statement infStmt = iter.next(); + + if (entailedInverseStmt(infStmt)) { + continue; + } + + inferenceModel.enterCriticalSection(Lock.WRITE); + try { + if (inferenceModel.contains(infStmt)) { + inferenceModel.remove(infStmt); + } + } finally { + inferenceModel.leaveCriticalSection(); + } + } + } + /* * Find the most specific types (classes) of an individual and * indicate them for the individual with the mostSpecificType @@ -740,13 +1051,12 @@ public class SimpleReasoner extends StatementListener { * inference models. */ protected synchronized void recomputeABox() { - HashSet unknownTypes = new HashSet(); - + // recompute the inferences inferenceRebuildModel.enterCriticalSection(Lock.WRITE); try { - log.info("Computing class-based ABox inferences."); + log.info("Computing ABox inferences."); inferenceRebuildModel.removeAll(); int numStmts = 0; @@ -784,6 +1094,57 @@ public class SimpleReasoner extends StatementListener { log.info("Still computing class-based ABox inferences..."); } + if (stopRequested) { + log.info("a stopRequested signal was received during recomputeABox. Halting Processing."); + return; + } + } + + log.info("Finished computing class-based ABox inferences"); + + Iterator invStatements = null; + tboxModel.enterCriticalSection(Lock.READ); + try { + invStatements = tboxModel.listStatements((Resource) null, OWL.inverseOf, (Resource) null); + } finally { + tboxModel.leaveCriticalSection(); + } + + while (invStatements.hasNext()) { + Statement stmt = invStatements.next(); + + try { + OntProperty prop1 = tboxModel.getOntProperty((stmt.getSubject()).getURI()); + if (prop1 == null) { + log.debug("didn't find subject property in the tbox: " + (stmt.getSubject()).getURI()); + continue; + } + + OntProperty prop2 = tboxModel.getOntProperty(((Resource)stmt.getObject()).getURI()); + if (prop2 == null) { + log.debug("didn't find object property in the tbox: " + ((Resource)stmt.getObject()).getURI()); + continue; + } + + addedInverseProperty(prop1, prop2, inferenceRebuildModel); + } catch (NullPointerException npe) { + log.error("a NullPointerException was received while recomputing the ABox inferences. Halting inference computation."); + return; + } catch (JenaException je) { + if (je.getMessage().equals("Statement models must no be null")) { + log.error("Exception while recomputing ABox inference model. Halting inference computation.", je); + return; + } + log.error("Exception while recomputing ABox inference model: ", je); + } catch (Exception e) { + log.error("Exception while recomputing ABox inference model: ", e); + } + + numStmts++; + if ((numStmts % 10000) == 0) { + log.info("Still computing property-based ABox inferences..."); + } + if (stopRequested) { log.info("a stopRequested signal was received during recomputeABox. Halting Processing."); return; @@ -798,7 +1159,7 @@ public class SimpleReasoner extends StatementListener { inferenceRebuildModel.leaveCriticalSection(); } - log.info("Finished computing class-based ABox inferences"); + log.info("Finished computing property-based ABox inferences"); // reflect the recomputed inferences into the application // inference model. diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerPropertyTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerPropertyTest.java new file mode 100644 index 000000000..0a6c06e4e --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/reasoner/SimpleReasonerPropertyTest.java @@ -0,0 +1,487 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.reasoner; + +import org.apache.log4j.Level; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mindswap.pellet.jena.PelletReasonerFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.ontology.OntModelSpec; +import com.hp.hpl.jena.ontology.OntProperty; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; +import com.hp.hpl.jena.rdf.model.Resource; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.utils.threads.VitroBackgroundThread; + +public class SimpleReasonerPropertyTest extends AbstractTestClass { + + long delay = 50; + + @Before + public void suppressErrorOutput() { + suppressSyserr(); + //Turn off log messages to console + setLoggerLevel(SimpleReasoner.class, Level.OFF); + setLoggerLevel(SimpleReasonerTBoxListener.class, Level.OFF); + } + + /* + * basic scenarios around adding abox data + * + * Create a Tbox with property P inverseOf property Q. + * Pellet will compute TBox inferences. Add a statement + * a P b, and verify that b Q a is inferred. + * Add a statement c Q d and verify that d Q c + * is inferred. + */ + @Test + public void addABoxAssertion1() { + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + + P.addInverseOf(Q); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // b Q a is inferred from a P b + aBox.add(a,P,b); + Assert.assertTrue(inf.contains(b,Q,a)); + + // d P c is inferred from c Q d. + aBox.add(c,Q,d); + Assert.assertTrue(inf.contains(d,P,c)); + } + + /* + * don't infer if it's already in the abox + */ + @Test + public void addABoxAssertion2() { + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + P.addInverseOf(Q); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and add statement b Q a + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + aBox.add(b,Q,a); + + // register SimpleReasoner + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // b Q a is inferred from a P b, but it is already in the abox + aBox.add(a,P,b); + Assert.assertFalse(inf.contains(b,Q,a)); + } + + /* + * don't infer if it's already in the abox + */ + @Test + public void addABoxAssertion3() { + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + P.addInverseOf(Q); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register SimpleReasoner + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + + // b Q a is inferred from a P b, but it is already in the abox + aBox.add(a,P,b); + aBox.add(b,Q,a); + Assert.assertFalse(inf.contains(b,Q,a)); + } + + /* + * adding abox data where the property has an inverse and + * and equivalent property. + */ + @Test + public void addABoxAssertion4() { + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty R = tBox.createOntProperty("http://test.vivo/R"); + R.setLabel("property R", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + + R.addEquivalentProperty(P); + P.addEquivalentProperty(R); + P.addInverseOf(Q); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // b Q a is inferred from a R b. + aBox.add(a,R,b); + Assert.assertTrue(inf.contains(b,Q,a)); + + // d P c is inferred from c Q d. + aBox.add(c,Q,d); + Assert.assertTrue(inf.contains(d,P,c)); + Assert.assertTrue(inf.contains(d,R,c)); + } + + /* + * basic scenarios around removing abox data + */ + @Test + public void removedABoxAssertion1() { + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + OntProperty T = tBox.createOntProperty("http://test.vivo/T"); + Q.setLabel("property T", "en-US"); + P.addInverseOf(Q); + P.addInverseOf(T); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // b Q a is inferred from a P b. + aBox.add(a,P,b); + Assert.assertTrue(inf.contains(b,Q,a)); + + // d P c is inferred from c Q d and from c T d + aBox.add(c,Q,d); + aBox.add(c,T,d); + Assert.assertTrue(inf.contains(d,P,c)); + + aBox.remove(a,P,b); + Assert.assertFalse(inf.contains(b,Q,a)); + + aBox.remove(c,Q,d); + Assert.assertTrue(inf.contains(d,P,c)); + } + + /* + * removing abox data with equivalent and inverse properties + */ + @Test + public void removedABoxAssertion2() { + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + OntProperty T = tBox.createOntProperty("http://test.vivo/T"); + Q.setLabel("property T", "en-US"); + P.addInverseOf(Q); + Q.addInverseOf(P); + P.addEquivalentProperty(T); + T.addEquivalentProperty(P); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + + // b Q a is inferred from a P b and a T b. + aBox.add(a,P,b); + aBox.add(a,T,b); + Assert.assertTrue(inf.contains(b,Q,a)); + Assert.assertFalse(inf.contains(a,P,b)); + + aBox.remove(a,P,b); + Assert.assertTrue(inf.contains(b,Q,a)); + } + + /* + * removing abox data with equivalent and inverse properties + */ + @Test + public void removedABoxAssertion3() { + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + P.addInverseOf(Q); + + // this is the model to receive inferences + Model inf = ModelFactory.createDefaultModel(); + + // create an ABox and register the SimpleReasoner listener with it + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + aBox.add(a,P,b); + + aBox.register(new SimpleReasoner(tBox, aBox, inf)); + + aBox.add(b,Q,a); + + Assert.assertFalse(inf.contains(b,Q,a)); + Assert.assertFalse(inf.contains(a,P,b)); + + aBox.remove(a,P,b); + Assert.assertTrue(inf.contains(a,P,b)); + } + + /* + * Basic scenario around adding an inverseOf assertion to the + * TBox + */ + @Test + public void addTBoxInverseAssertion1() throws InterruptedException { + + // Create TBox, ABox and Inference models and register + // the ABox reasoner listeners with the ABox and TBox + // Pellet will compute TBox inferences + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + Model inf = ModelFactory.createDefaultModel(); + + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + aBox.register(simpleReasoner); + SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); + tBox.register(simpleReasonerTBoxListener); + + // set up TBox and Abox + + OntProperty P = tBox.createOntProperty("http://test.vivo/P"); + P.setLabel("property P", "en-US"); + + OntProperty Q = tBox.createOntProperty("http://test.vivo/Q"); + Q.setLabel("property Q", "en-US"); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // abox statements + aBox.add(a,P,b); + aBox.add(c,P,d); + aBox.add(b,Q,a); + + // Assert P and Q as inverses and wait for SimpleReasoner TBox + // thread to end + + Q.addInverseOf(P); + + while (!VitroBackgroundThread.getLivingThreads().isEmpty()) { + Thread.sleep(delay); + } + + // Verify inferences + Assert.assertTrue(inf.contains(d,Q,c)); + Assert.assertFalse(inf.contains(b,Q,a)); + + simpleReasonerTBoxListener.setStopRequested(); + } + + /* + * Basic scenario around removing an inverseOf assertion to the + * TBox + */ + @Test + public void removeTBoxInverseAssertion1() throws InterruptedException { + + // Create TBox, ABox and Inference models and register + // the ABox reasoner listeners with the ABox and TBox + // Pellet will compute TBox inferences + + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + Model inf = ModelFactory.createDefaultModel(); + + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + aBox.register(simpleReasoner); + SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); + tBox.register(simpleReasonerTBoxListener); + + // set up TBox and Abox + + OntProperty P = tBox.createOntProperty("http://test.vivo/propP"); + P.setLabel("property P", "en-US"); + + OntProperty Q = tBox.createOntProperty("http://test.vivo/propQ"); + Q.setLabel("property Q", "en-US"); + + Q.addInverseOf(P); + + // Individuals a, b, c and d + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // abox statements + aBox.add(c,P,d); + + Assert.assertTrue(inf.contains(d,Q,c)); + + // Remove P and Q inverse relationship and wait for + // SimpleReasoner TBox thread to end. + + Q.removeInverseProperty(P); + + Thread.sleep(delay); + while (!VitroBackgroundThread.getLivingThreads().isEmpty()) { + Thread.sleep(delay); + } + + // Verify inferences + Assert.assertFalse(inf.contains(d,Q,c)); + + simpleReasonerTBoxListener.setStopRequested(); + } + + /* + * Basic scenario around recomputing the ABox inferences + */ + @Test + public void recomputeABox1() throws InterruptedException { + + // Create TBox, ABox and Inference models and register + // the ABox reasoner listeners with the ABox and TBox + // Pellet will compute TBox inferences + OntModel tBox = ModelFactory.createOntologyModel(PelletReasonerFactory.THE_SPEC); + OntModel aBox = ModelFactory.createOntologyModel(OntModelSpec.OWL_MEM); + Model inf = ModelFactory.createDefaultModel(); + + SimpleReasoner simpleReasoner = new SimpleReasoner(tBox, aBox, inf); + aBox.register(simpleReasoner); + SimpleReasonerTBoxListener simpleReasonerTBoxListener = getTBoxListener(simpleReasoner); + tBox.register(simpleReasonerTBoxListener); + + // set up TBox and Abox + OntProperty P = tBox.createOntProperty("http://test.vivo/propP"); + P.setLabel("property P", "en-US"); + OntProperty Q = tBox.createOntProperty("http://test.vivo/propQ"); + Q.setLabel("property Q", "en-US"); + Q.addInverseOf(P); + + OntProperty X = tBox.createOntProperty("http://test.vivo/propX"); + P.setLabel("property X", "en-US"); + OntProperty Y = tBox.createOntProperty("http://test.vivo/propY"); + Q.setLabel("property Y", "en-US"); + X.addInverseOf(Y); + + Thread.sleep(delay*3); + + // Individuals a, b, c and d + Resource a = aBox.createResource("http://test.vivo/a"); + Resource b = aBox.createResource("http://test.vivo/b"); + Resource c = aBox.createResource("http://test.vivo/c"); + Resource d = aBox.createResource("http://test.vivo/d"); + + // abox statements + aBox.add(a,P,b); + aBox.add(c,X,d); + + simpleReasoner.recompute(); + + Thread.sleep(delay); + while (!VitroBackgroundThread.getLivingThreads().isEmpty() || simpleReasoner.isRecomputing()) { + Thread.sleep(delay); + } + + // Verify inferences + Assert.assertTrue(inf.contains(b,Q,a)); + Assert.assertTrue(inf.contains(d,Y,c)); + + simpleReasonerTBoxListener.setStopRequested(); + } + + //==================================== Utility methods ==================== + SimpleReasonerTBoxListener getTBoxListener(SimpleReasoner simpleReasoner) { + return new SimpleReasonerTBoxListener(simpleReasoner, new Exception().getStackTrace()[1].getMethodName()); + } + + // To help in debugging the unit test + void printModel(Model model, String modelName) { + + System.out.println("\nThe " + modelName + " model has " + model.size() + " statements:"); + System.out.println("---------------------------------------------------------------------"); + model.write(System.out); + } + + // To help in debugging the unit test + void printModel(OntModel ontModel, String modelName) { + + System.out.println("\nThe " + modelName + " model has " + ontModel.size() + " statements:"); + System.out.println("---------------------------------------------------------------------"); + ontModel.writeAll(System.out,"N3",null); + + } +} \ No newline at end of file