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}