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

LearnLib / learnlib / 6805330902

08 Nov 2023 11:40PM UTC coverage: 93.335% (+0.008%) from 93.327%
6805330902

push

github

mtf90
typo

11736 of 12574 relevant lines covered (93.34%)

1.69 hits per line

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

97.48
/algorithms/active/ttt/src/main/java/de/learnlib/algorithm/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.algorithm.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.AccessSequenceProvider;
33
import de.learnlib.Resumable;
34
import de.learnlib.acex.AcexAnalyzer;
35
import de.learnlib.acex.AcexAnalyzers;
36
import de.learnlib.acex.OutInconsPrefixTransformAcex;
37
import de.learnlib.algorithm.LearningAlgorithm;
38
import de.learnlib.datastructure.discriminationtree.SplitData;
39
import de.learnlib.logging.Category;
40
import de.learnlib.oracle.MembershipOracle;
41
import de.learnlib.query.DefaultQuery;
42
import net.automatalib.alphabet.Alphabet;
43
import net.automatalib.alphabet.Alphabets;
44
import net.automatalib.alphabet.SupportsGrowingAlphabet;
45
import net.automatalib.automaton.fsa.DFA;
46
import net.automatalib.common.smartcollection.ElementReference;
47
import net.automatalib.common.smartcollection.UnorderedCollection;
48
import net.automatalib.word.Word;
49
import org.checkerframework.checker.nullness.qual.Nullable;
50
import org.slf4j.Logger;
51
import org.slf4j.LoggerFactory;
52

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

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

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

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

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

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

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

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

139
        dtNode.setData(state);
2✔
140
        state.dtLeaf = dtNode;
2✔
141
    }
2✔
142

143
    /*
144
     * Private helper methods.
145
     */
146

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

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

158
        closeTransitions();
2✔
159
    }
2✔
160

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

167
        while (refineHypothesisSingle(ceQuery)) {}
2✔
168

169
        return true;
2✔
170
    }
171

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

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

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

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

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

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

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

223
        return true;
2✔
224
    }
225

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

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

243
        TTTState<I, D> newState = makeTree(transition);
2✔
244

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

248
        link(children.nodeOld, oldState);
2✔
249
        link(children.nodeNew, newState);
2✔
250

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

256
    private void splitState(OutputInconsistency<I, D> outIncons) {
257

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

263
            Word<I> suffix = outIncons.suffix;
2✔
264

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

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

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

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

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

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

295
    protected abstract D succEffect(D effect);
296

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

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

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

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

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

337
        return curr;
2✔
338
    }
339

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

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

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

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

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

397
        AbstractBaseDTNode<I, D> bestBlockRoot = null;
2✔
398

399
        Splitter<I, D> bestSplitter = null;
2✔
400

401
        for (AbstractBaseDTNode<I, D> blockRoot : blockList) {
2✔
402
            Splitter<I, D> splitter = findSplitter(blockRoot);
2✔
403

404
            if (splitter != null) {
2✔
405
                if (bestSplitter == null || splitter.getDiscriminatorLength() < bestSplitter.getDiscriminatorLength()) {
2✔
406
                    bestSplitter = splitter;
2✔
407
                    bestBlockRoot = blockRoot;
2✔
408
                }
409

410
                if (!optimizeGlobal) {
2✔
411
                    break;
×
412
                }
413
            }
414
        }
2✔
415

416
        if (bestSplitter == null) {
2✔
417
            return null;
2✔
418
        }
419

420
        return new GlobalSplitter<>(bestBlockRoot, bestSplitter);
2✔
421
    }
422

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

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

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

456
        int shortestLen = Integer.MAX_VALUE;
2✔
457
        AbstractBaseDTNode<I, D> shortestLca = null;
2✔
458
        int shortestLcaSym = -1;
2✔
459

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

472
        if (shortestLca != null) {
2✔
473
            return new Splitter<>(shortestLcaSym, shortestLca);
2✔
474
        }
475
        return null;
2✔
476
    }
477

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

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

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

519
    protected OutputInconsistency<I, D> findOutputInconsistency() {
520
        OutputInconsistency<I, D> best = null;
2✔
521

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

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

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

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

561
            blockRoot.setDiscriminator(finalDiscriminator);
2✔
562
        }
563

564
        declareFinal(blockRoot);
2✔
565
    }
2✔
566

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

576
    protected void declareFinal(AbstractBaseDTNode<I, D> blockRoot) {
577
        blockRoot.setTemp(false);
2✔
578
        blockRoot.setSplitData(null);
2✔
579

580
        blockRoot.removeFromBlockList();
2✔
581

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

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

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

611
        AbstractBaseDTNode<I, D> succSeparator = splitter.succSeparator;
2✔
612

613
        dfsStack.push(node);
2✔
614
        assert node.getSplitData() == null;
2✔
615

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

620
            curr.setSplitData(new SplitData<>(IncomingList::new));
2✔
621

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

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

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

643
        }
2✔
644

645
        return discriminator;
2✔
646
    }
647

648
    protected abstract D predictSuccOutcome(TTTTransition<I, D> trans, AbstractBaseDTNode<I, D> succSeparator);
649

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

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

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

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

676
            AbstractBaseDTNode<I, D> original = curr.original;
2✔
677
            AbstractBaseDTNode<I, D> extracted = curr.extracted;
2✔
678

679
            moveIncoming(extracted, original, label);
2✔
680

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

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

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

717
            assert extracted.getSplitData() == null;
2✔
718
        }
2✔
719

720
        return firstExtracted;
2✔
721
    }
722

723
    protected <V> Map<D, V> createMap() {
724
        return new HashMap<>();
2✔
725
    }
726

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

738
        TTTState<I, D> newState = createState(newTreeTrans);
2✔
739
        link(newNode, newState);
2✔
740
        initializeState(newState);
2✔
741
    }
2✔
742

743
    protected abstract D computeHypothesisOutput(TTTState<I, D> state, Word<I> suffix);
744

745
    public AbstractTTTHypothesis<?, I, D, ?> getHypothesisDS() {
746
        return hypothesis;
2✔
747
    }
748

749
    protected void closeTransitions() {
750
        UnorderedCollection<AbstractBaseDTNode<I, D>> newStateNodes = new UnorderedCollection<>();
2✔
751

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

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

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

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

784
        if (transToSift.isEmpty()) {
2✔
785
            return Collections.emptyList();
2✔
786
        }
787

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

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

798
        assert !leavesIter.hasNext();
2✔
799
        return result;
2✔
800
    }
801

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

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

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

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

855
        AbstractBaseDTNode<I, D> dt = transition.getNonTreeTarget();
2✔
856
        dt = dtree.sift(dt, transition.getAccessSequence(), hard);
2✔
857
        transition.setNonTreeTarget(dt);
2✔
858

859
        return dt;
2✔
860
    }
861

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

867
        final List<AbstractBaseDTNode<I, D>> nodes = new ArrayList<>(transitions.size());
2✔
868
        final List<Word<I>> prefixes = new ArrayList<>(transitions.size());
2✔
869

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

874
                nodes.add(dt);
2✔
875
                prefixes.add(t.getAccessSequence());
2✔
876
            }
877
        }
2✔
878

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

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

892
        assert !leavesIter.hasNext();
2✔
893
        return result;
2✔
894
    }
895

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

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

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

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

940
    @Override
941
    public void addAlphabetSymbol(I symbol) {
942

943
        if (!this.alphabet.containsSymbol(symbol)) {
2✔
944
            Alphabets.toGrowingAlphabetOrThrowException(this.alphabet).addSymbol(symbol);
2✔
945
        }
946

947
        this.hypothesis.addAlphabetSymbol(symbol);
2✔
948

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

953
            final int newSymbolIdx = this.alphabet.getSymbolIndex(symbol);
2✔
954

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

962
            this.closeTransitions();
2✔
963
        }
964
    }
2✔
965

966
    protected abstract AbstractBaseDTNode<I, D> createNewNode(AbstractBaseDTNode<I, D> parent, D parentOutput);
967

968
    @Override
969
    public TTTLearnerState<I, D> suspend() {
970
        return new TTTLearnerState<>(hypothesis, dtree);
2✔
971
    }
972

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

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

988
    public static final class BuilderDefaults {
989

990
        private BuilderDefaults() {
991
            // prevent instantiation
992
        }
993

994
        public static AcexAnalyzer analyzer() {
995
            return AcexAnalyzers.BINARY_SEARCH_BWD;
2✔
996
        }
997
    }
998

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

1014
        public final int symbolIdx;
1015
        public final AbstractBaseDTNode<I, D> succSeparator;
1016

1017
        public Splitter(int symbolIdx) {
2✔
1018
            this.symbolIdx = symbolIdx;
2✔
1019
            this.succSeparator = null;
2✔
1020
        }
2✔
1021

1022
        public Splitter(int symbolIdx, AbstractBaseDTNode<I, D> succSeparator) {
2✔
1023
            assert !succSeparator.isTemp() && succSeparator.isInner();
2✔
1024

1025
            this.symbolIdx = symbolIdx;
2✔
1026
            this.succSeparator = succSeparator;
2✔
1027
        }
2✔
1028

1029
        public Word<I> getDiscriminator() {
1030
            return (succSeparator != null) ? succSeparator.getDiscriminator() : Word.epsilon();
2✔
1031
        }
1032

1033
        public int getDiscriminatorLength() {
1034
            return (succSeparator != null) ? succSeparator.getDiscriminator().length() : 0;
×
1035
        }
1036
    }
1037

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

1047
        public final Splitter<I, D> localSplitter;
1048
        public final AbstractBaseDTNode<I, D> blockRoot;
1049

1050
        GlobalSplitter(AbstractBaseDTNode<I, D> blockRoot, Splitter<I, D> localSplitter) {
2✔
1051
            this.blockRoot = blockRoot;
2✔
1052
            this.localSplitter = localSplitter;
2✔
1053
        }
2✔
1054
    }
1055

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

1066
        public final AbstractBaseDTNode<I, D> original;
1067
        public final AbstractBaseDTNode<I, D> extracted;
1068

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