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 org.semanticweb.owlapi.model.*; 022import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl; 023 024import java.util.*; 025 026/** 027 * Represents an EL description tree, which corresponds to a 028 * description in the EL description logic. Note that an EL description tree 029 * can be a subtree of another EL description tree. In general, 030 * an EL description tree is a tree where the node label is a set 031 * of named classes and the edges are labelled with a property. 032 * 033 * In the documentation below "this node" refers to the root node 034 * of this EL description (sub-)tree. One tree cannot be reused, 035 * i.e. used as subtree in several description trees, as some of 036 * the associated variables (level, simulation) depend on the overall 037 * tree. 038 * 039 * @author Jens Lehmann 040 * 041 */ 042@SuppressWarnings("unused") 043public class ELDescriptionNode { 044 045 // the reference tree for storing values, must not be null 046 protected ELDescriptionTree tree; 047 048 protected TreeSet<OWLClass> label = new TreeSet<>(); 049 050 protected List<ELDescriptionEdge> edges = new LinkedList<>(); 051 052 protected int level; 053 054 // parent node in the tree; 055 // null indicates that this node is a root node 056 protected ELDescriptionNode parent = null; 057 058 // simulation information (list or set?) 059 protected Set<ELDescriptionNode> in = new HashSet<>(); 060 protected Set<ELDescriptionNode> inSC1 = new HashSet<>(); 061 protected Set<ELDescriptionNode> inSC2 = new HashSet<>(); 062 protected Set<ELDescriptionNode> out = new HashSet<>(); 063 protected Set<ELDescriptionNode> outSC1 = new HashSet<>(); 064 protected Set<ELDescriptionNode> outSC2 = new HashSet<>(); 065 066 protected boolean isClassNode; 067 protected OWLDataRange dataRange; 068 069 public static final OWLDataFactory df = new OWLDataFactoryImpl(); 070 071 /** 072 * Internal constructor used for cloning nodes. 073 */ 074 protected ELDescriptionNode() { 075 076 } 077 078 /** 079 * Constructs an EL OWLClassExpression tree with empty root label. 080 */ 081 public ELDescriptionNode(ELDescriptionTree tree) { 082 this(tree, new TreeSet<>()); 083 } 084 085 // convenience constructor 086 public ELDescriptionNode(ELDescriptionTree tree, OWLClass... label) { 087 this(tree, new TreeSet<>(Arrays.asList(label))); 088 } 089 090 /** 091 * Constructs an EL OWLClassExpression tree given its root label. 092 * @param label Label of the root node. 093 */ 094 public ELDescriptionNode(ELDescriptionTree tree, TreeSet<OWLClass> label) { 095 this.label = label; 096 this.edges = new LinkedList<>(); 097 this.tree = tree; 098 level = 1; 099 parent = null; 100 // this is the root node of the overall tree 101 tree.rootNode = this; 102 tree.addNodeToLevel(this, level); 103 tree.size += label.size(); 104 105 isClassNode = true; 106 } 107 108 /** 109 * Constructs an EL description tree node given a description tree and the data range. 110 * @param tree the description tree 111 * @param dataRange the data range 112 */ 113 public ELDescriptionNode(ELDescriptionTree tree, OWLDataRange dataRange) { 114 this.dataRange = dataRange; 115 this.edges = new LinkedList<>(); 116 this.tree = tree; 117 level = 1; 118 parent = null; 119 // this is the root node of the overall tree 120 tree.rootNode = this; 121 tree.addNodeToLevel(this, level); 122 tree.size += label.size(); 123 124 isClassNode = false; 125 } 126 127 // convenience constructor 128 public ELDescriptionNode(ELDescriptionNode parentNode, OWLObjectProperty parentProperty, OWLClass... label) { 129 this(parentNode, parentProperty, new TreeSet<>(Arrays.asList(label))); 130 } 131 132 public ELDescriptionNode(ELDescriptionNode parentNode, OWLObjectProperty parentProperty, Set<OWLClass> label) { 133// this.label = label; 134 // we first need to add the edge and update the simulation and then add 135 // all classes iteratively to the label (each time updating the simulation again) 136 this.edges = new LinkedList<>(); 137 parent = parentNode; 138 // the reference tree is the same as for the parent tree 139 tree = parentNode.tree; 140 // level increases by 1 141 level = parentNode.level + 1; 142 // we add an edge from the parent to this node 143 ELDescriptionEdge<OWLObjectProperty> edge = new ELDescriptionEdge(parentProperty, this); 144 parent.edges.add(edge); 145 // we need to update the set of nodes on a particular level 146 tree.addNodeToLevel(this, level); 147 148 // simulation update 149// Monitor mon = MonitorFactory.start("simulation update"); 150 // the nodes, which need to be updated 151 Set<ELDescriptionNode> update = new HashSet<>(); 152 153 // loop over all nodes on the same level, which are not in the in set 154 Set<ELDescriptionNode> nodes = tree.getNodesOnLevel(level); 155 for(ELDescriptionNode w : nodes) { 156 // to save space, we do not add reflexive relations 157 if(w != this) { 158 // (w,v') is automatically added 159 tree.extendSimulation(w, this); 160 161 // check conditions for (v',w) 162 boolean sc1 = false, sc2 = false; 163 164 if(w.label.size() == 0) { 165 tree.extendSimulationSC1(this, w); 166 sc1 = true; 167 } 168 169 if(w.edges.size() == 0) { 170 tree.extendSimulationSC2(this, w); 171 sc2 = true; 172 } 173 174 if(sc1 && sc2) { 175 tree.extendSimulationSC12(this, w); 176 } 177 178 update.add(w.parent); 179 } 180 } 181 update.add(this.parent); 182 183// if(inSC1.contains(w) && tree.checkSC2(this, w)) { 184// tree.extendSimulation(this, w); 185// update.add(w.parent); 186// } 187 188 // loop over all nodes in out set 189// for(ELDescriptionNode w : out) { 190// if(!tree.checkSC1(this, w)) { 191// tree.shrinkSimulation(this, w); 192// update.add(w.parent); 193// } 194// } 195 196// System.out.println(update); 197 198 // apply updates recursively top-down 199 tree.updateSimulation(update); 200// mon.stop(); 201 202 // add all classes in label 203 for(OWLClass nc : label) { 204 extendLabel(nc); 205 } 206 207 // 1 for the edge (labels are already taken care of by extendLabel) 208 tree.size += 1; 209 210 isClassNode = true; 211 } 212 213 public ELDescriptionNode(ELDescriptionNode parentNode, OWLDataProperty parentProperty, OWLDataRange dataRange) { 214 this.dataRange = dataRange; 215 // this.label = label; 216 // we first need to add the edge and update the simulation and then add 217 // all classes iteratively to the label (each time updating the simulation again) 218 this.edges = new LinkedList<>(); 219 parent = parentNode; 220 // the reference tree is the same as for the parent tree 221 tree = parentNode.tree; 222 // level increases by 1 223 level = parentNode.level + 1; 224 // we add an edge from the parent to this node 225 ELDescriptionEdge edge = new ELDescriptionEdge(parentProperty, this); 226 parent.edges.add(edge); 227 // we need to update the set of nodes on a particular level 228 tree.addNodeToLevel(this, level); 229 230 // simulation update 231// Monitor mon = MonitorFactory.start("simulation update"); 232 // the nodes, which need to be updated 233 Set<ELDescriptionNode> update = new HashSet<>(); 234 235 // loop over all nodes on the same level, which are not in the in set 236 Set<ELDescriptionNode> nodes = tree.getNodesOnLevel(level); 237 for(ELDescriptionNode w : nodes) { 238 // to save space, we do not add reflexive relations 239 if(w != this) { 240 // (w,v') is automatically added 241 tree.extendSimulation(w, this); 242 243 // check conditions for (v',w) 244 boolean sc1 = false, sc2 = false; 245 246 if(w.label.size() == 0) { 247 tree.extendSimulationSC1(this, w); 248 sc1 = true; 249 } 250 251 if(w.edges.size() == 0) { 252 tree.extendSimulationSC2(this, w); 253 sc2 = true; 254 } 255 256 if(sc1 && sc2) { 257 tree.extendSimulationSC12(this, w); 258 } 259 260 update.add(w.parent); 261 } 262 } 263 update.add(this.parent); 264 265// if(inSC1.contains(w) && tree.checkSC2(this, w)) { 266// tree.extendSimulation(this, w); 267// update.add(w.parent); 268// } 269 270 // loop over all nodes in out set 271// for(ELDescriptionNode w : out) { 272// if(!tree.checkSC1(this, w)) { 273// tree.shrinkSimulation(this, w); 274// update.add(w.parent); 275// } 276// } 277 278// System.out.println(update); 279 280 // apply updates recursively top-down 281 tree.updateSimulation(update); 282// mon.stop(); 283 284 285 // 1 for the edge (labels are already taken care of by extendLabel) 286 tree.size += 1; 287 288 isClassNode = false; 289 } 290 291 /** 292 * @return the isClassNode 293 */ 294 public boolean isClassNode() { 295 return isClassNode; 296 } 297 298 /** 299 * @return the dataRange 300 */ 301 public OWLDataRange getDataRange() { 302 return dataRange; 303 } 304 305 306 /** 307 * Constructs an EL OWLClassExpression tree given its root label and edges. 308 * @param label Label of the root node. 309 * @param edges Edges connected to the root node. 310 */ 311// TODO: probably delete as this constructor is not straightforward to 312// implement within the new structure 313// public ELDescriptionNode(SortedSet<OWLClass> label, List<ELDescriptionEdge> edges) { 314// this.label = label; 315// this.edges = edges; 316// } 317 318 /** 319 * Checks whether this node has a parent. If the parent link 320 * is null, the node is considered to be a root node. 321 * @return True of this is the root node and false otherwise. 322 */ 323 public boolean isRoot() { 324 return parent == null; 325 } 326 327 /** 328 * Traverses the EL OWLClassExpression tree upwards until it finds 329 * the root and returns it. 330 * @return The root node of this EL OWLClassExpression tree. 331 */ 332 public ELDescriptionNode getRoot() { 333 ELDescriptionNode root = this; 334 while(root.parent != null) { 335 root = parent; 336 } 337 return root; 338 } 339 340 /** 341 * Traverses the tree until the root node and counts how 342 * many edges are traversed. If this node does not have a parent, 343 * zero is returned. This method is used for checking the integrity 344 * of the tree in unit tests. Use {@link #getLevel()} to get the 345 * level of the tree. 346 * @return The level of this node (or more specifically the root 347 * node of this subtree) within the overall EL OWLClassExpression tree. 348 */ 349 public int computeLevel() { 350 ELDescriptionNode root = this; 351 int level = 0; 352 while(root.parent != null) { 353 root = parent; 354 level++; 355 } 356 return level; 357 } 358 359 /** 360 * This method transform the tree to an EL description. The 361 * node labels are transformed to an {@link org.semanticweb.owlapi.model.OWLObjectIntersectionOf} 362 * of {@link org.semanticweb.owlapi.model.OWLClass}. Each edge is transformed to an 363 * {@link org.semanticweb.owlapi.model.OWLObjectSomeValuesFrom}, where the property is the edge 364 * label and the child description the subtree the edge points 365 * to. Edges are also added to the intersection. If the intersection 366 * is empty, {@link OWLDataFactory#getOWLThing()} is returned. 367 * @return The description corresponding to this EL description tree. 368 */ 369 public OWLClassExpression transformToDescription() { 370 int nrOfElements = label.size() + edges.size(); 371 // leaf labeled with \emptyset stands for owl:Thing 372 if(nrOfElements == 0) { 373 return df.getOWLThing(); 374 // we want to avoid intersections with only 1 element, so in this 375 // case we return either the OWLClass or ObjectSomeRestriction directly 376 } else if(nrOfElements == 1) { 377 if(label.size()==1) { 378 return label.first(); 379 } else { 380 ELDescriptionEdge edge = edges.get(0); 381 if(edge.isObjectProperty()){ 382 OWLClassExpression child = edge.getNode().transformToDescription(); 383 return df.getOWLObjectSomeValuesFrom(edge.getLabel().asOWLObjectProperty(), child); 384 } else { 385 OWLDataRange range = edge.getNode().getDataRange(); 386 return df.getOWLDataSomeValuesFrom(edge.getLabel().asOWLDataProperty(), range); 387 } 388 389 } 390 // return an intersection of labels and edges 391 } else { 392 Set<OWLClassExpression> operands = new TreeSet<OWLClassExpression>(label); 393 394 for(ELDescriptionEdge edge : edges) { 395 if(edge.isObjectProperty()){ 396 OWLClassExpression child = edge.getNode().transformToDescription(); 397 OWLClassExpression osr = df.getOWLObjectSomeValuesFrom(edge.getLabel().asOWLObjectProperty(), child); 398 operands.add(osr); 399 } else { 400 OWLDataRange range = edge.getNode().getDataRange(); 401 OWLClassExpression dsr = df.getOWLDataSomeValuesFrom(edge.getLabel().asOWLDataProperty(), range); 402 operands.add(dsr); 403 } 404 } 405 return df.getOWLObjectIntersectionOf(operands); 406 } 407 } 408 409 /** 410 * Gets a list describing the position of this node within the 411 * tree. If the list is e.g. [2,5,1], then the node can be reached 412 * by picking the second child of the root node, then picking the 413 * 5th child of this node and finally selecting the first child of 414 * the previous node. 415 * @return The position number of this node within the tree as described above. 416 */ 417 public int[] getCurrentPosition() { 418 int[] position = new int[level-1]; 419 ELDescriptionNode root = this; 420 while(root.parent != null) { 421 position[root.level-2] = root.getChildNumber(); 422 root = root.parent; 423 } 424 return position; 425 } 426 427 // returns the child number of this node, i.e. whether it is 428 // the first, second, third etc. child; 429 // TODO: might be a bit faster to store this explicitly 430 private int getChildNumber() { 431 int count = 0; 432 for(ELDescriptionEdge edge : parent.edges) { 433 if(edge.getNode() == this) { 434 return count; 435 } 436 count++; 437 } 438 throw new RuntimeException("Inconsistent tree. Child tree not reachable from parent."); 439 } 440 441 /** 442 * Replaces an entry in the node label. 443 * @param oldClass Class to remove from label. 444 * @param newClass Class to add to label. 445 */ 446 public void replaceInLabel(OWLClass oldClass, OWLClass newClass) { 447 label.remove(oldClass); 448 label.add(newClass); 449 labelSimulationUpdate(); 450 } 451 452 /** 453 * Adds an entry to the node label. 454 * @param newClass Class to add to label. 455 */ 456 public void extendLabel(OWLClass newClass) { 457 label.add(newClass); 458 labelSimulationUpdate(); 459 tree.size += 1; 460// System.out.println(tree); 461// System.out.println(tree.size); 462 } 463 464 // simulation update when extending or refining label 465 // (same in both cases) 466 private void labelSimulationUpdate() { 467// Monitor mon = MonitorFactory.start("simulation update"); 468 // compute the nodes, which need to be updated 469 Set<ELDescriptionNode> update = new HashSet<>(); 470 471 Set<ELDescriptionNode> tmp = tree.getNodesOnLevel(level); 472 for(ELDescriptionNode w : tmp) { 473 if(w != this) { 474 // SC1(v,w) can only change from false to true 475 if(!inSC1.contains(w) && tree.checkSC1(this, w)) { 476 tree.extendSimulationSC1(this, w); 477 if(inSC2.contains(w)) { 478 tree.extendSimulationSC12(this, w); 479 } 480 update.add(w.getParent()); 481 } 482 // SC1(w,v) can only change from true to false 483 if(outSC1.contains(w) && !tree.checkSC1(w, this)) { 484 tree.shrinkSimulationSC1(w, this); 485 if(outSC2.contains(w)) { 486 tree.shrinkSimulationSC12(w, this); 487 } 488 update.add(w.getParent()); 489 } 490 } 491 } 492 if(parent != null) { 493 update.add(parent); 494 } 495 496 /* 497 // loop over all nodes on the same level, which are not in the in set 498 Set<ELDescriptionNode> tmp = new HashSet<ELDescriptionNode>(tree.getNodesOnLevel(level)); 499 tmp.removeAll(in); 500 for(ELDescriptionNode w : tmp) { 501 if(w != this) { 502 // we only need to recompute SC1 503 if(inSC1.contains(w) && tree.checkSC2(this, w)) { 504 System.out.println("satisfied"); 505 tree.extendSimulation(this, w); 506 update.add(w.parent); 507 } 508 } 509 } 510 511 // loop over all nodes in out set (we make a copy, because out 512 // is potentially modified, so we cannot safely iterate over it) 513 tmp = new HashSet<ELDescriptionNode>(out); 514 for(ELDescriptionNode w : tmp) { 515 if(w != this) { 516 if(!tree.checkSC1(w, this)) { 517// tree.shrinkSimulation(w, this); 518 tree.shrinkSimulationSC1(w, this); 519 tree.shrinkSimulationSC12(w, this); 520 update.add(w.parent); 521 } 522 } 523 } 524 */ 525 526 // apply updates recursively top-down 527 tree.updateSimulation(update); 528// mon.stop(); 529 } 530 531 public void refineEdge(int edgeNumber, OWLProperty op) { 532 edges.get(edgeNumber).setLabel(op); 533 534// Monitor mon = MonitorFactory.start("simulation update"); 535 // compute the nodes, which need to be updated 536 Set<ELDescriptionNode> update = new HashSet<>(); 537 update.add(this); 538 539 /* 540 // loop over all nodes on the same level, which are not in the in set 541 Set<ELDescriptionNode> tmp = new HashSet<ELDescriptionNode>(tree.getNodesOnLevel(level)); 542 tmp.removeAll(in); 543 for(ELDescriptionNode w : tmp) { 544 if(w != this) { 545 // we only need to recompute SC1 546 if(inSC2.contains(w) && tree.checkSC1(this, w)) { 547 tree.extendSimulation(this, w); 548 update.add(w.parent); 549 } 550 } 551 } 552 553 // loop over all nodes in out set 554 for(ELDescriptionNode w : out) { 555 if(w != this) { 556 if(!tree.checkSC2(this, w)) { 557 tree.shrinkSimulation(this, w); 558 update.add(w.parent); 559 } 560 } 561 } 562 */ 563 564// update.add(this.parent); 565 566 // apply updates recursively top-down 567 tree.updateSimulation(update); 568// mon.stop(); 569 } 570 571 /** 572 * Gets the label of this node. Do not modify the returned object, 573 * but use the provided methods instead! 574 * @return The label of root node of this subtree. 575 */ 576 public NavigableSet<OWLClass> getLabel() { 577 return label; 578 } 579 580 /** 581 * Gets the edges of this node. Do not modify the 582 * returned object, but use the provided methods instead! 583 * @return The outgoing edges of this subtree. 584 */ 585 public List<ELDescriptionEdge> getEdges() { 586 return edges; 587 } 588 589 /** 590 * Gets the level (distance from root) of this node. The root node 591 * has level 1. 592 * @return The level of the (root node of) this subtree in the overall tree. 593 */ 594 public int getLevel() { 595 return level; 596 } 597 598 @Override 599 public String toString() { 600 return toString(0); 601 } 602 603 private String toString(int indent) { 604 String indentString = ""; 605 for(int i=0; i<indent; i++) 606 indentString += " "; 607 608 String str = indentString + label.toString() + "\n"; 609 for(ELDescriptionEdge edge : edges) { 610 str += indentString + "-- " + edge.getLabel() + " -->\n"; 611 str += edge.getNode().toString(indent + 2); 612 } 613 return str; 614 } 615 616 public String toDescriptionString() { 617 String str = ""; 618 if(isClassNode()){ 619 if(label.isEmpty()) { 620 str = "TOP"; 621 } else { 622 Iterator<OWLClass> it = label.iterator(); 623 while(it.hasNext()) { 624 OWLClass nc = it.next(); 625 if(it.hasNext()) { 626 str += nc.toString() + " AND "; 627 } else { 628 str += nc.toString(); 629 } 630 } 631 } 632 } else { 633 str += dataRange; 634 } 635 for(ELDescriptionEdge edge : edges) { 636 str += " AND EXISTS " + edge.getLabel().toString() + ".("; 637 str += edge.getNode().toDescriptionString() + ")"; 638 } 639 return str; 640 } 641 642 private String toDescriptionString(Set<ELDescriptionNode> nodes) { 643 String str = ""; 644 // comma separated list of descriptions 645 for(ELDescriptionNode node : nodes) { 646 str += node.toDescriptionString() + ","; 647 } 648 // remove last comma 649 if(str.length() > 0) { 650 str = str.substring(0, str.length()-1); 651 } 652 return str; 653 } 654 655 public String toSimulationString() { 656 String str = ""; 657 str += "in: " + toDescriptionString(in) + "\n"; 658 str += "inSC1: " + toDescriptionString(inSC1) + "\n"; 659 str += "inSC2: " + toDescriptionString(inSC2) + "\n"; 660 str += "out: " + toDescriptionString(out) + "\n"; 661 str += "outSC1: " + toDescriptionString(outSC1) + "\n"; 662 str += "outSC2: " + toDescriptionString(outSC2) + "\n"; 663 return str; 664 } 665 666 /** 667 * A convenience method (for debugging purposes) to get a comma separated list of nodes, where the 668 * nodes are given names (to make them readable). 669 * @param nodes The node objects. 670 * @param nodeNames A mapping to node names. 671 * @return A comma separated list of the node names. 672 */ 673 public static String toString(Set<ELDescriptionNode> nodes, Map<ELDescriptionNode,String> nodeNames) { 674 String str = ""; 675 // comma separated list of descriptions 676 for(ELDescriptionNode node : nodes) { 677 str += nodeNames.get(node) + ","; 678 } 679 // remove last comma 680 if(str.length() > 0) { 681 str = str.substring(0, str.length()-1); 682 } 683 return str; 684 } 685 686 public String toSimulationString(Map<ELDescriptionNode,String> nodeNames) { 687 String str = ""; 688 str += " in: " + toString(in, nodeNames) + "\n"; 689 str += " inSC1: " + toString(inSC1, nodeNames) + "\n"; 690 str += " inSC2: " + toString(inSC2, nodeNames) + "\n"; 691 str += " out: " + toString(out, nodeNames) + "\n"; 692 str += " outSC1: " + toString(outSC1, nodeNames) + "\n"; 693 str += " outSC2: " + toString(outSC2, nodeNames) + "\n"; 694 return str; 695 } 696 697 public ELDescriptionNode getParent() { 698 return parent; 699 } 700 701 public ELDescriptionEdge getParentEdge() { 702 int childNr = getChildNumber(); 703 return parent.edges.get(childNr); 704 } 705 706 /** 707 * @return the in 708 */ 709 public Set<ELDescriptionNode> getIn() { 710 return in; 711 } 712 713 /** 714 * @return the inSC1 715 */ 716 public Set<ELDescriptionNode> getInSC1() { 717 return inSC1; 718 } 719 720 /** 721 * @return the inSC2 722 */ 723 public Set<ELDescriptionNode> getInSC2() { 724 return inSC2; 725 } 726 727 /** 728 * @return the out 729 */ 730 public Set<ELDescriptionNode> getOut() { 731 return out; 732 } 733 734 /** 735 * @return the outSC1 736 */ 737 public Set<ELDescriptionNode> getOutSC1() { 738 return outSC1; 739 } 740 741 /** 742 * @return the outSC2 743 */ 744 public Set<ELDescriptionNode> getOutSC2() { 745 return outSC2; 746 } 747 748 public ELDescriptionTree getTree() { 749 return tree; 750 } 751}