diff options
author | René den Hertog | 2017-10-26 17:34:51 +0200 |
---|---|---|
committer | René den Hertog | 2017-10-26 17:34:51 +0200 |
commit | ac914c719d401c44cb2633127952b022bf563ff3 (patch) | |
tree | f7d2399b2ab6cddb86aa996096581a397ff407b9 /assignments/assignment2/Bounded Retransmission Protocol Tester/Source | |
parent | Finish assignment 2, part 1 (diff) |
'Bounded Retransmission Protocol' Blob
Diffstat (limited to 'assignments/assignment2/Bounded Retransmission Protocol Tester/Source')
12 files changed, 1182 insertions, 0 deletions
diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/Main.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/Main.java new file mode 100644 index 0000000..e991026 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/Main.java @@ -0,0 +1,189 @@ +import basiclearner.BasicLearner; +import basiclearner.SocketSUL; +import brpcompare.BRPCompare; +import com.google.common.base.Stopwatch; +import com.google.common.collect.ImmutableSet; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.net.InetAddress; +import java.time.LocalDateTime; +import java.util.Collection; +import java.util.concurrent.TimeUnit; +import java.util.Scanner; + +public class Main { + + public static void main(String[] arguments) throws Exception { + test("Reference"); + + for (int index = 1; index <= 6; index++) + test(Integer.toString(index)); + + System.out.println("###########"); + } + + private static void test(String name) throws Exception { + display("########### Testing '" + name + "' ###########"); + + display("Now it was " + LocalDateTime.now() + "."); + Stopwatch stopWatch = Stopwatch.createStarted(); + + learn(name); + + checkOutPutOfFinalModelOf(name); + + if (!name.equals("Reference")) + checkFinalModelOf(name); + + display("==========="); + + stopWatch.stop(); + display( "Now it was " + LocalDateTime.now() + ".\n" + + "Testing '" + name + "' took " + stopWatch + "." + ); + } + + private static void learn(String name) throws Exception { + display("=========== Learning '" + name + "' ==========="); + + File directory = new File( new File(System.getProperty("user.dir")) + , "Bounded Retransmission Protocol Implementations" + ); + // Throws : IOException + Process process = Runtime.getRuntime().exec("java -jar " + name + ".jar", null, directory); + display( "Started \"" + name + ".jar\" in \"" + directory + "\".\n" + + "Waiting a while for it to get ready." + ); + // Throws : InterruptedException + process.waitFor(1, TimeUnit.MINUTES); + display("Assuming \"" + name + ".jar\" is ready."); + + InetAddress internetProtocolAddress = InetAddress.getLoopbackAddress(); + int port = parsePort(new BufferedReader(new InputStreamReader(process.getInputStream()))); + // Throws : UnknownHostException + SocketSUL system = new SocketSUL( internetProtocolAddress + , port + , true + , "reset" + ); + display("Created socket system @ " + internetProtocolAddress + " : " + port + "."); + + display("~~~~~~~~~~~"); + + // Default : BasicLearner.randomWalk_numberOfSymbols = 300; + BasicLearner.randomWalk_numberOfSymbols = 1001; + // Default : BasicLearner.randomWalk_chanceOfResetting = 0.1; + BasicLearner.randomWalk_chanceOfResetting = 0.01; + BasicLearner.INTERMEDIATE_HYPOTHESIS_FILENAME = "Hypothesis Model Of " + name + " "; + BasicLearner.FINAL_MODEL_FILENAME = "Final Model Of " + name; + Collection<String> input = ImmutableSet.of( "IACK" + , "IREQ_0_0_0" + , "IREQ_0_0_1" + , "IREQ_0_1_0" + , "IREQ_0_1_1" + , "IREQ_1_0_0" + , "IREQ_1_0_1" + , "IREQ_1_1_0" + , "IREQ_1_1_1" + , "ISENDFRAME" + , "ITIMEOUT" + ); + // Throws : IOException + BasicLearner.runControlledExperiment( system + , BasicLearner.LearningMethod.LStar + , BasicLearner.TestingMethod.RandomWalk + , input + ); + + System.out.println(); + display("~~~~~~~~~~~"); + + // Throws : Exception + system.close(); + display("Closed socket system."); + + process.destroy(); + display( "Stopped \"" + name + ".jar\" gracefully.\n" + + "Waiting (a while) for it to stop gracefully." + ); + // Throws : InterruptedException + if (process.waitFor(1, TimeUnit.MINUTES)) + display("\"" + name + ".jar\" stopped gracefully."); + else { + display( "Assuming \"" + name + ".jar\" is not responding.\n" + + "Stopping it forcibly." + ); + process.destroyForcibly(); + display( "Stopped \"" + name + ".jar\" forcibly.\n" + + "Waiting (a while) for it to stop forcibly." + ); + // Throws : InterruptedException + if (process.waitFor(1, TimeUnit.MINUTES)) + display("\"" + name + ".jar\" stopped forcibly."); + else + display("Assuming \"" + name + ".jar\" stopped forcibly."); + } + } + + private static int parsePort(BufferedReader outPut) throws Exception { + String line = ""; + for (int index = 1; index <= 4; index ++) + // Throws : IOException + line = outPut.readLine(); + + return new Scanner(line).useDelimiter("[^0-9]+").nextInt(); + } + + private static void checkOutPutOfFinalModelOf(String name) throws Exception { + display("=========== Checking The Out Put Of The Final Model Of '" + name + "' ==========="); + + // Throws : FileNotFoundException + Scanner scanner = new Scanner(new File( new File(System.getProperty("user.dir")) + , "Final Model Of " + name + ".dot" + )); + boolean hasOCONF_0 = false, hasOCONF_1 = false, hasOCONF_2 = false; + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + if (!hasOCONF_0 && line.contains("OCONF_0")) + hasOCONF_0 = true; + if (!hasOCONF_1 && line.contains("OCONF_1")) + hasOCONF_1 = true; + if (!hasOCONF_2 && line.contains("OCONF_2")) + hasOCONF_2 = true; + if (hasOCONF_0 && hasOCONF_1 && hasOCONF_2) { + display( "The final model of '" + + name + + "' contains OCONF_0, OCONF_1 & OCONF_2 as out puts." + ); + return; + } + } + System.out.println("The final model of '" + name + "' does not contain ___ as out put."); + if (!hasOCONF_0) + System.out.println("OCONF_0"); + if (!hasOCONF_1) + System.out.println("OCONF_1"); + if (!hasOCONF_2) + System.out.println("OCONF_2"); + System.out.println(); + } + + private static void checkFinalModelOf(String name) throws Exception { + display("=========== Checking The Final Model Of '" + name + "' ==========="); + + display("~~~~~~~~~~~"); + + String[] arguments = {name}; + // Throws : IOException + BRPCompare.main(arguments); + + System.out.println(); + display("~~~~~~~~~~~"); + } + + private static void display(String string) { + System.out.println(string); + System.out.println(); + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/BasicLearner.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/BasicLearner.java new file mode 100644 index 0000000..4a3b6bc --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/BasicLearner.java @@ -0,0 +1,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); + } + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/CacheInconsistencyException.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/CacheInconsistencyException.java new file mode 100644 index 0000000..67c2e73 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/CacheInconsistencyException.java @@ -0,0 +1,60 @@ +package basiclearner; + +import net.automatalib.words.Word; + +/** + * Contains the full input for which non-determinism was observed, as well as the full new output + * and the (possibly shorter) old output with which it disagrees + * + * @author Ramon Janssen + */ +public class CacheInconsistencyException extends RuntimeException { + private final Word oldOutput, newOutput, input; + + public CacheInconsistencyException(Word input, Word oldOutput, Word newOutput) { + this.input = input; + this.oldOutput = oldOutput; + this.newOutput = newOutput; + } + + public CacheInconsistencyException(String message, Word input, Word oldOutput, Word newOutput) { + super(message); + this.input = input; + this.oldOutput = oldOutput; + this.newOutput = newOutput; + } + + + /** + * The shortest cached output word which does not correspond with the new output + * @return + */ + public Word getOldOutput() { + return this.oldOutput; + } + + /** + * The full new output word + * @return + */ + public Word getNewOutput() { + return this.newOutput; + } + + /** + * The shortest sublist of the input word which still shows non-determinism + * @return + */ + public Word getShortestInconsistentInput() { + int indexOfInconsistency = 0; + while (oldOutput.getSymbol(indexOfInconsistency).equals(newOutput.getSymbol(indexOfInconsistency))) { + indexOfInconsistency ++; + } + return this.input.subWord(0, indexOfInconsistency); + } + + @Override + public String toString() { + return "Non-determinism detected\nfull input:\n" + this.input + "\nfull new output:\n" + this.newOutput + "\nold output:\n" + this.oldOutput; + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleExperiment.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleExperiment.java new file mode 100644 index 0000000..3f67622 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleExperiment.java @@ -0,0 +1,41 @@ +package basiclearner; + +import com.google.common.collect.ImmutableSet; +import de.learnlib.api.SUL; + +import java.io.IOException; +import java.util.Collection; + +/** + * Created by ramon on 13-12-16. + */ +public class ExampleExperiment { + /** + * Example of how to call a learner in a simple way with this class. Learns the ExampleSUL. + * @param args + * @throws IOException + */ + public static void main(String [] args) throws IOException { + // Load the actual SUL-class + // For a SUL over a socket, use the SocketSUL-class + // You can also program an own SUL-class if you extend SUL<String,String> (or SUL<S,T> in + // general, with S and T the input and output types - but this class assumes strings) + SUL<String,String> sul = new ExampleSUL(); + + // the input alphabet + Collection<String> inputAlphabet = ImmutableSet.of("a", "b", "c"); + + try { + // runControlledExperiment for detailed statistics, runSimpleExperiment for just the result + BasicLearner.runControlledExperiment(sul, BasicLearner.LearningMethod.LStar, BasicLearner.TestingMethod.RandomWalk, inputAlphabet); + } finally { + if (sul instanceof AutoCloseable) { + try { + ((AutoCloseable) sul).close(); + } catch (Exception exception) { + // should not happen + } + } + } + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleSUL.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleSUL.java new file mode 100644 index 0000000..8cfdbac --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ExampleSUL.java @@ -0,0 +1,90 @@ +package basiclearner; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * Example of a three-state system, hard-coded. + * + * @author Ramon Janssen + */ +public class ExampleSUL implements SUL<String, String> { + private enum State{s0,s1,s2}; + private State currentState; + private static boolean VERBOSE = false; + + @Override + public void pre() { + // add any code here that should be run at the beginning of every 'session', + // i.e. put the system in its initial state + if (VERBOSE) { + System.out.println("Starting SUL"); + } + currentState = State.s0; + } + + @Override + public void post() { + // add any code here that should be run at the end of every 'session' + if (VERBOSE) { + System.out.println("Shutting down SUL"); + } + } + + @Override + public String step(String input) throws SULException { + State previousState = this.currentState; + String output = makeTransition(input); + State nextState = this.currentState; + if (VERBOSE) { + System.out.println(previousState + " --" + input + "/" + output + "-> " + nextState); + } + return output; + } + + /** + * The behaviour of the SUL. It takes one input, and returns an output. It now + * contains a hardcoded state-machine (so the result is easy to check). To learn + * an external program/system, connect this method to the SUL (e.g. via sockets + * or stdin/stdout) and make it perform an actual input, and retrieve an actual + * output. + * @param input + * @return + */ + public String makeTransition(String input) { + switch (currentState) { + case s0: + switch(input) { + case "a": + currentState = State.s1; + return "x"; + case "b": + currentState = State.s2; + return "y"; + case "c": + return "z"; + } + case s1: + switch(input) { + case "a": + return "z"; + case "b": + currentState = State.s2; + return "y"; + case "c": + return "z"; + } + case s2: + switch(input) { + case "a": + return "z"; + case "b": + currentState = State.s0; + return "y"; + case "c": + return "z"; + } + } + throw new SULException(new IllegalArgumentException("Argument '" + input + "' was not handled")); + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/NonDeterminismCheckingSUL.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/NonDeterminismCheckingSUL.java new file mode 100644 index 0000000..d4ea603 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/NonDeterminismCheckingSUL.java @@ -0,0 +1,48 @@ +package basiclearner; + +import java.util.ArrayList; +import java.util.List; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * SUL-wrapper to check for non-determinism, by use of an observation tree. + * + * @author Ramon Janssen + * + * @param <I> + * @param <O> + */ +public class NonDeterminismCheckingSUL<I,O> implements SUL<I,O> { + private final SUL<I,O> sul; + private final ObservationTree<I,O> root = new ObservationTree<I,O>(); + private final List<I> inputs = new ArrayList<>(); + private final List<O> outputs = new ArrayList<>(); + + public NonDeterminismCheckingSUL(SUL<I,O> sul) { + this.sul = sul; + } + + @Override + public void post() { + sul.post(); + // check for non-determinism: crashes if outputs are inconsistent with previous ones + root.addObservation(inputs, outputs); + inputs.clear(); + outputs.clear(); + } + + @Override + public void pre() { + sul.pre(); + } + + @Override + public O step(I input) throws SULException { + O result = sul.step(input); + inputs.add(input); + outputs.add(result); + return result; + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ObservationTree.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ObservationTree.java new file mode 100644 index 0000000..9cb6ec6 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/ObservationTree.java @@ -0,0 +1,118 @@ +package basiclearner; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import net.automatalib.words.Word; + +/** + * @author Ramon Janssen + * + * @param <I> the input type of the observations + * @param <O> the output type of the observations + */ +public class ObservationTree<I,O> { + private final ObservationTree<I,O> parent; + private final I parentInput; + private final O parentOutput; + private final Map<I, ObservationTree<I,O>> children; + private final Map<I, O> outputs; + + public ObservationTree() { + this(null, null, null); + } + + private ObservationTree(ObservationTree<I,O> parent, I parentInput, O parentOutput) { + this.children = new HashMap<>(); + this.outputs = new HashMap<>(); + this.parent = parent; + this.parentInput = parentInput; + this.parentOutput = parentOutput; + } + + /** + * @return The outputs observed from the root of the tree until this node + */ + private List<O> getOutputChain() { + if (this.parent == null) { + return new LinkedList<O>(); + } else { + List<O> parentChain = this.parent.getOutputChain(); + parentChain.add(parentOutput); + return parentChain; + } + } + + private List<I> getInputChain() { + if (this.parent == null) { + return new LinkedList<I>(); + } else { + List<I> parentChain = this.parent.getInputChain(); + parentChain.add(this.parentInput); + return parentChain; + } + } + + /** + * Add one input and output symbol and traverse the tree to the next node + * @param input + * @param output + * @return the next node + * @throws InconsistencyException + */ + public ObservationTree<I,O> addObservation(I input, O output) throws CacheInconsistencyException { + O previousOutput = this.outputs.get(input); + boolean createNewBranch = previousOutput == null; + if (createNewBranch) { + // input hasn't been queried before, make a new branch for it and traverse + this.outputs.put(input, output); + ObservationTree<I,O> child = new ObservationTree<I,O>(this, input, output); + this.children.put(input, child); + return child; + } else if (!previousOutput.equals(output)) { + // input is inconsistent with previous observations, throw exception + List<O> oldOutputChain = this.children.get(input).getOutputChain(); + List<O> newOutputChain = this.getOutputChain(); + List<I> inputChain = this.getInputChain(); + newOutputChain.add(output); + throw new CacheInconsistencyException(toWord(inputChain), toWord(oldOutputChain), toWord(newOutputChain)); + } else { + // input is consistent with previous observations, just traverse + return this.children.get(input); + } + } + + /** + * Add Observation to the tree + * @param inputs + * @param outputs + * @throws CacheInconsistencyException Inconsistency between new and stored observations + */ + public void addObservation(Word<I> inputs, Word<O> outputs) throws CacheInconsistencyException { + addObservation(inputs.asList(), outputs.asList()); + } + + + public void addObservation(List<I> inputs, List<O> outputs) throws CacheInconsistencyException { + if (inputs.isEmpty() && outputs.isEmpty()) { + return; + } else if (inputs.isEmpty() || outputs.isEmpty()) { + throw new RuntimeException("Input and output words should have the same length:\n" + inputs + "\n" + outputs); + } else { + I firstInput = inputs.get(0); + O firstOutput = outputs.get(0); + try { + this.addObservation(firstInput, firstOutput) + .addObservation(inputs.subList(1, inputs.size()), outputs.subList(1, outputs.size())); + } catch (CacheInconsistencyException e) { + throw new CacheInconsistencyException(toWord(inputs), e.getOldOutput(), toWord(outputs)); + } + } + } + + public static<T> Word<T> toWord(List<T> symbolList) { + return Word.fromList(symbolList); + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/SocketSUL.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/SocketSUL.java new file mode 100644 index 0000000..f7f0ed5 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/SocketSUL.java @@ -0,0 +1,80 @@ +package basiclearner; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.InetAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import de.learnlib.api.SUL; +import de.learnlib.api.SULException; + +/** + * Socket interface to connect to an SUT/test adapter over TCP. + * + * As an example, type into a unix terminal "nc -vl {ip} {port}" (where {ip} and + * {port} are the chosen values), and run this socketSUL. You can now control the + * SUL through the terminal. + * @author Ramon Janssen + */ +public class SocketSUL implements SUL<String, String>, AutoCloseable { + private final BufferedReader SULoutput; + private final PrintWriter SULinput; + private final Socket socket; + private final boolean extraNewLine; + private final String resetCmd; + + /** + * Socket-interface for SUTs. Connects to a SUT (or test-adapter) + * @param ip the ip-address, for example InetAddress.getLoopbackAddress() for localhost + * @param port the tcp-port + * @param extraNewLine whether to print a newline after every input to the SUT + * @param resetCmd the command to send for resetting the SUT + * @throws UnknownHostException + * @throws IOException + */ + public SocketSUL(InetAddress ip, int port, boolean extraNewLine, String resetCmd) throws UnknownHostException, IOException { + this.socket = new Socket(ip, port); + this.SULoutput = new BufferedReader(new InputStreamReader(socket.getInputStream())); + this.SULinput = new PrintWriter(socket.getOutputStream(), true); + this.extraNewLine = extraNewLine; + this.resetCmd = resetCmd; + } + + @Override + public void post() { + if (extraNewLine) { + this.SULinput.write(this.resetCmd + System.lineSeparator()); + } else { + this.SULinput.write(this.resetCmd); + } + this.SULinput.flush(); + } + + @Override + public void pre() { + + } + + @Override + public String step(String input) throws SULException { + if (extraNewLine) { + this.SULinput.write(input + System.lineSeparator()); + } else { + this.SULinput.write(input); + } + this.SULinput.flush(); + try { + return this.SULoutput.readLine(); + } catch (IOException e) { + throw new SULException(e); + } + } + + @Override + public void close() throws Exception { + this.socket.close(); + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/UserEQOracle.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/UserEQOracle.java new file mode 100644 index 0000000..a6c4be2 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/basiclearner/UserEQOracle.java @@ -0,0 +1,56 @@ +package basiclearner; + +import de.learnlib.api.EquivalenceOracle; +import de.learnlib.api.SUL; +import de.learnlib.oracles.DefaultQuery; +import net.automatalib.automata.transout.MealyMachine; +import net.automatalib.words.Word; + +import java.util.*; + +/** + * Created by ramon on 12-12-16. + */ +public class UserEQOracle implements EquivalenceOracle<MealyMachine<?, String, ?, String>, String, Word<String>> { + private final SUL<String,String> sul; + + public UserEQOracle(SUL<String,String> sul) { + this.sul = sul; + } + + @Override + public DefaultQuery<String, Word<String>> findCounterExample(MealyMachine<?, String, ?, String> hypothesis, Collection<? extends String> allowedInputs) { + System.out.println("Enter space-separated input sequence to try as a counter-example, or 'stop' to stop learning"); + Scanner userInputScanner = new Scanner(System.in); + do { + String userInput = userInputScanner.nextLine(); + if (userInput.equals("stop")) { + return null; + } else { + String[] sutInputs = userInput.split("\\s"); + if (sutInputs.length != 0) { + Word<String> input = Word.fromArray(sutInputs, 0, sutInputs.length); + Word<String> hypOutput = hypothesis.computeOutput(input); + Word<String> sulOutput = sulOutput(input); + System.out.println("SUL output: " + sulOutput); + if (!hypOutput.equals(sulOutput)) { + System.out.println(); + return new DefaultQuery<String, Word<String>>(Word.fromList(Collections.emptyList()), input, sulOutput); + } else { + System.out.println("Query '" + userInput + "' not a counterexample"); + } + } + } + } while (true); + } + + private Word<String> sulOutput(Word<String> inputs) { + sul.pre(); + List<String> output = new ArrayList<>(); + for (String input: inputs) { + output.add(sul.step(input)); + } + sul.post(); + return Word.fromList(output); + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/BRPCompare.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/BRPCompare.java new file mode 100755 index 0000000..78d822b --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/BRPCompare.java @@ -0,0 +1,38 @@ +package brpcompare; + +import com.google.common.collect.ImmutableSet; +import net.automatalib.util.automata.Automata; +import net.automatalib.words.Word; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.Collection; + +/** + * Created by petra on 14-12-16. + */ +public class BRPCompare { + + public static void main(String[] args) throws IOException { +// String model1 = "/hypothesis0.dot", +// model2 = "/hypothesis1.dot"; + String model1 = "/Final Model Of " + args[0] + ".dot", + model2 = "/Final Model Of Reference.dot"; + Collection<String> inputAlphabet = ImmutableSet.of("IACK", "IREQ_0_0_0", "IREQ_0_0_1", "IREQ_0_1_0", "IREQ_0_1_1", "IREQ_1_0_0", "IREQ_1_0_1", "IREQ_1_1_0", "IREQ_1_1_1", "ISENDFRAME", "ITIMEOUT"); + + GraphvizParser p = new GraphvizParser(Paths.get(new File("").getAbsolutePath().concat(model1))); + GraphvizParser p2 = new GraphvizParser(Paths.get(new File("").getAbsolutePath().concat(model2))); + try { + Word<String> compareResult = Automata.findSeparatingWord(p.createMachine(), p2.createMachine(), inputAlphabet); + if(compareResult == null) { + System.out.println("The automata are equal!"); + } else { + System.out.println("The automata are not equal! A counterexample is: " + compareResult); + } + } catch (NullPointerException e) { + e.printStackTrace(); + System.err.println("Did you compare with the right input alphabet?"); + } + } +} diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/GraphvizParser.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/GraphvizParser.java new file mode 100755 index 0000000..49c29c7 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/GraphvizParser.java @@ -0,0 +1,101 @@ +package brpcompare; + +import com.google.common.collect.Lists; +import net.automatalib.automata.transout.impl.compact.CompactMealy; +import net.automatalib.util.automata.builders.AutomatonBuilders; +import net.automatalib.util.automata.builders.MealyBuilder; +import net.automatalib.words.Alphabet; +import net.automatalib.words.impl.Alphabets; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.*; + +/** + * A class which is just able to parse the output generated by LearnLib (why is this not included in LearnLib itself? + * I have no clue.) It is *not* a general graphviz parser, it only parses the things I needed for the mealy machines. + */ +public class GraphvizParser { + public static class Edge { + public final String from; + public final String to; + public final String label; + Edge(String b, String e, String l){ + from = b; + to = e; + label = l; + } + } + + public final Set<String> nodes; + public final Set<Edge> edges; + public String initialState; + + GraphvizParser(Path filename) throws IOException { + File file = new File(filename.toString()); + if (!file.exists()) { + throw new RuntimeException("File '" + filename + "' does not exist"); + } + if (file.isDirectory()) { + throw new RuntimeException("File " + filename + "' is a directory"); + } + + nodes = new HashSet<>(); + edges = new HashSet<>(); + + Scanner s = new Scanner(filename); + while(s.hasNextLine()){ + String line = s.nextLine(); + + if(!line.contains("label")) continue; + + if(line.contains("->")){ + int e1 = line.indexOf('-'); + int e2 = line.indexOf('['); + int b3 = line.indexOf('"'); + int e3 = line.lastIndexOf('"'); + + String from = line.substring(0, e1).trim(); + String to = line.substring(e1+2, e2).trim(); + String label = line.substring(b3+1, e3).trim(); + + // First read state will be the initial one. + if(initialState == null) initialState = from; + + nodes.add(from); + nodes.add(to); + edges.add(new Edge(from, to, label)); + } else { + int end = line.indexOf('['); + if(end <= 0) continue; + String node = line.substring(0, end).trim(); + + nodes.add(node); + } + } + } + + CompactMealy<String, String> createMachine(){ + Set<String> inputs = new HashSet<>(); + for(Edge e : edges){ + String[] io = e.label.split("/"); + inputs.add(io[0].trim()); + } + + List<String> inputList = Lists.newArrayList(inputs.iterator()); + Alphabet<String> alphabet = new HashAlphabet<>(Alphabets.fromList(inputList)); + + MealyBuilder<?, String, ?, String, CompactMealy<String, String>>.MealyBuilder__1 builder = AutomatonBuilders.<String, String>newMealy(alphabet).withInitial(initialState); + + for(Edge e : edges){ + String[] io = e.label.split("/"); + + builder.from(e.from).on(io[0].trim()).withOutput(io[1].trim()).to(e.to); + } + + return builder.create(); + } + +} + diff --git a/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/HashAlphabet.java b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/HashAlphabet.java new file mode 100755 index 0000000..46742d2 --- /dev/null +++ b/assignments/assignment2/Bounded Retransmission Protocol Tester/Source/brpcompare/HashAlphabet.java @@ -0,0 +1,54 @@ +package brpcompare; + +import net.automatalib.words.Alphabet; +import net.automatalib.words.abstractimpl.AbstractAlphabet; + +import java.util.ArrayList; +import java.util.HashMap; + +/** + * Created by joshua on 14/07/2016. + * I noticed that getSymbolIndex was called a lot. So I optimized for that, with a look up structure. + * Too bad it wasn't in LearnLib already. + */ +public class HashAlphabet<I> extends AbstractAlphabet<I> { + private final ArrayList<I> symbols; + private final HashMap<I, Integer> indices; + + public HashAlphabet(Alphabet<I> in) { + symbols = new ArrayList<>(in); + indices = new HashMap<>(in.size()); + for (int i = 0; i < symbols.size(); i++) { + indices.put(symbols.get(i), i); + } + } + + @Override + public I getSymbol(int index) { + return symbols.get(index); + } + + @Override + public int getSymbolIndex(I symbol) { + return indices.get(symbol); + } + + @Override + public int size() { + return symbols.size(); + } + + /* (non-Javadoc) + * @see net.automatalib.words.abstractimpl.AbstractAlphabet#writeToArray(int, java.lang.Object[], int, int) + */ + @Override + public void writeToArray(int offset, Object[] array, int tgtOfs, int num) { + System.arraycopy(symbols, offset, array, tgtOfs, num); + } + + @Override + public boolean containsSymbol(I symbol) { + return indices.containsKey(symbol); + } + +} |