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