Skip to content

Commit ac99e3a

Browse files
committed
algorithms: correctly handle GrowingAlphabets
Fix the behaviour of the `addAlphabetSymbol` method if the learners were initiliazed with a GrowingAlphabet already. Previously, due to sharing the alphabet instance with the hypothesis automaton, caching mechanisms prevented the correct propagation of the new alphabet symbol. (cherry picked from commit 87477d8)
1 parent 905b21d commit ac99e3a

9 files changed

Lines changed: 89 additions & 52 deletions

File tree

algorithms/active/adt/src/main/java/de/learnlib/algorithms/adt/learner/ADTLearner.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
import net.automatalib.words.Word;
6666
import net.automatalib.words.WordBuilder;
6767
import net.automatalib.words.impl.Alphabets;
68+
import net.automatalib.words.impl.SymbolHidingAlphabet;
6869

6970
/**
7071
* The main learning algorithm.
@@ -101,7 +102,7 @@ public ADTLearner(final Alphabet<I> alphabet,
101102
final ADTExtender adtExtender,
102103
final SubtreeReplacer subtreeReplacer) {
103104

104-
this.alphabet = alphabet;
105+
this.alphabet = SymbolHidingAlphabet.wrapIfMutable(alphabet);
105106
this.observationTree = new ObservationTree<>(this.alphabet);
106107
this.oracle = new SQOOTBridge<>(this.observationTree, oracle, true);
107108

@@ -370,9 +371,17 @@ public void addAlphabetSymbol(I symbol) {
370371
return;
371372
}
372373

373-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
374374
this.hypothesis.addAlphabetSymbol(symbol);
375-
this.observationTree.getObservationTree().addAlphabetSymbol(symbol);
375+
376+
SymbolHidingAlphabet.runWhileHiding(alphabet,
377+
symbol,
378+
() -> this.observationTree.getObservationTree().addAlphabetSymbol(symbol));
379+
380+
// since we share the alphabet instance with our hypothesis, our alphabet might have already been updated (if it
381+
// was already a GrowableAlphabet)
382+
if (!this.alphabet.containsSymbol(symbol)) {
383+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
384+
}
376385

377386
for (final ADTState<I, O> s : this.hypothesis.getStates()) {
378387
this.openTransitions.add(this.hypothesis.createOpenTransition(s, symbol, this.adt.getRoot()));

algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/AbstractDTLearner.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,14 @@ public void addAlphabetSymbol(I symbol) {
222222

223223
final int newSymbolIdx = this.alphabet.size();
224224

225-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
226225
this.hypothesis.addAlphabetSymbol(symbol);
227226

227+
// since we share the alphabet instance with our hypothesis, our alphabet might have already been updated (if it
228+
// was already a GrowableAlphabet)
229+
if (!this.alphabet.containsSymbol(symbol)) {
230+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
231+
}
232+
228233
for (final HState<I, D, SP, TP> s : this.hypothesis.getStates()) {
229234
final HTransition<I, D, SP, TP> newTrans = new HTransition<>(s, symbol, dtree.getRoot());
230235
s.setTransition(newSymbolIdx, newTrans);

algorithms/active/discrimination-tree/src/main/java/de/learnlib/algorithms/discriminationtree/hypothesis/DTLearnerHypothesis.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import net.automatalib.visualization.DefaultVisualizationHelper;
3232
import net.automatalib.visualization.VisualizationHelper;
3333
import net.automatalib.words.Alphabet;
34-
import net.automatalib.words.GrowingAlphabet;
3534
import net.automatalib.words.Word;
3635
import net.automatalib.words.impl.Alphabets;
3736

@@ -132,8 +131,8 @@ public HState<I, O, SP, TP> getSuccessor(HTransition<I, O, SP, TP> trans) {
132131
@Override
133132
public void addAlphabetSymbol(I symbol) {
134133

135-
if (this.alphabet instanceof GrowingAlphabet) {
136-
((GrowingAlphabet<I>) this.alphabet).addSymbol(symbol);
134+
if (this.alphabet.containsSymbol(symbol)) {
135+
return;
137136
}
138137

139138
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);

algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/dfa/KearnsVaziraniDFA.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,14 @@ public void addAlphabetSymbol(I symbol) {
267267
}
268268

269269
final int inputIdx = this.alphabet.size();
270-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
271270
this.hypothesis.addAlphabetSymbol(symbol);
272271

272+
// since we share the alphabet instance with our hypothesis, our alphabet might have already been updated (if it
273+
// was already a GrowableAlphabet)
274+
if (!this.alphabet.containsSymbol(symbol)) {
275+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
276+
}
277+
273278
// use new list to prevent concurrent modification exception
274279
for (final StateInfo<I, Boolean> si : new ArrayList<>(this.stateInfos)) {
275280
final int state = si.id;

algorithms/active/kearns-vazirani/src/main/java/de/learnlib/algorithms/kv/mealy/KearnsVaziraniMealy.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,14 @@ public void addAlphabetSymbol(I symbol) {
283283
}
284284

285285
final int inputIdx = this.alphabet.size();
286-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
287286
this.hypothesis.addAlphabetSymbol(symbol);
288287

288+
// since we share the alphabet instance with our hypothesis, our alphabet might have already been updated (if it
289+
// was already a GrowableAlphabet)
290+
if (!this.alphabet.containsSymbol(symbol)) {
291+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
292+
}
293+
289294
// use new list to prevent concurrent modification exception
290295
for (final StateInfo<I, Word<O>> si : new ArrayList<>(this.stateInfos)) {
291296
final int state = si.id;

algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractAutomatonLStar.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import net.automatalib.automata.MutableDeterministic;
2929
import net.automatalib.commons.util.collections.CollectionsUtil;
3030
import net.automatalib.words.Alphabet;
31+
import net.automatalib.words.impl.SymbolHidingAlphabet;
3132

3233
/**
3334
* Abstract base class for algorithms that produce (subclasses of) {@link MutableDeterministic} automata.
@@ -63,7 +64,7 @@ public abstract class AbstractAutomatonLStar<A, I, D, S, T, SP, TP, AI extends M
6364
* the learning oracle
6465
*/
6566
protected AbstractAutomatonLStar(Alphabet<I> alphabet, MembershipOracle<I, D> oracle, AI internalHyp) {
66-
super(alphabet, oracle);
67+
super(SymbolHidingAlphabet.wrapIfMutable(alphabet), oracle);
6768
this.internalHyp = internalHyp;
6869
internalHyp.clear();
6970
}
@@ -191,8 +192,10 @@ public void addAlphabetSymbol(I symbol) {
191192
return;
192193
}
193194

