From 1efc189e90240c162b54cbc50362b46786643dad Mon Sep 17 00:00:00 2001 From: Federico Igne Date: Wed, 18 Nov 2020 19:13:25 +0000 Subject: Reorganize project with Java-like folder structure --- src/main/scala/rsacomb/CanonicalModel.scala | 278 -------------- src/main/scala/rsacomb/FilteringProgram.scala | 366 ------------------- src/main/scala/rsacomb/Main.scala | 249 ------------- src/main/scala/rsacomb/RDFoxAxiomConverter.scala | 106 ------ .../scala/rsacomb/RDFoxClassExprConverter.scala | 159 -------- .../scala/rsacomb/RDFoxPropertyExprConverter.scala | 35 -- src/main/scala/rsacomb/RDFoxRuleShards.scala | 5 - src/main/scala/rsacomb/RSA.scala | 100 ------ src/main/scala/rsacomb/RSAAtom.scala | 88 ----- src/main/scala/rsacomb/RSAAxiom.scala | 155 -------- src/main/scala/rsacomb/RSAOntology.scala | 378 ------------------- src/main/scala/rsacomb/RSASuffix.scala | 31 -- src/main/scala/rsacomb/SkolemStrategy.scala | 78 ---- .../scala/rsacomb/implicits/JavaCollections.scala | 13 - src/main/scala/rsacomb/implicits/RDFox.scala | 20 -- src/main/scala/rsacomb/util/RDFoxHelpers.scala | 100 ------ .../scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | 284 +++++++++++++++ .../uk/ac/ox/cs/rsacomb/FilteringProgram.scala | 366 +++++++++++++++++++ src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | 249 +++++++++++++ .../scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | 380 ++++++++++++++++++++ .../cs/rsacomb/converter/RDFoxAxiomConverter.scala | 105 ++++++ .../converter/RDFoxClassExprConverter.scala | 160 +++++++++ .../converter/RDFoxPropertyExprConverter.scala | 35 ++ .../ox/cs/rsacomb/converter/RDFoxRuleShards.scala | 5 + .../ox/cs/rsacomb/converter/SkolemStrategy.scala | 78 ++++ .../ox/cs/rsacomb/implicits/JavaCollections.scala | 13 + .../uk/ac/ox/cs/rsacomb/implicits/RDFox.scala | 20 ++ .../uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala | 88 +++++ .../uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala | 155 ++++++++ .../uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala | 31 ++ .../uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala | 100 ++++++ src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala | 100 ++++++ src/test/scala/rsacomb/CanonicalModelSpec.scala | 369 ------------------- src/test/scala/rsacomb/FilteringProgramSpecs.scala | 397 -------------------- src/test/scala/rsacomb/OWLAxiomSpec.scala | 335 ----------------- src/test/scala/rsacomb/OWLClassSpec.scala | 273 -------------- .../uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala | 370 +++++++++++++++++++ .../ac/ox/cs/rsacomb/FilteringProgramSpecs.scala | 398 +++++++++++++++++++++ .../scala/uk/ac/ox/cs/rsacomb/OWLAxiomSpec.scala | 336 +++++++++++++++++ .../scala/uk/ac/ox/cs/rsacomb/OWLClassSpec.scala | 277 ++++++++++++++ 40 files changed, 3550 insertions(+), 3535 deletions(-) delete mode 100644 src/main/scala/rsacomb/CanonicalModel.scala delete mode 100644 src/main/scala/rsacomb/FilteringProgram.scala delete mode 100644 src/main/scala/rsacomb/Main.scala delete mode 100644 src/main/scala/rsacomb/RDFoxAxiomConverter.scala delete mode 100644 src/main/scala/rsacomb/RDFoxClassExprConverter.scala delete mode 100644 src/main/scala/rsacomb/RDFoxPropertyExprConverter.scala delete mode 100644 src/main/scala/rsacomb/RDFoxRuleShards.scala delete mode 100644 src/main/scala/rsacomb/RSA.scala delete mode 100644 src/main/scala/rsacomb/RSAAtom.scala delete mode 100644 src/main/scala/rsacomb/RSAAxiom.scala delete mode 100644 src/main/scala/rsacomb/RSAOntology.scala delete mode 100644 src/main/scala/rsacomb/RSASuffix.scala delete mode 100644 src/main/scala/rsacomb/SkolemStrategy.scala delete mode 100644 src/main/scala/rsacomb/implicits/JavaCollections.scala delete mode 100644 src/main/scala/rsacomb/implicits/RDFox.scala delete mode 100644 src/main/scala/rsacomb/util/RDFoxHelpers.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala create mode 100644 src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala delete mode 100644 src/test/scala/rsacomb/CanonicalModelSpec.scala delete mode 100644 src/test/scala/rsacomb/FilteringProgramSpecs.scala delete mode 100644 src/test/scala/rsacomb/OWLAxiomSpec.scala delete mode 100644 src/test/scala/rsacomb/OWLClassSpec.scala create mode 100644 src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala create mode 100644 src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala create mode 100644 src/test/scala/uk/ac/ox/cs/rsacomb/OWLAxiomSpec.scala create mode 100644 src/test/scala/uk/ac/ox/cs/rsacomb/OWLClassSpec.scala (limited to 'src') diff --git a/src/main/scala/rsacomb/CanonicalModel.scala b/src/main/scala/rsacomb/CanonicalModel.scala deleted file mode 100644 index d9e1641..0000000 --- a/src/main/scala/rsacomb/CanonicalModel.scala +++ /dev/null @@ -1,278 +0,0 @@ -package rsacomb - -import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} -import org.semanticweb.owlapi.model.{ - OWLClass, - // OWLObjectProperty, - OWLSubObjectPropertyOfAxiom, - // OWLObjectPropertyExpression, - OWLObjectSomeValuesFrom, - OWLSubClassOfAxiom -} - -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - Rule, - BodyFormula, - TupleTableAtom, - Negation -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - Term, - Variable, - // Resource, - IRI -} - -import suffix.{Empty, Forward, Backward, Inverse} -import util.RSA - -class CanonicalModel(val ontology: RSAOntology) extends RSAAxiom { - - import implicits.RDFox._ - import implicits.JavaCollections._ - - val named: List[Rule] = - ontology.individuals.map(a => Rule.create(RSA.Named(a))) - - val rolesAdditionalRules: List[Rule] = { - // Given a role (predicate) compute additional logic rules - def additional(pred: String): Seq[Rule] = { - val varX = Variable.create("X") - val varY = Variable.create("Y") - for ( - (hSuffix, bSuffix) <- List( - (Empty, Forward), - (Empty, Backward), - (Inverse, Forward + Inverse), - (Inverse, Backward + Inverse), - (Backward + Inverse, Forward), - (Forward + Inverse, Backward), - (Backward, Forward + Inverse), - (Forward, Backward + Inverse) - ) - ) - yield Rule.create( - TupleTableAtom.rdf(varX, pred :: hSuffix, varY), - TupleTableAtom.rdf(varX, pred :: bSuffix, varY) - ) - } - // Compute additional rules per role - ontology.roles - .collect { case prop: OWLObjectProperty => prop } - .map(_.getIRI.getIRIString) - .flatMap(additional) - } - - private lazy val topAxioms: List[Rule] = { - val varX = Variable.create("X") - val varY = Variable.create("Y") - val concepts = ontology.concepts.map(c => { - Rule.create( - RSA.Thing(varX), - TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI) - ) - }) - val roles = ontology.roles.map(r => { - val name = r match { - case x: OWLObjectProperty => x.getIRI.getIRIString - case x: OWLObjectInverseOf => - x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse - } - Rule.create( - List(RSA.Thing(varX), RSA.Thing(varY)), - List(TupleTableAtom.rdf(varX, name, varY)) - ) - }) - concepts ::: roles - } - - private val equalityAxioms: List[Rule] = { - val varX = Variable.create("X") - val varY = Variable.create("Y") - val varZ = Variable.create("Z") - List( - // Reflexivity - Rule.create(RSA.congruent(varX, varX), RSA.Thing(varX)), - // Simmetry - Rule.create(RSA.congruent(varY, varX), RSA.congruent(varX, varY)), - // Transitivity - Rule.create( - RSA.congruent(varX, varZ), - RSA.congruent(varX, varY), - RSA.congruent(varY, varZ) - ) - ) - } - - val rules: List[Rule] = { - // Compute rules from ontology axioms - val rules = ontology.axioms.flatMap(_.accept(RuleGenerator)) - // Return full set of rules - rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: named - } - - object RuleGenerator - extends RDFoxAxiomConverter( - Variable.create("X"), - ontology.unsafeRoles, - SkolemStrategy.None, - Empty - ) { - - private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { - val unfold = ontology.unfold(axiom).toList - // Fresh Variables - val v0 = RSA("v0_" ++ axiom.hashed) - val varX = Variable.create("X") - implicit val unfoldTerm = RSA(unfold.hashCode.toString) - // TODO: use axiom.toTriple instead - val atomA: TupleTableAtom = { - val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) - } - val roleRf: TupleTableAtom = { - val visitor = - new RDFoxPropertyExprConverter(varX, v0, Forward) - axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getProperty - .accept(visitor) - .head - } - val atomB: TupleTableAtom = { - val cls = axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getFiller - .asInstanceOf[OWLClass] - .getIRI - TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) - } - // TODO: To be consistent with the specifics of the visitor we are - // returning facts as `Rule`s with true body. While this is correct - // there is an easier way to import facts into RDFox. Are we able to - // do that? - val facts = unfold.map(x => Rule.create(RSA.In(x))) - val rules = List( - Rule.create(roleRf, atomA, RSA.notIn(varX)), - Rule.create(atomB, atomA, RSA.notIn(varX)) - ) - facts ++ rules - } - - private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { - val roleR = - axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getProperty - if (ontology.confl(roleR) contains roleR) { - // Fresh Variables - val v0 = RSA("v0_" ++ axiom.hashed) - val v1 = RSA("v1_" ++ axiom.hashed) - val v2 = RSA("v2_" ++ axiom.hashed) - // Predicates - def atomA(t: Term): TupleTableAtom = { - val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) - } - def roleRf(t1: Term, t2: Term): TupleTableAtom = { - val visitor = - new RDFoxPropertyExprConverter(t1, t2, Forward) - roleR.accept(visitor).head - } - def atomB(t: Term): TupleTableAtom = { - val cls = axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getFiller - .asInstanceOf[OWLClass] - .getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) - } - //Rules - List( - Rule.create(roleRf(v0, v1), atomA(v0)), - Rule.create(atomB(v1), atomA(v0)), - Rule.create(roleRf(v1, v2), atomA(v1)), - Rule.create(atomB(v2), atomA(v1)) - ) - } else { - List() - } - } - - private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { - val cycle = ontology.cycle(axiom).toList - val roleR = - axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getProperty - // Fresh Variables - val v1 = RSA("v1_" ++ axiom.hashed) - // Predicates - def atomA(t: Term): TupleTableAtom = { - val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI - TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) - } - def roleRf(t: Term): TupleTableAtom = { - val visitor = - new RDFoxPropertyExprConverter(t, v1, Forward) - roleR.accept(visitor).head - } - val atomB: TupleTableAtom = { - val cls = axiom.getSuperClass - .asInstanceOf[OWLObjectSomeValuesFrom] - .getFiller - .asInstanceOf[OWLClass] - .getIRI - TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) - } - cycle.flatMap { x => - List( - Rule.create(roleRf(x), atomA(x)), - Rule.create(atomB, atomA(x)) - ) - } - } - - override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { - if (axiom.isT5) { - // TODO: get role in T5 axiom - // Assuming one role here - val role = axiom.objectPropertyExpressionsInSignature(0) - if (ontology.unsafeRoles contains role) { - val visitor = - new RDFoxAxiomConverter( - Variable.create("X"), - ontology.unsafeRoles, - SkolemStrategy.Standard(axiom.toString), - Forward - ) - axiom.accept(visitor) - } else { - rules1(axiom) ::: rules2(axiom) ::: rules3(axiom) - } - } else { - // Fallback to standard OWL to LP translation - super.visit(axiom) - } - } - - override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { - val varX = Variable.create("X") - val visitorF = new RDFoxAxiomConverter( - varX, - ontology.unsafeRoles, - SkolemStrategy.None, - Forward - ) - val visitorB = new RDFoxAxiomConverter( - varX, - ontology.unsafeRoles, - SkolemStrategy.None, - Backward - ) - axiom.accept(visitorB) ::: axiom.accept(visitorF) - } - - } - -} diff --git a/src/main/scala/rsacomb/FilteringProgram.scala b/src/main/scala/rsacomb/FilteringProgram.scala deleted file mode 100644 index c82dcb6..0000000 --- a/src/main/scala/rsacomb/FilteringProgram.scala +++ /dev/null @@ -1,366 +0,0 @@ -package rsacomb - -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.expression.{ - Term, - IRI, - Variable, - Literal, - FunctionCall -} -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - Rule, - TupleTableAtom, - BindAtom, - TupleTableName, - Atom, - BodyFormula, - Negation -} -import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{SelectQuery} -import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ - GroupGraphPattern, - ConjunctionPattern, - TriplePattern, - QueryPattern -} - -import scala.collection.JavaConverters._ - -import implicits.RSAAtom -import suffix.{RSASuffix, Forward, Backward} -import util.RSA - -class FilteringProgram(query: SelectQuery, constants: List[Term]) - extends RSAAtom { - - /* Makes mplicit conversion OWLAPI IRI <-> RDFox IRI available */ - // import implicits.RDFox._ - - implicit val variables: (List[Term], List[Term]) = { - val all: Set[Variable] = query.getQueryBody.getWherePattern match { - case b: ConjunctionPattern => { - b.getConjuncts.asScala.toSet.flatMap { conj: QueryPattern => - conj match { - case c: TriplePattern => - Set(c.getSubject, c.getPredicate, c.getObject).collect { - case v: Variable => v - } - case _ => Set() - } - } - } - case _ => Set() - } - if (query.getAllPossibleVariables) { - (all.toList, List()) - } else { - val answer = query.getSelection.asScala.map(_.getVariable).toSet - (answer.toList, (all &~ answer).toList) - } - } - - val (answer, bounded) = variables - - val facts: List[Rule] = constants.map(c => Rule.create(RSA.NI(c))) - val rules: List[Rule] = - this.generateFilteringProgram().map(reifyRule) ++ facts - - /* NOTE: we are restricting to queries that contain conjunctions of - * atoms for the time being. This might need to be reviewed in the - * future. - */ - private def queryToBody(body: GroupGraphPattern): List[TupleTableAtom] = - body match { - case b: ConjunctionPattern => { - val conjuncts = b.getConjuncts.asScala.toList - conjuncts flatMap { conj => - conj match { - case c: TriplePattern => - List( - TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject) - ) - case _ => List() - } - } - } - case _ => List() - } - - private def generateFilteringProgram(): List[Rule] = { - // General purpose variables - val varU = Variable.create("U") - val varV = Variable.create("V") - val varW = Variable.create("W") - // Query formula as a rule body - val body = queryToBody(query.getQueryBody.getWherePattern) - // Auxiliar predicates/helpers - def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) - // val predQM = - // TupleTableAtom.create( - // TupleTableName.create(RSA.Qm.getIRI), - // (answer ++ bounded): _* - // ) - // def predID(t1: Term, t2: Term) = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("ID").getIRI), - // (answer ++ bounded).appended(t1).appended(t2): _* - // ) - // def predNAMED(t1: Term): TupleTableAtom = - // TupleTableAtom.rdf(t1, IRI.RDF_TYPE, RSA.rsa("NAMED")) - // def predTQ(t1: Term, t2: Term, sx: RSASuffix) = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("TQ" :: sx).getIRI), - // (answer ++ bounded).appended(t1).appended(t2): _* - // ) - // def predAQ(t1: Term, t2: Term, sx: RSASuffix) = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("AQ" :: sx).getIRI), - // (answer ++ bounded).appended(t1).appended(t2): _* - // ) - // val predFK = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("FK").getIRI), - // (answer ++ bounded): _* - // ) - // val predSP = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("SP").getIRI), - // (answer ++ bounded): _* - // ) - // val predANS = - // TupleTableAtom.create( - // TupleTableName.create(RSA.rsa("ANS").getIRI), - // answer: _* - // ) - - /* Rule 1 */ - val r1 = Rule.create(RSA.QM, body: _*) - - /* Rules 3x */ - val r3a = - for ((v, i) <- bounded.zipWithIndex) - yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v))) - val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV)) - val r3c = - Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW)) - - /* Rules 4x */ - val r4a = for { - role1 <- body.filter(_.isRoleAssertion) - if bounded contains (role1.getArguments.get(2)) - role2 <- body.filter(_.isRoleAssertion) - if bounded contains (role2.getArguments.get(2)) - } yield Rule.create( - RSA.FK, - role1 << Forward, - role2 << Forward, - RSA.ID( - RSA(bounded.indexOf(role1.getArguments.get(2))), - RSA(bounded.indexOf(role2.getArguments.get(2))) - ), - not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(0))) - ) - val r4b = for { - role1 <- body.filter(_.isRoleAssertion) - if bounded contains (role1.getArguments.get(2)) - role2 <- body.filter(_.isRoleAssertion) - if bounded contains (role2.getArguments.get(0)) - } yield Rule.create( - RSA.FK, - role1 << Forward, - role2 << Backward, - RSA.ID( - RSA(bounded.indexOf(role1.getArguments.get(2))), - RSA(bounded.indexOf(role2.getArguments.get(0))) - ), - not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(2))) - ) - val r4c = for { - role1 <- body.filter(_.isRoleAssertion) - if bounded contains (role1.getArguments.get(0)) - role2 <- body.filter(_.isRoleAssertion) - if bounded contains (role2.getArguments.get(0)) - } yield Rule.create( - RSA.FK, - role1 << Backward, - role2 << Backward, - RSA.ID( - RSA(bounded.indexOf(role1.getArguments.get(0))), - RSA(bounded.indexOf(role2.getArguments.get(0))) - ), - not(RSA.congruent(role1.getArguments.get(2), role2.getArguments.get(2))) - ) - - /* Rules 5x */ - val r5a = for { - role1 <- body.filter(_.isRoleAssertion) - role1arg0 = role1.getArguments.get(0) - role1arg2 = role1.getArguments.get(2) - if bounded contains role1arg0 - if bounded contains role1arg2 - role2 <- body.filter(_.isRoleAssertion) - role2arg0 = role2.getArguments.get(0) - role2arg2 = role2.getArguments.get(2) - if bounded contains role2arg0 - if bounded contains role2arg2 - } yield Rule.create( - RSA.ID( - RSA(bounded indexOf role1arg0), - RSA(bounded indexOf role2arg0) - ), - role1 << Forward, - role2 << Forward, - RSA.ID( - RSA(bounded indexOf role1arg2), - RSA(bounded indexOf role2arg2) - ), - RSA.congruent(role1arg0, role2arg0), - not(RSA.NI(role1arg0)) - ) - val r5b = for { - role1 <- body.filter(_.isRoleAssertion) - role1arg0 = role1.getArguments.get(0) - role1arg2 = role1.getArguments.get(2) - if bounded contains role1arg0 - if bounded contains role1arg2 - role2 <- body.filter(_.isRoleAssertion) - role2arg0 = role2.getArguments.get(0) - role2arg2 = role2.getArguments.get(2) - if bounded contains role2arg0 - if bounded contains role2arg2 - } yield Rule.create( - RSA.ID( - RSA(bounded indexOf role1arg0), - RSA(bounded indexOf role2arg2) - ), - role1 << Forward, - role2 << Backward, - RSA.ID( - RSA(bounded indexOf role1arg2), - RSA(bounded indexOf role2arg0) - ), - RSA.congruent(role1arg0, role2arg2), - not(RSA.NI(role1arg0)) - ) - val r5c = for { - role1 <- body.filter(_.isRoleAssertion) - role1arg0 = role1.getArguments.get(0) - role1arg2 = role1.getArguments.get(2) - if bounded contains role1arg0 - if bounded contains role1arg2 - role2 <- body.filter(_.isRoleAssertion) - role2arg0 = role2.getArguments.get(0) - role2arg2 = role2.getArguments.get(2) - if bounded contains role2arg0 - if bounded contains role2arg2 - } yield Rule.create( - RSA.ID( - RSA(bounded indexOf role1arg2), - RSA(bounded indexOf role2arg2) - ), - role1 << Backward, - role2 << Backward, - RSA.ID( - RSA(bounded indexOf role1arg0), - RSA(bounded indexOf role2arg0) - ), - RSA.congruent(role1arg2, role2arg2), - not(RSA.NI(role1arg2)) - ) - - /* Rules 6 */ - val r6 = { - for { - role <- body.filter(_.isRoleAssertion) - arg0 = role.getArguments.get(0) - arg2 = role.getArguments.get(2) - if bounded contains arg0 - if bounded contains arg2 - suffix <- Seq(Forward, Backward) - } yield Rule.create( - RSA.AQ(varV, varW, suffix), - role << suffix, - RSA.ID(RSA(bounded indexOf arg0), varV), - RSA.ID(RSA(bounded indexOf arg2), varW) - ) - } - - /* Rules 7x */ - val r7a = { - for (suffix <- List(Forward, Backward)) - yield Rule.create( - RSA.TQ(varU, varV, suffix), - RSA.AQ(varU, varV, suffix) - ) - } - val r7b = { - for (suffix <- List(Forward, Backward)) - yield Rule.create( - RSA.TQ(varU, varW, suffix), - RSA.AQ(varU, varV, suffix), - RSA.TQ(varV, varW, suffix) - ) - } - - /* Rules 8x */ - val r8a = - for (v <- answer) yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v))) - val r8b = - Rule.create(RSA.SP, RSA.FK) - val r8c = - for (suffix <- List(Forward, Backward)) - yield Rule.create( - RSA.SP, - RSA.TQ(varV, varV, suffix) - ) - - /* Rule 9 */ - val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) - - r1 :: - r3a ::: r3b :: r3c :: - r4c ::: r4b ::: r4a ::: - r5c ::: r5b ::: r5a ::: - r6 ::: - r7b ::: r7a ::: - r8a ::: r8b :: r8c ::: - r9 :: - List() - } - - private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = { - atom match { - case atom: TupleTableAtom => atom.reified - case other => (None, List(other)) - } - } - - private def reifyBodyFormula(formula: BodyFormula): List[BodyFormula] = { - formula match { - case atom: TupleTableAtom => atom.reified._2 - case neg: Negation => { - val (bs, as) = neg.getNegatedAtoms.asScala.toList.map(reifyAtom).unzip - val bind = bs.flatten.map(_.getBoundVariable).asJava - val atoms = as.flatten.asJava - List(Negation.create(bind, atoms)) - } - case other => List(other) - } - } - - private def reifyRule(rule: Rule): Rule = { - val (bs, hs) = rule.getHead.asScala.toList.map(_.reified).unzip - val head: List[TupleTableAtom] = hs.flatten - val bind: List[BodyFormula] = bs.flatten - val body: List[BodyFormula] = - rule.getBody.asScala.toList.map(reifyBodyFormula).flatten - Rule.create(head.asJava, (body ++ bind).asJava) - } - -} // class FilteringProgram - -object FilteringProgram { - def apply(query: SelectQuery, constants: List[Term]): FilteringProgram = - new FilteringProgram(query, constants) -} // object FilteringProgram diff --git a/src/main/scala/rsacomb/Main.scala b/src/main/scala/rsacomb/Main.scala deleted file mode 100644 index 31dd5a0..0000000 --- a/src/main/scala/rsacomb/Main.scala +++ /dev/null @@ -1,249 +0,0 @@ -package rsacomb - -/* Java imports */ -import java.io.File -import java.util.HashMap -import scala.collection.JavaConverters._ - -import tech.oxfordsemantic.jrdfox.client.UpdateType -import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} - -/* Local imports */ -import util.{RDFoxHelpers, RSA} - -object RSAComb extends App { - - val help: String = """ - rsacomb - combined approach for CQ answering for RSA ontologies. - - USAGE - rsacomb - - where - the ontology is expected to be an OWL file and the (single) - query a SPARQL query file. - - """ - - /* Simple arguments handling - * - * TODO: use something better later on - */ - - if (args.length < 2) { - println(help) - sys.exit; - } - - val ontoPath = new File(args(0)) - val queryPath = new File(args(1)) - - if (!ontoPath.isFile || !queryPath.isFile) { - println("The provided arguments are not regular files.\n\n") - println(help) - sys.exit; - } - - /* Create RSA object from generic OWLOntology - * - * TODO: It might be required to check if the ontology in input is - * Horn-ALCHOIQ. At the moment we are assuming this is always the - * case. - */ - - val ontology = RSAOntology(ontoPath) - if (ontology.isRSA) { - - /* Load query */ - val query = RDFoxHelpers.parseSelectQuery( - """ - PREFIX : - - SELECT ?X - WHERE { - ?X a :D ; - :R ?Y . - ?Y :S ?Z . - ?Z a :D . - } - """ - ) - - /* Compute answers to query */ - query match { - case Some(query) => { - - import implicits.JavaCollections._ - - // Open connection to RDFox - val (server, data) = RDFoxHelpers.openConnection("AnswerComputation") - - { - println("\nQuery") - println(query) - } - - // Step 1. Computing the canonical model - val canon = ontology.canonicalModel - data.addRules(canon.rules) - - { - println("\nCanonical Model rules:") - canon.rules.foreach(println) - } - - // Step 2. Computing the canonical model - val nis = { - val query = "SELECT ?Y WHERE { ?X rsa:EquivTo ?Y ; a rsa:Named . }" - RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes).flatten - } - val filter = ontology.filteringProgram(query, nis) - data.addRules(filter.rules) - - { - println("\nFiltering rules") - filter.rules.foreach(println) - } - - // Retrieve answers - println("\nAnswers:") - val ans = - RDFoxHelpers.queryInternalPredicate(data, "Ans", filter.answer.length) - println(ans) - - /* DEBUG: adding additional checks - */ - { - import suffix.{Forward, Backward} - - val arity = filter.answer.length + filter.bounded.length - - println("\nIndividuals:") - ontology.individuals.foreach(println) - - println("\nThings:") - val things = RDFoxHelpers.submitSelectQuery( - data, - """ - PREFIX owl: - - SELECT ?X { - ?X a owl:Thing - } - """ - ) - println(things) - - println("\nNAMEDs:") - val named = RDFoxHelpers.submitSelectQuery( - data, - """ - SELECT ?X { - ?X a rsa:Named - } - """, - RSA.Prefixes - ) - println(named) - - println("\nNIs:") - val nis = RDFoxHelpers.submitSelectQuery( - data, - """ - SELECT ?X { - ?X a rsa:NI - } - """, - RSA.Prefixes - ) - println(nis) - - // ID instances - println("\nIDs:") - val ids = RDFoxHelpers.queryInternalPredicate( - data, - "ID", - arity + 2 - ) - println(ids) - - println("\nEquivTo:") - val equivs = RDFoxHelpers.submitSelectQuery( - data, - """ - SELECT ?X ?Y { - ?X rsa:EquivTo ?Y - } - """, - RSA.Prefixes - ) - println(equivs) - - // Unfiltered answers - println("\nPossible answers:") - val qms = RDFoxHelpers.queryInternalPredicate( - data, - "QM", - arity - ) - println(qms) - - // Cycle detected - println("\nCycle detection:") - val aqf = RDFoxHelpers.queryInternalPredicate( - data, - "AQ" :: Forward, - arity + 2 - ) - val aqb = RDFoxHelpers.queryInternalPredicate( - data, - "AQ" :: Backward, - arity + 2 - ) - println(aqf) - println(aqb) - - // Forks detected - println("\nForks:") - val fk = RDFoxHelpers.queryInternalPredicate( - data, - "FK", - arity - ) - println(fk) - - // Spurious answers - println("\nSpurious answers") - val sp = RDFoxHelpers.queryInternalPredicate( - data, - "SP", - arity - ) - println(sp) - } - - // Close connection to RDFox - RDFoxHelpers.closeConnection(server, data) - } - case None => {} - } - } -} - -/* Notes: - * - * To establish a connection with a local RDFox instance, do the - * following: - * - * ``` - * val serverConnection : ServerConnection = ConnectionFactory.newServerConnection("rdfox:local", "", "") - * serverConnection.createDataStore("test","seq",new HashMap()) - * val dataStoreConnection : DataStoreConnection = serverConnection.newDataStoreConnection("test") - * dataStoreConnection.importData( - * UpdateType.ADDITION, - * Prefixes.s_emptyPrefixes, - * new File("./path/to/file") - * ) - * ``` - */ diff --git a/src/main/scala/rsacomb/RDFoxAxiomConverter.scala b/src/main/scala/rsacomb/RDFoxAxiomConverter.scala deleted file mode 100644 index 9b78e8e..0000000 --- a/src/main/scala/rsacomb/RDFoxAxiomConverter.scala +++ /dev/null @@ -1,106 +0,0 @@ -package rsacomb - -import org.semanticweb.owlapi.model.{ - OWLAxiom, - OWLSubClassOfAxiom, - OWLEquivalentClassesAxiom, - OWLObjectPropertyExpression -} -import org.semanticweb.owlapi.model.OWLAxiomVisitorEx - -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - Rule, - BodyFormula, - TupleTableAtom, - TupleTableName -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - Term, - IRI, - Variable, - Literal -} - -import scala.collection.JavaConverters._ - -import rsacomb.SkolemStrategy -import rsacomb.RDFoxRuleShards -import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom -import org.semanticweb.owlapi.model.OWLObjectProperty -import org.semanticweb.owlapi.model.OWLClassAssertionAxiom - -import suffix.{RSASuffix, Empty} - -object RDFoxAxiomConverter { - - def apply( - term: Term, - unsafe: List[OWLObjectPropertyExpression], - skolem: SkolemStrategy = SkolemStrategy.None, - suffix: RSASuffix = Empty - ): RDFoxAxiomConverter = - new RDFoxAxiomConverter(term, unsafe, skolem, suffix) - -} // object RDFoxAxiomConverter - -class RDFoxAxiomConverter( - term: Term, - unsafe: List[OWLObjectPropertyExpression], - skolem: SkolemStrategy, - suffix: RSASuffix -) extends OWLAxiomVisitorEx[List[Rule]] { - - override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { - // Skolemization is needed only for the head of an axiom - val subVisitor = - new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) - val superVisitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) - // Each visitor returns a `RDFoxRuleShards`, a tuple (res,ext): - // - the `res` List is a list of atoms resulting from the conversion - // of the axiom. - // - for some Class Expressions appearing in the head of an Axiom, - // the conversion might produce atoms that need to appear in the - // body (and not in the head) of the rule. This is what the `ext` - // List is for. - val sub = axiom.getSubClass.accept(subVisitor) - val sup = axiom.getSuperClass.accept(superVisitor) - val head = sup.res.asJava - val body = (sub.res ++ sup.ext).asJava - List(Rule.create(head, body)) - } - - override def visit(axiom: OWLEquivalentClassesAxiom): List[Rule] = { - for { - axiom1 <- axiom.asPairwiseAxioms.asScala.toList - axiom2 <- axiom1.asOWLSubClassOfAxioms.asScala.toList - rule <- axiom2.accept(this) - } yield rule - } - - override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { - val term1 = RSAOntology.genFreshVariable() - val subVisitor = - new RDFoxPropertyExprConverter(term, term1, suffix) - val superVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) - val body: List[BodyFormula] = axiom.getSubProperty.accept(subVisitor) - val head: List[TupleTableAtom] = axiom.getSuperProperty.accept(superVisitor) - List(Rule.create(head.asJava, body.asJava)) - } - - override def visit(axiom: OWLClassAssertionAxiom): List[Rule] = { - val ind = axiom.getIndividual - if (ind.isNamed) { - val term = IRI.create(ind.asOWLNamedIndividual().getIRI.getIRIString) - val cls = axiom.getClassExpression - val visitor = - new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) - val shard = cls.accept(visitor) - List(Rule.create(shard.res.asJava, shard.ext.asJava)) - } else { - List() - } - } - - def doDefault(axiom: OWLAxiom): List[Rule] = List() - -} // class RDFoxAxiomConverter diff --git a/src/main/scala/rsacomb/RDFoxClassExprConverter.scala b/src/main/scala/rsacomb/RDFoxClassExprConverter.scala deleted file mode 100644 index f4187ed..0000000 --- a/src/main/scala/rsacomb/RDFoxClassExprConverter.scala +++ /dev/null @@ -1,159 +0,0 @@ -package rsacomb - -import scala.collection.JavaConverters._ -import java.util.stream.{Stream, Collectors} - -import org.semanticweb.owlapi.model.{ - OWLClassExpression, - OWLClass, - OWLObjectSomeValuesFrom, - OWLObjectIntersectionOf, - OWLObjectOneOf, - OWLObjectMaxCardinality -} -import org.semanticweb.owlapi.model.OWLClassExpressionVisitorEx -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - BindAtom, - TupleTableName, - TupleTableAtom -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - Term, - Literal, - Variable, - FunctionCall, - IRI -} - -import org.semanticweb.owlapi.model.OWLObjectPropertyExpression -import org.semanticweb.owlapi.model.OWLObjectProperty - -import suffix.{RSASuffix, Empty} -import util.RSA - -object RDFoxClassExprConverter { - - def apply( - term: Term, - unsafe: List[OWLObjectPropertyExpression] = List(), - skolem: SkolemStrategy = SkolemStrategy.None, - suffix: RSASuffix = Empty - ): RDFoxClassExprConverter = - new RDFoxClassExprConverter(term, unsafe, skolem, suffix) - - def merge(rules: List[RDFoxRuleShards]): RDFoxRuleShards = { - rules.foldLeft(RDFoxRuleShards(List(), List())) { (r1, r2) => - RDFoxRuleShards( - r1.res ++ r2.res, - r1.ext ++ r2.ext - ) - } - } - -} // object RDFoxClassExprConverter - -class RDFoxClassExprConverter( - term: Term, - unsafe: List[OWLObjectPropertyExpression], - skolem: SkolemStrategy, - suffix: RSASuffix -) extends OWLClassExpressionVisitorEx[RDFoxRuleShards] { - - import implicits.RDFox._ - - // OWLClass - override def visit(expr: OWLClass): RDFoxRuleShards = { - val iri: IRI = if (expr.isTopEntity()) IRI.THING else expr.getIRI() - val atom = List(TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri)) - RDFoxRuleShards(atom, List()) - } - - // OWLObjectIntersectionOf - override def visit(expr: OWLObjectIntersectionOf): RDFoxRuleShards = { - val visitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) - // TODO: maybe using `flatMap` instead of `merge` + `map` works as well - RDFoxClassExprConverter.merge( - expr.asConjunctSet.asScala.toList - .map((e: OWLClassExpression) => e.accept(visitor)) - ) - } - - // OWLObjectOneOf - override def visit(expr: OWLObjectOneOf): RDFoxRuleShards = { - val visitor = RDFoxClassExprConverter(term, unsafe, skolem, suffix) - // TODO: review nominal handling. Here we are taking "just" one - val ind = expr.individuals - .collect(Collectors.toList()) - .asScala - .filter(_.isOWLNamedIndividual) - .head // restricts to proper "nominals" - .asOWLNamedIndividual - .getIRI - val atom = List( - TupleTableAtom.rdf(term, IRI.SAME_AS, ind) - ) - RDFoxRuleShards(atom, List()) - } - - // OWLObjectSomeValuesFrom - override def visit(expr: OWLObjectSomeValuesFrom): RDFoxRuleShards = { - val y = RSAOntology.genFreshVariable() - // Here we are assuming a role name - val prop = expr.getProperty() - // Computes the result of rule skolemization. Depending on the used - // technique it might involve the introduction of additional atoms, - // and/or fresh constants and variables. - val (head, body, term1) = skolem match { - case SkolemStrategy.None => (List(), List(), y) - case SkolemStrategy.Constant(c) => (List(), List(), c) - case SkolemStrategy.ConstantRSA(c) => { - if (unsafe.contains(prop)) - (List(RSA.PE(term, c), RSA.U(c)), List(), c) - else - (List(), List(), c) - } - case SkolemStrategy.Standard(f) => { - ( - List(), - List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), y)), - y - ) - } - } - val classVisitor = - new RDFoxClassExprConverter(term1, unsafe, skolem, suffix) - val classResult = expr.getFiller.accept(classVisitor) - val propertyVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) - val propertyResult = expr.getProperty.accept(propertyVisitor) - RDFoxRuleShards( - classResult.res ++ propertyResult ++ head, - classResult.ext ++ body - ) - } - - // OWLObjectMaxCardinality - override def visit(expr: OWLObjectMaxCardinality): RDFoxRuleShards = { - // TODO: again, no hardcoded variables - val vars = - List(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) - val classResult = RDFoxClassExprConverter.merge( - vars - .map(new RDFoxClassExprConverter(_, unsafe, skolem, suffix)) - .map(expr.getFiller.accept(_)) - ) - val propertyResult = - vars - .map(new RDFoxPropertyExprConverter(term, _, suffix)) - .map(expr.getProperty.accept(_)) - .flatten - RDFoxRuleShards( - List(TupleTableAtom.rdf(vars(0), IRI.SAME_AS, vars(1))), - classResult.res ++ propertyResult - ) - } - - def doDefault(expr: OWLClassExpression): RDFoxRuleShards = - RDFoxRuleShards(List(), List()) - -} // class RDFoxClassExprConverter diff --git a/src/main/scala/rsacomb/RDFoxPropertyExprConverter.scala b/src/main/scala/rsacomb/RDFoxPropertyExprConverter.scala deleted file mode 100644 index 826e965..0000000 --- a/src/main/scala/rsacomb/RDFoxPropertyExprConverter.scala +++ /dev/null @@ -1,35 +0,0 @@ -package rsacomb - -import org.semanticweb.owlapi.model.{OWLPropertyExpression, OWLObjectProperty} -import org.semanticweb.owlapi.model.OWLPropertyExpressionVisitorEx - -import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom -import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, Literal} - -import org.semanticweb.owlapi.model.OWLObjectInverseOf - -import suffix.{RSASuffix, Inverse} - -class RDFoxPropertyExprConverter( - term1: Term, - term2: Term, - suffix: RSASuffix -) extends OWLPropertyExpressionVisitorEx[List[TupleTableAtom]] { - - // Automatically converts OWLAPI types into RDFox equivalent types. - import implicits.RDFox._ - - override def visit(expr: OWLObjectProperty): List[TupleTableAtom] = { - val base = expr.getIRI.getIRIString - val pred = IRI.create(base :: suffix) - List(TupleTableAtom.rdf(term1, pred, term2)) - } - - override def visit(expr: OWLObjectInverseOf): List[TupleTableAtom] = { - val visitor = new RDFoxPropertyExprConverter(term1, term2, suffix + Inverse) - expr.getInverse.accept(visitor) - } - - def doDefault(expr: OWLPropertyExpression): List[TupleTableAtom] = List() - -} // class RDFoxPropertyExprConverter diff --git a/src/main/scala/rsacomb/RDFoxRuleShards.scala b/src/main/scala/rsacomb/RDFoxRuleShards.scala deleted file mode 100644 index b61cc38..0000000 --- a/src/main/scala/rsacomb/RDFoxRuleShards.scala +++ /dev/null @@ -1,5 +0,0 @@ -package rsacomb - -import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, BodyFormula} - -case class RDFoxRuleShards(res: List[TupleTableAtom], ext: List[BodyFormula]) diff --git a/src/main/scala/rsacomb/RSA.scala b/src/main/scala/rsacomb/RSA.scala deleted file mode 100644 index ab4f539..0000000 --- a/src/main/scala/rsacomb/RSA.scala +++ /dev/null @@ -1,100 +0,0 @@ -package rsacomb.util - -/* Java imports */ -import java.util.Map - -import tech.oxfordsemantic.jrdfox.formats.SPARQLParser -import tech.oxfordsemantic.jrdfox.Prefixes -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - TupleTableAtom, - TupleTableName, - Negation -} -import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable, IRI} -import org.semanticweb.owlapi.model.OWLOntology -import org.semanticweb.owlapi.model.{ - OWLAxiom, - OWLClass, - OWLObjectPropertyExpression -} - -import rsacomb.suffix.RSASuffix - -// Debug only -import scala.collection.JavaConverters._ - -object RSA { - - val Prefixes: Prefixes = new Prefixes() - Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") - - private def atom(name: IRI, vars: List[Term]) = - TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) - - def PE(t1: Term, t2: Term) = - TupleTableAtom.rdf(t1, RSA("PE"), t2) - - def U(t: Term) = - TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U")) - - def In(t: Term)(implicit set: Term) = - TupleTableAtom.rdf(t, RSA("In"), set) - - def notIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set)) - - def congruent(t1: Term, t2: Term) = - TupleTableAtom.rdf(t1, RSA("congruent"), t2) - - def QM(implicit variables: (List[Term], List[Term])) = { - val (answer, bounded) = variables - atom(RSA("QM"), answer ::: bounded) - } - - def ID(t1: Term, t2: Term)(implicit variables: (List[Term], List[Term])) = { - val (answer, bounded) = variables - atom(RSA("ID"), (answer ::: bounded) :+ t1 :+ t2) - } - - def Named(t: Term) = - TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("Named")) - - def Thing(t: Term) = - TupleTableAtom.rdf(t, IRI.RDF_TYPE, IRI.THING) - - def NI(t: Term) = - TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI")) - - def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit - variables: (List[Term], List[Term]) - ) = { - val (answer, bounded) = variables - atom(RSA("TQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) - } - - def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit - variables: (List[Term], List[Term]) - ) = { - val (answer, bounded) = variables - atom(RSA("AQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) - } - - def FK(implicit variables: (List[Term], List[Term])) = { - val (answer, bounded) = variables - atom(RSA("FK"), answer ::: bounded) - } - - def SP(implicit variables: (List[Term], List[Term])) = { - val (answer, bounded) = variables - atom(RSA("SP"), answer ::: bounded) - } - - def Ans(implicit variables: (List[Term], List[Term])) = { - val (answer, _) = variables - atom(RSA("Ans"), answer) - } - - def apply(name: Any): IRI = - IRI.create( - Prefixes.getPrefixIRIsByPrefixName.get("rsa:").getIRI + name.toString - ) -} diff --git a/src/main/scala/rsacomb/RSAAtom.scala b/src/main/scala/rsacomb/RSAAtom.scala deleted file mode 100644 index 8832226..0000000 --- a/src/main/scala/rsacomb/RSAAtom.scala +++ /dev/null @@ -1,88 +0,0 @@ -package rsacomb.implicits - -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, FunctionCall} -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - BindAtom, - TupleTableAtom, - TupleTableName -} -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} -import scala.collection.JavaConverters._ - -import rsacomb.suffix.{RSASuffix, Nth} -import rsacomb.RSAOntology - -/* Is this the best way to determine if an atom is an RDF triple? - * Note that we can't use `getNumberOfArguments()` because is not - * "consistent": - * - for an atom created with `rdf(, , )`, - * `getNumberOfArguments` returns 3 - * - for an atom created with `Atom.create(, , - * , )`, `getNumberOfArguments()` returns 3 - * - * This is probably because `Atom.rdf(...) is implemented as: - * ```scala - * def rdf(term1: Term, term2: Term, term3: Term): Atom = - * Atom.create(TupleTableName.create("internal:triple"), term1, term2, term3) - * ``` - */ - -trait RSAAtom { - - implicit class RSAAtom(val atom: TupleTableAtom) { - - import RDFox._ - - val name: String = atom.getTupleTableName.getName - - val isRDF: Boolean = name == "internal:triple" - - val isClassAssertion: Boolean = { - isRDF && { - val pred = atom.getArguments.get(1) - pred == IRI.RDF_TYPE - } - } - - val isRoleAssertion: Boolean = isRDF && !isClassAssertion - - def <<(suffix: RSASuffix): TupleTableAtom = - if (isRDF) { - val subj = atom.getArguments.get(0) - val pred = atom.getArguments.get(1) - val obj = atom.getArguments.get(2) - if (isClassAssertion) { - val obj1 = obj match { - case iri: IRI => IRI.create(iri.getIRI :: suffix) - case other => other - } - TupleTableAtom.rdf(subj, pred, obj1) - } else { - val pred1 = pred match { - case iri: IRI => IRI.create(iri.getIRI :: suffix) - case other => other - } - TupleTableAtom.rdf(subj, pred1, obj) - } - } else { - val ttname = TupleTableName.create(name :: suffix) - TupleTableAtom.create(ttname, atom.getArguments()) - } - - lazy val reified: (Option[BindAtom], List[TupleTableAtom]) = - if (isRDF) { - (None, List(atom)) - } else { - val bvar = RSAOntology.genFreshVariable() - val str = Literal.create(name, Datatype.XSD_STRING) - val args = atom.getArguments.asScala.toList - val skolem = FunctionCall.create("SKOLEM", str :: args: _*) - val bind = BindAtom.create(skolem, bvar) - val atoms = args.zipWithIndex - .map { case (t, i) => TupleTableAtom.rdf(bvar, name :: Nth(i), t) } - (Some(bind), atoms) - } - } - -} diff --git a/src/main/scala/rsacomb/RSAAxiom.scala b/src/main/scala/rsacomb/RSAAxiom.scala deleted file mode 100644 index 08de5b7..0000000 --- a/src/main/scala/rsacomb/RSAAxiom.scala +++ /dev/null @@ -1,155 +0,0 @@ -package rsacomb - -/* Java imports */ -import org.semanticweb.owlapi.model.{ - OWLAxiom, - OWLSubClassOfAxiom, - OWLEquivalentClassesAxiom -} -import org.semanticweb.owlapi.model.{ - OWLObjectPropertyExpression, - OWLSubObjectPropertyOfAxiom, - OWLClass, - OWLClassExpression, - OWLObjectSomeValuesFrom, - OWLObjectMaxCardinality -} -import org.semanticweb.owlapi.model.ClassExpressionType -import org.semanticweb.owlapi.model.{ - OWLAxiomVisitorEx, - OWLClassExpressionVisitorEx -} -import org.semanticweb.owlapi.model.OWLObjectProperty -import scala.collection.JavaConverters._ - -/* Wrapper trait for the implicit class `RSAAxiom`. - */ -trait RSAAxiom { - - /* Identifies some of the axiom types in a Horn-ALCHOIQ ontology - * in normal form. Refer to the paper for more details on the - * chosen names. - */ - private sealed trait RSAAxiomType - private object RSAAxiomType { - case object T3 extends RSAAxiomType // ∃R.A ⊑ B - case object T3top extends RSAAxiomType // ∃R.⊤ ⊑ B - case object T4 extends RSAAxiomType // A ⊑ ≤1R.B - case object T5 extends RSAAxiomType // A ⊑ ∃R.B - } - - object RSAAxiom { - - def hashed( - cls1: OWLClass, - prop: OWLObjectPropertyExpression, - cls2: OWLClass - ): String = - (cls1, prop, cls2).hashCode.toString - - } - - /* Implements additional features on top of `OWLAxiom` from - * the OWLAPI. - */ - implicit class RSAAxiom(axiom: OWLAxiom) { - - /* Detecting axiom types: - * - * In order to reason about role unsafety in Horn-ALCHOIQ - * ontologies we need to detect and filter axioms by their - * "type". - * - * This is a simple implementation following the Visitor - * pattern imposed by the OWLAPI. - */ - private class RSAAxiomTypeDetector(t: RSAAxiomType) - extends OWLAxiomVisitorEx[Boolean] { - override def visit(axiom: OWLSubClassOfAxiom): Boolean = { - val sub = axiom.getSubClass().getClassExpressionType() - val sup = axiom.getSuperClass().getClassExpressionType() - t match { - case RSAAxiomType.T3top => // ∃R.⊤ ⊑ B - axiom.isT3 && axiom - .getSubClass() - .asInstanceOf[OWLObjectSomeValuesFrom] - .getFiller - .isOWLThing - case RSAAxiomType.T3 => // ∃R.A ⊑ B - sub == ClassExpressionType.OBJECT_SOME_VALUES_FROM && sup == ClassExpressionType.OWL_CLASS - case RSAAxiomType.T4 => // A ⊑ ≤1R.B - sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_MAX_CARDINALITY - case RSAAxiomType.T5 => // A ⊑ ∃R.B - sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_SOME_VALUES_FROM - } - } - - override def visit(axiom: OWLEquivalentClassesAxiom): Boolean = { - // TODO - false - } - - def doDefault(axiom: OWLAxiom): Boolean = false - } - - private def isOfType(t: RSAAxiomType): Boolean = { - val visitor = new RSAAxiomTypeDetector(t) - axiom.accept(visitor) - } - - /* Exposed methods */ - def isT3top: Boolean = isOfType(RSAAxiomType.T3top) - def isT3: Boolean = isOfType(RSAAxiomType.T3) - def isT4: Boolean = isOfType(RSAAxiomType.T4) - def isT5: Boolean = isOfType(RSAAxiomType.T5) - - /* Extracting ObjectPropertyExpressions from axioms - * - * This extracts all ObjectPropertyExpressions from a given - * axiom. While the implementation is generic we use it on axioms - * of specific types (see above). - * - * NOTE: it is not possible to use the `objectPropertyInSignature` - * method of `OWLAxiom` because it returns all "role names" involved - * in the signature of an axiom. In particular we won't get the inverse - * of a role if this appears in the axiom (but we will get the role - * itself instead). - */ - lazy val objectPropertyExpressionsInSignature - : List[OWLObjectPropertyExpression] = - axiom match { - case a: OWLSubClassOfAxiom => - rolesInExpr(a.getSubClass) ++ rolesInExpr(a.getSuperClass) - case a: OWLEquivalentClassesAxiom => - a.getClassExpressions.asScala.toList.flatMap(rolesInExpr(_)) - case a: OWLSubObjectPropertyOfAxiom => - List(a.getSubProperty, a.getSuperProperty) - case _ => List() - } - - private def rolesInExpr( - expr: OWLClassExpression - ): List[OWLObjectPropertyExpression] = - expr match { - case e: OWLObjectSomeValuesFrom => List(e.getProperty) - case e: OWLObjectMaxCardinality => List(e.getProperty) - case _ => List() - } - - lazy val toTriple: Option[(OWLClass, OWLObjectProperty, OWLClass)] = - for { - subClass <- Some(axiom) collect { case a: OWLSubClassOfAxiom => a } - cls1 <- Some(subClass.getSubClass) collect { case a: OWLClass => a } - someValues <- Some(subClass.getSuperClass) collect { - case a: OWLObjectSomeValuesFrom => a - } - prop <- Some(someValues.getProperty) collect { - case a: OWLObjectProperty => a - } - cls2 <- Some(someValues.getFiller) collect { case a: OWLClass => a } - } yield (cls1, prop, cls2) - - lazy val hashed: String = (RSAAxiom.hashed _) tupled toTriple.get - } - -} // trait RSAAxiom diff --git a/src/main/scala/rsacomb/RSAOntology.scala b/src/main/scala/rsacomb/RSAOntology.scala deleted file mode 100644 index 53bc560..0000000 --- a/src/main/scala/rsacomb/RSAOntology.scala +++ /dev/null @@ -1,378 +0,0 @@ -package rsacomb - -/* Java imports */ -import java.util.HashMap -import java.util.stream.{Collectors, Stream} -import java.io.File -import org.semanticweb.owlapi.apibinding.OWLManager - -import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} -import org.semanticweb.owlapi.model.{ - OWLClass, - OWLObjectProperty, - OWLSubObjectPropertyOfAxiom, - OWLObjectPropertyExpression, - OWLObjectSomeValuesFrom, - OWLSubClassOfAxiom -} -import org.semanticweb.owlapi.model.parameters.Imports -import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory -import org.semanticweb.owlapi.model.{IRI => OWLIRI} -import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl - -import tech.oxfordsemantic.jrdfox.client.{UpdateType, DataStoreConnection} -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - Rule, - TupleTableAtom, - Negation, - BodyFormula -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - Term, - Variable, - IRI, - Resource -} -import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery - -/* Scala imports */ -import scala.collection.JavaConverters._ -import scala.collection.mutable.Set -import scalax.collection.immutable.Graph -import scalax.collection.GraphEdge.UnDiEdge - -/* Debug only */ -import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer -import tech.oxfordsemantic.jrdfox.logic._ -import org.semanticweb.owlapi.model.OWLObjectInverseOf - -import suffix.{Empty, Forward, Backward, Inverse} -import util.{RDFoxHelpers, RSA} - -object RSAOntology { - - // Counter used to implement a simple fresh variable generator - private var counter = -1; - - def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) - - def apply(ontology: File): RSAOntology = - new RSAOntology(loadOntology(ontology)) - - def genFreshVariable(): Variable = { - counter += 1 - Variable.create(f"I$counter%03d") - } - - private def loadOntology(onto: File): OWLOntology = { - val manager = OWLManager.createOWLOntologyManager() - manager.loadOntologyFromOntologyDocument(onto) - } -} - -class RSAOntology(val ontology: OWLOntology) extends RSAAxiom { - - // Gather TBox/RBox/ABox from original ontology - val tbox: List[OWLAxiom] = - ontology - .tboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .asScala - .toList - - val rbox: List[OWLAxiom] = - ontology - .rboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .asScala - .toList - - val abox: List[OWLAxiom] = - ontology - .aboxAxioms(Imports.INCLUDED) - .collect(Collectors.toList()) - .asScala - .toList - - val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox - - /* Retrieve individuals in the original ontology - */ - val individuals: List[IRI] = - ontology - .getIndividualsInSignature() - .asScala - .map(_.getIRI) - .map(implicits.RDFox.owlapiToRdfoxIri) - .toList - - val concepts: List[OWLClass] = - ontology.getClassesInSignature().asScala.toList - - val roles: List[OWLObjectPropertyExpression] = - axioms - .flatMap(_.objectPropertyExpressionsInSignature) - .distinct - - // OWLAPI reasoner for same easier tasks - private val reasoner = - (new StructuralReasonerFactory()).createReasoner(ontology) - - /* Steps for RSA check - * 1) convert ontology axioms into LP rules - * 2) call RDFox on the onto and compute materialization - * 3) build graph from E(x,y) facts - * 4) check if the graph is tree-like - * ideally this annotates the graph with info about the reasons - * why the ontology might not be RSA. This could help a second - * step of approximation of an Horn-ALCHOIQ to RSA - */ - lazy val isRSA: Boolean = { - - val unsafe = this.unsafeRoles - - /* DEBUG: print rules in DL syntax and unsafe roles */ - //val renderer = new DLSyntaxObjectRenderer() - //println("\nDL rules:") - //axioms.foreach(x => println(renderer.render(x))) - //println("\nUnsafe roles:") - //println(unsafe) - - /* Ontology convertion into LP rules */ - val datalog = for { - axiom <- axioms - visitor = new RDFoxAxiomConverter( - RSAOntology.genFreshVariable(), - unsafe, - SkolemStrategy.ConstantRSA(axiom.toString), - Empty - ) - rule <- axiom.accept(visitor) - } yield rule - - /* DEBUG: print datalog rules */ - //println("\nDatalog roles:") - //datalog.foreach(println) - - // Open connection with RDFox - val (server, data) = RDFoxHelpers.openConnection("RSACheck") - // Add Data (hardcoded for now) - //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") - - /* Add built-in rules - */ - data.importData( - UpdateType.ADDITION, - RSA.Prefixes, - "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." - ) - - /* Add built-in rules - */ - // data.importData( - // UpdateType.ADDITION, - // RSA.Prefixes, - // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." - // ) - - /* Add ontology rules - */ - data.addRules(datalog.asJava) - - /* Build graph - */ - val graph = this.rsaGraph(data); - //println(graph) - - // Close connection to RDFox - RDFoxHelpers.closeConnection(server, data) - - /* To check if the graph is tree-like we check for acyclicity in a - * undirected graph. - * - * TODO: Implement additional checks (taking into account equality) - */ - graph.isAcyclic - } - - lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { - - /* DEBUG: print rules in DL syntax */ - //val renderer = new DLSyntaxObjectRenderer() - - /* Checking for (1) unsafety condition: - * - * For all roles r1 appearing in an axiom of type T5, r1 is unsafe - * if there exists a role r2 (different from top) appearing in an axiom - * of type T3 and r1 is a subproperty of the inverse of r2. - */ - val unsafe1 = for { - axiom <- tbox - if axiom.isT5 - role1 <- axiom.objectPropertyExpressionsInSignature - roleSuper = - role1 +: reasoner - .superObjectProperties(role1) - .collect(Collectors.toList()) - .asScala - roleSuperInv = roleSuper.map(_.getInverseProperty) - axiom <- tbox - if axiom.isT3 && !axiom.isT3top - role2 <- axiom.objectPropertyExpressionsInSignature - if roleSuperInv.contains(role2) - } yield role1 - - /* Checking for (2) unsafety condition: - * - * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if - * there exists a role p2 appearing in an axiom of type T4 and p1 is a - * subproperty of either p2 or the inverse of p2. - * - */ - val unsafe2 = for { - axiom <- tbox - if axiom.isT5 - role1 <- axiom.objectPropertyExpressionsInSignature - roleSuper = - role1 +: reasoner - .superObjectProperties(role1) - .collect(Collectors.toList()) - .asScala - roleSuperInv = roleSuper.map(_.getInverseProperty) - axiom <- tbox - if axiom.isT4 - role2 <- axiom.objectPropertyExpressionsInSignature - if roleSuper.contains(role2) || roleSuperInv.contains(role2) - } yield role1 - - (unsafe1 ++ unsafe2).toList - } - - private def rsaGraph( - data: DataStoreConnection - ): Graph[Resource, UnDiEdge] = { - val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" - val answers = RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes) - var edges: List[UnDiEdge[Resource]] = answers.map { - case n1 :: n2 :: _ => UnDiEdge(n1, n2) - } - Graph(edges: _*) - } - - def filteringProgram( - query: SelectQuery, - nis: List[Term] - ): FilteringProgram = - new FilteringProgram(query, nis) - - lazy val canonicalModel = new CanonicalModel(this) - - // TODO: the following functions needs testing - def confl( - role: OWLObjectPropertyExpression - ): Set[OWLObjectPropertyExpression] = { - - val invSuperRoles = reasoner - .superObjectProperties(role) - .collect(Collectors.toSet()) - .asScala - .addOne(role) - .map(_.getInverseProperty) - - invSuperRoles - .flatMap(x => - reasoner - .subObjectProperties(x) - .collect(Collectors.toSet()) - .asScala - .addOne(x) - ) - .filterNot(_.isOWLBottomObjectProperty()) - .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) - } - - def self(axiom: OWLSubClassOfAxiom): Set[Term] = { - // Assuming just one role in the signature of a T5 axiom - val role = axiom.objectPropertyExpressionsInSignature(0) - if (this.confl(role).contains(role)) { - Set( - RSA("v0_" ++ axiom.hashed), - RSA("v1_" ++ axiom.hashed) - ) - } else { - Set() - } - } - - // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { - // // Assuming just one role in the signature of a T5 axiom - // val roleR = axiom.objectPropertyExpressionsInSignature(0) - // val conflR = this.confl(roleR) - // // We just need the TBox to find - // val tbox = ontology - // .tboxAxioms(Imports.INCLUDED) - // .collect(Collectors.toSet()) - // .asScala - // for { - // axiom1 <- tbox - // // TODO: is this an optimization or an error? - // if axiom1.isT5 - // // We expect only one role coming out of a T5 axiom - // roleS <- axiom1.objectPropertyExpressionsInSignature - // // Triples ordering is among triples involving safe roles. - // if !unsafeRoles.contains(roleS) - // if conflR.contains(roleS) - // individual = - // if (axiom.hashCode < axiom1.hashCode) { - // RSA.rsa("v0_" ++ axiom1.hashCode.toString()) - // } else { - // RSA.rsa("v1_" ++ axiom1.hashCode.toString()) - // } - // } yield individual - // } - - def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { - // TODO: we can actually use `toTriple` from `RSAAxiom` - val classes = - axiom.classesInSignature.collect(Collectors.toList()).asScala - val classA = classes(0) - val roleR = axiom - .objectPropertyExpressionsInSignature(0) - .asInstanceOf[OWLObjectProperty] - val classB = classes(1) - cycle_aux(classA, roleR, classB) - } - - def cycle_aux( - classA: OWLClass, - roleR: OWLObjectProperty, - classB: OWLClass - ): Set[Term] = { - val conflR = this.confl(roleR) - val classes = ontology - .classesInSignature(Imports.INCLUDED) - .collect(Collectors.toSet()) - .asScala - for { - classD <- classes - roleS <- conflR - classC <- classes - // Keeping this check for now - if !unsafeRoles.contains(roleS) - tripleARB = RSAAxiom.hashed(classA, roleR, classB) - tripleDSC = RSAAxiom.hashed(classD, roleS, classC) - individual = - if (tripleARB > tripleDSC) { - RSA("v1_" ++ tripleDSC) - } else { - // Note that this is also the case for - // `tripleARB == tripleDSC` - RSA("v0_" ++ tripleDSC) - } - } yield individual - } - - def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = - this.self(axiom) | this.cycle(axiom) - -} // implicit class RSAOntology diff --git a/src/main/scala/rsacomb/RSASuffix.scala b/src/main/scala/rsacomb/RSASuffix.scala deleted file mode 100644 index ce36b10..0000000 --- a/src/main/scala/rsacomb/RSASuffix.scala +++ /dev/null @@ -1,31 +0,0 @@ -package rsacomb.suffix - -import org.semanticweb.owlapi.model.{ - OWLPropertyExpression, - OWLObjectInverseOf, - OWLObjectProperty -} - -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} -import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, TupleTableName} - -object RSASuffix { - - def apply(suffix: String => String): RSASuffix = new RSASuffix(suffix) - -} - -class RSASuffix(val suffix: String => String) { - - def +(that: RSASuffix): RSASuffix = - new RSASuffix(this.suffix andThen that.suffix) - - def ::(str: String): String = this suffix str - -} - -case object Empty extends RSASuffix(identity) -case object Forward extends RSASuffix((s) => s"${s}_f") -case object Backward extends RSASuffix((s) => s"${s}_b") -case object Inverse extends RSASuffix((s) => s"${s}_inv") -case class Nth(n: Int) extends RSASuffix((s) => s"${s}_$n") diff --git a/src/main/scala/rsacomb/SkolemStrategy.scala b/src/main/scala/rsacomb/SkolemStrategy.scala deleted file mode 100644 index a2ad0a1..0000000 --- a/src/main/scala/rsacomb/SkolemStrategy.scala +++ /dev/null @@ -1,78 +0,0 @@ -package rsacomb - -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, IRI} - -sealed trait SkolemStrategy - -object SkolemStrategy { - // TODO: might want to use something else other than `hashCode` as a - // function to generate a fresh function/constant - - /* No skolemization at all. - * - * From - * ∃R.A ⊑ B - * to - * R(x,y), B(y) -> B(x) - */ - case object None extends SkolemStrategy - - /* Functional skolemization - * - * From - * A ⊑ ∃R.B - * to - * A(x) -> R(x,f(x)), B(f(x)) - * for f, fresh function associated with the input axiom - * - * In RDFox this can represented combining the BIND operator with the - * SKOLEM operator as such: - * A(x), BIND(y, SKOLEM("f", x)) -> R(x,y), B(y) - * The first argument of a SKOLEM call is a literal string (ideally - * identifing the simulated function name). - * - * NOTE: this requirement for the SKOLEM operator is not enforced by - * RDFox, that will fail silently if omitted. - */ - case class Standard(func: Literal) extends SkolemStrategy - object Standard { - def apply(axiom: String) = - new Standard( - Literal.create(genFunctionString(axiom), Datatype.XSD_STRING) - ) - def genFunctionString(str: String) = "f_" ++ str.hashCode.toString - } - - /* Constant skolemization - * - * From - * A ⊑ ∃R.B - * to - * A(y) -> R(x,c), B(c) - * for c, fresh constant associated with the input axiom - */ - case class Constant(const: IRI) extends SkolemStrategy - object Constant { - def apply(axiom: String) = - new Constant(IRI.create(genConstantString(axiom))) - def genConstantString(str: String) = "c_" ++ str.hashCode.toString - } - - /* (RSA) Constant skolemization - * This is a special skolemization option to introduce additional atoms for RSA - * checking algorithm. - * - * From - * A ⊑ ∃R.B - * to - * A(y) -> R(x,c), PE(x,c), B(c) - * for c, fresh constant associated with the input axiom and PE an internal predicate. - */ - case class ConstantRSA(const: IRI) extends SkolemStrategy - object ConstantRSA { - def apply(axiom: String) = - new ConstantRSA(IRI.create(genConstantString(axiom))) - def genConstantString(str: String) = "c_" ++ str.hashCode.toString - } -} diff --git a/src/main/scala/rsacomb/implicits/JavaCollections.scala b/src/main/scala/rsacomb/implicits/JavaCollections.scala deleted file mode 100644 index 69e825b..0000000 --- a/src/main/scala/rsacomb/implicits/JavaCollections.scala +++ /dev/null @@ -1,13 +0,0 @@ -package rsacomb.implicits - -import scala.collection.JavaConverters._ - -object JavaCollections { - - implicit def javaToScalaList[A](list: java.util.List[A]): List[A] = - list.asScala.toList - - implicit def scalaToJavaList[A](list: List[A]): java.util.List[A] = - list.asJava - -} diff --git a/src/main/scala/rsacomb/implicits/RDFox.scala b/src/main/scala/rsacomb/implicits/RDFox.scala deleted file mode 100644 index 44b7c01..0000000 --- a/src/main/scala/rsacomb/implicits/RDFox.scala +++ /dev/null @@ -1,20 +0,0 @@ -package rsacomb.implicits - -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFoxIRI} -import org.semanticweb.owlapi.model.{IRI => OWLIRI} - -object RDFox { - - implicit def rdfoxToOwlapiIri(iri: RDFoxIRI): OWLIRI = { - OWLIRI.create(iri.getIRI) - } - - implicit def owlapiToRdfoxIri(iri: OWLIRI): RDFoxIRI = { - RDFoxIRI.create(iri.getIRIString()) - } - - implicit def stringToRdfoxIri(iri: String): RDFoxIRI = { - RDFoxIRI.create(iri) - } - -} diff --git a/src/main/scala/rsacomb/util/RDFoxHelpers.scala b/src/main/scala/rsacomb/util/RDFoxHelpers.scala deleted file mode 100644 index 9856e27..0000000 --- a/src/main/scala/rsacomb/util/RDFoxHelpers.scala +++ /dev/null @@ -1,100 +0,0 @@ -package rsacomb.util - -import java.util.{Map => JMap, HashMap => JHashMap} -import java.io.StringReader -import tech.oxfordsemantic.jrdfox.Prefixes -import tech.oxfordsemantic.jrdfox.client.{ - ConnectionFactory, - ServerConnection, - DataStoreConnection -} -import tech.oxfordsemantic.jrdfox.formats.SPARQLParser -import tech.oxfordsemantic.jrdfox.logic.expression.Resource -import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery - -import rsacomb.suffix.Nth - -object RDFoxHelpers { - - def openConnection( - dataStore: String, - opts: JMap[String, String] = new JHashMap[String, String]() - ): (ServerConnection, DataStoreConnection) = { - /* Create local server connection - */ - val serverUrl = "rdfox:local" - val role = "" - val password = "" - val server = - ConnectionFactory.newServerConnection(serverUrl, role, password) - - /* Create datastore connection - */ - // parameters.put("owl-in-rdf-support", "relaxed") - // parameters.put("equality", "noUNA") - server.createDataStore(dataStore, "par-complex-nn", opts) - val data = server.newDataStoreConnection(dataStore) - - (server, data) - } - - def parseSelectQuery( - query: String, - prefixes: Prefixes = new Prefixes() - ): Option[SelectQuery] = { - val parser = new SPARQLParser( - prefixes, - new StringReader(query) - ) - parser.parseSingleQuery() match { - case q: SelectQuery => Some(q) - case _ => None - } - } - - def submitSelectQuery( - data: DataStoreConnection, - query: String, - prefixes: Prefixes = new Prefixes(), - opts: JMap[String, String] = new JHashMap[String, String]() - ): List[List[Resource]] = { - val cursor = data.createCursor(prefixes, query, opts) - var answers: List[List[Resource]] = List() - var mul = cursor.open() - while (mul > 0) { - val answer = - (0 until cursor.getArity).map(cursor.getResource(_)).toList - answers = answer :: answers - mul = cursor.advance() - } - cursor.close(); - answers - } - - def queryInternalPredicate( - data: DataStoreConnection, - pred: String, - arity: Int, - opts: JMap[String, String] = new JHashMap[String, String]() - ): List[List[Resource]] = { - var query = "SELECT" - for (i <- 0 until arity) { - query ++= s" ?X$i" - } - query ++= " WHERE {" - for (i <- 0 until arity) { - query ++= s" ?S rsa:${pred :: Nth(i)} ?X$i ." - } - query ++= " }" - submitSelectQuery(data, query, RSA.Prefixes, opts) - } - - def closeConnection( - server: ServerConnection, - data: DataStoreConnection - ): Unit = { - server.close(); - data.close(); - } - -} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala new file mode 100644 index 0000000..f0f1bf8 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala @@ -0,0 +1,284 @@ +package uk.ac.ox.cs.rsacomb + +import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} +import org.semanticweb.owlapi.model.{ + OWLClass, + // OWLObjectProperty, + OWLSubObjectPropertyOfAxiom, + // OWLObjectPropertyExpression, + OWLObjectSomeValuesFrom, + OWLSubClassOfAxiom +} + +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + Rule, + BodyFormula, + TupleTableAtom, + Negation +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + Term, + Variable, + // Resource, + IRI +} + +import uk.ac.ox.cs.rsacomb.converter.{ + SkolemStrategy, + RDFoxAxiomConverter, + RDFoxPropertyExprConverter +} +import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom +import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} +import uk.ac.ox.cs.rsacomb.util.RSA + +class CanonicalModel(val ontology: RSAOntology) extends RSAAxiom { + + import implicits.RDFox._ + import implicits.JavaCollections._ + + val named: List[Rule] = + ontology.individuals.map(a => Rule.create(RSA.Named(a))) + + val rolesAdditionalRules: List[Rule] = { + // Given a role (predicate) compute additional logic rules + def additional(pred: String): Seq[Rule] = { + val varX = Variable.create("X") + val varY = Variable.create("Y") + for ( + (hSuffix, bSuffix) <- List( + (Empty, Forward), + (Empty, Backward), + (Inverse, Forward + Inverse), + (Inverse, Backward + Inverse), + (Backward + Inverse, Forward), + (Forward + Inverse, Backward), + (Backward, Forward + Inverse), + (Forward, Backward + Inverse) + ) + ) + yield Rule.create( + TupleTableAtom.rdf(varX, pred :: hSuffix, varY), + TupleTableAtom.rdf(varX, pred :: bSuffix, varY) + ) + } + // Compute additional rules per role + ontology.roles + .collect { case prop: OWLObjectProperty => prop } + .map(_.getIRI.getIRIString) + .flatMap(additional) + } + + private lazy val topAxioms: List[Rule] = { + val varX = Variable.create("X") + val varY = Variable.create("Y") + val concepts = ontology.concepts.map(c => { + Rule.create( + RSA.Thing(varX), + TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI) + ) + }) + val roles = ontology.roles.map(r => { + val name = r match { + case x: OWLObjectProperty => x.getIRI.getIRIString + case x: OWLObjectInverseOf => + x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse + } + Rule.create( + List(RSA.Thing(varX), RSA.Thing(varY)), + List(TupleTableAtom.rdf(varX, name, varY)) + ) + }) + concepts ::: roles + } + + private val equalityAxioms: List[Rule] = { + val varX = Variable.create("X") + val varY = Variable.create("Y") + val varZ = Variable.create("Z") + List( + // Reflexivity + Rule.create(RSA.congruent(varX, varX), RSA.Thing(varX)), + // Simmetry + Rule.create(RSA.congruent(varY, varX), RSA.congruent(varX, varY)), + // Transitivity + Rule.create( + RSA.congruent(varX, varZ), + RSA.congruent(varX, varY), + RSA.congruent(varY, varZ) + ) + ) + } + + val rules: List[Rule] = { + // Compute rules from ontology axioms + val rules = ontology.axioms.flatMap(_.accept(RuleGenerator)) + // Return full set of rules + rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: named + } + + object RuleGenerator + extends RDFoxAxiomConverter( + Variable.create("X"), + ontology.unsafeRoles, + SkolemStrategy.None, + Empty + ) { + + private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { + val unfold = ontology.unfold(axiom).toList + // Fresh Variables + val v0 = RSA("v0_" ++ axiom.hashed) + val varX = Variable.create("X") + implicit val unfoldTerm = RSA(unfold.hashCode.toString) + // TODO: use axiom.toTriple instead + val atomA: TupleTableAtom = { + val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI + TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) + } + val roleRf: TupleTableAtom = { + val visitor = + new RDFoxPropertyExprConverter(varX, v0, Forward) + axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getProperty + .accept(visitor) + .head + } + val atomB: TupleTableAtom = { + val cls = axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getFiller + .asInstanceOf[OWLClass] + .getIRI + TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) + } + // TODO: To be consistent with the specifics of the visitor we are + // returning facts as `Rule`s with true body. While this is correct + // there is an easier way to import facts into RDFox. Are we able to + // do that? + val facts = unfold.map(x => Rule.create(RSA.In(x))) + val rules = List( + Rule.create(roleRf, atomA, RSA.notIn(varX)), + Rule.create(atomB, atomA, RSA.notIn(varX)) + ) + facts ++ rules + } + + private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { + val roleR = + axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getProperty + if (ontology.confl(roleR) contains roleR) { + // Fresh Variables + val v0 = RSA("v0_" ++ axiom.hashed) + val v1 = RSA("v1_" ++ axiom.hashed) + val v2 = RSA("v2_" ++ axiom.hashed) + // Predicates + def atomA(t: Term): TupleTableAtom = { + val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI + TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + } + def roleRf(t1: Term, t2: Term): TupleTableAtom = { + val visitor = + new RDFoxPropertyExprConverter(t1, t2, Forward) + roleR.accept(visitor).head + } + def atomB(t: Term): TupleTableAtom = { + val cls = axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getFiller + .asInstanceOf[OWLClass] + .getIRI + TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + } + //Rules + List( + Rule.create(roleRf(v0, v1), atomA(v0)), + Rule.create(atomB(v1), atomA(v0)), + Rule.create(roleRf(v1, v2), atomA(v1)), + Rule.create(atomB(v2), atomA(v1)) + ) + } else { + List() + } + } + + private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { + val cycle = ontology.cycle(axiom).toList + val roleR = + axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getProperty + // Fresh Variables + val v1 = RSA("v1_" ++ axiom.hashed) + // Predicates + def atomA(t: Term): TupleTableAtom = { + val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI + TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) + } + def roleRf(t: Term): TupleTableAtom = { + val visitor = + new RDFoxPropertyExprConverter(t, v1, Forward) + roleR.accept(visitor).head + } + val atomB: TupleTableAtom = { + val cls = axiom.getSuperClass + .asInstanceOf[OWLObjectSomeValuesFrom] + .getFiller + .asInstanceOf[OWLClass] + .getIRI + TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) + } + cycle.flatMap { x => + List( + Rule.create(roleRf(x), atomA(x)), + Rule.create(atomB, atomA(x)) + ) + } + } + + override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { + if (axiom.isT5) { + // TODO: get role in T5 axiom + // Assuming one role here + val role = axiom.objectPropertyExpressionsInSignature(0) + if (ontology.unsafeRoles contains role) { + val visitor = + new RDFoxAxiomConverter( + Variable.create("X"), + ontology.unsafeRoles, + SkolemStrategy.Standard(axiom.toString), + Forward + ) + axiom.accept(visitor) + } else { + rules1(axiom) ::: rules2(axiom) ::: rules3(axiom) + } + } else { + // Fallback to standard OWL to LP translation + super.visit(axiom) + } + } + + override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { + val varX = Variable.create("X") + val visitorF = new RDFoxAxiomConverter( + varX, + ontology.unsafeRoles, + SkolemStrategy.None, + Forward + ) + val visitorB = new RDFoxAxiomConverter( + varX, + ontology.unsafeRoles, + SkolemStrategy.None, + Backward + ) + axiom.accept(visitorB) ::: axiom.accept(visitorF) + } + + } + +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala new file mode 100644 index 0000000..b154575 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala @@ -0,0 +1,366 @@ +package uk.ac.ox.cs.rsacomb + +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.expression.{ + Term, + IRI, + Variable, + Literal, + FunctionCall +} +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + Rule, + TupleTableAtom, + BindAtom, + TupleTableName, + Atom, + BodyFormula, + Negation +} +import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{SelectQuery} +import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ + GroupGraphPattern, + ConjunctionPattern, + TriplePattern, + QueryPattern +} + +import scala.collection.JavaConverters._ + +import uk.ac.ox.cs.rsacomb.implicits.RSAAtom +import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} +import uk.ac.ox.cs.rsacomb.util.RSA + +class FilteringProgram(query: SelectQuery, constants: List[Term]) + extends RSAAtom { + + /* Makes mplicit conversion OWLAPI IRI <-> RDFox IRI available */ + // import implicits.RDFox._ + + implicit val variables: (List[Term], List[Term]) = { + val all: Set[Variable] = query.getQueryBody.getWherePattern match { + case b: ConjunctionPattern => { + b.getConjuncts.asScala.toSet.flatMap { conj: QueryPattern => + conj match { + case c: TriplePattern => + Set(c.getSubject, c.getPredicate, c.getObject).collect { + case v: Variable => v + } + case _ => Set() + } + } + } + case _ => Set() + } + if (query.getAllPossibleVariables) { + (all.toList, List()) + } else { + val answer = query.getSelection.asScala.map(_.getVariable).toSet + (answer.toList, (all &~ answer).toList) + } + } + + val (answer, bounded) = variables + + val facts: List[Rule] = constants.map(c => Rule.create(RSA.NI(c))) + val rules: List[Rule] = + this.generateFilteringProgram().map(reifyRule) ++ facts + + /* NOTE: we are restricting to queries that contain conjunctions of + * atoms for the time being. This might need to be reviewed in the + * future. + */ + private def queryToBody(body: GroupGraphPattern): List[TupleTableAtom] = + body match { + case b: ConjunctionPattern => { + val conjuncts = b.getConjuncts.asScala.toList + conjuncts flatMap { conj => + conj match { + case c: TriplePattern => + List( + TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject) + ) + case _ => List() + } + } + } + case _ => List() + } + + private def generateFilteringProgram(): List[Rule] = { + // General purpose variables + val varU = Variable.create("U") + val varV = Variable.create("V") + val varW = Variable.create("W") + // Query formula as a rule body + val body = queryToBody(query.getQueryBody.getWherePattern) + // Auxiliar predicates/helpers + def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) + // val predQM = + // TupleTableAtom.create( + // TupleTableName.create(RSA.Qm.getIRI), + // (answer ++ bounded): _* + // ) + // def predID(t1: Term, t2: Term) = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("ID").getIRI), + // (answer ++ bounded).appended(t1).appended(t2): _* + // ) + // def predNAMED(t1: Term): TupleTableAtom = + // TupleTableAtom.rdf(t1, IRI.RDF_TYPE, RSA.rsa("NAMED")) + // def predTQ(t1: Term, t2: Term, sx: RSASuffix) = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("TQ" :: sx).getIRI), + // (answer ++ bounded).appended(t1).appended(t2): _* + // ) + // def predAQ(t1: Term, t2: Term, sx: RSASuffix) = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("AQ" :: sx).getIRI), + // (answer ++ bounded).appended(t1).appended(t2): _* + // ) + // val predFK = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("FK").getIRI), + // (answer ++ bounded): _* + // ) + // val predSP = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("SP").getIRI), + // (answer ++ bounded): _* + // ) + // val predANS = + // TupleTableAtom.create( + // TupleTableName.create(RSA.rsa("ANS").getIRI), + // answer: _* + // ) + + /* Rule 1 */ + val r1 = Rule.create(RSA.QM, body: _*) + + /* Rules 3x */ + val r3a = + for ((v, i) <- bounded.zipWithIndex) + yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v))) + val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV)) + val r3c = + Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW)) + + /* Rules 4x */ + val r4a = for { + role1 <- body.filter(_.isRoleAssertion) + if bounded contains (role1.getArguments.get(2)) + role2 <- body.filter(_.isRoleAssertion) + if bounded contains (role2.getArguments.get(2)) + } yield Rule.create( + RSA.FK, + role1 << Forward, + role2 << Forward, + RSA.ID( + RSA(bounded.indexOf(role1.getArguments.get(2))), + RSA(bounded.indexOf(role2.getArguments.get(2))) + ), + not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(0))) + ) + val r4b = for { + role1 <- body.filter(_.isRoleAssertion) + if bounded contains (role1.getArguments.get(2)) + role2 <- body.filter(_.isRoleAssertion) + if bounded contains (role2.getArguments.get(0)) + } yield Rule.create( + RSA.FK, + role1 << Forward, + role2 << Backward, + RSA.ID( + RSA(bounded.indexOf(role1.getArguments.get(2))), + RSA(bounded.indexOf(role2.getArguments.get(0))) + ), + not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(2))) + ) + val r4c = for { + role1 <- body.filter(_.isRoleAssertion) + if bounded contains (role1.getArguments.get(0)) + role2 <- body.filter(_.isRoleAssertion) + if bounded contains (role2.getArguments.get(0)) + } yield Rule.create( + RSA.FK, + role1 << Backward, + role2 << Backward, + RSA.ID( + RSA(bounded.indexOf(role1.getArguments.get(0))), + RSA(bounded.indexOf(role2.getArguments.get(0))) + ), + not(RSA.congruent(role1.getArguments.get(2), role2.getArguments.get(2))) + ) + + /* Rules 5x */ + val r5a = for { + role1 <- body.filter(_.isRoleAssertion) + role1arg0 = role1.getArguments.get(0) + role1arg2 = role1.getArguments.get(2) + if bounded contains role1arg0 + if bounded contains role1arg2 + role2 <- body.filter(_.isRoleAssertion) + role2arg0 = role2.getArguments.get(0) + role2arg2 = role2.getArguments.get(2) + if bounded contains role2arg0 + if bounded contains role2arg2 + } yield Rule.create( + RSA.ID( + RSA(bounded indexOf role1arg0), + RSA(bounded indexOf role2arg0) + ), + role1 << Forward, + role2 << Forward, + RSA.ID( + RSA(bounded indexOf role1arg2), + RSA(bounded indexOf role2arg2) + ), + RSA.congruent(role1arg0, role2arg0), + not(RSA.NI(role1arg0)) + ) + val r5b = for { + role1 <- body.filter(_.isRoleAssertion) + role1arg0 = role1.getArguments.get(0) + role1arg2 = role1.getArguments.get(2) + if bounded contains role1arg0 + if bounded contains role1arg2 + role2 <- body.filter(_.isRoleAssertion) + role2arg0 = role2.getArguments.get(0) + role2arg2 = role2.getArguments.get(2) + if bounded contains role2arg0 + if bounded contains role2arg2 + } yield Rule.create( + RSA.ID( + RSA(bounded indexOf role1arg0), + RSA(bounded indexOf role2arg2) + ), + role1 << Forward, + role2 << Backward, + RSA.ID( + RSA(bounded indexOf role1arg2), + RSA(bounded indexOf role2arg0) + ), + RSA.congruent(role1arg0, role2arg2), + not(RSA.NI(role1arg0)) + ) + val r5c = for { + role1 <- body.filter(_.isRoleAssertion) + role1arg0 = role1.getArguments.get(0) + role1arg2 = role1.getArguments.get(2) + if bounded contains role1arg0 + if bounded contains role1arg2 + role2 <- body.filter(_.isRoleAssertion) + role2arg0 = role2.getArguments.get(0) + role2arg2 = role2.getArguments.get(2) + if bounded contains role2arg0 + if bounded contains role2arg2 + } yield Rule.create( + RSA.ID( + RSA(bounded indexOf role1arg2), + RSA(bounded indexOf role2arg2) + ), + role1 << Backward, + role2 << Backward, + RSA.ID( + RSA(bounded indexOf role1arg0), + RSA(bounded indexOf role2arg0) + ), + RSA.congruent(role1arg2, role2arg2), + not(RSA.NI(role1arg2)) + ) + + /* Rules 6 */ + val r6 = { + for { + role <- body.filter(_.isRoleAssertion) + arg0 = role.getArguments.get(0) + arg2 = role.getArguments.get(2) + if bounded contains arg0 + if bounded contains arg2 + suffix <- Seq(Forward, Backward) + } yield Rule.create( + RSA.AQ(varV, varW, suffix), + role << suffix, + RSA.ID(RSA(bounded indexOf arg0), varV), + RSA.ID(RSA(bounded indexOf arg2), varW) + ) + } + + /* Rules 7x */ + val r7a = { + for (suffix <- List(Forward, Backward)) + yield Rule.create( + RSA.TQ(varU, varV, suffix), + RSA.AQ(varU, varV, suffix) + ) + } + val r7b = { + for (suffix <- List(Forward, Backward)) + yield Rule.create( + RSA.TQ(varU, varW, suffix), + RSA.AQ(varU, varV, suffix), + RSA.TQ(varV, varW, suffix) + ) + } + + /* Rules 8x */ + val r8a = + for (v <- answer) yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v))) + val r8b = + Rule.create(RSA.SP, RSA.FK) + val r8c = + for (suffix <- List(Forward, Backward)) + yield Rule.create( + RSA.SP, + RSA.TQ(varV, varV, suffix) + ) + + /* Rule 9 */ + val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) + + r1 :: + r3a ::: r3b :: r3c :: + r4c ::: r4b ::: r4a ::: + r5c ::: r5b ::: r5a ::: + r6 ::: + r7b ::: r7a ::: + r8a ::: r8b :: r8c ::: + r9 :: + List() + } + + private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = { + atom match { + case atom: TupleTableAtom => atom.reified + case other => (None, List(other)) + } + } + + private def reifyBodyFormula(formula: BodyFormula): List[BodyFormula] = { + formula match { + case atom: TupleTableAtom => atom.reified._2 + case neg: Negation => { + val (bs, as) = neg.getNegatedAtoms.asScala.toList.map(reifyAtom).unzip + val bind = bs.flatten.map(_.getBoundVariable).asJava + val atoms = as.flatten.asJava + List(Negation.create(bind, atoms)) + } + case other => List(other) + } + } + + private def reifyRule(rule: Rule): Rule = { + val (bs, hs) = rule.getHead.asScala.toList.map(_.reified).unzip + val head: List[TupleTableAtom] = hs.flatten + val bind: List[BodyFormula] = bs.flatten + val body: List[BodyFormula] = + rule.getBody.asScala.toList.map(reifyBodyFormula).flatten + Rule.create(head.asJava, (body ++ bind).asJava) + } + +} // class FilteringProgram + +object FilteringProgram { + def apply(query: SelectQuery, constants: List[Term]): FilteringProgram = + new FilteringProgram(query, constants) +} // object FilteringProgram diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala new file mode 100644 index 0000000..c3db99d --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala @@ -0,0 +1,249 @@ +package uk.ac.ox.cs.rsacomb + +/* Java imports */ +import java.io.File +import java.util.HashMap +import scala.collection.JavaConverters._ + +import tech.oxfordsemantic.jrdfox.client.UpdateType +import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} + +/* Local imports */ +import util.{RDFoxHelpers, RSA} + +object RSAComb extends App { + + val help: String = """ + rsacomb - combined approach for CQ answering for RSA ontologies. + + USAGE + rsacomb + + where + the ontology is expected to be an OWL file and the (single) + query a SPARQL query file. + + """ + + /* Simple arguments handling + * + * TODO: use something better later on + */ + + if (args.length < 2) { + println(help) + sys.exit; + } + + val ontoPath = new File(args(0)) + val queryPath = new File(args(1)) + + if (!ontoPath.isFile || !queryPath.isFile) { + println("The provided arguments are not regular files.\n\n") + println(help) + sys.exit; + } + + /* Create RSA object from generic OWLOntology + * + * TODO: It might be required to check if the ontology in input is + * Horn-ALCHOIQ. At the moment we are assuming this is always the + * case. + */ + + val ontology = RSAOntology(ontoPath) + if (ontology.isRSA) { + + /* Load query */ + val query = RDFoxHelpers.parseSelectQuery( + """ + PREFIX : + + SELECT ?X + WHERE { + ?X a :D ; + :R ?Y . + ?Y :S ?Z . + ?Z a :D . + } + """ + ) + + /* Compute answers to query */ + query match { + case Some(query) => { + + import implicits.JavaCollections._ + + // Open connection to RDFox + val (server, data) = RDFoxHelpers.openConnection("AnswerComputation") + + { + println("\nQuery") + println(query) + } + + // Step 1. Computing the canonical model + val canon = ontology.canonicalModel + data.addRules(canon.rules) + + { + println("\nCanonical Model rules:") + canon.rules.foreach(println) + } + + // Step 2. Computing the canonical model + val nis = { + val query = "SELECT ?Y WHERE { ?X rsa:EquivTo ?Y ; a rsa:Named . }" + RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes).flatten + } + val filter = ontology.filteringProgram(query, nis) + data.addRules(filter.rules) + + { + println("\nFiltering rules") + filter.rules.foreach(println) + } + + // Retrieve answers + println("\nAnswers:") + val ans = + RDFoxHelpers.queryInternalPredicate(data, "Ans", filter.answer.length) + println(ans) + + /* DEBUG: adding additional checks + */ + { + import suffix.{Forward, Backward} + + val arity = filter.answer.length + filter.bounded.length + + println("\nIndividuals:") + ontology.individuals.foreach(println) + + println("\nThings:") + val things = RDFoxHelpers.submitSelectQuery( + data, + """ + PREFIX owl: + + SELECT ?X { + ?X a owl:Thing + } + """ + ) + println(things) + + println("\nNAMEDs:") + val named = RDFoxHelpers.submitSelectQuery( + data, + """ + SELECT ?X { + ?X a rsa:Named + } + """, + RSA.Prefixes + ) + println(named) + + println("\nNIs:") + val nis = RDFoxHelpers.submitSelectQuery( + data, + """ + SELECT ?X { + ?X a rsa:NI + } + """, + RSA.Prefixes + ) + println(nis) + + // ID instances + println("\nIDs:") + val ids = RDFoxHelpers.queryInternalPredicate( + data, + "ID", + arity + 2 + ) + println(ids) + + println("\nEquivTo:") + val equivs = RDFoxHelpers.submitSelectQuery( + data, + """ + SELECT ?X ?Y { + ?X rsa:EquivTo ?Y + } + """, + RSA.Prefixes + ) + println(equivs) + + // Unfiltered answers + println("\nPossible answers:") + val qms = RDFoxHelpers.queryInternalPredicate( + data, + "QM", + arity + ) + println(qms) + + // Cycle detected + println("\nCycle detection:") + val aqf = RDFoxHelpers.queryInternalPredicate( + data, + "AQ" :: Forward, + arity + 2 + ) + val aqb = RDFoxHelpers.queryInternalPredicate( + data, + "AQ" :: Backward, + arity + 2 + ) + println(aqf) + println(aqb) + + // Forks detected + println("\nForks:") + val fk = RDFoxHelpers.queryInternalPredicate( + data, + "FK", + arity + ) + println(fk) + + // Spurious answers + println("\nSpurious answers") + val sp = RDFoxHelpers.queryInternalPredicate( + data, + "SP", + arity + ) + println(sp) + } + + // Close connection to RDFox + RDFoxHelpers.closeConnection(server, data) + } + case None => {} + } + } +} + +/* Notes: + * + * To establish a connection with a local RDFox instance, do the + * following: + * + * ``` + * val serverConnection : ServerConnection = ConnectionFactory.newServerConnection("rdfox:local", "", "") + * serverConnection.createDataStore("test","seq",new HashMap()) + * val dataStoreConnection : DataStoreConnection = serverConnection.newDataStoreConnection("test") + * dataStoreConnection.importData( + * UpdateType.ADDITION, + * Prefixes.s_emptyPrefixes, + * new File("./path/to/file") + * ) + * ``` + */ diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala new file mode 100644 index 0000000..ac86e3d --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala @@ -0,0 +1,380 @@ +package uk.ac.ox.cs.rsacomb + +/* Java imports */ +import java.util.HashMap +import java.util.stream.{Collectors, Stream} +import java.io.File +import org.semanticweb.owlapi.apibinding.OWLManager + +import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} +import org.semanticweb.owlapi.model.{ + OWLClass, + OWLObjectProperty, + OWLSubObjectPropertyOfAxiom, + OWLObjectPropertyExpression, + OWLObjectSomeValuesFrom, + OWLSubClassOfAxiom +} +import org.semanticweb.owlapi.model.parameters.Imports +import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory +import org.semanticweb.owlapi.model.{IRI => OWLIRI} +import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl + +import tech.oxfordsemantic.jrdfox.client.{UpdateType, DataStoreConnection} +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + Rule, + TupleTableAtom, + Negation, + BodyFormula +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + Term, + Variable, + IRI, + Resource +} +import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery + +/* Scala imports */ +import scala.collection.JavaConverters._ +import scala.collection.mutable.Set +import scalax.collection.immutable.Graph +import scalax.collection.GraphEdge.UnDiEdge + +/* Debug only */ +import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer +import tech.oxfordsemantic.jrdfox.logic._ +import org.semanticweb.owlapi.model.OWLObjectInverseOf + +import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} +import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom +import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} +import uk.ac.ox.cs.rsacomb.util.{RDFoxHelpers, RSA} + +object RSAOntology { + + // Counter used to implement a simple fresh variable generator + private var counter = -1; + + def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) + + def apply(ontology: File): RSAOntology = + new RSAOntology(loadOntology(ontology)) + + def genFreshVariable(): Variable = { + counter += 1 + Variable.create(f"I$counter%03d") + } + + private def loadOntology(onto: File): OWLOntology = { + val manager = OWLManager.createOWLOntologyManager() + manager.loadOntologyFromOntologyDocument(onto) + } +} + +class RSAOntology(val ontology: OWLOntology) extends RSAAxiom { + + // Gather TBox/RBox/ABox from original ontology + val tbox: List[OWLAxiom] = + ontology + .tboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .asScala + .toList + + val rbox: List[OWLAxiom] = + ontology + .rboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .asScala + .toList + + val abox: List[OWLAxiom] = + ontology + .aboxAxioms(Imports.INCLUDED) + .collect(Collectors.toList()) + .asScala + .toList + + val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox + + /* Retrieve individuals in the original ontology + */ + val individuals: List[IRI] = + ontology + .getIndividualsInSignature() + .asScala + .map(_.getIRI) + .map(implicits.RDFox.owlapiToRdfoxIri) + .toList + + val concepts: List[OWLClass] = + ontology.getClassesInSignature().asScala.toList + + val roles: List[OWLObjectPropertyExpression] = + axioms + .flatMap(_.objectPropertyExpressionsInSignature) + .distinct + + // OWLAPI reasoner for same easier tasks + private val reasoner = + (new StructuralReasonerFactory()).createReasoner(ontology) + + /* Steps for RSA check + * 1) convert ontology axioms into LP rules + * 2) call RDFox on the onto and compute materialization + * 3) build graph from E(x,y) facts + * 4) check if the graph is tree-like + * ideally this annotates the graph with info about the reasons + * why the ontology might not be RSA. This could help a second + * step of approximation of an Horn-ALCHOIQ to RSA + */ + lazy val isRSA: Boolean = { + + val unsafe = this.unsafeRoles + + /* DEBUG: print rules in DL syntax and unsafe roles */ + //val renderer = new DLSyntaxObjectRenderer() + //println("\nDL rules:") + //axioms.foreach(x => println(renderer.render(x))) + //println("\nUnsafe roles:") + //println(unsafe) + + /* Ontology convertion into LP rules */ + val datalog = for { + axiom <- axioms + visitor = new RDFoxAxiomConverter( + RSAOntology.genFreshVariable(), + unsafe, + SkolemStrategy.ConstantRSA(axiom.toString), + Empty + ) + rule <- axiom.accept(visitor) + } yield rule + + /* DEBUG: print datalog rules */ + //println("\nDatalog roles:") + //datalog.foreach(println) + + // Open connection with RDFox + val (server, data) = RDFoxHelpers.openConnection("RSACheck") + // Add Data (hardcoded for now) + //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") + + /* Add built-in rules + */ + data.importData( + UpdateType.ADDITION, + RSA.Prefixes, + "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." + ) + + /* Add built-in rules + */ + // data.importData( + // UpdateType.ADDITION, + // RSA.Prefixes, + // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." + // ) + + /* Add ontology rules + */ + data.addRules(datalog.asJava) + + /* Build graph + */ + val graph = this.rsaGraph(data); + //println(graph) + + // Close connection to RDFox + RDFoxHelpers.closeConnection(server, data) + + /* To check if the graph is tree-like we check for acyclicity in a + * undirected graph. + * + * TODO: Implement additional checks (taking into account equality) + */ + graph.isAcyclic + } + + lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { + + /* DEBUG: print rules in DL syntax */ + //val renderer = new DLSyntaxObjectRenderer() + + /* Checking for (1) unsafety condition: + * + * For all roles r1 appearing in an axiom of type T5, r1 is unsafe + * if there exists a role r2 (different from top) appearing in an axiom + * of type T3 and r1 is a subproperty of the inverse of r2. + */ + val unsafe1 = for { + axiom <- tbox + if axiom.isT5 + role1 <- axiom.objectPropertyExpressionsInSignature + roleSuper = + role1 +: reasoner + .superObjectProperties(role1) + .collect(Collectors.toList()) + .asScala + roleSuperInv = roleSuper.map(_.getInverseProperty) + axiom <- tbox + if axiom.isT3 && !axiom.isT3top + role2 <- axiom.objectPropertyExpressionsInSignature + if roleSuperInv.contains(role2) + } yield role1 + + /* Checking for (2) unsafety condition: + * + * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if + * there exists a role p2 appearing in an axiom of type T4 and p1 is a + * subproperty of either p2 or the inverse of p2. + * + */ + val unsafe2 = for { + axiom <- tbox + if axiom.isT5 + role1 <- axiom.objectPropertyExpressionsInSignature + roleSuper = + role1 +: reasoner + .superObjectProperties(role1) + .collect(Collectors.toList()) + .asScala + roleSuperInv = roleSuper.map(_.getInverseProperty) + axiom <- tbox + if axiom.isT4 + role2 <- axiom.objectPropertyExpressionsInSignature + if roleSuper.contains(role2) || roleSuperInv.contains(role2) + } yield role1 + + (unsafe1 ++ unsafe2).toList + } + + private def rsaGraph( + data: DataStoreConnection + ): Graph[Resource, UnDiEdge] = { + val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" + val answers = RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes) + var edges: List[UnDiEdge[Resource]] = answers.map { + case n1 :: n2 :: _ => UnDiEdge(n1, n2) + } + Graph(edges: _*) + } + + def filteringProgram( + query: SelectQuery, + nis: List[Term] + ): FilteringProgram = + new FilteringProgram(query, nis) + + lazy val canonicalModel = new CanonicalModel(this) + + // TODO: the following functions needs testing + def confl( + role: OWLObjectPropertyExpression + ): Set[OWLObjectPropertyExpression] = { + + val invSuperRoles = reasoner + .superObjectProperties(role) + .collect(Collectors.toSet()) + .asScala + .addOne(role) + .map(_.getInverseProperty) + + invSuperRoles + .flatMap(x => + reasoner + .subObjectProperties(x) + .collect(Collectors.toSet()) + .asScala + .addOne(x) + ) + .filterNot(_.isOWLBottomObjectProperty()) + .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) + } + + def self(axiom: OWLSubClassOfAxiom): Set[Term] = { + // Assuming just one role in the signature of a T5 axiom + val role = axiom.objectPropertyExpressionsInSignature(0) + if (this.confl(role).contains(role)) { + Set( + RSA("v0_" ++ axiom.hashed), + RSA("v1_" ++ axiom.hashed) + ) + } else { + Set() + } + } + + // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { + // // Assuming just one role in the signature of a T5 axiom + // val roleR = axiom.objectPropertyExpressionsInSignature(0) + // val conflR = this.confl(roleR) + // // We just need the TBox to find + // val tbox = ontology + // .tboxAxioms(Imports.INCLUDED) + // .collect(Collectors.toSet()) + // .asScala + // for { + // axiom1 <- tbox + // // TODO: is this an optimization or an error? + // if axiom1.isT5 + // // We expect only one role coming out of a T5 axiom + // roleS <- axiom1.objectPropertyExpressionsInSignature + // // Triples ordering is among triples involving safe roles. + // if !unsafeRoles.contains(roleS) + // if conflR.contains(roleS) + // individual = + // if (axiom.hashCode < axiom1.hashCode) { + // RSA.rsa("v0_" ++ axiom1.hashCode.toString()) + // } else { + // RSA.rsa("v1_" ++ axiom1.hashCode.toString()) + // } + // } yield individual + // } + + def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { + // TODO: we can actually use `toTriple` from `RSAAxiom` + val classes = + axiom.classesInSignature.collect(Collectors.toList()).asScala + val classA = classes(0) + val roleR = axiom + .objectPropertyExpressionsInSignature(0) + .asInstanceOf[OWLObjectProperty] + val classB = classes(1) + cycle_aux(classA, roleR, classB) + } + + def cycle_aux( + classA: OWLClass, + roleR: OWLObjectProperty, + classB: OWLClass + ): Set[Term] = { + val conflR = this.confl(roleR) + val classes = ontology + .classesInSignature(Imports.INCLUDED) + .collect(Collectors.toSet()) + .asScala + for { + classD <- classes + roleS <- conflR + classC <- classes + // Keeping this check for now + if !unsafeRoles.contains(roleS) + tripleARB = RSAAxiom.hashed(classA, roleR, classB) + tripleDSC = RSAAxiom.hashed(classD, roleS, classC) + individual = + if (tripleARB > tripleDSC) { + RSA("v1_" ++ tripleDSC) + } else { + // Note that this is also the case for + // `tripleARB == tripleDSC` + RSA("v0_" ++ tripleDSC) + } + } yield individual + } + + def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = + this.self(axiom) | this.cycle(axiom) + +} // implicit class RSAOntology diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala new file mode 100644 index 0000000..a8d1ffd --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxAxiomConverter.scala @@ -0,0 +1,105 @@ +package uk.ac.ox.cs.rsacomb.converter + +import org.semanticweb.owlapi.model.{ + OWLAxiom, + OWLSubClassOfAxiom, + OWLEquivalentClassesAxiom, + OWLObjectPropertyExpression +} +import org.semanticweb.owlapi.model.OWLAxiomVisitorEx + +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + Rule, + BodyFormula, + TupleTableAtom, + TupleTableName +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + Term, + IRI, + Variable, + Literal +} + +import scala.collection.JavaConverters._ + +import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom +import org.semanticweb.owlapi.model.OWLObjectProperty +import org.semanticweb.owlapi.model.OWLClassAssertionAxiom + +import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} + +object RDFoxAxiomConverter { + + def apply( + term: Term, + unsafe: List[OWLObjectPropertyExpression], + skolem: SkolemStrategy = SkolemStrategy.None, + suffix: RSASuffix = Empty + ): RDFoxAxiomConverter = + new RDFoxAxiomConverter(term, unsafe, skolem, suffix) + +} // object RDFoxAxiomConverter + +class RDFoxAxiomConverter( + term: Term, + unsafe: List[OWLObjectPropertyExpression], + skolem: SkolemStrategy, + suffix: RSASuffix +) extends OWLAxiomVisitorEx[List[Rule]] { + + override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { + // Skolemization is needed only for the head of an axiom + val subVisitor = + new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) + val superVisitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) + // Each visitor returns a `RDFoxRuleShards`, a tuple (res,ext): + // - the `res` List is a list of atoms resulting from the conversion + // of the axiom. + // - for some Class Expressions appearing in the head of an Axiom, + // the conversion might produce atoms that need to appear in the + // body (and not in the head) of the rule. This is what the `ext` + // List is for. + val sub = axiom.getSubClass.accept(subVisitor) + val sup = axiom.getSuperClass.accept(superVisitor) + val head = sup.res.asJava + val body = (sub.res ++ sup.ext).asJava + List(Rule.create(head, body)) + } + + override def visit(axiom: OWLEquivalentClassesAxiom): List[Rule] = { + for { + axiom1 <- axiom.asPairwiseAxioms.asScala.toList + axiom2 <- axiom1.asOWLSubClassOfAxioms.asScala.toList + rule <- axiom2.accept(this) + } yield rule + } + + override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { + val term1 = RSAOntology.genFreshVariable() + val subVisitor = + new RDFoxPropertyExprConverter(term, term1, suffix) + val superVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) + val body: List[BodyFormula] = axiom.getSubProperty.accept(subVisitor) + val head: List[TupleTableAtom] = axiom.getSuperProperty.accept(superVisitor) + List(Rule.create(head.asJava, body.asJava)) + } + + override def visit(axiom: OWLClassAssertionAxiom): List[Rule] = { + val ind = axiom.getIndividual + if (ind.isNamed) { + val term = IRI.create(ind.asOWLNamedIndividual().getIRI.getIRIString) + val cls = axiom.getClassExpression + val visitor = + new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) + val shard = cls.accept(visitor) + List(Rule.create(shard.res.asJava, shard.ext.asJava)) + } else { + List() + } + } + + def doDefault(axiom: OWLAxiom): List[Rule] = List() + +} // class RDFoxAxiomConverter diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala new file mode 100644 index 0000000..c151c9a --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxClassExprConverter.scala @@ -0,0 +1,160 @@ +package uk.ac.ox.cs.rsacomb.converter + +import scala.collection.JavaConverters._ +import java.util.stream.{Stream, Collectors} + +import org.semanticweb.owlapi.model.{ + OWLClassExpression, + OWLClass, + OWLObjectSomeValuesFrom, + OWLObjectIntersectionOf, + OWLObjectOneOf, + OWLObjectMaxCardinality +} +import org.semanticweb.owlapi.model.OWLClassExpressionVisitorEx +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + BindAtom, + TupleTableName, + TupleTableAtom +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + Term, + Literal, + Variable, + FunctionCall, + IRI +} + +import org.semanticweb.owlapi.model.OWLObjectPropertyExpression +import org.semanticweb.owlapi.model.OWLObjectProperty + +import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} +import uk.ac.ox.cs.rsacomb.util.RSA + +object RDFoxClassExprConverter { + + def apply( + term: Term, + unsafe: List[OWLObjectPropertyExpression] = List(), + skolem: SkolemStrategy = SkolemStrategy.None, + suffix: RSASuffix = Empty + ): RDFoxClassExprConverter = + new RDFoxClassExprConverter(term, unsafe, skolem, suffix) + + def merge(rules: List[RDFoxRuleShards]): RDFoxRuleShards = { + rules.foldLeft(RDFoxRuleShards(List(), List())) { (r1, r2) => + RDFoxRuleShards( + r1.res ++ r2.res, + r1.ext ++ r2.ext + ) + } + } + +} // object RDFoxClassExprConverter + +class RDFoxClassExprConverter( + term: Term, + unsafe: List[OWLObjectPropertyExpression], + skolem: SkolemStrategy, + suffix: RSASuffix +) extends OWLClassExpressionVisitorEx[RDFoxRuleShards] { + + import uk.ac.ox.cs.rsacomb.implicits.RDFox._ + + // OWLClass + override def visit(expr: OWLClass): RDFoxRuleShards = { + val iri: IRI = if (expr.isTopEntity()) IRI.THING else expr.getIRI() + val atom = List(TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri)) + RDFoxRuleShards(atom, List()) + } + + // OWLObjectIntersectionOf + override def visit(expr: OWLObjectIntersectionOf): RDFoxRuleShards = { + val visitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) + // TODO: maybe using `flatMap` instead of `merge` + `map` works as well + RDFoxClassExprConverter.merge( + expr.asConjunctSet.asScala.toList + .map((e: OWLClassExpression) => e.accept(visitor)) + ) + } + + // OWLObjectOneOf + override def visit(expr: OWLObjectOneOf): RDFoxRuleShards = { + val visitor = RDFoxClassExprConverter(term, unsafe, skolem, suffix) + // TODO: review nominal handling. Here we are taking "just" one + val ind = expr.individuals + .collect(Collectors.toList()) + .asScala + .filter(_.isOWLNamedIndividual) + .head // restricts to proper "nominals" + .asOWLNamedIndividual + .getIRI + val atom = List( + TupleTableAtom.rdf(term, IRI.SAME_AS, ind) + ) + RDFoxRuleShards(atom, List()) + } + + // OWLObjectSomeValuesFrom + override def visit(expr: OWLObjectSomeValuesFrom): RDFoxRuleShards = { + val y = RSAOntology.genFreshVariable() + // Here we are assuming a role name + val prop = expr.getProperty() + // Computes the result of rule skolemization. Depending on the used + // technique it might involve the introduction of additional atoms, + // and/or fresh constants and variables. + val (head, body, term1) = skolem match { + case SkolemStrategy.None => (List(), List(), y) + case SkolemStrategy.Constant(c) => (List(), List(), c) + case SkolemStrategy.ConstantRSA(c) => { + if (unsafe.contains(prop)) + (List(RSA.PE(term, c), RSA.U(c)), List(), c) + else + (List(), List(), c) + } + case SkolemStrategy.Standard(f) => { + ( + List(), + List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), y)), + y + ) + } + } + val classVisitor = + new RDFoxClassExprConverter(term1, unsafe, skolem, suffix) + val classResult = expr.getFiller.accept(classVisitor) + val propertyVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) + val propertyResult = expr.getProperty.accept(propertyVisitor) + RDFoxRuleShards( + classResult.res ++ propertyResult ++ head, + classResult.ext ++ body + ) + } + + // OWLObjectMaxCardinality + override def visit(expr: OWLObjectMaxCardinality): RDFoxRuleShards = { + // TODO: again, no hardcoded variables + val vars = + List(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) + val classResult = RDFoxClassExprConverter.merge( + vars + .map(new RDFoxClassExprConverter(_, unsafe, skolem, suffix)) + .map(expr.getFiller.accept(_)) + ) + val propertyResult = + vars + .map(new RDFoxPropertyExprConverter(term, _, suffix)) + .map(expr.getProperty.accept(_)) + .flatten + RDFoxRuleShards( + List(TupleTableAtom.rdf(vars(0), IRI.SAME_AS, vars(1))), + classResult.res ++ propertyResult + ) + } + + def doDefault(expr: OWLClassExpression): RDFoxRuleShards = + RDFoxRuleShards(List(), List()) + +} // class RDFoxClassExprConverter diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala new file mode 100644 index 0000000..94c7887 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxPropertyExprConverter.scala @@ -0,0 +1,35 @@ +package uk.ac.ox.cs.rsacomb.converter + +import org.semanticweb.owlapi.model.{OWLPropertyExpression, OWLObjectProperty} +import org.semanticweb.owlapi.model.OWLPropertyExpressionVisitorEx + +import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom +import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, Literal} + +import org.semanticweb.owlapi.model.OWLObjectInverseOf + +import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse} + +class RDFoxPropertyExprConverter( + term1: Term, + term2: Term, + suffix: RSASuffix +) extends OWLPropertyExpressionVisitorEx[List[TupleTableAtom]] { + + // Automatically converts OWLAPI types into RDFox equivalent types. + import uk.ac.ox.cs.rsacomb.implicits.RDFox._ + + override def visit(expr: OWLObjectProperty): List[TupleTableAtom] = { + val base = expr.getIRI.getIRIString + val pred = IRI.create(base :: suffix) + List(TupleTableAtom.rdf(term1, pred, term2)) + } + + override def visit(expr: OWLObjectInverseOf): List[TupleTableAtom] = { + val visitor = new RDFoxPropertyExprConverter(term1, term2, suffix + Inverse) + expr.getInverse.accept(visitor) + } + + def doDefault(expr: OWLPropertyExpression): List[TupleTableAtom] = List() + +} // class RDFoxPropertyExprConverter diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala new file mode 100644 index 0000000..c88cf3c --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxRuleShards.scala @@ -0,0 +1,5 @@ +package uk.ac.ox.cs.rsacomb.converter + +import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, BodyFormula} + +case class RDFoxRuleShards(res: List[TupleTableAtom], ext: List[BodyFormula]) diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala new file mode 100644 index 0000000..0d72226 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/SkolemStrategy.scala @@ -0,0 +1,78 @@ +package uk.ac.ox.cs.rsacomb.converter + +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, IRI} + +sealed trait SkolemStrategy + +object SkolemStrategy { + // TODO: might want to use something else other than `hashCode` as a + // function to generate a fresh function/constant + + /* No skolemization at all. + * + * From + * ∃R.A ⊑ B + * to + * R(x,y), B(y) -> B(x) + */ + case object None extends SkolemStrategy + + /* Functional skolemization + * + * From + * A ⊑ ∃R.B + * to + * A(x) -> R(x,f(x)), B(f(x)) + * for f, fresh function associated with the input axiom + * + * In RDFox this can represented combining the BIND operator with the + * SKOLEM operator as such: + * A(x), BIND(y, SKOLEM("f", x)) -> R(x,y), B(y) + * The first argument of a SKOLEM call is a literal string (ideally + * identifing the simulated function name). + * + * NOTE: this requirement for the SKOLEM operator is not enforced by + * RDFox, that will fail silently if omitted. + */ + case class Standard(func: Literal) extends SkolemStrategy + object Standard { + def apply(axiom: String) = + new Standard( + Literal.create(genFunctionString(axiom), Datatype.XSD_STRING) + ) + def genFunctionString(str: String) = "f_" ++ str.hashCode.toString + } + + /* Constant skolemization + * + * From + * A ⊑ ∃R.B + * to + * A(y) -> R(x,c), B(c) + * for c, fresh constant associated with the input axiom + */ + case class Constant(const: IRI) extends SkolemStrategy + object Constant { + def apply(axiom: String) = + new Constant(IRI.create(genConstantString(axiom))) + def genConstantString(str: String) = "c_" ++ str.hashCode.toString + } + + /* (RSA) Constant skolemization + * This is a special skolemization option to introduce additional atoms for RSA + * checking algorithm. + * + * From + * A ⊑ ∃R.B + * to + * A(y) -> R(x,c), PE(x,c), B(c) + * for c, fresh constant associated with the input axiom and PE an internal predicate. + */ + case class ConstantRSA(const: IRI) extends SkolemStrategy + object ConstantRSA { + def apply(axiom: String) = + new ConstantRSA(IRI.create(genConstantString(axiom))) + def genConstantString(str: String) = "c_" ++ str.hashCode.toString + } +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala new file mode 100644 index 0000000..3b621f4 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/JavaCollections.scala @@ -0,0 +1,13 @@ +package uk.ac.ox.cs.rsacomb.implicits + +import scala.collection.JavaConverters._ + +object JavaCollections { + + implicit def javaToScalaList[A](list: java.util.List[A]): List[A] = + list.asScala.toList + + implicit def scalaToJavaList[A](list: List[A]): java.util.List[A] = + list.asJava + +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala new file mode 100644 index 0000000..0462a47 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RDFox.scala @@ -0,0 +1,20 @@ +package uk.ac.ox.cs.rsacomb.implicits + +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFoxIRI} +import org.semanticweb.owlapi.model.{IRI => OWLIRI} + +object RDFox { + + implicit def rdfoxToOwlapiIri(iri: RDFoxIRI): OWLIRI = { + OWLIRI.create(iri.getIRI) + } + + implicit def owlapiToRdfoxIri(iri: OWLIRI): RDFoxIRI = { + RDFoxIRI.create(iri.getIRIString()) + } + + implicit def stringToRdfoxIri(iri: String): RDFoxIRI = { + RDFoxIRI.create(iri) + } + +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala new file mode 100644 index 0000000..a8afc72 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala @@ -0,0 +1,88 @@ +package uk.ac.ox.cs.rsacomb.implicits + +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, FunctionCall} +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + BindAtom, + TupleTableAtom, + TupleTableName +} +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} +import scala.collection.JavaConverters._ + +import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth} +import uk.ac.ox.cs.rsacomb.RSAOntology + +/* Is this the best way to determine if an atom is an RDF triple? + * Note that we can't use `getNumberOfArguments()` because is not + * "consistent": + * - for an atom created with `rdf(, , )`, + * `getNumberOfArguments` returns 3 + * - for an atom created with `Atom.create(, , + * , )`, `getNumberOfArguments()` returns 3 + * + * This is probably because `Atom.rdf(...) is implemented as: + * ```scala + * def rdf(term1: Term, term2: Term, term3: Term): Atom = + * Atom.create(TupleTableName.create("internal:triple"), term1, term2, term3) + * ``` + */ + +trait RSAAtom { + + implicit class RSAAtom(val atom: TupleTableAtom) { + + import RDFox._ + + val name: String = atom.getTupleTableName.getName + + val isRDF: Boolean = name == "internal:triple" + + val isClassAssertion: Boolean = { + isRDF && { + val pred = atom.getArguments.get(1) + pred == IRI.RDF_TYPE + } + } + + val isRoleAssertion: Boolean = isRDF && !isClassAssertion + + def <<(suffix: RSASuffix): TupleTableAtom = + if (isRDF) { + val subj = atom.getArguments.get(0) + val pred = atom.getArguments.get(1) + val obj = atom.getArguments.get(2) + if (isClassAssertion) { + val obj1 = obj match { + case iri: IRI => IRI.create(iri.getIRI :: suffix) + case other => other + } + TupleTableAtom.rdf(subj, pred, obj1) + } else { + val pred1 = pred match { + case iri: IRI => IRI.create(iri.getIRI :: suffix) + case other => other + } + TupleTableAtom.rdf(subj, pred1, obj) + } + } else { + val ttname = TupleTableName.create(name :: suffix) + TupleTableAtom.create(ttname, atom.getArguments()) + } + + lazy val reified: (Option[BindAtom], List[TupleTableAtom]) = + if (isRDF) { + (None, List(atom)) + } else { + val bvar = RSAOntology.genFreshVariable() + val str = Literal.create(name, Datatype.XSD_STRING) + val args = atom.getArguments.asScala.toList + val skolem = FunctionCall.create("SKOLEM", str :: args: _*) + val bind = BindAtom.create(skolem, bvar) + val atoms = args.zipWithIndex + .map { case (t, i) => TupleTableAtom.rdf(bvar, name :: Nth(i), t) } + (Some(bind), atoms) + } + } + +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala new file mode 100644 index 0000000..e39d5b2 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAxiom.scala @@ -0,0 +1,155 @@ +package uk.ac.ox.cs.rsacomb.implicits + +/* Java imports */ +import org.semanticweb.owlapi.model.{ + OWLAxiom, + OWLSubClassOfAxiom, + OWLEquivalentClassesAxiom +} +import org.semanticweb.owlapi.model.{ + OWLObjectPropertyExpression, + OWLSubObjectPropertyOfAxiom, + OWLClass, + OWLClassExpression, + OWLObjectSomeValuesFrom, + OWLObjectMaxCardinality +} +import org.semanticweb.owlapi.model.ClassExpressionType +import org.semanticweb.owlapi.model.{ + OWLAxiomVisitorEx, + OWLClassExpressionVisitorEx +} +import org.semanticweb.owlapi.model.OWLObjectProperty +import scala.collection.JavaConverters._ + +/* Wrapper trait for the implicit class `RSAAxiom`. + */ +trait RSAAxiom { + + /* Identifies some of the axiom types in a Horn-ALCHOIQ ontology + * in normal form. Refer to the paper for more details on the + * chosen names. + */ + private sealed trait RSAAxiomType + private object RSAAxiomType { + case object T3 extends RSAAxiomType // ∃R.A ⊑ B + case object T3top extends RSAAxiomType // ∃R.⊤ ⊑ B + case object T4 extends RSAAxiomType // A ⊑ ≤1R.B + case object T5 extends RSAAxiomType // A ⊑ ∃R.B + } + + object RSAAxiom { + + def hashed( + cls1: OWLClass, + prop: OWLObjectPropertyExpression, + cls2: OWLClass + ): String = + (cls1, prop, cls2).hashCode.toString + + } + + /* Implements additional features on top of `OWLAxiom` from + * the OWLAPI. + */ + implicit class RSAAxiom(axiom: OWLAxiom) { + + /* Detecting axiom types: + * + * In order to reason about role unsafety in Horn-ALCHOIQ + * ontologies we need to detect and filter axioms by their + * "type". + * + * This is a simple implementation following the Visitor + * pattern imposed by the OWLAPI. + */ + private class RSAAxiomTypeDetector(t: RSAAxiomType) + extends OWLAxiomVisitorEx[Boolean] { + override def visit(axiom: OWLSubClassOfAxiom): Boolean = { + val sub = axiom.getSubClass().getClassExpressionType() + val sup = axiom.getSuperClass().getClassExpressionType() + t match { + case RSAAxiomType.T3top => // ∃R.⊤ ⊑ B + axiom.isT3 && axiom + .getSubClass() + .asInstanceOf[OWLObjectSomeValuesFrom] + .getFiller + .isOWLThing + case RSAAxiomType.T3 => // ∃R.A ⊑ B + sub == ClassExpressionType.OBJECT_SOME_VALUES_FROM && sup == ClassExpressionType.OWL_CLASS + case RSAAxiomType.T4 => // A ⊑ ≤1R.B + sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_MAX_CARDINALITY + case RSAAxiomType.T5 => // A ⊑ ∃R.B + sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_SOME_VALUES_FROM + } + } + + override def visit(axiom: OWLEquivalentClassesAxiom): Boolean = { + // TODO + false + } + + def doDefault(axiom: OWLAxiom): Boolean = false + } + + private def isOfType(t: RSAAxiomType): Boolean = { + val visitor = new RSAAxiomTypeDetector(t) + axiom.accept(visitor) + } + + /* Exposed methods */ + def isT3top: Boolean = isOfType(RSAAxiomType.T3top) + def isT3: Boolean = isOfType(RSAAxiomType.T3) + def isT4: Boolean = isOfType(RSAAxiomType.T4) + def isT5: Boolean = isOfType(RSAAxiomType.T5) + + /* Extracting ObjectPropertyExpressions from axioms + * + * This extracts all ObjectPropertyExpressions from a given + * axiom. While the implementation is generic we use it on axioms + * of specific types (see above). + * + * NOTE: it is not possible to use the `objectPropertyInSignature` + * method of `OWLAxiom` because it returns all "role names" involved + * in the signature of an axiom. In particular we won't get the inverse + * of a role if this appears in the axiom (but we will get the role + * itself instead). + */ + lazy val objectPropertyExpressionsInSignature + : List[OWLObjectPropertyExpression] = + axiom match { + case a: OWLSubClassOfAxiom => + rolesInExpr(a.getSubClass) ++ rolesInExpr(a.getSuperClass) + case a: OWLEquivalentClassesAxiom => + a.getClassExpressions.asScala.toList.flatMap(rolesInExpr(_)) + case a: OWLSubObjectPropertyOfAxiom => + List(a.getSubProperty, a.getSuperProperty) + case _ => List() + } + + private def rolesInExpr( + expr: OWLClassExpression + ): List[OWLObjectPropertyExpression] = + expr match { + case e: OWLObjectSomeValuesFrom => List(e.getProperty) + case e: OWLObjectMaxCardinality => List(e.getProperty) + case _ => List() + } + + lazy val toTriple: Option[(OWLClass, OWLObjectProperty, OWLClass)] = + for { + subClass <- Some(axiom) collect { case a: OWLSubClassOfAxiom => a } + cls1 <- Some(subClass.getSubClass) collect { case a: OWLClass => a } + someValues <- Some(subClass.getSuperClass) collect { + case a: OWLObjectSomeValuesFrom => a + } + prop <- Some(someValues.getProperty) collect { + case a: OWLObjectProperty => a + } + cls2 <- Some(someValues.getFiller) collect { case a: OWLClass => a } + } yield (cls1, prop, cls2) + + lazy val hashed: String = (RSAAxiom.hashed _) tupled toTriple.get + } + +} // trait RSAAxiom diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala new file mode 100644 index 0000000..b22910b --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/suffix/RSASuffix.scala @@ -0,0 +1,31 @@ +package uk.ac.ox.cs.rsacomb.suffix + +import org.semanticweb.owlapi.model.{ + OWLPropertyExpression, + OWLObjectInverseOf, + OWLObjectProperty +} + +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} +import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, TupleTableName} + +object RSASuffix { + + def apply(suffix: String => String): RSASuffix = new RSASuffix(suffix) + +} + +class RSASuffix(val suffix: String => String) { + + def +(that: RSASuffix): RSASuffix = + new RSASuffix(this.suffix andThen that.suffix) + + def ::(str: String): String = this suffix str + +} + +case object Empty extends RSASuffix(identity) +case object Forward extends RSASuffix((s) => s"${s}_f") +case object Backward extends RSASuffix((s) => s"${s}_b") +case object Inverse extends RSASuffix((s) => s"${s}_inv") +case class Nth(n: Int) extends RSASuffix((s) => s"${s}_$n") diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala new file mode 100644 index 0000000..fd9e1c5 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxHelpers.scala @@ -0,0 +1,100 @@ +package uk.ac.ox.cs.rsacomb.util + +import java.util.{Map => JMap, HashMap => JHashMap} +import java.io.StringReader +import tech.oxfordsemantic.jrdfox.Prefixes +import tech.oxfordsemantic.jrdfox.client.{ + ConnectionFactory, + ServerConnection, + DataStoreConnection +} +import tech.oxfordsemantic.jrdfox.formats.SPARQLParser +import tech.oxfordsemantic.jrdfox.logic.expression.Resource +import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery + +import uk.ac.ox.cs.rsacomb.suffix.Nth + +object RDFoxHelpers { + + def openConnection( + dataStore: String, + opts: JMap[String, String] = new JHashMap[String, String]() + ): (ServerConnection, DataStoreConnection) = { + /* Create local server connection + */ + val serverUrl = "rdfox:local" + val role = "" + val password = "" + val server = + ConnectionFactory.newServerConnection(serverUrl, role, password) + + /* Create datastore connection + */ + // parameters.put("owl-in-rdf-support", "relaxed") + // parameters.put("equality", "noUNA") + server.createDataStore(dataStore, "par-complex-nn", opts) + val data = server.newDataStoreConnection(dataStore) + + (server, data) + } + + def parseSelectQuery( + query: String, + prefixes: Prefixes = new Prefixes() + ): Option[SelectQuery] = { + val parser = new SPARQLParser( + prefixes, + new StringReader(query) + ) + parser.parseSingleQuery() match { + case q: SelectQuery => Some(q) + case _ => None + } + } + + def submitSelectQuery( + data: DataStoreConnection, + query: String, + prefixes: Prefixes = new Prefixes(), + opts: JMap[String, String] = new JHashMap[String, String]() + ): List[List[Resource]] = { + val cursor = data.createCursor(prefixes, query, opts) + var answers: List[List[Resource]] = List() + var mul = cursor.open() + while (mul > 0) { + val answer = + (0 until cursor.getArity).map(cursor.getResource(_)).toList + answers = answer :: answers + mul = cursor.advance() + } + cursor.close(); + answers + } + + def queryInternalPredicate( + data: DataStoreConnection, + pred: String, + arity: Int, + opts: JMap[String, String] = new JHashMap[String, String]() + ): List[List[Resource]] = { + var query = "SELECT" + for (i <- 0 until arity) { + query ++= s" ?X$i" + } + query ++= " WHERE {" + for (i <- 0 until arity) { + query ++= s" ?S rsa:${pred :: Nth(i)} ?X$i ." + } + query ++= " }" + submitSelectQuery(data, query, RSA.Prefixes, opts) + } + + def closeConnection( + server: ServerConnection, + data: DataStoreConnection + ): Unit = { + server.close(); + data.close(); + } + +} diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala new file mode 100644 index 0000000..f9ff59b --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala @@ -0,0 +1,100 @@ +package uk.ac.ox.cs.rsacomb.util + +/* Java imports */ +import java.util.Map + +import tech.oxfordsemantic.jrdfox.formats.SPARQLParser +import tech.oxfordsemantic.jrdfox.Prefixes +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + TupleTableAtom, + TupleTableName, + Negation +} +import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable, IRI} +import org.semanticweb.owlapi.model.OWLOntology +import org.semanticweb.owlapi.model.{ + OWLAxiom, + OWLClass, + OWLObjectPropertyExpression +} + +import uk.ac.ox.cs.rsacomb.suffix.RSASuffix + +// Debug only +import scala.collection.JavaConverters._ + +object RSA { + + val Prefixes: Prefixes = new Prefixes() + Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") + + private def atom(name: IRI, vars: List[Term]) = + TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) + + def PE(t1: Term, t2: Term) = + TupleTableAtom.rdf(t1, RSA("PE"), t2) + + def U(t: Term) = + TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U")) + + def In(t: Term)(implicit set: Term) = + TupleTableAtom.rdf(t, RSA("In"), set) + + def notIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set)) + + def congruent(t1: Term, t2: Term) = + TupleTableAtom.rdf(t1, RSA("congruent"), t2) + + def QM(implicit variables: (List[Term], List[Term])) = { + val (answer, bounded) = variables + atom(RSA("QM"), answer ::: bounded) + } + + def ID(t1: Term, t2: Term)(implicit variables: (List[Term], List[Term])) = { + val (answer, bounded) = variables + atom(RSA("ID"), (answer ::: bounded) :+ t1 :+ t2) + } + + def Named(t: Term) = + TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("Named")) + + def Thing(t: Term) = + TupleTableAtom.rdf(t, IRI.RDF_TYPE, IRI.THING) + + def NI(t: Term) = + TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI")) + + def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit + variables: (List[Term], List[Term]) + ) = { + val (answer, bounded) = variables + atom(RSA("TQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) + } + + def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit + variables: (List[Term], List[Term]) + ) = { + val (answer, bounded) = variables + atom(RSA("AQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) + } + + def FK(implicit variables: (List[Term], List[Term])) = { + val (answer, bounded) = variables + atom(RSA("FK"), answer ::: bounded) + } + + def SP(implicit variables: (List[Term], List[Term])) = { + val (answer, bounded) = variables + atom(RSA("SP"), answer ::: bounded) + } + + def Ans(implicit variables: (List[Term], List[Term])) = { + val (answer, _) = variables + atom(RSA("Ans"), answer) + } + + def apply(name: Any): IRI = + IRI.create( + Prefixes.getPrefixIRIsByPrefixName.get("rsa:").getIRI + name.toString + ) +} diff --git a/src/test/scala/rsacomb/CanonicalModelSpec.scala b/src/test/scala/rsacomb/CanonicalModelSpec.scala deleted file mode 100644 index 06602e3..0000000 --- a/src/test/scala/rsacomb/CanonicalModelSpec.scala +++ /dev/null @@ -1,369 +0,0 @@ -package rsacomb - -import java.io.File -import org.scalatest.LoneElement -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import org.semanticweb.owlapi.model._ -import uk.ac.manchester.cs.owl.owlapi._ -import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer - -import tech.oxfordsemantic.jrdfox.logic.datalog.Rule -import tech.oxfordsemantic.jrdfox.logic.expression.Variable - -import scala.collection.JavaConverters._ - -import rsacomb.util.{RDFoxHelpers, RSA} - -object Ontology1_CanonicalModelSpec { - - /* Renderer to display OWL Axioms with DL syntax*/ - val renderer = new DLSyntaxObjectRenderer() - - def base(str: String): IRI = - IRI.create("http://example.com/rsa_example.owl#" + str) - - val ontology_path: File = new File("examples/example1.ttl") - val ontology = RSAOntology(ontology_path) - val program = ontology.canonicalModel - - val roleR = new OWLObjectPropertyImpl(base("R")) - val roleS = new OWLObjectPropertyImpl(base("S")) - val roleT = new OWLObjectPropertyImpl(base("T")) - val roleR_inv = roleR.getInverseProperty() - val roleS_inv = roleS.getInverseProperty() - val roleT_inv = roleT.getInverseProperty() - - val AsubClassOfD = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("A")), - new OWLClassImpl(base("D")), - Seq().asJava - ) - - val DsomeValuesFromRB = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("D")), - new OWLObjectSomeValuesFromImpl( - roleR, - new OWLClassImpl(base("B")) - ), - Seq().asJava - ) - - val BsomeValuesFromSD = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("B")), - new OWLObjectSomeValuesFromImpl( - roleS, - new OWLClassImpl(base("D")) - ), - Seq().asJava - ) - - val AsomeValuesFromSiC = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("A")), - new OWLObjectSomeValuesFromImpl( - roleS_inv, - new OWLClassImpl(base("C")) - ), - Seq().asJava - ) - - val SsubPropertyOfT = new OWLSubObjectPropertyOfAxiomImpl( - new OWLObjectPropertyImpl(base("S")), - new OWLObjectPropertyImpl(base("T")), - Seq().asJava - ) - -} - -class Ontology1_CanonicalModelSpec - extends AnyFlatSpec - with Matchers - with LoneElement { - - import Ontology1_CanonicalModelSpec._ - - "The program generated from Example #1" should "not be empty" in { - program.rules should not be empty - } - - renderer.render(AsubClassOfD) should "be converted into a single Rule" in { - val varX = Variable.create("X") - val visitor = program.RuleGenerator - val rules = AsubClassOfD.accept(visitor) - rules.loneElement shouldBe a[Rule] - } - - // Role R // - - renderer.render(roleR) should "be safe" in { - ontology.unsafeRoles should not contain roleR - } - - it should "have 3 elements in its conflict set" in { - ontology.confl(roleR) should have size 3 - } - - it should "contain S in its conflict set" in { - ontology.confl(roleR) should contain(roleS) - } - - it should "contain T in its conflict set" in { - ontology.confl(roleR) should contain(roleT) - } - - it should ("contain " + renderer.render( - roleR_inv - ) + " in its conflict set") in { - ontology.confl(roleR) should contain(roleR_inv) - } - - // Role S // - - renderer.render(roleS) should "be safe" in { - ontology.unsafeRoles should not contain roleS - } - - it should "have 3 elements in its conflict set" in { - ontology.confl(roleS) should have size 3 - } - - it should "contain R in its conflict set" in { - ontology.confl(roleS) should contain(roleR) - } - - it should ("contain " + renderer.render( - roleS_inv - ) + " in its conflict set") in { - ontology.confl(roleS) should contain(roleS_inv) - } - - it should ("contain " + renderer.render( - roleT_inv - ) + " in its conflict set") in { - ontology.confl(roleS) should contain(roleT_inv) - } - - // S⁻ - - renderer.render(roleS_inv) should "be unsafe" in { - ontology.unsafeRoles should contain(roleS_inv) - } - - renderer.render( - AsomeValuesFromSiC - ) should "produce 1 rule" in { - val varX = Variable.create("X") - val visitor = program.RuleGenerator - val rules = AsomeValuesFromSiC.accept(visitor) - rules should have length 1 - } - - renderer.render( - DsomeValuesFromRB - ) should "have a 'cycle' set of 48 elements" in { - // Cycle introduces a new constant for each possible triple (the - // order among triples is total). In this example there are 4 - // concept names and R has 3 safe roles in its conflict set (S, T, - // Inv(R)). Triples are - // (concept, role, concept) - // and hence we have 4*3*4=48 new constants introduced. - ontology.cycle(DsomeValuesFromRB) should have size 48 - } - - it should "produce 5 rules" in { - // Rule 1 provides 1 rule (split in 2) + 48 fact - // Rule 2 provides 0 rules - // Rule 3 provides 48 rule (split in 2) - // Then (1*2 + 48) + (0) + (48*2) = 146 - val varX = Variable.create("X") - val visitor = program.RuleGenerator - val rules = DsomeValuesFromRB.accept(visitor) - rules should have length 146 - } - - renderer.render( - BsomeValuesFromSD - ) should "have a 'cycle' set of 32 elements" in { - // Cycle introduces a new constant for each possible triple (the - // order among triples is total). In this example there are 4 - // concept names and S has 2 safe roles in its conflict set (R, - // Inv(T)). Triples are - // (concept, role, concept) - // and hence we have 4*2*4=32 new constants introduced. - ontology.cycle(BsomeValuesFromSD) should have size 32 - } - - it should "produce 5 rules" in { - // Rule 1 provides 1 rule (split in 2) + 32 fact - // Rule 2 provides 0 rules - // Rule 3 provides 32 rule (split in 2) - // Then (1*2 + 32) + (0) + (32*2) = 98 - val varX = Variable.create("X") - val visitor = program.RuleGenerator - val rules = DsomeValuesFromRB.accept(visitor) - rules should have length 146 - } - - renderer.render( - SsubPropertyOfT - ) should "produce 2 rules" in { - val varX = Variable.create("X") - val visitor = program.RuleGenerator - val rules = SsubPropertyOfT.accept(visitor) - rules should have length 2 - } - -} - -object Ontology2_CanonicalModelSpec { - - /* Renderer to display OWL Axioms with DL syntax*/ - val renderer = new DLSyntaxObjectRenderer() - - def base(str: String): IRI = - IRI.create("http://example.com/rsa_example.owl#" + str) - - val ontology_path: File = new File("examples/example2.owl") - val ontology = RSAOntology(ontology_path) - val program = ontology.canonicalModel - - val roleR = new OWLObjectPropertyImpl(base("R")) - val roleS = new OWLObjectPropertyImpl(base("S")) - val roleT = new OWLObjectPropertyImpl(base("T")) - val roleP = new OWLObjectPropertyImpl(base("P")) - val roleR_inv = roleR.getInverseProperty() - val roleS_inv = roleS.getInverseProperty() - val roleT_inv = roleT.getInverseProperty() - val roleP_inv = roleP.getInverseProperty() - - val AsomeValuesFromRB = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("A")), - new OWLObjectSomeValuesFromImpl( - roleR, - new OWLClassImpl(base("B")) - ), - Seq().asJava - ) - - val BsomeValuesFromSC = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("B")), - new OWLObjectSomeValuesFromImpl( - roleS, - new OWLClassImpl(base("C")) - ), - Seq().asJava - ) - - val CsomeValuesFromTD = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("C")), - new OWLObjectSomeValuesFromImpl( - roleT, - new OWLClassImpl(base("D")) - ), - Seq().asJava - ) - - val DsomeValuesFromPA = new OWLSubClassOfAxiomImpl( - new OWLClassImpl(base("D")), - new OWLObjectSomeValuesFromImpl( - roleP, - new OWLClassImpl(base("A")) - ), - Seq().asJava - ) - -} - -class Ontology2_CanonicalModelSpec - extends AnyFlatSpec - with Matchers - with LoneElement { - - import Ontology2_CanonicalModelSpec._ - - "The program generated from Example #1" should "not be empty" in { - program.rules should not be empty - } - - // Role R // - - renderer.render(roleR) should "be unsafe" in { - ontology.unsafeRoles should contain(roleR) - } - - it should "have only its inverse in its conflict set" in { - ontology.confl(roleR).loneElement shouldBe roleR_inv - } - - // Role S // - - renderer.render(roleS) should "be unsafe" in { - ontology.unsafeRoles should contain(roleS) - } - - it should "have only its inverse in its conflict set" in { - ontology.confl(roleS).loneElement shouldBe roleS_inv - } - - // Role T // - - renderer.render(roleT) should "be unsafe" in { - ontology.unsafeRoles should contain(roleT) - } - - it should "have only its inverse in its conflict set" in { - ontology.confl(roleT).loneElement shouldBe roleT_inv - } - - // Role P // - - renderer.render(roleP) should "be unsafe" in { - ontology.unsafeRoles should contain(roleP) - } - - it should "have only its inverse in its conflict set" in { - ontology.confl(roleP).loneElement shouldBe roleP_inv - } - - // A ⊑ ∃ R.B - - renderer.render( - AsomeValuesFromRB - ) should "produce 1 rule" in { - val visitor = program.RuleGenerator - val rules = AsomeValuesFromRB.accept(visitor) - rules should have length 1 - } - - // B ⊑ ∃ S.C - - renderer.render( - BsomeValuesFromSC - ) should "produce 1 rule" in { - val visitor = program.RuleGenerator - val rules = BsomeValuesFromSC.accept(visitor) - rules should have length 1 - } - - // C ⊑ ∃ T.D - - renderer.render( - CsomeValuesFromTD - ) should "produce 1 rule" in { - val visitor = program.RuleGenerator - val rules = CsomeValuesFromTD.accept(visitor) - rules should have length 1 - } - - // D ⊑ ∃ P.A - - renderer.render( - DsomeValuesFromPA - ) should "produce 1 rule" in { - val visitor = program.RuleGenerator - val rules = DsomeValuesFromPA.accept(visitor) - rules should have length 1 - } - -} diff --git a/src/test/scala/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/rsacomb/FilteringProgramSpecs.scala deleted file mode 100644 index 66c1cae..0000000 --- a/src/test/scala/rsacomb/FilteringProgramSpecs.scala +++ /dev/null @@ -1,397 +0,0 @@ -package rsacomb - -import java.io.File -import java.util.{ArrayList => JList} -import org.scalatest.LoneElement -import org.scalatest.Inspectors -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom -import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI} -import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{Query, SelectQuery} -import tech.oxfordsemantic.jrdfox.Prefixes - -import scala.collection.JavaConverters._ - -import rsacomb.util.RDFoxHelpers - -object FilteringProgramSpec { - - val prefixes = new Prefixes() - prefixes.declarePrefix( - ":", - "http://slegger.gitlab.io/slegge-obda/ontology/subsurface-exploration#" - ) - prefixes.declarePrefix("rdf:", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") - prefixes.declarePrefix("rdfs:", "http://www.w3.org/2000/01/rdf-schema#") - prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#") - - // DEBUG: Quick helper functions - def v(v: String): Variable = Variable.create(v) - def c(c: String): IRI = IRI.create(":" + c) - - // QUERY 0 - - val query0 = RDFoxHelpers - .parseSelectQuery(""" - SELECT ?subj - WHERE { - ?subj ?pred ?obj - } - """, prefixes) - .get - - // val query0 = Query.create( - // QueryType.SELECT, - // false, - // List(v("subj")).asJava, - // Atom.rdf(v("subj"), v("pred"), v("obj")) - // ) - - // QUERY 1 - - val query1 = RDFoxHelpers - .parseSelectQuery(""" - SELECT * - WHERE { - ?w a :Wellbore - } - """, prefixes) - .get - - // val query1 = Query.create( - // QueryType.SELECT, - // false, - // List(v("w")).asJava, - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")) - // ) - - // QUERY 2 - - val query2 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT * - WHERE { - ?w a :Wellbore ; - :wellboreDocument ?doc . - ?doc :hasURL ?document_hyperlink - } - """, - prefixes - ) - .get - - // val query2 = Query.create( - // QueryType.SELECT, - // false, - // List(v("w"), v("doc"), v("document_hyperlink")).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("wellboreDocument"), v("doc")), - // Atom.rdf(v("doc"), c("hasURL"), v("document_hyperlink")) - // ) - // ) - - // QUERY 3 - - val query3 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT ?wellbore ?formation_pressure - WHERE { - ?w a :Wellbore ; - :name ?wellbore ; - :hasFormationPressure ?fp . - ?fp :valueInStandardUnit ?formation_pressure - } - """, - prefixes - ) - .get - - // val query3 = Query.create( - // QueryType.SELECT, - // false, - // List(v("wellbore"), v("formation_pressure")).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("name"), v("wellbore")), - // Atom.rdf(v("w"), c("hasFormationPressure"), v("fp")), - // Atom.rdf(v("fp"), c("valueInStandardUnit"), v("formation_pressure")) - // ) - // ) - - // QUERY 4 - - val query4 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT * - WHERE { - ?w a :Wellbore ; - :hasGeochemicalMeasurement ?measurement . - ?measurement :cgType ?cgtype ; - :peakName ?peakType ; - :peakHeight ?peak_height ; - :peakAmount ?peak_amount - } - """, - prefixes - ) - .get - - // val query4 = Query.create( - // QueryType.SELECT, - // false, - // List( - // v("w"), - // v("measurement"), - // v("cgtype"), - // v("peakType"), - // v("peak_height"), - // v("peak_amount") - // ).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("hasGeochemicalMeasurement"), v("measurement")), - // Atom.rdf(v("measurement"), c("cgType"), v("cgtype")), - // Atom.rdf(v("measurement"), c("peakName"), v("peakType")), - // Atom.rdf(v("measurement"), c("peakHeight"), v("peak_height")), - // Atom.rdf(v("measurement"), c("peakAmount"), v("peak_amount")) - // ) - // ) - - // QUERY 5 - - val query5 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT ?wellbore ?unit_name ?discovery - WHERE { - ?w a :Wellbore ; - :name ?wellbore ; - :hasWellboreInterval ?c_int ; - :hasWellboreInterval ?f_int . - ?c_int :hasUnit ?c_unit . - ?c_unit :name ?unit_name . - ?f_int a :FluidZone ; - :name ?discovery ; - :overlapsWellboreInterval ?c_int - } - """, - prefixes - ) - .get - - // val query5 = Query.create( - // QueryType.SELECT, - // false, - // List(v("wellbore"), v("unit_name"), v("discovery")).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("name"), v("wellbore")), - // Atom.rdf(v("w"), c("hasWellboreInterval"), v("c_int")), - // Atom.rdf(v("w"), c("hasWellboreInterval"), v("f_int")), - // Atom.rdf(v("c_int"), c("hasUnit"), v("c_unit")), - // Atom.rdf(v("c_unit"), c("name"), v("unit_name")), - // Atom.rdf(v("f_int"), IRI.RDF_TYPE, c("FluidZone")), - // Atom.rdf(v("f_int"), c("name"), v("discovery")), - // Atom.rdf(v("f_int"), c("overlapsWellboreInterval"), v("c_int")) - // ) - // ) - - // QUERY 6 - - val query6 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT DISTINCT ?wellbore ?content - WHERE { - ?w a :Wellbore ; - :name ?wellbore ; - :hasWellboreInterval ?int . - ?int a :FluidZone ; - :fluidZoneContent ?content - } - """, - prefixes - ) - .get - - // val query6 = Query.create( - // QueryType.SELECT, - // true, - // List(v("wellbore"), v("content")).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("name"), v("wellbore")), - // Atom.rdf(v("w"), c("hasWellboreInterval"), v("int")), - // Atom.rdf(v("int"), IRI.RDF_TYPE, c("FluidZone")), - // Atom.rdf(v("int"), c("fluidZoneContent"), v("content")) - // ) - // ) - - // QUERY 7 - - val query7 = RDFoxHelpers - .parseSelectQuery( - """ - SELECT ?wName ?sample ?porosity ?top_depth_md ?bot_depth_md - WHERE { - ?w a :Wellbore ; - :name ?wName ; - :hasWellboreInterval ?z . - ?z :hasUnit ?u . - ?u :name ?strat_unit_name . - ?wellbore :hasWellboreInterval ?cored_int . - ?c :extractedFrom ?cored_int ; - :hasCoreSample ?sample . - ?sample :hasDepth ?sample_depth . - ?sample_depth - :inWellboreInterval ?z . - ?sample :hasPorosity ?p . - ?p :valueInStandardUnit ?porosity . - ?z :hasTopDepth ?top . - ?top a :MeasuredDepth ; - :valueInStandardUnit ?top_depth_md . - ?z :hasBottomDepth ?bot . - ?bot a :MeasuredDepth ; - :valueInStandardUnit ?bot_depth_md - } - """, - prefixes - ) - .get - - // val query7 = Query.create( - // QueryType.SELECT, - // false, - // List( - // v("wName"), - // v("sample"), - // v("porosity"), - // v("top_depth_md"), - // v("bot_depth_md") - // ).asJava, - // Conjunction.create( - // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), - // Atom.rdf(v("w"), c("name"), v("wName")), - // Atom.rdf(v("w"), c("hasWellboreInterval"), v("z")), - // Atom.rdf(v("z"), c("hasUnit"), v("u")), - // Atom.rdf(v("u"), c("name"), v("strat_unit_name")), - // Atom.rdf(v("wellbore"), c("hasWellboreInterval"), v("cored_int")), - // Atom.rdf(v("c"), c("extractedFrom"), v("cored_int")), - // Atom.rdf(v("c"), c("hasCoreSample"), v("sample")), - // Atom.rdf(v("sample"), c("hasDepth"), v("sample_depth")), - // Atom.rdf(v("sample_depth"), c("inWellboreInterval"), v("z")), - // Atom.rdf(v("sample"), c("hasPorosity"), v("p")), - // Atom.rdf(v("p"), c("valueInStandardUnit"), v("porosity")), - // Atom.rdf(v("z"), c("hasTopDepth"), v("top")), - // Atom.rdf(v("top"), IRI.RDF_TYPE, c("MeasuredDepth")), - // Atom.rdf(v("top"), c("valueInStandardUnit"), v("top_depth_md")), - // Atom.rdf(v("z"), c("hasBottomDepth"), v("bot")), - // Atom.rdf(v("bot"), IRI.RDF_TYPE, c("MeasuredDepth")), - // Atom.rdf(v("bot"), c("valueInStandardUnit"), v("bot_depth_md")) - // ) - // ) - - val queries = - List(query0, query1, query2, query3, query4, query5, query6, query7) -} - -class FilteringProgramSpec - extends AnyFlatSpec - with Matchers - with LoneElement - with Inspectors { - - import FilteringProgramSpec._ - - "Queries" should "have distinct answer and bounded variables" in { - for (query <- queries) { - val program = new FilteringProgram(query, List()) - forAll(program.answer) { v => program.bounded should not contain v } - forAll(program.bounded) { v => program.answer should not contain v } - } - } - - "Query 0" should "have {?obj, ?pred} as bounded variables" in { - val pred = Variable.create("obj") - val obj = Variable.create("pred") - val program = new FilteringProgram(query0, List()) - program.bounded should contain theSameElementsAs List(pred, obj) - } - - "Query 1" should "have no bounded variable" in { - val program = new FilteringProgram(query1, List()) - program.bounded shouldBe empty - } - - "Query 2" should "have no bounded variable" in { - val program = new FilteringProgram(query2, List()) - program.bounded shouldBe empty - } - - "Query 3" should "have {?w, ?fp} as bounded variables" in { - val w = Variable.create("w") - val fp = Variable.create("fp") - val program = new FilteringProgram(query3, List()) - program.bounded should contain theSameElementsAs List(w, fp) - } - - "Query 4" should "have no bounded variable" in { - val program = new FilteringProgram(query4, List()) - program.bounded shouldBe empty - } - - "Query 5" should "have a non-empty bounded set" in { - val w = Variable.create("w") - val c_int = Variable.create("c_int") - val f_int = Variable.create("f_int") - val c_unit = Variable.create("c_unit") - val program = new FilteringProgram(query5, List()) - program.bounded should contain theSameElementsAs List( - w, - c_int, - f_int, - c_unit - ) - } - - "Query 6" should "have a non-empty bounded set" in { - val w = Variable.create("w") - val int = Variable.create("int") - val program = new FilteringProgram(query6, List()) - program.bounded should contain theSameElementsAs List(w, int) - } - - "Query 7" should "have a non-empty bounded set" in { - val w = Variable.create("w") - val z = Variable.create("z") - val u = Variable.create("u") - val strat_unit_name = Variable.create("strat_unit_name") - val wellbore = Variable.create("wellbore") - val cored_int = Variable.create("cored_int") - val c = Variable.create("c") - val sample_depth = Variable.create("sample_depth") - val p = Variable.create("p") - val top = Variable.create("top") - val bot = Variable.create("bot") - val program = new FilteringProgram(query7, List()) - program.bounded should contain theSameElementsAs List( - w, - z, - u, - strat_unit_name, - wellbore, - cored_int, - c, - sample_depth, - p, - top, - bot - ) - } -} diff --git a/src/test/scala/rsacomb/OWLAxiomSpec.scala b/src/test/scala/rsacomb/OWLAxiomSpec.scala deleted file mode 100644 index 65333f5..0000000 --- a/src/test/scala/rsacomb/OWLAxiomSpec.scala +++ /dev/null @@ -1,335 +0,0 @@ -package rsacomb - -import java.util.{ArrayList => JList} -import org.scalatest.LoneElement -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import org.semanticweb.owlapi.model.OWLClassExpression -import uk.ac.manchester.cs.owl.owlapi.{OWLSubClassOfAxiomImpl} -import uk.ac.manchester.cs.owl.owlapi.{ - OWLClassImpl, - OWLObjectSomeValuesFromImpl, - OWLObjectIntersectionOfImpl, - OWLObjectOneOfImpl, - OWLObjectAllValuesFromImpl, - OWLObjectMaxCardinalityImpl, - OWLNamedIndividualImpl -} -import uk.ac.manchester.cs.owl.owlapi.{OWLObjectPropertyImpl} -import org.semanticweb.owlapi.model.{OWLAxiom} - -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - Rule, - BindAtom, - TupleTableAtom, - TupleTableName -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - FunctionCall, - Term, - Variable, - Literal -} - -import org.semanticweb.owlapi.model.{IRI => OWLIRI} -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFIRI} - -import rsacomb.util.RSA - -object OWLAxiomSpec { - - // IRI - val iri_Professor = OWLIRI.create("univ:Professor") - val iri_Female = OWLIRI.create("std:Female") - val iri_Student = OWLIRI.create("univ:Student") - val iri_PartTimeStudent = OWLIRI.create("univ:PartTimeStudent") - val iri_Worker = OWLIRI.create("univ:Worker") - val iri_alice = OWLIRI.create("univ:alice") - val iri_supervises = OWLIRI.create("univ:supervises") - val iri_hasSupervisor = OWLIRI.create("univ:hasSupervisor") - val iri_sameAs = OWLIRI.create("owl:sameAs") - - // RDFox Terms - val term_x = Variable.create("x") - val term_y = Variable.create("y") - val term_z = Variable.create("z") - val term_c1 = RSA("c_1") - val term_c2 = RSA("c_2") - val term_alice = RDFIRI.create("univ:alice") - - // RDFox Predicates - val pred_sameAs = TupleTableName.create("owl:sameAs") - val pred_Professor = TupleTableName.create(iri_Professor.getIRIString) - val pred_hasSupervisor = TupleTableName.create(iri_hasSupervisor.getIRIString) - - // OWL Classes - // Name Class corresponding to - // - // Professor - // - val class_Professor = new OWLClassImpl(iri_Professor) - val class_Female = new OWLClassImpl(iri_Female) - val class_Student = new OWLClassImpl(iri_Student) - val class_PartTimeStudent = new OWLClassImpl(iri_PartTimeStudent) - val class_Worker = new OWLClassImpl(iri_Worker) - val class_OWLClass = class_Professor - // Class Conjunction corresponding to - // - // Student ∧ Worker - // - val class_OWLObjectIntersectionOf = { - val conjuncts = new JList[OWLClassExpression]() - conjuncts.add(class_Student) - conjuncts.add(class_Worker) - new OWLObjectIntersectionOfImpl(conjuncts) - } - // Singleton Class corresponding to - // - // { alice } - // - val class_OWLObjectOneOf = - new OWLObjectOneOfImpl( - new OWLNamedIndividualImpl(iri_alice) - ) - // Object Existential Restiction corresponding to - // - // ∃ hasSupervisor.Professor - // - val class_OWLObjectSomeValuesFrom = - new OWLObjectSomeValuesFromImpl( - new OWLObjectPropertyImpl(iri_hasSupervisor), - class_Professor - ) - // Object Max Cardinality Restriction corresponding to - // - // ≤ 1 hasSupervisor . Professor - val class_OWLObjectMaxCardinality = - new OWLObjectMaxCardinalityImpl( - new OWLObjectPropertyImpl(iri_hasSupervisor), - 1, - class_Professor - ) - - // OWL Axioms - // Axiom SubClassOf corresponding to - // - // Student ∧ Worker -> PartTimeStudent - // - val axiom_OWLSubClassOf1 = - new OWLSubClassOfAxiomImpl( - class_OWLObjectIntersectionOf, - class_PartTimeStudent, - new JList() - ) - - // Axiom SubClassOf corresponding to - // - // Student -> ∃ hasSupervisor.Professor - // - val axiom_OWLSubClassOf2 = - new OWLSubClassOfAxiomImpl( - class_Student, - class_OWLObjectSomeValuesFrom, - new JList() - ) - - // Axiom SubClassOf corresponding to - // - // ∃ hasSupervisor.Professor -> Student - // - val axiom_OWLSubClassOf3 = - new OWLSubClassOfAxiomImpl( - class_OWLObjectSomeValuesFrom, - class_Student, - new JList() - ) - - // Axiom SubClassOf corresponding to - // - // Student -> { alice } - // - val axiom_OWLSubClassOf4 = - new OWLSubClassOfAxiomImpl( - class_Student, - class_OWLObjectOneOf, - new JList() - ) - - // Axiom SubClassOf corresponding to - // - // Student -> ≤1 hasSupervisor.Professor - // - val axiom_OWLSubClassOf5 = - new OWLSubClassOfAxiomImpl( - class_Student, - class_OWLObjectMaxCardinality, - new JList() - ) - - def convertAxiom( - axiom: OWLAxiom, - term: Term, - skolem: SkolemStrategy = SkolemStrategy.None - ): List[Rule] = { - axiom.accept(RDFoxAxiomConverter(term, List())) - } - -} // object OWLAxiomSpec - -class OWLAxiomSpec extends AnyFlatSpec with Matchers with LoneElement { - - // Import required data - import OWLAxiomSpec._ - // Implicit convertion from IRI in OWLAPI to IRI in JRDFox - import rsacomb.implicits.RDFox._ - - // OWLSubClassOfAxiom #1 - axiom_OWLSubClassOf1.toString should "be converted into a singleton List[Rule]" in { - val result = convertAxiom(axiom_OWLSubClassOf1, term_x) - result.loneElement shouldBe a[Rule] - } - - it should "contain a conjuction of atoms (Student[?x],Worker[?x]) in the body of the rule" in { - val result = convertAxiom(axiom_OWLSubClassOf1, term_x) - val body = List( - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student), - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Worker) - ) - result.loneElement.getBody should contain theSameElementsAs body - } - - it should "contain a single atom (PartTimeStudent[?x]) in the head of the rule" in { - val result = convertAxiom(axiom_OWLSubClassOf1, term_x) - val head = TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_PartTimeStudent) - result.loneElement.getHead.loneElement should be(head) - } - - // OWLSubClassOfAxiom #2 (w/ constant skolemization) - (axiom_OWLSubClassOf2.toString + "\n(w/ constant skolemization)") should - "be converted into a singleton List[Rule]" in { - val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) - val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - result.loneElement shouldBe a[Rule] - } - - it should "contain a single atom (Student[?x]) in the body of the rule" in { - val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) - val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - val body = - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student.getIRIString) - result.loneElement.getBody.loneElement should equal(body) - } - - // it should "contain a conjuction of atoms (hasSupervisor[?x,?c],Professor[?c]) in the head of the rule" in { - // val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) - // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - // val term_c = RSA.rsa(skolem.const.getIRI) - // val head = List( - // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_c), - // TupleTableAtom.rdf(term_c, RDFIRI.RDF_TYPE, iri_Professor) - // ) - // result.loneElement.getHead should contain theSameElementsAs (head) - // } - - // OWLSubClassOfAxiom #2 (w/ skolemization) - (axiom_OWLSubClassOf2.toString + "\n(w/ skolemization)") should - "be converted into a singleton List[Rule]" in { - val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) - val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - result.loneElement shouldBe a[Rule] - } - - it should "contain an atom (Student[?x]) in the body of the rule" in { - val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) - val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - val body = - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) - result.loneElement.getBody should contain(body) - } - - // it should "contain a built-in function call (BIND(?y,SKOLEM(?f,?x))) in the body of the rule" in { - // val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) - // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - // val call = - // BindAtom.create(BuiltinFunctionCall.create("SKOLEM", term_x), term_y) - // result.loneElement.getBody should contain(call) - // } - - // it should "contain a conjuction of atom (hasSupervisor[?x,?y],Professor[?y]) in the head of the rule" in { - // val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) - // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) - // val head = List( - // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), - // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor) - // ) - // result.loneElement.getHead should contain theSameElementsAs head - // } - - // OWLSubClassOfAxiom #3 - axiom_OWLSubClassOf3.toString should "be converted into a singleton List[Rule]" in { - val result = convertAxiom(axiom_OWLSubClassOf3, term_x) - result.loneElement shouldBe a[Rule] - } - - // it should "contain a conjunction of atoms (hasSupervisor[?x,?y],Professor[?y]) in the body of the rule" in { - // val result = convertAxiom(axiom_OWLSubClassOf3, term_x) - // val body = List( - // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), - // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor) - // ) - // result.loneElement.getBody should contain theSameElementsAs body - // } - - it should "contain a single atom (Student[?x]) in the head of the rule" in { - val result = convertAxiom(axiom_OWLSubClassOf3, term_x) - val head = - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) - result.loneElement.getHead.loneElement should be(head) - } - - // OWLSubClassOfAxiom #4 - axiom_OWLSubClassOf4.toString should "be converted into a singleton List[Rule]" in { - val result = convertAxiom(axiom_OWLSubClassOf4, term_x) - result.loneElement shouldBe a[Rule] - } - - it should "contain a single atoms (Student[?x]) in the body of the rule" in { - val result = convertAxiom(axiom_OWLSubClassOf4, term_x) - val body = - TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) - result.loneElement.getBody.loneElement should be(body) - } - - it should "contain a single atom (sameAs[?x,alice])) in the head of the rule" in { - val result = convertAxiom(axiom_OWLSubClassOf4, term_x) - val head = TupleTableAtom.rdf(term_x, RDFIRI.SAME_AS, term_alice) - result.loneElement.getHead.loneElement should be(head) - } - - // OWLSubClassOfAxiom #5 - axiom_OWLSubClassOf5.toString should "be converted into a singleton List[Rule]" in { - val result = convertAxiom(axiom_OWLSubClassOf5, term_x) - result.loneElement shouldBe a[Rule] - } - - // it should "contain a conjunction of atoms (...) in the body of the rule" in { - // val result = convertAxiom(axiom_OWLSubClassOf5, term_x) - // val body = List( - // TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student), - // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), - // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor), - // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_z), - // TupleTableAtom.rdf(term_z, RDFIRI.RDF_TYPE, iri_Professor) - // ) - // result.loneElement.getBody should contain theSameElementsAs body - // } - - // it should "contain a single atom (sameAs[?x,?z])) in the head of the rule" in { - // val result = convertAxiom(axiom_OWLSubClassOf5, term_x) - // val head = TupleTableAtom.rdf(term_y, RDFIRI.SAME_AS, term_z) - // result.loneElement.getHead.loneElement should be(head) - // } - -} // class OWLAxiomSpec diff --git a/src/test/scala/rsacomb/OWLClassSpec.scala b/src/test/scala/rsacomb/OWLClassSpec.scala deleted file mode 100644 index 27e0872..0000000 --- a/src/test/scala/rsacomb/OWLClassSpec.scala +++ /dev/null @@ -1,273 +0,0 @@ -package rsacomb - -import java.util.{ArrayList => JList} - -import org.scalatest.LoneElement -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import org.semanticweb.owlapi.model.OWLClassExpression -import uk.ac.manchester.cs.owl.owlapi.{ - OWLClassImpl, - OWLObjectSomeValuesFromImpl, - OWLObjectIntersectionOfImpl, - OWLObjectOneOfImpl, - OWLObjectAllValuesFromImpl, - OWLObjectMaxCardinalityImpl, - OWLNamedIndividualImpl -} -import uk.ac.manchester.cs.owl.owlapi.{OWLObjectPropertyImpl} -import org.semanticweb.owlapi.model.IRI -import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFIRI} - -import tech.oxfordsemantic.jrdfox.logic.Datatype -import tech.oxfordsemantic.jrdfox.logic.datalog.{ - TupleTableAtom, - TupleTableName, - BindAtom -} -import tech.oxfordsemantic.jrdfox.logic.expression.{ - FunctionCall, - Term, - Variable, - Literal -} - -import rsacomb.RDFoxRuleShards -import rsacomb.util.RSA - -object OWLClassSpec { - - // IRI - val iri_Professor = IRI.create("univ:Professor") - val iri_Female = IRI.create("std:Female") - val iri_Student = IRI.create("univ:Student") - val iri_Worker = IRI.create("univ:Worker") - val iri_alice = IRI.create("univ:alice") - val iri_supervises = IRI.create("univ:supervises") - val iri_hasSupervisor = IRI.create("univ:hasSupervisor") - - // RDFox Terms - val term_x = Variable.create("x") - val term_y = Variable.create("y") - val term_c1 = RSA("c_1") - val term_c2 = RSA("c_2") - val term_alice = RDFIRI.create("univ:alice") - - // RDFox Predicates - val pred_sameAs = TupleTableName.create("owl:sameAs") - val pred_Professor = TupleTableName.create(iri_Professor.getIRIString) - val pred_hasSupervisor = TupleTableName.create(iri_hasSupervisor.getIRIString) - - // OWL Classes - // Name Class corresponding to - // - // Professor - // - val class_Professor = new OWLClassImpl(iri_Professor) - val class_Female = new OWLClassImpl(iri_Female) - val class_Student = new OWLClassImpl(iri_Student) - val class_Worker = new OWLClassImpl(iri_Worker) - val class_OWLClass = class_Professor - - // Class Conjunction corresponding to - // - // Female ∧ Student ∧ Worker - // - val class_OWLObjectIntersectionOf = { - val conjuncts = new JList[OWLClassExpression]() - conjuncts.add(class_Female) - conjuncts.add(class_Student) - conjuncts.add(class_Worker) - new OWLObjectIntersectionOfImpl(conjuncts) - } - // Singleton Class corresponding to - // - // { alice } - // - val class_OWLObjectOneOf = - new OWLObjectOneOfImpl( - new OWLNamedIndividualImpl(iri_alice) - ) - // Object Existential Restiction corresponding to - // - // ∃ hasSupervisor.Professor - // - val class_OWLObjectSomeValuesFrom = - new OWLObjectSomeValuesFromImpl( - new OWLObjectPropertyImpl(iri_hasSupervisor), - class_Professor - ) - // Object Max Cardinality Restriction corresponding to - // - // ≤1 hasSupervisor.Professor - val class_OWLObjectMaxCardinality = - new OWLObjectMaxCardinalityImpl( - new OWLObjectPropertyImpl(iri_hasSupervisor), - 1, - class_Professor - ) -} // object OWLClassSpec - -class OWLClassSpec extends AnyFlatSpec with Matchers with LoneElement { - // Import required data - import OWLClassSpec._ - - // OWLClass - class_OWLClass.toString should "be converted into a RDFoxRuleShards" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLClass.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - it should "have a single TupleTableAtom in its result list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLClass.accept(visitor) - result.res.loneElement shouldBe an[TupleTableAtom] - } - - it should "have an empty extension list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLClass.accept(visitor) - result.ext shouldBe empty - } - - // OWLObjectIntersectionOf - class_OWLObjectIntersectionOf.toString should "be converted into a RDFoxRuleShards" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectIntersectionOf.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - it should "be converted in the union of its converted conjuncts" in { - val visitor = RDFoxClassExprConverter(term_x) - val result1 = class_OWLObjectIntersectionOf.accept(visitor) - val result2 = RDFoxClassExprConverter.merge( - List( - class_Female.accept(visitor), - class_Student.accept(visitor), - class_Worker.accept(visitor) - ) - ) - result1.res should contain theSameElementsAs result2.res - result1.ext should contain theSameElementsAs result2.ext - } - - // OWLObjectOneOf - class_OWLObjectOneOf.toString should "be converted into a RDFoxRuleShards" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectOneOf.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - // it should "be converted into a single TupleTableAtom" in { - // val visitor = RDFoxClassExprConverter(term_x) - // val result = class_OWLObjectOneOf.accept(visitor) - // result.res.loneElement should (be (a [TupleTableAtom]) and have ('tupleTableName (pred_sameAs))) - // } - - it should "have an empty extension list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectOneOf.accept(visitor) - result.ext shouldBe empty - } - - // OWLObjectSomeValuesFrom - (class_OWLObjectSomeValuesFrom.toString ++ " w/o skolemization") should - "be converted into a RDFoxRuleShards" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - it should "have two TupleTableAtoms in its result list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - exactly(2, result.res) should (be(an[TupleTableAtom]) - //and have('numberOfArguments (3)) - ) - } - - it should "have an empty extension list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result.ext shouldBe empty - } - - (class_OWLObjectSomeValuesFrom.toString ++ " w/ skolemization") should - "be converted into a RDFoxRuleShards" in { - val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - it should "have exactly two TupleTableAtoms in its result list" in { - val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - exactly(2, result.res) should (be(an[TupleTableAtom]) - //and have('numberOfArguments (3)) - ) - } - - it should "should have a single SKOLEM call in the extension list" in { - val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result.ext.loneElement shouldBe a[BindAtom] - val builtin = result.ext.head.asInstanceOf[BindAtom].getExpression - builtin should (be(a[FunctionCall]) and have( - 'functionName ("SKOLEM") - )) - } - - (class_OWLObjectSomeValuesFrom.toString ++ " w/ constant skolemization") should - "be converted into a RDFoxRuleShards" in { - val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - it should "have exactly two TupleTableAtoms in its result list" in { - val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - exactly(2, result.res) should (be(an[TupleTableAtom]) - //and have('numberOfArguments (3)) - ) - } - - it should "have an empty extension list" in { - val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) - val visitor = RDFoxClassExprConverter(term_x, List(), skolem) - val result = class_OWLObjectSomeValuesFrom.accept(visitor) - result.ext shouldBe empty - } - - // OWLObjectMaxCardinalityImpl - class_OWLObjectMaxCardinality.toString should - "be converted into a RDFoxRuleShards" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectMaxCardinality.accept(visitor) - result shouldBe a[RDFoxRuleShards] - } - - // it should "have a single TupleTableAtom in the result list" in { - // val visitor = RDFoxClassExprConverter(term_x) - // val result = class_OWLObjectMaxCardinality.accept(visitor) - // result.res.loneElement should (be(an[TupleTableAtom]) and have( - // 'tupleTableName (pred_sameAs) - // )) - // } - - it should "have 4 TupleTableAtoms in its extension list" in { - val visitor = RDFoxClassExprConverter(term_x) - val result = class_OWLObjectMaxCardinality.accept(visitor) - exactly(4, result.ext) should (be(an[TupleTableAtom]) - //and have('numberOfArguments (3)) - ) - } - -} // class OWLClassSpec diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala new file mode 100644 index 0000000..376729c --- /dev/null +++ b/src/test/scala/uk/ac/ox/cs/rsacomb/CanonicalModelSpec.scala @@ -0,0 +1,370 @@ +package rsacomb + +import java.io.File +import org.scalatest.LoneElement +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import org.semanticweb.owlapi.model._ +import uk.ac.manchester.cs.owl.owlapi._ +import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer + +import tech.oxfordsemantic.jrdfox.logic.datalog.Rule +import tech.oxfordsemantic.jrdfox.logic.expression.Variable + +import scala.collection.JavaConverters._ + +import uk.ac.ox.cs.rsacomb.RSAOntology +import uk.ac.ox.cs.rsacomb.util.{RDFoxHelpers, RSA} + +object Ontology1_CanonicalModelSpec { + + /* Renderer to display OWL Axioms with DL syntax*/ + val renderer = new DLSyntaxObjectRenderer() + + def base(str: String): IRI = + IRI.create("http://example.com/rsa_example.owl#" + str) + + val ontology_path: File = new File("examples/example1.ttl") + val ontology = RSAOntology(ontology_path) + val program = ontology.canonicalModel + + val roleR = new OWLObjectPropertyImpl(base("R")) + val roleS = new OWLObjectPropertyImpl(base("S")) + val roleT = new OWLObjectPropertyImpl(base("T")) + val roleR_inv = roleR.getInverseProperty() + val roleS_inv = roleS.getInverseProperty() + val roleT_inv = roleT.getInverseProperty() + + val AsubClassOfD = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("A")), + new OWLClassImpl(base("D")), + Seq().asJava + ) + + val DsomeValuesFromRB = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("D")), + new OWLObjectSomeValuesFromImpl( + roleR, + new OWLClassImpl(base("B")) + ), + Seq().asJava + ) + + val BsomeValuesFromSD = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("B")), + new OWLObjectSomeValuesFromImpl( + roleS, + new OWLClassImpl(base("D")) + ), + Seq().asJava + ) + + val AsomeValuesFromSiC = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("A")), + new OWLObjectSomeValuesFromImpl( + roleS_inv, + new OWLClassImpl(base("C")) + ), + Seq().asJava + ) + + val SsubPropertyOfT = new OWLSubObjectPropertyOfAxiomImpl( + new OWLObjectPropertyImpl(base("S")), + new OWLObjectPropertyImpl(base("T")), + Seq().asJava + ) + +} + +class Ontology1_CanonicalModelSpec + extends AnyFlatSpec + with Matchers + with LoneElement { + + import Ontology1_CanonicalModelSpec._ + + "The program generated from Example #1" should "not be empty" in { + program.rules should not be empty + } + + renderer.render(AsubClassOfD) should "be converted into a single Rule" in { + val varX = Variable.create("X") + val visitor = program.RuleGenerator + val rules = AsubClassOfD.accept(visitor) + rules.loneElement shouldBe a[Rule] + } + + // Role R // + + renderer.render(roleR) should "be safe" in { + ontology.unsafeRoles should not contain roleR + } + + it should "have 3 elements in its conflict set" in { + ontology.confl(roleR) should have size 3 + } + + it should "contain S in its conflict set" in { + ontology.confl(roleR) should contain(roleS) + } + + it should "contain T in its conflict set" in { + ontology.confl(roleR) should contain(roleT) + } + + it should ("contain " + renderer.render( + roleR_inv + ) + " in its conflict set") in { + ontology.confl(roleR) should contain(roleR_inv) + } + + // Role S // + + renderer.render(roleS) should "be safe" in { + ontology.unsafeRoles should not contain roleS + } + + it should "have 3 elements in its conflict set" in { + ontology.confl(roleS) should have size 3 + } + + it should "contain R in its conflict set" in { + ontology.confl(roleS) should contain(roleR) + } + + it should ("contain " + renderer.render( + roleS_inv + ) + " in its conflict set") in { + ontology.confl(roleS) should contain(roleS_inv) + } + + it should ("contain " + renderer.render( + roleT_inv + ) + " in its conflict set") in { + ontology.confl(roleS) should contain(roleT_inv) + } + + // S⁻ + + renderer.render(roleS_inv) should "be unsafe" in { + ontology.unsafeRoles should contain(roleS_inv) + } + + renderer.render( + AsomeValuesFromSiC + ) should "produce 1 rule" in { + val varX = Variable.create("X") + val visitor = program.RuleGenerator + val rules = AsomeValuesFromSiC.accept(visitor) + rules should have length 1 + } + + renderer.render( + DsomeValuesFromRB + ) should "have a 'cycle' set of 48 elements" in { + // Cycle introduces a new constant for each possible triple (the + // order among triples is total). In this example there are 4 + // concept names and R has 3 safe roles in its conflict set (S, T, + // Inv(R)). Triples are + // (concept, role, concept) + // and hence we have 4*3*4=48 new constants introduced. + ontology.cycle(DsomeValuesFromRB) should have size 48 + } + + it should "produce 5 rules" in { + // Rule 1 provides 1 rule (split in 2) + 48 fact + // Rule 2 provides 0 rules + // Rule 3 provides 48 rule (split in 2) + // Then (1*2 + 48) + (0) + (48*2) = 146 + val varX = Variable.create("X") + val visitor = program.RuleGenerator + val rules = DsomeValuesFromRB.accept(visitor) + rules should have length 146 + } + + renderer.render( + BsomeValuesFromSD + ) should "have a 'cycle' set of 32 elements" in { + // Cycle introduces a new constant for each possible triple (the + // order among triples is total). In this example there are 4 + // concept names and S has 2 safe roles in its conflict set (R, + // Inv(T)). Triples are + // (concept, role, concept) + // and hence we have 4*2*4=32 new constants introduced. + ontology.cycle(BsomeValuesFromSD) should have size 32 + } + + it should "produce 5 rules" in { + // Rule 1 provides 1 rule (split in 2) + 32 fact + // Rule 2 provides 0 rules + // Rule 3 provides 32 rule (split in 2) + // Then (1*2 + 32) + (0) + (32*2) = 98 + val varX = Variable.create("X") + val visitor = program.RuleGenerator + val rules = DsomeValuesFromRB.accept(visitor) + rules should have length 146 + } + + renderer.render( + SsubPropertyOfT + ) should "produce 2 rules" in { + val varX = Variable.create("X") + val visitor = program.RuleGenerator + val rules = SsubPropertyOfT.accept(visitor) + rules should have length 2 + } + +} + +object Ontology2_CanonicalModelSpec { + + /* Renderer to display OWL Axioms with DL syntax*/ + val renderer = new DLSyntaxObjectRenderer() + + def base(str: String): IRI = + IRI.create("http://example.com/rsa_example.owl#" + str) + + val ontology_path: File = new File("examples/example2.owl") + val ontology = RSAOntology(ontology_path) + val program = ontology.canonicalModel + + val roleR = new OWLObjectPropertyImpl(base("R")) + val roleS = new OWLObjectPropertyImpl(base("S")) + val roleT = new OWLObjectPropertyImpl(base("T")) + val roleP = new OWLObjectPropertyImpl(base("P")) + val roleR_inv = roleR.getInverseProperty() + val roleS_inv = roleS.getInverseProperty() + val roleT_inv = roleT.getInverseProperty() + val roleP_inv = roleP.getInverseProperty() + + val AsomeValuesFromRB = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("A")), + new OWLObjectSomeValuesFromImpl( + roleR, + new OWLClassImpl(base("B")) + ), + Seq().asJava + ) + + val BsomeValuesFromSC = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("B")), + new OWLObjectSomeValuesFromImpl( + roleS, + new OWLClassImpl(base("C")) + ), + Seq().asJava + ) + + val CsomeValuesFromTD = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("C")), + new OWLObjectSomeValuesFromImpl( + roleT, + new OWLClassImpl(base("D")) + ), + Seq().asJava + ) + + val DsomeValuesFromPA = new OWLSubClassOfAxiomImpl( + new OWLClassImpl(base("D")), + new OWLObjectSomeValuesFromImpl( + roleP, + new OWLClassImpl(base("A")) + ), + Seq().asJava + ) + +} + +class Ontology2_CanonicalModelSpec + extends AnyFlatSpec + with Matchers + with LoneElement { + + import Ontology2_CanonicalModelSpec._ + + "The program generated from Example #1" should "not be empty" in { + program.rules should not be empty + } + + // Role R // + + renderer.render(roleR) should "be unsafe" in { + ontology.unsafeRoles should contain(roleR) + } + + it should "have only its inverse in its conflict set" in { + ontology.confl(roleR).loneElement shouldBe roleR_inv + } + + // Role S // + + renderer.render(roleS) should "be unsafe" in { + ontology.unsafeRoles should contain(roleS) + } + + it should "have only its inverse in its conflict set" in { + ontology.confl(roleS).loneElement shouldBe roleS_inv + } + + // Role T // + + renderer.render(roleT) should "be unsafe" in { + ontology.unsafeRoles should contain(roleT) + } + + it should "have only its inverse in its conflict set" in { + ontology.confl(roleT).loneElement shouldBe roleT_inv + } + + // Role P // + + renderer.render(roleP) should "be unsafe" in { + ontology.unsafeRoles should contain(roleP) + } + + it should "have only its inverse in its conflict set" in { + ontology.confl(roleP).loneElement shouldBe roleP_inv + } + + // A ⊑ ∃ R.B + + renderer.render( + AsomeValuesFromRB + ) should "produce 1 rule" in { + val visitor = program.RuleGenerator + val rules = AsomeValuesFromRB.accept(visitor) + rules should have length 1 + } + + // B ⊑ ∃ S.C + + renderer.render( + BsomeValuesFromSC + ) should "produce 1 rule" in { + val visitor = program.RuleGenerator + val rules = BsomeValuesFromSC.accept(visitor) + rules should have length 1 + } + + // C ⊑ ∃ T.D + + renderer.render( + CsomeValuesFromTD + ) should "produce 1 rule" in { + val visitor = program.RuleGenerator + val rules = CsomeValuesFromTD.accept(visitor) + rules should have length 1 + } + + // D ⊑ ∃ P.A + + renderer.render( + DsomeValuesFromPA + ) should "produce 1 rule" in { + val visitor = program.RuleGenerator + val rules = DsomeValuesFromPA.accept(visitor) + rules should have length 1 + } + +} diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala new file mode 100644 index 0000000..49abd48 --- /dev/null +++ b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala @@ -0,0 +1,398 @@ +package rsacomb + +import java.io.File +import java.util.{ArrayList => JList} +import org.scalatest.LoneElement +import org.scalatest.Inspectors +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom +import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI} +import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{Query, SelectQuery} +import tech.oxfordsemantic.jrdfox.Prefixes + +import scala.collection.JavaConverters._ + +import uk.ac.ox.cs.rsacomb.FilteringProgram +import uk.ac.ox.cs.rsacomb.util.RDFoxHelpers + +object FilteringProgramSpec { + + val prefixes = new Prefixes() + prefixes.declarePrefix( + ":", + "http://slegger.gitlab.io/slegge-obda/ontology/subsurface-exploration#" + ) + prefixes.declarePrefix("rdf:", "http://www.w3.org/1999/02/22-rdf-syntax-ns#") + prefixes.declarePrefix("rdfs:", "http://www.w3.org/2000/01/rdf-schema#") + prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#") + + // DEBUG: Quick helper functions + def v(v: String): Variable = Variable.create(v) + def c(c: String): IRI = IRI.create(":" + c) + + // QUERY 0 + + val query0 = RDFoxHelpers + .parseSelectQuery(""" + SELECT ?subj + WHERE { + ?subj ?pred ?obj + } + """, prefixes) + .get + + // val query0 = Query.create( + // QueryType.SELECT, + // false, + // List(v("subj")).asJava, + // Atom.rdf(v("subj"), v("pred"), v("obj")) + // ) + + // QUERY 1 + + val query1 = RDFoxHelpers + .parseSelectQuery(""" + SELECT * + WHERE { + ?w a :Wellbore + } + """, prefixes) + .get + + // val query1 = Query.create( + // QueryType.SELECT, + // false, + // List(v("w")).asJava, + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")) + // ) + + // QUERY 2 + + val query2 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT * + WHERE { + ?w a :Wellbore ; + :wellboreDocument ?doc . + ?doc :hasURL ?document_hyperlink + } + """, + prefixes + ) + .get + + // val query2 = Query.create( + // QueryType.SELECT, + // false, + // List(v("w"), v("doc"), v("document_hyperlink")).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("wellboreDocument"), v("doc")), + // Atom.rdf(v("doc"), c("hasURL"), v("document_hyperlink")) + // ) + // ) + + // QUERY 3 + + val query3 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT ?wellbore ?formation_pressure + WHERE { + ?w a :Wellbore ; + :name ?wellbore ; + :hasFormationPressure ?fp . + ?fp :valueInStandardUnit ?formation_pressure + } + """, + prefixes + ) + .get + + // val query3 = Query.create( + // QueryType.SELECT, + // false, + // List(v("wellbore"), v("formation_pressure")).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("name"), v("wellbore")), + // Atom.rdf(v("w"), c("hasFormationPressure"), v("fp")), + // Atom.rdf(v("fp"), c("valueInStandardUnit"), v("formation_pressure")) + // ) + // ) + + // QUERY 4 + + val query4 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT * + WHERE { + ?w a :Wellbore ; + :hasGeochemicalMeasurement ?measurement . + ?measurement :cgType ?cgtype ; + :peakName ?peakType ; + :peakHeight ?peak_height ; + :peakAmount ?peak_amount + } + """, + prefixes + ) + .get + + // val query4 = Query.create( + // QueryType.SELECT, + // false, + // List( + // v("w"), + // v("measurement"), + // v("cgtype"), + // v("peakType"), + // v("peak_height"), + // v("peak_amount") + // ).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("hasGeochemicalMeasurement"), v("measurement")), + // Atom.rdf(v("measurement"), c("cgType"), v("cgtype")), + // Atom.rdf(v("measurement"), c("peakName"), v("peakType")), + // Atom.rdf(v("measurement"), c("peakHeight"), v("peak_height")), + // Atom.rdf(v("measurement"), c("peakAmount"), v("peak_amount")) + // ) + // ) + + // QUERY 5 + + val query5 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT ?wellbore ?unit_name ?discovery + WHERE { + ?w a :Wellbore ; + :name ?wellbore ; + :hasWellboreInterval ?c_int ; + :hasWellboreInterval ?f_int . + ?c_int :hasUnit ?c_unit . + ?c_unit :name ?unit_name . + ?f_int a :FluidZone ; + :name ?discovery ; + :overlapsWellboreInterval ?c_int + } + """, + prefixes + ) + .get + + // val query5 = Query.create( + // QueryType.SELECT, + // false, + // List(v("wellbore"), v("unit_name"), v("discovery")).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("name"), v("wellbore")), + // Atom.rdf(v("w"), c("hasWellboreInterval"), v("c_int")), + // Atom.rdf(v("w"), c("hasWellboreInterval"), v("f_int")), + // Atom.rdf(v("c_int"), c("hasUnit"), v("c_unit")), + // Atom.rdf(v("c_unit"), c("name"), v("unit_name")), + // Atom.rdf(v("f_int"), IRI.RDF_TYPE, c("FluidZone")), + // Atom.rdf(v("f_int"), c("name"), v("discovery")), + // Atom.rdf(v("f_int"), c("overlapsWellboreInterval"), v("c_int")) + // ) + // ) + + // QUERY 6 + + val query6 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT DISTINCT ?wellbore ?content + WHERE { + ?w a :Wellbore ; + :name ?wellbore ; + :hasWellboreInterval ?int . + ?int a :FluidZone ; + :fluidZoneContent ?content + } + """, + prefixes + ) + .get + + // val query6 = Query.create( + // QueryType.SELECT, + // true, + // List(v("wellbore"), v("content")).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("name"), v("wellbore")), + // Atom.rdf(v("w"), c("hasWellboreInterval"), v("int")), + // Atom.rdf(v("int"), IRI.RDF_TYPE, c("FluidZone")), + // Atom.rdf(v("int"), c("fluidZoneContent"), v("content")) + // ) + // ) + + // QUERY 7 + + val query7 = RDFoxHelpers + .parseSelectQuery( + """ + SELECT ?wName ?sample ?porosity ?top_depth_md ?bot_depth_md + WHERE { + ?w a :Wellbore ; + :name ?wName ; + :hasWellboreInterval ?z . + ?z :hasUnit ?u . + ?u :name ?strat_unit_name . + ?wellbore :hasWellboreInterval ?cored_int . + ?c :extractedFrom ?cored_int ; + :hasCoreSample ?sample . + ?sample :hasDepth ?sample_depth . + ?sample_depth + :inWellboreInterval ?z . + ?sample :hasPorosity ?p . + ?p :valueInStandardUnit ?porosity . + ?z :hasTopDepth ?top . + ?top a :MeasuredDepth ; + :valueInStandardUnit ?top_depth_md . + ?z :hasBottomDepth ?bot . + ?bot a :MeasuredDepth ; + :valueInStandardUnit ?bot_depth_md + } + """, + prefixes + ) + .get + + // val query7 = Query.create( + // QueryType.SELECT, + // false, + // List( + // v("wName"), + // v("sample"), + // v("porosity"), + // v("top_depth_md"), + // v("bot_depth_md") + // ).asJava, + // Conjunction.create( + // Atom.rdf(v("w"), IRI.RDF_TYPE, c("Wellbore")), + // Atom.rdf(v("w"), c("name"), v("wName")), + // Atom.rdf(v("w"), c("hasWellboreInterval"), v("z")), + // Atom.rdf(v("z"), c("hasUnit"), v("u")), + // Atom.rdf(v("u"), c("name"), v("strat_unit_name")), + // Atom.rdf(v("wellbore"), c("hasWellboreInterval"), v("cored_int")), + // Atom.rdf(v("c"), c("extractedFrom"), v("cored_int")), + // Atom.rdf(v("c"), c("hasCoreSample"), v("sample")), + // Atom.rdf(v("sample"), c("hasDepth"), v("sample_depth")), + // Atom.rdf(v("sample_depth"), c("inWellboreInterval"), v("z")), + // Atom.rdf(v("sample"), c("hasPorosity"), v("p")), + // Atom.rdf(v("p"), c("valueInStandardUnit"), v("porosity")), + // Atom.rdf(v("z"), c("hasTopDepth"), v("top")), + // Atom.rdf(v("top"), IRI.RDF_TYPE, c("MeasuredDepth")), + // Atom.rdf(v("top"), c("valueInStandardUnit"), v("top_depth_md")), + // Atom.rdf(v("z"), c("hasBottomDepth"), v("bot")), + // Atom.rdf(v("bot"), IRI.RDF_TYPE, c("MeasuredDepth")), + // Atom.rdf(v("bot"), c("valueInStandardUnit"), v("bot_depth_md")) + // ) + // ) + + val queries = + List(query0, query1, query2, query3, query4, query5, query6, query7) +} + +class FilteringProgramSpec + extends AnyFlatSpec + with Matchers + with LoneElement + with Inspectors { + + import FilteringProgramSpec._ + + "Queries" should "have distinct answer and bounded variables" in { + for (query <- queries) { + val program = new FilteringProgram(query, List()) + forAll(program.answer) { v => program.bounded should not contain v } + forAll(program.bounded) { v => program.answer should not contain v } + } + } + + "Query 0" should "have {?obj, ?pred} as bounded variables" in { + val pred = Variable.create("obj") + val obj = Variable.create("pred") + val program = new FilteringProgram(query0, List()) + program.bounded should contain theSameElementsAs List(pred, obj) + } + + "Query 1" should "have no bounded variable" in { + val program = new FilteringProgram(query1, List()) + program.bounded shouldBe empty + } + + "Query 2" should "have no bounded variable" in { + val program = new FilteringProgram(query2, List()) + program.bounded shouldBe empty + } + + "Query 3" should "have {?w, ?fp} as bounded variables" in { + val w = Variable.create("w") + val fp = Variable.create("fp") + val program = new FilteringProgram(query3, List()) + program.bounded should contain theSameElementsAs List(w, fp) + } + + "Query 4" should "have no bounded variable" in { + val program = new FilteringProgram(query4, List()) + program.bounded shouldBe empty + } + + "Query 5" should "have a non-empty bounded set" in { + val w = Variable.create("w") + val c_int = Variable.create("c_int") + val f_int = Variable.create("f_int") + val c_unit = Variable.create("c_unit") + val program = new FilteringProgram(query5, List()) + program.bounded should contain theSameElementsAs List( + w, + c_int, + f_int, + c_unit + ) + } + + "Query 6" should "have a non-empty bounded set" in { + val w = Variable.create("w") + val int = Variable.create("int") + val program = new FilteringProgram(query6, List()) + program.bounded should contain theSameElementsAs List(w, int) + } + + "Query 7" should "have a non-empty bounded set" in { + val w = Variable.create("w") + val z = Variable.create("z") + val u = Variable.create("u") + val strat_unit_name = Variable.create("strat_unit_name") + val wellbore = Variable.create("wellbore") + val cored_int = Variable.create("cored_int") + val c = Variable.create("c") + val sample_depth = Variable.create("sample_depth") + val p = Variable.create("p") + val top = Variable.create("top") + val bot = Variable.create("bot") + val program = new FilteringProgram(query7, List()) + program.bounded should contain theSameElementsAs List( + w, + z, + u, + strat_unit_name, + wellbore, + cored_int, + c, + sample_depth, + p, + top, + bot + ) + } +} diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/OWLAxiomSpec.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/OWLAxiomSpec.scala new file mode 100644 index 0000000..8aee03d --- /dev/null +++ b/src/test/scala/uk/ac/ox/cs/rsacomb/OWLAxiomSpec.scala @@ -0,0 +1,336 @@ +package rsacomb + +import java.util.{ArrayList => JList} +import org.scalatest.LoneElement +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import org.semanticweb.owlapi.model.OWLClassExpression +import uk.ac.manchester.cs.owl.owlapi.{OWLSubClassOfAxiomImpl} +import uk.ac.manchester.cs.owl.owlapi.{ + OWLClassImpl, + OWLObjectSomeValuesFromImpl, + OWLObjectIntersectionOfImpl, + OWLObjectOneOfImpl, + OWLObjectAllValuesFromImpl, + OWLObjectMaxCardinalityImpl, + OWLNamedIndividualImpl +} +import uk.ac.manchester.cs.owl.owlapi.{OWLObjectPropertyImpl} +import org.semanticweb.owlapi.model.{OWLAxiom} + +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + Rule, + BindAtom, + TupleTableAtom, + TupleTableName +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + FunctionCall, + Term, + Variable, + Literal +} + +import org.semanticweb.owlapi.model.{IRI => OWLIRI} +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFIRI} + +import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} +import uk.ac.ox.cs.rsacomb.util.RSA + +object OWLAxiomSpec { + + // IRI + val iri_Professor = OWLIRI.create("univ:Professor") + val iri_Female = OWLIRI.create("std:Female") + val iri_Student = OWLIRI.create("univ:Student") + val iri_PartTimeStudent = OWLIRI.create("univ:PartTimeStudent") + val iri_Worker = OWLIRI.create("univ:Worker") + val iri_alice = OWLIRI.create("univ:alice") + val iri_supervises = OWLIRI.create("univ:supervises") + val iri_hasSupervisor = OWLIRI.create("univ:hasSupervisor") + val iri_sameAs = OWLIRI.create("owl:sameAs") + + // RDFox Terms + val term_x = Variable.create("x") + val term_y = Variable.create("y") + val term_z = Variable.create("z") + val term_c1 = RSA("c_1") + val term_c2 = RSA("c_2") + val term_alice = RDFIRI.create("univ:alice") + + // RDFox Predicates + val pred_sameAs = TupleTableName.create("owl:sameAs") + val pred_Professor = TupleTableName.create(iri_Professor.getIRIString) + val pred_hasSupervisor = TupleTableName.create(iri_hasSupervisor.getIRIString) + + // OWL Classes + // Name Class corresponding to + // + // Professor + // + val class_Professor = new OWLClassImpl(iri_Professor) + val class_Female = new OWLClassImpl(iri_Female) + val class_Student = new OWLClassImpl(iri_Student) + val class_PartTimeStudent = new OWLClassImpl(iri_PartTimeStudent) + val class_Worker = new OWLClassImpl(iri_Worker) + val class_OWLClass = class_Professor + // Class Conjunction corresponding to + // + // Student ∧ Worker + // + val class_OWLObjectIntersectionOf = { + val conjuncts = new JList[OWLClassExpression]() + conjuncts.add(class_Student) + conjuncts.add(class_Worker) + new OWLObjectIntersectionOfImpl(conjuncts) + } + // Singleton Class corresponding to + // + // { alice } + // + val class_OWLObjectOneOf = + new OWLObjectOneOfImpl( + new OWLNamedIndividualImpl(iri_alice) + ) + // Object Existential Restiction corresponding to + // + // ∃ hasSupervisor.Professor + // + val class_OWLObjectSomeValuesFrom = + new OWLObjectSomeValuesFromImpl( + new OWLObjectPropertyImpl(iri_hasSupervisor), + class_Professor + ) + // Object Max Cardinality Restriction corresponding to + // + // ≤ 1 hasSupervisor . Professor + val class_OWLObjectMaxCardinality = + new OWLObjectMaxCardinalityImpl( + new OWLObjectPropertyImpl(iri_hasSupervisor), + 1, + class_Professor + ) + + // OWL Axioms + // Axiom SubClassOf corresponding to + // + // Student ∧ Worker -> PartTimeStudent + // + val axiom_OWLSubClassOf1 = + new OWLSubClassOfAxiomImpl( + class_OWLObjectIntersectionOf, + class_PartTimeStudent, + new JList() + ) + + // Axiom SubClassOf corresponding to + // + // Student -> ∃ hasSupervisor.Professor + // + val axiom_OWLSubClassOf2 = + new OWLSubClassOfAxiomImpl( + class_Student, + class_OWLObjectSomeValuesFrom, + new JList() + ) + + // Axiom SubClassOf corresponding to + // + // ∃ hasSupervisor.Professor -> Student + // + val axiom_OWLSubClassOf3 = + new OWLSubClassOfAxiomImpl( + class_OWLObjectSomeValuesFrom, + class_Student, + new JList() + ) + + // Axiom SubClassOf corresponding to + // + // Student -> { alice } + // + val axiom_OWLSubClassOf4 = + new OWLSubClassOfAxiomImpl( + class_Student, + class_OWLObjectOneOf, + new JList() + ) + + // Axiom SubClassOf corresponding to + // + // Student -> ≤1 hasSupervisor.Professor + // + val axiom_OWLSubClassOf5 = + new OWLSubClassOfAxiomImpl( + class_Student, + class_OWLObjectMaxCardinality, + new JList() + ) + + def convertAxiom( + axiom: OWLAxiom, + term: Term, + skolem: SkolemStrategy = SkolemStrategy.None + ): List[Rule] = { + axiom.accept(RDFoxAxiomConverter(term, List())) + } + +} // object OWLAxiomSpec + +class OWLAxiomSpec extends AnyFlatSpec with Matchers with LoneElement { + + // Import required data + import OWLAxiomSpec._ + // Implicit convertion from IRI in OWLAPI to IRI in JRDFox + import uk.ac.ox.cs.rsacomb.implicits.RDFox._ + + // OWLSubClassOfAxiom #1 + axiom_OWLSubClassOf1.toString should "be converted into a singleton List[Rule]" in { + val result = convertAxiom(axiom_OWLSubClassOf1, term_x) + result.loneElement shouldBe a[Rule] + } + + it should "contain a conjuction of atoms (Student[?x],Worker[?x]) in the body of the rule" in { + val result = convertAxiom(axiom_OWLSubClassOf1, term_x) + val body = List( + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student), + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Worker) + ) + result.loneElement.getBody should contain theSameElementsAs body + } + + it should "contain a single atom (PartTimeStudent[?x]) in the head of the rule" in { + val result = convertAxiom(axiom_OWLSubClassOf1, term_x) + val head = TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_PartTimeStudent) + result.loneElement.getHead.loneElement should be(head) + } + + // OWLSubClassOfAxiom #2 (w/ constant skolemization) + (axiom_OWLSubClassOf2.toString + "\n(w/ constant skolemization)") should + "be converted into a singleton List[Rule]" in { + val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) + val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + result.loneElement shouldBe a[Rule] + } + + it should "contain a single atom (Student[?x]) in the body of the rule" in { + val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) + val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + val body = + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student.getIRIString) + result.loneElement.getBody.loneElement should equal(body) + } + + // it should "contain a conjuction of atoms (hasSupervisor[?x,?c],Professor[?c]) in the head of the rule" in { + // val skolem = SkolemStrategy.Constant(axiom_OWLSubClassOf2.toString) + // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + // val term_c = RSA.rsa(skolem.const.getIRI) + // val head = List( + // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_c), + // TupleTableAtom.rdf(term_c, RDFIRI.RDF_TYPE, iri_Professor) + // ) + // result.loneElement.getHead should contain theSameElementsAs (head) + // } + + // OWLSubClassOfAxiom #2 (w/ skolemization) + (axiom_OWLSubClassOf2.toString + "\n(w/ skolemization)") should + "be converted into a singleton List[Rule]" in { + val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) + val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + result.loneElement shouldBe a[Rule] + } + + it should "contain an atom (Student[?x]) in the body of the rule" in { + val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) + val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + val body = + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) + result.loneElement.getBody should contain(body) + } + + // it should "contain a built-in function call (BIND(?y,SKOLEM(?f,?x))) in the body of the rule" in { + // val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) + // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + // val call = + // BindAtom.create(BuiltinFunctionCall.create("SKOLEM", term_x), term_y) + // result.loneElement.getBody should contain(call) + // } + + // it should "contain a conjuction of atom (hasSupervisor[?x,?y],Professor[?y]) in the head of the rule" in { + // val skolem = SkolemStrategy.Standard(axiom_OWLSubClassOf2.toString) + // val result = convertAxiom(axiom_OWLSubClassOf2, term_x, skolem) + // val head = List( + // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), + // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor) + // ) + // result.loneElement.getHead should contain theSameElementsAs head + // } + + // OWLSubClassOfAxiom #3 + axiom_OWLSubClassOf3.toString should "be converted into a singleton List[Rule]" in { + val result = convertAxiom(axiom_OWLSubClassOf3, term_x) + result.loneElement shouldBe a[Rule] + } + + // it should "contain a conjunction of atoms (hasSupervisor[?x,?y],Professor[?y]) in the body of the rule" in { + // val result = convertAxiom(axiom_OWLSubClassOf3, term_x) + // val body = List( + // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), + // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor) + // ) + // result.loneElement.getBody should contain theSameElementsAs body + // } + + it should "contain a single atom (Student[?x]) in the head of the rule" in { + val result = convertAxiom(axiom_OWLSubClassOf3, term_x) + val head = + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) + result.loneElement.getHead.loneElement should be(head) + } + + // OWLSubClassOfAxiom #4 + axiom_OWLSubClassOf4.toString should "be converted into a singleton List[Rule]" in { + val result = convertAxiom(axiom_OWLSubClassOf4, term_x) + result.loneElement shouldBe a[Rule] + } + + it should "contain a single atoms (Student[?x]) in the body of the rule" in { + val result = convertAxiom(axiom_OWLSubClassOf4, term_x) + val body = + TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student) + result.loneElement.getBody.loneElement should be(body) + } + + it should "contain a single atom (sameAs[?x,alice])) in the head of the rule" in { + val result = convertAxiom(axiom_OWLSubClassOf4, term_x) + val head = TupleTableAtom.rdf(term_x, RDFIRI.SAME_AS, term_alice) + result.loneElement.getHead.loneElement should be(head) + } + + // OWLSubClassOfAxiom #5 + axiom_OWLSubClassOf5.toString should "be converted into a singleton List[Rule]" in { + val result = convertAxiom(axiom_OWLSubClassOf5, term_x) + result.loneElement shouldBe a[Rule] + } + + // it should "contain a conjunction of atoms (...) in the body of the rule" in { + // val result = convertAxiom(axiom_OWLSubClassOf5, term_x) + // val body = List( + // TupleTableAtom.rdf(term_x, RDFIRI.RDF_TYPE, iri_Student), + // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_y), + // TupleTableAtom.rdf(term_y, RDFIRI.RDF_TYPE, iri_Professor), + // TupleTableAtom.rdf(term_x, iri_hasSupervisor, term_z), + // TupleTableAtom.rdf(term_z, RDFIRI.RDF_TYPE, iri_Professor) + // ) + // result.loneElement.getBody should contain theSameElementsAs body + // } + + // it should "contain a single atom (sameAs[?x,?z])) in the head of the rule" in { + // val result = convertAxiom(axiom_OWLSubClassOf5, term_x) + // val head = TupleTableAtom.rdf(term_y, RDFIRI.SAME_AS, term_z) + // result.loneElement.getHead.loneElement should be(head) + // } + +} // class OWLAxiomSpec diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/OWLClassSpec.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/OWLClassSpec.scala new file mode 100644 index 0000000..459fe21 --- /dev/null +++ b/src/test/scala/uk/ac/ox/cs/rsacomb/OWLClassSpec.scala @@ -0,0 +1,277 @@ +package rsacomb + +import java.util.{ArrayList => JList} + +import org.scalatest.LoneElement +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +import org.semanticweb.owlapi.model.OWLClassExpression +import uk.ac.manchester.cs.owl.owlapi.{ + OWLClassImpl, + OWLObjectSomeValuesFromImpl, + OWLObjectIntersectionOfImpl, + OWLObjectOneOfImpl, + OWLObjectAllValuesFromImpl, + OWLObjectMaxCardinalityImpl, + OWLNamedIndividualImpl +} +import uk.ac.manchester.cs.owl.owlapi.{OWLObjectPropertyImpl} +import org.semanticweb.owlapi.model.IRI +import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFIRI} + +import tech.oxfordsemantic.jrdfox.logic.Datatype +import tech.oxfordsemantic.jrdfox.logic.datalog.{ + TupleTableAtom, + TupleTableName, + BindAtom +} +import tech.oxfordsemantic.jrdfox.logic.expression.{ + FunctionCall, + Term, + Variable, + Literal +} + +import uk.ac.ox.cs.rsacomb.converter.{ + RDFoxRuleShards, + RDFoxClassExprConverter, + SkolemStrategy +} +import uk.ac.ox.cs.rsacomb.util.RSA + +object OWLClassSpec { + + // IRI + val iri_Professor = IRI.create("univ:Professor") + val iri_Female = IRI.create("std:Female") + val iri_Student = IRI.create("univ:Student") + val iri_Worker = IRI.create("univ:Worker") + val iri_alice = IRI.create("univ:alice") + val iri_supervises = IRI.create("univ:supervises") + val iri_hasSupervisor = IRI.create("univ:hasSupervisor") + + // RDFox Terms + val term_x = Variable.create("x") + val term_y = Variable.create("y") + val term_c1 = RSA("c_1") + val term_c2 = RSA("c_2") + val term_alice = RDFIRI.create("univ:alice") + + // RDFox Predicates + val pred_sameAs = TupleTableName.create("owl:sameAs") + val pred_Professor = TupleTableName.create(iri_Professor.getIRIString) + val pred_hasSupervisor = TupleTableName.create(iri_hasSupervisor.getIRIString) + + // OWL Classes + // Name Class corresponding to + // + // Professor + // + val class_Professor = new OWLClassImpl(iri_Professor) + val class_Female = new OWLClassImpl(iri_Female) + val class_Student = new OWLClassImpl(iri_Student) + val class_Worker = new OWLClassImpl(iri_Worker) + val class_OWLClass = class_Professor + + // Class Conjunction corresponding to + // + // Female ∧ Student ∧ Worker + // + val class_OWLObjectIntersectionOf = { + val conjuncts = new JList[OWLClassExpression]() + conjuncts.add(class_Female) + conjuncts.add(class_Student) + conjuncts.add(class_Worker) + new OWLObjectIntersectionOfImpl(conjuncts) + } + // Singleton Class corresponding to + // + // { alice } + // + val class_OWLObjectOneOf = + new OWLObjectOneOfImpl( + new OWLNamedIndividualImpl(iri_alice) + ) + // Object Existential Restiction corresponding to + // + // ∃ hasSupervisor.Professor + // + val class_OWLObjectSomeValuesFrom = + new OWLObjectSomeValuesFromImpl( + new OWLObjectPropertyImpl(iri_hasSupervisor), + class_Professor + ) + // Object Max Cardinality Restriction corresponding to + // + // ≤1 hasSupervisor.Professor + val class_OWLObjectMaxCardinality = + new OWLObjectMaxCardinalityImpl( + new OWLObjectPropertyImpl(iri_hasSupervisor), + 1, + class_Professor + ) +} // object OWLClassSpec + +class OWLClassSpec extends AnyFlatSpec with Matchers with LoneElement { + // Import required data + import OWLClassSpec._ + + // OWLClass + class_OWLClass.toString should "be converted into a RDFoxRuleShards" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLClass.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + it should "have a single TupleTableAtom in its result list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLClass.accept(visitor) + result.res.loneElement shouldBe an[TupleTableAtom] + } + + it should "have an empty extension list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLClass.accept(visitor) + result.ext shouldBe empty + } + + // OWLObjectIntersectionOf + class_OWLObjectIntersectionOf.toString should "be converted into a RDFoxRuleShards" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectIntersectionOf.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + it should "be converted in the union of its converted conjuncts" in { + val visitor = RDFoxClassExprConverter(term_x) + val result1 = class_OWLObjectIntersectionOf.accept(visitor) + val result2 = RDFoxClassExprConverter.merge( + List( + class_Female.accept(visitor), + class_Student.accept(visitor), + class_Worker.accept(visitor) + ) + ) + result1.res should contain theSameElementsAs result2.res + result1.ext should contain theSameElementsAs result2.ext + } + + // OWLObjectOneOf + class_OWLObjectOneOf.toString should "be converted into a RDFoxRuleShards" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectOneOf.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + // it should "be converted into a single TupleTableAtom" in { + // val visitor = RDFoxClassExprConverter(term_x) + // val result = class_OWLObjectOneOf.accept(visitor) + // result.res.loneElement should (be (a [TupleTableAtom]) and have ('tupleTableName (pred_sameAs))) + // } + + it should "have an empty extension list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectOneOf.accept(visitor) + result.ext shouldBe empty + } + + // OWLObjectSomeValuesFrom + (class_OWLObjectSomeValuesFrom.toString ++ " w/o skolemization") should + "be converted into a RDFoxRuleShards" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + it should "have two TupleTableAtoms in its result list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + exactly(2, result.res) should (be(an[TupleTableAtom]) + //and have('numberOfArguments (3)) + ) + } + + it should "have an empty extension list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result.ext shouldBe empty + } + + (class_OWLObjectSomeValuesFrom.toString ++ " w/ skolemization") should + "be converted into a RDFoxRuleShards" in { + val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + it should "have exactly two TupleTableAtoms in its result list" in { + val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + exactly(2, result.res) should (be(an[TupleTableAtom]) + //and have('numberOfArguments (3)) + ) + } + + it should "should have a single SKOLEM call in the extension list" in { + val skolem = SkolemStrategy.Standard(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result.ext.loneElement shouldBe a[BindAtom] + val builtin = result.ext.head.asInstanceOf[BindAtom].getExpression + builtin should (be(a[FunctionCall]) and have( + 'functionName ("SKOLEM") + )) + } + + (class_OWLObjectSomeValuesFrom.toString ++ " w/ constant skolemization") should + "be converted into a RDFoxRuleShards" in { + val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + it should "have exactly two TupleTableAtoms in its result list" in { + val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + exactly(2, result.res) should (be(an[TupleTableAtom]) + //and have('numberOfArguments (3)) + ) + } + + it should "have an empty extension list" in { + val skolem = SkolemStrategy.Constant(class_OWLObjectSomeValuesFrom.toString) + val visitor = RDFoxClassExprConverter(term_x, List(), skolem) + val result = class_OWLObjectSomeValuesFrom.accept(visitor) + result.ext shouldBe empty + } + + // OWLObjectMaxCardinalityImpl + class_OWLObjectMaxCardinality.toString should + "be converted into a RDFoxRuleShards" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectMaxCardinality.accept(visitor) + result shouldBe a[RDFoxRuleShards] + } + + // it should "have a single TupleTableAtom in the result list" in { + // val visitor = RDFoxClassExprConverter(term_x) + // val result = class_OWLObjectMaxCardinality.accept(visitor) + // result.res.loneElement should (be(an[TupleTableAtom]) and have( + // 'tupleTableName (pred_sameAs) + // )) + // } + + it should "have 4 TupleTableAtoms in its extension list" in { + val visitor = RDFoxClassExprConverter(term_x) + val result = class_OWLObjectMaxCardinality.accept(visitor) + exactly(4, result.ext) should (be(an[TupleTableAtom]) + //and have('numberOfArguments (3)) + ) + } + +} // class OWLClassSpec -- cgit v1.2.3