diff options
author | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-06 17:18:07 +0000 |
---|---|---|
committer | Federico Igne <federico.igne@cs.ox.ac.uk> | 2020-11-06 17:18:07 +0000 |
commit | b721703c349cebd3ffe888d9644f2b85d5a8eeb7 (patch) | |
tree | a3845fbf44827ff66fc956c90fe20c1d4be73115 /src | |
parent | c6babfd508f65e8b7596a96659214cb43881dadd (diff) | |
download | RSAComb-b721703c349cebd3ffe888d9644f2b85d5a8eeb7.tar.gz RSAComb-b721703c349cebd3ffe888d9644f2b85d5a8eeb7.zip |
Rework canonical model computation
This is a first attempt to avoid a bug triggered by the nature of the
class RSAOntology and CanonicalModel.
An OWLOntology is converted implicitly to an RSAOntology object
whenever it is needed. From within the RSAOntology class we used to
create a CanonicalModel object (and pass the underling OWLOntology
object as a parameter). Inside CanonicalModel we require RSAOntology
functionalities from the OWLOntology, triggering a new conversion into
RSAOntology (that would compute a new CanonicalModel and so on in a
loop).
While declaring the CanonicalModel as lazy in RSAOntology could solve
the problem, it does not fix the underlying issue of having a class
strictly related to RSAOntology as a top level class.
As a first attempt we moved CanonicalModel as an object inside
RSAOntology.
Diffstat (limited to 'src')
-rw-r--r-- | src/main/scala/rsacomb/CanonicalModel.scala | 255 | ||||
-rw-r--r-- | src/main/scala/rsacomb/Main.scala | 69 | ||||
-rw-r--r-- | src/main/scala/rsacomb/RSAOntology.scala | 331 |
3 files changed, 332 insertions, 323 deletions
diff --git a/src/main/scala/rsacomb/CanonicalModel.scala b/src/main/scala/rsacomb/CanonicalModel.scala deleted file mode 100644 index fcc404a..0000000 --- a/src/main/scala/rsacomb/CanonicalModel.scala +++ /dev/null | |||
@@ -1,255 +0,0 @@ | |||
1 | package rsacomb | ||
2 | |||
3 | import org.semanticweb.owlapi.model.{ | ||
4 | OWLOntology, | ||
5 | OWLClass, | ||
6 | OWLSubObjectPropertyOfAxiom, | ||
7 | OWLSubClassOfAxiom, | ||
8 | OWLObjectProperty, | ||
9 | OWLObjectPropertyExpression, | ||
10 | OWLObjectSomeValuesFrom | ||
11 | } | ||
12 | import tech.oxfordsemantic.jrdfox.logic.Datatype | ||
13 | import tech.oxfordsemantic.jrdfox.logic.expression.{IRI, Term, Variable} | ||
14 | import tech.oxfordsemantic.jrdfox.logic.datalog.{ | ||
15 | BodyFormula, | ||
16 | TupleTableAtom, | ||
17 | Rule | ||
18 | } | ||
19 | |||
20 | import scala.collection.JavaConverters._ | ||
21 | import rsacomb.RSA._ | ||
22 | |||
23 | object ProgramGenerator { | ||
24 | |||
25 | def apply( | ||
26 | ontology: OWLOntology, | ||
27 | term: Term = Variable.create("X") | ||
28 | ): RDFoxAxiomConverter = | ||
29 | new ProgramGenerator(ontology, term) | ||
30 | |||
31 | def generateRoleRules( | ||
32 | roles: Set[OWLObjectProperty] | ||
33 | ): List[Rule] = { | ||
34 | def additional(pred: String): Seq[Rule] = { | ||
35 | val varX = Variable.create("X") | ||
36 | val varY = Variable.create("Y") | ||
37 | List( | ||
38 | Rule.create( | ||
39 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
40 | TupleTableAtom | ||
41 | .rdf(varX, IRI.create(pred ++ RSASuffix.Forward.getSuffix), varY) | ||
42 | ), | ||
43 | Rule.create( | ||
44 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
45 | TupleTableAtom | ||
46 | .rdf(varX, IRI.create(pred ++ RSASuffix.Backward.getSuffix), varY) | ||
47 | ), | ||
48 | Rule.create( | ||
49 | TupleTableAtom.rdf( | ||
50 | varY, | ||
51 | IRI.create(pred ++ RSASuffix.Backward.getSuffix ++ "_inv"), | ||
52 | varX | ||
53 | ), | ||
54 | TupleTableAtom | ||
55 | .rdf(varX, IRI.create(pred ++ RSASuffix.Forward.getSuffix), varY) | ||
56 | ), | ||
57 | Rule.create( | ||
58 | TupleTableAtom.rdf( | ||
59 | varY, | ||
60 | IRI.create(pred ++ RSASuffix.Forward.getSuffix ++ "_inv"), | ||
61 | varX | ||
62 | ), | ||
63 | TupleTableAtom.rdf( | ||
64 | varX, | ||
65 | IRI.create(pred ++ RSASuffix.Backward.getSuffix), | ||
66 | varY | ||
67 | ) | ||
68 | ) | ||
69 | ) | ||
70 | } | ||
71 | roles | ||
72 | .map(_.getIRI.getIRIString) | ||
73 | .flatMap(additional) | ||
74 | .toList | ||
75 | } | ||
76 | |||
77 | def NIs(individuals: List[IRI]): List[TupleTableAtom] = | ||
78 | individuals.map(TupleTableAtom.rdf(_, IRI.RDF_TYPE, RSA.internal("NI"))) | ||
79 | |||
80 | } | ||
81 | |||
82 | class ProgramGenerator( | ||
83 | ontology: OWLOntology, | ||
84 | term: Term | ||
85 | ) extends RDFoxAxiomConverter( | ||
86 | term, | ||
87 | ontology.unsafeRoles, | ||
88 | SkolemStrategy.None, | ||
89 | RSASuffix.None | ||
90 | ) | ||
91 | with RSAAxiom { | ||
92 | |||
93 | import RDFoxUtil._ | ||
94 | |||
95 | def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
96 | val unfold = ontology.unfold(axiom).toList | ||
97 | // Fresh Variables | ||
98 | val v0 = IRI.create("v0_" ++ axiom.hashCode.toString) | ||
99 | val varX = Variable.create("X") | ||
100 | // Predicates | ||
101 | val atomA: TupleTableAtom = { | ||
102 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
103 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) | ||
104 | } | ||
105 | def notIn(t: Term): TupleTableAtom = { | ||
106 | TupleTableAtom.rdf( | ||
107 | t, | ||
108 | RSA.internal("notIn"), | ||
109 | RSA.internal(unfold.hashCode.toString) | ||
110 | ) | ||
111 | } | ||
112 | val roleRf: TupleTableAtom = { | ||
113 | val visitor = | ||
114 | new RDFoxPropertyExprConverter(varX, v0, RSASuffix.Forward) | ||
115 | axiom.getSuperClass | ||
116 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
117 | .getProperty | ||
118 | .accept(visitor) | ||
119 | .head | ||
120 | } | ||
121 | val atomB: TupleTableAtom = { | ||
122 | val cls = axiom.getSuperClass | ||
123 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
124 | .getFiller | ||
125 | .asInstanceOf[OWLClass] | ||
126 | .getIRI | ||
127 | TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) | ||
128 | } | ||
129 | // TODO: To be consistent with the specifics of the visitor we are | ||
130 | // returning facts as `Rule`s with true body. While this is correct | ||
131 | // there is an easier way to import facts into RDFox. Are we able to | ||
132 | // do that? | ||
133 | val facts = unfold.map(x => Rule.create(notIn(x))) | ||
134 | val rules = List( | ||
135 | Rule.create(roleRf, atomA, notIn(varX)), | ||
136 | Rule.create(atomB, atomA, notIn(varX)) | ||
137 | ) | ||
138 | facts ++ rules | ||
139 | } | ||
140 | |||
141 | def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
142 | val roleR = | ||
143 | axiom.getSuperClass | ||
144 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
145 | .getProperty | ||
146 | if (ontology.confl(roleR) contains roleR) { | ||
147 | // Fresh Variables | ||
148 | val v0 = IRI.create("v0_" ++ axiom.hashCode.toString) | ||
149 | val v1 = IRI.create("v1_" ++ axiom.hashCode.toString) | ||
150 | val v2 = IRI.create("v2_" ++ axiom.hashCode.toString) | ||
151 | // Predicates | ||
152 | def atomA(t: Term): TupleTableAtom = { | ||
153 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
154 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
155 | } | ||
156 | def roleRf(t1: Term, t2: Term): TupleTableAtom = { | ||
157 | val visitor = new RDFoxPropertyExprConverter(t1, t2, RSASuffix.Forward) | ||
158 | roleR.accept(visitor).head | ||
159 | } | ||
160 | def atomB(t: Term): TupleTableAtom = { | ||
161 | val cls = axiom.getSuperClass | ||
162 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
163 | .getFiller | ||
164 | .asInstanceOf[OWLClass] | ||
165 | .getIRI | ||
166 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
167 | } | ||
168 | //Rules | ||
169 | List( | ||
170 | Rule.create(roleRf(v0, v1), atomA(v0)), | ||
171 | Rule.create(atomB(v1), atomA(v0)), | ||
172 | Rule.create(roleRf(v1, v2), atomA(v1)), | ||
173 | Rule.create(atomB(v2), atomA(v1)) | ||
174 | ) | ||
175 | } else { | ||
176 | List() | ||
177 | } | ||
178 | } | ||
179 | |||
180 | def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
181 | val cycle = ontology.cycle(axiom).toList | ||
182 | val roleR = | ||
183 | axiom.getSuperClass | ||
184 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
185 | .getProperty | ||
186 | // Fresh Variables | ||
187 | val v1 = IRI.create("v1_" ++ axiom.hashCode.toString) | ||
188 | // Predicates | ||
189 | def atomA(t: Term): TupleTableAtom = { | ||
190 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
191 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
192 | } | ||
193 | def roleRf(t: Term): TupleTableAtom = { | ||
194 | val visitor = | ||
195 | new RDFoxPropertyExprConverter(t, v1, RSASuffix.Forward) | ||
196 | roleR.accept(visitor).head | ||
197 | } | ||
198 | val atomB: TupleTableAtom = { | ||
199 | val cls = axiom.getSuperClass | ||
200 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
201 | .getFiller | ||
202 | .asInstanceOf[OWLClass] | ||
203 | .getIRI | ||
204 | TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) | ||
205 | } | ||
206 | cycle.flatMap { x => | ||
207 | List( | ||
208 | Rule.create(roleRf(x), atomA(x)), | ||
209 | Rule.create(atomB, atomA(x)) | ||
210 | ) | ||
211 | } | ||
212 | } | ||
213 | |||
214 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
215 | if (axiom.isT5) { | ||
216 | // TODO: get role in T5 axiom | ||
217 | // Assuming one role here | ||
218 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
219 | if (ontology.unsafeRoles.contains(role)) { | ||
220 | val visitor = | ||
221 | new RDFoxAxiomConverter( | ||
222 | term, | ||
223 | ontology.unsafeRoles, | ||
224 | SkolemStrategy.Standard(axiom.toString), | ||
225 | RSASuffix.Forward | ||
226 | ) | ||
227 | axiom.accept(visitor) | ||
228 | } else { | ||
229 | rules1(axiom) ++ rules2(axiom) ++ rules3(axiom) | ||
230 | } | ||
231 | } else { | ||
232 | // Fallback to standard OWL to LP translation | ||
233 | super.visit(axiom) | ||
234 | } | ||
235 | } | ||
236 | |||
237 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | ||
238 | val varX = Variable.create("X") | ||
239 | val varY = Variable.create("Y") | ||
240 | val visitorF = new RDFoxAxiomConverter( | ||
241 | term, | ||
242 | ontology.unsafeRoles, | ||
243 | SkolemStrategy.None, | ||
244 | RSASuffix.Forward | ||
245 | ) | ||
246 | val visitorB = new RDFoxAxiomConverter( | ||
247 | term, | ||
248 | ontology.unsafeRoles, | ||
249 | SkolemStrategy.None, | ||
250 | RSASuffix.Backward | ||
251 | ) | ||
252 | axiom.accept(visitorB) ++ axiom.accept(visitorF) | ||
253 | } | ||
254 | |||
255 | } | ||
diff --git a/src/main/scala/rsacomb/Main.scala b/src/main/scala/rsacomb/Main.scala index 830f1e0..64343f5 100644 --- a/src/main/scala/rsacomb/Main.scala +++ b/src/main/scala/rsacomb/Main.scala | |||
@@ -2,6 +2,10 @@ package rsacomb | |||
2 | 2 | ||
3 | /* Java imports */ | 3 | /* Java imports */ |
4 | import java.io.File | 4 | import java.io.File |
5 | import java.util.HashMap | ||
6 | import scala.collection.JavaConverters._ | ||
7 | |||
8 | import tech.oxfordsemantic.jrdfox.client.UpdateType | ||
5 | 9 | ||
6 | /* Local imports */ | 10 | /* Local imports */ |
7 | import rsacomb.RSA._ | 11 | import rsacomb.RSA._ |
@@ -46,25 +50,64 @@ object RSAComb extends App { | |||
46 | * case. | 50 | * case. |
47 | */ | 51 | */ |
48 | 52 | ||
49 | val ontology = RSA.loadOntology(ontoPath) | 53 | val ontology: RSAOntology = RSA.loadOntology(ontoPath) |
50 | if (ontology.isRSA) { | 54 | if (ontology.isRSA) { |
51 | 55 | ||
52 | /* Build canonical model */ | ||
53 | //val tboxCanon = rsa.canonicalModel() | ||
54 | |||
55 | // DEBUG: print program to generate canonical model | ||
56 | { | ||
57 | ontology.canonicalModel.foreach(println) | ||
58 | } | ||
59 | |||
60 | /* Load query */ | 56 | /* Load query */ |
61 | val query = RDFoxUtil.parseQuery( | 57 | val query = RDFoxUtil.parseQuery( |
62 | "SELECT ?X WHERE {?X ?Y ?Z}" | 58 | """ |
59 | SELECT ?uno | ||
60 | WHERE { | ||
61 | ?uno a :D ; | ||
62 | :R ?due . | ||
63 | ?due :S ?tre . | ||
64 | ?tre a :D . | ||
65 | } | ||
66 | """ | ||
63 | ) | 67 | ) |
64 | 68 | ||
65 | val filter = query map { q => ontology.filteringProgram(q) } | 69 | /* Compute answers to query */ |
66 | 70 | query match { | |
67 | /* ... */ | 71 | case Some(query) => { |
72 | // Open connection to RDFox | ||
73 | val (server, data) = RDFoxUtil.openConnection("AnswerComputation") | ||
74 | |||
75 | // Gather canonical model and filtering rules | ||
76 | val canon = ontology.canonicalModel | ||
77 | val filter = ontology.filteringProgram(query) | ||
78 | |||
79 | // Import relevant data | ||
80 | data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .") | ||
81 | data.addRules(canon.rules.asJava) | ||
82 | data.addRules(filter.rules.asJava) | ||
83 | |||
84 | // Collect answers to query | ||
85 | for ((v, i) <- filter.variables.view.zipWithIndex) { | ||
86 | println(s"Variable $i:") | ||
87 | val query = s"SELECT ?X ?Y WHERE { ?X internal:Ans_$i ?Y }" | ||
88 | val cursor = | ||
89 | data.createCursor( | ||
90 | RSA.Prefixes, | ||
91 | query, | ||
92 | new HashMap[String, String]() | ||
93 | ); | ||
94 | var mul = cursor.open() | ||
95 | while (mul > 0) { | ||
96 | printf( | ||
97 | "Ans_%d(%s,%s)", | ||
98 | i, | ||
99 | cursor.getResource(0), | ||
100 | cursor.getResource(1) | ||
101 | ) | ||
102 | mul = cursor.advance() | ||
103 | } | ||
104 | } | ||
105 | |||
106 | // Close connection to RDFox | ||
107 | RDFoxUtil.closeConnection(server, data) | ||
108 | } | ||
109 | case None => {} | ||
110 | } | ||
68 | } | 111 | } |
69 | } | 112 | } |
70 | 113 | ||
diff --git a/src/main/scala/rsacomb/RSAOntology.scala b/src/main/scala/rsacomb/RSAOntology.scala index ef1885b..60008a2 100644 --- a/src/main/scala/rsacomb/RSAOntology.scala +++ b/src/main/scala/rsacomb/RSAOntology.scala | |||
@@ -4,11 +4,13 @@ package rsacomb | |||
4 | import java.util.HashMap | 4 | import java.util.HashMap |
5 | import java.util.stream.{Collectors, Stream} | 5 | import java.util.stream.{Collectors, Stream} |
6 | 6 | ||
7 | import org.semanticweb.owlapi.model.OWLOntology | 7 | import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} |
8 | import org.semanticweb.owlapi.model.{ | 8 | import org.semanticweb.owlapi.model.{ |
9 | OWLClass, | 9 | OWLClass, |
10 | OWLObjectProperty, | 10 | OWLObjectProperty, |
11 | OWLSubObjectPropertyOfAxiom, | ||
11 | OWLObjectPropertyExpression, | 12 | OWLObjectPropertyExpression, |
13 | OWLObjectSomeValuesFrom, | ||
12 | OWLSubClassOfAxiom | 14 | OWLSubClassOfAxiom |
13 | } | 15 | } |
14 | import org.semanticweb.owlapi.model.parameters.Imports | 16 | import org.semanticweb.owlapi.model.parameters.Imports |
@@ -45,24 +47,37 @@ trait RSAOntology { | |||
45 | */ | 47 | */ |
46 | implicit class RSAOntology(ontology: OWLOntology) extends RSAAxiom { | 48 | implicit class RSAOntology(ontology: OWLOntology) extends RSAAxiom { |
47 | 49 | ||
50 | // Gather TBox+RBox from original ontology | ||
51 | lazy val tbox: List[OWLAxiom] = | ||
52 | ontology | ||
53 | .tboxAxioms(Imports.INCLUDED) | ||
54 | .collect(Collectors.toList()) | ||
55 | .asScala | ||
56 | .toList | ||
57 | |||
58 | lazy val rbox: List[OWLAxiom] = | ||
59 | ontology | ||
60 | .rboxAxioms(Imports.INCLUDED) | ||
61 | .collect(Collectors.toList()) | ||
62 | .asScala | ||
63 | .toList | ||
64 | |||
65 | lazy val axioms: List[OWLAxiom] = tbox ++ rbox | ||
66 | |||
48 | /* Retrieve individuals in the original ontology | 67 | /* Retrieve individuals in the original ontology |
49 | */ | 68 | */ |
50 | lazy val individuals: List[IRI] = { | 69 | lazy val individuals: List[IRI] = |
51 | ontology | 70 | ontology |
52 | .getIndividualsInSignature() | 71 | .getIndividualsInSignature() |
53 | .asScala | 72 | .asScala |
54 | .map(_.getIRI) | 73 | .map(_.getIRI) |
55 | .map(RDFoxUtil.owlapi2rdfox) | 74 | .map(RDFoxUtil.owlapi2rdfox) |
56 | .toList | 75 | .toList |
57 | } | ||
58 | 76 | ||
59 | // private val roles: Set[OWLObjectPropertyExpression] = { | 77 | lazy val roles: List[OWLObjectPropertyExpression] = |
60 | // ontology | 78 | axioms |
61 | // .rboxAxioms(Imports.INCLUDED) | 79 | .flatMap(_.objectPropertyExpressionsInSignature) |
62 | // .collect(Collectors.toSet()) | 80 | .distinct |
63 | // .asScala | ||
64 | // .flatMap(_.objectPropertyExpressionsInSignature) | ||
65 | // } | ||
66 | 81 | ||
67 | // OWLAPI reasoner for same easier tasks | 82 | // OWLAPI reasoner for same easier tasks |
68 | private val reasoner = | 83 | private val reasoner = |
@@ -79,13 +94,6 @@ trait RSAOntology { | |||
79 | */ | 94 | */ |
80 | lazy val isRSA: Boolean = { | 95 | lazy val isRSA: Boolean = { |
81 | 96 | ||
82 | val tbox = ontology.tboxAxioms(Imports.INCLUDED) | ||
83 | val rbox = ontology.rboxAxioms(Imports.INCLUDED) | ||
84 | val axioms = | ||
85 | Stream | ||
86 | .concat(tbox, rbox) | ||
87 | .collect(Collectors.toList()) | ||
88 | .asScala | ||
89 | val unsafe = this.unsafeRoles | 97 | val unsafe = this.unsafeRoles |
90 | 98 | ||
91 | /* DEBUG: print rules in DL syntax and unsafe roles */ | 99 | /* DEBUG: print rules in DL syntax and unsafe roles */ |
@@ -154,11 +162,6 @@ trait RSAOntology { | |||
154 | 162 | ||
155 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { | 163 | lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { |
156 | 164 | ||
157 | val tbox = ontology | ||
158 | .tboxAxioms(Imports.INCLUDED) | ||
159 | .collect(Collectors.toSet()) | ||
160 | .asScala | ||
161 | |||
162 | /* DEBUG: print rules in DL syntax */ | 165 | /* DEBUG: print rules in DL syntax */ |
163 | //val renderer = new DLSyntaxObjectRenderer() | 166 | //val renderer = new DLSyntaxObjectRenderer() |
164 | 167 | ||
@@ -210,37 +213,6 @@ trait RSAOntology { | |||
210 | (unsafe1 ++ unsafe2).toList | 213 | (unsafe1 ++ unsafe2).toList |
211 | } | 214 | } |
212 | 215 | ||
213 | lazy val canonicalModel: List[Rule] = { | ||
214 | // Compute program to generate canonical model | ||
215 | val tbox = | ||
216 | ontology | ||
217 | .tboxAxioms(Imports.INCLUDED) | ||
218 | .collect(Collectors.toList()) | ||
219 | .asScala | ||
220 | .toList | ||
221 | val rbox = | ||
222 | ontology | ||
223 | .rboxAxioms(Imports.INCLUDED) | ||
224 | .collect(Collectors.toList()) | ||
225 | .asScala | ||
226 | .toList | ||
227 | val axioms = tbox ++ rbox | ||
228 | val varX = Variable.create("X") | ||
229 | val visitor = ProgramGenerator(ontology, varX) | ||
230 | val facts = ProgramGenerator.NIs(individuals) | ||
231 | val rules1 = ProgramGenerator.generateRoleRules( | ||
232 | axioms | ||
233 | .flatMap( | ||
234 | _.objectPropertiesInSignature.collect(Collectors.toSet()).asScala | ||
235 | ) | ||
236 | .toSet | ||
237 | ) | ||
238 | val rules2 = axioms.flatMap(_.accept(visitor)) | ||
239 | |||
240 | rules1 ++ rules2 | ||
241 | // Call RDFox to generate the canonical model | ||
242 | } | ||
243 | |||
244 | private def rsaGraph( | 216 | private def rsaGraph( |
245 | data: DataStoreConnection | 217 | data: DataStoreConnection |
246 | ): Graph[Resource, UnDiEdge] = { | 218 | ): Graph[Resource, UnDiEdge] = { |
@@ -256,8 +228,8 @@ trait RSAOntology { | |||
256 | Graph(edges: _*) | 228 | Graph(edges: _*) |
257 | } | 229 | } |
258 | 230 | ||
259 | def filteringProgram(query: SelectQuery): List[Rule] = | 231 | def filteringProgram(query: SelectQuery): FilteringProgram = |
260 | FilteringProgram(query, individuals).rules | 232 | FilteringProgram(query, individuals) |
261 | 233 | ||
262 | // TODO: the following functions needs testing | 234 | // TODO: the following functions needs testing |
263 | def confl( | 235 | def confl( |
@@ -366,6 +338,255 @@ trait RSAOntology { | |||
366 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = | 338 | def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = |
367 | this.self(axiom) | this.cycle(axiom) | 339 | this.self(axiom) | this.cycle(axiom) |
368 | 340 | ||
341 | object canonicalModel { | ||
342 | |||
343 | import RDFoxUtil._ | ||
344 | |||
345 | val NIs: List[Rule] = | ||
346 | individuals.map(a => | ||
347 | Rule.create(TupleTableAtom.rdf(a, IRI.RDF_TYPE, RSA.internal("NI"))) | ||
348 | ) | ||
349 | |||
350 | val rolesAdditionalRules: List[Rule] = { | ||
351 | // Given a role (predicate) compute additional logic rules | ||
352 | def additional(pred: String): Seq[Rule] = { | ||
353 | val varX = Variable.create("X") | ||
354 | val varY = Variable.create("Y") | ||
355 | List( | ||
356 | Rule.create( | ||
357 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
358 | TupleTableAtom | ||
359 | .rdf( | ||
360 | varX, | ||
361 | IRI.create(pred ++ RSASuffix.Forward.getSuffix), | ||
362 | varY | ||
363 | ) | ||
364 | ), | ||
365 | Rule.create( | ||
366 | TupleTableAtom.rdf(varX, IRI.create(pred), varY), | ||
367 | TupleTableAtom | ||
368 | .rdf( | ||
369 | varX, | ||
370 | IRI.create(pred ++ RSASuffix.Backward.getSuffix), | ||
371 | varY | ||
372 | ) | ||
373 | ), | ||
374 | Rule.create( | ||
375 | TupleTableAtom.rdf( | ||
376 | varY, | ||
377 | IRI.create(pred ++ RSASuffix.Backward.getSuffix ++ "_inv"), | ||
378 | varX | ||
379 | ), | ||
380 | TupleTableAtom | ||
381 | .rdf( | ||
382 | varX, | ||
383 | IRI.create(pred ++ RSASuffix.Forward.getSuffix), | ||
384 | varY | ||
385 | ) | ||
386 | ), | ||
387 | Rule.create( | ||
388 | TupleTableAtom.rdf( | ||
389 | varY, | ||
390 | IRI.create(pred ++ RSASuffix.Forward.getSuffix ++ "_inv"), | ||
391 | varX | ||
392 | ), | ||
393 | TupleTableAtom.rdf( | ||
394 | varX, | ||
395 | IRI.create(pred ++ RSASuffix.Backward.getSuffix), | ||
396 | varY | ||
397 | ) | ||
398 | ) | ||
399 | ) | ||
400 | } | ||
401 | // Compute additional rules per role | ||
402 | axioms | ||
403 | .flatMap( | ||
404 | _.objectPropertiesInSignature.collect(Collectors.toSet()).asScala | ||
405 | ) | ||
406 | .distinct | ||
407 | .map(_.getIRI.getIRIString) | ||
408 | .flatMap(additional) | ||
409 | } | ||
410 | |||
411 | val rules: List[Rule] = { | ||
412 | // Compute rules from ontology axioms | ||
413 | val rules = axioms.flatMap(_.accept(this.ProgramGenerator)) | ||
414 | // Return full set of rules | ||
415 | rules ++ rolesAdditionalRules ++ NIs | ||
416 | } | ||
417 | |||
418 | object ProgramGenerator | ||
419 | extends RDFoxAxiomConverter( | ||
420 | Variable.create("X"), | ||
421 | unsafeRoles, | ||
422 | SkolemStrategy.None, | ||
423 | RSASuffix.None | ||
424 | ) { | ||
425 | |||
426 | private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
427 | val unfold = ontology.unfold(axiom).toList | ||
428 | // Fresh Variables | ||
429 | val v0 = IRI.create("v0_" ++ axiom.hashCode.toString) | ||
430 | val varX = Variable.create("X") | ||
431 | // Predicates | ||
432 | val atomA: TupleTableAtom = { | ||
433 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
434 | TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls) | ||
435 | } | ||
436 | def notIn(t: Term): TupleTableAtom = { | ||
437 | TupleTableAtom.rdf( | ||
438 | t, | ||
439 | RSA.internal("notIn"), | ||
440 | RSA.internal(unfold.hashCode.toString) | ||
441 | ) | ||
442 | } | ||
443 | val roleRf: TupleTableAtom = { | ||
444 | val visitor = | ||
445 | new RDFoxPropertyExprConverter(varX, v0, RSASuffix.Forward) | ||
446 | axiom.getSuperClass | ||
447 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
448 | .getProperty | ||
449 | .accept(visitor) | ||
450 | .head | ||
451 | } | ||
452 | val atomB: TupleTableAtom = { | ||
453 | val cls = axiom.getSuperClass | ||
454 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
455 | .getFiller | ||
456 | .asInstanceOf[OWLClass] | ||
457 | .getIRI | ||
458 | TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls) | ||
459 | } | ||
460 | // TODO: To be consistent with the specifics of the visitor we are | ||
461 | // returning facts as `Rule`s with true body. While this is correct | ||
462 | // there is an easier way to import facts into RDFox. Are we able to | ||
463 | // do that? | ||
464 | val facts = unfold.map(x => Rule.create(notIn(x))) | ||
465 | val rules = List( | ||
466 | Rule.create(roleRf, atomA, notIn(varX)), | ||
467 | Rule.create(atomB, atomA, notIn(varX)) | ||
468 | ) | ||
469 | facts ++ rules | ||
470 | } | ||
471 | |||
472 | private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
473 | val roleR = | ||
474 | axiom.getSuperClass | ||
475 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
476 | .getProperty | ||
477 | if (ontology.confl(roleR) contains roleR) { | ||
478 | // Fresh Variables | ||
479 | val v0 = IRI.create("v0_" ++ axiom.hashCode.toString) | ||
480 | val v1 = IRI.create("v1_" ++ axiom.hashCode.toString) | ||
481 | val v2 = IRI.create("v2_" ++ axiom.hashCode.toString) | ||
482 | // Predicates | ||
483 | def atomA(t: Term): TupleTableAtom = { | ||
484 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
485 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
486 | } | ||
487 | def roleRf(t1: Term, t2: Term): TupleTableAtom = { | ||
488 | val visitor = | ||
489 | new RDFoxPropertyExprConverter(t1, t2, RSASuffix.Forward) | ||
490 | roleR.accept(visitor).head | ||
491 | } | ||
492 | def atomB(t: Term): TupleTableAtom = { | ||
493 | val cls = axiom.getSuperClass | ||
494 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
495 | .getFiller | ||
496 | .asInstanceOf[OWLClass] | ||
497 | .getIRI | ||
498 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
499 | } | ||
500 | //Rules | ||
501 | List( | ||
502 | Rule.create(roleRf(v0, v1), atomA(v0)), | ||
503 | Rule.create(atomB(v1), atomA(v0)), | ||
504 | Rule.create(roleRf(v1, v2), atomA(v1)), | ||
505 | Rule.create(atomB(v2), atomA(v1)) | ||
506 | ) | ||
507 | } else { | ||
508 | List() | ||
509 | } | ||
510 | } | ||
511 | |||
512 | private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
513 | val cycle = ontology.cycle(axiom).toList | ||
514 | val roleR = | ||
515 | axiom.getSuperClass | ||
516 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
517 | .getProperty | ||
518 | // Fresh Variables | ||
519 | val v1 = IRI.create("v1_" ++ axiom.hashCode.toString) | ||
520 | // Predicates | ||
521 | def atomA(t: Term): TupleTableAtom = { | ||
522 | val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI | ||
523 | TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls) | ||
524 | } | ||
525 | def roleRf(t: Term): TupleTableAtom = { | ||
526 | val visitor = | ||
527 | new RDFoxPropertyExprConverter(t, v1, RSASuffix.Forward) | ||
528 | roleR.accept(visitor).head | ||
529 | } | ||
530 | val atomB: TupleTableAtom = { | ||
531 | val cls = axiom.getSuperClass | ||
532 | .asInstanceOf[OWLObjectSomeValuesFrom] | ||
533 | .getFiller | ||
534 | .asInstanceOf[OWLClass] | ||
535 | .getIRI | ||
536 | TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls) | ||
537 | } | ||
538 | cycle.flatMap { x => | ||
539 | List( | ||
540 | Rule.create(roleRf(x), atomA(x)), | ||
541 | Rule.create(atomB, atomA(x)) | ||
542 | ) | ||
543 | } | ||
544 | } | ||
545 | |||
546 | override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { | ||
547 | if (axiom.isT5) { | ||
548 | // TODO: get role in T5 axiom | ||
549 | // Assuming one role here | ||
550 | val role = axiom.objectPropertyExpressionsInSignature(0) | ||
551 | if (ontology.unsafeRoles.contains(role)) { | ||
552 | val visitor = | ||
553 | new RDFoxAxiomConverter( | ||
554 | Variable.create("X"), | ||
555 | ontology.unsafeRoles, | ||
556 | SkolemStrategy.Standard(axiom.toString), | ||
557 | RSASuffix.Forward | ||
558 | ) | ||
559 | axiom.accept(visitor) | ||
560 | } else { | ||
561 | rules1(axiom) ++ rules2(axiom) ++ rules3(axiom) | ||
562 | } | ||
563 | } else { | ||
564 | // Fallback to standard OWL to LP translation | ||
565 | super.visit(axiom) | ||
566 | } | ||
567 | } | ||
568 | |||
569 | override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { | ||
570 | val varX = Variable.create("X") | ||
571 | val varY = Variable.create("Y") | ||
572 | val visitorF = new RDFoxAxiomConverter( | ||
573 | Variable.create("X"), | ||
574 | ontology.unsafeRoles, | ||
575 | SkolemStrategy.None, | ||
576 | RSASuffix.Forward | ||
577 | ) | ||
578 | val visitorB = new RDFoxAxiomConverter( | ||
579 | Variable.create("X"), | ||
580 | ontology.unsafeRoles, | ||
581 | SkolemStrategy.None, | ||
582 | RSASuffix.Backward | ||
583 | ) | ||
584 | axiom.accept(visitorB) ++ axiom.accept(visitorF) | ||
585 | } | ||
586 | |||
587 | } | ||
588 | |||
589 | } | ||
369 | } // implicit class RSAOntology | 590 | } // implicit class RSAOntology |
370 | 591 | ||
371 | } // trait RSAOntology | 592 | } // trait RSAOntology |