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.refinementoperators;
020
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024import java.util.SortedSet;
025import java.util.TreeMap;
026import java.util.TreeSet;
027
028import org.dllearner.core.AbstractReasonerComponent;
029import org.dllearner.core.owl.ClassHierarchy;
030import org.semanticweb.owlapi.model.OWLClass;
031import org.semanticweb.owlapi.model.OWLClassExpression;
032import org.semanticweb.owlapi.model.OWLDataFactory;
033import org.semanticweb.owlapi.model.OWLDataProperty;
034import org.semanticweb.owlapi.model.OWLIndividual;
035import org.semanticweb.owlapi.model.OWLObjectProperty;
036
037import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
038
039import com.google.common.collect.Sets;
040import com.jamonapi.Monitor;
041import com.jamonapi.MonitorFactory;
042
043/**
044 * Utility methods for constructing refinement operators.
045 * 
046 * @author Jens Lehmann
047 *
048 */
049public final class Utility {
050                
051        private AbstractReasonerComponent reasoner;
052        ClassHierarchy sh;
053        
054        // specifies whether to do real disjoint tests or check that
055        // two named classes do not have common instances
056        private boolean instanceBasedDisjoints = true;
057        
058        // cache for reasoner queries
059        private Map<OWLClassExpression,Map<OWLClassExpression,Boolean>> cachedDisjoints = new TreeMap<>();
060                
061        // cache for applicaple object properties
062        private Map<OWLClassExpression, SortedSet<OWLObjectProperty>> appOPCache = new TreeMap<>();
063        private Map<OWLClassExpression, SortedSet<OWLDataProperty>> appDPCache = new TreeMap<>();
064        private Map<OWLObjectProperty,OWLClassExpression> opDomains;
065        private Map<OWLDataProperty, OWLClassExpression> dpDomains;
066        
067        private OWLDataFactory df = new OWLDataFactoryImpl();
068        
069        public Utility(AbstractReasonerComponent rs) {
070                throw new Error("not implemented yet");
071        }
072        
073        public Utility(AbstractReasonerComponent rs, Map<OWLObjectProperty,OWLClassExpression> opDomains, boolean instanceBasedDisjoints) {
074                this.reasoner = rs;
075                sh = rs.getClassHierarchy();
076                // we cache object property domains
077                this.opDomains = opDomains;
078                this.instanceBasedDisjoints = instanceBasedDisjoints;
079        }
080        
081        public Utility(AbstractReasonerComponent rs, Map<OWLObjectProperty,OWLClassExpression> opDomains, Map<OWLDataProperty,OWLClassExpression> dpDomains, boolean instanceBasedDisjoints) {
082                this.reasoner = rs;
083                this.dpDomains = dpDomains;
084                sh = rs.getClassHierarchy();
085                // we cache object property domains
086                this.opDomains = opDomains;
087                this.instanceBasedDisjoints = instanceBasedDisjoints;
088        }
089        
090        /**
091         * Compute the set of applicable object properties for a
092         * given description.
093         * 
094         * @param index The index is a class expression which determines
095         * which of the properties are applicable. Exactly those which
096         * where the index and property domain are not disjoint are
097         * applicable, where disjoint is defined by {@link #isDisjoint(OWLClassExpression, OWLClassExpression)}.
098         * 
099         */
100        public SortedSet<OWLObjectProperty> computeApplicableObjectProperties(OWLClassExpression index) {
101                // use a cache, because large ontologies can have many object properties
102                SortedSet<OWLObjectProperty> applicableObjectProperties = appOPCache.get(index);
103                if(applicableObjectProperties == null) {
104                        Set<OWLObjectProperty> objectProperties = reasoner.getObjectProperties();
105                        applicableObjectProperties = new TreeSet<>();
106                        for(OWLObjectProperty op : objectProperties) {
107                                OWLClassExpression domain = opDomains.get(op);
108                                if(!isDisjoint(index,domain)) {
109                                        applicableObjectProperties.add(op);
110                                }
111                        }
112                        appOPCache.put(index, applicableObjectProperties);
113                }
114                return applicableObjectProperties;
115        }
116        
117        /**
118         * Compute the set of applicable data properties for a
119         * given description.
120         * 
121         * @param index The index is a OWLClassExpression which determines
122         * which of the properties are applicable. Exactly those which
123         * where the index and property domain are not disjoint are
124         * applicable, where disjoint is defined by {@link #isDisjoint(OWLClassExpression, OWLClassExpression)}.
125         * 
126         */
127        public SortedSet<OWLDataProperty> computeApplicableDatatypeProperties(OWLClassExpression index) {
128                // use a cache, because large ontologies can have many data properties
129                SortedSet<OWLDataProperty> applicableDatatypeProperties = appDPCache.get(index);
130                if(applicableDatatypeProperties == null) {
131                        Set<OWLDataProperty> datatypeProperties = reasoner.getDatatypeProperties();
132                        applicableDatatypeProperties = new TreeSet<>();
133                        for(OWLDataProperty op : datatypeProperties) {
134                                OWLClassExpression domain = dpDomains.get(op);
135                                if(!isDisjoint(index,domain)) {
136                                        applicableDatatypeProperties.add(op);
137                                }
138                        }
139                        appDPCache.put(index, applicableDatatypeProperties);
140                }
141                return applicableDatatypeProperties;
142        }
143        
144        /**
145         * Given a set of applicable object properties, this method returns
146         * the most general ones, i.e. those where more general ones do not
147         * exist in the set of applicable properties. Due to the definition
148         * of "applicable", the returned set is just the intersection of the most
149         * general object properties and the applicable properties. (A non-applicable
150         * property cannot have applicable subproperties, because subproperties
151         * can only restrict, but not broaden their domain.)
152         * 
153         * @param applicableObjectProperties The set of applicable properties.
154         * @return The most general applicable properties.
155         */
156        public Set<OWLObjectProperty> computeMgr(Set<OWLObjectProperty> applicableObjectProperties) {
157                return new HashSet<>(Sets.intersection(reasoner.getMostGeneralProperties(), applicableObjectProperties));
158        }
159        
160        /**
161         * Given a set of applicable data properties, this method returns
162         * the most general ones, i.e. those where more general ones do not
163         * exist in the set of applicable properties. Due to the definition
164         * of "applicable", the returned set is just the intersection of the most
165         * general object properties and the applicable properties. (A non-applicable
166         * property cannot have applicable subproperties, because subproperties
167         * can only restrict, but not broaden their domain.)
168         * 
169         * @param applicableDatatypeProperties The set of applicable properties.
170         * @return The most general applicable properties.
171         */
172        public Set<OWLDataProperty> computeMgrDP(Set<OWLDataProperty> applicableDatatypeProperties) {
173                return new HashSet<>(Sets.intersection(reasoner.getMostGeneralDatatypeProperties(), applicableDatatypeProperties));
174        }
175        
176        public Set<OWLClass> getClassCandidates(OWLClassExpression index, Set<OWLClass> existingClasses) {
177                return getClassCandidatesRecursive(index, existingClasses, df.getOWLThing());
178        }
179        
180        private Set<OWLClass> getClassCandidatesRecursive(OWLClassExpression index, Set<OWLClass> existingClasses, OWLClassExpression upperClass) {
181                Set<OWLClass> candidates = new TreeSet<>();
182                
183                // we descend the subsumption hierarchy to ensure that we get
184                // the most general concepts satisfying the criteria
185                // there are 4 checks a class has to satisfy to get into the set;
186                // for 2 of them we can stop further traversal in the subsumption
187                // hierarchy
188                for(OWLClassExpression d : sh.getSubClasses(upperClass, true)) {
189//                      System.out.println("d: " + d);
190                        // owl:Nothing is never a candidate (not in EL)
191                        if(!d.isOWLNothing()) {
192                                OWLClass candidate = d.asOWLClass();
193                                // we first do those checks where we know that we do not
194                                // need to traverse the subsumption hierarchy if they are
195                                // not satisfied
196                                // check1: disjointness with index
197                                // check3: no superclass exists already
198                                // check5: disjointness
199                                if(!isDisjoint(candidate,index) && checkSubClasses(existingClasses,candidate) && checkDisjoints(existingClasses,candidate)) {
200                                        // check whether the class is meaningful, i.e. adds something to the index
201                                        // to do this, we need to make sure that the class is not a superclass of the
202                                        // index (otherwise we get nothing new)
203                                        if(!isDisjoint(df.getOWLObjectComplementOf(candidate),index) && checkSuperClasses(existingClasses,candidate)) {
204                                                // candidate went successfully through all checks
205                                                candidates.add(candidate);
206                                        } else {
207//                                              System.out.println("k32: " + candidate + " index " + index + " cond1 " + isDisjoint(new Negation(candidate),index) + " cond2 " + checkSuperClasses(existingClasses,candidate));
208                                                // descend subsumption hierarchy to find candidates
209                                                candidates.addAll(getClassCandidatesRecursive(index, existingClasses, candidate));
210                                        }
211                                }
212                        }
213                }
214                return candidates;
215        }
216        
217        // returns true if the candidate is not subclass of an existing class,
218        // false otherwise (check 3)
219        private boolean checkSubClasses(Set<OWLClass> existingClasses, OWLClass candidate) {
220                for(OWLClass nc : existingClasses) {
221//                      System.out.println("csc: " + nc + candidate);
222                        if(sh.isSubclassOf(candidate, nc)) {
223                                return false;
224                        }
225                }
226                return true;
227        }
228        
229        // returns true if the candidate is not superclass of an existing class,
230        // false otherwise (check 4)
231        private boolean checkSuperClasses(Set<OWLClass> existingClasses, OWLClass candidate) {
232                for(OWLClass nc : existingClasses) {
233                        if(sh.isSubclassOf(nc, candidate))
234                                return false;
235                }
236                return true;
237        }
238        
239        // returns false if any of the classes is disjoint with the new one; true otherwise
240        private boolean checkDisjoints(Set<OWLClass> existingClasses, OWLClass candidate) {
241                for(OWLClass nc : existingClasses) {
242                        if(isDisjoint(nc, candidate))
243                                return false;
244                }
245                return true;
246        }
247                
248        
249        public boolean isDisjoint(OWLClassExpression d1, OWLClassExpression d2) {
250//              System.out.println("d1: " + d1);
251//              System.out.println("d2: " + d2);
252//              System.out.println("cache: " + cachedDisjoints);
253                
254                // check whether we have cached this query
255                Map<OWLClassExpression,Boolean> tmp = cachedDisjoints.get(d1);
256                Boolean tmp2 = null;
257                if(tmp != null)
258                        tmp2 = tmp.get(d2);
259                
260                if(tmp2==null) {
261                        Boolean result;
262                        if(instanceBasedDisjoints) {
263                                result = isDisjointInstanceBased(d1,d2);
264                        } else {
265                                OWLClassExpression d = df.getOWLObjectIntersectionOf(d1, d2);
266                                Monitor mon = MonitorFactory.start("disjointness reasoning");
267                                result = reasoner.isSuperClassOf(df.getOWLNothing(), d);
268                                mon.stop();
269                        }
270                        // add the result to the cache (we add it twice such that
271                        // the order of access does not matter)
272                        
273                        // create new entries if necessary
274                        if(tmp == null)
275                                cachedDisjoints.put(d1, new TreeMap<>());
276                        if(!cachedDisjoints.containsKey(d2))
277                                cachedDisjoints.put(d2, new TreeMap<>());
278                        
279                        // add result symmetrically in the OWLClassExpression matrix
280                        cachedDisjoints.get(d1).put(d2, result);
281                        cachedDisjoints.get(d2).put(d1, result);
282                        return result;
283                } else {
284                        return tmp2;
285                }
286        }
287        
288        private boolean isDisjointInstanceBased(OWLClassExpression d1, OWLClassExpression d2) {
289                SortedSet<OWLIndividual> d1Instances = reasoner.getIndividuals(d1);
290                SortedSet<OWLIndividual> d2Instances = reasoner.getIndividuals(d2);
291                for(OWLIndividual d1Instance : d1Instances) {
292                        if(d2Instances.contains(d1Instance))
293                                return false;
294                }
295                return true;
296        }
297
298}