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

changed datatypes to accomodate information needed for the new

algorithm.
changed the old algorithm to the point where it can do everything it
used to do with the new datatypes.
parent 5e3f838e
......@@ -4,6 +4,6 @@ file(GLOB_RECURSE headers "*.hpp")
set(libs)
add_library(common ${headers} ${sources} vector_printing.hpp)
add_library(common ${headers} ${sources} vector_printing.hpp readable_splitting_tree.hpp readable_splitting_tree.cpp)
target_link_libraries(common ${libs})
target_include_directories(common PUBLIC ".")
......@@ -150,19 +150,29 @@ std::pair<mealy, translation> read_mealy_from_dot(const string & filename, bool
}
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());
template <typename T, typename P>
std::vector<P> create_reverse_map_impl(std::unordered_map<P, T> const & indices) {
std::vector<P> 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);
}
std::vector<std::vector<state>> create_reverse_map(const std::unordered_map<state, size_t> & indices,size_t size){
std::vector<vector<state>> ret(size);
for(pair<state,size_t> kv_pair: indices)
{
ret[kv_pair.second].push_back( kv_pair.first);
}
return ret;
}
#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);
......
......@@ -47,8 +47,10 @@ struct reverse_translation{
};
*/
/// \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);
std::vector<std::string> create_reverse_map(const std::unordered_map<std::string, input> & indices);
std::vector<std::string> create_reverse_map(const std::unordered_map<std::string, output> & indices);
std::vector<std::vector<state>> create_reverse_map(const std::unordered_map<state, size_t> & indices,size_t size);
//todo: check of dit nog gebruikt wordt
/*
......
//
// Created by Gijs van Cuyck on 19/10/2018.
//
#include "readable_splitting_tree.hpp"
#include "vector_printing.hpp"
#include "read_mealy.hpp"
#include <algorithm>
const std::vector<readable_splitting_tree> & readable_splitting_tree::get_children(state s) const {
if (children.size() < 1)
throw std::runtime_error("get_children called when there were no children");
else if (valid_map.size() < 1 || valid_map.count(s) < 1)
return children[0];
else
return children[valid_map.at(s)];
}
const std::vector<std::string> &readable_splitting_tree::get_seperator(state s) const {
if (children.size() < 1)
throw std::runtime_error("get_seperator called when there were no seperators");
else if (valid_map.size() < 1 || valid_map.count(s) < 1)
return separators[0];
else
return separators[valid_map.at(s)];
}
readable_splitting_tree translate_splitting_tree(splitting_tree & tree, std::vector<std::string> & inputs, std::vector<std::string> & states) {
readable_splitting_tree translated_tree(tree.states.size(),tree.depth);
transform(tree.states.begin(),tree.states.end(),translated_tree.states.begin(),[&states](state s){return states[s];});
for (int i = 0; i<tree.separators.size(); i++)
{
std::vector<std::string> new_seperator(tree.separators[i].size());
transform(tree.separators[i].begin(),tree.separators[i].end(),new_seperator.begin(),[&inputs](input i){return inputs[i];});
translated_tree.separators.push_back(new_seperator);
}
for (std::vector<splitting_tree> children : tree.children) {
std::vector<readable_splitting_tree> new_children(children.size());
transform(children.begin(), children.end(), new_children.begin(),
[&inputs, &states](splitting_tree &child) {
return translate_splitting_tree(child, inputs, states);
});
translated_tree.children.push_back(new_children);
}
return translated_tree;
}
void rst_to_stream(readable_splitting_tree & tree, std::ostream & output, int ident){
std::string indentations(ident,'\t');
std::vector<std::vector<state>> reverse_valid_map;
if(tree.children.size()>1)
{
reverse_valid_map = create_reverse_map(const_cast<std::unordered_map<state,size_t>& >(tree.valid_map),tree.children.size());
}
output << indentations << "{" << join(tree.states.begin(),tree.states.end(),",","}") << "\n";
for(int i =0; i<tree.children.size(); i++)
{
output <<indentations << "seperator target: ";
if(reverse_valid_map.size()<=1)
output << "all states\n";
else
output << "{" << join(reverse_valid_map[i].begin(),reverse_valid_map[i].end(),",","}") << "\n";
output << indentations << join(tree.separators[i].begin(),tree.separators[i].end()) << "\n" ;
for(auto & child : tree.children[i]) {
rst_to_stream(child,output,ident+1);
}
}
}
void rst_to_stream(readable_splitting_tree & tree, std::ostream & output){rst_to_stream(tree,output,0);}
//
// Created by Gijs van Cuyck on 19/10/2018.
//
#ifndef COMPLETE_ADS_READABLE_SPLITTING_TREE_HPP
#define COMPLETE_ADS_READABLE_SPLITTING_TREE_HPP
#include "splitting_tree.hpp"
struct readable_splitting_tree {
/// \brief constructor that presets the internal vectors to the correct sizes for filling with iterators.
readable_splitting_tree(size_t state_amount, size_t depth_value)
: states(state_amount), depth(depth_value) {}
/// \brief constructor used to initialize the list of children with empty readable splitting trees.
readable_splitting_tree() {}
std::vector<std::string> states;
std::vector<std::vector<readable_splitting_tree>> children;
std::vector<std::vector<std::string>> separators;
std::unordered_map<state, size_t> valid_map;
size_t depth = 0;
const std::vector<readable_splitting_tree> &get_children(state s) const;
const std::vector<std::string> &get_seperator(state s) const;
};
readable_splitting_tree translate_splitting_tree(splitting_tree & tree, std::vector<std::string> & inputs, std::vector<std::string> & states);
void rst_to_stream(readable_splitting_tree & tree, std::ostream & output, int ident);
void rst_to_stream(readable_splitting_tree & tree, std::ostream & output);
#endif //COMPLETE_ADS_READABLE_SPLITTING_TREE_HPP
#include "splitting_tree.hpp"
#include "partition.hpp"
#include "vector_printing.hpp"
#include <algorithm>
#include <cassert>
......@@ -9,6 +8,7 @@
#include <queue>
#include <random>
#include <utility>
#include <unordered_set>
using namespace std;
......@@ -16,26 +16,20 @@ splitting_tree::splitting_tree(size_t N, size_t d) : states(N), depth(d) {
iota(begin(states), end(states), 0);
}
readable_splitting_tree translate_splitting_tree(splitting_tree & tree, std::vector<std::string> & inputs, std::vector<std::string> & states) {
readable_splitting_tree translated_tree(tree.states.size(),tree.depth,tree.separator.size(),tree.children.size());
transform(tree.states.begin(),tree.states.end(),translated_tree.states.begin(),[&states](state s){return states[s];});
transform(tree.separator.begin(),tree.separator.end(),translated_tree.separator.begin(),[&inputs](input i){return inputs[i];});
transform(tree.children.begin(),tree.children.end(),translated_tree.children.begin(),
[&inputs,&states](splitting_tree & child){ return translate_splitting_tree(child,inputs,states);});
return translated_tree;
}
void rst_to_stream(readable_splitting_tree & tree, ostream & output, int ident){
string indentations(ident,'\t');
output << indentations << "{" << join(tree.states.begin(),tree.states.end(),",","}") << "\n";
if(tree.children.size()>0)
{
output << indentations << join(tree.separator.begin(),tree.separator.end()) << "\n" ;
for(auto & child : tree.children) {
rst_to_stream(child,output,ident+1);
}
}
}
/// \brief struct used to combine splitting trees with additional data that wont end up in the result but is needed
/// for some computations.
struct work_set{
work_set(splitting_tree & tree, unordered_set<state> & valid):work(tree),valid_states(valid){}
work_set(splitting_tree & tree):work(tree){}
splitting_tree & work;
/// \brief the set of states for wich the current tree tries to be valid.
/// to conserve space, the empty set is used to indicate the tree tries to be valid for all states.
unordered_set<state> valid_states;
};
result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_seed) {
const auto N = g.graph_size;
......@@ -46,11 +40,12 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
auto & root = ret.root;
auto & succession = ret.successor_cache;
// We'll use a queue to keep track of leaves we have to investigate;
// In some cases we cannot split, and have to wait for other parts of the
// tree. We keep track of how many times we did no work. If this is too
// much, there is no complete splitting tree.
queue<reference_wrapper<splitting_tree>> work;
queue<work_set> work_list;
size_t days_without_progress = 0;
// List of inputs, will be shuffled in case of randomizations
......@@ -58,38 +53,51 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
iota(begin(all_inputs), end(all_inputs), 0);
mt19937 generator(random_seed);
size_t current_order = 0;
bool split_in_current_order = false;
// Some lambda functions capturing some state, makes the code a bit easier :)
const auto add_push_new_block = [&work](list<list<state>> const & new_blocks, splitting_tree& boom) {
boom.children.assign(new_blocks.size(), splitting_tree(0, boom.depth + 1));
const auto add_push_new_block = [&work_list](list<list<state>> const & new_blocks, splitting_tree& boom,unordered_set<state> & valid_list) {
boom.children.push_back(vector<splitting_tree>());
boom.children.back().assign(new_blocks.size(), splitting_tree(0, boom.depth + 1));
size_t i = 0;
for (auto && b : new_blocks) {
boom.children[i++].states.assign(begin(b), end(b));
boom.children.back()[i++].states.assign(begin(b), end(b));
}
for (auto && c : boom.children) {
work.push(c);
for (auto & c : boom.children.back()) {
work_set new_work_set = work_set(c,valid_list);
work_list.push(new_work_set);
}
assert(boom.states.size() == accumulate(begin(boom.children), end(boom.children), 0ul,
assert(boom.states.size() == accumulate(begin(boom.children.back()), end(boom.children.back()), 0ul,
[](size_t l, const splitting_tree & r) {
return l + r.states.size();
}));
};
const auto is_valid = [N, opt, &g](list<list<state>> const & blocks, input symbol) {
const auto is_valid = [N, opt, &g](list<list<state>> const & blocks, input symbol, unordered_set<state> & valid_set) {
for (auto && block : blocks) {
const auto new_blocks = partition_(begin(block), end(block), [symbol, &g](state state) {
return apply(g, state, symbol).to;
}, N);
for (auto && new_block : new_blocks) {
if (new_block.size() != 1) return false;
if (new_block.size() != 1) {
if(valid_set.size()==0)
{
return false;
}
else
{
for(state s : new_block)
{
if(valid_set.count(s)>0)
return false;
}
}
}
}
}
return true;
};
//todo: check of dit nog werkt/waar het gebruikt wordt
const auto update_succession = [N, &succession](state s, state t, size_t depth) {
if (succession.size() < depth + 1)
succession.resize(depth + 1, vector<state>(N, state(-1)));
......@@ -97,17 +105,28 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
};
// We'll start with the root, obviously
work.push(root);
while (!work.empty()) {
splitting_tree & boom = work.front();
work.pop();
work_set starting_set(root);
work_list.push(starting_set);
//flag used to signal new possible splits on output.
bool split_on_output = true;
while (!work_list.empty()) {
splitting_tree & boom = work_list.front().work;
unordered_set<state> & valid_set = work_list.front().valid_states;
work_list.pop();
//the state for which the current tree prioritizes validity.
//if there is no preference then the first one in the list is chosen.
state valid_target =0;
if(valid_set.size()>0)
valid_target = *valid_set.begin();
const size_t depth = boom.depth;
if (boom.states.size() == 1) continue;
if (opt.randomized) shuffle(begin(all_inputs), end(all_inputs), generator);
if (!opt.assert_minimal_order || current_order == 0) {
//first try to split on just output.
//once you know have exausted all options, dont check for it again until new options appear.
if (split_on_output) {
// First try to split on output
for (input symbol : all_inputs) {
const auto new_blocks = partition_(
......@@ -122,17 +141,17 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
if (new_blocks.size() == 1) continue;
// not a valid split -> continue
if (opt.check_validity && !is_valid(new_blocks, symbol)) continue;
if (opt.check_validity && !is_valid(new_blocks, symbol,valid_set)) continue;
// a succesful split, update partition and add the children
boom.separator = {symbol};
add_push_new_block(new_blocks, boom);
boom.separators.push_back({symbol});
add_push_new_block(new_blocks, boom,valid_set);
goto has_split;
}
}
if (!opt.assert_minimal_order || current_order > 0) {
// Then try to split on state
for (input symbol : all_inputs) {
vector<bool> successor_states(N, false);
......@@ -142,16 +161,13 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
const auto & oboom = lca(root, [&successor_states](state state) -> bool {
return successor_states[state];
});
},valid_target);
// a leaf, hence not a split -> try other symbols
if (oboom.children.empty()) continue;
// If we want to enforce the right order, we should :D
if (opt.assert_minimal_order && oboom.separator.size() != current_order) continue;
// possibly a succesful split, construct the children
const vector<input> word = concat(vector<input>(1, symbol), oboom.separator);
const vector<input> word = concat(vector<input>(1, symbol), oboom.get_seperator(valid_target));
const auto new_blocks = partition_(
begin(boom.states),
end(boom.states), [word, depth, &g, &update_succession](state state) {
......@@ -161,35 +177,39 @@ result create_splitting_tree(const mealy & g, options opt, uint_fast32_t random_
}, Q);
// not a valid split -> continue
if (opt.check_validity && !is_valid(new_blocks, symbol)) continue;
if (opt.check_validity && !is_valid(new_blocks, symbol,valid_set)) continue;
assert(new_blocks.size() > 1);
// update partition and add the children
boom.separator = word;
add_push_new_block(new_blocks, boom);
boom.separators.push_back(word);
add_push_new_block(new_blocks, boom,valid_set);
goto has_split;
goto has_split;
}
}
// We tried all we could, but did not succeed => declare incompleteness.
if (days_without_progress++ >= work.size()) {
if (!split_in_current_order || !opt.assert_minimal_order) {
if (days_without_progress++ >= work_list.size()) {
if(split_on_output) {
//if we get here than we have check all possible inputs and none of them give a possible split on output.
//start checking for splits on state now instead.
split_on_output = false;
days_without_progress = 0;
}
else {
ret.is_complete = false;
return ret;
}
current_order++;
split_in_current_order = false;
}
work.push(boom);
work_list.push(work_set(boom,valid_set));
continue;
has_split:
split_in_current_order = true;
days_without_progress = 0;
days_without_progress = 0;
}
return ret;
......
#pragma once
#include "mealy.hpp"
#include <unordered_map>
#include <stdexcept>
/// \brief A splitting tree as defined in Lee & Yannakakis.
/// This is also known as a derivation tree (Knuutila). Both the Gill/Moore/Hopcroft-style and the
......@@ -9,42 +11,38 @@ struct splitting_tree {
splitting_tree(size_t N, size_t depth);
std::vector<state> states;
std::vector<splitting_tree> children;
word separator;
std::vector<std::vector<splitting_tree>> children;
std::vector<word> separators;
size_t depth = 0;
std::unordered_map<state,size_t> valid_map;
const std::vector<splitting_tree> & get_children(state s) const {
if(children.size()<1)
throw std::runtime_error("get_children called when there were no children");
else if(valid_map.size()<1 ||valid_map.count(s)<1)
return children[0];
else
return children[valid_map.at(s)];
}
const word & get_seperator(state s) const {
if (children.size() < 1)
throw std::runtime_error("get_seperator called when there were no seperators");
else if (valid_map.size() < 1 || valid_map.count(s) < 1)
return separators[0];
else
return separators[valid_map.at(s)];
}
};
/// \brief used to turn a readable splitting tree into a string.
/// Not intended for direct use, instead call tree.to_string()
struct readable_splitting_tree {
/// \brief constructor that presets the internal vectors to the correct sizes for filling with iterators.
readable_splitting_tree (size_t state_amount, size_t depth_value, size_t seperator_size, size_t children_amount):
states(state_amount),depth(depth_value),separator(seperator_size),children(children_amount){}
readable_splitting_tree(){}
std::vector<std::string> states;
std::vector<readable_splitting_tree> children;
std::vector<std::string> separator;
size_t depth = 0;
};
void rst_to_stream(readable_splitting_tree & tree, std::ostream & output, int ident = 0);
/* todo: check of dit gebruikt wordt
std::ostream& operator<<(std::ostream& os, const readable_splitting_tree& tree);
*/
readable_splitting_tree translate_splitting_tree(splitting_tree & tree, std::vector<std::string> & inputs, std::vector<std::string> & states);
/// \brief the generic lca implementation.
/// It uses \p store to store the relevant nodes (in some bottom up order), the last store is the
/// actual lowest common ancestor (but the other might be relevant as well). The function \p f is
/// the predicate on the states (returns true for the states we want to compute the lca of).
/// valid_target is used to select a set of children when a node contains multiple separators.
/// The target can only be one node at a time for complexity reasons.
template <typename Fun, typename Store>
size_t lca_impl(splitting_tree const & node, Fun && f, Store && store) {
size_t lca_impl(splitting_tree const & node, Fun && f, Store && store, state valid_target) {
static_assert(std::is_same<decltype(f(state(0))), bool>::value, "f should return a bool");
if (node.children.empty()) {
// if it is a leaf, we search for the states
......@@ -62,8 +60,8 @@ size_t lca_impl(splitting_tree const & node, Fun && f, Store && store) {
// we return this (it's the lca), if more children return a non-nil
// node, then we are the lca
size_t count = 0;
for (auto & c : node.children) {
auto inner_count = lca_impl(c, f, store);
for (const splitting_tree & c : node.get_children(valid_target)) {
auto inner_count = lca_impl(c, f, store,valid_target);
if (inner_count > 0) count++;
}
......@@ -77,18 +75,19 @@ size_t lca_impl(splitting_tree const & node, Fun && f, Store && store) {
}
/// \brief Find the lowest common ancestor of elements on which \p f returns true.
template <typename Fun> splitting_tree & lca(splitting_tree & root, Fun && f) {
template <typename Fun> splitting_tree & lca(splitting_tree & root, Fun && f,state valid_target) {
splitting_tree const * store = nullptr;
lca_impl(root, f, [&store](splitting_tree const & node) { store = &node; });
lca_impl(root, f, [&store](splitting_tree const & node) { store = &node; },valid_target);
return const_cast<splitting_tree &>(*store); // NOTE: this const_cast is safe
}
template <typename Fun> const splitting_tree & lca(const splitting_tree & root, Fun && f) {
template <typename Fun> const splitting_tree & lca(const splitting_tree & root, Fun && f,state valid_target) {
splitting_tree const * store = nullptr;
lca_impl(root, f, [&store](splitting_tree const & node) { store = &node; });
lca_impl(root, f, [&store](splitting_tree const & node) { store = &node; },valid_target);
return *store;
}
/*todo:check of dit gebruikt wordt.
/// \brief Find "all" lca's of elements on which \p f returns true.
/// This can be used to collect all the separating sequences for the subset of states.
template <typename Fun>
......@@ -98,7 +97,7 @@ std::vector<std::reference_wrapper<const splitting_tree>> multi_lca(const splitt
lca_impl(root, f, [&ret](splitting_tree const & node) { ret.emplace_back(node); });
return ret;
}
*/
/// \brief Structure contains options to alter the splitting tree creation.
/// \p check_validity checks whether the transition/output map is injective on the current set of
......@@ -106,6 +105,8 @@ std::vector<std::reference_wrapper<const splitting_tree>> multi_lca(const splitt
/// sequences. \p assert_minimal_order is used to produce minimal (pairwise) separating sequences.
/// \p cach_succesors is needed by the second step in the LY algorithm and \p randomized randomizes
/// the loops over the alphabet.
//todo: check hoeveel hiervan verwijdert kan worden
struct options {
bool check_validity;
bool assert_minimal_order;
......
......@@ -3,6 +3,7 @@
#include <reachability.hpp>
#include <read_mealy.hpp>
#include <splitting_tree.hpp>
#include "readable_splitting_tree.hpp"
#include <algorithm>
......@@ -51,6 +52,8 @@ static const char USAGE[] =
-o <filename> Output filename ('-' or don't specify for stdout)
)";
static const string output_directory = "../outputs/";
enum Mode { ALL, FIXED, RANDOM, WSET };
enum PrefixMode { MIN, LEXMIN, BUGGY, DFS };
enum SuffixMode { HSI, HADS, NOSUFFIX };
......@@ -67,8 +70,8 @@ struct main_options {
unsigned long rnd_length = 8; // in addition to k_max
unsigned long seed = 0; // 0 for unset/noise
string input_filename = "../examples/coffe_machine.dot";
string output_filename = "../outputs/default_output_file.txt";
string input_filename = "../examples/lee_yannakakis_distinguishable.dot";
string output_filename = "";
};
main_options parse_options(int argc, char ** argv) {
......@@ -113,7 +116,7 @@ main_options parse_options(int argc, char ** argv) {
opts.input_filename = optarg;
break;
case 'o': // output filename
opts.output_filename += optarg;
opts.output_filename = output_directory + optarg;
break;
case ':': // some option without argument
throw runtime_error(string("No argument given to option -") + char(optopt));
......@@ -128,6 +131,18 @@ main_options parse_options(int argc, char ** argv) {
exit(2);
}
if(opts.output_filename == "")
{
size_t start = opts.input_filename.find_last_of('/');
if(start == std::string::npos)
start = opts.input_filename.find_last_of('\\');
size_t end = opts.input_filename.find_last_of('.');
if(start >end-start || end == std::string::npos)
opts.output_filename = output_directory + "default_output.txt";
else
opts.output_filename = output_directory + opts.input_filename.substr(start,end-start) + "_output.txt";
}
return opts;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment