001 /*
002 * To change this license header, choose License Headers in Project Properties.
003 * To change this template file, choose Tools | Templates
004 * and open the template in the editor.
005 */
006package org.dllearner.algorithms.probabilistic.structure.unife.leap;
007
008import com.clarkparsia.pellet.owlapiv3.PelletReasoner;
009import com.clarkparsia.pellet.owlapiv3.PelletReasonerFactory;
010import com.google.common.collect.Sets;
011import edu.stanford.nlp.util.Factory;
012import java.util.Collections;
013import java.util.HashSet;
014import java.util.LinkedHashMap;
015import java.util.LinkedHashSet;
016import java.util.LinkedList;
017import java.util.List;
018import java.util.Map;
019import java.util.Map.Entry;
020import java.util.NavigableSet;
021import java.util.Set;
022import java.util.TreeMap;
023import java.util.TreeSet;
024import org.apache.commons.lang.StringUtils;
025import org.dllearner.core.AbstractClassExpressionLearningProblem;
026import org.dllearner.core.ComponentInitException;
027import org.dllearner.core.EvaluatedDescription;
028import org.dllearner.core.LearningProblemUnsupportedException;
029import org.dllearner.core.config.ConfigOption;
030import org.dllearner.learningproblems.ClassLearningProblem;
031import org.dllearner.learningproblems.PosNegLP;
032import org.dllearner.learningproblems.PosOnlyLP;
033import org.dllearner.core.probabilistic.unife.AbstractPSLA;
034import org.dllearner.core.probabilistic.unife.StructureLearningException;
035import org.dllearner.algorithms.probabilistic.parameter.unife.edge.AbstractEDGE;
036import org.dllearner.core.AbstractCELA;
037import org.dllearner.core.AbstractReasonerComponent;
038import org.dllearner.core.probabilistic.unife.AbstractParameterLearningAlgorithm;
039import org.dllearner.exceptions.UnsupportedLearnedAxiom;
040import org.dllearner.reasoning.ClosedWorldReasoner;
041import org.dllearner.utils.unife.OWLClassExpressionSimplifierVisitorImpl;
042import org.dllearner.utils.unife.ReflectionHelper;
043import org.dllearner.utils.unife.OWLUtils;
044import org.semanticweb.owlapi.apibinding.OWLManager;
045import org.semanticweb.owlapi.model.AxiomType;
046import org.semanticweb.owlapi.model.ClassExpressionType;
047import org.semanticweb.owlapi.model.IRI;
048import org.semanticweb.owlapi.model.OWLAnnotation;
049import org.semanticweb.owlapi.model.OWLAxiom;
050import org.semanticweb.owlapi.model.OWLClass;
051import org.semanticweb.owlapi.model.OWLClassExpression;
052import org.semanticweb.owlapi.model.OWLDataFactory;
053import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
054import org.semanticweb.owlapi.model.OWLIndividual;
055import org.semanticweb.owlapi.model.OWLObjectUnionOf;
056import org.semanticweb.owlapi.model.OWLOntology;
057import org.semanticweb.owlapi.model.OWLOntologyManager;
058import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
059import org.semanticweb.owlapi.model.parameters.Imports;
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062import org.springframework.beans.factory.annotation.Autowired;
063import unife.bundle.exception.InconsistencyException;
064import unife.bundle.utilities.BundleUtilities;
065import unife.constants.UniFeIRI;
066
067/**
068 * There could be different version of LEAP (sequential, multi-thread,
069 * distributed), so we need an abstract class.
070 *
071 * @author Giuseppe Cota <giuseppe.cota@unife.it>, Riccardo Zese
072 * <riccardo.zese@unife.it>
073 */
074public abstract class AbstractLEAP extends AbstractPSLA {
075
076    private static final Logger logger = LoggerFactory.getLogger(AbstractLEAP.class);
077
078    @ConfigOption(defaultValue = "owl:learnedClass", description = "You can "
079            + "specify a start class for the algorithm. To do this, you have to "
080            + "use Manchester OWL syntax without using prefixes.")
081    protected OWLClass dummyClass;
082
083    @ConfigOption(description = "accuracy used during the computation of the "
084            + "probabilistic values (number of digital places)", defaultValue = "5")
085    protected int accuracy = 5;
086
087    @ConfigOption(description = "This is used to set the type of class axiom to learn. Accepted values (case insensitive): 'subClassOf', 'equivalentClasses', 'both'",
088            required = false,
089            defaultValue = "subClassOf")
090    protected String classAxiomType = "subClassOf";
091
092    @ConfigOption(defaultValue = "10", description = "maximum execution of the algorithm in seconds")
093    protected int maxExecutionTimeInSeconds = 10; // TO DO: stop when execution time is over
094
095    @ConfigOption(defaultValue = "1",
096            required = false,
097            description = "the number of probabilistic axioms that LEAP tries to "
098            + "add into the ontology at each iteration of the greedy search")
099    protected int blockSizeGreedySearch = 1;
100
101    protected TreeMap<String, Long> timers;
102
103    protected AbstractEDGE edge;
104
105    public AbstractLEAP() {
106
107    }
108
109    public AbstractLEAP(AbstractCELA cela, AbstractParameterLearningAlgorithm pla) {
110        super(cela, pla);
111    }
112
113    @Override
114    public void init() throws ComponentInitException {
115
116        timers = new TreeMap<>();
117
118        //OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
119        OWLOntologyManager manager = edge.getSourcesOntology().getOWLOntologyManager();
120        // create dummy class
121        if (dummyClass == null) {
122            OWLDataFactory owlFactory = manager.getOWLDataFactory();
123            dummyClass = owlFactory.getOWLClass(IRI.create(UniFeIRI.DISPONTE + "/learnedClass"));
124            OWLSubClassOfAxiom axiom = owlFactory.
125                    getOWLSubClassOfAxiom(dummyClass, owlFactory.getOWLThing());
126            manager.addAxiom(edge.getSourcesOntology(), axiom);
127        }
128
129        logger.debug("getting the individuals");
130        Set<OWLIndividual> positiveIndividuals;
131        Set<OWLIndividual> negativeIndividuals;
132        if (learningProblem == null) {
133            learningProblem = cela.getLearningProblem();
134        }
135        if (learningProblem instanceof PosNegLP) {
136            positiveIndividuals = ((PosNegLP) learningProblem).getPositiveExamples();
137            negativeIndividuals = ((PosNegLP) learningProblem).getNegativeExamples();
138        } else if (learningProblem instanceof PosOnlyLP) {
139            positiveIndividuals = ((PosOnlyLP) learningProblem).getPositiveExamples();
140            // use pseudo-negative individuals
141            negativeIndividuals = Sets.difference(learningProblem.getReasoner().getIndividuals(), positiveIndividuals);
142        } else if (learningProblem instanceof ClassLearningProblem) {
143            // Java Reflection has been used to get values from private fields. 
144            //It's neither a conventional way nor the universally suggested idea,
145            // but in this case is the only way to extract positive and negative individuals
146            // without modifing the DLLearner code (the creation of a plugin is the objective)
147            try {
148                List<OWLIndividual> positiveIndividualsList = ReflectionHelper.getPrivateField(learningProblem, "classInstances");
149                positiveIndividuals = new TreeSet<>(positiveIndividualsList);
150                negativeIndividuals = new TreeSet<>((List<OWLIndividual>) ReflectionHelper.getPrivateField(learningProblem, "superClassInstances"));
151            } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) {
152                String msg = "Cannot extract the individuals from"
153                        + " learning problem: " + e.getMessage();
154                logger.error(msg);
155                throw new ComponentInitException(msg);
156            }
157
158        } else {
159            try {
160                throw new LearningProblemUnsupportedException(((AbstractClassExpressionLearningProblem) learningProblem).getClass(), this.getClass());
161            } catch (LearningProblemUnsupportedException e) {
162                throw new ComponentInitException(e.getMessage());
163            }
164        }
165        // convert the individuals into assertional axioms
166        logger.debug("convert the individuals into assertional axioms");
167//        OWLDataFactory owlFactory = manager.getOWLDataFactory();
168        Set<OWLAxiom> positiveExamples = OWLUtils.convertIndividualsToAssertionalAxioms(positiveIndividuals, dummyClass);
169//        Set<OWLAxiom> positiveExamples = new HashSet<>();
170//        for (OWLIndividual ind : positiveIndividuals) {
171//            OWLAxiom axiom = owlFactory.getOWLClassAssertionAxiom(dummyClass, ind);
172//            positiveExamples.add(axiom);
173//        }
174
175        Set<OWLAxiom> negativeExamples = OWLUtils.convertIndividualsToAssertionalAxioms(negativeIndividuals, dummyClass);
176//        Set<OWLAxiom> negativeExamples = new HashSet<>();
177//        for (OWLIndividual ind : negativeIndividuals) {
178//            OWLAxiom axiom = owlFactory.getOWLClassAssertionAxiom(dummyClass, ind);
179//            negativeExamples.add(axiom);
180//        }
181
182        edge.setPositiveExampleAxioms(positiveExamples);
183        edge.setNegativeExampleAxioms(negativeExamples);
184
185    }
186
187    protected void printTimings(long totalTimeMills, long celaTimeMills, TreeMap<String, Long> timeMap) {
188        logger.info("Main: " + totalTimeMills + " ms");
189        logger.info("CELOE: " + celaTimeMills + " ms");
190        long timeOther = totalTimeMills - celaTimeMills;
191        for (Entry<String, Long> time : timeMap.entrySet()) {
192            String names[] = time.getKey().split("\\.");
193            if (names.length == 1) {
194                timeOther -= time.getValue();
195//                logger.info(timeMap.subMap(names[0], names[0] + Character.MAX_VALUE).toString());
196            }
197
198            String output = StringUtils.repeat("\t", names.length - 1);
199            output += names[names.length - 1] + ": " + time.getValue() + " ms";
200            logger.info(output);
201
202        }
203
204//        logger.info("EDGE: " + (timeMap.get("EM") + timeMap.get("Bundle")) + " ms");
205//        logger.info("\tBundle: " + timeMap.get("Bundle") + " ms");
206//        logger.info("\tEM: " + timeMap.get("EM") + " ms");
207//        long timeOther = totalTimeMills - celaTimeMills - (timeMap.get("EM") + timeMap.get("Bundle"));
208        logger.info("Other: " + timeOther + " ms");
209        logger.info("Program client: execution successfully terminated");
210    }
211
212    /**
213     * @return the dummyClass
214     */
215    public OWLClass getDummyClass() {
216        return dummyClass;
217    }
218
219    /**
220     * @param dummyClass the dummyClass to set
221     */
222    public void setDummyClass(OWLClass dummyClass) {
223        this.dummyClass = dummyClass;
224    }
225
226    /**
227     * @return the edge
228     */
229    public AbstractEDGE getEdge() {
230        return edge;
231    }
232
233    /**
234     * @param edge the edge to set
235     */
236    @Autowired
237    public void setEdge(AbstractEDGE edge) {
238        this.edge = edge;
239    }
240
241    /**
242     * @return the accuracy
243     */
244    public int getAccuracy() {
245        return accuracy;
246    }
247
248    /**
249     * @param accuracy the accuracy to set
250     */
251    public void setAccuracy(int accuracy) {
252        this.accuracy = accuracy;
253    }
254
255    private <T extends OWLAxiom> List<T> convertIntoAxioms(Class<T> type, OWLOntologyManager manager,
256            NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions) {
257        List<T> axioms = new LinkedList<>();
258        OWLDataFactory factory = manager.getOWLDataFactory();
259        for (EvaluatedDescription description : evaluatedDescriptions.descendingSet()) {
260            OWLClassExpression ce = (OWLClassExpression) description.getDescription();
261            ce = OWLClassExpressionSimplifierVisitorImpl.getOWLClassExpression(ce, manager);
262            OWLAnnotation annotation = factory.
263                    getOWLAnnotation(BundleUtilities.PROBABILISTIC_ANNOTATION_PROPERTY, factory.getOWLLiteral(description.getAccuracy()));
264//            if (classAxiomType.equalsIgnoreCase("subClassOf") || classAxiomType.equalsIgnoreCase("both")) {
265            T axiom;
266            if (type == OWLEquivalentClassesAxiom.class) {
267                axiom = (T) factory.getOWLEquivalentClassesAxiom(ce, dummyClass, Collections.singleton(annotation));
268
269            } else if (type == OWLSubClassOfAxiom.class) {
270                axiom = (T) factory.getOWLSubClassOfAxiom(ce, dummyClass, Collections.singleton(annotation));
271            } else {
272                throw new RuntimeException("convertIntoAxioms only works with "
273                        + "equivalent and subclassOf axioms");
274            }
275            axioms.add(axiom);
276        }
277        return axioms;
278    }
279
280    protected List<OWLSubClassOfAxiom> convertIntoSubClassOfAxioms(OWLOntologyManager manager, NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions) {
281        List<OWLSubClassOfAxiom> axioms = convertIntoAxioms(OWLSubClassOfAxiom.class, manager, evaluatedDescriptions);
282        return axioms;
283    }
284
285    protected List<OWLEquivalentClassesAxiom> convertIntoEquivalentClassesAxioms(OWLOntologyManager manager, NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions) {
286        List<OWLEquivalentClassesAxiom> axioms = convertIntoAxioms(OWLEquivalentClassesAxiom.class, manager, evaluatedDescriptions);
287        return axioms;
288        
289    }
290
291    /*
292     protected List<OWLSubClassOfAxiom> convertIntoSubClassOfAxioms(OWLOntologyManager manager, NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions) {
293     List<OWLSubClassOfAxiom> axioms = new LinkedList<>();
294     OWLDataFactory factory = manager.getOWLDataFactory();
295     for (EvaluatedDescription description : evaluatedDescriptions.descendingSet()) {
296     OWLClassExpression ce = (OWLClassExpression) description.getDescription();
297     ce = OWLClassExpressionSimplifierVisitorImpl.getOWLClassExpression(ce, manager);
298     OWLAnnotation annotation = factory.
299     getOWLAnnotation(BundleUtilities.PROBABILISTIC_ANNOTATION_PROPERTY, factory.getOWLLiteral(description.getAccuracy()));
300     OWLSubClassOfAxiom axiom = factory.
301     getOWLSubClassOfAxiom(ce, dummyClass, Collections.singleton(annotation));
302     axioms.add(axiom);
303     }
304     return axioms;
305     }
306
307     protected LinkedHashSet<OWLEquivalentClassesAxiom> convertIntoEquivalentClassesAxioms(OWLOntologyManager manager, NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions) {
308     LinkedHashSet<OWLEquivalentClassesAxiom> axioms = new LinkedHashSet<>(evaluatedDescriptions.size());
309     OWLDataFactory factory = manager.getOWLDataFactory();
310     for (EvaluatedDescription description : evaluatedDescriptions.descendingSet()) {
311     OWLClassExpression ce = (OWLClassExpression) description.getDescription();
312     ce = OWLClassExpressionSimplifierVisitorImpl.getOWLClassExpression(ce, manager);
313     OWLAnnotation annotation = factory.
314     getOWLAnnotation(BundleUtilities.PROBABILISTIC_ANNOTATION_PROPERTY, factory.getOWLLiteral(description.getAccuracy()));
315
316     OWLEquivalentClassesAxiom axiom = factory.getOWLEquivalentClassesAxiom(ce, dummyClass, Collections.singleton(annotation));
317     axioms.add(axiom);
318
319     }
320     return axioms;
321     }
322     */
323    /**
324     *
325     * @param finalOntology
326     * @param learnedAxioms
327     * @return
328     */
329    protected OWLOntology replaceDummyClass(OWLOntology finalOntology, Set<OWLAxiom> learnedAxioms) throws UnsupportedLearnedAxiom {
330        logger.debug("Replacing super class \"dummyClass\" with \"classToDescribe\"");
331        ClassLearningProblem clp = (ClassLearningProblem) cela.getLearningProblem();
332        OWLOntologyManager man = finalOntology.getOWLOntologyManager();
333        OWLDataFactory df = man.getOWLDataFactory();
334        int numInitialAxioms = finalOntology.getLogicalAxiomCount();
335        // remove the learned Axioms
336        //man.removeAxiom(finalOntology, learnedAxioms.iterator().next());
337        Set<OWLAxiom> learnedAxiomsCopy = new LinkedHashSet<>(learnedAxioms);
338        for (OWLAxiom axiom : finalOntology.getLogicalAxioms(Imports.EXCLUDED)) {
339            for (OWLAxiom axiomToRemove : learnedAxiomsCopy) {
340                // conviene usare una copia di probAddedAxioms 
341                //in maniera tale da eliminare gli assiomi gi� trovati durante la ricerca e 
342                //quindi ridurre il numero di check
343                //logger.debug("Learned axiom to remove: " + BundleUtilities.getManchesterSyntaxString(axiomToRemove));
344                if (axiomToRemove.equalsIgnoreAnnotations(axiom)) {
345                    man.removeAxiom(finalOntology, axiom);
346                    learnedAxiomsCopy.remove(axiomToRemove);
347                    break;
348                }
349            }
350        }
351        int numAxiomsAfterRemove = finalOntology.getLogicalAxiomCount();
352        // check if correctly removed
353        if (numAxiomsAfterRemove != numInitialAxioms - learnedAxioms.size()) {
354            String msg = "Error during the replacement of super class: "
355                    + "Axiom remotion was incorrect. "
356                    + "numAxiomsAfterRemove: " + numAxiomsAfterRemove
357                    + " numInitialAxioms: " + numInitialAxioms
358                    + " numAxioms to remove: " + learnedAxioms.size()
359                    + " numAxioms removed: " + (numInitialAxioms - numAxiomsAfterRemove);
360            logger.error(msg);
361            throw new StructureLearningException(msg);
362        }
363        LinkedHashSet<OWLAxiom> newAxioms = new LinkedHashSet<>();
364        for (OWLAxiom axiom : learnedAxioms) {
365            OWLAxiom newAxiom;
366            if (axiom.isOfType(AxiomType.SUBCLASS_OF)) {
367                newAxiom = df.getOWLSubClassOfAxiom(
368                        ((OWLSubClassOfAxiom) axiom).getSubClass(),
369                        clp.getClassToDescribe(),
370                        axiom.getAnnotations());
371            } else if (axiom.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
372                OWLClassExpression clazz = null;
373                for (OWLClassExpression c : ((OWLEquivalentClassesAxiom) axiom).getClassExpressions()) {
374
375                    if (c.compareTo(getDummyClass()) != 0) {
376                        clazz = c;
377                        break;
378                    }
379                }
380                if (clazz == null) {
381                    throw new UnsupportedLearnedAxiom("The learned axiom " + axiom
382                            + "has a null class");
383                }
384                newAxiom = df.getOWLEquivalentClassesAxiom(
385                        clazz,
386                        clp.getClassToDescribe(),
387                        axiom.getAnnotations());
388            } else {
389                throw new UnsupportedLearnedAxiom("The learned axiom " + axiom
390                        + "is not supported");
391            }
392            newAxioms.add(newAxiom);
393            logger.info("Learned Axiom: " + newAxiom);
394        }
395        man.addAxioms(finalOntology, newAxioms);
396        // check if correctly added
397        if (numInitialAxioms != numAxiomsAfterRemove + learnedAxioms.size()) {
398            String msg = "Error during the replacement of super class: "
399                    + "Axiom addition was incorrect."
400                    + " numAxiomsAfterRemove: " + numAxiomsAfterRemove
401                    + " numAxioms to add: " + learnedAxioms.size()
402                    + " numAxioms added: " + (numInitialAxioms - numAxiomsAfterRemove);;
403            logger.error(msg);
404            throw new StructureLearningException(msg);
405        }
406        logger.debug("Replaced all the super classes");
407        return finalOntology;
408    }
409
410    /**
411     * It tries to add a set of axioms into the ontology. If there is an inconsistency
412     * after adding the axiom the axiom is removed from the ontology and an
413     * InconsistencyException is thrown.
414     *
415     * @param ontology ontology to modify
416     * @param axioms axioms to add
417     * @throws InconsistencyException if adding the exceptions leads to an
418     * inconsistency
419     */
420    protected void addAxioms(OWLOntology ontology, List<? extends OWLAxiom> axioms) throws InconsistencyException {
421        OWLOntologyManager manager = ontology.getOWLOntologyManager();
422        manager.addAxioms(ontology, new HashSet<>(axioms));
423//        PelletReasoner pelletReasoner = new PelletReasonerFactory().createReasoner(ontology);
424        PelletReasoner pelletReasoner = PelletReasonerFactory.getInstance().createNonBufferingReasoner(ontology);
425        if (!pelletReasoner.isConsistent()) {
426            String message = "The axiom will make the KB inconsistent.\n"
427                    + "It will NOT be added";
428            logger.warn(message);
429            manager.removeAxioms(ontology, new HashSet<>(axioms));
430            pelletReasoner.dispose();
431            throw new InconsistencyException(message);
432        }
433        pelletReasoner.dispose();
434    }
435
436    protected void removeAxioms(OWLOntology ontology, List<? extends OWLAxiom> axioms) {
437        OWLOntologyManager manager = ontology.getOWLOntologyManager();
438        manager.removeAxioms(ontology, new HashSet<>(axioms));
439    }
440
441    /**
442     * @return the classAxiomType
443     */
444    public String getClassAxiomType() {
445        return classAxiomType;
446    }
447
448    /**
449     * @param classAxiomType the classAxiomType to set
450     */
451    public void setClassAxiomType(String classAxiomType) {
452        this.classAxiomType = classAxiomType;
453    }
454
455    /**
456     * @param maxExecutionTimeInSeconds the maxExecutionTimeInSeconds to set
457     */
458    public void setMaxExecutionTimeInSeconds(int maxExecutionTimeInSeconds) {
459        this.maxExecutionTimeInSeconds = maxExecutionTimeInSeconds;
460    }
461
462    /**
463     * @param blockSizeGreedySearch the blockSizeGreedySearch to set
464     */
465    public void setBlockSizeGreedySearch(int blockSizeGreedySearch) {
466        this.blockSizeGreedySearch = blockSizeGreedySearch;
467    }
468
469}