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

LearnLib / learnlib / 31619759710

12 Aug 2026 04:27PM UTC coverage: 95.488% (+1.1%) from 94.368%
31619759710

push

github

mtf90
use new version scheme

15533 of 16267 relevant lines covered (95.49%)

1.72 hits per line

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

91.21
/algorithms/active/lsharp/src/main/java/de/learnlib/algorithm/lsharp/LSOracle.java
1
/* Copyright (C) 2013-2026 TU Dortmund University
2
 * This file is part of LearnLib <https://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.algorithm.lsharp;
17

18
import java.util.ArrayList;
19
import java.util.Collection;
20
import java.util.Collections;
21
import java.util.List;
22
import java.util.Objects;
23
import java.util.Random;
24

25
import de.learnlib.algorithm.lsharp.ads.ADSTree;
26
import de.learnlib.oracle.AdaptiveMembershipOracle;
27
import de.learnlib.query.AdaptiveQuery;
28
import de.learnlib.util.mealy.WordAdaptiveQuery;
29
import net.automatalib.common.util.Pair;
30
import net.automatalib.word.Word;
31
import net.automatalib.word.WordBuilder;
32
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
33
import org.checkerframework.checker.nullness.qual.Nullable;
34

35
public class LSOracle<I, O> {
2✔
36

37
    private final AdaptiveMembershipOracle<I, O> sul;
38
    private final NormalObservationTree<I, O> obsTree;
39
    private final Rule2 rule2;
40
    private final Rule3 rule3;
41
    private @MonotonicNonNull Word<I> sinkState;
42
    private final O sinkOutput;
43
    private final Random random;
44

45
    public LSOracle(AdaptiveMembershipOracle<I, O> sul,
46
                    NormalObservationTree<I, O> obsTree,
47
                    Rule2 rule2,
48
                    Rule3 rule3,
49
                    @Nullable Word<I> sinkState,
50
                    O sinkOutput,
51
                    Random random) {
2✔
52
        this.sul = sul;
2✔
53
        this.obsTree = obsTree;
2✔
54
        this.rule2 = rule2;
2✔
55
        this.rule3 = rule3;
2✔
56
        this.sinkState = sinkState;
2✔
57
        this.sinkOutput = sinkOutput;
2✔
58
        this.random = random;
2✔
59
    }
2✔
60

61
    public NormalObservationTree<I, O> getTree() {
62
        return this.obsTree;
2✔
63
    }
64

65
    public void makeSink(Integer s) {
66
        for (I i : obsTree.getInputAlphabet()) {
×
67
            obsTree.insertObservation(s, Word.fromLetter(i), Word.fromLetter(sinkOutput));
×
68
        }
×
69
    }
×
70

71
    public Integer addObservation(Word<I> i, Word<O> o) {
72
        return obsTree.insertObservation(null, i, o);
2✔
73
    }
74

75
    private <T> List<T> sample2(Collection<T> collection) {
76
        List<T> shuffled = new ArrayList<>(collection);
2✔
77
        Collections.shuffle(shuffled, random);
2✔
78
        return shuffled.subList(0, 2);
2✔
79
    }
80

81
    @SuppressWarnings("PMD.SwitchDensity")
82
    private Pair<Word<I>, Word<O>> rule3IO(List<Word<I>> candidates, Word<I> prefix) {
83
        return switch (this.rule3) {
2✔
84
            case ADS:
85
                if (candidates.size() == 2) {
2✔
86
                    Word<I> q1Acc = candidates.get(0);
2✔
87
                    Word<I> q2Acc = candidates.get(1);
2✔
88
                    Integer q1 = obsTree.getSucc(obsTree.defaultState(), q1Acc);
2✔
89
                    Integer q2 = obsTree.getSucc(obsTree.defaultState(), q2Acc);
2✔
90
                    assert q1 != null;
2✔
91
                    assert q2 != null;
2✔
92

93
                    Word<I> wit = ApartnessUtil.computeWitness(obsTree, q1, q2);
2✔
94
                    assert wit != null;
2✔
95

96
                    assert !(ApartnessUtil.accStatesAreApart(obsTree, prefix, q1Acc) ||
2✔
97
                             ApartnessUtil.accStatesAreApart(obsTree, prefix, q2Acc));
2✔
98

99
                    Word<I> inputSeq = prefix.concat(wit);
2✔
100
                    Word<O> outputSeq = this.outputQuery(inputSeq);
2✔
101

102
                    yield Pair.of(inputSeq, outputSeq);
2✔
103
                } else {
104
                    List<Integer> candss = getSuccs(candidates);
2✔
105
                    ADSTree<Integer, I, O> suffix = new ADSTree<>(obsTree, candss, sinkOutput);
2✔
106
                    yield this.adaptiveOutputQuery(prefix, null, suffix);
2✔
107
                }
108
            case SEPSEQ:
109
                List<Integer> withS = getSuccs(sample2(candidates));
2✔
110
                Word<I> wit = ApartnessUtil.computeWitness(obsTree, withS.get(0), withS.get(1));
2✔
111
                assert wit != null;
2✔
112
                Word<I> inputSeq = prefix.concat(wit);
2✔
113
                yield Pair.of(inputSeq, this.outputQuery(inputSeq));
2✔
114
        };
115
    }
116

117
    private List<Integer> getSuccs(Collection<Word<I>> candidates) {
118
        List<Integer> result = new ArrayList<>(candidates.size());
2✔
119
        for (Word<I> c : candidates) {
2✔
120
            Integer succ = obsTree.getSucc(obsTree.defaultState(), c);
2✔
121
            assert succ != null;
2✔
122
            result.add(succ);
2✔
123
        }
2✔
124
        return result;
2✔
125
    }
126

127
    public List<Word<I>> identifyFrontier(Word<I> fsAcc, List<Word<I>> candidates) {
128
        Integer fs = obsTree.getSucc(obsTree.defaultState(), fsAcc);
2✔
129
        assert fs != null;
2✔
130
        candidates.removeIf(b -> {
2✔
131
            Integer bs = obsTree.getSucc(obsTree.defaultState(), b);
2✔
132
            assert bs != null;
2✔
133
            return ApartnessUtil.statesAreApart(obsTree, fs, bs);
2✔
134
        });
135

136
        int orgCandLen = candidates.size();
2✔
137
        if (orgCandLen < 2) {
2✔
138
            return candidates;
2✔
139
        }
140

141
        Pair<Word<I>, Word<O>> pair = rule3IO(candidates, fsAcc);
2✔
142
        obsTree.insertObservation(null, pair.getFirst(), pair.getSecond());
2✔
143
        candidates.removeIf(b -> ApartnessUtil.accStatesAreApart(obsTree, fsAcc, b));
2✔
144
        assert candidates.size() != orgCandLen;
2✔
145
        return candidates;
2✔
146
    }
147

148
    private Pair<Word<I>, Word<O>> rule2IO(Word<I> accessQ, I i, List<Integer> bss, Collection<Word<I>> basis) {
149
        return switch (this.rule2) {
2✔
150
            case ADS:
151
                ADSTree<Integer, I, O> suffix = new ADSTree<>(obsTree, bss, sinkOutput);
2✔
152
                yield this.adaptiveOutputQuery(accessQ, i, suffix);
2✔
153
            case NOTHING:
154
                Word<I> prefix = accessQ.append(i);
2✔
155
                Word<O> oSeq = this.outputQuery(prefix);
2✔
156
                yield Pair.of(prefix, oSeq);
2✔
157
            case SEPSEQ:
158
                Word<I> wit;
159
                if (basis.size() >= 2) {
2✔
160
                    List<Integer> ran = getSuccs(sample2(basis));
2✔
161
                    wit = ApartnessUtil.computeWitness(obsTree, ran.get(0), ran.get(1));
2✔
162
                    assert wit != null;
2✔
163
                } else {
2✔
164
                    wit = Word.epsilon();
2✔
165
                }
166
                Word<I> inputSeq = accessQ.append(i).concat(wit);
2✔
167
                Word<O> outputSeq = this.outputQuery(inputSeq);
2✔
168
                yield Pair.of(inputSeq, outputSeq);
2✔
169
        };
170
    }
171

172
    public List<Pair<Word<I>, List<Word<I>>>> exploreFrontier(Collection<Word<I>> basis) {
173
        List<Pair<Word<I>, List<Word<I>>>> frontier = new ArrayList<>();
2✔
174
        for (Word<I> b : basis) {
2✔
175
            for (I i : obsTree.getInputAlphabet()) {
2✔
176
                Integer bs = obsTree.getSucc(obsTree.defaultState(), b);
2✔
177
                assert bs != null;
2✔
178
                if (obsTree.getSucc(bs, Word.fromLetter(i)) == null) {
2✔
179
                    frontier.add(this.exploreFrontier(b, i, basis));
2✔
180
                }
181
            }
2✔
182
        }
2✔
183
        return frontier;
2✔
184
    }
185

186
    public Pair<Word<I>, List<Word<I>>> exploreFrontier(Word<I> accQ, I i, Collection<Word<I>> basis) {
187
        Word<I> accessQ = Word.fromWords(accQ);
2✔
188
        Integer q = obsTree.getSucc(obsTree.defaultState(), accQ);
2✔
189
        assert q != null;
2✔
190
        List<Integer> bss = getSuccs(basis);
2✔
191
        Pair<Word<I>, Word<O>> query = rule2IO(accessQ, i, bss, basis);
2✔
192
        Word<I> inputSeq = query.getFirst();
2✔
193
        Word<O> outputSeq = query.getSecond();
2✔
194

195
        obsTree.insertObservation(null, inputSeq, outputSeq);
2✔
196
        Integer fs = obsTree.getSucc(q, Word.fromLetter(i));
2✔
197
        assert fs != null;
2✔
198
        List<Word<I>> bsNotSep = new ArrayList<>(basis.size());
2✔
199
        for (Word<I> b : basis) {
2✔
200
            Integer bs = obsTree.getSucc(obsTree.defaultState(), b);
2✔
201
            assert bs != null;
2✔
202
            if (!ApartnessUtil.statesAreApart(obsTree, fs, bs)) {
2✔
203
                bsNotSep.add(b);
2✔
204
            }
205
        }
2✔
206
        return Pair.of(accQ.append(i), bsNotSep);
2✔
207
    }
208

209
    public Word<O> outputQuery(Word<I> inputSeq) {
210
        Word<O> out = obsTree.getObservation(null, inputSeq);
2✔
211
        if (out != null) {
2✔
212
            return out;
2✔
213
        }
214

215
        final WordAdaptiveQuery<I, O> query = new WordAdaptiveQuery<>(inputSeq);
2✔
216
        sul.processQuery(query);
2✔
217
        out = query.getOutput();
2✔
218

219
        if (sinkState == null && Objects.equals(out.lastSymbol(), sinkOutput)) {
2✔
220
            sinkState = inputSeq;
×
221
        }
222

223
        this.addObservation(inputSeq, out);
2✔
224
        return out;
2✔
225
    }
226

227
    public Pair<Word<I>, Word<O>> adaptiveOutputQuery(Word<I> prefix,
228
                                                      @Nullable I infix,
229
                                                      ADSTree<Integer, I, O> suffix) {
230
        return this.adaptiveOutputQuery(infix != null ? prefix.append(infix) : prefix, suffix);
2✔
231
    }
232

233
    public Pair<Word<I>, Word<O>> adaptiveOutputQuery(Word<I> prefix, ADSTree<Integer, I, O> suffix) {
234
        Pair<Word<I>, Word<O>> treeReply = null;
2✔
235
        Integer treeSucc = obsTree.getSucc(obsTree.defaultState(), prefix);
2✔
236
        if (treeSucc != null) {
2✔
237
            treeReply = this.answerADSFromTree(suffix, treeSucc);
2✔
238
        }
239

240
        suffix.resetToRoot();
2✔
241
        if (treeReply != null) {
2✔
242
            throw new IllegalStateException("ADS is not increasing the norm, we already knew this information.");
×
243
        }
244

245
        ADSTreeQuery query = new ADSTreeQuery(prefix, suffix);
2✔
246
        sul.processQuery(query);
2✔
247

248
        if (query.isSink()) {
2✔
249
            Word<O> output = query.getOutputSequence();
×
250
            Integer sink = this.addObservation(prefix, output);
×
251
            if (sinkState == null) {
×
252
                sinkState = prefix;
×
253
            }
254
            this.makeSink(sink);
×
255
            return Pair.of(prefix, output);
×
256
        }
257
        Word<I> inputSeq = query.getInputSequence();
2✔
258
        Word<O> outputSeq = query.getOutputSequence();
2✔
259
        this.addObservation(inputSeq, outputSeq);
2✔
260
        return Pair.of(inputSeq, outputSeq);
2✔
261
    }
262

263
    public @Nullable Pair<Word<I>, Word<O>> answerADSFromTree(ADSTree<Integer, I, O> ads, Integer fromState) {
264
        WordBuilder<I> inputsSent = new WordBuilder<>();
2✔
265
        WordBuilder<O> outputsReceived = new WordBuilder<>();
2✔
266
        Integer currState = fromState;
2✔
267

268
        O prevOutput;
269
        I nextInput = ads.nextInput(null);
2✔
270
        while (nextInput != null) {
2✔
271
            inputsSent.add(nextInput);
2✔
272
            Pair<O, Integer> pair = obsTree.getOutSucc(currState, nextInput);
2✔
273
            if (pair == null) {
2✔
274
                return null;
2✔
275
            }
276
            prevOutput = pair.getFirst();
2✔
277
            outputsReceived.add(pair.getFirst());
2✔
278
            currState = pair.getSecond();
2✔
279

280
            nextInput = ads.nextInput(prevOutput);
2✔
281
        }
2✔
282

283
        ads.resetToRoot();
×
284
        return Pair.of(inputsSent.toWord(), outputsReceived.toWord());
×
285
    }
286

287
    private class ADSTreeQuery implements AdaptiveQuery<I, O> {
288

289
        private final Word<I> prefix;
290
        private final ADSTree<Integer, I, O> ads;
291
        private final WordBuilder<I> input;
292
        private final WordBuilder<O> output;
293

294
        private int idx;
295
        private I adsSym;
296
        private boolean sink;
297

298
        ADSTreeQuery(Word<I> prefix, ADSTree<Integer, I, O> ads) {
2✔
299
            this.prefix = prefix;
2✔
300
            this.ads = ads;
2✔
301
            this.input = new WordBuilder<>();
2✔
302
            this.output = new WordBuilder<>();
2✔
303
        }
2✔
304

305
        @Override
306
        public I getInput() {
307
            if (idx < prefix.length()) {
2✔
308
                return prefix.getSymbol(idx);
2✔
309
            } else {
310
                return adsSym;
2✔
311
            }
312
        }
313

314
        @Override
315
        public Response processOutput(O out) {
316
            idx++;
2✔
317
            output.append(out);
2✔
318

319
            if (idx < prefix.length()) {
2✔
320
                return Response.SYMBOL;
2✔
321
            } else if (idx == prefix.length()) {
2✔
322
                if (Objects.equals(out, sinkOutput)) {
2✔
323
                    sink = true;
×
324
                    return Response.FINISHED;
×
325
                } else {
326
                    input.append(prefix);
2✔
327
                    return computeNext(null);
2✔
328
                }
329
            } else {
330
                return computeNext(out);
2✔
331
            }
332
        }
333

334
        private Response computeNext(@Nullable O out) {
335
            final I next = ads.nextInput(out);
2✔
336
            if (next == null) {
2✔
337
                return Response.FINISHED;
2✔
338
            } else {
339
                adsSym = next;
2✔
340
                input.append(next);
2✔
341
                return Response.SYMBOL;
2✔
342
            }
343
        }
344

345
        boolean isSink() {
346
            return sink;
2✔
347
        }
348

349
        Word<I> getInputSequence() {
350
            return input.toWord();
2✔
351
        }
352

353
        Word<O> getOutputSequence() {
354
            return output.toWord();
2✔
355
        }
356
    }
357

358
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc