diff options
author | Federico Igne <git@federicoigne.com> | 2021-07-22 08:29:11 +0100 |
---|---|---|
committer | Federico Igne <git@federicoigne.com> | 2021-07-22 08:29:11 +0100 |
commit | 18ddefc7c9e9cfaca027a054495325737ae6b9e6 (patch) | |
tree | 5c06d23c6462d90d3755fb544d36a88c76302a1d | |
parent | 53646646f924887768688794aee46874ed194673 (diff) | |
download | RSAComb-18ddefc7c9e9cfaca027a054495325737ae6b9e6.tar.gz RSAComb-18ddefc7c9e9cfaca027a054495325737ae6b9e6.zip |
Move some generic commands from RSAOntology to Ontology
3 files changed, 248 insertions, 259 deletions
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 9902fcd..630d2a0 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
@@ -63,6 +63,7 @@ import uk.ac.ox.cs.rsacomb.suffix._ | |||
63 | import uk.ac.ox.cs.rsacomb.sparql._ | 63 | import uk.ac.ox.cs.rsacomb.sparql._ |
64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
65 | import uk.ac.ox.cs.rsacomb.util.Logger | 65 | import uk.ac.ox.cs.rsacomb.util.Logger |
66 | import uk.ac.ox.cs.rsacomb.ontology.Ontology | ||
66 | 67 | ||
67 | object RSAUtil { | 68 | object RSAUtil { |
68 | 69 | ||
@@ -94,82 +95,94 @@ object RSAOntology { | |||
94 | 95 | ||
95 | /** Manager instance to interface with OWLAPI */ | 96 | /** Manager instance to interface with OWLAPI */ |
96 | val manager = OWLManager.createOWLOntologyManager() | 97 | val manager = OWLManager.createOWLOntologyManager() |
97 | val factory = manager.getOWLDataFactory() | ||
98 | 98 | ||
99 | /** Name of the RDFox data store used for CQ answering */ | 99 | /** Name of the RDFox data store used for CQ answering */ |
100 | private val DataStore = "answer_computation" | 100 | private val DataStore = "answer_computation" |
101 | 101 | ||
102 | /** Filtering program for a given query | ||
103 | * | ||
104 | * @param query the query to derive the filtering program | ||
105 | * @return the filtering program for the given query | ||
106 | */ | ||
107 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = | ||
108 | Logger.timed( | ||
109 | FilteringProgram(FilterType.REVISED)(query), | ||
110 | "Generating filtering program", | ||
111 | Logger.DEBUG | ||
112 | ) | ||
113 | |||
102 | def apply( | 114 | def apply( |
103 | axioms: List[OWLLogicalAxiom], | 115 | axioms: List[OWLLogicalAxiom], |
104 | datafiles: List[File] | 116 | datafiles: List[File] |
105 | ): RSAOntology = new RSAOntology(axioms, datafiles: _*) | 117 | ): RSAOntology = new RSAOntology(axioms, datafiles) |
106 | 118 | ||
107 | def apply( | 119 | // def apply( |
108 | ontofile: File, | 120 | // ontofile: File, |
109 | datafiles: List[File], | 121 | // datafiles: List[File], |
110 | approx: Option[Approximation] | 122 | // approx: Option[Approximation] |
111 | ): RSAOntology = { | 123 | // ): RSAOntology = { |
112 | val ontology = manager.loadOntologyFromOntologyDocument(ontofile) | 124 | // val ontology = manager.loadOntologyFromOntologyDocument(ontofile) |
113 | RSAOntology(ontology, datafiles, approx) | 125 | // RSAOntology(ontology, datafiles, approx) |
114 | } | 126 | // } |
115 | 127 | ||
116 | def apply( | 128 | // def apply( |
117 | ontology: OWLOntology, | 129 | // ontology: OWLOntology, |
118 | datafiles: List[File], | 130 | // datafiles: List[File], |
119 | approx: Option[Approximation] | 131 | // approx: Option[Approximation] |
120 | ): RSAOntology = { | 132 | // ): RSAOntology = { |
121 | val normalizer = new Normalizer() | 133 | // val normalizer = new Normalizer() |
122 | 134 | ||
123 | /** TBox axioms */ | 135 | // /** TBox axioms */ |
124 | var tbox: List[OWLLogicalAxiom] = | 136 | // var tbox: List[OWLLogicalAxiom] = |
125 | ontology | 137 | // ontology |
126 | .tboxAxioms(Imports.INCLUDED) | 138 | // .tboxAxioms(Imports.INCLUDED) |
127 | .collect(Collectors.toList()) | 139 | // .collect(Collectors.toList()) |
128 | .collect { case a: OWLLogicalAxiom => a } | 140 | // .collect { case a: OWLLogicalAxiom => a } |
129 | .flatMap(normalizer.normalize) | 141 | // .flatMap(normalizer.normalize) |
130 | 142 | ||
131 | /** RBox axioms */ | 143 | // /** RBox axioms */ |
132 | var rbox: List[OWLLogicalAxiom] = | 144 | // var rbox: List[OWLLogicalAxiom] = |
133 | ontology | 145 | // ontology |
134 | .rboxAxioms(Imports.INCLUDED) | 146 | // .rboxAxioms(Imports.INCLUDED) |
135 | .collect(Collectors.toList()) | 147 | // .collect(Collectors.toList()) |
136 | .collect { case a: OWLLogicalAxiom => a } | 148 | // .collect { case a: OWLLogicalAxiom => a } |
137 | .flatMap(normalizer.normalize) | 149 | // .flatMap(normalizer.normalize) |
138 | 150 | ||
139 | /** ABox axioms | 151 | // /** ABox axioms |
140 | * | 152 | // * |
141 | * @note this represents only the set of assertions contained in the | 153 | // * @note this represents only the set of assertions contained in the |
142 | * ontology file. Data files specified in `datafiles` are directly | 154 | // * ontology file. Data files specified in `datafiles` are directly |
143 | * imported in RDFox due to performance issues when trying to import | 155 | // * imported in RDFox due to performance issues when trying to import |
144 | * large data files via OWLAPI. | 156 | // * large data files via OWLAPI. |
145 | */ | 157 | // */ |
146 | var abox: List[OWLLogicalAxiom] = | 158 | // var abox: List[OWLLogicalAxiom] = |
147 | ontology | 159 | // ontology |
148 | .aboxAxioms(Imports.INCLUDED) | 160 | // .aboxAxioms(Imports.INCLUDED) |
149 | .collect(Collectors.toList()) | 161 | // .collect(Collectors.toList()) |
150 | .collect { case a: OWLLogicalAxiom => a } | 162 | // .collect { case a: OWLLogicalAxiom => a } |
151 | .flatMap(normalizer.normalize) | 163 | // .flatMap(normalizer.normalize) |
152 | 164 | ||
153 | /** Collection of logical axioms in the input ontology */ | 165 | // /** Collection of logical axioms in the input ontology */ |
154 | var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | 166 | // var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox |
155 | 167 | ||
156 | new RSAOntology( | 168 | // new RSAOntology( |
157 | approx match { | 169 | // approx match { |
158 | case Some(a) => a.approximate(axioms, datafiles) | 170 | // case Some(a) => a.approximate(axioms, datafiles) |
159 | case None => axioms | 171 | // case None => axioms |
160 | }, | 172 | // }, |
161 | datafiles: _* | 173 | // datafiles: _* |
162 | ) | 174 | // ) |
163 | } | 175 | // } |
164 | 176 | ||
165 | } | 177 | } |
166 | 178 | ||
167 | /** Wrapper class for an ontology in RSA | 179 | /** A wrapper for an RSA ontology |
168 | * | 180 | * |
169 | * @param ontology the input OWL2 ontology. | 181 | * @param ontology the input OWL2 ontology. |
170 | * @param datafiles additinal data (treated as part of the ABox) | 182 | * @param datafiles additinal data (treated as part of the ABox) |
171 | */ | 183 | */ |
172 | class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | 184 | class RSAOntology(axioms: List[OWLLogicalAxiom], datafiles: List[File]) |
185 | extends Ontology(axioms, datafiles) { | ||
173 | 186 | ||
174 | /** Simplify conversion between OWLAPI and RDFox concepts */ | 187 | /** Simplify conversion between OWLAPI and RDFox concepts */ |
175 | import implicits.RDFox._ | 188 | import implicits.RDFox._ |
@@ -179,34 +192,26 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
179 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | 192 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ |
180 | 193 | ||
181 | /** Set of axioms removed during the approximation to RSA */ | 194 | /** Set of axioms removed during the approximation to RSA */ |
182 | private var removed: Seq[OWLAxiom] = Seq.empty | 195 | //private var removed: Seq[OWLAxiom] = Seq.empty |
183 | |||
184 | /** Normalized Horn-ALCHOIQ ontology */ | ||
185 | val ontology = | ||
186 | RSAOntology.manager.createOntology((axioms: List[OWLAxiom]).asJava) | ||
187 | |||
188 | /** OWLAPI internal reasoner instantiated over the approximated ontology */ | ||
189 | private val reasoner = | ||
190 | (new StructuralReasonerFactory()).createReasoner(ontology) | ||
191 | 196 | ||
192 | /** Retrieve individuals/literals in the ontology */ | 197 | /** Retrieve individuals/literals in the ontology */ |
193 | val individuals: List[IRI] = | 198 | private val individuals: List[IRI] = |
194 | ontology | 199 | ontology |
195 | .getIndividualsInSignature() | 200 | .getIndividualsInSignature() |
196 | .asScala | 201 | .asScala |
197 | .map(_.getIRI) | 202 | .map(_.getIRI) |
198 | .map(implicits.RDFox.owlapiToRdfoxIri) | 203 | .map(implicits.RDFox.owlapiToRdfoxIri) |
199 | .toList | 204 | .toList |
200 | val literals: List[Literal] = | 205 | private val literals: List[Literal] = |
201 | axioms | 206 | axioms |
202 | .collect { case a: OWLDataPropertyAssertionAxiom => a } | 207 | .collect { case a: OWLDataPropertyAssertionAxiom => a } |
203 | .map(_.getObject) | 208 | .map(_.getObject) |
204 | .map(implicits.RDFox.owlapiToRdfoxLiteral) | 209 | .map(implicits.RDFox.owlapiToRdfoxLiteral) |
205 | 210 | ||
206 | /** Retrieve concepts/roles in the ontology */ | 211 | /** Retrieve concepts/roles in the ontology */ |
207 | val concepts: List[OWLClass] = | 212 | private val concepts: List[OWLClass] = |
208 | ontology.getClassesInSignature().asScala.toList | 213 | ontology.getClassesInSignature().asScala.toList |
209 | val roles: List[OWLObjectPropertyExpression] = | 214 | private val roles: List[OWLObjectPropertyExpression] = |
210 | axioms | 215 | axioms |
211 | .flatMap(_.objectPropertyExpressionsInSignature) | 216 | .flatMap(_.objectPropertyExpressionsInSignature) |
212 | .distinct | 217 | .distinct |
@@ -223,36 +228,36 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
223 | * if there exists a role p2 appearing in an axiom of type T4 and | 228 | * if there exists a role p2 appearing in an axiom of type T4 and |
224 | * p1 is a subproperty of either p2 or the inverse of p2. | 229 | * p1 is a subproperty of either p2 or the inverse of p2. |
225 | */ | 230 | */ |
226 | val unsafeRoles: List[OWLObjectPropertyExpression] = { | 231 | // val unsafeRoles: List[OWLObjectPropertyExpression] = { |
227 | 232 | ||
228 | /* Checking for unsafety condition (1) */ | 233 | // /* Checking for unsafety condition (1) */ |
229 | val unsafe1 = for { | 234 | // val unsafe1 = for { |
230 | axiom <- axioms | 235 | // axiom <- axioms |
231 | if axiom.isT5 | 236 | // if axiom.isT5 |
232 | role1 <- axiom.objectPropertyExpressionsInSignature | 237 | // role1 <- axiom.objectPropertyExpressionsInSignature |
233 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 238 | // roleSuper = role1 +: reasoner.superObjectProperties(role1) |
234 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 239 | // roleSuperInv = roleSuper.map(_.getInverseProperty) |
235 | axiom <- axioms | 240 | // axiom <- axioms |
236 | if axiom.isT3 && !axiom.isT3top | 241 | // if axiom.isT3 && !axiom.isT3top |
237 | role2 <- axiom.objectPropertyExpressionsInSignature | 242 | // role2 <- axiom.objectPropertyExpressionsInSignature |
238 | if roleSuperInv contains role2 | 243 | // if roleSuperInv contains role2 |
239 | } yield role1 | 244 | // } yield role1 |
240 | 245 | ||
241 | /* Checking for unsafety condition (2) */ | 246 | // /* Checking for unsafety condition (2) */ |
242 | val unsafe2 = for { | 247 | // val unsafe2 = for { |
243 | axiom <- axioms | 248 | // axiom <- axioms |
244 | if axiom.isT5 | 249 | // if axiom.isT5 |
245 | role1 <- axiom.objectPropertyExpressionsInSignature | 250 | // role1 <- axiom.objectPropertyExpressionsInSignature |
246 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 251 | // roleSuper = role1 +: reasoner.superObjectProperties(role1) |
247 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 252 | // roleSuperInv = roleSuper.map(_.getInverseProperty) |
248 | axiom <- axioms | 253 | // axiom <- axioms |
249 | if axiom.isT4 | 254 | // if axiom.isT4 |
250 | role2 <- axiom.objectPropertyExpressionsInSignature | 255 | // role2 <- axiom.objectPropertyExpressionsInSignature |
251 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | 256 | // if roleSuper.contains(role2) || roleSuperInv.contains(role2) |
252 | } yield role1 | 257 | // } yield role1 |
253 | 258 | ||
254 | unsafe1 ++ unsafe2 | 259 | // unsafe1 ++ unsafe2 |
255 | } | 260 | // } |
256 | 261 | ||
257 | /** Approximate a Horn-ALCHOIQ ontology to RSA | 262 | /** Approximate a Horn-ALCHOIQ ontology to RSA |
258 | * | 263 | * |
@@ -396,31 +401,27 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
396 | ) | 401 | ) |
397 | } | 402 | } |
398 | 403 | ||
404 | /** Canonical model of the ontology */ | ||
399 | lazy val canonicalModel = Logger.timed( | 405 | lazy val canonicalModel = Logger.timed( |
400 | new CanonicalModel(this), | 406 | new CanonicalModel(this), |
401 | "Generating canonical model program", | 407 | "Generating canonical model program", |
402 | Logger.DEBUG | 408 | Logger.DEBUG |
403 | ) | 409 | ) |
404 | 410 | ||
405 | def filteringProgram(query: ConjunctiveQuery): FilteringProgram = | 411 | /** Computes all roles conflicting with a given role |
406 | Logger.timed( | 412 | * |
407 | FilteringProgram(FilterType.REVISED)(query), | 413 | * @param role a role (object property expression). |
408 | "Generating filtering program", | 414 | * @return a set of roles conflicting with `role`. |
409 | Logger.DEBUG | 415 | */ |
410 | ) | ||
411 | |||
412 | def confl( | 416 | def confl( |
413 | role: OWLObjectPropertyExpression | 417 | role: OWLObjectPropertyExpression |
414 | ): Set[OWLObjectPropertyExpression] = { | 418 | ): Set[OWLObjectPropertyExpression] = { |
415 | 419 | reasoner | |
416 | val invSuperRoles = reasoner | ||
417 | .superObjectProperties(role) | 420 | .superObjectProperties(role) |
418 | .collect(Collectors.toSet()) | 421 | .collect(Collectors.toSet()) |
419 | .asScala | 422 | .asScala |
420 | .addOne(role) | 423 | .addOne(role) |
421 | .map(_.getInverseProperty) | 424 | .map(_.getInverseProperty) |
422 | |||
423 | invSuperRoles | ||
424 | .flatMap(x => | 425 | .flatMap(x => |
425 | reasoner | 426 | reasoner |
426 | .subObjectProperties(x) | 427 | .subObjectProperties(x) |
@@ -432,6 +433,77 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
432 | .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) | 433 | .filterNot(_.getInverseProperty.isOWLTopObjectProperty()) |
433 | } | 434 | } |
434 | 435 | ||
436 | /** Selfloop detection for a given axiom | ||
437 | * | ||
438 | * @param axiom an axiom of type [[OWLSubClassOfAxiom]] | ||
439 | * @return unfold set for the axiom | ||
440 | */ | ||
441 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
442 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
443 | if (this.confl(role).contains(role)) { | ||
444 | Set(RSA("v0_" ++ axiom.hashed), RSA("v1_" ++ axiom.hashed)) | ||
445 | } else { | ||
446 | Set() | ||
447 | } | ||
448 | } | ||
449 | |||
450 | /** Cycle detection for a give axiom | ||
451 | * | ||
452 | * @param axiom an axiom of type [[OWLSubClassOfAxiom]] | ||
453 | * @return unfold set for the axiom | ||
454 | * | ||
455 | * @todo we can actually use `toTriple` from `RSAAxiom` to get the | ||
456 | * classes and the role for a given axiom | ||
457 | */ | ||
458 | def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
459 | val classes = | ||
460 | axiom.classesInSignature.collect(Collectors.toList()).asScala | ||
461 | val classA = classes(0) | ||
462 | val roleR = axiom | ||
463 | .objectPropertyExpressionsInSignature(0) | ||
464 | .asInstanceOf[OWLObjectProperty] | ||
465 | val classB = classes(1) | ||
466 | cycle_aux(classA, roleR, classB) | ||
467 | } | ||
468 | |||
469 | /** Auxiliary function for [[RSAOntology.cycle]] */ | ||
470 | private def cycle_aux( | ||
471 | classA: OWLClass, | ||
472 | roleR: OWLObjectProperty, | ||
473 | classB: OWLClass | ||
474 | ): Set[Term] = { | ||
475 | val conflR = this.confl(roleR) | ||
476 | // TODO: technically we just need the TBox here | ||
477 | val terms = for { | ||
478 | axiom1 <- axioms | ||
479 | if axiom1.isT5 | ||
480 | // We expect only one role coming out of a T5 axiom | ||
481 | roleS <- axiom1.objectPropertyExpressionsInSignature | ||
482 | // Triples ordering is among triples involving safe roles. | ||
483 | if !unsafe.contains(roleS) | ||
484 | if conflR.contains(roleS) | ||
485 | tripleARB = RSAAxiom.hashed(classA, roleR, classB) | ||
486 | tripleDSC = axiom1.hashed | ||
487 | individual = | ||
488 | if (tripleARB > tripleDSC) { | ||
489 | RSA("v1_" ++ tripleDSC) | ||
490 | } else { | ||
491 | // Note that this is also the case for | ||
492 | // `tripleARB == tripleDSC` | ||
493 | RSA("v0_" ++ tripleDSC) | ||
494 | } | ||
495 | } yield individual | ||
496 | terms to Set | ||
497 | } | ||
498 | |||
499 | /** Returns unfold set for self-loop and cycle for the input axiom | ||
500 | * | ||
501 | * @param axiom an axiom of type [[OWLSubClassOfAxiom]] | ||
502 | * @return unfold set for the axiom | ||
503 | */ | ||
504 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | ||
505 | this.self(axiom) | this.cycle(axiom) | ||
506 | |||
435 | /** Returns the answers to a query | 507 | /** Returns the answers to a query |
436 | * | 508 | * |
437 | * @param query query to execute | 509 | * @param query query to execute |
@@ -439,10 +511,9 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
439 | */ | 511 | */ |
440 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed( | 512 | def ask(query: ConjunctiveQuery): ConjunctiveQueryAnswers = Logger.timed( |
441 | { | 513 | { |
442 | import implicits.JavaCollections._ | ||
443 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) | 514 | val (server, data) = RDFoxUtil.openConnection(RSAOntology.DataStore) |
444 | val canon = this.canonicalModel | 515 | val canon = this.canonicalModel |
445 | val filter = this.filteringProgram(query) | 516 | val filter = RSAOntology.filteringProgram(query) |
446 | 517 | ||
447 | /* Upload data from data file */ | 518 | /* Upload data from data file */ |
448 | RDFoxUtil.addData(data, datafiles: _*) | 519 | RDFoxUtil.addData(data, datafiles: _*) |
@@ -460,12 +531,15 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
460 | new java.util.HashMap[String, String] | 531 | new java.util.HashMap[String, String] |
461 | ) | 532 | ) |
462 | 533 | ||
534 | /* Add canonical model */ | ||
463 | Logger print s"Canonical model rules: ${canon.rules.length}" | 535 | Logger print s"Canonical model rules: ${canon.rules.length}" |
464 | RDFoxUtil.addRules(data, canon.rules) | 536 | RDFoxUtil.addRules(data, canon.rules) |
465 | 537 | ||
466 | Logger print s"Canonical model facts: ${canon.facts.length}" | 538 | Logger print s"Canonical model facts: ${canon.facts.length}" |
467 | RDFoxUtil.addFacts(data, canon.facts) | 539 | RDFoxUtil.addFacts(data, canon.facts) |
468 | 540 | ||
541 | RDFoxUtil printStatisticsFor data | ||
542 | |||
469 | //{ | 543 | //{ |
470 | // import java.io.{PrintStream, FileOutputStream, File} | 544 | // import java.io.{PrintStream, FileOutputStream, File} |
471 | // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog")) | 545 | // val rules1 = new FileOutputStream(new File("rules1-lubm200.dlog")) |
@@ -475,16 +549,13 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
475 | // rules2.print(filter.rules.mkString("\n")) | 549 | // rules2.print(filter.rules.mkString("\n")) |
476 | //} | 550 | //} |
477 | 551 | ||
478 | //canon.facts.foreach(println) | 552 | /* Add filtering program */ |
479 | //filter.rules.foreach(println) | ||
480 | |||
481 | RDFoxUtil printStatisticsFor data | ||
482 | |||
483 | Logger print s"Filtering program rules: ${filter.rules.length}" | 553 | Logger print s"Filtering program rules: ${filter.rules.length}" |
484 | RDFoxUtil.addRules(data, filter.rules) | 554 | RDFoxUtil.addRules(data, filter.rules) |
485 | 555 | ||
486 | RDFoxUtil printStatisticsFor data | 556 | RDFoxUtil printStatisticsFor data |
487 | 557 | ||
558 | /* Gather answers to the query */ | ||
488 | val answers = { | 559 | val answers = { |
489 | val ans = filter.answerQuery | 560 | val ans = filter.answerQuery |
490 | RDFoxUtil | 561 | RDFoxUtil |
@@ -492,7 +563,9 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
492 | .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) | 563 | .map(new ConjunctiveQueryAnswers(query.bcq, query.variables, _)) |
493 | .get | 564 | .get |
494 | } | 565 | } |
566 | |||
495 | RDFoxUtil.closeConnection(server, data) | 567 | RDFoxUtil.closeConnection(server, data) |
568 | |||
496 | answers | 569 | answers |
497 | }, | 570 | }, |
498 | "Answers computation", | 571 | "Answers computation", |
@@ -503,14 +576,15 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
503 | * | 576 | * |
504 | * @note This method does not add any facts or rules to the data | 577 | * @note This method does not add any facts or rules to the data |
505 | * store. It is most useful after the execution of a query using | 578 | * store. It is most useful after the execution of a query using |
506 | * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]]. | 579 | * [[RSAOntology.ask]]. |
507 | * @note This method has been introduced mostly for debugging purposes. | ||
508 | * | 580 | * |
509 | * @param query query to be executed against the environment | 581 | * @param query query to be executed against the environment |
510 | * @param prefixes additional prefixes for the query. It defaults to | 582 | * @param prefixes additional prefixes for the query. It defaults to |
511 | * an empty set. | 583 | * an empty set. |
512 | * @param opts additional options to RDFox. | 584 | * @param opts additional options to RDFox. |
513 | * @return a collection of answers to the input query. | 585 | * @return a collection of answers to the input query. |
586 | * | ||
587 | * @note This method has been introduced mostly for debugging purposes. | ||
514 | */ | 588 | */ |
515 | def queryDataStore( | 589 | def queryDataStore( |
516 | query: String, | 590 | query: String, |
@@ -532,122 +606,11 @@ class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { | |||
532 | * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]] | 606 | * [[uk.ac.ox.cs.rsacomb.RSAOntology.ask RSAOntology.ask]] |
533 | * for the corresponding query has been called. | 607 | * for the corresponding query has been called. |
534 | */ | 608 | */ |
535 | def askUnfiltered( | 609 | // def askUnfiltered( |
536 | cq: ConjunctiveQuery | 610 | // cq: ConjunctiveQuery |
537 | ): Option[Seq[(Long, Seq[Resource])]] = { | 611 | // ): Option[Seq[(Long, Seq[Resource])]] = { |
538 | val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length) | 612 | // val query = RDFoxUtil.buildDescriptionQuery("QM", cq.variables.length) |
539 | queryDataStore(query, RSA.Prefixes) | 613 | // queryDataStore(query, RSA.Prefixes) |
540 | } | ||
541 | |||
542 | def self(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
543 | // Assuming just one role in the signature of a T5 axiom | ||
544 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
545 | if (this.confl(role).contains(role)) { | ||
546 | Set( | ||
547 | RSA("v0_" ++ axiom.hashed), | ||
548 | RSA("v1_" ++ axiom.hashed) | ||
549 | ) | ||
550 | } else { | ||
551 | Set() | ||
552 | } | ||
553 | } | ||
554 | |||
555 | def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { | ||
556 | // TODO: we can actually use `toTriple` from `RSAAxiom` | ||
557 | val classes = | ||
558 | axiom.classesInSignature.collect(Collectors.toList()).asScala | ||
559 | val classA = classes(0) | ||
560 | val roleR = axiom | ||
561 | .objectPropertyExpressionsInSignature(0) | ||
562 | .asInstanceOf[OWLObjectProperty] | ||
563 | val classB = classes(1) | ||
564 | cycle_aux1(classA, roleR, classB) | ||
565 | } | ||
566 | |||
567 | def cycle_aux0( | ||
568 | classA: OWLClass, | ||
569 | roleR: OWLObjectProperty, | ||
570 | classB: OWLClass | ||
571 | ): Set[Term] = { | ||
572 | val conflR = this.confl(roleR) | ||
573 | val classes = ontology | ||
574 | .classesInSignature(Imports.INCLUDED) | ||
575 | .collect(Collectors.toSet()) | ||
576 | .asScala | ||
577 | for { | ||
578 | classD <- classes | ||
579 | roleS <- conflR | ||
580 | classC <- classes | ||
581 | // Keeping this check for now | ||
582 | if !unsafeRoles.contains(roleS) | ||
583 | tripleARB = RSAAxiom.hashed(classA, roleR, classB) | ||
584 | tripleDSC = RSAAxiom.hashed(classD, roleS, classC) | ||
585 | individual = | ||
586 | if (tripleARB > tripleDSC) { | ||
587 | RSA("v1_" ++ tripleDSC) | ||
588 | } else { | ||
589 | // Note that this is also the case for | ||
590 | // `tripleARB == tripleDSC` | ||
591 | RSA("v0_" ++ tripleDSC) | ||
592 | } | ||
593 | } yield individual | ||
594 | } | ||
595 | |||
596 | def cycle_aux1( | ||
597 | classA: OWLClass, | ||
598 | roleR: OWLObjectProperty, | ||
599 | classB: OWLClass | ||
600 | ): Set[Term] = { | ||
601 | val conflR = this.confl(roleR) | ||
602 | // We just need the TBox to find | ||
603 | val terms = for { | ||
604 | axiom1 <- axioms | ||
605 | if axiom1.isT5 | ||
606 | // We expect only one role coming out of a T5 axiom | ||
607 | roleS <- axiom1.objectPropertyExpressionsInSignature | ||
608 | // Triples ordering is among triples involving safe roles. | ||
609 | if !unsafeRoles.contains(roleS) | ||
610 | if conflR.contains(roleS) | ||
611 | tripleARB = RSAAxiom.hashed(classA, roleR, classB) | ||
612 | tripleDSC = axiom1.hashed | ||
613 | individual = | ||
614 | if (tripleARB > tripleDSC) { | ||
615 | RSA("v1_" ++ tripleDSC) | ||
616 | } else { | ||
617 | // Note that this is also the case for | ||
618 | // `tripleARB == tripleDSC` | ||
619 | RSA("v0_" ++ tripleDSC) | ||
620 | } | ||
621 | } yield individual | ||
622 | terms to Set | ||
623 | } | ||
624 | |||
625 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | ||
626 | this.self(axiom) | this.cycle(axiom) | ||
627 | |||
628 | /** Log normalization/approximation statistics */ | ||
629 | // def statistics(level: Logger.Level = Logger.DEBUG): Unit = { | ||
630 | // Logger.print( | ||
631 | // s"Logical axioms in original input ontology: ${original.getLogicalAxiomCount(true)}", | ||
632 | // level | ||
633 | // ) | ||
634 | // Logger.print( | ||
635 | // s"Logical axioms discarded in Horn-ALCHOIQ approximation: ${normalizer.discarded}", | ||
636 | // level | ||
637 | // ) | ||
638 | // Logger.print( | ||
639 | // s"Logical axioms shifted in Horn-ALCHOIQ approximation: ${normalizer.shifted}", | ||
640 | // level | ||
641 | // ) | ||
642 | // Logger.print( | ||
643 | // s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology | ||
644 | // .getLogicalAxiomCount(true)} (${axioms.length}/${axioms.length}/${axioms.length})", | ||
645 | // level | ||
646 | // ) | ||
647 | // Logger.print( | ||
648 | // s"Logical axioms discarded in RSA approximation: ${removed.length}", | ||
649 | // level | ||
650 | // ) | ||
651 | // } | 614 | // } |
652 | 615 | ||
653 | } // class RSAOntology | 616 | } |
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 index 1b49413..db2118f 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala | |||
@@ -8,8 +8,8 @@ trait Approximation { | |||
8 | 8 | ||
9 | /** Approximate an ontology. | 9 | /** Approximate an ontology. |
10 | * | 10 | * |
11 | * @param ontology input ontology | 11 | * @param ontology input ontology as a list of axioms |
12 | * @return a new approximated ontology | 12 | * @return the approximated ontology |
13 | */ | 13 | */ |
14 | def approximate( | 14 | def approximate( |
15 | ontology: List[OWLLogicalAxiom], | 15 | ontology: List[OWLLogicalAxiom], |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala index 7856b3a..4a23ec7 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/ontologies/Ontology.scala | |||
@@ -27,7 +27,15 @@ import org.semanticweb.owlapi.apibinding.OWLManager | |||
27 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} | 27 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom, OWLLogicalAxiom} |
28 | import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression} | 28 | import org.semanticweb.owlapi.model.{OWLObjectPropertyExpression} |
29 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory | 29 | import org.semanticweb.owlapi.reasoner.structural.StructuralReasonerFactory |
30 | import tech.oxfordsemantic.jrdfox.logic.expression.Resource | 30 | import tech.oxfordsemantic.jrdfox.logic.datalog.Rule |
31 | import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, Term, Variable} | ||
32 | |||
33 | import uk.ac.ox.cs.rsacomb.approximation.Approximation | ||
34 | import uk.ac.ox.cs.rsacomb.converter._ | ||
35 | import uk.ac.ox.cs.rsacomb.suffix._ | ||
36 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | ||
37 | |||
38 | import uk.ac.ox.cs.rsacomb.RSAUtil | ||
31 | 39 | ||
32 | object Ontology { | 40 | object Ontology { |
33 | 41 | ||
@@ -44,7 +52,6 @@ object Ontology { | |||
44 | * TODO: turn this into an implicit class parameter. | 52 | * TODO: turn this into an implicit class parameter. |
45 | */ | 53 | */ |
46 | val manager = OWLManager.createOWLOntologyManager() | 54 | val manager = OWLManager.createOWLOntologyManager() |
47 | //val factory = manager.getOWLDataFactory() | ||
48 | 55 | ||
49 | /** Compute the RSA dependency graph for a set of axioms | 56 | /** Compute the RSA dependency graph for a set of axioms |
50 | * | 57 | * |
@@ -66,22 +73,17 @@ object Ontology { | |||
66 | unsafe: List[OWLObjectPropertyExpression] | 73 | unsafe: List[OWLObjectPropertyExpression] |
67 | ): DependencyGraph = { | 74 | ): DependencyGraph = { |
68 | 75 | ||
69 | import org.semanticweb.owlapi.model.{ | ||
70 | OWLClassExpression, | ||
71 | OWLObjectSomeValuesFrom, | ||
72 | OWLDataSomeValuesFrom | ||
73 | } | ||
74 | import tech.oxfordsemantic.jrdfox.logic.datalog.Rule | ||
75 | import tech.oxfordsemantic.jrdfox.logic.expression.{Term, Variable} | ||
76 | import uk.ac.ox.cs.rsacomb.suffix._ | ||
77 | import uk.ac.ox.cs.rsacomb.converter._ | ||
78 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | ||
79 | import uk.ac.ox.cs.rsacomb.RSAUtil | ||
80 | |||
81 | var nodemap = Map.empty[String, OWLAxiom] | 76 | var nodemap = Map.empty[String, OWLAxiom] |
82 | 77 | ||
78 | /* Create custom converter */ | ||
83 | object RSAConverter extends RDFoxConverter { | 79 | object RSAConverter extends RDFoxConverter { |
84 | 80 | ||
81 | import org.semanticweb.owlapi.model.{ | ||
82 | OWLClassExpression, | ||
83 | OWLObjectSomeValuesFrom, | ||
84 | OWLDataSomeValuesFrom | ||
85 | } | ||
86 | |||
85 | override def convert( | 87 | override def convert( |
86 | expr: OWLClassExpression, | 88 | expr: OWLClassExpression, |
87 | term: Term, | 89 | term: Term, |
@@ -156,6 +158,9 @@ object Ontology { | |||
156 | } | 158 | } |
157 | 159 | ||
158 | /** A wrapper for a generic OWL2 ontology | 160 | /** A wrapper for a generic OWL2 ontology |
161 | * | ||
162 | * @param axioms list of axioms (roughly) corresponding to the TBox. | ||
163 | * @param datafiles files containing ABox data. | ||
159 | */ | 164 | */ |
160 | class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { | 165 | class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { |
161 | 166 | ||
@@ -170,11 +175,11 @@ class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { | |||
170 | * This is mainly used to instantiate a new reasoner to be used in | 175 | * This is mainly used to instantiate a new reasoner to be used in |
171 | * the computation of unsafe roles. | 176 | * the computation of unsafe roles. |
172 | */ | 177 | */ |
173 | private val ontology: OWLOntology = | 178 | protected val ontology: OWLOntology = |
174 | Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) | 179 | Ontology.manager.createOntology((axioms: List[OWLAxiom]).asJava) |
175 | 180 | ||
176 | /** OWLAPI internal reasoner for ontology */ | 181 | /** OWLAPI internal reasoner for ontology */ |
177 | private val reasoner = | 182 | protected val reasoner = |
178 | (new StructuralReasonerFactory()).createReasoner(ontology) | 183 | (new StructuralReasonerFactory()).createReasoner(ontology) |
179 | 184 | ||
180 | /** Unsafe roles in the ontology | 185 | /** Unsafe roles in the ontology |
@@ -219,7 +224,28 @@ class Ontology(val axioms: List[OWLLogicalAxiom], val datafiles: List[File]) { | |||
219 | unsafe1 ++ unsafe2 | 224 | unsafe1 ++ unsafe2 |
220 | } | 225 | } |
221 | 226 | ||
227 | /** Compute the dependency graph for the ontology */ | ||
222 | lazy val dependencyGraph: Ontology.DependencyGraph = | 228 | lazy val dependencyGraph: Ontology.DependencyGraph = |
223 | Ontology.dependencyGraph(axioms, datafiles, this.unsafe) | 229 | Ontology.dependencyGraph(axioms, datafiles, this.unsafe) |
224 | 230 | ||
231 | /** RSA check */ | ||
232 | lazy val isRSA: Boolean = ??? | ||
233 | |||
234 | /** Normalize the ontology according to the given normalizer | ||
235 | * | ||
236 | * @param normalizer the normalization technique to be used. | ||
237 | * @return a new normalized [[Ontology]]. | ||
238 | */ | ||
239 | def normalize(normalizer: Normalizer): Ontology = ??? | ||
240 | |||
241 | /** Approximate the ontology according to the given approximation | ||
242 | * technique. | ||
243 | * | ||
244 | * @param approximation the approximation to be used on the ontology. | ||
245 | * @return a new approximated [[Ontology]]. | ||
246 | */ | ||
247 | def approximate(approximation: Approximation): Ontology = { | ||
248 | val approx = approximation.approximate(axioms, datafiles) | ||
249 | new Ontology(approx, datafiles) | ||
250 | } | ||
225 | } | 251 | } |