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); + } +}