aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2021-01-30 10:59:38 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2021-01-30 10:59:38 +0000
commit7d6021c6c706c108b5b11d52071acd104c7d4ff8 (patch)
tree573146018eba8bc1cc16bc4b39edcab7c2c53ace
parentd04e2839689c4291afb4beb9a1913bb38fac1cd1 (diff)
downloadRSAComb-7d6021c6c706c108b5b11d52071acd104c7d4ff8.tar.gz
RSAComb-7d6021c6c706c108b5b11d52071acd104c7d4ff8.zip
Delay import of data files (#7)
This should partially solve the issue with data import through OWLAPI being too slow.
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala82
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala15
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala297
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala36
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala16
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala4
-rw-r--r--src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala12
7 files changed, 260 insertions, 202 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
index 96a953f..3777c6b 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala
@@ -12,17 +12,12 @@ import org.semanticweb.owlapi.model.{
12} 12}
13 13
14import tech.oxfordsemantic.jrdfox.logic.datalog.{ 14import tech.oxfordsemantic.jrdfox.logic.datalog.{
15 Rule,
16 BodyFormula, 15 BodyFormula,
17 TupleTableAtom, 16 Negation,
18 Negation 17 Rule,
19} 18 TupleTableAtom
20import tech.oxfordsemantic.jrdfox.logic.expression.{
21 Term,
22 Variable,
23 // Resource,
24 IRI
25} 19}
20import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term, Variable}
26 21
27import implicits.JavaCollections._ 22import implicits.JavaCollections._
28 23
@@ -78,73 +73,6 @@ class CanonicalModel(val ontology: RSAOntology) {
78 }) 73 })
79 } 74 }
80 75
81 /** Top axiomatization
82 *
83 * Corresponding to the following rules:
84 *
85 * ```
86 * [?a, rdf:type, owl:Thing] :- [?a, rdf:type, ?b] .
87 * [?a, rdf:type, owl:Thing], [?b, rdf:type, owl:Thing] :- [?a, ?r, ?b], FILTER(?r != rdf:type).
88 * ```
89 *
90 * @note this is a naïve implementation of top axiomatization and
91 * might change in the future. The ideal solution would be for RDFox
92 * to take care of this, but at the time of writing this is not
93 * compatible with the way we are using the tool.
94 */
95 private val topAxioms: List[Rule] = {
96 val varA = Variable.create("A")
97 val varR = Variable.create("R")
98 val varB = Variable.create("B")
99 List(
100 Rule.create(
101 RSA.Thing(varA),
102 TupleTableAtom.rdf(varA, IRI.RDF_TYPE, varB)
103 ),
104 Rule.create(
105 List(RSA.Thing(varA), RSA.Thing(varB)),
106 List(
107 TupleTableAtom.rdf(varA, varR, varB),
108 FilterAtom.create(FunctionCall.notEqual(varR, IRI.RDF_TYPE))
109 )
110 )
111 )
112 }
113
114 /** Equality axiomatization
115 *
116 * Introduce reflexivity, simmetry and transitivity rules for a naïve
117 * equality axiomatization.
118 *
119 * @note that we are using a custom `congruent` predicate to indicate
120 * equality. This is to avoid interfering with the standard
121 * `owl:sameAs`.
122 *
123 * @note RDFox is able to handle equality in a "smart" way, but this
124 * behaviour is incompatible with other needed features like
125 * negation-as-failure and aggregates.
126 *
127 * @todo to complete the equality axiomatization we need to introduce
128 * substitution rules to explicate a complete "equality" semantics.
129 */
130 private val equalityAxioms: List[Rule] = {
131 val varX = Variable.create("X")
132 val varY = Variable.create("Y")
133 val varZ = Variable.create("Z")
134 List(
135 // Reflexivity
136 Rule.create(RSA.Congruent(varX, varX), RSA.Thing(varX)),
137 // Simmetry
138 Rule.create(RSA.Congruent(varY, varX), RSA.Congruent(varX, varY)),
139 // Transitivity
140 Rule.create(
141 RSA.Congruent(varX, varZ),
142 RSA.Congruent(varX, varY),
143 RSA.Congruent(varY, varZ)
144 )
145 )
146 }
147
148 val (facts, rules): (List[TupleTableAtom], List[Rule]) = { 76 val (facts, rules): (List[TupleTableAtom], List[Rule]) = {
149 // Compute rules from ontology axioms 77 // Compute rules from ontology axioms
150 val (facts, rules) = { 78 val (facts, rules) = {
@@ -156,7 +84,7 @@ class CanonicalModel(val ontology: RSAOntology) {
156 } 84 }
157 ( 85 (
158 facts.flatten, 86 facts.flatten,
159 rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: rules.flatten 87 rolesAdditionalRules ::: rules.flatten
160 ) 88 )
161 } 89 }
162 90
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala
index 9427735..06224e7 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/FilteringProgram.scala
@@ -22,8 +22,8 @@ object FilteringProgram {
22 * @param constants constants in the original ontology. They will be 22 * @param constants constants in the original ontology. They will be
23 * used to initialize predicate `rsa:Named`. 23 * used to initialize predicate `rsa:Named`.
24 */ 24 */
25 def apply(query: ConjunctiveQuery, constants: List[Term]): FilteringProgram = 25 def apply(query: ConjunctiveQuery): FilteringProgram =
26 new FilteringProgram(query, constants) 26 new FilteringProgram(query)
27 27
28} 28}
29 29
@@ -34,7 +34,7 @@ object FilteringProgram {
34 * 34 *
35 * Instances can be created using the companion object. 35 * Instances can be created using the companion object.
36 */ 36 */
37class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) { 37class FilteringProgram(query: ConjunctiveQuery) {
38 38
39 /** Extends capabilities of 39 /** Extends capabilities of
40 * [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtom]] 40 * [[tech.oxfordsemantic.jrdfox.logic.datalog.TupleTableAtom TupleTableAtom]]
@@ -76,15 +76,6 @@ class FilteringProgram(query: ConjunctiveQuery, constants: List[Term]) {
76 val nis: Rule = 76 val nis: Rule =
77 Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY)) 77 Rule.create(RSA.NI(varX), RSA.Congruent(varX, varY), RSA.Named(varY))
78 78
79 /** Initializes instances of `rsa:Named`.
80 *
81 * They represent the set of constants appearing in the original
82 * ontology.
83 *
84 * @note corresponds to rules 2 in Table 3.
85 */
86 val facts = constants map RSA.Named
87
88 /** Collection of filtering program rules. */ 79 /** Collection of filtering program rules. */
89 val rules: List[Rule] = 80 val rules: List[Rule] =
90 nis :: { 81 nis :: {
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
index 8d5bf4c..0f1552a 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -46,6 +46,7 @@ import tech.oxfordsemantic.jrdfox.logic.expression.{
46import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery 46import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
47 47
48/* Scala imports */ 48/* Scala imports */
49import scala.util.{Try, Success, Failure}
49import scala.collection.JavaConverters._ 50import scala.collection.JavaConverters._
50import scala.collection.mutable.Set 51import scala.collection.mutable.Set
51import scalax.collection.immutable.Graph 52import scalax.collection.immutable.Graph
@@ -70,56 +71,75 @@ object RSAOntology {
70 /** Name of the RDFox data store used for CQ answering */ 71 /** Name of the RDFox data store used for CQ answering */
71 private val DataStore = "answer_computation" 72 private val DataStore = "answer_computation"
72 73
73 def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology) 74 def apply(ontology: File, data: File*): RSAOntology =
74 75 new RSAOntology(ontology, data: _*)
75 def apply(ontologies: File*): RSAOntology =
76 new RSAOntology(loadOntology(ontologies: _*))
77 76
78 def genFreshVariable(): Variable = { 77 def genFreshVariable(): Variable = {
79 counter += 1 78 counter += 1
80 Variable.create(f"I$counter%03d") 79 Variable.create(f"I$counter%03d")
81 } 80 }
82 81
83 private def loadOntology(ontologies: File*): OWLOntology = {
84 val manager = OWLManager.createOWLOntologyManager()
85 ontologies.foreach { manager.loadOntologyFromOntologyDocument(_) }
86 val merger = new OWLOntologyMerger(manager)
87 merger.createMergedOntology(manager, OWLIRI.create("_:merged"))
88 }
89} 82}
90 83
91class RSAOntology(val ontology: OWLOntology) { 84class RSAOntology(_ontology: File, val datafiles: File*) {
92 85
86 /** Simplify conversion between OWLAPI and RDFox concepts */
87 import implicits.RDFox._
93 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ 88 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
94 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 89 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
95 90
96 // Gather TBox/RBox/ABox from original ontology 91 /** Manager instance to interface with OWLAPI */
92 private val manager = OWLManager.createOWLOntologyManager()
93
94 /** TBox + RBox of the input knowledge base. */
95 val ontology: OWLOntology =
96 manager.loadOntologyFromOntologyDocument(_ontology)
97
98 /** OWLAPI internal reasoner some preliminary reasoning task. */
99 private val reasoner =
100 (new StructuralReasonerFactory()).createReasoner(ontology)
101
102 /** Imported knowledge base. */
103 //lazy val kbase: OWLOntology = {
104 // val merger = new OWLOntologyMerger(manager)
105 // _data.foreach { manager.loadOntologyFromOntologyDocument(_) }
106 // merger.createMergedOntology(manager, OWLIRI.create("_:merged"))
107 //}
108
109 /** TBox axioms */
97 val tbox: List[OWLLogicalAxiom] = 110 val tbox: List[OWLLogicalAxiom] =
98 ontology 111 ontology
99 .tboxAxioms(Imports.INCLUDED) 112 .tboxAxioms(Imports.INCLUDED)
100 .collect(Collectors.toList()) 113 .collect(Collectors.toList())
101 .collect { case a: OWLLogicalAxiom => a } 114 .collect { case a: OWLLogicalAxiom => a }
115 Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG)
102 116
117 /** RBox axioms */
103 val rbox: List[OWLLogicalAxiom] = 118 val rbox: List[OWLLogicalAxiom] =
104 ontology 119 ontology
105 .rboxAxioms(Imports.INCLUDED) 120 .rboxAxioms(Imports.INCLUDED)
106 .collect(Collectors.toList()) 121 .collect(Collectors.toList())
107 .collect { case a: OWLLogicalAxiom => a } 122 .collect { case a: OWLLogicalAxiom => a }
123 Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG)
108 124
125 /** ABox axioms
126 *
127 * @note this represents only the set of assertions contained in the
128 * ontology file. Data files specified in `datafiles` are directly
129 * imported in RDFox due to performance issues when trying to import
130 * large data files via OWLAPI.
131 */
109 val abox: List[OWLLogicalAxiom] = 132 val abox: List[OWLLogicalAxiom] =
110 ontology 133 ontology
111 .aboxAxioms(Imports.INCLUDED) 134 .aboxAxioms(Imports.INCLUDED)
112 .collect(Collectors.toList()) 135 .collect(Collectors.toList())
113 .collect { case a: OWLLogicalAxiom => a } 136 .collect { case a: OWLLogicalAxiom => a }
137 Logger.print(s"Original RBox: ${abox.length}", Logger.DEBUG)
114 138
115 val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox 139 /** Collection of logical axioms in the input ontology */
116 140 lazy val axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
117 Logger.print(s"Original TBox: ${tbox.length}", Logger.DEBUG)
118 Logger.print(s"Original RBox: ${rbox.length}", Logger.DEBUG)
119 Logger.print(s"Original ABox: ${abox.length}", Logger.DEBUG)
120 141
121 /* Retrieve individuals in the original ontology 142 /* Retrieve individuals in the original ontology */
122 */
123 val individuals: List[IRI] = 143 val individuals: List[IRI] =
124 ontology 144 ontology
125 .getIndividualsInSignature() 145 .getIndividualsInSignature()
@@ -129,7 +149,7 @@ class RSAOntology(val ontology: OWLOntology) {
129 .toList 149 .toList
130 150
131 val literals: List[Literal] = 151 val literals: List[Literal] =
132 abox 152 axioms
133 .collect { case a: OWLDataPropertyAssertionAxiom => a } 153 .collect { case a: OWLDataPropertyAssertionAxiom => a }
134 .map(_.getObject) 154 .map(_.getObject)
135 .map(implicits.RDFox.owlapiToRdfoxLiteral) 155 .map(implicits.RDFox.owlapiToRdfoxLiteral)
@@ -137,18 +157,13 @@ class RSAOntology(val ontology: OWLOntology) {
137 val concepts: List[OWLClass] = 157 val concepts: List[OWLClass] =
138 ontology.getClassesInSignature().asScala.toList 158 ontology.getClassesInSignature().asScala.toList
139 159
160 // This is needed in the computation of rules in the canonical model.
161 // Can we avoid this using RDFox built-in functions?
140 val roles: List[OWLObjectPropertyExpression] = 162 val roles: List[OWLObjectPropertyExpression] =
141 axioms 163 (tbox ++ rbox)
142 .flatMap(_.objectPropertyExpressionsInSignature) 164 .flatMap(_.objectPropertyExpressionsInSignature)
143 .distinct 165 .distinct
144 166
145 /** OWLAPI reasoner
146 *
147 * Used to carry out some preliminary reasoning task.
148 */
149 private val reasoner =
150 (new StructuralReasonerFactory()).createReasoner(ontology)
151
152 /* Steps for RSA check 167 /* Steps for RSA check
153 * 1) convert ontology axioms into LP rules 168 * 1) convert ontology axioms into LP rules
154 * 2) call RDFox on the onto and compute materialization 169 * 2) call RDFox on the onto and compute materialization
@@ -157,19 +172,16 @@ class RSAOntology(val ontology: OWLOntology) {
157 * ideally this annotates the graph with info about the reasons 172 * ideally this annotates the graph with info about the reasons
158 * why the ontology might not be RSA. This could help a second 173 * why the ontology might not be RSA. This could help a second
159 * step of approximation of an Horn-ALCHOIQ to RSA 174 * step of approximation of an Horn-ALCHOIQ to RSA
175 *
176 * TODO: Implement additional checks (taking into account equality)
177 *
178 * To check if the graph is tree-like we check for acyclicity in a
179 * undirected graph.
160 */ 180 */
161 lazy val isRSA: Boolean = Logger.timed( 181 lazy val isRSA: Boolean = Logger.timed(
162 { 182 {
163 val unsafe = this.unsafeRoles 183 val unsafe = this.unsafeRoles
164 184
165 // val renderer = new DLSyntaxObjectRenderer()
166 // println()
167 // println("Unsafe roles:")
168 // println(unsafe)
169 // println()
170 // println("DL rules:")
171 // tbox.foreach(x => println(renderer.render(x)))
172
173 object RSAConverter extends RDFoxConverter { 185 object RSAConverter extends RDFoxConverter {
174 186
175 override def convert( 187 override def convert(
@@ -195,66 +207,78 @@ class RSAOntology(val ontology: OWLOntology) {
195 207
196 case _ => super.convert(expr, term, unsafe, skolem, suffix) 208 case _ => super.convert(expr, term, unsafe, skolem, suffix)
197 } 209 }
198
199 } 210 }
200 211
201 /* Ontology convertion into LP rules */ 212 /* Ontology convertion into LP rules */
202 val term = RSAOntology.genFreshVariable() 213 val term = RSAOntology.genFreshVariable()
203 val datalog = axioms 214 val conversion = Try(
204 .map(a => RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)) 215 axioms.map(a =>
205 .unzip 216 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
206 val facts = datalog._1.flatten 217 )
207 val rules = datalog._2.flatten
208
209 //println("Datalog rules:")
210 //rules foreach println
211
212 // Open connection with RDFox
213 val (server, data) = RDFoxUtil.openConnection("RSACheck")
214
215 /* Add built-in rules
216 * TODO: substitute with RDFoxUtil.addRules
217 */
218 data.importData(
219 UpdateType.ADDITION,
220 RSA.Prefixes,
221 "rsa:E[?X,?Y] :- rsa:PE[?X,?Y], rsa:U[?X], rsa:U[?Y] ."
222 ) 218 )
223 219
224 /* Add ontology facts and rules */ 220 conversion match {
225 RDFoxUtil.addFacts(data, facts) 221 case Success(result) => {
226 RDFoxUtil.addRules(data, rules) 222 val datalog = result.unzip
227 223 val facts = datalog._1.flatten
228 /* Build graph */ 224 var rules = datalog._2.flatten
229 val graph = this.rsaGraph(data); 225
230 //println("Graph:") 226 /* Open connection with RDFox */
231 //println(graph) 227 val (server, data) = RDFoxUtil.openConnection("RSACheck")
232 228
233 // Close connection to RDFox 229 /* Add additional built-in rules */
234 RDFoxUtil.closeConnection(server, data) 230 val varX = Variable.create("X")
235 231 val varY = Variable.create("Y")
236 /* To check if the graph is tree-like we check for acyclicity in a 232 rules = Rule.create(
237 * undirected graph. 233 RSA.E(varX, varY),
238 * 234 RSA.PE(varX, varY),
239 * TODO: Implement additional checks (taking into account equality) 235 RSA.U(varX),
240 */ 236 RSA.U(varY)
241 graph.isAcyclic 237 ) :: rules
238
239 /* Load facts and rules from ontology */
240 RDFoxUtil.addFacts(data, facts)
241 RDFoxUtil.addRules(data, rules)
242 /* Load data files */
243 RDFoxUtil.addData(data, datafiles: _*)
244
245 /* Build graph */
246 val graph = this.rsaGraph(data);
247
248 /* Close connection to RDFox */
249 RDFoxUtil.closeConnection(server, data)
250
251 /* Acyclicity test */
252 graph.isAcyclic
253 }
254 case Failure(e) => {
255 Logger print s"Unsupported axiom: $e"
256 false
257 }
258 }
242 }, 259 },
243 "RSA check", 260 "RSA check",
244 Logger.DEBUG 261 Logger.DEBUG
245 ) 262 )
246 263
264 /** Unsafe roles of a given ontology.
265 *
266 * Unsafety conditions are the following:
267 *
268 * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe
269 * if there exists a role r2 (different from top) appearing in an
270 * axiom of type T3 and r1 is a subproperty of the inverse of r2.
271 *
272 * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe
273 * if there exists a role p2 appearing in an axiom of type T4 and
274 * p1 is a subproperty of either p2 or the inverse of p2.
275 */
247 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { 276 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = {
248 277
249 /* DEBUG: print rules in DL syntax */ 278 /* DEBUG: print rules in DL syntax */
250 //val renderer = new DLSyntaxObjectRenderer() 279 //val renderer = new DLSyntaxObjectRenderer()
251 280
252 /* Checking for (1) unsafety condition: 281 /* Checking for unsafety condition (1) */
253 *
254 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
255 * if there exists a role r2 (different from top) appearing in an axiom
256 * of type T3 and r1 is a subproperty of the inverse of r2.
257 */
258 val unsafe1 = for { 282 val unsafe1 = for {
259 axiom <- tbox 283 axiom <- tbox
260 if axiom.isT5 284 if axiom.isT5
@@ -271,13 +295,7 @@ class RSAOntology(val ontology: OWLOntology) {
271 if roleSuperInv.contains(role2) 295 if roleSuperInv.contains(role2)
272 } yield role1 296 } yield role1
273 297
274 /* Checking for (2) unsafety condition: 298 /* Checking for unsafety condition (2) */
275 *
276 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
277 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
278 * subproperty of either p2 or the inverse of p2.
279 *
280 */
281 val unsafe2 = for { 299 val unsafe2 = for {
282 axiom <- tbox 300 axiom <- tbox
283 if axiom.isT5 301 if axiom.isT5
@@ -307,12 +325,76 @@ class RSAOntology(val ontology: OWLOntology) {
307 Graph(edges: _*) 325 Graph(edges: _*)
308 } 326 }
309 327
310 def filteringProgram(query: ConjunctiveQuery): FilteringProgram = 328 /** Top axiomatization rules
311 Logger.timed( 329 *
312 new FilteringProgram(query, individuals ++ literals), 330 * For each concept/role *in the ontology file* introduce a rule to
313 "Generating filtering program", 331 * derive `owl:Thing`.
314 Logger.DEBUG 332 *
333 * @note this might not be enough in cases where data files contain
334 * concept/roles that are not in the ontology file. While this is
335 * non-standard, it is not forbidden either and may cause problems
336 * since not all individuals are considered part of `owl:Thing`.
337 *
338 * @note this is a naïve implementation of top axiomatization and
339 * might change in the future. The ideal solution would be for RDFox
340 * to take care of this, but at the time of writing this is not
341 * compatible with the way we are using the tool.
342 */
343 private val topAxioms: List[Rule] = {
344 val varX = Variable.create("X")
345 val varY = Variable.create("Y")
346 concepts
347 .map(c => {
348 Rule.create(
349 RSA.Thing(varX),
350 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI)
351 )
352 }) ++ roles.map(r => {
353 val name = r match {
354 case x: OWLObjectProperty => x.getIRI.getIRIString
355 case x: OWLObjectInverseOf =>
356 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
357 }
358 Rule.create(
359 List(RSA.Thing(varX), RSA.Thing(varY)),
360 List(TupleTableAtom.rdf(varX, name, varY))
361 )
362 })
363 }
364
365 /** Equality axiomatization rules
366 *
367 * Introduce reflexivity, simmetry and transitivity rules for a naïve
368 * equality axiomatization.
369 *
370 * @note that we are using a custom `congruent` predicate to indicate
371 * equality. This is to avoid interfering with the standard
372 * `owl:sameAs`.
373 *
374 * @note RDFox is able to handle equality in a "smart" way, but this
375 * behaviour is incompatible with other needed features like
376 * negation-as-failure and aggregates.
377 *
378 * @todo to complete the equality axiomatization we need to introduce
379 * substitution rules to explicate a complete "equality" semantics.
380 */
381 private val equalityAxioms: List[Rule] = {
382 val varX = Variable.create("X")
383 val varY = Variable.create("Y")
384 val varZ = Variable.create("Z")
385 List(
386 // Reflexivity
387 Rule.create(RSA.Congruent(varX, varX), RSA.Thing(varX)),
388 // Simmetry
389 Rule.create(RSA.Congruent(varY, varX), RSA.Congruent(varX, varY)),
390 // Transitivity
391 Rule.create(
392 RSA.Congruent(varX, varZ),
393 RSA.Congruent(varX, varY),
394 RSA.Congruent(varY, varZ)
395 )
315 ) 396 )
397 }
316 398
317 lazy val canonicalModel = Logger.timed( 399 lazy val canonicalModel = Logger.timed(
318 new CanonicalModel(this), 400 new CanonicalModel(this),
@@ -320,6 +402,13 @@ class RSAOntology(val ontology: OWLOntology) {
320 Logger.DEBUG 402 Logger.DEBUG
321 ) 403 )
322 404
405 def filteringProgram(query: ConjunctiveQuery): FilteringProgram =
406 Logger.timed(
407 new FilteringProgram(query),
408 "Generating filtering program",
409 Logger.DEBUG
410 )
411
323 // TODO: the following functions needs testing 412 // TODO: the following functions needs testing
324 def confl( 413 def confl(
325 role: OWLObjectPropertyExpression 414 role: OWLObjectPropertyExpression
@@ -356,18 +445,30 @@ class RSAOntology(val ontology: OWLOntology) {
356 val canon = this.canonicalModel 445 val canon = this.canonicalModel
357 val filter = this.filteringProgram(query) 446 val filter = this.filteringProgram(query)
358 447
359 //data.beginTransaction(TransactionType.READ_WRITE) 448 /* Upload data from data file */
449 RDFoxUtil.addData(data, datafiles: _*)
450
451 /* Top / equality axiomatization */
452 RDFoxUtil.addRules(data, topAxioms ++ equalityAxioms)
453
454 /* Generate `named` predicates */
455 RDFoxUtil.addFacts(data, (individuals ++ literals) map RSA.Named)
456 data.evaluateUpdate(
457 RSA.Prefixes,
458 "INSERT { ?X a rsa:Named } WHERE { ?X a owl:Thing }",
459 new java.util.HashMap[String, String]
460 )
360 461
361 Logger print s"Canonical model rules: ${canon.rules.length}" 462 Logger print s"Canonical model rules: ${canon.rules.length}"
362 RDFoxUtil.addRules(data, this.canonicalModel.rules) 463 RDFoxUtil.addRules(data, canon.rules)
363 464
364 Logger print s"Canonical model facts: ${canon.facts.length}" 465 Logger print s"Canonical model facts: ${canon.facts.length}"
365 RDFoxUtil.addFacts(data, this.canonicalModel.facts) 466 RDFoxUtil.addFacts(data, canon.facts)
366 467
367 RDFoxUtil printStatisticsFor data 468 //canon.facts.foreach(println)
469 //canon.rules.foreach(println)
368 470
369 Logger print s"Filtering program facts: ${filter.facts.length}" 471 RDFoxUtil printStatisticsFor data
370 RDFoxUtil.addFacts(data, filter.facts)
371 472
372 Logger print s"Filtering program rules: ${filter.rules.length}" 473 Logger print s"Filtering program rules: ${filter.rules.length}"
373 RDFoxUtil.addRules(data, filter.rules) 474 RDFoxUtil.addRules(data, filter.rules)
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala
index abb4815..c9bed35 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala
@@ -121,10 +121,12 @@ trait RDFoxConverter {
121 axiom match { 121 axiom match {
122 122
123 case a: OWLSubClassOfAxiom => { 123 case a: OWLSubClassOfAxiom => {
124 val subcls = a.getSubClass
125 val supcls = a.getSuperClass
124 val (sub, _) = 126 val (sub, _) =
125 convert(a.getSubClass, term, unsafe, NoSkolem, suffix) 127 convert(subcls, term, unsafe, NoSkolem, suffix)
126 val (sup, ext) = 128 val (sup, ext) =
127 convert(a.getSuperClass, term, unsafe, skolem, suffix) 129 convert(supcls, term, unsafe, skolem, suffix)
128 val rule = Rule.create(sup, ext ::: sub) 130 val rule = Rule.create(sup, ext ::: sub)
129 ResultR(List(rule)) 131 ResultR(List(rule))
130 } 132 }
@@ -241,14 +243,19 @@ trait RDFoxConverter {
241 case a: OWLDataPropertyRangeAxiom => 243 case a: OWLDataPropertyRangeAxiom =>
242 Result() // ignored 244 Result() // ignored
243 245
244 /** Catch-all case for all unhandled axiom types. */ 246 case a: OWLFunctionalDataPropertyAxiom =>
245 case a => 247 Result()
246 throw new RuntimeException(
247 s"Axiom '$a' is not supported (yet?)"
248 )
249 248
249 case a: OWLTransitiveObjectPropertyAxiom =>
250 Result()
251
252 /** Catch-all case for all unhandled axiom types. */
253 case a => default(axiom)
250 } 254 }
251 255
256 protected def default(axiom: OWLLogicalAxiom): Result =
257 throw new RuntimeException(s"Axiom '$axiom' is not supported (yet?)")
258
252 /** Converts a class expression into a collection of atoms. 259 /** Converts a class expression into a collection of atoms.
253 * 260 *
254 * @note not all possible class expressions are handled correctly. 261 * @note not all possible class expressions are handled correctly.
@@ -431,6 +438,16 @@ trait RDFoxConverter {
431 convert(expr, term, unsafe, skolem, suffix) 438 convert(expr, term, unsafe, skolem, suffix)
432 } 439 }
433 440
441 //case (_, sup: OWLObjectExactCardinality) => {
442 // println(s"Ignored: $a")
443 // return Result()
444 //}
445
446 //case (_, sup: OWLDataExactCardinality) => {
447 // println(s"Ignored: $a")
448 // return Result()
449 //}
450
434 /** Existential quantification with singleton filler 451 /** Existential quantification with singleton filler
435 * 452 *
436 * @see 453 * @see
@@ -451,6 +468,11 @@ trait RDFoxConverter {
451 */ 468 */
452 case e: OWLDataHasValue => 469 case e: OWLDataHasValue =>
453 (List(convert(e.getProperty, term, e.getFiller, suffix)), List()) 470 (List(convert(e.getProperty, term, e.getFiller, suffix)), List())
471
472 case e: OWLObjectUnionOf => {
473 (List(), List())
474 }
475
454 /** Catch-all case for all unhandled class expressions. */ 476 /** Catch-all case for all unhandled class expressions. */
455 case e => 477 case e =>
456 throw new RuntimeException( 478 throw new RuntimeException(
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala
index d072e48..61a8b79 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RDFoxUtil.scala
@@ -123,6 +123,22 @@ object RDFoxUtil {
123 facts.map(_.toString(Prefixes.s_emptyPrefixes)).mkString("", ".\n", ".") 123 facts.map(_.toString(Prefixes.s_emptyPrefixes)).mkString("", ".\n", ".")
124 ), 124 ),
125 "Loading facts", 125 "Loading facts",
126
127 /** Imports a sequence of files directly into a datastore.
128 *
129 * @param data datastore connection.
130 * @param files sequence of files to upload.
131 */
132 def addData(data: DataStoreConnection, files: File*): Unit =
133 Logger.timed(
134 files.foreach {
135 data.importData(
136 UpdateType.ADDITION,
137 RSA.Prefixes,
138 _
139 )
140 },
141 "Loading data files",
126 Logger.DEBUG 142 Logger.DEBUG
127 ) 143 )
128 144
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
index 4b04c40..ee2fdc1 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/util/RSA.scala
@@ -28,10 +28,14 @@ object RSA {
28 28
29 val Prefixes: Prefixes = new Prefixes() 29 val Prefixes: Prefixes = new Prefixes()
30 Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/") 30 Prefixes.declarePrefix("rsa:", "http://www.cs.ox.ac.uk/isg/rsa/")
31 Prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#")
31 32
32 private def atom(name: IRI, vars: List[Term]): TupleTableAtom = 33 private def atom(name: IRI, vars: List[Term]): TupleTableAtom =
33 TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*) 34 TupleTableAtom.create(TupleTableName.create(name.getIRI), vars: _*)
34 35
36 def E(t1: Term, t2: Term) =
37 TupleTableAtom.rdf(t1, RSA("E"), t2)
38
35 def PE(t1: Term, t2: Term) = 39 def PE(t1: Term, t2: Term) =
36 TupleTableAtom.rdf(t1, RSA("PE"), t2) 40 TupleTableAtom.rdf(t1, RSA("PE"), t2)
37 41
diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
index e627bf7..32ef8da 100644
--- a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
+++ b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala
@@ -67,29 +67,25 @@ class FilteringProgramSpec extends AnyFlatSpec with Matchers {
67 67
68 "CQ 0" should "generate 27 rules and 3 facts" in { 68 "CQ 0" should "generate 27 rules and 3 facts" in {
69 val cq = ConjunctiveQuery.parse(cq0).get 69 val cq = ConjunctiveQuery.parse(cq0).get
70 val filter = FilteringProgram(cq, constants) 70 val filter = FilteringProgram(cq)
71 filter.facts should have length 3
72 filter.rules should have length 27 71 filter.rules should have length 27
73 } 72 }
74 73
75 "CQ 1" should "generate 15 rules" in { 74 "CQ 1" should "generate 15 rules" in {
76 val cq = ConjunctiveQuery.parse(cq1).get 75 val cq = ConjunctiveQuery.parse(cq1).get
77 val filter = FilteringProgram(cq, List()) 76 val filter = FilteringProgram(cq)
78 filter.facts shouldBe empty
79 filter.rules should have length 15 77 filter.rules should have length 15
80 } 78 }
81 79
82 "CQ 2" should "generate 51 rules" in { 80 "CQ 2" should "generate 51 rules" in {
83 val cq = ConjunctiveQuery.parse(cq2).get 81 val cq = ConjunctiveQuery.parse(cq2).get
84 val filter = FilteringProgram(cq, List()) 82 val filter = FilteringProgram(cq)
85 filter.facts shouldBe empty
86 filter.rules should have length 51 83 filter.rules should have length 51
87 } 84 }
88 85
89 "BCQ 0" should "generate 46 rules" in { 86 "BCQ 0" should "generate 46 rules" in {
90 val cq = ConjunctiveQuery.parse(bcq0).get 87 val cq = ConjunctiveQuery.parse(bcq0).get
91 val filter = FilteringProgram(cq, constants) 88 val filter = FilteringProgram(cq)
92 filter.facts should have length 3
93 filter.rules should have length 43 89 filter.rules should have length 43
94 } 90 }
95 91