• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

LearnLib / automatalib / 19864403395

02 Dec 2025 03:40PM UTC coverage: 92.771% (+0.2%) from 92.565%
19864403395

push

github

web-flow
Code for Mealy machines with local timers. (#96)

* Added MMLT classes

* Added basic tests for MMLTs; included sample MMLT model file.

* Added serialization test for MMLTs.

* Separated more files into API and implementation to enable use from LearnLib.

* Removed I prefix for some interfaces for the MMLT automaton.

* Updated calculation of MMLT location cover to accept a list of inputs to be considered.

* Method for finding a separating word in an MMLT now considers a provided collection of inputs.

* Convenience flag for faster location cover calculation.

* Added more tests for the reduced semantics.

* Parser for MMLTs now accepts an input stream, in addition to a file.

* Updated Serialization for MMLTs: resets-attribute for edges is now included when resets cannot be inferred from context.

* initial refactorings / cleanups

* use InputSymbols directly

* use input type I directly

* Removed getUntimedAlphabet, as it has the same function as getInputAlphabet.

* CompactMMLT: allow for sizeHints

* MMLTs: add direct equivalence check

* make code-analysis pass

* mention MMLTs in README

* adjust to LearnLib refactorings

* add documentation

* add some more test cases

* Using LinkedHashMap instead of HashMap in MMLT-Cover for deterministic ordering.

* MMLT transition output can no longer be null.

* Added more validation tests for the MMLT-parser.

* TimerInfo now supports multiple outputs per timer. StringSymbolCombiner expects atomic outputs for its input list when combining symbols.

* MMLT parser now parses multiple outputs for one timer. Updated corresponding tests.

* fix analysis and adjust to LearnLib refactorings

* cleanups

* documentation

* last de-stream-ifications

---------

Co-authored-by: Paul Kogel <p.kogel@tu-berlin.de>
Co-authored-by: Markus Frohme <markus.frohme@udo.edu>

649 of 661 new or added lines in 21 files covered. (98.18%)

17146 of 18482 relevant lines covered (92.77%)

1.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/api/src/main/java/net/automatalib/automaton/mmlt/State.java
1
/* Copyright (C) 2013-2025 TU Dortmund University
2
 * This file is part of AutomataLib <https://automatalib.net>.
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16
package net.automatalib.automaton.mmlt;
17

18
import java.util.ArrayList;
19
import java.util.Arrays;
20
import java.util.Collections;
21
import java.util.List;
22
import java.util.Objects;
23

24
import org.checkerframework.checker.nullness.qual.Nullable;
25

26
/**
27
 * A state configuration of an MMLT. A configuration is a tuple of an active location and the valuation of its timers.
28
 *
29
 * @param <S>
30
 *         location type
31
 * @param <O>
32
 *         output symbol type
33
 */
34
public final class State<S, O> {
1✔
35

36
    private final S location;
37

38
    private final List<TimerInfo<S, O>> sortedTimers;
39
    private final long[] timerValues;
40
    private final long[] initialValues;
41
    private final long minimumTimerValue;
42

43
    private final long entryDistance;
44

45
    /**
46
     * Initializes the entry configuration for the provided location, where all timers have their initial value.
47
     *
48
     * @param location
49
     *         the location
50
     * @param sortedTimers
51
     *         the timers of the location, sorted by initial value
52
     */
53
    public State(S location, List<TimerInfo<S, O>> sortedTimers) {
1✔
54
        this.location = location;
1✔
55

56
        this.sortedTimers = sortedTimers;
1✔
57

58
        this.initialValues = new long[sortedTimers.size()];
1✔
59
        this.timerValues = new long[sortedTimers.size()];
1✔
60
        this.minimumTimerValue = sortedTimers.isEmpty() ? 0 : sortedTimers.get(0).initial();
1✔
61

62
        for (int i = 0; i < sortedTimers.size(); i++) {
1✔
63
            final long initial = sortedTimers.get(i).initial();
1✔
64
            initialValues[i] = initial;
1✔
65
            timerValues[i] = initial; // reset
1✔
66
        }
67

68
        this.entryDistance = 0;
1✔
69
    }
1✔
70

71
    private State(S location,
72
                  List<TimerInfo<S, O>> sortedTimers,
73
                  long[] timerValues,
74
                  long[] initialValues,
75
                  long entryDistance,
76
                  long minimumTimerValue) {
1✔
77
        this.location = location;
1✔
78
        this.sortedTimers = sortedTimers;
1✔
79

80
        this.initialValues = initialValues;
1✔
81
        this.minimumTimerValue = minimumTimerValue;
1✔
82
        this.timerValues = timerValues;
1✔
83
        this.entryDistance = entryDistance;
1✔
84
    }
1✔
85

86
    /**
87
     * Returns the MMLT location of this state.
88
     *
89
     * @return the location
90
     */
91
    public S getLocation() {
92
        return location;
1✔
93
    }
94

95
    /**
96
     * Returns the entry distance. This is the minimal number of time steps required to reach this configuration from
97
     * the entry configuration.
98
     *
99
     * @return the entry distance
100
     */
101
    public long getEntryDistance() {
102
        return entryDistance;
1✔
103
    }
104

105
    /**
106
     * Indicates if this is the entry configuration of the location. A configuration is the entry configuration if all
107
     * timers have their initial value.
108
     *
109
     * @return {@code true} if this is the entry configuration, {@code false} otherwise
110
     */
111
    public boolean isEntryConfig() {
112
        return this.entryDistance == 0;
1✔
113
    }
114

115
    /**
116
     * Indicates if this configuration is stable. A configuration is stable if its entry distance is less than the
117
     * initial value of the timer with the lowest initial value of the location. If the location has no timers, its only
118
     * configuration is its entry configuration, which is always stable.
119
     *
120
     * @return {@code true} if stable, {@code false} otherwise
121
     */
122
    public boolean isStableConfig() {
123
        return this.entryDistance == 0 || this.entryDistance < minimumTimerValue;
1✔
124
    }
125

126
    /**
127
     * Returns a copy of {@code this} state with all timers reset to their initial values.
128
     *
129
     * @return the new state with all its timers reset
130
     */
131
    public State<S, O> resetTimers() {
132
        return new State<>(location, sortedTimers, initialValues.clone(), initialValues, 0, minimumTimerValue);
1✔
133
    }
134

135
    /**
136
     * Returns all timers that time out in the least number of time steps.
137
     *
138
     * @return the timed out timers
139
     */
140
    public @Nullable TimeoutPair<S, O> getNextExpiringTimers() {
141
        if (sortedTimers.isEmpty()) {
1✔
142
            return null;
1✔
143
        } else if (this.sortedTimers.size() == 1) {
1✔
144
            // No need to collect timeouts - there is only one timer that can expire.
145
            // The time to its timeout is its remaining value:
146
            return new TimeoutPair<>(this.timerValues[0], Collections.singletonList(this.sortedTimers.get(0)));
1✔
147

148
        } else {
149
            // Multiple timers may time out at the same time.
150
            // Get minimum distance to next timeout:
151
            long minValue = Long.MAX_VALUE;
1✔
152
            for (int i = 0; i < sortedTimers.size(); i++) {
1✔
153
                if (timerValues[i] < minValue) {
1✔
154
                    minValue = timerValues[i];
1✔
155
                }
156
            }
157

158
            assert minValue != Long.MAX_VALUE;
1✔
159

160
            // Collect info of all timers that time out then:
161
            List<TimerInfo<S, O>> expiringTimers = new ArrayList<>();
1✔
162
            for (int i = 0; i < sortedTimers.size(); i++) {
1✔
163
                if (timerValues[i] == minValue) {
1✔
164
                    expiringTimers.add(this.sortedTimers.get(i));
1✔
165
                }
166
            }
167

168
            // Return timed-out timers:
169
            return new TimeoutPair<>(minValue, expiringTimers);
1✔
170
        }
171
    }
172

173
    /**
174
     * Returns a new state in which all timer values have been decreased by the specified amount. This amount must be at
175
     * most the time to the next timeout. If this sets a timer to zero, this timer is immediately reset to its initial
176
     * value.
177
     *
178
     * @param delay
179
     *         the value by which to decrement the current timers
180
     *
181
     * @return the new state with updated timers
182
     */
183
    public State<S, O> decrement(long delay) {
184
        int timerResets = 0;
1✔
185
        int oneShotResets = 0;
1✔
186

187
        final long[] newTimerValues = this.timerValues.clone();
1✔
188

189
        for (int i = 0; i < this.sortedTimers.size(); i++) {
1✔
190
            long newValue = newTimerValues[i] - delay;
1✔
191

192
            if (newValue < 0) {
1✔
NEW
193
                throw new IllegalArgumentException("Can only advance to next timeout.");
×
194
            } else if (newValue == 0) {
1✔
195
                if (!sortedTimers.get(i).periodic()) {
1✔
NEW
196
                    oneShotResets += 1;
×
197
                }
198

199
                newValue = initialValues[i];
1✔
200
                timerResets += 1;
1✔
201
            }
202
            newTimerValues[i] = newValue;
1✔
203
        }
204

205
        assert oneShotResets <= 1;
1✔
206
        final long newEntryDistance;
207

208
        if (timerResets == this.sortedTimers.size() || oneShotResets == 1) {
1✔
209
            // reset all timers -> back at entry config:
210
            newEntryDistance = 0;
1✔
211
        } else {
212
            newEntryDistance = this.entryDistance + delay;
1✔
213
        }
214

215
        return new State<>(location, sortedTimers, newTimerValues, initialValues, newEntryDistance, minimumTimerValue);
1✔
216
    }
217

218
    @Override
219
    public boolean equals(@Nullable Object o) {
220
        return this == o || o instanceof State<?, ?> that && this.minimumTimerValue == that.minimumTimerValue &&
1✔
221
                            entryDistance == that.entryDistance && Objects.equals(location, that.location) &&
1✔
222
                            Objects.equals(sortedTimers, that.sortedTimers) &&
1✔
223
                            Arrays.equals(timerValues, that.timerValues) &&
1✔
224
                            Arrays.equals(initialValues, that.initialValues);
1✔
225
    }
226

227
    @Override
228
    public int hashCode() {
229
        return Objects.hash(location,
1✔
230
                            sortedTimers,
231
                            Arrays.hashCode(timerValues),
1✔
232
                            Arrays.hashCode(initialValues),
1✔
233
                            minimumTimerValue,
1✔
234
                            entryDistance);
1✔
235
    }
236

237
    @Override
238
    public String toString() {
NEW
239
        return String.format("[%s,%d]", this.location, this.entryDistance);
×
240
    }
241
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc