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

LearnLib / learnlib / 6433387082

06 Oct 2023 03:10PM UTC coverage: 92.296% (-0.007%) from 92.303%
6433387082

push

github

mtf90
update Falk's developer id

11573 of 12539 relevant lines covered (92.3%)

1.67 hits per line

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

96.26
/algorithms/active/dhc/src/main/java/de/learnlib/algorithms/dhc/mealy/MealyDHC.java
1
/* Copyright (C) 2013-2023 TU Dortmund
2
 * This file is part of LearnLib, http://www.learnlib.de/.
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 de.learnlib.algorithms.dhc.mealy;
17

18
import java.util.ArrayDeque;
19
import java.util.ArrayList;
20
import java.util.Collection;
21
import java.util.Collections;
22
import java.util.HashMap;
23
import java.util.Iterator;
24
import java.util.LinkedHashSet;
25
import java.util.List;
26
import java.util.Map;
27
import java.util.Queue;
28
import java.util.Set;
29

30
import com.github.misberner.buildergen.annotations.GenerateBuilder;
31
import com.google.common.collect.Interner;
32
import com.google.common.collect.Interners;
33
import com.google.common.collect.Sets;
34
import de.learnlib.api.AccessSequenceTransformer;
35
import de.learnlib.api.Resumable;
36
import de.learnlib.api.algorithm.LearningAlgorithm.MealyLearner;
37
import de.learnlib.api.algorithm.feature.GlobalSuffixLearner.GlobalSuffixLearnerMealy;
38
import de.learnlib.api.oracle.MembershipOracle;
39
import de.learnlib.api.query.DefaultQuery;
40
import de.learnlib.counterexamples.GlobalSuffixFinder;
41
import de.learnlib.counterexamples.GlobalSuffixFinders;
42
import net.automatalib.SupportsGrowingAlphabet;
43
import net.automatalib.automata.transducers.impl.compact.CompactMealy;
44
import net.automatalib.commons.util.mappings.MapMapping;
45
import net.automatalib.commons.util.mappings.MutableMapping;
46
import net.automatalib.words.Alphabet;
47
import net.automatalib.words.Word;
48
import net.automatalib.words.impl.Alphabets;
49
import org.checkerframework.checker.nullness.qual.NonNull;
50
import org.checkerframework.checker.nullness.qual.Nullable;
51
import org.slf4j.Logger;
52
import org.slf4j.LoggerFactory;
53

54
public class MealyDHC<I, O> implements MealyLearner<I, O>,
55
                                       AccessSequenceTransformer<I>,
56
                                       GlobalSuffixLearnerMealy<I, O>,
57
                                       SupportsGrowingAlphabet<I>,
58
                                       Resumable<MealyDHCState<I, O>> {
59

60
    private static final Logger LOG = LoggerFactory.getLogger(MealyDHC.class);
2✔
61
    private final MembershipOracle<I, Word<O>> oracle;
62
    private final Alphabet<I> alphabet;
63
    private Set<Word<I>> splitters = new LinkedHashSet<>();
2✔
64
    private CompactMealy<I, O> hypothesis;
65
    private MutableMapping<Integer, QueueElement<I, O>> accessSequences;
66
    private final GlobalSuffixFinder<? super I, ? super Word<O>> suffixFinder;
67

68
    /**
69
     * Constructor, provided for backwards compatibility reasons.
70
     *
71
     * @param alphabet
72
     *         the learning alphabet
73
     * @param oracle
74
     *         the learning membership oracle
75
     */
76
    public MealyDHC(Alphabet<I> alphabet, MembershipOracle<I, Word<O>> oracle) {
77
        this(alphabet, oracle, GlobalSuffixFinders.RIVEST_SCHAPIRE, Collections.emptyList());
2✔
78
    }
2✔
79

80
    /**
81
     * Constructor.
82
     *
83
     * @param alphabet
84
     *         the learning alphabet
85
     * @param oracle
86
     *         the learning membership oracle
87
     * @param suffixFinder
88
     *         the {@link GlobalSuffixFinder suffix finder} to use for analyzing counterexamples
89
     * @param initialSplitters
90
     *         the initial set of splitters, {@code null} or an empty collection will result in the set of splitters
91
     *         being initialized as the set of alphabet symbols (interpreted as {@link Word}s)
92
     */
93
    @GenerateBuilder(defaults = BuilderDefaults.class, builderFinal = false)
