001/* Copyright (C) 2013 TU Dortmund
002 * This file is part of LearnLib, http://www.learnlib.de/.
003 * 
004 * LearnLib is free software; you can redistribute it and/or
005 * modify it under the terms of the GNU Lesser General Public
006 * License version 3.0 as published by the Free Software Foundation.
007 * 
008 * LearnLib is distributed in the hope that it will be useful,
009 * but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
011 * Lesser General Public License for more details.
012 * 
013 * You should have received a copy of the GNU Lesser General Public
014 * License along with LearnLib; if not, see
015 * <http://www.gnu.de/documents/lgpl.en.html>.
016 */
017package de.learnlib.algorithms.lstargeneric;
018
019import java.util.ArrayList;
020import java.util.List;
021import java.util.Objects;
022
023import net.automatalib.words.Alphabet;
024import net.automatalib.words.Word;
025import de.learnlib.algorithms.lstargeneric.ce.ObservationTableCEXHandlers;
026import de.learnlib.algorithms.lstargeneric.table.Inconsistency;
027import de.learnlib.algorithms.lstargeneric.table.ObservationTable;
028import de.learnlib.algorithms.lstargeneric.table.Row;
029import de.learnlib.api.LearningAlgorithm;
030import de.learnlib.api.MembershipOracle;
031import de.learnlib.oracles.DefaultQuery;
032
033/**
034 * An abstract base class for L*-style algorithms.
035 * 
036 * This class implements basic management features (table, alphabet, oracle) and
037 * the main loop of alternating completeness and consistency checks. It does not take
038 * care of choosing how to initialize the table and hypothesis construction.
039 * 
040 * @author Malte Isberner <malte.isberner@gmail.com>
041 *
042 * @param <A> automaton class.
043 * @param <I> input symbol class.
044 * @param <O> output class.
045 */
046public abstract class AbstractLStar<A, I, O> implements LearningAlgorithm<A, I, O> {
047        
048        protected final Alphabet<? extends I> alphabet;
049        protected final MembershipOracle<I, O> oracle;
050        protected final ObservationTable<I, O> table;
051
052        /**
053         * Constructor.
054         * @param alphabet the learning alphabet.
055         * @param oracle the membership oracle.
056         * @param outputMapping a mapping that translates between oracle outputs and data entries stored
057         * in the observation table.
058         */
059        public AbstractLStar(Alphabet<I> alphabet, MembershipOracle<I,O> oracle) {
060                this.alphabet = alphabet;
061                this.oracle = oracle;
062                
063                this.table = new ObservationTable<>(alphabet);
064        }
065        
066        
067        /*
068         * (non-Javadoc)
069         * @see de.learnlib.api.LearningAlgorithm#start()
070         */
071        @Override
072        public void startLearning() {
073                List<Word<I>> suffixes = initialSuffixes();
074                List<List<Row<I>>> initialUnclosed = table.initialize(suffixes, oracle);
075                
076                completeConsistentTable(initialUnclosed, false);
077        }
078
079        /*
080         * (non-Javadoc)
081         * @see de.learnlib.api.LearningAlgorithm#refineHypothesis(de.learnlib.api.Query)
082         */
083        @Override
084        public final boolean refineHypothesis(DefaultQuery<I, O> ceQuery) {
085                int oldDistinctRows = table.numDistinctRows();
086                doRefineHypothesis(ceQuery);
087                return (table.numDistinctRows() > oldDistinctRows);
088        }
089        
090        protected void doRefineHypothesis(DefaultQuery<I,O> ceQuery) {
091                List<List<Row<I>>> unclosed = incorporateCounterExample(ceQuery);
092                completeConsistentTable(unclosed, true);
093        }
094        
095        /**
096         * Iteratedly checks for unclosedness and inconsistencies in the table,
097         * and fixes any occurrences thereof. This process is repeated until the
098         * observation table is both closed and consistent. 
099         * @param unclosed the unclosed rows (equivalence classes) to start with.
100         */
101        protected void completeConsistentTable(List<List<Row<I>>> unclosed, boolean checkConsistency) {
102                do {
103                        while(!unclosed.isEmpty()) {
104                                List<Row<I>> closingRows = selectClosingRows(unclosed);
105                                unclosed = table.toShortPrefixes(closingRows, oracle);
106                        }
107                        
108                        
109                        if(checkConsistency) {
110                                Inconsistency<I,O> incons = null;
111                                
112                                do {
113                                        incons = table.findInconsistency();
114                                        if(incons != null) {
115                                                Word<I> newSuffix = analyzeInconsistency(incons);
116                                                unclosed = table.addSuffix(newSuffix, oracle);
117                                        }
118                                } while(unclosed.isEmpty() && (incons != null));
119                        }
120                } while(!unclosed.isEmpty());
121        }
122        
123        
124        /**
125         * Analyzes an inconsistency. This analysis consists in determining
126         * the column in which the two successor rows differ.
127         * @param incons the inconsistency description
128         * @return the suffix to add in order to fix the inconsistency
129         */
130        protected Word<I> analyzeInconsistency(Inconsistency<I,O> incons) {
131                int inputIdx = incons.getInputIndex();
132                
133                Row<I> succRow1 = incons.getFirstRow().getSuccessor(inputIdx);
134                Row<I> succRow2 = incons.getSecondRow().getSuccessor(inputIdx);
135                
136                int numSuffixes = table.numSuffixes();
137                
138                List<O> contents1 = table.rowContents(succRow1);
139                List<O> contents2 = table.rowContents(succRow2);
140                
141                for(int i = 0; i < numSuffixes; i++) {
142                        O val1 = contents1.get(i), val2 = contents2.get(i);
143                        if(!Objects.equals(val1, val2)) {
144                                I sym = alphabet.getSymbol(inputIdx);
145                                Word<I> suffix = table.getSuffixes().get(i);
146                                return suffix.prepend(sym);
147                        }
148                }
149                
150                throw new IllegalArgumentException("Bogus inconsistency");
151        }
152
153
154        /**
155         * Incorporates the information provided by a counterexample into
156         * the observation data structure.
157         * @param ce the query which contradicts the hypothesis
158         * @return the rows (equivalence classes) which became unclosed by
159         * adding the information. 
160         */
161        protected List<List<Row<I>>> incorporateCounterExample(DefaultQuery<I,O> ce) {
162                return ObservationTableCEXHandlers.handleClassicLStar(ce, table, oracle);
163        }
164        
165        /**
166         * This method selects a set of rows to use for closing the table.
167         * It receives as input a list of row lists, such that each (inner) list contains
168         * long prefix rows with (currently) identical contents, which have no matching
169         * short prefix row. The outer list is the list of all those equivalence classes.
170         * 
171         * @param unclosed a list of equivalence classes of unclosed rows.
172         * @return a list containing a representative row from each class to move
173         * to the short prefix part.
174         */
175        protected List<Row<I>> selectClosingRows(List<List<Row<I>>> unclosed) {
176                List<Row<I>> closingRows = new ArrayList<Row<I>>(unclosed.size());
177                
178                for(List<Row<I>> rowList : unclosed)
179                        closingRows.add(rowList.get(0));
180                
181                return closingRows;
182        }
183        
184        
185        /**
186         * Returns the list of initial suffixes which are used to initialize the table.
187         * @return the list of initial suffixes.
188         */
189        protected abstract List<Word<I>> initialSuffixes();
190
191}