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 | |
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.
7 files changed, 260 insertions, 202 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala index 96a953f..3777c6b 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/CanonicalModel.scala | |||
@@ -12,17 +12,12 @@ import org.semanticweb.owlapi.model.{ | |||
12 | } | 12 | } |
13 | 13 | ||
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 | ||
diff --git a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala index e627bf7..32ef8da 100644 --- a/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala +++ b/src/test/scala/uk/ac/ox/cs/rsacomb/FilteringProgramSpecs.scala | |||
@@ -67,29 +67,25 @@ class FilteringProgramSpec extends AnyFlatSpec with Matchers { | |||
67 | 67 | ||
68 | "CQ 0" should "generate 27 rules and 3 facts" in { | 68 | "CQ 0" should "generate 27 rules and 3 facts" in { |
69 | val cq = ConjunctiveQuery.parse(cq0).get | 69 | val cq = ConjunctiveQuery.parse(cq0).get |
70 | val filter = FilteringProgram(cq, constants) | 70 | val filter = FilteringProgram(cq) |
71 | filter.facts should have length 3 | ||
72 | filter.rules should have length 27 | 71 | filter.rules should have length 27 |
73 | } | 72 | } |
74 | 73 | ||
75 | "CQ 1" should "generate 15 rules" in { | 74 | "CQ 1" should "generate 15 rules" in { |
76 | val cq = ConjunctiveQuery.parse(cq1).get | 75 | val cq = ConjunctiveQuery.parse(cq1).get |
77 | val filter = FilteringProgram(cq, List()) | 76 | val filter = FilteringProgram(cq) |
78 | filter.facts shouldBe empty | ||
79 | filter.rules should have length 15 | 77 | filter.rules should have length 15 |
80 | } | 78 | } |
81 | 79 | ||
82 | "CQ 2" should "generate 51 rules" in { | 80 | "CQ 2" should "generate 51 rules" in { |
83 | val cq = ConjunctiveQuery.parse(cq2).get | 81 | val cq = ConjunctiveQuery.parse(cq2).get |
84 | val filter = FilteringProgram(cq, List()) | 82 | val filter = FilteringProgram(cq) |
85 | filter.facts shouldBe empty | ||
86 | filter.rules should have length 51 | 83 | filter.rules should have length 51 |
87 | } | 84 | } |
88 | 85 | ||
89 | "BCQ 0" should "generate 46 rules" in { | 86 | "BCQ 0" should "generate 46 rules" in { |
90 | val cq = ConjunctiveQuery.parse(bcq0).get | 87 | val cq = ConjunctiveQuery.parse(bcq0).get |
91 | val filter = FilteringProgram(cq, constants) | 88 | val filter = FilteringProgram(cq) |
92 | filter.facts should have length 3 | ||
93 | filter.rules should have length 43 | 89 | filter.rules should have length 43 |
94 | } | 90 | } |
95 | 91 | ||