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.properties;
020
021import org.apache.jena.query.*;
022import org.dllearner.core.EvaluatedAxiom;
023import org.dllearner.core.config.ConfigOption;
024import org.dllearner.kb.SparqlEndpointKS;
025import org.dllearner.learningproblems.AxiomScore;
026import org.semanticweb.owlapi.model.*;
027
028import java.util.Set;
029import java.util.SortedSet;
030import java.util.TreeSet;
031
032/**
033 * A learning algorithm for object property hierarchy axioms.
034 * @author Lorenz Buehmann
035 *
036 */
037public abstract class ObjectPropertyHierarchyAxiomLearner<T extends OWLObjectPropertyAxiom> extends ObjectPropertyAxiomLearner<T> {
038        
039        protected static final ParameterizedSparqlString PROPERTY_OVERLAP_QUERY = new ParameterizedSparqlString(
040                        "SELECT ?p_other (COUNT(*) AS ?overlap) WHERE {"
041                        + "?s ?p ?o; ?p_other ?o . "
042                        + "?p_other a <http://www.w3.org/2002/07/owl#ObjectProperty> . FILTER(?p != ?p_other)}"
043                        + " GROUP BY ?p_other");
044
045        protected static final ParameterizedSparqlString PROPERTY_OVERLAP_WITH_RANGE_QUERY = new ParameterizedSparqlString(
046                        "SELECT ?p_other (COUNT(*) AS ?overlap) WHERE {"
047                        + "?s ?p ?o; ?p_other ?o . "
048                        + "?p_other a <http://www.w3.org/2002/07/owl#ObjectProperty> ; rdfs:range ?range . FILTER(?p != ?p_other)}"
049                        + " GROUP BY ?p_other");
050        
051        protected static final ParameterizedSparqlString GIVEN_PROPERTY_OVERLAP_QUERY = new ParameterizedSparqlString(
052                                        "SELECT (COUNT(*) AS ?overlap) WHERE {?s ?p ?o; ?p_other ?o . }");
053        
054        private static final ParameterizedSparqlString SAMPLE_QUERY = new ParameterizedSparqlString(
055                        "CONSTRUCT {?s ?p ?o . ?s ?p1 ?o . ?p1 a <http://www.w3.org/2002/07/owl#ObjectProperty> .} "
056                        + "WHERE {?s ?p ?o . OPTIONAL{?s ?p1 ?o . FILTER(?p != ?p1)} }");
057
058        protected static final ParameterizedSparqlString PROPERTY_OVERLAP_WITH_POPULARITY_BATCH_QUERY = new ParameterizedSparqlString(
059                        "SELECT ?p_other (COUNT(*) AS ?overlap) WHERE {"
060                                        + "?s ?p ?o; ?p_other ?o . "
061                                        + "?p_other a <http://www.w3.org/2002/07/owl#ObjectProperty> ; rdfs:range ?range . FILTER(?p != ?p_other)}"
062                                        + " GROUP BY ?p_other");
063        
064        
065        // set strict mode, i.e. if for the property explicit domain and range is given
066        // we only consider properties with same range and domain
067        @ConfigOption(defaultValue = "false")
068        protected boolean strictMode = false;
069
070        @ConfigOption(defaultValue = "1.0", description = "the beta value for the F-score calculation")
071        protected double beta = 1.0;
072
073        @ConfigOption(defaultValue = "false", description = "compute everything in a single SPARQL query")
074        protected boolean batchMode = false;
075
076        
077        public ObjectPropertyHierarchyAxiomLearner(SparqlEndpointKS ks) {
078                this.ks = ks;
079                
080                super.posExamplesQueryTemplate = new ParameterizedSparqlString(
081                                "SELECT DISTINCT ?s ?o WHERE {?s ?p ?o ; ?p_other ?o}");
082                super.negExamplesQueryTemplate = new ParameterizedSparqlString(
083                                "SELECT DISTINCT ?s ?o WHERE {?s ?p ?o. FILTER NOT EXISTS{?s ?p_other ?o}}");
084        }
085        
086        @Override
087        public void setEntityToDescribe(OWLObjectProperty entityToDescribe) {
088                super.setEntityToDescribe(entityToDescribe);
089                
090                GIVEN_PROPERTY_OVERLAP_QUERY.setIri("p", entityToDescribe.toStringID());
091                PROPERTY_OVERLAP_QUERY.setIri("p", entityToDescribe.toStringID());
092                PROPERTY_OVERLAP_WITH_RANGE_QUERY.setIri("p", entityToDescribe.toStringID());
093        }
094        
095        /* (non-Javadoc)
096         * @see org.dllearner.algorithms.properties.PropertyAxiomLearner#getSampleQuery()
097         */
098        @Override
099        protected ParameterizedSparqlString getSampleQuery() {
100                return SAMPLE_QUERY;
101        }
102        
103        @Override
104        protected void run() {
105                if(batchMode) {
106                        runBatched();
107                } else {
108                        runIterative();
109                }
110        }
111
112        protected void runIterative() {
113                // get the candidates
114                SortedSet<OWLObjectProperty> candidates = getCandidates();
115
116                // check for each candidate if an overlap exist
117                int i = 1;
118                for (OWLObjectProperty p : candidates) {
119                        logger.debug("processing candidate property {}...", p);
120                        progressMonitor.learningProgressChanged(axiomType, i++, candidates.size());
121
122                        // get the popularity of the candidate
123                        int candidatePopularity = reasoner.getPopularity(p);
124
125                        if(candidatePopularity == 0){// skip empty properties
126                                logger.debug("Cannot compute equivalence statements for empty candidate property " + p);
127                                continue;
128                        }
129
130                        // get the number of overlapping triples, i.e. triples with the same subject and object
131                        GIVEN_PROPERTY_OVERLAP_QUERY.setIri("p_other", p.toStringID());
132                        ResultSet rs = executeSelectQuery(GIVEN_PROPERTY_OVERLAP_QUERY.toString());
133                        int overlap = rs.next().getLiteral("overlap").getInt();
134
135                        // compute the score
136                        AxiomScore score = computeScore(candidatePopularity, popularity, overlap);
137
138                        currentlyBestAxioms.add(new EvaluatedAxiom<>(getAxiom(entityToDescribe, p), score));
139                }
140        }
141        
142        /**
143         * In this method we try to compute the overlap with each property in one single SPARQL query.
144         * This method might be much slower as the query is much more complex.
145         *
146         * There are two options:
147         * 1) compute the overlap in a single query, but the popularity for each overlapping property separately
148         * 2) compute overlap and popularity in a single query
149         */
150        protected void runBatched() {
151                
152                String query;
153                if(strictMode){
154                        // get rdfs:range of the property
155                        OWLClassExpression range = reasoner.getRange(entityToDescribe);
156                        
157                        if(range != null && !range.isAnonymous() && !range.isOWLThing()){
158                                PROPERTY_OVERLAP_WITH_RANGE_QUERY.setIri("range", range.asOWLClass().toStringID());
159                                query = PROPERTY_OVERLAP_WITH_RANGE_QUERY.toString();
160                        } else {
161                                query = PROPERTY_OVERLAP_QUERY.toString();
162                        }
163                } else {
164                        query = PROPERTY_OVERLAP_QUERY.toString();
165                }
166
167                // compute the property candidates p_i that have at least one (s,o) in common with the target property p
168                ResultSet rs = executeSelectQuery(query);
169                ResultSetRewindable rsrw = ResultSetFactory.copyResults(rs);
170            int size = rsrw.size();
171            rs = rsrw;
172                while (rs.hasNext()) {
173                        QuerySolution qs = rsrw.next();
174
175                        progressMonitor.learningProgressChanged(axiomType, rs.getRowNumber(), size);
176                        
177                        OWLObjectProperty candidate = df.getOWLObjectProperty(IRI.create(qs.getResource("p_other").getURI()));
178                        
179                        // get the popularity of the candidate
180                        int candidatePopularity = reasoner.getPopularity(candidate);
181                        
182                        // get the number of overlapping triples, i.e. triples with the same subject and object
183                        int overlap = qs.getLiteral("overlap").getInt();
184                        
185                        // compute the score
186                        AxiomScore score = computeScore(candidatePopularity, popularity, overlap);
187
188                        currentlyBestAxioms.add(new EvaluatedAxiom<>(getAxiom(entityToDescribe, candidate), score));
189                }
190        }
191
192        public abstract T getAxiom(OWLObjectProperty property, OWLObjectProperty otherProperty);
193        
194        /**
195         * Returns the candidate properties for comparison.
196         * @return the candidate properties
197         */
198        protected SortedSet<OWLObjectProperty> getCandidates(){
199                // get the candidates
200                SortedSet<OWLObjectProperty> candidates = new TreeSet<>();
201
202                if (strictMode) { // that have the same domain and range 
203                        // get rdfs:domain of the property
204                        OWLClassExpression domain = reasoner.getDomain(entityToDescribe);
205
206                        // get rdfs:range of the property
207                        OWLClassExpression range = reasoner.getRange(entityToDescribe);
208
209                        String query = "SELECT ?p WHERE {?p a owl:ObjectProperty .";
210                        if (domain != null && !domain.isAnonymous() && !domain.isOWLThing()) {
211                                query += "?p rdfs:domain <" + domain.asOWLClass().toStringID() + "> .";
212                        }
213
214                        if (range != null && !range.isAnonymous() && !range.isOWLThing()) {
215                                query += "?p rdfs:range <" + range.asOWLClass().toStringID() + "> .";
216                        }
217                        query += "}";
218
219                        ResultSet rs = executeSelectQuery(query);
220                        while (rs.hasNext()) {
221                                OWLObjectProperty p = df.getOWLObjectProperty(IRI.create(rs.next().getResource("p").getURI()));
222                                candidates.add(p);
223                        }
224
225                } else {// we have to check all other properties
226                        candidates = reasoner.getOWLObjectProperties();
227                }
228                candidates.remove(entityToDescribe);
229                
230                return candidates;
231        }
232        
233        @Override
234        protected Set<OWLObjectPropertyAssertionAxiom> getExamples(ParameterizedSparqlString queryTemplate,
235                                                                                                                           EvaluatedAxiom<T> evAxiom) {
236                T axiom = evAxiom.getAxiom();
237                queryTemplate.setIri("p", entityToDescribe.toStringID());
238
239                OWLObjectProperty otherProperty;
240                if(axiom instanceof OWLNaryPropertyAxiom){// we assume a single atomic property
241                        otherProperty = ((OWLNaryPropertyAxiom<OWLObjectPropertyExpression>) axiom).getPropertiesMinus(entityToDescribe).iterator().next()
242                                        .asOWLObjectProperty();
243                } else {
244                        otherProperty = ((OWLSubObjectPropertyOfAxiom) axiom).getSuperProperty().asOWLObjectProperty();
245                }
246                queryTemplate.setIri("p_other", otherProperty.toStringID());
247
248                Set<OWLObjectPropertyAssertionAxiom> examples = new TreeSet<>();
249
250                ResultSet rs = executeSelectQuery(queryTemplate.toString());
251
252                while (rs.hasNext()) {
253                        QuerySolution qs = rs.next();
254                        OWLIndividual subject = df.getOWLNamedIndividual(IRI.create(qs.getResource("s").getURI()));
255                        OWLIndividual object = df.getOWLNamedIndividual(IRI.create(qs.getResource("o").getURI()));
256                        examples.add(df.getOWLObjectPropertyAssertionAxiom(entityToDescribe, subject, object));
257                }
258
259                return examples;
260        }
261
262        /**
263         * @param beta the beta to set
264         */
265        public void setBeta(double beta) {
266                this.beta = beta;
267        }
268        
269        /**
270         * @param strictMode the strictMode to set
271         */
272        public void setStrictMode(boolean strictMode) {
273                this.strictMode = strictMode;
274        }
275
276        public boolean isStrictMode() {
277                return strictMode;
278        }
279
280        public double getBeta() {
281                return beta;
282        }
283
284        /**
285         * If <code>true</code>, batch mode is used and only a single query will be used to compute the result. Otherwise,
286         * iteration over all properties in the ontology is done, i.e. at lots of queries - but simpler ones - will
287         * be executed.
288         *
289         * @param batchMode
290         */
291        public void setBatchMode(boolean batchMode) {
292                this.batchMode = batchMode;
293        }
294
295        public boolean isBatchMode() {
296                return batchMode;
297        }
298}