diff options
| author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-18 19:13:25 +0000 |
|---|---|---|
| committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-18 19:13:25 +0000 |
| commit | 1efc189e90240c162b54cbc50362b46786643dad (patch) | |
| tree | 9beabe0a2af7ba1674aea0060787782aa72e8a83 /src/main/scala/uk/ac/ox/cs | |
| parent | a45aeff72b82bbc9a52f10929bf15b414c868525 (diff) | |
| download | RSAComb-1efc189e90240c162b54cbc50362b46786643dad.tar.gz RSAComb-1efc189e90240c162b54cbc50362b46786643dad.zip | |
Reorganize project with Java-like folder structure
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs')
16 files changed, 2169 insertions, 0 deletions
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb | ||
| 2 | |||
| 3 | import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty} | ||
| 4 | import org.semanticweb.owlapi.model.{ | ||
| 5 | OWLClass, | ||
| 6 | // OWLObjectProperty, | ||
| 7 | OWLSubObjectPropertyOfAxiom, | ||
| 8 | // OWLObjectPropertyExpression, | ||
| 9 | OWLObjectSomeValuesFrom, | ||
| 10 | OWLSubClassOfAxiom | ||
| 11 | } | ||
| 12 | |||
| 13 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 14 | Rule, | ||
| 15 | BodyFormula, | ||
| 16 | TupleTableAtom, | ||
| 17 | Negation | ||
| 18 | } | ||
| 19 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
| 20 | Term, | ||
| 21 | Variable, | ||
| 22 | // Resource, | ||
| 23 | IRI | ||
| 24 | } | ||
| 25 | |||
| 26 | import uk.ac.ox.cs.rsacomb.converter.{ | ||
| 27 | SkolemStrategy, | ||
| 28 | RDFoxAxiomConverter, | ||
| 29 | RDFoxPropertyExprConverter | ||
| 30 | } | ||
| 31 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom | ||
| 32 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | ||
| 33 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
| 34 | |||
| 35 | class CanonicalModel(val ontology: RSAOntology) extends RSAAxiom { | ||
| 36 | |||
| 37 | import implicits.RDFox._ | ||
| 38 | import implicits.JavaCollections._ | ||
| 39 | |||
| 40 | val named: List[Rule] = | ||
| 41 | ontology.individuals.map(a => Rule.create(RSA.Named(a))) | ||
| 42 | |||
| 43 | val rolesAdditionalRules: List[Rule] = { | ||
| 44 | // Given a role (predicate) compute additional logic rules | ||
| 45 | def additional(pred: String): Seq[Rule] = { | ||
| 46 | val varX = Variable.create("X") | ||
| 47 | val varY = Variable.create("Y") | ||
| 48 | for ( | ||
| 49 | (hSuffix, bSuffix) <- List( | ||
| 50 | (Empty, Forward), | ||
| 51 | (Empty, Backward), | ||
| 52 | (Inverse, Forward + Inverse), | ||
| 53 | (Inverse, Backward + Inverse), | ||
| 54 | (Backward + Inverse, Forward), | ||
| 55 | (Forward + Inverse, Backward), | ||
| 56 | (Backward, Forward + Inverse), | ||
| 57 | (Forward, Backward + Inverse) | ||
| 58 | ) | ||
| 59 | ) | ||
| 60 | yield Rule.create( | ||
| 61 | TupleTableAtom.rdf(varX, pred :: hSuffix, varY), | ||
| 62 | TupleTableAtom.rdf(varX, pred :: bSuffix, varY) | ||
| 63 | ) | ||
| 64 | } | ||
| 65 | // Compute additional rules per role | ||
| 66 | ontology.roles | ||
| 67 | .collect { case prop: OWLObjectProperty => prop } | ||
| 68 | .map(_.getIRI.getIRIString) | ||
| 69 | .flatMap(additional) | ||
| 70 | } | ||
| 71 | |||
| 72 | private lazy val topAxioms: List[Rule] = { | ||
| 73 | val varX = Variable.create("X") | ||
| 74 | val varY = Variable.create("Y") | ||
| 75 | val concepts = ontology.concepts.map(c => { | ||
| 76 | Rule.create( | ||
| 77 | RSA.Thing(varX), | ||
| 78 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI) | ||
| 79 | ) | ||
| 80 | }) | ||
| 81 | val roles = ontology.roles.map(r => { | ||
| 82 | val name = r match { | ||
| 83 | case x: OWLObjectProperty => x.getIRI.getIRIString | ||
| 84 | case x: OWLObjectInverseOf => | ||
| 85 | x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse | ||
| 86 | } | ||
| 87 | Rule.create( | ||
| 88 | List(RSA.Thing(varX), RSA.Thing(varY)), | ||
| 89 | List(TupleTableAtom.rdf(varX, name, varY)) | ||
| 90 | ) | ||
| 91 | }) | ||
| 92 | concepts ::: roles | ||
| 93 | } | ||
| 94 | |||
| 95 | private val equalityAxioms: List[Rule] = { | ||
| 96 | val varX = Variable.create("X") | ||
| 97 | val varY = Variable.create("Y") | ||
| 98 | val varZ = Variable.create("Z") | ||
| 99 | List( | ||
| 100 | // Reflexivity | ||
| 101 | Rule.create(RSA.congruent(varX, varX), RSA.Thing(varX)), | ||
| 102 | // Simmetry | ||
| 103 | Rule.create(RSA.congruent(varY, varX), RSA.congruent(varX, varY)), | ||
| 104 | // Transitivity | ||
| 105 | Rule.create( | ||
| 106 | RSA.congruent(varX, varZ), | ||
| 107 | RSA.congruent(varX, varY), | ||
| 108 | RSA.congruent(varY, varZ) | ||
| 109 | ) | ||
| 110 | ) | ||
| 111 | } | ||
| 112 | |||
| 113 | val rules: List[Rule] = { | ||
| 114 | // Compute rules from ontology axioms | ||
| 115 | val rules = ontology.axioms.flatMap(_.accept(RuleGenerator)) | ||
| 116 | // Return full set of rules | ||
| 117 | rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: named | ||
| 118 | } | ||
| 119 | |||
| 120 | object RuleGenerator | ||
| 121 | extends RDFoxAxiomConverter( | ||
| 122 | Variable.create("X"), | ||
| 123 | ontology.unsafeRoles, | ||
| 124 | SkolemStrategy.None, | ||
| 125 | Empty | ||
| 126 | ) { | ||
| 127 | |||
| 128 | private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
| 129 | val unfold = ontology.unfold(axiom).toList | ||
| 130 | // Fresh Variables | ||
| 131 | val v0 = RSA("v0_" ++ axiom.hashed) | ||
| 132 | val varX = Variable.create("X") | ||
| 133 | implicit val unfoldTerm = RSA(unfold.hashCode.toString) | ||
| 134 | // TODO: use axiom.toTriple instead | ||
| 135 | val atomA: TupleTableAtom = { | ||
| 136 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
| 137 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) | ||
| 138 | } | ||
| 139 | val roleRf: TupleTableAtom = { | ||
| 140 | val visitor = | ||
| 141 | new RDFoxPropertyExprConverter(varX, v0, Forward) | ||
| 142 | axiom.getSuperClass | ||
| 143 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 144 | .getProperty | ||
| 145 | .accept(visitor) | ||
| 146 | .head | ||
| 147 | } | ||
| 148 | val atomB: TupleTableAtom = { | ||
| 149 | val cls = axiom.getSuperClass | ||
| 150 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 151 | .getFiller | ||
| 152 | .asInstanceOf[OWLClass] | ||
| 153 | .getIRI | ||
| 154 | TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) | ||
| 155 | } | ||
| 156 | // TODO: To be consistent with the specifics of the visitor we are | ||
| 157 | // returning facts as `Rule`s with true body. While this is correct | ||
| 158 | // there is an easier way to import facts into RDFox. Are we able to | ||
| 159 | // do that? | ||
| 160 | val facts = unfold.map(x => Rule.create(RSA.In(x))) | ||
| 161 | val rules = List( | ||
| 162 | Rule.create(roleRf, atomA, RSA.notIn(varX)), | ||
| 163 | Rule.create(atomB, atomA, RSA.notIn(varX)) | ||
| 164 | ) | ||
| 165 | facts ++ rules | ||
| 166 | } | ||
| 167 | |||
| 168 | private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
| 169 | val roleR = | ||
| 170 | axiom.getSuperClass | ||
| 171 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 172 | .getProperty | ||
| 173 | if (ontology.confl(roleR) contains roleR) { | ||
| 174 | // Fresh Variables | ||
| 175 | val v0 = RSA("v0_" ++ axiom.hashed) | ||
| 176 | val v1 = RSA("v1_" ++ axiom.hashed) | ||
| 177 | val v2 = RSA("v2_" ++ axiom.hashed) | ||
| 178 | // Predicates | ||
| 179 | def atomA(t: Term): TupleTableAtom = { | ||
| 180 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
| 181 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
| 182 | } | ||
| 183 | def roleRf(t1: Term, t2: Term): TupleTableAtom = { | ||
| 184 | val visitor = | ||
| 185 | new RDFoxPropertyExprConverter(t1, t2, Forward) | ||
| 186 | roleR.accept(visitor).head | ||
| 187 | } | ||
| 188 | def atomB(t: Term): TupleTableAtom = { | ||
| 189 | val cls = axiom.getSuperClass | ||
| 190 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 191 | .getFiller | ||
| 192 | .asInstanceOf[OWLClass] | ||
| 193 | .getIRI | ||
| 194 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
| 195 | } | ||
| 196 | //Rules | ||
| 197 | List( | ||
| 198 | Rule.create(roleRf(v0, v1), atomA(v0)), | ||
| 199 | Rule.create(atomB(v1), atomA(v0)), | ||
| 200 | Rule.create(roleRf(v1, v2), atomA(v1)), | ||
| 201 | Rule.create(atomB(v2), atomA(v1)) | ||
| 202 | ) | ||
| 203 | } else { | ||
| 204 | List() | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
| 209 | val cycle = ontology.cycle(axiom).toList | ||
| 210 | val roleR = | ||
| 211 | axiom.getSuperClass | ||
| 212 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 213 | .getProperty | ||
| 214 | // Fresh Variables | ||
| 215 | val v1 = RSA("v1_" ++ axiom.hashed) | ||
| 216 | // Predicates | ||
| 217 | def atomA(t: Term): TupleTableAtom = { | ||
| 218 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
| 219 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
| 220 | } | ||
| 221 | def roleRf(t: Term): TupleTableAtom = { | ||
| 222 | val visitor = | ||
| 223 | new RDFoxPropertyExprConverter(t, v1, Forward) | ||
| 224 | roleR.accept(visitor).head | ||
| 225 | } | ||
| 226 | val atomB: TupleTableAtom = { | ||
| 227 | val cls = axiom.getSuperClass | ||
| 228 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 229 | .getFiller | ||
| 230 | .asInstanceOf[OWLClass] | ||
| 231 | .getIRI | ||
| 232 | TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) | ||
| 233 | } | ||
| 234 | cycle.flatMap { x => | ||
| 235 | List( | ||
| 236 | Rule.create(roleRf(x), atomA(x)), | ||
| 237 | Rule.create(atomB, atomA(x)) | ||
| 238 | ) | ||
| 239 | } | ||
| 240 | } | ||
| 241 | |||
| 242 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
| 243 | if (axiom.isT5) { | ||
| 244 | // TODO: get role in T5 axiom | ||
| 245 | // Assuming one role here | ||
| 246 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
| 247 | if (ontology.unsafeRoles contains role) { | ||
| 248 | val visitor = | ||
| 249 | new RDFoxAxiomConverter( | ||
| 250 | Variable.create("X"), | ||
| 251 | ontology.unsafeRoles, | ||
| 252 | SkolemStrategy.Standard(axiom.toString), | ||
| 253 | Forward | ||
| 254 | ) | ||
| 255 | axiom.accept(visitor) | ||
| 256 | } else { | ||
| 257 | rules1(axiom) ::: rules2(axiom) ::: rules3(axiom) | ||
| 258 | } | ||
| 259 | } else { | ||
| 260 | // Fallback to standard OWL to LP translation | ||
| 261 | super.visit(axiom) | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | ||
| 266 | val varX = Variable.create("X") | ||
| 267 | val visitorF = new RDFoxAxiomConverter( | ||
| 268 | varX, | ||
| 269 | ontology.unsafeRoles, | ||
| 270 | SkolemStrategy.None, | ||
| 271 | Forward | ||
| 272 | ) | ||
| 273 | val visitorB = new RDFoxAxiomConverter( | ||
| 274 | varX, | ||
| 275 | ontology.unsafeRoles, | ||
| 276 | SkolemStrategy.None, | ||
| 277 | Backward | ||
| 278 | ) | ||
| 279 | axiom.accept(visitorB) ::: axiom.accept(visitorF) | ||
| 280 | } | ||
| 281 | |||
| 282 | } | ||
| 283 | |||
| 284 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb | ||
| 2 | |||
| 3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
| 4 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
| 5 | Term, | ||
| 6 | IRI, | ||
| 7 | Variable, | ||
| 8 | Literal, | ||
| 9 | FunctionCall | ||
| 10 | } | ||
| 11 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 12 | Rule, | ||
| 13 | TupleTableAtom, | ||
| 14 | BindAtom, | ||
| 15 | TupleTableName, | ||
| 16 | Atom, | ||
| 17 | BodyFormula, | ||
| 18 | Negation | ||
| 19 | } | ||
| 20 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.{SelectQuery} | ||
| 21 | import tech.oxfordsemantic.jrdfox.logic.sparql.pattern.{ | ||
| 22 | GroupGraphPattern, | ||
| 23 | ConjunctionPattern, | ||
| 24 | TriplePattern, | ||
| 25 | QueryPattern | ||
| 26 | } | ||
| 27 | |||
| 28 | import scala.collection.JavaConverters._ | ||
| 29 | |||
| 30 | import uk.ac.ox.cs.rsacomb.implicits.RSAAtom | ||
| 31 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Forward, Backward} | ||
| 32 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
| 33 | |||
| 34 | class FilteringProgram(query: SelectQuery, constants: List[Term]) | ||
| 35 | extends RSAAtom { | ||
| 36 | |||
| 37 | /* Makes mplicit conversion OWLAPI IRI <-> RDFox IRI available */ | ||
| 38 | // import implicits.RDFox._ | ||
| 39 | |||
| 40 | implicit val variables: (List[Term], List[Term]) = { | ||
| 41 | val all: Set[Variable] = query.getQueryBody.getWherePattern match { | ||
| 42 | case b: ConjunctionPattern => { | ||
| 43 | b.getConjuncts.asScala.toSet.flatMap { conj: QueryPattern => | ||
| 44 | conj match { | ||
| 45 | case c: TriplePattern => | ||
| 46 | Set(c.getSubject, c.getPredicate, c.getObject).collect { | ||
| 47 | case v: Variable => v | ||
| 48 | } | ||
| 49 | case _ => Set() | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | case _ => Set() | ||
| 54 | } | ||
| 55 | if (query.getAllPossibleVariables) { | ||
| 56 | (all.toList, List()) | ||
| 57 | } else { | ||
| 58 | val answer = query.getSelection.asScala.map(_.getVariable).toSet | ||
| 59 | (answer.toList, (all &~ answer).toList) | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | val (answer, bounded) = variables | ||
| 64 | |||
| 65 | val facts: List[Rule] = constants.map(c => Rule.create(RSA.NI(c))) | ||
| 66 | val rules: List[Rule] = | ||
| 67 | this.generateFilteringProgram().map(reifyRule) ++ facts | ||
| 68 | |||
| 69 | /* NOTE: we are restricting to queries that contain conjunctions of | ||
| 70 | * atoms for the time being. This might need to be reviewed in the | ||
| 71 | * future. | ||
| 72 | */ | ||
| 73 | private def queryToBody(body: GroupGraphPattern): List[TupleTableAtom] = | ||
| 74 | body match { | ||
| 75 | case b: ConjunctionPattern => { | ||
| 76 | val conjuncts = b.getConjuncts.asScala.toList | ||
| 77 | conjuncts flatMap { conj => | ||
| 78 | conj match { | ||
| 79 | case c: TriplePattern => | ||
| 80 | List( | ||
| 81 | TupleTableAtom.rdf(c.getSubject, c.getPredicate, c.getObject) | ||
| 82 | ) | ||
| 83 | case _ => List() | ||
| 84 | } | ||
| 85 | } | ||
| 86 | } | ||
| 87 | case _ => List() | ||
| 88 | } | ||
| 89 | |||
| 90 | private def generateFilteringProgram(): List[Rule] = { | ||
| 91 | // General purpose variables | ||
| 92 | val varU = Variable.create("U") | ||
| 93 | val varV = Variable.create("V") | ||
| 94 | val varW = Variable.create("W") | ||
| 95 | // Query formula as a rule body | ||
| 96 | val body = queryToBody(query.getQueryBody.getWherePattern) | ||
| 97 | // Auxiliar predicates/helpers | ||
| 98 | def not(atom: TupleTableAtom): BodyFormula = Negation.create(atom) | ||
| 99 | // val predQM = | ||
| 100 | // TupleTableAtom.create( | ||
| 101 | // TupleTableName.create(RSA.Qm.getIRI), | ||
| 102 | // (answer ++ bounded): _* | ||
| 103 | // ) | ||
| 104 | // def predID(t1: Term, t2: Term) = | ||
| 105 | // TupleTableAtom.create( | ||
| 106 | // TupleTableName.create(RSA.rsa("ID").getIRI), | ||
| 107 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
| 108 | // ) | ||
| 109 | // def predNAMED(t1: Term): TupleTableAtom = | ||
| 110 | // TupleTableAtom.rdf(t1, IRI.RDF_TYPE, RSA.rsa("NAMED")) | ||
| 111 | // def predTQ(t1: Term, t2: Term, sx: RSASuffix) = | ||
| 112 | // TupleTableAtom.create( | ||
| 113 | // TupleTableName.create(RSA.rsa("TQ" :: sx).getIRI), | ||
| 114 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
| 115 | // ) | ||
| 116 | // def predAQ(t1: Term, t2: Term, sx: RSASuffix) = | ||
| 117 | // TupleTableAtom.create( | ||
| 118 | // TupleTableName.create(RSA.rsa("AQ" :: sx).getIRI), | ||
| 119 | // (answer ++ bounded).appended(t1).appended(t2): _* | ||
| 120 | // ) | ||
| 121 | // val predFK = | ||
| 122 | // TupleTableAtom.create( | ||
| 123 | // TupleTableName.create(RSA.rsa("FK").getIRI), | ||
| 124 | // (answer ++ bounded): _* | ||
| 125 | // ) | ||
| 126 | // val predSP = | ||
| 127 | // TupleTableAtom.create( | ||
| 128 | // TupleTableName.create(RSA.rsa("SP").getIRI), | ||
| 129 | // (answer ++ bounded): _* | ||
| 130 | // ) | ||
| 131 | // val predANS = | ||
| 132 | // TupleTableAtom.create( | ||
| 133 | // TupleTableName.create(RSA.rsa("ANS").getIRI), | ||
| 134 | // answer: _* | ||
| 135 | // ) | ||
| 136 | |||
| 137 | /* Rule 1 */ | ||
| 138 | val r1 = Rule.create(RSA.QM, body: _*) | ||
| 139 | |||
| 140 | /* Rules 3x */ | ||
| 141 | val r3a = | ||
| 142 | for ((v, i) <- bounded.zipWithIndex) | ||
| 143 | yield Rule.create(RSA.ID(RSA(i), RSA(i)), RSA.QM, not(RSA.NI(v))) | ||
| 144 | val r3b = Rule.create(RSA.ID(varV, varU), RSA.ID(varU, varV)) | ||
| 145 | val r3c = | ||
| 146 | Rule.create(RSA.ID(varU, varW), RSA.ID(varU, varV), RSA.ID(varV, varW)) | ||
| 147 | |||
| 148 | /* Rules 4x */ | ||
| 149 | val r4a = for { | ||
| 150 | role1 <- body.filter(_.isRoleAssertion) | ||
| 151 | if bounded contains (role1.getArguments.get(2)) | ||
| 152 | role2 <- body.filter(_.isRoleAssertion) | ||
| 153 | if bounded contains (role2.getArguments.get(2)) | ||
| 154 | } yield Rule.create( | ||
| 155 | RSA.FK, | ||
| 156 | role1 << Forward, | ||
| 157 | role2 << Forward, | ||
| 158 | RSA.ID( | ||
| 159 | RSA(bounded.indexOf(role1.getArguments.get(2))), | ||
| 160 | RSA(bounded.indexOf(role2.getArguments.get(2))) | ||
| 161 | ), | ||
| 162 | not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(0))) | ||
| 163 | ) | ||
| 164 | val r4b = for { | ||
| 165 | role1 <- body.filter(_.isRoleAssertion) | ||
| 166 | if bounded contains (role1.getArguments.get(2)) | ||
| 167 | role2 <- body.filter(_.isRoleAssertion) | ||
| 168 | if bounded contains (role2.getArguments.get(0)) | ||
| 169 | } yield Rule.create( | ||
| 170 | RSA.FK, | ||
| 171 | role1 << Forward, | ||
| 172 | role2 << Backward, | ||
| 173 | RSA.ID( | ||
| 174 | RSA(bounded.indexOf(role1.getArguments.get(2))), | ||
| 175 | RSA(bounded.indexOf(role2.getArguments.get(0))) | ||
| 176 | ), | ||
| 177 | not(RSA.congruent(role1.getArguments.get(0), role2.getArguments.get(2))) | ||
| 178 | ) | ||
| 179 | val r4c = for { | ||
| 180 | role1 <- body.filter(_.isRoleAssertion) | ||
| 181 | if bounded contains (role1.getArguments.get(0)) | ||
| 182 | role2 <- body.filter(_.isRoleAssertion) | ||
| 183 | if bounded contains (role2.getArguments.get(0)) | ||
| 184 | } yield Rule.create( | ||
| 185 | RSA.FK, | ||
| 186 | role1 << Backward, | ||
| 187 | role2 << Backward, | ||
| 188 | RSA.ID( | ||
| 189 | RSA(bounded.indexOf(role1.getArguments.get(0))), | ||
| 190 | RSA(bounded.indexOf(role2.getArguments.get(0))) | ||
| 191 | ), | ||
| 192 | not(RSA.congruent(role1.getArguments.get(2), role2.getArguments.get(2))) | ||
| 193 | ) | ||
| 194 | |||
| 195 | /* Rules 5x */ | ||
| 196 | val r5a = for { | ||
| 197 | role1 <- body.filter(_.isRoleAssertion) | ||
| 198 | role1arg0 = role1.getArguments.get(0) | ||
| 199 | role1arg2 = role1.getArguments.get(2) | ||
| 200 | if bounded contains role1arg0 | ||
| 201 | if bounded contains role1arg2 | ||
| 202 | role2 <- body.filter(_.isRoleAssertion) | ||
| 203 | role2arg0 = role2.getArguments.get(0) | ||
| 204 | role2arg2 = role2.getArguments.get(2) | ||
| 205 | if bounded contains role2arg0 | ||
| 206 | if bounded contains role2arg2 | ||
| 207 | } yield Rule.create( | ||
| 208 | RSA.ID( | ||
| 209 | RSA(bounded indexOf role1arg0), | ||
| 210 | RSA(bounded indexOf role2arg0) | ||
| 211 | ), | ||
| 212 | role1 << Forward, | ||
| 213 | role2 << Forward, | ||
| 214 | RSA.ID( | ||
| 215 | RSA(bounded indexOf role1arg2), | ||
| 216 | RSA(bounded indexOf role2arg2) | ||
| 217 | ), | ||
| 218 | RSA.congruent(role1arg0, role2arg0), | ||
| 219 | not(RSA.NI(role1arg0)) | ||
| 220 | ) | ||
| 221 | val r5b = for { | ||
| 222 | role1 <- body.filter(_.isRoleAssertion) | ||
| 223 | role1arg0 = role1.getArguments.get(0) | ||
| 224 | role1arg2 = role1.getArguments.get(2) | ||
| 225 | if bounded contains role1arg0 | ||
| 226 | if bounded contains role1arg2 | ||
| 227 | role2 <- body.filter(_.isRoleAssertion) | ||
| 228 | role2arg0 = role2.getArguments.get(0) | ||
| 229 | role2arg2 = role2.getArguments.get(2) | ||
| 230 | if bounded contains role2arg0 | ||
| 231 | if bounded contains role2arg2 | ||
| 232 | } yield Rule.create( | ||
| 233 | RSA.ID( | ||
| 234 | RSA(bounded indexOf role1arg0), | ||
| 235 | RSA(bounded indexOf role2arg2) | ||
| 236 | ), | ||
| 237 | role1 << Forward, | ||
| 238 | role2 << Backward, | ||
| 239 | RSA.ID( | ||
| 240 | RSA(bounded indexOf role1arg2), | ||
| 241 | RSA(bounded indexOf role2arg0) | ||
| 242 | ), | ||
| 243 | RSA.congruent(role1arg0, role2arg2), | ||
| 244 | not(RSA.NI(role1arg0)) | ||
| 245 | ) | ||
| 246 | val r5c = for { | ||
| 247 | role1 <- body.filter(_.isRoleAssertion) | ||
| 248 | role1arg0 = role1.getArguments.get(0) | ||
| 249 | role1arg2 = role1.getArguments.get(2) | ||
| 250 | if bounded contains role1arg0 | ||
| 251 | if bounded contains role1arg2 | ||
| 252 | role2 <- body.filter(_.isRoleAssertion) | ||
| 253 | role2arg0 = role2.getArguments.get(0) | ||
| 254 | role2arg2 = role2.getArguments.get(2) | ||
| 255 | if bounded contains role2arg0 | ||
| 256 | if bounded contains role2arg2 | ||
| 257 | } yield Rule.create( | ||
| 258 | RSA.ID( | ||
| 259 | RSA(bounded indexOf role1arg2), | ||
| 260 | RSA(bounded indexOf role2arg2) | ||
| 261 | ), | ||
| 262 | role1 << Backward, | ||
| 263 | role2 << Backward, | ||
| 264 | RSA.ID( | ||
| 265 | RSA(bounded indexOf role1arg0), | ||
| 266 | RSA(bounded indexOf role2arg0) | ||
| 267 | ), | ||
| 268 | RSA.congruent(role1arg2, role2arg2), | ||
| 269 | not(RSA.NI(role1arg2)) | ||
| 270 | ) | ||
| 271 | |||
| 272 | /* Rules 6 */ | ||
| 273 | val r6 = { | ||
| 274 | for { | ||
| 275 | role <- body.filter(_.isRoleAssertion) | ||
| 276 | arg0 = role.getArguments.get(0) | ||
| 277 | arg2 = role.getArguments.get(2) | ||
| 278 | if bounded contains arg0 | ||
| 279 | if bounded contains arg2 | ||
| 280 | suffix <- Seq(Forward, Backward) | ||
| 281 | } yield Rule.create( | ||
| 282 | RSA.AQ(varV, varW, suffix), | ||
| 283 | role << suffix, | ||
| 284 | RSA.ID(RSA(bounded indexOf arg0), varV), | ||
| 285 | RSA.ID(RSA(bounded indexOf arg2), varW) | ||
| 286 | ) | ||
| 287 | } | ||
| 288 | |||
| 289 | /* Rules 7x */ | ||
| 290 | val r7a = { | ||
| 291 | for (suffix <- List(Forward, Backward)) | ||
| 292 | yield Rule.create( | ||
| 293 | RSA.TQ(varU, varV, suffix), | ||
| 294 | RSA.AQ(varU, varV, suffix) | ||
| 295 | ) | ||
| 296 | } | ||
| 297 | val r7b = { | ||
| 298 | for (suffix <- List(Forward, Backward)) | ||
| 299 | yield Rule.create( | ||
| 300 | RSA.TQ(varU, varW, suffix), | ||
| 301 | RSA.AQ(varU, varV, suffix), | ||
| 302 | RSA.TQ(varV, varW, suffix) | ||
| 303 | ) | ||
| 304 | } | ||
| 305 | |||
| 306 | /* Rules 8x */ | ||
| 307 | val r8a = | ||
| 308 | for (v <- answer) yield Rule.create(RSA.SP, RSA.QM, not(RSA.Named(v))) | ||
| 309 | val r8b = | ||
| 310 | Rule.create(RSA.SP, RSA.FK) | ||
| 311 | val r8c = | ||
| 312 | for (suffix <- List(Forward, Backward)) | ||
| 313 | yield Rule.create( | ||
| 314 | RSA.SP, | ||
| 315 | RSA.TQ(varV, varV, suffix) | ||
| 316 | ) | ||
| 317 | |||
| 318 | /* Rule 9 */ | ||
| 319 | val r9 = Rule.create(RSA.Ans, RSA.QM, not(RSA.SP)) | ||
| 320 | |||
| 321 | r1 :: | ||
| 322 | r3a ::: r3b :: r3c :: | ||
| 323 | r4c ::: r4b ::: r4a ::: | ||
| 324 | r5c ::: r5b ::: r5a ::: | ||
| 325 | r6 ::: | ||
| 326 | r7b ::: r7a ::: | ||
| 327 | r8a ::: r8b :: r8c ::: | ||
| 328 | r9 :: | ||
| 329 | List() | ||
| 330 | } | ||
| 331 | |||
| 332 | private def reifyAtom(atom: Atom): (Option[BindAtom], List[Atom]) = { | ||
| 333 | atom match { | ||
| 334 | case atom: TupleTableAtom => atom.reified | ||
| 335 | case other => (None, List(other)) | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | private def reifyBodyFormula(formula: BodyFormula): List[BodyFormula] = { | ||
| 340 | formula match { | ||
| 341 | case atom: TupleTableAtom => atom.reified._2 | ||
| 342 | case neg: Negation => { | ||
| 343 | val (bs, as) = neg.getNegatedAtoms.asScala.toList.map(reifyAtom).unzip | ||
| 344 | val bind = bs.flatten.map(_.getBoundVariable).asJava | ||
| 345 | val atoms = as.flatten.asJava | ||
| 346 | List(Negation.create(bind, atoms)) | ||
| 347 | } | ||
| 348 | case other => List(other) | ||
| 349 | } | ||
| 350 | } | ||
| 351 | |||
| 352 | private def reifyRule(rule: Rule): Rule = { | ||
| 353 | val (bs, hs) = rule.getHead.asScala.toList.map(_.reified).unzip | ||
| 354 | val head: List[TupleTableAtom] = hs.flatten | ||
| 355 | val bind: List[BodyFormula] = bs.flatten | ||
| 356 | val body: List[BodyFormula] = | ||
| 357 | rule.getBody.asScala.toList.map(reifyBodyFormula).flatten | ||
| 358 | Rule.create(head.asJava, (body ++ bind).asJava) | ||
| 359 | } | ||
| 360 | |||
| 361 | } // class FilteringProgram | ||
| 362 | |||
| 363 | object FilteringProgram { | ||
| 364 | def apply(query: SelectQuery, constants: List[Term]): FilteringProgram = | ||
| 365 | new FilteringProgram(query, constants) | ||
| 366 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb | ||
| 2 | |||
| 3 | /* Java imports */ | ||
| 4 | import java.io.File | ||
| 5 | import java.util.HashMap | ||
| 6 | import scala.collection.JavaConverters._ | ||
| 7 | |||
| 8 | import tech.oxfordsemantic.jrdfox.client.UpdateType | ||
| 9 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
| 10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term} | ||
| 11 | |||
| 12 | /* Local imports */ | ||
| 13 | import util.{RDFoxHelpers, RSA} | ||
| 14 | |||
| 15 | object RSAComb extends App { | ||
| 16 | |||
| 17 | val help: String = """ | ||
| 18 | rsacomb - combined approach for CQ answering for RSA ontologies. | ||
| 19 | |||
| 20 | USAGE | ||
| 21 | rsacomb <path/to/ontology.owl> <path/to/query.sparql> | ||
| 22 | |||
| 23 | where | ||
| 24 | the ontology is expected to be an OWL file and the (single) | ||
| 25 | query a SPARQL query file. | ||
| 26 | |||
| 27 | """ | ||
| 28 | |||
| 29 | /* Simple arguments handling | ||
| 30 | * | ||
| 31 | * TODO: use something better later on | ||
| 32 | */ | ||
| 33 | |||
| 34 | if (args.length < 2) { | ||
| 35 | println(help) | ||
| 36 | sys.exit; | ||
| 37 | } | ||
| 38 | |||
| 39 | val ontoPath = new File(args(0)) | ||
| 40 | val queryPath = new File(args(1)) | ||
| 41 | |||
| 42 | if (!ontoPath.isFile || !queryPath.isFile) { | ||
| 43 | println("The provided arguments are not regular files.\n\n") | ||
| 44 | println(help) | ||
| 45 | sys.exit; | ||
| 46 | } | ||
| 47 | |||
| 48 | /* Create RSA object from generic OWLOntology | ||
| 49 | * | ||
| 50 | * TODO: It might be required to check if the ontology in input is | ||
| 51 | * Horn-ALCHOIQ. At the moment we are assuming this is always the | ||
| 52 | * case. | ||
| 53 | */ | ||
| 54 | |||
| 55 | val ontology = RSAOntology(ontoPath) | ||
| 56 | if (ontology.isRSA) { | ||
| 57 | |||
| 58 | /* Load query */ | ||
| 59 | val query = RDFoxHelpers.parseSelectQuery( | ||
| 60 | """ | ||
| 61 | PREFIX : <http://example.com/rsa_example.owl#> | ||
| 62 | |||
| 63 | SELECT ?X | ||
| 64 | WHERE { | ||
| 65 | ?X a :D ; | ||
| 66 | :R ?Y . | ||
| 67 | ?Y :S ?Z . | ||
| 68 | ?Z a :D . | ||
| 69 | } | ||
| 70 | """ | ||
| 71 | ) | ||
| 72 | |||
| 73 | /* Compute answers to query */ | ||
| 74 | query match { | ||
| 75 | case Some(query) => { | ||
| 76 | |||
| 77 | import implicits.JavaCollections._ | ||
| 78 | |||
| 79 | // Open connection to RDFox | ||
| 80 | val (server, data) = RDFoxHelpers.openConnection("AnswerComputation") | ||
| 81 | |||
| 82 | { | ||
| 83 | println("\nQuery") | ||
| 84 | println(query) | ||
| 85 | } | ||
| 86 | |||
| 87 | // Step 1. Computing the canonical model | ||
| 88 | val canon = ontology.canonicalModel | ||
| 89 | data.addRules(canon.rules) | ||
| 90 | |||
| 91 | { | ||
| 92 | println("\nCanonical Model rules:") | ||
| 93 | canon.rules.foreach(println) | ||
| 94 | } | ||
| 95 | |||
| 96 | // Step 2. Computing the canonical model | ||
| 97 | val nis = { | ||
| 98 | val query = "SELECT ?Y WHERE { ?X rsa:EquivTo ?Y ; a rsa:Named . }" | ||
| 99 | RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes).flatten | ||
| 100 | } | ||
| 101 | val filter = ontology.filteringProgram(query, nis) | ||
| 102 | data.addRules(filter.rules) | ||
| 103 | |||
| 104 | { | ||
| 105 | println("\nFiltering rules") | ||
| 106 | filter.rules.foreach(println) | ||
| 107 | } | ||
| 108 | |||
| 109 | // Retrieve answers | ||
| 110 | println("\nAnswers:") | ||
| 111 | val ans = | ||
| 112 | RDFoxHelpers.queryInternalPredicate(data, "Ans", filter.answer.length) | ||
| 113 | println(ans) | ||
| 114 | |||
| 115 | /* DEBUG: adding additional checks | ||
| 116 | */ | ||
| 117 | { | ||
| 118 | import suffix.{Forward, Backward} | ||
| 119 | |||
| 120 | val arity = filter.answer.length + filter.bounded.length | ||
| 121 | |||
| 122 | println("\nIndividuals:") | ||
| 123 | ontology.individuals.foreach(println) | ||
| 124 | |||
| 125 | println("\nThings:") | ||
| 126 | val things = RDFoxHelpers.submitSelectQuery( | ||
| 127 | data, | ||
| 128 | """ | ||
| 129 | PREFIX owl: <http://www.w3.org/2002/07/owl#> | ||
| 130 | |||
| 131 | SELECT ?X { | ||
| 132 | ?X a owl:Thing | ||
| 133 | } | ||
| 134 | """ | ||
| 135 | ) | ||
| 136 | println(things) | ||
| 137 | |||
| 138 | println("\nNAMEDs:") | ||
| 139 | val named = RDFoxHelpers.submitSelectQuery( | ||
| 140 | data, | ||
| 141 | """ | ||
| 142 | SELECT ?X { | ||
| 143 | ?X a rsa:Named | ||
| 144 | } | ||
| 145 | """, | ||
| 146 | RSA.Prefixes | ||
| 147 | ) | ||
| 148 | println(named) | ||
| 149 | |||
| 150 | println("\nNIs:") | ||
| 151 | val nis = RDFoxHelpers.submitSelectQuery( | ||
| 152 | data, | ||
| 153 | """ | ||
| 154 | SELECT ?X { | ||
| 155 | ?X a rsa:NI | ||
| 156 | } | ||
| 157 | """, | ||
| 158 | RSA.Prefixes | ||
| 159 | ) | ||
| 160 | println(nis) | ||
| 161 | |||
| 162 | // ID instances | ||
| 163 | println("\nIDs:") | ||
| 164 | val ids = RDFoxHelpers.queryInternalPredicate( | ||
| 165 | data, | ||
| 166 | "ID", | ||
| 167 | arity + 2 | ||
| 168 | ) | ||
| 169 | println(ids) | ||
| 170 | |||
| 171 | println("\nEquivTo:") | ||
| 172 | val equivs = RDFoxHelpers.submitSelectQuery( | ||
| 173 | data, | ||
| 174 | """ | ||
| 175 | SELECT ?X ?Y { | ||
| 176 | ?X rsa:EquivTo ?Y | ||
| 177 | } | ||
| 178 | """, | ||
| 179 | RSA.Prefixes | ||
| 180 | ) | ||
| 181 | println(equivs) | ||
| 182 | |||
| 183 | // Unfiltered answers | ||
| 184 | println("\nPossible answers:") | ||
| 185 | val qms = RDFoxHelpers.queryInternalPredicate( | ||
| 186 | data, | ||
| 187 | "QM", | ||
| 188 | arity | ||
| 189 | ) | ||
| 190 | println(qms) | ||
| 191 | |||
| 192 | // Cycle detected | ||
| 193 | println("\nCycle detection:") | ||
| 194 | val aqf = RDFoxHelpers.queryInternalPredicate( | ||
| 195 | data, | ||
| 196 | "AQ" :: Forward, | ||
| 197 | arity + 2 | ||
| 198 | ) | ||
| 199 | val aqb = RDFoxHelpers.queryInternalPredicate( | ||
| 200 | data, | ||
| 201 | "AQ" :: Backward, | ||
| 202 | arity + 2 | ||
| 203 | ) | ||
| 204 | println(aqf) | ||
| 205 | println(aqb) | ||
| 206 | |||
| 207 | // Forks detected | ||
| 208 | println("\nForks:") | ||
| 209 | val fk = RDFoxHelpers.queryInternalPredicate( | ||
| 210 | data, | ||
| 211 | "FK", | ||
| 212 | arity | ||
| 213 | ) | ||
| 214 | println(fk) | ||
| 215 | |||
| 216 | // Spurious answers | ||
| 217 | println("\nSpurious answers") | ||
| 218 | val sp = RDFoxHelpers.queryInternalPredicate( | ||
| 219 | data, | ||
| 220 | "SP", | ||
| 221 | arity | ||
| 222 | ) | ||
| 223 | println(sp) | ||
| 224 | } | ||
| 225 | |||
| 226 | // Close connection to RDFox | ||
| 227 | RDFoxHelpers.closeConnection(server, data) | ||
| 228 | } | ||
| 229 | case None => {} | ||
| 230 | } | ||
| 231 | } | ||
| 232 | } | ||
| 233 | |||
| 234 | /* Notes: | ||
| 235 | * | ||
| 236 | * To establish a connection with a local RDFox instance, do the | ||
| 237 | * following: | ||
| 238 | * | ||
| 239 | * ``` | ||
| 240 | * val serverConnection : ServerConnection = ConnectionFactory.newServerConnection("rdfox:local", "", "") | ||
| 241 | * serverConnection.createDataStore("test","seq",new HashMap()) | ||
| 242 | * val dataStoreConnection : DataStoreConnection = serverConnection.newDataStoreConnection("test") | ||
| 243 | * dataStoreConnection.importData( | ||
| 244 | * UpdateType.ADDITION, | ||
| 245 | * Prefixes.s_emptyPrefixes, | ||
| 246 | * new File("./path/to/file") | ||
| 247 | * ) | ||
| 248 | * ``` | ||
| 249 | */ | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb | ||
| 2 | |||
| 3 | /* Java imports */ | ||
| 4 | import java.util.HashMap | ||
| 5 | import java.util.stream.{Collectors, Stream} | ||
| 6 | import java.io.File | ||
| 7 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
| 8 | |||
| 9 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} | ||
| 10 | import org.semanticweb.owlapi.model.{ | ||
| 11 | OWLClass, | ||
| 12 | OWLObjectProperty, | ||
| 13 | OWLSubObjectPropertyOfAxiom, | ||
| 14 | OWLObjectPropertyExpression, | ||
| 15 | OWLObjectSomeValuesFrom, | ||
| 16 | OWLSubClassOfAxiom | ||
| 17 | } | ||
| 18 | import org.semanticweb.owlapi.model.parameters.Imports | ||
| 19 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory | ||
| 20 | import org.semanticweb.owlapi.model.{IRI => OWLIRI} | ||
| 21 | import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl | ||
| 22 | |||
| 23 | import tech.oxfordsemantic.jrdfox.client.{UpdateType, DataStoreConnection} | ||
| 24 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 25 | Rule, | ||
| 26 | TupleTableAtom, | ||
| 27 | Negation, | ||
| 28 | BodyFormula | ||
| 29 | } | ||
| 30 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
| 31 | Term, | ||
| 32 | Variable, | ||
| 33 | IRI, | ||
| 34 | Resource | ||
| 35 | } | ||
| 36 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
| 37 | |||
| 38 | /* Scala imports */ | ||
| 39 | import scala.collection.JavaConverters._ | ||
| 40 | import scala.collection.mutable.Set | ||
| 41 | import scalax.collection.immutable.Graph | ||
| 42 | import scalax.collection.GraphEdge.UnDiEdge | ||
| 43 | |||
| 44 | /* Debug only */ | ||
| 45 | import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer | ||
| 46 | import tech.oxfordsemantic.jrdfox.logic._ | ||
| 47 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | ||
| 48 | |||
| 49 | import uk.ac.ox.cs.rsacomb.converter.{RDFoxAxiomConverter, SkolemStrategy} | ||
| 50 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom | ||
| 51 | import uk.ac.ox.cs.rsacomb.suffix.{Empty, Forward, Backward, Inverse} | ||
| 52 | import uk.ac.ox.cs.rsacomb.util.{RDFoxHelpers, RSA} | ||
| 53 | |||
| 54 | object RSAOntology { | ||
| 55 | |||
| 56 | // Counter used to implement a simple fresh variable generator | ||
| 57 | private var counter = -1; | ||
| 58 | |||
| 59 | def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) | ||
| 60 | |||
| 61 | def apply(ontology: File): RSAOntology = | ||
| 62 | new RSAOntology(loadOntology(ontology)) | ||
| 63 | |||
| 64 | def genFreshVariable(): Variable = { | ||
| 65 | counter += 1 | ||
| 66 | Variable.create(f"I$counter%03d") | ||
| 67 | } | ||
| 68 | |||
| 69 | private def loadOntology(onto: File): OWLOntology = { | ||
| 70 | val manager = OWLManager.createOWLOntologyManager() | ||
| 71 | manager.loadOntologyFromOntologyDocument(onto) | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | class RSAOntology(val ontology: OWLOntology) extends RSAAxiom { | ||
| 76 | |||
| 77 | // Gather TBox/RBox/ABox from original ontology | ||
| 78 | val tbox: List[OWLAxiom] = | ||
| 79 | ontology | ||
| 80 | .tboxAxioms(Imports.INCLUDED) | ||
| 81 | .collect(Collectors.toList()) | ||
| 82 | .asScala | ||
| 83 | .toList | ||
| 84 | |||
| 85 | val rbox: List[OWLAxiom] = | ||
| 86 | ontology | ||
| 87 | .rboxAxioms(Imports.INCLUDED) | ||
| 88 | .collect(Collectors.toList()) | ||
| 89 | .asScala | ||
| 90 | .toList | ||
| 91 | |||
| 92 | val abox: List[OWLAxiom] = | ||
| 93 | ontology | ||
| 94 | .aboxAxioms(Imports.INCLUDED) | ||
| 95 | .collect(Collectors.toList()) | ||
| 96 | .asScala | ||
| 97 | .toList | ||
| 98 | |||
| 99 | val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox | ||
| 100 | |||
| 101 | /* Retrieve individuals in the original ontology | ||
| 102 | */ | ||
| 103 | val individuals: List[IRI] = | ||
| 104 | ontology | ||
| 105 | .getIndividualsInSignature() | ||
| 106 | .asScala | ||
| 107 | .map(_.getIRI) | ||
| 108 | .map(implicits.RDFox.owlapiToRdfoxIri) | ||
| 109 | .toList | ||
| 110 | |||
| 111 | val concepts: List[OWLClass] = | ||
| 112 | ontology.getClassesInSignature().asScala.toList | ||
| 113 | |||
| 114 | val roles: List[OWLObjectPropertyExpression] = | ||
| 115 | axioms | ||
| 116 | .flatMap(_.objectPropertyExpressionsInSignature) | ||
| 117 | .distinct | ||
| 118 | |||
| 119 | // OWLAPI reasoner for same easier tasks | ||
| 120 | private val reasoner = | ||
| 121 | (new StructuralReasonerFactory()).createReasoner(ontology) | ||
| 122 | |||
| 123 | /* Steps for RSA check | ||
| 124 | * 1) convert ontology axioms into LP rules | ||
| 125 | * 2) call RDFox on the onto and compute materialization | ||
| 126 | * 3) build graph from E(x,y) facts | ||
| 127 | * 4) check if the graph is tree-like | ||
| 128 | * ideally this annotates the graph with info about the reasons | ||
| 129 | * why the ontology might not be RSA. This could help a second | ||
| 130 | * step of approximation of an Horn-ALCHOIQ to RSA | ||
| 131 | */ | ||
| 132 | lazy val isRSA: Boolean = { | ||
| 133 | |||
| 134 | val unsafe = this.unsafeRoles | ||
| 135 | |||
| 136 | /* DEBUG: print rules in DL syntax and unsafe roles */ | ||
| 137 | //val renderer = new DLSyntaxObjectRenderer() | ||
| 138 | //println("\nDL rules:") | ||
| 139 | //axioms.foreach(x => println(renderer.render(x))) | ||
| 140 | //println("\nUnsafe roles:") | ||
| 141 | //println(unsafe) | ||
| 142 | |||
| 143 | /* Ontology convertion into LP rules */ | ||
| 144 | val datalog = for { | ||
| 145 | axiom <- axioms | ||
| 146 | visitor = new RDFoxAxiomConverter( | ||
| 147 | RSAOntology.genFreshVariable(), | ||
| 148 | unsafe, | ||
| 149 | SkolemStrategy.ConstantRSA(axiom.toString), | ||
| 150 | Empty | ||
| 151 | ) | ||
| 152 | rule <- axiom.accept(visitor) | ||
| 153 | } yield rule | ||
| 154 | |||
| 155 | /* DEBUG: print datalog rules */ | ||
| 156 | //println("\nDatalog roles:") | ||
| 157 | //datalog.foreach(println) | ||
| 158 | |||
| 159 | // Open connection with RDFox | ||
| 160 | val (server, data) = RDFoxHelpers.openConnection("RSACheck") | ||
| 161 | // Add Data (hardcoded for now) | ||
| 162 | //data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") | ||
| 163 | |||
| 164 | /* Add built-in rules | ||
| 165 | */ | ||
| 166 | data.importData( | ||
| 167 | UpdateType.ADDITION, | ||
| 168 | RSA.Prefixes, | ||
| 169 | "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ." | ||
| 170 | ) | ||
| 171 | |||
| 172 | /* Add built-in rules | ||
| 173 | */ | ||
| 174 | // data.importData( | ||
| 175 | // UpdateType.ADDITION, | ||
| 176 | // RSA.Prefixes, | ||
| 177 | // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ." | ||
| 178 | // ) | ||
| 179 | |||
| 180 | /* Add ontology rules | ||
| 181 | */ | ||
| 182 | data.addRules(datalog.asJava) | ||
| 183 | |||
| 184 | /* Build graph | ||
| 185 | */ | ||
| 186 | val graph = this.rsaGraph(data); | ||
| 187 | //println(graph) | ||
| 188 | |||
| 189 | // Close connection to RDFox | ||
| 190 | RDFoxHelpers.closeConnection(server, data) | ||
| 191 | |||
| 192 | /* To check if the graph is tree-like we check for acyclicity in a | ||
| 193 | * undirected graph. | ||
| 194 | * | ||
| 195 | * TODO: Implement additional checks (taking into account equality) | ||
| 196 | */ | ||
| 197 | graph.isAcyclic | ||
| 198 | } | ||
| 199 | |||
| 200 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { | ||
| 201 | |||
| 202 | /* DEBUG: print rules in DL syntax */ | ||
| 203 | //val renderer = new DLSyntaxObjectRenderer() | ||
| 204 | |||
| 205 | /* Checking for (1) unsafety condition: | ||
| 206 | * | ||
| 207 | * For all roles r1 appearing in an axiom of type T5, r1 is unsafe | ||
| 208 | * if there exists a role r2 (different from top) appearing in an axiom | ||
| 209 | * of type T3 and r1 is a subproperty of the inverse of r2. | ||
| 210 | */ | ||
| 211 | val unsafe1 = for { | ||
| 212 | axiom <- tbox | ||
| 213 | if axiom.isT5 | ||
| 214 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
| 215 | roleSuper = | ||
| 216 | role1 +: reasoner | ||
| 217 | .superObjectProperties(role1) | ||
| 218 | .collect(Collectors.toList()) | ||
| 219 | .asScala | ||
| 220 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
| 221 | axiom <- tbox | ||
| 222 | if axiom.isT3 && !axiom.isT3top | ||
| 223 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
| 224 | if roleSuperInv.contains(role2) | ||
| 225 | } yield role1 | ||
| 226 | |||
| 227 | /* Checking for (2) unsafety condition: | ||
| 228 | * | ||
| 229 | * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if | ||
| 230 | * there exists a role p2 appearing in an axiom of type T4 and p1 is a | ||
| 231 | * subproperty of either p2 or the inverse of p2. | ||
| 232 | * | ||
| 233 | */ | ||
| 234 | val unsafe2 = for { | ||
| 235 | axiom <- tbox | ||
| 236 | if axiom.isT5 | ||
| 237 | role1 <- axiom.objectPropertyExpressionsInSignature | ||
| 238 | roleSuper = | ||
| 239 | role1 +: reasoner | ||
| 240 | .superObjectProperties(role1) | ||
| 241 | .collect(Collectors.toList()) | ||
| 242 | .asScala | ||
| 243 | roleSuperInv = roleSuper.map(_.getInverseProperty) | ||
| 244 | axiom <- tbox | ||
| 245 | if axiom.isT4 | ||
| 246 | role2 <- axiom.objectPropertyExpressionsInSignature | ||
| 247 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | ||
| 248 | } yield role1 | ||
| 249 | |||
| 250 | (unsafe1 ++ unsafe2).toList | ||
| 251 | } | ||
| 252 | |||
| 253 | private def rsaGraph( | ||
| 254 | data: DataStoreConnection | ||
| 255 | ): Graph[Resource, UnDiEdge] = { | ||
| 256 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | ||
| 257 | val answers = RDFoxHelpers.submitSelectQuery(data, query, RSA.Prefixes) | ||
| 258 | var edges: List[UnDiEdge[Resource]] = answers.map { | ||
| 259 | case n1 :: n2 :: _ => UnDiEdge(n1, n2) | ||
| 260 | } | ||
| 261 | Graph(edges: _*) | ||
| 262 | } | ||
| 263 | |||
| 264 | def filteringProgram( | ||
| 265 | query: SelectQuery, | ||
| 266 | nis: List[Term] | ||
| 267 | ): FilteringProgram = | ||
| 268 | new FilteringProgram(query, nis) | ||
| 269 | |||
| 270 | lazy val canonicalModel = new CanonicalModel(this) | ||
| 271 | |||
| 272 | // TODO: the following functions needs testing | ||
| 273 | def confl( | ||
| 274 | role: OWLObjectPropertyExpression | ||
| 275 | ): Set[OWLObjectPropertyExpression] = { | ||
| 276 | |||
| 277 | val invSuperRoles = reasoner | ||
| 278 | .superObjectProperties(role) | ||
| 279 | .collect(Collectors.toSet()) | ||
| 280 | .asScala | ||
| 281 | .addOne(role) | ||
| 282 | .map(_.getInverseProperty) | ||
| 283 | |||
| 284 | invSuperRoles | ||
| 285 | .flatMap(x => | ||
| 286 | reasoner | ||
| 287 | .subObjectProperties(x) | ||
| 288 | .collect(Collectors.toSet()) | ||
| 289 | .asScala | ||
| 290 | .addOne(x) | ||
| 291 | ) | ||
| 292 | .filterNot(_.isOWLBottomObjectProperty()) | ||
| 293 | .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) | ||
| 294 | } | ||
| 295 | |||
| 296 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
| 297 | // Assuming just one role in the signature of a T5 axiom | ||
| 298 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
| 299 | if (this.confl(role).contains(role)) { | ||
| 300 | Set( | ||
| 301 | RSA("v0_" ++ axiom.hashed), | ||
| 302 | RSA("v1_" ++ axiom.hashed) | ||
| 303 | ) | ||
| 304 | } else { | ||
| 305 | Set() | ||
| 306 | } | ||
| 307 | } | ||
| 308 | |||
| 309 | // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
| 310 | // // Assuming just one role in the signature of a T5 axiom | ||
| 311 | // val roleR = axiom.objectPropertyExpressionsInSignature(0) | ||
| 312 | // val conflR = this.confl(roleR) | ||
| 313 | // // We just need the TBox to find | ||
| 314 | // val tbox = ontology | ||
| 315 | // .tboxAxioms(Imports.INCLUDED) | ||
| 316 | // .collect(Collectors.toSet()) | ||
| 317 | // .asScala | ||
| 318 | // for { | ||
| 319 | // axiom1 <- tbox | ||
| 320 | // // TODO: is this an optimization or an error? | ||
| 321 | // if axiom1.isT5 | ||
| 322 | // // We expect only one role coming out of a T5 axiom | ||
| 323 | // roleS <- axiom1.objectPropertyExpressionsInSignature | ||
| 324 | // // Triples ordering is among triples involving safe roles. | ||
| 325 | // if !unsafeRoles.contains(roleS) | ||
| 326 | // if conflR.contains(roleS) | ||
| 327 | // individual = | ||
| 328 | // if (axiom.hashCode < axiom1.hashCode) { | ||
| 329 | // RSA.rsa("v0_" ++ axiom1.hashCode.toString()) | ||
| 330 | // } else { | ||
| 331 | // RSA.rsa("v1_" ++ axiom1.hashCode.toString()) | ||
| 332 | // } | ||
| 333 | // } yield individual | ||
| 334 | // } | ||
| 335 | |||
| 336 | def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
| 337 | // TODO: we can actually use `toTriple` from `RSAAxiom` | ||
| 338 | val classes = | ||
| 339 | axiom.classesInSignature.collect(Collectors.toList()).asScala | ||
| 340 | val classA = classes(0) | ||
| 341 | val roleR = axiom | ||
| 342 | .objectPropertyExpressionsInSignature(0) | ||
| 343 | .asInstanceOf[OWLObjectProperty] | ||
| 344 | val classB = classes(1) | ||
| 345 | cycle_aux(classA, roleR, classB) | ||
| 346 | } | ||
| 347 | |||
| 348 | def cycle_aux( | ||
| 349 | classA: OWLClass, | ||
| 350 | roleR: OWLObjectProperty, | ||
| 351 | classB: OWLClass | ||
| 352 | ): Set[Term] = { | ||
| 353 | val conflR = this.confl(roleR) | ||
| 354 | val classes = ontology | ||
| 355 | .classesInSignature(Imports.INCLUDED) | ||
| 356 | .collect(Collectors.toSet()) | ||
| 357 | .asScala | ||
| 358 | for { | ||
| 359 | classD <- classes | ||
| 360 | roleS <- conflR | ||
| 361 | classC <- classes | ||
| 362 | // Keeping this check for now | ||
| 363 | if !unsafeRoles.contains(roleS) | ||
| 364 | tripleARB = RSAAxiom.hashed(classA, roleR, classB) | ||
| 365 | tripleDSC = RSAAxiom.hashed(classD, roleS, classC) | ||
| 366 | individual = | ||
| 367 | if (tripleARB > tripleDSC) { | ||
| 368 | RSA("v1_" ++ tripleDSC) | ||
| 369 | } else { | ||
| 370 | // Note that this is also the case for | ||
| 371 | // `tripleARB == tripleDSC` | ||
| 372 | RSA("v0_" ++ tripleDSC) | ||
| 373 | } | ||
| 374 | } yield individual | ||
| 375 | } | ||
| 376 | |||
| 377 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | ||
| 378 | this.self(axiom) | this.cycle(axiom) | ||
| 379 | |||
| 380 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import org.semanticweb.owlapi.model.{ | ||
| 4 | OWLAxiom, | ||
| 5 | OWLSubClassOfAxiom, | ||
| 6 | OWLEquivalentClassesAxiom, | ||
| 7 | OWLObjectPropertyExpression | ||
| 8 | } | ||
| 9 | import org.semanticweb.owlapi.model.OWLAxiomVisitorEx | ||
| 10 | |||
| 11 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 12 | Rule, | ||
| 13 | BodyFormula, | ||
| 14 | TupleTableAtom, | ||
| 15 | TupleTableName | ||
| 16 | } | ||
| 17 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
| 18 | Term, | ||
| 19 | IRI, | ||
| 20 | Variable, | ||
| 21 | Literal | ||
| 22 | } | ||
| 23 | |||
| 24 | import scala.collection.JavaConverters._ | ||
| 25 | |||
| 26 | import org.semanticweb.owlapi.model.OWLSubObjectPropertyOfAxiom | ||
| 27 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
| 28 | import org.semanticweb.owlapi.model.OWLClassAssertionAxiom | ||
| 29 | |||
| 30 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 31 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} | ||
| 32 | |||
| 33 | object RDFoxAxiomConverter { | ||
| 34 | |||
| 35 | def apply( | ||
| 36 | term: Term, | ||
| 37 | unsafe: List[OWLObjectPropertyExpression], | ||
| 38 | skolem: SkolemStrategy = SkolemStrategy.None, | ||
| 39 | suffix: RSASuffix = Empty | ||
| 40 | ): RDFoxAxiomConverter = | ||
| 41 | new RDFoxAxiomConverter(term, unsafe, skolem, suffix) | ||
| 42 | |||
| 43 | } // object RDFoxAxiomConverter | ||
| 44 | |||
| 45 | class RDFoxAxiomConverter( | ||
| 46 | term: Term, | ||
| 47 | unsafe: List[OWLObjectPropertyExpression], | ||
| 48 | skolem: SkolemStrategy, | ||
| 49 | suffix: RSASuffix | ||
| 50 | ) extends OWLAxiomVisitorEx[List[Rule]] { | ||
| 51 | |||
| 52 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
| 53 | // Skolemization is needed only for the head of an axiom | ||
| 54 | val subVisitor = | ||
| 55 | new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) | ||
| 56 | val superVisitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) | ||
| 57 | // Each visitor returns a `RDFoxRuleShards`, a tuple (res,ext): | ||
| 58 | // - the `res` List is a list of atoms resulting from the conversion | ||
| 59 | // of the axiom. | ||
| 60 | // - for some Class Expressions appearing in the head of an Axiom, | ||
| 61 | // the conversion might produce atoms that need to appear in the | ||
| 62 | // body (and not in the head) of the rule. This is what the `ext` | ||
| 63 | // List is for. | ||
| 64 | val sub = axiom.getSubClass.accept(subVisitor) | ||
| 65 | val sup = axiom.getSuperClass.accept(superVisitor) | ||
| 66 | val head = sup.res.asJava | ||
| 67 | val body = (sub.res ++ sup.ext).asJava | ||
| 68 | List(Rule.create(head, body)) | ||
| 69 | } | ||
| 70 | |||
| 71 | override def visit(axiom: OWLEquivalentClassesAxiom): List[Rule] = { | ||
| 72 | for { | ||
| 73 | axiom1 <- axiom.asPairwiseAxioms.asScala.toList | ||
| 74 | axiom2 <- axiom1.asOWLSubClassOfAxioms.asScala.toList | ||
| 75 | rule <- axiom2.accept(this) | ||
| 76 | } yield rule | ||
| 77 | } | ||
| 78 | |||
| 79 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | ||
| 80 | val term1 = RSAOntology.genFreshVariable() | ||
| 81 | val subVisitor = | ||
| 82 | new RDFoxPropertyExprConverter(term, term1, suffix) | ||
| 83 | val superVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) | ||
| 84 | val body: List[BodyFormula] = axiom.getSubProperty.accept(subVisitor) | ||
| 85 | val head: List[TupleTableAtom] = axiom.getSuperProperty.accept(superVisitor) | ||
| 86 | List(Rule.create(head.asJava, body.asJava)) | ||
| 87 | } | ||
| 88 | |||
| 89 | override def visit(axiom: OWLClassAssertionAxiom): List[Rule] = { | ||
| 90 | val ind = axiom.getIndividual | ||
| 91 | if (ind.isNamed) { | ||
| 92 | val term = IRI.create(ind.asOWLNamedIndividual().getIRI.getIRIString) | ||
| 93 | val cls = axiom.getClassExpression | ||
| 94 | val visitor = | ||
| 95 | new RDFoxClassExprConverter(term, unsafe, SkolemStrategy.None, suffix) | ||
| 96 | val shard = cls.accept(visitor) | ||
| 97 | List(Rule.create(shard.res.asJava, shard.ext.asJava)) | ||
| 98 | } else { | ||
| 99 | List() | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | def doDefault(axiom: OWLAxiom): List[Rule] = List() | ||
| 104 | |||
| 105 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import scala.collection.JavaConverters._ | ||
| 4 | import java.util.stream.{Stream, Collectors} | ||
| 5 | |||
| 6 | import org.semanticweb.owlapi.model.{ | ||
| 7 | OWLClassExpression, | ||
| 8 | OWLClass, | ||
| 9 | OWLObjectSomeValuesFrom, | ||
| 10 | OWLObjectIntersectionOf, | ||
| 11 | OWLObjectOneOf, | ||
| 12 | OWLObjectMaxCardinality | ||
| 13 | } | ||
| 14 | import org.semanticweb.owlapi.model.OWLClassExpressionVisitorEx | ||
| 15 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
| 16 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 17 | BindAtom, | ||
| 18 | TupleTableName, | ||
| 19 | TupleTableAtom | ||
| 20 | } | ||
| 21 | import tech.oxfordsemantic.jrdfox.logic.expression.{ | ||
| 22 | Term, | ||
| 23 | Literal, | ||
| 24 | Variable, | ||
| 25 | FunctionCall, | ||
| 26 | IRI | ||
| 27 | } | ||
| 28 | |||
| 29 | import org.semanticweb.owlapi.model.OWLObjectPropertyExpression | ||
| 30 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
| 31 | |||
| 32 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 33 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Empty} | ||
| 34 | import uk.ac.ox.cs.rsacomb.util.RSA | ||
| 35 | |||
| 36 | object RDFoxClassExprConverter { | ||
| 37 | |||
| 38 | def apply( | ||
| 39 | term: Term, | ||
| 40 | unsafe: List[OWLObjectPropertyExpression] = List(), | ||
| 41 | skolem: SkolemStrategy = SkolemStrategy.None, | ||
| 42 | suffix: RSASuffix = Empty | ||
| 43 | ): RDFoxClassExprConverter = | ||
| 44 | new RDFoxClassExprConverter(term, unsafe, skolem, suffix) | ||
| 45 | |||
| 46 | def merge(rules: List[RDFoxRuleShards]): RDFoxRuleShards = { | ||
| 47 | rules.foldLeft(RDFoxRuleShards(List(), List())) { (r1, r2) => | ||
| 48 | RDFoxRuleShards( | ||
| 49 | r1.res ++ r2.res, | ||
| 50 | r1.ext ++ r2.ext | ||
| 51 | ) | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | } // object RDFoxClassExprConverter | ||
| 56 | |||
| 57 | class RDFoxClassExprConverter( | ||
| 58 | term: Term, | ||
| 59 | unsafe: List[OWLObjectPropertyExpression], | ||
| 60 | skolem: SkolemStrategy, | ||
| 61 | suffix: RSASuffix | ||
| 62 | ) extends OWLClassExpressionVisitorEx[RDFoxRuleShards] { | ||
| 63 | |||
| 64 | import uk.ac.ox.cs.rsacomb.implicits.RDFox._ | ||
| 65 | |||
| 66 | // OWLClass | ||
| 67 | override def visit(expr: OWLClass): RDFoxRuleShards = { | ||
| 68 | val iri: IRI = if (expr.isTopEntity()) IRI.THING else expr.getIRI() | ||
| 69 | val atom = List(TupleTableAtom.rdf(term, IRI.RDF_TYPE, iri)) | ||
| 70 | RDFoxRuleShards(atom, List()) | ||
| 71 | } | ||
| 72 | |||
| 73 | // OWLObjectIntersectionOf | ||
| 74 | override def visit(expr: OWLObjectIntersectionOf): RDFoxRuleShards = { | ||
| 75 | val visitor = new RDFoxClassExprConverter(term, unsafe, skolem, suffix) | ||
| 76 | // TODO: maybe using `flatMap` instead of `merge` + `map` works as well | ||
| 77 | RDFoxClassExprConverter.merge( | ||
| 78 | expr.asConjunctSet.asScala.toList | ||
| 79 | .map((e: OWLClassExpression) => e.accept(visitor)) | ||
| 80 | ) | ||
| 81 | } | ||
| 82 | |||
| 83 | // OWLObjectOneOf | ||
| 84 | override def visit(expr: OWLObjectOneOf): RDFoxRuleShards = { | ||
| 85 | val visitor = RDFoxClassExprConverter(term, unsafe, skolem, suffix) | ||
| 86 | // TODO: review nominal handling. Here we are taking "just" one | ||
| 87 | val ind = expr.individuals | ||
| 88 | .collect(Collectors.toList()) | ||
| 89 | .asScala | ||
| 90 | .filter(_.isOWLNamedIndividual) | ||
| 91 | .head // restricts to proper "nominals" | ||
| 92 | .asOWLNamedIndividual | ||
| 93 | .getIRI | ||
| 94 | val atom = List( | ||
| 95 | TupleTableAtom.rdf(term, IRI.SAME_AS, ind) | ||
| 96 | ) | ||
| 97 | RDFoxRuleShards(atom, List()) | ||
| 98 | } | ||
| 99 | |||
| 100 | // OWLObjectSomeValuesFrom | ||
| 101 | override def visit(expr: OWLObjectSomeValuesFrom): RDFoxRuleShards = { | ||
| 102 | val y = RSAOntology.genFreshVariable() | ||
| 103 | // Here we are assuming a role name | ||
| 104 | val prop = expr.getProperty() | ||
| 105 | // Computes the result of rule skolemization. Depending on the used | ||
| 106 | // technique it might involve the introduction of additional atoms, | ||
| 107 | // and/or fresh constants and variables. | ||
| 108 | val (head, body, term1) = skolem match { | ||
| 109 | case SkolemStrategy.None => (List(), List(), y) | ||
| 110 | case SkolemStrategy.Constant(c) => (List(), List(), c) | ||
| 111 | case SkolemStrategy.ConstantRSA(c) => { | ||
| 112 | if (unsafe.contains(prop)) | ||
| 113 | (List(RSA.PE(term, c), RSA.U(c)), List(), c) | ||
| 114 | else | ||
| 115 | (List(), List(), c) | ||
| 116 | } | ||
| 117 | case SkolemStrategy.Standard(f) => { | ||
| 118 | ( | ||
| 119 | List(), | ||
| 120 | List(BindAtom.create(FunctionCall.create("SKOLEM", f, term), y)), | ||
| 121 | y | ||
| 122 | ) | ||
| 123 | } | ||
| 124 | } | ||
| 125 | val classVisitor = | ||
| 126 | new RDFoxClassExprConverter(term1, unsafe, skolem, suffix) | ||
| 127 | val classResult = expr.getFiller.accept(classVisitor) | ||
| 128 | val propertyVisitor = new RDFoxPropertyExprConverter(term, term1, suffix) | ||
| 129 | val propertyResult = expr.getProperty.accept(propertyVisitor) | ||
| 130 | RDFoxRuleShards( | ||
| 131 | classResult.res ++ propertyResult ++ head, | ||
| 132 | classResult.ext ++ body | ||
| 133 | ) | ||
| 134 | } | ||
| 135 | |||
| 136 | // OWLObjectMaxCardinality | ||
| 137 | override def visit(expr: OWLObjectMaxCardinality): RDFoxRuleShards = { | ||
| 138 | // TODO: again, no hardcoded variables | ||
| 139 | val vars = | ||
| 140 | List(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) | ||
| 141 | val classResult = RDFoxClassExprConverter.merge( | ||
| 142 | vars | ||
| 143 | .map(new RDFoxClassExprConverter(_, unsafe, skolem, suffix)) | ||
| 144 | .map(expr.getFiller.accept(_)) | ||
| 145 | ) | ||
| 146 | val propertyResult = | ||
| 147 | vars | ||
| 148 | .map(new RDFoxPropertyExprConverter(term, _, suffix)) | ||
| 149 | .map(expr.getProperty.accept(_)) | ||
| 150 | .flatten | ||
| 151 | RDFoxRuleShards( | ||
| 152 | List(TupleTableAtom.rdf(vars(0), IRI.SAME_AS, vars(1))), | ||
| 153 | classResult.res ++ propertyResult | ||
| 154 | ) | ||
| 155 | } | ||
| 156 | |||
| 157 | def doDefault(expr: OWLClassExpression): RDFoxRuleShards = | ||
| 158 | RDFoxRuleShards(List(), List()) | ||
| 159 | |||
| 160 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import org.semanticweb.owlapi.model.{OWLPropertyExpression, OWLObjectProperty} | ||
| 4 | import org.semanticweb.owlapi.model.OWLPropertyExpressionVisitorEx | ||
| 5 | |||
| 6 | import tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom | ||
| 7 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, Literal} | ||
| 8 | |||
| 9 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | ||
| 10 | |||
| 11 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Inverse} | ||
| 12 | |||
| 13 | class RDFoxPropertyExprConverter( | ||
| 14 | term1: Term, | ||
| 15 | term2: Term, | ||
| 16 | suffix: RSASuffix | ||
| 17 | ) extends OWLPropertyExpressionVisitorEx[List[TupleTableAtom]] { | ||
| 18 | |||
| 19 | // Automatically converts OWLAPI types into RDFox equivalent types. | ||
| 20 | import uk.ac.ox.cs.rsacomb.implicits.RDFox._ | ||
| 21 | |||
| 22 | override def visit(expr: OWLObjectProperty): List[TupleTableAtom] = { | ||
| 23 | val base = expr.getIRI.getIRIString | ||
| 24 | val pred = IRI.create(base :: suffix) | ||
| 25 | List(TupleTableAtom.rdf(term1, pred, term2)) | ||
| 26 | } | ||
| 27 | |||
| 28 | override def visit(expr: OWLObjectInverseOf): List[TupleTableAtom] = { | ||
| 29 | val visitor = new RDFoxPropertyExprConverter(term1, term2, suffix + Inverse) | ||
| 30 | expr.getInverse.accept(visitor) | ||
| 31 | } | ||
| 32 | |||
| 33 | def doDefault(expr: OWLPropertyExpression): List[TupleTableAtom] = List() | ||
| 34 | |||
| 35 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, BodyFormula} | ||
| 4 | |||
| 5 | 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.converter | ||
| 2 | |||
| 3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
| 4 | import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, IRI} | ||
| 5 | |||
| 6 | sealed trait SkolemStrategy | ||
| 7 | |||
| 8 | object SkolemStrategy { | ||
| 9 | // TODO: might want to use something else other than `hashCode` as a | ||
| 10 | // function to generate a fresh function/constant | ||
| 11 | |||
| 12 | /* No skolemization at all. | ||
| 13 | * | ||
| 14 | * From | ||
| 15 | * ∃R.A ⊑ B | ||
| 16 | * to | ||
| 17 | * R(x,y), B(y) -> B(x) | ||
| 18 | */ | ||
| 19 | case object None extends SkolemStrategy | ||
| 20 | |||
| 21 | /* Functional skolemization | ||
| 22 | * | ||
| 23 | * From | ||
| 24 | * A ⊑ ∃R.B | ||
| 25 | * to | ||
| 26 | * A(x) -> R(x,f(x)), B(f(x)) | ||
| 27 | * for f, fresh function associated with the input axiom | ||
| 28 | * | ||
| 29 | * In RDFox this can represented combining the BIND operator with the | ||
| 30 | * SKOLEM operator as such: | ||
| 31 | * A(x), BIND(y, SKOLEM("f", x)) -> R(x,y), B(y) | ||
| 32 | * The first argument of a SKOLEM call is a literal string (ideally | ||
| 33 | * identifing the simulated function name). | ||
| 34 | * | ||
| 35 | * NOTE: this requirement for the SKOLEM operator is not enforced by | ||
| 36 | * RDFox, that will fail silently if omitted. | ||
| 37 | */ | ||
| 38 | case class Standard(func: Literal) extends SkolemStrategy | ||
| 39 | object Standard { | ||
| 40 | def apply(axiom: String) = | ||
| 41 | new Standard( | ||
| 42 | Literal.create(genFunctionString(axiom), Datatype.XSD_STRING) | ||
| 43 | ) | ||
| 44 | def genFunctionString(str: String) = "f_" ++ str.hashCode.toString | ||
| 45 | } | ||
| 46 | |||
| 47 | /* Constant skolemization | ||
| 48 | * | ||
| 49 | * From | ||
| 50 | * A ⊑ ∃R.B | ||
| 51 | * to | ||
| 52 | * A(y) -> R(x,c), B(c) | ||
| 53 | * for c, fresh constant associated with the input axiom | ||
| 54 | */ | ||
| 55 | case class Constant(const: IRI) extends SkolemStrategy | ||
| 56 | object Constant { | ||
| 57 | def apply(axiom: String) = | ||
| 58 | new Constant(IRI.create(genConstantString(axiom))) | ||
| 59 | def genConstantString(str: String) = "c_" ++ str.hashCode.toString | ||
| 60 | } | ||
| 61 | |||
| 62 | /* (RSA) Constant skolemization | ||
| 63 | * This is a special skolemization option to introduce additional atoms for RSA | ||
| 64 | * checking algorithm. | ||
| 65 | * | ||
| 66 | * From | ||
| 67 | * A ⊑ ∃R.B | ||
| 68 | * to | ||
| 69 | * A(y) -> R(x,c), PE(x,c), B(c) | ||
| 70 | * for c, fresh constant associated with the input axiom and PE an internal predicate. | ||
| 71 | */ | ||
| 72 | case class ConstantRSA(const: IRI) extends SkolemStrategy | ||
| 73 | object ConstantRSA { | ||
| 74 | def apply(axiom: String) = | ||
| 75 | new ConstantRSA(IRI.create(genConstantString(axiom))) | ||
| 76 | def genConstantString(str: String) = "c_" ++ str.hashCode.toString | ||
| 77 | } | ||
| 78 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.implicits | ||
| 2 | |||
| 3 | import scala.collection.JavaConverters._ | ||
| 4 | |||
| 5 | object JavaCollections { | ||
| 6 | |||
| 7 | implicit def javaToScalaList[A](list: java.util.List[A]): List[A] = | ||
| 8 | list.asScala.toList | ||
| 9 | |||
| 10 | implicit def scalaToJavaList[A](list: List[A]): java.util.List[A] = | ||
| 11 | list.asJava | ||
| 12 | |||
| 13 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.implicits | ||
| 2 | |||
| 3 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFoxIRI} | ||
| 4 | import org.semanticweb.owlapi.model.{IRI => OWLIRI} | ||
| 5 | |||
| 6 | object RDFox { | ||
| 7 | |||
| 8 | implicit def rdfoxToOwlapiIri(iri: RDFoxIRI): OWLIRI = { | ||
| 9 | OWLIRI.create(iri.getIRI) | ||
| 10 | } | ||
| 11 | |||
| 12 | implicit def owlapiToRdfoxIri(iri: OWLIRI): RDFoxIRI = { | ||
| 13 | RDFoxIRI.create(iri.getIRIString()) | ||
| 14 | } | ||
| 15 | |||
| 16 | implicit def stringToRdfoxIri(iri: String): RDFoxIRI = { | ||
| 17 | RDFoxIRI.create(iri) | ||
| 18 | } | ||
| 19 | |||
| 20 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.implicits | ||
| 2 | |||
| 3 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
| 4 | import tech.oxfordsemantic.jrdfox.logic.expression.{Literal, FunctionCall} | ||
| 5 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 6 | BindAtom, | ||
| 7 | TupleTableAtom, | ||
| 8 | TupleTableName | ||
| 9 | } | ||
| 10 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} | ||
| 11 | import scala.collection.JavaConverters._ | ||
| 12 | |||
| 13 | import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth} | ||
| 14 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 15 | |||
| 16 | /* Is this the best way to determine if an atom is an RDF triple? | ||
| 17 | * Note that we can't use `getNumberOfArguments()` because is not | ||
| 18 | * "consistent": | ||
| 19 | * - for an atom created with `rdf(<term1>, <term2>, <term3>)`, | ||
| 20 | * `getNumberOfArguments` returns 3 | ||
| 21 | * - for an atom created with `Atom.create(<tupletablename>, <term1>, | ||
| 22 | * <term2>, <term3>)`, `getNumberOfArguments()` returns 3 | ||
| 23 | * | ||
| 24 | * This is probably because `Atom.rdf(...) is implemented as: | ||
| 25 | * ```scala | ||
| 26 | * def rdf(term1: Term, term2: Term, term3: Term): Atom = | ||
| 27 | * Atom.create(TupleTableName.create("internal:triple"), term1, term2, term3) | ||
| 28 | * ``` | ||
| 29 | */ | ||
| 30 | |||
| 31 | trait RSAAtom { | ||
| 32 | |||
| 33 | implicit class RSAAtom(val atom: TupleTableAtom) { | ||
| 34 | |||
| 35 | import RDFox._ | ||
| 36 | |||
| 37 | val name: String = atom.getTupleTableName.getName | ||
| 38 | |||
| 39 | val isRDF: Boolean = name == "internal:triple" | ||
| 40 | |||
| 41 | val isClassAssertion: Boolean = { | ||
| 42 | isRDF && { | ||
| 43 | val pred = atom.getArguments.get(1) | ||
| 44 | pred == IRI.RDF_TYPE | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | val isRoleAssertion: Boolean = isRDF && !isClassAssertion | ||
| 49 | |||
| 50 | def <<(suffix: RSASuffix): TupleTableAtom = | ||
| 51 | if (isRDF) { | ||
| 52 | val subj = atom.getArguments.get(0) | ||
| 53 | val pred = atom.getArguments.get(1) | ||
| 54 | val obj = atom.getArguments.get(2) | ||
| 55 | if (isClassAssertion) { | ||
| 56 | val obj1 = obj match { | ||
| 57 | case iri: IRI => IRI.create(iri.getIRI :: suffix) | ||
| 58 | case other => other | ||
| 59 | } | ||
| 60 | TupleTableAtom.rdf(subj, pred, obj1) | ||
| 61 | } else { | ||
| 62 | val pred1 = pred match { | ||
| 63 | case iri: IRI => IRI.create(iri.getIRI :: suffix) | ||
| 64 | case other => other | ||
| 65 | } | ||
| 66 | TupleTableAtom.rdf(subj, pred1, obj) | ||
| 67 | } | ||
| 68 | } else { | ||
| 69 | val ttname = TupleTableName.create(name :: suffix) | ||
| 70 | TupleTableAtom.create(ttname, atom.getArguments()) | ||
| 71 | } | ||
| 72 | |||
| 73 | lazy val reified: (Option[BindAtom], List[TupleTableAtom]) = | ||
| 74 | if (isRDF) { | ||
| 75 | (None, List(atom)) | ||
| 76 | } else { | ||
| 77 | val bvar = RSAOntology.genFreshVariable() | ||
| 78 | val str = Literal.create(name, Datatype.XSD_STRING) | ||
| 79 | val args = atom.getArguments.asScala.toList | ||
| 80 | val skolem = FunctionCall.create("SKOLEM", str :: args: _*) | ||
| 81 | val bind = BindAtom.create(skolem, bvar) | ||
| 82 | val atoms = args.zipWithIndex | ||
| 83 | .map { case (t, i) => TupleTableAtom.rdf(bvar, name :: Nth(i), t) } | ||
| 84 | (Some(bind), atoms) | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.implicits | ||
| 2 | |||
| 3 | /* Java imports */ | ||
| 4 | import org.semanticweb.owlapi.model.{ | ||
| 5 | OWLAxiom, | ||
| 6 | OWLSubClassOfAxiom, | ||
| 7 | OWLEquivalentClassesAxiom | ||
| 8 | } | ||
| 9 | import org.semanticweb.owlapi.model.{ | ||
| 10 | OWLObjectPropertyExpression, | ||
| 11 | OWLSubObjectPropertyOfAxiom, | ||
| 12 | OWLClass, | ||
| 13 | OWLClassExpression, | ||
| 14 | OWLObjectSomeValuesFrom, | ||
| 15 | OWLObjectMaxCardinality | ||
| 16 | } | ||
| 17 | import org.semanticweb.owlapi.model.ClassExpressionType | ||
| 18 | import org.semanticweb.owlapi.model.{ | ||
| 19 | OWLAxiomVisitorEx, | ||
| 20 | OWLClassExpressionVisitorEx | ||
| 21 | } | ||
| 22 | import org.semanticweb.owlapi.model.OWLObjectProperty | ||
| 23 | import scala.collection.JavaConverters._ | ||
| 24 | |||
| 25 | /* Wrapper trait for the implicit class `RSAAxiom`. | ||
| 26 | */ | ||
| 27 | trait RSAAxiom { | ||
| 28 | |||
| 29 | /* Identifies some of the axiom types in a Horn-ALCHOIQ ontology | ||
| 30 | * in normal form. Refer to the paper for more details on the | ||
| 31 | * chosen names. | ||
| 32 | */ | ||
| 33 | private sealed trait RSAAxiomType | ||
| 34 | private object RSAAxiomType { | ||
| 35 | case object T3 extends RSAAxiomType // ∃R.A ⊑ B | ||
| 36 | case object T3top extends RSAAxiomType // ∃R.⊤ ⊑ B | ||
| 37 | case object T4 extends RSAAxiomType // A ⊑ ≤1R.B | ||
| 38 | case object T5 extends RSAAxiomType // A ⊑ ∃R.B | ||
| 39 | } | ||
| 40 | |||
| 41 | object RSAAxiom { | ||
| 42 | |||
| 43 | def hashed( | ||
| 44 | cls1: OWLClass, | ||
| 45 | prop: OWLObjectPropertyExpression, | ||
| 46 | cls2: OWLClass | ||
| 47 | ): String = | ||
| 48 | (cls1, prop, cls2).hashCode.toString | ||
| 49 | |||
| 50 | } | ||
| 51 | |||
| 52 | /* Implements additional features on top of `OWLAxiom` from | ||
| 53 | * the OWLAPI. | ||
| 54 | */ | ||
| 55 | implicit class RSAAxiom(axiom: OWLAxiom) { | ||
| 56 | |||
| 57 | /* Detecting axiom types: | ||
| 58 | * | ||
| 59 | * In order to reason about role unsafety in Horn-ALCHOIQ | ||
| 60 | * ontologies we need to detect and filter axioms by their | ||
| 61 | * "type". | ||
| 62 | * | ||
| 63 | * This is a simple implementation following the Visitor | ||
| 64 | * pattern imposed by the OWLAPI. | ||
| 65 | */ | ||
| 66 | private class RSAAxiomTypeDetector(t: RSAAxiomType) | ||
| 67 | extends OWLAxiomVisitorEx[Boolean] { | ||
| 68 | override def visit(axiom: OWLSubClassOfAxiom): Boolean = { | ||
| 69 | val sub = axiom.getSubClass().getClassExpressionType() | ||
| 70 | val sup = axiom.getSuperClass().getClassExpressionType() | ||
| 71 | t match { | ||
| 72 | case RSAAxiomType.T3top => // ∃R.⊤ ⊑ B | ||
| 73 | axiom.isT3 && axiom | ||
| 74 | .getSubClass() | ||
| 75 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
| 76 | .getFiller | ||
| 77 | .isOWLThing | ||
| 78 | case RSAAxiomType.T3 => // ∃R.A ⊑ B | ||
| 79 | sub == ClassExpressionType.OBJECT_SOME_VALUES_FROM && sup == ClassExpressionType.OWL_CLASS | ||
| 80 | case RSAAxiomType.T4 => // A ⊑ ≤1R.B | ||
| 81 | sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_MAX_CARDINALITY | ||
| 82 | case RSAAxiomType.T5 => // A ⊑ ∃R.B | ||
| 83 | sub == ClassExpressionType.OWL_CLASS && sup == ClassExpressionType.OBJECT_SOME_VALUES_FROM | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | override def visit(axiom: OWLEquivalentClassesAxiom): Boolean = { | ||
| 88 | // TODO | ||
| 89 | false | ||
| 90 | } | ||
| 91 | |||
| 92 | def doDefault(axiom: OWLAxiom): Boolean = false | ||
| 93 | } | ||
| 94 | |||
| 95 | private def isOfType(t: RSAAxiomType): Boolean = { | ||
| 96 | val visitor = new RSAAxiomTypeDetector(t) | ||
| 97 | axiom.accept(visitor) | ||
| 98 | } | ||
| 99 | |||
| 100 | /* Exposed methods */ | ||
| 101 | def isT3top: Boolean = isOfType(RSAAxiomType.T3top) | ||
| 102 | def isT3: Boolean = isOfType(RSAAxiomType.T3) | ||
| 103 | def isT4: Boolean = isOfType(RSAAxiomType.T4) | ||
| 104 | def isT5: Boolean = isOfType(RSAAxiomType.T5) | ||
| 105 | |||
| 106 | /* Extracting ObjectPropertyExpressions from axioms | ||
| 107 | * | ||
| 108 | * This extracts all ObjectPropertyExpressions from a given | ||
| 109 | * axiom. While the implementation is generic we use it on axioms | ||
| 110 | * of specific types (see above). | ||
| 111 | * | ||
| 112 | * NOTE: it is not possible to use the `objectPropertyInSignature` | ||
| 113 | * method of `OWLAxiom` because it returns all "role names" involved | ||
| 114 | * in the signature of an axiom. In particular we won't get the inverse | ||
| 115 | * of a role if this appears in the axiom (but we will get the role | ||
| 116 | * itself instead). | ||
| 117 | */ | ||
| 118 | lazy val objectPropertyExpressionsInSignature | ||
| 119 | : List[OWLObjectPropertyExpression] = | ||
| 120 | axiom match { | ||
| 121 | case a: OWLSubClassOfAxiom => | ||
| 122 | rolesInExpr(a.getSubClass) ++ rolesInExpr(a.getSuperClass) | ||
| 123 | case a: OWLEquivalentClassesAxiom => | ||
| 124 | a.getClassExpressions.asScala.toList.flatMap(rolesInExpr(_)) | ||
| 125 | case a: OWLSubObjectPropertyOfAxiom => | ||
| 126 | List(a.getSubProperty, a.getSuperProperty) | ||
| 127 | case _ => List() | ||
| 128 | } | ||
| 129 | |||
| 130 | private def rolesInExpr( | ||
| 131 | expr: OWLClassExpression | ||
| 132 | ): List[OWLObjectPropertyExpression] = | ||
| 133 | expr match { | ||
| 134 | case e: OWLObjectSomeValuesFrom => List(e.getProperty) | ||
| 135 | case e: OWLObjectMaxCardinality => List(e.getProperty) | ||
| 136 | case _ => List() | ||
| 137 | } | ||
| 138 | |||
| 139 | lazy val toTriple: Option[(OWLClass, OWLObjectProperty, OWLClass)] = | ||
| 140 | for { | ||
| 141 | subClass <- Some(axiom) collect { case a: OWLSubClassOfAxiom => a } | ||
| 142 | cls1 <- Some(subClass.getSubClass) collect { case a: OWLClass => a } | ||
| 143 | someValues <- Some(subClass.getSuperClass) collect { | ||
| 144 | case a: OWLObjectSomeValuesFrom => a | ||
| 145 | } | ||
| 146 | prop <- Some(someValues.getProperty) collect { | ||
| 147 | case a: OWLObjectProperty => a | ||
| 148 | } | ||
| 149 | cls2 <- Some(someValues.getFiller) collect { case a: OWLClass => a } | ||
| 150 | } yield (cls1, prop, cls2) | ||
| 151 | |||
| 152 | lazy val hashed: String = (RSAAxiom.hashed _) tupled toTriple.get | ||
| 153 | } | ||
| 154 | |||
| 155 | } // 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.suffix | ||
| 2 | |||
| 3 | import org.semanticweb.owlapi.model.{ | ||
| 4 | OWLPropertyExpression, | ||
| 5 | OWLObjectInverseOf, | ||
| 6 | OWLObjectProperty | ||
| 7 | } | ||
| 8 | |||
| 9 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} | ||
| 10 | import tech.oxfordsemantic.jrdfox.logic.datalog.{TupleTableAtom, TupleTableName} | ||
| 11 | |||
| 12 | object RSASuffix { | ||
| 13 | |||
| 14 | def apply(suffix: String => String): RSASuffix = new RSASuffix(suffix) | ||
| 15 | |||
| 16 | } | ||
| 17 | |||
| 18 | class RSASuffix(val suffix: String => String) { | ||
| 19 | |||
| 20 | def +(that: RSASuffix): RSASuffix = | ||
| 21 | new RSASuffix(this.suffix andThen that.suffix) | ||
| 22 | |||
| 23 | def ::(str: String): String = this suffix str | ||
| 24 | |||
| 25 | } | ||
| 26 | |||
| 27 | case object Empty extends RSASuffix(identity) | ||
| 28 | case object Forward extends RSASuffix((s) => s"${s}_f") | ||
| 29 | case object Backward extends RSASuffix((s) => s"${s}_b") | ||
| 30 | case object Inverse extends RSASuffix((s) => s"${s}_inv") | ||
| 31 | 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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.util | ||
| 2 | |||
| 3 | import java.util.{Map => JMap, HashMap => JHashMap} | ||
| 4 | import java.io.StringReader | ||
| 5 | import tech.oxfordsemantic.jrdfox.Prefixes | ||
| 6 | import tech.oxfordsemantic.jrdfox.client.{ | ||
| 7 | ConnectionFactory, | ||
| 8 | ServerConnection, | ||
| 9 | DataStoreConnection | ||
| 10 | } | ||
| 11 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser | ||
| 12 | import tech.oxfordsemantic.jrdfox.logic.expression.Resource | ||
| 13 | import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery | ||
| 14 | |||
| 15 | import uk.ac.ox.cs.rsacomb.suffix.Nth | ||
| 16 | |||
| 17 | object RDFoxHelpers { | ||
| 18 | |||
| 19 | def openConnection( | ||
| 20 | dataStore: String, | ||
| 21 | opts: JMap[String, String] = new JHashMap[String, String]() | ||
| 22 | ): (ServerConnection, DataStoreConnection) = { | ||
| 23 | /* Create local server connection | ||
| 24 | */ | ||
| 25 | val serverUrl = "rdfox:local" | ||
| 26 | val role = "" | ||
| 27 | val password = "" | ||
| 28 | val server = | ||
| 29 | ConnectionFactory.newServerConnection(serverUrl, role, password) | ||
| 30 | |||
| 31 | /* Create datastore connection | ||
| 32 | */ | ||
| 33 | // parameters.put("owl-in-rdf-support", "relaxed") | ||
| 34 | // parameters.put("equality", "noUNA") | ||
| 35 | server.createDataStore(dataStore, "par-complex-nn", opts) | ||
| 36 | val data = server.newDataStoreConnection(dataStore) | ||
| 37 | |||
| 38 | (server, data) | ||
| 39 | } | ||
| 40 | |||
| 41 | def parseSelectQuery( | ||
| 42 | query: String, | ||
| 43 | prefixes: Prefixes = new Prefixes() | ||
| 44 | ): Option[SelectQuery] = { | ||
| 45 | val parser = new SPARQLParser( | ||
| 46 | prefixes, | ||
| 47 | new StringReader(query) | ||
| 48 | ) | ||
| 49 | parser.parseSingleQuery() match { | ||
| 50 | case q: SelectQuery => Some(q) | ||
| 51 | case _ => None | ||
| 52 | } | ||
| 53 | } | ||
| 54 | |||
| 55 | def submitSelectQuery( | ||
| 56 | data: DataStoreConnection, | ||
| 57 | query: String, | ||
| 58 | prefixes: Prefixes = new Prefixes(), | ||
| 59 | opts: JMap[String, String] = new JHashMap[String, String]() | ||
| 60 | ): List[List[Resource]] = { | ||
| 61 | val cursor = data.createCursor(prefixes, query, opts) | ||
| 62 | var answers: List[List[Resource]] = List() | ||
| 63 | var mul = cursor.open() | ||
| 64 | while (mul > 0) { | ||
| 65 | val answer = | ||
| 66 | (0 until cursor.getArity).map(cursor.getResource(_)).toList | ||
| 67 | answers = answer :: answers | ||
| 68 | mul = cursor.advance() | ||
| 69 | } | ||
| 70 | cursor.close(); | ||
| 71 | answers | ||
| 72 | } | ||
| 73 | |||
| 74 | def queryInternalPredicate( | ||
| 75 | data: DataStoreConnection, | ||
| 76 | pred: String, | ||
| 77 | arity: Int, | ||
| 78 | opts: JMap[String, String] = new JHashMap[String, String]() | ||
| 79 | ): List[List[Resource]] = { | ||
| 80 | var query = "SELECT" | ||
| 81 | for (i <- 0 until arity) { | ||
| 82 | query ++= s" ?X$i" | ||
| 83 | } | ||
| 84 | query ++= " WHERE {" | ||
| 85 | for (i <- 0 until arity) { | ||
| 86 | query ++= s" ?S rsa:${pred :: Nth(i)} ?X$i ." | ||
| 87 | } | ||
| 88 | query ++= " }" | ||
| 89 | submitSelectQuery(data, query, RSA.Prefixes, opts) | ||
| 90 | } | ||
| 91 | |||
| 92 | def closeConnection( | ||
| 93 | server: ServerConnection, | ||
| 94 | data: DataStoreConnection | ||
| 95 | ): Unit = { | ||
| 96 | server.close(); | ||
| 97 | data.close(); | ||
| 98 | } | ||
| 99 | |||
| 100 | } | ||
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 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.util | ||
| 2 | |||
| 3 | /* Java imports */ | ||
| 4 | import java.util.Map | ||
| 5 | |||
| 6 | import tech.oxfordsemantic.jrdfox.formats.SPARQLParser | ||
| 7 | import tech.oxfordsemantic.jrdfox.Prefixes | ||
| 8 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
| 9 | TupleTableAtom, | ||
| 10 | TupleTableName, | ||
| 11 | Negation | ||
| 12 | } | ||
| 13 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable, IRI} | ||
| 14 | import org.semanticweb.owlapi.model.OWLOntology | ||
| 15 | import org.semanticweb.owlapi.model.{ | ||
| 16 | OWLAxiom, | ||
| 17 | OWLClass, | ||
| 18 | OWLObjectPropertyExpression | ||
| 19 | } | ||
| 20 | |||
| 21 | import uk.ac.ox.cs.rsacomb.suffix.RSASuffix | ||
| 22 | |||
| 23 | // Debug only | ||
| 24 | import scala.collection.JavaConverters._ | ||
| 25 | |||
| 26 | object RSA { | ||
| 27 | |||
| 28 | val Prefixes: Prefixes = new Prefixes() | ||
| 29 | Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") | ||
| 30 | |||
| 31 | private def atom(name: IRI, vars: List[Term]) = | ||
| 32 | TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) | ||
| 33 | |||
| 34 | def PE(t1: Term, t2: Term) = | ||
| 35 | TupleTableAtom.rdf(t1, RSA("PE"), t2) | ||
| 36 | |||
| 37 | def U(t: Term) = | ||
| 38 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("U")) | ||
| 39 | |||
| 40 | def In(t: Term)(implicit set: Term) = | ||
| 41 | TupleTableAtom.rdf(t, RSA("In"), set) | ||
| 42 | |||
| 43 | def notIn(t: Term)(implicit set: Term) = Negation.create(In(t)(set)) | ||
| 44 | |||
| 45 | def congruent(t1: Term, t2: Term) = | ||
| 46 | TupleTableAtom.rdf(t1, RSA("congruent"), t2) | ||
| 47 | |||
| 48 | def QM(implicit variables: (List[Term], List[Term])) = { | ||
| 49 | val (answer, bounded) = variables | ||
| 50 | atom(RSA("QM"), answer ::: bounded) | ||
| 51 | } | ||
| 52 | |||
| 53 | def ID(t1: Term, t2: Term)(implicit variables: (List[Term], List[Term])) = { | ||
| 54 | val (answer, bounded) = variables | ||
| 55 | atom(RSA("ID"), (answer ::: bounded) :+ t1 :+ t2) | ||
| 56 | } | ||
| 57 | |||
| 58 | def Named(t: Term) = | ||
| 59 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("Named")) | ||
| 60 | |||
| 61 | def Thing(t: Term) = | ||
| 62 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, IRI.THING) | ||
| 63 | |||
| 64 | def NI(t: Term) = | ||
| 65 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, RSA("NI")) | ||
| 66 | |||
| 67 | def TQ(t1: Term, t2: Term, sx: RSASuffix)(implicit | ||
| 68 | variables: (List[Term], List[Term]) | ||
| 69 | ) = { | ||
| 70 | val (answer, bounded) = variables | ||
| 71 | atom(RSA("TQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) | ||
| 72 | } | ||
| 73 | |||
| 74 | def AQ(t1: Term, t2: Term, sx: RSASuffix)(implicit | ||
| 75 | variables: (List[Term], List[Term]) | ||
| 76 | ) = { | ||
| 77 | val (answer, bounded) = variables | ||
| 78 | atom(RSA("AQ" :: sx), (answer ::: bounded) :+ t1 :+ t2) | ||
| 79 | } | ||
| 80 | |||
| 81 | def FK(implicit variables: (List[Term], List[Term])) = { | ||
| 82 | val (answer, bounded) = variables | ||
| 83 | atom(RSA("FK"), answer ::: bounded) | ||
| 84 | } | ||
| 85 | |||
| 86 | def SP(implicit variables: (List[Term], List[Term])) = { | ||
| 87 | val (answer, bounded) = variables | ||
| 88 | atom(RSA("SP"), answer ::: bounded) | ||
| 89 | } | ||
| 90 | |||
| 91 | def Ans(implicit variables: (List[Term], List[Term])) = { | ||
| 92 | val (answer, _) = variables | ||
| 93 | atom(RSA("Ans"), answer) | ||
| 94 | } | ||
| 95 | |||
| 96 | def apply(name: Any): IRI = | ||
| 97 | IRI.create( | ||
| 98 | Prefixes.getPrefixIRIsByPrefixName.get("rsa:").getIRI + name.toString | ||
| 99 | ) | ||
| 100 | } | ||
