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

LearnLib / learnlib / 21987934897

13 Feb 2026 01:06PM UTC coverage: 95.047% (-0.004%) from 95.051%
21987934897

push

github

web-flow
Add `Resumable` support for remaining Lambda learners (#159)

* initial work on resumable TTTLambda

* add test cases

* do not store refernce to oracle in datastructures

* warn on mismatching alphabets

* add changelog

72 of 74 new or added lines in 13 files covered. (97.3%)

14584 of 15344 relevant lines covered (95.05%)

1.74 hits per line

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

98.96
/algorithms/active/lambda/src/main/java/de/learnlib/algorithm/lambda/ttt/AbstractTTTLambda.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.lambda.ttt;
17

18
import java.util.ArrayDeque;
19
import java.util.Deque;
20
import java.util.List;
21
import java.util.Objects;
22

23
import de.learnlib.Resumable;
24
import de.learnlib.algorithm.LearningAlgorithm;
25
import de.learnlib.algorithm.lambda.ttt.dt.AbstractDecisionTree;
26
import de.learnlib.algorithm.lambda.ttt.dt.DTLeaf;
27
import de.learnlib.algorithm.lambda.ttt.pt.PTNode;
28
import de.learnlib.algorithm.lambda.ttt.pt.PrefixTree;
29
import de.learnlib.algorithm.lambda.ttt.st.SuffixTrie;
30
import de.learnlib.logging.Category;
31
import de.learnlib.oracle.MembershipOracle;
32
import de.learnlib.query.DefaultQuery;
33
import de.learnlib.util.MQUtil;
34
import net.automatalib.alphabet.Alphabet;
35
import net.automatalib.alphabet.SupportsGrowingAlphabet;
36
import net.automatalib.automaton.concept.FiniteRepresentation;
37
import net.automatalib.automaton.concept.SuffixOutput;
38
import net.automatalib.word.Word;
39
import org.checkerframework.checker.nullness.qual.Nullable;
40
import org.slf4j.Logger;
41
import org.slf4j.LoggerFactory;
42

43
public abstract class AbstractTTTLambda<M extends SuffixOutput<I, D>, I, D> implements LearningAlgorithm<M, I, D>,
44
                                                                                       SupportsGrowingAlphabet<I>,
45
                                                                                       Resumable<TTTLambdaState<I, D>>,
46
                                                                                       FiniteRepresentation {
47

48
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTTTLambda.class);
2 ✔
49

50
    private final MembershipOracle<I, D> mqs;
51
    private final MembershipOracle<I, D> ceqs;
52
    protected final Alphabet<I> alphabet;
53
    protected SuffixTrie<I> strie;
54
    protected PrefixTree<I, D> ptree;
55

56
    protected AbstractTTTLambda(Alphabet<I> alphabet, MembershipOracle<I, D> mqs, MembershipOracle<I, D> ceqs) {
2 ✔
57
        this.alphabet = alphabet;
2 ✔
58
        this.mqs = mqs;
2 ✔
59
        this.ceqs = ceqs;
2 ✔
60

61
        this.strie = new SuffixTrie<>();
2 ✔
62
        this.ptree = new PrefixTree<>();
2 ✔
63
    }
2 ✔
64

65
    protected abstract int maxSearchIndex(int ceLength);
66

67
    protected abstract @Nullable DTLeaf<I, D> getState(Word<I> prefix);
68

69
    protected abstract AbstractDecisionTree<I, D> dtree();
70

71
    @Override
72
    public void startLearning() {
73
        assert dtree() != null && getHypothesisModel() != null;
2 ✔
74
        dtree().sift(mqs, ptree.root());
2 ✔
75
        makeConsistent(mqs);
2 ✔
76
    }
2 ✔
77

78
    @Override
79
    public boolean refineHypothesis(DefaultQuery<I, D> counterexample) {
80
        final Deque<DefaultQuery<I, D>> witnesses = new ArrayDeque<>();
2 ✔
81
        witnesses.add(counterexample);
2 ✔
82
        boolean refined = false;
2 ✔
83

84
        while (MQUtil.isCounterexample(counterexample, getHypothesisModel())) {
2 ✔
85
            final DefaultQuery<I, D> witness = witnesses.getFirst();
2 ✔
86

87
            if (witness.getOutput() == null) {
2 ✔
88
                witness.answer(ceqs.answerQuery(witness.getPrefix(), witness.getSuffix()));
2 ✔
89
            }
90

91
            final boolean valid = MQUtil.isCounterexample(witness, getHypothesisModel());
2 ✔
92

93
            if (valid) {
2 ✔
94
                analyzeCounterexample(witness, witnesses);
2 ✔
95
                makeConsistent(mqs);
2 ✔
96
                refined = true;
2 ✔
97
            } else {
98
                witnesses.pop();
2 ✔
99
            }
100
        }
2 ✔
101

102
        assert size() == dtree().leaves().size();
2 ✔
103
        return refined;
2 ✔
104
    }
105

106
    @Override
107
    public void addAlphabetSymbol(I symbol) {
108
        if (!this.alphabet.containsSymbol(symbol)) {
2 ✔
109
            this.alphabet.asGrowingAlphabetOrThrowException().addSymbol(symbol);
2 ✔
110
        }
111

112
        // check if symbol is already part of ptree/hypothesis
113
        if (ptree.root().succ(symbol) == null) {
2 ✔
114

115
            List<DTLeaf<I, D>> leaves = dtree().leaves();
2 ✔
116

117
            for (DTLeaf<I, D> leaf : leaves) {
2 ✔
118
                PTNode<I, D> u = leaf.getShortPrefixes().get(0);
2 ✔
119
                assert u != null;
2 ✔
120
                PTNode<I, D> ua = u.append(symbol);
2 ✔
121
                assert ua != null;
2 ✔
122
                dtree().sift(mqs, ua);
2 ✔
123
            }
2 ✔
124

125
            makeConsistent(mqs);
2 ✔
126
        }
127
    }
2 ✔
128

129
    protected void makeConsistent(MembershipOracle<I, D> oracle) {
130
        while (dtree().makeConsistent(oracle)) {
2 ✔
131
            // do nothing ...
132
        }
133
    }
2 ✔
134

135
    private PTNode<I, D> longestShortPrefixOf(Word<I> ce) {
136
        PTNode<I, D> cur = ptree.root();
2 ✔
137
        int i = 0;
2 ✔
138
        do {
139
            cur = cur.succ(ce.getSymbol(i++));
2 ✔
140
            assert cur != null;
2 ✔
141
        } while (cur.state().getShortPrefixes().contains(cur) && i < ce.length());
2 ✔
142

143
        assert !cur.state().getShortPrefixes().contains(cur);
2 ✔
144
        return cur;
2 ✔
145
    }
146

147
    private void analyzeCounterexample(DefaultQuery<I, D> counterexample, Deque<DefaultQuery<I, D>> witnesses) {
148
        M hyp = getHypothesisModel();
2 ✔
149
        Word<I> ce = counterexample.getInput();
2 ✔
150
        PTNode<I, D> ua = null;
2 ✔
151
        PTNode<I, D> lsp = longestShortPrefixOf(ce);
2 ✔
152
        int upper = maxSearchIndex(ce.length());
2 ✔
153
        int lower = lsp.word().length() - 1;
2 ✔
154
        while (upper - lower > 1) {
2 ✔
155
            int mid = (upper + lower) / 2;
2 ✔
156
            Word<I> prefix = ce.prefix(mid);
2 ✔
157
            Word<I> suffix = ce.suffix(ce.length() - mid);
2 ✔
158

159
            DTLeaf<I, D> q = getState(prefix);
2 ✔
160
            assert q != null;
2 ✔
161

162
            boolean stillCe = false;
2 ✔
163
            for (PTNode<I, D> u : q.getShortPrefixes()) {
2 ✔
164
                D sysOut = ceqs.answerQuery(u.word(), suffix);
2 ✔
165
                D hypOut = hyp.computeSuffixOutput(u.word(), suffix);
2 ✔
166
                if (!Objects.equals(sysOut, hypOut)) {
2 ✔
167
                    ua = u.succ(suffix.firstSymbol());
2 ✔
168
                    lower = mid;
2 ✔
169
                    stillCe = true;
2 ✔
170
                    break;
2 ✔
171
                }
172
            }
2 ✔
173
            if (stillCe) {
2 ✔
174
                continue;
2 ✔
175
            }
176
            upper = mid;
2 ✔
177
        }
2 ✔
178

179
        if (ua == null) {
2 ✔
180
            assert lower == lsp.word().length() - 1;
2 ✔
181
            ua = lsp;
2 ✔
182
        }
183

184
        // add witnesses
185
        int mid = (upper + lower) / 2;
2 ✔
186
        Word<I> sprime = ce.suffix(ce.length() - (mid + 1));
2 ✔
187
        DTLeaf<I, D> qnext = getState(ua.word());
2 ✔
188
        assert qnext != null;
2 ✔
189
        for (PTNode<I, D> uprime : qnext.getShortPrefixes()) {
2 ✔
190
            witnesses.push(new DefaultQuery<>(uprime.word(), sprime));
2 ✔
191
        }
2 ✔
192
        witnesses.push(new DefaultQuery<>(ua.word(), sprime));
2 ✔
193

194
        ua.makeShortPrefix(mqs);
2 ✔
195
    }
2 ✔
196

197
    @Override
198
    public TTTLambdaState<I, D> suspend() {
199
        return new TTTLambdaState<>(strie, ptree, dtree());
2 ✔
200
    }
201

202
    @Override
203
    public void resume(TTTLambdaState<I, D> state) {
204
        this.strie = state.strie;
2 ✔
205
        this.ptree = state.ptree;
2 ✔
206

207
        final Alphabet<I> oldAlphabet = state.dtree.getAlphabet();
2 ✔
208
        if (!this.alphabet.equals(oldAlphabet)) {
2 ✔
NEW
209
            LOGGER.warn(Category.DATASTRUCTURE,
×
210
                        "The current alphabet '{}' differs from the resumed alphabet '{}'. Future behavior may be inconsistent",
211
                        this.alphabet,
212
                        oldAlphabet);
213
        }
214
    }
2 ✔
215
}
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