1
Fork 0
mirror of https://github.com/Jaxan/hybrid-ads.git synced 2025-04-27 06:57:44 +02:00

Adds the java code I use

This commit is contained in:
Joshua Moerman 2015-03-30 17:08:42 +02:00
parent ad58d6ac07
commit c2be20b2f0
5 changed files with 452 additions and 46 deletions

View file

@ -7,6 +7,7 @@ still useful for generating a seperating set (in the sense of Lee and
Yannakakis). The partial leaves will be augmented via the classical
seperating sequences.
## Building
```
@ -21,3 +22,8 @@ executable in the build directory. Note that you'll need c++14, but clang in
Mac OSX will understand that (and if not, you'll have to update Xcode).
## Java
For now the java code, which acts as a bridge between LearnLib and this c++
tool, is included here. But it should earn its own repo at some point. Also, my
javanese is a bit rusty...

86
java/GraphvizParser.java Normal file
View file

@ -0,0 +1,86 @@
package yannakakis;
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.IOException;
import java.nio.file.Path;
import java.util.HashSet;
import java.util.List;
import java.util.Scanner;
import java.util.Set;
/**
* 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 String from;
public String to;
public String label;
Edge(String b, String e, String l){
from = b;
to = e;
label = l;
}
}
public Set<String> nodes;
public Set<Edge> edges;
GraphvizParser(Path filename) throws IOException {
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();
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(GraphvizParser.Edge e : edges){
String[] io = e.label.split("/");
inputs.add(io[0].trim());
}
List<String> inputList = Lists.newArrayList(inputs.iterator());
Alphabet<String> alphabet = Alphabets.fromList(inputList);
MealyBuilder<?, String, ?, String, CompactMealy<String, String>>.MealyBuilder__1 builder = AutomatonBuilders.<String, String>newMealy(alphabet).withInitial("s0");
for(GraphvizParser.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();
}
}

182
java/Main.java Normal file
View file

@ -0,0 +1,182 @@
package yannakakis;
import com.google.common.collect.Lists;
import de.learnlib.algorithms.dhc.mealy.MealyDHC;
import de.learnlib.algorithms.lstargeneric.ce.ObservationTableCEXHandlers;
import de.learnlib.algorithms.lstargeneric.closing.ClosingStrategies;
import de.learnlib.algorithms.lstargeneric.mealy.ExtensibleLStarMealy;
import de.learnlib.api.EquivalenceOracle;
import de.learnlib.api.LearningAlgorithm;
import de.learnlib.api.MembershipOracle;
import de.learnlib.eqtests.basic.EQOracleChain;
import de.learnlib.eqtests.basic.SimulatorEQOracle;
import de.learnlib.eqtests.basic.WMethodEQOracle;
import de.learnlib.eqtests.basic.WpMethodEQOracle;
import de.learnlib.oracles.AbstractSingleQueryOracle;
import de.learnlib.oracles.DefaultQuery;
import de.learnlib.oracles.SimulatorOracle;
import net.automatalib.automata.transout.MealyMachine;
import net.automatalib.automata.transout.impl.compact.CompactMealy;
import net.automatalib.util.graphs.dot.GraphDOT;
import net.automatalib.words.Alphabet;
import net.automatalib.words.Word;
import net.automatalib.words.WordBuilder;
import javax.annotation.Nullable;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
/**
* Test for the Lee and Yannakakis implementation.
*/
public class Main {
public static void main(String[] args) throws IOException {
// We read the mealy machine we want to simulate
String filename = "esm-manual-controller.dot";
System.out.println("Reading dot file: " + filename);
GraphvizParser p = new GraphvizParser(Paths.get(filename));
CompactMealy<String, String> fm = p.createMachine();
Alphabet<String> alphabet = fm.getInputAlphabet();
System.out.println("created machine with " + fm.size() + " states and " + alphabet.size() + " inputs\n");
// We will have the simulating membership oracle. Since the equivalence oracles use a membership oracle to
// test equality, we make membership oracle which maintain a count. E.g. This way we can count the number of
// queries needed to find a counterexample.
System.out.println("Setting up membership oracles");
SimulatorOracle.MealySimulatorOracle<String, String> mOracleMealy = new SimulatorOracle.MealySimulatorOracle<>(fm);
CountingQueryOracle<String, Word<String>> mOracleCounting1 = new CountingQueryOracle<>(mOracleMealy);
CountingQueryOracle<String, Word<String>> mOracleCounting2 = new CountingQueryOracle<>(mOracleMealy);
// We can test multiple equivalence oracles. The eqOracleMealy sees the SUL as a white box mealy machine to
// find a counterexample. The others all treat the SUL as a black box. Since there was one specific
// counterexample which was hard to find, I created an oracle precisely for that example, and chain it with
// the Lee and Yannakakis oracle.
System.out.println("Setting up equivalence oracles");
SimulatorEQOracle.MealySimulatorEQOracle<String, String> eqOracleMealy = new SimulatorEQOracle.MealySimulatorEQOracle<>(fm);
WpMethodEQOracle.MealyWpMethodEQOracle<String, String> eqOracleWp = new WpMethodEQOracle.MealyWpMethodEQOracle<>(3, mOracleCounting2);
WMethodEQOracle.MealyWMethodEQOracle<String, String> eqOracleW = new WMethodEQOracle.MealyWMethodEQOracle<>(2, mOracleCounting2);
EquivalenceOracle.MealyEquivalenceOracle<String, String> eqOracleYannakakis = new YannakakisEQOracle<>(mOracleCounting2);
EquivalenceOracle.MealyEquivalenceOracle<String, String> eqOracleSpecific = new SpecificCounterExampleOracle(mOracleCounting2);
EQOracleChain.MealyEQOracleChain eqOracleYannakakisPlus = new EQOracleChain.MealyEQOracleChain(Arrays.asList(eqOracleSpecific, eqOracleYannakakis));
// The chosen oracle to experiment with.
EQOracleChain.MealyEQOracleChain eqOracle = eqOracleYannakakisPlus;
// Learnlib comes with different learning algorithms
System.out.println("Setting up learner(s)");
MealyDHC<String, String> learnerDHC = new MealyDHC<>(alphabet, mOracleCounting1);
ExtensibleLStarMealy<String, String> learnerLStar = new ExtensibleLStarMealy<>(alphabet, mOracleCounting1, Lists.<Word<String>>newArrayList(), ObservationTableCEXHandlers.CLASSIC_LSTAR, ClosingStrategies.CLOSE_FIRST);
// The chosen learning algorithm
LearningAlgorithm.MealyLearner<String, String> learner = learnerLStar;
// Here we will perform our experiment. I did not use the Experiment class from LearnLib, as I wanted some
// more control (for example, I want to reset the counters in the membership oracles). This control flow
// is suggested by LearnLib (on their wiki).
System.out.println("Starting experiment\n");
int stage = 0;
learner.startLearning();
while(true) {
DefaultQuery<String, Word<String>> ce = eqOracle.findCounterExample(learner.getHypothesisModel(), alphabet);
if(ce == null) break;
learner.refineHypothesis(ce);
// FIXME: Make this a command line option
String dir = "/Users/joshua/Documents/PhD/Machines/Mealy/esms3/";
String filenameh = dir + "hyp." + stage + ".obf.dot";
PrintWriter output = new PrintWriter(filenameh);
GraphDOT.write(learner.getHypothesisModel(), alphabet, output);
output.close();
System.out.println(stage++ + ": " + Calendar.getInstance().getTime());
System.out.println("Hypothesis has " + learner.getHypothesisModel().getStates().size() + " states");
mOracleCounting1.log_and_reset("learning");
mOracleCounting2.log_and_reset("finding a counter example");
System.out.println();
}
System.out.println(stage++ + ": " + Calendar.getInstance().getTime());
System.out.println("Conclusion has " + learner.getHypothesisModel().getStates().size() + " states");
mOracleCounting1.log_and_reset("learning");
mOracleCounting2.log_and_reset("finding a counter example");
System.out.println("Done!");
PrintWriter output = new PrintWriter("last_hypothesis.dot");
GraphDOT.write(learner.getHypothesisModel(), alphabet, output);
output.close();
}
/**
* An membership oracle which maintains a count of the number of queries (and symbols). Usefull for testing
* performance of equivalence oracles (which use membership oracles).
* @param <I> Input alphabet
* @param <O> Output alphabet
*/
public static class CountingQueryOracle<I, O> extends AbstractSingleQueryOracle<I, O>{
private final AbstractSingleQueryOracle<I, O> delegate;
public int query_count = 0;
public int symbol_count = 0;
CountingQueryOracle(AbstractSingleQueryOracle<I, O> delegate){
this.delegate = delegate;
}
public void reset(){
query_count = 0;
symbol_count = 0;
}
public void log_and_reset(String message){
System.out.println("used " + query_count + " queries and " + symbol_count + " symbols for " + message);
reset();
}
@Override
public O answerQuery(Word<I> word, Word<I> word1) {
query_count++;
symbol_count += word.length() + word1.length();
return delegate.answerQuery(word, word1);
}
}
/**
* An equivalence oracle to test a single sequence, which I needed to learn the Oce test case.
*/
public static class SpecificCounterExampleOracle implements EquivalenceOracle.MealyEquivalenceOracle<String, String> {
private final MembershipOracle<String, Word<String>> sulOracle;
private boolean fired = false;
SpecificCounterExampleOracle(MembershipOracle<String, Word<String>> sulOracle){
this.sulOracle = sulOracle;
}
@Nullable
@Override
public DefaultQuery<String, Word<String>> findCounterExample(MealyMachine<?, String, ?, String> objects, Collection<? extends String> collection) {
if(fired) return null;
WordBuilder<String> wb = new WordBuilder<>();
wb.append("52.5", "53.4", "21.0", "21.0", "21.0", "21.0", "21.0", "21.0", "21.0", "21.0", "21.0", "37.2", "10", "9.4");
Word<String> test = wb.toWord();
DefaultQuery<String, Word<String>> query = new DefaultQuery<>(test);
sulOracle.processQueries(Collections.singleton(query));
fired = true;
return query;
}
}
}

View file

@ -0,0 +1,178 @@
package yannakakis;
import de.learnlib.api.EquivalenceOracle;
import de.learnlib.api.MembershipOracle;
import de.learnlib.oracles.DefaultQuery;
import net.automatalib.automata.transout.MealyMachine;
import net.automatalib.util.graphs.dot.GraphDOT;
import net.automatalib.words.Word;
import net.automatalib.words.WordBuilder;
import javax.annotation.Nullable;
import java.io.*;
import java.util.Collection;
import java.util.Collections;
import java.util.Scanner;
/**
* Implements the Lee & Yannakakis suffixes by invoking an external program. Because of this indirection to an external
* program, the findCounterexample method might throw a RuntimeException. Sorry for the hard-coded path to the
* executable!
*
* @param <O> is the output alphabet. (So a query will have type Word<String, Word<O>>.)
*/
public class YannakakisEQOracle<O> implements EquivalenceOracle.MealyEquivalenceOracle<String, O> {
private final MembershipOracle<String, Word<O>> sulOracle;
private final ProcessBuilder pb;
private Process process;
private Writer processInput;
private BufferedReader processOutput;
private StreamGobbler errorGobbler;
/**
* @param sulOracle The membership oracle of the SUL, we need this to check the output on the test suite
* @throws IOException
*/
YannakakisEQOracle(MembershipOracle<String, Word<O>> sulOracle) throws IOException {
this.sulOracle = sulOracle;
pb = new ProcessBuilder("/Users/joshua/Documents/Code/Kweekvijver/Yannakakis/build/main", "--", "1", "stream");
}
/**
* A small class to print all stuff to stderr. Useful as I do not want stderr and stdout of the external program to
* be merged, but still want to redirect stderr to java's stderr.
*/
class StreamGobbler extends Thread {
private final InputStream stream;
private final String prefix;
StreamGobbler(InputStream stream, String prefix) {
this.stream = stream;
this.prefix = prefix;
}
public void run() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
String line;
while ((line = reader.readLine()) != null)
System.err.println(prefix + "> " + line);
} catch (IOException e) {
// It's fine if this thread crashes, nothing depends on it
e.printStackTrace();
}
}
}
/**
* Starts the process and creates buffered/whatnot streams for stdin stderr or the external program
* @throws IOException if the process could not be started (see ProcessBuilder.start for details).
*/
private void setupProcess() throws IOException {
process = pb.start();
processInput = new OutputStreamWriter(process.getOutputStream());
processOutput = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorGobbler = new StreamGobbler(process.getErrorStream(), "ERROR> main");
errorGobbler.start();
}
/**
* I thought this might be a good idea, but I'm not a native Java speaker, so maybe it's not needed.
*/
private void closeAll() {
// Since we're closing, I think it's ok to continue in case of an exception
try {
processInput.close();
processOutput.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
errorGobbler.join(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
process.destroy();
process = null;
processInput = null;
processOutput = null;
errorGobbler = null;
}
/**
* Uses an external program to find counterexamples. The hypothesis will be written to stdin. Then the external
* program might do some calculations and write its test suite to stdout. This is in turn read here and fed
* to the SUL. If a discrepancy occurs, an counterexample is returned. If the external program exits (with value
* 0), then no counterexample is found, and the hypothesis is correct.
*
* This method might throw a RuntimeException if the external program crashes (which it shouldn't of course), or if
* the communication went wrong (for whatever IO reason).
*/
@Nullable
@Override
public DefaultQuery<String, Word<O>> findCounterExample(MealyMachine<?, String, ?, O> machine, Collection<? extends String> inputs) {
try {
setupProcess();
} catch (IOException e) {
throw new RuntimeException("Unable to start the external program: " + e);
}
try {
// Write the hypothesis to stdin of the external program
GraphDOT.write(machine, inputs, processInput);
processInput.flush();
// Read every line outputted on stdout.
String line;
while ((line = processOutput.readLine()) != null) {
// Read every string of the line, this will be a symbol of the input sequence.
WordBuilder<String> wb = new WordBuilder<>();
Scanner s = new Scanner(line);
while(s.hasNext()) {
wb.add(s.next());
}
// Convert to a word and test on the SUL
Word<String> test = wb.toWord();
DefaultQuery<String, Word<O>> query = new DefaultQuery<>(test);
sulOracle.processQueries(Collections.singleton(query));
// Also test on the hypothesis
Word<O> o1 = machine.computeOutput(test);
Word<O> o2 = query.getOutput();
assert o1 != null;
assert o2 != null;
// If equal => no counterexample :(
if(o1.equals(o2)) continue;
// If not equal => counterexample :D
closeAll();
return query;
}
} catch (IOException e) {
throw new RuntimeException("Unable to communicate with the external program: " + e);
}
// At this point, the external program closed its stream, so it should have exited.
if(process.isAlive()){
System.err.println("ERROR> log> No counterexample but process stream still active!");
closeAll();
throw new RuntimeException("No counterexample but process stream still active!");
}
// If the program exited with a non-zero value, something went wrong (for example a segfault)!
int ret = process.exitValue();
if(ret != 0){
System.err.println("ERROR> log> Something went wrong with the process: return value = " + ret);
closeAll();
throw new RuntimeException("Something went wrong with the process: return value = " + ret);
}
// Here, the program exited normally, without counterexample, so we may return null.
return null;
}
}

View file

@ -1,46 +0,0 @@
all
===
for f in *.dot; do ../build/main $f; done
for f in *splitting_tree; do sed -i "" -e 's/label="........................................*"/label="truncated"/g' $f; done
for f in *dist_seq; do sed -i "" -e 's/label="........................................*"/label="truncated"/g' $f; done
dot -O -Tpng -Goverlap=false *dist_seq
dot -O -Tpng -Goverlap=false *splitting_tree
graphs
======
dot -O -Tpng -Goverlap=false *dist_seq
dot -O -Tpng -Goverlap=false *splitting_tree
neato -O -Tpng -Goverlap=false *.dot
truncation
==========
sed 's/label="........................................*"/label="truncated"/g' esm-manual-controller.dot.splitting_tree.dot > esm-manual-controller.dot.splitting_tree_truncated.dot
for f in *splitting_tree; do echo $f; sed -i "" -e 's/label="........................................*"/label="truncated"/g' $f; done
for f in *dist_seq; do echo $f; sed -i "" -e 's/label="........................................*"/label="truncated"/g' $f; done
cleaning
========
sed 's/\[label.*<td>I/ \[label="/g;s/<\/td>.*<td>O/ \/ /g;s/<\/td>.*]/"\];/g'
not needed ctor
===============
partition_refine(Blocks other_blocks){
auto beg = elements.begin();
for(auto && block : other_blocks){
std::copy(block.begin(), block.end(), std::back_inserter(elements));
blocks.insert(blocks.end(), {beg, std::prev(elements.end())});
beg = elements.end();
}
}