001/** 002 * Copyright (C) 2007 - 2016, Jens Lehmann 003 * 004 * This file is part of DL-Learner. 005 * 006 * DL-Learner is free software; you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation; either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * DL-Learner is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with this program. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package org.dllearner.algorithms.el; 020 021import java.util.Collection; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.LinkedList; 025import java.util.List; 026import java.util.Map; 027import java.util.Map.Entry; 028import java.util.NavigableSet; 029import java.util.Set; 030import java.util.TreeSet; 031 032import org.apache.log4j.Logger; 033import org.dllearner.core.AbstractReasonerComponent; 034import org.dllearner.core.owl.ClassHierarchy; 035import org.dllearner.core.owl.ObjectPropertyHierarchy; 036import org.dllearner.core.owl.UnsupportedLanguageException; 037import org.semanticweb.owlapi.model.OWLClass; 038import org.semanticweb.owlapi.model.OWLClassExpression; 039import org.semanticweb.owlapi.model.OWLDataProperty; 040import org.semanticweb.owlapi.model.OWLDataSomeValuesFrom; 041import org.semanticweb.owlapi.model.OWLObjectIntersectionOf; 042import org.semanticweb.owlapi.model.OWLObjectProperty; 043import org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom; 044import org.semanticweb.owlapi.model.OWLProperty; 045 046/** 047 * Represents an EL description tree. Unlike {@link ELDescriptionNode}, this is 048 * a tree-wide structure, i.e. it does not implement the tree structure itself, 049 * but is used to store information about the tree. 050 * 051 * @author Jens Lehmann 052 * 053 */ 054public class ELDescriptionTree implements Cloneable { 055 056 @SuppressWarnings("unused") 057 private static Logger logger = Logger.getLogger(ELDescriptionTree.class); 058 059 // to simplify equivalence checks and minimisation, we 060 // attach a simulation relation to the OWLClassExpression tree 061 // private Simulation simulation; 062 063 // max level = 0 means that there is no tree at all 064 // (max level = 1 means the root node exists) 065 private int maxLevel = 0; 066 067 protected int size = 1; 068 069 protected ELDescriptionNode rootNode; 070 071 // the set of all nodes in the tree 072 private Collection<ELDescriptionNode> nodes = new LinkedList<>(); 073 074 // nodes on a given level of the tree 075 private Map<Integer, Set<ELDescriptionNode>> levelNodeMapping = new HashMap<>(); 076 077 // the background knowledge (we need to have it explicitly here, 078 // since we store simulation information in the tree and simulation 079 // updates depend on background knowledge) 080 protected AbstractReasonerComponent rs; 081 protected ClassHierarchy subsumptionHierarchy; 082 protected ObjectPropertyHierarchy roleHierarchy; 083 084 public ELDescriptionTree(AbstractReasonerComponent rs) { 085 this.rs = rs; 086 subsumptionHierarchy = rs.getClassHierarchy(); 087 roleHierarchy = rs.getObjectPropertyHierarchy(); 088 } 089 090 /** 091 * Constructs an EL description tree from an EL class expression. 092 * 093 * @param ce the EL class expression 094 */ 095 public ELDescriptionTree(AbstractReasonerComponent rs, OWLClassExpression ce) { 096 this(rs); 097 // construct root node and recursively build the tree 098 rootNode = new ELDescriptionNode(this); 099 constructTree(ce, rootNode); 100 } 101 102 private void constructTree(OWLClassExpression description, ELDescriptionNode parentNode) { 103 if (description.isOWLThing()) { 104 // nothing needs to be done as an empty set is owl:Thing 105 } else if (!description.isAnonymous()) { 106 parentNode.extendLabel(description.asOWLClass()); 107 } else if (description instanceof OWLObjectSomeValuesFrom) { 108 OWLObjectProperty op = ((OWLObjectSomeValuesFrom) description).getProperty().asOWLObjectProperty(); 109 ELDescriptionNode newNode = new ELDescriptionNode(parentNode, op, new TreeSet<>()); 110 constructTree(((OWLObjectSomeValuesFrom) description).getFiller(), newNode); 111 } else if (description instanceof OWLDataSomeValuesFrom) { 112 OWLDataProperty op = ((OWLDataSomeValuesFrom) description).getProperty().asOWLDataProperty(); 113 ELDescriptionNode newNode = new ELDescriptionNode(parentNode, op, ((OWLDataSomeValuesFrom) description).getFiller()); 114 } else if (description instanceof OWLObjectIntersectionOf) { 115 // loop through all elements of the intersection 116 for (OWLClassExpression child : ((OWLObjectIntersectionOf) description).getOperands()) { 117 if (!child.isAnonymous()) { 118 parentNode.extendLabel(child.asOWLClass()); 119 } else if (child instanceof OWLObjectSomeValuesFrom) { 120 OWLObjectProperty op = ((OWLObjectSomeValuesFrom) child).getProperty().asOWLObjectProperty(); 121 ELDescriptionNode newNode = new ELDescriptionNode(parentNode, op, 122 new TreeSet<>()); 123 constructTree(((OWLObjectSomeValuesFrom) child).getFiller(), newNode); 124 } else { 125 throw new UnsupportedLanguageException(description + " specifically " + child, 126 "EL"); 127 } 128 } 129 } else { 130 throw new UnsupportedLanguageException(description.toString(), "EL"); 131 } 132 } 133 134 /** 135 * Gets the nodes on a specific level of the tree. This information is 136 * cached here for performance reasons. 137 * 138 * @param level 139 * The level (distance from root node). 140 * @return The set of all nodes on the specified level within this tree. 141 */ 142 public Set<ELDescriptionNode> getNodesOnLevel(int level) { 143 return levelNodeMapping.get(level); 144 } 145 146 public OWLClassExpression transformToClassExpression() { 147 return rootNode.transformToDescription(); 148 } 149 150 // checks whether this tree is minimal wrt. background knowledge 151 public boolean isMinimal() { 152// System.out.println(this); 153// System.out.println(levelNodeMapping); 154 // loop through all levels starting from root (level 1) 155 for(int i=1; i<=maxLevel; i++) { 156 // get all nodes of this level 157 Set<ELDescriptionNode> nodes = levelNodeMapping.get(i); 158// System.out.println("level " + i + ": " + nodes); 159 for(ELDescriptionNode node : nodes) { 160 List<ELDescriptionEdge> edges = node.getEdges(); 161 // we need to compare all combination of edges 162 // (in both directions because subsumption is obviously 163 // not symmetric) 164 for(int j=0; j<edges.size(); j++) { 165 for(int k=0; k<edges.size(); k++) { 166 if(j != k) { 167 // we first check inclusion property on edges 168 OWLProperty op1 = edges.get(j).getLabel(); 169 OWLProperty op2 = edges.get(k).getLabel(); 170 if(rs.isSubPropertyOf(op1, op2)) { 171 ELDescriptionNode node1 = edges.get(j).getNode(); 172 ELDescriptionNode node2 = edges.get(k).getNode(); 173 // check simulation condition 174 if(node1.in.contains(node2)) { // || node2.in.contains(node1)) { 175 // node1 is simulated by node2, i.e. we could remove one 176 // of them, so the tree is not minimal 177 return false; 178 } 179 } 180 } 181 } 182 } 183 } 184 } 185 return true; 186 } 187 188 /** 189 * Internal method for updating the node set and the level node mapping. It must be 190 * called when a new node is added to the tree. 191 * 192 * @param node 193 * The new node. 194 * @param level 195 * Level of the new node. 196 */ 197 protected void addNodeToLevel(ELDescriptionNode node, int level) { 198 nodes.add(node); 199 if (level <= maxLevel) { 200 levelNodeMapping.get(level).add(node); 201 } else if (level == maxLevel + 1) { 202 Set<ELDescriptionNode> set = new HashSet<>(); 203 set.add(node); 204 levelNodeMapping.put(level, set); 205 maxLevel++; 206 } else { 207 throw new RuntimeException("Inconsistent EL OWLClassExpression tree structure."); 208 } 209 } 210 211 /** 212 * @return the maxLevel 213 */ 214 public int getMaxLevel() { 215 return maxLevel; 216 } 217 218 /** 219 * @return the rootNode 220 */ 221 public ELDescriptionNode getRootNode() { 222 return rootNode; 223 } 224 225 /** 226 * Gets the node at the given position. The list is processed as follows: 227 * Starting with the root node, the first element i of list is read and the 228 * i-th child of root node is selected. This node is set as current node and 229 * the next element j of the list is read and the j-th child of the i-th 230 * child of the root node selected etc. 231 * 232 * @return The node at the specified position. 233 */ 234 public ELDescriptionNode getNode(int[] position) { 235// logger.trace(Helper.arrayContent(position)); 236// logger.trace(this); 237 ELDescriptionNode currentNode = rootNode; 238 for (int aPosition : position) { 239 currentNode = currentNode.getEdges().get(aPosition).getNode(); 240 } 241 return currentNode; 242 } 243 244 protected void updateSimulation(Set<ELDescriptionNode> nUpdate) { 245 // create a stack and initialize it with the nodes to be updated 246 LinkedList<ELDescriptionNode> list = new LinkedList<>(); 247 list.addAll(nUpdate); 248 249 while(list.size() != 0) { 250 // take element from bottom of stack (to ensure that all nodes on the 251 // same level are tested before any node of a lower level is tested) 252 ELDescriptionNode v = list.pollFirst(); 253 // loop through all nodes on same level 254 Set<ELDescriptionNode> sameLevel = levelNodeMapping.get(v.getLevel()); 255 for(ELDescriptionNode w : sameLevel) { 256 if(v != w) { 257 258// System.out.println(v); 259// System.out.println(w); 260 261 // we update if SC2 did not hold but does now 262 if(!v.inSC2.contains(w) && checkSC2(v,w)) { 263// System.out.println("extend sim. after update"); 264 265 extendSimulationSC2(v,w); 266 if(v.inSC1.contains(w)) { 267 extendSimulationSC12(v,w); 268 } 269 if(!list.contains(v.getParent())) { 270 list.add(v.getParent()); 271 } 272 if(!list.contains(w.getParent())) { 273 list.add(w.getParent()); 274 } 275 } 276 277 // similar case, but now possibly shrinking the simulation 278 if(w.inSC2.contains(v) && !checkSC2(w,v)) { 279// System.out.println("shrink sim. after update"); 280 281 shrinkSimulationSC2(w,v); 282 if(w.inSC1.contains(v)) { 283 shrinkSimulationSC12(w,v); 284 } 285 if(!list.contains(v.getParent())) { 286 list.add(v.getParent()); 287 } 288 if(!list.contains(w.getParent())) { 289 list.add(w.getParent()); 290 } 291 } 292 /* 293 if(!v.out.contains(w) ) { 294 System.out.println("test"); 295 if(checkSC2(v,w) && v.outSC1.contains(w)) { 296 extendSimulation(v,w); 297 list.add(v.getParent()); 298 list.add(w.getParent()); 299 } else { 300 System.out.println("test in"); 301 shrinkSimulationSC2(v,w); 302 } 303 } 304 if(!w.out.contains(v) ) { 305 if(checkSC2(w,v) && w.outSC1.contains(v)) { 306 extendSimulation(w,v); 307 list.add(v.getParent()); 308 list.add(w.getParent()); 309 } else { 310 shrinkSimulationSC2(w,v); 311 } 312 } 313 */ 314 } 315 } 316 } 317 } 318 319 // SC satisfied if both SC1 and SC2 satisfied 320 public boolean checkSC(ELDescriptionNode node1, ELDescriptionNode node2) { 321 return checkSC1(node1, node2) && checkSC2(node1, node2); 322 } 323 324 // tests simulation condition 1 (SC1) 325 public boolean checkSC1(ELDescriptionNode node1, ELDescriptionNode node2) { 326 return isSublabel(node1.getLabel(), node2.getLabel()); 327 } 328 329 private boolean isSublabel(NavigableSet<OWLClass> subLabel, NavigableSet<OWLClass> superLabel) { 330 // implemented according to definition in article 331 // (TODO can probably be done more efficiently) 332 for(OWLClass nc : superLabel) { 333 if(!containsSubclass(nc, subLabel)) { 334 return false; 335 } 336 } 337 return true; 338 } 339 340 private boolean containsSubclass(OWLClass superClass, NavigableSet<OWLClass> label) { 341 for(OWLClass nc : label) { 342 if(subsumptionHierarchy.isSubclassOf(nc, superClass)) { 343 return true; 344 } 345 } 346 return false; 347 } 348 349 // tests simulation condition 2 (SC2) 350 public boolean checkSC2(ELDescriptionNode node1, ELDescriptionNode node2) { 351 List<ELDescriptionEdge> edges1 = node1.getEdges(); 352 List<ELDescriptionEdge> edges2 = node2.getEdges(); 353 354// System.out.println(node1.transformToDescription()); 355// System.out.println(node2.transformToDescription()); 356 357 for(ELDescriptionEdge superEdge : edges2) { 358 // try to find an edge satisfying SC2 in the set, 359 // i.e. detect whether superEdge is indeed more general 360 if(!checkSC2Edge(superEdge, edges1)) { 361// System.out.println("false"); 362 return false; 363 } 364 } 365// System.out.println("true"); 366 return true; 367 } 368 369 // check whether edges contains an element satisfying SC2 370 private boolean checkSC2Edge(ELDescriptionEdge superEdge, List<ELDescriptionEdge> edges) { 371 OWLProperty superOP = superEdge.getLabel(); 372 ELDescriptionNode superNode = superEdge.getNode(); 373 374 for(ELDescriptionEdge edge : edges) { 375// System.out.println("superEdge: " + superEdge); 376// System.out.println("edge: " + edge); 377 378 OWLProperty op = edge.getLabel(); 379 // we first check the condition on the properties 380 if(rs.isSubPropertyOf(op, superOP)) { 381 // check condition on simulations of referred nodes 382 ELDescriptionNode node = edge.getNode(); 383// if(superNode.in.contains(node) || node.in.contains(superNode)) { 384 if(node.in.contains(superNode)) { 385 // we found a node satisfying the condition, so we can return 386 return true; 387 } 388 } 389 } 390 391 // none of the edges in the set satisfies the 2nd simulation criterion 392 // wrt. the first edge 393 return false; 394 } 395 396 // adds (node1,node2) to simulation, takes care of all helper sets 397 public void extendSimulation(ELDescriptionNode node1, ELDescriptionNode node2) { 398 node1.in.add(node2); 399 node1.inSC1.add(node2); 400 node1.inSC2.add(node2); 401 node2.out.add(node1); 402 node2.outSC1.add(node1); 403 node2.outSC2.add(node1); 404 } 405 406 public void extendSimulationSC1(ELDescriptionNode node1, ELDescriptionNode node2) { 407 node1.inSC1.add(node2); 408 node2.outSC1.add(node1); 409 } 410 411 public void extendSimulationSC2(ELDescriptionNode node1, ELDescriptionNode node2) { 412 node1.inSC2.add(node2); 413 node2.outSC2.add(node1); 414 } 415 416 public void extendSimulationSC12(ELDescriptionNode node1, ELDescriptionNode node2) { 417 node1.in.add(node2); 418 node2.out.add(node1); 419 } 420 421 // removes (node1,node2) from simulation, takes care of all helper sets 422 public void shrinkSimulation(ELDescriptionNode node1, ELDescriptionNode node2) { 423 node1.in.remove(node2); 424 node1.inSC1.remove(node2); 425 node1.inSC2.remove(node2); 426 node2.out.remove(node1); 427 node2.outSC1.remove(node1); 428 node2.outSC2.remove(node1); 429 } 430 431 public void shrinkSimulationSC1(ELDescriptionNode node1, ELDescriptionNode node2) { 432 node1.inSC1.remove(node2); 433 node2.outSC1.remove(node1); 434 } 435 436 public void shrinkSimulationSC2(ELDescriptionNode node1, ELDescriptionNode node2) { 437// System.out.println(node2.outSC2); 438 node1.inSC2.remove(node2); 439 node2.outSC2.remove(node1); 440// System.out.println(node2.outSC2); 441 } 442 443 public void shrinkSimulationSC12(ELDescriptionNode node1, ELDescriptionNode node2) { 444 node1.in.remove(node2); 445 node2.out.remove(node1); 446 } 447 448 public String toSimulationString() { 449 String str = ""; 450 for(ELDescriptionNode node : nodes) { 451 str += node.toSimulationString() + "\n"; 452 } 453 return str; 454 } 455 456 public String toSimulationString(Map<ELDescriptionNode,String> nodeNames) { 457 String str = ""; 458 for(Entry<ELDescriptionNode,String> entry : nodeNames.entrySet()) { 459 String nodeName = entry.getValue(); 460 ELDescriptionNode node = entry.getKey(); 461 str += nodeName + ":\n"; 462 str += node.toSimulationString(nodeNames) + "\n"; 463 } 464 return str; 465 } 466 467 @Override 468 @SuppressWarnings("unchecked") 469 public ELDescriptionTree clone() { 470// Monitor mon = MonitorFactory.start("tree clone"); 471 // clone "global" tree 472 ELDescriptionTree treeClone = new ELDescriptionTree(rs); 473 474 // a mapping between "old" and "new" nodes 475 // (hash map should be fast here, but one could also 476 // experiment with TreeMap) 477 Map<ELDescriptionNode, ELDescriptionNode> cloneMap = 478 new HashMap<>(); 479 480 // create a new (empty) node for each node in the tree 481 // (we loop through the level mapping, because it is cheaper 482 // than creating a set of all nodes) 483 for(int i=1; i<=maxLevel; i++) { 484 Set<ELDescriptionNode> tmp = levelNodeMapping.get(i); 485 for(ELDescriptionNode node : tmp) { 486 ELDescriptionNode nodeNew = new ELDescriptionNode(); 487 cloneMap.put(node, nodeNew); 488 } 489 } 490 491 ELDescriptionNode newRoot = null; 492 493 // loop through all nodes and perform copy operations 494 for(Entry<ELDescriptionNode, ELDescriptionNode> entry : cloneMap.entrySet()) { 495 ELDescriptionNode oldNode = entry.getKey(); 496 ELDescriptionNode newNode = entry.getValue(); 497 498 newNode.tree = treeClone; 499 newNode.level = oldNode.level; 500 newNode.label = (TreeSet<OWLClass>) oldNode.label.clone(); 501 newNode.dataRange = oldNode.dataRange; 502 newNode.isClassNode = oldNode.isClassNode; 503 if(oldNode.parent != null) { 504 newNode.parent = cloneMap.get(oldNode.parent); 505 } else { 506 newRoot = newNode; 507 } 508 509 // simulation information 510 for(ELDescriptionNode node : oldNode.in) { 511 newNode.in.add(cloneMap.get(node)); 512 } 513 for(ELDescriptionNode node : oldNode.inSC1) { 514 newNode.inSC1.add(cloneMap.get(node)); 515 } 516 for(ELDescriptionNode node : oldNode.inSC2) { 517 newNode.inSC2.add(cloneMap.get(node)); 518 } 519 for(ELDescriptionNode node : oldNode.out) { 520 newNode.out.add(cloneMap.get(node)); 521 } 522 for(ELDescriptionNode node : oldNode.outSC1) { 523 newNode.outSC1.add(cloneMap.get(node)); 524 } 525 for(ELDescriptionNode node : oldNode.outSC2) { 526 newNode.outSC2.add(cloneMap.get(node)); 527 } 528 529 // edges 530 for(ELDescriptionEdge edge : oldNode.edges) { 531 // create a new edge with same label and replace the node the edge points to 532 newNode.edges.add(new ELDescriptionEdge(edge.getLabel(), cloneMap.get(edge.getNode()))); 533 } 534 535 } 536 537 // update global tree 538 treeClone.rootNode = newRoot; 539 treeClone.maxLevel = maxLevel; 540 treeClone.size = size; 541 542 // nodes 543 treeClone.nodes = new LinkedList<>(); 544 for(ELDescriptionNode oldNode : nodes) { 545 treeClone.nodes.add(cloneMap.get(oldNode)); 546 } 547 548 // level node mapping 549 for(int i=1; i<=maxLevel; i++) { 550 Set<ELDescriptionNode> oldNodes = levelNodeMapping.get(i); 551 Set<ELDescriptionNode> newNodes = new HashSet<>(); 552 for(ELDescriptionNode oldNode : oldNodes) { 553 newNodes.add(cloneMap.get(oldNode)); 554 } 555 treeClone.levelNodeMapping.put(i, newNodes); 556 } 557 558// mon.stop(); 559 return treeClone; 560 } 561 562 @Override 563 public String toString() { 564 return rootNode.toString(); 565 } 566 567 /** 568 * Returns a string of the tree OWLClassExpression (without the overhead of converting 569 * the tree into a description). 570 * @return A string for the OWLClassExpression the tree stands for. 571 */ 572 public String toDescriptionString() { 573 return rootNode.toDescriptionString(); 574 } 575 576 /** 577 * @return the nodes 578 */ 579 public Collection<ELDescriptionNode> getNodes() { 580 return nodes; 581 } 582 583 public int getDepth() { 584 return maxLevel; 585 } 586 587 /** 588 * size of tree = number of nodes + sum of cardinality of node labels 589 * @return The tree size. 590 */ 591 public int getSize() { 592// int size = nodes.size(); 593// for(ELDescriptionNode node : nodes) { 594// size += node.getLabel().size(); 595// } 596 return size; 597 } 598}