diff options
Diffstat (limited to 'src/main/scala/uk/ac/ox/cs/rsacomb/approximation')
-rw-r--r-- | src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala | 38 | ||||
-rw-r--r-- | src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Upperbound.scala | 178 |
2 files changed, 199 insertions, 17 deletions
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 index 60a88fb..e261bce 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Lowerbound.scala | |||
@@ -13,10 +13,10 @@ import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ | |||
13 | import scalax.collection.GraphTraversal._ | 13 | import scalax.collection.GraphTraversal._ |
14 | 14 | ||
15 | import uk.ac.ox.cs.rsacomb.RSAOntology | 15 | import uk.ac.ox.cs.rsacomb.RSAOntology |
16 | import uk.ac.ox.cs.rsacomb.RSAUtil | ||
17 | import uk.ac.ox.cs.rsacomb.ontology.Ontology | 16 | import uk.ac.ox.cs.rsacomb.ontology.Ontology |
17 | import uk.ac.ox.cs.rsacomb.util.DataFactory | ||
18 | 18 | ||
19 | object LowerBound { | 19 | object Lowerbound { |
20 | 20 | ||
21 | private val manager = OWLManager.createOWLOntologyManager() | 21 | private val manager = OWLManager.createOWLOntologyManager() |
22 | private val factory = manager.getOWLDataFactory() | 22 | private val factory = manager.getOWLDataFactory() |
@@ -38,7 +38,8 @@ object LowerBound { | |||
38 | * | 38 | * |
39 | * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]] | 39 | * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]] |
40 | */ | 40 | */ |
41 | class LowerBound extends Approximation[RSAOntology] { | 41 | class Lowerbound(implicit fresh: DataFactory) |
42 | extends Approximation[RSAOntology] { | ||
42 | 43 | ||
43 | /** Simplify conversion between Java and Scala collections */ | 44 | /** Simplify conversion between Java and Scala collections */ |
44 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | 45 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ |
@@ -50,6 +51,7 @@ class LowerBound extends Approximation[RSAOntology] { | |||
50 | def approximate(ontology: Ontology): RSAOntology = | 51 | def approximate(ontology: Ontology): RSAOntology = |
51 | toRSA( | 52 | toRSA( |
52 | new Ontology( | 53 | new Ontology( |
54 | ontology.origin, | ||
53 | ontology.axioms filter inALCHOIQ flatMap shift, | 55 | ontology.axioms filter inALCHOIQ flatMap shift, |
54 | ontology.datafiles | 56 | ontology.datafiles |
55 | ) | 57 | ) |
@@ -122,27 +124,25 @@ class LowerBound extends Approximation[RSAOntology] { | |||
122 | val sup = a.getSuperClass.getNNF | 124 | val sup = a.getSuperClass.getNNF |
123 | sup match { | 125 | sup match { |
124 | case sup: OWLObjectUnionOf => { | 126 | case sup: OWLObjectUnionOf => { |
125 | val body = sub.asConjunctSet.map((atom) => | 127 | val body = |
126 | (atom, RSAUtil.getFreshOWLClass()) | 128 | sub.asConjunctSet.map((atom) => (atom, fresh.getOWLClass)) |
127 | ) | 129 | val head = |
128 | val head = sup.asDisjunctSet.map((atom) => | 130 | sup.asDisjunctSet.map((atom) => (atom, fresh.getOWLClass)) |
129 | (atom, RSAUtil.getFreshOWLClass()) | ||
130 | ) | ||
131 | 131 | ||
132 | val r1 = | 132 | val r1 = |
133 | LowerBound.factory.getOWLSubClassOfAxiom( | 133 | Lowerbound.factory.getOWLSubClassOfAxiom( |
134 | LowerBound.factory.getOWLObjectIntersectionOf( | 134 | Lowerbound.factory.getOWLObjectIntersectionOf( |
135 | (body.map(_._1) ++ head.map(_._2)): _* | 135 | (body.map(_._1) ++ head.map(_._2)): _* |
136 | ), | 136 | ), |
137 | LowerBound.factory.getOWLNothing | 137 | Lowerbound.factory.getOWLNothing |
138 | ) | 138 | ) |
139 | 139 | ||
140 | val r2s = | 140 | val r2s = |
141 | for { | 141 | for { |
142 | (a, na) <- head | 142 | (a, na) <- head |
143 | hs = head.map(_._2).filterNot(_ equals na) | 143 | hs = head.map(_._2).filterNot(_ equals na) |
144 | } yield LowerBound.factory.getOWLSubClassOfAxiom( | 144 | } yield Lowerbound.factory.getOWLSubClassOfAxiom( |
145 | LowerBound.factory.getOWLObjectIntersectionOf( | 145 | Lowerbound.factory.getOWLObjectIntersectionOf( |
146 | (body.map(_._1) ++ hs): _* | 146 | (body.map(_._1) ++ hs): _* |
147 | ), | 147 | ), |
148 | a | 148 | a |
@@ -152,8 +152,8 @@ class LowerBound extends Approximation[RSAOntology] { | |||
152 | for { | 152 | for { |
153 | (a, na) <- body | 153 | (a, na) <- body |
154 | bs = body.map(_._1).filterNot(_ equals a) | 154 | bs = body.map(_._1).filterNot(_ equals a) |
155 | } yield LowerBound.factory.getOWLSubClassOfAxiom( | 155 | } yield Lowerbound.factory.getOWLSubClassOfAxiom( |
156 | LowerBound.factory.getOWLObjectIntersectionOf( | 156 | Lowerbound.factory.getOWLObjectIntersectionOf( |
157 | (bs ++ head.map(_._2)): _* | 157 | (bs ++ head.map(_._2)): _* |
158 | ), | 158 | ), |
159 | na | 159 | na |
@@ -219,7 +219,11 @@ class LowerBound extends Approximation[RSAOntology] { | |||
219 | }.toList | 219 | }.toList |
220 | 220 | ||
221 | /* Remove axioms from approximated ontology */ | 221 | /* Remove axioms from approximated ontology */ |
222 | RSAOntology(ontology.axioms diff toDelete, ontology.datafiles) | 222 | RSAOntology( |
223 | ontology.origin, | ||
224 | ontology.axioms diff toDelete, | ||
225 | ontology.datafiles | ||
226 | ) | ||
223 | } | 227 | } |
224 | 228 | ||
225 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> | 229 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Upperbound.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Upperbound.scala new file mode 100644 index 0000000..469d774 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Upperbound.scala | |||
@@ -0,0 +1,178 @@ | |||
1 | package uk.ac.ox.cs.rsacomb.approximation | ||
2 | |||
3 | // import java.io.File | ||
4 | |||
5 | import org.semanticweb.owlapi.apibinding.OWLManager | ||
6 | import org.semanticweb.owlapi.model.{IRI => _, _} | ||
7 | |||
8 | import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI} | ||
9 | |||
10 | import scala.collection.mutable.Map | ||
11 | import scalax.collection.Graph | ||
12 | import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ | ||
13 | import scalax.collection.GraphTraversal._ | ||
14 | |||
15 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
16 | import uk.ac.ox.cs.rsacomb.ontology.Ontology | ||
17 | import uk.ac.ox.cs.rsacomb.util.DataFactory | ||
18 | |||
19 | object Upperbound { | ||
20 | |||
21 | private val manager = OWLManager.createOWLOntologyManager() | ||
22 | private val factory = manager.getOWLDataFactory() | ||
23 | |||
24 | } | ||
25 | |||
26 | /** Approximation algorithm that mantains completeness 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 | */ | ||
41 | class Upperbound(implicit fresh: DataFactory) | ||
42 | extends Approximation[RSAOntology] { | ||
43 | |||
44 | /** Simplify conversion between Java and Scala collections */ | ||
45 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
46 | |||
47 | /** Simplify conversion between OWLAPI and RDFox concepts */ | ||
48 | // import uk.ac.ox.cs.rsacomb.implicits.RDFox._ | ||
49 | |||
50 | /** Main entry point for the approximation algorithm */ | ||
51 | def approximate(ontology: Ontology): RSAOntology = | ||
52 | toRSA( | ||
53 | new Ontology( | ||
54 | ontology.origin, | ||
55 | ontology.axioms flatMap toConjuncts, | ||
56 | ontology.datafiles | ||
57 | ) | ||
58 | ) | ||
59 | |||
60 | /** Turn disjuncts into conjuncts | ||
61 | * | ||
62 | * This is a very naïve way of getting rid of disjunction preserving | ||
63 | * completeness of CQ answering. | ||
64 | * | ||
65 | * @todo implement a choice function that decides which disjunct to | ||
66 | * keep instead of keeping all of them. Note that PAGOdA is currently | ||
67 | * doing something similar. | ||
68 | */ | ||
69 | private def toConjuncts(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] = | ||
70 | axiom match { | ||
71 | case a: OWLSubClassOfAxiom => { | ||
72 | val sub = a.getSubClass.getNNF | ||
73 | val sup = a.getSuperClass.getNNF | ||
74 | sup match { | ||
75 | case sup: OWLObjectUnionOf => | ||
76 | sup.asDisjunctSet.map( | ||
77 | Upperbound.factory.getOWLSubClassOfAxiom(sub, _) | ||
78 | ) | ||
79 | case _ => List(axiom) | ||
80 | } | ||
81 | } | ||
82 | case _ => List(axiom) | ||
83 | } | ||
84 | |||
85 | /** Approximate a Horn-ALCHOIQ ontology to RSA | ||
86 | * | ||
87 | * This is done by gathering those existential axioms that prevent | ||
88 | * the ontology dependency graph from being tree-shaped and constant | ||
89 | * skolemize them. | ||
90 | * | ||
91 | * @param ontology the set of axioms to approximate. | ||
92 | * @return the approximated RSA ontology | ||
93 | */ | ||
94 | private def toRSA(ontology: Ontology): RSAOntology = { | ||
95 | /* Compute the dependency graph for the ontology */ | ||
96 | val (graph, nodemap) = ontology.dependencyGraph | ||
97 | |||
98 | /* Define node colors for the graph visit */ | ||
99 | sealed trait NodeColor | ||
100 | case object Unvisited extends NodeColor | ||
101 | case object Visited extends NodeColor | ||
102 | case object ToSkolem extends NodeColor | ||
103 | |||
104 | /* Keep track of node colors during graph visit */ | ||
105 | var color = Map.from[Resource, NodeColor]( | ||
106 | graph.nodes.toOuter.map(k => (k, Unvisited)) | ||
107 | ) | ||
108 | |||
109 | for { | ||
110 | component <- graph.componentTraverser().map(_ to Graph) | ||
111 | edge <- component | ||
112 | .outerEdgeTraverser(component.nodes.head) | ||
113 | .withKind(BreadthFirst) | ||
114 | } yield { | ||
115 | val source = edge._1 | ||
116 | val target = edge._2 | ||
117 | color(source) match { | ||
118 | case Unvisited | Visited => { | ||
119 | color(target) match { | ||
120 | case Unvisited => | ||
121 | color(source) = Visited; | ||
122 | color(target) = Visited | ||
123 | case Visited => | ||
124 | color(source) = ToSkolem | ||
125 | case ToSkolem => | ||
126 | color(source) = Visited | ||
127 | } | ||
128 | } | ||
129 | case ToSkolem => {} | ||
130 | } | ||
131 | } | ||
132 | |||
133 | val toSkolem = color.collect { case (resource: IRI, ToSkolem) => | ||
134 | nodemap(resource.getIRI) | ||
135 | }.toList | ||
136 | |||
137 | // Force constant skolemization by introducing a fresh individual | ||
138 | // (singleton class). | ||
139 | val skolemized = toSkolem flatMap { (axiom) => | ||
140 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ | ||
141 | axiom.toTriple match { | ||
142 | case Some((subclass, role, filler)) => { | ||
143 | val skolem = Upperbound.factory.getOWLNamedIndividual( | ||
144 | s"i_${axiom.toString.hashCode}" | ||
145 | ) | ||
146 | val cls = fresh.getOWLClass | ||
147 | List( | ||
148 | Upperbound.factory.getOWLSubClassOfAxiom( | ||
149 | subclass, | ||
150 | Upperbound.factory.getOWLObjectSomeValuesFrom(role, cls) | ||
151 | ), | ||
152 | Upperbound.factory.getOWLSubClassOfAxiom( | ||
153 | cls, | ||
154 | Upperbound.factory.getOWLObjectOneOf(skolem) | ||
155 | ), | ||
156 | Upperbound.factory.getOWLClassAssertionAxiom(filler, skolem) | ||
157 | ) | ||
158 | } | ||
159 | case None => List() | ||
160 | } | ||
161 | } | ||
162 | |||
163 | /* Substitute selected axioms with their "skolemized" version */ | ||
164 | RSAOntology( | ||
165 | ontology.origin, | ||
166 | ontology.axioms diff toSkolem concat skolemized, | ||
167 | ontology.datafiles | ||
168 | ) | ||
169 | } | ||
170 | |||
171 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> | ||
172 | // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, | ||
173 | // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) | ||
174 | // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N) | ||
175 | // val edges3 = Seq('P ~> 'O) | ||
176 | // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3) | ||
177 | |||
178 | } | ||