.
+ *
+ * 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.
+ */
+
+/**
+ * This module provides the implementation of the Sparse OT learning algorithm as described in the paper "Learning Mealy Machines with Sparse Observation Tables".
+ *
+ * This module is provided by the following Maven dependency:
+ *
+ * <dependency>
+ * <groupId>de.learnlib</groupId>
+ * <artifactId>learnlib-sparse</artifactId>
+ * <version>${version}</version>
+ * </dependency>
+ *
+ */
+open module de.learnlib.algorithm.sparse {
+
+ requires de.learnlib.common.counterexample;
+ requires de.learnlib.common.util;
+ requires de.learnlib.api;
+ requires net.automatalib.core;
+ requires net.automatalib.api;
+ requires net.automatalib.common.util;
+
+ exports de.learnlib.algorithm.sparse;
+}
diff --git a/algorithms/active/sparse/src/test/java/sparse/it/SparseIT.java b/algorithms/active/sparse/src/test/java/sparse/it/SparseIT.java
new file mode 100644
index 000000000..c708097a3
--- /dev/null
+++ b/algorithms/active/sparse/src/test/java/sparse/it/SparseIT.java
@@ -0,0 +1,33 @@
+/* Copyright (C) 2013-2025 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 sparse.it;
+
+import de.learnlib.algorithm.sparse.SparseLearner;
+import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
+import de.learnlib.testsupport.it.learner.AbstractMealyLearnerIT;
+import de.learnlib.testsupport.it.learner.LearnerVariantList;
+import net.automatalib.alphabet.Alphabet;
+
+public class SparseIT extends AbstractMealyLearnerIT {
+
+ @Override
+ protected void addLearnerVariants(Alphabet alphabet,
+ int targetSize,
+ MealyMembershipOracle mqOracle,
+ LearnerVariantList.MealyLearnerVariantList variants) {
+ variants.addLearnerVariant("sparse", new SparseLearner<>(alphabet, mqOracle));
+ }
+}
From d43221e4dbcabcf9421b1258f1f46485b1ecc0b7 Mon Sep 17 00:00:00 2001
From: stateMachinist <190835587+stateMachinist@users.noreply.github.com>
Date: Mon, 11 Aug 2025 20:30:43 +0200
Subject: [PATCH 2/5] fixed comment
---
.../main/java/de/learnlib/algorithm/sparse/SparseLearner.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
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 585f4038e..4f7ff2630 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
@@ -24,7 +24,7 @@
/**
* optimized implementation of the Ls learning algorithm,
- * as described in the appendix of the paper
+ * as described in section 6 of the paper "Learning Mealy Machines with Sparse Observation Tables"
*/
public class SparseLearner extends AbstractSparseLearner {
From dbeac250a506c9112709b8b0dd9ccf2b068ac31e Mon Sep 17 00:00:00 2001
From: stateMachinist <190835587+stateMachinist@users.noreply.github.com>
Date: Wed, 3 Sep 2025 16:23:09 +0200
Subject: [PATCH 3/5] adjustments for code style
---
algorithms/active/sparse/pom.xml | 16 +++++++
.../de/learnlib/algorithm/sparse/CoreRow.java | 7 +++-
...Learner.java => GenericSparseLearner.java} | 42 ++++++++++++-------
.../de/learnlib/algorithm/sparse/Leaf.java | 4 +-
.../de/learnlib/algorithm/sparse/Node.java | 6 +--
.../de/learnlib/algorithm/sparse/Row.java | 4 +-
.../learnlib/algorithm/sparse/Separator.java | 4 +-
.../algorithm/sparse/SparseLearner.java | 11 ++---
8 files changed, 62 insertions(+), 32 deletions(-)
rename algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/{AbstractSparseLearner.java => GenericSparseLearner.java} (92%)
diff --git a/algorithms/active/sparse/pom.xml b/algorithms/active/sparse/pom.xml
index 6e678f3db..24385d987 100644
--- a/algorithms/active/sparse/pom.xml
+++ b/algorithms/active/sparse/pom.xml
@@ -33,6 +33,22 @@ limitations under the License.
+
+ net.automatalib
+ automata-api
+
+
+ net.automatalib
+ automata-commons-util
+
+
+ net.automatalib
+ automata-core
+
+
+ de.learnlib
+ learnlib-api
+
de.learnlib
learnlib-util
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/CoreRow.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/CoreRow.java
index a526bd347..52816609e 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/CoreRow.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/CoreRow.java
@@ -15,9 +15,12 @@
*/
package de.learnlib.algorithm.sparse;
-import net.automatalib.word.Word;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
-import java.util.*;
+import net.automatalib.word.Word;
class CoreRow extends Row {
final S state; // hypothesis state associated with this row
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/AbstractSparseLearner.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java
similarity index 92%
rename from algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/AbstractSparseLearner.java
rename to algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java
index 74cf4539b..444020d8d 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/AbstractSparseLearner.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/GenericSparseLearner.java
@@ -15,6 +15,17 @@
*/
package de.learnlib.algorithm.sparse;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
import de.learnlib.algorithm.LearningAlgorithm.MealyLearner;
import de.learnlib.counterexample.LocalSuffixFinders;
import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
@@ -26,11 +37,7 @@
import net.automatalib.common.util.Pair;
import net.automatalib.word.Word;
-import java.util.*;
-import java.util.function.Function;
-import java.util.stream.Collectors;
-
-abstract class AbstractSparseLearner implements MealyLearner {
+class GenericSparseLearner implements MealyLearner {
private final Alphabet alphabet;
private final MealyMembershipOracle oracle;
@@ -49,10 +56,11 @@ abstract class AbstractSparseLearner implements MealyLearner {
private final Map, List> sufToVecs;
private final Map, Map, Integer>> sufToOutToIdx;
- AbstractSparseLearner(Alphabet alphabet,
- MealyMembershipOracle oracle,
- List> initialSuffixes,
- MutableMealyMachine initialHypothesis) {
+ protected GenericSparseLearner(Alphabet alphabet,
+ MealyMembershipOracle oracle,
+ List> initialSuffixes,
+ MutableMealyMachine emptyMachine) {
+ assert emptyMachine.size() == 0;
this.alphabet = alphabet;
this.oracle = oracle;
sufs = new ArrayDeque<>(initialSuffixes);
@@ -61,7 +69,7 @@ abstract class AbstractSparseLearner implements MealyLearner {
prefToFringe = new HashMap<>();
cells = new ArrayList<>();
cellToIdx = new HashMap<>();
- hyp = initialHypothesis;
+ hyp = emptyMachine;
stateToPrefix = new HashMap<>();
accSeq = p -> stateToPrefix.get(hyp.getState(p));
sufToVecs = new HashMap<>();
@@ -209,8 +217,9 @@ private Word query(Row r, Word suf) {
return out.suffix(suf.length());
}
- /** adds suffix-output pair to index if not yet contained
- * and returns a unique identifier representing the pair */
+ /**
+ * adds suffix-output pair to index if not yet contained
+ * and returns a unique identifier representing the pair. */
private int getUniqueCellIdx(Word suf, Word out) {
assert suf.length() == out.length();
final Pair, Word> cell = Pair.of(suf, out);
@@ -223,7 +232,7 @@ private int getUniqueCellIdx(Word suf, Word out) {
return idx;
}
- /** returns index of new core row */
+ /** returns index of new core row. */
private int moveToCore(FringeRow f, List cellIds) {
assert fRows.contains(f);
fRows.remove(f);
@@ -244,8 +253,9 @@ private int moveToCore(FringeRow f, List cellIds) {
return c.idx;
}
- /** takes fringe row and its observations, queries the missing entries
- * and returns a list containing the observations for all suffixes */
+ /**
+ * takes fringe row and its observations, queries the missing entries
+ * and returns a list containing the observations for all suffixes. */
private List completeRowObservations(FringeRow f, List cellIds) {
final List> sufsPresent = cellIds.stream().map(c -> this.cells.get(c).getFirst()).collect(Collectors.toList());
final List> sufsMissing = sufs.stream().filter(s -> !sufsPresent.contains(s)).collect(Collectors.toList());
@@ -316,4 +326,4 @@ private void updatePartitionMap(CoreRow c, Word suf, Word out) {
vecs.get(idx).set(c.idx);
}
-}
\ No newline at end of file
+}
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Leaf.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Leaf.java
index 322a2709d..10a8e1735 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Leaf.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Leaf.java
@@ -31,7 +31,7 @@ class Leaf extends Node {
// to check whether the separator is still guaranteed to be optimal
// or if it needs to be recomputed.
- /** creates split leaf without observations */
+ /** creates split leaf without observations. */
Leaf() {
super(Collections.emptyList());
cRow = null;
@@ -41,7 +41,7 @@ class Leaf extends Node {
// timestamps will be updated automatically
}
- /** creates unsplit leaf associated with the given core row and observations */
+ /** creates unsplit leaf associated with the given core row and observations. */
Leaf(CoreRow cRow, int numCRows, int numSufs, List cellIds) {
super(cellIds);
this.cRow = cRow;
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Node.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Node.java
index 455505620..0ad49b557 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Node.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Node.java
@@ -18,16 +18,16 @@
import java.util.BitSet;
import java.util.List;
-abstract class Node { // type parameters required for safe casting
+class Node { // type parameters required for safe casting
final List cellsIds; // cell identifiers of the fringe rows at this node
final BitSet remRows;
- Node(List cellsIds) {
+ protected Node(List cellsIds) {
this(cellsIds, new BitSet());
}
- Node(List cellsIds, BitSet remRows) {
+ protected Node(List cellsIds, BitSet remRows) {
this.cellsIds = cellsIds;
this.remRows = remRows;
}
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Row.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Row.java
index 1ad9c082d..26ee62442 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Row.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Row.java
@@ -17,10 +17,10 @@
import net.automatalib.word.Word;
-abstract class Row { // type parameters required for safe casting
+class Row { // type parameters required for safe casting
final Word prefix;
- Row(Word prefix) {
+ protected Row(Word prefix) {
this.prefix = prefix;
}
}
diff --git a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Separator.java b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Separator.java
index 1a278d4dd..61a5f2705 100644
--- a/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Separator.java
+++ b/algorithms/active/sparse/src/main/java/de/learnlib/algorithm/sparse/Separator.java
@@ -15,13 +15,13 @@
*/
package de.learnlib.algorithm.sparse;
-import net.automatalib.word.Word;
-
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import net.automatalib.word.Word;
+
class Separator extends Node {
final Word suffix;
final Map, Node> branchMap;
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 4f7ff2630..6412190e9 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
@@ -15,18 +15,19 @@
*/
package de.learnlib.algorithm.sparse;
+import java.util.Collections;
+import java.util.List;
+
import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle;
import net.automatalib.alphabet.Alphabet;
import net.automatalib.automaton.transducer.impl.CompactMealy;
import net.automatalib.word.Word;
-import java.util.*;
-
/**
* optimized implementation of the Ls learning algorithm,
- * as described in section 6 of the paper "Learning Mealy Machines with Sparse Observation Tables"
+ * as described in section 6 of the paper "Learning Mealy Machines with Sparse Observation Tables".
*/
-public class SparseLearner extends AbstractSparseLearner {
+public class SparseLearner extends GenericSparseLearner {
public SparseLearner(Alphabet alphabet, MealyMembershipOracle oracle) {
this(alphabet, oracle, Collections.emptyList());
@@ -35,4 +36,4 @@ public SparseLearner(Alphabet alphabet, MealyMembershipOracle oracle) {
public SparseLearner(Alphabet alphabet, MealyMembershipOracle oracle, List> initialSuffixes) {
super(alphabet, oracle, initialSuffixes, new CompactMealy<>(alphabet));
}
-}
\ No newline at end of file
+}
From 13e7214ba804c5d833052620ec275a950d95c65e Mon Sep 17 00:00:00 2001
From: stateMachinist <190835587+stateMachinist@users.noreply.github.com>
Date: Wed, 3 Sep 2025 21:12:17 +0200
Subject: [PATCH 4/5] extended contributor list
---
pom.xml | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/pom.xml b/pom.xml
index 6245bdfce..4c88da799 100644
--- a/pom.xml
+++ b/pom.xml
@@ -153,6 +153,15 @@ limitations under the License.
Developer
+
+ Wolffhardt Schwabe
+ schwabe@tu-berlin.de
+ TU Berlin, Software and Embedded Systems Engineering
+ https://tu.berlin/sese/
+
+ Developer
+
+