aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/Upperbound.scala
blob: 567691ffb0479ac856c7dec591470f312c162686 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package uk.ac.ox.cs.rsacomb.approximation

import org.semanticweb.owlapi.apibinding.OWLManager
import org.semanticweb.owlapi.model.{IRI => _, _}

import tech.oxfordsemantic.jrdfox.logic.expression.{Resource, IRI}

import scala.collection.mutable.Map
import scalax.collection.Graph
import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
import scalax.collection.GraphTraversal._

import uk.ac.ox.cs.rsacomb.ontology.RSAOntology
import uk.ac.ox.cs.rsacomb.ontology.Ontology
import uk.ac.ox.cs.rsacomb.util.DataFactory

object Upperbound {

  private val manager = OWLManager.createOWLOntologyManager()
  private val factory = manager.getOWLDataFactory()

}

/** Approximation algorithm that mantains completeness for CQ answering.
  *
  * The input OWL 2 ontology is assumed to be normalized and the output
  * ontology is guaranteed to be in RSA.
  *
  * The algorithm is performed in three steps:
  * 1. the ontology is reduced to ALCHOIQ by discarding any axiom
  *    that is not in the language;
  * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting
  *    axioms with disjunction on the rhs;
  * 3. the ontology is approximated to RSA by manipulating its
  *    dependency graph.
  *
  * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]]
  */
class Upperbound(implicit fresh: DataFactory)
    extends Approximation[RSAOntology] {

  /** Simplify conversion between Java and Scala collections */
  import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._

  /** Simplify conversion between OWLAPI and RDFox concepts */
  // import uk.ac.ox.cs.rsacomb.implicits.RDFox._

  /** Main entry point for the approximation algorithm */
  def approximate(ontology: Ontology): RSAOntology =
    toRSA(
      new Ontology(
        ontology.origin,
        ontology.axioms map chooseDisjunct,
        ontology.datafiles
      )
    )

  /** Choose a single disjunct
    *
    * This is a very naïve way of getting rid of disjunction preserving
    * completeness of CQ answering.
    */
  private def chooseDisjunct(axiom: OWLLogicalAxiom): OWLLogicalAxiom =
    axiom match {
      case a: OWLSubClassOfAxiom => {
        val sub = a.getSubClass.getNNF
        val sup = a.getSuperClass.getNNF
        sup match {
          case sup: OWLObjectUnionOf =>
            Upperbound.factory.getOWLSubClassOfAxiom(sub, sup.asDisjunctSet.head)
          case _ => axiom
        }
      }
      case _ => axiom
    }

  /** Approximate a Horn-ALCHOIQ ontology to RSA
    *
    * This is done by gathering those existential axioms that prevent
    * the ontology dependency graph from being tree-shaped and constant
    * skolemize them.
    *
    * @param ontology the set of axioms to approximate.
    * @return the approximated RSA ontology
    */
  private def toRSA(ontology: Ontology): RSAOntology = {
    /* Compute the dependency graph for the ontology */
    val (graph, nodemap) = ontology.dependencyGraph

    /* Define node colors for the graph visit */
    sealed trait NodeColor
    case object Unvisited extends NodeColor
    case object Visited extends NodeColor
    case object ToSkolem extends NodeColor

    /* Keep track of node colors during graph visit */
    var color = Map.from[Resource, NodeColor](
      graph.nodes.toOuter.map(k => (k, Unvisited))
    )

    for {
      component <- graph.componentTraverser().map(_ to Graph)
      edge <- component
        .outerEdgeTraverser(component.nodes.head)
        .withKind(BreadthFirst)
    } yield {
      val source = edge._1
      val target = edge._2
      color(source) match {
        case Unvisited | Visited => {
          color(target) match {
            case Unvisited =>
              color(source) = Visited;
              color(target) = Visited
            case Visited =>
              color(source) = ToSkolem
            case ToSkolem =>
              color(source) = Visited
          }
        }
        case ToSkolem => {}
      }
    }

    val toSkolem = color.collect { case (resource: IRI, ToSkolem) =>
      nodemap(resource.getIRI)
    }.toList

    // Force constant skolemization by introducing a fresh individual
    // (singleton class).
    val skolemized = toSkolem flatMap { (axiom) =>
      import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._
      axiom.toTriple match {
        case Some((subclass, role, filler)) => {
          val skolem = Upperbound.factory.getOWLNamedIndividual(
            s"i_${axiom.toString.hashCode}"
          )
          val cls = fresh.getOWLClass
          List(
            Upperbound.factory.getOWLSubClassOfAxiom(
              subclass,
              Upperbound.factory.getOWLObjectSomeValuesFrom(role, cls)
            ),
            Upperbound.factory.getOWLSubClassOfAxiom(
              cls,
              Upperbound.factory.getOWLObjectOneOf(skolem)
            ),
            Upperbound.factory.getOWLClassAssertionAxiom(filler, skolem)
          )
        }
        case None => List()
      }
    }

    /* Substitute selected axioms with their "skolemized" version */
    RSAOntology(
      ontology.origin,
      ontology.axioms diff toSkolem concat skolemized,
      ontology.datafiles
    )
  }

  // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~>
  // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F,
  // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D)
  // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N)
  // val edges3 = Seq('P ~> 'O)
  // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3)

}