
de.learnlib.algorithm.lstar.AbstractLStar Maven / Gradle / Ivy
Show all versions of learnlib-lstar Show documentation
/* Copyright (C) 2013-2023 TU Dortmund
* This file is part of LearnLib, http://www.learnlib.de/.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package de.learnlib.algorithm.lstar;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import de.learnlib.algorithm.GlobalSuffixLearner;
import de.learnlib.algorithm.lstar.ce.ObservationTableCEXHandlers;
import de.learnlib.datastructure.observationtable.GenericObservationTable;
import de.learnlib.datastructure.observationtable.Inconsistency;
import de.learnlib.datastructure.observationtable.OTLearner;
import de.learnlib.datastructure.observationtable.ObservationTable;
import de.learnlib.datastructure.observationtable.Row;
import de.learnlib.oracle.MembershipOracle;
import de.learnlib.query.DefaultQuery;
import de.learnlib.util.MQUtil;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.alphabet.Alphabets;
import net.automatalib.alphabet.SupportsGrowingAlphabet;
import net.automatalib.automaton.concept.SuffixOutput;
import net.automatalib.word.Word;
/**
* An abstract base class for L*-style algorithms.
*
* This class implements basic management features (table, alphabet, oracle) and the main loop of alternating
* completeness and consistency checks. It does not take care of choosing how to initialize the table and hypothesis
* construction.
*
* @param
* automaton type
* @param
* input symbol type
* @param
* output domain type
*/
public abstract class AbstractLStar
implements OTLearner, GlobalSuffixLearner, SupportsGrowingAlphabet {
protected final Alphabet alphabet;
protected final MembershipOracle oracle;
protected GenericObservationTable table;
/**
* Constructor.
*
* @param alphabet
* the learning alphabet.
* @param oracle
* the membership oracle.
*/
protected AbstractLStar(Alphabet alphabet, MembershipOracle oracle) {
this.alphabet = alphabet;
this.oracle = oracle;
this.table = new GenericObservationTable<>(alphabet);
}
@Override
public void startLearning() {
List> prefixes = initialPrefixes();
List> suffixes = initialSuffixes();
List>> initialUnclosed = table.initialize(prefixes, suffixes, oracle);
completeConsistentTable(initialUnclosed, table.isInitialConsistencyCheckRequired());
}
@Override
public final boolean refineHypothesis(DefaultQuery ceQuery) {
if (!MQUtil.isCounterexample(ceQuery, hypothesisOutput())) {
return false;
}
int oldDistinctRows = table.numberOfDistinctRows();
doRefineHypothesis(ceQuery);
assert table.numberOfDistinctRows() > oldDistinctRows;
return true;
}
protected abstract SuffixOutput hypothesisOutput();
protected void doRefineHypothesis(DefaultQuery ceQuery) {
List>> unclosed = incorporateCounterExample(ceQuery);
completeConsistentTable(unclosed, true);
}
/**
* Incorporates the information provided by a counterexample into the observation data structure.
*
* @param ce
* the query which contradicts the hypothesis
*
* @return the rows (equivalence classes) which became unclosed by adding the information.
*/
protected List>> incorporateCounterExample(DefaultQuery ce) {
return ObservationTableCEXHandlers.handleClassicLStar(ce, table, oracle);
}
protected List> initialPrefixes() {
return Collections.singletonList(Word.epsilon());
}
/**
* Returns the list of initial suffixes which are used to initialize the table.
*
* @return the list of initial suffixes.
*/
protected abstract List> initialSuffixes();
/**
* Iteratedly checks for unclosedness and inconsistencies in the table, and fixes any occurrences thereof. This
* process is repeated until the observation table is both closed and consistent.
*
* @param unclosed
* the unclosed rows (equivalence classes) to start with.
*/
protected boolean completeConsistentTable(List>> unclosed, boolean checkConsistency) {
boolean refined = false;
List>> unclosedIter = unclosed;
do {
while (!unclosedIter.isEmpty()) {
List> closingRows = selectClosingRows(unclosedIter);
unclosedIter = table.toShortPrefixes(closingRows, oracle);
refined = true;
}
if (checkConsistency) {
Inconsistency incons;
do {
incons = table.findInconsistency();
if (incons != null) {
Word newSuffix = analyzeInconsistency(incons);
unclosedIter = table.addSuffix(newSuffix, oracle);
}
} while (unclosedIter.isEmpty() && incons != null);
}
} while (!unclosedIter.isEmpty());
return refined;
}
/**
* This method selects a set of rows to use for closing the table. It receives as input a list of row lists, such
* that each (inner) list contains long prefix rows with (currently) identical contents, which have no matching
* short prefix row. The outer list is the list of all those equivalence classes.
*
* @param unclosed
* a list of equivalence classes of unclosed rows.
*
* @return a list containing a representative row from each class to move to the short prefix part.
*/
protected List> selectClosingRows(List>> unclosed) {
List> closingRows = new ArrayList<>(unclosed.size());
for (List> rowList : unclosed) {
closingRows.add(rowList.get(0));
}
return closingRows;
}
/**
* Analyzes an inconsistency. This analysis consists in determining the column in which the two successor rows
* differ.
*
* @param incons
* the inconsistency description
*
* @return the suffix to add in order to fix the inconsistency
*/
protected Word analyzeInconsistency(Inconsistency incons) {
int inputIdx = alphabet.getSymbolIndex(incons.getSymbol());
Row succRow1 = incons.getFirstRow().getSuccessor(inputIdx);
Row succRow2 = incons.getSecondRow().getSuccessor(inputIdx);
int numSuffixes = table.getSuffixes().size();
for (int i = 0; i < numSuffixes; i++) {
D val1 = table.cellContents(succRow1, i), val2 = table.cellContents(succRow2, i);
if (!Objects.equals(val1, val2)) {
I sym = alphabet.getSymbol(inputIdx);
Word suffix = table.getSuffixes().get(i);
return suffix.prepend(sym);
}
}
throw new IllegalArgumentException("Bogus inconsistency");
}
@Override
public Collection> getGlobalSuffixes() {
return Collections.unmodifiableCollection(table.getSuffixes());
}
@Override
public boolean addGlobalSuffixes(Collection extends Word> newGlobalSuffixes) {
List>> unclosed = table.addSuffixes(newGlobalSuffixes, oracle);
if (unclosed.isEmpty()) {
return false;
}
return completeConsistentTable(unclosed, false);
}
@Override
public ObservationTable getObservationTable() {
return table;
}
@Override
public void addAlphabetSymbol(I symbol) {
if (!this.alphabet.containsSymbol(symbol)) {
Alphabets.toGrowingAlphabetOrThrowException(this.alphabet).addSymbol(symbol);
}
final List>> unclosed = this.table.addAlphabetSymbol(symbol, oracle);
completeConsistentTable(unclosed, true);
}
}