aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
committerFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
commitd017662e2d65ec72e7decde3b76591c198da9819 (patch)
tree57193f145cb39223db0b0da6055556aca7d04622
parentc597b5efbe9e351a4313ef8fc1215f9e188b1ffd (diff)
parent7d619706551117a485d93d0d6847a25afa6a359d (diff)
downloadRSAComb-d017662e2d65ec72e7decde3b76591c198da9819.tar.gz
RSAComb-d017662e2d65ec72e7decde3b76591c198da9819.zip
Merge branch 'approximation'v0.2.0
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala4
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala41
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala663
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala18
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala232
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala26
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/converter/RDFoxConverter.scala13
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala3
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala301
9 files changed, 886 insertions, 415 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 b949ccb..ca54054 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala
@@ -92,8 +92,8 @@ class CanonicalModel(val ontology: RSAOntology) {
92 val (facts, rules): (List[TupleTableAtom], List[Rule]) = { 92 val (facts, rules): (List[TupleTableAtom], List[Rule]) = {
93 // Compute rules from ontology axioms 93 // Compute rules from ontology axioms
94 val (facts, rules) = { 94 val (facts, rules) = {
95 val term = RSAOntology.genFreshVariable() 95 val term = RSAUtil.genFreshVariable()
96 val unsafe = ontology.unsafeRoles 96 val unsafe = ontology.unsafe
97 ontology.axioms 97 ontology.axioms
98 .map(a => 98 .map(a =>
99 CanonicalModelConverter.convert(a, term, unsafe, Constant(a), Empty) 99 CanonicalModelConverter.convert(a, term, unsafe, Constant(a), Empty)
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala
index 231d2b8..258c226 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala
@@ -26,6 +26,10 @@ import tech.oxfordsemantic.jrdfox.logic.sparql.statement.SelectQuery
26import util.{Logger, RDFoxUtil, RSA} 26import util.{Logger, RDFoxUtil, RSA}
27import sparql.ConjunctiveQuery 27import sparql.ConjunctiveQuery
28 28
29import uk.ac.ox.cs.rsacomb.ontology.Ontology
30import uk.ac.ox.cs.rsacomb.converter.Normalizer
31import uk.ac.ox.cs.rsacomb.approximation.LowerBound
32
29case class RSAOption[+T](opt: T) { 33case class RSAOption[+T](opt: T) {
30 def get[T]: T = opt.asInstanceOf[T] 34 def get[T]: T = opt.asInstanceOf[T]
31} 35}
@@ -122,12 +126,15 @@ object RSAComb extends App {
122 /* Command-line options */ 126 /* Command-line options */
123 val config = RSAConfig.parse(args.toList) 127 val config = RSAConfig.parse(args.toList)
124 128
125 val ontology = RSAOntology( 129 /* Load original ontology and normalize it */
130 val ontology = Ontology(
126 config('ontology).get[File], 131 config('ontology).get[File],
127 config('data).get[List[File]]: _* 132 config('data).get[List[File]]
128 ) 133 ).normalize(new Normalizer)
129 val rsa = ontology.toRSA() 134
130 ontology.statistics() 135 /* Approximate the ontology to RSA */
136 val toRSA = new LowerBound
137 val rsa = ontology approximate toRSA
131 138
132 if (config contains 'query) { 139 if (config contains 'query) {
133 val query = 140 val query =
@@ -140,18 +147,18 @@ object RSAComb extends App {
140 Logger.print(s"$answers", Logger.VERBOSE) 147 Logger.print(s"$answers", Logger.VERBOSE)
141 Logger print s"Number of answers: ${answers.length} (${answers.lengthWithMultiplicity})" 148 Logger print s"Number of answers: ${answers.length} (${answers.lengthWithMultiplicity})"
142 // Retrieve unfiltered answers 149 // Retrieve unfiltered answers
143 val unfiltered = rsa.queryDataStore( 150 // val unfiltered = rsa.queryDataStore(
144 """ 151 // """
145 SELECT (count(?K) as ?COUNT) 152 // SELECT (count(?K) as ?COUNT)
146 WHERE { 153 // WHERE {
147 ?K a rsa:QM . 154 // ?K a rsa:QM .
148 } 155 // }
149 """, 156 // """,
150 RSA.Prefixes 157 // RSA.Prefixes
151 ) 158 // )
152 unfiltered.foreach((u) => 159 // unfiltered.foreach((u) =>
153 Logger print s"Number of unfiltered answers: ${u.head._2}" 160 // Logger print s"Number of unfiltered answers: ${u.head._2}"
154 ) 161 // )
155 } 162 }
156 case None => 163 case None =>
157 throw new RuntimeException("Submitted query is not conjunctive") 164 throw new RuntimeException("Submitted query is not conjunctive")
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 3e10697..30e1305 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala
@@ -66,117 +66,159 @@ import scala.collection.JavaConverters._
66import scala.collection.mutable.{Set, Map} 66import scala.collection.mutable.{Set, Map}
67import scalax.collection.Graph 67import scalax.collection.Graph
68import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ 68import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
69import scalax.collection.GraphTraversal._
70 69
71/* Debug only */ 70/* Debug only */
72import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer 71import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer
73import tech.oxfordsemantic.jrdfox.logic._ 72import tech.oxfordsemantic.jrdfox.logic._
74import org.semanticweb.owlapi.model.OWLObjectInverseOf 73import org.semanticweb.owlapi.model.OWLObjectInverseOf
75 74
75import uk.ac.ox.cs.rsacomb.approximation.Approximation
76import uk.ac.ox.cs.rsacomb.converter._ 76import uk.ac.ox.cs.rsacomb.converter._
77import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType} 77import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType}
78import uk.ac.ox.cs.rsacomb.suffix._ 78import uk.ac.ox.cs.rsacomb.suffix._
79import uk.ac.ox.cs.rsacomb.sparql._ 79import uk.ac.ox.cs.rsacomb.sparql._
80import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} 80import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
81import uk.ac.ox.cs.rsacomb.util.Logger 81import uk.ac.ox.cs.rsacomb.util.Logger
82import uk.ac.ox.cs.rsacomb.ontology.Ontology
82 83
83object RSAOntology { 84object RSAUtil {
84 85
85 /** Name of the RDFox data store used for CQ answering */ 86 // implicit def axiomsToOntology(axioms: Seq[OWLAxiom]) = {
86 private val DataStore = "answer_computation" 87 // val manager = OWLManager.createOWLOntologyManager()
88 // manager.createOntology(axioms.asJava)
89 // }
87 90
88 /** Simple fresh variable generator */ 91 /** Manager instance to interface with OWLAPI */
92 val manager = OWLManager.createOWLOntologyManager()
93 val factory = manager.getOWLDataFactory()
94
95 /** Simple fresh variable/class generator */
89 private var counter = -1; 96 private var counter = -1;
90 def genFreshVariable(): Variable = { 97 def genFreshVariable(): Variable = {
91 counter += 1 98 counter += 1
92 Variable.create(f"I$counter%05d") 99 Variable.create(f"I$counter%05d")
93 } 100 }
101 def getFreshOWLClass(): OWLClass = {
102 counter += 1
103 factory.getOWLClass(s"X$counter")
104 }
105
106}
107
108object RSAOntology {
109
110 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
94 111
95 /** Manager instance to interface with OWLAPI */ 112 /** Manager instance to interface with OWLAPI */
96 val manager = OWLManager.createOWLOntologyManager() 113 val manager = OWLManager.createOWLOntologyManager()
97 114
98 def apply(ontology: File, data: File*): RSAOntology = 115 /** Name of the RDFox data store used for CQ answering */
99 new RSAOntology( 116 private val DataStore = "answer_computation"
100 manager.loadOntologyFromOntologyDocument(ontology), 117
101 data: _* 118 /** Filtering program for a given query
119 *
120 * @param query the query to derive the filtering program
121 * @return the filtering program for the given query
122 */
123 def filteringProgram(query: ConjunctiveQuery): FilteringProgram =
124 Logger.timed(
125 FilteringProgram(FilterType.REVISED)(query),
126 "Generating filtering program",
127 Logger.DEBUG
102 ) 128 )
103 129
104 def apply(ontology: OWLOntology, data: File*): RSAOntology = 130 def apply(
105 new RSAOntology(ontology, data: _*) 131 axioms: List[OWLLogicalAxiom],
132 datafiles: List[File]
133 ): RSAOntology = new RSAOntology(axioms, datafiles)
134
135 // def apply(
136 // ontofile: File,
137 // datafiles: List[File],
138 // approx: Option[Approximation]
139 // ): RSAOntology = {
140 // val ontology = manager.loadOntologyFromOntologyDocument(ontofile)
141 // RSAOntology(ontology, datafiles, approx)
142 // }
143
144 // def apply(
145 // ontology: OWLOntology,
146 // datafiles: List[File],
147 // approx: Option[Approximation]
148 // ): RSAOntology = {
149 // val normalizer = new Normalizer()
150
151 // /** TBox axioms */
152 // var tbox: List[OWLLogicalAxiom] =
153 // ontology
154 // .tboxAxioms(Imports.INCLUDED)
155 // .collect(Collectors.toList())
156 // .collect { case a: OWLLogicalAxiom => a }
157 // .flatMap(normalizer.normalize)
158
159 // /** RBox axioms */
160 // var rbox: List[OWLLogicalAxiom] =
161 // ontology
162 // .rboxAxioms(Imports.INCLUDED)
163 // .collect(Collectors.toList())
164 // .collect { case a: OWLLogicalAxiom => a }
165 // .flatMap(normalizer.normalize)
166
167 // /** ABox axioms
168 // *
169 // * @note this represents only the set of assertions contained in the
170 // * ontology file. Data files specified in `datafiles` are directly
171 // * imported in RDFox due to performance issues when trying to import
172 // * large data files via OWLAPI.
173 // */
174 // var abox: List[OWLLogicalAxiom] =
175 // ontology
176 // .aboxAxioms(Imports.INCLUDED)
177 // .collect(Collectors.toList())
178 // .collect { case a: OWLLogicalAxiom => a }
179 // .flatMap(normalizer.normalize)
180
181 // /** Collection of logical axioms in the input ontology */
182 // var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
183
184 // new RSAOntology(
185 // approx match {
186 // case Some(a) => a.approximate(axioms, datafiles)
187 // case None => axioms
188 // },
189 // datafiles: _*
190 // )
191 // }
192
106} 193}
107 194
108/** Wrapper class for an ontology in RSA 195/** A wrapper for an RSA ontology
109 * 196 *
110 * @param ontology the input OWL2 ontology. 197 * @param ontology the input OWL2 ontology.
111 * @param datafiles additinal data (treated as part of the ABox) 198 * @param datafiles additinal data (treated as part of the ABox)
112 */ 199 */
113class RSAOntology(val original: OWLOntology, val datafiles: File*) { 200class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File])
201 extends Ontology(axioms, datafiles) {
114 202
115 /** Simplify conversion between OWLAPI and RDFox concepts */ 203 /** Simplify conversion between OWLAPI and RDFox concepts */
116 import implicits.RDFox._ 204 import implicits.RDFox._
117 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ 205 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
206
207 /** Simplify conversion between Java and Scala collections */
118 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 208 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
119 209
120 /** Set of axioms removed during the approximation to RSA */ 210 /** Set of axioms removed during the approximation to RSA */
121 private var removed: Seq[OWLAxiom] = Seq.empty 211 //private var removed: Seq[OWLAxiom] = Seq.empty
122
123 /** The normalizer normalizes the ontology and approximate it to
124 * Horn-ALCHOIQ. A further step is needed to obtain an RSA
125 * approximation of the input ontology `original`.
126 */
127 private val normalizer = new Normalizer()
128
129 /** TBox axioms */
130 var tbox: List[OWLLogicalAxiom] =
131 original
132 .tboxAxioms(Imports.INCLUDED)
133 .collect(Collectors.toList())
134 .collect { case a: OWLLogicalAxiom => a }
135 .flatMap(normalizer.normalize)
136
137 /** RBox axioms */
138 var rbox: List[OWLLogicalAxiom] =
139 original
140 .rboxAxioms(Imports.INCLUDED)
141 .collect(Collectors.toList())
142 .collect { case a: OWLLogicalAxiom => a }
143 .flatMap(normalizer.normalize)
144
145 /** ABox axioms
146 *
147 * @note this represents only the set of assertions contained in the
148 * ontology file. Data files specified in `datafiles` are directly
149 * imported in RDFox due to performance issues when trying to import
150 * large data files via OWLAPI.
151 */
152 var abox: List[OWLLogicalAxiom] =
153 original
154 .aboxAxioms(Imports.INCLUDED)
155 .collect(Collectors.toList())
156 .collect { case a: OWLLogicalAxiom => a }
157 .flatMap(normalizer.normalize)
158
159 /** Collection of logical axioms in the input ontology */
160 var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox
161
162 /** Normalized Horn-ALCHOIQ ontology */
163 val ontology = RSAOntology.manager.createOntology(
164 axioms.asInstanceOf[List[OWLAxiom]].asJava
165 )
166
167 /** OWLAPI internal reasoner instantiated over the approximated ontology */
168 private val reasoner =
169 (new StructuralReasonerFactory()).createReasoner(ontology)
170 212
171 /** Retrieve individuals/literals in the ontology */ 213 /** Retrieve individuals/literals in the ontology */
172 val individuals: List[IRI] = 214 private val individuals: List[IRI] =
173 ontology 215 ontology
174 .getIndividualsInSignature() 216 .getIndividualsInSignature()
175 .asScala 217 .asScala
176 .map(_.getIRI) 218 .map(_.getIRI)
177 .map(implicits.RDFox.owlapiToRdfoxIri) 219 .map(implicits.RDFox.owlapiToRdfoxIri)
178 .toList 220 .toList
179 val literals: List[Literal] = 221 private val literals: List[Literal] =
180 axioms 222 axioms
181 .collect { case a: OWLDataPropertyAssertionAxiom => a } 223 .collect { case a: OWLDataPropertyAssertionAxiom => a }
182 .map(_.getObject) 224 .map(_.getObject)
@@ -186,7 +228,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
186 val concepts: List[OWLClass] = 228 val concepts: List[OWLClass] =
187 ontology.getClassesInSignature().asScala.toList 229 ontology.getClassesInSignature().asScala.toList
188 val roles: List[OWLObjectPropertyExpression] = 230 val roles: List[OWLObjectPropertyExpression] =
189 (tbox ++ rbox) 231 axioms
190 .flatMap(_.objectPropertyExpressionsInSignature) 232 .flatMap(_.objectPropertyExpressionsInSignature)
191 .distinct 233 .distinct
192 234
@@ -202,123 +244,36 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
202 * if there exists a role p2 appearing in an axiom of type T4 and 244 * if there exists a role p2 appearing in an axiom of type T4 and
203 * p1 is a subproperty of either p2 or the inverse of p2. 245 * p1 is a subproperty of either p2 or the inverse of p2.
204 */ 246 */
205 val unsafeRoles: List[OWLObjectPropertyExpression] = { 247 // val unsafeRoles: List[OWLObjectPropertyExpression] = {
206 248
207 /* Checking for unsafety condition (1) */ 249 // /* Checking for unsafety condition (1) */
208 val unsafe1 = for { 250 // val unsafe1 = for {
209 axiom <- tbox 251 // axiom <- axioms
210 if axiom.isT5 252 // if axiom.isT5
211 role1 <- axiom.objectPropertyExpressionsInSignature 253 // role1 <- axiom.objectPropertyExpressionsInSignature
212 roleSuper = role1 +: reasoner.superObjectProperties(role1) 254 // roleSuper = role1 +: reasoner.superObjectProperties(role1)
213 roleSuperInv = roleSuper.map(_.getInverseProperty) 255 // roleSuperInv = roleSuper.map(_.getInverseProperty)
214 axiom <- tbox 256 // axiom <- axioms
215 if axiom.isT3 && !axiom.isT3top 257 // if axiom.isT3 && !axiom.isT3top
216 role2 <- axiom.objectPropertyExpressionsInSignature 258 // role2 <- axiom.objectPropertyExpressionsInSignature
217 if roleSuperInv contains role2 259 // if roleSuperInv contains role2
218 } yield role1 260 // } yield role1
219 261
220 /* Checking for unsafety condition (2) */ 262 // /* Checking for unsafety condition (2) */
221 val unsafe2 = for { 263 // val unsafe2 = for {
222 axiom <- tbox 264 // axiom <- axioms
223 if axiom.isT5 265 // if axiom.isT5
224 role1 <- axiom.objectPropertyExpressionsInSignature 266 // role1 <- axiom.objectPropertyExpressionsInSignature
225 roleSuper = role1 +: reasoner.superObjectProperties(role1) 267 // roleSuper = role1 +: reasoner.superObjectProperties(role1)
226 roleSuperInv = roleSuper.map(_.getInverseProperty) 268 // roleSuperInv = roleSuper.map(_.getInverseProperty)
227 axiom <- tbox 269 // axiom <- axioms
228 if axiom.isT4 270 // if axiom.isT4
229 role2 <- axiom.objectPropertyExpressionsInSignature 271 // role2 <- axiom.objectPropertyExpressionsInSignature
230 if roleSuper.contains(role2) || roleSuperInv.contains(role2) 272 // if roleSuper.contains(role2) || roleSuperInv.contains(role2)
231 } yield role1 273 // } yield role1
232 274
233 unsafe1 ++ unsafe2 275 // unsafe1 ++ unsafe2
234 } 276 // }
235
236 /** Compute the RSA dependency graph
237 *
238 * This is used to approximate the input ontology to RSA.
239 *
240 * @return a tuple containing the dependency graph and a map between
241 * the constants newly introduced and the corresponding axioms in the
242 * ontology.
243 */
244 private def dependencyGraph()
245 : (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = {
246 val unsafe = this.unsafeRoles
247 var nodemap = Map.empty[String, OWLAxiom]
248
249 object RSAConverter extends RDFoxConverter {
250
251 override def convert(
252 expr: OWLClassExpression,
253 term: Term,
254 unsafe: List[OWLObjectPropertyExpression],
255 skolem: SkolemStrategy,
256 suffix: RSASuffix
257 ): Shards =
258 (expr, skolem) match {
259
260 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
261 nodemap.update(c.iri.getIRI, c.axiom)
262 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
263 if (unsafe contains e.getProperty)
264 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
265 else
266 (RSA.PE(term, c.iri) :: res, ext)
267 }
268
269 case (e: OWLDataSomeValuesFrom, c: Constant) => {
270 nodemap.update(c.iri.getIRI, c.axiom)
271 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
272 if (unsafe contains e.getProperty)
273 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
274 else
275 (RSA.PE(term, c.iri) :: res, ext)
276 }
277
278 case _ => super.convert(expr, term, unsafe, skolem, suffix)
279 }
280 }
281
282 /* Ontology convertion into LP rules */
283 val term = RSAOntology.genFreshVariable()
284 val result = axioms.map(a =>
285 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
286 )
287
288 val datalog = result.unzip
289 val facts = datalog._1.flatten
290 var rules = datalog._2.flatten
291
292 /* Open connection with RDFox */
293 val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph")
294
295 /* Add additional built-in rules */
296 val varX = Variable.create("X")
297 val varY = Variable.create("Y")
298 rules = Rule.create(
299 RSA.E(varX, varY),
300 RSA.PE(varX, varY),
301 RSA.U(varX),
302 RSA.U(varY)
303 ) :: rules
304 /* Load facts and rules from ontology */
305 RDFoxUtil.addFacts(data, facts)
306 RDFoxUtil.addRules(data, rules)
307 /* Load data files */
308 RDFoxUtil.addData(data, datafiles: _*)
309
310 /* Build the graph */
311 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
312 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
313 var edges: Seq[DiEdge[Resource]] =
314 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
315 val graph = Graph(edges: _*)
316
317 /* Close connection to RDFox */
318 RDFoxUtil.closeConnection(server, data)
319
320 (graph, nodemap)
321 }
322 277
323 /** Approximate a Horn-ALCHOIQ ontology to RSA 278 /** Approximate a Horn-ALCHOIQ ontology to RSA
324 * 279 *
@@ -329,61 +284,61 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
329 * @param graph the graph used to compute the axioms to remove. 284 * @param graph the graph used to compute the axioms to remove.
330 * @param nodemap map from graph nodes to ontology axioms. 285 * @param nodemap map from graph nodes to ontology axioms.
331 */ 286 */
332 def toRSA(): RSAOntology = Logger.timed( 287 // def toRSA(): RSAOntology = Logger.timed(
333 { 288 // {
334 289
335 /* Compute the dependency graph for the ontology */ 290 // /* Compute the dependency graph for the ontology */
336 val (graph, nodemap) = this.dependencyGraph() 291 // val (graph, nodemap) = this.dependencyGraph()
337 292
338 /* Define node colors for the graph visit */ 293 // /* Define node colors for the graph visit */
339 sealed trait NodeColor 294 // sealed trait NodeColor
340 case object Unvisited extends NodeColor 295 // case object Unvisited extends NodeColor
341 case object Visited extends NodeColor 296 // case object Visited extends NodeColor
342 case object ToDelete extends NodeColor 297 // case object ToDelete extends NodeColor
343 298
344 /* Keep track of node colors during graph visit */ 299 // /* Keep track of node colors during graph visit */
345 var color = Map.from[Resource, NodeColor]( 300 // var color = Map.from[Resource, NodeColor](
346 graph.nodes.toOuter.map(k => (k, Unvisited)) 301 // graph.nodes.toOuter.map(k => (k, Unvisited))
347 ) 302 // )
348 303
349 for { 304 // for {
350 component <- graph.componentTraverser().map(_ to Graph) 305 // component <- graph.componentTraverser().map(_ to Graph)
351 edge <- component 306 // edge <- component
352 .outerEdgeTraverser(component.nodes.head) 307 // .outerEdgeTraverser(component.nodes.head)
353 .withKind(BreadthFirst) 308 // .withKind(BreadthFirst)
354 } yield { 309 // } yield {
355 val source = edge._1 310 // val source = edge._1
356 val target = edge._2 311 // val target = edge._2
357 color(source) match { 312 // color(source) match {
358 case Unvisited | Visited => { 313 // case Unvisited | Visited => {
359 color(target) match { 314 // color(target) match {
360 case Unvisited => 315 // case Unvisited =>
361 color(source) = Visited; 316 // color(source) = Visited;
362 color(target) = Visited 317 // color(target) = Visited
363 case Visited => 318 // case Visited =>
364 color(source) = ToDelete 319 // color(source) = ToDelete
365 case ToDelete => 320 // case ToDelete =>
366 color(source) = Visited 321 // color(source) = Visited
367 } 322 // }
368 } 323 // }
369 case ToDelete => 324 // case ToDelete =>
370 } 325 // }
371 } 326 // }
372 327
373 val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => 328 // val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) =>
374 nodemap(resource.getIRI) 329 // nodemap(resource.getIRI)
375 }.toSeq 330 // }.toSeq
376 331
377 /* Remove axioms from approximated ontology */ 332 // /* Remove axioms from approximated ontology */
378 ontology.removeAxioms(toDelete: _*) 333 // ontology.removeAxioms(toDelete: _*)
379 this.removed = toDelete 334 // this.removed = toDelete
380 335
381 /* Return RSA ontology */ 336 // /* Return RSA ontology */
382 RSAOntology(ontology, datafiles: _*) 337 // RSAOntology(ontology, datafiles: _*)
383 }, 338 // },
384 "Horn-ALCHOIQ to RSA approximation:", 339 // "Horn-ALCHOIQ to RSA approximation:",
385 Logger.DEBUG 340 // Logger.DEBUG
386 ) 341 // )
387 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> 342 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
388 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, 343 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
389 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) 344 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
@@ -462,31 +417,27 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
462 ) 417 )
463 } 418 }
464 419
420 /** Canonical model of the ontology */
465 lazy val canonicalModel = Logger.timed( 421 lazy val canonicalModel = Logger.timed(
466 new CanonicalModel(this), 422 new CanonicalModel(this),
467 "Generating canonical model program", 423 "Generating canonical model program",
468 Logger.DEBUG 424 Logger.DEBUG
469 ) 425 )
470 426
471 def filteringProgram(query: ConjunctiveQuery): FilteringProgram = 427 /** Computes all roles conflicting with a given role
472 Logger.timed( 428 *
473 FilteringProgram(FilterType.REVISED)(query), 429 * @param role a role (object property expression).
474 "Generating filtering program", 430 * @return a set of roles conflicting with `role`.
475 Logger.DEBUG 431 */
476 )
477
478 def confl( 432 def confl(
479 role: OWLObjectPropertyExpression 433 role: OWLObjectPropertyExpression
480 ): Set[OWLObjectPropertyExpression] = { 434 ): Set[OWLObjectPropertyExpression] = {
481 435 reasoner
482 val invSuperRoles = reasoner
483 .superObjectProperties(role) 436 .superObjectProperties(role)
484 .collect(Collectors.toSet()) 437 .collect(Collectors.toSet())
485 .asScala 438 .asScala
486 .addOne(role) 439 .addOne(role)
487 .map(_.getInverseProperty) 440 .map(_.getInverseProperty)
488
489 invSuperRoles
490 .flatMap(x => 441 .flatMap(x =>
491 reasoner 442 reasoner
492 .subObjectProperties(x) 443 .subObjectProperties(x)
@@ -498,6 +449,77 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
498 .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) 449 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
499 } 450 }
500 451
452 /** Selfloop detection for a given axiom
453 *
454 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
455 * @return unfold set for the axiom
456 */
457 def self(axiom: OWLSubClassOfAxiom): Set[Term] = {
458 val role = axiom.objectPropertyExpressionsInSignature(0)
459 if (this.confl(role).contains(role)) {
460 Set(RSA("v0_" ++ axiom.hashed), RSA("v1_" ++ axiom.hashed))
461 } else {
462 Set()
463 }
464 }
465
466 /** Cycle detection for a give axiom
467 *
468 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
469 * @return unfold set for the axiom
470 *
471 * @todo we can actually use `toTriple` from `RSAAxiom` to get the
472 * classes and the role for a given axiom
473 */
474 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
475 val classes =
476 axiom.classesInSignature.collect(Collectors.toList()).asScala
477 val classA = classes(0)
478 val roleR = axiom
479 .objectPropertyExpressionsInSignature(0)
480 .asInstanceOf[OWLObjectProperty]
481 val classB = classes(1)
482 cycle_aux(classA, roleR, classB)
483 }
484
485 /** Auxiliary function for [[RSAOntology.cycle]] */
486 private def cycle_aux(
487 classA: OWLClass,
488 roleR: OWLObjectProperty,
489 classB: OWLClass
490 ): Set[Term] = {
491 val conflR = this.confl(roleR)
492 // TODO: technically we just need the TBox here
493 val terms = for {
494 axiom1 <- axioms
495 if axiom1.isT5
496 // We expect only one role coming out of a T5 axiom
497 roleS <- axiom1.objectPropertyExpressionsInSignature
498 // Triples ordering is among triples involving safe roles.
499 if !unsafe.contains(roleS)
500 if conflR.contains(roleS)
501 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
502 tripleDSC = axiom1.hashed
503 individual =
504 if (tripleARB > tripleDSC) {
505 RSA("v1_" ++ tripleDSC)
506 } else {
507 // Note that this is also the case for
508 // `tripleARB == tripleDSC`
509 RSA("v0_" ++ tripleDSC)
510 }
511 } yield individual
512 terms to Set
513 }
514
515 /** Returns unfold set for self-loop and cycle for the input axiom
516 *
517 * @param axiom an axiom of type [[OWLSubClassOfAxiom]]
518 * @return unfold set for the axiom
519 */
520 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
521 this.self(axiom) | this.cycle(axiom)
522
501 /** Returns the answers to a query 523 /** Returns the answers to a query
502 * 524 *
503 * @param query query to execute 525 * @param query query to execute
@@ -505,10 +527,9 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
505 */ 527 */
506 def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed( 528 def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed(
507 { 529 {
508 import implicits.JavaCollections._
509 val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) 530 val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore)
510 val canon = this.canonicalModel 531 val canon = this.canonicalModel
511 val filter = this.filteringProgram(query) 532 val filter = RSAOntology.filteringProgram(query)
512 533
513 /* Upload data from data file */ 534 /* Upload data from data file */
514 RDFoxUtil.addData(data, datafiles: _*) 535 RDFoxUtil.addData(data, datafiles: _*)
@@ -526,12 +547,15 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
526 new java.util.HashMap[String, String] 547 new java.util.HashMap[String, String]
527 ) 548 )
528 549
550 /* Add canonical model */
529 Logger print s"Canonical model rules: ${canon.rules.length}" 551 Logger print s"Canonical model rules: ${canon.rules.length}"
530 RDFoxUtil.addRules(data, canon.rules) 552 RDFoxUtil.addRules(data, canon.rules)
531 553
532 Logger print s"Canonical model facts: ${canon.facts.length}" 554 Logger print s"Canonical model facts: ${canon.facts.length}"
533 RDFoxUtil.addFacts(data, canon.facts) 555 RDFoxUtil.addFacts(data, canon.facts)
534 556
557 RDFoxUtil printStatisticsFor data
558
535 //{ 559 //{
536 // import java.io.{PrintStream, FileOutputStream, File} 560 // import java.io.{PrintStream, FileOutputStream, File}
537 // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog")) 561 // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog"))
@@ -541,16 +565,13 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
541 // rules2.print(filter.rules.mkString("\n")) 565 // rules2.print(filter.rules.mkString("\n"))
542 //} 566 //}
543 567
544 //canon.facts.foreach(println) 568 /* Add filtering program */
545 //filter.rules.foreach(println)
546
547 RDFoxUtil printStatisticsFor data
548
549 Logger print s"Filtering program rules: ${filter.rules.length}" 569 Logger print s"Filtering program rules: ${filter.rules.length}"
550 RDFoxUtil.addRules(data, filter.rules) 570 RDFoxUtil.addRules(data, filter.rules)
551 571
552 RDFoxUtil printStatisticsFor data 572 RDFoxUtil printStatisticsFor data
553 573
574 /* Gather answers to the query */
554 val answers = { 575 val answers = {
555 val ans = filter.answerQuery 576 val ans = filter.answerQuery
556 RDFoxUtil 577 RDFoxUtil
@@ -558,7 +579,9 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
558 .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) 579 .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _))
559 .get 580 .get
560 } 581 }
582
561 RDFoxUtil.closeConnection(server, data) 583 RDFoxUtil.closeConnection(server, data)
584
562 answers 585 answers
563 }, 586 },
564 "Answers computation", 587 "Answers computation",
@@ -569,14 +592,15 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
569 * 592 *
570 * @note This method does not add any facts or rules to the data 593 * @note This method does not add any facts or rules to the data
571 * store. It is most useful after the execution of a query using 594 * store. It is most useful after the execution of a query using
572 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]]. 595 * [[RSAOntology.ask]].
573 * @note This method has been introduced mostly for debugging purposes.
574 * 596 *
575 * @param query query to be executed against the environment 597 * @param query query to be executed against the environment
576 * @param prefixes additional prefixes for the query. It defaults to 598 * @param prefixes additional prefixes for the query. It defaults to
577 * an empty set. 599 * an empty set.
578 * @param opts additional options to RDFox. 600 * @param opts additional options to RDFox.
579 * @return a collection of answers to the input query. 601 * @return a collection of answers to the input query.
602 *
603 * @note This method has been introduced mostly for debugging purposes.
580 */ 604 */
581 def queryDataStore( 605 def queryDataStore(
582 query: String, 606 query: String,
@@ -598,122 +622,11 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) {
598 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]] 622 * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]]
599 * for the corresponding query has been called. 623 * for the corresponding query has been called.
600 */ 624 */
601 def askUnfiltered( 625 // def askUnfiltered(
602 cq: ConjunctiveQuery 626 // cq: ConjunctiveQuery
603 ): Option[Seq[(Long, Seq[Resource])]] = { 627 // ): Option[Seq[(Long, Seq[Resource])]] = {
604 val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length) 628 // val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length)
605 queryDataStore(query, RSA.Prefixes) 629 // queryDataStore(query, RSA.Prefixes)
606 } 630 // }
607 631
608 def self(axiom: OWLSubClassOfAxiom): Set[Term] = { 632}
609 // Assuming just one role in the signature of a T5 axiom
610 val role = axiom.objectPropertyExpressionsInSignature(0)
611 if (this.confl(role).contains(role)) {
612 Set(
613 RSA("v0_" ++ axiom.hashed),
614 RSA("v1_" ++ axiom.hashed)
615 )
616 } else {
617 Set()
618 }
619 }
620
621 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
622 // TODO: we can actually use `toTriple` from `RSAAxiom`
623 val classes =
624 axiom.classesInSignature.collect(Collectors.toList()).asScala
625 val classA = classes(0)
626 val roleR = axiom
627 .objectPropertyExpressionsInSignature(0)
628 .asInstanceOf[OWLObjectProperty]
629 val classB = classes(1)
630 cycle_aux1(classA, roleR, classB)
631 }
632
633 def cycle_aux0(
634 classA: OWLClass,
635 roleR: OWLObjectProperty,
636 classB: OWLClass
637 ): Set[Term] = {
638 val conflR = this.confl(roleR)
639 val classes = ontology
640 .classesInSignature(Imports.INCLUDED)
641 .collect(Collectors.toSet())
642 .asScala
643 for {
644 classD <- classes
645 roleS <- conflR
646 classC <- classes
647 // Keeping this check for now
648 if !unsafeRoles.contains(roleS)
649 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
650 tripleDSC = RSAAxiom.hashed(classD, roleS, classC)
651 individual =
652 if (tripleARB > tripleDSC) {
653 RSA("v1_" ++ tripleDSC)
654 } else {
655 // Note that this is also the case for
656 // `tripleARB == tripleDSC`
657 RSA("v0_" ++ tripleDSC)
658 }
659 } yield individual
660 }
661
662 def cycle_aux1(
663 classA: OWLClass,
664 roleR: OWLObjectProperty,
665 classB: OWLClass
666 ): Set[Term] = {
667 val conflR = this.confl(roleR)
668 // We just need the TBox to find
669 val terms = for {
670 axiom1 <- tbox
671 if axiom1.isT5
672 // We expect only one role coming out of a T5 axiom
673 roleS <- axiom1.objectPropertyExpressionsInSignature
674 // Triples ordering is among triples involving safe roles.
675 if !unsafeRoles.contains(roleS)
676 if conflR.contains(roleS)
677 tripleARB = RSAAxiom.hashed(classA, roleR, classB)
678 tripleDSC = axiom1.hashed
679 individual =
680 if (tripleARB > tripleDSC) {
681 RSA("v1_" ++ tripleDSC)
682 } else {
683 // Note that this is also the case for
684 // `tripleARB == tripleDSC`
685 RSA("v0_" ++ tripleDSC)
686 }
687 } yield individual
688 terms to Set
689 }
690
691 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
692 this.self(axiom) | this.cycle(axiom)
693
694 /** Log normalization/approximation statistics */
695 def statistics(level: Logger.Level = Logger.DEBUG): Unit = {
696 Logger.print(
697 s"Logical axioms in original input ontology: ${original.getLogicalAxiomCount(true)}",
698 level
699 )
700 Logger.print(
701 s"Logical axioms discarded in Horn-ALCHOIQ approximation: ${normalizer.discarded}",
702 level
703 )
704 Logger.print(
705 s"Logical axioms shifted in Horn-ALCHOIQ approximation: ${normalizer.shifted}",
706 level
707 )
708 Logger.print(
709 s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology
710 .getLogicalAxiomCount(true)} (${tbox.length}/${rbox.length}/${abox.length})",
711 level
712 )
713 Logger.print(
714 s"Logical axioms discarded in RSA approximation: ${removed.length}",
715 level
716 )
717 }
718
719} // class RSAOntology
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala
new file mode 100644
index 0000000..344f0fe
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala
@@ -0,0 +1,18 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3import java.io.File
4import org.semanticweb.owlapi.model.OWLLogicalAxiom
5
6import uk.ac.ox.cs.rsacomb.ontology.Ontology
7
8/** Ontology approximation technique. */
9trait Approximation[T] {
10
11 /** Approximate an ontology.
12 *
13 * @param ontology input ontology as a list of axioms
14 * @return the approximated ontology
15 */
16 def approximate(ontology: Ontology): T
17
18}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala
new file mode 100644
index 0000000..60a88fb
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala
@@ -0,0 +1,232 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3import java.io.File
4
5import org.semanticweb.owlapi.apibinding.OWLManager
6import org.semanticweb.owlapi.model.{IRI => _, _}
7
8import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI}
9
10import scala.collection.mutable.{Set, Map}
11import scalax.collection.Graph
12import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
13import scalax.collection.GraphTraversal._
14
15import uk.ac.ox.cs.rsacomb.RSAOntology
16import uk.ac.ox.cs.rsacomb.RSAUtil
17import uk.ac.ox.cs.rsacomb.ontology.Ontology
18
19object LowerBound {
20
21 private val manager = OWLManager.createOWLOntologyManager()
22 private val factory = manager.getOWLDataFactory()
23
24}
25
26/** Approximation algorithm that mantains soundness for CQ answering.
27 *
28 * The input OWL 2 ontology is assumed to be normalized and the output
29 * ontology is guaranteed to be in RSA.
30 *
31 * The algorithm is performed in three steps:
32 * 1. the ontology is reduced to ALCHOIQ by discarding any axiom
33 * that is not in the language;
34 * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting
35 * axioms with disjunction on the rhs;
36 * 3. the ontology is approximated to RSA by manipulating its
37 * dependency graph.
38 *
39 * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]]
40 */
41class LowerBound extends Approximation[RSAOntology] {
42
43 /** Simplify conversion between Java and Scala collections */
44 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
45
46 /** Simplify conversion between OWLAPI and RDFox concepts */
47 import uk.ac.ox.cs.rsacomb.implicits.RDFox._
48
49 /** Main entry point for the approximation algorithm */
50 def approximate(ontology: Ontology): RSAOntology =
51 toRSA(
52 new Ontology(
53 ontology.axioms filter inALCHOIQ flatMap shift,
54 ontology.datafiles
55 )
56 )
57
58 /** Discards all axioms outside ALCHOIQ */
59 private def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean =
60 axiom match {
61 case a: OWLSubClassOfAxiom => {
62 val sub = a.getSubClass.getNNF
63 val sup = a.getSuperClass.getNNF
64 (sub, sup) match {
65 case (sub: OWLObjectAllValuesFrom, _) => false
66 case (sub: OWLDataAllValuesFrom, _) => false
67 case (_, sup: OWLDataAllValuesFrom) => false
68 case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 =>
69 false
70 case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 =>
71 false
72 case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 =>
73 false
74 case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 =>
75 false
76 case (sub: OWLObjectMaxCardinality, _) => false
77 case (sub: OWLDataMaxCardinality, _) => false
78 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
79 false
80 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
81 false
82 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
83 false
84 case (sub: OWLObjectHasSelf, _) => false
85 case (_, sup: OWLObjectHasSelf) => false
86 case _ => true
87 }
88 }
89 case a: OWLTransitiveObjectPropertyAxiom => false
90 case a: OWLReflexiveObjectPropertyAxiom => false
91 case a: OWLSubPropertyChainOfAxiom => false
92 case a: OWLAsymmetricObjectPropertyAxiom => false
93 case a => true
94 }
95
96 /** Shifting axioms with disjunction on the rhs.
97 *
98 * The process of shifting presenves soundness but completenes w.r.t.
99 * CQ answering is lost.
100 *
101 * @example
102 *
103 * A -> B1 u B2 u B3 .
104 *
105 * becomes
106 *
107 * A n nB1 n nB2 n nB3 -> bot .
108 * A n nB1 n nB2 -> B3 .
109 * A n nB1 n nB3 -> B2 .
110 * A n nB2 n nB3 -> B1 .
111 * nB1 n nB2 n nB3 -> nA .
112 *
113 * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to
114 * the negation of A, B1, B2, B3 respectively.
115 *
116 * @note this method maintains the normal form of the input axiom.
117 */
118 private def shift(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] =
119 axiom match {
120 case a: OWLSubClassOfAxiom => {
121 val sub = a.getSubClass.getNNF
122 val sup = a.getSuperClass.getNNF
123 sup match {
124 case sup: OWLObjectUnionOf => {
125 val body = sub.asConjunctSet.map((atom) =>
126 (atom, RSAUtil.getFreshOWLClass())
127 )
128 val head = sup.asDisjunctSet.map((atom) =>
129 (atom, RSAUtil.getFreshOWLClass())
130 )
131
132 val r1 =
133 LowerBound.factory.getOWLSubClassOfAxiom(
134 LowerBound.factory.getOWLObjectIntersectionOf(
135 (body.map(_._1) ++ head.map(_._2)): _*
136 ),
137 LowerBound.factory.getOWLNothing
138 )
139
140 val r2s =
141 for {
142 (a, na) <- head
143 hs = head.map(_._2).filterNot(_ equals na)
144 } yield LowerBound.factory.getOWLSubClassOfAxiom(
145 LowerBound.factory.getOWLObjectIntersectionOf(
146 (body.map(_._1) ++ hs): _*
147 ),
148 a
149 )
150
151 val r3s =
152 for {
153 (a, na) <- body
154 bs = body.map(_._1).filterNot(_ equals a)
155 } yield LowerBound.factory.getOWLSubClassOfAxiom(
156 LowerBound.factory.getOWLObjectIntersectionOf(
157 (bs ++ head.map(_._2)): _*
158 ),
159 na
160 )
161
162 List(r1) ++ r2s ++ r3s
163 }
164 case _ => List(axiom)
165 }
166 }
167 case _ => List(axiom)
168 }
169
170 /** Approximate a Horn-ALCHOIQ ontology to RSA
171 *
172 * This is done by gathering those axioms that prevent the ontology
173 * dependency graph from being tree-shaped, and removing them.
174 *
175 * @param ontology the set of axioms to approximate.
176 * @return the approximated RSA ontology
177 */
178 private def toRSA(ontology: Ontology): RSAOntology = {
179 /* Compute the dependency graph for the ontology */
180 val (graph, nodemap) = ontology.dependencyGraph
181
182 /* Define node colors for the graph visit */
183 sealed trait NodeColor
184 case object Unvisited extends NodeColor
185 case object Visited extends NodeColor
186 case object ToDelete extends NodeColor
187
188 /* Keep track of node colors during graph visit */
189 var color = Map.from[Resource, NodeColor](
190 graph.nodes.toOuter.map(k => (k, Unvisited))
191 )
192
193 for {
194 component <- graph.componentTraverser().map(_ to Graph)
195 edge <- component
196 .outerEdgeTraverser(component.nodes.head)
197 .withKind(BreadthFirst)
198 } yield {
199 val source = edge._1
200 val target = edge._2
201 color(source) match {
202 case Unvisited | Visited => {
203 color(target) match {
204 case Unvisited =>
205 color(source) = Visited;
206 color(target) = Visited
207 case Visited =>
208 color(source) = ToDelete
209 case ToDelete =>
210 color(source) = Visited
211 }
212 }
213 case ToDelete =>
214 }
215 }
216
217 val toDelete = color.collect { case (resource: IRI, ToDelete) =>
218 nodemap(resource.getIRI)
219 }.toList
220
221 /* Remove axioms from approximated ontology */
222 RSAOntology(ontology.axioms diff toDelete, ontology.datafiles)
223 }
224
225 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
226 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
227 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
228 // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N)
229 // val edges3 = Seq('P ~> 'O)
230 // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3)
231 //
232}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
index 254fd13..285040e 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala
@@ -20,6 +20,8 @@ import org.semanticweb.owlapi.apibinding.OWLManager
20import org.semanticweb.owlapi.model._ 20import org.semanticweb.owlapi.model._
21 21
22import uk.ac.ox.cs.rsacomb.util.Logger 22import uk.ac.ox.cs.rsacomb.util.Logger
23import uk.ac.ox.cs.rsacomb.RSAOntology
24import uk.ac.ox.cs.rsacomb.RSAUtil
23 25
24object Normalizer { 26object Normalizer {
25 27
@@ -41,12 +43,6 @@ class Normalizer() {
41 /** Simplify conversion between Java and Scala collections */ 43 /** Simplify conversion between Java and Scala collections */
42 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ 44 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
43 45
44 private var counter = -1
45 def freshOWLClass(): OWLClass = {
46 counter += 1
47 factory.getOWLClass(s"X$counter")
48 }
49
50 /** Statistics */ 46 /** Statistics */
51 var discarded = 0 47 var discarded = 0
52 var shifted = 0 48 var shifted = 0
@@ -74,7 +70,7 @@ class Normalizer() {
74 * C c D -> { C c X, X c D } 70 * C c D -> { C c X, X c D }
75 */ 71 */
76 case _ if !sub.isOWLClass && !sup.isOWLClass => { 72 case _ if !sub.isOWLClass && !sup.isOWLClass => {
77 val cls = freshOWLClass() 73 val cls = RSAUtil.getFreshOWLClass()
78 Seq( 74 Seq(
79 factory.getOWLSubClassOfAxiom(sub, cls), 75 factory.getOWLSubClassOfAxiom(sub, cls),
80 factory.getOWLSubClassOfAxiom(cls, sup) 76 factory.getOWLSubClassOfAxiom(cls, sup)
@@ -95,7 +91,7 @@ class Normalizer() {
95 if (conj.isOWLClass) 91 if (conj.isOWLClass)
96 (acc1 :+ conj, acc2) 92 (acc1 :+ conj, acc2)
97 else { 93 else {
98 val cls = freshOWLClass() 94 val cls = RSAUtil.getFreshOWLClass()
99 ( 95 (
100 acc1 :+ cls, 96 acc1 :+ cls,
101 acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls) 97 acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls)
@@ -154,7 +150,7 @@ class Normalizer() {
154 */ 150 */
155 case (sub: OWLObjectSomeValuesFrom, _) 151 case (sub: OWLObjectSomeValuesFrom, _)
156 if !sub.getFiller.isOWLClass => { 152 if !sub.getFiller.isOWLClass => {
157 val cls = freshOWLClass() 153 val cls = RSAUtil.getFreshOWLClass()
158 Seq( 154 Seq(
159 factory.getOWLSubClassOfAxiom(sub.getFiller, cls), 155 factory.getOWLSubClassOfAxiom(sub.getFiller, cls),
160 factory.getOWLSubClassOfAxiom( 156 factory.getOWLSubClassOfAxiom(
@@ -169,7 +165,7 @@ class Normalizer() {
169 */ 165 */
170 case (_, sup: OWLObjectSomeValuesFrom) 166 case (_, sup: OWLObjectSomeValuesFrom)
171 if !sup.getFiller.isOWLClass => { 167 if !sup.getFiller.isOWLClass => {
172 val cls = freshOWLClass() 168 val cls = RSAUtil.getFreshOWLClass()
173 Seq( 169 Seq(
174 factory.getOWLSubClassOfAxiom(cls, sup.getFiller), 170 factory.getOWLSubClassOfAxiom(cls, sup.getFiller),
175 factory.getOWLSubClassOfAxiom( 171 factory.getOWLSubClassOfAxiom(
@@ -314,7 +310,7 @@ class Normalizer() {
314 ) 310 )
315 case (_, sup: OWLObjectMaxCardinality) 311 case (_, sup: OWLObjectMaxCardinality)
316 if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => { 312 if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => {
317 val cls = freshOWLClass() 313 val cls = RSAUtil.getFreshOWLClass()
318 Seq( 314 Seq(
319 factory.getOWLSubClassOfAxiom(cls, sup.getFiller), 315 factory.getOWLSubClassOfAxiom(cls, sup.getFiller),
320 factory.getOWLSubClassOfAxiom( 316 factory.getOWLSubClassOfAxiom(
@@ -504,7 +500,7 @@ class Normalizer() {
504 * C(a) -> { X(a), X c C } 500 * C(a) -> { X(a), X c C }
505 */ 501 */
506 case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => { 502 case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => {
507 val cls = freshOWLClass() 503 val cls = RSAUtil.getFreshOWLClass()
508 Seq( 504 Seq(
509 factory.getOWLClassAssertionAxiom(cls, a.getIndividual), 505 factory.getOWLClassAssertionAxiom(cls, a.getIndividual),
510 factory.getOWLSubClassOfAxiom(cls, a.getClassExpression) 506 factory.getOWLSubClassOfAxiom(cls, a.getClassExpression)
@@ -548,8 +544,10 @@ class Normalizer() {
548 sub: OWLClassExpression, 544 sub: OWLClassExpression,
549 sup: OWLObjectUnionOf 545 sup: OWLObjectUnionOf
550 ): Seq[OWLLogicalAxiom] = { 546 ): Seq[OWLLogicalAxiom] = {
551 val body = sub.asConjunctSet.map((atom) => (atom, freshOWLClass())) 547 val body =
552 val head = sup.asDisjunctSet.map((atom) => (atom, freshOWLClass())) 548 sub.asConjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass()))
549 val head =
550 sup.asDisjunctSet.map((atom) => (atom, RSAUtil.getFreshOWLClass()))
553 551
554 /* Update statistics */ 552 /* Update statistics */
555 shifted += 1 553 shifted += 1
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 b892ff5..3aa3c5f 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
@@ -27,6 +27,7 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{
27 TupleTableAtom 27 TupleTableAtom
28} 28}
29import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall} 29import tech.oxfordsemantic.jrdfox.logic.expression.{Term, IRI, FunctionCall}
30import uk.ac.ox.cs.rsacomb.RSAUtil
30import uk.ac.ox.cs.rsacomb.RSAOntology 31import uk.ac.ox.cs.rsacomb.RSAOntology
31import uk.ac.ox.cs.rsacomb.suffix.{Empty, Inverse, RSASuffix} 32import uk.ac.ox.cs.rsacomb.suffix.{Empty, Inverse, RSASuffix}
32import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil} 33import uk.ac.ox.cs.rsacomb.util.{RSA, RDFoxUtil}
@@ -159,14 +160,14 @@ trait RDFoxConverter {
159 } 160 }
160 161
161 case a: OWLSubObjectPropertyOfAxiom => { 162 case a: OWLSubObjectPropertyOfAxiom => {
162 val term1 = RSAOntology.genFreshVariable() 163 val term1 = RSAUtil.genFreshVariable()
163 val body = convert(a.getSubProperty, term, term1, suffix) 164 val body = convert(a.getSubProperty, term, term1, suffix)
164 val head = convert(a.getSuperProperty, term, term1, suffix) 165 val head = convert(a.getSuperProperty, term, term1, suffix)
165 ResultR(List(Rule.create(head, body))) 166 ResultR(List(Rule.create(head, body)))
166 } 167 }
167 168
168 case a: OWLSubDataPropertyOfAxiom => { 169 case a: OWLSubDataPropertyOfAxiom => {
169 val term1 = RSAOntology.genFreshVariable() 170 val term1 = RSAUtil.genFreshVariable()
170 val body = convert(a.getSubProperty, term, term1, suffix) 171 val body = convert(a.getSubProperty, term, term1, suffix)
171 val head = convert(a.getSuperProperty, term, term1, suffix) 172 val head = convert(a.getSuperProperty, term, term1, suffix)
172 ResultR(List(Rule.create(head, body))) 173 ResultR(List(Rule.create(head, body)))
@@ -176,7 +177,7 @@ trait RDFoxConverter {
176 convert(a.asOWLSubClassOfAxiom, term, unsafe, skolem, suffix) 177 convert(a.asOWLSubClassOfAxiom, term, unsafe, skolem, suffix)
177 178
178 case a: OWLObjectPropertyRangeAxiom => { 179 case a: OWLObjectPropertyRangeAxiom => {
179 val term1 = RSAOntology.genFreshVariable() 180 val term1 = RSAUtil.genFreshVariable()
180 val (res, ext) = convert(a.getRange, term, unsafe, skolem, suffix) 181 val (res, ext) = convert(a.getRange, term, unsafe, skolem, suffix)
181 val prop = convert(a.getProperty, term1, term, suffix) 182 val prop = convert(a.getProperty, term1, term, suffix)
182 ResultR(List(Rule.create(res, prop :: ext))) 183 ResultR(List(Rule.create(res, prop :: ext)))
@@ -343,7 +344,7 @@ trait RDFoxConverter {
343 case e: OWLObjectSomeValuesFrom => { 344 case e: OWLObjectSomeValuesFrom => {
344 val cls = e.getFiller() 345 val cls = e.getFiller()
345 val role = e.getProperty() 346 val role = e.getProperty()
346 val varX = RSAOntology.genFreshVariable 347 val varX = RSAUtil.genFreshVariable
347 val (bind, term1) = skolem match { 348 val (bind, term1) = skolem match {
348 case NoSkolem => (None, varX) 349 case NoSkolem => (None, varX)
349 case c: Constant => (None, c.iri) 350 case c: Constant => (None, c.iri)
@@ -370,7 +371,7 @@ trait RDFoxConverter {
370 // Computes the result of rule skolemization. Depending on the used 371 // Computes the result of rule skolemization. Depending on the used
371 // technique it might involve the introduction of additional atoms, 372 // technique it might involve the introduction of additional atoms,
372 // and/or fresh constants and variables. 373 // and/or fresh constants and variables.
373 val varX = RSAOntology.genFreshVariable 374 val varX = RSAUtil.genFreshVariable
374 val (bind, term1) = skolem match { 375 val (bind, term1) = skolem match {
375 case NoSkolem => (None, varX) 376 case NoSkolem => (None, varX)
376 case c: Constant => (None, c.iri) 377 case c: Constant => (None, c.iri)
@@ -395,7 +396,7 @@ trait RDFoxConverter {
395 s"Class expression '$e' has cardinality restriction != 1." 396 s"Class expression '$e' has cardinality restriction != 1."
396 ) 397 )
397 val vars @ (y :: z :: _) = 398 val vars @ (y :: z :: _) =
398 Seq(RSAOntology.genFreshVariable(), RSAOntology.genFreshVariable()) 399 Seq(RSAUtil.genFreshVariable(), RSAUtil.genFreshVariable())
399 val cls = e.getFiller 400 val cls = e.getFiller
400 val role = e.getProperty 401 val role = e.getProperty
401 val (res, ext) = vars.map(convert(cls, _, unsafe, skolem, suffix)).unzip 402 val (res, ext) = vars.map(convert(cls, _, unsafe, skolem, suffix)).unzip
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
index ea87782..09bfa1e 100644
--- a/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/implicits/RSAAtom.scala
@@ -25,6 +25,7 @@ import tech.oxfordsemantic.jrdfox.logic.datalog.{
25} 25}
26import tech.oxfordsemantic.jrdfox.logic.expression.{IRI} 26import tech.oxfordsemantic.jrdfox.logic.expression.{IRI}
27 27
28import uk.ac.ox.cs.rsacomb.RSAUtil
28import uk.ac.ox.cs.rsacomb.RSAOntology 29import uk.ac.ox.cs.rsacomb.RSAOntology
29import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth} 30import uk.ac.ox.cs.rsacomb.suffix.{RSASuffix, Nth}
30import uk.ac.ox.cs.rsacomb.util.RDFoxUtil 31import uk.ac.ox.cs.rsacomb.util.RDFoxUtil
@@ -94,7 +95,7 @@ object RSAAtom {
94 if (isRDF) { 95 if (isRDF) {
95 (None, List(atom)) 96 (None, List(atom))
96 } else { 97 } else {
97 val varS = RSAOntology.genFreshVariable() 98 val varS = RSAUtil.genFreshVariable()
98 val skolem = RDFoxUtil.skolem(name, (args :+ varS): _*) 99 val skolem = RDFoxUtil.skolem(name, (args :+ varS): _*)
99 val atom = TupleTableAtom.rdf(varS, IRI.RDF_TYPE, name) 100 val atom = TupleTableAtom.rdf(varS, IRI.RDF_TYPE, name)
100 val atoms = args.zipWithIndex 101 val atoms = args.zipWithIndex
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
new file mode 100644
index 0000000..ba44605
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontology/Ontology.scala
@@ -0,0 +1,301 @@
1/*
2 * Copyright 2020, 2021 KRR Oxford
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package uk.ac.ox.cs.rsacomb.ontology
18
19import java.io.File
20import java.util.stream.Collectors
21
22import scala.collection.mutable.Map
23import scala.collection.JavaConverters._
24import scalax.collection.Graph
25import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
26
27import org.semanticweb.owlapi.model.parameters.Imports
28import org.semanticweb.owlapi.apibinding.OWLManager
29import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom}
30import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression}
31import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory
32import tech.oxfordsemantic.jrdfox.logic.datalog.Rule
33import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable}
34
35import uk.ac.ox.cs.rsacomb.approximation.Approximation
36import uk.ac.ox.cs.rsacomb.converter._
37import uk.ac.ox.cs.rsacomb.suffix._
38import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA}
39
40import uk.ac.ox.cs.rsacomb.RSAUtil
41
42object Ontology {
43
44 /** Simplify conversion between Java and Scala collections */
45 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
46
47 /** Type wrapper representing a dependency graph for the ontology.
48 *
49 * The graph is returned along with a map associating each node (IRI
50 * string of the resource), with the corresponding axiom in the
51 * original TBox.
52 */
53 type DependencyGraph = (Graph[Resource, DiEdge], Map[String, OWLAxiom])
54
55 /** Manager instance to interface with OWLAPI
56 *
57 * TODO: turn this into an implicit class parameter.
58 */
59 val manager = OWLManager.createOWLOntologyManager()
60
61 /** Compute the RSA dependency graph for a set of axioms
62 *
63 * @param axioms set of input axioms (TBox) to build the dependency
64 * graph.
65 * @param datafiles data (ABox) to build the dependency graph.
66 * @param unsafe list of unsafe roles in the TBox.
67 *
68 * @return a tuple containing the dependency graph and a map between
69 * the newly introduced constants and the corresponding input axioms.
70 *
71 * @note no check on the ontology language is performed since the
72 * construction of the dependency graph is computed regardless. The
73 * input axioms are assumed to be normalized.
74 */
75 def dependencyGraph(
76 axioms: List[OWLLogicalAxiom],
77 datafiles: List[File],
78 unsafe: List[OWLObjectPropertyExpression]
79 ): DependencyGraph = {
80
81 var nodemap = Map.empty[String, OWLAxiom]
82
83 /* Create custom converter */
84 object RSAConverter extends RDFoxConverter {
85
86 import org.semanticweb.owlapi.model.{
87 OWLClassExpression,
88 OWLObjectSomeValuesFrom,
89 OWLDataSomeValuesFrom
90 }
91
92 override def convert(
93 expr: OWLClassExpression,
94 term: Term,
95 unsafe: List[OWLObjectPropertyExpression],
96 skolem: SkolemStrategy,
97 suffix: RSASuffix
98 ): Shards =
99 (expr, skolem) match {
100
101 case (e: OWLObjectSomeValuesFrom, c: Constant) => {
102 nodemap.update(c.iri.getIRI, c.axiom)
103 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
104 if (unsafe contains e.getProperty)
105 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
106 else
107 (RSA.PE(term, c.iri) :: res, ext)
108 }
109
110 case (e: OWLDataSomeValuesFrom, c: Constant) => {
111 nodemap.update(c.iri.getIRI, c.axiom)
112 val (res, ext) = super.convert(e, term, unsafe, skolem, suffix)
113 if (unsafe contains e.getProperty)
114 (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext)
115 else
116 (RSA.PE(term, c.iri) :: res, ext)
117 }
118
119 case _ => super.convert(expr, term, unsafe, skolem, suffix)
120 }
121 }
122
123 /* Ontology convertion into LP rules */
124 val term = RSAUtil.genFreshVariable()
125 val result = axioms.map(a =>
126 RSAConverter.convert(a, term, unsafe, new Constant(a), Empty)
127 )
128
129 val datalog = result.unzip
130 val facts = datalog._1.flatten
131 var rules = datalog._2.flatten
132
133 /* Open connection with RDFox */
134 val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph")
135
136 /* Add additional built-in rules */
137 val varX = Variable.create("X")
138 val varY = Variable.create("Y")
139 rules = Rule.create(
140 RSA.E(varX, varY),
141 RSA.PE(varX, varY),
142 RSA.U(varX),
143 RSA.U(varY)
144 ) :: rules
145 /* Load facts and rules from ontology */
146 RDFoxUtil.addFacts(data, facts)
147 RDFoxUtil.addRules(data, rules)
148 /* Load data files */
149 RDFoxUtil.addData(data, datafiles: _*)
150
151 /* Build the graph */
152 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
153 val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get
154 var edges: Seq[DiEdge[Resource]] =
155 answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 }
156 val graph = Graph(edges: _*)
157
158 /* Close connection to RDFox */
159 RDFoxUtil.closeConnection(server, data)
160
161 (graph, nodemap)
162 }
163
164 def apply(axioms: List[OWLLogicalAxiom], datafiles: List[File]): Ontology =
165 new Ontology(axioms, datafiles)
166
167 def apply(ontology: OWLOntology, datafiles: List[File]): Ontology = {
168
169 /** TBox axioms */
170 var tbox: List[OWLLogicalAxiom] =
171 ontology
172 .tboxAxioms(Imports.INCLUDED)
173 .collect(Collectors.toList())
174 .collect { case a: OWLLogicalAxiom => a }
175
176 /** RBox axioms */
177 var rbox: List[OWLLogicalAxiom] =
178 ontology
179 .rboxAxioms(Imports.INCLUDED)
180 .collect(Collectors.toList())
181 .collect { case a: OWLLogicalAxiom => a }
182
183 /** ABox axioms
184 *
185 * @note this represents only the set of assertions contained in the
186 * ontology file. Data files specified in `datafiles` are directly
187 * imported in RDFox due to performance issues when trying to import
188 * large data files via OWLAPI.
189 */
190 var abox: List[OWLLogicalAxiom] =
191 ontology
192 .aboxAxioms(Imports.INCLUDED)
193 .collect(Collectors.toList())
194 .collect { case a: OWLLogicalAxiom => a }
195
196 Ontology(abox ::: tbox ::: rbox, datafiles)
197 }
198
199 def apply(ontofile: File, datafiles: List[File]): Ontology = {
200 val ontology = manager.loadOntologyFromOntologyDocument(ontofile)
201 Ontology(ontology, datafiles)
202 }
203
204}
205
206/** A wrapper for a generic OWL2 ontology
207 *
208 * @param axioms list of axioms (roughly) corresponding to the TBox.
209 * @param datafiles files containing ABox data.
210 */
211class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) {
212
213 /** Extend OWLAxiom functionalities */
214 import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
215
216 /** Simplify conversion between Java and Scala collections */
217 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
218
219 println(s"Axioms: ${axioms.length}")
220
221 /** OWLOntology based on input axioms
222 *
223 * This is mainly used to instantiate a new reasoner to be used in
224 * the computation of unsafe roles.
225 */
226 protected val ontology: OWLOntology =
227 Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava)
228
229 /** OWLAPI internal reasoner for ontology */
230 protected val reasoner =
231 (new StructuralReasonerFactory()).createReasoner(ontology)
232
233 /** Unsafe roles in the ontology
234 *
235 * Unsafety conditions are the following:
236 *
237 * 1) For all roles r1 appearing in an axiom of type T5, r1 is unsafe
238 * if there exists a role r2 (different from top) appearing in an
239 * axiom of type T3 and r1 is a subproperty of the inverse of r2.
240 *
241 * 2) For all roles p1 appearing in an axiom of type T5, p1 is unsafe
242 * if there exists a role p2 appearing in an axiom of type T4 and
243 * p1 is a subproperty of either p2 or the inverse of p2.
244 */
245 lazy val unsafe: List[OWLObjectPropertyExpression] = {
246
247 /* Checking for unsafety condition (1) */
248 val unsafe1 = for {
249 axiom <- axioms
250 if axiom.isT5
251 role1 <- axiom.objectPropertyExpressionsInSignature
252 roleSuper = role1 +: reasoner.superObjectProperties(role1)
253 axiom <- axioms
254 if axiom.isT3 && !axiom.isT3top
255 role2 <- axiom.objectPropertyExpressionsInSignature
256 if roleSuper contains role2.getInverseProperty
257 } yield role1
258
259 /* Checking for unsafety condition (2) */
260 val unsafe2 = for {
261 axiom <- axioms
262 if axiom.isT5
263 role1 <- axiom.objectPropertyExpressionsInSignature
264 roleSuper = role1 +: reasoner.superObjectProperties(role1)
265 axiom <- axioms
266 if axiom.isT4
267 role2 <- axiom.objectPropertyExpressionsInSignature
268 if roleSuper.contains(role2) ||
269 roleSuper.contains(role2.getInverseProperty)
270 } yield role1
271
272 unsafe1 ++ unsafe2
273 }
274
275 /** Compute the dependency graph for the ontology */
276 lazy val dependencyGraph: Ontology.DependencyGraph =
277 Ontology.dependencyGraph(axioms, datafiles, this.unsafe)
278
279 /** RSA check */
280 lazy val isRSA: Boolean = ???
281
282 /** Normalize the ontology according to the given normalizer
283 *
284 * @param normalizer the normalization technique to be used.
285 * @return a new normalized [[Ontology]].
286 */
287 def normalize(normalizer: Normalizer): Ontology =
288 new Ontology(
289 axioms flatMap normalizer.normalize,
290 datafiles
291 )
292
293 /** Approximate the ontology according to the given approximation
294 * technique.
295 *
296 * @param approximation the approximation to be used on the ontology.
297 * @return the result of the approximation.
298 */
299 def approximate[T](approximation: Approximation[T]): T =
300 approximation.approximate(this)
301}