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

LearnLib / learnlib / 6685153890

29 Oct 2023 06:39PM CUT coverage: 93.327%. Remained the same
6685153890

push

github

mtf90
cleanup changelog

11734 of 12573 relevant lines covered (93.33%)

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.acex.AcexAnalyzer;
33
import de.learnlib.acex.analyzer.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.logging.Category;
38
import de.learnlib.api.oracle.MembershipOracle;
39
import de.learnlib.api.query.DefaultQuery;
40
import de.learnlib.counterexample.acex.OutInconsPrefixTransformAcex;
41
import de.learnlib.datastructure.discriminationtree.SplitData;
42
import net.automatalib.SupportsGrowingAlphabet;
43
import net.automatalib.alphabet.Alphabet;
44
import net.automatalib.alphabet.Alphabets;
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.slf4j.Logger;
50
import org.slf4j.LoggerFactory;
51

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

168
        return true;
2✔
169
    }
170

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

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

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

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

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

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

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

222
        return true;
2✔
223
    }
224

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

294
    protected abstract D succEffect(D effect);
295

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

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

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

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

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

336
        return curr;
2✔
337
    }
338

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

579
        blockRoot.removeFromBlockList();
2✔
580

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

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

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

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

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

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

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

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

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

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

642
        }
2✔
643

644
        return discriminator;
2✔
645
    }
646

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

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

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

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

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

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

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

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

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

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

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

719
        return firstExtracted;
2✔
720
    }
721

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

858
        return dt;
2✔
859
    }
860

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

987
    public static final class BuilderDefaults {
988

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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