• 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

97.03
/algorithms/active/ttt/src/main/java/de/learnlib/algorithms/ttt/base/AbstractTTTLearner.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.ttt.base;
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.Deque;
23
import java.util.HashMap;
24
import java.util.HashSet;
25
import java.util.Iterator;
26
import java.util.List;
27
import java.util.Map;
28
import java.util.Objects;
29
import java.util.Set;
30

31
import com.google.common.collect.Iterators;
32
import de.learnlib.acex.AcexAnalyzer;
33
import de.learnlib.acex.analyzers.AcexAnalyzers;
34
import de.learnlib.api.AccessSequenceProvider;
35
import de.learnlib.api.Resumable;
36
import de.learnlib.api.algorithm.LearningAlgorithm;
37
import de.learnlib.api.oracle.MembershipOracle;
38
import de.learnlib.api.query.DefaultQuery;
39
import de.learnlib.counterexamples.acex.OutInconsPrefixTransformAcex;
40
import de.learnlib.datastructure.discriminationtree.SplitData;
41
import net.automatalib.SupportsGrowingAlphabet;
42
import net.automatalib.automata.fsa.DFA;
43
import net.automatalib.commons.smartcollections.ElementReference;
44
import net.automatalib.commons.smartcollections.UnorderedCollection;
45
import net.automatalib.words.Alphabet;
46
import net.automatalib.words.Word;
47
import net.automatalib.words.impl.Alphabets;
48
import org.slf4j.Logger;
49
import org.slf4j.LoggerFactory;
50

51
/**
52
 * The TTT learning algorithm for {@link DFA}.
53
 *
54
 * @param <I>
55
 *         input symbol type
56
 */
57
@SuppressWarnings("PMD.ExcessiveClassLength")
58
public abstract class AbstractTTTLearner<A, I, D>
59
        implements LearningAlgorithm<A, I, D>, SupportsGrowingAlphabet<I>, Resumable<TTTLearnerState<I, D>> {
60

61
    private static final Logger LOGGER = LoggerFactory.getLogger(AbstractTTTLearner.class);
2✔
62

63
    protected final Alphabet<I> alphabet;
64
    protected final MembershipOracle<I, D> oracle;
65
    protected final AcexAnalyzer analyzer;
66
    /**
67
     * Open transitions, i.e., transitions that possibly point to a non-leaf node in the discrimination tree.
68
     */
69
    protected final IncomingList<I, D> openTransitions = new IncomingList<>();
2✔
70
    /**
71
     * The blocks during a split operation. A block is a maximal subtree of the discrimination tree containing temporary
72
     * discriminators at its root.
73
     */
74
    protected final BlockList<I, D> blockList = new BlockList<>();
2✔
75
    protected AbstractTTTHypothesis<?, I, D, ?> hypothesis;
76
    protected BaseTTTDiscriminationTree<I, D> dtree;
77

78
    protected AbstractTTTLearner(Alphabet<I> alphabet,
79
                                 MembershipOracle<I, D> oracle,
80
                                 AbstractTTTHypothesis<?, I, D, ?> hypothesis,
81
                                 BaseTTTDiscriminationTree<I, D> dtree,
82
                                 AcexAnalyzer analyzer) {
2✔
83
        this.alphabet = alphabet;
2✔
84
        this.hypothesis = hypothesis;
2✔
85
        this.oracle = oracle;
2✔
86
        this.dtree = dtree;
2✔
87
        this.analyzer = analyzer;
2✔
88
    }
2✔
89

90
    /**
91
     * Marks a node, and propagates the label up to all nodes on the path from the block root to this node.
92
     *
93
     * @param node
94
     *         the node to mark
95
     * @param label
96
     *         the label to mark the node with
97
     */
98
    private static <I, D> void markAndPropagate(AbstractBaseDTNode<I, D> node, D label) {
99
        AbstractBaseDTNode<I, D> curr = node;
2✔
100

101
        while (curr != null && curr.getSplitData() != null) {
2✔
102
            if (!curr.getSplitData().mark(label)) {
2✔
103
                return;
2✔
104
            }
105
            curr = curr.getParent();
2✔
106
        }
107
    }
2✔
108

109
    /**
110
     * Moves all transition from the "incoming" list (for a given label) of an old node to the "incoming" list of a new
111
     * node.
112
     *
113
     * @param newNode
114
     *         the new node
115
     * @param oldNode
116
     *         the old node
117
     * @param label
118
     *         the label to consider
119
     */
120
    private static <I, D> void moveIncoming(AbstractBaseDTNode<I, D> newNode,
121
                                            AbstractBaseDTNode<I, D> oldNode,
122
                                            D label) {
123
        newNode.getIncoming().insertAllIncoming(oldNode.getSplitData().getIncoming(label));
2✔
124
    }
2✔
125

126
    /**
127
     * Establish the connection between a node in the discrimination tree and a state of the hypothesis.
128
     *
129
     * @param dtNode
130
     *         the node in the discrimination tree
131
     * @param state
132
     *         the state in the hypothesis
133
     */
134
    protected static <I, D> void link(AbstractBaseDTNode<I, D> dtNode, TTTState<I, D> state) {
135
        assert dtNode.isLeaf();
2✔
136

137
        dtNode.setData(state);
2✔
138
        state.dtLeaf = dtNode;
2✔
139
    }
2✔
140

141
    /*
142
     * Private helper methods.
143
     */
144

145
    @Override
146
    public void startLearning() {
147
        if (hypothesis.isInitialized()) {
2✔
148
            throw new IllegalStateException();
×
149
        }
150

151
        TTTState<I, D> init = hypothesis.initialize();
2✔
152
        AbstractBaseDTNode<I, D> initNode = dtree.sift(init.getAccessSequence(), false);
2✔
153
        link(initNode, init);
2✔
154
        initializeState(init);
2✔
155

156
        closeTransitions();
2✔
157
    }
2✔
158

159
    @Override
160
    public boolean refineHypothesis(DefaultQuery<I, D> ceQuery) {
161
        if (!refineHypothesisSingle(ceQuery)) {
2✔
162
            return false;
2✔
163
        }
164

165
        while (refineHypothesisSingle(ceQuery)) {}
2✔
166

167
        return true;
2✔
168
    }
169

170
    /**
171
     * Initializes a state. Creates its outgoing transition objects, and adds them to the "open" list.
172
     *
173
     * @param state
174
     *         the state to initialize
175
     */
176
    protected void initializeState(TTTState<I, D> state) {
177
        for (int i = 0; i < alphabet.size(); i++) {
2✔
178
            I sym = alphabet.getSymbol(i);
2✔
179
            TTTTransition<I, D> trans = createTransition(state, sym);
2✔
180
            trans.setNonTreeTarget(dtree.getRoot());
2✔
181
            state.setTransition(i, trans);
2✔
182
            openTransitions.insertIncoming(trans);
2✔
183
        }
184
    }
2✔
185

186
    protected TTTTransition<I, D> createTransition(TTTState<I, D> state, I sym) {
187
        return new TTTTransition<>(state, sym);
2✔
188
    }
189

190
    /**
191
     * Performs a single refinement of the hypothesis, i.e., without repeated counterexample evaluation. The parameter
192
     * and return value have the same significance as in {@link #refineHypothesis(DefaultQuery)}.
193
     *
194
     * @param ceQuery
195
     *         the counterexample (query) to be used for refinement
196
     *
197
     * @return {@code true} if the hypothesis was refined, {@code false} otherwise
198
     */
199
    protected boolean refineHypothesisSingle(DefaultQuery<I, D> ceQuery) {
200
        TTTState<I, D> state = getAnyState(ceQuery.getPrefix());
2✔
201
        D out = computeHypothesisOutput(state, ceQuery.getSuffix());
2✔
202

203
        if (Objects.equals(out, ceQuery.getOutput())) {
2✔
204
            return false;
2✔
205
        }
206

207
        OutputInconsistency<I, D> outIncons =
2✔
208
                new OutputInconsistency<>(state, ceQuery.getSuffix(), ceQuery.getOutput());
2✔
209

210
        do {
211
            splitState(outIncons);
2✔
212
            closeTransitions();
2✔
213
            while (finalizeAny()) {
2✔
214
                closeTransitions();
2✔
215
            }
216

217
            outIncons = findOutputInconsistency();
2✔
218
        } while (outIncons != null);
2✔
219
        assert allNodesFinal();
2✔
220

221
        return true;
2✔
222
    }
223

224
    /**
225
     * Splits a state in the hypothesis, using a temporary discriminator. The state to be split is identified by an
226
     * incoming non-tree transition. This transition is subsequently turned into a spanning tree transition.
227
     *
228
     * @param transition
229
     *         the transition
230
     * @param tempDiscriminator
231
     *         the temporary discriminator
232
     */
233
    private void splitState(TTTTransition<I, D> transition, Word<I> tempDiscriminator, D oldOut, D newOut) {
234
        assert !transition.isTree();
2✔
235

236
        AbstractBaseDTNode<I, D> dtNode = transition.getNonTreeTarget();
2✔
237
        assert dtNode.isLeaf();
2✔
238
        TTTState<I, D> oldState = dtNode.getData();
2✔
239
        assert oldState != null;
2✔
240

241
        TTTState<I, D> newState = makeTree(transition);
2✔
242

243
        AbstractBaseDTNode<I, D>.SplitResult children = split(dtNode, tempDiscriminator, oldOut, newOut);
2✔
244
        dtNode.setTemp(true);
2✔
245

246
        link(children.nodeOld, oldState);
2✔
247
        link(children.nodeNew, newState);
2✔
248

249
        if (dtNode.getParent() == null || !dtNode.getParent().isTemp()) {
2✔
250
            blockList.insertBlock(dtNode);
2✔
251
        }
252
    }
2✔
253

254
    private void splitState(OutputInconsistency<I, D> outIncons) {
255

256
        OutInconsPrefixTransformAcex<I, D> acex = deriveAcex(outIncons);
2✔
257
        try {
258
            int breakpoint = analyzer.analyzeAbstractCounterexample(acex);
2✔
259
            assert !acex.testEffects(breakpoint, breakpoint + 1);
2✔
260

261
            Word<I> suffix = outIncons.suffix;
2✔
262

263
            TTTState<I, D> predState = getDeterministicState(outIncons.srcState, suffix.prefix(breakpoint));
2✔
264
            TTTState<I, D> succState = getDeterministicState(outIncons.srcState, suffix.prefix(breakpoint + 1));
2✔
265
            assert getDeterministicState(predState, Word.fromLetter(suffix.getSymbol(breakpoint))) == succState;
2✔
266

267
            I sym = suffix.getSymbol(breakpoint);
2✔
268
            Word<I> splitSuffix = suffix.subWord(breakpoint + 1);
2✔
269
            TTTTransition<I, D> trans = predState.getTransition(alphabet.getSymbolIndex(sym));
2✔
270
            assert !trans.isTree();
2✔
271
            D oldOut = acex.effect(breakpoint + 1);
2✔
272
            D newOut = succEffect(acex.effect(breakpoint));
2✔
273

274
            splitState(trans, splitSuffix, oldOut, newOut);
2✔
275
        } catch (HypothesisChangedException ignored) {
×
276
            // ignore
277
        }
2✔
278
    }
2✔
279

280
    protected OutInconsPrefixTransformAcex<I, D> deriveAcex(OutputInconsistency<I, D> outIncons) {
281
        TTTState<I, D> source = outIncons.srcState;
2✔
282
        Word<I> suffix = outIncons.suffix;
2✔
283

284
        OutInconsPrefixTransformAcex<I, D> acex = new OutInconsPrefixTransformAcex<>(suffix,
2✔
285
                                                                                     oracle,
286
                                                                                     w -> getDeterministicState(source,
2✔
287
                                                                                                                w).getAccessSequence());
2✔
288

289
        acex.setEffect(0, outIncons.targetOut);
2✔
290
        return acex;
2✔
291
    }
292

293
    protected abstract D succEffect(D effect);
294

295
    /**
296
     * Chooses a block root, and finalizes the corresponding discriminator.
297
     *
298
     * @return {@code true} if a splittable block root was found, {@code false} otherwise.
299
     */
300
    protected boolean finalizeAny() {
301
        GlobalSplitter<I, D> splitter = findSplitterGlobal();
2✔
302
        if (splitter != null) {
2✔
303
            finalizeDiscriminator(splitter.blockRoot, splitter.localSplitter);
2✔
304
            return true;
2✔
305
        }
306
        return false;
2✔
307
    }
308

309
    protected TTTState<I, D> getDeterministicState(TTTState<I, D> start, Word<I> word) {
310
        TTTState<I, D> lastSingleton = start;
2✔
311
        int lastSingletonIndex = 0;
2✔
312

313
        Set<TTTState<I, D>> states = Collections.singleton(start);
2✔
314
        int i = 1;
2✔
315
        for (I sym : word) {
2✔
316
            Set<TTTState<I, D>> nextStates = getNondetSuccessors(states, sym);
2✔
317
            if (nextStates.size() == 1) {
2✔
318
                lastSingleton = nextStates.iterator().next();
2✔
319
                lastSingletonIndex = i;
2✔
320
            }
321
            states = nextStates;
2✔
322

323
            i++;
2✔
324
        }
2✔
325
        if (lastSingletonIndex == word.length()) {
2✔
326
            return lastSingleton;
2✔
327
        }
328

329
        TTTState<I, D> curr = lastSingleton;
2✔
330
        for (I sym : word.subWord(lastSingletonIndex)) {
2✔
331
            TTTTransition<I, D> trans = curr.getTransition(alphabet.getSymbolIndex(sym));
2✔
332
            curr = requireSuccessor(trans);
2✔
333
        }
2✔
334

335
        return curr;
2✔
336
    }
337

338
    protected Set<TTTState<I, D>> getNondetSuccessors(Collection<? extends TTTState<I, D>> states, I sym) {
339
        Set<TTTState<I, D>> result = new HashSet<>();
2✔
340
        int symIdx = alphabet.getSymbolIndex(sym);
2✔
341
        for (TTTState<I, D> state : states) {
2✔
342
            TTTTransition<I, D> trans = state.getTransition(symIdx);
2✔
343
            if (trans.isTree()) {
2✔
344
                result.add(trans.getTreeTarget());
2✔
345
            } else {
346
                AbstractBaseDTNode<I, D> tgtNode = trans.getNonTreeTarget();
2✔
347
                Iterators.addAll(result, tgtNode.subtreeStatesIterator());
2✔
348
            }
349
        }
2✔
350
        return result;
2✔
351
    }
352

353
    protected TTTState<I, D> getAnySuccessor(TTTState<I, D> state, I sym) {
354
        int symIdx = alphabet.getSymbolIndex(sym);
2✔
355
        TTTTransition<I, D> trans = state.getTransition(symIdx);
2✔
356
        if (trans.isTree()) {
2✔
357
            return trans.getTreeTarget();
2✔
358
        }
359
        return trans.getNonTreeTarget().subtreeStatesIterator().next();
2✔
360
    }
361

362
    protected TTTState<I, D> getAnySuccessor(TTTState<I, D> state, Iterable<? extends I> suffix) {
363
        TTTState<I, D> curr = state;
2✔
364
        for (I sym : suffix) {
2✔
365
            curr = getAnySuccessor(curr, sym);
2✔
366
        }
2✔
367
        return curr;
2✔
368
    }
369

370
    protected TTTTransition<I, D> getStateTransition(TTTState<I, D> state, I sym) {
371
        int idx = alphabet.getSymbolIndex(sym);
×
372
        return state.getTransition(idx);
×
373
    }
374

375
    private TTTState<I, D> requireSuccessor(TTTTransition<I, D> trans) {
376
        if (trans.isTree()) {
2✔
377
            return trans.getTreeTarget();
2✔
378
        }
379
        AbstractBaseDTNode<I, D> newTgtNode = updateDTTarget(trans, true);
2✔
380
        if (newTgtNode.getData() == null) {
2✔
381
            makeTree(trans);
×
382
            closeTransitions();
×
383
            // FIXME: using exception handling for this is not very nice, but it appears there
384
            // is no quicker way to abort counterexample analysis
385
            throw new HypothesisChangedException();
×
386
        }
387
        return newTgtNode.getData();
2✔
388
    }
389

390
    /**
391
     * Determines a global splitter, i.e., a splitter for any block. This method may (but is not required to) employ
392
     * heuristics to obtain a splitter with a relatively short suffix length.
393
     *
394
     * @return a splitter for any of the blocks
395
     */
396
    private GlobalSplitter<I, D> findSplitterGlobal() {
397
        // TODO: Make global option
398
        boolean optimizeGlobal = true;
2✔
399

400
        AbstractBaseDTNode<I, D> bestBlockRoot = null;
2✔
401

402
        Splitter<I, D> bestSplitter = null;
2✔
403

404
        for (AbstractBaseDTNode<I, D> blockRoot : blockList) {
2✔
405
            Splitter<I, D> splitter = findSplitter(blockRoot);
2✔
406

407
            if (splitter != null) {
2✔
408
                if (bestSplitter == null || splitter.getDiscriminatorLength() < bestSplitter.getDiscriminatorLength()) {
2✔
409
                    bestSplitter = splitter;
2✔
410
                    bestBlockRoot = blockRoot;
2✔
411
                }
412

413
                if (!optimizeGlobal) {
2✔
414
                    break;
×
415
                }
416
            }
417
        }
2✔
418

419
        if (bestSplitter == null) {
2✔
420
            return null;
2✔
421
        }
422

423
        return new GlobalSplitter<>(bestBlockRoot, bestSplitter);
2✔
424
    }
425

426
    /**
427
     * Determines a (local) splitter for a given block. This method may (but is not required to) employ heuristics to
428
     * obtain a splitter with a relatively short suffix.
429
     *
430
     * @param blockRoot
431
     *         the root of the block
432
     *
433
     * @return a splitter for this block, or {@code null} if no such splitter could be found.
434
     */
435
    private Splitter<I, D> findSplitter(AbstractBaseDTNode<I, D> blockRoot) {
436
        int alphabetSize = alphabet.size();
2✔
437

438
        Object[] properties = new Object[alphabetSize];
2✔
439
        @SuppressWarnings("unchecked")
440
        AbstractBaseDTNode<I, D>[] lcas = new AbstractBaseDTNode[alphabetSize];
2✔
441
        boolean first = true;
2✔
442

443
        for (TTTState<I, D> state : blockRoot.subtreeStates()) {
2✔
444
            for (int i = 0; i < alphabetSize; i++) {
2✔
445
                TTTTransition<I, D> trans = state.getTransition(i);
2✔
446
                if (first) {
2✔
447
                    properties[i] = trans.getProperty();
2✔
448
                    lcas[i] = trans.getDTTarget();
2✔
449
                } else {
450
                    if (!Objects.equals(properties[i], trans.getProperty())) {
2✔
451
                        return new Splitter<>(i);
2✔
452
                    }
453
                    lcas[i] = dtree.leastCommonAncestor(lcas[i], trans.getDTTarget());
2✔
454
                }
455
            }
456
            first = false;
2✔
457
        }
2✔
458

459
        int shortestLen = Integer.MAX_VALUE;
2✔
460
        AbstractBaseDTNode<I, D> shortestLca = null;
2✔
461
        int shortestLcaSym = -1;
2✔
462

463
        for (int i = 0; i < alphabetSize; i++) {
2✔
464
            AbstractBaseDTNode<I, D> lca = lcas[i];
2✔
465
            if (!lca.isTemp() && !lca.isLeaf()) {
2✔
466
                int lcaLen = lca.getDiscriminator().length();
2✔
467
                if (shortestLca == null || lcaLen < shortestLen) {
2✔
468
                    shortestLca = lca;
2✔
469
                    shortestLen = lcaLen;
2✔
470
                    shortestLcaSym = i;
2✔
471
                }
472
            }
473
        }
474

475
        if (shortestLca != null) {
2✔
476
            return new Splitter<>(shortestLcaSym, shortestLca);
2✔
477
        }
478
        return null;
2✔
479
    }
480

481
    /**
482
     * Creates a state in the hypothesis. This method cannot be used for the initial state, which has no incoming tree
483
     * transition.
484
     *
485
     * @param transition
486
     *         the "parent" transition in the spanning tree
487
     *
488
     * @return the newly created state
489
     */
490
    private TTTState<I, D> createState(TTTTransition<I, D> transition) {
491
        return hypothesis.createState(transition);
2✔
492
    }
493

494
    /**
495
     * Retrieves the target state of a given transition. This method works for both tree and non-tree transitions. If a
496
     * non-tree transition points to a non-leaf node, it is updated accordingly before a result is obtained.
497
     *
498
     * @param trans
499
     *         the transition
500
     *
501
     * @return the target state of this transition (possibly after it having been updated)
502
     */
503
    protected TTTState<I, D> getAnyTarget(TTTTransition<I, D> trans) {
504
        if (trans.isTree()) {
2✔
505
            return trans.getTreeTarget();
2✔
506
        }
507
        return trans.getNonTreeTarget().anySubtreeState();
2✔
508
    }
509

510
    /**
511
     * Retrieves the state reached by the given sequence of symbols, starting from the initial state.
512
     *
513
     * @param suffix
514
     *         the sequence of symbols to process
515
     *
516
     * @return the state reached after processing the specified symbols
517
     */
518
    private TTTState<I, D> getAnyState(Iterable<? extends I> suffix) {
519
        return getAnySuccessor(hypothesis.getInitialState(), suffix);
2✔
520
    }
521

522
    protected OutputInconsistency<I, D> findOutputInconsistency() {
523
        OutputInconsistency<I, D> best = null;
2✔
524

525
        for (TTTState<I, D> state : hypothesis.getStates()) {
2✔
526
            AbstractBaseDTNode<I, D> node = state.getDTLeaf();
2✔
527
            while (!node.isRoot()) {
2✔
528
                D expectedOut = node.getParentOutcome();
2✔
529
                node = node.getParent();
2✔
530
                Word<I> suffix = node.getDiscriminator();
2✔
531
                if (best == null || suffix.length() < best.suffix.length()) {
2✔
532
                    D hypOut = computeHypothesisOutput(state, suffix);
2✔
533
                    if (!Objects.equals(hypOut, expectedOut)) {
2✔
534
                        best = new OutputInconsistency<>(state, suffix, expectedOut);
2✔
535
                    }
536
                }
537
            }
2✔
538
        }
2✔
539
        return best;
2✔
540
    }
541

542
    /**
543
     * Finalize a discriminator. Given a block root and a {@link Splitter}, replace the discriminator at the block root
544
     * by the one derived from the splitter, and update the discrimination tree accordingly.
545
     *
546
     * @param blockRoot
547
     *         the block root whose discriminator to finalize
548
     * @param splitter
549
     *         the splitter to use for finalization
550
     */
551
    private void finalizeDiscriminator(AbstractBaseDTNode<I, D> blockRoot, Splitter<I, D> splitter) {
552
        assert blockRoot.isBlockRoot();
2✔
553

554
        Word<I> succDiscr = splitter.getDiscriminator().prepend(alphabet.getSymbol(splitter.symbolIdx));
2✔
555

556
        if (!blockRoot.getDiscriminator().equals(succDiscr)) {
2✔
557
            Word<I> finalDiscriminator = prepareSplit(blockRoot, splitter);
2✔
558
            Map<D, AbstractBaseDTNode<I, D>> repChildren = createMap();
2✔
559
            for (D label : blockRoot.getSplitData().getLabels()) {
2✔
560
                repChildren.put(label, extractSubtree(blockRoot, label));
2✔
561
            }
2✔
562
            blockRoot.replaceChildren(repChildren);
2✔
563

564
            blockRoot.setDiscriminator(finalDiscriminator);
2✔
565
        }
566

567
        declareFinal(blockRoot);
2✔
568
    }
2✔
569

570
    protected boolean allNodesFinal() {
571
        Iterator<AbstractBaseDTNode<I, D>> it = dtree.getRoot().subtreeNodesIterator();
2✔
572
        while (it.hasNext()) {
2✔
573
            AbstractBaseDTNode<I, D> node = it.next();
2✔
574
            assert !node.isTemp() : "Final node with discriminator " + node.getDiscriminator();
2✔
575
        }
2✔
576
        return true;
2✔
577
    }
578

579
    protected void declareFinal(AbstractBaseDTNode<I, D> blockRoot) {
580
        blockRoot.setTemp(false);
2✔
581
        blockRoot.setSplitData(null);
2✔
582

583
        blockRoot.removeFromBlockList();
2✔
584

585
        for (AbstractBaseDTNode<I, D> subtree : blockRoot.getChildren()) {
2✔
586
            assert subtree.getSplitData() == null;
2✔
587
            blockRoot.setChild(subtree.getParentOutcome(), subtree);
2✔
588
            // Register as blocks, if they are non-trivial subtrees
589
            if (subtree.isInner()) {
2✔
590
                blockList.insertBlock(subtree);
2✔
591
            }
592
        }
2✔
593
        openTransitions.insertAllIncoming(blockRoot.getIncoming());
2✔
594
    }
2✔
595

596
    /**
597
     * Prepare a split operation on a block, by marking all the nodes and transitions in the subtree (and annotating
598
     * them with {@link SplitData} objects).
599
     *
600
     * @param node
601
     *         the block root to be split
602
     * @param splitter
603
     *         the splitter to use for splitting the block
604
     *
605
     * @return the discriminator to use for splitting
606
     */
607
    private Word<I> prepareSplit(AbstractBaseDTNode<I, D> node, Splitter<I, D> splitter) {
608
        int symbolIdx = splitter.symbolIdx;
2✔
609
        I symbol = alphabet.getSymbol(symbolIdx);
2✔
610
        Word<I> discriminator = splitter.getDiscriminator().prepend(symbol);
2✔
611

612
        Deque<AbstractBaseDTNode<I, D>> dfsStack = new ArrayDeque<>();
2✔
613

614
        AbstractBaseDTNode<I, D> succSeparator = splitter.succSeparator;
2✔
615

616
        dfsStack.push(node);
2✔
617
        assert node.getSplitData() == null;
2✔
618

619
        while (!dfsStack.isEmpty()) {
2✔
620
            AbstractBaseDTNode<I, D> curr = dfsStack.pop();
2✔
621
            assert curr.getSplitData() == null;
2✔
622

623
            curr.setSplitData(new SplitData<>(IncomingList::new));
2✔
624

625
            for (TTTTransition<I, D> trans : curr.getIncoming()) {
2✔
626
                D outcome = query(trans, discriminator);
2✔
627
                curr.getSplitData().getIncoming(outcome).insertIncoming(trans);
2✔
628
                markAndPropagate(curr, outcome);
2✔
629
            }
2✔
630

631
            if (curr.isInner()) {
2✔
632
                for (AbstractBaseDTNode<I, D> child : curr.getChildren()) {
2✔
633
                    dfsStack.push(child);
2✔
634
                }
2✔
635
            } else {
636
                TTTState<I, D> state = curr.getData();
2✔
637
                assert state != null;
2✔
638

639
                TTTTransition<I, D> trans = state.getTransition(symbolIdx);
2✔
640
                D outcome = predictSuccOutcome(trans, succSeparator);
2✔
641
                assert outcome != null;
2✔
642
                curr.getSplitData().setStateLabel(outcome);
2✔
643
                markAndPropagate(curr, outcome);
2✔
644
            }
645

646
        }
2✔
647

648
        return discriminator;
2✔
649
    }
650

651
    protected abstract D predictSuccOutcome(TTTTransition<I, D> trans, AbstractBaseDTNode<I, D> succSeparator);
652

653
    /**
654
     * Extract a (reduced) subtree containing all nodes with the given label from the subtree given by its root.
655
     * "Reduced" here refers to the fact that the resulting subtree will contain no inner nodes with only one child.
656
     * <p>
657
     * The tree returned by this method (represented by its root) will have as a parent node the root that was passed to
658
     * this method.
659
     *
660
     * @param root
661
     *         the root of the subtree from which to extract
662
     * @param label
663
     *         the label of the nodes to extract
664
     *
665
     * @return the extracted subtree
666
     */
667
    private AbstractBaseDTNode<I, D> extractSubtree(AbstractBaseDTNode<I, D> root, D label) {
668
        assert root.getSplitData() != null;
2✔
669
        assert root.getSplitData().isMarked(label);
2✔
670

671
        Deque<ExtractRecord<I, D>> stack = new ArrayDeque<>();
2✔
672

673
        AbstractBaseDTNode<I, D> firstExtracted = createNewNode(root, label);
2✔
674

675
        stack.push(new ExtractRecord<>(root, firstExtracted));
2✔
676
        while (!stack.isEmpty()) {
2✔
677
            ExtractRecord<I, D> curr = stack.pop();
2✔
678

679
            AbstractBaseDTNode<I, D> original = curr.original;
2✔
680
            AbstractBaseDTNode<I, D> extracted = curr.extracted;
2✔
681

682
            moveIncoming(extracted, original, label);
2✔
683

684
            if (original.isLeaf()) {
2✔
685
                if (Objects.equals(original.getSplitData().getStateLabel(), label)) {
2✔
686
                    link(extracted, original.getData());
2✔
687
                } else {
688
                    createNewState(extracted);
×
689
                }
690
                extracted.updateIncoming();
2✔
691
            } else {
692
                List<AbstractBaseDTNode<I, D>> markedChildren = new ArrayList<>();
2✔
693

694
                for (AbstractBaseDTNode<I, D> child : original.getChildren()) {
2✔
695
                    if (child.getSplitData().isMarked(label)) {
2✔
696
                        markedChildren.add(child);
2✔
697
                    }
698
                }
2✔
699

700
                if (markedChildren.size() > 1) {
2✔
701
                    Map<D, AbstractBaseDTNode<I, D>> childMap = createMap();
2✔
702
                    for (AbstractBaseDTNode<I, D> c : markedChildren) {
2✔
703
                        D childLabel = c.getParentOutcome();
2✔
704
                        AbstractBaseDTNode<I, D> extractedChild = createNewNode(extracted, childLabel);
2✔
705
                        childMap.put(childLabel, extractedChild);
2✔
706
                        stack.push(new ExtractRecord<>(c, extractedChild));
2✔
707
                    }
2✔
708
                    extracted.setDiscriminator(original.getDiscriminator());
2✔
709
                    extracted.replaceChildren(childMap);
2✔
710
                    extracted.updateIncoming();
2✔
711
                    extracted.setTemp(true);
2✔
712
                } else if (markedChildren.size() == 1) {
2✔
713
                    stack.push(new ExtractRecord<>(markedChildren.get(0), extracted));
2✔
714
                } else { // markedChildren.isEmpty()
715
                    createNewState(extracted);
2✔
716
                    extracted.updateIncoming();
2✔
717
                }
718
            }
719

720
            assert extracted.getSplitData() == null;
2✔
721
        }
2✔
722

723
        return firstExtracted;
2✔
724
    }
725

726
    protected <V> Map<D, V> createMap() {
727
        return new HashMap<>();
2✔
728
    }
729

730
    /**
731
     * Create a new state during extraction on-the-fly. This is required if a node in the DT has an incoming transition
732
     * with a certain label, but in its subtree there are no leaves with this label as their state label.
733
     *
734
     * @param newNode
735
     *         the extracted node
736
     */
737
    private void createNewState(AbstractBaseDTNode<I, D> newNode) {
738
        TTTTransition<I, D> newTreeTrans = newNode.getIncoming().choose();
2✔
739
        assert newTreeTrans != null;
2✔
740

741
        TTTState<I, D> newState = createState(newTreeTrans);
2✔
742
        link(newNode, newState);
2✔
743
        initializeState(newState);
2✔
744
    }
2✔
745

746
    protected abstract D computeHypothesisOutput(TTTState<I, D> state, Word<I> suffix);
747

748
    public AbstractTTTHypothesis<?, I, D, ?> getHypothesisDS() {
749
        return hypothesis;
2✔
750
    }
751

752
    protected void closeTransitions() {
753
        UnorderedCollection<AbstractBaseDTNode<I, D>> newStateNodes = new UnorderedCollection<>();
2✔
754

755
        do {
756
            newStateNodes.addAll(closeTransitions(openTransitions, false));
2✔
757
            if (!newStateNodes.isEmpty()) {
2✔
758
                addNewStates(newStateNodes);
2✔
759
            }
760
        } while (!openTransitions.isEmpty());
2✔
761
    }
2✔
762

763
    /**
764
     * Ensures that the specified transitions point to a leaf-node. If a transition is a tree transition, this method
765
     * has no effect.
766
     * <p>
767
     * The provided transList is consumed in this process.
768
     * <p>
769
     * If a transition needs sifting, the reached leaf node will be collected in the returned collection.
770
     *
771
     * @param transList
772
     *         the list of transitions
773
     *
774
     * @return a collection containing the reached leaves of transitions that needed sifting
775
     */
776
    private List<AbstractBaseDTNode<I, D>> closeTransitions(IncomingList<I, D> transList, boolean hard) {
777

778
        final List<TTTTransition<I, D>> transToSift = new ArrayList<>(transList.size());
2✔
779

780
        TTTTransition<I, D> t;
781
        while ((t = transList.poll()) != null) {
2✔
782
            if (!t.isTree()) {
2✔
783
                transToSift.add(t);
2✔
784
            }
785
        }
786

787
        if (transToSift.isEmpty()) {
2✔
788
            return Collections.emptyList();
2✔
789
        }
790

791
        final Iterator<AbstractBaseDTNode<I, D>> leavesIter = updateDTTargets(transToSift, hard).iterator();
2✔
792
        final List<AbstractBaseDTNode<I, D>> result = new ArrayList<>(transToSift.size());
2✔
793

794
        for (TTTTransition<I, D> transition : transToSift) {
2✔
795
            final AbstractBaseDTNode<I, D> node = leavesIter.next();
2✔
796
            if (node.isLeaf() && node.getData() == null && transition.getNextElement() == null) {
2✔
797
                result.add(node);
2✔
798
            }
799
        }
2✔
800

801
        assert !leavesIter.hasNext();
2✔
802
        return result;
2✔
803
    }
804

805
    private void addNewStates(UnorderedCollection<AbstractBaseDTNode<I, D>> newStateNodes) {
806
        AbstractBaseDTNode<I, D> minTransNode = null;
2✔
807
        TTTTransition<I, D> minTrans = null;
2✔
808
        int minAsLen = Integer.MAX_VALUE;
2✔
809
        ElementReference minTransNodeRef = null;
2✔
810
        for (ElementReference ref : newStateNodes.references()) {
2✔
811
            AbstractBaseDTNode<I, D> newStateNode = newStateNodes.get(ref);
2✔
812
            for (TTTTransition<I, D> trans : newStateNode.getIncoming()) {
2✔
813
                Word<I> as = trans.getAccessSequence();
2✔
814
                int asLen = as.length();
2✔
815
                if (asLen < minAsLen) {
2✔
816
                    minTransNode = newStateNode;
2✔
817
                    minTrans = trans;
2✔
818
                    minAsLen = asLen;
2✔
819
                    minTransNodeRef = ref;
2✔
820
                }
821
            }
2✔
822
        }
2✔
823

824
        assert minTransNode != null;
2✔
825
        newStateNodes.remove(minTransNodeRef);
2✔
826
        TTTState<I, D> state = makeTree(minTrans);
2✔
827
        link(minTransNode, state);
2✔
828
        initializeState(state);
2✔
829
    }
2✔
830

831
    protected TTTState<I, D> makeTree(TTTTransition<I, D> trans) {
832
        assert !trans.isTree();
2✔
833
        AbstractBaseDTNode<I, D> node = trans.nonTreeTarget;
2✔
834
        assert node.isLeaf();
2✔
835
        TTTState<I, D> state = createState(trans);
2✔
836
        trans.removeFromList();
2✔
837
        link(node, state);
2✔
838
        initializeState(state);
2✔
839
        return state;
2✔
840
    }
841

842
    /**
843
     * Updates the transition to point to either a leaf in the discrimination tree, or---if the {@code hard} parameter
844
     * is set to {@code false}---to a block root.
845
     *
846
     * @param transition
847
     *         the transition
848
     * @param hard
849
     *         whether to consider leaves as sufficient targets only
850
     *
851
     * @return the new target node of the transition
852
     */
853
    private AbstractBaseDTNode<I, D> updateDTTarget(TTTTransition<I, D> transition, boolean hard) {
854
        if (transition.isTree()) {
2✔
855
            return transition.getTreeTarget().dtLeaf;
×
856
        }
857

858
        AbstractBaseDTNode<I, D> dt = transition.getNonTreeTarget();
2✔
859
        dt = dtree.sift(dt, transition.getAccessSequence(), hard);
2✔
860
        transition.setNonTreeTarget(dt);
2✔
861

862
        return dt;
2✔
863
    }
864

865
    /**
866
     * Bulk version of {@link #updateDTTarget(TTTTransition, boolean)}.
867
     */
868
    private List<AbstractBaseDTNode<I, D>> updateDTTargets(List<TTTTransition<I, D>> transitions, boolean hard) {
869

870
        final List<AbstractBaseDTNode<I, D>> nodes = new ArrayList<>(transitions.size());
2✔
871
        final List<Word<I>> prefixes = new ArrayList<>(transitions.size());
2✔
872

873
        for (TTTTransition<I, D> t : transitions) {
2✔
874
            if (!t.isTree()) {
2✔
875
                AbstractBaseDTNode<I, D> dt = t.getNonTreeTarget();
2✔
876

877
                nodes.add(dt);
2✔
878
                prefixes.add(t.getAccessSequence());
2✔
879
            }
880
        }
2✔
881

882
        final Iterator<AbstractBaseDTNode<I, D>> leavesIter = dtree.sift(nodes, prefixes, hard).iterator();
2✔
883
        final List<AbstractBaseDTNode<I, D>> result = new ArrayList<>(transitions.size());
2✔
884

885
        for (TTTTransition<I, D> t : transitions) {
2✔
886
            if (t.isTree()) {
2✔
887
                result.add(t.getTreeTarget().dtLeaf);
×
888
            } else {
889
                AbstractBaseDTNode<I, D> leaf = leavesIter.next();
2✔
890
                t.setNonTreeTarget(leaf);
2✔
891
                result.add(leaf);
2✔
892
            }
893
        }
2✔
894

895
        assert !leavesIter.hasNext();
2✔
896
        return result;
2✔
897
    }
898

899
    /**
900
     * Performs a membership query.
901
     *
902
     * @param prefix
903
     *         the prefix part of the query
904
     * @param suffix
905
     *         the suffix part of the query
906
     *
907
     * @return the output
908
     */
909
    protected D query(Word<I> prefix, Word<I> suffix) {
910
        return oracle.answerQuery(prefix, suffix);
2✔
911
    }
912

913
    /**
914
     * Performs a membership query, using an access sequence as its prefix.
915
     *
916
     * @param accessSeqProvider
917
     *         the object from which to obtain the access sequence
918
     * @param suffix
919
     *         the suffix part of the query
920
     *
921
     * @return the output
922
     */
923
    protected D query(AccessSequenceProvider<I> accessSeqProvider, Word<I> suffix) {
924
        return query(accessSeqProvider.getAccessSequence(), suffix);
2✔
925
    }
926

927
    /**
928
     * Returns the discrimination tree.
929
     *
930
     * @return the discrimination tree
931
     */
932
    public BaseTTTDiscriminationTree<I, D> getDiscriminationTree() {
933
        return dtree;
2✔
934
    }
935

936
    protected final AbstractBaseDTNode<I, D>.SplitResult split(AbstractBaseDTNode<I, D> node,
937
                                                               Word<I> discriminator,
938
                                                               D oldOutput,
939
                                                               D newOutput) {
940
        return node.split(discriminator, oldOutput, newOutput);
2✔
941
    }
942

943
    @Override
944
    public void addAlphabetSymbol(I symbol) {
945

946
        if (!this.alphabet.containsSymbol(symbol)) {
2✔
947
            Alphabets.toGrowingAlphabetOrThrowException(this.alphabet).addSymbol(symbol);
2✔
948
        }
949

950
        this.hypothesis.addAlphabetSymbol(symbol);
2✔
951

952
        // check if we already have information about the symbol (then the transition is defined) so we don't post
953
        // redundant queries
954
        if (this.hypothesis.getInitialState() != null && this.hypothesis.getState(Word.fromLetter(symbol)) == null) {
2✔
955

956
            final int newSymbolIdx = this.alphabet.getSymbolIndex(symbol);
2✔
957

958
            for (TTTState<I, D> s : this.hypothesis.getStates()) {
2✔
959
                final TTTTransition<I, D> trans = createTransition(s, symbol);
2✔
960
                trans.setNonTreeTarget(dtree.getRoot());
2✔
961
                s.setTransition(newSymbolIdx, trans);
2✔
962
                openTransitions.insertIncoming(trans);
2✔
963
            }
2✔
964

965
            this.closeTransitions();
2✔
966
        }
967
    }
2✔
968

969
    protected abstract AbstractBaseDTNode<I, D> createNewNode(AbstractBaseDTNode<I, D> parent, D parentOutput);
970

971
    @Override
972
    public TTTLearnerState<I, D> suspend() {
973
        return new TTTLearnerState<>(hypothesis, dtree);
2✔
974
    }
975

976
    @Override
977
    public void resume(TTTLearnerState<I, D> state) {
978
        this.hypothesis = state.getHypothesis();
2✔
979
        this.dtree = state.getDiscriminationTree();
2✔
980
        this.dtree.setOracle(oracle);
2✔
981

982
        final Alphabet<I> oldAlphabet = this.hypothesis.getInputAlphabet();
2✔
983
        if (!oldAlphabet.equals(this.alphabet)) {
2✔
984
            LOGGER.warn(
×
985
                    "The current alphabet '{}' differs from the resumed alphabet '{}'. Future behavior may be inconsistent",
986
                    this.alphabet,
987
                    oldAlphabet);
988
        }
989
    }
2✔
990

991
    public static final class BuilderDefaults {
992

993
        private BuilderDefaults() {
994
            // prevent instantiation
995
        }
996

997
        public static AcexAnalyzer analyzer() {
998
            return AcexAnalyzers.BINARY_SEARCH_BWD;
2✔
999
        }
1000
    }
1001

1002
    /**
1003
     * Data structure for representing a splitter.
1004
     * <p>
1005
     * A splitter is represented by an input symbol, and a DT node that separates the successors (wrt. the input symbol)
1006
     * of the original states. From this, a discriminator can be obtained by prepending the input symbol to the
1007
     * discriminator that labels the separating successor.
1008
     * <p>
1009
     * <b>Note:</b> as the discriminator finalization is applied to the root of a block and affects all nodes, there is
1010
     * no need to store references to the source states from which this splitter was obtained.
1011
     *
1012
     * @param <I>
1013
     *         input symbol type
1014
     */
1015
    public static final class Splitter<I, D> {
2✔
1016

1017
        public final int symbolIdx;
1018
        public final AbstractBaseDTNode<I, D> succSeparator;
1019

1020
        public Splitter(int symbolIdx) {
2✔
1021
            this.symbolIdx = symbolIdx;
2✔
1022
            this.succSeparator = null;
2✔
1023
        }
2✔
1024

1025
        public Splitter(int symbolIdx, AbstractBaseDTNode<I, D> succSeparator) {
2✔
1026
            assert !succSeparator.isTemp() && succSeparator.isInner();
2✔
1027

1028
            this.symbolIdx = symbolIdx;
2✔
1029
            this.succSeparator = succSeparator;
2✔
1030
        }
2✔
1031

1032
        public Word<I> getDiscriminator() {
1033
            return (succSeparator != null) ? succSeparator.getDiscriminator() : Word.epsilon();
2✔
1034
        }
1035

1036
        public int getDiscriminatorLength() {
1037
            return (succSeparator != null) ? succSeparator.getDiscriminator().length() : 0;
×
1038
        }
1039
    }
1040

1041
    /**
1042
     * A global splitter. In addition to the information stored in a (local) {@link Splitter}, this class also stores
1043
     * the block the local splitter applies to.
1044
     *
1045
     * @param <I>
1046
     *         input symbol type
1047
     */
1048
    private static final class GlobalSplitter<I, D> {
1049

1050
        public final Splitter<I, D> localSplitter;
1051
        public final AbstractBaseDTNode<I, D> blockRoot;
1052

1053
        GlobalSplitter(AbstractBaseDTNode<I, D> blockRoot, Splitter<I, D> localSplitter) {
2✔
1054
            this.blockRoot = blockRoot;
2✔
1055
            this.localSplitter = localSplitter;
2✔
1056
        }
2✔
1057
    }
1058

1059
    /**
1060
     * Data structure required during an extract operation. The latter basically works by copying nodes that are
1061
     * required in the extracted subtree, and this data structure is required to associate original nodes with their
1062
     * extracted copies.
1063
     *
1064
     * @param <I>
1065
     *         input symbol type
1066
     */
1067
    private static final class ExtractRecord<I, D> {
1068

1069
        public final AbstractBaseDTNode<I, D> original;
1070
        public final AbstractBaseDTNode<I, D> extracted;
1071

1072
        ExtractRecord(AbstractBaseDTNode<I, D> original, AbstractBaseDTNode<I, D> extracted) {
2✔
1073
            this.original = original;
2✔
1074
            this.extracted = extracted;
2✔
1075
        }
2✔
1076
    }
1077
}
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

© 2025 Coveralls, Inc