diff --git a/CHANGELOG.md b/CHANGELOG.md index 816516dcc..cc046ef9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * LearnLib now requires Java 17 at runtime. * Statistics collection has received a major rework. Previously, classes would implement the `StatisticCollector` interface and return a `StatisticData` object which 1) only allows for describing a very limited amount of data, and 2) requires you to keep track of all the objects that collect data. This approach has been *replaced* by a new `StatisticsService`. Instances of this service can be obtained similar to a logger via `Statistics.getService()` and require you to provide an implementation of this service on the classpath (a default one is provided by the `learnlib-statistics` module). The new service allows arbitrary components to collect various data which can be conveniently extracted based on the new `StatisticsKey`s used by the components. For more details on advanced scenarios (such as multi-threaded benchmarking), see the documentation of the respective classes. While this may require you to adjust the way you are collecting statistics, all functionality from beforehand should still be available. * `SimpleProfiler` has been replaced by the new clock-based statistics. +* Most learners now more rigorously implement the `LearningAlgorithm` contract that, e.g., duplicate invocations of `startLearning` or calling `refineHypothesis` / `getHypothesisModel` before `startLearning` throw `IllegalStateException`s. * The `generateTestWords` method of `AbstractTestWordEQOracle` now needs to be public. * The classes of `de.learnlib.testsupport.it.learner` have been split into the packages `de.learnlib.testsupport.it{,testcase,util,variant}` in the same module (`de.learnlib.testsupport:learnlib-learner-it-support`). diff --git a/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java b/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java index 3e67cf03b..d236176ec 100644 --- a/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java +++ b/algorithms/active/adt/src/main/java/de/learnlib/algorithm/adt/learner/ADTLearner.java @@ -30,6 +30,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.algorithm.LearningAlgorithm; import de.learnlib.algorithm.adt.adt.ADT; @@ -84,7 +85,8 @@ public class ADTLearner implements LearningAlgorithm.MealyLearner, PartialTransitionAnalyzer, I>, AccessSequenceTransformer, SupportsGrowingAlphabet, - Resumable, I, O>> { + Resumable, I, O>>, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(ADTLearner.class); @@ -138,6 +140,7 @@ public ADTLearner(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); final ADTState initialState = this.hypothesis.addInitialState(); initialState.setAccessSequence(Word.epsilon()); @@ -315,9 +318,15 @@ private ADTNode, I, O> findNodeForState(ADTState state) { @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return this.hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return !hypothesis.getStates().isEmpty(); + } + /** * Close all pending open transitions. */ @@ -852,12 +861,6 @@ private List> getIncomingNonSpanningTreeTransitions(ADTState return result; } - private void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - public ADT, I, O> getADT() { return adt; } diff --git a/algorithms/active/dhc/src/main/java/de/learnlib/algorithm/dhc/mealy/MealyDHC.java b/algorithms/active/dhc/src/main/java/de/learnlib/algorithm/dhc/mealy/MealyDHC.java index 0eda83c62..eb156119e 100644 --- a/algorithms/active/dhc/src/main/java/de/learnlib/algorithm/dhc/mealy/MealyDHC.java +++ b/algorithms/active/dhc/src/main/java/de/learnlib/algorithm/dhc/mealy/MealyDHC.java @@ -28,6 +28,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.algorithm.GlobalSuffixLearner.GlobalSuffixLearnerMealy; import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; @@ -61,7 +62,8 @@ public class MealyDHC implements MealyLearner, AccessSequenceTransformer, GlobalSuffixLearnerMealy, SupportsGrowingAlphabet, - Resumable> { + Resumable>, + LearnerStateTracker { private final MembershipOracle> oracle; private final Alphabet alphabet; @@ -120,29 +122,28 @@ public Collection> getGlobalSuffixes() { @Override public boolean addGlobalSuffixes(Collection> newGlobalSuffixes) { - checkInternalState(); + requireLearningProcessStarted(); return addSuffixesUnchecked(newGlobalSuffixes); } - private void checkInternalState() { - if (hypothesis == null) { - throw new IllegalStateException("No hypothesis learned yet"); - } - } - protected boolean addSuffixesUnchecked(Collection> newSuffixes) { int oldSize = hypothesis.size(); splitters.addAll(newSuffixes); - startLearning(); + startLearningInternal(); return hypothesis.size() != oldSize; } @Override public void startLearning() { + requireLearningProcessNotStarted(); + startLearningInternal(); + } + + private void startLearningInternal() { // initialize structure to store state output signatures Map>, Integer> signatures = new HashMap<>(); @@ -233,7 +234,7 @@ private void scheduleSuccessors(QueueElement elem, @Override public boolean refineHypothesis(DefaultQuery> ceQuery) { - checkInternalState(); + requireLearningProcessStarted(); if (hypothesis.computeSuffixOutput(ceQuery.getPrefix(), ceQuery.getSuffix()).equals(ceQuery.getOutput())) { return false; @@ -246,10 +247,15 @@ public boolean refineHypothesis(DefaultQuery> ceQuery) { @Override public CompactMealy getHypothesisModel() { - checkInternalState(); + requireLearningProcessStarted(); return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis != null; + } + @Override public void addAlphabetSymbol(I symbol) { @@ -275,7 +281,9 @@ public void addAlphabetSymbol(I symbol) { this.splitters = newSplitters; - this.startLearning(); + if (hasLearningProcessStarted()) { + this.startLearningInternal(); + } } } @@ -293,7 +301,7 @@ public void resume(MealyDHCState state) { @Override public Word transformAccessSequence(Word word) { - checkInternalState(); + requireLearningProcessStarted(); Integer state = hypothesis.getState(word); assert state != null; return assembleAccessSequence(accessSequences.get(state)); diff --git a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/dfa/KearnsVaziraniDFA.java b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/dfa/KearnsVaziraniDFA.java index 33e328d37..d19772eba 100644 --- a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/dfa/KearnsVaziraniDFA.java +++ b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/dfa/KearnsVaziraniDFA.java @@ -24,6 +24,7 @@ import java.util.function.BooleanSupplier; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.acex.AcexAnalyzer; @@ -59,7 +60,8 @@ public class KearnsVaziraniDFA implements DFALearner, AccessSequenceTransformer, SupportsGrowingAlphabet, - Resumable> { + Resumable>, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(KearnsVaziraniDFA.class); @@ -113,6 +115,7 @@ public KearnsVaziraniDFA(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); initialize(); } @@ -138,6 +141,11 @@ public DFA getHypothesisModel() { return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return this.hypothesis.size() != 0; + } + public BinaryDTree> getDiscriminationTree() { return discriminationTree; } @@ -315,12 +323,6 @@ private List> sift(List transformAccessSequence(Word word) { requireLearningProcessStarted(); diff --git a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/mealy/KearnsVaziraniMealy.java b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/mealy/KearnsVaziraniMealy.java index d2ca88a29..f3e4f5a86 100644 --- a/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/mealy/KearnsVaziraniMealy.java +++ b/algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithm/kv/mealy/KearnsVaziraniMealy.java @@ -24,6 +24,7 @@ import java.util.Objects; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.acex.AcexAnalyzer; @@ -62,7 +63,8 @@ public class KearnsVaziraniMealy implements MealyLearner, AccessSequenceTransformer, SupportsGrowingAlphabet, - Resumable> { + Resumable>, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(KearnsVaziraniMealy.class); @@ -116,6 +118,7 @@ public KearnsVaziraniMealy(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); initialize(); } @@ -143,6 +146,11 @@ public boolean refineHypothesis(DefaultQuery> ceQuery) { return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return this.hypothesis.size() != 0; + } + public MultiDTree, StateInfo>> getDiscriminationTree() { return discriminationTree; } @@ -343,12 +351,6 @@ private List>> sift(List return result; } - private void requireLearningProcessStarted() { - if (hypothesis.size() == 0) { - throw new IllegalStateException("Learning process has not been started"); - } - } - @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); diff --git a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/lstar/AbstractLLambda.java b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/lstar/AbstractLLambda.java index 144a4b1d1..81e63a072 100644 --- a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/lstar/AbstractLLambda.java +++ b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/lstar/AbstractLLambda.java @@ -28,6 +28,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.datastructure.observationtable.OTLearner; import de.learnlib.datastructure.observationtable.ObservationTable; @@ -47,7 +48,8 @@ abstract class AbstractLLambda, I, D> implements OT AccessSequenceTransformer, SupportsGrowingAlphabet, Resumable>, - FiniteRepresentation { + FiniteRepresentation, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLLambda.class); @@ -82,6 +84,7 @@ abstract class AbstractLLambda, I, D> implements OT @Override public void startLearning() { + requireLearningProcessNotStarted(); initTable(); learnLoop(); } @@ -116,6 +119,11 @@ public boolean refineHypothesis(DefaultQuery counterexample) { return refined; } + @Override + public boolean hasLearningProcessStarted() { + return !rows.isEmpty(); + } + private void initTable() { Word epsilon = Word.epsilon(); List rowData = initRow(epsilon); @@ -295,12 +303,6 @@ private void addShortPrefix(Word shortPrefix) { } } - protected void requireLearningProcessStarted() { - if (rows.isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - @Override public void addAlphabetSymbol(I symbol) { if (!this.alphabet.containsSymbol(symbol)) { diff --git a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/AbstractTTTLambda.java b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/AbstractTTTLambda.java index 3f6b3d3e8..cd56da902 100644 --- a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/AbstractTTTLambda.java +++ b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/AbstractTTTLambda.java @@ -21,6 +21,7 @@ import java.util.Objects; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.algorithm.LearningAlgorithm; import de.learnlib.algorithm.lambda.ttt.dt.AbstractDecisionTree; @@ -45,7 +46,8 @@ public abstract class AbstractTTTLambda, I, D> impl AccessSequenceTransformer, SupportsGrowingAlphabet, Resumable>, - FiniteRepresentation { + FiniteRepresentation, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTTTLambda.class); @@ -75,6 +77,7 @@ protected AbstractTTTLambda(Alphabet alphabet, MembershipOracle mqs, Me @Override public void startLearning() { + requireLearningProcessNotStarted(); dtree().sift(mqs, ptree.root()); makeConsistent(mqs); started = true; @@ -110,6 +113,11 @@ public boolean refineHypothesis(DefaultQuery counterexample) { return refined; } + @Override + public boolean hasLearningProcessStarted() { + return started; + } + @Override public void addAlphabetSymbol(I symbol) { if (!this.alphabet.containsSymbol(symbol)) { @@ -214,12 +222,6 @@ private void analyzeCounterexample(DefaultQuery counterexample, Deque suspend() { return new TTTLambdaState<>(strie, ptree, dtree(), started); diff --git a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/dfa/TTTLambdaDFA.java b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/dfa/TTTLambdaDFA.java index 4b9359ad3..ce527bbd3 100644 --- a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/dfa/TTTLambdaDFA.java +++ b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/dfa/TTTLambdaDFA.java @@ -56,6 +56,7 @@ protected int maxSearchIndex(int ceLength) { @Override public DFA getHypothesisModel() { + requireLearningProcessStarted(); return hypothesis; } diff --git a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/mealy/TTTLambdaMealy.java b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/mealy/TTTLambdaMealy.java index 4345fafd7..b28364bf9 100644 --- a/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/mealy/TTTLambdaMealy.java +++ b/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/mealy/TTTLambdaMealy.java @@ -62,6 +62,7 @@ protected int maxSearchIndex(int ceLength) { @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return hypothesis; } diff --git a/algorithms/active/lsharp/src/main/java/de/learnlib/algorithm/lsharp/LSharpMealy.java b/algorithms/active/lsharp/src/main/java/de/learnlib/algorithm/lsharp/LSharpMealy.java index 44d14fd12..17264ef8c 100644 --- a/algorithms/active/lsharp/src/main/java/de/learnlib/algorithm/lsharp/LSharpMealy.java +++ b/algorithms/active/lsharp/src/main/java/de/learnlib/algorithm/lsharp/LSharpMealy.java @@ -25,6 +25,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; import de.learnlib.oracle.AdaptiveMembershipOracle; import de.learnlib.query.DefaultQuery; @@ -52,7 +53,7 @@ * @param * output symbol type */ -public class LSharpMealy implements MealyLearner, AccessSequenceTransformer { +public class LSharpMealy implements MealyLearner, AccessSequenceTransformer, LearnerStateTracker { private final LSOracle oqOracle; private final Alphabet inputAlphabet; @@ -350,6 +351,7 @@ ArrayStorage> getAccessMap() { @Override public void startLearning() { + requireLearningProcessNotStarted(); this.initObsTree(null); buildHypothesis(); } @@ -368,6 +370,11 @@ public boolean refineHypothesis(DefaultQuery> ceQuery) { return constructHypothesis(); } + @Override + public boolean hasLearningProcessStarted() { + return !basisMap.isEmpty(); + } + @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); @@ -377,12 +384,6 @@ public Word transformAccessSequence(Word word) { return accessMap.get(bs); } - private void requireLearningProcessStarted() { - if (basisMap.isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - static final class BuilderDefaults { private BuilderDefaults() { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/AbstractLStar.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/AbstractLStar.java index f5c39f7dc..f47b3510c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/AbstractLStar.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/AbstractLStar.java @@ -22,6 +22,7 @@ import java.util.Objects; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.algorithm.GlobalSuffixLearner; import de.learnlib.algorithm.lstar.ce.ObservationTableCEXHandlers; import de.learnlib.datastructure.observationtable.GenericObservationTable; @@ -54,7 +55,8 @@ public abstract class AbstractLStar implements OTLearner, GlobalSuffixLearner, AccessSequenceTransformer, - SupportsGrowingAlphabet { + SupportsGrowingAlphabet, + LearnerStateTracker { protected final Alphabet alphabet; protected final MembershipOracle oracle; @@ -76,6 +78,7 @@ protected AbstractLStar(Alphabet alphabet, MembershipOracle oracle) { @Override public void startLearning() { + requireLearningProcessNotStarted(); List> prefixes = initialPrefixes(); List> suffixes = initialSuffixes(); List>> initialUnclosed = table.initialize(prefixes, suffixes, oracle); @@ -85,6 +88,7 @@ public void startLearning() { @Override public final boolean refineHypothesis(DefaultQuery ceQuery) { + requireLearningProcessStarted(); if (!MQUtil.isCounterexample(ceQuery, hypothesisOutput())) { return false; } @@ -227,6 +231,11 @@ public ObservationTable getObservationTable() { return table; } + @Override + public boolean hasLearningProcessStarted() { + return this.table.isInitialized(); + } + @Override public void addAlphabetSymbol(I symbol) { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/dfa/ExtensibleLStarDFA.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/dfa/ExtensibleLStarDFA.java index 57fa3830c..7eaccb4c6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/dfa/ExtensibleLStarDFA.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/dfa/ExtensibleLStarDFA.java @@ -119,6 +119,7 @@ public ExtensibleLStarDFA(Alphabet alphabet, @Override public DFA getHypothesisModel() { + requireLearningProcessStarted(); return internalHyp; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ClassicLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ClassicLStarMealy.java index 788bda06c..4bf425df3 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ClassicLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ClassicLStarMealy.java @@ -103,6 +103,7 @@ public ClassicLStarMealy(Alphabet alphabet, @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return internalHyp; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ExtensibleLStarMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ExtensibleLStarMealy.java index 161546ca0..a9319df35 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ExtensibleLStarMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mealy/ExtensibleLStarMealy.java @@ -126,6 +126,7 @@ public ExtensibleLStarMealy(Alphabet alphabet, @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return internalHyp; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java index 416d2468b..236ee729d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java @@ -22,6 +22,7 @@ import java.util.Map; import java.util.Map.Entry; +import de.learnlib.LearnerStateTracker; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; @@ -73,7 +74,7 @@ * output symbol type */ public class ExtensibleLStarMMLT - implements OTLearner, TimedInput, Word>> { + implements OTLearner, TimedInput, Word>>, LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); private static final String STATISTICS_ID = "L*-MMLT"; @@ -257,6 +258,7 @@ public static int selectOneShotTimer(List> sortedT @Override public MMLT getHypothesisModel() { + requireLearningProcessStarted(); return getInternalHypothesisModel(); } @@ -319,6 +321,7 @@ private void updateOutputs() { @Override public void startLearning() { + requireLearningProcessNotStarted(); List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); @@ -330,6 +333,7 @@ public void startLearning() { @Override public boolean refineHypothesis(DefaultQuery, Word>> ceQuery) { + requireLearningProcessStarted(); if (!refineHypothesisSingle(ceQuery)) { return false; // no valid CEX } @@ -487,6 +491,11 @@ public ObservationTable, Word>> getObservationTable return this.hypData.getTable(); } + @Override + public boolean hasLearningProcessStarted() { + return hypData.getTable().isInitialized(); + } + /** * Iteratively 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. diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java index f01a2b4e8..fb160fcc6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java @@ -307,7 +307,7 @@ List>>> initialize(List>> initialShort List>> initialSuffixes, TimedQueryOracle oracle) { - assert this.shortPrefixRowMap.isEmpty() && this.longPrefixRowMap.isEmpty() && initialShortPrefixes.isEmpty(); + assert !isInitialized() && initialShortPrefixes.isEmpty(); // Add initial suffixes: for (Word> suffix : initialSuffixes) { @@ -325,6 +325,10 @@ List>>> initialize(List>> initialShort return this.findUnclosedTransitions(); } + boolean isInitialized() { + return !(this.shortPrefixRowMap.isEmpty() && this.longPrefixRowMap.isEmpty()); + } + private void queryAllSuffixes(Collection>> rows, TimedQueryOracle timedOracle) { int numSuffixes = this.suffixes.size(); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ClassicLStarMoore.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ClassicLStarMoore.java index 9a56bb795..97a6d79d4 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ClassicLStarMoore.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ClassicLStarMoore.java @@ -59,6 +59,7 @@ public ClassicLStarMoore(Alphabet alphabet, @Override public MooreMachine getHypothesisModel() { + requireLearningProcessStarted(); return internalHyp; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ExtensibleLStarMoore.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ExtensibleLStarMoore.java index e96566f06..a5a7e6985 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ExtensibleLStarMoore.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/moore/ExtensibleLStarMoore.java @@ -119,6 +119,7 @@ public ExtensibleLStarMoore(Alphabet alphabet, @Override public MooreMachine getHypothesisModel() { + requireLearningProcessStarted(); return internalHyp; } diff --git a/algorithms/active/nlstar/src/main/java/de/learnlib/algorithm/nlstar/NLStarLearner.java b/algorithms/active/nlstar/src/main/java/de/learnlib/algorithm/nlstar/NLStarLearner.java index 5f74e4042..0b445a11e 100644 --- a/algorithms/active/nlstar/src/main/java/de/learnlib/algorithm/nlstar/NLStarLearner.java +++ b/algorithms/active/nlstar/src/main/java/de/learnlib/algorithm/nlstar/NLStarLearner.java @@ -19,6 +19,7 @@ import java.util.Arrays; import java.util.List; +import de.learnlib.LearnerStateTracker; import de.learnlib.algorithm.LearningAlgorithm.NFALearner; import de.learnlib.oracle.MembershipOracle; import de.learnlib.query.DefaultQuery; @@ -36,7 +37,7 @@ * @param * input symbol type */ -public class NLStarLearner implements NFALearner { +public class NLStarLearner implements NFALearner, LearnerStateTracker { private final Alphabet alphabet; private final ObservationTable table; @@ -59,9 +60,7 @@ public NLStarLearner(Alphabet alphabet, MembershipOracle oracle) @Override public void startLearning() { - if (hypothesis != null) { - throw new IllegalStateException(); - } + requireLearningProcessNotStarted(); List>> unclosed = table.initialize(); completeConsistentTable(unclosed); @@ -117,9 +116,7 @@ private List>> fixInconsistency(Inconsistency incons) { @Override public boolean refineHypothesis(DefaultQuery ceQuery) { - if (hypothesis == null) { - throw new IllegalStateException(); - } + requireLearningProcessStarted(); boolean refined = false; while (MQUtil.isCounterexample(ceQuery, hypothesis)) { @@ -185,12 +182,15 @@ private void constructHypothesis() { @Override public NFA getHypothesisModel() { - if (hypothesis == null) { - throw new IllegalStateException(); - } + requireLearningProcessStarted(); return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis != null; + } + public ObservationTable getObservationTable() { return table; } diff --git a/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/AbstractVPALearner.java b/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/AbstractVPALearner.java index 79f4bd333..a3a0039d9 100644 --- a/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/AbstractVPALearner.java +++ b/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/AbstractVPALearner.java @@ -20,6 +20,7 @@ import java.util.Iterator; import java.util.List; +import de.learnlib.LearnerStateTracker; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; import de.learnlib.algorithm.LearningAlgorithm; @@ -40,7 +41,8 @@ import net.automatalib.common.smartcollection.UnorderedCollection; import net.automatalib.word.Word; -public abstract class AbstractVPALearner implements LearningAlgorithm, I, Boolean> { +public abstract class AbstractVPALearner + implements LearningAlgorithm, I, Boolean>, LearnerStateTracker { protected final VPAlphabet alphabet; @@ -62,6 +64,7 @@ public AbstractVPALearner(VPAlphabet alphabet, MembershipOracle o @Override public void startLearning() { + requireLearningProcessNotStarted(); HypLoc initLoc = hypothesis.initialize(); DTNode leaf = dtree.sift(initLoc.getAccessSequence()); link(leaf, initLoc); @@ -72,6 +75,7 @@ public void startLearning() { @Override public boolean refineHypothesis(DefaultQuery ceQuery) { + requireLearningProcessStarted(); if (hypothesis.computeSuffixOutput(ceQuery.getPrefix(), ceQuery.getSuffix()).equals(ceQuery.getOutput())) { return false; } @@ -87,9 +91,15 @@ public boolean refineHypothesis(DefaultQuery ceQuery) { @Override public OneSEVPA getHypothesisModel() { + requireLearningProcessStarted(); return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis.isInitialized(); + } + public DTree getDiscriminationTree() { return dtree; } diff --git a/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/hypothesis/OneSEVPAHypothesis.java b/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/hypothesis/OneSEVPAHypothesis.java index 31868bc5a..b77be54ea 100644 --- a/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/hypothesis/OneSEVPAHypothesis.java +++ b/algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/hypothesis/OneSEVPAHypothesis.java @@ -81,6 +81,10 @@ public HypLoc initialize() { return loc; } + public boolean isInitialized() { + return this.initLoc != null; + } + @Override public HypLoc getInternalSuccessor(HypLoc loc, I intSym) { return loc.getInternalTransition(alphabet.getInternalSymbolIndex(intSym)).getTargetLocation(); diff --git a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/AbstractOPLearner.java b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/AbstractOPLearner.java index ae64aba38..36a274365 100644 --- a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/AbstractOPLearner.java +++ b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/AbstractOPLearner.java @@ -21,6 +21,7 @@ import java.util.List; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.algorithm.LearningAlgorithm; import de.learnlib.algorithm.observationpack.hypothesis.HState; @@ -61,7 +62,8 @@ public abstract class AbstractOPLearner, I, D, SP, implements LearningAlgorithm, AccessSequenceTransformer, SupportsGrowingAlphabet, - Resumable> { + Resumable>, + LearnerStateTracker { private final Alphabet alphabet; private final MembershipOracle oracle; @@ -88,6 +90,7 @@ protected AbstractOPLearner(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); HState init = hypothesis.createInitialState(); AbstractWordBasedDTNode> initDt = dtree.sift(init.getAccessSequence()); if (initDt.getData() != null) { @@ -252,10 +255,9 @@ public OPLearnerHypothesis getHypothesisDS() { return hypothesis; } - private void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } + @Override + public boolean hasLearningProcessStarted() { + return !hypothesis.getStates().isEmpty(); } @Override diff --git a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/dfa/OPLearnerDFA.java b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/dfa/OPLearnerDFA.java index fdbf5fb97..d63d6804a 100644 --- a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/dfa/OPLearnerDFA.java +++ b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/dfa/OPLearnerDFA.java @@ -80,6 +80,7 @@ public OPLearnerDFA(Alphabet alphabet, @Override public DFA getHypothesisModel() { + requireLearningProcessStarted(); return new HypothesisWrapperDFA<>(getHypothesisDS()); } diff --git a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/mealy/OPLearnerMealy.java b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/mealy/OPLearnerMealy.java index d71ae63e1..deaa84c90 100644 --- a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/mealy/OPLearnerMealy.java +++ b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/mealy/OPLearnerMealy.java @@ -75,6 +75,7 @@ public OPLearnerMealy(Alphabet alphabet, @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return new HypothesisWrapperMealy<>(getHypothesisDS()); } diff --git a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/moore/OPLearnerMoore.java b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/moore/OPLearnerMoore.java index 359ef0396..d431f8039 100644 --- a/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/moore/OPLearnerMoore.java +++ b/algorithms/active/observation-pack/src/main/java/de/learnlib/algorithm/observationpack/moore/OPLearnerMoore.java @@ -91,6 +91,7 @@ public void answer(Word output) { @Override public MooreMachine getHypothesisModel() { + requireLearningProcessStarted(); return new HypothesisWrapperMoore<>(getHypothesisDS()); } } diff --git a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/sba/SBALearner.java b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/sba/SBALearner.java index 84487b697..5790cc3b9 100644 --- a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/sba/SBALearner.java +++ b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/sba/SBALearner.java @@ -26,6 +26,7 @@ import java.util.function.Predicate; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; @@ -63,7 +64,7 @@ * sub-learner type */ public class SBALearner> & SupportsGrowingAlphabet> & AccessSequenceTransformer>> - implements LearningAlgorithm, I, Boolean> { + implements LearningAlgorithm, I, Boolean>, LearnerStateTracker { private final ProceduralInputAlphabet alphabet; private final MembershipOracle oracle; @@ -73,6 +74,7 @@ public class SBALearner> & SupportsGrow private final Map learners; private I initialCallSymbol; + boolean learningStarted; private final Map> mapping; @@ -98,6 +100,7 @@ public SBALearner(ProceduralInputAlphabet alphabet, this.atManager = atManager; this.learners = new HashMap<>(HashUtil.capacity(this.alphabet.getNumCalls())); + this.learningStarted = false; this.mapping = new HashMap<>(HashUtil.capacity(this.alphabet.size())); for (I i : this.alphabet.getInternalAlphabet()) { @@ -111,11 +114,14 @@ public SBALearner(ProceduralInputAlphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); + this.learningStarted = true; // do nothing, as we have to wait for evidence that the potential main procedure actually terminates } @Override public boolean refineHypothesis(DefaultQuery defaultQuery) { + requireLearningProcessStarted(); if (!MQUtil.isCounterexample(defaultQuery, getHypothesisModel())) { return false; @@ -166,6 +172,7 @@ private boolean refineHypothesisInternal(DefaultQuery defaultQuery) @Override public SBA getHypothesisModel() { + requireLearningProcessStarted(); if (this.learners.isEmpty()) { return new EmptySBA<>(this.alphabet); @@ -204,6 +211,11 @@ public SBA getHypothesisModel() { return new MappingSBA<>(alphabet, mapping, delegate); } + @Override + public boolean hasLearningProcessStarted() { + return this.learningStarted; + } + private boolean extractUsefulInformationFromCounterExample(DefaultQuery defaultQuery) { if (!defaultQuery.getOutput()) { diff --git a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spa/SPALearner.java b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spa/SPALearner.java index 285709732..001059feb 100644 --- a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spa/SPALearner.java +++ b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spa/SPALearner.java @@ -28,6 +28,7 @@ import java.util.function.Predicate; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; @@ -60,7 +61,7 @@ * sub-learner type */ public class SPALearner & SupportsGrowingAlphabet & AccessSequenceTransformer> - implements LearningAlgorithm, I, Boolean> { + implements LearningAlgorithm, I, Boolean>, LearnerStateTracker { private final ProceduralInputAlphabet alphabet; private final MembershipOracle oracle; @@ -71,6 +72,7 @@ public class SPALearner & SupportsGrowingAlphabet private final Map subLearners; private final Set activeAlphabet; private I initialCallSymbol; + private boolean learningStarted; public SPALearner(ProceduralInputAlphabet alphabet, MembershipOracle oracle, @@ -96,15 +98,19 @@ public SPALearner(ProceduralInputAlphabet alphabet, this.subLearners = new HashMap<>(HashUtil.capacity(this.alphabet.getNumCalls())); this.activeAlphabet = new HashSet<>(HashUtil.capacity(alphabet.getNumCalls() + alphabet.getNumInternals())); this.activeAlphabet.addAll(alphabet.getInternalAlphabet()); + this.learningStarted = false; } @Override public void startLearning() { + requireLearningProcessNotStarted(); + this.learningStarted = true; // do nothing, as we have to wait for evidence that the potential main procedure actually terminates } @Override public boolean refineHypothesis(DefaultQuery defaultQuery) { + requireLearningProcessStarted(); if (!MQUtil.isCounterexample(defaultQuery, getHypothesisModel())) { return false; @@ -160,6 +166,7 @@ private boolean refineHypothesisInternal(DefaultQuery defaultQuery) @Override public SPA getHypothesisModel() { + requireLearningProcessStarted(); if (this.subLearners.isEmpty()) { return new EmptySPA<>(this.alphabet); @@ -168,6 +175,11 @@ public SPA getHypothesisModel() { return new StackSPA<>(alphabet, initialCallSymbol, getSubModels()); } + @Override + public boolean hasLearningProcessStarted() { + return this.learningStarted; + } + private boolean extractUsefulInformationFromCounterExample(DefaultQuery defaultQuery) { if (!defaultQuery.getOutput()) { diff --git a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spmm/SPMMLearner.java b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spmm/SPMMLearner.java index b91999281..383708b29 100644 --- a/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spmm/SPMMLearner.java +++ b/algorithms/active/procedural/src/main/java/de/learnlib/algorithm/procedural/spmm/SPMMLearner.java @@ -26,6 +26,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.algorithm.LearnerConstructor; import de.learnlib.algorithm.LearningAlgorithm; import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; @@ -62,7 +63,7 @@ * sub-learner type */ public class SPMMLearner, O> & SupportsGrowingAlphabet> & AccessSequenceTransformer>> - implements LearningAlgorithm, I, Word> { + implements LearningAlgorithm, I, Word>, LearnerStateTracker { private final ProceduralInputAlphabet alphabet; private final O errorOutput; @@ -73,6 +74,7 @@ public class SPMMLearner, O> & Sup private final Map learners; private I initialCallSymbol; private O initialOutputSymbol; + private boolean learningStarted; private final Map> mapping; @@ -99,6 +101,7 @@ public SPMMLearner(ProceduralInputAlphabet alphabet, this.atManager = atManager; this.learners = new HashMap<>(HashUtil.capacity(this.alphabet.getNumCalls())); + this.learningStarted = false; this.mapping = new HashMap<>(HashUtil.capacity(this.alphabet.size())); for (I i : this.alphabet.getInternalAlphabet()) { @@ -112,11 +115,14 @@ public SPMMLearner(ProceduralInputAlphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); + this.learningStarted = true; // do nothing, as we have to wait for evidence that the potential main procedure actually terminates } @Override public boolean refineHypothesis(DefaultQuery> defaultQuery) { + requireLearningProcessStarted(); if (!MQUtil.isCounterexample(defaultQuery, getHypothesisModel())) { return false; @@ -166,8 +172,14 @@ private boolean refineHypothesisInternal(DefaultQuery> defaultQuery) return true; } + @Override + public boolean hasLearningProcessStarted() { + return this.learningStarted; + } + @Override public SPMM getHypothesisModel() { + requireLearningProcessStarted(); if (this.learners.isEmpty()) { return new EmptySPMM<>(this.alphabet, errorOutput); diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java index d660ae7e2..4514eb0b8 100644 --- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java +++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java @@ -26,6 +26,7 @@ import java.util.function.Function; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; import de.learnlib.counterexample.LocalSuffixFinders; import de.learnlib.oracle.MembershipOracle; @@ -39,7 +40,7 @@ import net.automatalib.word.Word; class GenericSparseLearner & SupportsGrowingAlphabet, S, I, O> - implements MealyLearner, AccessSequenceTransformer, SupportsGrowingAlphabet { + implements MealyLearner, AccessSequenceTransformer, SupportsGrowingAlphabet, LearnerStateTracker { private final Alphabet alphabet; private final MembershipOracle> oracle; @@ -123,11 +124,13 @@ protected GenericSparseLearner(Alphabet alphabet, @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return hyp; } @Override public void startLearning() { + requireLearningProcessNotStarted(); final S init = hyp.addInitialState(); final CoreRow c = new CoreRow<>(Word.epsilon(), init, 0); cRows.add(c); @@ -158,6 +161,11 @@ public boolean refineHypothesis(DefaultQuery> q) { return true; } + @Override + public boolean hasLearningProcessStarted() { + return !hyp.getStates().isEmpty(); + } + @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); @@ -418,12 +426,6 @@ private void updatePartitionMap(CoreRow c, Word suf, Word out) { vecs.get(idx).set(c.idx); } - private void requireLearningProcessStarted() { - if (hyp.getStates().isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - List> getCRows() { return cRows; } diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/base/AbstractTTTLearner.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/base/AbstractTTTLearner.java index 26f7e228e..0970c9d24 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/base/AbstractTTTLearner.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/base/AbstractTTTLearner.java @@ -29,6 +29,7 @@ import java.util.Set; import de.learnlib.AccessSequenceTransformer; +import de.learnlib.LearnerStateTracker; import de.learnlib.Resumable; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; @@ -66,7 +67,8 @@ public abstract class AbstractTTTLearner implements LearningAlgorithm, AccessSequenceTransformer, SupportsGrowingAlphabet, - Resumable> { + Resumable>, + LearnerStateTracker { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTTTLearner.class); @@ -162,9 +164,7 @@ protected static void link(AbstractBaseDTNode dtNode, TTTState init = hypothesis.initialize(); AbstractBaseDTNode initNode = dtree.sift(init.getAccessSequence(), false); @@ -189,6 +189,11 @@ public boolean refineHypothesis(DefaultQuery ceQuery) { return true; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis.isInitialized(); + } + /** * Initializes a state. Creates its outgoing transition objects, and adds them to the "open" list. * @@ -948,12 +953,6 @@ public BaseTTTDiscriminationTree getDiscriminationTree() { return dtree; } - protected void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/dfa/TTTLearnerDFA.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/dfa/TTTLearnerDFA.java index d26d48bb4..f8d98fab3 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/dfa/TTTLearnerDFA.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/dfa/TTTLearnerDFA.java @@ -65,7 +65,7 @@ protected TTTLearnerDFA(Alphabet alphabet, @Override @SuppressWarnings("unchecked") public DFA getHypothesisModel() { - return (TTTHypothesisDFA) hypothesis; + return (TTTHypothesisDFA) getHypothesisDS(); } @Override @@ -102,13 +102,6 @@ protected Boolean computeHypothesisOutput(TTTState state, Word su return ((TTTStateDFA) endState).accepting; } - @Override - @SuppressWarnings("unchecked") - public TTTHypothesisDFA getHypothesisDS() { - requireLearningProcessStarted(); - return (TTTHypothesisDFA) hypothesis; - } - @Override protected AbstractBaseDTNode createNewNode(AbstractBaseDTNode parent, Boolean parentOutput) { diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/mealy/TTTLearnerMealy.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/mealy/TTTLearnerMealy.java index 8e4f83d54..b9e895802 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/mealy/TTTLearnerMealy.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/mealy/TTTLearnerMealy.java @@ -66,7 +66,7 @@ public TTTLearnerMealy(Alphabet alphabet, MembershipOracle> oracle @Override @SuppressWarnings("unchecked") public MealyMachine getHypothesisModel() { - return (TTTHypothesisMealy) hypothesis; + return (TTTHypothesisMealy) getHypothesisDS(); } @Override diff --git a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/moore/TTTLearnerMoore.java b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/moore/TTTLearnerMoore.java index 72e334ea3..c2475914c 100644 --- a/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/moore/TTTLearnerMoore.java +++ b/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/moore/TTTLearnerMoore.java @@ -140,7 +140,7 @@ protected AbstractBaseDTNode> createNewNode(AbstractBaseDTNode getHypothesisModel() { - return (TTTHypothesisMoore) hypothesis; + return (TTTHypothesisMoore) getHypothesisDS(); } } diff --git a/api/src/main/java/de/learnlib/LearnerStateTracker.java b/api/src/main/java/de/learnlib/LearnerStateTracker.java new file mode 100644 index 000000000..342a57fab --- /dev/null +++ b/api/src/main/java/de/learnlib/LearnerStateTracker.java @@ -0,0 +1,58 @@ +/* Copyright (C) 2013-2026 TU Dortmund University + * This file is part of LearnLib . + * + * 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; + +import de.learnlib.algorithm.LearningAlgorithm; + +/** + * A utility interface for managing the learner state as required by the {@link LearningAlgorithm} contract. Given an + * implementation of {@link #hasLearningProcessStarted()}, this interface provides default implementations for requiring + * whether the learning process has started yet. + */ +@FunctionalInterface +public interface LearnerStateTracker { + + /** + * Returns whether the learning process has started yet. + * + * @return {@code true} if the learning process has started, {@code false} otherwise + */ + boolean hasLearningProcessStarted(); + + /** + * Requires that the learning process has not yet started. + * + * @throws IllegalStateException + * if {@link #hasLearningProcessStarted()} returns {@code true} + */ + default void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + /** + * Requires that the learning process has started. + * + * @throws IllegalArgumentException + * if {@link #hasLearningProcessStarted()} returns {@code false} + */ + default void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started yet"); + } + } +} diff --git a/api/src/main/java/de/learnlib/algorithm/LearningAlgorithm.java b/api/src/main/java/de/learnlib/algorithm/LearningAlgorithm.java index af1b043f8..57c9951e0 100644 --- a/api/src/main/java/de/learnlib/algorithm/LearningAlgorithm.java +++ b/api/src/main/java/de/learnlib/algorithm/LearningAlgorithm.java @@ -39,21 +39,25 @@ public interface LearningAlgorithm { /** - * Starts the model inference process, creating an initial hypothesis in the provided model object. Please note that - * it should be illegal to invoke this method twice. + * Starts the model inference process, creating an initial hypothesis in the provided model object. + * + * @throws IllegalStateException + * if this method is invoked twice. */ void startLearning(); /** * Triggers a refinement of the model by providing a counterexample. A counterexample is a query which exposes - * different behavior of the real SUL compared to the hypothesis. Please note that invoking this method before an - * initial invocation of {@link #startLearning()} should be illegal. + * different behavior of the real SUL compared to the hypothesis. * * @param ceQuery * the query which exposes diverging behavior, as posed to the real SUL (i.e. with the SULs output). * * @return {@code true} if the counterexample triggered a refinement of the hypothesis, {@code false} otherwise * (i.e., it was no counterexample). + * + * @throws IllegalStateException + * if this method is invoked before an initial invocation of {@link #startLearning()}. */ boolean refineHypothesis(DefaultQuery ceQuery); @@ -64,11 +68,11 @@ public interface LearningAlgorithm { * code (i.e., M generally should refer to an immutable interface), and its validity is retained only until the next * invocation of {@link #refineHypothesis(DefaultQuery)}. If older hypotheses have to be maintained, a copy of the * returned model must be made. - *

- * Please note that it should be illegal to invoke this method before an initial invocation of {@link - * #startLearning()}. * * @return the current hypothesis model. + * + * @throws IllegalStateException + * if this method is invoked before an initial invocation of {@link #startLearning()}. */ M getHypothesisModel(); diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/testcase/AbstractLearnerVariantITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/testcase/AbstractLearnerVariantITCase.java index 7d980e500..9a13a1f1b 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/testcase/AbstractLearnerVariantITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/testcase/AbstractLearnerVariantITCase.java @@ -27,6 +27,7 @@ import de.learnlib.testsupport.it.variant.LearnerVariant; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.concept.FiniteRepresentation; +import net.automatalib.word.Word; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; @@ -70,10 +71,21 @@ public void testLearning() { long start = System.nanoTime(); + Assert.assertThrows("Learner should not return a hypothesis before learning has started", + IllegalStateException.class, + learner::getHypothesisModel); + Assert.assertThrows("Learner should not refine hypothesis before learning has started", + IllegalStateException.class, + () -> learner.refineHypothesis(new DefaultQuery<>(Word.epsilon()))); + lockableOracle.unlock(); learner.startLearning(); lockableOracle.lock(); + Assert.assertThrows("Learner should not be started twice after in succession", + IllegalStateException.class, + learner::startLearning); + int roundCounter = 0; DefaultQuery ceQuery; List> ceQueries = new ArrayList<>(); @@ -107,6 +119,10 @@ public void testLearning() { "Learner should not report a hypothesis update on outdated counterexample"); } + Assert.assertThrows("Learner should not be started after having finished", + IllegalStateException.class, + learner::startLearning); + long duration = (System.nanoTime() - start) / NANOS_PER_MILLISECOND; LOGGER.info(Category.EVENT, "Passed learner integration test {} ... took [{}]",