94
    public MealyDHC(Alphabet<I> alphabet,
95
                    MembershipOracle<I, Word<O>> oracle,
96
                    GlobalSuffixFinder<? super I, ? super Word<O>> suffixFinder,
97
                    Collection<? extends Word<I>> initialSplitters) {
2✔
98
        this.alphabet = alphabet;
2✔
99
        this.oracle = oracle;
2✔
100
        this.suffixFinder = suffixFinder;
2✔
101
        // ensure that the first k splitters are the k alphabet symbols,
102
        // in correct order (this is required by scheduleSuccessors)
103
        for (I symbol : alphabet) {
2✔
104
            splitters.add(Word.fromLetter(symbol));
2✔
105
        }
2✔
106
        if (initialSplitters != null) {
2✔
107
            splitters.addAll(initialSplitters);
2✔
108
        }
109
    }
2✔
110

111
    @Override
112
    public Collection<Word<I>> getGlobalSuffixes() {
113
        return Collections.unmodifiableCollection(splitters);
×
114
    }
115

116
    @Override
117
    public boolean addGlobalSuffixes(Collection<? extends Word<I>> newGlobalSuffixes) {
118
        checkInternalState();
×
119

120
        return addSuffixesUnchecked(newGlobalSuffixes);
×
121
    }
122

123
    private void checkInternalState() {
124
        if (hypothesis == null) {
2✔
125
            throw new IllegalStateException("No hypothesis learned yet");
2✔
126
        }
127
    }
2✔
128

129
    protected boolean addSuffixesUnchecked(Collection<? extends Word<I>> newSuffixes) {
130
        int oldSize = hypothesis.size();
2✔
131

132
        for (Word<I> suf : newSuffixes) {
2✔
133
            if (!splitters.contains(suf)) {
2✔
134
                splitters.add(suf);
2✔
135
                LOG.debug("added suffix: {}", suf);
2✔
136
            }
137
        }
2✔
138

139
        startLearning();
2✔
140

141
        return hypothesis.size() != oldSize;
2✔
142
    }
143

144
    @Override
145
    public void startLearning() {
146
        // initialize structure to store state output signatures
147
        Map<List<Word<O>>, Integer> signatures = new HashMap<>();
2✔
148

149
        // set up new hypothesis machine
150
        hypothesis = new CompactMealy<>(alphabet);
2✔
151

152
        // initialize exploration queue
153
        Queue<QueueElement<I, O>> queue = new ArrayDeque<>();
2✔
154

155
        // initialize storage for access sequences
156
        accessSequences = hypothesis.createDynamicStateMapping();
2✔
157

158
        // first element to be explored represents the initial state with no predecessor
159
        queue.add(new QueueElement<>(null, null, null, null));
2✔
160

161
        Interner<Word<O>> deduplicator = Interners.newStrongInterner();
2✔
162

163
        while (!queue.isEmpty()) {
2✔
164
            // get element to be explored from queue
165
            @SuppressWarnings("nullness") // false positive https://github.com/typetools/checker-framework/issues/399
166
            @NonNull QueueElement<I, O> elem = queue.poll();
2✔
167

168
            // determine access sequence for state
169
            Word<I> access = assembleAccessSequence(elem);
2✔
170

171
            // assemble queries
172
            ArrayList<DefaultQuery<I, Word<O>>> queries = new ArrayList<>(splitters.size());
2✔
173
            for (Word<I> suffix : splitters) {
2✔
174
                queries.add(new DefaultQuery<>(access, suffix));
2✔
175
            }
2✔
176

177
            // retrieve answers
178
            oracle.processQueries(queries);
2✔
179

180
            // assemble output signature
181
            List<Word<O>> sig = new ArrayList<>(splitters.size());
2✔
182
            for (DefaultQuery<I, Word<O>> query : queries) {
2✔
183
                sig.add(deduplicator.intern(query.getOutput()));
2✔
184
            }
2✔
185

186
            Integer sibling = signatures.get(sig);
2✔
187

188
            if (sibling != null) {
2✔
189
                // this element does not possess a new output signature
190
                // create a transition from parent state to sibling
191
                hypothesis.addTransition(elem.parentState, elem.transIn, sibling, elem.transOut);
2✔
192
            } else {
193
                // this is actually an observably distinct state! Progress!
194
                // Create state and connect via transition to parent
195
                Integer state = elem.parentElement == null ? hypothesis.addInitialState() : hypothesis.addState();
2✔
196
                if (elem.parentElement != null) {
2✔
197
                    hypothesis.addTransition(elem.parentState, elem.transIn, state, elem.transOut);
2✔
198
                }
199
                signatures.put(sig, state);
2✔
200
                accessSequences.put(state, elem);
2✔
201

202
                scheduleSuccessors(elem, state, queue, sig);
2✔
203
            }
204
        }
2✔
205
    }
2✔
206

207
    private Word<I> assembleAccessSequence(QueueElement<I, O> elem) {
208
        List<I> word = new ArrayList<>(elem.depth);
2✔
209

210
        QueueElement<I, O> pre = elem.parentElement;
2✔
211
        I sym = elem.transIn;
2✔
212
        while (pre != null && sym != null) {
2✔
213
            word.add(sym);
2✔
214
            sym = pre.transIn;
2✔
215
            pre = pre.parentElement;
2✔
216
        }
217

218
        Collections.reverse(word);
2✔
219
        return Word.fromList(word);
2✔
220
    }
