Skip to content

Commit 1849dcd

Browse files
committed
improve parallelizability of TTT
closes #140 (the original issue was not a bug but this one came up in the resulting disucssion)
1 parent 3cae11b commit 1849dcd

File tree

6 files changed

+151
-44
lines changed

6 files changed

+151
-44
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
2828
* The `AbstractVisualizationTest` has been refactored into the `VisualizationUtils` factory.
2929
* Various counters (especially `*Counter*SUL`s) have been streamlined. In most cases there now exists a single counter that tracks multiple properties.
3030
* The `ReuseOracleBuilder` and `ReuseTreeBuilder` classes are now auto-generated and therefore reside in the respective packages of their previously enclosing classes.
31+
* The `TTTLearnerMealy#createTransition` method no longer queries for its transition output directly, but instead requires a call to `initTransitions` now.
3132
* With the removal of the `learnlib-annotation-processor` artifact (see below), the `learnlib-build-config` artifact is now part of the `de.learnlib` group again.
3233
* The `learnlib-datastructure-ot`, `learnlib-datastructure-dt`, `learnlib-datastructure-list`, and `learnlib-datastructure-pta` artifacts have been merged into a new `learnlib-datastructures` artifact.
3334
* The `learnlib-oml` artifact (including its packages and class names) has been renamed to `learnlib-lambda`.
@@ -40,6 +41,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
4041
* `PropertyOracle`s can no longer set a property. This value is now immutable and must be provided during instantiation. Previously, the internal state wasn't updated accordingly if a property was overridden.
4142
* `SymbolQueryOracle`s (and related code such as the respective caches, counters, etc.) have been removed without replacement. Equivalent functionality on the basis of the new `AdaptiveMembershipOracle`s is available instead.
4243

44+
### Fixed
45+
46+
* Improved query batching of `TTT` learner (both the regular and visibly push-down version).
47+
4348

4449
## [0.17.0] - 2023-11-15
4550

algorithms/active/observation-pack-vpa/src/main/java/de/learnlib/algorithm/observationpack/vpa/AbstractVPALearner.java

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.util.Iterator;
2121
import java.util.List;
2222

23-
import de.learnlib.AccessSequenceProvider;
2423
import de.learnlib.acex.AcexAnalyzer;
2524
import de.learnlib.acex.AcexAnalyzers;
2625
import de.learnlib.algorithm.LearningAlgorithm;
@@ -263,10 +262,6 @@ protected HypLoc<I> createLocation(AbstractHypTrans<I> trans) {
263262
return hypothesis.createLocation(false, trans);
264263
}
265264

266-
protected Boolean query(AccessSequenceProvider<I> asp, ContextPair<I> context) {
267-
return oracle.answerQuery(context.getPrefix().concat(asp.getAccessSequence()), context.getSuffix());
268-
}
269-
270265
public static final class BuilderDefaults {
271266

272267
private BuilderDefaults() {

algorithms/active/ttt-vpa/src/main/java/de/learnlib/algorithm/ttt/vpa/TTTLearnerVPA.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import de.learnlib.datastructure.list.IntrusiveList;
3838
import de.learnlib.oracle.MembershipOracle.DFAMembershipOracle;
3939
import de.learnlib.query.DefaultQuery;
40+
import de.learnlib.query.Query;
4041
import de.learnlib.tooling.annotation.builder.GenerateBuilder;
4142
import net.automatalib.alphabet.VPAlphabet;
4243
import net.automatalib.automaton.vpa.SEVPA;
@@ -432,6 +433,7 @@ private ContextPair<I> prepareSplit(DTNode<I> node, Splitter<I> splitter) {
432433
ContextPair<I> discriminator = splitter.getNewDiscriminator();
433434

434435
Deque<DTNode<I>> dfsStack = new ArrayDeque<>();
436+
List<SplitQuery<I>> queries = new ArrayList<>();
435437

436438
DTNode<I> succSeparator = splitter.succSeparator;
437439

@@ -445,9 +447,18 @@ private ContextPair<I> prepareSplit(DTNode<I> node, Splitter<I> splitter) {
445447
curr.setSplitData(new SplitData<>(TransList::new));
446448

447449
for (AbstractHypTrans<I> trans : curr.getIncoming()) {
448-
Boolean outcome = query(trans, discriminator);
449-
curr.getSplitData().getIncoming(outcome).add(trans);
450-
markAndPropagate(curr, outcome);
450+
queries.add(new SplitQuery<>(trans, discriminator));
451+
}
452+
453+
if (!queries.isEmpty()) {
454+
oracle.processQueries(queries);
455+
456+
for (SplitQuery<I> query : queries) {
457+
curr.getSplitData().getIncoming(query.output).add(query.transition);
458+
markAndPropagate(curr, query.output);
459+
}
460+
461+
queries.clear();
451462
}
452463

453464
if (curr.isInner()) {
@@ -627,4 +638,30 @@ protected void determinize(State<HypLoc<I>> state, Word<I> suffix) {
627638
}
628639
}
629640

641+
private static final class SplitQuery<I> extends Query<I, Boolean> {
642+
643+
private final AbstractHypTrans<I> transition;
644+
private final ContextPair<I> discriminator;
645+
private Boolean output;
646+
647+
SplitQuery(AbstractHypTrans<I> transition, ContextPair<I> discriminator) {
648+
this.transition = transition;
649+
this.discriminator = discriminator;
650+
}
651+
652+
@Override
653+
public void answer(Boolean output) {
654+
this.output = output;
655+
}
656+
657+
@Override
658+
public Word<I> getPrefix() {
659+
return discriminator.getPrefix().concat(transition.getAccessSequence());
660+
}
661+
662+
@Override
663+
public Word<I> getSuffix() {
664+
return discriminator.getSuffix();
665+
}
666+
}
630667
}

algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/base/AbstractTTTLearner.java

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import java.util.Objects;
2929
import java.util.Set;
3030

31-
import de.learnlib.AccessSequenceProvider;
3231
import de.learnlib.Resumable;
3332
import de.learnlib.acex.AcexAnalyzer;
3433
import de.learnlib.acex.AcexAnalyzers;
@@ -39,6 +38,7 @@
3938
import de.learnlib.logging.Category;
4039
import de.learnlib.oracle.MembershipOracle;
4140
import de.learnlib.query.DefaultQuery;
41+
import de.learnlib.query.Query;
4242
import net.automatalib.alphabet.Alphabet;
4343
import net.automatalib.alphabet.SupportsGrowingAlphabet;
4444
import net.automatalib.common.smartcollection.ElementReference;
@@ -188,19 +188,34 @@ public boolean refineHypothesis(DefaultQuery<I, D> ceQuery) {
188188
* the state to initialize
189189
*/
190190
protected void initializeState(TTTState<I, D> state) {
191+
TTTTransition<I, D> head = null;
191192
for (int i = 0; i < alphabet.size(); i++) {
192193
I sym = alphabet.getSymbol(i);
193194
TTTTransition<I, D> trans = createTransition(state, sym);
194195
trans.setNonTreeTarget(dtree.getRoot());
195196
state.setTransition(i, trans);
196197
openTransitions.add(trans);
198+
head = trans;
197199
}
200+
initTransitions(head, alphabet.size());
198201
}
199202

200203
protected TTTTransition<I, D> createTransition(TTTState<I, D> state, I sym) {
201204
return new TTTTransition<>(state, sym);
202205
}
203206

207+
/**
208+
* A post-processing hook for transitions created by {@link #createTransition(TTTState, Object)}, e.g., after
209+
* {@link #initializeState(TTTState)} or {@link #addAlphabetSymbol(Object)}. This is mainly useful for transition
210+
* output hypotheses that want to initialize the transition outputs in a bulk operation.
211+
*
212+
* @param head
213+
* the head of (the list of) the created transitions
214+
* @param num
215+
* the number of created transitions
216+
*/
217+
protected void initTransitions(TTTTransition<I, D> head, int num) {}
218+
204219
/**
205220
* Performs a single refinement of the hypothesis, i.e., without repeated counterexample evaluation. The parameter
206221
* and return value have the same significance as in {@link #refineHypothesis(DefaultQuery)}.
@@ -618,6 +633,7 @@ private Word<I> prepareSplit(AbstractBaseDTNode<I, D> node, Splitter<I, D> split
618633
Word<I> discriminator = splitter.getDiscriminator().prepend(symbol);
619634

620635
Deque<AbstractBaseDTNode<I, D>> dfsStack = new ArrayDeque<>();
636+
List<SplitQuery<I, D>> queries = new ArrayList<>();
621637

622638
AbstractBaseDTNode<I, D> succSeparator = splitter.succSeparator;
623639

@@ -631,9 +647,18 @@ private Word<I> prepareSplit(AbstractBaseDTNode<I, D> node, Splitter<I, D> split
631647
curr.setSplitData(new SplitData<>(IntrusiveList::new));
632648

633649
for (TTTTransition<I, D> trans : curr.getIncoming()) {
634-
D outcome = query(trans, discriminator);
635-
curr.getSplitData().getIncoming(outcome).add(trans);
636-
markAndPropagate(curr, outcome);
650+
queries.add(new SplitQuery<>(trans, discriminator));
651+
}
652+
653+
if (!queries.isEmpty()) {
654+
oracle.processQueries(queries);
655+
656+
for (SplitQuery<I, D> query : queries) {
657+
curr.getSplitData().getIncoming(query.output).add(query.transition);
658+
markAndPropagate(curr, query.output);
659+
}
660+
661+
queries.clear();
637662
}
638663

639664
if (curr.isInner()) {
@@ -905,34 +930,6 @@ private List<AbstractBaseDTNode<I, D>> updateDTTargets(List<TTTTransition<I, D>>
905930
return result;
906931
}
907932

908-
/**
909-
* Performs a membership query.
910-
*
911-
* @param prefix
912-
* the prefix part of the query
913-
* @param suffix
914-
* the suffix part of the query
915-
*
916-
* @return the output
917-
*/
918-
protected D query(Word<I> prefix, Word<I> suffix) {
919-
return oracle.answerQuery(prefix, suffix);
920-
}
921-
922-
/**
923-
* Performs a membership query, using an access sequence as its prefix.
924-
*
925-
* @param accessSeqProvider
926-
* the object from which to obtain the access sequence
927-
* @param suffix
928-
* the suffix part of the query
929-
*
930-
* @return the output
931-
*/
932-
protected D query(AccessSequenceProvider<I> accessSeqProvider, Word<I> suffix) {
933-
return query(accessSeqProvider.getAccessSequence(), suffix);
934-
}
935-
936933
/**
937934
* Returns the discrimination tree.
938935
*
@@ -956,14 +953,17 @@ public void addAlphabetSymbol(I symbol) {
956953
if (this.hypothesis.getInitialState() != null && this.hypothesis.getState(Word.fromLetter(symbol)) == null) {
957954

958955
final int newSymbolIdx = this.alphabet.getSymbolIndex(symbol);
956+
TTTTransition<I, D> head = null;
959957

960958
for (TTTState<I, D> s : this.hypothesis.getStates()) {
961959
final TTTTransition<I, D> trans = createTransition(s, symbol);
962960
trans.setNonTreeTarget(dtree.getRoot());
963961
s.setTransition(newSymbolIdx, trans);
964962
openTransitions.add(trans);
963+
head = trans;
965964
}
966965

966+
this.initTransitions(head, this.hypothesis.size());
967967
this.closeTransitions();
968968
}
969969
}
@@ -1076,4 +1076,31 @@ private static final class ExtractRecord<I, D> {
10761076
this.extracted = extracted;
10771077
}
10781078
}
1079+
1080+
private static final class SplitQuery<I, D> extends Query<I, D> {
1081+
1082+
private final TTTTransition<I, D> transition;
1083+
private final Word<I> discriminator;
1084+
private D output;
1085+
1086+
SplitQuery(TTTTransition<I, D> transition, Word<I> discriminator) {
1087+
this.transition = transition;
1088+
this.discriminator = discriminator;
1089+
}
1090+
1091+
@Override
1092+
public void answer(D output) {
1093+
this.output = output;
1094+
}
1095+
1096+
@Override
1097+
public Word<I> getPrefix() {
1098+
return transition.getAccessSequence();
1099+
}
1100+
1101+
@Override
1102+
public Word<I> getSuffix() {
1103+
return discriminator;
1104+
}
1105+
}
10791106
}

algorithms/active/ttt/src/main/java/de/learnlib/algorithm/ttt/mealy/TTTLearnerMealy.java

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package de.learnlib.algorithm.ttt.mealy;
1717

18+
import java.util.ArrayList;
19+
import java.util.List;
20+
1821
import de.learnlib.acex.AcexAnalyzer;
1922
import de.learnlib.acex.MealyOutInconsPrefixTransformAcex;
2023
import de.learnlib.acex.OutInconsPrefixTransformAcex;
@@ -25,8 +28,10 @@
2528
import de.learnlib.algorithm.ttt.base.OutputInconsistency;
2629
import de.learnlib.algorithm.ttt.base.TTTState;
2730
import de.learnlib.algorithm.ttt.base.TTTTransition;
31+
import de.learnlib.datastructure.list.IntrusiveListEntry;
2832
import de.learnlib.oracle.MembershipOracle;
2933
import de.learnlib.query.DefaultQuery;
34+
import de.learnlib.query.Query;
3035
import de.learnlib.tooling.annotation.builder.GenerateBuilder;
3136
import de.learnlib.util.mealy.MealyUtil;
3237
import net.automatalib.alphabet.Alphabet;
@@ -62,9 +67,21 @@ public TTTLearnerMealy(Alphabet<I> alphabet, MembershipOracle<I, Word<O>> oracle
6267

6368
@Override
6469
protected TTTTransition<I, Word<O>> createTransition(TTTState<I, Word<O>> state, I sym) {
65-
TTTTransitionMealy<I, O> trans = new TTTTransitionMealy<>(state, sym);
66-
trans.output = query(state, Word.fromLetter(sym)).firstSymbol();
67-
return trans;
70+
return new TTTTransitionMealy<>(state, sym);
71+
}
72+
73+
@Override
74+
protected void initTransitions(TTTTransition<I, Word<O>> head, int num) {
75+
final List<TransitionOutputQuery<I, O>> queries = new ArrayList<>(num);
76+
IntrusiveListEntry<TTTTransition<I, Word<O>>> iter = head;
77+
78+
for (int i = 0; i < num; i++) {
79+
assert iter != null;
80+
queries.add(new TransitionOutputQuery<>((TTTTransitionMealy<I, O>) iter.getElement()));
81+
iter = iter.getNext();
82+
}
83+
84+
oracle.processQueries(queries);
6885
}
6986

7087
@Override
@@ -151,4 +168,28 @@ protected AbstractBaseDTNode<I, Word<O>> createNewNode(AbstractBaseDTNode<I, Wor
151168
Word<O> parentOutput) {
152169
return new TTTDTNodeMealy<>(parent, parentOutput);
153170
}
171+
172+
private static final class TransitionOutputQuery<I, O> extends Query<I, Word<O>> {
173+
174+
private final TTTTransitionMealy<I, O> transition;
175+
176+
TransitionOutputQuery(TTTTransitionMealy<I, O> transition) {
177+
this.transition = transition;
178+
}
179+
180+
@Override
181+
public void answer(Word<O> output) {
182+
transition.output = output.firstSymbol();
183+
}
184+
185+
@Override
186+
public Word<I> getPrefix() {
187+
return transition.getSource().getAccessSequence();
188+
}
189+
190+
@Override
191+
public Word<I> getSuffix() {
192+
return Word.fromLetter(transition.getInput());
193+
}
194+
}
154195
}

commons/datastructures/src/main/java/de/learnlib/datastructure/discriminationtree/model/AbstractDiscriminationTree.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,11 @@ protected List<N> sift(List<N> starts, List<Word<I>> prefixes, Predicate<N> cont
102102
activeVector.set(i, continueExploring.test(result.get(i)));
103103
}
104104

105+
final List<DefaultQuery<I, O>> queries = new ArrayList<>(activeVector.cardinality());
105106
final List<Word<I>> prefixStorage = CollectionUtil.randomAccessList(prefixes);
106107

107108
while (!activeVector.isEmpty()) {
108109

109-
final List<DefaultQuery<I, O>> queries = new ArrayList<>(activeVector.cardinality());
110110
final BitSetIterator preIter = new BitSetIterator(activeVector);
111111

112112
while (preIter.hasNext()) {
@@ -130,6 +130,8 @@ protected List<N> sift(List<N> starts, List<Word<I>> prefixes, Predicate<N> cont
130130
activeVector.clear(idx);
131131
}
132132
}
133+
134+
queries.clear();
133135
}
134136

135137
return result;

0 commit comments

Comments
 (0)