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 | } |
