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.ComponentAnn;
023import org.dllearner.core.ConsoleAxiomLearningProgressMonitor;
024import org.dllearner.core.EvaluatedAxiom;
025import org.dllearner.core.config.ConfigOption;
026import org.dllearner.kb.SparqlEndpointKS;
027import org.dllearner.kb.sparql.SparqlEndpoint;
028import org.dllearner.learningproblems.AxiomScore;
029import org.dllearner.learningproblems.Heuristics;
030import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer;
031import org.semanticweb.owlapi.io.ToStringRenderer;
032import org.semanticweb.owlapi.model.*;
033import uk.ac.manchester.cs.owl.owlapi.OWLObjectPropertyImpl;
034
035import java.util.Set;
036import java.util.TreeSet;
037
038@ComponentAnn(name="object property range learner", shortName="oplrange", version=0.1, description="A learning algorithm for object property range axioms.")
039public class ObjectPropertyRangeAxiomLearner extends ObjectPropertyAxiomLearner<OWLObjectPropertyRangeAxiom> {
040        
041        private static final ParameterizedSparqlString DISTINCT_OBJECTS_COUNT_QUERY = new ParameterizedSparqlString(
042                        "SELECT (COUNT(DISTINCT(?o)) as ?cnt) WHERE {?s ?p ?o .}");
043        
044        private static final ParameterizedSparqlString OBJECTS_OF_TYPE_COUNT_QUERY = new ParameterizedSparqlString(
045                        "SELECT (COUNT(DISTINCT(?o)) AS ?cnt) WHERE {?s ?p ?o . ?o a ?type .}");
046        private static final ParameterizedSparqlString OBJECTS_OF_TYPE_WITH_INFERENCE_COUNT_QUERY = new ParameterizedSparqlString(
047                        "SELECT (COUNT(DISTINCT(?o)) AS ?cnt) WHERE {?s ?p ?o . ?o rdf:type/rdfs:subClassOf* ?type .}");
048        
049        private static final ParameterizedSparqlString OBJECTS_OF_TYPE_COUNT_BATCHED_QUERY = new ParameterizedSparqlString(
050                        "PREFIX owl:<http://www.w3.org/2002/07/owl#> SELECT ?type (COUNT(DISTINCT(?o)) AS ?cnt) WHERE {?s ?p ?o . ?o a ?type . ?type a owl:Class .} GROUP BY ?type");
051        private static final ParameterizedSparqlString OBJECTS_OF_TYPE_WITH_INFERENCE_COUNT_BATCHED_QUERY = new ParameterizedSparqlString(
052                        "PREFIX owl:<http://www.w3.org/2002/07/owl#> SELECT ?type (COUNT(DISTINCT(?o)) AS ?cnt) WHERE {?s ?p ?o . ?o rdf:type/rdfs:subClassOf* ?type . ?type a owl:Class .} GROUP BY ?type");
053
054        @ConfigOption(defaultValue = "false", description = "compute everything in a single SPARQL query")
055        protected boolean batchMode = false;
056
057        public ObjectPropertyRangeAxiomLearner() {
058                super.posExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s ?o WHERE {?o ?p ?s. ?s a ?type .}");
059                super.negExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s ?o WHERE {?o ?p ?s. FILTER NOT EXISTS {?s a ?type}}");
060
061                COUNT_QUERY = DISTINCT_OBJECTS_COUNT_QUERY;
062
063                axiomType = AxiomType.OBJECT_PROPERTY_RANGE;
064        }
065
066        public ObjectPropertyRangeAxiomLearner(SparqlEndpointKS ks){
067                this();
068                this.ks = ks;
069        }
070        
071        /* (non-Javadoc)
072         * @see org.dllearner.algorithms.properties.PropertyAxiomLearner#setEntityToDescribe(org.semanticweb.owlapi.model.OWLProperty)
073         */
074        @Override
075        public void setEntityToDescribe(OWLObjectProperty entityToDescribe) {
076                super.setEntityToDescribe(entityToDescribe);
077                
078                DISTINCT_OBJECTS_COUNT_QUERY.setIri("p", entityToDescribe.toStringID());
079                OBJECTS_OF_TYPE_COUNT_QUERY.setIri("p", entityToDescribe.toStringID());
080                OBJECTS_OF_TYPE_WITH_INFERENCE_COUNT_QUERY.setIri("p", entityToDescribe.toStringID());
081                OBJECTS_OF_TYPE_COUNT_BATCHED_QUERY.setIri("p", entityToDescribe.toStringID());
082                OBJECTS_OF_TYPE_WITH_INFERENCE_COUNT_BATCHED_QUERY.setIri("p", entityToDescribe.toStringID());
083        }
084        
085        /* (non-Javadoc)
086         * @see org.dllearner.core.AbstractAxiomLearningAlgorithm#getExistingAxioms()
087         */
088        @Override
089        protected void getExistingAxioms() {
090                OWLClassExpression existingRange = reasoner.getRange(entityToDescribe);
091                if (existingRange != null) {
092                        existingAxioms.add(df.getOWLObjectPropertyRangeAxiom(entityToDescribe, existingRange));
093                        logger.info("Existing range: " + existingRange);
094                        if (reasoner.isPrepared()) {
095                                if (reasoner.getClassHierarchy().contains(existingRange)) {
096                                        for (OWLClassExpression sup : reasoner.getClassHierarchy().getSuperClasses(existingRange)) {
097                                                existingAxioms.add(df.getOWLObjectPropertyRangeAxiom(entityToDescribe, existingRange));
098                                                logger.info("Existing range(inferred): " + sup);
099                                        }
100                                }
101                        }
102                }
103        }
104        
105        /* (non-Javadoc)
106         * @see org.dllearner.algorithms.properties.PropertyAxiomLearner#getSampleQuery()
107         */
108        @Override
109        protected ParameterizedSparqlString getSampleQuery() {
110                return new ParameterizedSparqlString(
111                                "PREFIX owl:<http://www.w3.org/2002/07/owl#> "
112                                + "CONSTRUCT "
113                                + "{?s ?p ?o . ?o a ?cls . "
114                                + (strictOWLMode ? "?cls a owl:Class . " : "")
115                                + "} "
116                                + "WHERE "
117                                + "{?s ?p ?o . ?o a ?cls . "
118                                + (strictOWLMode ? "?cls a owl:Class . " : "")
119                                + "}");
120        }
121
122        @Override
123        protected void run(){
124                if(batchMode) {
125                        runBatched();
126                } else {
127                        runIterative();
128                }
129        }
130        
131        private void runIterative(){
132                // get the candidates
133                Set<OWLClass> candidates = reasoner.getNonEmptyOWLClasses();
134                
135                // check for each candidate how often the subject belongs to it
136                int i = 1;
137                for (OWLClass candidate : candidates) {
138                        logger.debug("Candidate:" + candidate);
139                        progressMonitor.learningProgressChanged(axiomType, i++, candidates.size());
140                        
141                        // get total number of instances of B
142                        int cntB = reasoner.getPopularity(candidate);
143                        logger.debug("Popularity:" + cntB);
144                        
145                        if(cntB == 0){// skip empty properties
146                                logger.debug("Cannot compute range statements for empty candidate class " + candidate);
147                                continue;
148                        }
149                        
150                        // get number of instances of (A AND B)
151                        OBJECTS_OF_TYPE_COUNT_QUERY.setIri("type", candidate.toStringID());
152                        int cntAB = executeSelectQuery(OBJECTS_OF_TYPE_COUNT_QUERY.toString()).next().getLiteral("cnt").getInt();
153                        logger.debug("Candidate:" + candidate + "\npopularity:" + cntB + "\noverlap:" + cntAB);
154                        
155                        // compute score
156                        AxiomScore score = computeScore(popularity, cntB, cntAB);
157
158                        currentlyBestAxioms.add(
159                                        new EvaluatedAxiom<>(df.getOWLObjectPropertyRangeAxiom(entityToDescribe, candidate), score));
160                }
161        }
162
163        /**
164         * We can handle the domain axiom Domain(r, C) as a subclass of axiom \exists r.\top \sqsubseteq C
165         */
166        private void runBatched(){
167                
168                // we can compute the popularity of the properties once which can avoid sending several single 
169                // query later on
170                reasoner.precomputeClassPopularity();
171                
172                // get for each object type the frequency
173                ResultSet rs = executeSelectQuery(OBJECTS_OF_TYPE_COUNT_BATCHED_QUERY.toString());
174                ResultSetRewindable rsrw = ResultSetFactory.copyResults(rs);
175                int size = rsrw.size();
176                rsrw.reset();
177                int i = 1;
178                while(rsrw.hasNext()){
179                        QuerySolution qs = rsrw.next();
180                        if(qs.getResource("type").isURIResource()){
181                                progressMonitor.learningProgressChanged(axiomType, i++, size);
182                                
183                                OWLClass candidate = df.getOWLClass(IRI.create(qs.getResource("type").getURI()));
184                                
185                                //get total number of instances of B
186                                int cntB = reasoner.getPopularity(candidate);
187                                
188                                //get number of instances of (A AND B)
189                                int cntAB = qs.getLiteral("cnt").getInt();
190                                
191                                //precision (A AND B)/B
192                                double precision = Heuristics.getConfidenceInterval95WaldAverage(cntB, cntAB);
193                                
194                                //recall (A AND B)/A
195                                double recall = Heuristics.getConfidenceInterval95WaldAverage(popularity, cntAB);
196                                
197                                //F score
198                                double score = Heuristics.getFScore(recall, precision, beta);
199                                
200                                currentlyBestAxioms.add(
201                                                new EvaluatedAxiom<>(
202                                                                df.getOWLObjectPropertyRangeAxiom(entityToDescribe, candidate),
203                                                                new AxiomScore(score, useSampling)));
204                                
205                        }
206                }
207        }
208
209        @Override
210        protected Set<OWLObjectPropertyAssertionAxiom> getExamples(ParameterizedSparqlString queryTemplate,
211                                                                                                                           EvaluatedAxiom<OWLObjectPropertyRangeAxiom> evAxiom) {
212                OWLObjectPropertyRangeAxiom axiom = evAxiom.getAxiom();
213                queryTemplate.setIri("p", entityToDescribe.toStringID());
214                queryTemplate.setIri("type", axiom.getRange().asOWLClass().toStringID());
215
216                Set<OWLObjectPropertyAssertionAxiom> examples = new TreeSet<>();
217
218                ResultSet rs = executeSelectQuery(queryTemplate.toString());
219
220                while (rs.hasNext()) {
221                        QuerySolution qs = rs.next();
222                        OWLIndividual subject = df.getOWLNamedIndividual(IRI.create(qs.getResource("s").getURI()));
223                        OWLIndividual object = df.getOWLNamedIndividual(IRI.create(qs.getResource("o").getURI()));
224                        examples.add(df.getOWLObjectPropertyAssertionAxiom(entityToDescribe, subject, object));
225                }
226
227                return examples;
228        }
229
230        public void setBatchMode(boolean batchMode) {
231                this.batchMode = batchMode;
232        }
233
234        public boolean isBatchMode() {
235                return batchMode;
236        }
237
238
239        public static void main(String[] args) throws Exception {
240                ToStringRenderer.getInstance().setRenderer(new DLSyntaxObjectRenderer());
241                SparqlEndpointKS ks = new SparqlEndpointKS(SparqlEndpoint.getEndpointDBpedia());
242                ks.init();
243
244                ObjectPropertyRangeAxiomLearner la = new ObjectPropertyRangeAxiomLearner(ks);
245                la.setPropertyToDescribe(new OWLObjectPropertyImpl(IRI.create("http://dbpedia.org/ontology/author")));
246                la.setUseSampling(false);
247                la.setBatchMode(true);
248                la.setProgressMonitor(new ConsoleAxiomLearningProgressMonitor());
249                la.init();
250
251                la.start();
252
253                la.getCurrentlyBestEvaluatedAxioms().forEach(ax -> {
254                        System.out.println("---------------\n" + ax);
255                        la.getPositiveExamples(ax).stream().limit(5).forEach(System.out::println);
256                });
257        }
258}