001    /**
002     * Copyright (C) 2007-2008, 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     *
019     */
020    
021    package org.dllearner.algorithms.refinement2;
022    
023    import java.io.File;
024    import java.util.Collection;
025    import java.util.LinkedList;
026    import java.util.List;
027    import java.util.Set;
028    import java.util.SortedSet;
029    
030    import org.apache.log4j.Level;
031    import org.apache.log4j.Logger;
032    import org.dllearner.core.ComponentInitException;
033    import org.dllearner.core.LearningAlgorithm;
034    import org.dllearner.core.LearningProblem;
035    import org.dllearner.core.ReasonerComponent;
036    import org.dllearner.core.configurators.ROLComponent2Configurator;
037    import org.dllearner.core.options.BooleanConfigOption;
038    import org.dllearner.core.options.CommonConfigMappings;
039    import org.dllearner.core.options.CommonConfigOptions;
040    import org.dllearner.core.options.ConfigEntry;
041    import org.dllearner.core.options.ConfigOption;
042    import org.dllearner.core.options.DoubleConfigOption;
043    import org.dllearner.core.options.IntegerConfigOption;
044    import org.dllearner.core.options.InvalidConfigOptionValueException;
045    import org.dllearner.core.options.StringConfigOption;
046    import org.dllearner.core.owl.ClassHierarchy;
047    import org.dllearner.core.owl.Description;
048    import org.dllearner.core.owl.NamedClass;
049    import org.dllearner.core.owl.ObjectProperty;
050    import org.dllearner.learningproblems.EvaluatedDescriptionPosNeg;
051    import org.dllearner.learningproblems.PosNegLP;
052    import org.dllearner.learningproblems.PosOnlyLP;
053    import org.dllearner.learningproblems.ScorePosNeg;
054    import org.dllearner.reasoning.ReasonerType;
055    import org.dllearner.refinementoperators.RhoDRDown;
056    import org.dllearner.utilities.Files;
057    import org.dllearner.utilities.Helper;
058    
059    /**
060     * The DL-Learner learning algorithm component for the example
061     * based refinement operator approach. It handles all 
062     * configuration options, creates the corresponding objects and
063     * passes them to the actual refinement operator, heuristic, and
064     * learning algorithm implementations.
065     * 
066     * Note: The options supported by the ROLearner component and this
067     * one are not equal. Options that have been dropped for now:
068     * - horizontal expansion factor: The goal of the algorithm will
069     *     be to (hopefully) be able to learn long and complex concepts
070     *     more efficiently.
071     *     A horizontal expansion factor has its benefits, but limits
072     *     the length of concepts learnable in reasonable time to
073     *     about 15 with its default value of 0.6 and a small sized
074     *     background knowledge base. We hope to get more fine-grained
075     *     control of whether it makes sense to extend a node with
076     *     more sophisticated heuristics.
077     *     Dropping the horizontal expansion factor means that the
078     *     completeness of the algorithm depends on the heuristic.
079     * 
080     * @author Jens Lehmann
081     *
082     */
083    public class ROLComponent2 extends LearningAlgorithm {
084            
085            private ROLComponent2Configurator configurator;
086            @Override
087            public ROLComponent2Configurator getConfigurator(){
088                    return configurator;
089            }
090            
091            // actual algorithm
092            private ROLearner2 algorithm;
093            private static Logger logger = Logger
094                    .getLogger(ROLComponent2.class);
095            private String logLevel = CommonConfigOptions.logLevelDefault;  
096                    
097            // configuration options
098            private boolean writeSearchTree;
099            private File searchTreeFile;
100            private boolean replaceSearchTree = false;
101            private static String defaultSearchTreeFile = "log/searchTree.txt";
102            private String heuristic = "multi";
103            Set<NamedClass> allowedConcepts;
104            Set<ObjectProperty> allowedRoles;
105            Set<NamedClass> ignoredConcepts;
106            Set<ObjectProperty> ignoredRoles;
107            // these are computed as the result of the previous four settings
108            Set<NamedClass> usedConcepts;
109            Set<ObjectProperty> usedRoles;    
110            private boolean applyAllFilter = true;
111            private boolean applyExistsFilter = true;       
112            private boolean useTooWeakList = true;
113            private boolean useOverlyGeneralList = true;
114            private boolean useShortConceptConstruction = true;
115            private boolean improveSubsumptionHierarchy = true;
116            private boolean useAllConstructor = CommonConfigOptions.useAllConstructorDefault;
117            private boolean useExistsConstructor = CommonConfigOptions.useExistsConstructorDefault;
118            private boolean useHasValueConstructor = CommonConfigOptions.useHasValueConstructorDefault;
119            private int valueFrequencyThreshold = CommonConfigOptions.valueFrequencyThresholdDefault;
120            private boolean useCardinalityRestrictions = CommonConfigOptions.useCardinalityRestrictionsDefault;
121            private boolean useNegation = CommonConfigOptions.useNegationDefault;
122            private boolean useBooleanDatatypes = CommonConfigOptions.useBooleanDatatypesDefault;
123            private boolean useDoubleDatatypes = CommonConfigOptions.useDoubleDatatypesDefault;
124            private static double noisePercentageDefault = 0.0;
125            private double noisePercentage = noisePercentageDefault;
126            private NamedClass startClass = null;
127            //refactor this
128            private static boolean usePropernessChecksDefault = false;
129            private boolean usePropernessChecks = usePropernessChecksDefault;
130            //      refactor this
131            private static int maxPosOnlyExpansionDefault = 4;
132            private int maxPosOnlyExpansion = maxPosOnlyExpansionDefault;
133            private boolean forceRefinementLengthIncrease = true;
134            //extended Options
135            //in seconds
136            private int maxExecutionTimeInSeconds = CommonConfigOptions.maxExecutionTimeInSecondsDefault;
137            private int minExecutionTimeInSeconds = CommonConfigOptions.minExecutionTimeInSecondsDefault;
138            private int guaranteeXgoodDescriptions = CommonConfigOptions.guaranteeXgoodDescriptionsDefault;
139            private int maxClassDescriptionTests = CommonConfigOptions.maxClassDescriptionTestsDefault;
140            
141            // Variablen zur Einstellung der Protokollierung
142            // boolean quiet = false;
143            boolean showBenchmarkInformation = false;
144            // boolean createTreeString = false;
145            // String searchTree = new String();
146    
147            // Konfiguration des Algorithmus
148            // Faktor für horizontale Erweiterung (notwendig für completeness)
149            // double horizontalExpansionFactor = 0.6;      
150    
151            // soll später einen Operator und eine Heuristik entgegennehmen
152            // public ROLearner(LearningProblem learningProblem, LearningProblem learningProblem2) {
153            public ROLComponent2(PosNegLP learningProblem, ReasonerComponent reasoningService) {
154                    super(learningProblem, reasoningService);
155                    this.configurator = new ROLComponent2Configurator(this);
156            }
157            
158            public ROLComponent2(PosOnlyLP learningProblem, ReasonerComponent reasoningService) {
159                    super(learningProblem, reasoningService);
160                    this.configurator = new ROLComponent2Configurator(this);
161            }
162            
163            public static Collection<Class<? extends LearningProblem>> supportedLearningProblems() {
164                    Collection<Class<? extends LearningProblem>> problems = new LinkedList<Class<? extends LearningProblem>>();
165                    problems.add(PosNegLP.class);
166                    problems.add(PosOnlyLP.class);
167                    return problems;
168            }
169            
170            public static Collection<ConfigOption<?>> createConfigOptions() {
171                    Collection<ConfigOption<?>> options = new LinkedList<ConfigOption<?>>();
172                    options.add(new BooleanConfigOption("writeSearchTree", "specifies whether to write a search tree", false));
173                    options.add(new StringConfigOption("searchTreeFile","file to use for the search tree", defaultSearchTreeFile));
174                    options.add(new BooleanConfigOption("replaceSearchTree","specifies whether to replace the search tree in the log file after each run or append the new search tree", false));
175                    StringConfigOption heuristicOption = new StringConfigOption("heuristic", "specifiy the heuristic to use", "lexicographic");
176                    heuristicOption.setAllowedValues(new String[] {"lexicographic", "flexible"});
177                    options.add(heuristicOption);
178                    options.add(new BooleanConfigOption("applyAllFilter", "usage of equivalence ALL R.C AND ALL R.D = ALL R.(C AND D)", true));
179                    options.add(new BooleanConfigOption("applyExistsFilter", "usage of equivalence EXISTS R.C OR EXISTS R.D = EXISTS R.(C OR D)", true));
180                    options.add(new BooleanConfigOption("useTooWeakList", "try to filter out too weak concepts without sending them to the reasoner", true));
181                    options.add(new BooleanConfigOption("useOverlyGeneralList", "try to find overly general concept without sending them to the reasoner", true));
182                    options.add(new BooleanConfigOption("useShortConceptConstruction", "shorten concept to see whether they already exist", true));
183                    DoubleConfigOption horizExp = new DoubleConfigOption("horizontalExpansionFactor", "horizontal expansion factor (see publication for description)", 0.6);
184                    horizExp.setLowerLimit(0.0);
185                    horizExp.setUpperLimit(1.0);
186                    options.add(horizExp);
187                    options.add(new BooleanConfigOption("improveSubsumptionHierarchy", "simplify subsumption hierarchy to reduce search space (see publication for description)", true));
188                    // allowed/ignored concepts/roles could also be a reasoner option (?)
189                    options.add(CommonConfigOptions.allowedConcepts());
190                    options.add(CommonConfigOptions.ignoredConcepts());
191                    options.add(CommonConfigOptions.allowedRoles());
192                    options.add(CommonConfigOptions.ignoredRoles());
193                    options.add(CommonConfigOptions.useAllConstructor());
194                    options.add(CommonConfigOptions.useExistsConstructor());
195                    options.add(CommonConfigOptions.useHasValueConstructor());
196                    options.add(CommonConfigOptions.valueFreqencyThreshold());
197                    options.add(CommonConfigOptions.useCardinalityRestrictions());
198                    options.add(CommonConfigOptions.cardinalityLimit());
199                    options.add(CommonConfigOptions.useNegation());
200                    options.add(CommonConfigOptions.useBooleanDatatypes());
201                    options.add(CommonConfigOptions.useDoubleDatatypes());
202                    options.add(CommonConfigOptions.maxExecutionTimeInSeconds());
203                    options.add(CommonConfigOptions.minExecutionTimeInSeconds());
204                    options.add(CommonConfigOptions.guaranteeXgoodDescriptions());
205                    options.add(CommonConfigOptions.maxClassDescriptionTests());
206                    options.add(CommonConfigOptions.getLogLevel());
207                    options.add(new BooleanConfigOption("usePropernessChecks", "specifies whether to check for equivalence (i.e. discard equivalent refinements)",usePropernessChecksDefault));
208                    options.add(new IntegerConfigOption("maxPosOnlyExpansion", "specifies how often a node in the search tree of a posonly learning problem needs to be expanded before it is" +
209                                    " considered as solution candidate",maxPosOnlyExpansionDefault));
210                    options.add(CommonConfigOptions.getNoisePercentage());
211                    options.add(CommonConfigOptions.getTerminateOnNoiseReached());
212                    options.add(new StringConfigOption("startClass", "the named class which should be used to start the algorithm (GUI: needs a widget for selecting a class)"));
213                    options.add(new BooleanConfigOption("forceRefinementLengthIncrease", "specifies whether nodes should be expanded until only longer refinements are reached"));
214                    options.add(new DoubleConfigOption("negativeWeight", "Used to penalise errors on negative examples different from those of positive examples (lower = less importance for negatives).",1.0));
215                    options.add(new DoubleConfigOption("startNodeBonus", "You can use this to give a heuristic bonus on the start node (= initially broader exploration of search space).",0.0));
216                    options.add(new IntegerConfigOption("negationPenalty", "Penalty on negations (TODO: better explanation).", 0));
217                    options.add(CommonConfigOptions.getExpansionPenaltyFactor(0.02));
218                    return options;
219            }
220            
221            /* (non-Javadoc)
222             * @see org.dllearner.core.Component#applyConfigEntry(org.dllearner.core.ConfigEntry)
223             */
224            @Override
225            @SuppressWarnings({"unchecked"})
226            public <T> void applyConfigEntry(ConfigEntry<T> entry) throws InvalidConfigOptionValueException {
227                    String name = entry.getOptionName();
228                    if(name.equals("writeSearchTree"))
229                            writeSearchTree = (Boolean) entry.getValue();
230                    else if(name.equals("searchTreeFile"))
231                            searchTreeFile = new File((String)entry.getValue());
232                    else if(name.equals("replaceSearchTree"))
233                            replaceSearchTree = (Boolean) entry.getValue();
234                    else if(name.equals("heuristic")) {
235                            String value = (String) entry.getValue();
236                            if(value.equals("lexicographic"))
237                                    heuristic = "lexicographic";
238                            else
239                                    heuristic = "flexible";
240                    } else if(name.equals("allowedConcepts")) {
241                            allowedConcepts = CommonConfigMappings.getAtomicConceptSet((Set<String>)entry.getValue());
242                    } else if(name.equals("allowedRoles")) {
243                            allowedRoles = CommonConfigMappings.getAtomicRoleSet((Set<String>)entry.getValue());
244                    } else if(name.equals("ignoredConcepts")) {
245                            ignoredConcepts = CommonConfigMappings.getAtomicConceptSet((Set<String>)entry.getValue());
246                    } else if(name.equals("ignoredRoles")) {
247                            ignoredRoles = CommonConfigMappings.getAtomicRoleSet((Set<String>)entry.getValue());
248                    } else if(name.equals("applyAllFilter")) {
249                            applyAllFilter = (Boolean) entry.getValue();
250                    } else if(name.equals("applyExistsFilter")) {
251                            applyExistsFilter = (Boolean) entry.getValue();
252                    } else if(name.equals("useTooWeakList")) {
253                            useTooWeakList = (Boolean) entry.getValue();
254                    } else if(name.equals("useOverlyGeneralList")) {
255                            useOverlyGeneralList = (Boolean) entry.getValue();
256                    } else if(name.equals("useShortConceptConstruction")) {
257                            useShortConceptConstruction = (Boolean) entry.getValue();
258                    } else if(name.equals("improveSubsumptionHierarchy")) {
259                            improveSubsumptionHierarchy = (Boolean) entry.getValue();
260                    } else if(name.equals("useAllConstructor")) {
261                            useAllConstructor = (Boolean) entry.getValue();
262                    } else if(name.equals("useExistsConstructor")) {
263                            useExistsConstructor = (Boolean) entry.getValue();
264                    } else if(name.equals("useHasValueConstructor")) {
265                            useHasValueConstructor = (Boolean) entry.getValue();
266                    } else if(name.equals("valueFrequencyThreshold")) {
267                            valueFrequencyThreshold = (Integer) entry.getValue();
268                    } else if(name.equals("useCardinalityRestrictions")) {
269                            useCardinalityRestrictions = (Boolean) entry.getValue();
270                    } else if(name.equals("useNegation")) {
271                            useNegation = (Boolean) entry.getValue();
272                    } else if(name.equals("noisePercentage")) {
273                            noisePercentage = (Double) entry.getValue();
274                    } else if(name.equals("useBooleanDatatypes")) {
275                            useBooleanDatatypes = (Boolean) entry.getValue();
276                    } else if(name.equals("useDoubleDatatypes")) {
277                            useDoubleDatatypes = (Boolean) entry.getValue();
278                    } else if(name.equals("usePropernessChecks")) {
279                            usePropernessChecks = (Boolean) entry.getValue();
280                    } else if(name.equals("maxPosOnlyExpansion")) {
281                            maxPosOnlyExpansion = (Integer) entry.getValue();
282                    } else if(name.equals("startClass")) {
283                            startClass = new NamedClass((String)entry.getValue());
284                    }else if(name.equals("maxExecutionTimeInSeconds")) {
285                            maxExecutionTimeInSeconds = (Integer) entry.getValue();
286                    }else if(name.equals("minExecutionTimeInSeconds")) {
287                            minExecutionTimeInSeconds = (Integer) entry.getValue();
288                    }else if(name.equals("guaranteeXgoodDescriptions")) {
289                            guaranteeXgoodDescriptions =  (Integer) entry.getValue();
290                    } else if(name.equals("maxClassDescriptionTests")) {
291                            maxClassDescriptionTests =  (Integer) entry.getValue();
292                    } else if(name.equals("logLevel")) {
293                            logLevel = ((String)entry.getValue()).toUpperCase();
294                    } else if(name.equals("forceRefinementLengthIncrease")) {
295                            forceRefinementLengthIncrease = (Boolean) entry.getValue();
296                    }
297            }
298    
299            /* (non-Javadoc)
300             * @see org.dllearner.core.Component#init()
301             */
302            @Override
303            public void init() throws ComponentInitException {
304                    
305                    // exit with a ComponentInitException if the reasoner is unsupported for this learning algorithm
306                    if(reasoner.getReasonerType() == ReasonerType.DIG) {
307                            throw new ComponentInitException("DIG does not support the inferences needed in the selected learning algorithm component: " + getName());
308                    }
309                    
310                    // set log level if the option has been set
311                    if(!logLevel.equals(CommonConfigOptions.logLevelDefault))
312                            logger.setLevel(Level.toLevel(logLevel,Level.toLevel(CommonConfigOptions.logLevelDefault)));
313                    
314                    if(searchTreeFile == null)
315                            searchTreeFile = new File(defaultSearchTreeFile);
316    
317                    if(writeSearchTree)
318                            Files.clearFile(searchTreeFile);
319    
320                    // adjust heuristic
321                    ExampleBasedHeuristic algHeuristic;
322                    
323                    if(heuristic == "lexicographic")
324                            algHeuristic = new LexicographicHeuristic();
325                    else if(heuristic == "flexible") {
326                            if(learningProblem instanceof PosOnlyLP) {
327                                    throw new RuntimeException("does not work with positive examples only yet");
328                            }
329                            algHeuristic = new FlexibleHeuristic(((PosNegLP)learningProblem).getNegativeExamples().size(), ((PosNegLP)learningProblem).getPercentPerLengthUnit());
330                    } else {
331                            if(learningProblem instanceof PosOnlyLP) {
332    //                              throw new RuntimeException("does not work with positive examples only yet");
333                                    algHeuristic = new MultiHeuristic(((PosOnlyLP)learningProblem).getPositiveExamples().size(),0, configurator);
334                            } else {
335                                    algHeuristic = new MultiHeuristic(((PosNegLP)learningProblem).getPositiveExamples().size(),((PosNegLP)learningProblem).getNegativeExamples().size(), configurator);
336                            }
337                    }
338                    
339                    // compute used concepts/roles from allowed/ignored
340                    // concepts/roles
341                    if(allowedConcepts != null) {
342                            // sanity check to control if no non-existing concepts are in the list
343                            Helper.checkConcepts(reasoner, allowedConcepts);
344                            usedConcepts = allowedConcepts;
345                    } else if(ignoredConcepts != null) {
346                            usedConcepts = Helper.computeConceptsUsingIgnoreList(reasoner, ignoredConcepts);
347                    } else {
348                            usedConcepts = Helper.computeConcepts(reasoner);
349                    }
350                    
351                    if(allowedRoles != null) {
352                            Helper.checkRoles(reasoner, allowedRoles);
353                            usedRoles = allowedRoles;
354                    } else if(ignoredRoles != null) {
355                            Helper.checkRoles(reasoner, ignoredRoles);
356                            usedRoles = Helper.difference(reasoner.getObjectProperties(), ignoredRoles);
357                    } else {
358                            usedRoles = reasoner.getObjectProperties();
359                    }
360                    
361                    // prepare subsumption and role hierarchies, because they are needed
362                    // during the run of the algorithm;
363                    // in contrast to before, the learning algorithms have to maintain their
364                    // own view on the class hierarchy
365                    ClassHierarchy classHierarchy = reasoner.getClassHierarchy().cloneAndRestrict(usedConcepts);
366                    if(improveSubsumptionHierarchy)
367                            classHierarchy.thinOutSubsumptionHierarchy();
368                    
369    //              reasoner.prepareRoleHierarchy(usedRoles);
370                    // prepare datatype hierarchy only if necessary
371    //              if(reasoner.hasDatatypeSupport())
372    //                      reasoner.prepareDatatypePropertyHierarchy();
373                    
374                    // create a refinement operator and pass all configuration
375                    // variables to it
376                    RhoDRDown operator = new RhoDRDown(
377                                    reasoner,
378                                    classHierarchy,
379                                    configurator,
380                                            applyAllFilter,
381                                            applyExistsFilter,
382                                            useAllConstructor, 
383                                            useExistsConstructor,
384                                            useHasValueConstructor,
385                                            valueFrequencyThreshold,
386                                            useCardinalityRestrictions,
387                                            useNegation,
388                                            useBooleanDatatypes,
389                                            useDoubleDatatypes,
390                                            startClass
391                            );              
392                            
393                    // create an algorithm object and pass all configuration
394                    // options to it
395                    algorithm = new ROLearner2(
396                                    configurator,
397                                    learningProblem,
398                                    reasoner,
399                                    operator,
400                                    algHeuristic,
401                                    startClass,
402                                    // usedConcepts,
403                                    // usedRoles,
404                                    noisePercentage/(double)100,
405                                    writeSearchTree,
406                                    replaceSearchTree,
407                                    searchTreeFile,
408                                    useTooWeakList,
409                                    useOverlyGeneralList,
410                                    useShortConceptConstruction,
411                                    usePropernessChecks,
412                                    maxPosOnlyExpansion,
413                                    maxExecutionTimeInSeconds,
414                                    minExecutionTimeInSeconds,
415                                    guaranteeXgoodDescriptions,
416                                    maxClassDescriptionTests,
417                                    forceRefinementLengthIncrease
418                    );
419                    // note: used concepts and roles do not need to be passed
420                    // as argument, because it is sufficient to prepare the
421                    // concept and role hierarchy accordingly
422            }
423            
424            public static String getName() {
425                    return "refinement operator based learning algorithm II";
426            }
427            
428            public static String getUsage() {
429                    return "algorithm = refexamples;";
430            }
431            
432            @Override
433            public void start() {
434                    algorithm.start();
435            }
436            
437    //      @Override
438            public ScorePosNeg getSolutionScore() {
439                    return algorithm.getSolutionScore();
440            }
441            
442            @Override
443            public Description getCurrentlyBestDescription() {
444                    return algorithm.getBestSolution();
445            }
446            
447            @Override
448            public synchronized List<Description> getCurrentlyBestDescriptions() {
449                    return algorithm.getCurrentlyBestDescriptions();
450            }       
451            
452            @Override
453            public EvaluatedDescriptionPosNeg getCurrentlyBestEvaluatedDescription() {
454                    return new EvaluatedDescriptionPosNeg(algorithm.getBestSolution(),algorithm.getSolutionScore());
455            }
456            
457            @Override
458            public synchronized SortedSet<EvaluatedDescriptionPosNeg> getCurrentlyBestEvaluatedDescriptions() {
459                    return algorithm.getCurrentlyBestEvaluatedDescriptions();
460            }
461    
462            /** {@inheritDoc} */
463            @Override
464            public void stop() {
465                    algorithm.stop();
466            }
467    
468            public ExampleBasedNode getStartNode() {
469                    return algorithm.getStartNode();
470            }
471    
472            /** {@inheritDoc} */
473            @Override
474            public void pause() {
475                    // TODO: not implemented
476            }
477    
478            /** {@inheritDoc} */
479            @Override
480            public void resume() {
481                    // TODO: not implemented
482            }
483    
484            /* (non-Javadoc)
485             * @see org.dllearner.core.LearningAlgorithm#isRunning()
486             */
487            /** {@inheritDoc} */
488            @Override
489            public boolean isRunning() {
490                    return algorithm.isRunning();
491            }
492            
493    }