221

222
    private void scheduleSuccessors(QueueElement<I, O> elem,
223
                                    Integer state,
224
                                    Queue<QueueElement<I, O>> queue,
225
                                    List<Word<O>> sig) {
226
        for (int i = 0; i < alphabet.size(); ++i) {
2✔
227
            // retrieve I/O for transition
228
            I input = alphabet.getSymbol(i);
2✔
229
            O output = sig.get(i).getSymbol(0);
2✔
230

231
            // create successor element and schedule for exploration
232
            queue.add(new QueueElement<>(state, elem, input, output));
2✔
233
        }
234
    }
2✔
235

236
    @Override
237
    public boolean refineHypothesis(DefaultQuery<I, Word<O>> ceQuery) {
238
        checkInternalState();
2✔
239

240
        Collection<Word<I>> ceSuffixes = suffixFinder.findSuffixes(ceQuery, this, hypothesis, oracle);
2✔
241

242
        return addSuffixesUnchecked(ceSuffixes);
2✔
243
    }
244

245
    @Override
246
    public CompactMealy<I, O> getHypothesisModel() {
247
        checkInternalState();
2✔
248
        return hypothesis;
2✔
249
    }
250

251
    @Override
252
    public void addAlphabetSymbol(I symbol) {
253

254
        if (!this.alphabet.containsSymbol(symbol)) {
2✔
255
            Alphabets.toGrowingAlphabetOrThrowException(this.alphabet).addSymbol(symbol);
2✔
256
        }
257

258
        if (!this.splitters.contains(Word.fromLetter(symbol))) {
2✔
259
            final Iterator<Word<I>> splitterIterator = this.splitters.iterator();
2✔
260
            final LinkedHashSet<Word<I>> newSplitters =
2✔
261
                    Sets.newLinkedHashSetWithExpectedSize(this.splitters.size() + 1);
2✔
262

263
            // see initial initialization of the splitters
264
            for (int i = 0; i < this.alphabet.size() - 1; i++) {
2✔
265
                newSplitters.add(splitterIterator.next());
2✔
266
            }
267

268
            newSplitters.add(Word.fromLetter(symbol));
2✔
269

270
            while (splitterIterator.hasNext()) {
2✔
271
                newSplitters.add(splitterIterator.next());
×
272
            }
273

274
            this.splitters = newSplitters;
2✔
275

276
            this.startLearning();
2✔
277
        }
278
    }
2✔
279

280
    @Override
281
    public MealyDHCState<I, O> suspend() {
282
        return new MealyDHCState<>(splitters, hypothesis, accessSequences);
2✔
283
    }
284

285
    @Override
286
    public void resume(MealyDHCState<I, O> state) {
287
        this.splitters = state.getSplitters();
2✔
288
        this.accessSequences = new MapMapping<>(state.getAccessSequences());
2✔
289
        this.hypothesis = state.getHypothesis();
2✔
290
    }
2✔
291

292
    @Override
293
    public Word<I> transformAccessSequence(Word<I> word) {
294
        checkInternalState();
2✔
295
        Integer state = hypothesis.getSuccessor(hypothesis.getInitialState(), word);
2✔
296
        return assembleAccessSequence(accessSequences.get(state));
2✔
297
    }
298

299
    public static final class BuilderDefaults {
300

301
        private BuilderDefaults() {
302
            // prevent instantiation
303
        }
304

305
        public static <I, O> GlobalSuffixFinder<? super I, ? super Word<O>> suffixFinder() {
306
            return GlobalSuffixFinders.RIVEST_SCHAPIRE;
2✔
307
        }
308

309
        public static <I> Collection<Word<I>> initialSplitters() {
310
            return Collections.emptyList();
2✔
311
        }
312
    }
313

314
    static final class QueueElement<I, O> {
315

316
        private final @Nullable Integer parentState;
317
        private final @Nullable QueueElement<I, O> parentElement;
318
        private final @Nullable I transIn;
319
        private final @Nullable O transOut;
320
        private final int depth;
321

322
        QueueElement(@Nullable Integer parentState,
323
                     @Nullable QueueElement<I, O> parentElement,
324
                     @Nullable I transIn,
325
                     @Nullable O transOut) {
2✔
326
            this.parentState = parentState;
2✔
327
            this.parentElement = parentElement;
2✔
328
            this.transIn = transIn;
2✔
329
            this.transOut = transOut;
2✔
330
            this.depth = (parentElement != null) ? parentElement.depth + 1 : 0;
2✔
331
        }
2✔
332
    }
333
}
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