diff --git a/algorithms/active/aaar/pom.xml b/algorithms/active/aaar/pom.xml
index 3da26115b..4d928a844 100644
--- a/algorithms/active/aaar/pom.xml
+++ b/algorithms/active/aaar/pom.xml
@@ -96,6 +96,11 @@ limitations under the License.
learnlib-observation-pack
test
+
+ de.learnlib
+ learnlib-sparse
+ test
+
de.learnlib
learnlib-ttt
diff --git a/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AAARTestUtil.java b/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AAARTestUtil.java
index 7bcf2f149..6c8042135 100644
--- a/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AAARTestUtil.java
+++ b/algorithms/active/aaar/src/test/java/de/learnlib/algorithm/aaar/AAARTestUtil.java
@@ -16,10 +16,8 @@
package de.learnlib.algorithm.aaar;
import java.util.Arrays;
-import java.util.Collections;
import java.util.List;
-import de.learnlib.acex.AcexAnalyzers;
import de.learnlib.algorithm.LearningAlgorithm.DFALearner;
import de.learnlib.algorithm.LearningAlgorithm.MealyLearner;
import de.learnlib.algorithm.LearningAlgorithm.MooreLearner;
@@ -27,8 +25,6 @@
import de.learnlib.algorithm.kv.mealy.KearnsVaziraniMealy;
import de.learnlib.algorithm.lambda.ttt.dfa.TTTLambdaDFA;
import de.learnlib.algorithm.lambda.ttt.mealy.TTTLambdaMealy;
-import de.learnlib.algorithm.lstar.ce.ObservationTableCEXHandlers;
-import de.learnlib.algorithm.lstar.closing.ClosingStrategies;
import de.learnlib.algorithm.lstar.dfa.ClassicLStarDFA;
import de.learnlib.algorithm.lstar.mealy.ExtensibleLStarMealy;
import de.learnlib.algorithm.lstar.moore.ExtensibleLStarMoore;
@@ -38,10 +34,10 @@
import de.learnlib.algorithm.rivestschapire.RivestSchapireDFA;
import de.learnlib.algorithm.rivestschapire.RivestSchapireMealy;
import de.learnlib.algorithm.rivestschapire.RivestSchapireMoore;
+import de.learnlib.algorithm.sparse.SparseLearner;
import de.learnlib.algorithm.ttt.dfa.TTTLearnerDFA;
import de.learnlib.algorithm.ttt.mealy.TTTLearnerMealy;
import de.learnlib.algorithm.ttt.moore.TTTLearnerMoore;
-import de.learnlib.counterexample.LocalSuffixFinders;
import net.automatalib.common.util.Pair;
import net.automatalib.word.Word;
@@ -55,63 +51,46 @@ public static List, I,
final ComboConstructor, I, Boolean> lstar = ClassicLStarDFA::new;
final ComboConstructor, I, Boolean> rs = RivestSchapireDFA::new;
- final ComboConstructor, I, Boolean> kv =
- (alph, mqo) -> new KearnsVaziraniDFA<>(alph, mqo, true, AcexAnalyzers.BINARY_SEARCH_FWD);
- final ComboConstructor, I, Boolean> dt =
- (alph, mqo) -> new OPLearnerDFA<>(alph, mqo, LocalSuffixFinders.RIVEST_SCHAPIRE, true, true);
- final ComboConstructor, I, Boolean> ttt =
- (alph, mqo) -> new TTTLearnerDFA<>(alph, mqo, AcexAnalyzers.BINARY_SEARCH_FWD);
- final ComboConstructor, I, Boolean> lambda = (alph, mqo) -> new TTTLambdaDFA<>(alph, mqo, mqo);
+ final ComboConstructor, I, Boolean> kv = KearnsVaziraniDFA::new;
+ final ComboConstructor, I, Boolean> op = OPLearnerDFA::new;
+ final ComboConstructor, I, Boolean> ttt = TTTLearnerDFA::new;
+ final ComboConstructor, I, Boolean> lambda = TTTLambdaDFA::new;
return Arrays.asList(Pair.of("L*", lstar),
Pair.of("RS", rs),
Pair.of("KV", kv),
- Pair.of("DT", dt),
+ Pair.of("OP", op),
Pair.of("TTT", ttt),
Pair.of("TTTLambda", lambda));
}
public static List, I, Word>>> getMealyLearners() {
- final ComboConstructor, I, Word> lstar =
- (alph, mqo) -> new ExtensibleLStarMealy<>(alph,
- mqo,
- Collections.emptyList(),
- ObservationTableCEXHandlers.CLASSIC_LSTAR,
- ClosingStrategies.CLOSE_FIRST);
+ final ComboConstructor, I, Word> lstar = ExtensibleLStarMealy::new;
final ComboConstructor, I, Word> rs = RivestSchapireMealy::new;
- final ComboConstructor, I, Word> kv =
- (alph, mqo) -> new KearnsVaziraniMealy<>(alph, mqo, true, AcexAnalyzers.BINARY_SEARCH_FWD);
- final ComboConstructor, I, Word> dt =
- (alph, mqo) -> new OPLearnerMealy<>(alph, mqo, LocalSuffixFinders.RIVEST_SCHAPIRE, true);
- final ComboConstructor, I, Word> ttt =
- (alph, mqo) -> new TTTLearnerMealy<>(alph, mqo, AcexAnalyzers.BINARY_SEARCH_FWD);
- final ComboConstructor, I, Word> lambda =
- (alph, mqo) -> new TTTLambdaMealy<>(alph, mqo, mqo);
+ final ComboConstructor, I, Word> kv = KearnsVaziraniMealy::new;
+ final ComboConstructor, I, Word> op = OPLearnerMealy::new;
+ final ComboConstructor, I, Word> sparse = SparseLearner::new;
+ final ComboConstructor, I, Word> ttt = TTTLearnerMealy::new;
+ final ComboConstructor, I, Word> lambda = TTTLambdaMealy::new;
return Arrays.asList(Pair.of("L*", lstar),
Pair.of("RS", rs),
Pair.of("KV", kv),
- Pair.of("DT", dt),
+ Pair.of("OP", op),
+ Pair.of("Sparse", sparse),
Pair.of("TTT", ttt),
Pair.of("TTTLambda", lambda));
}
public static List, I, Word>>> getMooreLearners() {
- final ComboConstructor, I, Word> lstar =
- (alph, mqo) -> new ExtensibleLStarMoore<>(alph,
- mqo,
- Collections.emptyList(),
- ObservationTableCEXHandlers.CLASSIC_LSTAR,
- ClosingStrategies.CLOSE_FIRST);
+ final ComboConstructor, I, Word> lstar = ExtensibleLStarMoore::new;
final ComboConstructor, I, Word> rs = RivestSchapireMoore::new;
- final ComboConstructor, I, Word> dt =
- (alph, mqo) -> new OPLearnerMoore<>(alph, mqo, LocalSuffixFinders.RIVEST_SCHAPIRE, true);
- final ComboConstructor, I, Word> ttt =
- (alph, mqo) -> new TTTLearnerMoore<>(alph, mqo, AcexAnalyzers.BINARY_SEARCH_FWD);
+ final ComboConstructor, I, Word> op = OPLearnerMoore::new;
+ final ComboConstructor, I, Word> ttt = TTTLearnerMoore::new;
- return Arrays.asList(Pair.of("L*", lstar), Pair.of("RS", rs), Pair.of("DT", dt), Pair.of("TTT", ttt));
+ return Arrays.asList(Pair.of("L*", lstar), Pair.of("RS", rs), Pair.of("OP", op), Pair.of("TTT", ttt));
}
}
diff --git a/algorithms/active/procedural/pom.xml b/algorithms/active/procedural/pom.xml
index c04efb36b..7383bd476 100644
--- a/algorithms/active/procedural/pom.xml
+++ b/algorithms/active/procedural/pom.xml
@@ -98,6 +98,11 @@ limitations under the License.
learnlib-observation-pack
test
+
+ de.learnlib
+ learnlib-sparse
+ test
+
de.learnlib
learnlib-ttt
diff --git a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/it/SPMMIT.java b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/it/SPMMIT.java
index 8a5e6a19e..70fb34d19 100644
--- a/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/it/SPMMIT.java
+++ b/algorithms/active/procedural/src/test/java/de/learnlib/algorithm/procedural/spmm/it/SPMMIT.java
@@ -34,6 +34,7 @@
import de.learnlib.algorithm.procedural.spmm.manager.DefaultATManager;
import de.learnlib.algorithm.procedural.spmm.manager.OptimizingATManager;
import de.learnlib.algorithm.rivestschapire.RivestSchapireMealy;
+import de.learnlib.algorithm.sparse.SparseLearner;
import de.learnlib.algorithm.ttt.mealy.TTTLearnerMealy;
import de.learnlib.oracle.MembershipOracle;
import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
@@ -61,6 +62,7 @@ protected void addLearnerVariants(ProceduralInputAlphabet alphabet,
builder.addLearnerVariant(TTTLambdaMealy::new);
builder.addLearnerVariant(RivestSchapireMealy::new);
builder.addLearnerVariant(TTTLearnerMealy::new);
+ builder.addLearnerVariant(SparseLearner::new);
}
private static class Builder {
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 678183285..d660ae7e2 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
@@ -28,19 +28,21 @@
import de.learnlib.AccessSequenceTransformer;
import de.learnlib.algorithm.LearningAlgorithm.MealyLearner;
import de.learnlib.counterexample.LocalSuffixFinders;
-import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
+import de.learnlib.oracle.MembershipOracle;
import de.learnlib.query.DefaultQuery;
import de.learnlib.util.mealy.MealyUtil;
import net.automatalib.alphabet.Alphabet;
+import net.automatalib.alphabet.SupportsGrowingAlphabet;
import net.automatalib.automaton.transducer.MealyMachine;
import net.automatalib.automaton.transducer.MutableMealyMachine;
import net.automatalib.common.util.Pair;
import net.automatalib.word.Word;
-class GenericSparseLearner implements MealyLearner, AccessSequenceTransformer {
+class GenericSparseLearner & SupportsGrowingAlphabet, S, I, O>
+ implements MealyLearner, AccessSequenceTransformer, SupportsGrowingAlphabet {
private final Alphabet alphabet;
- private final MealyMembershipOracle oracle;
+ private final MembershipOracle> oracle;
/**
* Suffixes.
@@ -76,7 +78,7 @@ class GenericSparseLearner implements MealyLearner, AccessSequenc
/**
* Hypothesis.
*/
- private final MutableMealyMachine hyp;
+ private final M hyp;
/**
* Maps each state to its core row prefix.
@@ -99,9 +101,9 @@ class GenericSparseLearner implements MealyLearner, AccessSequenc
private final Map, Map, Integer>> sufToOutToIdx;
protected GenericSparseLearner(Alphabet alphabet,
- MealyMembershipOracle oracle,
+ MembershipOracle> oracle,
List> initialSuffixes,
- MutableMealyMachine emptyMachine) {
+ M emptyMachine) {
this.alphabet = alphabet;
this.oracle = oracle;
sufs = new ArrayDeque<>(initialSuffixes);
@@ -131,7 +133,7 @@ public void startLearning() {
cRows.add(c);
sufs.forEach(s -> addSuffixToCoreRow(c, s));
stateToPrefix.put(init, c.prefix);
- extendFringe(c, init, new Leaf<>(c, 1, sufs.size(), Collections.emptyList()));
+ extendFringe(c, new Leaf<>(c, 1, sufs.size(), Collections.emptyList()));
fRows.forEach(f -> query(f, Word.epsilon())); // query transition outputs
// initially, transition outputs must be queried manually,
// for later transitions, they derive from suffix queries
@@ -162,6 +164,29 @@ public Word transformAccessSequence(Word word) {
return accSeq.apply(word);
}
+ @Override
+ public void addAlphabetSymbol(I i) {
+ alphabet.asGrowingAlphabetOrThrowException().addSymbol(i);
+ hyp.addAlphabetSymbol(i);
+ if (cRows.isEmpty()) {
+ return; // learning has not started yet
+ }
+
+ // add new fringe rows
+ if (cRows.size() == 1) {
+ // there is only a single state yet
+ final Leaf l = fRows.getFirst().leaf;
+ assert l != null;
+ final FringeRow f = addFringeRow(cRows.get(0), i, l);
+ query(f, Word.epsilon()); // for the first state, transition outputs must be queried manually
+ } else {
+ final Leaf l = new Leaf<>(); // fringe rows that spawn together should share the same leaf
+ cRows.forEach(c -> addFringeRow(c, i, l));
+ }
+
+ updateHypothesis();
+ }
+
private void updateHypothesis() {
for (FringeRow f : fRows) {
classifyFringePrefix(f);
@@ -303,7 +328,7 @@ private int moveToCore(FringeRow f, List cellIds) {
}
cRows.add(c);
- extendFringe(c, state, new Leaf<>());
+ extendFringe(c, new Leaf<>());
assert c.cellIds.size() == sufs.size();
assert c == cRows.get(c.idx);
return c.idx;
@@ -321,16 +346,26 @@ private List completeRowObservations(FringeRow f, List c, S state, Leaf leaf) {
+ /**
+ * Add fringe rows for the transitions from a new core prefix.
+ */
+ private void extendFringe(CoreRow c, Leaf l) {
for (I i : alphabet) {
- // add missing fringe rows for new transitions
- final Word prefix = c.prefix.append(i);
- final FringeRow fRow = new FringeRow<>(prefix, state, leaf);
- prefToFringe.put(prefix, fRow);
- fRows.push(fRow); // prioritize new rows during classification
+ addFringeRow(c, i, l);
}
}
+ /**
+ * Creates a new fringe row and returns it after integrating it into the internal data structures.
+ */
+ private FringeRow addFringeRow(CoreRow c, I i, Leaf l) {
+ final Word prefix = c.prefix.append(i);
+ final FringeRow f = new FringeRow<>(prefix, c.state, l);
+ prefToFringe.put(prefix, f);
+ fRows.push(f); // prioritize new rows during classification
+ return f;
+ }
+
private void identifyNewState(DefaultQuery> q) {
final Word cex = q.getInput();
final int idxSuf = LocalSuffixFinders.findRivestSchapire(q, accSeq::apply, hyp, oracle);
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/SparseLearner.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/SparseLearner.java
index 3e9a6301d..ba718585f 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/SparseLearner.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/SparseLearner.java
@@ -18,7 +18,7 @@
import java.util.Collections;
import java.util.List;
-import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
+import de.learnlib.oracle.MembershipOracle;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.automaton.transducer.impl.CompactMealy;
import net.automatalib.word.Word;
@@ -28,13 +28,13 @@
* Learning Mealy Machines with Sparse Observation Tables
* by Wolffhardt Schwabe, Paul Kogel, and Sabine Glesner.
*/
-public class SparseLearner extends GenericSparseLearner {
+public class SparseLearner extends GenericSparseLearner, Integer, I, O> {
- public SparseLearner(Alphabet alphabet, MealyMembershipOracle oracle) {
+ public SparseLearner(Alphabet alphabet, MembershipOracle> oracle) {
this(alphabet, oracle, Collections.emptyList());
}
- public SparseLearner(Alphabet alphabet, MealyMembershipOracle oracle, List> initialSuffixes) {
+ public SparseLearner(Alphabet alphabet, MembershipOracle> oracle, List> initialSuffixes) {
super(alphabet, oracle, initialSuffixes, new CompactMealy<>(alphabet));
}
}
diff --git a/algorithms/active/sparse/src/test/java/de/learnlib/algorithm/sparse/SparseGrowingAlphabetTest.java b/algorithms/active/sparse/src/test/java/de/learnlib/algorithm/sparse/SparseGrowingAlphabetTest.java
new file mode 100644
index 000000000..887ee4188
--- /dev/null
+++ b/algorithms/active/sparse/src/test/java/de/learnlib/algorithm/sparse/SparseGrowingAlphabetTest.java
@@ -0,0 +1,29 @@
+/* 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.algorithm.sparse;
+
+import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
+import de.learnlib.testsupport.AbstractGrowingAlphabetMealyTest;
+import net.automatalib.alphabet.Alphabet;
+
+public class SparseGrowingAlphabetTest extends AbstractGrowingAlphabetMealyTest> {
+
+ @Override
+ protected SparseLearner getLearner(MealyMembershipOracle oracle,
+ Alphabet alphabet) {
+ return new SparseLearner<>(alphabet, oracle);
+ }
+}