194-
super.addAlphabetSymbol(symbol);
195195
this.internalHyp.addAlphabetSymbol(symbol);
196+
197+
SymbolHidingAlphabet.runWhileHiding(alphabet, symbol, () -> super.addAlphabetSymbol(symbol));
198+
196199
this.updateInternalHypothesis();
197200
}
198201

algorithms/active/lstar/src/main/java/de/learnlib/algorithms/lstar/AbstractLStar.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,10 +232,14 @@ public void addAlphabetSymbol(I symbol) {
232232
return;
233233
}
234234

235-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
236-
237235
final List<List<Row<I>>> unclosed = this.table.addAlphabetSymbol(symbol, oracle);
238236

237+
// since we share the alphabet instance with our observation table, our alphabet might have already been updated
238+
// (if it was already a GrowableAlphabet)
239+
if (!this.alphabet.containsSymbol(symbol)) {
240+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
241+
}
242+
239243
completeConsistentTable(unclosed, true);
240244
}
241245
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,9 +949,14 @@ public void addAlphabetSymbol(I symbol) {
949949

950950
final int newSymbolIdx = this.alphabet.size();
951951

952-
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
953952
this.hypothesis.addAlphabetSymbol(symbol);
954953

954+
// since we share the alphabet instance with our hypothesis, our alphabet might have already been updated (if it
955+
// was already a GrowableAlphabet)
956+
if (!this.alphabet.containsSymbol(symbol)) {
957+
this.alphabet = Alphabets.withNewSymbol(this.alphabet, symbol);
958+
}
959+
955960
for (final TTTState<I, D> s : this.hypothesis.getStates()) {
956961
final TTTTransition<I, D> trans = createTransition(s, symbol);
957962
trans.setNonTreeTarget(dtree.getRoot());

test-support/learner-it-support/src/main/java/de/learnlib/testsupport/AbstractGrowingAlphabetTest.java

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import java.util.ArrayList;
1919
import java.util.Collection;
2020
import java.util.List;
21-
import java.util.Random;
2221

2322
import de.learnlib.api.algorithm.LearningAlgorithm;
2423
import de.learnlib.api.algorithm.feature.SupportsGrowingAlphabet;
@@ -28,7 +27,6 @@
2827
import net.automatalib.util.automata.Automata;
2928
import net.automatalib.words.Alphabet;
3029
import net.automatalib.words.Word;
31-
import net.automatalib.words.WordBuilder;
3230
import net.automatalib.words.impl.Alphabets;
3331
import net.automatalib.words.impl.SimpleAlphabet;
3432
import org.testng.Assert;
@@ -43,24 +41,19 @@
4341
*/
4442
public abstract class AbstractGrowingAlphabetTest<L extends SupportsGrowingAlphabet<I> & LearningAlgorithm<M, I, D>, M extends UniversalDeterministicAutomaton<?, I, ?, ?, ?> & Output<I, D>, OR, I, D> {
4543

46-
private static final int MAX_VERIFICATION_TRIES = 100;
4744
protected static final int RANDOM_SEED = 42;
4845
protected static final int DEFAULT_AUTOMATON_SIZE = 15;
4946

50-
private Random random;
51-
5247
private M target;
5348

54-
private L learner;
49+
private OR oracle;
5550

5651
private Alphabet<I> initialAlphabet;
5752

5853
private Collection<I> alphabetExtensions;
5954

6055
@BeforeClass
6156
public void setup() {
62-
random = new Random(RANDOM_SEED);
63-
6457
initialAlphabet = getInitialAlphabet();
6558
alphabetExtensions = getAlphabetExtensions();
6659

@@ -69,9 +62,7 @@ public void setup() {
6962
compoundAlphabet.addAll(alphabetExtensions);
7063

7164
target = getTarget(Alphabets.fromList(compoundAlphabet));
72-
final OR oracle = getOracle(target);
73-
74-
learner = getLearner(oracle, initialAlphabet);
65+
oracle = getOracle(target);
7566
}
7667

7768
protected abstract Alphabet<I> getInitialAlphabet();
@@ -86,11 +77,41 @@ public void setup() {
8677

8778
@Test
8879
public void testInitialAlphabet() {
80+
testAlphabet(initialAlphabet);
81+
}
82+
83+
/**
84+
* In case of passing a growing alphabet, the learners may use the existing
85+
* {@link net.automatalib.words.GrowingAlphabet#addSymbol(Object)} functionality. Due to references, this may alter
86+
* their behavior. Check it!
87+
*/
88+
@Test
89+
public void testGrowingAlphabetAlphabet() {
90+
testAlphabet(new SimpleAlphabet<>(initialAlphabet));
91+
}
92+
93+
private void testAlphabet(Alphabet<I> alphabet) {
94+
final L learner = getLearner(oracle, alphabet);
95+
8996
learner.startLearning();
90-
this.performLearnLoop(initialAlphabet);
97+
this.performLearnLoopAndCheck(learner, alphabet);
98+
99+
final List<I> currentAlphabet = new ArrayList<>(alphabet.size() + alphabetExtensions.size());
100+
currentAlphabet.addAll(alphabet);
101+
102+
for (final I i : alphabetExtensions) {
103+
currentAlphabet.add(i);
104+
learner.addAlphabetSymbol(i);
105+
106+
final UniversalDeterministicAutomaton<?, I, ?, ?, ?> hyp = learner.getHypothesisModel();
107+
108+
this.checkCompletenessOfHypothesis(hyp, currentAlphabet);
109+
this.performLearnLoopAndCheck(learner, currentAlphabet);
110+
}
111+
91112
}
92113

93-
private void performLearnLoop(final Alphabet<I> effectiveAlphabet) {
114+
private void performLearnLoopAndCheck(final L learner, final Collection<? extends I> effectiveAlphabet) {
94115

95116
M hyp = learner.getHypothesisModel();
96117
Word<I> sepWord = Automata.findSeparatingWord(target, hyp, effectiveAlphabet);
@@ -108,33 +129,14 @@ private void performLearnLoop(final Alphabet<I> effectiveAlphabet) {
108129
Assert.assertTrue(Automata.testEquivalence(target, hyp, effectiveAlphabet));
109130
}
110131

111-
@Test(dependsOnMethods = "testInitialAlphabet")
112-
public void testAddingAlphabetSymbols() {
113-
final Alphabet<I> currentAlphabet = new SimpleAlphabet<>(initialAlphabet);
114-
115-
for (final I i : alphabetExtensions) {
116-
currentAlphabet.add(i);
117-
learner.addAlphabetSymbol(i);
118-
119-
this.checkCompletenessOfHypothesis(currentAlphabet);
120-
this.performLearnLoop(currentAlphabet);
121-
}
122-
}
123-
124-
private void checkCompletenessOfHypothesis(final Alphabet<I> alphabet) {
132+
private <S, T> void checkCompletenessOfHypothesis(final UniversalDeterministicAutomaton<S, I, T, ?, ?> hypothesis, final Collection<? extends I> alphabet) {
133+
for (final S s : hypothesis.getStates()) {
134+
for (final I i : alphabet) {
135+
final T trans = hypothesis.getTransition(s, i);
125136

126-
final int alphabetSize = alphabet.size();
127-
final M hypothesis = learner.getHypothesisModel();
128-
129-
for (int i = 0; i < MAX_VERIFICATION_TRIES; i++) {
130-
final WordBuilder<I> testTraceBuilder = new WordBuilder<>(i);
131-
132-
for (int j = 0; j < i; j++) {
133-
testTraceBuilder.add(alphabet.getSymbol(random.nextInt(alphabetSize)));
137+
Assert.assertNotNull(trans);
138+
Assert.assertNotNull(hypothesis.getSuccessor(trans));
134139
}
135-
136-
// simply try to compute output without an exception
137-
hypothesis.computeOutput(testTraceBuilder.toWord());
138140
}
139141
}
140142

0 commit comments

Comments
 (0)