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}