Adding signature based minimization of mealy machines
* spot/twaalgos/mealy_machine.cc: Here * tests/python/mealy.py: Adding tests
This commit is contained in:
parent
3da41aa9a1
commit
9669806cd0
2 changed files with 753 additions and 17 deletions
|
|
@ -20,6 +20,7 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include <spot/twaalgos/mealy_machine.hh>
|
#include <spot/twaalgos/mealy_machine.hh>
|
||||||
|
|
||||||
|
#include <queue>
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <list>
|
#include <list>
|
||||||
|
|
@ -51,6 +52,553 @@
|
||||||
# define dotimeprint while (0) std::cerr
|
# define dotimeprint while (0) std::cerr
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Anonymous for minimize_mealy_fast
|
||||||
|
using namespace spot;
|
||||||
|
|
||||||
|
// Used to get the signature of the state.
|
||||||
|
typedef std::vector<bdd> vector_state_bdd;
|
||||||
|
|
||||||
|
// Get the list of state for each class.
|
||||||
|
// Note: Use map as iter. are not invalidated by inserting new elements
|
||||||
|
typedef std::map<bdd, std::vector<unsigned>,
|
||||||
|
bdd_less_than> map_bdd_lstate;
|
||||||
|
|
||||||
|
// This part is just a copy of a part of simulation.cc only suitable for
|
||||||
|
// deterministic monitors.
|
||||||
|
class sig_calculator final
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
typedef std::unordered_map<bdd, bdd, bdd_hash> map_bdd_bdd;
|
||||||
|
int acc_vars;
|
||||||
|
acc_cond::mark_t all_inf_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
sig_calculator(twa_graph_ptr aut, bool implications) : a_(aut),
|
||||||
|
po_size_(0),
|
||||||
|
want_implications_(implications)
|
||||||
|
{
|
||||||
|
size_a_ = a_->num_states();
|
||||||
|
// Now, we have to get the bdd which will represent the
|
||||||
|
// class. We register one bdd by state, because in the worst
|
||||||
|
// case, |Class| == |State|.
|
||||||
|
unsigned set_num = a_->get_dict()
|
||||||
|
->register_anonymous_variables(size_a_, this);
|
||||||
|
|
||||||
|
bdd init = bdd_ithvar(set_num++);
|
||||||
|
|
||||||
|
used_var_.emplace_back(init);
|
||||||
|
|
||||||
|
// Initialize all classes to init.
|
||||||
|
previous_class_.resize(size_a_);
|
||||||
|
for (unsigned s = 0; s < size_a_; ++s)
|
||||||
|
previous_class_[s] = init;
|
||||||
|
for (unsigned i = set_num; i < set_num + size_a_ - 1; ++i)
|
||||||
|
free_var_.push(i);
|
||||||
|
|
||||||
|
relation_.reserve(size_a_);
|
||||||
|
relation_[init] = init;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reverse all the acceptance condition at the destruction of
|
||||||
|
// this object, because it occurs after the return of the
|
||||||
|
// function simulation.
|
||||||
|
virtual ~sig_calculator()
|
||||||
|
{
|
||||||
|
a_->get_dict()->unregister_all_my_variables(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the name of the classes.
|
||||||
|
void update_previous_class()
|
||||||
|
{
|
||||||
|
auto it_bdd = used_var_.begin();
|
||||||
|
|
||||||
|
// We run through the map bdd/list<state>, and we update
|
||||||
|
// the previous_class_ with the new data.
|
||||||
|
for (auto& p : sorted_classes_)
|
||||||
|
{
|
||||||
|
// If the signature of a state is bddfalse (no
|
||||||
|
// edges) the class of this state is bddfalse
|
||||||
|
// instead of an anonymous variable. It allows
|
||||||
|
// simplifications in the signature by removing a
|
||||||
|
// edge which has as a destination a state with
|
||||||
|
// no outgoing edge.
|
||||||
|
if (p->first == bddfalse)
|
||||||
|
for (unsigned s : p->second)
|
||||||
|
previous_class_[s] = bddfalse;
|
||||||
|
else
|
||||||
|
for (unsigned s : p->second)
|
||||||
|
previous_class_[s] = *it_bdd;
|
||||||
|
++it_bdd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main_loop()
|
||||||
|
{
|
||||||
|
unsigned int nb_partition_before = 0;
|
||||||
|
unsigned int nb_po_before = po_size_ - 1;
|
||||||
|
|
||||||
|
while (nb_partition_before != bdd_lstate_.size()
|
||||||
|
|| nb_po_before != po_size_)
|
||||||
|
{
|
||||||
|
update_previous_class();
|
||||||
|
nb_partition_before = bdd_lstate_.size();
|
||||||
|
nb_po_before = po_size_;
|
||||||
|
po_size_ = 0;
|
||||||
|
update_sig();
|
||||||
|
go_to_next_it();
|
||||||
|
}
|
||||||
|
update_previous_class();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Take a state and compute its signature.
|
||||||
|
bdd compute_sig(unsigned src)
|
||||||
|
{
|
||||||
|
bdd res = bddfalse;
|
||||||
|
|
||||||
|
for (auto& t : a_->out(src))
|
||||||
|
{
|
||||||
|
// to_add is a conjunction of the acceptance condition,
|
||||||
|
// the label of the edge and the class of the
|
||||||
|
// destination and all the class it implies.
|
||||||
|
bdd to_add = t.cond & relation_[previous_class_[t.dst]];
|
||||||
|
|
||||||
|
res |= to_add;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void update_sig()
|
||||||
|
{
|
||||||
|
bdd_lstate_.clear();
|
||||||
|
sorted_classes_.clear();
|
||||||
|
for (unsigned s = 0; s < size_a_; ++s)
|
||||||
|
{
|
||||||
|
bdd sig = compute_sig(s);
|
||||||
|
auto p = bdd_lstate_.emplace(std::piecewise_construct,
|
||||||
|
std::forward_as_tuple(sig),
|
||||||
|
std::forward_as_tuple(1, s));
|
||||||
|
if (p.second)
|
||||||
|
sorted_classes_.emplace_back(p.first);
|
||||||
|
else
|
||||||
|
p.first->second.emplace_back(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method renames the color set, updates the partial order.
|
||||||
|
void go_to_next_it()
|
||||||
|
{
|
||||||
|
int nb_new_color = bdd_lstate_.size() - used_var_.size();
|
||||||
|
|
||||||
|
// If we have created more partitions, we need to use more
|
||||||
|
// variables.
|
||||||
|
for (int i = 0; i < nb_new_color; ++i)
|
||||||
|
{
|
||||||
|
assert(!free_var_.empty());
|
||||||
|
used_var_.emplace_back(bdd_ithvar(free_var_.front()));
|
||||||
|
free_var_.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have reduced the number of partition, we 'free' them
|
||||||
|
// in the free_var_ list.
|
||||||
|
for (int i = 0; i > nb_new_color; --i)
|
||||||
|
{
|
||||||
|
assert(!used_var_.empty());
|
||||||
|
free_var_.push(bdd_var(used_var_.front()));
|
||||||
|
used_var_.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
assert((bdd_lstate_.size() == used_var_.size())
|
||||||
|
|| (bdd_lstate_.find(bddfalse) != bdd_lstate_.end()
|
||||||
|
&& bdd_lstate_.size() == used_var_.size() + 1));
|
||||||
|
|
||||||
|
// This vector links the tuple "C^(i-1), N^(i-1)" to the
|
||||||
|
// new class coloring for the next iteration.
|
||||||
|
std::vector<std::pair<bdd, bdd>> now_to_next;
|
||||||
|
unsigned sz = bdd_lstate_.size();
|
||||||
|
now_to_next.reserve(sz);
|
||||||
|
|
||||||
|
auto it_bdd = used_var_.begin();
|
||||||
|
|
||||||
|
for (auto& p : sorted_classes_)
|
||||||
|
{
|
||||||
|
// If the signature of a state is bddfalse (no edges) the
|
||||||
|
// class of this state is bddfalse instead of an anonymous
|
||||||
|
// variable. It allows simplifications in the signature by
|
||||||
|
// removing an edge which has as a destination a state
|
||||||
|
// with no outgoing edge.
|
||||||
|
bdd acc = bddfalse;
|
||||||
|
if (p->first != bddfalse)
|
||||||
|
acc = *it_bdd;
|
||||||
|
now_to_next.emplace_back(p->first, acc);
|
||||||
|
++it_bdd;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the partial order.
|
||||||
|
|
||||||
|
// This loop follows the pattern given by the paper.
|
||||||
|
// foreach class do
|
||||||
|
// | foreach class do
|
||||||
|
// | | update po if needed
|
||||||
|
// | od
|
||||||
|
// od
|
||||||
|
|
||||||
|
for (unsigned n = 0; n < sz; ++n)
|
||||||
|
{
|
||||||
|
bdd n_sig = now_to_next[n].first;
|
||||||
|
bdd n_class = now_to_next[n].second;
|
||||||
|
if (want_implications_)
|
||||||
|
for (unsigned m = 0; m < sz; ++m)
|
||||||
|
{
|
||||||
|
if (n == m)
|
||||||
|
continue;
|
||||||
|
if (bdd_implies(n_sig, now_to_next[m].first))
|
||||||
|
{
|
||||||
|
n_class &= now_to_next[m].second;
|
||||||
|
++po_size_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relation_[now_to_next[n].second] = n_class;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The list of states for each class at the current_iteration.
|
||||||
|
// Computed in `update_sig'.
|
||||||
|
map_bdd_lstate bdd_lstate_;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// The automaton which is reduced.
|
||||||
|
twa_graph_ptr a_;
|
||||||
|
|
||||||
|
// Implications between classes.
|
||||||
|
map_bdd_bdd relation_;
|
||||||
|
|
||||||
|
// Represent the class of each state at the previous iteration.
|
||||||
|
vector_state_bdd previous_class_;
|
||||||
|
|
||||||
|
// The above map, sorted by states number instead of BDD
|
||||||
|
// identifier to avoid non-determinism while iterating over all
|
||||||
|
// states.
|
||||||
|
std::vector<map_bdd_lstate::const_iterator> sorted_classes_;
|
||||||
|
|
||||||
|
// The queue of free bdd. They will be used as the identifier
|
||||||
|
// for the class.
|
||||||
|
std::queue<int> free_var_;
|
||||||
|
|
||||||
|
// The list of used bdd. They are in used as identifier for class.
|
||||||
|
std::deque<bdd> used_var_;
|
||||||
|
|
||||||
|
// Size of the automaton.
|
||||||
|
unsigned int size_a_;
|
||||||
|
|
||||||
|
// Used to know when there is no evolution in the partial order.
|
||||||
|
unsigned int po_size_;
|
||||||
|
|
||||||
|
// Whether to compute implications between classes. This is costly
|
||||||
|
// and useless when we want to recognize the same language.
|
||||||
|
bool want_implications_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An acyclic digraph such that there is an edge q1 -> q2 if
|
||||||
|
// q1.label_ ⇒ q2.label_
|
||||||
|
class bdd_digraph
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
bdd label_;
|
||||||
|
unsigned state_;
|
||||||
|
std::vector<std::shared_ptr<bdd_digraph>> children_;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bdd_digraph() : label_(bddtrue), state_(-1U) {}
|
||||||
|
|
||||||
|
bdd_digraph(bdd label, unsigned state) : label_(label), state_(state) {}
|
||||||
|
|
||||||
|
void
|
||||||
|
all_children_aux_(std::set<std::shared_ptr<bdd_digraph>>& res)
|
||||||
|
{
|
||||||
|
for (auto c : children_)
|
||||||
|
if (res.insert(c).second)
|
||||||
|
c->all_children_aux_(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::shared_ptr<bdd_digraph>>
|
||||||
|
all_children()
|
||||||
|
{
|
||||||
|
std::set<std::shared_ptr<bdd_digraph>> res;
|
||||||
|
all_children_aux_(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
add_aux_(std::shared_ptr<bdd_digraph>& new_node, std::vector<bool>& done)
|
||||||
|
{
|
||||||
|
// Avoid doing twice the same state
|
||||||
|
if (state_ != -1U)
|
||||||
|
done[state_] = true;
|
||||||
|
for (auto& ch : children_)
|
||||||
|
{
|
||||||
|
if (done[ch->state_])
|
||||||
|
continue;
|
||||||
|
if (bdd_implies(new_node->label_, ch->label_))
|
||||||
|
ch->add_aux_(new_node, done);
|
||||||
|
else if (bdd_implies(ch->label_, new_node->label_))
|
||||||
|
{
|
||||||
|
auto ch_nodes = ch->all_children();
|
||||||
|
new_node->children_.push_back(ch);
|
||||||
|
for (auto& x : ch_nodes)
|
||||||
|
new_node->children_.push_back(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(bdd_implies(new_node->label_, label_));
|
||||||
|
children_.push_back(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
add(std::shared_ptr<bdd_digraph>& new_node, bool rec,
|
||||||
|
unsigned max_state)
|
||||||
|
{
|
||||||
|
if (new_node->label_ == bddtrue)
|
||||||
|
{
|
||||||
|
assert(label_ == bddtrue);
|
||||||
|
state_ = new_node->state_;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rec)
|
||||||
|
{
|
||||||
|
std::vector<bool> done(max_state, false);
|
||||||
|
add_aux_(new_node, done);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
children_.push_back(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned
|
||||||
|
flatten_aux(std::unordered_map<bdd, unsigned, spot::bdd_hash>& res)
|
||||||
|
{
|
||||||
|
if (children_.empty())
|
||||||
|
{
|
||||||
|
res.insert({label_, state_});
|
||||||
|
return state_;
|
||||||
|
}
|
||||||
|
auto ch_size = children_.size();
|
||||||
|
unsigned pos = ch_size - 1;
|
||||||
|
auto my_repr = children_[pos]->flatten_aux(res);
|
||||||
|
res.insert({label_, my_repr});
|
||||||
|
for (unsigned i = 0; i < ch_size; ++i)
|
||||||
|
{
|
||||||
|
if (i == pos)
|
||||||
|
continue;
|
||||||
|
children_[i]->flatten_aux(res);
|
||||||
|
}
|
||||||
|
return my_repr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<bdd, unsigned, spot::bdd_hash>
|
||||||
|
flatten()
|
||||||
|
{
|
||||||
|
std::unordered_map<bdd, unsigned, spot::bdd_hash> res;
|
||||||
|
flatten_aux(res);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transforms children_ such that the child with the higher use_count() is
|
||||||
|
// at the end.
|
||||||
|
void
|
||||||
|
sort_nodes()
|
||||||
|
{
|
||||||
|
if (!children_.empty())
|
||||||
|
{
|
||||||
|
auto max_pos = std::max_element(children_.begin(), children_.end(),
|
||||||
|
[](const std::shared_ptr<bdd_digraph>& n1,
|
||||||
|
const std::shared_ptr<bdd_digraph>& n2)
|
||||||
|
{
|
||||||
|
return n1.use_count() < n2.use_count();
|
||||||
|
});
|
||||||
|
std::iter_swap(max_pos, children_.end() - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Associate to a state a representative. The first value of the result
|
||||||
|
// is -1U if ∀i repr[i] = i
|
||||||
|
std::vector<unsigned>
|
||||||
|
get_repres(twa_graph_ptr& a, bool rec)
|
||||||
|
{
|
||||||
|
const auto a_num_states = a->num_states();
|
||||||
|
|
||||||
|
std::vector<unsigned> repr(a_num_states);
|
||||||
|
bdd_digraph graph;
|
||||||
|
std::vector<bdd> signatures(a_num_states);
|
||||||
|
sig_calculator red(a, rec);
|
||||||
|
red.main_loop();
|
||||||
|
if (!rec && red.bdd_lstate_.size() == a_num_states)
|
||||||
|
{
|
||||||
|
repr[0] = -1U;
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
for (auto& [sig, states] : red.bdd_lstate_)
|
||||||
|
{
|
||||||
|
assert(!states.empty());
|
||||||
|
bool in_tree = false;
|
||||||
|
for (auto state : states)
|
||||||
|
{
|
||||||
|
signatures[state] = sig;
|
||||||
|
// If it is not the first iteration, le BDD is already in the graph.
|
||||||
|
if (!in_tree)
|
||||||
|
{
|
||||||
|
in_tree = true;
|
||||||
|
auto new_node =
|
||||||
|
std::make_shared<bdd_digraph>(bdd_digraph(sig, state));
|
||||||
|
graph.add(new_node, rec, a_num_states);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
graph.sort_nodes();
|
||||||
|
auto repr_map = graph.flatten();
|
||||||
|
|
||||||
|
bool is_useless_map = true;
|
||||||
|
for (unsigned i = 0; i < a_num_states; ++i)
|
||||||
|
{
|
||||||
|
repr[i] = repr_map[signatures[i]];
|
||||||
|
is_useless_map &= (repr[i] == i);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_useless_map)
|
||||||
|
{
|
||||||
|
repr[0] = -1U;
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
return repr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace spot
|
||||||
|
{
|
||||||
|
twa_graph_ptr minimize_mealy_fast(const const_twa_graph_ptr& mm,
|
||||||
|
bool use_inclusion)
|
||||||
|
{
|
||||||
|
bool orig_is_split = true;
|
||||||
|
bdd* outsptr = nullptr;
|
||||||
|
bdd outs = bddfalse;
|
||||||
|
|
||||||
|
twa_graph_ptr mmc;
|
||||||
|
if (mm->get_named_prop<std::vector<bool>>("state-player") != nullptr)
|
||||||
|
{
|
||||||
|
outsptr = mm->get_named_prop<bdd>("synthesis-outputs");
|
||||||
|
assert(outsptr && "If the original strat is split, "
|
||||||
|
"we need \"synthesis-outputs\".");
|
||||||
|
outs = *outsptr;
|
||||||
|
mmc = unsplit_2step(mm);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mmc = make_twa_graph(mm, twa::prop_set::all());
|
||||||
|
mmc->copy_ap_of(mm);
|
||||||
|
mmc->copy_acceptance_of(mm);
|
||||||
|
orig_is_split = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
minimize_mealy_fast_here(mmc, use_inclusion);
|
||||||
|
|
||||||
|
// Split if the initial aut was split
|
||||||
|
if (orig_is_split)
|
||||||
|
{
|
||||||
|
bdd ins = bddtrue;
|
||||||
|
for (const auto& ap : mmc->ap())
|
||||||
|
if (!bdd_implies(outs,
|
||||||
|
bdd_ithvar(mmc->register_ap(ap))))
|
||||||
|
ins &= bdd_ithvar(mmc->register_ap(ap));
|
||||||
|
mmc = split_2step(mmc, ins, outs, false, false);
|
||||||
|
bool orig_init_player =
|
||||||
|
mm->get_named_prop<std::vector<bool>>("state-player")
|
||||||
|
->at(mm->get_init_state_number());
|
||||||
|
alternate_players(mmc, orig_init_player, false);
|
||||||
|
}
|
||||||
|
// Try to set outputs
|
||||||
|
if (outsptr)
|
||||||
|
mmc->set_named_prop("synthesis-outputs", new bdd(outs));
|
||||||
|
|
||||||
|
return mmc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void minimize_mealy_fast_here(twa_graph_ptr& mm, bool use_inclusion)
|
||||||
|
{
|
||||||
|
|
||||||
|
bool orig_is_split = false;
|
||||||
|
bdd* outsptr = nullptr;
|
||||||
|
bdd outs = bddfalse;
|
||||||
|
|
||||||
|
auto sp_ptr = mm->get_named_prop<std::vector<bool>>("state-player");
|
||||||
|
|
||||||
|
bool init_player = sp_ptr ? sp_ptr->at(mm->get_init_state_number())
|
||||||
|
: false;
|
||||||
|
|
||||||
|
// Only consider infinite runs
|
||||||
|
mm->purge_dead_states();
|
||||||
|
|
||||||
|
if (mm->get_named_prop<std::vector<bool>>("state-player") != nullptr)
|
||||||
|
{
|
||||||
|
// Check if done
|
||||||
|
if (std::count(sp_ptr->begin(), sp_ptr->end(), false) == 1)
|
||||||
|
return;
|
||||||
|
outsptr = mm->get_named_prop<bdd>("synthesis-outputs");
|
||||||
|
assert(outsptr && "If the original strat is split, "
|
||||||
|
"we need \"synthesis-outputs\".");
|
||||||
|
outs = *outsptr;
|
||||||
|
init_player =
|
||||||
|
mm->get_named_prop<std::vector<bool>>("state-player")->at(
|
||||||
|
mm->get_init_state_number());
|
||||||
|
mm = unsplit_2step(mm);
|
||||||
|
orig_is_split = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
// Check if done
|
||||||
|
if (mm->num_states() == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto repr = get_repres(mm, use_inclusion);
|
||||||
|
if (repr[0] == -1U)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Change the destination of transitions using a DFT to avoid useless
|
||||||
|
// modifications.
|
||||||
|
auto init = repr[mm->get_init_state_number()];
|
||||||
|
mm->set_init_state(init);
|
||||||
|
std::stack<unsigned> todo;
|
||||||
|
std::vector<bool> done(mm->num_states(), false);
|
||||||
|
todo.emplace(init);
|
||||||
|
while (!todo.empty())
|
||||||
|
{
|
||||||
|
auto current = todo.top();
|
||||||
|
todo.pop();
|
||||||
|
done[current] = true;
|
||||||
|
for (auto& e : mm->out(current))
|
||||||
|
{
|
||||||
|
auto repr_dst = repr[e.dst];
|
||||||
|
e.dst = repr_dst;
|
||||||
|
if (!done[repr_dst])
|
||||||
|
todo.emplace(repr_dst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mm->purge_unreachable_states();
|
||||||
|
if (orig_is_split)
|
||||||
|
{
|
||||||
|
bdd ins = bddtrue;
|
||||||
|
for (const auto& ap : mm->ap())
|
||||||
|
if (!bdd_implies(outs,
|
||||||
|
bdd_ithvar(mm->register_ap(ap))))
|
||||||
|
ins &= bdd_ithvar(mm->register_ap(ap));
|
||||||
|
mm = split_2step(mm, ins, outs, false, false);
|
||||||
|
alternate_players(mm, init_player, false);
|
||||||
|
mm->set_named_prop("synthesis-outputs", new bdd(outs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Anonymous for mealy_min
|
// Anonymous for mealy_min
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
|
@ -3058,23 +3606,6 @@ namespace spot
|
||||||
|
|
||||||
namespace spot
|
namespace spot
|
||||||
{
|
{
|
||||||
|
|
||||||
void minimize_mealy_fast_here(twa_graph_ptr& mm,
|
|
||||||
bool use_inclusion)
|
|
||||||
{
|
|
||||||
(void) mm;
|
|
||||||
(void) use_inclusion;
|
|
||||||
}
|
|
||||||
|
|
||||||
twa_graph_ptr minimize_mealy_fast(const const_twa_graph_ptr& mm,
|
|
||||||
bool use_inclusion)
|
|
||||||
{
|
|
||||||
// Dummy for now
|
|
||||||
(void) use_inclusion;
|
|
||||||
return make_twa_graph(mm, twa::prop_set::all());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool is_mealy_specialization(const_twa_graph_ptr left,
|
bool is_mealy_specialization(const_twa_graph_ptr left,
|
||||||
const_twa_graph_ptr right,
|
const_twa_graph_ptr right,
|
||||||
bool verbose)
|
bool verbose)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@
|
||||||
|
|
||||||
import spot, buddy
|
import spot, buddy
|
||||||
|
|
||||||
|
# Testing Sat-based approach
|
||||||
|
|
||||||
# Empty bdd intersection bug test
|
# Empty bdd intersection bug test
|
||||||
# See issue #472
|
# See issue #472
|
||||||
true = buddy.bddtrue
|
true = buddy.bddtrue
|
||||||
|
|
@ -380,7 +382,210 @@ for (mealy_str, nenv_min) in test_auts:
|
||||||
assert(spot.is_mealy_specialization(mealy, mealy_min_us_s, True))
|
assert(spot.is_mealy_specialization(mealy, mealy_min_us_s, True))
|
||||||
|
|
||||||
|
|
||||||
|
# Testing bisimulation (with output assignment)
|
||||||
|
|
||||||
|
|
||||||
|
# Syntcomp: Alarm_2d1010f8.tlsf
|
||||||
|
aut = spot.automaton("""HOA: v1
|
||||||
|
States: 17
|
||||||
|
Start: 0
|
||||||
|
AP: 6 "u02alarm29control02alarm29control"
|
||||||
|
"u02alarm29control0f1d2alarm29turn2off1b"
|
||||||
|
"u02alarm29control0f1d2alarm29turn2on1b" "p0p0off02alarm29intent"
|
||||||
|
"p0p0on02alarm29intent" "p0b02alarm29alarm"
|
||||||
|
acc-name: all
|
||||||
|
Acceptance: 0 t
|
||||||
|
properties: trans-labels explicit-labels state-acc deterministic
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&1&!2&!3&!4 | !0&1&!2&3&4 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4] 1
|
||||||
|
[!0&!1&2&!3&4&!5 | !0&!1&2&3&!4&5] 2
|
||||||
|
[!0&!1&2&!3&4&5 | !0&!1&2&3&!4&!5] 3
|
||||||
|
State: 1
|
||||||
|
[!0&!1&2 | !0&1&!2 | 0&!1&!2] 1
|
||||||
|
State: 2
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&1&!2&!3&!4 | !0&1&!2&3&4 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4] 4
|
||||||
|
[!0&!1&2&!3&4&!5] 5
|
||||||
|
[!0&!1&2&3&!4&!5] 6
|
||||||
|
[!0&!1&2&!3&4&5] 7
|
||||||
|
[!0&1&!2&3&!4&5] 8
|
||||||
|
State: 3
|
||||||
|
[!0&!1&2&3&!4&5] 2
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&1&!2&!3&!4 | !0&1&!2&3&4 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4] 4
|
||||||
|
[!0&!1&2&!3&4&!5] 5
|
||||||
|
[!0&!1&2&3&!4&!5] 6
|
||||||
|
[!0&!1&2&!3&4&5] 7
|
||||||
|
State: 4
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&!1&2&!4&5 | !0&1&!2&!3&!4 | !0&1&!2&3&4
|
||||||
|
| !0&1&!2&!4&5 | 0&!1&!2&!3&!4 | 0&!1&!2&3&4 | 0&!1&!2&!4&5] 4
|
||||||
|
[!0&!1&2&!3&4 | !0&1&!2&!3&4 | 0&!1&!2&!3&4] 9
|
||||||
|
[!0&!1&2&3&!4&!5 | !0&1&!2&3&!4&!5 | 0&!1&!2&3&!4&!5] 10
|
||||||
|
State: 5
|
||||||
|
[!0&!1&2&!3&!4 | !0&1&!2&!3&!4 | 0&!1&!2&!3&!4] 4
|
||||||
|
[!0&!1&2&!3&4] 5
|
||||||
|
[!0&!1&2&3&4 | !0&1&!2&3&4 | 0&!1&!2&3&4] 9
|
||||||
|
[!0&!1&2&3&!4] 11
|
||||||
|
State: 6
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4&5 | !0&1&!2&!3&!4 | !0&1&!2&3&4&5 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4&5] 4
|
||||||
|
[!0&!1&2&!3&4] 5
|
||||||
|
[!0&!1&2&3&!4&!5] 6
|
||||||
|
[!0&!1&2&3&!4&5] 11
|
||||||
|
[!0&!1&2&3&4&!5 | !0&1&!2&3&4&!5 | 0&!1&!2&3&4&!5] 10
|
||||||
|
State: 7
|
||||||
|
[!0&!1&2&3&!4&5] 2
|
||||||
|
[!0&!1&2&!3&!4 | !0&1&!2&!3&!4 | 0&!1&!2&!3&!4] 4
|
||||||
|
[!0&!1&2&!3&4&!5] 5
|
||||||
|
[!0&!1&2&!3&4&5] 7
|
||||||
|
[!0&!1&2&3&4&!5 | !0&1&!2&3&4&!5 | 0&!1&!2&3&4&!5] 9
|
||||||
|
[!0&!1&2&3&!4&!5] 11
|
||||||
|
[!0&!1&2&3&4&5 | !0&1&!2&3&4&5 | 0&!1&!2&3&4&5] 12
|
||||||
|
State: 8
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&1&!2&!3&!4 | !0&1&!2&3&4 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4] 4
|
||||||
|
[!0&!1&2&3&!4&5] 11
|
||||||
|
[!0&!1&2&!3&4&5] 13
|
||||||
|
[!0&!1&2&!3&4&!5] 14
|
||||||
|
[!0&1&!2&3&!4&!5] 15
|
||||||
|
State: 9
|
||||||
|
[!0&!1&2&!4 | !0&1&!2&!4 | 0&!1&!2&!4] 4
|
||||||
|
[!0&!1&2&4 | !0&1&!2&4 | 0&!1&!2&4] 9
|
||||||
|
State: 10
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&5 | !0&1&!2&!3&!4 | !0&1&!2&3&5 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&5] 4
|
||||||
|
[!0&!1&2&!3&4 | !0&1&!2&!3&4 | 0&!1&!2&!3&4] 9
|
||||||
|
[!0&!1&2&3&!5 | !0&1&!2&3&!5 | 0&!1&!2&3&!5] 10
|
||||||
|
State: 11
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4 | !0&1&!2&!3&!4 | !0&1&!2&3&4 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4] 4
|
||||||
|
[!0&!1&2&!3&4] 5
|
||||||
|
[!0&!1&2&3&!4&!5] 6
|
||||||
|
[!0&!1&2&3&!4&5] 11
|
||||||
|
State: 12
|
||||||
|
[!0&!1&2&!4 | !0&1&!2&!4 | 0&!1&!2&!4] 4
|
||||||
|
[!0&!1&2&4&!5 | !0&1&!2&4&!5 | 0&!1&!2&4&!5] 9
|
||||||
|
[!0&!1&2&4&5 | !0&1&!2&4&5 | 0&!1&!2&4&5] 12
|
||||||
|
State: 13
|
||||||
|
[!0&!1&2&!3&!4 | !0&1&!2&!3&!4 | 0&!1&!2&!3&!4] 4
|
||||||
|
[!0&!1&2&!3&4&!5] 5
|
||||||
|
[!0&!1&2&3&4&!5 | !0&1&!2&3&4&!5 | 0&!1&!2&3&4&!5] 9
|
||||||
|
[!0&!1&2&3&!4] 11
|
||||||
|
[!0&!1&2&3&4&5 | !0&1&!2&3&4&5 | 0&!1&!2&3&4&5] 12
|
||||||
|
[!0&!1&2&!3&4&5] 13
|
||||||
|
State: 14
|
||||||
|
[!0&!1&2&3&!4&5] 2
|
||||||
|
[!0&!1&2&!3&!4 | !0&1&!2&!3&!4 | 0&!1&!2&!3&!4] 4
|
||||||
|
[!0&!1&2&!3&4&!5] 5
|
||||||
|
[!0&!1&2&!3&4&5] 7
|
||||||
|
[!0&!1&2&3&4 | !0&1&!2&3&4 | 0&!1&!2&3&4] 9
|
||||||
|
[!0&!1&2&3&!4&!5] 11
|
||||||
|
State: 15
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&4&5 | !0&1&!2&!3&!4 | !0&1&!2&3&4&5 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&4&5] 4
|
||||||
|
[!0&!1&2&!3&4&5] 5
|
||||||
|
[!0&!1&2&3&!4&5] 11
|
||||||
|
[!0&!1&2&!3&4&!5] 14
|
||||||
|
[!0&1&!2&3&!4&!5] 15
|
||||||
|
[!0&!1&2&3&4&!5 | !0&1&!2&3&4&!5 | 0&!1&!2&3&4&!5] 16
|
||||||
|
State: 16
|
||||||
|
[!0&!1&2&!3&!4 | !0&!1&2&3&5 | !0&1&!2&!3&!4 | !0&1&!2&3&5 | 0&!1&!2&!3&!4
|
||||||
|
| 0&!1&!2&3&5] 4
|
||||||
|
[!0&!1&2&!3&4 | !0&1&!2&!3&4 | 0&!1&!2&!3&4] 9
|
||||||
|
[!0&!1&2&3&!5 | !0&1&!2&3&!5 | 0&!1&!2&3&!5] 16
|
||||||
|
--END--""")
|
||||||
|
|
||||||
|
# Build an equivalent deterministic monitor
|
||||||
|
min_equiv = spot.minimize_mealy_fast(aut, False)
|
||||||
|
assert min_equiv.num_states() == 6
|
||||||
|
assert spot.are_equivalent(min_equiv, aut)
|
||||||
|
|
||||||
|
# Build an automaton that recognizes a subset of the language of the original
|
||||||
|
# automaton
|
||||||
|
min_sub = spot.minimize_mealy_fast(aut, True)
|
||||||
|
assert min_sub.num_states() == 5
|
||||||
|
prod = spot.product(spot.complement(aut), min_sub)
|
||||||
|
assert spot.generic_emptiness_check(prod)
|
||||||
|
|
||||||
|
aut = spot.automaton("""
|
||||||
|
HOA: v1
|
||||||
|
States: 4
|
||||||
|
Start: 0
|
||||||
|
AP: 2 "a" "b"
|
||||||
|
acc-name: all
|
||||||
|
Acceptance: 0 t
|
||||||
|
properties: trans-labels explicit-labels state-acc deterministic
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[!0&!1] 1
|
||||||
|
[!0&1] 2
|
||||||
|
[0] 3
|
||||||
|
State: 1
|
||||||
|
[0] 1
|
||||||
|
State: 2
|
||||||
|
[1] 2
|
||||||
|
State: 3
|
||||||
|
[0&1] 3
|
||||||
|
--END--
|
||||||
|
""")
|
||||||
|
|
||||||
|
exp = """HOA: v1
|
||||||
|
States: 1
|
||||||
|
Start: 0
|
||||||
|
AP: 2 "a" "b"
|
||||||
|
acc-name: all
|
||||||
|
Acceptance: 0 t
|
||||||
|
properties: trans-labels explicit-labels state-acc deterministic
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[0&1] 0
|
||||||
|
--END--"""
|
||||||
|
|
||||||
|
# An example that shows that we should not build a tree when we use inclusion.
|
||||||
|
res = spot.minimize_mealy_fast(aut, True)
|
||||||
|
assert res.to_str() == exp
|
||||||
|
|
||||||
|
aut = spot.automaton("""
|
||||||
|
HOA: v1
|
||||||
|
States: 4
|
||||||
|
Start: 0
|
||||||
|
AP: 2 "a" "b"
|
||||||
|
acc-name: all
|
||||||
|
Acceptance: 0 t
|
||||||
|
properties: trans-labels explicit-labels state-acc
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[!0&!1] 1
|
||||||
|
[!0&1] 2
|
||||||
|
[0&!1] 3
|
||||||
|
State: 1
|
||||||
|
[0] 1
|
||||||
|
State: 2
|
||||||
|
[1] 2
|
||||||
|
State: 3
|
||||||
|
[0&1] 3
|
||||||
|
--END--
|
||||||
|
""")
|
||||||
|
|
||||||
|
exp = """HOA: v1
|
||||||
|
States: 2
|
||||||
|
Start: 0
|
||||||
|
AP: 2 "a" "b"
|
||||||
|
acc-name: all
|
||||||
|
Acceptance: 0 t
|
||||||
|
properties: trans-labels explicit-labels state-acc deterministic
|
||||||
|
--BODY--
|
||||||
|
State: 0
|
||||||
|
[!0&!1] 1
|
||||||
|
[!0&1] 1
|
||||||
|
[0&!1] 1
|
||||||
|
State: 1
|
||||||
|
[0&1] 1
|
||||||
|
--END--"""
|
||||||
|
|
||||||
|
res = spot.minimize_mealy_fast(aut, True)
|
||||||
|
assert res.to_str() == exp
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue