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;
020
021import org.apache.jena.query.ParameterizedSparqlString;
022import org.apache.jena.query.QuerySolution;
023import org.apache.jena.query.ResultSet;
024import org.apache.jena.rdf.model.RDFNode;
025import org.dllearner.core.*;
026import org.dllearner.core.config.ConfigOption;
027import org.dllearner.kb.SparqlEndpointKS;
028import org.dllearner.kb.sparql.SparqlEndpoint;
029import org.dllearner.learningproblems.AxiomScore;
030import org.dllearner.learningproblems.ClassScore;
031import org.dllearner.learningproblems.ScoreSimple;
032import org.dllearner.utilities.OwlApiJenaUtils;
033import org.semanticweb.owlapi.dlsyntax.renderer.DLSyntaxObjectRenderer;
034import org.semanticweb.owlapi.io.ToStringRenderer;
035import org.semanticweb.owlapi.model.*;
036import uk.ac.manchester.cs.owl.owlapi.OWLClassImpl;
037
038import java.util.*;
039import java.util.function.Function;
040import java.util.stream.Collectors;
041
042/**
043 * Learns subclass-relationships for a given class by using SPARQL queries.
044 * 
045 * @author Lorenz Bühmann
046 * @author Jens Lehmann
047 *
048 */
049@ComponentAnn(name = "simple subclass learner", shortName = "clsub", version = 0.1)
050public class SimpleSubclassLearner extends AbstractAxiomLearningAlgorithm<OWLSubClassOfAxiom, OWLClassAssertionAxiom, OWLClass>
051                implements ClassExpressionLearningAlgorithm {
052
053        private static final ParameterizedSparqlString SAMPLE_QUERY = new ParameterizedSparqlString(
054                        "CONSTRUCT{?s a ?entity . ?s a ?cls1 .} WHERE {?s a ?entity . OPTIONAL {?s a ?cls1 . }}");
055
056        private static final ParameterizedSparqlString CLASS_OVERLAP_BATCH_QUERY = new ParameterizedSparqlString(
057                        "SELECT ?cls_other (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" +
058                                        "?s a ?cls . ?s a ?cls_other . " +
059                                        "FILTER(?cls_other != ?cls) " +
060                                        "FILTER(?cls_other != <http://www.w3.org/2002/07/owl#NamedIndividual>) " +
061                                        "} GROUP BY ?cls_other");
062
063        private static final ParameterizedSparqlString CLASS_OVERLAP_BATCH_QUERY_STRICT_OWL = new ParameterizedSparqlString(
064                        "SELECT ?cls_other (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" +
065                                        "?s a ?cls . ?s a ?cls_other . " +
066                                        "?cls_other a <http://www.w3.org/2002/07/owl#Class> . " +
067                                        "FILTER(?cls_other != ?cls) " +
068                                        "FILTER(?cls_other != <http://www.w3.org/2002/07/owl#NamedIndividual>) " +
069                                        "} GROUP BY ?cls_other");
070
071        private static final ParameterizedSparqlString CLASS_OVERLAP_QUERY = new ParameterizedSparqlString(
072                        "SELECT (COUNT(DISTINCT ?s) AS ?cnt) WHERE {" +
073                                        "?s a ?cls . ?s a ?cls_other . }");
074
075
076        @ConfigOption(defaultValue = "false", description = "compute everything in a single SPARQL query")
077        private boolean batchMode = false;
078
079        @ConfigOption(defaultValue = "false")
080        private boolean strictOWLMode = false;
081
082        private List<EvaluatedDescription<? extends Score>> currentlyBestEvaluatedDescriptions;
083
084        public SimpleSubclassLearner() {
085                super.posExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s WHERE {?s a ?cls1 . ?s a ?cls2}");
086                super.negExamplesQueryTemplate = new ParameterizedSparqlString("SELECT ?s WHERE {?s a ?cls1 . FILTER NOT EXISTS{?s a ?cls2}}");
087
088                axiomType = AxiomType.SUBCLASS_OF;
089        }
090
091        public SimpleSubclassLearner(SparqlEndpointKS ks) {
092                this();
093
094                this.ks = ks;
095        }
096
097        @Override
098        public List<EvaluatedAxiom<OWLSubClassOfAxiom>> getCurrentlyBestEvaluatedAxioms() {
099                return getCurrentlyBestEvaluatedAxioms(currentlyBestEvaluatedDescriptions.size());
100        }
101
102        @Override
103        public List<OWLClassExpression> getCurrentlyBestDescriptions(int nrOfDescriptions) {
104                return getCurrentlyBestEvaluatedDescriptions(nrOfDescriptions).stream()
105                                .map(EvaluatedHypothesis::getDescription)
106                                .collect(Collectors.toList());
107        }
108
109        @Override
110        public List<? extends EvaluatedDescription<? extends Score>> getCurrentlyBestEvaluatedDescriptions(int nrOfDescriptions) {
111                int max = Math.min(currentlyBestEvaluatedDescriptions.size(), nrOfDescriptions);
112                return currentlyBestEvaluatedDescriptions.subList(0, max);
113        }
114
115        @Override
116        public List<OWLSubClassOfAxiom> getCurrentlyBestAxioms(int nrOfAxioms) {
117                return getCurrentlyBestEvaluatedAxioms(nrOfAxioms).stream()
118                                                                                                .map(EvaluatedAxiom::getAxiom)
119                                                                                                .collect(Collectors.toList());
120        }
121
122        @Override
123        public List<EvaluatedAxiom<OWLSubClassOfAxiom>> getCurrentlyBestEvaluatedAxioms(int nrOfAxioms) {
124                currentlyBestAxioms = new TreeSet<>();
125                for (EvaluatedDescription<? extends Score> ed : getCurrentlyBestEvaluatedDescriptions(nrOfAxioms)) {
126                        currentlyBestAxioms.add(new EvaluatedAxiom<>(df.getOWLSubClassOfAxiom(entityToDescribe,
127                                        ed.getDescription()), new AxiomScore(ed.getAccuracy())));
128                }
129                return new ArrayList<>(currentlyBestAxioms);
130        }
131
132        @Override
133        public void start() {
134                currentlyBestEvaluatedDescriptions = new ArrayList<>();
135                super.start();
136        }
137        
138        /* (non-Javadoc)
139         * @see org.dllearner.core.AbstractAxiomLearningAlgorithm#getSampleQuery()
140         */
141        @Override
142        protected ParameterizedSparqlString getSampleQuery() {
143                return SAMPLE_QUERY;
144        }
145
146        /*
147         * (non-Javadoc)
148         * 
149         * @see
150         * org.dllearner.core.AbstractAxiomLearningAlgorithm#getExistingAxioms()
151         */
152        @Override
153        protected void getExistingAxioms() {
154                // get existing super classes
155                SortedSet<OWLClassExpression> existingSuperClasses = reasoner.getSuperClasses(entityToDescribe, false);
156                existingSuperClasses.remove(df.getOWLThing());
157                logger.info("Existing super classes: " + existingSuperClasses);
158
159                existingAxioms.addAll(existingSuperClasses.stream()
160                                                                .map(sup -> df.getOWLSubClassOfAxiom(entityToDescribe, sup))
161                                                                .collect(Collectors.toList()));
162        }
163
164        /*
165         * (non-Javadoc)
166         * 
167         * @see org.dllearner.core.AbstractAxiomLearningAlgorithm#learnAxioms()
168         */
169        @Override
170        protected void learnAxioms() {
171                if(batchMode) {
172                        runBatched();
173                } else {
174                        runIterative();
175                }
176
177                currentlyBestEvaluatedDescriptions.forEach(
178                                ed -> currentlyBestAxioms.add(
179                                                new EvaluatedAxiom<>(df.getOWLSubClassOfAxiom(entityToDescribe, ed.getDescription()),
180                                                                                         new AxiomScore(ed.getAccuracy()))));
181        }
182
183        private void runIterative() {
184                CLASS_OVERLAP_QUERY.setIri("cls", entityToDescribe.toStringID());
185
186                // get the candidates
187                SortedSet<OWLClass> candidates = getCandidates();
188
189                // check for each candidate if an overlap exist
190                int i = 1;
191                for (OWLClass cls : candidates) {
192                        progressMonitor.learningProgressChanged(axiomType, i++, candidates.size());
193
194                        // get the popularity of the candidate
195                        int candidatePopularity = reasoner.getPopularity(cls);
196
197                        if(candidatePopularity == 0){// skip empty classes
198                                logger.debug("Cannot compute subclass statements for empty candidate class " + cls);
199                                continue;
200                        }
201
202                        // get the number of instances that belong to both classes
203                        CLASS_OVERLAP_QUERY.setIri("cls_other", cls.toStringID());
204
205                        ResultSet rs = executeSelectQuery(CLASS_OVERLAP_QUERY.toString());
206                        int overlap = rs.next().getLiteral("cnt").getInt();
207
208                        // compute the score
209                        AxiomScore score = computeScore(popularity, overlap);
210
211                        currentlyBestEvaluatedDescriptions.add(new EvaluatedDescription(cls, new ScoreSimple(score.getAccuracy())));
212                }
213        }
214
215        /**
216         * Returns the candidate properties for comparison.
217         * @return  the candidate properties
218         */
219        private SortedSet<OWLClass> getCandidates(){
220                // get the candidates
221                SortedSet<OWLClass> candidates = strictOWLMode ? reasoner.getOWLClasses() : reasoner.getClasses();
222
223                // remove class to describe
224                candidates.remove(entityToDescribe);
225
226                return candidates;
227        }
228
229        private void runBatched() {
230                ParameterizedSparqlString template = strictOWLMode ? CLASS_OVERLAP_BATCH_QUERY_STRICT_OWL : CLASS_OVERLAP_BATCH_QUERY;
231
232                template.setIri("cls", entityToDescribe.toStringID());
233
234                ResultSet rs = executeSelectQuery(template.toString());
235
236                while (rs.hasNext()) {
237                        QuerySolution qs = rs.next();
238
239                        RDFNode cls = qs.get("cls_other");
240                        if (!cls.isAnon()) {
241                                OWLClass sup = OwlApiJenaUtils.asOWLEntity(cls.asNode(), EntityType.CLASS);
242                                int overlap = qs.get("cnt").asLiteral().getInt();
243                                if (!sup.isOWLThing() && !entityToDescribe.equals(sup)) {//omit owl:Thing and the class to describe itself
244                                        currentlyBestEvaluatedDescriptions.add(new EvaluatedDescription(sup, computeScore(popularity,
245                                                                                                                                                                                                          overlap)));
246                                }
247                        } else {
248                                logger.warn("Ignoring anonymous super class candidate: " + cls);
249                        }
250                }
251        }
252
253        protected Set<OWLClassAssertionAxiom> getExamples(ParameterizedSparqlString queryTemplate, EvaluatedAxiom<OWLSubClassOfAxiom> evAxiom) {
254                OWLSubClassOfAxiom axiom = evAxiom.getAxiom();
255                queryTemplate.setIri("cls1", axiom.getSubClass().asOWLClass().toStringID());
256                queryTemplate.setIri("cls2", axiom.getSuperClass().asOWLClass().toStringID());
257
258                Set<OWLClassAssertionAxiom> examples = new TreeSet<>();
259
260                ResultSet rs = executeSelectQuery(queryTemplate.toString());
261
262                while (rs.hasNext()) {
263                        QuerySolution qs = rs.next();
264                        OWLIndividual subject = df.getOWLNamedIndividual(IRI.create(qs.getResource("s").getURI()));
265                        examples.add(df.getOWLClassAssertionAxiom(axiom.getSuperClass(), subject));
266                }
267
268                return examples;
269        }
270
271        public void setBatchMode(boolean batchMode) {
272                this.batchMode = batchMode;
273        }
274
275        public boolean isBatchMode() {
276                return batchMode;
277        }
278
279        public void setStrictOWLMode(boolean strictOWLMode) {
280                this.strictOWLMode = strictOWLMode;
281        }
282
283        public boolean isStrictOWLMode() {
284                return strictOWLMode;
285        }
286
287        public static void main(String[] args) throws Exception {
288                ToStringRenderer.getInstance().setRenderer(new DLSyntaxObjectRenderer());
289                SparqlEndpointKS ks = new SparqlEndpointKS(SparqlEndpoint.getEndpointDBpedia());
290                ks.init();
291
292                SimpleSubclassLearner la = new SimpleSubclassLearner(ks);
293                la.setEntityToDescribe(new OWLClassImpl(IRI.create("http://dbpedia.org/ontology/Book")));
294                la.setUseSampling(false);
295                la.setBatchMode(true);
296                la.setStrictOWLMode(true);
297                la.setReturnOnlyNewAxioms(true);
298                la.setProgressMonitor(new ConsoleAxiomLearningProgressMonitor());
299                la.init();
300
301                la.start();
302
303                la.getCurrentlyBestEvaluatedAxioms(0.3).forEach(ax -> {
304                        System.out.println("---------------\n" + ax);
305                        la.getPositiveExamples(ax).stream().limit(5).forEach(System.out::println);
306                });
307        }
308}