001package org.dllearner.algorithms.probabilistic.structure.unife.leap;
002
003import java.math.BigDecimal;
004import java.math.RoundingMode;
005import java.util.Collections;
006import java.util.HashSet;
007import java.util.LinkedHashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.NavigableSet;
011import java.util.Set;
012import org.dllearner.algorithms.probabilistic.parameter.unife.edge.AbstractEDGE;
013import org.dllearner.core.AbstractCELA;
014import org.dllearner.core.ComponentAnn;
015import org.dllearner.core.ComponentInitException;
016import org.dllearner.core.EvaluatedDescription;
017import org.dllearner.learningproblems.ClassLearningProblem;
018import org.slf4j.Logger;
019import org.slf4j.LoggerFactory;
020import org.dllearner.core.probabilistic.unife.StructureLearningException;
021import org.dllearner.exceptions.UnsupportedLearnedAxiom;
022
023import org.dllearner.utils.unife.OWLUtils;
024import org.semanticweb.owlapi.apibinding.OWLManager;
025import org.semanticweb.owlapi.model.AxiomType;
026import org.semanticweb.owlapi.model.OWLAnnotation;
027import org.semanticweb.owlapi.model.OWLAxiom;
028import org.semanticweb.owlapi.model.OWLDataFactory;
029import org.semanticweb.owlapi.model.OWLEquivalentClassesAxiom;
030import org.semanticweb.owlapi.model.OWLOntology;
031import org.semanticweb.owlapi.model.OWLOntologyManager;
032import org.semanticweb.owlapi.model.OWLOntologyStorageException;
033import org.semanticweb.owlapi.model.OWLSubClassOfAxiom;
034import unife.bundle.exception.InconsistencyException;
035import unife.bundle.utilities.BundleUtilities;
036import unife.math.utilities.MathUtilities;
037import static unife.utilities.GeneralUtils.safe;
038
039@ComponentAnn(name = "LEAP", shortName = "leap", version = 1.0)
040public class LEAP extends AbstractLEAP {
041
042    private static final Logger logger = LoggerFactory.getLogger(LEAP.class);
043
044    public LEAP() {
045
046    }
047
048    public LEAP(AbstractCELA cela, AbstractEDGE lpr) {
049        super(cela, lpr);
050    }
051
052    @Override
053    public void init() throws ComponentInitException {
054        super.init();
055    }
056
057    @Override
058    public void start() {
059        stop = false;
060        isRunning = true;
061
062        long totalTimeMills = System.currentTimeMillis();
063        long celaTimeMills = 0;
064        //AbstractEDGEDistributed edge = (AbstractEDGEDistributed) pla;
065        // First step: run Distributed EDGE
066        //edge.start();
067
068//        logger.debug("First EDGE cycle terminated.");
069        //logger.debug("Initial Log-likelihood: " + edge.getLL());
070        //OWLOntology originalOntology = edge.getLearnedOntology();
071        logger.debug("Starting structure learner LEAP");
072//            Set<KnowledgeSource> newSources = Collections.singleton((KnowledgeSource) new OWLAPIOntology(ontology));
073//            AbstractReasonerComponent reasoner = cela.getReasoner();
074//            reasoner.changeSources(newSources);
075//            try {
076//                reasoner.init();
077//                cela.init();
078//            } catch (ComponentInitException cie) {
079//                logger.error("Error: " + cie.getMessage());
080//                throw new StructureLearningException(cie);
081//            }
082        // start class expression learning algorithm
083        celaTimeMills = System.currentTimeMillis();
084        cela.start();
085        cela.getReasoner().releaseKB();
086        celaTimeMills = System.currentTimeMillis() - celaTimeMills;
087        // get the best class expressions
088        NavigableSet<? extends EvaluatedDescription> evaluatedDescriptions = cela.getCurrentlyBestEvaluatedDescriptions();
089        // convert the class expressions into axioms
090//        OWLOntologyManager manager = OWLManager.createOWLOntologyManager();
091        OWLOntologyManager manager = edge.getSourcesOntology().getOWLOntologyManager();
092        List<? extends OWLAxiom> candidateAxioms;
093        if (getClassAxiomType().equalsIgnoreCase("subClassOf") || getClassAxiomType().equalsIgnoreCase("both")) {
094            candidateAxioms = convertIntoSubClassOfAxioms(manager, evaluatedDescriptions);
095        } else {
096            candidateAxioms = convertIntoEquivalentClassesAxioms(manager, evaluatedDescriptions);
097        }
098        // perform a greedy search 
099        logger.info("Start greedy search");
100        // temporaneo i raffinamenti dopo dovranno essere assegnati ad ogni processo
101        Set<OWLAxiom> learnedAxioms = null;
102        try {
103            learnedAxioms = greedySearch(candidateAxioms);
104        } catch (UnsupportedLearnedAxiom ex) {
105            logger.error(ex.getMessage());
106            System.exit(-1);
107        }
108        logger.info("Greedy search finished");
109
110        OWLOntology finalOntology = edge.getSourcesOntology();
111        // In case replace super class
112        if (cela.getLearningProblem() instanceof ClassLearningProblem) {
113            try {
114                finalOntology = replaceDummyClass(finalOntology, learnedAxioms);
115            } catch (UnsupportedLearnedAxiom ex) {
116                logger.error(ex.getMessage());
117                System.exit(-1);
118            }
119        } else {
120            for (OWLAxiom axiom : safe(learnedAxioms)) {
121                logger.info("Learned Axiom: " + axiom);
122            }
123        }
124        // final step save the ontology
125        try {
126            logger.info("Saving the learned ontology");
127            OWLUtils.saveOntology(finalOntology, outputFile, outFormat);
128        } catch (OWLOntologyStorageException e) {
129            String msg = "Cannot save the learned ontology: " + e.getMessage();
130            throw new StructureLearningException(msg);
131        }
132        totalTimeMills = System.currentTimeMillis() - totalTimeMills;
133        printTimings(totalTimeMills, celaTimeMills, timers);
134    }
135
136    /**
137     * Returns the name of the algorithm.
138     *
139     * @return "LEAP"
140     */
141    public static String getName() {
142        return "LEAP";
143    }
144
145    /**
146     * This method performs a greedy search in the space of Theories. Given a
147     * set of axioms it executes a loop for each axiom. For each iteration it
148     * adds an axiom into the knowledge base and if the log-likelihood (LL)
149     * increases the axiom is kept otherwise it is removed from the ontology.
150     * For EquivalentClassesAxiom: if we keep an EquivalentClassesAxiom because
151     * the LL has increased but a SubClassOfAxiom with the same classes has been
152     * already added, the SubClassOfAxiom is removed.
153     *
154     * @param candidateAxioms the set of candidate axiom that we would like to
155     * add in the knowledge base.
156     * @return the set of axioms added in the knowledge base.
157     */
158    private Set<OWLAxiom> greedySearch(List<? extends OWLAxiom> candidateAxioms) throws UnsupportedLearnedAxiom {
159        BigDecimal bestLL;
160        if (edge instanceof AbstractEDGE) {
161            bestLL = ((AbstractEDGE) edge).getLOGZERO().multiply(
162                    new BigDecimal(edge.getPositiveExampleAxioms().size()));
163            bestLL = bestLL.setScale(accuracy, RoundingMode.HALF_UP);
164        } else {
165            bestLL = BigDecimal.ZERO.multiply(
166                    new BigDecimal(edge.getPositiveExampleAxioms().size()));
167            bestLL = bestLL.setScale(accuracy, RoundingMode.HALF_UP);
168        }
169        logger.debug("Initial Log-likelihood: " + bestLL.toString());
170//        BigDecimal bestLL = edge.getLL();
171//        logger.debug("Resetting EDGE");
172//        edge.reset();
173        OWLOntology ontology = edge.getLearnedOntology();
174        edge.changeSourcesOntology(ontology); // queste operazioni fanno perdere tempo, sono da ottimizzare
175        LinkedHashSet<OWLAxiom> learnedAxioms = new LinkedHashSet<>();
176        OWLDataFactory df = ontology.getOWLOntologyManager().getOWLDataFactory();
177        String infoMsg = "Type of axiom to learn: ";
178        switch (getClassAxiomType().toLowerCase()) {
179            case "subclassof":
180                infoMsg += "subClassOf axioms";
181                break;
182            case "equivalentclasses":
183                infoMsg += "equivalentClasses axioms";
184                break;
185            case "both":
186                throw new UnsupportedLearnedAxiom("LEAP cannot learn this type of axioms: " + getClassAxiomType());
187//                infoMsg += "subClassOf and equivalentClasses axioms";
188//                break;
189            default:
190                throw new UnsupportedLearnedAxiom("LEAP cannot learn this type of axioms: " + getClassAxiomType());
191        }
192        logger.info(infoMsg);
193//        int i = 0;
194        int numChunks = (int) Math.ceil((double) candidateAxioms.size() / blockSizeGreedySearch);
195        logger.info("number of axiom chunks: " + numChunks);
196//        for (OWLAxiom axiom : candidateAxioms) {
197        for (int i = 0; i < numChunks; i++) {
198            int lastIndex = i < numChunks - 1 ? (i + 1) * blockSizeGreedySearch : candidateAxioms.size();
199            List<? extends OWLAxiom> axioms = candidateAxioms.subList(i * blockSizeGreedySearch, lastIndex);
200            if (i >= 0) {
201                for (OWLAxiom axiom : axioms) {
202                    logger.info("Adding axiom: " + axiom);
203                }
204                try {
205                    addAxioms(ontology, axioms);
206                } catch (InconsistencyException iex) {
207                    logger.info(iex.getMessage());
208                    logger.info("Trying with the next class expression");
209                    continue;
210                }
211                logger.info("Running parameter learner");
212                edge.start();
213                BigDecimal currLL = edge.getLL();
214                logger.info("Current Log-Likelihood: " + currLL);
215                if (getClassAxiomType().equalsIgnoreCase("both")) {
216                    // Not supported yet
217                    // TO DO: I need to save the probabilistic values before adding 
218                    // the equivalentClasses axiom. Because if I add the equivalentClasses axiom
219                    // and the resulting Log-likelihood is worse than adding the 
220                    // subClassOf axiom, then I need to recover the previously 
221                    // computated probabilistic values
222                    // (without running EDGE again)
223                    throw new UnsupportedOperationException("Not supported yet.");
224                    /* To uncomment in the future after the modifications of EDGE
225                     logger.info("Trying to add the corresponding equivalent axiom");
226                     logger.debug("but first I remove the axiom: " + axiom);
227                     removeAxiom(ontology, axiom);
228                     // here i need to sava the probabilistic values. A solution could be
229                     // to modify EDGE. It should update the PMap object when it learns the
230                     // parameters and EDGE should read it when the learned ontology
231                     // is requested
232                     OWLEquivalentClassesAxiom equivAxiom = OWLUtils.convertSubClassOfIntoEquivalentClassesAxiom((OWLSubClassOfAxiom) axiom);
233                     logger.debug("Adding axiom: " + equivAxiom);
234                     try {
235                     addAxiom(ontology, axiom);
236                     } catch (InconsistencyException iex) {
237                     logger.info(iex.getMessage());
238                     logger.info("Trying with the next class expression");
239                     continue;
240                     }
241                     logger.info("Running parameter learner");
242                     edge.start();
243                     BigDecimal currLL2 = edge.getLL();
244                     // if adding the equivalentClasses axioms leads to better log-likelihood I keep it
245                     if (currLL2.compareTo(currLL) > 0) {
246                     currLL = currLL2;
247                     axiom = equivAxiom;
248                     } else {
249                     removeAxiom(ontology, equivAxiom);
250                     ontology.getOWLOntologyManager().addAxiom(ontology, axiom);
251                     }*/
252                }
253                if (currLL.compareTo(bestLL) > 0) {
254                    logger.info("Log-Likelihood enhanced. Updating ontologies...");
255                    // I recover the annotation containing the learned probabilistic values
256
257                    for (OWLAxiom axiom : axioms) {
258                        OWLAxiom updatedAxiom;
259
260                        OWLAnnotation annotation = df.
261                                getOWLAnnotation(BundleUtilities.PROBABILISTIC_ANNOTATION_PROPERTY,
262                                        df.getOWLLiteral(edge.getParameter(axiom).doubleValue()));
263
264                        if (axiom.isOfType(AxiomType.SUBCLASS_OF)) {
265                            updatedAxiom = df.getOWLSubClassOfAxiom(
266                                    ((OWLSubClassOfAxiom) axiom).getSubClass(),
267                                    ((OWLSubClassOfAxiom) axiom).getSuperClass(),
268                                    Collections.singleton(annotation));
269                        } else {
270                            if (axiom.isOfType(AxiomType.EQUIVALENT_CLASSES)) {
271
272                                // I have to remove the subsumption a
273//                        for (OWLClass subClass : ((OWLEquivalentClassesAxiom) axiom).getNamedClasses()) {
274//                            if (subClass.compareTo(getDummyClass()) != 0) {
275////                                subClass = c;
276//                                for (OWLAxiom lax : learnedAxioms) {
277//                                    
278//                                    if (lax.isOfType(AxiomType.SUBCLASS_OF)
279//                                            && ((OWLSubClassOfAxiom) lax).getSubClass().compareTo(subClass) == 0) {
280//                                        logger.in removeAxiom(ontology, lax
281//                                        );
282//                                        learnedAxioms.remove(lax);
283//                                        break;
284//                                    }
285//                                }
286//                                break;
287//                            }
288//                        }
289                                updatedAxiom = df.getOWLEquivalentClassesAxiom(
290                                        ((OWLEquivalentClassesAxiom) axiom).getClassExpressions(),
291                                        Collections.singleton(annotation));
292                            } else {
293                                throw new UnsupportedLearnedAxiom("The axiom to add is not supported: "
294                                        + BundleUtilities.getManchesterSyntaxString(axiom));
295                            }
296                        }
297                        learnedAxioms.add(updatedAxiom);
298                    }
299                    updateOntology(); // queste operazioni fanno perdere tempo, sono da ottimizzare
300                    bestLL = currLL;
301                } else {
302                    logger.info("Log-Likelihood worsened. Removing Last Axioms...");
303                    removeAxioms(ontology, axioms);
304                }
305                for (Map.Entry<String, Long> timer : edge.getTimeMap().entrySet()) {
306                    Long previousValue = timers.get(timer.getKey());
307                    if (previousValue == null) {
308                        previousValue = 0L;
309                    }
310                    timers.put(timer.getKey(), previousValue + timer.getValue());
311                }
312            }
313        }
314        return learnedAxioms;
315    }
316
317    private void updateOntology() {
318        logger.debug("Updating ontology");
319        OWLOntology ontology = edge.getLearnedOntology();
320        edge.changeSourcesOntology(ontology);
321        logger.debug("Ontology Updated");
322    }
323
324}