NIHVIVO-3293 add inverse property reasoning to SimpleReasoner

This commit is contained in:
stellamit 2012-06-04 21:47:23 +00:00
parent a31b91bddd
commit 20b04e3e8e
2 changed files with 897 additions and 49 deletions

View file

@ -14,6 +14,7 @@ import com.hp.hpl.jena.ontology.AnnotationProperty;
import com.hp.hpl.jena.ontology.OntClass; import com.hp.hpl.jena.ontology.OntClass;
import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntModel;
import com.hp.hpl.jena.ontology.OntModelSpec; 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.Query;
import com.hp.hpl.jena.query.QueryExecution; import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory; 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.Literal;
import com.hp.hpl.jena.rdf.model.Model; import com.hp.hpl.jena.rdf.model.Model;
import com.hp.hpl.jena.rdf.model.ModelFactory; 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.RDFNode;
import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.Resource;
import com.hp.hpl.jena.rdf.model.ResourceFactory; 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.rdf.model.StmtIterator;
import com.hp.hpl.jena.shared.JenaException; import com.hp.hpl.jena.shared.JenaException;
import com.hp.hpl.jena.shared.Lock; 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.OWL;
import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS; import com.hp.hpl.jena.vocabulary.RDFS;
@ -126,11 +129,12 @@ public class SimpleReasoner extends StatementListener {
*/ */
@Override @Override
public void addedStatement(Statement stmt) { public void addedStatement(Statement stmt) {
try { try {
if (stmt.getPredicate().equals(RDF.type)) { if (stmt.getPredicate().equals(RDF.type)) {
addedABoxTypeAssertion(stmt, inferenceModel, new HashSet<String>()); addedABoxTypeAssertion(stmt, inferenceModel, new HashSet<String>());
setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>()); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>());
} else {
addedABoxAssertion(stmt, inferenceModel);
} }
doPlugins(ModelUpdate.Operation.ADD,stmt); doPlugins(ModelUpdate.Operation.ADD,stmt);
@ -150,8 +154,9 @@ public class SimpleReasoner extends StatementListener {
public void removedStatement(Statement stmt) { public void removedStatement(Statement stmt) {
try { try {
if (!isInterestedInRemovedStatement(stmt)) { return; } // if (!isInterestedInRemovedStatement(stmt)) { return; }
// interested in all of them now that we are doing inverse
// property reasoning
handleRemovedStatement(stmt); handleRemovedStatement(stmt);
} catch (Exception e) { } catch (Exception e) {
@ -160,7 +165,6 @@ public class SimpleReasoner extends StatementListener {
} }
} }
/* /*
* Synchronized part of removedStatement. Interacts * Synchronized part of removedStatement. Interacts
* with DeltaComputer. * with DeltaComputer.
@ -175,6 +179,8 @@ public class SimpleReasoner extends StatementListener {
if (stmt.getPredicate().equals(RDF.type)) { if (stmt.getPredicate().equals(RDF.type)) {
removedABoxTypeAssertion(stmt, inferenceModel); removedABoxTypeAssertion(stmt, inferenceModel);
setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>()); setMostSpecificTypes(stmt.getSubject(), inferenceModel, new HashSet<String>());
} else {
removedABoxAssertion(stmt, inferenceModel);
} }
doPlugins(ModelUpdate.Operation.RETRACT,stmt); doPlugins(ModelUpdate.Operation.RETRACT,stmt);
} }
@ -184,19 +190,16 @@ public class SimpleReasoner extends StatementListener {
* Performs incremental ABox reasoning based * Performs incremental ABox reasoning based
* on changes to the class hierarchy. * on changes to the class hierarchy.
* *
* Handles rdfs:subclassOf, owl:equivalentClass, * Handles rdfs:subclassOf, owl:equivalentClass, and owl:inverseOf
*/ */
public void addedTBoxStatement(Statement stmt) { public void addedTBoxStatement(Statement stmt) {
try { try {
log.debug("added TBox assertion = " + stmt.toString()); if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) {
if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) {
// ignore anonymous classes
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
return; return;
} }
log.debug("added TBox assertion = " + stmt.toString());
if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) {
log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); log.warn("The object of this assertion has a null URI: " + stmtString(stmt));
return; return;
@ -207,6 +210,12 @@ public class SimpleReasoner extends StatementListener {
return; return;
} }
if (stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass)) {
// ignore anonymous classes
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
return;
}
OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI());
if (subject == null) { if (subject == null) {
log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI());
@ -226,6 +235,20 @@ public class SimpleReasoner extends StatementListener {
addedSubClass(subject,object,inferenceModel); addedSubClass(subject,object,inferenceModel);
addedSubClass(object,subject,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) { } catch (Exception e) {
// don't stop the edit if there's an exception // don't stop the edit if there's an exception
@ -237,19 +260,16 @@ public class SimpleReasoner extends StatementListener {
* Performs incremental ABox reasoning based * Performs incremental ABox reasoning based
* on changes to the class hierarchy. * 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 { try {
log.debug("removed TBox assertion = " + stmt.toString()); if (!(stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) || stmt.getPredicate().equals(OWL.inverseOf))) {
if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) {
// ignore anonymous classes
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
return; return;
} }
log.debug("removed TBox assertion = " + stmt.toString());
if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) { if ( stmt.getObject().isResource() && (stmt.getObject().asResource()).getURI() == null ) {
log.warn("The object of this assertion has a null URI: " + stmtString(stmt)); log.warn("The object of this assertion has a null URI: " + stmtString(stmt));
return; return;
@ -260,6 +280,13 @@ public class SimpleReasoner extends StatementListener {
return; return;
} }
if ( stmt.getPredicate().equals(RDFS.subClassOf) || stmt.getPredicate().equals(OWL.equivalentClass) ) {
// ignore anonymous classes
if (stmt.getSubject().isAnon() || stmt.getObject().isAnon()) {
return;
}
OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI()); OntClass subject = tboxModel.getOntClass((stmt.getSubject()).getURI());
if (subject == null) { if (subject == null) {
log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI()); log.debug("didn't find subject class in the tbox: " + (stmt.getSubject()).getURI());
@ -279,6 +306,20 @@ public class SimpleReasoner extends StatementListener {
removedSubClass(subject,object,inferenceModel); removedSubClass(subject,object,inferenceModel);
removedSubClass(object,subject,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) { } catch (Exception e) {
// don't stop the edit if there's an exception // don't stop the edit if there's an exception
@ -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<OntProperty> inverseProperties = getInverseProperties(stmt);
Iterator<OntProperty> 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 * 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 * the inferred statement that B is of that type UNLESS it is otherwise entailed
@ -387,7 +467,6 @@ public class SimpleReasoner extends StatementListener {
tboxModel.enterCriticalSection(Lock.READ); tboxModel.enterCriticalSection(Lock.READ);
try { try {
OntClass cls = null; OntClass cls = null;
if ( (stmt.getObject().asResource()).getURI() != null ) { if ( (stmt.getObject().asResource()).getURI() != null ) {
@ -453,11 +532,48 @@ 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<OntProperty> inverseProperties = getInverseProperties(stmt);
Iterator<OntProperty> 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 // Returns true if it is entailed by class subsumption that
// subject is of type cls; otherwise returns false. // subject is of type cls; otherwise returns false.
protected boolean entailedType(Resource subject, OntClass cls) { protected boolean entailedType(Resource subject, OntClass cls) {
aboxModel.enterCriticalSection(Lock.READ);
tboxModel.enterCriticalSection(Lock.READ); tboxModel.enterCriticalSection(Lock.READ);
aboxModel.enterCriticalSection(Lock.READ);
try { try {
List<OntClass> subclasses = null; List<OntClass> subclasses = null;
@ -483,6 +599,95 @@ public class SimpleReasoner extends StatementListener {
} }
} }
// Returns true if the triple is entailed by inverse property
// reasoning; otherwise returns false.
protected boolean entailedInverseStmt(Statement stmt) {
ExtendedIterator <? extends OntProperty> 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<OntProperty> getInverseProperties(Statement stmt) {
List<OntProperty> inverses = new ArrayList<OntProperty>();
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 <? extends OntProperty> 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 * 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 * 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 * Find the most specific types (classes) of an individual and
* indicate them for the individual with the mostSpecificType * indicate them for the individual with the mostSpecificType
@ -740,13 +1051,12 @@ public class SimpleReasoner extends StatementListener {
* inference models. * inference models.
*/ */
protected synchronized void recomputeABox() { protected synchronized void recomputeABox() {
HashSet<String> unknownTypes = new HashSet<String>(); HashSet<String> unknownTypes = new HashSet<String>();
// recompute the inferences // recompute the inferences
inferenceRebuildModel.enterCriticalSection(Lock.WRITE); inferenceRebuildModel.enterCriticalSection(Lock.WRITE);
try { try {
log.info("Computing class-based ABox inferences."); log.info("Computing ABox inferences.");
inferenceRebuildModel.removeAll(); inferenceRebuildModel.removeAll();
int numStmts = 0; int numStmts = 0;
@ -784,6 +1094,57 @@ public class SimpleReasoner extends StatementListener {
log.info("Still computing class-based ABox inferences..."); 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<Statement> 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) { if (stopRequested) {
log.info("a stopRequested signal was received during recomputeABox. Halting Processing."); log.info("a stopRequested signal was received during recomputeABox. Halting Processing.");
return; return;
@ -798,7 +1159,7 @@ public class SimpleReasoner extends StatementListener {
inferenceRebuildModel.leaveCriticalSection(); 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 // reflect the recomputed inferences into the application
// inference model. // inference model.

View file

@ -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);
}
}