aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFederico Igne <federico.igne@cs.ox.ac.uk>2020-11-17 14:19:08 +0000
committerFederico Igne <federico.igne@cs.ox.ac.uk>2020-11-17 14:19:08 +0000
commite1a04294ed8737444e40323474f4084cb64c1d55 (patch)
treef3464a10152e7df6b7831f11e3d355b34dec1a2c
parente6c211b6a9c497b625710f7b97f793d1a796b461 (diff)
downloadRSAComb-e1a04294ed8737444e40323474f4084cb64c1d55.tar.gz
RSAComb-e1a04294ed8737444e40323474f4084cb64c1d55.zip
Remove implicit RSAOntology conversion
This was causing problems without giving any significant advantage. Now it will be easier to refactor classes like RSA, RSAOntology, CanonicalModel.
-rw-r--r--src/main/scala/rsacomb/CanonicalModel.scala292
-rw-r--r--src/main/scala/rsacomb/Main.scala2
-rw-r--r--src/main/scala/rsacomb/RDFoxUtil.scala10
-rw-r--r--src/main/scala/rsacomb/RSA.scala11
-rw-r--r--src/main/scala/rsacomb/RSAOntology.scala951
-rw-r--r--src/test/scala/rsacomb/CanonicalModelSpec.scala22
-rw-r--r--src/test/scala/rsacomb/FilteringProgramSpecs.scala1
7 files changed, 624 insertions, 665 deletions
diff --git a/src/main/scala/rsacomb/CanonicalModel.scala b/src/main/scala/rsacomb/CanonicalModel.scala
new file mode 100644
index 0000000..f7a45a7
--- /dev/null
+++ b/src/main/scala/rsacomb/CanonicalModel.scala
@@ -0,0 +1,292 @@
1package rsacomb
2
3import org.semanticweb.owlapi.model.{OWLObjectInverseOf, OWLObjectProperty}
4import org.semanticweb.owlapi.model.{
5 OWLClass,
6 // OWLObjectProperty,
7 OWLSubObjectPropertyOfAxiom,
8 // OWLObjectPropertyExpression,
9 OWLObjectSomeValuesFrom,
10 OWLSubClassOfAxiom
11}
12
13import tech.oxfordsemantic.jrdfox.logic.datalog.{
14 Rule,
15 BodyFormula,
16 TupleTableAtom,
17 Negation
18}
19import tech.oxfordsemantic.jrdfox.logic.expression.{
20 Term,
21 Variable,
22 // Resource,
23 IRI
24}
25
26import suffix.{Empty, Forward, Backward, Inverse}
27
28class CanonicalModel(val ontology: RSAOntology) extends RSAAxiom {
29
30 // Makes working with IRIs less painful
31 import RDFoxUtil._
32
33 val named: List[Rule] =
34 ontology.individuals.map(a =>
35 Rule.create(TupleTableAtom.rdf(a, IRI.RDF_TYPE, RSA.Named))
36 )
37
38 val rolesAdditionalRules: List[Rule] = {
39 // Given a role (predicate) compute additional logic rules
40 def additional(pred: String): Seq[Rule] = {
41 val varX = Variable.create("X")
42 val varY = Variable.create("Y")
43 for (
44 (hSuffix, bSuffix) <- List(
45 (Empty, Forward),
46 (Empty, Backward),
47 (Inverse, Forward + Inverse),
48 (Inverse, Backward + Inverse),
49 (Backward + Inverse, Forward),
50 (Forward + Inverse, Backward),
51 (Backward, Forward + Inverse),
52 (Forward, Backward + Inverse)
53 )
54 )
55 yield Rule.create(
56 TupleTableAtom.rdf(varX, pred :: hSuffix, varY),
57 TupleTableAtom.rdf(varX, pred :: bSuffix, varY)
58 )
59 }
60 // Compute additional rules per role
61 ontology.roles
62 .collect { case prop: OWLObjectProperty => prop }
63 .map(_.getIRI.getIRIString)
64 .flatMap(additional)
65 }
66
67 private lazy val topAxioms: List[Rule] = {
68 val varX = Variable.create("X")
69 val varY = Variable.create("Y")
70 val concepts = ontology.concepts.map(c => {
71 Rule.create(
72 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, IRI.THING),
73 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, c.getIRI)
74 )
75 })
76 val roles = ontology.roles.map(r => {
77 val name = r match {
78 case x: OWLObjectProperty => x.getIRI.getIRIString
79 case x: OWLObjectInverseOf =>
80 x.getInverse.getNamedProperty.getIRI.getIRIString :: Inverse
81 }
82 Rule.create(
83 List(
84 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, IRI.THING),
85 TupleTableAtom.rdf(varY, IRI.RDF_TYPE, IRI.THING)
86 ),
87 List(TupleTableAtom.rdf(varX, name, varY))
88 )
89 })
90 concepts ::: roles
91 }
92
93 private val equalityAxioms: List[Rule] = {
94 val varX = Variable.create("X")
95 val varY = Variable.create("Y")
96 val varZ = Variable.create("Z")
97 List(
98 Rule.create(
99 TupleTableAtom.rdf(varX, RSA.EquivTo, varX),
100 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, IRI.THING)
101 ),
102 Rule.create(
103 TupleTableAtom.rdf(varY, RSA.EquivTo, varX),
104 TupleTableAtom.rdf(varX, RSA.EquivTo, varY)
105 ),
106 Rule.create(
107 TupleTableAtom.rdf(varX, RSA.EquivTo, varZ),
108 TupleTableAtom.rdf(varX, RSA.EquivTo, varY),
109 TupleTableAtom.rdf(varY, RSA.EquivTo, varZ)
110 )
111 )
112 }
113
114 val rules: List[Rule] = {
115 // Compute rules from ontology axioms
116 val rules = ontology.axioms.flatMap(_.accept(RuleGenerator))
117 // Return full set of rules
118 rules ::: rolesAdditionalRules ::: topAxioms ::: equalityAxioms ::: named
119 }
120
121 object RuleGenerator
122 extends RDFoxAxiomConverter(
123 Variable.create("X"),
124 ontology.unsafeRoles,
125 SkolemStrategy.None,
126 Empty
127 ) {
128
129 private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = {
130 val unfold = ontology.unfold(axiom).toList
131 // Fresh Variables
132 val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom))
133 val varX = Variable.create("X")
134 // Predicates
135 val atomA: TupleTableAtom = {
136 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
137 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls)
138 }
139 def predIN(t: Term): TupleTableAtom = {
140 TupleTableAtom.rdf(
141 t,
142 RSA.rsa("IN"),
143 RSA.rsa(unfold.hashCode.toString)
144 )
145 }
146 def notIn(t: Term): Negation = Negation.create(predIN(t))
147 val roleRf: TupleTableAtom = {
148 val visitor =
149 new RDFoxPropertyExprConverter(varX, v0, Forward)
150 axiom.getSuperClass
151 .asInstanceOf[OWLObjectSomeValuesFrom]
152 .getProperty
153 .accept(visitor)
154 .head
155 }
156 val atomB: TupleTableAtom = {
157 val cls = axiom.getSuperClass
158 .asInstanceOf[OWLObjectSomeValuesFrom]
159 .getFiller
160 .asInstanceOf[OWLClass]
161 .getIRI
162 TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls)
163 }
164 // TODO: To be consistent with the specifics of the visitor we are
165 // returning facts as `Rule`s with true body. While this is correct
166 // there is an easier way to import facts into RDFox. Are we able to
167 // do that?
168 val facts = unfold.map(x => Rule.create(predIN(x)))
169 val rules = List(
170 Rule.create(roleRf, atomA, notIn(varX)),
171 Rule.create(atomB, atomA, notIn(varX))
172 )
173 facts ++ rules
174 }
175
176 private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = {
177 val roleR =
178 axiom.getSuperClass
179 .asInstanceOf[OWLObjectSomeValuesFrom]
180 .getProperty
181 if (ontology.confl(roleR) contains roleR) {
182 // Fresh Variables
183 val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom))
184 val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom))
185 val v2 = RSA.rsa("v2_" ++ RSA.hashed(axiom))
186 // Predicates
187 def atomA(t: Term): TupleTableAtom = {
188 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
189 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
190 }
191 def roleRf(t1: Term, t2: Term): TupleTableAtom = {
192 val visitor =
193 new RDFoxPropertyExprConverter(t1, t2, Forward)
194 roleR.accept(visitor).head
195 }
196 def atomB(t: Term): TupleTableAtom = {
197 val cls = axiom.getSuperClass
198 .asInstanceOf[OWLObjectSomeValuesFrom]
199 .getFiller
200 .asInstanceOf[OWLClass]
201 .getIRI
202 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
203 }
204 //Rules
205 List(
206 Rule.create(roleRf(v0, v1), atomA(v0)),
207 Rule.create(atomB(v1), atomA(v0)),
208 Rule.create(roleRf(v1, v2), atomA(v1)),
209 Rule.create(atomB(v2), atomA(v1))
210 )
211 } else {
212 List()
213 }
214 }
215
216 private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = {
217 val cycle = ontology.cycle(axiom).toList
218 val roleR =
219 axiom.getSuperClass
220 .asInstanceOf[OWLObjectSomeValuesFrom]
221 .getProperty
222 // Fresh Variables
223 val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom))
224 // Predicates
225 def atomA(t: Term): TupleTableAtom = {
226 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
227 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
228 }
229 def roleRf(t: Term): TupleTableAtom = {
230 val visitor =
231 new RDFoxPropertyExprConverter(t, v1, Forward)
232 roleR.accept(visitor).head
233 }
234 val atomB: TupleTableAtom = {
235 val cls = axiom.getSuperClass
236 .asInstanceOf[OWLObjectSomeValuesFrom]
237 .getFiller
238 .asInstanceOf[OWLClass]
239 .getIRI
240 TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls)
241 }
242 cycle.flatMap { x =>
243 List(
244 Rule.create(roleRf(x), atomA(x)),
245 Rule.create(atomB, atomA(x))
246 )
247 }
248 }
249
250 override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = {
251 if (axiom.isT5) {
252 // TODO: get role in T5 axiom
253 // Assuming one role here
254 val role = axiom.objectPropertyExpressionsInSignature(0)
255 if (ontology.unsafeRoles contains role) {
256 val visitor =
257 new RDFoxAxiomConverter(
258 Variable.create("X"),
259 ontology.unsafeRoles,
260 SkolemStrategy.Standard(axiom.toString),
261 Forward
262 )
263 axiom.accept(visitor)
264 } else {
265 rules1(axiom) ::: rules2(axiom) ::: rules3(axiom)
266 }
267 } else {
268 // Fallback to standard OWL to LP translation
269 super.visit(axiom)
270 }
271 }
272
273 override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = {
274 val varX = Variable.create("X")
275 val visitorF = new RDFoxAxiomConverter(
276 varX,
277 ontology.unsafeRoles,
278 SkolemStrategy.None,
279 Forward
280 )
281 val visitorB = new RDFoxAxiomConverter(
282 varX,
283 ontology.unsafeRoles,
284 SkolemStrategy.None,
285 Backward
286 )
287 axiom.accept(visitorB) ::: axiom.accept(visitorF)
288 }
289
290 }
291
292}
diff --git a/src/main/scala/rsacomb/Main.scala b/src/main/scala/rsacomb/Main.scala
index 3963981..8270205 100644
--- a/src/main/scala/rsacomb/Main.scala
+++ b/src/main/scala/rsacomb/Main.scala
@@ -52,7 +52,7 @@ object RSAComb extends App {
52 * case. 52 * case.
53 */ 53 */
54 54
55 val ontology: RSAOntology = RSA.loadOntology(ontoPath) 55 val ontology = RSAOntology(ontoPath)
56 if (ontology.isRSA) { 56 if (ontology.isRSA) {
57 57
58 /* Load query */ 58 /* Load query */
diff --git a/src/main/scala/rsacomb/RDFoxUtil.scala b/src/main/scala/rsacomb/RDFoxUtil.scala
index d391d41..653c51f 100644
--- a/src/main/scala/rsacomb/RDFoxUtil.scala
+++ b/src/main/scala/rsacomb/RDFoxUtil.scala
@@ -15,6 +15,8 @@ import tech.oxfordsemantic.jrdfox.formats.SPARQLParser
15import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFox_IRI} 15import tech.oxfordsemantic.jrdfox.logic.expression.{IRI => RDFox_IRI}
16import org.semanticweb.owlapi.model.{IRI => OWL_IRI} 16import org.semanticweb.owlapi.model.{IRI => OWL_IRI}
17 17
18import scala.collection.JavaConverters._
19
18object RDFoxUtil { 20object RDFoxUtil {
19 21
20 implicit def rdfox2owlapi(iri: RDFox_IRI): OWL_IRI = { 22 implicit def rdfox2owlapi(iri: RDFox_IRI): OWL_IRI = {
@@ -29,6 +31,14 @@ object RDFoxUtil {
29 RDFox_IRI.create(iri) 31 RDFox_IRI.create(iri)
30 } 32 }
31 33
34 implicit def javaToScalaList[A](list: java.util.List[A]): List[A] = {
35 list.asScala.toList
36 }
37
38 implicit def scalaToJavaList[A](list: List[A]): java.util.List[A] = {
39 list.asJava
40 }
41
32 def openConnection( 42 def openConnection(
33 dataStore: String 43 dataStore: String
34 ): (ServerConnection, DataStoreConnection) = { 44 ): (ServerConnection, DataStoreConnection) = {
diff --git a/src/main/scala/rsacomb/RSA.scala b/src/main/scala/rsacomb/RSA.scala
index dd09899..b0e140b 100644
--- a/src/main/scala/rsacomb/RSA.scala
+++ b/src/main/scala/rsacomb/RSA.scala
@@ -1,13 +1,11 @@
1package rsacomb 1package rsacomb
2 2
3/* Java imports */ 3/* Java imports */
4import java.io.File
5import java.util.Map 4import java.util.Map
6 5
7import tech.oxfordsemantic.jrdfox.formats.SPARQLParser 6import tech.oxfordsemantic.jrdfox.formats.SPARQLParser
8import tech.oxfordsemantic.jrdfox.Prefixes 7import tech.oxfordsemantic.jrdfox.Prefixes
9import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI} 8import tech.oxfordsemantic.jrdfox.logic.expression.{Variable, IRI}
10import org.semanticweb.owlapi.apibinding.OWLManager
11import org.semanticweb.owlapi.model.OWLOntology 9import org.semanticweb.owlapi.model.OWLOntology
12import org.semanticweb.owlapi.model.{ 10import org.semanticweb.owlapi.model.{
13 OWLAxiom, 11 OWLAxiom,
@@ -18,7 +16,7 @@ import org.semanticweb.owlapi.model.{
18// Debug only 16// Debug only
19import scala.collection.JavaConverters._ 17import scala.collection.JavaConverters._
20 18
21object RSA extends RSAOntology with RSAAxiom { 19object RSA extends RSAAxiom {
22 20
23 val Prefixes: Prefixes = new Prefixes() 21 val Prefixes: Prefixes = new Prefixes()
24 Prefixes.declarePrefix(":", "http://example.com/rsa_example.owl#") 22 Prefixes.declarePrefix(":", "http://example.com/rsa_example.owl#")
@@ -28,6 +26,7 @@ object RSA extends RSAOntology with RSAAxiom {
28 Prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#") 26 Prefixes.declarePrefix("owl:", "http://www.w3.org/2002/07/owl#")
29 27
30 val EquivTo: IRI = this.rsa("EquivTo") 28 val EquivTo: IRI = this.rsa("EquivTo")
29 val Named: IRI = this.rsa("NAMED")
31 30
32 // Counter used to implement a simple fresh variable generator 31 // Counter used to implement a simple fresh variable generator
33 private var counter = -1; 32 private var counter = -1;
@@ -61,10 +60,4 @@ object RSA extends RSAOntology with RSAAxiom {
61 this.hashed(cls1, prop, cls2) 60 this.hashed(cls1, prop, cls2)
62 } 61 }
63 62
64 // TODO: move this somewhere else... maybe an OntoUtils class or something.
65 def loadOntology(onto: File): OWLOntology = {
66 val manager = OWLManager.createOWLOntologyManager()
67 manager.loadOntologyFromOntologyDocument(onto)
68 }
69
70} // object RSA 63} // object RSA
diff --git a/src/main/scala/rsacomb/RSAOntology.scala b/src/main/scala/rsacomb/RSAOntology.scala
index dd65116..52bff37 100644
--- a/src/main/scala/rsacomb/RSAOntology.scala
+++ b/src/main/scala/rsacomb/RSAOntology.scala
@@ -3,6 +3,8 @@ package rsacomb
3/* Java imports */ 3/* Java imports */
4import java.util.HashMap 4import java.util.HashMap
5import java.util.stream.{Collectors, Stream} 5import java.util.stream.{Collectors, Stream}
6import java.io.File
7import org.semanticweb.owlapi.apibinding.OWLManager
6 8
7import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom} 9import org.semanticweb.owlapi.model.{OWLOntology, OWLAxiom}
8import org.semanticweb.owlapi.model.{ 10import org.semanticweb.owlapi.model.{
@@ -46,663 +48,326 @@ import org.semanticweb.owlapi.model.OWLObjectInverseOf
46 48
47import suffix.{Empty, Forward, Backward, Inverse} 49import suffix.{Empty, Forward, Backward, Inverse}
48 50
49object RSAOntology {} 51object RSAOntology {
50/* Wrapper trait for the implicit class `RSAOntology`.
51 */
52trait RSAOntology {
53 52
54 /* Implements additional features to reason about RSA ontologies 53 def apply(ontology: OWLOntology): RSAOntology = new RSAOntology(ontology)
55 * on top of `OWLOntology` from the OWLAPI.
56 */
57 implicit class RSAOntology(ontology: OWLOntology) extends RSAAxiom {
58
59 // Gather TBox/RBox/ABox from original ontology
60 lazy val tbox: List[OWLAxiom] =
61 ontology
62 .tboxAxioms(Imports.INCLUDED)
63 .collect(Collectors.toList())
64 .asScala
65 .toList
66
67 lazy val rbox: List[OWLAxiom] =
68 ontology
69 .rboxAxioms(Imports.INCLUDED)
70 .collect(Collectors.toList())
71 .asScala
72 .toList
73
74 lazy val abox: List[OWLAxiom] =
75 ontology
76 .aboxAxioms(Imports.INCLUDED)
77 .collect(Collectors.toList())
78 .asScala
79 .toList
80
81 lazy val axioms: List[OWLAxiom] = abox ++ tbox ++ rbox
82
83 /* Retrieve individuals in the original ontology
84 */
85 lazy val individuals: List[IRI] =
86 ontology
87 .getIndividualsInSignature()
88 .asScala
89 .map(_.getIRI)
90 .map(RDFoxUtil.owlapi2rdfox)
91 .toList
92
93 lazy val concepts: List[OWLClass] =
94 ontology.getClassesInSignature().asScala.toList
95
96 lazy val roles: List[OWLObjectPropertyExpression] =
97 axioms
98 .flatMap(_.objectPropertyExpressionsInSignature)
99 .distinct
100
101 // OWLAPI reasoner for same easier tasks
102 private val reasoner =
103 (new StructuralReasonerFactory()).createReasoner(ontology)
104
105 /* Steps for RSA check
106 * 1) convert ontology axioms into LP rules
107 * 2) call RDFox on the onto and compute materialization
108 * 3) build graph from E(x,y) facts
109 * 4) check if the graph is tree-like
110 * ideally this annotates the graph with info about the reasons
111 * why the ontology might not be RSA. This could help a second
112 * step of approximation of an Horn-ALCHOIQ to RSA
113 */
114 lazy val isRSA: Boolean = {
115
116 val unsafe = this.unsafeRoles
117
118 /* DEBUG: print rules in DL syntax and unsafe roles */
119 //val renderer = new DLSyntaxObjectRenderer()
120 //println("\nDL rules:")
121 //axioms.foreach(x => println(renderer.render(x)))
122 //println("\nUnsafe roles:")
123 //println(unsafe)
124
125 /* Ontology convertion into LP rules */
126 val datalog = for {
127 axiom <- axioms
128 visitor = new RDFoxAxiomConverter(
129 RSA.getFreshVariable(),
130 unsafe,
131 SkolemStrategy.ConstantRSA(axiom.toString),
132 Empty
133 )
134 rule <- axiom.accept(visitor)
135 } yield rule
136
137 /* DEBUG: print datalog rules */
138 //println("\nDatalog roles:")
139 //datalog.foreach(println)
140
141 // Open connection with RDFox
142 val (server, data) = RDFoxUtil.openConnection("RSACheck")
143 // Add Data (hardcoded for now)
144 data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .")
145
146 /* Add built-in rules
147 */
148 data.importData(
149 UpdateType.ADDITION,
150 RSA.Prefixes,
151 "<http://127.0.0.1/E>[?X,?Y] :- <http://127.0.0.1/PE>[?X,?Y], <http://127.0.0.1/U>[?X], <http://127.0.0.1/U>[?Y] ."
152 )
153 54
154 /* Add built-in rules 55 def apply(ontology: File): RSAOntology =
155 */ 56 new RSAOntology(loadOntology(ontology))
156 // data.importData(
157 // UpdateType.ADDITION,
158 // RSA.Prefixes,
159 // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ."
160 // )
161
162 /* Add ontology rules
163 */
164 data.addRules(datalog.asJava)
165
166 /* Build graph
167 */
168 val graph = this.rsaGraph(data);
169 //println(graph)
170
171 // Close connection to RDFox
172 RDFoxUtil.closeConnection(server, data)
173
174 /* To check if the graph is tree-like we check for acyclicity in a
175 * undirected graph.
176 *
177 * TODO: Implement additional checks (taking into account equality)
178 */
179 graph.isAcyclic
180 }
181 57
182 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = { 58 private def loadOntology(onto: File): OWLOntology = {
183 59 val manager = OWLManager.createOWLOntologyManager()
184 /* DEBUG: print rules in DL syntax */ 60 manager.loadOntologyFromOntologyDocument(onto)
185 //val renderer = new DLSyntaxObjectRenderer() 61 }
186 62}
187 /* Checking for (1) unsafety condition:
188 *
189 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
190 * if there exists a role r2 (different from top) appearing in an axiom
191 * of type T3 and r1 is a subproperty of the inverse of r2.
192 */
193 val unsafe1 = for {
194 axiom <- tbox
195 if axiom.isT5
196 role1 <- axiom.objectPropertyExpressionsInSignature
197 roleSuper =
198 role1 +: reasoner
199 .superObjectProperties(role1)
200 .collect(Collectors.toList())
201 .asScala
202 roleSuperInv = roleSuper.map(_.getInverseProperty)
203 axiom <- tbox
204 if axiom.isT3 && !axiom.isT3top
205 role2 <- axiom.objectPropertyExpressionsInSignature
206 if roleSuperInv.contains(role2)
207 } yield role1
208
209 /* Checking for (2) unsafety condition:
210 *
211 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
212 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
213 * subproperty of either p2 or the inverse of p2.
214 *
215 */
216 val unsafe2 = for {
217 axiom <- tbox
218 if axiom.isT5
219 role1 <- axiom.objectPropertyExpressionsInSignature
220 roleSuper =
221 role1 +: reasoner
222 .superObjectProperties(role1)
223 .collect(Collectors.toList())
224 .asScala
225 roleSuperInv = roleSuper.map(_.getInverseProperty)
226 axiom <- tbox
227 if axiom.isT4
228 role2 <- axiom.objectPropertyExpressionsInSignature
229 if roleSuper.contains(role2) || roleSuperInv.contains(role2)
230 } yield role1
231
232 (unsafe1 ++ unsafe2).toList
233 }
234 63
235 private def rsaGraph( 64class RSAOntology(val ontology: OWLOntology) extends RSAAxiom {
236 data: DataStoreConnection 65
237 ): Graph[Resource, UnDiEdge] = { 66 // Gather TBox/RBox/ABox from original ontology
238 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" 67 val tbox: List[OWLAxiom] =
239 val cursor = 68 ontology
240 data.createCursor(RSA.Prefixes, query, new HashMap[String, String]()); 69 .tboxAxioms(Imports.INCLUDED)
241 var mul = cursor.open() 70 .collect(Collectors.toList())
242 var edges: List[UnDiEdge[Resource]] = List() 71 .asScala
243 while (mul > 0) { 72 .toList
244 edges = UnDiEdge(cursor.getResource(0), cursor.getResource(1)) :: edges 73
245 mul = cursor.advance() 74 val rbox: List[OWLAxiom] =
246 } 75 ontology
247 Graph(edges: _*) 76 .rboxAxioms(Imports.INCLUDED)
248 } 77 .collect(Collectors.toList())
78 .asScala
79 .toList
80
81 val abox: List[OWLAxiom] =
82 ontology
83 .aboxAxioms(Imports.INCLUDED)
84 .collect(Collectors.toList())
85 .asScala
86 .toList
87
88 val axioms: List[OWLAxiom] = abox ::: tbox ::: rbox
89
90 /* Retrieve individuals in the original ontology
91 */
92 val individuals: List[IRI] =
93 ontology
94 .getIndividualsInSignature()
95 .asScala
96 .map(_.getIRI)
97 .map(RDFoxUtil.owlapi2rdfox)
98 .toList
99
100 val concepts: List[OWLClass] =
101 ontology.getClassesInSignature().asScala.toList
102
103 val roles: List[OWLObjectPropertyExpression] =
104 axioms
105 .flatMap(_.objectPropertyExpressionsInSignature)
106 .distinct
107
108 // OWLAPI reasoner for same easier tasks
109 private val reasoner =
110 (new StructuralReasonerFactory()).createReasoner(ontology)
111
112 /* Steps for RSA check
113 * 1) convert ontology axioms into LP rules
114 * 2) call RDFox on the onto and compute materialization
115 * 3) build graph from E(x,y) facts
116 * 4) check if the graph is tree-like
117 * ideally this annotates the graph with info about the reasons
118 * why the ontology might not be RSA. This could help a second
119 * step of approximation of an Horn-ALCHOIQ to RSA
120 */
121 lazy val isRSA: Boolean = {
122
123 val unsafe = this.unsafeRoles
124
125 /* DEBUG: print rules in DL syntax and unsafe roles */
126 //val renderer = new DLSyntaxObjectRenderer()
127 //println("\nDL rules:")
128 //axioms.foreach(x => println(renderer.render(x)))
129 //println("\nUnsafe roles:")
130 //println(unsafe)
131
132 /* Ontology convertion into LP rules */
133 val datalog = for {
134 axiom <- axioms
135 visitor = new RDFoxAxiomConverter(
136 RSA.getFreshVariable(),
137 unsafe,
138 SkolemStrategy.ConstantRSA(axiom.toString),
139 Empty
140 )
141 rule <- axiom.accept(visitor)
142 } yield rule
249 143
250 def filteringProgram( 144 /* DEBUG: print datalog rules */
251 query: SelectQuery, 145 //println("\nDatalog roles:")
252 nis: List[Term] 146 //datalog.foreach(println)
253 ): FilteringProgram =
254 new FilteringProgram(query, nis)
255
256 // TODO: the following functions needs testing
257 def confl(
258 role: OWLObjectPropertyExpression
259 ): Set[OWLObjectPropertyExpression] = {
260
261 val invSuperRoles = reasoner
262 .superObjectProperties(role)
263 .collect(Collectors.toSet())
264 .asScala
265 .addOne(role)
266 .map(_.getInverseProperty)
267
268 invSuperRoles
269 .flatMap(x =>
270 reasoner
271 .subObjectProperties(x)
272 .collect(Collectors.toSet())
273 .asScala
274 .addOne(x)
275 )
276 .filterNot(_.isOWLBottomObjectProperty())
277 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
278 }
279 147
280 def self(axiom: OWLSubClassOfAxiom): Set[Term] = { 148 // Open connection with RDFox
281 // Assuming just one role in the signature of a T5 axiom 149 val (server, data) = RDFoxUtil.openConnection("RSACheck")
282 val role = axiom.objectPropertyExpressionsInSignature(0) 150 // Add Data (hardcoded for now)
283 if (this.confl(role).contains(role)) { 151 data.importData(UpdateType.ADDITION, RSA.Prefixes, ":a a :A .")
284 Set(
285 RSA.rsa("v0_" ++ RSA.hashed(axiom)),
286 RSA.rsa("v1_" ++ RSA.hashed(axiom))
287 )
288 } else {
289 Set()
290 }
291 }
292 152
293 // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = { 153 /* Add built-in rules
294 // // Assuming just one role in the signature of a T5 axiom 154 */
295 // val roleR = axiom.objectPropertyExpressionsInSignature(0) 155 data.importData(
296 // val conflR = this.confl(roleR) 156 UpdateType.ADDITION,
297 // // We just need the TBox to find 157 RSA.Prefixes,
298 // val tbox = ontology 158 "<http://127.0.0.1/E>[?X,?Y] :- <http://127.0.0.1/PE>[?X,?Y], <http://127.0.0.1/U>[?X], <http://127.0.0.1/U>[?Y] ."
299 // .tboxAxioms(Imports.INCLUDED) 159 )
300 // .collect(Collectors.toSet())
301 // .asScala
302 // for {
303 // axiom1 <- tbox
304 // // TODO: is this an optimization or an error?
305 // if axiom1.isT5
306 // // We expect only one role coming out of a T5 axiom
307 // roleS <- axiom1.objectPropertyExpressionsInSignature
308 // // Triples ordering is among triples involving safe roles.
309 // if !unsafeRoles.contains(roleS)
310 // if conflR.contains(roleS)
311 // individual =
312 // if (axiom.hashCode < axiom1.hashCode) {
313 // RSA.rsa("v0_" ++ axiom1.hashCode.toString())
314 // } else {
315 // RSA.rsa("v1_" ++ axiom1.hashCode.toString())
316 // }
317 // } yield individual
318 // }
319
320 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
321 // TODO: we can actually use `toTriple` from `RSAAxiom`
322 val classes =
323 axiom.classesInSignature.collect(Collectors.toList()).asScala
324 val classA = classes(0)
325 val roleR = axiom
326 .objectPropertyExpressionsInSignature(0)
327 .asInstanceOf[OWLObjectProperty]
328 val classB = classes(1)
329 cycle_aux(classA, roleR, classB)
330 }
331 160
332 def cycle_aux( 161 /* Add built-in rules
333 classA: OWLClass, 162 */
334 roleR: OWLObjectProperty, 163 // data.importData(
335 classB: OWLClass 164 // UpdateType.ADDITION,
336 ): Set[Term] = { 165 // RSA.Prefixes,
337 val conflR = this.confl(roleR) 166 // "[?entity, a, ?superClass] :- [?entity, a, ?class], [?class, rdfs:subClassOf, ?superClass] ."
338 val classes = ontology 167 // )
339 .classesInSignature(Imports.INCLUDED)
340 .collect(Collectors.toSet())
341 .asScala
342 for {
343 classD <- classes
344 roleS <- conflR
345 classC <- classes
346 // Keeping this check for now
347 if !unsafeRoles.contains(roleS)
348 tripleARB = RSA.hashed(classA, roleR, classB)
349 tripleDSC = RSA.hashed(classD, roleS, classC)
350 individual =
351 if (tripleARB > tripleDSC) {
352 RSA.rsa("v1_" ++ tripleDSC)
353 } else {
354 // Note that this is also the case for
355 // `tripleARB == tripleDSC`
356 RSA.rsa("v0_" ++ tripleDSC)
357 }
358 } yield individual
359 }
360 168
361 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] = 169 /* Add ontology rules
362 this.self(axiom) | this.cycle(axiom) 170 */
363 171 data.addRules(datalog.asJava)
364 object canonicalModel {
365
366 import RDFoxUtil._
367
368 val named: List[Rule] =
369 individuals.map(a =>
370 Rule.create(
371 TupleTableAtom.rdf(a, IRI.RDF_TYPE, RSA.rsa("NAMED"))
372 )
373 )
374
375 val rolesAdditionalRules: List[Rule] = {
376 // Given a role (predicate) compute additional logic rules
377 def additional(pred: String): Seq[Rule] = {
378 val varX = Variable.create("X")
379 val varY = Variable.create("Y")
380 List(
381 Rule.create(
382 TupleTableAtom.rdf(varX, IRI.create(pred), varY),
383 TupleTableAtom
384 .rdf(
385 varX,
386 IRI.create(pred :: Forward),
387 varY
388 )
389 ),
390 Rule.create(
391 TupleTableAtom.rdf(varX, IRI.create(pred), varY),
392 TupleTableAtom
393 .rdf(
394 varX,
395 IRI.create(pred :: Backward),
396 varY
397 )
398 ),
399 Rule.create(
400 TupleTableAtom.rdf(varX, IRI.create(pred ++ "_inv"), varY),
401 TupleTableAtom
402 .rdf(
403 varX,
404 IRI.create(pred :: Forward + Inverse),
405 varY
406 )
407 ),
408 Rule.create(
409 TupleTableAtom.rdf(varX, IRI.create(pred ++ "_inv"), varY),
410 TupleTableAtom
411 .rdf(
412 varX,
413 IRI.create(pred :: Backward + Inverse),
414 varY
415 )
416 ),
417 Rule.create(
418 TupleTableAtom.rdf(
419 varY,
420 IRI.create(pred :: Backward + Inverse),
421 varX
422 ),
423 TupleTableAtom
424 .rdf(
425 varX,
426 IRI.create(pred :: Forward),
427 varY
428 )
429 ),
430 Rule.create(
431 TupleTableAtom.rdf(
432 varY,
433 IRI.create(pred :: Forward + Inverse),
434 varX
435 ),
436 TupleTableAtom.rdf(
437 varX,
438 IRI.create(pred :: Backward),
439 varY
440 )
441 ),
442 Rule.create(
443 TupleTableAtom.rdf(
444 varY,
445 IRI.create(pred :: Backward),
446 varX
447 ),
448 TupleTableAtom
449 .rdf(
450 varX,
451 IRI.create(pred :: Forward + Inverse),
452 varY
453 )
454 ),
455 Rule.create(
456 TupleTableAtom.rdf(
457 varY,
458 IRI.create(pred :: Forward),
459 varX
460 ),
461 TupleTableAtom.rdf(
462 varX,
463 IRI.create(pred :: Backward + Inverse),
464 varY
465 )
466 )
467 )
468 }
469 // Compute additional rules per role
470 axioms
471 .flatMap(
472 _.objectPropertiesInSignature.collect(Collectors.toSet()).asScala
473 )
474 .distinct
475 .map(_.getIRI.getIRIString)
476 .flatMap(additional)
477 }
478
479 private lazy val topAxioms: List[Rule] =
480 concepts.map(c => {
481 val x = Variable.create("X")
482 Rule.create(
483 TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING),
484 TupleTableAtom.rdf(x, IRI.RDF_TYPE, c.getIRI)
485 )
486 }) ++
487 roles.map(r => {
488 val x = Variable.create("X")
489 val y = Variable.create("Y")
490 val iri: IRI = r match {
491 case x: OWLObjectProperty => x.getIRI
492 case x: OWLObjectInverseOf =>
493 IRI.create(x.getNamedProperty.getIRI.getIRIString ++ "_inv")
494 case x => x.getNamedProperty.getIRI
495 }
496 Rule.create(
497 List(
498 TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING),
499 TupleTableAtom.rdf(y, IRI.RDF_TYPE, IRI.THING)
500 ).asJava,
501 List[BodyFormula](TupleTableAtom.rdf(x, iri, y)).asJava
502 )
503 })
504
505 private val equalityAxioms: List[Rule] = {
506 val x = Variable.create("X")
507 val y = Variable.create("Y")
508 val z = Variable.create("Z")
509 List(
510 Rule.create(
511 TupleTableAtom.rdf(x, RSA.EquivTo, x),
512 TupleTableAtom.rdf(x, IRI.RDF_TYPE, IRI.THING)
513 ),
514 Rule.create(
515 TupleTableAtom.rdf(y, RSA.EquivTo, x),
516 TupleTableAtom.rdf(x, RSA.EquivTo, y)
517 ),
518 Rule.create(
519 TupleTableAtom.rdf(x, RSA.EquivTo, z),
520 TupleTableAtom.rdf(x, RSA.EquivTo, y),
521 TupleTableAtom.rdf(y, RSA.EquivTo, z)
522 )
523 )
524 }
525
526 val rules: List[Rule] = {
527 // Compute rules from ontology axioms
528 val rules = axioms.flatMap(_.accept(this.ProgramGenerator))
529 // Return full set of rules
530 rules ++ rolesAdditionalRules ++ topAxioms ++ equalityAxioms ++ named
531 }
532
533 object ProgramGenerator
534 extends RDFoxAxiomConverter(
535 Variable.create("X"),
536 unsafeRoles,
537 SkolemStrategy.None,
538 Empty
539 ) {
540
541 private def rules1(axiom: OWLSubClassOfAxiom): List[Rule] = {
542 val unfold = ontology.unfold(axiom).toList
543 // Fresh Variables
544 val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom))
545 val varX = Variable.create("X")
546 // Predicates
547 val atomA: TupleTableAtom = {
548 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
549 TupleTableAtom.rdf(varX, IRI.RDF_TYPE, cls)
550 }
551 def in(t: Term): TupleTableAtom = {
552 TupleTableAtom.rdf(
553 t,
554 RSA.rsa("IN"),
555 RSA.rsa(unfold.hashCode.toString)
556 )
557 }
558 def notIn(t: Term): Negation = Negation.create(in(t))
559 val roleRf: TupleTableAtom = {
560 val visitor =
561 new RDFoxPropertyExprConverter(varX, v0, Forward)
562 axiom.getSuperClass
563 .asInstanceOf[OWLObjectSomeValuesFrom]
564 .getProperty
565 .accept(visitor)
566 .head
567 }
568 val atomB: TupleTableAtom = {
569 val cls = axiom.getSuperClass
570 .asInstanceOf[OWLObjectSomeValuesFrom]
571 .getFiller
572 .asInstanceOf[OWLClass]
573 .getIRI
574 TupleTableAtom.rdf(v0, IRI.RDF_TYPE, cls)
575 }
576 // TODO: To be consistent with the specifics of the visitor we are
577 // returning facts as `Rule`s with true body. While this is correct
578 // there is an easier way to import facts into RDFox. Are we able to
579 // do that?
580 val facts = unfold.map(x => Rule.create(in(x)))
581 val rules = List(
582 Rule.create(roleRf, atomA, notIn(varX)),
583 Rule.create(atomB, atomA, notIn(varX))
584 )
585 facts ++ rules
586 }
587 172
588 private def rules2(axiom: OWLSubClassOfAxiom): List[Rule] = { 173 /* Build graph
589 val roleR = 174 */
590 axiom.getSuperClass 175 val graph = this.rsaGraph(data);
591 .asInstanceOf[OWLObjectSomeValuesFrom] 176 //println(graph)
592 .getProperty
593 if (ontology.confl(roleR) contains roleR) {
594 // Fresh Variables
595 val v0 = RSA.rsa("v0_" ++ RSA.hashed(axiom))
596 val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom))
597 val v2 = RSA.rsa("v2_" ++ RSA.hashed(axiom))
598 // Predicates
599 def atomA(t: Term): TupleTableAtom = {
600 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
601 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
602 }
603 def roleRf(t1: Term, t2: Term): TupleTableAtom = {
604 val visitor =
605 new RDFoxPropertyExprConverter(t1, t2, Forward)
606 roleR.accept(visitor).head
607 }
608 def atomB(t: Term): TupleTableAtom = {
609 val cls = axiom.getSuperClass
610 .asInstanceOf[OWLObjectSomeValuesFrom]
611 .getFiller
612 .asInstanceOf[OWLClass]
613 .getIRI
614 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
615 }
616 //Rules
617 List(
618 Rule.create(roleRf(v0, v1), atomA(v0)),
619 Rule.create(atomB(v1), atomA(v0)),
620 Rule.create(roleRf(v1, v2), atomA(v1)),
621 Rule.create(atomB(v2), atomA(v1))
622 )
623 } else {
624 List()
625 }
626 }
627 177
628 private def rules3(axiom: OWLSubClassOfAxiom): List[Rule] = { 178 // Close connection to RDFox
629 val cycle = ontology.cycle(axiom).toList 179 RDFoxUtil.closeConnection(server, data)
630 val roleR =
631 axiom.getSuperClass
632 .asInstanceOf[OWLObjectSomeValuesFrom]
633 .getProperty
634 // Fresh Variables
635 val v1 = RSA.rsa("v1_" ++ RSA.hashed(axiom))
636 // Predicates
637 def atomA(t: Term): TupleTableAtom = {
638 val cls = axiom.getSubClass.asInstanceOf[OWLClass].getIRI
639 TupleTableAtom.rdf(t, IRI.RDF_TYPE, cls)
640 }
641 def roleRf(t: Term): TupleTableAtom = {
642 val visitor =
643 new RDFoxPropertyExprConverter(t, v1, Forward)
644 roleR.accept(visitor).head
645 }
646 val atomB: TupleTableAtom = {
647 val cls = axiom.getSuperClass
648 .asInstanceOf[OWLObjectSomeValuesFrom]
649 .getFiller
650 .asInstanceOf[OWLClass]
651 .getIRI
652 TupleTableAtom.rdf(v1, IRI.RDF_TYPE, cls)
653 }
654 cycle.flatMap { x =>
655 List(
656 Rule.create(roleRf(x), atomA(x)),
657 Rule.create(atomB, atomA(x))
658 )
659 }
660 }
661 180
662 override def visit(axiom: OWLSubClassOfAxiom): List[Rule] = { 181 /* To check if the graph is tree-like we check for acyclicity in a
663 if (axiom.isT5) { 182 * undirected graph.
664 // TODO: get role in T5 axiom 183 *
665 // Assuming one role here 184 * TODO: Implement additional checks (taking into account equality)
666 val role = axiom.objectPropertyExpressionsInSignature(0) 185 */
667 if (ontology.unsafeRoles.contains(role)) { 186 graph.isAcyclic
668 val visitor = 187 }
669 new RDFoxAxiomConverter(
670 Variable.create("X"),
671 ontology.unsafeRoles,
672 SkolemStrategy.Standard(axiom.toString),
673 Forward
674 )
675 axiom.accept(visitor)
676 } else {
677 rules1(axiom) ++ rules2(axiom) ++ rules3(axiom)
678 }
679 } else {
680 // Fallback to standard OWL to LP translation
681 super.visit(axiom)
682 }
683 }
684 188
685 override def visit(axiom: OWLSubObjectPropertyOfAxiom): List[Rule] = { 189 lazy val unsafeRoles: List[OWLObjectPropertyExpression] = {
686 val varX = Variable.create("X")
687 val varY = Variable.create("Y")
688 val visitorF = new RDFoxAxiomConverter(
689 Variable.create("X"),
690 ontology.unsafeRoles,
691 SkolemStrategy.None,
692 Forward
693 )
694 val visitorB = new RDFoxAxiomConverter(
695 Variable.create("X"),
696 ontology.unsafeRoles,
697 SkolemStrategy.None,
698 Backward
699 )
700 axiom.accept(visitorB) ++ axiom.accept(visitorF)
701 }
702 190
703 } 191 /* DEBUG: print rules in DL syntax */
192 //val renderer = new DLSyntaxObjectRenderer()
704 193
194 /* Checking for (1) unsafety condition:
195 *
196 * For all roles r1 appearing in an axiom of type T5, r1 is unsafe
197 * if there exists a role r2 (different from top) appearing in an axiom
198 * of type T3 and r1 is a subproperty of the inverse of r2.
199 */
200 val unsafe1 = for {
201 axiom <- tbox
202 if axiom.isT5
203 role1 <- axiom.objectPropertyExpressionsInSignature
204 roleSuper =
205 role1 +: reasoner
206 .superObjectProperties(role1)
207 .collect(Collectors.toList())
208 .asScala
209 roleSuperInv = roleSuper.map(_.getInverseProperty)
210 axiom <- tbox
211 if axiom.isT3 && !axiom.isT3top
212 role2 <- axiom.objectPropertyExpressionsInSignature
213 if roleSuperInv.contains(role2)
214 } yield role1
215
216 /* Checking for (2) unsafety condition:
217 *
218 * For all roles p1 appearing in an axiom of type T5, p1 is unsafe if
219 * there exists a role p2 appearing in an axiom of type T4 and p1 is a
220 * subproperty of either p2 or the inverse of p2.
221 *
222 */
223 val unsafe2 = for {
224 axiom <- tbox
225 if axiom.isT5
226 role1 <- axiom.objectPropertyExpressionsInSignature
227 roleSuper =
228 role1 +: reasoner
229 .superObjectProperties(role1)
230 .collect(Collectors.toList())
231 .asScala
232 roleSuperInv = roleSuper.map(_.getInverseProperty)
233 axiom <- tbox
234 if axiom.isT4
235 role2 <- axiom.objectPropertyExpressionsInSignature
236 if roleSuper.contains(role2) || roleSuperInv.contains(role2)
237 } yield role1
238
239 (unsafe1 ++ unsafe2).toList
240 }
241
242 private def rsaGraph(
243 data: DataStoreConnection
244 ): Graph[Resource, UnDiEdge] = {
245 val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }"
246 val cursor =
247 data.createCursor(RSA.Prefixes, query, new HashMap[String, String]());
248 var mul = cursor.open()
249 var edges: List[UnDiEdge[Resource]] = List()
250 while (mul > 0) {
251 edges = UnDiEdge(cursor.getResource(0), cursor.getResource(1)) :: edges
252 mul = cursor.advance()
253 }
254 Graph(edges: _*)
255 }
256
257 def filteringProgram(
258 query: SelectQuery,
259 nis: List[Term]
260 ): FilteringProgram =
261 new FilteringProgram(query, nis)
262
263 lazy val canonicalModel = new CanonicalModel(this)
264
265 // TODO: the following functions needs testing
266 def confl(
267 role: OWLObjectPropertyExpression
268 ): Set[OWLObjectPropertyExpression] = {
269
270 val invSuperRoles = reasoner
271 .superObjectProperties(role)
272 .collect(Collectors.toSet())
273 .asScala
274 .addOne(role)
275 .map(_.getInverseProperty)
276
277 invSuperRoles
278 .flatMap(x =>
279 reasoner
280 .subObjectProperties(x)
281 .collect(Collectors.toSet())
282 .asScala
283 .addOne(x)
284 )
285 .filterNot(_.isOWLBottomObjectProperty())
286 .filterNot(_.getInverseProperty.isOWLTopObjectProperty())
287 }
288
289 def self(axiom: OWLSubClassOfAxiom): Set[Term] = {
290 // Assuming just one role in the signature of a T5 axiom
291 val role = axiom.objectPropertyExpressionsInSignature(0)
292 if (this.confl(role).contains(role)) {
293 Set(
294 RSA.rsa("v0_" ++ RSA.hashed(axiom)),
295 RSA.rsa("v1_" ++ RSA.hashed(axiom))
296 )
297 } else {
298 Set()
705 } 299 }
706 } // implicit class RSAOntology 300 }
301
302 // def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
303 // // Assuming just one role in the signature of a T5 axiom
304 // val roleR = axiom.objectPropertyExpressionsInSignature(0)
305 // val conflR = this.confl(roleR)
306 // // We just need the TBox to find
307 // val tbox = ontology
308 // .tboxAxioms(Imports.INCLUDED)
309 // .collect(Collectors.toSet())
310 // .asScala
311 // for {
312 // axiom1 <- tbox
313 // // TODO: is this an optimization or an error?
314 // if axiom1.isT5
315 // // We expect only one role coming out of a T5 axiom
316 // roleS <- axiom1.objectPropertyExpressionsInSignature
317 // // Triples ordering is among triples involving safe roles.
318 // if !unsafeRoles.contains(roleS)
319 // if conflR.contains(roleS)
320 // individual =
321 // if (axiom.hashCode < axiom1.hashCode) {
322 // RSA.rsa("v0_" ++ axiom1.hashCode.toString())
323 // } else {
324 // RSA.rsa("v1_" ++ axiom1.hashCode.toString())
325 // }
326 // } yield individual
327 // }
328
329 def cycle(axiom: OWLSubClassOfAxiom): Set[Term] = {
330 // TODO: we can actually use `toTriple` from `RSAAxiom`
331 val classes =
332 axiom.classesInSignature.collect(Collectors.toList()).asScala
333 val classA = classes(0)
334 val roleR = axiom
335 .objectPropertyExpressionsInSignature(0)
336 .asInstanceOf[OWLObjectProperty]
337 val classB = classes(1)
338 cycle_aux(classA, roleR, classB)
339 }
340
341 def cycle_aux(
342 classA: OWLClass,
343 roleR: OWLObjectProperty,
344 classB: OWLClass
345 ): Set[Term] = {
346 val conflR = this.confl(roleR)
347 val classes = ontology
348 .classesInSignature(Imports.INCLUDED)
349 .collect(Collectors.toSet())
350 .asScala
351 for {
352 classD <- classes
353 roleS <- conflR
354 classC <- classes
355 // Keeping this check for now
356 if !unsafeRoles.contains(roleS)
357 tripleARB = RSA.hashed(classA, roleR, classB)
358 tripleDSC = RSA.hashed(classD, roleS, classC)
359 individual =
360 if (tripleARB > tripleDSC) {
361 RSA.rsa("v1_" ++ tripleDSC)
362 } else {
363 // Note that this is also the case for
364 // `tripleARB == tripleDSC`
365 RSA.rsa("v0_" ++ tripleDSC)
366 }
367 } yield individual
368 }
369
370 def unfold(axiom: OWLSubClassOfAxiom): Set[Term] =
371 this.self(axiom) | this.cycle(axiom)
707 372
708} // trait RSAOntology 373} // implicit class RSAOntology
diff --git a/src/test/scala/rsacomb/CanonicalModelSpec.scala b/src/test/scala/rsacomb/CanonicalModelSpec.scala
index ef27a4f..cac40a3 100644
--- a/src/test/scala/rsacomb/CanonicalModelSpec.scala
+++ b/src/test/scala/rsacomb/CanonicalModelSpec.scala
@@ -23,7 +23,7 @@ object Ontology1_CanonicalModelSpec {
23 val renderer = new DLSyntaxObjectRenderer() 23 val renderer = new DLSyntaxObjectRenderer()
24 24
25 val ontology_path: File = new File("examples/example1.ttl") 25 val ontology_path: File = new File("examples/example1.ttl")
26 val ontology: RSAOntology = RSA.loadOntology(ontology_path) 26 val ontology = RSAOntology(ontology_path)
27 val program = ontology.canonicalModel 27 val program = ontology.canonicalModel
28 28
29 val roleR = new OWLObjectPropertyImpl(RSA.base("R")) 29 val roleR = new OWLObjectPropertyImpl(RSA.base("R"))
@@ -87,7 +87,7 @@ class Ontology1_CanonicalModelSpec
87 87
88 renderer.render(AsubClassOfD) should "be converted into a single Rule" in { 88 renderer.render(AsubClassOfD) should "be converted into a single Rule" in {
89 val varX = Variable.create("X") 89 val varX = Variable.create("X")
90 val visitor = program.ProgramGenerator 90 val visitor = program.RuleGenerator
91 val rules = AsubClassOfD.accept(visitor) 91 val rules = AsubClassOfD.accept(visitor)
92 rules.loneElement shouldBe a[Rule] 92 rules.loneElement shouldBe a[Rule]
93 } 93 }
@@ -152,7 +152,7 @@ class Ontology1_CanonicalModelSpec
152 AsomeValuesFromSiC 152 AsomeValuesFromSiC
153 ) should "produce 1 rule" in { 153 ) should "produce 1 rule" in {
154 val varX = Variable.create("X") 154 val varX = Variable.create("X")
155 val visitor = program.ProgramGenerator 155 val visitor = program.RuleGenerator
156 val rules = AsomeValuesFromSiC.accept(visitor) 156 val rules = AsomeValuesFromSiC.accept(visitor)
157 rules should have length 1 157 rules should have length 1
158 } 158 }
@@ -175,7 +175,7 @@ class Ontology1_CanonicalModelSpec
175 // Rule 3 provides 48 rule (split in 2) 175 // Rule 3 provides 48 rule (split in 2)
176 // Then (1*2 + 48) + (0) + (48*2) = 146 176 // Then (1*2 + 48) + (0) + (48*2) = 146
177 val varX = Variable.create("X") 177 val varX = Variable.create("X")
178 val visitor = program.ProgramGenerator 178 val visitor = program.RuleGenerator
179 val rules = DsomeValuesFromRB.accept(visitor) 179 val rules = DsomeValuesFromRB.accept(visitor)
180 rules should have length 146 180 rules should have length 146
181 } 181 }
@@ -198,7 +198,7 @@ class Ontology1_CanonicalModelSpec
198 // Rule 3 provides 32 rule (split in 2) 198 // Rule 3 provides 32 rule (split in 2)
199 // Then (1*2 + 32) + (0) + (32*2) = 98 199 // Then (1*2 + 32) + (0) + (32*2) = 98
200 val varX = Variable.create("X") 200 val varX = Variable.create("X")
201 val visitor = program.ProgramGenerator 201 val visitor = program.RuleGenerator
202 val rules = DsomeValuesFromRB.accept(visitor) 202 val rules = DsomeValuesFromRB.accept(visitor)
203 rules should have length 146 203 rules should have length 146
204 } 204 }
@@ -207,7 +207,7 @@ class Ontology1_CanonicalModelSpec
207 SsubPropertyOfT 207 SsubPropertyOfT
208 ) should "produce 2 rules" in { 208 ) should "produce 2 rules" in {
209 val varX = Variable.create("X") 209 val varX = Variable.create("X")
210 val visitor = program.ProgramGenerator 210 val visitor = program.RuleGenerator
211 val rules = SsubPropertyOfT.accept(visitor) 211 val rules = SsubPropertyOfT.accept(visitor)
212 rules should have length 2 212 rules should have length 2
213 } 213 }
@@ -220,7 +220,7 @@ object Ontology2_CanonicalModelSpec {
220 val renderer = new DLSyntaxObjectRenderer() 220 val renderer = new DLSyntaxObjectRenderer()
221 221
222 val ontology_path: File = new File("examples/example2.owl") 222 val ontology_path: File = new File("examples/example2.owl")
223 val ontology: RSAOntology = RSA.loadOntology(ontology_path) 223 val ontology = RSAOntology(ontology_path)
224 val program = ontology.canonicalModel 224 val program = ontology.canonicalModel
225 225
226 val roleR = new OWLObjectPropertyImpl(RSA.base("R")) 226 val roleR = new OWLObjectPropertyImpl(RSA.base("R"))
@@ -326,7 +326,7 @@ class Ontology2_CanonicalModelSpec
326 renderer.render( 326 renderer.render(
327 AsomeValuesFromRB 327 AsomeValuesFromRB
328 ) should "produce 1 rule" in { 328 ) should "produce 1 rule" in {
329 val visitor = program.ProgramGenerator 329 val visitor = program.RuleGenerator
330 val rules = AsomeValuesFromRB.accept(visitor) 330 val rules = AsomeValuesFromRB.accept(visitor)
331 rules should have length 1 331 rules should have length 1
332 } 332 }
@@ -336,7 +336,7 @@ class Ontology2_CanonicalModelSpec
336 renderer.render( 336 renderer.render(
337 BsomeValuesFromSC 337 BsomeValuesFromSC
338 ) should "produce 1 rule" in { 338 ) should "produce 1 rule" in {
339 val visitor = program.ProgramGenerator 339 val visitor = program.RuleGenerator
340 val rules = BsomeValuesFromSC.accept(visitor) 340 val rules = BsomeValuesFromSC.accept(visitor)
341 rules should have length 1 341 rules should have length 1
342 } 342 }
@@ -346,7 +346,7 @@ class Ontology2_CanonicalModelSpec
346 renderer.render( 346 renderer.render(
347 CsomeValuesFromTD 347 CsomeValuesFromTD
348 ) should "produce 1 rule" in { 348 ) should "produce 1 rule" in {
349 val visitor = program.ProgramGenerator 349 val visitor = program.RuleGenerator
350 val rules = CsomeValuesFromTD.accept(visitor) 350 val rules = CsomeValuesFromTD.accept(visitor)
351 rules should have length 1 351 rules should have length 1
352 } 352 }
@@ -356,7 +356,7 @@ class Ontology2_CanonicalModelSpec
356 renderer.render( 356 renderer.render(
357 DsomeValuesFromPA 357 DsomeValuesFromPA
358 ) should "produce 1 rule" in { 358 ) should "produce 1 rule" in {
359 val visitor = program.ProgramGenerator 359 val visitor = program.RuleGenerator
360 val rules = DsomeValuesFromPA.accept(visitor) 360 val rules = DsomeValuesFromPA.accept(visitor)
361 rules should have length 1 361 rules should have length 1
362 } 362 }
diff --git a/src/test/scala/rsacomb/FilteringProgramSpecs.scala b/src/test/scala/rsacomb/FilteringProgramSpecs.scala
index 26b0857..79e675f 100644
--- a/src/test/scala/rsacomb/FilteringProgramSpecs.scala
+++ b/src/test/scala/rsacomb/FilteringProgramSpecs.scala
@@ -283,7 +283,6 @@ object FilteringProgramSpec {
283 283
284 val queries = 284 val queries =
285 List(query0, query1, query2, query3, query4, query5, query6, query7) 285 List(query0, query1, query2, query3, query4, query5, query6, query7)
286
287} 286}
288 287
289class FilteringProgramSpec 288class FilteringProgramSpec