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.utilities.owl;
020
021import com.google.common.collect.Sets;
022import org.dllearner.core.AbstractReasonerComponent;
023import org.dllearner.core.owl.NNF;
024import org.semanticweb.owlapi.model.*;
025import org.semanticweb.owlapi.util.OWLObjectDuplicator;
026import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
027
028import java.util.*;
029
030/**
031 * Concept transformation and concept checking methods.
032 * 
033 * @author Jens Lehmann
034 */
035public class ConceptTransformation {
036
037        public static long cleaningTimeNs = 0;
038        public static long onnfTimeNs = 0;
039        private static long onnfTimeNsStart = 0;
040        public static long shorteningTimeNs = 0;
041
042        private static final OWLDataFactory df = new OWLDataFactoryImpl();
043        private static final OWLObjectDuplicator DUPLICATOR = new OWLObjectDuplicator(df);
044        private static final OWLClassExpressionCleaner CLASS_EXPRESSION_CLEANER = new OWLClassExpressionCleaner(df);
045        
046        public static OWLClassExpression cleanConceptNonRecursive(OWLClassExpression concept) {
047                return cleanConcept(concept);
048        }
049        
050        public static OWLClassExpression cleanConcept(OWLClassExpression concept) {
051                long cleaningTimeNsStart = System.nanoTime();
052                OWLClassExpression cleanedConcept = concept.accept(CLASS_EXPRESSION_CLEANER);
053                cleaningTimeNs += System.nanoTime() - cleaningTimeNsStart;
054                return cleanedConcept;
055        }
056        
057        /**
058         * Returns the class expression in negation normal form.
059         * @param ce the class expression
060         * @return the class expression in negation normal form
061         */
062        public static OWLClassExpression nnf(OWLClassExpression ce) {
063                NNF nnfGen = new NNF(df);
064                return ce.accept(nnfGen);
065        }
066        
067        /**
068         * Expand the class expression by adding \exists r.\top for all properties r
069         * that are involved in some \forall r.C on the same modal depth. 
070         * @param ce the class expression to expand
071         * @return
072         */
073        public static OWLClassExpression appendSomeValuesFrom(OWLClassExpression ce) {
074                // if forall semantics is someonly
075                if (ce instanceof OWLObjectIntersectionOf) {
076                        Set<OWLClassExpression> newOperands = new HashSet<>();
077                        Set<OWLObjectPropertyExpression> universallyQuantifiedProperties = new HashSet<>();
078                        Set<OWLObjectPropertyExpression> existentiallyQuantifiedProperties = new HashSet<>();
079                        for (OWLClassExpression operand : ((OWLObjectIntersectionOf) ce).getOperands()) {
080                                newOperands.add(appendSomeValuesFrom(operand));
081                                if(operand instanceof OWLObjectAllValuesFrom) {
082                                        universallyQuantifiedProperties.add(((OWLObjectAllValuesFrom) operand).getProperty());
083                                } else if(operand instanceof OWLObjectSomeValuesFrom) {
084                                        existentiallyQuantifiedProperties.add(((OWLObjectSomeValuesFrom) operand).getProperty());
085                                }
086                        }
087                        for (OWLObjectPropertyExpression ope : Sets.difference(universallyQuantifiedProperties, existentiallyQuantifiedProperties)) {
088                                newOperands.add(df.getOWLObjectSomeValuesFrom(ope, df.getOWLThing()));
089                        }
090                        return df.getOWLObjectIntersectionOf(newOperands);
091                } 
092//              else if(ce instanceof OWLObjectUnionOf) {
093//                      Set<OWLClassExpression> newOperands = new HashSet<OWLClassExpression>();
094//                      
095//                      for (OWLClassExpression operand : ((OWLObjectUnionOf) ce).getOperands()) {
096//                              OWLClassExpression newOperand = appendSomeValuesFrom(operand);
097//                              if(newOperand instanceof OWLObjectAllValuesFrom) {
098//                                      newOperand = df.getOWLObjectIntersectionOf(ce,
099//                                                      df.getOWLObjectSomeValuesFrom(((OWLObjectAllValuesFrom) newOperand).getProperty(), df.getOWLThing()));
100//                              } 
101//                              newOperands.add(newOperand);
102//                      }
103//                      
104//                      return df.getOWLObjectUnionOf(newOperands);
105//              }
106                return ce.getNNF();
107        }
108        
109        // nimmt Konzept in Negationsnormalform und wendet äquivalenzerhaltende
110        // Regeln an, die TOP und BOTTOM aus Disjunktion/Konjunktion entfernen
111        public static OWLClassExpression applyEquivalenceRules(OWLClassExpression concept) {
112                
113                OWLClassExpression conceptClone = DUPLICATOR.duplicateObject(concept);
114                
115                // remove \top and \bot from disjunction 
116                if(conceptClone instanceof OWLObjectUnionOf) {
117                        SortedSet<OWLClassExpression> newOperands = new TreeSet<>();
118                        for (OWLClassExpression op : ((OWLObjectUnionOf) conceptClone).getOperandsAsList()) {
119                                OWLClassExpression c = applyEquivalenceRules(op);
120                                
121                                if(c.isOWLThing()) {// \top in C => C \equiv \top
122                                        return df.getOWLThing();
123                                } 
124                                else if(c.isOWLNothing()) {// \bot in C => remove
125                                        
126                                } else {
127                                        newOperands.add(c);
128                                }
129                        }
130                        
131                        // if there are no children the last child was \bot
132                        if (newOperands.isEmpty()) {
133                                return df.getOWLNothing();
134                        }if (newOperands.size() == 1) {
135                                return newOperands.first();
136                        } else {
137                                return df.getOWLObjectUnionOf(newOperands);
138                        }
139                } else if(conceptClone instanceof OWLObjectIntersectionOf) {// remove \top and \bot from intersection 
140                        SortedSet<OWLClassExpression> newOperands = new TreeSet<>();
141                        for (OWLClassExpression op : ((OWLObjectIntersectionOf) conceptClone).getOperandsAsList()) {
142                                OWLClassExpression c = applyEquivalenceRules(op);
143                                
144                                if(c.isOWLThing()) {// \top in C => remove
145                                        
146                                } else if(c.isOWLNothing()) {// \bot in C => C \equiv \bot
147                                        return df.getOWLNothing();
148                                } else {
149                                        newOperands.add(c);
150                                }
151                        }
152                        
153                        // if there are no children the last child was \top
154                        if (newOperands.isEmpty()) {
155                                return df.getOWLThing();
156                        } else if (newOperands.size() == 1) {
157                                return newOperands.first();
158                        } else {
159                                return df.getOWLObjectIntersectionOf(newOperands);
160                        }
161                }               
162                
163                return conceptClone;
164        }
165        
166        /**
167         * Tries to shorten a concept, e.g. male AND male is shortened to male. 
168         * @param concept The input concepts.
169         * @return A shortened version of the concept (equal to the input concept if it cannot be shortened).
170         */
171        public static OWLClassExpression getShortConcept(OWLClassExpression concept) {
172                long shorteningTimeNsStart = System.nanoTime();
173                OWLClassExpression clone = DUPLICATOR.duplicateObject(concept);
174                shorteningTimeNs += System.nanoTime() - shorteningTimeNsStart;
175                return clone;
176        }
177        
178        /**
179         * Method to determine, whether a class expression is minimal,
180         * e.g. \forall r.\top (\equiv \top) or male \sqcup male are not
181         * minimal.     This method performs heuristic sanity checks (it will
182         * not try to find semantically equivalent shorter descriptions).
183         * @param description Input description.
184         * @return True if a superfluous construct has been found.
185         */
186        public static boolean isDescriptionMinimal(OWLClassExpression description) {
187                int length = OWLClassExpressionUtils.getLength(description);
188                int length2 = OWLClassExpressionUtils.getLength(ConceptTransformation.getShortConcept(description));
189                if(length2 < length)
190                        return false;
191        return !ConceptTransformation.findEquivalences(description);
192    }
193 
194        private static boolean findEquivalences(OWLClassExpression description) {
195                // \exists r.\bot \equiv \bot
196                if(description instanceof OWLObjectSomeValuesFrom && ((OWLObjectSomeValuesFrom)description).getFiller().isOWLNothing())
197                        return true;
198                // \forall r.\top \equiv \top
199                if(description instanceof OWLObjectAllValuesFrom && ((OWLObjectAllValuesFrom)description).getFiller().isOWLThing())
200                        return true;
201                // check children
202                for(OWLClassExpression child : OWLClassExpressionUtils.getChildren(description)) {
203                        if(findEquivalences(child))
204                                return true;
205                }
206                // false if none of the checks was successful
207                return false;
208        }
209        
210        // replaces EXISTS hasChild.TOP with EXISTS hasChild.Person, 
211        // i.e. TOP is replaced by the range of the property; 
212        // this is semantically equivalent, but easier to read for some people
213        public static OWLClassExpression replaceRange(OWLClassExpression description, AbstractReasonerComponent rs) {
214                OWLClassExpression rewrittenClassExpression = description;
215                if(description instanceof OWLNaryBooleanClassExpression){
216                        Set<OWLClassExpression> newOperands = new TreeSet<>();
217                        for (OWLClassExpression operand : ((OWLNaryBooleanClassExpression) description).getOperands()) {
218                                newOperands.add(replaceRange(operand, rs));
219                        }
220                        if(description instanceof OWLObjectIntersectionOf){
221                                rewrittenClassExpression = df.getOWLObjectIntersectionOf(newOperands);
222                        } else {
223                                rewrittenClassExpression = df.getOWLObjectUnionOf(newOperands);
224                        }
225                } else if(description instanceof OWLObjectSomeValuesFrom) {
226                        // \exists r.\bot \equiv \bot
227                        
228                        OWLObjectPropertyExpression pe = ((OWLObjectSomeValuesFrom) description).getProperty();
229                        OWLClassExpression filler = ((OWLObjectSomeValuesFrom) description).getFiller();
230                        
231                        if(pe.isAnonymous()) {
232                                if(filler.isOWLThing()) {
233                                        filler = rs.getDomain(pe.getNamedProperty());
234                                } else if(filler.isAnonymous()){
235                                        filler = replaceRange(filler, rs);
236                                }
237                        } else {
238                                if(filler.isOWLThing()) {
239                                        filler = rs.getRange(pe.asOWLObjectProperty());
240                                } else if(filler.isAnonymous()){
241                                        filler = replaceRange(filler, rs);
242                                }
243                        }
244                        
245                        rewrittenClassExpression = df.getOWLObjectSomeValuesFrom(pe, filler);
246                }
247                return rewrittenClassExpression;
248        }
249        
250        /**
251         * Tests whether a description is a subdescription in the sense that when
252         * parts of <code>description</code> can be removed to yield <code>subdescription</code>.
253         * 
254         * @param description A description.
255         * @param subDescription A potential subdescription.
256         * @return True if <code>subdescription</code> is indeed a sub description and false
257         * otherwise.
258         */
259        public static boolean isSubdescription(OWLClassExpression description, OWLClassExpression subDescription) {
260//              if(OWLClassExpression instanceof Thing) {
261//                      return (subDescription instanceof Thing);
262//              } else if(OWLClassExpression instanceof Nothing) {
263//                      return (subDescription instanceof Thing);
264//              } else if(OWLClassExpression instanceof NamedClass) {
265//                      return ((subDescription instanceof NamedClass) && (((NamedClass)description).toStringID().equals(((NamedClass)subDescription).toStringID())));
266//              }
267                
268                List<OWLClassExpression> children = new ArrayList<>(OWLClassExpressionUtils.getChildren(description));
269                List<OWLClassExpression> subChildren = new ArrayList<>(OWLClassExpressionUtils.getChildren(subDescription));
270
271                // no children: both have to be equal
272                if(children.size()==0) {
273                        return (description.compareTo(subDescription)==0);
274                // one child: both have to be of the same class, type, and the first
275                // child has to be sub OWLClassExpression of the other child
276                } else if(children.size()==1) {
277                        return (subChildren.size() == 1) && description.getClass().equals(subDescription.getClass()) && isSubdescription(children.get(0), subChildren.get(0));
278                // intersection or union
279                } else {
280                        // test whether subdescription corresponds to an element of the 
281                        // intersection/union
282                        if(subChildren.size()<2) {
283                                for(OWLClassExpression child : children) {
284                                        if(isSubdescription(child, subDescription)) {
285                                                return true;
286                                        }
287                                }
288                                return false;
289                        }
290                        
291                        // make sure that both are of the same type and subdescription actually has fewer children
292                        if(!description.getClass().equals(subDescription.getClass()) || subChildren.size() > children.size()) {
293                                return false;
294                        }
295                        
296                        // comparing everything is quadratic; the faster linear variant (below)
297                        // using 
298                        
299                        for(OWLClassExpression subChild : subChildren) {
300                                boolean foundMatch = false;
301                                for(OWLClassExpression child : children) {
302                                        if(isSubdescription(child, subChild)) {
303                                                foundMatch = true;
304                                                break;
305                                        }
306                                }
307                                if(!foundMatch) {
308                                        return false;
309                                }
310                        }
311                        
312                        return true;
313                        
314//                      // method core; traverse the descriptions in linear time using ordered
315//                      // normal form (TODO: does not always work e.g. A2 \sqcap (A1 \sqcup A3)
316                        // and A1 \sqcap A2 -> it won't find the A2 match because it has advanced
317                        // beyond it already)
318//                      int j = 0;
319//                      for(OWLClassExpression child : children) {
320//                              if(isSubdescription(child, subChildren.get(j))) {
321//                                      j++;
322//                              }
323//                              if(j == subChildren.size()) {
324//                                      return true;
325//                              }
326//                      }
327//                      // there is at least one child we could not match
328//                      return false;
329                }
330        }
331        
332        /**
333         * Counts occurrences of \forall in description.
334         * @param description A description.
335         * @return Number of \forall occurrences.
336         */
337        public static int getForallOccurences(OWLClassExpression description) {
338                int count = 0;
339                for (OWLClassExpression expression : description.getNestedClassExpressions()) {
340                        if(expression instanceof OWLObjectAllValuesFrom) {
341                                count++;
342                        }
343                }
344                return count;
345        }
346        
347        /**
348         * Gets the "contexts" of all \forall occurrences in a description. A context
349         * is a set of properties, i.e. in \exists hasChild.\exists hasBrother.\forall hasChild.male,
350         * the context of the only \forall occurrence is [hasChild, hasBrother, hasChild]. 
351         * @param description A description.
352         * @return Set of property contexts.
353         */
354        public static SortedSet<PropertyContext> getForallContexts(OWLClassExpression description) {
355                return getForallContexts(description, new PropertyContext());
356        }
357        
358        @SuppressWarnings("rawtypes")
359        private static SortedSet<PropertyContext> getForallContexts(OWLClassExpression description, PropertyContext currentContext) {
360                // the context changes if we have a restriction
361                if(description instanceof OWLRestriction) {
362                        if(((OWLRestriction) description).isObjectRestriction()){
363                                OWLObjectPropertyExpression pe = (OWLObjectPropertyExpression)((OWLRestriction) description).getProperty();
364                                OWLObjectProperty op = pe.getNamedProperty();
365                                PropertyContext currentContextCopy = (PropertyContext) currentContext.clone();
366                                // if we have an all-restriction, we return it; otherwise we call the child
367                                // (if it exists)
368                                if(description instanceof OWLObjectAllValuesFrom) {
369                                        OWLClassExpression filler = ((OWLObjectAllValuesFrom) description).getFiller();
370                                        currentContextCopy.add(op);
371//                                      System.out.println("cc: " + currentContext);
372                                        TreeSet<PropertyContext> contexts = new TreeSet<>();
373                                        contexts.add(currentContextCopy);
374                                        contexts.addAll(getForallContexts(filler, currentContextCopy));
375                                        return contexts;
376                                // restriction with one child
377                                } else if(description instanceof OWLQuantifiedRestriction) {
378                                        OWLClassExpression filler = (OWLClassExpression) ((OWLQuantifiedRestriction) description).getFiller();
379                                        currentContextCopy.add(op);
380                                        return getForallContexts(filler, currentContextCopy);
381                                // restrictions without a child (has value)
382                                } else {
383                                        return new TreeSet<>();
384                                }
385                        } else {
386                                return new TreeSet<>();
387                        }
388                // for non-restrictions, we collect contexts over all children
389                } else {
390                        TreeSet<PropertyContext> contexts = new TreeSet<>();
391                        if(description instanceof OWLNaryBooleanClassExpression){
392                                for(OWLClassExpression child : ((OWLNaryBooleanClassExpression) description).getOperands()) {
393//                                      System.out.println("testing child " + child + " " + currentContext);
394                                        contexts.addAll(getForallContexts(child, currentContext));
395                                }
396                        }
397                        return contexts;
398                }
399        }
400}