Commit d4a2b4e2 authored by Gijs van Cuyck's avatar Gijs van Cuyck

added the code from the hybrid ads project for use in comparing results.

wrapped all the hybrid ads code in the hybrid namespace and all the
complete ads code in the complete namespace to prevent ambiguity and
unintended interactions.

added an extra command line option to calculate a test suite using both
sets of code and then compare the results. WIP.
parent 43f3e7ae
......@@ -2,8 +2,21 @@ cmake_minimum_required(VERSION 3.8)
project(complete_ads)
set(CMAKE_CXX_STANDARD 17)
#set(CMAKE_CXX_FLAGS "-static-libgcc -static-libstdc++")
#set(CMAKE_CXX_FLAGS "-static-libgcc")
set(SOURCE_FILES main.cpp main_test.cpp)
add_subdirectory("lib")
set(CMAKE_CXX_FLAGS "-static-libgcc -static-libstdc++ -static")
set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -static")
set(SOURCE_FILES main.cpp)
#add_subdirectory("lib")
#add_subdirectory("hybrid_lib")
file(GLOB_RECURSE sources "lib/*.cpp")
file(GLOB_RECURSE hybrid_sources "hybrid_lib/*.cpp")
file(GLOB_RECURSE headers "lib/*.hpp")
file(GLOB_RECURSE hybrid_headers "hybrid_lib/*.hpp")
set(libs)
add_library(common ${headers} ${sources} ${hybrid_headers} ${hybrid_sources})
target_link_libraries(common ${libs})
#target_include_directories(common PUBLIC ".")
add_subdirectory("src")
\ No newline at end of file
No preview for this file type
#include "adaptive_distinguishing_sequence.hpp"
#include <algorithm>
#include <cassert>
#include <functional>
#include <queue>
namespace hybrid
{
using namespace std;
adaptive_distinguishing_sequence::adaptive_distinguishing_sequence(size_t N, size_t d)
: CI(N), depth(d)
{
for (size_t i = 0; i < N; ++i) CI[i] = {i, i};
}
adaptive_distinguishing_sequence create_adaptive_distinguishing_sequence(const result &splitting_tree)
{
const auto &root = splitting_tree.root;
const auto &succession = splitting_tree.successor_cache;
const auto N = root.states.size();
adaptive_distinguishing_sequence sequence(N, 0);
queue<reference_wrapper<adaptive_distinguishing_sequence>> work;
work.push(sequence);
while (!work.empty())
{
adaptive_distinguishing_sequence &node = work.front();
work.pop();
if (node.CI.size() < 2) continue;
vector<bool> states(N, false);
for (auto &&state : node.CI)
{
states[state.first] = true;
}
const auto &oboom = lca(root, [&states](state state) -> bool
{
return states[state];
});
if (oboom.children.empty()) continue;
node.w = oboom.separator;
for (auto &&c : oboom.children)
{
adaptive_distinguishing_sequence new_c(0, node.depth + 1);
size_t i = 0;
size_t j = 0;
while (i < node.CI.size() && j < c.states.size())
{
if (node.CI[i].first < c.states[j])
{
i++;
} else if (node.CI[i].first > c.states[j])
{
j++;
} else
{
const auto curr = succession[oboom.depth][node.CI[i].first];
const auto init = node.CI[i].second;
new_c.CI.push_back({curr, init});
i++;
j++;
}
}
// FIXME: this should/could be done without sorting...
sort(begin(new_c.CI), end(new_c.CI));
if (!new_c.CI.empty())
{
node.children.push_back(move(new_c));
}
}
assert(node.children.size() > 1);
for (auto &c : node.children)
{
work.push(c);
}
}
return sequence;
}
}
\ No newline at end of file
#pragma once
#include "types.hpp"
#include "splitting_tree.hpp"
#include <utility>
/*
* The adaptive distinguishing sequence as described in Lee & Yannakakis. This
* is not a sequence, but a decision tree! It can be constructed from the Lee
* & Yannakakis-style splitting tree. We also need some other data produced
* by the splitting tree algorithm.
*/
namespace hybrid
{
struct adaptive_distinguishing_sequence
{
adaptive_distinguishing_sequence(size_t N, size_t depth);
// current, initial
std::vector<std::pair<state, state>> CI;
std::vector<adaptive_distinguishing_sequence> children;
word w;
size_t depth;
};
adaptive_distinguishing_sequence create_adaptive_distinguishing_sequence(result const &splitting_tree);
}
\ No newline at end of file
#pragma once
#include <chrono>
#include <iostream>
namespace hybrid
{
struct timer
{
using clock = std::chrono::high_resolution_clock;
using time = std::chrono::time_point<clock>;
using seconds = std::chrono::duration<double>;
std::string name;
time begin;
bool active = true;
timer(std::string n)
: name(n), begin(clock::now())
{
std::cerr << name << std::endl;
}
void stop()
{
if (!active) return;
time end = clock::now();
std::cerr << "* " << from_duration(end - begin) << '\t' << name << std::endl;
active = false;
}
~timer()
{
stop();
}
static double from_duration(seconds s)
{
return s.count();
}
};
// has same signature, but does not log :)
struct silent_timer
{
silent_timer(std::string)
{}
void stop();
};
}
\ No newline at end of file
#pragma once
#include "types.hpp"
#include <map>
#include <string>
#include <vector>
/*
* Everything is indexed by size_t's, so that we can index vectors
* in constant time. Can only represent deterministic machines,
* but partiality still can occur.
*
* Note that graph_size == graph.size() and that input_size equals the size
* of the biggest row in graph. Finally output_size bounds the number of
* outputs. These values are redundant, but nice to have here.
*/
namespace hybrid
{
struct mealy
{
struct edge
{
edge() = default;
edge(state t, output o) : to(t), out(o)
{}
state to = state(-1);
output out = output(-1);
};
// state -> input -> (output, state)
std::vector<std::vector<edge>> graph;
size_t graph_size = 0;
size_t input_size = 0;
size_t output_size = 0;
};
inline bool is_complete(const mealy &m)
{
for (state n = 0; n < m.graph_size; ++n)
{
if (m.graph[n].size() != m.input_size) return false;
for (auto &&e : m.graph[n]) if (e.to == state(-1) || e.out == output(-1)) return false;
}
return true;
}
inline bool defined(mealy const &m, state s, input i)
{
if (s >= m.graph.size()) return false;
if (i >= m.graph[s].size()) return false;
if (m.graph[s][i].to == state(-1) || m.graph[s][i].out == output(-1)) return false;
return true;
}
inline mealy::edge apply(mealy const &m, state state, input input)
{
return m.graph[state][input];
}
template<typename Iterator>
mealy::edge apply(mealy const &m, state state, Iterator b, Iterator e)
{
mealy::edge ret;
ret.to = state;
while (b != e)
{
ret = apply(m, ret.to, *b++);
}
return ret;
}
}
\ No newline at end of file
#pragma once
#include <cassert>
#include <list>
#include <numeric>
#include <stdexcept>
#include <type_traits>
#include <vector>
namespace hybrid
{
template<typename Iterator, typename Fun>
std::list<std::list<typename Iterator::value_type>>
partition_(Iterator b, Iterator e, Fun &&function, size_t output_size)
{
using namespace std;
using T = typename decay<decltype(*b)>::type;
list<T> elements(b, e);
list<list<T>> blocks;
using ref = typename list<list<T>>::iterator;
vector<ref> A(output_size);
auto it = begin(elements);
auto ed = end(elements);
while (it != ed)
{
const auto current = it++;
const auto y = function(*current);
if (y >= output_size) throw runtime_error("Output is too big");
auto &ar = A[y];
if (ar == ref{})
{
ar = blocks.insert(blocks.end(), list<T>{});
}
ar->splice(ar->end(), elements, current);
}
return blocks;
}
}
\ No newline at end of file
#include "reachability.hpp"
#include "mealy.hpp"
#include <map>
#include <queue>
#include <vector>
namespace hybrid
{
using namespace std;
mealy reachable_submachine(const mealy &in, state start)
{
using state_out = state;
state_out max_state = 0;
map<state, state_out> new_state;
vector<bool> visited(in.graph_size, false);
mealy out;
queue<state> work;
work.push(start);
while (!work.empty())
{
state s = work.front();
work.pop();
if (visited[s]) continue;
visited[s] = true;
if (!new_state.count(s)) new_state[s] = max_state++;
state_out s2 = new_state[s];
for (input i = 0; i < in.input_size; ++i)
{
const auto ret = apply(in, s, i);
const output o = ret.out;
const state t = ret.to;
if (!new_state.count(t)) new_state[t] = max_state++;
state_out t2 = new_state[t];
if (out.graph.size() < max_state) out.graph.resize(max_state);
if (out.graph[s2].size() < in.input_size) out.graph[s2].resize(in.input_size);
out.graph[s2][i] = mealy::edge(t2, o);
if (!visited[t]) work.push(t);
}
}
out.graph_size = max_state;
out.input_size = in.input_size;
out.output_size = in.output_size;
if (out.graph_size == 0) throw runtime_error("Empty state set");
if (out.input_size == 0) throw runtime_error("Empty input set");
if (out.output_size == 0) throw runtime_error("Empty output set");
if (!is_complete(out)) throw runtime_error("Partial machine");
return out;
}
}
\ No newline at end of file
#pragma once
#include "types.hpp"
namespace hybrid
{
struct mealy;
mealy reachable_submachine(const mealy &in, state start);
}
\ No newline at end of file
#include "read_mealy.hpp"
#include "mealy.hpp"
#include <cassert>
#include <cctype>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
namespace hybrid
{
using namespace std;
static string easy_substr(string const &s, size_t begin, size_t end)
{
return s.substr(begin, end - begin);
}
static string trim_copy(string const &str)
{
auto it = str.begin();
while (it != str.end() && isspace(*it))
{
it++;
}
auto e = str.end();
while (it != e && isspace(*(e - 1)))
{
e--;
}
return string(it, e);
}
mealy read_mealy_from_txt(std::istream &in, bool check)
{
mealy m;
state max_state = 0;
input max_input = 0;
output max_output = 0;
string line;
while (getline(in, line))
{
state from, to;
input i;
output o;
string separator;
stringstream ss(line);
ss >> from >> separator >> i >> separator >> o >> separator >> to;
if (from >= max_state) max_state = from + 1;
if (to >= max_state) max_state = to + 1;
if (i >= max_input) max_input = i + 1;
if (o >= max_output) max_output = o + 1;
if (defined(m, from, i)) throw runtime_error("Nondeterministic machine");
m.graph.resize(max_state);
auto &v = m.graph[from];
v.resize(max_input);
v[i] = mealy::edge(to, o);
assert(defined(m, from, i));
}
m.graph_size = max_state;
m.input_size = max_input;
m.output_size = max_output;
if (m.graph_size == 0) throw runtime_error("Empty state set");
if (m.input_size == 0) throw runtime_error("Empty input set");
if (m.output_size == 0) throw runtime_error("Empty output set");
if (check && !is_complete(m)) throw runtime_error("Partial machine");
return m;
}
mealy read_mealy_from_txt(const std::string &filename, bool check)
{
std::ifstream file(filename);
return read_mealy_from_txt(file, check);
}
mealy read_mealy_from_dot(std::istream &in, translation &t, bool check)
{
mealy m;
std::unordered_map<std::string, state> state_indices;
state max_state = 0;
string line;
while (getline(in, line))
{
const auto npos = std::string::npos;
if (line.find("}") != string::npos) break;
// parse states
const auto arrow_pos = line.find("->");
const auto bracket_pos = line.find('[');
if (arrow_pos == npos || bracket_pos == npos) continue;
const auto lh = trim_copy(easy_substr(line, 0, arrow_pos));
const auto rh = trim_copy(easy_substr(line, arrow_pos + 2, bracket_pos));
// parse input/output
const auto quote1_pos = line.find('\"', bracket_pos);
const auto slash_pos = line.find('/', quote1_pos);
const auto quote2_pos = line.find('\"', slash_pos);
if (quote1_pos == npos || slash_pos == npos || quote2_pos == npos) continue;
const auto input = trim_copy(easy_substr(line, quote1_pos + 1, slash_pos));
const auto output = trim_copy(easy_substr(line, slash_pos + 1, quote2_pos));
// make fresh indices, if needed
if (state_indices.count(lh) < 1) state_indices[lh] = max_state++;
if (state_indices.count(rh) < 1) state_indices[rh] = max_state++;
if (t.input_indices.count(input) < 1) t.input_indices[input] = t.max_input++;
if (t.output_indices.count(output) < 1) t.output_indices[output] = t.max_output++;
if (defined(m, state_indices[lh], t.input_indices[input]))
throw runtime_error("Nondeterministic machine");
// add edge
m.graph.resize(max_state);
auto &v = m.graph[state_indices[lh]];
v.resize(t.max_input);
v[t.input_indices[input]] = mealy::edge(state_indices[rh], t.output_indices[output]);
}
m.graph_size = max_state;
m.input_size = t.max_input;
m.output_size = t.max_output;
if (m.graph_size == 0) throw runtime_error("Empty state set");
if (m.input_size == 0) throw runtime_error("Empty input set");
if (m.output_size == 0) throw runtime_error("Empty output set");
if (check && !is_complete(m)) throw runtime_error("Partial machine");
return m;
}
mealy read_mealy_from_dot(const string &filename, translation &t, bool check)
{
ifstream file(filename);
return read_mealy_from_dot(file, t, check);
}
std::pair<mealy, translation> read_mealy_from_dot(istream &in, bool check)
{
translation t;
const auto m = read_mealy_from_dot(in, t, check);
return {move(m), move(t)};
}
std::pair<mealy, translation> read_mealy_from_dot(const string &filename, bool check)
{
translation t;
const auto m = read_mealy_from_dot(filename, t, check);
return {move(m), move(t)};
}
template<typename T>
std::vector<std::string> create_reverse_map_impl(std::unordered_map<std::string, T> const &indices)
{
std::vector<std::string> ret(indices.size());
for (auto &&p : indices)
{
ret[p.second] = p.first;
}
return ret;
}
std::vector<string> create_reverse_map(const std::unordered_map<string, input> &indices)
{
return create_reverse_map_impl(indices);
}
#if 0 // Note: input and output are equal types, so this would be a redecl
std::vector<string> create_reverse_map(const std::map<string, output> & indices) {
return create_reverse_map_impl(indices);
}
#endif
translation create_translation_for_mealy(const mealy &m)
{
translation t;
t.max_input = m.input_size;
t.max_output = m.output_size;
for (input i = 0; i < t.max_input; ++i)
{
t.input_indices[to_string(i)] = i;
}
for (output o = 0; o < t.max_output; ++o)
{
t.output_indices[to_string(o)] = o;
}
return t;
}
}
\ No newline at end of file
#pragma once
#include "types.hpp"
#include <iosfwd>
#include <string>
#include <unordered_map>
#include <utility>
namespace hybrid
{
struct mealy;
struct translation;
/// \brief reads a mealy machine from plain txt file as provided by A. T. Endo
/// States, inputs and outputs in these files are already integral, so no need for translation
mealy read_mealy_from_txt(std::istream &in, bool check = true);
mealy read_mealy_from_txt(std::string const &filename, bool check = true);
/// \brief reads a mealy machine from dot files as generated by learnlib
/// Here we need a translation, which is extended during parsing
mealy read_mealy_from_dot(std::istream &in, translation &t, bool check = true);
mealy read_mealy_from_dot(std::string const &filename, translation &t, bool check = true);
/// \brief reads a mealy machine from dot files as generated by learnlib
/// Here the translation starts out empty and is returned in the end
std::pair<mealy, translation> read_mealy_from_dot(std::istream &in, bool check = true);
std::pair<mealy, translation> read_mealy_from_dot(std::string const &filename, bool check = true);
/// \brief For non-integral formats we use a translation to integers
struct translation
{
std::unordered_map<std::string, input> input_indices;
input max_input = 0;
std::unordered_map<std::string, output> output_indices;
output max_output = 0;
};
/// \brief inverts the input_indices and output_indices maps
std::vector<std::string> create_reverse_map(std::unordered_map<std::string, input> const &indices);
std::vector<std::string> create_reverse_map(std::unordered_map<std::string, output> const &indices);
/// \brief defines trivial translation (the string represent integers directly)