aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/approximation
diff options
context:
space:
mode:
authorFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
committerFederico Igne <git@federicoigne.com>2021-07-27 10:34:57 +0100
commitd017662e2d65ec72e7decde3b76591c198da9819 (patch)
tree57193f145cb39223db0b0da6055556aca7d04622 /src/main/scala/uk/ac/ox/cs/rsacomb/approximation
parentc597b5efbe9e351a4313ef8fc1215f9e188b1ffd (diff)
parent7d619706551117a485d93d0d6847a25afa6a359d (diff)
downloadRSAComb-0.2.0.tar.gz
RSAComb-0.2.0.zip
Merge branch 'approximation'v0.2.0
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/rsacomb/approximation')
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala18
-rw-r--r--src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala232
2 files changed, 250 insertions, 0 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala
new file mode 100644
index 0000000..344f0fe
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Approximation.scala
@@ -0,0 +1,18 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3import java.io.File
4import org.semanticweb.owlapi.model.OWLLogicalAxiom
5
6import uk.ac.ox.cs.rsacomb.ontology.Ontology
7
8/** Ontology approximation technique. */
9trait Approximation[T] {
10
11 /** Approximate an ontology.
12 *
13 * @param ontology input ontology as a list of axioms
14 * @return the approximated ontology
15 */
16 def approximate(ontology: Ontology): T
17
18}
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala
new file mode 100644
index 0000000..60a88fb
--- /dev/null
+++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala
@@ -0,0 +1,232 @@
1package uk.ac.ox.cs.rsacomb.approximation
2
3import java.io.File
4
5import org.semanticweb.owlapi.apibinding.OWLManager
6import org.semanticweb.owlapi.model.{IRI => _, _}
7
8import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI}
9
10import scala.collection.mutable.{Set, Map}
11import scalax.collection.Graph
12import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
13import scalax.collection.GraphTraversal._
14
15import uk.ac.ox.cs.rsacomb.RSAOntology
16import uk.ac.ox.cs.rsacomb.RSAUtil
17import uk.ac.ox.cs.rsacomb.ontology.Ontology
18
19object LowerBound {
20
21 private val manager = OWLManager.createOWLOntologyManager()
22 private val factory = manager.getOWLDataFactory()
23
24}
25
26/** Approximation algorithm that mantains soundness for CQ answering.
27 *
28 * The input OWL 2 ontology is assumed to be normalized and the output
29 * ontology is guaranteed to be in RSA.
30 *
31 * The algorithm is performed in three steps:
32 * 1. the ontology is reduced to ALCHOIQ by discarding any axiom
33 * that is not in the language;
34 * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting
35 * axioms with disjunction on the rhs;
36 * 3. the ontology is approximated to RSA by manipulating its
37 * dependency graph.
38 *
39 * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]]
40 */
41class LowerBound extends Approximation[RSAOntology] {
42
43 /** Simplify conversion between Java and Scala collections */
44 import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._
45
46 /** Simplify conversion between OWLAPI and RDFox concepts */
47 import uk.ac.ox.cs.rsacomb.implicits.RDFox._
48
49 /** Main entry point for the approximation algorithm */
50 def approximate(ontology: Ontology): RSAOntology =
51 toRSA(
52 new Ontology(
53 ontology.axioms filter inALCHOIQ flatMap shift,
54 ontology.datafiles
55 )
56 )
57
58 /** Discards all axioms outside ALCHOIQ */
59 private def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean =
60 axiom match {
61 case a: OWLSubClassOfAxiom => {
62 val sub = a.getSubClass.getNNF
63 val sup = a.getSuperClass.getNNF
64 (sub, sup) match {
65 case (sub: OWLObjectAllValuesFrom, _) => false
66 case (sub: OWLDataAllValuesFrom, _) => false
67 case (_, sup: OWLDataAllValuesFrom) => false
68 case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 =>
69 false
70 case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 =>
71 false
72 case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 =>
73 false
74 case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 =>
75 false
76 case (sub: OWLObjectMaxCardinality, _) => false
77 case (sub: OWLDataMaxCardinality, _) => false
78 case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 =>
79 false
80 case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 =>
81 false
82 case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 =>
83 false
84 case (sub: OWLObjectHasSelf, _) => false
85 case (_, sup: OWLObjectHasSelf) => false
86 case _ => true
87 }
88 }
89 case a: OWLTransitiveObjectPropertyAxiom => false
90 case a: OWLReflexiveObjectPropertyAxiom => false
91 case a: OWLSubPropertyChainOfAxiom => false
92 case a: OWLAsymmetricObjectPropertyAxiom => false
93 case a => true
94 }
95
96 /** Shifting axioms with disjunction on the rhs.
97 *
98 * The process of shifting presenves soundness but completenes w.r.t.
99 * CQ answering is lost.
100 *
101 * @example
102 *
103 * A -> B1 u B2 u B3 .
104 *
105 * becomes
106 *
107 * A n nB1 n nB2 n nB3 -> bot .
108 * A n nB1 n nB2 -> B3 .
109 * A n nB1 n nB3 -> B2 .
110 * A n nB2 n nB3 -> B1 .
111 * nB1 n nB2 n nB3 -> nA .
112 *
113 * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to
114 * the negation of A, B1, B2, B3 respectively.
115 *
116 * @note this method maintains the normal form of the input axiom.
117 */
118 private def shift(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] =
119 axiom match {
120 case a: OWLSubClassOfAxiom => {
121 val sub = a.getSubClass.getNNF
122 val sup = a.getSuperClass.getNNF
123 sup match {
124 case sup: OWLObjectUnionOf => {
125 val body = sub.asConjunctSet.map((atom) =>
126 (atom, RSAUtil.getFreshOWLClass())
127 )
128 val head = sup.asDisjunctSet.map((atom) =>
129 (atom, RSAUtil.getFreshOWLClass())
130 )
131
132 val r1 =
133 LowerBound.factory.getOWLSubClassOfAxiom(
134 LowerBound.factory.getOWLObjectIntersectionOf(
135 (body.map(_._1) ++ head.map(_._2)): _*
136 ),
137 LowerBound.factory.getOWLNothing
138 )
139
140 val r2s =
141 for {
142 (a, na) <- head
143 hs = head.map(_._2).filterNot(_ equals na)
144 } yield LowerBound.factory.getOWLSubClassOfAxiom(
145 LowerBound.factory.getOWLObjectIntersectionOf(
146 (body.map(_._1) ++ hs): _*
147 ),
148 a
149 )
150
151 val r3s =
152 for {
153 (a, na) <- body
154 bs = body.map(_._1).filterNot(_ equals a)
155 } yield LowerBound.factory.getOWLSubClassOfAxiom(
156 LowerBound.factory.getOWLObjectIntersectionOf(
157 (bs ++ head.map(_._2)): _*
158 ),
159 na
160 )
161
162 List(r1) ++ r2s ++ r3s
163 }
164 case _ => List(axiom)
165 }
166 }
167 case _ => List(axiom)
168 }
169
170 /** Approximate a Horn-ALCHOIQ ontology to RSA
171 *
172 * This is done by gathering those axioms that prevent the ontology
173 * dependency graph from being tree-shaped, and removing them.
174 *
175 * @param ontology the set of axioms to approximate.
176 * @return the approximated RSA ontology
177 */
178 private def toRSA(ontology: Ontology): RSAOntology = {
179 /* Compute the dependency graph for the ontology */
180 val (graph, nodemap) = ontology.dependencyGraph
181
182 /* Define node colors for the graph visit */
183 sealed trait NodeColor
184 case object Unvisited extends NodeColor
185 case object Visited extends NodeColor
186 case object ToDelete extends NodeColor
187
188 /* Keep track of node colors during graph visit */
189 var color = Map.from[Resource, NodeColor](
190 graph.nodes.toOuter.map(k => (k, Unvisited))
191 )
192
193 for {
194 component <- graph.componentTraverser().map(_ to Graph)
195 edge <- component
196 .outerEdgeTraverser(component.nodes.head)
197 .withKind(BreadthFirst)
198 } yield {
199 val source = edge._1
200 val target = edge._2
201 color(source) match {
202 case Unvisited | Visited => {
203 color(target) match {
204 case Unvisited =>
205 color(source) = Visited;
206 color(target) = Visited
207 case Visited =>
208 color(source) = ToDelete
209 case ToDelete =>
210 color(source) = Visited
211 }
212 }
213 case ToDelete =>
214 }
215 }
216
217 val toDelete = color.collect { case (resource: IRI, ToDelete) =>
218 nodemap(resource.getIRI)
219 }.toList
220
221 /* Remove axioms from approximated ontology */
222 RSAOntology(ontology.axioms diff toDelete, ontology.datafiles)
223 }
224
225 // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
226 // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
227 // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
228 // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N)
229 // val edges3 = Seq('P ~> 'O)
230 // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3)
231 //
232}