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