From e2c174e023855b5e2e0185ec7756e28b75777a49 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 8 Oct 2025 14:31:08 +0200 Subject: [PATCH 01/55] Made RowImpl public to enable access in custom observation table implementations. --- .../de/learnlib/datastructure/observationtable/RowImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java b/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java index cdee25aa0..79291c63a 100644 --- a/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java +++ b/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java @@ -18,7 +18,7 @@ import net.automatalib.common.util.array.ArrayStorage; import net.automatalib.word.Word; -final class RowImpl implements Row { +public final class RowImpl implements Row { private final Word label; private final int rowId; From 4807bc30e360d2d2866d0dc842034fdb875f4b39 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 8 Oct 2025 15:57:26 +0200 Subject: [PATCH 02/55] Made several methods of RowImpl public, to enable external access from observation tables that are defined in the learner module. --- .../datastructure/observationtable/RowImpl.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java b/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java index 79291c63a..b75ec76e7 100644 --- a/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java +++ b/commons/datastructures/src/main/java/de/learnlib/datastructure/observationtable/RowImpl.java @@ -37,7 +37,7 @@ public final class RowImpl implements Row { * @param alphabetSize * the size of the alphabet, used for initializing the successor array */ - RowImpl(Word label, int rowId, int alphabetSize) { + public RowImpl(Word label, int rowId, int alphabetSize) { this(label, rowId); makeShort(alphabetSize); @@ -51,7 +51,7 @@ public final class RowImpl implements Row { * @param rowId * the unique row identifier */ - RowImpl(Word label, int rowId) { + public RowImpl(Word label, int rowId) { this.label = label; this.rowId = rowId; } @@ -63,7 +63,7 @@ public final class RowImpl implements Row { * @param initialAlphabetSize * the size of the input alphabet. */ - void makeShort(int initialAlphabetSize) { + public void makeShort(int initialAlphabetSize) { if (lpIndex == -1) { return; } @@ -85,7 +85,7 @@ public RowImpl getSuccessor(int inputIdx) { * @param succ * the successor row */ - void setSuccessor(int inputIdx, RowImpl succ) { + public void setSuccessor(int inputIdx, RowImpl succ) { successors.set(inputIdx, succ); } @@ -110,7 +110,7 @@ public int getRowContentId() { * @param id * the contents id */ - void setRowContentId(int id) { + public void setRowContentId(int id) { this.rowContentId = id; } @@ -127,11 +127,11 @@ int getLpIndex() { return lpIndex; } - void setLpIndex(int lpIndex) { + public void setLpIndex(int lpIndex) { this.lpIndex = lpIndex; } - void ensureInputCapacity(int capacity) { + public void ensureInputCapacity(int capacity) { this.successors.ensureCapacity(capacity); } } From b029c9c63952199bdb0dfcf4fb1c69f645b068a8 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 12:18:05 +0200 Subject: [PATCH 03/55] Started with integration of MMLT. --- .../lstar/mmlt/LStarLocalTimerMealy.java | 431 +++++++++++++ .../LStarLocalTimerMealyHypDataContainer.java | 72 +++ .../LocalTimerMealyHypothesisBuilder.java | 127 ++++ .../mmlt/LocalTimerMealyObservationTable.java | 582 ++++++++++++++++++ .../lstar/mmlt/LocationTimerInfo.java | 119 ++++ .../lstar/mmlt/cex/CexPreprocessor.java | 37 ++ .../lstar/mmlt/cex/ExtendedDecomposition.java | 39 ++ ...lTimerMealyCounterexampleDecompositor.java | 139 +++++ .../LocalTimerMealyCounterexampleHandler.java | 176 ++++++ ...alTimerMealyInconsPrefixTransformAcex.java | 66 ++ .../LocalTimerMealyOutputInconsistency.java | 22 + .../mmlt/cex/results/CexAnalysisResult.java | 11 + .../mmlt/cex/results/FalseIgnoreResult.java | 29 + .../results/MissingDiscriminatorResult.java | 36 ++ .../cex/results/MissingOneShotResult.java | 28 + .../mmlt/cex/results/MissingResetResult.java | 30 + .../IInternalLocalTimerMealyHypothesis.java | 55 ++ .../mmlt/hyp/LocalTimerMealyHypothesis.java | 118 ++++ .../algorithm/LocalTimerMealyModelParams.java | 40 ++ .../de/learnlib/oracle/TimedQueryOracle.java | 82 +++ .../container/DummyStatsContainer.java | 61 ++ .../container/LearnerStatsProvider.java | 13 + .../statistic/container/StatsContainerX.java | 124 ++++ .../de/learnlib/sul/LocalTimerMealySUL.java | 119 ++++ .../learnlib/symbol_filter/SymbolFilter.java | 38 ++ .../symbol_filter/SymbolFilterResponse.java | 27 + api/src/main/java/module-info.java | 2 + .../src/main/java/module-info.java | 1 + .../statistic/container/CounterStatistic.java | 32 + .../statistic/container/FlagStatistic.java | 23 + .../statistic/container/LearnerStatistic.java | 36 ++ .../container/MapStatsContainer.java | 99 +++ .../container/StopClockStatistic.java | 36 ++ .../statistic/container/TextStatistic.java | 16 + .../LocalTimerMealySimulatorSUL.java | 71 +++ .../filter/cache/mmlt/CacheTreeNode.java | 170 +++++ .../mmlt/FastLocalTimerMealyTreeCacheSUL.java | 261 ++++++++ .../filter/cache/mmlt/TimeoutReducerSUL.java | 80 +++ filters/statistics/pom.xml | 5 + .../sul/LocalTimerMealyStatsSUL.java | 98 +++ .../statistics/src/main/java/module-info.java | 1 + .../mmlt/LocalTimerMealySimulatorOracle.java | 63 ++ .../equivalence/mmlt/ResetSearchOracle.java | 167 +++++ .../oracle/membership/TimedQueryOracle.java | 271 ++++++++ 44 files changed, 4053 insertions(+) create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java create mode 100644 api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java create mode 100644 api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java create mode 100644 api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java create mode 100644 api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java create mode 100644 api/src/main/java/de/learnlib/statistic/container/StatsContainerX.java create mode 100644 api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java create mode 100644 api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java create mode 100644 api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java create mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java create mode 100644 drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java create mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java create mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java create mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java create mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java create mode 100644 oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java create mode 100644 oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java create mode 100644 oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java new file mode 100644 index 000000000..acd23c388 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -0,0 +1,431 @@ +package de.learnlib.algorithm.lstar.mmlt; + + +import de.learnlib.acex.AcexAnalyzer; +import de.learnlib.acex.AcexAnalyzers; +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.algorithm.lstar.closing.ClosingStrategies; +import de.learnlib.algorithm.lstar.closing.ClosingStrategy; +import de.learnlib.algorithm.lstar.mmlt.cex.CexPreprocessor; +import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyCounterexampleHandler; +import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyOutputInconsistency; +import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; +import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; +import de.learnlib.datastructure.observationtable.OTLearner; +import de.learnlib.datastructure.observationtable.ObservationTable; +import de.learnlib.datastructure.observationtable.Row; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; + +/** + * The MMLT learner. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LStarLocalTimerMealy implements OTLearner, LocalTimerMealySemanticInputSymbol, Word>>, LearnerStatsProvider { + + private static final Logger logger = LoggerFactory.getLogger(LStarLocalTimerMealy.class); + private StatsContainerX stats = new DummyStatsContainer(); + + private final ClosingStrategy, ? super Word>> closingStrategy; + + private final TimedQueryOracle timeOracle; + private final SymbolFilter symbolFilter; + + private final LStarLocalTimerMealyHypDataContainer hypData; + + // ============================ + + + private final List>> initialSuffixes; + private final LocalTimerMealyCounterexampleHandler cexAnalyzer; + + /** + * Instantiates a new Rivest-Schapire learner for MMLTs. + *

+ * Uses the close-shortest strategy for closing the observation table and + * binary-backwards search for decomposing counterexamples. + * + * @param alphabet Input alphabet for the semantic automaton + * @param modelParams Model parameters + * @param initialSuffixes Initial set of suffixes. May be empty. + * @param timeOracle The output query oracle for MMLTs. + * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. + */ + public LStarLocalTimerMealy(Alphabet> alphabet, + LocalTimerMealyModelParams modelParams, + @NonNull + List>> initialSuffixes, + TimedQueryOracle timeOracle, + SymbolFilter symbolFilter) { + this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); + } + + /** + * Instantiates a new Rivest-Schapire learner for MMLTs. + * + * @param alphabet Input alphabet for the semantic automaton + * @param modelParams Model parameters + * @param initialSuffixes Initial set of suffixes. May be empty. + * @param closingStrategy Closing strategy for the observation table. + * @param timeOracle The output query oracle for MMLTs. + * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. + * @param analyzer The strategy for decomposing counterexamples. + */ + public LStarLocalTimerMealy(Alphabet> alphabet, + LocalTimerMealyModelParams modelParams, + @NonNull + List>> initialSuffixes, + ClosingStrategy, ? super Word>> closingStrategy, + TimedQueryOracle timeOracle, + @NonNull + SymbolFilter symbolFilter, + AcexAnalyzer analyzer) { + this.closingStrategy = closingStrategy; + this.timeOracle = timeOracle; + this.initialSuffixes = initialSuffixes; + + // Prepare hyp data: + + // Init hypothesis data: + this.hypData = new LStarLocalTimerMealyHypDataContainer<>(alphabet, modelParams, + new LocalTimerMealyObservationTable<>(alphabet, modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); + + this.cexAnalyzer = new LocalTimerMealyCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); + this.symbolFilter = symbolFilter; + } + + /** + * Heuristically chooses a new one-shot timer from the provided timers: + * takes the timer with the highest initial value that a) does not exceed maxInitialValue and b) has not timer + * with a lower initial value that times out at the same time. + * + * @param sortedTimers Timers, sorted ascendingly by their initial value + * @param maxInitialValue Max. initial value to consider + * @param Output type + * @return New one-shot timer + */ + public static MealyTimerInfo selectOneShotTimer(List> sortedTimers, long maxInitialValue) { + + // Filter relevant timers: + // Start at timer with the highest initial value. + // Ignore all timers whose initial value exceeds the maximum value. + // Also ignore timers whose timeout is the multiple of another timer's initial value. + List> relevantTimers = new ArrayList<>(); + for (int i = sortedTimers.size() - 1; i >= 0; i--) { + MealyTimerInfo timer = sortedTimers.get(i); + + if (timer.initial() > maxInitialValue) { + continue; // could not have expired + } + + // Ignore timers whose initial value is a multiple of another one. + // When set to one-shot, these would expire at same time as periodic timer -> non-deterministic behavior! + boolean multiple = false; + for (int j = 0; j < i; j++) { + MealyTimerInfo otherTimer = sortedTimers.get(j); + if (timer.initial() % otherTimer.initial() == 0) { + multiple = true; + break; + } + } + if (multiple) { + continue; + } + + relevantTimers.add(timer); // not a multiple and within time + } + + if (relevantTimers.isEmpty()) { + throw new IllegalStateException("Max. initial value is too low; must include at least one timer."); + } + + // Return the candidate with the highest initial value one-shot: + return relevantTimers.get(0); // order is reversed -> first is last + } + + + /** + * Constructs an MMLT hypothesis. + * This updates all transition outputs, if required. + * + * @return MMLT hypothesis + */ + public LocalTimerMealy getHypothesisModel() { + return getInternalLocalTimerMealyHypothesis(); + } + + /** + * Like the construction above, but returns an LocalTimerMealyHypothesis object instead. + * This objects provides additional functions that are just intended for the learner but not the teacher. + * + * @return MMLT hypothesis + */ + private LocalTimerMealyHypothesis getInternalLocalTimerMealyHypothesis() { + this.updateOutputs(); + var hyp = LocalTimerMealyHypothesisBuilder.constructHypothesis(this.hypData); + + return new LocalTimerMealyHypothesis<>(hyp.automaton(), hyp.prefixMap()); + } + + protected List>> selectClosingRows(List>>> unclosed) { + return closingStrategy.selectClosingRows(unclosed, hypData.getTable(), timeOracle); + } + + + protected void updateOutputs() { + // Query output of newly-added transitions: + Stream.concat(this.hypData.getTable().getShortPrefixRows().stream(), this.hypData.getTable().getLongPrefixRows().stream()) + .forEach(row -> { + if (row.getLabel().isEmpty()) { + return; // initial state + } + + if (this.hypData.getTransitionOutputMap().containsKey(row.getLabel())) { + return; // already queried + } + + Word> prefix = row.getLabel().prefix(-1); + LocalTimerMealySemanticInputSymbol inputSym = row.getLabel().suffix(1).lastSymbol(); + + LocalTimerMealyOutputSymbol output = null; + if (inputSym instanceof TimeStepSequence ws) { + // Query timer output from table: + MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.getTimeSteps()); + if (timerInfo == null) { + throw new AssertionError(); + } + output = new LocalTimerMealyOutputSymbol<>(timerInfo.output()); + } else { + output = this.timeOracle.querySuffixOutput(prefix, Word.fromLetter(inputSym)).lastSymbol(); + } + + if (output != null) { + this.hypData.getTransitionOutputMap().put(row.getLabel(), output); + } + }); + } + + // ========================== + + @Override + public void startLearning() { + List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); + + // Ensure that closed: + this.completeConsistentTable(initialUnclosed); + } + + @Override + public boolean refineHypothesis(DefaultQuery, Word>> ceQuery) { + if (!refineHypothesisSingle(ceQuery)) { + return false; // no valid CEX + } + while (refineHypothesisSingle(ceQuery)) { + } + return true; + } + + + /** + * Transforms the provided counterexample to an inconsistency object: + * First, checks if still a counterexample. If so, cuts the cex after the first output deviation. + * + * @param ceQuery Counterexample + * @param hypothesis Current hypothesis + * @return The resulting inconsistency, or null, if the counterexample is not a counterexample. + */ + @Nullable + private LocalTimerMealyOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, LocalTimerMealyHypothesis hypothesis) { + // 1. Cut example after first deviation: + Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(ceQuery.getPrefix(), ceQuery.getSuffix()); + DefaultQuery, Word>> shortQuery = CexPreprocessor.truncateCEX(ceQuery, hypOutput); + if (shortQuery == null) { + return null; + } + + // 2. Calculate shortened hypothesis output: + var shortHypOutput = hypothesis.getSemantics().computeSuffixOutput(shortQuery.getPrefix(), shortQuery.getSuffix()); + if (shortHypOutput.equals(shortQuery.getOutput())) { + throw new AssertionError("Deviation lost after shortening."); + } + + return new LocalTimerMealyOutputInconsistency<>(shortQuery.getPrefix(), + shortQuery.getSuffix(), + shortQuery.getOutput(), shortHypOutput); + } + + private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { + // 1. Update hypothesis (may have changed since last refinement): + var hypothesis = this.getInternalLocalTimerMealyHypothesis(); + + // 2. Transform to output inconsistency: + var outputIncons = this.toOutputInconsistency(ceQuery, hypothesis); + if (outputIncons == null) { + return false; + } + + logger.debug(String.format("Refining with inconsistency %s", outputIncons)); + + // 3. Identify source of deviation: + stats.startOrResumeClock("clk_cex_analysis", "Total cex analysis time"); + stats.increaseCounter("cnt_cex_analysis", "Cex analyses"); + var analysisResult = this.cexAnalyzer.analyzeInconsistency(outputIncons, hypothesis); + stats.pauseClock("clk_cex_analysis"); + + // 4. Refine: + if (analysisResult instanceof MissingDiscriminatorResult locSplit) { + stats.increaseCounter("INACC_MISSING_DISC", + "Inaccuracies: missing discriminators"); + + // Add new discriminator as suffix: + if (hypData.getTable().getSuffixes().contains(locSplit.getDiscriminator())) throw new AssertionError(); + List>> suffixes = Collections.singletonList(locSplit.getDiscriminator()); + var unclosed = hypData.getTable().addSuffixes(suffixes, timeOracle); + + // Close transitions: + this.completeConsistentTable(unclosed); // no consistency check for RS + } else if (analysisResult instanceof MissingResetResult noReset) { + stats.increaseCounter("INACC_MISSING_RESETS", + "Inaccuracies: missing resets"); + + // Add missing reset: + var resetTrans = hypothesis.getPrefix(noReset.getLocation()).append(noReset.getInput()); + this.hypData.getTransitionResetSet().add(resetTrans); + } else if (analysisResult instanceof MissingOneShotResult noAperiodic) { + stats.increaseCounter("INACC_MISSING_OS", + "Inaccuracies: missing one-shot timers"); + + // Identify corresponding sp row: + Word> locPrefix = hypothesis.getPrefix(noAperiodic.getLocation()); + Row> spRow = hypData.getTable().getRow(locPrefix); + if (spRow == null || !spRow.isShortPrefixRow()) { + throw new AssertionError(); + } + + this.handleMissingTimeoutChange(spRow, noAperiodic.getTimeout()); + } else if (analysisResult instanceof FalseIgnoreResult falseIgnore) { + stats.increaseCounter("INACC_MISSING_FI", + "Inaccuracies: false ignores"); + + if (this.symbolFilter == null) { + throw new AssertionError("Cannot detect false ignores without symbol filter."); + } + + // Identify corresponding sp row: + Word> locPrefix = hypothesis.getPrefix(falseIgnore.getLocation()); + Row> spRow = hypData.getTable().getRow(locPrefix); + if (spRow == null || !spRow.isShortPrefixRow()) { + throw new AssertionError(); + } + + // Update filter: + this.symbolFilter.update(locPrefix, falseIgnore.getSymbol(), SymbolFilterResponse.ACCEPT); + + // Legalize symbol + close table: + var unclosed = hypData.getTable().addOutgoingTransition(spRow, falseIgnore.getSymbol(), this.timeOracle); + stats.increaseCounter("Count_legalized", "Legalized symbols"); + + this.completeConsistentTable(unclosed); + } else { + throw new IllegalStateException("Unknown inconsistency type."); + } + + return true; + } + + private void handleMissingTimeoutChange(Row> spRow, MealyTimerInfo timeout) { + var locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); + if (locationTimerInfo == null) { + throw new AssertionError("Location with missing one-shot timer must have timers."); + } + + // Only timer with highest initial value can be one-shot. + // If location already has a one-shot timer, prefix of its timeout-transition might be core or fringe prefix. + // If it is a fringe prefix, we need to remove it: + var lastTimerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(locationTimerInfo.getLastTimer().initial())); + if (!locationTimerInfo.getLastTimer().periodic()) { + if (!hypData.getTable().getRow(lastTimerTransPrefix).isShortPrefixRow()) { + // Last timer is one-shot + has fringe prefix: + this.hypData.getTable().removeLpRow(lastTimerTransPrefix); + } + } + + // Prefix for timeout-transition of new one-shot timer: + Word> timerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(timeout.initial())); + if (this.hypData.getTable().getRow(timerTransPrefix) != null) { + throw new AssertionError("Timer already appears to be one-shot."); + } + + // Remove all timers with greater timeout (are now redundant): + var subsequentTimers = locationTimerInfo.getSortedTimers().stream() + .filter(t -> t.initial() > timeout.initial()) + .map(MealyTimerInfo::name).toList(); + subsequentTimers.forEach(locationTimerInfo::removeTimer); + + // Change from periodic to one-shot: + locationTimerInfo.setOneShotTimer(timeout.name()); + + // Update fringe prefixes + close table: + List>>> unclosed = this.hypData.getTable().addTimerTransition(spRow, timeout, this.timeOracle); + this.completeConsistentTable(unclosed); + } + + + @Override + public ObservationTable, Word>> getObservationTable() { + return this.hypData.getTable(); + } + + /** + * 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. + *

+ * Simplified version for RS learner: assumes that OT is always consistent. + * + * @param unclosed the unclosed rows (equivalence classes) to start with. + */ + protected void completeConsistentTable(List>>> unclosed) { + List>>> unclosedIter = unclosed; + while (!unclosedIter.isEmpty()) { + List>> closingRows = this.selectClosingRows(unclosedIter); + + // Add new states: + unclosedIter = hypData.getTable().toShortPrefixes(closingRows, timeOracle); + } + + } + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + this.cexAnalyzer.setStatsContainer(container); + } + + +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java new file mode 100644 index 000000000..14954492a --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java @@ -0,0 +1,72 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.datastructure.observationtable.Row; +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +/** + * Stores various data used for describing the MMLT hypothesis. + * This includes the OT, a list of local resets, and a list of outputs. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +class LStarLocalTimerMealyHypDataContainer { + private final Alphabet> alphabet; + + private final LocalTimerMealyObservationTable table; + private final Map>, LocalTimerMealyOutputSymbol> transitionOutputMap; + private final Set>> transitionResetSet; // all transitions that trigger a reset + + private final LocalTimerMealyModelParams modelParams; + + public LStarLocalTimerMealyHypDataContainer(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, LocalTimerMealyObservationTable table) { + this.alphabet = alphabet; + this.modelParams = modelParams; + this.table = table; + + this.transitionOutputMap = new HashMap<>(); + this.transitionResetSet = new HashSet<>(); + } + + @Nullable + protected LocalTimerMealyOutputSymbol getTransitionOutput(Row> stateRow, int inputIdx) { + Row> transRow = stateRow.getSuccessor(inputIdx); + if (transRow == null) { + return null; + } + + return this.transitionOutputMap.getOrDefault(transRow.getLabel(), null); + } + + + public LocalTimerMealyModelParams getModelParams() { + return modelParams; + } + + public Alphabet> getAlphabet() { + return alphabet; + } + + + public LocalTimerMealyObservationTable getTable() { + return table; + } + + public Map>, LocalTimerMealyOutputSymbol> getTransitionOutputMap() { + return transitionOutputMap; + } + + public Set>> getTransitionResetSet() { + return transitionResetSet; + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java new file mode 100644 index 000000000..d23788eba --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java @@ -0,0 +1,127 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import de.learnlib.datastructure.observationtable.Row; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +import java.util.HashMap; +import java.util.Map; + +class LocalTimerMealyHypothesisBuilder { + + record LocalTimerMealyHypothesisBuildResult(LocalTimerMealy automaton, + Map>> prefixMap) { + + } + + /** + * Constructs a hypothesis MMLT from an observation table, inferred local resets, and inferred local timers. + */ + static LocalTimerMealyHypothesisBuildResult constructHypothesis(LStarLocalTimerMealyHypDataContainer hypData) { + + // 1. Create map that stores link between contentID and short-prefix row: + final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location + for (var spRow : hypData.getTable().getShortPrefixRows()) { + if (locationContentIdMap.containsKey(spRow.getRowContentId())) { + // Multiple sp rows may have same contentID. Thus, assign each id one location: + continue; + } + locationContentIdMap.put(spRow.getRowContentId(), spRow); + } + + // 2. Create untimed alphabet: + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + for (var symbol : hypData.getAlphabet()) { + if (symbol instanceof NonDelayingInput ndi) { + alphabet.add(ndi); + } + } + + // 3. Prepare objects for automaton, timers and resets: + int numLocations = hypData.getTable().numberOfShortPrefixRows(); + var hypothesis = new CompactLocalTimerMealy<>(alphabet, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner()); + + final Map stateMap = new HashMap<>(numLocations); // row content id -> state id + + final Map>> prefixMap = new HashMap<>(numLocations); // state id -> location prefix + + // 4. Create one state per location: + for (var row : hypData.getTable().getShortPrefixRows()) { + int newStateId = hypothesis.addState(); + stateMap.putIfAbsent(row.getRowContentId(), newStateId); + prefixMap.put(newStateId, row.getLabel()); + + if (row.getLabel().equals(Word.epsilon())) { + hypothesis.setInitialState(newStateId); + } + } + // Ensure initial location: + if (hypothesis.getInitialState() == null) { + throw new IllegalArgumentException("Automaton must have an initial location."); + } + + // 5. Create outgoing transitions for non-delaying inputs: + for (var rowContentId : stateMap.keySet()) { + Row> spLocation = locationContentIdMap.get(rowContentId); + + for (var symbol : alphabet) { + int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); + + var transOutput = hypData.getTransitionOutput(spLocation, symIdx); + O output = hypData.getModelParams().silentOutput(); // silent by default + if (transOutput != null) { + output = transOutput.getSymbol(); + } + + int successorId; + if (spLocation.getSuccessor(symIdx) == null) { + successorId = spLocation.getRowContentId(); // not in local alphabet -> self-loop + } else { + successorId = spLocation.getSuccessor(symIdx).getRowContentId(); + } + + // Add transition to automaton: + int sourceLocId = stateMap.get(rowContentId); + int successorLocId = stateMap.get(successorId); + hypothesis.addTransition(sourceLocId, symbol, output, successorLocId); + + // Check for local reset: + var targetTransition = spLocation.getLabel().append(symbol); + if (hypData.getTransitionResetSet().contains(targetTransition) && sourceLocId == successorLocId) { + hypothesis.addLocalReset(sourceLocId, symbol); + } + } + + } + + // 6. Add timeout transitions: + for (var rowContentId : stateMap.keySet()) { + Row> spLocation = locationContentIdMap.get(rowContentId); + + var timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); + if (timerInfo == null) { + continue; // no timers + } + + for (var timer : timerInfo.getLocalTimers().values()) { + if (timer.periodic()) { + hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output()); + } else { + // One-shot: use successor from table + LocalTimerMealySemanticInputSymbol symbol = new TimeStepSequence<>(timer.initial()); + + int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); + int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); + + hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output(), stateMap.get(successorId)); + } + } + } + + return new LocalTimerMealyHypothesisBuildResult<>(hypothesis, prefixMap); + } + +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java new file mode 100644 index 000000000..ccc4bddb7 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java @@ -0,0 +1,582 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import de.learnlib.datastructure.observationtable.MutableObservationTable; +import de.learnlib.datastructure.observationtable.Row; +import de.learnlib.datastructure.observationtable.RowImpl; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.MembershipOracle; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * The observation table used by the MMLT learner. + *

+ * Unlike an OT for standard Mealy learning, includes prefixes for the timeout transitions of one-shot timers. + * Intended to be used with a symbol filter. The filter is queried before adding a new transition for a non-delaying input. + * If the filter considers the transition to be a silent self-loop, the output of the transition is first verified. + * If it is actually silent the learner considers the transition to be a silent self-loop. Consequently, + * it does not add a transition for it. Transitions may be added later if an input was falsely ignored. + *

+ * Assumes that all short prefixes lead to different locations (-> no need to make canonical) + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyObservationTable implements MutableObservationTable, Word>> { + + private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyObservationTable.class); + + private final SymbolFilter symbolFilter; + + private final Map>, LocationTimerInfo> timerInfoMap; // prefix -> timer info + + private final Map>, RowImpl>> shortPrefixRowMap; // label -> row info + private final Map>, RowImpl>> longPrefixRowMap; // label -> row info + + private final List>> sortedShortPrefixes; // values of shortPrefixRowMap sorted by label, for faster access. + private final List>> longPrefixList; // values of longPrefixRowMap as list, for faster access. + + private final Map> rowContentMap; // contentID -> row content + private static final int NO_CONTENT = -1; + + private final List>> suffixes = new ArrayList<>(); + private final Set>> suffixSet = new HashSet<>(); + + private final Alphabet> alphabet; + private final long minTimerQueryWaitTime; + private final LocalTimerMealyOutputSymbol silentOutput; // used for symbol filtering + + public LocalTimerMealyObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, + SymbolFilter symbolFilter, O silentOutput) { + this.alphabet = alphabet; + + this.symbolFilter = symbolFilter; + this.silentOutput = new LocalTimerMealyOutputSymbol<>(silentOutput); + this.minTimerQueryWaitTime = minTimerQueryWaitTime; + + this.timerInfoMap = new HashMap<>(); + + this.shortPrefixRowMap = new HashMap<>(); + this.sortedShortPrefixes = new ArrayList<>(); + + this.longPrefixRowMap = new HashMap<>(); + this.longPrefixList = new ArrayList<>(); + + this.rowContentMap = new HashMap<>(); + } + + /** + * Infers local timers for the provided location. + * + * @param location Source location. + */ + private void identifyLocalTimers(LocationTimerInfo location, TimedQueryOracle timeOracle) { + var timerQueryResponse = timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); + + if (timerQueryResponse.aborted()) { + var newOneShot = LStarLocalTimerMealy.selectOneShotTimer(timerQueryResponse.timers(), Long.MAX_VALUE); + newOneShot.setOneShot(); + } + + // Add timers up to one-shot: + for (var timer : timerQueryResponse.timers()) { + location.addTimer(timer); + this.extendAlphabet(new TimeStepSequence<>(timer.initial())); + if (!timer.periodic()) { + break; + } + } + } + + /** + * Extends the global alphabet without adding new transitions. + * + * @param symbol New alphabet symbol + */ + private void extendAlphabet(TimeStepSequence symbol) { + if (!alphabet.containsSymbol(symbol)) { + alphabet.asGrowingAlphabetOrThrowException().addSymbol(symbol); + } + + for (RowImpl> prefix : this.shortPrefixRowMap.values()) { + prefix.ensureInputCapacity(alphabet.size()); + } + } + + /** + * Adds the initial location. + * + * @return Corresponding row in the OT + */ + private RowImpl> addInitialLocation() { + RowImpl> newRow = new RowImpl<>(Word.epsilon(), 0, alphabet.size()); + newRow.makeShort(alphabet.size()); + this.shortPrefixRowMap.put(Word.epsilon(), newRow); + this.sortedShortPrefixes.add(newRow); + this.sortedShortPrefixes.sort(Comparator.comparing(r -> r.getLabel().toString())); + + return newRow; + } + + + /** + * Adds a new location that belongs to the provided short-prefix row. + * Infers timers for this location and creates outgoing transitions. + * + * @param newRow Newly-added short prefix row + * @param timeOracle Time oracle + */ + private void initLocation(RowImpl> newRow, TimedQueryOracle timeOracle) { + LocationTimerInfo timerInfo = new LocationTimerInfo<>(newRow.getLabel()); + this.identifyLocalTimers(timerInfo, timeOracle); + + if (timerInfo.getLastTimer() != null) { // location has timer + this.timerInfoMap.put(newRow.getLabel(), timerInfo); + } + + // Add outgoing transitions: + List>> transitions = this.createOutgoingTransitions(newRow, timeOracle); + transitions.forEach(t -> this.queryAllSuffixes(t, timeOracle)); + } + + /** + * Creates transitions for the provided short-prefix row. Adds transitions for non-delaying inputs + * and a transition for the one-shot timer of the location, if present. + *

+ * If a symbol filter is provided, the filter is queried before adding a transition for a non-delaying input. + * If the filter considers the input a silent self-loop, no transition is explicitly created for the input. + * + * @param spRow Short prefix row + * @param timeOracle Time query oracle + * @return New transitions + */ + private List>> createOutgoingTransitions(RowImpl> spRow, TimedQueryOracle timeOracle) { + List>> transitions = new ArrayList<>(); + + Word> sp = spRow.getLabel(); + + // First, add transitions for non-delaying symbols: + for (int i = 0; i < alphabet.size(); i++) { + LocalTimerMealySemanticInputSymbol sym = alphabet.getSymbol(i); + if (sym instanceof TimeStepSequence || sym instanceof TimeoutSymbol) { + continue; + } + + Word> lp = sp.append(sym); + assert !this.shortPrefixRowMap.containsKey(lp); + + RowImpl> succRow = this.longPrefixRowMap.get(lp); + if (succRow == null) { + // Query symbol filter before adding transition: + var filterResponse = this.symbolFilter.query(sp, (NonDelayingInput) sym); + if (filterResponse == SymbolFilterResponse.IGNORE) { + // Verify that output is silent: + var response = timeOracle.querySuffixOutput(sp, Word.fromLetter(sym)); + assert response.size() == 1; + if (!response.firstSymbol().equals(silentOutput)) { + // Not silent -> cannot be silent self-loop: + filterResponse = SymbolFilterResponse.ACCEPT; + + // Update filter: + this.symbolFilter.update(sp, (NonDelayingInput) sym, SymbolFilterResponse.ACCEPT); + } + } + + if (filterResponse == SymbolFilterResponse.ACCEPT) { + // Treat as usual: + succRow = this.createLpRow(lp); + } + } + + spRow.setSuccessor(i, succRow); + if (succRow != null) { + transitions.add(succRow); + } + } + + // Second, add one-shot timer transition (if any): + var locTimers = timerInfoMap.getOrDefault(spRow.getLabel(), null); + if (locTimers != null && !locTimers.getLastTimer().periodic()) { + LocalTimerMealySemanticInputSymbol waitSym = new TimeStepSequence<>(locTimers.getLastTimer().initial()); + Word> lp = sp.append(waitSym); + assert !this.shortPrefixRowMap.containsKey(lp); + + RowImpl> succRow = this.longPrefixRowMap.get(lp); + if (succRow == null) { + succRow = this.createLpRow(lp); + } + spRow.setSuccessor(this.alphabet.getSymbolIndex(waitSym), succRow); + transitions.add(succRow); + } + + return transitions; + } + + private RowImpl> createLpRow(Word> prefix) { + RowImpl> newRow = new RowImpl<>(prefix, 0); + this.longPrefixRowMap.put(prefix, newRow); + this.longPrefixList.add(newRow); + if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + + newRow.setLpIndex(0); // unused + + return newRow; + } + + /** + * Identify transitions that have not been closed. + * I.e., there is no state with the same suffix behavior. + * Also removes unused content ids. + *

+ * Guarantees that returned transition list order is deterministic. + */ + public List>>> findUnclosedTransitions() { + // Identify contentIds for locations: + Set spContentIds = this.shortPrefixRowMap.values().stream() + .map(RowImpl::getRowContentId) + .collect(Collectors.toSet()); + + // Group lp rows by their content id: + Map>>> lpContentMap = new HashMap<>(); + for (var lpRow : this.longPrefixRowMap.values()) { + lpContentMap.putIfAbsent(lpRow.getRowContentId(), new ArrayList<>()); + lpContentMap.get(lpRow.getRowContentId()).add(lpRow); + } + + // Identify ids that are not used by any SP: + List>>> unclosedRows = new ArrayList<>(); + List sortedLpIds = lpContentMap.keySet().stream().sorted().toList(); + for (var lpId : sortedLpIds) { + if (spContentIds.contains(lpId)) { + continue; + } + + // Sort row s.t. list order deterministic: + List>> unclosedWithId = lpContentMap.get(lpId); + unclosedWithId.sort(Comparator.comparing(r -> r.getLabel().toString())); + unclosedRows.add(unclosedWithId); + } + + // Remove unused content ids: + Set usedContentIds = Stream.concat(this.shortPrefixRowMap.values().stream(), this.longPrefixRowMap.values().stream()) + .map(RowImpl::getRowContentId).collect(Collectors.toSet()); + + List oldContentIds = this.rowContentMap.keySet().stream().toList(); + for (int oldId : oldContentIds) { + if (!usedContentIds.contains(oldId)) { + this.rowContentMap.remove(oldId); + } + } + + + return unclosedRows; + } + + @Override + public List>>> initialize(List>> initialShortPrefixes, + List>> initialSuffixes, + MembershipOracle, Word>> oracle) { + + if (isInitialized()) { + throw new IllegalStateException("Called initialize, but there are already rows present"); + } + if (!initialShortPrefixes.isEmpty()) { + throw new IllegalArgumentException("Init with short prefixes is not supported."); + } + if (!(oracle instanceof TimedQueryOracle timedOracle)) { + throw new IllegalArgumentException("Must use timed oracle!"); + } + + // Add initial suffixes: + for (Word> suffix : initialSuffixes) { + if (suffixSet.add(suffix)) { + suffixes.add(suffix); + } + } + + // 1. Create initial location: + var newLoc = this.addInitialLocation(); + this.initLocation(newLoc, timedOracle); + this.queryAllSuffixes(newLoc, timedOracle); + + // 2. Identify unclosed transitions: + return this.findUnclosedTransitions(); + } + + private void queryAllSuffixes(RowImpl> row, TimedQueryOracle timedOracle) { + Word> prefix = row.getLabel(); + + List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); + for (Word> suffix : this.suffixes) { + Word> output = timedOracle.querySuffixOutput(prefix, suffix); + suffixOutputs.add(output); + } + + this.processSuffixOutputs(row, suffixOutputs); + } + + private void processSuffixOutputs(RowImpl> row, List>> rowContents) { + if (rowContents.isEmpty()) { + row.setRowContentId(NO_CONTENT); + return; + } + + RowContent content = new RowContent<>(rowContents); + int contentId = content.hashCode(); + this.rowContentMap.putIfAbsent(contentId, content); + row.setRowContentId(contentId); + } + + @Override + public boolean isInitialized() { + return !(shortPrefixRowMap.isEmpty() && longPrefixRowMap.isEmpty()); + } + + @Override + public boolean isInitialConsistencyCheckRequired() { + return false; + } + + @Override + public List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { + if (!(oracle instanceof TimedQueryOracle timedOracle)) { + throw new IllegalArgumentException(); + } + + // 1. Extend current suffixes + identify new suffixes: + List>> newSuffixList = new ArrayList<>(); + for (Word> suffix : newSuffixes) { + if (this.suffixSet.add(suffix)) { + logger.debug(String.format("Adding new suffix '%s'", suffix)); + + newSuffixList.add(suffix); + this.suffixes.add(suffix); + } + } + if (newSuffixList.isEmpty()) { + return Collections.emptyList(); + } + + // 2. Update row content: + Stream.concat(shortPrefixRowMap.values().stream(), longPrefixRowMap.values().stream()).forEach(row -> { + List>> updatedOutputs = new ArrayList<>(); + if (row.getRowContentId() != NO_CONTENT) { + // Add existing suffix outputs: + updatedOutputs.addAll(this.rowContentMap.get(row.getRowContentId()).outputs()); + } + + for (Word> suffix : newSuffixList) { + Word> output = timedOracle.querySuffixOutput(row.getLabel(), suffix); + updatedOutputs.add(output); + } + + this.processSuffixOutputs(row, updatedOutputs); + }); + + return this.findUnclosedTransitions(); + } + + @Override + public List>>> addShortPrefixes(List>> shortPrefixes, MembershipOracle, Word>> oracle) { + throw new IllegalStateException("Not supported."); + } + + @Override + public List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { + if (!(oracle instanceof TimedQueryOracle timedOracle)) { + throw new IllegalArgumentException(); + } + + for (Row> row : lpRows) { + logger.debug(String.format("Adding new location with prefix '%s'", row.getLabel())); + + final RowImpl> lpRow = (RowImpl>) row; + + // Delete from LP rows: + var removed = this.longPrefixRowMap.remove(row.getLabel()); + this.longPrefixList.remove(removed); + if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + + // Add to SP rows: + this.shortPrefixRowMap.put(row.getLabel(), lpRow); + this.sortedShortPrefixes.add(lpRow); + this.sortedShortPrefixes.sort(Comparator.comparing(r -> r.getLabel().toString())); + + lpRow.makeShort(alphabet.size()); + + this.initLocation(lpRow, timedOracle); + } + return this.findUnclosedTransitions(); + } + + @Override + public List>>> addAlphabetSymbol(LocalTimerMealySemanticInputSymbol symbol, MembershipOracle, Word>> oracle) { + throw new IllegalStateException("Not supported."); + } + + @Override + public Alphabet> getInputAlphabet() { + return this.alphabet; + } + + @Override + public Collection>> getShortPrefixRows() { + if (this.sortedShortPrefixes.size() != this.shortPrefixRowMap.size()) throw new AssertionError(); + return Collections.unmodifiableList(this.sortedShortPrefixes); + } + + @Override + public Collection>> getLongPrefixRows() { + return Collections.unmodifiableList(this.longPrefixList); + } + + @Override + public Row> getRow(int idx) { + throw new IllegalStateException("Not supported. Use prefix to access rows instead."); + } + + @Override + public @Nullable Row> getRow(Word> prefix) { + if (this.shortPrefixRowMap.containsKey(prefix)) { + return this.shortPrefixRowMap.get(prefix); + } + if (this.longPrefixRowMap.containsKey(prefix)) { + return this.longPrefixRowMap.get(prefix); + } + return null; + } + + @Override + public int numberOfDistinctRows() { + return this.rowContentMap.size(); + } + + @Override + public List>> getSuffixes() { + return this.suffixes; + } + + @Override + @Nullable + public List>> rowContents(Row> row) { + if (this.rowContentMap.isEmpty()) { + // OT may be empty if only single location with timers: + if (!this.suffixes.isEmpty()) { + throw new AssertionError(); + } + return Collections.emptyList(); + } + + return this.rowContentMap.get(row.getRowContentId()).outputs(); + } + + @Override + public Word> transformAccessSequence(Word> word) { + throw new IllegalStateException("Not implemented."); + } + + + @Override + public boolean isAccessSequence(Word> word) { + throw new IllegalStateException("Not implemented."); + } + + public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { + var info = this.timerInfoMap.get(prefix); + if (info != null) { + return info.getTimerInfo(initial); + } + return null; + } + + @Nullable + public LocationTimerInfo getLocationTimerInfo(Row> sp) { + return this.timerInfoMap.getOrDefault(sp.getLabel(), null); + } + + /** + * Adds an outgoing transition for the given symbol to the given location + * and subsequently tests for unclosed transitions. + *

+ * Raises an error if this transition already exists. + * + * @param spRow Source location + * @param symbol Input symbol + * @param timeOracle Oracle + * @return List of unclosed rows. Empty, if none. + */ + public List>>> addOutgoingTransition(Row> spRow, LocalTimerMealySemanticInputSymbol symbol, TimedQueryOracle timeOracle) { + if (!this.alphabet.containsSymbol(symbol)) { + throw new IllegalArgumentException("Unknown symbol."); + } + + Word> transitionPrefix = spRow.getLabel().append(symbol); + + // Add long-prefix row: + if (this.getRow(transitionPrefix) != null) { + throw new AssertionError("Location already has an outgoing transition for the provided symbol"); + } + + RowImpl> succRow = this.createLpRow(transitionPrefix); + + // Set as successor: + int symIdx = this.alphabet.getSymbolIndex(symbol); + ((RowImpl>) spRow).setSuccessor(symIdx, succRow); + + // Update suffixes: + this.queryAllSuffixes(succRow, timeOracle); + + return this.findUnclosedTransitions(); + } + + public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, TimedQueryOracle timeOracle) { + return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); + } + + /** + * Removes a long prefix row. Should only be used when removing a transition of a former one-shot timer. + * When turning a long into a short prefix, use toShortPrefix instead, + * + * @param prefix Row prefix + */ + public void removeLpRow(Word> prefix) { + if (!this.longPrefixRowMap.containsKey(prefix)) { + throw new IllegalArgumentException("Attempting to remove lp row that does not exist."); + } + + // Remove lp row: + var removed = this.longPrefixRowMap.remove(prefix); + this.longPrefixList.remove(removed); + if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + + // Unset as successor: + int symIdx = this.alphabet.getSymbolIndex(prefix.lastSymbol()); + RowImpl> spRow = this.shortPrefixRowMap.get(prefix.prefix(-1)); + assert spRow != null; + + spRow.setSuccessor(symIdx, null); + } + + // ============================= + + private record RowContent(List>> outputs) { + + @Override + public int hashCode() { + return outputs.toString().hashCode(); + } + } + + +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java new file mode 100644 index 000000000..f0ab4616d --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java @@ -0,0 +1,119 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Serializable; +import java.util.*; + +/** + * Stores information about local timers of a location. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocationTimerInfo implements Serializable { + + private static final Logger logger = LoggerFactory.getLogger(LocationTimerInfo.class); + + private final Map> timers; // name -> info + + // Keep a list of timers sorted by their initial value. This lets us avoid redundant sort operations. + private final List> sortedTimers; + + private final Word> prefix; + + public LocationTimerInfo(Word> prefix) { + this.prefix = prefix; + this.timers = new HashMap<>(); + this.sortedTimers = new ArrayList<>(); + } + + public Word> getPrefix() { + return prefix; + } + + // ==================== + + /** + * Adds a local timer to this location. + * + */ + public void addTimer(MealyTimerInfo timer) { + this.timers.put(timer.name(), timer); + this.sortedTimers.add(timer); + this.sortedTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + } + + public void removeTimer(String timerName) { + if (!this.timers.containsKey(timerName)) { + logger.warn("Attempted to remove an unknown timer."); + return; + } + MealyTimerInfo removedTimer = this.timers.remove(timerName); + this.sortedTimers.remove(removedTimer); + } + + @Nullable + public MealyTimerInfo getTimerInfo(long initial) { + Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); + return timer.orElse(null); + } + + + /** + * Returns the timer with the highest initial value + * + * @return Timer with maximum timeout. Null, if no timers defined. + */ + @Nullable + public MealyTimerInfo getLastTimer() { + if (this.timers.isEmpty()) { + return null; + } + return sortedTimers.get(sortedTimers.size() - 1); + } + + /** + * Sets the given timer to one-shot, ensuring that there is only one one-shot timer at a time. + * This is preferred over setting the timer property. + * + * @param name Name of the new one-shot timer + */ + public void setOneShotTimer(String name) { + var oneShotTimer = this.timers.get(name); + if (oneShotTimer == null) { + throw new IllegalArgumentException("Unknown one-shot timer name."); + } + if (!oneShotTimer.equals(sortedTimers.get(sortedTimers.size() - 1))) { + throw new IllegalArgumentException("Only the timer with maximum timeout can be one-shot."); + } + + oneShotTimer.setOneShot(); + } + + /** + * Returns a list of all timers defined in this location, sorted by their initial value. + * + * @return List of local timers. Empty, if none. + */ + public List> getSortedTimers() { + return Collections.unmodifiableList(sortedTimers); + } + + /** + * Returns an unmodifiable view of the timers defined for this location. + * Format: name -> info + * + * @return Map of local timers. Empty, if none defined. + */ + @NonNull + public Map> getLocalTimers() { + return Collections.unmodifiableMap(this.timers); + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java new file mode 100644 index 000000000..d4ade3082 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java @@ -0,0 +1,37 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + +import de.learnlib.query.DefaultQuery; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.word.Word; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class CexPreprocessor { + + private static final Logger logger = LoggerFactory.getLogger(CexPreprocessor.class); + + /** + * Cuts-off a counterexample when its output deviates from the hypothesis output. + * + * @param cexQuery Counterexample + * @param hypAnswer Hypothesis response to suffix of the counterexample + * @return The shortened counterexample, or null, if hypothesis and SUL show identical behavior. + */ + public static DefaultQuery, Word>> truncateCEX(DefaultQuery, Word>> cexQuery, + Word> hypAnswer) { + + for (int i = 0; i < cexQuery.getSuffix().size(); i++) { + if (!hypAnswer.getSymbol(i).equals(cexQuery.getOutput().getSymbol(i))) { + // Cut after deviation: + return new DefaultQuery<>(cexQuery.getPrefix(), + cexQuery.getSuffix().prefix(i + 1), + cexQuery.getOutput().prefix(i + 1)); + } + } + return null; // no deviation found + } + +} + diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java new file mode 100644 index 000000000..5fac3ec1e --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java @@ -0,0 +1,39 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * An extended decomposition represents a transition with an incorrect target or output in the expanded form of + * a hypothesis MMLT. + * + * @param state Source state in expanded form of hypothesis + * @param input Input of some transition with incorrect output or target source state + * @param discriminator If not null: transition has incorrect target + * @param Input type for non-delaying inputs + */ +record ExtendedDecomposition(LocalTimerMealyConfiguration state, + @NonNull LocalTimerMealySemanticInputSymbol input, + @Nullable Word> discriminator) { + + public ExtendedDecomposition(LocalTimerMealyConfiguration state, @NonNull LocalTimerMealySemanticInputSymbol input) { + this(state, input, null); + } + + public boolean isForIncorrectOutput() { + return this.discriminator == null; + } + + @Override + public String toString() { + if (this.isForIncorrectOutput()) { + return String.format("Incorrect output (%s|%s)", state, input); + } else { + return String.format("Incorrect target (%s|%s|%s)", state, input, discriminator); + } + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java new file mode 100644 index 000000000..e82c123e9 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java @@ -0,0 +1,139 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + +import de.learnlib.acex.AcexAnalyzer; +import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; +import de.learnlib.oracle.TimedQueryOracle; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.word.Word; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implements the search for an extended decomposition of a truncated counterexample and the post-processing of an extended decomposition. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +class LocalTimerMealyCounterexampleDecompositor { + + private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleDecompositor.class); + + + private final TimedQueryOracle timeOracle; + private final AcexAnalyzer acexAnalyzer; + + public LocalTimerMealyCounterexampleDecompositor(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { + this.timeOracle = timeOracle; + this.acexAnalyzer = acexAnalyzer; + } + + ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputInconsistency outIncons, + LocalTimerMealyHypothesis hypothesis) { + + if (outIncons.suffix().length() == 1) { + // Incorrect output: + var prefixState = hypothesis.getSemantics().traceInputs(outIncons.prefix()); + return new ExtendedDecomposition<>(prefixState, outIncons.suffix().firstSymbol()); + } + + // Verify breakpoint condition: + LocalTimerMealyInconsPrefixTransformAcex acex = new LocalTimerMealyInconsPrefixTransformAcex<>(outIncons.suffix(), timeOracle, + w -> hypothesis.getPrefix(outIncons.prefix().concat(w))); + + if (acex.testEffects(0, acex.getLength() - 1)) { + // Breakpoint condition not met -> must be incorrect output: + var lastStatePrefix = outIncons.prefix().concat(outIncons.suffix().prefix(outIncons.suffix().length() - 1)); + var lastState = hypothesis.getSemantics().traceInputs(lastStatePrefix); + + return new ExtendedDecomposition<>(lastState, outIncons.suffix().lastSymbol()); + } + + // Breakpoint condition met -> find decomposition: + int breakpoint = this.acexAnalyzer.analyzeAbstractCounterexample(acex); + if (acex.testEffects(breakpoint, breakpoint + 1)) { + throw new AssertionError("Failed to find valid decomposition."); + } + + // Get components: + Word> prefix = outIncons.prefix().concat(outIncons.suffix().prefix(breakpoint)); + LocalTimerMealySemanticInputSymbol sym = outIncons.suffix().getSymbol(breakpoint); + Word> discriminator = outIncons.suffix().subWord(breakpoint + 1); + + var prefixState = hypothesis.getSemantics().traceInputs(prefix); + + logger.debug(String.format("Decomposing to %s|%s|%s %n" + "Output at %d: %s. %nOutput at %d: %s", prefixState, sym, discriminator, breakpoint, + acex.computeEffect(breakpoint), breakpoint + 1, acex.computeEffect(breakpoint + 1))); + + return new ExtendedDecomposition<>(prefixState, sym, discriminator); + } + + /** + * Post-processes an extended decomposition: if the decomposition corresponds to a transition with an incorrect target or output at a timeout symbol, + * transforms the decomposition so that the input is either a non-delaying input or a single time step. + * + * @param decomposition Extended decomposition + * @return Post-processed decomposition + */ + ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, + LocalTimerMealyHypothesis hypothesis) { + if (!(decomposition.input() instanceof TimeoutSymbol)) { + return decomposition; + } + + var statePrefix = hypothesis.getPrefix(decomposition.state()); + var hypOutput = hypothesis.getSemantics().computeSuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); + var sulOutput = timeOracle.querySuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); + + if (decomposition.isForIncorrectOutput()) { + // Incorrect output at tout: + long minWaitTime; + if (hypOutput.firstSymbol().getDelay() == 0 && sulOutput.firstSymbol().getDelay() != 0) { + throw new AssertionError(); + } else if (hypOutput.firstSymbol().getDelay() != 0 && sulOutput.firstSymbol().getDelay() == 0) { + // If there is no timeout in either hyp or sul, need to trigger next observable timeout: + minWaitTime = hypOutput.firstSymbol().getDelay(); + } else { + // If there is a timeout in hyp and sul, go to next timeout: + minWaitTime = Math.min(hypOutput.firstSymbol().getDelay(), sulOutput.firstSymbol().getDelay()); + } + + // if minimum time is zero (= no timeout) or one, need to append empty word to prefix: + LocalTimerMealyConfiguration newPrefixState; + if (minWaitTime <= 1) { + newPrefixState = decomposition.state(); + } else { + newPrefixState = hypothesis.getSemantics().traceInputs(statePrefix.append(new TimeStepSequence<>(minWaitTime - 1))); + } + + logger.debug("Updated incorrect output at tout during post-processing."); + return new ExtendedDecomposition<>(newPrefixState, new TimeStepSymbol<>()); + } else { + if (decomposition.state().isStableConfig() || hypOutput.equals(sulOutput)) { + // stable-configuration or same output at tout -> same wait time for output in hyp and SUL: + if (hypOutput.firstSymbol().getDelay() != sulOutput.firstSymbol().getDelay()) { + throw new AssertionError(); + } + + long waitTime = hypOutput.firstSymbol().getDelay(); + LocalTimerMealyConfiguration newPrefixState; + if (waitTime <= 1) { + newPrefixState = decomposition.state(); + } else { + newPrefixState = hypothesis.getSemantics().traceInputs(statePrefix.append(new TimeStepSequence<>(waitTime - 1))); + } + + logger.debug("Updated incorrect target at tout during post-processing."); + return new ExtendedDecomposition<>(newPrefixState, new TimeStepSymbol<>(), decomposition.discriminator()); + } else { + // different output at tout -> found incorrect output: + logger.debug("Found incorrect output through post-processing."); + return postProcessExtendedDecomposition(new ExtendedDecomposition<>(decomposition.state(), decomposition.input()), hypothesis); + } + } + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java new file mode 100644 index 000000000..9ee73e305 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -0,0 +1,176 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + +import de.learnlib.acex.AcexAnalyzer; +import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; +import de.learnlib.algorithm.lstar.mmlt.cex.results.*; +import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Processes a truncated counterexample for a hypothesis MMLT: + * searches for an extended decomposition, post-processes it, and infers an inaccuracy from it. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyCounterexampleHandler implements LearnerStatsProvider { + private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleHandler.class); + private final SymbolFilter symbolFilter; + private StatsContainerX stats = new DummyStatsContainer(); + + protected final TimedQueryOracle timeOracle; + private final LocalTimerMealyCounterexampleDecompositor decompositor; + + public LocalTimerMealyCounterexampleHandler(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, SymbolFilter symbolFilter) { + this.timeOracle = timeOracle; + this.decompositor = new LocalTimerMealyCounterexampleDecompositor<>(timeOracle, acexAnalyzer); + this.symbolFilter = symbolFilter; + } + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } + + public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutputInconsistency outIncons, + LocalTimerMealyHypothesis hypothesis) { + + // Search for an extended decomposition: + var decomposition = decompositor.findExtendedDecomposition(outIncons, hypothesis); + logger.debug("Found an extended decomposition: {}", decomposition); + + // Post-process the decomposition: + decomposition = decompositor.postProcessExtendedDecomposition(decomposition, hypothesis); + logger.debug("Post-processed decomposition: {}", decomposition); + + if (decomposition.isForIncorrectOutput()) { + return handleIncorrectOutput(decomposition, hypothesis); + } else { + return handleIncorrectTarget(decomposition, hypothesis); + } + } + + private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + // Transition with incorrect output always implies missing one-shot timer: + logger.debug("Found missing one-shot via incorrect output."); + return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); + } + + private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + if (decomposition.input() instanceof NonDelayingInput ndi) { + // If decomposition at non-delaying input + considered as self-loop, treat as false ignore: + if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == SymbolFilterResponse.IGNORE) { + return new FalseIgnoreResult<>(decomposition.state().getLocation(), ndi); + } + + return this.handleIncorrectTargetNonDelaying(decomposition, hypothesis); + + } else if (decomposition.input() instanceof TimeStepSymbol) { + return this.handleIncorrectTargetTimeStep(decomposition, hypothesis); + } else { + throw new AssertionError("Unexpected symbol type."); + } + } + + private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis, long maxInitialValue) { + var newOneShot = LStarLocalTimerMealy.selectOneShotTimer(hypothesis.getSortedTimers(decomposition.state().getLocation()), maxInitialValue); + logger.debug("Missing one-shot: setting ({}|{}) to one-shot.", hypothesis.getLocationPrefix(decomposition.state()), newOneShot); + return new MissingOneShotResult<>(decomposition.state().getLocation(), newOneShot); + } + + private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + // Check if there is a one-shot timer expiring at the next time step: + + List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); + assert !localTimers.isEmpty(); + + // If location has a one-shot timer, this is the one with the highest initial value: + var lastTimer = localTimers.get(localTimers.size() - 1); + if (!lastTimer.periodic()) { + if (lastTimer.initial() - 1 != decomposition.state().getEntryDistance()) { + throw new AssertionError("Incorrect target must be at timeout of non-periodic timer."); + } + logger.debug("Inferred missing discriminator at timeout."); + return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), decomposition.input(), decomposition.discriminator()); + } else if (!decomposition.state().isStableConfig()) { + logger.debug("Found missing one-shot via incorrect target in non-stable config."); + return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); + } else { + logger.debug("Found missing one-shot via incorrect target in stable config."); + return new MissingOneShotResult<>(decomposition.state().getLocation(), localTimers.get(0)); // lowest initial value + } + } + + private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + // 1: can be a missing discriminator? + + // Check if correct target in entry w.r.t. discriminator: + var transPrefix = hypothesis.getLocationPrefix(decomposition.state()).append(decomposition.input()); + var succState = hypothesis.getSemantics().traceInputs(transPrefix); // successor state in hypothesis + + var actualSuffixOutput = this.timeOracle.querySuffixOutput(transPrefix, decomposition.discriminator()); + var expSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getPrefix(succState), decomposition.discriminator()); + + if (!actualSuffixOutput.equals(expSuffixOutput)) { + logger.debug("Inferred missing discriminator at non-delaying input."); + return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), decomposition.input(), decomposition.discriminator()); + } + + // 2: can be a local reset? + if (decomposition.state().isStableConfig()) { + logger.debug("Inferred missing reset in stable config."); + return new MissingResetResult<>(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + } + + // Non-stable -> explicitly test for missing reset: + var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + var trans = hypothesis.getTransition(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + if (trans == null) { + throw new AssertionError(); + } + + // Must loop without reset: + if (trans.successor().equals(decomposition.state().getLocation()) && (!isLocalReset)) { + // Must have at least two stable configs: + var firstTimer = hypothesis.getSortedTimers(decomposition.state().getLocation()).get(0); + if (firstTimer.initial() > 1) { + // Must not self-loop in at least one non-entry stable config: + var resetTransPrefix = hypothesis.getPrefix(decomposition.state()) + .append(new TimeStepSymbol<>()) // prefix of first stable config that is not entry config + .append(decomposition.input()); // successor at $i$ in that config + + Word> suffix = Word.fromLetter(new TimeoutSymbol<>()); + var transSuffixOutput = this.timeOracle.querySuffixOutput(resetTransPrefix, suffix); + var entryConfigSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getLocationPrefix(decomposition.state()), suffix); + + if (transSuffixOutput.equals(entryConfigSuffixOutput)) { + logger.debug("Inferred missing reset in non-stable config."); + return new MissingResetResult<>(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + } + } + + } + + // 3: add missing local reset + logger.debug("Inferred missing one-shot timer from incorrect target at non-delaying input."); + return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); + } + + +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java new file mode 100644 index 000000000..cc1942d08 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java @@ -0,0 +1,66 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + +import de.learnlib.acex.AbstractBaseCounterexample; +import de.learnlib.oracle.TimedQueryOracle; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.word.Word; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.function.Function; + +/** + * An abstract counterexample used by the MMLT learner. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { + + private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyInconsPrefixTransformAcex.class); + + private final TimedQueryOracle timeOracle; + private final Word> suffix; + + private final Function>, Word>> asTransform; + + /** + * Constructor. + * + * @param suffix suffix of the counterexample (= the word that we analyze) + * @param timeOracle membership oracle + * @param asTransform retrieves the prefix of the system state in the hypothesis addressed by a word + */ + public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, TimedQueryOracle timeOracle, Function>, Word>> asTransform) { + super(suffix.length()); + this.timeOracle = timeOracle; + this.suffix = suffix; + this.asTransform = asTransform; + } + + public Function>, Word>> getAsTransform() { + return asTransform; + } + + @Override + public Word> computeEffect(int index) { + // Split the word at our index: + Word> prefix = this.suffix.prefix(index); // everything up to *index* (exclusive) + Word> suffix = this.suffix.subWord(index); // everything from *index* (inclusive) + + // Identify access sequence of system state for prefix: + Word> accessSequence = this.asTransform.apply(prefix); + + // Query *hypothesis state* + *suffix*: + return this.timeOracle.querySuffixOutput(accessSequence, suffix); + } + + + @Override + public boolean checkEffects(Word> eff1, Word> eff2) { + // Same behavior at different indices? + logger.debug(String.format("Comparing (%s) AND (%s): %s", eff1, eff2, eff2.isSuffixOf(eff1))); + return eff2.isSuffixOf(eff1); + } +} \ No newline at end of file diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java new file mode 100644 index 000000000..a5a584f79 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java @@ -0,0 +1,22 @@ +package de.learnlib.algorithm.lstar.mmlt.cex; + + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.word.Word; + +/** + * Represents an output inconsistency used by the MMLT learner. + * + * @param prefix Prefix + * @param suffix Suffix input + * @param targetOut Suffix output in SUL + * @param hypOut Suffix output in hypothesis + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public record LocalTimerMealyOutputInconsistency(Word> prefix, + Word> suffix, + Word> targetOut, + Word> hypOut) { +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java new file mode 100644 index 000000000..cb990699d --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java @@ -0,0 +1,11 @@ +package de.learnlib.algorithm.lstar.mmlt.cex.results; + +/** + * Outcome of a counterexample analysis. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public abstract class CexAnalysisResult { +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java new file mode 100644 index 000000000..36c4eaa5b --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java @@ -0,0 +1,29 @@ +package de.learnlib.algorithm.lstar.mmlt.cex.results; + + +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; + +/** + * The specified symbol is considered to be falsely ignored by the symbol filter. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class FalseIgnoreResult extends CexAnalysisResult { + private final S location; + private final NonDelayingInput symbol; + + public FalseIgnoreResult(S location, NonDelayingInput symbol) { + this.location = location; + this.symbol = symbol; + } + + public S getLocation() { + return location; + } + + public NonDelayingInput getSymbol() { + return symbol; + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java new file mode 100644 index 000000000..9e2034f7d --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java @@ -0,0 +1,36 @@ +package de.learnlib.algorithm.lstar.mmlt.cex.results; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.word.Word; + +/** + * The target at the identified transition is incorrect due to a missing discriminator. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class MissingDiscriminatorResult extends CexAnalysisResult { + private final S location; + private final LocalTimerMealySemanticInputSymbol input; + private final Word> discriminator; + + public MissingDiscriminatorResult(S location, LocalTimerMealySemanticInputSymbol input, Word> discriminator) { + this.location = location; + this.input = input; + this.discriminator = discriminator; + } + + public S getLocation() { + return location; + } + + public LocalTimerMealySemanticInputSymbol getInput() { + return input; + } + + public Word> getDiscriminator() { + return discriminator; + } + +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java new file mode 100644 index 000000000..3da5d5f01 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -0,0 +1,28 @@ +package de.learnlib.algorithm.lstar.mmlt.cex.results; + +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; + +/** + * The provided timer should become one-shot. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class MissingOneShotResult extends CexAnalysisResult { + private final S location; + private final MealyTimerInfo timeout; + + public MissingOneShotResult(S location, MealyTimerInfo timeout) { + this.location = location; + this.timeout = timeout; + } + + public S getLocation() { + return location; + } + + public MealyTimerInfo getTimeout() { + return timeout; + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java new file mode 100644 index 000000000..e6e538a25 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java @@ -0,0 +1,30 @@ +package de.learnlib.algorithm.lstar.mmlt.cex.results; + + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; + +/** + * There should be a local reset at the specified transition. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class MissingResetResult extends CexAnalysisResult { + private final S location; + private final NonDelayingInput input; + + public MissingResetResult(S location, NonDelayingInput input) { + this.location = location; + this.input = input; + } + + public S getLocation() { + return location; + } + + public LocalTimerMealySemanticInputSymbol getInput() { + return input; + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java new file mode 100644 index 000000000..53c0228c4 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java @@ -0,0 +1,55 @@ +package de.learnlib.algorithm.lstar.mmlt.hyp; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.word.Word; + +import java.util.List; + +/** + * Defines several methods that the learner can use to interact with its hypothesis. + * These methods should not be used by the teacher, to maintain separation between both. + * + * @param Location type + * @param Input type for non-delaying inputs + */ +public interface IInternalLocalTimerMealyHypothesis { + + /** + * Returns the prefix assigned to the provided configuration. + * The assigned prefix is the concatenation of the prefix assigned to the active location + * and the minimal number of time steps needed to reach the configuration after entering its location + * (= entry distance). + * + * @param configuration Considered configuration + * @return Assigned prefix + */ + Word> getPrefix(LocalTimerMealyConfiguration configuration); + + Word> getPrefix(Word> prefix); + + /** + * Returns the prefix assigned to the location that is active in the provided configuration. + * + * @param configuration Considered configuration + * @return Assigned prefix + */ + Word> getLocationPrefix(LocalTimerMealyConfiguration configuration); + + /** + * Returns a prefix for the given location. + * This prefix is deterministic in the RS learner. + * + * @param location Location + * @return Location prefix + */ + Word> getPrefix(S location); + + /** + * Convenience method that sorts timers of the provided location by initial value. + * + * @return Sorted timers. Empty list if no timers. + */ + List> getSortedTimers(S location); +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java new file mode 100644 index 000000000..5a751c009 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java @@ -0,0 +1,118 @@ +package de.learnlib.algorithm.lstar.mmlt.hyp; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * An MMLT hypothesis that includes a prefix mapping. + * This mapping assigns a short prefix to each location. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyHypothesis implements LocalTimerMealy, IInternalLocalTimerMealyHypothesis { + private final LocalTimerMealy automaton; + private final Map>> prefixMap; // location -> prefix + + public LocalTimerMealyHypothesis(LocalTimerMealy automaton, Map>> prefixMap) { + this.automaton = automaton; + this.prefixMap = prefixMap; + } + + @Override + public Word> getPrefix(LocalTimerMealyConfiguration configuration) { + var locPrefix = getLocationPrefix(configuration); + if (configuration.isEntryConfig()) { + return locPrefix; // entry distance = 0 + } else { + return locPrefix.append(new TimeStepSequence<>(configuration.getEntryDistance())); + } + } + + @Override + public Word> getPrefix(Word> prefix) { + var resultingConfig = getSemantics().traceInputs(prefix); + return getPrefix(resultingConfig); + } + + + @Override + public Word> getLocationPrefix(LocalTimerMealyConfiguration configuration) { + var locPrefix = this.prefixMap.get(configuration.getLocation()); + if (locPrefix == null) throw new AssertionError(); + return locPrefix; + } + + + @Override + public Word> getPrefix(S location) { + return prefixMap.get(location); + } + + @Override + public O getSilentOutput() { + return automaton.getSilentOutput(); + } + + @Override + public AbstractSymbolCombiner getOutputCombiner() { + return automaton.getOutputCombiner(); + } + + @Override + public Alphabet> getInputAlphabet() { + return automaton.getInputAlphabet(); + } + + @Override + public Alphabet> getUntimedAlphabet() { + return automaton.getUntimedAlphabet(); + } + + @Override + public S getInitialState() { + return automaton.getInitialState(); + } + + @Override + public Collection getStates() { + return automaton.getStates(); + } + + @Override + public @Nullable LocalTimerMealyTransition getTransition(S location, LocalTimerMealyInputSymbol input) { + return automaton.getTransition(location, input); + } + + @Override + public boolean isLocalReset(S location, NonDelayingInput input) { + return automaton.isLocalReset(location, input); + } + + @Override + public List> getSortedTimers(S location) { + return automaton.getSortedTimers(location); + } + + @Override + public LocalTimerMealySemantics getSemantics() { + return new net.automatalib.automaton.time.impl.mmlt.LocalTimerMealySemantics<>(this); + } + + +} diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java new file mode 100644 index 000000000..a4e9aa8e4 --- /dev/null +++ b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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; + +import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; + +/** + * Model-specific parameters for the MMLT-learner. + * These are used by various filters, oracles, and the MMLT simulator. + * + * @param silentOutput Silent output symbol + * @param maxTimeoutWaitingTime Maximum waiting time for a timeout symbol + * @param maxTimerQueryWaitingTime Maximum waiting time for timer queries + * @param outputCombiner Function for combining simultaneously occurring outputs of timers + * @param + */ +public record LocalTimerMealyModelParams(O silentOutput, + long maxTimeoutWaitingTime, + long maxTimerQueryWaitingTime, + AbstractSymbolCombiner outputCombiner) { +} diff --git a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java new file mode 100644 index 000000000..3d3a92947 --- /dev/null +++ b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java @@ -0,0 +1,82 @@ +package de.learnlib.oracle; + +import de.learnlib.query.DefaultQuery; +import de.learnlib.query.Query; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; + +import java.util.Collection; +import java.util.List; + +/** + * Type of oracle used by an MMLT learner. + *

+ * Like a traditional query oracle, this answers output queries. + * In addition, it infers timers by observing timeouts. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public abstract class TimedQueryOracle implements MembershipOracle.MealyMembershipOracle, LocalTimerMealyOutputSymbol> { + + /** + * Response for a timer query. + * + * @param aborted True if query was aborted due to missing timeout. + * @param timers Identified timers + * @param Untimed output suffix type + */ + public record TimerQueryResult(boolean aborted, List> timers) { + + } + + @Override + public void processQueries(Collection, Word>>> collection) { + for (var q : collection) { + DefaultQuery, Word>> query = new DefaultQuery<>(q.getPrefix(), q.getSuffix()); + this.querySuffixOutput(query); + q.answer(query.getOutput()); + } + } + + /** + * Observes and aggregates any timeouts that occur after providing the given input to the SUL. + * Stops when observing inconsistent behavior. + * + * @param prefix Input to give to the SUL. + * @param maxTotalWaitingTime Maximum total time that is waited for timeouts. + * @return Observed timeouts. Empty, if none. + */ + public abstract TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime); + + /** + * Queries the suffix output for the provided query. + * + * @param query Input query. + */ + public void querySuffixOutput(DefaultQuery, Word>> query) { + this.querySuffixOutputInternal(query); + } + + /** + * Like querySuffixOutput but does not require a query object. + * + * @param prefix Prefix + * @param suffix Suffix + */ + public final Word> querySuffixOutput(Word> prefix, Word> suffix) { + DefaultQuery, Word>> query = new DefaultQuery<>(prefix, suffix); + this.querySuffixOutputInternal(query); + return query.getOutput(); + } + + /** + * Queries the output for the provided input sequence. + * + * @param query Input query. + */ + protected abstract void querySuffixOutputInternal(DefaultQuery, Word>> query); + +} diff --git a/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java b/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java new file mode 100644 index 000000000..1f89ec976 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java @@ -0,0 +1,61 @@ +package de.learnlib.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.time.Duration; +import java.util.Optional; + +/** + * A dummy implementation of {@link StatsContainerX} that does nothing. + */ +public class DummyStatsContainer implements StatsContainerX { + @Override + public void addTextInfo(String id, @Nullable String description, String text) { + + } + + @Override + public Optional getTextValue(String id) { + return Optional.empty(); + } + + @Override + public void setFlag(String id, @Nullable String description, boolean value) { + + } + + @Override + public Optional getFlagValue(String id) { + return Optional.empty(); + } + + @Override + public void startOrResumeClock(String id, @Nullable String description) { + + } + + @Override + public void pauseClock(String id) { + + } + + @Override + public Optional getClockValue(String id) { + return Optional.empty(); + } + + @Override + public void increaseCounter(String id, @Nullable String description, long increment) { + + } + + @Override + public void setCounter(String id, @Nullable String description, long count) { + + } + + @Override + public Optional getCount(String id) { + return Optional.empty(); + } +} diff --git a/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java b/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java new file mode 100644 index 000000000..af7f64a59 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java @@ -0,0 +1,13 @@ +package de.learnlib.statistic.container; + +/** + * Interface for a component that is interested in storing statistics in a container. + */ +public interface LearnerStatsProvider { + /** + * Provides a container for storing statistics. + * + * @param container Stats container. + */ + void setStatsContainer(StatsContainerX container); +} diff --git a/api/src/main/java/de/learnlib/statistic/container/StatsContainerX.java b/api/src/main/java/de/learnlib/statistic/container/StatsContainerX.java new file mode 100644 index 000000000..943c5b513 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/container/StatsContainerX.java @@ -0,0 +1,124 @@ +package de.learnlib.statistic.container; + + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.time.Duration; +import java.util.Optional; + +/** + * Interface for a container that stores various statistics during learning. + */ +public interface StatsContainerX { + + // Generic text + + /** + * Stores the provided text for the given id + * and assigns the provided description. + * + * @param id Text id + * @param description Description of the data, e.g., "configuration" + * @param text The text to be stored + */ + void addTextInfo(String id, @Nullable String description, String text); + + /** + * Retrieves the text with the provided id. + * + * @param id Id of the text value + * @return The stored text, or empty if there is no text with this id. + */ + Optional getTextValue(String id); + + + // Boolean flags + + /** + * Stores the provided boolean for the given id + * and optionally assigns the provided description. + * + * @param id Flag id + * @param description Description of the boolean, e.g., "accurate" + * @param value The boolean value to be stored + */ + void setFlag(String id, @Nullable String description, boolean value); + + /** + * Retrieves the flag with the provided id. + * + * @param id Id of the boolean value + * @return The stored boolean, or empty, if no boolean with this id exists. + */ + Optional getFlagValue(String id); + + + // Time + + /** + * Starts the clock with the given id and optionally assigns the provided description. + * If there is already a clock with this id, it is resumed. + * + * @param id Clock id + * @param description Description of the clock, e.g., "learning time" + */ + void startOrResumeClock(String id, @Nullable String description); + + /** + * Pauses the clock with the given id. If there is no clock with this id, nothing happens. + * + * @param id Clock id + */ + void pauseClock(String id); + + /** + * Returns the current value of the clock with the given id. + * + * @param id Clock id + * @return The current value of the clock, or empty, if no clock with this id exists. + */ + Optional getClockValue(String id); + + + // Counter + + /** + * Increases the counter with the given id and optionally assigns the provided description. + * If no counter with this id exists, it is created. + * + * @param id Counter id + * @param description Description of the counter, e.g., "number of rounds" + */ + default void increaseCounter(String id, @Nullable String description) { + increaseCounter(id, description, 1); + } + + /** + * Increases the counter with the given id by the provided increment + * and optionally assigns the provided description. + * If no counter with this id exists, it is created and initialized with the provided increment. + * + * @param id Counter id + * @param description Description of the counter, e.g., "number of rounds" + * @param increment Amount to increase the counter by + */ + void increaseCounter(String id, @Nullable String description, long increment); + + /** + * Sets the counter with the given id to the provided value and optionally assigns the provided description. + * If no counter with this id exists, it is created. + * + * @param id Counter id + * @param description Description of the counter, e.g., "number of rounds" + * @param count New value for the counter. Must be greater than zero. + */ + void setCounter(String id, @Nullable String description, long count); + + /** + * Gets the value of the counter with the given id. Returns empty, if no counter with this id exists. + * + * @param id Counter id + * @return The value of the counter, or empty, if no counter with this id exists. + */ + Optional getCount(String id); +} diff --git a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java new file mode 100644 index 000000000..a77f04cc2 --- /dev/null +++ b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java @@ -0,0 +1,119 @@ +package de.learnlib.sul; + +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A SUL with MMLT semantics. We use this type to interface with real systems and to + * simulate MMLT models. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public abstract class LocalTimerMealySUL { + + /** + * Follows the provided input word, starting at the current system state. + * The input word must not contain timeout symbols. Otherwise, an error occurs. + * + * @param input Input suffix. + */ + public void follow(Word> input) { + this.follow(input, -1); + } + + /** + * Follows the provided input word, starting at the current configuration. + * + * @param input Input suffix. + * @param maxTimeout Max. timeout to use for timeoutSymbols. + */ + public void follow(Word> input, long maxTimeout) { + for (var s : input) { + if (s instanceof NonDelayingInput ndi) { + this.step(ndi); + } else if (s instanceof TimeStepSequence) { + this.collectTimeouts((TimeStepSequence) s); + } else if (s instanceof TimeoutSymbol) { + if (maxTimeout <= 0) { + throw new IllegalArgumentException("Must supply timeout when using timeout symbols."); + } + this.timeoutStep(maxTimeout); + } else { + throw new IllegalArgumentException("Unknown suffix type."); + } + } + } + + /** + * Provides an input to the SUL and returns the observed output. + * + * @param input Input + * @return SUL output. + */ + public abstract LocalTimerMealyOutputSymbol step(NonDelayingInput input); + + /** + * Waits until a timeout occurs or the provided time is reached. + *

+ * We may observe no timeout if either the waiting time is too small or there are no timers defined + * in the current location. + * + * @param maxTime Maximum waiting time. + * @return Observed timer output with waiting time, or null, if no timeout observed. + */ + @Nullable + public abstract LocalTimerMealyOutputSymbol timeoutStep(long maxTime); + + /** + * Waits for one time unit and returns the observed output. + * + * @return Null if no output occurred, timer output if at least one timer expired. + * The delay of this output is set to zero. + */ + @Nullable + public LocalTimerMealyOutputSymbol timeStep() { + var res = this.timeoutStep(1); + if (res != null) { + return new LocalTimerMealyOutputSymbol<>(res.getSymbol()); + } + return null; + } + + /** + * Waits for the specified time and returns all observed timeouts. + * + * @param input Waiting time. + * @return Observed timeouts. Empty, if none. + */ + public Word> collectTimeouts(TimeStepSequence input) { + WordBuilder> wbOutput = new WordBuilder<>(); + + long remainingTime = input.getTimeSteps(); + while (remainingTime > 0) { + LocalTimerMealyOutputSymbol nextTimeout = this.timeoutStep(remainingTime); + if (nextTimeout == null) { + // No timer will expire during remaining waiting time: + break; + } else { + wbOutput.append(nextTimeout); + remainingTime -= nextTimeout.getDelay(); + } + } + + return wbOutput.toWord(); + } + + + /** + * Prepares the SUL for a new query. + */ + public abstract void pre(); + + /** + * Deinitializes the SUL. + */ + public abstract void post(); +} diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java new file mode 100644 index 000000000..d88c23043 --- /dev/null +++ b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java @@ -0,0 +1,38 @@ +package de.learnlib.symbol_filter; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.word.Word; + +/** + * Interface for a symbol filter that can be used to speed-up the learning of MMLTs. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public interface SymbolFilter { + + /** + * Predicts whether the provided symbol is not ignorable in the configuration + * identified by the provided prefix. + *

+ * "Ignorable" means the symbol belongs to a silent self-loop. + *

+ * Predictions may not be correct, i.e., an accepted symbol may be actually ignorable and an ignored symbol + * may be actually not ignorable. + * + * @param prefix Configuration prefix. May contain delay-symbols (= tau inputs). + * @param symbol Queried transition + * @return IGNORE if the symbol is considered ignorable, ACCEPT if it is not. + */ + SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol); + + /** + * Sets the response of the filter for the given transition to the provided response. + * + * @param prefix Configuration prefix. + * @param symbol Queried transition + * @param response New response + */ + void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response); +} diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java new file mode 100644 index 000000000..6c06e93ce --- /dev/null +++ b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.symbol_filter; + +public enum SymbolFilterResponse { + ACCEPT, + IGNORE +} diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index e62d48409..fd3814f99 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -43,4 +43,6 @@ exports de.learnlib.query; exports de.learnlib.statistic; exports de.learnlib.sul; + exports de.learnlib.statistic.container; + exports de.learnlib.symbol_filter; } diff --git a/commons/datastructures/src/main/java/module-info.java b/commons/datastructures/src/main/java/module-info.java index e9ccbfa9b..5b1d9590d 100644 --- a/commons/datastructures/src/main/java/module-info.java +++ b/commons/datastructures/src/main/java/module-info.java @@ -36,6 +36,7 @@ // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static org.checkerframework.checker.qual; + requires org.slf4j; exports de.learnlib.datastructure.discriminationtree; exports de.learnlib.datastructure.discriminationtree.iterators; diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java new file mode 100644 index 000000000..c14a33bc3 --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java @@ -0,0 +1,32 @@ +package de.learnlib.util.statistic.container; + +/** + * A counter that can be increased and set to a particular positive number. + */ +class CounterStatistic extends LearnerStatistic { + private long count; + + public CounterStatistic(String id, String description) { + this(id, description, 0); + } + + public CounterStatistic(String id, String description, long count) { + super(id, description); + this.count = count; + } + + public void setCount(long count) { + if (count < 0) { + throw new IllegalArgumentException(); + } + this.count = count; + } + + public void increase(long increment) { + this.count += increment; + } + + public long getCount() { + return count; + } +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java new file mode 100644 index 000000000..5bb04bcec --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java @@ -0,0 +1,23 @@ +package de.learnlib.util.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A boolean flag that is unset by default and can be set. + */ +class FlagStatistic extends LearnerStatistic { + private boolean flagged; + + public FlagStatistic(String id, @Nullable String description, boolean value) { + super(id, description); + this.flagged = value; + } + + public void setFlag(boolean value) { + this.flagged = value; + } + + public boolean isFlagged() { + return flagged; + } +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java new file mode 100644 index 000000000..efba05c48 --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java @@ -0,0 +1,36 @@ +package de.learnlib.util.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Various types of statistical data to be stored in a StatsContainer. + */ +abstract class LearnerStatistic { + private final String id; + private final String description; + + /** + * Creates a new LearnerStatistic. + * + * @param id Unique id of the statistic. Must be unique within the StatsContainer. + * @param description Optional description of the statistic. If no description is provided, the id is used. + */ + public LearnerStatistic(String id, @Nullable String description) { + this.id = id; + + if (description == null) { + this.description = id; + } else { + this.description = description; + } + } + + public String getId() { + return id; + } + + public String getDescription() { + return description; + } + +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java new file mode 100644 index 000000000..7710aa7a0 --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java @@ -0,0 +1,99 @@ +package de.learnlib.util.statistic.container; + +import de.learnlib.statistic.container.StatsContainerX; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A {@link StatsContainerX} that stores all statistics in a {@link Map}. + */ +public class MapStatsContainer implements StatsContainerX { + private final Map statistics = new HashMap<>(); // id -> stat + + @Override + public void addTextInfo(String id, @Nullable String description, String text) { + statistics.put(id, new TextStatistic(id, description, text)); + } + + @Override + public Optional getTextValue(String id) { + var value = statistics.get(id); + if (value instanceof TextStatistic textStatistic) { + return Optional.of(textStatistic.getText()); + } + return Optional.empty(); + } + + @Override + public void setFlag(String id, @Nullable String description, boolean value) { + statistics.put(id, new FlagStatistic(id, description, value)); + } + + @Override + public Optional getFlagValue(String id) { + var value = statistics.get(id); + if (value instanceof FlagStatistic flagStatistic) { + return Optional.of(flagStatistic.isFlagged()); + } + return Optional.empty(); + } + + @Override + public void startOrResumeClock(String id, @Nullable String description) { + var value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + clockStatistic.resume(); + } else { + // Create and start a new clock: + var newClock = new StopClockStatistic(id, description); + statistics.put(id, newClock); + newClock.resume(); + } + } + + @Override + public void pauseClock(String id) { + var value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + clockStatistic.pause(); + } + } + + @Override + public Optional getClockValue(String id) { + var value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + return Optional.of(clockStatistic.getElapsed()); + } + return Optional.empty(); + } + + @Override + public void increaseCounter(String id, @Nullable String description, long increment) { + var value = statistics.get(id); + if (value instanceof CounterStatistic counterStatistic) { + counterStatistic.increase(increment); + } else { + // Create a new counter: + setCounter(id, description, increment); + } + } + + @Override + public void setCounter(String id, @Nullable String description, long count) { + statistics.put(id, new CounterStatistic(id, description, count)); + } + + @Override + public Optional getCount(String id) { + var value = statistics.get(id); + if (value instanceof CounterStatistic counterStatistic) { + return Optional.of(counterStatistic.getCount()); + } + return Optional.empty(); + } +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java new file mode 100644 index 000000000..9adf3c797 --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java @@ -0,0 +1,36 @@ +package de.learnlib.util.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.time.Duration; +import java.time.Instant; + +/** + * A stop clock that can be paused and resumed. + */ +class StopClockStatistic extends LearnerStatistic { + private Instant started; + private Duration elapsed; + + public StopClockStatistic(String id, @Nullable String description) { + super(id, description); + this.elapsed = Duration.ZERO; + this.started = null; + } + + public void resume() { + this.started = Instant.now(); + } + + public void pause() { + if (started == null) { + return; + } + this.elapsed = this.elapsed.plus(Duration.between(started, Instant.now())); + this.started = null; + } + + public Duration getElapsed() { + return this.elapsed; + } +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java b/commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java new file mode 100644 index 000000000..5d9e81aaf --- /dev/null +++ b/commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java @@ -0,0 +1,16 @@ +package de.learnlib.util.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +class TextStatistic extends LearnerStatistic { + private final String text; + + public TextStatistic(String id, @Nullable String description, String text) { + super(id, description); + this.text = text; + } + + public String getText() { + return text; + } +} diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java new file mode 100644 index 000000000..bdcd17af0 --- /dev/null +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java @@ -0,0 +1,71 @@ +package de.learnlib.driver.simulator; + +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import org.checkerframework.checker.nullness.qual.Nullable; + + +/** + * Simulates the extended semantics of an MMLT. + * + * @param Location type. + * @param Non-delaying input type. + * @param Output symbol type. + */ +public class LocalTimerMealySimulatorSUL extends LocalTimerMealySUL { + + private final LocalTimerMealy automaton; + + private LocalTimerMealyConfiguration currentConfiguration; + + + public LocalTimerMealySimulatorSUL(LocalTimerMealy automaton) { + this.automaton = automaton; + this.currentConfiguration = null; + } + + + @Override + public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + if (this.currentConfiguration == null) { + throw new IllegalStateException("Not initialized!"); + } + + var trans = this.automaton.getSemantics().getTransition(this.currentConfiguration, input); + this.currentConfiguration = trans.target(); + return trans.output(); + } + + @Override + public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + if (this.currentConfiguration == null) { + throw new IllegalStateException("Not initialized!"); + } + + var trans = this.automaton.getSemantics().getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); + this.currentConfiguration = trans.target(); + + if (trans.output().equals(automaton.getSemantics().getSilentOutput())) { + // No timeout observed: + return null; + } else { + return trans.output(); + } + } + + @Override + public void pre() { + this.currentConfiguration = automaton.getSemantics().getInitialConfiguration().copy(); + } + + @Override + public void post() { + this.currentConfiguration = null; + } + + +} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java new file mode 100644 index 000000000..bddb81c3c --- /dev/null +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -0,0 +1,170 @@ +package de.learnlib.filter.cache.mmlt; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A node in the tree cache used by the MMLT learner. + *

+ * A node has a parent and children for an arbitrary number of transitions + * with a non-delaying input. + * There is at most one timed transition. + * This transition has a sequence of time steps as input. + * The output is the output at the last time step in the sequence. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class CacheTreeNode { + private record CacheTreeTransition(LocalTimerMealyOutputSymbol output, CacheTreeNode target) { + } + + + @Nullable + private CacheTreeNode parent; + private LocalTimerMealySemanticInputSymbol parentInput; + + private long timeout; + @Nullable + private CacheTreeTransition timeTransition; + + private Map, CacheTreeTransition> untimedChildren; + + public CacheTreeNode(CacheTreeNode parent, LocalTimerMealySemanticInputSymbol parentInput) { + this.parent = parent; + this.parentInput = parentInput; + + this.timeTransition = null; + this.timeout = -1; + + this.untimedChildren = new HashMap<>(); + } + + public CacheTreeNode addTimeChild(long timeout, LocalTimerMealyOutputSymbol output) { + if (this.hasTimeChild()) { + throw new IllegalStateException("State already has time child."); + } + + CacheTreeNode newChild = new CacheTreeNode<>(this, new TimeStepSequence<>(timeout)); + this.timeout = timeout; + this.timeTransition = new CacheTreeTransition<>(output, newChild); + return newChild; + } + + + // ------------------------------------------------------- + + /** + * Infers the number of predecessor nodes, i.e., the level + * of this node. + * + * @return Number of predecessors. Zero if this is the cache root. + */ + public int getNumPredecessors() { + int parentCount = 0; + var current = this; + + while (current.getParent() != null) { + parentCount++; + current = current.getParent(); + } + return parentCount; + } + + public boolean hasTimeChild() { + return this.timeTransition != null; + } + + public long getTimeout() { + if (!this.hasTimeChild()) { + throw new IllegalStateException(); + } + return timeout; + } + + public LocalTimerMealyOutputSymbol getTimeoutOutput() { + if (!this.hasTimeChild()) { + throw new IllegalStateException(); + } + return this.timeTransition.output(); + } + + public CacheTreeNode getTimeoutChild() { + if (!this.hasTimeChild()) { + throw new IllegalStateException(); + } + return this.timeTransition.target(); + } + + /** + * Breaks the time sequence: introduces a new child cx after the given number of time steps + * and adds the former child as child to cx. + * + * @param newTimeout Time at which the timeout sequence is split + * @param output Output at the end of the new time sequence + * @return New child node + */ + public CacheTreeNode splitTimeout(long newTimeout, LocalTimerMealyOutputSymbol output) { + if (this.timeTransition == null || newTimeout >= this.getTimeout()) { + throw new IllegalArgumentException("Must split at lower timeout."); + } + + CacheTreeNode newChild = new CacheTreeNode<>(this, new TimeStepSequence<>(newTimeout)); + newChild.timeout = this.timeout - newTimeout; + newChild.timeTransition = this.timeTransition; // keep output + target + this.timeTransition.target().setParent(newChild, new TimeStepSequence<>(this.timeout - newTimeout)); + + this.timeout = newTimeout; + this.timeTransition = new CacheTreeTransition<>(output, newChild); + + return newChild; + } + + // ------------------------------------------------------- + public CacheTreeNode getParent() { + return parent; + } + + public LocalTimerMealySemanticInputSymbol getParentInput() { + return parentInput; + } + + public void setParent(CacheTreeNode parent, LocalTimerMealySemanticInputSymbol parentInput) { + this.parent = parent; + this.parentInput = parentInput; + } + + // ------------------------------------------------------- + public boolean hasChild(NonDelayingInput input) { + return this.untimedChildren.containsKey(input); + } + + public LocalTimerMealyOutputSymbol getOutput(NonDelayingInput input) { + return this.untimedChildren.get(input).output(); + } + + public CacheTreeNode getChild(NonDelayingInput input) { + return this.untimedChildren.get(input).target(); + } + + public CacheTreeNode addUntimedChild(NonDelayingInput input, LocalTimerMealyOutputSymbol output) { + if (untimedChildren.containsKey(input)) { + throw new IllegalArgumentException("State already has an child for this input."); + } + + CacheTreeNode child = new CacheTreeNode<>(this, input); + this.untimedChildren.put(input, new CacheTreeTransition<>(output, child)); + return child; + } + + public Map, CacheTreeTransition> getUntimedChildren() { + return Collections.unmodifiableMap(this.untimedChildren); + } +} \ No newline at end of file diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java new file mode 100644 index 000000000..4ece66999 --- /dev/null +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java @@ -0,0 +1,261 @@ +package de.learnlib.filter.cache.mmlt; + + +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.automaton.transducer.impl.CompactMealy; +import net.automatalib.graph.Graph; +import net.automatalib.graph.concept.GraphViewable; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; + +/** + * Caches queries sent to an AbstractLocalTimerMealySUL. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class FastLocalTimerMealyTreeCacheSUL extends LocalTimerMealySUL implements GraphViewable, LearnerStatsProvider { + private final LocalTimerMealySUL delegate; + + private final CacheTreeNode cacheRoot; + private CacheTreeNode currentState; + + private final LocalTimerMealyOutputSymbol silentOutput; + private boolean cacheMiss; + + private StatsContainerX stats = new DummyStatsContainer(); + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } + + public FastLocalTimerMealyTreeCacheSUL(LocalTimerMealySUL delegate, O silentOutput) { + this.delegate = delegate; + this.silentOutput = new LocalTimerMealyOutputSymbol<>(silentOutput); + + // Init cache: + this.cacheRoot = new CacheTreeNode<>(null, null); + this.currentState = null; + } + + + private void followCurrentPrefix() { + this.delegate.pre(); + + WordBuilder> wbPrefix = new WordBuilder<>(); + + var current = this.currentState; + while (current.getParent() != null) { + wbPrefix.append(current.getParentInput()); + current = current.getParent(); + } + + Word> prefix = wbPrefix.reverse().toWord(); + this.delegate.follow(prefix); + } + + @Override + public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + if (this.currentState == null) { + throw new IllegalStateException(); + } + + if (!cacheMiss) { + if (this.currentState.hasChild(input)) { + LocalTimerMealyOutputSymbol output = this.currentState.getOutput(input); + this.currentState = this.currentState.getChild(input); + return output; + } + this.followCurrentPrefix(); + this.cacheMiss = true; + } + + // Cache miss -> query + insert: + LocalTimerMealyOutputSymbol output = this.delegate.step(input); + this.currentState = this.currentState.addUntimedChild(input, output); + return output; + } + + + @Override + public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + if (currentState == null) { + throw new IllegalStateException(); + } + + long remaining = maxTime; + if (!this.cacheMiss) { + // Move to closest state in cache: + while (remaining > 0) { + if (!currentState.hasTimeChild()) { + break; // cache miss + } + + if (currentState.getTimeout() > remaining) { + // Split current timeout: + this.currentState = this.currentState.splitTimeout(remaining, silentOutput); + return null; // no timer in this state + } + + LocalTimerMealyOutputSymbol currentOutput = currentState.getTimeoutOutput(); + remaining -= currentState.getTimeout(); + this.currentState = this.currentState.getTimeoutChild(); + + if (!currentOutput.equals(this.silentOutput)) { + // Found valid timeout: + return new LocalTimerMealyOutputSymbol<>(maxTime - remaining, currentOutput.getSymbol()); + } + } + + if (remaining == 0) { + return null; // no timer in this state + } + + this.followCurrentPrefix(); + this.cacheMiss = true; + } + + + LocalTimerMealyOutputSymbol timeoutStepResult = this.delegate.timeoutStep(remaining); + if (timeoutStepResult == null) { // no timers here + this.currentState = this.currentState.addTimeChild(remaining, this.silentOutput); + return null; + } else { + this.currentState = this.currentState.addTimeChild(timeoutStepResult.getDelay(), new LocalTimerMealyOutputSymbol<>(timeoutStepResult.getSymbol())); + return new LocalTimerMealyOutputSymbol<>(maxTime - remaining + timeoutStepResult.getDelay(), timeoutStepResult.getSymbol()); + } + + } + + + @Override + public void pre() { + this.currentState = this.cacheRoot; + this.cacheMiss = false; + } + + @Override + public void post() { + this.currentState = null; + + if (this.cacheMiss) { + this.delegate.post(); + stats.increaseCounter("Cache_Missed_Count", "Cache misses"); + } else { + stats.increaseCounter("Cache_Hit_Count", "Cache hits"); + } + } + + // ------------------------------------------------------- + + /** + * Returns the leaves of the cache tree. + * + * @return List of leaf nodes. + */ + private List> getLeaves() { + List> leaves = new ArrayList<>(); + + Deque> unvisited = new ArrayDeque<>(); + unvisited.add(this.cacheRoot); + + while (!unvisited.isEmpty()) { + CacheTreeNode currentNode = unvisited.remove(); + + int successors = 0; + if (currentNode.hasTimeChild()) { + unvisited.add(currentNode.getTimeoutChild()); + successors++; + } + + for (var sym : currentNode.getUntimedChildren().keySet()) { + unvisited.add(currentNode.getChild(sym)); + successors++; + } + if (successors == 0) { // leaf + leaves.add(currentNode); + } + } + + return leaves; + } + + public List>> listAllWords() { + List> leaves = this.getLeaves(); + + List>> finalWords = new ArrayList<>(leaves.size()); + + for (var leaf : leaves) { + // Word builder capacity = number of predecessors: + int symCount = leaf.getNumPredecessors(); + WordBuilder> wbInput = new WordBuilder<>(symCount); + + // Move towards the root: + var current = leaf; + while (current.getParent() != null) { + wbInput.append(current.getParentInput()); + current = current.getParent(); + } + + // Start at root -> flip buffer: + wbInput.reverse(); + finalWords.add(wbInput.toWord()); + } + + return finalWords; + } + + @Override + public Graph graphView() { + // Convert tree to a mealy automaton: + CompactMealy, LocalTimerMealyOutputSymbol> mealy = new CompactMealy<>(new GrowingMapAlphabet<>()); + + Map, Integer> stateMap = new HashMap<>(); + stateMap.put(this.cacheRoot, mealy.addInitialState()); + + Deque> pending = new ArrayDeque<>(); + pending.add(this.cacheRoot); + + + while (!pending.isEmpty()) { + CacheTreeNode current = pending.remove(); + + if (current.hasTimeChild()) { + var child = current.getTimeoutChild(); + if (!stateMap.containsKey(child)) { + stateMap.put(child, mealy.addState()); + pending.add(child); + } + mealy.addAlphabetSymbol(new TimeStepSequence<>(current.getTimeout())); + mealy.addTransition(stateMap.get(current), new TimeStepSequence<>(current.getTimeout()), stateMap.get(child), current.getTimeoutOutput()); + } + + for (var sym : current.getUntimedChildren().keySet()) { + var child = current.getChild(sym); + if (!stateMap.containsKey(child)) { + stateMap.put(child, mealy.addState()); + pending.add(child); + } + mealy.addAlphabetSymbol(sym); + mealy.addTransition(stateMap.get(current), sym, stateMap.get(child), current.getOutput(sym)); + } + } + + return mealy.graphView(); + } + + +} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java new file mode 100644 index 000000000..7f4817d68 --- /dev/null +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -0,0 +1,80 @@ +package de.learnlib.filter.cache.mmlt; + +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Avoids redundant queries for timeouts. + *

+ * Assume we waited maxDelay for a timeout and observed no expiration. + * Then, any consecutive timeout-input must also show no timer (assuming sufficient maxDelay). + * Hence, we do not need to query the SUL for these. + *

+ * We may observe a timeout again after any non-delaying input, as this may + * trigger a location-change. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class TimeoutReducerSUL extends LocalTimerMealySUL implements LearnerStatsProvider { + + private final LocalTimerMealySUL delegate; + private final long maxDelay; + + /** + * Delay since the last non-delaying input OR + * timer expiration. + */ + private long noTimeoutWaitingTime; + + private StatsContainerX stats; + + public TimeoutReducerSUL(LocalTimerMealySUL delegate, long maxDelay, StatsContainerX stats) { + this.delegate = delegate; + this.maxDelay = maxDelay; + this.stats = stats; + } + + @Override + public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + this.noTimeoutWaitingTime = 0; // might observe expirations again + return delegate.step(input); + } + + @Override + public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + if (this.noTimeoutWaitingTime >= this.maxDelay) { + return null; // cannot observe expiration until non-delaying input + } + + var result = delegate.timeoutStep(maxTime); + + if (result == null) { + this.noTimeoutWaitingTime += maxTime; + } else { + this.noTimeoutWaitingTime = 0; + } + + return result; + } + + @Override + public void pre() { + delegate.pre(); + this.noTimeoutWaitingTime = 0; + } + + @Override + public void post() { + delegate.post(); + } + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } +} diff --git a/filters/statistics/pom.xml b/filters/statistics/pom.xml index 83ab5b76e..2e7ec208c 100644 --- a/filters/statistics/pom.xml +++ b/filters/statistics/pom.xml @@ -76,6 +76,11 @@ limitations under the License. mockito-core + + org.checkerframework + checker-qual + + org.testng testng diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java new file mode 100644 index 000000000..0f15793ee --- /dev/null +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java @@ -0,0 +1,98 @@ +package de.learnlib.filter.statistic.sul; + +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + + +/** + * Wrapper for an MMLT SUL that gathers various statistics on queries sent to this SUL. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyStatsSUL extends LocalTimerMealySUL implements LearnerStatsProvider { + private final LocalTimerMealySUL delegate; + private StatsContainerX stats; + + @Nullable + private final String name; + + public LocalTimerMealyStatsSUL(LocalTimerMealySUL delegate, StatsContainerX stats) { + this(delegate, stats, null); + } + + public LocalTimerMealyStatsSUL(LocalTimerMealySUL delegate, StatsContainerX stats, String name) { + this.delegate = delegate; + this.stats = stats; + this.name = name; + } + + public long getResetCount() { + if (this.stats == null) { + throw new IllegalStateException("No stats container set up."); + } + return this.stats.getCount(withPrefix("sul_resets_counter")).get(); + } + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } + + private String withPrefix(String label) { + if (this.name == null) { + return label; + } + return this.name + ":" + label; + } + + @Override + public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + stats.increaseCounter(withPrefix("sul_untimed_syms_counter"), + withPrefix("Total untimed symbols")); + return this.delegate.step(input); + } + + @Override + public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + LocalTimerMealyOutputSymbol res = this.delegate.timeoutStep(maxTime); + if (res == null) { + // Waited until maxTime, no timeout occurred: + stats.increaseCounter(withPrefix("sul_total_time"), + withPrefix("Total query time"), maxTime); + } else { + stats.increaseCounter(withPrefix("sul_total_time"), + withPrefix("Total query time"), res.getDelay()); + } + + return res; + } + + @Override + public Word> collectTimeouts(TimeStepSequence input) { + stats.increaseCounter(withPrefix("sul_total_time"), + withPrefix("Total query time"), + input.getTimeSteps()); + return this.delegate.collectTimeouts(input); + } + + @Override + public void pre() { + this.delegate.pre(); + stats.increaseCounter(withPrefix("sul_resets_counter"), + withPrefix("SUL resets")); + } + + @Override + public void post() { + this.delegate.post(); + } + + +} diff --git a/filters/statistics/src/main/java/module-info.java b/filters/statistics/src/main/java/module-info.java index f262e24f5..90ce2aef0 100644 --- a/filters/statistics/src/main/java/module-info.java +++ b/filters/statistics/src/main/java/module-info.java @@ -34,6 +34,7 @@ // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static de.learnlib.tooling.annotation; + requires static org.checkerframework.checker.qual; exports de.learnlib.filter.statistic; exports de.learnlib.filter.statistic.learner; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java new file mode 100644 index 000000000..b942a2a37 --- /dev/null +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.oracle.equivalence.mmlt; + +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; + +/** + * A simulator oracle for MMLTs. + * + * @param Reference model location type + * @param Hypothesis model location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealySimulatorOracle implements EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>> { + + private final LocalTimerMealy refModel; + + public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { + this.refModel = refModel; + } + + + @Override + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> ignored) { + var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis); + + if (separatingWord != null) { + var sulOutput = refModel.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); + return new DefaultQuery<>(Word.epsilon(), separatingWord, sulOutput); + } else { + return null; + } + } +} diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java new file mode 100644 index 000000000..6557f76b8 --- /dev/null +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.oracle.equivalence.mmlt; + +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.common.util.random.RandomUtil; +import net.automatalib.common.util.string.AbstractPrintable; +import net.automatalib.util.automaton.cover.LocalTimerMealyCover; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * Searches for counterexamples that reveal local resets. + *

+ * - Takes any prefix from a known location + * - Appends a single time step. + * - Appends inputs of all inputs that self-loop in that location. + * - Appends timeout. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class ResetSearchOracle implements EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>> { + + private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); + + private final TimedQueryOracle timeOracle; + private final Random locPrefixRandom; + + private final double loopInsertPerc; + private final double testedLocPerc; + + private final long loopingInputSelectionSeed; + + public ResetSearchOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { + this.timeOracle = timeOracle; + this.locPrefixRandom = new Random(seed); + this.loopInsertPerc = loopInsertPerc; + this.testedLocPerc = testedLocPerc; + + this.loopingInputSelectionSeed = seed; + } + + private List> getLoopingSymbols(S sourceLoc, List> alphabet, LocalTimerMealy hypothesis) { + + List> loopingInputs = new ArrayList<>(); + for (var sym : alphabet) { + if (!(sym instanceof NonDelayingInput ndi)) { + throw new AssertionError(); + } + var trans = hypothesis.getTransition(sourceLoc, ndi); + + // Collect self-loops: + if (trans == null || (trans.successor().equals(sourceLoc))) { + loopingInputs.add(sym); + } + } + + return loopingInputs; + } + + @Override + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> ignored) { + if (loopInsertPerc == 0) { + return null; // oracle is disabled + } + + return this.findCexInternal(hypothesis); + } + + private @Nullable DefaultQuery, Word>> findCexInternal + (LocalTimerMealy hypothesis) { + + // Retrieve prefixes from state cover, to establish some separation between learner and teacher: + var stateCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis); + + // Only keep locations that have at least two stable configs: + List>> prefixes = new ArrayList<>(); + for (var loc : stateCover.keySet()) { + if (!hypothesis.getSortedTimers(loc).isEmpty() && + hypothesis.getSortedTimers(loc).get(0).initial() > 1) { + prefixes.add(stateCover.get(loc)); + } + } + + // Sort alphabetically, so that experiments are easily reproducible: + prefixes.sort(Comparator.comparing(AbstractPrintable::toString)); + + // Determine number of tested locations: + int randPrefixes = (int) Math.round(testedLocPerc * prefixes.size()); + if (randPrefixes == 0) { + logger.warn("No prefixes tested. Need higher percentage?"); + return null; + } + + List>> chosenPrefixes = RandomUtil.sampleUnique(locPrefixRandom, prefixes, randPrefixes); + + + List> listAlphabet = new ArrayList<>(hypothesis.getUntimedAlphabet()); + + for (var prefix : chosenPrefixes) { + // Retrieve looping symbols: + var sourceLoc = hypothesis.getSemantics().traceInputs(prefix).getLocation(); + var loopingInputs = getLoopingSymbols(sourceLoc, listAlphabet, hypothesis); + if (loopingInputs.isEmpty()) { + continue; // no loops + } + + // Determine number of looping symbols we want to append: + int randElements = (int) Math.round(loopInsertPerc * loopingInputs.size()); + randElements = Math.min(loopingInputs.size(), randElements); + + List> chosenLoopingInputs = RandomUtil.sampleUnique(new Random(loopingInputSelectionSeed), loopingInputs, randElements); + + + // Create test word: + WordBuilder> wbTestWord = new WordBuilder<>(); + wbTestWord.append(prefix); + wbTestWord.append(new TimeStepSymbol<>()); + wbTestWord.append(Word.fromList(chosenLoopingInputs)); + wbTestWord.append(new TimeoutSymbol<>()); + + // Check if counterexample: + var testWord = wbTestWord.toWord(); + + var hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), testWord); + var sulOutput = timeOracle.querySuffixOutput(Word.epsilon(), testWord); + if (!hypOutput.equals(sulOutput)) { + return new DefaultQuery<>(testWord, sulOutput); + } + + } + return null; + } + + +} diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java new file mode 100644 index 000000000..20a01f057 --- /dev/null +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java @@ -0,0 +1,271 @@ +package de.learnlib.oracle.membership; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.query.DefaultQuery; +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Implements a timed query oracle for MMLT learning. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class TimedQueryOracle extends de.learnlib.oracle.TimedQueryOracle { + + private final static Logger logger = LoggerFactory.getLogger(TimedQueryOracle.class); + + // List of all possible timer names. + // Each name is assigned at most once while this oracle exists. This ensures globally-unique timer names. + private final List timerNames; + private int timerNameIndex = 0; + + private final LocalTimerMealySUL sul; + private final LocalTimerMealyModelParams modelParams; + + public TimedQueryOracle(LocalTimerMealySUL sul, LocalTimerMealyModelParams modelParams) { + this.sul = sul; + this.modelParams = modelParams; + + this.timerNames = generateTimerNames(); + } + + private List generateTimerNames() { + List names = new ArrayList<>(); + // Add single letter names + for (char c = 'a'; c <= 'z'; c++) { + names.add(String.valueOf(c)); + } + // Add double letter names + for (char c1 = 'a'; c1 <= 'z'; c1++) { + for (char c2 = 'a'; c2 <= 'z'; c2++) { + names.add("" + c1 + c2); + } + } + return names; + } + + /** + * Observes and aggregates any timeouts that occur after providing the given input to the SUL. + * Stops when observing inconsistent behavior. + * + * @param prefix Input to give to the SUL. + * @param maxTotalWaitingTime Maximum time that is waited for timeouts. + * @return Observed timeouts. Empty, if none. + */ + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + this.sul.pre(); + + // Go to location: + this.sul.follow(prefix); + + // Collect timeouts: + TimerQueryResult timers = this.collectTimeouts(maxTotalWaitingTime); + + this.sul.post(); + return timers; + } + + /** + * Identifies the time at which the next known timeout(s) are expected. + * + * @param timeouts Known timeouts + * @param currentTime Current time. + * @return Next timeout time. + */ + private long calcNextExpectedTimeout(List> timeouts, long currentTime) { + if (timeouts.isEmpty()) { + throw new AssertionError(); + } + + long minNext = Long.MAX_VALUE; + for (var to : timeouts) { + long occurrences = currentTime / to.initial(); + long nextOcc = (occurrences + 1) * to.initial(); // time of next occ + + if (nextOcc < minNext) { + minNext = nextOcc; + } + } + + if (minNext == Long.MAX_VALUE) { + throw new AssertionError(); + } + + return minNext; + } + + private String getUniqueTimerName() { + var newTimerName = timerNames.get(timerNameIndex); + this.timerNameIndex += 1; + return newTimerName; + } + + /** + * Identifies timeouts in the current location by waiting at most [maxDelay]. + *

+ * All inferred timers are initially considered periodic. + * Stops when reaching maxTotalWaitingTime OR when an expected timeout does not occur. + * In the latter case, the "aborted" flag is set. + * + * @param maxTotalWaitingTime Maximum time until timeouts are collected. + * @return List of periodic timeouts. Null, if none observed. + */ + private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { + if (maxTotalWaitingTime < this.modelParams.maxTimeoutWaitingTime()) { + throw new IllegalArgumentException("Timer query waiting time must be at least max. waiting time for a single timeout."); + } + + List> knownTimers = new ArrayList<>(); + + // Wait for the first timeout: + LocalTimerMealyOutputSymbol firstTimeout = this.sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); + if (firstTimeout == null) { + return new TimerQueryResult<>(false, Collections.emptyList()); // no timeouts found + } + + if (this.modelParams.outputCombiner().isCombinedSymbol(firstTimeout.getSymbol())) { + logger.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); + } + + knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.getDelay(), firstTimeout.getSymbol())); + + // Wait for further timeouts: + long currentTimeStep = firstTimeout.getDelay(); // already waited for first timeout + + boolean inconsistent = false; + while (currentTimeStep < maxTotalWaitingTime) { + // Identify time of next expected timeout: + long nextExpectedTime = this.calcNextExpectedTimeout(knownTimers, currentTimeStep); + + // Wait either until next timeout OR until maximum waiting time reached: + long nextWaiting = Math.min(nextExpectedTime, maxTotalWaitingTime) - currentTimeStep; + + // Wait until next timeout: + LocalTimerMealyOutputSymbol nextOutput = this.sul.timeoutStep(nextWaiting); + if (nextOutput == null) { + if (nextExpectedTime <= maxTotalWaitingTime) { + // Expected a timeout within max. waiting time but nothing happened: + inconsistent = true; + } + + break; // either max time exceeded OR missing timeout (-> inconsistent) + } + + // Compare observed timeout with expectation: + long nextActualTime = nextOutput.getDelay() + currentTimeStep; + + TimerCheckResult evalResult = evaluateNextTimer(nextActualTime, nextExpectedTime, nextOutput, knownTimers); + if (evalResult.newTimer() != null) { + knownTimers.add(evalResult.newTimer()); + } else if (evalResult.inconsistent()) { + inconsistent = true; + break; + } + + currentTimeStep = nextActualTime; + } + + knownTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + return new TimerQueryResult<>(inconsistent, knownTimers); + } + + private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) { + + } + + private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, LocalTimerMealyOutputSymbol nextOutput, List> knownTimers) { + if (nextActualTime < nextExpectedTime) { + // A timeout occurred before we expected one -> new timer: + var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.getSymbol()); + return new TimerCheckResult<>(newTimer, false); + } else if (nextActualTime == nextExpectedTime) { + // Timeout occurred at expected time -> check if matching expected output: + Map expectedOutputs = knownTimers.stream() + .filter(t -> nextExpectedTime % t.initial() == 0) + .map(t -> modelParams.outputCombiner().separateSymbols(t.output())) // separate output of timers with same initial value + .flatMap(Collection::stream) + .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences + + Map actualOutputs = this.modelParams.outputCombiner().separateSymbols(nextOutput.getSymbol()) + .stream() + .collect(Collectors.groupingBy(e -> e, Collectors.counting())); + + // Any missing outputs? + boolean missingOutputs = expectedOutputs.keySet().stream() + .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < expectedOutputs.get(o)); // less than expected + if (missingOutputs) { + // Same time but missing output -> missed location change: + return new TimerCheckResult<>(null, true); + } + + // Any new outputs? + List newOutputs = actualOutputs.keySet().stream() + .filter(o -> expectedOutputs.getOrDefault(o, 0L) < actualOutputs.get(o)) // less than actual + .toList(); + if (!newOutputs.isEmpty()) { + // Same time and more outputs -> add new timer that uses the new outputs: + var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, this.modelParams.outputCombiner().combineSymbols(newOutputs)); + return new TimerCheckResult<>(newTimer, false); + } + } else { + throw new IllegalStateException(); + } + + return new TimerCheckResult<>(null, false); + } + + + @Override + protected void querySuffixOutputInternal(DefaultQuery, Word>> query) { + + sul.pre(); + sul.follow(query.getPrefix(), this.modelParams.maxTimeoutWaitingTime()); + + // Query the SUL, one symbol at a time: + WordBuilder> wbOutput = new WordBuilder<>(); + for (var s : query.getSuffix()) { + if (s instanceof TimeoutSymbol) { + LocalTimerMealyOutputSymbol output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); + if (output != null) { + wbOutput.append(output); + } else { + wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); // no output in time -> silent + } + } else if (s instanceof NonDelayingInput ndi) { + LocalTimerMealyOutputSymbol output = sul.step(ndi); + wbOutput.append(output); + } else if (s instanceof TimeStepSequence ws) { + if (ws.getTimeSteps() > 1) { + throw new IllegalArgumentException("Only single wait step allowed in suffix."); + } + + // Wait for a single time step: + LocalTimerMealyOutputSymbol output = sul.timeStep(); + if (output != null) { + wbOutput.append(output); + } else { + wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); // no output in time -> silent + } + + } else { + throw new IllegalArgumentException("Only timeout or untimed symbols allowed in suffix."); + } + } + + + sul.post(); + query.answer(wbOutput.toWord()); + } + +} From de732023a4b24d98177c6abe19c10d9b5a895f68 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 12:26:50 +0200 Subject: [PATCH 04/55] Extended EquivalenceOracle.java with LocalTimerMealyEquivalenceOracle. --- .../de/learnlib/oracle/EquivalenceOracle.java | 11 ++ .../mmlt/LocalTimerMealyRandomWpOracle.java | 169 ++++++++++++++++++ .../mmlt/LocalTimerMealySimulatorOracle.java | 4 +- .../equivalence/mmlt/ResetSearchOracle.java | 4 +- 4 files changed, 184 insertions(+), 4 deletions(-) create mode 100644 oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index ee8b75e88..960fda01a 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -18,7 +18,10 @@ import java.util.Collection; import de.learnlib.query.DefaultQuery; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.word.Word; @@ -92,4 +95,12 @@ interface MealyEquivalenceOracle extends EquivalenceOracle extends EquivalenceOracle, I, Word> {} + /** + * A specialization of the {@link EquivalenceOracle} interface for a Local Timer Mealy learning scenario. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ + interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>>{} } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java new file mode 100644 index 000000000..c28de0323 --- /dev/null +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.oracle.equivalence.mmlt; + +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.common.util.string.AbstractPrintable; +import net.automatalib.util.automaton.Automata; +import net.automatalib.util.automaton.cover.LocalTimerMealyCover; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * RandomWP counterexample search for MMLT learning. + * Key modification: samples prefix from entry prefixes instead of all state prefixes. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { + private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyRandomWpOracle.class); + private final TimedQueryOracle timeOracle; + + private StatsContainerX stats = new DummyStatsContainer(); + + private final Random random; + private final int minSize; + private final int rndLen; + private final int bound; + + public LocalTimerMealyRandomWpOracle(TimedQueryOracle timeOracle, + long randomSeed, + int minSize, int rndAddLength, int bound) { + + this.timeOracle = timeOracle; + + this.random = new Random(randomSeed); + + this.minSize = minSize; + this.rndLen = rndAddLength; + this.bound = bound; + } + + @Override + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + // Make expanded form of hypothesis: + var hypSemModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(hypothesis); + + // Create a list of symbols (for faster access): + List> listAlphabet = new ArrayList<>(hypothesis.getUntimedAlphabet()); + listAlphabet.add(new TimeoutSymbol<>()); + listAlphabet.add(new TimeStepSymbol<>()); + + // Identify global suffixes: + var globalSuffixes = Automata.characterizingSet(hypSemModel, hypSemModel.getInputAlphabet()); + + // Get list of prefixes in deterministic order (so we can reproduce experiment easily): + var locationCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis); + var prefixList = locationCover + .values() + .stream() + .sorted(Comparator.comparing(AbstractPrintable::toString)) + .toList(); + + // Generate test words: + for (int i = 0; i < this.bound; i++) { + stats.increaseCounter("WP_TESTED_WORD", "RandomWpOracle: tested words"); + + var sulAnswer = this.generateTestword(prefixList, globalSuffixes, hypothesis, hypSemModel, listAlphabet); + Word> hypAnswer = hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); + + // Found inconsistency if outputs do no match: + if (!sulAnswer.getOutput().equals(hypAnswer)) { + return sulAnswer; // expected SUL output + } + } + + return null; // no counterexample found + } + + private DefaultQuery, Word>> generateTestword(List>> prefixes, + List>> globalSuffixes, + LocalTimerMealy hypothesis, + ReducedLocalTimerMealySemantics hypSemModel, + List> alphabet) { + + WordBuilder> wbTestWord = new WordBuilder<>(); + + // 1. Pick a random entry config prefix: + Word> prefix = prefixes.get(this.random.nextInt(prefixes.size())); + wbTestWord.append(prefix); + + // 2. Add random middle part: + int size = minSize; + while ((size > 0) || (this.random.nextDouble() > 1 / (this.rndLen + 1.0))) { + var nextSymbol = alphabet.get(this.random.nextInt(alphabet.size())); + wbTestWord.append(nextSymbol); + + if (size > 0) { + size--; + } + } + + // 3. Pick a random suffix for this state: + // 50% chance for state testing, 50% chance for transition testing + Word> suffix = Word.epsilon(); + if (this.random.nextBoolean()) { + if (!globalSuffixes.isEmpty()) { + suffix = globalSuffixes.get(random.nextInt(globalSuffixes.size())); + } + } else { + // Identify configuration reached by prefix: + var currentConfig = hypothesis.getSemantics().traceInputs(wbTestWord.toWord()); + var state = hypSemModel.getStateForConfiguration(currentConfig, true); + var localSuffixes = Automata.stateCharacterizingSet(hypSemModel, hypSemModel.getInputAlphabet(), state); + + if (!localSuffixes.isEmpty()) { + suffix = localSuffixes.get(random.nextInt(localSuffixes.size())); + } + } + wbTestWord.append(suffix); + + // Query SUL: + var testWord = wbTestWord.toWord(); + var sulAnswer = timeOracle.answerQuery(testWord); + return new DefaultQuery<>(testWord, sulAnswer); + } + + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } +} diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java index b942a2a37..1d82bfd4b 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java @@ -40,7 +40,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealySimulatorOracle implements EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>> { +public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final LocalTimerMealy refModel; @@ -50,7 +50,7 @@ public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis); if (separatingWord != null) { diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 6557f76b8..9cd044406 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -50,7 +50,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ResetSearchOracle implements EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>> { +public class ResetSearchOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); @@ -90,7 +90,7 @@ private List> getLoopingSymbols(S sourceLo } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { if (loopInsertPerc == 0) { return null; // oracle is disabled } From 17ee81618394cb1e54503149849084e01de5c7df Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 13:13:22 +0200 Subject: [PATCH 05/55] Created module for symbol filters; added tests for counterexample handling for MMLT learner. --- algorithms/active/lstar/pom.xml | 10 + .../lstar/mmlt/LStarLocalTimerMealy.java | 8 +- .../mmlt/LocalTimerMealyObservationTable.java | 20 +- ...lTimerMealyCounterexampleDecompositor.java | 6 +- .../LocalTimerMealyCounterexampleHandler.java | 6 +- ...alTimerMealyInconsPrefixTransformAcex.java | 6 +- ...tarLocalTimerMealyCounterexampleTests.java | 245 ++++++++++++++++++ .../lstar/mmlt/LocalTimerMealyTestUtil.java | 51 ++++ .../test/resources/mmlt/ambiguous_minimal.dot | 21 ++ .../src/test/resources/mmlt/greedy_fail.dot | 14 + .../test/resources/mmlt/initial_value_one.dot | 15 ++ .../resources/mmlt/isolated_loc_permanent.dot | 19 ++ .../test/resources/mmlt/isolated_loc_temp.dot | 27 ++ .../mmlt/missing_oneshot_high_waiting.dot | 17 ++ .../test/resources/mmlt/over_approx_reset.dot | 17 ++ .../test/resources/mmlt/recursive_decomp.dot | 37 +++ .../resources/mmlt/same_initial_value.dot | 13 + .../test/resources/mmlt/sensor_collector.dot | 26 ++ .../src/test/resources/mmlt/syntax_demo.dot | 20 ++ .../test/resources/mmlt/unique_minimal.dot | 18 ++ ...cle.java => AbstractTimedQueryOracle.java} | 2 +- .../mmlt/LocalTimerMealyRandomWpOracle.java | 8 +- .../equivalence/mmlt/ResetSearchOracle.java | 6 +- .../oracle/membership/TimedQueryOracle.java | 3 +- oracles/pom.xml | 1 + oracles/symbol-filters/pom.xml | 88 +++++++ .../mmlt/AcceptAllSymbolFilter.java | 25 ++ .../mmlt/CachedSymbolFilter.java | 47 ++++ .../mmlt/IgnoreAllSymbolFilter.java | 25 ++ .../mmlt/PerfectSymbolFilter.java | 65 +++++ .../mmlt/RandomSymbolFilter.java | 65 +++++ .../mmlt/StatisticsSymbolFilter.java | 64 +++++ pom.xml | 1 + 33 files changed, 964 insertions(+), 32 deletions(-) create mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java create mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/ambiguous_minimal.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/greedy_fail.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/initial_value_one.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_permanent.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_temp.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/missing_oneshot_high_waiting.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/over_approx_reset.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/recursive_decomp.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/same_initial_value.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/unique_minimal.dot rename api/src/main/java/de/learnlib/oracle/{TimedQueryOracle.java => AbstractTimedQueryOracle.java} (94%) create mode 100644 oracles/symbol-filters/pom.xml create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java diff --git a/algorithms/active/lstar/pom.xml b/algorithms/active/lstar/pom.xml index c785db71e..0a9b0e37c 100644 --- a/algorithms/active/lstar/pom.xml +++ b/algorithms/active/lstar/pom.xml @@ -124,6 +124,16 @@ limitations under the License. org.testng testng + + de.learnlib + learnlib-symbol-filters + test + + + net.automatalib + automata-serialization-dot + test + diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index acd23c388..8fcbbc8de 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -17,7 +17,7 @@ import de.learnlib.datastructure.observationtable.OTLearner; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -54,7 +54,7 @@ public class LStarLocalTimerMealy implements OTLearner, ? super Word>> closingStrategy; - private final TimedQueryOracle timeOracle; + private final AbstractTimedQueryOracle timeOracle; private final SymbolFilter symbolFilter; private final LStarLocalTimerMealyHypDataContainer hypData; @@ -81,7 +81,7 @@ public LStarLocalTimerMealy(Alphabet> alph LocalTimerMealyModelParams modelParams, @NonNull List>> initialSuffixes, - TimedQueryOracle timeOracle, + AbstractTimedQueryOracle timeOracle, SymbolFilter symbolFilter) { this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); } @@ -102,7 +102,7 @@ public LStarLocalTimerMealy(Alphabet> alph @NonNull List>> initialSuffixes, ClosingStrategy, ? super Word>> closingStrategy, - TimedQueryOracle timeOracle, + AbstractTimedQueryOracle timeOracle, @NonNull SymbolFilter symbolFilter, AcexAnalyzer analyzer) { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java index ccc4bddb7..8a39c3981 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java @@ -3,7 +3,7 @@ import de.learnlib.datastructure.observationtable.MutableObservationTable; import de.learnlib.datastructure.observationtable.Row; import de.learnlib.datastructure.observationtable.RowImpl; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.oracle.MembershipOracle; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; @@ -81,7 +81,7 @@ public LocalTimerMealyObservationTable(Alphabet location, TimedQueryOracle timeOracle) { + private void identifyLocalTimers(LocationTimerInfo location, AbstractTimedQueryOracle timeOracle) { var timerQueryResponse = timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); if (timerQueryResponse.aborted()) { @@ -137,7 +137,7 @@ private RowImpl> addInitialLocation() { * @param newRow Newly-added short prefix row * @param timeOracle Time oracle */ - private void initLocation(RowImpl> newRow, TimedQueryOracle timeOracle) { + private void initLocation(RowImpl> newRow, AbstractTimedQueryOracle timeOracle) { LocationTimerInfo timerInfo = new LocationTimerInfo<>(newRow.getLabel()); this.identifyLocalTimers(timerInfo, timeOracle); @@ -161,7 +161,7 @@ private void initLocation(RowImpl> newRow, * @param timeOracle Time query oracle * @return New transitions */ - private List>> createOutgoingTransitions(RowImpl> spRow, TimedQueryOracle timeOracle) { + private List>> createOutgoingTransitions(RowImpl> spRow, AbstractTimedQueryOracle timeOracle) { List>> transitions = new ArrayList<>(); Word> sp = spRow.getLabel(); @@ -294,7 +294,7 @@ public List>>> initialize(List timedOracle)) { + if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { throw new IllegalArgumentException("Must use timed oracle!"); } @@ -314,7 +314,7 @@ public List>>> initialize(List> row, TimedQueryOracle timedOracle) { + private void queryAllSuffixes(RowImpl> row, AbstractTimedQueryOracle timedOracle) { Word> prefix = row.getLabel(); List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); @@ -350,7 +350,7 @@ public boolean isInitialConsistencyCheckRequired() { @Override public List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof TimedQueryOracle timedOracle)) { + if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } @@ -394,7 +394,7 @@ public List>>> addShortPrefixes(L @Override public List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof TimedQueryOracle timedOracle)) { + if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } @@ -516,7 +516,7 @@ public LocationTimerInfo getLocationTimerInfo(Row>>> addOutgoingTransition(Row> spRow, LocalTimerMealySemanticInputSymbol symbol, TimedQueryOracle timeOracle) { + public List>>> addOutgoingTransition(Row> spRow, LocalTimerMealySemanticInputSymbol symbol, AbstractTimedQueryOracle timeOracle) { if (!this.alphabet.containsSymbol(symbol)) { throw new IllegalArgumentException("Unknown symbol."); } @@ -540,7 +540,7 @@ public List>>> addOutgoingTransit return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, TimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java index e82c123e9..275bacfff 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java @@ -2,7 +2,7 @@ import de.learnlib.acex.AcexAnalyzer; import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.TimeStepSequence; import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; @@ -24,10 +24,10 @@ class LocalTimerMealyCounterexampleDecompositor { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleDecompositor.class); - private final TimedQueryOracle timeOracle; + private final AbstractTimedQueryOracle timeOracle; private final AcexAnalyzer acexAnalyzer; - public LocalTimerMealyCounterexampleDecompositor(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { + public LocalTimerMealyCounterexampleDecompositor(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { this.timeOracle = timeOracle; this.acexAnalyzer = acexAnalyzer; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java index 9ee73e305..487b3509c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -4,7 +4,7 @@ import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; import de.learnlib.algorithm.lstar.mmlt.cex.results.*; import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainerX; @@ -34,10 +34,10 @@ public class LocalTimerMealyCounterexampleHandler implements LearnerSta private final SymbolFilter symbolFilter; private StatsContainerX stats = new DummyStatsContainer(); - protected final TimedQueryOracle timeOracle; + protected final AbstractTimedQueryOracle timeOracle; private final LocalTimerMealyCounterexampleDecompositor decompositor; - public LocalTimerMealyCounterexampleHandler(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, SymbolFilter symbolFilter) { + public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, SymbolFilter symbolFilter) { this.timeOracle = timeOracle; this.decompositor = new LocalTimerMealyCounterexampleDecompositor<>(timeOracle, acexAnalyzer); this.symbolFilter = symbolFilter; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java index cc1942d08..2430aa394 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java @@ -1,7 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt.cex; import de.learnlib.acex.AbstractBaseCounterexample; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; import net.automatalib.word.Word; @@ -20,7 +20,7 @@ public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBase private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyInconsPrefixTransformAcex.class); - private final TimedQueryOracle timeOracle; + private final AbstractTimedQueryOracle timeOracle; private final Word> suffix; private final Function>, Word>> asTransform; @@ -32,7 +32,7 @@ public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBase * @param timeOracle membership oracle * @param asTransform retrieves the prefix of the system state in the hypothesis addressed by a word */ - public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, TimedQueryOracle timeOracle, Function>, Word>> asTransform) { + public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, AbstractTimedQueryOracle timeOracle, Function>, Word>> asTransform) { super(suffix.length()); this.timeOracle = timeOracle; this.suffix = suffix; diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java new file mode 100644 index 000000000..5bb29d384 --- /dev/null +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -0,0 +1,245 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; +import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.oracle.symbol_filters.mmlt.AcceptAllSymbolFilter; +import de.learnlib.query.DefaultQuery; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.alphabet.GrowingAlphabet; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.testng.annotations.Test; + + +import java.util.Collections; +import java.util.List; + +/** + * Tests several different cases of counterexamples. + */ +public class LStarLocalTimerMealyCounterexampleTests { + + private static void learnModel(LocalTimerMealyTestUtil.Model model, List>> counterexamples) { + + GrowingAlphabet> alphabet = new GrowingMapAlphabet<>(); + model.automaton().getUntimedAlphabet().forEach(alphabet::addSymbol); + + LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(model.automaton()); + TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); + + var learner = new LStarLocalTimerMealy<>(alphabet, model.params(), Collections.emptyList(), + timeOracle, new AcceptAllSymbolFilter<>()); + + learner.startLearning(); + + System.out.println("Initial hypothesis:"); + LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); + + // Trigger the expected cases with the provided counterexamples: + for (var cex : counterexamples) { + System.out.println(); + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + System.out.println(); + + var output = timeOracle.querySuffixOutput(Word.epsilon(), cex); + learner.refineHypothesis(new DefaultQuery<>(cex, output)); + + System.out.println("Current hypothesis:"); + LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); + } + + // Now continue until arriving at an accurate model: + System.out.println("Running to completion"); + LocalTimerMealySimulatorOracle simOracle = new LocalTimerMealySimulatorOracle<>(model.automaton()); + int round = 0; + while (round < 100) { + var hyp = learner.getHypothesisModel(); + var cex = simOracle.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + if (cex != null) { + learner.refineHypothesis(cex); + } else { + break; + } + round++; + } + + System.out.println("Took " + (round + 1) + " additional rounds."); + System.out.println("Final hypothesis:"); + LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); + System.out.println(); + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + System.out.println(); + + } + + + private Word> getTimeStepSequence(int timeSteps) { + WordBuilder> wbTimeStep = new WordBuilder<>(); + wbTimeStep.repeatAppend(timeSteps, new TimeStepSymbol<>()); + return wbTimeStep.toWord(); + } + + private Word> getTimeoutSequence(int timeouts) { + WordBuilder> wbTimeouts = new WordBuilder<>(); + wbTimeouts.repeatAppend(timeouts, new TimeoutSymbol<>()); + return wbTimeouts.toWord(); + } + + @Test + public void testOverApproxReset() { + // Infers a missing local reset instead of a missing discriminator first. + var model = LocalTimerMealyTestUtil.automatonFromFile("over_approx_reset.dot"); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + Word.fromSymbols(new TimeStepSymbol<>(), new NonDelayingInput<>("i"), new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + } + + @Test + public void testRecursiveDecomp() { + // Triggers recursive decomposition + var model = LocalTimerMealyTestUtil.automatonFromFile("recursive_decomp.dot", 3); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p"), new NonDelayingInput<>("f")), + + Word.fromSymbols(new NonDelayingInput<>("u"), + new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), + new NonDelayingInput<>("f")), + + Word.fromSymbols(new NonDelayingInput<>("u"), + new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), + new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + } + + @Test + public void testMissingDiscriminators() { + var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot"); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeStepSymbol<>(), new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + ); + + // Missing discriminator at one-shot: + List>> cex2 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + @Test + public void testMissingResets() { + var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 40); + + // Missing reset in stable config: + List>> cex1 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromSymbols(new NonDelayingInput<>("p1"), new TimeStepSymbol<>(), new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + ); + + // Missing reset in non-stable config: + List>> cex2 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromSymbols(new NonDelayingInput<>("p1"), + new TimeStepSymbol<>(), new TimeStepSymbol<>(), new TimeStepSymbol<>(), + new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + + } + + @Test + public void testMissingOneShotModelB() { + // Setting max waiting = 6 -> all inferred timers are periodic: + var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 6); + + // Missing one-shot via bad return to entry: + List>> cex1 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), getTimeoutSequence(14) + ) + ); + + // Missing one-shot in location with single timer: + List>> cex2 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + @Test + public void testMissingOneShotModelA() { + var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 40); + + // Missing one-shot via bad output: + List>> cex1 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), + getTimeStepSequence(40), + Word.fromLetter(new TimeoutSymbol<>())) // alternatively: new NonDelayingInput<>("abort") + ); + + // Missing one-shot via bad target: + List>> cex2 = List.of( + // Initial hyp: + Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), + Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + + Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), + getTimeoutSequence(14), + Word.fromLetter(new NonDelayingInput<>("collect")), + Word.fromLetter(new NonDelayingInput<>("p1")) + ) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + +} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java new file mode 100644 index 000000000..6c398b4c0 --- /dev/null +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -0,0 +1,51 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.serialization.dot.GraphDOT; +import net.automatalib.serialization.dot.LocalTimerMealyGraphvizParser; +import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; + +import java.io.File; +import java.io.IOException; + +/** + * Utility class for loading MMLTs from resources and printing them. + */ +class LocalTimerMealyTestUtil { + + record Model(LocalTimerMealy automaton, LocalTimerMealyModelParams params) { + + } + + static void printModel(LocalTimerMealy model) { + try { + GraphDOT.write(model.transitionGraphView(true, true), System.out); + } catch (IOException ignored) { + } + } + + + static Model automatonFromFile(String name) { + return automatonFromFile(name, -1); + } + + /** + * Loads the automaton model with the provided resource name. + * + * @param name Resource name + * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. + * @return The automaton model. + */ + static Model automatonFromFile(String name, int maxTimerQueryWaiting) { + var modelResource = LocalTimerMealyTestUtil.class.getResource("/mmlt/" + name); + var automaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(modelResource.getFile()), "void", StringSymbolCombiner.getInstance()); + + long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? maxTimerQueryWaiting : LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; + + return new Model<>(automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); + } + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/ambiguous_minimal.dot b/algorithms/active/lstar/src/test/resources/mmlt/ambiguous_minimal.dot new file mode 100644 index 000000000..115f68259 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/ambiguous_minimal.dot @@ -0,0 +1,21 @@ +// In this example, the learner infers an equivalent MMLT +// that uses more timers per location but still has the same number of locations. +digraph g { + + s0 [shape="circle"]; + s1 [shape="circle" timers="a=2"]; + s2 [shape="circle" timers="b=1"]; + s3 [shape="circle"]; + s4 [shape="circle"]; + + s1 -> s2 [label="to[a] / A"]; + s2 -> s3 [label="to[b] / B"]; + + s0 -> s1 [label="y / X"]; + s0 -> s4 [label="x / Y"]; + s4 -> s2 [label="x / Z"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/greedy_fail.dot b/algorithms/active/lstar/src/test/resources/mmlt/greedy_fail.dot new file mode 100644 index 000000000..21d0cf0ea --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/greedy_fail.dot @@ -0,0 +1,14 @@ +// This is an example of a model where the greedy timer inference leads to an unnecessarily large model. +digraph g { + + s0 [timers="a=3" shape="circle"]; + s1 [timers="a=2,b=5" shape="circle"]; + + s0 -> s1 [label="to[a] / a"]; + s1 -> s1 [label="to[a] / b"]; + s1 -> s1 [label="to[b] / c"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/initial_value_one.dot b/algorithms/active/lstar/src/test/resources/mmlt/initial_value_one.dot new file mode 100644 index 000000000..3e3a79932 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/initial_value_one.dot @@ -0,0 +1,15 @@ +// The location s0 of this MMLT has a timer with the initial value one and a local reset. +// We allow local resets only in locations with at least two stable configurations. +// Hence, the smallest accurate hypothesis MMLT for this model must have two locations. +digraph g { + + s0 [timers="x=1,y=2" shape="circle"]; + + s0 -> s0 [label="a / void" resets="x,y"]; + s0 -> s0 [label="to[x] / X"]; + s0 -> s0 [label="to[y] / Y"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_permanent.dot b/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_permanent.dot new file mode 100644 index 000000000..1a6e3e082 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_permanent.dot @@ -0,0 +1,19 @@ +// Example of location that becomes permanently isolated +digraph g { + + s0 [timers="x=3" shape="circle"]; + s1 [timers="y=2, z=3" shape="circle"]; + s2 [shape="circle"]; + + s0 -> s0 [label="a / A"]; + s1 -> s1 [label="a / B"]; + s2 -> s2 [label="a / C"]; + + s0 -> s1 [label="to[x] / X"]; + s1 -> s1 [label="to[y] / Y"]; + s1 -> s2 [label="to[z] / Z"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_temp.dot b/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_temp.dot new file mode 100644 index 000000000..0b6e851b2 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/isolated_loc_temp.dot @@ -0,0 +1,27 @@ +// Example of location that becomes temporarily isolated +// Tested with a whitebox oracle only. +digraph g { + + s0 [timers="x=3" shape="circle"]; + s1 [timers="y=2, z=3" shape="circle"]; + s2 [shape="circle"]; + + s4 [shape="circle"]; + s5 [timers="v=1" shape="circle"]; + + s0 -> s0 [label="a / A"]; + s1 -> s1 [label="a / B"]; + s2 -> s4 [label="a / C"]; + s4 -> s5 [label="a / C"]; + + s0 -> s1 [label="to[x] / X"]; + s1 -> s1 [label="to[y] / Y"]; + s1 -> s2 [label="to[z] / Z"]; + + s5 -> s5 [label="a / B"] + s5 -> s2 [label="to[v] / Z"] + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/missing_oneshot_high_waiting.dot b/algorithms/active/lstar/src/test/resources/mmlt/missing_oneshot_high_waiting.dot new file mode 100644 index 000000000..bde3ff611 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/missing_oneshot_high_waiting.dot @@ -0,0 +1,17 @@ +// This is an example of a model where we infer a periodic timer b that cannot be periodic, even with a high maximum waiting time.l +digraph g { + + s0 [timers="a=3" shape="circle"]; + s1 [timers="b=1" shape="circle"]; + s2 [timers="c=2" shape="circle"]; + + s0 -> s1 [label="to[a] / A"]; + s1 -> s2 [label="to[b] / B"]; + s2 -> s2 [label="to[c] / C"]; + + s1 -> s1 [label="x / X"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/over_approx_reset.dot b/algorithms/active/lstar/src/test/resources/mmlt/over_approx_reset.dot new file mode 100644 index 000000000..3e08b3fbe --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/over_approx_reset.dot @@ -0,0 +1,17 @@ +// Make sure to not initialize the suffixes with the input alphabet in this example +// In this example, the learner will infer a missing local reset, although there is actually a missing discriminator. +digraph g { + + s0 [timers="a=3" shape="circle"]; + s1 [timers="b=3" shape="circle"]; + + s0 -> s0 [label="to[a] / A"]; + s0 -> s1 [label="i / void"]; + + s1 -> s1 [label="to[b] / A"]; + s1 -> s1 [label="x / X"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/recursive_decomp.dot b/algorithms/active/lstar/src/test/resources/mmlt/recursive_decomp.dot new file mode 100644 index 000000000..67120a67d --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/recursive_decomp.dot @@ -0,0 +1,37 @@ +// This is an example of a model where the post-processing discovers an incorrect output while processing an incorrect target. +// You need to set maximum the query time to three. +// Use "TestDissExample" to send counterexamples that trigger the expected behavior. +digraph g { + + s0 [shape="circle"]; + s1 [timers="a=2,b=3" shape="circle"]; + s2 [shape="circle"]; + s3 [timers="c=2" shape="circle"]; + s4 [timers="d=1" shape="circle"]; + s5 [timers="e=1" shape="circle"]; + s6 [timers="f=2" shape="circle"]; + + s0 -> s0 [label="f / F"] + + s0 -> s1 [label="p / P"]; + s1 -> s1 [label="to[a] / A"]; + s1 -> s2 [label="to[b] / B"]; + + s1 -> s1 [label="f / G"] + s2 -> s2 [label="f / H"] + + // ---- + + s0 -> s3 [label="u / U"]; + s3 -> s4 [label="to[c] / A"]; + s4 -> s5 [label="to[d] / B"]; + s5 -> s6 [label="to[e] / A"]; + s6 -> s7 [label="to[f] / A|B"]; + + s3 -> s3 [label="f / G"] + + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/same_initial_value.dot b/algorithms/active/lstar/src/test/resources/mmlt/same_initial_value.dot new file mode 100644 index 000000000..17ec7ede6 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/same_initial_value.dot @@ -0,0 +1,13 @@ +// In this example, the learner will infer a timer with a combined output. +digraph g { + + s0 [timers="a=3,b=3,c=6" shape="circle"]; + + s0 -> s0 [label="to[a] / A"]; + s0 -> s0 [label="to[b] / C"]; + s0 -> s0 [label="to[c] / B"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot b/algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot new file mode 100644 index 000000000..240314743 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot @@ -0,0 +1,26 @@ +// This sensor node is used in the diss as running example. +// p1 starts a normal measurement. part triggers a sensor for particulate matter, noise a sensor for ambient noise. +// p2 performs a self-check. +// Set maximum waiting time = 40 to reproduce bad hypothesis from diss. +digraph g { + s0 [label="L0" timers=""] + s1 [label="L1" timers="a=3,b=6,c=40"] + s2 [label="L2" timers="d=4"] + s3 [label="L3" timers=""] + + s0 -> s1 [label="p1/go"] + + s1 -> s1 [label="abort / ok" resets="a,b,c"] + s1 -> s1 [label="to[a] / part"] + s1 -> s1 [label="to[b] / noise"] + s1 -> s3 [label="to[c] / done"] + + s0 -> s2 [label="p2 / go"] + s2 -> s3 [label="abort / void"] + s2 -> s3 [label="to[d] / done"] + + s3 -> s0 [label="collect / void"] + + __start0 [label="" shape="none" width="0" height="0"]; + __start0 -> s0; +} \ No newline at end of file diff --git a/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot b/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot new file mode 100644 index 000000000..2379c825b --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot @@ -0,0 +1,20 @@ +// This file demonstrates the syntax for defining a custom MMLT +digraph g { + s0 [label="L0" timers="a=2"] + s1 [label="L1" timers="b=4,c=6"] + s2 [label="L2" timers="d=2,e=3"] + + s0 -> s1 [label="to[a] / A"] // one-shot with location change + s1 -> s1 [label="to[b] / B"] // periodic + s1 -> s1 [label="to[c] / C" resets="b,c"] // one-shot with loop + + s2 -> s2 [label="to[d] / D" resets="d"] // periodic with explicit resets + s2 -> s2 [label="to[e] / E"] // periodic + + s1 -> s2 [label="x / void"] + s1 -> s1 [label="y / Y" resets="b,c"] // loop with local reset + s2 -> s2 [label="y / D"] // loop without reset + + __start0 [label="" shape="none" width="0" height="0"]; + __start0 -> s0; +} \ No newline at end of file diff --git a/algorithms/active/lstar/src/test/resources/mmlt/unique_minimal.dot b/algorithms/active/lstar/src/test/resources/mmlt/unique_minimal.dot new file mode 100644 index 000000000..20749808c --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/unique_minimal.dot @@ -0,0 +1,18 @@ +// This example demonstrates that there is no unique minimal form for an MMLT. +// Learning this model with maxQueryTime=5 yields a model where s1 has two timers and s0 has one. +// Setting maxQueryTime to 10 instead yields a model where the initial location has two timers and the following has one. +// The total number of locations, timers, max. timers per location, and average timers per location are identical. +digraph g { + + s0 [shape="circle" timers="a=5"]; + s1 [shape="circle" timers="b=3"]; + s2 [shape="circle" timers="c=2"]; + + s0 -> s1 [label="to[a] / A"]; + s1 -> s2 [label="to[b] / B"]; + s2 -> s3 [label="to[c] / C"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java similarity index 94% rename from api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java rename to api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java index 3d3a92947..f36a3fbcc 100644 --- a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java @@ -19,7 +19,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public abstract class TimedQueryOracle implements MembershipOracle.MealyMembershipOracle, LocalTimerMealyOutputSymbol> { +public abstract class AbstractTimedQueryOracle implements MembershipOracle.MealyMembershipOracle, LocalTimerMealyOutputSymbol> { /** * Response for a timer query. diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java index c28de0323..364c8bde7 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -22,7 +22,7 @@ package de.learnlib.oracle.equivalence.mmlt; import de.learnlib.oracle.EquivalenceOracle; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -54,7 +54,7 @@ */ public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyRandomWpOracle.class); - private final TimedQueryOracle timeOracle; + private final AbstractTimedQueryOracle timeOracle; private StatsContainerX stats = new DummyStatsContainer(); @@ -63,7 +63,7 @@ public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle private final int rndLen; private final int bound; - public LocalTimerMealyRandomWpOracle(TimedQueryOracle timeOracle, + public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, long randomSeed, int minSize, int rndAddLength, int bound) { @@ -89,7 +89,7 @@ public LocalTimerMealyRandomWpOracle(TimedQueryOracle timeOracle, // Identify global suffixes: var globalSuffixes = Automata.characterizingSet(hypSemModel, hypSemModel.getInputAlphabet()); - // Get list of prefixes in deterministic order (so we can reproduce experiment easily): + // Get list of prefixes in deterministic order (so we can reproduce experiments easily): var locationCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis); var prefixList = locationCover .values() diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 9cd044406..b3fdf9e94 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -21,7 +21,7 @@ package de.learnlib.oracle.equivalence.mmlt; -import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; import net.automatalib.alphabet.time.mmlt.*; @@ -54,7 +54,7 @@ public class ResetSearchOracle implements EquivalenceOracle.LocalTimerM private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); - private final TimedQueryOracle timeOracle; + private final AbstractTimedQueryOracle timeOracle; private final Random locPrefixRandom; private final double loopInsertPerc; @@ -62,7 +62,7 @@ public class ResetSearchOracle implements EquivalenceOracle.LocalTimerM private final long loopingInputSelectionSeed; - public ResetSearchOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { + public ResetSearchOracle(AbstractTimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { this.timeOracle = timeOracle; this.locPrefixRandom = new Random(seed); this.loopInsertPerc = loopInsertPerc; diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java index 20a01f057..712bdd231 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java @@ -1,6 +1,7 @@ package de.learnlib.oracle.membership; import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.sul.LocalTimerMealySUL; import net.automatalib.alphabet.time.mmlt.*; @@ -20,7 +21,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class TimedQueryOracle extends de.learnlib.oracle.TimedQueryOracle { +public class TimedQueryOracle extends AbstractTimedQueryOracle { private final static Logger logger = LoggerFactory.getLogger(TimedQueryOracle.class); diff --git a/oracles/pom.xml b/oracles/pom.xml index acc5a8e8f..9fe0cb944 100644 --- a/oracles/pom.xml +++ b/oracles/pom.xml @@ -37,5 +37,6 @@ limitations under the License. membership-oracles parallelism property-oracles + symbol-filters diff --git a/oracles/symbol-filters/pom.xml b/oracles/symbol-filters/pom.xml new file mode 100644 index 000000000..ec0777f2f --- /dev/null +++ b/oracles/symbol-filters/pom.xml @@ -0,0 +1,88 @@ + + + 4.0.0 + + + de.learnlib + learnlib-oracles-parent + 0.19.0-SNAPSHOT + ../pom.xml + + + learnlib-symbol-filters + + LearnLib :: Oracles :: Symbol Filters + A collection of symbol filters + + + + + de.learnlib + learnlib-api + + + + + net.automatalib + automata-api + + + + org.checkerframework + checker-qual + + + + org.slf4j + slf4j-api + + + + + de.learnlib.tooling + annotations + + + + + de.learnlib + learnlib-emptiness-oracles + test + + + de.learnlib + learnlib-equivalence-oracles + test + + + de.learnlib.testsupport + learnlib-learning-examples + + + de.learnlib + learnlib-membership-oracles + test + + + + net.automatalib + automata-core + test + + + net.automatalib + automata-modelchecking-ltsmin + test + + + net.automatalib + automata-util + test + + + + org.testng + testng + + + diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java new file mode 100644 index 000000000..1353f7022 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java @@ -0,0 +1,25 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.word.Word; + +/** + * A pass-through filter that accepts all inputs. + * + * @param Input type for non-delaying inputs + */ +public class AcceptAllSymbolFilter implements SymbolFilter { + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + return SymbolFilterResponse.ACCEPT; + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java new file mode 100644 index 000000000..394ba1cdf --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java @@ -0,0 +1,47 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.word.Word; + +import java.util.HashMap; +import java.util.Map; + +/** + * Wrapper for a symbol filter that caches previous responses + allows caller to update these. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class CachedSymbolFilter implements SymbolFilter { + private final Map>, Boolean> previousResponses; // transition -> legal/ignore + private final SymbolFilter delegate; + + public CachedSymbolFilter(SymbolFilter delegate) { + this.delegate = delegate; + this.previousResponses = new HashMap<>(); + } + + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + var oldResponse = this.previousResponses.get(prefix.append(symbol)); + if (oldResponse != null) { + return (oldResponse) ? SymbolFilterResponse.ACCEPT : SymbolFilterResponse.IGNORE; + } + + var res = delegate.query(prefix, symbol); + this.previousResponses.put(prefix.append(symbol), res == SymbolFilterResponse.ACCEPT); + return res; + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + if (!this.previousResponses.containsKey(prefix.append(symbol))) { + throw new IllegalArgumentException("Can only update response if already queried."); + } + this.previousResponses.put(prefix.append(symbol), (response == SymbolFilterResponse.ACCEPT)); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java new file mode 100644 index 000000000..2d3530be3 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java @@ -0,0 +1,25 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.word.Word; + +/** + * A symbol filter that ignores all symbols. + * + * @param Input type for non-delaying inputs + */ +public class IgnoreAllSymbolFilter implements SymbolFilter { + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + return SymbolFilterResponse.IGNORE; + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } + +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java new file mode 100644 index 000000000..413731b54 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2023-2024 Paul Kogel, TU Berlin + * + * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, + * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF + * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR + * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL + * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. + * + * Apart from that, this software is licensed under the + * GNU Affero Public License version 3 (AGPLv3). + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * 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.oracle.symbol_filters.mmlt; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +/** + * A symbol filter that correctly accepts and ignores all transitions. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class PerfectSymbolFilter implements SymbolFilter { + + private final LocalTimerMealy sulModel; + + public PerfectSymbolFilter(LocalTimerMealy sulModel) { + this.sulModel = sulModel; + } + + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + // Check if silent self-loop: + + var targetConfig = this.sulModel.getSemantics().traceInputs(prefix); + var trans = this.sulModel.getSemantics().getTransition(targetConfig, symbol); + + if (trans.output().equals(sulModel.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target())) { + return SymbolFilterResponse.IGNORE; + } else { + return SymbolFilterResponse.ACCEPT; + } + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java new file mode 100644 index 000000000..f5216d52f --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java @@ -0,0 +1,65 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +import java.util.Random; + +/** + * A symbol filter that falsely answers a query with a specified probability. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class RandomSymbolFilter implements SymbolFilter { + + private final double inaccurateProb; + private final Random random; + private final LocalTimerMealy sulModel; + + public RandomSymbolFilter(LocalTimerMealy sulModel, + double inaccurateProb, Random random) { + if (inaccurateProb > 1 || inaccurateProb < 0) { + throw new IllegalArgumentException("Ratios must be between zero and 1 (inclusive)."); + } + + this.inaccurateProb = inaccurateProb; + this.random = random; + + this.sulModel = sulModel; + } + + private boolean isSilentSelfLoop(Word> prefix, NonDelayingInput symbol) { + var targetConfig = this.sulModel.getSemantics().traceInputs(prefix); + var trans = this.sulModel.getSemantics().getTransition(targetConfig, symbol); + return trans.output().equals(sulModel.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target()); + } + + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + // Check if silent self-loop: + boolean ignorable = isSilentSelfLoop(prefix, symbol); + + // Randomly misclassify: + if (this.random.nextDouble() <= this.inaccurateProb) { + ignorable = !ignorable; + } + + if (ignorable) { + return SymbolFilterResponse.IGNORE; + } else { + return SymbolFilterResponse.ACCEPT; + } + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java new file mode 100644 index 000000000..115eeaadd --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java @@ -0,0 +1,64 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +/** + * Collects various statistics on symbol filtering, including false accepts + false ignores. + * + * @param Input type for non-delaying inputs + */ +public class StatisticsSymbolFilter implements SymbolFilter, LearnerStatsProvider { + + private final SymbolFilter delegate; + private final PerfectSymbolFilter perfectFilter; + private StatsContainerX stats = new DummyStatsContainer(); + + + public StatisticsSymbolFilter(SymbolFilter delegate, LocalTimerMealy sulModel) { + this.delegate = delegate; + this.perfectFilter = new PerfectSymbolFilter<>(sulModel); + } + + @Override + public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + stats.increaseCounter("cnt_isf_queries", "Filter: queries"); + + SymbolFilterResponse filterResponse = this.delegate.query(prefix, symbol); + SymbolFilterResponse expectedResponse = this.perfectFilter.query(prefix, symbol); + + // Count false ignores, rejects + correct predictions: + if (filterResponse.equals(SymbolFilterResponse.ACCEPT)) { + if (filterResponse.equals(expectedResponse)) { + stats.increaseCounter("cnt_isf_correct_accepts", "Filter: correct accepts"); + } else { + stats.increaseCounter("cnt_isf_false_accepts", "Filter: false accepts"); + } + } else { + if (filterResponse.equals(expectedResponse)) { + stats.increaseCounter("cnt_isf_correct_ignores", "Filter: correct ignores"); + } else { + stats.increaseCounter("cnt_isf_false_ignores", "Filter: false ignores"); + } + } + + return filterResponse; + } + + @Override + public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + delegate.update(prefix, symbol, response); + } + + @Override + public void setStatsContainer(StatsContainerX container) { + this.stats = container; + } +} diff --git a/pom.xml b/pom.xml index b249b5ead..43980aa47 100644 --- a/pom.xml +++ b/pom.xml @@ -170,6 +170,7 @@ limitations under the License. filters oracles test-support + oracles/symbol-filters - @{argLine} --add-reads=de.learnlib.algorithm.lstar=net.automatalib.util + + @{argLine} --add-reads=de.learnlib.algorithm.lstar=net.automatalib.util --add-reads=de.learnlib.algorithm.lstar=de.learnlib.filter.statistic + diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index 8fcbbc8de..f3331e456 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -21,7 +21,7 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainerX; +import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.alphabet.Alphabet; @@ -50,7 +50,7 @@ public class LStarLocalTimerMealy implements OTLearner, LocalTimerMealySemanticInputSymbol, Word>>, LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LStarLocalTimerMealy.class); - private StatsContainerX stats = new DummyStatsContainer(); + private StatsContainer stats = new DummyStatsContainer(); private final ClosingStrategy, ? super Word>> closingStrategy; @@ -422,7 +422,7 @@ protected void completeConsistentTable(List implements LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleHandler.class); private final SymbolFilter symbolFilter; - private StatsContainerX stats = new DummyStatsContainer(); + private StatsContainer stats = new DummyStatsContainer(); protected final AbstractTimedQueryOracle timeOracle; private final LocalTimerMealyCounterexampleDecompositor decompositor; @@ -44,7 +44,7 @@ public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeO } @Override - public void setStatsContainer(StatsContainerX container) { + public void setStatsContainer(StatsContainer container) { this.stats = container; } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java new file mode 100644 index 000000000..e0bdd42a3 --- /dev/null +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -0,0 +1,167 @@ +package de.learnlib.algorithm.lstar.mmlt; + + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyEQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyRandomWpOracle; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; +import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.oracle.symbol_filters.mmlt.*; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.sul.LocalTimerMealySUL; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.util.statistic.container.MapStatsContainer; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; +import org.testng.annotations.Test; + +import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeCacheSUL; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * Integration tests for the MMLT learner that uses several EQ oracles, symbol filters + * and a cache to learn different models. + */ +@Test +public class LStarLocalTimerMealyBenchmarkTests { + + private enum FilterMode { + none, random, ignore_all, perfect + } + + private static void runExperiment(LStarLocalTimerMealy learner, EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, StatsContainer stats, int maxRounds, + boolean printFinalResult) { + stats.startOrResumeClock("learningRt", "Processing time"); + learner.startLearning(); + + var hyp = learner.getHypothesisModel(); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", "CEX queries"); + + int roundCount = 1; + while (cex != null && roundCount < maxRounds) { + learner.refineHypothesis(cex); + hyp = learner.getHypothesisModel(); + cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", null); + roundCount += 1; + } + stats.pauseClock("learningRt"); + + final var finalHypothesis = learner.getHypothesisModel(); + + // Add some more stats: + stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); + + // Print stats: + stats.printStats(); + + if (printFinalResult) { + System.out.println("Final hypothesis:"); + LocalTimerMealyTestUtil.printModel(finalHypothesis); + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + } + } + + private static void learnModel(String name, LocalTimerMealy automaton, LocalTimerMealyModelParams params, + FilterMode symbolFilterMode, long seed, boolean printResults) { + + // Add some stats: + var stats = new MapStatsContainer(); + stats.addTextInfo("Model", null, name); + stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getUntimedAlphabet().size()); + + // Set up a pipeline: + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addAll(automaton.getUntimedAlphabet()); + + // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL + LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton); + LocalTimerMealyStatsSUL statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); + LocalTimerMealyTreeCacheSUL cacheSUL = new LocalTimerMealyTreeCacheSUL<>(statsAfterCache, params.silentOutput()); + cacheSUL.setStatsContainer(stats); + LocalTimerMealySUL toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime(), stats); + + TimedQueryOracle timeOracle = new TimedQueryOracle<>(toReducerSul, params); + + // Prepare cex oracle chain: + + LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); + //chainOracle.addOracle(new LocalTimerMealyCacheOracle<>(cacheSUL, params)); TODO! + chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); + chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 6, 12, 100)); + chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(automaton)); // ensure that we eventually find an accurate model + chainOracle.setStatsContainer(stats); + + // Create learner: + List>> suffixes = new ArrayList<>(); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + // Configure symbol filter: + SymbolFilter filter = new AcceptAllSymbolFilter<>(); // pass-through + switch (symbolFilterMode) { + case perfect -> filter = new PerfectSymbolFilter<>(automaton); + case random -> filter = new RandomSymbolFilter<>(automaton, 0.1, new Random(seed)); + case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); + } + + filter = new StatisticsSymbolFilter<>(filter, automaton); + filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + + var learner = new LStarLocalTimerMealy<>(alphabet, params, suffixes, timeOracle, filter); + learner.setStatsContainer(stats); + + // Start learning: + runExperiment(learner, chainOracle, stats, 100, printResults); + } + + + @Test + public void learnExamplesNoFilter() { + for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { + var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.none, 100, true); + } + } + + @Test + public void learnExamplesIgnoreAllFilter() { + for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { + var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100, true); + } + } + + @Test + public void learnExamplesPerfectFilter() { + for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { + var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100, true); + } + } + + @Test + public void learnExamplesRandomFilter() { + for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { + var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100, true); + } + } + +} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java index 6c398b4c0..8ef8edd6a 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -9,6 +9,13 @@ import java.io.File; import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; /** * Utility class for loading MMLTs from resources and printing them. @@ -26,6 +33,22 @@ static void printModel(LocalTimerMealy model) { } } + static List listModelFiles() { + var models = new ArrayList(); + try { + var modelFiles = LocalTimerMealyTestUtil.class.getResource("/mmlt"); + if (modelFiles != null) { + try (Stream paths = Files.list(Paths.get(modelFiles.toURI()))) { + paths.filter(p -> p.toString().endsWith(".dot")) + .map(p -> p.getFileName().toString()) + .forEach(models::add); + } + } + } catch (IOException | URISyntaxException e) { + throw new RuntimeException("Failed to list model files", e); + } + return models; + } static Model automatonFromFile(String name) { return automatonFromFile(name, -1); diff --git a/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java b/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java index ca39e9d93..2066a7503 100644 --- a/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java +++ b/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java @@ -58,4 +58,9 @@ public void setCounter(String id, @Nullable String description, long count) { public Optional getCount(String id) { return Optional.empty(); } + + @Override + public void printStats() { + System.out.println("Dummy container"); + } } diff --git a/commons/util/src/main/java/module-info.java b/commons/util/src/main/java/module-info.java index e4b6a888c..0c15ae9d7 100644 --- a/commons/util/src/main/java/module-info.java +++ b/commons/util/src/main/java/module-info.java @@ -43,4 +43,5 @@ exports de.learnlib.util.moore; exports de.learnlib.util.nfa; exports de.learnlib.util.statistic; + exports de.learnlib.util.statistic.container; } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java similarity index 97% rename from filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java rename to filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java index 5036f6f0a..26165e9e2 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/FastLocalTimerMealyTreeCacheSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java @@ -26,7 +26,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class FastLocalTimerMealyTreeCacheSUL extends LocalTimerMealySUL implements GraphViewable, LearnerStatsProvider { +public class LocalTimerMealyTreeCacheSUL extends LocalTimerMealySUL implements GraphViewable, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private final CacheTreeNode cacheRoot; @@ -42,7 +42,7 @@ public void setStatsContainer(StatsContainer container) { this.stats = container; } - public FastLocalTimerMealyTreeCacheSUL(LocalTimerMealySUL delegate, O silentOutput) { + public LocalTimerMealyTreeCacheSUL(LocalTimerMealySUL delegate, O silentOutput) { this.delegate = delegate; this.silentOutput = new LocalTimerMealyOutputSymbol<>(silentOutput); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java new file mode 100644 index 000000000..520c89634 --- /dev/null +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java @@ -0,0 +1,90 @@ +package de.learnlib.oracle.equivalence.mmlt; + +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.DummyStatsContainer; +import de.learnlib.statistic.container.LearnerStatsProvider; +import de.learnlib.statistic.container.StatsContainer; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * A chain of MMLT equivalence oracles. The oracles are queried in the given order until either a counterexample is found + * or nor example is found. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyEQOracleChain implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { + + private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyEQOracleChain.class); + + private final List> oracles = new ArrayList<>(); + private StatsContainer stats = new DummyStatsContainer(); + + /** + * Stores names for the equivalence oracles that are used in statistics. + */ + private List oracleNames; + + public void addOracle(LocalTimerMealyEquivalenceOracle oracle) { + this.oracles.add(oracle); + + // Update names: + // Names follow the convention typeName + # + index of this oracle among all oracles of same type. + this.oracleNames = new ArrayList<>(oracles.size()); + + Map typeCounter = new HashMap<>(); + for (var eqOracle : this.oracles) { + String typeName = eqOracle.getClass().getSimpleName(); + + int currentCount = typeCounter.getOrDefault(typeName, -1); + int newCount = currentCount + 1; + typeCounter.put(typeName, newCount); + + this.oracleNames.add(typeName + "#" + newCount); + } + } + + + @Override + public void setStatsContainer(StatsContainer container) { + this.stats = container; + + // Propagate to all oracles: + for (var oracle : this.oracles) { + if (oracle instanceof LearnerStatsProvider provider) { + provider.setStatsContainer(stats); + } + } + } + + @Override + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + if (this.oracles.isEmpty()) throw new IllegalStateException("Must specify at least one cex oracle in chain."); + + int oracleIdx = 0; + for (var oracle : this.oracles) { + var cex = oracle.findCounterExample(hypothesis, inputs); + if (cex != null) { + String oracleName = this.oracleNames.get(oracleIdx); + stats.increaseCounter("cnt_cex_" + oracleName, "CEX from " + oracleName); + + logger.debug("{} found counterexample: {}", "cnt_cex_" + oracleName, cex); + + return cex; + } + oracleIdx++; + } + + return null; + } +} diff --git a/oracles/equivalence-oracles/src/main/java/module-info.java b/oracles/equivalence-oracles/src/main/java/module-info.java index 9c5d3a390..c7d83f328 100644 --- a/oracles/equivalence-oracles/src/main/java/module-info.java +++ b/oracles/equivalence-oracles/src/main/java/module-info.java @@ -46,4 +46,5 @@ exports de.learnlib.oracle.equivalence.spa; exports de.learnlib.oracle.equivalence.spmm; exports de.learnlib.oracle.equivalence.vpa; + exports de.learnlib.oracle.equivalence.mmlt; } From 3bff48d00ea4ed624a30c62b90f25681a8c6b540 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 14:40:36 +0200 Subject: [PATCH 10/55] Added more test models for the MMLT learner. --- .../lstar/mmlt/LocalTimerMealyTestUtil.java | 4 + .../lstar/src/test/resources/mmlt/HVAC.gv | 61 +++++ .../lstar/src/test/resources/mmlt/Oven.gv | 22 ++ .../lstar/src/test/resources/mmlt/SCTP.gv | 225 ++++++++++++++++++ .../lstar/src/test/resources/mmlt/WSN.gv | 22 ++ .../src/test/resources/mmlt/WashingMachine.gv | 186 +++++++++++++++ 6 files changed, 520 insertions(+) create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/Oven.gv create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/WSN.gv create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java index 8ef8edd6a..765669919 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -68,6 +68,10 @@ static Model automatonFromFile(String name, int maxTime long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? maxTimerQueryWaiting : LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; + if (name.contains("SCTP")) { + maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + } + return new Model<>(automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); } diff --git a/algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv b/algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv new file mode 100644 index 000000000..5c176a909 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv @@ -0,0 +1,61 @@ +// Model of an HVAC system +// Adapted from Taylor and Taylor: Patterns in the Machine +digraph g { + + s0 [shape="circle"]; + s1 [timers="a=2000" shape="circle"]; + s2 [shape="circle"]; + s3 [shape="circle"]; + s4 [timers="a=2000" shape="circle"]; + s5 [shape="circle"]; + s6 [timers="a=2000" shape="circle"]; + s7 [timers="a=2000" shape="circle"]; + s8 [timers="a=2000" shape="circle"]; + s9 [timers="a=2000" shape="circle"]; + s10 [shape="circle"]; + s0 -> s1 [resets="a" label="Supplementing.Active / FromTransition.init"]; + s0 -> s2 [label="Supplementing.Inactive / Activity.initializeActive"]; + s1 -> s10 [label="Activity.OffMode / Stage.init"]; + s1 -> s2 [label="FromTransition.Completed / void"]; + s1 -> s1 [resets="a" label="to[a] / FromTransition.check"]; + s2 -> s10 [label="Activity.OffMode / Stage.init"]; + s2 -> s3 [label="Capacity.Excess / void"]; + s2 -> s5 [label="Capacity.NeedMore / Supplementing.enter"]; + s2 -> s6 [resets="a" label="OffCycle.IsStartInOffCycle / OffCycle.startCycling,OffCycle.startOffTime,Stage.startingOff"]; + s2 -> s8 [resets="a" label="OnCycle.IsStartInOnCycle / OnCycle.startCycling,OnCycle.startOnTime,Stage.startingOn"]; + s3 -> s10 [label="Activity.OffMode / Stage.init"]; + s3 -> s4 [resets="a" label="Activity.OnRequest / void"]; + s3 -> s10 [label="Supplementing.Inactive / Stage.shutdown"]; + s4 -> s10 [label="Activity.OffMode / Stage.init"]; + s4 -> s10 [label="BackTransition.Completed / Stage.notifyLower,Stage.shutdown"]; + s4 -> s4 [resets="a" label="to[a] / BackTransition.check"]; + s5 -> s10 [label="Activity.OffMode / Stage.init"]; + s5 -> s2 [label="Capacity.NeedLess / Supplementing.ext"]; + s6 -> s10 [label="Activity.OffMode / Stage.init"]; + s6 -> s3 [label="Capacity.Excess / void"]; + s6 -> s5 [label="Capacity.NeedMore / Supplementing.enter"]; + s6 -> s8 [resets="a" label="OffCycle.OffTimeExpired / OnCycle.startOnTime,Stage.startingOn"]; + s6 -> s7 [resets="a" label="OffCycle.StartingOffTimeExpired / Stage.off"]; + s6 -> s6 [resets="a" label="to[a] / OffCycle.checkStartingOffTime"]; + s7 -> s10 [label="Activity.OffMode / Stage.init"]; + s7 -> s3 [label="Capacity.Excess / void"]; + s7 -> s5 [label="Capacity.NeedMore / Supplementing.enter"]; + s7 -> s8 [resets="a" label="OffCycle.OffTimeExpired / OnCycle.startOnTime,Stage.startingOn"]; + s7 -> s7 [resets="a" label="to[a] / OffCycle.checkOffTime"]; + s8 -> s10 [label="Activity.OffMode / Stage.init"]; + s8 -> s3 [label="Capacity.Excess / void"]; + s8 -> s5 [label="Capacity.NeedMore / Supplementing.enter"]; + s8 -> s6 [resets="a" label="OnCycle.OnTimeExpired / OffCycle.startOffTime,Stage.startingOff"]; + s8 -> s9 [resets="a" label="OnCycle.StartingOnTimeExpired / Stage.on"]; + s8 -> s8 [resets="a" label="to[a] / OnCycle.checkStartingOnTime"]; + s9 -> s10 [label="Activity.OffMode / Stage.init"]; + s9 -> s3 [label="Capacity.Excess / void"]; + s9 -> s5 [label="Capacity.NeedMore / Supplementing.enter"]; + s9 -> s6 [resets="a" label="OnCycle.OnTimeExpired / OffCycle.startOffTime,Stage.startingOff"]; + s9 -> s9 [resets="a" label="to[a] / OnCycle.checkOnTime"]; + s10 -> s0 [label="Activity.OnRequest / void"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s10; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/Oven.gv b/algorithms/active/lstar/src/test/resources/mmlt/Oven.gv new file mode 100644 index 000000000..3c934d0c0 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/Oven.gv @@ -0,0 +1,22 @@ +// Model of an oven +digraph g { + + s0 [shape="circle"]; + s1 [timers="a=3500,b=300000" shape="circle"]; + s2 [timers="a=5000" shape="circle"]; + s3 [shape="circle"]; + s0 -> s3 [label="User.Power / void"]; + s0 -> s1 [resets="a,b" label="User.Start / Temp.on"]; + s1 -> s0 [label="User.Stop / Temp.off"]; + s1 -> s1 [resets="a" label="to[a] / Temp.adjust"]; + s1 -> s2 [resets="a" label="to[b] / Alarm.start,Temp.off"]; + s2 -> s1 [resets="a,b" label="User.Extend / Alarm.stop,Temp.on"]; + s2 -> s0 [label="User.Open / Alarm.stop"]; + s2 -> s0 [label="User.Stop / Alarm.stop"]; + s2 -> s0 [label="to[a] / Alarm.stop"]; + s3 -> s0 [label="User.Power / void"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s3; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv b/algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv new file mode 100644 index 000000000..f6308a512 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv @@ -0,0 +1,225 @@ +// Model of the association in the SCTP protocol. +// Adapted from Stewart et al.: Stream Control Transmission Protocol (RFC 9260, Figure 3) +digraph g { + + s0 [shape="circle" ]; + s1 [shape="circle" ]; + s2 [timers="a=1000" shape="circle" ]; + s3 [timers="a=1000" shape="circle" ]; + s4 [timers="a=1000" shape="circle" ]; + s5 [timers="a=1000" shape="circle" ]; + s6 [timers="a=1000" shape="circle" ]; + s7 [timers="a=1000" shape="circle" ]; + s8 [timers="a=1000" shape="circle" ]; + s9 [timers="a=1000" shape="circle" ]; + s10 [timers="a=1000" shape="circle" ]; + s11 [shape="circle" ]; + s12 [timers="a=1000" shape="circle" ]; + s13 [timers="a=1000" shape="circle" ]; + s14 [timers="a=1000" shape="circle" ]; + s15 [timers="a=1000" shape="circle" ]; + s16 [timers="a=1000" shape="circle" ]; + s17 [timers="a=1000" shape="circle" ]; + s18 [timers="a=1000" shape="circle" ]; + s19 [timers="a=1000" shape="circle" ]; + s20 [timers="a=1000" shape="circle" ]; + s21 [timers="a=1000" shape="circle" ]; + s22 [timers="a=1000" shape="circle" ]; + s23 [timers="a=1000" shape="circle" ]; + s24 [timers="a=1000" shape="circle" ]; + s25 [timers="a=1000" shape="circle" ]; + s26 [timers="a=1000" shape="circle" ]; + s27 [timers="a=1000" shape="circle" ]; + s28 [timers="a=1000" shape="circle" ]; + s29 [timers="a=1000" shape="circle" ]; + s30 [timers="a=1000" shape="circle" ]; + s31 [shape="circle" ]; + s32 [timers="a=1000" shape="circle" ]; + s33 [timers="a=1000" shape="circle" ]; + s34 [timers="a=1000" shape="circle" ]; + s35 [timers="a=1000" shape="circle" ]; + s36 [timers="a=1000" shape="circle" ]; + s37 [timers="a=1000" shape="circle" ]; + s38 [timers="a=1000" shape="circle" ]; + s39 [timers="a=1000" shape="circle" ]; + s40 [shape="circle" label="Closed"]; + s0 -> s40 [label="Receive.Abort / void"]; + s0 -> s1 [label="Receive.Shutdown / void"]; + s0 -> s40 [label="User.Abort / Send.abort"]; + s0 -> s11 [label="User.Shutdown / void"]; + s1 -> s2 [resets="a" label="no_outstanding / Send.shutdown_ack"]; + s1 -> s40 [label="Receive.Abort / void"]; + s1 -> s40 [label="User.Abort / Send.abort"]; + s2 -> s40 [label="Receive.Abort / void"]; + s2 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s2 -> s40 [label="Receive.Shutdown_complete / void"]; + s2 -> s40 [label="User.Abort / Send.abort"]; + s2 -> s3 [resets="a" label="to[a] / Send.shutdown_ack"]; + s3 -> s40 [label="Receive.Abort / void"]; + s3 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s3 -> s40 [label="Receive.Shutdown_complete / void"]; + s3 -> s40 [label="User.Abort / Send.abort"]; + s3 -> s4 [resets="a" label="to[a] / Send.shutdown_ack"]; + s4 -> s40 [label="Receive.Abort / void"]; + s4 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s4 -> s40 [label="Receive.Shutdown_complete / void"]; + s4 -> s40 [label="User.Abort / Send.abort"]; + s4 -> s5 [resets="a" label="to[a] / Send.shutdown_ack"]; + s5 -> s40 [label="Receive.Abort / void"]; + s5 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s5 -> s40 [label="Receive.Shutdown_complete / void"]; + s5 -> s40 [label="User.Abort / Send.abort"]; + s5 -> s6 [resets="a" label="to[a] / Send.shutdown_ack"]; + s6 -> s40 [label="Receive.Abort / void"]; + s6 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s6 -> s40 [label="Receive.Shutdown_complete / void"]; + s6 -> s40 [label="User.Abort / Send.abort"]; + s6 -> s7 [resets="a" label="to[a] / Send.shutdown_ack"]; + s7 -> s40 [label="Receive.Abort / void"]; + s7 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s7 -> s40 [label="Receive.Shutdown_complete / void"]; + s7 -> s40 [label="User.Abort / Send.abort"]; + s7 -> s8 [resets="a" label="to[a] / Send.shutdown_ack"]; + s8 -> s40 [label="Receive.Abort / void"]; + s8 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s8 -> s40 [label="Receive.Shutdown_complete / void"]; + s8 -> s40 [label="User.Abort / Send.abort"]; + s8 -> s9 [resets="a" label="to[a] / Send.shutdown_ack"]; + s9 -> s40 [label="Receive.Abort / void"]; + s9 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s9 -> s40 [label="Receive.Shutdown_complete / void"]; + s9 -> s40 [label="User.Abort / Send.abort"]; + s9 -> s10 [resets="a" label="to[a] / Send.shutdown_ack"]; + s10 -> s40 [label="Receive.Abort / void"]; + s10 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s10 -> s40 [label="Receive.Shutdown_complete / void"]; + s10 -> s40 [label="User.Abort / Send.abort"]; + s10 -> s31 [label="to[a] / User.error"]; + s11 -> s12 [resets="a" label="no_outstanding / Send.shutdown"]; + s11 -> s40 [label="Receive.Abort / void"]; + s11 -> s40 [label="User.Abort / Send.abort"]; + s12 -> s40 [label="Receive.Abort / void"]; + s12 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s12 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s12 -> s40 [label="User.Abort / Send.abort"]; + s12 -> s13 [resets="a" label="to[a] / Send.shutdown"]; + s13 -> s40 [label="Receive.Abort / void"]; + s13 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s13 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s13 -> s40 [label="User.Abort / Send.abort"]; + s13 -> s14 [resets="a" label="to[a] / Send.shutdown"]; + s14 -> s40 [label="Receive.Abort / void"]; + s14 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s14 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s14 -> s40 [label="User.Abort / Send.abort"]; + s14 -> s15 [resets="a" label="to[a] / Send.shutdown"]; + s15 -> s40 [label="Receive.Abort / void"]; + s15 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s15 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s15 -> s40 [label="User.Abort / Send.abort"]; + s15 -> s16 [resets="a" label="to[a] / Send.shutdown"]; + s16 -> s40 [label="Receive.Abort / void"]; + s16 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s16 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s16 -> s40 [label="User.Abort / Send.abort"]; + s16 -> s17 [resets="a" label="to[a] / Send.shutdown"]; + s17 -> s40 [label="Receive.Abort / void"]; + s17 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s17 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s17 -> s40 [label="User.Abort / Send.abort"]; + s17 -> s18 [resets="a" label="to[a] / Send.shutdown"]; + s18 -> s40 [label="Receive.Abort / void"]; + s18 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s18 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s18 -> s40 [label="User.Abort / Send.abort"]; + s18 -> s19 [resets="a" label="to[a] / Send.shutdown"]; + s19 -> s40 [label="Receive.Abort / void"]; + s19 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s19 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s19 -> s40 [label="User.Abort / Send.abort"]; + s19 -> s20 [resets="a" label="to[a] / Send.shutdown"]; + s20 -> s40 [label="Receive.Abort / void"]; + s20 -> s2 [resets="a" label="Receive.Shutdown / Send.shutdown_ack"]; + s20 -> s40 [label="Receive.Shutdown_ack / Send.shutdown_complete"]; + s20 -> s40 [label="User.Abort / Send.abort"]; + s20 -> s31 [label="to[a] / User.error"]; + s21 -> s40 [label="Receive.Abort / void"]; + s21 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s21 -> s40 [label="User.Abort / Send.abort"]; + s21 -> s32 [resets="a" label="to[a] / Send.init"]; + s22 -> s40 [label="Receive.Abort / void"]; + s22 -> s0 [label="Receive.Cookie_ack / void"]; + s22 -> s40 [label="User.Abort / Send.abort"]; + s22 -> s23 [resets="a" label="to[a] / Send.cookie_echo"]; + s23 -> s40 [label="Receive.Abort / void"]; + s23 -> s0 [label="Receive.Cookie_ack / void"]; + s23 -> s40 [label="User.Abort / Send.abort"]; + s23 -> s24 [resets="a" label="to[a] / Send.cookie_echo"]; + s24 -> s40 [label="Receive.Abort / void"]; + s24 -> s0 [label="Receive.Cookie_ack / void"]; + s24 -> s40 [label="User.Abort / Send.abort"]; + s24 -> s25 [resets="a" label="to[a] / Send.cookie_echo"]; + s25 -> s40 [label="Receive.Abort / void"]; + s25 -> s0 [label="Receive.Cookie_ack / void"]; + s25 -> s40 [label="User.Abort / Send.abort"]; + s25 -> s26 [resets="a" label="to[a] / Send.cookie_echo"]; + s26 -> s40 [label="Receive.Abort / void"]; + s26 -> s0 [label="Receive.Cookie_ack / void"]; + s26 -> s40 [label="User.Abort / Send.abort"]; + s26 -> s27 [resets="a" label="to[a] / Send.cookie_echo"]; + s27 -> s40 [label="Receive.Abort / void"]; + s27 -> s0 [label="Receive.Cookie_ack / void"]; + s27 -> s40 [label="User.Abort / Send.abort"]; + s27 -> s28 [resets="a" label="to[a] / Send.cookie_echo"]; + s28 -> s40 [label="Receive.Abort / void"]; + s28 -> s0 [label="Receive.Cookie_ack / void"]; + s28 -> s40 [label="User.Abort / Send.abort"]; + s28 -> s29 [resets="a" label="to[a] / Send.cookie_echo"]; + s29 -> s40 [label="Receive.Abort / void"]; + s29 -> s0 [label="Receive.Cookie_ack / void"]; + s29 -> s40 [label="User.Abort / Send.abort"]; + s29 -> s30 [resets="a" label="to[a] / Send.cookie_echo"]; + s30 -> s40 [label="Receive.Abort / void"]; + s30 -> s0 [label="Receive.Cookie_ack / void"]; + s30 -> s40 [label="User.Abort / Send.abort"]; + s30 -> s31 [label="to[a] / User.error"]; + s32 -> s40 [label="Receive.Abort / void"]; + s32 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s32 -> s40 [label="User.Abort / Send.abort"]; + s32 -> s33 [resets="a" label="to[a] / Send.init"]; + s33 -> s40 [label="Receive.Abort / void"]; + s33 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s33 -> s40 [label="User.Abort / Send.abort"]; + s33 -> s34 [resets="a" label="to[a] / Send.init"]; + s34 -> s40 [label="Receive.Abort / void"]; + s34 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s34 -> s40 [label="User.Abort / Send.abort"]; + s34 -> s35 [resets="a" label="to[a] / Send.init"]; + s35 -> s40 [label="Receive.Abort / void"]; + s35 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s35 -> s40 [label="User.Abort / Send.abort"]; + s35 -> s36 [resets="a" label="to[a] / Send.init"]; + s36 -> s40 [label="Receive.Abort / void"]; + s36 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s36 -> s40 [label="User.Abort / Send.abort"]; + s36 -> s37 [resets="a" label="to[a] / Send.init"]; + s37 -> s40 [label="Receive.Abort / void"]; + s37 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s37 -> s40 [label="User.Abort / Send.abort"]; + s37 -> s38 [resets="a" label="to[a] / Send.init"]; + s38 -> s40 [label="Receive.Abort / void"]; + s38 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s38 -> s40 [label="User.Abort / Send.abort"]; + s38 -> s39 [resets="a" label="to[a] / Send.init"]; + s39 -> s40 [label="Receive.Abort / void"]; + s39 -> s22 [resets="a" label="Receive.Init_ack / Send.cookie_echo"]; + s39 -> s40 [label="User.Abort / Send.abort"]; + s39 -> s31 [label="to[a] / User.error"]; + s40 -> s40 [label="Receive.Init / Send.init_ack"]; + s40 -> s0 [label="Receive.Valid / Send.cookie_ack"]; + s40 -> s21 [resets="a" label="User.Associate / Send.init"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s40; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/WSN.gv b/algorithms/active/lstar/src/test/resources/mmlt/WSN.gv new file mode 100644 index 000000000..a8288e71d --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/WSN.gv @@ -0,0 +1,22 @@ +// Model of a wireless sensor node that collects data and transmits if +// if the battery has sufficient charge. +digraph g { + + s0 [timers="a=60000,b=3600000" shape="circle"]; + s1 [timers="a=300000" shape="circle"]; + s2 [shape="circle"]; + s3 [shape="circle"]; + s0 -> s1 [resets="a" label="Battery.Low / Tx.disable"]; + s0 -> s3 [label="User.Power / void"]; + s0 -> s0 [resets="a" label="to[a] / Sensor.sample"]; + s0 -> s0 [resets="b" label="to[b] / Tx.send"]; + s1 -> s2 [label="Battery.Empty / void"]; + s1 -> s1 [resets="a" label="User.Collect / Buffer.get"]; + s1 -> s3 [label="User.Power / void"]; + s1 -> s1 [resets="a" label="to[a] / Sensor.sample"]; + s3 -> s0 [resets="a,b" label="User.Power / void"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s3; + +} diff --git a/algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv b/algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv new file mode 100644 index 000000000..1e112ec4f --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv @@ -0,0 +1,186 @@ +// Model of a washing machine +digraph g { + + s0 [timers="a=10000" shape="circle"]; + s1 [timers="a=10000" shape="circle"]; + s2 [shape="circle"]; + s3 [shape="circle"]; + s4 [timers="a=2000" shape="circle"]; + s5 [shape="circle"]; + s6 [timers="a=360000" shape="circle"]; + s7 [timers="a=360000" shape="circle"]; + s8 [timers="a=360000" shape="circle"]; + s9 [timers="a=360000" shape="circle"]; + s10 [timers="a=360000" shape="circle"]; + s11 [timers="a=360000" shape="circle"]; + s12 [timers="a=360000" shape="circle"]; + s13 [timers="a=360000" shape="circle"]; + s14 [timers="a=360000" shape="circle"]; + s15 [timers="a=360000" shape="circle"]; + s16 [timers="a=360000" shape="circle"]; + s17 [timers="a=360000" shape="circle"]; + s18 [timers="a=360000" shape="circle"]; + s19 [timers="a=360000" shape="circle"]; + s20 [timers="a=360000" shape="circle"]; + s21 [timers="a=360000" shape="circle"]; + s22 [timers="a=360000" shape="circle"]; + s23 [timers="a=360000" shape="circle"]; + s24 [timers="a=360000" shape="circle"]; + s25 [timers="a=360000" shape="circle"]; + s26 [shape="circle"]; + s27 [timers="a=180000" shape="circle"]; + s28 [shape="circle"]; + s29 [shape="circle"]; + s30 [timers="a=10000" shape="circle"]; + s31 [shape="circle"]; + s32 [timers="a=3600000" shape="circle"]; + s33 [shape="circle"]; + s34 [shape="circle"]; + s0 -> s30 [resets="a" label="Buttons.Start_short / void"]; + s0 -> s1 [resets="a" label="Buttons.Start / void"]; + s0 -> s34 [label="to[a] / Display.off"]; + s1 -> s2 [label="Door.Closed / Display.rem_normal,Door.lock,PumpIn.start"]; + s1 -> s0 [resets="a" label="Door.Open / Display.door_warning"]; + s1 -> s34 [label="to[a] / Display.off"]; + s2 -> s3 [label="Buttons.Stop / PumpIn.stop,PumpOut.start"]; + s2 -> s29 [label="Water.Leak / Display.alarm,PumpIn.stop"]; + s2 -> s5 [label="Water.Full / Heater.start,PumpIn.stop"]; + s3 -> s4 [resets="a" label="Water.Empty / Display.done,Door.unlock"]; + s4 -> s0 [resets="a" label="Buttons.Start_short / void"]; + s4 -> s0 [resets="a" label="Buttons.On / void"]; + s4 -> s0 [resets="a" label="Buttons.Start / void"]; + s4 -> s0 [resets="a" label="Buttons.Stop / void"]; + s4 -> s0 [resets="a" label="Door.Open / void"]; + s4 -> s4 [resets="a" label="to[a] / Beeper.beep"]; + s5 -> s3 [label="Buttons.Stop / Heater.stop,PumpOut.start"]; + s5 -> s29 [label="Water.Leak / Display.alarm,Heater.stop"]; + s5 -> s6 [resets="a" label="Water.Temp_ok / Detergent.add,Drum.normal_speed,Heater.stop"]; + s6 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s6 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s6 -> s6 [label="Water.Temp_low / Heater.start"]; + s6 -> s6 [label="Water.Temp_ok / Heater.stop"]; + s6 -> s7 [resets="a" label="to[a] / Drum.change_speed"]; + s7 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s7 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s7 -> s7 [label="Water.Temp_low / Heater.start"]; + s7 -> s7 [label="Water.Temp_ok / Heater.stop"]; + s7 -> s8 [resets="a" label="to[a] / Drum.change_speed"]; + s8 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s8 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s8 -> s8 [label="Water.Temp_low / Heater.start"]; + s8 -> s8 [label="Water.Temp_ok / Heater.stop"]; + s8 -> s9 [resets="a" label="to[a] / Drum.change_speed"]; + s9 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s9 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s9 -> s9 [label="Water.Temp_low / Heater.start"]; + s9 -> s9 [label="Water.Temp_ok / Heater.stop"]; + s9 -> s10 [resets="a" label="to[a] / Drum.change_speed"]; + s10 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s10 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s10 -> s10 [label="Water.Temp_low / Heater.start"]; + s10 -> s10 [label="Water.Temp_ok / Heater.stop"]; + s10 -> s11 [resets="a" label="to[a] / Drum.change_speed"]; + s11 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s11 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s11 -> s11 [label="Water.Temp_low / Heater.start"]; + s11 -> s11 [label="Water.Temp_ok / Heater.stop"]; + s11 -> s12 [resets="a" label="to[a] / Drum.change_speed"]; + s12 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s12 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s12 -> s12 [label="Water.Temp_low / Heater.start"]; + s12 -> s12 [label="Water.Temp_ok / Heater.stop"]; + s12 -> s13 [resets="a" label="to[a] / Drum.change_speed"]; + s13 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s13 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s13 -> s13 [label="Water.Temp_low / Heater.start"]; + s13 -> s13 [label="Water.Temp_ok / Heater.stop"]; + s13 -> s14 [resets="a" label="to[a] / Drum.change_speed"]; + s14 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s14 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s14 -> s14 [label="Water.Temp_low / Heater.start"]; + s14 -> s14 [label="Water.Temp_ok / Heater.stop"]; + s14 -> s15 [resets="a" label="to[a] / Drum.change_speed"]; + s15 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s15 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s15 -> s15 [label="Water.Temp_low / Heater.start"]; + s15 -> s15 [label="Water.Temp_ok / Heater.stop"]; + s15 -> s16 [resets="a" label="to[a] / Drum.change_speed"]; + s16 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s16 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s16 -> s16 [label="Water.Temp_low / Heater.start"]; + s16 -> s16 [label="Water.Temp_ok / Heater.stop"]; + s16 -> s17 [resets="a" label="to[a] / Drum.change_speed"]; + s17 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s17 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s17 -> s17 [label="Water.Temp_low / Heater.start"]; + s17 -> s17 [label="Water.Temp_ok / Heater.stop"]; + s17 -> s18 [resets="a" label="to[a] / Drum.change_speed"]; + s18 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s18 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s18 -> s18 [label="Water.Temp_low / Heater.start"]; + s18 -> s18 [label="Water.Temp_ok / Heater.stop"]; + s18 -> s19 [resets="a" label="to[a] / Drum.change_speed"]; + s19 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s19 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s19 -> s19 [label="Water.Temp_low / Heater.start"]; + s19 -> s19 [label="Water.Temp_ok / Heater.stop"]; + s19 -> s20 [resets="a" label="to[a] / Drum.change_speed"]; + s20 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s20 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s20 -> s20 [label="Water.Temp_low / Heater.start"]; + s20 -> s20 [label="Water.Temp_ok / Heater.stop"]; + s20 -> s21 [resets="a" label="to[a] / Drum.change_speed"]; + s21 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s21 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s21 -> s21 [label="Water.Temp_low / Heater.start"]; + s21 -> s21 [label="Water.Temp_ok / Heater.stop"]; + s21 -> s22 [resets="a" label="to[a] / Drum.change_speed"]; + s22 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s22 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s22 -> s22 [label="Water.Temp_low / Heater.start"]; + s22 -> s22 [label="Water.Temp_ok / Heater.stop"]; + s22 -> s23 [resets="a" label="to[a] / Drum.change_speed"]; + s23 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s23 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s23 -> s23 [label="Water.Temp_low / Heater.start"]; + s23 -> s23 [label="Water.Temp_ok / Heater.stop"]; + s23 -> s24 [resets="a" label="to[a] / Drum.change_speed"]; + s24 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s24 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s24 -> s24 [label="Water.Temp_low / Heater.start"]; + s24 -> s24 [label="Water.Temp_ok / Heater.stop"]; + s24 -> s25 [resets="a" label="to[a] / Drum.change_speed"]; + s25 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s25 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s25 -> s25 [label="Water.Temp_low / Heater.start"]; + s25 -> s25 [label="Water.Temp_ok / Heater.stop"]; + s25 -> s26 [label="to[a] / Drum.stop,Heater.stop,PumpOut.start"]; + s26 -> s3 [label="Buttons.Stop / PumpOut.start"]; + s26 -> s29 [label="Water.Leak / Display.alarm,PumpOut.stop"]; + s26 -> s27 [resets="a" label="Water.Low / Drum.full_speed,PumpOut.stop"]; + s27 -> s3 [label="Buttons.Stop / Drum.stop,PumpOut.start"]; + s27 -> s29 [label="Water.Leak / Display.alarm,Drum.stop"]; + s27 -> s28 [label="to[a] / Drum.stop,PumpOut.start"]; + s28 -> s3 [label="Buttons.Stop / PumpOut.start"]; + s28 -> s4 [resets="a" label="Water.Empty / Display.done,Door.unlock,PumpOut.stop"]; + s28 -> s29 [label="Water.Leak / Display.alarm,PumpOut.stop"]; + s30 -> s31 [label="Door.Closed / Display.rem_short,Door.lock,Heater.start,PumpIn.start"]; + s30 -> s0 [resets="a" label="Door.Open / Display.door_warning"]; + s30 -> s34 [label="to[a] / Display.off"]; + s31 -> s3 [label="Buttons.Stop / Heater.stop,PumpIn.stop,PumpOut.start"]; + s31 -> s29 [label="Water.Leak / Display.alarm,Heater.stop,PumpIn.stop"]; + s31 -> s32 [resets="a" label="Water.Full / Detergent.add,Drum.normal_speed,Heater.stop,PumpIn.stop"]; + s32 -> s3 [label="Buttons.Stop / Drum.stop,Heater.stop,PumpOut.start"]; + s32 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,Heater.stop"]; + s32 -> s32 [label="Water.Temp_low / Heater.start"]; + s32 -> s32 [label="Water.Temp_ok / Heater.stop"]; + s32 -> s33 [label="to[a] / Drum.normal_speed,Heater.stop,PumpOut.start"]; + s33 -> s3 [label="Buttons.Stop / Drum.stop,PumpOut.start"]; + s33 -> s4 [resets="a" label="Water.Empty / Display.done,Door.unlock,Drum.stop,PumpOut.stop"]; + s33 -> s29 [label="Water.Leak / Display.alarm,Drum.stop,PumpOut.stop"]; + s34 -> s0 [resets="a" label="Buttons.On / Display.welcome"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s34; + +} From 283b02615a680034d53d5f42a0e513d5d487e770 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 14:47:41 +0200 Subject: [PATCH 11/55] Added more test models for the MMLT learner. --- .../lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java | 2 +- .../active/lstar/src/test/resources/mmlt/{HVAC.gv => HVAC.dot} | 0 .../active/lstar/src/test/resources/mmlt/{Oven.gv => Oven.dot} | 0 .../active/lstar/src/test/resources/mmlt/{SCTP.gv => SCTP.dot} | 0 .../active/lstar/src/test/resources/mmlt/{WSN.gv => WSN.dot} | 0 5 files changed, 1 insertion(+), 1 deletion(-) rename algorithms/active/lstar/src/test/resources/mmlt/{HVAC.gv => HVAC.dot} (100%) rename algorithms/active/lstar/src/test/resources/mmlt/{Oven.gv => Oven.dot} (100%) rename algorithms/active/lstar/src/test/resources/mmlt/{SCTP.gv => SCTP.dot} (100%) rename algorithms/active/lstar/src/test/resources/mmlt/{WSN.gv => WSN.dot} (100%) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index e0bdd42a3..c6a90f726 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -73,7 +73,7 @@ private static void runExperiment(LStarLocalTimerMealy learner, Equ if (printFinalResult) { System.out.println("Final hypothesis:"); LocalTimerMealyTestUtil.printModel(finalHypothesis); - new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + //new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); } } diff --git a/algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv b/algorithms/active/lstar/src/test/resources/mmlt/HVAC.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/HVAC.gv rename to algorithms/active/lstar/src/test/resources/mmlt/HVAC.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/Oven.gv b/algorithms/active/lstar/src/test/resources/mmlt/Oven.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/Oven.gv rename to algorithms/active/lstar/src/test/resources/mmlt/Oven.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv b/algorithms/active/lstar/src/test/resources/mmlt/SCTP.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/SCTP.gv rename to algorithms/active/lstar/src/test/resources/mmlt/SCTP.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/WSN.gv b/algorithms/active/lstar/src/test/resources/mmlt/WSN.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/WSN.gv rename to algorithms/active/lstar/src/test/resources/mmlt/WSN.dot From 0ae231855f2c03a4cb348b21e82b9e922c3c3099 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 15:14:24 +0200 Subject: [PATCH 12/55] Added cache consistency test for MMLT learning. --- .../LStarLocalTimerMealyBenchmarkTests.java | 3 +- .../filter/cache/LocalTimerMealyCache.java | 24 +++ .../LocalTimerMealyCacheConsistencyTest.java | 167 ++++++++++++++++++ .../mmlt/LocalTimerMealyTreeCacheSUL.java | 9 +- 4 files changed, 199 insertions(+), 4 deletions(-) create mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java create mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index c6a90f726..46ffc27c9 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -4,6 +4,7 @@ import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; +import de.learnlib.filter.cache.mmlt.LocalTimerMealyCacheConsistencyTest; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; import de.learnlib.oracle.EquivalenceOracle; @@ -102,7 +103,7 @@ private static void learnModel(String name, LocalTimerMealy a // Prepare cex oracle chain: LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); - //chainOracle.addOracle(new LocalTimerMealyCacheOracle<>(cacheSUL, params)); TODO! + chainOracle.addOracle(new LocalTimerMealyCacheConsistencyTest<>(cacheSUL, params)); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 6, 12, 100)); chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(automaton)); // ensure that we eventually find an accurate model diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java new file mode 100644 index 000000000..7e9647bef --- /dev/null +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java @@ -0,0 +1,24 @@ +package de.learnlib.filter.cache; + +import de.learnlib.sul.LocalTimerMealySUL; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.word.Word; + +import java.util.List; + +/** + * Abstract class for caches for {@link LocalTimerMealySUL}. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public abstract class LocalTimerMealyCache extends LocalTimerMealySUL { + + /** + * Lists all words that are currently in the cache. + * + * @return List of all stored words. + */ + public abstract List>> listAllWords(); + +} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java new file mode 100644 index 000000000..3e5d9998e --- /dev/null +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -0,0 +1,167 @@ +package de.learnlib.filter.cache.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; + +/** + * Searches for counterexamples by comparing the behavior of the hypothesis and the query cache. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { + private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyCacheConsistencyTest.class); + + private final LocalTimerMealyTreeCacheSUL sulCache; + private final LocalTimerMealyModelParams modelParams; + + public LocalTimerMealyCacheConsistencyTest(LocalTimerMealyTreeCacheSUL sulCache, LocalTimerMealyModelParams modelParams) { + this.sulCache = sulCache; + this.modelParams = modelParams; + } + + private DefaultQuery, Word>> queryCache(Word> word) { + WordBuilder> wbInput = new WordBuilder<>(); + WordBuilder> wbOutput = new WordBuilder<>(); + + this.sulCache.pre(); + for (var sym : word) { + if (sym instanceof NonDelayingInput ndi) { + LocalTimerMealyOutputSymbol res = this.sulCache.step(ndi); + wbInput.append(ndi); + wbOutput.append(res); + } else if (sym instanceof TimeStepSequence ws) { + LocalTimerMealyOutputSymbol res = this.sulCache.timeoutStep(ws.getTimeSteps()); + wbInput.append(ws); + + if (res == null) { + wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); + } else { + wbOutput.append(res); + } + } else { + throw new AssertionError("Symbol type must not be used in cache."); + } + } + this.sulCache.post(); + + return new DefaultQuery<>(wbInput.toWord(), wbOutput.toWord()); + } + + /** + * The cache does not use timeout symbols. Using these instead of tau-sequences has several performance benefits. + * This function converts a query with a tau-sequence to one that uses timeout symbols where possible. + * + * @param originalQuery Original query + * @return Converted query + */ + private DefaultQuery, Word>> convertTimeSequences(DefaultQuery, Word>> originalQuery) { + WordBuilder> wbInput = new WordBuilder<>(); + WordBuilder> wbOutput = new WordBuilder<>(); + + int symIdx = 0; + var queryInput = originalQuery.getInput(); + var queryOutput = originalQuery.getOutput(); + + while (symIdx < queryInput.length()) { + var inputSym = queryInput.getSymbol(symIdx); + var outputSym = queryOutput.getSymbol(symIdx); + symIdx++; + + if (inputSym instanceof NonDelayingInput ds) { + wbInput.append(ds); + wbOutput.append(outputSym); + } else if (inputSym instanceof TimeStepSequence ws) { + if (!outputSym.getSymbol().equals(this.modelParams.silentOutput()) || ws.getTimeSteps() == this.modelParams.maxTimeoutWaitingTime()) { + // Found a timeout OR no timeout after max_delay: + wbInput.append(new TimeoutSymbol<>()); + wbOutput.append(outputSym); + continue; + } + if (ws.getTimeSteps() >= this.modelParams.maxTimeoutWaitingTime()) { + throw new AssertionError("Wait time that exceeds max_delay in cache."); + } + + // Special case: silent output before max delay + // Cannot replace with "timeout", as this implies wait until max_delay. + // Hence: skip subsequent waits until reaching wait with output OR max_delay OR end of word: + long combinedWaitTime = ws.getTimeSteps(); + LocalTimerMealyOutputSymbol combinedOutput = outputSym; + + while (combinedOutput.getSymbol().equals(this.modelParams.silentOutput()) && combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() + && symIdx < queryInput.length() && + queryInput.getSymbol(symIdx) instanceof TimeStepSequence nextWs) { + combinedWaitTime += nextWs.getTimeSteps(); + combinedOutput = queryOutput.getSymbol(symIdx); + symIdx++; + } + + if (combinedWaitTime == this.modelParams.maxTimeoutWaitingTime() || !combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { + wbInput.append(new TimeoutSymbol<>()); + + if (combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { + // Reached max delay -> no timeout: + wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); + } else { + // Found non-silent output: + wbOutput.append(new LocalTimerMealyOutputSymbol<>(combinedWaitTime, combinedOutput.getSymbol())); + } + } else { + // Reached end of word before max_delay OR exceeding max delay OR non-wait symbol -> ignore rest of this word: + if (symIdx < queryInput.length() - 1) { + logger.warn("Ignoring at least one symbol during cache comparison."); + } + break; + } + } + } + return new DefaultQuery<>(wbInput.toWord(), wbOutput.toWord()); + } + + + @Override + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + // TODO only with the provided inputs! + + // Query all cached words: + List>> cachedWords = this.sulCache.listAllWords(); + + List, Word>>> counterexamples = new ArrayList<>(); + for (var word : cachedWords) { + // First, query word as-is (may include wait-symbols in input): + DefaultQuery, Word>> rawCacheQuery = this.queryCache(word); + + // Next, convert query that includes wait-symbols to query with timeout-symbols: + var convertedQuery = this.convertTimeSequences(rawCacheQuery); + + // Finally, query hypothesis using the converted query: + Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), convertedQuery.getInput()); + + if (!hypOutput.equals(convertedQuery.getOutput())) { + // Hyp gives different output than cache (= SUL): + counterexamples.add(convertedQuery); + } + } + + if (counterexamples.isEmpty()) { + return null; + } + + // Take the shortest word: + return counterexamples.stream().min(Comparator.comparingInt(w -> w.getInput().length())).get(); + } +} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java index 26165e9e2..01ece3bc3 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java @@ -1,6 +1,8 @@ package de.learnlib.filter.cache.mmlt; +import de.learnlib.filter.cache.LocalTimerMealyCache; +import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; @@ -21,12 +23,12 @@ import java.util.*; /** - * Caches queries sent to an AbstractLocalTimerMealySUL. + * Caches queries sent to a LocalTimerMealySUL. * * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyTreeCacheSUL extends LocalTimerMealySUL implements GraphViewable, LearnerStatsProvider { +public class LocalTimerMealyTreeCacheSUL extends LocalTimerMealyCache implements GraphViewable, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private final CacheTreeNode cacheRoot; @@ -193,6 +195,7 @@ private List> getLeaves() { return leaves; } + @Override public List>> listAllWords() { List> leaves = this.getLeaves(); @@ -218,6 +221,7 @@ public List>> listAllWords() { return finalWords; } + @Override public Graph graphView() { // Convert tree to a mealy automaton: @@ -257,5 +261,4 @@ public List>> listAllWords() { return mealy.graphView(); } - } From 0efb484798fd4340fe5d20857b5ce0d75b85eca1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 08:12:33 +0200 Subject: [PATCH 13/55] Removed location type parameter from LocalTimerMealyEquivalenceOracle. --- .../LStarLocalTimerMealyBenchmarkTests.java | 4 ++-- .../de/learnlib/oracle/EquivalenceOracle.java | 3 +-- .../LocalTimerMealyCacheConsistencyTest.java | 6 ++--- .../mmlt/LocalTimerMealyEQOracleChain.java | 12 ++++++---- .../mmlt/LocalTimerMealyRandomWpOracle.java | 23 +++++++++++-------- .../mmlt/LocalTimerMealySimulatorOracle.java | 4 ++-- .../equivalence/mmlt/ResetSearchOracle.java | 9 ++++---- 7 files changed, 32 insertions(+), 29 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index 46ffc27c9..0b41c556d 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -44,7 +44,7 @@ private enum FilterMode { none, random, ignore_all, perfect } - private static void runExperiment(LStarLocalTimerMealy learner, EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, StatsContainer stats, int maxRounds, + private static void runExperiment(LStarLocalTimerMealy learner, EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, StatsContainer stats, int maxRounds, boolean printFinalResult) { stats.startOrResumeClock("learningRt", "Processing time"); learner.startLearning(); @@ -102,7 +102,7 @@ private static void learnModel(String name, LocalTimerMealy a // Prepare cex oracle chain: - LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); + LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); chainOracle.addOracle(new LocalTimerMealyCacheConsistencyTest<>(cacheSUL, params)); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 6, 12, 100)); diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index 960fda01a..40f9b46f8 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -98,9 +98,8 @@ interface MooreEquivalenceOracle extends EquivalenceOracle Location type * @param Input type for non-delaying inputs * @param Output symbol type */ - interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>>{} + interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>>{} } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 3e5d9998e..15b23df2e 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -19,11 +19,10 @@ /** * Searches for counterexamples by comparing the behavior of the hypothesis and the query cache. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyCacheConsistencyTest.class); private final LocalTimerMealyTreeCacheSUL sulCache; @@ -134,7 +133,7 @@ private DefaultQuery, Word, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { // TODO only with the provided inputs! // Query all cached words: @@ -164,4 +163,5 @@ private DefaultQuery, Word w.getInput().length())).get(); } + } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java index 520c89634..33fbeb6a2 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java @@ -18,16 +18,18 @@ /** * A chain of MMLT equivalence oracles. The oracles are queried in the given order until either a counterexample is found * or nor example is found. + *

+ * This operates similarly to {@link de.learnlib.oracle.equivalence.EQOracleChain}, + * but also stores statistics about the queries in a {@link StatsContainer}. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyEQOracleChain implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { +public class LocalTimerMealyEQOracleChain implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyEQOracleChain.class); - private final List> oracles = new ArrayList<>(); + private final List> oracles = new ArrayList<>(); private StatsContainer stats = new DummyStatsContainer(); /** @@ -35,7 +37,7 @@ public class LocalTimerMealyEQOracleChain implements EquivalenceOracle. */ private List oracleNames; - public void addOracle(LocalTimerMealyEquivalenceOracle oracle) { + public void addOracle(LocalTimerMealyEquivalenceOracle oracle) { this.oracles.add(oracle); // Update names: @@ -68,7 +70,7 @@ public void setStatsContainer(StatsContainer container) { } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { if (this.oracles.isEmpty()) throw new IllegalStateException("Must specify at least one cex oracle in chain."); int oracleIdx = 0; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java index 4d2f38c56..ab27d8e81 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -48,11 +48,10 @@ * RandomWP counterexample search for MMLT learning. * Key modification: samples prefix from entry prefixes instead of all state prefixes. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { +public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyRandomWpOracle.class); private final AbstractTimedQueryOracle timeOracle; @@ -77,7 +76,11 @@ public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + return findCounterExampleInternal(hypothesis); + } + + private DefaultQuery, Word>> findCounterExampleInternal(LocalTimerMealy hypothesis) { // Make expanded form of hypothesis: var hypSemModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(hypothesis); @@ -106,18 +109,18 @@ public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, // Found inconsistency if outputs do no match: if (!sulAnswer.getOutput().equals(hypAnswer)) { - return sulAnswer; // expected SUL output + return sulAnswer; } } - return null; // no counterexample found + return null; } - private DefaultQuery, Word>> generateTestword(List>> prefixes, - List>> globalSuffixes, - LocalTimerMealy hypothesis, - ReducedLocalTimerMealySemantics hypSemModel, - List> alphabet) { + private DefaultQuery, Word>> generateTestword(List>> prefixes, + List>> globalSuffixes, + LocalTimerMealy hypothesis, + ReducedLocalTimerMealySemantics hypSemModel, + List> alphabet) { WordBuilder> wbTestWord = new WordBuilder<>(); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java index 1d82bfd4b..5fcfc56b5 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java @@ -40,7 +40,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final LocalTimerMealy refModel; @@ -50,7 +50,7 @@ public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis); if (separatingWord != null) { diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index b3fdf9e94..0c7ee5b60 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -46,11 +46,10 @@ * - Appends inputs of all inputs that self-loop in that location. * - Appends timeout. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ResetSearchOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class ResetSearchOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); @@ -71,7 +70,7 @@ public ResetSearchOracle(AbstractTimedQueryOracle timeOracle, long seed, d this.loopingInputSelectionSeed = seed; } - private List> getLoopingSymbols(S sourceLoc, List> alphabet, LocalTimerMealy hypothesis) { + private List> getLoopingSymbols(S sourceLoc, List> alphabet, LocalTimerMealy hypothesis) { List> loopingInputs = new ArrayList<>(); for (var sym : alphabet) { @@ -90,7 +89,7 @@ private List> getLoopingSymbols(S sourceLo } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { if (loopInsertPerc == 0) { return null; // oracle is disabled } @@ -98,7 +97,7 @@ private List> getLoopingSymbols(S sourceLo return this.findCexInternal(hypothesis); } - private @Nullable DefaultQuery, Word>> findCexInternal + private @Nullable DefaultQuery, Word>> findCexInternal (LocalTimerMealy hypothesis) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: From 6aeeb9db6fe93b25928b19aeccbadb32c07f5e1b Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 08:28:47 +0200 Subject: [PATCH 14/55] Cache for MMLTs now inherits LearningCache --- .../LStarLocalTimerMealyBenchmarkTests.java | 8 +++---- .../learnlib/filter/cache/LearningCache.java | 20 ++++++++++++++++ .../filter/cache/LocalTimerMealyCache.java | 24 ------------------- .../filter/cache/mmlt/CacheTreeNode.java | 2 +- .../LocalTimerMealyCacheConsistencyTest.java | 4 ++-- ....java => LocalTimerMealyTreeSULCache.java} | 18 ++++++++++---- 6 files changed, 39 insertions(+), 37 deletions(-) delete mode 100644 filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java rename filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/{LocalTimerMealyTreeCacheSUL.java => LocalTimerMealyTreeSULCache.java} (91%) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index 0b41c556d..22f23aeab 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -2,9 +2,7 @@ import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; -import de.learnlib.filter.cache.mmlt.LocalTimerMealyCacheConsistencyTest; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; import de.learnlib.oracle.EquivalenceOracle; @@ -27,7 +25,7 @@ import net.automatalib.word.Word; import org.testng.annotations.Test; -import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeCacheSUL; +import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeSULCache; import java.util.ArrayList; import java.util.List; @@ -94,7 +92,7 @@ private static void learnModel(String name, LocalTimerMealy a // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton); LocalTimerMealyStatsSUL statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); - LocalTimerMealyTreeCacheSUL cacheSUL = new LocalTimerMealyTreeCacheSUL<>(statsAfterCache, params.silentOutput()); + LocalTimerMealyTreeSULCache cacheSUL = new LocalTimerMealyTreeSULCache<>(statsAfterCache, params); cacheSUL.setStatsContainer(stats); LocalTimerMealySUL toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime(), stats); @@ -103,7 +101,7 @@ private static void learnModel(String name, LocalTimerMealy a // Prepare cex oracle chain: LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); - chainOracle.addOracle(new LocalTimerMealyCacheConsistencyTest<>(cacheSUL, params)); + chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 6, 12, 100)); chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(automaton)); // ensure that we eventually find an accurate model diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java index 9f6b13244..57b57785b 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java @@ -16,11 +16,16 @@ package de.learnlib.filter.cache; import de.learnlib.oracle.EquivalenceOracle; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.word.Word; +import java.util.List; + /** * Interface for a cache used in automata learning. *

@@ -84,4 +89,19 @@ interface MealyLearningCache extends LearningCache extends LearningCache, I, Word> {} + + /** + * Specialization of the {@link LearningCache} interface for MMLT learning. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ + interface LocalTimerMealyLearningCache extends LearningCache, LocalTimerMealySemanticInputSymbol, Word>>{ + /** + * Lists all words that are currently in the cache. + * + * @return List of all stored words. + */ + List>> listAllWords(); + } } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java deleted file mode 100644 index 7e9647bef..000000000 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LocalTimerMealyCache.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.learnlib.filter.cache; - -import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.word.Word; - -import java.util.List; - -/** - * Abstract class for caches for {@link LocalTimerMealySUL}. - * - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public abstract class LocalTimerMealyCache extends LocalTimerMealySUL { - - /** - * Lists all words that are currently in the cache. - * - * @return List of all stored words. - */ - public abstract List>> listAllWords(); - -} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index bddb81c3c..111cba4e8 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -22,7 +22,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class CacheTreeNode { +class CacheTreeNode { private record CacheTreeTransition(LocalTimerMealyOutputSymbol output, CacheTreeNode target) { } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 15b23df2e..0d092f244 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -25,10 +25,10 @@ public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyCacheConsistencyTest.class); - private final LocalTimerMealyTreeCacheSUL sulCache; + private final LocalTimerMealyTreeSULCache sulCache; private final LocalTimerMealyModelParams modelParams; - public LocalTimerMealyCacheConsistencyTest(LocalTimerMealyTreeCacheSUL sulCache, LocalTimerMealyModelParams modelParams) { + LocalTimerMealyCacheConsistencyTest(LocalTimerMealyTreeSULCache sulCache, LocalTimerMealyModelParams modelParams) { this.sulCache = sulCache; this.modelParams = modelParams; } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java similarity index 91% rename from filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java rename to filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java index 01ece3bc3..b8200c1be 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeCacheSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java @@ -1,7 +1,8 @@ package de.learnlib.filter.cache.mmlt; -import de.learnlib.filter.cache.LocalTimerMealyCache; +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.filter.cache.LearningCache; import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -28,12 +29,13 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyTreeCacheSUL extends LocalTimerMealyCache implements GraphViewable, LearnerStatsProvider { +public class LocalTimerMealyTreeSULCache extends LocalTimerMealySUL implements LearningCache.LocalTimerMealyLearningCache, GraphViewable, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private final CacheTreeNode cacheRoot; private CacheTreeNode currentState; + private final LocalTimerMealyModelParams modelParams; private final LocalTimerMealyOutputSymbol silentOutput; private boolean cacheMiss; @@ -44,9 +46,10 @@ public void setStatsContainer(StatsContainer container) { this.stats = container; } - public LocalTimerMealyTreeCacheSUL(LocalTimerMealySUL delegate, O silentOutput) { + public LocalTimerMealyTreeSULCache(LocalTimerMealySUL delegate, LocalTimerMealyModelParams modelParams) { this.delegate = delegate; - this.silentOutput = new LocalTimerMealyOutputSymbol<>(silentOutput); + this.modelParams = modelParams; + this.silentOutput = new LocalTimerMealyOutputSymbol<>(modelParams.silentOutput()); // Init cache: this.cacheRoot = new CacheTreeNode<>(null, null); @@ -108,7 +111,7 @@ public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { if (currentState.getTimeout() > remaining) { // Split current timeout: - this.currentState = this.currentState.splitTimeout(remaining, silentOutput); + this.currentState = this.currentState.splitTimeout(remaining, this.silentOutput); return null; // no timer in this state } @@ -261,4 +264,9 @@ public List>> listAllWords() { return mealy.graphView(); } + @Override + public EquivalenceOracle.LocalTimerMealyEquivalenceOracle createCacheConsistencyTest() { + return new LocalTimerMealyCacheConsistencyTest<>(this, this.modelParams); + } + } From 11aab343389fff26c527c48641b3d7ba6f2be836 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 09:22:16 +0200 Subject: [PATCH 15/55] Multiple EQ tests for MMLTs can now respect the provided list of inputs for counterexamples. --- ...tarLocalTimerMealyCounterexampleTests.java | 2 +- .../mmlt/LocalTimerMealyRandomWpOracle.java | 18 +++++++---------- .../mmlt/LocalTimerMealySimulatorOracle.java | 20 +++++++++---------- .../equivalence/mmlt/ResetSearchOracle.java | 20 +++++++++---------- 4 files changed, 27 insertions(+), 33 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index 0658b03d6..961bc2a30 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -57,7 +57,7 @@ private static void learnModel(LocalTimerMealyTestUtil.Model // Now continue until arriving at an accurate model: System.out.println("Running to completion"); - LocalTimerMealySimulatorOracle simOracle = new LocalTimerMealySimulatorOracle<>(model.automaton()); + LocalTimerMealySimulatorOracle simOracle = new LocalTimerMealySimulatorOracle<>(model.automaton()); int round = 0; while (round < 100) { var hyp = learner.getHypothesisModel(); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java index ab27d8e81..bad704645 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -29,8 +29,6 @@ import de.learnlib.statistic.container.StatsContainer; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.common.util.string.AbstractPrintable; @@ -76,24 +74,22 @@ public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { - return findCounterExampleInternal(hypothesis); + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + return findCounterExampleInternal(hypothesis, inputs); } - private DefaultQuery, Word>> findCounterExampleInternal(LocalTimerMealy hypothesis) { + private DefaultQuery, Word>> findCounterExampleInternal(LocalTimerMealy hypothesis, Collection> inputs) { // Make expanded form of hypothesis: var hypSemModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(hypothesis); // Create a list of symbols (for faster access): - List> listAlphabet = new ArrayList<>(hypothesis.getUntimedAlphabet()); - listAlphabet.add(new TimeoutSymbol<>()); - listAlphabet.add(new TimeStepSymbol<>()); + List> listAlphabet = new ArrayList<>(inputs); // Identify global suffixes: - var globalSuffixes = Automata.characterizingSet(hypSemModel, hypSemModel.getInputAlphabet()); + var globalSuffixes = Automata.characterizingSet(hypSemModel, inputs); // Get list of prefixes in deterministic order (so we can reproduce experiments easily): - var locationCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis); + var locationCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis, listAlphabet); var prefixList = locationCover .values() .stream() @@ -150,7 +146,7 @@ private DefaultQuery, Word Reference model location type - * @param Hypothesis model location type - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param Input type for non-delaying inputs + * @param Output symbol type */ -public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { - private final LocalTimerMealy refModel; + private final LocalTimerMealy refModel; - public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { + public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { this.refModel = refModel; } - @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { - var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis); + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + List> listInputs = new ArrayList<>(inputs); + var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis, listInputs); if (separatingWord != null) { var sulOutput = refModel.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); return new DefaultQuery<>(Word.epsilon(), separatingWord, sulOutput); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 0c7ee5b60..66c94e839 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -43,7 +43,7 @@ *

* - Takes any prefix from a known location * - Appends a single time step. - * - Appends inputs of all inputs that self-loop in that location. + * - Appends inputs of all non-delaying inputs that self-loop in that location. * - Appends timeout. * * @param Input type for non-delaying inputs @@ -75,7 +75,7 @@ private List> getLoopingSymbols(S sour List> loopingInputs = new ArrayList<>(); for (var sym : alphabet) { if (!(sym instanceof NonDelayingInput ndi)) { - throw new AssertionError(); + continue; // only consider non-delaying inputs, as only these can perform local resets } var trans = hypothesis.getTransition(sourceLoc, ndi); @@ -89,21 +89,21 @@ private List> getLoopingSymbols(S sour } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, @Nullable Collection> ignored) { + public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { if (loopInsertPerc == 0) { return null; // oracle is disabled } - - return this.findCexInternal(hypothesis); + List> listInputs = new ArrayList<>(inputs); + return this.findCexInternal(hypothesis, listInputs); } private @Nullable DefaultQuery, Word>> findCexInternal - (LocalTimerMealy hypothesis) { + (LocalTimerMealy hypothesis, List> inputs) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: - var stateCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis); + var stateCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis, inputs); - // Only keep locations that have at least two stable configs: + // Only keep locations that have at least two stable configs (only these can have local resets): List>> prefixes = new ArrayList<>(); for (var loc : stateCover.keySet()) { if (!hypothesis.getSortedTimers(loc).isEmpty() && @@ -125,12 +125,10 @@ private List> getLoopingSymbols(S sour List>> chosenPrefixes = RandomUtil.sampleUnique(locPrefixRandom, prefixes, randPrefixes); - List> listAlphabet = new ArrayList<>(hypothesis.getUntimedAlphabet()); - for (var prefix : chosenPrefixes) { // Retrieve looping symbols: var sourceLoc = hypothesis.getSemantics().traceInputs(prefix).getLocation(); - var loopingInputs = getLoopingSymbols(sourceLoc, listAlphabet, hypothesis); + var loopingInputs = getLoopingSymbols(sourceLoc, inputs, hypothesis); if (loopingInputs.isEmpty()) { continue; // no loops } From 2cc45bab198716e0fa4c8373fbb7a872e0191b80 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 10:40:48 +0200 Subject: [PATCH 16/55] Added tests for the cache; cleaned-up some files. --- .../algorithm/LocalTimerMealyModelParams.java | 23 +---- .../symbol_filter/SymbolFilterResponse.java | 21 ----- .../cache/mmlt/LocalTimerMealyCacheTest.java | 94 +++++++++++++++++++ .../mmlt/LocalTimerMealyRandomWpOracle.java | 21 ----- .../mmlt/LocalTimerMealySimulatorOracle.java | 21 ----- .../equivalence/mmlt/ResetSearchOracle.java | 21 ----- .../mmlt/PerfectSymbolFilter.java | 21 ----- 7 files changed, 95 insertions(+), 127 deletions(-) create mode 100644 filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java index a4e9aa8e4..e33fd5978 100644 --- a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java +++ b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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; import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; @@ -31,7 +10,7 @@ * @param maxTimeoutWaitingTime Maximum waiting time for a timeout symbol * @param maxTimerQueryWaitingTime Maximum waiting time for timer queries * @param outputCombiner Function for combining simultaneously occurring outputs of timers - * @param + * @param Output symbol type */ public record LocalTimerMealyModelParams(O silentOutput, long maxTimeoutWaitingTime, diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java index 6c06e93ce..e0a5dd693 100644 --- a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java +++ b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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.symbol_filter; public enum SymbolFilterResponse { diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java new file mode 100644 index 000000000..dc6cfdb3e --- /dev/null +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java @@ -0,0 +1,94 @@ +package de.learnlib.filter.cache.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; +import de.learnlib.oracle.membership.TimedQueryOracle; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +import net.automatalib.common.util.random.RandomUtil; +import net.automatalib.word.Word; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +@Test +public class LocalTimerMealyCacheTest { + private CompactLocalTimerMealy buildBaseModel() { + var symbols = List.of("p1", "p2", "abort", "collect"); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> alphabet.add(new NonDelayingInput<>(s))); + + var model = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + + var s0 = model.addState(); + var s1 = model.addState(); + var s2 = model.addState(); + var s3 = model.addState(); + + model.setInitialState(s0); + + model.addTransition(s0, new NonDelayingInput<>("p1"), "go", s1); + model.addTransition(s1, new NonDelayingInput<>("abort"), "ok", s1); + model.addLocalReset(s1, new NonDelayingInput<>("abort")); + + model.addPeriodicTimer(s1, "a", 3, "part"); + model.addPeriodicTimer(s1, "b", 6, "noise"); + model.addOneShotTimer(s1, "c", 40, "done", s3); + + model.addTransition(s0, new NonDelayingInput<>("p2"), "go", s2); + model.addTransition(s2, new NonDelayingInput<>("abort"), "void", s3); + model.addOneShotTimer(s2, "d", 4, "done", s3); + + model.addTransition(s3, new NonDelayingInput<>("collect"), "void", s0); + + return model; + } + + /** + * Tests if the information in the cache is consistent with the output of the SUL. + */ + public void testCacheConsistency() { + Random random = new Random(100); + + var automaton = buildBaseModel(); + var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + + var sul = new LocalTimerMealySimulatorSUL<>(automaton); + var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); + var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); + var timeOracleWithoutCache = new TimedQueryOracle<>(sul, params); + + + var listAlphabet = new ArrayList<>(automaton.getSemantics().getInputAlphabet()); + + // Generate some random words and compare outputs of the cache, SUL, and automaton: + List>> words = new ArrayList<>(); + for (int i = 0; i < 500; i++) { + int maxLength = random.nextInt(1, 500); + var symbols = RandomUtil.sample(random, listAlphabet, maxLength); + var word = Word.fromList(symbols); + words.add(word); + + var cacheOutput = timeOracleWithCache.querySuffixOutput(Word.epsilon(), word); + var sulOutput = timeOracleWithoutCache.querySuffixOutput(Word.epsilon(), word); + var automatonOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), word); + + Assert.assertEquals(sulOutput, automatonOutput, "Automaton output does not match SUL output for word " + word); + Assert.assertEquals(cacheOutput, sulOutput, "Cache output does not match SUL output for word " + word); + } + + // Now that the cache contents have changed, ensure that the results are still correct: + for (var word : words) { + var cacheOutput = timeOracleWithCache.querySuffixOutput(Word.epsilon(), word); + var sulOutput = timeOracleWithoutCache.querySuffixOutput(Word.epsilon(), word); + + Assert.assertEquals(sulOutput, cacheOutput, "Cache output does not match SUL output for word " + word); + } + } +} diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java index bad704645..51be1240c 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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.oracle.equivalence.mmlt; import de.learnlib.oracle.EquivalenceOracle; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java index f428b6d7c..acd60c8ad 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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.oracle.equivalence.mmlt; import de.learnlib.oracle.EquivalenceOracle; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 66c94e839..4f37a5eca 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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.oracle.equivalence.mmlt; import de.learnlib.oracle.AbstractTimedQueryOracle; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java index 413731b54..eb0fadf5d 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java @@ -1,24 +1,3 @@ -/* - * Copyright (C) 2023-2024 Paul Kogel, TU Berlin - * - * THIS SOFTWARE IS INTENDED FOR ACADEMIC, RESEARCH AND EDUCATIONAL PURPOSES ONLY, - * MOST PROMINENTLY TO FACILITATE RESEARCH ON AUTOMATA LEARNING. FOR-PROFIT USE OF - * THIS SOFTWARE, INCLUDING, BUT NOT LIMITED TO, SELLING THE SOFTWARE ON ITS OWN OR - * AS PART OF TOOLS AND/OR SERVICES IS PROHIBITED. COMMERCIAL USE OR COMMERCIAL - * DISTRIBUTION OF THIS SOFTWARE OR OF ANY DERIVATES IS PROHIBITED. - * - * Apart from that, this software is licensed under the - * GNU Affero Public License version 3 (AGPLv3). - * - * https://www.gnu.org/licenses/agpl-3.0.txt - * - * 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.oracle.symbol_filters.mmlt; From 93cc1b156b61af01cf9884ca2bdd9904fad6c7ce Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 11:28:59 +0200 Subject: [PATCH 17/55] Added tests for the MMLT cache consistency test. --- .../learnlib/filter/cache/LearningCache.java | 1 + .../LocalTimerMealyCacheConsistencyTest.java | 39 +++++++++++----- .../cache/mmlt/LocalTimerMealyCacheTest.java | 46 ++++++++++++++++++- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java index 57b57785b..a34f1e023 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java @@ -99,6 +99,7 @@ interface MooreLearningCache extends LearningCache extends LearningCache, LocalTimerMealySemanticInputSymbol, Word>>{ /** * Lists all words that are currently in the cache. + * If a cached word is a prefix of another cached word, only the longer of them is returned. * * @return List of all stored words. */ diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 0d092f244..045a537f5 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -11,13 +11,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.List; +import java.util.*; /** * Searches for counterexamples by comparing the behavior of the hypothesis and the query cache. + * If there are multiple counterexamples, the shortest one is returned. * * @param Input type for non-delaying inputs * @param Output symbol type @@ -109,18 +107,18 @@ private DefaultQuery, Word= this.modelParams.maxTimeoutWaitingTime() || !combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { wbInput.append(new TimeoutSymbol<>()); if (combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { - // Reached max delay -> no timeout: + // Reached max delay -> waiting for any time will now produce no more timeouts: wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); } else { // Found non-silent output: wbOutput.append(new LocalTimerMealyOutputSymbol<>(combinedWaitTime, combinedOutput.getSymbol())); } } else { - // Reached end of word before max_delay OR exceeding max delay OR non-wait symbol -> ignore rest of this word: + // Reached end of word before max_delay OR non-wait symbol -> ignore rest of this word: if (symIdx < queryInput.length() - 1) { logger.warn("Ignoring at least one symbol during cache comparison."); } @@ -131,10 +129,25 @@ private DefaultQuery, Word(wbInput.toWord(), wbOutput.toWord()); } + private DefaultQuery, Word>> reduceToAllowedInputs(Set> allowedInputs, DefaultQuery, Word>> query) { + // Find the longest prefix with allowed inputs: + int prefixLength = 0; + while (prefixLength < query.getInput().length() && allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { + prefixLength++; + } + + if (prefixLength == query.getInput().length()) { + return query; // maximum length -> no need to reduce + } else { + return new DefaultQuery<>(query.getInput().subWord(0, prefixLength), query.getOutput().subWord(0, prefixLength)); + } + } + @Override public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { - // TODO only with the provided inputs! + Set> allowedInputs = new HashSet<>(inputs); + boolean allInputsConsidered = allowedInputs.containsAll(hypothesis.getSemantics().getInputAlphabet()); // Query all cached words: List>> cachedWords = this.sulCache.listAllWords(); @@ -147,12 +160,16 @@ private DefaultQuery, Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), convertedQuery.getInput()); + Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); - if (!hypOutput.equals(convertedQuery.getOutput())) { + if (!hypOutput.equals(reducedQuery.getOutput())) { // Hyp gives different output than cache (= SUL): - counterexamples.add(convertedQuery); + counterexamples.add(reducedQuery); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java index dc6cfdb3e..b0eb0810d 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java @@ -6,6 +6,8 @@ import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import net.automatalib.common.util.random.RandomUtil; @@ -14,6 +16,7 @@ import org.testng.annotations.Test; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.Random; @@ -53,7 +56,7 @@ private CompactLocalTimerMealy buildBaseModel() { /** * Tests if the information in the cache is consistent with the output of the SUL. */ - public void testCacheConsistency() { + public void testCacheAndSULConsistency() { Random random = new Random(100); var automaton = buildBaseModel(); @@ -91,4 +94,45 @@ public void testCacheConsistency() { Assert.assertEquals(sulOutput, cacheOutput, "Cache output does not match SUL output for word " + word); } } + + @Test + public void testCacheConsistencyTest() { + // Test if the cache consistency test works correctly: + var refAutomaton = buildBaseModel(); + var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + + var sul = new LocalTimerMealySimulatorSUL<>(refAutomaton); + var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); + var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); + + // Add word to cache: + Word> testWord = Word.fromSymbols( + new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeStepSymbol<>(), new TimeoutSymbol<>() + ); + timeOracleWithCache.querySuffixOutput(Word.epsilon(), testWord); + + // Create a bad hypothesis: + var badAutomaton = buildBaseModel(); + badAutomaton.removeTimer(2, "d"); + badAutomaton.addPeriodicTimer(2, "d", 4, "done"); + + // Query the cache for a counterexample: + Word> expectedCex = Word.fromSymbols( + new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>() + ); + + var cacheConsistencyTest = cacheSUL.createCacheConsistencyTest(); + var cex = cacheConsistencyTest.findCounterExample(badAutomaton, refAutomaton.getSemantics().getInputAlphabet()); + Assert.assertNotNull(cex); + Assert.assertEquals(cex.getInput(), expectedCex); + + // Now test with a reduced alphabet: + var symbols = List.of("p1", "abort", "collect"); // not p1 + GrowingMapAlphabet> reducedAlphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> reducedAlphabet.add(new NonDelayingInput<>(s))); + reducedAlphabet.add(new TimeoutSymbol<>()); + + // The only counterexample in the cache has the prefix p2, which is now omitted: + Assert.assertNull(cacheConsistencyTest.findCounterExample(badAutomaton, reducedAlphabet)); + } } From 86432b50ff385b328829258df102efd458773038 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 11:48:15 +0200 Subject: [PATCH 18/55] Made the MMLT SUL an interface with default methods. --- .../de/learnlib/sul/LocalTimerMealySUL.java | 31 +++++++++---------- .../learnlib/symbol_filter/SymbolFilter.java | 2 +- .../LocalTimerMealySimulatorSUL.java | 2 +- .../LocalTimerMealyCacheConsistencyTest.java | 4 +-- .../mmlt/LocalTimerMealyTreeSULCache.java | 2 +- .../filter/cache/mmlt/TimeoutReducerSUL.java | 2 +- .../sul/LocalTimerMealyStatsSUL.java | 2 +- 7 files changed, 22 insertions(+), 23 deletions(-) diff --git a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java index a77f04cc2..8ab564a7d 100644 --- a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java +++ b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java @@ -6,13 +6,12 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** - * A SUL with MMLT semantics. We use this type to interface with real systems and to - * simulate MMLT models. + * Interface for a SUL with MMLT semantics. * * @param Input type for non-delaying inputs * @param Output symbol type */ -public abstract class LocalTimerMealySUL { +public interface LocalTimerMealySUL { /** * Follows the provided input word, starting at the current system state. @@ -20,7 +19,7 @@ public abstract class LocalTimerMealySUL { * * @param input Input suffix. */ - public void follow(Word> input) { + default void follow(Word> input) { this.follow(input, -1); } @@ -28,9 +27,9 @@ public void follow(Word> input) { * Follows the provided input word, starting at the current configuration. * * @param input Input suffix. - * @param maxTimeout Max. timeout to use for timeoutSymbols. + * @param maxTimeout Max. waiting time to use for timeoutSymbols. */ - public void follow(Word> input, long maxTimeout) { + default void follow(Word> input, long maxTimeout) { for (var s : input) { if (s instanceof NonDelayingInput ndi) { this.step(ndi); @@ -48,24 +47,24 @@ public void follow(Word> input, long maxTi } /** - * Provides an input to the SUL and returns the observed output. + * Provides a non-delaying input to the SUL and returns the observed output. * * @param input Input * @return SUL output. */ - public abstract LocalTimerMealyOutputSymbol step(NonDelayingInput input); + LocalTimerMealyOutputSymbol step(NonDelayingInput input); /** * Waits until a timeout occurs or the provided time is reached. *

- * We may observe no timeout if either the waiting time is too small or there are no timers defined - * in the current location. + * We may observe no timeout if either the waiting time is too small or if the active location + * has no timers. * * @param maxTime Maximum waiting time. - * @return Observed timer output with waiting time, or null, if no timeout observed. + * @return Observed timer output with waiting time, or null, if no timeout was observed. */ @Nullable - public abstract LocalTimerMealyOutputSymbol timeoutStep(long maxTime); + LocalTimerMealyOutputSymbol timeoutStep(long maxTime); /** * Waits for one time unit and returns the observed output. @@ -74,7 +73,7 @@ public void follow(Word> input, long maxTi * The delay of this output is set to zero. */ @Nullable - public LocalTimerMealyOutputSymbol timeStep() { + default LocalTimerMealyOutputSymbol timeStep() { var res = this.timeoutStep(1); if (res != null) { return new LocalTimerMealyOutputSymbol<>(res.getSymbol()); @@ -88,7 +87,7 @@ public LocalTimerMealyOutputSymbol timeStep() { * @param input Waiting time. * @return Observed timeouts. Empty, if none. */ - public Word> collectTimeouts(TimeStepSequence input) { + default Word> collectTimeouts(TimeStepSequence input) { WordBuilder> wbOutput = new WordBuilder<>(); long remainingTime = input.getTimeSteps(); @@ -110,10 +109,10 @@ public Word> collectTimeouts(TimeStepSequence /** * Prepares the SUL for a new query. */ - public abstract void pre(); + void pre(); /** * Deinitializes the SUL. */ - public abstract void post(); + void post(); } diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java index d88c23043..6e535c969 100644 --- a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java +++ b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java @@ -21,7 +21,7 @@ public interface SymbolFilter { * Predictions may not be correct, i.e., an accepted symbol may be actually ignorable and an ignored symbol * may be actually not ignorable. * - * @param prefix Configuration prefix. May contain delay-symbols (= tau inputs). + * @param prefix Configuration prefix. May contain time steps. * @param symbol Queried transition * @return IGNORE if the symbol is considered ignorable, ACCEPT if it is not. */ diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java index bdcd17af0..8b2543947 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java @@ -16,7 +16,7 @@ * @param Non-delaying input type. * @param Output symbol type. */ -public class LocalTimerMealySimulatorSUL extends LocalTimerMealySUL { +public class LocalTimerMealySimulatorSUL implements LocalTimerMealySUL { private final LocalTimerMealy automaton; diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 045a537f5..03c805ae0 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -60,8 +60,8 @@ private DefaultQuery, Word Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyTreeSULCache extends LocalTimerMealySUL implements LearningCache.LocalTimerMealyLearningCache, GraphViewable, LearnerStatsProvider { +public class LocalTimerMealyTreeSULCache implements LocalTimerMealySUL, LearningCache.LocalTimerMealyLearningCache, GraphViewable, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private final CacheTreeNode cacheRoot; diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index b6b080a17..461d8d199 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -20,7 +20,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class TimeoutReducerSUL extends LocalTimerMealySUL implements LearnerStatsProvider { +public class TimeoutReducerSUL implements LocalTimerMealySUL, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private final long maxDelay; diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java index 2c668730c..cd6fdbcaf 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java @@ -16,7 +16,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyStatsSUL extends LocalTimerMealySUL implements LearnerStatsProvider { +public class LocalTimerMealyStatsSUL implements LocalTimerMealySUL, LearnerStatsProvider { private final LocalTimerMealySUL delegate; private StatsContainer stats; From f60e7f234a2eaf29baa2a2b5b1663b1629fe52cf Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 11:26:08 +0200 Subject: [PATCH 19/55] Made symbol filters more independent from MMLTs --- .../lstar/mmlt/LStarLocalTimerMealy.java | 8 +-- .../mmlt/LocalTimerMealyObservationTable.java | 9 +-- .../LocalTimerMealyCounterexampleHandler.java | 5 +- .../LStarLocalTimerMealyBenchmarkTests.java | 13 ++-- ...tarLocalTimerMealyCounterexampleTests.java | 2 +- .../learnlib/symbol_filter/SymbolFilter.java | 32 ++++----- .../symbol_filters/AcceptAllSymbolFilter.java | 24 +++++++ .../symbol_filters/CachedSymbolFilter.java | 44 +++++++++++++ .../symbol_filters/IgnoreAllSymbolFilter.java | 25 +++++++ .../symbol_filters/PerfectSymbolFilter.java | 34 ++++++++++ .../symbol_filters/RandomSymbolFilter.java | 52 +++++++++++++++ .../{mmlt => }/StatisticsSymbolFilter.java | 25 ++++--- .../mmlt/AcceptAllSymbolFilter.java | 25 ------- .../mmlt/CachedSymbolFilter.java | 47 -------------- .../mmlt/IgnoreAllSymbolFilter.java | 25 ------- .../LocalTimerMealyPerfectSymbolFilter.java | 32 +++++++++ .../LocalTimerMealyRandomSymbolFilter.java | 35 ++++++++++ ...LocalTimerMealyStatisticsSymbolFilter.java | 25 +++++++ .../mmlt/LocalTimerMealySymbolFilterUtil.java | 31 +++++++++ .../mmlt/PerfectSymbolFilter.java | 44 ------------- .../mmlt/RandomSymbolFilter.java | 65 ------------------- 21 files changed, 352 insertions(+), 250 deletions(-) create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java rename oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/{mmlt => }/StatisticsSymbolFilter.java (61%) delete mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java delete mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java delete mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java create mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java delete mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java delete mode 100644 oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index f3331e456..e5ebc3cc0 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -27,6 +27,7 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.alphabet.time.mmlt.TimeStepSequence; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; @@ -55,7 +56,7 @@ public class LStarLocalTimerMealy implements OTLearner, ? super Word>> closingStrategy; private final AbstractTimedQueryOracle timeOracle; - private final SymbolFilter symbolFilter; + private final SymbolFilter, NonDelayingInput> symbolFilter; private final LStarLocalTimerMealyHypDataContainer hypData; @@ -82,7 +83,7 @@ public LStarLocalTimerMealy(Alphabet> alph @NonNull List>> initialSuffixes, AbstractTimedQueryOracle timeOracle, - SymbolFilter symbolFilter) { + @NonNull SymbolFilter, NonDelayingInput> symbolFilter) { this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); } @@ -103,8 +104,7 @@ public LStarLocalTimerMealy(Alphabet> alph List>> initialSuffixes, ClosingStrategy, ? super Word>> closingStrategy, AbstractTimedQueryOracle timeOracle, - @NonNull - SymbolFilter symbolFilter, + @NonNull SymbolFilter, NonDelayingInput> symbolFilter, AcexAnalyzer analyzer) { this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java index 8a39c3981..163961d83 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java @@ -11,6 +11,7 @@ import net.automatalib.alphabet.time.mmlt.*; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,7 +38,7 @@ public class LocalTimerMealyObservationTable implements MutableObservation private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyObservationTable.class); - private final SymbolFilter symbolFilter; + private final SymbolFilter, NonDelayingInput> symbolFilter; private final Map>, LocationTimerInfo> timerInfoMap; // prefix -> timer info @@ -58,7 +59,7 @@ public class LocalTimerMealyObservationTable implements MutableObservation private final LocalTimerMealyOutputSymbol silentOutput; // used for symbol filtering public LocalTimerMealyObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, - SymbolFilter symbolFilter, O silentOutput) { + @NonNull SymbolFilter, NonDelayingInput> symbolFilter, O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; @@ -285,8 +286,8 @@ public List>>> findUnclosedTransi @Override public List>>> initialize(List>> initialShortPrefixes, - List>> initialSuffixes, - MembershipOracle, Word>> oracle) { + List>> initialSuffixes, + MembershipOracle, Word>> oracle) { if (isInitialized()) { throw new IllegalStateException("Called initialize, but there are already rows present"); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java index 442fe2fac..f2d4fdc70 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -16,6 +16,7 @@ import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,13 +32,13 @@ */ public class LocalTimerMealyCounterexampleHandler implements LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleHandler.class); - private final SymbolFilter symbolFilter; + private final SymbolFilter, NonDelayingInput> symbolFilter; private StatsContainer stats = new DummyStatsContainer(); protected final AbstractTimedQueryOracle timeOracle; private final LocalTimerMealyCounterexampleDecompositor decompositor; - public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, SymbolFilter symbolFilter) { + public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, NonDelayingInput> symbolFilter) { this.timeOracle = timeOracle; this.decompositor = new LocalTimerMealyCounterexampleDecompositor<>(timeOracle, acexAnalyzer); this.symbolFilter = symbolFilter; diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index 22f23aeab..84a3999f4 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -11,6 +11,10 @@ import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; +import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; +import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; +import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.*; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.StatsContainer; @@ -20,6 +24,7 @@ import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.word.Word; @@ -113,14 +118,14 @@ private static void learnModel(String name, LocalTimerMealy a suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // Configure symbol filter: - SymbolFilter filter = new AcceptAllSymbolFilter<>(); // pass-through + SymbolFilter, NonDelayingInput> filter = new AcceptAllSymbolFilter<>(); // pass-through switch (symbolFilterMode) { - case perfect -> filter = new PerfectSymbolFilter<>(automaton); - case random -> filter = new RandomSymbolFilter<>(automaton, 0.1, new Random(seed)); + case perfect -> filter = new LocalTimerMealyPerfectSymbolFilter<>(automaton); + case random -> filter = new LocalTimerMealyRandomSymbolFilter<>(automaton, 0.1, new Random(seed)); case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); } - filter = new StatisticsSymbolFilter<>(filter, automaton); + filter = new LocalTimerMealyStatisticsSymbolFilter<>(automaton, filter); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses var learner = new LStarLocalTimerMealy<>(alphabet, params, suffixes, timeOracle, filter); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index 961bc2a30..cf81228c1 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -3,7 +3,7 @@ import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; import de.learnlib.oracle.membership.TimedQueryOracle; -import de.learnlib.oracle.symbol_filters.mmlt.AcceptAllSymbolFilter; +import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java index 6e535c969..9af1f079d 100644 --- a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java +++ b/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java @@ -1,38 +1,40 @@ package de.learnlib.symbol_filter; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.word.Word; /** - * Interface for a symbol filter that can be used to speed-up the learning of MMLTs. + * Interface for a symbol filter. + * A symbol filter predicts whether a given transition is ignorable in a given state. + * This information can be used to avoid redundant queries. + * A symbol filter may answer incorrectly. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols */ -public interface SymbolFilter { +public interface SymbolFilter { /** - * Predicts whether the provided symbol is not ignorable in the configuration - * identified by the provided prefix. + * Predicts whether the provided symbol is ignorable in the state + * that is addressed by the given prefix. *

- * "Ignorable" means the symbol belongs to a silent self-loop. + * ignorable typically means that the symbol triggers a silent self-loop in the considered state. + * However, the semantics may vary depending on the concrete implementation. *

* Predictions may not be correct, i.e., an accepted symbol may be actually ignorable and an ignored symbol * may be actually not ignorable. * - * @param prefix Configuration prefix. May contain time steps. - * @param symbol Queried transition + * @param prefix State prefix. + * @param symbol Input of the queried transition. * @return IGNORE if the symbol is considered ignorable, ACCEPT if it is not. */ - SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol); + SymbolFilterResponse query(Word prefix, V symbol); /** * Sets the response of the filter for the given transition to the provided response. * - * @param prefix Configuration prefix. - * @param symbol Queried transition + * @param prefix State prefix. + * @param symbol Input of the transition that should be updated. * @param response New response */ - void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response); + void update(Word prefix, V symbol, SymbolFilterResponse response); } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java new file mode 100644 index 000000000..c972c5571 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java @@ -0,0 +1,24 @@ +package de.learnlib.oracle.symbol_filters; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.word.Word; + +/** + * A pass-through filter that accepts all inputs. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public class AcceptAllSymbolFilter implements SymbolFilter { + @Override + public SymbolFilterResponse query(Word prefix, V symbol) { + return SymbolFilterResponse.ACCEPT; + } + + @Override + public void update(Word prefix, V symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java new file mode 100644 index 000000000..7dc0e57d9 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java @@ -0,0 +1,44 @@ +package de.learnlib.oracle.symbol_filters; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.word.Word; + +import java.util.HashMap; +import java.util.Map; + +/** + * Wrapper for a symbol filter that caches previous responses + allows caller to update these. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public class CachedSymbolFilter implements SymbolFilter { + private final Map, Map> previousResponses; // prefix -> (input -> legal/ignore) + private final SymbolFilter delegate; + + public CachedSymbolFilter(SymbolFilter delegate) { + this.delegate = delegate; + this.previousResponses = new HashMap<>(); + } + + @Override + public SymbolFilterResponse query(Word prefix, V symbol) { + this.previousResponses.putIfAbsent(prefix, new HashMap<>()); + var oldResponse = this.previousResponses.get(prefix).get(symbol); + if (oldResponse != null) { + return (oldResponse) ? SymbolFilterResponse.ACCEPT : SymbolFilterResponse.IGNORE; + } + + var res = delegate.query(prefix, symbol); + this.update(prefix, symbol, res); + return res; + } + + @Override + public void update(Word prefix, V symbol, SymbolFilterResponse response) { + this.previousResponses.putIfAbsent(prefix, new HashMap<>()); + this.previousResponses.get(prefix).put(symbol, (response == SymbolFilterResponse.ACCEPT)); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java new file mode 100644 index 000000000..aebb9a6b8 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java @@ -0,0 +1,25 @@ +package de.learnlib.oracle.symbol_filters; + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.word.Word; + +/** + * A pass-through filter that ignores all inputs. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public class IgnoreAllSymbolFilter implements SymbolFilter { + @Override + public SymbolFilterResponse query(Word prefix, V symbol) { + return SymbolFilterResponse.IGNORE; + } + + @Override + public void update(Word prefix, V symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java new file mode 100644 index 000000000..d37da2ec7 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java @@ -0,0 +1,34 @@ +package de.learnlib.oracle.symbol_filters; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.word.Word; + +import java.util.Random; + +/** + * A symbol filter that answers all queries correctly. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public abstract class PerfectSymbolFilter implements SymbolFilter { + + protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); + + @Override + public SymbolFilterResponse query(Word prefix, V symbol) { + + if (isIgnorable(prefix, symbol) == SymbolFilterResponse.IGNORE) { + return SymbolFilterResponse.IGNORE; + } else { + return SymbolFilterResponse.ACCEPT; + } + } + + @Override + public void update(Word prefix, V symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java new file mode 100644 index 000000000..df301b561 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java @@ -0,0 +1,52 @@ +package de.learnlib.oracle.symbol_filters; + + +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.word.Word; + +import java.util.Random; + +/** + * A symbol filter that falsely answers a query with a specified probability. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public abstract class RandomSymbolFilter implements SymbolFilter { + + private final double inaccurateProb; + private final Random random; + + public RandomSymbolFilter(double inaccurateProb, Random random) { + if (inaccurateProb > 1 || inaccurateProb < 0) { + throw new IllegalArgumentException("Ratios must be between zero and 1 (inclusive)."); + } + + this.inaccurateProb = inaccurateProb; + this.random = random; + } + + protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); + + @Override + public SymbolFilterResponse query(Word prefix, V symbol) { + boolean ignorable = isIgnorable(prefix, symbol) == SymbolFilterResponse.IGNORE; + + // Randomly misclassify: + if (this.random.nextDouble() <= this.inaccurateProb) { + ignorable = !ignorable; + } + + if (ignorable) { + return SymbolFilterResponse.IGNORE; + } else { + return SymbolFilterResponse.ACCEPT; + } + } + + @Override + public void update(Word prefix, V symbol, SymbolFilterResponse response) { + throw new IllegalStateException("Not supported."); + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java similarity index 61% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java rename to oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java index dbdc13730..01112f980 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/StatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java @@ -1,38 +1,35 @@ -package de.learnlib.oracle.symbol_filters.mmlt; +package de.learnlib.oracle.symbol_filters; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.word.Word; /** * Collects various statistics on symbol filtering, including false accepts + false ignores. * - * @param Input type for non-delaying inputs + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols */ -public class StatisticsSymbolFilter implements SymbolFilter, LearnerStatsProvider { +public abstract class StatisticsSymbolFilter implements SymbolFilter, LearnerStatsProvider { - private final SymbolFilter delegate; - private final PerfectSymbolFilter perfectFilter; + private final SymbolFilter delegate; private StatsContainer stats = new DummyStatsContainer(); - - public StatisticsSymbolFilter(SymbolFilter delegate, LocalTimerMealy sulModel) { + public StatisticsSymbolFilter(SymbolFilter delegate) { this.delegate = delegate; - this.perfectFilter = new PerfectSymbolFilter<>(sulModel); } + protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); + @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { + public SymbolFilterResponse query(Word prefix, V symbol) { stats.increaseCounter("cnt_isf_queries", "Filter: queries"); SymbolFilterResponse filterResponse = this.delegate.query(prefix, symbol); - SymbolFilterResponse expectedResponse = this.perfectFilter.query(prefix, symbol); + SymbolFilterResponse expectedResponse = this.isIgnorable(prefix, symbol); // Count false ignores, rejects + correct predictions: if (filterResponse.equals(SymbolFilterResponse.ACCEPT)) { @@ -53,7 +50,7 @@ public SymbolFilterResponse query(Word> pr } @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { + public void update(Word prefix, V symbol, SymbolFilterResponse response) { delegate.update(prefix, symbol, response); } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java deleted file mode 100644 index 1353f7022..000000000 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/AcceptAllSymbolFilter.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.learnlib.oracle.symbol_filters.mmlt; - - -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.word.Word; - -/** - * A pass-through filter that accepts all inputs. - * - * @param Input type for non-delaying inputs - */ -public class AcceptAllSymbolFilter implements SymbolFilter { - @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { - return SymbolFilterResponse.ACCEPT; - } - - @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } -} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java deleted file mode 100644 index 394ba1cdf..000000000 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/CachedSymbolFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.learnlib.oracle.symbol_filters.mmlt; - - -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.word.Word; - -import java.util.HashMap; -import java.util.Map; - -/** - * Wrapper for a symbol filter that caches previous responses + allows caller to update these. - * - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public class CachedSymbolFilter implements SymbolFilter { - private final Map>, Boolean> previousResponses; // transition -> legal/ignore - private final SymbolFilter delegate; - - public CachedSymbolFilter(SymbolFilter delegate) { - this.delegate = delegate; - this.previousResponses = new HashMap<>(); - } - - @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { - var oldResponse = this.previousResponses.get(prefix.append(symbol)); - if (oldResponse != null) { - return (oldResponse) ? SymbolFilterResponse.ACCEPT : SymbolFilterResponse.IGNORE; - } - - var res = delegate.query(prefix, symbol); - this.previousResponses.put(prefix.append(symbol), res == SymbolFilterResponse.ACCEPT); - return res; - } - - @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { - if (!this.previousResponses.containsKey(prefix.append(symbol))) { - throw new IllegalArgumentException("Can only update response if already queried."); - } - this.previousResponses.put(prefix.append(symbol), (response == SymbolFilterResponse.ACCEPT)); - } -} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java deleted file mode 100644 index 2d3530be3..000000000 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/IgnoreAllSymbolFilter.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.learnlib.oracle.symbol_filters.mmlt; - -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.word.Word; - -/** - * A symbol filter that ignores all symbols. - * - * @param Input type for non-delaying inputs - */ -public class IgnoreAllSymbolFilter implements SymbolFilter { - @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { - return SymbolFilterResponse.IGNORE; - } - - @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } - -} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java new file mode 100644 index 000000000..3c6fae763 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java @@ -0,0 +1,32 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + + +import de.learnlib.oracle.symbol_filters.PerfectSymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +/** + * A symbol filter for MMLTs that correctly accepts and ignores all transitions + * that silently self-loop. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyPerfectSymbolFilter extends PerfectSymbolFilter, NonDelayingInput> { + + private final LocalTimerMealy automaton; + + public LocalTimerMealyPerfectSymbolFilter(LocalTimerMealy automaton) { + this.automaton = automaton; + } + + @Override + protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + } + +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java new file mode 100644 index 000000000..cd627496f --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java @@ -0,0 +1,35 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + + +import de.learnlib.oracle.symbol_filters.RandomSymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +import java.util.Random; + +/** + * A symbol filter that falsely answers a query with a specified probability. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class LocalTimerMealyRandomSymbolFilter extends RandomSymbolFilter, NonDelayingInput> { + + private final LocalTimerMealy automaton; + + public LocalTimerMealyRandomSymbolFilter(LocalTimerMealy automaton, + double inaccurateProb, Random random) { + super(inaccurateProb, random); + this.automaton = automaton; + } + + + @Override + protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + } +} \ No newline at end of file diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java new file mode 100644 index 000000000..e37118c6b --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java @@ -0,0 +1,25 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + +import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +public class LocalTimerMealyStatisticsSymbolFilter extends StatisticsSymbolFilter, NonDelayingInput> { + + private final LocalTimerMealy automaton; + + public LocalTimerMealyStatisticsSymbolFilter(LocalTimerMealy automaton, SymbolFilter, NonDelayingInput> delegate) { + super(delegate); + this.automaton = automaton; + } + + @Override + protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + } + +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java new file mode 100644 index 000000000..07874ba93 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java @@ -0,0 +1,31 @@ +package de.learnlib.oracle.symbol_filters.mmlt; + +import de.learnlib.symbol_filter.SymbolFilterResponse; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.word.Word; + +class LocalTimerMealySymbolFilterUtil { + + /** + * Returns IGNORE if the provided input triggers a transition that silently self-loops, + * and ACCEPT otherwise. + * + * @param automaton Automaton + * @param prefix State prefix + * @param symbol Input symbol + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + * @return IGNORE for silent self-loops, ACCEPT otherwise. + */ + static SymbolFilterResponse isIgnorable(LocalTimerMealy automaton, Word> prefix, NonDelayingInput symbol) { + var targetConfig = automaton.getSemantics().traceInputs(prefix); + var trans = automaton.getSemantics().getTransition(targetConfig, symbol); + + boolean ignorable = trans.output().equals(automaton.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target()); + + return ignorable ? SymbolFilterResponse.IGNORE : SymbolFilterResponse.ACCEPT; + } +} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java deleted file mode 100644 index eb0fadf5d..000000000 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/PerfectSymbolFilter.java +++ /dev/null @@ -1,44 +0,0 @@ -package de.learnlib.oracle.symbol_filters.mmlt; - - -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.word.Word; - -/** - * A symbol filter that correctly accepts and ignores all transitions. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public class PerfectSymbolFilter implements SymbolFilter { - - private final LocalTimerMealy sulModel; - - public PerfectSymbolFilter(LocalTimerMealy sulModel) { - this.sulModel = sulModel; - } - - @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { - // Check if silent self-loop: - - var targetConfig = this.sulModel.getSemantics().traceInputs(prefix); - var trans = this.sulModel.getSemantics().getTransition(targetConfig, symbol); - - if (trans.output().equals(sulModel.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target())) { - return SymbolFilterResponse.IGNORE; - } else { - return SymbolFilterResponse.ACCEPT; - } - } - - @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } -} diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java deleted file mode 100644 index f5216d52f..000000000 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/RandomSymbolFilter.java +++ /dev/null @@ -1,65 +0,0 @@ -package de.learnlib.oracle.symbol_filters.mmlt; - - -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.word.Word; - -import java.util.Random; - -/** - * A symbol filter that falsely answers a query with a specified probability. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public class RandomSymbolFilter implements SymbolFilter { - - private final double inaccurateProb; - private final Random random; - private final LocalTimerMealy sulModel; - - public RandomSymbolFilter(LocalTimerMealy sulModel, - double inaccurateProb, Random random) { - if (inaccurateProb > 1 || inaccurateProb < 0) { - throw new IllegalArgumentException("Ratios must be between zero and 1 (inclusive)."); - } - - this.inaccurateProb = inaccurateProb; - this.random = random; - - this.sulModel = sulModel; - } - - private boolean isSilentSelfLoop(Word> prefix, NonDelayingInput symbol) { - var targetConfig = this.sulModel.getSemantics().traceInputs(prefix); - var trans = this.sulModel.getSemantics().getTransition(targetConfig, symbol); - return trans.output().equals(sulModel.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target()); - } - - @Override - public SymbolFilterResponse query(Word> prefix, NonDelayingInput symbol) { - // Check if silent self-loop: - boolean ignorable = isSilentSelfLoop(prefix, symbol); - - // Randomly misclassify: - if (this.random.nextDouble() <= this.inaccurateProb) { - ignorable = !ignorable; - } - - if (ignorable) { - return SymbolFilterResponse.IGNORE; - } else { - return SymbolFilterResponse.ACCEPT; - } - } - - @Override - public void update(Word> prefix, NonDelayingInput symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } -} From e76e954a63f717926c28e25b0d30b4e0d1fba6d3 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 12:17:54 +0200 Subject: [PATCH 20/55] StatisticsSymbolFilter has stats container as constructor parameter. --- .../lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java | 3 +-- .../oracle/symbol_filters/StatisticsSymbolFilter.java | 3 ++- .../mmlt/LocalTimerMealyStatisticsSymbolFilter.java | 5 +++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index 84a3999f4..b31e41008 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -14,7 +14,6 @@ import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; -import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.*; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.StatsContainer; @@ -125,7 +124,7 @@ private static void learnModel(String name, LocalTimerMealy a case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); } - filter = new LocalTimerMealyStatisticsSymbolFilter<>(automaton, filter); + filter = new LocalTimerMealyStatisticsSymbolFilter<>(automaton, filter, stats); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses var learner = new LStarLocalTimerMealy<>(alphabet, params, suffixes, timeOracle, filter); diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java index 01112f980..221c98936 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java @@ -18,8 +18,9 @@ public abstract class StatisticsSymbolFilter implements SymbolFilter private final SymbolFilter delegate; private StatsContainer stats = new DummyStatsContainer(); - public StatisticsSymbolFilter(SymbolFilter delegate) { + public StatisticsSymbolFilter(SymbolFilter delegate, StatsContainer stats) { this.delegate = delegate; + this.stats = stats; } protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java index e37118c6b..5323869a0 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java @@ -1,6 +1,7 @@ package de.learnlib.oracle.symbol_filters.mmlt; import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; +import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; @@ -12,8 +13,8 @@ public class LocalTimerMealyStatisticsSymbolFilter extends StatisticsSy private final LocalTimerMealy automaton; - public LocalTimerMealyStatisticsSymbolFilter(LocalTimerMealy automaton, SymbolFilter, NonDelayingInput> delegate) { - super(delegate); + public LocalTimerMealyStatisticsSymbolFilter(LocalTimerMealy automaton, SymbolFilter, NonDelayingInput> delegate, StatsContainer stats) { + super(delegate, stats); this.automaton = automaton; } From 61e9b1973e0db4c8c7cf8be236ec9b48d9d4272c Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 13:35:32 +0200 Subject: [PATCH 21/55] Moved several MMLT examples to test-support. --- .../lstar/mmlt/LStarLocalTimerMealy.java | 4 +- .../LStarLocalTimerMealyBenchmarkTests.java | 13 +- ...tarLocalTimerMealyCounterexampleTests.java | 15 ++- .../lstar/mmlt/LocalTimerMealyTestUtil.java | 23 ++-- .../algorithm/LocalTimerMealyModelParams.java | 79 ++++++++++-- .../equivalence/RandomWMethodEQOracle.java | 2 +- .../equivalence/RandomWpMethodEQOracle.java | 2 +- test-support/learning-examples/pom.xml | 4 + .../example/mmlt/LocalTimerMealyExamples.java | 121 ++++++++++++++++++ .../example/mmlt/LocalTimerMealyModel.java | 20 +++ .../src/main/java/module-info.java | 1 + .../src/main}/resources/mmlt/HVAC.dot | 0 .../src/main}/resources/mmlt/Oven.dot | 0 .../src/main}/resources/mmlt/SCTP.dot | 0 .../src/main/resources/mmlt/WM.dot | 0 .../src/main}/resources/mmlt/WSN.dot | 0 .../main}/resources/mmlt/sensor_collector.dot | 0 17 files changed, 252 insertions(+), 32 deletions(-) create mode 100644 test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java create mode 100644 test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java rename {algorithms/active/lstar/src/test => test-support/learning-examples/src/main}/resources/mmlt/HVAC.dot (100%) rename {algorithms/active/lstar/src/test => test-support/learning-examples/src/main}/resources/mmlt/Oven.dot (100%) rename {algorithms/active/lstar/src/test => test-support/learning-examples/src/main}/resources/mmlt/SCTP.dot (100%) rename algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv => test-support/learning-examples/src/main/resources/mmlt/WM.dot (100%) rename {algorithms/active/lstar/src/test => test-support/learning-examples/src/main}/resources/mmlt/WSN.dot (100%) rename {algorithms/active/lstar/src/test => test-support/learning-examples/src/main}/resources/mmlt/sensor_collector.dot (100%) diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index e5ebc3cc0..2622abf45 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -73,7 +73,7 @@ public class LStarLocalTimerMealy implements OTLearner> alph * Instantiates a new Rivest-Schapire learner for MMLTs. * * @param alphabet Input alphabet for the semantic automaton - * @param modelParams Model parameters + * @param modelParams LocalTimerMealyModel parameters * @param initialSuffixes Initial set of suffixes. May be empty. * @param closingStrategy Closing strategy for the observation table. * @param timeOracle The output query oracle for MMLTs. diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index b31e41008..6eaca933d 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -19,6 +19,7 @@ import de.learnlib.statistic.container.StatsContainer; import de.learnlib.sul.LocalTimerMealySUL; import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; @@ -85,7 +86,7 @@ private static void learnModel(String name, LocalTimerMealy a // Add some stats: var stats = new MapStatsContainer(); - stats.addTextInfo("Model", null, name); + stats.addTextInfo("LocalTimerMealyModel", null, name); stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getUntimedAlphabet().size()); @@ -139,8 +140,10 @@ private static void learnModel(String name, LocalTimerMealy a public void learnExamplesNoFilter() { for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.none, 100, true); + learnModel(model.name(), model.automaton(), model.params(), FilterMode.none, 100, true); } + LocalTimerMealyExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.none, 100, true)); } @Test @@ -149,6 +152,8 @@ public void learnExamplesIgnoreAllFilter() { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100, true); } + LocalTimerMealyExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.ignore_all, 100, true)); } @Test @@ -157,6 +162,8 @@ public void learnExamplesPerfectFilter() { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100, true); } + LocalTimerMealyExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.perfect, 100, true)); } @Test @@ -165,6 +172,8 @@ public void learnExamplesRandomFilter() { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100, true); } + LocalTimerMealyExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.random, 100, true)); } } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index cf81228c1..edd3bd9f1 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -6,6 +6,8 @@ import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; @@ -26,7 +28,7 @@ @Test public class LStarLocalTimerMealyCounterexampleTests { - private static void learnModel(LocalTimerMealyTestUtil.Model model, List>> counterexamples) { + private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { GrowingAlphabet> alphabet = new GrowingMapAlphabet<>(); model.automaton().getUntimedAlphabet().forEach(alphabet::addSymbol); @@ -129,7 +131,7 @@ public void testRecursiveDecomp() { @Test public void testMissingDiscriminators() { - var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot"); + var model = LocalTimerMealyExamples.SensorCollector(); // Missing discriminator at non-del in stable config: List>> cex1 = List.of( @@ -155,7 +157,8 @@ public void testMissingDiscriminators() { @Test public void testMissingResets() { - var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 40); + var model = LocalTimerMealyExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(40); // Missing reset in stable config: List>> cex1 = List.of( @@ -185,7 +188,8 @@ public void testMissingResets() { @Test public void testMissingOneShotModelB() { // Setting max waiting = 6 -> all inferred timers are periodic: - var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 6); + var model = LocalTimerMealyExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(6); // Missing one-shot via bad return to entry: List>> cex1 = List.of( @@ -212,7 +216,8 @@ public void testMissingOneShotModelB() { @Test public void testMissingOneShotModelA() { - var model = LocalTimerMealyTestUtil.automatonFromFile("sensor_collector.dot", 40); + var model = LocalTimerMealyExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(40); // Missing one-shot via bad output: List>> cex1 = List.of( diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java index 765669919..1360c54f4 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -1,6 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt; import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.serialization.dot.GraphDOT; @@ -20,12 +21,11 @@ /** * Utility class for loading MMLTs from resources and printing them. */ -class LocalTimerMealyTestUtil { - - record Model(LocalTimerMealy automaton, LocalTimerMealyModelParams params) { - - } +public class LocalTimerMealyTestUtil { + /** + * Prints the provided MMLT to stdout. + */ static void printModel(LocalTimerMealy model) { try { GraphDOT.write(model.transitionGraphView(true, true), System.out); @@ -33,6 +33,9 @@ static void printModel(LocalTimerMealy model) { } } + /** + * Lists all MMLT models in the resources directory. + */ static List listModelFiles() { var models = new ArrayList(); try { @@ -50,7 +53,7 @@ static List listModelFiles() { return models; } - static Model automatonFromFile(String name) { + static LocalTimerMealyModel automatonFromFile(String name) { return automatonFromFile(name, -1); } @@ -61,18 +64,14 @@ static Model automatonFromFile(String name) { * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. * @return The automaton model. */ - static Model automatonFromFile(String name, int maxTimerQueryWaiting) { + static LocalTimerMealyModel automatonFromFile(String name, int maxTimerQueryWaiting) { var modelResource = LocalTimerMealyTestUtil.class.getResource("/mmlt/" + name); var automaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(modelResource.getFile()), "void", StringSymbolCombiner.getInstance()); long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? maxTimerQueryWaiting : LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; - if (name.contains("SCTP")) { - maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time - } - - return new Model<>(automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); + return new LocalTimerMealyModel<>(name, automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); } } diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java index e33fd5978..3cf87babc 100644 --- a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java +++ b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java @@ -2,18 +2,79 @@ import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; +import java.util.Objects; + /** * Model-specific parameters for the MMLT-learner. * These are used by various filters, oracles, and the MMLT simulator. * - * @param silentOutput Silent output symbol - * @param maxTimeoutWaitingTime Maximum waiting time for a timeout symbol - * @param maxTimerQueryWaitingTime Maximum waiting time for timer queries - * @param outputCombiner Function for combining simultaneously occurring outputs of timers - * @param Output symbol type + * @param Output symbol type */ -public record LocalTimerMealyModelParams(O silentOutput, - long maxTimeoutWaitingTime, - long maxTimerQueryWaitingTime, - AbstractSymbolCombiner outputCombiner) { +public final class LocalTimerMealyModelParams { + private final O silentOutput; + private final AbstractSymbolCombiner outputCombiner; + private final long maxTimeoutWaitingTime; + private long maxTimerQueryWaitingTime; + + /** + * @param silentOutput Silent output symbol + * @param maxTimeoutWaitingTime Maximum waiting time to wait for a timeout in any configuration + * @param maxTimerQueryWaitingTime Maximum waiting time for timer queries + * @param outputCombiner Function for combining simultaneously occurring outputs of timers + */ + public LocalTimerMealyModelParams(O silentOutput, + long maxTimeoutWaitingTime, + long maxTimerQueryWaitingTime, + AbstractSymbolCombiner outputCombiner) { + this.silentOutput = silentOutput; + this.maxTimeoutWaitingTime = maxTimeoutWaitingTime; + this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; + this.outputCombiner = outputCombiner; + } + + public O silentOutput() { + return silentOutput; + } + + public long maxTimeoutWaitingTime() { + return maxTimeoutWaitingTime; + } + + public long maxTimerQueryWaitingTime() { + return maxTimerQueryWaitingTime; + } + + public AbstractSymbolCombiner outputCombiner() { + return outputCombiner; + } + + public void setMaxTimerQueryWaitingTime(long maxTimerQueryWaitingTime) { + this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (LocalTimerMealyModelParams) obj; + return Objects.equals(this.silentOutput, that.silentOutput) && + this.maxTimeoutWaitingTime == that.maxTimeoutWaitingTime && + this.maxTimerQueryWaitingTime == that.maxTimerQueryWaitingTime && + Objects.equals(this.outputCombiner, that.outputCombiner); + } + + @Override + public int hashCode() { + return Objects.hash(silentOutput, maxTimeoutWaitingTime, maxTimerQueryWaitingTime, outputCombiner); + } + + @Override + public String toString() { + return "LocalTimerMealyModelParams[" + + "silentOutput=" + silentOutput + ", " + + "maxTimeoutWaitingTime=" + maxTimeoutWaitingTime + ", " + + "maxTimerQueryWaitingTime=" + maxTimerQueryWaitingTime + ", " + + "outputCombiner=" + outputCombiner + ']'; + } + } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java index 7cba476b0..e09d84089 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java @@ -44,7 +44,7 @@ /** * Implements an equivalence test based on a randomized version of the W-method as described in Complementing Model Learning with Mutation-Based Fuzzing by Rick + * href="https://arxiv.org/abs/1611.02429">Complementing LocalTimerMealyModel Learning with Mutation-Based Fuzzing by Rick * Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a * sampling implementation: *

    diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java index 2b525f98a..799f8ec09 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java @@ -45,7 +45,7 @@ /** * Implements an equivalence test based on a randomized version of the W(p)-method as described in Complementing Model Learning with Mutation-Based Fuzzing by Rick + * href="https://arxiv.org/abs/1611.02429">Complementing LocalTimerMealyModel Learning with Mutation-Based Fuzzing by Rick * Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a * sampling implementation: *
      diff --git a/test-support/learning-examples/pom.xml b/test-support/learning-examples/pom.xml index 87a2d91f6..a71c37641 100644 --- a/test-support/learning-examples/pom.xml +++ b/test-support/learning-examples/pom.xml @@ -75,5 +75,9 @@ limitations under the License. org.testng testng + + net.automatalib + automata-serialization-dot + diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java new file mode 100644 index 000000000..1bfa66824 --- /dev/null +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java @@ -0,0 +1,121 @@ +package de.learnlib.testsupport.example.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +import net.automatalib.serialization.dot.LocalTimerMealyGraphvizParser; +import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; + +import java.io.File; +import java.util.List; + +public class LocalTimerMealyExamples { + + public static List> getAll() { + return List.of( + HVAC(), SCTP(), SensorCollector(), WM(), Oven(), WSN()); + } + + /** + * Returns an MMLT model of an HVAC system. + *

      + * The system has been adapted from: Taylor and Taylor: Patterns in the Machine + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel HVAC() { + return automatonFromFile("HVAC"); + } + + /** + * Returns an MMLT model of an endpoint in the stream control and transmission protocol. + *

      + * The model has been adapted from: Stewart et al.: Stream Control Transmission Protocol (RFC 9260, Figure 3) + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel SCTP() { + return automatonFromFile("SCTP"); + } + + /** + * Returns an MMLT model of a sensor collector. + *

      + * The sensor measures particulate matter and ambient noise. + * This program automatically ends after some time. The program may be restarted at any time. + * Alternatively, a self-check program can be entered. This also ends after some time and may be aborted. + * At the end of either program, the collected data may be collected. + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel SensorCollector() { + return automatonFromFile("sensor_collector"); + } + + /** + * Returns an MMLT model of a washing machine. + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel WM() { + return automatonFromFile("WM"); + } + + /** + * Returns an MMLT model of an oven with a time-controlled baking program. + *

      + * After powering the oven on, the oven remains idle until the program is started. + * During the program, the oven regularly measures and adjusts the temperature. + * At the end of the program, an alarm sounds. Then, the user may extend the program. + * If not extended, the program ends either when the user opens the door, presses a button, or a timeout occurs. + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel Oven() { + return automatonFromFile("Oven"); + } + + /** + * Returns an MMLT model of a wireless sensor node. + *

      + * The node regularly collects and transmits data. If the battery is low, no data is transmitted. Then, + * a user may collect the data manually. + * The node can be shut down at any time. If the battery is empty, it is shut down automatically. + * + * @return LocalTimerMealyModel + */ + public static LocalTimerMealyModel WSN() { + return automatonFromFile("WSN"); + } + + // =================================== + + /** + * Loads the automaton model with the provided resource name. + * Also infers suitable model parameters: + * - Maximum time to wait for a timeout in any configuration. + * - Maximum waiting time for timer queries. This must be at least the max. time for a timeout. + * We choose twice the maximum value of any timer in the model. When inferring a timer with one of these values, + * the learner has the chance to observe its timeout at least twice. This increases the chance of observing non-periodic behavior. + * + */ + static LocalTimerMealyModel automatonFromFile(String name) { + + net.automatalib.automaton.time.mmlt.LocalTimerMealy automaton; + try (var modelResource = LocalTimerMealyExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { + automaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(modelResource, "void", StringSymbolCombiner.getInstance()); + } catch (Exception ex) { + throw new RuntimeException("Failed to load automaton from resource " + name, ex); + } + + long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; + + if (name.contains("SCTP")) { + maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + } + + return new LocalTimerMealyModel<>(name, automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); + } + + +} diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java new file mode 100644 index 000000000..07cba765e --- /dev/null +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java @@ -0,0 +1,20 @@ +package de.learnlib.testsupport.example.mmlt; + +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; + +/** + * Convenience class for storing a name, an automaton and model parameters. + * + * @param name Automaton name + * @param automaton MMLT + * @param params Model parameters + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public record LocalTimerMealyModel(String name, + LocalTimerMealy automaton, + LocalTimerMealyModelParams params) { + +} diff --git a/test-support/learning-examples/src/main/java/module-info.java b/test-support/learning-examples/src/main/java/module-info.java index ac70dd871..41817450a 100644 --- a/test-support/learning-examples/src/main/java/module-info.java +++ b/test-support/learning-examples/src/main/java/module-info.java @@ -37,6 +37,7 @@ requires net.automatalib.serialization.learnlibv2; requires net.automatalib.util; requires org.slf4j; + requires net.automatalib.serialization.dot; // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static org.checkerframework.checker.qual; diff --git a/algorithms/active/lstar/src/test/resources/mmlt/HVAC.dot b/test-support/learning-examples/src/main/resources/mmlt/HVAC.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/HVAC.dot rename to test-support/learning-examples/src/main/resources/mmlt/HVAC.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/Oven.dot b/test-support/learning-examples/src/main/resources/mmlt/Oven.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/Oven.dot rename to test-support/learning-examples/src/main/resources/mmlt/Oven.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/SCTP.dot b/test-support/learning-examples/src/main/resources/mmlt/SCTP.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/SCTP.dot rename to test-support/learning-examples/src/main/resources/mmlt/SCTP.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv b/test-support/learning-examples/src/main/resources/mmlt/WM.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/WashingMachine.gv rename to test-support/learning-examples/src/main/resources/mmlt/WM.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/WSN.dot b/test-support/learning-examples/src/main/resources/mmlt/WSN.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/WSN.dot rename to test-support/learning-examples/src/main/resources/mmlt/WSN.dot diff --git a/algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot b/test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot similarity index 100% rename from algorithms/active/lstar/src/test/resources/mmlt/sensor_collector.dot rename to test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot From be3dac3c789d3b9d692319a6901d6aba2f282a11 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 14:37:39 +0200 Subject: [PATCH 22/55] Added an example for learning MMLTs; added module info for symbol filter module. --- .../lstar/src/main/java/module-info.java | 1 + examples/pom.xml | 4 + .../de/learnlib/example/mmlt/Example1.java | 137 ++++++++++++++++++ examples/src/main/java/module-info.java | 1 + .../src/main/java/module-info.java | 39 +++++ .../src/main/java/module-info.java | 1 + 6 files changed, 183 insertions(+) create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/Example1.java create mode 100644 oracles/symbol-filters/src/main/java/module-info.java diff --git a/algorithms/active/lstar/src/main/java/module-info.java b/algorithms/active/lstar/src/main/java/module-info.java index 5ec25fb05..c4e6a3c50 100644 --- a/algorithms/active/lstar/src/main/java/module-info.java +++ b/algorithms/active/lstar/src/main/java/module-info.java @@ -54,4 +54,5 @@ exports de.learnlib.algorithm.lstar.moore; exports de.learnlib.algorithm.malerpnueli; exports de.learnlib.algorithm.rivestschapire; + exports de.learnlib.algorithm.lstar.mmlt; } diff --git a/examples/pom.xml b/examples/pom.xml index 1afeacbc7..1dd88ee0a 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -178,6 +178,10 @@ limitations under the License. org.mockito mockito-core + + de.learnlib + learnlib-symbol-filters + diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java new file mode 100644 index 000000000..8f701d297 --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -0,0 +1,137 @@ +package de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; +import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeSULCache; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyEQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyRandomWpOracle; +import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; +import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.LocalTimerMealyRandomSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.LocalTimerMealyStatisticsSymbolFilter; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.sul.LocalTimerMealySUL; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; +import de.learnlib.util.statistic.container.MapStatsContainer; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.serialization.dot.GraphDOT; +import net.automatalib.word.Word; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +/** + * This example shows how to learn a Mealy machine with local timers, + * an automaton model for real-time systems. + */ +public class Example1 { + + public static void main(String[] args) { + var model = LocalTimerMealyExamples.SensorCollector(); + + // We first create a statistics container. + // This container will store various statistical data during learning: + var stats = new MapStatsContainer(); + stats.addTextInfo("LocalTimerMealyModel", null, model.name()); + stats.setCounter("original_locs", "Locations in original", model.automaton().getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", model.automaton().getUntimedAlphabet().size()); + + // ====================== + // Set up the pipeline: + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addAll(model.automaton().getUntimedAlphabet()); + + // We use a simulator SUL to simulate our automaton: + var sul = new LocalTimerMealySimulatorSUL<>(model.automaton()); + + // We count all operations that are performed on the SUL with a stats-SUL: + var statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); + + // We use a cache to avoid redundant operations: + var cacheSUL = new LocalTimerMealyTreeSULCache<>(statsAfterCache, model.params()); + cacheSUL.setStatsContainer(stats); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.params().maxTimeoutWaitingTime(), stats); + + // We use a query oracle to answer queries from the learner: + var timeOracle = new TimedQueryOracle<>(toReducerSul, model.params()); + + // We use a chain of different equivalence oracles: + LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); + chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); + chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, 100, 1.0, 1.0)); + chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, 100, 6, 12, 100)); + chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(model.automaton())); // ensure that we eventually find an accurate model + chainOracle.setStatsContainer(stats); + + // Set up our L* learner: + List>> suffixes = new ArrayList<>(); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + // A symbol filter allows us to reduce queries by exploiting prior knowledge. + // For this example, we use a RandomSymbolFilter. This filter correctly predicts + // whether a transition silently self-loops with an accuracy of 90%: + SymbolFilter, NonDelayingInput> filter = + new LocalTimerMealyRandomSymbolFilter<>(model.automaton(), 0.1, new Random(100)); + + filter = new LocalTimerMealyStatisticsSymbolFilter<>(model.automaton(), filter, stats); + filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + + var learner = new LStarLocalTimerMealy<>(alphabet, model.params(), suffixes, timeOracle, filter); + learner.setStatsContainer(stats); + + // Start learning: + runExperiment(learner, chainOracle, stats, 100); + } + + private static void runExperiment(LStarLocalTimerMealy learner, + EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, + StatsContainer stats, int maxRounds) { + stats.startOrResumeClock("learningRt", "Processing time"); + learner.startLearning(); + + var hyp = learner.getHypothesisModel(); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", "CEX queries"); + + int roundCount = 1; + while (cex != null && roundCount < maxRounds) { + learner.refineHypothesis(cex); + hyp = learner.getHypothesisModel(); + cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", null); + roundCount += 1; + } + stats.pauseClock("learningRt"); + + final var finalHypothesis = learner.getHypothesisModel(); + + // Add some more stats: + stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); + + // Print final result + statistics: + stats.printStats(); + + System.out.println("Final hypothesis:"); + try { + GraphDOT.write(finalHypothesis.transitionGraphView(true, true), System.out); + } catch (IOException ignored) { + } + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + + } +} diff --git a/examples/src/main/java/module-info.java b/examples/src/main/java/module-info.java index 30cc747eb..529134784 100644 --- a/examples/src/main/java/module-info.java +++ b/examples/src/main/java/module-info.java @@ -39,6 +39,7 @@ requires de.learnlib.oracle.emptiness; requires de.learnlib.oracle.equivalence; requires de.learnlib.oracle.membership; + requires de.learnlib.oracle.symbol_filters; requires de.learnlib.oracle.parallelism; requires de.learnlib.oracle.property; requires de.learnlib.testsupport.example; diff --git a/oracles/symbol-filters/src/main/java/module-info.java b/oracles/symbol-filters/src/main/java/module-info.java new file mode 100644 index 000000000..bddf05940 --- /dev/null +++ b/oracles/symbol-filters/src/main/java/module-info.java @@ -0,0 +1,39 @@ +/* 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. + */ + +/** + * This module provides a collection of symbol filters. + *

      + * This module is provided by the following Maven dependency: + *

      + * <dependency>
      + *   <groupId>de.learnlib</groupId>
      + *   <artifactId>learnlib-symbol-filters</artifactId>
      + *   <version>${version}</version>
      + * </dependency>
      + * 
      + */ +open module de.learnlib.oracle.symbol_filters { + + // annotations are 'provided'-scoped and do not need to be loaded at runtime + requires static de.learnlib.tooling.annotation; + requires static org.checkerframework.checker.qual; + requires de.learnlib.api; + requires net.automatalib.api; + + exports de.learnlib.oracle.symbol_filters; + exports de.learnlib.oracle.symbol_filters.mmlt; +} diff --git a/test-support/learning-examples/src/main/java/module-info.java b/test-support/learning-examples/src/main/java/module-info.java index 41817450a..222665195 100644 --- a/test-support/learning-examples/src/main/java/module-info.java +++ b/test-support/learning-examples/src/main/java/module-info.java @@ -51,4 +51,5 @@ exports de.learnlib.testsupport.example.spmm; exports de.learnlib.testsupport.example.sst; exports de.learnlib.testsupport.example.vpa; + exports de.learnlib.testsupport.example.mmlt; } From 20e4db124e66a8c879e293c8f9aceb990b72991a Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 14:40:38 +0200 Subject: [PATCH 23/55] Added an example for learning MMLTs; added module info for symbol filter module. --- examples/src/main/java/de/learnlib/example/mmlt/Example1.java | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 8f701d297..1b6fc27e9 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -17,7 +17,6 @@ import de.learnlib.oracle.symbol_filters.mmlt.LocalTimerMealyStatisticsSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.LocalTimerMealySUL; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; From 4cf0a0a82efa6d829a53134a3756b4cd9a58d7bf Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 15 Oct 2025 09:33:00 +0200 Subject: [PATCH 24/55] Added more descriptions for included MMLT models --- .../LocalTimerMealyCacheConsistencyTest.java | 2 +- .../statistic/sul/LocalTimerMealyStatsSUL.java | 7 ------- .../example/mmlt/LocalTimerMealyExamples.java | 18 +++++++++++++++--- .../src/main/resources/mmlt/Oven.dot | 6 +++++- .../src/main/resources/mmlt/SCTP.dot | 2 +- .../src/main/resources/mmlt/WM.dot | 10 ++++++++++ .../src/main/resources/mmlt/WSN.dot | 6 ++++-- .../main/resources/mmlt/sensor_collector.dot | 8 ++++---- 8 files changed, 40 insertions(+), 19 deletions(-) diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 03c805ae0..64a8cab48 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -120,7 +120,7 @@ private DefaultQuery, Word ignore rest of this word: if (symIdx < queryInput.length() - 1) { - logger.warn("Ignoring at least one symbol during cache comparison."); + logger.debug("Ignoring at least one symbol during cache comparison."); } break; } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java index cd6fdbcaf..eacbb2299 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java @@ -33,13 +33,6 @@ public LocalTimerMealyStatsSUL(LocalTimerMealySUL delegate, StatsContainer this.name = name; } - public long getResetCount() { - if (this.stats == null) { - throw new IllegalStateException("No stats container set up."); - } - return this.stats.getCount(withPrefix("sul_resets_counter")).get(); - } - @Override public void setStatsContainer(StatsContainer container) { this.stats = container; diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java index 1bfa66824..d7a2b894a 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java @@ -5,7 +5,6 @@ import net.automatalib.serialization.dot.LocalTimerMealyGraphvizParser; import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; -import java.io.File; import java.util.List; public class LocalTimerMealyExamples { @@ -41,9 +40,9 @@ public static LocalTimerMealyModel SCTP() { * Returns an MMLT model of a sensor collector. *

      * The sensor measures particulate matter and ambient noise. - * This program automatically ends after some time. The program may be restarted at any time. + * The measurement program automatically ends after some time. The program may be restarted at any time. * Alternatively, a self-check program can be entered. This also ends after some time and may be aborted. - * At the end of either program, the collected data may be collected. + * At the end of either program, the collected data may be retrieved. * * @return LocalTimerMealyModel */ @@ -53,6 +52,19 @@ public static LocalTimerMealyModel SensorCollector() { /** * Returns an MMLT model of a washing machine. + *

      + * The machine is initially off. After powering it on and closing the door, + * the user can start either the short or the normal program. An open + * door prevents starting and triggers a warning. Not choosing a program within 10 seconds turns the machine off. + *

      + * In normal model, the machine fills the drum, heats the water, and starts the main wash. During this wash, + * it regularly adjusts the drum speed and maintains temperature. After 2 hours, + * the water is drained and the drum is spun at full speed for some time. Afterwards the remaining water is drained. + * The short program makes less adjustments, so that a wash ends after 1 hour. + *

      + * Both programs are interrupted when a leak is detected. Normal mode may also be interrupted by "stop". + * This drains the drum immediately. Once done, the door is unlocked, a message is shown, and the machine + * beeps repeatedly until the user presses any button or opens the door. * * @return LocalTimerMealyModel */ diff --git a/test-support/learning-examples/src/main/resources/mmlt/Oven.dot b/test-support/learning-examples/src/main/resources/mmlt/Oven.dot index 3c934d0c0..4de25c32d 100644 --- a/test-support/learning-examples/src/main/resources/mmlt/Oven.dot +++ b/test-support/learning-examples/src/main/resources/mmlt/Oven.dot @@ -1,4 +1,8 @@ -// Model of an oven +// Model of an oven with a time-controlled baking program. +// After powering the oven on, the oven remains idle until the program is started. +// During the program, the oven regularly measures and adjusts the temperature. +// At the end of the program, an alarm sounds. Then, the user may extend the program. +// If not extended, the program ends either when the user opens the door, presses a button, or a timeout occurs. digraph g { s0 [shape="circle"]; diff --git a/test-support/learning-examples/src/main/resources/mmlt/SCTP.dot b/test-support/learning-examples/src/main/resources/mmlt/SCTP.dot index f6308a512..9f2d239ad 100644 --- a/test-support/learning-examples/src/main/resources/mmlt/SCTP.dot +++ b/test-support/learning-examples/src/main/resources/mmlt/SCTP.dot @@ -1,4 +1,4 @@ -// Model of the association in the SCTP protocol. +// Model of the endpoint association in the SCTP protocol. // Adapted from Stewart et al.: Stream Control Transmission Protocol (RFC 9260, Figure 3) digraph g { diff --git a/test-support/learning-examples/src/main/resources/mmlt/WM.dot b/test-support/learning-examples/src/main/resources/mmlt/WM.dot index 1e112ec4f..89f9ade3e 100644 --- a/test-support/learning-examples/src/main/resources/mmlt/WM.dot +++ b/test-support/learning-examples/src/main/resources/mmlt/WM.dot @@ -1,4 +1,14 @@ // Model of a washing machine +// The machine is initially off. After powering it on and closing the door, +// the user can start either the short or the normal program. An open +// door prevents starting and triggers a warning. Not choosing a program within 10 seconds turns the machine off. +// In normal model, the machine fills the drum, heats the water, and starts the main wash. During this wash, +// it regularly adjusts the drum speed and maintains temperature. After 2 hours, +// the water is drained and the drum is spun at full speed for some time. Afterwards the remaining water is drained. +// The short program makes less adjustments, so that a wash ends after 1 hour. +// Both programs are interrupted when a leak is detected. Normal mode may also be interrupted by "stop". +// This drains the drum immediately. Once done, the door is unlocked, a message is shown, and the machine +// beeps repeatedly until the user presses any button or opens the door. digraph g { s0 [timers="a=10000" shape="circle"]; diff --git a/test-support/learning-examples/src/main/resources/mmlt/WSN.dot b/test-support/learning-examples/src/main/resources/mmlt/WSN.dot index a8288e71d..12d305ae3 100644 --- a/test-support/learning-examples/src/main/resources/mmlt/WSN.dot +++ b/test-support/learning-examples/src/main/resources/mmlt/WSN.dot @@ -1,5 +1,7 @@ -// Model of a wireless sensor node that collects data and transmits if -// if the battery has sufficient charge. +// Model of a wireless sensor node that regularly collects and transmits data. +// If the battery is low, no data is transmitted. Then, +// a user may collect the data manually. +// The node can be shut down at any time. If the battery is empty, it is shut down automatically. digraph g { s0 [timers="a=60000,b=3600000" shape="circle"]; diff --git a/test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot b/test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot index 240314743..9035e0a60 100644 --- a/test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot +++ b/test-support/learning-examples/src/main/resources/mmlt/sensor_collector.dot @@ -1,7 +1,7 @@ -// This sensor node is used in the diss as running example. -// p1 starts a normal measurement. part triggers a sensor for particulate matter, noise a sensor for ambient noise. -// p2 performs a self-check. -// Set maximum waiting time = 40 to reproduce bad hypothesis from diss. +// An MMLT model of a sensor that measures particulate matter and ambient noise. +// The measurement program automatically ends after some time. It may be restarted at any time. +// Alternatively, a self-check program can be entered. This also ends after some time and may be aborted. +// At the end of either program, the collected data may be retrieved. digraph g { s0 [label="L0" timers=""] s1 [label="L1" timers="a=3,b=6,c=40"] From 29c276f76dde7f897a75fbf425a1ed6310fefa6c Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 15 Oct 2025 10:06:01 +0200 Subject: [PATCH 25/55] More info on model params --- .../algorithm/LocalTimerMealyModelParams.java | 13 +++++++++++-- .../java/de/learnlib/example/mmlt/Example1.java | 14 ++++++++++---- 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java index 3cf87babc..01884f8d3 100644 --- a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java +++ b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java @@ -18,8 +18,17 @@ public final class LocalTimerMealyModelParams { /** * @param silentOutput Silent output symbol - * @param maxTimeoutWaitingTime Maximum waiting time to wait for a timeout in any configuration - * @param maxTimerQueryWaitingTime Maximum waiting time for timer queries + * @param maxTimeoutWaitingTime Maximum time to wait for a timeout in any configuration. + * If no timeout is observed after this time, the learner assumes that no timers are active. + * Hence, if this value is set too low, the learner will miss timeouts. This usually results in an + * incomplete model but can also trigger exceptions due to unsatisfied assumptions. + * @param maxTimerQueryWaitingTime Maximum waiting time to wait when inferring timers for a location. + * This must be at least the max. time for a timeout. + * We recommend setting this value to at least twice the highest value of any timer + * in the SUL, if these values are known or can be estimated. + * This increases the likelihood of detecting + * non-periodic behavior during timer inference, and thus reduces + * the need for equivalence queries. * @param outputCombiner Function for combining simultaneously occurring outputs of timers */ public LocalTimerMealyModelParams(O silentOutput, diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 1b6fc27e9..18f45a0e2 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -21,10 +21,7 @@ import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; +import net.automatalib.alphabet.time.mmlt.*; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.word.Word; @@ -95,6 +92,15 @@ public static void main(String[] args) { // Start learning: runExperiment(learner, chainOracle, stats, 100); + + // Troubleshooting + // If you attempt to learn a model of some application and the learner + // throws assertion errors or illegal state exceptions, + // your SUL likely has no MMLT semantics. + // In this case, you can try to learn a partial model by excluding TimeStepSymbol + // from the input alphabet for the counterexample search: + // Replace tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + // with -> hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList() } private static void runExperiment(LStarLocalTimerMealy learner, From 2acd44ac577300916c5a055b59c271803c92e961 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 16 Oct 2025 09:07:04 +0200 Subject: [PATCH 26/55] Updated reset search oracle to check if it can return a counterexample with the provided inputs. --- .../lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java | 2 +- .../src/main/java/de/learnlib/example/mmlt/Example1.java | 4 ++-- .../learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index 6eaca933d..bd356ab47 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -108,7 +108,7 @@ private static void learnModel(String name, LocalTimerMealy a LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); - chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 6, 12, 100)); + chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 16, 0, 100)); chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(automaton)); // ensure that we eventually find an accurate model chainOracle.setStatsContainer(stats); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 18f45a0e2..3613d6860 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -69,7 +69,7 @@ public static void main(String[] args) { LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, 100, 1.0, 1.0)); - chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, 100, 6, 12, 100)); + chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, 100, 16, 0, 100)); chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(model.automaton())); // ensure that we eventually find an accurate model chainOracle.setStatsContainer(stats); @@ -100,7 +100,7 @@ public static void main(String[] args) { // In this case, you can try to learn a partial model by excluding TimeStepSymbol // from the input alphabet for the counterexample search: // Replace tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - // with -> hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList() + // with: tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList()); } private static void runExperiment(LStarLocalTimerMealy learner, diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 4f37a5eca..a55d78df8 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -73,6 +73,11 @@ private List> getLoopingSymbols(S sour return null; // oracle is disabled } List> listInputs = new ArrayList<>(inputs); + if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSymbol) || + listInputs.stream().noneMatch(s -> s instanceof TimeoutSymbol)) { + logger.warn("ResetSearchOracle requires inputs to contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); + return null; + } return this.findCexInternal(hypothesis, listInputs); } From de21bb0a67d6036369099792e35ed6c346092bf0 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 16:10:09 +0100 Subject: [PATCH 27/55] adjust to AutomataLib refactorings --- README.md | 26 +-- .../lstar/mmlt/LStarLocalTimerMealy.java | 99 +++++------ .../LStarLocalTimerMealyHypDataContainer.java | 22 +-- .../LocalTimerMealyHypothesisBuilder.java | 34 ++-- .../mmlt/LocalTimerMealyObservationTable.java | 160 +++++++++--------- .../lstar/mmlt/LocationTimerInfo.java | 10 +- .../lstar/mmlt/cex/CexPreprocessor.java | 37 ---- .../lstar/mmlt/cex/ExtendedDecomposition.java | 12 +- ...lTimerMealyCounterexampleDecompositor.java | 49 +++--- .../LocalTimerMealyCounterexampleHandler.java | 60 ++++--- ...alTimerMealyInconsPrefixTransformAcex.java | 24 +-- .../LocalTimerMealyOutputInconsistency.java | 12 +- .../mmlt/cex/results/FalseIgnoreResult.java | 8 +- .../results/MissingDiscriminatorResult.java | 12 +- .../cex/results/MissingOneShotResult.java | 2 +- .../mmlt/cex/results/MissingResetResult.java | 10 +- .../IInternalLocalTimerMealyHypothesis.java | 16 +- .../mmlt/hyp/LocalTimerMealyHypothesis.java | 64 ++++--- .../LStarLocalTimerMealyBenchmarkTests.java | 32 ++-- ...tarLocalTimerMealyCounterexampleTests.java | 132 ++++++--------- .../lstar/mmlt/LocalTimerMealyTestUtil.java | 54 ++++-- .../algorithm/LocalTimerMealyModelParams.java | 8 +- .../oracle/AbstractTimedQueryOracle.java | 22 +-- .../de/learnlib/oracle/EquivalenceOracle.java | 8 +- .../de/learnlib/sul/LocalTimerMealySUL.java | 30 ++-- .../de/learnlib/util/mealy/MealyUtil.java | 3 +- .../LocalTimerMealySimulatorSUL.java | 43 ++--- .../de/learnlib/example/mmlt/Example1.java | 27 +-- .../learnlib/filter/cache/LearningCache.java | 10 +- .../filter/cache/mmlt/CacheTreeNode.java | 36 ++-- .../LocalTimerMealyCacheConsistencyTest.java | 64 +++---- .../mmlt/LocalTimerMealyTreeSULCache.java | 42 ++--- .../filter/cache/mmlt/TimeoutReducerSUL.java | 8 +- .../cache/mmlt/LocalTimerMealyCacheTest.java | 58 +++---- .../sul/LocalTimerMealyStatsSUL.java | 18 +- .../mmlt/LocalTimerMealyEQOracleChain.java | 8 +- .../mmlt/LocalTimerMealyRandomWpOracle.java | 40 ++--- .../mmlt/LocalTimerMealySimulatorOracle.java | 22 +-- .../equivalence/mmlt/ResetSearchOracle.java | 56 +++--- .../oracle/membership/TimedQueryOracle.java | 46 ++--- .../symbol_filters/IgnoreAllSymbolFilter.java | 2 - .../LocalTimerMealyPerfectSymbolFilter.java | 15 +- .../LocalTimerMealyRandomSymbolFilter.java | 15 +- ...LocalTimerMealyStatisticsSymbolFilter.java | 14 +- .../mmlt/LocalTimerMealySymbolFilterUtil.java | 24 ++- pom.xml | 8 + .../example/mmlt/LocalTimerMealyExamples.java | 67 ++++---- .../example/mmlt/LocalTimerMealyModel.java | 6 +- 48 files changed, 800 insertions(+), 775 deletions(-) delete mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java diff --git a/README.md b/README.md index b924e69d6..12c086904 100644 --- a/README.md +++ b/README.md @@ -17,19 +17,19 @@ While certain features have been stripped for improved modularity, development h Currently, the following learning algorithms with respective target models are supported: -| Algorithm (active) | Target models | | Algorithm (passive) | Models | -|---------------------|-----------------------------|-----|-----------------------|-----------------------| -| AAAR | `DFA` `Mealy` `Moore` | | OSTIA | `SST` | -| ADT | `Mealy` | | RPNI (incl. variants) | `DFA` `Mealy` `Moore` | -| DHC | `Mealy` | | | | -| Kearns & Vazirani | `DFA` `Mealy` | | | | -| Lambda | `DFA` `Mealy` | | | | -| L# | `Mealy` | | | | -| L* (incl. variants) | `DFA` `Mealy` `Moore` | | | | -| NL* | `NFA` | | | | -| Observation Pack | `DFA` `Mealy` `Moore` `VPA` | | | | -| Procedural | `SPA` `SBA` `SPMM` | | | | -| TTT | `DFA` `Mealy` `Moore` `VPA` | | | | +| Algorithm (active) | Target models | | Algorithm (passive) | Models | +|---------------------|------------------------------|-----|-----------------------|-----------------------| +| AAAR | `DFA` `Mealy` `Moore` | | OSTIA | `SST` | +| ADT | `Mealy` | | RPNI (incl. variants) | `DFA` `Mealy` `Moore` | +| DHC | `Mealy` | | | | +| Kearns & Vazirani | `DFA` `Mealy` | | | | +| Lambda | `DFA` `Mealy` | | | | +| L# | `Mealy` | | | | +| L* (incl. variants) | `DFA` `Mealy` `Moore` `MMLT` | | | | +| NL* | `NFA` | | | | +| Observation Pack | `DFA` `Mealy` `Moore` `VPA` | | | | +| Procedural | `SPA` `SBA` `SPMM` | | | | +| TTT | `DFA` `Mealy` `Moore` `VPA` | | | | Additionally, LearnLib offers a variety of tools to ease the practical application of automata learning on real-world systems. This includes drivers and mappers for interfacing software systems with the LearnLib API as well as caches and parallelization for improving the overall performance of the learning setup. diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index 2622abf45..80b05a882 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -1,12 +1,15 @@ package de.learnlib.algorithm.lstar.mmlt; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; import de.learnlib.algorithm.lstar.closing.ClosingStrategy; -import de.learnlib.algorithm.lstar.mmlt.cex.CexPreprocessor; import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyCounterexampleHandler; import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyOutputInconsistency; import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; @@ -24,46 +27,41 @@ import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Stream; - /** * The MMLT learner. * * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LStarLocalTimerMealy implements OTLearner, LocalTimerMealySemanticInputSymbol, Word>>, LearnerStatsProvider { +public class LStarLocalTimerMealy implements OTLearner, TimedInput, Word>>, LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LStarLocalTimerMealy.class); private StatsContainer stats = new DummyStatsContainer(); - private final ClosingStrategy, ? super Word>> closingStrategy; + private final ClosingStrategy, ? super Word>> closingStrategy; private final AbstractTimedQueryOracle timeOracle; - private final SymbolFilter, NonDelayingInput> symbolFilter; + private final SymbolFilter, InputSymbol> symbolFilter; private final LStarLocalTimerMealyHypDataContainer hypData; // ============================ - private final List>> initialSuffixes; + private final List>> initialSuffixes; private final LocalTimerMealyCounterexampleHandler cexAnalyzer; /** @@ -78,12 +76,11 @@ public class LStarLocalTimerMealy implements OTLearner> alphabet, + public LStarLocalTimerMealy(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, - @NonNull - List>> initialSuffixes, + List>> initialSuffixes, AbstractTimedQueryOracle timeOracle, - @NonNull SymbolFilter, NonDelayingInput> symbolFilter) { + SymbolFilter, InputSymbol> symbolFilter) { this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); } @@ -98,13 +95,12 @@ public LStarLocalTimerMealy(Alphabet> alph * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. * @param analyzer The strategy for decomposing counterexamples. */ - public LStarLocalTimerMealy(Alphabet> alphabet, + public LStarLocalTimerMealy(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, - @NonNull - List>> initialSuffixes, - ClosingStrategy, ? super Word>> closingStrategy, + List>> initialSuffixes, + ClosingStrategy, ? super Word>> closingStrategy, AbstractTimedQueryOracle timeOracle, - @NonNull SymbolFilter, NonDelayingInput> symbolFilter, + SymbolFilter, InputSymbol> symbolFilter, AcexAnalyzer analyzer) { this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; @@ -176,7 +172,7 @@ public static MealyTimerInfo selectOneShotTimer(List> s * * @return MMLT hypothesis */ - public LocalTimerMealy getHypothesisModel() { + public MMLT getHypothesisModel() { return getInternalLocalTimerMealyHypothesis(); } @@ -186,14 +182,14 @@ public LocalTimerMealy getHypothesisModel() { * * @return MMLT hypothesis */ - private LocalTimerMealyHypothesis getInternalLocalTimerMealyHypothesis() { + private LocalTimerMealyHypothesis getInternalLocalTimerMealyHypothesis() { this.updateOutputs(); var hyp = LocalTimerMealyHypothesisBuilder.constructHypothesis(this.hypData); return new LocalTimerMealyHypothesis<>(hyp.automaton(), hyp.prefixMap()); } - protected List>> selectClosingRows(List>>> unclosed) { + protected List>> selectClosingRows(List>>> unclosed) { return closingStrategy.selectClosingRows(unclosed, hypData.getTable(), timeOracle); } @@ -210,17 +206,17 @@ protected void updateOutputs() { return; // already queried } - Word> prefix = row.getLabel().prefix(-1); - LocalTimerMealySemanticInputSymbol inputSym = row.getLabel().suffix(1).lastSymbol(); + Word> prefix = row.getLabel().prefix(-1); + TimedInput inputSym = row.getLabel().suffix(1).lastSymbol(); - LocalTimerMealyOutputSymbol output = null; + TimedOutput output = null; if (inputSym instanceof TimeStepSequence ws) { // Query timer output from table: - MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.getTimeSteps()); + MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); if (timerInfo == null) { throw new AssertionError(); } - output = new LocalTimerMealyOutputSymbol<>(timerInfo.output()); + output = new TimedOutput<>(timerInfo.output()); } else { output = this.timeOracle.querySuffixOutput(prefix, Word.fromLetter(inputSym)).lastSymbol(); } @@ -235,14 +231,14 @@ protected void updateOutputs() { @Override public void startLearning() { - List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); + List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); // Ensure that closed: this.completeConsistentTable(initialUnclosed); } @Override - public boolean refineHypothesis(DefaultQuery, Word>> ceQuery) { + public boolean refineHypothesis(DefaultQuery, Word>> ceQuery) { if (!refineHypothesisSingle(ceQuery)) { return false; // no valid CEX } @@ -261,10 +257,9 @@ public boolean refineHypothesis(DefaultQuery toOutputInconsistency(DefaultQuery, Word>> ceQuery, LocalTimerMealyHypothesis hypothesis) { + private LocalTimerMealyOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, LocalTimerMealyHypothesis hypothesis) { // 1. Cut example after first deviation: - Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(ceQuery.getPrefix(), ceQuery.getSuffix()); - DefaultQuery, Word>> shortQuery = CexPreprocessor.truncateCEX(ceQuery, hypOutput); + DefaultQuery, Word>> shortQuery = MealyUtil.shortenCounterExample(hypothesis.getSemantics(), ceQuery); if (shortQuery == null) { return null; } @@ -280,7 +275,7 @@ private LocalTimerMealyOutputInconsistency toOutputInconsistency(DefaultQu shortQuery.getOutput(), shortHypOutput); } - private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { + private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { // 1. Update hypothesis (may have changed since last refinement): var hypothesis = this.getInternalLocalTimerMealyHypothesis(); @@ -305,7 +300,7 @@ private boolean refineHypothesisSingle(DefaultQuery>> suffixes = Collections.singletonList(locSplit.getDiscriminator()); + List>> suffixes = Collections.singletonList(locSplit.getDiscriminator()); var unclosed = hypData.getTable().addSuffixes(suffixes, timeOracle); // Close transitions: @@ -322,8 +317,8 @@ private boolean refineHypothesisSingle(DefaultQuery> locPrefix = hypothesis.getPrefix(noAperiodic.getLocation()); - Row> spRow = hypData.getTable().getRow(locPrefix); + Word> locPrefix = hypothesis.getPrefix(noAperiodic.getLocation()); + Row> spRow = hypData.getTable().getRow(locPrefix); if (spRow == null || !spRow.isShortPrefixRow()) { throw new AssertionError(); } @@ -338,8 +333,8 @@ private boolean refineHypothesisSingle(DefaultQuery> locPrefix = hypothesis.getPrefix(falseIgnore.getLocation()); - Row> spRow = hypData.getTable().getRow(locPrefix); + Word> locPrefix = hypothesis.getPrefix(falseIgnore.getLocation()); + Row> spRow = hypData.getTable().getRow(locPrefix); if (spRow == null || !spRow.isShortPrefixRow()) { throw new AssertionError(); } @@ -359,7 +354,7 @@ private boolean refineHypothesisSingle(DefaultQuery> spRow, MealyTimerInfo timeout) { + private void handleMissingTimeoutChange(Row> spRow, MealyTimerInfo timeout) { var locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); if (locationTimerInfo == null) { throw new AssertionError("Location with missing one-shot timer must have timers."); @@ -377,7 +372,7 @@ private void handleMissingTimeoutChange(Row> timerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(timeout.initial())); + Word> timerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(timeout.initial())); if (this.hypData.getTable().getRow(timerTransPrefix) != null) { throw new AssertionError("Timer already appears to be one-shot."); } @@ -392,13 +387,13 @@ private void handleMissingTimeoutChange(Row>>> unclosed = this.hypData.getTable().addTimerTransition(spRow, timeout, this.timeOracle); + List>>> unclosed = this.hypData.getTable().addTimerTransition(spRow, timeout, this.timeOracle); this.completeConsistentTable(unclosed); } @Override - public ObservationTable, Word>> getObservationTable() { + public ObservationTable, Word>> getObservationTable() { return this.hypData.getTable(); } @@ -410,10 +405,10 @@ public ObservationTable, Word>>> unclosed) { - List>>> unclosedIter = unclosed; + protected void completeConsistentTable(List>>> unclosed) { + List>>> unclosedIter = unclosed; while (!unclosedIter.isEmpty()) { - List>> closingRows = this.selectClosingRows(unclosedIter); + List>> closingRows = this.selectClosingRows(unclosedIter); // Add new states: unclosedIter = hypData.getTable().toShortPrefixes(closingRows, timeOracle); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java index 14954492a..5c225858b 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java @@ -3,8 +3,8 @@ import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.datastructure.observationtable.Row; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -21,15 +21,15 @@ * @param Output symbol type */ class LStarLocalTimerMealyHypDataContainer { - private final Alphabet> alphabet; + private final Alphabet> alphabet; private final LocalTimerMealyObservationTable table; - private final Map>, LocalTimerMealyOutputSymbol> transitionOutputMap; - private final Set>> transitionResetSet; // all transitions that trigger a reset + private final Map>, TimedOutput> transitionOutputMap; + private final Set>> transitionResetSet; // all transitions that trigger a reset private final LocalTimerMealyModelParams modelParams; - public LStarLocalTimerMealyHypDataContainer(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, LocalTimerMealyObservationTable table) { + public LStarLocalTimerMealyHypDataContainer(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, LocalTimerMealyObservationTable table) { this.alphabet = alphabet; this.modelParams = modelParams; this.table = table; @@ -39,8 +39,8 @@ public LStarLocalTimerMealyHypDataContainer(Alphabet getTransitionOutput(Row> stateRow, int inputIdx) { - Row> transRow = stateRow.getSuccessor(inputIdx); + protected TimedOutput getTransitionOutput(Row> stateRow, int inputIdx) { + Row> transRow = stateRow.getSuccessor(inputIdx); if (transRow == null) { return null; } @@ -53,7 +53,7 @@ public LocalTimerMealyModelParams getModelParams() { return modelParams; } - public Alphabet> getAlphabet() { + public Alphabet> getAlphabet() { return alphabet; } @@ -62,11 +62,11 @@ public LocalTimerMealyObservationTable getTable() { return table; } - public Map>, LocalTimerMealyOutputSymbol> getTransitionOutputMap() { + public Map>, TimedOutput> getTransitionOutputMap() { return transitionOutputMap; } - public Set>> getTransitionResetSet() { + public Set>> getTransitionResetSet() { return transitionResetSet; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java index d23788eba..83fb90c7b 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java @@ -2,9 +2,11 @@ import de.learnlib.datastructure.observationtable.Row; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.*; -import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.word.Word; import java.util.HashMap; @@ -12,18 +14,18 @@ class LocalTimerMealyHypothesisBuilder { - record LocalTimerMealyHypothesisBuildResult(LocalTimerMealy automaton, - Map>> prefixMap) { + record LocalTimerMealyHypothesisBuildResult(MMLT automaton, + Map>> prefixMap) { } /** * Constructs a hypothesis MMLT from an observation table, inferred local resets, and inferred local timers. */ - static LocalTimerMealyHypothesisBuildResult constructHypothesis(LStarLocalTimerMealyHypDataContainer hypData) { + static LocalTimerMealyHypothesisBuildResult constructHypothesis(LStarLocalTimerMealyHypDataContainer hypData) { // 1. Create map that stores link between contentID and short-prefix row: - final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location + final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location for (var spRow : hypData.getTable().getShortPrefixRows()) { if (locationContentIdMap.containsKey(spRow.getRowContentId())) { // Multiple sp rows may have same contentID. Thus, assign each id one location: @@ -33,20 +35,20 @@ static LocalTimerMealyHypothesisBuildResult constructHypot } // 2. Create untimed alphabet: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); for (var symbol : hypData.getAlphabet()) { - if (symbol instanceof NonDelayingInput ndi) { + if (symbol instanceof InputSymbol ndi) { alphabet.add(ndi); } } // 3. Prepare objects for automaton, timers and resets: int numLocations = hypData.getTable().numberOfShortPrefixRows(); - var hypothesis = new CompactLocalTimerMealy<>(alphabet, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner()); + var hypothesis = new CompactMMLT<>(alphabet, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner()); final Map stateMap = new HashMap<>(numLocations); // row content id -> state id - final Map>> prefixMap = new HashMap<>(numLocations); // state id -> location prefix + final Map>> prefixMap = new HashMap<>(numLocations); // state id -> location prefix // 4. Create one state per location: for (var row : hypData.getTable().getShortPrefixRows()) { @@ -65,7 +67,7 @@ static LocalTimerMealyHypothesisBuildResult constructHypot // 5. Create outgoing transitions for non-delaying inputs: for (var rowContentId : stateMap.keySet()) { - Row> spLocation = locationContentIdMap.get(rowContentId); + Row> spLocation = locationContentIdMap.get(rowContentId); for (var symbol : alphabet) { int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); @@ -73,7 +75,7 @@ static LocalTimerMealyHypothesisBuildResult constructHypot var transOutput = hypData.getTransitionOutput(spLocation, symIdx); O output = hypData.getModelParams().silentOutput(); // silent by default if (transOutput != null) { - output = transOutput.getSymbol(); + output = transOutput.symbol(); } int successorId; @@ -86,7 +88,7 @@ static LocalTimerMealyHypothesisBuildResult constructHypot // Add transition to automaton: int sourceLocId = stateMap.get(rowContentId); int successorLocId = stateMap.get(successorId); - hypothesis.addTransition(sourceLocId, symbol, output, successorLocId); + hypothesis.addTransition(sourceLocId, symbol, successorLocId, output); // Check for local reset: var targetTransition = spLocation.getLabel().append(symbol); @@ -99,7 +101,7 @@ static LocalTimerMealyHypothesisBuildResult constructHypot // 6. Add timeout transitions: for (var rowContentId : stateMap.keySet()) { - Row> spLocation = locationContentIdMap.get(rowContentId); + Row> spLocation = locationContentIdMap.get(rowContentId); var timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); if (timerInfo == null) { @@ -111,7 +113,7 @@ static LocalTimerMealyHypothesisBuildResult constructHypot hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output()); } else { // One-shot: use successor from table - LocalTimerMealySemanticInputSymbol symbol = new TimeStepSequence<>(timer.initial()); + TimedInput symbol = new TimeStepSequence<>(timer.initial()); int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java index 163961d83..09e752f92 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java @@ -8,8 +8,12 @@ import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.*; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -34,36 +38,36 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyObservationTable implements MutableObservationTable, Word>> { +public class LocalTimerMealyObservationTable implements MutableObservationTable, Word>> { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyObservationTable.class); - private final SymbolFilter, NonDelayingInput> symbolFilter; + private final SymbolFilter, InputSymbol> symbolFilter; - private final Map>, LocationTimerInfo> timerInfoMap; // prefix -> timer info + private final Map>, LocationTimerInfo> timerInfoMap; // prefix -> timer info - private final Map>, RowImpl>> shortPrefixRowMap; // label -> row info - private final Map>, RowImpl>> longPrefixRowMap; // label -> row info + private final Map>, RowImpl>> shortPrefixRowMap; // label -> row info + private final Map>, RowImpl>> longPrefixRowMap; // label -> row info - private final List>> sortedShortPrefixes; // values of shortPrefixRowMap sorted by label, for faster access. - private final List>> longPrefixList; // values of longPrefixRowMap as list, for faster access. + private final List>> sortedShortPrefixes; // values of shortPrefixRowMap sorted by label, for faster access. + private final List>> longPrefixList; // values of longPrefixRowMap as list, for faster access. private final Map> rowContentMap; // contentID -> row content private static final int NO_CONTENT = -1; - private final List>> suffixes = new ArrayList<>(); - private final Set>> suffixSet = new HashSet<>(); + private final List>> suffixes = new ArrayList<>(); + private final Set>> suffixSet = new HashSet<>(); - private final Alphabet> alphabet; + private final Alphabet> alphabet; private final long minTimerQueryWaitTime; - private final LocalTimerMealyOutputSymbol silentOutput; // used for symbol filtering + private final TimedOutput silentOutput; // used for symbol filtering - public LocalTimerMealyObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, - @NonNull SymbolFilter, NonDelayingInput> symbolFilter, O silentOutput) { + public LocalTimerMealyObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, + @NonNull SymbolFilter, InputSymbol> symbolFilter, O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; - this.silentOutput = new LocalTimerMealyOutputSymbol<>(silentOutput); + this.silentOutput = new TimedOutput<>(silentOutput); this.minTimerQueryWaitTime = minTimerQueryWaitTime; this.timerInfoMap = new HashMap<>(); @@ -110,7 +114,7 @@ private void extendAlphabet(TimeStepSequence symbol) { alphabet.asGrowingAlphabetOrThrowException().addSymbol(symbol); } - for (RowImpl> prefix : this.shortPrefixRowMap.values()) { + for (RowImpl> prefix : this.shortPrefixRowMap.values()) { prefix.ensureInputCapacity(alphabet.size()); } } @@ -120,8 +124,8 @@ private void extendAlphabet(TimeStepSequence symbol) { * * @return Corresponding row in the OT */ - private RowImpl> addInitialLocation() { - RowImpl> newRow = new RowImpl<>(Word.epsilon(), 0, alphabet.size()); + private RowImpl> addInitialLocation() { + RowImpl> newRow = new RowImpl<>(Word.epsilon(), 0, alphabet.size()); newRow.makeShort(alphabet.size()); this.shortPrefixRowMap.put(Word.epsilon(), newRow); this.sortedShortPrefixes.add(newRow); @@ -138,7 +142,7 @@ private RowImpl> addInitialLocation() { * @param newRow Newly-added short prefix row * @param timeOracle Time oracle */ - private void initLocation(RowImpl> newRow, AbstractTimedQueryOracle timeOracle) { + private void initLocation(RowImpl> newRow, AbstractTimedQueryOracle timeOracle) { LocationTimerInfo timerInfo = new LocationTimerInfo<>(newRow.getLabel()); this.identifyLocalTimers(timerInfo, timeOracle); @@ -147,7 +151,7 @@ private void initLocation(RowImpl> newRow, } // Add outgoing transitions: - List>> transitions = this.createOutgoingTransitions(newRow, timeOracle); + List>> transitions = this.createOutgoingTransitions(newRow, timeOracle); transitions.forEach(t -> this.queryAllSuffixes(t, timeOracle)); } @@ -162,25 +166,25 @@ private void initLocation(RowImpl> newRow, * @param timeOracle Time query oracle * @return New transitions */ - private List>> createOutgoingTransitions(RowImpl> spRow, AbstractTimedQueryOracle timeOracle) { - List>> transitions = new ArrayList<>(); + private List>> createOutgoingTransitions(RowImpl> spRow, AbstractTimedQueryOracle timeOracle) { + List>> transitions = new ArrayList<>(); - Word> sp = spRow.getLabel(); + Word> sp = spRow.getLabel(); // First, add transitions for non-delaying symbols: for (int i = 0; i < alphabet.size(); i++) { - LocalTimerMealySemanticInputSymbol sym = alphabet.getSymbol(i); + TimedInput sym = alphabet.getSymbol(i); if (sym instanceof TimeStepSequence || sym instanceof TimeoutSymbol) { continue; } - Word> lp = sp.append(sym); + Word> lp = sp.append(sym); assert !this.shortPrefixRowMap.containsKey(lp); - RowImpl> succRow = this.longPrefixRowMap.get(lp); + RowImpl> succRow = this.longPrefixRowMap.get(lp); if (succRow == null) { // Query symbol filter before adding transition: - var filterResponse = this.symbolFilter.query(sp, (NonDelayingInput) sym); + var filterResponse = this.symbolFilter.query(sp, (InputSymbol) sym); if (filterResponse == SymbolFilterResponse.IGNORE) { // Verify that output is silent: var response = timeOracle.querySuffixOutput(sp, Word.fromLetter(sym)); @@ -190,7 +194,7 @@ private List>> createOutgoingTrans filterResponse = SymbolFilterResponse.ACCEPT; // Update filter: - this.symbolFilter.update(sp, (NonDelayingInput) sym, SymbolFilterResponse.ACCEPT); + this.symbolFilter.update(sp, (InputSymbol) sym, SymbolFilterResponse.ACCEPT); } } @@ -209,11 +213,11 @@ private List>> createOutgoingTrans // Second, add one-shot timer transition (if any): var locTimers = timerInfoMap.getOrDefault(spRow.getLabel(), null); if (locTimers != null && !locTimers.getLastTimer().periodic()) { - LocalTimerMealySemanticInputSymbol waitSym = new TimeStepSequence<>(locTimers.getLastTimer().initial()); - Word> lp = sp.append(waitSym); + TimedInput waitSym = new TimeStepSequence<>(locTimers.getLastTimer().initial()); + Word> lp = sp.append(waitSym); assert !this.shortPrefixRowMap.containsKey(lp); - RowImpl> succRow = this.longPrefixRowMap.get(lp); + RowImpl> succRow = this.longPrefixRowMap.get(lp); if (succRow == null) { succRow = this.createLpRow(lp); } @@ -224,8 +228,8 @@ private List>> createOutgoingTrans return transitions; } - private RowImpl> createLpRow(Word> prefix) { - RowImpl> newRow = new RowImpl<>(prefix, 0); + private RowImpl> createLpRow(Word> prefix) { + RowImpl> newRow = new RowImpl<>(prefix, 0); this.longPrefixRowMap.put(prefix, newRow); this.longPrefixList.add(newRow); if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); @@ -242,21 +246,21 @@ private RowImpl> createLpRow(Word * Guarantees that returned transition list order is deterministic. */ - public List>>> findUnclosedTransitions() { + public List>>> findUnclosedTransitions() { // Identify contentIds for locations: Set spContentIds = this.shortPrefixRowMap.values().stream() .map(RowImpl::getRowContentId) .collect(Collectors.toSet()); // Group lp rows by their content id: - Map>>> lpContentMap = new HashMap<>(); + Map>>> lpContentMap = new HashMap<>(); for (var lpRow : this.longPrefixRowMap.values()) { lpContentMap.putIfAbsent(lpRow.getRowContentId(), new ArrayList<>()); lpContentMap.get(lpRow.getRowContentId()).add(lpRow); } // Identify ids that are not used by any SP: - List>>> unclosedRows = new ArrayList<>(); + List>>> unclosedRows = new ArrayList<>(); List sortedLpIds = lpContentMap.keySet().stream().sorted().toList(); for (var lpId : sortedLpIds) { if (spContentIds.contains(lpId)) { @@ -264,7 +268,7 @@ public List>>> findUnclosedTransi } // Sort row s.t. list order deterministic: - List>> unclosedWithId = lpContentMap.get(lpId); + List>> unclosedWithId = lpContentMap.get(lpId); unclosedWithId.sort(Comparator.comparing(r -> r.getLabel().toString())); unclosedRows.add(unclosedWithId); } @@ -285,9 +289,9 @@ public List>>> findUnclosedTransi } @Override - public List>>> initialize(List>> initialShortPrefixes, - List>> initialSuffixes, - MembershipOracle, Word>> oracle) { + public List>>> initialize(List>> initialShortPrefixes, + List>> initialSuffixes, + MembershipOracle, Word>> oracle) { if (isInitialized()) { throw new IllegalStateException("Called initialize, but there are already rows present"); @@ -300,7 +304,7 @@ public List>>> initialize(List> suffix : initialSuffixes) { + for (Word> suffix : initialSuffixes) { if (suffixSet.add(suffix)) { suffixes.add(suffix); } @@ -315,19 +319,19 @@ public List>>> initialize(List> row, AbstractTimedQueryOracle timedOracle) { - Word> prefix = row.getLabel(); + private void queryAllSuffixes(RowImpl> row, AbstractTimedQueryOracle timedOracle) { + Word> prefix = row.getLabel(); - List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); - for (Word> suffix : this.suffixes) { - Word> output = timedOracle.querySuffixOutput(prefix, suffix); + List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); + for (Word> suffix : this.suffixes) { + Word> output = timedOracle.querySuffixOutput(prefix, suffix); suffixOutputs.add(output); } this.processSuffixOutputs(row, suffixOutputs); } - private void processSuffixOutputs(RowImpl> row, List>> rowContents) { + private void processSuffixOutputs(RowImpl> row, List>> rowContents) { if (rowContents.isEmpty()) { row.setRowContentId(NO_CONTENT); return; @@ -350,14 +354,14 @@ public boolean isInitialConsistencyCheckRequired() { } @Override - public List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { + public List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } // 1. Extend current suffixes + identify new suffixes: - List>> newSuffixList = new ArrayList<>(); - for (Word> suffix : newSuffixes) { + List>> newSuffixList = new ArrayList<>(); + for (Word> suffix : newSuffixes) { if (this.suffixSet.add(suffix)) { logger.debug(String.format("Adding new suffix '%s'", suffix)); @@ -371,14 +375,14 @@ public List>>> addSuffixes(Collec // 2. Update row content: Stream.concat(shortPrefixRowMap.values().stream(), longPrefixRowMap.values().stream()).forEach(row -> { - List>> updatedOutputs = new ArrayList<>(); + List>> updatedOutputs = new ArrayList<>(); if (row.getRowContentId() != NO_CONTENT) { // Add existing suffix outputs: updatedOutputs.addAll(this.rowContentMap.get(row.getRowContentId()).outputs()); } - for (Word> suffix : newSuffixList) { - Word> output = timedOracle.querySuffixOutput(row.getLabel(), suffix); + for (Word> suffix : newSuffixList) { + Word> output = timedOracle.querySuffixOutput(row.getLabel(), suffix); updatedOutputs.add(output); } @@ -389,20 +393,20 @@ public List>>> addSuffixes(Collec } @Override - public List>>> addShortPrefixes(List>> shortPrefixes, MembershipOracle, Word>> oracle) { + public List>>> addShortPrefixes(List>> shortPrefixes, MembershipOracle, Word>> oracle) { throw new IllegalStateException("Not supported."); } @Override - public List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { + public List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } - for (Row> row : lpRows) { + for (Row> row : lpRows) { logger.debug(String.format("Adding new location with prefix '%s'", row.getLabel())); - final RowImpl> lpRow = (RowImpl>) row; + final RowImpl> lpRow = (RowImpl>) row; // Delete from LP rows: var removed = this.longPrefixRowMap.remove(row.getLabel()); @@ -422,33 +426,33 @@ public List>>> toShortPrefixes(Li } @Override - public List>>> addAlphabetSymbol(LocalTimerMealySemanticInputSymbol symbol, MembershipOracle, Word>> oracle) { + public List>>> addAlphabetSymbol(TimedInput symbol, MembershipOracle, Word>> oracle) { throw new IllegalStateException("Not supported."); } @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return this.alphabet; } @Override - public Collection>> getShortPrefixRows() { + public Collection>> getShortPrefixRows() { if (this.sortedShortPrefixes.size() != this.shortPrefixRowMap.size()) throw new AssertionError(); return Collections.unmodifiableList(this.sortedShortPrefixes); } @Override - public Collection>> getLongPrefixRows() { + public Collection>> getLongPrefixRows() { return Collections.unmodifiableList(this.longPrefixList); } @Override - public Row> getRow(int idx) { + public Row> getRow(int idx) { throw new IllegalStateException("Not supported. Use prefix to access rows instead."); } @Override - public @Nullable Row> getRow(Word> prefix) { + public @Nullable Row> getRow(Word> prefix) { if (this.shortPrefixRowMap.containsKey(prefix)) { return this.shortPrefixRowMap.get(prefix); } @@ -464,13 +468,13 @@ public int numberOfDistinctRows() { } @Override - public List>> getSuffixes() { + public List>> getSuffixes() { return this.suffixes; } @Override @Nullable - public List>> rowContents(Row> row) { + public List>> rowContents(Row> row) { if (this.rowContentMap.isEmpty()) { // OT may be empty if only single location with timers: if (!this.suffixes.isEmpty()) { @@ -483,17 +487,17 @@ public List>> rowContents(Row> transformAccessSequence(Word> word) { + public Word> transformAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } @Override - public boolean isAccessSequence(Word> word) { + public boolean isAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } - public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { + public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { var info = this.timerInfoMap.get(prefix); if (info != null) { return info.getTimerInfo(initial); @@ -502,7 +506,7 @@ public boolean isAccessSequence(Word> word } @Nullable - public LocationTimerInfo getLocationTimerInfo(Row> sp) { + public LocationTimerInfo getLocationTimerInfo(Row> sp) { return this.timerInfoMap.getOrDefault(sp.getLabel(), null); } @@ -517,23 +521,23 @@ public LocationTimerInfo getLocationTimerInfo(Row>>> addOutgoingTransition(Row> spRow, LocalTimerMealySemanticInputSymbol symbol, AbstractTimedQueryOracle timeOracle) { + public List>>> addOutgoingTransition(Row> spRow, TimedInput symbol, AbstractTimedQueryOracle timeOracle) { if (!this.alphabet.containsSymbol(symbol)) { throw new IllegalArgumentException("Unknown symbol."); } - Word> transitionPrefix = spRow.getLabel().append(symbol); + Word> transitionPrefix = spRow.getLabel().append(symbol); // Add long-prefix row: if (this.getRow(transitionPrefix) != null) { throw new AssertionError("Location already has an outgoing transition for the provided symbol"); } - RowImpl> succRow = this.createLpRow(transitionPrefix); + RowImpl> succRow = this.createLpRow(transitionPrefix); // Set as successor: int symIdx = this.alphabet.getSymbolIndex(symbol); - ((RowImpl>) spRow).setSuccessor(symIdx, succRow); + ((RowImpl>) spRow).setSuccessor(symIdx, succRow); // Update suffixes: this.queryAllSuffixes(succRow, timeOracle); @@ -541,7 +545,7 @@ public List>>> addOutgoingTransit return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } @@ -551,7 +555,7 @@ public List>>> addTimerTransition * * @param prefix Row prefix */ - public void removeLpRow(Word> prefix) { + public void removeLpRow(Word> prefix) { if (!this.longPrefixRowMap.containsKey(prefix)) { throw new IllegalArgumentException("Attempting to remove lp row that does not exist."); } @@ -563,7 +567,7 @@ public void removeLpRow(Word> prefix) { // Unset as successor: int symIdx = this.alphabet.getSymbolIndex(prefix.lastSymbol()); - RowImpl> spRow = this.shortPrefixRowMap.get(prefix.prefix(-1)); + RowImpl> spRow = this.shortPrefixRowMap.get(prefix.prefix(-1)); assert spRow != null; spRow.setSuccessor(symIdx, null); @@ -571,7 +575,7 @@ public void removeLpRow(Word> prefix) { // ============================= - private record RowContent(List>> outputs) { + private record RowContent(List>> outputs) { @Override public int hashCode() { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java index f0ab4616d..47a610145 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java @@ -1,7 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -26,15 +26,15 @@ public class LocationTimerInfo implements Serializable { // Keep a list of timers sorted by their initial value. This lets us avoid redundant sort operations. private final List> sortedTimers; - private final Word> prefix; + private final Word> prefix; - public LocationTimerInfo(Word> prefix) { + public LocationTimerInfo(Word> prefix) { this.prefix = prefix; this.timers = new HashMap<>(); this.sortedTimers = new ArrayList<>(); } - public Word> getPrefix() { + public Word> getPrefix() { return prefix; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java deleted file mode 100644 index d4ade3082..000000000 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/CexPreprocessor.java +++ /dev/null @@ -1,37 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt.cex; - -import de.learnlib.query.DefaultQuery; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.word.Word; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class CexPreprocessor { - - private static final Logger logger = LoggerFactory.getLogger(CexPreprocessor.class); - - /** - * Cuts-off a counterexample when its output deviates from the hypothesis output. - * - * @param cexQuery Counterexample - * @param hypAnswer Hypothesis response to suffix of the counterexample - * @return The shortened counterexample, or null, if hypothesis and SUL show identical behavior. - */ - public static DefaultQuery, Word>> truncateCEX(DefaultQuery, Word>> cexQuery, - Word> hypAnswer) { - - for (int i = 0; i < cexQuery.getSuffix().size(); i++) { - if (!hypAnswer.getSymbol(i).equals(cexQuery.getOutput().getSymbol(i))) { - // Cut after deviation: - return new DefaultQuery<>(cexQuery.getPrefix(), - cexQuery.getSuffix().prefix(i + 1), - cexQuery.getOutput().prefix(i + 1)); - } - } - return null; // no deviation found - } - -} - diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java index 5fac3ec1e..4a67a376c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.cex; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.State; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -16,11 +16,11 @@ * @param discriminator If not null: transition has incorrect target * @param Input type for non-delaying inputs */ -record ExtendedDecomposition(LocalTimerMealyConfiguration state, - @NonNull LocalTimerMealySemanticInputSymbol input, - @Nullable Word> discriminator) { +record ExtendedDecomposition(State state, + @NonNull TimedInput input, + @Nullable Word> discriminator) { - public ExtendedDecomposition(LocalTimerMealyConfiguration state, @NonNull LocalTimerMealySemanticInputSymbol input) { + public ExtendedDecomposition(State state, @NonNull TimedInput input) { this(state, input, null); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java index 275bacfff..1ce52be2a 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java @@ -3,11 +3,10 @@ import de.learnlib.acex.AcexAnalyzer; import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; import de.learnlib.oracle.AbstractTimedQueryOracle; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,11 +32,11 @@ public LocalTimerMealyCounterexampleDecompositor(AbstractTimedQueryOracle } ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputInconsistency outIncons, - LocalTimerMealyHypothesis hypothesis) { + LocalTimerMealyHypothesis hypothesis) { if (outIncons.suffix().length() == 1) { // Incorrect output: - var prefixState = hypothesis.getSemantics().traceInputs(outIncons.prefix()); + var prefixState = hypothesis.getSemantics().getState(outIncons.prefix()); return new ExtendedDecomposition<>(prefixState, outIncons.suffix().firstSymbol()); } @@ -48,7 +47,7 @@ ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputIn if (acex.testEffects(0, acex.getLength() - 1)) { // Breakpoint condition not met -> must be incorrect output: var lastStatePrefix = outIncons.prefix().concat(outIncons.suffix().prefix(outIncons.suffix().length() - 1)); - var lastState = hypothesis.getSemantics().traceInputs(lastStatePrefix); + var lastState = hypothesis.getSemantics().getState(lastStatePrefix); return new ExtendedDecomposition<>(lastState, outIncons.suffix().lastSymbol()); } @@ -60,11 +59,11 @@ ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputIn } // Get components: - Word> prefix = outIncons.prefix().concat(outIncons.suffix().prefix(breakpoint)); - LocalTimerMealySemanticInputSymbol sym = outIncons.suffix().getSymbol(breakpoint); - Word> discriminator = outIncons.suffix().subWord(breakpoint + 1); + Word> prefix = outIncons.prefix().concat(outIncons.suffix().prefix(breakpoint)); + TimedInput sym = outIncons.suffix().getSymbol(breakpoint); + Word> discriminator = outIncons.suffix().subWord(breakpoint + 1); - var prefixState = hypothesis.getSemantics().traceInputs(prefix); + var prefixState = hypothesis.getSemantics().getState(prefix); logger.debug(String.format("Decomposing to %s|%s|%s %n" + "Output at %d: %s. %nOutput at %d: %s", prefixState, sym, discriminator, breakpoint, acex.computeEffect(breakpoint), breakpoint + 1, acex.computeEffect(breakpoint + 1))); @@ -80,7 +79,7 @@ ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputIn * @return Post-processed decomposition */ ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, - LocalTimerMealyHypothesis hypothesis) { + LocalTimerMealyHypothesis hypothesis) { if (!(decomposition.input() instanceof TimeoutSymbol)) { return decomposition; } @@ -92,43 +91,43 @@ ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecompos if (decomposition.isForIncorrectOutput()) { // Incorrect output at tout: long minWaitTime; - if (hypOutput.firstSymbol().getDelay() == 0 && sulOutput.firstSymbol().getDelay() != 0) { + if (hypOutput.firstSymbol().delay() == 0 && sulOutput.firstSymbol().delay() != 0) { throw new AssertionError(); - } else if (hypOutput.firstSymbol().getDelay() != 0 && sulOutput.firstSymbol().getDelay() == 0) { + } else if (hypOutput.firstSymbol().delay() != 0 && sulOutput.firstSymbol().delay() == 0) { // If there is no timeout in either hyp or sul, need to trigger next observable timeout: - minWaitTime = hypOutput.firstSymbol().getDelay(); + minWaitTime = hypOutput.firstSymbol().delay(); } else { // If there is a timeout in hyp and sul, go to next timeout: - minWaitTime = Math.min(hypOutput.firstSymbol().getDelay(), sulOutput.firstSymbol().getDelay()); + minWaitTime = Math.min(hypOutput.firstSymbol().delay(), sulOutput.firstSymbol().delay()); } // if minimum time is zero (= no timeout) or one, need to append empty word to prefix: - LocalTimerMealyConfiguration newPrefixState; + State newPrefixState; if (minWaitTime <= 1) { newPrefixState = decomposition.state(); } else { - newPrefixState = hypothesis.getSemantics().traceInputs(statePrefix.append(new TimeStepSequence<>(minWaitTime - 1))); + newPrefixState = hypothesis.getSemantics().getState(statePrefix.append(new TimeStepSequence<>(minWaitTime - 1))); } logger.debug("Updated incorrect output at tout during post-processing."); - return new ExtendedDecomposition<>(newPrefixState, new TimeStepSymbol<>()); + return new ExtendedDecomposition<>(newPrefixState, TimedInput.step()); } else { if (decomposition.state().isStableConfig() || hypOutput.equals(sulOutput)) { // stable-configuration or same output at tout -> same wait time for output in hyp and SUL: - if (hypOutput.firstSymbol().getDelay() != sulOutput.firstSymbol().getDelay()) { + if (hypOutput.firstSymbol().delay() != sulOutput.firstSymbol().delay()) { throw new AssertionError(); } - long waitTime = hypOutput.firstSymbol().getDelay(); - LocalTimerMealyConfiguration newPrefixState; + long waitTime = hypOutput.firstSymbol().delay(); + State newPrefixState; if (waitTime <= 1) { newPrefixState = decomposition.state(); } else { - newPrefixState = hypothesis.getSemantics().traceInputs(statePrefix.append(new TimeStepSequence<>(waitTime - 1))); + newPrefixState = hypothesis.getSemantics().getState(statePrefix.append(new TimeStepSequence<>(waitTime - 1))); } logger.debug("Updated incorrect target at tout during post-processing."); - return new ExtendedDecomposition<>(newPrefixState, new TimeStepSymbol<>(), decomposition.discriminator()); + return new ExtendedDecomposition<>(newPrefixState, TimedInput.step(), decomposition.discriminator()); } else { // different output at tout -> found incorrect output: logger.debug("Found incorrect output through post-processing."); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java index f2d4fdc70..56eea0281 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -1,8 +1,14 @@ package de.learnlib.algorithm.lstar.mmlt.cex; +import java.util.List; + import de.learnlib.acex.AcexAnalyzer; import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; -import de.learnlib.algorithm.lstar.mmlt.cex.results.*; +import de.learnlib.algorithm.lstar.mmlt.cex.results.CexAnalysisResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; +import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.statistic.container.DummyStatsContainer; @@ -10,18 +16,16 @@ import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; - /** * Processes a truncated counterexample for a hypothesis MMLT: * searches for an extended decomposition, post-processes it, and infers an inaccuracy from it. @@ -32,13 +36,13 @@ */ public class LocalTimerMealyCounterexampleHandler implements LearnerStatsProvider { private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleHandler.class); - private final SymbolFilter, NonDelayingInput> symbolFilter; + private final SymbolFilter, InputSymbol> symbolFilter; private StatsContainer stats = new DummyStatsContainer(); protected final AbstractTimedQueryOracle timeOracle; private final LocalTimerMealyCounterexampleDecompositor decompositor; - public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, NonDelayingInput> symbolFilter) { + public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, InputSymbol> symbolFilter) { this.timeOracle = timeOracle; this.decompositor = new LocalTimerMealyCounterexampleDecompositor<>(timeOracle, acexAnalyzer); this.symbolFilter = symbolFilter; @@ -49,8 +53,8 @@ public void setStatsContainer(StatsContainer container) { this.stats = container; } - public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutputInconsistency outIncons, - LocalTimerMealyHypothesis hypothesis) { + public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutputInconsistency outIncons, + LocalTimerMealyHypothesis hypothesis) { // Search for an extended decomposition: var decomposition = decompositor.findExtendedDecomposition(outIncons, hypothesis); @@ -67,14 +71,14 @@ public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutputInco } } - private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { // Transition with incorrect output always implies missing one-shot timer: logger.debug("Found missing one-shot via incorrect output."); return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); } - private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { - if (decomposition.input() instanceof NonDelayingInput ndi) { + private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + if (decomposition.input() instanceof InputSymbol ndi) { // If decomposition at non-delaying input + considered as self-loop, treat as false ignore: if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == SymbolFilterResponse.IGNORE) { return new FalseIgnoreResult<>(decomposition.state().getLocation(), ndi); @@ -82,20 +86,20 @@ private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition) { + } else if (decomposition.input() instanceof TimeStepSequence) { return this.handleIncorrectTargetTimeStep(decomposition, hypothesis); } else { throw new AssertionError("Unexpected symbol type."); } } - private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis, long maxInitialValue) { + private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis, long maxInitialValue) { var newOneShot = LStarLocalTimerMealy.selectOneShotTimer(hypothesis.getSortedTimers(decomposition.state().getLocation()), maxInitialValue); logger.debug("Missing one-shot: setting ({}|{}) to one-shot.", hypothesis.getLocationPrefix(decomposition.state()), newOneShot); return new MissingOneShotResult<>(decomposition.state().getLocation(), newOneShot); } - private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { // Check if there is a one-shot timer expiring at the next time step: List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); @@ -118,12 +122,12 @@ private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecompo } } - private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { // 1: can be a missing discriminator? // Check if correct target in entry w.r.t. discriminator: var transPrefix = hypothesis.getLocationPrefix(decomposition.state()).append(decomposition.input()); - var succState = hypothesis.getSemantics().traceInputs(transPrefix); // successor state in hypothesis + var succState = hypothesis.getSemantics().getState(transPrefix); // successor state in hypothesis var actualSuffixOutput = this.timeOracle.querySuffixOutput(transPrefix, decomposition.discriminator()); var expSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getPrefix(succState), decomposition.discriminator()); @@ -136,33 +140,35 @@ private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDeco // 2: can be a local reset? if (decomposition.state().isStableConfig()) { logger.debug("Inferred missing reset in stable config."); - return new MissingResetResult<>(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + return new MissingResetResult<>(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); } // Non-stable -> explicitly test for missing reset: - var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); - var trans = hypothesis.getTransition(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); + var trans = hypothesis.getTransition(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); if (trans == null) { throw new AssertionError(); } + var successor = hypothesis.getSuccessor(trans); + // Must loop without reset: - if (trans.successor().equals(decomposition.state().getLocation()) && (!isLocalReset)) { + if (successor.equals(decomposition.state().getLocation()) && (!isLocalReset)) { // Must have at least two stable configs: var firstTimer = hypothesis.getSortedTimers(decomposition.state().getLocation()).get(0); if (firstTimer.initial() > 1) { // Must not self-loop in at least one non-entry stable config: var resetTransPrefix = hypothesis.getPrefix(decomposition.state()) - .append(new TimeStepSymbol<>()) // prefix of first stable config that is not entry config + .append(TimedInput.step()) // prefix of first stable config that is not entry config .append(decomposition.input()); // successor at $i$ in that config - Word> suffix = Word.fromLetter(new TimeoutSymbol<>()); + Word> suffix = Word.fromLetter(new TimeoutSymbol<>()); var transSuffixOutput = this.timeOracle.querySuffixOutput(resetTransPrefix, suffix); var entryConfigSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getLocationPrefix(decomposition.state()), suffix); if (transSuffixOutput.equals(entryConfigSuffixOutput)) { logger.debug("Inferred missing reset in non-stable config."); - return new MissingResetResult<>(decomposition.state().getLocation(), (NonDelayingInput) decomposition.input()); + return new MissingResetResult<>(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java index 2430aa394..5c587ba59 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java @@ -2,8 +2,8 @@ import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.oracle.AbstractTimedQueryOracle; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,14 +16,14 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { +public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyInconsPrefixTransformAcex.class); private final AbstractTimedQueryOracle timeOracle; - private final Word> suffix; + private final Word> suffix; - private final Function>, Word>> asTransform; + private final Function>, Word>> asTransform; /** * Constructor. @@ -32,25 +32,25 @@ public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBase * @param timeOracle membership oracle * @param asTransform retrieves the prefix of the system state in the hypothesis addressed by a word */ - public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, AbstractTimedQueryOracle timeOracle, Function>, Word>> asTransform) { + public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, AbstractTimedQueryOracle timeOracle, Function>, Word>> asTransform) { super(suffix.length()); this.timeOracle = timeOracle; this.suffix = suffix; this.asTransform = asTransform; } - public Function>, Word>> getAsTransform() { + public Function>, Word>> getAsTransform() { return asTransform; } @Override - public Word> computeEffect(int index) { + public Word> computeEffect(int index) { // Split the word at our index: - Word> prefix = this.suffix.prefix(index); // everything up to *index* (exclusive) - Word> suffix = this.suffix.subWord(index); // everything from *index* (inclusive) + Word> prefix = this.suffix.prefix(index); // everything up to *index* (exclusive) + Word> suffix = this.suffix.subWord(index); // everything from *index* (inclusive) // Identify access sequence of system state for prefix: - Word> accessSequence = this.asTransform.apply(prefix); + Word> accessSequence = this.asTransform.apply(prefix); // Query *hypothesis state* + *suffix*: return this.timeOracle.querySuffixOutput(accessSequence, suffix); @@ -58,7 +58,7 @@ public Word> computeEffect(int index) { @Override - public boolean checkEffects(Word> eff1, Word> eff2) { + public boolean checkEffects(Word> eff1, Word> eff2) { // Same behavior at different indices? logger.debug(String.format("Comparing (%s) AND (%s): %s", eff1, eff2, eff2.isSuffixOf(eff1))); return eff2.isSuffixOf(eff1); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java index a5a584f79..b2019050a 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.cex; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; /** @@ -15,8 +15,8 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public record LocalTimerMealyOutputInconsistency(Word> prefix, - Word> suffix, - Word> targetOut, - Word> hypOut) { +public record LocalTimerMealyOutputInconsistency(Word> prefix, + Word> suffix, + Word> targetOut, + Word> hypOut) { } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java index 36c4eaa5b..40a0e97c4 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java @@ -1,7 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt.cex.results; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.symbol.time.InputSymbol; /** * The specified symbol is considered to be falsely ignored by the symbol filter. @@ -12,9 +12,9 @@ */ public class FalseIgnoreResult extends CexAnalysisResult { private final S location; - private final NonDelayingInput symbol; + private final InputSymbol symbol; - public FalseIgnoreResult(S location, NonDelayingInput symbol) { + public FalseIgnoreResult(S location, InputSymbol symbol) { this.location = location; this.symbol = symbol; } @@ -23,7 +23,7 @@ public S getLocation() { return location; } - public NonDelayingInput getSymbol() { + public InputSymbol getSymbol() { return symbol; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java index 9e2034f7d..8e8389a43 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm.lstar.mmlt.cex.results; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; /** @@ -12,10 +12,10 @@ */ public class MissingDiscriminatorResult extends CexAnalysisResult { private final S location; - private final LocalTimerMealySemanticInputSymbol input; - private final Word> discriminator; + private final TimedInput input; + private final Word> discriminator; - public MissingDiscriminatorResult(S location, LocalTimerMealySemanticInputSymbol input, Word> discriminator) { + public MissingDiscriminatorResult(S location, TimedInput input, Word> discriminator) { this.location = location; this.input = input; this.discriminator = discriminator; @@ -25,11 +25,11 @@ public S getLocation() { return location; } - public LocalTimerMealySemanticInputSymbol getInput() { + public TimedInput getInput() { return input; } - public Word> getDiscriminator() { + public Word> getDiscriminator() { return discriminator; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java index 3da5d5f01..971895f10 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm.lstar.mmlt.cex.results; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MealyTimerInfo; /** * The provided timer should become one-shot. diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java index e6e538a25..251c3711d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.cex.results; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; /** * There should be a local reset at the specified transition. @@ -13,9 +13,9 @@ */ public class MissingResetResult extends CexAnalysisResult { private final S location; - private final NonDelayingInput input; + private final InputSymbol input; - public MissingResetResult(S location, NonDelayingInput input) { + public MissingResetResult(S location, InputSymbol input) { this.location = location; this.input = input; } @@ -24,7 +24,7 @@ public S getLocation() { return location; } - public LocalTimerMealySemanticInputSymbol getInput() { + public TimedInput getInput() { return input; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java index 53c0228c4..1b5483a1c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.hyp; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.State; import net.automatalib.word.Word; import java.util.List; @@ -25,9 +25,9 @@ public interface IInternalLocalTimerMealyHypothesis { * @param configuration Considered configuration * @return Assigned prefix */ - Word> getPrefix(LocalTimerMealyConfiguration configuration); + Word> getPrefix(State configuration); - Word> getPrefix(Word> prefix); + Word> getPrefix(Word> prefix); /** * Returns the prefix assigned to the location that is active in the provided configuration. @@ -35,7 +35,7 @@ public interface IInternalLocalTimerMealyHypothesis { * @param configuration Considered configuration * @return Assigned prefix */ - Word> getLocationPrefix(LocalTimerMealyConfiguration configuration); + Word> getLocationPrefix(State configuration); /** * Returns a prefix for the given location. @@ -44,12 +44,12 @@ public interface IInternalLocalTimerMealyHypothesis { * @param location Location * @return Location prefix */ - Word> getPrefix(S location); + Word> getPrefix(S location); /** * Convenience method that sorts timers of the provided location by initial value. * * @return Sorted timers. Empty list if no timers. */ - List> getSortedTimers(S location); +// List> getSortedTimers(S location); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java index 5a751c009..3eb165a9a 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java @@ -1,15 +1,16 @@ package de.learnlib.algorithm.lstar.mmlt.hyp; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; -import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; +import net.automatalib.automaton.mmlt.impl.CompactMMLTSemantics; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.automaton.mmlt.SymbolCombiner; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -25,17 +26,17 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyHypothesis implements LocalTimerMealy, IInternalLocalTimerMealyHypothesis { - private final LocalTimerMealy automaton; - private final Map>> prefixMap; // location -> prefix +public class LocalTimerMealyHypothesis implements MMLT, IInternalLocalTimerMealyHypothesis { + private final MMLT automaton; + private final Map>> prefixMap; // location -> prefix - public LocalTimerMealyHypothesis(LocalTimerMealy automaton, Map>> prefixMap) { + public LocalTimerMealyHypothesis(MMLT automaton, Map>> prefixMap) { this.automaton = automaton; this.prefixMap = prefixMap; } @Override - public Word> getPrefix(LocalTimerMealyConfiguration configuration) { + public Word> getPrefix(State configuration) { var locPrefix = getLocationPrefix(configuration); if (configuration.isEntryConfig()) { return locPrefix; // entry distance = 0 @@ -45,14 +46,14 @@ public Word> getPrefix(LocalTimerMealyConf } @Override - public Word> getPrefix(Word> prefix) { - var resultingConfig = getSemantics().traceInputs(prefix); + public Word> getPrefix(Word> prefix) { + var resultingConfig = getSemantics().getState(prefix); return getPrefix(resultingConfig); } @Override - public Word> getLocationPrefix(LocalTimerMealyConfiguration configuration) { + public Word> getLocationPrefix(State configuration) { var locPrefix = this.prefixMap.get(configuration.getLocation()); if (locPrefix == null) throw new AssertionError(); return locPrefix; @@ -60,7 +61,7 @@ public Word> getLocationPrefix(LocalTimerM @Override - public Word> getPrefix(S location) { + public Word> getPrefix(S location) { return prefixMap.get(location); } @@ -70,17 +71,17 @@ public O getSilentOutput() { } @Override - public AbstractSymbolCombiner getOutputCombiner() { + public SymbolCombiner getOutputCombiner() { return automaton.getOutputCombiner(); } @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return automaton.getInputAlphabet(); } @Override - public Alphabet> getUntimedAlphabet() { + public Alphabet> getUntimedAlphabet() { return automaton.getUntimedAlphabet(); } @@ -95,12 +96,12 @@ public Collection getStates() { } @Override - public @Nullable LocalTimerMealyTransition getTransition(S location, LocalTimerMealyInputSymbol input) { + public @Nullable T getTransition(S location, SymbolicInput input) { return automaton.getTransition(location, input); } @Override - public boolean isLocalReset(S location, NonDelayingInput input) { + public boolean isLocalReset(S location, InputSymbol input) { return automaton.isLocalReset(location, input); } @@ -110,9 +111,22 @@ public List> getSortedTimers(S location) { } @Override - public LocalTimerMealySemantics getSemantics() { - return new net.automatalib.automaton.time.impl.mmlt.LocalTimerMealySemantics<>(this); + public MMLTSemantics getSemantics() { + return new CompactMMLTSemantics<>(this); } + @Override + public Void getStateProperty(S state) { + return automaton.getStateProperty(state); + } + @Override + public O getTransitionProperty(T transition) { + return automaton.getTransitionProperty(transition); + } + + @Override + public S getSuccessor(T transition) { + return automaton.getSuccessor(transition); + } } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index bd356ab47..dd41cda7c 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -22,16 +22,18 @@ import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.exception.FormatException; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; import org.testng.annotations.Test; import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeSULCache; +import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -53,7 +55,7 @@ private static void runExperiment(LStarLocalTimerMealy learner, Equ learner.startLearning(); var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); stats.increaseCounter("roundCount", "CEX queries"); int roundCount = 1; @@ -81,7 +83,7 @@ private static void runExperiment(LStarLocalTimerMealy learner, Equ } } - private static void learnModel(String name, LocalTimerMealy automaton, LocalTimerMealyModelParams params, + private static void learnModel(String name, MMLT automaton, LocalTimerMealyModelParams params, FilterMode symbolFilterMode, long seed, boolean printResults) { // Add some stats: @@ -91,11 +93,11 @@ private static void learnModel(String name, LocalTimerMealy a stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getUntimedAlphabet().size()); // Set up a pipeline: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); alphabet.addAll(automaton.getUntimedAlphabet()); // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL - LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton); + LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); LocalTimerMealyStatsSUL statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); LocalTimerMealyTreeSULCache cacheSUL = new LocalTimerMealyTreeSULCache<>(statsAfterCache, params); cacheSUL.setStatsContainer(stats); @@ -113,12 +115,12 @@ private static void learnModel(String name, LocalTimerMealy a chainOracle.setStatsContainer(stats); // Create learner: - List>> suffixes = new ArrayList<>(); + List>> suffixes = new ArrayList<>(); alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // Configure symbol filter: - SymbolFilter, NonDelayingInput> filter = new AcceptAllSymbolFilter<>(); // pass-through + SymbolFilter, InputSymbol> filter = new AcceptAllSymbolFilter<>(); // pass-through switch (symbolFilterMode) { case perfect -> filter = new LocalTimerMealyPerfectSymbolFilter<>(automaton); case random -> filter = new LocalTimerMealyRandomSymbolFilter<>(automaton, 0.1, new Random(seed)); @@ -137,7 +139,7 @@ private static void learnModel(String name, LocalTimerMealy a @Test - public void learnExamplesNoFilter() { + public void learnExamplesNoFilter() throws IOException, FormatException { for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(model.name(), model.automaton(), model.params(), FilterMode.none, 100, true); @@ -147,7 +149,7 @@ public void learnExamplesNoFilter() { } @Test - public void learnExamplesIgnoreAllFilter() { + public void learnExamplesIgnoreAllFilter() throws IOException, FormatException { for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100, true); @@ -157,7 +159,7 @@ public void learnExamplesIgnoreAllFilter() { } @Test - public void learnExamplesPerfectFilter() { + public void learnExamplesPerfectFilter() throws IOException, FormatException { for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100, true); @@ -167,7 +169,7 @@ public void learnExamplesPerfectFilter() { } @Test - public void learnExamplesRandomFilter() { + public void learnExamplesRandomFilter() throws IOException, FormatException { for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100, true); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index edd3bd9f1..a5d0d0079 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -1,39 +1,38 @@ package de.learnlib.algorithm.lstar.mmlt; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; import de.learnlib.oracle.membership.TimedQueryOracle; import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.alphabet.GrowingAlphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.exception.FormatException; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; -import net.automatalib.word.WordBuilder; import org.testng.annotations.Test; - -import java.util.Collections; -import java.util.List; - /** * Tests several different cases of counterexamples. */ @Test public class LStarLocalTimerMealyCounterexampleTests { - private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { + private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { - GrowingAlphabet> alphabet = new GrowingMapAlphabet<>(); + GrowingAlphabet> alphabet = new GrowingMapAlphabet<>(); model.automaton().getUntimedAlphabet().forEach(alphabet::addSymbol); - LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(model.automaton()); + var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); var learner = new LStarLocalTimerMealy<>(alphabet, model.params(), Collections.emptyList(), @@ -81,47 +80,34 @@ private static void learnModel(LocalTimerMealyModel model, Li } - - private Word> getTimeStepSequence(int timeSteps) { - WordBuilder> wbTimeStep = new WordBuilder<>(); - wbTimeStep.repeatAppend(timeSteps, new TimeStepSymbol<>()); - return wbTimeStep.toWord(); - } - - private Word> getTimeoutSequence(int timeouts) { - WordBuilder> wbTimeouts = new WordBuilder<>(); - wbTimeouts.repeatAppend(timeouts, new TimeoutSymbol<>()); - return wbTimeouts.toWord(); - } - @Test - public void testOverApproxReset() { + public void testOverApproxReset() throws IOException, FormatException { // Infers a missing local reset instead of a missing discriminator first. var model = LocalTimerMealyTestUtil.automatonFromFile("over_approx_reset.dot"); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - Word.fromSymbols(new TimeStepSymbol<>(), new NonDelayingInput<>("i"), new TimeoutSymbol<>()) + List>> cex1 = List.of( + Word.fromSymbols(TimedInput.step(), new InputSymbol<>("i"), new TimeoutSymbol<>()) ); learnModel(model, cex1); } @Test - public void testRecursiveDecomp() { + public void testRecursiveDecomp() throws IOException, FormatException { // Triggers recursive decomposition var model = LocalTimerMealyTestUtil.automatonFromFile("recursive_decomp.dot", 3); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( + List>> cex1 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p"), new NonDelayingInput<>("f")), + Word.fromSymbols(new InputSymbol<>("p"), new InputSymbol<>("f")), - Word.fromSymbols(new NonDelayingInput<>("u"), + Word.fromSymbols(new InputSymbol<>("u"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), - new NonDelayingInput<>("f")), + new InputSymbol<>("f")), - Word.fromSymbols(new NonDelayingInput<>("u"), + Word.fromSymbols(new InputSymbol<>("u"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>()) ); @@ -134,21 +120,21 @@ public void testMissingDiscriminators() { var model = LocalTimerMealyExamples.SensorCollector(); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( + List>> cex1 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeStepSymbol<>(), new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + Word.fromSymbols(new InputSymbol<>("p2"), TimedInput.step(), new InputSymbol<>("abort"), new TimeoutSymbol<>()) ); // Missing discriminator at one-shot: - List>> cex2 = List.of( + List>> cex2 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) + Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) ); learnModel(model, cex1); @@ -161,23 +147,23 @@ public void testMissingResets() { model.params().setMaxTimerQueryWaitingTime(40); // Missing reset in stable config: - List>> cex1 = List.of( + List>> cex1 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromSymbols(new NonDelayingInput<>("p1"), new TimeStepSymbol<>(), new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + Word.fromSymbols(new InputSymbol<>("p1"), TimedInput.step(), new InputSymbol<>("abort"), new TimeoutSymbol<>()) ); // Missing reset in non-stable config: - List>> cex2 = List.of( + List>> cex2 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromSymbols(new NonDelayingInput<>("p1"), - new TimeStepSymbol<>(), new TimeStepSymbol<>(), new TimeStepSymbol<>(), - new NonDelayingInput<>("abort"), new TimeoutSymbol<>()) + Word.fromSymbols(new InputSymbol<>("p1"), + TimedInput.step(), TimedInput.step(), TimedInput.step(), + new InputSymbol<>("abort"), new TimeoutSymbol<>()) ); learnModel(model, cex1); @@ -192,22 +178,21 @@ public void testMissingOneShotModelB() { model.params().setMaxTimerQueryWaitingTime(6); // Missing one-shot via bad return to entry: - List>> cex1 = List.of( + List>> cex1 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), getTimeoutSequence(14) - ) + Word.fromWords(Word.fromLetter(new InputSymbol<>("p1")), TimedInput.timeouts(14)) ); // Missing one-shot in location with single timer: - List>> cex2 = List.of( + List>> cex2 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) + Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) ); learnModel(model, cex1); @@ -220,27 +205,22 @@ public void testMissingOneShotModelA() { model.params().setMaxTimerQueryWaitingTime(40); // Missing one-shot via bad output: - List>> cex1 = List.of( + List>> cex1 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), - getTimeStepSequence(40), + Word.fromWords(Word.fromLetter(new InputSymbol<>("p1")), + TimedInput.steps(40), Word.fromLetter(new TimeoutSymbol<>())) // alternatively: new NonDelayingInput<>("abort") ); // Missing one-shot via bad target: - List>> cex2 = List.of( + List>> cex2 = List.of( // Initial hyp: - Word.fromSymbols(new NonDelayingInput<>("p1"), new NonDelayingInput<>("p1")), - Word.fromSymbols(new NonDelayingInput<>("p2"), new NonDelayingInput<>("abort")), - - Word.fromWords(Word.fromLetter(new NonDelayingInput<>("p1")), - getTimeoutSequence(14), - Word.fromLetter(new NonDelayingInput<>("collect")), - Word.fromLetter(new NonDelayingInput<>("p1")) - ) + Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), + Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14), TimedInput.inputs("collect", "p1")) ); learnModel(model, cex1); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java index 1360c54f4..6916ede36 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -1,15 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.serialization.dot.GraphDOT; -import net.automatalib.serialization.dot.LocalTimerMealyGraphvizParser; -import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; - -import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; @@ -18,6 +10,16 @@ import java.util.List; import java.util.stream.Stream; +import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTParsers; +import net.automatalib.serialization.dot.GraphDOT; +import net.automatalib.util.automaton.mmlt.MMLTUtil; + /** * Utility class for loading MMLTs from resources and printing them. */ @@ -26,9 +28,9 @@ public class LocalTimerMealyTestUtil { /** * Prints the provided MMLT to stdout. */ - static void printModel(LocalTimerMealy model) { + static void printModel(MMLT model) { try { - GraphDOT.write(model.transitionGraphView(true, true), System.out); + GraphDOT.write(model.transitionGraphView(model.getInputAlphabet()), System.out, new MMLTVisualizationHelper<>(model, true, true)); } catch (IOException ignored) { } } @@ -53,7 +55,8 @@ static List listModelFiles() { return models; } - static LocalTimerMealyModel automatonFromFile(String name) { + static LocalTimerMealyModel automatonFromFile(String name) + throws IOException, FormatException { return automatonFromFile(name, -1); } @@ -64,14 +67,29 @@ static LocalTimerMealyModel automatonFromFile(String na * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. * @return The automaton model. */ - static LocalTimerMealyModel automatonFromFile(String name, int maxTimerQueryWaiting) { - var modelResource = LocalTimerMealyTestUtil.class.getResource("/mmlt/" + name); - var automaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(modelResource.getFile()), "void", StringSymbolCombiner.getInstance()); + static LocalTimerMealyModel automatonFromFile(String name, int maxTimerQueryWaiting) + throws IOException, FormatException { + + var silentOutput = "void"; + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); - long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? maxTimerQueryWaiting : LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; + try (InputStream is = LocalTimerMealyTestUtil.class.getResourceAsStream("/mmlt/" + name)) { + var model = parser.readModel(is); + var automaton = model.model; - return new LocalTimerMealyModel<>(name, automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); + long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? + maxTimerQueryWaiting : + MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + + return new LocalTimerMealyModel<>(name, + automaton, + new LocalTimerMealyModelParams<>(silentOutput, + maxTimeoutDelay, + maxTimerQueryWaitingFinal, + outputCombiner)); + } } } diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java index 01884f8d3..2f1876991 100644 --- a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java +++ b/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm; -import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; +import net.automatalib.automaton.mmlt.SymbolCombiner; import java.util.Objects; @@ -12,7 +12,7 @@ */ public final class LocalTimerMealyModelParams { private final O silentOutput; - private final AbstractSymbolCombiner outputCombiner; + private final SymbolCombiner outputCombiner; private final long maxTimeoutWaitingTime; private long maxTimerQueryWaitingTime; @@ -34,7 +34,7 @@ public final class LocalTimerMealyModelParams { public LocalTimerMealyModelParams(O silentOutput, long maxTimeoutWaitingTime, long maxTimerQueryWaitingTime, - AbstractSymbolCombiner outputCombiner) { + SymbolCombiner outputCombiner) { this.silentOutput = silentOutput; this.maxTimeoutWaitingTime = maxTimeoutWaitingTime; this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; @@ -53,7 +53,7 @@ public long maxTimerQueryWaitingTime() { return maxTimerQueryWaitingTime; } - public AbstractSymbolCombiner outputCombiner() { + public SymbolCombiner outputCombiner() { return outputCombiner; } diff --git a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java index f36a3fbcc..bd6221d2f 100644 --- a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java @@ -2,9 +2,9 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.query.Query; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.word.Word; import java.util.Collection; @@ -19,7 +19,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public abstract class AbstractTimedQueryOracle implements MembershipOracle.MealyMembershipOracle, LocalTimerMealyOutputSymbol> { +public abstract class AbstractTimedQueryOracle implements MembershipOracle.MealyMembershipOracle, TimedOutput> { /** * Response for a timer query. @@ -33,9 +33,9 @@ public record TimerQueryResult(boolean aborted, List> timer } @Override - public void processQueries(Collection, Word>>> collection) { + public void processQueries(Collection, Word>>> collection) { for (var q : collection) { - DefaultQuery, Word>> query = new DefaultQuery<>(q.getPrefix(), q.getSuffix()); + DefaultQuery, Word>> query = new DefaultQuery<>(q.getPrefix(), q.getSuffix()); this.querySuffixOutput(query); q.answer(query.getOutput()); } @@ -49,14 +49,14 @@ public void processQueries(Collection queryTimers(Word> prefix, long maxTotalWaitingTime); + public abstract TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime); /** * Queries the suffix output for the provided query. * * @param query Input query. */ - public void querySuffixOutput(DefaultQuery, Word>> query) { + public void querySuffixOutput(DefaultQuery, Word>> query) { this.querySuffixOutputInternal(query); } @@ -66,8 +66,8 @@ public void querySuffixOutput(DefaultQuery * @param prefix Prefix * @param suffix Suffix */ - public final Word> querySuffixOutput(Word> prefix, Word> suffix) { - DefaultQuery, Word>> query = new DefaultQuery<>(prefix, suffix); + public final Word> querySuffixOutput(Word> prefix, Word> suffix) { + DefaultQuery, Word>> query = new DefaultQuery<>(prefix, suffix); this.querySuffixOutputInternal(query); return query.getOutput(); } @@ -77,6 +77,6 @@ public final Word> querySuffixOutput(Word, Word>> query); + protected abstract void querySuffixOutputInternal(DefaultQuery, Word>> query); } diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index 40f9b46f8..8c4efe16a 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -18,10 +18,10 @@ import java.util.Collection; import de.learnlib.query.DefaultQuery; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.automaton.fsa.DFA; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.word.Word; @@ -101,5 +101,5 @@ interface MooreEquivalenceOracle extends EquivalenceOracle Input type for non-delaying inputs * @param Output symbol type */ - interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, LocalTimerMealySemanticInputSymbol, Word>>{} + interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, TimedInput, Word>>{} } diff --git a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java index 8ab564a7d..5c6016ae7 100644 --- a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java +++ b/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java @@ -1,6 +1,10 @@ package de.learnlib.sul; -import net.automatalib.alphabet.time.mmlt.*; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -19,7 +23,7 @@ public interface LocalTimerMealySUL { * * @param input Input suffix. */ - default void follow(Word> input) { + default void follow(Word> input) { this.follow(input, -1); } @@ -29,9 +33,9 @@ default void follow(Word> input) { * @param input Input suffix. * @param maxTimeout Max. waiting time to use for timeoutSymbols. */ - default void follow(Word> input, long maxTimeout) { + default void follow(Word> input, long maxTimeout) { for (var s : input) { - if (s instanceof NonDelayingInput ndi) { + if (s instanceof InputSymbol ndi) { this.step(ndi); } else if (s instanceof TimeStepSequence) { this.collectTimeouts((TimeStepSequence) s); @@ -52,7 +56,7 @@ default void follow(Word> input, long maxT * @param input Input * @return SUL output. */ - LocalTimerMealyOutputSymbol step(NonDelayingInput input); + TimedOutput step(InputSymbol input); /** * Waits until a timeout occurs or the provided time is reached. @@ -64,7 +68,7 @@ default void follow(Word> input, long maxT * @return Observed timer output with waiting time, or null, if no timeout was observed. */ @Nullable - LocalTimerMealyOutputSymbol timeoutStep(long maxTime); + TimedOutput timeoutStep(long maxTime); /** * Waits for one time unit and returns the observed output. @@ -73,10 +77,10 @@ default void follow(Word> input, long maxT * The delay of this output is set to zero. */ @Nullable - default LocalTimerMealyOutputSymbol timeStep() { + default TimedOutput timeStep() { var res = this.timeoutStep(1); if (res != null) { - return new LocalTimerMealyOutputSymbol<>(res.getSymbol()); + return new TimedOutput<>(res.symbol()); } return null; } @@ -87,18 +91,18 @@ default LocalTimerMealyOutputSymbol timeStep() { * @param input Waiting time. * @return Observed timeouts. Empty, if none. */ - default Word> collectTimeouts(TimeStepSequence input) { - WordBuilder> wbOutput = new WordBuilder<>(); + default Word> collectTimeouts(TimeStepSequence input) { + WordBuilder> wbOutput = new WordBuilder<>(); - long remainingTime = input.getTimeSteps(); + long remainingTime = input.timeSteps(); while (remainingTime > 0) { - LocalTimerMealyOutputSymbol nextTimeout = this.timeoutStep(remainingTime); + TimedOutput nextTimeout = this.timeoutStep(remainingTime); if (nextTimeout == null) { // No timer will expire during remaining waiting time: break; } else { wbOutput.append(nextTimeout); - remainingTime -= nextTimeout.getDelay(); + remainingTime -= nextTimeout.delay(); } } diff --git a/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java b/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java index 56b80adb4..36dcdcbdf 100644 --- a/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java +++ b/commons/util/src/main/java/de/learnlib/util/mealy/MealyUtil.java @@ -22,6 +22,7 @@ import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; import de.learnlib.oracle.MembershipOracle; import de.learnlib.query.DefaultQuery; +import net.automatalib.automaton.concept.SuffixOutput; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -85,7 +86,7 @@ private static int doFindMismatch(MealyMachine hypothes return NO_MISMATCH; } - public static @Nullable DefaultQuery> shortenCounterExample(MealyMachine hypothesis, + public static @Nullable DefaultQuery> shortenCounterExample(SuffixOutput> hypothesis, DefaultQuery> ceQuery) { Word cePrefix = ceQuery.getPrefix(), ceSuffix = ceQuery.getSuffix(); Word hypOut = hypothesis.computeSuffixOutput(cePrefix, ceSuffix); diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java index 8b2543947..89fa528bf 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java @@ -1,11 +1,12 @@ package de.learnlib.driver.simulator; import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MMLTSemantics; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; import org.checkerframework.checker.nullness.qual.Nullable; @@ -16,50 +17,50 @@ * @param Non-delaying input type. * @param Output symbol type. */ -public class LocalTimerMealySimulatorSUL implements LocalTimerMealySUL { +public class LocalTimerMealySimulatorSUL implements LocalTimerMealySUL { - private final LocalTimerMealy automaton; + private final MMLTSemantics semantics; - private LocalTimerMealyConfiguration currentConfiguration; + private State currentConfiguration; - - public LocalTimerMealySimulatorSUL(LocalTimerMealy automaton) { - this.automaton = automaton; + public LocalTimerMealySimulatorSUL(MMLTSemantics semantics) { + this.semantics = semantics; this.currentConfiguration = null; } @Override - public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + public TimedOutput step(InputSymbol input) { if (this.currentConfiguration == null) { throw new IllegalStateException("Not initialized!"); } - var trans = this.automaton.getSemantics().getTransition(this.currentConfiguration, input); - this.currentConfiguration = trans.target(); - return trans.output(); + var trans = this.semantics.getTransition(this.currentConfiguration, input); + this.currentConfiguration = this.semantics.getSuccessor(trans); + return this.semantics.getTransitionOutput(trans); } @Override - public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + public @Nullable TimedOutput timeoutStep(long maxTime) { if (this.currentConfiguration == null) { throw new IllegalStateException("Not initialized!"); } - var trans = this.automaton.getSemantics().getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); - this.currentConfiguration = trans.target(); + var trans = this.semantics.getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); + this.currentConfiguration = this.semantics.getSuccessor(trans); + var output = this.semantics.getTransitionOutput(trans); - if (trans.output().equals(automaton.getSemantics().getSilentOutput())) { + if (output.equals(semantics.getSilentOutput())) { // No timeout observed: return null; } else { - return trans.output(); + return output; } } @Override public void pre() { - this.currentConfiguration = automaton.getSemantics().getInitialConfiguration().copy(); + this.currentConfiguration = semantics.getInitialState().copy(); } @Override diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 3613d6860..6a2d25b70 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -1,5 +1,10 @@ package de.learnlib.example.mmlt; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; @@ -21,15 +26,13 @@ import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.*; import net.automatalib.serialization.dot.GraphDOT; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - /** * This example shows how to learn a Mealy machine with local timers, * an automaton model for real-time systems. @@ -48,11 +51,11 @@ public static void main(String[] args) { // ====================== // Set up the pipeline: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); alphabet.addAll(model.automaton().getUntimedAlphabet()); // We use a simulator SUL to simulate our automaton: - var sul = new LocalTimerMealySimulatorSUL<>(model.automaton()); + var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); @@ -74,14 +77,14 @@ public static void main(String[] args) { chainOracle.setStatsContainer(stats); // Set up our L* learner: - List>> suffixes = new ArrayList<>(); + List>> suffixes = new ArrayList<>(); alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // A symbol filter allows us to reduce queries by exploiting prior knowledge. // For this example, we use a RandomSymbolFilter. This filter correctly predicts // whether a transition silently self-loops with an accuracy of 90%: - SymbolFilter, NonDelayingInput> filter = + SymbolFilter, InputSymbol> filter = new LocalTimerMealyRandomSymbolFilter<>(model.automaton(), 0.1, new Random(100)); filter = new LocalTimerMealyStatisticsSymbolFilter<>(model.automaton(), filter, stats); @@ -110,7 +113,7 @@ private static void runExperiment(LStarLocalTimerMealy learner, learner.startLearning(); var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); stats.increaseCounter("roundCount", "CEX queries"); int roundCount = 1; @@ -133,7 +136,7 @@ private static void runExperiment(LStarLocalTimerMealy learner, System.out.println("Final hypothesis:"); try { - GraphDOT.write(finalHypothesis.transitionGraphView(true, true), System.out); + GraphDOT.write(finalHypothesis.transitionGraphView(finalHypothesis.getInputAlphabet()), System.out); } catch (IOException ignored) { } new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java index a34f1e023..85a51f6fe 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java @@ -16,10 +16,10 @@ package de.learnlib.filter.cache; import de.learnlib.oracle.EquivalenceOracle; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.automaton.fsa.DFA; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.word.Word; @@ -96,13 +96,13 @@ interface MooreLearningCache extends LearningCache Input type for non-delaying inputs * @param Output symbol type */ - interface LocalTimerMealyLearningCache extends LearningCache, LocalTimerMealySemanticInputSymbol, Word>>{ + interface LocalTimerMealyLearningCache extends LearningCache, TimedInput, Word>>{ /** * Lists all words that are currently in the cache. * If a cached word is a prefix of another cached word, only the longer of them is returned. * * @return List of all stored words. */ - List>> listAllWords(); + List>> listAllWords(); } } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index 111cba4e8..5dd28b855 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -1,9 +1,9 @@ package de.learnlib.filter.cache.mmlt; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; import org.checkerframework.checker.nullness.qual.Nullable; import java.util.Collections; @@ -23,21 +23,21 @@ * @param Output symbol type */ class CacheTreeNode { - private record CacheTreeTransition(LocalTimerMealyOutputSymbol output, CacheTreeNode target) { + private record CacheTreeTransition(TimedOutput output, CacheTreeNode target) { } @Nullable private CacheTreeNode parent; - private LocalTimerMealySemanticInputSymbol parentInput; + private TimedInput parentInput; private long timeout; @Nullable private CacheTreeTransition timeTransition; - private Map, CacheTreeTransition> untimedChildren; + private Map, CacheTreeTransition> untimedChildren; - public CacheTreeNode(CacheTreeNode parent, LocalTimerMealySemanticInputSymbol parentInput) { + public CacheTreeNode(CacheTreeNode parent, TimedInput parentInput) { this.parent = parent; this.parentInput = parentInput; @@ -47,7 +47,7 @@ public CacheTreeNode(CacheTreeNode parent, LocalTimerMealySemanticInputSym this.untimedChildren = new HashMap<>(); } - public CacheTreeNode addTimeChild(long timeout, LocalTimerMealyOutputSymbol output) { + public CacheTreeNode addTimeChild(long timeout, TimedOutput output) { if (this.hasTimeChild()) { throw new IllegalStateException("State already has time child."); } @@ -89,7 +89,7 @@ public long getTimeout() { return timeout; } - public LocalTimerMealyOutputSymbol getTimeoutOutput() { + public TimedOutput getTimeoutOutput() { if (!this.hasTimeChild()) { throw new IllegalStateException(); } @@ -111,7 +111,7 @@ public CacheTreeNode getTimeoutChild() { * @param output Output at the end of the new time sequence * @return New child node */ - public CacheTreeNode splitTimeout(long newTimeout, LocalTimerMealyOutputSymbol output) { + public CacheTreeNode splitTimeout(long newTimeout, TimedOutput output) { if (this.timeTransition == null || newTimeout >= this.getTimeout()) { throw new IllegalArgumentException("Must split at lower timeout."); } @@ -132,29 +132,29 @@ public CacheTreeNode getParent() { return parent; } - public LocalTimerMealySemanticInputSymbol getParentInput() { + public TimedInput getParentInput() { return parentInput; } - public void setParent(CacheTreeNode parent, LocalTimerMealySemanticInputSymbol parentInput) { + public void setParent(CacheTreeNode parent, TimedInput parentInput) { this.parent = parent; this.parentInput = parentInput; } // ------------------------------------------------------- - public boolean hasChild(NonDelayingInput input) { + public boolean hasChild(InputSymbol input) { return this.untimedChildren.containsKey(input); } - public LocalTimerMealyOutputSymbol getOutput(NonDelayingInput input) { + public TimedOutput getOutput(InputSymbol input) { return this.untimedChildren.get(input).output(); } - public CacheTreeNode getChild(NonDelayingInput input) { + public CacheTreeNode getChild(InputSymbol input) { return this.untimedChildren.get(input).target(); } - public CacheTreeNode addUntimedChild(NonDelayingInput input, LocalTimerMealyOutputSymbol output) { + public CacheTreeNode addUntimedChild(InputSymbol input, TimedOutput output) { if (untimedChildren.containsKey(input)) { throw new IllegalArgumentException("State already has an child for this input."); } @@ -164,7 +164,7 @@ public CacheTreeNode addUntimedChild(NonDelayingInput input, LocalTimer return child; } - public Map, CacheTreeTransition> getUntimedChildren() { + public Map, CacheTreeTransition> getUntimedChildren() { return Collections.unmodifiableMap(this.untimedChildren); } } \ No newline at end of file diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java index 64a8cab48..95691cf73 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java @@ -3,8 +3,12 @@ import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; -import net.automatalib.alphabet.time.mmlt.*; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -31,22 +35,22 @@ public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOra this.modelParams = modelParams; } - private DefaultQuery, Word>> queryCache(Word> word) { - WordBuilder> wbInput = new WordBuilder<>(); - WordBuilder> wbOutput = new WordBuilder<>(); + private DefaultQuery, Word>> queryCache(Word> word) { + WordBuilder> wbInput = new WordBuilder<>(); + WordBuilder> wbOutput = new WordBuilder<>(); this.sulCache.pre(); for (var sym : word) { - if (sym instanceof NonDelayingInput ndi) { - LocalTimerMealyOutputSymbol res = this.sulCache.step(ndi); + if (sym instanceof InputSymbol ndi) { + TimedOutput res = this.sulCache.step(ndi); wbInput.append(ndi); wbOutput.append(res); } else if (sym instanceof TimeStepSequence ws) { - LocalTimerMealyOutputSymbol res = this.sulCache.timeoutStep(ws.getTimeSteps()); + TimedOutput res = this.sulCache.timeoutStep(ws.timeSteps()); wbInput.append(ws); if (res == null) { - wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); } else { wbOutput.append(res); } @@ -66,9 +70,9 @@ private DefaultQuery, Word, Word>> convertTimeSequences(DefaultQuery, Word>> originalQuery) { - WordBuilder> wbInput = new WordBuilder<>(); - WordBuilder> wbOutput = new WordBuilder<>(); + private DefaultQuery, Word>> convertTimeSequences(DefaultQuery, Word>> originalQuery) { + WordBuilder> wbInput = new WordBuilder<>(); + WordBuilder> wbOutput = new WordBuilder<>(); int symIdx = 0; var queryInput = originalQuery.getInput(); @@ -79,43 +83,43 @@ private DefaultQuery, Word ds) { + if (inputSym instanceof InputSymbol ds) { wbInput.append(ds); wbOutput.append(outputSym); } else if (inputSym instanceof TimeStepSequence ws) { - if (!outputSym.getSymbol().equals(this.modelParams.silentOutput()) || ws.getTimeSteps() == this.modelParams.maxTimeoutWaitingTime()) { + if (!outputSym.symbol().equals(this.modelParams.silentOutput()) || ws.timeSteps() == this.modelParams.maxTimeoutWaitingTime()) { // Found a timeout OR no timeout after max_delay: wbInput.append(new TimeoutSymbol<>()); wbOutput.append(outputSym); continue; } - if (ws.getTimeSteps() >= this.modelParams.maxTimeoutWaitingTime()) { + if (ws.timeSteps() >= this.modelParams.maxTimeoutWaitingTime()) { throw new AssertionError("Wait time that exceeds max_delay in cache."); } // Special case: silent output before max delay // Cannot replace with "timeout", as this implies wait until max_delay. // Hence: skip subsequent waits until reaching wait with output OR max_delay OR end of word: - long combinedWaitTime = ws.getTimeSteps(); - LocalTimerMealyOutputSymbol combinedOutput = outputSym; + long combinedWaitTime = ws.timeSteps(); + TimedOutput combinedOutput = outputSym; - while (combinedOutput.getSymbol().equals(this.modelParams.silentOutput()) && combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() + while (combinedOutput.symbol().equals(this.modelParams.silentOutput()) && combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() && symIdx < queryInput.length() && queryInput.getSymbol(symIdx) instanceof TimeStepSequence nextWs) { - combinedWaitTime += nextWs.getTimeSteps(); + combinedWaitTime += nextWs.timeSteps(); combinedOutput = queryOutput.getSymbol(symIdx); symIdx++; } - if (combinedWaitTime >= this.modelParams.maxTimeoutWaitingTime() || !combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { + if (combinedWaitTime >= this.modelParams.maxTimeoutWaitingTime() || !combinedOutput.symbol().equals(this.modelParams.silentOutput())) { wbInput.append(new TimeoutSymbol<>()); - if (combinedOutput.getSymbol().equals(this.modelParams.silentOutput())) { + if (combinedOutput.symbol().equals(this.modelParams.silentOutput())) { // Reached max delay -> waiting for any time will now produce no more timeouts: - wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); } else { // Found non-silent output: - wbOutput.append(new LocalTimerMealyOutputSymbol<>(combinedWaitTime, combinedOutput.getSymbol())); + wbOutput.append(new TimedOutput<>(combinedOutput.symbol(), combinedWaitTime)); } } else { // Reached end of word before max_delay OR non-wait symbol -> ignore rest of this word: @@ -129,7 +133,7 @@ private DefaultQuery, Word(wbInput.toWord(), wbOutput.toWord()); } - private DefaultQuery, Word>> reduceToAllowedInputs(Set> allowedInputs, DefaultQuery, Word>> query) { + private DefaultQuery, Word>> reduceToAllowedInputs(Set> allowedInputs, DefaultQuery, Word>> query) { // Find the longest prefix with allowed inputs: int prefixLength = 0; while (prefixLength < query.getInput().length() && allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { @@ -145,17 +149,17 @@ private DefaultQuery, Word, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { - Set> allowedInputs = new HashSet<>(inputs); + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { + Set> allowedInputs = new HashSet<>(inputs); boolean allInputsConsidered = allowedInputs.containsAll(hypothesis.getSemantics().getInputAlphabet()); // Query all cached words: - List>> cachedWords = this.sulCache.listAllWords(); + List>> cachedWords = this.sulCache.listAllWords(); - List, Word>>> counterexamples = new ArrayList<>(); + List, Word>>> counterexamples = new ArrayList<>(); for (var word : cachedWords) { // First, query word as-is (may include wait-symbols in input): - DefaultQuery, Word>> rawCacheQuery = this.queryCache(word); + DefaultQuery, Word>> rawCacheQuery = this.queryCache(word); // Next, convert query that includes wait-symbols to query with timeout-symbols: var convertedQuery = this.convertTimeSequences(rawCacheQuery); @@ -165,7 +169,7 @@ private DefaultQuery, Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); + Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); if (!hypOutput.equals(reducedQuery.getOutput())) { // Hyp gives different output than cache (= SUL): diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java index 0e259485b..26fdf4b5f 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java @@ -10,10 +10,10 @@ import de.learnlib.sul.LocalTimerMealySUL; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.automaton.transducer.impl.CompactMealy; import net.automatalib.graph.Graph; import net.automatalib.graph.concept.GraphViewable; @@ -36,7 +36,7 @@ public class LocalTimerMealyTreeSULCache implements LocalTimerMealySUL currentState; private final LocalTimerMealyModelParams modelParams; - private final LocalTimerMealyOutputSymbol silentOutput; + private final TimedOutput silentOutput; private boolean cacheMiss; private StatsContainer stats = new DummyStatsContainer(); @@ -49,7 +49,7 @@ public void setStatsContainer(StatsContainer container) { public LocalTimerMealyTreeSULCache(LocalTimerMealySUL delegate, LocalTimerMealyModelParams modelParams) { this.delegate = delegate; this.modelParams = modelParams; - this.silentOutput = new LocalTimerMealyOutputSymbol<>(modelParams.silentOutput()); + this.silentOutput = new TimedOutput<>(modelParams.silentOutput()); // Init cache: this.cacheRoot = new CacheTreeNode<>(null, null); @@ -60,7 +60,7 @@ public LocalTimerMealyTreeSULCache(LocalTimerMealySUL delegate, LocalTimer private void followCurrentPrefix() { this.delegate.pre(); - WordBuilder> wbPrefix = new WordBuilder<>(); + WordBuilder> wbPrefix = new WordBuilder<>(); var current = this.currentState; while (current.getParent() != null) { @@ -68,19 +68,19 @@ private void followCurrentPrefix() { current = current.getParent(); } - Word> prefix = wbPrefix.reverse().toWord(); + Word> prefix = wbPrefix.reverse().toWord(); this.delegate.follow(prefix); } @Override - public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + public TimedOutput step(InputSymbol input) { if (this.currentState == null) { throw new IllegalStateException(); } if (!cacheMiss) { if (this.currentState.hasChild(input)) { - LocalTimerMealyOutputSymbol output = this.currentState.getOutput(input); + TimedOutput output = this.currentState.getOutput(input); this.currentState = this.currentState.getChild(input); return output; } @@ -89,14 +89,14 @@ public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { } // Cache miss -> query + insert: - LocalTimerMealyOutputSymbol output = this.delegate.step(input); + TimedOutput output = this.delegate.step(input); this.currentState = this.currentState.addUntimedChild(input, output); return output; } @Override - public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + public @Nullable TimedOutput timeoutStep(long maxTime) { if (currentState == null) { throw new IllegalStateException(); } @@ -115,13 +115,13 @@ public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { return null; // no timer in this state } - LocalTimerMealyOutputSymbol currentOutput = currentState.getTimeoutOutput(); + TimedOutput currentOutput = currentState.getTimeoutOutput(); remaining -= currentState.getTimeout(); this.currentState = this.currentState.getTimeoutChild(); if (!currentOutput.equals(this.silentOutput)) { // Found valid timeout: - return new LocalTimerMealyOutputSymbol<>(maxTime - remaining, currentOutput.getSymbol()); + return new TimedOutput<>(currentOutput.symbol(), maxTime - remaining); } } @@ -134,13 +134,13 @@ public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { } - LocalTimerMealyOutputSymbol timeoutStepResult = this.delegate.timeoutStep(remaining); + TimedOutput timeoutStepResult = this.delegate.timeoutStep(remaining); if (timeoutStepResult == null) { // no timers here this.currentState = this.currentState.addTimeChild(remaining, this.silentOutput); return null; } else { - this.currentState = this.currentState.addTimeChild(timeoutStepResult.getDelay(), new LocalTimerMealyOutputSymbol<>(timeoutStepResult.getSymbol())); - return new LocalTimerMealyOutputSymbol<>(maxTime - remaining + timeoutStepResult.getDelay(), timeoutStepResult.getSymbol()); + this.currentState = this.currentState.addTimeChild(timeoutStepResult.delay(), new TimedOutput<>(timeoutStepResult.symbol())); + return new TimedOutput<>(timeoutStepResult.symbol(), maxTime - remaining + timeoutStepResult.delay()); } } @@ -199,15 +199,15 @@ private List> getLeaves() { } @Override - public List>> listAllWords() { + public List>> listAllWords() { List> leaves = this.getLeaves(); - List>> finalWords = new ArrayList<>(leaves.size()); + List>> finalWords = new ArrayList<>(leaves.size()); for (var leaf : leaves) { // Word builder capacity = number of predecessors: int symCount = leaf.getNumPredecessors(); - WordBuilder> wbInput = new WordBuilder<>(symCount); + WordBuilder> wbInput = new WordBuilder<>(symCount); // Move towards the root: var current = leaf; @@ -228,7 +228,7 @@ public List>> listAllWords() { @Override public Graph graphView() { // Convert tree to a mealy automaton: - CompactMealy, LocalTimerMealyOutputSymbol> mealy = new CompactMealy<>(new GrowingMapAlphabet<>()); + CompactMealy, TimedOutput> mealy = new CompactMealy<>(new GrowingMapAlphabet<>()); Map, Integer> stateMap = new HashMap<>(); stateMap.put(this.cacheRoot, mealy.addInitialState()); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index 461d8d199..b3f17c76f 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -3,8 +3,8 @@ import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.InputSymbol; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -40,13 +40,13 @@ public TimeoutReducerSUL(LocalTimerMealySUL delegate, long maxDelay, Stats } @Override - public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + public TimedOutput step(InputSymbol input) { this.noTimeoutWaitingTime = 0; // might observe expirations again return delegate.step(input); } @Override - public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { + public @Nullable TimedOutput timeoutStep(long maxTime) { if (this.noTimeoutWaitingTime >= this.maxDelay) { return null; // cannot observe expiration until non-delaying input } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java index b0eb0810d..705d9d76d 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java @@ -1,33 +1,31 @@ package de.learnlib.filter.cache.mmlt; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; import de.learnlib.oracle.membership.TimedQueryOracle; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; -import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.common.util.random.RandomUtil; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import org.testng.Assert; import org.testng.annotations.Test; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Random; - @Test public class LocalTimerMealyCacheTest { - private CompactLocalTimerMealy buildBaseModel() { + private CompactMMLT buildBaseModel() { var symbols = List.of("p1", "p2", "abort", "collect"); - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - symbols.forEach(s -> alphabet.add(new NonDelayingInput<>(s))); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> alphabet.add(new InputSymbol<>(s))); - var model = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); var s0 = model.addState(); var s1 = model.addState(); @@ -36,19 +34,19 @@ private CompactLocalTimerMealy buildBaseModel() { model.setInitialState(s0); - model.addTransition(s0, new NonDelayingInput<>("p1"), "go", s1); - model.addTransition(s1, new NonDelayingInput<>("abort"), "ok", s1); - model.addLocalReset(s1, new NonDelayingInput<>("abort")); + model.addTransition(s0, new InputSymbol<>("p1"), s1, "go"); + model.addTransition(s1, new InputSymbol<>("abort"), s1, "ok"); + model.addLocalReset(s1, new InputSymbol<>("abort")); model.addPeriodicTimer(s1, "a", 3, "part"); model.addPeriodicTimer(s1, "b", 6, "noise"); model.addOneShotTimer(s1, "c", 40, "done", s3); - model.addTransition(s0, new NonDelayingInput<>("p2"), "go", s2); - model.addTransition(s2, new NonDelayingInput<>("abort"), "void", s3); + model.addTransition(s0, new InputSymbol<>("p2"), s2, "go"); + model.addTransition(s2, new InputSymbol<>("abort"), s3, "void"); model.addOneShotTimer(s2, "d", 4, "done", s3); - model.addTransition(s3, new NonDelayingInput<>("collect"), "void", s0); + model.addTransition(s3, new InputSymbol<>("collect"), s0, "void"); return model; } @@ -62,7 +60,7 @@ public void testCacheAndSULConsistency() { var automaton = buildBaseModel(); var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); - var sul = new LocalTimerMealySimulatorSUL<>(automaton); + var sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); var timeOracleWithoutCache = new TimedQueryOracle<>(sul, params); @@ -71,7 +69,7 @@ public void testCacheAndSULConsistency() { var listAlphabet = new ArrayList<>(automaton.getSemantics().getInputAlphabet()); // Generate some random words and compare outputs of the cache, SUL, and automaton: - List>> words = new ArrayList<>(); + List>> words = new ArrayList<>(); for (int i = 0; i < 500; i++) { int maxLength = random.nextInt(1, 500); var symbols = RandomUtil.sample(random, listAlphabet, maxLength); @@ -101,14 +99,12 @@ public void testCacheConsistencyTest() { var refAutomaton = buildBaseModel(); var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); - var sul = new LocalTimerMealySimulatorSUL<>(refAutomaton); + var sul = new LocalTimerMealySimulatorSUL<>(refAutomaton.getSemantics()); var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); // Add word to cache: - Word> testWord = Word.fromSymbols( - new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeStepSymbol<>(), new TimeoutSymbol<>() - ); + Word> testWord = Word.fromSymbols(TimedInput.input("p2"), TimedInput.timeout(), TimedInput.step(), TimedInput.timeout()); timeOracleWithCache.querySuffixOutput(Word.epsilon(), testWord); // Create a bad hypothesis: @@ -117,8 +113,8 @@ public void testCacheConsistencyTest() { badAutomaton.addPeriodicTimer(2, "d", 4, "done"); // Query the cache for a counterexample: - Word> expectedCex = Word.fromSymbols( - new NonDelayingInput<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>() + Word> expectedCex = Word.fromSymbols( + new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>() ); var cacheConsistencyTest = cacheSUL.createCacheConsistencyTest(); @@ -128,8 +124,8 @@ public void testCacheConsistencyTest() { // Now test with a reduced alphabet: var symbols = List.of("p1", "abort", "collect"); // not p1 - GrowingMapAlphabet> reducedAlphabet = new GrowingMapAlphabet<>(); - symbols.forEach(s -> reducedAlphabet.add(new NonDelayingInput<>(s))); + GrowingMapAlphabet> reducedAlphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> reducedAlphabet.add(new InputSymbol<>(s))); reducedAlphabet.add(new TimeoutSymbol<>()); // The only counterexample in the cache has the prefix p2, which is now omitted: diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java index eacbb2299..2827840e2 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java @@ -3,9 +3,9 @@ import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -46,32 +46,32 @@ private String withPrefix(String label) { } @Override - public LocalTimerMealyOutputSymbol step(NonDelayingInput input) { + public TimedOutput step(InputSymbol input) { stats.increaseCounter(withPrefix("sul_untimed_syms_counter"), withPrefix("Total untimed symbols")); return this.delegate.step(input); } @Override - public @Nullable LocalTimerMealyOutputSymbol timeoutStep(long maxTime) { - LocalTimerMealyOutputSymbol res = this.delegate.timeoutStep(maxTime); + public @Nullable TimedOutput timeoutStep(long maxTime) { + TimedOutput res = this.delegate.timeoutStep(maxTime); if (res == null) { // Waited until maxTime, no timeout occurred: stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), maxTime); } else { stats.increaseCounter(withPrefix("sul_total_time"), - withPrefix("Total query time"), res.getDelay()); + withPrefix("Total query time"), res.delay()); } return res; } @Override - public Word> collectTimeouts(TimeStepSequence input) { + public Word> collectTimeouts(TimeStepSequence input) { stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), - input.getTimeSteps()); + input.timeSteps()); return this.delegate.collectTimeouts(input); } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java index 33fbeb6a2..0e43aa3a3 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java @@ -5,9 +5,9 @@ import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; @@ -70,7 +70,7 @@ public void setStatsContainer(StatsContainer container) { } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { if (this.oracles.isEmpty()) throw new IllegalStateException("Must specify at least one cex oracle in chain."); int oracleIdx = 0; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java index 51be1240c..bb8eccc03 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java @@ -6,13 +6,13 @@ import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.common.util.string.AbstractPrintable; import net.automatalib.util.automaton.Automata; -import net.automatalib.util.automaton.cover.LocalTimerMealyCover; +import net.automatalib.util.automaton.cover.MMLTCover; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -53,22 +53,22 @@ public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { return findCounterExampleInternal(hypothesis, inputs); } - private DefaultQuery, Word>> findCounterExampleInternal(LocalTimerMealy hypothesis, Collection> inputs) { + private DefaultQuery, Word>> findCounterExampleInternal(MMLT hypothesis, Collection> inputs) { // Make expanded form of hypothesis: - var hypSemModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(hypothesis); + var hypSemModel = ReducedMMLTSemantics.forLocalTimerMealy(hypothesis); // Create a list of symbols (for faster access): - List> listAlphabet = new ArrayList<>(inputs); + List> listAlphabet = new ArrayList<>(inputs); // Identify global suffixes: var globalSuffixes = Automata.characterizingSet(hypSemModel, inputs); // Get list of prefixes in deterministic order (so we can reproduce experiments easily): - var locationCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis, listAlphabet); + var locationCover = MMLTCover.getLocalTimerMealyLocationCover(hypothesis, listAlphabet); var prefixList = locationCover .values() .stream() @@ -80,7 +80,7 @@ private DefaultQuery, Word> hypAnswer = hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); + Word> hypAnswer = hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); // Found inconsistency if outputs do no match: if (!sulAnswer.getOutput().equals(hypAnswer)) { @@ -91,16 +91,16 @@ private DefaultQuery, Word DefaultQuery, Word>> generateTestword(List>> prefixes, - List>> globalSuffixes, - LocalTimerMealy hypothesis, - ReducedLocalTimerMealySemantics hypSemModel, - List> alphabet) { + private DefaultQuery, Word>> generateTestword(List>> prefixes, + List>> globalSuffixes, + MMLT hypothesis, + ReducedMMLTSemantics hypSemModel, + List> alphabet) { - WordBuilder> wbTestWord = new WordBuilder<>(); + WordBuilder> wbTestWord = new WordBuilder<>(); // 1. Pick a random entry config prefix: - Word> prefix = prefixes.get(this.random.nextInt(prefixes.size())); + Word> prefix = prefixes.get(this.random.nextInt(prefixes.size())); wbTestWord.append(prefix); // 2. Add random middle part: @@ -116,14 +116,14 @@ private DefaultQuery, Word> suffix = Word.epsilon(); + Word> suffix = Word.epsilon(); if (this.random.nextBoolean()) { if (!globalSuffixes.isEmpty()) { suffix = globalSuffixes.get(random.nextInt(globalSuffixes.size())); } } else { // Identify configuration reached by prefix: - var currentConfig = hypothesis.getSemantics().traceInputs(wbTestWord.toWord()); + var currentConfig = hypothesis.getSemantics().getState(wbTestWord.toWord()); var state = hypSemModel.getStateForConfiguration(currentConfig, true); var localSuffixes = Automata.stateCharacterizingSet(hypSemModel, alphabet, state); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java index acd60c8ad..936974644 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java @@ -2,10 +2,10 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.util.automaton.mmlt.MMLTUtil; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -21,20 +21,20 @@ */ public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { - private final LocalTimerMealy refModel; + private final MMLT refModel; - public LocalTimerMealySimulatorOracle(LocalTimerMealy refModel) { + public LocalTimerMealySimulatorOracle(MMLT refModel) { this.refModel = refModel; } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { - List> listInputs = new ArrayList<>(inputs); + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { + List> listInputs = new ArrayList<>(inputs); - var separatingWord = LocalTimerMealyUtil.findSeparatingWord(refModel, hypothesis, listInputs); + var separatingWord = MMLTUtil.findSeparatingWord(refModel, hypothesis, listInputs); if (separatingWord != null) { - var sulOutput = refModel.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); - return new DefaultQuery<>(Word.epsilon(), separatingWord, sulOutput); + var sulOutput = refModel.getSemantics().computeOutput(separatingWord); + return new DefaultQuery<>(separatingWord, sulOutput); } else { return null; } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index a55d78df8..410df70b1 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -1,22 +1,30 @@ package de.learnlib.oracle.equivalence.mmlt; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Random; + import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; -import net.automatalib.alphabet.time.mmlt.*; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.common.util.random.RandomUtil; import net.automatalib.common.util.string.AbstractPrintable; -import net.automatalib.util.automaton.cover.LocalTimerMealyCover; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.util.automaton.cover.MMLTCover; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; - import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; - /** * Searches for counterexamples that reveal local resets. *

      @@ -49,17 +57,17 @@ public ResetSearchOracle(AbstractTimedQueryOracle timeOracle, long seed, d this.loopingInputSelectionSeed = seed; } - private List> getLoopingSymbols(S sourceLoc, List> alphabet, LocalTimerMealy hypothesis) { + private List> getLoopingSymbols(S sourceLoc, List> alphabet, MMLT hypothesis) { - List> loopingInputs = new ArrayList<>(); + List> loopingInputs = new ArrayList<>(); for (var sym : alphabet) { - if (!(sym instanceof NonDelayingInput ndi)) { + if (!(sym instanceof InputSymbol ndi)) { continue; // only consider non-delaying inputs, as only these can perform local resets } var trans = hypothesis.getTransition(sourceLoc, ndi); // Collect self-loops: - if (trans == null || (trans.successor().equals(sourceLoc))) { + if (trans == null || Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { loopingInputs.add(sym); } } @@ -68,27 +76,27 @@ private List> getLoopingSymbols(S sour } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(LocalTimerMealy hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { if (loopInsertPerc == 0) { return null; // oracle is disabled } - List> listInputs = new ArrayList<>(inputs); - if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSymbol) || - listInputs.stream().noneMatch(s -> s instanceof TimeoutSymbol)) { + List> listInputs = new ArrayList<>(inputs); + if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSequence) || + listInputs.stream().noneMatch(s -> s instanceof TimeoutSymbol)) { logger.warn("ResetSearchOracle requires inputs to contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); return null; } return this.findCexInternal(hypothesis, listInputs); } - private @Nullable DefaultQuery, Word>> findCexInternal - (LocalTimerMealy hypothesis, List> inputs) { + private @Nullable DefaultQuery, Word>> findCexInternal + (MMLT hypothesis, List> inputs) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: - var stateCover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(hypothesis, inputs); + var stateCover = MMLTCover.getLocalTimerMealyLocationCover(hypothesis, inputs); // Only keep locations that have at least two stable configs (only these can have local resets): - List>> prefixes = new ArrayList<>(); + List>> prefixes = new ArrayList<>(); for (var loc : stateCover.keySet()) { if (!hypothesis.getSortedTimers(loc).isEmpty() && hypothesis.getSortedTimers(loc).get(0).initial() > 1) { @@ -106,12 +114,12 @@ private List> getLoopingSymbols(S sour return null; } - List>> chosenPrefixes = RandomUtil.sampleUnique(locPrefixRandom, prefixes, randPrefixes); + List>> chosenPrefixes = RandomUtil.sampleUnique(locPrefixRandom, prefixes, randPrefixes); for (var prefix : chosenPrefixes) { // Retrieve looping symbols: - var sourceLoc = hypothesis.getSemantics().traceInputs(prefix).getLocation(); + var sourceLoc = hypothesis.getSemantics().getState(prefix).getLocation(); var loopingInputs = getLoopingSymbols(sourceLoc, inputs, hypothesis); if (loopingInputs.isEmpty()) { continue; // no loops @@ -121,15 +129,15 @@ private List> getLoopingSymbols(S sour int randElements = (int) Math.round(loopInsertPerc * loopingInputs.size()); randElements = Math.min(loopingInputs.size(), randElements); - List> chosenLoopingInputs = RandomUtil.sampleUnique(new Random(loopingInputSelectionSeed), loopingInputs, randElements); + List> chosenLoopingInputs = RandomUtil.sampleUnique(new Random(loopingInputSelectionSeed), loopingInputs, randElements); // Create test word: - WordBuilder> wbTestWord = new WordBuilder<>(); + WordBuilder> wbTestWord = new WordBuilder<>(); wbTestWord.append(prefix); - wbTestWord.append(new TimeStepSymbol<>()); + wbTestWord.append(TimedInput.step()); wbTestWord.append(Word.fromList(chosenLoopingInputs)); - wbTestWord.append(new TimeoutSymbol<>()); + wbTestWord.append(TimedInput.timeout()); // Check if counterexample: var testWord = wbTestWord.toWord(); diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java index 712bdd231..235050fee 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java @@ -4,8 +4,12 @@ import de.learnlib.oracle.AbstractTimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.alphabet.time.mmlt.*; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; @@ -64,7 +68,7 @@ private List generateTimerNames() { * @return Observed timeouts. Empty, if none. */ @Override - public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { this.sul.pre(); // Go to location: @@ -130,19 +134,19 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { List> knownTimers = new ArrayList<>(); // Wait for the first timeout: - LocalTimerMealyOutputSymbol firstTimeout = this.sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); + TimedOutput firstTimeout = this.sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); if (firstTimeout == null) { return new TimerQueryResult<>(false, Collections.emptyList()); // no timeouts found } - if (this.modelParams.outputCombiner().isCombinedSymbol(firstTimeout.getSymbol())) { + if (this.modelParams.outputCombiner().isCombinedSymbol(firstTimeout.symbol())) { logger.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); } - knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.getDelay(), firstTimeout.getSymbol())); + knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol())); // Wait for further timeouts: - long currentTimeStep = firstTimeout.getDelay(); // already waited for first timeout + long currentTimeStep = firstTimeout.delay(); // already waited for first timeout boolean inconsistent = false; while (currentTimeStep < maxTotalWaitingTime) { @@ -153,7 +157,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { long nextWaiting = Math.min(nextExpectedTime, maxTotalWaitingTime) - currentTimeStep; // Wait until next timeout: - LocalTimerMealyOutputSymbol nextOutput = this.sul.timeoutStep(nextWaiting); + TimedOutput nextOutput = this.sul.timeoutStep(nextWaiting); if (nextOutput == null) { if (nextExpectedTime <= maxTotalWaitingTime) { // Expected a timeout within max. waiting time but nothing happened: @@ -164,7 +168,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { } // Compare observed timeout with expectation: - long nextActualTime = nextOutput.getDelay() + currentTimeStep; + long nextActualTime = nextOutput.delay() + currentTimeStep; TimerCheckResult evalResult = evaluateNextTimer(nextActualTime, nextExpectedTime, nextOutput, knownTimers); if (evalResult.newTimer() != null) { @@ -185,10 +189,10 @@ private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean } - private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, LocalTimerMealyOutputSymbol nextOutput, List> knownTimers) { + private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: - var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.getSymbol()); + var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol()); return new TimerCheckResult<>(newTimer, false); } else if (nextActualTime == nextExpectedTime) { // Timeout occurred at expected time -> check if matching expected output: @@ -198,7 +202,7 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpe .flatMap(Collection::stream) .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences - Map actualOutputs = this.modelParams.outputCombiner().separateSymbols(nextOutput.getSymbol()) + Map actualOutputs = this.modelParams.outputCombiner().separateSymbols(nextOutput.symbol()) .stream() .collect(Collectors.groupingBy(e -> e, Collectors.counting())); @@ -228,35 +232,35 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpe @Override - protected void querySuffixOutputInternal(DefaultQuery, Word>> query) { + protected void querySuffixOutputInternal(DefaultQuery, Word>> query) { sul.pre(); sul.follow(query.getPrefix(), this.modelParams.maxTimeoutWaitingTime()); // Query the SUL, one symbol at a time: - WordBuilder> wbOutput = new WordBuilder<>(); + WordBuilder> wbOutput = new WordBuilder<>(); for (var s : query.getSuffix()) { if (s instanceof TimeoutSymbol) { - LocalTimerMealyOutputSymbol output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); + TimedOutput output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); if (output != null) { wbOutput.append(output); } else { - wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); // no output in time -> silent + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent } - } else if (s instanceof NonDelayingInput ndi) { - LocalTimerMealyOutputSymbol output = sul.step(ndi); + } else if (s instanceof InputSymbol ndi) { + TimedOutput output = sul.step(ndi); wbOutput.append(output); } else if (s instanceof TimeStepSequence ws) { - if (ws.getTimeSteps() > 1) { + if (ws.timeSteps() > 1) { throw new IllegalArgumentException("Only single wait step allowed in suffix."); } // Wait for a single time step: - LocalTimerMealyOutputSymbol output = sul.timeStep(); + TimedOutput output = sul.timeStep(); if (output != null) { wbOutput.append(output); } else { - wbOutput.append(new LocalTimerMealyOutputSymbol<>(this.modelParams.silentOutput())); // no output in time -> silent + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent } } else { diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java index aebb9a6b8..978d21582 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java @@ -2,8 +2,6 @@ import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.word.Word; /** diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java index 3c6fae763..43b93730b 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java @@ -3,29 +3,28 @@ import de.learnlib.oracle.symbol_filters.PerfectSymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; /** * A symbol filter for MMLTs that correctly accepts and ignores all transitions * that silently self-loop. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyPerfectSymbolFilter extends PerfectSymbolFilter, NonDelayingInput> { +public class LocalTimerMealyPerfectSymbolFilter extends PerfectSymbolFilter, InputSymbol> { - private final LocalTimerMealy automaton; + private final MMLT automaton; - public LocalTimerMealyPerfectSymbolFilter(LocalTimerMealy automaton) { + public LocalTimerMealyPerfectSymbolFilter(MMLT automaton) { this.automaton = automaton; } @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java index cd627496f..a06b6bff2 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java @@ -3,9 +3,9 @@ import de.learnlib.oracle.symbol_filters.RandomSymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; import java.util.Random; @@ -13,15 +13,14 @@ /** * A symbol filter that falsely answers a query with a specified probability. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyRandomSymbolFilter extends RandomSymbolFilter, NonDelayingInput> { +public class LocalTimerMealyRandomSymbolFilter extends RandomSymbolFilter, InputSymbol> { - private final LocalTimerMealy automaton; + private final MMLT automaton; - public LocalTimerMealyRandomSymbolFilter(LocalTimerMealy automaton, + public LocalTimerMealyRandomSymbolFilter(MMLT automaton, double inaccurateProb, Random random) { super(inaccurateProb, random); this.automaton = automaton; @@ -29,7 +28,7 @@ public LocalTimerMealyRandomSymbolFilter(LocalTimerMealy automaton, @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } } \ No newline at end of file diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java index 5323869a0..da7fc904b 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java @@ -4,22 +4,22 @@ import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; -public class LocalTimerMealyStatisticsSymbolFilter extends StatisticsSymbolFilter, NonDelayingInput> { +public class LocalTimerMealyStatisticsSymbolFilter extends StatisticsSymbolFilter, InputSymbol> { - private final LocalTimerMealy automaton; + private final MMLT automaton; - public LocalTimerMealyStatisticsSymbolFilter(LocalTimerMealy automaton, SymbolFilter, NonDelayingInput> delegate, StatsContainer stats) { + public LocalTimerMealyStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatsContainer stats) { super(delegate, stats); this.automaton = automaton; } @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, NonDelayingInput symbol) { + protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java index 07874ba93..e98d92011 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java @@ -1,9 +1,12 @@ package de.learnlib.oracle.symbol_filters.mmlt; +import java.util.Objects; + import de.learnlib.symbol_filter.SymbolFilterResponse; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLTSemantics; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; class LocalTimerMealySymbolFilterUtil { @@ -15,16 +18,21 @@ class LocalTimerMealySymbolFilterUtil { * @param automaton Automaton * @param prefix State prefix * @param symbol Input symbol - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type * @return IGNORE for silent self-loops, ACCEPT otherwise. */ - static SymbolFilterResponse isIgnorable(LocalTimerMealy automaton, Word> prefix, NonDelayingInput symbol) { - var targetConfig = automaton.getSemantics().traceInputs(prefix); - var trans = automaton.getSemantics().getTransition(targetConfig, symbol); + static SymbolFilterResponse isIgnorable(MMLT automaton, Word> prefix, InputSymbol symbol) { + return isIgnorable(automaton.getSemantics(), prefix, symbol); + } + + static SymbolFilterResponse isIgnorable(MMLTSemantics semantics, Word> prefix, InputSymbol symbol) { + var targetConfig = semantics.getState(prefix); + var trans = semantics.getTransition(targetConfig, symbol); + var target = semantics.getSuccessor(trans); + var output = semantics.getTransitionOutput(trans); - boolean ignorable = trans.output().equals(automaton.getSemantics().getSilentOutput()) && targetConfig.equals(trans.target()); + boolean ignorable = Objects.equals(output, semantics.getSilentOutput()) && Objects.equals(targetConfig, target); return ignorable ? SymbolFilterResponse.IGNORE : SymbolFilterResponse.ACCEPT; } diff --git a/pom.xml b/pom.xml index 0ac66d5a9..c1d9e1c0b 100644 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,14 @@ limitations under the License. Developer + + Paul Kogel + TU Berlin, Software and Embedded Systems Engineering + https://www.tu.berlin/sese + + Developer + + Jeroen Meijer j.j.g.meijer@utwente.nl diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java index d7a2b894a..a99378905 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java @@ -1,17 +1,19 @@ package de.learnlib.testsupport.example.mmlt; import de.learnlib.algorithm.LocalTimerMealyModelParams; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; -import net.automatalib.serialization.dot.LocalTimerMealyGraphvizParser; -import net.automatalib.util.automaton.mmlt.LocalTimerMealyUtil; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTParsers; +import net.automatalib.util.automaton.mmlt.MMLTUtil; +import java.io.IOException; +import java.io.InputStream; import java.util.List; public class LocalTimerMealyExamples { - public static List> getAll() { - return List.of( - HVAC(), SCTP(), SensorCollector(), WM(), Oven(), WSN()); + public static List> getAll() { + return List.of(HVAC(), SCTP(), SensorCollector(), WM(), Oven(), WSN()); } /** @@ -21,7 +23,7 @@ public static List> getAll() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel HVAC() { + public static LocalTimerMealyModel HVAC() { return automatonFromFile("HVAC"); } @@ -32,7 +34,7 @@ public static LocalTimerMealyModel HVAC() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel SCTP() { + public static LocalTimerMealyModel SCTP() { return automatonFromFile("SCTP"); } @@ -46,7 +48,7 @@ public static LocalTimerMealyModel SCTP() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel SensorCollector() { + public static LocalTimerMealyModel SensorCollector() { return automatonFromFile("sensor_collector"); } @@ -68,7 +70,7 @@ public static LocalTimerMealyModel SensorCollector() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel WM() { + public static LocalTimerMealyModel WM() { return automatonFromFile("WM"); } @@ -82,7 +84,7 @@ public static LocalTimerMealyModel WM() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel Oven() { + public static LocalTimerMealyModel Oven() { return automatonFromFile("Oven"); } @@ -95,7 +97,7 @@ public static LocalTimerMealyModel Oven() { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel WSN() { + public static LocalTimerMealyModel WSN() { return automatonFromFile("WSN"); } @@ -110,24 +112,31 @@ public static LocalTimerMealyModel WSN() { * the learner has the chance to observe its timeout at least twice. This increases the chance of observing non-periodic behavior. * */ - static LocalTimerMealyModel automatonFromFile(String name) { - - net.automatalib.automaton.time.mmlt.LocalTimerMealy automaton; - try (var modelResource = LocalTimerMealyExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { - automaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(modelResource, "void", StringSymbolCombiner.getInstance()); - } catch (Exception ex) { - throw new RuntimeException("Failed to load automaton from resource " + name, ex); - } - - long maxTimeoutDelay = LocalTimerMealyUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = LocalTimerMealyUtil.getMaximumInitialTimerValue(automaton) * 2; - - if (name.contains("SCTP")) { - maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + static LocalTimerMealyModel automatonFromFile(String name) { + var silentOutput = "void"; + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + + try (InputStream is = LocalTimerMealyExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { + var model = parser.readModel(is); + var automaton = model.model; + + long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + + if (name.contains("SCTP")) { + maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + } + + return new LocalTimerMealyModel<>(name, + automaton, + new LocalTimerMealyModelParams<>(silentOutput, + maxTimeoutDelay, + maxTimerQueryWaitingFinal, + outputCombiner)); + } catch (IOException | FormatException e) { + throw new RuntimeException("Unable to load model " + name, e); } - - return new LocalTimerMealyModel<>(name, automaton, new LocalTimerMealyModelParams<>("void", maxTimeoutDelay, maxTimerQueryWaitingFinal, StringSymbolCombiner.getInstance())); } - } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java index 07cba765e..c4ec7138f 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java @@ -1,7 +1,7 @@ package de.learnlib.testsupport.example.mmlt; import de.learnlib.algorithm.LocalTimerMealyModelParams; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.mmlt.MMLT; /** * Convenience class for storing a name, an automaton and model parameters. @@ -13,8 +13,8 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public record LocalTimerMealyModel(String name, - LocalTimerMealy automaton, +public record LocalTimerMealyModel(String name, + MMLT automaton, LocalTimerMealyModelParams params) { } From 5beb6fb1491fe92bcf807dca284d0e73dd9bb77c Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 19:20:20 +0100 Subject: [PATCH 28/55] adjust to AutomataLib refactorings --- .../lstar/mmlt/LStarLocalTimerMealy.java | 12 ++++++------ .../mmlt/LocalTimerMealyObservationTable.java | 4 ++-- .../lstar/mmlt/LocationTimerInfo.java | 18 +++++++++--------- .../LocalTimerMealyCounterexampleHandler.java | 2 +- .../mmlt/cex/results/MissingOneShotResult.java | 6 +++--- .../mmlt/hyp/LocalTimerMealyHypothesis.java | 7 +++---- .../lstar/mmlt/LocalTimerMealyTestUtil.java | 2 +- .../oracle/AbstractTimedQueryOracle.java | 2 +- .../oracle/membership/TimedQueryOracle.java | 14 +++++++------- 9 files changed, 33 insertions(+), 34 deletions(-) diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index 80b05a882..c40b356d3 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -126,15 +126,15 @@ public LStarLocalTimerMealy(Alphabet> alphabet, * @param Output type * @return New one-shot timer */ - public static MealyTimerInfo selectOneShotTimer(List> sortedTimers, long maxInitialValue) { + public static MealyTimerInfo selectOneShotTimer(List> sortedTimers, long maxInitialValue) { // Filter relevant timers: // Start at timer with the highest initial value. // Ignore all timers whose initial value exceeds the maximum value. // Also ignore timers whose timeout is the multiple of another timer's initial value. - List> relevantTimers = new ArrayList<>(); + List> relevantTimers = new ArrayList<>(); for (int i = sortedTimers.size() - 1; i >= 0; i--) { - MealyTimerInfo timer = sortedTimers.get(i); + MealyTimerInfo timer = sortedTimers.get(i); if (timer.initial() > maxInitialValue) { continue; // could not have expired @@ -144,7 +144,7 @@ public static MealyTimerInfo selectOneShotTimer(List> s // When set to one-shot, these would expire at same time as periodic timer -> non-deterministic behavior! boolean multiple = false; for (int j = 0; j < i; j++) { - MealyTimerInfo otherTimer = sortedTimers.get(j); + MealyTimerInfo otherTimer = sortedTimers.get(j); if (timer.initial() % otherTimer.initial() == 0) { multiple = true; break; @@ -212,7 +212,7 @@ protected void updateOutputs() { TimedOutput output = null; if (inputSym instanceof TimeStepSequence ws) { // Query timer output from table: - MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); + MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); if (timerInfo == null) { throw new AssertionError(); } @@ -354,7 +354,7 @@ private boolean refineHypothesisSingle(DefaultQuery, Word> spRow, MealyTimerInfo timeout) { + private void handleMissingTimeoutChange(Row> spRow, MealyTimerInfo timeout) { var locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); if (locationTimerInfo == null) { throw new AssertionError("Location with missing one-shot timer must have timers."); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java index 09e752f92..b388825a6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java @@ -497,7 +497,7 @@ public boolean isAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } - public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { + public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { var info = this.timerInfoMap.get(prefix); if (info != null) { return info.getTimerInfo(initial); @@ -545,7 +545,7 @@ public List>>> addOutgoingTransition(Row> s return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java index 47a610145..6744c09ed 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java @@ -21,10 +21,10 @@ public class LocationTimerInfo implements Serializable { private static final Logger logger = LoggerFactory.getLogger(LocationTimerInfo.class); - private final Map> timers; // name -> info + private final Map> timers; // name -> info // Keep a list of timers sorted by their initial value. This lets us avoid redundant sort operations. - private final List> sortedTimers; + private final List> sortedTimers; private final Word> prefix; @@ -44,7 +44,7 @@ public Word> getPrefix() { * Adds a local timer to this location. * */ - public void addTimer(MealyTimerInfo timer) { + public void addTimer(MealyTimerInfo timer) { this.timers.put(timer.name(), timer); this.sortedTimers.add(timer); this.sortedTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); @@ -55,13 +55,13 @@ public void removeTimer(String timerName) { logger.warn("Attempted to remove an unknown timer."); return; } - MealyTimerInfo removedTimer = this.timers.remove(timerName); + MealyTimerInfo removedTimer = this.timers.remove(timerName); this.sortedTimers.remove(removedTimer); } @Nullable - public MealyTimerInfo getTimerInfo(long initial) { - Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); + public MealyTimerInfo getTimerInfo(long initial) { + Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); return timer.orElse(null); } @@ -72,7 +72,7 @@ public MealyTimerInfo getTimerInfo(long initial) { * @return Timer with maximum timeout. Null, if no timers defined. */ @Nullable - public MealyTimerInfo getLastTimer() { + public MealyTimerInfo getLastTimer() { if (this.timers.isEmpty()) { return null; } @@ -102,7 +102,7 @@ public void setOneShotTimer(String name) { * * @return List of local timers. Empty, if none. */ - public List> getSortedTimers() { + public List> getSortedTimers() { return Collections.unmodifiableList(sortedTimers); } @@ -113,7 +113,7 @@ public List> getSortedTimers() { * @return Map of local timers. Empty, if none defined. */ @NonNull - public Map> getLocalTimers() { + public Map> getLocalTimers() { return Collections.unmodifiableMap(this.timers); } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java index 56eea0281..90c08ad9f 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -102,7 +102,7 @@ private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { // Check if there is a one-shot timer expiring at the next time step: - List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); + List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); assert !localTimers.isEmpty(); // If location has a one-shot timer, this is the one with the highest initial value: diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java index 971895f10..5f8a0f334 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -11,9 +11,9 @@ */ public class MissingOneShotResult extends CexAnalysisResult { private final S location; - private final MealyTimerInfo timeout; + private final MealyTimerInfo timeout; - public MissingOneShotResult(S location, MealyTimerInfo timeout) { + public MissingOneShotResult(S location, MealyTimerInfo timeout) { this.location = location; this.timeout = timeout; } @@ -22,7 +22,7 @@ public S getLocation() { return location; } - public MealyTimerInfo getTimeout() { + public MealyTimerInfo getTimeout() { return timeout; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java index 3eb165a9a..c40dfcb72 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java @@ -2,7 +2,6 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.mmlt.impl.CompactMMLTSemantics; -import net.automatalib.symbol.time.SymbolicInput; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; @@ -76,7 +75,7 @@ public SymbolCombiner getOutputCombiner() { } @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return automaton.getInputAlphabet(); } @@ -96,7 +95,7 @@ public Collection getStates() { } @Override - public @Nullable T getTransition(S location, SymbolicInput input) { + public @Nullable T getTransition(S location, InputSymbol input) { return automaton.getTransition(location, input); } @@ -106,7 +105,7 @@ public boolean isLocalReset(S location, InputSymbol input) { } @Override - public List> getSortedTimers(S location) { + public List> getSortedTimers(S location) { return automaton.getSortedTimers(location); } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java index 6916ede36..dde0e9fc7 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java @@ -30,7 +30,7 @@ public class LocalTimerMealyTestUtil { */ static void printModel(MMLT model) { try { - GraphDOT.write(model.transitionGraphView(model.getInputAlphabet()), System.out, new MMLTVisualizationHelper<>(model, true, true)); + GraphDOT.write(model.graphView(), System.out, new MMLTVisualizationHelper<>(model, true, true)); } catch (IOException ignored) { } } diff --git a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java index bd6221d2f..b264355e0 100644 --- a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java @@ -28,7 +28,7 @@ public abstract class AbstractTimedQueryOracle implements MembershipOracle * @param timers Identified timers * @param Untimed output suffix type */ - public record TimerQueryResult(boolean aborted, List> timers) { + public record TimerQueryResult(boolean aborted, List> timers) { } diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java index 235050fee..0cb58d510 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java @@ -88,7 +88,7 @@ public TimerQueryResult queryTimers(Word> prefix, long maxTotal * @param currentTime Current time. * @return Next timeout time. */ - private long calcNextExpectedTimeout(List> timeouts, long currentTime) { + private long calcNextExpectedTimeout(List> timeouts, long currentTime) { if (timeouts.isEmpty()) { throw new AssertionError(); } @@ -131,7 +131,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { throw new IllegalArgumentException("Timer query waiting time must be at least max. waiting time for a single timeout."); } - List> knownTimers = new ArrayList<>(); + List> knownTimers = new ArrayList<>(); // Wait for the first timeout: TimedOutput firstTimeout = this.sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); @@ -143,7 +143,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { logger.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); } - knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol())); + knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol(), null)); // Wait for further timeouts: long currentTimeStep = firstTimeout.delay(); // already waited for first timeout @@ -185,14 +185,14 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { return new TimerQueryResult<>(inconsistent, knownTimers); } - private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) { + private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) { } - private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { + private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: - var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol()); + var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol(), null); return new TimerCheckResult<>(newTimer, false); } else if (nextActualTime == nextExpectedTime) { // Timeout occurred at expected time -> check if matching expected output: @@ -220,7 +220,7 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpe .toList(); if (!newOutputs.isEmpty()) { // Same time and more outputs -> add new timer that uses the new outputs: - var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, this.modelParams.outputCombiner().combineSymbols(newOutputs)); + var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, this.modelParams.outputCombiner().combineSymbols(newOutputs), null); return new TimerCheckResult<>(newTimer, false); } } else { From 015bfc3afab5df75871c0e38c8265b61a06ccdb6 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 21:01:41 +0100 Subject: [PATCH 29/55] adjust to AutomataLib refactorings --- .../LocalTimerMealyHypothesisBuilder.java | 8 +++--- .../LocalTimerMealyCounterexampleHandler.java | 4 +-- .../mmlt/hyp/LocalTimerMealyHypothesis.java | 27 +++++++++---------- .../LStarLocalTimerMealyBenchmarkTests.java | 4 +-- ...tarLocalTimerMealyCounterexampleTests.java | 6 ++--- .../de/learnlib/example/mmlt/Example1.java | 5 ++-- .../cache/mmlt/LocalTimerMealyCacheTest.java | 18 ++++++------- .../equivalence/mmlt/ResetSearchOracle.java | 2 +- 8 files changed, 36 insertions(+), 38 deletions(-) diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java index 83fb90c7b..2ad928fa8 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java @@ -35,10 +35,10 @@ record LocalTimerMealyHypothesisBuildResult(MMLT automat } // 2. Create untimed alphabet: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + GrowingMapAlphabet alphabet = new GrowingMapAlphabet<>(); for (var symbol : hypData.getAlphabet()) { if (symbol instanceof InputSymbol ndi) { - alphabet.add(ndi); + alphabet.add(ndi.symbol()); } } @@ -70,7 +70,7 @@ record LocalTimerMealyHypothesisBuildResult(MMLT automat Row> spLocation = locationContentIdMap.get(rowContentId); for (var symbol : alphabet) { - int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); + int symIdx = hypData.getAlphabet().getSymbolIndex(TimedInput.input(symbol)); var transOutput = hypData.getTransitionOutput(spLocation, symIdx); O output = hypData.getModelParams().silentOutput(); // silent by default @@ -91,7 +91,7 @@ record LocalTimerMealyHypothesisBuildResult(MMLT automat hypothesis.addTransition(sourceLocId, symbol, successorLocId, output); // Check for local reset: - var targetTransition = spLocation.getLabel().append(symbol); + var targetTransition = spLocation.getLabel().append(TimedInput.input(symbol)); if (hypData.getTransitionResetSet().contains(targetTransition) && sourceLocId == successorLocId) { hypothesis.addLocalReset(sourceLocId, symbol); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java index 90c08ad9f..9fecb1993 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java @@ -144,8 +144,8 @@ private CexAnalysisResult handleIncorrectTargetNonDelaying(Extended } // Non-stable -> explicitly test for missing reset: - var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); - var trans = hypothesis.getTransition(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); + var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), ((InputSymbol) decomposition.input()).symbol()); + var trans = hypothesis.getTransition(decomposition.state().getLocation(), ((InputSymbol) decomposition.input()).symbol()); if (trans == null) { throw new AssertionError(); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java index c40dfcb72..77c226e03 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java @@ -1,22 +1,21 @@ package de.learnlib.algorithm.lstar.mmlt.hyp; +import java.util.Collection; +import java.util.List; +import java.util.Map; + import net.automatalib.alphabet.Alphabet; -import net.automatalib.automaton.mmlt.impl.CompactMMLTSemantics; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeStepSequence; -import net.automatalib.automaton.mmlt.SymbolCombiner; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.automaton.mmlt.State; -import net.automatalib.automaton.mmlt.MMLTSemantics; +import net.automatalib.automaton.mmlt.SymbolCombiner; +import net.automatalib.automaton.mmlt.impl.CompactMMLTSemantics; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.Collection; -import java.util.List; -import java.util.Map; - /** * An MMLT hypothesis that includes a prefix mapping. * This mapping assigns a short prefix to each location. @@ -75,12 +74,12 @@ public SymbolCombiner getOutputCombiner() { } @Override - public Alphabet> getInputAlphabet() { + public Alphabet getInputAlphabet() { return automaton.getInputAlphabet(); } @Override - public Alphabet> getUntimedAlphabet() { + public Alphabet getUntimedAlphabet() { return automaton.getUntimedAlphabet(); } @@ -95,12 +94,12 @@ public Collection getStates() { } @Override - public @Nullable T getTransition(S location, InputSymbol input) { + public @Nullable T getTransition(S location, I input) { return automaton.getTransition(location, input); } @Override - public boolean isLocalReset(S location, InputSymbol input) { + public boolean isLocalReset(S location, I input) { return automaton.isLocalReset(location, input); } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index dd41cda7c..b811b0f3b 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -21,6 +21,7 @@ import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; +import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.exception.FormatException; import net.automatalib.symbol.time.TimedOutput; @@ -93,8 +94,7 @@ private static void learnModel(String name, MMLT automa stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getUntimedAlphabet().size()); // Set up a pipeline: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addAll(automaton.getUntimedAlphabet()); + Alphabet> alphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet().stream().map(TimedInput::input).toList()); // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index a5d0d0079..0b92ea76a 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -12,7 +12,7 @@ import de.learnlib.query.DefaultQuery; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; -import net.automatalib.alphabet.GrowingAlphabet; +import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.exception.FormatException; import net.automatalib.symbol.time.InputSymbol; @@ -29,8 +29,8 @@ public class LStarLocalTimerMealyCounterexampleTests { private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { - GrowingAlphabet> alphabet = new GrowingMapAlphabet<>(); - model.automaton().getUntimedAlphabet().forEach(alphabet::addSymbol); + Alphabet> alphabet = + new GrowingMapAlphabet<>(model.automaton().getUntimedAlphabet().stream().map(TimedInput::input).toList()); var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 6a2d25b70..f9dda93fe 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -25,6 +25,7 @@ import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; import de.learnlib.util.statistic.container.MapStatsContainer; +import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.symbol.time.InputSymbol; @@ -51,8 +52,8 @@ public static void main(String[] args) { // ====================== // Set up the pipeline: - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addAll(model.automaton().getUntimedAlphabet()); + Alphabet> alphabet = + new GrowingMapAlphabet<>(model.automaton().getUntimedAlphabet().stream().map(TimedInput::input).toList()); // We use a simulator SUL to simulate our automaton: var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java index 705d9d76d..cf4adb9ae 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java @@ -7,6 +7,7 @@ import de.learnlib.algorithm.LocalTimerMealyModelParams; import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; import de.learnlib.oracle.membership.TimedQueryOracle; +import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; @@ -21,10 +22,7 @@ @Test public class LocalTimerMealyCacheTest { private CompactMMLT buildBaseModel() { - var symbols = List.of("p1", "p2", "abort", "collect"); - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - symbols.forEach(s -> alphabet.add(new InputSymbol<>(s))); - + var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); var s0 = model.addState(); @@ -34,19 +32,19 @@ private CompactMMLT buildBaseModel() { model.setInitialState(s0); - model.addTransition(s0, new InputSymbol<>("p1"), s1, "go"); - model.addTransition(s1, new InputSymbol<>("abort"), s1, "ok"); - model.addLocalReset(s1, new InputSymbol<>("abort")); + model.addTransition(s0, "p1", s1, "go"); + model.addTransition(s1, "abort", s1, "ok"); + model.addLocalReset(s1, "abort"); model.addPeriodicTimer(s1, "a", 3, "part"); model.addPeriodicTimer(s1, "b", 6, "noise"); model.addOneShotTimer(s1, "c", 40, "done", s3); - model.addTransition(s0, new InputSymbol<>("p2"), s2, "go"); - model.addTransition(s2, new InputSymbol<>("abort"), s3, "void"); + model.addTransition(s0, "p2", s2, "go"); + model.addTransition(s2, "abort", s3, "void"); model.addOneShotTimer(s2, "d", 4, "done", s3); - model.addTransition(s3, new InputSymbol<>("collect"), s0, "void"); + model.addTransition(s3, "collect", s0, "void"); return model; } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 410df70b1..06b54c92a 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -64,7 +64,7 @@ private List> getLoopingSymbols(S sourceLoc, List ndi)) { continue; // only consider non-delaying inputs, as only these can perform local resets } - var trans = hypothesis.getTransition(sourceLoc, ndi); + var trans = hypothesis.getTransition(sourceLoc, ndi.symbol()); // Collect self-loops: if (trans == null || Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { From 379502aac59a12aa5c47ac9016d190d79389e4b0 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 11 Nov 2025 09:47:58 +0100 Subject: [PATCH 30/55] Using correct function to render MMLT in example. --- examples/src/main/java/de/learnlib/example/mmlt/Example1.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index f9dda93fe..abad1f0cd 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -27,6 +27,7 @@ import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; @@ -137,7 +138,8 @@ private static void runExperiment(LStarLocalTimerMealy learner, System.out.println("Final hypothesis:"); try { - GraphDOT.write(finalHypothesis.transitionGraphView(finalHypothesis.getInputAlphabet()), System.out); + GraphDOT.write(finalHypothesis.graphView(), System.out, + new MMLTVisualizationHelper<>(finalHypothesis, true, true)); } catch (IOException ignored) { } new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); From fce5471d4dedf050a38a02ddcde0498ae59fa6a1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 11 Nov 2025 09:52:09 +0100 Subject: [PATCH 31/55] Replaced getUntimedAlphabet with getInputAlphabet --- .../algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java | 5 ----- .../lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java | 4 ++-- .../lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java | 2 +- .../src/main/java/de/learnlib/example/mmlt/Example1.java | 4 ++-- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java index 77c226e03..cf6bc85a0 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java @@ -78,11 +78,6 @@ public Alphabet getInputAlphabet() { return automaton.getInputAlphabet(); } - @Override - public Alphabet getUntimedAlphabet() { - return automaton.getUntimedAlphabet(); - } - @Override public S getInitialState() { return automaton.getInitialState(); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index b811b0f3b..dc29fb650 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -91,10 +91,10 @@ private static void learnModel(String name, MMLT automa var stats = new MapStatsContainer(); stats.addTextInfo("LocalTimerMealyModel", null, name); stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getUntimedAlphabet().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getInputAlphabet().size()); // Set up a pipeline: - Alphabet> alphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet().stream().map(TimedInput::input).toList()); + Alphabet> alphabet = new GrowingMapAlphabet<>(automaton.getInputAlphabet().stream().map(TimedInput::input).toList()); // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index 0b92ea76a..f58efd3cb 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -30,7 +30,7 @@ public class LStarLocalTimerMealyCounterexampleTests { private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { Alphabet> alphabet = - new GrowingMapAlphabet<>(model.automaton().getUntimedAlphabet().stream().map(TimedInput::input).toList()); + new GrowingMapAlphabet<>(model.automaton().getInputAlphabet().stream().map(TimedInput::input).toList()); var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index abad1f0cd..1412ddd81 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -49,12 +49,12 @@ public static void main(String[] args) { var stats = new MapStatsContainer(); stats.addTextInfo("LocalTimerMealyModel", null, model.name()); stats.setCounter("original_locs", "Locations in original", model.automaton().getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", model.automaton().getUntimedAlphabet().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", model.automaton().getInputAlphabet().size()); // ====================== // Set up the pipeline: Alphabet> alphabet = - new GrowingMapAlphabet<>(model.automaton().getUntimedAlphabet().stream().map(TimedInput::input).toList()); + new GrowingMapAlphabet<>(model.automaton().getInputAlphabet().stream().map(TimedInput::input).toList()); // We use a simulator SUL to simulate our automaton: var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); From 929b07eb704f61389353d2e75e23b5cf0b0fdcf1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 11 Nov 2025 10:24:40 +0100 Subject: [PATCH 32/55] MMLT-Learner now explicitly takes untimed inputs in constructor. --- .../lstar/mmlt/LStarLocalTimerMealy.java | 20 +++++++++++++------ .../LStarLocalTimerMealyBenchmarkTests.java | 6 ++---- ...tarLocalTimerMealyCounterexampleTests.java | 5 +---- .../de/learnlib/example/mmlt/Example1.java | 7 ++----- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java index c40b356d3..5fd29034d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java @@ -29,6 +29,9 @@ import de.learnlib.symbol_filter.SymbolFilterResponse; import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.GrowingAlphabet; +import net.automatalib.alphabet.impl.Alphabets; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.symbol.time.InputSymbol; @@ -70,13 +73,13 @@ public class LStarLocalTimerMealy implements OTLearner> alphabet, + public LStarLocalTimerMealy(Alphabet alphabet, LocalTimerMealyModelParams modelParams, List>> initialSuffixes, AbstractTimedQueryOracle timeOracle, @@ -87,7 +90,7 @@ public LStarLocalTimerMealy(Alphabet> alphabet, /** * Instantiates a new Rivest-Schapire learner for MMLTs. * - * @param alphabet Input alphabet for the semantic automaton + * @param alphabet Alphabet of non-delaying inputs * @param modelParams LocalTimerMealyModel parameters * @param initialSuffixes Initial set of suffixes. May be empty. * @param closingStrategy Closing strategy for the observation table. @@ -95,7 +98,7 @@ public LStarLocalTimerMealy(Alphabet> alphabet, * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. * @param analyzer The strategy for decomposing counterexamples. */ - public LStarLocalTimerMealy(Alphabet> alphabet, + public LStarLocalTimerMealy(Alphabet alphabet, LocalTimerMealyModelParams modelParams, List>> initialSuffixes, ClosingStrategy, ? super Word>> closingStrategy, @@ -108,9 +111,14 @@ public LStarLocalTimerMealy(Alphabet> alphabet, // Prepare hyp data: + // Internally, the learner also stores TimeStepSequences in its alphabet: + GrowingAlphabet> internalAlphabet = new GrowingMapAlphabet<>(); + alphabet.forEach(s -> internalAlphabet.add(TimedInput.input(s))); + // Init hypothesis data: - this.hypData = new LStarLocalTimerMealyHypDataContainer<>(alphabet, modelParams, - new LocalTimerMealyObservationTable<>(alphabet, modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); + this.hypData = new LStarLocalTimerMealyHypDataContainer<>(internalAlphabet, modelParams, + new LocalTimerMealyObservationTable<>(internalAlphabet, + modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); this.cexAnalyzer = new LocalTimerMealyCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); this.symbolFilter = symbolFilter; diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java index dc29fb650..64de36475 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java @@ -94,8 +94,6 @@ private static void learnModel(String name, MMLT automa stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getInputAlphabet().size()); // Set up a pipeline: - Alphabet> alphabet = new GrowingMapAlphabet<>(automaton.getInputAlphabet().stream().map(TimedInput::input).toList()); - // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); LocalTimerMealyStatsSUL statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); @@ -116,7 +114,7 @@ private static void learnModel(String name, MMLT automa // Create learner: List>> suffixes = new ArrayList<>(); - alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); + automaton.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // Configure symbol filter: @@ -130,7 +128,7 @@ private static void learnModel(String name, MMLT automa filter = new LocalTimerMealyStatisticsSymbolFilter<>(automaton, filter, stats); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new LStarLocalTimerMealy<>(alphabet, params, suffixes, timeOracle, filter); + var learner = new LStarLocalTimerMealy<>(automaton.getInputAlphabet(), params, suffixes, timeOracle, filter); learner.setStatsContainer(stats); // Start learning: diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java index f58efd3cb..333711b62 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java @@ -29,13 +29,10 @@ public class LStarLocalTimerMealyCounterexampleTests { private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { - Alphabet> alphabet = - new GrowingMapAlphabet<>(model.automaton().getInputAlphabet().stream().map(TimedInput::input).toList()); - var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); - var learner = new LStarLocalTimerMealy<>(alphabet, model.params(), Collections.emptyList(), + var learner = new LStarLocalTimerMealy<>(model.automaton().getInputAlphabet(), model.params(), Collections.emptyList(), timeOracle, new AcceptAllSymbolFilter<>()); learner.startLearning(); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 1412ddd81..5eaf1aa1f 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -53,9 +53,6 @@ public static void main(String[] args) { // ====================== // Set up the pipeline: - Alphabet> alphabet = - new GrowingMapAlphabet<>(model.automaton().getInputAlphabet().stream().map(TimedInput::input).toList()); - // We use a simulator SUL to simulate our automaton: var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); @@ -80,7 +77,7 @@ public static void main(String[] args) { // Set up our L* learner: List>> suffixes = new ArrayList<>(); - alphabet.forEach(s -> suffixes.add(Word.fromLetter(s))); + model.automaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // A symbol filter allows us to reduce queries by exploiting prior knowledge. @@ -92,7 +89,7 @@ public static void main(String[] args) { filter = new LocalTimerMealyStatisticsSymbolFilter<>(model.automaton(), filter, stats); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new LStarLocalTimerMealy<>(alphabet, model.params(), suffixes, timeOracle, filter); + var learner = new LStarLocalTimerMealy<>(model.automaton().getInputAlphabet(), model.params(), suffixes, timeOracle, filter); learner.setStatsContainer(stats); // Start learning: From 903cdf7a849e349d8c22c690ae2138e67e544af8 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 11 Nov 2025 16:43:37 +0100 Subject: [PATCH 33/55] initial refactorings / cleanups --- algorithms/active/lstar/pom.xml | 18 +- ...merMealy.java => ExtensibleLStarMMLT.java} | 186 +++++++++++--- .../LocalTimerMealyHypothesisBuilder.java | 129 ---------- ...ntainer.java => MMLTHypDataContainer.java} | 14 +- .../algorithm/lstar/mmlt/MMLTHypothesis.java | 83 ++++++ ...onTable.java => MMLTObservationTable.java} | 36 +-- .../lstar/mmlt/cex/ExtendedDecomposition.java | 8 +- ...va => MMLTCounterexampleDecompositor.java} | 28 +-- ...er.java => MMLTCounterexampleHandler.java} | 42 ++-- ...ava => MMLTInconsPrefixTransformAcex.java} | 12 +- ...ency.java => MMLTOutputInconsistency.java} | 8 +- .../mmlt/cex/results/CexAnalysisResult.java | 3 +- .../mmlt/cex/results/FalseIgnoreResult.java | 9 +- .../results/MissingDiscriminatorResult.java | 9 +- .../cex/results/MissingOneShotResult.java | 9 +- .../mmlt/cex/results/MissingResetResult.java | 9 +- .../IInternalLocalTimerMealyHypothesis.java | 55 ---- .../mmlt/hyp/LocalTimerMealyHypothesis.java | 125 --------- ...xtensibleLStarMMLTCounterexampleTests.java | 180 +++++++++++++ .../lstar/it/ExtensibleLStarMMLTIT.java | 238 ++++++++++++++++++ .../LStarLocalTimerMealyBenchmarkTests.java | 179 ------------- ...tarLocalTimerMealyCounterexampleTests.java | 228 ----------------- .../lstar/mmlt/LocalTimerMealyTestUtil.java | 95 ------- ...yModelParams.java => MMLTModelParams.java} | 12 +- .../oracle/AbstractTimedQueryOracle.java | 82 ------ .../de/learnlib/oracle/EquivalenceOracle.java | 2 +- .../de/learnlib/oracle/TimedQueryOracle.java | 40 +++ ...{LocalTimerMealySUL.java => TimedSUL.java} | 20 +- ...imulatorSUL.java => MMLTSimulatorSUL.java} | 7 +- .../de/learnlib/example/mmlt/Example1.java | 52 ++-- .../learnlib/filter/cache/LearningCache.java | 2 +- .../filter/cache/mmlt/CacheTreeNode.java | 2 +- ...est.java => MMLTCacheConsistencyTest.java} | 14 +- ...eeSULCache.java => TimedSULTreeCache.java} | 21 +- .../filter/cache/mmlt/TimeoutReducerSUL.java | 8 +- ...MealyCacheTest.java => MMLTCacheTest.java} | 36 +-- ...ealyStatsSUL.java => CounterTimedSUL.java} | 10 +- .../equivalence/RandomWMethodEQOracle.java | 2 +- .../equivalence/RandomWpMethodEQOracle.java | 2 +- ...yEQOracleChain.java => EQOracleChain.java} | 10 +- ...andomWpOracle.java => RandomWpOracle.java} | 16 +- .../equivalence/mmlt/ResetSearchOracle.java | 12 +- ...atorOracle.java => SimulatorEQOracle.java} | 6 +- ...edQueryOracle.java => TimedSULOracle.java} | 79 +++--- ...lter.java => MMLTPerfectSymbolFilter.java} | 6 +- ...ilter.java => MMLTRandomSymbolFilter.java} | 8 +- ...r.java => MMLTStatisticsSymbolFilter.java} | 6 +- ...terUtil.java => MMLTSymbolFilterUtil.java} | 2 +- ...erMealyExamples.java => MMLTExamples.java} | 34 +-- ...calTimerMealyModel.java => MMLTModel.java} | 8 +- 50 files changed, 964 insertions(+), 1238 deletions(-) rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/{LStarLocalTimerMealy.java => ExtensibleLStarMMLT.java} (68%) delete mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/{LStarLocalTimerMealyHypDataContainer.java => MMLTHypDataContainer.java} (77%) create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/{LocalTimerMealyObservationTable.java => MMLTObservationTable.java} (93%) rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/{LocalTimerMealyCounterexampleDecompositor.java => MMLTCounterexampleDecompositor.java} (83%) rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/{LocalTimerMealyCounterexampleHandler.java => MMLTCounterexampleHandler.java} (76%) rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/{LocalTimerMealyInconsPrefixTransformAcex.java => MMLTInconsPrefixTransformAcex.java} (78%) rename algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/{LocalTimerMealyOutputInconsistency.java => MMLTOutputInconsistency.java} (59%) delete mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java delete mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java create mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java create mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java delete mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java delete mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java delete mode 100644 algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java rename api/src/main/java/de/learnlib/algorithm/{LocalTimerMealyModelParams.java => MMLTModelParams.java} (90%) delete mode 100644 api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java create mode 100644 api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java rename api/src/main/java/de/learnlib/sul/{LocalTimerMealySUL.java => TimedSUL.java} (89%) rename drivers/simulator/src/main/java/de/learnlib/driver/simulator/{LocalTimerMealySimulatorSUL.java => MMLTSimulatorSUL.java} (88%) rename filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/{LocalTimerMealyCacheConsistencyTest.java => MMLTCacheConsistencyTest.java} (94%) rename filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/{LocalTimerMealyTreeSULCache.java => TimedSULTreeCache.java} (91%) rename filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/{LocalTimerMealyCacheTest.java => MMLTCacheTest.java} (76%) rename filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/{LocalTimerMealyStatsSUL.java => CounterTimedSUL.java} (85%) rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{LocalTimerMealyEQOracleChain.java => EQOracleChain.java} (88%) rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{LocalTimerMealyRandomWpOracle.java => RandomWpOracle.java} (91%) rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{LocalTimerMealySimulatorOracle.java => SimulatorEQOracle.java} (84%) rename oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/{TimedQueryOracle.java => TimedSULOracle.java} (83%) rename oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/{LocalTimerMealyPerfectSymbolFilter.java => MMLTPerfectSymbolFilter.java} (72%) rename oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/{LocalTimerMealyRandomSymbolFilter.java => MMLTRandomSymbolFilter.java} (68%) rename oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/{LocalTimerMealyStatisticsSymbolFilter.java => MMLTStatisticsSymbolFilter.java} (65%) rename oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/{LocalTimerMealySymbolFilterUtil.java => MMLTSymbolFilterUtil.java} (97%) rename test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/{LocalTimerMealyExamples.java => MMLTExamples.java} (79%) rename test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/{LocalTimerMealyModel.java => MMLTModel.java} (59%) diff --git a/algorithms/active/lstar/pom.xml b/algorithms/active/lstar/pom.xml index a361afdb0..a6a2d92d5 100644 --- a/algorithms/active/lstar/pom.xml +++ b/algorithms/active/lstar/pom.xml @@ -155,7 +155,23 @@ limitations under the License. - @{argLine} --add-reads=de.learnlib.algorithm.lstar=net.automatalib.util --add-reads=de.learnlib.algorithm.lstar=de.learnlib.filter.statistic + + @{argLine} + --add-reads=de.learnlib.algorithm.lstar=net.automatalib.util + --add-reads=de.learnlib.algorithm.lstar=de.learnlib.filter.statistic + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + @{argLine} + --add-reads=de.learnlib.algorithm.lstar=net.automatalib.util + --add-reads=de.learnlib.algorithm.lstar=de.learnlib.filter.statistic diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java similarity index 68% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java index 5fd29034d..5ae549c8a 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealy.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/ExtensibleLStarMMLT.java @@ -2,25 +2,26 @@ import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Stream; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; -import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; import de.learnlib.algorithm.lstar.closing.ClosingStrategy; -import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyCounterexampleHandler; -import de.learnlib.algorithm.lstar.mmlt.cex.LocalTimerMealyOutputInconsistency; +import de.learnlib.algorithm.lstar.mmlt.cex.MMLTCounterexampleHandler; +import de.learnlib.algorithm.lstar.mmlt.cex.MMLTOutputInconsistency; import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; -import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; import de.learnlib.datastructure.observationtable.OTLearner; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -30,10 +31,10 @@ import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; -import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.common.util.HashUtil; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -49,23 +50,23 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LStarLocalTimerMealy implements OTLearner, TimedInput, Word>>, LearnerStatsProvider { +public class ExtensibleLStarMMLT implements OTLearner, TimedInput, Word>>, LearnerStatsProvider { - private static final Logger logger = LoggerFactory.getLogger(LStarLocalTimerMealy.class); + private static final Logger logger = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); private StatsContainer stats = new DummyStatsContainer(); private final ClosingStrategy, ? super Word>> closingStrategy; - private final AbstractTimedQueryOracle timeOracle; + private final TimedQueryOracle timeOracle; private final SymbolFilter, InputSymbol> symbolFilter; - private final LStarLocalTimerMealyHypDataContainer hypData; + private final MMLTHypDataContainer hypData; // ============================ private final List>> initialSuffixes; - private final LocalTimerMealyCounterexampleHandler cexAnalyzer; + private final MMLTCounterexampleHandler cexAnalyzer; /** * Instantiates a new Rivest-Schapire learner for MMLTs. @@ -79,11 +80,11 @@ public class LStarLocalTimerMealy implements OTLearner alphabet, - LocalTimerMealyModelParams modelParams, - List>> initialSuffixes, - AbstractTimedQueryOracle timeOracle, - SymbolFilter, InputSymbol> symbolFilter) { + public ExtensibleLStarMMLT(Alphabet alphabet, + MMLTModelParams modelParams, + List>> initialSuffixes, + TimedQueryOracle timeOracle, + SymbolFilter, InputSymbol> symbolFilter) { this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); } @@ -98,13 +99,13 @@ public LStarLocalTimerMealy(Alphabet alphabet, * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. * @param analyzer The strategy for decomposing counterexamples. */ - public LStarLocalTimerMealy(Alphabet alphabet, - LocalTimerMealyModelParams modelParams, - List>> initialSuffixes, - ClosingStrategy, ? super Word>> closingStrategy, - AbstractTimedQueryOracle timeOracle, - SymbolFilter, InputSymbol> symbolFilter, - AcexAnalyzer analyzer) { + public ExtensibleLStarMMLT(Alphabet alphabet, + MMLTModelParams modelParams, + List>> initialSuffixes, + ClosingStrategy, ? super Word>> closingStrategy, + TimedQueryOracle timeOracle, + SymbolFilter, InputSymbol> symbolFilter, + AcexAnalyzer analyzer) { this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; this.initialSuffixes = initialSuffixes; @@ -116,11 +117,11 @@ public LStarLocalTimerMealy(Alphabet alphabet, alphabet.forEach(s -> internalAlphabet.add(TimedInput.input(s))); // Init hypothesis data: - this.hypData = new LStarLocalTimerMealyHypDataContainer<>(internalAlphabet, modelParams, - new LocalTimerMealyObservationTable<>(internalAlphabet, - modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); + this.hypData = new MMLTHypDataContainer<>(internalAlphabet, modelParams, + new MMLTObservationTable<>(internalAlphabet, + modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); - this.cexAnalyzer = new LocalTimerMealyCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); + this.cexAnalyzer = new MMLTCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); this.symbolFilter = symbolFilter; } @@ -162,7 +163,7 @@ public static MealyTimerInfo selectOneShotTimer(List MealyTimerInfo selectOneShotTimer(List getInternalLocalTimerMealyHypothesis() { + private MMLTHypothesis getInternalLocalTimerMealyHypothesis() { this.updateOutputs(); - var hyp = LocalTimerMealyHypothesisBuilder.constructHypothesis(this.hypData); - - return new LocalTimerMealyHypothesis<>(hyp.automaton(), hyp.prefixMap()); + return constructHypothesis(this.hypData); } protected List>> selectClosingRows(List>>> unclosed) { @@ -226,7 +225,7 @@ protected void updateOutputs() { } output = new TimedOutput<>(timerInfo.output()); } else { - output = this.timeOracle.querySuffixOutput(prefix, Word.fromLetter(inputSym)).lastSymbol(); + output = this.timeOracle.answerQuery(prefix, Word.fromLetter(inputSym)).lastSymbol(); } if (output != null) { @@ -265,7 +264,7 @@ public boolean refineHypothesis(DefaultQuery, Word> * @return The resulting inconsistency, or null, if the counterexample is not a counterexample. */ @Nullable - private LocalTimerMealyOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, LocalTimerMealyHypothesis hypothesis) { + private MMLTOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, MMLTHypothesis hypothesis) { // 1. Cut example after first deviation: DefaultQuery, Word>> shortQuery = MealyUtil.shortenCounterExample(hypothesis.getSemantics(), ceQuery); if (shortQuery == null) { @@ -278,9 +277,10 @@ private LocalTimerMealyOutputInconsistency toOutputInconsistency(DefaultQu throw new AssertionError("Deviation lost after shortening."); } - return new LocalTimerMealyOutputInconsistency<>(shortQuery.getPrefix(), - shortQuery.getSuffix(), - shortQuery.getOutput(), shortHypOutput); + return new MMLTOutputInconsistency<>(shortQuery.getPrefix(), + shortQuery.getSuffix(), + shortQuery.getOutput(), + shortHypOutput); } private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { @@ -302,7 +302,7 @@ private boolean refineHypothesisSingle(DefaultQuery, Word locSplit) { + if (analysisResult instanceof MissingDiscriminatorResult locSplit) { stats.increaseCounter("INACC_MISSING_DISC", "Inaccuracies: missing discriminators"); @@ -313,14 +313,14 @@ private boolean refineHypothesisSingle(DefaultQuery, Word noReset) { + } else if (analysisResult instanceof MissingResetResult noReset) { stats.increaseCounter("INACC_MISSING_RESETS", "Inaccuracies: missing resets"); // Add missing reset: var resetTrans = hypothesis.getPrefix(noReset.getLocation()).append(noReset.getInput()); this.hypData.getTransitionResetSet().add(resetTrans); - } else if (analysisResult instanceof MissingOneShotResult noAperiodic) { + } else if (analysisResult instanceof MissingOneShotResult noAperiodic) { stats.increaseCounter("INACC_MISSING_OS", "Inaccuracies: missing one-shot timers"); @@ -332,7 +332,7 @@ private boolean refineHypothesisSingle(DefaultQuery, Word falseIgnore) { + } else if (analysisResult instanceof FalseIgnoreResult falseIgnore) { stats.increaseCounter("INACC_MISSING_FI", "Inaccuracies: false ignores"); @@ -430,5 +430,109 @@ public void setStatsContainer(StatsContainer container) { this.cexAnalyzer.setStatsContainer(container); } + /** + * Constructs a hypothesis MMLT from an observation table, inferred local resets, and inferred local timers. + */ + private static MMLTHypothesis constructHypothesis(MMLTHypDataContainer hypData) { + + // 1. Create map that stores link between contentID and short-prefix row: + final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location + for (var spRow : hypData.getTable().getShortPrefixRows()) { + if (locationContentIdMap.containsKey(spRow.getRowContentId())) { + // Multiple sp rows may have same contentID. Thus, assign each id one location: + continue; + } + locationContentIdMap.put(spRow.getRowContentId(), spRow); + } + + // 2. Create untimed alphabet: + GrowingMapAlphabet alphabet = new GrowingMapAlphabet<>(); + for (var symbol : hypData.getAlphabet()) { + if (symbol instanceof InputSymbol ndi) { + alphabet.add(ndi.symbol()); + } + } + + // 3. Prepare objects for automaton, timers and resets: + int numLocations = hypData.getTable().numberOfShortPrefixRows(); + final Map stateMap = new HashMap<>(HashUtil.capacity(numLocations)); // row content id -> state id + final Map>> prefixMap = new HashMap<>(HashUtil.capacity(numLocations)); // state id -> location prefix + var hypothesis = new MMLTHypothesis<>(alphabet, numLocations, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner(), prefixMap); // we pass the prefix map as reference so that we can fill it later + + // 4. Create one state per location: + for (var row : hypData.getTable().getShortPrefixRows()) { + int newStateId = hypothesis.addState(); + stateMap.putIfAbsent(row.getRowContentId(), newStateId); + prefixMap.put(newStateId, row.getLabel()); + + if (row.getLabel().equals(Word.epsilon())) { + hypothesis.setInitialState(newStateId); + } + } + // Ensure initial location: + if (hypothesis.getInitialState() == null) { + throw new IllegalArgumentException("Automaton must have an initial location."); + } + + // 5. Create outgoing transitions for non-delaying inputs: + for (var rowContentId : stateMap.keySet()) { + Row> spLocation = locationContentIdMap.get(rowContentId); + + for (var symbol : alphabet) { + int symIdx = hypData.getAlphabet().getSymbolIndex(TimedInput.input(symbol)); + + var transOutput = hypData.getTransitionOutput(spLocation, symIdx); + O output = hypData.getModelParams().silentOutput(); // silent by default + if (transOutput != null) { + output = transOutput.symbol(); + } + + int successorId; + if (spLocation.getSuccessor(symIdx) == null) { + successorId = spLocation.getRowContentId(); // not in local alphabet -> self-loop + } else { + successorId = spLocation.getSuccessor(symIdx).getRowContentId(); + } + + // Add transition to automaton: + int sourceLocId = stateMap.get(rowContentId); + int successorLocId = stateMap.get(successorId); + hypothesis.addTransition(sourceLocId, symbol, successorLocId, output); + + // Check for local reset: + var targetTransition = spLocation.getLabel().append(TimedInput.input(symbol)); + if (hypData.getTransitionResetSet().contains(targetTransition) && sourceLocId == successorLocId) { + hypothesis.addLocalReset(sourceLocId, symbol); + } + } + + } + + // 6. Add timeout transitions: + for (var rowContentId : stateMap.keySet()) { + Row> spLocation = locationContentIdMap.get(rowContentId); + + var timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); + if (timerInfo == null) { + continue; // no timers + } + + for (var timer : timerInfo.getLocalTimers().values()) { + if (timer.periodic()) { + hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output()); + } else { + // One-shot: use successor from table + TimedInput symbol = new TimeStepSequence<>(timer.initial()); + + int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); + int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); + + hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output(), stateMap.get(successorId)); + } + } + } + + return hypothesis; + } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java deleted file mode 100644 index 2ad928fa8..000000000 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyHypothesisBuilder.java +++ /dev/null @@ -1,129 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt; - -import de.learnlib.datastructure.observationtable.Row; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.automaton.mmlt.impl.CompactMMLT; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeStepSequence; -import net.automatalib.word.Word; - -import java.util.HashMap; -import java.util.Map; - -class LocalTimerMealyHypothesisBuilder { - - record LocalTimerMealyHypothesisBuildResult(MMLT automaton, - Map>> prefixMap) { - - } - - /** - * Constructs a hypothesis MMLT from an observation table, inferred local resets, and inferred local timers. - */ - static LocalTimerMealyHypothesisBuildResult constructHypothesis(LStarLocalTimerMealyHypDataContainer hypData) { - - // 1. Create map that stores link between contentID and short-prefix row: - final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location - for (var spRow : hypData.getTable().getShortPrefixRows()) { - if (locationContentIdMap.containsKey(spRow.getRowContentId())) { - // Multiple sp rows may have same contentID. Thus, assign each id one location: - continue; - } - locationContentIdMap.put(spRow.getRowContentId(), spRow); - } - - // 2. Create untimed alphabet: - GrowingMapAlphabet alphabet = new GrowingMapAlphabet<>(); - for (var symbol : hypData.getAlphabet()) { - if (symbol instanceof InputSymbol ndi) { - alphabet.add(ndi.symbol()); - } - } - - // 3. Prepare objects for automaton, timers and resets: - int numLocations = hypData.getTable().numberOfShortPrefixRows(); - var hypothesis = new CompactMMLT<>(alphabet, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner()); - - final Map stateMap = new HashMap<>(numLocations); // row content id -> state id - - final Map>> prefixMap = new HashMap<>(numLocations); // state id -> location prefix - - // 4. Create one state per location: - for (var row : hypData.getTable().getShortPrefixRows()) { - int newStateId = hypothesis.addState(); - stateMap.putIfAbsent(row.getRowContentId(), newStateId); - prefixMap.put(newStateId, row.getLabel()); - - if (row.getLabel().equals(Word.epsilon())) { - hypothesis.setInitialState(newStateId); - } - } - // Ensure initial location: - if (hypothesis.getInitialState() == null) { - throw new IllegalArgumentException("Automaton must have an initial location."); - } - - // 5. Create outgoing transitions for non-delaying inputs: - for (var rowContentId : stateMap.keySet()) { - Row> spLocation = locationContentIdMap.get(rowContentId); - - for (var symbol : alphabet) { - int symIdx = hypData.getAlphabet().getSymbolIndex(TimedInput.input(symbol)); - - var transOutput = hypData.getTransitionOutput(spLocation, symIdx); - O output = hypData.getModelParams().silentOutput(); // silent by default - if (transOutput != null) { - output = transOutput.symbol(); - } - - int successorId; - if (spLocation.getSuccessor(symIdx) == null) { - successorId = spLocation.getRowContentId(); // not in local alphabet -> self-loop - } else { - successorId = spLocation.getSuccessor(symIdx).getRowContentId(); - } - - // Add transition to automaton: - int sourceLocId = stateMap.get(rowContentId); - int successorLocId = stateMap.get(successorId); - hypothesis.addTransition(sourceLocId, symbol, successorLocId, output); - - // Check for local reset: - var targetTransition = spLocation.getLabel().append(TimedInput.input(symbol)); - if (hypData.getTransitionResetSet().contains(targetTransition) && sourceLocId == successorLocId) { - hypothesis.addLocalReset(sourceLocId, symbol); - } - } - - } - - // 6. Add timeout transitions: - for (var rowContentId : stateMap.keySet()) { - Row> spLocation = locationContentIdMap.get(rowContentId); - - var timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); - if (timerInfo == null) { - continue; // no timers - } - - for (var timer : timerInfo.getLocalTimers().values()) { - if (timer.periodic()) { - hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output()); - } else { - // One-shot: use successor from table - TimedInput symbol = new TimeStepSequence<>(timer.initial()); - - int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); - int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); - - hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output(), stateMap.get(successorId)); - } - } - } - - return new LocalTimerMealyHypothesisBuildResult<>(hypothesis, prefixMap); - } - -} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java similarity index 77% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java index 5c225858b..7355e6ada 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyHypDataContainer.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm.lstar.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.datastructure.observationtable.Row; import net.automatalib.alphabet.Alphabet; import net.automatalib.symbol.time.TimedInput; @@ -20,16 +20,16 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -class LStarLocalTimerMealyHypDataContainer { +class MMLTHypDataContainer { private final Alphabet> alphabet; - private final LocalTimerMealyObservationTable table; + private final MMLTObservationTable table; private final Map>, TimedOutput> transitionOutputMap; private final Set>> transitionResetSet; // all transitions that trigger a reset - private final LocalTimerMealyModelParams modelParams; + private final MMLTModelParams modelParams; - public LStarLocalTimerMealyHypDataContainer(Alphabet> alphabet, LocalTimerMealyModelParams modelParams, LocalTimerMealyObservationTable table) { + public MMLTHypDataContainer(Alphabet> alphabet, MMLTModelParams modelParams, MMLTObservationTable table) { this.alphabet = alphabet; this.modelParams = modelParams; this.table = table; @@ -49,7 +49,7 @@ protected TimedOutput getTransitionOutput(Row> stateRow, int in } - public LocalTimerMealyModelParams getModelParams() { + public MMLTModelParams getModelParams() { return modelParams; } @@ -58,7 +58,7 @@ public Alphabet> getAlphabet() { } - public LocalTimerMealyObservationTable getTable() { + public MMLTObservationTable getTable() { return table; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java new file mode 100644 index 000000000..bbf3fc734 --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java @@ -0,0 +1,83 @@ +package de.learnlib.algorithm.lstar.mmlt; + +import java.util.Map; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.automaton.mmlt.SymbolCombiner; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.word.Word; + +/** + * An MMLT hypothesis that includes a prefix mapping. This mapping assigns a short prefix to each location. + * + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type + */ +public class MMLTHypothesis extends CompactMMLT { + + private final Map>> prefixMap; // location -> prefix + + MMLTHypothesis(Alphabet alphabet, + int sizeHint, + O silentOuput, + SymbolCombiner outputCombiner, + Map>> prefixMap) { + super(alphabet, sizeHint, silentOuput, outputCombiner); + this.prefixMap = prefixMap; + } + + /** + * Returns the prefix assigned to the provided configuration. The assigned prefix is the concatenation of the prefix + * assigned to the active location and the minimal number of time steps needed to reach the configuration after + * entering its location (= entry distance). + * + * @param configuration + * Considered configuration + * + * @return Assigned prefix + */ + public Word> getPrefix(State configuration) { + var locPrefix = getLocationPrefix(configuration); + if (configuration.isEntryConfig()) { + return locPrefix; // entry distance = 0 + } else { + return locPrefix.append(new TimeStepSequence<>(configuration.getEntryDistance())); + } + } + + public Word> getPrefix(Word> prefix) { + var resultingConfig = getSemantics().getState(prefix); + return getPrefix(resultingConfig); + } + + /** + * Returns the prefix assigned to the location that is active in the provided configuration. + * + * @param configuration + * Considered configuration + * + * @return Assigned prefix + */ + public Word> getLocationPrefix(State configuration) { + var locPrefix = this.prefixMap.get(configuration.getLocation()); + if (locPrefix == null) {throw new AssertionError();} + return locPrefix; + } + + /** + * Returns a prefix for the given location. This prefix is deterministic in the RS learner. + * + * @param location + * Location + * + * @return Location prefix + */ + public Word> getPrefix(Integer location) { + return prefixMap.get(location); + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java similarity index 93% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java index b388825a6..8c58dbe3b 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyObservationTable.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTObservationTable.java @@ -3,7 +3,7 @@ import de.learnlib.datastructure.observationtable.MutableObservationTable; import de.learnlib.datastructure.observationtable.Row; import de.learnlib.datastructure.observationtable.RowImpl; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.oracle.MembershipOracle; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; @@ -38,9 +38,9 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyObservationTable implements MutableObservationTable, Word>> { +public class MMLTObservationTable implements MutableObservationTable, Word>> { - private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyObservationTable.class); + private static final Logger logger = LoggerFactory.getLogger(MMLTObservationTable.class); private final SymbolFilter, InputSymbol> symbolFilter; @@ -62,8 +62,8 @@ public class LocalTimerMealyObservationTable implements MutableObservation private final long minTimerQueryWaitTime; private final TimedOutput silentOutput; // used for symbol filtering - public LocalTimerMealyObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, - @NonNull SymbolFilter, InputSymbol> symbolFilter, O silentOutput) { + public MMLTObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, + @NonNull SymbolFilter, InputSymbol> symbolFilter, O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; @@ -86,11 +86,11 @@ public LocalTimerMealyObservationTable(Alphabet> alphabet, long mi * * @param location Source location. */ - private void identifyLocalTimers(LocationTimerInfo location, AbstractTimedQueryOracle timeOracle) { + private void identifyLocalTimers(LocationTimerInfo location, TimedQueryOracle timeOracle) { var timerQueryResponse = timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); if (timerQueryResponse.aborted()) { - var newOneShot = LStarLocalTimerMealy.selectOneShotTimer(timerQueryResponse.timers(), Long.MAX_VALUE); + var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(timerQueryResponse.timers(), Long.MAX_VALUE); newOneShot.setOneShot(); } @@ -142,7 +142,7 @@ private RowImpl> addInitialLocation() { * @param newRow Newly-added short prefix row * @param timeOracle Time oracle */ - private void initLocation(RowImpl> newRow, AbstractTimedQueryOracle timeOracle) { + private void initLocation(RowImpl> newRow, TimedQueryOracle timeOracle) { LocationTimerInfo timerInfo = new LocationTimerInfo<>(newRow.getLabel()); this.identifyLocalTimers(timerInfo, timeOracle); @@ -166,7 +166,7 @@ private void initLocation(RowImpl> newRow, AbstractTimedQueryOracl * @param timeOracle Time query oracle * @return New transitions */ - private List>> createOutgoingTransitions(RowImpl> spRow, AbstractTimedQueryOracle timeOracle) { + private List>> createOutgoingTransitions(RowImpl> spRow, TimedQueryOracle timeOracle) { List>> transitions = new ArrayList<>(); Word> sp = spRow.getLabel(); @@ -187,7 +187,7 @@ private List>> createOutgoingTransitions(RowImpl) sym); if (filterResponse == SymbolFilterResponse.IGNORE) { // Verify that output is silent: - var response = timeOracle.querySuffixOutput(sp, Word.fromLetter(sym)); + var response = timeOracle.answerQuery(sp, Word.fromLetter(sym)); assert response.size() == 1; if (!response.firstSymbol().equals(silentOutput)) { // Not silent -> cannot be silent self-loop: @@ -299,7 +299,7 @@ public List>>> initialize(List>> initi if (!initialShortPrefixes.isEmpty()) { throw new IllegalArgumentException("Init with short prefixes is not supported."); } - if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { + if (!(oracle instanceof TimedQueryOracle timedOracle)) { throw new IllegalArgumentException("Must use timed oracle!"); } @@ -319,12 +319,12 @@ public List>>> initialize(List>> initi return this.findUnclosedTransitions(); } - private void queryAllSuffixes(RowImpl> row, AbstractTimedQueryOracle timedOracle) { + private void queryAllSuffixes(RowImpl> row, TimedQueryOracle timedOracle) { Word> prefix = row.getLabel(); List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); for (Word> suffix : this.suffixes) { - Word> output = timedOracle.querySuffixOutput(prefix, suffix); + Word> output = timedOracle.answerQuery(prefix, suffix); suffixOutputs.add(output); } @@ -355,7 +355,7 @@ public boolean isInitialConsistencyCheckRequired() { @Override public List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { + if (!(oracle instanceof TimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } @@ -382,7 +382,7 @@ public List>>> addSuffixes(Collection> suffix : newSuffixList) { - Word> output = timedOracle.querySuffixOutput(row.getLabel(), suffix); + Word> output = timedOracle.answerQuery(row.getLabel(), suffix); updatedOutputs.add(output); } @@ -399,7 +399,7 @@ public List>>> addShortPrefixes(List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof AbstractTimedQueryOracle timedOracle)) { + if (!(oracle instanceof TimedQueryOracle timedOracle)) { throw new IllegalArgumentException(); } @@ -521,7 +521,7 @@ public LocationTimerInfo getLocationTimerInfo(Row> sp) { * @param timeOracle Oracle * @return List of unclosed rows. Empty, if none. */ - public List>>> addOutgoingTransition(Row> spRow, TimedInput symbol, AbstractTimedQueryOracle timeOracle) { + public List>>> addOutgoingTransition(Row> spRow, TimedInput symbol, TimedQueryOracle timeOracle) { if (!this.alphabet.containsSymbol(symbol)) { throw new IllegalArgumentException("Unknown symbol."); } @@ -545,7 +545,7 @@ public List>>> addOutgoingTransition(Row> s return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, AbstractTimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, TimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java index 4a67a376c..6dfde3bbe 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java @@ -16,11 +16,11 @@ * @param discriminator If not null: transition has incorrect target * @param Input type for non-delaying inputs */ -record ExtendedDecomposition(State state, - @NonNull TimedInput input, - @Nullable Word> discriminator) { +record ExtendedDecomposition(State state, + @NonNull TimedInput input, + @Nullable Word> discriminator) { - public ExtendedDecomposition(State state, @NonNull TimedInput input) { + public ExtendedDecomposition(State state, @NonNull TimedInput input) { this(state, input, null); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java similarity index 83% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java index 1ce52be2a..6a0713739 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleDecompositor.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.cex; import de.learnlib.acex.AcexAnalyzer; -import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; +import de.learnlib.oracle.TimedQueryOracle; import net.automatalib.automaton.mmlt.State; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -18,21 +18,21 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -class LocalTimerMealyCounterexampleDecompositor { +class MMLTCounterexampleDecompositor { - private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleDecompositor.class); + private static final Logger logger = LoggerFactory.getLogger(MMLTCounterexampleDecompositor.class); - private final AbstractTimedQueryOracle timeOracle; + private final TimedQueryOracle timeOracle; private final AcexAnalyzer acexAnalyzer; - public LocalTimerMealyCounterexampleDecompositor(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { + public MMLTCounterexampleDecompositor(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { this.timeOracle = timeOracle; this.acexAnalyzer = acexAnalyzer; } - ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputInconsistency outIncons, - LocalTimerMealyHypothesis hypothesis) { + ExtendedDecomposition findExtendedDecomposition(MMLTOutputInconsistency outIncons, + MMLTHypothesis hypothesis) { if (outIncons.suffix().length() == 1) { // Incorrect output: @@ -41,7 +41,7 @@ ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputIn } // Verify breakpoint condition: - LocalTimerMealyInconsPrefixTransformAcex acex = new LocalTimerMealyInconsPrefixTransformAcex<>(outIncons.suffix(), timeOracle, + MMLTInconsPrefixTransformAcex acex = new MMLTInconsPrefixTransformAcex<>(outIncons.suffix(), timeOracle, w -> hypothesis.getPrefix(outIncons.prefix().concat(w))); if (acex.testEffects(0, acex.getLength() - 1)) { @@ -78,15 +78,15 @@ ExtendedDecomposition findExtendedDecomposition(LocalTimerMealyOutputIn * @param decomposition Extended decomposition * @return Post-processed decomposition */ - ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, - LocalTimerMealyHypothesis hypothesis) { + ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { if (!(decomposition.input() instanceof TimeoutSymbol)) { return decomposition; } var statePrefix = hypothesis.getPrefix(decomposition.state()); var hypOutput = hypothesis.getSemantics().computeSuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); - var sulOutput = timeOracle.querySuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); + var sulOutput = timeOracle.answerQuery(statePrefix, Word.fromLetter(decomposition.input())); if (decomposition.isForIncorrectOutput()) { // Incorrect output at tout: @@ -102,7 +102,7 @@ ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecompos } // if minimum time is zero (= no timeout) or one, need to append empty word to prefix: - State newPrefixState; + State newPrefixState; if (minWaitTime <= 1) { newPrefixState = decomposition.state(); } else { @@ -119,7 +119,7 @@ ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecompos } long waitTime = hypOutput.firstSymbol().delay(); - State newPrefixState; + State newPrefixState; if (waitTime <= 1) { newPrefixState = decomposition.state(); } else { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java similarity index 76% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java index 9fecb1993..b1aabe4ea 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java @@ -3,14 +3,14 @@ import java.util.List; import de.learnlib.acex.AcexAnalyzer; -import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.algorithm.lstar.mmlt.cex.results.CexAnalysisResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; -import de.learnlib.algorithm.lstar.mmlt.hyp.LocalTimerMealyHypothesis; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; @@ -34,17 +34,17 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyCounterexampleHandler implements LearnerStatsProvider { - private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyCounterexampleHandler.class); +public class MMLTCounterexampleHandler implements LearnerStatsProvider { + private static final Logger logger = LoggerFactory.getLogger(MMLTCounterexampleHandler.class); private final SymbolFilter, InputSymbol> symbolFilter; private StatsContainer stats = new DummyStatsContainer(); - protected final AbstractTimedQueryOracle timeOracle; - private final LocalTimerMealyCounterexampleDecompositor decompositor; + protected final TimedQueryOracle timeOracle; + private final MMLTCounterexampleDecompositor decompositor; - public LocalTimerMealyCounterexampleHandler(AbstractTimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, InputSymbol> symbolFilter) { + public MMLTCounterexampleHandler(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, InputSymbol> symbolFilter) { this.timeOracle = timeOracle; - this.decompositor = new LocalTimerMealyCounterexampleDecompositor<>(timeOracle, acexAnalyzer); + this.decompositor = new MMLTCounterexampleDecompositor<>(timeOracle, acexAnalyzer); this.symbolFilter = symbolFilter; } @@ -53,8 +53,8 @@ public void setStatsContainer(StatsContainer container) { this.stats = container; } - public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutputInconsistency outIncons, - LocalTimerMealyHypothesis hypothesis) { + public CexAnalysisResult analyzeInconsistency(MMLTOutputInconsistency outIncons, + MMLTHypothesis hypothesis) { // Search for an extended decomposition: var decomposition = decompositor.findExtendedDecomposition(outIncons, hypothesis); @@ -71,13 +71,13 @@ public CexAnalysisResult analyzeInconsistency(LocalTimerMealyOutput } } - private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { // Transition with incorrect output always implies missing one-shot timer: logger.debug("Found missing one-shot via incorrect output."); return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); } - private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { if (decomposition.input() instanceof InputSymbol ndi) { // If decomposition at non-delaying input + considered as self-loop, treat as false ignore: if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == SymbolFilterResponse.IGNORE) { @@ -93,13 +93,13 @@ private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition selectOneShotTimer(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis, long maxInitialValue) { - var newOneShot = LStarLocalTimerMealy.selectOneShotTimer(hypothesis.getSortedTimers(decomposition.state().getLocation()), maxInitialValue); + private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis, long maxInitialValue) { + var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(hypothesis.getSortedTimers(decomposition.state().getLocation()), maxInitialValue); logger.debug("Missing one-shot: setting ({}|{}) to one-shot.", hypothesis.getLocationPrefix(decomposition.state()), newOneShot); return new MissingOneShotResult<>(decomposition.state().getLocation(), newOneShot); } - private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { // Check if there is a one-shot timer expiring at the next time step: List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); @@ -122,15 +122,15 @@ private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecompo } } - private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, LocalTimerMealyHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { // 1: can be a missing discriminator? // Check if correct target in entry w.r.t. discriminator: var transPrefix = hypothesis.getLocationPrefix(decomposition.state()).append(decomposition.input()); var succState = hypothesis.getSemantics().getState(transPrefix); // successor state in hypothesis - var actualSuffixOutput = this.timeOracle.querySuffixOutput(transPrefix, decomposition.discriminator()); - var expSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getPrefix(succState), decomposition.discriminator()); + var actualSuffixOutput = this.timeOracle.answerQuery(transPrefix, decomposition.discriminator()); + var expSuffixOutput = this.timeOracle.answerQuery(hypothesis.getPrefix(succState), decomposition.discriminator()); if (!actualSuffixOutput.equals(expSuffixOutput)) { logger.debug("Inferred missing discriminator at non-delaying input."); @@ -163,8 +163,8 @@ private CexAnalysisResult handleIncorrectTargetNonDelaying(Extended .append(decomposition.input()); // successor at $i$ in that config Word> suffix = Word.fromLetter(new TimeoutSymbol<>()); - var transSuffixOutput = this.timeOracle.querySuffixOutput(resetTransPrefix, suffix); - var entryConfigSuffixOutput = this.timeOracle.querySuffixOutput(hypothesis.getLocationPrefix(decomposition.state()), suffix); + var transSuffixOutput = this.timeOracle.answerQuery(resetTransPrefix, suffix); + var entryConfigSuffixOutput = this.timeOracle.answerQuery(hypothesis.getLocationPrefix(decomposition.state()), suffix); if (transSuffixOutput.equals(entryConfigSuffixOutput)) { logger.debug("Inferred missing reset in non-stable config."); diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java similarity index 78% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java index 5c587ba59..24ea6bb66 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyInconsPrefixTransformAcex.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java @@ -1,7 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt.cex; import de.learnlib.acex.AbstractBaseCounterexample; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; @@ -16,11 +16,11 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { +public class MMLTInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { - private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyInconsPrefixTransformAcex.class); + private final static Logger logger = LoggerFactory.getLogger(MMLTInconsPrefixTransformAcex.class); - private final AbstractTimedQueryOracle timeOracle; + private final TimedQueryOracle timeOracle; private final Word> suffix; private final Function>, Word>> asTransform; @@ -32,7 +32,7 @@ public class LocalTimerMealyInconsPrefixTransformAcex extends AbstractBase * @param timeOracle membership oracle * @param asTransform retrieves the prefix of the system state in the hypothesis addressed by a word */ - public LocalTimerMealyInconsPrefixTransformAcex(Word> suffix, AbstractTimedQueryOracle timeOracle, Function>, Word>> asTransform) { + public MMLTInconsPrefixTransformAcex(Word> suffix, TimedQueryOracle timeOracle, Function>, Word>> asTransform) { super(suffix.length()); this.timeOracle = timeOracle; this.suffix = suffix; @@ -53,7 +53,7 @@ public Word> computeEffect(int index) { Word> accessSequence = this.asTransform.apply(prefix); // Query *hypothesis state* + *suffix*: - return this.timeOracle.querySuffixOutput(accessSequence, suffix); + return this.timeOracle.answerQuery(accessSequence, suffix); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java similarity index 59% rename from algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java index b2019050a..0b93e875d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/LocalTimerMealyOutputInconsistency.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java @@ -15,8 +15,8 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public record LocalTimerMealyOutputInconsistency(Word> prefix, - Word> suffix, - Word> targetOut, - Word> hypOut) { +public record MMLTOutputInconsistency(Word> prefix, + Word> suffix, + Word> targetOut, + Word> hypOut) { } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java index cb990699d..b9e55cb4d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java @@ -3,9 +3,8 @@ /** * Outcome of a counterexample analysis. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public abstract class CexAnalysisResult { +public abstract class CexAnalysisResult { } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java index 40a0e97c4..1332dbc59 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java @@ -6,20 +6,19 @@ /** * The specified symbol is considered to be falsely ignored by the symbol filter. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class FalseIgnoreResult extends CexAnalysisResult { - private final S location; +public class FalseIgnoreResult extends CexAnalysisResult { + private final Integer location; private final InputSymbol symbol; - public FalseIgnoreResult(S location, InputSymbol symbol) { + public FalseIgnoreResult(Integer location, InputSymbol symbol) { this.location = location; this.symbol = symbol; } - public S getLocation() { + public Integer getLocation() { return location; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java index 8e8389a43..92f960a0b 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java @@ -6,22 +6,21 @@ /** * The target at the identified transition is incorrect due to a missing discriminator. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MissingDiscriminatorResult extends CexAnalysisResult { - private final S location; +public class MissingDiscriminatorResult extends CexAnalysisResult { + private final Integer location; private final TimedInput input; private final Word> discriminator; - public MissingDiscriminatorResult(S location, TimedInput input, Word> discriminator) { + public MissingDiscriminatorResult(Integer location, TimedInput input, Word> discriminator) { this.location = location; this.input = input; this.discriminator = discriminator; } - public S getLocation() { + public Integer getLocation() { return location; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java index 5f8a0f334..c8915d388 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -5,20 +5,19 @@ /** * The provided timer should become one-shot. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MissingOneShotResult extends CexAnalysisResult { - private final S location; +public class MissingOneShotResult extends CexAnalysisResult { + private final Integer location; private final MealyTimerInfo timeout; - public MissingOneShotResult(S location, MealyTimerInfo timeout) { + public MissingOneShotResult(Integer location, MealyTimerInfo timeout) { this.location = location; this.timeout = timeout; } - public S getLocation() { + public Integer getLocation() { return location; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java index 251c3711d..7dc56bc4c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java @@ -7,20 +7,19 @@ /** * There should be a local reset at the specified transition. * - * @param Location type * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MissingResetResult extends CexAnalysisResult { - private final S location; +public class MissingResetResult extends CexAnalysisResult { + private final Integer location; private final InputSymbol input; - public MissingResetResult(S location, InputSymbol input) { + public MissingResetResult(Integer location, InputSymbol input) { this.location = location; this.input = input; } - public S getLocation() { + public Integer getLocation() { return location; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java deleted file mode 100644 index 1b5483a1c..000000000 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/IInternalLocalTimerMealyHypothesis.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt.hyp; - -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.automaton.mmlt.MealyTimerInfo; -import net.automatalib.automaton.mmlt.State; -import net.automatalib.word.Word; - -import java.util.List; - -/** - * Defines several methods that the learner can use to interact with its hypothesis. - * These methods should not be used by the teacher, to maintain separation between both. - * - * @param Location type - * @param Input type for non-delaying inputs - */ -public interface IInternalLocalTimerMealyHypothesis { - - /** - * Returns the prefix assigned to the provided configuration. - * The assigned prefix is the concatenation of the prefix assigned to the active location - * and the minimal number of time steps needed to reach the configuration after entering its location - * (= entry distance). - * - * @param configuration Considered configuration - * @return Assigned prefix - */ - Word> getPrefix(State configuration); - - Word> getPrefix(Word> prefix); - - /** - * Returns the prefix assigned to the location that is active in the provided configuration. - * - * @param configuration Considered configuration - * @return Assigned prefix - */ - Word> getLocationPrefix(State configuration); - - /** - * Returns a prefix for the given location. - * This prefix is deterministic in the RS learner. - * - * @param location Location - * @return Location prefix - */ - Word> getPrefix(S location); - - /** - * Convenience method that sorts timers of the provided location by initial value. - * - * @return Sorted timers. Empty list if no timers. - */ -// List> getSortedTimers(S location); -} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java deleted file mode 100644 index cf6bc85a0..000000000 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/hyp/LocalTimerMealyHypothesis.java +++ /dev/null @@ -1,125 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt.hyp; - -import java.util.Collection; -import java.util.List; -import java.util.Map; - -import net.automatalib.alphabet.Alphabet; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.automaton.mmlt.MMLTSemantics; -import net.automatalib.automaton.mmlt.MealyTimerInfo; -import net.automatalib.automaton.mmlt.State; -import net.automatalib.automaton.mmlt.SymbolCombiner; -import net.automatalib.automaton.mmlt.impl.CompactMMLTSemantics; -import net.automatalib.symbol.time.TimeStepSequence; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * An MMLT hypothesis that includes a prefix mapping. - * This mapping assigns a short prefix to each location. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public class LocalTimerMealyHypothesis implements MMLT, IInternalLocalTimerMealyHypothesis { - private final MMLT automaton; - private final Map>> prefixMap; // location -> prefix - - public LocalTimerMealyHypothesis(MMLT automaton, Map>> prefixMap) { - this.automaton = automaton; - this.prefixMap = prefixMap; - } - - @Override - public Word> getPrefix(State configuration) { - var locPrefix = getLocationPrefix(configuration); - if (configuration.isEntryConfig()) { - return locPrefix; // entry distance = 0 - } else { - return locPrefix.append(new TimeStepSequence<>(configuration.getEntryDistance())); - } - } - - @Override - public Word> getPrefix(Word> prefix) { - var resultingConfig = getSemantics().getState(prefix); - return getPrefix(resultingConfig); - } - - - @Override - public Word> getLocationPrefix(State configuration) { - var locPrefix = this.prefixMap.get(configuration.getLocation()); - if (locPrefix == null) throw new AssertionError(); - return locPrefix; - } - - - @Override - public Word> getPrefix(S location) { - return prefixMap.get(location); - } - - @Override - public O getSilentOutput() { - return automaton.getSilentOutput(); - } - - @Override - public SymbolCombiner getOutputCombiner() { - return automaton.getOutputCombiner(); - } - - @Override - public Alphabet getInputAlphabet() { - return automaton.getInputAlphabet(); - } - - @Override - public S getInitialState() { - return automaton.getInitialState(); - } - - @Override - public Collection getStates() { - return automaton.getStates(); - } - - @Override - public @Nullable T getTransition(S location, I input) { - return automaton.getTransition(location, input); - } - - @Override - public boolean isLocalReset(S location, I input) { - return automaton.isLocalReset(location, input); - } - - @Override - public List> getSortedTimers(S location) { - return automaton.getSortedTimers(location); - } - - @Override - public MMLTSemantics getSemantics() { - return new CompactMMLTSemantics<>(this); - } - - @Override - public Void getStateProperty(S state) { - return automaton.getStateProperty(state); - } - - @Override - public O getTransitionProperty(T transition) { - return automaton.getTransitionProperty(transition); - } - - @Override - public S getSuccessor(T transition) { - return automaton.getSuccessor(transition); - } -} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java new file mode 100644 index 000000000..a42e33ce9 --- /dev/null +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -0,0 +1,180 @@ +package de.learnlib.algorithm.lstar; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; +import de.learnlib.query.DefaultQuery; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import de.learnlib.testsupport.example.mmlt.MMLTModel; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.exception.FormatException; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.word.Word; +import org.testng.annotations.Test; + +/** + * Tests several different cases of counterexamples. + */ +@Test +public class ExtensibleLStarMMLTCounterexampleTests { + + private static void learnModel(MMLTModel model, List>> counterexamples) { + + var sul = new MMLTSimulatorSUL<>(model.automaton().getSemantics()); + var timeOracle = new TimedSULOracle<>(sul, model.params()); + + var learner = new ExtensibleLStarMMLT<>(model.automaton().getInputAlphabet(), model.params(), Collections.emptyList(), + timeOracle, new AcceptAllSymbolFilter<>()); + + learner.startLearning(); + + for (var cex : counterexamples) { + var output = timeOracle.answerQuery(cex); + learner.refineHypothesis(new DefaultQuery<>(cex, output)); + } + + // Now continue until arriving at an accurate model: + SimulatorEQOracle simOracle = new SimulatorEQOracle<>(model.automaton()); + + DefaultQuery, Word>> cex; + MMLT hyp = learner.getHypothesisModel(); + + while ((cex = simOracle.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet())) != null) { + learner.refineHypothesis(cex); + hyp = learner.getHypothesisModel(); + } + } + + @Test + public void testOverApproxReset() throws IOException, FormatException { + // Infers a missing local reset instead of a missing discriminator first. + var model = ExtensibleLStarMMLTIT.automatonFromFile("over_approx_reset.dot"); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + Word.fromSymbols(TimedInput.step(), new InputSymbol<>("i"), new TimeoutSymbol<>()) + ); + + learnModel(model, cex1); + } + + @Test + public void testRecursiveDecomp() throws IOException, FormatException { + // Triggers recursive decomposition + var model = ExtensibleLStarMMLTIT.automatonFromFile("recursive_decomp.dot", 3); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + Word.upcast(TimedInput.inputs("p", "f")), + Word.fromWords(TimedInput.inputs("u"), TimedInput.timeouts(4), TimedInput.inputs("f")), + Word.fromWords(TimedInput.inputs("u"), TimedInput.timeouts(5)) + ); + + learnModel(model, cex1); + } + + @Test + public void testMissingDiscriminators() { + var model = MMLTExamples.SensorCollector(); + + // Missing discriminator at non-del in stable config: + List>> cex1 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromSymbols(TimedInput.input("p2"), TimedInput.step(), TimedInput.input("abort"), TimedInput.timeout()) + ); + + // Missing discriminator at one-shot: + List>> cex2 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2)) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + @Test + public void testMissingResets() { + var model = MMLTExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(40); + + // Missing reset in stable config: + List>> cex1 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromSymbols(TimedInput.input("p1"), TimedInput.step(), TimedInput.input("abort"), TimedInput.timeout()) + ); + + // Missing reset in non-stable config: + List>> cex2 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.steps(3), TimedInput.inputs("abort"), TimedInput.timeouts(1)) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + + } + + @Test + public void testMissingOneShotModelB() { + // Setting max waiting = 6 -> all inferred timers are periodic: + var model = MMLTExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(6); + + // Missing one-shot via bad return to entry: + List>> cex1 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14)) + ); + + // Missing one-shot in location with single timer: + List>> cex2 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2)) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + @Test + public void testMissingOneShotModelA() { + var model = MMLTExamples.SensorCollector(); + model.params().setMaxTimerQueryWaitingTime(40); + + // Missing one-shot via bad output: + List>> cex1 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.steps(40), TimedInput.timeouts(1)) // alternatively: new InputSymbol<>("abort") + ); + + // Missing one-shot via bad target: + List>> cex2 = List.of( + Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14), TimedInput.inputs("collect", "p1")) + ); + + learnModel(model, cex1); + learnModel(model, cex2); + } + + +} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java new file mode 100644 index 000000000..520bdade2 --- /dev/null +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -0,0 +1,238 @@ +package de.learnlib.algorithm.lstar.it; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Stream; + +import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; +import de.learnlib.oracle.equivalence.mmlt.EQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.RandomWpOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; +import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; +import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.MMLTPerfectSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.MMLTStatisticsSymbolFilter; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.sul.TimedSUL; +import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import de.learnlib.testsupport.example.mmlt.MMLTModel; +import de.learnlib.util.statistic.container.MapStatsContainer; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTParsers; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.word.Word; +import org.testng.annotations.Test; + +/** + * Integration tests for the MMLT learner that uses several EQ oracles, symbol filters + * and a cache to learn different models. + */ +@Test +public class ExtensibleLStarMMLTIT { + + private enum FilterMode { + none, random, ignore_all, perfect + } + + private static void runExperiment(ExtensibleLStarMMLT learner, MMLTEquivalenceOracle tester, StatsContainer stats, int maxRounds) { + stats.startOrResumeClock("learningRt", "Processing time"); + learner.startLearning(); + + var hyp = learner.getHypothesisModel(); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", "CEX queries"); + + int roundCount = 1; + while (cex != null && roundCount < maxRounds) { + learner.refineHypothesis(cex); + hyp = learner.getHypothesisModel(); + cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + stats.increaseCounter("roundCount", null); + roundCount += 1; + } + stats.pauseClock("learningRt"); + + final var finalHypothesis = learner.getHypothesisModel(); + + // Add some more stats: + stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); + + // Print stats: + stats.printStats(); + } + + private static void learnModel(String name, MMLT automaton, MMLTModelParams params, + FilterMode symbolFilterMode, long seed) { + + // Add some stats: + var stats = new MapStatsContainer(); + stats.addTextInfo("LocalTimerMealyModel", null, name); + stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getInputAlphabet().size()); + + // Set up a pipeline: + // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL + MMLTSimulatorSUL sul = new MMLTSimulatorSUL<>(automaton.getSemantics()); + CounterTimedSUL statsAfterCache = new CounterTimedSUL<>(sul, stats); + TimedSULTreeCache cacheSUL = new TimedSULTreeCache<>(statsAfterCache, params); + cacheSUL.setStatsContainer(stats); + TimedSUL toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime(), stats); + + TimedSULOracle timeOracle = new TimedSULOracle<>(toReducerSul, params); + + // Prepare cex oracle chain: + + EQOracleChain chainOracle = new EQOracleChain<>(); + chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); + chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); + chainOracle.addOracle(new RandomWpOracle<>(timeOracle, seed, 16, 0, 100)); + chainOracle.addOracle(new SimulatorEQOracle<>(automaton)); // ensure that we eventually find an accurate model + chainOracle.setStatsContainer(stats); + + // Create learner: + List>> suffixes = new ArrayList<>(); + automaton.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + // Configure symbol filter: + SymbolFilter, InputSymbol> filter = new AcceptAllSymbolFilter<>(); // pass-through + switch (symbolFilterMode) { + case perfect -> filter = new MMLTPerfectSymbolFilter<>(automaton); + case random -> filter = new MMLTRandomSymbolFilter<>(automaton, 0.1, new Random(seed)); + case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); + } + + filter = new MMLTStatisticsSymbolFilter<>(automaton, filter, stats); + filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + + var learner = new ExtensibleLStarMMLT<>(automaton.getInputAlphabet(), params, suffixes, timeOracle, filter); + learner.setStatsContainer(stats); + + // Start learning: + runExperiment(learner, chainOracle, stats, 100); + } + + + @Test + public void learnExamplesNoFilter() throws IOException, FormatException { + for (String modelFile : listModelFiles()) { + var model = automatonFromFile(modelFile); + learnModel(model.name(), model.automaton(), model.params(), FilterMode.none, 100); + } + MMLTExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.none, 100)); + } + + @Test + public void learnExamplesIgnoreAllFilter() throws IOException, FormatException { + for (String modelFile : listModelFiles()) { + var model = automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100); + } + MMLTExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.ignore_all, 100)); + } + + @Test + public void learnExamplesPerfectFilter() throws IOException, FormatException { + for (String modelFile : listModelFiles()) { + var model = automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100); + } + MMLTExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.perfect, 100)); + } + + @Test + public void learnExamplesRandomFilter() throws IOException, FormatException { + for (String modelFile : listModelFiles()) { + var model = automatonFromFile(modelFile); + learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100); + } + MMLTExamples.getAll().forEach(m -> + learnModel(m.name(), m.automaton(), m.params(), FilterMode.random, 100)); + } + + /** + * Lists all MMLT models in the resources directory. + */ + static List listModelFiles() { + var models = new ArrayList(); + try { + var modelFiles = ExtensibleLStarMMLTIT.class.getResource("/mmlt"); + if (modelFiles != null) { + try (Stream paths = Files.list(Paths.get(modelFiles.toURI()))) { + paths.filter(p -> p.toString().endsWith(".dot")) + .map(p -> p.getFileName().toString()) + .forEach(models::add); + } + } + } catch (IOException | URISyntaxException e) { + throw new RuntimeException("Failed to list model files", e); + } + return models; + } + + public static MMLTModel automatonFromFile(String name) + throws IOException, FormatException { + return automatonFromFile(name, -1); + } + + /** + * Loads the automaton model with the provided resource name. + * + * @param name Resource name + * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. + * @return The automaton model. + */ + public static MMLTModel automatonFromFile(String name, int maxTimerQueryWaiting) + throws IOException, FormatException { + + var silentOutput = "void"; + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + + try (InputStream is = ExtensibleLStarMMLTIT.class.getResourceAsStream("/mmlt/" + name)) { + var model = parser.readModel(is); + var automaton = model.model; + + long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? + maxTimerQueryWaiting : + MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + + return new MMLTModel<>(name, + automaton, + new MMLTModelParams<>(silentOutput, + maxTimeoutDelay, + maxTimerQueryWaitingFinal, + outputCombiner)); + } + } + +} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java deleted file mode 100644 index 64de36475..000000000 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyBenchmarkTests.java +++ /dev/null @@ -1,179 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt; - - -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; -import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; -import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; -import de.learnlib.oracle.EquivalenceOracle; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyEQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyRandomWpOracle; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; -import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; -import de.learnlib.oracle.membership.TimedQueryOracle; -import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; -import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; -import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.*; -import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.LocalTimerMealySUL; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; -import de.learnlib.util.statistic.container.MapStatsContainer; -import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.exception.FormatException; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.word.Word; -import org.testng.annotations.Test; - -import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeSULCache; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -/** - * Integration tests for the MMLT learner that uses several EQ oracles, symbol filters - * and a cache to learn different models. - */ -@Test -public class LStarLocalTimerMealyBenchmarkTests { - - private enum FilterMode { - none, random, ignore_all, perfect - } - - private static void runExperiment(LStarLocalTimerMealy learner, EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, StatsContainer stats, int maxRounds, - boolean printFinalResult) { - stats.startOrResumeClock("learningRt", "Processing time"); - learner.startLearning(); - - var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", "CEX queries"); - - int roundCount = 1; - while (cex != null && roundCount < maxRounds) { - learner.refineHypothesis(cex); - hyp = learner.getHypothesisModel(); - cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", null); - roundCount += 1; - } - stats.pauseClock("learningRt"); - - final var finalHypothesis = learner.getHypothesisModel(); - - // Add some more stats: - stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); - - // Print stats: - stats.printStats(); - - if (printFinalResult) { - System.out.println("Final hypothesis:"); - LocalTimerMealyTestUtil.printModel(finalHypothesis); - //new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); - } - } - - private static void learnModel(String name, MMLT automaton, LocalTimerMealyModelParams params, - FilterMode symbolFilterMode, long seed, boolean printResults) { - - // Add some stats: - var stats = new MapStatsContainer(); - stats.addTextInfo("LocalTimerMealyModel", null, name); - stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getInputAlphabet().size()); - - // Set up a pipeline: - // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL - LocalTimerMealySimulatorSUL sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); - LocalTimerMealyStatsSUL statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); - LocalTimerMealyTreeSULCache cacheSUL = new LocalTimerMealyTreeSULCache<>(statsAfterCache, params); - cacheSUL.setStatsContainer(stats); - LocalTimerMealySUL toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime(), stats); - - TimedQueryOracle timeOracle = new TimedQueryOracle<>(toReducerSul, params); - - // Prepare cex oracle chain: - - LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); - chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); - chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); - chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, seed, 16, 0, 100)); - chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(automaton)); // ensure that we eventually find an accurate model - chainOracle.setStatsContainer(stats); - - // Create learner: - List>> suffixes = new ArrayList<>(); - automaton.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); - suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - - // Configure symbol filter: - SymbolFilter, InputSymbol> filter = new AcceptAllSymbolFilter<>(); // pass-through - switch (symbolFilterMode) { - case perfect -> filter = new LocalTimerMealyPerfectSymbolFilter<>(automaton); - case random -> filter = new LocalTimerMealyRandomSymbolFilter<>(automaton, 0.1, new Random(seed)); - case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); - } - - filter = new LocalTimerMealyStatisticsSymbolFilter<>(automaton, filter, stats); - filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - - var learner = new LStarLocalTimerMealy<>(automaton.getInputAlphabet(), params, suffixes, timeOracle, filter); - learner.setStatsContainer(stats); - - // Start learning: - runExperiment(learner, chainOracle, stats, 100, printResults); - } - - - @Test - public void learnExamplesNoFilter() throws IOException, FormatException { - for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { - var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); - learnModel(model.name(), model.automaton(), model.params(), FilterMode.none, 100, true); - } - LocalTimerMealyExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.none, 100, true)); - } - - @Test - public void learnExamplesIgnoreAllFilter() throws IOException, FormatException { - for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { - var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100, true); - } - LocalTimerMealyExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.ignore_all, 100, true)); - } - - @Test - public void learnExamplesPerfectFilter() throws IOException, FormatException { - for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { - var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100, true); - } - LocalTimerMealyExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.perfect, 100, true)); - } - - @Test - public void learnExamplesRandomFilter() throws IOException, FormatException { - for (String modelFile : LocalTimerMealyTestUtil.listModelFiles()) { - var model = LocalTimerMealyTestUtil.automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100, true); - } - LocalTimerMealyExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.random, 100, true)); - } - -} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java deleted file mode 100644 index 333711b62..000000000 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LStarLocalTimerMealyCounterexampleTests.java +++ /dev/null @@ -1,228 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt; - -import java.io.IOException; -import java.util.Collections; -import java.util.List; - -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; -import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; -import de.learnlib.oracle.membership.TimedQueryOracle; -import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; -import de.learnlib.query.DefaultQuery; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; -import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.exception.FormatException; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.word.Word; -import org.testng.annotations.Test; - -/** - * Tests several different cases of counterexamples. - */ -@Test -public class LStarLocalTimerMealyCounterexampleTests { - - private static void learnModel(LocalTimerMealyModel model, List>> counterexamples) { - - var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); - TimedQueryOracle timeOracle = new TimedQueryOracle<>(sul, model.params()); - - var learner = new LStarLocalTimerMealy<>(model.automaton().getInputAlphabet(), model.params(), Collections.emptyList(), - timeOracle, new AcceptAllSymbolFilter<>()); - - learner.startLearning(); - - System.out.println("Initial hypothesis:"); - LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); - - // Trigger the expected cases with the provided counterexamples: - for (var cex : counterexamples) { - System.out.println(); - new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); - System.out.println(); - - var output = timeOracle.querySuffixOutput(Word.epsilon(), cex); - learner.refineHypothesis(new DefaultQuery<>(cex, output)); - - System.out.println("Current hypothesis:"); - LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); - } - - // Now continue until arriving at an accurate model: - System.out.println("Running to completion"); - LocalTimerMealySimulatorOracle simOracle = new LocalTimerMealySimulatorOracle<>(model.automaton()); - int round = 0; - while (round < 100) { - var hyp = learner.getHypothesisModel(); - var cex = simOracle.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - if (cex != null) { - learner.refineHypothesis(cex); - } else { - break; - } - round++; - } - - System.out.println("Took " + (round + 1) + " additional rounds."); - System.out.println("Final hypothesis:"); - LocalTimerMealyTestUtil.printModel(learner.getHypothesisModel()); - System.out.println(); - new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); - System.out.println(); - - } - - @Test - public void testOverApproxReset() throws IOException, FormatException { - // Infers a missing local reset instead of a missing discriminator first. - var model = LocalTimerMealyTestUtil.automatonFromFile("over_approx_reset.dot"); - - // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - Word.fromSymbols(TimedInput.step(), new InputSymbol<>("i"), new TimeoutSymbol<>()) - ); - - learnModel(model, cex1); - } - - @Test - public void testRecursiveDecomp() throws IOException, FormatException { - // Triggers recursive decomposition - var model = LocalTimerMealyTestUtil.automatonFromFile("recursive_decomp.dot", 3); - - // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p"), new InputSymbol<>("f")), - - Word.fromSymbols(new InputSymbol<>("u"), - new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), - new InputSymbol<>("f")), - - Word.fromSymbols(new InputSymbol<>("u"), - new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), - new TimeoutSymbol<>()) - ); - - learnModel(model, cex1); - } - - @Test - public void testMissingDiscriminators() { - var model = LocalTimerMealyExamples.SensorCollector(); - - // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromSymbols(new InputSymbol<>("p2"), TimedInput.step(), new InputSymbol<>("abort"), new TimeoutSymbol<>()) - ); - - // Missing discriminator at one-shot: - List>> cex2 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) - ); - - learnModel(model, cex1); - learnModel(model, cex2); - } - - @Test - public void testMissingResets() { - var model = LocalTimerMealyExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(40); - - // Missing reset in stable config: - List>> cex1 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromSymbols(new InputSymbol<>("p1"), TimedInput.step(), new InputSymbol<>("abort"), new TimeoutSymbol<>()) - ); - - // Missing reset in non-stable config: - List>> cex2 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromSymbols(new InputSymbol<>("p1"), - TimedInput.step(), TimedInput.step(), TimedInput.step(), - new InputSymbol<>("abort"), new TimeoutSymbol<>()) - ); - - learnModel(model, cex1); - learnModel(model, cex2); - - } - - @Test - public void testMissingOneShotModelB() { - // Setting max waiting = 6 -> all inferred timers are periodic: - var model = LocalTimerMealyExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(6); - - // Missing one-shot via bad return to entry: - List>> cex1 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromWords(Word.fromLetter(new InputSymbol<>("p1")), TimedInput.timeouts(14)) - ); - - // Missing one-shot in location with single timer: - List>> cex2 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()) - ); - - learnModel(model, cex1); - learnModel(model, cex2); - } - - @Test - public void testMissingOneShotModelA() { - var model = LocalTimerMealyExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(40); - - // Missing one-shot via bad output: - List>> cex1 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - - Word.fromWords(Word.fromLetter(new InputSymbol<>("p1")), - TimedInput.steps(40), - Word.fromLetter(new TimeoutSymbol<>())) // alternatively: new NonDelayingInput<>("abort") - ); - - // Missing one-shot via bad target: - List>> cex2 = List.of( - // Initial hyp: - Word.fromSymbols(new InputSymbol<>("p1"), new InputSymbol<>("p1")), - Word.fromSymbols(new InputSymbol<>("p2"), new InputSymbol<>("abort")), - Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14), TimedInput.inputs("collect", "p1")) - ); - - learnModel(model, cex1); - learnModel(model, cex2); - } - - -} diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java deleted file mode 100644 index dde0e9fc7..000000000 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/mmlt/LocalTimerMealyTestUtil.java +++ /dev/null @@ -1,95 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Stream; - -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyModel; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; -import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.exception.FormatException; -import net.automatalib.serialization.dot.DOTParsers; -import net.automatalib.serialization.dot.GraphDOT; -import net.automatalib.util.automaton.mmlt.MMLTUtil; - -/** - * Utility class for loading MMLTs from resources and printing them. - */ -public class LocalTimerMealyTestUtil { - - /** - * Prints the provided MMLT to stdout. - */ - static void printModel(MMLT model) { - try { - GraphDOT.write(model.graphView(), System.out, new MMLTVisualizationHelper<>(model, true, true)); - } catch (IOException ignored) { - } - } - - /** - * Lists all MMLT models in the resources directory. - */ - static List listModelFiles() { - var models = new ArrayList(); - try { - var modelFiles = LocalTimerMealyTestUtil.class.getResource("/mmlt"); - if (modelFiles != null) { - try (Stream paths = Files.list(Paths.get(modelFiles.toURI()))) { - paths.filter(p -> p.toString().endsWith(".dot")) - .map(p -> p.getFileName().toString()) - .forEach(models::add); - } - } - } catch (IOException | URISyntaxException e) { - throw new RuntimeException("Failed to list model files", e); - } - return models; - } - - static LocalTimerMealyModel automatonFromFile(String name) - throws IOException, FormatException { - return automatonFromFile(name, -1); - } - - /** - * Loads the automaton model with the provided resource name. - * - * @param name Resource name - * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. - * @return The automaton model. - */ - static LocalTimerMealyModel automatonFromFile(String name, int maxTimerQueryWaiting) - throws IOException, FormatException { - - var silentOutput = "void"; - var outputCombiner = StringSymbolCombiner.getInstance(); - var parser = DOTParsers.mmlt(silentOutput, outputCombiner); - - try (InputStream is = LocalTimerMealyTestUtil.class.getResourceAsStream("/mmlt/" + name)) { - var model = parser.readModel(is); - var automaton = model.model; - - long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? - maxTimerQueryWaiting : - MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; - - return new LocalTimerMealyModel<>(name, - automaton, - new LocalTimerMealyModelParams<>(silentOutput, - maxTimeoutDelay, - maxTimerQueryWaitingFinal, - outputCombiner)); - } - } - -} diff --git a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java b/api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java similarity index 90% rename from api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java rename to api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java index 2f1876991..4a70351f0 100644 --- a/api/src/main/java/de/learnlib/algorithm/LocalTimerMealyModelParams.java +++ b/api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java @@ -10,7 +10,7 @@ * * @param Output symbol type */ -public final class LocalTimerMealyModelParams { +public final class MMLTModelParams { private final O silentOutput; private final SymbolCombiner outputCombiner; private final long maxTimeoutWaitingTime; @@ -31,10 +31,10 @@ public final class LocalTimerMealyModelParams { * the need for equivalence queries. * @param outputCombiner Function for combining simultaneously occurring outputs of timers */ - public LocalTimerMealyModelParams(O silentOutput, - long maxTimeoutWaitingTime, - long maxTimerQueryWaitingTime, - SymbolCombiner outputCombiner) { + public MMLTModelParams(O silentOutput, + long maxTimeoutWaitingTime, + long maxTimerQueryWaitingTime, + SymbolCombiner outputCombiner) { this.silentOutput = silentOutput; this.maxTimeoutWaitingTime = maxTimeoutWaitingTime; this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; @@ -65,7 +65,7 @@ public void setMaxTimerQueryWaitingTime(long maxTimerQueryWaitingTime) { public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (LocalTimerMealyModelParams) obj; + var that = (MMLTModelParams) obj; return Objects.equals(this.silentOutput, that.silentOutput) && this.maxTimeoutWaitingTime == that.maxTimeoutWaitingTime && this.maxTimerQueryWaitingTime == that.maxTimerQueryWaitingTime && diff --git a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java deleted file mode 100644 index b264355e0..000000000 --- a/api/src/main/java/de/learnlib/oracle/AbstractTimedQueryOracle.java +++ /dev/null @@ -1,82 +0,0 @@ -package de.learnlib.oracle; - -import de.learnlib.query.DefaultQuery; -import de.learnlib.query.Query; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.automaton.mmlt.MealyTimerInfo; -import net.automatalib.word.Word; - -import java.util.Collection; -import java.util.List; - -/** - * Type of oracle used by an MMLT learner. - *

      - * Like a traditional query oracle, this answers output queries. - * In addition, it infers timers by observing timeouts. - * - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public abstract class AbstractTimedQueryOracle implements MembershipOracle.MealyMembershipOracle, TimedOutput> { - - /** - * Response for a timer query. - * - * @param aborted True if query was aborted due to missing timeout. - * @param timers Identified timers - * @param Untimed output suffix type - */ - public record TimerQueryResult(boolean aborted, List> timers) { - - } - - @Override - public void processQueries(Collection, Word>>> collection) { - for (var q : collection) { - DefaultQuery, Word>> query = new DefaultQuery<>(q.getPrefix(), q.getSuffix()); - this.querySuffixOutput(query); - q.answer(query.getOutput()); - } - } - - /** - * Observes and aggregates any timeouts that occur after providing the given input to the SUL. - * Stops when observing inconsistent behavior. - * - * @param prefix Input to give to the SUL. - * @param maxTotalWaitingTime Maximum total time that is waited for timeouts. - * @return Observed timeouts. Empty, if none. - */ - public abstract TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime); - - /** - * Queries the suffix output for the provided query. - * - * @param query Input query. - */ - public void querySuffixOutput(DefaultQuery, Word>> query) { - this.querySuffixOutputInternal(query); - } - - /** - * Like querySuffixOutput but does not require a query object. - * - * @param prefix Prefix - * @param suffix Suffix - */ - public final Word> querySuffixOutput(Word> prefix, Word> suffix) { - DefaultQuery, Word>> query = new DefaultQuery<>(prefix, suffix); - this.querySuffixOutputInternal(query); - return query.getOutput(); - } - - /** - * Queries the output for the provided input sequence. - * - * @param query Input query. - */ - protected abstract void querySuffixOutputInternal(DefaultQuery, Word>> query); - -} diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index 8c4efe16a..4d976d563 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -101,5 +101,5 @@ interface MooreEquivalenceOracle extends EquivalenceOracle Input type for non-delaying inputs * @param Output symbol type */ - interface LocalTimerMealyEquivalenceOracle extends EquivalenceOracle, TimedInput, Word>>{} + interface MMLTEquivalenceOracle extends EquivalenceOracle, TimedInput, Word>>{} } diff --git a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java new file mode 100644 index 000000000..f0042b6e2 --- /dev/null +++ b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java @@ -0,0 +1,40 @@ +package de.learnlib.oracle; + +import java.util.List; + +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; + +/** + * Type of oracle used by an MMLT learner. + *

      + * Like a traditional query oracle, this answers output queries. + * In addition, it infers timers by observing timeouts. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public interface TimedQueryOracle extends MembershipOracle.MealyMembershipOracle, TimedOutput> { + + /** + * Observes and aggregates any timeouts that occur after providing the given input to the SUL. + * Stops when observing inconsistent behavior. + * + * @param prefix Input to give to the SUL. + * @param maxTotalWaitingTime Maximum total time that is waited for timeouts. + * @return Observed timeouts. Empty, if none. + */ + TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime); + + /** + * Response for a timer query. + * + * @param aborted True if query was aborted due to missing timeout. + * @param timers Identified timers + * @param Untimed output suffix type + */ + record TimerQueryResult(boolean aborted, List> timers) {} + +} diff --git a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java b/api/src/main/java/de/learnlib/sul/TimedSUL.java similarity index 89% rename from api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java rename to api/src/main/java/de/learnlib/sul/TimedSUL.java index 5c6016ae7..1e2d30827 100644 --- a/api/src/main/java/de/learnlib/sul/LocalTimerMealySUL.java +++ b/api/src/main/java/de/learnlib/sul/TimedSUL.java @@ -15,7 +15,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public interface LocalTimerMealySUL { +public interface TimedSUL extends SUL, TimedOutput> { /** * Follows the provided input word, starting at the current system state. @@ -50,14 +50,6 @@ default void follow(Word> input, long maxTimeout) { } } - /** - * Provides a non-delaying input to the SUL and returns the observed output. - * - * @param input Input - * @return SUL output. - */ - TimedOutput step(InputSymbol input); - /** * Waits until a timeout occurs or the provided time is reached. *

      @@ -109,14 +101,4 @@ default Word> collectTimeouts(TimeStepSequence input) { return wbOutput.toWord(); } - - /** - * Prepares the SUL for a new query. - */ - void pre(); - - /** - * Deinitializes the SUL. - */ - void post(); } diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java similarity index 88% rename from drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java rename to drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java index 89fa528bf..d0e6fbafe 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/LocalTimerMealySimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java @@ -1,7 +1,6 @@ package de.learnlib.driver.simulator; -import de.learnlib.sul.LocalTimerMealySUL; -import net.automatalib.automaton.mmlt.MMLT; +import de.learnlib.sul.TimedSUL; import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.mmlt.State; import net.automatalib.symbol.time.InputSymbol; @@ -17,13 +16,13 @@ * @param Non-delaying input type. * @param Output symbol type. */ -public class LocalTimerMealySimulatorSUL implements LocalTimerMealySUL { +public class MMLTSimulatorSUL implements TimedSUL { private final MMLTSemantics semantics; private State currentConfiguration; - public LocalTimerMealySimulatorSUL(MMLTSemantics semantics) { + public MMLTSimulatorSUL(MMLTSemantics semantics) { this.semantics = semantics; this.currentConfiguration = null; } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 5eaf1aa1f..b866eef0e 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -5,28 +5,26 @@ import java.util.List; import java.util.Random; -import de.learnlib.algorithm.lstar.mmlt.LStarLocalTimerMealy; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; -import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; -import de.learnlib.filter.cache.mmlt.LocalTimerMealyTreeSULCache; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; -import de.learnlib.filter.statistic.sul.LocalTimerMealyStatsSUL; -import de.learnlib.oracle.EquivalenceOracle; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyEQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealyRandomWpOracle; -import de.learnlib.oracle.equivalence.mmlt.LocalTimerMealySimulatorOracle; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; +import de.learnlib.oracle.equivalence.mmlt.EQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.RandomWpOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; -import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.LocalTimerMealyRandomSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.LocalTimerMealyStatisticsSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; +import de.learnlib.oracle.symbol_filters.mmlt.MMLTStatisticsSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.testsupport.example.mmlt.LocalTimerMealyExamples; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; import de.learnlib.util.statistic.container.MapStatsContainer; -import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.symbol.time.InputSymbol; @@ -42,7 +40,7 @@ public class Example1 { public static void main(String[] args) { - var model = LocalTimerMealyExamples.SensorCollector(); + var model = MMLTExamples.SensorCollector(); // We first create a statistics container. // This container will store various statistical data during learning: @@ -54,25 +52,25 @@ public static void main(String[] args) { // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new LocalTimerMealySimulatorSUL<>(model.automaton().getSemantics()); + var sul = new MMLTSimulatorSUL<>(model.automaton().getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: - var statsAfterCache = new LocalTimerMealyStatsSUL<>(sul, stats); + var statsAfterCache = new CounterTimedSUL<>(sul, stats); // We use a cache to avoid redundant operations: - var cacheSUL = new LocalTimerMealyTreeSULCache<>(statsAfterCache, model.params()); + var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.params()); cacheSUL.setStatsContainer(stats); var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.params().maxTimeoutWaitingTime(), stats); // We use a query oracle to answer queries from the learner: - var timeOracle = new TimedQueryOracle<>(toReducerSul, model.params()); + var timeOracle = new TimedSULOracle<>(toReducerSul, model.params()); // We use a chain of different equivalence oracles: - LocalTimerMealyEQOracleChain chainOracle = new LocalTimerMealyEQOracleChain<>(); + EQOracleChain chainOracle = new EQOracleChain<>(); chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, 100, 1.0, 1.0)); - chainOracle.addOracle(new LocalTimerMealyRandomWpOracle<>(timeOracle, 100, 16, 0, 100)); - chainOracle.addOracle(new LocalTimerMealySimulatorOracle<>(model.automaton())); // ensure that we eventually find an accurate model + chainOracle.addOracle(new RandomWpOracle<>(timeOracle, 100, 16, 0, 100)); + chainOracle.addOracle(new SimulatorEQOracle<>(model.automaton())); // ensure that we eventually find an accurate model chainOracle.setStatsContainer(stats); // Set up our L* learner: @@ -84,12 +82,12 @@ public static void main(String[] args) { // For this example, we use a RandomSymbolFilter. This filter correctly predicts // whether a transition silently self-loops with an accuracy of 90%: SymbolFilter, InputSymbol> filter = - new LocalTimerMealyRandomSymbolFilter<>(model.automaton(), 0.1, new Random(100)); + new MMLTRandomSymbolFilter<>(model.automaton(), 0.1, new Random(100)); - filter = new LocalTimerMealyStatisticsSymbolFilter<>(model.automaton(), filter, stats); + filter = new MMLTStatisticsSymbolFilter<>(model.automaton(), filter, stats); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new LStarLocalTimerMealy<>(model.automaton().getInputAlphabet(), model.params(), suffixes, timeOracle, filter); + var learner = new ExtensibleLStarMMLT<>(model.automaton().getInputAlphabet(), model.params(), suffixes, timeOracle, filter); learner.setStatsContainer(stats); // Start learning: @@ -105,8 +103,8 @@ public static void main(String[] args) { // with: tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList()); } - private static void runExperiment(LStarLocalTimerMealy learner, - EquivalenceOracle.LocalTimerMealyEquivalenceOracle tester, + private static void runExperiment(ExtensibleLStarMMLT learner, + MMLTEquivalenceOracle tester, StatsContainer stats, int maxRounds) { stats.startOrResumeClock("learningRt", "Processing time"); learner.startLearning(); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java index 85a51f6fe..b7093fca6 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java @@ -96,7 +96,7 @@ interface MooreLearningCache extends LearningCache Input type for non-delaying inputs * @param Output symbol type */ - interface LocalTimerMealyLearningCache extends LearningCache, TimedInput, Word>>{ + interface MMLTLearningCache extends LearningCache, TimedInput, Word>>{ /** * Lists all words that are currently in the cache. * If a cached word is a prefix of another cached word, only the longer of them is returned. diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index 5dd28b855..013d76143 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -35,7 +35,7 @@ private record CacheTreeTransition(TimedOutput output, CacheTreeNode timeTransition; - private Map, CacheTreeTransition> untimedChildren; + private final Map, CacheTreeTransition> untimedChildren; public CacheTreeNode(CacheTreeNode parent, TimedInput parentInput) { this.parent = parent; diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java similarity index 94% rename from filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java rename to filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java index 95691cf73..23ee4372e 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java @@ -1,7 +1,7 @@ package de.learnlib.filter.cache.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.query.DefaultQuery; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.TimedOutput; @@ -24,13 +24,13 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyCacheConsistencyTest implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { - private final static Logger logger = LoggerFactory.getLogger(LocalTimerMealyCacheConsistencyTest.class); +public class MMLTCacheConsistencyTest implements MMLTEquivalenceOracle { + private final static Logger logger = LoggerFactory.getLogger(MMLTCacheConsistencyTest.class); - private final LocalTimerMealyTreeSULCache sulCache; - private final LocalTimerMealyModelParams modelParams; + private final TimedSULTreeCache sulCache; + private final MMLTModelParams modelParams; - LocalTimerMealyCacheConsistencyTest(LocalTimerMealyTreeSULCache sulCache, LocalTimerMealyModelParams modelParams) { + MMLTCacheConsistencyTest(TimedSULTreeCache sulCache, MMLTModelParams modelParams) { this.sulCache = sulCache; this.modelParams = modelParams; } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java similarity index 91% rename from filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java rename to filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java index 26fdf4b5f..a084ad9c9 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyTreeSULCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java @@ -1,13 +1,13 @@ package de.learnlib.filter.cache.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.filter.cache.LearningCache; -import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.filter.cache.LearningCache.MMLTLearningCache; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.LocalTimerMealySUL; +import de.learnlib.sul.TimedSUL; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.symbol.time.TimedInput; @@ -29,13 +29,14 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyTreeSULCache implements LocalTimerMealySUL, LearningCache.LocalTimerMealyLearningCache, GraphViewable, LearnerStatsProvider { - private final LocalTimerMealySUL delegate; +public class TimedSULTreeCache implements TimedSUL, + MMLTLearningCache, GraphViewable, LearnerStatsProvider { + private final TimedSUL delegate; private final CacheTreeNode cacheRoot; private CacheTreeNode currentState; - private final LocalTimerMealyModelParams modelParams; + private final MMLTModelParams modelParams; private final TimedOutput silentOutput; private boolean cacheMiss; @@ -46,7 +47,7 @@ public void setStatsContainer(StatsContainer container) { this.stats = container; } - public LocalTimerMealyTreeSULCache(LocalTimerMealySUL delegate, LocalTimerMealyModelParams modelParams) { + public TimedSULTreeCache(TimedSUL delegate, MMLTModelParams modelParams) { this.delegate = delegate; this.modelParams = modelParams; this.silentOutput = new TimedOutput<>(modelParams.silentOutput()); @@ -265,8 +266,8 @@ public List>> listAllWords() { } @Override - public EquivalenceOracle.LocalTimerMealyEquivalenceOracle createCacheConsistencyTest() { - return new LocalTimerMealyCacheConsistencyTest<>(this, this.modelParams); + public MMLTEquivalenceOracle createCacheConsistencyTest() { + return new MMLTCacheConsistencyTest<>(this, this.modelParams); } } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index b3f17c76f..ebc521d15 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -2,7 +2,7 @@ import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.LocalTimerMealySUL; +import de.learnlib.sul.TimedSUL; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.InputSymbol; import org.checkerframework.checker.nullness.qual.Nullable; @@ -20,9 +20,9 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class TimeoutReducerSUL implements LocalTimerMealySUL, LearnerStatsProvider { +public class TimeoutReducerSUL implements TimedSUL, LearnerStatsProvider { - private final LocalTimerMealySUL delegate; + private final TimedSUL delegate; private final long maxDelay; /** @@ -33,7 +33,7 @@ public class TimeoutReducerSUL implements LocalTimerMealySUL, Learne private StatsContainer stats; - public TimeoutReducerSUL(LocalTimerMealySUL delegate, long maxDelay, StatsContainer stats) { + public TimeoutReducerSUL(TimedSUL delegate, long maxDelay, StatsContainer stats) { this.delegate = delegate; this.maxDelay = maxDelay; this.stats = stats; diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java similarity index 76% rename from filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java rename to filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java index cf4adb9ae..3d783ad9e 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/LocalTimerMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java @@ -4,9 +4,9 @@ import java.util.List; import java.util.Random; -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.driver.simulator.LocalTimerMealySimulatorSUL; -import de.learnlib.oracle.membership.TimedQueryOracle; +import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.oracle.membership.TimedSULOracle; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.impl.CompactMMLT; @@ -20,7 +20,7 @@ import org.testng.annotations.Test; @Test -public class LocalTimerMealyCacheTest { +public class MMLTCacheTest { private CompactMMLT buildBaseModel() { var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); @@ -56,12 +56,12 @@ public void testCacheAndSULConsistency() { Random random = new Random(100); var automaton = buildBaseModel(); - var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + var params = new MMLTModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); - var sul = new LocalTimerMealySimulatorSUL<>(automaton.getSemantics()); - var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); - var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); - var timeOracleWithoutCache = new TimedQueryOracle<>(sul, params); + var sul = new MMLTSimulatorSUL<>(automaton.getSemantics()); + var cacheSUL = new TimedSULTreeCache<>(sul, params); + var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); + var timeOracleWithoutCache = new TimedSULOracle<>(sul, params); var listAlphabet = new ArrayList<>(automaton.getSemantics().getInputAlphabet()); @@ -74,8 +74,8 @@ public void testCacheAndSULConsistency() { var word = Word.fromList(symbols); words.add(word); - var cacheOutput = timeOracleWithCache.querySuffixOutput(Word.epsilon(), word); - var sulOutput = timeOracleWithoutCache.querySuffixOutput(Word.epsilon(), word); + var cacheOutput = timeOracleWithCache.answerQuery(word); + var sulOutput = timeOracleWithoutCache.answerQuery(word); var automatonOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), word); Assert.assertEquals(sulOutput, automatonOutput, "Automaton output does not match SUL output for word " + word); @@ -84,8 +84,8 @@ public void testCacheAndSULConsistency() { // Now that the cache contents have changed, ensure that the results are still correct: for (var word : words) { - var cacheOutput = timeOracleWithCache.querySuffixOutput(Word.epsilon(), word); - var sulOutput = timeOracleWithoutCache.querySuffixOutput(Word.epsilon(), word); + var cacheOutput = timeOracleWithCache.answerQuery(word); + var sulOutput = timeOracleWithoutCache.answerQuery(word); Assert.assertEquals(sulOutput, cacheOutput, "Cache output does not match SUL output for word " + word); } @@ -95,15 +95,15 @@ public void testCacheAndSULConsistency() { public void testCacheConsistencyTest() { // Test if the cache consistency test works correctly: var refAutomaton = buildBaseModel(); - var params = new LocalTimerMealyModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + var params = new MMLTModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); - var sul = new LocalTimerMealySimulatorSUL<>(refAutomaton.getSemantics()); - var cacheSUL = new LocalTimerMealyTreeSULCache<>(sul, params); - var timeOracleWithCache = new TimedQueryOracle<>(cacheSUL, params); + var sul = new MMLTSimulatorSUL<>(refAutomaton.getSemantics()); + var cacheSUL = new TimedSULTreeCache<>(sul, params); + var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); // Add word to cache: Word> testWord = Word.fromSymbols(TimedInput.input("p2"), TimedInput.timeout(), TimedInput.step(), TimedInput.timeout()); - timeOracleWithCache.querySuffixOutput(Word.epsilon(), testWord); + timeOracleWithCache.answerQuery(testWord); // Create a bad hypothesis: var badAutomaton = buildBaseModel(); diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java similarity index 85% rename from filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index 2827840e2..b6bf750da 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/LocalTimerMealyStatsSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -2,7 +2,7 @@ import de.learnlib.statistic.container.LearnerStatsProvider; import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.LocalTimerMealySUL; +import de.learnlib.sul.TimedSUL; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; @@ -16,18 +16,18 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyStatsSUL implements LocalTimerMealySUL, LearnerStatsProvider { - private final LocalTimerMealySUL delegate; +public class CounterTimedSUL implements TimedSUL, LearnerStatsProvider { + private final TimedSUL delegate; private StatsContainer stats; @Nullable private final String name; - public LocalTimerMealyStatsSUL(LocalTimerMealySUL delegate, StatsContainer stats) { + public CounterTimedSUL(TimedSUL delegate, StatsContainer stats) { this(delegate, stats, null); } - public LocalTimerMealyStatsSUL(LocalTimerMealySUL delegate, StatsContainer stats, String name) { + public CounterTimedSUL(TimedSUL delegate, StatsContainer stats, String name) { this.delegate = delegate; this.stats = stats; this.name = name; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java index e09d84089..7cba476b0 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWMethodEQOracle.java @@ -44,7 +44,7 @@ /** * Implements an equivalence test based on a randomized version of the W-method as described in Complementing LocalTimerMealyModel Learning with Mutation-Based Fuzzing by Rick + * href="https://arxiv.org/abs/1611.02429">Complementing Model Learning with Mutation-Based Fuzzing by Rick * Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a * sampling implementation: *

        diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java index 799f8ec09..2b525f98a 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/RandomWpMethodEQOracle.java @@ -45,7 +45,7 @@ /** * Implements an equivalence test based on a randomized version of the W(p)-method as described in Complementing LocalTimerMealyModel Learning with Mutation-Based Fuzzing by Rick + * href="https://arxiv.org/abs/1611.02429">Complementing Model Learning with Mutation-Based Fuzzing by Rick * Smetsers, Joshua Moerman, Mark Janssen, Sicco Verwer. Instead of enumerating the test suite in order, this is a * sampling implementation: *
          diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java similarity index 88% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java index 0e43aa3a3..dfae400a5 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyEQOracleChain.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java @@ -1,6 +1,6 @@ package de.learnlib.oracle.equivalence.mmlt; -import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -25,11 +25,11 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyEQOracleChain implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { +public class EQOracleChain implements MMLTEquivalenceOracle, LearnerStatsProvider { - private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyEQOracleChain.class); + private static final Logger logger = LoggerFactory.getLogger(EQOracleChain.class); - private final List> oracles = new ArrayList<>(); + private final List> oracles = new ArrayList<>(); private StatsContainer stats = new DummyStatsContainer(); /** @@ -37,7 +37,7 @@ public class LocalTimerMealyEQOracleChain implements EquivalenceOracle.Loc */ private List oracleNames; - public void addOracle(LocalTimerMealyEquivalenceOracle oracle) { + public void addOracle(MMLTEquivalenceOracle oracle) { this.oracles.add(oracle); // Update names: diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java similarity index 91% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java index bb8eccc03..10a1be877 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealyRandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java @@ -1,7 +1,7 @@ package de.learnlib.oracle.equivalence.mmlt; -import de.learnlib.oracle.EquivalenceOracle; -import de.learnlib.oracle.AbstractTimedQueryOracle; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.container.DummyStatsContainer; import de.learnlib.statistic.container.LearnerStatsProvider; @@ -28,9 +28,9 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle, LearnerStatsProvider { - private static final Logger logger = LoggerFactory.getLogger(LocalTimerMealyRandomWpOracle.class); - private final AbstractTimedQueryOracle timeOracle; +public class RandomWpOracle implements MMLTEquivalenceOracle, LearnerStatsProvider { + private static final Logger logger = LoggerFactory.getLogger(RandomWpOracle.class); + private final TimedQueryOracle timeOracle; private StatsContainer stats = new DummyStatsContainer(); @@ -39,9 +39,9 @@ public class LocalTimerMealyRandomWpOracle implements EquivalenceOracle.Lo private final int rndLen; private final int bound; - public LocalTimerMealyRandomWpOracle(AbstractTimedQueryOracle timeOracle, - long randomSeed, - int minSize, int rndAddLength, int bound) { + public RandomWpOracle(TimedQueryOracle timeOracle, + long randomSeed, + int minSize, int rndAddLength, int bound) { this.timeOracle = timeOracle; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java index 06b54c92a..fa4279115 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java @@ -7,8 +7,8 @@ import java.util.Objects; import java.util.Random; -import de.learnlib.oracle.AbstractTimedQueryOracle; -import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.common.util.random.RandomUtil; @@ -36,11 +36,11 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ResetSearchOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class ResetSearchOracle implements MMLTEquivalenceOracle { private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); - private final AbstractTimedQueryOracle timeOracle; + private final TimedQueryOracle timeOracle; private final Random locPrefixRandom; private final double loopInsertPerc; @@ -48,7 +48,7 @@ public class ResetSearchOracle implements EquivalenceOracle.LocalTimerMeal private final long loopingInputSelectionSeed; - public ResetSearchOracle(AbstractTimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { + public ResetSearchOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { this.timeOracle = timeOracle; this.locPrefixRandom = new Random(seed); this.loopInsertPerc = loopInsertPerc; @@ -143,7 +143,7 @@ private List> getLoopingSymbols(S sourceLoc, List(testWord, sulOutput); } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java similarity index 84% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java index 936974644..b675d48bd 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/LocalTimerMealySimulatorOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java @@ -1,6 +1,6 @@ package de.learnlib.oracle.equivalence.mmlt; -import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.query.DefaultQuery; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; @@ -19,11 +19,11 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealySimulatorOracle implements EquivalenceOracle.LocalTimerMealyEquivalenceOracle { +public class SimulatorEQOracle implements MMLTEquivalenceOracle { private final MMLT refModel; - public LocalTimerMealySimulatorOracle(MMLT refModel) { + public SimulatorEQOracle(MMLT refModel) { this.refModel = refModel; } diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java similarity index 83% rename from oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java rename to oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index 0cb58d510..9c29aeb42 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedQueryOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -1,14 +1,22 @@ package de.learnlib.oracle.membership; -import de.learnlib.algorithm.LocalTimerMealyModelParams; -import de.learnlib.oracle.AbstractTimedQueryOracle; -import de.learnlib.query.DefaultQuery; -import de.learnlib.sul.LocalTimerMealySUL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import de.learnlib.sul.TimedSUL; import net.automatalib.automaton.mmlt.MealyTimerInfo; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; @@ -16,57 +24,37 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.stream.Collectors; - /** * Implements a timed query oracle for MMLT learning. * * @param Input type for non-delaying inputs * @param Output symbol type */ -public class TimedQueryOracle extends AbstractTimedQueryOracle { +public class TimedSULOracle implements TimedQueryOracle { - private final static Logger logger = LoggerFactory.getLogger(TimedQueryOracle.class); + private final static Logger logger = LoggerFactory.getLogger(TimedSULOracle.class); - // List of all possible timer names. - // Each name is assigned at most once while this oracle exists. This ensures globally-unique timer names. - private final List timerNames; - private int timerNameIndex = 0; + /** + * To ensure globally unique timer names, we index them according to this counter. + */ + private int timerCounter; - private final LocalTimerMealySUL sul; - private final LocalTimerMealyModelParams modelParams; + private final TimedSUL sul; + private final MMLTModelParams modelParams; - public TimedQueryOracle(LocalTimerMealySUL sul, LocalTimerMealyModelParams modelParams) { + public TimedSULOracle(TimedSUL sul, MMLTModelParams modelParams) { this.sul = sul; this.modelParams = modelParams; - - this.timerNames = generateTimerNames(); + this.timerCounter = 0; } - private List generateTimerNames() { - List names = new ArrayList<>(); - // Add single letter names - for (char c = 'a'; c <= 'z'; c++) { - names.add(String.valueOf(c)); - } - // Add double letter names - for (char c1 = 'a'; c1 <= 'z'; c1++) { - for (char c2 = 'a'; c2 <= 'z'; c2++) { - names.add("" + c1 + c2); - } + @Override + public void processQueries(Collection, Word>>> queries) { + for (var q : queries) { + this.querySuffixOutputInternal(q); } - return names; } - /** - * Observes and aggregates any timeouts that occur after providing the given input to the SUL. - * Stops when observing inconsistent behavior. - * - * @param prefix Input to give to the SUL. - * @param maxTotalWaitingTime Maximum time that is waited for timeouts. - * @return Observed timeouts. Empty, if none. - */ @Override public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { this.sul.pre(); @@ -111,9 +99,7 @@ private long calcNextExpectedTimeout(List> timeouts, long c } private String getUniqueTimerName() { - var newTimerName = timerNames.get(timerNameIndex); - this.timerNameIndex += 1; - return newTimerName; + return "t_" + (++this.timerCounter); } /** @@ -185,9 +171,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { return new TimerQueryResult<>(inconsistent, knownTimers); } - private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) { - - } + private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) {} private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { if (nextActualTime < nextExpectedTime) { @@ -231,8 +215,7 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpe } - @Override - protected void querySuffixOutputInternal(DefaultQuery, Word>> query) { + private void querySuffixOutputInternal(Query, Word>> query) { sul.pre(); sul.follow(query.getPrefix(), this.modelParams.maxTimeoutWaitingTime()); diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java similarity index 72% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java rename to oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java index 43b93730b..2b16a1d63 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyPerfectSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java @@ -15,17 +15,17 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyPerfectSymbolFilter extends PerfectSymbolFilter, InputSymbol> { +public class MMLTPerfectSymbolFilter extends PerfectSymbolFilter, InputSymbol> { private final MMLT automaton; - public LocalTimerMealyPerfectSymbolFilter(MMLT automaton) { + public MMLTPerfectSymbolFilter(MMLT automaton) { this.automaton = automaton; } @Override protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { - return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java similarity index 68% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java rename to oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java index a06b6bff2..b11fe76de 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyRandomSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java @@ -16,12 +16,12 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class LocalTimerMealyRandomSymbolFilter extends RandomSymbolFilter, InputSymbol> { +public class MMLTRandomSymbolFilter extends RandomSymbolFilter, InputSymbol> { private final MMLT automaton; - public LocalTimerMealyRandomSymbolFilter(MMLT automaton, - double inaccurateProb, Random random) { + public MMLTRandomSymbolFilter(MMLT automaton, + double inaccurateProb, Random random) { super(inaccurateProb, random); this.automaton = automaton; } @@ -29,6 +29,6 @@ public LocalTimerMealyRandomSymbolFilter(MMLT automaton, @Override protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { - return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } } \ No newline at end of file diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java similarity index 65% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java rename to oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java index da7fc904b..53e71c91d 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealyStatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java @@ -9,18 +9,18 @@ import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; -public class LocalTimerMealyStatisticsSymbolFilter extends StatisticsSymbolFilter, InputSymbol> { +public class MMLTStatisticsSymbolFilter extends StatisticsSymbolFilter, InputSymbol> { private final MMLT automaton; - public LocalTimerMealyStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatsContainer stats) { + public MMLTStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatsContainer stats) { super(delegate, stats); this.automaton = automaton; } @Override protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { - return LocalTimerMealySymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); + return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java similarity index 97% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java rename to oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java index e98d92011..dd6727a36 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/LocalTimerMealySymbolFilterUtil.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java @@ -9,7 +9,7 @@ import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; -class LocalTimerMealySymbolFilterUtil { +class MMLTSymbolFilterUtil { /** * Returns IGNORE if the provided input triggers a transition that silently self-loops, diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java similarity index 79% rename from test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java rename to test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java index a99378905..f5cb935d7 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java @@ -1,6 +1,6 @@ package de.learnlib.testsupport.example.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.algorithm.MMLTModelParams; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; import net.automatalib.serialization.dot.DOTParsers; @@ -10,9 +10,9 @@ import java.io.InputStream; import java.util.List; -public class LocalTimerMealyExamples { +public class MMLTExamples { - public static List> getAll() { + public static List> getAll() { return List.of(HVAC(), SCTP(), SensorCollector(), WM(), Oven(), WSN()); } @@ -23,7 +23,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel HVAC() { + public static MMLTModel HVAC() { return automatonFromFile("HVAC"); } @@ -34,7 +34,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel SCTP() { + public static MMLTModel SCTP() { return automatonFromFile("SCTP"); } @@ -48,7 +48,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel SensorCollector() { + public static MMLTModel SensorCollector() { return automatonFromFile("sensor_collector"); } @@ -70,7 +70,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel WM() { + public static MMLTModel WM() { return automatonFromFile("WM"); } @@ -84,7 +84,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel Oven() { + public static MMLTModel Oven() { return automatonFromFile("Oven"); } @@ -97,7 +97,7 @@ public class LocalTimerMealyExamples { * * @return LocalTimerMealyModel */ - public static LocalTimerMealyModel WSN() { + public static MMLTModel WSN() { return automatonFromFile("WSN"); } @@ -112,12 +112,12 @@ public class LocalTimerMealyExamples { * the learner has the chance to observe its timeout at least twice. This increases the chance of observing non-periodic behavior. * */ - static LocalTimerMealyModel automatonFromFile(String name) { + static MMLTModel automatonFromFile(String name) { var silentOutput = "void"; var outputCombiner = StringSymbolCombiner.getInstance(); var parser = DOTParsers.mmlt(silentOutput, outputCombiner); - try (InputStream is = LocalTimerMealyExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { + try (InputStream is = MMLTExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { var model = parser.readModel(is); var automaton = model.model; @@ -128,12 +128,12 @@ public class LocalTimerMealyExamples { maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time } - return new LocalTimerMealyModel<>(name, - automaton, - new LocalTimerMealyModelParams<>(silentOutput, - maxTimeoutDelay, - maxTimerQueryWaitingFinal, - outputCombiner)); + return new MMLTModel<>(name, + automaton, + new MMLTModelParams<>(silentOutput, + maxTimeoutDelay, + maxTimerQueryWaitingFinal, + outputCombiner)); } catch (IOException | FormatException e) { throw new RuntimeException("Unable to load model " + name, e); } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java similarity index 59% rename from test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java rename to test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java index c4ec7138f..a28dd681e 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/LocalTimerMealyModel.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java @@ -1,6 +1,6 @@ package de.learnlib.testsupport.example.mmlt; -import de.learnlib.algorithm.LocalTimerMealyModelParams; +import de.learnlib.algorithm.MMLTModelParams; import net.automatalib.automaton.mmlt.MMLT; /** @@ -13,8 +13,8 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public record LocalTimerMealyModel(String name, - MMLT automaton, - LocalTimerMealyModelParams params) { +public record MMLTModel(String name, + MMLT automaton, + MMLTModelParams params) { } From e76ec6a39dbd82fe890800eda9f11b9753073c50 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 18 Nov 2025 13:56:52 +0100 Subject: [PATCH 34/55] cleanup/generalize (integration-) tests --- ...xtensibleLStarMMLTCounterexampleTests.java | 37 +-- .../lstar/it/ExtensibleLStarMMLTIT.java | 254 +++++++----------- .../de/learnlib/example/mmlt/Example1.java | 37 ++- .../de/learnlib/example/ExamplesTest.java | 6 + .../learner/AbstractLearnerVariantITCase.java | 28 +- .../it/learner/AbstractMMLTLearnerIT.java | 88 ++++++ .../testsupport/it/learner/LearnerITUtil.java | 36 ++- .../it/learner/LearnerVariantList.java | 5 + .../it/learner/LearnerVariantListImpl.java | 6 + .../it/learner/MMLTLearnerITCase.java | 49 ++++ .../it/learner/OneSEVPALearnerITCase.java | 8 +- .../it/learner/SBALearnerITCase.java | 8 +- .../it/learner/SPALearnerITCase.java | 8 +- .../it/learner/SPMMLearnerITCase.java | 7 +- .../UniversalDeterministicLearnerITCase.java | 8 +- .../example/DefaultLearningExample.java | 23 ++ .../testsupport/example/LearningExample.java | 16 ++ .../testsupport/example/LearningExamples.java | 11 + .../example/mmlt/MMLTExamples.java | 146 +++++----- .../testsupport/example/mmlt/MMLTModel.java | 20 -- 20 files changed, 467 insertions(+), 334 deletions(-) create mode 100644 test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java create mode 100644 test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java delete mode 100644 test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index a42e33ce9..b55ac5501 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -1,20 +1,18 @@ package de.learnlib.algorithm.lstar; -import java.io.IOException; import java.util.Collections; import java.util.List; -import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT; +import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT.Example; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.example.mmlt.MMLTExamples; -import de.learnlib.testsupport.example.mmlt.MMLTModel; import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.exception.FormatException; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; @@ -28,13 +26,17 @@ @Test public class ExtensibleLStarMMLTCounterexampleTests { - private static void learnModel(MMLTModel model, List>> counterexamples) { + private static void learnModel(MMLTLearningExample example, + List>> counterexamples) { - var sul = new MMLTSimulatorSUL<>(model.automaton().getSemantics()); - var timeOracle = new TimedSULOracle<>(sul, model.params()); + var sul = new MMLTSimulatorSUL<>(example.getReferenceAutomaton().getSemantics()); + var timeOracle = new TimedSULOracle<>(sul, example.getParams()); - var learner = new ExtensibleLStarMMLT<>(model.automaton().getInputAlphabet(), model.params(), Collections.emptyList(), - timeOracle, new AcceptAllSymbolFilter<>()); + var learner = new ExtensibleLStarMMLT<>(example.getReferenceAutomaton().getInputAlphabet(), + example.getParams(), + Collections.emptyList(), + timeOracle, + new AcceptAllSymbolFilter<>()); learner.startLearning(); @@ -44,7 +46,7 @@ private static void learnModel(MMLTModel model, List simOracle = new SimulatorEQOracle<>(model.automaton()); + SimulatorEQOracle simOracle = new SimulatorEQOracle<>(example.getReferenceAutomaton()); DefaultQuery, Word>> cex; MMLT hyp = learner.getHypothesisModel(); @@ -56,9 +58,9 @@ private static void learnModel(MMLTModel model, List>> cex1 = List.of( @@ -69,9 +71,9 @@ public void testOverApproxReset() throws IOException, FormatException { } @Test - public void testRecursiveDecomp() throws IOException, FormatException { + public void testRecursiveDecomp() { // Triggers recursive decomposition - var model = ExtensibleLStarMMLTIT.automatonFromFile("recursive_decomp.dot", 3); + var model = new Example("recursive_decomp.dot", 3); // Missing discriminator at non-del in stable config: List>> cex1 = List.of( @@ -108,7 +110,7 @@ public void testMissingDiscriminators() { @Test public void testMissingResets() { var model = MMLTExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(40); + model.getParams().setMaxTimerQueryWaitingTime(40); // Missing reset in stable config: List>> cex1 = List.of( @@ -133,7 +135,7 @@ public void testMissingResets() { public void testMissingOneShotModelB() { // Setting max waiting = 6 -> all inferred timers are periodic: var model = MMLTExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(6); + model.getParams().setMaxTimerQueryWaitingTime(6); // Missing one-shot via bad return to entry: List>> cex1 = List.of( @@ -156,7 +158,7 @@ public void testMissingOneShotModelB() { @Test public void testMissingOneShotModelA() { var model = MMLTExamples.SensorCollector(); - model.params().setMaxTimerQueryWaitingTime(40); + model.getParams().setMaxTimerQueryWaitingTime(40); // Missing one-shot via bad output: List>> cex1 = List.of( @@ -176,5 +178,4 @@ public void testMissingOneShotModelA() { learnModel(model, cex2); } - } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 520bdade2..97676192f 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -13,175 +13,83 @@ import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; -import de.learnlib.driver.simulator.MMLTSimulatorSUL; -import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; -import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; -import de.learnlib.filter.statistic.sul.CounterTimedSUL; -import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; -import de.learnlib.oracle.equivalence.mmlt.EQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.RandomWpOracle; -import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; -import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; -import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.MMLTPerfectSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.MMLTStatisticsSymbolFilter; -import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.StatsContainer; -import de.learnlib.sul.TimedSUL; import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.testsupport.example.mmlt.MMLTExamples; -import de.learnlib.testsupport.example.mmlt.MMLTModel; -import de.learnlib.util.statistic.container.MapStatsContainer; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; +import de.learnlib.testsupport.it.learner.AbstractMMLTLearnerIT; +import de.learnlib.testsupport.it.learner.LearnerVariantList.MMLTLearnerVariantList; +import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; import net.automatalib.serialization.dot.DOTParsers; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.util.automaton.mmlt.MMLTUtil; import net.automatalib.word.Word; import org.testng.annotations.Test; -/** - * Integration tests for the MMLT learner that uses several EQ oracles, symbol filters - * and a cache to learn different models. - */ @Test -public class ExtensibleLStarMMLTIT { +public class ExtensibleLStarMMLTIT extends AbstractMMLTLearnerIT { - private enum FilterMode { - none, random, ignore_all, perfect - } - - private static void runExperiment(ExtensibleLStarMMLT learner, MMLTEquivalenceOracle tester, StatsContainer stats, int maxRounds) { - stats.startOrResumeClock("learningRt", "Processing time"); - learner.startLearning(); - - var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", "CEX queries"); - - int roundCount = 1; - while (cex != null && roundCount < maxRounds) { - learner.refineHypothesis(cex); - hyp = learner.getHypothesisModel(); - cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", null); - roundCount += 1; - } - stats.pauseClock("learningRt"); - - final var finalHypothesis = learner.getHypothesisModel(); - - // Add some more stats: - stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); - - // Print stats: - stats.printStats(); - } - - private static void learnModel(String name, MMLT automaton, MMLTModelParams params, - FilterMode symbolFilterMode, long seed) { + @Override + protected void addLearnerVariants(Alphabet alphabet, + TimedQueryOracle mqOracle, + MMLTLearningExample example, + MMLTLearnerVariantList variants) { - // Add some stats: - var stats = new MapStatsContainer(); - stats.addTextInfo("LocalTimerMealyModel", null, name); - stats.setCounter("original_locs", "Locations in original", automaton.getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", automaton.getInputAlphabet().size()); + var mmlt = example.getReferenceAutomaton(); + int counters = countTimers(mmlt); - // Set up a pipeline: - // Query oracle -> TimeoutReducer -> Cache -> Query stats -> SUL - MMLTSimulatorSUL sul = new MMLTSimulatorSUL<>(automaton.getSemantics()); - CounterTimedSUL statsAfterCache = new CounterTimedSUL<>(sul, stats); - TimedSULTreeCache cacheSUL = new TimedSULTreeCache<>(statsAfterCache, params); - cacheSUL.setStatsContainer(stats); - TimedSUL toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime(), stats); - - TimedSULOracle timeOracle = new TimedSULOracle<>(toReducerSul, params); - - // Prepare cex oracle chain: - - EQOracleChain chainOracle = new EQOracleChain<>(); - chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); - chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, seed, 1.0, 1.0)); - chainOracle.addOracle(new RandomWpOracle<>(timeOracle, seed, 16, 0, 100)); - chainOracle.addOracle(new SimulatorEQOracle<>(automaton)); // ensure that we eventually find an accurate model - chainOracle.setStatsContainer(stats); - - // Create learner: List>> suffixes = new ArrayList<>(); - automaton.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - // Configure symbol filter: - SymbolFilter, InputSymbol> filter = new AcceptAllSymbolFilter<>(); // pass-through - switch (symbolFilterMode) { - case perfect -> filter = new MMLTPerfectSymbolFilter<>(automaton); - case random -> filter = new MMLTRandomSymbolFilter<>(automaton, 0.1, new Random(seed)); - case ignore_all -> filter = new IgnoreAllSymbolFilter<>(); - } + for (FilterMode filterMode : FilterMode.values()) { - filter = new MMLTStatisticsSymbolFilter<>(automaton, filter, stats); - filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + SymbolFilter, InputSymbol> filter = switch (filterMode) { + case perfect -> new MMLTPerfectSymbolFilter<>(mmlt); + case random -> new MMLTRandomSymbolFilter<>(mmlt, 0.1, new Random(42)); + case ignore_all -> new IgnoreAllSymbolFilter<>(); + case none -> new AcceptAllSymbolFilter<>(); + }; - var learner = new ExtensibleLStarMMLT<>(automaton.getInputAlphabet(), params, suffixes, timeOracle, filter); - learner.setStatsContainer(stats); + filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - // Start learning: - runExperiment(learner, chainOracle, stats, 100); + var learner = new ExtensibleLStarMMLT<>(alphabet, example.getParams(), suffixes, mqOracle, filter); + variants.addLearnerVariant("system=" + example + ",filter=" + filterMode, learner, counters + mmlt.size()); + } } + @Override + protected List> getAdditionalLearningExamples() { + var modelFiles = listModelFiles(); + var result = new ArrayList>(modelFiles.size()); - @Test - public void learnExamplesNoFilter() throws IOException, FormatException { - for (String modelFile : listModelFiles()) { - var model = automatonFromFile(modelFile); - learnModel(model.name(), model.automaton(), model.params(), FilterMode.none, 100); + for (String modelFile : modelFiles) { + result.add(new Example(modelFile)); } - MMLTExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.none, 100)); - } - @Test - public void learnExamplesIgnoreAllFilter() throws IOException, FormatException { - for (String modelFile : listModelFiles()) { - var model = automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.ignore_all, 100); - } - MMLTExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.ignore_all, 100)); + return result; } - @Test - public void learnExamplesPerfectFilter() throws IOException, FormatException { - for (String modelFile : listModelFiles()) { - var model = automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.perfect, 100); - } - MMLTExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.perfect, 100)); - } + private static int countTimers(MMLT mmlt) { + int cntr = 0; - @Test - public void learnExamplesRandomFilter() throws IOException, FormatException { - for (String modelFile : listModelFiles()) { - var model = automatonFromFile(modelFile); - learnModel(modelFile, model.automaton(), model.params(), FilterMode.random, 100); + for (S s : mmlt) { + cntr += mmlt.getSortedTimers(s).size(); } - MMLTExamples.getAll().forEach(m -> - learnModel(m.name(), m.automaton(), m.params(), FilterMode.random, 100)); + + return cntr; } - /** - * Lists all MMLT models in the resources directory. - */ - static List listModelFiles() { + private static List listModelFiles() { var models = new ArrayList(); try { var modelFiles = ExtensibleLStarMMLTIT.class.getResource("/mmlt"); @@ -198,40 +106,60 @@ static List listModelFiles() { return models; } - public static MMLTModel automatonFromFile(String name) - throws IOException, FormatException { - return automatonFromFile(name, -1); + private enum FilterMode { + none, + random, + ignore_all, + perfect } - /** - * Loads the automaton model with the provided resource name. - * - * @param name Resource name - * @param maxTimerQueryWaiting Maximum timer query waiting time. If set to -1, the maximum initial timer value is used. - * @return The automaton model. - */ - public static MMLTModel automatonFromFile(String name, int maxTimerQueryWaiting) - throws IOException, FormatException { - - var silentOutput = "void"; - var outputCombiner = StringSymbolCombiner.getInstance(); - var parser = DOTParsers.mmlt(silentOutput, outputCombiner); - - try (InputStream is = ExtensibleLStarMMLTIT.class.getResourceAsStream("/mmlt/" + name)) { - var model = parser.readModel(is); - var automaton = model.model; - - long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? - maxTimerQueryWaiting : - MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; - - return new MMLTModel<>(name, - automaton, - new MMLTModelParams<>(silentOutput, - maxTimeoutDelay, - maxTimerQueryWaitingFinal, - outputCombiner)); + public static class Example implements MMLTLearningExample { + + private final String name; + private final MMLT mmlt; + private final MMLTModelParams params; + + public Example(String name) { + this(name, -1); + } + + public Example(String name, int maxTimerQueryWaiting) { + this.name = name; + + var silentOutput = "void"; + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + + try (InputStream is = ExtensibleLStarMMLTIT.class.getResourceAsStream("/mmlt/" + name)) { + var model = parser.readModel(is); + var automaton = model.model; + + long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? + maxTimerQueryWaiting : + MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + + this.mmlt = automaton; + this.params = + new MMLTModelParams<>(silentOutput, maxTimeoutDelay, maxTimerQueryWaitingFinal, outputCombiner); + } catch (IOException | FormatException e) { + throw new RuntimeException("Unable to load model " + name, e); + } + } + + @Override + public MMLTModelParams getParams() { + return this.params; + } + + @Override + public MMLT getReferenceAutomaton() { + return this.mmlt; + } + + @Override + public String toString() { + return this.name; } } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index b866eef0e..162cc373e 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -1,6 +1,5 @@ package de.learnlib.example.mmlt; -import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -14,8 +13,8 @@ import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.equivalence.mmlt.EQOracleChain; import de.learnlib.oracle.equivalence.mmlt.RandomWpOracle; -import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; @@ -26,11 +25,11 @@ import de.learnlib.testsupport.example.mmlt.MMLTExamples; import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.visualization.Visualization; import net.automatalib.word.Word; /** @@ -45,49 +44,49 @@ public static void main(String[] args) { // We first create a statistics container. // This container will store various statistical data during learning: var stats = new MapStatsContainer(); - stats.addTextInfo("LocalTimerMealyModel", null, model.name()); - stats.setCounter("original_locs", "Locations in original", model.automaton().getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", model.automaton().getInputAlphabet().size()); + stats.addTextInfo("LocalTimerMealyModel", null, model.toString()); + stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(model.automaton().getSemantics()); + var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul, stats); // We use a cache to avoid redundant operations: - var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.params()); + var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.getParams()); cacheSUL.setStatsContainer(stats); - var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.params().maxTimeoutWaitingTime(), stats); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.getParams().maxTimeoutWaitingTime(), stats); // We use a query oracle to answer queries from the learner: - var timeOracle = new TimedSULOracle<>(toReducerSul, model.params()); + var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); // We use a chain of different equivalence oracles: EQOracleChain chainOracle = new EQOracleChain<>(); chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, 100, 1.0, 1.0)); chainOracle.addOracle(new RandomWpOracle<>(timeOracle, 100, 16, 0, 100)); - chainOracle.addOracle(new SimulatorEQOracle<>(model.automaton())); // ensure that we eventually find an accurate model + chainOracle.addOracle(new SimulatorEQOracle<>(model.getReferenceAutomaton())); // ensure that we eventually find an accurate model chainOracle.setStatsContainer(stats); // Set up our L* learner: List>> suffixes = new ArrayList<>(); - model.automaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // A symbol filter allows us to reduce queries by exploiting prior knowledge. // For this example, we use a RandomSymbolFilter. This filter correctly predicts // whether a transition silently self-loops with an accuracy of 90%: SymbolFilter, InputSymbol> filter = - new MMLTRandomSymbolFilter<>(model.automaton(), 0.1, new Random(100)); + new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); - filter = new MMLTStatisticsSymbolFilter<>(model.automaton(), filter, stats); + filter = new MMLTStatisticsSymbolFilter<>(model.getReferenceAutomaton(), filter, stats); filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLT<>(model.automaton().getInputAlphabet(), model.params(), suffixes, timeOracle, filter); + var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, filter); learner.setStatsContainer(stats); // Start learning: @@ -131,13 +130,9 @@ private static void runExperiment(ExtensibleLStarMMLT learner, // Print final result + statistics: stats.printStats(); - System.out.println("Final hypothesis:"); - try { - GraphDOT.write(finalHypothesis.graphView(), System.out, - new MMLTVisualizationHelper<>(finalHypothesis, true, true)); - } catch (IOException ignored) { - } new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + System.out.println("Final hypothesis:"); + Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); } } diff --git a/examples/src/test/java/de/learnlib/example/ExamplesTest.java b/examples/src/test/java/de/learnlib/example/ExamplesTest.java index f1491d23d..5531d95f8 100644 --- a/examples/src/test/java/de/learnlib/example/ExamplesTest.java +++ b/examples/src/test/java/de/learnlib/example/ExamplesTest.java @@ -106,6 +106,12 @@ public void testBBCExample4() { de.learnlib.example.bbc.Example4.main(new String[0]); } + @Test + public void testMMLTExample1() { + requireJVMCompatibility(); + de.learnlib.example.mmlt.Example1.main(new String[0]); + } + @Test public void testParallelismExample1() { de.learnlib.example.parallelism.ParallelismExample1.main(new String[0]); diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java index 1b96e719f..ad99e4c61 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java @@ -26,17 +26,13 @@ import de.learnlib.testsupport.example.LearningExample; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.concept.FiniteRepresentation; -import net.automatalib.automaton.concept.Output; -import net.automatalib.common.util.random.RandomUtil; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.Assert; import org.testng.ITest; import org.testng.annotations.Test; -abstract class AbstractLearnerVariantITCase> implements ITest { +abstract class AbstractLearnerVariantITCase implements ITest { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLearnerVariantITCase.class); @@ -73,6 +69,7 @@ public void testLearning() { int roundCounter = 0; DefaultQuery ceQuery; + List> ceQueries = new ArrayList<>(); while ((ceQuery = eqOracle.findCounterExample(learner.getHypothesisModel(), alphabet)) != null) { roundCounter++; @@ -82,16 +79,21 @@ public void testLearning() { boolean refined = learner.refineHypothesis(ceQuery); Assert.assertTrue(refined, "Real counterexample " + ceQuery.getInput() + " did not refine hypothesis"); + ceQueries.add(ceQuery); } M hypothesis = learner.getHypothesisModel(); - Assert.assertEquals(hypothesis.size(), reference.size()); - Assert.assertNull(checkEquivalence(hypothesis), "Final hypothesis does not match reference automaton"); + Assert.assertTrue(testEquivalence(hypothesis), "Final hypothesis does not match reference automaton"); - final List trace = RandomUtil.sample(new Random(42), new ArrayList<>(alphabet), 5); - final D output = reference.computeOutput(trace); + if (hasCanonicalModel()) { + Assert.assertEquals(hypothesis.size(), reference.size(), "Final hypothesis is not canonical"); + } - Assert.assertFalse(learner.refineHypothesis(new DefaultQuery<>(Word.fromList(trace), output))); + if (!ceQueries.isEmpty()) { + DefaultQuery oldCe = ceQueries.get(new Random(42).nextInt(ceQueries.size())); + Assert.assertFalse(learner.refineHypothesis(oldCe), + "Learner should not report a hypothesis update on outdated counterexample"); + } long duration = (System.nanoTime() - start) / NANOS_PER_MILLISECOND; LOGGER.info(Category.EVENT, @@ -105,6 +107,10 @@ public String getTestName() { return variant.getLearnerName() + "[" + variant.getName() + "]/" + example.getClass().getSimpleName(); } - protected abstract @Nullable Word checkEquivalence(M hypothesis); + protected boolean hasCanonicalModel() { + return true; + } + + protected abstract boolean testEquivalence(M hypothesis); } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java new file mode 100644 index 000000000..522708592 --- /dev/null +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java @@ -0,0 +1,88 @@ +/* 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 de.learnlib.testsupport.it.learner; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; +import de.learnlib.testsupport.example.LearningExamples; +import de.learnlib.testsupport.it.learner.LearnerVariantList.MMLTLearnerVariantList; +import de.learnlib.testsupport.it.learner.LearnerVariantListImpl.MMLTLearnerVariantListImpl; +import net.automatalib.alphabet.Alphabet; +import org.testng.annotations.Factory; + +public abstract class AbstractMMLTLearnerIT { + + @Factory + public Object[] createExampleITCases() { + final List> examples = LearningExamples.createMMLTExamples(); + final List> extras = getAdditionalLearningExamples(); + final List> result = new ArrayList<>(); + + for (MMLTLearningExample example : examples) { + result.addAll(createAllVariantsITCase(example)); + } + for (MMLTLearningExample example : extras) { + result.addAll(createAllVariantsITCase(example)); + } + + return result.toArray(); + } + + private List> createAllVariantsITCase(MMLTLearningExample example) { + + final Alphabet alphabet = example.getUntimedAlphabet(); + final TimedQueryOracle mqOracle = + new TimedSULOracle<>(new MMLTSimulatorSUL<>(example.getReferenceAutomaton().getSemantics()), + example.getParams()); + final MMLTLearnerVariantListImpl variants = new MMLTLearnerVariantListImpl<>(); + addLearnerVariants(alphabet, mqOracle, example, variants); + + return LearnerITUtil.createExampleITCases(example, + variants, + new SimulatorEQOracle<>(example.getReferenceAutomaton())); + } + + protected List> getAdditionalLearningExamples() { + return Collections.emptyList(); + } + + /** + * Adds, for a given setup, all the variants of the MMLT learner to be tested to the specified + * {@link LearnerVariantList variant list}. + * + * @param + * input symbol type + * @param alphabet + * the input alphabet + * @param mqOracle + * the membership oracle + * @param example + * the learning example to potentially extract additional information + * @param variants + * list to add the learner variants to + */ + protected abstract void addLearnerVariants(Alphabet alphabet, + TimedQueryOracle mqOracle, + MMLTLearningExample example, + MMLTLearnerVariantList variants); +} diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerITUtil.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerITUtil.java index d81d0b177..da9fbef25 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerITUtil.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerITUtil.java @@ -23,12 +23,14 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.testsupport.example.LearningExample; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.example.LearningExample.OneSEVPALearningExample; import de.learnlib.testsupport.example.LearningExample.SBALearningExample; import de.learnlib.testsupport.example.LearningExample.SPALearningExample; import de.learnlib.testsupport.example.LearningExample.SPMMLearningExample; import de.learnlib.testsupport.example.LearningExample.UniversalDeterministicLearningExample; import de.learnlib.testsupport.example.PassiveLearningExample; +import de.learnlib.testsupport.it.learner.LearnerVariantListImpl.MMLTLearnerVariantListImpl; import de.learnlib.testsupport.it.learner.LearnerVariantListImpl.OneSEVPALearnerVariantListImpl; import de.learnlib.testsupport.it.learner.LearnerVariantListImpl.SBALearnerVariantListImpl; import de.learnlib.testsupport.it.learner.LearnerVariantListImpl.SPALearnerVariantListImpl; @@ -39,10 +41,13 @@ import net.automatalib.automaton.concept.FiniteRepresentation; import net.automatalib.automaton.concept.Output; import net.automatalib.automaton.concept.SuffixOutput; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; import net.automatalib.automaton.vpa.OneSEVPA; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; @@ -91,6 +96,33 @@ private LearnerITUtil() { UniversalDeterministicLearnerITCase::new); } + /** + * Creates a list of per-example test cases for all learner variants. + * + * @param example + * the example system + * @param variants + * the list containing the various learner variants + * @param eqOracle + * the equivalence oracle to use by the learning process + * @param + * input symbol type + * @param + * output symbol type + * + * @return the list of test cases, one for each example + */ + public static List> createExampleITCases(MMLTLearningExample example, + MMLTLearnerVariantListImpl variants, + EquivalenceOracle, TimedInput, Word>> eqOracle) { + // explicit generics are required for correct type-inference + return LearnerITUtil., Word>, MMLT, MMLTLearningExample, MMLTLearnerITCase>createExampleITCasesInternal( + example, + variants, + eqOracle, + MMLTLearnerITCase::new); + } + /** * Creates a list of per-example test cases for all learner variants. * @@ -193,7 +225,7 @@ public static List> createExampleITCases(OneSEVPALe OneSEVPALearnerITCase::new); } - private static , L extends LearningExample, C extends AbstractLearnerVariantITCase> List createExampleITCasesInternal( + private static , C extends AbstractLearnerVariantITCase> List createExampleITCasesInternal( L example, LearnerVariantListImpl variants, EquivalenceOracle eqOracle, @@ -266,7 +298,7 @@ public static > List, L extends LearningExample, C extends AbstractLearnerVariantITCase> { + private interface ITCaseBuilder, C extends AbstractLearnerVariantITCase> { C build(LearnerVariant variant, L example, EquivalenceOracle eqOracle); } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantList.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantList.java index 02537613f..b5404319a 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantList.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantList.java @@ -17,12 +17,15 @@ import de.learnlib.algorithm.LearningAlgorithm; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.automaton.vpa.OneSEVPA; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; /** @@ -79,6 +82,8 @@ interface MooreLearnerVariantList extends LearnerVariantList extends LearnerVariantList, I, O> {} + interface MMLTLearnerVariantList extends LearnerVariantList, TimedInput, Word>> {} + interface SPALearnerVariantList extends LearnerVariantList, I, Boolean> {} interface SBALearnerVariantList extends LearnerVariantList, I, Boolean> {} diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantListImpl.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantListImpl.java index ccdcae879..89149e98e 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantListImpl.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/LearnerVariantListImpl.java @@ -22,12 +22,15 @@ import de.learnlib.util.mealy.MealyUtil; import de.learnlib.util.moore.MooreUtil; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.automaton.vpa.OneSEVPA; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; public class LearnerVariantListImpl implements LearnerVariantList { @@ -60,6 +63,9 @@ public static class MooreLearnerVariantListImpl extends LearnerVariantListImpl, I, Word> implements MooreLearnerVariantList {} + public static class MMLTLearnerVariantListImpl extends LearnerVariantListImpl, TimedInput, Word>> + implements MMLTLearnerVariantList {} + public static class OneSEVPALearnerVariantListImpl extends LearnerVariantListImpl, I, Boolean> implements OneSEVPALearnerVariantList {} diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java new file mode 100644 index 000000000..9c5d311fd --- /dev/null +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java @@ -0,0 +1,49 @@ +/* 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 de.learnlib.testsupport.it.learner; + +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.word.Word; + +public class MMLTLearnerITCase + extends AbstractLearnerVariantITCase, Word>, MMLT> { + + private final MMLTLearningExample example; + + MMLTLearnerITCase(LearnerVariant, TimedInput, Word>> variant, + MMLTLearningExample example, + EquivalenceOracle, TimedInput, Word>> eqOracle) { + super(variant, example, eqOracle); + this.example = example; + } + + @Override + protected boolean hasCanonicalModel() { + return false; + } + + @Override + protected boolean testEquivalence(MMLT hypothesis) { + return MMLTUtil.testEquivalence(this.example.getReferenceAutomaton(), + hypothesis, + this.example.getReferenceAutomaton().getSemantics().getInputAlphabet()); + } +} diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/OneSEVPALearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/OneSEVPALearnerITCase.java index 8f6b9e77f..8d7fe1bbe 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/OneSEVPALearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/OneSEVPALearnerITCase.java @@ -19,8 +19,6 @@ import de.learnlib.testsupport.example.LearningExample.OneSEVPALearningExample; import net.automatalib.automaton.vpa.OneSEVPA; import net.automatalib.util.automaton.vpa.OneSEVPAs; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; public class OneSEVPALearnerITCase extends AbstractLearnerVariantITCase> { @@ -34,9 +32,7 @@ public class OneSEVPALearnerITCase extends AbstractLearnerVariantITCase checkEquivalence(OneSEVPA hypothesis) { - return OneSEVPAs.findSeparatingWord(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getAlphabet()); + protected boolean testEquivalence(OneSEVPA hypothesis) { + return OneSEVPAs.testEquivalence(this.example.getReferenceAutomaton(), hypothesis, this.example.getAlphabet()); } } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SBALearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SBALearnerITCase.java index 039ee9b78..68812b1b2 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SBALearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SBALearnerITCase.java @@ -19,8 +19,6 @@ import de.learnlib.testsupport.example.LearningExample.SBALearningExample; import net.automatalib.automaton.procedural.SBA; import net.automatalib.util.automaton.procedural.SBAs; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; public class SBALearnerITCase extends AbstractLearnerVariantITCase> { @@ -34,9 +32,7 @@ public class SBALearnerITCase extends AbstractLearnerVariantITCase checkEquivalence(SBA hypothesis) { - return SBAs.findSeparatingWord(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getAlphabet()); + protected boolean testEquivalence(SBA hypothesis) { + return SBAs.testEquivalence(this.example.getReferenceAutomaton(), hypothesis, this.example.getAlphabet()); } } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPALearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPALearnerITCase.java index 94ef835bc..315498a7a 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPALearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPALearnerITCase.java @@ -19,8 +19,6 @@ import de.learnlib.testsupport.example.LearningExample.SPALearningExample; import net.automatalib.automaton.procedural.SPA; import net.automatalib.util.automaton.procedural.SPAs; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; public class SPALearnerITCase extends AbstractLearnerVariantITCase> { @@ -34,9 +32,7 @@ public class SPALearnerITCase extends AbstractLearnerVariantITCase checkEquivalence(SPA hypothesis) { - return SPAs.findSeparatingWord(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getAlphabet()); + protected boolean testEquivalence(SPA hypothesis) { + return SPAs.testEquivalence(this.example.getReferenceAutomaton(), hypothesis, this.example.getAlphabet()); } } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPMMLearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPMMLearnerITCase.java index a5c0d2151..79cc0c26f 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPMMLearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/SPMMLearnerITCase.java @@ -20,7 +20,6 @@ import net.automatalib.automaton.procedural.SPMM; import net.automatalib.util.automaton.procedural.SPMMs; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; public class SPMMLearnerITCase extends AbstractLearnerVariantITCase, SPMM> { @@ -34,9 +33,7 @@ public class SPMMLearnerITCase extends AbstractLearnerVariantITCase checkEquivalence(SPMM hypothesis) { - return SPMMs.findSeparatingWord(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getAlphabet()); + protected boolean testEquivalence(SPMM hypothesis) { + return SPMMs.testEquivalence(this.example.getReferenceAutomaton(), hypothesis, this.example.getAlphabet()); } } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/UniversalDeterministicLearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/UniversalDeterministicLearnerITCase.java index defc7fdab..c541bcc50 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/UniversalDeterministicLearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/UniversalDeterministicLearnerITCase.java @@ -20,8 +20,6 @@ import net.automatalib.automaton.UniversalDeterministicAutomaton; import net.automatalib.automaton.concept.Output; import net.automatalib.util.automaton.Automata; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; public class UniversalDeterministicLearnerITCase & Output> extends AbstractLearnerVariantITCase { @@ -36,9 +34,7 @@ public class UniversalDeterministicLearnerITCase checkEquivalence(M hypothesis) { - return Automata.findSeparatingWord(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getAlphabet()); + protected boolean testEquivalence(M hypothesis) { + return Automata.testEquivalence(this.example.getReferenceAutomaton(), hypothesis, this.example.getAlphabet()); } } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java index 1d35b9723..eae6e956c 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java @@ -15,6 +15,7 @@ */ package de.learnlib.testsupport.example; +import de.learnlib.algorithm.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.VPAlphabet; @@ -22,6 +23,7 @@ import net.automatalib.automaton.concept.InputAlphabetHolder; import net.automatalib.automaton.concept.SuffixOutput; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; @@ -112,6 +114,27 @@ public DefaultSSTLearningExample(Alphabet alphabet, SubsequentialTransducer implements MMLTLearningExample { + + private final MMLT mmlt; + private final MMLTModelParams params; + + public DefaultMMLTLearningExample(MMLT mmlt, MMLTModelParams params) { + this.mmlt = mmlt; + this.params = params; + } + + @Override + public MMLTModelParams getParams() { + return this.params; + } + + @Override + public MMLT getReferenceAutomaton() { + return this.mmlt; + } + } + public static class DefaultOneSEVPALearningExample implements OneSEVPALearningExample { private final VPAlphabet alphabet; diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java index 0b3c6b986..fa6b7a68c 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java @@ -15,11 +15,13 @@ */ package de.learnlib.testsupport.example; +import de.learnlib.algorithm.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.VPAlphabet; import net.automatalib.automaton.UniversalAutomaton; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; @@ -28,6 +30,7 @@ import net.automatalib.automaton.transducer.StateLocalInputMealyMachine; import net.automatalib.automaton.transducer.SubsequentialTransducer; import net.automatalib.automaton.vpa.OneSEVPA; +import net.automatalib.symbol.time.TimedInput; public interface LearningExample { @@ -62,6 +65,19 @@ interface StateLocalInputMealyLearningExample } + interface MMLTLearningExample extends LearningExample, MMLT> { + + MMLTModelParams getParams(); + + default Alphabet> getAlphabet() { + return getReferenceAutomaton().getSemantics().getInputAlphabet(); + } + + default Alphabet getUntimedAlphabet() { + return getReferenceAutomaton().getInputAlphabet(); + } + } + interface SPALearningExample extends LearningExample> { @Override diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java index cd2e5f8b0..f462746c7 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java @@ -22,6 +22,7 @@ import java.util.Random; import de.learnlib.testsupport.example.LearningExample.DFALearningExample; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.example.LearningExample.MealyLearningExample; import de.learnlib.testsupport.example.LearningExample.MooreLearningExample; import de.learnlib.testsupport.example.LearningExample.OneSEVPALearningExample; @@ -41,6 +42,7 @@ import de.learnlib.testsupport.example.mealy.ExampleShahbazGroz; import de.learnlib.testsupport.example.mealy.ExampleStack; import de.learnlib.testsupport.example.mealy.ExampleTinyMealy; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; import de.learnlib.testsupport.example.moore.ExampleRandomMoore; import de.learnlib.testsupport.example.sba.ExampleRandomSBA; import de.learnlib.testsupport.example.spa.ExamplePalindrome; @@ -129,6 +131,15 @@ public static List> createDFAExamples() { RANDOM_SST_PROPS)); } + public static List> createMMLTExamples() { + return Arrays.asList(MMLTExamples.HVAC(), + MMLTExamples.SCTP(), + MMLTExamples.SensorCollector(), + MMLTExamples.WM(), + MMLTExamples.Oven(), + MMLTExamples.WSN()); + } + public static List> createSPAExamples() { return Arrays.asList(ExamplePalindrome.createExample(), ExampleRandomSPA.createExample(new Random(RANDOM_SEED), diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java index f5cb935d7..ac2e132f9 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java @@ -1,21 +1,18 @@ package de.learnlib.testsupport.example.mmlt; +import java.io.IOException; +import java.io.InputStream; + import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; import net.automatalib.serialization.dot.DOTParsers; import net.automatalib.util.automaton.mmlt.MMLTUtil; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - public class MMLTExamples { - public static List> getAll() { - return List.of(HVAC(), SCTP(), SensorCollector(), WM(), Oven(), WSN()); - } - /** * Returns an MMLT model of an HVAC system. *

          @@ -23,8 +20,8 @@ public class MMLTExamples { * * @return LocalTimerMealyModel */ - public static MMLTModel HVAC() { - return automatonFromFile("HVAC"); + public static MMLTLearningExample HVAC() { + return new Example("HVAC"); } /** @@ -34,108 +31,117 @@ public class MMLTExamples { * * @return LocalTimerMealyModel */ - public static MMLTModel SCTP() { - return automatonFromFile("SCTP"); + public static MMLTLearningExample SCTP() { + return new Example("SCTP"); } /** * Returns an MMLT model of a sensor collector. *

          - * The sensor measures particulate matter and ambient noise. - * The measurement program automatically ends after some time. The program may be restarted at any time. - * Alternatively, a self-check program can be entered. This also ends after some time and may be aborted. - * At the end of either program, the collected data may be retrieved. + * The sensor measures particulate matter and ambient noise. The measurement program automatically ends after some + * time. The program may be restarted at any time. Alternatively, a self-check program can be entered. This also + * ends after some time and may be aborted. At the end of either program, the collected data may be retrieved. * * @return LocalTimerMealyModel */ - public static MMLTModel SensorCollector() { - return automatonFromFile("sensor_collector"); + public static MMLTLearningExample SensorCollector() { + return new Example("sensor_collector"); } /** * Returns an MMLT model of a washing machine. *

          - * The machine is initially off. After powering it on and closing the door, - * the user can start either the short or the normal program. An open - * door prevents starting and triggers a warning. Not choosing a program within 10 seconds turns the machine off. + * The machine is initially off. After powering it on and closing the door, the user can start either the short or + * the normal program. An open door prevents starting and triggers a warning. Not choosing a program within 10 + * seconds turns the machine off. *

          - * In normal model, the machine fills the drum, heats the water, and starts the main wash. During this wash, - * it regularly adjusts the drum speed and maintains temperature. After 2 hours, - * the water is drained and the drum is spun at full speed for some time. Afterwards the remaining water is drained. - * The short program makes less adjustments, so that a wash ends after 1 hour. + * In normal model, the machine fills the drum, heats the water, and starts the main wash. During this wash, it + * regularly adjusts the drum speed and maintains temperature. After 2 hours, the water is drained and the drum is + * spun at full speed for some time. Afterwards the remaining water is drained. The short program makes less + * adjustments, so that a wash ends after 1 hour. *

          - * Both programs are interrupted when a leak is detected. Normal mode may also be interrupted by "stop". - * This drains the drum immediately. Once done, the door is unlocked, a message is shown, and the machine - * beeps repeatedly until the user presses any button or opens the door. + * Both programs are interrupted when a leak is detected. Normal mode may also be interrupted by "stop". This drains + * the drum immediately. Once done, the door is unlocked, a message is shown, and the machine beeps repeatedly until + * the user presses any button or opens the door. * * @return LocalTimerMealyModel */ - public static MMLTModel WM() { - return automatonFromFile("WM"); + public static MMLTLearningExample WM() { + return new Example("WM"); } /** * Returns an MMLT model of an oven with a time-controlled baking program. *

          - * After powering the oven on, the oven remains idle until the program is started. - * During the program, the oven regularly measures and adjusts the temperature. - * At the end of the program, an alarm sounds. Then, the user may extend the program. - * If not extended, the program ends either when the user opens the door, presses a button, or a timeout occurs. + * After powering the oven on, the oven remains idle until the program is started. During the program, the oven + * regularly measures and adjusts the temperature. At the end of the program, an alarm sounds. Then, the user may + * extend the program. If not extended, the program ends either when the user opens the door, presses a button, or a + * timeout occurs. * * @return LocalTimerMealyModel */ - public static MMLTModel Oven() { - return automatonFromFile("Oven"); + public static MMLTLearningExample Oven() { + return new Example("Oven"); } /** * Returns an MMLT model of a wireless sensor node. *

          - * The node regularly collects and transmits data. If the battery is low, no data is transmitted. Then, - * a user may collect the data manually. - * The node can be shut down at any time. If the battery is empty, it is shut down automatically. + * The node regularly collects and transmits data. If the battery is low, no data is transmitted. Then, a user may + * collect the data manually. The node can be shut down at any time. If the battery is empty, it is shut down + * automatically. * * @return LocalTimerMealyModel */ - public static MMLTModel WSN() { - return automatonFromFile("WSN"); + public static MMLTLearningExample WSN() { + return new Example("WSN"); } - // =================================== + private static class Example implements MMLTLearningExample { - /** - * Loads the automaton model with the provided resource name. - * Also infers suitable model parameters: - * - Maximum time to wait for a timeout in any configuration. - * - Maximum waiting time for timer queries. This must be at least the max. time for a timeout. - * We choose twice the maximum value of any timer in the model. When inferring a timer with one of these values, - * the learner has the chance to observe its timeout at least twice. This increases the chance of observing non-periodic behavior. - * - */ - static MMLTModel automatonFromFile(String name) { - var silentOutput = "void"; - var outputCombiner = StringSymbolCombiner.getInstance(); - var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + private final String name; + private final MMLT mmlt; + private final MMLTModelParams params; + + private Example(String name) { + this.name = name; + + var silentOutput = "void"; + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); - try (InputStream is = MMLTExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { - var model = parser.readModel(is); - var automaton = model.model; + try (InputStream is = MMLTExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { + var model = parser.readModel(is); + var automaton = model.model; - long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; - if (name.contains("SCTP")) { - maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + if (name.contains("SCTP")) { + maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + } + + this.mmlt = automaton; + this.params = + new MMLTModelParams<>(silentOutput, maxTimeoutDelay, maxTimerQueryWaitingFinal, outputCombiner); + } catch (IOException | FormatException e) { + throw new RuntimeException("Unable to load model " + name, e); } + } + + @Override + public MMLTModelParams getParams() { + return this.params; + } + + @Override + public MMLT getReferenceAutomaton() { + return this.mmlt; + } - return new MMLTModel<>(name, - automaton, - new MMLTModelParams<>(silentOutput, - maxTimeoutDelay, - maxTimerQueryWaitingFinal, - outputCombiner)); - } catch (IOException | FormatException e) { - throw new RuntimeException("Unable to load model " + name, e); + @Override + public String toString() { + return this.name; } } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java deleted file mode 100644 index a28dd681e..000000000 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTModel.java +++ /dev/null @@ -1,20 +0,0 @@ -package de.learnlib.testsupport.example.mmlt; - -import de.learnlib.algorithm.MMLTModelParams; -import net.automatalib.automaton.mmlt.MMLT; - -/** - * Convenience class for storing a name, an automaton and model parameters. - * - * @param name Automaton name - * @param automaton MMLT - * @param params Model parameters - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public record MMLTModel(String name, - MMLT automaton, - MMLTModelParams params) { - -} From 1926d9346dc79ddc4c77fe28b8fc29316e6212b5 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 19 Nov 2025 14:08:12 +0100 Subject: [PATCH 35/55] replace explicit StatsContainer with ServiceLoader-based approach --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 16 +--- .../mmlt/cex/MMLTCounterexampleHandler.java | 17 +--- .../{container => }/DummyStatsContainer.java | 4 +- .../de/learnlib/statistic/Statistics.java | 41 +++++++++ .../statistic/StatisticsProvider.java | 8 ++ .../{container => }/StatsContainer.java | 2 +- .../container/LearnerStatsProvider.java | 13 --- api/src/main/java/module-info.java | 5 +- commons/util/src/main/java/module-info.java | 1 - .../de/learnlib/example/mmlt/Example1.java | 30 +++--- .../filter/cache/mmlt/TimedSULTreeCache.java | 54 ++++++----- .../filter/cache/mmlt/TimeoutReducerSUL.java | 33 +++---- .../statistic/container/CounterStatistic.java | 2 +- .../statistic/container/FlagStatistic.java | 2 +- .../statistic/container/LearnerStatistic.java | 2 +- .../container/MapStatsContainer.java | 4 +- .../statistic/container/MapStatsProvider.java | 19 ++++ .../container/StopClockStatistic.java | 2 +- .../statistic/container/TextStatistic.java | 2 +- .../statistic/oracle/CounterEQOracle.java | 39 ++++++++ .../filter/statistic/sul/CounterTimedSUL.java | 23 ++--- .../statistics/src/main/java/module-info.java | 5 + .../oracle/equivalence/EQOracleChain.java | 14 +++ .../equivalence/mmlt/EQOracleChain.java | 92 ------------------- ...domWpOracle.java => RandomWpEQOracle.java} | 41 ++++----- ...chOracle.java => ResetSearchEQOracle.java} | 6 +- .../StatisticsSymbolFilter.java | 24 ++--- .../mmlt/MMLTStatisticsSymbolFilter.java | 4 +- 28 files changed, 242 insertions(+), 263 deletions(-) rename api/src/main/java/de/learnlib/statistic/{container => }/DummyStatsContainer.java (92%) create mode 100644 api/src/main/java/de/learnlib/statistic/Statistics.java create mode 100644 api/src/main/java/de/learnlib/statistic/StatisticsProvider.java rename api/src/main/java/de/learnlib/statistic/{container => }/StatsContainer.java (98%) delete mode 100644 api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/CounterStatistic.java (93%) rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/FlagStatistic.java (91%) rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/LearnerStatistic.java (94%) rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/MapStatsContainer.java (98%) create mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/StopClockStatistic.java (94%) rename {commons/util/src/main/java/de/learnlib/util => filters/statistics/src/main/java/de/learnlib/filter}/statistic/container/TextStatistic.java (87%) create mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java delete mode 100644 oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{RandomWpOracle.java => RandomWpEQOracle.java} (87%) rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{ResetSearchOracle.java => ResetSearchEQOracle.java} (96%) 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 5ae549c8a..93a9ff559 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 @@ -23,9 +23,8 @@ import de.learnlib.datastructure.observationtable.Row; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import de.learnlib.util.mealy.MealyUtil; @@ -50,10 +49,10 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ExtensibleLStarMMLT implements OTLearner, TimedInput, Word>>, LearnerStatsProvider { +public class ExtensibleLStarMMLT implements OTLearner, TimedInput, Word>> { private static final Logger logger = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); - private StatsContainer stats = new DummyStatsContainer(); + private final StatsContainer stats; private final ClosingStrategy, ? super Word>> closingStrategy; @@ -109,6 +108,7 @@ public ExtensibleLStarMMLT(Alphabet alphabet, this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; this.initialSuffixes = initialSuffixes; + this.stats = Statistics.getContainer(); // Prepare hyp data: @@ -424,12 +424,6 @@ protected void completeConsistentTable(List>>> unclosed) } - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - this.cexAnalyzer.setStatsContainer(container); - } - /** * Constructs a hypothesis MMLT from an observation table, inferred local resets, and inferred local timers. */ diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java index b1aabe4ea..07904b184 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java @@ -4,16 +4,13 @@ import de.learnlib.acex.AcexAnalyzer; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; import de.learnlib.algorithm.lstar.mmlt.cex.results.CexAnalysisResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; -import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.automaton.mmlt.MealyTimerInfo; @@ -34,10 +31,9 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MMLTCounterexampleHandler implements LearnerStatsProvider { +public class MMLTCounterexampleHandler { private static final Logger logger = LoggerFactory.getLogger(MMLTCounterexampleHandler.class); private final SymbolFilter, InputSymbol> symbolFilter; - private StatsContainer stats = new DummyStatsContainer(); protected final TimedQueryOracle timeOracle; private final MMLTCounterexampleDecompositor decompositor; @@ -48,13 +44,8 @@ public MMLTCounterexampleHandler(TimedQueryOracle timeOracle, AcexAnalyzer this.symbolFilter = symbolFilter; } - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - } - - public CexAnalysisResult analyzeInconsistency(MMLTOutputInconsistency outIncons, - MMLTHypothesis hypothesis) { + public CexAnalysisResult analyzeInconsistency(MMLTOutputInconsistency outIncons, + MMLTHypothesis hypothesis) { // Search for an extended decomposition: var decomposition = decompositor.findExtendedDecomposition(outIncons, hypothesis); diff --git a/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java b/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java similarity index 92% rename from api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java rename to api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java index 2066a7503..bbfdeb263 100644 --- a/api/src/main/java/de/learnlib/statistic/container/DummyStatsContainer.java +++ b/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java @@ -1,4 +1,4 @@ -package de.learnlib.statistic.container; +package de.learnlib.statistic; import org.checkerframework.checker.nullness.qual.Nullable; @@ -8,7 +8,7 @@ /** * A dummy implementation of {@link StatsContainer} that does nothing. */ -public class DummyStatsContainer implements StatsContainer { +class DummyStatsContainer implements StatsContainer { @Override public void addTextInfo(String id, @Nullable String description, String text) { diff --git a/api/src/main/java/de/learnlib/statistic/Statistics.java b/api/src/main/java/de/learnlib/statistic/Statistics.java new file mode 100644 index 000000000..be9870216 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/Statistics.java @@ -0,0 +1,41 @@ +package de.learnlib.statistic; + +import java.util.ServiceLoader; + +public class Statistics { + + private static final StatisticsProvider PROVIDER; + + static { + final ServiceLoader loader = ServiceLoader.load(StatisticsProvider.class); + + StatisticsProvider bestProvider = new DummyProvider(); + for (StatisticsProvider sp : loader) { + if (sp.getPriority() > bestProvider.getPriority()) { + bestProvider = sp; + } + } + + PROVIDER = bestProvider; + } + + public static StatsContainer getContainer() { + return PROVIDER.getContainer(); + } + + private static class DummyProvider implements StatisticsProvider { + + private static final StatsContainer CONTAINER = new DummyStatsContainer(); + + @Override + public int getPriority() { + return Integer.MIN_VALUE; + } + + @Override + public StatsContainer getContainer() { + return CONTAINER; + } + } + +} diff --git a/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java b/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java new file mode 100644 index 000000000..0d9abfd11 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java @@ -0,0 +1,8 @@ +package de.learnlib.statistic; + +public interface StatisticsProvider { + + int getPriority(); + + StatsContainer getContainer(); +} diff --git a/api/src/main/java/de/learnlib/statistic/container/StatsContainer.java b/api/src/main/java/de/learnlib/statistic/StatsContainer.java similarity index 98% rename from api/src/main/java/de/learnlib/statistic/container/StatsContainer.java rename to api/src/main/java/de/learnlib/statistic/StatsContainer.java index 90b5d3cbb..5660a76fa 100644 --- a/api/src/main/java/de/learnlib/statistic/container/StatsContainer.java +++ b/api/src/main/java/de/learnlib/statistic/StatsContainer.java @@ -1,4 +1,4 @@ -package de.learnlib.statistic.container; +package de.learnlib.statistic; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java b/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java deleted file mode 100644 index 79ee95c39..000000000 --- a/api/src/main/java/de/learnlib/statistic/container/LearnerStatsProvider.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.learnlib.statistic.container; - -/** - * Interface for a component that is interested in storing statistics in a container. - */ -public interface LearnerStatsProvider { - /** - * Provides a container for storing statistics. - * - * @param container Stats container. - */ - void setStatsContainer(StatsContainer container); -} diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index fd3814f99..b143216f7 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -14,6 +14,8 @@ * limitations under the License. */ +import de.learnlib.statistic.StatisticsProvider; + /** * This module provides the core interfaces of LearnLib. *

          @@ -43,6 +45,7 @@ exports de.learnlib.query; exports de.learnlib.statistic; exports de.learnlib.sul; - exports de.learnlib.statistic.container; exports de.learnlib.symbol_filter; + + uses StatisticsProvider; } diff --git a/commons/util/src/main/java/module-info.java b/commons/util/src/main/java/module-info.java index 0c15ae9d7..e4b6a888c 100644 --- a/commons/util/src/main/java/module-info.java +++ b/commons/util/src/main/java/module-info.java @@ -43,5 +43,4 @@ exports de.learnlib.util.moore; exports de.learnlib.util.nfa; exports de.learnlib.util.statistic; - exports de.learnlib.util.statistic.container; } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 162cc373e..6c3994770 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -9,21 +9,22 @@ import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.oracle.CounterEQOracle; import de.learnlib.filter.statistic.sul.CounterTimedSUL; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; -import de.learnlib.oracle.equivalence.mmlt.EQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.RandomWpOracle; -import de.learnlib.oracle.equivalence.mmlt.ResetSearchOracle; +import de.learnlib.oracle.equivalence.MMLTEQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.RandomWpEQOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; import de.learnlib.oracle.symbol_filters.mmlt.MMLTStatisticsSymbolFilter; import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.testsupport.example.mmlt.MMLTExamples; -import de.learnlib.util.statistic.container.MapStatsContainer; import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; @@ -43,7 +44,7 @@ public static void main(String[] args) { // We first create a statistics container. // This container will store various statistical data during learning: - var stats = new MapStatsContainer(); + var stats = Statistics.getContainer(); stats.addTextInfo("LocalTimerMealyModel", null, model.toString()); stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); @@ -54,23 +55,21 @@ public static void main(String[] args) { var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: - var statsAfterCache = new CounterTimedSUL<>(sul, stats); + var statsAfterCache = new CounterTimedSUL<>(sul); // We use a cache to avoid redundant operations: var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.getParams()); - cacheSUL.setStatsContainer(stats); - var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.getParams().maxTimeoutWaitingTime(), stats); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.getParams().maxTimeoutWaitingTime()); // We use a query oracle to answer queries from the learner: var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); // We use a chain of different equivalence oracles: - EQOracleChain chainOracle = new EQOracleChain<>(); - chainOracle.addOracle(cacheSUL.createCacheConsistencyTest()); - chainOracle.addOracle(new ResetSearchOracle<>(timeOracle, 100, 1.0, 1.0)); - chainOracle.addOracle(new RandomWpOracle<>(timeOracle, 100, 16, 0, 100)); - chainOracle.addOracle(new SimulatorEQOracle<>(model.getReferenceAutomaton())); // ensure that we eventually find an accurate model - chainOracle.setStatsContainer(stats); + MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); + chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); + chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + chainOracle.addOracle(new CounterEQOracle<>(new SimulatorEQOracle<>(model.getReferenceAutomaton()), "sim")); // ensure that we eventually find an accurate model // Set up our L* learner: List>> suffixes = new ArrayList<>(); @@ -87,7 +86,6 @@ public static void main(String[] args) { filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, filter); - learner.setStatsContainer(stats); // Start learning: runExperiment(learner, chainOracle, stats, 100); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java index a084ad9c9..32edba421 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java @@ -1,36 +1,40 @@ package de.learnlib.filter.cache.mmlt; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.filter.cache.LearningCache.MMLTLearningCache; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.sul.TimedSUL; import net.automatalib.alphabet.impl.GrowingMapAlphabet; - -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.automaton.transducer.impl.CompactMealy; import net.automatalib.graph.Graph; import net.automatalib.graph.concept.GraphViewable; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.*; - /** * Caches queries sent to a LocalTimerMealySUL. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type */ -public class TimedSULTreeCache implements TimedSUL, - MMLTLearningCache, GraphViewable, LearnerStatsProvider { +public class TimedSULTreeCache implements TimedSUL, MMLTLearningCache, GraphViewable { + private final TimedSUL delegate; private final CacheTreeNode cacheRoot; @@ -40,24 +44,19 @@ public class TimedSULTreeCache implements TimedSUL, private final TimedOutput silentOutput; private boolean cacheMiss; - private StatsContainer stats = new DummyStatsContainer(); - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - } + private final StatsContainer stats; public TimedSULTreeCache(TimedSUL delegate, MMLTModelParams modelParams) { this.delegate = delegate; this.modelParams = modelParams; this.silentOutput = new TimedOutput<>(modelParams.silentOutput()); + this.stats = Statistics.getContainer(); // Init cache: this.cacheRoot = new CacheTreeNode<>(null, null); this.currentState = null; } - private void followCurrentPrefix() { this.delegate.pre(); @@ -95,7 +94,6 @@ public TimedOutput step(InputSymbol input) { return output; } - @Override public @Nullable TimedOutput timeoutStep(long maxTime) { if (currentState == null) { @@ -134,19 +132,18 @@ public TimedOutput step(InputSymbol input) { this.cacheMiss = true; } - TimedOutput timeoutStepResult = this.delegate.timeoutStep(remaining); if (timeoutStepResult == null) { // no timers here this.currentState = this.currentState.addTimeChild(remaining, this.silentOutput); return null; } else { - this.currentState = this.currentState.addTimeChild(timeoutStepResult.delay(), new TimedOutput<>(timeoutStepResult.symbol())); + this.currentState = this.currentState.addTimeChild(timeoutStepResult.delay(), + new TimedOutput<>(timeoutStepResult.symbol())); return new TimedOutput<>(timeoutStepResult.symbol(), maxTime - remaining + timeoutStepResult.delay()); } } - @Override public void pre() { this.currentState = this.cacheRoot; @@ -225,7 +222,6 @@ public List>> listAllWords() { return finalWords; } - @Override public Graph graphView() { // Convert tree to a mealy automaton: @@ -237,7 +233,6 @@ public List>> listAllWords() { Deque> pending = new ArrayDeque<>(); pending.add(this.cacheRoot); - while (!pending.isEmpty()) { CacheTreeNode current = pending.remove(); @@ -248,7 +243,10 @@ public List>> listAllWords() { pending.add(child); } mealy.addAlphabetSymbol(new TimeStepSequence<>(current.getTimeout())); - mealy.addTransition(stateMap.get(current), new TimeStepSequence<>(current.getTimeout()), stateMap.get(child), current.getTimeoutOutput()); + mealy.addTransition(stateMap.get(current), + new TimeStepSequence<>(current.getTimeout()), + stateMap.get(child), + current.getTimeoutOutput()); } for (var sym : current.getUntimedChildren().keySet()) { diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index ebc521d15..b2f630198 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -1,42 +1,36 @@ package de.learnlib.filter.cache.mmlt; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; import de.learnlib.sul.TimedSUL; -import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedOutput; import org.checkerframework.checker.nullness.qual.Nullable; /** * Avoids redundant queries for timeouts. *

          - * Assume we waited maxDelay for a timeout and observed no expiration. - * Then, any consecutive timeout-input must also show no timer (assuming sufficient maxDelay). - * Hence, we do not need to query the SUL for these. + * Assume we waited maxDelay for a timeout and observed no expiration. Then, any consecutive timeout-input must also + * show no timer (assuming sufficient maxDelay). Hence, we do not need to query the SUL for these. *

          - * We may observe a timeout again after any non-delaying input, as this may - * trigger a location-change. + * We may observe a timeout again after any non-delaying input, as this may trigger a location-change. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type */ -public class TimeoutReducerSUL implements TimedSUL, LearnerStatsProvider { +public class TimeoutReducerSUL implements TimedSUL { private final TimedSUL delegate; private final long maxDelay; /** - * Delay since the last non-delaying input OR - * timer expiration. + * Delay since the last non-delaying input OR timer expiration. */ private long noTimeoutWaitingTime; - private StatsContainer stats; - - public TimeoutReducerSUL(TimedSUL delegate, long maxDelay, StatsContainer stats) { + public TimeoutReducerSUL(TimedSUL delegate, long maxDelay) { this.delegate = delegate; this.maxDelay = maxDelay; - this.stats = stats; } @Override @@ -72,9 +66,4 @@ public void pre() { public void post() { delegate.post(); } - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - } } diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java similarity index 93% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java index c14a33bc3..d4bee83dc 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/CounterStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java @@ -1,4 +1,4 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; /** * A counter that can be increased and set to a particular positive number. diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java similarity index 91% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java index 5bb04bcec..e0be903f5 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/FlagStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java @@ -1,4 +1,4 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java similarity index 94% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java index efba05c48..afd652e1d 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/LearnerStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java @@ -1,4 +1,4 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java similarity index 98% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java index 9fce4f4e8..ecb72edd9 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/MapStatsContainer.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java @@ -1,6 +1,6 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.StatsContainer; import org.checkerframework.checker.nullness.qual.Nullable; import java.time.Duration; diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java new file mode 100644 index 000000000..eb5e3c6ad --- /dev/null +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java @@ -0,0 +1,19 @@ +package de.learnlib.filter.statistic.container; + +import de.learnlib.statistic.StatisticsProvider; +import de.learnlib.statistic.StatsContainer; + +public class MapStatsProvider implements StatisticsProvider { + + final ThreadLocal threadLocal = ThreadLocal.withInitial(MapStatsContainer::new); + + @Override + public int getPriority() { + return 0; + } + + @Override + public StatsContainer getContainer() { + return threadLocal.get(); + } +} diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java similarity index 94% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java index 9adf3c797..174c61030 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/StopClockStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java @@ -1,4 +1,4 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java similarity index 87% rename from commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java rename to filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java index 5d9e81aaf..2beb08935 100644 --- a/commons/util/src/main/java/de/learnlib/util/statistic/container/TextStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java @@ -1,4 +1,4 @@ -package de.learnlib.util.statistic.container; +package de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java new file mode 100644 index 000000000..507929d60 --- /dev/null +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java @@ -0,0 +1,39 @@ +package de.learnlib.filter.statistic.oracle; + +import java.util.Collection; + +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; +import org.checkerframework.checker.nullness.qual.Nullable; + +public class CounterEQOracle implements EquivalenceOracle { + + private final EquivalenceOracle delegate; + private final StatsContainer stats; + private final String prefix; + + public CounterEQOracle(EquivalenceOracle delegate) { + this(delegate, ""); + } + + public CounterEQOracle(EquivalenceOracle delegate, String prefix) { + this.delegate = delegate; + this.prefix = prefix; + this.stats = Statistics.getContainer(); + } + + @Override + public @Nullable DefaultQuery findCounterExample(A hypothesis, Collection inputs) { + final String suffix = prefix.isEmpty() ? "" : " from '" + prefix + '\''; + + stats.startOrResumeClock(prefix + "-cex-dur", "Duration of CEX search" + suffix); + final DefaultQuery cex = this.delegate.findCounterExample(hypothesis, inputs); + stats.pauseClock(prefix + "-cex-dur"); + if (cex != null) { + stats.increaseCounter(prefix + "-cex-cnt", "Found CEX" + suffix); + } + return cex; + } +} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index b6bf750da..065f2ca1e 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -1,11 +1,11 @@ package de.learnlib.filter.statistic.sul; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.sul.TimedSUL; -import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -16,26 +16,21 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class CounterTimedSUL implements TimedSUL, LearnerStatsProvider { +public class CounterTimedSUL implements TimedSUL { private final TimedSUL delegate; - private StatsContainer stats; + private final StatsContainer stats; @Nullable private final String name; - public CounterTimedSUL(TimedSUL delegate, StatsContainer stats) { - this(delegate, stats, null); + public CounterTimedSUL(TimedSUL delegate) { + this(delegate, null); } - public CounterTimedSUL(TimedSUL delegate, StatsContainer stats, String name) { + public CounterTimedSUL(TimedSUL delegate, String name) { this.delegate = delegate; - this.stats = stats; this.name = name; - } - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; + this.stats = Statistics.getContainer(); } private String withPrefix(String label) { diff --git a/filters/statistics/src/main/java/module-info.java b/filters/statistics/src/main/java/module-info.java index 90ce2aef0..f627ed4e4 100644 --- a/filters/statistics/src/main/java/module-info.java +++ b/filters/statistics/src/main/java/module-info.java @@ -14,6 +14,9 @@ * limitations under the License. */ +import de.learnlib.filter.statistic.container.MapStatsProvider; +import de.learnlib.statistic.StatisticsProvider; + /** * This module provides filters for collecting statistical data. *

          @@ -40,4 +43,6 @@ exports de.learnlib.filter.statistic.learner; exports de.learnlib.filter.statistic.oracle; exports de.learnlib.filter.statistic.sul; + + provides StatisticsProvider with MapStatsProvider; } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/EQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/EQOracleChain.java index 50b8e9d26..cf0fed167 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/EQOracleChain.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/EQOracleChain.java @@ -22,6 +22,7 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.oracle.EquivalenceOracle.DFAEquivalenceOracle; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.EquivalenceOracle.MealyEquivalenceOracle; import de.learnlib.oracle.EquivalenceOracle.MooreEquivalenceOracle; import de.learnlib.query.DefaultQuery; @@ -30,8 +31,10 @@ import de.learnlib.tooling.annotation.refinement.Interface; import de.learnlib.tooling.annotation.refinement.Mapping; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -66,6 +69,17 @@ generics = {@Generic("I"), @Generic("O")}), interfaces = @Interface(clazz = MooreEquivalenceOracle.class, generics = {@Generic("I"), @Generic("O")})) +@GenerateRefinement(name = "MMLTEQOracleChain", + generics = {@Generic(value = "I", desc = "input symbol type"), + @Generic(value = "O", desc = "output symbol type")}, + parentGenerics = {@Generic(clazz = MMLT.class, generics = {"?", "I", "?", "O"}), + @Generic(clazz = TimedInput.class, generics = "I"), + @Generic(clazz = Word.class, generics = "net.automatalib.symbol.time.TimedOutput")}, + typeMappings = @Mapping(from = EquivalenceOracle.class, + to = MMLTEquivalenceOracle.class, + generics = {@Generic("I"), @Generic("O")}), + interfaces = @Interface(clazz = MMLTEquivalenceOracle.class, + generics = {@Generic("I"), @Generic("O")})) public class EQOracleChain implements EquivalenceOracle { private final List> oracles; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java deleted file mode 100644 index dfae400a5..000000000 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/EQOracleChain.java +++ /dev/null @@ -1,92 +0,0 @@ -package de.learnlib.oracle.equivalence.mmlt; - -import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; -import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -/** - * A chain of MMLT equivalence oracles. The oracles are queried in the given order until either a counterexample is found - * or nor example is found. - *

          - * This operates similarly to {@link de.learnlib.oracle.equivalence.EQOracleChain}, - * but also stores statistics about the queries in a {@link StatsContainer}. - * - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public class EQOracleChain implements MMLTEquivalenceOracle, LearnerStatsProvider { - - private static final Logger logger = LoggerFactory.getLogger(EQOracleChain.class); - - private final List> oracles = new ArrayList<>(); - private StatsContainer stats = new DummyStatsContainer(); - - /** - * Stores names for the equivalence oracles that are used in statistics. - */ - private List oracleNames; - - public void addOracle(MMLTEquivalenceOracle oracle) { - this.oracles.add(oracle); - - // Update names: - // Names follow the convention typeName + # + index of this oracle among all oracles of same type. - this.oracleNames = new ArrayList<>(oracles.size()); - - Map typeCounter = new HashMap<>(); - for (var eqOracle : this.oracles) { - String typeName = eqOracle.getClass().getSimpleName(); - - int currentCount = typeCounter.getOrDefault(typeName, -1); - int newCount = currentCount + 1; - typeCounter.put(typeName, newCount); - - this.oracleNames.add(typeName + "#" + newCount); - } - } - - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - - // Propagate to all oracles: - for (var oracle : this.oracles) { - if (oracle instanceof LearnerStatsProvider provider) { - provider.setStatsContainer(stats); - } - } - } - - @Override - public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { - if (this.oracles.isEmpty()) throw new IllegalStateException("Must specify at least one cex oracle in chain."); - - int oracleIdx = 0; - for (var oracle : this.oracles) { - var cex = oracle.findCounterExample(hypothesis, inputs); - if (cex != null) { - String oracleName = this.oracleNames.get(oracleIdx); - stats.increaseCounter("cnt_cex_" + oracleName, "CEX from " + oracleName); - - logger.debug("{} found counterexample: {}", "cnt_cex_" + oracleName, cex); - - return cex; - } - oracleIdx++; - } - - return null; - } -} diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java similarity index 87% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java index 10a1be877..b1fd16093 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java @@ -1,25 +1,26 @@ package de.learnlib.oracle.equivalence.mmlt; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.List; +import java.util.Random; + import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; import net.automatalib.common.util.string.AbstractPrintable; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.util.automaton.Automata; import net.automatalib.util.automaton.cover.MMLTCover; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; /** * RandomWP counterexample search for MMLT learning. @@ -28,22 +29,22 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class RandomWpOracle implements MMLTEquivalenceOracle, LearnerStatsProvider { - private static final Logger logger = LoggerFactory.getLogger(RandomWpOracle.class); - private final TimedQueryOracle timeOracle; +public class RandomWpEQOracle implements MMLTEquivalenceOracle { - private StatsContainer stats = new DummyStatsContainer(); + private final TimedQueryOracle timeOracle; + private final StatsContainer stats; private final Random random; private final int minSize; private final int rndLen; private final int bound; - public RandomWpOracle(TimedQueryOracle timeOracle, - long randomSeed, - int minSize, int rndAddLength, int bound) { + public RandomWpEQOracle(TimedQueryOracle timeOracle, + long randomSeed, + int minSize, int rndAddLength, int bound) { this.timeOracle = timeOracle; + this.stats = Statistics.getContainer(); this.random = new Random(randomSeed); @@ -138,10 +139,4 @@ private DefaultQuery, Word>> generateTestwor var sulAnswer = timeOracle.answerQuery(testWord); return new DefaultQuery<>(testWord, sulAnswer); } - - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - } } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java similarity index 96% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index fa4279115..ab949d26a 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -36,9 +36,9 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ResetSearchOracle implements MMLTEquivalenceOracle { +public class ResetSearchEQOracle implements MMLTEquivalenceOracle { - private final static Logger logger = LoggerFactory.getLogger(ResetSearchOracle.class); + private final static Logger logger = LoggerFactory.getLogger(ResetSearchEQOracle.class); private final TimedQueryOracle timeOracle; private final Random locPrefixRandom; @@ -48,7 +48,7 @@ public class ResetSearchOracle implements MMLTEquivalenceOracle { private final long loopingInputSelectionSeed; - public ResetSearchOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { + public ResetSearchEQOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { this.timeOracle = timeOracle; this.locPrefixRandom = new Random(seed); this.loopInsertPerc = loopInsertPerc; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java index 221c98936..d82b486a3 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java @@ -1,8 +1,7 @@ package de.learnlib.oracle.symbol_filters; -import de.learnlib.statistic.container.DummyStatsContainer; -import de.learnlib.statistic.container.LearnerStatsProvider; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.word.Word; @@ -10,17 +9,19 @@ /** * Collects various statistics on symbol filtering, including false accepts + false ignores. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * Type for symbols in the prefix of the considered states + * @param + * Type of the queried symbols */ -public abstract class StatisticsSymbolFilter implements SymbolFilter, LearnerStatsProvider { +public abstract class StatisticsSymbolFilter implements SymbolFilter { private final SymbolFilter delegate; - private StatsContainer stats = new DummyStatsContainer(); + private final StatsContainer stats; - public StatisticsSymbolFilter(SymbolFilter delegate, StatsContainer stats) { + public StatisticsSymbolFilter(SymbolFilter delegate) { this.delegate = delegate; - this.stats = stats; + this.stats = Statistics.getContainer(); } protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); @@ -54,9 +55,4 @@ public SymbolFilterResponse query(Word prefix, V symbol) { public void update(Word prefix, V symbol, SymbolFilterResponse response) { delegate.update(prefix, symbol, response); } - - @Override - public void setStatsContainer(StatsContainer container) { - this.stats = container; - } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java index 53e71c91d..2553fc2ba 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java +++ b/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java @@ -1,7 +1,7 @@ package de.learnlib.oracle.symbol_filters.mmlt; import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; -import de.learnlib.statistic.container.StatsContainer; +import de.learnlib.statistic.StatsContainer; import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.symbol_filter.SymbolFilterResponse; import net.automatalib.symbol.time.TimedInput; @@ -14,7 +14,7 @@ public class MMLTStatisticsSymbolFilter extends StatisticsSymbolFilter automaton; public MMLTStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatsContainer stats) { - super(delegate, stats); + super(delegate); this.automaton = automaton; } From 910274d18ef4c262f88667a7b8b092856aedd6bc Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Wed, 19 Nov 2025 20:37:23 +0100 Subject: [PATCH 36/55] experimental: replace old statistics collection with new approach code compiles but parallel oracle tests still fail because the implementation is not thread-safe. final solution is still open for discussion. --- .../de/learnlib/algorithm/adt/it/ADTIT.java | 5 +- .../statistic/DummyStatsContainer.java | 32 ++---- .../statistic/StatisticCollector.java | 32 ------ .../de/learnlib/statistic/StatisticData.java | 31 ------ .../learnlib/statistic/StatisticLearner.java | 41 ------- .../learnlib/statistic/StatisticOracle.java | 29 ----- .../de/learnlib/statistic/StatisticSUL.java | 20 ---- .../de/learnlib/statistic/StatsContainer.java | 4 +- .../resources/archetype-resources/pom.xml | 4 + .../src/main/java/Example.java | 14 +-- .../src/main/java/Example.java | 14 +-- commons/util/pom.xml | 9 +- .../java/de/learnlib/util/Experiment.java | 82 ++++---------- .../util/statistic/SimpleProfiler.java | 97 ----------------- commons/util/src/main/java/module-info.java | 2 - .../java/de/learnlib/util/ExperimentTest.java | 45 +++++--- .../java/de/learnlib/example/Example1.java | 14 +-- .../java/de/learnlib/example/Example2.java | 18 +-- .../parallelism/ParallelismExample2.java | 9 +- .../example/resumable/ResumableExample.java | 3 +- .../de/learnlib/example/sli/Example2.java | 9 +- .../filter/cache/AbstractCacheTest.java | 2 + .../cache/dfa/AbstractDFACacheTest.java | 3 +- .../filter/cache/dfa/DFAHashCacheTest.java | 3 +- .../cache/dfa/DFAParallelCacheTest.java | 8 +- .../cache/mealy/AbstractMealyCacheTest.java | 3 +- .../cache/mealy/AdaptiveQueryCacheTest.java | 3 +- .../cache/mealy/MealyParallelCacheTest.java | 8 +- .../cache/moore/AbstractMooreCacheTest.java | 3 +- .../cache/moore/MooreParallelCacheTest.java | 8 +- .../cache/sul/AbstractSULCacheTest.java | 3 +- .../cache/sul/SLISULParallelCacheTest.java | 7 +- .../cache/sul/SULParallelCacheTest.java | 6 +- .../sul/StateLocalInputSULTreeCacheTest.java | 20 ++-- .../statistic/AbstractStatisticData.java | 44 -------- .../de/learnlib/filter/statistic/Counter.java | 54 --------- .../filter/statistic/CounterCollection.java | 63 ----------- .../filter/statistic/HistogramDataSet.java | 99 ----------------- .../container/MapStatsContainer.java | 22 ++-- .../learner/RefinementCounterLearner.java | 36 +++--- .../oracle/CounterAdaptiveQueryOracle.java | 53 ++++----- .../statistic/oracle/CounterOracle.java | 53 ++++----- .../statistic/oracle/HistogramOracle.java | 103 ------------------ .../statistic/sul/CounterObservableSUL.java | 10 +- .../filter/statistic/sul/CounterSUL.java | 42 +++---- .../sul/CounterStateLocalInputSUL.java | 33 ++---- .../statistics/src/main/java/module-info.java | 1 - .../oracle/CounterAdaptiveOracleTest.java | 23 ++-- .../statistic/oracle/CounterOracleTest.java | 17 ++- .../statistic/oracle/HistogramOracleTest.java | 97 ----------------- .../statistic/sul/AbstractCounterSULTest.java | 11 +- .../sul/AbstractResetCounterSULTest.java | 4 +- .../sul/AbstractSymbolCounterSULTest.java | 4 +- .../sul/ResetCounterObservableSULTest.java | 10 +- .../statistic/sul/ResetCounterSULTest.java | 10 +- .../ResetCounterStateLocalInputSULTest.java | 10 +- .../sul/SymbolCounterObservableSULTest.java | 10 +- .../statistic/sul/SymbolCounterSULTest.java | 10 +- .../SymbolCounterStateLocalInputSULTest.java | 10 +- 59 files changed, 322 insertions(+), 1098 deletions(-) delete mode 100644 api/src/main/java/de/learnlib/statistic/StatisticCollector.java delete mode 100644 api/src/main/java/de/learnlib/statistic/StatisticData.java delete mode 100644 api/src/main/java/de/learnlib/statistic/StatisticLearner.java delete mode 100644 api/src/main/java/de/learnlib/statistic/StatisticOracle.java delete mode 100644 api/src/main/java/de/learnlib/statistic/StatisticSUL.java delete mode 100644 commons/util/src/main/java/de/learnlib/util/statistic/SimpleProfiler.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/AbstractStatisticData.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/Counter.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/CounterCollection.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/HistogramDataSet.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/HistogramOracle.java delete mode 100644 filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/HistogramOracleTest.java diff --git a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java index c1bead3f7..29f0163f3 100644 --- a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java +++ b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java @@ -42,6 +42,7 @@ import de.learnlib.oracle.equivalence.MealySimulatorEQOracle; import de.learnlib.oracle.membership.MealySimulatorOracle; import de.learnlib.oracle.membership.SULAdaptiveOracle; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import de.learnlib.testsupport.MQ2AQWrapper; import de.learnlib.testsupport.it.learner.AbstractMealyLearnerIT; @@ -130,6 +131,7 @@ public void testIssue137() throws IOException, FormatException { for (int seed = 0; seed < 50; seed++) { long last = 0; for (int iter = 0; iter < 5; iter++) { + Statistics.getContainer().clear(); final CounterAdaptiveQueryOracle counter = new CounterAdaptiveQueryOracle<>(aqo); final ADTLearner learner = new ADTLearner<>(alphabet, @@ -151,7 +153,8 @@ public void testIssue137() throws IOException, FormatException { exp.run(); - final long count = counter.getResetCounter().getCount(); + final long count = + Statistics.getContainer().getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L); if (iter == 0) { last = count; diff --git a/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java b/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java index bbfdeb263..a067d3fa4 100644 --- a/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java +++ b/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java @@ -9,10 +9,9 @@ * A dummy implementation of {@link StatsContainer} that does nothing. */ class DummyStatsContainer implements StatsContainer { - @Override - public void addTextInfo(String id, @Nullable String description, String text) { - } + @Override + public void addTextInfo(String id, @Nullable String description, String text) {} @Override public Optional getTextValue(String id) { @@ -20,9 +19,7 @@ public Optional getTextValue(String id) { } @Override - public void setFlag(String id, @Nullable String description, boolean value) { - - } + public void setFlag(String id, @Nullable String description, boolean value) {} @Override public Optional getFlagValue(String id) { @@ -30,14 +27,10 @@ public Optional getFlagValue(String id) { } @Override - public void startOrResumeClock(String id, @Nullable String description) { - - } + public void startOrResumeClock(String id, @Nullable String description) {} @Override - public void pauseClock(String id) { - - } + public void pauseClock(String id) {} @Override public Optional getClockValue(String id) { @@ -45,14 +38,10 @@ public Optional getClockValue(String id) { } @Override - public void increaseCounter(String id, @Nullable String description, long increment) { - - } + public void increaseCounter(String id, @Nullable String description, long increment) {} @Override - public void setCounter(String id, @Nullable String description, long count) { - - } + public void setCounter(String id, @Nullable String description, long count) {} @Override public Optional getCount(String id) { @@ -60,7 +49,10 @@ public Optional getCount(String id) { } @Override - public void printStats() { - System.out.println("Dummy container"); + public void clear() {} + + @Override + public String printStats() { + return "Dummy container"; } } diff --git a/api/src/main/java/de/learnlib/statistic/StatisticCollector.java b/api/src/main/java/de/learnlib/statistic/StatisticCollector.java deleted file mode 100644 index 9919b876a..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatisticCollector.java +++ /dev/null @@ -1,32 +0,0 @@ -/* 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 de.learnlib.statistic; - -/** - * A utility interface to indicate that the implementing class collects statistical information that may be obtained via - * its {@link #getStatisticalData()} method. - */ -@FunctionalInterface -public interface StatisticCollector { - - /** - * Returns this statistical data gathered by this collector. - * - * @return the statistical data gathered by this collector - */ - StatisticData getStatisticalData(); - -} diff --git a/api/src/main/java/de/learnlib/statistic/StatisticData.java b/api/src/main/java/de/learnlib/statistic/StatisticData.java deleted file mode 100644 index aa0f80c0c..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatisticData.java +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 de.learnlib.statistic; - -/** - * Common interface for statistical data. - */ -public interface StatisticData { - - String getName(); - - String getUnit(); - - String getSummary(); - - String getDetails(); -} - diff --git a/api/src/main/java/de/learnlib/statistic/StatisticLearner.java b/api/src/main/java/de/learnlib/statistic/StatisticLearner.java deleted file mode 100644 index d4530511b..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatisticLearner.java +++ /dev/null @@ -1,41 +0,0 @@ -/* 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 de.learnlib.statistic; - -import de.learnlib.algorithm.LearningAlgorithm; -import net.automatalib.automaton.fsa.DFA; -import net.automatalib.automaton.transducer.MealyMachine; -import net.automatalib.automaton.transducer.MooreMachine; -import net.automatalib.word.Word; - -/** - * Common interface for learners keeping statistics. - * - * @param - * the automaton type - * @param - * input symbol class - * @param - * output symbol class - */ -public interface StatisticLearner extends LearningAlgorithm, StatisticCollector { - - interface DFAStatisticLearner extends StatisticLearner, I, Boolean> {} - - interface MealyStatisticLearner extends StatisticLearner, I, Word> {} - - interface MooreStatisticLearner extends StatisticLearner, I, Word> {} -} diff --git a/api/src/main/java/de/learnlib/statistic/StatisticOracle.java b/api/src/main/java/de/learnlib/statistic/StatisticOracle.java deleted file mode 100644 index 6a9a6cab0..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatisticOracle.java +++ /dev/null @@ -1,29 +0,0 @@ -/* 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 de.learnlib.statistic; - -import de.learnlib.oracle.MembershipOracle; - -/** - * Common interface for oracles keeping statistics. - * - * @param - * input symbol class - * @param - * output domain class - */ -public interface StatisticOracle extends MembershipOracle, StatisticCollector {} diff --git a/api/src/main/java/de/learnlib/statistic/StatisticSUL.java b/api/src/main/java/de/learnlib/statistic/StatisticSUL.java deleted file mode 100644 index e44a87342..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatisticSUL.java +++ /dev/null @@ -1,20 +0,0 @@ -/* 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 de.learnlib.statistic; - -import de.learnlib.sul.SUL; - -public interface StatisticSUL extends SUL, StatisticCollector {} diff --git a/api/src/main/java/de/learnlib/statistic/StatsContainer.java b/api/src/main/java/de/learnlib/statistic/StatsContainer.java index 5660a76fa..02c8bbe50 100644 --- a/api/src/main/java/de/learnlib/statistic/StatsContainer.java +++ b/api/src/main/java/de/learnlib/statistic/StatsContainer.java @@ -122,8 +122,10 @@ default void increaseCounter(String id, @Nullable String description) { */ Optional getCount(String id); + + void clear(); /** * Prints all stored statistics. */ - void printStats(); + String printStats(); } diff --git a/archetypes/basic/src/main/resources/archetype-resources/pom.xml b/archetypes/basic/src/main/resources/archetype-resources/pom.xml index fca03662d..de5e1db1c 100644 --- a/archetypes/basic/src/main/resources/archetype-resources/pom.xml +++ b/archetypes/basic/src/main/resources/archetype-resources/pom.xml @@ -48,6 +48,10 @@ de.learnlib learnlib-lstar + + de.learnlib + learnlib-statistics + de.learnlib learnlib-util diff --git a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java index 4b108515b..ae0e6ce09 100644 --- a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java +++ b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java @@ -9,8 +9,8 @@ import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.oracle.equivalence.DFAWMethodEQOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; +import de.learnlib.statistic.Statistics; import de.learnlib.util.Experiment.DFAExperiment; -import de.learnlib.util.statistic.SimpleProfiler; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.DFA; @@ -60,12 +60,6 @@ public static void main(String[] args) throws IOException { // active learning DFAExperiment experiment = new DFAExperiment<>(lstar, wMethod, inputs); - // turn on time profiling - experiment.setProfile(true); - - // enable logging of models - experiment.setLogModels(true); - // run experiment experiment.run(); @@ -75,12 +69,8 @@ public static void main(String[] args) throws IOException { // report results System.out.println("-------------------------------------------------------"); - // profiling - SimpleProfiler.logResults(); - // learning statistics - System.out.println(experiment.getRounds().getSummary()); - System.out.println(mqOracle.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java index 4b108515b..ae0e6ce09 100644 --- a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java +++ b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java @@ -9,8 +9,8 @@ import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.oracle.equivalence.DFAWMethodEQOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; +import de.learnlib.statistic.Statistics; import de.learnlib.util.Experiment.DFAExperiment; -import de.learnlib.util.statistic.SimpleProfiler; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.DFA; @@ -60,12 +60,6 @@ public static void main(String[] args) throws IOException { // active learning DFAExperiment experiment = new DFAExperiment<>(lstar, wMethod, inputs); - // turn on time profiling - experiment.setProfile(true); - - // enable logging of models - experiment.setLogModels(true); - // run experiment experiment.run(); @@ -75,12 +69,8 @@ public static void main(String[] args) throws IOException { // report results System.out.println("-------------------------------------------------------"); - // profiling - SimpleProfiler.logResults(); - // learning statistics - System.out.println(experiment.getRounds().getSummary()); - System.out.println(mqOracle.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/commons/util/pom.xml b/commons/util/pom.xml index 5ac79e051..e25cd3d98 100644 --- a/commons/util/pom.xml +++ b/commons/util/pom.xml @@ -36,10 +36,6 @@ limitations under the License. de.learnlib learnlib-api - - de.learnlib - learnlib-statistics - @@ -66,6 +62,11 @@ limitations under the License. + + org.mockito + mockito-core + + org.testng testng diff --git a/commons/util/src/main/java/de/learnlib/util/Experiment.java b/commons/util/src/main/java/de/learnlib/util/Experiment.java index 305d4f877..649dc3613 100644 --- a/commons/util/src/main/java/de/learnlib/util/Experiment.java +++ b/commons/util/src/main/java/de/learnlib/util/Experiment.java @@ -16,11 +16,11 @@ package de.learnlib.util; import de.learnlib.algorithm.LearningAlgorithm; -import de.learnlib.filter.statistic.Counter; import de.learnlib.logging.Category; import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; -import de.learnlib.util.statistic.SimpleProfiler; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.transducer.MealyMachine; @@ -38,20 +38,20 @@ */ public class Experiment { - public static final String LEARNING_PROFILE_KEY = "Learning"; - public static final String COUNTEREXAMPLE_PROFILE_KEY = "Searching for counterexample"; + public static final String LEARNING_PROFILE_KEY = "exp-expl-dur"; + public static final String COUNTEREXAMPLE_PROFILE_KEY = "exp-ce-dur"; + public static final String LEARNING_ROUNDS_KEY = "exp-rnd"; private static final Logger LOGGER = LoggerFactory.getLogger(Experiment.class); private final ExperimentImpl impl; - private boolean logModels; - private boolean profile; - private final Counter rounds = new Counter("Learning rounds", "#"); + private final StatsContainer statistics; private @Nullable A finalHypothesis; public Experiment(LearningAlgorithm learningAlgorithm, EquivalenceOracle equivalenceAlgorithm, Alphabet inputs) { this.impl = new ExperimentImpl<>(learningAlgorithm, equivalenceAlgorithm, inputs); + this.statistics = Statistics.getContainer(); } /** @@ -87,52 +87,12 @@ public A getFinalHypothesis() { return finalHypothesis; } - private void profileStart(String taskname) { - if (profile) { - SimpleProfiler.start(taskname); - } - } - - private void profileStop(String taskname) { - if (profile) { - SimpleProfiler.stop(taskname); - } - } - - /** - * Decides whether intermediate hypothesis models should be logged. - * - * @param logModels - * flag whether models should be logged - */ - public void setLogModels(boolean logModels) { - this.logModels = logModels; - } - - /** - * Decides whether the experiment runtime should be profiled. - * - * @param profile - * flag whether learning process should be profiled - */ - public void setProfile(boolean profile) { - this.profile = profile; - } - - /** - * Returns the counter for the number of refinement rounds the experiment took. - * - * @return the rounds - */ - public Counter getRounds() { - return rounds; - } - private final class ExperimentImpl { private final LearningAlgorithm learningAlgorithm; private final EquivalenceOracle equivalenceAlgorithm; private final Alphabet inputs; + private int rounds; ExperimentImpl(LearningAlgorithm learningAlgorithm, EquivalenceOracle equivalenceAlgorithm, @@ -143,26 +103,23 @@ private final class ExperimentImpl { } public A run() { - rounds.increment(); - LOGGER.info(Category.PHASE, "Starting round {}", rounds.getCount()); + rounds++; + statistics.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); + LOGGER.info(Category.PHASE, "Starting round {}", rounds); LOGGER.info(Category.PHASE, "Learning"); - profileStart(LEARNING_PROFILE_KEY); + statistics.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); learningAlgorithm.startLearning(); - profileStop(LEARNING_PROFILE_KEY); + statistics.pauseClock(LEARNING_PROFILE_KEY); while (true) { final A hyp = learningAlgorithm.getHypothesisModel(); - if (logModels) { - LOGGER.info(Category.MODEL, hyp.toString()); - } - LOGGER.info(Category.PHASE, "Searching for counterexample"); - profileStart(COUNTEREXAMPLE_PROFILE_KEY); + statistics.startOrResumeClock(COUNTEREXAMPLE_PROFILE_KEY, "Duration of counterexample search"); DefaultQuery ce = equivalenceAlgorithm.findCounterExample(hyp, inputs); - profileStop(COUNTEREXAMPLE_PROFILE_KEY); + statistics.pauseClock(COUNTEREXAMPLE_PROFILE_KEY); if (ce == null) { return hyp; @@ -171,13 +128,14 @@ public A run() { LOGGER.info(Category.COUNTEREXAMPLE, ce.getInput().toString()); // next round ... - rounds.increment(); - LOGGER.info(Category.PHASE, "Starting round {}", rounds.getCount()); + rounds++; + statistics.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); + LOGGER.info(Category.PHASE, "Starting round {}", rounds); LOGGER.info(Category.PHASE, "Learning"); - profileStart(LEARNING_PROFILE_KEY); + statistics.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); final boolean refined = learningAlgorithm.refineHypothesis(ce); - profileStop(LEARNING_PROFILE_KEY); + statistics.pauseClock(LEARNING_PROFILE_KEY); assert refined; } diff --git a/commons/util/src/main/java/de/learnlib/util/statistic/SimpleProfiler.java b/commons/util/src/main/java/de/learnlib/util/statistic/SimpleProfiler.java deleted file mode 100644 index f45c28fb3..000000000 --- a/commons/util/src/main/java/de/learnlib/util/statistic/SimpleProfiler.java +++ /dev/null @@ -1,97 +0,0 @@ -/* 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 de.learnlib.util.statistic; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import de.learnlib.filter.statistic.Counter; -import de.learnlib.logging.Category; -import org.checkerframework.checker.nullness.qual.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Very rudimentary profiler. - */ -public final class SimpleProfiler { - - private static final Map CUMULATED = new ConcurrentHashMap<>(); - private static final Map PENDING = new ConcurrentHashMap<>(); - private static final Logger LOGGER = LoggerFactory.getLogger(SimpleProfiler.class.getName()); - private static final double MILLISECONDS_PER_SECOND = 1000.0; - - private SimpleProfiler() { - // prevent initialization - } - - /** - * Reset internal data. - */ - public static void reset() { - CUMULATED.clear(); - PENDING.clear(); - } - - /** - * Start the timer identified by the given key. - * - * @param name - * The name of the timer to be started. - */ - public static void start(String name) { - PENDING.put(name, System.currentTimeMillis()); - } - - /** - * Stop the timer identified by the given key. After stopping a timer, the time passed from its - * {@link #start(String) initialization} will be added to the cumulated time of the specific timer. - * - * @param name - * The name of the timer to be stopped. - */ - public static void stop(String name) { - Long start = PENDING.remove(name); - if (start == null) { - return; - } - long duration = System.currentTimeMillis() - start; - Counter sum = CUMULATED.computeIfAbsent(name, k -> new Counter(k, "ms")); - sum.increment(duration); - } - - /** - * Return the counter for the cumulated (passed) time of the given timer. - * - * @param name - * The name of the timer to be returned. - * - * @return The counter for tracking the passed milliseconds of the timer - */ - public static @Nullable Counter cumulated(String name) { - return CUMULATED.get(name); - } - - /** - * Log results in category PROFILING. - */ - public static void logResults() { - for (Counter c : CUMULATED.values()) { - LOGGER.info(Category.PROFILING, "{}, ({} s)", c.getSummary(), c.getCount() / MILLISECONDS_PER_SECOND); - } - } - -} diff --git a/commons/util/src/main/java/module-info.java b/commons/util/src/main/java/module-info.java index e4b6a888c..255f653e6 100644 --- a/commons/util/src/main/java/module-info.java +++ b/commons/util/src/main/java/module-info.java @@ -29,7 +29,6 @@ open module de.learnlib.common.util { requires de.learnlib.api; - requires de.learnlib.filter.statistic; requires net.automatalib.api; requires net.automatalib.core; requires net.automatalib.util; @@ -42,5 +41,4 @@ exports de.learnlib.util.mealy; exports de.learnlib.util.moore; exports de.learnlib.util.nfa; - exports de.learnlib.util.statistic; } diff --git a/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java b/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java index 80b1c1903..a3631ecec 100644 --- a/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java +++ b/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java @@ -21,8 +21,9 @@ import de.learnlib.algorithm.LearningAlgorithm.DFALearner; import de.learnlib.oracle.EquivalenceOracle.DFAEquivalenceOracle; import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.util.Experiment.DFAExperiment; -import de.learnlib.util.statistic.SimpleProfiler; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.DFA; @@ -30,6 +31,9 @@ import net.automatalib.util.automaton.random.RandomAutomata; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; +import org.mockito.ArgumentMatchers; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.testng.Assert; import org.testng.annotations.Test; @@ -48,25 +52,40 @@ public void testExperiment() { final MockUpLearner learner = new MockUpLearner<>(target, intermediateTarget); final DFAEquivalenceOracle eq = new MockUpOracle<>(intermediateTarget); - DFAExperiment experiment = new DFAExperiment<>(learner, eq, alphabet); - experiment.setProfile(true); + final StatsContainer statMock = Mockito.mock(StatsContainer.class); - Assert.assertThrows(experiment::getFinalHypothesis); + try (MockedStatic statistics = Mockito.mockStatic(Statistics.class)) { + statistics.when(Statistics::getContainer).thenReturn(statMock); - experiment.run(); + DFAExperiment experiment = new DFAExperiment<>(learner, eq, alphabet); - Assert.assertThrows(experiment::run); + Assert.assertThrows(experiment::getFinalHypothesis); - DFA finalModel = experiment.getFinalHypothesis(); + experiment.run(); - Assert.assertNotNull(experiment.getFinalHypothesis()); - Assert.assertSame(finalModel, target); + Assert.assertThrows(experiment::run); - Assert.assertTrue(learner.startLearningCalled); - Assert.assertEquals(learner.refinementSteps, REFINEMENT_STEPS); + DFA finalModel = experiment.getFinalHypothesis(); - Assert.assertNotNull(SimpleProfiler.cumulated(Experiment.LEARNING_PROFILE_KEY)); - Assert.assertNotNull(SimpleProfiler.cumulated(Experiment.COUNTEREXAMPLE_PROFILE_KEY)); + Assert.assertNotNull(experiment.getFinalHypothesis()); + Assert.assertSame(finalModel, target); + + Assert.assertTrue(learner.startLearningCalled); + Assert.assertEquals(learner.refinementSteps, REFINEMENT_STEPS); + + Mockito.verify(statMock, Mockito.atLeastOnce()) + .startOrResumeClock(ArgumentMatchers.eq(Experiment.LEARNING_PROFILE_KEY), + ArgumentMatchers.anyString()); + Mockito.verify(statMock, Mockito.atLeastOnce()) + .pauseClock(ArgumentMatchers.eq(Experiment.LEARNING_PROFILE_KEY)); + Mockito.verify(statMock, Mockito.atLeastOnce()) + .startOrResumeClock(ArgumentMatchers.eq(Experiment.COUNTEREXAMPLE_PROFILE_KEY), + ArgumentMatchers.anyString()); + Mockito.verify(statMock, Mockito.atLeastOnce()) + .pauseClock(ArgumentMatchers.eq(Experiment.COUNTEREXAMPLE_PROFILE_KEY)); + Mockito.verify(statMock, Mockito.atLeastOnce()) + .increaseCounter(ArgumentMatchers.eq(Experiment.LEARNING_ROUNDS_KEY), ArgumentMatchers.anyString()); + } } private static final class MockUpLearner implements DFALearner { diff --git a/examples/src/main/java/de/learnlib/example/Example1.java b/examples/src/main/java/de/learnlib/example/Example1.java index 4f445a05a..8b732eed9 100644 --- a/examples/src/main/java/de/learnlib/example/Example1.java +++ b/examples/src/main/java/de/learnlib/example/Example1.java @@ -26,8 +26,8 @@ import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.oracle.equivalence.DFAWMethodEQOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; +import de.learnlib.statistic.Statistics; import de.learnlib.util.Experiment.DFAExperiment; -import de.learnlib.util.statistic.SimpleProfiler; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.DFA; @@ -79,12 +79,6 @@ public static void main(String[] args) throws IOException { // active learning DFAExperiment experiment = new DFAExperiment<>(lstar, wMethod, inputs); - // turn on time profiling - experiment.setProfile(true); - - // enable logging of models - experiment.setLogModels(true); - // run experiment experiment.run(); @@ -94,12 +88,8 @@ public static void main(String[] args) throws IOException { // report results System.out.println("-------------------------------------------------------"); - // profiling - SimpleProfiler.logResults(); - // learning statistics - System.out.println(experiment.getRounds().getSummary()); - System.out.println(mqOracle.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/examples/src/main/java/de/learnlib/example/Example2.java b/examples/src/main/java/de/learnlib/example/Example2.java index fb76febdf..30be3b6eb 100644 --- a/examples/src/main/java/de/learnlib/example/Example2.java +++ b/examples/src/main/java/de/learnlib/example/Example2.java @@ -33,10 +33,9 @@ import de.learnlib.oracle.EquivalenceOracle.MealyEquivalenceOracle; import de.learnlib.oracle.equivalence.mealy.RandomWalkEQOracle; import de.learnlib.oracle.membership.SULOracle; -import de.learnlib.statistic.StatisticSUL; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import de.learnlib.util.Experiment.MealyExperiment; -import de.learnlib.util.statistic.SimpleProfiler; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.serialization.dot.GraphDOT; import net.automatalib.visualization.Visualization; @@ -75,9 +74,8 @@ public static void main(String[] args) throws NoSuchMethodException, IOException MethodInput poll = driver.addInput("poll", mPoll); // oracle for counting queries wraps sul - StatisticSUL statisticSul = new CounterSUL<>(driver); + SUL effectiveSul = new CounterSUL<>(driver); - SUL effectiveSul = statisticSul; // use caching in order to avoid duplicate queries effectiveSul = SULCaches.createCache(driver.getInputs(), effectiveSul); @@ -114,12 +112,6 @@ public static void main(String[] args) throws NoSuchMethodException, IOException MealyExperiment experiment = new MealyExperiment<>(lstar, randomWalks, driver.getInputs()); - // turn on time profiling - experiment.setProfile(true); - - // enable logging of models - experiment.setLogModels(true); - // run experiment experiment.run(); @@ -129,12 +121,8 @@ public static void main(String[] args) throws NoSuchMethodException, IOException // report results System.out.println("-------------------------------------------------------"); - // profiling - SimpleProfiler.logResults(); - // learning statistics - System.out.println(experiment.getRounds().getSummary()); - System.out.println(statisticSul.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java b/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java index 86ded3983..79498d53e 100644 --- a/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java +++ b/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java @@ -30,6 +30,7 @@ import de.learnlib.oracle.ParallelOracle; import de.learnlib.oracle.parallelism.ParallelOracleBuilders; import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; @@ -110,9 +111,9 @@ private void runSingleCache() { final MealyMembershipOracle cache = MealyCaches.createCache(alphabet, parallelOracle); // print results - System.out.println("Single-threaded cache performance: "); + System.out.println("Single-threaded cache performance:"); answerQueries(cache); - System.out.println(" " + counter.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); parallelOracle.shutdownNow(); } @@ -142,9 +143,9 @@ private void runThreadSafeCache() { .create(); // print results - System.out.println("Shared cache performance: "); + System.out.println("Shared cache performance:"); answerQueries(parallelOracle); - System.out.println(" " + counter.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); parallelOracle.shutdownNow(); } diff --git a/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java b/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java index 767936737..9cddd763b 100644 --- a/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java +++ b/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java @@ -27,6 +27,7 @@ import de.learnlib.oracle.equivalence.DFASimulatorEQOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; @@ -121,7 +122,7 @@ private static T fromBytes(byte[] bytes) { private static void printStats(Setup setup) { System.out.println("Hypothesis size: " + setup.learner.getHypothesisModel().size()); - System.out.println(setup.counter.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); System.out.println(); } diff --git a/examples/src/main/java/de/learnlib/example/sli/Example2.java b/examples/src/main/java/de/learnlib/example/sli/Example2.java index 18fa718fd..957903a0d 100644 --- a/examples/src/main/java/de/learnlib/example/sli/Example2.java +++ b/examples/src/main/java/de/learnlib/example/sli/Example2.java @@ -34,6 +34,7 @@ import de.learnlib.oracle.equivalence.mealy.StateLocalInputMealySimulatorEQOracle; import de.learnlib.oracle.membership.SULOracle; import de.learnlib.oracle.membership.StateLocalInputSULOracle; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import de.learnlib.sul.StateLocalInputSUL; import de.learnlib.testsupport.example.mealy.ExampleRandomStateLocalInputMealy; @@ -82,6 +83,8 @@ public static void main(String[] args) { */ static void runSLILearner(boolean withCache) { + Statistics.getContainer().clear(); + // setup SULs and counters final StateLocalInputSUL target = new StateLocalInputMealySimulatorSUL<>(TARGET); final CounterStateLocalInputSUL counterSUL = new CounterStateLocalInputSUL<>(target); @@ -127,7 +130,7 @@ static void runSLILearner(boolean withCache) { System.out.println("State Local Input SUL" + (withCache ? ", with cache" : "")); System.out.println("-------------------------------------------------------"); - System.out.println(counterSUL.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); System.out.println("-------------------------------------------------------"); } @@ -137,6 +140,8 @@ static void runSLILearner(boolean withCache) { */ static void runNormalLearner(boolean withCache) { + Statistics.getContainer().clear(); + // setup SULs and counters final SUL target = new MealySimulatorSUL<>(TARGET, UNDEFINED); final CounterSUL counterSUL = new CounterSUL<>(target); @@ -181,7 +186,7 @@ static void runNormalLearner(boolean withCache) { System.out.println("Regular SUL" + (withCache ? ", with cache" : "")); System.out.println("-------------------------------------------------------"); - System.out.println(counterSUL.getStatisticalData().getSummary()); + System.out.println(Statistics.getContainer().printStats()); System.out.println("-------------------------------------------------------"); } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java index 4f26db5f6..c643149b4 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java @@ -24,6 +24,7 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.query.Query; +import de.learnlib.statistic.Statistics; import de.learnlib.testsupport.ResumeUtils; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.SupportsGrowingAlphabet; @@ -50,6 +51,7 @@ public void setup() { alphabet = getAlphabet(); oracle = getCachedOracle(); queries = new ArrayList<>(); + Statistics.getContainer().clear(); } @Test diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java index 9b1fb889d..95863699f 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java @@ -20,6 +20,7 @@ import de.learnlib.filter.statistic.oracle.DFACounterOracle; import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.fsa.DFA; @@ -57,7 +58,7 @@ protected DFACacheOracle getResumedOracle(DFACacheOracle o @Override protected long getNumberOfPosedQueries() { - return counter.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(DFACounterOracle.SYMBOL_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java index fb6c1735e..e783f24fd 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java @@ -19,6 +19,7 @@ import de.learnlib.filter.cache.CacheTestUtils; import de.learnlib.filter.statistic.oracle.DFACounterOracle; import de.learnlib.oracle.membership.DFASimulatorOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.fsa.DFA; @@ -55,7 +56,7 @@ protected DFAHashCacheOracle getResumedOracle(DFAHashCacheOracle, Character, Boolean> { - private final DFACounterOracle sul; private final ThreadSafeDFACacheOracle cacheRepresentative; private final ParallelOracle parallelOracle; @Factory(dataProvider = "caches") public DFAParallelCacheTest(DFACacheCreator> creator) { - this.sul = CacheTestUtils.getCounter(CacheTestUtils.DFA); + DFACounterOracle sul = CacheTestUtils.getCounter(CacheTestUtils.DFA); final CacheConfig> config = - creator.apply(CacheTestUtils.INPUT_ALPHABET, this.sul); + creator.apply(CacheTestUtils.INPUT_ALPHABET, sul); this.cacheRepresentative = config.getRepresentative(); this.parallelOracle = config.getParallelOracle(); @@ -75,6 +75,6 @@ protected ParallelOracle getParallelOracle() { @Override protected long getNumberOfQueries() { - return this.sul.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(DFACounterOracle.SYMBOL_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java index f51b681f3..ac7c36733 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java @@ -20,6 +20,7 @@ import de.learnlib.filter.statistic.oracle.MealyCounterOracle; import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle; import de.learnlib.oracle.membership.MealySimulatorOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.transducer.MealyMachine; @@ -62,7 +63,7 @@ protected MealyCacheOracle getResumedOracle(MealyCacheOracle @Override protected long getNumberOfPosedQueries() { - return counter.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java index 1ccf91b62..4f76ab0b4 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java @@ -29,6 +29,7 @@ import de.learnlib.oracle.membership.SULAdaptiveOracle; import de.learnlib.query.AdaptiveQuery; import de.learnlib.query.Query; +import de.learnlib.statistic.Statistics; import de.learnlib.util.mealy.PresetAdaptiveQuery; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.SupportsGrowingAlphabet; @@ -83,7 +84,7 @@ protected Wrapper getResumedOracle(Wrapper, Character, Word> { - private final MealyCounterOracle sul; private final ThreadSafeMealyCacheOracle cacheRepresentative; private final ParallelOracle> parallelOracle; @Factory(dataProvider = "caches") public MealyParallelCacheTest(MealyCacheCreator> creator) { - this.sul = CacheTestUtils.getCounter(CacheTestUtils.MEALY); + MealyCounterOracle sul = CacheTestUtils.getCounter(CacheTestUtils.MEALY); final CacheConfig, ThreadSafeMealyCacheOracle> config = - creator.apply(CacheTestUtils.INPUT_ALPHABET, this.sul); + creator.apply(CacheTestUtils.INPUT_ALPHABET, sul); this.cacheRepresentative = config.getRepresentative(); this.parallelOracle = config.getParallelOracle(); @@ -82,6 +82,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return this.sul.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java index 007011aa3..f533099af 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java @@ -20,6 +20,7 @@ import de.learnlib.filter.statistic.oracle.MooreCounterOracle; import de.learnlib.oracle.MembershipOracle.MooreMembershipOracle; import de.learnlib.oracle.membership.MooreSimulatorOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.transducer.MooreMachine; @@ -62,7 +63,7 @@ protected MooreCacheOracle getResumedOracle(MooreCacheOracle @Override protected long getNumberOfPosedQueries() { - return counter.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java index 369b1c51c..bad56f164 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java @@ -21,6 +21,7 @@ import de.learnlib.filter.cache.CacheTestUtils; import de.learnlib.filter.statistic.oracle.MooreCounterOracle; import de.learnlib.oracle.ParallelOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.word.Word; @@ -30,16 +31,15 @@ public class MooreParallelCacheTest extends AbstractParallelCacheTest, Character, Word> { - private final MooreCounterOracle sul; private final ThreadSafeMooreCacheOracle cacheRepresentative; private final ParallelOracle> parallelOracle; @Factory(dataProvider = "caches") public MooreParallelCacheTest(MooreCacheCreator> creator) { - this.sul = CacheTestUtils.getCounter(CacheTestUtils.MOORE); + MooreCounterOracle sul = CacheTestUtils.getCounter(CacheTestUtils.MOORE); final CacheConfig, ThreadSafeMooreCacheOracle> config = - creator.apply(CacheTestUtils.INPUT_ALPHABET, this.sul); + creator.apply(CacheTestUtils.INPUT_ALPHABET, sul); this.cacheRepresentative = config.getRepresentative(); this.parallelOracle = config.getParallelOracle(); @@ -75,6 +75,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return this.sul.getQueryCounter().getCount(); + return Statistics.getContainer().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java index cb7360921..0a8b9414f 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java @@ -20,6 +20,7 @@ import de.learnlib.filter.cache.CacheTestUtils; import de.learnlib.filter.cache.SULLearningCacheOracle; import de.learnlib.filter.statistic.sul.CounterSUL; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; @@ -60,7 +61,7 @@ protected SULLearningCacheOracle, Character, Word> { - private final CounterStateLocalInputSUL sul; private final ThreadSafeStateLocalInputSULCache cacheRepresentative; private final ParallelOracle> parallelOracle; @Factory(dataProvider = "caches") public SLISULParallelCacheTest(SLISULCacheCreator> creator) { - this.sul = CacheTestUtils.getCounter(CacheTestUtils.SLI_SUL); + CounterStateLocalInputSUL sul = CacheTestUtils.getCounter(CacheTestUtils.SLI_SUL); final CacheConfig, ThreadSafeStateLocalInputSULCache> config = creator.apply(CacheTestUtils.INPUT_ALPHABET, sul); @@ -73,6 +74,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return this.sul.getResetCounter().getCount(); + return Statistics.getContainer().getCount(CounterSUL.RESET_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java index e3f0bec38..0094147ff 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java @@ -21,6 +21,7 @@ import de.learnlib.filter.cache.CacheTestUtils; import de.learnlib.filter.statistic.sul.CounterSUL; import de.learnlib.oracle.ParallelOracle; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.word.Word; @@ -30,13 +31,12 @@ public class SULParallelCacheTest extends AbstractParallelCacheTest, Character, Word> { - private final CounterSUL sul; private final ThreadSafeSULCache cacheRepresentative; private final ParallelOracle> parallelOracle; @Factory(dataProvider = "caches") public SULParallelCacheTest(SULCacheCreator> creator) { - this.sul = CacheTestUtils.getCounter(CacheTestUtils.SUL); + CounterSUL sul = CacheTestUtils.getCounter(CacheTestUtils.SUL); final CacheConfig, ThreadSafeSULCache> config = creator.apply(CacheTestUtils.INPUT_ALPHABET, sul); @@ -74,6 +74,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return this.sul.getResetCounter().getCount(); + return Statistics.getContainer().getCount(CounterSUL.RESET_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java index 10062f567..87d6d218e 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java @@ -20,6 +20,7 @@ import de.learnlib.filter.cache.CacheTestUtils; import de.learnlib.filter.cache.SULLearningCacheOracle; import de.learnlib.filter.statistic.sul.CounterStateLocalInputSUL; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.word.Word; @@ -41,42 +42,47 @@ public StateLocalInputSULTreeCacheTest() { @Override public void testNoQueriesReceived() { super.testNoQueriesReceived(); - Assert.assertEquals(counter.getInputCounter().getCount(), 0); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), 0); } @Test(dependsOnMethods = "testNoQueriesReceived") @Override public void testFirstQuery() { super.testFirstQuery(); - Assert.assertEquals(counter.getInputCounter().getCount(), oracle.getCache().size()); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + oracle.getCache().size()); } @Test(dependsOnMethods = "testFirstQuery") @Override public void testFirstDuplicate() { super.testFirstDuplicate(); - Assert.assertEquals(counter.getInputCounter().getCount(), oracle.getCache().size()); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + oracle.getCache().size()); } @Test(dependsOnMethods = "testFirstDuplicate") @Override public void testTwoQueriesOneDuplicate() { super.testTwoQueriesOneDuplicate(); - Assert.assertEquals(counter.getInputCounter().getCount(), oracle.getCache().size()); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + oracle.getCache().size()); } @Test(dependsOnMethods = "testTwoQueriesOneDuplicate") @Override public void testOneNewQuery() { super.testOneNewQuery(); - Assert.assertEquals(counter.getInputCounter().getCount(), oracle.getCache().size()); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + oracle.getCache().size()); } @Test(dependsOnMethods = "testOneNewQuery") @Override public void testPrefix() { super.testPrefix(); - Assert.assertEquals(counter.getInputCounter().getCount(), oracle.getCache().size()); + Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + oracle.getCache().size()); } @Test(dependsOnMethods = "testPrefix") @@ -142,7 +148,7 @@ protected SULLearningCacheOracle. - * - * 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.filter.statistic; - -import de.learnlib.statistic.StatisticData; - -/** - * Common interface for statistical data. - */ -public abstract class AbstractStatisticData implements StatisticData { - - private final String name; - private final String unit; - - protected AbstractStatisticData(String name, String unit) { - this.name = name; - this.unit = unit; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getUnit() { - return unit; - } - -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/Counter.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/Counter.java deleted file mode 100644 index eae4a3f68..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/Counter.java +++ /dev/null @@ -1,54 +0,0 @@ -/* 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 de.learnlib.filter.statistic; - -import java.util.concurrent.atomic.AtomicLong; - -/** - * A simple counter. - */ -public class Counter extends AbstractStatisticData { - - private final AtomicLong count; - - public Counter(String name, String unit) { - super(name, unit); - this.count = new AtomicLong(0L); - } - - public void increment(long inc) { - count.addAndGet(inc); - } - - public void increment() { - count.incrementAndGet(); - } - - public long getCount() { - return count.get(); - } - - @Override - public String getSummary() { - return getName() + " [" + getUnit() + "]: " + count; - } - - @Override - public String getDetails() { - return getSummary(); - } - -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/CounterCollection.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/CounterCollection.java deleted file mode 100644 index b475a3dfc..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/CounterCollection.java +++ /dev/null @@ -1,63 +0,0 @@ -/* 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 de.learnlib.filter.statistic; - -import java.util.StringJoiner; -import java.util.function.Function; - -import de.learnlib.statistic.StatisticData; - -/** - * A collection of counters. - */ -public class CounterCollection implements StatisticData { - - private final Counter[] counters; - - public CounterCollection(Counter... counters) { - this.counters = counters; - } - - @Override - public String getName() { - return collect(Counter::getName); - } - - @Override - public String getUnit() { - return collect(Counter::getUnit); - } - - @Override - public String getSummary() { - return collect(Counter::getSummary); - } - - @Override - public String getDetails() { - return collect(Counter::getDetails); - } - - private String collect(Function extractor) { - final StringJoiner sj = new StringJoiner("\n"); - - for (Counter c : counters) { - sj.add(extractor.apply(c)); - } - - return sj.toString(); - } -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/HistogramDataSet.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/HistogramDataSet.java deleted file mode 100644 index 159f132c4..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/HistogramDataSet.java +++ /dev/null @@ -1,99 +0,0 @@ -/* 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 de.learnlib.filter.statistic; - -import java.util.Map.Entry; -import java.util.SortedMap; -import java.util.TreeMap; - -/** - * A simple histogram data set. - */ -public class HistogramDataSet extends AbstractStatisticData { - - private final SortedMap histogram = new TreeMap<>(); - - private long size; - - private long sum; - - private double mean; - - public HistogramDataSet(String name, String unit) { - super(name, unit); - } - - public void addDataPoint(Long value) { - Integer i = histogram.get(value); - if (i == null) { - i = 0; - } - histogram.put(value, i + 1); - sum += value; - size++; - mean = mean + ((value - mean) / size); - } - - public SortedMap getHistogram() { - return histogram; - } - - public double getMean() { - return mean; - } - - public long getSize() { - return size; - } - - public long getSum() { - return sum; - } - - public double getMedian() { - long idx = 0; - for (Entry e : histogram.entrySet()) { - int count = e.getValue(); - idx += count; - if (idx >= size / 2) { - return e.getKey(); - } - } - return 0.0; - } - - @Override - public String getSummary() { - return getName() + " [" + getUnit() + "]: " + size + " (count), " + sum + " (sum), " + mean + " (mean), " + - getMedian() + " (median)"; - } - - @Override - public String getDetails() { - StringBuilder sb = new StringBuilder(); - sb.append(getSummary()).append(System.lineSeparator()); - for (Entry e : histogram.entrySet()) { - sb.append('\t') - .append(e.getKey()) - .append(", ") - .append(e.getValue()) - .append(System.lineSeparator()); - } - return sb.toString(); - } - -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java index ecb72edd9..0d69dd135 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java @@ -10,6 +10,7 @@ * A {@link StatsContainer} that stores all statistics in a {@link Map}. */ public class MapStatsContainer implements StatsContainer { + private final Map statistics = new HashMap<>(); // id -> stat @Override @@ -95,6 +96,11 @@ public Optional getCount(String id) { return Optional.empty(); } + @Override + public void clear() { + statistics.clear(); + } + // ================== public String toJson() { @@ -120,7 +126,6 @@ public String toJson() { return "{" + String.join(",\n", lines) + "}"; } - public String toYaml() { List sortedStats = statistics.values().stream().sorted(Comparator.comparing(LearnerStatistic::getDescription)).toList(); @@ -148,12 +153,13 @@ public String toYaml() { return String.join("\n", lines); } - - public void printStats() { - // Print results: - System.out.println("============================================"); - System.out.println("Statistics:"); - System.out.println(this.toYaml()); - System.out.println("============================================"); + public String printStats() { + final String pattern = """ + ============================================ + Statistics: + %s + ============================================ + """; + return String.format(pattern, toYaml()); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java index ec64d4595..02c999698 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java @@ -19,15 +19,11 @@ import de.learnlib.algorithm.LearningAlgorithm.DFALearner; import de.learnlib.algorithm.LearningAlgorithm.MealyLearner; import de.learnlib.algorithm.LearningAlgorithm.MooreLearner; -import de.learnlib.filter.statistic.Counter; import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.StatisticLearner; -import de.learnlib.statistic.StatisticLearner.DFAStatisticLearner; -import de.learnlib.statistic.StatisticLearner.MealyStatisticLearner; -import de.learnlib.statistic.StatisticLearner.MooreStatisticLearner; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.tooling.annotation.refinement.GenerateRefinement; import de.learnlib.tooling.annotation.refinement.Generic; -import de.learnlib.tooling.annotation.refinement.Interface; import de.learnlib.tooling.annotation.refinement.Mapping; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.transducer.MealyMachine; @@ -37,8 +33,6 @@ /** * Counts the number of hypothesis refinements. *

          - * The value of the {@link Counter} returned by {@link #getStatisticalData()} returns the same value as - * Experiment.getRounds(). * * @param * automaton type @@ -54,8 +48,7 @@ @Generic(clazz = Boolean.class)}, typeMappings = @Mapping(from = LearningAlgorithm.class, to = DFALearner.class, - generics = @Generic("I")), - interfaces = @Interface(clazz = DFAStatisticLearner.class, generics = @Generic("I"))) + generics = @Generic("I"))) @GenerateRefinement(name = "MealyRefinementCounterLearner", generics = {@Generic(value = "I", desc = "input symbol type"), @Generic(value = "O", desc = "output symbol type")}, @@ -64,8 +57,6 @@ @Generic(clazz = Word.class, generics = "O")}, typeMappings = @Mapping(from = LearningAlgorithm.class, to = MealyLearner.class, - generics = {@Generic("I"), @Generic("O")}), - interfaces = @Interface(clazz = MealyStatisticLearner.class, generics = {@Generic("I"), @Generic("O")})) @GenerateRefinement(name = "MooreRefinementCounterLearner", generics = {@Generic(value = "I", desc = "input symbol type"), @@ -75,18 +66,22 @@ @Generic(clazz = Word.class, generics = "O")}, typeMappings = @Mapping(from = LearningAlgorithm.class, to = MooreLearner.class, - generics = {@Generic("I"), @Generic("O")}), - interfaces = @Interface(clazz = MooreStatisticLearner.class, generics = {@Generic("I"), @Generic("O")})) -public class RefinementCounterLearner implements StatisticLearner { +public class RefinementCounterLearner implements LearningAlgorithm { private final LearningAlgorithm learningAlgorithm; - private final Counter counter; + private final StatsContainer statistics; + private final String prefix; public RefinementCounterLearner(LearningAlgorithm learningAlgorithm) { + this(learningAlgorithm, ""); + } + + public RefinementCounterLearner(LearningAlgorithm learningAlgorithm, String prefix) { this.learningAlgorithm = learningAlgorithm; - this.counter = new Counter("Refinements", "#"); + this.prefix = prefix; + this.statistics = Statistics.getContainer(); } @Override @@ -98,7 +93,7 @@ public void startLearning() { public boolean refineHypothesis(DefaultQuery ceQuery) { final boolean refined = learningAlgorithm.refineHypothesis(ceQuery); if (refined) { - counter.increment(); + statistics.increaseCounter(prefix + "-ref-cnt", "Number of refinements"); } return refined; } @@ -107,9 +102,4 @@ public boolean refineHypothesis(DefaultQuery ceQuery) { public M getHypothesisModel() { return learningAlgorithm.getHypothesisModel(); } - - @Override - public Counter getStatisticalData() { - return counter; - } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java index f6e04cd07..1451f62a8 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java @@ -19,13 +19,11 @@ import java.util.Collection; import java.util.List; -import de.learnlib.filter.statistic.Counter; -import de.learnlib.filter.statistic.CounterCollection; import de.learnlib.oracle.AdaptiveMembershipOracle; import de.learnlib.query.AdaptiveQuery; import de.learnlib.query.AdaptiveQuery.Response; -import de.learnlib.statistic.StatisticCollector; -import de.learnlib.statistic.StatisticData; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; /** * A simple wrapper for counting the number of {@link Response#RESET resets} and {@link Response#SYMBOL symbols} of an @@ -36,45 +34,48 @@ * @param * output symbol type */ -public class CounterAdaptiveQueryOracle implements AdaptiveMembershipOracle, StatisticCollector { +public class CounterAdaptiveQueryOracle implements AdaptiveMembershipOracle { + + public static final String DUR_KEY = "-qry-dur"; + public static final String RESET_KEY = "-reset-cnt"; + public static final String SYMBOL_KEY = "-sym-cnt"; private final AdaptiveMembershipOracle delegate; - private final Counter resetCounter; - private final Counter symbolCounter; + private final StatsContainer statistics; + private final String prefix; public CounterAdaptiveQueryOracle(AdaptiveMembershipOracle delegate) { - this.delegate = delegate; - this.resetCounter = new Counter("Resets", "#"); - this.symbolCounter = new Counter("Symbols", "#"); + this(delegate, ""); } - public Counter getResetCounter() { - return resetCounter; - } - - public Counter getSymbolCounter() { - return symbolCounter; + public CounterAdaptiveQueryOracle(AdaptiveMembershipOracle delegate, String prefix) { + this.delegate = delegate; + this.prefix = prefix; + this.statistics = Statistics.getContainer(); } @Override public void processQueries(Collection> queries) { - final List wrappers = new ArrayList<>(queries.size()); + final List> wrappers = new ArrayList<>(queries.size()); for (AdaptiveQuery q : queries) { - wrappers.add(new CountingQuery(q)); + wrappers.add(new CountingQuery<>(q)); } + statistics.startOrResumeClock(prefix + DUR_KEY, "Duration of queries"); this.delegate.processQueries(wrappers); + statistics.pauseClock(prefix + DUR_KEY); + // statContainer is not thread-safe so we need to count in post-processing + for (CountingQuery wrapper : wrappers) { + this.statistics.increaseCounter(prefix + RESET_KEY, "Number of resets", wrapper.resets); + this.statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of symbols", wrapper.symbols); + } } - @Override - public StatisticData getStatisticalData() { - return new CounterCollection(this.resetCounter, this.symbolCounter); - } - - private class CountingQuery implements AdaptiveQuery { + private static class CountingQuery implements AdaptiveQuery { private final AdaptiveQuery delegate; + private int symbols, resets; CountingQuery(AdaptiveQuery delegate) { this.delegate = delegate; @@ -87,12 +88,12 @@ public I getInput() { @Override public Response processOutput(O out) { - symbolCounter.increment(); + symbols++; final Response response = delegate.processOutput(out); if (response != Response.SYMBOL) { - resetCounter.increment(); + resets++; } return response; diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java index 130a9f2cb..3ece23107 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java @@ -17,15 +17,13 @@ import java.util.Collection; -import de.learnlib.filter.statistic.Counter; -import de.learnlib.filter.statistic.CounterCollection; import de.learnlib.oracle.MembershipOracle; import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle; import de.learnlib.oracle.MembershipOracle.MooreMembershipOracle; import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticData; -import de.learnlib.statistic.StatisticOracle; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.tooling.annotation.refinement.GenerateRefinement; import de.learnlib.tooling.annotation.refinement.Generic; import de.learnlib.tooling.annotation.refinement.Interface; @@ -66,47 +64,34 @@ generics = {@Generic("I"), @Generic("O")}), interfaces = @Interface(clazz = MooreMembershipOracle.class, generics = {@Generic("I"), @Generic("O")})) -public class CounterOracle implements StatisticOracle { +public class CounterOracle implements MembershipOracle { + + public static final String DUR_KEY = "-qry-dur"; + public static final String QUERY_KEY = "-qry-cnt"; + public static final String SYMBOL_KEY = "-sym-cnt"; private final MembershipOracle delegate; - private final Counter queryCounter; - private final Counter symbolCounter; + private final StatsContainer statistics; + private final String prefix; public CounterOracle(MembershipOracle delegate) { + this(delegate, ""); + } + + public CounterOracle(MembershipOracle delegate, String prefix) { this.delegate = delegate; - this.queryCounter = new Counter("Queries", "#"); - this.symbolCounter = new Counter("Symbols", "#"); + this.prefix = prefix; + this.statistics = Statistics.getContainer(); } @Override public void processQueries(Collection> queries) { - queryCounter.increment(queries.size()); + statistics.increaseCounter(prefix + QUERY_KEY, "Number of queries", queries.size()); for (Query qry : queries) { - symbolCounter.increment(qry.getPrefix().length() + qry.getSuffix().length()); + statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of symbols", qry.getPrefix().length() + qry.getSuffix().length()); } + statistics.startOrResumeClock(prefix + DUR_KEY, "Duration of queries"); delegate.processQueries(queries); - } - - /** - * Retrieves {@link Counter} for the number of queries posed to this oracle. - * - * @return the counter of queries - */ - public Counter getQueryCounter() { - return queryCounter; - } - - /** - * Retrieves the {@link Counter} for the number of symbols in all queries posed to this oracle. - * - * @return the counter of symbols - */ - public Counter getSymbolCounter() { - return symbolCounter; - } - - @Override - public StatisticData getStatisticalData() { - return new CounterCollection(queryCounter, symbolCounter); + statistics.pauseClock(prefix + DUR_KEY); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/HistogramOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/HistogramOracle.java deleted file mode 100644 index 5cf1ee1c7..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/HistogramOracle.java +++ /dev/null @@ -1,103 +0,0 @@ -/* 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 de.learnlib.filter.statistic.oracle; - -import java.util.Collection; - -import de.learnlib.filter.statistic.HistogramDataSet; -import de.learnlib.oracle.MembershipOracle; -import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle; -import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle; -import de.learnlib.oracle.MembershipOracle.MooreMembershipOracle; -import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticOracle; -import de.learnlib.tooling.annotation.refinement.GenerateRefinement; -import de.learnlib.tooling.annotation.refinement.Generic; -import de.learnlib.tooling.annotation.refinement.Interface; -import de.learnlib.tooling.annotation.refinement.Mapping; -import net.automatalib.word.Word; - -/** - * Collects a histogram of passed query lengths. - * - * @param - * input symbol type - * @param - * output symbol type - */ -@GenerateRefinement(name = "DFAHistogramOracle", - generics = @Generic(value = "I", desc = "input symbol type"), - parentGenerics = {@Generic("I"), @Generic(clazz = Boolean.class)}, - typeMappings = @Mapping(from = MembershipOracle.class, - to = DFAMembershipOracle.class, - generics = @Generic("I")), - interfaces = @Interface(clazz = DFAMembershipOracle.class, generics = @Generic("I"))) -@GenerateRefinement(name = "MealyHistogramOracle", - generics = {@Generic(value = "I", desc = "input symbol type"), - @Generic(value = "O", desc = "output symbol type")}, - parentGenerics = {@Generic("I"), @Generic(clazz = Word.class, generics = "O")}, - typeMappings = @Mapping(from = MembershipOracle.class, - to = MealyMembershipOracle.class, - generics = {@Generic("I"), @Generic("O")}), - interfaces = @Interface(clazz = MealyMembershipOracle.class, - generics = {@Generic("I"), @Generic("O")})) -@GenerateRefinement(name = "MooreHistogramOracle", - generics = {@Generic(value = "I", desc = "input symbol type"), - @Generic(value = "O", desc = "output symbol type")}, - parentGenerics = {@Generic("I"), @Generic(clazz = Word.class, generics = "O")}, - typeMappings = @Mapping(from = MembershipOracle.class, - to = MooreMembershipOracle.class, - generics = {@Generic("I"), @Generic("O")}), - interfaces = @Interface(clazz = MooreMembershipOracle.class, - generics = {@Generic("I"), @Generic("O")})) -public class HistogramOracle implements StatisticOracle { - - /** - * dataset to be collected. - */ - private final HistogramDataSet dataSet; - - /** - * oracle used to answer queries. - */ - private final MembershipOracle delegate; - - /** - * Default constructor. - * - * @param next - * real oracle - * @param name - * name of the collected data set - */ - public HistogramOracle(MembershipOracle next, String name) { - this.delegate = next; - this.dataSet = new HistogramDataSet(name, "query length"); - } - - @Override - public final void processQueries(Collection> queries) { - for (Query q : queries) { - this.dataSet.addDataPoint((long) q.getPrefix().size() + q.getSuffix().size()); - } - this.delegate.processQueries(queries); - } - - @Override - public final HistogramDataSet getStatisticalData() { - return this.dataSet; - } -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java index ce1dfc3b2..6cab19983 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java @@ -15,7 +15,6 @@ */ package de.learnlib.filter.statistic.sul; -import de.learnlib.filter.statistic.Counter; import de.learnlib.sul.ObservableSUL; public class CounterObservableSUL extends CounterSUL implements ObservableSUL { @@ -23,18 +22,17 @@ public class CounterObservableSUL extends CounterSUL implements O private final ObservableSUL sul; public CounterObservableSUL(ObservableSUL sul) { - super(sul); - this.sul = sul; + this(sul, ""); } - private CounterObservableSUL(ObservableSUL sul, Counter resetCounter, Counter symbolCounter) { - super(sul, resetCounter, symbolCounter); + private CounterObservableSUL(ObservableSUL sul, String prefix) { + super(sul, prefix); this.sul = sul; } @Override public ObservableSUL fork() { - return new CounterObservableSUL<>(this.sul.fork(), super.resetCounter, super.symbolCounter); + return new CounterObservableSUL<>(this.sul.fork(), super.prefix); } @Override diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java index 85b1e8c59..22a07b437 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java @@ -15,31 +15,32 @@ */ package de.learnlib.filter.statistic.sul; -import de.learnlib.filter.statistic.Counter; -import de.learnlib.filter.statistic.CounterCollection; -import de.learnlib.statistic.StatisticData; -import de.learnlib.statistic.StatisticSUL; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.sul.SUL; -public class CounterSUL implements StatisticSUL { +public class CounterSUL implements SUL { + + public static final String RESET_KEY = "-sul-reset-cnt"; + public static final String SYMBOL_KEY = "-sul-step-cnt"; private final SUL sul; - protected final Counter resetCounter; - protected final Counter symbolCounter; + protected final StatsContainer statistics; + protected final String prefix; public CounterSUL(SUL sul) { - this(sul, new Counter("Resets", "#"), new Counter("Symbols", "#")); + this(sul, ""); } - protected CounterSUL(SUL sul, Counter resetCounter, Counter symbolCounter) { + public CounterSUL(SUL sul, String prefix) { this.sul = sul; - this.resetCounter = resetCounter; - this.symbolCounter = symbolCounter; + this.prefix = prefix; + this.statistics = Statistics.getContainer(); } @Override public void pre() { - this.resetCounter.increment(); + this.statistics.increaseCounter(prefix + RESET_KEY, "Number of SUL resets"); this.sul.pre(); } @@ -50,7 +51,7 @@ public void post() { @Override public O step(I in) { - this.symbolCounter.increment(); + this.statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of SUL steps"); return sul.step(in); } @@ -61,19 +62,6 @@ public boolean canFork() { @Override public SUL fork() { - return new CounterSUL<>(this.sul.fork(), this.resetCounter, this.symbolCounter); - } - - @Override - public StatisticData getStatisticalData() { - return new CounterCollection(this.resetCounter, this.symbolCounter); - } - - public Counter getResetCounter() { - return this.resetCounter; - } - - public Counter getSymbolCounter() { - return this.symbolCounter; + return new CounterSUL<>(this.sul.fork(), this.prefix); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java index 976f2233b..d0654e9c0 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java @@ -17,51 +17,32 @@ import java.util.Collection; -import de.learnlib.filter.statistic.Counter; -import de.learnlib.filter.statistic.CounterCollection; -import de.learnlib.statistic.StatisticData; import de.learnlib.sul.StateLocalInputSUL; public class CounterStateLocalInputSUL extends CounterSUL implements StateLocalInputSUL { + public static final String INPUT_KEY = "-sul-inp-cnt"; + private final StateLocalInputSUL sul; - private final Counter inputCounter; public CounterStateLocalInputSUL(StateLocalInputSUL sul) { - super(sul); - this.sul = sul; - this.inputCounter = new Counter("Input Checks", "#"); + this(sul, ""); } - private CounterStateLocalInputSUL(StateLocalInputSUL sul, - Counter resetCounter, - Counter symbolCounter, - Counter inputCounter) { - super(sul, resetCounter, symbolCounter); + private CounterStateLocalInputSUL(StateLocalInputSUL sul, String prefix) { + super(sul, prefix); this.sul = sul; - this.inputCounter = inputCounter; } @Override public Collection currentlyEnabledInputs() { - this.inputCounter.increment(); + super.statistics.increaseCounter(prefix + INPUT_KEY, "Number of enabled input checks"); return this.sul.currentlyEnabledInputs(); } @Override public StateLocalInputSUL fork() { - return new CounterStateLocalInputSUL<>(this.sul.fork(), - super.resetCounter, - super.symbolCounter, - this.inputCounter); - } - - @Override - public StatisticData getStatisticalData() { - return new CounterCollection(super.resetCounter, super.symbolCounter, this.inputCounter); + return new CounterStateLocalInputSUL<>(this.sul.fork(), super.prefix); } - public Counter getInputCounter() { - return this.inputCounter; - } } diff --git a/filters/statistics/src/main/java/module-info.java b/filters/statistics/src/main/java/module-info.java index f627ed4e4..f75a1201a 100644 --- a/filters/statistics/src/main/java/module-info.java +++ b/filters/statistics/src/main/java/module-info.java @@ -39,7 +39,6 @@ requires static de.learnlib.tooling.annotation; requires static org.checkerframework.checker.qual; - exports de.learnlib.filter.statistic; exports de.learnlib.filter.statistic.learner; exports de.learnlib.filter.statistic.oracle; exports de.learnlib.filter.statistic.sul; diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java index 178c175db..9a3b8805b 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java @@ -25,10 +25,12 @@ import de.learnlib.query.AdaptiveQuery; import de.learnlib.query.AdaptiveQuery.Response; import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticData; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class CounterAdaptiveOracleTest { @@ -39,6 +41,11 @@ public CounterAdaptiveOracleTest() { this.oracle = new CounterAdaptiveQueryOracle<>(new DummyOracle()); } + @BeforeClass + public void setUp() { + Statistics.getContainer().clear(); + } + @Test public void testInitialState() { verifyCounts(0, 0); @@ -71,18 +78,16 @@ public void testSecondQueryBatch() { verifyCounts(3, 11); } - @Test + @Test(dependsOnMethods = "testSecondQueryBatch") public void testStatistics() { - final StatisticData statisticalData = oracle.getStatisticalData(); - Assert.assertTrue(statisticalData.getName().contains("\n")); - Assert.assertTrue(statisticalData.getUnit().contains("\n")); - Assert.assertTrue(statisticalData.getSummary().contains("\n")); - Assert.assertTrue(statisticalData.getDetails().contains("\n")); + final StatsContainer container = Statistics.getContainer(); + Assert.assertTrue(container.printStats().contains("\n")); } private void verifyCounts(long queries, long symbols) { - Assert.assertEquals(oracle.getResetCounter().getCount(), queries); - Assert.assertEquals(oracle.getSymbolCounter().getCount(), symbols); + final StatsContainer container = Statistics.getContainer(); + Assert.assertEquals(container.getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L), queries); + Assert.assertEquals(container.getCount(CounterAdaptiveQueryOracle.SYMBOL_KEY).orElse(0L), symbols); } private Collection>> generateQueries(int numQueries, diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java index 81fb3d4bc..a7749eb0c 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java @@ -21,7 +21,8 @@ import de.learnlib.filter.statistic.TestQueries; import de.learnlib.oracle.MembershipOracle; import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticData; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import net.automatalib.word.Word; import org.mockito.Mockito; import org.testng.Assert; @@ -62,18 +63,16 @@ public void testSecondQueryBatch() { verifyCounts(4, 10); } - @Test + @Test(dependsOnMethods = "testSecondQueryBatch") public void testStatistics() { - final StatisticData statisticalData = oracle.getStatisticalData(); - Assert.assertTrue(statisticalData.getName().contains("\n")); - Assert.assertTrue(statisticalData.getUnit().contains("\n")); - Assert.assertTrue(statisticalData.getSummary().contains("\n")); - Assert.assertTrue(statisticalData.getDetails().contains("\n")); + final StatsContainer container = Statistics.getContainer(); + Assert.assertTrue(container.printStats().contains("\n")); } private void verifyCounts(long queries, long symbols) { - Assert.assertEquals(oracle.getQueryCounter().getCount(), queries); - Assert.assertEquals(oracle.getSymbolCounter().getCount(), symbols); + final StatsContainer container = Statistics.getContainer(); + Assert.assertEquals(container.getCount(CounterOracle.QUERY_KEY).orElse(0L), queries); + Assert.assertEquals(container.getCount(CounterOracle.SYMBOL_KEY).orElse(0L), symbols); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/HistogramOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/HistogramOracleTest.java deleted file mode 100644 index 632f067f1..000000000 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/HistogramOracleTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* 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 de.learnlib.filter.statistic.oracle; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.Collections; - -import de.learnlib.filter.statistic.TestQueries; -import de.learnlib.oracle.MembershipOracle; -import de.learnlib.query.Query; -import net.automatalib.common.util.IOUtil; -import net.automatalib.word.Word; -import org.mockito.Mockito; -import org.testng.Assert; -import org.testng.annotations.Test; - -public class HistogramOracleTest { - - private static final String COUNTER_NAME = "testCounter"; - - private final HistogramOracle> oracle; - - @SuppressWarnings("unchecked") - public HistogramOracleTest() { - this.oracle = new HistogramOracle>(Mockito.mock(MembershipOracle.class), COUNTER_NAME); - } - - @Test - public void testInitialState() { - verifyCounts(0, 0, 0, 0); - } - - @Test(dependsOnMethods = "testInitialState") - public void testFirstQueryBatch() { - Collection>> queries = TestQueries.createNoopQueries(2); - oracle.processQueries(queries); - verifyCounts(2, 0, 0, 0); - } - - @Test(dependsOnMethods = "testFirstQueryBatch") - public void testEmptyQueryBatch() { - Collection>> noQueries = Collections.emptySet(); - oracle.processQueries(noQueries); - verifyCounts(2, 0, 0, 0); - } - - @Test(dependsOnMethods = "testEmptyQueryBatch") - public void testSecondQueryBatch() { - Collection>> queries = TestQueries.createNoopQueries(2, 5, TestQueries.INPUTS); - oracle.processQueries(queries); - verifyCounts(4, 10, 2.5, 0); - } - - @Test(dependsOnMethods = "testSecondQueryBatch") - public void testSummary() throws IOException { - - final String details = oracle.getStatisticalData().getDetails(); - final String summary = oracle.getStatisticalData().getSummary(); - - try (InputStream detailStream = HistogramOracleTest.class.getResourceAsStream("/histogram_details.txt"); - InputStream summaryStream = HistogramOracleTest.class.getResourceAsStream("/histogram_summary.txt")) { - - final String expectedDetail = IOUtil.toString(IOUtil.asBufferedUTF8Reader(detailStream)); - final String expectedSummary = IOUtil.toString(IOUtil.asBufferedUTF8Reader(summaryStream)); - - Assert.assertEquals(details, expectedDetail); - Assert.assertEquals(summary, expectedSummary); - } - } - - @Test - public void testGetName() { - Assert.assertEquals(oracle.getStatisticalData().getName(), COUNTER_NAME); - } - - private void verifyCounts(long size, long sum, double mean, long median) { - Assert.assertEquals(oracle.getStatisticalData().getSize(), size); - Assert.assertEquals(oracle.getStatisticalData().getSum(), sum); - Assert.assertEquals(oracle.getStatisticalData().getMean(), mean); - Assert.assertEquals(oracle.getStatisticalData().getMedian(), median); - } -} diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java index 37ab6bdd0..dd0f75bcc 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java @@ -17,26 +17,26 @@ import java.util.Collection; import java.util.Collections; +import java.util.Optional; -import de.learnlib.filter.statistic.Counter; import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle; import de.learnlib.oracle.SingleQueryOracle.SingleQueryOracleMealy; import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticSUL; +import de.learnlib.statistic.Statistics; import de.learnlib.sul.SUL; import net.automatalib.word.Word; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; -public abstract class AbstractCounterSULTest> { +public abstract class AbstractCounterSULTest> { private S statisticSUL; private MealyMembershipOracle asOracle; protected abstract S getStatisticSUL(); - protected abstract Counter getCounter(S sul); + protected abstract Optional getCount(S sul); protected abstract int getCountIncreasePerQuery(); @@ -46,6 +46,7 @@ public abstract class AbstractCounterSULTest> +public abstract class AbstractResetCounterSULTest> extends AbstractCounterSULTest { @Override diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractSymbolCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractSymbolCounterSULTest.java index f60e73db6..25a6e5e22 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractSymbolCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractSymbolCounterSULTest.java @@ -19,10 +19,10 @@ import de.learnlib.filter.statistic.TestQueries; import de.learnlib.query.Query; -import de.learnlib.statistic.StatisticSUL; +import de.learnlib.sul.SUL; import net.automatalib.word.Word; -public abstract class AbstractSymbolCounterSULTest> +public abstract class AbstractSymbolCounterSULTest> extends AbstractCounterSULTest { private static final int QUERY_LENGTH = 5; diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java index 86c22f70a..8eca0672c 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.ObservableMealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class ResetCounterObservableSULTest extends AbstractResetCounterSULTest> { @@ -28,7 +31,8 @@ protected CounterObservableSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterObservableSUL sul) { - return sul.getResetCounter(); + protected Optional getCount(CounterObservableSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterObservableSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java index c5005aac0..07e0dba34 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.MealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class ResetCounterSULTest extends AbstractResetCounterSULTest> { @@ -27,7 +30,8 @@ protected CounterSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterSUL sul) { - return sul.getResetCounter(); + protected Optional getCount(CounterSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java index d96ffe930..ca6d47e7d 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.StateLocalInputMealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class ResetCounterStateLocalInputSULTest extends AbstractResetCounterSULTest> { @@ -28,7 +31,8 @@ protected CounterStateLocalInputSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterStateLocalInputSUL sul) { - return sul.getResetCounter(); + protected Optional getCount(CounterStateLocalInputSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterStateLocalInputSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java index f6958d146..efcacb504 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.ObservableMealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class SymbolCounterObservableSULTest extends AbstractSymbolCounterSULTest> { @@ -28,7 +31,8 @@ protected CounterObservableSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterObservableSUL sul) { - return sul.getSymbolCounter(); + protected Optional getCount(CounterObservableSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterObservableSUL.SYMBOL_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java index 3ac59bb13..db9e6872b 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.MealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class SymbolCounterSULTest extends AbstractSymbolCounterSULTest> { @@ -27,8 +30,9 @@ protected CounterSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterSUL sul) { - return sul.getSymbolCounter(); + protected Optional getCount(CounterSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterSUL.SYMBOL_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java index 8b543ffbf..b5adb7d5a 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java @@ -15,9 +15,12 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.Optional; + import de.learnlib.driver.simulator.StateLocalInputMealySimulatorSUL; -import de.learnlib.filter.statistic.Counter; import de.learnlib.filter.statistic.TestQueries; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; public class SymbolCounterStateLocalInputSULTest extends AbstractSymbolCounterSULTest> { @@ -28,7 +31,8 @@ protected CounterStateLocalInputSUL getStatisticSUL() { } @Override - protected Counter getCounter(CounterStateLocalInputSUL sul) { - return sul.getSymbolCounter(); + protected Optional getCount(CounterStateLocalInputSUL sul) { + final StatsContainer container = Statistics.getContainer(); + return container.getCount(CounterStateLocalInputSUL.SYMBOL_KEY); } } From 5f1fb467cb4407de1bdedb9933d68ed9dcde8113 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Thu, 20 Nov 2025 10:57:53 +0100 Subject: [PATCH 37/55] add missing stats reset --- .../learnlib/filter/statistic/oracle/CounterOracleTest.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java index a7749eb0c..79fc5cab7 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java @@ -26,6 +26,7 @@ import net.automatalib.word.Word; import org.mockito.Mockito; import org.testng.Assert; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; public class CounterOracleTest { @@ -37,6 +38,11 @@ public CounterOracleTest() { this.oracle = new CounterOracle>(Mockito.mock(MembershipOracle.class)); } + @BeforeClass + public void setUp() { + Statistics.getContainer().clear(); + } + @Test public void testInitialState() { verifyCounts(0, 0); From e9a1975a1475a6ca36bb96f741d882443d5eebab Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Thu, 20 Nov 2025 16:57:58 +0100 Subject: [PATCH 38/55] make MapStatsContainer thread-safe and fix tests --- .../main/java/de/learnlib/sul/TimedSUL.java | 8 +++++-- .../de/learnlib/example/mmlt/Example1.java | 2 +- .../cache/AbstractParallelCacheTest.java | 6 +++-- .../cache/dfa/AbstractDFACacheTest.java | 2 +- .../filter/cache/dfa/DFAHashCacheTest.java | 2 +- .../cache/dfa/DFAParallelCacheTest.java | 2 +- filters/statistics/pom.xml | 4 ++++ .../container/MapStatsContainer.java | 22 +++++++++---------- .../statistic/container/MapStatsProvider.java | 2 ++ .../statistic/sul/CounterObservableSUL.java | 12 +++++++--- .../filter/statistic/sul/CounterSUL.java | 8 +++++-- .../sul/CounterStateLocalInputSUL.java | 10 +++++++-- .../filter/statistic/sul/CounterTimedSUL.java | 16 +++++++++++++- .../statistics/src/main/java/module-info.java | 1 + 14 files changed, 70 insertions(+), 27 deletions(-) diff --git a/api/src/main/java/de/learnlib/sul/TimedSUL.java b/api/src/main/java/de/learnlib/sul/TimedSUL.java index 1e2d30827..2f362894b 100644 --- a/api/src/main/java/de/learnlib/sul/TimedSUL.java +++ b/api/src/main/java/de/learnlib/sul/TimedSUL.java @@ -1,9 +1,9 @@ package de.learnlib.sul; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; @@ -101,4 +101,8 @@ default Word> collectTimeouts(TimeStepSequence input) { return wbOutput.toWord(); } + @Override + default TimedSUL fork() { + throw new UnsupportedOperationException(); + } } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 6c3994770..f67a55d2a 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -126,7 +126,7 @@ private static void runExperiment(ExtensibleLStarMMLT learner, stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); // Print final result + statistics: - stats.printStats(); + System.out.println(stats.printStats()); new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java index 8377ff6ed..e6b0a9c6a 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java @@ -22,6 +22,7 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.oracle.ParallelOracle; import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.common.util.collection.IterableUtil; import net.automatalib.word.Word; @@ -64,6 +65,7 @@ public void setUp() { this.targetModel = getTargetModel(); this.cache = getCacheRepresentative(); this.parallelOracle = getParallelOracle(); + Statistics.getContainer().clear(); } @AfterClass @@ -71,7 +73,7 @@ public void teardown() { this.parallelOracle.shutdownNow(); } - @Test(timeOut = 20000) + @Test public void testConcurrentMembershipQueries() { Assert.assertEquals(getNumberOfQueries(), 0); @@ -103,7 +105,7 @@ public void testConcurrentMembershipQueries() { Assert.assertEquals(numOfQueriesAfter, numOfQueriesBefore); } - @Test(dependsOnMethods = "testConcurrentMembershipQueries", timeOut = 20000) + @Test(dependsOnMethods = "testConcurrentMembershipQueries") public void testConcurrentEquivalenceQueries() { final long previousCount = getNumberOfQueries(); final EquivalenceOracle eqOracle = cache.createCacheConsistencyTest(); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java index 95863699f..8a3d2251b 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java @@ -58,7 +58,7 @@ protected DFACacheOracle getResumedOracle(DFACacheOracle o @Override protected long getNumberOfPosedQueries() { - return Statistics.getContainer().getCount(DFACounterOracle.SYMBOL_KEY).orElse(0L); + return Statistics.getContainer().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java index e783f24fd..f22be17c9 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java @@ -56,7 +56,7 @@ protected DFAHashCacheOracle getResumedOracle(DFAHashCacheOracle getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(DFACounterOracle.SYMBOL_KEY).orElse(0L); + return Statistics.getContainer().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/statistics/pom.xml b/filters/statistics/pom.xml index 2e7ec208c..11351b016 100644 --- a/filters/statistics/pom.xml +++ b/filters/statistics/pom.xml @@ -52,6 +52,10 @@ limitations under the License. de.learnlib.tooling annotations + + org.kohsuke.metainf-services + metainf-services + diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java index 0d69dd135..1ea594727 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java @@ -14,12 +14,12 @@ public class MapStatsContainer implements StatsContainer { private final Map statistics = new HashMap<>(); // id -> stat @Override - public void addTextInfo(String id, @Nullable String description, String text) { + public synchronized void addTextInfo(String id, @Nullable String description, String text) { statistics.put(id, new TextStatistic(id, description, text)); } @Override - public Optional getTextValue(String id) { + public synchronized Optional getTextValue(String id) { var value = statistics.get(id); if (value instanceof TextStatistic textStatistic) { return Optional.of(textStatistic.getText()); @@ -28,12 +28,12 @@ public Optional getTextValue(String id) { } @Override - public void setFlag(String id, @Nullable String description, boolean value) { + public synchronized void setFlag(String id, @Nullable String description, boolean value) { statistics.put(id, new FlagStatistic(id, description, value)); } @Override - public Optional getFlagValue(String id) { + public synchronized Optional getFlagValue(String id) { var value = statistics.get(id); if (value instanceof FlagStatistic flagStatistic) { return Optional.of(flagStatistic.isFlagged()); @@ -42,7 +42,7 @@ public Optional getFlagValue(String id) { } @Override - public void startOrResumeClock(String id, @Nullable String description) { + public synchronized void startOrResumeClock(String id, @Nullable String description) { var value = statistics.get(id); if (value instanceof StopClockStatistic clockStatistic) { clockStatistic.resume(); @@ -55,7 +55,7 @@ public void startOrResumeClock(String id, @Nullable String description) { } @Override - public void pauseClock(String id) { + public synchronized void pauseClock(String id) { var value = statistics.get(id); if (value instanceof StopClockStatistic clockStatistic) { clockStatistic.pause(); @@ -63,7 +63,7 @@ public void pauseClock(String id) { } @Override - public Optional getClockValue(String id) { + public synchronized Optional getClockValue(String id) { var value = statistics.get(id); if (value instanceof StopClockStatistic clockStatistic) { return Optional.of(clockStatistic.getElapsed()); @@ -72,7 +72,7 @@ public Optional getClockValue(String id) { } @Override - public void increaseCounter(String id, @Nullable String description, long increment) { + public synchronized void increaseCounter(String id, @Nullable String description, long increment) { var value = statistics.get(id); if (value instanceof CounterStatistic counterStatistic) { counterStatistic.increase(increment); @@ -83,12 +83,12 @@ public void increaseCounter(String id, @Nullable String description, long increm } @Override - public void setCounter(String id, @Nullable String description, long count) { + public synchronized void setCounter(String id, @Nullable String description, long count) { statistics.put(id, new CounterStatistic(id, description, count)); } @Override - public Optional getCount(String id) { + public synchronized Optional getCount(String id) { var value = statistics.get(id); if (value instanceof CounterStatistic counterStatistic) { return Optional.of(counterStatistic.getCount()); @@ -97,7 +97,7 @@ public Optional getCount(String id) { } @Override - public void clear() { + public synchronized void clear() { statistics.clear(); } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java index eb5e3c6ad..c616b852f 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java @@ -2,7 +2,9 @@ import de.learnlib.statistic.StatisticsProvider; import de.learnlib.statistic.StatsContainer; +import org.kohsuke.MetaInfServices; +@MetaInfServices(StatisticsProvider.class) public class MapStatsProvider implements StatisticsProvider { final ThreadLocal threadLocal = ThreadLocal.withInitial(MapStatsContainer::new); diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java index 6cab19983..15b6199db 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java @@ -15,6 +15,8 @@ */ package de.learnlib.filter.statistic.sul; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.sul.ObservableSUL; public class CounterObservableSUL extends CounterSUL implements ObservableSUL { @@ -25,14 +27,18 @@ public CounterObservableSUL(ObservableSUL sul) { this(sul, ""); } - private CounterObservableSUL(ObservableSUL sul, String prefix) { - super(sul, prefix); + public CounterObservableSUL(ObservableSUL sul, String prefix) { + this(sul, prefix, Statistics.getContainer()); + } + + protected CounterObservableSUL(ObservableSUL sul, String prefix, StatsContainer statistics) { + super(sul, prefix, statistics); this.sul = sul; } @Override public ObservableSUL fork() { - return new CounterObservableSUL<>(this.sul.fork(), super.prefix); + return new CounterObservableSUL<>(this.sul.fork(), super.prefix, super.statistics); } @Override diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java index 22a07b437..60d3cacf3 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java @@ -33,9 +33,13 @@ public CounterSUL(SUL sul) { } public CounterSUL(SUL sul, String prefix) { + this(sul, prefix, Statistics.getContainer()); + } + + protected CounterSUL(SUL sul, String prefix, StatsContainer statistics) { this.sul = sul; this.prefix = prefix; - this.statistics = Statistics.getContainer(); + this.statistics = statistics; } @Override @@ -62,6 +66,6 @@ public boolean canFork() { @Override public SUL fork() { - return new CounterSUL<>(this.sul.fork(), this.prefix); + return new CounterSUL<>(this.sul.fork(), this.prefix, this.statistics); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java index d0654e9c0..b3e65ce35 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java @@ -17,6 +17,8 @@ import java.util.Collection; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatsContainer; import de.learnlib.sul.StateLocalInputSUL; public class CounterStateLocalInputSUL extends CounterSUL implements StateLocalInputSUL { @@ -30,7 +32,11 @@ public CounterStateLocalInputSUL(StateLocalInputSUL sul) { } private CounterStateLocalInputSUL(StateLocalInputSUL sul, String prefix) { - super(sul, prefix); + this(sul, prefix, Statistics.getContainer()); + } + + protected CounterStateLocalInputSUL(StateLocalInputSUL sul, String prefix, StatsContainer statistics) { + super(sul, prefix, statistics); this.sul = sul; } @@ -42,7 +48,7 @@ public Collection currentlyEnabledInputs() { @Override public StateLocalInputSUL fork() { - return new CounterStateLocalInputSUL<>(this.sul.fork(), super.prefix); + return new CounterStateLocalInputSUL<>(this.sul.fork(), super.prefix, super.statistics); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index 065f2ca1e..60bdbc997 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -28,9 +28,13 @@ public CounterTimedSUL(TimedSUL delegate) { } public CounterTimedSUL(TimedSUL delegate, String name) { + this(delegate, name, Statistics.getContainer()); + } + + protected CounterTimedSUL(TimedSUL delegate, String name, StatsContainer statistics) { this.delegate = delegate; this.name = name; - this.stats = Statistics.getContainer(); + this.stats = statistics; } private String withPrefix(String label) { @@ -82,5 +86,15 @@ public void post() { this.delegate.post(); } + @Override + public boolean canFork() { + return this.delegate.canFork(); + } + + @Override + public TimedSUL fork() { + return new CounterTimedSUL<>(this.delegate.fork(), this.name, this.stats); + } + } diff --git a/filters/statistics/src/main/java/module-info.java b/filters/statistics/src/main/java/module-info.java index f75a1201a..f9fbdf818 100644 --- a/filters/statistics/src/main/java/module-info.java +++ b/filters/statistics/src/main/java/module-info.java @@ -38,6 +38,7 @@ // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static de.learnlib.tooling.annotation; requires static org.checkerframework.checker.qual; + requires static org.kohsuke.metainf_services; exports de.learnlib.filter.statistic.learner; exports de.learnlib.filter.statistic.oracle; From 3adca96f5b04b5f55ebf1a084009ddda7784b2f2 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Fri, 21 Nov 2025 14:19:57 +0100 Subject: [PATCH 39/55] refactor symbol filters --- algorithms/active/lstar/pom.xml | 9 ++--- .../lstar/mmlt/ExtensibleLStarMMLT.java | 11 +++--- .../lstar/mmlt/MMLTObservationTable.java | 10 ++--- .../mmlt/cex/MMLTCounterexampleHandler.java | 4 +- .../mmlt/filter}/MMLTPerfectSymbolFilter.java | 6 +-- .../mmlt/filter}/MMLTRandomSymbolFilter.java | 6 +-- .../filter}/MMLTStatisticsSymbolFilter.java | 8 ++-- .../mmlt/filter}/MMLTSymbolFilterUtil.java | 4 +- .../lstar/src/main/java/module-info.java | 6 ++- ...xtensibleLStarMMLTCounterexampleTests.java | 2 +- .../lstar/it/ExtensibleLStarMMLTIT.java | 16 ++++---- .../learnlib/filter/MutableSymbolFilter.java | 25 ++++++++++++ .../SymbolFilter.java | 11 +----- .../SymbolFilterResponse.java | 2 +- api/src/main/java/module-info.java | 2 +- examples/pom.xml | 8 ++-- .../de/learnlib/example/mmlt/Example1.java | 12 +++--- examples/src/main/java/module-info.java | 2 +- filters/pom.xml | 1 + {oracles => filters}/symbol-filters/pom.xml | 38 +------------------ .../filter/symbol}/AcceptAllSymbolFilter.java | 12 +++--- .../filter/symbol}/CachedSymbolFilter.java | 15 +++++--- .../filter/symbol}/IgnoreAllSymbolFilter.java | 11 ++---- .../filter/symbol}/PerfectSymbolFilter.java | 13 ++----- .../filter/symbol}/RandomSymbolFilter.java | 11 ++---- .../symbol}/StatisticsSymbolFilter.java | 17 ++++++--- .../src/main/java/module-info.java | 5 +-- oracles/pom.xml | 1 - 28 files changed, 121 insertions(+), 147 deletions(-) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt => algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter}/MMLTPerfectSymbolFilter.java (83%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt => algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter}/MMLTRandomSymbolFilter.java (85%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt => algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter}/MMLTStatisticsSymbolFilter.java (78%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt => algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter}/MMLTSymbolFilterUtil.java (93%) create mode 100644 api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java rename api/src/main/java/de/learnlib/{symbol_filter => filter}/SymbolFilter.java (75%) rename api/src/main/java/de/learnlib/{symbol_filter => filter}/SymbolFilterResponse.java (63%) rename {oracles => filters}/symbol-filters/pom.xml (54%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/AcceptAllSymbolFilter.java (50%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/CachedSymbolFilter.java (73%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/IgnoreAllSymbolFilter.java (57%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/PerfectSymbolFilter.java (66%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/RandomSymbolFilter.java (80%) rename {oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters => filters/symbol-filters/src/main/java/de/learnlib/filter/symbol}/StatisticsSymbolFilter.java (76%) rename {oracles => filters}/symbol-filters/src/main/java/module-info.java (89%) diff --git a/algorithms/active/lstar/pom.xml b/algorithms/active/lstar/pom.xml index a6a2d92d5..2c7966646 100644 --- a/algorithms/active/lstar/pom.xml +++ b/algorithms/active/lstar/pom.xml @@ -50,6 +50,10 @@ limitations under the License. de.learnlib learnlib-counterexamples + + de.learnlib + learnlib-symbol-filters + de.learnlib learnlib-util @@ -134,11 +138,6 @@ limitations under the License. learnlib-cache test - - de.learnlib - learnlib-symbol-filters - test - de.learnlib learnlib-statistics 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 93a9ff559..35dc69116 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 @@ -21,12 +21,11 @@ import de.learnlib.datastructure.observationtable.OTLearner; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; +import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatsContainer; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; @@ -57,7 +56,7 @@ public class ExtensibleLStarMMLT implements OTLearner, ? super Word>> closingStrategy; private final TimedQueryOracle timeOracle; - private final SymbolFilter, InputSymbol> symbolFilter; + private final MutableSymbolFilter, InputSymbol> symbolFilter; private final MMLTHypDataContainer hypData; @@ -83,7 +82,7 @@ public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, List>> initialSuffixes, TimedQueryOracle timeOracle, - SymbolFilter, InputSymbol> symbolFilter) { + MutableSymbolFilter, InputSymbol> symbolFilter) { this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); } @@ -103,7 +102,7 @@ public ExtensibleLStarMMLT(Alphabet alphabet, List>> initialSuffixes, ClosingStrategy, ? super Word>> closingStrategy, TimedQueryOracle timeOracle, - SymbolFilter, InputSymbol> symbolFilter, + MutableSymbolFilter, InputSymbol> symbolFilter, AcexAnalyzer analyzer) { this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; @@ -348,7 +347,7 @@ private boolean refineHypothesisSingle(DefaultQuery, Word implements MutableObservationTable, InputSymbol> symbolFilter; + private final MutableSymbolFilter, InputSymbol> symbolFilter; private final Map>, LocationTimerInfo> timerInfoMap; // prefix -> timer info @@ -63,7 +63,7 @@ public class MMLTObservationTable implements MutableObservationTable silentOutput; // used for symbol filtering public MMLTObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, - @NonNull SymbolFilter, InputSymbol> symbolFilter, O silentOutput) { + @NonNull MutableSymbolFilter, InputSymbol> symbolFilter, O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; @@ -194,7 +194,7 @@ private List>> createOutgoingTransitions(RowImpl) sym, SymbolFilterResponse.ACCEPT); + this.symbolFilter.accept(sp, (InputSymbol) sym); } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java index 07904b184..bb8dcdbaf 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java @@ -11,8 +11,8 @@ import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java similarity index 83% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java index 2b16a1d63..36b5b4601 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTPerfectSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java @@ -1,8 +1,8 @@ -package de.learnlib.oracle.symbol_filters.mmlt; +package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.oracle.symbol_filters.PerfectSymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.symbol.PerfectSymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java similarity index 85% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java index b11fe76de..3bf8c559e 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTRandomSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java @@ -1,8 +1,8 @@ -package de.learnlib.oracle.symbol_filters.mmlt; +package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.oracle.symbol_filters.RandomSymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.symbol.RandomSymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java similarity index 78% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java index 2553fc2ba..d40bb95ff 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTStatisticsSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java @@ -1,9 +1,9 @@ -package de.learnlib.oracle.symbol_filters.mmlt; +package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.oracle.symbol_filters.StatisticsSymbolFilter; +import de.learnlib.filter.symbol.StatisticsSymbolFilter; import de.learnlib.statistic.StatsContainer; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java similarity index 93% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java rename to algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java index dd6727a36..52d27ac7a 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/mmlt/MMLTSymbolFilterUtil.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java @@ -1,8 +1,8 @@ -package de.learnlib.oracle.symbol_filters.mmlt; +package de.learnlib.algorithm.lstar.mmlt.filter; import java.util.Objects; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; diff --git a/algorithms/active/lstar/src/main/java/module-info.java b/algorithms/active/lstar/src/main/java/module-info.java index c4e6a3c50..ed5c9e9b5 100644 --- a/algorithms/active/lstar/src/main/java/module-info.java +++ b/algorithms/active/lstar/src/main/java/module-info.java @@ -37,6 +37,7 @@ requires de.learnlib.common.counterexample; requires de.learnlib.common.util; requires de.learnlib.datastructure; + requires de.learnlib.filter.symbol; requires net.automatalib.api; requires net.automatalib.common.util; requires net.automatalib.core; @@ -51,8 +52,11 @@ exports de.learnlib.algorithm.lstar.closing; exports de.learnlib.algorithm.lstar.dfa; exports de.learnlib.algorithm.lstar.mealy; + exports de.learnlib.algorithm.lstar.mmlt; + exports de.learnlib.algorithm.lstar.mmlt.cex; + exports de.learnlib.algorithm.lstar.mmlt.cex.results; + exports de.learnlib.algorithm.lstar.mmlt.filter; exports de.learnlib.algorithm.lstar.moore; exports de.learnlib.algorithm.malerpnueli; exports de.learnlib.algorithm.rivestschapire; - exports de.learnlib.algorithm.lstar.mmlt; } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index b55ac5501..cd017aaf1 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -8,7 +8,7 @@ import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.example.mmlt.MMLTExamples; diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 97676192f..63d7c2a3d 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -14,12 +14,12 @@ import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.oracle.symbol_filters.AcceptAllSymbolFilter; -import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; -import de.learnlib.oracle.symbol_filters.IgnoreAllSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.MMLTPerfectSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; -import de.learnlib.symbol_filter.SymbolFilter; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; +import de.learnlib.filter.symbol.CachedSymbolFilter; +import de.learnlib.filter.symbol.IgnoreAllSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTPerfectSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; +import de.learnlib.filter.SymbolFilter; import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.it.learner.AbstractMMLTLearnerIT; import de.learnlib.testsupport.it.learner.LearnerVariantList.MMLTLearnerVariantList; @@ -60,9 +60,9 @@ protected void addLearnerVariants(Alphabet alphabet, case none -> new AcceptAllSymbolFilter<>(); }; - filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLT<>(alphabet, example.getParams(), suffixes, mqOracle, filter); + var learner = new ExtensibleLStarMMLT<>(alphabet, example.getParams(), suffixes, mqOracle, cachedFilter); variants.addLearnerVariant("system=" + example + ",filter=" + filterMode, learner, counters + mmlt.size()); } } diff --git a/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java b/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java new file mode 100644 index 000000000..ef404ef78 --- /dev/null +++ b/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java @@ -0,0 +1,25 @@ +package de.learnlib.filter; + +import net.automatalib.word.Word; + +/** + * Interface for a symbol filter. + * A symbol filter predicts whether a given transition is ignorable in a given state. + * This information can be used to avoid redundant queries. + * A symbol filter may answer incorrectly. + * + * @param Type for symbols in the prefix of the considered states + * @param Type of the queried symbols + */ +public interface MutableSymbolFilter extends SymbolFilter { + + /** + * Sets the response of the filter for the given transition to the provided response. + * + * @param prefix + * State prefix. + * @param symbol + * Input of the transition that should be updated. + */ + void accept(Word prefix, V symbol); +} diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java b/api/src/main/java/de/learnlib/filter/SymbolFilter.java similarity index 75% rename from api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java rename to api/src/main/java/de/learnlib/filter/SymbolFilter.java index 9af1f079d..43f6c6095 100644 --- a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilter.java +++ b/api/src/main/java/de/learnlib/filter/SymbolFilter.java @@ -1,4 +1,4 @@ -package de.learnlib.symbol_filter; +package de.learnlib.filter; import net.automatalib.word.Word; @@ -28,13 +28,4 @@ public interface SymbolFilter { * @return IGNORE if the symbol is considered ignorable, ACCEPT if it is not. */ SymbolFilterResponse query(Word prefix, V symbol); - - /** - * Sets the response of the filter for the given transition to the provided response. - * - * @param prefix State prefix. - * @param symbol Input of the transition that should be updated. - * @param response New response - */ - void update(Word prefix, V symbol, SymbolFilterResponse response); } diff --git a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java b/api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java similarity index 63% rename from api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java rename to api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java index e0a5dd693..67fb8c20b 100644 --- a/api/src/main/java/de/learnlib/symbol_filter/SymbolFilterResponse.java +++ b/api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java @@ -1,4 +1,4 @@ -package de.learnlib.symbol_filter; +package de.learnlib.filter; public enum SymbolFilterResponse { ACCEPT, diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index b143216f7..bca925741 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -40,12 +40,12 @@ exports de.learnlib; exports de.learnlib.algorithm; exports de.learnlib.exception; + exports de.learnlib.filter; exports de.learnlib.logging; exports de.learnlib.oracle; exports de.learnlib.query; exports de.learnlib.statistic; exports de.learnlib.sul; - exports de.learnlib.symbol_filter; uses StatisticsProvider; } diff --git a/examples/pom.xml b/examples/pom.xml index 1dd88ee0a..7edf1f1f2 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -111,6 +111,10 @@ limitations under the License. de.learnlib learnlib-statistics + + de.learnlib + learnlib-symbol-filters + de.learnlib learnlib-ttt @@ -178,10 +182,6 @@ limitations under the License. org.mockito mockito-core - - de.learnlib - learnlib-symbol-filters - diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index f67a55d2a..60814d4fb 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -7,6 +7,7 @@ import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.SymbolFilter; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; import de.learnlib.filter.statistic.oracle.CounterEQOracle; @@ -17,13 +18,12 @@ import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.oracle.symbol_filters.CachedSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.MMLTRandomSymbolFilter; -import de.learnlib.oracle.symbol_filters.mmlt.MMLTStatisticsSymbolFilter; +import de.learnlib.filter.symbol.CachedSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatsContainer; -import de.learnlib.symbol_filter.SymbolFilter; import de.learnlib.testsupport.example.mmlt.MMLTExamples; import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.symbol.time.InputSymbol; @@ -83,9 +83,9 @@ public static void main(String[] args) { new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); filter = new MMLTStatisticsSymbolFilter<>(model.getReferenceAutomaton(), filter, stats); - filter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, filter); + var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, cachedFilter); // Start learning: runExperiment(learner, chainOracle, stats, 100); diff --git a/examples/src/main/java/module-info.java b/examples/src/main/java/module-info.java index 529134784..22f860017 100644 --- a/examples/src/main/java/module-info.java +++ b/examples/src/main/java/module-info.java @@ -36,10 +36,10 @@ requires de.learnlib.filter.cache; requires de.learnlib.filter.reuse; requires de.learnlib.filter.statistic; + requires de.learnlib.filter.symbol; requires de.learnlib.oracle.emptiness; requires de.learnlib.oracle.equivalence; requires de.learnlib.oracle.membership; - requires de.learnlib.oracle.symbol_filters; requires de.learnlib.oracle.parallelism; requires de.learnlib.oracle.property; requires de.learnlib.testsupport.example; diff --git a/filters/pom.xml b/filters/pom.xml index ce77c4bb6..6bb856a92 100644 --- a/filters/pom.xml +++ b/filters/pom.xml @@ -35,5 +35,6 @@ limitations under the License. cache reuse statistics + symbol-filters diff --git a/oracles/symbol-filters/pom.xml b/filters/symbol-filters/pom.xml similarity index 54% rename from oracles/symbol-filters/pom.xml rename to filters/symbol-filters/pom.xml index ec0777f2f..49eb124cc 100644 --- a/oracles/symbol-filters/pom.xml +++ b/filters/symbol-filters/pom.xml @@ -4,7 +4,7 @@ de.learnlib - learnlib-oracles-parent + learnlib-filters-parent 0.19.0-SNAPSHOT ../pom.xml @@ -44,42 +44,6 @@ - - de.learnlib - learnlib-emptiness-oracles - test - - - de.learnlib - learnlib-equivalence-oracles - test - - - de.learnlib.testsupport - learnlib-learning-examples - - - de.learnlib - learnlib-membership-oracles - test - - - - net.automatalib - automata-core - test - - - net.automatalib - automata-modelchecking-ltsmin - test - - - net.automatalib - automata-util - test - - org.testng testng diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java similarity index 50% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java index c972c5571..d4afdcc2d 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/AcceptAllSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java @@ -1,8 +1,8 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.MutableSymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; /** @@ -11,14 +11,14 @@ * @param Type for symbols in the prefix of the considered states * @param Type of the queried symbols */ -public class AcceptAllSymbolFilter implements SymbolFilter { +public class AcceptAllSymbolFilter implements MutableSymbolFilter { @Override public SymbolFilterResponse query(Word prefix, V symbol) { return SymbolFilterResponse.ACCEPT; } @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); + public void accept(Word prefix, V symbol) { + // we don't need to do anything because we always return ACCEPT anyway } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java similarity index 73% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java index 7dc0e57d9..681bb851e 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/CachedSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java @@ -1,8 +1,9 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.MutableSymbolFilter; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; import java.util.HashMap; @@ -14,7 +15,7 @@ * @param Type for symbols in the prefix of the considered states * @param Type of the queried symbols */ -public class CachedSymbolFilter implements SymbolFilter { +public class CachedSymbolFilter implements MutableSymbolFilter { private final Map, Map> previousResponses; // prefix -> (input -> legal/ignore) private final SymbolFilter delegate; @@ -37,7 +38,11 @@ public SymbolFilterResponse query(Word prefix, V symbol) { } @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { + public void accept(Word prefix, V symbol) { + this.update(prefix, symbol, SymbolFilterResponse.ACCEPT); + } + + private void update(Word prefix, V symbol, SymbolFilterResponse response) { this.previousResponses.putIfAbsent(prefix, new HashMap<>()); this.previousResponses.get(prefix).put(symbol, (response == SymbolFilterResponse.ACCEPT)); } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java similarity index 57% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java index 978d21582..b3005aa71 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/IgnoreAllSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java @@ -1,7 +1,7 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; /** @@ -15,9 +15,4 @@ public class IgnoreAllSymbolFilter implements SymbolFilter { public SymbolFilterResponse query(Word prefix, V symbol) { return SymbolFilterResponse.IGNORE; } - - @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java similarity index 66% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java index d37da2ec7..fa9de249e 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/PerfectSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java @@ -1,12 +1,10 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; -import java.util.Random; - /** * A symbol filter that answers all queries correctly. * @@ -26,9 +24,4 @@ public SymbolFilterResponse query(Word prefix, V symbol) { return SymbolFilterResponse.ACCEPT; } } - - @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java similarity index 80% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java index df301b561..45401438c 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/RandomSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java @@ -1,8 +1,8 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; import java.util.Random; @@ -44,9 +44,4 @@ public SymbolFilterResponse query(Word prefix, V symbol) { return SymbolFilterResponse.ACCEPT; } } - - @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { - throw new IllegalStateException("Not supported."); - } } diff --git a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java similarity index 76% rename from oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java rename to filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java index d82b486a3..cdc66ea99 100644 --- a/oracles/symbol-filters/src/main/java/de/learnlib/oracle/symbol_filters/StatisticsSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java @@ -1,9 +1,10 @@ -package de.learnlib.oracle.symbol_filters; +package de.learnlib.filter.symbol; +import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatsContainer; -import de.learnlib.symbol_filter.SymbolFilter; -import de.learnlib.symbol_filter.SymbolFilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; /** @@ -14,7 +15,7 @@ * @param * Type of the queried symbols */ -public abstract class StatisticsSymbolFilter implements SymbolFilter { +public abstract class StatisticsSymbolFilter implements MutableSymbolFilter { private final SymbolFilter delegate; private final StatsContainer stats; @@ -52,7 +53,11 @@ public SymbolFilterResponse query(Word prefix, V symbol) { } @Override - public void update(Word prefix, V symbol, SymbolFilterResponse response) { - delegate.update(prefix, symbol, response); + public void accept(Word prefix, V symbol) { + if (delegate instanceof MutableSymbolFilter mut) { + mut.accept(prefix, symbol); + } else { + throw new UnsupportedOperationException("delegate filter does not support updates"); + } } } diff --git a/oracles/symbol-filters/src/main/java/module-info.java b/filters/symbol-filters/src/main/java/module-info.java similarity index 89% rename from oracles/symbol-filters/src/main/java/module-info.java rename to filters/symbol-filters/src/main/java/module-info.java index bddf05940..304049f02 100644 --- a/oracles/symbol-filters/src/main/java/module-info.java +++ b/filters/symbol-filters/src/main/java/module-info.java @@ -26,7 +26,7 @@ * </dependency> * */ -open module de.learnlib.oracle.symbol_filters { +open module de.learnlib.filter.symbol { // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static de.learnlib.tooling.annotation; @@ -34,6 +34,5 @@ requires de.learnlib.api; requires net.automatalib.api; - exports de.learnlib.oracle.symbol_filters; - exports de.learnlib.oracle.symbol_filters.mmlt; + exports de.learnlib.filter.symbol; } diff --git a/oracles/pom.xml b/oracles/pom.xml index 9fe0cb944..acc5a8e8f 100644 --- a/oracles/pom.xml +++ b/oracles/pom.xml @@ -37,6 +37,5 @@ limitations under the License. membership-oracles parallelism property-oracles - symbol-filters From 7d0af4fd88d7f74b065754a50dfa768845187512 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sat, 22 Nov 2025 01:52:06 +0100 Subject: [PATCH 40/55] adjust to AutomataLib refactorings --- .../algorithm/lstar/it/ExtensibleLStarMMLTIT.java | 6 +++--- .../de/learnlib/driver/simulator/MMLTSimulatorSUL.java | 2 +- .../oracle/equivalence/mmlt/RandomWpEQOracle.java | 4 ++-- .../oracle/equivalence/mmlt/ResetSearchEQOracle.java | 2 +- .../oracle/equivalence/mmlt/SimulatorEQOracle.java | 4 ++-- .../testsupport/it/learner/MMLTLearnerITCase.java | 8 ++++---- .../learnlib/testsupport/example/mmlt/MMLTExamples.java | 6 +++--- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 63d7c2a3d..7bfc40e40 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -31,7 +31,7 @@ import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; import org.testng.annotations.Test; @@ -134,10 +134,10 @@ public Example(String name, int maxTimerQueryWaiting) { var model = parser.readModel(is); var automaton = model.model; - long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); + long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(automaton); long maxTimerQueryWaitingFinal = (maxTimerQueryWaiting > 0) ? maxTimerQueryWaiting : - MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + MMLTs.getMaximumInitialTimerValue(automaton) * 2; this.mmlt = automaton; this.params = diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java index d0e6fbafe..09267c9bd 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java @@ -59,7 +59,7 @@ public TimedOutput step(InputSymbol input) { @Override public void pre() { - this.currentConfiguration = semantics.getInitialState().copy(); + this.currentConfiguration = semantics.getInitialState(); } @Override diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java index b1fd16093..c6ac1ada5 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java @@ -60,7 +60,7 @@ public RandomWpEQOracle(TimedQueryOracle timeOracle, private DefaultQuery, Word>> findCounterExampleInternal(MMLT hypothesis, Collection> inputs) { // Make expanded form of hypothesis: - var hypSemModel = ReducedMMLTSemantics.forLocalTimerMealy(hypothesis); + var hypSemModel = ReducedMMLTSemantics.forMMLT(hypothesis); // Create a list of symbols (for faster access): List> listAlphabet = new ArrayList<>(inputs); @@ -69,7 +69,7 @@ private DefaultQuery, Word>> findCounterExample var globalSuffixes = Automata.characterizingSet(hypSemModel, inputs); // Get list of prefixes in deterministic order (so we can reproduce experiments easily): - var locationCover = MMLTCover.getLocalTimerMealyLocationCover(hypothesis, listAlphabet); + var locationCover = MMLTCover.getMMLTLocationCover(hypothesis, listAlphabet); var prefixList = locationCover .values() .stream() diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index ab949d26a..67c586985 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -93,7 +93,7 @@ private List> getLoopingSymbols(S sourceLoc, List hypothesis, List> inputs) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: - var stateCover = MMLTCover.getLocalTimerMealyLocationCover(hypothesis, inputs); + var stateCover = MMLTCover.getMMLTLocationCover(hypothesis, inputs); // Only keep locations that have at least two stable configs (only these can have local resets): List>> prefixes = new ArrayList<>(); diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java index b675d48bd..9152fc0fc 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java @@ -5,7 +5,7 @@ import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -31,7 +31,7 @@ public SimulatorEQOracle(MMLT refModel) { public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { List> listInputs = new ArrayList<>(inputs); - var separatingWord = MMLTUtil.findSeparatingWord(refModel, hypothesis, listInputs); + var separatingWord = MMLTs.findSeparatingWord(refModel, hypothesis, listInputs); if (separatingWord != null) { var sulOutput = refModel.getSemantics().computeOutput(separatingWord); return new DefaultQuery<>(separatingWord, sulOutput); diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java index 9c5d311fd..9f27ba839 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/MMLTLearnerITCase.java @@ -20,7 +20,7 @@ import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; public class MMLTLearnerITCase @@ -42,8 +42,8 @@ protected boolean hasCanonicalModel() { @Override protected boolean testEquivalence(MMLT hypothesis) { - return MMLTUtil.testEquivalence(this.example.getReferenceAutomaton(), - hypothesis, - this.example.getReferenceAutomaton().getSemantics().getInputAlphabet()); + return MMLTs.testEquivalence(this.example.getReferenceAutomaton(), + hypothesis, + this.example.getReferenceAutomaton().getSemantics().getInputAlphabet()); } } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java index ac2e132f9..4fc10668e 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java @@ -9,7 +9,7 @@ import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; import net.automatalib.serialization.dot.DOTParsers; -import net.automatalib.util.automaton.mmlt.MMLTUtil; +import net.automatalib.util.automaton.mmlt.MMLTs; public class MMLTExamples { @@ -114,8 +114,8 @@ private Example(String name) { var model = parser.readModel(is); var automaton = model.model; - long maxTimeoutDelay = MMLTUtil.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = MMLTUtil.getMaximumInitialTimerValue(automaton) * 2; + long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(automaton); + long maxTimerQueryWaitingFinal = MMLTs.getMaximumInitialTimerValue(automaton) * 2; if (name.contains("SCTP")) { maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time From 0db864fab57323b1d861ff38a39a1edf722c57f4 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 23 Nov 2025 02:00:17 +0100 Subject: [PATCH 41/55] fix code-analysis remarks up until L* module --- .../de/learnlib/algorithm/adt/it/ADTIT.java | 4 +- .../lstar/mmlt/ExtensibleLStarMMLT.java | 35 ++-- .../lstar/mmlt/LocationTimerInfo.java | 27 +-- .../lstar/mmlt/MMLTHypDataContainer.java | 2 +- .../lstar/mmlt/MMLTObservationTable.java | 19 +- .../mmlt/cex/MMLTCounterexampleHandler.java | 13 +- .../cex/results/MissingOneShotResult.java | 8 +- .../mmlt/filter/MMLTPerfectSymbolFilter.java | 8 +- .../mmlt/filter/MMLTRandomSymbolFilter.java | 8 +- .../filter/MMLTStatisticsSymbolFilter.java | 12 +- .../mmlt/filter/MMLTSymbolFilterUtil.java | 8 +- ...xtensibleLStarMMLTCounterexampleTests.java | 148 +++++++------- .../lstar/it/ExtensibleLStarMMLTIT.java | 4 +- .../learnlib/algorithm/MMLTModelParams.java | 89 --------- .../de/learnlib/filter/FilterResponse.java | 30 +++ .../learnlib/filter/MutableSymbolFilter.java | 27 ++- .../java/de/learnlib/filter/SymbolFilter.java | 55 ++++-- .../learnlib/filter/SymbolFilterResponse.java | 6 - .../de/learnlib/oracle/EquivalenceOracle.java | 16 +- .../de/learnlib/oracle/TimedQueryOracle.java | 55 ++++-- .../statistic/DummyStatsContainer.java | 58 ------ .../de/learnlib/statistic/NoopCollector.java | 94 +++++++++ .../de/learnlib/statistic/Statistics.java | 47 ++++- .../statistic/StatisticsCollector.java | 181 ++++++++++++++++++ .../statistic/StatisticsProvider.java | 37 +++- .../de/learnlib/statistic/StatsContainer.java | 131 ------------- .../main/java/de/learnlib/sul/TimedSUL.java | 65 ++++--- .../de/learnlib/time/MMLTModelParams.java | 42 ++++ api/src/main/java/module-info.java | 1 + .../src/main/java/Example.java | 2 +- .../src/main/java/Example.java | 2 +- .../java/de/learnlib/util/Experiment.java | 22 +-- .../java/de/learnlib/util/ExperimentTest.java | 6 +- .../driver/simulator/MMLTSimulatorSUL.java | 36 +++- .../java/de/learnlib/example/Example1.java | 2 +- .../java/de/learnlib/example/Example2.java | 2 +- .../de/learnlib/example/mmlt/Example1.java | 24 +-- .../parallelism/ParallelismExample2.java | 4 +- .../example/resumable/ResumableExample.java | 2 +- .../de/learnlib/example/sli/Example2.java | 8 +- .../learnlib/filter/cache/LearningCache.java | 23 +-- .../filter/cache/mmlt/CacheTreeNode.java | 87 ++++----- .../cache/mmlt/MMLTCacheConsistencyTest.java | 108 +++++++---- .../filter/cache/mmlt/TimedSULTreeCache.java | 56 ++++-- .../filter/cache/mmlt/TimeoutReducerSUL.java | 21 +- .../filter/cache/AbstractCacheTest.java | 2 +- .../cache/AbstractParallelCacheTest.java | 2 +- .../cache/dfa/AbstractDFACacheTest.java | 2 +- .../filter/cache/dfa/DFAHashCacheTest.java | 2 +- .../cache/dfa/DFAParallelCacheTest.java | 2 +- .../cache/mealy/AbstractMealyCacheTest.java | 2 +- .../cache/mealy/AdaptiveQueryCacheTest.java | 2 +- .../cache/mealy/MealyParallelCacheTest.java | 2 +- .../filter/cache/mmlt/MMLTCacheTest.java | 39 ++-- .../cache/moore/AbstractMooreCacheTest.java | 2 +- .../cache/moore/MooreParallelCacheTest.java | 2 +- .../cache/sul/AbstractSULCacheTest.java | 2 +- .../cache/sul/SLISULParallelCacheTest.java | 2 +- .../cache/sul/SULParallelCacheTest.java | 2 +- .../sul/StateLocalInputSULTreeCacheTest.java | 14 +- .../container/AbstractStatistic.java | 64 +++++++ .../statistic/container/CounterStatistic.java | 27 ++- .../statistic/container/FlagStatistic.java | 25 ++- .../statistic/container/LearnerStatistic.java | 36 ---- .../container/MapStatisticsCollector.java | 156 +++++++++++++++ .../container/MapStatsContainer.java | 165 ---------------- .../statistic/container/MapStatsProvider.java | 21 +- .../container/StopClockStatistic.java | 29 ++- .../statistic/container/TextStatistic.java | 25 ++- .../learner/RefinementCounterLearner.java | 17 +- .../oracle/CounterAdaptiveQueryOracle.java | 26 +-- .../statistic/oracle/CounterEQOracle.java | 38 +++- .../statistic/oracle/CounterOracle.java | 28 +-- .../statistic/sul/CounterObservableSUL.java | 12 +- .../filter/statistic/sul/CounterSUL.java | 26 +-- .../sul/CounterStateLocalInputSUL.java | 16 +- .../filter/statistic/sul/CounterTimedSUL.java | 53 ++--- .../oracle/CounterAdaptiveOracleTest.java | 14 +- .../statistic/oracle/CounterOracleTest.java | 14 +- .../statistic/sul/AbstractCounterSULTest.java | 14 +- .../sul/ResetCounterObservableSULTest.java | 6 +- .../statistic/sul/ResetCounterSULTest.java | 6 +- .../ResetCounterStateLocalInputSULTest.java | 6 +- .../sul/SymbolCounterObservableSULTest.java | 6 +- .../statistic/sul/SymbolCounterSULTest.java | 6 +- .../SymbolCounterStateLocalInputSULTest.java | 6 +- .../src/test/resources/histogram_details.txt | 3 - .../src/test/resources/histogram_summary.txt | 1 - filters/symbol-filters/pom.xml | 17 +- .../symbol/AbstractPerfectSymbolFilter.java | 41 ++++ .../symbol/AbstractRandomSymbolFilter.java | 70 +++++++ .../AbstractStatisticsSymbolFilter.java | 83 ++++++++ .../symbol/AbstractTruthfulSymbolFilter.java | 46 +++++ .../filter/symbol/AcceptAllSymbolFilter.java | 29 ++- .../filter/symbol/CachedSymbolFilter.java | 43 +++-- .../filter/symbol/IgnoreAllSymbolFilter.java | 28 ++- .../filter/symbol/PerfectSymbolFilter.java | 27 --- .../filter/symbol/RandomSymbolFilter.java | 47 ----- .../filter/symbol/StatisticsSymbolFilter.java | 63 ------ .../src/main/java/module-info.java | 5 +- .../equivalence/mmlt/RandomWpEQOracle.java | 82 +++++--- .../equivalence/mmlt/ResetSearchEQOracle.java | 128 ++++++++----- .../equivalence/mmlt/SimulatorEQOracle.java | 37 ++-- .../oracle/membership/TimedSULOracle.java | 131 ++++++++----- test-support/learner-it-support/pom.xml | 4 - .../learner/AbstractLearnerVariantITCase.java | 3 +- .../it/learner/AbstractMMLTLearnerIT.java | 4 +- .../src/main/java/module-info.java | 1 - .../example/DefaultLearningExample.java | 2 +- .../testsupport/example/LearningExample.java | 3 +- .../testsupport/example/LearningExamples.java | 12 +- .../example/mmlt/MMLTExamples.java | 92 ++++++--- 112 files changed, 2135 insertions(+), 1423 deletions(-) delete mode 100644 api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java create mode 100644 api/src/main/java/de/learnlib/filter/FilterResponse.java delete mode 100644 api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java delete mode 100644 api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java create mode 100644 api/src/main/java/de/learnlib/statistic/NoopCollector.java create mode 100644 api/src/main/java/de/learnlib/statistic/StatisticsCollector.java delete mode 100644 api/src/main/java/de/learnlib/statistic/StatsContainer.java create mode 100644 api/src/main/java/de/learnlib/time/MMLTModelParams.java create mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/container/AbstractStatistic.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java create mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java delete mode 100644 filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java delete mode 100644 filters/statistics/src/test/resources/histogram_details.txt delete mode 100644 filters/statistics/src/test/resources/histogram_summary.txt create mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractPerfectSymbolFilter.java create mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractRandomSymbolFilter.java create mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractStatisticsSymbolFilter.java create mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractTruthfulSymbolFilter.java delete mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java delete mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java delete mode 100644 filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java diff --git a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java index 29f0163f3..0ef688b11 100644 --- a/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java +++ b/algorithms/active/adt/src/test/java/de/learnlib/algorithm/adt/it/ADTIT.java @@ -131,7 +131,7 @@ public void testIssue137() throws IOException, FormatException { for (int seed = 0; seed < 50; seed++) { long last = 0; for (int iter = 0; iter < 5; iter++) { - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); final CounterAdaptiveQueryOracle counter = new CounterAdaptiveQueryOracle<>(aqo); final ADTLearner learner = new ADTLearner<>(alphabet, @@ -154,7 +154,7 @@ public void testIssue137() throws IOException, FormatException { exp.run(); final long count = - Statistics.getContainer().getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L); + Statistics.getCollector().getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L); if (iter == 0) { last = count; 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 35dc69116..4877e7949 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 @@ -1,6 +1,5 @@ package de.learnlib.algorithm.lstar.mmlt; -import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -9,7 +8,7 @@ import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; -import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; import de.learnlib.algorithm.lstar.closing.ClosingStrategy; import de.learnlib.algorithm.lstar.mmlt.cex.MMLTCounterexampleHandler; @@ -25,13 +24,13 @@ import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.common.util.HashUtil; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; @@ -51,7 +50,7 @@ public class ExtensibleLStarMMLT implements OTLearner, TimedInput, Word>> { private static final Logger logger = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); - private final StatsContainer stats; + private final StatisticsCollector stats; private final ClosingStrategy, ? super Word>> closingStrategy; @@ -107,7 +106,7 @@ public ExtensibleLStarMMLT(Alphabet alphabet, this.closingStrategy = closingStrategy; this.timeOracle = timeOracle; this.initialSuffixes = initialSuffixes; - this.stats = Statistics.getContainer(); + this.stats = Statistics.getCollector(); // Prepare hyp data: @@ -134,15 +133,14 @@ public ExtensibleLStarMMLT(Alphabet alphabet, * @param Output type * @return New one-shot timer */ - public static MealyTimerInfo selectOneShotTimer(List> sortedTimers, long maxInitialValue) { + public static int selectOneShotTimer(List> sortedTimers, long maxInitialValue) { // Filter relevant timers: // Start at timer with the highest initial value. // Ignore all timers whose initial value exceeds the maximum value. // Also ignore timers whose timeout is the multiple of another timer's initial value. - List> relevantTimers = new ArrayList<>(); for (int i = sortedTimers.size() - 1; i >= 0; i--) { - MealyTimerInfo timer = sortedTimers.get(i); + TimerInfo timer = sortedTimers.get(i); if (timer.initial() > maxInitialValue) { continue; // could not have expired @@ -152,7 +150,7 @@ public static MealyTimerInfo selectOneShotTimer(List non-deterministic behavior! boolean multiple = false; for (int j = 0; j < i; j++) { - MealyTimerInfo otherTimer = sortedTimers.get(j); + TimerInfo otherTimer = sortedTimers.get(j); if (timer.initial() % otherTimer.initial() == 0) { multiple = true; break; @@ -162,15 +160,10 @@ public static MealyTimerInfo selectOneShotTimer(List first is last + throw new IllegalStateException("Max. initial value is too low; must include at least one timer."); } @@ -218,7 +211,7 @@ protected void updateOutputs() { TimedOutput output = null; if (inputSym instanceof TimeStepSequence ws) { // Query timer output from table: - MealyTimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); + TimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); if (timerInfo == null) { throw new AssertionError(); } @@ -361,7 +354,7 @@ private boolean refineHypothesisSingle(DefaultQuery, Word> spRow, MealyTimerInfo timeout) { + private void handleMissingTimeoutChange(Row> spRow, TimerInfo timeout) { var locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); if (locationTimerInfo == null) { throw new AssertionError("Location with missing one-shot timer must have timers."); @@ -386,8 +379,8 @@ private void handleMissingTimeoutChange(Row> spRow, MealyTimerInfo // Remove all timers with greater timeout (are now redundant): var subsequentTimers = locationTimerInfo.getSortedTimers().stream() - .filter(t -> t.initial() > timeout.initial()) - .map(MealyTimerInfo::name).toList(); + .filter(t -> t.initial() > timeout.initial()) + .map(TimerInfo::name).toList(); subsequentTimers.forEach(locationTimerInfo::removeTimer); // Change from periodic to one-shot: diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java index 6744c09ed..ee7dd3c85 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java @@ -1,7 +1,7 @@ package de.learnlib.algorithm.lstar.mmlt; import net.automatalib.symbol.time.TimedInput; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; @@ -21,10 +21,10 @@ public class LocationTimerInfo implements Serializable { private static final Logger logger = LoggerFactory.getLogger(LocationTimerInfo.class); - private final Map> timers; // name -> info + private final Map> timers; // name -> info // Keep a list of timers sorted by their initial value. This lets us avoid redundant sort operations. - private final List> sortedTimers; + private final List> sortedTimers; private final Word> prefix; @@ -44,10 +44,10 @@ public Word> getPrefix() { * Adds a local timer to this location. * */ - public void addTimer(MealyTimerInfo timer) { + public void addTimer(TimerInfo timer) { this.timers.put(timer.name(), timer); this.sortedTimers.add(timer); - this.sortedTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + this.sortedTimers.sort(Comparator.comparingLong(TimerInfo::initial)); } public void removeTimer(String timerName) { @@ -55,13 +55,13 @@ public void removeTimer(String timerName) { logger.warn("Attempted to remove an unknown timer."); return; } - MealyTimerInfo removedTimer = this.timers.remove(timerName); + TimerInfo removedTimer = this.timers.remove(timerName); this.sortedTimers.remove(removedTimer); } @Nullable - public MealyTimerInfo getTimerInfo(long initial) { - Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); + public TimerInfo getTimerInfo(long initial) { + Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); return timer.orElse(null); } @@ -72,7 +72,7 @@ public MealyTimerInfo getTimerInfo(long initial) { * @return Timer with maximum timeout. Null, if no timers defined. */ @Nullable - public MealyTimerInfo getLastTimer() { + public TimerInfo getLastTimer() { if (this.timers.isEmpty()) { return null; } @@ -94,7 +94,10 @@ public void setOneShotTimer(String name) { throw new IllegalArgumentException("Only the timer with maximum timeout can be one-shot."); } - oneShotTimer.setOneShot(); + // update references + var newTimer = oneShotTimer.asOneShot(); + this.timers.put(name, newTimer); + this.sortedTimers.set(sortedTimers.size() - 1, newTimer); } /** @@ -102,7 +105,7 @@ public void setOneShotTimer(String name) { * * @return List of local timers. Empty, if none. */ - public List> getSortedTimers() { + public List> getSortedTimers() { return Collections.unmodifiableList(sortedTimers); } @@ -113,7 +116,7 @@ public List> getSortedTimers() { * @return Map of local timers. Empty, if none defined. */ @NonNull - public Map> getLocalTimers() { + public Map> getLocalTimers() { return Collections.unmodifiableMap(this.timers); } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java index 7355e6ada..2643f65eb 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm.lstar.mmlt; -import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.time.MMLTModelParams; import de.learnlib.datastructure.observationtable.Row; import net.automatalib.alphabet.Alphabet; import net.automatalib.symbol.time.TimedInput; 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 ee8e0296d..26fbee4fd 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 @@ -6,9 +6,9 @@ import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.oracle.MembershipOracle; -import de.learnlib.filter.SymbolFilterResponse; +import de.learnlib.filter.FilterResponse; import net.automatalib.alphabet.Alphabet; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; @@ -88,10 +88,11 @@ public MMLTObservationTable(Alphabet> alphabet, long minTimerQuery */ private void identifyLocalTimers(LocationTimerInfo location, TimedQueryOracle timeOracle) { var timerQueryResponse = timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); + var timers = timerQueryResponse.timers(); if (timerQueryResponse.aborted()) { - var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(timerQueryResponse.timers(), Long.MAX_VALUE); - newOneShot.setOneShot(); + var end = ExtensibleLStarMMLT.selectOneShotTimer(timers, Long.MAX_VALUE); + timers.set(end, timers.get(end).asOneShot()); } // Add timers up to one-shot: @@ -185,20 +186,20 @@ private List>> createOutgoingTransitions(RowImpl) sym); - if (filterResponse == SymbolFilterResponse.IGNORE) { + if (filterResponse == FilterResponse.IGNORE) { // Verify that output is silent: var response = timeOracle.answerQuery(sp, Word.fromLetter(sym)); assert response.size() == 1; if (!response.firstSymbol().equals(silentOutput)) { // Not silent -> cannot be silent self-loop: - filterResponse = SymbolFilterResponse.ACCEPT; + filterResponse = FilterResponse.ACCEPT; // Update filter: this.symbolFilter.accept(sp, (InputSymbol) sym); } } - if (filterResponse == SymbolFilterResponse.ACCEPT) { + if (filterResponse == FilterResponse.ACCEPT) { // Treat as usual: succRow = this.createLpRow(lp); } @@ -497,7 +498,7 @@ public boolean isAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } - public @Nullable MealyTimerInfo getTimerInfo(Word> prefix, long initial) { + public @Nullable TimerInfo getTimerInfo(Word> prefix, long initial) { var info = this.timerInfoMap.get(prefix); if (info != null) { return info.getTimerInfo(initial); @@ -545,7 +546,7 @@ public List>>> addOutgoingTransition(Row> s return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, MealyTimerInfo timeout, TimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, TimerInfo timeout, TimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java index bb8dcdbaf..9475ff3c3 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java @@ -12,8 +12,8 @@ import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import de.learnlib.filter.FilterResponse; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -71,7 +71,7 @@ private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition handleIncorrectTarget(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { if (decomposition.input() instanceof InputSymbol ndi) { // If decomposition at non-delaying input + considered as self-loop, treat as false ignore: - if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == SymbolFilterResponse.IGNORE) { + if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == FilterResponse.IGNORE) { return new FalseIgnoreResult<>(decomposition.state().getLocation(), ndi); } @@ -85,15 +85,16 @@ private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition selectOneShotTimer(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis, long maxInitialValue) { - var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(hypothesis.getSortedTimers(decomposition.state().getLocation()), maxInitialValue); + List> timers = hypothesis.getSortedTimers(decomposition.state().getLocation()); + var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(timers, maxInitialValue); logger.debug("Missing one-shot: setting ({}|{}) to one-shot.", hypothesis.getLocationPrefix(decomposition.state()), newOneShot); - return new MissingOneShotResult<>(decomposition.state().getLocation(), newOneShot); + return new MissingOneShotResult<>(decomposition.state().getLocation(), timers.get(newOneShot)); } private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { // Check if there is a one-shot timer expiring at the next time step: - List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); + List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); assert !localTimers.isEmpty(); // If location has a one-shot timer, this is the one with the highest initial value: diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java index c8915d388..5884b2ff2 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -1,6 +1,6 @@ package de.learnlib.algorithm.lstar.mmlt.cex.results; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.TimerInfo; /** * The provided timer should become one-shot. @@ -10,9 +10,9 @@ */ public class MissingOneShotResult extends CexAnalysisResult { private final Integer location; - private final MealyTimerInfo timeout; + private final TimerInfo timeout; - public MissingOneShotResult(Integer location, MealyTimerInfo timeout) { + public MissingOneShotResult(Integer location, TimerInfo timeout) { this.location = location; this.timeout = timeout; } @@ -21,7 +21,7 @@ public Integer getLocation() { return location; } - public MealyTimerInfo getTimeout() { + public TimerInfo getTimeout() { return timeout; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java index 36b5b4601..4eb5a37a0 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.filter.symbol.PerfectSymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; +import de.learnlib.filter.symbol.AbstractPerfectSymbolFilter; +import de.learnlib.filter.FilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; @@ -15,7 +15,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MMLTPerfectSymbolFilter extends PerfectSymbolFilter, InputSymbol> { +public class MMLTPerfectSymbolFilter extends AbstractPerfectSymbolFilter, InputSymbol> { private final MMLT automaton; @@ -24,7 +24,7 @@ public MMLTPerfectSymbolFilter(MMLT automaton) { } @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { + protected FilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java index 3bf8c559e..020dcc0bf 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java @@ -1,8 +1,8 @@ package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.filter.symbol.RandomSymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; +import de.learnlib.filter.symbol.AbstractRandomSymbolFilter; +import de.learnlib.filter.FilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; @@ -16,7 +16,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class MMLTRandomSymbolFilter extends RandomSymbolFilter, InputSymbol> { +public class MMLTRandomSymbolFilter extends AbstractRandomSymbolFilter, InputSymbol> { private final MMLT automaton; @@ -28,7 +28,7 @@ public MMLTRandomSymbolFilter(MMLT automaton, @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { + protected FilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } } \ No newline at end of file diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java index d40bb95ff..c52368868 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java @@ -1,25 +1,25 @@ package de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.filter.symbol.StatisticsSymbolFilter; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.filter.symbol.AbstractStatisticsSymbolFilter; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; +import de.learnlib.filter.FilterResponse; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.word.Word; -public class MMLTStatisticsSymbolFilter extends StatisticsSymbolFilter, InputSymbol> { +public class MMLTStatisticsSymbolFilter extends AbstractStatisticsSymbolFilter, InputSymbol> { private final MMLT automaton; - public MMLTStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatsContainer stats) { + public MMLTStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatisticsCollector stats) { super(delegate); this.automaton = automaton; } @Override - protected SymbolFilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { + protected FilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java index 52d27ac7a..54c55a74c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java @@ -2,7 +2,7 @@ import java.util.Objects; -import de.learnlib.filter.SymbolFilterResponse; +import de.learnlib.filter.FilterResponse; import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; @@ -22,11 +22,11 @@ class MMLTSymbolFilterUtil { * @param Output symbol type * @return IGNORE for silent self-loops, ACCEPT otherwise. */ - static SymbolFilterResponse isIgnorable(MMLT automaton, Word> prefix, InputSymbol symbol) { + static FilterResponse isIgnorable(MMLT automaton, Word> prefix, InputSymbol symbol) { return isIgnorable(automaton.getSemantics(), prefix, symbol); } - static SymbolFilterResponse isIgnorable(MMLTSemantics semantics, Word> prefix, InputSymbol symbol) { + static FilterResponse isIgnorable(MMLTSemantics semantics, Word> prefix, InputSymbol symbol) { var targetConfig = semantics.getState(prefix); var trans = semantics.getTransition(targetConfig, symbol); var target = semantics.getSuccessor(trans); @@ -34,6 +34,6 @@ static SymbolFilterResponse isIgnorable(MMLTSemantics s boolean ignorable = Objects.equals(output, semantics.getSilentOutput()) && Objects.equals(targetConfig, target); - return ignorable ? SymbolFilterResponse.IGNORE : SymbolFilterResponse.ACCEPT; + return ignorable ? FilterResponse.IGNORE : FilterResponse.ACCEPT; } } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index cd017aaf1..d6269aaf7 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -3,14 +3,14 @@ import java.util.Collections; import java.util.List; +import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT.Example; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.query.DefaultQuery; -import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.example.mmlt.MMLTExamples; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.InputSymbol; @@ -26,14 +26,15 @@ @Test public class ExtensibleLStarMMLTCounterexampleTests { - private static void learnModel(MMLTLearningExample example, + private static void learnModel(MMLT example, + MMLTModelParams params, List>> counterexamples) { - var sul = new MMLTSimulatorSUL<>(example.getReferenceAutomaton().getSemantics()); - var timeOracle = new TimedSULOracle<>(sul, example.getParams()); + var sul = new MMLTSimulatorSUL<>(example.getSemantics()); + var timeOracle = new TimedSULOracle<>(sul, params); - var learner = new ExtensibleLStarMMLT<>(example.getReferenceAutomaton().getInputAlphabet(), - example.getParams(), + var learner = new ExtensibleLStarMMLT<>(example.getInputAlphabet(), + params, Collections.emptyList(), timeOracle, new AcceptAllSymbolFilter<>()); @@ -46,7 +47,7 @@ private static void learnModel(MMLTLearningExample example, } // Now continue until arriving at an accurate model: - SimulatorEQOracle simOracle = new SimulatorEQOracle<>(example.getReferenceAutomaton()); + SimulatorEQOracle simOracle = new SimulatorEQOracle<>(example); DefaultQuery, Word>> cex; MMLT hyp = learner.getHypothesisModel(); @@ -63,11 +64,10 @@ public void testOverApproxReset() { var model = new Example("over_approx_reset.dot"); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - Word.fromSymbols(TimedInput.step(), new InputSymbol<>("i"), new TimeoutSymbol<>()) - ); + List>> cex1 = + List.of(Word.fromSymbols(TimedInput.step(), new InputSymbol<>("i"), new TimeoutSymbol<>())); - learnModel(model, cex1); + learnModel(model.getReferenceAutomaton(), model.getParams(), cex1); } @Test @@ -76,106 +76,108 @@ public void testRecursiveDecomp() { var model = new Example("recursive_decomp.dot", 3); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - Word.upcast(TimedInput.inputs("p", "f")), - Word.fromWords(TimedInput.inputs("u"), TimedInput.timeouts(4), TimedInput.inputs("f")), - Word.fromWords(TimedInput.inputs("u"), TimedInput.timeouts(5)) - ); + List>> cex1 = List.of(Word.upcast(TimedInput.inputs("p", "f")), + Word.fromWords(TimedInput.inputs("u"), + TimedInput.timeouts(4), + TimedInput.inputs("f")), + Word.fromWords(TimedInput.inputs("u"), TimedInput.timeouts(5))); - learnModel(model, cex1); + learnModel(model.getReferenceAutomaton(), model.getParams(), cex1); } @Test public void testMissingDiscriminators() { - var model = MMLTExamples.SensorCollector(); + var model = MMLTExamples.sensorCollector(); // Missing discriminator at non-del in stable config: - List>> cex1 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromSymbols(TimedInput.input("p2"), TimedInput.step(), TimedInput.input("abort"), TimedInput.timeout()) - ); + List>> cex1 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromSymbols(TimedInput.input("p2"), + TimedInput.step(), + TimedInput.input("abort"), + TimedInput.timeout())); // Missing discriminator at one-shot: - List>> cex2 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2)) - ); + List>> cex2 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2))); - learnModel(model, cex1); - learnModel(model, cex2); + learnModel(model.getReferenceAutomaton(), model.getParams(), cex1); + learnModel(model.getReferenceAutomaton(), model.getParams(), cex2); } @Test public void testMissingResets() { - var model = MMLTExamples.SensorCollector(); - model.getParams().setMaxTimerQueryWaitingTime(40); + var model = MMLTExamples.sensorCollector(); + var p = model.getParams(); + var params = new MMLTModelParams<>(p.silentOutput(), p.outputCombiner(), p.maxTimeoutWaitingTime(), 40); // Missing reset in stable config: - List>> cex1 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromSymbols(TimedInput.input("p1"), TimedInput.step(), TimedInput.input("abort"), TimedInput.timeout()) - ); + List>> cex1 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromSymbols(TimedInput.input("p1"), + TimedInput.step(), + TimedInput.input("abort"), + TimedInput.timeout())); // Missing reset in non-stable config: - List>> cex2 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p1"), TimedInput.steps(3), TimedInput.inputs("abort"), TimedInput.timeouts(1)) - ); + List>> cex2 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), + TimedInput.steps(3), + TimedInput.inputs("abort"), + TimedInput.timeouts(1))); - learnModel(model, cex1); - learnModel(model, cex2); + learnModel(model.getReferenceAutomaton(), params, cex1); + learnModel(model.getReferenceAutomaton(), params, cex2); } @Test public void testMissingOneShotModelB() { // Setting max waiting = 6 -> all inferred timers are periodic: - var model = MMLTExamples.SensorCollector(); - model.getParams().setMaxTimerQueryWaitingTime(6); + var model = MMLTExamples.sensorCollector(); + var p = model.getParams(); + var params = new MMLTModelParams<>(p.silentOutput(), p.outputCombiner(), p.maxTimeoutWaitingTime(), 6); // Missing one-shot via bad return to entry: - List>> cex1 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14)) - ); + List>> cex1 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14))); // Missing one-shot in location with single timer: - List>> cex2 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2)) - ); + List>> cex2 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p2"), TimedInput.timeouts(2))); - learnModel(model, cex1); - learnModel(model, cex2); + learnModel(model.getReferenceAutomaton(), params, cex1); + learnModel(model.getReferenceAutomaton(), params, cex2); } @Test public void testMissingOneShotModelA() { - var model = MMLTExamples.SensorCollector(); - model.getParams().setMaxTimerQueryWaitingTime(40); + var model = MMLTExamples.sensorCollector(); + var p = model.getParams(); + var params = new MMLTModelParams<>(p.silentOutput(), p.outputCombiner(), p.maxTimeoutWaitingTime(), 40); // Missing one-shot via bad output: - List>> cex1 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p1"), TimedInput.steps(40), TimedInput.timeouts(1)) // alternatively: new InputSymbol<>("abort") + List>> cex1 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), + TimedInput.steps(40), + TimedInput.timeouts(1)) + // alternatively: new InputSymbol<>("abort") ); // Missing one-shot via bad target: - List>> cex2 = List.of( - Word.upcast(TimedInput.inputs("p1", "p1")), - Word.upcast(TimedInput.inputs("p2", "abort")), - Word.fromWords(TimedInput.inputs("p1"), TimedInput.timeouts(14), TimedInput.inputs("collect", "p1")) - ); - - learnModel(model, cex1); - learnModel(model, cex2); + List>> cex2 = List.of(Word.upcast(TimedInput.inputs("p1", "p1")), + Word.upcast(TimedInput.inputs("p2", "abort")), + Word.fromWords(TimedInput.inputs("p1"), + TimedInput.timeouts(14), + TimedInput.inputs("collect", "p1"))); + + learnModel(model.getReferenceAutomaton(), params, cex1); + learnModel(model.getReferenceAutomaton(), params, cex2); } } diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 7bfc40e40..2ba5917d5 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -11,7 +11,7 @@ import java.util.Random; import java.util.stream.Stream; -import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.filter.symbol.AcceptAllSymbolFilter; @@ -141,7 +141,7 @@ public Example(String name, int maxTimerQueryWaiting) { this.mmlt = automaton; this.params = - new MMLTModelParams<>(silentOutput, maxTimeoutDelay, maxTimerQueryWaitingFinal, outputCombiner); + new MMLTModelParams<>(silentOutput, outputCombiner, maxTimeoutDelay, maxTimerQueryWaitingFinal); } catch (IOException | FormatException e) { throw new RuntimeException("Unable to load model " + name, e); } diff --git a/api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java b/api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java deleted file mode 100644 index 4a70351f0..000000000 --- a/api/src/main/java/de/learnlib/algorithm/MMLTModelParams.java +++ /dev/null @@ -1,89 +0,0 @@ -package de.learnlib.algorithm; - -import net.automatalib.automaton.mmlt.SymbolCombiner; - -import java.util.Objects; - -/** - * Model-specific parameters for the MMLT-learner. - * These are used by various filters, oracles, and the MMLT simulator. - * - * @param Output symbol type - */ -public final class MMLTModelParams { - private final O silentOutput; - private final SymbolCombiner outputCombiner; - private final long maxTimeoutWaitingTime; - private long maxTimerQueryWaitingTime; - - /** - * @param silentOutput Silent output symbol - * @param maxTimeoutWaitingTime Maximum time to wait for a timeout in any configuration. - * If no timeout is observed after this time, the learner assumes that no timers are active. - * Hence, if this value is set too low, the learner will miss timeouts. This usually results in an - * incomplete model but can also trigger exceptions due to unsatisfied assumptions. - * @param maxTimerQueryWaitingTime Maximum waiting time to wait when inferring timers for a location. - * This must be at least the max. time for a timeout. - * We recommend setting this value to at least twice the highest value of any timer - * in the SUL, if these values are known or can be estimated. - * This increases the likelihood of detecting - * non-periodic behavior during timer inference, and thus reduces - * the need for equivalence queries. - * @param outputCombiner Function for combining simultaneously occurring outputs of timers - */ - public MMLTModelParams(O silentOutput, - long maxTimeoutWaitingTime, - long maxTimerQueryWaitingTime, - SymbolCombiner outputCombiner) { - this.silentOutput = silentOutput; - this.maxTimeoutWaitingTime = maxTimeoutWaitingTime; - this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; - this.outputCombiner = outputCombiner; - } - - public O silentOutput() { - return silentOutput; - } - - public long maxTimeoutWaitingTime() { - return maxTimeoutWaitingTime; - } - - public long maxTimerQueryWaitingTime() { - return maxTimerQueryWaitingTime; - } - - public SymbolCombiner outputCombiner() { - return outputCombiner; - } - - public void setMaxTimerQueryWaitingTime(long maxTimerQueryWaitingTime) { - this.maxTimerQueryWaitingTime = maxTimerQueryWaitingTime; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (MMLTModelParams) obj; - return Objects.equals(this.silentOutput, that.silentOutput) && - this.maxTimeoutWaitingTime == that.maxTimeoutWaitingTime && - this.maxTimerQueryWaitingTime == that.maxTimerQueryWaitingTime && - Objects.equals(this.outputCombiner, that.outputCombiner); - } - - @Override - public int hashCode() { - return Objects.hash(silentOutput, maxTimeoutWaitingTime, maxTimerQueryWaitingTime, outputCombiner); - } - - @Override - public String toString() { - return "LocalTimerMealyModelParams[" + - "silentOutput=" + silentOutput + ", " + - "maxTimeoutWaitingTime=" + maxTimeoutWaitingTime + ", " + - "maxTimerQueryWaitingTime=" + maxTimerQueryWaitingTime + ", " + - "outputCombiner=" + outputCombiner + ']'; - } - -} diff --git a/api/src/main/java/de/learnlib/filter/FilterResponse.java b/api/src/main/java/de/learnlib/filter/FilterResponse.java new file mode 100644 index 000000000..a811a7695 --- /dev/null +++ b/api/src/main/java/de/learnlib/filter/FilterResponse.java @@ -0,0 +1,30 @@ +/* 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 de.learnlib.filter; + +/** + * Describes the possible responses of a {@link SymbolFilter}. + */ +public enum FilterResponse { + /** + * Indicates that a transition is relevant and should be included in, e.g., hypothesis construction. + */ + ACCEPT, + /** + * Indicates that a transition can be ignored for, e.g., hypothesis construction. + */ + IGNORE +} diff --git a/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java b/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java index ef404ef78..15e631698 100644 --- a/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java +++ b/api/src/main/java/de/learnlib/filter/MutableSymbolFilter.java @@ -1,15 +1,30 @@ +/* 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 de.learnlib.filter; import net.automatalib.word.Word; /** - * Interface for a symbol filter. - * A symbol filter predicts whether a given transition is ignorable in a given state. - * This information can be used to avoid redundant queries. - * A symbol filter may answer incorrectly. + * A mutable extension of a {@link SymbolFilter} that allows one to override the query behavior. In particular, + * previously ignored transitions can be marked as non-ignorable. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label */ public interface MutableSymbolFilter extends SymbolFilter { diff --git a/api/src/main/java/de/learnlib/filter/SymbolFilter.java b/api/src/main/java/de/learnlib/filter/SymbolFilter.java index 43f6c6095..47f7e9770 100644 --- a/api/src/main/java/de/learnlib/filter/SymbolFilter.java +++ b/api/src/main/java/de/learnlib/filter/SymbolFilter.java @@ -1,31 +1,50 @@ +/* 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 de.learnlib.filter; import net.automatalib.word.Word; /** - * Interface for a symbol filter. - * A symbol filter predicts whether a given transition is ignorable in a given state. - * This information can be used to avoid redundant queries. - * A symbol filter may answer incorrectly. + * A symbol filter allows one to incorporate additional external knowledge by predicting whether a given transition + * (identified by an input symbol) is ignorable in a given state (identified by an access sequence). Ignorable + * typically means that the symbol triggers a (silent) self-loop in the considered state. This information can be used + * by, e.g., learning algorithms to avoid posing redundant queries. + *

          + * Note that a symbol filter is not required to answer queries correctly. In particular, an initially ignored transition + * may turn out to be relevant to the system behavior. As a result, learners that want to support these kinds of + * semantics need to be able to handle the potentially resulting nondeterministic query behavior. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label */ +@FunctionalInterface public interface SymbolFilter { /** - * Predicts whether the provided symbol is ignorable in the state - * that is addressed by the given prefix. - *

          - * ignorable typically means that the symbol triggers a silent self-loop in the considered state. - * However, the semantics may vary depending on the concrete implementation. - *

          - * Predictions may not be correct, i.e., an accepted symbol may be actually ignorable and an ignored symbol - * may be actually not ignorable. + * Predicts whether the provided symbol is ignorable in the state that is addressed by the given prefix. + * + * @param prefix + * the prefix identifying the state + * @param symbol + * the input symbol identifying the transition * - * @param prefix State prefix. - * @param symbol Input of the queried transition. - * @return IGNORE if the symbol is considered ignorable, ACCEPT if it is not. + * @return {@link FilterResponse#IGNORE} if the symbol is considered ignorable, {@link FilterResponse#ACCEPT} + * otherwise */ - SymbolFilterResponse query(Word prefix, V symbol); + FilterResponse query(Word prefix, V symbol); } diff --git a/api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java b/api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java deleted file mode 100644 index 67fb8c20b..000000000 --- a/api/src/main/java/de/learnlib/filter/SymbolFilterResponse.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.learnlib.filter; - -public enum SymbolFilterResponse { - ACCEPT, - IGNORE -} diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index 4d976d563..cf832bcf4 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -18,12 +18,12 @@ import java.util.Collection; import de.learnlib.query.DefaultQuery; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -96,10 +96,14 @@ interface MealyEquivalenceOracle extends EquivalenceOracle extends EquivalenceOracle, I, Word> {} /** - * A specialization of the {@link EquivalenceOracle} interface for a Local Timer Mealy learning scenario. + * A specialization of the {@link EquivalenceOracle} interface for an {@link MMLT} learning scenario. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input type (of non-delaying inputs) + * @param + * output symbol type */ - interface MMLTEquivalenceOracle extends EquivalenceOracle, TimedInput, Word>>{} + @FunctionalInterface + interface MMLTEquivalenceOracle + extends EquivalenceOracle, TimedInput, Word>> {} } diff --git a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java index f0042b6e2..d0783d949 100644 --- a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java @@ -1,40 +1,61 @@ +/* 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 de.learnlib.oracle; import java.util.List; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import de.learnlib.oracle.MembershipOracle.MealyMembershipOracle; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; /** - * Type of oracle used by an MMLT learner. - *

          - * Like a traditional query oracle, this answers output queries. - * In addition, it infers timers by observing timeouts. + * An oracle for querying {@link TimedInput timed inputs} and timers by observing timeouts. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input type (of non-delaying inputs) + * @param + * output symbol type */ -public interface TimedQueryOracle extends MembershipOracle.MealyMembershipOracle, TimedOutput> { +public interface TimedQueryOracle extends MealyMembershipOracle, TimedOutput> { /** - * Observes and aggregates any timeouts that occur after providing the given input to the SUL. - * Stops when observing inconsistent behavior. + * Observes and aggregates any timeouts that occur after providing the given input to the SUL. Stops when observing + * inconsistent behavior. + * + * @param prefix + * input to give to the SUL. + * @param maxTotalWaitingTime + * maximum total time that is waited for timeouts. * - * @param prefix Input to give to the SUL. - * @param maxTotalWaitingTime Maximum total time that is waited for timeouts. - * @return Observed timeouts. Empty, if none. + * @return observed timeouts (may be empty) */ TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime); /** * Response for a timer query. * - * @param aborted True if query was aborted due to missing timeout. - * @param timers Identified timers - * @param Untimed output suffix type + * @param aborted + * {@code true} if query was aborted due to missing timeout, {@code false} otherwise. + * @param timers + * identified timers + * @param + * output symbol type */ - record TimerQueryResult(boolean aborted, List> timers) {} + record TimerQueryResult(boolean aborted, List> timers) {} } diff --git a/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java b/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java deleted file mode 100644 index a067d3fa4..000000000 --- a/api/src/main/java/de/learnlib/statistic/DummyStatsContainer.java +++ /dev/null @@ -1,58 +0,0 @@ -package de.learnlib.statistic; - -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.time.Duration; -import java.util.Optional; - -/** - * A dummy implementation of {@link StatsContainer} that does nothing. - */ -class DummyStatsContainer implements StatsContainer { - - @Override - public void addTextInfo(String id, @Nullable String description, String text) {} - - @Override - public Optional getTextValue(String id) { - return Optional.empty(); - } - - @Override - public void setFlag(String id, @Nullable String description, boolean value) {} - - @Override - public Optional getFlagValue(String id) { - return Optional.empty(); - } - - @Override - public void startOrResumeClock(String id, @Nullable String description) {} - - @Override - public void pauseClock(String id) {} - - @Override - public Optional getClockValue(String id) { - return Optional.empty(); - } - - @Override - public void increaseCounter(String id, @Nullable String description, long increment) {} - - @Override - public void setCounter(String id, @Nullable String description, long count) {} - - @Override - public Optional getCount(String id) { - return Optional.empty(); - } - - @Override - public void clear() {} - - @Override - public String printStats() { - return "Dummy container"; - } -} diff --git a/api/src/main/java/de/learnlib/statistic/NoopCollector.java b/api/src/main/java/de/learnlib/statistic/NoopCollector.java new file mode 100644 index 000000000..3b416a1fa --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/NoopCollector.java @@ -0,0 +1,94 @@ +/* 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 de.learnlib.statistic; + +import java.time.Duration; +import java.util.Collection; +import java.util.Collections; +import java.util.Optional; + +/** + * A no-op implementation of a {@link StatisticsCollector} that does nothing. + */ +class NoopCollector implements StatisticsCollector { + + @Override + public Collection getKeys() { + return Collections.emptyList(); + } + + @Override + public void clear() {} + + @Override + public void addText(String id, String description, String text) {} + + @Override + public Optional getText(String id) { + return Optional.empty(); + } + + @Override + public void setFlag(String id, String description, boolean value) {} + + @Override + public Optional getFlag(String id) { + return Optional.empty(); + } + + @Override + public void startOrResumeClock(String id, String description) {} + + @Override + public void pauseClock(String id) {} + + @Override + public Optional getClock(String id) { + return Optional.empty(); + } + + @Override + public void increaseCounter(String id, String description, long increment) {} + + @Override + public void setCounter(String id, String description, long count) {} + + @Override + public Optional getCount(String id) { + return Optional.empty(); + } + + @Override + public String printStats() { + return """ + ################################################ + This is a no-op collector. If you plan on + collecting statistics, make sure to provide a + StatisticsProvider service on the classpath. + + A default implementation can be found in the + statistics module of LearnLib which can be + included with the following Maven dependency: + + + de.learnlib + learnlib-statistics + ${version} + + ################################################ + """; + } +} diff --git a/api/src/main/java/de/learnlib/statistic/Statistics.java b/api/src/main/java/de/learnlib/statistic/Statistics.java index be9870216..228d02647 100644 --- a/api/src/main/java/de/learnlib/statistic/Statistics.java +++ b/api/src/main/java/de/learnlib/statistic/Statistics.java @@ -1,15 +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 de.learnlib.statistic; import java.util.ServiceLoader; -public class Statistics { +/** + * Factory for obtaining {@link StatisticsCollector}s. + */ +public final class Statistics { private static final StatisticsProvider PROVIDER; static { final ServiceLoader loader = ServiceLoader.load(StatisticsProvider.class); - StatisticsProvider bestProvider = new DummyProvider(); + StatisticsProvider bestProvider = new NoopProvider(); for (StatisticsProvider sp : loader) { if (sp.getPriority() > bestProvider.getPriority()) { bestProvider = sp; @@ -19,13 +37,25 @@ public class Statistics { PROVIDER = bestProvider; } - public static StatsContainer getContainer() { - return PROVIDER.getContainer(); + private Statistics() { + // prevent instantiation } - private static class DummyProvider implements StatisticsProvider { + /** + * Returns a {@link StatisticsCollector} for collecting statistics. Note that the returned instances should behave + * as "per-thread-singletons", i.e., within a thread, the same instance should be returned as to enable client-code + * to collect statistics over various invocations across different components. However, in a multi-threaded + * benchmark scenario, each thread should obtain its own copy. + * + * @return the collector + */ + public static StatisticsCollector getCollector() { + return PROVIDER.getCollector(); + } + + private static final class NoopProvider implements StatisticsProvider { - private static final StatsContainer CONTAINER = new DummyStatsContainer(); + private static final StatisticsCollector COLLECTOR = new NoopCollector(); @Override public int getPriority() { @@ -33,9 +63,8 @@ public int getPriority() { } @Override - public StatsContainer getContainer() { - return CONTAINER; + public StatisticsCollector getCollector() { + return COLLECTOR; } } - } diff --git a/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java b/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java new file mode 100644 index 000000000..930e5a101 --- /dev/null +++ b/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java @@ -0,0 +1,181 @@ +/* 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 de.learnlib.statistic; + +import java.time.Duration; +import java.util.Collection; +import java.util.Optional; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * A container that collects various statistics of different types. Individual measurements are identified via an + * {@code id} which can be optionally enhanced by a description for the purpose of pretty-printing. Implementations may + * allow for key collisions as a means to merge statistics from different components. + *

          + * Note that implementations of this interface should be thread-safe as instances of the same collector may be passed + * across multiple threads. + */ +public interface StatisticsCollector { + + // General + + /** + * Returns all registered keys of this collector. May be used when exporting data. + * + * @return the keys for which any data as been collected. + */ + Collection getKeys(); + + /** + * Clears the data of this collector. May be used when wanting to start a fresh data collection. + */ + void clear(); + + // Generic text + + /** + * Stores the provided text for the given id. + * + * @param id + * the id of the text + * @param description + * description of the data, e.g., "configuration" + * @param text + * the text to be stored + */ + void addText(String id, @Nullable String description, String text); + + /** + * Retrieves the text with the given id. + * + * @param id + * the id of the text + * + * @return the stored text, or {@link Optional#empty()} if no text for this id exists + */ + Optional getText(String id); + + // Boolean flags + + /** + * Stores the provided boolean for the given id. + * + * @param id + * the id of the flag + * @param description + * description of the boolean, e.g., "accurate" + * @param value + * the boolean value to be stored + */ + void setFlag(String id, @Nullable String description, boolean value); + + /** + * Retrieves the flag with the given id. + * + * @param id + * the id of the flag + * + * @return the stored boolean, or {@link Optional#empty()} if no boolean for this id exists + */ + Optional getFlag(String id); + + // Time + + /** + * Starts the clock with the given id. If there is already a clock with this id, it is resumed. + * + * @param id + * the id of the clock + * @param description + * description of the clock, e.g., "learning time" + */ + void startOrResumeClock(String id, @Nullable String description); + + /** + * Pauses the clock with the given id. If there is no clock with this id, nothing happens. + * + * @param id + * the id of the clock + */ + void pauseClock(String id); + + /** + * Returns the current value of the clock with the given id. + * + * @param id + * the id of the clock + * + * @return the current value of the clock, or {@link Optional#empty()} if no clock for this id exists + */ + Optional getClock(String id); + + // Counter + + /** + * Increases the counter with the given id. If no counter with this id exists, it is created. + * + * @param id + * the id of the counter + * @param description + * description of the counter, e.g., "number of rounds" + */ + default void increaseCounter(String id, @Nullable String description) { + increaseCounter(id, description, 1); + } + + /** + * Increases the counter with the given id by the provided increment. If no counter with this id exists, it is + * created and initialized with the provided increment. + * + * @param id + * the id of the counter + * @param description + * description of the counter, e.g., "number of rounds" + * @param increment + * the amount to increase the counter by (must be greater than zero) + */ + void increaseCounter(String id, String description, long increment); + + /** + * Sets the counter with the given id to the provided value. If no counter with this id exists, it is created. + * + * @param id + * the id of the counter + * @param description + * description of the counter, e.g., "number of rounds" + * @param count + * New value for the counter (must be greater than zero) + */ + void setCounter(String id, @Nullable String description, long count); + + /** + * Gets the value of the counter with the given id. + * + * @param id + * the id of the counter + * + * @return The value of the counter, or {@link Optional#empty()} if no counter for this id exists + */ + Optional getCount(String id); + + /** + * Returns a string-based representation of the collected statistics. + * + * @return a string-based representation of the collected statistics + */ + String printStats(); +} diff --git a/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java b/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java index 0d9abfd11..1b57da193 100644 --- a/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java +++ b/api/src/main/java/de/learnlib/statistic/StatisticsProvider.java @@ -1,8 +1,43 @@ +/* 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 de.learnlib.statistic; +import java.util.ServiceLoader; + +/** + * A statistics provider is means to register different implementations of {@link StatisticsCollector}s via + * {@link ServiceLoader service loading}. + */ public interface StatisticsProvider { + /** + * Returns the priority of this provider. In general, providers with a high priority should be preferred over + * providers with a low priority. + * + * @return the priority of this provider + */ int getPriority(); - StatsContainer getContainer(); + /** + * Returns the instance of the {@link StatisticsCollector}. Note that the returned instances should behave as + * "per-thread-singletons", i.e., within a thread, the same instance should be returned as to enable client-code to + * collect statistics over various invocations across different components. However, in a multi-threaded benchmark + * scenario, each thread should obtain its own copy. + * + * @return the statistics collector + */ + StatisticsCollector getCollector(); } diff --git a/api/src/main/java/de/learnlib/statistic/StatsContainer.java b/api/src/main/java/de/learnlib/statistic/StatsContainer.java deleted file mode 100644 index 02c8bbe50..000000000 --- a/api/src/main/java/de/learnlib/statistic/StatsContainer.java +++ /dev/null @@ -1,131 +0,0 @@ -package de.learnlib.statistic; - - -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.time.Duration; -import java.util.Optional; - -/** - * Interface for a container that stores various statistics during learning. - */ -public interface StatsContainer { - - // Generic text - - /** - * Stores the provided text for the given id - * and assigns the provided description. - * - * @param id Text id - * @param description Description of the data, e.g., "configuration" - * @param text The text to be stored - */ - void addTextInfo(String id, @Nullable String description, String text); - - /** - * Retrieves the text with the provided id. - * - * @param id Id of the text value - * @return The stored text, or empty if there is no text with this id. - */ - Optional getTextValue(String id); - - - // Boolean flags - - /** - * Stores the provided boolean for the given id - * and optionally assigns the provided description. - * - * @param id Flag id - * @param description Description of the boolean, e.g., "accurate" - * @param value The boolean value to be stored - */ - void setFlag(String id, @Nullable String description, boolean value); - - /** - * Retrieves the flag with the provided id. - * - * @param id Id of the boolean value - * @return The stored boolean, or empty, if no boolean with this id exists. - */ - Optional getFlagValue(String id); - - - // Time - - /** - * Starts the clock with the given id and optionally assigns the provided description. - * If there is already a clock with this id, it is resumed. - * - * @param id Clock id - * @param description Description of the clock, e.g., "learning time" - */ - void startOrResumeClock(String id, @Nullable String description); - - /** - * Pauses the clock with the given id. If there is no clock with this id, nothing happens. - * - * @param id Clock id - */ - void pauseClock(String id); - - /** - * Returns the current value of the clock with the given id. - * - * @param id Clock id - * @return The current value of the clock, or empty, if no clock with this id exists. - */ - Optional getClockValue(String id); - - - // Counter - - /** - * Increases the counter with the given id and optionally assigns the provided description. - * If no counter with this id exists, it is created. - * - * @param id Counter id - * @param description Description of the counter, e.g., "number of rounds" - */ - default void increaseCounter(String id, @Nullable String description) { - increaseCounter(id, description, 1); - } - - /** - * Increases the counter with the given id by the provided increment - * and optionally assigns the provided description. - * If no counter with this id exists, it is created and initialized with the provided increment. - * - * @param id Counter id - * @param description Description of the counter, e.g., "number of rounds" - * @param increment Amount to increase the counter by - */ - void increaseCounter(String id, @Nullable String description, long increment); - - /** - * Sets the counter with the given id to the provided value and optionally assigns the provided description. - * If no counter with this id exists, it is created. - * - * @param id Counter id - * @param description Description of the counter, e.g., "number of rounds" - * @param count New value for the counter. Must be greater than zero. - */ - void setCounter(String id, @Nullable String description, long count); - - /** - * Gets the value of the counter with the given id. Returns empty, if no counter with this id exists. - * - * @param id Counter id - * @return The value of the counter, or empty, if no counter with this id exists. - */ - Optional getCount(String id); - - - void clear(); - /** - * Prints all stored statistics. - */ - String printStats(); -} diff --git a/api/src/main/java/de/learnlib/sul/TimedSUL.java b/api/src/main/java/de/learnlib/sul/TimedSUL.java index 2f362894b..009406abf 100644 --- a/api/src/main/java/de/learnlib/sul/TimedSUL.java +++ b/api/src/main/java/de/learnlib/sul/TimedSUL.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.sul; import net.automatalib.symbol.time.InputSymbol; @@ -12,16 +27,19 @@ /** * Interface for a SUL with MMLT semantics. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type */ public interface TimedSUL extends SUL, TimedOutput> { /** - * Follows the provided input word, starting at the current system state. - * The input word must not contain timeout symbols. Otherwise, an error occurs. + * Follows the provided input word, starting at the current system state. The input word must not contain timeout + * symbols. Otherwise, an error occurs. * - * @param input Input suffix. + * @param input + * the input word */ default void follow(Word> input) { this.follow(input, -1); @@ -30,11 +48,13 @@ default void follow(Word> input) { /** * Follows the provided input word, starting at the current configuration. * - * @param input Input suffix. - * @param maxTimeout Max. waiting time to use for timeoutSymbols. + * @param input + * the input word + * @param maxTimeout + * maximum waiting time to use for {@link TimeoutSymbol}s. */ default void follow(Word> input, long maxTimeout) { - for (var s : input) { + for (TimedInput s : input) { if (s instanceof InputSymbol ndi) { this.step(ndi); } else if (s instanceof TimeStepSequence) { @@ -51,26 +71,25 @@ default void follow(Word> input, long maxTimeout) { } /** - * Waits until a timeout occurs or the provided time is reached. - *

          - * We may observe no timeout if either the waiting time is too small or if the active location - * has no timers. + * Waits until a timeout occurs or the provided time is reached. May observe no timeout if either the waiting time + * is too small or if the active location has no timers. * - * @param maxTime Maximum waiting time. - * @return Observed timer output with waiting time, or null, if no timeout was observed. + * @param maxTime + * maximum waiting time. + * + * @return observed timer output with waiting time, or {@code null} if no timeout was observed. */ - @Nullable - TimedOutput timeoutStep(long maxTime); + @Nullable TimedOutput timeoutStep(long maxTime); /** * Waits for one time unit and returns the observed output. * - * @return Null if no output occurred, timer output if at least one timer expired. - * The delay of this output is set to zero. + * @return {@code null} if no output occurred, or a timer output if at least one timer expired. The delay of this + * output is set to zero. */ - @Nullable - default TimedOutput timeStep() { - var res = this.timeoutStep(1); + + default @Nullable TimedOutput timeStep() { + TimedOutput res = this.timeoutStep(1); if (res != null) { return new TimedOutput<>(res.symbol()); } @@ -80,7 +99,9 @@ default TimedOutput timeStep() { /** * Waits for the specified time and returns all observed timeouts. * - * @param input Waiting time. + * @param input + * Waiting time. + * * @return Observed timeouts. Empty, if none. */ default Word> collectTimeouts(TimeStepSequence input) { diff --git a/api/src/main/java/de/learnlib/time/MMLTModelParams.java b/api/src/main/java/de/learnlib/time/MMLTModelParams.java new file mode 100644 index 000000000..2946a817e --- /dev/null +++ b/api/src/main/java/de/learnlib/time/MMLTModelParams.java @@ -0,0 +1,42 @@ +/* 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 de.learnlib.time; + +import net.automatalib.automaton.mmlt.SymbolCombiner; + +/** + * Model-specific parameters for MMLT-based learners. These are used by various filters, oracles, and the MMLT + * simulator. + * + * @param silentOutput + * Silent output symbol + * @param outputCombiner + * Function for combining simultaneously occurring outputs of timers + * @param maxTimeoutWaitingTime + * Maximum time to wait for a timeout in any configuration. If no timeout is observed after this time, the + * learner assumes that no timers are active. Hence, if this value is set too low, the learner will miss + * timeouts. This usually results in an incomplete model but can also trigger exceptions due to unsatisfied + * assumptions. + * @param maxTimerQueryWaitingTime + * Maximum waiting time to wait when inferring timers for a location. This must be at least the max. time for a + * timeout. We recommend setting this value to at least twice the highest value of any timer in the SUL, if + * these values are known or can be estimated. This increases the likelihood of detecting non-periodic behavior + * during timer inference, and thus reduces the need for equivalence queries. + * @param + * Output symbol type + */ +public record MMLTModelParams(O silentOutput, SymbolCombiner outputCombiner, long maxTimeoutWaitingTime, + long maxTimerQueryWaitingTime) {} diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index bca925741..73411f18b 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -46,6 +46,7 @@ exports de.learnlib.query; exports de.learnlib.statistic; exports de.learnlib.sul; + exports de.learnlib.time; uses StatisticsProvider; } diff --git a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java index ae0e6ce09..c1b71bae7 100644 --- a/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java +++ b/archetypes/basic/src/main/resources/archetype-resources/src/main/java/Example.java @@ -70,7 +70,7 @@ public static void main(String[] args) throws IOException { System.out.println("-------------------------------------------------------"); // learning statistics - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java index ae0e6ce09..c1b71bae7 100644 --- a/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java +++ b/archetypes/complete/src/main/resources/archetype-resources/src/main/java/Example.java @@ -70,7 +70,7 @@ public static void main(String[] args) throws IOException { System.out.println("-------------------------------------------------------"); // learning statistics - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/commons/util/src/main/java/de/learnlib/util/Experiment.java b/commons/util/src/main/java/de/learnlib/util/Experiment.java index 649dc3613..e61db2d98 100644 --- a/commons/util/src/main/java/de/learnlib/util/Experiment.java +++ b/commons/util/src/main/java/de/learnlib/util/Experiment.java @@ -20,7 +20,7 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.transducer.MealyMachine; @@ -44,14 +44,14 @@ public class Experiment { private static final Logger LOGGER = LoggerFactory.getLogger(Experiment.class); private final ExperimentImpl impl; - private final StatsContainer statistics; + private final StatisticsCollector statisticsCollector; private @Nullable A finalHypothesis; public Experiment(LearningAlgorithm learningAlgorithm, EquivalenceOracle equivalenceAlgorithm, Alphabet inputs) { this.impl = new ExperimentImpl<>(learningAlgorithm, equivalenceAlgorithm, inputs); - this.statistics = Statistics.getContainer(); + this.statisticsCollector = Statistics.getCollector(); } /** @@ -104,22 +104,22 @@ private final class ExperimentImpl { public A run() { rounds++; - statistics.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); + statisticsCollector.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); LOGGER.info(Category.PHASE, "Starting round {}", rounds); LOGGER.info(Category.PHASE, "Learning"); - statistics.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); + statisticsCollector.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); learningAlgorithm.startLearning(); - statistics.pauseClock(LEARNING_PROFILE_KEY); + statisticsCollector.pauseClock(LEARNING_PROFILE_KEY); while (true) { final A hyp = learningAlgorithm.getHypothesisModel(); LOGGER.info(Category.PHASE, "Searching for counterexample"); - statistics.startOrResumeClock(COUNTEREXAMPLE_PROFILE_KEY, "Duration of counterexample search"); + statisticsCollector.startOrResumeClock(COUNTEREXAMPLE_PROFILE_KEY, "Duration of counterexample search"); DefaultQuery ce = equivalenceAlgorithm.findCounterExample(hyp, inputs); - statistics.pauseClock(COUNTEREXAMPLE_PROFILE_KEY); + statisticsCollector.pauseClock(COUNTEREXAMPLE_PROFILE_KEY); if (ce == null) { return hyp; @@ -129,13 +129,13 @@ public A run() { // next round ... rounds++; - statistics.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); + statisticsCollector.increaseCounter(LEARNING_ROUNDS_KEY, "Number of learning rounds"); LOGGER.info(Category.PHASE, "Starting round {}", rounds); LOGGER.info(Category.PHASE, "Learning"); - statistics.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); + statisticsCollector.startOrResumeClock(LEARNING_PROFILE_KEY, "Duration of exploration"); final boolean refined = learningAlgorithm.refineHypothesis(ce); - statistics.pauseClock(LEARNING_PROFILE_KEY); + statisticsCollector.pauseClock(LEARNING_PROFILE_KEY); assert refined; } diff --git a/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java b/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java index a3631ecec..ad26e25f7 100644 --- a/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java +++ b/commons/util/src/test/java/de/learnlib/util/ExperimentTest.java @@ -22,7 +22,7 @@ import de.learnlib.oracle.EquivalenceOracle.DFAEquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.util.Experiment.DFAExperiment; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; @@ -52,10 +52,10 @@ public void testExperiment() { final MockUpLearner learner = new MockUpLearner<>(target, intermediateTarget); final DFAEquivalenceOracle eq = new MockUpOracle<>(intermediateTarget); - final StatsContainer statMock = Mockito.mock(StatsContainer.class); + final StatisticsCollector statMock = Mockito.mock(StatisticsCollector.class); try (MockedStatic statistics = Mockito.mockStatic(Statistics.class)) { - statistics.when(Statistics::getContainer).thenReturn(statMock); + statistics.when(Statistics::getCollector).thenReturn(statMock); DFAExperiment experiment = new DFAExperiment<>(learner, eq, alphabet); diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java index 09267c9bd..9cb2ea98f 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java @@ -1,6 +1,22 @@ +/* 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 de.learnlib.driver.simulator; import de.learnlib.sul.TimedSUL; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.mmlt.State; import net.automatalib.symbol.time.InputSymbol; @@ -8,13 +24,15 @@ import net.automatalib.symbol.time.TimeoutSymbol; import org.checkerframework.checker.nullness.qual.Nullable; - /** - * Simulates the extended semantics of an MMLT. + * Simulates the semantics of an {@link MMLT}. * - * @param Location type. - * @param Non-delaying input type. - * @param Output symbol type. + * @param + * location type. + * @param + * input symbol type (of non-delaying inputs). + * @param + * output symbol type. */ public class MMLTSimulatorSUL implements TimedSUL { @@ -27,14 +45,13 @@ public MMLTSimulatorSUL(MMLTSemantics semantics) { this.currentConfiguration = null; } - @Override public TimedOutput step(InputSymbol input) { if (this.currentConfiguration == null) { throw new IllegalStateException("Not initialized!"); } - var trans = this.semantics.getTransition(this.currentConfiguration, input); + T trans = this.semantics.getTransition(this.currentConfiguration, input); this.currentConfiguration = this.semantics.getSuccessor(trans); return this.semantics.getTransitionOutput(trans); } @@ -45,9 +62,9 @@ public TimedOutput step(InputSymbol input) { throw new IllegalStateException("Not initialized!"); } - var trans = this.semantics.getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); + T trans = this.semantics.getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); this.currentConfiguration = this.semantics.getSuccessor(trans); - var output = this.semantics.getTransitionOutput(trans); + TimedOutput output = this.semantics.getTransitionOutput(trans); if (output.equals(semantics.getSilentOutput())) { // No timeout observed: @@ -67,5 +84,4 @@ public void post() { this.currentConfiguration = null; } - } diff --git a/examples/src/main/java/de/learnlib/example/Example1.java b/examples/src/main/java/de/learnlib/example/Example1.java index 8b732eed9..dfe55cb3f 100644 --- a/examples/src/main/java/de/learnlib/example/Example1.java +++ b/examples/src/main/java/de/learnlib/example/Example1.java @@ -89,7 +89,7 @@ public static void main(String[] args) throws IOException { System.out.println("-------------------------------------------------------"); // learning statistics - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/examples/src/main/java/de/learnlib/example/Example2.java b/examples/src/main/java/de/learnlib/example/Example2.java index 30be3b6eb..a665382be 100644 --- a/examples/src/main/java/de/learnlib/example/Example2.java +++ b/examples/src/main/java/de/learnlib/example/Example2.java @@ -122,7 +122,7 @@ public static void main(String[] args) throws NoSuchMethodException, IOException System.out.println("-------------------------------------------------------"); // learning statistics - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); // model statistics System.out.println("States: " + result.size()); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 60814d4fb..3990d78c7 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -23,7 +23,7 @@ import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.testsupport.example.mmlt.MMLTExamples; import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.symbol.time.InputSymbol; @@ -40,12 +40,12 @@ public class Example1 { public static void main(String[] args) { - var model = MMLTExamples.SensorCollector(); + var model = MMLTExamples.sensorCollector(); // We first create a statistics container. // This container will store various statistical data during learning: - var stats = Statistics.getContainer(); - stats.addTextInfo("LocalTimerMealyModel", null, model.toString()); + var stats = Statistics.getCollector(); + stats.addText("LocalTimerMealyModel", null, model.toString()); stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); @@ -77,7 +77,7 @@ public static void main(String[] args) { suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // A symbol filter allows us to reduce queries by exploiting prior knowledge. - // For this example, we use a RandomSymbolFilter. This filter correctly predicts + // For this example, we use a AbstractRandomSymbolFilter. This filter correctly predicts // whether a transition silently self-loops with an accuracy of 90%: SymbolFilter, InputSymbol> filter = new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); @@ -102,31 +102,31 @@ public static void main(String[] args) { private static void runExperiment(ExtensibleLStarMMLT learner, MMLTEquivalenceOracle tester, - StatsContainer stats, int maxRounds) { - stats.startOrResumeClock("learningRt", "Processing time"); + StatisticsCollector statisticsCollector, int maxRounds) { + statisticsCollector.startOrResumeClock("learningRt", "Processing time"); learner.startLearning(); var hyp = learner.getHypothesisModel(); DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", "CEX queries"); + statisticsCollector.increaseCounter("roundCount", "CEX queries"); int roundCount = 1; while (cex != null && roundCount < maxRounds) { learner.refineHypothesis(cex); hyp = learner.getHypothesisModel(); cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - stats.increaseCounter("roundCount", null); + statisticsCollector.increaseCounter("roundCount", null); roundCount += 1; } - stats.pauseClock("learningRt"); + statisticsCollector.pauseClock("learningRt"); final var finalHypothesis = learner.getHypothesisModel(); // Add some more stats: - stats.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); + statisticsCollector.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); // Print final result + statistics: - System.out.println(stats.printStats()); + System.out.println(statisticsCollector.printStats()); new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); diff --git a/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java b/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java index 79498d53e..01888cd42 100644 --- a/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java +++ b/examples/src/main/java/de/learnlib/example/parallelism/ParallelismExample2.java @@ -113,7 +113,7 @@ private void runSingleCache() { // print results System.out.println("Single-threaded cache performance:"); answerQueries(cache); - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); parallelOracle.shutdownNow(); } @@ -145,7 +145,7 @@ private void runThreadSafeCache() { // print results System.out.println("Shared cache performance:"); answerQueries(parallelOracle); - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); parallelOracle.shutdownNow(); } diff --git a/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java b/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java index 9cddd763b..f70b7ff3b 100644 --- a/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java +++ b/examples/src/main/java/de/learnlib/example/resumable/ResumableExample.java @@ -122,7 +122,7 @@ private static T fromBytes(byte[] bytes) { private static void printStats(Setup setup) { System.out.println("Hypothesis size: " + setup.learner.getHypothesisModel().size()); - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); System.out.println(); } diff --git a/examples/src/main/java/de/learnlib/example/sli/Example2.java b/examples/src/main/java/de/learnlib/example/sli/Example2.java index 957903a0d..e3fcb5f71 100644 --- a/examples/src/main/java/de/learnlib/example/sli/Example2.java +++ b/examples/src/main/java/de/learnlib/example/sli/Example2.java @@ -83,7 +83,7 @@ public static void main(String[] args) { */ static void runSLILearner(boolean withCache) { - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); // setup SULs and counters final StateLocalInputSUL target = new StateLocalInputMealySimulatorSUL<>(TARGET); @@ -130,7 +130,7 @@ static void runSLILearner(boolean withCache) { System.out.println("State Local Input SUL" + (withCache ? ", with cache" : "")); System.out.println("-------------------------------------------------------"); - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); System.out.println("-------------------------------------------------------"); } @@ -140,7 +140,7 @@ static void runSLILearner(boolean withCache) { */ static void runNormalLearner(boolean withCache) { - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); // setup SULs and counters final SUL target = new MealySimulatorSUL<>(TARGET, UNDEFINED); @@ -186,7 +186,7 @@ static void runNormalLearner(boolean withCache) { System.out.println("Regular SUL" + (withCache ? ", with cache" : "")); System.out.println("-------------------------------------------------------"); - System.out.println(Statistics.getContainer().printStats()); + System.out.println(Statistics.getCollector().printStats()); System.out.println("-------------------------------------------------------"); } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java index b7093fca6..5c03a934b 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCache.java @@ -16,16 +16,14 @@ package de.learnlib.filter.cache; import de.learnlib.oracle.EquivalenceOracle; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; -import java.util.List; - /** * Interface for a cache used in automata learning. *

          @@ -93,16 +91,11 @@ interface MooreLearningCache extends LearningCache Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ - interface MMLTLearningCache extends LearningCache, TimedInput, Word>>{ - /** - * Lists all words that are currently in the cache. - * If a cached word is a prefix of another cached word, only the longer of them is returned. - * - * @return List of all stored words. - */ - List>> listAllWords(); - } + @FunctionalInterface + interface MMLTLearningCache extends LearningCache, TimedInput, Word>> {} } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index 013d76143..efa9f4e3b 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -1,43 +1,49 @@ +/* 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 de.learnlib.filter.cache.mmlt; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeStepSequence; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.util.Collections; import java.util.HashMap; import java.util.Map; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import org.checkerframework.checker.nullness.qual.Nullable; + /** - * A node in the tree cache used by the MMLT learner. - *

          - * A node has a parent and children for an arbitrary number of transitions - * with a non-delaying input. - * There is at most one timed transition. - * This transition has a sequence of time steps as input. + * A node in the {@link TimedSULTreeCache}. A node has a parent and children for an arbitrary number of transitions with + * a non-delaying input. There is at most one timed transition. This transition has a sequence of time steps as input. * The output is the output at the last time step in the sequence. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ class CacheTreeNode { - private record CacheTreeTransition(TimedOutput output, CacheTreeNode target) { - } - - @Nullable - private CacheTreeNode parent; + private @Nullable CacheTreeNode parent; private TimedInput parentInput; - private long timeout; - @Nullable - private CacheTreeTransition timeTransition; - + private @Nullable CacheTreeTransition timeTransition; private final Map, CacheTreeTransition> untimedChildren; - public CacheTreeNode(CacheTreeNode parent, TimedInput parentInput) { + CacheTreeNode(CacheTreeNode parent, TimedInput parentInput) { this.parent = parent; this.parentInput = parentInput; @@ -58,26 +64,8 @@ public CacheTreeNode addTimeChild(long timeout, TimedOutput output) { return newChild; } - // ------------------------------------------------------- - /** - * Infers the number of predecessor nodes, i.e., the level - * of this node. - * - * @return Number of predecessors. Zero if this is the cache root. - */ - public int getNumPredecessors() { - int parentCount = 0; - var current = this; - - while (current.getParent() != null) { - parentCount++; - current = current.getParent(); - } - return parentCount; - } - public boolean hasTimeChild() { return this.timeTransition != null; } @@ -104,11 +92,14 @@ public CacheTreeNode getTimeoutChild() { } /** - * Breaks the time sequence: introduces a new child cx after the given number of time steps - * and adds the former child as child to cx. + * Breaks the time sequence: introduces a new child cx after the given number of time steps and adds the former + * child as child to cx. + * + * @param newTimeout + * Time at which the timeout sequence is split + * @param output + * Output at the end of the new time sequence * - * @param newTimeout Time at which the timeout sequence is split - * @param output Output at the end of the new time sequence * @return New child node */ public CacheTreeNode splitTimeout(long newTimeout, TimedOutput output) { @@ -167,4 +158,6 @@ public CacheTreeNode addUntimedChild(InputSymbol input, TimedOutput public Map, CacheTreeTransition> getUntimedChildren() { return Collections.unmodifiableMap(this.untimedChildren); } -} \ No newline at end of file + + public record CacheTreeTransition(TimedOutput output, CacheTreeNode target) {} +} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java index 23ee4372e..2b6594775 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java @@ -1,13 +1,35 @@ +/* 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 de.learnlib.filter.cache.mmlt; -import de.learnlib.algorithm.MMLTModelParams; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.query.DefaultQuery; +import de.learnlib.time.MMLTModelParams; import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; @@ -15,17 +37,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; - /** - * Searches for counterexamples by comparing the behavior of the hypothesis and the query cache. - * If there are multiple counterexamples, the shortest one is returned. + * Searches for counterexamples by comparing the behavior of the hypothesis and the query cache. If there are multiple + * counterexamples, the shortest one is returned. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ +@SuppressWarnings("PMD.TestClassWithoutTestCases") // not a traditional test class public class MMLTCacheConsistencyTest implements MMLTEquivalenceOracle { - private final static Logger logger = LoggerFactory.getLogger(MMLTCacheConsistencyTest.class); + + private static final Logger LOGGER = LoggerFactory.getLogger(MMLTCacheConsistencyTest.class); private final TimedSULTreeCache sulCache; private final MMLTModelParams modelParams; @@ -40,7 +64,7 @@ private DefaultQuery, Word>> queryCache(Word> wbOutput = new WordBuilder<>(); this.sulCache.pre(); - for (var sym : word) { + for (TimedInput sym : word) { if (sym instanceof InputSymbol ndi) { TimedOutput res = this.sulCache.step(ndi); wbInput.append(ndi); @@ -55,7 +79,7 @@ private DefaultQuery, Word>> queryCache(Word, Word>> queryCache(Word, Word>> convertTimeSequences(DefaultQuery, Word>> originalQuery) { @@ -75,27 +102,28 @@ private DefaultQuery, Word>> convertTimeSequences(D WordBuilder> wbOutput = new WordBuilder<>(); int symIdx = 0; - var queryInput = originalQuery.getInput(); - var queryOutput = originalQuery.getOutput(); + Word> queryInput = originalQuery.getInput(); + Word> queryOutput = originalQuery.getOutput(); while (symIdx < queryInput.length()) { - var inputSym = queryInput.getSymbol(symIdx); - var outputSym = queryOutput.getSymbol(symIdx); + TimedInput inputSym = queryInput.getSymbol(symIdx); + TimedOutput outputSym = queryOutput.getSymbol(symIdx); symIdx++; if (inputSym instanceof InputSymbol ds) { wbInput.append(ds); wbOutput.append(outputSym); } else if (inputSym instanceof TimeStepSequence ws) { - if (!outputSym.symbol().equals(this.modelParams.silentOutput()) || ws.timeSteps() == this.modelParams.maxTimeoutWaitingTime()) { + if (!outputSym.symbol().equals(this.modelParams.silentOutput()) || + ws.timeSteps() == this.modelParams.maxTimeoutWaitingTime()) { // Found a timeout OR no timeout after max_delay: wbInput.append(new TimeoutSymbol<>()); wbOutput.append(outputSym); continue; } - if (ws.timeSteps() >= this.modelParams.maxTimeoutWaitingTime()) { - throw new AssertionError("Wait time that exceeds max_delay in cache."); - } + + assert ws.timeSteps() < this.modelParams.maxTimeoutWaitingTime() : + "Wait time that exceeds max_delay in cache."; // Special case: silent output before max delay // Cannot replace with "timeout", as this implies wait until max_delay. @@ -103,15 +131,16 @@ private DefaultQuery, Word>> convertTimeSequences(D long combinedWaitTime = ws.timeSteps(); TimedOutput combinedOutput = outputSym; - while (combinedOutput.symbol().equals(this.modelParams.silentOutput()) && combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() - && symIdx < queryInput.length() && - queryInput.getSymbol(symIdx) instanceof TimeStepSequence nextWs) { + while (combinedOutput.symbol().equals(this.modelParams.silentOutput()) && + combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() && symIdx < queryInput.length() && + queryInput.getSymbol(symIdx) instanceof TimeStepSequence nextWs) { combinedWaitTime += nextWs.timeSteps(); combinedOutput = queryOutput.getSymbol(symIdx); symIdx++; } - if (combinedWaitTime >= this.modelParams.maxTimeoutWaitingTime() || !combinedOutput.symbol().equals(this.modelParams.silentOutput())) { + if (combinedWaitTime >= this.modelParams.maxTimeoutWaitingTime() || + !combinedOutput.symbol().equals(this.modelParams.silentOutput())) { wbInput.append(new TimeoutSymbol<>()); if (combinedOutput.symbol().equals(this.modelParams.silentOutput())) { @@ -124,7 +153,7 @@ private DefaultQuery, Word>> convertTimeSequences(D } else { // Reached end of word before max_delay OR non-wait symbol -> ignore rest of this word: if (symIdx < queryInput.length() - 1) { - logger.debug("Ignoring at least one symbol during cache comparison."); + LOGGER.debug("Ignoring at least one symbol during cache comparison."); } break; } @@ -133,23 +162,26 @@ private DefaultQuery, Word>> convertTimeSequences(D return new DefaultQuery<>(wbInput.toWord(), wbOutput.toWord()); } - private DefaultQuery, Word>> reduceToAllowedInputs(Set> allowedInputs, DefaultQuery, Word>> query) { + private DefaultQuery, Word>> reduceToAllowedInputs(Set> allowedInputs, + DefaultQuery, Word>> query) { // Find the longest prefix with allowed inputs: int prefixLength = 0; - while (prefixLength < query.getInput().length() && allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { + while (prefixLength < query.getInput().length() && + allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { prefixLength++; } if (prefixLength == query.getInput().length()) { return query; // maximum length -> no need to reduce } else { - return new DefaultQuery<>(query.getInput().subWord(0, prefixLength), query.getOutput().subWord(0, prefixLength)); + return new DefaultQuery<>(query.getInput().subWord(0, prefixLength), + query.getOutput().subWord(0, prefixLength)); } } - @Override - public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, + Collection> inputs) { Set> allowedInputs = new HashSet<>(inputs); boolean allInputsConsidered = allowedInputs.containsAll(hypothesis.getSemantics().getInputAlphabet()); @@ -157,19 +189,21 @@ private DefaultQuery, Word>> reduceToAllowedInputs( List>> cachedWords = this.sulCache.listAllWords(); List, Word>>> counterexamples = new ArrayList<>(); - for (var word : cachedWords) { + for (Word> word : cachedWords) { // First, query word as-is (may include wait-symbols in input): DefaultQuery, Word>> rawCacheQuery = this.queryCache(word); // Next, convert query that includes wait-symbols to query with timeout-symbols: - var convertedQuery = this.convertTimeSequences(rawCacheQuery); + DefaultQuery, Word>> convertedQuery = this.convertTimeSequences(rawCacheQuery); // The counterexample may only use a subset of the allowed inputs. // If so, cut the query to the prefix of the word that is allowed: - var reducedQuery = (allInputsConsidered) ? convertedQuery : this.reduceToAllowedInputs(allowedInputs, convertedQuery); + DefaultQuery, Word>> reducedQuery = + allInputsConsidered ? convertedQuery : this.reduceToAllowedInputs(allowedInputs, convertedQuery); // Finally, query hypothesis using the converted query: - Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); + Word> hypOutput = + hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); if (!hypOutput.equals(reducedQuery.getOutput())) { // Hyp gives different output than cache (= SUL): diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java index 32edba421..bb9be5a13 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.filter.cache.mmlt; import java.util.ArrayDeque; @@ -7,12 +22,12 @@ import java.util.List; import java.util.Map; -import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.filter.cache.LearningCache.MMLTLearningCache; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.TimedSUL; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.transducer.impl.CompactMealy; import net.automatalib.graph.Graph; @@ -29,9 +44,9 @@ * Caches queries sent to a LocalTimerMealySUL. * * @param - * Input type for non-delaying inputs + * input symbol type (of non-delaying inputs) * @param - * Output symbol type + * output symbol type */ public class TimedSULTreeCache implements TimedSUL, MMLTLearningCache, GraphViewable { @@ -44,13 +59,13 @@ public class TimedSULTreeCache implements TimedSUL, MMLTLearningCach private final TimedOutput silentOutput; private boolean cacheMiss; - private final StatsContainer stats; + private final StatisticsCollector statisticsCollector; public TimedSULTreeCache(TimedSUL delegate, MMLTModelParams modelParams) { this.delegate = delegate; this.modelParams = modelParams; this.silentOutput = new TimedOutput<>(modelParams.silentOutput()); - this.stats = Statistics.getContainer(); + this.statisticsCollector = Statistics.getCollector(); // Init cache: this.cacheRoot = new CacheTreeNode<>(null, null); @@ -62,7 +77,7 @@ private void followCurrentPrefix() { WordBuilder> wbPrefix = new WordBuilder<>(); - var current = this.currentState; + CacheTreeNode current = this.currentState; while (current.getParent() != null) { wbPrefix.append(current.getParentInput()); current = current.getParent(); @@ -156,9 +171,9 @@ public void post() { if (this.cacheMiss) { this.delegate.post(); - stats.increaseCounter("Cache_Missed_Count", "Cache misses"); + statisticsCollector.increaseCounter("Cache_Missed_Count", "Cache misses"); } else { - stats.increaseCounter("Cache_Hit_Count", "Cache hits"); + statisticsCollector.increaseCounter("Cache_Hit_Count", "Cache hits"); } } @@ -184,7 +199,7 @@ private List> getLeaves() { successors++; } - for (var sym : currentNode.getUntimedChildren().keySet()) { + for (InputSymbol sym : currentNode.getUntimedChildren().keySet()) { unvisited.add(currentNode.getChild(sym)); successors++; } @@ -196,19 +211,22 @@ private List> getLeaves() { return leaves; } - @Override + /** + * Lists all words that are currently in the cache. If a cached word is a prefix of another cached word, only + * the longer of them is returned. + * + * @return List of all stored words. + */ public List>> listAllWords() { List> leaves = this.getLeaves(); List>> finalWords = new ArrayList<>(leaves.size()); - for (var leaf : leaves) { - // Word builder capacity = number of predecessors: - int symCount = leaf.getNumPredecessors(); - WordBuilder> wbInput = new WordBuilder<>(symCount); + for (CacheTreeNode leaf : leaves) { + WordBuilder> wbInput = new WordBuilder<>(); // Move towards the root: - var current = leaf; + CacheTreeNode current = leaf; while (current.getParent() != null) { wbInput.append(current.getParentInput()); current = current.getParent(); @@ -237,7 +255,7 @@ public List>> listAllWords() { CacheTreeNode current = pending.remove(); if (current.hasTimeChild()) { - var child = current.getTimeoutChild(); + CacheTreeNode child = current.getTimeoutChild(); if (!stateMap.containsKey(child)) { stateMap.put(child, mealy.addState()); pending.add(child); @@ -249,8 +267,8 @@ public List>> listAllWords() { current.getTimeoutOutput()); } - for (var sym : current.getUntimedChildren().keySet()) { - var child = current.getChild(sym); + for (InputSymbol sym : current.getUntimedChildren().keySet()) { + CacheTreeNode child = current.getChild(sym); if (!stateMap.containsKey(child)) { stateMap.put(child, mealy.addState()); pending.add(child); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index b2f630198..6e931c47f 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.filter.cache.mmlt; import de.learnlib.sul.TimedSUL; @@ -14,9 +29,9 @@ * We may observe a timeout again after any non-delaying input, as this may trigger a location-change. * * @param - * Input type for non-delaying inputs + * input symbol type (of non-delaying inputs) * @param - * Output symbol type + * output symbol type */ public class TimeoutReducerSUL implements TimedSUL { @@ -45,7 +60,7 @@ public TimedOutput step(InputSymbol input) { return null; // cannot observe expiration until non-delaying input } - var result = delegate.timeoutStep(maxTime); + TimedOutput result = delegate.timeoutStep(maxTime); if (result == null) { this.noTimeoutWaitingTime += maxTime; diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java index c643149b4..6afccdc1d 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java @@ -51,7 +51,7 @@ public void setup() { alphabet = getAlphabet(); oracle = getCachedOracle(); queries = new ArrayList<>(); - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); } @Test diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java index e6b0a9c6a..b88129950 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractParallelCacheTest.java @@ -65,7 +65,7 @@ public void setUp() { this.targetModel = getTargetModel(); this.cache = getCacheRepresentative(); this.parallelOracle = getParallelOracle(); - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); } @AfterClass diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java index 8a3d2251b..29035da52 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java @@ -58,7 +58,7 @@ protected DFACacheOracle getResumedOracle(DFACacheOracle o @Override protected long getNumberOfPosedQueries() { - return Statistics.getContainer().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java index f22be17c9..25ab6cf13 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java @@ -56,7 +56,7 @@ protected DFAHashCacheOracle getResumedOracle(DFAHashCacheOracle getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java index ac7c36733..b39093065 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java @@ -63,7 +63,7 @@ protected MealyCacheOracle getResumedOracle(MealyCacheOracle @Override protected long getNumberOfPosedQueries() { - return Statistics.getContainer().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java index 4f76ab0b4..01dffda7d 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java @@ -84,7 +84,7 @@ protected Wrapper getResumedOracle(Wrapper> getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java index 3d783ad9e..e56f0e9f4 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java @@ -1,12 +1,27 @@ +/* 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 de.learnlib.filter.cache.mmlt; import java.util.ArrayList; import java.util.List; import java.util.Random; -import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.mmlt.impl.CompactMMLT; @@ -21,17 +36,16 @@ @Test public class MMLTCacheTest { + private CompactMMLT buildBaseModel() { var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); - var s0 = model.addState(); + var s0 = model.addInitialState(); var s1 = model.addState(); var s2 = model.addState(); var s3 = model.addState(); - model.setInitialState(s0); - model.addTransition(s0, "p1", s1, "go"); model.addTransition(s1, "abort", s1, "ok"); model.addLocalReset(s1, "abort"); @@ -56,14 +70,13 @@ public void testCacheAndSULConsistency() { Random random = new Random(100); var automaton = buildBaseModel(); - var params = new MMLTModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + var params = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); var sul = new MMLTSimulatorSUL<>(automaton.getSemantics()); var cacheSUL = new TimedSULTreeCache<>(sul, params); var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); var timeOracleWithoutCache = new TimedSULOracle<>(sul, params); - var listAlphabet = new ArrayList<>(automaton.getSemantics().getInputAlphabet()); // Generate some random words and compare outputs of the cache, SUL, and automaton: @@ -78,7 +91,9 @@ public void testCacheAndSULConsistency() { var sulOutput = timeOracleWithoutCache.answerQuery(word); var automatonOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), word); - Assert.assertEquals(sulOutput, automatonOutput, "Automaton output does not match SUL output for word " + word); + Assert.assertEquals(sulOutput, + automatonOutput, + "Automaton output does not match SUL output for word " + word); Assert.assertEquals(cacheOutput, sulOutput, "Cache output does not match SUL output for word " + word); } @@ -95,14 +110,15 @@ public void testCacheAndSULConsistency() { public void testCacheConsistencyTest() { // Test if the cache consistency test works correctly: var refAutomaton = buildBaseModel(); - var params = new MMLTModelParams<>("void", 4, 80, StringSymbolCombiner.getInstance()); + var params = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); var sul = new MMLTSimulatorSUL<>(refAutomaton.getSemantics()); var cacheSUL = new TimedSULTreeCache<>(sul, params); var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); // Add word to cache: - Word> testWord = Word.fromSymbols(TimedInput.input("p2"), TimedInput.timeout(), TimedInput.step(), TimedInput.timeout()); + Word> testWord = + Word.fromSymbols(TimedInput.input("p2"), TimedInput.timeout(), TimedInput.step(), TimedInput.timeout()); timeOracleWithCache.answerQuery(testWord); // Create a bad hypothesis: @@ -111,9 +127,8 @@ public void testCacheConsistencyTest() { badAutomaton.addPeriodicTimer(2, "d", 4, "done"); // Query the cache for a counterexample: - Word> expectedCex = Word.fromSymbols( - new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>() - ); + Word> expectedCex = + Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()); var cacheConsistencyTest = cacheSUL.createCacheConsistencyTest(); var cex = cacheConsistencyTest.findCounterExample(badAutomaton, refAutomaton.getSemantics().getInputAlphabet()); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java index f533099af..bac90c872 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java @@ -63,7 +63,7 @@ protected MooreCacheOracle getResumedOracle(MooreCacheOracle @Override protected long getNumberOfPosedQueries() { - return Statistics.getContainer().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); } @Override diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java index bad56f164..b4f432079 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/MooreParallelCacheTest.java @@ -75,6 +75,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); + return Statistics.getCollector().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java index 0a8b9414f..3caf9f226 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java @@ -61,7 +61,7 @@ protected SULLearningCacheOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(CounterSUL.RESET_KEY).orElse(0L); + return Statistics.getCollector().getCount(CounterSUL.RESET_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java index 0094147ff..4ee05dc3b 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/SULParallelCacheTest.java @@ -74,6 +74,6 @@ protected ParallelOracle> getParallelOracle() { @Override protected long getNumberOfQueries() { - return Statistics.getContainer().getCount(CounterSUL.RESET_KEY).orElse(0L); + return Statistics.getCollector().getCount(CounterSUL.RESET_KEY).orElse(0L); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java index 87d6d218e..41cb1c78b 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java @@ -42,14 +42,14 @@ public StateLocalInputSULTreeCacheTest() { @Override public void testNoQueriesReceived() { super.testNoQueriesReceived(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), 0); + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), 0); } @Test(dependsOnMethods = "testNoQueriesReceived") @Override public void testFirstQuery() { super.testFirstQuery(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), oracle.getCache().size()); } @@ -57,7 +57,7 @@ public void testFirstQuery() { @Override public void testFirstDuplicate() { super.testFirstDuplicate(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), oracle.getCache().size()); } @@ -65,7 +65,7 @@ public void testFirstDuplicate() { @Override public void testTwoQueriesOneDuplicate() { super.testTwoQueriesOneDuplicate(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), oracle.getCache().size()); } @@ -73,7 +73,7 @@ public void testTwoQueriesOneDuplicate() { @Override public void testOneNewQuery() { super.testOneNewQuery(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), oracle.getCache().size()); } @@ -81,7 +81,7 @@ public void testOneNewQuery() { @Override public void testPrefix() { super.testPrefix(); - Assert.assertEquals(Statistics.getContainer().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), + Assert.assertEquals(Statistics.getCollector().getCount(CounterStateLocalInputSUL.INPUT_KEY).orElse(0L), oracle.getCache().size()); } @@ -148,7 +148,7 @@ protected SULLearningCacheOracle. + * + * 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.filter.statistic.container; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Various types of statistical data to be stored in a StatisticsCollector. + */ +abstract class AbstractStatistic { + + private final String id; + private final @Nullable String description; + + /** + * Default constructor. + * + * @param id + * id of the statistic. Must be unique within the StatisticsCollector. + * @param description + * Optional description of the statistic. If no description is provided, the id is used. + */ + AbstractStatistic(String id, @Nullable String description) { + this.id = id; + + if (description == null) { + this.description = id; + } else { + this.description = description; + } + } + + public String getId() { + return id; + } + + public @Nullable String getDescription() { + return description; + } + + protected abstract String renderValue(); + + @Override + public String toString() { + if (description == null) { + return id + ": " + renderValue(); + } else { + return description + ": " + renderValue(); + } + } +} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java index d4bee83dc..0bdd44c4f 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java @@ -1,16 +1,32 @@ +/* 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 de.learnlib.filter.statistic.container; /** * A counter that can be increased and set to a particular positive number. */ -class CounterStatistic extends LearnerStatistic { +class CounterStatistic extends AbstractStatistic { + private long count; - public CounterStatistic(String id, String description) { + CounterStatistic(String id, String description) { this(id, description, 0); } - public CounterStatistic(String id, String description, long count) { + CounterStatistic(String id, String description, long count) { super(id, description); this.count = count; } @@ -29,4 +45,9 @@ public void increase(long increment) { public long getCount() { return count; } + + @Override + public String renderValue() { + return Long.toString(count); + } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java index e0be903f5..9f70a8584 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/FlagStatistic.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; @@ -5,10 +20,11 @@ /** * A boolean flag that is unset by default and can be set. */ -class FlagStatistic extends LearnerStatistic { +class FlagStatistic extends AbstractStatistic { + private boolean flagged; - public FlagStatistic(String id, @Nullable String description, boolean value) { + FlagStatistic(String id, @Nullable String description, boolean value) { super(id, description); this.flagged = value; } @@ -20,4 +36,9 @@ public void setFlag(boolean value) { public boolean isFlagged() { return flagged; } + + @Override + public String renderValue() { + return Boolean.toString(flagged); + } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java deleted file mode 100644 index afd652e1d..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/LearnerStatistic.java +++ /dev/null @@ -1,36 +0,0 @@ -package de.learnlib.filter.statistic.container; - -import org.checkerframework.checker.nullness.qual.Nullable; - -/** - * Various types of statistical data to be stored in a StatsContainer. - */ -abstract class LearnerStatistic { - private final String id; - private final String description; - - /** - * Creates a new LearnerStatistic. - * - * @param id Unique id of the statistic. Must be unique within the StatsContainer. - * @param description Optional description of the statistic. If no description is provided, the id is used. - */ - public LearnerStatistic(String id, @Nullable String description) { - this.id = id; - - if (description == null) { - this.description = id; - } else { - this.description = description; - } - } - - public String getId() { - return id; - } - - public String getDescription() { - return description; - } - -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java new file mode 100644 index 000000000..fde249684 --- /dev/null +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java @@ -0,0 +1,156 @@ +/* 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 de.learnlib.filter.statistic.container; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import de.learnlib.statistic.StatisticsCollector; + +/** + * A {@link StatisticsCollector} that stores all statistics in a {@link Map}. + */ +@SuppressWarnings("PMD.AvoidSynchronizedAtMethodLevel") // quick'n'dirty for now +public class MapStatisticsCollector implements StatisticsCollector { + + private final Map statistics; + + public MapStatisticsCollector() { + this.statistics = new HashMap<>(); + } + + @Override + public Collection getKeys() { + return new HashSet<>(this.statistics.keySet()); + } + + @Override + public synchronized void clear() { + statistics.clear(); + } + + @Override + public synchronized void addText(String id, String description, String text) { + statistics.put(id, new TextStatistic(id, description, text)); + } + + @Override + public synchronized Optional getText(String id) { + AbstractStatistic value = statistics.get(id); + if (value instanceof TextStatistic textStatistic) { + return Optional.of(textStatistic.getText()); + } + return Optional.empty(); + } + + @Override + public synchronized void setFlag(String id, String description, boolean value) { + statistics.put(id, new FlagStatistic(id, description, value)); + } + + @Override + public synchronized Optional getFlag(String id) { + AbstractStatistic value = statistics.get(id); + if (value instanceof FlagStatistic flagStatistic) { + return Optional.of(flagStatistic.isFlagged()); + } + return Optional.empty(); + } + + @Override + public synchronized void startOrResumeClock(String id, String description) { + AbstractStatistic value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + clockStatistic.resume(); + } else { + // Create and start a new clock: + StopClockStatistic newClock = new StopClockStatistic(id, description); + statistics.put(id, newClock); + newClock.resume(); + } + } + + @Override + public synchronized void pauseClock(String id) { + AbstractStatistic value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + clockStatistic.pause(); + } + } + + @Override + public synchronized Optional getClock(String id) { + AbstractStatistic value = statistics.get(id); + if (value instanceof StopClockStatistic clockStatistic) { + return Optional.of(clockStatistic.getElapsed()); + } + return Optional.empty(); + } + + @Override + public synchronized void increaseCounter(String id, String description, long increment) { + AbstractStatistic value = statistics.get(id); + if (value instanceof CounterStatistic counterStatistic) { + counterStatistic.increase(increment); + } else { + // Create a new counter: + setCounter(id, description, increment); + } + } + + @Override + public synchronized void setCounter(String id, String description, long count) { + statistics.put(id, new CounterStatistic(id, description, count)); + } + + @Override + public synchronized Optional getCount(String id) { + AbstractStatistic value = statistics.get(id); + if (value instanceof CounterStatistic counterStatistic) { + return Optional.of(counterStatistic.getCount()); + } + return Optional.empty(); + } + + // ================== + + @Override + public synchronized String printStats() { + + List stats = new ArrayList<>(statistics.values()); + stats.sort(Comparator.comparing(AbstractStatistic::getDescription).thenComparing(AbstractStatistic::getId)); + + final StringBuilder sb = new StringBuilder(125); + + sb.append("Statistics:\n============================================\n"); + + for (AbstractStatistic stat : stats) { + sb.append("* ").append(stat).append('\n'); + } + + sb.append("============================================\n"); + + return sb.toString(); + } + +} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java deleted file mode 100644 index 1ea594727..000000000 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsContainer.java +++ /dev/null @@ -1,165 +0,0 @@ -package de.learnlib.filter.statistic.container; - -import de.learnlib.statistic.StatsContainer; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.time.Duration; -import java.util.*; - -/** - * A {@link StatsContainer} that stores all statistics in a {@link Map}. - */ -public class MapStatsContainer implements StatsContainer { - - private final Map statistics = new HashMap<>(); // id -> stat - - @Override - public synchronized void addTextInfo(String id, @Nullable String description, String text) { - statistics.put(id, new TextStatistic(id, description, text)); - } - - @Override - public synchronized Optional getTextValue(String id) { - var value = statistics.get(id); - if (value instanceof TextStatistic textStatistic) { - return Optional.of(textStatistic.getText()); - } - return Optional.empty(); - } - - @Override - public synchronized void setFlag(String id, @Nullable String description, boolean value) { - statistics.put(id, new FlagStatistic(id, description, value)); - } - - @Override - public synchronized Optional getFlagValue(String id) { - var value = statistics.get(id); - if (value instanceof FlagStatistic flagStatistic) { - return Optional.of(flagStatistic.isFlagged()); - } - return Optional.empty(); - } - - @Override - public synchronized void startOrResumeClock(String id, @Nullable String description) { - var value = statistics.get(id); - if (value instanceof StopClockStatistic clockStatistic) { - clockStatistic.resume(); - } else { - // Create and start a new clock: - var newClock = new StopClockStatistic(id, description); - statistics.put(id, newClock); - newClock.resume(); - } - } - - @Override - public synchronized void pauseClock(String id) { - var value = statistics.get(id); - if (value instanceof StopClockStatistic clockStatistic) { - clockStatistic.pause(); - } - } - - @Override - public synchronized Optional getClockValue(String id) { - var value = statistics.get(id); - if (value instanceof StopClockStatistic clockStatistic) { - return Optional.of(clockStatistic.getElapsed()); - } - return Optional.empty(); - } - - @Override - public synchronized void increaseCounter(String id, @Nullable String description, long increment) { - var value = statistics.get(id); - if (value instanceof CounterStatistic counterStatistic) { - counterStatistic.increase(increment); - } else { - // Create a new counter: - setCounter(id, description, increment); - } - } - - @Override - public synchronized void setCounter(String id, @Nullable String description, long count) { - statistics.put(id, new CounterStatistic(id, description, count)); - } - - @Override - public synchronized Optional getCount(String id) { - var value = statistics.get(id); - if (value instanceof CounterStatistic counterStatistic) { - return Optional.of(counterStatistic.getCount()); - } - return Optional.empty(); - } - - @Override - public synchronized void clear() { - statistics.clear(); - } - - // ================== - - public String toJson() { - List sortedStats = statistics.values().stream().sorted(Comparator.comparing(LearnerStatistic::getDescription)).toList(); - - List lines = new ArrayList<>(); - for (var stat : sortedStats) { - if (stat instanceof StopClockStatistic sc) { - lines.add(String.format("\"%s [ms]\": %d", stat.getDescription(), sc.getElapsed().toMillis())); - } else if (stat instanceof CounterStatistic c) { - lines.add(String.format("\"%s\": %d", stat.getDescription(), c.getCount())); - } else if (stat instanceof FlagStatistic f) { - lines.add(String.format("\"%s\": %s", stat.getDescription(), f.isFlagged())); - } else if (stat instanceof TextStatistic t) { - lines.add(String.format("\"%s\": \"%s\"", stat.getDescription(), t.getText())); - } - } - - if (lines.isEmpty()) { - return "{}"; - } - - return "{" + String.join(",\n", lines) + "}"; - } - - public String toYaml() { - List sortedStats = statistics.values().stream().sorted(Comparator.comparing(LearnerStatistic::getDescription)).toList(); - - List lines = new ArrayList<>(); - for (var stat : sortedStats) { - if (stat instanceof StopClockStatistic sc) { - lines.add(String.format(" \"%s [ms]\": %d", stat.getDescription(), sc.getElapsed().toMillis())); - } else if (stat instanceof CounterStatistic c) { - lines.add(String.format(" \"%s\": %d", stat.getDescription(), c.getCount())); - } else if (stat instanceof FlagStatistic f) { - lines.add(String.format(" \"%s\": %s", stat.getDescription(), f.isFlagged())); - } else if (stat instanceof TextStatistic t) { - lines.add(String.format(" \"%s\": \"%s\"", stat.getDescription(), t.getText())); - } - } - - if (lines.isEmpty()) { - return null; - } - - // Add dash to first line: - String newFirstLine = " - " + lines.get(0).stripLeading(); - lines.set(0, newFirstLine); - - return String.join("\n", lines); - } - - public String printStats() { - final String pattern = """ - ============================================ - Statistics: - %s - ============================================ - """; - return String.format(pattern, toYaml()); - } -} diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java index c616b852f..62b85e3b7 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatsProvider.java @@ -1,13 +1,28 @@ +/* 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 de.learnlib.filter.statistic.container; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.statistic.StatisticsProvider; -import de.learnlib.statistic.StatsContainer; import org.kohsuke.MetaInfServices; @MetaInfServices(StatisticsProvider.class) public class MapStatsProvider implements StatisticsProvider { - final ThreadLocal threadLocal = ThreadLocal.withInitial(MapStatsContainer::new); + final ThreadLocal threadLocal = ThreadLocal.withInitial(MapStatisticsCollector::new); @Override public int getPriority() { @@ -15,7 +30,7 @@ public int getPriority() { } @Override - public StatsContainer getContainer() { + public StatisticsCollector getCollector() { return threadLocal.get(); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java index 174c61030..fdd5b29cc 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java @@ -1,18 +1,34 @@ +/* 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 de.learnlib.filter.statistic.container; -import org.checkerframework.checker.nullness.qual.Nullable; - import java.time.Duration; import java.time.Instant; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * A stop clock that can be paused and resumed. */ -class StopClockStatistic extends LearnerStatistic { +class StopClockStatistic extends AbstractStatistic { + private Instant started; private Duration elapsed; - public StopClockStatistic(String id, @Nullable String description) { + StopClockStatistic(String id, @Nullable String description) { super(id, description); this.elapsed = Duration.ZERO; this.started = null; @@ -33,4 +49,9 @@ public void pause() { public Duration getElapsed() { return this.elapsed; } + + @Override + public String renderValue() { + return elapsed.toMillis() + " ms"; + } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java index 2beb08935..9767a793b 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/TextStatistic.java @@ -1,11 +1,27 @@ +/* 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 de.learnlib.filter.statistic.container; import org.checkerframework.checker.nullness.qual.Nullable; -class TextStatistic extends LearnerStatistic { +class TextStatistic extends AbstractStatistic { + private final String text; - public TextStatistic(String id, @Nullable String description, String text) { + TextStatistic(String id, @Nullable String description, String text) { super(id, description); this.text = text; } @@ -13,4 +29,9 @@ public TextStatistic(String id, @Nullable String description, String text) { public String getText() { return text; } + + @Override + public String renderValue() { + return text; + } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java index 02c999698..a49827088 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/learner/RefinementCounterLearner.java @@ -21,7 +21,7 @@ import de.learnlib.algorithm.LearningAlgorithm.MooreLearner; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.tooling.annotation.refinement.GenerateRefinement; import de.learnlib.tooling.annotation.refinement.Generic; import de.learnlib.tooling.annotation.refinement.Mapping; @@ -69,19 +69,20 @@ generics = {@Generic("I"), @Generic("O")})) public class RefinementCounterLearner implements LearningAlgorithm { - private final LearningAlgorithm learningAlgorithm; + public static final String KEY_CNT = "ref-cnt"; - private final StatsContainer statistics; - private final String prefix; + private final LearningAlgorithm learningAlgorithm; + private final StatisticsCollector statisticsCollector; + private final String id; public RefinementCounterLearner(LearningAlgorithm learningAlgorithm) { this(learningAlgorithm, ""); } - public RefinementCounterLearner(LearningAlgorithm learningAlgorithm, String prefix) { + public RefinementCounterLearner(LearningAlgorithm learningAlgorithm, String id) { this.learningAlgorithm = learningAlgorithm; - this.prefix = prefix; - this.statistics = Statistics.getContainer(); + this.id = id; + this.statisticsCollector = Statistics.getCollector(); } @Override @@ -93,7 +94,7 @@ public void startLearning() { public boolean refineHypothesis(DefaultQuery ceQuery) { final boolean refined = learningAlgorithm.refineHypothesis(ceQuery); if (refined) { - statistics.increaseCounter(prefix + "-ref-cnt", "Number of refinements"); + statisticsCollector.increaseCounter(KEY_CNT + id, "Number of refinements"); } return refined; } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java index 1451f62a8..e1e2ec761 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveQueryOracle.java @@ -23,7 +23,7 @@ import de.learnlib.query.AdaptiveQuery; import de.learnlib.query.AdaptiveQuery.Response; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; /** * A simple wrapper for counting the number of {@link Response#RESET resets} and {@link Response#SYMBOL symbols} of an @@ -36,22 +36,22 @@ */ public class CounterAdaptiveQueryOracle implements AdaptiveMembershipOracle { - public static final String DUR_KEY = "-qry-dur"; - public static final String RESET_KEY = "-reset-cnt"; - public static final String SYMBOL_KEY = "-sym-cnt"; + public static final String DUR_KEY = "amq-qry-dur"; + public static final String RESET_KEY = "amq-reset-cnt"; + public static final String SYMBOL_KEY = "amq-sym-cnt"; private final AdaptiveMembershipOracle delegate; - private final StatsContainer statistics; - private final String prefix; + private final StatisticsCollector statisticsCollector; + private final String id; public CounterAdaptiveQueryOracle(AdaptiveMembershipOracle delegate) { this(delegate, ""); } - public CounterAdaptiveQueryOracle(AdaptiveMembershipOracle delegate, String prefix) { + public CounterAdaptiveQueryOracle(AdaptiveMembershipOracle delegate, String id) { this.delegate = delegate; - this.prefix = prefix; - this.statistics = Statistics.getContainer(); + this.id = id; + this.statisticsCollector = Statistics.getCollector(); } @Override @@ -61,14 +61,14 @@ public void processQueries(Collection> queries) { wrappers.add(new CountingQuery<>(q)); } - statistics.startOrResumeClock(prefix + DUR_KEY, "Duration of queries"); + statisticsCollector.startOrResumeClock(DUR_KEY + id, "Duration of queries"); this.delegate.processQueries(wrappers); - statistics.pauseClock(prefix + DUR_KEY); + statisticsCollector.pauseClock(DUR_KEY + id); // statContainer is not thread-safe so we need to count in post-processing for (CountingQuery wrapper : wrappers) { - this.statistics.increaseCounter(prefix + RESET_KEY, "Number of resets", wrapper.resets); - this.statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of symbols", wrapper.symbols); + this.statisticsCollector.increaseCounter(RESET_KEY + id, "Number of resets", wrapper.resets); + this.statisticsCollector.increaseCounter(SYMBOL_KEY + id, "Number of symbols", wrapper.symbols); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java index 507929d60..d9020ec09 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterEQOracle.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.filter.statistic.oracle; import java.util.Collection; @@ -5,34 +20,37 @@ import de.learnlib.oracle.EquivalenceOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import org.checkerframework.checker.nullness.qual.Nullable; public class CounterEQOracle implements EquivalenceOracle { + public static final String KEY_CEX_CNT = "cex-cnt"; + public static final String KEY_CEX_DUR = "cex-dur"; + private final EquivalenceOracle delegate; - private final StatsContainer stats; - private final String prefix; + private final StatisticsCollector statisticsCollector; + private final String id; public CounterEQOracle(EquivalenceOracle delegate) { this(delegate, ""); } - public CounterEQOracle(EquivalenceOracle delegate, String prefix) { + public CounterEQOracle(EquivalenceOracle delegate, String id) { this.delegate = delegate; - this.prefix = prefix; - this.stats = Statistics.getContainer(); + this.id = id; + this.statisticsCollector = Statistics.getCollector(); } @Override public @Nullable DefaultQuery findCounterExample(A hypothesis, Collection inputs) { - final String suffix = prefix.isEmpty() ? "" : " from '" + prefix + '\''; + final String suffix = id.isEmpty() ? "" : " from '" + id + '\''; - stats.startOrResumeClock(prefix + "-cex-dur", "Duration of CEX search" + suffix); + statisticsCollector.startOrResumeClock(KEY_CEX_DUR + id, "Duration of CEX search" + suffix); final DefaultQuery cex = this.delegate.findCounterExample(hypothesis, inputs); - stats.pauseClock(prefix + "-cex-dur"); + statisticsCollector.pauseClock(KEY_CEX_DUR + id); if (cex != null) { - stats.increaseCounter(prefix + "-cex-cnt", "Found CEX" + suffix); + statisticsCollector.increaseCounter(KEY_CEX_CNT + id, "Found CEX" + suffix); } return cex; } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java index 3ece23107..93970d616 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java @@ -23,7 +23,7 @@ import de.learnlib.oracle.MembershipOracle.MooreMembershipOracle; import de.learnlib.query.Query; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.tooling.annotation.refinement.GenerateRefinement; import de.learnlib.tooling.annotation.refinement.Generic; import de.learnlib.tooling.annotation.refinement.Interface; @@ -66,32 +66,34 @@ generics = {@Generic("I"), @Generic("O")})) public class CounterOracle implements MembershipOracle { - public static final String DUR_KEY = "-qry-dur"; - public static final String QUERY_KEY = "-qry-cnt"; - public static final String SYMBOL_KEY = "-sym-cnt"; + public static final String DUR_KEY = "mq-qry-dur"; + public static final String QUERY_KEY = "mq-qry-cnt"; + public static final String SYMBOL_KEY = "mq-sym-cnt"; private final MembershipOracle delegate; - private final StatsContainer statistics; - private final String prefix; + private final StatisticsCollector statisticsCollector; + private final String id; public CounterOracle(MembershipOracle delegate) { this(delegate, ""); } - public CounterOracle(MembershipOracle delegate, String prefix) { + public CounterOracle(MembershipOracle delegate, String id) { this.delegate = delegate; - this.prefix = prefix; - this.statistics = Statistics.getContainer(); + this.id = id; + this.statisticsCollector = Statistics.getCollector(); } @Override public void processQueries(Collection> queries) { - statistics.increaseCounter(prefix + QUERY_KEY, "Number of queries", queries.size()); + statisticsCollector.increaseCounter(QUERY_KEY + id, "Number of queries", queries.size()); for (Query qry : queries) { - statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of symbols", qry.getPrefix().length() + qry.getSuffix().length()); + statisticsCollector.increaseCounter(SYMBOL_KEY + id, + "Number of symbols", + qry.getPrefix().length() + qry.getSuffix().length()); } - statistics.startOrResumeClock(prefix + DUR_KEY, "Duration of queries"); + statisticsCollector.startOrResumeClock(DUR_KEY + id, "Duration of queries"); delegate.processQueries(queries); - statistics.pauseClock(prefix + DUR_KEY); + statisticsCollector.pauseClock(DUR_KEY + id); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java index 15b6199db..dc1629c2f 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterObservableSUL.java @@ -16,7 +16,7 @@ package de.learnlib.filter.statistic.sul; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.ObservableSUL; public class CounterObservableSUL extends CounterSUL implements ObservableSUL { @@ -27,18 +27,18 @@ public CounterObservableSUL(ObservableSUL sul) { this(sul, ""); } - public CounterObservableSUL(ObservableSUL sul, String prefix) { - this(sul, prefix, Statistics.getContainer()); + public CounterObservableSUL(ObservableSUL sul, String id) { + this(sul, id, Statistics.getCollector()); } - protected CounterObservableSUL(ObservableSUL sul, String prefix, StatsContainer statistics) { - super(sul, prefix, statistics); + protected CounterObservableSUL(ObservableSUL sul, String id, StatisticsCollector statistics) { + super(sul, id, statistics); this.sul = sul; } @Override public ObservableSUL fork() { - return new CounterObservableSUL<>(this.sul.fork(), super.prefix, super.statistics); + return new CounterObservableSUL<>(this.sul.fork(), super.id, super.statisticsCollector); } @Override diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java index 60d3cacf3..675e9524f 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterSUL.java @@ -16,35 +16,35 @@ package de.learnlib.filter.statistic.sul; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.SUL; public class CounterSUL implements SUL { - public static final String RESET_KEY = "-sul-reset-cnt"; - public static final String SYMBOL_KEY = "-sul-step-cnt"; + public static final String RESET_KEY = "sul-reset-cnt"; + public static final String SYMBOL_KEY = "sul-step-cnt"; private final SUL sul; - protected final StatsContainer statistics; - protected final String prefix; + protected final StatisticsCollector statisticsCollector; + protected final String id; public CounterSUL(SUL sul) { this(sul, ""); } - public CounterSUL(SUL sul, String prefix) { - this(sul, prefix, Statistics.getContainer()); + public CounterSUL(SUL sul, String id) { + this(sul, id, Statistics.getCollector()); } - protected CounterSUL(SUL sul, String prefix, StatsContainer statistics) { + protected CounterSUL(SUL sul, String id, StatisticsCollector statisticsCollector) { this.sul = sul; - this.prefix = prefix; - this.statistics = statistics; + this.id = id; + this.statisticsCollector = statisticsCollector; } @Override public void pre() { - this.statistics.increaseCounter(prefix + RESET_KEY, "Number of SUL resets"); + this.statisticsCollector.increaseCounter(RESET_KEY + id, "Number of SUL resets"); this.sul.pre(); } @@ -55,7 +55,7 @@ public void post() { @Override public O step(I in) { - this.statistics.increaseCounter(prefix + SYMBOL_KEY, "Number of SUL steps"); + this.statisticsCollector.increaseCounter(SYMBOL_KEY + id, "Number of SUL steps"); return sul.step(in); } @@ -66,6 +66,6 @@ public boolean canFork() { @Override public SUL fork() { - return new CounterSUL<>(this.sul.fork(), this.prefix, this.statistics); + return new CounterSUL<>(this.sul.fork(), this.id, this.statisticsCollector); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java index b3e65ce35..e249becea 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterStateLocalInputSUL.java @@ -18,12 +18,12 @@ import java.util.Collection; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.StateLocalInputSUL; public class CounterStateLocalInputSUL extends CounterSUL implements StateLocalInputSUL { - public static final String INPUT_KEY = "-sul-inp-cnt"; + public static final String INPUT_KEY = "sul-input-cnt"; private final StateLocalInputSUL sul; @@ -31,24 +31,24 @@ public CounterStateLocalInputSUL(StateLocalInputSUL sul) { this(sul, ""); } - private CounterStateLocalInputSUL(StateLocalInputSUL sul, String prefix) { - this(sul, prefix, Statistics.getContainer()); + private CounterStateLocalInputSUL(StateLocalInputSUL sul, String id) { + this(sul, id, Statistics.getCollector()); } - protected CounterStateLocalInputSUL(StateLocalInputSUL sul, String prefix, StatsContainer statistics) { - super(sul, prefix, statistics); + protected CounterStateLocalInputSUL(StateLocalInputSUL sul, String id, StatisticsCollector statistics) { + super(sul, id, statistics); this.sul = sul; } @Override public Collection currentlyEnabledInputs() { - super.statistics.increaseCounter(prefix + INPUT_KEY, "Number of enabled input checks"); + super.statisticsCollector.increaseCounter(INPUT_KEY + super.id, "Number of enabled input checks"); return this.sul.currentlyEnabledInputs(); } @Override public StateLocalInputSUL fork() { - return new CounterStateLocalInputSUL<>(this.sul.fork(), super.prefix, super.statistics); + return new CounterStateLocalInputSUL<>(this.sul.fork(), super.id, super.statisticsCollector); } } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index 60bdbc997..173dd034a 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -1,7 +1,22 @@ +/* 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 de.learnlib.filter.statistic.sul; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.TimedSUL; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; @@ -9,29 +24,30 @@ import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; - /** - * Wrapper for an MMLT SUL that gathers various statistics on queries sent to this SUL. + * Wrapper for a {@link TimedSUL} that gathers various statistics on queries sent to this SUL. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class CounterTimedSUL implements TimedSUL { + private final TimedSUL delegate; - private final StatsContainer stats; + private final StatisticsCollector stats; - @Nullable - private final String name; + private final @Nullable String name; public CounterTimedSUL(TimedSUL delegate) { this(delegate, null); } public CounterTimedSUL(TimedSUL delegate, String name) { - this(delegate, name, Statistics.getContainer()); + this(delegate, name, Statistics.getCollector()); } - protected CounterTimedSUL(TimedSUL delegate, String name, StatsContainer statistics) { + protected CounterTimedSUL(TimedSUL delegate, String name, StatisticsCollector statistics) { this.delegate = delegate; this.name = name; this.stats = statistics; @@ -46,8 +62,7 @@ private String withPrefix(String label) { @Override public TimedOutput step(InputSymbol input) { - stats.increaseCounter(withPrefix("sul_untimed_syms_counter"), - withPrefix("Total untimed symbols")); + stats.increaseCounter(withPrefix("sul_untimed_syms_counter"), withPrefix("Total untimed symbols")); return this.delegate.step(input); } @@ -56,11 +71,9 @@ public TimedOutput step(InputSymbol input) { TimedOutput res = this.delegate.timeoutStep(maxTime); if (res == null) { // Waited until maxTime, no timeout occurred: - stats.increaseCounter(withPrefix("sul_total_time"), - withPrefix("Total query time"), maxTime); + stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), maxTime); } else { - stats.increaseCounter(withPrefix("sul_total_time"), - withPrefix("Total query time"), res.delay()); + stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), res.delay()); } return res; @@ -68,17 +81,14 @@ public TimedOutput step(InputSymbol input) { @Override public Word> collectTimeouts(TimeStepSequence input) { - stats.increaseCounter(withPrefix("sul_total_time"), - withPrefix("Total query time"), - input.timeSteps()); + stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), input.timeSteps()); return this.delegate.collectTimeouts(input); } @Override public void pre() { this.delegate.pre(); - stats.increaseCounter(withPrefix("sul_resets_counter"), - withPrefix("SUL resets")); + stats.increaseCounter(withPrefix("sul_resets_counter"), withPrefix("SUL resets")); } @Override @@ -96,5 +106,4 @@ public TimedSUL fork() { return new CounterTimedSUL<>(this.delegate.fork(), this.name, this.stats); } - } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java index 9a3b8805b..ba3c699f8 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterAdaptiveOracleTest.java @@ -26,7 +26,7 @@ import de.learnlib.query.AdaptiveQuery.Response; import de.learnlib.query.Query; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.testng.Assert; @@ -43,7 +43,7 @@ public CounterAdaptiveOracleTest() { @BeforeClass public void setUp() { - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); } @Test @@ -80,14 +80,14 @@ public void testSecondQueryBatch() { @Test(dependsOnMethods = "testSecondQueryBatch") public void testStatistics() { - final StatsContainer container = Statistics.getContainer(); - Assert.assertTrue(container.printStats().contains("\n")); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + Assert.assertFalse(statisticsCollector.getKeys().isEmpty()); } private void verifyCounts(long queries, long symbols) { - final StatsContainer container = Statistics.getContainer(); - Assert.assertEquals(container.getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L), queries); - Assert.assertEquals(container.getCount(CounterAdaptiveQueryOracle.SYMBOL_KEY).orElse(0L), symbols); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + Assert.assertEquals(statisticsCollector.getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L), queries); + Assert.assertEquals(statisticsCollector.getCount(CounterAdaptiveQueryOracle.SYMBOL_KEY).orElse(0L), symbols); } private Collection>> generateQueries(int numQueries, diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java index 79fc5cab7..790bfbe55 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/oracle/CounterOracleTest.java @@ -22,7 +22,7 @@ import de.learnlib.oracle.MembershipOracle; import de.learnlib.query.Query; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import net.automatalib.word.Word; import org.mockito.Mockito; import org.testng.Assert; @@ -40,7 +40,7 @@ public CounterOracleTest() { @BeforeClass public void setUp() { - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); } @Test @@ -71,14 +71,14 @@ public void testSecondQueryBatch() { @Test(dependsOnMethods = "testSecondQueryBatch") public void testStatistics() { - final StatsContainer container = Statistics.getContainer(); - Assert.assertTrue(container.printStats().contains("\n")); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + Assert.assertFalse(statisticsCollector.getKeys().isEmpty()); } private void verifyCounts(long queries, long symbols) { - final StatsContainer container = Statistics.getContainer(); - Assert.assertEquals(container.getCount(CounterOracle.QUERY_KEY).orElse(0L), queries); - Assert.assertEquals(container.getCount(CounterOracle.SYMBOL_KEY).orElse(0L), symbols); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + Assert.assertEquals(statisticsCollector.getCount(CounterOracle.QUERY_KEY).orElse(0L), queries); + Assert.assertEquals(statisticsCollector.getCount(CounterOracle.SYMBOL_KEY).orElse(0L), symbols); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java index dd0f75bcc..bfe648adc 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/AbstractCounterSULTest.java @@ -36,17 +36,21 @@ public abstract class AbstractCounterSULTest> protected abstract S getStatisticSUL(); - protected abstract Optional getCount(S sul); - protected abstract int getCountIncreasePerQuery(); protected abstract Collection>> createQueries(int num); + protected abstract Optional getCount(S sul); + + private long getCount() { + return getCount(statisticSUL).orElse(0L); + } + @BeforeClass public void setUp() { this.statisticSUL = getStatisticSUL(); this.asOracle = getSimulator(this.statisticSUL); - Statistics.getContainer().clear(); + Statistics.getCollector().clear(); } @Test @@ -100,10 +104,6 @@ public void testSharedForkCounter() { Assert.assertEquals(getCount(), oldCount + 2L * 3 * getCountIncreasePerQuery()); } - private long getCount() { - return getCount(statisticSUL).orElse(0L); - } - // use custom class to prevent cyclic dependency on learnlib-membership-oracles private static SingleQueryOracleMealy getSimulator(SUL sul) { return (prefix, suffix) -> { diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java index 8eca0672c..c116a7dba 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterObservableSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.ObservableMealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class ResetCounterObservableSULTest extends AbstractResetCounterSULTest> { @@ -32,7 +32,7 @@ protected CounterObservableSUL getStatisticSUL() { @Override protected Optional getCount(CounterObservableSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterObservableSUL.RESET_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterObservableSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java index 07e0dba34..5aa26d731 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.MealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class ResetCounterSULTest extends AbstractResetCounterSULTest> { @@ -31,7 +31,7 @@ protected CounterSUL getStatisticSUL() { @Override protected Optional getCount(CounterSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterSUL.RESET_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java index ca6d47e7d..99723bf6c 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/ResetCounterStateLocalInputSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.StateLocalInputMealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class ResetCounterStateLocalInputSULTest extends AbstractResetCounterSULTest> { @@ -32,7 +32,7 @@ protected CounterStateLocalInputSUL getStatisticSUL() { @Override protected Optional getCount(CounterStateLocalInputSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterStateLocalInputSUL.RESET_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterStateLocalInputSUL.RESET_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java index efcacb504..8172027e5 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterObservableSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.ObservableMealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class SymbolCounterObservableSULTest extends AbstractSymbolCounterSULTest> { @@ -32,7 +32,7 @@ protected CounterObservableSUL getStatisticSUL() { @Override protected Optional getCount(CounterObservableSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterObservableSUL.SYMBOL_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterObservableSUL.SYMBOL_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java index db9e6872b..34d539567 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.MealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class SymbolCounterSULTest extends AbstractSymbolCounterSULTest> { @@ -31,8 +31,8 @@ protected CounterSUL getStatisticSUL() { @Override protected Optional getCount(CounterSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterSUL.SYMBOL_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterSUL.SYMBOL_KEY); } } diff --git a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java index b5adb7d5a..6fa73d2c9 100644 --- a/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java +++ b/filters/statistics/src/test/java/de/learnlib/filter/statistic/sul/SymbolCounterStateLocalInputSULTest.java @@ -20,7 +20,7 @@ import de.learnlib.driver.simulator.StateLocalInputMealySimulatorSUL; import de.learnlib.filter.statistic.TestQueries; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; public class SymbolCounterStateLocalInputSULTest extends AbstractSymbolCounterSULTest> { @@ -32,7 +32,7 @@ protected CounterStateLocalInputSUL getStatisticSUL() { @Override protected Optional getCount(CounterStateLocalInputSUL sul) { - final StatsContainer container = Statistics.getContainer(); - return container.getCount(CounterStateLocalInputSUL.SYMBOL_KEY); + final StatisticsCollector statisticsCollector = Statistics.getCollector(); + return statisticsCollector.getCount(CounterStateLocalInputSUL.SYMBOL_KEY); } } diff --git a/filters/statistics/src/test/resources/histogram_details.txt b/filters/statistics/src/test/resources/histogram_details.txt deleted file mode 100644 index 11944aacc..000000000 --- a/filters/statistics/src/test/resources/histogram_details.txt +++ /dev/null @@ -1,3 +0,0 @@ -testCounter [query length]: 4 (count), 10 (sum), 2.5 (mean), 0.0 (median) - 0, 2 - 5, 2 diff --git a/filters/statistics/src/test/resources/histogram_summary.txt b/filters/statistics/src/test/resources/histogram_summary.txt deleted file mode 100644 index a034eecfb..000000000 --- a/filters/statistics/src/test/resources/histogram_summary.txt +++ /dev/null @@ -1 +0,0 @@ -testCounter [query length]: 4 (count), 10 (sum), 2.5 (mean), 0.0 (median) \ No newline at end of file diff --git a/filters/symbol-filters/pom.xml b/filters/symbol-filters/pom.xml index 49eb124cc..cc79558e5 100644 --- a/filters/symbol-filters/pom.xml +++ b/filters/symbol-filters/pom.xml @@ -11,7 +11,7 @@ learnlib-symbol-filters - LearnLib :: Oracles :: Symbol Filters + LearnLib :: Filters :: Symbol Filters A collection of symbol filters @@ -27,22 +27,17 @@ automata-api - - org.checkerframework - checker-qual - - - - org.slf4j - slf4j-api - - de.learnlib.tooling annotations + + org.checkerframework + checker-qual + + org.testng diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractPerfectSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractPerfectSymbolFilter.java new file mode 100644 index 000000000..d071d841f --- /dev/null +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractPerfectSymbolFilter.java @@ -0,0 +1,41 @@ +/* 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 de.learnlib.filter.symbol; + +import de.learnlib.filter.FilterResponse; +import de.learnlib.filter.SymbolFilter; +import net.automatalib.word.Word; + +/** + * A symbol filter that answers all queries correctly. + * + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label + */ +public abstract class AbstractPerfectSymbolFilter extends AbstractTruthfulSymbolFilter + implements SymbolFilter { + + @Override + public FilterResponse query(Word prefix, V symbol) { + if (isIgnorable(prefix, symbol) == FilterResponse.IGNORE) { + return FilterResponse.IGNORE; + } else { + return FilterResponse.ACCEPT; + } + } +} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractRandomSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractRandomSymbolFilter.java new file mode 100644 index 000000000..c999d4cd4 --- /dev/null +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractRandomSymbolFilter.java @@ -0,0 +1,70 @@ +/* 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 de.learnlib.filter.symbol; + +import java.util.Random; + +import de.learnlib.filter.FilterResponse; +import de.learnlib.filter.SymbolFilter; +import net.automatalib.word.Word; + +/** + * A symbol filter that falsely answers a query with a specified probability. + * + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label + */ +public abstract class AbstractRandomSymbolFilter extends AbstractTruthfulSymbolFilter implements SymbolFilter { + + private final double inaccurateProb; + private final Random random; + + public AbstractRandomSymbolFilter(double inaccurateProb, Random random) { + this(inaccurateProb, random, valideProbability(inaccurateProb)); + } + + // utility constructor to prevent finalizer attacks, see SEI CERT Rule OBJ-11 + @SuppressWarnings("PMD.UnusedFormalParameter") + private AbstractRandomSymbolFilter(double inaccurateProb, Random random, boolean validated) { + this.inaccurateProb = inaccurateProb; + this.random = random; + } + + private static boolean valideProbability(double inaccurateProb) { + if (inaccurateProb > 1 || inaccurateProb < 0) { + throw new IllegalArgumentException("Ratios must be between zero and 1 (inclusive)."); + } + return true; + } + + @Override + public FilterResponse query(Word prefix, V symbol) { + boolean ignorable = isIgnorable(prefix, symbol) == FilterResponse.IGNORE; + + // Randomly misclassify: + if (this.random.nextDouble() <= this.inaccurateProb) { + ignorable = !ignorable; + } + + if (ignorable) { + return FilterResponse.IGNORE; + } else { + return FilterResponse.ACCEPT; + } + } +} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractStatisticsSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractStatisticsSymbolFilter.java new file mode 100644 index 000000000..4db865797 --- /dev/null +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractStatisticsSymbolFilter.java @@ -0,0 +1,83 @@ +/* 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 de.learnlib.filter.symbol; + +import de.learnlib.filter.FilterResponse; +import de.learnlib.filter.MutableSymbolFilter; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatisticsCollector; +import net.automatalib.word.Word; + +/** + * Collects various statistics on symbol filtering, including false accepts + false ignores. + * + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label + */ +public abstract class AbstractStatisticsSymbolFilter extends AbstractTruthfulSymbolFilter + implements MutableSymbolFilter { + + public static final String KEY_QUERIES = "sf-qry-cnt"; + public static final String KEY_TRUE_POSITIVES = "sf-tp-cnt"; + public static final String KEY_FALSE_POSITIVES = "sf-fp-cnt"; + public static final String KEY_TRUE_NEGATIVES = "sf-tn-cnt"; + public static final String KEY_FALSE_NEGATIVES = "sf-fn-cnt"; + + private final SymbolFilter delegate; + private final StatisticsCollector statisticsCollector; + + public AbstractStatisticsSymbolFilter(SymbolFilter delegate) { + this.delegate = delegate; + this.statisticsCollector = Statistics.getCollector(); + } + + @Override + public FilterResponse query(Word prefix, V symbol) { + statisticsCollector.increaseCounter(KEY_QUERIES, "Filter: queries"); + + FilterResponse filterResponse = this.delegate.query(prefix, symbol); + FilterResponse expectedResponse = this.isIgnorable(prefix, symbol); + + // Count false ignores, rejects + correct predictions: + if (filterResponse == FilterResponse.ACCEPT) { + if (filterResponse.equals(expectedResponse)) { + statisticsCollector.increaseCounter(KEY_TRUE_POSITIVES, "Filter: correct accepts"); + } else { + statisticsCollector.increaseCounter(KEY_FALSE_POSITIVES, "Filter: false accepts"); + } + } else { + if (filterResponse == expectedResponse) { + statisticsCollector.increaseCounter(KEY_TRUE_NEGATIVES, "Filter: correct ignores"); + } else { + statisticsCollector.increaseCounter(KEY_FALSE_NEGATIVES, "Filter: false ignores"); + } + } + + return filterResponse; + } + + @Override + public void accept(Word prefix, V symbol) { + if (delegate instanceof MutableSymbolFilter mut) { + mut.accept(prefix, symbol); + } else { + throw new UnsupportedOperationException("delegate filter does not support updates"); + } + } +} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractTruthfulSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractTruthfulSymbolFilter.java new file mode 100644 index 000000000..b027cbc4e --- /dev/null +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AbstractTruthfulSymbolFilter.java @@ -0,0 +1,46 @@ +/* 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 de.learnlib.filter.symbol; + +import de.learnlib.filter.FilterResponse; +import de.learnlib.filter.SymbolFilter; +import net.automatalib.word.Word; + +/** + * A utility class that requires to answer the filter question truthfully. + * + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label + */ +public abstract class AbstractTruthfulSymbolFilter implements SymbolFilter { + + /** + * See {@link SymbolFilter#query(Word, Object)}. The only difference is that this method is not allowed to return + * false responses. + * + * @param prefix + * the prefix identifying the state + * @param symbol + * the input symbol identifying the transition + * + * @return {@link FilterResponse#IGNORE} if the symbol is ignorable, {@link FilterResponse#ACCEPT} otherwise + * + * @see SymbolFilter#query(Word, Object) + */ + protected abstract FilterResponse isIgnorable(Word prefix, V symbol); +} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java index d4afdcc2d..59c901089 100644 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/AcceptAllSymbolFilter.java @@ -1,20 +1,37 @@ +/* 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 de.learnlib.filter.symbol; - +import de.learnlib.filter.FilterResponse; import de.learnlib.filter.MutableSymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; /** * A pass-through filter that accepts all inputs. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label */ public class AcceptAllSymbolFilter implements MutableSymbolFilter { + @Override - public SymbolFilterResponse query(Word prefix, V symbol) { - return SymbolFilterResponse.ACCEPT; + public FilterResponse query(Word prefix, V symbol) { + return FilterResponse.ACCEPT; } @Override diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java index 681bb851e..1a25009f8 100644 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/CachedSymbolFilter.java @@ -1,21 +1,38 @@ +/* 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 de.learnlib.filter.symbol; +import java.util.HashMap; +import java.util.Map; +import de.learnlib.filter.FilterResponse; import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; -import java.util.HashMap; -import java.util.Map; - /** * Wrapper for a symbol filter that caches previous responses + allows caller to update these. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label */ public class CachedSymbolFilter implements MutableSymbolFilter { + private final Map, Map> previousResponses; // prefix -> (input -> legal/ignore) private final SymbolFilter delegate; @@ -25,25 +42,25 @@ public CachedSymbolFilter(SymbolFilter delegate) { } @Override - public SymbolFilterResponse query(Word prefix, V symbol) { + public FilterResponse query(Word prefix, V symbol) { this.previousResponses.putIfAbsent(prefix, new HashMap<>()); - var oldResponse = this.previousResponses.get(prefix).get(symbol); + Boolean oldResponse = this.previousResponses.get(prefix).get(symbol); if (oldResponse != null) { - return (oldResponse) ? SymbolFilterResponse.ACCEPT : SymbolFilterResponse.IGNORE; + return oldResponse ? FilterResponse.ACCEPT : FilterResponse.IGNORE; } - var res = delegate.query(prefix, symbol); + FilterResponse res = delegate.query(prefix, symbol); this.update(prefix, symbol, res); return res; } @Override public void accept(Word prefix, V symbol) { - this.update(prefix, symbol, SymbolFilterResponse.ACCEPT); + this.update(prefix, symbol, FilterResponse.ACCEPT); } - private void update(Word prefix, V symbol, SymbolFilterResponse response) { + private void update(Word prefix, V symbol, FilterResponse response) { this.previousResponses.putIfAbsent(prefix, new HashMap<>()); - this.previousResponses.get(prefix).put(symbol, (response == SymbolFilterResponse.ACCEPT)); + this.previousResponses.get(prefix).put(symbol, response == FilterResponse.ACCEPT); } } diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java index b3005aa71..0100d8996 100644 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java +++ b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/IgnoreAllSymbolFilter.java @@ -1,18 +1,36 @@ +/* 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 de.learnlib.filter.symbol; +import de.learnlib.filter.FilterResponse; import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; import net.automatalib.word.Word; /** * A pass-through filter that ignores all inputs. * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols + * @param + * input symbol type of the prefix + * @param + * input symbol type of the transition label */ public class IgnoreAllSymbolFilter implements SymbolFilter { + @Override - public SymbolFilterResponse query(Word prefix, V symbol) { - return SymbolFilterResponse.IGNORE; + public FilterResponse query(Word prefix, V symbol) { + return FilterResponse.IGNORE; } } diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java deleted file mode 100644 index fa9de249e..000000000 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/PerfectSymbolFilter.java +++ /dev/null @@ -1,27 +0,0 @@ -package de.learnlib.filter.symbol; - - -import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; -import net.automatalib.word.Word; - -/** - * A symbol filter that answers all queries correctly. - * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols - */ -public abstract class PerfectSymbolFilter implements SymbolFilter { - - protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); - - @Override - public SymbolFilterResponse query(Word prefix, V symbol) { - - if (isIgnorable(prefix, symbol) == SymbolFilterResponse.IGNORE) { - return SymbolFilterResponse.IGNORE; - } else { - return SymbolFilterResponse.ACCEPT; - } - } -} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java deleted file mode 100644 index 45401438c..000000000 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/RandomSymbolFilter.java +++ /dev/null @@ -1,47 +0,0 @@ -package de.learnlib.filter.symbol; - - -import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; -import net.automatalib.word.Word; - -import java.util.Random; - -/** - * A symbol filter that falsely answers a query with a specified probability. - * - * @param Type for symbols in the prefix of the considered states - * @param Type of the queried symbols - */ -public abstract class RandomSymbolFilter implements SymbolFilter { - - private final double inaccurateProb; - private final Random random; - - public RandomSymbolFilter(double inaccurateProb, Random random) { - if (inaccurateProb > 1 || inaccurateProb < 0) { - throw new IllegalArgumentException("Ratios must be between zero and 1 (inclusive)."); - } - - this.inaccurateProb = inaccurateProb; - this.random = random; - } - - protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); - - @Override - public SymbolFilterResponse query(Word prefix, V symbol) { - boolean ignorable = isIgnorable(prefix, symbol) == SymbolFilterResponse.IGNORE; - - // Randomly misclassify: - if (this.random.nextDouble() <= this.inaccurateProb) { - ignorable = !ignorable; - } - - if (ignorable) { - return SymbolFilterResponse.IGNORE; - } else { - return SymbolFilterResponse.ACCEPT; - } - } -} diff --git a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java b/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java deleted file mode 100644 index cdc66ea99..000000000 --- a/filters/symbol-filters/src/main/java/de/learnlib/filter/symbol/StatisticsSymbolFilter.java +++ /dev/null @@ -1,63 +0,0 @@ -package de.learnlib.filter.symbol; - -import de.learnlib.filter.MutableSymbolFilter; -import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; -import de.learnlib.filter.SymbolFilter; -import de.learnlib.filter.SymbolFilterResponse; -import net.automatalib.word.Word; - -/** - * Collects various statistics on symbol filtering, including false accepts + false ignores. - * - * @param - * Type for symbols in the prefix of the considered states - * @param - * Type of the queried symbols - */ -public abstract class StatisticsSymbolFilter implements MutableSymbolFilter { - - private final SymbolFilter delegate; - private final StatsContainer stats; - - public StatisticsSymbolFilter(SymbolFilter delegate) { - this.delegate = delegate; - this.stats = Statistics.getContainer(); - } - - protected abstract SymbolFilterResponse isIgnorable(Word prefix, V symbol); - - @Override - public SymbolFilterResponse query(Word prefix, V symbol) { - stats.increaseCounter("cnt_isf_queries", "Filter: queries"); - - SymbolFilterResponse filterResponse = this.delegate.query(prefix, symbol); - SymbolFilterResponse expectedResponse = this.isIgnorable(prefix, symbol); - - // Count false ignores, rejects + correct predictions: - if (filterResponse.equals(SymbolFilterResponse.ACCEPT)) { - if (filterResponse.equals(expectedResponse)) { - stats.increaseCounter("cnt_isf_correct_accepts", "Filter: correct accepts"); - } else { - stats.increaseCounter("cnt_isf_false_accepts", "Filter: false accepts"); - } - } else { - if (filterResponse.equals(expectedResponse)) { - stats.increaseCounter("cnt_isf_correct_ignores", "Filter: correct ignores"); - } else { - stats.increaseCounter("cnt_isf_false_ignores", "Filter: false ignores"); - } - } - - return filterResponse; - } - - @Override - public void accept(Word prefix, V symbol) { - if (delegate instanceof MutableSymbolFilter mut) { - mut.accept(prefix, symbol); - } else { - throw new UnsupportedOperationException("delegate filter does not support updates"); - } - } -} diff --git a/filters/symbol-filters/src/main/java/module-info.java b/filters/symbol-filters/src/main/java/module-info.java index 304049f02..c96f2fc02 100644 --- a/filters/symbol-filters/src/main/java/module-info.java +++ b/filters/symbol-filters/src/main/java/module-info.java @@ -28,11 +28,12 @@ */ open module de.learnlib.filter.symbol { + requires de.learnlib.api; + requires net.automatalib.api; + // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static de.learnlib.tooling.annotation; requires static org.checkerframework.checker.qual; - requires de.learnlib.api; - requires net.automatalib.api; exports de.learnlib.filter.symbol; } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java index c6ac1ada5..b6f2f9558 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java @@ -1,17 +1,35 @@ +/* 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 de.learnlib.oracle.equivalence.mmlt; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Random; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.equivalence.RandomWpMethodEQOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatsContainer; +import de.learnlib.statistic.StatisticsCollector; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.State; import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; import net.automatalib.common.util.string.AbstractPrintable; import net.automatalib.symbol.time.TimedInput; @@ -23,16 +41,20 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** - * RandomWP counterexample search for MMLT learning. - * Key modification: samples prefix from entry prefixes instead of all state prefixes. + * Implements the partial W-method for MMLT learning. The key modification compared to {@link RandomWpMethodEQOracle} is + * that prefixes are sampled from entry prefixes only instead of all state prefixes. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class RandomWpEQOracle implements MMLTEquivalenceOracle { + public static final String KEY_TESTED_WORDS = "WP_TESTED_WORD"; + private final TimedQueryOracle timeOracle; - private final StatsContainer stats; + private final StatisticsCollector statisticsCollector; private final Random random; private final int minSize; @@ -41,10 +63,12 @@ public class RandomWpEQOracle implements MMLTEquivalenceOracle { public RandomWpEQOracle(TimedQueryOracle timeOracle, long randomSeed, - int minSize, int rndAddLength, int bound) { + int minSize, + int rndAddLength, + int bound) { this.timeOracle = timeOracle; - this.stats = Statistics.getContainer(); + this.statisticsCollector = Statistics.getCollector(); this.random = new Random(randomSeed); @@ -54,34 +78,35 @@ public RandomWpEQOracle(TimedQueryOracle timeOracle, } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, + Collection> inputs) { return findCounterExampleInternal(hypothesis, inputs); } - private DefaultQuery, Word>> findCounterExampleInternal(MMLT hypothesis, Collection> inputs) { + private DefaultQuery, Word>> findCounterExampleInternal(MMLT hypothesis, + Collection> inputs) { // Make expanded form of hypothesis: - var hypSemModel = ReducedMMLTSemantics.forMMLT(hypothesis); + ReducedMMLTSemantics hypSemModel = ReducedMMLTSemantics.forMMLT(hypothesis); // Create a list of symbols (for faster access): List> listAlphabet = new ArrayList<>(inputs); // Identify global suffixes: - var globalSuffixes = Automata.characterizingSet(hypSemModel, inputs); + List>> globalSuffixes = Automata.characterizingSet(hypSemModel, inputs); // Get list of prefixes in deterministic order (so we can reproduce experiments easily): - var locationCover = MMLTCover.getMMLTLocationCover(hypothesis, listAlphabet); - var prefixList = locationCover - .values() - .stream() - .sorted(Comparator.comparing(AbstractPrintable::toString)) - .toList(); + Map>> locationCover = MMLTCover.getMMLTLocationCover(hypothesis, listAlphabet); + List>> prefixList = + locationCover.values().stream().sorted(Comparator.comparing(AbstractPrintable::toString)).toList(); // Generate test words: for (int i = 0; i < this.bound; i++) { - stats.increaseCounter("WP_TESTED_WORD", "RandomWpOracle: tested words"); + statisticsCollector.increaseCounter(KEY_TESTED_WORDS, "RandomWpOracle: tested words"); - var sulAnswer = this.generateTestword(prefixList, globalSuffixes, hypothesis, hypSemModel, listAlphabet); - Word> hypAnswer = hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); + DefaultQuery, Word>> sulAnswer = + this.generateTestword(prefixList, globalSuffixes, hypothesis, hypSemModel, listAlphabet); + Word> hypAnswer = + hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); // Found inconsistency if outputs do no match: if (!sulAnswer.getOutput().equals(hypAnswer)) { @@ -106,8 +131,8 @@ private DefaultQuery, Word>> generateTestwor // 2. Add random middle part: int size = minSize; - while ((size > 0) || (this.random.nextDouble() > 1 / (this.rndLen + 1.0))) { - var nextSymbol = alphabet.get(this.random.nextInt(alphabet.size())); + while (size > 0 || this.random.nextDouble() > 1 / (this.rndLen + 1.0)) { + TimedInput nextSymbol = alphabet.get(this.random.nextInt(alphabet.size())); wbTestWord.append(nextSymbol); if (size > 0) { @@ -124,9 +149,10 @@ private DefaultQuery, Word>> generateTestwor } } else { // Identify configuration reached by prefix: - var currentConfig = hypothesis.getSemantics().getState(wbTestWord.toWord()); - var state = hypSemModel.getStateForConfiguration(currentConfig, true); - var localSuffixes = Automata.stateCharacterizingSet(hypSemModel, alphabet, state); + State currentConfig = hypothesis.getSemantics().getState(wbTestWord.toWord()); + assert currentConfig != null; + Integer state = hypSemModel.getStateForConfiguration(currentConfig, true); + List>> localSuffixes = Automata.stateCharacterizingSet(hypSemModel, alphabet, state); if (!localSuffixes.isEmpty()) { suffix = localSuffixes.get(random.nextInt(localSuffixes.size())); @@ -135,8 +161,8 @@ private DefaultQuery, Word>> generateTestwor wbTestWord.append(suffix); // Query SUL: - var testWord = wbTestWord.toWord(); - var sulAnswer = timeOracle.answerQuery(testWord); + Word> testWord = wbTestWord.toWord(); + Word> sulAnswer = timeOracle.answerQuery(testWord); return new DefaultQuery<>(testWord, sulAnswer); } } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index 67c586985..9c1d7b974 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -1,9 +1,26 @@ +/* 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 de.learnlib.oracle.equivalence.mmlt; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Random; @@ -11,6 +28,8 @@ import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.common.util.random.RandomUtil; import net.automatalib.common.util.string.AbstractPrintable; import net.automatalib.symbol.time.InputSymbol; @@ -26,49 +45,57 @@ import org.slf4j.LoggerFactory; /** - * Searches for counterexamples that reveal local resets. - *

          - * - Takes any prefix from a known location - * - Appends a single time step. - * - Appends inputs of all non-delaying inputs that self-loop in that location. - * - Appends timeout. + * Searches for counterexamples that reveal local resets by + *

          . * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class ResetSearchEQOracle implements MMLTEquivalenceOracle { - private final static Logger logger = LoggerFactory.getLogger(ResetSearchEQOracle.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ResetSearchEQOracle.class); private final TimedQueryOracle timeOracle; private final Random locPrefixRandom; - private final double loopInsertPerc; - private final double testedLocPerc; + private final double loopInsertPercentage; + private final double testedLocPercentage; private final long loopingInputSelectionSeed; - public ResetSearchEQOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPerc, double testedLocPerc) { + public ResetSearchEQOracle(TimedQueryOracle timeOracle, + long seed, + double loopInsertPercentage, + double testedLocPercentage) { this.timeOracle = timeOracle; this.locPrefixRandom = new Random(seed); - this.loopInsertPerc = loopInsertPerc; - this.testedLocPerc = testedLocPerc; + this.loopInsertPercentage = loopInsertPercentage; + this.testedLocPercentage = testedLocPercentage; this.loopingInputSelectionSeed = seed; } - private List> getLoopingSymbols(S sourceLoc, List> alphabet, MMLT hypothesis) { + private List> getLoopingSymbols(S sourceLoc, + List> alphabet, + MMLT hypothesis) { + final List> loopingInputs = new ArrayList<>(); - List> loopingInputs = new ArrayList<>(); - for (var sym : alphabet) { - if (!(sym instanceof InputSymbol ndi)) { - continue; // only consider non-delaying inputs, as only these can perform local resets - } - var trans = hypothesis.getTransition(sourceLoc, ndi.symbol()); + for (TimedInput sym : alphabet) { + // only consider non-delaying inputs, as only these can perform local resets + if (sym instanceof InputSymbol ndi) { + final T trans = hypothesis.getTransition(sourceLoc, ndi.symbol()); - // Collect self-loops: - if (trans == null || Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { - loopingInputs.add(sym); + // Collect self-loops: + if (trans == null || Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { + loopingInputs.add(sym); + } } } @@ -76,31 +103,33 @@ private List> getLoopingSymbols(S sourceLoc, List, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { - if (loopInsertPerc == 0) { + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, + Collection> inputs) { + if (loopInsertPercentage == 0) { return null; // oracle is disabled } List> listInputs = new ArrayList<>(inputs); - if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSequence) || - listInputs.stream().noneMatch(s -> s instanceof TimeoutSymbol)) { - logger.warn("ResetSearchOracle requires inputs to contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); + + if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSequence || s instanceof TimeoutSymbol)) { + LOGGER.warn( + "ResetSearchOracle requires inputs to contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); return null; } return this.findCexInternal(hypothesis, listInputs); } - private @Nullable DefaultQuery, Word>> findCexInternal - (MMLT hypothesis, List> inputs) { + private @Nullable DefaultQuery, Word>> findCexInternal(MMLT hypothesis, + List> inputs) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: - var stateCover = MMLTCover.getMMLTLocationCover(hypothesis, inputs); + Map>> stateCover = MMLTCover.getMMLTLocationCover(hypothesis, inputs); // Only keep locations that have at least two stable configs (only these can have local resets): List>> prefixes = new ArrayList<>(); - for (var loc : stateCover.keySet()) { - if (!hypothesis.getSortedTimers(loc).isEmpty() && - hypothesis.getSortedTimers(loc).get(0).initial() > 1) { - prefixes.add(stateCover.get(loc)); + for (Entry>> e : stateCover.entrySet()) { + List> timers = hypothesis.getSortedTimers(e.getKey()); + if (!timers.isEmpty() && timers.get(0).initial() > 1) { + prefixes.add(e.getValue()); } } @@ -108,29 +137,30 @@ private List> getLoopingSymbols(S sourceLoc, List>> chosenPrefixes = RandomUtil.sampleUnique(locPrefixRandom, prefixes, randPrefixes); - - for (var prefix : chosenPrefixes) { + for (Word> prefix : chosenPrefixes) { // Retrieve looping symbols: - var sourceLoc = hypothesis.getSemantics().getState(prefix).getLocation(); - var loopingInputs = getLoopingSymbols(sourceLoc, inputs, hypothesis); + State state = hypothesis.getSemantics().getState(prefix); + assert state != null; + S sourceLoc = state.getLocation(); + List> loopingInputs = getLoopingSymbols(sourceLoc, inputs, hypothesis); if (loopingInputs.isEmpty()) { continue; // no loops } // Determine number of looping symbols we want to append: - int randElements = (int) Math.round(loopInsertPerc * loopingInputs.size()); + int randElements = (int) Math.round(loopInsertPercentage * loopingInputs.size()); randElements = Math.min(loopingInputs.size(), randElements); - List> chosenLoopingInputs = RandomUtil.sampleUnique(new Random(loopingInputSelectionSeed), loopingInputs, randElements); - + List> chosenLoopingInputs = + RandomUtil.sampleUnique(new Random(loopingInputSelectionSeed), loopingInputs, randElements); // Create test word: WordBuilder> wbTestWord = new WordBuilder<>(); @@ -140,17 +170,15 @@ private List> getLoopingSymbols(S sourceLoc, List> testWord = wbTestWord.toWord(); - var hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), testWord); - var sulOutput = timeOracle.answerQuery(testWord); + Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), testWord); + Word> sulOutput = timeOracle.answerQuery(testWord); if (!hypOutput.equals(sulOutput)) { return new DefaultQuery<>(testWord, sulOutput); } - } return null; } - } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java index 9152fc0fc..eb9bd6d15 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/SimulatorEQOracle.java @@ -1,23 +1,38 @@ +/* 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 de.learnlib.oracle.equivalence.mmlt; +import java.util.Collection; + import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.query.DefaultQuery; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - /** * A simulator oracle for MMLTs. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class SimulatorEQOracle implements MMLTEquivalenceOracle { @@ -28,12 +43,12 @@ public SimulatorEQOracle(MMLT refModel) { } @Override - public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, Collection> inputs) { - List> listInputs = new ArrayList<>(inputs); + public @Nullable DefaultQuery, Word>> findCounterExample(MMLT hypothesis, + Collection> inputs) { + final Word> separatingWord = MMLTs.findSeparatingWord(refModel, hypothesis, inputs); - var separatingWord = MMLTs.findSeparatingWord(refModel, hypothesis, listInputs); if (separatingWord != null) { - var sulOutput = refModel.getSemantics().computeOutput(separatingWord); + final Word> sulOutput = refModel.getSemantics().computeOutput(separatingWord); return new DefaultQuery<>(separatingWord, sulOutput); } else { return null; diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index 9c29aeb42..11cf9bb0c 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.oracle.membership; import java.util.ArrayList; @@ -8,11 +23,11 @@ import java.util.Map; import java.util.stream.Collectors; -import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.Query; import de.learnlib.sul.TimedSUL; -import net.automatalib.automaton.mmlt.MealyTimerInfo; +import de.learnlib.time.MMLTModelParams; +import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -25,14 +40,16 @@ import org.slf4j.LoggerFactory; /** - * Implements a timed query oracle for MMLT learning. + * Implements a {@link TimedQueryOracle} given a {@link TimedSUL}. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class TimedSULOracle implements TimedQueryOracle { - private final static Logger logger = LoggerFactory.getLogger(TimedSULOracle.class); + private static final Logger LOGGER = LoggerFactory.getLogger(TimedSULOracle.class); /** * To ensure globally unique timer names, we index them according to this counter. @@ -50,7 +67,7 @@ public TimedSULOracle(TimedSUL sul, MMLTModelParams modelParams) { @Override public void processQueries(Collection, Word>>> queries) { - for (var q : queries) { + for (Query, Word>> q : queries) { this.querySuffixOutputInternal(q); } } @@ -72,17 +89,18 @@ public TimerQueryResult queryTimers(Word> prefix, long maxTotal /** * Identifies the time at which the next known timeout(s) are expected. * - * @param timeouts Known timeouts - * @param currentTime Current time. - * @return Next timeout time. + * @param timeouts + * known timeouts + * @param currentTime + * current time. + * + * @return next timeout time */ - private long calcNextExpectedTimeout(List> timeouts, long currentTime) { - if (timeouts.isEmpty()) { - throw new AssertionError(); - } + private long calcNextExpectedTimeout(List> timeouts, long currentTime) { + assert !timeouts.isEmpty(); long minNext = Long.MAX_VALUE; - for (var to : timeouts) { + for (TimerInfo to : timeouts) { long occurrences = currentTime / to.initial(); long nextOcc = (occurrences + 1) * to.initial(); // time of next occ @@ -91,9 +109,7 @@ private long calcNextExpectedTimeout(List> timeouts, long c } } - if (minNext == Long.MAX_VALUE) { - throw new AssertionError(); - } + assert minNext != Long.MAX_VALUE; return minNext; } @@ -103,21 +119,23 @@ private String getUniqueTimerName() { } /** - * Identifies timeouts in the current location by waiting at most [maxDelay]. + * Identifies timeouts in the current location by waiting at most {@code maxTotalWaitingTime}. *

          - * All inferred timers are initially considered periodic. - * Stops when reaching maxTotalWaitingTime OR when an expected timeout does not occur. - * In the latter case, the "aborted" flag is set. + * All inferred timers are initially considered periodic. Stops when reaching {@code maxTotalWaitingTime} or when an + * expected timeout does not occur. In the latter case, the {@link TimerQueryResult#aborted()}} flag is set. + * + * @param maxTotalWaitingTime + * maximum time until timeouts are collected * - * @param maxTotalWaitingTime Maximum time until timeouts are collected. - * @return List of periodic timeouts. Null, if none observed. + * @return list of periodic timeouts or {@code null} if none observed */ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { if (maxTotalWaitingTime < this.modelParams.maxTimeoutWaitingTime()) { - throw new IllegalArgumentException("Timer query waiting time must be at least max. waiting time for a single timeout."); + throw new IllegalArgumentException( + "Timer query waiting time must be at least max. waiting time for a single timeout."); } - List> knownTimers = new ArrayList<>(); + List> knownTimers = new ArrayList<>(); // Wait for the first timeout: TimedOutput firstTimeout = this.sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); @@ -126,10 +144,10 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { } if (this.modelParams.outputCombiner().isCombinedSymbol(firstTimeout.symbol())) { - logger.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); + LOGGER.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); } - knownTimers.add(new MealyTimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol(), null)); + knownTimers.add(new TimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol(), null)); // Wait for further timeouts: long currentTimeStep = firstTimeout.delay(); // already waited for first timeout @@ -156,7 +174,8 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { // Compare observed timeout with expectation: long nextActualTime = nextOutput.delay() + currentTimeStep; - TimerCheckResult evalResult = evaluateNextTimer(nextActualTime, nextExpectedTime, nextOutput, knownTimers); + TimerCheckResult evalResult = + evaluateNextTimer(nextActualTime, nextExpectedTime, nextOutput, knownTimers); if (evalResult.newTimer() != null) { knownTimers.add(evalResult.newTimer()); } else if (evalResult.inconsistent()) { @@ -167,44 +186,55 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { currentTimeStep = nextActualTime; } - knownTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + knownTimers.sort(Comparator.comparingLong(TimerInfo::initial)); return new TimerQueryResult<>(inconsistent, knownTimers); } - private record TimerCheckResult(@Nullable MealyTimerInfo newTimer, boolean inconsistent) {} - - private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { + private TimerCheckResult evaluateNextTimer(long nextActualTime, + long nextExpectedTime, + TimedOutput nextOutput, + List> knownTimers) { if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: - var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol(), null); + TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol(), null); return new TimerCheckResult<>(newTimer, false); } else if (nextActualTime == nextExpectedTime) { // Timeout occurred at expected time -> check if matching expected output: Map expectedOutputs = knownTimers.stream() - .filter(t -> nextExpectedTime % t.initial() == 0) - .map(t -> modelParams.outputCombiner().separateSymbols(t.output())) // separate output of timers with same initial value - .flatMap(Collection::stream) - .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences - - Map actualOutputs = this.modelParams.outputCombiner().separateSymbols(nextOutput.symbol()) - .stream() - .collect(Collectors.groupingBy(e -> e, Collectors.counting())); + .filter(t -> nextExpectedTime % t.initial() == 0) + .map(t -> modelParams.outputCombiner() + .separateSymbols(t.output())) // separate output of timers with same initial value + .flatMap(Collection::stream) + .collect(Collectors.groupingBy(t -> t, + Collectors.counting())); // count occurrences + + Map actualOutputs = this.modelParams.outputCombiner() + .separateSymbols(nextOutput.symbol()) + .stream() + .collect(Collectors.groupingBy(e -> e, Collectors.counting())); // Any missing outputs? - boolean missingOutputs = expectedOutputs.keySet().stream() - .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < expectedOutputs.get(o)); // less than expected + boolean missingOutputs = expectedOutputs.keySet() + .stream() + .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < + expectedOutputs.get(o)); // less than expected if (missingOutputs) { // Same time but missing output -> missed location change: return new TimerCheckResult<>(null, true); } // Any new outputs? - List newOutputs = actualOutputs.keySet().stream() - .filter(o -> expectedOutputs.getOrDefault(o, 0L) < actualOutputs.get(o)) // less than actual - .toList(); + List newOutputs = actualOutputs.keySet() + .stream() + .filter(o -> expectedOutputs.getOrDefault(o, 0L) < + actualOutputs.get(o)) // less than actual + .toList(); if (!newOutputs.isEmpty()) { // Same time and more outputs -> add new timer that uses the new outputs: - var newTimer = new MealyTimerInfo<>(getUniqueTimerName(), nextActualTime, this.modelParams.outputCombiner().combineSymbols(newOutputs), null); + TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), + nextActualTime, + this.modelParams.outputCombiner().combineSymbols(newOutputs), + null); return new TimerCheckResult<>(newTimer, false); } } else { @@ -214,7 +244,6 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpe return new TimerCheckResult<>(null, false); } - private void querySuffixOutputInternal(Query, Word>> query) { sul.pre(); @@ -222,7 +251,7 @@ private void querySuffixOutputInternal(Query, Word> // Query the SUL, one symbol at a time: WordBuilder> wbOutput = new WordBuilder<>(); - for (var s : query.getSuffix()) { + for (TimedInput s : query.getSuffix()) { if (s instanceof TimeoutSymbol) { TimedOutput output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); if (output != null) { @@ -251,9 +280,9 @@ private void querySuffixOutputInternal(Query, Word> } } - sul.post(); query.answer(wbOutput.toWord()); } + private record TimerCheckResult(@Nullable TimerInfo newTimer, boolean inconsistent) {} } diff --git a/test-support/learner-it-support/pom.xml b/test-support/learner-it-support/pom.xml index 703a76c22..bda6c74f3 100644 --- a/test-support/learner-it-support/pom.xml +++ b/test-support/learner-it-support/pom.xml @@ -71,10 +71,6 @@ limitations under the License. net.automatalib automata-api - - net.automatalib - automata-commons-util - net.automatalib automata-core diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java index ad99e4c61..be66a2e66 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractLearnerVariantITCase.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.List; -import java.util.Random; import de.learnlib.algorithm.LearningAlgorithm; import de.learnlib.logging.Category; @@ -90,7 +89,7 @@ public void testLearning() { } if (!ceQueries.isEmpty()) { - DefaultQuery oldCe = ceQueries.get(new Random(42).nextInt(ceQueries.size())); + DefaultQuery oldCe = ceQueries.get(0); Assert.assertFalse(learner.refineHypothesis(oldCe), "Learner should not report a hypothesis update on outdated counterexample"); } diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java index 522708592..fe5625ab1 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java @@ -71,7 +71,9 @@ private List> createAllVariantsITCase(MMLTLearnin * {@link LearnerVariantList variant list}. * * @param - * input symbol type + * input symbol type (of non-delaying inputs) + * @param + * output symbol type * @param alphabet * the input alphabet * @param mqOracle diff --git a/test-support/learner-it-support/src/main/java/module-info.java b/test-support/learner-it-support/src/main/java/module-info.java index 92bc5f47a..f98b1411f 100644 --- a/test-support/learner-it-support/src/main/java/module-info.java +++ b/test-support/learner-it-support/src/main/java/module-info.java @@ -37,7 +37,6 @@ requires de.learnlib.oracle.equivalence; requires de.learnlib.testsupport.example; requires net.automatalib.api; - requires net.automatalib.common.util; requires net.automatalib.util; requires org.slf4j; requires org.testng; diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java index eae6e956c..d52da0539 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/DefaultLearningExample.java @@ -15,7 +15,7 @@ */ package de.learnlib.testsupport.example; -import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.VPAlphabet; diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java index fa6b7a68c..9c5b9232e 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java @@ -15,7 +15,7 @@ */ package de.learnlib.testsupport.example; -import de.learnlib.algorithm.MMLTModelParams; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.VPAlphabet; @@ -69,6 +69,7 @@ interface MMLTLearningExample extends LearningExample, MMLT< MMLTModelParams getParams(); + @Override default Alphabet> getAlphabet() { return getReferenceAutomaton().getSemantics().getInputAlphabet(); } diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java index f462746c7..4c8fc9920 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExamples.java @@ -132,12 +132,12 @@ public static List> createDFAExamples() { } public static List> createMMLTExamples() { - return Arrays.asList(MMLTExamples.HVAC(), - MMLTExamples.SCTP(), - MMLTExamples.SensorCollector(), - MMLTExamples.WM(), - MMLTExamples.Oven(), - MMLTExamples.WSN()); + return Arrays.asList(MMLTExamples.hvac(), + MMLTExamples.sctp(), + MMLTExamples.sensorCollector(), + MMLTExamples.wm(), + MMLTExamples.oven(), + MMLTExamples.wsn()); } public static List> createSPAExamples() { diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java index 4fc10668e..372c92a86 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/mmlt/MMLTExamples.java @@ -1,55 +1,80 @@ +/* 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 de.learnlib.testsupport.example.mmlt; import java.io.IOException; import java.io.InputStream; -import de.learnlib.algorithm.MMLTModelParams; import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; +import de.learnlib.time.MMLTModelParams; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTInputModelData; +import net.automatalib.serialization.dot.DOTInputModelDeserializer; import net.automatalib.serialization.dot.DOTParsers; import net.automatalib.util.automaton.mmlt.MMLTs; -public class MMLTExamples { +/** + * A collection of {@link MMLT}-based learning examples. + */ +public final class MMLTExamples { + + private MMLTExamples() { + // prevent instantiation + } /** - * Returns an MMLT model of an HVAC system. + * Returns an MMLT example of an HVAC system. *

          * The system has been adapted from: Taylor and Taylor: Patterns in the Machine * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample HVAC() { + public static MMLTLearningExample hvac() { return new Example("HVAC"); } /** - * Returns an MMLT model of an endpoint in the stream control and transmission protocol. + * Returns an MMLT example of an endpoint in the stream control and transmission protocol. *

          * The model has been adapted from: Stewart et al.: Stream Control Transmission Protocol (RFC 9260, Figure 3) * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample SCTP() { + public static MMLTLearningExample sctp() { return new Example("SCTP"); } /** - * Returns an MMLT model of a sensor collector. + * Returns an MMLT example of a sensor collector. *

          * The sensor measures particulate matter and ambient noise. The measurement program automatically ends after some * time. The program may be restarted at any time. Alternatively, a self-check program can be entered. This also * ends after some time and may be aborted. At the end of either program, the collected data may be retrieved. * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample SensorCollector() { + public static MMLTLearningExample sensorCollector() { return new Example("sensor_collector"); } /** - * Returns an MMLT model of a washing machine. + * Returns an MMLT example of a washing machine. *

          * The machine is initially off. After powering it on and closing the door, the user can start either the short or * the normal program. An open door prevents starting and triggers a warning. Not choosing a program within 10 @@ -64,40 +89,42 @@ public static MMLTLearningExample SensorCollector() { * the drum immediately. Once done, the door is unlocked, a message is shown, and the machine beeps repeatedly until * the user presses any button or opens the door. * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample WM() { + public static MMLTLearningExample wm() { return new Example("WM"); } /** - * Returns an MMLT model of an oven with a time-controlled baking program. + * Returns an MMLT example of an oven with a time-controlled baking program. *

          * After powering the oven on, the oven remains idle until the program is started. During the program, the oven * regularly measures and adjusts the temperature. At the end of the program, an alarm sounds. Then, the user may * extend the program. If not extended, the program ends either when the user opens the door, presses a button, or a * timeout occurs. * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample Oven() { + public static MMLTLearningExample oven() { return new Example("Oven"); } /** - * Returns an MMLT model of a wireless sensor node. + * Returns an MMLT example of a wireless sensor node. *

          * The node regularly collects and transmits data. If the battery is low, no data is transmitted. Then, a user may * collect the data manually. The node can be shut down at any time. If the battery is empty, it is shut down * automatically. * - * @return LocalTimerMealyModel + * @return a learning example for the specified machine */ - public static MMLTLearningExample WSN() { + public static MMLTLearningExample wsn() { return new Example("WSN"); } - private static class Example implements MMLTLearningExample { + private static final class Example implements MMLTLearningExample { + + private static final int SCTP_TIMEOUT = 9000; // SCTP needs more waiting time private final String name; private final MMLT mmlt; @@ -106,26 +133,29 @@ private static class Example implements MMLTLearningExample { private Example(String name) { this.name = name; - var silentOutput = "void"; - var outputCombiner = StringSymbolCombiner.getInstance(); - var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + final String silentOutput = "void"; + final StringSymbolCombiner outputCombiner = StringSymbolCombiner.getInstance(); + final DOTInputModelDeserializer> parser = + DOTParsers.mmlt(silentOutput, outputCombiner); try (InputStream is = MMLTExamples.class.getResourceAsStream("/mmlt/" + name + ".dot")) { - var model = parser.readModel(is); - var automaton = model.model; + final DOTInputModelData> model = parser.readModel(is); + final CompactMMLT automaton = model.model; - long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(automaton); - long maxTimerQueryWaitingFinal = MMLTs.getMaximumInitialTimerValue(automaton) * 2; + final long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(automaton); + final long maxTimerQueryWaitingFinal; if (name.contains("SCTP")) { - maxTimerQueryWaitingFinal = 9000; // SCTP needs more waiting time + maxTimerQueryWaitingFinal = SCTP_TIMEOUT; + } else { + maxTimerQueryWaitingFinal = MMLTs.getMaximumInitialTimerValue(automaton) * 2; } this.mmlt = automaton; this.params = - new MMLTModelParams<>(silentOutput, maxTimeoutDelay, maxTimerQueryWaitingFinal, outputCombiner); + new MMLTModelParams<>(silentOutput, outputCombiner, maxTimeoutDelay, maxTimerQueryWaitingFinal); } catch (IOException | FormatException e) { - throw new RuntimeException("Unable to load model " + name, e); + throw new IllegalStateException("Unable to load model " + name, e); } } From fe3979f3b7119fe68e3867a9270d798e1af46e9e Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Sun, 23 Nov 2025 17:07:25 +0100 Subject: [PATCH 42/55] cleanups --- .../main/java/de/learnlib/sul/TimedSUL.java | 32 ++++++++++--------- .../de/learnlib/example/mmlt/Example1.java | 4 +-- .../filter/statistic/sul/CounterTimedSUL.java | 5 +-- ...racle.java => RandomWpMethodEQOracle.java} | 18 +++++------ pom.xml | 1 + 5 files changed, 32 insertions(+), 28 deletions(-) rename oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/{RandomWpEQOracle.java => RandomWpMethodEQOracle.java} (92%) diff --git a/api/src/main/java/de/learnlib/sul/TimedSUL.java b/api/src/main/java/de/learnlib/sul/TimedSUL.java index 009406abf..28aca4467 100644 --- a/api/src/main/java/de/learnlib/sul/TimedSUL.java +++ b/api/src/main/java/de/learnlib/sul/TimedSUL.java @@ -15,22 +15,24 @@ */ package de.learnlib.sul; +import java.util.ArrayList; +import java.util.List; + import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; -import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.Nullable; /** * Interface for a SUL with MMLT semantics. * * @param - * Input type for non-delaying inputs + * input type (of non-delaying inputs) * @param - * Output symbol type + * output symbol type */ public interface TimedSUL extends SUL, TimedOutput> { @@ -54,12 +56,12 @@ default void follow(Word> input) { * maximum waiting time to use for {@link TimeoutSymbol}s. */ default void follow(Word> input, long maxTimeout) { - for (TimedInput s : input) { - if (s instanceof InputSymbol ndi) { + for (TimedInput i : input) { + if (i instanceof InputSymbol ndi) { this.step(ndi); - } else if (s instanceof TimeStepSequence) { - this.collectTimeouts((TimeStepSequence) s); - } else if (s instanceof TimeoutSymbol) { + } else if (i instanceof TimeStepSequence tss) { + this.collectTimeouts(tss); + } else if (i instanceof TimeoutSymbol) { if (maxTimeout <= 0) { throw new IllegalArgumentException("Must supply timeout when using timeout symbols."); } @@ -97,15 +99,15 @@ default void follow(Word> input, long maxTimeout) { } /** - * Waits for the specified time and returns all observed timeouts. + * Waits for the duration of the given time step and returns all observed timeouts. * * @param input - * Waiting time. + * the time step to wait. * - * @return Observed timeouts. Empty, if none. + * @return a list of observed timeouts (may be empty if no time outs occurred in the given time) */ - default Word> collectTimeouts(TimeStepSequence input) { - WordBuilder> wbOutput = new WordBuilder<>(); + default List> collectTimeouts(TimeStepSequence input) { + List> timeouts = new ArrayList<>(); long remainingTime = input.timeSteps(); while (remainingTime > 0) { @@ -114,12 +116,12 @@ default Word> collectTimeouts(TimeStepSequence input) { // No timer will expire during remaining waiting time: break; } else { - wbOutput.append(nextTimeout); + timeouts.add(nextTimeout); remainingTime -= nextTimeout.delay(); } } - return wbOutput.toWord(); + return timeouts; } @Override diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 3990d78c7..8e39eb6a0 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -14,7 +14,7 @@ import de.learnlib.filter.statistic.sul.CounterTimedSUL; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.equivalence.MMLTEQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.RandomWpEQOracle; +import de.learnlib.oracle.equivalence.mmlt.RandomWpMethodEQOracle; import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; @@ -68,7 +68,7 @@ public static void main(String[] args) { MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); - chainOracle.addOracle(new CounterEQOracle<>(new RandomWpEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); chainOracle.addOracle(new CounterEQOracle<>(new SimulatorEQOracle<>(model.getReferenceAutomaton()), "sim")); // ensure that we eventually find an accurate model // Set up our L* learner: diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index 173dd034a..9643c2c64 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -15,13 +15,14 @@ */ package de.learnlib.filter.statistic.sul; +import java.util.List; + import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.TimedSUL; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -80,7 +81,7 @@ public TimedOutput step(InputSymbol input) { } @Override - public Word> collectTimeouts(TimeStepSequence input) { + public List> collectTimeouts(TimeStepSequence input) { stats.increaseCounter(withPrefix("sul_total_time"), withPrefix("Total query time"), input.timeSteps()); return this.delegate.collectTimeouts(input); } diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java similarity index 92% rename from oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java rename to oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java index b6f2f9558..097c9699a 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java @@ -24,7 +24,6 @@ import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.oracle.equivalence.RandomWpMethodEQOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; @@ -41,15 +40,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** - * Implements the partial W-method for MMLT learning. The key modification compared to {@link RandomWpMethodEQOracle} is - * that prefixes are sampled from entry prefixes only instead of all state prefixes. + * Implements the partial W-method for MMLT learning. The key modification compared to + * {@link de.learnlib.oracle.equivalence.RandomWpMethodEQOracle} is that prefixes are sampled from entry prefixes only + * instead of all state prefixes. * * @param * input symbol type (of non-delaying inputs) * @param * output symbol type */ -public class RandomWpEQOracle implements MMLTEquivalenceOracle { +public class RandomWpMethodEQOracle implements MMLTEquivalenceOracle { public static final String KEY_TESTED_WORDS = "WP_TESTED_WORD"; @@ -61,11 +61,11 @@ public class RandomWpEQOracle implements MMLTEquivalenceOracle { private final int rndLen; private final int bound; - public RandomWpEQOracle(TimedQueryOracle timeOracle, - long randomSeed, - int minSize, - int rndAddLength, - int bound) { + public RandomWpMethodEQOracle(TimedQueryOracle timeOracle, + long randomSeed, + int minSize, + int rndAddLength, + int bound) { this.timeOracle = timeOracle; this.statisticsCollector = Statistics.getCollector(); diff --git a/pom.xml b/pom.xml index c1d9e1c0b..9835d620e 100644 --- a/pom.xml +++ b/pom.xml @@ -105,6 +105,7 @@ limitations under the License. Paul Kogel + p.kogel@tu-berlin.de TU Berlin, Software and Embedded Systems Engineering https://www.tu.berlin/sese From 7d8babb11f56d6e08aa5f86ba37bb4cc2e7ef6b1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 24 Nov 2025 09:41:26 +0100 Subject: [PATCH 43/55] Removed sorting of prefixes from location cover, to reflect automataLib updates. --- .../oracle/equivalence/mmlt/RandomWpMethodEQOracle.java | 3 +-- .../learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java index 097c9699a..29bfe291c 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java @@ -96,8 +96,7 @@ private DefaultQuery, Word>> findCounterExample // Get list of prefixes in deterministic order (so we can reproduce experiments easily): Map>> locationCover = MMLTCover.getMMLTLocationCover(hypothesis, listAlphabet); - List>> prefixList = - locationCover.values().stream().sorted(Comparator.comparing(AbstractPrintable::toString)).toList(); + List>> prefixList = new ArrayList<>(locationCover.values()); // Generate test words: for (int i = 0; i < this.bound; i++) { diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index 9c1d7b974..800ceddb9 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -133,9 +133,6 @@ private List> getLoopingSymbols(S sourceLoc, } } - // Sort alphabetically, so that experiments are easily reproducible: - prefixes.sort(Comparator.comparing(AbstractPrintable::toString)); - // Determine number of tested locations: int randPrefixes = (int) Math.round(testedLocPercentage * prefixes.size()); if (randPrefixes == 0) { From 811b8834056ea07949deaf62e768d2ded6f02f0a Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 24 Nov 2025 11:17:56 +0100 Subject: [PATCH 44/55] Updated MMLT examples. --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 35 ++++-- .../de/learnlib/example/mmlt/Example1.java | 94 +++----------- .../de/learnlib/example/mmlt/Example2.java | 113 +++++++++++++++++ .../de/learnlib/example/mmlt/Example3.java | 117 ++++++++++++++++++ .../de/learnlib/example/mmlt/ExampleUtil.java | 55 ++++++++ 5 files changed, 330 insertions(+), 84 deletions(-) create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/Example2.java create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/Example3.java create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java 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 4877e7949..8fa6c490c 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 @@ -8,6 +8,8 @@ import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; +import de.learnlib.filter.symbol.CachedSymbolFilter; import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; import de.learnlib.algorithm.lstar.closing.ClosingStrategy; @@ -65,6 +67,25 @@ public class ExtensibleLStarMMLT implements OTLearner>> initialSuffixes; private final MMLTCounterexampleHandler cexAnalyzer; + /** + * Instantiates a new Rivest-Schapire learner for MMLTs. + *

          + * Uses the close-shortest strategy for closing the observation table, + * binary-backwards search for decomposing counterexamples, and no symbol filter. + * + * @param alphabet Alphabet of non-delaying inputs + * @param modelParams LocalTimerMealyModel parameters + * @param initialSuffixes Initial set of suffixes. May be empty. + * @param timeOracle The output query oracle for MMLTs. + */ + public ExtensibleLStarMMLT(Alphabet alphabet, + MMLTModelParams modelParams, + List>> initialSuffixes, + TimedQueryOracle timeOracle) { + this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, + new CachedSymbolFilter<>(new AcceptAllSymbolFilter<>()), AcexAnalyzers.BINARY_SEARCH_BWD); + } + /** * Instantiates a new Rivest-Schapire learner for MMLTs. *

          @@ -116,8 +137,8 @@ public ExtensibleLStarMMLT(Alphabet alphabet, // Init hypothesis data: this.hypData = new MMLTHypDataContainer<>(internalAlphabet, modelParams, - new MMLTObservationTable<>(internalAlphabet, - modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); + new MMLTObservationTable<>(internalAlphabet, + modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); this.cexAnalyzer = new MMLTCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); this.symbolFilter = symbolFilter; @@ -270,9 +291,9 @@ private MMLTOutputInconsistency toOutputInconsistency(DefaultQuery(shortQuery.getPrefix(), - shortQuery.getSuffix(), - shortQuery.getOutput(), - shortHypOutput); + shortQuery.getSuffix(), + shortQuery.getOutput(), + shortHypOutput); } private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { @@ -379,8 +400,8 @@ private void handleMissingTimeoutChange(Row> spRow, TimerInfo t.initial() > timeout.initial()) - .map(TimerInfo::name).toList(); + .filter(t -> t.initial() > timeout.initial()) + .map(TimerInfo::name).toList(); subsequentTimers.forEach(locationTimerInfo::removeTimer); // Change from periodic to one-shot: diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 8e39eb6a0..25392e1ca 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -2,44 +2,33 @@ import java.util.ArrayList; import java.util.List; -import java.util.Random; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.MMLTSimulatorSUL; -import de.learnlib.filter.SymbolFilter; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; -import de.learnlib.filter.statistic.oracle.CounterEQOracle; import de.learnlib.filter.statistic.sul.CounterTimedSUL; -import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; -import de.learnlib.oracle.equivalence.MMLTEQOracleChain; -import de.learnlib.oracle.equivalence.mmlt.RandomWpMethodEQOracle; -import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.filter.symbol.CachedSymbolFilter; -import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; -import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; -import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatisticsCollector; import de.learnlib.testsupport.example.mmlt.MMLTExamples; -import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.visualization.Visualization; import net.automatalib.word.Word; /** - * This example shows how to learn a Mealy machine with local timers, + * This example shows how to learn a Mealy machine with local timers (MMLT), * an automaton model for real-time systems. + *

          + * MMLTs extend Mealy machines with multiple timers. More information about MMLTs can be found + * in the included README file. + *

          + * This example uses a very basic learner set-up. */ public class Example1 { public static void main(String[] args) { + // We use the included sensor collector model as reference automaton: var model = MMLTExamples.sensorCollector(); // We first create a statistics container. @@ -64,73 +53,24 @@ public static void main(String[] args) { // We use a query oracle to answer queries from the learner: var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); - // We use a chain of different equivalence oracles: - MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); - chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); - chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); - chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); - chainOracle.addOracle(new CounterEQOracle<>(new SimulatorEQOracle<>(model.getReferenceAutomaton()), "sim")); // ensure that we eventually find an accurate model + // In the basic set-up, we use a simulator oracle to answer equivalence queries. + // This oracle has perfect knowledge of the reference automaton. + var eqOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); // Set up our L* learner: + + // We provide the learner with an initial set of suffixes. + // We include all untimed inputs and the symbolic timeout symbol, which causes the learner to wait + // until the next timeout (but no longer than model.getParams().maxTimeoutWaitingTime()). List>> suffixes = new ArrayList<>(); model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - // A symbol filter allows us to reduce queries by exploiting prior knowledge. - // For this example, we use a AbstractRandomSymbolFilter. This filter correctly predicts - // whether a transition silently self-loops with an accuracy of 90%: - SymbolFilter, InputSymbol> filter = - new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); - - filter = new MMLTStatisticsSymbolFilter<>(model.getReferenceAutomaton(), filter, stats); - var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - - var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, cachedFilter); + var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle); // Start learning: - runExperiment(learner, chainOracle, stats, 100); - - // Troubleshooting - // If you attempt to learn a model of some application and the learner - // throws assertion errors or illegal state exceptions, - // your SUL likely has no MMLT semantics. - // In this case, you can try to learn a partial model by excluding TimeStepSymbol - // from the input alphabet for the counterexample search: - // Replace tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - // with: tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList()); - } - - private static void runExperiment(ExtensibleLStarMMLT learner, - MMLTEquivalenceOracle tester, - StatisticsCollector statisticsCollector, int maxRounds) { - statisticsCollector.startOrResumeClock("learningRt", "Processing time"); - learner.startLearning(); - - var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - statisticsCollector.increaseCounter("roundCount", "CEX queries"); + ExampleUtil.runExperiment(learner, eqOracle, stats, 100); - int roundCount = 1; - while (cex != null && roundCount < maxRounds) { - learner.refineHypothesis(cex); - hyp = learner.getHypothesisModel(); - cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - statisticsCollector.increaseCounter("roundCount", null); - roundCount += 1; - } - statisticsCollector.pauseClock("learningRt"); - - final var finalHypothesis = learner.getHypothesisModel(); - - // Add some more stats: - statisticsCollector.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); - - // Print final result + statistics: - System.out.println(statisticsCollector.printStats()); - - new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); - - System.out.println("Final hypothesis:"); - Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); } + } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example2.java b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java new file mode 100644 index 000000000..b55f76405 --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java @@ -0,0 +1,113 @@ +package de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.oracle.CounterEQOracle; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.filter.symbol.CachedSymbolFilter; +import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; +import de.learnlib.oracle.equivalence.MMLTEQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.RandomWpMethodEQOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.Statistics; +import de.learnlib.statistic.StatisticsCollector; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.visualization.Visualization; +import net.automatalib.word.Word; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static de.learnlib.example.mmlt.ExampleUtil.runExperiment; + +/** + * This example shows a basic set-up of the MMLT-learner for a black-box setting. + *

          + * For this, we use a chain of different equivalence oracles + * that can be applied if the reference automaton is not known. + */ +public class Example2 { + + public static void main(String[] args) { + var model = MMLTExamples.sensorCollector(); + + // We first create a statistics container. + // This container will store various statistical data during learning: + var stats = Statistics.getCollector(); + stats.addText("LocalTimerMealyModel", null, model.toString()); + stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); + + // ====================== + // Set up the pipeline: + // We use a simulator SUL to simulate our automaton: + var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); + + // We count all operations that are performed on the SUL with a stats-SUL: + var statsAfterCache = new CounterTimedSUL<>(sul); + + // We use a cache to avoid redundant operations: + var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.getParams()); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.getParams().maxTimeoutWaitingTime()); + + // We use a query oracle to answer queries from the learner: + var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); + + // We use a chain of different equivalence oracles to find counterexamples more efficiently: + MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); + // A cache oracle tests if the current hypothesis and the reference automaton give the same outputs + // for all words that have already been queried. As the words have already been queried, this + // executes no additional queries on the SUL: + chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); + + // A ResetSearchOracle tests for missing local resets, which often require many and/or long test words + // when using random-based testing. We configure the tester to consider all transitions that might cause a reset: + chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); + + // Finally, we add an MMLT-specific RandomWp oracle: + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + + // Set up our L* learner: + List>> suffixes = new ArrayList<>(); + model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle); + + // Start learning: + var finalModel = runExperiment(learner, chainOracle, stats, 100); + + // In this set-up, we actually know the reference automaton. + // This allows us to check that we learned an accurate model: + var simOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); + if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { + throw new AssertionError("Incorrect model learned."); + } + + // Troubleshooting + // If you attempt to learn a model of some application and the learner + // throws assertion errors or illegal state exceptions, + // your SUL likely has no MMLT semantics. + // In this case, you can try to learn a partial model by excluding TimeStepSymbol + // from the input alphabet for the counterexample search: + // In your learn-loop (see ExampleUtil), replace + // tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + // with: tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet().stream().filter(s -> !(s instanceof TimeStepSymbol)).toList()); + } + +} diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example3.java b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java new file mode 100644 index 000000000..74c500a7f --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java @@ -0,0 +1,117 @@ +package de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; +import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.oracle.CounterEQOracle; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.filter.symbol.CachedSymbolFilter; +import de.learnlib.oracle.equivalence.MMLTEQOracleChain; +import de.learnlib.oracle.equivalence.mmlt.RandomWpMethodEQOracle; +import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.statistic.Statistics; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.word.Word; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static de.learnlib.example.mmlt.ExampleUtil.runExperiment; + +/** + * This example illustrates how to learn MMLTs with symbol filtering. + *

          + * A symbol filter is a component that provides information about transitions that might silently self-loop. + * The learner exploits this information to avoid redundant queries on the SUL. + * The symbol filter might incorrectly classify a transition as silent self-loop. + * The MMLT-learner detects and corrects such errors. + *

          + * LearnLib includes four types of symbol filter: + *

            + *
          • AcceptAllSymbolFilter: no transition is considered as silent self-loop. + * This is the default behavior if no filter is provided.
          • + *
          • PerfectSymbolFilter: simulates perfect knowledge of silent self-loops. + * Perfect knowledge is useful for benchmarking but rarely the case in practice.
          • + *
          • IgnoreAllSymbolFilter: considers all transitions to be silent self-loops. + * If no knowledge of the SUL is available, this filter still often yields strong performance improvements.
          • + *
          • RandomSymbolFilter: simulates incorrect responses with a certain percentage.
          • + *
          + *

          + * When you apply MMLT-learning in practice, you usually want to implement your own symbol filter that exploits specific + * domain knowledge. + */ +public class Example3 { + + public static void main(String[] args) { + var model = MMLTExamples.sensorCollector(); + + // We first create a statistics container. + // This container will store various statistical data during learning: + var stats = Statistics.getCollector(); + stats.addText("LocalTimerMealyModel", null, model.toString()); + stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); + + // ====================== + // Set up the pipeline: + // We use a simulator SUL to simulate our automaton: + var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); + + // We count all operations that are performed on the SUL with a stats-SUL: + var statsAfterCache = new CounterTimedSUL<>(sul); + + // We use a cache to avoid redundant operations: + var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, model.getParams()); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, model.getParams().maxTimeoutWaitingTime()); + + // We use a query oracle to answer queries from the learner: + var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); + + // We use a chain of different equivalence oracles (see Example2): + MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); + chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); + chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + + // Set up our L* learner: + List>> suffixes = new ArrayList<>(); + model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + // A symbol filter allows us to reduce queries by exploiting prior knowledge. + // For this example, we use a AbstractRandomSymbolFilter. This filter correctly predicts + // whether a transition silently self-loops with an accuracy of 90%: + SymbolFilter, InputSymbol> filter = + new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); + + // We wrap our filter with a StatisticsFilter to collect useful statistics about the filter: + filter = new MMLTStatisticsSymbolFilter<>(model.getReferenceAutomaton(), filter, stats); + + // The learner may need to update incorrect responses of the filter. + // To facilitate this, we wrap our filter with a CachedFilter: + var cachedFilter = new CachedSymbolFilter<>(filter); + + var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, cachedFilter); + + // Start learning: + var finalModel = runExperiment(learner, chainOracle, stats, 100); + + // In this set-up, we actually know the reference automaton. + // This allows us to check that we learned an accurate model: + var simOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); + if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { + throw new AssertionError("Incorrect model learned."); + } + } + +} diff --git a/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java b/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java new file mode 100644 index 000000000..0de82bf9e --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java @@ -0,0 +1,55 @@ +package de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.query.DefaultQuery; +import de.learnlib.statistic.StatisticsCollector; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.visualization.Visualization; +import net.automatalib.word.Word; + +public class ExampleUtil { + static MMLT runExperiment(ExtensibleLStarMMLT learner, + EquivalenceOracle.MMLTEquivalenceOracle tester, + StatisticsCollector statisticsCollector, int maxRounds) { + statisticsCollector.startOrResumeClock("learningRt", "Processing time"); + learner.startLearning(); + + // Our experiment follows the usual learn-loop: + // We retrieve a hypothesis and ask for a counterexample. If a counterexample is found, we + // provide it to the learner to refine the hypothesis. This process repeats until no + // counterexample is found. + var hyp = learner.getHypothesisModel(); + DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + statisticsCollector.increaseCounter("roundCount", "CEX queries"); + + int roundCount = 1; + while (cex != null && roundCount < maxRounds) { + learner.refineHypothesis(cex); + hyp = learner.getHypothesisModel(); + cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); + statisticsCollector.increaseCounter("roundCount", null); + roundCount += 1; + } + statisticsCollector.pauseClock("learningRt"); + + final var finalHypothesis = learner.getHypothesisModel(); + + // Add some more stats: + statisticsCollector.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); + + // Print final result + statistics: + System.out.println(statisticsCollector.printStats()); + + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + + System.out.println("Final hypothesis:"); + //Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); + + return finalHypothesis; + } +} From 2e0210fc4f9955738dd790be721506b9fc9153c5 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 24 Nov 2025 11:58:19 +0100 Subject: [PATCH 45/55] Added more info about MMLTs. --- .../de/learnlib/example/mmlt/Example1.java | 47 ++++++++++++++++--- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index 25392e1ca..d2c4873f0 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -17,13 +17,48 @@ import net.automatalib.word.Word; /** - * This example shows how to learn a Mealy machine with local timers (MMLT), + * This example shows a basic learning setup for Mealy machine with local timers (MMLT), * an automaton model for real-time systems. - *

          - * MMLTs extend Mealy machines with multiple timers. More information about MMLTs can be found - * in the included README file. - *

          - * This example uses a very basic learner set-up. + * + *

          Mealy Machines with Local Timers (MMLTs) are an extension of Mealy machines for real-time behavior. + * They extend Mealy machines with multiple timers. A timer in an MMLT counts down as time progresses. + * When reaching zero, it stops and triggers an action.

          + * + *
            + *
          • A timer in an MMLT is bound to a specific location. It can only time out in its associated location and only be reset + * at transitions that target this location.
          • + *
          • The timeout-action of a timer $x$ is modeled with a transition that uses the internal input to[x]. These inputs + * cannot be provided to the model directly. Instead, they are internally triggered after sufficient time has passed. All + * other input symbols are called non-delaying inputs.
          • + *
          • The output of a timer at timeout must not be silent.
          • + *
          • There are two types of timers: + *
              + *
            • A periodic timer automatically resets itself on timeout. It cannot cause a change to a different location.
            • + *
            • A one-shot timer may cause a change to a different location on timeout. Regardless of that, it resets all timers + * of the targeted location.
            • + *
            + *
          • + *
          • All timers of a location reset to their initial value when this location is entered from a different location. If + * the initial location has timers, they are reset when the system is activated.
          • + *
          • A self-loop with a non-delaying input does not reset timers by default. However, it might optionally reset all timers + * of its source location. This behavior is called a local reset.
          • + *
          • A location can have multiple timers: + *
              + *
            • A location can also have multiple periodic timers. These can even time out simultaneously. Then, their outputs are + * combined to a single output through concatenation.
            • + *
            • A periodic and a one-shot timer must never time out simultaneously.
            • + *
            + *
          • + *
          + * + *

          As for many other real-time systems, the semantics of an MMLT are defined with an associated transition system. + * For MMLTs, this system is a Mealy machine. When inferring the behavior of the unknown system, the learner + * conceptually interacts with this Mealy machine. + * The inputs of this machine are the non-delaying inputs of the MMLT, discrete time steps, + * and a symbolic timeout symbol. The latter prompts a delay until the next timeout.

          + * + *

          More information about MMLTs can be found here: + * Learning Mealy Machines with Local Timers.

          */ public class Example1 { From 7e4269c60b6edb3c3149734983c74f11abf8b333 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 24 Nov 2025 13:03:26 +0100 Subject: [PATCH 46/55] Re-enabled visualization in MMLT examples. --- .../src/main/java/de/learnlib/example/mmlt/ExampleUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java b/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java index 0de82bf9e..c0218c519 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java @@ -48,7 +48,7 @@ public class ExampleUtil { new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); System.out.println("Final hypothesis:"); - //Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); + Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); return finalHypothesis; } From 07aeef6bd422723eac410f54802a02336fc384b2 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 24 Nov 2025 15:47:52 +0100 Subject: [PATCH 47/55] Updated to AutomataLib changes: timers in MMLTs now support multiple outputs explicitly. --- .../algorithm/lstar/mmlt/ExtensibleLStarMMLT.java | 7 ++++--- .../test/resources/mmlt/duplicate_timer_outputs.dot | 13 +++++++++++++ .../learnlib/oracle/membership/TimedSULOracle.java | 13 +++++++------ 3 files changed, 24 insertions(+), 9 deletions(-) create mode 100644 algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot 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 8fa6c490c..d497693c0 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 @@ -236,7 +236,8 @@ protected void updateOutputs() { if (timerInfo == null) { throw new AssertionError(); } - output = new TimedOutput<>(timerInfo.output()); + var combinedOutput = this.hypData.getModelParams().outputCombiner().combineSymbols(timerInfo.outputs()); + output = new TimedOutput<>(combinedOutput); } else { output = this.timeOracle.answerQuery(prefix, Word.fromLetter(inputSym)).lastSymbol(); } @@ -526,7 +527,7 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai for (var timer : timerInfo.getLocalTimers().values()) { if (timer.periodic()) { - hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output()); + hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.outputs()); } else { // One-shot: use successor from table TimedInput symbol = new TimeStepSequence<>(timer.initial()); @@ -534,7 +535,7 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); - hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.output(), stateMap.get(successorId)); + hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.outputs(), stateMap.get(successorId)); } } } diff --git a/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot b/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot new file mode 100644 index 000000000..c91772919 --- /dev/null +++ b/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot @@ -0,0 +1,13 @@ +// In this example, one timer produces the same output multiple times. +digraph g { + + s0 [timers="a=3,b=3,c=6" shape="circle"]; + + s0 -> s0 [label="to[a] / A"]; + s0 -> s0 [label="to[b] / A"]; + s0 -> s0 [label="to[c] / B"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index 11cf9bb0c..6752699c7 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -143,11 +143,12 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { return new TimerQueryResult<>(false, Collections.emptyList()); // no timeouts found } - if (this.modelParams.outputCombiner().isCombinedSymbol(firstTimeout.symbol())) { + List firstTimeoutOutputs = this.modelParams.outputCombiner().separateSymbols(firstTimeout.symbol()); + if (firstTimeoutOutputs.size() > 1) { LOGGER.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); } - knownTimers.add(new TimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeout.symbol(), null)); + knownTimers.add(new TimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeoutOutputs, null, true)); // Wait for further timeouts: long currentTimeStep = firstTimeout.delay(); // already waited for first timeout @@ -202,8 +203,7 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, // Timeout occurred at expected time -> check if matching expected output: Map expectedOutputs = knownTimers.stream() .filter(t -> nextExpectedTime % t.initial() == 0) - .map(t -> modelParams.outputCombiner() - .separateSymbols(t.output())) // separate output of timers with same initial value + .map(TimerInfo::outputs) // outputs of timers with same initial value .flatMap(Collection::stream) .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences @@ -233,8 +233,9 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, // Same time and more outputs -> add new timer that uses the new outputs: TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, - this.modelParams.outputCombiner().combineSymbols(newOutputs), - null); + newOutputs, + null, + true); return new TimerCheckResult<>(newTimer, false); } } else { From b37eefdb7e25e5b36967d784bde442ccac20005e Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 25 Nov 2025 08:10:57 +0100 Subject: [PATCH 48/55] Added example for loading setting up a custom MMLT model. --- .../de/learnlib/example/mmlt/Example4.java | 116 ++++++++++++++++++ examples/src/main/resources/mmlt_example.dot | 34 +++++ 2 files changed, 150 insertions(+) create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/Example4.java create mode 100644 examples/src/main/resources/mmlt_example.dot diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example4.java b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java new file mode 100644 index 000000000..292f0c92f --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java @@ -0,0 +1,116 @@ +package de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; +import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.statistic.Statistics; +import de.learnlib.testsupport.example.LearningExample; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import de.learnlib.time.MMLTModelParams; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTParsers; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.util.automaton.mmlt.MMLTs; +import net.automatalib.word.Word; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * This example demonstrates how to load an MMLT from a dot-file and learn it using the L* algorithm. + *

          + * A description of the dot-file syntax for MMLTs can be found in AutomataLib + * (see {@link net.automatalib.serialization.dot.DOTMMLTParser}). + */ +public class Example4 { + + + public static void main(String[] args) { + // First, we load the file "mmlt_example.dot" from the "resources" folder: + MMLT targetModel; + MMLTModelParams params; + + // We define the output that represents silence: + var silentOutput = "void"; + + // In an MMLT, a timeout may yield multiple outputs. + // We use a symbol combiner to combine them into a single output. + // Here, we use a StringSymbolCombiner that sorts symbols and then concatenates them with a pipe: + var outputCombiner = StringSymbolCombiner.getInstance(); + var parser = DOTParsers.mmlt(silentOutput, outputCombiner); + + try (InputStream is = Example4.class.getResourceAsStream("/mmlt_example.dot")) { + var parsedModel = parser.readModel(is); + targetModel = parsedModel.model; + + // During learning, we use a symbolic "timeout" symbol to indicate that the + // teacher should wait for the next timeout. To avoid an infinite runtime, + // we set a maximum waiting time for these symbols. + // This time should be at least the maximum time to the next timeout in any + // state of the system. We configure this as follows: + long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(targetModel); + + // After adding a new location, the learner infers timers for it by watching the SUL for timeouts. + // To learn an accurate model, the maximum time to watch for these timeouts must be at + // least the value of "maxTimeoutDelay". + // If the maximum initial value of timers in the SUL is known or can be reasonably estimated, + // setting the watch time to twice that value usually yields good results: + long maxTimerQueryWaitingFinal = MMLTs.getMaximumInitialTimerValue(targetModel) * 2; + + + params = new MMLTModelParams<>(silentOutput, outputCombiner, maxTimeoutDelay, maxTimerQueryWaitingFinal); + } catch (IOException | FormatException e) { + throw new RuntimeException("Unable to load model from file."); + } + + // Proceed as in Example1: + + var stats = Statistics.getCollector(); + stats.addText("LocalTimerMealyModel", null, "mmlt_example.dot"); + stats.setCounter("original_locs", "Locations in original", targetModel.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", targetModel.getInputAlphabet().size()); + + // Set up the pipeline: + // We use a simulator SUL to simulate our automaton: + var sul = new MMLTSimulatorSUL<>(targetModel.getSemantics()); + + // We count all operations that are performed on the SUL with a stats-SUL: + var statsAfterCache = new CounterTimedSUL<>(sul); + + // We use a cache to avoid redundant operations: + var cacheSUL = new TimedSULTreeCache<>(statsAfterCache, params); + var toReducerSul = new TimeoutReducerSUL<>(cacheSUL, params.maxTimeoutWaitingTime()); + + // We use a query oracle to answer queries from the learner: + var timeOracle = new TimedSULOracle<>(toReducerSul, params); + + // In the basic set-up, we use a simulator oracle to answer equivalence queries. + // This oracle has perfect knowledge of the reference automaton. + var eqOracle = new SimulatorEQOracle<>(targetModel); + + // Set up our L* learner: + + // We provide the learner with an initial set of suffixes. + // We include all untimed inputs and the symbolic timeout symbol, which causes the learner to wait + // until the next timeout (but no longer than params.maxTimeoutWaitingTime()). + List>> suffixes = new ArrayList<>(); + targetModel.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + var learner = new ExtensibleLStarMMLT<>(targetModel.getInputAlphabet(), params, suffixes, timeOracle); + + // Start learning: + ExampleUtil.runExperiment(learner, eqOracle, stats, 100); + + } + +} diff --git a/examples/src/main/resources/mmlt_example.dot b/examples/src/main/resources/mmlt_example.dot new file mode 100644 index 000000000..a5cbcddfb --- /dev/null +++ b/examples/src/main/resources/mmlt_example.dot @@ -0,0 +1,34 @@ +// This MMLT model demonstrates various features of the file syntax: +digraph g { + s0 [label="L0" timers="a=2"] + s1 [label="L1" timers="b=4,c=6"] + s2 [label="L2" timers="d=2,e=3"] + + // one-shot with location change: + s0 -> s1 [label="to[a] / A"] + + // periodic with multiple outputs, + // assuming a {net.automatalib.automaton.mmlt.impl.StringSymbolCombiner} to combine and separate outputs: + s1 -> s1 [label="to[b] / B|Z"] + + // one-shot with loop: + s1 -> s1 [label="to[c] / C" resets="b,c"] + + // periodic with explicit resets: + s2 -> s2 [label="to[d] / D" resets="d"] + + // periodic: + s2 -> s2 [label="to[e] / E"] + + // normal transition with silent output: + s1 -> s2 [label="x / void"] + + // loop with reset: + s1 -> s1 [label="y / Y" resets="b,c"] + + // loop without reset: + s2 -> s2 [label="y / D"] + + __start0 [label="" shape="none" width="0" height="0"]; + __start0 -> s0; +} \ No newline at end of file From bb860e53b98c8c37528e936f28fd3d72bf02ab37 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 25 Nov 2025 08:16:03 +0100 Subject: [PATCH 49/55] Wording --- .../src/test/resources/mmlt/syntax_demo.dot | 43 +++++++++++++------ examples/src/main/resources/mmlt_example.dot | 2 +- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot b/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot index 2379c825b..f04f42d59 100644 --- a/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot +++ b/algorithms/active/lstar/src/test/resources/mmlt/syntax_demo.dot @@ -1,20 +1,35 @@ -// This file demonstrates the syntax for defining a custom MMLT +// This file demonstrates the syntax for defining a custom MMLT. +// It is identical to "mmlt_example.dot" in the "examples" module. digraph g { - s0 [label="L0" timers="a=2"] - s1 [label="L1" timers="b=4,c=6"] - s2 [label="L2" timers="d=2,e=3"] + s0 [label="L0" timers="a=2"] + s1 [label="L1" timers="b=4,c=6"] + s2 [label="L2" timers="d=2,e=3"] - s0 -> s1 [label="to[a] / A"] // one-shot with location change - s1 -> s1 [label="to[b] / B"] // periodic - s1 -> s1 [label="to[c] / C" resets="b,c"] // one-shot with loop + // one-shot with location change: + s0 -> s1 [label="to[a] / A"] - s2 -> s2 [label="to[d] / D" resets="d"] // periodic with explicit resets - s2 -> s2 [label="to[e] / E"] // periodic + // periodic with multiple outputs, + // assuming a {net.automatalib.automaton.mmlt.impl.StringSymbolCombiner} to combine and separate outputs: + s1 -> s1 [label="to[b] / B|Z"] - s1 -> s2 [label="x / void"] - s1 -> s1 [label="y / Y" resets="b,c"] // loop with local reset - s2 -> s2 [label="y / D"] // loop without reset + // one-shot with loop: + s1 -> s1 [label="to[c] / C" resets="b,c"] - __start0 [label="" shape="none" width="0" height="0"]; - __start0 -> s0; + // periodic with explicit resets: + s2 -> s2 [label="to[d] / D" resets="d"] + + // periodic: + s2 -> s2 [label="to[e] / E"] + + // normal transition with silent output: + s1 -> s2 [label="x / void"] + + // loop with reset: + s1 -> s1 [label="y / Y" resets="b,c"] + + // loop without reset: + s2 -> s2 [label="y / D"] + + __start0 [label="" shape="none" width="0" height="0"]; + __start0 -> s0; } \ No newline at end of file diff --git a/examples/src/main/resources/mmlt_example.dot b/examples/src/main/resources/mmlt_example.dot index a5cbcddfb..645f39e27 100644 --- a/examples/src/main/resources/mmlt_example.dot +++ b/examples/src/main/resources/mmlt_example.dot @@ -1,4 +1,4 @@ -// This MMLT model demonstrates various features of the file syntax: +// This file demonstrates the syntax for defining a custom MMLT. digraph g { s0 [label="L0" timers="a=2"] s1 [label="L1" timers="b=4,c=6"] From c47d0e99572529976e04b17d176dfa602144dc81 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 25 Nov 2025 08:37:52 +0100 Subject: [PATCH 50/55] Fixed bug that prevented duplicate timer outputs during timer inference. --- .../mmlt/duplicate_timer_outputs.dot | 5 +-- .../oracle/membership/TimedSULOracle.java | 34 ++++++++++++------- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot b/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot index c91772919..fc0ca63e7 100644 --- a/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot +++ b/algorithms/active/lstar/src/test/resources/mmlt/duplicate_timer_outputs.dot @@ -1,11 +1,12 @@ -// In this example, one timer produces the same output multiple times. +// In this example, the same output is produced multiple times at the same timeouts: digraph g { - s0 [timers="a=3,b=3,c=6" shape="circle"]; + s0 [timers="a=3,b=3,c=6,d=10" shape="circle"]; s0 -> s0 [label="to[a] / A"]; s0 -> s0 [label="to[b] / A"]; s0 -> s0 [label="to[c] / B"]; + s0 -> s0 [label="to[d] / A|A"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s0; diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index 6752699c7..e434dbdff 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -195,9 +195,12 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, long nextExpectedTime, TimedOutput nextOutput, List> knownTimers) { + + var nextOutputSymbols = this.modelParams.outputCombiner().separateSymbols(nextOutput.symbol()); + if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: - TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutput.symbol(), null); + TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); return new TimerCheckResult<>(newTimer, false); } else if (nextActualTime == nextExpectedTime) { // Timeout occurred at expected time -> check if matching expected output: @@ -208,27 +211,32 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences - Map actualOutputs = this.modelParams.outputCombiner() - .separateSymbols(nextOutput.symbol()) - .stream() - .collect(Collectors.groupingBy(e -> e, Collectors.counting())); + Map actualOutputs = nextOutputSymbols.stream() + .collect(Collectors.groupingBy(e -> e, Collectors.counting())); - // Any missing outputs? + // Any outputs that were expected but are not present? boolean missingOutputs = expectedOutputs.keySet() .stream() .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < - expectedOutputs.get(o)); // less than expected + expectedOutputs.get(o)); // less than expected if (missingOutputs) { // Same time but missing output -> missed location change: return new TimerCheckResult<>(null, true); } - // Any new outputs? - List newOutputs = actualOutputs.keySet() - .stream() - .filter(o -> expectedOutputs.getOrDefault(o, 0L) < - actualOutputs.get(o)) // less than actual - .toList(); + // At least all expected outputs are present. + // Check for additional outputs: + List newOutputs = new ArrayList<>(); + for (O output : actualOutputs.keySet()) { + long expectedCount = expectedOutputs.getOrDefault(output, 0L); + long actualCount = actualOutputs.get(output); + + long additional = actualCount - expectedCount; + for (int i = 0; i < additional; i++) { + newOutputs.add(output); + } + } + if (!newOutputs.isEmpty()) { // Same time and more outputs -> add new timer that uses the new outputs: TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), From 5b86f6353ef6863d22287beb2b6bb78e56a4b7e5 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 25 Nov 2025 15:05:09 +0100 Subject: [PATCH 51/55] fix issues flagged by code-analysis --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 444 +++++++++++------- .../lstar/mmlt/LocationTimerInfo.java | 97 ++-- .../lstar/mmlt/MMLTHypDataContainer.java | 59 ++- .../algorithm/lstar/mmlt/MMLTHypothesis.java | 55 ++- .../lstar/mmlt/MMLTObservationTable.java | 344 +++++++------- .../lstar/mmlt/cex/ExtendedDecomposition.java | 45 +- .../cex/MMLTCounterexampleDecomposer.java | 172 +++++++ .../cex/MMLTCounterexampleDecompositor.java | 138 ------ .../mmlt/cex/MMLTCounterexampleHandler.java | 165 ++++--- .../cex/MMLTInconsPrefixTransformAcex.java | 49 +- .../mmlt/cex/MMLTOutputInconsistency.java | 41 +- .../mmlt/cex/results/CexAnalysisResult.java | 24 +- .../mmlt/cex/results/FalseIgnoreResult.java | 25 +- .../results/MissingDiscriminatorResult.java | 24 +- .../cex/results/MissingOneShotResult.java | 24 +- .../mmlt/cex/results/MissingResetResult.java | 27 +- .../mmlt/filter/MMLTPerfectSymbolFilter.java | 31 +- .../mmlt/filter/MMLTRandomSymbolFilter.java | 36 +- .../filter/MMLTStatisticsSymbolFilter.java | 27 +- .../mmlt/filter/MMLTSymbolFilterUtil.java | 56 ++- ...xtensibleLStarMMLTCounterexampleTests.java | 18 +- .../lstar/it/ExtensibleLStarMMLTIT.java | 25 +- .../de/learnlib/oracle/EquivalenceOracle.java | 2 +- .../de/learnlib/oracle/TimedQueryOracle.java | 2 +- .../de/learnlib/statistic/NoopCollector.java | 12 +- .../statistic/StatisticsCollector.java | 2 +- .../main/java/de/learnlib/sul/TimedSUL.java | 2 +- .../java/de/learnlib/util/Experiment.java | 5 +- .../driver/simulator/MMLTSimulatorSUL.java | 2 +- .../de/learnlib/example/mmlt/Example1.java | 51 +- .../de/learnlib/example/mmlt/Example2.java | 77 +-- .../de/learnlib/example/mmlt/Example3.java | 72 ++- .../de/learnlib/example/mmlt/Example4.java | 48 +- .../learnlib/example/mmlt/ExampleRunner.java | 57 +++ .../de/learnlib/example/mmlt/ExampleUtil.java | 55 --- .../filter/cache/mmlt/CacheTreeNode.java | 6 +- .../cache/mmlt/MMLTCacheConsistencyTest.java | 9 +- .../filter/cache/mmlt/TimedSULTreeCache.java | 8 +- .../statistic/container/CounterStatistic.java | 6 +- .../container/MapStatisticsCollector.java | 11 +- .../container/StopClockStatistic.java | 2 +- .../filter/statistic/sul/CounterTimedSUL.java | 4 +- .../mmlt/RandomWpMethodEQOracle.java | 2 - .../equivalence/mmlt/ResetSearchEQOracle.java | 2 - .../oracle/membership/TimedSULOracle.java | 24 +- 45 files changed, 1464 insertions(+), 923 deletions(-) create mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecomposer.java delete mode 100644 algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java create mode 100644 examples/src/main/java/de/learnlib/example/mmlt/ExampleRunner.java delete mode 100644 examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java 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 d497693c0..a3d198fff 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 @@ -1,20 +1,34 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Stream; +import java.util.Map.Entry; import de.learnlib.acex.AcexAnalyzer; import de.learnlib.acex.AcexAnalyzers; -import de.learnlib.filter.symbol.AcceptAllSymbolFilter; -import de.learnlib.filter.symbol.CachedSymbolFilter; -import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.closing.ClosingStrategies; import de.learnlib.algorithm.lstar.closing.ClosingStrategy; import de.learnlib.algorithm.lstar.mmlt.cex.MMLTCounterexampleHandler; import de.learnlib.algorithm.lstar.mmlt.cex.MMLTOutputInconsistency; +import de.learnlib.algorithm.lstar.mmlt.cex.results.CexAnalysisResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.FalseIgnoreResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; @@ -23,10 +37,13 @@ import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; import de.learnlib.filter.MutableSymbolFilter; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; +import de.learnlib.time.MMLTModelParams; +import de.learnlib.tooling.annotation.builder.GenerateBuilder; import de.learnlib.util.mealy.MealyUtil; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; @@ -46,12 +63,15 @@ /** * The MMLT learner. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class ExtensibleLStarMMLT implements OTLearner, TimedInput, Word>> { +public class ExtensibleLStarMMLT + implements OTLearner, TimedInput, Word>> { - private static final Logger logger = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); + private static final Logger LOGGER = LoggerFactory.getLogger(ExtensibleLStarMMLT.class); private final StatisticsCollector stats; private final ClosingStrategy, ? super Word>> closingStrategy; @@ -63,60 +83,87 @@ public class ExtensibleLStarMMLT implements OTLearner>> initialSuffixes; - private final MMLTCounterexampleHandler cexAnalyzer; + private final MMLTCounterexampleHandler cexAnalyzer; /** * Instantiates a new Rivest-Schapire learner for MMLTs. *

          - * Uses the close-shortest strategy for closing the observation table, - * binary-backwards search for decomposing counterexamples, and no symbol filter. + * Uses the close-shortest strategy for closing the observation table, binary-backwards search for decomposing + * counterexamples, and no symbol filter. * - * @param alphabet Alphabet of non-delaying inputs - * @param modelParams LocalTimerMealyModel parameters - * @param initialSuffixes Initial set of suffixes. May be empty. - * @param timeOracle The output query oracle for MMLTs. + * @param alphabet + * alphabet (of non-delaying inputs) + * @param modelParams + * model parameters + * @param initialSuffixes + * initial set of suffixes (may be empty) + * @param timeOracle + * the query oracle for MMLTs */ public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, List>> initialSuffixes, TimedQueryOracle timeOracle) { - this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, - new CachedSymbolFilter<>(new AcceptAllSymbolFilter<>()), AcexAnalyzers.BINARY_SEARCH_BWD); + this(alphabet, + modelParams, + initialSuffixes, + ClosingStrategies.CLOSE_SHORTEST, + timeOracle, + new AcceptAllSymbolFilter<>(), + AcexAnalyzers.BINARY_SEARCH_BWD); } /** * Instantiates a new Rivest-Schapire learner for MMLTs. *

          - * Uses the close-shortest strategy for closing the observation table and - * binary-backwards search for decomposing counterexamples. + * Uses the close-shortest strategy for closing the observation table and binary-backwards search for decomposing + * counterexamples. * - * @param alphabet Alphabet of non-delaying inputs - * @param modelParams LocalTimerMealyModel parameters - * @param initialSuffixes Initial set of suffixes. May be empty. - * @param timeOracle The output query oracle for MMLTs. - * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. + * @param alphabet + * alphabet (of non-delaying inputs) + * @param modelParams + * model parameters + * @param initialSuffixes + * initial set of suffixes (may be empty) + * @param timeOracle + * the query oracle for MMLTs + * @param symbolFilter + * the symbol filter */ public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, List>> initialSuffixes, TimedQueryOracle timeOracle, MutableSymbolFilter, InputSymbol> symbolFilter) { - this(alphabet, modelParams, initialSuffixes, ClosingStrategies.CLOSE_SHORTEST, timeOracle, symbolFilter, AcexAnalyzers.BINARY_SEARCH_BWD); + this(alphabet, + modelParams, + initialSuffixes, + ClosingStrategies.CLOSE_SHORTEST, + timeOracle, + symbolFilter, + AcexAnalyzers.BINARY_SEARCH_BWD); } /** * Instantiates a new Rivest-Schapire learner for MMLTs. * - * @param alphabet Alphabet of non-delaying inputs - * @param modelParams LocalTimerMealyModel parameters - * @param initialSuffixes Initial set of suffixes. May be empty. - * @param closingStrategy Closing strategy for the observation table. - * @param timeOracle The output query oracle for MMLTs. - * @param symbolFilter The symbol filter. If no filter should be used, use the AcceptAll filter. - * @param analyzer The strategy for decomposing counterexamples. + * @param alphabet + * alphabet (of non-delaying inputs) + * @param modelParams + * model parameters + * @param initialSuffixes + * initial set of suffixes (may be empty) + * @param closingStrategy + * closing strategy for the observation table. + * @param timeOracle + * the query oracle for MMLTs + * @param symbolFilter + * the symbol filter + * @param analyzer + * The strategy for decomposing counterexamples. */ + @GenerateBuilder(defaults = BuilderDefaults.class) public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, List>> initialSuffixes, @@ -136,22 +183,29 @@ public ExtensibleLStarMMLT(Alphabet alphabet, alphabet.forEach(s -> internalAlphabet.add(TimedInput.input(s))); // Init hypothesis data: - this.hypData = new MMLTHypDataContainer<>(internalAlphabet, modelParams, - new MMLTObservationTable<>(internalAlphabet, - modelParams.maxTimerQueryWaitingTime(), symbolFilter, modelParams.silentOutput())); + this.hypData = new MMLTHypDataContainer<>(internalAlphabet, + modelParams, + new MMLTObservationTable<>(internalAlphabet, + modelParams.maxTimerQueryWaitingTime(), + symbolFilter, + modelParams.silentOutput())); this.cexAnalyzer = new MMLTCounterexampleHandler<>(timeOracle, analyzer, symbolFilter); this.symbolFilter = symbolFilter; } /** - * Heuristically chooses a new one-shot timer from the provided timers: - * takes the timer with the highest initial value that a) does not exceed maxInitialValue and b) has not timer - * with a lower initial value that times out at the same time. + * Heuristically chooses a new one-shot timer from the provided timers: takes the timer with the highest initial + * value that a) does not exceed maxInitialValue and b) has not timer with a lower initial value that times out at + * the same time. + * + * @param sortedTimers + * Timers, sorted ascendingly by their initial value + * @param maxInitialValue + * Max. initial value to consider + * @param + * Output type * - * @param sortedTimers Timers, sorted ascendingly by their initial value - * @param maxInitialValue Max. initial value to consider - * @param Output type * @return New one-shot timer */ public static int selectOneShotTimer(List> sortedTimers, long maxInitialValue) { @@ -160,47 +214,42 @@ public static int selectOneShotTimer(List> sortedT // Start at timer with the highest initial value. // Ignore all timers whose initial value exceeds the maximum value. // Also ignore timers whose timeout is the multiple of another timer's initial value. + timers: for (int i = sortedTimers.size() - 1; i >= 0; i--) { TimerInfo timer = sortedTimers.get(i); - if (timer.initial() > maxInitialValue) { - continue; // could not have expired - } + // could not have expired + if (timer.initial() <= maxInitialValue) { - // Ignore timers whose initial value is a multiple of another one. - // When set to one-shot, these would expire at same time as periodic timer -> non-deterministic behavior! - boolean multiple = false; - for (int j = 0; j < i; j++) { - TimerInfo otherTimer = sortedTimers.get(j); - if (timer.initial() % otherTimer.initial() == 0) { - multiple = true; - break; + // Ignore timers whose initial value is a multiple of another one. + // When set to one-shot, these would expire at same time as periodic timer -> non-deterministic behavior! + for (int j = 0; j < i; j++) { + TimerInfo otherTimer = sortedTimers.get(j); + if (timer.initial() % otherTimer.initial() == 0) { + continue timers; + } } - } - if (multiple) { - continue; - } - return i; // not a multiple and within time + return i; // not a multiple and within time + } } throw new IllegalStateException("Max. initial value is too low; must include at least one timer."); } - /** - * Constructs an MMLT hypothesis. - * This updates all transition outputs, if required. + * Constructs an MMLT hypothesis. This updates all transition outputs, if required. * * @return MMLT hypothesis */ + @Override public MMLT getHypothesisModel() { return getInternalLocalTimerMealyHypothesis(); } /** - * Like the construction above, but returns an LocalTimerMealyHypothesis object instead. - * This objects provides additional functions that are just intended for the learner but not the teacher. + * Like the construction above, but returns an LocalTimerMealyHypothesis object instead. This objects provides + * additional functions that are just intended for the learner but not the teacher. * * @return MMLT hypothesis */ @@ -213,46 +262,48 @@ protected List>> selectClosingRows(List return closingStrategy.selectClosingRows(unclosed, hypData.getTable(), timeOracle); } - - protected void updateOutputs() { + private void updateOutputs() { // Query output of newly-added transitions: - Stream.concat(this.hypData.getTable().getShortPrefixRows().stream(), this.hypData.getTable().getLongPrefixRows().stream()) - .forEach(row -> { - if (row.getLabel().isEmpty()) { - return; // initial state - } + updateOutputs(this.hypData.getTable().getShortPrefixRows()); + updateOutputs(this.hypData.getTable().getLongPrefixRows()); + } - if (this.hypData.getTransitionOutputMap().containsKey(row.getLabel())) { - return; // already queried - } + private void updateOutputs(Collection>> rows) { + for (Row> row : rows) { + if (row.getLabel().isEmpty()) { + continue; // initial state + } - Word> prefix = row.getLabel().prefix(-1); - TimedInput inputSym = row.getLabel().suffix(1).lastSymbol(); - - TimedOutput output = null; - if (inputSym instanceof TimeStepSequence ws) { - // Query timer output from table: - TimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); - if (timerInfo == null) { - throw new AssertionError(); - } - var combinedOutput = this.hypData.getModelParams().outputCombiner().combineSymbols(timerInfo.outputs()); - output = new TimedOutput<>(combinedOutput); - } else { - output = this.timeOracle.answerQuery(prefix, Word.fromLetter(inputSym)).lastSymbol(); - } + if (this.hypData.getTransitionOutputMap().containsKey(row.getLabel())) { + continue; // already queried + } - if (output != null) { - this.hypData.getTransitionOutputMap().put(row.getLabel(), output); - } - }); + Word> prefix = row.getLabel().prefix(-1); + TimedInput inputSym = row.getLabel().suffix(1).lastSymbol(); + + TimedOutput output; + if (inputSym instanceof TimeStepSequence ws) { + // Query timer output from table: + TimerInfo timerInfo = this.hypData.getTable().getTimerInfo(prefix, ws.timeSteps()); + assert timerInfo != null; + O combinedOutput = this.hypData.getModelParams().outputCombiner().combineSymbols(timerInfo.outputs()); + output = new TimedOutput<>(combinedOutput); + } else { + output = this.timeOracle.answerQuery(prefix, Word.fromLetter(inputSym)).lastSymbol(); + } + + if (output != null) { + this.hypData.getTransitionOutputMap().put(row.getLabel(), output); + } + } } // ========================== @Override public void startLearning() { - List>>> initialUnclosed = this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); + List>>> initialUnclosed = + this.hypData.getTable().initialize(Collections.emptyList(), this.initialSuffixes, timeOracle); // Ensure that closed: this.completeConsistentTable(initialUnclosed); @@ -264,108 +315,103 @@ public boolean refineHypothesis(DefaultQuery, Word> return false; // no valid CEX } while (refineHypothesisSingle(ceQuery)) { + // analyze exhaustively } return true; } - /** - * Transforms the provided counterexample to an inconsistency object: - * First, checks if still a counterexample. If so, cuts the cex after the first output deviation. + * Transforms the provided counterexample to an inconsistency object: First, checks if still a counterexample. If + * so, cuts the cex after the first output deviation. + * + * @param ceQuery + * Counterexample + * @param hypothesis + * Current hypothesis * - * @param ceQuery Counterexample - * @param hypothesis Current hypothesis * @return The resulting inconsistency, or null, if the counterexample is not a counterexample. */ - @Nullable - private MMLTOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, MMLTHypothesis hypothesis) { + private @Nullable MMLTOutputInconsistency toOutputInconsistency(DefaultQuery, Word>> ceQuery, + MMLTHypothesis hypothesis) { // 1. Cut example after first deviation: - DefaultQuery, Word>> shortQuery = MealyUtil.shortenCounterExample(hypothesis.getSemantics(), ceQuery); + DefaultQuery, Word>> shortQuery = + MealyUtil.shortenCounterExample(hypothesis.getSemantics(), ceQuery); if (shortQuery == null) { return null; } // 2. Calculate shortened hypothesis output: - var shortHypOutput = hypothesis.getSemantics().computeSuffixOutput(shortQuery.getPrefix(), shortQuery.getSuffix()); - if (shortHypOutput.equals(shortQuery.getOutput())) { - throw new AssertionError("Deviation lost after shortening."); - } + Word> shortHypOutput = + hypothesis.getSemantics().computeSuffixOutput(shortQuery.getPrefix(), shortQuery.getSuffix()); + + assert !shortHypOutput.equals(shortQuery.getOutput()) : "Deviation lost after shortening."; return new MMLTOutputInconsistency<>(shortQuery.getPrefix(), - shortQuery.getSuffix(), - shortQuery.getOutput(), - shortHypOutput); + shortQuery.getSuffix(), + shortQuery.getOutput(), + shortHypOutput); } private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { // 1. Update hypothesis (may have changed since last refinement): - var hypothesis = this.getInternalLocalTimerMealyHypothesis(); + MMLTHypothesis hypothesis = this.getInternalLocalTimerMealyHypothesis(); // 2. Transform to output inconsistency: - var outputIncons = this.toOutputInconsistency(ceQuery, hypothesis); + MMLTOutputInconsistency outputIncons = this.toOutputInconsistency(ceQuery, hypothesis); if (outputIncons == null) { return false; } - logger.debug(String.format("Refining with inconsistency %s", outputIncons)); + LOGGER.debug("Refining with inconsistency {}", outputIncons); // 3. Identify source of deviation: stats.startOrResumeClock("clk_cex_analysis", "Total cex analysis time"); stats.increaseCounter("cnt_cex_analysis", "Cex analyses"); - var analysisResult = this.cexAnalyzer.analyzeInconsistency(outputIncons, hypothesis); + CexAnalysisResult analysisResult = this.cexAnalyzer.analyzeInconsistency(outputIncons, hypothesis); stats.pauseClock("clk_cex_analysis"); // 4. Refine: if (analysisResult instanceof MissingDiscriminatorResult locSplit) { - stats.increaseCounter("INACC_MISSING_DISC", - "Inaccuracies: missing discriminators"); + stats.increaseCounter("INACC_MISSING_DISC", "Inaccuracies: missing discriminators"); // Add new discriminator as suffix: - if (hypData.getTable().getSuffixes().contains(locSplit.getDiscriminator())) throw new AssertionError(); + assert !hypData.getTable().getSuffixes().contains(locSplit.getDiscriminator()); List>> suffixes = Collections.singletonList(locSplit.getDiscriminator()); - var unclosed = hypData.getTable().addSuffixes(suffixes, timeOracle); + List>>> unclosed = hypData.getTable().addSuffixes(suffixes, timeOracle); // Close transitions: this.completeConsistentTable(unclosed); // no consistency check for RS } else if (analysisResult instanceof MissingResetResult noReset) { - stats.increaseCounter("INACC_MISSING_RESETS", - "Inaccuracies: missing resets"); + stats.increaseCounter("INACC_MISSING_RESETS", "Inaccuracies: missing resets"); // Add missing reset: - var resetTrans = hypothesis.getPrefix(noReset.getLocation()).append(noReset.getInput()); + Word> resetTrans = hypothesis.getPrefix(noReset.getLocation()).append(noReset.getInput()); this.hypData.getTransitionResetSet().add(resetTrans); } else if (analysisResult instanceof MissingOneShotResult noAperiodic) { - stats.increaseCounter("INACC_MISSING_OS", - "Inaccuracies: missing one-shot timers"); + stats.increaseCounter("INACC_MISSING_OS", "Inaccuracies: missing one-shot timers"); // Identify corresponding sp row: Word> locPrefix = hypothesis.getPrefix(noAperiodic.getLocation()); Row> spRow = hypData.getTable().getRow(locPrefix); - if (spRow == null || !spRow.isShortPrefixRow()) { - throw new AssertionError(); - } + + assert spRow != null && spRow.isShortPrefixRow(); this.handleMissingTimeoutChange(spRow, noAperiodic.getTimeout()); } else if (analysisResult instanceof FalseIgnoreResult falseIgnore) { - stats.increaseCounter("INACC_MISSING_FI", - "Inaccuracies: false ignores"); - - if (this.symbolFilter == null) { - throw new AssertionError("Cannot detect false ignores without symbol filter."); - } + stats.increaseCounter("INACC_MISSING_FI", "Inaccuracies: false ignores"); // Identify corresponding sp row: Word> locPrefix = hypothesis.getPrefix(falseIgnore.getLocation()); Row> spRow = hypData.getTable().getRow(locPrefix); - if (spRow == null || !spRow.isShortPrefixRow()) { - throw new AssertionError(); - } + + assert spRow != null && spRow.isShortPrefixRow(); // Update filter: this.symbolFilter.accept(locPrefix, falseIgnore.getSymbol()); // Legalize symbol + close table: - var unclosed = hypData.getTable().addOutgoingTransition(spRow, falseIgnore.getSymbol(), this.timeOracle); + List>>> unclosed = + hypData.getTable().addOutgoingTransition(spRow, falseIgnore.getSymbol(), this.timeOracle); stats.increaseCounter("Count_legalized", "Legalized symbols"); this.completeConsistentTable(unclosed); @@ -377,43 +423,45 @@ private boolean refineHypothesisSingle(DefaultQuery, Word> spRow, TimerInfo timeout) { - var locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); - if (locationTimerInfo == null) { - throw new AssertionError("Location with missing one-shot timer must have timers."); - } + LocationTimerInfo locationTimerInfo = hypData.getTable().getLocationTimerInfo(spRow); + assert locationTimerInfo != null : "Location with missing one-shot timer must have timers."; // Only timer with highest initial value can be one-shot. // If location already has a one-shot timer, prefix of its timeout-transition might be core or fringe prefix. // If it is a fringe prefix, we need to remove it: - var lastTimerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(locationTimerInfo.getLastTimer().initial())); - if (!locationTimerInfo.getLastTimer().periodic()) { - if (!hypData.getTable().getRow(lastTimerTransPrefix).isShortPrefixRow()) { + TimerInfo lastTimer = locationTimerInfo.getLastTimer(); + assert lastTimer != null; + Word> lastTimerTransPrefix = spRow.getLabel().append(TimedInput.step(lastTimer.initial())); + if (!lastTimer.periodic()) { + Row> row = hypData.getTable().getRow(lastTimerTransPrefix); + assert row != null; + if (!row.isShortPrefixRow()) { // Last timer is one-shot + has fringe prefix: this.hypData.getTable().removeLpRow(lastTimerTransPrefix); } } // Prefix for timeout-transition of new one-shot timer: - Word> timerTransPrefix = spRow.getLabel().append(new TimeStepSequence<>(timeout.initial())); - if (this.hypData.getTable().getRow(timerTransPrefix) != null) { - throw new AssertionError("Timer already appears to be one-shot."); - } + Word> timerTransPrefix = spRow.getLabel().append(TimedInput.step(timeout.initial())); + assert this.hypData.getTable().getRow(timerTransPrefix) == null : "Timer already appears to be one-shot."; // Remove all timers with greater timeout (are now redundant): - var subsequentTimers = locationTimerInfo.getSortedTimers().stream() - .filter(t -> t.initial() > timeout.initial()) - .map(TimerInfo::name).toList(); + List subsequentTimers = locationTimerInfo.getSortedTimers() + .stream() + .filter(t -> t.initial() > timeout.initial()) + .map(TimerInfo::name) + .toList(); subsequentTimers.forEach(locationTimerInfo::removeTimer); // Change from periodic to one-shot: locationTimerInfo.setOneShotTimer(timeout.name()); // Update fringe prefixes + close table: - List>>> unclosed = this.hypData.getTable().addTimerTransition(spRow, timeout, this.timeOracle); + List>>> unclosed = + this.hypData.getTable().addTimerTransition(spRow, timeout, this.timeOracle); this.completeConsistentTable(unclosed); } - @Override public ObservationTable, Word>> getObservationTable() { return this.hypData.getTable(); @@ -425,7 +473,8 @@ public ObservationTable, Word>> getObservationTable *

          * Simplified version for RS learner: assumes that OT is always consistent. * - * @param unclosed the unclosed rows (equivalence classes) to start with. + * @param unclosed + * the unclosed rows (equivalence classes) to start with. */ protected void completeConsistentTable(List>>> unclosed) { List>>> unclosedIter = unclosed; @@ -445,17 +494,14 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai // 1. Create map that stores link between contentID and short-prefix row: final Map>> locationContentIdMap = new HashMap<>(); // contentId -> sp location - for (var spRow : hypData.getTable().getShortPrefixRows()) { - if (locationContentIdMap.containsKey(spRow.getRowContentId())) { - // Multiple sp rows may have same contentID. Thus, assign each id one location: - continue; - } - locationContentIdMap.put(spRow.getRowContentId(), spRow); + for (Row> spRow : hypData.getTable().getShortPrefixRows()) { + // Multiple sp rows may have same contentID. Thus, assign each id only one location: + locationContentIdMap.putIfAbsent(spRow.getRowContentId(), spRow); } // 2. Create untimed alphabet: GrowingMapAlphabet alphabet = new GrowingMapAlphabet<>(); - for (var symbol : hypData.getAlphabet()) { + for (TimedInput symbol : hypData.getAlphabet()) { if (symbol instanceof InputSymbol ndi) { alphabet.add(ndi.symbol()); } @@ -463,12 +509,18 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai // 3. Prepare objects for automaton, timers and resets: int numLocations = hypData.getTable().numberOfShortPrefixRows(); - final Map stateMap = new HashMap<>(HashUtil.capacity(numLocations)); // row content id -> state id - final Map>> prefixMap = new HashMap<>(HashUtil.capacity(numLocations)); // state id -> location prefix - var hypothesis = new MMLTHypothesis<>(alphabet, numLocations, hypData.getModelParams().silentOutput(), hypData.getModelParams().outputCombiner(), prefixMap); // we pass the prefix map as reference so that we can fill it later + final Map stateMap = + new HashMap<>(HashUtil.capacity(numLocations)); // row content id -> state id + final Map>> prefixMap = + new HashMap<>(HashUtil.capacity(numLocations)); // state id -> location prefix + MMLTHypothesis hypothesis = new MMLTHypothesis<>(alphabet, + numLocations, + hypData.getModelParams().silentOutput(), + hypData.getModelParams().outputCombiner(), + prefixMap); // we pass the prefix map as reference so that we can fill it later // 4. Create one state per location: - for (var row : hypData.getTable().getShortPrefixRows()) { + for (Row> row : hypData.getTable().getShortPrefixRows()) { int newStateId = hypothesis.addState(); stateMap.putIfAbsent(row.getRowContentId(), newStateId); prefixMap.put(newStateId, row.getLabel()); @@ -483,13 +535,14 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai } // 5. Create outgoing transitions for non-delaying inputs: - for (var rowContentId : stateMap.keySet()) { + for (Entry e : stateMap.entrySet()) { + Integer rowContentId = e.getKey(); Row> spLocation = locationContentIdMap.get(rowContentId); - for (var symbol : alphabet) { + for (I symbol : alphabet) { int symIdx = hypData.getAlphabet().getSymbolIndex(TimedInput.input(symbol)); - var transOutput = hypData.getTransitionOutput(spLocation, symIdx); + TimedOutput transOutput = hypData.getTransitionOutput(spLocation, symIdx); O output = hypData.getModelParams().silentOutput(); // silent by default if (transOutput != null) { output = transOutput.symbol(); @@ -503,12 +556,12 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai } // Add transition to automaton: - int sourceLocId = stateMap.get(rowContentId); + int sourceLocId = e.getValue(); int successorLocId = stateMap.get(successorId); hypothesis.addTransition(sourceLocId, symbol, successorLocId, output); // Check for local reset: - var targetTransition = spLocation.getLabel().append(TimedInput.input(symbol)); + Word> targetTransition = spLocation.getLabel().append(TimedInput.input(symbol)); if (hypData.getTransitionResetSet().contains(targetTransition) && sourceLocId == successorLocId) { hypothesis.addLocalReset(sourceLocId, symbol); } @@ -517,25 +570,31 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai } // 6. Add timeout transitions: - for (var rowContentId : stateMap.keySet()) { + for (Entry e : stateMap.entrySet()) { + Integer rowContentId = e.getKey(); Row> spLocation = locationContentIdMap.get(rowContentId); - var timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); - if (timerInfo == null) { - continue; // no timers - } + assert spLocation != null; - for (var timer : timerInfo.getLocalTimers().values()) { - if (timer.periodic()) { - hypothesis.addPeriodicTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.outputs()); - } else { - // One-shot: use successor from table - TimedInput symbol = new TimeStepSequence<>(timer.initial()); + LocationTimerInfo timerInfo = hypData.getTable().getLocationTimerInfo(spLocation); - int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); - int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); + if (timerInfo != null) { + for (TimerInfo timer : timerInfo.getLocalTimers().values()) { + if (timer.periodic()) { + hypothesis.addPeriodicTimer(e.getValue(), timer.name(), timer.initial(), timer.outputs()); + } else { + // One-shot: use successor from table + TimedInput symbol = new TimeStepSequence<>(timer.initial()); + + int symIdx = hypData.getAlphabet().getSymbolIndex(symbol); + int successorId = spLocation.getSuccessor(symIdx).getRowContentId(); - hypothesis.addOneShotTimer(stateMap.get(rowContentId), timer.name(), timer.initial(), timer.outputs(), stateMap.get(successorId)); + hypothesis.addOneShotTimer(e.getValue(), + timer.name(), + timer.initial(), + timer.outputs(), + stateMap.get(successorId)); + } } } } @@ -543,4 +602,27 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai return hypothesis; } + static final class BuilderDefaults { + + private BuilderDefaults() { + // prevent instantiation + } + + static List>> initialSuffixes() { + return Collections.emptyList(); + } + + static ClosingStrategy, ? super Word>> closingStrategy() { + return ClosingStrategies.CLOSE_SHORTEST; + } + + static MutableSymbolFilter, InputSymbol> symbolFilter() { + return new AcceptAllSymbolFilter<>(); + } + + static AcexAnalyzer analyzer() { + return AcexAnalyzers.LINEAR_BWD; + } + } + } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java index ee7dd3c85..78d8bcc2c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/LocationTimerInfo.java @@ -1,25 +1,45 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt; -import net.automatalib.symbol.time.TimedInput; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + import net.automatalib.automaton.mmlt.TimerInfo; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Serializable; -import java.util.*; - /** * Stores information about local timers of a location. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class LocationTimerInfo implements Serializable { +public class LocationTimerInfo { - private static final Logger logger = LoggerFactory.getLogger(LocationTimerInfo.class); + private static final Logger LOGGER = LoggerFactory.getLogger(LocationTimerInfo.class); private final Map> timers; // name -> info @@ -43,6 +63,9 @@ public Word> getPrefix() { /** * Adds a local timer to this location. * + * @param timer + * the timer to add + * */ public void addTimer(TimerInfo timer) { this.timers.put(timer.name(), timer); @@ -51,28 +74,37 @@ public void addTimer(TimerInfo timer) { } public void removeTimer(String timerName) { - if (!this.timers.containsKey(timerName)) { - logger.warn("Attempted to remove an unknown timer."); - return; + final TimerInfo removedTimer = this.timers.remove(timerName); + if (removedTimer == null) { + LOGGER.warn("Attempted to remove an unknown timer."); + } else { + this.sortedTimers.remove(removedTimer); } - TimerInfo removedTimer = this.timers.remove(timerName); - this.sortedTimers.remove(removedTimer); } - @Nullable - public TimerInfo getTimerInfo(long initial) { - Optional> timer = this.sortedTimers.stream().filter(t -> t.initial() == initial).findAny(); - return timer.orElse(null); + /** + * Returns the timer with the given initial value. + * + * @param initial + * the queried initial value + * + * @return the timer with given timeout, {@code null} if no such timer exists + */ + public @Nullable TimerInfo getTimerInfo(long initial) { + for (TimerInfo t : this.sortedTimers) { + if (t.initial() == initial) { + return t; + } + } + return null; } - /** - * Returns the timer with the highest initial value + * Returns the timer with the highest initial value. * - * @return Timer with maximum timeout. Null, if no timers defined. + * @return the timer with maximum timeout, {@code null} if no timers defined */ - @Nullable - public TimerInfo getLastTimer() { + public @Nullable TimerInfo getLastTimer() { if (this.timers.isEmpty()) { return null; } @@ -80,13 +112,14 @@ public TimerInfo getLastTimer() { } /** - * Sets the given timer to one-shot, ensuring that there is only one one-shot timer at a time. - * This is preferred over setting the timer property. + * Sets the given timer to one-shot, ensuring that there is only one one-shot timer at a time. This is preferred + * over setting the timer property. * - * @param name Name of the new one-shot timer + * @param name + * name of the new one-shot timer */ public void setOneShotTimer(String name) { - var oneShotTimer = this.timers.get(name); + TimerInfo oneShotTimer = this.timers.get(name); if (oneShotTimer == null) { throw new IllegalArgumentException("Unknown one-shot timer name."); } @@ -95,7 +128,7 @@ public void setOneShotTimer(String name) { } // update references - var newTimer = oneShotTimer.asOneShot(); + TimerInfo newTimer = oneShotTimer.asOneShot(); this.timers.put(name, newTimer); this.sortedTimers.set(sortedTimers.size() - 1, newTimer); } @@ -103,19 +136,17 @@ public void setOneShotTimer(String name) { /** * Returns a list of all timers defined in this location, sorted by their initial value. * - * @return List of local timers. Empty, if none. + * @return list of local timers, may be empty */ public List> getSortedTimers() { return Collections.unmodifiableList(sortedTimers); } /** - * Returns an unmodifiable view of the timers defined for this location. - * Format: name -> info + * Returns an unmodifiable view of the timers defined for this location. Format: name -> info * - * @return Map of local timers. Empty, if none defined. + * @return map of local timers, may be empty */ - @NonNull public Map> getLocalTimers() { return Collections.unmodifiableMap(this.timers); } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java index 2643f65eb..5ce6d64a7 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypDataContainer.java @@ -1,26 +1,44 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt; -import de.learnlib.time.MMLTModelParams; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + import de.learnlib.datastructure.observationtable.Row; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - /** - * Stores various data used for describing the MMLT hypothesis. - * This includes the OT, a list of local resets, and a list of outputs. + * Stores various data used for describing the MMLT hypothesis. This includes the observation table, a list of local + * resets, and a list of outputs. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ class MMLTHypDataContainer { + private final Alphabet> alphabet; private final MMLTObservationTable table; @@ -29,7 +47,9 @@ class MMLTHypDataContainer { private final MMLTModelParams modelParams; - public MMLTHypDataContainer(Alphabet> alphabet, MMLTModelParams modelParams, MMLTObservationTable table) { + MMLTHypDataContainer(Alphabet> alphabet, + MMLTModelParams modelParams, + MMLTObservationTable table) { this.alphabet = alphabet; this.modelParams = modelParams; this.table = table; @@ -38,35 +58,32 @@ public MMLTHypDataContainer(Alphabet> alphabet, MMLTModelParams this.transitionResetSet = new HashSet<>(); } - @Nullable - protected TimedOutput getTransitionOutput(Row> stateRow, int inputIdx) { + @Nullable TimedOutput getTransitionOutput(Row> stateRow, int inputIdx) { Row> transRow = stateRow.getSuccessor(inputIdx); if (transRow == null) { return null; } - return this.transitionOutputMap.getOrDefault(transRow.getLabel(), null); + return this.transitionOutputMap.get(transRow.getLabel()); } - - public MMLTModelParams getModelParams() { + MMLTModelParams getModelParams() { return modelParams; } - public Alphabet> getAlphabet() { + Alphabet> getAlphabet() { return alphabet; } - - public MMLTObservationTable getTable() { + MMLTObservationTable getTable() { return table; } - public Map>, TimedOutput> getTransitionOutputMap() { + Map>, TimedOutput> getTransitionOutputMap() { return transitionOutputMap; } - public Set>> getTransitionResetSet() { + Set>> getTransitionResetSet() { return transitionResetSet; } } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java index bbf3fc734..2509b1b7f 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/MMLTHypothesis.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt; import java.util.Map; @@ -6,7 +21,6 @@ import net.automatalib.automaton.mmlt.State; import net.automatalib.automaton.mmlt.SymbolCombiner; import net.automatalib.automaton.mmlt.impl.CompactMMLT; -import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; @@ -14,9 +28,9 @@ * An MMLT hypothesis that includes a prefix mapping. This mapping assigns a short prefix to each location. * * @param - * Input type for non-delaying inputs + * input symbol type (of non-delaying inputs) * @param - * Output symbol type + * output symbol type */ public class MMLTHypothesis extends CompactMMLT { @@ -42,16 +56,29 @@ public class MMLTHypothesis extends CompactMMLT { * @return Assigned prefix */ public Word> getPrefix(State configuration) { - var locPrefix = getLocationPrefix(configuration); + Word> locPrefix = getLocationPrefix(configuration); if (configuration.isEntryConfig()) { return locPrefix; // entry distance = 0 } else { - return locPrefix.append(new TimeStepSequence<>(configuration.getEntryDistance())); + return locPrefix.append(TimedInput.step(configuration.getEntryDistance())); } } + /** + * Returns a prefix for the given location. This prefix is deterministic in the learner. + * + * @param location + * Location + * + * @return Location prefix + */ + public Word> getPrefix(Integer location) { + return prefixMap.get(location); + } + public Word> getPrefix(Word> prefix) { - var resultingConfig = getSemantics().getState(prefix); + State resultingConfig = getSemantics().getState(prefix); + assert resultingConfig != null; return getPrefix(resultingConfig); } @@ -64,20 +91,8 @@ public Word> getPrefix(Word> prefix) { * @return Assigned prefix */ public Word> getLocationPrefix(State configuration) { - var locPrefix = this.prefixMap.get(configuration.getLocation()); - if (locPrefix == null) {throw new AssertionError();} + Word> locPrefix = this.prefixMap.get(configuration.getLocation()); + assert locPrefix != null; return locPrefix; } - - /** - * Returns a prefix for the given location. This prefix is deterministic in the RS learner. - * - * @param location - * Location - * - * @return Location prefix - */ - public Word> getPrefix(Integer location) { - return prefixMap.get(location); - } } 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 26fbee4fd..403f6a66f 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 @@ -1,46 +1,70 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt; -import de.learnlib.datastructure.observationtable.MutableObservationTable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; import de.learnlib.datastructure.observationtable.RowImpl; +import de.learnlib.filter.FilterResponse; import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.oracle.MembershipOracle; -import de.learnlib.filter.FilterResponse; +import de.learnlib.oracle.TimedQueryOracle.TimerQueryResult; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.mmlt.TimerInfo; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; -import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * The observation table used by the MMLT learner. *

          - * Unlike an OT for standard Mealy learning, includes prefixes for the timeout transitions of one-shot timers. - * Intended to be used with a symbol filter. The filter is queried before adding a new transition for a non-delaying input. - * If the filter considers the transition to be a silent self-loop, the output of the transition is first verified. - * If it is actually silent the learner considers the transition to be a silent self-loop. Consequently, - * it does not add a transition for it. Transitions may be added later if an input was falsely ignored. + * Unlike an OT for standard Mealy learning, includes prefixes for the timeout transitions of one-shot timers. Intended + * to be used with a symbol filter. The filter is queried before adding a new transition for a non-delaying input. If + * the filter considers the transition to be a silent self-loop, the output of the transition is first verified. If it + * is actually silent the learner considers the transition to be a silent self-loop. Consequently, it does not add a + * transition for it. Transitions may be added later if an input was falsely ignored. *

          * Assumes that all short prefixes lead to different locations (-> no need to make canonical) * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class MMLTObservationTable implements MutableObservationTable, Word>> { +public class MMLTObservationTable implements ObservationTable, Word>> { - private static final Logger logger = LoggerFactory.getLogger(MMLTObservationTable.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MMLTObservationTable.class); + private static final int NO_CONTENT = -1; private final MutableSymbolFilter, InputSymbol> symbolFilter; @@ -49,11 +73,11 @@ public class MMLTObservationTable implements MutableObservationTable>, RowImpl>> shortPrefixRowMap; // label -> row info private final Map>, RowImpl>> longPrefixRowMap; // label -> row info - private final List>> sortedShortPrefixes; // values of shortPrefixRowMap sorted by label, for faster access. + private final List>> sortedShortPrefixes; + // values of shortPrefixRowMap sorted by label, for faster access. private final List>> longPrefixList; // values of longPrefixRowMap as list, for faster access. private final Map> rowContentMap; // contentID -> row content - private static final int NO_CONTENT = -1; private final List>> suffixes = new ArrayList<>(); private final Set>> suffixSet = new HashSet<>(); @@ -62,8 +86,10 @@ public class MMLTObservationTable implements MutableObservationTable silentOutput; // used for symbol filtering - public MMLTObservationTable(Alphabet> alphabet, long minTimerQueryWaitTime, - @NonNull MutableSymbolFilter, InputSymbol> symbolFilter, O silentOutput) { + public MMLTObservationTable(Alphabet> alphabet, + long minTimerQueryWaitTime, + MutableSymbolFilter, InputSymbol> symbolFilter, + O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; @@ -84,21 +110,23 @@ public MMLTObservationTable(Alphabet> alphabet, long minTimerQuery /** * Infers local timers for the provided location. * - * @param location Source location. + * @param location + * source location */ private void identifyLocalTimers(LocationTimerInfo location, TimedQueryOracle timeOracle) { - var timerQueryResponse = timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); - var timers = timerQueryResponse.timers(); + TimerQueryResult timerQueryResponse = + timeOracle.queryTimers(location.getPrefix(), this.minTimerQueryWaitTime); + List> timers = timerQueryResponse.timers(); if (timerQueryResponse.aborted()) { - var end = ExtensibleLStarMMLT.selectOneShotTimer(timers, Long.MAX_VALUE); + int end = ExtensibleLStarMMLT.selectOneShotTimer(timers, Long.MAX_VALUE); timers.set(end, timers.get(end).asOneShot()); } // Add timers up to one-shot: - for (var timer : timerQueryResponse.timers()) { + for (TimerInfo timer : timerQueryResponse.timers()) { location.addTimer(timer); - this.extendAlphabet(new TimeStepSequence<>(timer.initial())); + this.extendAlphabet(TimedInput.step(timer.initial())); if (!timer.periodic()) { break; } @@ -108,7 +136,8 @@ private void identifyLocalTimers(LocationTimerInfo location, TimedQueryOra /** * Extends the global alphabet without adding new transitions. * - * @param symbol New alphabet symbol + * @param symbol + * new alphabet symbol */ private void extendAlphabet(TimeStepSequence symbol) { if (!alphabet.containsSymbol(symbol)) { @@ -123,7 +152,7 @@ private void extendAlphabet(TimeStepSequence symbol) { /** * Adds the initial location. * - * @return Corresponding row in the OT + * @return corresponding row in the observation table */ private RowImpl> addInitialLocation() { RowImpl> newRow = new RowImpl<>(Word.epsilon(), 0, alphabet.size()); @@ -135,13 +164,14 @@ private RowImpl> addInitialLocation() { return newRow; } - /** - * Adds a new location that belongs to the provided short-prefix row. - * Infers timers for this location and creates outgoing transitions. + * Adds a new location that belongs to the provided short-prefix row. Infers timers for this location and creates + * outgoing transitions. * - * @param newRow Newly-added short prefix row - * @param timeOracle Time oracle + * @param newRow + * newly-added short prefix row + * @param timeOracle + * time oracle */ private void initLocation(RowImpl> newRow, TimedQueryOracle timeOracle) { LocationTimerInfo timerInfo = new LocationTimerInfo<>(newRow.getLabel()); @@ -152,22 +182,27 @@ private void initLocation(RowImpl> newRow, TimedQueryOracle } // Add outgoing transitions: - List>> transitions = this.createOutgoingTransitions(newRow, timeOracle); - transitions.forEach(t -> this.queryAllSuffixes(t, timeOracle)); + for (RowImpl> t : this.createOutgoingTransitions(newRow, timeOracle)) { + this.queryAllSuffixes(t, timeOracle); + } } /** - * Creates transitions for the provided short-prefix row. Adds transitions for non-delaying inputs - * and a transition for the one-shot timer of the location, if present. + * Creates transitions for the provided short-prefix row. Adds transitions for non-delaying inputs and a transition + * for the one-shot timer of the location, if present. *

          - * If a symbol filter is provided, the filter is queried before adding a transition for a non-delaying input. - * If the filter considers the input a silent self-loop, no transition is explicitly created for the input. + * If a symbol filter is provided, the filter is queried before adding a transition for a non-delaying input. If the + * filter considers the input a silent self-loop, no transition is explicitly created for the input. + * + * @param spRow + * short prefix row + * @param timeOracle + * time query oracle * - * @param spRow Short prefix row - * @param timeOracle Time query oracle - * @return New transitions + * @return new transitions */ - private List>> createOutgoingTransitions(RowImpl> spRow, TimedQueryOracle timeOracle) { + private List>> createOutgoingTransitions(RowImpl> spRow, + TimedQueryOracle timeOracle) { List>> transitions = new ArrayList<>(); Word> sp = spRow.getLabel(); @@ -175,55 +210,58 @@ private List>> createOutgoingTransitions(RowImpl sym = alphabet.getSymbol(i); - if (sym instanceof TimeStepSequence || sym instanceof TimeoutSymbol) { - continue; - } + if (sym instanceof InputSymbol in) { + + Word> lp = sp.append(sym); + assert !this.shortPrefixRowMap.containsKey(lp); + + RowImpl> succRow = this.longPrefixRowMap.get(lp); + if (succRow == null) { + // Query symbol filter before adding transition: + FilterResponse filterResponse = this.symbolFilter.query(sp, in); + if (filterResponse == FilterResponse.IGNORE) { + // Verify that output is silent: + Word> response = timeOracle.answerQuery(sp, Word.fromLetter(sym)); + assert response.size() == 1; + if (!response.firstSymbol().equals(silentOutput)) { + // Not silent -> cannot be silent self-loop: + filterResponse = FilterResponse.ACCEPT; + + // Update filter: + this.symbolFilter.accept(sp, in); + } + } - Word> lp = sp.append(sym); - assert !this.shortPrefixRowMap.containsKey(lp); - - RowImpl> succRow = this.longPrefixRowMap.get(lp); - if (succRow == null) { - // Query symbol filter before adding transition: - var filterResponse = this.symbolFilter.query(sp, (InputSymbol) sym); - if (filterResponse == FilterResponse.IGNORE) { - // Verify that output is silent: - var response = timeOracle.answerQuery(sp, Word.fromLetter(sym)); - assert response.size() == 1; - if (!response.firstSymbol().equals(silentOutput)) { - // Not silent -> cannot be silent self-loop: - filterResponse = FilterResponse.ACCEPT; - - // Update filter: - this.symbolFilter.accept(sp, (InputSymbol) sym); + if (filterResponse == FilterResponse.ACCEPT) { + // Treat as usual: + succRow = this.createLpRow(lp); } } - if (filterResponse == FilterResponse.ACCEPT) { - // Treat as usual: - succRow = this.createLpRow(lp); + spRow.setSuccessor(i, succRow); + if (succRow != null) { + transitions.add(succRow); } } - spRow.setSuccessor(i, succRow); - if (succRow != null) { - transitions.add(succRow); - } } // Second, add one-shot timer transition (if any): - var locTimers = timerInfoMap.getOrDefault(spRow.getLabel(), null); - if (locTimers != null && !locTimers.getLastTimer().periodic()) { - TimedInput waitSym = new TimeStepSequence<>(locTimers.getLastTimer().initial()); - Word> lp = sp.append(waitSym); - assert !this.shortPrefixRowMap.containsKey(lp); - - RowImpl> succRow = this.longPrefixRowMap.get(lp); - if (succRow == null) { - succRow = this.createLpRow(lp); + LocationTimerInfo locTimers = timerInfoMap.get(spRow.getLabel()); + if (locTimers != null) { + TimerInfo lastTimer = locTimers.getLastTimer(); + if (lastTimer != null && !lastTimer.periodic()) { + TimedInput waitSym = new TimeStepSequence<>(lastTimer.initial()); + Word> lp = sp.append(waitSym); + assert !this.shortPrefixRowMap.containsKey(lp); + + RowImpl> succRow = this.longPrefixRowMap.get(lp); + if (succRow == null) { + succRow = this.createLpRow(lp); + } + spRow.setSuccessor(this.alphabet.getSymbolIndex(waitSym), succRow); + transitions.add(succRow); } - spRow.setSuccessor(this.alphabet.getSymbolIndex(waitSym), succRow); - transitions.add(succRow); } return transitions; @@ -233,7 +271,7 @@ private RowImpl> createLpRow(Word> prefix) { RowImpl> newRow = new RowImpl<>(prefix, 0); this.longPrefixRowMap.put(prefix, newRow); this.longPrefixList.add(newRow); - if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + assert this.longPrefixList.size() == this.longPrefixRowMap.size(); newRow.setLpIndex(0); // unused @@ -241,21 +279,19 @@ private RowImpl> createLpRow(Word> prefix) { } /** - * Identify transitions that have not been closed. - * I.e., there is no state with the same suffix behavior. - * Also removes unused content ids. - *

          - * Guarantees that returned transition list order is deterministic. + * Identify transitions that have not been closed, i.e., there is no state with the same suffix behavior. Also + * removes unused content ids. + * + * @return the list of unclosed transition, in a deterministic order */ public List>>> findUnclosedTransitions() { // Identify contentIds for locations: - Set spContentIds = this.shortPrefixRowMap.values().stream() - .map(RowImpl::getRowContentId) - .collect(Collectors.toSet()); + Set spContentIds = + this.shortPrefixRowMap.values().stream().map(RowImpl::getRowContentId).collect(Collectors.toSet()); // Group lp rows by their content id: Map>>> lpContentMap = new HashMap<>(); - for (var lpRow : this.longPrefixRowMap.values()) { + for (RowImpl> lpRow : this.longPrefixRowMap.values()) { lpContentMap.putIfAbsent(lpRow.getRowContentId(), new ArrayList<>()); lpContentMap.get(lpRow.getRowContentId()).add(lpRow); } @@ -263,7 +299,7 @@ public List>>> findUnclosedTransitions() { // Identify ids that are not used by any SP: List>>> unclosedRows = new ArrayList<>(); List sortedLpIds = lpContentMap.keySet().stream().sorted().toList(); - for (var lpId : sortedLpIds) { + for (Integer lpId : sortedLpIds) { if (spContentIds.contains(lpId)) { continue; } @@ -275,8 +311,10 @@ public List>>> findUnclosedTransitions() { } // Remove unused content ids: - Set usedContentIds = Stream.concat(this.shortPrefixRowMap.values().stream(), this.longPrefixRowMap.values().stream()) - .map(RowImpl::getRowContentId).collect(Collectors.toSet()); + Set usedContentIds = + Stream.concat(this.shortPrefixRowMap.values().stream(), this.longPrefixRowMap.values().stream()) + .map(RowImpl::getRowContentId) + .collect(Collectors.toSet()); List oldContentIds = this.rowContentMap.keySet().stream().toList(); for (int oldId : oldContentIds) { @@ -285,14 +323,12 @@ public List>>> findUnclosedTransitions() { } } - return unclosedRows; } - @Override public List>>> initialize(List>> initialShortPrefixes, List>> initialSuffixes, - MembershipOracle, Word>> oracle) { + TimedQueryOracle oracle) { if (isInitialized()) { throw new IllegalStateException("Called initialize, but there are already rows present"); @@ -300,9 +336,6 @@ public List>>> initialize(List>> initi if (!initialShortPrefixes.isEmpty()) { throw new IllegalArgumentException("Init with short prefixes is not supported."); } - if (!(oracle instanceof TimedQueryOracle timedOracle)) { - throw new IllegalArgumentException("Must use timed oracle!"); - } // Add initial suffixes: for (Word> suffix : initialSuffixes) { @@ -312,9 +345,9 @@ public List>>> initialize(List>> initi } // 1. Create initial location: - var newLoc = this.addInitialLocation(); - this.initLocation(newLoc, timedOracle); - this.queryAllSuffixes(newLoc, timedOracle); + RowImpl> newLoc = this.addInitialLocation(); + this.initLocation(newLoc, oracle); + this.queryAllSuffixes(newLoc, oracle); // 2. Identify unclosed transitions: return this.findUnclosedTransitions(); @@ -344,27 +377,17 @@ private void processSuffixOutputs(RowImpl> row, List>>> addSuffixes(Collection>> newSuffixes, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof TimedQueryOracle timedOracle)) { - throw new IllegalArgumentException(); - } - + public List>>> addSuffixes(Collection>> newSuffixes, + TimedQueryOracle oracle) { // 1. Extend current suffixes + identify new suffixes: List>> newSuffixList = new ArrayList<>(); for (Word> suffix : newSuffixes) { if (this.suffixSet.add(suffix)) { - logger.debug(String.format("Adding new suffix '%s'", suffix)); + LOGGER.debug("Adding new suffix '{}'", suffix); newSuffixList.add(suffix); this.suffixes.add(suffix); @@ -383,7 +406,7 @@ public List>>> addSuffixes(Collection> suffix : newSuffixList) { - Word> output = timedOracle.answerQuery(row.getLabel(), suffix); + Word> output = oracle.answerQuery(row.getLabel(), suffix); updatedOutputs.add(output); } @@ -393,26 +416,17 @@ public List>>> addSuffixes(Collection>>> addShortPrefixes(List>> shortPrefixes, MembershipOracle, Word>> oracle) { - throw new IllegalStateException("Not supported."); - } - - @Override - public List>>> toShortPrefixes(List>> lpRows, MembershipOracle, Word>> oracle) { - if (!(oracle instanceof TimedQueryOracle timedOracle)) { - throw new IllegalArgumentException(); - } - + public List>>> toShortPrefixes(List>> lpRows, + TimedQueryOracle oracle) { for (Row> row : lpRows) { - logger.debug(String.format("Adding new location with prefix '%s'", row.getLabel())); + LOGGER.debug("Adding new location with prefix '{}'", row.getLabel()); final RowImpl> lpRow = (RowImpl>) row; // Delete from LP rows: - var removed = this.longPrefixRowMap.remove(row.getLabel()); + RowImpl> removed = this.longPrefixRowMap.remove(row.getLabel()); this.longPrefixList.remove(removed); - if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + assert this.longPrefixList.size() == this.longPrefixRowMap.size(); // Add to SP rows: this.shortPrefixRowMap.put(row.getLabel(), lpRow); @@ -421,16 +435,11 @@ public List>>> toShortPrefixes(List>> l lpRow.makeShort(alphabet.size()); - this.initLocation(lpRow, timedOracle); + this.initLocation(lpRow, oracle); } return this.findUnclosedTransitions(); } - @Override - public List>>> addAlphabetSymbol(TimedInput symbol, MembershipOracle, Word>> oracle) { - throw new IllegalStateException("Not supported."); - } - @Override public Alphabet> getInputAlphabet() { return this.alphabet; @@ -438,7 +447,7 @@ public Alphabet> getInputAlphabet() { @Override public Collection>> getShortPrefixRows() { - if (this.sortedShortPrefixes.size() != this.shortPrefixRowMap.size()) throw new AssertionError(); + assert this.sortedShortPrefixes.size() == this.shortPrefixRowMap.size(); return Collections.unmodifiableList(this.sortedShortPrefixes); } @@ -474,7 +483,6 @@ public List>> getSuffixes() { } @Override - @Nullable public List>> rowContents(Row> row) { if (this.rowContentMap.isEmpty()) { // OT may be empty if only single location with timers: @@ -492,37 +500,41 @@ public Word> transformAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } - @Override public boolean isAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } public @Nullable TimerInfo getTimerInfo(Word> prefix, long initial) { - var info = this.timerInfoMap.get(prefix); + LocationTimerInfo info = this.timerInfoMap.get(prefix); if (info != null) { return info.getTimerInfo(initial); } return null; } - @Nullable - public LocationTimerInfo getLocationTimerInfo(Row> sp) { + public @Nullable LocationTimerInfo getLocationTimerInfo(Row> sp) { return this.timerInfoMap.getOrDefault(sp.getLabel(), null); } /** - * Adds an outgoing transition for the given symbol to the given location - * and subsequently tests for unclosed transitions. + * Adds an outgoing transition for the given symbol to the given location and subsequently tests for unclosed + * transitions. *

          * Raises an error if this transition already exists. * - * @param spRow Source location - * @param symbol Input symbol - * @param timeOracle Oracle + * @param spRow + * Source location + * @param symbol + * Input symbol + * @param timeOracle + * Oracle + * * @return List of unclosed rows. Empty, if none. */ - public List>>> addOutgoingTransition(Row> spRow, TimedInput symbol, TimedQueryOracle timeOracle) { + public List>>> addOutgoingTransition(Row> spRow, + TimedInput symbol, + TimedQueryOracle timeOracle) { if (!this.alphabet.containsSymbol(symbol)) { throw new IllegalArgumentException("Unknown symbol."); } @@ -546,15 +558,18 @@ public List>>> addOutgoingTransition(Row> s return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, TimerInfo timeout, TimedQueryOracle timeOracle) { + public List>>> addTimerTransition(Row> spRow, + TimerInfo timeout, + TimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } /** - * Removes a long prefix row. Should only be used when removing a transition of a former one-shot timer. - * When turning a long into a short prefix, use toShortPrefix instead, + * Removes a long prefix row. Should only be used when removing a transition of a former one-shot timer. When + * turning a long into a short prefix, use toShortPrefix instead, * - * @param prefix Row prefix + * @param prefix + * Row prefix */ public void removeLpRow(Word> prefix) { if (!this.longPrefixRowMap.containsKey(prefix)) { @@ -562,9 +577,9 @@ public void removeLpRow(Word> prefix) { } // Remove lp row: - var removed = this.longPrefixRowMap.remove(prefix); + RowImpl> removed = this.longPrefixRowMap.remove(prefix); this.longPrefixList.remove(removed); - if (this.longPrefixList.size() != this.longPrefixRowMap.size()) throw new AssertionError(); + assert this.longPrefixList.size() == this.longPrefixRowMap.size(); // Unset as successor: int symIdx = this.alphabet.getSymbolIndex(prefix.lastSymbol()); @@ -576,13 +591,6 @@ public void removeLpRow(Word> prefix) { // ============================= - private record RowContent(List>> outputs) { - - @Override - public int hashCode() { - return outputs.toString().hashCode(); - } - } - + private record RowContent(List>> outputs) {} } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java index 6dfde3bbe..ff354d016 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/ExtendedDecomposition.java @@ -1,30 +1,49 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex; - -import net.automatalib.symbol.time.TimedInput; import net.automatalib.automaton.mmlt.State; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; /** - * An extended decomposition represents a transition with an incorrect target or output in the expanded form of - * a hypothesis MMLT. + * An extended decomposition represents a transition with an incorrect target or output in the expanded form of a + * hypothesis MMLT. * - * @param state Source state in expanded form of hypothesis - * @param input Input of some transition with incorrect output or target source state - * @param discriminator If not null: transition has incorrect target - * @param Input type for non-delaying inputs + * @param state + * source state in expanded form of hypothesis + * @param input + * input of some transition with incorrect output or target source state + * @param discriminator + * discriminator for identifying an incorrect target state (may be {@code null} if the decomposition identifies + * an incorrect output only) + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -record ExtendedDecomposition(State state, - @NonNull TimedInput input, +record ExtendedDecomposition(State state, TimedInput input, @Nullable Word> discriminator) { - public ExtendedDecomposition(State state, @NonNull TimedInput input) { + ExtendedDecomposition(State state, TimedInput input) { this(state, input, null); } - public boolean isForIncorrectOutput() { + boolean isForIncorrectOutput() { return this.discriminator == null; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecomposer.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecomposer.java new file mode 100644 index 000000000..80bcbbcaf --- /dev/null +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecomposer.java @@ -0,0 +1,172 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex; + +import de.learnlib.acex.AcexAnalyzer; +import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; +import de.learnlib.oracle.TimedQueryOracle; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.word.Word; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Implements the search for an extended decomposition of a truncated counterexample and the post-processing of an + * extended decomposition. + * + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + */ +class MMLTCounterexampleDecomposer { + + private static final Logger LOGGER = LoggerFactory.getLogger(MMLTCounterexampleDecomposer.class); + + private final TimedQueryOracle timeOracle; + private final AcexAnalyzer acexAnalyzer; + + MMLTCounterexampleDecomposer(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { + this.timeOracle = timeOracle; + this.acexAnalyzer = acexAnalyzer; + } + + ExtendedDecomposition findExtendedDecomposition(MMLTOutputInconsistency outIncons, + MMLTHypothesis hypothesis) { + + if (outIncons.suffix().length() == 1) { + // Incorrect output: + State prefixState = hypothesis.getSemantics().getState(outIncons.prefix()); + return new ExtendedDecomposition<>(prefixState, outIncons.suffix().firstSymbol()); + } + + // Verify breakpoint condition: + MMLTInconsPrefixTransformAcex acex = new MMLTInconsPrefixTransformAcex<>(outIncons.suffix(), + timeOracle, + w -> hypothesis.getPrefix( + outIncons.prefix() + .concat(w))); + + if (acex.testEffects(0, acex.getLength() - 1)) { + // Breakpoint condition not met -> must be incorrect output: + Word> lastStatePrefix = + outIncons.prefix().concat(outIncons.suffix().prefix(outIncons.suffix().length() - 1)); + State lastState = hypothesis.getSemantics().getState(lastStatePrefix); + + return new ExtendedDecomposition<>(lastState, outIncons.suffix().lastSymbol()); + } + + // Breakpoint condition met -> find decomposition: + int breakpoint = this.acexAnalyzer.analyzeAbstractCounterexample(acex); + + assert !acex.testEffects(breakpoint, breakpoint + 1) : "Failed to find valid decomposition."; + + // Get components: + Word> prefix = outIncons.prefix().concat(outIncons.suffix().prefix(breakpoint)); + TimedInput sym = outIncons.suffix().getSymbol(breakpoint); + Word> discriminator = outIncons.suffix().subWord(breakpoint + 1); + + State prefixState = hypothesis.getSemantics().getState(prefix); + + LOGGER.debug(""" + Decomposing to {}|{}|{} + Output at {}: {}. + Output at {}: {} + """, + prefixState, + sym, + discriminator, + breakpoint, + acex.computeEffect(breakpoint), + breakpoint + 1, + acex.computeEffect(breakpoint + 1)); + + return new ExtendedDecomposition<>(prefixState, sym, discriminator); + } + + /** + * Post-processes an extended decomposition: if the decomposition corresponds to a transition with an incorrect + * target or output at a timeout symbol, transforms the decomposition so that the input is either a non-delaying + * input or a single time step. + * + * @param decomposition + * extended decomposition + * + * @return post-processed decomposition + */ + ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { + if (!(decomposition.input() instanceof TimeoutSymbol)) { + return decomposition; + } + + Word> statePrefix = hypothesis.getPrefix(decomposition.state()); + Word> hypOutput = + hypothesis.getSemantics().computeSuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); + Word> sulOutput = timeOracle.answerQuery(statePrefix, Word.fromLetter(decomposition.input())); + + if (decomposition.isForIncorrectOutput()) { + assert hypOutput.firstSymbol().delay() != 0 || sulOutput.firstSymbol().delay() == 0; + + // Incorrect output at tout: + long minWaitTime; + if (hypOutput.firstSymbol().delay() != 0 && sulOutput.firstSymbol().delay() == 0) { + // If there is no timeout in either hyp or sul, need to trigger next observable timeout: + minWaitTime = hypOutput.firstSymbol().delay(); + } else { + // If there is a timeout in hyp and sul, go to next timeout: + minWaitTime = Math.min(hypOutput.firstSymbol().delay(), sulOutput.firstSymbol().delay()); + } + + // if minimum time is zero (= no timeout) or one, need to append empty word to prefix: + State newPrefixState; + if (minWaitTime <= 1) { + newPrefixState = decomposition.state(); + } else { + newPrefixState = + hypothesis.getSemantics().getState(statePrefix.append(TimedInput.step(minWaitTime - 1))); + } + + LOGGER.debug("Updated incorrect output at tout during post-processing."); + return new ExtendedDecomposition<>(newPrefixState, TimedInput.step()); + } else { + if (decomposition.state().isStableConfig() || hypOutput.equals(sulOutput)) { + // stable-configuration or same output at tout -> same wait time for output in hyp and SUL: + assert hypOutput.firstSymbol().delay() == sulOutput.firstSymbol().delay(); + + long waitTime = hypOutput.firstSymbol().delay(); + State newPrefixState; + if (waitTime <= 1) { + newPrefixState = decomposition.state(); + } else { + newPrefixState = + hypothesis.getSemantics().getState(statePrefix.append(TimedInput.step(waitTime - 1))); + } + + LOGGER.debug("Updated incorrect target at tout during post-processing."); + return new ExtendedDecomposition<>(newPrefixState, TimedInput.step(), decomposition.discriminator()); + } else { + // different output at tout -> found incorrect output: + LOGGER.debug("Found incorrect output through post-processing."); + return postProcessExtendedDecomposition(new ExtendedDecomposition<>(decomposition.state(), + decomposition.input()), hypothesis); + } + } + } +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java deleted file mode 100644 index 6a0713739..000000000 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleDecompositor.java +++ /dev/null @@ -1,138 +0,0 @@ -package de.learnlib.algorithm.lstar.mmlt.cex; - -import de.learnlib.acex.AcexAnalyzer; -import de.learnlib.algorithm.lstar.mmlt.MMLTHypothesis; -import de.learnlib.oracle.TimedQueryOracle; -import net.automatalib.automaton.mmlt.State; -import net.automatalib.symbol.time.TimeStepSequence; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.word.Word; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Implements the search for an extended decomposition of a truncated counterexample and the post-processing of an extended decomposition. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -class MMLTCounterexampleDecompositor { - - private static final Logger logger = LoggerFactory.getLogger(MMLTCounterexampleDecompositor.class); - - - private final TimedQueryOracle timeOracle; - private final AcexAnalyzer acexAnalyzer; - - public MMLTCounterexampleDecompositor(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer) { - this.timeOracle = timeOracle; - this.acexAnalyzer = acexAnalyzer; - } - - ExtendedDecomposition findExtendedDecomposition(MMLTOutputInconsistency outIncons, - MMLTHypothesis hypothesis) { - - if (outIncons.suffix().length() == 1) { - // Incorrect output: - var prefixState = hypothesis.getSemantics().getState(outIncons.prefix()); - return new ExtendedDecomposition<>(prefixState, outIncons.suffix().firstSymbol()); - } - - // Verify breakpoint condition: - MMLTInconsPrefixTransformAcex acex = new MMLTInconsPrefixTransformAcex<>(outIncons.suffix(), timeOracle, - w -> hypothesis.getPrefix(outIncons.prefix().concat(w))); - - if (acex.testEffects(0, acex.getLength() - 1)) { - // Breakpoint condition not met -> must be incorrect output: - var lastStatePrefix = outIncons.prefix().concat(outIncons.suffix().prefix(outIncons.suffix().length() - 1)); - var lastState = hypothesis.getSemantics().getState(lastStatePrefix); - - return new ExtendedDecomposition<>(lastState, outIncons.suffix().lastSymbol()); - } - - // Breakpoint condition met -> find decomposition: - int breakpoint = this.acexAnalyzer.analyzeAbstractCounterexample(acex); - if (acex.testEffects(breakpoint, breakpoint + 1)) { - throw new AssertionError("Failed to find valid decomposition."); - } - - // Get components: - Word> prefix = outIncons.prefix().concat(outIncons.suffix().prefix(breakpoint)); - TimedInput sym = outIncons.suffix().getSymbol(breakpoint); - Word> discriminator = outIncons.suffix().subWord(breakpoint + 1); - - var prefixState = hypothesis.getSemantics().getState(prefix); - - logger.debug(String.format("Decomposing to %s|%s|%s %n" + "Output at %d: %s. %nOutput at %d: %s", prefixState, sym, discriminator, breakpoint, - acex.computeEffect(breakpoint), breakpoint + 1, acex.computeEffect(breakpoint + 1))); - - return new ExtendedDecomposition<>(prefixState, sym, discriminator); - } - - /** - * Post-processes an extended decomposition: if the decomposition corresponds to a transition with an incorrect target or output at a timeout symbol, - * transforms the decomposition so that the input is either a non-delaying input or a single time step. - * - * @param decomposition Extended decomposition - * @return Post-processed decomposition - */ - ExtendedDecomposition postProcessExtendedDecomposition(ExtendedDecomposition decomposition, - MMLTHypothesis hypothesis) { - if (!(decomposition.input() instanceof TimeoutSymbol)) { - return decomposition; - } - - var statePrefix = hypothesis.getPrefix(decomposition.state()); - var hypOutput = hypothesis.getSemantics().computeSuffixOutput(statePrefix, Word.fromLetter(decomposition.input())); - var sulOutput = timeOracle.answerQuery(statePrefix, Word.fromLetter(decomposition.input())); - - if (decomposition.isForIncorrectOutput()) { - // Incorrect output at tout: - long minWaitTime; - if (hypOutput.firstSymbol().delay() == 0 && sulOutput.firstSymbol().delay() != 0) { - throw new AssertionError(); - } else if (hypOutput.firstSymbol().delay() != 0 && sulOutput.firstSymbol().delay() == 0) { - // If there is no timeout in either hyp or sul, need to trigger next observable timeout: - minWaitTime = hypOutput.firstSymbol().delay(); - } else { - // If there is a timeout in hyp and sul, go to next timeout: - minWaitTime = Math.min(hypOutput.firstSymbol().delay(), sulOutput.firstSymbol().delay()); - } - - // if minimum time is zero (= no timeout) or one, need to append empty word to prefix: - State newPrefixState; - if (minWaitTime <= 1) { - newPrefixState = decomposition.state(); - } else { - newPrefixState = hypothesis.getSemantics().getState(statePrefix.append(new TimeStepSequence<>(minWaitTime - 1))); - } - - logger.debug("Updated incorrect output at tout during post-processing."); - return new ExtendedDecomposition<>(newPrefixState, TimedInput.step()); - } else { - if (decomposition.state().isStableConfig() || hypOutput.equals(sulOutput)) { - // stable-configuration or same output at tout -> same wait time for output in hyp and SUL: - if (hypOutput.firstSymbol().delay() != sulOutput.firstSymbol().delay()) { - throw new AssertionError(); - } - - long waitTime = hypOutput.firstSymbol().delay(); - State newPrefixState; - if (waitTime <= 1) { - newPrefixState = decomposition.state(); - } else { - newPrefixState = hypothesis.getSemantics().getState(statePrefix.append(new TimeStepSequence<>(waitTime - 1))); - } - - logger.debug("Updated incorrect target at tout during post-processing."); - return new ExtendedDecomposition<>(newPrefixState, TimedInput.step(), decomposition.discriminator()); - } else { - // different output at tout -> found incorrect output: - logger.debug("Found incorrect output through post-processing."); - return postProcessExtendedDecomposition(new ExtendedDecomposition<>(decomposition.state(), decomposition.input()), hypothesis); - } - } - } -} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java index 9475ff3c3..c8a641c8d 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTCounterexampleHandler.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex; import java.util.List; @@ -10,37 +25,43 @@ import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingDiscriminatorResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingOneShotResult; import de.learnlib.algorithm.lstar.mmlt.cex.results.MissingResetResult; -import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.filter.SymbolFilter; import de.learnlib.filter.FilterResponse; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.oracle.TimedQueryOracle; +import net.automatalib.automaton.impl.CompactTransition; +import net.automatalib.automaton.mmlt.State; import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; -import org.checkerframework.checker.nullness.qual.NonNull; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * Processes a truncated counterexample for a hypothesis MMLT: - * searches for an extended decomposition, post-processes it, and infers an inaccuracy from it. + * Processes a truncated counterexample for a hypothesis MMLT: searches for an extended decomposition, post-processes + * it, and infers an inaccuracy from it. * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param input + * symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class MMLTCounterexampleHandler { - private static final Logger logger = LoggerFactory.getLogger(MMLTCounterexampleHandler.class); - private final SymbolFilter, InputSymbol> symbolFilter; +public class MMLTCounterexampleHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(MMLTCounterexampleHandler.class); protected final TimedQueryOracle timeOracle; - private final MMLTCounterexampleDecompositor decompositor; + private final MMLTCounterexampleDecomposer decomposer; + private final SymbolFilter, InputSymbol> symbolFilter; - public MMLTCounterexampleHandler(TimedQueryOracle timeOracle, AcexAnalyzer acexAnalyzer, @NonNull SymbolFilter, InputSymbol> symbolFilter) { + public MMLTCounterexampleHandler(TimedQueryOracle timeOracle, + AcexAnalyzer acexAnalyzer, + SymbolFilter, InputSymbol> symbolFilter) { this.timeOracle = timeOracle; - this.decompositor = new MMLTCounterexampleDecompositor<>(timeOracle, acexAnalyzer); + this.decomposer = new MMLTCounterexampleDecomposer<>(timeOracle, acexAnalyzer); this.symbolFilter = symbolFilter; } @@ -48,12 +69,12 @@ public CexAnalysisResult analyzeInconsistency(MMLTOutputInconsistency hypothesis) { // Search for an extended decomposition: - var decomposition = decompositor.findExtendedDecomposition(outIncons, hypothesis); - logger.debug("Found an extended decomposition: {}", decomposition); + ExtendedDecomposition decomposition = decomposer.findExtendedDecomposition(outIncons, hypothesis); + LOGGER.debug("Found an extended decomposition: {}", decomposition); // Post-process the decomposition: - decomposition = decompositor.postProcessExtendedDecomposition(decomposition, hypothesis); - logger.debug("Post-processed decomposition: {}", decomposition); + decomposition = decomposer.postProcessExtendedDecomposition(decomposition, hypothesis); + LOGGER.debug("Post-processed decomposition: {}", decomposition); if (decomposition.isForIncorrectOutput()) { return handleIncorrectOutput(decomposition, hypothesis); @@ -62,13 +83,15 @@ public CexAnalysisResult analyzeInconsistency(MMLTOutputInconsistency handleIncorrectOutput(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectOutput(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { // Transition with incorrect output always implies missing one-shot timer: - logger.debug("Found missing one-shot via incorrect output."); + LOGGER.debug("Found missing one-shot via incorrect output."); return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); } - private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { if (decomposition.input() instanceof InputSymbol ndi) { // If decomposition at non-delaying input + considered as self-loop, treat as false ignore: if (symbolFilter.query(hypothesis.getLocationPrefix(decomposition.state()), ndi) == FilterResponse.IGNORE) { @@ -84,92 +107,108 @@ private CexAnalysisResult handleIncorrectTarget(ExtendedDecomposition selectOneShotTimer(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis, long maxInitialValue) { + private CexAnalysisResult selectOneShotTimer(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis, + long maxInitialValue) { List> timers = hypothesis.getSortedTimers(decomposition.state().getLocation()); - var newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(timers, maxInitialValue); - logger.debug("Missing one-shot: setting ({}|{}) to one-shot.", hypothesis.getLocationPrefix(decomposition.state()), newOneShot); - return new MissingOneShotResult<>(decomposition.state().getLocation(), timers.get(newOneShot)); + int newOneShot = ExtensibleLStarMMLT.selectOneShotTimer(timers, maxInitialValue); + TimerInfo timer = timers.get(newOneShot); + LOGGER.debug("Missing one-shot: setting ({}|{}) to one-shot.", + hypothesis.getLocationPrefix(decomposition.state()), + timer); + return new MissingOneShotResult<>(decomposition.state().getLocation(), timer); } - private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetTimeStep(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { // Check if there is a one-shot timer expiring at the next time step: - List> localTimers = hypothesis.getSortedTimers(decomposition.state().getLocation()); assert !localTimers.isEmpty(); // If location has a one-shot timer, this is the one with the highest initial value: - var lastTimer = localTimers.get(localTimers.size() - 1); + TimerInfo lastTimer = localTimers.get(localTimers.size() - 1); if (!lastTimer.periodic()) { - if (lastTimer.initial() - 1 != decomposition.state().getEntryDistance()) { - throw new AssertionError("Incorrect target must be at timeout of non-periodic timer."); - } - logger.debug("Inferred missing discriminator at timeout."); - return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), decomposition.input(), decomposition.discriminator()); + assert lastTimer.initial() - 1 == decomposition.state().getEntryDistance() : + "Incorrect target must be at timeout of non-periodic timer."; + LOGGER.debug("Inferred missing discriminator at timeout."); + return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), + decomposition.input(), + decomposition.discriminator()); } else if (!decomposition.state().isStableConfig()) { - logger.debug("Found missing one-shot via incorrect target in non-stable config."); + LOGGER.debug("Found missing one-shot via incorrect target in non-stable config."); return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); } else { - logger.debug("Found missing one-shot via incorrect target in stable config."); - return new MissingOneShotResult<>(decomposition.state().getLocation(), localTimers.get(0)); // lowest initial value + LOGGER.debug("Found missing one-shot via incorrect target in stable config."); + return new MissingOneShotResult<>(decomposition.state().getLocation(), + localTimers.get(0)); // lowest initial value } } - private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, MMLTHypothesis hypothesis) { + private CexAnalysisResult handleIncorrectTargetNonDelaying(ExtendedDecomposition decomposition, + MMLTHypothesis hypothesis) { // 1: can be a missing discriminator? // Check if correct target in entry w.r.t. discriminator: - var transPrefix = hypothesis.getLocationPrefix(decomposition.state()).append(decomposition.input()); - var succState = hypothesis.getSemantics().getState(transPrefix); // successor state in hypothesis + Word> transPrefix = + hypothesis.getLocationPrefix(decomposition.state()).append(decomposition.input()); + State succState = hypothesis.getSemantics().getState(transPrefix); // successor state in hypothesis + Word> discriminator = decomposition.discriminator(); - var actualSuffixOutput = this.timeOracle.answerQuery(transPrefix, decomposition.discriminator()); - var expSuffixOutput = this.timeOracle.answerQuery(hypothesis.getPrefix(succState), decomposition.discriminator()); + assert succState != null && discriminator != null; + + Word> actualSuffixOutput = this.timeOracle.answerQuery(transPrefix, discriminator); + Word> expSuffixOutput = + this.timeOracle.answerQuery(hypothesis.getPrefix(succState), discriminator); if (!actualSuffixOutput.equals(expSuffixOutput)) { - logger.debug("Inferred missing discriminator at non-delaying input."); - return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), decomposition.input(), decomposition.discriminator()); + LOGGER.debug("Inferred missing discriminator at non-delaying input."); + return new MissingDiscriminatorResult<>(decomposition.state().getLocation(), + decomposition.input(), + discriminator); } // 2: can be a local reset? if (decomposition.state().isStableConfig()) { - logger.debug("Inferred missing reset in stable config."); - return new MissingResetResult<>(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); + LOGGER.debug("Inferred missing reset in stable config."); + return new MissingResetResult<>(decomposition.state().getLocation(), + (InputSymbol) decomposition.input()); } // Non-stable -> explicitly test for missing reset: - var isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), ((InputSymbol) decomposition.input()).symbol()); - var trans = hypothesis.getTransition(decomposition.state().getLocation(), ((InputSymbol) decomposition.input()).symbol()); - if (trans == null) { - throw new AssertionError(); - } - - var successor = hypothesis.getSuccessor(trans); + boolean isLocalReset = hypothesis.isLocalReset(decomposition.state().getLocation(), + ((InputSymbol) decomposition.input()).symbol()); + CompactTransition trans = hypothesis.getTransition(decomposition.state().getLocation(), + ((InputSymbol) decomposition.input()).symbol()); + assert trans != null; + Integer successor = hypothesis.getSuccessor(trans); // Must loop without reset: - if (successor.equals(decomposition.state().getLocation()) && (!isLocalReset)) { + if (successor.equals(decomposition.state().getLocation()) && !isLocalReset) { // Must have at least two stable configs: - var firstTimer = hypothesis.getSortedTimers(decomposition.state().getLocation()).get(0); + TimerInfo firstTimer = hypothesis.getSortedTimers(decomposition.state().getLocation()).get(0); if (firstTimer.initial() > 1) { // Must not self-loop in at least one non-entry stable config: - var resetTransPrefix = hypothesis.getPrefix(decomposition.state()) - .append(TimedInput.step()) // prefix of first stable config that is not entry config - .append(decomposition.input()); // successor at $i$ in that config + Word> resetTransPrefix = hypothesis.getPrefix(decomposition.state()) + .append(TimedInput.step()) // prefix of first stable config that is not entry config + .append(decomposition.input()); // successor at $i$ in that config Word> suffix = Word.fromLetter(new TimeoutSymbol<>()); - var transSuffixOutput = this.timeOracle.answerQuery(resetTransPrefix, suffix); - var entryConfigSuffixOutput = this.timeOracle.answerQuery(hypothesis.getLocationPrefix(decomposition.state()), suffix); + Word> transSuffixOutput = this.timeOracle.answerQuery(resetTransPrefix, suffix); + Word> entryConfigSuffixOutput = + this.timeOracle.answerQuery(hypothesis.getLocationPrefix(decomposition.state()), suffix); if (transSuffixOutput.equals(entryConfigSuffixOutput)) { - logger.debug("Inferred missing reset in non-stable config."); - return new MissingResetResult<>(decomposition.state().getLocation(), (InputSymbol) decomposition.input()); + LOGGER.debug("Inferred missing reset in non-stable config."); + return new MissingResetResult<>(decomposition.state().getLocation(), + (InputSymbol) decomposition.input()); } } } // 3: add missing local reset - logger.debug("Inferred missing one-shot timer from incorrect target at non-delaying input."); + LOGGER.debug("Inferred missing one-shot timer from incorrect target at non-delaying input."); return this.selectOneShotTimer(decomposition, hypothesis, decomposition.state().getEntryDistance()); } - } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java index 24ea6bb66..8673765c6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTInconsPrefixTransformAcex.java @@ -1,5 +1,22 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex; +import java.util.function.Function; + import de.learnlib.acex.AbstractBaseCounterexample; import de.learnlib.oracle.TimedQueryOracle; import net.automatalib.symbol.time.TimedInput; @@ -8,17 +25,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.function.Function; - /** * An abstract counterexample used by the MMLT learner. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class MMLTInconsPrefixTransformAcex extends AbstractBaseCounterexample>> { - private final static Logger logger = LoggerFactory.getLogger(MMLTInconsPrefixTransformAcex.class); + private static final Logger LOGGER = LoggerFactory.getLogger(MMLTInconsPrefixTransformAcex.class); private final TimedQueryOracle timeOracle; private final Word> suffix; @@ -28,21 +45,22 @@ public class MMLTInconsPrefixTransformAcex extends AbstractBaseCounterexam /** * Constructor. * - * @param suffix suffix of the counterexample (= the word that we analyze) - * @param timeOracle membership oracle - * @param asTransform retrieves the prefix of the system state in the hypothesis addressed by a word + * @param suffix + * suffix of the counterexample, i.e., the word that we analyze + * @param timeOracle + * membership oracle + * @param asTransform + * retrieves the prefix of the system state in the hypothesis addressed by a word */ - public MMLTInconsPrefixTransformAcex(Word> suffix, TimedQueryOracle timeOracle, Function>, Word>> asTransform) { + public MMLTInconsPrefixTransformAcex(Word> suffix, + TimedQueryOracle timeOracle, + Function>, Word>> asTransform) { super(suffix.length()); this.timeOracle = timeOracle; this.suffix = suffix; this.asTransform = asTransform; } - public Function>, Word>> getAsTransform() { - return asTransform; - } - @Override public Word> computeEffect(int index) { // Split the word at our index: @@ -56,11 +74,10 @@ public Word> computeEffect(int index) { return this.timeOracle.answerQuery(accessSequence, suffix); } - @Override public boolean checkEffects(Word> eff1, Word> eff2) { // Same behavior at different indices? - logger.debug(String.format("Comparing (%s) AND (%s): %s", eff1, eff2, eff2.isSuffixOf(eff1))); + LOGGER.debug("Comparing ({}) AND ({}): {}", eff1, eff2, eff2.isSuffixOf(eff1)); return eff2.isSuffixOf(eff1); } -} \ No newline at end of file +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java index 0b93e875d..c462a3d57 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/MMLTOutputInconsistency.java @@ -1,6 +1,20 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex; - import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; @@ -8,15 +22,18 @@ /** * Represents an output inconsistency used by the MMLT learner. * - * @param prefix Prefix - * @param suffix Suffix input - * @param targetOut Suffix output in SUL - * @param hypOut Suffix output in hypothesis - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param prefix + * prefix + * @param suffix + * suffix input + * @param targetOut + * suffix output in SUL + * @param hypOut + * suffix output in hypothesis + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public record MMLTOutputInconsistency(Word> prefix, - Word> suffix, - Word> targetOut, - Word> hypOut) { -} +public record MMLTOutputInconsistency(Word> prefix, Word> suffix, + Word> targetOut, Word> hypOut) {} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java index b9e55cb4d..8ce858570 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/CexAnalysisResult.java @@ -1,10 +1,26 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex.results; /** * Outcome of a counterexample analysis. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public abstract class CexAnalysisResult { -} +public interface CexAnalysisResult {} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java index 1332dbc59..15afbbd42 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/FalseIgnoreResult.java @@ -1,15 +1,32 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex.results; - import net.automatalib.symbol.time.InputSymbol; /** * The specified symbol is considered to be falsely ignored by the symbol filter. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class FalseIgnoreResult extends CexAnalysisResult { +public class FalseIgnoreResult implements CexAnalysisResult { + private final Integer location; private final InputSymbol symbol; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java index 92f960a0b..265999d67 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingDiscriminatorResult.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex.results; import net.automatalib.symbol.time.TimedInput; @@ -6,10 +21,13 @@ /** * The target at the identified transition is incorrect due to a missing discriminator. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class MissingDiscriminatorResult extends CexAnalysisResult { +public class MissingDiscriminatorResult implements CexAnalysisResult { + private final Integer location; private final TimedInput input; private final Word> discriminator; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java index 5884b2ff2..0a73803ec 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingOneShotResult.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.cex.results; import net.automatalib.automaton.mmlt.TimerInfo; @@ -5,10 +20,13 @@ /** * The provided timer should become one-shot. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class MissingOneShotResult extends CexAnalysisResult { +public class MissingOneShotResult implements CexAnalysisResult { + private final Integer location; private final TimerInfo timeout; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java index 7dc56bc4c..5eb8da01c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/cex/results/MissingResetResult.java @@ -1,16 +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 de.learnlib.algorithm.lstar.mmlt.cex.results; - -import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; /** * There should be a local reset at the specified transition. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ -public class MissingResetResult extends CexAnalysisResult { +public class MissingResetResult implements CexAnalysisResult { + private final Integer location; private final InputSymbol input; diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java index 4eb5a37a0..5fd71ff05 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTPerfectSymbolFilter.java @@ -1,19 +1,34 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.filter; - -import de.learnlib.filter.symbol.AbstractPerfectSymbolFilter; import de.learnlib.filter.FilterResponse; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; +import de.learnlib.filter.symbol.AbstractPerfectSymbolFilter; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; /** - * A symbol filter for MMLTs that correctly accepts and ignores all transitions - * that silently self-loop. + * A symbol filter for MMLTs that correctly accepts and ignores all transitions that silently self-loop. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class MMLTPerfectSymbolFilter extends AbstractPerfectSymbolFilter, InputSymbol> { diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java index 020dcc0bf..cfd9ab4f6 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTRandomSymbolFilter.java @@ -1,34 +1,48 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.filter; +import java.util.Random; -import de.learnlib.filter.symbol.AbstractRandomSymbolFilter; import de.learnlib.filter.FilterResponse; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; +import de.learnlib.filter.symbol.AbstractRandomSymbolFilter; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; -import java.util.Random; - /** * A symbol filter that falsely answers a query with a specified probability. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type */ public class MMLTRandomSymbolFilter extends AbstractRandomSymbolFilter, InputSymbol> { private final MMLT automaton; - public MMLTRandomSymbolFilter(MMLT automaton, - double inaccurateProb, Random random) { + public MMLTRandomSymbolFilter(MMLT automaton, double inaccurateProb, Random random) { super(inaccurateProb, random); this.automaton = automaton; } - @Override protected FilterResponse isIgnorable(Word> prefix, InputSymbol symbol) { return MMLTSymbolFilterUtil.isIgnorable(this.automaton, prefix, symbol); } -} \ No newline at end of file +} diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java index c52368868..69be8d097 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTStatisticsSymbolFilter.java @@ -1,19 +1,34 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.filter; -import de.learnlib.filter.symbol.AbstractStatisticsSymbolFilter; -import de.learnlib.statistic.StatisticsCollector; -import de.learnlib.filter.SymbolFilter; import de.learnlib.filter.FilterResponse; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; +import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.symbol.AbstractStatisticsSymbolFilter; import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.word.Word; public class MMLTStatisticsSymbolFilter extends AbstractStatisticsSymbolFilter, InputSymbol> { private final MMLT automaton; - public MMLTStatisticsSymbolFilter(MMLT automaton, SymbolFilter, InputSymbol> delegate, StatisticsCollector stats) { + public MMLTStatisticsSymbolFilter(MMLT automaton, + SymbolFilter, InputSymbol> delegate) { super(delegate); this.automaton = automaton; } diff --git a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java index 54c55a74c..37c038d3c 100644 --- a/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java +++ b/algorithms/active/lstar/src/main/java/de/learnlib/algorithm/lstar/mmlt/filter/MMLTSymbolFilterUtil.java @@ -1,36 +1,50 @@ +/* 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 de.learnlib.algorithm.lstar.mmlt.filter; import java.util.Objects; import de.learnlib.filter.FilterResponse; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.MMLTSemantics; -import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.State; import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; -class MMLTSymbolFilterUtil { - - /** - * Returns IGNORE if the provided input triggers a transition that silently self-loops, - * and ACCEPT otherwise. - * - * @param automaton Automaton - * @param prefix State prefix - * @param symbol Input symbol - * @param Input type for non-delaying inputs - * @param Output symbol type - * @return IGNORE for silent self-loops, ACCEPT otherwise. - */ - static FilterResponse isIgnorable(MMLT automaton, Word> prefix, InputSymbol symbol) { +final class MMLTSymbolFilterUtil { + + private MMLTSymbolFilterUtil() { + // prevent instantiation + } + + static FilterResponse isIgnorable(MMLT automaton, + Word> prefix, + InputSymbol symbol) { return isIgnorable(automaton.getSemantics(), prefix, symbol); } - static FilterResponse isIgnorable(MMLTSemantics semantics, Word> prefix, InputSymbol symbol) { - var targetConfig = semantics.getState(prefix); - var trans = semantics.getTransition(targetConfig, symbol); - var target = semantics.getSuccessor(trans); - var output = semantics.getTransitionOutput(trans); + static FilterResponse isIgnorable(MMLTSemantics semantics, + Word> prefix, + InputSymbol symbol) { + State targetConfig = semantics.getState(prefix); + T trans = semantics.getTransition(targetConfig, symbol); + State target = semantics.getSuccessor(trans); + TimedOutput output = semantics.getTransitionOutput(trans); boolean ignorable = Objects.equals(output, semantics.getSilentOutput()) && Objects.equals(targetConfig, target); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index d6269aaf7..fd54224d8 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -1,9 +1,23 @@ +/* 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 de.learnlib.algorithm.lstar; import java.util.Collections; import java.util.List; -import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT.Example; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.driver.simulator.MMLTSimulatorSUL; @@ -12,6 +26,7 @@ import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.query.DefaultQuery; import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import de.learnlib.time.MMLTModelParams; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; @@ -166,7 +181,6 @@ public void testMissingOneShotModelA() { Word.fromWords(TimedInput.inputs("p1"), TimedInput.steps(40), TimedInput.timeouts(1)) - // alternatively: new InputSymbol<>("abort") ); // Missing one-shot via bad target: diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 2ba5917d5..e4510b9ce 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.algorithm.lstar.it; import java.io.IOException; @@ -11,18 +26,18 @@ import java.util.Random; import java.util.stream.Stream; -import de.learnlib.time.MMLTModelParams; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; -import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.filter.symbol.AcceptAllSymbolFilter; -import de.learnlib.filter.symbol.CachedSymbolFilter; -import de.learnlib.filter.symbol.IgnoreAllSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTPerfectSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; import de.learnlib.filter.SymbolFilter; +import de.learnlib.filter.symbol.AcceptAllSymbolFilter; +import de.learnlib.filter.symbol.CachedSymbolFilter; +import de.learnlib.filter.symbol.IgnoreAllSymbolFilter; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.testsupport.example.LearningExample.MMLTLearningExample; import de.learnlib.testsupport.it.learner.AbstractMMLTLearnerIT; import de.learnlib.testsupport.it.learner.LearnerVariantList.MMLTLearnerVariantList; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; diff --git a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java index cf832bcf4..dc7928116 100644 --- a/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java +++ b/api/src/main/java/de/learnlib/oracle/EquivalenceOracle.java @@ -99,7 +99,7 @@ interface MooreEquivalenceOracle extends EquivalenceOracle - * input type (of non-delaying inputs) + * input symbol type (of non-delaying inputs) * @param * output symbol type */ diff --git a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java index d0783d949..ab93843d8 100644 --- a/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/TimedQueryOracle.java @@ -27,7 +27,7 @@ * An oracle for querying {@link TimedInput timed inputs} and timers by observing timeouts. * * @param - * input type (of non-delaying inputs) + * input symbol type (of non-delaying inputs) * @param * output symbol type */ diff --git a/api/src/main/java/de/learnlib/statistic/NoopCollector.java b/api/src/main/java/de/learnlib/statistic/NoopCollector.java index 3b416a1fa..3a81a8c8b 100644 --- a/api/src/main/java/de/learnlib/statistic/NoopCollector.java +++ b/api/src/main/java/de/learnlib/statistic/NoopCollector.java @@ -20,6 +20,8 @@ import java.util.Collections; import java.util.Optional; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * A no-op implementation of a {@link StatisticsCollector} that does nothing. */ @@ -34,7 +36,7 @@ public Collection getKeys() { public void clear() {} @Override - public void addText(String id, String description, String text) {} + public void addText(String id, @Nullable String description, String text) {} @Override public Optional getText(String id) { @@ -42,7 +44,7 @@ public Optional getText(String id) { } @Override - public void setFlag(String id, String description, boolean value) {} + public void setFlag(String id, @Nullable String description, boolean value) {} @Override public Optional getFlag(String id) { @@ -50,7 +52,7 @@ public Optional getFlag(String id) { } @Override - public void startOrResumeClock(String id, String description) {} + public void startOrResumeClock(String id, @Nullable String description) {} @Override public void pauseClock(String id) {} @@ -61,10 +63,10 @@ public Optional getClock(String id) { } @Override - public void increaseCounter(String id, String description, long increment) {} + public void increaseCounter(String id, @Nullable String description, long increment) {} @Override - public void setCounter(String id, String description, long count) {} + public void setCounter(String id, @Nullable String description, long count) {} @Override public Optional getCount(String id) { diff --git a/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java b/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java index 930e5a101..1c2bf9f6d 100644 --- a/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java +++ b/api/src/main/java/de/learnlib/statistic/StatisticsCollector.java @@ -148,7 +148,7 @@ default void increaseCounter(String id, @Nullable String description) { * @param increment * the amount to increase the counter by (must be greater than zero) */ - void increaseCounter(String id, String description, long increment); + void increaseCounter(String id, @Nullable String description, long increment); /** * Sets the counter with the given id to the provided value. If no counter with this id exists, it is created. diff --git a/api/src/main/java/de/learnlib/sul/TimedSUL.java b/api/src/main/java/de/learnlib/sul/TimedSUL.java index 28aca4467..672169788 100644 --- a/api/src/main/java/de/learnlib/sul/TimedSUL.java +++ b/api/src/main/java/de/learnlib/sul/TimedSUL.java @@ -30,7 +30,7 @@ * Interface for a SUL with MMLT semantics. * * @param - * input type (of non-delaying inputs) + * input symbol type (of non-delaying inputs) * @param * output symbol type */ diff --git a/commons/util/src/main/java/de/learnlib/util/Experiment.java b/commons/util/src/main/java/de/learnlib/util/Experiment.java index e61db2d98..dc240b09d 100644 --- a/commons/util/src/main/java/de/learnlib/util/Experiment.java +++ b/commons/util/src/main/java/de/learnlib/util/Experiment.java @@ -22,6 +22,7 @@ import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; import net.automatalib.alphabet.Alphabet; +import net.automatalib.automaton.concept.FiniteRepresentation; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; @@ -36,11 +37,12 @@ * @param * the automaton type */ -public class Experiment { +public class Experiment { public static final String LEARNING_PROFILE_KEY = "exp-expl-dur"; public static final String COUNTEREXAMPLE_PROFILE_KEY = "exp-ce-dur"; public static final String LEARNING_ROUNDS_KEY = "exp-rnd"; + public static final String FINAL_SIZE_KEY = "hyp-size"; private static final Logger LOGGER = LoggerFactory.getLogger(Experiment.class); private final ExperimentImpl impl; @@ -122,6 +124,7 @@ public A run() { statisticsCollector.pauseClock(COUNTEREXAMPLE_PROFILE_KEY); if (ce == null) { + statisticsCollector.setCounter(FINAL_SIZE_KEY, "Final hypothesis size", hyp.size()); return hyp; } diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java index 9cb2ea98f..9761c79e7 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java @@ -38,7 +38,7 @@ public class MMLTSimulatorSUL implements TimedSUL { private final MMLTSemantics semantics; - private State currentConfiguration; + private @Nullable State currentConfiguration; public MMLTSimulatorSUL(MMLTSemantics semantics) { this.semantics = semantics; diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index d2c4873f0..b3eecc321 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -1,3 +1,18 @@ +/* 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 de.learnlib.example.mmlt; import java.util.ArrayList; @@ -17,12 +32,12 @@ import net.automatalib.word.Word; /** - * This example shows a basic learning setup for Mealy machine with local timers (MMLT), - * an automaton model for real-time systems. - * - *

          Mealy Machines with Local Timers (MMLTs) are an extension of Mealy machines for real-time behavior. - * They extend Mealy machines with multiple timers. A timer in an MMLT counts down as time progresses. - * When reaching zero, it stops and triggers an action.

          + * This example shows a basic learning setup for Mealy machine with local timers (MMLT), an automaton model for + * real-time systems. + *

          + * Mealy Machines with Local Timers (MMLTs) are an extension of Mealy machines for real-time behavior. + * They extend Mealy machines with multiple timers. A timer in an MMLT counts down as time progresses. When + * reaching zero, it stops and triggers an action.

          * *
            *
          • A timer in an MMLT is bound to a specific location. It can only time out in its associated location and only be reset @@ -60,23 +75,30 @@ *

            More information about MMLTs can be found here: * Learning Mealy Machines with Local Timers.

            */ -public class Example1 { +@SuppressWarnings("PMD.UseExplicitTypes") // allow vars in examples +public final class Example1 { + + private Example1() { + // prevent instantiation + } public static void main(String[] args) { // We use the included sensor collector model as reference automaton: var model = MMLTExamples.sensorCollector(); + var mmlt = model.getReferenceAutomaton(); + var alphabet = mmlt.getInputAlphabet(); // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); stats.addText("LocalTimerMealyModel", null, model.toString()); - stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); + stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -90,7 +112,7 @@ public static void main(String[] args) { // In the basic set-up, we use a simulator oracle to answer equivalence queries. // This oracle has perfect knowledge of the reference automaton. - var eqOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); + var eqOracle = new SimulatorEQOracle<>(mmlt); // Set up our L* learner: @@ -98,14 +120,13 @@ public static void main(String[] args) { // We include all untimed inputs and the symbolic timeout symbol, which causes the learner to wait // until the next timeout (but no longer than model.getParams().maxTimeoutWaitingTime()). List>> suffixes = new ArrayList<>(); - model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle); + var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle); // Start learning: - ExampleUtil.runExperiment(learner, eqOracle, stats, 100); - + ExampleRunner.runExperiment(learner, eqOracle, mmlt.getSemantics().getInputAlphabet(), stats); } } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example2.java b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java index b55f76405..cf90f1c9b 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example2.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java @@ -1,62 +1,74 @@ +/* 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 de.learnlib.example.mmlt; +import java.util.ArrayList; +import java.util.List; + import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; -import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; -import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; import de.learnlib.driver.simulator.MMLTSimulatorSUL; -import de.learnlib.filter.SymbolFilter; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; import de.learnlib.filter.statistic.oracle.CounterEQOracle; import de.learnlib.filter.statistic.sul.CounterTimedSUL; -import de.learnlib.filter.symbol.CachedSymbolFilter; -import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.oracle.equivalence.MMLTEQOracleChain; import de.learnlib.oracle.equivalence.mmlt.RandomWpMethodEQOracle; import de.learnlib.oracle.equivalence.mmlt.ResetSearchEQOracle; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.query.DefaultQuery; import de.learnlib.statistic.Statistics; -import de.learnlib.statistic.StatisticsCollector; import de.learnlib.testsupport.example.mmlt.MMLTExamples; -import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.visualization.Visualization; import net.automatalib.word.Word; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import static de.learnlib.example.mmlt.ExampleUtil.runExperiment; - /** * This example shows a basic set-up of the MMLT-learner for a black-box setting. *

            - * For this, we use a chain of different equivalence oracles - * that can be applied if the reference automaton is not known. + * For this, we use a chain of different equivalence oracles that can be applied if the reference automaton is not + * known. */ -public class Example2 { +@SuppressWarnings("PMD.UseExplicitTypes") // allow magic numbers and vars in examples +public final class Example2 { + + private static final int BOUND = 100; + private static final int MIN_SIZE = 16; + private static final double PERCENTAGE = 1.0; + private static final int SEED = 100; + + private Example2() { + // prevent instantiation + } public static void main(String[] args) { var model = MMLTExamples.sensorCollector(); + var mmlt = model.getReferenceAutomaton(); + var alphabet = mmlt.getInputAlphabet(); // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); stats.addText("LocalTimerMealyModel", null, model.toString()); - stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); + stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -69,7 +81,7 @@ public static void main(String[] args) { var timeOracle = new TimedSULOracle<>(toReducerSul, model.getParams()); // We use a chain of different equivalence oracles to find counterexamples more efficiently: - MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); + var chainOracle = new MMLTEQOracleChain(); // A cache oracle tests if the current hypothesis and the reference automaton give the same outputs // for all words that have already been queried. As the words have already been queried, this // executes no additional queries on the SUL: @@ -77,24 +89,27 @@ public static void main(String[] args) { // A ResetSearchOracle tests for missing local resets, which often require many and/or long test words // when using random-based testing. We configure the tester to consider all transitions that might cause a reset: - chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); + chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, SEED, PERCENTAGE, PERCENTAGE), + "reset")); // Finally, we add an MMLT-specific RandomWp oracle: - chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, SEED, MIN_SIZE, 0, BOUND), + "wp")); // Set up our L* learner: List>> suffixes = new ArrayList<>(); - model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle); + var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle); // Start learning: - var finalModel = runExperiment(learner, chainOracle, stats, 100); + var finalModel = + ExampleRunner.runExperiment(learner, chainOracle, mmlt.getSemantics().getInputAlphabet(), stats); // In this set-up, we actually know the reference automaton. // This allows us to check that we learned an accurate model: - var simOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); + var simOracle = new SimulatorEQOracle<>(mmlt); if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { throw new AssertionError("Incorrect model learned."); } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example3.java b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java index 74c500a7f..c64faaab0 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example3.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java @@ -1,5 +1,24 @@ +/* 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 de.learnlib.example.mmlt; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; @@ -22,19 +41,12 @@ import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -import static de.learnlib.example.mmlt.ExampleUtil.runExperiment; - /** * This example illustrates how to learn MMLTs with symbol filtering. *

            - * A symbol filter is a component that provides information about transitions that might silently self-loop. - * The learner exploits this information to avoid redundant queries on the SUL. - * The symbol filter might incorrectly classify a transition as silent self-loop. - * The MMLT-learner detects and corrects such errors. + * A symbol filter is a component that provides information about transitions that might silently self-loop. The learner + * exploits this information to avoid redundant queries on the SUL. The symbol filter might incorrectly classify a + * transition as silent self-loop. The MMLT-learner detects and corrects such errors. *

            * LearnLib includes four types of symbol filter: *

              @@ -50,22 +62,35 @@ * When you apply MMLT-learning in practice, you usually want to implement your own symbol filter that exploits specific * domain knowledge. */ -public class Example3 { +@SuppressWarnings({"checkstyle:magicnumber", "PMD.UseExplicitTypes"}) // allow magic numbers and vars in examples +public final class Example3 { + + private static final int BOUND = 100; + private static final double INACC_PROB = 0.1; + private static final int MIN_SIZE = 16; + private static final double PERCENTAGE = 1.0; + private static final int SEED = 100; + + private Example3() { + // prevent instantiation + } public static void main(String[] args) { var model = MMLTExamples.sensorCollector(); + var mmlt = model.getReferenceAutomaton(); + var alphabet = mmlt.getInputAlphabet(); // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); stats.addText("LocalTimerMealyModel", null, model.toString()); - stats.setCounter("original_locs", "Locations in original", model.getReferenceAutomaton().getStates().size()); - stats.setCounter("original_inputs", "Untimed alphabet size in original", model.getReferenceAutomaton().getInputAlphabet().size()); + stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); + stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(model.getReferenceAutomaton().getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -80,35 +105,38 @@ public static void main(String[] args) { // We use a chain of different equivalence oracles (see Example2): MMLTEQOracleChain chainOracle = new MMLTEQOracleChain<>(); chainOracle.addOracle(new CounterEQOracle<>(cacheSUL.createCacheConsistencyTest(), "cache")); - chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, 100, 1.0, 1.0), "reset")); - chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, 100, 16, 0, 100), "wp")); + chainOracle.addOracle(new CounterEQOracle<>(new ResetSearchEQOracle<>(timeOracle, SEED, PERCENTAGE, PERCENTAGE), + "reset")); + chainOracle.addOracle(new CounterEQOracle<>(new RandomWpMethodEQOracle<>(timeOracle, SEED, MIN_SIZE, 0, BOUND), + "wp")); // Set up our L* learner: List>> suffixes = new ArrayList<>(); - model.getReferenceAutomaton().getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); + alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); // A symbol filter allows us to reduce queries by exploiting prior knowledge. // For this example, we use a AbstractRandomSymbolFilter. This filter correctly predicts // whether a transition silently self-loops with an accuracy of 90%: SymbolFilter, InputSymbol> filter = - new MMLTRandomSymbolFilter<>(model.getReferenceAutomaton(), 0.1, new Random(100)); + new MMLTRandomSymbolFilter<>(mmlt, INACC_PROB, new Random(SEED)); // We wrap our filter with a StatisticsFilter to collect useful statistics about the filter: - filter = new MMLTStatisticsSymbolFilter<>(model.getReferenceAutomaton(), filter, stats); + filter = new MMLTStatisticsSymbolFilter<>(mmlt, filter); // The learner may need to update incorrect responses of the filter. // To facilitate this, we wrap our filter with a CachedFilter: var cachedFilter = new CachedSymbolFilter<>(filter); - var learner = new ExtensibleLStarMMLT<>(model.getReferenceAutomaton().getInputAlphabet(), model.getParams(), suffixes, timeOracle, cachedFilter); + var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle, cachedFilter); // Start learning: - var finalModel = runExperiment(learner, chainOracle, stats, 100); + var finalModel = + ExampleRunner.runExperiment(learner, chainOracle, mmlt.getSemantics().getInputAlphabet(), stats); // In this set-up, we actually know the reference automaton. // This allows us to check that we learned an accurate model: - var simOracle = new SimulatorEQOracle<>(model.getReferenceAutomaton()); + var simOracle = new SimulatorEQOracle<>(mmlt); if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { throw new AssertionError("Incorrect model learned."); } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example4.java b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java index 292f0c92f..3f5f854cd 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example4.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java @@ -1,5 +1,25 @@ +/* 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 de.learnlib.example.mmlt; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; @@ -8,31 +28,28 @@ import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.statistic.Statistics; -import de.learnlib.testsupport.example.LearningExample; -import de.learnlib.testsupport.example.mmlt.MMLTExamples; import de.learnlib.time.MMLTModelParams; import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.exception.FormatException; +import net.automatalib.serialization.dot.DOTMMLTParser; import net.automatalib.serialization.dot.DOTParsers; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; - /** * This example demonstrates how to load an MMLT from a dot-file and learn it using the L* algorithm. *

              - * A description of the dot-file syntax for MMLTs can be found in AutomataLib - * (see {@link net.automatalib.serialization.dot.DOTMMLTParser}). + * A description of the dot-file syntax for MMLTs can be found in AutomataLib (see {@link DOTMMLTParser}). */ -public class Example4 { +@SuppressWarnings("PMD.UseExplicitTypes") // allow vars in examples +public final class Example4 { + private Example4() { + // prevent instantiation + } public static void main(String[] args) { // First, we load the file "mmlt_example.dot" from the "resources" folder: @@ -52,24 +69,23 @@ public static void main(String[] args) { var parsedModel = parser.readModel(is); targetModel = parsedModel.model; - // During learning, we use a symbolic "timeout" symbol to indicate that the + // During learning, we use a symbolic "timeout" symbol to indicate that the // teacher should wait for the next timeout. To avoid an infinite runtime, // we set a maximum waiting time for these symbols. - // This time should be at least the maximum time to the next timeout in any + // This time should be at least the maximum time to the next timeout in any // state of the system. We configure this as follows: long maxTimeoutDelay = MMLTs.getMaximumTimeoutDelay(targetModel); // After adding a new location, the learner infers timers for it by watching the SUL for timeouts. - // To learn an accurate model, the maximum time to watch for these timeouts must be at + // To learn an accurate model, the maximum time to watch for these timeouts must be at // least the value of "maxTimeoutDelay". // If the maximum initial value of timers in the SUL is known or can be reasonably estimated, // setting the watch time to twice that value usually yields good results: long maxTimerQueryWaitingFinal = MMLTs.getMaximumInitialTimerValue(targetModel) * 2; - params = new MMLTModelParams<>(silentOutput, outputCombiner, maxTimeoutDelay, maxTimerQueryWaitingFinal); } catch (IOException | FormatException e) { - throw new RuntimeException("Unable to load model from file."); + throw new IllegalStateException("Unable to load model from file.", e); } // Proceed as in Example1: @@ -109,7 +125,7 @@ public static void main(String[] args) { var learner = new ExtensibleLStarMMLT<>(targetModel.getInputAlphabet(), params, suffixes, timeOracle); // Start learning: - ExampleUtil.runExperiment(learner, eqOracle, stats, 100); + ExampleRunner.runExperiment(learner, eqOracle, targetModel.getSemantics().getInputAlphabet(), stats); } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/ExampleRunner.java b/examples/src/main/java/de/learnlib/example/mmlt/ExampleRunner.java new file mode 100644 index 000000000..05381d2bb --- /dev/null +++ b/examples/src/main/java/de/learnlib/example/mmlt/ExampleRunner.java @@ -0,0 +1,57 @@ +/* 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 de.learnlib.example.mmlt; + +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.statistic.StatisticsCollector; +import de.learnlib.util.Experiment; +import net.automatalib.alphabet.Alphabet; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.visualization.Visualization; + +@SuppressWarnings({"PMD.SystemPrintln", "PMD.UseExplicitTypes"}) // allow for sysouts and vars in examples +final class ExampleRunner { + + private ExampleRunner() { + // prevent instantiation + } + + static MMLT runExperiment(ExtensibleLStarMMLT learner, + EquivalenceOracle.MMLTEquivalenceOracle tester, + Alphabet> alphabet, + StatisticsCollector statisticsCollector) { + // Start learning: + final var experiment = new Experiment<>(learner, tester, alphabet); + experiment.run(); + + final var finalHypothesis = experiment.getFinalHypothesis(); + + // Print final result + statistics: + System.out.println(statisticsCollector.printStats()); + + new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); + + System.out.println("Final hypothesis:"); + Visualization.visualize(finalHypothesis.graphView(), + new MMLTVisualizationHelper<>(finalHypothesis, true, true)); + + return finalHypothesis; + } +} diff --git a/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java b/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java deleted file mode 100644 index c0218c519..000000000 --- a/examples/src/main/java/de/learnlib/example/mmlt/ExampleUtil.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.learnlib.example.mmlt; - -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; -import de.learnlib.datastructure.observationtable.writer.ObservationTableASCIIWriter; -import de.learnlib.oracle.EquivalenceOracle; -import de.learnlib.query.DefaultQuery; -import de.learnlib.statistic.StatisticsCollector; -import net.automatalib.automaton.mmlt.MMLT; -import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.visualization.Visualization; -import net.automatalib.word.Word; - -public class ExampleUtil { - static MMLT runExperiment(ExtensibleLStarMMLT learner, - EquivalenceOracle.MMLTEquivalenceOracle tester, - StatisticsCollector statisticsCollector, int maxRounds) { - statisticsCollector.startOrResumeClock("learningRt", "Processing time"); - learner.startLearning(); - - // Our experiment follows the usual learn-loop: - // We retrieve a hypothesis and ask for a counterexample. If a counterexample is found, we - // provide it to the learner to refine the hypothesis. This process repeats until no - // counterexample is found. - var hyp = learner.getHypothesisModel(); - DefaultQuery, Word>> cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - statisticsCollector.increaseCounter("roundCount", "CEX queries"); - - int roundCount = 1; - while (cex != null && roundCount < maxRounds) { - learner.refineHypothesis(cex); - hyp = learner.getHypothesisModel(); - cex = tester.findCounterExample(hyp, hyp.getSemantics().getInputAlphabet()); - statisticsCollector.increaseCounter("roundCount", null); - roundCount += 1; - } - statisticsCollector.pauseClock("learningRt"); - - final var finalHypothesis = learner.getHypothesisModel(); - - // Add some more stats: - statisticsCollector.setCounter("result_locs", "Locations in result", finalHypothesis.getStates().size()); - - // Print final result + statistics: - System.out.println(statisticsCollector.printStats()); - - new ObservationTableASCIIWriter<>().write(learner.getObservationTable(), System.out); - - System.out.println("Final hypothesis:"); - Visualization.visualize(finalHypothesis.graphView(), new MMLTVisualizationHelper<>(finalHypothesis, true, true)); - - return finalHypothesis; - } -} diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index efa9f4e3b..64677d746 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -23,6 +23,7 @@ import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; +import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -38,12 +39,12 @@ class CacheTreeNode { private @Nullable CacheTreeNode parent; - private TimedInput parentInput; + private @Nullable TimedInput parentInput; private long timeout; private @Nullable CacheTreeTransition timeTransition; private final Map, CacheTreeTransition> untimedChildren; - CacheTreeNode(CacheTreeNode parent, TimedInput parentInput) { + CacheTreeNode(@Nullable CacheTreeNode parent, @Nullable TimedInput parentInput) { this.parent = parent; this.parentInput = parentInput; @@ -66,6 +67,7 @@ public CacheTreeNode addTimeChild(long timeout, TimedOutput output) { // ------------------------------------------------------- + @EnsuresNonNullIf(result = true, expression = "this.timeTransition") public boolean hasTimeChild() { return this.timeTransition != null; } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java index 2b6594775..df865baf2 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java @@ -20,6 +20,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; @@ -114,7 +115,7 @@ private DefaultQuery, Word>> convertTimeSequences(D wbInput.append(ds); wbOutput.append(outputSym); } else if (inputSym instanceof TimeStepSequence ws) { - if (!outputSym.symbol().equals(this.modelParams.silentOutput()) || + if (!Objects.equals(outputSym.symbol(), this.modelParams.silentOutput()) || ws.timeSteps() == this.modelParams.maxTimeoutWaitingTime()) { // Found a timeout OR no timeout after max_delay: wbInput.append(new TimeoutSymbol<>()); @@ -131,7 +132,7 @@ private DefaultQuery, Word>> convertTimeSequences(D long combinedWaitTime = ws.timeSteps(); TimedOutput combinedOutput = outputSym; - while (combinedOutput.symbol().equals(this.modelParams.silentOutput()) && + while (Objects.equals(combinedOutput.symbol(), this.modelParams.silentOutput()) && combinedWaitTime < this.modelParams.maxTimeoutWaitingTime() && symIdx < queryInput.length() && queryInput.getSymbol(symIdx) instanceof TimeStepSequence nextWs) { combinedWaitTime += nextWs.timeSteps(); @@ -140,10 +141,10 @@ private DefaultQuery, Word>> convertTimeSequences(D } if (combinedWaitTime >= this.modelParams.maxTimeoutWaitingTime() || - !combinedOutput.symbol().equals(this.modelParams.silentOutput())) { + !Objects.equals(combinedOutput.symbol(), this.modelParams.silentOutput())) { wbInput.append(new TimeoutSymbol<>()); - if (combinedOutput.symbol().equals(this.modelParams.silentOutput())) { + if (Objects.equals(combinedOutput.symbol(), this.modelParams.silentOutput())) { // Reached max delay -> waiting for any time will now produce no more timeouts: wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); } else { diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java index bb9be5a13..340f5c13c 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java @@ -53,7 +53,7 @@ public class TimedSULTreeCache implements TimedSUL, MMLTLearningCach private final TimedSUL delegate; private final CacheTreeNode cacheRoot; - private CacheTreeNode currentState; + private @Nullable CacheTreeNode currentState; private final MMLTModelParams modelParams; private final TimedOutput silentOutput; @@ -212,8 +212,8 @@ private List> getLeaves() { } /** - * Lists all words that are currently in the cache. If a cached word is a prefix of another cached word, only - * the longer of them is returned. + * Lists all words that are currently in the cache. If a cached word is a prefix of another cached word, only the + * longer of them is returned. * * @return List of all stored words. */ @@ -262,7 +262,7 @@ public List>> listAllWords() { } mealy.addAlphabetSymbol(new TimeStepSequence<>(current.getTimeout())); mealy.addTransition(stateMap.get(current), - new TimeStepSequence<>(current.getTimeout()), + TimedInput.step(current.getTimeout()), stateMap.get(child), current.getTimeoutOutput()); } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java index 0bdd44c4f..1f2cb0304 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/CounterStatistic.java @@ -15,6 +15,8 @@ */ package de.learnlib.filter.statistic.container; +import org.checkerframework.checker.nullness.qual.Nullable; + /** * A counter that can be increased and set to a particular positive number. */ @@ -22,11 +24,11 @@ class CounterStatistic extends AbstractStatistic { private long count; - CounterStatistic(String id, String description) { + CounterStatistic(String id, @Nullable String description) { this(id, description, 0); } - CounterStatistic(String id, String description, long count) { + CounterStatistic(String id, @Nullable String description, long count) { super(id, description); this.count = count; } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java index fde249684..3d9d3d20b 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/MapStatisticsCollector.java @@ -26,6 +26,7 @@ import java.util.Optional; import de.learnlib.statistic.StatisticsCollector; +import org.checkerframework.checker.nullness.qual.Nullable; /** * A {@link StatisticsCollector} that stores all statistics in a {@link Map}. @@ -50,7 +51,7 @@ public synchronized void clear() { } @Override - public synchronized void addText(String id, String description, String text) { + public synchronized void addText(String id, @Nullable String description, String text) { statistics.put(id, new TextStatistic(id, description, text)); } @@ -64,7 +65,7 @@ public synchronized Optional getText(String id) { } @Override - public synchronized void setFlag(String id, String description, boolean value) { + public synchronized void setFlag(String id, @Nullable String description, boolean value) { statistics.put(id, new FlagStatistic(id, description, value)); } @@ -78,7 +79,7 @@ public synchronized Optional getFlag(String id) { } @Override - public synchronized void startOrResumeClock(String id, String description) { + public synchronized void startOrResumeClock(String id, @Nullable String description) { AbstractStatistic value = statistics.get(id); if (value instanceof StopClockStatistic clockStatistic) { clockStatistic.resume(); @@ -108,7 +109,7 @@ public synchronized Optional getClock(String id) { } @Override - public synchronized void increaseCounter(String id, String description, long increment) { + public synchronized void increaseCounter(String id, @Nullable String description, long increment) { AbstractStatistic value = statistics.get(id); if (value instanceof CounterStatistic counterStatistic) { counterStatistic.increase(increment); @@ -119,7 +120,7 @@ public synchronized void increaseCounter(String id, String description, long inc } @Override - public synchronized void setCounter(String id, String description, long count) { + public synchronized void setCounter(String id, @Nullable String description, long count) { statistics.put(id, new CounterStatistic(id, description, count)); } diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java index fdd5b29cc..877bac88b 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/container/StopClockStatistic.java @@ -25,7 +25,7 @@ */ class StopClockStatistic extends AbstractStatistic { - private Instant started; + private @Nullable Instant started; private Duration elapsed; StopClockStatistic(String id, @Nullable String description) { diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index 9643c2c64..b7fb056f9 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -44,11 +44,11 @@ public CounterTimedSUL(TimedSUL delegate) { this(delegate, null); } - public CounterTimedSUL(TimedSUL delegate, String name) { + public CounterTimedSUL(TimedSUL delegate, @Nullable String name) { this(delegate, name, Statistics.getCollector()); } - protected CounterTimedSUL(TimedSUL delegate, String name, StatisticsCollector statistics) { + protected CounterTimedSUL(TimedSUL delegate, @Nullable String name, StatisticsCollector statistics) { this.delegate = delegate; this.name = name; this.stats = statistics; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java index 29bfe291c..b6e08743d 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Random; @@ -30,7 +29,6 @@ import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.State; import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; -import net.automatalib.common.util.string.AbstractPrintable; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.util.automaton.Automata; diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index 800ceddb9..f7cc4e3b2 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -17,7 +17,6 @@ import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -31,7 +30,6 @@ import net.automatalib.automaton.mmlt.State; import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.common.util.random.RandomUtil; -import net.automatalib.common.util.string.AbstractPrintable; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index e434dbdff..e8695cdb8 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -21,6 +21,7 @@ import java.util.Comparator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.stream.Collectors; import de.learnlib.oracle.TimedQueryOracle; @@ -196,11 +197,12 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, TimedOutput nextOutput, List> knownTimers) { - var nextOutputSymbols = this.modelParams.outputCombiner().separateSymbols(nextOutput.symbol()); + List nextOutputSymbols = this.modelParams.outputCombiner().separateSymbols(nextOutput.symbol()); if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: - TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); + TimerInfo newTimer = + new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); return new TimerCheckResult<>(newTimer, false); } else if (nextActualTime == nextExpectedTime) { // Timeout occurred at expected time -> check if matching expected output: @@ -211,14 +213,14 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, .collect(Collectors.groupingBy(t -> t, Collectors.counting())); // count occurrences - Map actualOutputs = nextOutputSymbols.stream() - .collect(Collectors.groupingBy(e -> e, Collectors.counting())); + Map actualOutputs = + nextOutputSymbols.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting())); // Any outputs that were expected but are not present? boolean missingOutputs = expectedOutputs.keySet() .stream() .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < - expectedOutputs.get(o)); // less than expected + expectedOutputs.get(o)); // less than expected if (missingOutputs) { // Same time but missing output -> missed location change: return new TimerCheckResult<>(null, true); @@ -227,9 +229,10 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, // At least all expected outputs are present. // Check for additional outputs: List newOutputs = new ArrayList<>(); - for (O output : actualOutputs.keySet()) { + for (Entry e : actualOutputs.entrySet()) { + O output = e.getKey(); long expectedCount = expectedOutputs.getOrDefault(output, 0L); - long actualCount = actualOutputs.get(output); + long actualCount = e.getValue(); long additional = actualCount - expectedCount; for (int i = 0; i < additional; i++) { @@ -239,11 +242,8 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, if (!newOutputs.isEmpty()) { // Same time and more outputs -> add new timer that uses the new outputs: - TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), - nextActualTime, - newOutputs, - null, - true); + TimerInfo newTimer = + new TimerInfo<>(getUniqueTimerName(), nextActualTime, newOutputs, null, true); return new TimerCheckResult<>(newTimer, false); } } else { From 1500902a366922ffe4cb4cca4b91e9699334409e Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Fri, 28 Nov 2025 14:56:22 +0100 Subject: [PATCH 52/55] cleanups + tests --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 95 ++++------ .../lstar/mmlt/MMLTObservationTable.java | 24 +-- ...xtensibleLStarMMLTCounterexampleTests.java | 10 +- .../lstar/it/ExtensibleLStarMMLTIT.java | 9 +- api/pom.xml | 20 --- .../main/java/de/learnlib/query/Query.java | 28 ++- .../de/learnlib/statistic/StatisticTest.java | 59 +++++++ .../driver/simulator/MMLTSimulatorSUL.java | 78 +++++---- .../driver/simulator/MealySimulatorSUL.java | 9 +- .../java/de/learnlib/example/Example3.java | 2 +- .../de/learnlib/example/mmlt/Example1.java | 14 +- .../de/learnlib/example/mmlt/Example2.java | 14 +- .../de/learnlib/example/mmlt/Example3.java | 15 +- .../de/learnlib/example/mmlt/Example4.java | 13 +- .../de/learnlib/example/ExamplesTest.java | 18 ++ .../filter/cache/LearningCacheOracle.java | 17 ++ .../filter/cache/mmlt/CacheTreeNode.java | 56 +++--- .../cache/mmlt/MMLTCacheConsistencyTest.java | 31 ++-- .../filter/cache/mmlt/TimedSULTreeCache.java | 145 ++++++--------- .../filter/cache/mmlt/TimeoutReducerSUL.java | 4 +- .../learnlib/filter/cache/sul/SULCaches.java | 49 +++++- .../filter/cache/AbstractCacheTest.java | 9 +- .../learnlib/filter/cache/CacheTestUtils.java | 39 +++++ .../cache/TimedSULLearningCacheOracle.java | 72 ++++++++ .../cache/dfa/AbstractDFACacheTest.java | 6 + .../filter/cache/dfa/DFAHashCacheTest.java | 6 + .../cache/mealy/AbstractMealyCacheTest.java | 5 + .../cache/mealy/AdaptiveQueryCacheTest.java | 5 + .../filter/cache/mmlt/MMLTCacheTest.java | 165 ++++++++---------- .../cache/moore/AbstractMooreCacheTest.java | 5 + .../cache/sul/AbstractSULCacheTest.java | 5 + .../sul/StateLocalInputSULTreeCacheTest.java | 5 + .../statistic/oracle/CounterOracle.java | 4 +- .../filter/statistic/sul/CounterTimedSUL.java | 4 +- .../equivalence/mmlt/ResetSearchEQOracle.java | 83 ++++++--- .../oracle/membership/TimedSULOracle.java | 7 +- .../membership/SimulatorOracleTest.java | 4 +- .../oracle/membership/TimedSULOracleTest.java | 49 ++++++ .../it/learner/AbstractMMLTLearnerIT.java | 3 +- 39 files changed, 712 insertions(+), 474 deletions(-) create mode 100644 api/src/test/java/de/learnlib/statistic/StatisticTest.java create mode 100644 filters/cache/src/test/java/de/learnlib/filter/cache/TimedSULLearningCacheOracle.java create mode 100644 oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/TimedSULOracleTest.java 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 a3d198fff..46e0d37bc 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 @@ -61,7 +61,7 @@ import org.slf4j.LoggerFactory; /** - * The MMLT learner. + * An L*-based leaner for inferring {@link MMLT}s. * * @param * input symbol type (of non-delaying inputs) @@ -87,88 +87,61 @@ public class ExtensibleLStarMMLT private final MMLTCounterexampleHandler cexAnalyzer; /** - * Instantiates a new Rivest-Schapire learner for MMLTs. + * Instantiates a new learner. *

              - * Uses the close-shortest strategy for closing the observation table, binary-backwards search for decomposing - * counterexamples, and no symbol filter. + * This is a convenience constructor for + * {@link #ExtensibleLStarMMLT(Alphabet, MMLTModelParams, TimedQueryOracle, List, ClosingStrategy, + * MutableSymbolFilter, AcexAnalyzer)} which uses + *

                + *
              • {@link Collections#emptyList()} for {@code initialSuffixes},
              • + *
              • {@link ClosingStrategies#CLOSE_SHORTEST} for {@code closingStrategy},
              • + *
              • {@link AcceptAllSymbolFilter} for {@code symbolFilter}, and
              • + *
              • {@link AcexAnalyzers#BINARY_SEARCH_BWD} for {@code analyzer}.
              • + *
              * * @param alphabet * alphabet (of non-delaying inputs) * @param modelParams * model parameters - * @param initialSuffixes - * initial set of suffixes (may be empty) * @param timeOracle * the query oracle for MMLTs */ public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, - List>> initialSuffixes, TimedQueryOracle timeOracle) { this(alphabet, modelParams, - initialSuffixes, - ClosingStrategies.CLOSE_SHORTEST, timeOracle, + Collections.emptyList(), + ClosingStrategies.CLOSE_SHORTEST, new AcceptAllSymbolFilter<>(), AcexAnalyzers.BINARY_SEARCH_BWD); } /** - * Instantiates a new Rivest-Schapire learner for MMLTs. - *

              - * Uses the close-shortest strategy for closing the observation table and binary-backwards search for decomposing - * counterexamples. + * Instantiates a new learner. * * @param alphabet * alphabet (of non-delaying inputs) * @param modelParams * model parameters - * @param initialSuffixes - * initial set of suffixes (may be empty) * @param timeOracle * the query oracle for MMLTs - * @param symbolFilter - * the symbol filter - */ - public ExtensibleLStarMMLT(Alphabet alphabet, - MMLTModelParams modelParams, - List>> initialSuffixes, - TimedQueryOracle timeOracle, - MutableSymbolFilter, InputSymbol> symbolFilter) { - this(alphabet, - modelParams, - initialSuffixes, - ClosingStrategies.CLOSE_SHORTEST, - timeOracle, - symbolFilter, - AcexAnalyzers.BINARY_SEARCH_BWD); - } - - /** - * Instantiates a new Rivest-Schapire learner for MMLTs. - * - * @param alphabet - * alphabet (of non-delaying inputs) - * @param modelParams - * model parameters * @param initialSuffixes * initial set of suffixes (may be empty) * @param closingStrategy * closing strategy for the observation table. - * @param timeOracle - * the query oracle for MMLTs * @param symbolFilter * the symbol filter * @param analyzer - * The strategy for decomposing counterexamples. + * the strategy for decomposing counterexamples. */ @GenerateBuilder(defaults = BuilderDefaults.class) public ExtensibleLStarMMLT(Alphabet alphabet, MMLTModelParams modelParams, + TimedQueryOracle timeOracle, List>> initialSuffixes, ClosingStrategy, ? super Word>> closingStrategy, - TimedQueryOracle timeOracle, MutableSymbolFilter, InputSymbol> symbolFilter, AcexAnalyzer analyzer) { this.closingStrategy = closingStrategy; @@ -195,18 +168,21 @@ public ExtensibleLStarMMLT(Alphabet alphabet, } /** - * Heuristically chooses a new one-shot timer from the provided timers: takes the timer with the highest initial - * value that a) does not exceed maxInitialValue and b) has not timer with a lower initial value that times out at - * the same time. + * Heuristically chooses a new one-shot timer from the provided timers. Takes the timer with the highest initial + * value that + *

                + *
              • does not exceed {@code maxInitialValue} and
              • + *
              • has not timer with a lower initial value that times out at the same time.
              • + *
              * * @param sortedTimers - * Timers, sorted ascendingly by their initial value + * timers, sorted ascendingly by their initial value * @param maxInitialValue - * Max. initial value to consider + * max. initial value to consider * @param - * Output type + * output type * - * @return New one-shot timer + * @return the index (in {@code sortedTimers}) of the new one-shot candidate */ public static int selectOneShotTimer(List> sortedTimers, long maxInitialValue) { @@ -237,28 +213,23 @@ public static int selectOneShotTimer(List> sortedT throw new IllegalStateException("Max. initial value is too low; must include at least one timer."); } - /** - * Constructs an MMLT hypothesis. This updates all transition outputs, if required. - * - * @return MMLT hypothesis - */ @Override public MMLT getHypothesisModel() { - return getInternalLocalTimerMealyHypothesis(); + return getInternalHypothesisModel(); } /** - * Like the construction above, but returns an LocalTimerMealyHypothesis object instead. This objects provides + * Like {@link #getHypothesisModel()}, but returns an {@link MMLTHypothesis} object instead. This objects provides * additional functions that are just intended for the learner but not the teacher. * - * @return MMLT hypothesis + * @return the internal hypothesis */ - private MMLTHypothesis getInternalLocalTimerMealyHypothesis() { + private MMLTHypothesis getInternalHypothesisModel() { this.updateOutputs(); return constructHypothesis(this.hypData); } - protected List>> selectClosingRows(List>>> unclosed) { + private List>> selectClosingRows(List>>> unclosed) { return closingStrategy.selectClosingRows(unclosed, hypData.getTable(), timeOracle); } @@ -354,7 +325,7 @@ public boolean refineHypothesis(DefaultQuery, Word> private boolean refineHypothesisSingle(DefaultQuery, Word>> ceQuery) { // 1. Update hypothesis (may have changed since last refinement): - MMLTHypothesis hypothesis = this.getInternalLocalTimerMealyHypothesis(); + MMLTHypothesis hypothesis = this.getInternalHypothesisModel(); // 2. Transform to output inconsistency: MMLTOutputInconsistency outputIncons = this.toOutputInconsistency(ceQuery, hypothesis); @@ -476,7 +447,7 @@ public ObservationTable, Word>> getObservationTable * @param unclosed * the unclosed rows (equivalence classes) to start with. */ - protected void completeConsistentTable(List>>> unclosed) { + private void completeConsistentTable(List>>> unclosed) { List>>> unclosedIter = unclosed; while (!unclosedIter.isEmpty()) { List>> closingRows = this.selectClosingRows(unclosedIter); 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 403f6a66f..493e14181 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 @@ -61,7 +61,7 @@ * @param * output symbol type */ -public class MMLTObservationTable implements ObservationTable, Word>> { +class MMLTObservationTable implements ObservationTable, Word>> { private static final Logger LOGGER = LoggerFactory.getLogger(MMLTObservationTable.class); private static final int NO_CONTENT = -1; @@ -86,10 +86,10 @@ public class MMLTObservationTable implements ObservationTable silentOutput; // used for symbol filtering - public MMLTObservationTable(Alphabet> alphabet, - long minTimerQueryWaitTime, - MutableSymbolFilter, InputSymbol> symbolFilter, - O silentOutput) { + MMLTObservationTable(Alphabet> alphabet, + long minTimerQueryWaitTime, + MutableSymbolFilter, InputSymbol> symbolFilter, + O silentOutput) { this.alphabet = alphabet; this.symbolFilter = symbolFilter; @@ -486,9 +486,7 @@ public List>> getSuffixes() { public List>> rowContents(Row> row) { if (this.rowContentMap.isEmpty()) { // OT may be empty if only single location with timers: - if (!this.suffixes.isEmpty()) { - throw new AssertionError(); - } + assert this.suffixes.isEmpty(); return Collections.emptyList(); } @@ -500,11 +498,6 @@ public Word> transformAccessSequence(Word> word) { throw new IllegalStateException("Not implemented."); } - @Override - public boolean isAccessSequence(Word> word) { - throw new IllegalStateException("Not implemented."); - } - public @Nullable TimerInfo getTimerInfo(Word> prefix, long initial) { LocationTimerInfo info = this.timerInfoMap.get(prefix); if (info != null) { @@ -542,9 +535,8 @@ public List>>> addOutgoingTransition(Row> s Word> transitionPrefix = spRow.getLabel().append(symbol); // Add long-prefix row: - if (this.getRow(transitionPrefix) != null) { - throw new AssertionError("Location already has an outgoing transition for the provided symbol"); - } + assert this.getRow(transitionPrefix) == null : + "Location already has an outgoing transition for the provided symbol"; RowImpl> succRow = this.createLpRow(transitionPrefix); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index fd54224d8..da79c9e8c 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -15,13 +15,11 @@ */ package de.learnlib.algorithm.lstar; -import java.util.Collections; import java.util.List; import de.learnlib.algorithm.lstar.it.ExtensibleLStarMMLTIT.Example; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; import de.learnlib.driver.simulator.MMLTSimulatorSUL; -import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.oracle.equivalence.mmlt.SimulatorEQOracle; import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.query.DefaultQuery; @@ -45,14 +43,10 @@ private static void learnModel(MMLT example, MMLTModelParams params, List>> counterexamples) { - var sul = new MMLTSimulatorSUL<>(example.getSemantics()); + var sul = new MMLTSimulatorSUL<>(example); var timeOracle = new TimedSULOracle<>(sul, params); - var learner = new ExtensibleLStarMMLT<>(example.getInputAlphabet(), - params, - Collections.emptyList(), - timeOracle, - new AcceptAllSymbolFilter<>()); + var learner = new ExtensibleLStarMMLT<>(example.getInputAlphabet(), params, timeOracle); learner.startLearning(); diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index e4510b9ce..4bfec0ae5 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -26,7 +26,7 @@ import java.util.Random; import java.util.stream.Stream; -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTPerfectSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; import de.learnlib.filter.SymbolFilter; @@ -77,7 +77,12 @@ protected void addLearnerVariants(Alphabet alphabet, var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLT<>(alphabet, example.getParams(), suffixes, mqOracle, cachedFilter); + var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) + .withModelParams(example.getParams()) + .withTimeOracle(mqOracle) + .withInitialSuffixes(suffixes) + .withSymbolFilter(cachedFilter) + .create(); variants.addLearnerVariant("system=" + example + ",filter=" + filterMode, learner, counters + mmlt.size()); } } diff --git a/api/pom.xml b/api/pom.xml index 680ec2a55..368515408 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -52,31 +52,11 @@ limitations under the License. - - - ch.qos.logback - logback-core - test - - - ch.qos.logback - logback-classic - test - - org.testng testng test - - org.mockito - mockito-core - test - diff --git a/api/src/main/java/de/learnlib/query/Query.java b/api/src/main/java/de/learnlib/query/Query.java index 0eab8d69c..50561ccd7 100644 --- a/api/src/main/java/de/learnlib/query/Query.java +++ b/api/src/main/java/de/learnlib/query/Query.java @@ -25,8 +25,8 @@ import org.checkerframework.checker.nullness.qual.Nullable; /** - * A query is the basic form of interaction between a {@link LearningAlgorithm learner} and a {@link MembershipOracle - * (membership) oracle}, or teacher. + * A query is the basic form of interaction between a {@link LearningAlgorithm learner} and a + * {@link MembershipOracle (membership) oracle}, or teacher. *

              * In LearnLib, queries are performed in a callback-like fashion: an oracle does not return the responses to the * queries, but rather invokes the {@link #answer(Object)} method on the query objects it was provided with. This allows @@ -35,9 +35,9 @@ * is no need for maintaining a common (synchronized) result data structure such as a map. However, this means that a * learner cannot rely on the {@link #answer(Object)} method of a query being called from the same thread which invoked * {@link MembershipOracle#processQueries(java.util.Collection)}. If this causes concurrency issues, a safe choice is to - * use queries of class {@link DefaultQuery}, which simply store the response and make it accessible via {@link - * DefaultQuery#getOutput()} for processing after the {@link MembershipOracle#processQueries(java.util.Collection)} call - * returns, guaranteeing thread-safety. + * use queries of class {@link DefaultQuery}, which simply store the response and make it accessible via + * {@link DefaultQuery#getOutput()} for processing after the + * {@link MembershipOracle#processQueries(java.util.Collection)} call returns, guaranteeing thread-safety. *

              * Conceptually, a query is divided into a {@link #getPrefix() prefix} and a {@link #getSuffix()} suffix. The prefix * part of a query identifies a state in the (unknown) target system, whereas the suffix is the "experiment" which is @@ -73,8 +73,8 @@ public abstract class Query { * throwing an exception. * * @param output - * the output, i.e., the directly observable response to the query's suffix (cf. {@link Query main - * documentation}) + * the output, i.e., the directly observable response to the query's suffix (cf. + * {@link Query main documentation}) */ public abstract void answer(D output); @@ -104,6 +104,16 @@ public Word getInput() { */ public abstract Word getSuffix(); + /** + * Returns the number of symbols of this query by adding the {@link Word#length() lengths} of the + * {@link #getPrefix() prefix} and {@link #getSuffix() suffix}. + * + * @return the length of this query + */ + public int length() { + return getPrefix().length() + getSuffix().length(); + } + @Override public final int hashCode() { if (hashCode != 0) { @@ -125,8 +135,8 @@ public final boolean equals(@Nullable Object o) { /** * Returns the string representation of this query. * - * @return A string of the form {@code "Query[|]"} for queries not containing an answer or {@code - * "Query[| / ]"} if an answer may be specified. + * @return A string of the form {@code "Query[|]"} for queries not containing an answer or + * {@code "Query[| / ]"} if an answer may be specified. */ @Override public String toString() { diff --git a/api/src/test/java/de/learnlib/statistic/StatisticTest.java b/api/src/test/java/de/learnlib/statistic/StatisticTest.java new file mode 100644 index 000000000..3c887eacd --- /dev/null +++ b/api/src/test/java/de/learnlib/statistic/StatisticTest.java @@ -0,0 +1,59 @@ +/* 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 de.learnlib.statistic; + +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StatisticTest { + + @Test + public void testSingleton() { + StatisticsCollector col1 = Statistics.getCollector(); + StatisticsCollector col2 = Statistics.getCollector(); + + Assert.assertSame(col1, col2); + } + + @Test + public void testNoop() { + StatisticsCollector collector = Statistics.getCollector(); + + String id1 = "id1"; + collector.addText(id1, null, "text"); + Assert.assertTrue(collector.getText(id1).isEmpty()); + + String id2 = "id2"; + collector.setFlag(id2, null, true); + Assert.assertTrue(collector.getFlag(id2).isEmpty()); + + String id3 = "id3"; + collector.startOrResumeClock(id3, null); + collector.pauseClock(id3); + Assert.assertTrue(collector.getClock(id3).isEmpty()); + + String id4 = "id4"; + collector.increaseCounter(id4, null); + Assert.assertTrue(collector.getCount(id4).isEmpty()); + + Assert.assertTrue(collector.getKeys().isEmpty()); + + // assert no throws + collector.clear(); + + Assert.assertNotNull(collector.printStats()); + } +} diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java index 9761c79e7..4b755d9e1 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MMLTSimulatorSUL.java @@ -20,68 +20,70 @@ import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.mmlt.State; import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; -import net.automatalib.symbol.time.TimeoutSymbol; import org.checkerframework.checker.nullness.qual.Nullable; /** * Simulates the semantics of an {@link MMLT}. * - * @param - * location type. * @param * input symbol type (of non-delaying inputs). * @param * output symbol type. */ -public class MMLTSimulatorSUL implements TimedSUL { +public class MMLTSimulatorSUL extends MealySimulatorSUL, TimedOutput> + implements TimedSUL { - private final MMLTSemantics semantics; + private final MMLTSimulatorSULImpl impl; - private @Nullable State currentConfiguration; + public MMLTSimulatorSUL(MMLT semantics) { + this(new MMLTSimulatorSULImpl<>(semantics.getSemantics())); + } - public MMLTSimulatorSUL(MMLTSemantics semantics) { - this.semantics = semantics; - this.currentConfiguration = null; + private MMLTSimulatorSUL(MMLTSimulatorSULImpl impl) { + super(impl); + this.impl = impl; } @Override - public TimedOutput step(InputSymbol input) { - if (this.currentConfiguration == null) { - throw new IllegalStateException("Not initialized!"); - } - - T trans = this.semantics.getTransition(this.currentConfiguration, input); - this.currentConfiguration = this.semantics.getSuccessor(trans); - return this.semantics.getTransitionOutput(trans); + public @Nullable TimedOutput timeoutStep(long maxTime) { + return this.impl.timeoutStep(maxTime); } @Override - public @Nullable TimedOutput timeoutStep(long maxTime) { - if (this.currentConfiguration == null) { - throw new IllegalStateException("Not initialized!"); - } + public TimedSUL fork() { + return new MMLTSimulatorSUL<>(this.impl.fork()); + } + + private static final class MMLTSimulatorSULImpl + extends MealySimulatorSULImpl, InputSymbol, T, TimedOutput> implements TimedSUL { - T trans = this.semantics.getTransition(this.currentConfiguration, new TimeoutSymbol<>(), maxTime); - this.currentConfiguration = this.semantics.getSuccessor(trans); - TimedOutput output = this.semantics.getTransitionOutput(trans); + private final MMLTSemantics semantics; - if (output.equals(semantics.getSilentOutput())) { - // No timeout observed: - return null; - } else { - return output; + MMLTSimulatorSULImpl(MMLTSemantics semantics) { + super(semantics, semantics.getSilentOutput()); + this.semantics = semantics; } - } - @Override - public void pre() { - this.currentConfiguration = semantics.getInitialState(); - } + @Override + public @Nullable TimedOutput timeoutStep(long maxTime) { + final State curr = getCurr(); + final T trans = this.semantics.getTransition(curr, TimedInput.timeout(), maxTime); + setCurr(this.semantics.getSuccessor(trans)); + TimedOutput output = this.semantics.getTransitionOutput(trans); - @Override - public void post() { - this.currentConfiguration = null; - } + if (output.equals(semantics.getSilentOutput())) { + // No timeout observed: + return null; + } else { + return output; + } + } + @Override + public MMLTSimulatorSULImpl fork() { + return new MMLTSimulatorSULImpl<>(semantics); + } + } } diff --git a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MealySimulatorSUL.java b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MealySimulatorSUL.java index dfbef28cd..9fa41628c 100644 --- a/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MealySimulatorSUL.java +++ b/drivers/simulator/src/main/java/de/learnlib/driver/simulator/MealySimulatorSUL.java @@ -17,6 +17,7 @@ import de.learnlib.sul.SUL; import net.automatalib.automaton.transducer.MealyMachine; +import net.automatalib.ts.output.MealyTransitionSystem; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -105,11 +106,11 @@ public SUL fork() { */ static class MealySimulatorSULImpl implements SUL { - private final MealyMachine mealy; + private final MealyTransitionSystem mealy; private final O noTransOut; private @Nullable S curr; - MealySimulatorSULImpl(MealyMachine mealy, O noTransOut) { + MealySimulatorSULImpl(MealyTransitionSystem mealy, O noTransOut) { this.mealy = mealy; this.noTransOut = noTransOut; } @@ -155,6 +156,10 @@ S getCurr() { } return curr; } + + void setCurr(S curr) { + this.curr = curr; + } } } diff --git a/examples/src/main/java/de/learnlib/example/Example3.java b/examples/src/main/java/de/learnlib/example/Example3.java index c73da80a8..17627ea98 100644 --- a/examples/src/main/java/de/learnlib/example/Example3.java +++ b/examples/src/main/java/de/learnlib/example/Example3.java @@ -202,7 +202,7 @@ class FullMembershipQueryOracle implements MealyMembershipOracle>> queries) { for (Query> query : queries) { resets++; - symbols += query.getInput().size(); + symbols += query.length(); BoundedStringQueue s = new BoundedStringQueue(); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java index b3eecc321..99cab63ba 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example1.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example1.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.List; -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; @@ -42,7 +42,7 @@ *

                *
              • A timer in an MMLT is bound to a specific location. It can only time out in its associated location and only be reset * at transitions that target this location.
              • - *
              • The timeout-action of a timer $x$ is modeled with a transition that uses the internal input to[x]. These inputs + *
              • The timeout-action of a timer $x$ is modeled with a transition that uses the internal input {@code to[x]}. These inputs * cannot be provided to the model directly. Instead, they are internally triggered after sufficient time has passed. All * other input symbols are called non-delaying inputs.
              • *
              • The output of a timer at timeout must not be silent.
              • @@ -91,14 +91,14 @@ public static void main(String[] args) { // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); - stats.addText("LocalTimerMealyModel", null, model.toString()); + stats.addText("model", null, model.toString()); stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -123,7 +123,11 @@ public static void main(String[] args) { alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle); + var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) + .withModelParams(model.getParams()) + .withTimeOracle(timeOracle) + .withInitialSuffixes(suffixes) + .create(); // Start learning: ExampleRunner.runExperiment(learner, eqOracle, mmlt.getSemantics().getInputAlphabet(), stats); diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example2.java b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java index cf90f1c9b..d2dff2ab2 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example2.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example2.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.List; -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; @@ -61,14 +61,14 @@ public static void main(String[] args) { // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); - stats.addText("LocalTimerMealyModel", null, model.toString()); + stats.addText("model", null, model.toString()); stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -101,7 +101,11 @@ public static void main(String[] args) { alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle); + var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) + .withModelParams(model.getParams()) + .withTimeOracle(timeOracle) + .withInitialSuffixes(suffixes) + .create(); // Start learning: var finalModel = @@ -111,7 +115,7 @@ public static void main(String[] args) { // This allows us to check that we learned an accurate model: var simOracle = new SimulatorEQOracle<>(mmlt); if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { - throw new AssertionError("Incorrect model learned."); + throw new IllegalStateException("Incorrect model learned."); } // Troubleshooting diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example3.java b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java index c64faaab0..6783989fb 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example3.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example3.java @@ -19,7 +19,7 @@ import java.util.List; import java.util.Random; -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTStatisticsSymbolFilter; import de.learnlib.driver.simulator.MMLTSimulatorSUL; @@ -83,14 +83,14 @@ public static void main(String[] args) { // We first create a statistics container. // This container will store various statistical data during learning: var stats = Statistics.getCollector(); - stats.addText("LocalTimerMealyModel", null, model.toString()); + stats.addText("model", null, model.toString()); stats.setCounter("original_locs", "Locations in original", mmlt.getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", alphabet.size()); // ====================== // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(mmlt.getSemantics()); + var sul = new MMLTSimulatorSUL<>(mmlt); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -128,7 +128,12 @@ public static void main(String[] args) { // To facilitate this, we wrap our filter with a CachedFilter: var cachedFilter = new CachedSymbolFilter<>(filter); - var learner = new ExtensibleLStarMMLT<>(alphabet, model.getParams(), suffixes, timeOracle, cachedFilter); + var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) + .withModelParams(model.getParams()) + .withTimeOracle(timeOracle) + .withInitialSuffixes(suffixes) + .withSymbolFilter(cachedFilter) + .create(); // Start learning: var finalModel = @@ -138,7 +143,7 @@ public static void main(String[] args) { // This allows us to check that we learned an accurate model: var simOracle = new SimulatorEQOracle<>(mmlt); if (simOracle.findCounterExample(finalModel, finalModel.getSemantics().getInputAlphabet()) != null) { - throw new AssertionError("Incorrect model learned."); + throw new IllegalStateException("Incorrect model learned."); } } diff --git a/examples/src/main/java/de/learnlib/example/mmlt/Example4.java b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java index 3f5f854cd..8a971948f 100644 --- a/examples/src/main/java/de/learnlib/example/mmlt/Example4.java +++ b/examples/src/main/java/de/learnlib/example/mmlt/Example4.java @@ -20,7 +20,7 @@ import java.util.ArrayList; import java.util.List; -import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLT; +import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.driver.simulator.MMLTSimulatorSUL; import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.filter.cache.mmlt.TimeoutReducerSUL; @@ -91,13 +91,13 @@ public static void main(String[] args) { // Proceed as in Example1: var stats = Statistics.getCollector(); - stats.addText("LocalTimerMealyModel", null, "mmlt_example.dot"); + stats.addText("model", null, "mmlt_example.dot"); stats.setCounter("original_locs", "Locations in original", targetModel.getStates().size()); stats.setCounter("original_inputs", "Untimed alphabet size in original", targetModel.getInputAlphabet().size()); // Set up the pipeline: // We use a simulator SUL to simulate our automaton: - var sul = new MMLTSimulatorSUL<>(targetModel.getSemantics()); + var sul = new MMLTSimulatorSUL<>(targetModel); // We count all operations that are performed on the SUL with a stats-SUL: var statsAfterCache = new CounterTimedSUL<>(sul); @@ -122,11 +122,14 @@ public static void main(String[] args) { targetModel.getInputAlphabet().forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - var learner = new ExtensibleLStarMMLT<>(targetModel.getInputAlphabet(), params, suffixes, timeOracle); + var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(targetModel.getInputAlphabet()) + .withModelParams(params) + .withTimeOracle(timeOracle) + .withInitialSuffixes(suffixes) + .create(); // Start learning: ExampleRunner.runExperiment(learner, eqOracle, targetModel.getSemantics().getInputAlphabet(), stats); - } } diff --git a/examples/src/test/java/de/learnlib/example/ExamplesTest.java b/examples/src/test/java/de/learnlib/example/ExamplesTest.java index 5531d95f8..e02ad9968 100644 --- a/examples/src/test/java/de/learnlib/example/ExamplesTest.java +++ b/examples/src/test/java/de/learnlib/example/ExamplesTest.java @@ -112,6 +112,24 @@ public void testMMLTExample1() { de.learnlib.example.mmlt.Example1.main(new String[0]); } + @Test + public void testMMLTExample2() { + requireJVMCompatibility(); + de.learnlib.example.mmlt.Example2.main(new String[0]); + } + + @Test + public void testMMLTExample3() { + requireJVMCompatibility(); + de.learnlib.example.mmlt.Example3.main(new String[0]); + } + + @Test + public void testMMLTExample4() { + requireJVMCompatibility(); + de.learnlib.example.mmlt.Example4.main(new String[0]); + } + @Test public void testParallelismExample1() { de.learnlib.example.parallelism.ParallelismExample1.main(new String[0]); diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCacheOracle.java b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCacheOracle.java index fbc158cba..1d0e3bfbe 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCacheOracle.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/LearningCacheOracle.java @@ -16,9 +16,13 @@ package de.learnlib.filter.cache; import de.learnlib.oracle.MembershipOracle; +import de.learnlib.oracle.TimedQueryOracle; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; /** @@ -65,4 +69,17 @@ interface MealyLearningCacheOracle extends LearningCacheOracle extends LearningCacheOracle, I, Word>, MooreLearningCache, MooreMembershipOracle {} + + /** + * Specialization of the {@link LearningCacheOracle} interface for MMLT learning. + * + * @param + * input symbol type + * @param + * output symbol type + */ + interface MMMLTLearningCacheOracle + extends LearningCacheOracle, TimedInput, Word>>, + MMLTLearningCache, + TimedQueryOracle {} } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java index 64677d746..770522aff 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/CacheTreeNode.java @@ -54,11 +54,8 @@ class CacheTreeNode { this.untimedChildren = new HashMap<>(); } - public CacheTreeNode addTimeChild(long timeout, TimedOutput output) { - if (this.hasTimeChild()) { - throw new IllegalStateException("State already has time child."); - } - + CacheTreeNode addTimeChild(long timeout, TimedOutput output) { + assert !this.hasTimeChild() : "State already has time child."; CacheTreeNode newChild = new CacheTreeNode<>(this, new TimeStepSequence<>(timeout)); this.timeout = timeout; this.timeTransition = new CacheTreeTransition<>(output, newChild); @@ -68,28 +65,22 @@ public CacheTreeNode addTimeChild(long timeout, TimedOutput output) { // ------------------------------------------------------- @EnsuresNonNullIf(result = true, expression = "this.timeTransition") - public boolean hasTimeChild() { + boolean hasTimeChild() { return this.timeTransition != null; } - public long getTimeout() { - if (!this.hasTimeChild()) { - throw new IllegalStateException(); - } + long getTimeout() { + assert this.hasTimeChild(); return timeout; } - public TimedOutput getTimeoutOutput() { - if (!this.hasTimeChild()) { - throw new IllegalStateException(); - } + TimedOutput getTimeoutOutput() { + assert this.hasTimeChild(); return this.timeTransition.output(); } - public CacheTreeNode getTimeoutChild() { - if (!this.hasTimeChild()) { - throw new IllegalStateException(); - } + CacheTreeNode getTimeoutChild() { + assert this.hasTimeChild(); return this.timeTransition.target(); } @@ -104,10 +95,8 @@ public CacheTreeNode getTimeoutChild() { * * @return New child node */ - public CacheTreeNode splitTimeout(long newTimeout, TimedOutput output) { - if (this.timeTransition == null || newTimeout >= this.getTimeout()) { - throw new IllegalArgumentException("Must split at lower timeout."); - } + CacheTreeNode splitTimeout(long newTimeout, TimedOutput output) { + assert this.hasTimeChild() && newTimeout < this.getTimeout() : "Must split at lower timeout."; CacheTreeNode newChild = new CacheTreeNode<>(this, new TimeStepSequence<>(newTimeout)); newChild.timeout = this.timeout - newTimeout; @@ -121,45 +110,42 @@ public CacheTreeNode splitTimeout(long newTimeout, TimedOutput output) } // ------------------------------------------------------- - public CacheTreeNode getParent() { + CacheTreeNode getParent() { return parent; } - public TimedInput getParentInput() { + TimedInput getParentInput() { return parentInput; } - public void setParent(CacheTreeNode parent, TimedInput parentInput) { + void setParent(CacheTreeNode parent, TimedInput parentInput) { this.parent = parent; this.parentInput = parentInput; } // ------------------------------------------------------- - public boolean hasChild(InputSymbol input) { + boolean hasChild(InputSymbol input) { return this.untimedChildren.containsKey(input); } - public TimedOutput getOutput(InputSymbol input) { + TimedOutput getOutput(InputSymbol input) { return this.untimedChildren.get(input).output(); } - public CacheTreeNode getChild(InputSymbol input) { + CacheTreeNode getChild(InputSymbol input) { return this.untimedChildren.get(input).target(); } - public CacheTreeNode addUntimedChild(InputSymbol input, TimedOutput output) { - if (untimedChildren.containsKey(input)) { - throw new IllegalArgumentException("State already has an child for this input."); - } - + CacheTreeNode addUntimedChild(InputSymbol input, TimedOutput output) { + assert !untimedChildren.containsKey(input) : "State already has an child for this input."; CacheTreeNode child = new CacheTreeNode<>(this, input); this.untimedChildren.put(input, new CacheTreeTransition<>(output, child)); return child; } - public Map, CacheTreeTransition> getUntimedChildren() { + Map, CacheTreeTransition> getUntimedChildren() { return Collections.unmodifiableMap(this.untimedChildren); } - public record CacheTreeTransition(TimedOutput output, CacheTreeNode target) {} + record CacheTreeTransition(TimedOutput output, CacheTreeNode target) {} } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java index df865baf2..e5ab6cc68 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/MMLTCacheConsistencyTest.java @@ -15,11 +15,9 @@ */ package de.learnlib.filter.cache.mmlt; -import java.util.ArrayList; import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; -import java.util.List; +import java.util.Iterator; import java.util.Objects; import java.util.Set; @@ -167,12 +165,11 @@ private DefaultQuery, Word>> reduceToAllowedInputs( DefaultQuery, Word>> query) { // Find the longest prefix with allowed inputs: int prefixLength = 0; - while (prefixLength < query.getInput().length() && - allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { + while (prefixLength < query.length() && allowedInputs.contains(query.getInput().getSymbol(prefixLength))) { prefixLength++; } - if (prefixLength == query.getInput().length()) { + if (prefixLength == query.length()) { return query; // maximum length -> no need to reduce } else { return new DefaultQuery<>(query.getInput().subWord(0, prefixLength), @@ -186,11 +183,12 @@ private DefaultQuery, Word>> reduceToAllowedInputs( Set> allowedInputs = new HashSet<>(inputs); boolean allInputsConsidered = allowedInputs.containsAll(hypothesis.getSemantics().getInputAlphabet()); - // Query all cached words: - List>> cachedWords = this.sulCache.listAllWords(); + // Iterator over all cached words: + Iterator>> iter = this.sulCache.allWordsIterator(); + + while (iter.hasNext()) { + Word> word = iter.next(); - List, Word>>> counterexamples = new ArrayList<>(); - for (Word> word : cachedWords) { // First, query word as-is (may include wait-symbols in input): DefaultQuery, Word>> rawCacheQuery = this.queryCache(word); @@ -203,21 +201,14 @@ private DefaultQuery, Word>> reduceToAllowedInputs( allInputsConsidered ? convertedQuery : this.reduceToAllowedInputs(allowedInputs, convertedQuery); // Finally, query hypothesis using the converted query: - Word> hypOutput = - hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), reducedQuery.getInput()); + Word> hypOutput = hypothesis.getSemantics().computeOutput(reducedQuery.getInput()); if (!hypOutput.equals(reducedQuery.getOutput())) { // Hyp gives different output than cache (= SUL): - counterexamples.add(reducedQuery); + return reducedQuery; } } - if (counterexamples.isEmpty()) { - return null; - } - - // Take the shortest word: - return counterexamples.stream().min(Comparator.comparingInt(w -> w.getInput().length())).get(); + return null; } - } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java index 340f5c13c..de88d9981 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimedSULTreeCache.java @@ -16,39 +16,35 @@ package de.learnlib.filter.cache.mmlt; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Deque; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.Iterator; import de.learnlib.filter.cache.LearningCache.MMLTLearningCache; +import de.learnlib.filter.cache.mmlt.CacheTreeNode.CacheTreeTransition; import de.learnlib.oracle.EquivalenceOracle.MMLTEquivalenceOracle; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; import de.learnlib.sul.TimedSUL; import de.learnlib.time.MMLTModelParams; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.automaton.transducer.impl.CompactMealy; -import net.automatalib.graph.Graph; -import net.automatalib.graph.concept.GraphViewable; +import net.automatalib.common.util.collection.AbstractSimplifiedIterator; +import net.automatalib.common.util.collection.IteratorUtil; import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; /** - * Caches queries sent to a LocalTimerMealySUL. + * Caches queries sent to a {@link TimedSUL}. * * @param * input symbol type (of non-delaying inputs) * @param * output symbol type */ -public class TimedSULTreeCache implements TimedSUL, MMLTLearningCache, GraphViewable { +public class TimedSULTreeCache implements TimedSUL, MMLTLearningCache { private final TimedSUL delegate; @@ -177,113 +173,70 @@ public void post() { } } - // ------------------------------------------------------- + @Override + public MMLTEquivalenceOracle createCacheConsistencyTest() { + return new MMLTCacheConsistencyTest<>(this, this.modelParams); + } /** - * Returns the leaves of the cache tree. + * Returns an iterator that traverses all words (leaves of this tree) in a BFS-style fashion. * - * @return List of leaf nodes. + * @return iterator over all words of this tree */ - private List> getLeaves() { - List> leaves = new ArrayList<>(); - - Deque> unvisited = new ArrayDeque<>(); - unvisited.add(this.cacheRoot); - - while (!unvisited.isEmpty()) { - CacheTreeNode currentNode = unvisited.remove(); + public Iterator>> allWordsIterator() { + return IteratorUtil.map(new LeavesIterator<>(this.cacheRoot), this::extractWord); + } - int successors = 0; - if (currentNode.hasTimeChild()) { - unvisited.add(currentNode.getTimeoutChild()); - successors++; - } + private Word> extractWord(CacheTreeNode leaf) { + final WordBuilder> wb = new WordBuilder<>(); - for (InputSymbol sym : currentNode.getUntimedChildren().keySet()) { - unvisited.add(currentNode.getChild(sym)); - successors++; - } - if (successors == 0) { // leaf - leaves.add(currentNode); - } + // Move towards the root: + CacheTreeNode current = leaf; + while (current.getParent() != null) { + wb.append(current.getParentInput()); + current = current.getParent(); } - return leaves; + // Start at root -> flip buffer: + wb.reverse(); + return wb.toWord(); } - /** - * Lists all words that are currently in the cache. If a cached word is a prefix of another cached word, only the - * longer of them is returned. - * - * @return List of all stored words. - */ - public List>> listAllWords() { - List> leaves = this.getLeaves(); - - List>> finalWords = new ArrayList<>(leaves.size()); + private static final class LeavesIterator extends AbstractSimplifiedIterator> { - for (CacheTreeNode leaf : leaves) { - WordBuilder> wbInput = new WordBuilder<>(); + private final Deque> queue; - // Move towards the root: - CacheTreeNode current = leaf; - while (current.getParent() != null) { - wbInput.append(current.getParentInput()); - current = current.getParent(); - } - - // Start at root -> flip buffer: - wbInput.reverse(); - finalWords.add(wbInput.toWord()); + private LeavesIterator(CacheTreeNode root) { + this.queue = new ArrayDeque<>(); + this.queue.add(root); } - return finalWords; - } - - @Override - public Graph graphView() { - // Convert tree to a mealy automaton: - CompactMealy, TimedOutput> mealy = new CompactMealy<>(new GrowingMapAlphabet<>()); + @Override + protected boolean calculateNext() { - Map, Integer> stateMap = new HashMap<>(); - stateMap.put(this.cacheRoot, mealy.addInitialState()); + while (!queue.isEmpty()) { + @SuppressWarnings("nullness") //false positive https://github.com/typetools/checker-framework/issues/399 + final @NonNull CacheTreeNode node = queue.poll(); - Deque> pending = new ArrayDeque<>(); - pending.add(this.cacheRoot); + boolean hasChildren = false; - while (!pending.isEmpty()) { - CacheTreeNode current = pending.remove(); + if (node.hasTimeChild()) { + queue.add(node.getTimeoutChild()); + hasChildren = true; + } - if (current.hasTimeChild()) { - CacheTreeNode child = current.getTimeoutChild(); - if (!stateMap.containsKey(child)) { - stateMap.put(child, mealy.addState()); - pending.add(child); + for (CacheTreeTransition t : node.getUntimedChildren().values()) { + queue.add(t.target()); + hasChildren = true; } - mealy.addAlphabetSymbol(new TimeStepSequence<>(current.getTimeout())); - mealy.addTransition(stateMap.get(current), - TimedInput.step(current.getTimeout()), - stateMap.get(child), - current.getTimeoutOutput()); - } - for (InputSymbol sym : current.getUntimedChildren().keySet()) { - CacheTreeNode child = current.getChild(sym); - if (!stateMap.containsKey(child)) { - stateMap.put(child, mealy.addState()); - pending.add(child); + if (!hasChildren) { + super.nextValue = node; + return true; } - mealy.addAlphabetSymbol(sym); - mealy.addTransition(stateMap.get(current), sym, stateMap.get(child), current.getOutput(sym)); } - } - return mealy.graphView(); - } - - @Override - public MMLTEquivalenceOracle createCacheConsistencyTest() { - return new MMLTCacheConsistencyTest<>(this, this.modelParams); + return false; + } } - } diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index 6e931c47f..a11a56662 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -23,8 +23,8 @@ /** * Avoids redundant queries for timeouts. *

                - * Assume we waited maxDelay for a timeout and observed no expiration. Then, any consecutive timeout-input must also - * show no timer (assuming sufficient maxDelay). Hence, we do not need to query the SUL for these. + * Assume we waited maxDelay for a timeout and observed no expiration. Then any consecutive timeout-input must also show + * no timer (assuming sufficient maxDelay). Hence, we do not need to query the SUL for these. *

                * We may observe a timeout again after any non-delaying input, as this may trigger a location-change. * diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/sul/SULCaches.java b/filters/cache/src/main/java/de/learnlib/filter/cache/sul/SULCaches.java index aff177cb9..ac3e1ed4c 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/sul/SULCaches.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/sul/SULCaches.java @@ -15,8 +15,11 @@ */ package de.learnlib.filter.cache.sul; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; import de.learnlib.sul.SUL; import de.learnlib.sul.StateLocalInputSUL; +import de.learnlib.sul.TimedSUL; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.incremental.mealy.dag.IncrementalMealyDAGBuilder; import net.automatalib.incremental.mealy.tree.IncrementalMealyTreeBuilder; @@ -33,8 +36,8 @@ private SULCaches() { /** * Creates a {@link SULCache} for a given {@link SUL}. *

                - * Note that this method does not specify the implementation to use for the cache. Currently, a DAG ({@link - * #createDAGCache}) is used; however, this may change in the future. + * Note that this method does not specify the implementation to use for the cache. Currently, a DAG + * ({@link #createDAGCache}) is used; however, this may change in the future. * * @param alphabet * the input alphabet @@ -94,8 +97,8 @@ public static SULCache createTreeCache(Alphabet alphabet, SUL - * Note that this method does not specify the implementation to use for the cache. Currently, a tree ({@link - * #createStateLocalInputTreeCache}) is used; however, this may change in the future. + * Note that this method does not specify the implementation to use for the cache. Currently, a tree + * ({@link #createStateLocalInputTreeCache}) is used; however, this may change in the future. * * @param alphabet * the input alphabet @@ -134,4 +137,42 @@ public static StateLocalInputSULCache createStateLocalInputTreeCach StateLocalInputSUL sul) { return new StateLocalInputSULCache<>(new IncrementalMealyTreeBuilder<>(alphabet), sul); } + + /** + * Creates a {@link TimedSULTreeCache} for a given {@link TimedSUL}. + * + * @param sul + * the sul + * @param params + * the specific parameter for time related queries + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return the cache + * + */ + public static TimedSULTreeCache createTimedCache(TimedSUL sul, MMLTModelParams params) { + return createTimedTreeCache(sul, params); + } + + /** + * Creates a {@link TimedSULTreeCache} for a given {@link TimedSUL}, using a tree for internal cache organization. + * + * @param sul + * the sul + * @param params + * the specific parameter for time related queries + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return the cache + * + */ + public static TimedSULTreeCache createTimedTreeCache(TimedSUL sul, MMLTModelParams params) { + return new TimedSULTreeCache<>(sul, params); + } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java index 6afccdc1d..9a6daf510 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/AbstractCacheTest.java @@ -28,7 +28,6 @@ import de.learnlib.testsupport.ResumeUtils; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.SupportsGrowingAlphabet; -import net.automatalib.automaton.concept.Output; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.testng.Assert; @@ -38,7 +37,7 @@ /** * A simple test against various cache implementations. */ -public abstract class AbstractCacheTest, A extends Output, I, D> { +public abstract class AbstractCacheTest, A, I, D> { protected static final int LENGTH = 5; private final Random random = new Random(42); @@ -125,8 +124,8 @@ public void testCacheConsistency() { Assert.assertNull(targetCE); Assert.assertNotNull(invalidTargetCE); - Assert.assertNotEquals(invalidTarget.computeOutput(invalidTargetCE.getInput()), - target.computeOutput(invalidTargetCE.getInput())); + Assert.assertNotEquals(computeOutput(invalidTarget, invalidTargetCE.getInput()), + computeOutput(target, invalidTargetCE.getInput())); } @Test(dependsOnMethods = "testCacheConsistency") @@ -250,6 +249,8 @@ protected Query getQuery(int i) { protected abstract OR getResumedOracle(OR original); + protected abstract D computeOutput(A model, Word input); + protected abstract long getNumberOfPosedQueries(); protected abstract boolean supportsPrefixes(); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java b/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java index ea47d5017..29a7d300e 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java @@ -29,9 +29,13 @@ import de.learnlib.oracle.membership.MooreSimulatorOracle; import de.learnlib.sul.SUL; import de.learnlib.sul.StateLocalInputSUL; +import de.learnlib.time.MMLTModelParams; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.impl.CompactDFA; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.automaton.transducer.impl.CompactMealy; @@ -50,6 +54,9 @@ public final class CacheTestUtils { public static final CompactMealy MEALY_INVALID; public static final CompactMoore MOORE; public static final CompactMoore MOORE_INVALID; + public static final CompactMMLT MMLT; + public static final MMLTModelParams MMLT_PARAMS; + public static final CompactMMLT MMLT_INVALID; public static final SUL SUL; public static final StateLocalInputSUL SLI_SUL; @@ -73,12 +80,44 @@ public final class CacheTestUtils { MEALY_INVALID = RandomAutomata.randomMealy(random, size, combinedAlphabet, OUTPUT_ALPHABET); MOORE_INVALID = RandomAutomata.randomMoore(random, size, combinedAlphabet, OUTPUT_ALPHABET); + MMLT = buildMMLT(); + MMLT_PARAMS = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); + MMLT_INVALID = buildMMLT(); + MMLT_INVALID.removeTimer(2, "d"); + MMLT_INVALID.addPeriodicTimer(2, "d", 4, "done"); + SUL = new MealySimulatorSUL<>(MEALY); SLI_SUL = new StateLocalInputMealySimulatorSUL<>(MEALY); } private CacheTestUtils() {} + private static CompactMMLT buildMMLT() { + var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); + var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + + var s0 = model.addInitialState(); + var s1 = model.addState(); + var s2 = model.addState(); + var s3 = model.addState(); + + model.addTransition(s0, "p1", s1, "go"); + model.addTransition(s1, "abort", s1, "ok"); + model.addLocalReset(s1, "abort"); + + model.addPeriodicTimer(s1, "a", 3, "part"); + model.addPeriodicTimer(s1, "b", 6, "noise"); + model.addOneShotTimer(s1, "c", 40, "done", s3); + + model.addTransition(s0, "p2", s2, "go"); + model.addTransition(s2, "abort", s3, "void"); + model.addOneShotTimer(s2, "d", 4, "done", s3); + + model.addTransition(s3, "collect", s0, "void"); + + return model; + } + public static DFACounterOracle getCounter(net.automatalib.automaton.fsa.DFA delegate) { return new DFACounterOracle<>(new DFASimulatorOracle<>(delegate)); } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/TimedSULLearningCacheOracle.java b/filters/cache/src/test/java/de/learnlib/filter/cache/TimedSULLearningCacheOracle.java new file mode 100644 index 000000000..d6d70e5b5 --- /dev/null +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/TimedSULLearningCacheOracle.java @@ -0,0 +1,72 @@ +/* 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 de.learnlib.filter.cache; + +import java.util.Collection; + +import de.learnlib.filter.cache.LearningCache.MMLTLearningCache; +import de.learnlib.filter.cache.LearningCacheOracle.MMMLTLearningCacheOracle; +import de.learnlib.filter.cache.mmlt.TimedSULTreeCache; +import de.learnlib.oracle.EquivalenceOracle; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.query.Query; +import de.learnlib.time.MMLTModelParams; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; + +public class TimedSULLearningCacheOracle> + implements MMMLTLearningCacheOracle { + + private final C cache; + private final TimedQueryOracle oracle; + + public TimedSULLearningCacheOracle(C cache, TimedQueryOracle oracle) { + this.cache = cache; + this.oracle = oracle; + } + + @Override + public void processQueries(Collection, Word>>> queries) { + oracle.processQueries(queries); + } + + @Override + public EquivalenceOracle, TimedInput, Word>> createCacheConsistencyTest() { + return cache.createCacheConsistencyTest(); + } + + public C getCache() { + return cache; + } + + public TimedQueryOracle getOracle() { + return oracle; + } + + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + return oracle.queryTimers(prefix, maxTotalWaitingTime); + } + + public static TimedSULLearningCacheOracle> fromTimedSULCache(TimedSULTreeCache cache, + MMLTModelParams params) { + return new TimedSULLearningCacheOracle<>(cache, new TimedSULOracle<>(cache, params)); + } + +} diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java index 29035da52..ba496cb0f 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/AbstractDFACacheTest.java @@ -24,6 +24,7 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.word.Word; public abstract class AbstractDFACacheTest extends AbstractCacheTest, DFA, Character, Boolean> { @@ -56,6 +57,11 @@ protected DFACacheOracle getResumedOracle(DFACacheOracle o return fresh; } + @Override + protected Boolean computeOutput(DFA model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java index 25ab6cf13..4ee38c62f 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/dfa/DFAHashCacheTest.java @@ -22,6 +22,7 @@ import de.learnlib.statistic.Statistics; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.fsa.DFA; +import net.automatalib.word.Word; public class DFAHashCacheTest extends AbstractCacheTest, DFA, Character, Boolean> { @@ -54,6 +55,11 @@ protected DFAHashCacheOracle getResumedOracle(DFAHashCacheOracle model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(DFACounterOracle.QUERY_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java index b39093065..f8ea36dc5 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AbstractMealyCacheTest.java @@ -61,6 +61,11 @@ protected MealyCacheOracle getResumedOracle(MealyCacheOracle return fresh; } + @Override + protected Word computeOutput(MealyMachine model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(MealyCounterOracle.QUERY_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java index 01dffda7d..b9bb6de80 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mealy/AdaptiveQueryCacheTest.java @@ -82,6 +82,11 @@ protected Wrapper getResumedOracle(Wrapper(fresh); } + @Override + protected Word computeOutput(MealyMachine model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(CounterAdaptiveQueryOracle.RESET_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java index e56f0e9f4..05c65c7ad 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/MMLTCacheTest.java @@ -15,133 +15,110 @@ */ package de.learnlib.filter.cache.mmlt; -import java.util.ArrayList; import java.util.List; -import java.util.Random; import de.learnlib.driver.simulator.MMLTSimulatorSUL; -import de.learnlib.oracle.membership.TimedSULOracle; -import de.learnlib.time.MMLTModelParams; +import de.learnlib.filter.cache.AbstractCacheTest; +import de.learnlib.filter.cache.CacheTestUtils; +import de.learnlib.filter.cache.TimedSULLearningCacheOracle; +import de.learnlib.filter.cache.sul.SULCaches; +import de.learnlib.filter.statistic.sul.CounterTimedSUL; +import de.learnlib.statistic.Statistics; +import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.automaton.mmlt.impl.CompactMMLT; -import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; -import net.automatalib.common.util.random.RandomUtil; +import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; import org.testng.Assert; import org.testng.annotations.Test; @Test -public class MMLTCacheTest { +public class MMLTCacheTest + extends AbstractCacheTest>, MMLT, TimedInput, Word>> { - private CompactMMLT buildBaseModel() { - var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); - var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + private final CounterTimedSUL counter; - var s0 = model.addInitialState(); - var s1 = model.addState(); - var s2 = model.addState(); - var s3 = model.addState(); + public MMLTCacheTest() { + counter = new CounterTimedSUL<>(new MMLTSimulatorSUL<>(CacheTestUtils.MMLT)); + } + + @Override + protected Alphabet> getAlphabet() { + return CacheTestUtils.MMLT.getSemantics().getInputAlphabet(); + } - model.addTransition(s0, "p1", s1, "go"); - model.addTransition(s1, "abort", s1, "ok"); - model.addLocalReset(s1, "abort"); + @Override + protected Alphabet> getExtensionAlphabet() { + return Alphabets.fromArray(); + } - model.addPeriodicTimer(s1, "a", 3, "part"); - model.addPeriodicTimer(s1, "b", 6, "noise"); - model.addOneShotTimer(s1, "c", 40, "done", s3); + @Override + protected MMLT getTargetModel() { + return CacheTestUtils.MMLT; + } + + @Override + protected MMLT getInvalidTargetModel() { + return CacheTestUtils.MMLT_INVALID; + } - model.addTransition(s0, "p2", s2, "go"); - model.addTransition(s2, "abort", s3, "void"); - model.addOneShotTimer(s2, "d", 4, "done", s3); + @Override + protected TimedSULLearningCacheOracle> getCachedOracle() { + return TimedSULLearningCacheOracle.fromTimedSULCache(SULCaches.createTimedCache(counter, + CacheTestUtils.MMLT_PARAMS), + CacheTestUtils.MMLT_PARAMS); + } - model.addTransition(s3, "collect", s0, "void"); + @Override + protected TimedSULLearningCacheOracle> getResumedOracle( + TimedSULLearningCacheOracle> original) { + return original; + } - return model; + @Override + protected Word> computeOutput(MMLT model, + Word> input) { + return model.getSemantics().computeOutput(input); } - /** - * Tests if the information in the cache is consistent with the output of the SUL. - */ - public void testCacheAndSULConsistency() { - Random random = new Random(100); - - var automaton = buildBaseModel(); - var params = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); - - var sul = new MMLTSimulatorSUL<>(automaton.getSemantics()); - var cacheSUL = new TimedSULTreeCache<>(sul, params); - var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); - var timeOracleWithoutCache = new TimedSULOracle<>(sul, params); - - var listAlphabet = new ArrayList<>(automaton.getSemantics().getInputAlphabet()); - - // Generate some random words and compare outputs of the cache, SUL, and automaton: - List>> words = new ArrayList<>(); - for (int i = 0; i < 500; i++) { - int maxLength = random.nextInt(1, 500); - var symbols = RandomUtil.sample(random, listAlphabet, maxLength); - var word = Word.fromList(symbols); - words.add(word); - - var cacheOutput = timeOracleWithCache.answerQuery(word); - var sulOutput = timeOracleWithoutCache.answerQuery(word); - var automatonOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), word); - - Assert.assertEquals(sulOutput, - automatonOutput, - "Automaton output does not match SUL output for word " + word); - Assert.assertEquals(cacheOutput, sulOutput, "Cache output does not match SUL output for word " + word); - } - - // Now that the cache contents have changed, ensure that the results are still correct: - for (var word : words) { - var cacheOutput = timeOracleWithCache.answerQuery(word); - var sulOutput = timeOracleWithoutCache.answerQuery(word); - - Assert.assertEquals(sulOutput, cacheOutput, "Cache output does not match SUL output for word " + word); - } + @Override + protected long getNumberOfPosedQueries() { + return Statistics.getCollector().getCount(CounterTimedSUL.KEY_RESETS).orElse(0L); } - @Test - public void testCacheConsistencyTest() { - // Test if the cache consistency test works correctly: - var refAutomaton = buildBaseModel(); - var params = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); + @Override + protected boolean supportsPrefixes() { + return true; + } - var sul = new MMLTSimulatorSUL<>(refAutomaton.getSemantics()); - var cacheSUL = new TimedSULTreeCache<>(sul, params); - var timeOracleWithCache = new TimedSULOracle<>(cacheSUL, params); + @Override + protected boolean supportsGrowing() { + return false; + } - // Add word to cache: + @Override + @Test(dependsOnMethods = "testPrefix") + public void testCacheConsistency() { + // Add word to cache to ensure counter example Word> testWord = Word.fromSymbols(TimedInput.input("p2"), TimedInput.timeout(), TimedInput.step(), TimedInput.timeout()); - timeOracleWithCache.answerQuery(testWord); + super.oracle.getOracle().answerQuery(testWord); - // Create a bad hypothesis: - var badAutomaton = buildBaseModel(); - badAutomaton.removeTimer(2, "d"); - badAutomaton.addPeriodicTimer(2, "d", 4, "done"); - - // Query the cache for a counterexample: - Word> expectedCex = - Word.fromSymbols(new InputSymbol<>("p2"), new TimeoutSymbol<>(), new TimeoutSymbol<>()); - - var cacheConsistencyTest = cacheSUL.createCacheConsistencyTest(); - var cex = cacheConsistencyTest.findCounterExample(badAutomaton, refAutomaton.getSemantics().getInputAlphabet()); - Assert.assertNotNull(cex); - Assert.assertEquals(cex.getInput(), expectedCex); + super.testCacheConsistency(); + } + @Test(dependsOnMethods = "testCacheConsistency") + public void testReducedAlphabet() { // Now test with a reduced alphabet: var symbols = List.of("p1", "abort", "collect"); // not p1 - GrowingMapAlphabet> reducedAlphabet = new GrowingMapAlphabet<>(); - symbols.forEach(s -> reducedAlphabet.add(new InputSymbol<>(s))); + var reducedAlphabet = symbols.stream().>map(InputSymbol::new).collect(Alphabets.collector()); reducedAlphabet.add(new TimeoutSymbol<>()); // The only counterexample in the cache has the prefix p2, which is now omitted: - Assert.assertNull(cacheConsistencyTest.findCounterExample(badAutomaton, reducedAlphabet)); + Assert.assertNull(super.oracle.createCacheConsistencyTest() + .findCounterExample(CacheTestUtils.MMLT_INVALID, reducedAlphabet)); } } diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java index bac90c872..21450745a 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/moore/AbstractMooreCacheTest.java @@ -61,6 +61,11 @@ protected MooreCacheOracle getResumedOracle(MooreCacheOracle return fresh; } + @Override + protected Word computeOutput(MooreMachine model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(MooreCounterOracle.QUERY_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java index 3caf9f226..ca771496b 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/AbstractSULCacheTest.java @@ -59,6 +59,11 @@ protected SULLearningCacheOracle computeOutput(MealyMachine model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(CounterSUL.RESET_KEY).orElse(0L); diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java index 41cb1c78b..6c30014bd 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/sul/StateLocalInputSULTreeCacheTest.java @@ -146,6 +146,11 @@ protected SULLearningCacheOracle computeOutput(MealyMachine model, Word input) { + return model.computeOutput(input); + } + @Override protected long getNumberOfPosedQueries() { return Statistics.getCollector().getCount(CounterStateLocalInputSUL.RESET_KEY).orElse(0L); diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java index 93970d616..94c396b6f 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/oracle/CounterOracle.java @@ -88,9 +88,7 @@ public CounterOracle(MembershipOracle delegate, String id) { public void processQueries(Collection> queries) { statisticsCollector.increaseCounter(QUERY_KEY + id, "Number of queries", queries.size()); for (Query qry : queries) { - statisticsCollector.increaseCounter(SYMBOL_KEY + id, - "Number of symbols", - qry.getPrefix().length() + qry.getSuffix().length()); + statisticsCollector.increaseCounter(SYMBOL_KEY + id, "Number of symbols", qry.length()); } statisticsCollector.startOrResumeClock(DUR_KEY + id, "Duration of queries"); delegate.processQueries(queries); diff --git a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java index b7fb056f9..95d826b88 100644 --- a/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java +++ b/filters/statistics/src/main/java/de/learnlib/filter/statistic/sul/CounterTimedSUL.java @@ -35,6 +35,8 @@ */ public class CounterTimedSUL implements TimedSUL { + public static final String KEY_RESETS = "sul_resets_counter"; + private final TimedSUL delegate; private final StatisticsCollector stats; @@ -89,7 +91,7 @@ public List> collectTimeouts(TimeStepSequence input) { @Override public void pre() { this.delegate.pre(); - stats.increaseCounter(withPrefix("sul_resets_counter"), withPrefix("SUL resets")); + stats.increaseCounter(withPrefix(KEY_RESETS), withPrefix("SUL resets")); } @Override diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java index f7cc4e3b2..912baf0b5 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracle.java @@ -68,36 +68,30 @@ public class ResetSearchEQOracle implements MMLTEquivalenceOracle { private final long loopingInputSelectionSeed; + /** + * Constructor. + * + * @param timeOracle + * the oracle to execute tests on + * @param seed + * the seed for sampling prefixes + * @param loopInsertPercentage + * the percentage of looping symbols that should be used for sampling the random infix (should be between 0 + * and 1) + * @param testedLocPercentage + * the percentage of locations for which prefixes should be included (should be between 0 and 1) + */ public ResetSearchEQOracle(TimedQueryOracle timeOracle, long seed, double loopInsertPercentage, double testedLocPercentage) { this.timeOracle = timeOracle; this.locPrefixRandom = new Random(seed); - this.loopInsertPercentage = loopInsertPercentage; - this.testedLocPercentage = testedLocPercentage; - this.loopingInputSelectionSeed = seed; - } - - private List> getLoopingSymbols(S sourceLoc, - List> alphabet, - MMLT hypothesis) { - final List> loopingInputs = new ArrayList<>(); + this.loopInsertPercentage = Math.max(0, Math.min(loopInsertPercentage, 1)); + this.testedLocPercentage = Math.max(0, Math.min(testedLocPercentage, 1)); - for (TimedInput sym : alphabet) { - // only consider non-delaying inputs, as only these can perform local resets - if (sym instanceof InputSymbol ndi) { - final T trans = hypothesis.getTransition(sourceLoc, ndi.symbol()); - - // Collect self-loops: - if (trans == null || Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { - loopingInputs.add(sym); - } - } - } - - return loopingInputs; + this.loopingInputSelectionSeed = seed; } @Override @@ -106,18 +100,29 @@ private List> getLoopingSymbols(S sourceLoc, if (loopInsertPercentage == 0) { return null; // oracle is disabled } - List> listInputs = new ArrayList<>(inputs); - if (listInputs.stream().noneMatch(s -> s instanceof TimeStepSequence || s instanceof TimeoutSymbol)) { - LOGGER.warn( - "ResetSearchOracle requires inputs to contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); + if (!containsTimeoutAndTimeStep(inputs)) { + LOGGER.warn("Inputs must contain TimeoutSymbol and TimeStepSymbol. Will not find counterexample."); return null; } - return this.findCexInternal(hypothesis, listInputs); + + return this.findCexInternal(hypothesis, inputs); + } + + private boolean containsTimeoutAndTimeStep(Collection> inputs) { + boolean timeout = false; + boolean timestep = false; + + for (TimedInput i : inputs) { + timeout |= i instanceof TimeoutSymbol; + timestep |= i instanceof TimeStepSequence; + } + + return timeout && timestep; } private @Nullable DefaultQuery, Word>> findCexInternal(MMLT hypothesis, - List> inputs) { + Collection> inputs) { // Retrieve prefixes from state cover, to establish some separation between learner and teacher: Map>> stateCover = MMLTCover.getMMLTLocationCover(hypothesis, inputs); @@ -167,7 +172,7 @@ private List> getLoopingSymbols(S sourceLoc, // Check if counterexample: Word> testWord = wbTestWord.toWord(); - Word> hypOutput = hypothesis.getSemantics().computeSuffixOutput(Word.epsilon(), testWord); + Word> hypOutput = hypothesis.getSemantics().computeOutput(testWord); Word> sulOutput = timeOracle.answerQuery(testWord); if (!hypOutput.equals(sulOutput)) { return new DefaultQuery<>(testWord, sulOutput); @@ -176,4 +181,24 @@ private List> getLoopingSymbols(S sourceLoc, return null; } + private List> getLoopingSymbols(S sourceLoc, + Collection> inputs, + MMLT hypothesis) { + final List> loopingInputs = new ArrayList<>(); + + for (TimedInput sym : inputs) { + // only consider non-delaying inputs, as only these can perform local resets + if (sym instanceof InputSymbol ndi) { + final T trans = hypothesis.getTransition(sourceLoc, ndi.symbol()); + + // Collect self-loops: + if (trans != null && Objects.equals(hypothesis.getSuccessor(trans), sourceLoc)) { + loopingInputs.add(sym); + } + } + } + + return loopingInputs; + } + } diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index e8695cdb8..a74317467 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -93,7 +93,7 @@ public TimerQueryResult queryTimers(Word> prefix, long maxTotal * @param timeouts * known timeouts * @param currentTime - * current time. + * current time * * @return next timeout time */ @@ -204,7 +204,8 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, TimerInfo newTimer = new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); return new TimerCheckResult<>(newTimer, false); - } else if (nextActualTime == nextExpectedTime) { + } else { + assert nextActualTime == nextExpectedTime; // Timeout occurred at expected time -> check if matching expected output: Map expectedOutputs = knownTimers.stream() .filter(t -> nextExpectedTime % t.initial() == 0) @@ -246,8 +247,6 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, new TimerInfo<>(getUniqueTimerName(), nextActualTime, newOutputs, null, true); return new TimerCheckResult<>(newTimer, false); } - } else { - throw new IllegalStateException(); } return new TimerCheckResult<>(null, false); diff --git a/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SimulatorOracleTest.java b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SimulatorOracleTest.java index ac68bfae7..48e99a287 100644 --- a/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SimulatorOracleTest.java +++ b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/SimulatorOracleTest.java @@ -46,8 +46,8 @@ public void testDFASimulatorOracle() { queries.add(q1); queries.add(q2); - Assert.assertEquals(queries.get(0).getInput().size(), 3); - Assert.assertEquals(queries.get(1).getInput().size(), 3); + Assert.assertEquals(queries.get(0).length(), 3); + Assert.assertEquals(queries.get(1).length(), 3); oracle.processQueries(queries); diff --git a/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/TimedSULOracleTest.java b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/TimedSULOracleTest.java new file mode 100644 index 000000000..15510a9ed --- /dev/null +++ b/oracles/membership-oracles/src/test/java/de/learnlib/oracle/membership/TimedSULOracleTest.java @@ -0,0 +1,49 @@ +/* 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 de.learnlib.oracle.membership; + +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.word.Word; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TimedSULOracleTest { + + @Test + public void testValidation() { + var example = MMLTExamples.sensorCollector(); + var mmlt = example.getReferenceAutomaton(); + var params = example.getParams(); + + var oracle = new TimedSULOracle<>(new MMLTSimulatorSUL<>(mmlt), params); + + Assert.assertThrows(IllegalArgumentException.class, + () -> oracle.queryTimers(Word.epsilon(), params.maxTimeoutWaitingTime() - 1)); + Assert.assertThrows(IllegalArgumentException.class, + () -> oracle.queryTimers(Word.fromLetter(TimedInput.timeout()), + params.maxTimeoutWaitingTime())); + Assert.assertThrows(IllegalArgumentException.class, + () -> oracle.answerQuery(Word.epsilon(), Word.fromLetter(TimedInput.step(2)))); + + // assert not throwing + Assert.assertEquals(oracle.answerQuery(Word.fromLetter(TimedInput.timeout()), + Word.fromLetter(TimedInput.timeout())), + Word.fromLetter(mmlt.getSemantics().getSilentOutput())); + + } +} diff --git a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java index fe5625ab1..bb73477ef 100644 --- a/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java +++ b/test-support/learner-it-support/src/main/java/de/learnlib/testsupport/it/learner/AbstractMMLTLearnerIT.java @@ -52,8 +52,7 @@ private List> createAllVariantsITCase(MMLTLearnin final Alphabet alphabet = example.getUntimedAlphabet(); final TimedQueryOracle mqOracle = - new TimedSULOracle<>(new MMLTSimulatorSUL<>(example.getReferenceAutomaton().getSemantics()), - example.getParams()); + new TimedSULOracle<>(new MMLTSimulatorSUL<>(example.getReferenceAutomaton()), example.getParams()); final MMLTLearnerVariantListImpl variants = new MMLTLearnerVariantListImpl<>(); addLearnerVariants(alphabet, mqOracle, example, variants); From d0749ae6e7ff53dea5765b778da6b24179c0a9f1 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 1 Dec 2025 21:13:29 +0100 Subject: [PATCH 53/55] cleanups, parallel timed oracles, test cases --- .../lstar/it/ExtensibleLStarMMLTIT.java | 25 +- .../oracle/ParallelTimedQueryOracle.java | 28 ++ .../de/learnlib/oracle/SingleQueryOracle.java | 5 + drivers/simulator/pom.xml | 22 ++ .../learnlib/driver/simulator/ForkTest.java | 174 +++++++++++ filters/cache/pom.xml | 4 + .../filter/cache/mmlt/TimeoutReducerSUL.java | 2 +- .../learnlib/filter/cache/CacheTestUtils.java | 1 - .../cache/mmlt/TimeoutReducerSULTest.java | 57 ++++ .../mmlt/RandomWpMethodEQOracle.java | 37 ++- .../mmlt/RandomWpMethodEQOracleTest.java | 88 ++++++ .../mmlt/ResetSearchEQOracleTest.java | 87 ++++++ .../oracle/membership/TimedSULOracle.java | 126 ++++---- ...ynamicParallelTimedOracleQueryBuilder.java | 53 ++++ .../DynamicParallelTimedQueryOracle.java | 58 ++++ .../parallelism/ParallelOracleBuilders.java | 161 ++++++++++ .../StaticParallelTimedQueryOracle.java | 57 ++++ ...StaticParallelTimedQueryOracleBuilder.java | 53 ++++ ...ctDynamicParallelTimedQueryOracleTest.java | 163 ++++++++++ ...actStaticParallelTimedQueryOracleTest.java | 292 ++++++++++++++++++ .../DynamicParallelTimedQueryOracleTest.java | 133 ++++++++ .../DynamicParallelTimedSULTest.java | 27 ++ .../DynamicParallelTimedSupplierTest.java | 24 ++ .../StaticParallelObservableSULTest.java | 3 +- .../StaticParallelTimedQueryOracleTest.java | 53 ++++ .../StaticParallelTimedSULTest.java | 55 ++++ .../StaticParallelTimedSupplierTest.java | 39 +++ test-support/learning-examples/pom.xml | 8 +- .../testsupport/example/LearningExample.java | 14 + 29 files changed, 1740 insertions(+), 109 deletions(-) create mode 100644 api/src/main/java/de/learnlib/oracle/ParallelTimedQueryOracle.java create mode 100644 drivers/simulator/src/test/java/de/learnlib/driver/simulator/ForkTest.java create mode 100644 filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSULTest.java create mode 100644 oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracleTest.java create mode 100644 oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracleTest.java create mode 100644 oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedOracleQueryBuilder.java create mode 100644 oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracle.java create mode 100644 oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracle.java create mode 100644 oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleBuilder.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelTimedQueryOracleTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelTimedQueryOracleTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracleTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSULTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSupplierTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSULTest.java create mode 100644 oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSupplierTest.java diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index 4bfec0ae5..f9ac0e6b5 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Random; import java.util.stream.Stream; @@ -60,30 +61,27 @@ protected void addLearnerVariants(Alphabet alphabet, MMLTLearnerVariantList variants) { var mmlt = example.getReferenceAutomaton(); - int counters = countTimers(mmlt); + var counters = countTimers(mmlt); List>> suffixes = new ArrayList<>(); alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); - for (FilterMode filterMode : FilterMode.values()) { + var filters = Arrays.asList(new MMLTPerfectSymbolFilter<>(mmlt), + new MMLTRandomSymbolFilter<>(mmlt, 0.1, new Random(42)), + new IgnoreAllSymbolFilter, InputSymbol>(), + new AcceptAllSymbolFilter, InputSymbol>()); - SymbolFilter, InputSymbol> filter = switch (filterMode) { - case perfect -> new MMLTPerfectSymbolFilter<>(mmlt); - case random -> new MMLTRandomSymbolFilter<>(mmlt, 0.1, new Random(42)); - case ignore_all -> new IgnoreAllSymbolFilter<>(); - case none -> new AcceptAllSymbolFilter<>(); - }; + for (SymbolFilter, InputSymbol> filter : filters) { var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) .withModelParams(example.getParams()) .withTimeOracle(mqOracle) .withInitialSuffixes(suffixes) .withSymbolFilter(cachedFilter) .create(); - variants.addLearnerVariant("system=" + example + ",filter=" + filterMode, learner, counters + mmlt.size()); + variants.addLearnerVariant("system=" + example + ",filter=" + filter, learner, counters + mmlt.size()); } } @@ -126,13 +124,6 @@ private static List listModelFiles() { return models; } - private enum FilterMode { - none, - random, - ignore_all, - perfect - } - public static class Example implements MMLTLearningExample { private final String name; diff --git a/api/src/main/java/de/learnlib/oracle/ParallelTimedQueryOracle.java b/api/src/main/java/de/learnlib/oracle/ParallelTimedQueryOracle.java new file mode 100644 index 000000000..6f32e3df0 --- /dev/null +++ b/api/src/main/java/de/learnlib/oracle/ParallelTimedQueryOracle.java @@ -0,0 +1,28 @@ +/* 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 de.learnlib.oracle; + +/** + * {@link ParallelOracle} equivalent for {@link OmegaMembershipOracle}s. + * + * @param + * oracle state type + * @param + * input symbol type + * @param + * output domain type + */ +public interface ParallelTimedQueryOracle extends ThreadPool, TimedQueryOracle {} diff --git a/api/src/main/java/de/learnlib/oracle/SingleQueryOracle.java b/api/src/main/java/de/learnlib/oracle/SingleQueryOracle.java index ba940dccb..a67973d07 100644 --- a/api/src/main/java/de/learnlib/oracle/SingleQueryOracle.java +++ b/api/src/main/java/de/learnlib/oracle/SingleQueryOracle.java @@ -18,6 +18,8 @@ import java.util.Collection; import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.word.Word; /** @@ -58,4 +60,7 @@ interface SingleQueryOracleMealy extends SingleQueryOracle>, Me @FunctionalInterface interface SingleQueryOracleMoore extends SingleQueryOracle>, MooreMembershipOracle {} + interface SingleQueryOracleMMLT + extends SingleQueryOracle, Word>>, TimedQueryOracle {} + } diff --git a/drivers/simulator/pom.xml b/drivers/simulator/pom.xml index d9d6fa3e9..cf2f8aa04 100644 --- a/drivers/simulator/pom.xml +++ b/drivers/simulator/pom.xml @@ -43,9 +43,31 @@ limitations under the License. automata-api + org.checkerframework checker-qual + + + + de.learnlib.testsupport + learnlib-learning-examples + + + + net.automatalib + automata-core + + + + org.mockito + mockito-core + + + org.testng + testng + + diff --git a/drivers/simulator/src/test/java/de/learnlib/driver/simulator/ForkTest.java b/drivers/simulator/src/test/java/de/learnlib/driver/simulator/ForkTest.java new file mode 100644 index 000000000..6688bbf94 --- /dev/null +++ b/drivers/simulator/src/test/java/de/learnlib/driver/simulator/ForkTest.java @@ -0,0 +1,174 @@ +/* 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 de.learnlib.driver.simulator; + +import de.learnlib.testsupport.example.mealy.ExampleCoffeeMachine; +import de.learnlib.testsupport.example.mealy.ExampleCoffeeMachine.Input; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ForkTest { + + @Test + public void testMealy() { + var sul = new MealySimulatorSUL<>(ExampleCoffeeMachine.constructMachine()); + + Assert.assertTrue(sul.canFork()); + + // check delegation + var spy = Mockito.spy(sul); + var fork = spy.fork(); + + Assert.assertNotNull(fork); + + fork.pre(); + fork.step(Input.CLEAN); + fork.post(); + fork.canFork(); + fork.fork(); + + Mockito.verify(spy, Mockito.only()).fork(); + + // check independency + spy.pre(); + spy.step(Input.WATER); + spy.step(Input.POD); + + fork.pre(); + fork.post(); + + var out = spy.step(Input.BUTTON); + Assert.assertEquals(out, ExampleCoffeeMachine.OUT_COFFEE); + + spy.post(); + } + + @Test + public void testMMLT() { + var mmlt = MMLTExamples.sensorCollector().getReferenceAutomaton(); + var alphabet = mmlt.getInputAlphabet(); + var sul = new MMLTSimulatorSUL<>(mmlt); + var input = TimedInput.input(alphabet.getSymbol(0)); + + Assert.assertTrue(sul.canFork()); + + // check delegation + var spy = Mockito.spy(sul); + var fork = spy.fork(); + + Assert.assertNotNull(fork); + + fork.pre(); + fork.step(input); + fork.follow(Word.fromLetter(input)); + fork.timeoutStep(2); + fork.post(); + fork.canFork(); + fork.fork(); + + Mockito.verify(spy, Mockito.only()).fork(); + + // check independency + spy.pre(); + spy.step(input); + + fork.pre(); + fork.post(); + + var out = spy.timeoutStep(3); + Assert.assertEquals(out, new TimedOutput<>("part", 3)); + + spy.post(); + } + + @Test + public void testObservable() { + var sul = new ObservableMealySimulatorSUL<>(ExampleCoffeeMachine.constructMachine()); + + Assert.assertTrue(sul.canFork()); + + // check delegation + var spy = Mockito.spy(sul); + var fork = spy.fork(); + + Assert.assertNotNull(fork); + + fork.pre(); + fork.step(Input.CLEAN); + fork.deepCopies(); + fork.getState(); + fork.post(); + fork.canFork(); + fork.fork(); + + Mockito.verify(spy, Mockito.only()).fork(); + + // check independency + spy.pre(); + spy.step(Input.WATER); + spy.step(Input.POD); + + fork.pre(); + fork.post(); + + var out = spy.step(Input.BUTTON); + Assert.assertEquals(out, ExampleCoffeeMachine.OUT_COFFEE); + + spy.post(); + } + + @Test + public void testSLI() { + var sul = new StateLocalInputMealySimulatorSUL<>(ExampleCoffeeMachine.constructMachine()); + + Assert.assertTrue(sul.canFork()); + + // check delegation + var spy = Mockito.spy(sul); + var fork = spy.fork(); + + Assert.assertNotNull(fork); + + fork.pre(); + fork.step(Input.CLEAN); + fork.currentlyEnabledInputs(); + fork.post(); + fork.canFork(); + fork.fork(); + + Mockito.verify(spy, Mockito.only()).fork(); + + // check independency + spy.pre(); + spy.step(Input.WATER); + spy.step(Input.POD); + + fork.pre(); + fork.post(); + + var out = spy.step(Input.BUTTON); + Assert.assertEquals(out, ExampleCoffeeMachine.OUT_COFFEE); + + spy.post(); + } +} + + diff --git a/filters/cache/pom.xml b/filters/cache/pom.xml index 55327c449..1dcf9a267 100644 --- a/filters/cache/pom.xml +++ b/filters/cache/pom.xml @@ -109,6 +109,10 @@ limitations under the License. test + + org.mockito + mockito-core + org.testng testng diff --git a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java index a11a56662..33b3256b1 100644 --- a/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java +++ b/filters/cache/src/main/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSUL.java @@ -26,7 +26,7 @@ * Assume we waited maxDelay for a timeout and observed no expiration. Then any consecutive timeout-input must also show * no timer (assuming sufficient maxDelay). Hence, we do not need to query the SUL for these. *

                - * We may observe a timeout again after any non-delaying input, as this may trigger a location-change. + * We may observe a timeout again after any non-delaying input, as this may trigger a change of location. * * @param * input symbol type (of non-delaying inputs) diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java b/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java index 29a7d300e..e1405f8b1 100644 --- a/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/CacheTestUtils.java @@ -33,7 +33,6 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.fsa.impl.CompactDFA; -import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.automaton.transducer.MealyMachine; diff --git a/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSULTest.java b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSULTest.java new file mode 100644 index 000000000..a947b2ee7 --- /dev/null +++ b/filters/cache/src/test/java/de/learnlib/filter/cache/mmlt/TimeoutReducerSULTest.java @@ -0,0 +1,57 @@ +/* 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 de.learnlib.filter.cache.mmlt; + +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.filter.cache.CacheTestUtils; +import net.automatalib.symbol.time.TimedInput; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class TimeoutReducerSULTest { + + @Test + public void testCaching() { + + var mmlt = CacheTestUtils.MMLT; + var sul = new MMLTSimulatorSUL<>(mmlt); + + var mock = Mockito.spy(sul); + var toSUL = new TimeoutReducerSUL<>(mock, 1); + + toSUL.pre(); + toSUL.step(TimedInput.input("p1")); + var output = toSUL.timeStep(); + + Assert.assertNull(output); + Mockito.verify(mock, Mockito.times(1)).timeoutStep(ArgumentMatchers.anyLong()); + + output = toSUL.timeStep(); + + Assert.assertNull(output); + Mockito.verify(mock, Mockito.times(1)).timeoutStep(ArgumentMatchers.anyLong()); + + toSUL.step(TimedInput.input("p2")); + output = toSUL.timeoutStep(4); + + Assert.assertNotNull(output); + Mockito.verify(mock, Mockito.times(2)).timeoutStep(ArgumentMatchers.anyLong()); + + toSUL.post(); + } +} diff --git a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java index b6e08743d..4acdad8eb 100644 --- a/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java +++ b/oracles/equivalence-oracles/src/main/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracle.java @@ -100,37 +100,38 @@ private DefaultQuery, Word>> findCounterExample for (int i = 0; i < this.bound; i++) { statisticsCollector.increaseCounter(KEY_TESTED_WORDS, "RandomWpOracle: tested words"); - DefaultQuery, Word>> sulAnswer = + Word> testword = this.generateTestword(prefixList, globalSuffixes, hypothesis, hypSemModel, listAlphabet); - Word> hypAnswer = - hypothesis.getSemantics().computeSuffixOutput(sulAnswer.getPrefix(), sulAnswer.getSuffix()); + + Word> sulAnswer = timeOracle.answerQuery(testword); + Word> hypAnswer = hypothesis.getSemantics().computeOutput(testword); // Found inconsistency if outputs do no match: - if (!sulAnswer.getOutput().equals(hypAnswer)) { - return sulAnswer; + if (!sulAnswer.equals(hypAnswer)) { + return new DefaultQuery<>(testword, sulAnswer); } } return null; } - private DefaultQuery, Word>> generateTestword(List>> prefixes, - List>> globalSuffixes, - MMLT hypothesis, - ReducedMMLTSemantics hypSemModel, - List> alphabet) { + private Word> generateTestword(List>> prefixes, + List>> globalSuffixes, + MMLT hypothesis, + ReducedMMLTSemantics hypSemModel, + List> alphabet) { - WordBuilder> wbTestWord = new WordBuilder<>(); + WordBuilder> wb = new WordBuilder<>(); // 1. Pick a random entry config prefix: Word> prefix = prefixes.get(this.random.nextInt(prefixes.size())); - wbTestWord.append(prefix); + wb.append(prefix); // 2. Add random middle part: int size = minSize; while (size > 0 || this.random.nextDouble() > 1 / (this.rndLen + 1.0)) { TimedInput nextSymbol = alphabet.get(this.random.nextInt(alphabet.size())); - wbTestWord.append(nextSymbol); + wb.append(nextSymbol); if (size > 0) { size--; @@ -146,7 +147,7 @@ private DefaultQuery, Word>> generateTestwor } } else { // Identify configuration reached by prefix: - State currentConfig = hypothesis.getSemantics().getState(wbTestWord.toWord()); + State currentConfig = hypothesis.getSemantics().getState(wb); assert currentConfig != null; Integer state = hypSemModel.getStateForConfiguration(currentConfig, true); List>> localSuffixes = Automata.stateCharacterizingSet(hypSemModel, alphabet, state); @@ -155,11 +156,9 @@ private DefaultQuery, Word>> generateTestwor suffix = localSuffixes.get(random.nextInt(localSuffixes.size())); } } - wbTestWord.append(suffix); - // Query SUL: - Word> testWord = wbTestWord.toWord(); - Word> sulAnswer = timeOracle.answerQuery(testWord); - return new DefaultQuery<>(testWord, sulAnswer); + wb.append(suffix); + + return wb.toWord(); } } diff --git a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracleTest.java b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracleTest.java new file mode 100644 index 000000000..c2bd7f455 --- /dev/null +++ b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/RandomWpMethodEQOracleTest.java @@ -0,0 +1,88 @@ +/* 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 de.learnlib.oracle.equivalence.mmlt; + +import de.learnlib.driver.simulator.MMLTSimulatorSUL; +import de.learnlib.oracle.membership.TimedSULOracle; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import de.learnlib.time.MMLTModelParams; +import net.automatalib.alphabet.impl.Alphabets; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class RandomWpMethodEQOracleTest { + + @Test + public void testEquivalence() { + var example = MMLTExamples.sensorCollector(); + var mmlt = example.getReferenceAutomaton(); + var alphabet = example.getAlphabet(); + + var mqo = new TimedSULOracle<>(new MMLTSimulatorSUL<>(mmlt), example.getParams()); + var eqo = new RandomWpMethodEQOracle<>(mqo, 123, 10, 0, 100); + var cex = eqo.findCounterExample(mmlt, alphabet); + + Assert.assertNull(cex); + } + + @Test + public void testInequivalence() { + var mmlt = buildMMLT(); + var alphabet = mmlt.getSemantics().getInputAlphabet(); + var params = new MMLTModelParams<>("void", StringSymbolCombiner.getInstance(), 4, 80); + + var hyp = buildMMLT(); + var t = hyp.getTransition(1, "abort"); + hyp.setTransitionOutput(t, "part"); + + var mqo = new TimedSULOracle<>(new MMLTSimulatorSUL<>(mmlt), params); + var eqo = new RandomWpMethodEQOracle<>(mqo, 42, 0, 2, 100); + var cex = eqo.findCounterExample(hyp, alphabet); + + Assert.assertNotNull(cex); + Assert.assertEquals(cex.getOutput(), mmlt.getSemantics().computeSuffixOutput(cex.getPrefix(), cex.getSuffix())); + Assert.assertNotEquals(mmlt.getSemantics().computeSuffixOutput(cex.getPrefix(), cex.getSuffix()), + hyp.getSemantics().computeSuffixOutput(cex.getPrefix(), cex.getSuffix())); + } + + private static CompactMMLT buildMMLT() { + var alphabet = Alphabets.fromArray("p1", "p2", "abort", "collect"); + var model = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + + var s0 = model.addInitialState(); + var s1 = model.addState(); + var s2 = model.addState(); + var s3 = model.addState(); + + model.addTransition(s0, "p1", s1, "go"); + model.addTransition(s1, "abort", s1, "ok"); + model.addLocalReset(s1, "abort"); + + model.addPeriodicTimer(s1, "a", 3, "part"); + model.addPeriodicTimer(s1, "b", 6, "noise"); + model.addOneShotTimer(s1, "c", 40, "done", s3); + + model.addTransition(s0, "p2", s2, "go"); + model.addTransition(s2, "abort", s3, "void"); + model.addOneShotTimer(s2, "d", 4, "done", s3); + + model.addTransition(s3, "collect", s0, "void"); + + return model; + } +} diff --git a/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracleTest.java b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracleTest.java new file mode 100644 index 000000000..0e87e9b42 --- /dev/null +++ b/oracles/equivalence-oracles/src/test/java/de/learnlib/oracle/equivalence/mmlt/ResetSearchEQOracleTest.java @@ -0,0 +1,87 @@ +/* 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 de.learnlib.oracle.equivalence.mmlt; + +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.testsupport.example.mmlt.MMLTExamples; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.symbol.time.TimedInput; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class ResetSearchEQOracleTest { + + @Test + public void testInsertPercentages() { + @SuppressWarnings("unchecked") + TimedQueryOracle mock = Mockito.mock(TimedQueryOracle.class); + + var example = MMLTExamples.sensorCollector(); + var mmlt = example.getReferenceAutomaton(); + var alphabet = example.getAlphabet(); + + var eqo = new ResetSearchEQOracle<>(mock, 123, 0, 1); + var cex = eqo.findCounterExample(mmlt, alphabet); + + Assert.assertNull(cex); + Mockito.verifyNoInteractions(mock); + + eqo = new ResetSearchEQOracle<>(mock, 123, 1, 0); + cex = eqo.findCounterExample(mmlt, alphabet); + + Assert.assertNull(cex); + Mockito.verifyNoInteractions(mock); + } + + @Test + public void testAlphabetFilter() { + @SuppressWarnings("unchecked") + TimedQueryOracle mock = Mockito.spy(TimedQueryOracle.class); + + var example = MMLTExamples.sensorCollector(); + var mmlt = example.getReferenceAutomaton(); + var alphabet = example.getUntimedAlphabet().stream().>map(TimedInput::input).toList(); + + var eqo = new ResetSearchEQOracle<>(mock, 123, 1, 1); + var cex = eqo.findCounterExample(mmlt, alphabet); + + Assert.assertNull(cex); + Mockito.verifyNoInteractions(mock); + + var alphabetWithTimeOut = new GrowingMapAlphabet<>(alphabet); + alphabetWithTimeOut.add(TimedInput.timeout()); + cex = eqo.findCounterExample(mmlt, alphabetWithTimeOut); + + Assert.assertNull(cex); + Mockito.verifyNoInteractions(mock); + + var alphabetWithTimestep = new GrowingMapAlphabet<>(alphabet); + alphabetWithTimestep.add(TimedInput.step()); + cex = eqo.findCounterExample(mmlt, alphabetWithTimestep); + + Assert.assertNull(cex); + Mockito.verifyNoInteractions(mock); + + var validAlphabe = example.getAlphabet(); + cex = eqo.findCounterExample(mmlt, validAlphabe); + + // mock always returns null which differs from any non-null hypothesis output + Assert.assertNotNull(cex); + Mockito.verify(mock, Mockito.atLeastOnce()).processQueries(ArgumentMatchers.anyCollection()); + } +} diff --git a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java index a74317467..b3b5d4702 100644 --- a/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java +++ b/oracles/membership-oracles/src/main/java/de/learnlib/oracle/membership/TimedSULOracle.java @@ -16,16 +16,15 @@ package de.learnlib.oracle.membership; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; -import java.util.stream.Collectors; +import de.learnlib.oracle.SingleQueryOracle.SingleQueryOracleMMLT; import de.learnlib.oracle.TimedQueryOracle; -import de.learnlib.query.Query; import de.learnlib.sul.TimedSUL; import de.learnlib.time.MMLTModelParams; import net.automatalib.automaton.mmlt.TimerInfo; @@ -48,7 +47,7 @@ * @param * output symbol type */ -public class TimedSULOracle implements TimedQueryOracle { +public class TimedSULOracle implements SingleQueryOracleMMLT { private static final Logger LOGGER = LoggerFactory.getLogger(TimedSULOracle.class); @@ -67,10 +66,43 @@ public TimedSULOracle(TimedSUL sul, MMLTModelParams modelParams) { } @Override - public void processQueries(Collection, Word>>> queries) { - for (Query, Word>> q : queries) { - this.querySuffixOutputInternal(q); + public Word> answerQuery(Word> prefix, Word> suffix) { + sul.pre(); + sul.follow(prefix, this.modelParams.maxTimeoutWaitingTime()); + + // Query the SUL, one symbol at a time: + WordBuilder> wbOutput = new WordBuilder<>(); + for (TimedInput s : suffix) { + if (s instanceof TimeoutSymbol) { + TimedOutput output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); + if (output != null) { + wbOutput.append(output); + } else { + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent + } + } else if (s instanceof InputSymbol ndi) { + TimedOutput output = sul.step(ndi); + wbOutput.append(output); + } else if (s instanceof TimeStepSequence ws) { + if (ws.timeSteps() > 1) { + throw new IllegalArgumentException("Only single wait step allowed in suffix."); + } + + // Wait for a single time step: + TimedOutput output = sul.timeStep(); + if (output != null) { + wbOutput.append(output); + } else { + wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent + } + + } else { + throw new IllegalArgumentException("Only timeout or untimed symbols allowed in suffix."); + } } + + sul.post(); + return wbOutput.toWord(); } @Override @@ -115,7 +147,7 @@ private long calcNextExpectedTimeout(List> timeouts, long curren return minNext; } - private String getUniqueTimerName() { + private String newUniqueTimerName() { return "t_" + (++this.timerCounter); } @@ -149,7 +181,7 @@ private TimerQueryResult collectTimeouts(long maxTotalWaitingTime) { LOGGER.warn("Multiple timers expiring at first timeout, automaton may not be minimal."); } - knownTimers.add(new TimerInfo<>(getUniqueTimerName(), firstTimeout.delay(), firstTimeoutOutputs, null, true)); + knownTimers.add(new TimerInfo<>(newUniqueTimerName(), firstTimeout.delay(), firstTimeoutOutputs, null, true)); // Wait for further timeouts: long currentTimeStep = firstTimeout.delay(); // already waited for first timeout @@ -202,29 +234,31 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, if (nextActualTime < nextExpectedTime) { // A timeout occurred before we expected one -> new timer: TimerInfo newTimer = - new TimerInfo<>(getUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); + new TimerInfo<>(newUniqueTimerName(), nextActualTime, nextOutputSymbols, null, true); return new TimerCheckResult<>(newTimer, false); } else { assert nextActualTime == nextExpectedTime; // Timeout occurred at expected time -> check if matching expected output: - Map expectedOutputs = knownTimers.stream() - .filter(t -> nextExpectedTime % t.initial() == 0) - .map(TimerInfo::outputs) // outputs of timers with same initial value - .flatMap(Collection::stream) - .collect(Collectors.groupingBy(t -> t, - Collectors.counting())); // count occurrences + Map expectedOutputs = new HashMap<>(); + for (TimerInfo t : knownTimers) { + if (nextExpectedTime % t.initial() == 0) { + for (O o : t.outputs()) { + expectedOutputs.merge(o, 1L, Long::sum); + } + } + } - Map actualOutputs = - nextOutputSymbols.stream().collect(Collectors.groupingBy(e -> e, Collectors.counting())); + Map actualOutputs = new HashMap<>(); + for (O o : nextOutputSymbols) { + actualOutputs.merge(o, 1L, Long::sum); + } // Any outputs that were expected but are not present? - boolean missingOutputs = expectedOutputs.keySet() - .stream() - .anyMatch(o -> actualOutputs.getOrDefault(o, 0L) < - expectedOutputs.get(o)); // less than expected - if (missingOutputs) { - // Same time but missing output -> missed location change: - return new TimerCheckResult<>(null, true); + for (Entry e : expectedOutputs.entrySet()) { + if (actualOutputs.getOrDefault(e.getKey(), 0L) < e.getValue()) { + // Same time but missing output -> missed location change: + return new TimerCheckResult<>(null, true); + } } // At least all expected outputs are present. @@ -244,7 +278,7 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, if (!newOutputs.isEmpty()) { // Same time and more outputs -> add new timer that uses the new outputs: TimerInfo newTimer = - new TimerInfo<>(getUniqueTimerName(), nextActualTime, newOutputs, null, true); + new TimerInfo<>(newUniqueTimerName(), nextActualTime, newOutputs, null, true); return new TimerCheckResult<>(newTimer, false); } } @@ -252,45 +286,5 @@ private TimerCheckResult evaluateNextTimer(long nextActualTime, return new TimerCheckResult<>(null, false); } - private void querySuffixOutputInternal(Query, Word>> query) { - - sul.pre(); - sul.follow(query.getPrefix(), this.modelParams.maxTimeoutWaitingTime()); - - // Query the SUL, one symbol at a time: - WordBuilder> wbOutput = new WordBuilder<>(); - for (TimedInput s : query.getSuffix()) { - if (s instanceof TimeoutSymbol) { - TimedOutput output = sul.timeoutStep(this.modelParams.maxTimeoutWaitingTime()); - if (output != null) { - wbOutput.append(output); - } else { - wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent - } - } else if (s instanceof InputSymbol ndi) { - TimedOutput output = sul.step(ndi); - wbOutput.append(output); - } else if (s instanceof TimeStepSequence ws) { - if (ws.timeSteps() > 1) { - throw new IllegalArgumentException("Only single wait step allowed in suffix."); - } - - // Wait for a single time step: - TimedOutput output = sul.timeStep(); - if (output != null) { - wbOutput.append(output); - } else { - wbOutput.append(new TimedOutput<>(this.modelParams.silentOutput())); // no output in time -> silent - } - - } else { - throw new IllegalArgumentException("Only timeout or untimed symbols allowed in suffix."); - } - } - - sul.post(); - query.answer(wbOutput.toWord()); - } - private record TimerCheckResult(@Nullable TimerInfo newTimer, boolean inconsistent) {} } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedOracleQueryBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedOracleQueryBuilder.java new file mode 100644 index 000000000..9a0fdba89 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedOracleQueryBuilder.java @@ -0,0 +1,53 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; + +/** + * A specialized {@link AbstractDynamicBatchProcessorBuilder} for {@link TimedQueryOracle}s. + * + * @param + * input symbol type + * @param + * output symbol type + */ +public class DynamicParallelTimedOracleQueryBuilder + extends AbstractDynamicBatchProcessorBuilder, Word>>, TimedQueryOracle, DynamicParallelTimedQueryOracle> { + + public DynamicParallelTimedOracleQueryBuilder(Supplier> oracleSupplier) { + super(oracleSupplier); + } + + public DynamicParallelTimedOracleQueryBuilder(Collection> oracles) { + super(oracles); + } + + @Override + protected DynamicParallelTimedQueryOracle buildOracle(Supplier> supplier, + int minBatchSize, + ExecutorService executor) { + return new DynamicParallelTimedQueryOracle<>(supplier, minBatchSize, executor); + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracle.java new file mode 100644 index 000000000..14c5cd1c1 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracle.java @@ -0,0 +1,58 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import de.learnlib.oracle.ParallelTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.checkerframework.checker.index.qual.NonNegative; + +/** + * A specialized {@link AbstractDynamicBatchProcessor} for {@link TimedQueryOracle}s that implements + * {@link ParallelTimedQueryOracle}. + * + * @param + * input symbol type + * @param + * output symbol type + */ +public class DynamicParallelTimedQueryOracle + extends AbstractDynamicBatchProcessor, Word>>, TimedQueryOracle> + implements ParallelTimedQueryOracle { + + public DynamicParallelTimedQueryOracle(Supplier> oracleSupplier, + @NonNegative int minBatchSize, + ExecutorService executorService) { + super(oracleSupplier, minBatchSize, executorService); + } + + @Override + public void processQueries(Collection, Word>>> queries) { + processBatch(queries); + } + + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + return getProcessor().queryTimers(prefix, maxTotalWaitingTime); + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java index 328267304..91eb4822a 100644 --- a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/ParallelOracleBuilders.java @@ -22,13 +22,17 @@ import de.learnlib.oracle.MembershipOracle; import de.learnlib.oracle.OmegaMembershipOracle; import de.learnlib.oracle.ThreadPool.PoolPolicy; +import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.oracle.membership.AbstractSULOmegaOracle; import de.learnlib.oracle.membership.SULAdaptiveOracle; import de.learnlib.oracle.membership.SULOracle; import de.learnlib.oracle.membership.StateLocalInputSULOracle; +import de.learnlib.oracle.membership.TimedSULOracle; import de.learnlib.sul.ObservableSUL; import de.learnlib.sul.SUL; import de.learnlib.sul.StateLocalInputSUL; +import de.learnlib.sul.TimedSUL; +import de.learnlib.time.MMLTModelParams; import net.automatalib.common.util.collection.CollectionUtil; import net.automatalib.word.Word; @@ -320,6 +324,83 @@ public static DynamicParallelAdaptiveOracleBuilder newDynamicParall return new DynamicParallelAdaptiveOracleBuilder<>(oracles); } + /** + * Creates a {@link DynamicParallelTimedOracleQueryBuilder} using the provided {@code sul} as a supplier. This + * requires that the sul is {@link SUL#canFork() forkable}. + * + * @param sul + * the sul instance for spawning new thread-specific oracle instances + * @param params + * additional parameters for answering queries + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + public static DynamicParallelTimedOracleQueryBuilder newDynamicParallelTimedQueryOracle(TimedSUL sul, + MMLTModelParams params) { + checkFork(sul); + return newDynamicParallelTimedQueryOracle(toSupplier(sul, params)); + } + + /** + * Creates a {@link DynamicParallelTimedOracleQueryBuilder} using the provided supplier. + * + * @param oracleSupplier + * the supplier for spawning new thread-specific oracle instances + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + public static DynamicParallelTimedOracleQueryBuilder newDynamicParallelTimedQueryOracle(Supplier> oracleSupplier) { + return new DynamicParallelTimedOracleQueryBuilder<>(oracleSupplier); + } + + /** + * Convenience method for {@link #newDynamicParallelTimedQueryOracle(Collection)}. + * + * @param firstOracle + * the first (mandatory) oracle + * @param otherOracles + * further (optional) oracles to be used by other threads + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + @SafeVarargs + public static DynamicParallelTimedOracleQueryBuilder newDynamicParallelTimedQueryOracle( + TimedQueryOracle firstOracle, + TimedQueryOracle... otherOracles) { + return newDynamicParallelTimedQueryOracle(CollectionUtil.list(firstOracle, otherOracles)); + } + + /** + * Creates a {@link DynamicParallelTimedOracleQueryBuilder} using the provided collection of membership oracles. The + * resulting parallel oracle will always use a {@link PoolPolicy#FIXED} pool policy and spawn a separate thread for + * each of the provided oracles (so that the oracles do not need to care about synchronization if they don't share + * state). + * + * @param oracles + * the oracle instances to distribute the queries to + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return the preconfigured oracle builder + */ + public static DynamicParallelTimedOracleQueryBuilder newDynamicParallelTimedQueryOracle(Collection> oracles) { + return new DynamicParallelTimedOracleQueryBuilder<>(oracles); + } + /** * Creates a {@link StaticParallelOracleBuilder} using the provided {@code sul} as a supplier. This requires that * the sul is {@link SUL#canFork() forkable}. @@ -569,6 +650,82 @@ public static StaticParallelAdaptiveOracleBuilder newStaticParallel return new StaticParallelAdaptiveOracleBuilder<>(oracles); } + /** + * Creates a {@link StaticParallelTimedQueryOracleBuilder} using the provided {@code sul} as a supplier. This + * requires that the sul is {@link SUL#canFork() forkable}. + * + * @param sul + * the sul instance for spawning new thread-specific oracle instances + * @param params + * additional parameters for answering queries + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + public static StaticParallelTimedQueryOracleBuilder newStaticParallelTimedQueryOracle(TimedSUL sul, + MMLTModelParams params) { + checkFork(sul); + return newStaticParallelTimedQueryOracle(toSupplier(sul, params)); + } + + /** + * Creates a {@link StaticParallelTimedQueryOracleBuilder} using the provided supplier. + * + * @param oracleSupplier + * the supplier for spawning new thread-specific oracle instances + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + public static StaticParallelTimedQueryOracleBuilder newStaticParallelTimedQueryOracle(Supplier> oracleSupplier) { + return new StaticParallelTimedQueryOracleBuilder<>(oracleSupplier); + } + + /** + * Convenience method for {@link #newStaticParallelTimedQueryOracle(Collection)}. + * + * @param firstOracle + * the first (mandatory) oracle + * @param otherOracles + * further (optional) oracles to be used by other threads + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return a preconfigured oracle builder + */ + @SafeVarargs + public static StaticParallelTimedQueryOracleBuilder newStaticParallelTimedQueryOracle(TimedQueryOracle firstOracle, + TimedQueryOracle... otherOracles) { + return newStaticParallelTimedQueryOracle(CollectionUtil.list(firstOracle, otherOracles)); + } + + /** + * Creates a {@link StaticParallelTimedQueryOracleBuilder} using the provided collection of membership oracles. The + * resulting parallel oracle will always use a {@link PoolPolicy#FIXED} pool policy and spawn a separate thread for + * each of the provided oracles (so that the oracles do not need to care about synchronization if they don't share + * state). + * + * @param oracles + * the oracle instances to distribute the queries to + * @param + * input symbol type (of non-delaying inputs) + * @param + * output symbol type + * + * @return the preconfigured oracle builder + */ + public static StaticParallelTimedQueryOracleBuilder newStaticParallelTimedQueryOracle(Collection> oracles) { + return new StaticParallelTimedQueryOracleBuilder<>(oracles); + } + private static Supplier> toSupplier(SUL sul) { return () -> new SULOracle<>(sul.fork()); } @@ -582,6 +739,10 @@ private static Supplier>> toSuppli return () -> AbstractSULOmegaOracle.newOracle(sul.fork()); } + private static Supplier> toSupplier(TimedSUL sul, MMLTModelParams params) { + return () -> new TimedSULOracle<>(sul.fork(), params); + } + private static Supplier> toAdaptiveSupplier(SUL sul) { return () -> new SULAdaptiveOracle<>(sul.fork()); } diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracle.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracle.java new file mode 100644 index 000000000..ca465bb82 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracle.java @@ -0,0 +1,57 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; + +import de.learnlib.oracle.ParallelTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.checkerframework.checker.index.qual.NonNegative; + +/** + * A specialized {@link AbstractStaticBatchProcessor} for {@link TimedQueryOracle}s that implements + * {@link ParallelTimedQueryOracle}. + * + * @param + * input symbol type + * @param + * output symbol type + */ +public class StaticParallelTimedQueryOracle + extends AbstractStaticBatchProcessor, Word>>, TimedQueryOracle> + implements ParallelTimedQueryOracle { + + public StaticParallelTimedQueryOracle(Collection> oracles, + @NonNegative int minBatchSize, + ExecutorService executorService) { + super(oracles, minBatchSize, executorService); + } + + @Override + public void processQueries(Collection, Word>>> queries) { + processBatch(queries); + } + + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + return getProcessor().queryTimers(prefix, maxTotalWaitingTime); + } +} diff --git a/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleBuilder.java b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleBuilder.java new file mode 100644 index 000000000..75fcfa7c0 --- /dev/null +++ b/oracles/parallelism/src/main/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleBuilder.java @@ -0,0 +1,53 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Collection; +import java.util.concurrent.ExecutorService; +import java.util.function.Supplier; + +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; + +/** + * A specialized {@link AbstractStaticBatchProcessorBuilder} for {@link TimedQueryOracle}s. + * + * @param + * input symbol type + * @param + * output symbol type + */ +public class StaticParallelTimedQueryOracleBuilder + extends AbstractStaticBatchProcessorBuilder, Word>>, TimedQueryOracle, StaticParallelTimedQueryOracle> { + + public StaticParallelTimedQueryOracleBuilder(Supplier> oracleSupplier) { + super(oracleSupplier); + } + + public StaticParallelTimedQueryOracleBuilder(Collection> oracles) { + super(oracles); + } + + @Override + protected StaticParallelTimedQueryOracle buildOracle(Collection> oracleInstances, + int minBatchSize, + ExecutorService executor) { + return new StaticParallelTimedQueryOracle<>(oracleInstances, minBatchSize, executor); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelTimedQueryOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelTimedQueryOracleTest.java new file mode 100644 index 000000000..d5f7125c1 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractDynamicParallelTimedQueryOracleTest.java @@ -0,0 +1,163 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +import de.learnlib.oracle.ParallelTimedQueryOracle; +import de.learnlib.oracle.ThreadPool.PoolPolicy; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.query.Query; +import de.learnlib.sul.TimedSUL; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.testng.Assert; +import org.testng.annotations.Test; + +@Test +public abstract class AbstractDynamicParallelTimedQueryOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testEmpty(PoolPolicy poolPolicy) { + ParallelTimedQueryOracle oracle = getBuilder().withPoolPolicy(poolPolicy).create(); + + try { + oracle.processQueries(Collections.emptyList()); + } finally { + oracle.shutdownNow(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testDistinctQueries(PoolPolicy poolPolicy) { + ParallelTimedQueryOracle oracle = + getBuilder().withBatchSize(1).withPoolSize(4).withPoolPolicy(poolPolicy).create(); + + try { + List> queries = createQueries(100); + + oracle.processQueries(queries); + + for (AnswerOnceQuery query : queries) { + Assert.assertTrue(query.answered.get()); + } + } finally { + oracle.shutdown(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, expectedExceptions = IllegalStateException.class) + public void testDuplicateQueries(PoolPolicy poolPolicy) { + ParallelTimedQueryOracle oracle = + getBuilder().withBatchSize(1).withPoolSize(4).withPoolPolicy(poolPolicy).create(); + try { + List> queries = new ArrayList<>(createQueries(100)); + queries.add(queries.get(0)); + + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + protected abstract DynamicParallelTimedOracleQueryBuilder getBuilder(); + + protected static List> createQueries(int numQueries) { + List> queries = new ArrayList<>(numQueries); + + for (int i = 0; i < numQueries; i++) { + queries.add(new AnswerOnceQuery<>()); + } + + return queries; + } + + static class NullSUL implements TimedSUL { + + @Override + public void pre() {} + + @Override + public void post() {} + + @Override + public TimedOutput step(InputSymbol in) { + return new TimedOutput<>(null); + } + + @Override + public @Nullable TimedOutput timeoutStep(long maxTime) { + return null; + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public TimedSUL fork() { + return new NullSUL(); + } + } + + static class NullOracle implements TimedQueryOracle { + + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + return new TimerQueryResult<>(false, Collections.emptyList()); + } + + @Override + public void processQueries(Collection, Word>>> queries) { + for (Query q : queries) { + q.answer(null); + } + + } + } + + static final class AnswerOnceQuery extends Query, Word>> { + + private final AtomicBoolean answered = new AtomicBoolean(false); + + @Override + public void answer(Word> output) { + boolean wasAnswered = answered.getAndSet(true); + if (wasAnswered) { + throw new IllegalStateException("Query was already answered"); + } + } + + @Override + public Word> getPrefix() { + return Word.epsilon(); + } + + @Override + public Word> getSuffix() { + return Word.epsilon(); + } + } + +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelTimedQueryOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelTimedQueryOracleTest.java new file mode 100644 index 000000000..63a72459a --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/AbstractStaticParallelTimedQueryOracleTest.java @@ -0,0 +1,292 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.oracle.ThreadPool.PoolPolicy; +import de.learnlib.oracle.TimedQueryOracle; +import de.learnlib.oracle.parallelism.Utils.Analysis; +import de.learnlib.query.DefaultQuery; +import de.learnlib.query.Query; +import de.learnlib.sul.TimedSUL; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.testng.Assert; +import org.testng.annotations.Test; + +public abstract class AbstractStaticParallelTimedQueryOracleTest { + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testZeroQueries(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + oracle.processQueries(Collections.emptyList()); + Analysis ana = analyze(Collections.emptyList()); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 0); + oracle.shutdownNow(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanMin(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + List, Word>>> queries = createQueries(Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testMin(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + List, Word>>> queries = createQueries(Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanTwoBatches(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + List, Word>>> queries = + createQueries(2 * Utils.MIN_BATCH_SIZE - 1); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 1); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testLessThanSixBatches(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + List, Word>>> queries = + createQueries(5 * Utils.MIN_BATCH_SIZE + Utils.MIN_BATCH_SIZE / 2); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), 5); + oracle.shutdown(); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class) + public void testFullLoad(PoolPolicy policy) { + StaticParallelTimedQueryOracle oracle = getOracle(policy); + List, Word>>> queries = + createQueries(2 * Utils.NUM_ORACLES * Utils.MIN_BATCH_SIZE); + oracle.processQueries(queries); + Analysis ana = analyze(queries); + Utils.sanityCheck(ana); + Assert.assertEquals(ana.involvedOracles.size(), Utils.NUM_ORACLES); + oracle.shutdown(); + } + + protected abstract StaticParallelTimedQueryOracleBuilder getBuilder(); + + protected abstract TestOutput extractTestOutput(Word> output); + + protected TestMembershipOracle[] getOracles() { + TestMembershipOracle[] oracles = new TestMembershipOracle[Utils.NUM_ORACLES]; + for (int i = 0; i < Utils.NUM_ORACLES; i++) { + oracles[i] = new TestMembershipOracle(i); + } + + return oracles; + } + + private StaticParallelTimedQueryOracle getOracle(PoolPolicy poolPolicy) { + return getBuilder().withMinBatchSize(Utils.MIN_BATCH_SIZE) + .withNumInstances(Utils.NUM_ORACLES) + .withPoolPolicy(poolPolicy) + .create(); + } + + protected int getMinQueryLength() { + return 0; + } + + private List, Word>>> createQueries(int num) { + List, Word>>> result = new ArrayList<>(num); + for (int i = 0; i < num; i++) { + Word> prefix = + Utils.createWord(getMinQueryLength()).stream().map(TimedInput::input).collect(Word.collector()); + Word> suffix = + Utils.createWord(getMinQueryLength()).stream().map(TimedInput::input).collect(Word.collector()); + result.add(new DefaultQuery<>(prefix, suffix)); + } + return result; + } + + private Analysis analyze(Collection, Word>>> queries) { + List oracles = new ArrayList<>(); + Map> seqIds = new HashMap<>(); + Map incorrectAnswers = new HashMap<>(); + + for (DefaultQuery, Word>> qry : queries) { + TestOutput out = extractTestOutput(qry.getOutput()); + Assert.assertNotNull(out); + int oracleId = out.oracleId; + List seqIdList = seqIds.get(oracleId); + if (seqIdList == null) { + oracles.add(oracleId); + seqIdList = new ArrayList<>(); + seqIds.put(oracleId, seqIdList); + incorrectAnswers.put(oracleId, 0); + } + + int seqId = out.batchSeqId; + seqIdList.add(seqId); + + if (!qry.getPrefix().equals(out.prefix) || !qry.getSuffix().equals(out.suffix)) { + incorrectAnswers.put(oracleId, incorrectAnswers.get(oracleId) + 1); + } + } + + int minBatchSize = -1; + int maxBatchSize = -1; + for (List batch : seqIds.values()) { + if (minBatchSize == -1) { + maxBatchSize = batch.size(); + minBatchSize = maxBatchSize; + } else { + if (batch.size() < minBatchSize) { + minBatchSize = batch.size(); + } + if (batch.size() > maxBatchSize) { + maxBatchSize = batch.size(); + } + } + } + + return new Analysis(oracles, seqIds, incorrectAnswers, minBatchSize, maxBatchSize); + } + + static final class TestOutput { + + public final int oracleId; + public final int batchSeqId; + public final Word> prefix; + public final Word> suffix; + + TestOutput(int oracleId, int batchSeqId, Word> prefix, Word> suffix) { + this.oracleId = oracleId; + this.batchSeqId = batchSeqId; + this.prefix = prefix; + this.suffix = suffix; + } + } + + static final class TestSULOutput { + + final int oracleId; + final int batchSeqId; + final Word> word; + + TestSULOutput(int oracleId, int batchSeqId, Word> word) { + this.oracleId = oracleId; + this.batchSeqId = batchSeqId; + this.word = word; + } + } + + static final class TestMembershipOracle implements TimedQueryOracle { + + private final int oracleId; + + TestMembershipOracle(int oracleId) { + this.oracleId = oracleId; + } + + @Override + public void processQueries(Collection, Word>>> queries) { + int batchSeqId = 0; + for (Query, Word>> qry : queries) { + qry.answer(Word.fromLetter(new TimedOutput<>(new TestOutput(oracleId, + batchSeqId++, + qry.getPrefix(), + qry.getSuffix())))); + } + } + + @Override + public TimerQueryResult queryTimers(Word> prefix, long maxTotalWaitingTime) { + return new TimerQueryResult<>(false, Collections.emptyList()); + } + } + + static final class TestSUL implements TimedSUL { + + private final AtomicInteger atomicInteger; + private final int oracleId; + private int batchSeqId; + + private final WordBuilder> wb; + + TestSUL(AtomicInteger atomicInteger) { + this.atomicInteger = atomicInteger; + this.oracleId = atomicInteger.getAndIncrement(); + this.batchSeqId = -1; // so that our first query starts at 0 + + this.wb = new WordBuilder<>(); + } + + @Override + public void pre() { + batchSeqId++; + } + + @Override + public void post() { + this.wb.clear(); + } + + @Override + public TimedOutput step(InputSymbol in) { + this.wb.append(in); + return new TimedOutput<>(new TestSULOutput(oracleId, batchSeqId, wb.toWord())); + } + + @Override + public @Nullable TimedOutput timeoutStep(long maxTime) { + return null; + } + + @Override + public boolean canFork() { + return true; + } + + @Override + public TestSUL fork() { + return new TestSUL(this.atomicInteger); + } + } + +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracleTest.java new file mode 100644 index 000000000..1a27adb06 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedQueryOracleTest.java @@ -0,0 +1,133 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +import de.learnlib.oracle.ParallelTimedQueryOracle; +import de.learnlib.oracle.ThreadPool.PoolPolicy; +import de.learnlib.oracle.TimedQueryOracle.TimerQueryResult; +import de.learnlib.query.Query; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class DynamicParallelTimedQueryOracleTest extends AbstractDynamicParallelTimedQueryOracleTest { + + @Override + protected DynamicParallelTimedOracleQueryBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelTimedQueryOracle(Arrays.asList(new NullOracle(), + new NullOracle(), + new NullOracle())); + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadCreation(PoolPolicy poolPolicy) { + + final List> queries = createQueries(10); + final int expectedThreads = queries.size(); + + final CountDownLatch latch = new CountDownLatch(expectedThreads); + final NullOracle[] oracles = new NullOracle[expectedThreads]; + + for (int i = 0; i < expectedThreads; i++) { + oracles[i] = new NullOracle() { + + @Override + public void processQueries(Collection, Word>>> queries) { + try { + latch.countDown(); + latch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + super.processQueries(queries); + } + }; + } + + final ParallelTimedQueryOracle oracle = ParallelOracleBuilders.newDynamicParallelTimedQueryOracle( + oracles[0], + Arrays.copyOfRange(oracles, 1, oracles.length)) + .withBatchSize(1) + .withPoolSize(oracles.length) + .withPoolPolicy(poolPolicy) + .create(); + + try { + // this method only returns, if 'expectedThreads' threads are spawned, which all decrease the shared latch + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + @Test(dataProvider = "policies", dataProviderClass = Utils.class, timeOut = 2000) + public void testThreadScheduling(PoolPolicy poolPolicy) { + + final List> queries = createQueries(10); + final CountDownLatch latch = new CountDownLatch(queries.size() - 1); + + final NullOracle awaitingOracle = new NullOracle() { + + @Override + public void processQueries(Collection, Word>>> queries) { + try { + latch.await(); + } catch (InterruptedException e) { + throw new IllegalStateException(e); + } + super.processQueries(queries); + } + }; + + final NullOracle countDownOracle = new NullOracle() { + + @Override + public void processQueries(Collection, Word>>> queries) { + latch.countDown(); + super.processQueries(queries); + } + }; + + final ParallelTimedQueryOracle oracle = + ParallelOracleBuilders.newDynamicParallelTimedQueryOracle(awaitingOracle, countDownOracle) + .withPoolSize(2) + .withPoolPolicy(poolPolicy) + .create(); + + try { + // this method only returns, if the countDownOracle was scheduled 9 times to unblock the awaitingOracle + oracle.processQueries(queries); + } finally { + oracle.shutdown(); + } + } + + @Test + public void testSingleMethods() { + final ParallelTimedQueryOracle oracle = getBuilder().create(); + + TimerQueryResult timer = oracle.queryTimers(Word.epsilon(), 0); + Assert.assertFalse(timer.aborted()); + Assert.assertTrue(timer.timers().isEmpty()); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSULTest.java new file mode 100644 index 000000000..036b8798b --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSULTest.java @@ -0,0 +1,27 @@ +/* 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 de.learnlib.oracle.parallelism; + +import de.learnlib.time.MMLTModelParams; + +public class DynamicParallelTimedSULTest extends AbstractDynamicParallelTimedQueryOracleTest { + + @Override + protected DynamicParallelTimedOracleQueryBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelTimedQueryOracle(new NullSUL(), + new MMLTModelParams<>(null, null, 0, 0)); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSupplierTest.java new file mode 100644 index 000000000..882b8b78c --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/DynamicParallelTimedSupplierTest.java @@ -0,0 +1,24 @@ +/* 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 de.learnlib.oracle.parallelism; + +public class DynamicParallelTimedSupplierTest extends AbstractDynamicParallelTimedQueryOracleTest { + + @Override + protected DynamicParallelTimedOracleQueryBuilder getBuilder() { + return ParallelOracleBuilders.newDynamicParallelTimedQueryOracle(NullOracle::new); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java index 34972d28e..7734cd52f 100644 --- a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelObservableSULTest.java @@ -19,6 +19,7 @@ import de.learnlib.oracle.parallelism.Utils.TestSULOutput; import net.automatalib.word.Word; +import org.testng.Assert; public class StaticParallelObservableSULTest extends AbstractStaticParallelOmegaOracleTest> { @@ -30,7 +31,7 @@ protected StaticParallelOmegaOracleBuilder> getB @Override protected TestOutput extractTestOutput(Word output) { - assert !output.isEmpty(); + Assert.assertFalse(output.isEmpty()); final TestSULOutput lastSym = output.lastSymbol(); final int oracleId = lastSym.oracleId; diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleTest.java new file mode 100644 index 000000000..55baf1a1a --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedQueryOracleTest.java @@ -0,0 +1,53 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.Arrays; + +import de.learnlib.oracle.ParallelTimedQueryOracle; +import de.learnlib.oracle.TimedQueryOracle.TimerQueryResult; +import de.learnlib.oracle.parallelism.AbstractStaticParallelTimedQueryOracleTest.TestOutput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.testng.Assert; +import org.testng.annotations.Test; + +public class StaticParallelTimedQueryOracleTest extends AbstractStaticParallelTimedQueryOracleTest { + + @Override + protected StaticParallelTimedQueryOracleBuilder getBuilder() { + TestMembershipOracle[] oracles = getOracles(); + return ParallelOracleBuilders.newStaticParallelTimedQueryOracle(oracles[0], + Arrays.copyOfRange(oracles, 1, oracles.length)); + } + + @Override + protected TestOutput extractTestOutput(Word> output) { + Assert.assertFalse(output.isEmpty()); + + final TestOutput lastSym = output.lastSymbol().symbol(); + return new TestOutput(lastSym.oracleId, lastSym.batchSeqId, lastSym.prefix, lastSym.suffix); + } + + @Test + public void testSingleMethods() { + final ParallelTimedQueryOracle oracle = getBuilder().create(); + + TimerQueryResult timer = oracle.queryTimers(Word.epsilon(), 0); + Assert.assertFalse(timer.aborted()); + Assert.assertTrue(timer.timers().isEmpty()); + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSULTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSULTest.java new file mode 100644 index 000000000..915cff221 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSULTest.java @@ -0,0 +1,55 @@ +/* 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 de.learnlib.oracle.parallelism; + +import java.util.concurrent.atomic.AtomicInteger; + +import de.learnlib.oracle.parallelism.AbstractStaticParallelTimedQueryOracleTest.TestSULOutput; +import de.learnlib.time.MMLTModelParams; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.testng.Assert; + +public class StaticParallelTimedSULTest extends AbstractStaticParallelTimedQueryOracleTest { + + @Override + protected StaticParallelTimedQueryOracleBuilder getBuilder() { + // since we fork our initial SUL, start at -1 + return ParallelOracleBuilders.newStaticParallelTimedQueryOracle(new TestSUL(new AtomicInteger(-1)), + new MMLTModelParams<>(null, null, 0, 0)); + } + + @Override + protected TestOutput extractTestOutput(Word> output) { + Assert.assertFalse(output.isEmpty()); + + final TestSULOutput lastSym = output.lastSymbol().symbol(); + final int oracleId = lastSym.oracleId; + final int batchSeqId = lastSym.batchSeqId; + + final Word> word = lastSym.word; + final Word> prefix = word.prefix(word.size() - output.size()); + final Word> suffix = word.subWord(word.size() - output.size()); + + return new TestOutput(oracleId, batchSeqId, prefix, suffix); + } + + @Override + protected int getMinQueryLength() { + return 1; + } +} diff --git a/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSupplierTest.java b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSupplierTest.java new file mode 100644 index 000000000..77fa871b4 --- /dev/null +++ b/oracles/parallelism/src/test/java/de/learnlib/oracle/parallelism/StaticParallelTimedSupplierTest.java @@ -0,0 +1,39 @@ +/* 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 de.learnlib.oracle.parallelism; + +import de.learnlib.oracle.parallelism.AbstractDynamicBatchProcessorBuilder.StaticOracleProvider; +import de.learnlib.oracle.parallelism.AbstractStaticParallelTimedQueryOracleTest.TestOutput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.word.Word; +import org.testng.Assert; + +public class StaticParallelTimedSupplierTest extends AbstractStaticParallelTimedQueryOracleTest { + + @Override + protected StaticParallelTimedQueryOracleBuilder getBuilder() { + TestMembershipOracle[] oracles = getOracles(); + return ParallelOracleBuilders.newStaticParallelTimedQueryOracle(new StaticOracleProvider<>(oracles)); + } + + @Override + protected TestOutput extractTestOutput(Word> output) { + Assert.assertFalse(output.isEmpty()); + + final TestOutput lastSym = output.lastSymbol().symbol(); + return new TestOutput(lastSym.oracleId, lastSym.batchSeqId, lastSym.prefix, lastSym.suffix); + } +} diff --git a/test-support/learning-examples/pom.xml b/test-support/learning-examples/pom.xml index a71c37641..ac5160f30 100644 --- a/test-support/learning-examples/pom.xml +++ b/test-support/learning-examples/pom.xml @@ -51,6 +51,10 @@ limitations under the License. net.automatalib automata-core + + net.automatalib + automata-serialization-dot + net.automatalib automata-serialization-learnlibv2 @@ -75,9 +79,5 @@ limitations under the License. org.testng testng - - net.automatalib - automata-serialization-dot - diff --git a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java index 9c5b9232e..c12d501a0 100644 --- a/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java +++ b/test-support/learning-examples/src/main/java/de/learnlib/testsupport/example/LearningExample.java @@ -30,7 +30,9 @@ import net.automatalib.automaton.transducer.StateLocalInputMealyMachine; import net.automatalib.automaton.transducer.SubsequentialTransducer; import net.automatalib.automaton.vpa.OneSEVPA; +import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeoutSymbol; public interface LearningExample { @@ -69,11 +71,23 @@ interface MMLTLearningExample extends LearningExample, MMLT< MMLTModelParams getParams(); + /** + * Returns the fully timed alphabet, including the {@link TimeoutSymbol} and {@link TimeStepSequence} symbol. + * + * @return the full (semantic) alphabet + * + * @see #getUntimedAlphabet() + */ @Override default Alphabet> getAlphabet() { return getReferenceAutomaton().getSemantics().getInputAlphabet(); } + /** + * Returns the direct inputs of the {@link MMLT#getInputAlphabet() MMLT}. + * + * @return the direct input alphabet + */ default Alphabet getUntimedAlphabet() { return getReferenceAutomaton().getInputAlphabet(); } From 160b7b85171d3e38d0a775770857dc8c9fbbdb22 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 2 Dec 2025 15:25:47 +0100 Subject: [PATCH 54/55] more rigorous testing --- ...xtensibleLStarMMLTCounterexampleTests.java | 3 ++ .../lstar/it/ExtensibleLStarMMLTIT.java | 30 ++++++++++++------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java index da79c9e8c..88e8f7057 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/ExtensibleLStarMMLTCounterexampleTests.java @@ -31,6 +31,7 @@ import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; import net.automatalib.word.Word; +import org.testng.Assert; import org.testng.annotations.Test; /** @@ -65,6 +66,8 @@ private static void learnModel(MMLT example, learner.refineHypothesis(cex); hyp = learner.getHypothesisModel(); } + + Assert.assertEquals(learner.getObservationTable().numberOfDistinctRows(), hyp.size()); } @Test diff --git a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java index f9ac0e6b5..dbb4fc6b8 100644 --- a/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java +++ b/algorithms/active/lstar/src/test/java/de/learnlib/algorithm/lstar/it/ExtensibleLStarMMLTIT.java @@ -27,6 +27,8 @@ import java.util.Random; import java.util.stream.Stream; +import de.learnlib.algorithm.lstar.closing.ClosingStrategies; +import de.learnlib.algorithm.lstar.closing.ClosingStrategy; import de.learnlib.algorithm.lstar.mmlt.ExtensibleLStarMMLTBuilder; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTPerfectSymbolFilter; import de.learnlib.algorithm.lstar.mmlt.filter.MMLTRandomSymbolFilter; @@ -46,7 +48,7 @@ import net.automatalib.serialization.dot.DOTParsers; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.symbol.time.TimedOutput; import net.automatalib.util.automaton.mmlt.MMLTs; import net.automatalib.word.Word; import org.testng.annotations.Test; @@ -65,23 +67,29 @@ protected void addLearnerVariants(Alphabet alphabet, List>> suffixes = new ArrayList<>(); alphabet.forEach(s -> suffixes.add(Word.fromLetter(TimedInput.input(s)))); - suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + // Do not include TimeoutSymbol because we want to check analyzing and handling counterexamples with it + // suffixes.add(Word.fromLetter(new TimeoutSymbol<>())); + + var builder = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) + .withModelParams(example.getParams()) + .withTimeOracle(mqOracle) + .withInitialSuffixes(suffixes); var filters = Arrays.asList(new MMLTPerfectSymbolFilter<>(mmlt), new MMLTRandomSymbolFilter<>(mmlt, 0.1, new Random(42)), new IgnoreAllSymbolFilter, InputSymbol>(), new AcceptAllSymbolFilter, InputSymbol>()); - for (SymbolFilter, InputSymbol> filter : filters) { + for (ClosingStrategy, ? super Word>> strategy : ClosingStrategies.values()) { + builder.setClosingStrategy(strategy); + for (SymbolFilter, InputSymbol> filter : filters) { - var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses - var learner = new ExtensibleLStarMMLTBuilder().withAlphabet(alphabet) - .withModelParams(example.getParams()) - .withTimeOracle(mqOracle) - .withInitialSuffixes(suffixes) - .withSymbolFilter(cachedFilter) - .create(); - variants.addLearnerVariant("system=" + example + ",filter=" + filter, learner, counters + mmlt.size()); + var cachedFilter = new CachedSymbolFilter<>(filter); // need to wrap to enable updates to responses + var learner = builder.withSymbolFilter(cachedFilter).create(); + variants.addLearnerVariant("system=" + example + ",strategy=" + strategy + ",filter=" + filter, + learner, + counters + mmlt.size()); + } } } From 56b47811b27c211bdf8dc1656af5c979f1d80368 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 2 Dec 2025 16:17:17 +0100 Subject: [PATCH 55/55] make learner + OT batch queries to better support parallel oracles --- .../lstar/mmlt/ExtensibleLStarMMLT.java | 94 +++++--- .../lstar/mmlt/MMLTObservationTable.java | 215 +++++++++--------- 2 files changed, 171 insertions(+), 138 deletions(-) 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 46e0d37bc..bf1965171 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 @@ -15,7 +15,7 @@ */ package de.learnlib.algorithm.lstar.mmlt; -import java.util.Collection; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -40,6 +40,7 @@ import de.learnlib.filter.symbol.AcceptAllSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.query.DefaultQuery; +import de.learnlib.query.Query; import de.learnlib.statistic.Statistics; import de.learnlib.statistic.StatisticsCollector; import de.learnlib.time.MMLTModelParams; @@ -51,6 +52,7 @@ import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.TimerInfo; import net.automatalib.common.util.HashUtil; +import net.automatalib.common.util.collection.IterableUtil; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -235,22 +237,22 @@ private List>> selectClosingRows(List>> private void updateOutputs() { // Query output of newly-added transitions: - updateOutputs(this.hypData.getTable().getShortPrefixRows()); - updateOutputs(this.hypData.getTable().getLongPrefixRows()); - } + MMLTObservationTable ot = this.hypData.getTable(); + List> queries = new ArrayList<>(); + + for (Row> row : IterableUtil.concat(ot.getShortPrefixRows(), ot.getLongPrefixRows())) { + Word> label = row.getLabel(); - private void updateOutputs(Collection>> rows) { - for (Row> row : rows) { - if (row.getLabel().isEmpty()) { + if (label.isEmpty()) { continue; // initial state } - if (this.hypData.getTransitionOutputMap().containsKey(row.getLabel())) { + if (this.hypData.getTransitionOutputMap().containsKey(label)) { continue; // already queried } - Word> prefix = row.getLabel().prefix(-1); - TimedInput inputSym = row.getLabel().suffix(1).lastSymbol(); + Word> prefix = label.prefix(-1); + TimedInput inputSym = label.lastSymbol(); TimedOutput output; if (inputSym instanceof TimeStepSequence ws) { @@ -259,12 +261,17 @@ private void updateOutputs(Collection>> rows) { assert timerInfo != null; O combinedOutput = this.hypData.getModelParams().outputCombiner().combineSymbols(timerInfo.outputs()); output = new TimedOutput<>(combinedOutput); + this.hypData.getTransitionOutputMap().put(label, output); } else { - output = this.timeOracle.answerQuery(prefix, Word.fromLetter(inputSym)).lastSymbol(); + queries.add(new OutputQuery<>(label, prefix)); } + } - if (output != null) { - this.hypData.getTransitionOutputMap().put(row.getLabel(), output); + if (!queries.isEmpty()) { + timeOracle.processQueries(queries); + + for (OutputQuery q : queries) { + q.process(this.hypData.getTransitionOutputMap()); } } } @@ -402,8 +409,8 @@ private void handleMissingTimeoutChange(Row> spRow, TimerInfo lastTimer = locationTimerInfo.getLastTimer(); assert lastTimer != null; - Word> lastTimerTransPrefix = spRow.getLabel().append(TimedInput.step(lastTimer.initial())); if (!lastTimer.periodic()) { + Word> lastTimerTransPrefix = spRow.getLabel().append(TimedInput.step(lastTimer.initial())); Row> row = hypData.getTable().getRow(lastTimerTransPrefix); assert row != null; if (!row.isShortPrefixRow()) { @@ -413,16 +420,15 @@ private void handleMissingTimeoutChange(Row> spRow, TimerInfo> timerTransPrefix = spRow.getLabel().append(TimedInput.step(timeout.initial())); - assert this.hypData.getTable().getRow(timerTransPrefix) == null : "Timer already appears to be one-shot."; + assert this.hypData.getTable().getRow(spRow.getLabel().append(TimedInput.step(timeout.initial()))) == null : + "Timer already appears to be one-shot."; // Remove all timers with greater timeout (are now redundant): - List subsequentTimers = locationTimerInfo.getSortedTimers() - .stream() - .filter(t -> t.initial() > timeout.initial()) - .map(TimerInfo::name) - .toList(); - subsequentTimers.forEach(locationTimerInfo::removeTimer); + for (TimerInfo t : new ArrayList<>(locationTimerInfo.getSortedTimers())) { + if (t.initial() > timeout.initial()) { + locationTimerInfo.removeTimer(t.name()); + } + } // Change from periodic to one-shot: locationTimerInfo.setOneShotTimer(timeout.name()); @@ -501,9 +507,7 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai } } // Ensure initial location: - if (hypothesis.getInitialState() == null) { - throw new IllegalArgumentException("Automaton must have an initial location."); - } + assert hypothesis.getInitialState() != null : "Automaton must have an initial location."; // 5. Create outgoing transitions for non-delaying inputs: for (Entry e : stateMap.entrySet()) { @@ -573,6 +577,44 @@ private static MMLTHypothesis constructHypothesis(MMLTHypDataContai return hypothesis; } + private static final class OutputQuery extends Query, Word>> { + + private final Word> label; + private final Word> prefix; + private TimedOutput output; + + private OutputQuery(Word> label, Word> prefix) { + this.label = label; + this.prefix = prefix; + } + + @Override + public void answer(Word> output) { + assert output.size() == 1; + this.output = output.firstSymbol(); + } + + @Override + public Word> getPrefix() { + return prefix; + } + + @Override + public Word> getSuffix() { + return Word.fromLetter(label.lastSymbol()); + } + + /** + * Processes the query result by mapping the given label to the (single) response. + * + * @param outputs + * the output map to write the mapping to + */ + void process(Map>, TimedOutput> outputs) { + outputs.put(label, output); + } + } + static final class BuilderDefaults { private BuilderDefaults() { @@ -592,7 +634,7 @@ static MutableSymbolFilter, InputSymbol> symbolFilter() { } static AcexAnalyzer analyzer() { - return AcexAnalyzers.LINEAR_BWD; + return AcexAnalyzers.BINARY_SEARCH_BWD; } } 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 493e14181..61e639eeb 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 @@ -18,14 +18,13 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import java.util.stream.Stream; import de.learnlib.datastructure.observationtable.ObservationTable; import de.learnlib.datastructure.observationtable.Row; @@ -34,8 +33,11 @@ import de.learnlib.filter.MutableSymbolFilter; import de.learnlib.oracle.TimedQueryOracle; import de.learnlib.oracle.TimedQueryOracle.TimerQueryResult; +import de.learnlib.query.DefaultQuery; import net.automatalib.alphabet.Alphabet; import net.automatalib.automaton.mmlt.TimerInfo; +import net.automatalib.common.util.HashUtil; +import net.automatalib.common.util.collection.IterableUtil; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.TimeStepSequence; import net.automatalib.symbol.time.TimedInput; @@ -73,14 +75,10 @@ class MMLTObservationTable implements ObservationTable, Word private final Map>, RowImpl>> shortPrefixRowMap; // label -> row info private final Map>, RowImpl>> longPrefixRowMap; // label -> row info - private final List>> sortedShortPrefixes; - // values of shortPrefixRowMap sorted by label, for faster access. - private final List>> longPrefixList; // values of longPrefixRowMap as list, for faster access. - private final Map> rowContentMap; // contentID -> row content - private final List>> suffixes = new ArrayList<>(); - private final Set>> suffixSet = new HashSet<>(); + private final List>> suffixes; + private final Set>> suffixSet; private final Alphabet> alphabet; private final long minTimerQueryWaitTime; @@ -98,13 +96,13 @@ class MMLTObservationTable implements ObservationTable, Word this.timerInfoMap = new HashMap<>(); - this.shortPrefixRowMap = new HashMap<>(); - this.sortedShortPrefixes = new ArrayList<>(); - - this.longPrefixRowMap = new HashMap<>(); - this.longPrefixList = new ArrayList<>(); + // use linked hashmaps for stable insertion-order + this.shortPrefixRowMap = new LinkedHashMap<>(); + this.longPrefixRowMap = new LinkedHashMap<>(); this.rowContentMap = new HashMap<>(); + this.suffixes = new ArrayList<>(); + this.suffixSet = new HashSet<>(); } /** @@ -158,8 +156,6 @@ private RowImpl> addInitialLocation() { RowImpl> newRow = new RowImpl<>(Word.epsilon(), 0, alphabet.size()); newRow.makeShort(alphabet.size()); this.shortPrefixRowMap.put(Word.epsilon(), newRow); - this.sortedShortPrefixes.add(newRow); - this.sortedShortPrefixes.sort(Comparator.comparing(r -> r.getLabel().toString())); return newRow; } @@ -182,9 +178,8 @@ private void initLocation(RowImpl> newRow, TimedQueryOracle } // Add outgoing transitions: - for (RowImpl> t : this.createOutgoingTransitions(newRow, timeOracle)) { - this.queryAllSuffixes(t, timeOracle); - } + List>> transitions = this.createOutgoingTransitions(newRow, timeOracle); + this.queryAllSuffixes(transitions, timeOracle); } /** @@ -270,8 +265,6 @@ private List>> createOutgoingTransitions(RowImpl> createLpRow(Word> prefix) { RowImpl> newRow = new RowImpl<>(prefix, 0); this.longPrefixRowMap.put(prefix, newRow); - this.longPrefixList.add(newRow); - assert this.longPrefixList.size() == this.longPrefixRowMap.size(); newRow.setLpIndex(0); // unused @@ -284,58 +277,37 @@ private RowImpl> createLpRow(Word> prefix) { * * @return the list of unclosed transition, in a deterministic order */ - public List>>> findUnclosedTransitions() { + List>>> findUnclosedTransitions() { // Identify contentIds for locations: - Set spContentIds = - this.shortPrefixRowMap.values().stream().map(RowImpl::getRowContentId).collect(Collectors.toSet()); - - // Group lp rows by their content id: - Map>>> lpContentMap = new HashMap<>(); - for (RowImpl> lpRow : this.longPrefixRowMap.values()) { - lpContentMap.putIfAbsent(lpRow.getRowContentId(), new ArrayList<>()); - lpContentMap.get(lpRow.getRowContentId()).add(lpRow); + Set spContentIds = new HashSet<>(this.shortPrefixRowMap.size()); + + for (RowImpl> row : this.shortPrefixRowMap.values()) { + spContentIds.add(row.getRowContentId()); } - // Identify ids that are not used by any SP: - List>>> unclosedRows = new ArrayList<>(); - List sortedLpIds = lpContentMap.keySet().stream().sorted().toList(); - for (Integer lpId : sortedLpIds) { - if (spContentIds.contains(lpId)) { - continue; - } + // Identify ids that are not used by any SP and group them by their content id: + Map>>> lpContentMap = + new HashMap<>(HashUtil.capacity(this.longPrefixRowMap.size())); - // Sort row s.t. list order deterministic: - List>> unclosedWithId = lpContentMap.get(lpId); - unclosedWithId.sort(Comparator.comparing(r -> r.getLabel().toString())); - unclosedRows.add(unclosedWithId); - } + for (RowImpl> row : this.longPrefixRowMap.values()) { + int id = row.getRowContentId(); - // Remove unused content ids: - Set usedContentIds = - Stream.concat(this.shortPrefixRowMap.values().stream(), this.longPrefixRowMap.values().stream()) - .map(RowImpl::getRowContentId) - .collect(Collectors.toSet()); - - List oldContentIds = this.rowContentMap.keySet().stream().toList(); - for (int oldId : oldContentIds) { - if (!usedContentIds.contains(oldId)) { - this.rowContentMap.remove(oldId); + if (!spContentIds.contains(id)) { + lpContentMap.computeIfAbsent(id, k -> new ArrayList<>()).add(row); } } - return unclosedRows; + // Remove unused content ids: + this.rowContentMap.keySet().removeIf(key -> !(spContentIds.contains(key) || lpContentMap.containsKey(key))); + + return new ArrayList<>(lpContentMap.values()); } - public List>>> initialize(List>> initialShortPrefixes, - List>> initialSuffixes, - TimedQueryOracle oracle) { + List>>> initialize(List>> initialShortPrefixes, + List>> initialSuffixes, + TimedQueryOracle oracle) { - if (isInitialized()) { - throw new IllegalStateException("Called initialize, but there are already rows present"); - } - if (!initialShortPrefixes.isEmpty()) { - throw new IllegalArgumentException("Init with short prefixes is not supported."); - } + assert this.shortPrefixRowMap.isEmpty() && this.longPrefixRowMap.isEmpty() && initialShortPrefixes.isEmpty(); // Add initial suffixes: for (Word> suffix : initialSuffixes) { @@ -347,22 +319,34 @@ public List>>> initialize(List>> initi // 1. Create initial location: RowImpl> newLoc = this.addInitialLocation(); this.initLocation(newLoc, oracle); - this.queryAllSuffixes(newLoc, oracle); + this.queryAllSuffixes(Collections.singleton(newLoc), oracle); // 2. Identify unclosed transitions: return this.findUnclosedTransitions(); } - private void queryAllSuffixes(RowImpl> row, TimedQueryOracle timedOracle) { - Word> prefix = row.getLabel(); + private void queryAllSuffixes(Collection>> rows, TimedQueryOracle timedOracle) { + + int numSuffixes = this.suffixes.size(); + List, Word>>> queries = new ArrayList<>(rows.size() * numSuffixes); - List>> suffixOutputs = new ArrayList<>(this.suffixes.size()); - for (Word> suffix : this.suffixes) { - Word> output = timedOracle.answerQuery(prefix, suffix); - suffixOutputs.add(output); + for (RowImpl> row : rows) { + Word> prefix = row.getLabel(); + + for (Word> suffix : this.suffixes) { + queries.add(new DefaultQuery<>(prefix, suffix)); + } } - this.processSuffixOutputs(row, suffixOutputs); + timedOracle.processQueries(queries); + Iterator, Word>>> iter = queries.iterator(); + + for (RowImpl> row : rows) { + List>> outputs = new ArrayList<>(numSuffixes); + fetchResults(iter, outputs, numSuffixes); + + this.processSuffixOutputs(row, outputs); + } } private void processSuffixOutputs(RowImpl> row, List>> rowContents) { @@ -377,61 +361,71 @@ private void processSuffixOutputs(RowImpl> row, List void fetchResults(Iterator> queryIt, List output, int numSuffixes) { + for (int j = 0; j < numSuffixes; j++) { + DefaultQuery qry = queryIt.next(); + output.add(qry.getOutput()); + } } - public List>>> addSuffixes(Collection>> newSuffixes, - TimedQueryOracle oracle) { + List>>> addSuffixes(Collection>> newSuffixes, + TimedQueryOracle oracle) { // 1. Extend current suffixes + identify new suffixes: - List>> newSuffixList = new ArrayList<>(); + int numOld = this.suffixes.size(); for (Word> suffix : newSuffixes) { if (this.suffixSet.add(suffix)) { LOGGER.debug("Adding new suffix '{}'", suffix); - - newSuffixList.add(suffix); this.suffixes.add(suffix); } } - if (newSuffixList.isEmpty()) { + int numNew = this.suffixes.size(); + + if (numOld == numNew) { return Collections.emptyList(); } // 2. Update row content: - Stream.concat(shortPrefixRowMap.values().stream(), longPrefixRowMap.values().stream()).forEach(row -> { - List>> updatedOutputs = new ArrayList<>(); - if (row.getRowContentId() != NO_CONTENT) { - // Add existing suffix outputs: - updatedOutputs.addAll(this.rowContentMap.get(row.getRowContentId()).outputs()); + int numNewSuffixes = numNew - numOld; + List, Word>>> queries = + new ArrayList<>(numNewSuffixes * numberOfRows()); + Iterable>> rows = + IterableUtil.concat(shortPrefixRowMap.values(), longPrefixRowMap.values()); + List>> newSuffixList = this.suffixes.subList(numOld, numNew); + + for (RowImpl> row : rows) { + for (Word> suffix : newSuffixList) { + queries.add(new DefaultQuery<>(row.getLabel(), suffix)); } + } - for (Word> suffix : newSuffixList) { - Word> output = oracle.answerQuery(row.getLabel(), suffix); - updatedOutputs.add(output); + oracle.processQueries(queries); + Iterator, Word>>> iterator = queries.iterator(); + + for (RowImpl> row : rows) { + List>> updatedOutputs = new ArrayList<>(numNew); + if (row.getRowContentId() != NO_CONTENT) { + // Add existing suffix outputs: + updatedOutputs.addAll(rowContents(row)); } + fetchResults(iterator, updatedOutputs, numNewSuffixes); this.processSuffixOutputs(row, updatedOutputs); - }); + } return this.findUnclosedTransitions(); } - public List>>> toShortPrefixes(List>> lpRows, - TimedQueryOracle oracle) { + List>>> toShortPrefixes(List>> lpRows, TimedQueryOracle oracle) { for (Row> row : lpRows) { LOGGER.debug("Adding new location with prefix '{}'", row.getLabel()); final RowImpl> lpRow = (RowImpl>) row; // Delete from LP rows: - RowImpl> removed = this.longPrefixRowMap.remove(row.getLabel()); - this.longPrefixList.remove(removed); - assert this.longPrefixList.size() == this.longPrefixRowMap.size(); + this.longPrefixRowMap.remove(row.getLabel()); // Add to SP rows: this.shortPrefixRowMap.put(row.getLabel(), lpRow); - this.sortedShortPrefixes.add(lpRow); - this.sortedShortPrefixes.sort(Comparator.comparing(r -> r.getLabel().toString())); lpRow.makeShort(alphabet.size()); @@ -447,18 +441,17 @@ public Alphabet> getInputAlphabet() { @Override public Collection>> getShortPrefixRows() { - assert this.sortedShortPrefixes.size() == this.shortPrefixRowMap.size(); - return Collections.unmodifiableList(this.sortedShortPrefixes); + return Collections.unmodifiableCollection(this.shortPrefixRowMap.values()); } @Override public Collection>> getLongPrefixRows() { - return Collections.unmodifiableList(this.longPrefixList); + return Collections.unmodifiableCollection(this.longPrefixRowMap.values()); } @Override public Row> getRow(int idx) { - throw new IllegalStateException("Not supported. Use prefix to access rows instead."); + throw new UnsupportedOperationException("Not supported. Use prefix to access rows instead."); } @Override @@ -495,10 +488,10 @@ public List>> rowContents(Row> row) { @Override public Word> transformAccessSequence(Word> word) { - throw new IllegalStateException("Not implemented."); + throw new UnsupportedOperationException("Not implemented."); } - public @Nullable TimerInfo getTimerInfo(Word> prefix, long initial) { + @Nullable TimerInfo getTimerInfo(Word> prefix, long initial) { LocationTimerInfo info = this.timerInfoMap.get(prefix); if (info != null) { return info.getTimerInfo(initial); @@ -506,7 +499,7 @@ public Word> transformAccessSequence(Word> word) { return null; } - public @Nullable LocationTimerInfo getLocationTimerInfo(Row> sp) { + @Nullable LocationTimerInfo getLocationTimerInfo(Row> sp) { return this.timerInfoMap.getOrDefault(sp.getLabel(), null); } @@ -525,9 +518,9 @@ public Word> transformAccessSequence(Word> word) { * * @return List of unclosed rows. Empty, if none. */ - public List>>> addOutgoingTransition(Row> spRow, - TimedInput symbol, - TimedQueryOracle timeOracle) { + List>>> addOutgoingTransition(Row> spRow, + TimedInput symbol, + TimedQueryOracle timeOracle) { if (!this.alphabet.containsSymbol(symbol)) { throw new IllegalArgumentException("Unknown symbol."); } @@ -545,14 +538,14 @@ public List>>> addOutgoingTransition(Row> s ((RowImpl>) spRow).setSuccessor(symIdx, succRow); // Update suffixes: - this.queryAllSuffixes(succRow, timeOracle); + this.queryAllSuffixes(Collections.singleton(succRow), timeOracle); return this.findUnclosedTransitions(); } - public List>>> addTimerTransition(Row> spRow, - TimerInfo timeout, - TimedQueryOracle timeOracle) { + List>>> addTimerTransition(Row> spRow, + TimerInfo timeout, + TimedQueryOracle timeOracle) { return this.addOutgoingTransition(spRow, new TimeStepSequence<>(timeout.initial()), timeOracle); } @@ -563,15 +556,13 @@ public List>>> addTimerTransition(Row> spRo * @param prefix * Row prefix */ - public void removeLpRow(Word> prefix) { + void removeLpRow(Word> prefix) { if (!this.longPrefixRowMap.containsKey(prefix)) { throw new IllegalArgumentException("Attempting to remove lp row that does not exist."); } // Remove lp row: - RowImpl> removed = this.longPrefixRowMap.remove(prefix); - this.longPrefixList.remove(removed); - assert this.longPrefixList.size() == this.longPrefixRowMap.size(); + this.longPrefixRowMap.remove(prefix); // Unset as successor: int symIdx = this.alphabet.getSymbolIndex(prefix.lastSymbol());