diff options
5 files changed, 532 insertions, 241 deletions
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala index f9de801..b749401 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/Main.scala | |||
| @@ -106,12 +106,11 @@ object RSAComb extends App { | |||
| 106 | /* Command-line options */ | 106 | /* Command-line options */ |
| 107 | val config = RSAConfig.parse(args.toList) | 107 | val config = RSAConfig.parse(args.toList) |
| 108 | 108 | ||
| 109 | val ontology = RSAOntology( | 109 | val rsa = RSAOntology( |
| 110 | config('ontology).get[File], | 110 | config('ontology).get[File], |
| 111 | config('data).get[List[File]]: _* | 111 | config('data).get[List[File]], |
| 112 | None | ||
| 112 | ) | 113 | ) |
| 113 | val rsa = ontology.toRSA() | ||
| 114 | ontology.statistics() | ||
| 115 | 114 | ||
| 116 | if (config contains 'query) { | 115 | if (config contains 'query) { |
| 117 | val query = | 116 | val query = |
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala index 0e10f89..bbbbcf3 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/RSAOntology.scala | |||
| @@ -50,13 +50,13 @@ import scala.collection.JavaConverters._ | |||
| 50 | import scala.collection.mutable.{Set, Map} | 50 | import scala.collection.mutable.{Set, Map} |
| 51 | import scalax.collection.Graph | 51 | import scalax.collection.Graph |
| 52 | import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ | 52 | import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._ |
| 53 | import scalax.collection.GraphTraversal._ | ||
| 54 | 53 | ||
| 55 | /* Debug only */ | 54 | /* Debug only */ |
| 56 | import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer | 55 | import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer |
| 57 | import tech.oxfordsemantic.jrdfox.logic._ | 56 | import tech.oxfordsemantic.jrdfox.logic._ |
| 58 | import org.semanticweb.owlapi.model.OWLObjectInverseOf | 57 | import org.semanticweb.owlapi.model.OWLObjectInverseOf |
| 59 | 58 | ||
| 59 | import uk.ac.ox.cs.rsacomb.approximation.Approximation | ||
| 60 | import uk.ac.ox.cs.rsacomb.converter._ | 60 | import uk.ac.ox.cs.rsacomb.converter._ |
| 61 | import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType} | 61 | import uk.ac.ox.cs.rsacomb.filtering.{FilteringProgram, FilterType} |
| 62 | import uk.ac.ox.cs.rsacomb.suffix._ | 62 | import uk.ac.ox.cs.rsacomb.suffix._ |
| @@ -64,29 +64,189 @@ import uk.ac.ox.cs.rsacomb.sparql._ | |||
| 64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} | 64 | import uk.ac.ox.cs.rsacomb.util.{RDFoxUtil, RSA} |
| 65 | import uk.ac.ox.cs.rsacomb.util.Logger | 65 | import uk.ac.ox.cs.rsacomb.util.Logger |
| 66 | 66 | ||
| 67 | object RSAUtil { | ||
| 68 | |||
| 69 | // implicit def axiomsToOntology(axioms: Seq[OWLAxiom]) = { | ||
| 70 | // val manager = OWLManager.createOWLOntologyManager() | ||
| 71 | // manager.createOntology(axioms.asJava) | ||
| 72 | // } | ||
| 73 | |||
| 74 | /** Compute the RSA dependency graph for a set of axioms | ||
| 75 | * | ||
| 76 | * @return a tuple containing the dependency graph and a map between | ||
| 77 | * the newly introduced constants and the corresponding input axioms. | ||
| 78 | * | ||
| 79 | * @note no check on the ontology language is performed since the | ||
| 80 | * construction of the dependency graph is computed regardless. The | ||
| 81 | * input axioms are assumed to be normalized. | ||
| 82 | */ | ||
| 83 | def dependencyGraph( | ||
| 84 | axioms: List[OWLLogicalAxiom], | ||
| 85 | datafiles: List[File] | ||
| 86 | ): (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = { | ||
| 87 | val unsafe = RSAOntology(axioms, datafiles).unsafeRoles | ||
| 88 | var nodemap = Map.empty[String, OWLAxiom] | ||
| 89 | |||
| 90 | object RSAConverter extends RDFoxConverter { | ||
| 91 | |||
| 92 | override def convert( | ||
| 93 | expr: OWLClassExpression, | ||
| 94 | term: Term, | ||
| 95 | unsafe: List[OWLObjectPropertyExpression], | ||
| 96 | skolem: SkolemStrategy, | ||
| 97 | suffix: RSASuffix | ||
| 98 | ): Shards = | ||
| 99 | (expr, skolem) match { | ||
| 100 | |||
| 101 | case (e: OWLObjectSomeValuesFrom, c: Constant) => { | ||
| 102 | nodemap.update(c.iri.getIRI, c.axiom) | ||
| 103 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 104 | if (unsafe contains e.getProperty) | ||
| 105 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
| 106 | else | ||
| 107 | (RSA.PE(term, c.iri) :: res, ext) | ||
| 108 | } | ||
| 109 | |||
| 110 | case (e: OWLDataSomeValuesFrom, c: Constant) => { | ||
| 111 | nodemap.update(c.iri.getIRI, c.axiom) | ||
| 112 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 113 | if (unsafe contains e.getProperty) | ||
| 114 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
| 115 | else | ||
| 116 | (RSA.PE(term, c.iri) :: res, ext) | ||
| 117 | } | ||
| 118 | |||
| 119 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | ||
| 120 | } | ||
| 121 | } | ||
| 122 | |||
| 123 | /* Ontology convertion into LP rules */ | ||
| 124 | val term = RSAOntology.genFreshVariable() | ||
| 125 | val result = axioms.map(a => | ||
| 126 | RSAConverter.convert(a, term, unsafe, new Constant(a), Empty) | ||
| 127 | ) | ||
| 128 | |||
| 129 | val datalog = result.unzip | ||
| 130 | val facts = datalog._1.flatten | ||
| 131 | var rules = datalog._2.flatten | ||
| 132 | |||
| 133 | /* Open connection with RDFox */ | ||
| 134 | val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph") | ||
| 135 | |||
| 136 | /* Add additional built-in rules */ | ||
| 137 | val varX = Variable.create("X") | ||
| 138 | val varY = Variable.create("Y") | ||
| 139 | rules = Rule.create( | ||
| 140 | RSA.E(varX, varY), | ||
| 141 | RSA.PE(varX, varY), | ||
| 142 | RSA.U(varX), | ||
| 143 | RSA.U(varY) | ||
| 144 | ) :: rules | ||
| 145 | /* Load facts and rules from ontology */ | ||
| 146 | RDFoxUtil.addFacts(data, facts) | ||
| 147 | RDFoxUtil.addRules(data, rules) | ||
| 148 | /* Load data files */ | ||
| 149 | RDFoxUtil.addData(data, datafiles: _*) | ||
| 150 | |||
| 151 | /* Build the graph */ | ||
| 152 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | ||
| 153 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get | ||
| 154 | var edges: Seq[DiEdge[Resource]] = | ||
| 155 | answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 } | ||
| 156 | val graph = Graph(edges: _*) | ||
| 157 | |||
| 158 | /* Close connection to RDFox */ | ||
| 159 | RDFoxUtil.closeConnection(server, data) | ||
| 160 | |||
| 161 | (graph, nodemap) | ||
| 162 | } | ||
| 163 | } | ||
| 164 | |||
| 67 | object RSAOntology { | 165 | object RSAOntology { |
| 68 | 166 | ||
| 167 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
| 168 | |||
| 169 | /** Manager instance to interface with OWLAPI */ | ||
| 170 | val manager = OWLManager.createOWLOntologyManager() | ||
| 171 | val factory = manager.getOWLDataFactory() | ||
| 172 | |||
| 69 | /** Name of the RDFox data store used for CQ answering */ | 173 | /** Name of the RDFox data store used for CQ answering */ |
| 70 | private val DataStore = "answer_computation" | 174 | private val DataStore = "answer_computation" |
| 71 | 175 | ||
| 72 | /** Simple fresh variable generator */ | 176 | /** Simple fresh variable/class generator */ |
| 73 | private var counter = -1; | 177 | private var counter = -1; |
| 74 | def genFreshVariable(): Variable = { | 178 | def genFreshVariable(): Variable = { |
| 75 | counter += 1 | 179 | counter += 1 |
| 76 | Variable.create(f"I$counter%05d") | 180 | Variable.create(f"I$counter%05d") |
| 77 | } | 181 | } |
| 182 | def getFreshOWLClass(): OWLClass = { | ||
| 183 | counter += 1 | ||
| 184 | factory.getOWLClass(s"X$counter") | ||
| 185 | } | ||
| 78 | 186 | ||
| 79 | /** Manager instance to interface with OWLAPI */ | 187 | def apply( |
| 80 | val manager = OWLManager.createOWLOntologyManager() | 188 | axioms: List[OWLLogicalAxiom], |
| 189 | datafiles: List[File] | ||
| 190 | ): RSAOntology = new RSAOntology(axioms, datafiles: _*) | ||
| 191 | |||
| 192 | def apply( | ||
| 193 | ontofile: File, | ||
| 194 | datafiles: List[File], | ||
| 195 | approx: Option[Approximation] | ||
| 196 | ): RSAOntology = { | ||
| 197 | val ontology = manager.loadOntologyFromOntologyDocument(ontofile) | ||
| 198 | RSAOntology(ontology, datafiles, approx) | ||
| 199 | } | ||
| 200 | |||
| 201 | def apply( | ||
| 202 | ontology: OWLOntology, | ||
| 203 | datafiles: List[File], | ||
| 204 | approx: Option[Approximation] | ||
| 205 | ): RSAOntology = { | ||
| 206 | val normalizer = new Normalizer() | ||
| 207 | |||
| 208 | /** TBox axioms */ | ||
| 209 | var tbox: List[OWLLogicalAxiom] = | ||
| 210 | ontology | ||
| 211 | .tboxAxioms(Imports.INCLUDED) | ||
| 212 | .collect(Collectors.toList()) | ||
| 213 | .collect { case a: OWLLogicalAxiom => a } | ||
| 214 | .flatMap(normalizer.normalize) | ||
| 215 | |||
| 216 | /** RBox axioms */ | ||
| 217 | var rbox: List[OWLLogicalAxiom] = | ||
| 218 | ontology | ||
| 219 | .rboxAxioms(Imports.INCLUDED) | ||
| 220 | .collect(Collectors.toList()) | ||
| 221 | .collect { case a: OWLLogicalAxiom => a } | ||
| 222 | .flatMap(normalizer.normalize) | ||
| 223 | |||
| 224 | /** ABox axioms | ||
| 225 | * | ||
| 226 | * @note this represents only the set of assertions contained in the | ||
| 227 | * ontology file. Data files specified in `datafiles` are directly | ||
| 228 | * imported in RDFox due to performance issues when trying to import | ||
| 229 | * large data files via OWLAPI. | ||
| 230 | */ | ||
| 231 | var abox: List[OWLLogicalAxiom] = | ||
| 232 | ontology | ||
| 233 | .aboxAxioms(Imports.INCLUDED) | ||
| 234 | .collect(Collectors.toList()) | ||
| 235 | .collect { case a: OWLLogicalAxiom => a } | ||
| 236 | .flatMap(normalizer.normalize) | ||
| 237 | |||
| 238 | /** Collection of logical axioms in the input ontology */ | ||
| 239 | var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | ||
| 81 | 240 | ||
| 82 | def apply(ontology: File, data: File*): RSAOntology = | ||
| 83 | new RSAOntology( | 241 | new RSAOntology( |
| 84 | manager.loadOntologyFromOntologyDocument(ontology), | 242 | approx match { |
| 85 | data: _* | 243 | case Some(a) => a.approximate(axioms, datafiles) |
| 244 | case None => axioms | ||
| 245 | }, | ||
| 246 | datafiles: _* | ||
| 86 | ) | 247 | ) |
| 248 | } | ||
| 87 | 249 | ||
| 88 | def apply(ontology: OWLOntology, data: File*): RSAOntology = | ||
| 89 | new RSAOntology(ontology, data: _*) | ||
| 90 | } | 250 | } |
| 91 | 251 | ||
| 92 | /** Wrapper class for an ontology in RSA | 252 | /** Wrapper class for an ontology in RSA |
| @@ -94,59 +254,21 @@ object RSAOntology { | |||
| 94 | * @param ontology the input OWL2 ontology. | 254 | * @param ontology the input OWL2 ontology. |
| 95 | * @param datafiles additinal data (treated as part of the ABox) | 255 | * @param datafiles additinal data (treated as part of the ABox) |
| 96 | */ | 256 | */ |
| 97 | class RSAOntology(val original: OWLOntology, val datafiles: File*) { | 257 | class RSAOntology(val axioms: List[OWLLogicalAxiom], val datafiles: File*) { |
| 98 | 258 | ||
| 99 | /** Simplify conversion between OWLAPI and RDFox concepts */ | 259 | /** Simplify conversion between OWLAPI and RDFox concepts */ |
| 100 | import implicits.RDFox._ | 260 | import implicits.RDFox._ |
| 101 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ | 261 | import uk.ac.ox.cs.rsacomb.implicits.RSAAxiom._ |
| 262 | |||
| 263 | /** Simplify conversion between Java and Scala collections */ | ||
| 102 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | 264 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ |
| 103 | 265 | ||
| 104 | /** Set of axioms removed during the approximation to RSA */ | 266 | /** Set of axioms removed during the approximation to RSA */ |
| 105 | private var removed: Seq[OWLAxiom] = Seq.empty | 267 | private var removed: Seq[OWLAxiom] = Seq.empty |
| 106 | 268 | ||
| 107 | /** The normalizer normalizes the ontology and approximate it to | ||
| 108 | * Horn-ALCHOIQ. A further step is needed to obtain an RSA | ||
| 109 | * approximation of the input ontology `original`. | ||
| 110 | */ | ||
| 111 | private val normalizer = new Normalizer() | ||
| 112 | |||
| 113 | /** TBox axioms */ | ||
| 114 | var tbox: List[OWLLogicalAxiom] = | ||
| 115 | original | ||
| 116 | .tboxAxioms(Imports.INCLUDED) | ||
| 117 | .collect(Collectors.toList()) | ||
| 118 | .collect { case a: OWLLogicalAxiom => a } | ||
| 119 | .flatMap(normalizer.normalize) | ||
| 120 | |||
| 121 | /** RBox axioms */ | ||
| 122 | var rbox: List[OWLLogicalAxiom] = | ||
| 123 | original | ||
| 124 | .rboxAxioms(Imports.INCLUDED) | ||
| 125 | .collect(Collectors.toList()) | ||
| 126 | .collect { case a: OWLLogicalAxiom => a } | ||
| 127 | .flatMap(normalizer.normalize) | ||
| 128 | |||
| 129 | /** ABox axioms | ||
| 130 | * | ||
| 131 | * @note this represents only the set of assertions contained in the | ||
| 132 | * ontology file. Data files specified in `datafiles` are directly | ||
| 133 | * imported in RDFox due to performance issues when trying to import | ||
| 134 | * large data files via OWLAPI. | ||
| 135 | */ | ||
| 136 | var abox: List[OWLLogicalAxiom] = | ||
| 137 | original | ||
| 138 | .aboxAxioms(Imports.INCLUDED) | ||
| 139 | .collect(Collectors.toList()) | ||
| 140 | .collect { case a: OWLLogicalAxiom => a } | ||
| 141 | .flatMap(normalizer.normalize) | ||
| 142 | |||
| 143 | /** Collection of logical axioms in the input ontology */ | ||
| 144 | var axioms: List[OWLLogicalAxiom] = abox ::: tbox ::: rbox | ||
| 145 | |||
| 146 | /** Normalized Horn-ALCHOIQ ontology */ | 269 | /** Normalized Horn-ALCHOIQ ontology */ |
| 147 | val ontology = RSAOntology.manager.createOntology( | 270 | val ontology = |
| 148 | axioms.asInstanceOf[List[OWLAxiom]].asJava | 271 | RSAOntology.manager.createOntology((axioms: List[OWLAxiom]).asJava) |
| 149 | ) | ||
| 150 | 272 | ||
| 151 | /** OWLAPI internal reasoner instantiated over the approximated ontology */ | 273 | /** OWLAPI internal reasoner instantiated over the approximated ontology */ |
| 152 | private val reasoner = | 274 | private val reasoner = |
| @@ -170,7 +292,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 170 | val concepts: List[OWLClass] = | 292 | val concepts: List[OWLClass] = |
| 171 | ontology.getClassesInSignature().asScala.toList | 293 | ontology.getClassesInSignature().asScala.toList |
| 172 | val roles: List[OWLObjectPropertyExpression] = | 294 | val roles: List[OWLObjectPropertyExpression] = |
| 173 | (tbox ++ rbox) | 295 | axioms |
| 174 | .flatMap(_.objectPropertyExpressionsInSignature) | 296 | .flatMap(_.objectPropertyExpressionsInSignature) |
| 175 | .distinct | 297 | .distinct |
| 176 | 298 | ||
| @@ -190,12 +312,12 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 190 | 312 | ||
| 191 | /* Checking for unsafety condition (1) */ | 313 | /* Checking for unsafety condition (1) */ |
| 192 | val unsafe1 = for { | 314 | val unsafe1 = for { |
| 193 | axiom <- tbox | 315 | axiom <- axioms |
| 194 | if axiom.isT5 | 316 | if axiom.isT5 |
| 195 | role1 <- axiom.objectPropertyExpressionsInSignature | 317 | role1 <- axiom.objectPropertyExpressionsInSignature |
| 196 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 318 | roleSuper = role1 +: reasoner.superObjectProperties(role1) |
| 197 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 319 | roleSuperInv = roleSuper.map(_.getInverseProperty) |
| 198 | axiom <- tbox | 320 | axiom <- axioms |
| 199 | if axiom.isT3 && !axiom.isT3top | 321 | if axiom.isT3 && !axiom.isT3top |
| 200 | role2 <- axiom.objectPropertyExpressionsInSignature | 322 | role2 <- axiom.objectPropertyExpressionsInSignature |
| 201 | if roleSuperInv contains role2 | 323 | if roleSuperInv contains role2 |
| @@ -203,12 +325,12 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 203 | 325 | ||
| 204 | /* Checking for unsafety condition (2) */ | 326 | /* Checking for unsafety condition (2) */ |
| 205 | val unsafe2 = for { | 327 | val unsafe2 = for { |
| 206 | axiom <- tbox | 328 | axiom <- axioms |
| 207 | if axiom.isT5 | 329 | if axiom.isT5 |
| 208 | role1 <- axiom.objectPropertyExpressionsInSignature | 330 | role1 <- axiom.objectPropertyExpressionsInSignature |
| 209 | roleSuper = role1 +: reasoner.superObjectProperties(role1) | 331 | roleSuper = role1 +: reasoner.superObjectProperties(role1) |
| 210 | roleSuperInv = roleSuper.map(_.getInverseProperty) | 332 | roleSuperInv = roleSuper.map(_.getInverseProperty) |
| 211 | axiom <- tbox | 333 | axiom <- axioms |
| 212 | if axiom.isT4 | 334 | if axiom.isT4 |
| 213 | role2 <- axiom.objectPropertyExpressionsInSignature | 335 | role2 <- axiom.objectPropertyExpressionsInSignature |
| 214 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) | 336 | if roleSuper.contains(role2) || roleSuperInv.contains(role2) |
| @@ -217,93 +339,6 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 217 | unsafe1 ++ unsafe2 | 339 | unsafe1 ++ unsafe2 |
| 218 | } | 340 | } |
| 219 | 341 | ||
| 220 | /** Compute the RSA dependency graph | ||
| 221 | * | ||
| 222 | * This is used to approximate the input ontology to RSA. | ||
| 223 | * | ||
| 224 | * @return a tuple containing the dependency graph and a map between | ||
| 225 | * the constants newly introduced and the corresponding axioms in the | ||
| 226 | * ontology. | ||
| 227 | */ | ||
| 228 | private def dependencyGraph() | ||
| 229 | : (Graph[Resource, DiEdge], Map[String, OWLAxiom]) = { | ||
| 230 | val unsafe = this.unsafeRoles | ||
| 231 | var nodemap = Map.empty[String, OWLAxiom] | ||
| 232 | |||
| 233 | object RSAConverter extends RDFoxConverter { | ||
| 234 | |||
| 235 | override def convert( | ||
| 236 | expr: OWLClassExpression, | ||
| 237 | term: Term, | ||
| 238 | unsafe: List[OWLObjectPropertyExpression], | ||
| 239 | skolem: SkolemStrategy, | ||
| 240 | suffix: RSASuffix | ||
| 241 | ): Shards = | ||
| 242 | (expr, skolem) match { | ||
| 243 | |||
| 244 | case (e: OWLObjectSomeValuesFrom, c: Constant) => { | ||
| 245 | nodemap.update(c.iri.getIRI, c.axiom) | ||
| 246 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 247 | if (unsafe contains e.getProperty) | ||
| 248 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
| 249 | else | ||
| 250 | (RSA.PE(term, c.iri) :: res, ext) | ||
| 251 | } | ||
| 252 | |||
| 253 | case (e: OWLDataSomeValuesFrom, c: Constant) => { | ||
| 254 | nodemap.update(c.iri.getIRI, c.axiom) | ||
| 255 | val (res, ext) = super.convert(e, term, unsafe, skolem, suffix) | ||
| 256 | if (unsafe contains e.getProperty) | ||
| 257 | (RSA.PE(term, c.iri) :: RSA.U(c.iri) :: res, ext) | ||
| 258 | else | ||
| 259 | (RSA.PE(term, c.iri) :: res, ext) | ||
| 260 | } | ||
| 261 | |||
| 262 | case _ => super.convert(expr, term, unsafe, skolem, suffix) | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | /* Ontology convertion into LP rules */ | ||
| 267 | val term = RSAOntology.genFreshVariable() | ||
| 268 | val result = axioms.map(a => | ||
| 269 | RSAConverter.convert(a, term, unsafe, new Constant(a), Empty) | ||
| 270 | ) | ||
| 271 | |||
| 272 | val datalog = result.unzip | ||
| 273 | val facts = datalog._1.flatten | ||
| 274 | var rules = datalog._2.flatten | ||
| 275 | |||
| 276 | /* Open connection with RDFox */ | ||
| 277 | val (server, data) = RDFoxUtil.openConnection("rsa_dependency_graph") | ||
| 278 | |||
| 279 | /* Add additional built-in rules */ | ||
| 280 | val varX = Variable.create("X") | ||
| 281 | val varY = Variable.create("Y") | ||
| 282 | rules = Rule.create( | ||
| 283 | RSA.E(varX, varY), | ||
| 284 | RSA.PE(varX, varY), | ||
| 285 | RSA.U(varX), | ||
| 286 | RSA.U(varY) | ||
| 287 | ) :: rules | ||
| 288 | /* Load facts and rules from ontology */ | ||
| 289 | RDFoxUtil.addFacts(data, facts) | ||
| 290 | RDFoxUtil.addRules(data, rules) | ||
| 291 | /* Load data files */ | ||
| 292 | RDFoxUtil.addData(data, datafiles: _*) | ||
| 293 | |||
| 294 | /* Build the graph */ | ||
| 295 | val query = "SELECT ?X ?Y WHERE { ?X rsa:E ?Y }" | ||
| 296 | val answers = RDFoxUtil.submitQuery(data, query, RSA.Prefixes).get | ||
| 297 | var edges: Seq[DiEdge[Resource]] = | ||
| 298 | answers.collect { case (_, Seq(n1, n2)) => n1 ~> n2 } | ||
| 299 | val graph = Graph(edges: _*) | ||
| 300 | |||
| 301 | /* Close connection to RDFox */ | ||
| 302 | RDFoxUtil.closeConnection(server, data) | ||
| 303 | |||
| 304 | (graph, nodemap) | ||
| 305 | } | ||
| 306 | |||
| 307 | /** Approximate a Horn-ALCHOIQ ontology to RSA | 342 | /** Approximate a Horn-ALCHOIQ ontology to RSA |
| 308 | * | 343 | * |
| 309 | * This is done by gathering those axioms that prevent the ontology | 344 | * This is done by gathering those axioms that prevent the ontology |
| @@ -313,61 +348,61 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 313 | * @param graph the graph used to compute the axioms to remove. | 348 | * @param graph the graph used to compute the axioms to remove. |
| 314 | * @param nodemap map from graph nodes to ontology axioms. | 349 | * @param nodemap map from graph nodes to ontology axioms. |
| 315 | */ | 350 | */ |
| 316 | def toRSA(): RSAOntology = Logger.timed( | 351 | // def toRSA(): RSAOntology = Logger.timed( |
| 317 | { | 352 | // { |
| 318 | 353 | ||
| 319 | /* Compute the dependency graph for the ontology */ | 354 | // /* Compute the dependency graph for the ontology */ |
| 320 | val (graph, nodemap) = this.dependencyGraph() | 355 | // val (graph, nodemap) = this.dependencyGraph() |
| 321 | 356 | ||
| 322 | /* Define node colors for the graph visit */ | 357 | // /* Define node colors for the graph visit */ |
| 323 | sealed trait NodeColor | 358 | // sealed trait NodeColor |
| 324 | case object Unvisited extends NodeColor | 359 | // case object Unvisited extends NodeColor |
| 325 | case object Visited extends NodeColor | 360 | // case object Visited extends NodeColor |
| 326 | case object ToDelete extends NodeColor | 361 | // case object ToDelete extends NodeColor |
| 327 | 362 | ||
| 328 | /* Keep track of node colors during graph visit */ | 363 | // /* Keep track of node colors during graph visit */ |
| 329 | var color = Map.from[Resource, NodeColor]( | 364 | // var color = Map.from[Resource, NodeColor]( |
| 330 | graph.nodes.toOuter.map(k => (k, Unvisited)) | 365 | // graph.nodes.toOuter.map(k => (k, Unvisited)) |
| 331 | ) | 366 | // ) |
| 332 | 367 | ||
| 333 | for { | 368 | // for { |
| 334 | component <- graph.componentTraverser().map(_ to Graph) | 369 | // component <- graph.componentTraverser().map(_ to Graph) |
| 335 | edge <- component | 370 | // edge <- component |
| 336 | .outerEdgeTraverser(component.nodes.head) | 371 | // .outerEdgeTraverser(component.nodes.head) |
| 337 | .withKind(BreadthFirst) | 372 | // .withKind(BreadthFirst) |
| 338 | } yield { | 373 | // } yield { |
| 339 | val source = edge._1 | 374 | // val source = edge._1 |
| 340 | val target = edge._2 | 375 | // val target = edge._2 |
| 341 | color(source) match { | 376 | // color(source) match { |
| 342 | case Unvisited | Visited => { | 377 | // case Unvisited | Visited => { |
| 343 | color(target) match { | 378 | // color(target) match { |
| 344 | case Unvisited => | 379 | // case Unvisited => |
| 345 | color(source) = Visited; | 380 | // color(source) = Visited; |
| 346 | color(target) = Visited | 381 | // color(target) = Visited |
| 347 | case Visited => | 382 | // case Visited => |
| 348 | color(source) = ToDelete | 383 | // color(source) = ToDelete |
| 349 | case ToDelete => | 384 | // case ToDelete => |
| 350 | color(source) = Visited | 385 | // color(source) = Visited |
| 351 | } | 386 | // } |
| 352 | } | 387 | // } |
| 353 | case ToDelete => | 388 | // case ToDelete => |
| 354 | } | 389 | // } |
| 355 | } | 390 | // } |
| 356 | 391 | ||
| 357 | val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => | 392 | // val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => |
| 358 | nodemap(resource.getIRI) | 393 | // nodemap(resource.getIRI) |
| 359 | }.toSeq | 394 | // }.toSeq |
| 360 | 395 | ||
| 361 | /* Remove axioms from approximated ontology */ | 396 | // /* Remove axioms from approximated ontology */ |
| 362 | ontology.removeAxioms(toDelete: _*) | 397 | // ontology.removeAxioms(toDelete: _*) |
| 363 | this.removed = toDelete | 398 | // this.removed = toDelete |
| 364 | 399 | ||
| 365 | /* Return RSA ontology */ | 400 | // /* Return RSA ontology */ |
| 366 | RSAOntology(ontology, datafiles: _*) | 401 | // RSAOntology(ontology, datafiles: _*) |
| 367 | }, | 402 | // }, |
| 368 | "Horn-ALCHOIQ to RSA approximation:", | 403 | // "Horn-ALCHOIQ to RSA approximation:", |
| 369 | Logger.DEBUG | 404 | // Logger.DEBUG |
| 370 | ) | 405 | // ) |
| 371 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> | 406 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> |
| 372 | // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, | 407 | // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, |
| 373 | // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) | 408 | // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) |
| @@ -651,7 +686,7 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 651 | val conflR = this.confl(roleR) | 686 | val conflR = this.confl(roleR) |
| 652 | // We just need the TBox to find | 687 | // We just need the TBox to find |
| 653 | val terms = for { | 688 | val terms = for { |
| 654 | axiom1 <- tbox | 689 | axiom1 <- axioms |
| 655 | if axiom1.isT5 | 690 | if axiom1.isT5 |
| 656 | // We expect only one role coming out of a T5 axiom | 691 | // We expect only one role coming out of a T5 axiom |
| 657 | roleS <- axiom1.objectPropertyExpressionsInSignature | 692 | roleS <- axiom1.objectPropertyExpressionsInSignature |
| @@ -676,28 +711,28 @@ class RSAOntology(val original: OWLOntology, val datafiles: File*) { | |||
| 676 | this.self(axiom) | this.cycle(axiom) | 711 | this.self(axiom) | this.cycle(axiom) |
| 677 | 712 | ||
| 678 | /** Log normalization/approximation statistics */ | 713 | /** Log normalization/approximation statistics */ |
| 679 | def statistics(level: Logger.Level = Logger.DEBUG): Unit = { | 714 | // def statistics(level: Logger.Level = Logger.DEBUG): Unit = { |
| 680 | Logger.print( | 715 | // Logger.print( |
| 681 | s"Logical axioms in original input ontology: ${original.getLogicalAxiomCount(true)}", | 716 | // s"Logical axioms in original input ontology: ${original.getLogicalAxiomCount(true)}", |
| 682 | level | 717 | // level |
| 683 | ) | 718 | // ) |
| 684 | Logger.print( | 719 | // Logger.print( |
| 685 | s"Logical axioms discarded in Horn-ALCHOIQ approximation: ${normalizer.discarded}", | 720 | // s"Logical axioms discarded in Horn-ALCHOIQ approximation: ${normalizer.discarded}", |
| 686 | level | 721 | // level |
| 687 | ) | 722 | // ) |
| 688 | Logger.print( | 723 | // Logger.print( |
| 689 | s"Logical axioms shifted in Horn-ALCHOIQ approximation: ${normalizer.shifted}", | 724 | // s"Logical axioms shifted in Horn-ALCHOIQ approximation: ${normalizer.shifted}", |
| 690 | level | 725 | // level |
| 691 | ) | 726 | // ) |
| 692 | Logger.print( | 727 | // Logger.print( |
| 693 | s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology | 728 | // s"Logical axioms in Horn-ALCHOIQ ontology: ${ontology |
| 694 | .getLogicalAxiomCount(true)} (${tbox.length}/${rbox.length}/${abox.length})", | 729 | // .getLogicalAxiomCount(true)} (${axioms.length}/${axioms.length}/${axioms.length})", |
| 695 | level | 730 | // level |
| 696 | ) | 731 | // ) |
| 697 | Logger.print( | 732 | // Logger.print( |
| 698 | s"Logical axioms discarded in RSA approximation: ${removed.length}", | 733 | // s"Logical axioms discarded in RSA approximation: ${removed.length}", |
| 699 | level | 734 | // level |
| 700 | ) | 735 | // ) |
| 701 | } | 736 | // } |
| 702 | 737 | ||
| 703 | } // class RSAOntology | 738 | } // class RSAOntology |
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..1b49413 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/approximation.scala | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | package uk.ac.ox.cs.rsacomb.approximation | ||
| 2 | |||
| 3 | import java.io.File | ||
| 4 | import org.semanticweb.owlapi.model.OWLLogicalAxiom | ||
| 5 | |||
| 6 | /** Ontology approximation technique. */ | ||
| 7 | trait Approximation { | ||
| 8 | |||
| 9 | /** Approximate an ontology. | ||
| 10 | * | ||
| 11 | * @param ontology input ontology | ||
| 12 | * @return a new approximated ontology | ||
| 13 | */ | ||
| 14 | def approximate( | ||
| 15 | ontology: List[OWLLogicalAxiom], | ||
| 16 | datafiles: List[File] | ||
| 17 | ): List[OWLLogicalAxiom] | ||
| 18 | |||
| 19 | } | ||
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..8a86d19 --- /dev/null +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/approximation/lowerbound.scala | |||
| @@ -0,0 +1,241 @@ | |||
| 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.{Set, 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.RSAUtil | ||
| 17 | import uk.ac.ox.cs.rsacomb.converter.Normalizer | ||
| 18 | |||
| 19 | /** Approximation algorithm that mantains soundness for CQ answering. | ||
| 20 | * | ||
| 21 | * The input OWL 2 ontology is assumed to be normalized and the output | ||
| 22 | * ontology is guaranteed to be in RSA. | ||
| 23 | * | ||
| 24 | * The algorithm is performed in three steps: | ||
| 25 | * 1. the ontology is reduced to ALCHOIQ by discarding any axiom | ||
| 26 | * that is not in the language; | ||
| 27 | * 2. the ontology is further reduced to Horn-ALCHOIQ by shifting | ||
| 28 | * axioms with disjunction on the rhs; | ||
| 29 | * 3. the ontology is approximated to RSA by manipulating its | ||
| 30 | * dependency graph. | ||
| 31 | * | ||
| 32 | * @see [[uk.ac.ox.cs.rsacomb.converter.Normalizer]] | ||
| 33 | */ | ||
| 34 | class LowerBound extends Approximation { | ||
| 35 | |||
| 36 | /** Simplify conversion between Java and Scala collections */ | ||
| 37 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | ||
| 38 | |||
| 39 | /** Simplify conversion between OWLAPI and RDFox concepts */ | ||
| 40 | import uk.ac.ox.cs.rsacomb.implicits.RDFox._ | ||
| 41 | |||
| 42 | /** Manager instance to interface with OWLAPI */ | ||
| 43 | val manager = OWLManager.createOWLOntologyManager() | ||
| 44 | val factory = manager.getOWLDataFactory() | ||
| 45 | |||
| 46 | val normalizer = new Normalizer() | ||
| 47 | |||
| 48 | /** Main entry point for the approximation algorithm */ | ||
| 49 | def approximate( | ||
| 50 | ontology: List[OWLLogicalAxiom], | ||
| 51 | datafiles: List[File] | ||
| 52 | ): List[OWLLogicalAxiom] = { | ||
| 53 | /* Normalize axioms */ | ||
| 54 | val axioms1 = ontology flatMap normalizer.normalize | ||
| 55 | /* Delete any axiom outside of ALCHOIQ */ | ||
| 56 | val axioms2 = axioms1 filterNot inALCHOIQ | ||
| 57 | /* Shift any axiom with disjunction on the rhs */ | ||
| 58 | val axioms3 = for { | ||
| 59 | a1 <- axioms1 | ||
| 60 | a2 <- shift(a1) | ||
| 61 | a3 <- normalizer.normalize(a2) | ||
| 62 | } yield a3 | ||
| 63 | /* Approximate to RSA */ | ||
| 64 | toRSA(axioms3, datafiles) | ||
| 65 | } | ||
| 66 | |||
| 67 | /** Discards all axioms outside ALCHOIQ */ | ||
| 68 | def inALCHOIQ(axiom: OWLLogicalAxiom): Boolean = | ||
| 69 | axiom match { | ||
| 70 | case a: OWLSubClassOfAxiom => { | ||
| 71 | val sub = a.getSubClass.getNNF | ||
| 72 | val sup = a.getSuperClass.getNNF | ||
| 73 | (sub, sup) match { | ||
| 74 | case (sub: OWLObjectAllValuesFrom, _) => false | ||
| 75 | case (sub: OWLDataAllValuesFrom, _) => false | ||
| 76 | case (_, sup: OWLDataAllValuesFrom) => false | ||
| 77 | case (sub: OWLObjectMinCardinality, _) if sub.getCardinality >= 2 => | ||
| 78 | false | ||
| 79 | case (sub: OWLDataMinCardinality, _) if sub.getCardinality >= 2 => | ||
| 80 | false | ||
| 81 | case (_, sup: OWLObjectMinCardinality) if sup.getCardinality >= 2 => | ||
| 82 | false | ||
| 83 | case (_, sup: OWLDataMinCardinality) if sup.getCardinality >= 2 => | ||
| 84 | false | ||
| 85 | case (sub: OWLObjectMaxCardinality, _) => false | ||
| 86 | case (sub: OWLDataMaxCardinality, _) => false | ||
| 87 | case (_, sup: OWLObjectMaxCardinality) if sup.getCardinality >= 2 => | ||
| 88 | false | ||
| 89 | case (_, sup: OWLDataMaxCardinality) if sup.getCardinality >= 1 => | ||
| 90 | false | ||
| 91 | case (_, sup: OWLObjectOneOf) if sup.getIndividuals.length > 2 => | ||
| 92 | false | ||
| 93 | case (sub: OWLObjectHasSelf, _) => false | ||
| 94 | case (_, sup: OWLObjectHasSelf) => false | ||
| 95 | case _ => true | ||
| 96 | } | ||
| 97 | } | ||
| 98 | case a: OWLTransitiveObjectPropertyAxiom => false | ||
| 99 | case a: OWLReflexiveObjectPropertyAxiom => false | ||
| 100 | case a: OWLSubPropertyChainOfAxiom => false | ||
| 101 | case a: OWLAsymmetricObjectPropertyAxiom => false | ||
| 102 | case a => true | ||
| 103 | } | ||
| 104 | |||
| 105 | /** Shifting axioms with disjunction on the rhs. | ||
| 106 | * | ||
| 107 | * The process of shifting presenves soundness but completenes w.r.t. | ||
| 108 | * CQ answering is lost. | ||
| 109 | * | ||
| 110 | * @example | ||
| 111 | * | ||
| 112 | * A -> B1 u B2 u B3 . | ||
| 113 | * | ||
| 114 | * becomes | ||
| 115 | * | ||
| 116 | * A n nB1 n nB2 -> B3 . | ||
| 117 | * A n nB1 n nB3 -> B2 . | ||
| 118 | * A n nB2 n nB3 -> B1 . | ||
| 119 | * nB1 n nB2 n nB3 -> nA . | ||
| 120 | * | ||
| 121 | * where nA, nB1, nB2, nB3 are fresh predicates "corresponding" to | ||
| 122 | * the negation of A, B1, B2, B3 respectively. | ||
| 123 | */ | ||
| 124 | def shift(axiom: OWLLogicalAxiom): List[OWLLogicalAxiom] = | ||
| 125 | axiom match { | ||
| 126 | case a: OWLSubClassOfAxiom => { | ||
| 127 | val sub = a.getSubClass.getNNF | ||
| 128 | val sup = a.getSuperClass.getNNF | ||
| 129 | sup match { | ||
| 130 | case sup: OWLObjectUnionOf => { | ||
| 131 | val body = sub.asConjunctSet.map((atom) => | ||
| 132 | (atom, RSAOntology.getFreshOWLClass()) | ||
| 133 | ) | ||
| 134 | val head = sup.asDisjunctSet.map((atom) => | ||
| 135 | (atom, RSAOntology.getFreshOWLClass()) | ||
| 136 | ) | ||
| 137 | |||
| 138 | val r1 = | ||
| 139 | factory.getOWLSubClassOfAxiom( | ||
| 140 | factory.getOWLObjectIntersectionOf( | ||
| 141 | (body.map(_._1) ++ head.map(_._2)): _* | ||
| 142 | ), | ||
| 143 | factory.getOWLNothing | ||
| 144 | ) | ||
| 145 | |||
| 146 | val r2s = | ||
| 147 | for { | ||
| 148 | (a, na) <- head | ||
| 149 | hs = head.map(_._2).filterNot(_ equals na) | ||
| 150 | } yield factory.getOWLSubClassOfAxiom( | ||
| 151 | factory.getOWLObjectIntersectionOf( | ||
| 152 | (body.map(_._1) ++ hs): _* | ||
| 153 | ), | ||
| 154 | a | ||
| 155 | ) | ||
| 156 | |||
| 157 | val r3s = | ||
| 158 | for { | ||
| 159 | (a, na) <- body | ||
| 160 | bs = body.map(_._1).filterNot(_ equals a) | ||
| 161 | } yield factory.getOWLSubClassOfAxiom( | ||
| 162 | factory.getOWLObjectIntersectionOf( | ||
| 163 | (bs ++ head.map(_._2)): _* | ||
| 164 | ), | ||
| 165 | na | ||
| 166 | ) | ||
| 167 | |||
| 168 | List(r1) ++ r2s ++ r3s | ||
| 169 | } | ||
| 170 | case _ => List(axiom) | ||
| 171 | } | ||
| 172 | } | ||
| 173 | case _ => List(axiom) | ||
| 174 | } | ||
| 175 | |||
| 176 | /** Approximate a Horn-ALCHOIQ ontology to RSA | ||
| 177 | * | ||
| 178 | * This is done by gathering those axioms that prevent the ontology | ||
| 179 | * dependency graph from being tree-shaped, and removing them. | ||
| 180 | * | ||
| 181 | * @param axioms the set of axioms to approximate. | ||
| 182 | * @return the approximated set of axioms. | ||
| 183 | */ | ||
| 184 | def toRSA( | ||
| 185 | axioms: List[OWLLogicalAxiom], | ||
| 186 | datafiles: List[File] | ||
| 187 | ): List[OWLLogicalAxiom] = { | ||
| 188 | /* Compute the dependency graph for the ontology */ | ||
| 189 | val (graph, nodemap) = RSAUtil.dependencyGraph(axioms, datafiles) | ||
| 190 | |||
| 191 | /* Define node colors for the graph visit */ | ||
| 192 | sealed trait NodeColor | ||
| 193 | case object Unvisited extends NodeColor | ||
| 194 | case object Visited extends NodeColor | ||
| 195 | case object ToDelete extends NodeColor | ||
| 196 | |||
| 197 | /* Keep track of node colors during graph visit */ | ||
| 198 | var color = Map.from[Resource, NodeColor]( | ||
| 199 | graph.nodes.toOuter.map(k => (k, Unvisited)) | ||
| 200 | ) | ||
| 201 | |||
| 202 | for { | ||
| 203 | component <- graph.componentTraverser().map(_ to Graph) | ||
| 204 | edge <- component | ||
| 205 | .outerEdgeTraverser(component.nodes.head) | ||
| 206 | .withKind(BreadthFirst) | ||
| 207 | } yield { | ||
| 208 | val source = edge._1 | ||
| 209 | val target = edge._2 | ||
| 210 | color(source) match { | ||
| 211 | case Unvisited | Visited => { | ||
| 212 | color(target) match { | ||
| 213 | case Unvisited => | ||
| 214 | color(source) = Visited; | ||
| 215 | color(target) = Visited | ||
| 216 | case Visited => | ||
| 217 | color(source) = ToDelete | ||
| 218 | case ToDelete => | ||
| 219 | color(source) = Visited | ||
| 220 | } | ||
| 221 | } | ||
| 222 | case ToDelete => | ||
| 223 | } | ||
| 224 | } | ||
| 225 | |||
| 226 | val toDelete = color.iterator.collect { case (resource: IRI, ToDelete) => | ||
| 227 | nodemap(resource.getIRI) | ||
| 228 | }.toList | ||
| 229 | |||
| 230 | /* Remove axioms from approximated ontology */ | ||
| 231 | axioms diff toDelete | ||
| 232 | } | ||
| 233 | |||
| 234 | // val edges1 = Seq('A ~> 'B, 'B ~> 'C, 'C ~> 'D, 'D ~> 'H, 'H ~> | ||
| 235 | // 'G, 'G ~> 'F, 'E ~> 'A, 'E ~> 'F, 'B ~> 'E, 'F ~> 'G, 'B ~> 'F, | ||
| 236 | // 'C ~> 'G, 'D ~> 'C, 'H ~> 'D) | ||
| 237 | // val edges2 = Seq('I ~> 'M, 'I ~> 'L, 'L ~> 'N, 'M ~> 'N) | ||
| 238 | // val edges3 = Seq('P ~> 'O) | ||
| 239 | // val graph = Graph.from(edges = edges1 ++ edges2 ++ edges3) | ||
| 240 | // | ||
| 241 | } | ||
diff --git a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala index 5329f26..4b298f4 100644 --- a/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala +++ b/src/main/scala/uk/ac/ox/cs/rsacomb/converter/Normalizer.scala | |||
| @@ -4,6 +4,7 @@ import org.semanticweb.owlapi.apibinding.OWLManager | |||
| 4 | import org.semanticweb.owlapi.model._ | 4 | import org.semanticweb.owlapi.model._ |
| 5 | 5 | ||
| 6 | import uk.ac.ox.cs.rsacomb.util.Logger | 6 | import uk.ac.ox.cs.rsacomb.util.Logger |
| 7 | import uk.ac.ox.cs.rsacomb.RSAOntology | ||
| 7 | 8 | ||
| 8 | object Normalizer { | 9 | object Normalizer { |
| 9 | 10 | ||
| @@ -25,12 +26,6 @@ class Normalizer() { | |||
| 25 | /** Simplify conversion between Java and Scala collections */ | 26 | /** Simplify conversion between Java and Scala collections */ |
| 26 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ | 27 | import uk.ac.ox.cs.rsacomb.implicits.JavaCollections._ |
| 27 | 28 | ||
| 28 | private var counter = -1 | ||
| 29 | def freshOWLClass(): OWLClass = { | ||
| 30 | counter += 1 | ||
| 31 | factory.getOWLClass(s"X$counter") | ||
| 32 | } | ||
| 33 | |||
| 34 | /** Statistics */ | 29 | /** Statistics */ |
| 35 | var discarded = 0 | 30 | var discarded = 0 |
| 36 | var shifted = 0 | 31 | var shifted = 0 |
| @@ -58,7 +53,7 @@ class Normalizer() { | |||
| 58 | * C c D -> { C c X, X c D } | 53 | * C c D -> { C c X, X c D } |
| 59 | */ | 54 | */ |
| 60 | case _ if !sub.isOWLClass && !sup.isOWLClass => { | 55 | case _ if !sub.isOWLClass && !sup.isOWLClass => { |
| 61 | val cls = freshOWLClass() | 56 | val cls = RSAOntology.getFreshOWLClass() |
| 62 | Seq( | 57 | Seq( |
| 63 | factory.getOWLSubClassOfAxiom(sub, cls), | 58 | factory.getOWLSubClassOfAxiom(sub, cls), |
| 64 | factory.getOWLSubClassOfAxiom(cls, sup) | 59 | factory.getOWLSubClassOfAxiom(cls, sup) |
| @@ -79,7 +74,7 @@ class Normalizer() { | |||
| 79 | if (conj.isOWLClass) | 74 | if (conj.isOWLClass) |
| 80 | (acc1 :+ conj, acc2) | 75 | (acc1 :+ conj, acc2) |
| 81 | else { | 76 | else { |
| 82 | val cls = freshOWLClass() | 77 | val cls = RSAOntology.getFreshOWLClass() |
| 83 | ( | 78 | ( |
| 84 | acc1 :+ cls, | 79 | acc1 :+ cls, |
| 85 | acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls) | 80 | acc2 :+ factory.getOWLSubClassOfAxiom(conj, cls) |
| @@ -138,7 +133,7 @@ class Normalizer() { | |||
| 138 | */ | 133 | */ |
| 139 | case (sub: OWLObjectSomeValuesFrom, _) | 134 | case (sub: OWLObjectSomeValuesFrom, _) |
| 140 | if !sub.getFiller.isOWLClass => { | 135 | if !sub.getFiller.isOWLClass => { |
| 141 | val cls = freshOWLClass() | 136 | val cls = RSAOntology.getFreshOWLClass() |
| 142 | Seq( | 137 | Seq( |
| 143 | factory.getOWLSubClassOfAxiom(sub.getFiller, cls), | 138 | factory.getOWLSubClassOfAxiom(sub.getFiller, cls), |
| 144 | factory.getOWLSubClassOfAxiom( | 139 | factory.getOWLSubClassOfAxiom( |
| @@ -153,7 +148,7 @@ class Normalizer() { | |||
| 153 | */ | 148 | */ |
| 154 | case (_, sup: OWLObjectSomeValuesFrom) | 149 | case (_, sup: OWLObjectSomeValuesFrom) |
| 155 | if !sup.getFiller.isOWLClass => { | 150 | if !sup.getFiller.isOWLClass => { |
| 156 | val cls = freshOWLClass() | 151 | val cls = RSAOntology.getFreshOWLClass() |
| 157 | Seq( | 152 | Seq( |
| 158 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), | 153 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), |
| 159 | factory.getOWLSubClassOfAxiom( | 154 | factory.getOWLSubClassOfAxiom( |
| @@ -298,7 +293,7 @@ class Normalizer() { | |||
| 298 | ) | 293 | ) |
| 299 | case (_, sup: OWLObjectMaxCardinality) | 294 | case (_, sup: OWLObjectMaxCardinality) |
| 300 | if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => { | 295 | if sup.getCardinality == 1 && !sup.getFiller.isOWLClass => { |
| 301 | val cls = freshOWLClass() | 296 | val cls = RSAOntology.getFreshOWLClass() |
| 302 | Seq( | 297 | Seq( |
| 303 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), | 298 | factory.getOWLSubClassOfAxiom(cls, sup.getFiller), |
| 304 | factory.getOWLSubClassOfAxiom( | 299 | factory.getOWLSubClassOfAxiom( |
| @@ -488,7 +483,7 @@ class Normalizer() { | |||
| 488 | * C(a) -> { X(a), X c C } | 483 | * C(a) -> { X(a), X c C } |
| 489 | */ | 484 | */ |
| 490 | case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => { | 485 | case a: OWLClassAssertionAxiom if !a.getClassExpression.isOWLClass => { |
| 491 | val cls = freshOWLClass() | 486 | val cls = RSAOntology.getFreshOWLClass() |
| 492 | Seq( | 487 | Seq( |
| 493 | factory.getOWLClassAssertionAxiom(cls, a.getIndividual), | 488 | factory.getOWLClassAssertionAxiom(cls, a.getIndividual), |
| 494 | factory.getOWLSubClassOfAxiom(cls, a.getClassExpression) | 489 | factory.getOWLSubClassOfAxiom(cls, a.getClassExpression) |
| @@ -532,8 +527,10 @@ class Normalizer() { | |||
| 532 | sub: OWLClassExpression, | 527 | sub: OWLClassExpression, |
| 533 | sup: OWLObjectUnionOf | 528 | sup: OWLObjectUnionOf |
| 534 | ): Seq[OWLLogicalAxiom] = { | 529 | ): Seq[OWLLogicalAxiom] = { |
| 535 | val body = sub.asConjunctSet.map((atom) => (atom, freshOWLClass())) | 530 | val body = |
| 536 | val head = sup.asDisjunctSet.map((atom) => (atom, freshOWLClass())) | 531 | sub.asConjunctSet.map((atom) => (atom, RSAOntology.getFreshOWLClass())) |
| 532 | val head = | ||
| 533 | sup.asDisjunctSet.map((atom) => (atom, RSAOntology.getFreshOWLClass())) | ||
| 537 | 534 | ||
| 538 | /* Update statistics */ | 535 | /* Update statistics */ |
| 539 | shifted += 1 | 536 | shifted += 1 |
