1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
|
package basiclearner;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Calendar;
import java.util.Collection;
import java.util.Random;
import net.automatalib.automata.transout.MealyMachine;
import net.automatalib.commons.dotutil.DOT;
import net.automatalib.util.graphs.dot.GraphDOT;
import net.automatalib.words.Alphabet;
import net.automatalib.words.Word;
import net.automatalib.words.impl.SimpleAlphabet;
import com.google.common.collect.Lists;
import de.learnlib.acex.analyzers.AcexAnalyzers;
import de.learnlib.algorithms.kv.mealy.KearnsVaziraniMealy;
import de.learnlib.algorithms.lstargeneric.ce.ObservationTableCEXHandlers;
import de.learnlib.algorithms.lstargeneric.closing.ClosingStrategies;
import de.learnlib.algorithms.lstargeneric.mealy.ExtensibleLStarMealy;
import de.learnlib.algorithms.ttt.mealy.TTTLearnerMealy;
import de.learnlib.api.EquivalenceOracle;
import de.learnlib.api.LearningAlgorithm;
import de.learnlib.api.MembershipOracle.MealyMembershipOracle;
import de.learnlib.api.SUL;
import de.learnlib.eqtests.basic.WMethodEQOracle;
import de.learnlib.eqtests.basic.WpMethodEQOracle;
import de.learnlib.eqtests.basic.mealy.RandomWalkEQOracle;
import de.learnlib.experiments.Experiment.MealyExperiment;
import de.learnlib.oracles.DefaultQuery;
import de.learnlib.oracles.ResetCounterSUL;
import de.learnlib.oracles.SULOracle;
import de.learnlib.oracles.SymbolCounterSUL;
import de.learnlib.statistics.Counter;
/**
* General learning testing framework. All basic settings are at the top of this file and can be configured
* by hard-coding or by simply changing them from your own code.
*
* Based on the learner experiment setup of Joshua Moerman, https://gitlab.science.ru.nl/moerman/Learnlib-Experiments
*
* @author Ramon Janssen
*/
public class BasicLearner {
//***********************************************************************************//
// Learning settings (hardcoded, simply set to a different value to change learning) //
//***********************************************************************************//
/**
* name to give to the resulting .dot-file and .pdf-file (extensions are added automatically)
*/
public static String
FINAL_MODEL_FILENAME = "learnedModel",
INTERMEDIATE_HYPOTHESIS_FILENAME = "hypothesis";
/**
* For controlled experiments only: store every hypotheses as a file. Useful for 'debugging'
* if the learner does not terminate (hint: the TTT-algorithm produces many hypotheses).
*/
public static boolean saveAllHypotheses = true;
/**
* For random walk, the chance to reset after every input
*/
public static double randomWalk_chanceOfResetting = 0.1;
/**
* For random walk, the number of symbols that is tested in total (divided over multiple traces).
*/
public static int randomWalk_numberOfSymbols = 300;
/**
* MaxDepth-parameter for W-method and Wp-method. Typically not larger than 3. Decrease for quicker runs.
*/
public static int w_wp_methods_maxDepth = 2;
//*****************************************//
// Predefined learning and testing methods //
//*****************************************//
/**
* The learning algorithms. LStar is the basic algorithm, TTT performs much faster
* but is a bit more inaccurate and produces more intermediate hypotheses, so test well)
*/
public enum LearningMethod { LStar, RivestSchapire, TTT, KearnsVazirani }
/**
* The testing algorithms. Random walk is the simplest, but performs badly on large models:
* the chance of hitting a erroneous long trace is very small. WMethod and WpMethod are
* smarter. UserQueries asks the user for which inputs to try as counter-example: have a
* look at the hypothesis, and try to think of one
*/
public enum TestingMethod { RandomWalk, WMethod, WpMethod, UserQueries }
public static LearningAlgorithm<MealyMachine<?, String, ?, String>, String, Word<String>> loadLearner(
LearningMethod learningMethod, MealyMembershipOracle<String,String> sulOracle, Alphabet<String> alphabet) {
switch (learningMethod){
case LStar:
return new ExtensibleLStarMealy<String, String>(alphabet, sulOracle, Lists.<Word<String>>newArrayList(), ObservationTableCEXHandlers.CLASSIC_LSTAR, ClosingStrategies.CLOSE_SHORTEST);
case RivestSchapire:
return new ExtensibleLStarMealy<String, String>(alphabet, sulOracle, Lists.<Word<String>>newArrayList(), ObservationTableCEXHandlers.RIVEST_SCHAPIRE, ClosingStrategies.CLOSE_SHORTEST);
case TTT:
return new TTTLearnerMealy<String, String>(alphabet, sulOracle, AcexAnalyzers.LINEAR_FWD);
case KearnsVazirani:
return new KearnsVaziraniMealy<String, String>(alphabet, sulOracle, false, AcexAnalyzers.LINEAR_FWD);
default:
throw new RuntimeException("No learner selected");
}
}
public static EquivalenceOracle<MealyMachine<?, String, ?, String>, String, Word<String>> loadTester(
TestingMethod testMethod, SUL<String,String> sul, MealyMembershipOracle<String,String> sulOracle) {
switch (testMethod){
// simplest method, but doesn't perform well in practice, especially for large models
case RandomWalk:
return new RandomWalkEQOracle<>(randomWalk_chanceOfResetting, randomWalk_numberOfSymbols, true, new Random(123456l), sul);
// Other methods are somewhat smarter than random testing: state coverage, trying to distinguish states, etc.
case WMethod:
return new WMethodEQOracle.MealyWMethodEQOracle<>(w_wp_methods_maxDepth, sulOracle);
case WpMethod:
return new WpMethodEQOracle.MealyWpMethodEQOracle<>(w_wp_methods_maxDepth, sulOracle);
case UserQueries:
return new UserEQOracle(sul);
default:
throw new RuntimeException("No test oracle selected!");
}
}
/**
* Simple example of running a learning experiment
* @param learner Learning algorithm, wrapping the SUL
* @param eqOracle Testing algorithm, wrapping the SUL
* @param alphabet Input alphabet
* @throws IOException if the result cannot be written
*/
public static void runSimpleExperiment(
LearningAlgorithm<MealyMachine<?, String, ?, String>, String, Word<String>> learner,
EquivalenceOracle<MealyMachine<?, String, ?, String>, String, Word<String>> eqOracle,
Alphabet<String> alphabet) throws IOException {
MealyExperiment<String, String> experiment
= new MealyExperiment<String, String>(learner, eqOracle, alphabet);
experiment.run();
System.out.println("Ran " + experiment.getRounds().getCount() + " rounds");
produceOutput(FINAL_MODEL_FILENAME, experiment.getFinalHypothesis(), alphabet, true);
}
/**
* Simple example of running a learning experiment
* @param sul Direct access to SUL
* @param learningMethod One of the default learning methods from this class
* @param testingMethod One of the default testing methods from this class
* @param alphabet Input alphabet
* @throws IOException if the result cannot be written
*/
public static void runSimpleExperiment (
SUL<String,String> sul,
LearningMethod learningMethod,
TestingMethod testingMethod,
Collection<String> alphabet
) throws IOException {
Alphabet<String> learlibAlphabet = new SimpleAlphabet<String>(alphabet);
LearningSetup learningSetup = new LearningSetup(sul, learningMethod, testingMethod, learlibAlphabet);
runSimpleExperiment(learningSetup.learner, learningSetup.eqOracle, learlibAlphabet);
}
/**
* More detailed example of running a learning experiment. Starts learning, and then loops testing,
* and if counterexamples are found, refining again. Also prints some statistics about the experiment
* @param learner learner Learning algorithm, wrapping the SUL
* @param eqOracle Testing algorithm, wrapping the SUL
* @param nrSymbols A counter for the number of symbols that have been sent to the SUL (for statistics)
* @param nrResets A counter for the number of resets that have been sent to the SUL (for statistics)
* @param alphabet Input alphabet
* @throws IOException
*/
public static void runControlledExperiment(
LearningAlgorithm<MealyMachine<?, String, ?, String>, String, Word<String>> learner,
EquivalenceOracle<MealyMachine<?, String, ?, String>, String, Word<String>> eqOracle,
Counter nrSymbols, Counter nrResets,
Alphabet<String> alphabet) throws IOException {
try {
// prepare some counters for printing statistics
int stage = 0;
long lastNrResetsValue = 0, lastNrSymbolsValue = 0;
// start the actual learning
learner.startLearning();
while(true) {
// store hypothesis as file
if(saveAllHypotheses) {
String outputFilename = INTERMEDIATE_HYPOTHESIS_FILENAME + stage;
produceOutput(outputFilename, learner.getHypothesisModel(), alphabet, false);
System.out.println("model size " + learner.getHypothesisModel().getStates().size());
}
// Print statistics
System.out.println(stage + ": " + Calendar.getInstance().getTime());
// Log number of queries/symbols
System.out.println("Hypothesis size: " + learner.getHypothesisModel().size() + " states");
long roundResets = nrResets.getCount() - lastNrResetsValue, roundSymbols = nrSymbols.getCount() - lastNrSymbolsValue;
System.out.println("learning queries/symbols: " + nrResets.getCount() + "/" + nrSymbols.getCount()
+ "(" + roundResets + "/" + roundSymbols + " this learning round)");
lastNrResetsValue = nrResets.getCount();
lastNrSymbolsValue = nrSymbols.getCount();
// Search for CE
DefaultQuery<String, Word<String>> ce = eqOracle.findCounterExample(learner.getHypothesisModel(), alphabet);
// Log number of queries/symbols
roundResets = nrResets.getCount() - lastNrResetsValue;
roundSymbols = nrSymbols.getCount() - lastNrSymbolsValue;
System.out.println("testing queries/symbols: " + nrResets.getCount() + "/" + nrSymbols.getCount()
+ "(" + roundResets + "/" + roundSymbols + " this testing round)");
lastNrResetsValue = nrResets.getCount();
lastNrSymbolsValue = nrSymbols.getCount();
if(ce == null) {
// No counterexample found, stop learning
System.out.println("\nFinished learning!");
produceOutput(FINAL_MODEL_FILENAME, learner.getHypothesisModel(), alphabet, true);
break;
} else {
// Counterexample found, rinse and repeat
System.out.println();
stage++;
learner.refineHypothesis(ce);
}
}
} catch (Exception e) {
String errorHypName = "hyp.before.crash.dot";
produceOutput(errorHypName, learner.getHypothesisModel(), alphabet, true);
throw e;
}
}
/**
* More detailed example of running a learning experiment. Starts learning, and then loops testing,
* and if counterexamples are found, refining again. Also prints some statistics about the experiment
* @param sul Direct access to SUL
* @param learningMethod One of the default learning methods from this class
* @param testingMethod One of the default testing methods from this class
* @param alphabet Input alphabet
* @param alphabet Input alphabet
* @throws IOException
*/
public static void runControlledExperiment(
SUL<String,String> sul,
LearningMethod learningMethod,
TestingMethod testingMethod,
Collection<String> alphabet
) throws IOException {
Alphabet<String> learnlibAlphabet = new SimpleAlphabet<String>(alphabet);
LearningSetup learningSetup = new LearningSetup(sul, learningMethod, testingMethod, learnlibAlphabet);
runControlledExperiment(learningSetup.learner, learningSetup.eqOracle, learningSetup.nrSymbols, learningSetup.nrResets, learnlibAlphabet);
}
/**
* Produces a dot-file and a PDF (if graphviz is installed)
* @param fileName filename without extension - will be used for the .dot and .pdf
* @param model
* @param alphabet
* @param verboseError whether to print an error explaing that you need graphviz
* @throws FileNotFoundException
* @throws IOException
*/
public static void produceOutput(String fileName, MealyMachine<?,String,?,String> model, Alphabet<String> alphabet, boolean verboseError) throws FileNotFoundException, IOException {
PrintWriter dotWriter = new PrintWriter(fileName + ".dot");
GraphDOT.write(model, alphabet, dotWriter);
try {
DOT.runDOT(new File(fileName + ".dot"), "pdf", new File(fileName + ".pdf"));
} catch (Exception e) {
if (verboseError) {
System.err.println("Warning: Install graphviz to convert dot-files to PDF");
System.err.println(e.getMessage());
}
}
dotWriter.close();
}
/**
* Helper class to configure a learning and equivalence oracle. Tell it which learning and testing method you
* want, and it produces the corresponding oracles (and counters for statistics) as attributes.
*/
public static class LearningSetup {
public final EquivalenceOracle<MealyMachine<?, String, ?, String>, String, Word<String>> eqOracle;
public final LearningAlgorithm<MealyMachine<?, String, ?, String>, String, Word<String>> learner;
public final Counter nrSymbols, nrResets;
public LearningSetup(SUL<String,String> sul, LearningMethod learningMethod, TestingMethod testingMethod, Alphabet<String> alphabet) {
// Wrap the SUL in a detector for non-determinism
SUL<String,String> nonDetSul = new NonDeterminismCheckingSUL<String,String>(sul);
// Wrap the SUL in counters for symbols/resets, so that we can record some statistics
SymbolCounterSUL<String, String> symbolCounterSul = new SymbolCounterSUL<>("symbol counter", nonDetSul);
ResetCounterSUL<String, String> resetCounterSul = new ResetCounterSUL<>("reset counter", symbolCounterSul);
nrSymbols = symbolCounterSul.getStatisticalData();
nrResets = resetCounterSul.getStatisticalData();
// we should use the sul only through those wrappers
sul = resetCounterSul;
// Most testing/learning-algorithms want a membership-oracle instead of a SUL directly
MealyMembershipOracle<String,String> sulOracle = new SULOracle<>(sul);
// Choosing an equivalence oracle
eqOracle = loadTester(testingMethod, sul, sulOracle);
// Choosing a learner
learner = loadLearner(learningMethod, sulOracle, alphabet);
}
}
}
|