From 9029182d6b9110bd984abc6e53b3dca4f843fd4c Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 7 Jul 2026 16:40:42 +0200 Subject: [PATCH 1/5] first chunk of regular learners --- .../algorithm/adt/learner/ADTLearner.java | 14 +++++++- .../algorithm/dhc/mealy/MealyDHC.java | 35 ++++++++++++++----- .../algorithm/kv/dfa/KearnsVaziraniDFA.java | 13 ++++++- .../kv/mealy/KearnsVaziraniMealy.java | 13 ++++++- .../lambda/lstar/AbstractLLambda.java | 13 ++++++- .../lambda/ttt/AbstractTTTLambda.java | 15 ++++++-- .../lambda/ttt/dfa/TTTLambdaDFA.java | 1 + .../lambda/ttt/mealy/TTTLambdaMealy.java | 1 + .../algorithm/lsharp/LSharpMealy.java | 13 ++++++- .../algorithm/lstar/AbstractLStar.java | 14 ++++++++ .../lstar/dfa/ExtensibleLStarDFA.java | 1 + .../lstar/mealy/ClassicLStarMealy.java | 1 + .../lstar/mealy/ExtensibleLStarMealy.java | 1 + .../lstar/moore/ClassicLStarMoore.java | 1 + .../lstar/moore/ExtensibleLStarMoore.java | 1 + .../observationpack/AbstractOPLearner.java | 15 ++++++-- .../observationpack/dfa/OPLearnerDFA.java | 1 + .../observationpack/mealy/OPLearnerMealy.java | 1 + .../observationpack/moore/OPLearnerMoore.java | 1 + .../sparse/GenericSparseLearner.java | 14 +++++++- .../ttt/base/AbstractTTTLearner.java | 16 ++++++--- .../algorithm/ttt/dfa/TTTLearnerDFA.java | 9 +---- .../algorithm/ttt/mealy/TTTLearnerMealy.java | 2 +- .../algorithm/ttt/moore/TTTLearnerMoore.java | 2 +- .../learnlib/algorithm/LearningAlgorithm.java | 18 ++++++---- .../AbstractLearnerVariantITCase.java | 16 +++++++++ 26 files changed, 192 insertions(+), 40 deletions(-) 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..a2713f1a0 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 @@ -138,6 +138,7 @@ public ADTLearner(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); final ADTState initialState = this.hypothesis.addInitialState(); initialState.setAccessSequence(Word.epsilon()); @@ -315,6 +316,7 @@ private ADTNode, I, O> findNodeForState(ADTState state) { @Override public MealyMachine getHypothesisModel() { + requireLearningProcessStarted(); return this.hypothesis; } @@ -853,11 +855,21 @@ private List> getIncomingNonSpanningTreeTransitions(ADTState } private void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return !hypothesis.getStates().isEmpty(); + } + 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..a2b375c03 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 @@ -120,29 +120,44 @@ 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"); + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return hypothesis != null; + } + 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 +248,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,7 +261,7 @@ public boolean refineHypothesis(DefaultQuery> ceQuery) { @Override public CompactMealy getHypothesisModel() { - checkInternalState(); + requireLearningProcessStarted(); return hypothesis; } @@ -275,7 +290,9 @@ public void addAlphabetSymbol(I symbol) { this.splitters = newSplitters; - this.startLearning(); + if (hasLearningProcessStarted()) { + this.startLearningInternal(); + } } } @@ -293,7 +310,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..5b03f3664 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 @@ -113,6 +113,7 @@ public KearnsVaziraniDFA(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); initialize(); } @@ -316,11 +317,21 @@ 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..1f34bb7ab 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 @@ -116,6 +116,7 @@ public KearnsVaziraniMealy(Alphabet alphabet, @Override public void startLearning() { + requireLearningProcessNotStarted(); initialize(); } @@ -344,11 +345,21 @@ private List>> sift(List } private void requireLearningProcessStarted() { - if (hypothesis.size() == 0) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return this.hypothesis.size() != 0; + } + @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..391442b75 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 @@ -82,6 +82,7 @@ abstract class AbstractLLambda, I, D> implements OT @Override public void startLearning() { + requireLearningProcessNotStarted(); initTable(); learnLoop(); } @@ -296,11 +297,21 @@ private void addShortPrefix(Word shortPrefix) { } protected void requireLearningProcessStarted() { - if (rows.isEmpty()) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return !rows.isEmpty(); + } + @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..3d21a3865 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 @@ -75,6 +75,7 @@ protected AbstractTTTLambda(Alphabet alphabet, MembershipOracle mqs, Me @Override public void startLearning() { + requireLearningProcessNotStarted(); dtree().sift(mqs, ptree.root()); makeConsistent(mqs); started = true; @@ -214,12 +215,22 @@ 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..59cfbf206 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 @@ -350,6 +350,7 @@ ArrayStorage> getAccessMap() { @Override public void startLearning() { + requireLearningProcessNotStarted(); this.initObsTree(null); buildHypothesis(); } @@ -378,11 +379,21 @@ public Word transformAccessSequence(Word word) { } private void requireLearningProcessStarted() { - if (basisMap.isEmpty()) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return !basisMap.isEmpty(); + } + 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..24af0b6a5 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 @@ -76,6 +76,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 +86,7 @@ public void startLearning() { @Override public final boolean refineHypothesis(DefaultQuery ceQuery) { + requireLearningProcessStarted(); if (!MQUtil.isCounterexample(ceQuery, hypothesisOutput())) { return false; } @@ -242,4 +244,16 @@ public void addAlphabetSymbol(I symbol) { public Word transformAccessSequence(Word word) { return this.table.transformAccessSequence(word); } + + protected void requireLearningProcessStarted() { + if (!this.table.isInitialized()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (this.table.isInitialized()) { + throw new IllegalStateException("Learning process has already been started"); + } + } } 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/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/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..371b74115 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 @@ -88,6 +88,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,12 +253,22 @@ public OPLearnerHypothesis getHypothesisDS() { return hypothesis; } - private void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { + protected void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return !hypothesis.getStates().isEmpty(); + } + @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); 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/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..d3e98afe4 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 @@ -123,11 +123,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); @@ -419,11 +421,21 @@ private void updatePartitionMap(CoreRow c, Word suf, Word out) { } private void requireLearningProcessStarted() { - if (hyp.getStates().isEmpty()) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return !hyp.getStates().isEmpty(); + } + 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..a8291b979 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 @@ -162,9 +162,7 @@ protected static void link(AbstractBaseDTNode dtNode, TTTState init = hypothesis.initialize(); AbstractBaseDTNode initNode = dtree.sift(init.getAccessSequence(), false); @@ -949,11 +947,21 @@ public BaseTTTDiscriminationTree getDiscriminationTree() { } protected void requireLearningProcessStarted() { - if (hypothesis.getStates().isEmpty()) { + if (!hasLearningProcessStarted()) { throw new IllegalStateException("Learning process has not been started"); } } + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return hypothesis.isInitialized(); + } + @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/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 [{}]", From 4cf01c3ca97445f77c0ae1b4b6f1f30fcd3b60f9 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 8 Jul 2026 16:01:24 +0200 Subject: [PATCH 2/5] add non-regular learners --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 19 ++++++++++++++++ .../lstar/mmlt/MMLTObservationTable.java | 6 ++++- .../vpa/AbstractVPALearner.java | 19 ++++++++++++++++ .../vpa/hypothesis/OneSEVPAHypothesis.java | 4 ++++ .../algorithm/procedural/sba/SBALearner.java | 22 +++++++++++++++++++ .../algorithm/procedural/spa/SPALearner.java | 22 +++++++++++++++++++ .../procedural/spmm/SPMMLearner.java | 22 +++++++++++++++++++ 7 files changed, 113 insertions(+), 1 deletion(-) 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..014c6432d 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 @@ -257,6 +257,7 @@ public static int selectOneShotTimer(List> sortedT @Override public MMLT getHypothesisModel() { + requireLearningProcessStarted(); return getInternalHypothesisModel(); } @@ -319,6 +320,7 @@ private void updateOutputs() { @Override public void startLearning() { + requireLearningProcessNotStarted(); List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); @@ -330,6 +332,7 @@ public void startLearning() { @Override public boolean refineHypothesis(DefaultQuery, Word>> ceQuery) { + requireLearningProcessStarted(); if (!refineHypothesisSingle(ceQuery)) { return false; // no valid CEX } @@ -625,6 +628,22 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai return hypothesis; } + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return hypData.getTable().isInitialized(); + } + private static final class OutputQuery extends Query, Word>> { private final Word> label; 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/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..4466401a8 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 @@ -62,6 +62,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 +73,7 @@ public void startLearning() { @Override public boolean refineHypothesis(DefaultQuery ceQuery) { + requireLearningProcessStarted(); if (hypothesis.computeSuffixOutput(ceQuery.getPrefix(), ceQuery.getSuffix()).equals(ceQuery.getOutput())) { return false; } @@ -87,6 +89,7 @@ public boolean refineHypothesis(DefaultQuery ceQuery) { @Override public OneSEVPA getHypothesisModel() { + requireLearningProcessStarted(); return hypothesis; } @@ -262,6 +265,22 @@ protected HypLoc createLocation(AbstractHypTrans trans) { return hypothesis.createLocation(false, trans); } + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return hypothesis.isInitialized(); + } + public static final class BuilderDefaults { private BuilderDefaults() { 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/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..41cb9ba96 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 @@ -73,6 +73,7 @@ public class SBALearner> & SupportsGrow private final Map learners; private I initialCallSymbol; + boolean learningStarted; private final Map> mapping; @@ -98,6 +99,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 +113,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 +171,7 @@ private boolean refineHypothesisInternal(DefaultQuery defaultQuery) @Override public SBA getHypothesisModel() { + requireLearningProcessStarted(); if (this.learners.isEmpty()) { return new EmptySBA<>(this.alphabet); @@ -341,6 +347,22 @@ private boolean ensureCallAndReturnClosure(DFA> hyp, return true; } + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return this.learningStarted; + } + private static class Acex extends AbstractBaseCounterexample { private final Word input; 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..2350a7150 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 @@ -71,6 +71,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 +97,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 +165,7 @@ private boolean refineHypothesisInternal(DefaultQuery defaultQuery) @Override public SPA getHypothesisModel() { + requireLearningProcessStarted(); if (this.subLearners.isEmpty()) { return new EmptySPA<>(this.alphabet); @@ -293,6 +299,22 @@ private boolean checkSingleTerminatingSequence(Word input, Map> return refinement; } + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return this.learningStarted; + } + private final class Acex extends AbstractBaseCounterexample { private final Word input; 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..3a1c4be23 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 @@ -73,6 +73,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 +100,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 +114,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; @@ -168,6 +173,7 @@ private boolean refineHypothesisInternal(DefaultQuery> defaultQuery) @Override public SPMM getHypothesisModel() { + requireLearningProcessStarted(); if (this.learners.isEmpty()) { return new EmptySPMM<>(this.alphabet, errorOutput); @@ -368,4 +374,20 @@ private int detectMismatchingIdx(SPMM spmm, Word input, Wo throw new IllegalArgumentException("Non-counterexamples shouldn't be scanned for a mis-match"); } + + private void requireLearningProcessStarted() { + if (!hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has not been started"); + } + } + + private void requireLearningProcessNotStarted() { + if (hasLearningProcessStarted()) { + throw new IllegalStateException("Learning process has already been started"); + } + } + + private boolean hasLearningProcessStarted() { + return this.learningStarted; + } } From 63d341a93d0eae9295d488ecb8e4eddcecd85704 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 8 Jul 2026 18:03:33 +0200 Subject: [PATCH 3/5] share common functionality in utility interface --- .../algorithm/adt/learner/ADTLearner.java | 25 +++----- .../algorithm/dhc/mealy/MealyDHC.java | 25 +++----- .../algorithm/kv/dfa/KearnsVaziraniDFA.java | 25 +++----- .../kv/mealy/KearnsVaziraniMealy.java | 25 +++----- .../lambda/lstar/AbstractLLambda.java | 25 +++----- .../lambda/ttt/AbstractTTTLambda.java | 25 +++----- .../algorithm/lsharp/LSharpMealy.java | 24 +++----- .../algorithm/lstar/AbstractLStar.java | 20 +++---- .../lstar/mmlt/ExtensibleLStarMMLT.java | 24 +++----- .../algorithm/nlstar/NLStarLearner.java | 20 +++---- .../vpa/AbstractVPALearner.java | 25 +++----- .../observationpack/AbstractOPLearner.java | 19 ++---- .../algorithm/procedural/sba/SBALearner.java | 24 +++----- .../algorithm/procedural/spa/SPALearner.java | 24 +++----- .../procedural/spmm/SPMMLearner.java | 24 +++----- .../sparse/GenericSparseLearner.java | 24 +++----- .../ttt/base/AbstractTTTLearner.java | 25 +++----- .../java/de/learnlib/LearnerStateTracker.java | 58 +++++++++++++++++++ 18 files changed, 187 insertions(+), 274 deletions(-) create mode 100644 api/src/main/java/de/learnlib/LearnerStateTracker.java 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 a2713f1a0..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); @@ -320,6 +322,11 @@ private ADTNode, I, O> findNodeForState(ADTState state) { return this.hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return !hypothesis.getStates().isEmpty(); + } + /** * Close all pending open transitions. */ @@ -854,22 +861,6 @@ private List> getIncomingNonSpanningTreeTransitions(ADTState return result; } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return !hypothesis.getStates().isEmpty(); - } - 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 a2b375c03..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; @@ -125,22 +127,6 @@ public boolean addGlobalSuffixes(Collection> newGlobalSuffixes return addSuffixesUnchecked(newGlobalSuffixes); } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return hypothesis != null; - } - protected boolean addSuffixesUnchecked(Collection> newSuffixes) { int oldSize = hypothesis.size(); @@ -265,6 +251,11 @@ public CompactMealy getHypothesisModel() { return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis != null; + } + @Override public void addAlphabetSymbol(I symbol) { 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 5b03f3664..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); @@ -139,6 +141,11 @@ public DFA getHypothesisModel() { return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return this.hypothesis.size() != 0; + } + public BinaryDTree> getDiscriminationTree() { return discriminationTree; } @@ -316,22 +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 1f34bb7ab..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); @@ -144,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; } @@ -344,22 +351,6 @@ private List>> sift(List return result; } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return this.hypothesis.size() != 0; - } - @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 391442b75..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); @@ -117,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); @@ -296,22 +303,6 @@ private void addShortPrefix(Word shortPrefix) { } } - protected void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return !rows.isEmpty(); - } - @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 3d21a3865..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); @@ -111,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)) { @@ -215,22 +222,6 @@ private void analyzeCounterexample(DefaultQuery counterexample, Deque suspend() { return new TTTLambdaState<>(strie, ptree, dtree(), started); 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 59cfbf206..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; @@ -369,6 +370,11 @@ public boolean refineHypothesis(DefaultQuery> ceQuery) { return constructHypothesis(); } + @Override + public boolean hasLearningProcessStarted() { + return !basisMap.isEmpty(); + } + @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); @@ -378,22 +384,6 @@ public Word transformAccessSequence(Word word) { return accessMap.get(bs); } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return !basisMap.isEmpty(); - } - 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 24af0b6a5..9f74f1828 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; @@ -229,6 +231,11 @@ public ObservationTable getObservationTable() { return table; } + @Override + public boolean hasLearningProcessStarted() { + return this.table.isInitialized(); + } + @Override public void addAlphabetSymbol(I symbol) { @@ -245,15 +252,4 @@ public Word transformAccessSequence(Word word) { return this.table.transformAccessSequence(word); } - protected void requireLearningProcessStarted() { - if (!this.table.isInitialized()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (this.table.isInitialized()) { - throw new IllegalStateException("Learning process has already been started"); - } - } } 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 014c6432d..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"; @@ -490,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. @@ -628,22 +634,6 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai return hypothesis; } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return hypData.getTable().isInitialized(); - } - private static final class OutputQuery extends Query, Word>> { private final Word> label; 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 4466401a8..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; @@ -93,6 +95,11 @@ public OneSEVPA getHypothesisModel() { return hypothesis; } + @Override + public boolean hasLearningProcessStarted() { + return hypothesis.isInitialized(); + } + public DTree getDiscriminationTree() { return dtree; } @@ -265,22 +272,6 @@ protected HypLoc createLocation(AbstractHypTrans trans) { return hypothesis.createLocation(false, trans); } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return hypothesis.isInitialized(); - } - public static final class BuilderDefaults { private BuilderDefaults() { 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 371b74115..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; @@ -253,19 +255,8 @@ public OPLearnerHypothesis getHypothesisDS() { return hypothesis; } - protected void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { + @Override + public boolean hasLearningProcessStarted() { return !hypothesis.getStates().isEmpty(); } 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 41cb9ba96..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; @@ -210,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()) { @@ -347,22 +353,6 @@ private boolean ensureCallAndReturnClosure(DFA> hyp, return true; } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return this.learningStarted; - } - private static class Acex extends AbstractBaseCounterexample { private final Word input; 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 2350a7150..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; @@ -174,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()) { @@ -299,22 +305,6 @@ private boolean checkSingleTerminatingSequence(Word input, Map> return refinement; } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return this.learningStarted; - } - private final class Acex extends AbstractBaseCounterexample { private final Word input; 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 3a1c4be23..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; @@ -171,6 +172,11 @@ private boolean refineHypothesisInternal(DefaultQuery> defaultQuery) return true; } + @Override + public boolean hasLearningProcessStarted() { + return this.learningStarted; + } + @Override public SPMM getHypothesisModel() { requireLearningProcessStarted(); @@ -374,20 +380,4 @@ private int detectMismatchingIdx(SPMM spmm, Word input, Wo throw new IllegalArgumentException("Non-counterexamples shouldn't be scanned for a mis-match"); } - - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return this.learningStarted; - } } 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 d3e98afe4..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; @@ -160,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(); @@ -420,22 +426,6 @@ private void updatePartitionMap(CoreRow c, Word suf, Word out) { vecs.get(idx).set(c.idx); } - private void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return !hyp.getStates().isEmpty(); - } - 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 a8291b979..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); @@ -187,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. * @@ -946,22 +953,6 @@ public BaseTTTDiscriminationTree getDiscriminationTree() { return dtree; } - protected void requireLearningProcessStarted() { - if (!hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has not been started"); - } - } - - private void requireLearningProcessNotStarted() { - if (hasLearningProcessStarted()) { - throw new IllegalStateException("Learning process has already been started"); - } - } - - private boolean hasLearningProcessStarted() { - return hypothesis.isInitialized(); - } - @Override public Word transformAccessSequence(Word word) { requireLearningProcessStarted(); 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"); + } + } +} From fa0cb606da37f5cde768b503b459924df55aebff Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 8 Jul 2026 18:20:13 +0200 Subject: [PATCH 4/5] formatting --- .../src/main/java/de/learnlib/algorithm/lstar/AbstractLStar.java | 1 - 1 file changed, 1 deletion(-) 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 9f74f1828..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 @@ -251,5 +251,4 @@ public void addAlphabetSymbol(I symbol) { public Word transformAccessSequence(Word word) { return this.table.transformAccessSequence(word); } - } From 98bdbddc6c3fc5a7a05176700c469fd7363f0fd4 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 8 Jul 2026 19:40:35 +0200 Subject: [PATCH 5/5] add changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) 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`).