From e26aa8225b37c017f1c6fa006956bfafe730b18e Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 7 Oct 2025 13:32:25 +0200 Subject: [PATCH 01/18] Bumped java version to 17 --- pom.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 339a94462..59f1160c0 100644 --- a/pom.xml +++ b/pom.xml @@ -185,9 +185,9 @@ limitations under the License. UTF-8 UTF-8 - 11 - 11 - 11 + 17 + 17 + 17 From 3e3bc1773ef9d29459d19d766a2ccdee491ac82a Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 7 Oct 2025 13:44:52 +0200 Subject: [PATCH 02/18] Added MMLT classes --- core/src/main/java/module-info.java | 4 + .../mmlt/ILocalTimerMealyInputSymbol.java | 7 + .../ILocalTimerMealySemanticInputSymbol.java | 10 + .../mmlt/LocalTimerMealyOutputSymbol.java | 60 ++++ .../impl/time/mmlt/NonDelayingInput.java | 40 +++ .../impl/time/mmlt/TimeStepSequence.java | 48 ++++ .../impl/time/mmlt/TimeStepSymbol.java | 12 + .../impl/time/mmlt/TimeoutSymbol.java | 25 ++ .../impl/time/mmlt/TimerTimeoutSymbol.java | 41 +++ .../time/mmlt/AbstractSymbolCombiner.java | 36 +++ .../time/mmlt/CompactLocalTimerMealy.java | 229 +++++++++++++++ .../automaton/time/mmlt/LocalTimerMealy.java | 179 ++++++++++++ .../LocalTimerMealyVisualizationHelper.java | 105 +++++++ .../automaton/time/mmlt/MealyTimerInfo.java | 86 ++++++ .../time/mmlt/MutableLocalTimerMealy.java | 96 +++++++ .../time/mmlt/StringSymbolCombiner.java | 64 +++++ .../LocalTimerMealyConfiguration.java | 210 ++++++++++++++ .../semantics/LocalTimerMealySemantics.java | 272 ++++++++++++++++++ .../ReducedLocalTimerMealySemantics.java | 162 +++++++++++ .../time/mmlt/semantics/TimeoutPair.java | 22 ++ .../dot/LocalTimerMealyGraphvizParser.java | 231 +++++++++++++++ .../automaton/cover/LocalTimerMealyCover.java | 69 +++++ .../automaton/mmlt/LocalTimerMealyUtil.java | 154 ++++++++++ 23 files changed, 2162 insertions(+) create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java create mode 100644 core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java create mode 100644 core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java create mode 100644 serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java create mode 100644 util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java create mode 100644 util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index c482544dd..ab59cfb7a 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -35,6 +35,7 @@ // annotations are 'provided'-scoped and do not need to be loaded at runtime requires static org.checkerframework.checker.qual; + requires org.slf4j; exports net.automatalib.alphabet.impl; exports net.automatalib.automaton.base; @@ -51,4 +52,7 @@ exports net.automatalib.ts.modal.impl; exports net.automatalib.ts.modal.transition.impl; exports net.automatalib.ts.powerset.impl; + exports net.automatalib.alphabet.impl.time.mmlt; + exports net.automatalib.automaton.time.mmlt.semantics; + exports net.automatalib.automaton.time.mmlt; } diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java new file mode 100644 index 000000000..796cf1439 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java @@ -0,0 +1,7 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +/** + * Base type for input symbols for the structural automaton of an MMLT. + */ +public interface ILocalTimerMealyInputSymbol { +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java new file mode 100644 index 000000000..cd5618440 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java @@ -0,0 +1,10 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +/** + * Base class for an input for the semantics automaton of some MMLT. + * + * @param + */ +public interface ILocalTimerMealySemanticInputSymbol { + +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java new file mode 100644 index 000000000..8bdf590a4 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java @@ -0,0 +1,60 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * Output type for the semantics automaton: an output that occurs with some or no delay. + */ +public class LocalTimerMealyOutputSymbol { + + private final U userObject; + private final long delay; + + public LocalTimerMealyOutputSymbol(long delay, @NonNull U userObject) { + if (delay < 0) { + throw new IllegalArgumentException("Delay must not be negative."); + } + this.userObject = userObject; + this.delay = delay; + } + + public LocalTimerMealyOutputSymbol(@NonNull U userObject) { + this(0, userObject); + } + + public long getDelay() { + return delay; + } + + public boolean isDelayed() { + return this.delay > 0; + } + + public U getSymbol() { + return userObject; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LocalTimerMealyOutputSymbol that = (LocalTimerMealyOutputSymbol) o; + return delay == that.delay && Objects.equals(userObject, that.userObject); + } + + @Override + public int hashCode() { + return Objects.hash(userObject, delay); + } + + @Override + public String toString() { + if (this.isDelayed()) { + return String.format("[%d]%s", this.getDelay(), this.userObject); + } + return this.userObject.toString(); + } + +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java new file mode 100644 index 000000000..74a903b33 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java @@ -0,0 +1,40 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.Objects; + +/** + * An non-delaying input for the structural and semantics automaton. + * + * @param + */ +public class NonDelayingInput implements ILocalTimerMealySemanticInputSymbol, ILocalTimerMealyInputSymbol { + private final U symbol; + + public NonDelayingInput(@NonNull U input) { + this.symbol = input; + } + + public U getSymbol() { + return this.symbol; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NonDelayingInput that = (NonDelayingInput) o; + return Objects.equals(symbol, that.symbol); + } + + @Override + public int hashCode() { + return Objects.hashCode(symbol); + } + + @Override + public String toString() { + return symbol.toString(); + } +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java new file mode 100644 index 000000000..cc78c5e49 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java @@ -0,0 +1,48 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +import java.util.Objects; + +/** + * Convenience type for aggregating multiple subsequent time steps. + */ +public class TimeStepSequence implements ILocalTimerMealySemanticInputSymbol { + + private long timeSteps; + + public TimeStepSequence(long timeSteps) { + this.timeSteps = timeSteps; + + if (timeSteps <= 0) { + throw new IllegalArgumentException("Timeout must be larger than zero."); + } + } + + public void setTimeSteps(long timeSteps) { + this.timeSteps = timeSteps; + if (timeSteps <= 0) { + throw new IllegalArgumentException("Timeout must be larger than zero."); + } + } + + public long getTimeSteps() { + return timeSteps; + } + + @Override + public String toString() { + return String.format("wait[%d]", this.timeSteps); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimeStepSequence that = (TimeStepSequence) o; + return timeSteps == that.timeSteps; + } + + @Override + public int hashCode() { + return Objects.hash(timeSteps); + } +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java new file mode 100644 index 000000000..8978a7866 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java @@ -0,0 +1,12 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +/** + * Input for the semantics automaton: delay for a single time step. + */ +public class TimeStepSymbol extends TimeStepSequence { + + public TimeStepSymbol() { + super(1); + } + +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java new file mode 100644 index 000000000..65bde2880 --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java @@ -0,0 +1,25 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +import java.util.Objects; + +/** + * Symbolic input for the semantics automaton: causes a delay until the next timeout. + */ +public class TimeoutSymbol implements ILocalTimerMealySemanticInputSymbol { + + @Override + public String toString() { + return "timeout"; + } + + @Override + public boolean equals(Object o) { + return o != null && getClass() == o.getClass(); + } + + @Override + public int hashCode() { + return Objects.hash(this.toString()); + } + +} diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java new file mode 100644 index 000000000..eebd606dd --- /dev/null +++ b/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java @@ -0,0 +1,41 @@ +package net.automatalib.alphabet.impl.time.mmlt; + +import java.util.Objects; + +/** + * The timeout symbol of a timer, as used by the structural automaton. + *

+ * This input is used for the transition and output function of some MMLT. + * These symbols are not inputs for the expanded form of an MMLT. + * + * @param + */ +public class TimerTimeoutSymbol implements ILocalTimerMealyInputSymbol { + private final String timer; + + public TimerTimeoutSymbol(String timer) { + this.timer = timer; + } + + public String getTimer() { + return timer; + } + + @Override + public String toString() { + return String.format("to[%s]", this.timer); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TimerTimeoutSymbol that = (TimerTimeoutSymbol) o; + return Objects.equals(timer, that.timer); + } + + @Override + public int hashCode() { + return Objects.hashCode(timer); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java new file mode 100644 index 000000000..b3ab1a3d0 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java @@ -0,0 +1,36 @@ +package net.automatalib.automaton.time.mmlt; + +import java.util.List; + +/** + * In an MMLT, multiple timeouts may occur simultaneously. + * We use these symbol combiners to combine their outputs deterministically. + * + * @param Symbol type + */ +public abstract class AbstractSymbolCombiner { + + /** + * Indicates if the provided suffix is a combined suffix. + * + * @param symbol Symbol for testing + * @return True if combined suffix, false if not. + */ + public abstract boolean isCombinedSymbol(U symbol); + + /** + * Combines the provided symbols to a single suffix of same data type. Must be deterministic. + * + * @param symbols Provided symbols. + * @return Combined suffix + */ + public abstract U combineSymbols(List symbols); + + /** + * Attempts to separate the provided combined suffix into individual symbols. + * + * @param symbol Combined symbols + * @return Individual symbols + */ + public abstract List separateSymbols(U symbol); +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java new file mode 100644 index 000000000..6cf601ce1 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java @@ -0,0 +1,229 @@ +package net.automatalib.automaton.time.mmlt; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.GrowingAlphabet; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.impl.time.mmlt.TimerTimeoutSymbol; +import net.automatalib.automaton.transducer.impl.CompactMealy; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; + +/** + * Implements a LocalTimerMealy that is mutable. + * The structure automaton is backed by a CompactMealy automaton. + * + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class CompactLocalTimerMealy implements LocalTimerMealy, MutableLocalTimerMealy { + private final CompactMealy, O> automaton; + private final Map>> sortedTimers; // location -> (sorted timers) + private final Map>> resets; // location -> inputs (that reset all timers) + + private final GrowingAlphabet> untimedAlphabet; + + private final O silentOutput; + private final AbstractSymbolCombiner outputCombiner; + + /** + * Initializes a new CompactLocalTimerMealy. + * + * @param nonDelayingInputs Non-delaying inputs used by this MMLT. + * @param silentOutput The silent output used by this MMLT. + * @param outputCombiner The combiner function for simultaneous timeouts of periodic timers. + */ + public CompactLocalTimerMealy(Collection> nonDelayingInputs, + O silentOutput, + AbstractSymbolCombiner outputCombiner + ) { + this.untimedAlphabet = new GrowingMapAlphabet<>(); + this.untimedAlphabet.addAll(nonDelayingInputs); + + this.sortedTimers = new HashMap<>(); + this.resets = new HashMap<>(); + + this.silentOutput = silentOutput; + this.outputCombiner = outputCombiner; + + // Prepare compact Mealy: + GrowingMapAlphabet> inputAlphabet = new GrowingMapAlphabet<>(); + inputAlphabet.addAll(nonDelayingInputs); + this.automaton = new CompactMealy<>(inputAlphabet); + } + + + @Override + public O getSilentOutput() { + return this.silentOutput; + } + + @Override + public AbstractSymbolCombiner getOutputCombiner() { + return this.outputCombiner; + } + + @Override + public Alphabet> getInputAlphabet() { + return this.automaton.getInputAlphabet(); + } + + @Override + public Alphabet> getUntimedAlphabet() { + return this.untimedAlphabet; + } + + @Override + public boolean isLocalReset(Integer location, NonDelayingInput input) { + return this.resets.getOrDefault(location, Collections.emptySet()).contains(input); + } + + @Override + public List> getSortedTimers(Integer location) { + return Collections.unmodifiableList(this.sortedTimers.getOrDefault(location, Collections.emptyList())); + } + + @Override + public Collection getStates() { + return automaton.getStates(); + } + + @Override + public @Nullable LocalTimerMealyTransition getTransition(Integer location, ILocalTimerMealyInputSymbol input) { + var trans = this.automaton.getTransition(location, input); + if (trans == null) { + return null; + } + return new LocalTimerMealyTransition<>(trans.getSuccId(), trans.getProperty()); + } + + @Override + public @Nullable Integer getInitialState() { + return automaton.getInitialState(); + } + + @Override + public Integer addState() { + return automaton.addState(); + } + + @Override + public void setInitialState(Integer location) { + automaton.setInitialState(location); + } + + @Override + public void addTransition(Integer source, NonDelayingInput input, O output, Integer target) { + automaton.addAlphabetSymbol(input); + this.untimedAlphabet.addSymbol(input); + + automaton.removeAllTransitions(source, input); // remove transition if already defined + automaton.addTransition(source, input, target, output); + } + + @Override + public void removeTransition(Integer source, NonDelayingInput input) { + automaton.removeAllTransitions(source, input); + } + + private void ensureThatCanAddTimer(List> timers, String name, long initial, O output, boolean periodic) { + if (output.equals(this.silentOutput)) { + throw new IllegalArgumentException(String.format("Provided silent output for timer '%s'.", name)); + } + + // Verify that the timer name is unique: + if (timers.stream().anyMatch(t -> t.name().equals(name))) { + throw new IllegalArgumentException(String.format("Location already has a timer of the name '%s'.", name)); + } + + // Ensure that our new timer can time out AND that its timeouts do not coincide with that of an existing one-shot timer: + var oldOneShot = timers.stream().filter(t -> !t.periodic()).findFirst(); + if (oldOneShot.isPresent()) { + if (initial > oldOneShot.get().initial()) { + throw new IllegalArgumentException(String.format("The initial value %d of '%s' exceeds that of a one-shot timer; will never time out.", initial, name)); + } + if (periodic && (oldOneShot.get().initial() % initial == 0)) { + // Our new periodic timer will time out at the same time as the existing one-shot timer. + // This makes the model non-deterministic and is not allowed: + throw new IllegalArgumentException(String.format("The timer '%s' times out at the same time as a one-shot timer (%d).", name, initial)); + } + } + if (!periodic) { + // Our new one-shot timer is the one-shot timer with the highest initial value (or the only one). + // Check that no timer with a lower initial value will time out at the same time: + for (var timer : timers) { + if (timer.initial() <= initial && initial % timer.initial() == 0) { + throw new IllegalArgumentException(String.format("The existing timer '%s' times out at the same time as the new one-shot timer (%d).", timer.name(), initial)); + } + } + } + } + + @Override + public void addPeriodicTimer(Integer location, String name, long initial, O output) { + this.sortedTimers.putIfAbsent(location, new ArrayList<>()); + var localTimers = this.sortedTimers.get(location); + + ensureThatCanAddTimer(localTimers, name, initial, output, true); + localTimers.add(new MealyTimerInfo<>(name, initial, output, true)); + localTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + + // Add self-looping transition: + TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); + this.automaton.addAlphabetSymbol(newTimerSymbol); + automaton.addTransition(location, newTimerSymbol, location, output); + } + + @Override + public void addOneShotTimer(Integer location, String name, long initial, O output, Integer target) { + this.sortedTimers.putIfAbsent(location, new ArrayList<>()); + var localTimers = this.sortedTimers.get(location); + + ensureThatCanAddTimer(localTimers, name, initial, output, false); + localTimers.add(new MealyTimerInfo<>(name, initial, output, false)); + localTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); + + // Add transition with location change: + TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); + this.automaton.addAlphabetSymbol(newTimerSymbol); + automaton.addTransition(location, newTimerSymbol, target, output); + + // Remove all timers with higher initial value, as these can no longer time out: + localTimers.removeIf(t -> t.initial() > initial); + } + + @Override + public void removeTimer(Integer location, String timerName) { + var localTimers = this.sortedTimers.get(location); + if (localTimers == null) { + return; + } + + localTimers.removeIf(t -> t.name().equals(timerName)); + automaton.removeAllTransitions(location, new TimerTimeoutSymbol<>(timerName)); + } + + @Override + public void addLocalReset(Integer location, NonDelayingInput input) { + // Ensure that input causes self-loop: + var target = this.getSuccessor(location, input); + if (target == null || !target.equals(location)) { + throw new IllegalArgumentException("Provided input is not defined or does not trigger a self-loop."); + } + + resets.putIfAbsent(location, new HashSet<>()); + resets.get(location).add(input); + } + + @Override + public void removeLocalReset(Integer location, NonDelayingInput input) { + var localResets = resets.get(location); + if (localResets == null) { + return; + } + + localResets.remove(input); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java new file mode 100644 index 000000000..783d8b74c --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java @@ -0,0 +1,179 @@ +package net.automatalib.automaton.time.mmlt; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.UniversalDeterministicAutomaton; +import net.automatalib.automaton.graph.TransitionEdge; +import net.automatalib.automaton.graph.UniversalAutomatonGraphView; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; +import net.automatalib.graph.UniversalGraph; +import net.automatalib.visualization.VisualizationHelper; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +/** + * Base type for a Mealy Machine with Local Timers (MMLT). + *

+ * An MMLT extends Mealy machines with local timers. + * Each location can have multiple timers. A timer can only be active in its assigned location. + * All timers of a location reset when this location is entered from a different location or, in case of the initial + * location, if the location is entered for the first time. + * There are periodic and one-shot timers. Periodic timers reset themselves on timeout. They cannot cause a location + * change. One-shot timers can cause a location change. They reset all timers of the target location at timeout. + * A location can have arbitrarily many periodic timers and up to one one-shot timers. + * Timers are always reset to their initial value. The initial values must be chosen so that a periodic timer never + * times out at the same time as a one-shot timer (to preserve determinism). Multiple periodic timers may time out + * simultaneously. In this case, their outputs are combined using an AbstractSymbolCombiner. + *

+ * The timeout of a timer is modeled with a transition that has a TimerTimeoutSymbol as input. Other inputs are called + * non-delaying. A non-delaying input that causes a self-loop can cause a local reset. Then, all timers of that + * location reset. + *

+ * To be able to subclass existing automata, some methods refer to locations as "state". + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public interface LocalTimerMealy extends UniversalDeterministicAutomaton, LocalTimerMealy.LocalTimerMealyTransition, Void, O> { + + record LocalTimerMealyTransition(@NonNull S successor, @NonNull O output) { + + } + + /** + * Returns the symbol used for silent outputs. + * + * @return Silent output symbol + */ + O getSilentOutput(); + + /** + * Multiple periodic timers may out simultaneously. Then, their outputs are combined + * using an AbstractSymbolCombiner. This method returns the combiner used for + * this model. + * + * @return Symbol combiner used for this model. + */ + AbstractSymbolCombiner getOutputCombiner(); + + /** + * Returns the input alphabet of this MMLT, consisting of non-delaying inputs + * and timeout-symbols for its timers. + * + * @return Input alphabet + */ + Alphabet> getInputAlphabet(); + + /** + * Retrieves the non-delaying inputs for this automaton. + * Excludes timer timeout symbols. May be empty. + * + * @return Untimed alphabet. + */ + Alphabet> getUntimedAlphabet(); + + @Override + default O getTransitionProperty(LocalTimerMealyTransition transition) { + return transition.output(); + } + + @Override + default S getSuccessor(LocalTimerMealyTransition transition) { + return transition.successor(); + } + + /** + * Indicates if the provided input performs a local reset in the given location. + * + * @param location Location + * @param input Non-delaying input + * @return True if performing a local reset + */ + boolean isLocalReset(S location, NonDelayingInput input); + + /** + * Returns the timers of the specified location sorted ascendingly by their initial time. + * + * @param location Location + * @return Sorted list of local timers. Empty if location has no timers. + */ + List> getSortedTimers(S location); + + /** + * Returns the semantics automaton that describes the behavior of this MMLT. + * + * @return Semantics automaton + */ + default LocalTimerMealySemantics getSemantics() { + return new LocalTimerMealySemantics<>(this); + } + + // ======================================= + + @Override + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(Collection> inputs) { + return new LocalTimerMealyGraphView<>(this, inputs, false, false); + } + + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView() { + return this.transitionGraphView(getInputAlphabet()); + } + + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(boolean colorEdges, boolean includeResets) { + return new LocalTimerMealyGraphView<>(this, getInputAlphabet(), colorEdges, includeResets); + } + + // ======================================= + + @Override + default Void getStateProperty(S state) { + return null; + } + + // We do not want to provide tracing abilities for inputs of the structure automaton: + @Override + default Set getStates(Iterable> input) { + throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); + } + + @Override + @Nullable + default S getSuccessor(S state, Iterable> input) { + throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); + } + + @Override + @Nullable + default S getState(Iterable> input) { + throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); + } + + // ======================================= + + class LocalTimerMealyGraphView extends + UniversalAutomatonGraphView, LocalTimerMealy.LocalTimerMealyTransition, Void, OX, LocalTimerMealy> { + + private final boolean colorEdges; + private final boolean includeResets; + + public LocalTimerMealyGraphView(LocalTimerMealy automaton, + Collection> inputs, + boolean colorEdges, boolean includeResets) { + super(automaton, inputs); + this.colorEdges = colorEdges; + this.includeResets = includeResets; + } + + @Override + public VisualizationHelper, LocalTimerMealyTransition>> getVisualizationHelper() { + return new LocalTimerMealyVisualizationHelper<>(automaton, colorEdges, includeResets); + } + } + +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java new file mode 100644 index 000000000..d793ed2a4 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java @@ -0,0 +1,105 @@ +package net.automatalib.automaton.time.mmlt; + +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.impl.time.mmlt.TimerTimeoutSymbol; +import net.automatalib.automaton.graph.TransitionEdge; +import net.automatalib.automaton.visualization.AutomatonVisualizationHelper; + +import java.util.Map; +import java.util.stream.Collectors; + +class LocalTimerMealyVisualizationHelper extends + AutomatonVisualizationHelper, LocalTimerMealy.LocalTimerMealyTransition, LocalTimerMealy> { + + private final boolean colorEdges; + private final boolean includeResets; + + /** + * Creates a new visualization helper for an MMLT. + * Allows edge coloring and explicit resets in transition labels for easier inspection. + *

+ * If you want to serialize the resulting file to graphviz, disable explicit resets. + * The parse does not know how to treat the reset information. + * + * @param automaton Automaton + * @param colorEdges If set, the transitions for local resets, periodic timers, and one-shot timers are colored differently. + * @param includeResets If set, each transition includes a list of timers that it resets. + */ + public LocalTimerMealyVisualizationHelper(LocalTimerMealy automaton, boolean colorEdges, boolean includeResets) { + super(automaton); + this.colorEdges = colorEdges; + this.includeResets = includeResets; + } + + @Override + public boolean getNodeProperties(S node, Map properties) { + super.getNodeProperties(node, properties); + + // Include timer assignments: + var localTimers = automaton.getSortedTimers(node); + if (!localTimers.isEmpty()) { + // Add local timer info: + String timers = localTimers.stream() + .map(t -> String.format("%s=%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + properties.put("timers", timers); + } + + return true; + } + + @Override + public boolean getEdgeProperties(S src, + TransitionEdge, LocalTimerMealy.LocalTimerMealyTransition> edge, + S tgt, Map properties) { + super.getEdgeProperties(src, edge, tgt, properties); + + String label = String.format("%s / %s", edge.getInput(), edge.getTransition().output()); + + // Infer the label color + reset information for the transition: + String resetInfo = ""; + String edgeColor = ""; + if (edge.getInput() instanceof TimerTimeoutSymbol ts) { + // Get info for corresponding timer: + var optTimer = automaton.getSortedTimers(src).stream() + .filter(t -> t.name().equals(ts.getTimer())) + .findFirst(); + assert optTimer.isPresent(); + + if (optTimer.get().periodic()) { + // Periodic -> resets itself: + resetInfo = String.format("%s↦%d", optTimer.get().name(), optTimer.get().initial()); + edgeColor = "cornflowerblue"; + } else { + // One-shot -> resets all in target: + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(t -> String.format("%s↦%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + edgeColor = "chartreuse3"; + } + } else if (edge.getInput() instanceof NonDelayingInput ndi) { + if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { + // Self-loop + local reset -> resets all in target: + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(t -> String.format("%s↦%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + edgeColor = "orange"; + } + } + + if (this.colorEdges && !edgeColor.isBlank()) { + properties.put(EdgeAttrs.COLOR, edgeColor); + properties.put("fontcolor", edgeColor); + } + if (this.includeResets) { + label += " {" + resetInfo + "}"; + } + properties.put(EdgeAttrs.LABEL, label); + + return true; + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java new file mode 100644 index 000000000..a7870e9e9 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java @@ -0,0 +1,86 @@ +package net.automatalib.automaton.time.mmlt; + +import java.util.Objects; + +/** + * Provides information about a timer that is stored in an MMLT. + * + * @param Output symbol type + */ +public class MealyTimerInfo { + /** + * Name of the timer + */ + private final String name; + + /** + * Initial value of the timer + */ + private final long initial; + + /** + * Symbol that the timer produces at timeout. Must not be silent. + */ + private final O output; + + /** + * True if the timer is periodic. + */ + private boolean periodic; + + public MealyTimerInfo(String name, long initial, O output, boolean periodic) { + if (initial <= 0) { + throw new IllegalArgumentException("Timer values must be greater than zero."); + } + + this.name = name; + this.initial = initial; + this.output = output; + this.periodic = periodic; + } + + public MealyTimerInfo(String name, long initial, O output) { + this(name, initial, output, true); + } + + @Override + public String toString() { + return String.format("%s=%d/%s", name, initial, output); + } + + public void setOneShot() { + this.periodic = false; + } + + public String name() { + return name; + } + + public long initial() { + return initial; + } + + public boolean periodic() { + return this.periodic; + } + + public O output() { + return output; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + var that = (MealyTimerInfo) obj; + return Objects.equals(this.name, that.name) && + this.initial == that.initial && + Objects.equals(this.output, that.output); + } + + @Override + public int hashCode() { + return Objects.hash(name, initial, output); + } + +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java new file mode 100644 index 000000000..e684e55e8 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java @@ -0,0 +1,96 @@ +package net.automatalib.automaton.time.mmlt; + +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; + +public interface MutableLocalTimerMealy { + + /** + * Adds a new location to the MMLT. + * + * @return The newly-added location. + */ + S addState(); + + /** + * Sets the initial location to the provided location. + * + * @param location New initial location + */ + void setInitialState(S location); + + /** + * Adds a transition for a non-delaying input, adding the input + * to the automaton's alphabet if necessary. + * + * @param source Source location + * @param input Non-delaying input symbol + * @param output Output + * @param target Target location + */ + void addTransition(S source, NonDelayingInput input, O output, S target); + + /** + * Removes the transition at the provided non-delaying input. + * No effect if the location has no such transition. + * + * @param source Transition source location + * @param input Transition input + */ + void removeTransition(S source, NonDelayingInput input); + + /** + * Adds a new periodic timer to the provided location. + *

+ * Throws an error if a) the output is silent b) the initial value is less zero or less + * c) the initial value exceeds that of a one-shot timer (-> timer never expires) + * d) the timer will time out at the same time as a one-shot timer. + * + * @param location Location of the timer + * @param name Timer name + * @param initial Initial value + * @param output Output at timeout + */ + void addPeriodicTimer(S location, String name, long initial, O output); + + /** + * Adds a new one-shot timer to the provided location. + * Removes all timers of that location with higher initial value, as these can no longer time out. + *

+ * Throws an error if a) the output is silent b) the initial value is less zero or less + * c) the initial value exceeds that of a one-shot timer (-> timer never expires) + * d) the timer will time out at the same time as a periodic timer. + * + * @param location Location of the timer + * @param name Timer name + * @param initial Initial value + * @param output Output at timeout + */ + void addOneShotTimer(S location, String name, long initial, O output, S target); + + /** + * Removes the timer with the provided name. + * No effect if the location has no such timer. + * + * @param location Location of the timer + * @param timerName Name of the timer + */ + void removeTimer(S location, String timerName); + + /** + * Adds a local reset at the provided input in the provided location. + * Throws an error if the transition does not self-loop. + * + * @param location Source location + * @param input Input of the transition that should perform a local reset + */ + void addLocalReset(S location, NonDelayingInput input); + + /** + * Removes a local reset at the provided input in the provided location. + * No effect if the input does not trigger a local reset. + * + * @param location Source location + * @param input Input of the transition that performs a local reset. + */ + void removeLocalReset(S location, NonDelayingInput input); +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java new file mode 100644 index 000000000..7d1991468 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java @@ -0,0 +1,64 @@ +package net.automatalib.automaton.time.mmlt; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Combines multiple String outputs by concatenating them and using a pipe as separator. + */ +public class StringSymbolCombiner extends AbstractSymbolCombiner { + + private static final StringSymbolCombiner combiner = new StringSymbolCombiner(); + + public static StringSymbolCombiner getInstance() { + return combiner; + } + + private StringSymbolCombiner() { + + } + + + @Override + public boolean isCombinedSymbol(String symbol) { + return symbol.contains("|") && symbol.length() > 1; + } + + @Override + public String combineSymbols(List symbols) { + + // Break all inputs (if needed) + put the results in a set: + Set expandedSymbols = new HashSet<>(); + for (var sym : symbols) { + if (sym.equals("|")) { + throw new IllegalArgumentException("The output | is reserved as delimiter."); + } + + if (this.isCombinedSymbol(sym)) { + expandedSymbols.addAll(this.separateSymbols(sym)); + } else { + expandedSymbols.add(sym); + } + } + + // Sort the symbols + separate with pipe: + return expandedSymbols.stream() + .sorted() + .collect(Collectors.joining("|")); + } + + @Override + public List separateSymbols(String symbol) { + if (!this.isCombinedSymbol(symbol)) { + return List.of(symbol); + } + + return Arrays.stream(symbol.split("\\|")) + .distinct() + .sorted() + .toList(); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java new file mode 100644 index 000000000..e7f1c025e --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java @@ -0,0 +1,210 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.*; + +/** + * A configuration, a.k.a., state of an MMLT. + * A configuration is a tuple of an active location and the values of its timers. + * + * @param Location type + * @param Output symbol type + */ +public final class LocalTimerMealyConfiguration { + private final S location; + + private final List> sortedTimers; + private final long[] timerValues; + private final long[] initialValues; + private final long minimumTimerValue; + + private long entryDistance; + + /** + * Initializes the entry configuration for the provided location, where all timers have their initial value. + * + * @param location Location + * @param sortedTimers Timers of the location, sorted by initial value. + */ + public LocalTimerMealyConfiguration(S location, List> sortedTimers) { + this(location, sortedTimers, null); + } + + public LocalTimerMealyConfiguration(S location, List> sortedTimers, Word> locationPrefix) { + this.location = location; + + this.sortedTimers = sortedTimers; + + this.initialValues = new long[sortedTimers.size()]; + this.timerValues = new long[sortedTimers.size()]; + this.minimumTimerValue = (sortedTimers.isEmpty()) ? 0 : sortedTimers.get(0).initial(); + + for (int i = 0; i < sortedTimers.size(); i++) { + initialValues[i] = sortedTimers.get(i).initial(); + timerValues[i] = initialValues[i]; // reset + } + + this.entryDistance = 0; + } + + + private LocalTimerMealyConfiguration(S location, List> sortedTimers, + long[] timerValues, long[] initialValues, long entryDistance, long minimumTimerValue) { + this.location = location; + this.sortedTimers = sortedTimers; + + this.initialValues = initialValues; + this.minimumTimerValue = minimumTimerValue; + this.timerValues = Arrays.copyOf(timerValues, timerValues.length); + this.entryDistance = entryDistance; + } + + /** + * Creates a copy of this configuration. + * The location, timers, prefix, and initialValue still point to the original instances. + * The current timer values are copied. + * Modifying these in the resulting object does not affect the original configuration. + */ + public LocalTimerMealyConfiguration copy() { + return new LocalTimerMealyConfiguration<>(location, sortedTimers, timerValues, initialValues, entryDistance, minimumTimerValue); + } + + public S getLocation() { + return location; + } + + /** + * Returns the entry distance. This is the minimal number of time steps + * required to reach this configuration from the entry configuration. + * + * @return Entry distance + */ + public long getEntryDistance() { + return entryDistance; + } + + /** + * Indicates if this is the entry configuration of the location. + * A configuration is the entry configuration if all timers have their initial value. + * + * @return True if entry configuration. + */ + public boolean isEntryConfig() { + return this.entryDistance == 0; + } + + /** + * Indicates if this configuration is stable. + * A configuration is stable if its entry distance is less than the initial value + * of the timer with the lowest initial value of the location. + * If the location has no timers, its only configuration is its entry configuration, which is always stable. + * + * @return True if stable + */ + public boolean isStableConfig() { + return this.entryDistance == 0 || this.entryDistance < minimumTimerValue; + } + + /** + * Resets all timers to their initial values. + */ + public void resetTimers() { + System.arraycopy(this.initialValues, 0, this.timerValues, 0, sortedTimers.size()); + this.entryDistance = 0; + } + + + /** + * Returns all timers that time out in the least number of time steps. + */ + @Nullable + public TimeoutPair getNextExpiringTimers() { + if (sortedTimers.isEmpty()) { + return null; + } else if (this.sortedTimers.size() == 1) { + // No need to collect timeouts - there is only one timer that can expire. + // The time to its timeout is its remaining value: + return new TimeoutPair<>(this.timerValues[0], + Collections.singletonList(this.sortedTimers.get(0))); + + } else { + // Multiple timers may time out at the same time. + // Get minimum distance to next timeout: + long minValue = Long.MAX_VALUE; + for (int i = 0; i < sortedTimers.size(); i++) { + if (timerValues[i] < minValue) { + minValue = timerValues[i]; + } + } + + assert minValue != Long.MAX_VALUE; + + // Collect info of all timers that time out then: + List> expiringTimers = new ArrayList<>(); + for (int i = 0; i < sortedTimers.size(); i++) { + if (timerValues[i] == minValue) { + expiringTimers.add(this.sortedTimers.get(i)); + } + } + + // Return timed-out timers: + return new TimeoutPair<>(minValue, expiringTimers); + } + } + + /** + * Decreases all timer values by the specified amount. This amount must be at most the time to the next timeout. + * If this sets a timer to zero, this timer is immediately reset. + * to its initial value. + * + * @param delay Decrement + */ + public void decrement(long delay) { + int timerResets = 0; + int oneShotResets = 0; + for (int i = 0; i < this.sortedTimers.size(); i++) { + long newValue = this.timerValues[i] - delay; + + if (newValue < 0) { + throw new IllegalArgumentException("Can only advance to next timeout."); + } else if (newValue == 0) { + if (!sortedTimers.get(i).periodic()) { + oneShotResets += 1; + } + + newValue = this.initialValues[i]; + timerResets += 1; + } + this.timerValues[i] = newValue; + } + + if (oneShotResets > 1) throw new AssertionError(); + if (timerResets == this.sortedTimers.size() || oneShotResets == 1) { + // reset all timers -> back at entry config: + this.entryDistance = 0; + } else { + this.entryDistance += delay; + } + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + LocalTimerMealyConfiguration that = (LocalTimerMealyConfiguration) o; + return minimumTimerValue == that.minimumTimerValue && entryDistance == that.entryDistance && Objects.equals(location, that.location) && Objects.equals(sortedTimers, that.sortedTimers) && Objects.deepEquals(timerValues, that.timerValues) && Objects.deepEquals(initialValues, that.initialValues); + } + + @Override + public int hashCode() { + return Objects.hash(location, sortedTimers, Arrays.hashCode(timerValues), Arrays.hashCode(initialValues), minimumTimerValue, entryDistance); + } + + @Override + public String toString() { + return String.format("[%s,%d]", this.location, this.entryDistance); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java new file mode 100644 index 000000000..20de8b9e4 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java @@ -0,0 +1,272 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.*; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.NonNull; + +import java.util.List; + +/** + * Defines the semantics of an MMLT. + *

+ * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are + * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values + * of this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the + * symbolic input timeout, which causes a delay until the next timeout. + *

+ * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all + * transitions, except for those with the input timeout. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output type of the MMLT + */ +public class LocalTimerMealySemantics { + private final LocalTimerMealyConfiguration initialConfiguration; + private final LocalTimerMealy model; + + private final Alphabet> alphabet; + private final LocalTimerMealyOutputSymbol silentOutput; + + /** + * Represents a transition in the semantics automaton ("expanded form") of an MMLT. + * + * @param output Transition output + * @param target Transition target + * @param Location type + * @param Input type + * @param Output type + */ + public record LocalTimerMealySemanticTransition(LocalTimerMealyOutputSymbol output, + LocalTimerMealyConfiguration target) { + + } + + public LocalTimerMealySemantics(LocalTimerMealy model) { + this.model = model; + + var initialLocation = model.getInitialState(); + this.initialConfiguration = new LocalTimerMealyConfiguration<>(initialLocation, model.getSortedTimers(initialLocation)); + + this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet()); + this.alphabet.add(new TimeoutSymbol<>()); + this.alphabet.add(new TimeStepSymbol<>()); + + this.silentOutput = new LocalTimerMealyOutputSymbol<>(model.getSilentOutput()); + } + + /** + * Returns the input alphabet of the semantics automaton. This consists of all non-delaying inputs + * of the associated MMLT, as well as the time step symbol and the symbolic timeout symbol. + * + * @return Input alphabet + */ + public Alphabet> getInputAlphabet() { + return alphabet; + } + + /** + * Returns the symbol used for silent outputs. + * + * @return Silent output symbol + */ + public LocalTimerMealyOutputSymbol getSilentOutput() { + return this.silentOutput; + } + + /** + * Returns the initial configuration of this MMLT. This is a tuple of the + * initial location and its initial timer values. + * + * @return Initial configuration + */ + public LocalTimerMealyConfiguration getInitialConfiguration() { + return this.initialConfiguration; + } + + /** + * Enters the suffix into the provided configuration and returns corresponding outputs. + *

+ * Cannot provide TimeSequences with more than 1 symbol for the suffix, as this might trigger multiple timeouts + * and thus lead to output sequences that are longer than the suffix. + * + * @param configuration Configuration + * @param suffix Suffix inputs + * @return Outputs for the suffix + */ + public Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix) { + WordBuilder> wbOutput = new WordBuilder<>(); + + var currentConfiguration = configuration; + for (var sym : suffix) { + var trans = getTransition(currentConfiguration, sym); + currentConfiguration = trans.target(); + + if (trans.output() == null) { + throw new IllegalArgumentException("Cannot use time step sequences in suffix that have more than one symbol."); + } + wbOutput.append(trans.output()); + } + + return wbOutput.toWord(); + } + + /** + * Enters the prefix and suffix sequences into the automaton and returns the outputs that occur for the suffixes. + * + * @param prefix Configuration prefix + * @param suffix Suffix inputs + * @return Outputs for the suffix + */ + public Word> computeSuffixOutput(Word> prefix, Word> suffix) { + var prefixConfig = this.traceInputs(prefix); + return computeSuffixOutput(prefixConfig, suffix); + } + + /** + * Traces the provided prefix and returns the reached configuration. + * + * @param prefix Configuration prefix + * @return Reached configuration + */ + public LocalTimerMealyConfiguration traceInputs(Word> prefix) { + var currentConfiguration = getInitialConfiguration().copy(); + for (var sym : prefix) { + currentConfiguration = getTransition(currentConfiguration, sym).target(); + } + return currentConfiguration; + } + + + @NonNull + public LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input) { + return getTransition(source, input, Long.MAX_VALUE); + } + + /** + * Retrieves the transition in the semantics automaton that has the provided input and source configuration. + *

+ * If the input is a sequence of time steps, the target of the transition is the configuration reached after + * executing all time steps. If the sequence counts more than one step, the sequence might trigger multiple + * timeouts. To avoid ambiguity, the transition output is set to null in this case. + * If the sequence comprises a single time step only, the output is either that of a timeout or silence. + * + * @param source Source configuration + * @param input Input symbol + * @param maxWaitingTime Maximum time steps to wait for a timeout + * @return Transition in semantics automaton + */ + @NonNull + public LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, + ILocalTimerMealySemanticInputSymbol input, + long maxWaitingTime) { + var sourceCopy = source.copy(); // we do not want to modify values of the source configuration + + if (input instanceof NonDelayingInput ndi) { + return getTransition(sourceCopy, ndi); + } else if (input instanceof TimeoutSymbol) { + return getTimeoutTransition(sourceCopy, maxWaitingTime); + } else if (input instanceof TimeStepSequence ts) { + // Per step, we can advance at most by the time to the next timeout: + var currentConfig = sourceCopy; + LocalTimerMealyOutputSymbol lastOutput = null; + long remainingTime = ts.getTimeSteps(); + while (remainingTime > 0) { + var nextTimeoutTrans = getTimeoutTransition(currentConfig, remainingTime); + lastOutput = nextTimeoutTrans.output(); + if (nextTimeoutTrans.output().equals(this.getSilentOutput())) { + // No timer will expire during remaining waiting time: + break; + } else { + remainingTime -= nextTimeoutTrans.output().getDelay(); + currentConfig = nextTimeoutTrans.target(); + } + } + + if (ts.getTimeSteps() > 1) { + lastOutput = null; // ignore multiple outputs + } else { + // Output for single time step includes no delay by definition: + lastOutput = new LocalTimerMealyOutputSymbol<>(lastOutput.getSymbol()); + } + + // Return final target + output: + return new LocalTimerMealySemanticTransition<>(lastOutput, currentConfig); + } else { + throw new IllegalArgumentException("Unknown input symbol type"); + } + } + + private LocalTimerMealySemanticTransition getTimeoutTransition(LocalTimerMealyConfiguration source, long maxWaitingTime) { + LocalTimerMealyConfiguration target; + LocalTimerMealyOutputSymbol output; + + var nextTimeouts = source.getNextExpiringTimers(); + if (nextTimeouts == null) { + // no timers: + output = this.getSilentOutput(); + target = source; + } else if (nextTimeouts.delay() > maxWaitingTime) { + // timers, but too far away: + target = source; + target.decrement(maxWaitingTime); + output = this.getSilentOutput(); + } else { + if (nextTimeouts.allPeriodic()) { + target = source; + target.decrement(nextTimeouts.delay()); + } else { + // query target + update configuration: + assert nextTimeouts.timers().size() == 1; + TimerTimeoutSymbol expiringTimerSym = new TimerTimeoutSymbol<>(nextTimeouts.timers().get(0).name()); + + var successor = model.getSuccessor(source.getLocation(), expiringTimerSym); + target = new LocalTimerMealyConfiguration<>(successor, model.getSortedTimers(successor)); + target.resetTimers(); + } + + // Create combined output: + if (nextTimeouts.timers().size() == 1) { + output = new LocalTimerMealyOutputSymbol<>(nextTimeouts.delay(), nextTimeouts.timers().get(0).output()); + } else { + List outputs = nextTimeouts.timers().stream().map(MealyTimerInfo::output).toList(); + O combinedOutput = model.getOutputCombiner().combineSymbols(outputs); + output = new LocalTimerMealyOutputSymbol<>(nextTimeouts.delay(), combinedOutput); + } + } + + return new LocalTimerMealySemanticTransition<>(output, target); + } + + private LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, NonDelayingInput input) { + LocalTimerMealyConfiguration target; + LocalTimerMealyOutputSymbol output; + + var trans = model.getTransition(source.getLocation(), input); + if (trans == null) { // silent self-loop + target = source; + output = this.getSilentOutput(); + } else { + // Identify successor configuration: + if (!trans.successor().equals(source.getLocation())) { + // Change to a different location resets all timers in target: + target = new LocalTimerMealyConfiguration<>(trans.successor(), model.getSortedTimers(trans.successor())); + target.resetTimers(); + } else if (model.isLocalReset(source.getLocation(), input)) { + target = source; + target.resetTimers(); + } else { + target = source; + } + output = new LocalTimerMealyOutputSymbol<>(trans.output()); + } + + // Return output: + return new LocalTimerMealySemanticTransition<>(output, target); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java new file mode 100644 index 000000000..dbf99f5da --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java @@ -0,0 +1,162 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.transducer.impl.CompactMealy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Provides a reduced version of the semantics automaton of an MMLT. + * This reduced version retains all configurations that can be reached by timeouts and non-delaying inputs. + * It omits configurations that can only be reached by at least two subsequent time steps. + *

+ * The resulting automaton suffices to check the equivalence of two MMLTs. + * However, as the timeStep-transition is undefined in some configurations, + * the automaton cannot execute any sequence of inputs that can be executed on an MMLT. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public class ReducedLocalTimerMealySemantics extends CompactMealy, LocalTimerMealyOutputSymbol> { + + private final static Logger logger = LoggerFactory.getLogger(ReducedLocalTimerMealySemantics.class); + + private final Map, Integer> stateMap; + + private ReducedLocalTimerMealySemantics(Alphabet> alphabet) { + super(alphabet); + this.stateMap = new HashMap<>(); + } + + public static ReducedLocalTimerMealySemantics forLocalTimerMealy(LocalTimerMealy automaton) { + // Create alphabet for expanded form: + var alphabet = automaton.getSemantics().getInputAlphabet(); + + ReducedLocalTimerMealySemantics mealy = new ReducedLocalTimerMealySemantics<>(alphabet); + + // 1a: Add all configurations that can be reached via timeouts/non-delaying inputs, or are at least one time + // step away from these configurations: + for (var loc : automaton.getStates()) { + getRelevantConfigurations(loc, automaton) + .forEach(c -> mealy.stateMap.put(c, mealy.addState())); + } + + // 1b: Mark initial state: + var initialConfig = automaton.getSemantics().getInitialConfiguration(); + mealy.setInitialState(mealy.stateMap.get(initialConfig)); + + // 2. Add transitions: + for (var config : mealy.stateMap.keySet()) { + var sourceState = mealy.stateMap.get(config); + + for (var sym : alphabet) { + var trans = automaton.getSemantics().getTransition(config, sym); + var output = trans.output(); + + // Try to find matching state. If not found, leave undefined: + int targetId = mealy.stateMap.getOrDefault(trans.target(), -1); + if (targetId != -1) { + mealy.addTransition(sourceState, sym, targetId, output); + } + } + } + + logger.debug("Expanded from {} locations to {} states.", + automaton.getStates().size(), mealy.size()); + + return mealy; + } + + /** + * Retrieves a list of configurations of the provided location + * that can be reached via timeouts, and those that are at most one time step away from these. + * + * @param Location type + * @param location Considered location + * @param automaton MMLT + * @return List of the relevant configurations of the location + */ + private static List> getRelevantConfigurations(S location, + LocalTimerMealy automaton) { + + List> configurations = new ArrayList<>(); + + LocalTimerMealyConfiguration currentConfiguration = new LocalTimerMealyConfiguration<>(location, automaton.getSortedTimers(location)); + configurations.add(currentConfiguration); + + // Enumerate all timeouts, until we change to a different location or re-enter the entry configuration + // of this location: + while (true) { + // Wait for next timeout: + var trans = automaton.getSemantics().getTransition(currentConfiguration, new TimeoutSymbol<>()); + if (trans.output().equals(automaton.getSemantics().getSilentOutput())) { + break; // no timeout + } + + if (trans.output().getDelay() > 1) { + // More than one time unit away -> add 1-step successor config. + // If one time unit away, the successor is already in our list. + var newGapConfig = currentConfiguration.copy(); + newGapConfig.decrement(1); + configurations.add(newGapConfig); + } + + if (trans.target().isEntryConfig()) { + break; // location change OR repeating behavior + } + configurations.add(trans.target()); + currentConfiguration = trans.target(); + } + return configurations; + } + + + /** + * Returns the state that represents the provided configuration. + *

+ * If the configuration is not included and allowApproximate is set, + * the closest configuration of the same location (with a smaller entry distance) + * will be returned. If allowApproximate is not set, an error is thrown. + * + * @param configuration Provided configuration + * @param allowApproximate If set, the closest matching state is returned if the configuration is not part of the reduced automaton + * @return Corresponding state in the reduced automaton + */ + public Integer getStateForConfiguration(LocalTimerMealyConfiguration configuration, boolean allowApproximate) { + + LocalTimerMealyConfiguration closestMatch = null; + + for (var cfg : stateMap.keySet()) { + if (!cfg.getLocation().equals(configuration.getLocation()) || + cfg.getEntryDistance() > configuration.getEntryDistance()) { + continue; + } + + if (cfg.getEntryDistance() == configuration.getEntryDistance()) { + // Perfect match: + return stateMap.get(cfg); + } + + if (closestMatch == null || cfg.getEntryDistance() > closestMatch.getEntryDistance()) { + // Closer than previous candidate: + closestMatch = cfg; + } + } + + if (closestMatch == null || !allowApproximate) { + throw new IllegalStateException("Could not find corresponding configuration in expanded form."); + } + + return stateMap.get(closestMatch); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java new file mode 100644 index 000000000..53536d31d --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java @@ -0,0 +1,22 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; + +import java.util.List; + +/** + * Stores information about timers that expire after a given time from now. + * + * @param delay Offset to the next timeout + * @param timers Timers expiring simultaneously at next timeout + * @param Output suffix type + */ +public record TimeoutPair(long delay, List> timers) { + + public boolean allPeriodic() { + if (timers.size() == 1) { + return timers.get(0).periodic(); + } + return timers.stream().allMatch(MealyTimerInfo::periodic); + } +} diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java new file mode 100644 index 000000000..acd28675a --- /dev/null +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java @@ -0,0 +1,231 @@ +package net.automatalib.serialization.dot; + +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.*; +import net.automatalib.common.util.IOUtil; +import net.automatalib.common.util.Pair; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parses a DOT file that defines an MMLT automaton. + *

Expected syntax:

+ *
    + *
  • Mealy labels: input/output
  • + *
  • Initial node marker: __start0
  • + *
  • Timeout input: to[x] where x is the name of a local timer
  • + *
  • Local timers: node attribute timers with comma-separated assignments x=t with t > 0. + * Timer names must be unique per location. For one-shot timers choose values such that they never expire at the + * same time as another local timer.
  • + *
  • Reset behavior: edge attribute resets as specified below.
  • + *
+ *

Resets:

+ *
    + *
  • If the input is a timeout symbol: + *
      + *
    • Attribute omitted: periodic if self-loop, one-shot otherwise.
    • + *
    • Self-loop and value is list of all local timers: one-shot timer.
    • + *
    • Self-loop and value is the name of the timed-out timer: periodic.
    • + *
    • Any other value: invalid.
    • + *
    + *
  • + *
  • If the input is a normal input and the edge is a self-loop: + *
      + *
    • Attribute omitted: regular edge.
    • + *
    • Value is list of all local timers: local reset.
    • + *
    • Any other value: invalid.
    • + *
    + * If the edge is not a self-loop, reset values are ignored. + *
  • + *
+ *

Notes:

+ *
    + *
  • It is currently not possible to define a location with a single timer that is one-shot and whose timeout + * causes a self-loop. Such a timer is always considered periodic. This is semantically equivalent for learning, + * but hypotheses may still use the former variant; those timers may be highlighted specially for debugging.
  • + *
  • Edges with a timeout input must not be silent.
  • + *
+ *

Example DOT:

+ *
{@code
+ * 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 reset
+ *    s2 -> s2 [label="y / D"] // loop without reset
+ *
+ *    __start0 [label="" shape="none" width="0" height="0"];
+ *    __start0 -> s0;
+ * }
+ * }
+ */ + +public class LocalTimerMealyGraphvizParser { + + private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); + + public static LocalTimerMealy parseLocalTimerMealy(File path, String silentOutput, AbstractSymbolCombiner outputCombiner) { + InternalDOTParser parser; + try (InputStream r = IOUtil.asUncompressedBufferedInputStream(new FileInputStream(path))) { + parser = new InternalDOTParser(r); + parser.parse(); + } catch (Exception e) { + throw new RuntimeException(String.format("Parsing \"%s\" failed: %n %s", path, e)); + } + return parseLocalTimerMealy(parser, silentOutput, outputCombiner); + } + + private static LocalTimerMealy parseLocalTimerMealy(InternalDOTParser parser, String silentOutput, AbstractSymbolCombiner outputCombiner) { + final Collection nodes = parser.getNodes(); + final Collection edges = parser.getEdges(); + + final Map>> timers = new HashMap<>(nodes.size() - 1); // id in dot -> local timers + final Map stateMap = new HashMap<>(nodes.size() - 1); // name in dot -> new id + + final CompactLocalTimerMealy result = new CompactLocalTimerMealy<>(new GrowingMapAlphabet<>(), silentOutput, outputCombiner); + + // Parse nodes: + for (var node : nodes) { + if (node.id.equals(GraphDOT.initialLabel(0))) { + continue; // initial node marker + } + final int n = result.addState(); + stateMap.put(node.id, n); + + // Parse timers: + if (node.attributes.containsKey("timers")) { + String[] settings = node.attributes.get("timers").split(","); + for (String setting : settings) { + Matcher m = assignPattern.matcher(setting.trim()); // remove whitespace + if (!m.matches()) { + continue; + } + String timerName = m.group(1).trim(); + int value = Integer.parseInt(m.group(2)); + if (value <= 0) { + throw new IllegalArgumentException(String.format("Reset for timer %s in location %s must be greater zero.", + timerName, node.id)); + } + + timers.putIfAbsent(node.id, new HashMap<>()); + if (timers.get(node.id).containsKey(timerName)) { + throw new IllegalArgumentException(String.format("Timer %s in location %s must only be set once.", + timerName, node.id)); + } + + // Add timer: + timers.get(node.id).put(timerName, new MealyTimerInfo<>(timerName, value, null)); + } + } else { + timers.put(node.id, Collections.emptyMap()); // no timers in this location + } + } + + // Parse edges: + for (var edge : edges) { + // Parse input/output: + Pair<@Nullable String, @Nullable String> props = DOTParsers.DEFAULT_MEALY_EDGE_PARSER.apply(edge.attributes); + if (props.getFirst() == null || props.getSecond() == null) { + if (edge.src.equals(GraphDOT.initialLabel(0))) { + result.setInitialState(stateMap.get(edge.tgt)); + continue; + } + throw new IllegalArgumentException("All edges must have an input and an output."); + } + + // Check for resets: + HashSet edgeResets = new HashSet<>(); + if (edge.attributes.containsKey("resets")) { + // Parse resets: + for (String timer : edge.attributes.get("resets").split(",")) { + edgeResets.add(timer.strip()); + } + } + + + if (props.getFirst().startsWith("to[")) { + // Ensure that we defined the corresponding timer: + String timerName = props.getFirst().substring(3, props.getFirst().length() - 1); + if (!timers.get(edge.src).containsKey(timerName)) { + throw new IllegalArgumentException(String.format("Defined %s in state %s, but timer value is not set.", + props.getFirst(), edge.src)); + } + + // Add output to timer info: + MealyTimerInfo oldInfo = timers.get(edge.src).get(timerName); + + // Infer timer type: + var updatedInfo = new MealyTimerInfo<>(timerName, oldInfo.initial(), props.getSecond()); + if (edge.src.equals(edge.tgt)) { + if (edgeResets.size() == 1) { + if (!edgeResets.contains(timerName)) { + // Invalid periodic timer: + throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); + } + } else if (edgeResets.size() > 1) { + // Need to contain all local timers to be one-shot with loop: + for (var locTimer : timers.get(edge.tgt).keySet()) { + if (!edgeResets.contains(locTimer)) { + throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); + } + } + updatedInfo.setOneShot(); + } + } else { + // No need to check resets on location-change: always resetting all in target + updatedInfo.setOneShot(); + } + + // Add timer to location: + if (updatedInfo.periodic()) { + result.addPeriodicTimer(stateMap.get(edge.src), updatedInfo.name(), updatedInfo.initial(), updatedInfo.output()); + } else { + result.addOneShotTimer(stateMap.get(edge.src), + updatedInfo.name(), updatedInfo.initial(), updatedInfo.output(), + stateMap.get(edge.tgt)); + } + } else { + // Non-delaying input: + var nonDelInput = new NonDelayingInput<>(props.getFirst()); + + result.addTransition(stateMap.get(edge.src), nonDelInput, props.getSecond(), + stateMap.get(edge.tgt)); + + // Parse resets of self-loops with untimed input: + if (edge.src.equals(edge.tgt) && !edgeResets.isEmpty()) { + // Reset list needs to contain all local timers: + for (var locTimer : timers.get(edge.tgt).keySet()) { + if (!edgeResets.contains(locTimer)) { + throw new IllegalArgumentException(String.format("Invalid local reset at %s", nonDelInput)); + } + } + result.addLocalReset(stateMap.get(edge.src), nonDelInput); + } + } + } + + // Ensure initial location: + if (result.getInitialState() == null) { + throw new IllegalArgumentException("Automaton must have an initial location."); + } + + return result; + } + +} diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java new file mode 100644 index 000000000..052d8beae --- /dev/null +++ b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java @@ -0,0 +1,69 @@ +package net.automatalib.util.automaton.cover; + +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.impl.time.mmlt.TimeoutSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.word.Word; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LocalTimerMealyCover { + + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton) { + return getLocalTimerMealyLocationCover(automaton, true); + } + + /** + * Calculates a location cover for an MMLT. + *

+ * The cover provides one prefix for each location of the MMLT. + * The returned prefixes use inputs for the extended semantics. + * If some locations are isolated, they are excluded from the cover. + * + * @param automaton MMLT + * @param allowIncomplete If set, no error is thrown if some locations are unreachable. + * @return Location cover in format location -> prefix. + */ + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, boolean allowIncomplete) { + Map>> locPrefixes = new HashMap<>(); + Map, Word>> cfgPrefixes = new HashMap<>(); + List> queue = new ArrayList<>(); + + queue.add(automaton.getSemantics().getInitialConfiguration()); + cfgPrefixes.put(automaton.getSemantics().getInitialConfiguration(), Word.epsilon()); + locPrefixes.put(automaton.getInitialState(), Word.epsilon()); + + GrowingMapAlphabet> exploreAlphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet()); + exploreAlphabet.add(new TimeoutSymbol<>()); + + while (!queue.isEmpty()) { + var current = queue.remove(0); + for (var symbol : exploreAlphabet) { + var trans = automaton.getSemantics().getTransition(current, symbol); + if (trans.target().equals(current)) { + continue; // self-loop + } + if (!cfgPrefixes.containsKey(trans.target())) { + var newPrefix = cfgPrefixes.get(current).append(symbol); + cfgPrefixes.put(trans.target(), newPrefix); + queue.add(trans.target()); + + if (trans.target().isEntryConfig()) { + locPrefixes.put(trans.target().getLocation(), newPrefix); + } + } + } + } + + if (!allowIncomplete && locPrefixes.size() != automaton.getStates().size()) { + throw new AssertionError("Incomplete state cover."); + } + + return locPrefixes; + } +} diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java new file mode 100644 index 000000000..8b6ce0e4f --- /dev/null +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java @@ -0,0 +1,154 @@ +package net.automatalib.util.automaton.mmlt; + +import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +import net.automatalib.automaton.time.mmlt.semantics.ReducedLocalTimerMealySemantics; +import net.automatalib.util.automaton.Automata; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * Provides various functions that are related MMLTs. + */ +public class LocalTimerMealyUtil { + + public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB) { + var expandedA = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelA); + var expandedB = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelB); + + var separatingWord = Automata.findSeparatingWord(expandedA, expandedB, expandedA.getInputAlphabet()); + + if (separatingWord != null) { + var outputA = modelA.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); + var outputB = modelB.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); + if (outputA.equals(outputB)) { + throw new AssertionError("Invalid separating word."); + } + } + + return separatingWord; + } + + /** + * Returns the number of configurations for the given location. This is defined as follows: + * - When l has no timers: one + * - When l has a one-shot timer: initial value of this timer + * - When l only has periodic timers: least-common multiple of their initial values + * + * @param automaton Considered automaton + * @param location Considered location + * @param Location type + * @param Input type + * @param Output type + * @return Maximum configuration time. Long.MAX_VALUE, if exceeding integer maximum. + */ + public static long getConfigurationCount(LocalTimerMealy automaton, L location) { + Collection> timers = automaton.getSortedTimers(location); + if (timers.isEmpty()) { + return 1; + } + + // Single timer: take that timer's value: + if (timers.size() == 1) { + return timers.stream().findFirst().get().initial(); + } + + var optOneShot = timers.stream().filter(t -> !t.periodic()).findFirst(); + if (optOneShot.isPresent()) { + return optOneShot.get().initial(); // leave location at latest when one-shot expires + } + + // The lcm of multiple numbers is equal to the product of their multiple with their gcd. + // Therefore: calculate gcd and multiple. Then, divide multiple by gcd. + List bigTimeouts = timers.stream().map(t -> new BigInteger(String.valueOf(t.initial()))).toList(); + + // Calculate gcd of all timeouts: + BigInteger gcd = bigTimeouts.get(0); + BigInteger multiple = bigTimeouts.get(0); + for (int i = 1; i < bigTimeouts.size(); i++) { + gcd = gcd.gcd(bigTimeouts.get(i)); + multiple = multiple.multiply(bigTimeouts.get(i)); + } + BigInteger lcm = multiple.divide(gcd); + + try { + return lcm.longValueExact(); + } catch (ArithmeticException ignored) { + return Long.MAX_VALUE; + } + } + + + /** + * Returns the maximum initial value of all timers. + * + * @return Maximum initial timer value. Zero, if no timers are used. + */ + public static long getMaximumInitialTimerValue(LocalTimerMealy automaton) { + long maxValue = 0; + for (S loc : automaton.getStates()) { + var optMaxInitial = automaton.getSortedTimers(loc).stream().mapToLong(MealyTimerInfo::initial).max(); + if (optMaxInitial.isPresent()) { + maxValue = Math.max(maxValue, optMaxInitial.getAsLong()); + } + } + return maxValue; + } + + /** + * Retrieves the maximum delay to the next timeout in this model. + * + * @return Maximum timeout delay. Is at least one. + */ + public static long getMaximumTimeoutDelay(LocalTimerMealy automaton) { + long maxValue = 1; + + for (S loc : automaton.getStates()) { + var timers = automaton.getSortedTimers(loc); + if (timers.isEmpty()) { + continue; + } + + if (timers.size() == 1) { + maxValue = Math.max(maxValue, timers.stream().findFirst().get().initial()); + continue; + } + + // Get the least common multiple of the initial times: + long lcm = LocalTimerMealyUtil.getConfigurationCount(automaton, loc); + + // Calculate expiration times of all timers: + List timeouts = new ArrayList<>(); + for (var timer : automaton.getSortedTimers(loc)) { + long currentValue = timer.initial(); + while (currentValue < lcm) { + timeouts.add(currentValue); + currentValue += timer.initial(); + } + } + + // Find the largest distance between two successive expirations: + timeouts.add(0L); // need to consider time to first expiration + Collections.sort(timeouts); + long maxDiff = 0; + for (int i = 0; i < timeouts.size() - 1; i++) { + long diff = timeouts.get(i + 1) - timeouts.get(i); + if (diff > maxDiff) { + maxDiff = diff; + } + } + + maxValue = Math.max(maxValue, maxDiff); + } + + return maxValue; + } + +} From 633ff0309ef04b77825d0b92f1f17f87dd8640f1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 7 Oct 2025 14:44:06 +0200 Subject: [PATCH 03/18] Added basic tests for MMLTs; included sample MMLT model file. --- .../automaton/impl/LocalTimerMealyTests.java | 153 +++++++++++++++++ .../LocalTimerMealyDeserializationTest.java | 96 +++++++++++ .../dot/src/test/resources/sensor_mmlt.dot | 26 +++ .../automaton/mmlt/LocalTimerMealyTests.java | 156 ++++++++++++++++++ 4 files changed, 431 insertions(+) create mode 100644 core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java create mode 100644 serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java create mode 100644 serialization/dot/src/test/resources/sensor_mmlt.dot create mode 100644 util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java diff --git a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java new file mode 100644 index 000000000..71c8f4f23 --- /dev/null +++ b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java @@ -0,0 +1,153 @@ +package net.automatalib.automaton.impl; + +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.impl.time.mmlt.TimeStepSequence; +import net.automatalib.automaton.time.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +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; + +public class LocalTimerMealyTests { + + public 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; + } + + private static List generateRandomWords(int count, int wordLength) { + Random r = new Random(100); + List words = new ArrayList<>(); + for (int j = 0; j < count; j++) { + StringBuilder sb = new StringBuilder(wordLength); + for (int i = 0; i < wordLength; i++) { + char tmp = (char) ('a' + r.nextInt('z' - 'a')); + sb.append(tmp); + } + words.add(sb.toString()); + } + return words; + } + + @Test + public void testStringCombiner() { + var combiner = StringSymbolCombiner.getInstance(); + var words = generateRandomWords(100, 10); + + var combined = combiner.combineSymbols(words); + Assert.assertTrue(combiner.isCombinedSymbol(combined)); + var separated = combiner.separateSymbols(combined); + + Assert.assertEquals(words.stream().sorted().toList(), separated.stream().sorted().toList()); + + // Tests words with an absent output: + Assert.assertThrows(IllegalArgumentException.class, + () -> combiner.combineSymbols(List.of("|", "d|c")) + ); + + var combinedAbsent = combiner.combineSymbols(List.of("a|", "d|c")); + Assert.assertEquals(combinedAbsent, "a|c|d"); + + // Now test words that contain a pipe: + var combinedPipe = combiner.combineSymbols(List.of("b|a", "d|c")); + Assert.assertEquals(combinedPipe, "a|b|c|d"); + + // Test words with duplicate characters: + var combinedDupes = combiner.combineSymbols(List.of("b|a", "c|a")); + var separateDupes = combiner.separateSymbols(combinedDupes); + Assert.assertEquals(combinedDupes, "a|b|c"); + Assert.assertEquals(List.of("a", "b", "c"), separateDupes); + } + + @Test + public void testConfigurationProperties() { + var model = buildBaseModel(); + + var nonStableConfig = model.getSemantics().traceInputs(Word.fromSymbols( + new NonDelayingInput<>("p1"), new TimeStepSequence<>(12) + )); + Assert.assertFalse(nonStableConfig.isStableConfig()); + Assert.assertFalse(nonStableConfig.isEntryConfig()); + Assert.assertEquals(nonStableConfig.getEntryDistance(), 12); + Assert.assertEquals(nonStableConfig.getLocation().intValue(), 1); + + + var stableConfig = model.getSemantics().traceInputs(Word.fromSymbols( + new NonDelayingInput<>("p1"), new TimeStepSequence<>(2) + )); + Assert.assertTrue(stableConfig.isStableConfig()); + Assert.assertFalse(stableConfig.isEntryConfig()); + Assert.assertEquals(stableConfig.getEntryDistance(), 2); + Assert.assertEquals(stableConfig.getLocation().intValue(), 1); + } + + + @Test + public void testInvalidTimerChecks() { + var automaton = buildBaseModel(); + + int s1 = 1; + + // Duplicate timer name: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "a", 3, "test") + ); + + // Timer with silent output: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 3, "void") + ); + + // Timer never expires: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 41, "test") + ); + + // One-shot timer that times out at same time as periodic: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) + ); + + // Periodic timer that times out at same time as one-shot: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 20, "test") + ); + + // Duplicate one-shot timer: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) + ); + } +} diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java new file mode 100644 index 000000000..525518722 --- /dev/null +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java @@ -0,0 +1,96 @@ +package net.automatalib.serialization.dot; + +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.LocalTimerMealy; +import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.io.File; + +public class LocalTimerMealyDeserializationTest { + + @Test + public void parseSensorModel() { + // Load a model from file: + var resource = LocalTimerMealyDeserializationTest.class.getResource("/sensor_mmlt.dot"); + + var dotAutomaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(resource.getFile()), "void", StringSymbolCombiner.getInstance()); + + // Compare to reference: + var ndiP1 = new NonDelayingInput<>("p1"); + var ndiP2 = new NonDelayingInput<>("p2"); + var ndiAbort = new NonDelayingInput<>("abort"); + var ndiCollect = new NonDelayingInput<>("collect"); + + int s0 = 0; + int s1 = 1; + int s2 = 2; + int s3 = 3; + + // Check non-delaying transitions: + assertSilentLoop(dotAutomaton, s0, ndiAbort); + assertSilentLoop(dotAutomaton, s0, ndiCollect); + assertTransition(dotAutomaton, s0, s1, ndiP1, "go"); + assertTransition(dotAutomaton, s0, s2, ndiP2, "go"); + + assertTransition(dotAutomaton, s1, s1, ndiAbort, "ok"); + Assert.assertTrue(dotAutomaton.isLocalReset(s1, ndiAbort)); + assertSilentLoop(dotAutomaton, s1, ndiP1); + assertSilentLoop(dotAutomaton, s1, ndiP2); + assertSilentLoop(dotAutomaton, s1, ndiCollect); + + assertTransition(dotAutomaton, s2, s3, ndiAbort, "void"); + assertSilentLoop(dotAutomaton, s2, ndiP1); + assertSilentLoop(dotAutomaton, s2, ndiP2); + assertSilentLoop(dotAutomaton, s2, ndiCollect); + + assertSilentLoop(dotAutomaton, s3, ndiAbort); + assertSilentLoop(dotAutomaton, s3, ndiP1); + assertSilentLoop(dotAutomaton, s3, ndiP2); + assertTransition(dotAutomaton, s3, s0, ndiCollect, "void"); + + // Check timers: + Assert.assertTrue(dotAutomaton.getSortedTimers(s0).isEmpty()); + Assert.assertTrue(dotAutomaton.getSortedTimers(s3).isEmpty()); + Assert.assertEquals(dotAutomaton.getSortedTimers(s1).size(), 3); + Assert.assertEquals(dotAutomaton.getSortedTimers(s2).size(), 1); + + var firstTimerS1 = dotAutomaton.getSortedTimers(s1).get(0); + Assert.assertEquals(firstTimerS1.initial(), 3); + Assert.assertEquals(firstTimerS1.output(), "part"); + Assert.assertTrue(firstTimerS1.periodic()); + + var secondTimerS1 = dotAutomaton.getSortedTimers(s1).get(1); + Assert.assertEquals(secondTimerS1.initial(), 6); + Assert.assertEquals(secondTimerS1.output(), "noise"); + Assert.assertTrue(secondTimerS1.periodic()); + + var thirdTimerS1 = dotAutomaton.getSortedTimers(s1).get(2); + Assert.assertEquals(thirdTimerS1.initial(), 40); + Assert.assertEquals(thirdTimerS1.output(), "done"); + Assert.assertFalse(thirdTimerS1.periodic()); + + var firstTimerS2 = dotAutomaton.getSortedTimers(s2).get(0); + Assert.assertEquals(firstTimerS2.initial(), 4); + Assert.assertEquals(firstTimerS2.output(), "done"); + Assert.assertFalse(firstTimerS2.periodic()); + } + + private void assertTransition(LocalTimerMealy model, int state, int target, NonDelayingInput input, String output) { + var trans = model.getTransition(state, input); + if (trans == null || (trans.successor() != target) || !trans.output().equals(output)) { + throw new AssertionError(); + } + } + + private void assertSilentLoop(LocalTimerMealy model, int state, NonDelayingInput input) { + var trans = model.getTransition(state, input); + if (trans != null && (trans.successor() != state || !trans.output().equals("void"))) { + throw new AssertionError(); + } + Assert.assertFalse(model.isLocalReset(state, input)); + } + + +} diff --git a/serialization/dot/src/test/resources/sensor_mmlt.dot b/serialization/dot/src/test/resources/sensor_mmlt.dot new file mode 100644 index 000000000..a6a57caf8 --- /dev/null +++ b/serialization/dot/src/test/resources/sensor_mmlt.dot @@ -0,0 +1,26 @@ +// This is an example for a sensor node. +// 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/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java new file mode 100644 index 000000000..0567f60ad --- /dev/null +++ b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java @@ -0,0 +1,156 @@ +package net.automatalib.util.automaton.mmlt; + +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +import org.testng.Assert; +import org.testng.annotations.Test; + + +import java.util.List; + +public class LocalTimerMealyTests { + + public 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; + } + + @Test + public void testTimerAndResetRemovals() { + var model = buildBaseModel(); + + int s1 = 1; + int s3 = 3; + + // Remove and add some timers: + model.addPeriodicTimer(s1, "e", 12, "test"); + model.removeTimer(s1, "b"); + model.removeTimer(s1, "a"); + model.addPeriodicTimer(s1, "a", 3, "part"); + model.addPeriodicTimer(s1, "b", 6, "noise"); + model.removeTimer(s1, "c"); + model.addOneShotTimer(s1, "c", 40, "done", s3); + model.removeTimer(s1, "e"); + + model.removeLocalReset(s1, new NonDelayingInput<>("abort")); + model.addLocalReset(s1, new NonDelayingInput<>("abort")); + + // Still needs to be equivalent to original: + var originalModel = buildBaseModel(); + Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(model, originalModel)); + } + + @Test + public void testSeparatedByResetsSimple() { + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addSymbol(new NonDelayingInput<>("x")); + + // Same model, but with reset in A and no reset in B: + var modelA = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0 = modelA.addState(); + modelA.setInitialState(s0); + modelA.addPeriodicTimer(s0, "a", 3, "test"); + modelA.addTransition(s0, new NonDelayingInput<>("x"), "ok", s0); + modelA.addLocalReset(s0, new NonDelayingInput<>("x")); + + var modelB = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0B = modelB.addState(); + modelB.setInitialState(s0B); + modelB.addPeriodicTimer(s0B, "a", 3, "test"); + modelB.addTransition(s0B, new NonDelayingInput<>("x"), "ok", s0B); + + Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB)); + } + + @Test + public void testSeparatedByResetsComplex() { + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addSymbol(new NonDelayingInput<>("x")); + + // Same model, but with reset in A and no reset in B: + var modelA = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0 = modelA.addState(); + var s1 = modelA.addState(); + modelA.setInitialState(s0); + modelA.addPeriodicTimer(s0, "a", 3, "test"); + modelA.addOneShotTimer(s0, "b", 5, "test2", s1); + + var modelB = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0B = modelB.addState(); + var s1B = modelB.addState(); + var s2B = modelB.addState(); + modelB.setInitialState(s0B); + modelB.addOneShotTimer(s0B, "a", 3, "test", s1B); + modelB.addOneShotTimer(s1B, "b", 2, "test2", s2B); + modelB.addTransition(s1B, new NonDelayingInput<>("x"), "void", s1B); + modelB.addLocalReset(s1B, new NonDelayingInput<>("x")); + + Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB)); + } + + + @Test + public void testInvalidTimerChecks() { + var automaton = buildBaseModel(); + + int s1 = 1; + + // Duplicate timer name: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "a", 3, "test") + ); + + // Timer with silent output: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 3, "void") + ); + + // Timer never expires: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 41, "test") + ); + + // One-shot timer that times out at same time as periodic: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) + ); + + // Periodic timer that times out at same time as one-shot: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addPeriodicTimer(s1, "e", 20, "test") + ); + + // Duplicate one-shot timer: + Assert.assertThrows(IllegalArgumentException.class, + () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) + ); + } +} From 6f51d593f310eea7e0760cb444915d5e113b5bce Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 8 Oct 2025 08:55:36 +0200 Subject: [PATCH 04/18] Added serialization test for MMLTs. --- ...ionTest.java => LocalTimerMealyTests.java} | 36 +++++++++++++++++-- .../resources/demo_expected_serialized.dot | 20 +++++++++++ .../dot/src/test/resources/demo_mmlt.dot | 20 +++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) rename serialization/dot/src/test/java/net/automatalib/serialization/dot/{LocalTimerMealyDeserializationTest.java => LocalTimerMealyTests.java} (73%) create mode 100644 serialization/dot/src/test/resources/demo_expected_serialized.dot create mode 100644 serialization/dot/src/test/resources/demo_mmlt.dot diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java similarity index 73% rename from serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java rename to serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java index 525518722..8479380c9 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyDeserializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java @@ -7,13 +7,45 @@ import org.testng.annotations.Test; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; -public class LocalTimerMealyDeserializationTest { +public class LocalTimerMealyTests { + + @Test + public void parseAndWriteDemoModel() { + var demoResource = LocalTimerMealyTests.class.getResource("/demo_mmlt.dot"); + var expResultPath = Paths.get(LocalTimerMealyTests.class.getResource("/demo_expected_serialized.dot").getPath()); + var demoModel = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(demoResource.getFile()), "void", StringSymbolCombiner.getInstance()); + + + StringBuilder sbOutput = new StringBuilder(); + try { + List expected = Files.readAllLines(expResultPath).stream() + .filter(l -> !l.isBlank() && !l.startsWith("//")) + .map(String::trim) + .toList(); + + GraphDOT.write(demoModel.transitionGraphView(true, true), sbOutput); + var actualData = sbOutput.toString(); + System.out.println(actualData); + List actual = Arrays.stream(actualData.split("\\n")) + .filter(l -> !l.isBlank()) + .map(String::trim) + .toList(); + + Assert.assertEquals(expected, actual); + } catch (Exception ex) { + throw new AssertionError(); + } + } @Test public void parseSensorModel() { // Load a model from file: - var resource = LocalTimerMealyDeserializationTest.class.getResource("/sensor_mmlt.dot"); + var resource = LocalTimerMealyTests.class.getResource("/sensor_mmlt.dot"); var dotAutomaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(resource.getFile()), "void", StringSymbolCombiner.getInstance()); diff --git a/serialization/dot/src/test/resources/demo_expected_serialized.dot b/serialization/dot/src/test/resources/demo_expected_serialized.dot new file mode 100644 index 000000000..b7cbf2684 --- /dev/null +++ b/serialization/dot/src/test/resources/demo_expected_serialized.dot @@ -0,0 +1,20 @@ +// Resulting model that is expected when deserializing demo_mmlt.gv first, and then +// serializing the result with edge coloring and reset information enabled. +digraph g { + + s0 [timers="a=2" shape="circle" label="0"]; + s1 [timers="b=4,c=6" shape="circle" label="1"]; + s2 [timers="d=2,e=3" shape="circle" label="2"]; + s0 -> s1 [color="chartreuse3" fontcolor="chartreuse3" label="to[a] / A {b↦4,c↦6}"]; + s1 -> s1 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[b] / B {b↦4}"]; + s1 -> s1 [color="chartreuse3" fontcolor="chartreuse3" label="to[c] / C {b↦4,c↦6}"]; + s1 -> s2 [label="x / void {}"]; + s1 -> s1 [color="orange" fontcolor="orange" label="y / Y {b↦4,c↦6}"]; + s2 -> s2 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[d] / D {d↦2}"]; + s2 -> s2 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[e] / E {e↦3}"]; + s2 -> s2 [label="y / D {}"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} \ No newline at end of file diff --git a/serialization/dot/src/test/resources/demo_mmlt.dot b/serialization/dot/src/test/resources/demo_mmlt.dot new file mode 100644 index 000000000..d657df7bf --- /dev/null +++ b/serialization/dot/src/test/resources/demo_mmlt.dot @@ -0,0 +1,20 @@ +// This file demonstrates several MMLT features +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 From 4c7657e991b2db52963828fc60f3dc837e7cf2fa Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Wed, 8 Oct 2025 16:34:28 +0200 Subject: [PATCH 05/18] Separated more files into API and implementation to enable use from LearnLib. --- api/src/main/java/module-info.java | 3 + .../mmlt/ILocalTimerMealyInputSymbol.java | 2 +- .../ILocalTimerMealySemanticInputSymbol.java | 2 +- .../mmlt/LocalTimerMealyOutputSymbol.java | 2 +- .../alphabet}/time/mmlt/NonDelayingInput.java | 2 +- .../alphabet}/time/mmlt/TimeStepSequence.java | 2 +- .../alphabet}/time/mmlt/TimeStepSymbol.java | 2 +- .../alphabet}/time/mmlt/TimeoutSymbol.java | 2 +- .../time/mmlt/TimerTimeoutSymbol.java | 2 +- .../time/mmlt/AbstractSymbolCombiner.java | 0 .../automaton/time/mmlt/LocalTimerMealy.java | 8 +- .../LocalTimerMealyVisualizationHelper.java | 6 +- .../automaton/time/mmlt/MealyTimerInfo.java | 0 .../time/mmlt/MutableLocalTimerMealy.java | 2 +- .../LocalTimerMealyConfiguration.java | 2 +- .../LocalTimerMealySemanticTransition.java | 17 +++ .../semantics/LocalTimerMealySemantics.java | 97 +++++++++++++++++ .../time/mmlt/semantics/TimeoutPair.java | 0 core/src/main/java/module-info.java | 4 +- .../mmlt/CompactLocalTimerMealy.java | 18 +++- .../mmlt}/LocalTimerMealySemantics.java | 100 +++++------------- .../ReducedLocalTimerMealySemantics.java | 9 +- .../{ => impl}/mmlt/StringSymbolCombiner.java | 4 +- .../automaton/impl/LocalTimerMealyTests.java | 8 +- .../dot/LocalTimerMealyGraphvizParser.java | 3 +- .../dot/LocalTimerMealyTests.java | 4 +- .../automaton/cover/LocalTimerMealyCover.java | 4 +- .../automaton/mmlt/LocalTimerMealyUtil.java | 4 +- .../automaton/mmlt/LocalTimerMealyTests.java | 6 +- 29 files changed, 195 insertions(+), 120 deletions(-) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/ILocalTimerMealyInputSymbol.java (73%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/ILocalTimerMealySemanticInputSymbol.java (76%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/LocalTimerMealyOutputSymbol.java (96%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/NonDelayingInput.java (94%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/TimeStepSequence.java (96%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/TimeStepSymbol.java (80%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/TimeoutSymbol.java (90%) rename {core/src/main/java/net/automatalib/alphabet/impl => api/src/main/java/net/automatalib/alphabet}/time/mmlt/TimerTimeoutSymbol.java (95%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java (100%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java (96%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java (95%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java (100%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java (98%) rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java (98%) create mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java create mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java rename {core => api}/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java (100%) rename core/src/main/java/net/automatalib/automaton/time/{ => impl}/mmlt/CompactLocalTimerMealy.java (92%) rename core/src/main/java/net/automatalib/automaton/time/{mmlt/semantics => impl/mmlt}/LocalTimerMealySemantics.java (73%) rename core/src/main/java/net/automatalib/automaton/time/{mmlt/semantics => impl/mmlt}/ReducedLocalTimerMealySemantics.java (95%) rename core/src/main/java/net/automatalib/automaton/time/{ => impl}/mmlt/StringSymbolCombiner.java (93%) diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index dadb03ec7..4d5caf8c4 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -70,6 +70,9 @@ exports net.automatalib.ts.simple; exports net.automatalib.visualization; exports net.automatalib.word; + exports net.automatalib.alphabet.time.mmlt; + exports net.automatalib.automaton.time.mmlt.semantics; + exports net.automatalib.automaton.time.mmlt; uses VisualizationProvider; } diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java similarity index 73% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java index 796cf1439..71cd30f07 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealyInputSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; /** * Base type for input symbols for the structural automaton of an MMLT. diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java similarity index 76% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java index cd5618440..add92a233 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/ILocalTimerMealySemanticInputSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; /** * Base class for an input for the semantics automaton of some MMLT. diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java similarity index 96% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java index 8bdf590a4..a73aadb25 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/LocalTimerMealyOutputSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; import org.checkerframework.checker.nullness.qual.NonNull; diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java similarity index 94% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java index 74a903b33..a16ae1189 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/NonDelayingInput.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; import org.checkerframework.checker.nullness.qual.NonNull; diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java similarity index 96% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java index cc78c5e49..ff7f18268 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSequence.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; import java.util.Objects; diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java similarity index 80% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java index 8978a7866..c5a78e8d8 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeStepSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; /** * Input for the semantics automaton: delay for a single time step. diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java similarity index 90% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java index 65bde2880..46307ed45 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimeoutSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; import java.util.Objects; diff --git a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java similarity index 95% rename from core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java index eebd606dd..1caec3a57 100644 --- a/core/src/main/java/net/automatalib/alphabet/impl/time/mmlt/TimerTimeoutSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java @@ -1,4 +1,4 @@ -package net.automatalib.alphabet.impl.time.mmlt; +package net.automatalib.alphabet.time.mmlt; import java.util.Objects; diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java similarity index 100% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java similarity index 96% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java index 783d8b74c..9401c3310 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java @@ -1,8 +1,8 @@ package net.automatalib.automaton.time.mmlt; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.automaton.UniversalDeterministicAutomaton; import net.automatalib.automaton.graph.TransitionEdge; import net.automatalib.automaton.graph.UniversalAutomatonGraphView; @@ -110,9 +110,7 @@ default S getSuccessor(LocalTimerMealyTransition transition) { * * @return Semantics automaton */ - default LocalTimerMealySemantics getSemantics() { - return new LocalTimerMealySemantics<>(this); - } + LocalTimerMealySemantics getSemantics(); // ======================================= diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java similarity index 95% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java index d793ed2a4..57db24f54 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java @@ -1,8 +1,8 @@ package net.automatalib.automaton.time.mmlt; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.impl.time.mmlt.TimerTimeoutSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; import net.automatalib.automaton.graph.TransitionEdge; import net.automatalib.automaton.visualization.AutomatonVisualizationHelper; diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java similarity index 100% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java similarity index 98% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java index e684e55e8..445566288 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java @@ -1,6 +1,6 @@ package net.automatalib.automaton.time.mmlt; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; public interface MutableLocalTimerMealy { diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java similarity index 98% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java index e7f1c025e..a1a712876 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java @@ -1,6 +1,6 @@ package net.automatalib.automaton.time.mmlt.semantics; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java new file mode 100644 index 000000000..58e8235b2 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java @@ -0,0 +1,17 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; + +/** + * Represents a transition in the semantics automaton ("expanded form") of an MMLT. + * + * @param output Transition output + * @param target Transition target + * @param Location type + * @param Input type for non-delaying inputs + * @param Output symbol type + */ +public record LocalTimerMealySemanticTransition(LocalTimerMealyOutputSymbol output, + LocalTimerMealyConfiguration target) { + +} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java new file mode 100644 index 000000000..2a7b7ffb0 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java @@ -0,0 +1,97 @@ +package net.automatalib.automaton.time.mmlt.semantics; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +import net.automatalib.word.Word; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Defines the semantics of an MMLT. + *

+ * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are + * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values + * of this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the + * symbolic input timeout, which causes a delay until the next timeout. + *

+ * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all + * transitions, except for those with the input timeout. + * + * @param Location type + * @param Input type for non-delaying inputs + * @param Output type of the MMLT + */ +public interface LocalTimerMealySemantics { + /** + * Returns the input alphabet of the semantics automaton. This consists of all non-delaying inputs + * of the associated MMLT, as well as the time step symbol and the symbolic timeout symbol. + * + * @return Input alphabet + */ + Alphabet> getInputAlphabet(); + + /** + * Returns the symbol used for silent outputs. + * + * @return Silent output symbol + */ + LocalTimerMealyOutputSymbol getSilentOutput(); + + /** + * Returns the initial configuration of this MMLT. This is a tuple of the + * initial location and its initial timer values. + * + * @return Initial configuration + */ + LocalTimerMealyConfiguration getInitialConfiguration(); + + /** + * Enters the suffix into the provided configuration and returns corresponding outputs. + *

+ * Cannot provide TimeSequences with more than 1 symbol for the suffix, as this might trigger multiple timeouts + * and thus lead to output sequences that are longer than the suffix. + * + * @param configuration Configuration + * @param suffix Suffix inputs + * @return Outputs for the suffix + */ + Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix); + + /** + * Enters the prefix and suffix sequences into the automaton and returns the outputs that occur for the suffixes. + * + * @param prefix Configuration prefix + * @param suffix Suffix inputs + * @return Outputs for the suffix + */ + Word> computeSuffixOutput(Word> prefix, Word> suffix); + + /** + * Traces the provided prefix and returns the reached configuration. + * + * @param prefix Configuration prefix + * @return Reached configuration + */ + LocalTimerMealyConfiguration traceInputs(Word> prefix); + + @NonNull + LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input); + + /** + * Retrieves the transition in the semantics automaton that has the provided input and source configuration. + *

+ * If the input is a sequence of time steps, the target of the transition is the configuration reached after + * executing all time steps. If the sequence counts more than one step, the sequence might trigger multiple + * timeouts. To avoid ambiguity, the transition output is set to null in this case. + * If the sequence comprises a single time step only, the output is either that of a timeout or silence. + * + * @param source Source configuration + * @param input Input symbol + * @param maxWaitingTime Maximum time steps to wait for a timeout + * @return Transition in semantics automaton + */ + @NonNull + LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, + ILocalTimerMealySemanticInputSymbol input, + long maxWaitingTime); +} diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java similarity index 100% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java rename to api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index ab59cfb7a..07e6b9bab 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -52,7 +52,5 @@ exports net.automatalib.ts.modal.impl; exports net.automatalib.ts.modal.transition.impl; exports net.automatalib.ts.powerset.impl; - exports net.automatalib.alphabet.impl.time.mmlt; - exports net.automatalib.automaton.time.mmlt.semantics; - exports net.automatalib.automaton.time.mmlt; + exports net.automatalib.automaton.time.impl.mmlt; } diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java similarity index 92% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java rename to core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java index 6cf601ce1..ffdaf11da 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/CompactLocalTimerMealy.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java @@ -1,11 +1,16 @@ -package net.automatalib.automaton.time.mmlt; +package net.automatalib.automaton.time.impl.mmlt; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealyInputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.impl.time.mmlt.TimerTimeoutSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; +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.MutableLocalTimerMealy; +import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; import net.automatalib.automaton.transducer.impl.CompactMealy; import org.checkerframework.checker.nullness.qual.Nullable; @@ -85,6 +90,11 @@ public List> getSortedTimers(Integer location) { return Collections.unmodifiableList(this.sortedTimers.getOrDefault(location, Collections.emptyList())); } + @Override + public LocalTimerMealySemantics getSemantics() { + return new net.automatalib.automaton.time.impl.mmlt.LocalTimerMealySemantics<>(this); + } + @Override public Collection getStates() { return automaton.getStates(); diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java similarity index 73% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java rename to core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java index 20de8b9e4..4dc52b622 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java @@ -1,10 +1,12 @@ -package net.automatalib.automaton.time.mmlt.semantics; +package net.automatalib.automaton.time.impl.mmlt; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.*; +import net.automatalib.alphabet.time.mmlt.*; 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.LocalTimerMealySemanticTransition; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.NonNull; @@ -26,27 +28,13 @@ * @param Input type for non-delaying inputs * @param Output type of the MMLT */ -public class LocalTimerMealySemantics { +public class LocalTimerMealySemantics implements net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics { private final LocalTimerMealyConfiguration initialConfiguration; private final LocalTimerMealy model; private final Alphabet> alphabet; private final LocalTimerMealyOutputSymbol silentOutput; - /** - * Represents a transition in the semantics automaton ("expanded form") of an MMLT. - * - * @param output Transition output - * @param target Transition target - * @param Location type - * @param Input type - * @param Output type - */ - public record LocalTimerMealySemanticTransition(LocalTimerMealyOutputSymbol output, - LocalTimerMealyConfiguration target) { - - } - public LocalTimerMealySemantics(LocalTimerMealy model) { this.model = model; @@ -60,45 +48,26 @@ public LocalTimerMealySemantics(LocalTimerMealy model) { this.silentOutput = new LocalTimerMealyOutputSymbol<>(model.getSilentOutput()); } - /** - * Returns the input alphabet of the semantics automaton. This consists of all non-delaying inputs - * of the associated MMLT, as well as the time step symbol and the symbolic timeout symbol. - * - * @return Input alphabet - */ + + @Override public Alphabet> getInputAlphabet() { return alphabet; } - /** - * Returns the symbol used for silent outputs. - * - * @return Silent output symbol - */ + + @Override public LocalTimerMealyOutputSymbol getSilentOutput() { return this.silentOutput; } - /** - * Returns the initial configuration of this MMLT. This is a tuple of the - * initial location and its initial timer values. - * - * @return Initial configuration - */ + + @Override public LocalTimerMealyConfiguration getInitialConfiguration() { return this.initialConfiguration; } - /** - * Enters the suffix into the provided configuration and returns corresponding outputs. - *

- * Cannot provide TimeSequences with more than 1 symbol for the suffix, as this might trigger multiple timeouts - * and thus lead to output sequences that are longer than the suffix. - * - * @param configuration Configuration - * @param suffix Suffix inputs - * @return Outputs for the suffix - */ + + @Override public Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix) { WordBuilder> wbOutput = new WordBuilder<>(); @@ -116,24 +85,15 @@ public Word> computeSuffixOutput(LocalTimerMealyC return wbOutput.toWord(); } - /** - * Enters the prefix and suffix sequences into the automaton and returns the outputs that occur for the suffixes. - * - * @param prefix Configuration prefix - * @param suffix Suffix inputs - * @return Outputs for the suffix - */ + + @Override public Word> computeSuffixOutput(Word> prefix, Word> suffix) { var prefixConfig = this.traceInputs(prefix); return computeSuffixOutput(prefixConfig, suffix); } - /** - * Traces the provided prefix and returns the reached configuration. - * - * @param prefix Configuration prefix - * @return Reached configuration - */ + + @Override public LocalTimerMealyConfiguration traceInputs(Word> prefix) { var currentConfiguration = getInitialConfiguration().copy(); for (var sym : prefix) { @@ -143,28 +103,16 @@ public LocalTimerMealyConfiguration traceInputs(Word getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input) { + @Override + public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input) { return getTransition(source, input, Long.MAX_VALUE); } - /** - * Retrieves the transition in the semantics automaton that has the provided input and source configuration. - *

- * If the input is a sequence of time steps, the target of the transition is the configuration reached after - * executing all time steps. If the sequence counts more than one step, the sequence might trigger multiple - * timeouts. To avoid ambiguity, the transition output is set to null in this case. - * If the sequence comprises a single time step only, the output is either that of a timeout or silence. - * - * @param source Source configuration - * @param input Input symbol - * @param maxWaitingTime Maximum time steps to wait for a timeout - * @return Transition in semantics automaton - */ - @NonNull - public LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, - ILocalTimerMealySemanticInputSymbol input, - long maxWaitingTime) { + + @Override + public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, + ILocalTimerMealySemanticInputSymbol input, + long maxWaitingTime) { var sourceCopy = source.copy(); // we do not want to modify values of the source configuration if (input instanceof NonDelayingInput ndi) { diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java similarity index 95% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java rename to core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java index dbf99f5da..7508eb520 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/semantics/ReducedLocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java @@ -1,10 +1,11 @@ -package net.automatalib.automaton.time.mmlt.semantics; +package net.automatalib.automaton.time.impl.mmlt; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.LocalTimerMealyOutputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.TimeoutSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; +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.transducer.impl.CompactMealy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java similarity index 93% rename from core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java rename to core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java index 7d1991468..1157e118a 100644 --- a/core/src/main/java/net/automatalib/automaton/time/mmlt/StringSymbolCombiner.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java @@ -1,4 +1,6 @@ -package net.automatalib.automaton.time.mmlt; +package net.automatalib.automaton.time.impl.mmlt; + +import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; import java.util.Arrays; import java.util.HashSet; diff --git a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java index 71c8f4f23..c3f167fa5 100644 --- a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java +++ b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java @@ -1,10 +1,10 @@ package net.automatalib.automaton.impl; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.impl.time.mmlt.TimeStepSequence; -import net.automatalib.automaton.time.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import net.automatalib.word.Word; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java index acd28675a..f3478cb25 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java @@ -1,7 +1,8 @@ package net.automatalib.serialization.dot; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; import net.automatalib.automaton.time.mmlt.*; import net.automatalib.common.util.IOUtil; import net.automatalib.common.util.Pair; diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java index 8479380c9..0a443814b 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java @@ -1,8 +1,8 @@ package net.automatalib.serialization.dot; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java index 052d8beae..697ffdf14 100644 --- a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java +++ b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java @@ -1,8 +1,8 @@ package net.automatalib.util.automaton.cover; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.impl.time.mmlt.TimeoutSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +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.word.Word; diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java index 8b6ce0e4f..9eed88701 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java @@ -1,9 +1,9 @@ package net.automatalib.util.automaton.mmlt; -import net.automatalib.alphabet.impl.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; -import net.automatalib.automaton.time.mmlt.semantics.ReducedLocalTimerMealySemantics; +import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; import net.automatalib.util.automaton.Automata; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; diff --git a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java index 0567f60ad..3289db674 100644 --- a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java +++ b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java @@ -1,9 +1,9 @@ package net.automatalib.util.automaton.mmlt; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.impl.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.mmlt.StringSymbolCombiner; +import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import org.testng.Assert; import org.testng.annotations.Test; From d9311cc2f8a18ee024ae58a70e8bd842c0fb1b60 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Thu, 9 Oct 2025 11:28:38 +0200 Subject: [PATCH 06/18] Removed I prefix for some interfaces for the MMLT automaton. --- ...l.java => LocalTimerMealyInputSymbol.java} | 2 +- ...> LocalTimerMealySemanticInputSymbol.java} | 4 ++-- .../alphabet/time/mmlt/NonDelayingInput.java | 2 +- .../alphabet/time/mmlt/TimeStepSequence.java | 2 +- .../alphabet/time/mmlt/TimeoutSymbol.java | 2 +- .../time/mmlt/TimerTimeoutSymbol.java | 2 +- .../automaton/time/mmlt/LocalTimerMealy.java | 24 +++++++++---------- .../LocalTimerMealyVisualizationHelper.java | 6 ++--- .../LocalTimerMealyConfiguration.java | 4 ++-- .../semantics/LocalTimerMealySemantics.java | 14 +++++------ .../impl/mmlt/CompactLocalTimerMealy.java | 10 ++++---- .../impl/mmlt/LocalTimerMealySemantics.java | 14 +++++------ .../mmlt/ReducedLocalTimerMealySemantics.java | 6 ++--- util/src/main/java/module-info.java | 1 + .../automaton/cover/LocalTimerMealyCover.java | 12 +++++----- .../automaton/mmlt/LocalTimerMealyUtil.java | 4 ++-- 16 files changed, 55 insertions(+), 54 deletions(-) rename api/src/main/java/net/automatalib/alphabet/time/mmlt/{ILocalTimerMealyInputSymbol.java => LocalTimerMealyInputSymbol.java} (71%) rename api/src/main/java/net/automatalib/alphabet/time/mmlt/{ILocalTimerMealySemanticInputSymbol.java => LocalTimerMealySemanticInputSymbol.java} (64%) diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java similarity index 71% rename from api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java index 71cd30f07..4843139f0 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealyInputSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java @@ -3,5 +3,5 @@ /** * Base type for input symbols for the structural automaton of an MMLT. */ -public interface ILocalTimerMealyInputSymbol { +public interface LocalTimerMealyInputSymbol { } diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java similarity index 64% rename from api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java rename to api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java index add92a233..294583de0 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/ILocalTimerMealySemanticInputSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java @@ -3,8 +3,8 @@ /** * Base class for an input for the semantics automaton of some MMLT. * - * @param + * @param */ -public interface ILocalTimerMealySemanticInputSymbol { +public interface LocalTimerMealySemanticInputSymbol { } diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java index a16ae1189..594fed52e 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java @@ -9,7 +9,7 @@ * * @param */ -public class NonDelayingInput implements ILocalTimerMealySemanticInputSymbol, ILocalTimerMealyInputSymbol { +public class NonDelayingInput implements LocalTimerMealySemanticInputSymbol, LocalTimerMealyInputSymbol { private final U symbol; public NonDelayingInput(@NonNull U input) { diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java index ff7f18268..ad4f50b72 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java @@ -5,7 +5,7 @@ /** * Convenience type for aggregating multiple subsequent time steps. */ -public class TimeStepSequence implements ILocalTimerMealySemanticInputSymbol { +public class TimeStepSequence implements LocalTimerMealySemanticInputSymbol { private long timeSteps; diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java index 46307ed45..a7a789d94 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java @@ -5,7 +5,7 @@ /** * Symbolic input for the semantics automaton: causes a delay until the next timeout. */ -public class TimeoutSymbol implements ILocalTimerMealySemanticInputSymbol { +public class TimeoutSymbol implements LocalTimerMealySemanticInputSymbol { @Override public String toString() { diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java index 1caec3a57..22f90d57b 100644 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java +++ b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java @@ -10,7 +10,7 @@ * * @param */ -public class TimerTimeoutSymbol implements ILocalTimerMealyInputSymbol { +public class TimerTimeoutSymbol implements LocalTimerMealyInputSymbol { private final String timer; public TimerTimeoutSymbol(String timer) { diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java index 9401c3310..1ebd06503 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java @@ -1,7 +1,7 @@ package net.automatalib.automaton.time.mmlt; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.automaton.UniversalDeterministicAutomaton; import net.automatalib.automaton.graph.TransitionEdge; @@ -40,7 +40,7 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public interface LocalTimerMealy extends UniversalDeterministicAutomaton, LocalTimerMealy.LocalTimerMealyTransition, Void, O> { +public interface LocalTimerMealy extends UniversalDeterministicAutomaton, LocalTimerMealy.LocalTimerMealyTransition, Void, O> { record LocalTimerMealyTransition(@NonNull S successor, @NonNull O output) { @@ -68,7 +68,7 @@ record LocalTimerMealyTransition(@NonNull S successor, @NonNull O output) * * @return Input alphabet */ - Alphabet> getInputAlphabet(); + Alphabet> getInputAlphabet(); /** * Retrieves the non-delaying inputs for this automaton. @@ -115,15 +115,15 @@ default S getSuccessor(LocalTimerMealyTransition transition) { // ======================================= @Override - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(Collection> inputs) { + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(Collection> inputs) { return new LocalTimerMealyGraphView<>(this, inputs, false, false); } - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView() { + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView() { return this.transitionGraphView(getInputAlphabet()); } - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(boolean colorEdges, boolean includeResets) { + default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(boolean colorEdges, boolean includeResets) { return new LocalTimerMealyGraphView<>(this, getInputAlphabet(), colorEdges, includeResets); } @@ -136,32 +136,32 @@ default Void getStateProperty(S state) { // We do not want to provide tracing abilities for inputs of the structure automaton: @Override - default Set getStates(Iterable> input) { + default Set getStates(Iterable> input) { throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); } @Override @Nullable - default S getSuccessor(S state, Iterable> input) { + default S getSuccessor(S state, Iterable> input) { throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); } @Override @Nullable - default S getState(Iterable> input) { + default S getState(Iterable> input) { throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); } // ======================================= class LocalTimerMealyGraphView extends - UniversalAutomatonGraphView, LocalTimerMealy.LocalTimerMealyTransition, Void, OX, LocalTimerMealy> { + UniversalAutomatonGraphView, LocalTimerMealy.LocalTimerMealyTransition, Void, OX, LocalTimerMealy> { private final boolean colorEdges; private final boolean includeResets; public LocalTimerMealyGraphView(LocalTimerMealy automaton, - Collection> inputs, + Collection> inputs, boolean colorEdges, boolean includeResets) { super(automaton, inputs); this.colorEdges = colorEdges; @@ -169,7 +169,7 @@ public LocalTimerMealyGraphView(LocalTimerMealy automaton, } @Override - public VisualizationHelper, LocalTimerMealyTransition>> getVisualizationHelper() { + public VisualizationHelper, LocalTimerMealyTransition>> getVisualizationHelper() { return new LocalTimerMealyVisualizationHelper<>(automaton, colorEdges, includeResets); } } diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java index 57db24f54..9ece20559 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java @@ -1,6 +1,6 @@ package net.automatalib.automaton.time.mmlt; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; import net.automatalib.automaton.graph.TransitionEdge; @@ -10,7 +10,7 @@ import java.util.stream.Collectors; class LocalTimerMealyVisualizationHelper extends - AutomatonVisualizationHelper, LocalTimerMealy.LocalTimerMealyTransition, LocalTimerMealy> { + AutomatonVisualizationHelper, LocalTimerMealy.LocalTimerMealyTransition, LocalTimerMealy> { private final boolean colorEdges; private final boolean includeResets; @@ -52,7 +52,7 @@ public boolean getNodeProperties(S node, Map properties) { @Override public boolean getEdgeProperties(S src, - TransitionEdge, LocalTimerMealy.LocalTimerMealyTransition> edge, + TransitionEdge, LocalTimerMealy.LocalTimerMealyTransition> edge, S tgt, Map properties) { super.getEdgeProperties(src, edge, tgt, properties); diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java index a1a712876..93867cdcc 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java @@ -1,6 +1,6 @@ package net.automatalib.automaton.time.mmlt.semantics; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +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.Nullable; @@ -34,7 +34,7 @@ public LocalTimerMealyConfiguration(S location, List> sortedTi this(location, sortedTimers, null); } - public LocalTimerMealyConfiguration(S location, List> sortedTimers, Word> locationPrefix) { + public LocalTimerMealyConfiguration(S location, List> sortedTimers, Word> locationPrefix) { this.location = location; this.sortedTimers = sortedTimers; diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java index 2a7b7ffb0..94fc66985 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java @@ -1,7 +1,7 @@ package net.automatalib.automaton.time.mmlt.semantics; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +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.NonNull; @@ -28,7 +28,7 @@ public interface LocalTimerMealySemantics { * * @return Input alphabet */ - Alphabet> getInputAlphabet(); + Alphabet> getInputAlphabet(); /** * Returns the symbol used for silent outputs. @@ -55,7 +55,7 @@ public interface LocalTimerMealySemantics { * @param suffix Suffix inputs * @return Outputs for the suffix */ - Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix); + Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix); /** * Enters the prefix and suffix sequences into the automaton and returns the outputs that occur for the suffixes. @@ -64,7 +64,7 @@ public interface LocalTimerMealySemantics { * @param suffix Suffix inputs * @return Outputs for the suffix */ - Word> computeSuffixOutput(Word> prefix, Word> suffix); + Word> computeSuffixOutput(Word> prefix, Word> suffix); /** * Traces the provided prefix and returns the reached configuration. @@ -72,10 +72,10 @@ public interface LocalTimerMealySemantics { * @param prefix Configuration prefix * @return Reached configuration */ - LocalTimerMealyConfiguration traceInputs(Word> prefix); + LocalTimerMealyConfiguration traceInputs(Word> prefix); @NonNull - LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input); + LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, LocalTimerMealySemanticInputSymbol input); /** * Retrieves the transition in the semantics automaton that has the provided input and source configuration. @@ -92,6 +92,6 @@ public interface LocalTimerMealySemantics { */ @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, - ILocalTimerMealySemanticInputSymbol input, + LocalTimerMealySemanticInputSymbol input, long maxWaitingTime); } diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java index ffdaf11da..049015026 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java @@ -3,7 +3,7 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.GrowingAlphabet; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealyInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; import net.automatalib.alphabet.time.mmlt.NonDelayingInput; import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; @@ -24,7 +24,7 @@ * @param Output symbol type */ public class CompactLocalTimerMealy implements LocalTimerMealy, MutableLocalTimerMealy { - private final CompactMealy, O> automaton; + private final CompactMealy, O> automaton; private final Map>> sortedTimers; // location -> (sorted timers) private final Map>> resets; // location -> inputs (that reset all timers) @@ -54,7 +54,7 @@ public CompactLocalTimerMealy(Collection> nonDelayingInputs, this.outputCombiner = outputCombiner; // Prepare compact Mealy: - GrowingMapAlphabet> inputAlphabet = new GrowingMapAlphabet<>(); + GrowingMapAlphabet> inputAlphabet = new GrowingMapAlphabet<>(); inputAlphabet.addAll(nonDelayingInputs); this.automaton = new CompactMealy<>(inputAlphabet); } @@ -71,7 +71,7 @@ public AbstractSymbolCombiner getOutputCombiner() { } @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return this.automaton.getInputAlphabet(); } @@ -101,7 +101,7 @@ public Collection getStates() { } @Override - public @Nullable LocalTimerMealyTransition getTransition(Integer location, ILocalTimerMealyInputSymbol input) { + public @Nullable LocalTimerMealyTransition getTransition(Integer location, LocalTimerMealyInputSymbol input) { var trans = this.automaton.getTransition(location, input); if (trans == null) { return null; diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java index 4dc52b622..e7411b296 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java @@ -32,7 +32,7 @@ public class LocalTimerMealySemantics implements net.automatalib.automa private final LocalTimerMealyConfiguration initialConfiguration; private final LocalTimerMealy model; - private final Alphabet> alphabet; + private final Alphabet> alphabet; private final LocalTimerMealyOutputSymbol silentOutput; public LocalTimerMealySemantics(LocalTimerMealy model) { @@ -50,7 +50,7 @@ public LocalTimerMealySemantics(LocalTimerMealy model) { @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return alphabet; } @@ -68,7 +68,7 @@ public LocalTimerMealyConfiguration getInitialConfiguration() { @Override - public Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix) { + public Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix) { WordBuilder> wbOutput = new WordBuilder<>(); var currentConfiguration = configuration; @@ -87,14 +87,14 @@ public Word> computeSuffixOutput(LocalTimerMealyC @Override - public Word> computeSuffixOutput(Word> prefix, Word> suffix) { + public Word> computeSuffixOutput(Word> prefix, Word> suffix) { var prefixConfig = this.traceInputs(prefix); return computeSuffixOutput(prefixConfig, suffix); } @Override - public LocalTimerMealyConfiguration traceInputs(Word> prefix) { + public LocalTimerMealyConfiguration traceInputs(Word> prefix) { var currentConfiguration = getInitialConfiguration().copy(); for (var sym : prefix) { currentConfiguration = getTransition(currentConfiguration, sym).target(); @@ -104,14 +104,14 @@ public LocalTimerMealyConfiguration traceInputs(Word getTransition(LocalTimerMealyConfiguration source, ILocalTimerMealySemanticInputSymbol input) { + public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, LocalTimerMealySemanticInputSymbol input) { return getTransition(source, input, Long.MAX_VALUE); } @Override public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, - ILocalTimerMealySemanticInputSymbol input, + LocalTimerMealySemanticInputSymbol input, long maxWaitingTime) { var sourceCopy = source.copy(); // we do not want to modify values of the source configuration diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java index 7508eb520..096dd9ab8 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java @@ -1,7 +1,7 @@ package net.automatalib.automaton.time.impl.mmlt; import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; @@ -28,13 +28,13 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ReducedLocalTimerMealySemantics extends CompactMealy, LocalTimerMealyOutputSymbol> { +public class ReducedLocalTimerMealySemantics extends CompactMealy, LocalTimerMealyOutputSymbol> { private final static Logger logger = LoggerFactory.getLogger(ReducedLocalTimerMealySemantics.class); private final Map, Integer> stateMap; - private ReducedLocalTimerMealySemantics(Alphabet> alphabet) { + private ReducedLocalTimerMealySemantics(Alphabet> alphabet) { super(alphabet); this.stateMap = new HashMap<>(); } diff --git a/util/src/main/java/module-info.java b/util/src/main/java/module-info.java index 61e6fba30..7139d86e4 100644 --- a/util/src/main/java/module-info.java +++ b/util/src/main/java/module-info.java @@ -70,4 +70,5 @@ exports net.automatalib.util.ts.modal; exports net.automatalib.util.ts.transducer; exports net.automatalib.util.ts.traversal; + exports net.automatalib.util.automaton.mmlt; } diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java index 697ffdf14..4ab017300 100644 --- a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java +++ b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java @@ -1,7 +1,7 @@ package net.automatalib.util.automaton.cover; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.alphabet.time.mmlt.TimeoutSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; @@ -14,7 +14,7 @@ public class LocalTimerMealyCover { - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton) { + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton) { return getLocalTimerMealyLocationCover(automaton, true); } @@ -29,16 +29,16 @@ public static Map>> get * @param allowIncomplete If set, no error is thrown if some locations are unreachable. * @return Location cover in format location -> prefix. */ - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, boolean allowIncomplete) { - Map>> locPrefixes = new HashMap<>(); - Map, Word>> cfgPrefixes = new HashMap<>(); + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, boolean allowIncomplete) { + Map>> locPrefixes = new HashMap<>(); + Map, Word>> cfgPrefixes = new HashMap<>(); List> queue = new ArrayList<>(); queue.add(automaton.getSemantics().getInitialConfiguration()); cfgPrefixes.put(automaton.getSemantics().getInitialConfiguration(), Word.epsilon()); locPrefixes.put(automaton.getInitialState(), Word.epsilon()); - GrowingMapAlphabet> exploreAlphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet()); + GrowingMapAlphabet> exploreAlphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet()); exploreAlphabet.add(new TimeoutSymbol<>()); while (!queue.isEmpty()) { diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java index 9eed88701..a71d7fa74 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java @@ -1,6 +1,6 @@ package net.automatalib.util.automaton.mmlt; -import net.automatalib.alphabet.time.mmlt.ILocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.time.mmlt.MealyTimerInfo; import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; @@ -19,7 +19,7 @@ */ public class LocalTimerMealyUtil { - public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB) { + public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB) { var expandedA = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelA); var expandedB = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelB); From e33c012b65b17cb7912e07b0e3afaadabff367b1 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 08:52:37 +0200 Subject: [PATCH 07/18] Updated calculation of MMLT location cover to accept a list of inputs to be considered. --- .../automaton/cover/LocalTimerMealyCover.java | 23 ++--- .../cover/LocalTimerMealyCoverTest.java | 91 +++++++++++++++++++ 2 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java index 4ab017300..ff4a0780a 100644 --- a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java +++ b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java @@ -1,35 +1,33 @@ package net.automatalib.util.automaton.cover; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -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.word.Word; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; public class LocalTimerMealyCover { - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton) { - return getLocalTimerMealyLocationCover(automaton, true); + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs) { + return getLocalTimerMealyLocationCover(automaton, inputs, true); } /** * Calculates a location cover for an MMLT. *

* The cover provides one prefix for each location of the MMLT. - * The returned prefixes use inputs for the extended semantics. + * The returned prefixes use only the provided inputs. + *

* If some locations are isolated, they are excluded from the cover. + * If some locations cannot be reached with the provided inputs, they are also excluded. * * @param automaton MMLT + * @param inputs The inputs to use when calculating the cover. Must be an ordered collection. * @param allowIncomplete If set, no error is thrown if some locations are unreachable. * @return Location cover in format location -> prefix. */ - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, boolean allowIncomplete) { + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs, boolean allowIncomplete) { Map>> locPrefixes = new HashMap<>(); Map, Word>> cfgPrefixes = new HashMap<>(); List> queue = new ArrayList<>(); @@ -38,12 +36,9 @@ public static Map>> getL cfgPrefixes.put(automaton.getSemantics().getInitialConfiguration(), Word.epsilon()); locPrefixes.put(automaton.getInitialState(), Word.epsilon()); - GrowingMapAlphabet> exploreAlphabet = new GrowingMapAlphabet<>(automaton.getUntimedAlphabet()); - exploreAlphabet.add(new TimeoutSymbol<>()); - while (!queue.isEmpty()) { var current = queue.remove(0); - for (var symbol : exploreAlphabet) { + for (var symbol : inputs) { var trans = automaton.getSemantics().getTransition(current, symbol); if (trans.target().equals(current)) { continue; // self-loop diff --git a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java new file mode 100644 index 000000000..75f35a0b6 --- /dev/null +++ b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java @@ -0,0 +1,91 @@ +package net.automatalib.util.automaton.cover; + +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.TimeoutSymbol; +import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.List; + +@Test +public class LocalTimerMealyCoverTest { + 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; + } + + @Test + public void computeFullCover() { + var automaton = buildBaseModel(); + var cover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, automaton.getSemantics().getInputAlphabet()); + + // Verify that all states are covered: + Assert.assertEquals(cover.size(), automaton.size()); + for (var state : automaton.getStates()) { + Assert.assertTrue(cover.containsKey(state)); + } + + // Verify that the assigned prefixes are correct: + for (var entry : cover.entrySet()) { + var targetConfig = automaton.getSemantics().traceInputs(entry.getValue()); + Assert.assertTrue(targetConfig.isEntryConfig()); + Assert.assertEquals(targetConfig.getLocation(), entry.getKey()); + } + } + + @Test + public void computeIncompleteCover() { + var automaton = buildBaseModel(); + + // Create alphabet where state 3 is unreachable: + var symbols = List.of("p1", "p2", "collect"); + GrowingMapAlphabet> partialAlphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> partialAlphabet.add(new NonDelayingInput<>(s))); + + // Test if detecting incomplete cover: + Assert.assertThrows(AssertionError.class, () -> LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet, false)); + + // Verify that state 3 is not covered: + var cover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet); + Assert.assertTrue(cover.size() < automaton.size()); + for (var state : automaton.getStates()) { + if (state != 3) { + Assert.assertTrue(cover.containsKey(state)); + } else { + Assert.assertFalse(cover.containsKey(state)); + } + } + + } + +} From a0e713c316578437cedff47c87c09cb1c1075d04 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 08:58:10 +0200 Subject: [PATCH 08/18] Method for finding a separating word in an MMLT now considers a provided collection of inputs. --- .../automaton/mmlt/LocalTimerMealyUtil.java | 5 +++-- .../automaton/mmlt/LocalTimerMealyTests.java | 19 ++++++++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java index a71d7fa74..1679ae4b1 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java @@ -19,11 +19,12 @@ */ public class LocalTimerMealyUtil { - public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB) { + public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB, + Collection> inputs) { var expandedA = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelA); var expandedB = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelB); - var separatingWord = Automata.findSeparatingWord(expandedA, expandedB, expandedA.getInputAlphabet()); + var separatingWord = Automata.findSeparatingWord(expandedA, expandedB, inputs); if (separatingWord != null) { var outputA = modelA.getSemantics().computeSuffixOutput(Word.epsilon(), separatingWord); diff --git a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java index 3289db674..15c1ddc67 100644 --- a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java +++ b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java @@ -1,13 +1,16 @@ package net.automatalib.util.automaton.mmlt; 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.TimeoutSymbol; import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import org.testng.Assert; import org.testng.annotations.Test; +import java.util.ArrayList; import java.util.List; public class LocalTimerMealyTests { @@ -65,7 +68,7 @@ public void testTimerAndResetRemovals() { // Still needs to be equivalent to original: var originalModel = buildBaseModel(); - Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(model, originalModel)); + Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(model, originalModel, originalModel.getSemantics().getInputAlphabet())); } @Test @@ -87,7 +90,12 @@ public void testSeparatedByResetsSimple() { modelB.addPeriodicTimer(s0B, "a", 3, "test"); modelB.addTransition(s0B, new NonDelayingInput<>("x"), "ok", s0B); - Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB)); + Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); + + // If we remove the timestep, should not find a counterexample: + List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); + reducedInputs.add(new TimeoutSymbol<>()); + Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, reducedInputs)); } @Test @@ -113,7 +121,12 @@ public void testSeparatedByResetsComplex() { modelB.addTransition(s1B, new NonDelayingInput<>("x"), "void", s1B); modelB.addLocalReset(s1B, new NonDelayingInput<>("x")); - Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB)); + Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); + + // If we remove the timestep, should not find a counterexample: + List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); + reducedInputs.add(new TimeoutSymbol<>()); + Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, reducedInputs)); } From c14653dd3adbf32e6d5d4df028771a862a40b62b Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 09:20:27 +0200 Subject: [PATCH 09/18] Convenience flag for faster location cover calculation. --- .../automaton/cover/LocalTimerMealyCover.java | 15 +++++++++++++-- .../automaton/cover/LocalTimerMealyCoverTest.java | 2 +- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java index ff4a0780a..b0e6deacd 100644 --- a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java +++ b/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java @@ -1,6 +1,8 @@ package net.automatalib.util.automaton.cover; import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; +import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; import net.automatalib.automaton.time.mmlt.LocalTimerMealy; import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; import net.automatalib.word.Word; @@ -10,7 +12,7 @@ public class LocalTimerMealyCover { public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs) { - return getLocalTimerMealyLocationCover(automaton, inputs, true); + return getLocalTimerMealyLocationCover(automaton, inputs, true, true); } /** @@ -18,16 +20,21 @@ public static Map>> getL *

* The cover provides one prefix for each location of the MMLT. * The returned prefixes use only the provided inputs. + * Time steps are not needed to calculate a full cover. If set, they are ignored when calculating the cover, + * even if they are part of the provided inputs. *

* If some locations are isolated, they are excluded from the cover. * If some locations cannot be reached with the provided inputs, they are also excluded. * * @param automaton MMLT * @param inputs The inputs to use when calculating the cover. Must be an ordered collection. + * @param ignoreTimeStep If this option is set, time steps are ignored when calculating the cover, + * even if they are part of the provided inputs. This is a convenience option for not having to + * create a separate alphabet when calculating location covers. * @param allowIncomplete If set, no error is thrown if some locations are unreachable. * @return Location cover in format location -> prefix. */ - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs, boolean allowIncomplete) { + public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs, boolean ignoreTimeStep, boolean allowIncomplete) { Map>> locPrefixes = new HashMap<>(); Map, Word>> cfgPrefixes = new HashMap<>(); List> queue = new ArrayList<>(); @@ -39,6 +46,10 @@ public static Map>> getL while (!queue.isEmpty()) { var current = queue.remove(0); for (var symbol : inputs) { + if (symbol instanceof TimeStepSequence) { + continue; + } + var trans = automaton.getSemantics().getTransition(current, symbol); if (trans.target().equals(current)) { continue; // self-loop diff --git a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java index 75f35a0b6..f3d5f567e 100644 --- a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java +++ b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java @@ -73,7 +73,7 @@ public void computeIncompleteCover() { symbols.forEach(s -> partialAlphabet.add(new NonDelayingInput<>(s))); // Test if detecting incomplete cover: - Assert.assertThrows(AssertionError.class, () -> LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet, false)); + Assert.assertThrows(AssertionError.class, () -> LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet, true, false)); // Verify that state 3 is not covered: var cover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet); From 985aee6d5009683fdbeeb5f5c744918be03b3a78 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Fri, 10 Oct 2025 09:58:23 +0200 Subject: [PATCH 10/18] Added more tests for the reduced semantics. --- .../mmlt/ReducedLocalTimerMealySemantics.java | 15 +++++ .../automaton/impl/LocalTimerMealyTests.java | 59 ++++++++++++++++++- .../cover/LocalTimerMealyCoverTest.java | 1 - 3 files changed, 72 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java index 096dd9ab8..28ed120f9 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java @@ -160,4 +160,19 @@ public Integer getStateForConfiguration(LocalTimerMealyConfiguration co return stateMap.get(closestMatch); } + + /** + * Returns the configuration that represents the provided state. + * Throws an error if the state is not part of the reduced automaton. + * + * @param state Considered state + * @return Corresponding configuration + */ + public LocalTimerMealyConfiguration getConfigurationForState(int state) { + return this.stateMap.entrySet().stream() + .filter(e -> e.getValue() == state) + .map(Map.Entry::getKey) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Could not find corresponding configuration in expanded form.")); + } } diff --git a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java index c3f167fa5..43f6cab24 100644 --- a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java +++ b/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java @@ -1,9 +1,9 @@ package net.automatalib.automaton.impl; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; +import net.automatalib.alphabet.time.mmlt.*; import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; +import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import net.automatalib.word.Word; import org.testng.Assert; @@ -113,6 +113,61 @@ public void testConfigurationProperties() { Assert.assertEquals(stableConfig.getLocation().intValue(), 1); } + @Test + public void testReducedSemanticsIncludedConfiguration() { + var automaton = buildBaseModel(); + var reducedSemanticsModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(automaton); + Assert.assertEquals(reducedSemanticsModel.size(), 31); + + // Reachable in both automata: + Word> includedConfigPrefix = Word.fromSymbols( + new NonDelayingInput<>("p1"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), + new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeStepSymbol<>() + ); + var includedConfig = automaton.getSemantics().traceInputs(includedConfigPrefix); + + // Verify that the reached states are identical: + var expectedState = reducedSemanticsModel.getStateForConfiguration(includedConfig, false); + var reachedState = reducedSemanticsModel.getState(includedConfigPrefix); + + // Verify that the output is identical: + var fullOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), includedConfigPrefix); + var reducedOutput = reducedSemanticsModel.computeOutput(includedConfigPrefix); + Assert.assertEquals(fullOutput, reducedOutput); + + Assert.assertEquals(expectedState, reachedState); + } + + @Test + public void testReducedSemanticsOmittedConfiguration() { + var automaton = buildBaseModel(); + var reducedSemanticsModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(automaton); + Assert.assertEquals(reducedSemanticsModel.size(), 31); + + // Only reachable via at least two following time steps: + Word> omittedConfigPrefix = Word.fromSymbols( + new NonDelayingInput<>("p1"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), + new TimeStepSymbol<>(), new TimeStepSymbol<>() + ); + var omittedConfig = automaton.getSemantics().traceInputs(omittedConfigPrefix); + + // Verify that we cannot reach this state in the reduced semantics: + Assert.assertNull(reducedSemanticsModel.getState(omittedConfigPrefix)); + Assert.assertThrows(IllegalStateException.class, () -> reducedSemanticsModel.getStateForConfiguration(omittedConfig, false)); + + // Verify that the output is incomplete: + var fullOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), omittedConfigPrefix); + var reducedOutput = reducedSemanticsModel.computeOutput(omittedConfigPrefix); + Assert.assertNotEquals(fullOutput, reducedOutput); + + // Check the approximated state: + var approxStateId = reducedSemanticsModel.getStateForConfiguration(omittedConfig, true); + var approxConfig = reducedSemanticsModel.getConfigurationForState(approxStateId); + + Assert.assertEquals(approxConfig.getLocation(), omittedConfig.getLocation()); + Assert.assertEquals(approxConfig.getEntryDistance(), 7); + } + @Test public void testInvalidTimerChecks() { diff --git a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java index f3d5f567e..51cca14ee 100644 --- a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java +++ b/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java @@ -3,7 +3,6 @@ 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.TimeoutSymbol; import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; import org.testng.Assert; From 9afd4fac89002a95fc7562dc5332d15666807970 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 13 Oct 2025 13:32:59 +0200 Subject: [PATCH 11/18] Parser for MMLTs now accepts an input stream, in addition to a file. --- .../dot/LocalTimerMealyGraphvizParser.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java index f3478cb25..5fe9c6398 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java @@ -6,6 +6,7 @@ import net.automatalib.automaton.time.mmlt.*; import net.automatalib.common.util.IOUtil; import net.automatalib.common.util.Pair; +import net.automatalib.exception.FormatException; import org.checkerframework.checker.nullness.qual.Nullable; import java.io.File; @@ -81,6 +82,17 @@ public class LocalTimerMealyGraphvizParser { private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); + public static LocalTimerMealy parseLocalTimerMealy(InputStream stream, String silentOutput, AbstractSymbolCombiner outputCombiner) { + InternalDOTParser parser; + try { + parser = new InternalDOTParser(stream); + parser.parse(); + } catch (FormatException fe) { + throw new RuntimeException(String.format("Parsing failed: %s", fe)); + } + return parseLocalTimerMealy(parser, silentOutput, outputCombiner); + } + public static LocalTimerMealy parseLocalTimerMealy(File path, String silentOutput, AbstractSymbolCombiner outputCombiner) { InternalDOTParser parser; try (InputStream r = IOUtil.asUncompressedBufferedInputStream(new FileInputStream(path))) { From f526fcb83201245e96e7014dba7e7df8e9a4c6d4 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Mon, 10 Nov 2025 11:51:10 +0100 Subject: [PATCH 12/18] Updated Serialization for MMLTs: resets-attribute for edges is now included when resets cannot be inferred from context. --- .../LocalTimerMealyVisualizationHelper.java | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java index 9ece20559..435bb5f94 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java @@ -59,6 +59,7 @@ public boolean getEdgeProperties(S src, String label = String.format("%s / %s", edge.getInput(), edge.getTransition().output()); // Infer the label color + reset information for the transition: + String resetExtraInfo = ""; String resetInfo = ""; String edgeColor = ""; if (edge.getInput() instanceof TimerTimeoutSymbol ts) { @@ -70,23 +71,37 @@ public boolean getEdgeProperties(S src, if (optTimer.get().periodic()) { // Periodic -> resets itself: - resetInfo = String.format("%s↦%d", optTimer.get().name(), optTimer.get().initial()); + resetExtraInfo = String.format("%s↦%d", optTimer.get().name(), optTimer.get().initial()); edgeColor = "cornflowerblue"; } else { // One-shot -> resets all in target: - resetInfo = automaton.getSortedTimers(tgt).stream() + resetExtraInfo = automaton.getSortedTimers(tgt).stream() .map(t -> String.format("%s↦%d", t.name(), t.initial())) .sorted() .collect(Collectors.joining(",")); edgeColor = "chartreuse3"; + + if (tgt.equals(src)) { + // If the target is another location, reset info can always be inferred from context. + // --> Only include if self-loop: + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(MealyTimerInfo::name) + .sorted() + .collect(Collectors.joining(",")); + } } } else if (edge.getInput() instanceof NonDelayingInput ndi) { if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { // Self-loop + local reset -> resets all in target: - resetInfo = automaton.getSortedTimers(tgt).stream() + resetExtraInfo = automaton.getSortedTimers(tgt).stream() .map(t -> String.format("%s↦%d", t.name(), t.initial())) .sorted() .collect(Collectors.joining(",")); + + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(MealyTimerInfo::name) + .sorted() + .collect(Collectors.joining(",")); edgeColor = "orange"; } } @@ -96,7 +111,10 @@ public boolean getEdgeProperties(S src, properties.put("fontcolor", edgeColor); } if (this.includeResets) { - label += " {" + resetInfo + "}"; + label += " {" + resetExtraInfo + "}"; + } + if (!resetInfo.isEmpty()) { + properties.put("resets", resetInfo); } properties.put(EdgeAttrs.LABEL, label); From 4fb547a9d1c48399089e0cdfebdedd87f81c9e7d Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 16:08:42 +0100 Subject: [PATCH 13/18] initial refactorings / cleanups --- api/src/main/java/module-info.java | 5 +- .../time/mmlt/LocalTimerMealyInputSymbol.java | 7 - .../mmlt/LocalTimerMealyOutputSymbol.java | 60 ---- .../LocalTimerMealySemanticInputSymbol.java | 10 - .../alphabet/time/mmlt/NonDelayingInput.java | 40 --- .../alphabet/time/mmlt/TimeStepSequence.java | 48 --- .../alphabet/time/mmlt/TimeStepSymbol.java | 12 - .../alphabet/time/mmlt/TimeoutSymbol.java | 25 -- .../time/mmlt/TimerTimeoutSymbol.java | 41 --- .../net/automatalib/automaton/mmlt/MMLT.java | 119 +++++++ .../automaton/mmlt/MMLTCreator.java | 30 ++ .../automaton/mmlt/MMLTSemantics.java | 68 ++++ .../{time => }/mmlt/MealyTimerInfo.java | 12 +- .../MutableMMLT.java} | 46 +-- .../State.java} | 94 +++--- .../automaton/mmlt/SymbolCombiner.java | 43 +++ .../mmlt/semantics => mmlt}/TimeoutPair.java | 13 +- .../time/mmlt/AbstractSymbolCombiner.java | 36 -- .../automaton/time/mmlt/LocalTimerMealy.java | 177 ---------- .../LocalTimerMealyVisualizationHelper.java | 123 ------- .../LocalTimerMealySemanticTransition.java | 17 - .../semantics/LocalTimerMealySemantics.java | 97 ------ .../MMLTVisualizationHelper.java | 129 +++++++ .../automatalib/symbol/time/InputSymbol.java | 19 ++ .../symbol/time/SymbolicInput.java | 11 + .../symbol/time/TimeStepSequence.java | 24 ++ .../automatalib/symbol/time/TimedInput.java | 59 ++++ .../automatalib/symbol/time/TimedOutput.java | 39 +++ .../symbol/time/TimeoutSymbol.java | 16 + .../symbol/time/TimerTimeoutSymbol.java | 20 ++ .../visualization/VisualizationHelper.java | 20 +- core/src/main/java/module-info.java | 2 +- .../impl/CompactMMLT.java} | 177 ++++++---- .../mmlt/impl/CompactMMLTSemantics.java | 221 ++++++++++++ .../impl/ReducedMMLTSemantics.java} | 70 ++-- .../impl}/StringSymbolCombiner.java | 20 +- .../impl/mmlt/LocalTimerMealySemantics.java | 220 ------------ .../impl/MMLTTests.java} | 119 ++++--- pom.xml | 8 + .../serialization/dot/DOTMMLTParser.java | 317 ++++++++++++++++++ .../serialization/dot/DOTParsers.java | 51 +++ .../dot/LocalTimerMealyGraphvizParser.java | 244 -------------- .../dot/DOTDeserializationTest.java | 125 ++++++- .../dot/DOTSerializationTest.java | 14 + .../dot/DOTSerializationUtil.java | 36 ++ .../dot/LocalTimerMealyTests.java | 128 ------- .../dot/src/test/resources/demo_mmlt.dot | 20 -- serialization/dot/src/test/resources/mmlt.dot | 18 + .../{sensor_mmlt.dot => mmlt_sensor.dot} | 0 ...ed_serialized.dot => mmlt_with_resets.dot} | 12 +- ...calTimerMealyCover.java => MMLTCover.java} | 45 +-- ...LocalTimerMealyUtil.java => MMLTUtil.java} | 28 +- ...MealyCoverTest.java => MMLTCoverTest.java} | 42 +-- .../automaton/mmlt/LocalTimerMealyTests.java | 169 ---------- .../util/automaton/mmlt/MMLTUtilTest.java | 161 +++++++++ 55 files changed, 1889 insertions(+), 1818 deletions(-) delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java delete mode 100644 api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java create mode 100644 api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java create mode 100644 api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java create mode 100644 api/src/main/java/net/automatalib/automaton/mmlt/MMLTSemantics.java rename api/src/main/java/net/automatalib/automaton/{time => }/mmlt/MealyTimerInfo.java (97%) rename api/src/main/java/net/automatalib/automaton/{time/mmlt/MutableLocalTimerMealy.java => mmlt/MutableMMLT.java} (62%) rename api/src/main/java/net/automatalib/automaton/{time/mmlt/semantics/LocalTimerMealyConfiguration.java => mmlt/State.java} (61%) create mode 100644 api/src/main/java/net/automatalib/automaton/mmlt/SymbolCombiner.java rename api/src/main/java/net/automatalib/automaton/{time/mmlt/semantics => mmlt}/TimeoutPair.java (59%) delete mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java delete mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java delete mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java delete mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java delete mode 100644 api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java create mode 100644 api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/InputSymbol.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/SymbolicInput.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/TimeStepSequence.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/TimedInput.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/TimedOutput.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/TimeoutSymbol.java create mode 100644 api/src/main/java/net/automatalib/symbol/time/TimerTimeoutSymbol.java rename core/src/main/java/net/automatalib/automaton/{time/impl/mmlt/CompactLocalTimerMealy.java => mmlt/impl/CompactMMLT.java} (54%) create mode 100644 core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java rename core/src/main/java/net/automatalib/automaton/{time/impl/mmlt/ReducedLocalTimerMealySemantics.java => mmlt/impl/ReducedMMLTSemantics.java} (67%) rename core/src/main/java/net/automatalib/automaton/{time/impl/mmlt => mmlt/impl}/StringSymbolCombiner.java (73%) delete mode 100644 core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java rename core/src/test/java/net/automatalib/automaton/{impl/LocalTimerMealyTests.java => mmlt/impl/MMLTTests.java} (59%) create mode 100644 serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java delete mode 100644 serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java delete mode 100644 serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java delete mode 100644 serialization/dot/src/test/resources/demo_mmlt.dot create mode 100644 serialization/dot/src/test/resources/mmlt.dot rename serialization/dot/src/test/resources/{sensor_mmlt.dot => mmlt_sensor.dot} (100%) rename serialization/dot/src/test/resources/{demo_expected_serialized.dot => mmlt_with_resets.dot} (66%) rename util/src/main/java/net/automatalib/util/automaton/cover/{LocalTimerMealyCover.java => MMLTCover.java} (53%) rename util/src/main/java/net/automatalib/util/automaton/mmlt/{LocalTimerMealyUtil.java => MMLTUtil.java} (80%) rename util/src/test/java/net/automatalib/util/automaton/cover/{LocalTimerMealyCoverTest.java => MMLTCoverTest.java} (51%) delete mode 100644 util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java create mode 100644 util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java diff --git a/api/src/main/java/module-info.java b/api/src/main/java/module-info.java index 4d5caf8c4..60bd01305 100644 --- a/api/src/main/java/module-info.java +++ b/api/src/main/java/module-info.java @@ -47,6 +47,7 @@ exports net.automatalib.automaton.fsa; exports net.automatalib.automaton.graph; exports net.automatalib.automaton.helper; + exports net.automatalib.automaton.mmlt; exports net.automatalib.automaton.procedural; exports net.automatalib.automaton.simple; exports net.automatalib.automaton.transducer; @@ -61,6 +62,7 @@ exports net.automatalib.graph.visualization; exports net.automatalib.modelchecking; exports net.automatalib.serialization; + exports net.automatalib.symbol.time; exports net.automatalib.ts; exports net.automatalib.ts.acceptor; exports net.automatalib.ts.modal; @@ -70,9 +72,6 @@ exports net.automatalib.ts.simple; exports net.automatalib.visualization; exports net.automatalib.word; - exports net.automatalib.alphabet.time.mmlt; - exports net.automatalib.automaton.time.mmlt.semantics; - exports net.automatalib.automaton.time.mmlt; uses VisualizationProvider; } diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java deleted file mode 100644 index 4843139f0..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyInputSymbol.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -/** - * Base type for input symbols for the structural automaton of an MMLT. - */ -public interface LocalTimerMealyInputSymbol { -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java deleted file mode 100644 index a73aadb25..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealyOutputSymbol.java +++ /dev/null @@ -1,60 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.Objects; - -/** - * Output type for the semantics automaton: an output that occurs with some or no delay. - */ -public class LocalTimerMealyOutputSymbol { - - private final U userObject; - private final long delay; - - public LocalTimerMealyOutputSymbol(long delay, @NonNull U userObject) { - if (delay < 0) { - throw new IllegalArgumentException("Delay must not be negative."); - } - this.userObject = userObject; - this.delay = delay; - } - - public LocalTimerMealyOutputSymbol(@NonNull U userObject) { - this(0, userObject); - } - - public long getDelay() { - return delay; - } - - public boolean isDelayed() { - return this.delay > 0; - } - - public U getSymbol() { - return userObject; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - LocalTimerMealyOutputSymbol that = (LocalTimerMealyOutputSymbol) o; - return delay == that.delay && Objects.equals(userObject, that.userObject); - } - - @Override - public int hashCode() { - return Objects.hash(userObject, delay); - } - - @Override - public String toString() { - if (this.isDelayed()) { - return String.format("[%d]%s", this.getDelay(), this.userObject); - } - return this.userObject.toString(); - } - -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java deleted file mode 100644 index 294583de0..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/LocalTimerMealySemanticInputSymbol.java +++ /dev/null @@ -1,10 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -/** - * Base class for an input for the semantics automaton of some MMLT. - * - * @param - */ -public interface LocalTimerMealySemanticInputSymbol { - -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java deleted file mode 100644 index 594fed52e..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/NonDelayingInput.java +++ /dev/null @@ -1,40 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.Objects; - -/** - * An non-delaying input for the structural and semantics automaton. - * - * @param - */ -public class NonDelayingInput implements LocalTimerMealySemanticInputSymbol, LocalTimerMealyInputSymbol { - private final U symbol; - - public NonDelayingInput(@NonNull U input) { - this.symbol = input; - } - - public U getSymbol() { - return this.symbol; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - NonDelayingInput that = (NonDelayingInput) o; - return Objects.equals(symbol, that.symbol); - } - - @Override - public int hashCode() { - return Objects.hashCode(symbol); - } - - @Override - public String toString() { - return symbol.toString(); - } -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java deleted file mode 100644 index ad4f50b72..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSequence.java +++ /dev/null @@ -1,48 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -import java.util.Objects; - -/** - * Convenience type for aggregating multiple subsequent time steps. - */ -public class TimeStepSequence implements LocalTimerMealySemanticInputSymbol { - - private long timeSteps; - - public TimeStepSequence(long timeSteps) { - this.timeSteps = timeSteps; - - if (timeSteps <= 0) { - throw new IllegalArgumentException("Timeout must be larger than zero."); - } - } - - public void setTimeSteps(long timeSteps) { - this.timeSteps = timeSteps; - if (timeSteps <= 0) { - throw new IllegalArgumentException("Timeout must be larger than zero."); - } - } - - public long getTimeSteps() { - return timeSteps; - } - - @Override - public String toString() { - return String.format("wait[%d]", this.timeSteps); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TimeStepSequence that = (TimeStepSequence) o; - return timeSteps == that.timeSteps; - } - - @Override - public int hashCode() { - return Objects.hash(timeSteps); - } -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java deleted file mode 100644 index c5a78e8d8..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeStepSymbol.java +++ /dev/null @@ -1,12 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -/** - * Input for the semantics automaton: delay for a single time step. - */ -public class TimeStepSymbol extends TimeStepSequence { - - public TimeStepSymbol() { - super(1); - } - -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java deleted file mode 100644 index a7a789d94..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimeoutSymbol.java +++ /dev/null @@ -1,25 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -import java.util.Objects; - -/** - * Symbolic input for the semantics automaton: causes a delay until the next timeout. - */ -public class TimeoutSymbol implements LocalTimerMealySemanticInputSymbol { - - @Override - public String toString() { - return "timeout"; - } - - @Override - public boolean equals(Object o) { - return o != null && getClass() == o.getClass(); - } - - @Override - public int hashCode() { - return Objects.hash(this.toString()); - } - -} diff --git a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java b/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java deleted file mode 100644 index 22f90d57b..000000000 --- a/api/src/main/java/net/automatalib/alphabet/time/mmlt/TimerTimeoutSymbol.java +++ /dev/null @@ -1,41 +0,0 @@ -package net.automatalib.alphabet.time.mmlt; - -import java.util.Objects; - -/** - * The timeout symbol of a timer, as used by the structural automaton. - *

- * This input is used for the transition and output function of some MMLT. - * These symbols are not inputs for the expanded form of an MMLT. - * - * @param - */ -public class TimerTimeoutSymbol implements LocalTimerMealyInputSymbol { - private final String timer; - - public TimerTimeoutSymbol(String timer) { - this.timer = timer; - } - - public String getTimer() { - return timer; - } - - @Override - public String toString() { - return String.format("to[%s]", this.timer); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - TimerTimeoutSymbol that = (TimerTimeoutSymbol) o; - return Objects.equals(timer, that.timer); - } - - @Override - public int hashCode() { - return Objects.hashCode(timer); - } -} diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java new file mode 100644 index 000000000..369e07aaf --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java @@ -0,0 +1,119 @@ +package net.automatalib.automaton.mmlt; + +import java.util.Collection; +import java.util.List; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.UniversalDeterministicAutomaton; +import net.automatalib.automaton.concept.InputAlphabetHolder; +import net.automatalib.automaton.graph.TransitionEdge; +import net.automatalib.automaton.graph.TransitionEdge.Property; +import net.automatalib.automaton.graph.UniversalAutomatonGraphView; +import net.automatalib.graph.UniversalGraph; +import net.automatalib.symbol.time.TimerTimeoutSymbol; +import net.automatalib.visualization.VisualizationHelper; + +/** + * Base type for a Mealy Machine with Local Timers (MMLT). + *

+ * An MMLT extends Mealy machines with local timers. Each location can have multiple timers. A timer can only be active + * in its assigned location. All timers of a location reset when this location is entered from a different location or, + * in case of the initial location, if the location is entered for the first time. There are periodic and one-shot + * timers. Periodic timers reset themselves on timeout. They cannot cause a location change. One-shot timers can cause a + * location change. They reset all timers of the target location at timeout. A location can have arbitrarily many + * periodic timers and up to one one-shot timers. Timers are always reset to their initial value. The initial values + * must be chosen so that a periodic timer never times out at the same time as a one-shot timer (to preserve + * determinism). Multiple periodic timers may time out simultaneously. In this case, their outputs are combined using an + * AbstractSymbolCombiner. + *

+ * The timeout of a timer is modeled with a transition that has a {@link TimerTimeoutSymbol} as input. Other inputs are + * {@link InputSymbol non-delaying}. A non-delaying input that causes a self-loop can cause a local reset. Then, all + * timers of that location reset. + *

+ * Implementation note: this class resembles a "structural" view on the MMLT. For a semantic view with + * time-sensitive transductions, see the {@link #getSemantics()} method. + * + * @param + * Location type + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type + */ +public interface MMLT extends UniversalDeterministicAutomaton, T, Void, O>, + InputAlphabetHolder> { + + /** + * Returns the symbol used for silent outputs. + * + * @return Silent output symbol + */ + O getSilentOutput(); + + /** + * Multiple periodic timers may out simultaneously. Then, their outputs are combined using an SymbolCombiner. This + * method returns the combiner used for this model. + * + * @return symbol combiner used for this model. + */ + SymbolCombiner getOutputCombiner(); + + /** + * Returns the input alphabet of this MMLT, consisting of non-delaying inputs and timeout-symbols for its timers. + * + * @return Input alphabet + */ + Alphabet> getInputAlphabet(); + + /** + * Retrieves the non-delaying inputs for this automaton. Excludes timer timeout symbols. May be empty. + * + * @return Untimed alphabet. + */ + Alphabet> getUntimedAlphabet(); + + /** + * Indicates if the provided input performs a local reset in the given location. + * + * @param location + * Location + * @param input + * Non-delaying input + * + * @return True if performing a local reset + */ + boolean isLocalReset(S location, InputSymbol input); + + /** + * Returns the timers of the specified location sorted ascendingly by their initial time. + * + * @param location + * Location + * + * @return Sorted list of local timers. Empty if location has no timers. + */ + List> getSortedTimers(S location); + + /** + * Returns the semantics automaton that describes the behavior of this MMLT. + * + * @return Semantics automaton + */ + MMLTSemantics getSemantics(); + + @Override + default UniversalGraph, T>, Void, Property, O>> transitionGraphView( + Collection> inputs) { + return new UniversalAutomatonGraphView<>(this, inputs) { + + @Override + public VisualizationHelper, T>> getVisualizationHelper() { + return new MMLTVisualizationHelper<>(automaton, false, false); + } + }; + } + +} diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java new file mode 100644 index 000000000..548f1c8ef --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java @@ -0,0 +1,30 @@ +/* Copyright (C) 2013-2025 TU Dortmund University + * This file is part of AutomataLib . + * + * 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 net.automatalib.automaton.mmlt; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.symbol.time.InputSymbol; + +@FunctionalInterface +public interface MMLTCreator { + + default A createMMLT(Alphabet> alphabet, int numStatesHint, O silentOutput, SymbolCombiner outputCombiner) { + return createMMLT(alphabet, silentOutput, outputCombiner); + } + + A createMMLT(Alphabet> alphabet, O silentOutput, SymbolCombiner symbolCombiner); + +} diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTSemantics.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTSemantics.java new file mode 100644 index 000000000..e214997c2 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTSemantics.java @@ -0,0 +1,68 @@ +package net.automatalib.automaton.mmlt; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.concept.InputAlphabetHolder; +import net.automatalib.automaton.concept.SuffixOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.ts.output.MealyTransitionSystem; +import net.automatalib.word.Word; + +/** + * Defines the semantics of an MMLT. + *

+ * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are + * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values of + * this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the symbolic input + * timeout, which causes a delay until the next timeout. + *

+ * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all + * transitions, except for those with the input {@link TimeoutSymbol}. + * + * @param + * Location type + * @param + * Input type for non-delaying inputs + * @param + * Output type of the MMLT + */ +public interface MMLTSemantics + extends MealyTransitionSystem, TimedInput, T, TimedOutput>, + SuffixOutput, Word>>, + InputAlphabetHolder> { + + /** + * Returns the input alphabet of the semantics automaton. This consists of all non-delaying inputs of the associated + * MMLT, as well as the time step symbol and the symbolic timeout symbol. + * + * @return Input alphabet + */ + Alphabet> getInputAlphabet(); + + /** + * Returns the symbol used for silent outputs. + * + * @return Silent output symbol + */ + TimedOutput getSilentOutput(); + + /** + * Retrieves the transition in the semantics automaton that has the provided input and source configuration. + *

+ * If the input is a sequence of time steps, the target of the transition is the configuration reached after + * executing all time steps. If the sequence counts more than one step, the sequence might trigger multiple + * timeouts. To avoid ambiguity, the transition output is set to null in this case. If the sequence comprises a + * single time step only, the output is either that of a timeout or silence. + * + * @param source + * Source configuration + * @param input + * Input symbol + * @param maxWaitingTime + * Maximum time steps to wait for a timeout + * + * @return Transition in semantics automaton + */ + T getTransition(State source, TimedInput input, long maxWaitingTime); +} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java b/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java similarity index 97% rename from api/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java rename to api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java index a7870e9e9..ac3458bf5 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/MealyTimerInfo.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java @@ -1,4 +1,4 @@ -package net.automatalib.automaton.time.mmlt; +package net.automatalib.automaton.mmlt; import java.util.Objects; @@ -43,11 +43,6 @@ public MealyTimerInfo(String name, long initial, O output) { this(name, initial, output, true); } - @Override - public String toString() { - return String.format("%s=%d/%s", name, initial, output); - } - public void setOneShot() { this.periodic = false; } @@ -83,4 +78,9 @@ public int hashCode() { return Objects.hash(name, initial, output); } + @Override + public String toString() { + return String.format("%s=%d/%s", name, initial, output); + } + } diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java similarity index 62% rename from api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java rename to api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java index 445566288..5a8995072 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/MutableLocalTimerMealy.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java @@ -1,42 +1,10 @@ -package net.automatalib.automaton.time.mmlt; +package net.automatalib.automaton.mmlt; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.MutableDeterministic; -public interface MutableLocalTimerMealy { - - /** - * Adds a new location to the MMLT. - * - * @return The newly-added location. - */ - S addState(); - - /** - * Sets the initial location to the provided location. - * - * @param location New initial location - */ - void setInitialState(S location); - - /** - * Adds a transition for a non-delaying input, adding the input - * to the automaton's alphabet if necessary. - * - * @param source Source location - * @param input Non-delaying input symbol - * @param output Output - * @param target Target location - */ - void addTransition(S source, NonDelayingInput input, O output, S target); - - /** - * Removes the transition at the provided non-delaying input. - * No effect if the location has no such transition. - * - * @param source Transition source location - * @param input Transition input - */ - void removeTransition(S source, NonDelayingInput input); +public interface MutableMMLT extends MMLT, MutableDeterministic, T, Void, O> { /** * Adds a new periodic timer to the provided location. @@ -83,7 +51,7 @@ public interface MutableLocalTimerMealy { * @param location Source location * @param input Input of the transition that should perform a local reset */ - void addLocalReset(S location, NonDelayingInput input); + void addLocalReset(S location, InputSymbol input); /** * Removes a local reset at the provided input in the provided location. @@ -92,5 +60,5 @@ public interface MutableLocalTimerMealy { * @param location Source location * @param input Input of the transition that performs a local reset. */ - void removeLocalReset(S location, NonDelayingInput input); + void removeLocalReset(S location, InputSymbol input); } diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java b/api/src/main/java/net/automatalib/automaton/mmlt/State.java similarity index 61% rename from api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java rename to api/src/main/java/net/automatalib/automaton/mmlt/State.java index 93867cdcc..18867ae9a 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealyConfiguration.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/State.java @@ -1,20 +1,20 @@ -package net.automatalib.automaton.time.mmlt.semantics; +package net.automatalib.automaton.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.Nullable; import java.util.*; /** - * A configuration, a.k.a., state of an MMLT. - * A configuration is a tuple of an active location and the values of its timers. + * A configuration, a.k.a., state of an MMLT. A configuration is a tuple of an active location and the values of its + * timers. * - * @param Location type - * @param Output symbol type + * @param + * Location type + * @param + * Output symbol type */ -public final class LocalTimerMealyConfiguration { +public final class State { + private final S location; private final List> sortedTimers; @@ -27,14 +27,12 @@ public final class LocalTimerMealyConfiguration { /** * Initializes the entry configuration for the provided location, where all timers have their initial value. * - * @param location Location - * @param sortedTimers Timers of the location, sorted by initial value. + * @param location + * Location + * @param sortedTimers + * Timers of the location, sorted by initial value. */ - public LocalTimerMealyConfiguration(S location, List> sortedTimers) { - this(location, sortedTimers, null); - } - - public LocalTimerMealyConfiguration(S location, List> sortedTimers, Word> locationPrefix) { + public State(S location, List> sortedTimers) { this.location = location; this.sortedTimers = sortedTimers; @@ -51,9 +49,12 @@ public LocalTimerMealyConfiguration(S location, List> sortedTi this.entryDistance = 0; } - - private LocalTimerMealyConfiguration(S location, List> sortedTimers, - long[] timerValues, long[] initialValues, long entryDistance, long minimumTimerValue) { + private State(S location, + List> sortedTimers, + long[] timerValues, + long[] initialValues, + long entryDistance, + long minimumTimerValue) { this.location = location; this.sortedTimers = sortedTimers; @@ -64,13 +65,12 @@ private LocalTimerMealyConfiguration(S location, List> sortedT } /** - * Creates a copy of this configuration. - * The location, timers, prefix, and initialValue still point to the original instances. - * The current timer values are copied. - * Modifying these in the resulting object does not affect the original configuration. + * Creates a copy of this configuration. The location, timers, prefix, and initialValue still point to the original + * instances. The current timer values are copied. Modifying these in the resulting object does not affect the + * original configuration. */ - public LocalTimerMealyConfiguration copy() { - return new LocalTimerMealyConfiguration<>(location, sortedTimers, timerValues, initialValues, entryDistance, minimumTimerValue); + public State copy() { + return new State<>(location, sortedTimers, timerValues, initialValues, entryDistance, minimumTimerValue); } public S getLocation() { @@ -78,8 +78,8 @@ public S getLocation() { } /** - * Returns the entry distance. This is the minimal number of time steps - * required to reach this configuration from the entry configuration. + * Returns the entry distance. This is the minimal number of time steps required to reach this configuration from + * the entry configuration. * * @return Entry distance */ @@ -88,8 +88,8 @@ public long getEntryDistance() { } /** - * Indicates if this is the entry configuration of the location. - * A configuration is the entry configuration if all timers have their initial value. + * Indicates if this is the entry configuration of the location. A configuration is the entry configuration if all + * timers have their initial value. * * @return True if entry configuration. */ @@ -98,10 +98,9 @@ public boolean isEntryConfig() { } /** - * Indicates if this configuration is stable. - * A configuration is stable if its entry distance is less than the initial value - * of the timer with the lowest initial value of the location. - * If the location has no timers, its only configuration is its entry configuration, which is always stable. + * Indicates if this configuration is stable. A configuration is stable if its entry distance is less than the + * initial value of the timer with the lowest initial value of the location. If the location has no timers, its only + * configuration is its entry configuration, which is always stable. * * @return True if stable */ @@ -117,7 +116,6 @@ public void resetTimers() { this.entryDistance = 0; } - /** * Returns all timers that time out in the least number of time steps. */ @@ -128,8 +126,7 @@ public TimeoutPair getNextExpiringTimers() { } else if (this.sortedTimers.size() == 1) { // No need to collect timeouts - there is only one timer that can expire. // The time to its timeout is its remaining value: - return new TimeoutPair<>(this.timerValues[0], - Collections.singletonList(this.sortedTimers.get(0))); + return new TimeoutPair<>(this.timerValues[0], Collections.singletonList(this.sortedTimers.get(0))); } else { // Multiple timers may time out at the same time. @@ -157,11 +154,11 @@ public TimeoutPair getNextExpiringTimers() { } /** - * Decreases all timer values by the specified amount. This amount must be at most the time to the next timeout. - * If this sets a timer to zero, this timer is immediately reset. - * to its initial value. + * Decreases all timer values by the specified amount. This amount must be at most the time to the next timeout. If + * this sets a timer to zero, this timer is immediately reset. to its initial value. * - * @param delay Decrement + * @param delay + * Decrement */ public void decrement(long delay) { int timerResets = 0; @@ -182,7 +179,7 @@ public void decrement(long delay) { this.timerValues[i] = newValue; } - if (oneShotResets > 1) throw new AssertionError(); + if (oneShotResets > 1) {throw new AssertionError();} if (timerResets == this.sortedTimers.size() || oneShotResets == 1) { // reset all timers -> back at entry config: this.entryDistance = 0; @@ -193,14 +190,21 @@ public void decrement(long delay) { @Override public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) return false; - LocalTimerMealyConfiguration that = (LocalTimerMealyConfiguration) o; - return minimumTimerValue == that.minimumTimerValue && entryDistance == that.entryDistance && Objects.equals(location, that.location) && Objects.equals(sortedTimers, that.sortedTimers) && Objects.deepEquals(timerValues, that.timerValues) && Objects.deepEquals(initialValues, that.initialValues); + if (o == null || getClass() != o.getClass()) {return false;} + State that = (State) o; + return minimumTimerValue == that.minimumTimerValue && entryDistance == that.entryDistance && + Objects.equals(location, that.location) && Objects.equals(sortedTimers, that.sortedTimers) && + Arrays.equals(timerValues, that.timerValues) && Arrays.equals(initialValues, that.initialValues); } @Override public int hashCode() { - return Objects.hash(location, sortedTimers, Arrays.hashCode(timerValues), Arrays.hashCode(initialValues), minimumTimerValue, entryDistance); + return Objects.hash(location, + sortedTimers, + Arrays.hashCode(timerValues), + Arrays.hashCode(initialValues), + minimumTimerValue, + entryDistance); } @Override diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/SymbolCombiner.java b/api/src/main/java/net/automatalib/automaton/mmlt/SymbolCombiner.java new file mode 100644 index 000000000..23ea44ac0 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/mmlt/SymbolCombiner.java @@ -0,0 +1,43 @@ +package net.automatalib.automaton.mmlt; + +import java.util.List; + +/** + * In an MMLT, multiple timeouts may occur simultaneously. We use these symbol combiners to combine their outputs + * deterministically. + * + * @param + * Symbol type + */ +public interface SymbolCombiner { + + /** + * Indicates if the provided suffix is a combined suffix. + * + * @param symbol + * Symbol for testing + * + * @return True if combined suffix, false if not. + */ + boolean isCombinedSymbol(U symbol); + + /** + * Combines the provided symbols to a single suffix of same data type. Must be deterministic. + * + * @param symbols + * Provided symbols. + * + * @return Combined suffix + */ + U combineSymbols(List symbols); + + /** + * Attempts to separate the provided combined suffix into individual symbols. + * + * @param symbol + * Combined symbols + * + * @return Individual symbols + */ + List separateSymbols(U symbol); +} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java b/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java similarity index 59% rename from api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java rename to api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java index 53536d31d..7cbfbae5e 100644 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/TimeoutPair.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java @@ -1,15 +1,16 @@ -package net.automatalib.automaton.time.mmlt.semantics; - -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; +package net.automatalib.automaton.mmlt; import java.util.List; /** * Stores information about timers that expire after a given time from now. * - * @param delay Offset to the next timeout - * @param timers Timers expiring simultaneously at next timeout - * @param Output suffix type + * @param delay + * Offset to the next timeout + * @param timers + * Timers expiring simultaneously at next timeout + * @param + * Output suffix type */ public record TimeoutPair(long delay, List> timers) { diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java deleted file mode 100644 index b3ab1a3d0..000000000 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/AbstractSymbolCombiner.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.automatalib.automaton.time.mmlt; - -import java.util.List; - -/** - * In an MMLT, multiple timeouts may occur simultaneously. - * We use these symbol combiners to combine their outputs deterministically. - * - * @param Symbol type - */ -public abstract class AbstractSymbolCombiner { - - /** - * Indicates if the provided suffix is a combined suffix. - * - * @param symbol Symbol for testing - * @return True if combined suffix, false if not. - */ - public abstract boolean isCombinedSymbol(U symbol); - - /** - * Combines the provided symbols to a single suffix of same data type. Must be deterministic. - * - * @param symbols Provided symbols. - * @return Combined suffix - */ - public abstract U combineSymbols(List symbols); - - /** - * Attempts to separate the provided combined suffix into individual symbols. - * - * @param symbol Combined symbols - * @return Individual symbols - */ - public abstract List separateSymbols(U symbol); -} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java deleted file mode 100644 index 1ebd06503..000000000 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealy.java +++ /dev/null @@ -1,177 +0,0 @@ -package net.automatalib.automaton.time.mmlt; - -import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.UniversalDeterministicAutomaton; -import net.automatalib.automaton.graph.TransitionEdge; -import net.automatalib.automaton.graph.UniversalAutomatonGraphView; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; -import net.automatalib.graph.UniversalGraph; -import net.automatalib.visualization.VisualizationHelper; -import org.checkerframework.checker.nullness.qual.NonNull; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.util.Collection; -import java.util.List; -import java.util.Set; - -/** - * Base type for a Mealy Machine with Local Timers (MMLT). - *

- * An MMLT extends Mealy machines with local timers. - * Each location can have multiple timers. A timer can only be active in its assigned location. - * All timers of a location reset when this location is entered from a different location or, in case of the initial - * location, if the location is entered for the first time. - * There are periodic and one-shot timers. Periodic timers reset themselves on timeout. They cannot cause a location - * change. One-shot timers can cause a location change. They reset all timers of the target location at timeout. - * A location can have arbitrarily many periodic timers and up to one one-shot timers. - * Timers are always reset to their initial value. The initial values must be chosen so that a periodic timer never - * times out at the same time as a one-shot timer (to preserve determinism). Multiple periodic timers may time out - * simultaneously. In this case, their outputs are combined using an AbstractSymbolCombiner. - *

- * The timeout of a timer is modeled with a transition that has a TimerTimeoutSymbol as input. Other inputs are called - * non-delaying. A non-delaying input that causes a self-loop can cause a local reset. Then, all timers of that - * location reset. - *

- * To be able to subclass existing automata, some methods refer to locations as "state". - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public interface LocalTimerMealy extends UniversalDeterministicAutomaton, LocalTimerMealy.LocalTimerMealyTransition, Void, O> { - - record LocalTimerMealyTransition(@NonNull S successor, @NonNull O output) { - - } - - /** - * Returns the symbol used for silent outputs. - * - * @return Silent output symbol - */ - O getSilentOutput(); - - /** - * Multiple periodic timers may out simultaneously. Then, their outputs are combined - * using an AbstractSymbolCombiner. This method returns the combiner used for - * this model. - * - * @return Symbol combiner used for this model. - */ - AbstractSymbolCombiner getOutputCombiner(); - - /** - * Returns the input alphabet of this MMLT, consisting of non-delaying inputs - * and timeout-symbols for its timers. - * - * @return Input alphabet - */ - Alphabet> getInputAlphabet(); - - /** - * Retrieves the non-delaying inputs for this automaton. - * Excludes timer timeout symbols. May be empty. - * - * @return Untimed alphabet. - */ - Alphabet> getUntimedAlphabet(); - - @Override - default O getTransitionProperty(LocalTimerMealyTransition transition) { - return transition.output(); - } - - @Override - default S getSuccessor(LocalTimerMealyTransition transition) { - return transition.successor(); - } - - /** - * Indicates if the provided input performs a local reset in the given location. - * - * @param location Location - * @param input Non-delaying input - * @return True if performing a local reset - */ - boolean isLocalReset(S location, NonDelayingInput input); - - /** - * Returns the timers of the specified location sorted ascendingly by their initial time. - * - * @param location Location - * @return Sorted list of local timers. Empty if location has no timers. - */ - List> getSortedTimers(S location); - - /** - * Returns the semantics automaton that describes the behavior of this MMLT. - * - * @return Semantics automaton - */ - LocalTimerMealySemantics getSemantics(); - - // ======================================= - - @Override - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(Collection> inputs) { - return new LocalTimerMealyGraphView<>(this, inputs, false, false); - } - - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView() { - return this.transitionGraphView(getInputAlphabet()); - } - - default UniversalGraph, LocalTimerMealyTransition>, Void, TransitionEdge.Property, O>> transitionGraphView(boolean colorEdges, boolean includeResets) { - return new LocalTimerMealyGraphView<>(this, getInputAlphabet(), colorEdges, includeResets); - } - - // ======================================= - - @Override - default Void getStateProperty(S state) { - return null; - } - - // We do not want to provide tracing abilities for inputs of the structure automaton: - @Override - default Set getStates(Iterable> input) { - throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); - } - - @Override - @Nullable - default S getSuccessor(S state, Iterable> input) { - throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); - } - - @Override - @Nullable - default S getState(Iterable> input) { - throw new IllegalStateException("Not supported. Use the semantics automaton to trace inputs."); - } - - // ======================================= - - class LocalTimerMealyGraphView extends - UniversalAutomatonGraphView, LocalTimerMealy.LocalTimerMealyTransition, Void, OX, LocalTimerMealy> { - - private final boolean colorEdges; - private final boolean includeResets; - - public LocalTimerMealyGraphView(LocalTimerMealy automaton, - Collection> inputs, - boolean colorEdges, boolean includeResets) { - super(automaton, inputs); - this.colorEdges = colorEdges; - this.includeResets = includeResets; - } - - @Override - public VisualizationHelper, LocalTimerMealyTransition>> getVisualizationHelper() { - return new LocalTimerMealyVisualizationHelper<>(automaton, colorEdges, includeResets); - } - } - -} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java deleted file mode 100644 index 435bb5f94..000000000 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/LocalTimerMealyVisualizationHelper.java +++ /dev/null @@ -1,123 +0,0 @@ -package net.automatalib.automaton.time.mmlt; - -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; -import net.automatalib.automaton.graph.TransitionEdge; -import net.automatalib.automaton.visualization.AutomatonVisualizationHelper; - -import java.util.Map; -import java.util.stream.Collectors; - -class LocalTimerMealyVisualizationHelper extends - AutomatonVisualizationHelper, LocalTimerMealy.LocalTimerMealyTransition, LocalTimerMealy> { - - private final boolean colorEdges; - private final boolean includeResets; - - /** - * Creates a new visualization helper for an MMLT. - * Allows edge coloring and explicit resets in transition labels for easier inspection. - *

- * If you want to serialize the resulting file to graphviz, disable explicit resets. - * The parse does not know how to treat the reset information. - * - * @param automaton Automaton - * @param colorEdges If set, the transitions for local resets, periodic timers, and one-shot timers are colored differently. - * @param includeResets If set, each transition includes a list of timers that it resets. - */ - public LocalTimerMealyVisualizationHelper(LocalTimerMealy automaton, boolean colorEdges, boolean includeResets) { - super(automaton); - this.colorEdges = colorEdges; - this.includeResets = includeResets; - } - - @Override - public boolean getNodeProperties(S node, Map properties) { - super.getNodeProperties(node, properties); - - // Include timer assignments: - var localTimers = automaton.getSortedTimers(node); - if (!localTimers.isEmpty()) { - // Add local timer info: - String timers = localTimers.stream() - .map(t -> String.format("%s=%d", t.name(), t.initial())) - .sorted() - .collect(Collectors.joining(",")); - properties.put("timers", timers); - } - - return true; - } - - @Override - public boolean getEdgeProperties(S src, - TransitionEdge, LocalTimerMealy.LocalTimerMealyTransition> edge, - S tgt, Map properties) { - super.getEdgeProperties(src, edge, tgt, properties); - - String label = String.format("%s / %s", edge.getInput(), edge.getTransition().output()); - - // Infer the label color + reset information for the transition: - String resetExtraInfo = ""; - String resetInfo = ""; - String edgeColor = ""; - if (edge.getInput() instanceof TimerTimeoutSymbol ts) { - // Get info for corresponding timer: - var optTimer = automaton.getSortedTimers(src).stream() - .filter(t -> t.name().equals(ts.getTimer())) - .findFirst(); - assert optTimer.isPresent(); - - if (optTimer.get().periodic()) { - // Periodic -> resets itself: - resetExtraInfo = String.format("%s↦%d", optTimer.get().name(), optTimer.get().initial()); - edgeColor = "cornflowerblue"; - } else { - // One-shot -> resets all in target: - resetExtraInfo = automaton.getSortedTimers(tgt).stream() - .map(t -> String.format("%s↦%d", t.name(), t.initial())) - .sorted() - .collect(Collectors.joining(",")); - edgeColor = "chartreuse3"; - - if (tgt.equals(src)) { - // If the target is another location, reset info can always be inferred from context. - // --> Only include if self-loop: - resetInfo = automaton.getSortedTimers(tgt).stream() - .map(MealyTimerInfo::name) - .sorted() - .collect(Collectors.joining(",")); - } - } - } else if (edge.getInput() instanceof NonDelayingInput ndi) { - if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { - // Self-loop + local reset -> resets all in target: - resetExtraInfo = automaton.getSortedTimers(tgt).stream() - .map(t -> String.format("%s↦%d", t.name(), t.initial())) - .sorted() - .collect(Collectors.joining(",")); - - resetInfo = automaton.getSortedTimers(tgt).stream() - .map(MealyTimerInfo::name) - .sorted() - .collect(Collectors.joining(",")); - edgeColor = "orange"; - } - } - - if (this.colorEdges && !edgeColor.isBlank()) { - properties.put(EdgeAttrs.COLOR, edgeColor); - properties.put("fontcolor", edgeColor); - } - if (this.includeResets) { - label += " {" + resetExtraInfo + "}"; - } - if (!resetInfo.isEmpty()) { - properties.put("resets", resetInfo); - } - properties.put(EdgeAttrs.LABEL, label); - - return true; - } -} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java deleted file mode 100644 index 58e8235b2..000000000 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemanticTransition.java +++ /dev/null @@ -1,17 +0,0 @@ -package net.automatalib.automaton.time.mmlt.semantics; - -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyOutputSymbol; - -/** - * Represents a transition in the semantics automaton ("expanded form") of an MMLT. - * - * @param output Transition output - * @param target Transition target - * @param Location type - * @param Input type for non-delaying inputs - * @param Output symbol type - */ -public record LocalTimerMealySemanticTransition(LocalTimerMealyOutputSymbol output, - LocalTimerMealyConfiguration target) { - -} diff --git a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java b/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java deleted file mode 100644 index 94fc66985..000000000 --- a/api/src/main/java/net/automatalib/automaton/time/mmlt/semantics/LocalTimerMealySemantics.java +++ /dev/null @@ -1,97 +0,0 @@ -package net.automatalib.automaton.time.mmlt.semantics; - -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.NonNull; - -/** - * Defines the semantics of an MMLT. - *

- * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are - * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values - * of this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the - * symbolic input timeout, which causes a delay until the next timeout. - *

- * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all - * transitions, except for those with the input timeout. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output type of the MMLT - */ -public interface LocalTimerMealySemantics { - /** - * Returns the input alphabet of the semantics automaton. This consists of all non-delaying inputs - * of the associated MMLT, as well as the time step symbol and the symbolic timeout symbol. - * - * @return Input alphabet - */ - Alphabet> getInputAlphabet(); - - /** - * Returns the symbol used for silent outputs. - * - * @return Silent output symbol - */ - LocalTimerMealyOutputSymbol getSilentOutput(); - - /** - * Returns the initial configuration of this MMLT. This is a tuple of the - * initial location and its initial timer values. - * - * @return Initial configuration - */ - LocalTimerMealyConfiguration getInitialConfiguration(); - - /** - * Enters the suffix into the provided configuration and returns corresponding outputs. - *

- * Cannot provide TimeSequences with more than 1 symbol for the suffix, as this might trigger multiple timeouts - * and thus lead to output sequences that are longer than the suffix. - * - * @param configuration Configuration - * @param suffix Suffix inputs - * @return Outputs for the suffix - */ - Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix); - - /** - * Enters the prefix and suffix sequences into the automaton and returns the outputs that occur for the suffixes. - * - * @param prefix Configuration prefix - * @param suffix Suffix inputs - * @return Outputs for the suffix - */ - Word> computeSuffixOutput(Word> prefix, Word> suffix); - - /** - * Traces the provided prefix and returns the reached configuration. - * - * @param prefix Configuration prefix - * @return Reached configuration - */ - LocalTimerMealyConfiguration traceInputs(Word> prefix); - - @NonNull - LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, LocalTimerMealySemanticInputSymbol input); - - /** - * Retrieves the transition in the semantics automaton that has the provided input and source configuration. - *

- * If the input is a sequence of time steps, the target of the transition is the configuration reached after - * executing all time steps. If the sequence counts more than one step, the sequence might trigger multiple - * timeouts. To avoid ambiguity, the transition output is set to null in this case. - * If the sequence comprises a single time step only, the output is either that of a timeout or silence. - * - * @param source Source configuration - * @param input Input symbol - * @param maxWaitingTime Maximum time steps to wait for a timeout - * @return Transition in semantics automaton - */ - @NonNull - LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, - LocalTimerMealySemanticInputSymbol input, - long maxWaitingTime); -} diff --git a/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java new file mode 100644 index 000000000..93084f173 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java @@ -0,0 +1,129 @@ +package net.automatalib.automaton.visualization; + +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimerTimeoutSymbol; +import net.automatalib.automaton.graph.TransitionEdge; + +import java.util.Map; +import java.util.stream.Collectors; + +public class MMLTVisualizationHelper + extends AutomatonVisualizationHelper, T, MMLT> { + + private final boolean colorEdges; + private final boolean includeResets; + + /** + * Creates a new visualization helper for an MMLT. Allows edge coloring and explicit resets in transition labels for + * easier inspection. + *

+ * If you want to serialize the resulting file to graphviz, disable explicit resets. The parse does not know how to + * treat the reset information. + * + * @param automaton + * Automaton + * @param colorEdges + * If set, the transitions for local resets, periodic timers, and one-shot timers are colored differently. + * @param includeResets + * If set, each transition includes a list of timers that it resets. + */ + public MMLTVisualizationHelper(MMLT automaton, + boolean colorEdges, + boolean includeResets) { + super(automaton); + this.colorEdges = colorEdges; + this.includeResets = includeResets; + } + + @Override + public boolean getNodeProperties(S node, Map properties) { + super.getNodeProperties(node, properties); + + // Include timer assignments: + var localTimers = automaton.getSortedTimers(node); + if (!localTimers.isEmpty()) { + // Add local timer info: + String timers = localTimers.stream() + .map(t -> String.format("%s=%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + properties.put(MMLTNodeAttrs.TIMERS, timers); + } + + return true; + } + + @Override + public boolean getEdgeProperties(S src, TransitionEdge, T> edge, S tgt, Map properties) { + super.getEdgeProperties(src, edge, tgt, properties); + + final SymbolicInput input = edge.getInput(); + + String label = String.format("%s / %s", input, automaton.getTransitionProperty(edge.getTransition())); + + // Infer the label color + reset information for the transition: + String resetExtraInfo = ""; + String resetInfo = ""; + String edgeColor = ""; + if (edge.getInput() instanceof TimerTimeoutSymbol ts) { + // Get info for corresponding timer: + var optTimer = automaton.getSortedTimers(src).stream() + .filter(t -> t.name().equals(ts.timer())) + .findFirst(); + assert optTimer.isPresent(); + + if (optTimer.get().periodic()) { + // Periodic -> resets itself: + resetExtraInfo = String.format("%s↦%d", optTimer.get().name(), optTimer.get().initial()); + edgeColor = "cornflowerblue"; + } else { + // One-shot -> resets all in target: + resetExtraInfo = automaton.getSortedTimers(tgt) + .stream() + .map(t -> String.format("%s↦%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + if (tgt.equals(src)) { + // If the target is another location, reset info can always be inferred from context. + // --> Only include if self-loop: + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(MealyTimerInfo::name) + .sorted() + .collect(Collectors.joining(",")); + } + edgeColor = "chartreuse3"; + } + } else if (edge.getInput() instanceof InputSymbol ndi) { + if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { + // Self-loop + local reset -> resets all in target: + resetExtraInfo = automaton.getSortedTimers(tgt) + .stream() + .map(t -> String.format("%s↦%d", t.name(), t.initial())) + .sorted() + .collect(Collectors.joining(",")); + resetInfo = automaton.getSortedTimers(tgt).stream() + .map(MealyTimerInfo::name) + .sorted() + .collect(Collectors.joining(",")); + edgeColor = "orange"; + } + } + + if (this.colorEdges && !edgeColor.isBlank()) { + properties.put(EdgeAttrs.COLOR, edgeColor); + properties.put("fontcolor", edgeColor); + } + if (this.includeResets) { + label += " {" + resetExtraInfo + "}"; + } + if (!resetInfo.isEmpty()) { + properties.put(MMLTEdgeAttrs.RESETS, resetInfo); + } + properties.put(EdgeAttrs.LABEL, label); + + return true; + } +} diff --git a/api/src/main/java/net/automatalib/symbol/time/InputSymbol.java b/api/src/main/java/net/automatalib/symbol/time/InputSymbol.java new file mode 100644 index 000000000..449e62974 --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/InputSymbol.java @@ -0,0 +1,19 @@ +package net.automatalib.symbol.time; + +import java.util.Objects; + +/** + * An input symbol that represents a direct action without any delay. + * + * @param symbol + * the symbolic action + * @param + * input symbol type + */ +public record InputSymbol(I symbol) implements TimedInput, SymbolicInput { + + @Override + public String toString() { + return Objects.toString(symbol); + } +} diff --git a/api/src/main/java/net/automatalib/symbol/time/SymbolicInput.java b/api/src/main/java/net/automatalib/symbol/time/SymbolicInput.java new file mode 100644 index 000000000..1f542d8b0 --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/SymbolicInput.java @@ -0,0 +1,11 @@ +package net.automatalib.symbol.time; + +import net.automatalib.automaton.mmlt.MMLT; + +/** + * Markup-interface for structural inputs currently used in {@link MMLT}s. + * + * @param + * input symbol type + */ +public sealed interface SymbolicInput permits InputSymbol, TimerTimeoutSymbol {} diff --git a/api/src/main/java/net/automatalib/symbol/time/TimeStepSequence.java b/api/src/main/java/net/automatalib/symbol/time/TimeStepSequence.java new file mode 100644 index 000000000..d2ed6ff5f --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/TimeStepSequence.java @@ -0,0 +1,24 @@ +package net.automatalib.symbol.time; + +/** + * An input that represents multiple subsequent time steps. + * + * @param timeSteps + * the number of time steps this symbol should elapse + * @param + * input symbol type (of other timed symbols) + */ +public record TimeStepSequence(long timeSteps) implements TimedInput { + + public TimeStepSequence { + if (timeSteps <= 0) { + throw new IllegalArgumentException("Timeout must be larger than zero."); + } + } + + @Override + public String toString() { + return String.format("wait[%d]", this.timeSteps); + } + +} diff --git a/api/src/main/java/net/automatalib/symbol/time/TimedInput.java b/api/src/main/java/net/automatalib/symbol/time/TimedInput.java new file mode 100644 index 000000000..90cbebf7a --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/TimedInput.java @@ -0,0 +1,59 @@ +package net.automatalib.symbol.time; + +import java.util.function.Supplier; + +import net.automatalib.automaton.mmlt.MMLTSemantics; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; + +/** + * Markup-interface for timing-sensitive inputs currently used in {@link MMLTSemantics}s. Contains utility methods for + * conveniently constructing instances of timed symbols. + * + * @param + * input symbol type + */ +public sealed interface TimedInput permits InputSymbol, TimeoutSymbol, TimeStepSequence { + + static InputSymbol input(I symbol) { + return new InputSymbol<>(symbol); + } + + @SafeVarargs + static Word> inputs(I... symbols) { + var wb = new WordBuilder>(symbols.length); + for (I symbol : symbols) { + wb.add(new InputSymbol<>(symbol)); + } + return wb.toWord(); + } + + static TimeoutSymbol timeout() { + return new TimeoutSymbol<>(); + } + + static Word> timeouts(int i) { + return generate(i, TimeoutSymbol::new); + } + + static TimeStepSequence step() { + return new TimeStepSequence<>(1); + } + + static TimeStepSequence step(int i) { + return new TimeStepSequence<>(i); + } + + static Word> steps(int i) { + return generate(i, () -> new TimeStepSequence<>(1)); + } + + private static Word generate(int i, Supplier supplier) { + var wb = new WordBuilder(i); + for (int j = 0; j < i; j++) { + wb.append(supplier.get()); + } + + return wb.toWord(); + } +} diff --git a/api/src/main/java/net/automatalib/symbol/time/TimedOutput.java b/api/src/main/java/net/automatalib/symbol/time/TimedOutput.java new file mode 100644 index 000000000..d5b17f331 --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/TimedOutput.java @@ -0,0 +1,39 @@ +package net.automatalib.symbol.time; + +import java.util.Objects; + +/** + * Output that may occur with some or no delay. + * + * @param symbol + * the output symbol + * @param delay + * the delay + * @param + * output symbol type + */ +public record TimedOutput(O symbol, long delay) { + + public TimedOutput { + if (delay < 0) { + throw new IllegalArgumentException("Delay must not be negative."); + } + } + + public TimedOutput(O symbol) { + this(symbol, 0); + } + + public boolean isDelayed() { + return this.delay > 0; + } + + @Override + public String toString() { + if (this.isDelayed()) { + return String.format("[%d]%s", this.delay, this.symbol); + } + return Objects.toString(this.symbol); + } + +} diff --git a/api/src/main/java/net/automatalib/symbol/time/TimeoutSymbol.java b/api/src/main/java/net/automatalib/symbol/time/TimeoutSymbol.java new file mode 100644 index 000000000..5f4c02b00 --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/TimeoutSymbol.java @@ -0,0 +1,16 @@ +package net.automatalib.symbol.time; + +/** + * An input that causes a delay until the next timeout. + * + * @param + * input symbol type (of other timed symbols) + */ +public record TimeoutSymbol() implements TimedInput { + + @Override + public String toString() { + return "timeout"; + } + +} diff --git a/api/src/main/java/net/automatalib/symbol/time/TimerTimeoutSymbol.java b/api/src/main/java/net/automatalib/symbol/time/TimerTimeoutSymbol.java new file mode 100644 index 000000000..a516a6eae --- /dev/null +++ b/api/src/main/java/net/automatalib/symbol/time/TimerTimeoutSymbol.java @@ -0,0 +1,20 @@ +package net.automatalib.symbol.time; + +import net.automatalib.automaton.mmlt.MMLT; + +/** + * The timeout symbol of a timer currently used by {@link MMLT}s. + * + * @param timer + * the name of the timer + * @param + * input symbol type (of other timed symbols) + */ +public record TimerTimeoutSymbol(String timer) implements SymbolicInput { + + @Override + public String toString() { + return String.format("to[%s]", this.timer); + } + +} diff --git a/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java b/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java index 0e779f200..9177e4fab 100644 --- a/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java +++ b/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java @@ -83,7 +83,7 @@ private CommonAttrs() { } } - final class NodeAttrs extends CommonAttrs { + class NodeAttrs extends CommonAttrs { public static final String SHAPE = "shape"; public static final String WIDTH = "width"; @@ -97,6 +97,15 @@ private NodeAttrs() { } } + final class MMLTNodeAttrs extends NodeAttrs { + + public static final String TIMERS = "timers"; + + private MMLTNodeAttrs() { + // prevent instantiation + } + } + class EdgeAttrs extends CommonAttrs { public static final String PENWIDTH = "penwidth"; @@ -166,4 +175,13 @@ private MTSEdgeAttrs() { // prevent instantiation } } + + final class MMLTEdgeAttrs extends NodeAttrs { + + public static final String RESETS = "resets"; + + private MMLTEdgeAttrs() { + // prevent instantiation + } + } } diff --git a/core/src/main/java/module-info.java b/core/src/main/java/module-info.java index 07e6b9bab..cb0dc0b71 100644 --- a/core/src/main/java/module-info.java +++ b/core/src/main/java/module-info.java @@ -41,6 +41,7 @@ exports net.automatalib.automaton.base; exports net.automatalib.automaton.fsa.impl; exports net.automatalib.automaton.impl; + exports net.automatalib.automaton.mmlt.impl; exports net.automatalib.automaton.procedural.impl; exports net.automatalib.automaton.transducer.impl; exports net.automatalib.automaton.transducer.probabilistic.impl; @@ -52,5 +53,4 @@ exports net.automatalib.ts.modal.impl; exports net.automatalib.ts.modal.transition.impl; exports net.automatalib.ts.powerset.impl; - exports net.automatalib.automaton.time.impl.mmlt; } diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java similarity index 54% rename from core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java rename to core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java index 049015026..d41018ab6 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/CompactLocalTimerMealy.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java @@ -1,51 +1,61 @@ -package net.automatalib.automaton.time.impl.mmlt; +package net.automatalib.automaton.mmlt.impl; + +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 net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.GrowingAlphabet; +import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealyInputSymbol; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.alphabet.time.mmlt.TimerTimeoutSymbol; -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.MutableLocalTimerMealy; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics; +import net.automatalib.automaton.impl.CompactTransition; +import net.automatalib.automaton.mmlt.MMLTSemantics; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MutableMMLT; +import net.automatalib.automaton.mmlt.SymbolCombiner; import net.automatalib.automaton.transducer.impl.CompactMealy; +import net.automatalib.common.util.mapping.MutableMapping; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.TimerTimeoutSymbol; import org.checkerframework.checker.nullness.qual.Nullable; -import java.util.*; - /** - * Implements a LocalTimerMealy that is mutable. - * The structure automaton is backed by a CompactMealy automaton. + * Implements a LocalTimerMealy that is mutable. The structure automaton is backed by a CompactMealy automaton. * - * @param Input type for non-delaying inputs - * @param Output symbol type + * @param + * Input type for non-delaying inputs + * @param + * Output symbol type */ -public class CompactLocalTimerMealy implements LocalTimerMealy, MutableLocalTimerMealy { - private final CompactMealy, O> automaton; +public class CompactMMLT implements MutableMMLT, O> { + + private final CompactMealy, O> automaton; private final Map>> sortedTimers; // location -> (sorted timers) - private final Map>> resets; // location -> inputs (that reset all timers) + private final Map>> resets; // location -> inputs (that reset all timers) - private final GrowingAlphabet> untimedAlphabet; + private final Alphabet> untimedAlphabet; private final O silentOutput; - private final AbstractSymbolCombiner outputCombiner; + private final SymbolCombiner outputCombiner; /** * Initializes a new CompactLocalTimerMealy. * - * @param nonDelayingInputs Non-delaying inputs used by this MMLT. - * @param silentOutput The silent output used by this MMLT. - * @param outputCombiner The combiner function for simultaneous timeouts of periodic timers. + * @param nonDelayingInputs + * Non-delaying inputs used by this MMLT. + * @param silentOutput + * The silent output used by this MMLT. + * @param outputCombiner + * The combiner function for simultaneous timeouts of periodic timers. */ - public CompactLocalTimerMealy(Collection> nonDelayingInputs, - O silentOutput, - AbstractSymbolCombiner outputCombiner - ) { - this.untimedAlphabet = new GrowingMapAlphabet<>(); - this.untimedAlphabet.addAll(nonDelayingInputs); + public CompactMMLT(Collection> nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { + this.untimedAlphabet = Alphabets.fromCollection(nonDelayingInputs); this.sortedTimers = new HashMap<>(); this.resets = new HashMap<>(); @@ -54,34 +64,31 @@ public CompactLocalTimerMealy(Collection> nonDelayingInputs, this.outputCombiner = outputCombiner; // Prepare compact Mealy: - GrowingMapAlphabet> inputAlphabet = new GrowingMapAlphabet<>(); - inputAlphabet.addAll(nonDelayingInputs); - this.automaton = new CompactMealy<>(inputAlphabet); + this.automaton = new CompactMealy<>(new GrowingMapAlphabet<>(nonDelayingInputs)); } - @Override public O getSilentOutput() { return this.silentOutput; } @Override - public AbstractSymbolCombiner getOutputCombiner() { + public SymbolCombiner getOutputCombiner() { return this.outputCombiner; } @Override - public Alphabet> getInputAlphabet() { + public Alphabet> getInputAlphabet() { return this.automaton.getInputAlphabet(); } @Override - public Alphabet> getUntimedAlphabet() { + public Alphabet> getUntimedAlphabet() { return this.untimedAlphabet; } @Override - public boolean isLocalReset(Integer location, NonDelayingInput input) { + public boolean isLocalReset(Integer location, InputSymbol input) { return this.resets.getOrDefault(location, Collections.emptySet()).contains(input); } @@ -91,8 +98,8 @@ public List> getSortedTimers(Integer location) { } @Override - public LocalTimerMealySemantics getSemantics() { - return new net.automatalib.automaton.time.impl.mmlt.LocalTimerMealySemantics<>(this); + public MMLTSemantics getSemantics() { + return new CompactMMLTSemantics<>(this); } @Override @@ -101,12 +108,8 @@ public Collection getStates() { } @Override - public @Nullable LocalTimerMealyTransition getTransition(Integer location, LocalTimerMealyInputSymbol input) { - var trans = this.automaton.getTransition(location, input); - if (trans == null) { - return null; - } - return new LocalTimerMealyTransition<>(trans.getSuccId(), trans.getProperty()); + public @Nullable CompactTransition getTransition(Integer location, SymbolicInput input) { + return automaton.getTransition(location, input); } @Override @@ -115,8 +118,31 @@ public Collection getStates() { } @Override - public Integer addState() { - return automaton.addState(); + public void clear() { + this.automaton.clear(); + } + + @Override + public Integer addState(@Nullable Void property) { + return this.automaton.addState(property); + } + + @Override + public void setStateProperty(Integer state, Void property) {} + + @Override + public void setTransitionProperty(CompactTransition transition, O property) { + this.automaton.setTransitionProperty(transition, property); + } + + @Override + public void removeAllTransitions(Integer state) { + this.automaton.removeAllTransitions(state); + } + + @Override + public CompactTransition createTransition(Integer successor, O properties) { + return this.automaton.createTransition(successor, properties); } @Override @@ -125,20 +151,40 @@ public void setInitialState(Integer location) { } @Override - public void addTransition(Integer source, NonDelayingInput input, O output, Integer target) { - automaton.addAlphabetSymbol(input); - this.untimedAlphabet.addSymbol(input); + public void setTransition(Integer state, SymbolicInput input, @Nullable CompactTransition transition) { + this.automaton.setTransition(state, input, transition); + } + + @Override + public Void getStateProperty(Integer state) { + return null; + } + + @Override + public O getTransitionProperty(CompactTransition transition) { + return transition.getProperty(); + } - automaton.removeAllTransitions(source, input); // remove transition if already defined - automaton.addTransition(source, input, target, output); + @Override + public Integer getSuccessor(CompactTransition transition) { + return transition.getSuccId(); + } + + @Override + public MutableMapping createStaticStateMapping() { + return automaton.createStaticStateMapping(); } @Override - public void removeTransition(Integer source, NonDelayingInput input) { - automaton.removeAllTransitions(source, input); + public MutableMapping createDynamicStateMapping() { + return automaton.createDynamicStateMapping(); } - private void ensureThatCanAddTimer(List> timers, String name, long initial, O output, boolean periodic) { + private void ensureThatCanAddTimer(List> timers, + String name, + long initial, + O output, + boolean periodic) { if (output.equals(this.silentOutput)) { throw new IllegalArgumentException(String.format("Provided silent output for timer '%s'.", name)); } @@ -152,12 +198,18 @@ private void ensureThatCanAddTimer(List> timers, String name, var oldOneShot = timers.stream().filter(t -> !t.periodic()).findFirst(); if (oldOneShot.isPresent()) { if (initial > oldOneShot.get().initial()) { - throw new IllegalArgumentException(String.format("The initial value %d of '%s' exceeds that of a one-shot timer; will never time out.", initial, name)); + throw new IllegalArgumentException(String.format( + "The initial value %d of '%s' exceeds that of a one-shot timer; will never time out.", + initial, + name)); } if (periodic && (oldOneShot.get().initial() % initial == 0)) { // Our new periodic timer will time out at the same time as the existing one-shot timer. // This makes the model non-deterministic and is not allowed: - throw new IllegalArgumentException(String.format("The timer '%s' times out at the same time as a one-shot timer (%d).", name, initial)); + throw new IllegalArgumentException(String.format( + "The timer '%s' times out at the same time as a one-shot timer (%d).", + name, + initial)); } } if (!periodic) { @@ -165,7 +217,10 @@ private void ensureThatCanAddTimer(List> timers, String name, // Check that no timer with a lower initial value will time out at the same time: for (var timer : timers) { if (timer.initial() <= initial && initial % timer.initial() == 0) { - throw new IllegalArgumentException(String.format("The existing timer '%s' times out at the same time as the new one-shot timer (%d).", timer.name(), initial)); + throw new IllegalArgumentException(String.format( + "The existing timer '%s' times out at the same time as the new one-shot timer (%d).", + timer.name(), + initial)); } } } @@ -216,7 +271,7 @@ public void removeTimer(Integer location, String timerName) { } @Override - public void addLocalReset(Integer location, NonDelayingInput input) { + public void addLocalReset(Integer location, InputSymbol input) { // Ensure that input causes self-loop: var target = this.getSuccessor(location, input); if (target == null || !target.equals(location)) { @@ -228,7 +283,7 @@ public void addLocalReset(Integer location, NonDelayingInput input) { } @Override - public void removeLocalReset(Integer location, NonDelayingInput input) { + public void removeLocalReset(Integer location, InputSymbol input) { var localResets = resets.get(location); if (localResets == null) { return; diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java new file mode 100644 index 000000000..5dad90786 --- /dev/null +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java @@ -0,0 +1,221 @@ +package net.automatalib.automaton.mmlt.impl; + +import java.util.List; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.automaton.concept.Output; +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.transducer.impl.MealyTransition; +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.symbol.time.TimerTimeoutSymbol; +import net.automatalib.word.Word; +import net.automatalib.word.WordBuilder; +import org.checkerframework.checker.nullness.qual.NonNull; + +/** + * Defines the semantics of an MMLT. + *

+ * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are + * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values of + * this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the symbolic input + * timeout, which causes a delay until the next timeout. + *

+ * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all + * transitions, except for those with the input timeout. + * + * @param + * Location type + * @param + * Input type for non-delaying inputs + * @param + * Output type of the MMLT + */ +public class CompactMMLTSemantics + implements MMLTSemantics, TimedOutput>, O> { + + private final State initialConfiguration; + private final MMLT model; + + private final Alphabet> alphabet; + private final TimedOutput silentOutput; + + public CompactMMLTSemantics(MMLT model) { + this.model = model; + + var initialLocation = model.getInitialState(); + this.initialConfiguration = new State<>(initialLocation, model.getSortedTimers(initialLocation)); + + this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet()); + this.alphabet.add(TimedInput.timeout()); + this.alphabet.add(TimedInput.step()); + + this.silentOutput = new TimedOutput<>(model.getSilentOutput()); + } + + @Override + public Alphabet> getInputAlphabet() { + return alphabet; + } + + @Override + public TimedOutput getSilentOutput() { + return this.silentOutput; + } + + @Override + public State getInitialState() { + return this.initialConfiguration; + } + + @Override + public Word> computeSuffixOutput(Iterable> prefix, + Iterable> suffix) { + WordBuilder> wbOutput = Output.getBuilderFor(suffix); + var currentConfiguration = getState(prefix); + for (var sym : suffix) { + var trans = getTransition(currentConfiguration, sym); + currentConfiguration = trans.getSuccessor(); + + if (trans.getOutput() == null) { + throw new IllegalArgumentException( + "Cannot use time step sequences in suffix that have more than one symbol."); + } + wbOutput.append(trans.getOutput()); + } + + return wbOutput.toWord(); + } + + @Override + public @NonNull MealyTransition, TimedOutput> getTransition(State source, TimedInput input) { + return getTransition(source, input, Long.MAX_VALUE); + } + + @Override + public @NonNull MealyTransition, TimedOutput> getTransition(State source, + TimedInput input, + long maxWaitingTime) { + var sourceCopy = source.copy(); // we do not want to modify values of the source configuration + + if (input instanceof InputSymbol ndi) { + return getTransition(sourceCopy, ndi); + } else if (input instanceof TimeoutSymbol) { + return getTimeoutTransition(sourceCopy, maxWaitingTime); + } else if (input instanceof TimeStepSequence ts) { + // Per step, we can advance at most by the time to the next timeout: + var currentConfig = sourceCopy; + TimedOutput lastOutput = null; + long remainingTime = ts.timeSteps(); + while (remainingTime > 0) { + var nextTimeoutTrans = getTimeoutTransition(currentConfig, remainingTime); + lastOutput = nextTimeoutTrans.getOutput(); + if (nextTimeoutTrans.getOutput().equals(this.getSilentOutput())) { + // No timer will expire during remaining waiting time: + break; + } else { + remainingTime -= nextTimeoutTrans.getOutput().delay(); + currentConfig = nextTimeoutTrans.getSuccessor(); + } + } + + if (ts.timeSteps() > 1) { + lastOutput = null; // ignore multiple outputs + } else { + // Output for single time step includes no delay by definition: + lastOutput = new TimedOutput<>(lastOutput.symbol()); + } + + // Return final target + output: + return new MealyTransition<>(currentConfig, lastOutput); + } else { + throw new IllegalArgumentException("Unknown input symbol type"); + } + } + + @Override + public TimedOutput getTransitionOutput(MealyTransition, TimedOutput> transition) { + return transition.getOutput(); + } + + @Override + public State getSuccessor(MealyTransition, TimedOutput> transition) { + return transition.getSuccessor(); + } + + private MealyTransition, TimedOutput> getTimeoutTransition(State source, long maxWaitingTime) { + State target; + TimedOutput output; + + var nextTimeouts = source.getNextExpiringTimers(); + if (nextTimeouts == null) { + // no timers: + output = this.getSilentOutput(); + target = source; + } else if (nextTimeouts.delay() > maxWaitingTime) { + // timers, but too far away: + target = source; + target.decrement(maxWaitingTime); + output = this.getSilentOutput(); + } else { + if (nextTimeouts.allPeriodic()) { + target = source; + target.decrement(nextTimeouts.delay()); + } else { + // query target + update configuration: + assert nextTimeouts.timers().size() == 1; + TimerTimeoutSymbol expiringTimerSym = new TimerTimeoutSymbol<>(nextTimeouts.timers().get(0).name()); + + var successor = model.getSuccessor(source.getLocation(), expiringTimerSym); + target = new State<>(successor, model.getSortedTimers(successor)); + target.resetTimers(); + } + + // Create combined output: + if (nextTimeouts.timers().size() == 1) { + output = new TimedOutput<>(nextTimeouts.timers().get(0).output(), nextTimeouts.delay()); + } else { + List outputs = nextTimeouts.timers().stream().map(MealyTimerInfo::output).toList(); + O combinedOutput = model.getOutputCombiner().combineSymbols(outputs); + output = new TimedOutput<>(combinedOutput, nextTimeouts.delay()); + } + } + + return new MealyTransition<>(target, output); + } + + private MealyTransition, TimedOutput> getTransition(State source, InputSymbol input) { + State target; + TimedOutput output; + + var trans = model.getTransition(source.getLocation(), input); + if (trans == null) { // silent self-loop + target = source; + output = this.getSilentOutput(); + } else { + // Identify successor configuration: + S succ = model.getSuccessor(trans); + if (!succ.equals(source.getLocation())) { + // Change to a different location resets all timers in target: + target = new State<>(succ, model.getSortedTimers(succ)); + target.resetTimers(); + } else if (model.isLocalReset(source.getLocation(), input)) { + target = source; + target.resetTimers(); + } else { + target = source; + } + output = new TimedOutput<>(model.getTransitionProperty(trans)); + } + + // Return output: + return new MealyTransition<>(target, output); + } +} diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/ReducedMMLTSemantics.java similarity index 67% rename from core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java rename to core/src/main/java/net/automatalib/automaton/mmlt/impl/ReducedMMLTSemantics.java index 28ed120f9..bd3b52a7d 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/ReducedLocalTimerMealySemantics.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/ReducedMMLTSemantics.java @@ -1,11 +1,12 @@ -package net.automatalib.automaton.time.impl.mmlt; +package net.automatalib.automaton.mmlt.impl; 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.TimeoutSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimedOutput; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.transducer.impl.CompactMealy; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -28,32 +29,36 @@ * @param Input type for non-delaying inputs * @param Output symbol type */ -public class ReducedLocalTimerMealySemantics extends CompactMealy, LocalTimerMealyOutputSymbol> { +public class ReducedMMLTSemantics extends CompactMealy, TimedOutput> { - private final static Logger logger = LoggerFactory.getLogger(ReducedLocalTimerMealySemantics.class); + private final static Logger logger = LoggerFactory.getLogger(ReducedMMLTSemantics.class); - private final Map, Integer> stateMap; + private final Map, Integer> stateMap; - private ReducedLocalTimerMealySemantics(Alphabet> alphabet) { + private ReducedMMLTSemantics(Alphabet> alphabet) { super(alphabet); this.stateMap = new HashMap<>(); } - public static ReducedLocalTimerMealySemantics forLocalTimerMealy(LocalTimerMealy automaton) { + public static ReducedMMLTSemantics forLocalTimerMealy(MMLT automaton) { + return forLocalTimerMealy(automaton, automaton.getSemantics()); + } + + private static ReducedMMLTSemantics forLocalTimerMealy(MMLT automaton, MMLTSemantics semantics) { // Create alphabet for expanded form: - var alphabet = automaton.getSemantics().getInputAlphabet(); + var alphabet = semantics.getInputAlphabet(); - ReducedLocalTimerMealySemantics mealy = new ReducedLocalTimerMealySemantics<>(alphabet); + ReducedMMLTSemantics mealy = new ReducedMMLTSemantics<>(alphabet); // 1a: Add all configurations that can be reached via timeouts/non-delaying inputs, or are at least one time // step away from these configurations: for (var loc : automaton.getStates()) { - getRelevantConfigurations(loc, automaton) + getRelevantConfigurations(loc, automaton, semantics) .forEach(c -> mealy.stateMap.put(c, mealy.addState())); } // 1b: Mark initial state: - var initialConfig = automaton.getSemantics().getInitialConfiguration(); + var initialConfig = semantics.getInitialState(); mealy.setInitialState(mealy.stateMap.get(initialConfig)); // 2. Add transitions: @@ -61,11 +66,11 @@ public static ReducedLocalTimerMealySemantics forLocalTimerMe var sourceState = mealy.stateMap.get(config); for (var sym : alphabet) { - var trans = automaton.getSemantics().getTransition(config, sym); - var output = trans.output(); + var trans = semantics.getTransition(config, sym); + var output = semantics.getTransitionOutput(trans); // Try to find matching state. If not found, leave undefined: - int targetId = mealy.stateMap.getOrDefault(trans.target(), -1); + int targetId = mealy.stateMap.getOrDefault(semantics.getSuccessor(trans), -1); if (targetId != -1) { mealy.addTransition(sourceState, sym, targetId, output); } @@ -87,24 +92,27 @@ public static ReducedLocalTimerMealySemantics forLocalTimerMe * @param automaton MMLT * @return List of the relevant configurations of the location */ - private static List> getRelevantConfigurations(S location, - LocalTimerMealy automaton) { + private static List> getRelevantConfigurations(S location, + MMLT automaton, + MMLTSemantics semantics) { - List> configurations = new ArrayList<>(); + List> configurations = new ArrayList<>(); - LocalTimerMealyConfiguration currentConfiguration = new LocalTimerMealyConfiguration<>(location, automaton.getSortedTimers(location)); + State currentConfiguration = new State<>(location, automaton.getSortedTimers(location)); configurations.add(currentConfiguration); // Enumerate all timeouts, until we change to a different location or re-enter the entry configuration // of this location: while (true) { // Wait for next timeout: - var trans = automaton.getSemantics().getTransition(currentConfiguration, new TimeoutSymbol<>()); - if (trans.output().equals(automaton.getSemantics().getSilentOutput())) { + var trans = semantics.getTransition(currentConfiguration, new TimeoutSymbol<>()); + var output = semantics.getTransitionOutput(trans); + var target = semantics.getSuccessor(trans); + if (output.equals(semantics.getSilentOutput())) { break; // no timeout } - if (trans.output().getDelay() > 1) { + if (output.delay() > 1) { // More than one time unit away -> add 1-step successor config. // If one time unit away, the successor is already in our list. var newGapConfig = currentConfiguration.copy(); @@ -112,11 +120,11 @@ private static List> getRelevant configurations.add(newGapConfig); } - if (trans.target().isEntryConfig()) { + if (target.isEntryConfig()) { break; // location change OR repeating behavior } - configurations.add(trans.target()); - currentConfiguration = trans.target(); + configurations.add(target); + currentConfiguration = target; } return configurations; } @@ -133,9 +141,9 @@ private static List> getRelevant * @param allowApproximate If set, the closest matching state is returned if the configuration is not part of the reduced automaton * @return Corresponding state in the reduced automaton */ - public Integer getStateForConfiguration(LocalTimerMealyConfiguration configuration, boolean allowApproximate) { + public Integer getStateForConfiguration(State configuration, boolean allowApproximate) { - LocalTimerMealyConfiguration closestMatch = null; + State closestMatch = null; for (var cfg : stateMap.keySet()) { if (!cfg.getLocation().equals(configuration.getLocation()) || @@ -168,7 +176,7 @@ public Integer getStateForConfiguration(LocalTimerMealyConfiguration co * @param state Considered state * @return Corresponding configuration */ - public LocalTimerMealyConfiguration getConfigurationForState(int state) { + public State getConfigurationForState(int state) { return this.stateMap.entrySet().stream() .filter(e -> e.getValue() == state) .map(Map.Entry::getKey) diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/StringSymbolCombiner.java similarity index 73% rename from core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java rename to core/src/main/java/net/automatalib/automaton/mmlt/impl/StringSymbolCombiner.java index 1157e118a..aa5472517 100644 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/StringSymbolCombiner.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/StringSymbolCombiner.java @@ -1,6 +1,6 @@ -package net.automatalib.automaton.time.impl.mmlt; +package net.automatalib.automaton.mmlt.impl; -import net.automatalib.automaton.time.mmlt.AbstractSymbolCombiner; +import net.automatalib.automaton.mmlt.SymbolCombiner; import java.util.Arrays; import java.util.HashSet; @@ -11,7 +11,7 @@ /** * Combines multiple String outputs by concatenating them and using a pipe as separator. */ -public class StringSymbolCombiner extends AbstractSymbolCombiner { +public class StringSymbolCombiner implements SymbolCombiner { private static final StringSymbolCombiner combiner = new StringSymbolCombiner(); @@ -19,10 +19,7 @@ public static StringSymbolCombiner getInstance() { return combiner; } - private StringSymbolCombiner() { - - } - + private StringSymbolCombiner() {} @Override public boolean isCombinedSymbol(String symbol) { @@ -47,9 +44,7 @@ public String combineSymbols(List symbols) { } // Sort the symbols + separate with pipe: - return expandedSymbols.stream() - .sorted() - .collect(Collectors.joining("|")); + return expandedSymbols.stream().sorted().collect(Collectors.joining("|")); } @Override @@ -58,9 +53,6 @@ public List separateSymbols(String symbol) { return List.of(symbol); } - return Arrays.stream(symbol.split("\\|")) - .distinct() - .sorted() - .toList(); + return Arrays.stream(symbol.split("\\|")).distinct().sorted().toList(); } } diff --git a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java b/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java deleted file mode 100644 index e7411b296..000000000 --- a/core/src/main/java/net/automatalib/automaton/time/impl/mmlt/LocalTimerMealySemantics.java +++ /dev/null @@ -1,220 +0,0 @@ -package net.automatalib.automaton.time.impl.mmlt; - -import net.automatalib.alphabet.Alphabet; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.*; -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.LocalTimerMealySemanticTransition; -import net.automatalib.word.Word; -import net.automatalib.word.WordBuilder; -import org.checkerframework.checker.nullness.qual.NonNull; - -import java.util.List; - -/** - * Defines the semantics of an MMLT. - *

- * The semantics of an MMLT are defined with an associated Mealy machine. The states of this machine are - * LocalTimerMealyConfiguration objects. These represent tuples of an active location and the current timer values - * of this location. The inputs of the machine are non-delaying inputs, discrete time steps, and the - * symbolic input timeout, which causes a delay until the next timeout. - *

- * The outputs of this machine are the outputs of the MMLT, extended with a delay. This delay is zero for all - * transitions, except for those with the input timeout. - * - * @param Location type - * @param Input type for non-delaying inputs - * @param Output type of the MMLT - */ -public class LocalTimerMealySemantics implements net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealySemantics { - private final LocalTimerMealyConfiguration initialConfiguration; - private final LocalTimerMealy model; - - private final Alphabet> alphabet; - private final LocalTimerMealyOutputSymbol silentOutput; - - public LocalTimerMealySemantics(LocalTimerMealy model) { - this.model = model; - - var initialLocation = model.getInitialState(); - this.initialConfiguration = new LocalTimerMealyConfiguration<>(initialLocation, model.getSortedTimers(initialLocation)); - - this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet()); - this.alphabet.add(new TimeoutSymbol<>()); - this.alphabet.add(new TimeStepSymbol<>()); - - this.silentOutput = new LocalTimerMealyOutputSymbol<>(model.getSilentOutput()); - } - - - @Override - public Alphabet> getInputAlphabet() { - return alphabet; - } - - - @Override - public LocalTimerMealyOutputSymbol getSilentOutput() { - return this.silentOutput; - } - - - @Override - public LocalTimerMealyConfiguration getInitialConfiguration() { - return this.initialConfiguration; - } - - - @Override - public Word> computeSuffixOutput(LocalTimerMealyConfiguration configuration, Word> suffix) { - WordBuilder> wbOutput = new WordBuilder<>(); - - var currentConfiguration = configuration; - for (var sym : suffix) { - var trans = getTransition(currentConfiguration, sym); - currentConfiguration = trans.target(); - - if (trans.output() == null) { - throw new IllegalArgumentException("Cannot use time step sequences in suffix that have more than one symbol."); - } - wbOutput.append(trans.output()); - } - - return wbOutput.toWord(); - } - - - @Override - public Word> computeSuffixOutput(Word> prefix, Word> suffix) { - var prefixConfig = this.traceInputs(prefix); - return computeSuffixOutput(prefixConfig, suffix); - } - - - @Override - public LocalTimerMealyConfiguration traceInputs(Word> prefix) { - var currentConfiguration = getInitialConfiguration().copy(); - for (var sym : prefix) { - currentConfiguration = getTransition(currentConfiguration, sym).target(); - } - return currentConfiguration; - } - - - @Override - public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, LocalTimerMealySemanticInputSymbol input) { - return getTransition(source, input, Long.MAX_VALUE); - } - - - @Override - public @NonNull LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, - LocalTimerMealySemanticInputSymbol input, - long maxWaitingTime) { - var sourceCopy = source.copy(); // we do not want to modify values of the source configuration - - if (input instanceof NonDelayingInput ndi) { - return getTransition(sourceCopy, ndi); - } else if (input instanceof TimeoutSymbol) { - return getTimeoutTransition(sourceCopy, maxWaitingTime); - } else if (input instanceof TimeStepSequence ts) { - // Per step, we can advance at most by the time to the next timeout: - var currentConfig = sourceCopy; - LocalTimerMealyOutputSymbol lastOutput = null; - long remainingTime = ts.getTimeSteps(); - while (remainingTime > 0) { - var nextTimeoutTrans = getTimeoutTransition(currentConfig, remainingTime); - lastOutput = nextTimeoutTrans.output(); - if (nextTimeoutTrans.output().equals(this.getSilentOutput())) { - // No timer will expire during remaining waiting time: - break; - } else { - remainingTime -= nextTimeoutTrans.output().getDelay(); - currentConfig = nextTimeoutTrans.target(); - } - } - - if (ts.getTimeSteps() > 1) { - lastOutput = null; // ignore multiple outputs - } else { - // Output for single time step includes no delay by definition: - lastOutput = new LocalTimerMealyOutputSymbol<>(lastOutput.getSymbol()); - } - - // Return final target + output: - return new LocalTimerMealySemanticTransition<>(lastOutput, currentConfig); - } else { - throw new IllegalArgumentException("Unknown input symbol type"); - } - } - - private LocalTimerMealySemanticTransition getTimeoutTransition(LocalTimerMealyConfiguration source, long maxWaitingTime) { - LocalTimerMealyConfiguration target; - LocalTimerMealyOutputSymbol output; - - var nextTimeouts = source.getNextExpiringTimers(); - if (nextTimeouts == null) { - // no timers: - output = this.getSilentOutput(); - target = source; - } else if (nextTimeouts.delay() > maxWaitingTime) { - // timers, but too far away: - target = source; - target.decrement(maxWaitingTime); - output = this.getSilentOutput(); - } else { - if (nextTimeouts.allPeriodic()) { - target = source; - target.decrement(nextTimeouts.delay()); - } else { - // query target + update configuration: - assert nextTimeouts.timers().size() == 1; - TimerTimeoutSymbol expiringTimerSym = new TimerTimeoutSymbol<>(nextTimeouts.timers().get(0).name()); - - var successor = model.getSuccessor(source.getLocation(), expiringTimerSym); - target = new LocalTimerMealyConfiguration<>(successor, model.getSortedTimers(successor)); - target.resetTimers(); - } - - // Create combined output: - if (nextTimeouts.timers().size() == 1) { - output = new LocalTimerMealyOutputSymbol<>(nextTimeouts.delay(), nextTimeouts.timers().get(0).output()); - } else { - List outputs = nextTimeouts.timers().stream().map(MealyTimerInfo::output).toList(); - O combinedOutput = model.getOutputCombiner().combineSymbols(outputs); - output = new LocalTimerMealyOutputSymbol<>(nextTimeouts.delay(), combinedOutput); - } - } - - return new LocalTimerMealySemanticTransition<>(output, target); - } - - private LocalTimerMealySemanticTransition getTransition(LocalTimerMealyConfiguration source, NonDelayingInput input) { - LocalTimerMealyConfiguration target; - LocalTimerMealyOutputSymbol output; - - var trans = model.getTransition(source.getLocation(), input); - if (trans == null) { // silent self-loop - target = source; - output = this.getSilentOutput(); - } else { - // Identify successor configuration: - if (!trans.successor().equals(source.getLocation())) { - // Change to a different location resets all timers in target: - target = new LocalTimerMealyConfiguration<>(trans.successor(), model.getSortedTimers(trans.successor())); - target.resetTimers(); - } else if (model.isLocalReset(source.getLocation(), input)) { - target = source; - target.resetTimers(); - } else { - target = source; - } - output = new LocalTimerMealyOutputSymbol<>(trans.output()); - } - - // Return output: - return new LocalTimerMealySemanticTransition<>(output, target); - } -} diff --git a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java b/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java similarity index 59% rename from core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java rename to core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java index 43f6cab24..d40ccb401 100644 --- a/core/src/test/java/net/automatalib/automaton/impl/LocalTimerMealyTests.java +++ b/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java @@ -1,27 +1,26 @@ -package net.automatalib.automaton.impl; +package net.automatalib.automaton.mmlt.impl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; 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.impl.mmlt.ReducedLocalTimerMealySemantics; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; +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.testng.Assert; import org.testng.annotations.Test; +public class MMLTTests { -import java.util.ArrayList; -import java.util.List; -import java.util.Random; - -public class LocalTimerMealyTests { - - public CompactLocalTimerMealy buildBaseModel() { + public 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(); @@ -30,19 +29,19 @@ public 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; } @@ -73,9 +72,7 @@ public void testStringCombiner() { Assert.assertEquals(words.stream().sorted().toList(), separated.stream().sorted().toList()); // Tests words with an absent output: - Assert.assertThrows(IllegalArgumentException.class, - () -> combiner.combineSymbols(List.of("|", "d|c")) - ); + Assert.assertThrows(IllegalArgumentException.class, () -> combiner.combineSymbols(List.of("|", "d|c"))); var combinedAbsent = combiner.combineSymbols(List.of("a|", "d|c")); Assert.assertEquals(combinedAbsent, "a|c|d"); @@ -95,18 +92,18 @@ public void testStringCombiner() { public void testConfigurationProperties() { var model = buildBaseModel(); - var nonStableConfig = model.getSemantics().traceInputs(Word.fromSymbols( - new NonDelayingInput<>("p1"), new TimeStepSequence<>(12) - )); + var nonStableConfig = model.getSemantics() + .getState(Word.fromSymbols(new InputSymbol<>("p1"), + new TimeStepSequence<>(12))); + Assert.assertNotNull(nonStableConfig); Assert.assertFalse(nonStableConfig.isStableConfig()); Assert.assertFalse(nonStableConfig.isEntryConfig()); Assert.assertEquals(nonStableConfig.getEntryDistance(), 12); Assert.assertEquals(nonStableConfig.getLocation().intValue(), 1); - - var stableConfig = model.getSemantics().traceInputs(Word.fromSymbols( - new NonDelayingInput<>("p1"), new TimeStepSequence<>(2) - )); + var stableConfig = model.getSemantics() + .getState(Word.fromSymbols(new InputSymbol<>("p1"), new TimeStepSequence<>(2))); + Assert.assertNotNull(stableConfig); Assert.assertTrue(stableConfig.isStableConfig()); Assert.assertFalse(stableConfig.isEntryConfig()); Assert.assertEquals(stableConfig.getEntryDistance(), 2); @@ -116,15 +113,20 @@ public void testConfigurationProperties() { @Test public void testReducedSemanticsIncludedConfiguration() { var automaton = buildBaseModel(); - var reducedSemanticsModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(automaton); + var reducedSemanticsModel = ReducedMMLTSemantics.forLocalTimerMealy(automaton); Assert.assertEquals(reducedSemanticsModel.size(), 31); // Reachable in both automata: - Word> includedConfigPrefix = Word.fromSymbols( - new NonDelayingInput<>("p1"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), - new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeoutSymbol<>(), new TimeStepSymbol<>() - ); - var includedConfig = automaton.getSemantics().traceInputs(includedConfigPrefix); + Word> includedConfigPrefix = + Word.fromSymbols(new InputSymbol<>("p1"), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + TimedInput.step()); + var includedConfig = automaton.getSemantics().getState(includedConfigPrefix); // Verify that the reached states are identical: var expectedState = reducedSemanticsModel.getStateForConfiguration(includedConfig, false); @@ -141,19 +143,22 @@ public void testReducedSemanticsIncludedConfiguration() { @Test public void testReducedSemanticsOmittedConfiguration() { var automaton = buildBaseModel(); - var reducedSemanticsModel = ReducedLocalTimerMealySemantics.forLocalTimerMealy(automaton); + var reducedSemanticsModel = ReducedMMLTSemantics.forLocalTimerMealy(automaton); Assert.assertEquals(reducedSemanticsModel.size(), 31); // Only reachable via at least two following time steps: - Word> omittedConfigPrefix = Word.fromSymbols( - new NonDelayingInput<>("p1"), new TimeoutSymbol<>(), new TimeoutSymbol<>(), - new TimeStepSymbol<>(), new TimeStepSymbol<>() - ); - var omittedConfig = automaton.getSemantics().traceInputs(omittedConfigPrefix); + Word> omittedConfigPrefix = + Word.fromSymbols(new InputSymbol<>("p1"), + new TimeoutSymbol<>(), + new TimeoutSymbol<>(), + TimedInput.step(), + TimedInput.step()); + var omittedConfig = automaton.getSemantics().getState(omittedConfigPrefix); // Verify that we cannot reach this state in the reduced semantics: Assert.assertNull(reducedSemanticsModel.getState(omittedConfigPrefix)); - Assert.assertThrows(IllegalStateException.class, () -> reducedSemanticsModel.getStateForConfiguration(omittedConfig, false)); + Assert.assertThrows(IllegalStateException.class, + () -> reducedSemanticsModel.getStateForConfiguration(omittedConfig, false)); // Verify that the output is incomplete: var fullOutput = automaton.getSemantics().computeSuffixOutput(Word.epsilon(), omittedConfigPrefix); @@ -161,6 +166,7 @@ public void testReducedSemanticsOmittedConfiguration() { Assert.assertNotEquals(fullOutput, reducedOutput); // Check the approximated state: + Assert.assertNotNull(omittedConfig); var approxStateId = reducedSemanticsModel.getStateForConfiguration(omittedConfig, true); var approxConfig = reducedSemanticsModel.getConfigurationForState(approxStateId); @@ -168,7 +174,6 @@ public void testReducedSemanticsOmittedConfiguration() { Assert.assertEquals(approxConfig.getEntryDistance(), 7); } - @Test public void testInvalidTimerChecks() { var automaton = buildBaseModel(); @@ -176,33 +181,21 @@ public void testInvalidTimerChecks() { int s1 = 1; // Duplicate timer name: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "a", 3, "test") - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "a", 3, "test")); // Timer with silent output: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 3, "void") - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 3, "void")); // Timer never expires: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 41, "test") - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 41, "test")); // One-shot timer that times out at same time as periodic: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3)); // Periodic timer that times out at same time as one-shot: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 20, "test") - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 20, "test")); // Duplicate one-shot timer: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) - ); + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3)); } } diff --git a/pom.xml b/pom.xml index 59f1160c0..bf48e1acd 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,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/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java new file mode 100644 index 000000000..554f0a26e --- /dev/null +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java @@ -0,0 +1,317 @@ +package net.automatalib.serialization.dot; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.Alphabets; +import net.automatalib.alphabet.impl.MapAlphabet; +import net.automatalib.automaton.mmlt.MMLTCreator; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.MutableMMLT; +import net.automatalib.automaton.mmlt.SymbolCombiner; +import net.automatalib.common.util.IOUtil; +import net.automatalib.common.util.mapping.Mapping; +import net.automatalib.common.util.mapping.MutableMapping; +import net.automatalib.exception.FormatException; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.visualization.VisualizationHelper.EdgeAttrs; +import net.automatalib.visualization.VisualizationHelper.MMLTEdgeAttrs; +import net.automatalib.visualization.VisualizationHelper.MMLTNodeAttrs; + +/** + * Parses a DOT file that defines an MMLT automaton. + *

Expected syntax:

+ *
    + *
  • Mealy labels: input/output
  • + *
  • Initial node marker: __start0
  • + *
  • Timeout input: to[x] where x is the name of a local timer
  • + *
  • Local timers: node attribute timers with comma-separated assignments x=t with t > 0. + * Timer names must be unique per location. For one-shot timers choose values such that they never expire at the + * same time as another local timer.
  • + *
  • Reset behavior: edge attribute resets as specified below.
  • + *
+ *

Resets:

+ *
    + *
  • If the input is a timeout symbol: + *
      + *
    • Attribute omitted: periodic if self-loop, one-shot otherwise.
    • + *
    • Self-loop and value is list of all local timers: one-shot timer.
    • + *
    • Self-loop and value is the name of the timed-out timer: periodic.
    • + *
    • Any other value: invalid.
    • + *
    + *
  • + *
  • If the input is a normal input and the edge is a self-loop: + *
      + *
    • Attribute omitted: regular edge.
    • + *
    • Value is list of all local timers: local reset.
    • + *
    • Any other value: invalid.
    • + *
    + * If the edge is not a self-loop, reset values are ignored. + *
  • + *
+ *

Notes:

+ *
    + *
  • It is currently not possible to define a location with a single timer that is one-shot and whose timeout + * causes a self-loop. Such a timer is always considered periodic. This is semantically equivalent for learning, + * but hypotheses may still use the former variant; those timers may be highlighted specially for debugging.
  • + *
  • Edges with a timeout input must not be silent.
  • + *
+ *

Example DOT:

+ *
{@code
+ * 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 reset
+ *    s2 -> s2 [label="y / D"] // loop without reset
+ *
+ *    __start0 [label="" shape="none" width="0" height="0"];
+ *    __start0 -> s0;
+ * }
+ * }
+ */ + +public class DOTMMLTParser> + implements DOTInputModelDeserializer, A> { + + private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); + + private final MMLTCreator creator; + private final Function> inputParser; + private final Function outputParser; + private final O silentSymbol; + private final SymbolCombiner outputCombiner; + private final Collection initialNodeIds; + private final boolean fakeInitialNodeIds; + + public DOTMMLTParser(MMLTCreator creator, + Function> inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner, + Collection initialNodeIds, + boolean fakeInitialNodeIds) { + this.creator = creator; + this.inputParser = inputParser; + this.outputParser = outputParser; + this.silentSymbol = silentOutput; + this.outputCombiner = outputCombiner; + this.initialNodeIds = initialNodeIds; + this.fakeInitialNodeIds = fakeInitialNodeIds; + } + + @Override + public DOTInputModelData, A> readModel(InputStream is) throws IOException, FormatException { + + try (Reader r = IOUtil.asNonClosingUTF8Reader(is)) { + InternalDOTParser parser = new InternalDOTParser(r); + parser.parse(); + + assert parser.isDirected(); + + final Set> inputs = new HashSet<>(); + + for (Edge edge : parser.getEdges()) { + if (!fakeInitialNodeIds || !initialNodeIds.contains(edge.src)) { + final String input = tokenizeLabel(edge)[0].trim(); + if (!input.startsWith("to[")) { + inputs.add(inputParser.apply(input)); + } + } + } + + final Alphabet> alphabet = Alphabets.fromCollection(inputs); + final A automaton = creator.createMMLT(alphabet, parser.getNodes().size(), silentSymbol, outputCombiner); + + final Mapping labels = parseNodesAndEdges(parser, automaton); + + return new DOTInputModelData<>(automaton, new MapAlphabet<>(alphabet), labels); + } + } + + private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableMMLT result) { + + final Collection nodes = parser.getNodes(); + final Collection edges = parser.getEdges(); + + final Map>> timers = + new HashMap<>(nodes.size() - 1); // id in dot -> local timers + final Map stateMap = new HashMap<>(nodes.size() - 1); // name in dot -> new id + final MutableMapping mapping = result.createDynamicStateMapping(); + + // Parse nodes: + for (var node : nodes) { + final S n; + + if (fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + continue; + } else if (!fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + n = result.addInitialState(); + } else { + n = result.addState(); + } + + stateMap.put(node.id, n); + mapping.put(n, node.id); + + // Parse timers: + final String timersAttr = node.attributes.get(MMLTNodeAttrs.TIMERS); + if (timersAttr != null) { + String[] settings = timersAttr.split(","); + for (String setting : settings) { + Matcher m = assignPattern.matcher(setting.trim()); // remove whitespace + if (!m.matches()) { + continue; + } + String timerName = m.group(1).trim(); + int value = Integer.parseInt(m.group(2)); + if (value <= 0) { + throw new IllegalArgumentException(String.format( + "Reset for timer %s in location %s must be greater zero.", + timerName, + node.id)); + } + + Map> timeInfo = timers.computeIfAbsent(node.id, k -> new HashMap<>()); + if (timeInfo.containsKey(timerName)) { + throw new IllegalArgumentException(String.format( + "Timer %s in location %s must only be set once.", + timerName, + node.id)); + } + + // Add timer: + timeInfo.put(timerName, new MealyTimerInfo<>(timerName, value, null)); + } + } else { + timers.put(node.id, Collections.emptyMap()); // no timers in this location + } + } + + // Parse edges: + for (var edge : edges) { + + if (fakeInitialNodeIds && initialNodeIds.contains(edge.src)) { + result.setInitial(stateMap.get(edge.tgt), true); + continue; + } + + // Check for resets: + Set edgeResets = new HashSet<>(); + String resetAttr = edge.attributes.get(MMLTEdgeAttrs.RESETS); + if (resetAttr != null) { + // Parse resets: + for (String timer : resetAttr.split(",")) { + edgeResets.add(timer.strip()); + } + } + + final String[] tokens = tokenizeLabel(edge); + final String input = tokens[0].trim(); + final String output = tokens[1].trim(); + + if (input.startsWith("to[")) { + // Ensure that we defined the corresponding timer: + String timerName = input.substring(3, input.length() - 1); + if (!timers.get(edge.src).containsKey(timerName)) { + throw new IllegalArgumentException(String.format( + "Defined %s in state %s, but timer value is not set.", + input, + edge.src)); + } + + // Add output to timer info: + final MealyTimerInfo oldInfo = timers.get(edge.src).get(timerName); + + // Infer timer type: + final long initial = oldInfo.initial(); + boolean periodic = true; + if (edge.src.equals(edge.tgt)) { + if (edgeResets.size() == 1) { + if (!edgeResets.contains(timerName)) { + // Invalid periodic timer: + throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); + } + } else if (edgeResets.size() > 1) { + // Need to contain all local timers to be one-shot with loop: + for (var locTimer : timers.get(edge.tgt).keySet()) { + if (!edgeResets.contains(locTimer)) { + throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); + } + } + periodic = false; + } + } else { + // No need to check resets on location-change: always resetting all in target + periodic = false; + } + + final O o = outputParser.apply(output); + + // Add timer to location: + if (periodic) { + result.addPeriodicTimer(stateMap.get(edge.src), timerName, initial, o); + } else { + result.addOneShotTimer(stateMap.get(edge.src), timerName, initial, o, stateMap.get(edge.tgt)); + } + } else { + // Non-delaying input: + final InputSymbol i = inputParser.apply(input); + final O o = outputParser.apply(output); + + result.addTransition(stateMap.get(edge.src), i, stateMap.get(edge.tgt), o); + + // Parse resets of self-loops with untimed input: + if (edge.src.equals(edge.tgt) && !edgeResets.isEmpty()) { + // Reset list needs to contain all local timers: + for (var locTimer : timers.get(edge.tgt).keySet()) { + if (!edgeResets.contains(locTimer)) { + throw new IllegalArgumentException(String.format("Invalid local reset at %s", i)); + } + } + result.addLocalReset(stateMap.get(edge.src), i); + } + } + } + + return mapping; + } + + private static String[] tokenizeLabel(Edge edge) { + final String label = edge.attributes.get(EdgeAttrs.LABEL); + + if (label == null) { + throw new IllegalArgumentException("All edges must have an input and an output."); + } + + final String[] tokens = label.split("/"); + + if (tokens.length != 2) { + throw new IllegalArgumentException("All edges must have an input and an output."); + } + + return tokens; + } + +} diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java index 2380e3b54..ee144ac38 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java @@ -30,6 +30,10 @@ import net.automatalib.automaton.fsa.NFA; import net.automatalib.automaton.fsa.impl.CompactDFA; import net.automatalib.automaton.fsa.impl.CompactNFA; +import net.automatalib.automaton.mmlt.MMLTCreator; +import net.automatalib.automaton.mmlt.MutableMMLT; +import net.automatalib.automaton.mmlt.SymbolCombiner; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.transducer.MealyMachine; import net.automatalib.automaton.transducer.MooreMachine; import net.automatalib.automaton.transducer.MutableMealyMachine; @@ -41,6 +45,9 @@ import net.automatalib.graph.MutableGraph; import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.serialization.ModelDeserializer; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.ts.modal.ModalTransitionSystem; import net.automatalib.ts.modal.MutableModalTransitionSystem; import net.automatalib.ts.modal.impl.CompactMTS; @@ -695,6 +702,50 @@ public static ModelDeserializer> graph(Fu true); } + public static DOTInputModelDeserializer, CompactMMLT> mmlt(String silentOutput, + SymbolCombiner outputCombiner) { + return mmlt(TimedInput::input, Function.identity(), silentOutput, outputCombiner); + } + + public static DOTInputModelDeserializer, CompactMMLT> mmlt(Function> inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner) { + return mmlt(CompactMMLT::new, inputParser, outputParser, silentOutput, outputCombiner); + } + + public static > DOTInputModelDeserializer, A> mmlt( + MMLTCreator creator, + Function> inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner) { + return mmlt(creator, + inputParser, + outputParser, + silentOutput, + outputCombiner, + Collections.singletonList(GraphDOT.initialLabel(0)), + true); + } + + public static > DOTInputModelDeserializer, A> mmlt( + MMLTCreator creator, + Function> inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner, + Collection initialNodeIds, + boolean fakeInitialNodeIds) { + return new DOTMMLTParser<>(creator, + inputParser, + outputParser, + silentOutput, + outputCombiner, + initialNodeIds, + fakeInitialNodeIds); + } + private static String getAndRequireNotNull(Map map, String attribute) { final String value = map.get(attribute); if (value == null) { diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java deleted file mode 100644 index 5fe9c6398..000000000 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/LocalTimerMealyGraphvizParser.java +++ /dev/null @@ -1,244 +0,0 @@ -package net.automatalib.serialization.dot; - -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.mmlt.*; -import net.automatalib.common.util.IOUtil; -import net.automatalib.common.util.Pair; -import net.automatalib.exception.FormatException; -import org.checkerframework.checker.nullness.qual.Nullable; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Parses a DOT file that defines an MMLT automaton. - *

Expected syntax:

- *
    - *
  • Mealy labels: input/output
  • - *
  • Initial node marker: __start0
  • - *
  • Timeout input: to[x] where x is the name of a local timer
  • - *
  • Local timers: node attribute timers with comma-separated assignments x=t with t > 0. - * Timer names must be unique per location. For one-shot timers choose values such that they never expire at the - * same time as another local timer.
  • - *
  • Reset behavior: edge attribute resets as specified below.
  • - *
- *

Resets:

- *
    - *
  • If the input is a timeout symbol: - *
      - *
    • Attribute omitted: periodic if self-loop, one-shot otherwise.
    • - *
    • Self-loop and value is list of all local timers: one-shot timer.
    • - *
    • Self-loop and value is the name of the timed-out timer: periodic.
    • - *
    • Any other value: invalid.
    • - *
    - *
  • - *
  • If the input is a normal input and the edge is a self-loop: - *
      - *
    • Attribute omitted: regular edge.
    • - *
    • Value is list of all local timers: local reset.
    • - *
    • Any other value: invalid.
    • - *
    - * If the edge is not a self-loop, reset values are ignored. - *
  • - *
- *

Notes:

- *
    - *
  • It is currently not possible to define a location with a single timer that is one-shot and whose timeout - * causes a self-loop. Such a timer is always considered periodic. This is semantically equivalent for learning, - * but hypotheses may still use the former variant; those timers may be highlighted specially for debugging.
  • - *
  • Edges with a timeout input must not be silent.
  • - *
- *

Example DOT:

- *
{@code
- * 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 reset
- *    s2 -> s2 [label="y / D"] // loop without reset
- *
- *    __start0 [label="" shape="none" width="0" height="0"];
- *    __start0 -> s0;
- * }
- * }
- */ - -public class LocalTimerMealyGraphvizParser { - - private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); - - public static LocalTimerMealy parseLocalTimerMealy(InputStream stream, String silentOutput, AbstractSymbolCombiner outputCombiner) { - InternalDOTParser parser; - try { - parser = new InternalDOTParser(stream); - parser.parse(); - } catch (FormatException fe) { - throw new RuntimeException(String.format("Parsing failed: %s", fe)); - } - return parseLocalTimerMealy(parser, silentOutput, outputCombiner); - } - - public static LocalTimerMealy parseLocalTimerMealy(File path, String silentOutput, AbstractSymbolCombiner outputCombiner) { - InternalDOTParser parser; - try (InputStream r = IOUtil.asUncompressedBufferedInputStream(new FileInputStream(path))) { - parser = new InternalDOTParser(r); - parser.parse(); - } catch (Exception e) { - throw new RuntimeException(String.format("Parsing \"%s\" failed: %n %s", path, e)); - } - return parseLocalTimerMealy(parser, silentOutput, outputCombiner); - } - - private static LocalTimerMealy parseLocalTimerMealy(InternalDOTParser parser, String silentOutput, AbstractSymbolCombiner outputCombiner) { - final Collection nodes = parser.getNodes(); - final Collection edges = parser.getEdges(); - - final Map>> timers = new HashMap<>(nodes.size() - 1); // id in dot -> local timers - final Map stateMap = new HashMap<>(nodes.size() - 1); // name in dot -> new id - - final CompactLocalTimerMealy result = new CompactLocalTimerMealy<>(new GrowingMapAlphabet<>(), silentOutput, outputCombiner); - - // Parse nodes: - for (var node : nodes) { - if (node.id.equals(GraphDOT.initialLabel(0))) { - continue; // initial node marker - } - final int n = result.addState(); - stateMap.put(node.id, n); - - // Parse timers: - if (node.attributes.containsKey("timers")) { - String[] settings = node.attributes.get("timers").split(","); - for (String setting : settings) { - Matcher m = assignPattern.matcher(setting.trim()); // remove whitespace - if (!m.matches()) { - continue; - } - String timerName = m.group(1).trim(); - int value = Integer.parseInt(m.group(2)); - if (value <= 0) { - throw new IllegalArgumentException(String.format("Reset for timer %s in location %s must be greater zero.", - timerName, node.id)); - } - - timers.putIfAbsent(node.id, new HashMap<>()); - if (timers.get(node.id).containsKey(timerName)) { - throw new IllegalArgumentException(String.format("Timer %s in location %s must only be set once.", - timerName, node.id)); - } - - // Add timer: - timers.get(node.id).put(timerName, new MealyTimerInfo<>(timerName, value, null)); - } - } else { - timers.put(node.id, Collections.emptyMap()); // no timers in this location - } - } - - // Parse edges: - for (var edge : edges) { - // Parse input/output: - Pair<@Nullable String, @Nullable String> props = DOTParsers.DEFAULT_MEALY_EDGE_PARSER.apply(edge.attributes); - if (props.getFirst() == null || props.getSecond() == null) { - if (edge.src.equals(GraphDOT.initialLabel(0))) { - result.setInitialState(stateMap.get(edge.tgt)); - continue; - } - throw new IllegalArgumentException("All edges must have an input and an output."); - } - - // Check for resets: - HashSet edgeResets = new HashSet<>(); - if (edge.attributes.containsKey("resets")) { - // Parse resets: - for (String timer : edge.attributes.get("resets").split(",")) { - edgeResets.add(timer.strip()); - } - } - - - if (props.getFirst().startsWith("to[")) { - // Ensure that we defined the corresponding timer: - String timerName = props.getFirst().substring(3, props.getFirst().length() - 1); - if (!timers.get(edge.src).containsKey(timerName)) { - throw new IllegalArgumentException(String.format("Defined %s in state %s, but timer value is not set.", - props.getFirst(), edge.src)); - } - - // Add output to timer info: - MealyTimerInfo oldInfo = timers.get(edge.src).get(timerName); - - // Infer timer type: - var updatedInfo = new MealyTimerInfo<>(timerName, oldInfo.initial(), props.getSecond()); - if (edge.src.equals(edge.tgt)) { - if (edgeResets.size() == 1) { - if (!edgeResets.contains(timerName)) { - // Invalid periodic timer: - throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); - } - } else if (edgeResets.size() > 1) { - // Need to contain all local timers to be one-shot with loop: - for (var locTimer : timers.get(edge.tgt).keySet()) { - if (!edgeResets.contains(locTimer)) { - throw new IllegalArgumentException(String.format("Invalid reset at to[%s]", timerName)); - } - } - updatedInfo.setOneShot(); - } - } else { - // No need to check resets on location-change: always resetting all in target - updatedInfo.setOneShot(); - } - - // Add timer to location: - if (updatedInfo.periodic()) { - result.addPeriodicTimer(stateMap.get(edge.src), updatedInfo.name(), updatedInfo.initial(), updatedInfo.output()); - } else { - result.addOneShotTimer(stateMap.get(edge.src), - updatedInfo.name(), updatedInfo.initial(), updatedInfo.output(), - stateMap.get(edge.tgt)); - } - } else { - // Non-delaying input: - var nonDelInput = new NonDelayingInput<>(props.getFirst()); - - result.addTransition(stateMap.get(edge.src), nonDelInput, props.getSecond(), - stateMap.get(edge.tgt)); - - // Parse resets of self-loops with untimed input: - if (edge.src.equals(edge.tgt) && !edgeResets.isEmpty()) { - // Reset list needs to contain all local timers: - for (var locTimer : timers.get(edge.tgt).keySet()) { - if (!edgeResets.contains(locTimer)) { - throw new IllegalArgumentException(String.format("Invalid local reset at %s", nonDelInput)); - } - } - result.addLocalReset(stateMap.get(edge.src), nonDelInput); - } - } - } - - // Ensure initial location: - if (result.getInitialState() == null) { - throw new IllegalArgumentException("Automaton must have an initial location."); - } - - return result; - } - -} diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java index 9bc88c199..2e3010a2a 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java @@ -35,6 +35,9 @@ import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.fsa.impl.CompactDFA; import net.automatalib.automaton.fsa.impl.CompactNFA; +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; @@ -43,6 +46,7 @@ import net.automatalib.exception.FormatException; import net.automatalib.graph.UniversalGraph; import net.automatalib.serialization.InputModelData; +import net.automatalib.symbol.time.InputSymbol; import net.automatalib.ts.modal.impl.CompactMTS; import net.automatalib.word.Word; import org.testng.Assert; @@ -56,8 +60,7 @@ public void testRegularDFADeserialization() throws IOException, FormatException final CompactDFA dfa = DOTSerializationUtil.DFA; final DFA parsed = - DOTParsers.dfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.DFA_RESOURCE)). - model; + DOTParsers.dfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.DFA_RESOURCE)).model; checkIsomorphism(dfa, parsed, dfa.getInputAlphabet()); } @@ -68,8 +71,7 @@ public void testRegularNFADeserialization() throws IOException, FormatException final CompactNFA nfa = DOTSerializationUtil.NFA; final CompactNFA parsed = - DOTParsers.nfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA_RESOURCE)). - model; + DOTParsers.nfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA_RESOURCE)).model; checkIsomorphism(nfa, parsed, nfa.getInputAlphabet()); } @@ -82,8 +84,7 @@ public void testRegularNFA2Deserialization() throws IOException, FormatException DOTParsers.DEFAULT_EDGE_PARSER, Arrays.asList("s0", "s1", "s2"), false) - .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA2_RESOURCE)). - model; + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA2_RESOURCE)).model; Assert.assertEquals(parsed.size(), 3); Assert.assertEquals(parsed.getInitialStates().size(), 3); @@ -103,9 +104,9 @@ public void testRegularMealyDeserialization() throws IOException, FormatExceptio final CompactMealy mealy = DOTSerializationUtil.MEALY; - final MealyMachine parsed = - DOTParsers.mealy().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.MEALY_RESOURCE)). - model; + final MealyMachine parsed = DOTParsers.mealy() + .readModel(DOTSerializationUtil.getResource( + DOTSerializationUtil.MEALY_RESOURCE)).model; checkIsomorphism(mealy, parsed, mealy.getInputAlphabet()); } @@ -114,9 +115,9 @@ public void testRegularMealyDeserialization() throws IOException, FormatExceptio public void testRegularMooreDeserialization() throws IOException, FormatException { final CompactMoore moore = DOTSerializationUtil.MOORE; - final MooreMachine parsed = - DOTParsers.moore().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.MOORE_RESOURCE)). - model; + final MooreMachine parsed = DOTParsers.moore() + .readModel(DOTSerializationUtil.getResource( + DOTSerializationUtil.MOORE_RESOURCE)).model; checkIsomorphism(moore, parsed, moore.getInputAlphabet()); } @@ -144,6 +145,84 @@ public void testRegularMTSDeserialization() throws IOException, FormatException checkIsomorphism(mts, parsed, alphabet); } + @Test + public void testRegularMMLTDeserialization() throws IOException, FormatException { + final CompactMMLT mts = DOTSerializationUtil.MMLT; + + var model = DOTParsers.mmlt("void", StringSymbolCombiner.getInstance()) + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.MMLT_RESOURCE)); + + var alphabet = model.alphabet; + var parsed = model.model; + + checkIsomorphism(mts, parsed, alphabet); + } + + @Test + public void testMMLTSensorModel() throws IOException, FormatException { + var mmlt = DOTParsers.mmlt("void", StringSymbolCombiner.getInstance()) + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.MMLT_SENSOR)).model; + + // Compare to reference: + var p1 = new InputSymbol<>("p1"); + var p2 = new InputSymbol<>("p2"); + var abort = new InputSymbol<>("abort"); + var collect = new InputSymbol<>("collect"); + + int s0 = 0; + int s1 = 1; + int s2 = 2; + int s3 = 3; + + // Check non-delaying transitions: + assertSilentLoop(mmlt, s0, abort); + assertSilentLoop(mmlt, s0, collect); + assertTransition(mmlt, s0, s1, p1, "go"); + assertTransition(mmlt, s0, s2, p2, "go"); + + assertTransition(mmlt, s1, s1, abort, "ok"); + Assert.assertTrue(mmlt.isLocalReset(s1, abort)); + assertSilentLoop(mmlt, s1, p1); + assertSilentLoop(mmlt, s1, p2); + assertSilentLoop(mmlt, s1, collect); + + assertTransition(mmlt, s2, s3, abort, "void"); + assertSilentLoop(mmlt, s2, p1); + assertSilentLoop(mmlt, s2, p2); + assertSilentLoop(mmlt, s2, collect); + + assertSilentLoop(mmlt, s3, abort); + assertSilentLoop(mmlt, s3, p1); + assertSilentLoop(mmlt, s3, p2); + assertTransition(mmlt, s3, s0, collect, "void"); + + // Check timers: + Assert.assertTrue(mmlt.getSortedTimers(s0).isEmpty()); + Assert.assertTrue(mmlt.getSortedTimers(s3).isEmpty()); + Assert.assertEquals(mmlt.getSortedTimers(s1).size(), 3); + Assert.assertEquals(mmlt.getSortedTimers(s2).size(), 1); + + var firstTimerS1 = mmlt.getSortedTimers(s1).get(0); + Assert.assertEquals(firstTimerS1.initial(), 3); + Assert.assertEquals(firstTimerS1.output(), "part"); + Assert.assertTrue(firstTimerS1.periodic()); + + var secondTimerS1 = mmlt.getSortedTimers(s1).get(1); + Assert.assertEquals(secondTimerS1.initial(), 6); + Assert.assertEquals(secondTimerS1.output(), "noise"); + Assert.assertTrue(secondTimerS1.periodic()); + + var thirdTimerS1 = mmlt.getSortedTimers(s1).get(2); + Assert.assertEquals(thirdTimerS1.initial(), 40); + Assert.assertEquals(thirdTimerS1.output(), "done"); + Assert.assertFalse(thirdTimerS1.periodic()); + + var firstTimerS2 = mmlt.getSortedTimers(s2).get(0); + Assert.assertEquals(firstTimerS2.initial(), 4); + Assert.assertEquals(firstTimerS2.output(), "done"); + Assert.assertFalse(firstTimerS2.periodic()); + } + @Test(expectedExceptions = FormatException.class) public void testFaultyAutomatonDeserialization() throws IOException, FormatException { DOTParsers.dfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.FAULTY_AUTOMATON_RESOURCE)); @@ -294,4 +373,26 @@ private static , EP extends Comparable, N2 Assert.assertEquals(sourceQueue.isEmpty(), targetQueue.isEmpty()); } + + private void assertTransition(MMLT model, + int state, + int target, + InputSymbol input, + String output) { + var trans = model.getTransition(state, input); + Assert.assertNotNull(trans); + + if ((model.getSuccessor(trans) != target) || !model.getTransitionProperty(trans).equals(output)) { + throw new AssertionError(); + } + } + + private void assertSilentLoop(MMLT model, int state, InputSymbol input) { + var trans = model.getTransition(state, input); + if (trans != null && + (model.getSuccessor(trans) != state || !"void".equals(model.getTransitionProperty(trans)))) { + throw new AssertionError(); + } + Assert.assertFalse(model.isLocalReset(state, input)); + } } diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java index 25be3db7b..e5ed4f2c6 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java @@ -30,6 +30,7 @@ import net.automatalib.automaton.fsa.impl.CompactNFA; import net.automatalib.automaton.graph.TransitionEdge; import net.automatalib.automaton.impl.CompactTransition; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; @@ -37,6 +38,7 @@ import net.automatalib.automaton.transducer.impl.CompactMealy; import net.automatalib.automaton.transducer.impl.CompactMoore; import net.automatalib.automaton.transducer.impl.CompactSST; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; import net.automatalib.common.util.IOUtil; import net.automatalib.common.util.io.UnclosableOutputStream; import net.automatalib.graph.Graph; @@ -181,6 +183,18 @@ public void testSPMMExport() throws IOException { checkDOTOutput(writer, DOTSerializationUtil.SPMM_RESOURCE); } + @Test + public void testMMLTExport() throws IOException { + + final CompactMMLT mmlt = DOTSerializationUtil.MMLT; + + ThrowingWriter writer = w -> GraphDOT.write(mmlt, mmlt.getInputAlphabet(), w); + checkDOTOutput(writer, DOTSerializationUtil.MMLT_RESOURCE); + + ThrowingWriter writer2 = w -> GraphDOT.write(mmlt, mmlt.getInputAlphabet(), w, new MMLTVisualizationHelper<>(mmlt, true, true)); + checkDOTOutput(writer2, DOTSerializationUtil.MMLT_WITH_RESETS_RESOURCE); + } + @Test public void testVisualizationHelper() throws IOException { diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java index ba25b8d71..a7a54dc68 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java @@ -26,11 +26,14 @@ import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.DefaultProceduralInputAlphabet; +import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.fsa.impl.CompactDFA; import net.automatalib.automaton.fsa.impl.CompactNFA; import net.automatalib.automaton.fsa.impl.FastDFA; import net.automatalib.automaton.fsa.impl.FastDFAState; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import net.automatalib.automaton.procedural.SBA; import net.automatalib.automaton.procedural.SPA; import net.automatalib.automaton.procedural.SPMM; @@ -47,6 +50,7 @@ import net.automatalib.graph.impl.CompactPMPGEdge; import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.graph.impl.DefaultCFMPS; +import net.automatalib.symbol.time.InputSymbol; import net.automatalib.ts.modal.impl.CompactMTS; import net.automatalib.ts.modal.transition.ModalEdgeProperty.ModalType; import net.automatalib.ts.modal.transition.MutableProceduralModalEdgeProperty; @@ -74,6 +78,9 @@ final class DOTSerializationUtil { static final String SPA_RESOURCE = "/spa.dot"; static final String SBA_RESOURCE = "/sba.dot"; static final String SPMM_RESOURCE = "/spmm.dot"; + static final String MMLT_RESOURCE = "/mmlt.dot"; + static final String MMLT_WITH_RESETS_RESOURCE = "/mmlt_with_resets.dot"; + static final String MMLT_SENSOR = "/mmlt_sensor.dot"; static final String FAULTY_AUTOMATON_RESOURCE = "/faulty_automaton.dot"; static final String FAULTY_GRAPH_RESOURCE = "/faulty_graph.dot"; @@ -94,6 +101,7 @@ final class DOTSerializationUtil { static final SPA SPA; static final SBA SBA; static final SPMM SPMM; + static final CompactMMLT MMLT; static { STRING_ALPHABET = Alphabets.closedCharStringRange('a', 'c'); @@ -110,6 +118,7 @@ final class DOTSerializationUtil { SPA = buildSPA(); SBA = buildSBA(); SPMM = buildSPMM(); + MMLT = buildMMLT(); } private DOTSerializationUtil() {} @@ -434,4 +443,31 @@ private static StackSBA buildSBA() { procedures.put('G', pG); return new StackSPMM<>(alphabet, 'F', '+', '-', procedures); } + + private static CompactMMLT buildMMLT() { + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + Alphabets.closedCharStringRange('x', 'y').forEach(s -> alphabet.add(new InputSymbol<>(s))); + + final CompactMMLT mmlt = new CompactMMLT<>(alphabet, + "void", + StringSymbolCombiner.getInstance()); + var s0 = mmlt.addInitialState(); + var s1 = mmlt.addState(); + var s2 = mmlt.addState(); + + mmlt.addTransition(s1, new InputSymbol<>("x"), s2, "void"); + mmlt.addTransition(s1, new InputSymbol<>("y"), s1, "Y"); + mmlt.addTransition(s2, new InputSymbol<>("y"), s2, "D"); + + mmlt.addLocalReset(s1, new InputSymbol<>("y")); + + mmlt.addOneShotTimer(s0, "a", 2, "A", s1); + mmlt.addPeriodicTimer(s1, "b", 4, "B"); + mmlt.addOneShotTimer(s1, "c", 6, "C", s1); + + mmlt.addPeriodicTimer(s2, "d", 2, "D"); + mmlt.addPeriodicTimer(s2, "e", 3, "E"); + + return mmlt; + } } diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java deleted file mode 100644 index 0a443814b..000000000 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/LocalTimerMealyTests.java +++ /dev/null @@ -1,128 +0,0 @@ -package net.automatalib.serialization.dot; - -import net.automatalib.alphabet.time.mmlt.NonDelayingInput; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.File; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.Arrays; -import java.util.List; - -public class LocalTimerMealyTests { - - @Test - public void parseAndWriteDemoModel() { - var demoResource = LocalTimerMealyTests.class.getResource("/demo_mmlt.dot"); - var expResultPath = Paths.get(LocalTimerMealyTests.class.getResource("/demo_expected_serialized.dot").getPath()); - var demoModel = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(demoResource.getFile()), "void", StringSymbolCombiner.getInstance()); - - - StringBuilder sbOutput = new StringBuilder(); - try { - List expected = Files.readAllLines(expResultPath).stream() - .filter(l -> !l.isBlank() && !l.startsWith("//")) - .map(String::trim) - .toList(); - - GraphDOT.write(demoModel.transitionGraphView(true, true), sbOutput); - var actualData = sbOutput.toString(); - System.out.println(actualData); - List actual = Arrays.stream(actualData.split("\\n")) - .filter(l -> !l.isBlank()) - .map(String::trim) - .toList(); - - Assert.assertEquals(expected, actual); - } catch (Exception ex) { - throw new AssertionError(); - } - } - - @Test - public void parseSensorModel() { - // Load a model from file: - var resource = LocalTimerMealyTests.class.getResource("/sensor_mmlt.dot"); - - var dotAutomaton = LocalTimerMealyGraphvizParser.parseLocalTimerMealy(new File(resource.getFile()), "void", StringSymbolCombiner.getInstance()); - - // Compare to reference: - var ndiP1 = new NonDelayingInput<>("p1"); - var ndiP2 = new NonDelayingInput<>("p2"); - var ndiAbort = new NonDelayingInput<>("abort"); - var ndiCollect = new NonDelayingInput<>("collect"); - - int s0 = 0; - int s1 = 1; - int s2 = 2; - int s3 = 3; - - // Check non-delaying transitions: - assertSilentLoop(dotAutomaton, s0, ndiAbort); - assertSilentLoop(dotAutomaton, s0, ndiCollect); - assertTransition(dotAutomaton, s0, s1, ndiP1, "go"); - assertTransition(dotAutomaton, s0, s2, ndiP2, "go"); - - assertTransition(dotAutomaton, s1, s1, ndiAbort, "ok"); - Assert.assertTrue(dotAutomaton.isLocalReset(s1, ndiAbort)); - assertSilentLoop(dotAutomaton, s1, ndiP1); - assertSilentLoop(dotAutomaton, s1, ndiP2); - assertSilentLoop(dotAutomaton, s1, ndiCollect); - - assertTransition(dotAutomaton, s2, s3, ndiAbort, "void"); - assertSilentLoop(dotAutomaton, s2, ndiP1); - assertSilentLoop(dotAutomaton, s2, ndiP2); - assertSilentLoop(dotAutomaton, s2, ndiCollect); - - assertSilentLoop(dotAutomaton, s3, ndiAbort); - assertSilentLoop(dotAutomaton, s3, ndiP1); - assertSilentLoop(dotAutomaton, s3, ndiP2); - assertTransition(dotAutomaton, s3, s0, ndiCollect, "void"); - - // Check timers: - Assert.assertTrue(dotAutomaton.getSortedTimers(s0).isEmpty()); - Assert.assertTrue(dotAutomaton.getSortedTimers(s3).isEmpty()); - Assert.assertEquals(dotAutomaton.getSortedTimers(s1).size(), 3); - Assert.assertEquals(dotAutomaton.getSortedTimers(s2).size(), 1); - - var firstTimerS1 = dotAutomaton.getSortedTimers(s1).get(0); - Assert.assertEquals(firstTimerS1.initial(), 3); - Assert.assertEquals(firstTimerS1.output(), "part"); - Assert.assertTrue(firstTimerS1.periodic()); - - var secondTimerS1 = dotAutomaton.getSortedTimers(s1).get(1); - Assert.assertEquals(secondTimerS1.initial(), 6); - Assert.assertEquals(secondTimerS1.output(), "noise"); - Assert.assertTrue(secondTimerS1.periodic()); - - var thirdTimerS1 = dotAutomaton.getSortedTimers(s1).get(2); - Assert.assertEquals(thirdTimerS1.initial(), 40); - Assert.assertEquals(thirdTimerS1.output(), "done"); - Assert.assertFalse(thirdTimerS1.periodic()); - - var firstTimerS2 = dotAutomaton.getSortedTimers(s2).get(0); - Assert.assertEquals(firstTimerS2.initial(), 4); - Assert.assertEquals(firstTimerS2.output(), "done"); - Assert.assertFalse(firstTimerS2.periodic()); - } - - private void assertTransition(LocalTimerMealy model, int state, int target, NonDelayingInput input, String output) { - var trans = model.getTransition(state, input); - if (trans == null || (trans.successor() != target) || !trans.output().equals(output)) { - throw new AssertionError(); - } - } - - private void assertSilentLoop(LocalTimerMealy model, int state, NonDelayingInput input) { - var trans = model.getTransition(state, input); - if (trans != null && (trans.successor() != state || !trans.output().equals("void"))) { - throw new AssertionError(); - } - Assert.assertFalse(model.isLocalReset(state, input)); - } - - -} diff --git a/serialization/dot/src/test/resources/demo_mmlt.dot b/serialization/dot/src/test/resources/demo_mmlt.dot deleted file mode 100644 index d657df7bf..000000000 --- a/serialization/dot/src/test/resources/demo_mmlt.dot +++ /dev/null @@ -1,20 +0,0 @@ -// This file demonstrates several MMLT features -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/serialization/dot/src/test/resources/mmlt.dot b/serialization/dot/src/test/resources/mmlt.dot new file mode 100644 index 000000000..6aa7a14a5 --- /dev/null +++ b/serialization/dot/src/test/resources/mmlt.dot @@ -0,0 +1,18 @@ +digraph g { + + s0 [timers="a=2" shape="circle" label="0"]; + s1 [timers="b=4,c=6" shape="circle" label="1"]; + s2 [timers="d=2,e=3" shape="circle" label="2"]; + s0 -> s1 [label="to[a] / A"]; + s1 -> s2 [label="x / void"]; + s1 -> s1 [resets="b,c" label="y / Y"]; + s1 -> s1 [label="to[b] / B"]; + s1 -> s1 [resets="b,c" label="to[c] / C"]; + s2 -> s2 [label="y / D"]; + s2 -> s2 [label="to[d] / D"]; + s2 -> s2 [label="to[e] / E"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; + +} diff --git a/serialization/dot/src/test/resources/sensor_mmlt.dot b/serialization/dot/src/test/resources/mmlt_sensor.dot similarity index 100% rename from serialization/dot/src/test/resources/sensor_mmlt.dot rename to serialization/dot/src/test/resources/mmlt_sensor.dot diff --git a/serialization/dot/src/test/resources/demo_expected_serialized.dot b/serialization/dot/src/test/resources/mmlt_with_resets.dot similarity index 66% rename from serialization/dot/src/test/resources/demo_expected_serialized.dot rename to serialization/dot/src/test/resources/mmlt_with_resets.dot index b7cbf2684..6bfbfdf72 100644 --- a/serialization/dot/src/test/resources/demo_expected_serialized.dot +++ b/serialization/dot/src/test/resources/mmlt_with_resets.dot @@ -1,20 +1,18 @@ -// Resulting model that is expected when deserializing demo_mmlt.gv first, and then -// serializing the result with edge coloring and reset information enabled. digraph g { s0 [timers="a=2" shape="circle" label="0"]; s1 [timers="b=4,c=6" shape="circle" label="1"]; s2 [timers="d=2,e=3" shape="circle" label="2"]; s0 -> s1 [color="chartreuse3" fontcolor="chartreuse3" label="to[a] / A {b↦4,c↦6}"]; - s1 -> s1 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[b] / B {b↦4}"]; - s1 -> s1 [color="chartreuse3" fontcolor="chartreuse3" label="to[c] / C {b↦4,c↦6}"]; s1 -> s2 [label="x / void {}"]; - s1 -> s1 [color="orange" fontcolor="orange" label="y / Y {b↦4,c↦6}"]; + s1 -> s1 [color="orange" resets="b,c" fontcolor="orange" label="y / Y {b↦4,c↦6}"]; + s1 -> s1 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[b] / B {b↦4}"]; + s1 -> s1 [color="chartreuse3" resets="b,c" fontcolor="chartreuse3" label="to[c] / C {b↦4,c↦6}"]; + s2 -> s2 [label="y / D {}"]; s2 -> s2 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[d] / D {d↦2}"]; s2 -> s2 [color="cornflowerblue" fontcolor="cornflowerblue" label="to[e] / E {e↦3}"]; - s2 -> s2 [label="y / D {}"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s0; -} \ No newline at end of file +} diff --git a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java b/util/src/main/java/net/automatalib/util/automaton/cover/MMLTCover.java similarity index 53% rename from util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java rename to util/src/main/java/net/automatalib/util/automaton/cover/MMLTCover.java index b0e6deacd..e1d973025 100644 --- a/util/src/main/java/net/automatalib/util/automaton/cover/LocalTimerMealyCover.java +++ b/util/src/main/java/net/automatalib/util/automaton/cover/MMLTCover.java @@ -1,17 +1,17 @@ package net.automatalib.util.automaton.cover; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.alphabet.time.mmlt.TimeStepSequence; -import net.automatalib.alphabet.time.mmlt.TimeStepSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.semantics.LocalTimerMealyConfiguration; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.TimeStepSequence; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.State; +import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.word.Word; import java.util.*; -public class LocalTimerMealyCover { +public class MMLTCover { - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs) { + public static Map>> getLocalTimerMealyLocationCover(MMLT automaton, Collection> inputs) { return getLocalTimerMealyLocationCover(automaton, inputs, true, true); } @@ -34,13 +34,17 @@ public static Map>> getL * @param allowIncomplete If set, no error is thrown if some locations are unreachable. * @return Location cover in format location -> prefix. */ - public static Map>> getLocalTimerMealyLocationCover(LocalTimerMealy automaton, Collection> inputs, boolean ignoreTimeStep, boolean allowIncomplete) { - Map>> locPrefixes = new HashMap<>(); - Map, Word>> cfgPrefixes = new HashMap<>(); - List> queue = new ArrayList<>(); + public static Map>> getLocalTimerMealyLocationCover(MMLT automaton, Collection> inputs, boolean ignoreTimeStep, boolean allowIncomplete) { + return getLocalTimerMealyLocationCover(automaton, automaton.getSemantics(), inputs, ignoreTimeStep, allowIncomplete); + } + + public static Map>> getLocalTimerMealyLocationCover(MMLT automaton, MMLTSemantics semantics, Collection> inputs, boolean ignoreTimeStep, boolean allowIncomplete) { + Map>> locPrefixes = new HashMap<>(); + Map, Word>> cfgPrefixes = new HashMap<>(); + List> queue = new ArrayList<>(); - queue.add(automaton.getSemantics().getInitialConfiguration()); - cfgPrefixes.put(automaton.getSemantics().getInitialConfiguration(), Word.epsilon()); + queue.add(semantics.getInitialState()); + cfgPrefixes.put(semantics.getInitialState(), Word.epsilon()); locPrefixes.put(automaton.getInitialState(), Word.epsilon()); while (!queue.isEmpty()) { @@ -50,17 +54,18 @@ public static Map>> getL continue; } - var trans = automaton.getSemantics().getTransition(current, symbol); - if (trans.target().equals(current)) { + var trans = semantics.getTransition(current, symbol); + var succ = semantics.getSuccessor(trans); + if (succ.equals(current)) { continue; // self-loop } - if (!cfgPrefixes.containsKey(trans.target())) { + if (!cfgPrefixes.containsKey(succ)) { var newPrefix = cfgPrefixes.get(current).append(symbol); - cfgPrefixes.put(trans.target(), newPrefix); - queue.add(trans.target()); + cfgPrefixes.put(succ, newPrefix); + queue.add(succ); - if (trans.target().isEntryConfig()) { - locPrefixes.put(trans.target().getLocation(), newPrefix); + if (succ.isEntryConfig()) { + locPrefixes.put(succ.getLocation(), newPrefix); } } } diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java similarity index 80% rename from util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java rename to util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java index 1679ae4b1..9f3e3ae62 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java @@ -1,9 +1,9 @@ package net.automatalib.util.automaton.mmlt; -import net.automatalib.alphabet.time.mmlt.LocalTimerMealySemanticInputSymbol; -import net.automatalib.automaton.time.mmlt.LocalTimerMealy; -import net.automatalib.automaton.time.mmlt.MealyTimerInfo; -import net.automatalib.automaton.time.impl.mmlt.ReducedLocalTimerMealySemantics; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.automaton.mmlt.MMLT; +import net.automatalib.automaton.mmlt.MealyTimerInfo; +import net.automatalib.automaton.mmlt.impl.ReducedMMLTSemantics; import net.automatalib.util.automaton.Automata; import net.automatalib.word.Word; import org.checkerframework.checker.nullness.qual.Nullable; @@ -17,12 +17,12 @@ /** * Provides various functions that are related MMLTs. */ -public class LocalTimerMealyUtil { +public class MMLTUtil { - public static @Nullable Word> findSeparatingWord(LocalTimerMealy modelA, LocalTimerMealy modelB, - Collection> inputs) { - var expandedA = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelA); - var expandedB = ReducedLocalTimerMealySemantics.forLocalTimerMealy(modelB); + public static @Nullable Word> findSeparatingWord(MMLT modelA, MMLT modelB, + Collection> inputs) { + var expandedA = ReducedMMLTSemantics.forLocalTimerMealy(modelA); + var expandedB = ReducedMMLTSemantics.forLocalTimerMealy(modelB); var separatingWord = Automata.findSeparatingWord(expandedA, expandedB, inputs); @@ -45,12 +45,12 @@ public class LocalTimerMealyUtil { * * @param automaton Considered automaton * @param location Considered location - * @param Location type + * @param Location type * @param Input type * @param Output type * @return Maximum configuration time. Long.MAX_VALUE, if exceeding integer maximum. */ - public static long getConfigurationCount(LocalTimerMealy automaton, L location) { + public static long getConfigurationCount(MMLT automaton, S location) { Collection> timers = automaton.getSortedTimers(location); if (timers.isEmpty()) { return 1; @@ -92,7 +92,7 @@ public static long getConfigurationCount(LocalTimerMealy auto * * @return Maximum initial timer value. Zero, if no timers are used. */ - public static long getMaximumInitialTimerValue(LocalTimerMealy automaton) { + public static long getMaximumInitialTimerValue(MMLT automaton) { long maxValue = 0; for (S loc : automaton.getStates()) { var optMaxInitial = automaton.getSortedTimers(loc).stream().mapToLong(MealyTimerInfo::initial).max(); @@ -108,7 +108,7 @@ public static long getMaximumInitialTimerValue(LocalTimerMealy long getMaximumTimeoutDelay(LocalTimerMealy automaton) { + public static long getMaximumTimeoutDelay(MMLT automaton) { long maxValue = 1; for (S loc : automaton.getStates()) { @@ -123,7 +123,7 @@ public static long getMaximumTimeoutDelay(LocalTimerMealy aut } // Get the least common multiple of the initial times: - long lcm = LocalTimerMealyUtil.getConfigurationCount(automaton, loc); + long lcm = MMLTUtil.getConfigurationCount(automaton, loc); // Calculate expiration times of all timers: List timeouts = new ArrayList<>(); diff --git a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java b/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java similarity index 51% rename from util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java rename to util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java index 51cca14ee..24059fc33 100644 --- a/util/src/test/java/net/automatalib/util/automaton/cover/LocalTimerMealyCoverTest.java +++ b/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java @@ -1,23 +1,23 @@ package net.automatalib.util.automaton.cover; 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.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; import org.testng.Assert; import org.testng.annotations.Test; import java.util.List; @Test -public class LocalTimerMealyCoverTest { - private CompactLocalTimerMealy buildBaseModel() { +public class MMLTCoverTest { + 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(); @@ -26,19 +26,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; } @@ -46,7 +46,7 @@ private CompactLocalTimerMealy buildBaseModel() { @Test public void computeFullCover() { var automaton = buildBaseModel(); - var cover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, automaton.getSemantics().getInputAlphabet()); + var cover = MMLTCover.getLocalTimerMealyLocationCover(automaton, automaton.getSemantics().getInputAlphabet()); // Verify that all states are covered: Assert.assertEquals(cover.size(), automaton.size()); @@ -56,7 +56,7 @@ public void computeFullCover() { // Verify that the assigned prefixes are correct: for (var entry : cover.entrySet()) { - var targetConfig = automaton.getSemantics().traceInputs(entry.getValue()); + var targetConfig = automaton.getSemantics().getState(entry.getValue()); Assert.assertTrue(targetConfig.isEntryConfig()); Assert.assertEquals(targetConfig.getLocation(), entry.getKey()); } @@ -68,14 +68,14 @@ public void computeIncompleteCover() { // Create alphabet where state 3 is unreachable: var symbols = List.of("p1", "p2", "collect"); - GrowingMapAlphabet> partialAlphabet = new GrowingMapAlphabet<>(); - symbols.forEach(s -> partialAlphabet.add(new NonDelayingInput<>(s))); + GrowingMapAlphabet> partialAlphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> partialAlphabet.add(new InputSymbol<>(s))); // Test if detecting incomplete cover: - Assert.assertThrows(AssertionError.class, () -> LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet, true, false)); + Assert.assertThrows(AssertionError.class, () -> MMLTCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet, true, false)); // Verify that state 3 is not covered: - var cover = LocalTimerMealyCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet); + var cover = MMLTCover.getLocalTimerMealyLocationCover(automaton, partialAlphabet); Assert.assertTrue(cover.size() < automaton.size()); for (var state : automaton.getStates()) { if (state != 3) { diff --git a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java deleted file mode 100644 index 15c1ddc67..000000000 --- a/util/src/test/java/net/automatalib/util/automaton/mmlt/LocalTimerMealyTests.java +++ /dev/null @@ -1,169 +0,0 @@ -package net.automatalib.util.automaton.mmlt; - -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.TimeoutSymbol; -import net.automatalib.automaton.time.impl.mmlt.CompactLocalTimerMealy; -import net.automatalib.automaton.time.impl.mmlt.StringSymbolCombiner; -import org.testng.Assert; -import org.testng.annotations.Test; - - -import java.util.ArrayList; -import java.util.List; - -public class LocalTimerMealyTests { - - public 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; - } - - @Test - public void testTimerAndResetRemovals() { - var model = buildBaseModel(); - - int s1 = 1; - int s3 = 3; - - // Remove and add some timers: - model.addPeriodicTimer(s1, "e", 12, "test"); - model.removeTimer(s1, "b"); - model.removeTimer(s1, "a"); - model.addPeriodicTimer(s1, "a", 3, "part"); - model.addPeriodicTimer(s1, "b", 6, "noise"); - model.removeTimer(s1, "c"); - model.addOneShotTimer(s1, "c", 40, "done", s3); - model.removeTimer(s1, "e"); - - model.removeLocalReset(s1, new NonDelayingInput<>("abort")); - model.addLocalReset(s1, new NonDelayingInput<>("abort")); - - // Still needs to be equivalent to original: - var originalModel = buildBaseModel(); - Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(model, originalModel, originalModel.getSemantics().getInputAlphabet())); - } - - @Test - public void testSeparatedByResetsSimple() { - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addSymbol(new NonDelayingInput<>("x")); - - // Same model, but with reset in A and no reset in B: - var modelA = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); - var s0 = modelA.addState(); - modelA.setInitialState(s0); - modelA.addPeriodicTimer(s0, "a", 3, "test"); - modelA.addTransition(s0, new NonDelayingInput<>("x"), "ok", s0); - modelA.addLocalReset(s0, new NonDelayingInput<>("x")); - - var modelB = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); - var s0B = modelB.addState(); - modelB.setInitialState(s0B); - modelB.addPeriodicTimer(s0B, "a", 3, "test"); - modelB.addTransition(s0B, new NonDelayingInput<>("x"), "ok", s0B); - - Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); - - // If we remove the timestep, should not find a counterexample: - List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); - reducedInputs.add(new TimeoutSymbol<>()); - Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, reducedInputs)); - } - - @Test - public void testSeparatedByResetsComplex() { - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addSymbol(new NonDelayingInput<>("x")); - - // Same model, but with reset in A and no reset in B: - var modelA = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); - var s0 = modelA.addState(); - var s1 = modelA.addState(); - modelA.setInitialState(s0); - modelA.addPeriodicTimer(s0, "a", 3, "test"); - modelA.addOneShotTimer(s0, "b", 5, "test2", s1); - - var modelB = new CompactLocalTimerMealy<>(alphabet, "void", StringSymbolCombiner.getInstance()); - var s0B = modelB.addState(); - var s1B = modelB.addState(); - var s2B = modelB.addState(); - modelB.setInitialState(s0B); - modelB.addOneShotTimer(s0B, "a", 3, "test", s1B); - modelB.addOneShotTimer(s1B, "b", 2, "test2", s2B); - modelB.addTransition(s1B, new NonDelayingInput<>("x"), "void", s1B); - modelB.addLocalReset(s1B, new NonDelayingInput<>("x")); - - Assert.assertNotNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); - - // If we remove the timestep, should not find a counterexample: - List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); - reducedInputs.add(new TimeoutSymbol<>()); - Assert.assertNull(LocalTimerMealyUtil.findSeparatingWord(modelA, modelB, reducedInputs)); - } - - - @Test - public void testInvalidTimerChecks() { - var automaton = buildBaseModel(); - - int s1 = 1; - - // Duplicate timer name: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "a", 3, "test") - ); - - // Timer with silent output: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 3, "void") - ); - - // Timer never expires: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 41, "test") - ); - - // One-shot timer that times out at same time as periodic: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) - ); - - // Periodic timer that times out at same time as one-shot: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addPeriodicTimer(s1, "e", 20, "test") - ); - - // Duplicate one-shot timer: - Assert.assertThrows(IllegalArgumentException.class, - () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3) - ); - } -} diff --git a/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java new file mode 100644 index 000000000..e34b9ee50 --- /dev/null +++ b/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java @@ -0,0 +1,161 @@ +package net.automatalib.util.automaton.mmlt; + +import net.automatalib.alphabet.impl.GrowingMapAlphabet; +import net.automatalib.symbol.time.TimedInput; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.TimeoutSymbol; +import net.automatalib.automaton.mmlt.impl.CompactMMLT; +import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import org.testng.Assert; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; + +public class MMLTUtilTest { + + public CompactMMLT buildBaseModel() { + var symbols = List.of("p1", "p2", "abort", "collect"); + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + symbols.forEach(s -> alphabet.add(new InputSymbol<>(s))); + + var model = new CompactMMLT<>(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 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 InputSymbol<>("p2"), s2, "go"); + model.addTransition(s2, new InputSymbol<>("abort"), s3, "void"); + model.addOneShotTimer(s2, "d", 4, "done", s3); + + model.addTransition(s3, new InputSymbol<>("collect"), s0, "void"); + + return model; + } + + @Test + public void testTimerAndResetRemovals() { + var model = buildBaseModel(); + + int s1 = 1; + int s3 = 3; + + // Remove and add some timers: + model.addPeriodicTimer(s1, "e", 12, "test"); + model.removeTimer(s1, "b"); + model.removeTimer(s1, "a"); + model.addPeriodicTimer(s1, "a", 3, "part"); + model.addPeriodicTimer(s1, "b", 6, "noise"); + model.removeTimer(s1, "c"); + model.addOneShotTimer(s1, "c", 40, "done", s3); + model.removeTimer(s1, "e"); + + model.removeLocalReset(s1, new InputSymbol<>("abort")); + model.addLocalReset(s1, new InputSymbol<>("abort")); + + // Still needs to be equivalent to original: + var originalModel = buildBaseModel(); + Assert.assertNull(MMLTUtil.findSeparatingWord(model, + originalModel, + originalModel.getSemantics().getInputAlphabet())); + } + + @Test + public void testSeparatedByResetsSimple() { + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addSymbol(new InputSymbol<>("x")); + + // Same model, but with reset in A and no reset in B: + var modelA = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0 = modelA.addState(); + modelA.setInitialState(s0); + modelA.addPeriodicTimer(s0, "a", 3, "test"); + modelA.addTransition(s0, new InputSymbol<>("x"), s0, "ok"); + modelA.addLocalReset(s0, new InputSymbol<>("x")); + + var modelB = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0B = modelB.addState(); + modelB.setInitialState(s0B); + modelB.addPeriodicTimer(s0B, "a", 3, "test"); + modelB.addTransition(s0B, new InputSymbol<>("x"), s0B, "ok"); + + Assert.assertNotNull(MMLTUtil.findSeparatingWord(modelA, + modelB, + modelA.getSemantics().getInputAlphabet())); + + // If we remove the timestep, should not find a counterexample: + List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); + reducedInputs.add(new TimeoutSymbol<>()); + Assert.assertNull(MMLTUtil.findSeparatingWord(modelA, modelB, reducedInputs)); + } + + @Test + public void testSeparatedByResetsComplex() { + GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); + alphabet.addSymbol(new InputSymbol<>("x")); + + // Same model, but with reset in A and no reset in B: + var modelA = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0 = modelA.addState(); + var s1 = modelA.addState(); + modelA.setInitialState(s0); + modelA.addPeriodicTimer(s0, "a", 3, "test"); + modelA.addOneShotTimer(s0, "b", 5, "test2", s1); + + var modelB = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); + var s0B = modelB.addState(); + var s1B = modelB.addState(); + var s2B = modelB.addState(); + modelB.setInitialState(s0B); + modelB.addOneShotTimer(s0B, "a", 3, "test", s1B); + modelB.addOneShotTimer(s1B, "b", 2, "test2", s2B); + modelB.addTransition(s1B, new InputSymbol<>("x"), s1B, "void"); + modelB.addLocalReset(s1B, new InputSymbol<>("x")); + + Assert.assertNotNull(MMLTUtil.findSeparatingWord(modelA, + modelB, + modelA.getSemantics().getInputAlphabet())); + + // If we remove the timestep, should not find a counterexample: + List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); + reducedInputs.add(new TimeoutSymbol<>()); + Assert.assertNull(MMLTUtil.findSeparatingWord(modelA, modelB, reducedInputs)); + } + + @Test + public void testInvalidTimerChecks() { + var automaton = buildBaseModel(); + + int s1 = 1; + + // Duplicate timer name: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "a", 3, "test")); + + // Timer with silent output: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 3, "void")); + + // Timer never expires: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 41, "test")); + + // One-shot timer that times out at same time as periodic: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3)); + + // Periodic timer that times out at same time as one-shot: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addPeriodicTimer(s1, "e", 20, "test")); + + // Duplicate one-shot timer: + Assert.assertThrows(IllegalArgumentException.class, () -> automaton.addOneShotTimer(s1, "e", 12, "test", 3)); + } +} From 9cd034256ad3d0ba0cf947f0fbc6447557660fd8 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 19:19:42 +0100 Subject: [PATCH 14/18] use InputSymbols directly --- .../net/automatalib/automaton/mmlt/MMLT.java | 35 ++--- .../automaton/mmlt/MMLTGraphView.java | 61 ++++++++ .../automaton/mmlt/MealyTimerInfo.java | 20 ++- .../automaton/mmlt/MutableMMLT.java | 5 +- .../net/automatalib/automaton/mmlt/State.java | 10 +- .../automaton/mmlt/TimeoutPair.java | 2 +- .../MMLTVisualizationHelper.java | 31 ++-- .../automaton/mmlt/impl/CompactMMLT.java | 140 ++++-------------- .../mmlt/impl/CompactMMLTSemantics.java | 5 +- .../serialization/dot/DOTMMLTParser.java | 16 +- .../serialization/dot/DOTParsers.java | 17 +-- .../dot/DOTSerializationTest.java | 4 +- .../util/automaton/mmlt/MMLTUtil.java | 2 +- 13 files changed, 165 insertions(+), 183 deletions(-) create mode 100644 api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java index 369e07aaf..14a241ec8 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java @@ -1,20 +1,16 @@ package net.automatalib.automaton.mmlt; -import java.util.Collection; import java.util.List; import net.automatalib.alphabet.Alphabet; -import net.automatalib.automaton.visualization.MMLTVisualizationHelper; -import net.automatalib.symbol.time.SymbolicInput; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.UniversalDeterministicAutomaton; import net.automatalib.automaton.concept.InputAlphabetHolder; -import net.automatalib.automaton.graph.TransitionEdge; -import net.automatalib.automaton.graph.TransitionEdge.Property; -import net.automatalib.automaton.graph.UniversalAutomatonGraphView; -import net.automatalib.graph.UniversalGraph; +import net.automatalib.common.util.Triple; +import net.automatalib.graph.Graph; +import net.automatalib.graph.concept.GraphViewable; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; import net.automatalib.symbol.time.TimerTimeoutSymbol; -import net.automatalib.visualization.VisualizationHelper; /** * Base type for a Mealy Machine with Local Timers (MMLT). @@ -43,8 +39,9 @@ * @param * Output symbol type */ -public interface MMLT extends UniversalDeterministicAutomaton, T, Void, O>, - InputAlphabetHolder> { +public interface MMLT extends UniversalDeterministicAutomaton, T, Void, O>, + InputAlphabetHolder>, + GraphViewable { /** * Returns the symbol used for silent outputs. @@ -66,7 +63,7 @@ public interface MMLT extends UniversalDeterministicAutomaton> getInputAlphabet(); + Alphabet> getInputAlphabet(); /** * Retrieves the non-delaying inputs for this automaton. Excludes timer timeout symbols. May be empty. @@ -95,7 +92,7 @@ public interface MMLT extends UniversalDeterministicAutomaton> getSortedTimers(S location); + List> getSortedTimers(S location); /** * Returns the semantics automaton that describes the behavior of this MMLT. @@ -105,15 +102,7 @@ public interface MMLT extends UniversalDeterministicAutomaton getSemantics(); @Override - default UniversalGraph, T>, Void, Property, O>> transitionGraphView( - Collection> inputs) { - return new UniversalAutomatonGraphView<>(this, inputs) { - - @Override - public VisualizationHelper, T>> getVisualizationHelper() { - return new MMLTVisualizationHelper<>(automaton, false, false); - } - }; + default Graph, O, S>> graphView() { + return new MMLTGraphView<>(this); } - } diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java new file mode 100644 index 000000000..1513ea169 --- /dev/null +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java @@ -0,0 +1,61 @@ +package net.automatalib.automaton.mmlt; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.automaton.visualization.MMLTVisualizationHelper; +import net.automatalib.common.util.Triple; +import net.automatalib.graph.Graph; +import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.TimerTimeoutSymbol; +import net.automatalib.visualization.VisualizationHelper; + +public class MMLTGraphView implements Graph, O, S>> { + + private final MMLT mmlt; + + public MMLTGraphView(MMLT mmlt) { + this.mmlt = mmlt; + } + + @Override + public Collection, O, S>> getOutgoingEdges(S node) { + + Alphabet> alphabet = mmlt.getInputAlphabet(); + List> timers = mmlt.getSortedTimers(node); + + List, O, S>> result = new ArrayList<>(alphabet.size() + timers.size()); + + for (InputSymbol i : alphabet) { + var t = mmlt.getTransition(node, i); + if (t != null) { + result.add(Triple.of(i, mmlt.getTransitionProperty(t), mmlt.getSuccessor(t))); + } + } + + for (MealyTimerInfo t : timers) { + result.add(Triple.of(new TimerTimeoutSymbol<>(t.name()), t.output(), t.target())); + + } + + return result; + } + + @Override + public S getTarget(Triple, O, S> edge) { + return edge.getThird(); + } + + @Override + public Collection getNodes() { + return mmlt.getStates(); + } + + @Override + public VisualizationHelper, O, S>> getVisualizationHelper() { + return new MMLTVisualizationHelper<>(mmlt, false, false); + } +} diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java b/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java index ac3458bf5..02edb7951 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MealyTimerInfo.java @@ -7,7 +7,7 @@ * * @param Output symbol type */ -public class MealyTimerInfo { +public class MealyTimerInfo { /** * Name of the timer */ @@ -28,7 +28,10 @@ public class MealyTimerInfo { */ private boolean periodic; - public MealyTimerInfo(String name, long initial, O output, boolean periodic) { + private final S target; + + public MealyTimerInfo(String name, long initial, O output, boolean periodic, S target) { + this.target = target; if (initial <= 0) { throw new IllegalArgumentException("Timer values must be greater than zero."); } @@ -39,8 +42,8 @@ public MealyTimerInfo(String name, long initial, O output, boolean periodic) { this.periodic = periodic; } - public MealyTimerInfo(String name, long initial, O output) { - this(name, initial, output, true); + public MealyTimerInfo(String name, long initial, O output, S target) { + this(name, initial, output, true, target); } public void setOneShot() { @@ -63,6 +66,10 @@ public O output() { return output; } + public S target() { + return target; + } + @Override public boolean equals(Object obj) { if (obj == this) return true; @@ -70,12 +77,13 @@ public boolean equals(Object obj) { var that = (MealyTimerInfo) obj; return Objects.equals(this.name, that.name) && this.initial == that.initial && - Objects.equals(this.output, that.output); + Objects.equals(this.output, that.output) && + Objects.equals(this.target, that.target); } @Override public int hashCode() { - return Objects.hash(name, initial, output); + return Objects.hash(name, initial, output, target); } @Override diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java index 5a8995072..06a3f0cf0 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java @@ -1,10 +1,9 @@ package net.automatalib.automaton.mmlt; -import net.automatalib.symbol.time.SymbolicInput; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.automaton.MutableDeterministic; +import net.automatalib.symbol.time.InputSymbol; -public interface MutableMMLT extends MMLT, MutableDeterministic, T, Void, O> { +public interface MutableMMLT extends MMLT, MutableDeterministic, T, Void, O> { /** * Adds a new periodic timer to the provided location. diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/State.java b/api/src/main/java/net/automatalib/automaton/mmlt/State.java index 18867ae9a..2d7a8c33a 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/State.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/State.java @@ -17,7 +17,7 @@ public final class State { private final S location; - private final List> sortedTimers; + private final List> sortedTimers; private final long[] timerValues; private final long[] initialValues; private final long minimumTimerValue; @@ -32,7 +32,7 @@ public final class State { * @param sortedTimers * Timers of the location, sorted by initial value. */ - public State(S location, List> sortedTimers) { + public State(S location, List> sortedTimers) { this.location = location; this.sortedTimers = sortedTimers; @@ -50,7 +50,7 @@ public State(S location, List> sortedTimers) { } private State(S location, - List> sortedTimers, + List> sortedTimers, long[] timerValues, long[] initialValues, long entryDistance, @@ -120,7 +120,7 @@ public void resetTimers() { * Returns all timers that time out in the least number of time steps. */ @Nullable - public TimeoutPair getNextExpiringTimers() { + public TimeoutPair getNextExpiringTimers() { if (sortedTimers.isEmpty()) { return null; } else if (this.sortedTimers.size() == 1) { @@ -141,7 +141,7 @@ public TimeoutPair getNextExpiringTimers() { assert minValue != Long.MAX_VALUE; // Collect info of all timers that time out then: - List> expiringTimers = new ArrayList<>(); + List> expiringTimers = new ArrayList<>(); for (int i = 0; i < sortedTimers.size(); i++) { if (timerValues[i] == minValue) { expiringTimers.add(this.sortedTimers.get(i)); diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java b/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java index 7cbfbae5e..be967d5d9 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/TimeoutPair.java @@ -12,7 +12,7 @@ * @param * Output suffix type */ -public record TimeoutPair(long delay, List> timers) { +public record TimeoutPair(long delay, List> timers) { public boolean allPeriodic() { if (timers.size() == 1) { diff --git a/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java index 93084f173..39741aaa9 100644 --- a/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java @@ -1,18 +1,21 @@ package net.automatalib.automaton.visualization; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + import net.automatalib.automaton.mmlt.MMLT; import net.automatalib.automaton.mmlt.MealyTimerInfo; -import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.common.util.Triple; import net.automatalib.symbol.time.InputSymbol; +import net.automatalib.symbol.time.SymbolicInput; import net.automatalib.symbol.time.TimerTimeoutSymbol; -import net.automatalib.automaton.graph.TransitionEdge; - -import java.util.Map; -import java.util.stream.Collectors; +import net.automatalib.visualization.DefaultVisualizationHelper; public class MMLTVisualizationHelper - extends AutomatonVisualizationHelper, T, MMLT> { + extends DefaultVisualizationHelper, O, S>> { + private final MMLT automaton; private final boolean colorEdges; private final boolean includeResets; @@ -33,7 +36,7 @@ public class MMLTVisualizationHelper public MMLTVisualizationHelper(MMLT automaton, boolean colorEdges, boolean includeResets) { - super(automaton); + this.automaton = automaton; this.colorEdges = colorEdges; this.includeResets = includeResets; } @@ -42,6 +45,10 @@ public MMLTVisualizationHelper(MMLT automaton, public boolean getNodeProperties(S node, Map properties) { super.getNodeProperties(node, properties); + if (Objects.equals(node, automaton.getInitialState())) { + properties.put(NodeAttrs.INITIAL, Boolean.TRUE.toString()); + } + // Include timer assignments: var localTimers = automaton.getSortedTimers(node); if (!localTimers.isEmpty()) { @@ -57,18 +64,18 @@ public boolean getNodeProperties(S node, Map properties) { } @Override - public boolean getEdgeProperties(S src, TransitionEdge, T> edge, S tgt, Map properties) { + public boolean getEdgeProperties(S src, Triple, O, S> edge, S tgt, Map properties) { super.getEdgeProperties(src, edge, tgt, properties); - final SymbolicInput input = edge.getInput(); + final SymbolicInput input = edge.getFirst(); - String label = String.format("%s / %s", input, automaton.getTransitionProperty(edge.getTransition())); + String label = String.format("%s / %s", input, edge.getSecond()); // Infer the label color + reset information for the transition: String resetExtraInfo = ""; String resetInfo = ""; String edgeColor = ""; - if (edge.getInput() instanceof TimerTimeoutSymbol ts) { + if (input instanceof TimerTimeoutSymbol ts) { // Get info for corresponding timer: var optTimer = automaton.getSortedTimers(src).stream() .filter(t -> t.name().equals(ts.timer())) @@ -96,7 +103,7 @@ public boolean getEdgeProperties(S src, TransitionEdge, T> edge } edgeColor = "chartreuse3"; } - } else if (edge.getInput() instanceof InputSymbol ndi) { + } else if (input instanceof InputSymbol ndi) { if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { // Self-loop + local reset -> resets all in target: resetExtraInfo = automaton.getSortedTimers(tgt) diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java index d41018ab6..431b51714 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java @@ -12,18 +12,17 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.impl.CompactTransition; +import net.automatalib.automaton.mmlt.MMLTGraphView; import net.automatalib.automaton.mmlt.MMLTSemantics; import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.automaton.mmlt.MutableMMLT; import net.automatalib.automaton.mmlt.SymbolCombiner; import net.automatalib.automaton.transducer.impl.CompactMealy; -import net.automatalib.common.util.mapping.MutableMapping; +import net.automatalib.common.util.Triple; +import net.automatalib.graph.Graph; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.SymbolicInput; -import net.automatalib.symbol.time.TimerTimeoutSymbol; -import org.checkerframework.checker.nullness.qual.Nullable; /** * Implements a LocalTimerMealy that is mutable. The structure automaton is backed by a CompactMealy automaton. @@ -33,14 +32,11 @@ * @param * Output symbol type */ -public class CompactMMLT implements MutableMMLT, O> { +public class CompactMMLT extends CompactMealy, O> implements MutableMMLT, O> { - private final CompactMealy, O> automaton; - private final Map>> sortedTimers; // location -> (sorted timers) + private final Map>> sortedTimers; // location -> (sorted timers) private final Map>> resets; // location -> inputs (that reset all timers) - private final Alphabet> untimedAlphabet; - private final O silentOutput; private final SymbolCombiner outputCombiner; @@ -54,17 +50,14 @@ public class CompactMMLT implements MutableMMLT> nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { - this.untimedAlphabet = Alphabets.fromCollection(nonDelayingInputs); + public CompactMMLT(Alphabet> nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { + super(nonDelayingInputs); this.sortedTimers = new HashMap<>(); this.resets = new HashMap<>(); this.silentOutput = silentOutput; this.outputCombiner = outputCombiner; - - // Prepare compact Mealy: - this.automaton = new CompactMealy<>(new GrowingMapAlphabet<>(nonDelayingInputs)); } @Override @@ -77,14 +70,9 @@ public SymbolCombiner getOutputCombiner() { return this.outputCombiner; } - @Override - public Alphabet> getInputAlphabet() { - return this.automaton.getInputAlphabet(); - } - @Override public Alphabet> getUntimedAlphabet() { - return this.untimedAlphabet; + return getInputAlphabet(); } @Override @@ -93,7 +81,7 @@ public boolean isLocalReset(Integer location, InputSymbol input) { } @Override - public List> getSortedTimers(Integer location) { + public List> getSortedTimers(Integer location) { return Collections.unmodifiableList(this.sortedTimers.getOrDefault(location, Collections.emptyList())); } @@ -102,85 +90,7 @@ public List> getSortedTimers(Integer location) { return new CompactMMLTSemantics<>(this); } - @Override - public Collection getStates() { - return automaton.getStates(); - } - - @Override - public @Nullable CompactTransition getTransition(Integer location, SymbolicInput input) { - return automaton.getTransition(location, input); - } - - @Override - public @Nullable Integer getInitialState() { - return automaton.getInitialState(); - } - - @Override - public void clear() { - this.automaton.clear(); - } - - @Override - public Integer addState(@Nullable Void property) { - return this.automaton.addState(property); - } - - @Override - public void setStateProperty(Integer state, Void property) {} - - @Override - public void setTransitionProperty(CompactTransition transition, O property) { - this.automaton.setTransitionProperty(transition, property); - } - - @Override - public void removeAllTransitions(Integer state) { - this.automaton.removeAllTransitions(state); - } - - @Override - public CompactTransition createTransition(Integer successor, O properties) { - return this.automaton.createTransition(successor, properties); - } - - @Override - public void setInitialState(Integer location) { - automaton.setInitialState(location); - } - - @Override - public void setTransition(Integer state, SymbolicInput input, @Nullable CompactTransition transition) { - this.automaton.setTransition(state, input, transition); - } - - @Override - public Void getStateProperty(Integer state) { - return null; - } - - @Override - public O getTransitionProperty(CompactTransition transition) { - return transition.getProperty(); - } - - @Override - public Integer getSuccessor(CompactTransition transition) { - return transition.getSuccId(); - } - - @Override - public MutableMapping createStaticStateMapping() { - return automaton.createStaticStateMapping(); - } - - @Override - public MutableMapping createDynamicStateMapping() { - return automaton.createDynamicStateMapping(); - } - - private void ensureThatCanAddTimer(List> timers, + private void ensureThatCanAddTimer(List> timers, String name, long initial, O output, @@ -232,13 +142,13 @@ public void addPeriodicTimer(Integer location, String name, long initial, O outp var localTimers = this.sortedTimers.get(location); ensureThatCanAddTimer(localTimers, name, initial, output, true); - localTimers.add(new MealyTimerInfo<>(name, initial, output, true)); + localTimers.add(new MealyTimerInfo<>(name, initial, output, true, location)); localTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); // Add self-looping transition: - TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); - this.automaton.addAlphabetSymbol(newTimerSymbol); - automaton.addTransition(location, newTimerSymbol, location, output); +// TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); +// this.automaton.addAlphabetSymbol(newTimerSymbol); +// automaton.addTransition(location, newTimerSymbol, location, output); } @Override @@ -247,13 +157,13 @@ public void addOneShotTimer(Integer location, String name, long initial, O outpu var localTimers = this.sortedTimers.get(location); ensureThatCanAddTimer(localTimers, name, initial, output, false); - localTimers.add(new MealyTimerInfo<>(name, initial, output, false)); + localTimers.add(new MealyTimerInfo<>(name, initial, output, false, target)); localTimers.sort(Comparator.comparingLong(MealyTimerInfo::initial)); // Add transition with location change: - TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); - this.automaton.addAlphabetSymbol(newTimerSymbol); - automaton.addTransition(location, newTimerSymbol, target, output); +// TimerTimeoutSymbol newTimerSymbol = new TimerTimeoutSymbol<>(name); +// this.automaton.addAlphabetSymbol(newTimerSymbol); +// automaton.addTransition(location, newTimerSymbol, target, output); // Remove all timers with higher initial value, as these can no longer time out: localTimers.removeIf(t -> t.initial() > initial); @@ -267,7 +177,7 @@ public void removeTimer(Integer location, String timerName) { } localTimers.removeIf(t -> t.name().equals(timerName)); - automaton.removeAllTransitions(location, new TimerTimeoutSymbol<>(timerName)); +// automaton.removeAllTransitions(location, new TimerTimeoutSymbol<>(timerName)); } @Override @@ -291,4 +201,16 @@ public void removeLocalReset(Integer location, InputSymbol input) { localResets.remove(input); } + + @Override + public void clear() { + super.clear(); + this.sortedTimers.clear(); + this.resets.clear(); + } + + @Override + public Graph, O, Integer>> graphView() { + return new MMLTGraphView<>(this); + } } diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java index 5dad90786..17c501b49 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java @@ -15,7 +15,6 @@ import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimedOutput; import net.automatalib.symbol.time.TimeoutSymbol; -import net.automatalib.symbol.time.TimerTimeoutSymbol; import net.automatalib.word.Word; import net.automatalib.word.WordBuilder; import org.checkerframework.checker.nullness.qual.NonNull; @@ -171,9 +170,9 @@ private MealyTransition, TimedOutput> getTimeoutTransition(State< } else { // query target + update configuration: assert nextTimeouts.timers().size() == 1; - TimerTimeoutSymbol expiringTimerSym = new TimerTimeoutSymbol<>(nextTimeouts.timers().get(0).name()); + var timer = nextTimeouts.timers().get(0); + var successor = timer.target(); - var successor = model.getSuccessor(source.getLocation(), expiringTimerSym); target = new State<>(successor, model.getSortedTimers(successor)); target.resetTimers(); } diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java index 554f0a26e..5b0f4f868 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java @@ -15,7 +15,6 @@ import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; -import net.automatalib.alphabet.impl.MapAlphabet; import net.automatalib.automaton.mmlt.MMLTCreator; import net.automatalib.automaton.mmlt.MealyTimerInfo; import net.automatalib.automaton.mmlt.MutableMMLT; @@ -25,7 +24,6 @@ import net.automatalib.common.util.mapping.MutableMapping; import net.automatalib.exception.FormatException; import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.SymbolicInput; import net.automatalib.visualization.VisualizationHelper.EdgeAttrs; import net.automatalib.visualization.VisualizationHelper.MMLTEdgeAttrs; import net.automatalib.visualization.VisualizationHelper.MMLTNodeAttrs; @@ -93,7 +91,7 @@ */ public class DOTMMLTParser> - implements DOTInputModelDeserializer, A> { + implements DOTInputModelDeserializer, A> { private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); @@ -122,7 +120,7 @@ public DOTMMLTParser(MMLTCreator creator, } @Override - public DOTInputModelData, A> readModel(InputStream is) throws IOException, FormatException { + public DOTInputModelData, A> readModel(InputStream is) throws IOException, FormatException { try (Reader r = IOUtil.asNonClosingUTF8Reader(is)) { InternalDOTParser parser = new InternalDOTParser(r); @@ -146,7 +144,7 @@ public DOTInputModelData, A> readModel(InputStream is) throw final Mapping labels = parseNodesAndEdges(parser, automaton); - return new DOTInputModelData<>(automaton, new MapAlphabet<>(alphabet), labels); + return new DOTInputModelData<>(automaton, alphabet, labels); } } @@ -155,7 +153,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM final Collection nodes = parser.getNodes(); final Collection edges = parser.getEdges(); - final Map>> timers = + final Map>> timers = new HashMap<>(nodes.size() - 1); // id in dot -> local timers final Map stateMap = new HashMap<>(nodes.size() - 1); // name in dot -> new id final MutableMapping mapping = result.createDynamicStateMapping(); @@ -193,7 +191,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM node.id)); } - Map> timeInfo = timers.computeIfAbsent(node.id, k -> new HashMap<>()); + Map> timeInfo = timers.computeIfAbsent(node.id, k -> new HashMap<>()); if (timeInfo.containsKey(timerName)) { throw new IllegalArgumentException(String.format( "Timer %s in location %s must only be set once.", @@ -202,7 +200,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM } // Add timer: - timeInfo.put(timerName, new MealyTimerInfo<>(timerName, value, null)); + timeInfo.put(timerName, new MealyTimerInfo<>(timerName, value, null, null)); } } else { timers.put(node.id, Collections.emptyMap()); // no timers in this location @@ -242,7 +240,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM } // Add output to timer info: - final MealyTimerInfo oldInfo = timers.get(edge.src).get(timerName); + final MealyTimerInfo oldInfo = timers.get(edge.src).get(timerName); // Infer timer type: final long initial = oldInfo.initial(); diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java index ee144ac38..702a2d9e8 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java @@ -46,7 +46,6 @@ import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.serialization.ModelDeserializer; import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.SymbolicInput; import net.automatalib.symbol.time.TimedInput; import net.automatalib.ts.modal.ModalTransitionSystem; import net.automatalib.ts.modal.MutableModalTransitionSystem; @@ -702,19 +701,19 @@ public static ModelDeserializer> graph(Fu true); } - public static DOTInputModelDeserializer, CompactMMLT> mmlt(String silentOutput, - SymbolCombiner outputCombiner) { + public static DOTInputModelDeserializer, CompactMMLT> mmlt(String silentOutput, + SymbolCombiner outputCombiner) { return mmlt(TimedInput::input, Function.identity(), silentOutput, outputCombiner); } - public static DOTInputModelDeserializer, CompactMMLT> mmlt(Function> inputParser, - Function outputParser, - O silentOutput, - SymbolCombiner outputCombiner) { + public static DOTInputModelDeserializer, CompactMMLT> mmlt(Function> inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner) { return mmlt(CompactMMLT::new, inputParser, outputParser, silentOutput, outputCombiner); } - public static > DOTInputModelDeserializer, A> mmlt( + public static > DOTInputModelDeserializer, A> mmlt( MMLTCreator creator, Function> inputParser, Function outputParser, @@ -729,7 +728,7 @@ public static > DOTInputModelDeser true); } - public static > DOTInputModelDeserializer, A> mmlt( + public static > DOTInputModelDeserializer, A> mmlt( MMLTCreator creator, Function> inputParser, Function outputParser, diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java index e5ed4f2c6..782e1e467 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationTest.java @@ -188,10 +188,10 @@ public void testMMLTExport() throws IOException { final CompactMMLT mmlt = DOTSerializationUtil.MMLT; - ThrowingWriter writer = w -> GraphDOT.write(mmlt, mmlt.getInputAlphabet(), w); + ThrowingWriter writer = w -> GraphDOT.write(mmlt, w); checkDOTOutput(writer, DOTSerializationUtil.MMLT_RESOURCE); - ThrowingWriter writer2 = w -> GraphDOT.write(mmlt, mmlt.getInputAlphabet(), w, new MMLTVisualizationHelper<>(mmlt, true, true)); + ThrowingWriter writer2 = w -> GraphDOT.write(mmlt.graphView(), w, new MMLTVisualizationHelper<>(mmlt, true, true)); checkDOTOutput(writer2, DOTSerializationUtil.MMLT_WITH_RESETS_RESOURCE); } diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java index 9f3e3ae62..f0a67cc22 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java @@ -51,7 +51,7 @@ public class MMLTUtil { * @return Maximum configuration time. Long.MAX_VALUE, if exceeding integer maximum. */ public static long getConfigurationCount(MMLT automaton, S location) { - Collection> timers = automaton.getSortedTimers(location); + Collection> timers = automaton.getSortedTimers(location); if (timers.isEmpty()) { return 1; } From 64897aeddd3439dce6f4a4690411ccabd2edec88 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Mon, 10 Nov 2025 21:01:28 +0100 Subject: [PATCH 15/18] use input type I directly --- .../net/automatalib/automaton/mmlt/MMLT.java | 10 ++-- .../automaton/mmlt/MMLTCreator.java | 4 +- .../automaton/mmlt/MMLTGraphView.java | 7 ++- .../automaton/mmlt/MutableMMLT.java | 7 +-- .../MMLTVisualizationHelper.java | 2 +- .../automaton/mmlt/impl/CompactMMLT.java | 14 ++--- .../mmlt/impl/CompactMMLTSemantics.java | 6 +- .../automaton/mmlt/impl/MMLTTests.java | 21 +++---- .../serialization/dot/DOTMMLTParser.java | 15 +++-- .../serialization/dot/DOTParsers.java | 42 ++++++-------- .../dot/DOTDeserializationTest.java | 12 ++-- .../dot/DOTSerializationUtil.java | 18 +++--- .../util/automaton/cover/MMLTCoverTest.java | 18 +++--- .../util/automaton/mmlt/MMLTUtilTest.java | 58 +++++++++---------- 14 files changed, 107 insertions(+), 127 deletions(-) diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java index 14a241ec8..fbe090ba4 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java @@ -39,8 +39,8 @@ * @param * Output symbol type */ -public interface MMLT extends UniversalDeterministicAutomaton, T, Void, O>, - InputAlphabetHolder>, +public interface MMLT extends UniversalDeterministicAutomaton, + InputAlphabetHolder, GraphViewable { /** @@ -63,14 +63,14 @@ public interface MMLT extends UniversalDeterministicAutomaton> getInputAlphabet(); + Alphabet getInputAlphabet(); /** * Retrieves the non-delaying inputs for this automaton. Excludes timer timeout symbols. May be empty. * * @return Untimed alphabet. */ - Alphabet> getUntimedAlphabet(); + Alphabet getUntimedAlphabet(); /** * Indicates if the provided input performs a local reset in the given location. @@ -82,7 +82,7 @@ public interface MMLT extends UniversalDeterministicAutomaton input); + boolean isLocalReset(S location, I input); /** * Returns the timers of the specified location sorted ascendingly by their initial time. diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java index 548f1c8ef..053ada709 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTCreator.java @@ -21,10 +21,10 @@ @FunctionalInterface public interface MMLTCreator { - default A createMMLT(Alphabet> alphabet, int numStatesHint, O silentOutput, SymbolCombiner outputCombiner) { + default A createMMLT(Alphabet alphabet, int numStatesHint, O silentOutput, SymbolCombiner outputCombiner) { return createMMLT(alphabet, silentOutput, outputCombiner); } - A createMMLT(Alphabet> alphabet, O silentOutput, SymbolCombiner symbolCombiner); + A createMMLT(Alphabet alphabet, O silentOutput, SymbolCombiner symbolCombiner); } diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java index 1513ea169..82551bfef 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLTGraphView.java @@ -10,6 +10,7 @@ import net.automatalib.graph.Graph; import net.automatalib.symbol.time.InputSymbol; import net.automatalib.symbol.time.SymbolicInput; +import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.TimerTimeoutSymbol; import net.automatalib.visualization.VisualizationHelper; @@ -24,15 +25,15 @@ public MMLTGraphView(MMLT mmlt) { @Override public Collection, O, S>> getOutgoingEdges(S node) { - Alphabet> alphabet = mmlt.getInputAlphabet(); + Alphabet alphabet = mmlt.getInputAlphabet(); List> timers = mmlt.getSortedTimers(node); List, O, S>> result = new ArrayList<>(alphabet.size() + timers.size()); - for (InputSymbol i : alphabet) { + for (I i : alphabet) { var t = mmlt.getTransition(node, i); if (t != null) { - result.add(Triple.of(i, mmlt.getTransitionProperty(t), mmlt.getSuccessor(t))); + result.add(Triple.of(TimedInput.input(i), mmlt.getTransitionProperty(t), mmlt.getSuccessor(t))); } } diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java index 06a3f0cf0..7b16a1f3c 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MutableMMLT.java @@ -1,9 +1,8 @@ package net.automatalib.automaton.mmlt; import net.automatalib.automaton.MutableDeterministic; -import net.automatalib.symbol.time.InputSymbol; -public interface MutableMMLT extends MMLT, MutableDeterministic, T, Void, O> { +public interface MutableMMLT extends MMLT, MutableDeterministic { /** * Adds a new periodic timer to the provided location. @@ -50,7 +49,7 @@ public interface MutableMMLT extends MMLT, MutableDeterm * @param location Source location * @param input Input of the transition that should perform a local reset */ - void addLocalReset(S location, InputSymbol input); + void addLocalReset(S location, I input); /** * Removes a local reset at the provided input in the provided location. @@ -59,5 +58,5 @@ public interface MutableMMLT extends MMLT, MutableDeterm * @param location Source location * @param input Input of the transition that performs a local reset. */ - void removeLocalReset(S location, InputSymbol input); + void removeLocalReset(S location, I input); } diff --git a/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java index 39741aaa9..c5125dd8d 100644 --- a/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/automaton/visualization/MMLTVisualizationHelper.java @@ -104,7 +104,7 @@ public boolean getEdgeProperties(S src, Triple, O, S> edge, S t edgeColor = "chartreuse3"; } } else if (input instanceof InputSymbol ndi) { - if (src.equals(tgt) && automaton.isLocalReset(src, ndi)) { + if (src.equals(tgt) && automaton.isLocalReset(src, ndi.symbol())) { // Self-loop + local reset -> resets all in target: resetExtraInfo = automaton.getSortedTimers(tgt) .stream() diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java index 431b51714..b16389132 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java @@ -32,10 +32,10 @@ * @param * Output symbol type */ -public class CompactMMLT extends CompactMealy, O> implements MutableMMLT, O> { +public class CompactMMLT extends CompactMealy implements MutableMMLT, O> { private final Map>> sortedTimers; // location -> (sorted timers) - private final Map>> resets; // location -> inputs (that reset all timers) + private final Map> resets; // location -> inputs (that reset all timers) private final O silentOutput; private final SymbolCombiner outputCombiner; @@ -50,7 +50,7 @@ public class CompactMMLT extends CompactMealy, O> implement * @param outputCombiner * The combiner function for simultaneous timeouts of periodic timers. */ - public CompactMMLT(Alphabet> nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { + public CompactMMLT(Alphabet nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { super(nonDelayingInputs); this.sortedTimers = new HashMap<>(); @@ -71,12 +71,12 @@ public SymbolCombiner getOutputCombiner() { } @Override - public Alphabet> getUntimedAlphabet() { + public Alphabet getUntimedAlphabet() { return getInputAlphabet(); } @Override - public boolean isLocalReset(Integer location, InputSymbol input) { + public boolean isLocalReset(Integer location, I input) { return this.resets.getOrDefault(location, Collections.emptySet()).contains(input); } @@ -181,7 +181,7 @@ public void removeTimer(Integer location, String timerName) { } @Override - public void addLocalReset(Integer location, InputSymbol input) { + public void addLocalReset(Integer location, I input) { // Ensure that input causes self-loop: var target = this.getSuccessor(location, input); if (target == null || !target.equals(location)) { @@ -193,7 +193,7 @@ public void addLocalReset(Integer location, InputSymbol input) { } @Override - public void removeLocalReset(Integer location, InputSymbol input) { + public void removeLocalReset(Integer location, I input) { var localResets = resets.get(location); if (localResets == null) { return; diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java index 17c501b49..58103f864 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java @@ -52,7 +52,7 @@ public CompactMMLTSemantics(MMLT model) { var initialLocation = model.getInitialState(); this.initialConfiguration = new State<>(initialLocation, model.getSortedTimers(initialLocation)); - this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet()); + this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet().stream().map(TimedInput::input).toList()); this.alphabet.add(TimedInput.timeout()); this.alphabet.add(TimedInput.step()); @@ -194,7 +194,7 @@ private MealyTransition, TimedOutput> getTransition(State s State target; TimedOutput output; - var trans = model.getTransition(source.getLocation(), input); + var trans = model.getTransition(source.getLocation(), input.symbol()); if (trans == null) { // silent self-loop target = source; output = this.getSilentOutput(); @@ -205,7 +205,7 @@ private MealyTransition, TimedOutput> getTransition(State s // Change to a different location resets all timers in target: target = new State<>(succ, model.getSortedTimers(succ)); target.resetTimers(); - } else if (model.isLocalReset(source.getLocation(), input)) { + } else if (model.isLocalReset(source.getLocation(), input.symbol())) { target = source; target.resetTimers(); } else { diff --git a/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java b/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java index d40ccb401..ddad3494b 100644 --- a/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java +++ b/core/src/test/java/net/automatalib/automaton/mmlt/impl/MMLTTests.java @@ -4,10 +4,10 @@ import java.util.List; import java.util.Random; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.symbol.time.TimedInput; +import net.automatalib.alphabet.impl.Alphabets; 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.testng.Assert; @@ -16,10 +16,7 @@ public class MMLTTests { public 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(); @@ -29,19 +26,19 @@ public 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/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java index 5b0f4f868..76a61ecfa 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java @@ -23,7 +23,6 @@ import net.automatalib.common.util.mapping.Mapping; import net.automatalib.common.util.mapping.MutableMapping; import net.automatalib.exception.FormatException; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.visualization.VisualizationHelper.EdgeAttrs; import net.automatalib.visualization.VisualizationHelper.MMLTEdgeAttrs; import net.automatalib.visualization.VisualizationHelper.MMLTNodeAttrs; @@ -91,12 +90,12 @@ */ public class DOTMMLTParser> - implements DOTInputModelDeserializer, A> { + implements DOTInputModelDeserializer { private static final Pattern assignPattern = Pattern.compile("(\\S+)=(\\d+)"); private final MMLTCreator creator; - private final Function> inputParser; + private final Function inputParser; private final Function outputParser; private final O silentSymbol; private final SymbolCombiner outputCombiner; @@ -104,7 +103,7 @@ public class DOTMMLTParser> private final boolean fakeInitialNodeIds; public DOTMMLTParser(MMLTCreator creator, - Function> inputParser, + Function inputParser, Function outputParser, O silentOutput, SymbolCombiner outputCombiner, @@ -120,7 +119,7 @@ public DOTMMLTParser(MMLTCreator creator, } @Override - public DOTInputModelData, A> readModel(InputStream is) throws IOException, FormatException { + public DOTInputModelData readModel(InputStream is) throws IOException, FormatException { try (Reader r = IOUtil.asNonClosingUTF8Reader(is)) { InternalDOTParser parser = new InternalDOTParser(r); @@ -128,7 +127,7 @@ public DOTInputModelData, A> readModel(InputStream is) throws assert parser.isDirected(); - final Set> inputs = new HashSet<>(); + final Set inputs = new HashSet<>(); for (Edge edge : parser.getEdges()) { if (!fakeInitialNodeIds || !initialNodeIds.contains(edge.src)) { @@ -139,7 +138,7 @@ public DOTInputModelData, A> readModel(InputStream is) throws } } - final Alphabet> alphabet = Alphabets.fromCollection(inputs); + final Alphabet alphabet = Alphabets.fromCollection(inputs); final A automaton = creator.createMMLT(alphabet, parser.getNodes().size(), silentSymbol, outputCombiner); final Mapping labels = parseNodesAndEdges(parser, automaton); @@ -275,7 +274,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM } } else { // Non-delaying input: - final InputSymbol i = inputParser.apply(input); + final I i = inputParser.apply(input); final O o = outputParser.apply(output); result.addTransition(stateMap.get(edge.src), i, stateMap.get(edge.tgt), o); diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java index 702a2d9e8..90184a029 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java @@ -45,8 +45,6 @@ import net.automatalib.graph.MutableGraph; import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.serialization.ModelDeserializer; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimedInput; import net.automatalib.ts.modal.ModalTransitionSystem; import net.automatalib.ts.modal.MutableModalTransitionSystem; import net.automatalib.ts.modal.impl.CompactMTS; @@ -701,24 +699,23 @@ public static ModelDeserializer> graph(Fu true); } - public static DOTInputModelDeserializer, CompactMMLT> mmlt(String silentOutput, - SymbolCombiner outputCombiner) { - return mmlt(TimedInput::input, Function.identity(), silentOutput, outputCombiner); + public static DOTInputModelDeserializer> mmlt(String silentOutput, + SymbolCombiner outputCombiner) { + return mmlt(Function.identity(), Function.identity(), silentOutput, outputCombiner); } - public static DOTInputModelDeserializer, CompactMMLT> mmlt(Function> inputParser, - Function outputParser, - O silentOutput, - SymbolCombiner outputCombiner) { + public static DOTInputModelDeserializer> mmlt(Function inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner) { return mmlt(CompactMMLT::new, inputParser, outputParser, silentOutput, outputCombiner); } - public static > DOTInputModelDeserializer, A> mmlt( - MMLTCreator creator, - Function> inputParser, - Function outputParser, - O silentOutput, - SymbolCombiner outputCombiner) { + public static > DOTInputModelDeserializer mmlt(MMLTCreator creator, + Function inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner) { return mmlt(creator, inputParser, outputParser, @@ -728,14 +725,13 @@ public static > DOTInputModelDeser true); } - public static > DOTInputModelDeserializer, A> mmlt( - MMLTCreator creator, - Function> inputParser, - Function outputParser, - O silentOutput, - SymbolCombiner outputCombiner, - Collection initialNodeIds, - boolean fakeInitialNodeIds) { + public static > DOTInputModelDeserializer mmlt(MMLTCreator creator, + Function inputParser, + Function outputParser, + O silentOutput, + SymbolCombiner outputCombiner, + Collection initialNodeIds, + boolean fakeInitialNodeIds) { return new DOTMMLTParser<>(creator, inputParser, outputParser, diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java index 2e3010a2a..25c5d095f 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java @@ -164,10 +164,10 @@ public void testMMLTSensorModel() throws IOException, FormatException { .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.MMLT_SENSOR)).model; // Compare to reference: - var p1 = new InputSymbol<>("p1"); - var p2 = new InputSymbol<>("p2"); - var abort = new InputSymbol<>("abort"); - var collect = new InputSymbol<>("collect"); + var p1 = "p1"; + var p2 = "p2"; + var abort = "abort"; + var collect = "collect"; int s0 = 0; int s1 = 1; @@ -377,7 +377,7 @@ private static , EP extends Comparable, N2 private void assertTransition(MMLT model, int state, int target, - InputSymbol input, + String input, String output) { var trans = model.getTransition(state, input); Assert.assertNotNull(trans); @@ -387,7 +387,7 @@ private void assertTransition(MMLT model, } } - private void assertSilentLoop(MMLT model, int state, InputSymbol input) { + private void assertSilentLoop(MMLT model, int state, String input) { var trans = model.getTransition(state, input); if (trans != null && (model.getSuccessor(trans) != state || !"void".equals(model.getTransitionProperty(trans)))) { diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java index a7a54dc68..869542a9b 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java @@ -26,7 +26,6 @@ import net.automatalib.alphabet.ProceduralInputAlphabet; import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.DefaultProceduralInputAlphabet; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.automaton.fsa.DFA; import net.automatalib.automaton.fsa.impl.CompactDFA; import net.automatalib.automaton.fsa.impl.CompactNFA; @@ -50,7 +49,6 @@ import net.automatalib.graph.impl.CompactPMPGEdge; import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.graph.impl.DefaultCFMPS; -import net.automatalib.symbol.time.InputSymbol; import net.automatalib.ts.modal.impl.CompactMTS; import net.automatalib.ts.modal.transition.ModalEdgeProperty.ModalType; import net.automatalib.ts.modal.transition.MutableProceduralModalEdgeProperty; @@ -445,21 +443,19 @@ private static StackSBA buildSBA() { } private static CompactMMLT buildMMLT() { - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - Alphabets.closedCharStringRange('x', 'y').forEach(s -> alphabet.add(new InputSymbol<>(s))); + Alphabet alphabet = Alphabets.closedCharStringRange('x', 'y'); - final CompactMMLT mmlt = new CompactMMLT<>(alphabet, - "void", - StringSymbolCombiner.getInstance()); + final CompactMMLT mmlt = + new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); var s0 = mmlt.addInitialState(); var s1 = mmlt.addState(); var s2 = mmlt.addState(); - mmlt.addTransition(s1, new InputSymbol<>("x"), s2, "void"); - mmlt.addTransition(s1, new InputSymbol<>("y"), s1, "Y"); - mmlt.addTransition(s2, new InputSymbol<>("y"), s2, "D"); + mmlt.addTransition(s1, "x", s2, "void"); + mmlt.addTransition(s1, "y", s1, "Y"); + mmlt.addTransition(s2, "y", s2, "D"); - mmlt.addLocalReset(s1, new InputSymbol<>("y")); + mmlt.addLocalReset(s1, "y"); mmlt.addOneShotTimer(s0, "a", 2, "A", s1); mmlt.addPeriodicTimer(s1, "b", 4, "B"); diff --git a/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java b/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java index 24059fc33..48e11e946 100644 --- a/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java +++ b/util/src/test/java/net/automatalib/util/automaton/cover/MMLTCoverTest.java @@ -1,5 +1,6 @@ package net.automatalib.util.automaton.cover; +import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.alphabet.impl.GrowingMapAlphabet; import net.automatalib.symbol.time.TimedInput; import net.automatalib.symbol.time.InputSymbol; @@ -13,10 +14,7 @@ @Test public class MMLTCoverTest { 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(); @@ -26,19 +24,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/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java b/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java index e34b9ee50..317c63273 100644 --- a/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java +++ b/util/src/test/java/net/automatalib/util/automaton/mmlt/MMLTUtilTest.java @@ -1,24 +1,20 @@ package net.automatalib.util.automaton.mmlt; -import net.automatalib.alphabet.impl.GrowingMapAlphabet; -import net.automatalib.symbol.time.TimedInput; -import net.automatalib.symbol.time.InputSymbol; -import net.automatalib.symbol.time.TimeoutSymbol; +import java.util.HashSet; +import java.util.Set; + +import net.automatalib.alphabet.Alphabet; +import net.automatalib.alphabet.impl.Alphabets; import net.automatalib.automaton.mmlt.impl.CompactMMLT; import net.automatalib.automaton.mmlt.impl.StringSymbolCombiner; +import net.automatalib.symbol.time.TimedInput; import org.testng.Assert; import org.testng.annotations.Test; -import java.util.ArrayList; -import java.util.List; - public class MMLTUtilTest { public 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(); @@ -28,19 +24,19 @@ public 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; } @@ -62,8 +58,8 @@ public void testTimerAndResetRemovals() { model.addOneShotTimer(s1, "c", 40, "done", s3); model.removeTimer(s1, "e"); - model.removeLocalReset(s1, new InputSymbol<>("abort")); - model.addLocalReset(s1, new InputSymbol<>("abort")); + model.removeLocalReset(s1, "abort"); + model.addLocalReset(s1, "abort"); // Still needs to be equivalent to original: var originalModel = buildBaseModel(); @@ -74,37 +70,35 @@ public void testTimerAndResetRemovals() { @Test public void testSeparatedByResetsSimple() { - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addSymbol(new InputSymbol<>("x")); + Alphabet alphabet = Alphabets.singleton("x"); // Same model, but with reset in A and no reset in B: var modelA = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); var s0 = modelA.addState(); modelA.setInitialState(s0); modelA.addPeriodicTimer(s0, "a", 3, "test"); - modelA.addTransition(s0, new InputSymbol<>("x"), s0, "ok"); - modelA.addLocalReset(s0, new InputSymbol<>("x")); + modelA.addTransition(s0, "x", s0, "ok"); + modelA.addLocalReset(s0, "x"); var modelB = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); var s0B = modelB.addState(); modelB.setInitialState(s0B); modelB.addPeriodicTimer(s0B, "a", 3, "test"); - modelB.addTransition(s0B, new InputSymbol<>("x"), s0B, "ok"); + modelB.addTransition(s0B, "x", s0B, "ok"); Assert.assertNotNull(MMLTUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); // If we remove the timestep, should not find a counterexample: - List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); - reducedInputs.add(new TimeoutSymbol<>()); + Set> reducedInputs = new HashSet<>(modelA.getSemantics().getInputAlphabet()); + reducedInputs.remove(TimedInput.step()); Assert.assertNull(MMLTUtil.findSeparatingWord(modelA, modelB, reducedInputs)); } @Test public void testSeparatedByResetsComplex() { - GrowingMapAlphabet> alphabet = new GrowingMapAlphabet<>(); - alphabet.addSymbol(new InputSymbol<>("x")); + Alphabet alphabet = Alphabets.singleton("x"); // Same model, but with reset in A and no reset in B: var modelA = new CompactMMLT<>(alphabet, "void", StringSymbolCombiner.getInstance()); @@ -121,16 +115,16 @@ public void testSeparatedByResetsComplex() { modelB.setInitialState(s0B); modelB.addOneShotTimer(s0B, "a", 3, "test", s1B); modelB.addOneShotTimer(s1B, "b", 2, "test2", s2B); - modelB.addTransition(s1B, new InputSymbol<>("x"), s1B, "void"); - modelB.addLocalReset(s1B, new InputSymbol<>("x")); + modelB.addTransition(s1B, "x", s1B, "void"); + modelB.addLocalReset(s1B, "x"); Assert.assertNotNull(MMLTUtil.findSeparatingWord(modelA, modelB, modelA.getSemantics().getInputAlphabet())); // If we remove the timestep, should not find a counterexample: - List> reducedInputs = new ArrayList<>(modelA.getUntimedAlphabet()); - reducedInputs.add(new TimeoutSymbol<>()); + Set> reducedInputs = new HashSet<>(modelA.getSemantics().getInputAlphabet()); + reducedInputs.remove(TimedInput.step()); Assert.assertNull(MMLTUtil.findSeparatingWord(modelA, modelB, reducedInputs)); } From fc8f78c3d7be84edc5848bc2212bddd5623de609 Mon Sep 17 00:00:00 2001 From: Paul Kogel Date: Tue, 11 Nov 2025 09:52:35 +0100 Subject: [PATCH 16/18] Removed getUntimedAlphabet, as it has the same function as getInputAlphabet. --- .../main/java/net/automatalib/automaton/mmlt/MMLT.java | 9 +-------- .../net/automatalib/automaton/mmlt/impl/CompactMMLT.java | 5 ----- .../automaton/mmlt/impl/CompactMMLTSemantics.java | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java index fbe090ba4..6ce422131 100644 --- a/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java +++ b/api/src/main/java/net/automatalib/automaton/mmlt/MMLT.java @@ -59,19 +59,12 @@ public interface MMLT extends UniversalDeterministicAutomaton getOutputCombiner(); /** - * Returns the input alphabet of this MMLT, consisting of non-delaying inputs and timeout-symbols for its timers. + * Retrieves the non-delaying inputs for this automaton. Excludes timer timeout symbols. May be empty. * * @return Input alphabet */ Alphabet getInputAlphabet(); - /** - * Retrieves the non-delaying inputs for this automaton. Excludes timer timeout symbols. May be empty. - * - * @return Untimed alphabet. - */ - Alphabet getUntimedAlphabet(); - /** * Indicates if the provided input performs a local reset in the given location. * diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java index b16389132..10fc1a40f 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java @@ -70,11 +70,6 @@ public SymbolCombiner getOutputCombiner() { return this.outputCombiner; } - @Override - public Alphabet getUntimedAlphabet() { - return getInputAlphabet(); - } - @Override public boolean isLocalReset(Integer location, I input) { return this.resets.getOrDefault(location, Collections.emptySet()).contains(input); diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java index 58103f864..b8e337b73 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLTSemantics.java @@ -52,7 +52,7 @@ public CompactMMLTSemantics(MMLT model) { var initialLocation = model.getInitialState(); this.initialConfiguration = new State<>(initialLocation, model.getSortedTimers(initialLocation)); - this.alphabet = new GrowingMapAlphabet<>(model.getUntimedAlphabet().stream().map(TimedInput::input).toList()); + this.alphabet = new GrowingMapAlphabet<>(model.getInputAlphabet().stream().map(TimedInput::input).toList()); this.alphabet.add(TimedInput.timeout()); this.alphabet.add(TimedInput.step()); From 8762c0282b97715c931ce7582c7d4452e747add5 Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 11 Nov 2025 16:27:12 +0100 Subject: [PATCH 17/18] CompactMMLT: allow for sizeHints --- .../net/automatalib/automaton/mmlt/impl/CompactMMLT.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java index 10fc1a40f..8ada1a3a3 100644 --- a/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java +++ b/core/src/main/java/net/automatalib/automaton/mmlt/impl/CompactMMLT.java @@ -51,7 +51,11 @@ public class CompactMMLT extends CompactMealy implements MutableMMLT * The combiner function for simultaneous timeouts of periodic timers. */ public CompactMMLT(Alphabet nonDelayingInputs, O silentOutput, SymbolCombiner outputCombiner) { - super(nonDelayingInputs); + this(nonDelayingInputs, DEFAULT_INIT_CAPACITY, silentOutput, outputCombiner); + } + + public CompactMMLT(Alphabet nonDelayingInputs, int sizeHint, O silentOutput, SymbolCombiner outputCombiner) { + super(nonDelayingInputs, sizeHint); this.sortedTimers = new HashMap<>(); this.resets = new HashMap<>(); From 0afeef077d7c0b45bae73c3a8f29dbee6a64a86c Mon Sep 17 00:00:00 2001 From: Markus Frohme Date: Tue, 18 Nov 2025 13:56:20 +0100 Subject: [PATCH 18/18] MMLTs: add direct equivalence check --- .../java/net/automatalib/util/automaton/mmlt/MMLTUtil.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java index f0a67cc22..fa76f8730 100644 --- a/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java +++ b/util/src/main/java/net/automatalib/util/automaton/mmlt/MMLTUtil.java @@ -19,6 +19,12 @@ */ public class MMLTUtil { + public static boolean testEquivalence(MMLT modelA, + MMLT modelB, + Collection> inputs) { + return findSeparatingWord(modelA, modelB, inputs) == null; + } + public static @Nullable Word> findSeparatingWord(MMLT modelA, MMLT modelB, Collection> inputs) { var expandedA = ReducedMMLTSemantics.forLocalTimerMealy(modelA);